[TASK] RTE: Migrate to RequireJS 32/35132/2
authorStanislas Rolland <typo3@sjbr.ca>
Sun, 7 Dec 2014 19:22:23 +0000 (14:22 -0500)
committerStanislas Rolland <typo3@sjbr.ca>
Sun, 7 Dec 2014 19:24:08 +0000 (20:24 +0100)
This is a follow-up to http://review.typo3.org/35131

Releases: master
Resolves: #63636
Change-Id: I35b29c2163536cae16388510f7e2268c1a5cb565
Reviewed-on: http://review.typo3.org/35132
Reviewed-by: Stanislas Rolland <typo3@sjbr.ca>
Tested-by: Stanislas Rolland <typo3@sjbr.ca>
typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/Abbreviation.js [new file with mode: 0644]
typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/Language.js [new file with mode: 0644]
typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Color.js [new file with mode: 0644]
typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Image.js [new file with mode: 0644]
typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Link.js [new file with mode: 0644]

diff --git a/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/Abbreviation.js b/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/Abbreviation.js
new file mode 100644 (file)
index 0000000..af479e9
--- /dev/null
@@ -0,0 +1,561 @@
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+/**
+ * Abbreviation plugin for htmlArea RTE
+ */
+define('TYPO3/CMS/Rtehtmlarea/Plugins/Abbreviation',
+       ['TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util'],
+       function (Plugin, Util) {
+
+       var Abbreviation = Ext.extend(Plugin, {
+
+               /**
+                * This function gets called by the class constructor
+                */
+               configurePlugin: function(editor) {
+                       this.pageTSConfiguration = this.editorConfiguration.buttons.abbreviation;
+                       /*
+                        * Registering plugin "About" information
+                        */
+                       var pluginInformation = {
+                               version         : '7.0',
+                               developer       : 'Stanislas Rolland',
+                               developerUrl    : 'http://www.sjbr.ca/',
+                               copyrightOwner  : 'Stanislas Rolland',
+                               sponsor         : 'SJBR',
+                               sponsorUrl      : 'http://www.sjbr.ca/',
+                               license         : 'GPL'
+                       };
+                       this.registerPluginInformation(pluginInformation);
+                       /*
+                        * Registering the button
+                        */
+                       var buttonId = 'Abbreviation';
+                       var buttonConfiguration = {
+                               id              : buttonId,
+                               tooltip         : this.localize('Insert abbreviation'),
+                               action          : 'onButtonPress',
+                               hide            : (this.pageTSConfiguration.noAcronym && this.pageTSConfiguration.noAbbr),
+                               dialog          : true,
+                               iconCls         : 'htmlarea-action-abbreviation-edit',
+                               contextMenuTitle: this.localize(buttonId + '-contextMenuTitle')
+                       };
+                       this.registerButton(buttonConfiguration);
+                       return true;
+                },
+               /*
+                * Sets of default configuration values for dialogue form fields
+                */
+               configDefaults: {
+                       combo: {
+                               editable: true,
+                               selectOnFocus: true,
+                               typeAhead: true,
+                               triggerAction: 'all',
+                               forceSelection: true,
+                               mode: 'local'
+                       }
+               },
+               /*
+                * This function gets called when the button was pressed
+                *
+                * @param       object          editor: the editor instance
+                * @param       string          id: the button id or the key
+                *
+                * @return      boolean         false if action is completed
+                */
+               onButtonPress: function(editor, id) {
+                               // Could be a button or its hotkey
+                       var buttonId = this.translateHotKey(id);
+                       buttonId = buttonId ? buttonId : id;
+                       var abbr = editor.getSelection().getParentElement();
+                               // Working around Safari issue
+                       if (!abbr && this.editor.statusBar && this.editor.statusBar.getSelection()) {
+                               abbr = this.editor.statusBar.getSelection();
+                       }
+                       if (!abbr || !/^(acronym|abbr)$/i.test(abbr.nodeName)) {
+                               abbr = editor.getSelection().getFirstAncestorOfType(['acronym', 'abbr']);
+                       }
+                       var type = typeof abbr === 'object' && abbr !== null ? abbr.nodeName.toLowerCase() : '';
+                       this.params = {
+                               abbr: abbr,
+                               title: typeof abbr === 'object' && abbr !== null ? abbr.title : '',
+                               text: typeof abbr === 'object' && abbr !== null ? abbr.innerHTML : this.editor.getSelection().getHtml()
+                       };
+                               // Open the dialogue window
+                       this.openDialogue(
+                               this.getButton(buttonId).tooltip.title,
+                               buttonId,
+                               this.getWindowDimensions({ width: 580}, buttonId),
+                               this.buildTabItemsConfig(abbr),
+                               this.buildButtonsConfig(abbr, this.okHandler, this.deleteHandler),
+                               (type == 'acronym') ? 1 : 0
+                       );
+                       return false;
+               },
+               /*
+                * Open the dialogue window
+                *
+                * @param       string          title: the window title
+                * @param       string          buttonId: the itemId of the button that was pressed
+                * @param       integer         dimensions: the opening width of the window
+                * @param       object          tabItems: the configuration of the tabbed panel
+                * @param       object          buttonsConfig: the configuration of the buttons
+                * @param       number          activeTab: index of the opening tab
+                *
+                * @return      void
+                */
+               openDialogue: function (title, buttonId, dimensions, tabItems, buttonsConfig, activeTab) {
+                       this.dialog = new Ext.Window({
+                               title: this.getHelpTip('', title),
+                               cls: 'htmlarea-window',
+                               border: false,
+                               width: dimensions.width,
+                               height: 'auto',
+                               iconCls: this.getButton(buttonId).iconCls,
+                               listeners: {
+                                       close: {
+                                               fn: this.onClose,
+                                               scope: this
+                                       }
+                               },
+                               items: {
+                                       xtype: 'tabpanel',
+                                       activeTab: activeTab ? activeTab : 0,
+                                       defaults: {
+                                               xtype: 'container',
+                                               layout: 'form',
+                                               defaults: {
+                                                       labelWidth: 150
+                                               }
+                                       },
+                                       listeners: {
+                                               tabchange: {
+                                                       fn: this.syncHeight,
+                                                       scope: this
+                                               }
+                                       },
+                                       items: tabItems
+                               },
+                               buttons: buttonsConfig
+                       });
+                       this.show();
+               },
+               /*
+                * Build the dialogue tab items config
+                *
+                * @param       object          element: the element being edited, if any
+                *
+                * @return      object          the tab items configuration
+                */
+               buildTabItemsConfig: function (element) {
+                       var type = typeof element === 'object' && element !== null ? element.nodeName.toLowerCase() : '';
+                       var tabItems = [];
+                       var abbrTabItems = [];
+                               // abbr tab not shown if the current selection is an acronym
+                       if (type !== 'acronym') {
+                               if (!this.pageTSConfiguration.noAbbr) {
+                                       this.addConfigElement(this.buildDefinedTermFieldsetConfig((type == 'abbr') ? element : null, 'abbr'), abbrTabItems);
+                               }
+                               this.addConfigElement(this.buildUseTermFieldsetConfig((type == 'abbr') ? element : null, 'abbr'), abbrTabItems);
+                       }
+                       if (abbrTabItems.length > 0) {
+                               tabItems.push({
+                                       title: this.localize('Abbreviation'),
+                                       itemId: 'abbr',
+                                       items: abbrTabItems
+                               });
+                       }
+                       var acronymTabItems = [];
+                               // acronym tab not shown if the current selection is an abbr
+                       if (type !== 'abbr') {
+                               if (!this.pageTSConfiguration.noAcronym) {
+                                       this.addConfigElement(this.buildDefinedTermFieldsetConfig((type == 'acronym') ? element : null, 'acronym'), acronymTabItems);
+                               }
+                               this.addConfigElement(this.buildUseTermFieldsetConfig((type == 'abbr') ? element : null, 'abbr'), acronymTabItems);
+                       }
+                       if (acronymTabItems.length > 0) {
+                               tabItems.push({
+                                       title: this.localize('Acronym'),
+                                       itemId: 'acronym',
+                                       items: acronymTabItems
+                               });
+                       }
+                       return tabItems;
+               },
+               /*
+                * Build the dialogue buttons config
+                *
+                * @param       object          element: the element being edited, if any
+                * @param       function        okHandler: the handler for the ok button
+                * @param       function        deleteHandler: the handler for the delete button
+                *
+                * @return      object          the buttons configuration
+                */
+               buildButtonsConfig: function (element, okHandler, deleteHandler) {
+                       var buttonsConfig = [this.buildButtonConfig('OK', okHandler)];
+                       if (element) {
+                               buttonsConfig.push(this.buildButtonConfig('Delete', deleteHandler));
+                       }
+                       buttonsConfig.push(this.buildButtonConfig('Cancel', this.onCancel));
+                       return buttonsConfig;
+               },
+
+               /**
+                * This function builds the configuration object for the defined Abbreviation or Acronym fieldset
+                *
+                * @param object element: the element being edited, if any
+                * @param string type: 'abbr' or 'acronym'
+                *
+                * @return object the fieldset configuration object
+                */
+               buildDefinedTermFieldsetConfig: function (element, type) {
+                       var itemsConfig = [];
+                       itemsConfig.push(Util.apply({
+                               xtype: 'combo',
+                               displayField: 'term',
+                               valueField: 'term',
+                               fieldLabel: this.getHelpTip('unabridgedTerm', 'Unabridged_term'),
+                               itemId: 'termSelector',
+                               tpl: '<tpl for="."><div ext:qtip="{abbr}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{term}</div></tpl>',
+                               store: new Ext.data.JsonStore({
+                                       autoDestroy:  true,
+                                       autoLoad: true,
+                                       root: type,
+                                       fields: [ { name: 'term'}, { name: 'abbr'},  { name: 'language'}],
+                                       url: this.pageTSConfiguration.abbreviationUrl
+                               }),
+                               width: 350,
+                               listeners: {
+                                       beforerender: {
+                                               fn: function (combo) {
+                                                               // Ensure the store is loaded
+                                                       combo.getStore().load({
+                                                               callback: function () { this.onSelectorRender(combo); },
+                                                               scope: this
+                                                       });
+                                               },
+                                               scope: this
+                                       },
+                                       select: {
+                                               fn: this.onTermSelect,
+                                               scope: this
+                                       }
+                               }
+                       }, this.configDefaults['combo']));
+                       itemsConfig.push(Util.apply({
+                               xtype: 'combo',
+                               displayField: 'abbr',
+                               valueField: 'abbr',
+                               tpl: '<tpl for="."><div ext:qtip="{language}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{abbr}</div></tpl>',
+                               fieldLabel: this.getHelpTip('abridgedTerm', 'Abridged_term'),
+                               itemId: 'abbrSelector',
+                               store: new Ext.data.JsonStore({
+                                       autoDestroy:  true,
+                                       autoLoad: true,
+                                       root: type,
+                                       fields: [ { name: 'term'}, { name: 'abbr'},  { name: 'language'}],
+                                       url: this.pageTSConfiguration.abbreviationUrl
+                               }),
+                               width: 100,
+                               listeners: {
+                                       beforerender: {
+                                               fn: function (combo) {
+                                                               // Ensure the store is loaded
+                                                       combo.getStore().load({
+                                                               callback: function () { this.onSelectorRender(combo); },
+                                                               scope: this
+                                                       });
+                                               },
+                                               scope: this
+                                       },
+                                       select: {
+                                               fn: this.onAbbrSelect,
+                                               scope: this
+                                       }
+                               }
+                       }, this.configDefaults['combo']));
+                       var languageObject = this.getPluginInstance('Language');
+                       if (this.getButton('Language')) {
+                               var selectedLanguage = typeof element === 'object' && element !== null ? languageObject.getLanguageAttribute(element) : 'none';
+                               itemsConfig.push(Util.apply({
+                                       xtype: 'combo',
+                                       fieldLabel: this.getHelpTip('language', 'Language'),
+                                       itemId: 'language',
+                                       valueField: 'value',
+                                       displayField: 'text',
+                                       tpl: '<tpl for="."><div ext:qtip="{value}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>',
+                                       store: new Ext.data.JsonStore({
+                                               autoDestroy:  true,
+                                               root: 'options',
+                                               fields: [ { name: 'text'}, { name: 'value'} ],
+                                               url: this.getDropDownConfiguration('Language').dataUrl,
+                                               listeners: {
+                                                       load: {
+                                                               fn: function (store) {
+                                                                       if (selectedLanguage !== 'none') {
+                                                                               store.removeAt(0);
+                                                                               store.insert(0, new store.recordType({
+                                                                                       text: languageObject.localize('Remove language mark'),
+                                                                                       value: 'none'
+                                                                               }));
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }),
+                                       width: 200,
+                                       value: selectedLanguage,
+                                       listeners: {
+                                               beforerender: {
+                                                       fn: function (combo) {
+                                                               // Ensure the store is loaded
+                                                               combo.getStore().load({
+                                                                       callback: function () { combo.setValue(selectedLanguage); }
+                                                               });
+                                                       }
+                                               }
+                                       }
+                               }, this.configDefaults['combo']));
+                       }
+                       return {
+                               xtype: 'fieldset',
+                               title: this.getHelpTip('preDefined' + ((type == 'abbr') ? 'Abbreviation' : 'Acronym'), 'Defined_' + type),
+                               items: itemsConfig,
+                               listeners: {
+                                       render: {
+                                               fn: this.onDefinedTermFieldsetRender,
+                                               scope: this
+                                       }
+                               }
+                       };
+               },
+               /*
+                * Handler on rendering the defined abbreviation fieldset
+                * If an abbr is selected but no term is selected, select any corresponding term with the correct language value, if any
+                */
+               onDefinedTermFieldsetRender: function (fieldset) {
+                       var termSelector = fieldset.find('itemId', 'termSelector')[0];
+                       var term = termSelector.getValue();
+                       var abbrSelector = fieldset.find('itemId', 'abbrSelector')[0];
+                       var abbr = abbrSelector.getValue();
+                       var language = '';
+                       var languageSelector = fieldset.find('itemId', 'language')[0];
+                       if (languageSelector) {
+                               var language = languageSelector.getValue();
+                               if (language == 'none') {
+                                       language = '';
+                               }
+                       }
+                       if (abbr && !term) {
+                               var abbrStore = abbrSelector.getStore();
+                               var index = abbrStore.findBy(function (record) {
+                                       return record.get('abbr') == abbr && (!languageSelector || record.get('language') == language);
+                               }, this);
+                               if (index !== -1) {
+                                       term = abbrStore.getAt(index).get('term');
+                                       termSelector.setValue(term);
+                                       fieldset.ownerCt.find('itemId', 'useTerm')[0].setValue(term);
+                               }
+                       }
+               },
+               /*
+                * Filter the term and abbr selector lists
+                * Set initial values
+                * If there is already an abbr and the filtered list has only one or no element, hide the fieldset
+                */
+               onSelectorRender: function (combo) {
+                       var store = combo.getStore();
+                       store.filterBy(function (record) {
+                               return !this.params.text || !this.params.title || this.params.text == record.get('term') || this.params.title == record.get('term') || this.params.title == record.get('abbr');
+                       }, this);
+                               // Make sure the combo list is filtered
+                       store.snapshot = store.data;
+                       var store = combo.getStore();
+                               // Initialize the term and abbr combos
+                       if (combo.getItemId() == 'termSelector') {
+                               if (this.params.title) {
+                                       var index = store.findExact('term', this.params.title);
+                                       if (index !== -1) {
+                                               var record = store.getAt(index);
+                                               combo.setValue(record.get('term'));
+                                               this.onTermSelect(combo, record, index);
+                                       }
+                               } else if (this.params.text) {
+                                       var index = store.findExact('term', this.params.text);
+                                       if (index !== -1) {
+                                               var record = store.getAt(index);
+                                               combo.setValue(record.get('term'));
+                                               this.onTermSelect(combo, record, index);
+                                       }
+                               }
+                       } else if (combo.getItemId() == 'abbrSelector' && this.params.text) {
+                               var index = store.findExact('abbr', this.params.text);
+                               if (index !== -1) {
+                                       var record = store.getAt(index);
+                                       combo.setValue(record.get('abbr'));
+                                       this.onAbbrSelect(combo, record, index);
+                               }
+                       }
+               },
+               /*
+                * Handler when a term is selected
+                */
+               onTermSelect: function (combo, record, index) {
+                       var fieldset = combo.findParentByType('fieldset');
+                       var tab = fieldset.findParentByType('container');
+                       var term = record.get('term');
+                       var abbr = record.get('abbr');
+                       var language = record.get('language');
+                               // Update the abbreviation selector
+                       var abbrSelector = tab.find('itemId', 'abbrSelector')[0];
+                       abbrSelector.setValue(abbr);
+                               // Update the language selector
+                       var languageSelector = tab.find('itemId', 'language');
+                       if (languageSelector.length > 0) {
+                               if (language) {
+                                       languageSelector[0].setValue(language);
+                               } else {
+                                       languageSelector[0].setValue('none');
+                               }
+                       }
+                               // Update the term to use
+                       tab.find('itemId', 'useTerm')[0].setValue(term);
+               },
+               /*
+                * Handler when an abbreviation or acronym is selected
+                */
+               onAbbrSelect: function (combo, record, index) {
+                       var fieldset = combo.findParentByType('fieldset');
+                       var tab = fieldset.findParentByType('container');
+                       var term = record.get('term');
+                       var language = record.get('language');
+                               // Update the term selector
+                       var termSelector = tab.find('itemId', 'termSelector')[0];
+                       termSelector.setValue(term);
+                               // Update the language selector
+                       var languageSelector = tab.find('itemId', 'language');
+                       if (languageSelector.length > 0) {
+                               if (language) {
+                                       languageSelector[0].setValue(language);
+                               } else {
+                                       languageSelector[0].setValue('none');
+                               }
+                       }
+                               // Update the term to use
+                       tab.find('itemId', 'useTerm')[0].setValue(term);
+               },
+               /*
+                * This function builds the configuration object for the Abbreviation or Acronym to use fieldset
+                *
+                * @param       object          element: the element being edited, if any
+                *
+                * @return      object          the fieldset configuration object
+                */
+               buildUseTermFieldsetConfig: function (element, type) {
+                       var itemsConfig = [];
+                       itemsConfig.push({
+                               fieldLabel: this.getHelpTip('useThisTerm', 'Use_this_term'),
+                               labelSeparator: '',
+                               itemId: 'useTerm',
+                               value: element ? element.title : '',
+                               width: 300
+                       });
+                       return {
+                               xtype: 'fieldset',
+                               title: this.getHelpTip('termToAbridge', 'Term_to_abridge'),
+                               defaultType: 'textfield',
+                               items: itemsConfig
+                       };
+               },
+               /*
+                * Handler when the ok button is pressed
+                */
+               okHandler: function (button, event) {
+                       this.restoreSelection();
+                       var tab = this.dialog.findByType('tabpanel')[0].getActiveTab();
+                       var type = tab.getItemId();
+                       var languageSelector = tab.find('itemId', 'language');
+                       var language = languageSelector && languageSelector.length > 0 ? languageSelector[0].getValue() : '';
+                       var termSelector = tab.find('itemId', 'termSelector');
+                       var term = termSelector && termSelector.length > 0 ? termSelector[0].getValue() : '';
+                       var abbrSelector = tab.find('itemId', 'abbrSelector');
+                       if (!this.params.abbr) {
+                               var abbr = this.editor.document.createElement(type);
+                               abbr.title = tab.find('itemId', 'useTerm')[0].getValue();
+                               if (term == abbr.title && abbrSelector && abbrSelector.length > 0) {
+                                       abbr.innerHTML = abbrSelector[0].getValue();
+                               } else {
+                                       abbr.innerHTML = this.params.text;
+                               }
+                               if (language) {
+                                       this.getPluginInstance('Language').setLanguageAttributes(abbr, language);
+                               }
+                               this.editor.getSelection().insertNode(abbr);
+                               // Position the cursor just after the inserted abbreviation
+                               abbr = this.editor.getSelection().getParentElement();
+                               if (abbr.nextSibling) {
+                                       this.editor.getSelection().selectNodeContents(abbr.nextSibling, true);
+                               } else {
+                                       this.editor.getSelection().selectNodeContents(abbr.parentNode, false);
+                               }
+                       } else {
+                               var abbr = this.params.abbr;
+                               abbr.title = tab.find('itemId', 'useTerm')[0].getValue();
+                               if (language) {
+                                       this.getPluginInstance('Language').setLanguageAttributes(abbr, language);
+                               }
+                               if (term == abbr.title && abbrSelector && abbrSelector.length > 0) {
+                                       abbr.innerHTML = abbrSelector[0].getValue();
+                               }
+                       }
+                       this.close();
+                       event.stopEvent();
+               },
+               /*
+                * Handler when the delete button is pressed
+                */
+               deleteHandler: function (button, event) {
+                       this.restoreSelection();
+                       var abbr = this.params.abbr;
+                       if (abbr) {
+                               this.editor.getDomNode().removeMarkup(abbr);
+                       }
+                       this.close();
+                       event.stopEvent();
+               },
+               /*
+                * This function gets called when the toolbar is updated
+                */
+               onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
+                       if ((mode === 'wysiwyg') && this.editor.isEditable()) {
+                               var el = this.editor.getSelection().getParentElement();
+                               if (el) {
+                                       button.setDisabled(((el.nodeName.toLowerCase() == 'acronym' && this.pageTSConfiguration.noAcronym) || (el.nodeName.toLowerCase() == 'abbr' && this.pageTSConfiguration.noAbbr)));
+                                       button.setInactive(!(el.nodeName.toLowerCase() == 'acronym' && !this.pageTSConfiguration.noAcronym) && !(el.nodeName.toLowerCase() == 'abbr' && !this.pageTSConfiguration.noAbbr));
+                               }
+                               button.setTooltip({
+                                       title: this.localize((button.disabled || button.inactive) ? 'Insert abbreviation' : 'Edit abbreviation')
+                               });
+                               button.contextMenuTitle = '';
+                               if (this.dialog) {
+                                       this.dialog.focus();
+                               }
+                       }
+               }
+       });
+
+       return Abbreviation;
+
+});
diff --git a/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/Language.js b/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/Language.js
new file mode 100644 (file)
index 0000000..e45d25c
--- /dev/null
@@ -0,0 +1,463 @@
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+/**
+ * Language Plugin for TYPO3 htmlArea RTE
+ */
+define('TYPO3/CMS/Rtehtmlarea/Plugins/Language',
+       ['TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM'],
+       function (Plugin, UserAgent, Dom) {
+
+       var Language = Ext.extend(Plugin, {
+
+               /**
+                * This function gets called by the class constructor
+                */
+               configurePlugin: function (editor) {
+
+                       /**
+                        * Setting up some properties from PageTSConfig
+                        */
+                       this.buttonsConfiguration = this.editorConfiguration.buttons;
+                       this.useAttribute = {};
+                       this.useAttribute.lang = (this.buttonsConfiguration.language && this.buttonsConfiguration.language.useLangAttribute) ? this.buttonsConfiguration.language.useLangAttribute : true;
+                       this.useAttribute.xmlLang = (this.buttonsConfiguration.language && this.buttonsConfiguration.language.useXmlLangAttribute) ? this.buttonsConfiguration.language.useXmlLangAttribute : false;
+                       if (!this.useAttribute.lang && !this.useAttribute.xmlLang) {
+                               this.useAttribute.lang = true;
+                       }
+
+                       // Importing list of allowed attributes
+                       if (this.getPluginInstance('TextStyle')) {
+                               this.allowedAttributes = this.getPluginInstance('TextStyle').allowedAttributes;
+                       }
+                       if (!this.allowedAttributes && this.getPluginInstance('InlineElements')) {
+                               this.allowedAttributes = this.getPluginInstance('InlineElements').allowedAttributes;
+                       }
+                       if (!this.allowedAttributes && this.getPluginInstance('BlockElements')) {
+                               this.allowedAttributes = this.getPluginInstance('BlockElements').allowedAttributes;
+                       }
+                       if (!this.allowedAttributes) {
+                               this.allowedAttributes = new Array('id', 'title', 'lang', 'xml:lang', 'dir', 'class');
+                               if (UserAgent.isIEBeforeIE9) {
+                                       this.allowedAttributes.push('className');
+                               }
+                       }
+                       /*
+                        * Registering plugin "About" information
+                        */
+                       var pluginInformation = {
+                               version         : '2.2',
+                               developer       : 'Stanislas Rolland',
+                               developerUrl    : 'http://www.sjbr.ca/',
+                               copyrightOwner  : 'Stanislas Rolland',
+                               sponsor         : this.localize('Technische Universitat Ilmenau'),
+                               sponsorUrl      : 'http://www.tu-ilmenau.de/',
+                               license         : 'GPL'
+                       };
+                       this.registerPluginInformation(pluginInformation);
+                       /*
+                        * Registering the buttons
+                        */
+                       var buttonList = this.buttonList, buttonId;
+                       for (var i = 0, n = buttonList.length; i < n; ++i) {
+                               var button = buttonList[i];
+                               buttonId = button[0];
+                               var buttonConfiguration = {
+                                       id              : buttonId,
+                                       tooltip         : this.localize(buttonId + '-Tooltip'),
+                                       iconCls         : 'htmlarea-action-' + button[2],
+                                       action          : 'onButtonPress',
+                                       context         : button[1]
+                               };
+                               this.registerButton(buttonConfiguration);
+                       }
+                       /*
+                        * Registering the dropdown list
+                        */
+                       var buttonId = 'Language';
+                       if (this.buttonsConfiguration[buttonId.toLowerCase()] && this.buttonsConfiguration[buttonId.toLowerCase()].dataUrl) {
+                               var dropDownConfiguration = {
+                                       id              : buttonId,
+                                       tooltip         : this.localize(buttonId + '-Tooltip'),
+                                       storeUrl        : this.buttonsConfiguration[buttonId.toLowerCase()].dataUrl,
+                                       action          : 'onChange'
+                               };
+                               if (this.buttonsConfiguration.language) {
+                                       dropDownConfiguration.width = this.buttonsConfiguration.language.width ? parseInt(this.buttonsConfiguration.language.width, 10) : 200;
+                                       if (this.buttonsConfiguration.language.listWidth) {
+                                               dropDownConfiguration.listWidth = parseInt(this.buttonsConfiguration.language.listWidth, 10);
+                                       }
+                                       if (this.buttonsConfiguration.language.maxHeight) {
+                                               dropDownConfiguration.maxHeight = parseInt(this.buttonsConfiguration.language.maxHeight, 10);
+                                       }
+                               }
+                               this.registerDropDown(dropDownConfiguration);
+                       }
+                       return true;
+               },
+               /*
+                * The list of buttons added by this plugin
+                */
+               buttonList: [
+                       ['LeftToRight', null, 'text-direction-left-to-right'],
+                       ['RightToLeft', null, 'text-direction-right-to-left'],
+                       ['ShowLanguageMarks', null, 'language-marks-show']
+               ],
+               /*
+                * This function gets called when the editor is generated
+                */
+               onGenerate: function () {
+                       var select = this.getButton('Language');
+                       if (select) {
+                               if (select.getStore().getCount() > 1) {
+                                       this.addLanguageMarkingRules();
+                               } else {
+                                               // Monitor the language combo's store being loaded
+                                       select.mon(select.getStore(), 'load', function () {
+                                               this.addLanguageMarkingRules();
+                                               var selection = this.editor.getSelection(),
+                                                       selectionEmpty = selection.isEmpty(),
+                                                       ancestors = selection.getAllAncestors(),
+                                                       endPointsInSameBlock = selection.endPointsInSameBlock();
+                                               this.onUpdateToolbar(select, this.getEditorMode(), selectionEmpty, ancestors, endPointsInSameBlock);
+                                       }, this);
+                               }
+                       }
+               },
+               /*
+                * This function adds rules to the stylesheet for language mark highlighting
+                * Model: body.htmlarea-show-language-marks *[lang=en]:before { content: "en: "; }
+                * Works in IE8, but not in earlier versions of IE
+                */
+               addLanguageMarkingRules: function () {
+                       var select = this.getButton('Language');
+                       if (select) {
+                               var styleSheet = this.editor.document.styleSheets[0];
+                               select.getStore().each(function (option) {
+                                       var selector = 'body.htmlarea-show-language-marks *[' + 'lang="' + option.get('value') + '"]:before';
+                                       var style = 'content: "' + option.get('value') + ': ";';
+                                       var rule = selector + ' { ' + style + ' }';
+                                       if (!UserAgent.isIEBeforeIE9) {
+                                               try {
+                                                       styleSheet.insertRule(rule, styleSheet.cssRules.length);
+                                               } catch (e) {
+                                                       this.appendToLog('onGenerate', 'Error inserting css rule: ' + rule + ' Error text: ' + e, 'warn');
+                                               }
+                                       } else {
+                                               styleSheet.addRule(selector, style);
+                                       }
+                                       return true;
+                               }, this);
+                       }
+               },
+
+               /**
+                * This function gets called when a button was pressed.
+                *
+                * @param       object          editor: the editor instance
+                * @param       string          id: the button id or the key
+                *
+                * @return      boolean         false if action is completed
+                */
+               onButtonPress: function (editor, id, target) {
+                       // Could be a button or its hotkey
+                       var buttonId = this.translateHotKey(id);
+                       buttonId = buttonId ? buttonId : id;
+                       switch (buttonId) {
+                               case 'RightToLeft':
+                               case 'LeftToRight':
+                                       this.setDirAttribute(buttonId);
+                                       break;
+                               case 'ShowLanguageMarks':
+                                       this.toggleLanguageMarks();
+                                       break;
+                               default :
+                                       break;
+                       }
+                       return false;
+               },
+
+               /**
+                * Sets the dir attribute
+                *
+                * @param       string          buttonId: the button id
+                *
+                * @return      void
+                */
+               setDirAttribute: function (buttonId) {
+                       var direction = (buttonId == 'RightToLeft') ? 'rtl' : 'ltr';
+                       var element = this.editor.getSelection().getParentElement();
+                       if (element) {
+                               if (/^bdo$/i.test(element.nodeName)) {
+                                       element.dir = direction;
+                               } else {
+                                       element.dir = (element.dir == direction || element.style.direction == direction) ? '' : direction;
+                               }
+                               element.style.direction = '';
+                       }
+                },
+               /*
+                * Toggles the display of language marks
+                *
+                * @param       boolean         forceLanguageMarks: if set, language marks are displayed whatever the current state
+                *
+                * @return      void
+                */
+               toggleLanguageMarks: function (forceLanguageMarks) {
+                       var body = this.editor.document.body;
+                       if (!Dom.hasClass(body, 'htmlarea-show-language-marks')) {
+                               Dom.addClass(body,'htmlarea-show-language-marks');
+                       } else if (!forceLanguageMarks) {
+                               Dom.removeClass(body,'htmlarea-show-language-marks');
+                       }
+               },
+               /*
+                * This function gets called when some language was selected in the drop-down list
+                */
+               onChange: function (editor, combo, record, index) {
+                       this.applyLanguageMark(combo.getValue());
+               },
+               /*
+                * This function applies the langauge mark to the selection
+                */
+               applyLanguageMark: function (language) {
+                       var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
+                       var range = this.editor.getSelection().createRange();
+                       var parent = this.editor.getSelection().getParentElement();
+                       var selectionEmpty = this.editor.getSelection().isEmpty();
+                       var endPointsInSameBlock = this.editor.getSelection().endPointsInSameBlock();
+                       var fullNodeSelected = false;
+                       if (!selectionEmpty) {
+                               if (endPointsInSameBlock) {
+                                       var ancestors = this.editor.getSelection().getAllAncestors();
+                                       for (var i = 0; i < ancestors.length; ++i) {
+                                               fullNodeSelected =  (!UserAgent.isIEBeforeIE9 && ((statusBarSelection === ancestors[i] && ancestors[i].textContent === range.toString()) || (!statusBarSelection && ancestors[i].textContent === range.toString())))
+                                                       || (UserAgent.isIEBeforeIE9 && statusBarSelection === ancestors[i] && ((this.editor.getSelection().getType() !== 'Control' && ancestors[i].innerText === range.text) || (this.editor.getSelection().getType() === 'Control' && ancestors[i].innerText === range.item(0).text)));
+                                               if (fullNodeSelected) {
+                                                       parent = ancestors[i];
+                                                       break;
+                                               }
+                                       }
+                                               // Working around bug in Safari selectNodeContents
+                                       if (!fullNodeSelected && UserAgent.isWebKit && statusBarSelection && statusBarSelection.textContent === range.toString()) {
+                                               fullNodeSelected = true;
+                                               parent = statusBarSelection;
+                                       }
+                               }
+                       }
+                       if (selectionEmpty || fullNodeSelected) {
+                                       // Selection is empty or parent is selected in the status bar
+                               if (parent) {
+                                               // Set language attributes
+                                       this.setLanguageAttributes(parent, language);
+                               }
+                       } else if (endPointsInSameBlock) {
+                                       // The selection is not empty, nor full element
+                               if (language != 'none') {
+                                               // Add span element with lang attribute(s)
+                                       var newElement = this.editor.document.createElement('span');
+                                       this.setLanguageAttributes(newElement, language);
+                                       this.editor.getDomNode().wrapWithInlineElement(newElement, range);
+                                       if (!UserAgent.isIEBeforeIE9) {
+                                               range.detach();
+                                       }
+                               }
+                       } else {
+                               this.setLanguageAttributeOnBlockElements(language);
+                       }
+               },
+
+               /**
+                * This function gets the language attribute on the given element
+                *
+                * @param       object          element: the element from which to retrieve the attribute value
+                *
+                * @return      string          value of the lang attribute, or of the xml:lang attribute
+                */
+               getLanguageAttribute: function (element) {
+                       var xmllang = 'none';
+                       try {
+                                       // IE7 complains about xml:lang
+                               xmllang = element.getAttribute('xml:lang') ? element.getAttribute('xml:lang') : 'none';
+                       } catch(e) { }
+                       return element.getAttribute('lang') ? element.getAttribute('lang') : xmllang;
+               },
+               /*
+                * This function sets the language attributes on the given element
+                *
+                * @param       object          element: the element on which to set the value of the lang and/or xml:lang attribute
+                * @param       string          language: value of the lang attributes, or "none", in which case, the attribute(s) is(are) removed
+                *
+                * @return      void
+                */
+               setLanguageAttributes: function (element, language) {
+                       if (element) {
+                               if (language == 'none') {
+                                               // Remove language mark, if any
+                                       element.removeAttribute('lang');
+                                       try {
+                                                       // Do not let IE7 complain
+                                               element.removeAttribute('xml:lang');
+                                       } catch(e) { }
+                                               // Remove the span tag if it has no more attribute
+                                       if (/^span$/i.test(element.nodeName) && !Dom.hasAllowedAttributes(element, this.allowedAttributes)) {
+                                               this.editor.getDomNode().removeMarkup(element);
+                                       }
+                               } else {
+                                       if (this.useAttribute.lang) {
+                                               element.setAttribute('lang', language);
+                                       }
+                                       if (this.useAttribute.xmlLang) {
+                                               try {
+                                                               // Do not let IE7 complain
+                                                       element.setAttribute('xml:lang', language);
+                                               } catch(e) { }
+                                       }
+                               }
+                       }
+               },
+               /*
+                * This function gets the language attributes from blocks sibling of the block containing the start container of the selection
+                *
+                * @return      string          value of the lang attribute, or of the xml:lang attribute, or "none", if all blocks sibling do not have the same attribute value as the block containing the start container
+                */
+               getLanguageAttributeFromBlockElements: function () {
+                       var endBlocks = this.editor.getSelection().getEndBlocks();
+                       var startAncestors = Dom.getBlockAncestors(endBlocks.start);
+                       var endAncestors = Dom.getBlockAncestors(endBlocks.end);
+                       var index = 0;
+                       while (index < startAncestors.length && index < endAncestors.length && startAncestors[index] === endAncestors[index]) {
+                               ++index;
+                       }
+                       if (endBlocks.start === endBlocks.end) {
+                               --index;
+                       }
+                       var language = this.getLanguageAttribute(startAncestors[index]);
+                       for (var block = startAncestors[index]; block; block = block.nextSibling) {
+                               if (Dom.isBlockElement(block)) {
+                                       if (this.getLanguageAttribute(block) != language || this.getLanguageAttribute(block) == 'none') {
+                                               language = 'none';
+                                               break;
+                                       }
+                               }
+                               if (block == endAncestors[index]) {
+                                       break;
+                               }
+                       }
+                       return language;
+               },
+               /*
+                * This function sets the language attributes on blocks sibling of the block containing the start container of the selection
+                */
+               setLanguageAttributeOnBlockElements: function (language) {
+                       var endBlocks = this.editor.getSelection().getEndBlocks();
+                       var startAncestors = Dom.getBlockAncestors(endBlocks.start);
+                       var endAncestors = Dom.getBlockAncestors(endBlocks.end);
+                       var index = 0;
+                       while (index < startAncestors.length && index < endAncestors.length && startAncestors[index] === endAncestors[index]) {
+                               ++index;
+                       }
+                       if (endBlocks.start === endBlocks.end) {
+                               --index;
+                       }
+                       for (var block = startAncestors[index]; block; block = block.nextSibling) {
+                               if (Dom.isBlockElement(block)) {
+                                       this.setLanguageAttributes(block, language);
+                               }
+                               if (block == endAncestors[index]) {
+                                       break;
+                               }
+                       }
+               },
+
+               /**
+                * This function gets called when the toolbar is updated
+                */
+               onUpdateToolbar: function (button, mode, selectionEmpty, ancestors, endPointsInSameBlock) {
+                       if (mode === 'wysiwyg' && this.editor.isEditable()) {
+                               var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
+                               var range = this.editor.getSelection().createRange();
+                               var parent = this.editor.getSelection().getParentElement();
+                               switch (button.itemId) {
+                                       case 'RightToLeft':
+                                       case 'LeftToRight':
+                                               if (parent) {
+                                                       var direction = (button.itemId === 'RightToLeft') ? 'rtl' : 'ltr';
+                                                       button.setInactive(parent.dir != direction && parent.style.direction != direction);
+                                                       button.setDisabled(/^body$/i.test(parent.nodeName));
+                                               } else {
+                                                       button.setDisabled(true);
+                                               }
+                                               break;
+                                       case 'ShowLanguageMarks':
+                                               button.setInactive(!Dom.hasClass(this.editor.document.body, 'htmlarea-show-language-marks'));
+                                               break;
+                                       case 'Language':
+                                                       // Updating the language drop-down
+                                               var fullNodeSelected = false;
+                                               var language = this.getLanguageAttribute(parent);
+                                               if (!selectionEmpty) {
+                                                       if (endPointsInSameBlock) {
+                                                               for (var i = 0; i < ancestors.length; ++i) {
+                                                                       fullNodeSelected =  (!UserAgent.isIEBeforeIE9 && ((statusBarSelection === ancestors[i] && ancestors[i].textContent === range.toString()) || (!statusBarSelection && ancestors[i].textContent === range.toString())))
+                                                                               || (UserAgent.isIEBeforeIE9 && statusBarSelection === ancestors[i] && ((this.editor.getSelection().getType() !== 'Control' && ancestors[i].innerText === range.text) || (this.editor.getSelection().getType() === 'Control' && ancestors[i].innerText === range.item(0).text)));
+                                                                       if (fullNodeSelected) {
+                                                                               parent = ancestors[i];
+                                                                               break;
+                                                                       }
+                                                               }
+                                                                       // Working around bug in Safari selectNodeContents
+                                                               if (!fullNodeSelected && UserAgent.isWebKit && statusBarSelection && statusBarSelection.textContent === range.toString()) {
+                                                                       fullNodeSelected = true;
+                                                                       parent = statusBarSelection;
+                                                               }
+                                                               language = this.getLanguageAttribute(parent);
+                                                       } else {
+                                                               language = this.getLanguageAttributeFromBlockElements();
+                                                       }
+                                               }
+                                               this.updateValue(button, language, selectionEmpty, fullNodeSelected, endPointsInSameBlock);
+                                               break;
+                                       default:
+                                               break;
+                               }
+                       }
+               },
+
+               /**
+                * This function updates the language drop-down list
+                */
+               updateValue: function (select, language, selectionEmpty, fullNodeSelected, endPointsInSameBlock) {
+                       var store = select.getStore();
+                       store.removeAt(0);
+                       if ((store.findExact('value', language) != -1) && (selectionEmpty || fullNodeSelected || !endPointsInSameBlock)) {
+                               select.setValue(language);
+                               store.insert(0, new store.recordType({
+                                       text: this.localize('Remove language mark'),
+                                       value: 'none'
+                               }));
+                       } else {
+                               store.insert(0, new store.recordType({
+                                       text: this.localize('No language mark'),
+                                       value: 'none'
+                               }));
+                               select.setValue('none');
+                       }
+                       select.setDisabled(!(store.getCount()>1) || (selectionEmpty && /^body$/i.test(this.editor.getSelection().getParentElement().nodeName)));
+               }
+       });
+
+       return Language;
+
+});
diff --git a/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Color.js b/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Color.js
new file mode 100644 (file)
index 0000000..703c51c
--- /dev/null
@@ -0,0 +1,406 @@
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+/**
+ * TYPO3 Color Plugin for TYPO3 htmlArea RTE
+ */
+define('TYPO3/CMS/Rtehtmlarea/Plugins/TYPO3Color',
+       ['TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Color',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/Extjs/ColorPalette'],
+       function (Plugin, UserAgent, Dom, Color, ColorPalette) {
+
+       var TYPO3Color = Ext.extend(Plugin, {
+
+               /**
+                * This function gets called by the class constructor
+                */
+               configurePlugin: function (editor) {
+                       this.buttonsConfiguration = this.editorConfiguration.buttons;
+                       this.colorsConfiguration = this.editorConfiguration.colors;
+                       this.disableColorPicker = this.editorConfiguration.disableColorPicker;
+                               // Coloring will use the style attribute
+                       if (this.getPluginInstance('TextStyle')) {
+                               this.getPluginInstance('TextStyle').addAllowedAttribute('style');
+                               this.allowedAttributes = this.getPluginInstance('TextStyle').allowedAttributes;
+                       }
+                       if (this.getPluginInstance('InlineElements')) {
+                               this.getPluginInstance('InlineElements').addAllowedAttribute('style');
+                               if (!this.allowedAllowedAttributes) {
+                                       this.allowedAttributes = this.getPluginInstance('InlineElements').allowedAttributes;
+                               }
+                       }
+                       if (this.getPluginInstance('BlockElements')) {
+                               this.getPluginInstance('BlockElements').addAllowedAttribute('style');
+                       }
+                       if (!this.allowedAttributes) {
+                               this.allowedAttributes = new Array('id', 'title', 'lang', 'xml:lang', 'dir', 'class', 'style');
+                               if (UserAgent.isIEBeforeIE9) {
+                                       this.allowedAttributes.push('className');
+                               }
+                       }
+                       /*
+                        * Registering plugin "About" information
+                        */
+                       var pluginInformation = {
+                               version         : '4.3',
+                               developer       : 'Stanislas Rolland',
+                               developerUrl    : 'http://www.sjbr.ca/',
+                               copyrightOwner  : 'Stanislas Rolland',
+                               sponsor         : 'SJBR',
+                               sponsorUrl      : 'http://www.sjbr.ca/',
+                               license         : 'GPL'
+                       };
+                       this.registerPluginInformation(pluginInformation);
+                       /*
+                        * Registering the buttons
+                        */
+                       var buttonList = this.buttonList, buttonId;
+                       for (var i = 0; i < buttonList.length; ++i) {
+                               var button = buttonList[i];
+                               buttonId = button[0];
+                               var buttonConfiguration = {
+                                       id              : buttonId,
+                                       tooltip         : this.localize(buttonId),
+                                       iconCls         : 'htmlarea-action-' + button[2],
+                                       action          : 'onButtonPress',
+                                       hotKey          : (this.buttonsConfiguration[button[1]] ? this.buttonsConfiguration[button[1]].hotKey : null),
+                                       dialog          : true
+                               };
+                               this.registerButton(buttonConfiguration);
+                       }
+                       return true;
+                },
+               /*
+                * The list of buttons added by this plugin
+                */
+               buttonList: [
+                       ['ForeColor', 'textcolor', 'color-foreground'],
+                       ['HiliteColor', 'bgcolor', 'color-background']
+               ],
+               /*
+                * Conversion object: button name to corresponding style property name
+                */
+               styleProperty: {
+                       ForeColor       : 'color',
+                       HiliteColor     : 'backgroundColor'
+               },
+               colors: [
+                       '000000', '222222', '444444', '666666', '999999', 'BBBBBB', 'DDDDDD', 'FFFFFF',
+                       '660000', '663300', '996633', '003300', '003399', '000066', '330066', '660066',
+                       '990000', '993300', 'CC9900', '006600', '0033FF', '000099', '660099', '990066',
+                       'CC0000', 'CC3300', 'FFCC00', '009900', '0066FF', '0000CC', '663399', 'CC0099',
+                       'FF0000', 'FF3300', 'FFFF00', '00CC00', '0099FF', '0000FF', '9900CC', 'FF0099',
+                       'CC3333', 'FF6600', 'FFFF33', '00FF00', '00CCFF', '3366FF', '9933FF', 'FF00FF',
+                       'FF6666', 'FF6633', 'FFFF66', '66FF66', '00FFFF', '3399FF', '9966FF', 'FF66FF',
+                       'FF9999', 'FF9966', 'FFFF99', '99FF99', '99FFFF', '66CCFF', '9999FF', 'FF99FF',
+                       'FFCCCC', 'FFCC99', 'FFFFCC', 'CCFFCC', 'CCFFFF', '99CCFF', 'CCCCFF', 'FFCCFF'
+               ],
+               /*
+                * This function gets called when the button was pressed.
+                *
+                * @param       object          editor: the editor instance
+                * @param       string          id: the button id or the key
+                * @param       object          target: the target element of the contextmenu event, when invoked from the context menu
+                *
+                * @return      boolean         false if action is completed
+                */
+               onButtonPress: function (editor, id, target) {
+                               // Could be a button or its hotkey
+                       var buttonId = this.translateHotKey(id);
+                       buttonId = buttonId ? buttonId : id;
+                       var element = this.editor.getSelection().getParentElement();
+                       this.openDialogue(
+                               buttonId + '_title',
+                               {
+                                       element: element,
+                                       buttonId: buttonId
+                               },
+                               this.getWindowDimensions(
+                                       {
+                                               width: 350,
+                                               height: 350
+                                       },
+                                       buttonId
+                               ),
+                               this.buildItemsConfig(element, buttonId),
+                               this.setColor
+                       );
+               },
+               /*
+                * Build the window items config
+                */
+               buildItemsConfig: function (element, buttonId) {
+                       var itemsConfig = [];
+                       var paletteItems = [];
+                               // Standard colors palette (boxed)
+                       if (!this.disableColorPicker) {
+                               paletteItems.push({
+                                       xtype: 'container',
+                                       items: {
+                                               xtype: 'colorpalette',
+                                               itemId: 'color-palette',
+                                               colors: this.colors,
+                                               cls: 'color-palette',
+                                               value: (element && element.style[this.styleProperty[buttonId]]) ? Color.colorToHex(element.style[this.styleProperty[buttonId]]).substr(1, 6) : '',
+                                               allowReselect: true,
+                                               listeners: {
+                                                       select: {
+                                                               fn: this.onSelect,
+                                                               scope: this
+                                                       }
+                                               }
+                                       }
+                               });
+                       }
+                               // Custom colors palette (boxed)
+                       if (this.colorsConfiguration) {
+                               paletteItems.push({
+                                       xtype: 'container',
+                                       items: {
+                                               xtype: 'colorpalette',
+                                               itemId: 'custom-colors',
+                                               cls: 'htmlarea-custom-colors',
+                                               colors: this.colorsConfiguration,
+                                               value: (element && element.style[this.styleProperty[buttonId]]) ? Color.colorToHex(element.style[this.styleProperty[buttonId]]).substr(1, 6) : '',
+                                               tpl: new Ext.XTemplate(
+                                                       '<tpl for="."><a href="#" class="color-{1}" hidefocus="on"><em><span style="background:#{1}" unselectable="on">&#160;</span></em><span unselectable="on">{0}<span></a></tpl>'
+                                               ),
+                                               allowReselect: true,
+                                               listeners: {
+                                                       select: {
+                                                               fn: this.onSelect,
+                                                               scope: this
+                                                       }
+                                               }
+                                       }
+                               });
+                       }
+                       itemsConfig.push({
+                               xtype: 'container',
+                               layout: 'hbox',
+                               items: paletteItems
+                       });
+                       itemsConfig.push({
+                               xtype: 'displayfield',
+                               itemId: 'show-color',
+                               cls: 'show-color',
+                               width: 60,
+                               height: 22,
+                               helpTitle: this.localize(buttonId)
+                       });
+                       itemsConfig.push({
+                               itemId: 'color',
+                               cls: 'color',
+                               width: 60,
+                               minValue: 0,
+                               value: (element && element.style[this.styleProperty[buttonId]]) ? Color.colorToHex(element.style[this.styleProperty[buttonId]]).substr(1, 6) : '',
+                               enableKeyEvents: true,
+                               fieldLabel: this.localize(buttonId),
+                               helpTitle: this.localize(buttonId),
+                               listeners: {
+                                       change: {
+                                               fn: this.onChange,
+                                               scope: this
+                                       },
+                                       afterrender: {
+                                               fn: this.onAfterRender,
+                                               scope: this
+                                       }
+                               }
+                       });
+                       return {
+                               xtype: 'fieldset',
+                               title: this.localize('color_title'),
+                               defaultType: 'textfield',
+                               labelWidth: 175,
+                               defaults: {
+                                       helpIcon: false
+                               },
+                               items: itemsConfig
+                       };
+               },
+               /*
+                * On select handler: set the value of the color field, display the new color and update the other palette
+                */
+               onSelect: function (palette, color) {
+                       this.dialog.find('itemId', 'color')[0].setValue(color);
+                       this.showColor(color);
+                       if (palette.getItemId() == 'color-palette') {
+                               var customPalette = this.dialog.find('itemId', 'custom-colors')[0];
+                               if (customPalette) {
+                                       customPalette.deSelect();
+                               }
+                       } else {
+                               var standardPalette = this.dialog.find('itemId', 'color-palette')[0];
+                               if (standardPalette) {
+                                       standardPalette.deSelect();
+                               }
+                       }
+               },
+               /*
+                * Display the selected color
+                */
+               showColor: function (color) {
+                       if (color) {
+                               var newColor = color;
+                               if (newColor.indexOf('#') == 0) {
+                                       newColor = newColor.substr(1);
+                               }
+                               this.dialog.find('itemId', 'show-color')[0].el.setStyle('backgroundColor', Color.colorToHex(parseInt(newColor, 16)));
+                       }
+               },
+               /*
+                * On change handler: display the new color and select it in the palettes, if it exists
+                */
+               onChange: function (field, value) {
+                       if (value) {
+                               var color = value.toUpperCase();
+                               this.showColor(color);
+                               var standardPalette = this.dialog.find('itemId', 'color-palette')[0];
+                               if (standardPalette) {
+                                       standardPalette.select(color);
+                               }
+                               var customPalette = this.dialog.find('itemId', 'custom-colors')[0];
+                               if (customPalette) {
+                                       customPalette.select(color);
+                               }
+                       }
+               },
+               /*
+                * On after render handler: display the color
+                */
+               onAfterRender: function (field) {
+                       var value = field.getValue();
+                       if (typeof value === 'string' && value.length > 0) {
+                               this.showColor(value);
+                       }
+               },
+               /*
+                * Open the dialogue window
+                *
+                * @param       string          title: the window title
+                * @param       object          arguments: some arguments for the handler
+                * @param       integer         dimensions: the opening width of the window
+                * @param       object          tabItems: the configuration of the tabbed panel
+                * @param       function        handler: handler when the OK button if clicked
+                *
+                * @return      void
+                */
+               openDialogue: function (title, arguments, dimensions, items, handler) {
+                       if (this.dialog) {
+                               this.dialog.close();
+                       }
+                       this.dialog = new Ext.Window({
+                               title: this.localize(title),
+                               arguments: arguments,
+                               cls: 'htmlarea-window',
+                               border: false,
+                               width: dimensions.width,
+                               height: dimensions.height,
+                               autoScroll: true,
+                               iconCls: this.getButton(arguments.buttonId).iconCls,
+                               listeners: {
+                                       close: {
+                                               fn: this.onClose,
+                                               scope: this
+                                       }
+                               },
+                               items: {
+                                       xtype: 'container',
+                                       layout: 'form',
+                                       style: {
+                                               width: '95%'
+                                       },
+                                       defaults: {
+                                               labelWidth: 150
+                                       },
+                                       items: items
+                               },
+                               buttons: [
+                                       this.buildButtonConfig('OK', handler),
+                                       this.buildButtonConfig('Cancel', this.onCancel)
+                               ]
+                       });
+                       this.show();
+               },
+               /*
+                * Set the color and close the dialogue
+                */
+               setColor: function(button, event) {
+                       this.restoreSelection();
+                       var buttonId = this.dialog.arguments.buttonId;
+                       var color = this.dialog.find('itemId', 'color')[0].getValue();
+                       if (color) {
+                               if (color.indexOf('#') == 0) {
+                                       color = color.substr(1);
+                               }
+                               color = Color.colorToHex(parseInt(color, 16));
+                       }
+                       var     element,
+                               fullNodeSelected = false;
+                       var range = this.editor.getSelection().createRange();
+                       var parent = this.editor.getSelection().getParentElement();
+                       var selectionEmpty = this.editor.getSelection().isEmpty();
+                       var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
+                       if (!selectionEmpty) {
+                               var fullySelectedNode = this.editor.getSelection().getFullySelectedNode();
+                               if (fullySelectedNode) {
+                                       fullNodeSelected = true;
+                                       parent = fullySelectedNode;
+                               }
+                       }
+                       if (selectionEmpty || fullNodeSelected) {
+                               element = parent;
+                                       // Set the color in the style attribute
+                               element.style[this.styleProperty[buttonId]] = color;
+                                       // Remove the span tag if it has no more attribute
+                               if (/^span$/i.test(element.nodeName) && !Dom.hasAllowedAttributes(element, this.allowedAttributes)) {
+                                       this.editor.getDomNode().removeMarkup(element);
+                               }
+                       } else if (statusBarSelection) {
+                               var element = statusBarSelection;
+                                       // Set the color in the style attribute
+                               element.style[this.styleProperty[buttonId]] = color;
+                                       // Remove the span tag if it has no more attribute
+                               if (/^span$/i.test(element.nodeName) && !Dom.hasAllowedAttributes(element, this.allowedAttributes)) {
+                                       this.editor.getDomNode().removeMarkup(element);
+                               }
+                       } else if (color && this.editor.getSelection().endPointsInSameBlock()) {
+                               var element = this.editor.document.createElement('span');
+                                       // Set the color in the style attribute
+                               element.style[this.styleProperty[buttonId]] = color;
+                               this.editor.getDomNode().wrapWithInlineElement(element, range);
+                       }
+                       this.close();
+                       event.stopEvent();
+               },
+
+               /**
+                * This function gets called when the toolbar is updated
+                */
+               onUpdateToolbar: function (button, mode, selectionEmpty, ancestors, endPointsInSameBlock) {
+                       if (mode === 'wysiwyg' && this.editor.isEditable()) {
+                               var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null,
+                                       parentElement = statusBarSelection ? statusBarSelection : this.editor.getSelection().getParentElement(),
+                                       disabled = !endPointsInSameBlock || (selectionEmpty && /^body$/i.test(parentElement.nodeName));
+                               button.setInactive(!parentElement.style[this.styleProperty[button.itemId]]);
+                               button.setDisabled(disabled);
+                       }
+               }
+       });
+
+       return TYPO3Color;
+
+});
diff --git a/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Image.js b/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Image.js
new file mode 100644 (file)
index 0000000..4368e00
--- /dev/null
@@ -0,0 +1,146 @@
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+/**
+ * TYPO3Image plugin for htmlArea RTE
+ */
+define('TYPO3/CMS/Rtehtmlarea/Plugins/TYPO3Image',
+       ['TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/Event/Event'],
+       function (Plugin, UserAgent, Event) {
+
+       var TYPO3Image = Ext.extend(Plugin, {
+
+               /**
+                * This function gets called by the class constructor
+                */
+               configurePlugin: function (editor) {
+                       this.pageTSConfiguration = this.editorConfiguration.buttons.image;
+                       this.imageModulePath = this.pageTSConfiguration.pathImageModule;
+
+                       /**
+                        * Registering plugin "About" information
+                        */
+                       var pluginInformation = {
+                               version         : '2.3',
+                               developer       : 'Stanislas Rolland',
+                               developerUrl    : 'http://www.sjbr.ca/',
+                               copyrightOwner  : 'Stanislas Rolland',
+                               sponsor         : 'SJBR',
+                               sponsorUrl      : 'http://www.sjbr.ca/',
+                               license         : 'GPL'
+                       };
+                       this.registerPluginInformation(pluginInformation);
+
+                       /**
+                        * Registering the button
+                        */
+                       var buttonId = 'InsertImage';
+                       var buttonConfiguration = {
+                               id              : buttonId,
+                               tooltip         : this.localize(buttonId + '-Tooltip'),
+                               iconCls         : 'htmlarea-action-image-edit',
+                               action          : 'onButtonPress',
+                               hotKey          : (this.pageTSConfiguration ? this.pageTSConfiguration.hotKey : null),
+                               dialog          : true
+                       };
+                       this.registerButton(buttonConfiguration);
+                       return true;
+               },
+
+               /**
+                * This function gets called when the button was pressed
+                *
+                * @param       object          editor: the editor instance
+                * @param       string          id: the button id or the key
+                *
+                * @return      boolean         false if action is completed
+                */
+               onButtonPress: function (editor, id) {
+                       // Could be a button or its hotkey
+                       var buttonId = this.translateHotKey(id);
+                       buttonId = buttonId ? buttonId : id;
+                       var additionalParameter;
+                       this.image = this.editor.getSelection().getParentElement();
+                       if (this.image && !/^img$/i.test(this.image.nodeName)) {
+                               this.image = null;
+                       }
+                       if (this.image) {
+                               additionalParameter = '&act=image';
+                       }
+                       this.openContainerWindow(
+                               buttonId,
+                               this.getButton(buttonId).tooltip.title,
+                               this.getWindowDimensions(
+                                       {
+                                               width:  650,
+                                               height: 500
+                                       },
+                                       buttonId
+                               ),
+                               this.makeUrlFromModulePath(this.imageModulePath, additionalParameter)
+                       );
+                       var self = this;
+                       Event.one(UserAgent.isIE ? this.editor.document.body : this.editor.document.documentElement, 'drop.TYPO3Image', function (event) { return self.onDrop(event); });
+                       return false;
+               },
+
+               /**
+                * Insert the image
+                * This function is called from the TYPO3 image script
+                */
+               insertImage: function(image) {
+                       this.restoreSelection();
+                       this.editor.getSelection().insertHtml(image);
+                       this.close();
+               },
+
+               /**
+                * Handlers for drag and drop operations
+                */
+               onDrop: function (event) {
+                       if (UserAgent.isWebKit) {
+                               this.editor.iframe.onDrop();
+                       }
+                       this.close();
+                       return true;
+               },
+
+               /**
+                * Remove the event listeners
+                */
+               removeListeners: function () {
+                       Event.off(UserAgent.isIE ? this.editor.document.body : this.editor.document.documentElement, '.TYPO3Image');
+               },
+
+               /**
+                * This function gets called when the toolbar is updated
+                */
+               onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
+                       if (mode === 'wysiwyg' && this.editor.isEditable() && button.itemId === 'InsertImage' && !button.disabled) {
+                               var image = this.editor.getSelection().getParentElement();
+                               if (image && !/^img$/i.test(image.nodeName)) {
+                                       image = null;
+                               }
+                               if (image) {
+                                       button.setTooltip({ title: this.localize('Modify image') });
+                               } else {
+                                       button.setTooltip({ title: this.localize('Insert image') });
+                               }
+                       }
+               }
+       });
+
+       return TYPO3Image;
+
+});
diff --git a/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Link.js b/typo3/sysext/rtehtmlarea/Resources/Public/JavaScript/Plugins/TYPO3Link.js
new file mode 100644 (file)
index 0000000..6e15ec1
--- /dev/null
@@ -0,0 +1,487 @@
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+/**
+ * TYPO3Link plugin for htmlArea RTE
+ */
+define('TYPO3/CMS/Rtehtmlarea/Plugins/TYPO3Link',
+       ['TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
+       'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM'],
+       function (Plugin, UserAgent, Dom) {
+
+       var TYPO3Link = Ext.extend(Plugin, {
+
+               /**
+                * This function gets called by the class constructor
+                */
+               configurePlugin: function (editor) {
+                       this.pageTSConfiguration = this.editorConfiguration.buttons.link;
+                       this.modulePath = this.pageTSConfiguration.pathLinkModule;
+                       this.classesAnchorUrl = this.pageTSConfiguration.classesAnchorUrl;
+
+                       /**
+                        * Registering plugin "About" information
+                        */
+                       var pluginInformation = {
+                               version         : '2.2',
+                               developer       : 'Stanislas Rolland',
+                               developerUrl    : 'http://www.sjbr.ca/',
+                               copyrightOwner  : 'Stanislas Rolland',
+                               sponsor         : 'SJBR',
+                               sponsorUrl      : 'http://www.sjbr.ca/',
+                               license         : 'GPL'
+                       };
+                       this.registerPluginInformation(pluginInformation);
+                       /*
+                        * Registering the buttons
+                        */
+                       var buttonList = this.buttonList, buttonId;
+                       for (var i = 0; i < buttonList.length; ++i) {
+                               var button = buttonList[i];
+                               buttonId = button[0];
+                               var buttonConfiguration = {
+                                       id              : buttonId,
+                                       tooltip         : this.localize(buttonId.toLowerCase()),
+                                       iconCls         : 'htmlarea-action-' + button[4],
+                                       action          : 'onButtonPress',
+                                       hotKey          : (this.pageTSConfiguration ? this.pageTSConfiguration.hotKey : null),
+                                       context         : button[1],
+                                       selection       : button[2],
+                                       dialog          : button[3]
+                               };
+                               this.registerButton(buttonConfiguration);
+                       }
+                       return true;
+               },
+               /*
+                * The list of buttons added by this plugin
+                */
+               buttonList: [
+                       ['CreateLink', 'a,img', false, true, 'link-edit'],
+                       ['UnLink', 'a', false, false, 'unlink']
+               ],
+               /*
+                * This function is invoked when the editor is being generated
+                */
+               onGenerate: function () {
+                               // Download the definition of special anchor classes if not yet done
+                       if (this.classesAnchorUrl && typeof HTMLArea.classesAnchorSetup === 'undefined') {
+                               this.getJavascriptFile(this.classesAnchorUrl, function (options, success, response) {
+                                       if (success) {
+                                               try {
+                                                       if (typeof HTMLArea.classesAnchorSetup === 'undefined') {
+                                                               eval(response.responseText);
+                                                       }
+                                               } catch(e) {
+                                                       this.appendToLog('ongenerate', 'Error evaluating contents of Javascript file: ' + this.classesAnchorUrl, 'error');
+                                               }
+                                       }
+                               });
+                       }
+               },
+               /*
+                * This function gets called when the button was pressed
+                *
+                * @param       object          editor: the editor instance
+                * @param       string          id: the button id or the key
+                * @param       object          target: the target element of the contextmenu event, when invoked from the context menu
+                *
+                * @return      boolean         false if action is completed
+                */
+               onButtonPress: function(editor, id, target) {
+                               // Could be a button or its hotkey
+                       var buttonId = this.translateHotKey(id);
+                       buttonId = buttonId ? buttonId : id;
+                               // Download the definition of special anchor classes if not yet done
+                       if (this.classesAnchorUrl && typeof HTMLArea.classesAnchorSetup === 'undefined') {
+                               this.getJavascriptFile(this.classesAnchorUrl, function (options, success, response) {
+                                       if (success) {
+                                               try {
+                                                       if (typeof HTMLArea.classesAnchorSetup === 'undefined') {
+                                                               eval(response.responseText);
+                                                       }
+                                                       this.onButtonPress(editor, id, target);
+                                               } catch(e) {
+                                                       this.appendToLog('onButtonPress', 'Error evaluating contents of Javascript file: ' + this.classesAnchorUrl, 'error');
+                                               }
+                                       }
+                               });
+                       } else {
+                               if (buttonId === 'UnLink') {
+                                       this.unLink(true);
+                                       return false;
+                               }
+                               var additionalParameter;
+                               var node = this.editor.getSelection().getParentElement();
+                               var el = this.editor.getSelection().getFirstAncestorOfType('a');
+                               if (el != null) {
+                                       node = el;
+                               }
+                               if (node != null && /^a$/i.test(node.nodeName)) {
+                                       additionalParameter = '&curUrl[href]=' + encodeURIComponent(node.getAttribute('href'));
+                                       if (node.target) additionalParameter += '&curUrl[target]=' + encodeURIComponent(node.target);
+                                       if (node.className) additionalParameter += '&curUrl[class]=' + encodeURIComponent(node.className);
+                                       if (node.title) additionalParameter += '&curUrl[title]=' + encodeURIComponent(node.title);
+                                       if (this.pageTSConfiguration && this.pageTSConfiguration.additionalAttributes) {
+                                               var additionalAttributes = this.pageTSConfiguration.additionalAttributes.split(',');
+                                               for (var i = additionalAttributes.length; --i >= 0;) {
+                                                               // hasAttribute() not available in IE < 8
+                                                       if ((node.hasAttribute && node.hasAttribute(additionalAttributes[i])) || node.getAttribute(additionalAttributes[i]) != null) {
+                                                               additionalParameter += '&curUrl[' + additionalAttributes[i] + ']=' + encodeURIComponent(node.getAttribute(additionalAttributes[i]));
+                                                       }
+                                               }
+                                       }
+                               } else if (!this.editor.getSelection().isEmpty()) {
+                                       var text = this.editor.getSelection().getHtml();
+                                       if (text && text != null) {
+                                               var offset = text.toLowerCase().indexOf('<a');
+                                               if (offset != -1) {
+                                                       var ATagContent = text.substring(offset+2);
+                                                       offset = ATagContent.toUpperCase().indexOf('>');
+                                                       ATagContent = ATagContent.substring(0, offset);
+                                                       additionalParameter = '&curUrl[all]=' + encodeURIComponent(ATagContent);
+                                               }
+                                       }
+                               }
+                               this.openContainerWindow(
+                                       buttonId,
+                                       this.getButton(buttonId).tooltip.title,
+                                       this.getWindowDimensions(
+                                               {
+                                                       width:  550,
+                                                       height: 500
+                                               },
+                                               buttonId
+                                       ),
+                                       this.makeUrlFromModulePath(this.modulePath, additionalParameter)
+                               );
+                       }
+                       return false;
+               },
+               /*
+                * Add a link to the selection.
+                * This function is called from the TYPO3 link popup.
+                *
+                * @param       string  theLink: the href attribute of the link to be created
+                * @param       string  cur_target: value for the target attribute
+                * @param       string  cur_class: value for the class attribute
+                * @param       string  cur_title: value for the title attribute
+                * @param       object  additionalValues: values for additional attributes (may be used by extension)
+                *
+                * @return void
+                */
+               createLink: function(theLink,cur_target,cur_class,cur_title,additionalValues) {
+                       var range, anchorClass, imageNode = null, addIconAfterLink;
+                       this.restoreSelection();
+                       var node = this.editor.getSelection().getFirstAncestorOfType('a');
+                       if (!node) {
+                               node = this.editor.getSelection().getParentElement();
+                       }
+                       if (HTMLArea.classesAnchorSetup && cur_class) {
+                               for (var i = HTMLArea.classesAnchorSetup.length; --i >= 0;) {
+                                       anchorClass = HTMLArea.classesAnchorSetup[i];
+                                       if (anchorClass.name == cur_class && anchorClass.image) {
+                                               imageNode = this.editor.document.createElement('img');
+                                               imageNode.src = anchorClass.image;
+                                               imageNode.alt = anchorClass.altText;
+                                               addIconAfterLink = anchorClass.addIconAfterLink;
+                                               break;
+                                       }
+                               }
+                       }
+                       if (node != null && /^a$/i.test(node.nodeName)) {
+                                       // Update existing link
+                               this.editor.getSelection().selectNode(node);
+                               range = this.editor.getSelection().createRange();
+                                       // Clean images, keep links
+                               if (HTMLArea.classesAnchorSetup) {
+                                       this.cleanAllLinks(node, range, true);
+                               }
+                                       // Update link href
+                                       // In IE, setting href may update the content of the element. We don't want this feature.
+                               if (UserAgent.isIE) {
+                                       var content = node.innerHTML;
+                               }
+                               node.href = UserAgent.isGecko ? encodeURI(theLink) : theLink;
+                               if (UserAgent.isIE) {
+                                       node.innerHTML = content;
+                               }
+                                       // Update link attributes
+                               this.setLinkAttributes(node, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink, additionalValues);
+                       } else {
+                                       // Create new link
+                                       // Cleanup selected range
+                               range = this.editor.getSelection().createRange();
+                                       // Clean existing anchors otherwise Mozilla may create nested anchors while IE may update existing link
+                               if (UserAgent.isIEBeforeIE9) {
+                                       this.cleanAllLinks(node, range, true);
+                                       this.editor.getSelection().execCommand('UnLink', false, null);
+                               } else {
+                                               // Selection may be lost when cleaning links
+                                               // Note: In IE6-8, the following procedure breaks the selection used by the execCommand
+                                       var bookMark = this.editor.getBookMark().get(range);
+                                       this.cleanAllLinks(node, range);
+                                       range = this.editor.getBookMark().moveTo(bookMark);
+                                       this.editor.getSelection().selectRange(range);
+                               }
+                               if (UserAgent.isGecko) {
+                                       this.editor.getSelection().execCommand('CreateLink', false, encodeURI(theLink));
+                               } else {
+                                       this.editor.getSelection().execCommand('CreateLink', false, theLink);
+                               }
+                               // Get the created link or parent
+                               node = this.editor.getSelection().getParentElement();
+                               // Re-establish the range of the selection
+                               range = this.editor.getSelection().createRange();
+                               if (node) {
+                                               // Export trailing br that IE may include in the link
+                                       if (UserAgent.isIE) {
+                                               if (node.lastChild && /^br$/i.test(node.lastChild.nodeName)) {
+                                                       Dom.removeFromParent(node.lastChild);
+                                                       node.parentNode.insertBefore(this.editor.document.createElement('br'), node.nextSibling);
+                                               }
+                                       }
+                                               // We may have created multiple links in as many blocks
+                                       this.setLinkAttributes(node, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink, additionalValues);
+                               }
+                       }
+                       this.close();
+               },
+
+               /**
+                * Unlink the selection.
+                * This function is called from the TYPO3 link popup and from unlink button pressed in toolbar or context menu.
+                *
+                * @param       string  buttonPressd: true if the unlink button was pressed
+                *
+                * @return void
+                */
+               unLink: function (buttonPressed) {
+                               // If no dialogue window was opened, the selection should not be restored
+                       if (!buttonPressed) {
+                               this.restoreSelection();
+                       }
+                       var node = this.editor.getSelection().getParentElement();
+                       var el = this.editor.getSelection().getFirstAncestorOfType('a');
+                       if (el != null) {
+                               node = el;
+                       }
+                       if (node != null && /^a$/i.test(node.nodeName)) {
+                               this.editor.getSelection().selectNode(node);
+                       }
+                       if (HTMLArea.classesAnchorSetup) {
+                               var range = this.editor.getSelection().createRange();
+                               if (!UserAgent.isIEBeforeIE9) {
+                                       this.cleanAllLinks(node, range, false);
+                               } else {
+                                       this.cleanAllLinks(node, range, true);
+                                       this.editor.getSelection().execCommand('Unlink', false, '');
+                               }
+                       } else {
+                               this.editor.getSelection().execCommand('Unlink', false, '');
+                       }
+                       if (this.dialog) {
+                               this.close();
+                       }
+               },
+
+               /**
+                * Set attributes of anchors intersecting a range in the given node
+                *
+                * @param object node: a node that may interesect the range
+                * @param object range: set attributes on all nodes intersecting this range
+                * @param string cur_target: value for the target attribute
+                * @param string cur_class: value for the class attribute
+                * @param string cur_title: value for the title attribute
+                * @param object imageNode: image to clone and append to the anchor
+                * @param boolean addIconAfterLink: add icon after rather than before the link
+                * @param object additionalValues: values for additional attributes (may be used by extension)
+                * @return void
+                */
+               setLinkAttributes: function(node, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink, additionalValues) {
+                       if (/^a$/i.test(node.nodeName)) {
+                               var nodeInRange = false;
+                               if (!UserAgent.isIEBeforeIE9) {
+                                       this.editor.focus();
+                                       nodeInRange = Dom.rangeIntersectsNode(range, node);
+                               } else {
+                                       if (this.editor.getSelection().getType() === 'Control') {
+                                                       // we assume an image is selected
+                                               nodeInRange = true;
+                                       } else {
+                                               var nodeRange = this.editor.document.body.createTextRange();
+                                               nodeRange.moveToElementText(node);
+                                               nodeInRange = nodeRange.inRange(range) || range.inRange(nodeRange) || (range.compareEndPoints('StartToStart', nodeRange) == 0) || (range.compareEndPoints('EndToEnd', nodeRange) == 0);
+                                       }
+                               }
+                               if (nodeInRange) {
+                                       if (imageNode != null) {
+                                               if (addIconAfterLink) {
+                                                       node.appendChild(imageNode.cloneNode(false));
+                                               } else {
+                                                       node.insertBefore(imageNode.cloneNode(false), node.firstChild);
+                                               }
+                                       }
+                                       if (UserAgent.isGecko) {
+                                               node.href = decodeURI(node.href);
+                                       }
+                                       if (cur_target.trim()) node.target = cur_target.trim();
+                                               else node.removeAttribute('target');
+                                       if (cur_class.trim()) {
+                                               node.className = cur_class.trim();
+                                       } else {
+                                               if (!UserAgent.isOpera) {
+                                                       node.removeAttribute('class');
+                                                       if (UserAgent.isIEBeforeIE9) {
+                                                               node.removeAttribute('className');
+                                                       }
+                                               } else {
+                                                       node.className = '';
+                                               }
+                                       }
+                                       if (cur_title.trim()) {
+                                               node.title = cur_title.trim();
+                                       } else {
+                                               node.removeAttribute('title');
+                                               node.removeAttribute('rtekeep');
+                                       }
+                                       if (this.pageTSConfiguration && this.pageTSConfiguration.additionalAttributes && typeof(additionalValues) == 'object') {
+                                               for (additionalAttribute in additionalValues) {
+                                                       if (additionalValues.hasOwnProperty(additionalAttribute)) {
+                                                               if (additionalValues[additionalAttribute].toString().trim()) {
+                                                                       node.setAttribute(additionalAttribute, additionalValues[additionalAttribute]);
+                                                               } else {
+                                                                       node.removeAttribute(additionalAttribute);
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       } else {
+                               for (var i = node.firstChild; i; i = i.nextSibling) {
+                                       if (i.nodeType === Dom.ELEMENT_NODE || i.nodeType === Dom.DOCUMENT_FRAGMENT_NODE) {
+                                               this.setLinkAttributes(i, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink, additionalValues);
+                                       }
+                               }
+                       }
+               },
+
+               /**
+                * Clean up images in special anchor classes
+                */
+               cleanClassesAnchorImages: function(node) {
+                       var nodeArray = [], splitArray1 = [], splitArray2 = [];
+                       for (var childNode = node.firstChild; childNode; childNode = childNode.nextSibling) {
+                               if (/^img$/i.test(childNode.nodeName)) {
+                                       splitArray1 = childNode.src.split('/');
+                                       for (var i = HTMLArea.classesAnchorSetup.length; --i >= 0;) {
+                                               if (HTMLArea.classesAnchorSetup[i]['image']) {
+                                                       splitArray2 = HTMLArea.classesAnchorSetup[i]['image'].split('/');
+                                                       if (splitArray1[splitArray1.length-1] == splitArray2[splitArray2.length-1]) {
+                                                               nodeArray.push(childNode);
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       for (i = nodeArray.length; --i >= 0;) {
+                               node.removeChild(nodeArray[i]);
+                       }
+               },
+
+               /**
+                * Clean up all anchors intesecting with the range in the given node
+                */
+               cleanAllLinks: function(node, range, keepLinks) {
+                       if (/^a$/i.test(node.nodeName)) {
+                               var intersection = false;
+                               if (!UserAgent.isIEBeforeIE9) {
+                                       this.editor.focus();
+                                       intersection = Dom.rangeIntersectsNode(range, node);
+                               } else {
+                                       if (this.editor.getSelection().getType() === 'Control') {
+                                                       // we assume an image is selected
+                                               intersection = true;
+                                       } else {
+                                               var nodeRange = this.editor.document.body.createTextRange();
+                                               nodeRange.moveToElementText(node);
+                                               intersection = range.inRange(nodeRange) || ((range.compareEndPoints('StartToStart', nodeRange) > 0) && (range.compareEndPoints('StartToEnd', nodeRange) < 0)) || ((range.compareEndPoints('EndToStart', nodeRange) > 0) && (range.compareEndPoints('EndToEnd', nodeRange) < 0));
+                                       }
+                               }
+                               if (intersection) {
+                                       this.cleanClassesAnchorImages(node);
+                                       if (!keepLinks) {
+                                               while (node.firstChild) {
+                                                       node.parentNode.insertBefore(node.firstChild, node);
+                                               }
+                                               node.parentNode.removeChild(node);
+                                       }
+                               }
+                       } else {
+                               var child = node.firstChild,
+                                       nextSibling;
+                               while (child) {
+                                               // Save next sibling as child may be removed
+                                       nextSibling = child.nextSibling;
+                                       if (child.nodeType === Dom.ELEMENT_NODE || child.nodeType === Dom.DOCUMENT_FRAGMENT_NODE) {
+                                               this.cleanAllLinks(child, range, keepLinks);
+                                       }
+                                       child = nextSibling;
+                               }
+                       }
+               },
+
+               /**
+                * This function gets called when the toolbar is updated
+                */
+               onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
+                       if (mode === 'wysiwyg' && this.editor.isEditable()) {
+                               switch (button.itemId) {
+                                       case 'CreateLink':
+                                               button.setDisabled(selectionEmpty && !button.isInContext(mode, selectionEmpty, ancestors));
+                                               if (!button.disabled) {
+                                                       var node = this.editor.getSelection().getParentElement();
+                                                       var el = this.editor.getSelection().getFirstAncestorOfType('a');
+                                                       if (el != null) {
+                                                               node = el;
+                                                       }
+                                                       if (node != null && /^a$/i.test(node.nodeName)) {
+                                                               button.setTooltip({ title: this.localize('Modify link') });
+                                                       } else {
+                                                               button.setTooltip({ title: this.localize('Insert link') });
+                                                       }
+                                               }
+                                               break;
+                                       case 'UnLink':
+                                               var link = false;
+                                                       // Let's see if a link was double-clicked in Firefox
+                                               if (UserAgent.isGecko && !selectionEmpty) {
+                                                       var range = this.editor.getSelection().createRange();
+                                                       if (range.startContainer.nodeType === Dom.ELEMENT_NODE && range.startContainer == range.endContainer && (range.endOffset - range.startOffset == 1)) {
+                                                               var node = range.startContainer.childNodes[range.startOffset];
+                                                               if (node && /^a$/i.test(node.nodeName) && node.textContent == range.toString()) {
+                                                                       link = true;
+                                                               }
+                                                       }
+                                               }
+                                               button.setDisabled(!link && !button.isInContext(mode, selectionEmpty, ancestors));
+                                               break;
+                               }
+                       }
+               }
+       });
+
+       return TYPO3Link;
+
+});