Merge branch 'wip-mdl-51424' of https://github.com/dthies/moodle
[moodle.git] / lib / editor / atto / yui / src / editor / js / commands.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * @module moodle-editor_atto-editor
18  * @submodule commands
19  */
21 /**
22  * Selection functions for the Atto editor.
23  *
24  * See {{#crossLink "M.editor_atto.Editor"}}{{/crossLink}} for details.
25  *
26  * @namespace M.editor_atto
27  * @class EditorCommand
28  */
30 function EditorCommand() {}
32 EditorCommand.ATTRS= {
33 };
35 EditorCommand.prototype = {
36     /**
37      * Applies a callback method to editor if selection is uncollapsed or waits for input to select first.
38      * @method applyFormat
39      * @param e EventTarget Event to be passed to callback if selection is uncollapsed
40      * @param method callback A callback method which changes editor when text is selected.
41      * @param object context Context to be used for callback method
42      * @param array args Array of arguments to pass to callback
43      */
44     applyFormat: function(e, callback, context, args) {
45         function handleInsert(e, callback, context, args, anchorNode, anchorOffset) {
46             // After something is inputed, select it and apply the formating function.
47             Y.soon(Y.bind(function(e, callback, context, args, anchorNode, anchorOffset) {
48                 var selection = window.rangy.getSelection();
50                 // Set the start of the selection to where it was when the method was first called.
51                 var range = selection.getRangeAt(0);
52                 range.setStart(anchorNode, anchorOffset);
53                 selection.setSingleRange(range);
55                 // Now apply callback to the new text that is selected.
56                 callback.apply(context, [e, args]);
58                 // Collapse selection so cursor is at end of inserted material.
59                 selection.collapseToEnd();
61                 // Save save selection and editor contents.
62                 this.saveSelection();
63                 this.updateOriginal();
64             }, this, e, callback, context, args, anchorNode, anchorOffset));
65         }
67         // Set default context for the method.
68         context = context || this;
70         // Check whether range is collapsed.
71         var selection = window.rangy.getSelection();
73         if (selection.isCollapsed) {
74             // Selection is collapsed so listen for input into editor.
75             var handle = this.editor.once('input', handleInsert, this, callback, context, args,
76                     selection.anchorNode, selection.anchorOffset);
78             // Cancel if selection changes before input.
79             this.editor.onceAfter(['click', 'selectstart'], handle.detach, handle);
81             return;
82         }
84         // The range is not collapsed; so apply callback method immediately.
85         callback.apply(context, [e, args]);
87         // Save save selection and editor contents.
88         this.saveSelection();
89         this.updateOriginal();
90     },
92     /**
93      * Replaces all the tags in a node list with new type.
94      * @method replaceTags
95      * @param NodeList nodelist
96      * @param String tag
97      */
98     replaceTags: function(nodelist, tag) {
99         // We mark elements in the node list for iterations.
100         nodelist.setAttribute('data-iterate', true);
101         var node = this.editor.one('[data-iterate="true"]');
102         while (node) {
103             var clone = Y.Node.create('<' + tag + ' />')
104                 .setAttrs(node.getAttrs())
105                 .removeAttribute('data-iterate');
106             // Copy class and style if not blank.
107             if (node.getAttribute('style')) {
108                 clone.setAttribute('style', node.getAttribute('style'));
109             }
110             if (node.getAttribute('class')) {
111                 clone.setAttribute('class', node.getAttribute('class'));
112             }
113             // We use childNodes here because we are interested in both type 1 and 3 child nodes.
114             var children = node.getDOMNode().childNodes, child;
115             child = children[0];
116             while (typeof child !== "undefined") {
117                 clone.append(child);
118                 child = children[0];
119             }
120             node.replace(clone);
121             node = this.editor.one('[data-iterate="true"]');
122         }
123     },
125     /**
126      * Change all tags with given type to a span with CSS class attribute.
127      * @method changeToCSS
128      * @param String tag Tag type to be changed to span
129      * @param String markerClass CSS class that corresponds to desired tag
130      */
131     changeToCSS: function(tag, markerClass) {
132         // Save the selection.
133         var selection = window.rangy.saveSelection();
135         // Remove display:none from rangy markers so browser doesn't delete them.
136         this.editor.all('.rangySelectionBoundary').setStyle('display', null);
138         // Replace tags with CSS classes.
139         this.editor.all(tag).addClass(markerClass);
140         this.replaceTags(this.editor.all('.' + markerClass), 'span');
142         // Restore selection and toggle class.
143         window.rangy.restoreSelection(selection);
144     },
146     /**
147      * Change spans with CSS classes in editor into elements with given tag.
148      * @method changeToCSS
149      * @param String markerClass CSS class that corresponds to desired tag
150      * @param String tag New tag type to be created
151      */
152     changeToTags: function(markerClass, tag) {
153         // Save the selection.
154         var selection = window.rangy.saveSelection();
156         // Remove display:none from rangy markers so browser doesn't delete them.
157         this.editor.all('.rangySelectionBoundary').setStyle('display', null);
159         // Replace spans with given tag.
160         this.replaceTags(this.editor.all('span[class="' + markerClass + '"]'), tag);
161         this.editor.all(tag + '[class="' + markerClass + '"]').removeAttribute('class');
162         this.editor.all('.' + markerClass).each(function(n) {
163             n.wrap('<' + tag + '/>');
164             n.removeClass(markerClass);
165         });
167         // Remove CSS classes.
168         this.editor.all('[class="' + markerClass + '"]').removeAttribute('class');
169         this.editor.all(tag).removeClass(markerClass);
171         // Restore selection.
172         window.rangy.restoreSelection(selection);
173     }
174 };
176 Y.Base.mix(Y.M.editor_atto.Editor, [EditorCommand]);