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.
This commit is contained in:
+44
-2
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -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"]});
|
||||
|
||||
+44
-2
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
+44
-2
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -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"]});
|
||||
|
||||
+44
-2
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
+170
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* @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",
|
||||
|
||||
+4
-4
File diff suppressed because one or more lines are too long
+170
@@ -1390,6 +1390,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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* @module moodle-editor_atto-editor
|
||||
* @submodule toolbar
|
||||
@@ -2370,6 +2539,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",
|
||||
|
||||
+10
-1
@@ -687,9 +687,18 @@ EditorPluginButtons.prototype = {
|
||||
inheritFrom = {};
|
||||
}
|
||||
|
||||
|
||||
// First we wrap the callback in function to handle formating of text inserted into collapsed selection.
|
||||
config.inlineFormat = config.inlineFormat || inheritFrom.inlineFormat;
|
||||
config._inlineCallback = config.callback || inheritFrom.callback;
|
||||
config._callback = config.callback || inheritFrom.callback;
|
||||
if (config.inlineFormat && typeof config._inlineCallback === 'function') {
|
||||
config._callback = function(e, args) {
|
||||
this.get('host').applyFormat(e, config._inlineCallback, this, args);
|
||||
};
|
||||
}
|
||||
// We wrap the callback in function to prevent the default action, check whether the editor is
|
||||
// active and focus it, and then mark the field as updated.
|
||||
config._callback = config.callback || inheritFrom.callback;
|
||||
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
|
||||
|
||||
config._callbackNormalized = true;
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+10
-1
@@ -685,9 +685,18 @@ EditorPluginButtons.prototype = {
|
||||
inheritFrom = {};
|
||||
}
|
||||
|
||||
|
||||
// First we wrap the callback in function to handle formating of text inserted into collapsed selection.
|
||||
config.inlineFormat = config.inlineFormat || inheritFrom.inlineFormat;
|
||||
config._inlineCallback = config.callback || inheritFrom.callback;
|
||||
config._callback = config.callback || inheritFrom.callback;
|
||||
if (config.inlineFormat && typeof config._inlineCallback === 'function') {
|
||||
config._callback = function(e, args) {
|
||||
this.get('host').applyFormat(e, config._inlineCallback, this, args);
|
||||
};
|
||||
}
|
||||
// We wrap the callback in function to prevent the default action, check whether the editor is
|
||||
// active and focus it, and then mark the field as updated.
|
||||
config._callback = config.callback || inheritFrom.callback;
|
||||
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
|
||||
|
||||
config._callbackNormalized = true;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"textarea.js",
|
||||
"autosave.js",
|
||||
"clean.js",
|
||||
"commands.js",
|
||||
"toolbar.js",
|
||||
"toolbar-keyboardnav.js",
|
||||
"selection.js",
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* @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]);
|
||||
+10
-1
@@ -538,9 +538,18 @@ EditorPluginButtons.prototype = {
|
||||
inheritFrom = {};
|
||||
}
|
||||
|
||||
|
||||
// First we wrap the callback in function to handle formating of text inserted into collapsed selection.
|
||||
config.inlineFormat = config.inlineFormat || inheritFrom.inlineFormat;
|
||||
config._inlineCallback = config.callback || inheritFrom.callback;
|
||||
config._callback = config.callback || inheritFrom.callback;
|
||||
if (config.inlineFormat && typeof config._inlineCallback === 'function') {
|
||||
config._callback = function(e, args) {
|
||||
this.get('host').applyFormat(e, config._inlineCallback, this, args);
|
||||
};
|
||||
}
|
||||
// We wrap the callback in function to prevent the default action, check whether the editor is
|
||||
// active and focus it, and then mark the field as updated.
|
||||
config._callback = config.callback || inheritFrom.callback;
|
||||
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
|
||||
|
||||
config._callbackNormalized = true;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"event",
|
||||
"event-simulate",
|
||||
"event-custom",
|
||||
"node-event-html5",
|
||||
"yui-throttle",
|
||||
"moodle-core-notification-dialogue",
|
||||
"moodle-core-notification-confirm",
|
||||
|
||||
Reference in New Issue
Block a user