Fixed bug #14004: htmlArea RTE: Applying color or font repeatedly produces nested...
authorStanislas Rolland <typo3@sjbr.ca>
Tue, 6 Apr 2010 01:16:21 +0000 (01:16 +0000)
committerStanislas Rolland <typo3@sjbr.ca>
Tue, 6 Apr 2010 01:16:21 +0000 (01:16 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@7246 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
typo3/sysext/rtehtmlarea/ChangeLog
typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js
typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineElements/inline-elements.js
typo3/sysext/rtehtmlarea/htmlarea/plugins/SelectFont/select-font.js
typo3/sysext/rtehtmlarea/htmlarea/plugins/TYPO3Color/typo3color.js

index 165db54..4eb73af 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2010-04-05  Stanislas Rolland  <typo3@sjbr.ca>
+
+       * Fixed bug #14004: htmlArea RTE: Applying color or font repeatedly produces nested span elements
+
 2010-04-05  Jeff Segars  <jeff@webempoweredchurch.org>
 
        * Fixed bug #14003: "Check for extension updates" does not always find latest version
index 2923f0c..8c3e251 100644 (file)
@@ -1,3 +1,7 @@
+2010-04-05  Stanislas Rolland  <typo3@sjbr.ca>
+
+       * Fixed bug #14004: htmlArea RTE: Applying color or font repeatedly produces nested span elements
+
 2010-03-30  Stanislas Rolland  <typo3@sjbr.ca>
 
        * Follow-up to feature #13954: htmlArea RTE: Extjize TYPO3 link, image and user elements dialogue windows
index 7b23e45..f814253 100644 (file)
@@ -3081,7 +3081,49 @@ HTMLArea.Editor.prototype._getFirstAncestor = function(sel,types) {
        }
        return null;
 };
-
+/*
+ * Get the node whose contents are currently fully selected
+ *
+ * @param      array           selection: the current selection
+ * @param      array           range: the range of the current selection
+ * @param      array           ancestors: the array of ancestors node of the current selection
+ *
+ * @return     object          the fully selected node, if any, null otherwise
+ */
+HTMLArea.Editor.prototype.getFullySelectedNode = function (selection, range, ancestors) {
+       var node, fullNodeSelected = false;
+       if (!selection) {
+               var selection = this._getSelection();
+       }
+       if (!this._selectionEmpty(selection)) {
+               if (!range) {
+                       var range = this._createRange(selection);
+               }
+               if (!ancestors) {
+                       var ancestors = this.getAllAncestors();
+               }
+               Ext.each(ancestors, function (ancestor) {
+                       if (Ext.isIE) {
+                               fullNodeSelected = (selection.type !== 'Control' && ancestor.innerText == range.text) || (selection.type === 'Control' && ancestor.innerText == range.item(0).text);
+                       } else {
+                               fullNodeSelected = (ancestor.textContent == range.toString());
+                       }
+                       if (fullNodeSelected) {
+                               node = ancestor;
+                               return false;
+                       }
+               });
+                       // Working around bug with WebKit selection
+               if (Ext.isWebKit && !fullNodeSelected) {
+                       var statusBarSelection = this.statusBar ? this.statusBar.getSelection() : null;
+                       if (statusBarSelection && statusBarSelection.textContent == range.toString()) {
+                               fullNodeSelected = true;
+                               node = statusBarSelection;
+                       }
+               }
+       }
+       return fullNodeSelected ? node : null;
+};
 /***************************************************
  *  Category: EVENT HANDLERS
  ***************************************************/
index 118b505..f583aa5 100644 (file)
@@ -224,13 +224,13 @@ InlineElements = HTMLArea.Plugin.extend({
         * This function applies to the selection the markup chosen in the drop-down list or corresponding to the button pressed
         */
        applyInlineElement : function (editor, element) {
-               editor.focusEditor();
+               editor.focus();
                var selection = editor._getSelection();
                var range = editor._createRange(selection);
                var parent = editor.getParentElement(selection, range);
                var ancestors = editor.getAllAncestors();
                var elementIsAncestor = false;
-               var selectionEmpty = editor._selectionEmpty(selection);
+               var fullNodeSelected = false;
                if (HTMLArea.is_ie) {
                        var bookmark = editor.getBookmark(range);
                }
@@ -242,30 +242,13 @@ InlineElements = HTMLArea.Plugin.extend({
                                break;
                        }
                }
-               if (!selectionEmpty) {
-                       var statusBarSelection = (editor.statusBar ? editor.statusBar.getSelection() : null);
-                               // The selection is not empty.
-                       for (var i = 0; i < ancestors.length; ++i) {
-                               fullNodeSelected = (HTMLArea.is_ie && ((selection.type !== "Control" && ancestors[i].innerText === range.text) || (selection.type === "Control" && ancestors[i].innerText === range.item(0).text)))
-                                                       || (HTMLArea.is_gecko && ((statusBarSelection === ancestors[i] && ancestors[i].textContent === range.toString()) || (!statusBarSelection && ancestors[i].textContent === range.toString())));
-                               if (fullNodeSelected) {
-                                       if (!HTMLArea.isBlockElement(ancestors[i])) {
-                                               parent = ancestors[i];
-                                       }
-                                       break;
-                               }
-                       }
-                               // Working around bug in Safari selectNodeContents
-                       if (!fullNodeSelected && HTMLArea.is_safari && statusBarSelection && this.isInlineElement(statusBarSelection) && statusBarSelection.textContent === range.toString()) {
-                               fullNodeSelected = true;
-                               parent = statusBarSelection;
-                       }
-                       
-                       var fullNodeTextSelected = (HTMLArea.is_gecko && parent.textContent === range.toString())
-                                                       || (HTMLArea.is_ie && parent.innerText === range.text);
-                       if (fullNodeTextSelected && elementIsAncestor) {
-                               fullNodeSelected = true;
+               if (!editor._selectionEmpty(selection)) {
+                       var fullySelectedNode = editor.getFullySelectedNode(selection, range, ancestors);
+                       fullNodeSelected = this.isInlineElement(fullySelectedNode);
+                       if (fullNodeSelected) {
+                               parent = fullySelectedNode;
                        }
+                       var statusBarSelection = (editor.statusBar ? editor.statusBar.getSelection() : null);
                        if (element !== "none" && !(fullNodeSelected && elementIsAncestor)) {
                                        // Add markup
                                var newElement = editor._doc.createElement(element);
@@ -274,17 +257,17 @@ InlineElements = HTMLArea.Plugin.extend({
                                }
                                if (HTMLArea.is_gecko) {
                                        if (fullNodeSelected && statusBarSelection) {
-                                               if (HTMLArea.is_safari) {
-                                                       editor.selectNode(parent);
-                                                       selection = editor._getSelection();
-                                                       range = editor._createRange(selection);
+                                               if (Ext.isWebKit) {
+                                                       newElement = parent.parentNode.insertBefore(newElement, statusBarSelection);
+                                                       newElement.appendChild(statusBarSelection);
+                                                       newElement.normalize();
                                                } else {
                                                        range.selectNode(parent);
+                                                       editor.wrapWithInlineElement(newElement, selection, range);
                                                }
-                                       }
-                                       editor.wrapWithInlineElement(newElement, selection, range);
-                                       if (fullNodeSelected && statusBarSelection && !HTMLArea.is_safari) {
                                                editor.selectNodeContents(newElement.lastChild, false);
+                                       } else {
+                                               editor.wrapWithInlineElement(newElement, selection, range);
                                        }
                                        range.detach();
                                } else {
@@ -313,7 +296,11 @@ InlineElements = HTMLArea.Plugin.extend({
                                        if (elementIsAncestor) {
                                                parent = ancestors[elementAncestorIndex];
                                        }
+                                       var parentElement = parent.parentNode;
                                        editor.removeMarkup(parent);
+                                       if (Ext.isWebKit && this.isInlineElement(parentElement)) {
+                                               editor.selectNodeContents(parentElement, false);
+                                       }
                                }
                        }
                } else {
@@ -378,36 +365,25 @@ InlineElements = HTMLArea.Plugin.extend({
                }
                return newElement;
        },
-       
        /*
        * This function gets called when the toolbar is updated
        */
        onUpdateToolbar : function (button, mode, selectionEmpty, ancestors, endPointsInSameBlock) {
                var editor = this.editor;
                if (mode === "wysiwyg" && editor.isEditable()) {
-                       var tagName = false, fullNodeSelected = false;
-                       var sel = editor._getSelection();
-                       var range = editor._createRange(sel);
-                       var parent = editor.getParentElement(sel);
+                       var     tagName = false,
+                               fullNodeSelected = false;
+                       var selection = editor._getSelection();
+                       var range = editor._createRange(selection);
+                       var parent = editor.getParentElement(selection);
                        if (parent && !HTMLArea.isBlockElement(parent)) {
                                tagName = parent.nodeName.toLowerCase();
                        }
                        if (!selectionEmpty) {
-                               var statusBarSelection = editor.statusBar ? editor.statusBar.getSelection() : null;
-                               for (var i = 0, n = ancestors.length; i < n; ++i) {
-                                       fullNodeSelected = (statusBarSelection === ancestors[i])
-                                               && ((HTMLArea.is_gecko && ancestors[i].textContent === range.toString()) || (HTMLArea.is_ie && ((sel.type !== "Control" && ancestors[i].innerText === range.text) || (sel.type === "Control" && ancestors[i].innerText === range.item(0).text))));
-                                       if (fullNodeSelected) {
-                                               if (!HTMLArea.isBlockElement(ancestors[i])) {
-                                                       tagName = ancestors[i].nodeName.toLowerCase();
-                                               }
-                                               break;
-                                       }
-                               }
-                                       // Working around bug in Safari selectNodeContents
-                               if (!fullNodeSelected && HTMLArea.is_safari && statusBarSelection && this.isInlineElement(statusBarSelection) && statusBarSelection.textContent === range.toString()) {
-                                       fullNodeSelected = true;
-                                       tagName = statusBarSelection.nodeName.toLowerCase();
+                               var fullySelectedNode = editor.getFullySelectedNode(selection, range, ancestors);
+                               fullNodeSelected = this.isInlineElement(fullySelectedNode);
+                               if (fullNodeSelected) {
+                                       tagName = fullySelectedNode.nodeName.toLowerCase();
                                }
                        }
                        var selectionInInlineElement = tagName && this.REInlineElements.test(tagName);
@@ -426,13 +402,12 @@ InlineElements = HTMLArea.Plugin.extend({
                                                        return true;
                                                }
                                        }, this);
-                                       button.setInactive(!activeButton);
+                                       button.setInactive(!activeButton && this.convertBtn[button.itemId] !== tagName);
                                        button.setDisabled(disabled);
                                        break;
                        }
                }
        },
-       
        /*
        * This function updates the drop-down list of inline elemenents
        */
index 15e6833..30d5066 100644 (file)
@@ -63,21 +63,19 @@ SelectFont = HTMLArea.Plugin.extend({
                                this.allowedAttributes.push("className");
                        }
                }
-
                /*
                 * Registering plugin "About" information
                 */
                var pluginInformation = {
-                       version         : "1.0",
-                       developer       : "Stanislas Rolland",
-                       developerUrl    : "http://www.sjbr.ca/",
-                       copyrightOwner  : "Stanislas Rolland",
-                       sponsor         : "SJBR",
-                       sponsorUrl      : "http://www.sjbr.ca/",
-                       license         : "GPL"
+                       version         : '2.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 dropdowns
                 */
@@ -138,19 +136,29 @@ SelectFont = HTMLArea.Plugin.extend({
                FontName        : "font-family",
                FontSize        : "font-size"
        },
-        
        /*
         * This function gets called when some font style or font size was selected from the dropdown lists
         */
-       onChange : function (editor, combo, record, index) {
+       onChange: function (editor, combo, record, index) {
                var param = combo.getValue();
-               editor.focusEditor();
-               var selection = editor._getSelection(),
-                       range = editor._createRange(selection),
-                       statusBarSelection = editor.statusBar ? editor.statusBar.getSelection() : null,
-                       element;
-               if (editor._selectionEmpty(selection)) {
-                       element = editor.getParentElement(selection, range);
+               editor.focus();
+               var     element,
+                       fullNodeSelected = false;
+               var selection = editor._getSelection();
+               var range = editor._createRange(selection);
+               var parent = editor.getParentElement(selection, range);
+               var selectionEmpty = editor._selectionEmpty(selection);
+               var statusBarSelection = editor.statusBar ? editor.statusBar.getSelection() : null;
+               if (!selectionEmpty) {
+                       var ancestors = editor.getAllAncestors();
+                       var fullySelectedNode = editor.getFullySelectedNode(selection, range, ancestors);
+                       if (fullySelectedNode) {
+                               fullNodeSelected = true;
+                               parent = fullySelectedNode;
+                       }
+               }
+               if (selectionEmpty || fullNodeSelected) {
+                       element = parent;
                                // Set the style attribute
                        this.setStyle(element, combo.itemId, param);
                                // Remove the span tag if it has no more attribute
@@ -177,7 +185,6 @@ SelectFont = HTMLArea.Plugin.extend({
                }
                return false;
        },
-
        /*
         * This function sets the style attribute on the element
         *
index 97ecedd..1c84047 100644 (file)
  * TYPO3 SVN ID: $Id$
  */
 TYPO3Color = HTMLArea.Plugin.extend({
-       
-       constructor : function(editor, pluginName) {
+       constructor: function(editor, pluginName) {
                this.base(editor, pluginName);
        },
-       
        /*
         * This function gets called by the class constructor
         */
-       configurePlugin : function(editor) {
-               
+       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.editor.plugins.TextStyle && this.editor.plugins.TextStyle.instance) {
-                       this.editor.plugins.TextStyle.instance.addAllowedAttribute("style");
+                       this.editor.plugins.TextStyle.instance.addAllowedAttribute('style');
                        this.allowedAttributes = this.editor.plugins.TextStyle.instance.allowedAttributes;
-               }                       
+               }
                if (this.editor.plugins.InlineElements && this.editor.plugins.InlineElements.instance) {
-                       this.editor.plugins.InlineElements.instance.addAllowedAttribute("style");
+                       this.editor.plugins.InlineElements.instance.addAllowedAttribute('style');
                        if (!this.allowedAllowedAttributes) {
                                this.allowedAttributes = this.editor.plugins.InlineElements.instance.allowedAttributes;
                        }
                }
                if (this.editor.plugins.BlockElements && this.editor.plugins.BlockElements.instance) {
-                       this.editor.plugins.BlockElements.instance.addAllowedAttribute("style");
+                       this.editor.plugins.BlockElements.instance.addAllowedAttribute('style');
                }
                if (!this.allowedAttributes) {
-                       this.allowedAttributes = new Array("id", "title", "lang", "xml:lang", "dir", "class", "style");
+                       this.allowedAttributes = new Array('id', 'title', 'lang', 'xml:lang', 'dir', 'class', 'style');
                        if (HTMLArea.is_ie) {
-                               this.allowedAttributes.push("className");
+                               this.allowedAttributes.push('className');
                        }
                }
                /*
                 * Registering plugin "About" information
                 */
                var pluginInformation = {
-                       version         : "4.0",
-                       developer       : "Stanislas Rolland",
-                       developerUrl    : "http://www.sjbr.ca/",
-                       copyrightOwner  : "Stanislas Rolland",
-                       sponsor         : "SJBR",
-                       sponsorUrl      : "http://www.sjbr.ca/",
-                       license         : "GPL"
+                       version         : '4.0',
+                       developer       : 'Stanislas Rolland',
+                       developerUrl    : 'http://www.sjbr.ca/',
+                       copyrightOwner  : 'Stanislas Rolland',
+                       sponsor         : 'SJBR',
+                       sponsorUrl      : 'http://www.sjbr.ca/',
+                       license         : 'GPL'
                };
                this.registerPluginInformation(pluginInformation);
                /*
@@ -87,7 +83,7 @@ TYPO3Color = HTMLArea.Plugin.extend({
                        var buttonConfiguration = {
                                id              : buttonId,
                                tooltip         : this.localize(buttonId),
-                               action          : "onButtonPress",
+                               action          : 'onButtonPress',
                                hotKey          : (this.buttonsConfiguration[button[1]] ? this.buttonsConfiguration[button[1]].hotKey : null),
                                dialog          : true
                        };
@@ -342,11 +338,24 @@ TYPO3Color = HTMLArea.Plugin.extend({
                this.restoreSelection();
                var buttonId = this.dialog.arguments.buttonId;
                var color = '#' + this.dialog.find('itemId', 'color')[0].getValue();
+               this.editor.focus();
+               var     element,
+                       fullNodeSelected = false;
                var selection = this.editor._getSelection();
-               var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
                var range = this.editor._createRange(selection);
-               if (this.editor._selectionEmpty(selection)) {
-                       var element = this.editor.getParentElement(selection, range);
+               var parent = this.editor.getParentElement(selection, range);
+               var selectionEmpty = this.editor._selectionEmpty(selection);
+               var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
+               if (!selectionEmpty) {
+                       var ancestors = this.editor.getAllAncestors();
+                       var fullySelectedNode = this.editor.getFullySelectedNode(selection, range, ancestors);
+                       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