WYSIWYG editor based on GWT

Last modified by Vincent Massol on 2024/02/26 17:54

 XWiki
 Implementation
 Completed

Description

Issues with current TinyMCE editor

  • Slow
  • Complex code using complex regexs. This leads to a fragility that makes it break somewhere whenever someone adds a modification to it.
  • Lots of bug reports consistently for the past year, showing a non-optimal design

New Architecture proposal

  • Use GWT
    • High performance (AJAX + no popups)
    • Lean and mean code
    • Written in Java
      • Only one parser
    • Better XWiki integeration (images, attachments, links, link suggest, spell checking)
  • Conversions are done on the server side
  • Use WikiModel as the underlying tool for:
    • Wiki syntax to HTML conversions
    • HTML conversions to wiki syntax
  • Ability to section pages: sections for edited only in WYSIWYG mode, and sections only allowing wiki markup and code editing so a user can edit wiki markup and code directly (using wiki editor within the WYSIWYG editor) without it affecting the WYSIWYG markup (so you only need the WYSIWYG editor with the embedded wiki editor) preventing the need to be able to flip between the WYSIWYG editor and wiki editors to do coding, etc, and the associated problems with converting between the two editors (problems such as the WYSIWYG markup being fairly unreadable and uneditable in the wiki editor). This architecture should help provide more control and flexibility for editing XWiki pages.

New Interface Proposal

See the dedicated page

Requirements

  • Iso-feature with current TinyMCE editor

For the future:

  • Ability to import Office documents (Word, Excel) and OpenOffice ones too
  • Real Time editing
  • Wysiwyg Macros support
  • Support importing of other wikis syntax with WikiModel
  • Cut and paste from Word and web pages easily i.e. without having to go back and adjust the formatting of what's been pasted onto an XWiki page
  • Cut and paste images without attaching first (i.e. when you cut and paste images are automatically attached the page)
  • Improved appearance of tables 
  • Ability to layout pages to improve page appearance so text can be grouped in columns and boxes more easily

Open questions/Todos

  • Is there a GWT editor component we can/should reuse? To be checked with Ludovic since he's the one with the most experience on GWT so far
  • Check WikiModel capabilities for conversions in both directions and especially from HTML to wiki syntax.
  • How to integrate WikiModel in the Rendering layer of XWiki? (Vincent to work on this)
  • Is there any code to be salvaged from the work done by Christian and Nam in that branch: svn+ssh://svn.forge.objectweb.org/svnroot/xwiki/xwiki-platform/core/branches/XWIKI_WYSIWYG_NEWARCHI

Agreed stuff

Task List

Deadlines

TBD

Requirements from Guillaume

Description

Introduction

The current WYSIWYG editor has to be replaced. Indeed, many problems have been identified while using it, among which the following:

  • The editor loads too slowly
  • It is missing key features and is not easy to extend
  • It has far (FAR) too many bugs even though lot of work has been done on it
  • Its community does not supprot it any longer
  • The way it handles WYSIWYG to XWiki markup and other syntaxes & text editors to XWiki markup is very poor
  • Actions are not handled with enough clarity

The aim is to build a new text editor that would provide an efficient way for end-users to interact with the wiki in Edit mode.

The demographics of XWiki users is twofolds:

  • Newbies -> users of the WYSIWYG editor
  • Advanced Users -> users of the XWiki syntax & other advanced stuff

Our end-user aim is to make XWiki edition available & easy for both groups. Our technical aim is to make the editor's code extensible (making potential new features easier to add), well integrated in the core and its execution in a browser as fast as possible.

The underlying technology choice is to use GWT to generate clean code and make it easy to maintain since it will be written in Java once and for all, making testing and integration easier.

Architecture & Ergonomics Principles Speed & Effectiveness

The idea would be to use GWT to create a powerful AJAX editor. To increase its effectiveness & speed, a completely modular architecture would be better. That is, the content of elements of the interface have to be loaded only when required.

Toolbar buttons & toolbar behavior have to be selected carefully so as not to trigger the load of elements that are not required to complete a specific task.

Example: I highlight a word, click on the "weblink" button > the link editor opens in the "weblink" tab > i change my mind and want to make it a wikilink instead > I click on the "wikilink" tab > the contents of that tab are loaded & displayed at that moment only.

The coordination & complementarity between the toolbar & right-click menus is important too and needs to be given proper thought.

Example: I have written a new "Fireworks" macro that displays text in a box with a black & red background and a small fireworks icon. I want users to be able to use it right away. What code do I need to add and where so that it can be used by newbies through the editor? > they can choose it in a list of visual macros and immediately see what their text will look like in the visual box.

Features

The editor main features will include the following:

Editing a page

Editing a page is the single most important experience of a wiki user. A wiki is basically all about letting people contribute content to the website - and then handling that content.

The editor must provide both types of users with a good experience. Another point to keep in mind is that with XEclipse power users have an alternative to using XWiki's usual wiki edition mode.

When people will want to add content to the wiki they will typically either paste it from another text editor or type it directly in the editor. Therefore it should cater for both situations. 

We need to decide how to support text coming from MS Word & OO Writer -> use plain-text pasting, use a HTML filter to convert their output to wiki syntax before pasting it on a page...

While editing a page, visual macros have to be easy to add and to edit.

Link creation

This is the second most important feature of the editor. It shall provide users with a quick and intuitive way to create links between documents on the wiki. The current link creator offers some good features but its implementation through popups is too slow. Shortcuts to specific types of links (weblinks for instance) could be doe through a button directly on the toobar rather than stepping into the wikilink mode first.

The link editor will provide an interface to access many elements inside XWiki, namely documents (XWiki pages) & attachments (PDFs, Images...). I'd suggest a specific toolbar button for the following:

  • WikiLink
  • WebLink
  • Image
  • Other type of attachment
  • ? (email) ?

Then the link creator has to cater for every situation, offering quick access (using a drop-down list view) to all docs and attachments in the wiki with features such as:

  • Browse / Search all docs
  • Filter by whole wiki / space / page
  • Recently viewed / Recently modified
  • Browse / Search only for:
    • Pages
    • Pages with a specific object within
    • Images
    • PDFs
    • Word Docs
    • Attachments -> offering the ability to bulk upload attachments to a page
    • Add here...
Ability to start a real-time edition session

We will be confronted to 4 types of situations: 

WikiWYSIWYG
1 peopleFastSemi-fast
More than 1Light real-time sessionReal-time session

The aim is to be able to load the right editor modules so as to cater for the following scenarios: 

  • Single people page edition: load the WYSIWYG part only when necessary
  • Multi-people page edition: load the real time module only when the 2nd person arrives on the page, load the WYSIWYG only if needed.
Saving

One of the frequently encountered problems while using a wiki is the data loss issue -> eg, loosing unsaved text because the connexion failed just before saving the page. We need to choose a way to prevent this from happening, by automatically saving the page on a regular basis and providing a connexion indicator to notify users when the connexion is lost.\

This will require a decision to be made on the format used for those temporary versions: will each version take the place of the previous one, will they be kept as minor or even sub-minor versions ?

Tables

Limited advanced table support would be great. It would look like the current table editor, with the addition of a few functions being supported (division, sum, multiplication, selection handle...)

The table sorter included in XWiki would be available on the GUI (rather than through html code only) -> it could then be used easily to decide what columns to sort, what kind of filter to use and so on (see this page for an example of the table sorter.)...

Another point: tables can be used for layout purposes. Specific macros could be created / used to display basic layout shapes without actually showing table borders on a page (say, a square divided in 4 smaller squares of equal size with a given margin). Content could then be added in those sub-squares (see the features page on XWiki.org to see what I mean more precisely).

Detailed Features List

StyleListsTablesLinksMacros
BoldBulletedCreation / InsertionWiki LinkInformation
ItalicsNumberedAdd / delete row / columnBrowseWarning
UnderlinedGreekSumSearchError
StrikedHiriganaSubstractionRecently ViewedFloating Box
SubscriptKatanakaMultiplicationRecently ModifiedCode
SuperscriptRomanDivisionWeb LinkGeneric Box (using a custom div)
QuoteUppercase romanSelection handleFile Link+ other potential macros
HeadingsAlphabeticalMerge cellsE-mail 
Font familyUppercase alphabeticalCell propertiesImages 
Font size Table deletionImages upload (+ bulk upload) 
Text color Cut / Paste / Copy row / columnAttachments 
Background color  Attachments upload (+ bulk ) 
Align left / right    
Centered / Justified    
Indent left / right    
Special characters    
Smileys    
Horizontal line    
Remove formatting    
ActionsCodeSyntaxesArchitecture
Undo / RedoVelocityXWikiGWT
Cut / Copy / PasteGroovyWiki CreoleAJAX
SpellcheckingHTMLWiki ModelCross-browser
Real - time editionJSMedia WikiWikiModel Engine
Find & ReplaceSnippetsTWikiIntegrated in core
Auto - suggestWidgetsConfluenceModules
Import from...  Clean Code
Autosave  Full Link Parser
Save  WYSIWYG Development Interface
Minor save  Class fields Drag & Drop on a page
Cancel  MS Access of the Web
Preview   
Wiki Mode   
Full Screen   
Chat   

See the following for example of other WYSIWYG web editors for other ideas for editor features:

The new architecture

A general view

The idea behind this design proposal is to make a lightweight and extensible editor, both for WYSIWYG editing and for Wiki-syntax editing. More precisely, the editor should have a lightweight core and should offer the possibility of adding new features (plug-ins), making it extensible.

The Core

The core's architecture is split in two: the client side and the server side. Both of them will be written in Java, but the client side will be translated in JavaScript with the help of GWT.

The client side has the following components:

  1. The tool bar handles a collection of action widgets, affecting the way we view and edit the content of the text area. It should be displayed at the top of the text area.
  2. The rich text area allows us to view and edit formatted text, as in the case of a word processor application.
  3. The status bar is a way of displaying to the user information such as: number of lines, connection status, time elapsed since last save operation etc. In a way, it is similar to the tool bar, but having only static widgets. The status bar should be positioned at the bottom of the text area.
  4. The context menu has a list of menu entries which allow us to do context sensible actions, such as 'Paste here'. It should become visible on mouse right click.
  5. The floating context tool bar is needed when we do a complex operation (like editing a table or an image) which has some options we can chose from and these options can't be found on the tool bar. Being context sensitive, it should be displayed when we 'activate' some object from the text area, and must be draggable.
  6. The plug-in configuration panel should offer the possibility of configuring the editor's available features. For instance, in the case of the auto save feature, the user might want to set the save interval. I don't know if right now this preferences can be saved on a user's profile

Each of the components above will have an interface and at least one implementation of it. For instance, in the case of the rich text area one implementation could wrap the RichTextArea widget provided by the last version of the GWT.

The server-side will be implemented as a GWT service (a servlet in reality). Here we have two options: we either create a new service facade (something like XWikiEditorServiceFacade) specific to our needs, or extend the XWikiService and XWikiGWTApp interfaces (which are used within XWiki Watch) with the needed functionality.

The editor's core shouldn't offer any feature at all. Instead, it should allows us to install and uninstall features (plug-ins) depending on the displayed/edited content (WYSIWYG/Wiki-syntax). These plug-ins could be developed independent of the core.

The Plug-ins

The plug-ins add functionality to the core. They are registered for a type of content: some plug-ins are available for the WYSIWYG editing (HTML content) and some are for the Wiki-syntax editing (XWiki, Creole, Confluence etc.). Also, the plug-ins can either act on the client-side only (make text bold, change font-family) or act both on the client side and on the server-side (auto save, add macro).

Before the editor loads a document it installs all the available plug-ins associated with its content. When a plug-in is being installed it can: add an action widget to tool bar, add an entry to the context menu, add a static widget to the status bar, add a page (composite widget) to the configuration panel.

Some plug-ins depend on a specific functionality on the server-side and because of this they need a way of accessing it. The editor's core should provide an API for this: Object executePlugin(String pluginName, Object args); this way the 'add macro' plug-in could retrieve from the server the list of the available macros and the 'auto save' plug-in could send the new content to the server, to be saved.

The API

I've defined an API for the new GWT editor. You should be aware that all package, interface, class, method, field names are subject to change and I'm open to your suggestions. We should use self-explanatory and natural names as much as we can.

org.xwiki.editor.client.core.impl.def

public class DefaultConfigPanel extends AbstractConfigPanel {
}

public class DefaultContextMenu extends AbstractContextMenu {
}

public class DefaultEditor extends AbstractEditor{
 public DefaultEditor(PlugInManager plugInManager, EditorServiceAsync editorService) {
 }
}

public class DefaultFloatingToolBar extends AbstractFloatingToolBar {
}

public class DefaultRichTextArea extends AbstractRichTextArea {
}

public class DefaultStatusBar extends AbstractStatusBar {
}

public class DefaultToolBar extends AbstractToolBar {
}

org.xwiki.editor.client.core.impl

public abstract class AbstractConfigPanel extends Composite implements ConfigPanel {
}

public abstract class AbstractContextMenu extends Composite implements ContextMenu {
}

public abstract class AbstractEditor extends Composite implements Editor {
  protected EditorServiceAsync editorService;
  protected PlugInManager plugInManager;
  protected AbstractConfigPanel configPanel;
  protected AbstractContextMenu contextMenu;
  protected AbstractFloatingToolBar floatingToolBar;
  protected AbstractRichTextArea richTextArea;
  protected AbstractStatusBar statusBar;
  protected AbstractToolBar toolBar;
}

public abstract class AbstractFloatingToolBar extends Composite implements FloatingToolBar {
}

public abstract class AbstractPropertyManager implements PropertyManager {
  protected boolean dirty;
  protected abstract void doApplyChanges();
  protected abstract void doDiscardChanges();
 public boolean isDirty() {}
  protected abstract void doRestoreDefaults();
 public void setDirty(boolean dirty) {}
}

public abstract class AbstractRichTextArea extends Composite implements RichTextArea {
}

public abstract class AbstractStatusBar extends Composite implements StatusBar {
}

public abstract class AbstractToolBar extends Composite implements ToolBar {
}

org.xwiki.editor.client.core

public interface ConfigPanel {
  void addPage(String name, Widget page, PropertyManager manager);
}

public enum ContentType {HTML, XWIKI, CREOLE, CONFLUENCE}

public interface ContextMenu {
  void addCheckBox(String label, Command cmd);
  void addImage(AbstractImagePrototype imagePrototype, String label, Command cmd);
  void addRadioButton(String groupName, String label, Command cmd);
  ContextMenu addSubMenu(AbstractImagePrototype imagePrototype, String label);
}

public interface Editor {
  ConfigPanel getConfigPanel();
  ContextMenu getContextMenu();
  EditorServiceAsync getEditorService();
  FloatingToolBar getFloatingToolBar();
  RichTextArea getRichTextArea();
  StatusBar getStatusBar();
  ToolBar getToolBar();
}

public interface EditorService {
 Serializable invoke(String plugInServiceName, Serializable args);
 Map<String, String> restoreProperties(String plugInName);
  void storeProperties(String plugInName, Map<String, String> properties);
}

public interface EditorServiceAsync {
  void invoke(String plugInServiceName, Serializable args, AsyncCallback callback);
  void restoreProperties(String plugInName, AsyncCallback callback);
  void storeProperties(String plugInName, Map<String, String> properties, AsyncCallback callback);
}

public interface FloatingToolBar extends ToolBar {
}

public interface PropertyManager {
  void applyChanges();
  void discardChanges();
  void restoreDefaults();
}

public interface RichTextArea extends SourcesClickEvents, SourcesFocusEvents, SourcesKeyboardEvents, SourcesMouseEvents, SourcesMouseWheelEvents, SourcesScrollEvents, HasHTML, HasText, com.google.gwt.user.client.ui.RichTextArea.ExtendedFormatter {
}

public interface StatusBar extends HasWidgets {
}

public interface ToolBar extends HasWidgets {
}

org.xwiki.editor.client.plugin.impl.def

public class DefaultPlugInManager extends AbstractPlugInManager {
  private Map<ContentType, List<PlugIn>> plugInMap;
}

public class DefaultPlugInPropertyManager extends AbstractPropertyManager {
  private PlugIn plugIn;
  private Map<String, String> properties;
  private Map<String, String> defaults;
 public DefaultPlugInPropertyManager(PlugIn plugIn, Map<String, String> defaults) {}
}

org.xwiki.editor.client.plugin.impl

public abstract class AbstractPlugIn implements PlugIn {
  protected Editor editor;
  protected PropertyManager propertyManager;
  protected Map<String, String> properties;
  protected abstract void installConfigPanel();
  protected abstract void installContextMenu();
  protected abstract void installFloatingToolbar();
  protected abstract void installRichTextArea();
  protected abstract void installStatusBar();
  protected abstract void installToolBar();
  protected abstract void uninstallConfigPanel();
  protected abstract void uninstallContextMenu();
  protected abstract void uninstallFloatingToolbar();
  protected abstract void uninstallRichTextArea();
  protected abstract void uninstallStatusBar();
  protected abstract void uninstallToolBar();
}

public abstract class AbstractPlugInManager implements PlugInManager {
}

org.xwiki.editor.client.plugin

public interface PlugIn {
  String getName();
  String getProperty(String name);
  PropertyManager getPropertyManager();
  Iterator<String> getPropertyNames();
  void install(Editor editor);
  void setProperty(String name, String value);
  void uninstall();
}

public interface PlugInManager {
  void register(PlugIn plugIn, ContentType contentType);
  void register(PlugIn plugIn, ContentType[] contentTypes);
  void unregister(PlugIn plugIn, ContentType contentType);
  void unregister(PlugIn plugIn, ContentType[] contentTypes);
  Iterator iterator(ContentType contentType);
}

org.xwiki.editor.server.core.impl.def

public class DefaultEditorService extends RemoteServiceServlet implements EditorService {
  private PlugInServiceManager plugInServiceManager;
}

org.xwiki.editor.server.plugin.impl.def

public class DefaultPlugInServiceManager implements PlugInServiceManager {
  private Map<String, PlugInService> plugInServiceMap;
}

org.xwiki.editor.server.plugin.impl

public abstract class AbstractPlugInService implements PlugInService {
}

org.xwiki.editor.server.plugin

public interface PlugInService {
 Serializable invoke(Serializable args);
  String getName();
}

public interface PlugInServiceManager {
  PlugInService get(String plugInServiceName);
  Iterator iterator();
  void register(PlugInService plugInService);
  void unregister(PlugInService plugInService);
}

All the interfaces and classes that I'm referring to but I'm not defining them are from the last version of the GWT.


 

Get Connected