Certificate Expired Warning Dialog Appears

Symptom

When loading EditLive! for the first time in a browser session, a security warning dialog appears stating that the certificate has expired. Note that this is slightly different to the initial security warning dialog which states that the certificate is valid - that’s normal and users should choose to “Always Accept” the certificate to prevent it reappearing.

Cause

For security reasons, the EditLive! applet is signed with a digital certificate that verifies that the applet is provided by Ephox and that it hasn’t been modified maliciously. This lets users know that it is safe to run the applet. The digital certificates used to do this are issued by a trusted issuer who verifies that Ephox is a legitimate company, however the certificate can only be issued for a specific period of time - usually two years.

The security warning appears once the digital certificate used to sign the editlivejava.jar file has expired.

Solution

The simplest solution is to update to the latest version of EditLive! which will be signed with a valid certificate (we renew the certificate some time before it expires to help avoid this issue).  Alternatively, users can simply choose to always accept the certificate again and the warning will not reappear.

New Plugin: Paste As Code

Two of our engineers, Andy and Rob have put their heads together and come up with a new plugin to help sites like LiveWorks! that paste code snippets. It provides a “Paste As Code…” menu item that neatly formats the code on the clipboard and marks it up with class names so it’s easy to apply syntax highlighting. A sample stylesheet is included with the plugin.

So in the future you should see much clearer and easier to read code snippets on LiveWorks!

Uninstalling EditLive! for IBM Web Content Management

While we hope you never need or want to, if you ever decide to uninstall EditLive! and go back to using the default editor within IBM’s Web Content Management system (IWWCM), simply follow the steps below. If you’ve decided not to use EditLive! anymore we’d appreciate it if you could drop us a note to let us know why as it will help us to improve our products in the future.

  1. In the IWWCM Authoring interface, click the Configure link.
  2. Expand the Rich Text section. Remove the reference to EditLiveJavaEditor.jsp in the provided text field (so that it is blank). Click OK. The ODC Editor is now the default editor for new rich text fields.
  3. Select your desired Authoring template. Once open, click the Edit button.
  4. Click the Default Content Settings link. Notice that EditLive! will still appear for each registered rich text field in the Authoring template.
  5. For each rich text field element open the properties of the rich text field.
  6. Where the EditLiveJavaEditor.jsp file is referenced, remove the reference to EditLiveJavaEditor.jsp so that the text field is blank.
  7. Save the Authoring template. Each content item which uses this Authoring template will represent Rich Text fields using the IBM editor.
  8. Perform steps 3 to 8 for all the authoring templates where you no longer wish to use EditLive! for IWWCM.

 

Solving Extremely Slow Load Times

Symptom

Loading EditLive! takes 5 minutes or more. The loading progress indicator may progress but extremely slowly or the browser may appear to have hung.

Cause

The delay in loading is triggered when the editlivejava.jar file is served without a “Last-Modified” HTTP header, preventing the Java plugin from caching the jar. Due to this, every time a new class is initialized the entire jar file is downloaded again. Even on fast networks or local access this causes substantial delays.

In all known cases this has been caused by a web server or proxy misconfiguration. Either the web server is not including a Last-Modified header at all or a proxy server is stripping it out. In particular this issue has been seen with the Vignette Trusted Authentication Service (TAS) and Microsoft ISA Proxy but is possibly with any server or proxy if configured incorrectly.

Note that using the “Pragma: no-cache” header for the EditLive! files does not trigger these extreme load times. The “Pragma: no-cache” header still allows the jar file to be cached, but the plugin checks for updates with every new browser session. If EditLive! hasn’t been updated, the file is not downloaded again.

Solution

The server or proxy configuration must be corrected so that the Last-Modified header is correctly sent. It is not possible to work around the issue within EditLive! or the user’s computer configuration.

Where Is EditLive! Cached?

Once EditLive! downloads the first time, it is cached on the user’s machine so that in the future it starts up lightning fast. Unlike most content from web sites, EditLive! isn’t cached in the browser’s cache because items in browser cache are regularly discarded as other web site content is downloaded and cached instead. EditLive! is actually cached by the Java plugin which provides a long term cache.

So where is it? It depends. Where the Java cache is actually stored on disk will vary depending on what OS you have, what Java version you have and how it’s all configured. Fortunately, the Java Control Panel provides an easy way for you to view what’s in the cache and delete entries from it. On Windows, there should be a “Java” icon in the Control Panel directory. On Mac, the application is called “Java Preferences” and it’s located in /Applications/Utilities/Java/. Finally on Linux and Solaris you need to run the ControlPanel executable from the bin directory of your JRE. For more information see Sun’s documentation.

Preventing Users From Pasting Images

Preventing users form inserting images requires two parts:

  1. Remove the insert image related items from the configuration file so the insert image dialog is unavailable.
  2. Prevent users from pasting images into the editor.

The first part is quite simple, just delete the “ImageServer” menu item and toolbar buttons from your configuration file.

The second part however isn’t directly supported by EditLive! Fortunately, we’ve now made a plugin available that automatically strips any images out of pasted content. Like all the plugins on LiveWorks! we don’t offer direct support for the plugin, but the full source code is included and help is generally available on the mailing list. The instructions for using the plugin are on the plugin page - it’s just a simple line of JavaScript.

If you look at the source code for the plugin you’ll find it’s a very simple PasteFilter (see this previous article). The main method simply uses a regular expression to filter out the img tags:

public String filterIn(String source) {
  String regex = "<img[^>]*";
  if (FILTER_ONLY_LOCAL_IMAGES) {
    regex += "src=[\"']file:[^>]*";
  }
  regex += "/?>";
  Pattern pattern = Pattern.compile(regex,
      Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
  Matcher matcher = pattern.matcher(source);
  return matcher.replaceAll("");
}

FILTER_ONLY_LOCAL_IMAGES is a final variable declared at the top of the file. It’s set to false in the standard version so that all images are removed, but if you change it to true, only local images will be removed and remote images will be pasted normally. This is a great way to prevent users from pasting in local images but still allow them to use images from the repository or other sites. It also shows how simple it is to adapt the regular expression to be more selective about which images to filter out. With a little knowledge of regular expression and this plugin as a starting point you should be able to achieve whatever image filtering policy you need.

EditLive! Support For Safari

A number of our clients have been trying out Safari for Windows and finding that EditLive! doesn't load, instead falling back to a standard text area and thinking that we don’t support Safari at all. The reality is that Safari comes in two distinct versions - one for Mac OS X which we support and one for Windows which we don’t. While the two versions look almost identical, there are some key differences around the way they handle applets which is currently preventing EditLive! from running in the Windows version. We also want to make sure we’ve completely tested it out so there aren’t any nasty surprises.

So to be clear - Safari on Mac OS X is completely supported and works great. Safari on Windows isn’t yet supported and will use a text area instead.

Hidden Delights Of 6.4

While the CSS improvements in the upcoming 6.4 release may take center stage, there are a number of neat little quality of life improvements that you might be interested in:

Keyboard Shortcuts

Keyboard shortcuts are enabled for all items on the toolbar, even if they are not present in the menu or if there is no menu. This has been requested by clients a number of times. Sometimes space is extremely limited so you want to remove the menu bar entirely. Previously, this also disabled the keyboard shortcuts like control-B. With 6.4 adding items to the toolbar will also enable the keyboard shortcut.

Paragraphs In DIVs and Tables

We've improved the way content in DIVs and tables works when the user presses enter. Since the usual case for these elements is to have content directly inside them without any P tags, the editor inserted a BR tag when the user pressed enter. In 6.4 this has changed so that the existing content is wrapped in a P tag and a new empty paragraph tag is inserted. This makes the resulting content semantically correct and works a lot better when applying block styles or changing heading levels.

Navigation In Nested and Adjacent Elements

With previous versions of EditLive! it was difficult or impossible to position the caret between two adjacent tables or position it accurately in nested DIVs.  With 6.4 you can simply position the caret in the nested DIV and press up or down. EditLive! will automatically create a space for the caret to fit in so you can start typing. If you just keep pressing up to go past there it will remove the space again automatically. It's a bit hard to explain but we think it works really well.

Improved File Dialogs On OS X

This is my personal favorite. The open and save dialogs on OS X have always been pretty ordinary but in 6.4 we've dramatically improved them so they work just like the native ones (in fact, they are the native ones). While having a more consistent UI is a big benefit on it's own, the native dialogs let you easily browse your iPhoto library to select local images.

Gridlines Around DIVs

EditLive! 6.4 adds a lot of support for DIVs and one of the enhancements is that we make the visible to users by displaying gridlines around them just like we do for tables. Of course, if you've been working with DIVs already you might find those gridlines just get in the way so we've provided a simple switch to turn them off. Just set the wysiwygEdior attribute “showSectionGridlines” to “false”.

And More…

For all the details, including the long list of bug fixes, the change log is available on the early access page where you can also download the latest build to try out.

Inserting Content Between Uneditable Sections

One of our long time partners came to us with a problem recently - they use the contenteditable attribute heavily to make sections of the document uneditable for users. The problem was that when two uneditable sections were next to each other, users can't insert anything between them. For many of our clients this is the desired behavior but it doesn't suit everyone. To solve the problem we actually need a new method to temporarily ignore the contenteditable attribute and allow our code to make changes. That new method is now available in all the early access builds via the DocumentModifier.setOverrideContentEditable method. We'll also make use of the technique for finding specific parent elements described in last week's article.

The solution we'll create is to add a custom menu item that allows a user to insert content before the current element. In the process, we'll demonstrate a very powerful form of the insertHtml method available from DocumentModifier that enables a wide range of possibilities.

First we need to define the custom menu item in our XML configuration file:

<shrtMenuItem name="insertBefore" action="raiseEvent"
  value="insertBefore" text="Insert Before" />

Simple enough, when the menu's selected it will raise an event we can catch with the value "insertBefore". We need to register an editor event listener to catch that event:

bean.addEditorEventListener(this);

Then implement the EventListener interface with a raise event method:

public void raiseEvent(TextEvent e) {
  if (e.getActionCommand() == TextEvent.CUSTOM_ACTION &&
    e.getExtraInt() == TextEvent.CustomAction.RAISE_EVENT &&
    "insertBefore".equals(e.getExtraString())) {
      insertBeforeSection();
  }
}

We're looking for the text event with the TextEvent.CUSTOM_ACTION action command, TextEvent.CustomAction.RAISE_EVENT as the extra int and "insertBefore" as the extra string. When we get it we call the method that does all the work.

The basic algorithm we'll follow is:

  1. Find the element for the uneditable section.
  2. Override the contenteditable attribute.
  3. Insert an empty paragraph before the uneditable section.
  4. Put the caret into the new paragraph.
  5. Enable the contenteditable attribute again.

To find the uneditable section we use the getUneditableBlock method from the previous article.

Element uneditableBlock = getUneditableBlock(bean);

Next we use the new setOverrideContentEditable method to make the section editable temporarily.

DocumentModifier modifier = bean.getDocumentModifier();
modifier.setOverrideContentEditable(true);

Inserting the empty paragraph is a little complex because we need to insert it at the right point in the HTML element structure. If we used the normal insertHTMLAtCursor function, the paragraph would be inserted into the uneditable section:

What we really want is to insert the new paragraph into the parent element of the uneditable DIV so that it becomes a sibling of the DIV:

To achieve this, we need to use the full form of the insertHTML method in DocumentModifier:

modifier.insertHtml(uneditableBlock.getStartOffset(),
  "<p></p>", HTML.Tag.P, uneditableBlock.getParentElement());

The first two arguments are fairly simple - the character offset to insert the content at and the HTML string to insert. The next two arguments are what give this method so much power.

The HTML.Tag.P argument specifies the first tag to start inserting content from. When the parser parses the HTML string we're inserting, it always creates a complete, well-formed HTML document including the HTML and BODY tags. To insert just a snippet of HTML instead we have to specify when to start inserting. With the simple form of insertHTML this is automatically set to the first tag you actually specify in the HTML string, but complex insertions occasionally require manually setting this parameter. In this case, we want to start inserting from the P tag we specified so we pass in HTML.Tag.P.

The final argument is the one we really need in this case. It allows us to specify how far up the element tree to insert the new content. The simple form of insertHtml always inserts the content as close to the leaf elements as allowed in HTML - in our case that would insert the P tag into the uneditable DIV because that's the first element that is allowed to contain a P tag. If we were inserting an IMG tag it would be inserted into the P tag inside the DIV if there were one etc. By specifying the parent of the uneditable DIV here, we ensure that the inserted paragraph has the same parent element as the uneditable DIV, making them siblings.

Now we need to move the character into the newly inserted paragraph. Since the inserted paragraph is immediately before the uneditable DIV we can just set the caret one position before the start of the DIV and it will be in the new paragraph.

bean.getHTMLPane().setCaretPosition(uneditableBlock.getStartOffset() - 1);

Finally we enable the contenteditable attribute again.

modifier.setOverrideContentEditable(false);

When we put the method together it winds up looking like:

private void insertBeforeSection() {
  Element uneditableBlock = getUneditableBlock(bean);
  DocumentModifier modifier = bean.getDocumentModifier();
  modifier.setOverrideContentEditable(true);
  try {
    modifier.insertHtml(uneditableBlock.getStartOffset(),
      "<p></p>", HTML.Tag.P,
      uneditableBlock.getParentElement());
  } catch (BadLocationException e1) {
    e1.printStackTrace();
  }
  bean.getHTMLPane().setCaretPosition(uneditableBlock.getStartOffset() - 1);
  modifier.setOverrideContentEditable(false);
}

While some of the concepts involved are quite foreign to developers who haven't worked with the Swing text APIs before, the resulting code is fairly simple and our users can now insert content between uneditable sections with a simple menu item.

Finding Specific Parent Elements

When developing custom functionality within EditLive! you occasionally want to work with a specific element in the tree. For example, you might want to find the element that wraps an uneditable section that the user has clicked within. Other examples might be implementing a properties dialog for a custom block tag you've registered or finding the table tag that the caret is within.

We'll use the uneditable section example in this article, but it's simple to adjust this technique for whatever type of element you need to find.

Set Up

We'll assume you've already got a plugin set up and have access to the ELJBean instance for the editor. If not, take a look at our previous article on creating plugins.

Main Loop

The basic idea is to start from the character element (the leaf element in the tree) and work our way back up until we find an element that matches our criteria. If there isn't a matching element we'll eventually reach the root of the tree and return null.

public Element getUneditableBlock(ELJBean bean) {
  JTextPane pane = bean.getHTMLPane();
  HTMLDocument document = (HTMLDocument) pane.getDocument();
  Element element = document.getCharacterElement(pane.getCaretPosition());
  while (element != null && !isUneditableBlock(element)) {
    element = element.getParentElement();
  }
  return element;
}

Note that the getHTMLPane() method returns a standard JTextPane so all the usual Swing text component methods are available. Iterating up the tree is very similar to iterating up a standard DOM.

Conditional Selection

All that's left is to implement the isUneditableBlock method which provides the conditional logic for selecting the element we're looking for. This is the method that will vary based on exactly what you're looking for. In this case we're looking for an element that has a "contenteditable" attribute that's set to "false".

private boolean isUneditableBlock(Element element) {
  AttributeSet attributes = element.getAttributes();
  return attributes.isDefined("contenteditable") &&
    attributes.getAttribute("contenteditable").equals("false");
}

Note that we check attributes.isDefined("contenteditable") as well as that it's set to false because getAttribute will search through the parent elements as well. Also note that the contenteditable attribute isn't recognized by the parser so it appears as a string - recognized attributes use usually instances of HTML.Attribute or CSS.Attribute, for example HTML.Attribute.WIDTH or CSS.Attribute.COLOR.

An alternate implementation of the method that finds the table element would be:

private boolean isUneditableBlock(Element element) {
  return HTML.Tag.TABLE ==
    element.getAttributes().getAttribute(AttributeSet.NameAttribute);
}

Doing Something Useful

Just to round out our example, here's a mouseClicked implementation that would use these methods to select the entire uneditable section whenever the user clicks within it.

public void mouseClicked(MouseEvent e) {
  Element block = getUneditableBlock(bean);
  if (block != null) {
    bean.raiseEvent(new TextEvent(this, TextEvent.SELECT_NODE_ACTION, block, -1));
  }
}

Note that you have to wait for the LOADING_COMPLETE event to fire before adding the mouse listener, otherwise ELJBean.getHTMLPane will return null. For more information, see the previous article on initializing plugins.