Inline Editor Control

This document explains how to create a button for the inline editor.

You will :

  1. Create the button itself and place it in the ribbon
  2. Code the logic of the button to write in the htmleditor
  3. Modify the htmleditor cleanup system to accept your new tag
  4. Modify the htmleditor css to represent your new tag
  5. Add a xsl to transform your htmleditor tag to a docbook extension tag
  6. Add a xsl to transform your docbook extension tag to a htmleditor tag for re-editing
  7. Add a xsl to transform your docbook extension tag to a html tag for rendering
  8. Optionally use a different tag in inline editor and in storing for inline editor (very advanced feature)
  1. Create the button and place it
    1. EditorButton
    2. EditorButton to create a character tag around a selection
      1. Declare your button as described here in your plugin.xml file
      2. Create the following script file in your resource dir
    3. EditorButton to create a character tag around a selection with a user valued attribute
  2. Action
    1. I don't find a command that suits
    2. I want to create a marker
  3. Inline editor clean up
    1. handleTag
      1. emptyTag
      2. replaceTags
      3. handleAttribute
        1. defaultValue
        2. values
  4. Modify the htmleditor css to represent your new tag
  5. htmleditor2docbook
    1. htmleditor2docbook.xsl
    2. Declare your file
    3. How to check ?
    4. Keep in mind
    5. Java Transformer
  6. docbook2htmleditor
  7. docbook2consistency
  8. docbook2html
    1. XSL
    2. Java Transformer
  9. Optionally use a different tag in inline editor and in storing for inline editor (very advanced feature)
    1. Example


Create the button and place it

An editor button is a button. Please read the documentation relative to the creation of ribbon elements first (Ribbon Control)

Editor button should be enabled/disabled/toggled depending on a selection message with :

  • a Content target with
    • a form subtarget (content in edition) with
      • a rich text field subtarget
        • with a node subtarget.

EditorButton

You can inherit the EditorButton (org.ametys.cms.ribbon.control.button.EditorButton) that implements a onMessage method and gives you a new entry point "selection-listener".

Declare it like that

<extension id="org.ametys.cms.edition.caracter.Bold"
           point="org.ametys.cms.workspace.ribbon.RibbonControlsManager"
           class="org.ametys.cms.workspace.impl.StaticContextualClientSideElement">
    <action class="org.ametys.cms.ribbon.button.EditorButton">
        <param name="action">Ext.ametys.cms.editor.basicstyles.Bold</param>
        <param name="selection-listener">Ext.ametys.cms.editor.basicstyles.BoldSelectionListener</param>

        <param name="label" i18n="true">CONTENT_EDITION_CARACTER_BOLD_LABEL</param>
        <param name="default-description" i18n="true">CONTENT_EDITION_CARACTER_BOLD_DESCRIPTION</param>
        <param name="icon-small" file="true">img/content/edition/caracter/bold_16.gif</param>
        <param name="icon-medium" file="true">img/content/edition/caracter/bold_32.gif</param>
        <param name="icon-large" file="true">img/content/edition/caracter/bold_32.gif</param>
    </action>

    <scripts>
        <file>js/org/ametys/cms/ribbon/button/EditorButton.js</file>
        <file>js/org/ametys/cms/editor/BasicStyles.js</file>
    </scripts>
</extension>

The selection listener allows you to enable/disable/toggle/change icon the button following the current selected node.
Here is the sample of the bold listener : it enables the bold button when the current selection is a inline editor node ; it toggles the button on when the current selected inline editor node is bold

org.ametys.cms.editor.basicstyles.BoldSelectionListener = function(button, target, node)
{
	var off = target == null || node == null;

	button.getElement().setDisabled(off);
	button.getElement().toggle(!off && tinyMCE.activeEditor.queryCommandState("Bold"));
}

EditorButton to create a character tag around a selection

When you want to surround the current user selection.

For example, in the following html, we want to create a quote button. The <cite> tag is to be inserted around the quoted text.

<p>This is what he said "Hello folks!"</p>

can become when "Hello folks!" is selected

<p>This is what he said <cite>"Hello folks!"</cite></p>

You can do the following

Declare your button as described here in your plugin.xml file
   <extension id="MYID"
    	point="org.ametys.cms.workspace.ribbon.RibbonControlsManager"
    	class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
	<action class="org.ametys.cms.ribbon.button.EditorButton">
		<param name="action">MYACTION</param>
		<param name="selection-listener">MYLISTENER</param>

		<param name="label">MYBUTTONLABEL</param>
		<param name="default-description">MYBUTTONDESCRIPTION</param>
		<param name="icon-small" file="true">MYPATHTOMYIMG_16.png</param>
		<param name="icon-medium" file="true">MYPATHTOMYIMG_32.png</param>
		<param name="icon-large" file="true">MYPATHTOMYIMG_48.png</param>
	</action>
	<scripts>
		<file plugin="cms">js/org/ametys/cms/ribbon/button/EditorButton.js</file>
		<file>MYPATHTOMYQUOTESCRIPT.js</file>
		<file plugin="cms">js/org/ametys/cms/editor/InsertElementHelper.i18n.js</file>
	</scripts>
   </extension>
Create the following script file in your resource dir
var tagName = "MYHTMLTAGTOINJECT"
MYACTION = function()
{
	org.ametys.cms.editor.InsertElementHelper.actionDirect(tagName);
};
MYLISTENER = function(button, target, node)
{
	org.ametys.cms.editor.InsertElementHelper.listener (button, target, node,
		tagName, null,
		"MYBUTTONDESCRIPTION",
		"MYBUTTONDESCRIPTIONWHENDISABLED"
	);
}

org.ametys.cms.editor.InsertElementHelper.actionDirect surrounds the currently selected tag with a new tag <tagName>.
org.ametys.cms.editor.InsertElementHelper.listener toggles the button when the current selection is in a <tagName>. The 2 descriptions are used depending if the button is enabled or disabled.

You can optionally add a defined attribute on the tagname and also a className.

You can create code like

<span myattribute="myhardcodedvalue" class="myclasstoreflectmystate">previously selected text</span>

EditorButton to create a character tag around a selection with a user valued attribute

If you want to do as before but want to previously ask the user for an input (that will become an attribute value) do the same as the previous section but with the following script.
For example, the following html

<p>W3C rocks</p>

can become when W3C is selected

<p><acronym title="World Wide Web Consortium">W3C</acronym></p>

displaying the following dialog box to the user :

var tagName = "MYHTMLTAGTOINJECT"
MYACTION = function()
{
	org.ametys.cms.editor.InsertElementHelper.action
	(
		getPluginResourcesUrl('MYPLUGINNAME') + 'PATHTOAICONFORTHEDIALOGBOX_16.png',
		"CAPTIONOFTHEDIALOGBOX",
		"NAMEOFTHEFIELD",
		"DEFAULTVALUEOFTHEFIELD",
		"INTRODUCTIVEDESCRIPTION",
		"POSTDESCRIPTION",
		tagName, "MYATTRIBUTETOFILL"
	);
};
MYLISTENER = function(button, target, node)
{
	org.ametys.cms.editor.InsertElementHelper.listener (button, target, node,
		tagName, null,
		"MYBUTTONDESCRIPTION",
		"MYBUTTONDESCRIPTIONWHENDISABLED"
	);
}

Action

Most of the time you just have to simply call commands to the editor.
See tinyMCE documentation http://wiki.moxiecode.com/index.php/TinyMCE:Index

Here is the code of the bold button

org.ametys.cms.editor.basicstyles.Bold = function()
{
    tinyMCE.focus();
    tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
    tinyMCE.activeEditor.execCommand('Bold');
    tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
};

Always end by giving the focus back !
This is not a tinyMCE normal behavior, but we have to do so because we use our own toolbar

Always start by looking if the button your are creating already exists in tinyMCE. If so you will be able to copy the main code, but still have to create the hmi specific code

I don't find a command that suits

You have always the solution to insert your own html code.
Replace the execCommand ('Bold') of the preceding sample by:

tinyMCE.activeEditor.execCommand('mceInsertContent', false, "<img src='myimage.png'/>");

This will allow you to create any tag you want

I want to create a marker

A marker is a ufo in the middle of your text to bookmark a place.
For example: "<p>Lorem ipsum<marker/> doler</p>
The marker set in the inline editor will be replaced during rendering by a feature.
For example: "<p>Lorem ipsum<img src="warning.gif"/> doler</p>

To set a marker in your code you can use the image tag, but with the attribute marker="marker" to explain this is not a classic image.
You also sould use class="mceItemNoResize" to make your marker not resizable by the user.

You have to differenciate your marker by specifying a special attribute. For example myfeature="myfeature"
You have to graphically specify an appearance to your button. You can use the src, or specify a transparent file in the src and set a specific class.

Sample :
Here is the tag I want to handle

<img src="/plugins/cms/img/conten/edition/htmlexpert/trans.gif"
     class="htmlexpert mceItemNoResize"
     marker="marker"
     htmlexpert="-"/>

With the css code :

img.htmlexpert
{
	background-image: url('../../img/content/edition/htmlexpert/htmlexpert.png');
	background-repeat: no-repeat;
	background-position: center;
	background-color: transparent;

	margin-left: 5px;
	margin-right: 5px;
	margin-top: 1px;
	margin-bottom: 1px;

	width: 45px;
	height: 13px;

	vertical-align: middle;
}

Inline editor clean up

The inline editor cleans up html code. This means that it destroys any unauthorized tag, attribute or attribute value.

You have to declare DURING THE INITIALIZATION OF THE APPLICATION the tag you are handling.

handleTag

This method allows this tag.

This example allows the <ul> tag

org.ametys.cms.editor.Editor.handleTag("ul");

This method returns an object with the following attributes and methods:

emptyTag

This attribute is a string that can be "", "-", "+" or "#". See http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/valid_elements.
Note that the last that put this value will win.

This example allow the <ul> tag, and tell that an empty <ul/> will be destroyed.

org.ametys.cms.editor.Editor.handleTag("ul").emptyTag = "-";
replaceTags

This attribute is an array of tags that can be converted to this tag. These are tags you do not handle but you want to convert to your tag.

If you declare a tag in replaceTags but someone else handle it, your declaration for replacing will be ignored

The following example authorizes <li> and means that any <dd> or <dt> found, will be replaced by <li>

org.ametys.cms.editor.Editor.handleTag("li").replaceTags.push("dd");
org.ametys.cms.editor.Editor.handleTag("li").replaceTags.push("dt");
handleAttribute

This method allows an attribute on the tag.

This example allows the tag <table> and the attribute "class" (whatever its value is)

org.ametys.cms.editor.Editor.handleTag("table").handleAttribute("class");

defaultValue

This is the default value of the attribute.
If this is null, there is no default value.
If this is a string that means that the attribute will always exists.

This example allows the tag <table> and the attribute "class" (with the default value "myclass").

org.ametys.cms.editor.Editor.handleTag("table").handleAttribute("class").defaultValue = "myclass";

values

This attribute is an array of string of the values allowed for this attribute.

This example allows the tag <table> and the attribute "class". The value of class can be myclass or myclass2.

org.ametys.cms.editor.Editor.handleTag("table").handleAttribute("class").values.push("myclass");
org.ametys.cms.editor.Editor.handleTag("table").handleAttribute("class").values.push("myclass2");

Modify the htmleditor css to represent your new tag

As you have just done with cleanup, you have during your initialization to register your css.

Here is a sample :

org.ametys.cms.editor.Editor.useCSSFile.push("pathtomycssfile.css");

htmleditor2docbook

htmleditor2docbook.xsl

In your plugin, create a file "htmleditor2docbook.xsl"
This xsl will have to handle the code you have inserted in the htmleditor and transform it to an acceptable docbook tag.

For sample, the style drop down list generates this kind of htmleditor code

    <p class="introduction">This is a test</p>

That can be handled with the following xsl

<xsl:stylesheet version="1.0"
    xmlns:docbook="http://docbook.org/ns/docbook"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="p">
         <docbook:para>
                  <xsl:copy-of select="@class"/>

                  <xsl:apply-templates/>
         </docbook:para>
    </xsl:template>

</xsl:stylesheet>

Declare your file

In your plugin file add a new feature that declares an extension to org.ametys.cms.transformation.docbook.DocbookEnhancementExtensionPoint

The configuration will be

    <feature name="docbookDefaultTags">
    	<extensions>
    		<extension id="myuniqueid"
    				   point="org.ametys.cms.transformation.docbook.DocbookEnhancementExtensionPoint">
    			<htmleditor2docbook>
    				<xslt>stylesheets/htmleditor2docbook.xsl</xslt>
    			</htmleditor2docbook>
    		</extension>
    	</extensions>
    </feature>

Where stylesheets/htmleditor2docbook.xsl is the file you have created above.
You can also use protocols if needed (plugin:cms://stylesheets/htmleditor2docbook.xsl or skin://stylesheets/htmleditor2docbook.xsl)

How to check ?

Use the repository workspace to see your content as a XML. (The repository workspace is available in the administration space of the cms)
You should see something like

...
        <docbook:para class="introduction">This is a test</docbook:para>
...

Keep in mind

All the xsls declared using this way are mixed all together, but without a defined order.
If several xslt defines the same matchers, you can not know which one will be used, except if you use the "priority" tag on it.

The sample code above is a good example to break to default save process as the default htmleditor2docbook.xsl is much more complicated than this one (and your code may interfer).

So be sure to use tags that no one also use (in the htmleditor and in the docbook).

Java Transformer

Before your XSL, you can have a java transformer to do more sophisticated stuff.

For example, you can transform copy/paste links to internal links if needed

To do so, first create a javaclass that handles your tag.
Extends org.ametys.cms.transformation.htmledition.HTMLEditionHandler

Then declare it as an extension of org.ametys.cms.transformation.htmledition.HTMLEditionHandlerExtensionPoint under the id of your choice.
Finally add a <handler> tag under <html2docbook> to indicate the role.

    <feature name="docbookDefaultTags">
    	<components>
    		<component class="org.ametys.cms.transformation.docbook.TestProxyEnhancementHandler"
    				   role="org.ametys.cms.transformation.docbook.TestProxyEnhancementHandler">
    		</component>
    	</components>
    	<extensions>
    		<extension id="org.ametys.cms.transformation.docbook.defaulttags"
    				   point="org.ametys.cms.transformation.docbook.DocbookEnhancementExtensionPoint">
    			<htmleditor2docbook>
    				<xslt>stylesheets/htmleditor2docbook.xsl</xslt>
    				<handler>org.ametys.cms.transformation.htmledition.UploadedDataHTMLEditionHandler</handler>
    			</htmleditor2docbook>
    			<docbook2htmleditor>
    				<xslt>stylesheets/docbook2htmleditor.xsl</xslt>
    			</docbook2htmleditor>
    			<docbook2html>
    				<xslt>stylesheets/docbook2html.xsl</xslt>
    				<handler>org.ametys.cms.transformation.docbook.TestProxyEnhancementHandler</handler>
    			</docbook2html>
    		</extension>
    	</extensions>
    </feature>
package org.ametys.cms.transformation.htmledition;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.components.ContextHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.xml.AttributesImpl;
import org.apache.commons.io.IOUtils;
import org.apache.excalibur.source.SourceResolver;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import org.ametys.runtime.upload.Upload;
import org.ametys.runtime.upload.UploadManager;
import org.ametys.runtime.user.CurrentUserProvider;

/**
 * This transformer extracts uploaded files' ids from the incoming HTML for further processing.
 */
public class UploadedDataHTMLEditionHandler extends HTMLEditionHandler
{
   // do your code here
}

docbook2htmleditor

This is exactly the same as above, but in the opposite way : you have to write a XSL that convert your docbook extension to the html editor
In the plugin configuration replace <htmleditor2docbook> by <docbook2htmleditor>

Here is the sample that goes with the preceding one above.

<xsl:stylesheet version="1.0"
    xmlns:docbook="http://docbook.org/ns/docbook"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

	<xsl:template match="docbook:para">
		<p>
			<xsl:copy-of select="@class"/>
  			<xsl:apply-templates/>
		</p>
	</xsl:template>
</xsl:stylesheet>

docbook2consistency

The new tags your are now handling may need to take place in the consistency checker (that for example, check that links are not broken)
In that case, you have to write a XSL that convert your docbook extension to the consitency format.
In the plugin configuration replace <htmleditor2docbook> by <docbook2consistency>

The output format must be :

<consitencies>
    <consistency type="TYPE">VALUE</consistency>
    ...
</consistencies>

The VALUE depends on the TYPE.
The list of handled types depends on the plugin your are using.
The main type is link and imply that a linkchecker will goes on. The pattern of the value is LINKTYPE:REFERNCE.
The REFERENCE depends on the LINKTYPE.
The LINKTYPE depends on the plugins you are running, but in a web configuration, you have at least :

  • (empty) (for external links) then the reference is a http url. e.g. http://www.ametys.org
  • page (for links on pages) then the reference is an ametys object id. e.g. page:page://f69e9744-852c-4d9d-834b-242cbe5d01db
  • attachment-content (for links on attachments) then the reference is an ametys object. e.g. attachment-content:resource://6df076d3-2437-4b77-95ae-56947f6c1b57
  • explorer (for links on explorer files) then the reference is an ametys object. e.g. explorer:resource://2973249e-9bd4-4eec-b7d9-823c0d879cde
  • local (for links on metadata local to the content) then the reference is an ametys object + a metadata path. e.g. local:defaultWebContent://591538d9-6489-4c99-802d-a0cf5459b11e@content;image.jpg

For example, here is the code of the default XSLT that analyses links and images in docbook

<xsl:stylesheet version="1.0"
    xmlns:docbook="http://docbook.org/ns/docbook"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/*">
    	<consistencies>
    		<xsl:apply-templates/>
    	</consistencies>
    </xsl:template>

    <xsl:template match="*">
    		<xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="text()"/>

    <xsl:template match="docbook:link">
    	<consistency type="link"><xsl:value-of select="@xlink:type"/>:<xsl:value-of select="@xlink:href"/></consistency>
    </xsl:template>

    <xsl:template match="docbook:imageobject">
    	<consistency type="link"><xsl:value-of select="docbook:imagedata/@type"/>:<xsl:value-of select="docbook:imagedata/@fileref"/></consistency>
    </xsl:template>
</xsl:stylesheet>

docbook2html

This step is nearly the same as above, but the result will be use for the final rendering (instead of for edition)

XSL

This XSL will mainly have the same look as docbook2htmleditor.xsl but with some differences.
In the plugin configuration use <docbook2html> instead of <htmleditor2docbook> to specify your xsl file.

For example, "title 1" is converted to "h1" for edition, but to "h3" for rendering.
Why "h3", for accessibility purposes, there are generally already h1 and h2, and content takes place under the h2 as a "h3".

Another example : you create a button that insert a youtube video.
In the htmleditor you set <div class="youtube" youtube="youtubeid"/>
In the docbook you save <docbook:video youtube=youtubeid"/>
In docbook2htmleditor you will recreate the <div> tag, but in docbook2html you will create the flash player tag.

See docbook2html in the skin documentation for more information.

Java Transformer

After your XSL, you can have a java transformer to do more sophisticated stuff.

For example, you can request a ldap to replace you brand new tag <emergencyNumber/>

To do so, first create a javaclass that handles your tag.
Implement org.ametys.cms.transformation.docbook.EnhancementHandler (or extends org.ametys.cms.transformation.docbook.ProxyEnhancementHandler)

Then declare it a an avalon component in your plugin configuration under the role of your choice.
Finally add a <handler> tag under <docbook2html> to indicate the role.

    <feature name="docbookDefaultTags">
    	<components>
    		<component class="org.ametys.cms.transformation.docbook.TestProxyEnhancementHandler"
    				   role="org.ametys.cms.transformation.docbook.TestProxyEnhancementHandler">
    		</component>
    	</components>
    	<extensions>
    		<extension id="org.ametys.cms.transformation.docbook.defaulttags"
    				   point="org.ametys.cms.transformation.docbook.DocbookEnhancementExtensionPoint">
    			<htmleditor2docbook>
    				<xslt>stylesheets/htmleditor2docbook.xsl</xslt>
    			</htmleditor2docbook>
    			<docbook2htmleditor>
    				<xslt>stylesheets/docbook2htmleditor.xsl</xslt>
    			</docbook2htmleditor>
    			<docbook2html>
    				<xslt>stylesheets/docbook2html.xsl</xslt>
    				<handler>org.ametys.cms.transformation.docbook.TestProxyEnhancementHandler</handler>
    			</docbook2html>
    		</extension>
    	</extensions>
    </feature>
/*
 *  Copyright 2010 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.ametys.cms.transformation.docbook;

import org.apache.cocoon.xml.AttributesImpl;
import org.apache.cocoon.xml.XMLUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

public class TestProxyEnhancementHandler extends EnhancementHandler
{
    private static final String MYTAG = "emergencyNumber";

    @Override
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException
    {
        if (MYTAG.equals(localName))
        {
            String ldapEmergencyNumber = "";
            // Do my ldap request that fill ldapEmergencyNumber

            AttributesImpl attrs = new AttributesImpl();
            attrs.addCDATAAttribute("class", "emergency-number");
            XMLUtils.createElement(_contentHandler, "span", attrs, ldapEmergencyNumber);
        }
        else
        {
            super.startElement(uri, localName, qName, atts);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException
    {
        if (!MYTAG.equals(localName))
        {
            super.endElement(uri, localName, qName);
        }
    }
}

Optionally use a different tag in inline editor and in storing for inline editor (very advanced feature)

Some times, you use a tag in the inline editor but you want to substitute it by another one.
For example, you sometimes need a <img marker="marker"/> to represent a marker in the inline editor, but you want to on-the-fly replace it by its logical role. (to avoid playing with several img tags in your xsls)

In that very rare and advanced case, you have to register 2 methods to the online editor. One when the content is set and one when it is read.
The signature of these methods are (editor, object) where object.content is the whole editor content.

org.ametys.cms.editor.Editor.onGetContent.push(function (editor, object) {...} );
org.ametys.cms.editor.Editor.onSetContent.push(function (editor, object) {...} );

The job is then to search in object.content to replace base object by transformed object.

Example

I want to handle a "htmlexpert" feature: this allows to enter html code that will be kept intact.
I do it by setting a marker in the inline editor that will hold the html code.
The inline editor will use a <img marker="marker" htmlexpert="myhtmlcode"/>
But in my logic I want to represent it by <!-- htmlexper myhtmlcode -->

I want onGetContent to convert from img to comment and the opposite onSetContent

// Search for <img htmlexpert="..." .../> to replace by <!-- htmlexpert ... -->
org.ametys.cms.editor.Editor.onGetContent.push(
	function (editor, object)
	{
		object.content = object.content.replace(
			/&lt;img[^&gt;]+ htmlexpert="-([^"]*)"[^&gt;]+&gt;/g,
			function (s)
			{
				var htmlexpert = RegExp.$1;
				return "&lt;!-- htmlexpert " + htmlexpert.replace(/-/g, "%2D") + " --&gt;";
			}
		);
	}
);

// Search for <!-- htmlexpert ... --> to replace by <img htmlexpert="..." .../>
org.ametys.cms.editor.Editor.onSetContent.push(
	function (editor, object)
	{
		object.content = object.content.replace(
			/&lt;!-- htmlexpert ([^-]*) --&gt;/g,
			function(s)
			{
				var htmlexpert = RegExp.$1;
				return org.ametys.cms.editor.htmlexpert._createHTML(htmlexpert.replace(/%2D/g, "-"));
			}
		);
	}
);