From dc7e49f611caac9d045c36d6078d45b876becb75 Mon Sep 17 00:00:00 2001 From: Daniel Thies Date: Wed, 5 Aug 2015 06:56:47 -0500 Subject: [PATCH] MDL-46387 editor-atto: Toggle sub/sup tags using rangy In order to have a consistant browser behavior and prevent nesting of subscripts and superscripts, tags are converted to CSS classes and then the CSS classes in the selection are toggeled using the rangy crossbrowser selection library already included in Atto without using an execCommand. --- .../moodle-atto_subscript-button-debug.js | 46 ++++- .../moodle-atto_subscript-button-min.js | 2 +- .../moodle-atto_subscript-button.js | 46 ++++- .../subscript/yui/src/button/js/button.js | 46 ++++- .../moodle-atto_superscript-button-debug.js | 46 ++++- .../moodle-atto_superscript-button-min.js | 2 +- .../moodle-atto_superscript-button.js | 46 ++++- .../superscript/yui/src/button/js/button.js | 46 ++++- .../moodle-editor_atto-editor-debug.js | 170 ++++++++++++++++++ .../moodle-editor_atto-editor-min.js | 8 +- .../moodle-editor_atto-editor.js | 170 ++++++++++++++++++ .../moodle-editor_atto-plugin-debug.js | 11 +- .../moodle-editor_atto-plugin-min.js | 4 +- .../moodle-editor_atto-plugin.js | 11 +- lib/editor/atto/yui/src/editor/build.json | 1 + lib/editor/atto/yui/src/editor/js/commands.js | 169 +++++++++++++++++ .../src/editor/js/editor-plugin-buttons.js | 11 +- .../atto/yui/src/editor/meta/editor.json | 1 + 18 files changed, 813 insertions(+), 23 deletions(-) create mode 100644 lib/editor/atto/yui/src/editor/js/commands.js diff --git a/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-debug.js b/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-debug.js index a9d5e22d7db..43a8d5ecaf1 100644 --- a/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-debug.js +++ b/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-debug.js @@ -34,13 +34,55 @@ YUI.add('moodle-atto_subscript-button', function (Y, NAME) { */ Y.namespace('M.atto_subscript').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { + /** + * A rangy object to alter CSS classes. + * + * @property _subscriptApplier + * @type Object + * @private + */ + _subscriptApplier: null, + + /** + * A rangy object to alter CSS classes. + * + * @property _superscriptApplier + * @type Object + * @private + */ + _superscriptApplier: null, + initializer: function() { - this.addBasicButton({ - exec: 'subscript', + this.addButton({ + buttonName: 'subscript', + callback: this.toggleSubscript, + icon: 'e/subscript', + inlineFormat: true, // Watch the following tags and add/remove highlighting as appropriate: tags: 'sub' }); + this._subscriptApplier = window.rangy.createCssClassApplier("editor-subscript"); + this._superscriptApplier = window.rangy.createCssClassApplier("editor-superscript"); + }, + + /** + * Toggle subscripts in selection + * + * @method toggleSubscript + */ + toggleSubscript: function() { + // Replace all the sub and sup tags. + this.get('host').changeToCSS('sup', 'editor-superscript'); + this.get('host').changeToCSS('sub', 'editor-subscript'); + + // Remove all superscripts inselection and toggle subscript. + this._superscriptApplier.undoToSelection(); + this._subscriptApplier.toggleSelection(); + + // Replace CSS classes with tags. + this.get('host').changeToTags('editor-subscript', 'sub'); + this.get('host').changeToTags('editor-superscript', 'sup'); } }); diff --git a/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-min.js b/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-min.js index 9510f931d8e..e92ce238c83 100644 --- a/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-min.js +++ b/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-min.js @@ -1 +1 @@ -YUI.add("moodle-atto_subscript-button",function(e,t){e.namespace("M.atto_subscript").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addBasicButton({exec:"subscript",tags:"sub"})}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]}); +YUI.add("moodle-atto_subscript-button",function(e,t){e.namespace("M.atto_subscript").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_subscriptApplier:null,_superscriptApplier:null,initializer:function(){this.addButton({buttonName:"subscript",callback:this.toggleSubscript,icon:"e/subscript",inlineFormat:!0,tags:"sub"}),this._subscriptApplier=window.rangy.createCssClassApplier("editor-subscript"),this._superscriptApplier=window.rangy.createCssClassApplier("editor-superscript")},toggleSubscript:function(){this.get("host").changeToCSS("sup","editor-superscript"),this.get("host").changeToCSS("sub","editor-subscript"),this._superscriptApplier.undoToSelection(),this._subscriptApplier.toggleSelection(),this.get("host").changeToTags("editor-subscript","sub"),this.get("host").changeToTags("editor-superscript","sup")}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]}); diff --git a/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button.js b/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button.js index a9d5e22d7db..43a8d5ecaf1 100644 --- a/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button.js +++ b/lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button.js @@ -34,13 +34,55 @@ YUI.add('moodle-atto_subscript-button', function (Y, NAME) { */ Y.namespace('M.atto_subscript').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { + /** + * A rangy object to alter CSS classes. + * + * @property _subscriptApplier + * @type Object + * @private + */ + _subscriptApplier: null, + + /** + * A rangy object to alter CSS classes. + * + * @property _superscriptApplier + * @type Object + * @private + */ + _superscriptApplier: null, + initializer: function() { - this.addBasicButton({ - exec: 'subscript', + this.addButton({ + buttonName: 'subscript', + callback: this.toggleSubscript, + icon: 'e/subscript', + inlineFormat: true, // Watch the following tags and add/remove highlighting as appropriate: tags: 'sub' }); + this._subscriptApplier = window.rangy.createCssClassApplier("editor-subscript"); + this._superscriptApplier = window.rangy.createCssClassApplier("editor-superscript"); + }, + + /** + * Toggle subscripts in selection + * + * @method toggleSubscript + */ + toggleSubscript: function() { + // Replace all the sub and sup tags. + this.get('host').changeToCSS('sup', 'editor-superscript'); + this.get('host').changeToCSS('sub', 'editor-subscript'); + + // Remove all superscripts inselection and toggle subscript. + this._superscriptApplier.undoToSelection(); + this._subscriptApplier.toggleSelection(); + + // Replace CSS classes with tags. + this.get('host').changeToTags('editor-subscript', 'sub'); + this.get('host').changeToTags('editor-superscript', 'sup'); } }); diff --git a/lib/editor/atto/plugins/subscript/yui/src/button/js/button.js b/lib/editor/atto/plugins/subscript/yui/src/button/js/button.js index 337d3628e40..07dc361b32f 100644 --- a/lib/editor/atto/plugins/subscript/yui/src/button/js/button.js +++ b/lib/editor/atto/plugins/subscript/yui/src/button/js/button.js @@ -32,12 +32,54 @@ */ Y.namespace('M.atto_subscript').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { + /** + * A rangy object to alter CSS classes. + * + * @property _subscriptApplier + * @type Object + * @private + */ + _subscriptApplier: null, + + /** + * A rangy object to alter CSS classes. + * + * @property _superscriptApplier + * @type Object + * @private + */ + _superscriptApplier: null, + initializer: function() { - this.addBasicButton({ - exec: 'subscript', + this.addButton({ + buttonName: 'subscript', + callback: this.toggleSubscript, + icon: 'e/subscript', + inlineFormat: true, // Watch the following tags and add/remove highlighting as appropriate: tags: 'sub' }); + this._subscriptApplier = window.rangy.createCssClassApplier("editor-subscript"); + this._superscriptApplier = window.rangy.createCssClassApplier("editor-superscript"); + }, + + /** + * Toggle subscripts in selection + * + * @method toggleSubscript + */ + toggleSubscript: function() { + // Replace all the sub and sup tags. + this.get('host').changeToCSS('sup', 'editor-superscript'); + this.get('host').changeToCSS('sub', 'editor-subscript'); + + // Remove all superscripts inselection and toggle subscript. + this._superscriptApplier.undoToSelection(); + this._subscriptApplier.toggleSelection(); + + // Replace CSS classes with tags. + this.get('host').changeToTags('editor-subscript', 'sub'); + this.get('host').changeToTags('editor-superscript', 'sup'); } }); diff --git a/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-debug.js b/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-debug.js index bcad8e55b01..d5285d34734 100644 --- a/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-debug.js +++ b/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-debug.js @@ -34,13 +34,55 @@ YUI.add('moodle-atto_superscript-button', function (Y, NAME) { */ Y.namespace('M.atto_superscript').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { + /** + * A rangy object to alter CSS classes. + * + * @property _subscriptApplier + * @type Object + * @private + */ + _subscriptApplier: null, + + /** + * A rangy object to alter CSS classes. + * + * @property _superscriptApplier + * @type Object + * @private + */ + _superscriptApplier: null, + initializer: function() { - this.addBasicButton({ - exec: 'superscript', + this.addButton({ + buttonName: 'superscript', + inlineFormat: true, + callback: this.toggleSuperscript, + icon: 'e/superscript', // Watch the following tags and add/remove highlighting as appropriate: tags: 'sup' }); + this._subscriptApplier = window.rangy.createCssClassApplier("editor-subscript"); + this._superscriptApplier = window.rangy.createCssClassApplier("editor-superscript"); + }, + + /** + * Toggle superscripts in selection + * + * @method toggleSuperscript + */ + toggleSuperscript: function() { + // Replace all the sub and sup tags. + this.get('host').changeToCSS('sub', 'editor-subscript'); + this.get('host').changeToCSS('sup', 'editor-superscript'); + + // Remove all subscripts inselection and toggle superscript. + this._superscriptApplier.toggleSelection(); + this._subscriptApplier.undoToSelection(); + + // Replace CSS classes with tags. + this.get('host').changeToTags('editor-superscript', 'sup'); + this.get('host').changeToTags('editor-subscript', 'sub'); } }); diff --git a/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-min.js b/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-min.js index 70ac08d0fc7..d7dcc400135 100644 --- a/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-min.js +++ b/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-min.js @@ -1 +1 @@ -YUI.add("moodle-atto_superscript-button",function(e,t){e.namespace("M.atto_superscript").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){this.addBasicButton({exec:"superscript",tags:"sup"})}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]}); +YUI.add("moodle-atto_superscript-button",function(e,t){e.namespace("M.atto_superscript").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_subscriptApplier:null,_superscriptApplier:null,initializer:function(){this.addButton({buttonName:"superscript",inlineFormat:!0,callback:this.toggleSuperscript,icon:"e/superscript",tags:"sup"}),this._subscriptApplier=window.rangy.createCssClassApplier("editor-subscript"),this._superscriptApplier=window.rangy.createCssClassApplier("editor-superscript")},toggleSuperscript:function(){this.get("host").changeToCSS("sub","editor-subscript"),this.get("host").changeToCSS("sup","editor-superscript"),this._superscriptApplier.toggleSelection(),this._subscriptApplier.undoToSelection(),this.get("host").changeToTags("editor-superscript","sup"),this.get("host").changeToTags("editor-subscript","sub")}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]}); diff --git a/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button.js b/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button.js index bcad8e55b01..d5285d34734 100644 --- a/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button.js +++ b/lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button.js @@ -34,13 +34,55 @@ YUI.add('moodle-atto_superscript-button', function (Y, NAME) { */ Y.namespace('M.atto_superscript').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { + /** + * A rangy object to alter CSS classes. + * + * @property _subscriptApplier + * @type Object + * @private + */ + _subscriptApplier: null, + + /** + * A rangy object to alter CSS classes. + * + * @property _superscriptApplier + * @type Object + * @private + */ + _superscriptApplier: null, + initializer: function() { - this.addBasicButton({ - exec: 'superscript', + this.addButton({ + buttonName: 'superscript', + inlineFormat: true, + callback: this.toggleSuperscript, + icon: 'e/superscript', // Watch the following tags and add/remove highlighting as appropriate: tags: 'sup' }); + this._subscriptApplier = window.rangy.createCssClassApplier("editor-subscript"); + this._superscriptApplier = window.rangy.createCssClassApplier("editor-superscript"); + }, + + /** + * Toggle superscripts in selection + * + * @method toggleSuperscript + */ + toggleSuperscript: function() { + // Replace all the sub and sup tags. + this.get('host').changeToCSS('sub', 'editor-subscript'); + this.get('host').changeToCSS('sup', 'editor-superscript'); + + // Remove all subscripts inselection and toggle superscript. + this._superscriptApplier.toggleSelection(); + this._subscriptApplier.undoToSelection(); + + // Replace CSS classes with tags. + this.get('host').changeToTags('editor-superscript', 'sup'); + this.get('host').changeToTags('editor-subscript', 'sub'); } }); diff --git a/lib/editor/atto/plugins/superscript/yui/src/button/js/button.js b/lib/editor/atto/plugins/superscript/yui/src/button/js/button.js index 80bfeece74e..fce14a12bb2 100644 --- a/lib/editor/atto/plugins/superscript/yui/src/button/js/button.js +++ b/lib/editor/atto/plugins/superscript/yui/src/button/js/button.js @@ -32,12 +32,54 @@ */ Y.namespace('M.atto_superscript').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { + /** + * A rangy object to alter CSS classes. + * + * @property _subscriptApplier + * @type Object + * @private + */ + _subscriptApplier: null, + + /** + * A rangy object to alter CSS classes. + * + * @property _superscriptApplier + * @type Object + * @private + */ + _superscriptApplier: null, + initializer: function() { - this.addBasicButton({ - exec: 'superscript', + this.addButton({ + buttonName: 'superscript', + callback: this.toggleSuperscript, + icon: 'e/superscript', + inlineFormat: true, // Watch the following tags and add/remove highlighting as appropriate: tags: 'sup' }); + this._subscriptApplier = window.rangy.createCssClassApplier("editor-subscript"); + this._superscriptApplier = window.rangy.createCssClassApplier("editor-superscript"); + }, + + /** + * Toggle superscripts in selection + * + * @method toggleSuperscript + */ + toggleSuperscript: function() { + // Replace all the sub and sup tags. + this.get('host').changeToCSS('sub', 'editor-subscript'); + this.get('host').changeToCSS('sup', 'editor-superscript'); + + // Remove all subscripts inselection and toggle superscript. + this._superscriptApplier.toggleSelection(); + this._subscriptApplier.undoToSelection(); + + // Replace CSS classes with tags. + this.get('host').changeToTags('editor-superscript', 'sup'); + this.get('host').changeToTags('editor-subscript', 'sub'); } }); diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js index 04823a04705..22162fc1a89 100644 --- a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js +++ b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js @@ -1401,6 +1401,175 @@ Y.Base.mix(Y.M.editor_atto.Editor, [EditorClean]); // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * @module moodle-editor_atto-editor + * @submodule commands + */ + +/** + * Selection functions for the Atto editor. + * + * See {{#crossLink "M.editor_atto.Editor"}}{{/crossLink}} for details. + * + * @namespace M.editor_atto + * @class EditorCommand + */ + +function EditorCommand() {} + +EditorCommand.ATTRS= { +}; + +EditorCommand.prototype = { + /** + * Applies a callback method to editor if selection is uncollapsed or waits for input to select first. + * @method applyFormat + * @param e EventTarget Event to be passed to callback if selection is uncollapsed + * @param method callback A callback method which changes editor when text is selected. + * @param object context Context to be used for callback method + * @param array args Array of arguments to pass to callback + */ + applyFormat: function(e, callback, context, args) { + function handleInsert(e, callback, context, args, anchorNode, anchorOffset) { + // After something is inputed, select it and apply the formating function. + Y.soon(Y.bind(function(e, callback, context, args, anchorNode, anchorOffset) { + var selection = window.rangy.getSelection(); + + // Set the start of the selection to where it was when the method was first called. + var range = selection.getRangeAt(0); + range.setStart(anchorNode, anchorOffset); + selection.setSingleRange(range); + + // Now apply callback to the new text that is selected. + callback.apply(context, [e, args]); + + // Collapse selection so cursor is at end of inserted material. + selection.collapseToEnd(); + }, this, e, callback, context, args, anchorNode, anchorOffset)); + } + + // Set default context for the method. + context = context || this; + + // Check whether range is collapsed. + var selection = window.rangy.getSelection(); + + if (selection.isCollapsed) { + // Selection is collapsed so listen for input into editor. + var handle = this.editor.once('input', handleInsert, this, callback, context, args, + selection.anchorNode, selection.anchorOffset); + + // Cancel if selection changes before input. + this.editor.onceAfter(['click', 'selectstart'], handle.detach, handle); + + return; + } + + // The range is not collapsed; so apply callback method immediately. + callback.apply(context, [e, args]); + + }, + + /** + * Replaces all the tags in a node list with new type. + * @method replaceTags + * @param NodeList nodelist + * @param String tag + */ + replaceTags: function(nodelist, tag) { + // We mark elements in the node list for iterations. + nodelist.setAttribute('data-iterate', true); + var node = this.editor.one('[data-iterate="true"]'); + while (node) { + var clone = Y.Node.create('<' + tag + ' />') + .setAttrs(node.getAttrs()) + .removeAttribute('data-iterate'); + // Copy class and style if not blank. + if (node.getAttribute('style')) { + clone.setAttribute('style', node.getAttribute('style')); + } + if (node.getAttribute('class')) { + clone.setAttribute('class', node.getAttribute('class')); + } + // We use childNodes here because we are interested in both type 1 and 3 child nodes. + var children = node.getDOMNode().childNodes, child; + child = children[0]; + while (typeof child !== "undefined") { + clone.append(child); + child = children[0]; + } + node.replace(clone); + node = this.editor.one('[data-iterate="true"]'); + } + }, + + /** + * Change all tags with given type to a span with CSS class attribute. + * @method changeToCSS + * @param String tag Tag type to be changed to span + * @param String markerClass CSS class that corresponds to desired tag + */ + changeToCSS: function(tag, markerClass) { + // Save the selection. + var selection = window.rangy.saveSelection(); + + // Remove display:none from rangy markers so browser doesn't delete them. + this.editor.all('.rangySelectionBoundary').setStyle('display', null); + + // Replace tags with CSS classes. + this.editor.all(tag).addClass(markerClass); + this.replaceTags(this.editor.all('.' + markerClass), 'span'); + + // Restore selection and toggle class. + window.rangy.restoreSelection(selection); + }, + + /** + * Change spans with CSS classes in editor into elements with given tag. + * @method changeToCSS + * @param String markerClass CSS class that corresponds to desired tag + * @param String tag New tag type to be created + */ + changeToTags: function(markerClass, tag) { + // Save the selection. + var selection = window.rangy.saveSelection(); + + // Remove display:none from rangy markers so browser doesn't delete them. + this.editor.all('.rangySelectionBoundary').setStyle('display', null); + + // Replace spans with given tag. + this.replaceTags(this.editor.all('span[class="' + markerClass + '"]'), tag); + this.editor.all(tag + '[class="' + markerClass + '"]').removeAttribute('class'); + this.editor.all('.' + markerClass).each(function(n) { + n.wrap('<' + tag + '/>'); + n.removeClass(markerClass); + }); + + // Remove CSS classes. + this.editor.all('[class="' + markerClass + '"]').removeAttribute('class'); + this.editor.all(tag).removeClass(markerClass); + + // Restore selection. + window.rangy.restoreSelection(selection); + } +}; + +Y.Base.mix(Y.M.editor_atto.Editor, [EditorCommand]); +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + /** * @module moodle-editor_atto-editor * @submodule toolbar @@ -2383,6 +2552,7 @@ Y.Base.mix(Y.M.editor_atto.Editor, [EditorFilepicker]); "event", "event-simulate", "event-custom", + "node-event-html5", "yui-throttle", "moodle-core-notification-dialogue", "moodle-core-notification-confirm", diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js index a8fac2b38eb..a27465ad534 100644 --- a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js +++ b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js @@ -1,4 +1,4 @@ -YUI.add("moodle-editor_atto-editor",function(e,t){function i(){i.superclass.constructor.apply(this,arguments)}function a(){}function f(){}function p(){}function d(){}function v(){}function m(){}function g(){}function y(){}function b(){}var n="moodle-editor_atto-editor",r={CONTENT:"editor_atto_content",CONTENTWRAPPER:"editor_atto_content_wrap",TOOLBAR:"editor_atto_toolbar",WRAPPER:"editor_atto",HIGHLIGHT:"highlight"};e.extend(i,e.Base,{BLOCK_TAGS:["address","article","aside","audio","blockquote","canvas","dd","div","dl","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","hr","noscript","ol","output","p","pre","section","table","tfoot","ul","video"],PLACEHOLDER_CLASS:"atto-tmp-class",ALL_NODES_SELECTOR:"[style],font[face]",FONT_FAMILY:"fontFamily",_wrapper:null,editor:null,textarea:null,textareaLabel:null,plugins:null,_eventHandles:null,initializer:function(){var t;this.textarea=e.one(document.getElementById(this.get("elementid")));if(!this.textarea)return;this._eventHandles=[],this._wrapper=e.Node.create('
'),t=e.Handlebars.compile('
'),this.editor=e.Node.create(t({elementid:this.get("elementid"),CSS:r})),this.textareaLabel=e.one('[for="'+this.get("elementid")+'"]'),this.textareaLabel&&(this.textareaLabel.generateID(),this.editor.setAttribute("aria-labelledby",this.textareaLabel.get("id"))),this.setupToolbar();var n=e.Node.create('
');n.appendChild(this.editor),this._wrapper.appendChild(n),this.editor.setStyle("minHeight",20*this.textarea.getAttribute("rows")+8+"px"),e.UA.ie===0&&this.editor.setStyle("height",20*this.textarea.getAttribute("rows")+8+"px"),this.disableCssStyling(),document.queryCommandSupported("DefaultParagraphSeparator")&&document.execCommand("DefaultParagraphSeparator",!1,"p"),this.textarea.get("parentNode").insert(this._wrapper,this.textarea).setAttribute("class","editor_atto_wrap"),this.textarea.hide(),this.updateFromTextArea(),this.publishEvents(),this.setupSelectionWatchers(),this.setupAutomaticPolling(),this.setupPlugins(),this.setupAutosave(),this.setupNotifications()},focus:function(){return this.editor.focus(),this},publishEvents:function(){return this.publish("change",{broadcast:!0,preventable:!0}),this.publish("pluginsloaded",{fireOnce:!0}),this.publish("atto:selectionchanged",{prefix:"atto"}),this},setupAutomaticPolling:function(){return this._registerEventHandle(this.editor.on(["keyup","cut"],this.updateOriginal,this)),this._registerEventHandle(this.editor.on("paste",this.pasteCleanup,this)),this._registerEventHandle(this.editor.on("drop",this.updateOriginalDelayed,this)),this},updateOriginalDelayed:function(){return e.soon(e.bind(this.updateOriginal,this)),this},setupPlugins:function(){this.plugins={};var t=this.get("plugins"),n,r,i,s,o;for(n in t){r=t[n];if(!r.plugins)continue;for(i in r.plugins){s=r.plugins[i],o=e.mix({name:s.name,group:r.group,editor:this.editor,toolbar:this.toolbar,host:this},s);if(typeof e.M["atto_"+s.name]=="undefined")continue;this.plugins[s.name]=new e.M["atto_"+s.name].Button(o)}}return this.fire("pluginsloaded"),this},enablePlugins:function(e){this._setPluginState(!0,e)},disablePlugins:function(e){this._setPluginState(!1,e)},_setPluginState:function(t,n){var r="disableButtons";t&&(r="enableButtons"),n?this.plugins[n][r]():e.Object.each(this.plugins,function(e){e[r]()},this)},_registerEventHandle:function(e){this._eventHandles.push(e)}},{NS:"editor_atto",ATTRS:{elementid:{value:null,writeOnce:!0},contextid:{value:null,writeOnce:!0},plugins:{value:{},writeOnce:!0}}}),e.augment(i,e.EventTarget),e.namespace("M.editor_atto").Editor=i,e.namespace("M.editor_atto.Editor").init=function(t){return new e.M.editor_atto.Editor(t)};var s="moodle-editor_atto-editor-notify",o="info",u="warning";a.ATTRS={},a.prototype={messageOverlay:null,hideTimer:null,setupNotifications:function(){var e=new Image,t=new Image;return e.src=M.util.image_url("i/warning","moodle"),t.src=M.util.image_url("i/info","moodle"),this},showMessage:function(t,n,r){var i="",s,a;return this.messageOverlay===null&&(this.messageOverlay=e.Node.create('
'),this.messageOverlay.hide(!0),this.textarea.get("parentNode").append(this.messageOverlay),this.messageOverlay.on("click",function(){this.messageOverlay.hide(!0)},this)),this.hideTimer!==null&&this.hideTimer.cancel(),n===u?i=''+M.util.get_string(':n===o&&(i=''+M.util.get_string('),s=parseInt(r,10),s<=0&&(s=6e4),n="atto_"+n,a=e.Node.create('"),this.messageOverlay.empty(),this.messageOverlay.append(a),this.messageOverlay.show(!0),this.hideTimer=e.later(s,this,function(){this.hideTimer=null,this.messageOverlay.hide(!0)}),this}},e.Base.mix(e.M.editor_atto.Editor,[a]),f.ATTRS={},f.prototype={_getEmptyContent:function(){return e.UA.ie&&e.UA.ie<10?"

":"


"},updateFromTextArea:function(){return this.editor.setHTML(""),this.editor.append(this._cleanHTML(this.textarea.get("value"))),this.editor.getHTML()===""&&this.editor.setHTML(this._getEmptyContent()),this},updateOriginal:function(){var e=this.textarea.get("value"),t=this.getCleanHTML();return t===""&&this.isActive()&&(t=this._getEmptyContent()),e!==t&&(this.textarea.set("value",t),this.textarea.simulate("change"),this.fire("change")),this}},e.Base.mix(e.M.editor_atto.Editor,[f]);var l=5e3,c=6e4,h="moodle-editor_atto-editor-autosave";p.ATTRS={autosaveEnabled:{value:!0,writeOnce:!0},autosaveFrequency:{value:60,writeOnce:!0},pageHash:{value:"",writeOnce:!0},autosaveAjaxScript:{value:"/lib/editor/atto/autosave-ajax.php",readOnly:!0}},p.prototype={lastText:"",autosaveInstance -:null,setupAutosave:function(){var t=-1,n,r=null,i=this.get("filepickeroptions");if(!this.get("autosaveEnabled"))return;this.autosaveInstance=e.stamp(this);for(r in i)typeof i[r].itemid!="undefined"&&(t=i[r].itemid);url=M.cfg.wwwroot+this.get("autosaveAjaxScript"),params={sesskey:M.cfg.sesskey,contextid:this.get("contextid"),action:"resume",drafttext:"",draftid:t,elementid:this.get("elementid"),pageinstance:this.autosaveInstance,pagehash:this.get("pageHash")},e.io(url,{method:"POST",data:params,context:this,on:{success:function(e,t){if(typeof t.responseText!="undefined"&&t.responseText!==""){response_json=JSON.parse(t.responseText);if(response_json.result==="

"||response_json.result==="


"||response_json.result==="
")response_json.result="";if(response_json.result==="

 

"||response_json.result==="


 

")response_json.result="";response_json.error||typeof response_json.result=="undefined"?this.showMessage(M.util.get_string("errortextrecovery","editor_atto"),u,c):response_json.result!==this.textarea.get("value")&&response_json.result!==""&&this.recoverText(response_json.result),this._fireSelectionChanged()}},failure:function(){this.showMessage(M.util.get_string("errortextrecovery","editor_atto"),u,c)}}});var s=parseInt(this.get("autosaveFrequency"),10)*1e3;return e.later(s,this,this.saveDraft,!1,!0),n=this.textarea.ancestor("form"),n&&n.on("submit",this.resetAutosave,this),this},resetAutosave:function(){return url=M.cfg.wwwroot+this.get("autosaveAjaxScript"),params={sesskey:M.cfg.sesskey,contextid:this.get("contextid"),action:"reset",elementid:this.get("elementid"),pageinstance:this.autosaveInstance,pagehash:this.get("pageHash")},e.io(url,{method:"POST",data:params,sync:!0}),this},recoverText:function(e){return this.editor.setHTML(e),this.saveSelection(),this.updateOriginal(),this.lastText=e,this.showMessage(M.util.get_string("textrecovered","editor_atto"),o,c),this},saveDraft:function(){this.editor.get("hidden")||this.updateOriginal();var t=this.textarea.get("value");if(t!==this.lastText){url=M.cfg.wwwroot+this.get("autosaveAjaxScript"),params={sesskey:M.cfg.sesskey,contextid:this.get("contextid"),action:"save",drafttext:t,elementid:this.get("elementid"),pagehash:this.get("pageHash"),pageinstance:this.autosaveInstance};var n=function(e,t){var n=parseInt(this.get("autosaveFrequency"),10)*1e3;this.showMessage(M.util.get_string("autosavefailed","editor_atto"),u,n)};e.io(url,{method:"POST",data:params,context:this,on:{error:n,failure:n,success:function(r,i){i.responseText!==""?e.soon(e.bind(n,this,[r,i])):(this.lastText=t,this.showMessage(M.util.get_string("autosavesucceeded","editor_atto"),o,l))}}})}return this}},e.Base.mix(e.M.editor_atto.Editor,[p]),d.ATTRS={},d.prototype={getCleanHTML:function(){var t=this.editor.cloneNode(!0),n;return e.each(t.all('[id^="yui"]'),function(e){e.removeAttribute("id")}),t.all(".atto_control").remove(!0),n=t.get("innerHTML"),n==="

"||n==="


"?"":this._cleanHTML(n)},cleanEditorHTML:function(){var e=this.editor.get("innerHTML");return this.editor.set("innerHTML",this._cleanHTML(e)),this},_cleanHTML:function(e){var t=[{regex:/]*>[\s\S]*?<\/style>/gi,replace:""},{regex:/)/gi,replace:""},{regex:/<\/?(?:title|meta|style|st\d|head|font|html|body|link)[^>]*?>/gi,replace:""}];return this._filterContentWithRules(e,t)},_filterContentWithRules:function(e,t){var n=0;for(n=0;n-1);if(r){var i;try{i=t.clipboardData.getData("text/html")}catch(s){return this.fallbackPasteCleanupDelayed(),!0}e.preventDefault(),i=this._cleanPasteHTML(i);var o=window.rangy.saveSelection();return this.insertContentAtFocusPoint(i),window.rangy.restoreSelection(o),window.rangy.getSelection().collapseToEnd(),this.updateOriginal(),!1}return this.fallbackPasteCleanupDelayed(),!0}return this.fallbackPasteCleanupDelayed(),!0}return this.updateOriginalDelayed(),!0},fallbackPasteCleanup:function(){var e=window.rangy.saveSelection(),t=this.editor.get("innerHTML");return this.editor.set("innerHTML",this._cleanPasteHTML(t)),this.updateOriginal(),window.rangy.restoreSelection(e),this},fallbackPasteCleanupDelayed:function(){return e.soon(e.bind(this.fallbackPasteCleanup,this)),this},_cleanPasteHTML:function(e){if(!e||e.length===0)return"";var t=[{regex:/<\s*\/html\s*>([\s\S]+)$/gi,replace:""},{regex://gi,replace:""},{regex://gi,replace:""},{regex:/]*>[\s\S]*?<\/xml>/gi,replace:""},{regex:/<\?xml[^>]*>[\s\S]*?<\\\?xml>/gi,replace:""},{regex:/<\/?\w+:[^>]*>/gi,replace:""}];e=this._filterContentWithRules(e,t),e=this._cleanHTML(e);if(e.length===0||!e.match(/\S/))return e;var n=document.createElement("div");return n.innerHTML=e,e=n.innerHTML,n.innerHTML="",t=[{regex:/(<[^>]*?style\s*?=\s*?")([^>"]*)(")/gi,replace:function(e,t,n,r){return n=n.replace(/(?:^|;)[\s]*MSO[-:](?:&[\w]*;|[^;"])*/gi,""),t+n+r}},{regex:/(<[^>]*?class\s*?=\s*?")([^>"]*)(")/gi,replace:function(e,t,n,r){return n=n.replace(/(?:^|[\s])[\s]*MSO[_a-zA-Z0-9\-]*/gi,""),n=n.replace(/(?:^|[\s])[\s]*Apple-[_a-zA-Z0-9\-]*/gi,""),t+n+r}},{regex:/]*?name\s*?=\s*?"OLE_LINK\d*?"[^>]*?>\s*?<\/a>/gi,replace:""}],e=this._filterContentWithRules(e,t),e=this._cleanHTML(e),e=this._cleanSpans(e),e},_cleanSpans:function(e){if(!e||e.length===0)return"";if(e.length===0||!e.match(/\S/))return e;var t=[{regex:/(<[^>]*?)(?:[\s]*(?:class|style|id)\s*?=\s*?"\s*?")+/gi,replace:"$1"}];e=this._filterContentWithRules(e,t);var n=document.createElement("div");n.innerHTML=e;var r=n.getElementsByTagName("span"),i=Array.prototype.slice.call(r,0);return i.forEach(function(e){if(!e.hasAttributes()){while(e.firstChild -)e.parentNode.insertBefore(e.firstChild,e);e.parentNode.removeChild(e)}}),n.innerHTML}},e.Base.mix(e.M.editor_atto.Editor,[d]),v.ATTRS={},v.prototype={toolbar:null,openMenus:null,setupToolbar:function(){return this.toolbar=e.Node.create('