Index: typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js =================================================================== --- typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js (révision 7296) +++ typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js (copie de travail) @@ -87,8 +87,6 @@ if (!Ext.isString(HTMLArea.editedContentCSS)) { HTMLArea.editedContentCSS = HTMLArea.editorSkin + 'htmlarea-edited-content.css'; } - // Initialize pending request flag for Opera - HTMLArea.pendingSynchronousXMLHttpRequest = false; // Localization of core script HTMLArea.I18N = HTMLArea_langArray; HTMLArea.isReady = true; @@ -1171,8 +1169,8 @@ * Handler for other key events */ onAnyKey: function(event) { - // In Opera, inhibit key events while synchronous XMLHttpRequest is being processed - if (Ext.isOpera && HTMLArea.pendingSynchronousXMLHttpRequest) { + // Inhibit key events while server-based cleaning is being processed + if (this.getEditor().inhibitKeyboardInput) { event.stopEvent(); return false; } @@ -2288,6 +2286,8 @@ this.registerPlugin(plugin); } }, this); + // Initialize keyboard input inhibit flag + this.inhibitKeyboardInput = false; this.addEvents( /* * @event editorready @@ -4160,6 +4160,7 @@ success = true; }, failure: function (response) { + this.editor.inhibitKeyboardInput = false; this.appendToLog('getJavascriptFile', 'Unable to get ' + url + ' . Server reported ' + response.status); }, scope: this Index: typo3/sysext/rtehtmlarea/htmlarea/plugins/CopyPaste/copy-paste.js =================================================================== --- typo3/sysext/rtehtmlarea/htmlarea/plugins/CopyPaste/copy-paste.js (révision 7296) +++ typo3/sysext/rtehtmlarea/htmlarea/plugins/CopyPaste/copy-paste.js (copie de travail) @@ -30,59 +30,45 @@ * TYPO3 SVN ID: $Id$ */ CopyPaste = 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) { /* * Setting up some properties from PageTSConfig */ this.buttonsConfiguration = this.editorConfiguration.buttons; - /* * Registering plugin "About" information */ var pluginInformation = { - version : "2.0", - 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" + version : '2.0', + 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 */ - for (var buttonId in this.buttonList) { - if (this.buttonList.hasOwnProperty(buttonId)) { - var button = this.buttonList[buttonId]; - var buttonConfiguration = { - id : buttonId, - tooltip : this.localize(buttonId.toLowerCase()), - iconCls : 'htmlarea-action-' + button[2], - action : 'onButtonPress', - context : button[0], - selection : button[3], - hotKey : (this.buttonsConfiguration[button[2]] ? this.buttonsConfiguration[button[2]].hotKey : (button[1] ? button[1] : null)) - }; - this.registerButton(buttonConfiguration); - if (!this.isButtonInToolbar(buttonId)) { - var hotKeyConfiguration = { - id : buttonConfiguration.hotKey, - cmd : buttonConfiguration.id - }; - this.registerHotKey(hotKeyConfiguration); - } - } - } + Ext.iterate(this.buttonList, function (buttonId, button) { + var buttonConfiguration = { + id : buttonId, + tooltip : this.localize(buttonId.toLowerCase()), + iconCls : 'htmlarea-action-' + button[2], + action : 'onButtonPress', + context : button[0], + selection : button[3], + hotKey : button[1] + }; + this.registerButton(buttonConfiguration); + }, this); return true; }, /* @@ -94,75 +80,134 @@ Paste : [null, 'v', 'paste', false] }, /* + * This function gets called when the editor is generated + */ + onGenerate: function () { + this.editor.iframe.mon(Ext.get(Ext.isIE ? this.editor.document.body : this.editor.document.documentElement), 'cut', this.cutHandler, this); + // Add hot key handling if the button is not enabled in the toolbar + Ext.iterate(this.buttonList, function (buttonId, button) { + if (!this.isButtonInToolbar(buttonId)) { + this.editor.iframe.hotKeyMap.addBinding({ + key: button[1].toUpperCase(), + ctrl: true, + shift: false, + alt: false, + handler: this.onHotKey, + scope: this + }); + // Ensure the hot key can be translated + this.editorConfiguration.hotKeyList[button[1]] = { + id : button[1], + cmd : buttonId + }; + } + }, this); + }, + /* * This function gets called when a button or a hotkey 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) { + onButtonPress: function (editor, id) { // Could be a button or its hotkey var buttonId = this.translateHotKey(id); buttonId = buttonId ? buttonId : id; this.editor.focus(); - if (!this.applyToTable(buttonId, target)) { + if (!this.applyToTable(buttonId)) { // If we are not handling table cells switch (buttonId) { - case "Copy": - case "Cut" : + case 'Copy': if (buttonId == id) { // If we are handling a button, not a hotkey this.applyBrowserCommand(buttonId); - } else if (buttonId == "Cut") { - // If we are handling the cut hotkey - this.removeEmptyLinkLater.defer(50, this); } break; - case "Paste": + case 'Cut' : if (buttonId == id) { // If we are handling a button, not a hotkey this.applyBrowserCommand(buttonId); } + // Opera will not trigger the onCut event + if (Ext.isOpera) { + this.cutHandler(); + } + break; + case 'Paste': + if (buttonId == id) { + // If we are handling a button, not a hotkey + this.applyBrowserCommand(buttonId); + } // In FF3, the paste operation will indeed trigger the onPaste event not in FF2; nor in Opera - if (Ext.isOpera || Ext.isGecko2 || Ext.isWebKit) { - var cleaner = this.getPluginInstance('DefaultClean'); - if (!cleaner) { - cleaner = this.getPluginInstance('TYPO3HtmlParser'); - } + if (Ext.isOpera || Ext.isGecko2) { + var cleaner = this.getButton('CleanWord'); if (cleaner) { - cleaner.clean.defer(50, cleaner); + cleaner.fireEvent.defer(250, cleaner, ['click', cleaner]); } } break; default: break; } + // Stop the event if a button was handled return (buttonId != id); } else { - // We handled the table case + // The table case was handled, let the event be stopped. + // No cleaning required as the pasted cells are copied from the editor. + // However paste by Opera cannot be stopped. + // Revert Opera's operation as it produces invalid html anyways + if (Ext.isOpera) { + this.editor.inhibitKeyboardInput = true; + var bookmark = this.editor.getBookmark(this.editor._createRange(this.editor._getSelection())); + var html = this.editor.getInnerHTML(); + this.revertPaste.defer(200, this, [html, bookmark]); + } return false; } }, - - applyBrowserCommand : function (buttonId) { + /* + * This funcion reverts the paste operation (performed by Opera) + */ + revertPaste: function (html, bookmark) { + this.editor.setHTML(html); + this.editor.selectRange(this.editor.moveToBookmark(bookmark)); + this.editor.inhibitKeyboardInput = false; + }, + /* + * This function applies the browser command when a button is pressed + * In the case of hot key, the browser does it automatically + */ + applyBrowserCommand: function (buttonId) { try { - this.editor._doc.execCommand(buttonId, false, null); + this.editor.document.execCommand(buttonId, false, null); } catch (e) { if (Ext.isGecko) { this.mozillaClipboardAccessException(); } } - if (buttonId == "Cut") { - this.removeEmptyLink(); + }, + /* + * Handler for hotkeys configured through the hotKeyMap while button not enabled in toolbar (see onGenerate above) + */ + onHotKey: function (key, event) { + var hotKey = String.fromCharCode(key).toLowerCase(); + // Stop the event if it was handled here + if (!this.onButtonPress(this, hotKey)) { + event.stopEvent(); } }, - /* + * This function removes any link left over by the cut operation + */ + cutHandler: function (event) { + this.removeEmptyLink.defer(50, this); + }, + /* * This function unlinks any empty link left over by the cut operation */ - removeEmptyLink : function() { + removeEmptyLink: function() { var selection = this.editor._getSelection(); var range = this.editor._createRange(selection); var parent = this.editor.getParentElement(selection, range); @@ -177,7 +222,7 @@ this.editor.removeMarkup(parent); // Opera does not render empty list items if (Ext.isOpera && /^(li)$/i.test(container.nodeName) && !container.firstChild) { - container.innerHTML = "
"; + container.innerHTML = '
'; this.editor.selectNodeContents(container, true); } } else { @@ -187,49 +232,45 @@ } if (Ext.isWebKit) { // Remove Apple's span and font tags - this.editor.cleanAppleStyleSpans(this.editor._doc.body); + this.editor.cleanAppleStyleSpans(this.editor.document.body); // Reset Safari selection in order to prevent insertion of span and/or font tags on next text input var bookmark = this.editor.getBookmark(this.editor._createRange(this.editor._getSelection())); this.editor.selectRange(this.editor.moveToBookmark(bookmark)); } - }, - - /* - * This function removes any link left over by the cut operation triggered by hotkey - */ - removeEmptyLinkLater : function() { - this.removeEmptyLink(); this.editor.updateToolbar(); }, - /* - * This function gets called by the main editor when a copy/cut/paste operation is to be performed + * This function gets called when a copy/cut/paste operation is to be performed + * This feature allows to paste a region of table cells */ - applyToTable : function (buttonId, target) { + applyToTable: function (buttonId) { var selection = this.editor._getSelection(); var range = this.editor._createRange(selection); var parent = this.editor.getParentElement(selection, range); var endBlocks = this.editor.getEndBlocks(selection); switch (buttonId) { - case "Copy": - case "Cut" : + case 'Copy': + case 'Cut' : HTMLArea.copiedCells = null; var endBlocks = this.editor.getEndBlocks(selection); if ((/^(tr)$/i.test(parent.nodeName) && !Ext.isIE) || (/^(td|th)$/i.test(endBlocks.start.nodeName) && /^(td|th)$/i.test(endBlocks.end.nodeName) && !Ext.isGecko && endBlocks.start != endBlocks.end)) { HTMLArea.copiedCells = this.collectCells(buttonId, selection, endBlocks); - if (buttonId === "Cut") return true; } break; - case "Paste": + case 'Paste': if (/^(tr|td|th)$/i.test(parent.nodeName) && HTMLArea.copiedCells) { return this.pasteCells(selection, endBlocks); } break; + default: + break; } return false; }, - - pasteCells : function (selection, endBlocks) { + /* + * This function handles pasting of a collection of table cells + */ + pasteCells: function (selection, endBlocks) { var cell = null; if (Ext.isGecko) { range = selection.getRangeAt(0); @@ -241,8 +282,11 @@ if (!cell && /^(td|th)$/i.test(endBlocks.start.nodeName)) { cell = endBlocks.start; } - if (!cell) return false; - var tableParts = ["thead", "tbody", "tfoot"]; + if (!cell) { + // Let the browser do it + return false; + } + var tableParts = ['thead', 'tbody', 'tfoot']; var tablePartsIndex = { thead : 0, tbody : 1, tfoot : 2 }; var tablePart = cell.parentNode.parentNode; var tablePartIndex = tablePartsIndex[tablePart.nodeName.toLowerCase()] @@ -274,12 +318,11 @@ } return true; }, - /* * This function collects the selected table cells for copy/cut operations */ - collectCells : function (operation, selection, endBlocks) { - var tableParts = ["thead", "tbody", "tfoot"]; + collectCells: function (operation, selection, endBlocks) { + var tableParts = ['thead', 'tbody', 'tfoot']; var tablePartsIndex = { thead : 0, tbody : 1, tfoot : 2 }; var selection = this.editor._getSelection(); var range, i = 0, cell, cells = null; @@ -295,10 +338,10 @@ for (var i = 0, n = endBlocks.start.cells.length; i < n; ++i) { cell = endBlocks.start.cells[i]; cells.push(cell.innerHTML); - if (operation === "Cut") { - cell.innerHTML = "
"; + if (operation === 'Cut') { + cell.innerHTML = '
'; } - if (operation === "Cut") { + if (operation === 'Cut') { cutRows.push(endBlocks.start); } } @@ -311,15 +354,15 @@ cell = range.startContainer.childNodes[range.startOffset]; if (cell.parentNode != row) { (cells) && rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells); - if (operation === "Cut" && firstCellOfRow && lastCellOfRow) cutRows.push(row); + if (operation === 'Cut' && firstCellOfRow && lastCellOfRow) cutRows.push(row); row = cell.parentNode; cells = []; firstCellOfRow = false; lastCellOfRow = false; } cells.push(cell.innerHTML); - if (operation === "Cut") { - cell.innerHTML = "
"; + if (operation === 'Cut') { + cell.innerHTML = '
'; } if (!cell.previousSibling) firstCellOfRow = true; if (!cell.nextSibling) lastCellOfRow = true; @@ -328,7 +371,7 @@ /* finished walking through selection */ } try { rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells); } catch(e) { } - if (row && operation === "Cut" && firstCellOfRow && lastCellOfRow) { + if (row && operation === 'Cut' && firstCellOfRow && lastCellOfRow) { cutRows.push(row); } } @@ -342,8 +385,8 @@ cell = endBlocks.start; while (cell) { cells.push(cell.innerHTML); - if (operation === "Cut") { - cell.innerHTML = ""; + if (operation === 'Cut') { + cell.innerHTML = ''; } if (!cell.previousSibling) firstCellOfRow = true; if (!cell.nextSibling) lastCellOfRow = true; @@ -351,19 +394,19 @@ cell = cell.nextSibling; } rows[tablePartsIndex[firstRow.parentNode.nodeName.toLowerCase()]].push(cells); - if (operation === "Cut" && firstCellOfRow && lastCellOfRow) cutRows.push(firstRow); + if (operation === 'Cut' && firstCellOfRow && lastCellOfRow) cutRows.push(firstRow); } else { // Collect all cells on selected rows row = firstRow; while (row) { cells = []; for (var i = 0, n = row.cells.length; i < n ; ++i) { cells.push(row.cells[i].innerHTML); - if (operation === "Cut") { - row.cells[i].innerHTML = ""; + if (operation === 'Cut') { + row.cells[i].innerHTML = ''; } } rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells); - if (operation === "Cut") cutRows.push(row); + if (operation === 'Cut') cutRows.push(row); if (row == lastRow) break; row = row.nextSibling; } @@ -385,52 +428,48 @@ } return rows; }, - /* * This function gets called when the toolbar is updated */ onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) { if (mode === 'wysiwyg' && this.editor.isEditable() && button.itemId === 'Paste') { try { - button.setDisabled(!this.editor._doc.queryCommandEnabled(button.itemId)); + button.setDisabled(!this.editor.document.queryCommandEnabled(button.itemId)); } catch(e) { button.setDisabled(true); } } }, - /* * Mozilla clipboard access exception handler */ - mozillaClipboardAccessException : function () { + mozillaClipboardAccessException: function () { if (this.buttonsConfiguration.paste && this.buttonsConfiguration.paste.mozillaAllowClipboardURL) { - if (confirm(this.localize("Allow-Clipboard-Helper-Extension"))) { + if (confirm(this.localize('Allow-Clipboard-Helper-Extension'))) { if (InstallTrigger.enabled()) { var mozillaXpi = new Object(); - mozillaXpi["AllowClipboard Helper"] = this.buttonsConfiguration.paste.mozillaAllowClipboardURL; + mozillaXpi['AllowClipboard Helper'] = this.buttonsConfiguration.paste.mozillaAllowClipboardURL; InstallTrigger.install(mozillaXpi, this.mozillaInstallCallback); } else { - alert(this.localize("Mozilla-Org-Install-Not-Enabled")); - this.appendToLog("mozillaClipboardAccessException", "Mozilla install was not enabled."); + alert(this.localize('Mozilla-Org-Install-Not-Enabled')); + this.appendToLog('mozillaClipboardAccessException', 'Mozilla install was not enabled.'); return; } } - } else if (confirm(this.localize("Moz-Clipboard"))) { - window.open("http://mozilla.org/editor/midasdemo/securityprefs.html"); + } else if (confirm(this.localize('Moz-Clipboard'))) { + window.open('http://mozilla.org/editor/midasdemo/securityprefs.html'); } }, - /* * Mozilla Add-on installer call back */ - mozillaInstallCallback : function (url, returnCode) { + mozillaInstallCallback: function (url, returnCode) { if (returnCode == 0) { - alert(this.localize("Allow-Clipboard-Helper-Extension-Success")); + alert(this.localize('Allow-Clipboard-Helper-Extension-Success')); } else { - alert(this.localize("Moz-Extension-Failure")); - this.appendToLog("mozillaInstallCallback", "Mozilla install return code was: " + returnCode + "."); + alert(this.localize('Moz-Extension-Failure')); + this.appendToLog('mozillaInstallCallback', 'Mozilla install return code was: ' + returnCode + '.'); } return; } }); - Index: typo3/sysext/rtehtmlarea/htmlarea/plugins/TYPO3HtmlParser/typo3html-parser.js =================================================================== --- typo3/sysext/rtehtmlarea/htmlarea/plugins/TYPO3HtmlParser/typo3html-parser.js (révision 7296) +++ typo3/sysext/rtehtmlarea/htmlarea/plugins/TYPO3HtmlParser/typo3html-parser.js (copie de travail) @@ -86,6 +86,7 @@ this.editor.iframe.mon(Ext.get(Ext.isIE ? this.editor.document.body : this.editor.document.documentElement), 'paste', this.wordCleanHandler, this); }, clean: function() { + this.editor.inhibitKeyboardInput = true; var editor = this.editor; if (Ext.isWebKit) { editor.cleanAppleStyleSpans(editor._doc.body); @@ -107,6 +108,7 @@ } else { this.appendToLog('clean', 'Post request to ' + url + ' failed. Server reported ' + response.status); } + this.editor.inhibitKeyboardInput = false; } ); }, @@ -114,6 +116,6 @@ * Handler for paste, dragdrop and drop events */ wordCleanHandler: function (event) { - this.clean.defer(250, this); + this.clean.defer(50, this); } });