MDL-51605 Atto: Fix deprecation warnings from upgraded rangy
[moodle.git] / lib / editor / atto / yui / src / editor / js / styling.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 styling
19  */
21 /**
22  * Editor styling functions for the Atto editor.
23  *
24  * See {{#crossLink "M.editor_atto.Editor"}}{{/crossLink}} for details.
25  *
26  * @namespace M.editor_atto
27  * @class EditorStyling
28  */
30 function EditorStyling() {}
32 EditorStyling.ATTRS= {
33 };
35 EditorStyling.prototype = {
36     /**
37      * Disable CSS styling.
38      *
39      * @method disableCssStyling
40      */
41     disableCssStyling: function() {
42         try {
43             document.execCommand("styleWithCSS", 0, false);
44         } catch (e1) {
45             try {
46                 document.execCommand("useCSS", 0, true);
47             } catch (e2) {
48                 try {
49                     document.execCommand('styleWithCSS', false, false);
50                 } catch (e3) {
51                     // We did our best.
52                 }
53             }
54         }
55     },
57     /**
58      * Enable CSS styling.
59      *
60      * @method enableCssStyling
61      */
62     enableCssStyling: function() {
63         try {
64             document.execCommand("styleWithCSS", 0, true);
65         } catch (e1) {
66             try {
67                 document.execCommand("useCSS", 0, false);
68             } catch (e2) {
69                 try {
70                     document.execCommand('styleWithCSS', false, true);
71                 } catch (e3) {
72                     // We did our best.
73                 }
74             }
75         }
76     },
78     /**
79      * Change the formatting for the current selection.
80      *
81      * This will wrap the selection in span tags, adding the provided classes.
82      *
83      * If the selection covers multiple block elements, multiple spans will be inserted to preserve the original structure.
84      *
85      * @method toggleInlineSelectionClass
86      * @param {Array} toggleclasses - Class names to be toggled on or off.
87      */
88     toggleInlineSelectionClass: function(toggleclasses) {
89         var classname = toggleclasses.join(" ");
90         var originalSelection = this.getSelection();
91         var cssApplier = rangy.createClassApplier(classname, {normalize: true});
93         cssApplier.toggleSelection();
95         this.setSelection(originalSelection);
96     },
98     /**
99      * Change the formatting for the current selection.
100      *
101      * This will set inline styles on the current selection.
102      *
103      * @method toggleInlineSelectionClass
104      * @param {Array} styles - Style attributes to set on the nodes.
105      */
106     formatSelectionInlineStyle: function(styles) {
107         var classname = this.PLACEHOLDER_CLASS;
108         var originalSelection = this.getSelection();
109         var cssApplier = rangy.createClassApplier(classname, {normalize: true});
111         cssApplier.applyToSelection();
113         this.editor.all('.' + classname).each(function (node) {
114             node.removeClass(classname).setStyles(styles);
115         }, this);
117         this.setSelection(originalSelection);
118     },
120     /**
121      * Change the formatting for the current selection.
122      *
123      * Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
124      *
125      * @method formatSelectionBlock
126      * @param {String} [blocktag] Change the block level tag to this. Empty string, means do not change the tag.
127      * @param {Object} [attributes] The keys and values for attributes to be added/changed in the block tag.
128      * @return {Node|boolean} The Node that was formatted if a change was made, otherwise false.
129      */
130     formatSelectionBlock: function(blocktag, attributes) {
131         // First find the nearest ancestor of the selection that is a block level element.
132         var selectionparentnode = this.getSelectionParentNode(),
133             boundary,
134             cell,
135             nearestblock,
136             newcontent,
137             match,
138             replacement;
140         if (!selectionparentnode) {
141             // No selection, nothing to format.
142             return false;
143         }
145         boundary = this.editor;
147         selectionparentnode = Y.one(selectionparentnode);
149         // If there is a table cell in between the selectionparentnode and the boundary,
150         // move the boundary to the table cell.
151         // This is because we might have a table in a div, and we select some text in a cell,
152         // want to limit the change in style to the table cell, not the entire table (via the outer div).
153         cell = selectionparentnode.ancestor(function (node) {
154             var tagname = node.get('tagName');
155             if (tagname) {
156                 tagname = tagname.toLowerCase();
157             }
158             return (node === boundary) ||
159                    (tagname === 'td') ||
160                    (tagname === 'th');
161         }, true);
163         if (cell) {
164             // Limit the scope to the table cell.
165             boundary = cell;
166         }
168         nearestblock = selectionparentnode.ancestor(this.BLOCK_TAGS.join(', '), true);
169         if (nearestblock) {
170             // Check that the block is contained by the boundary.
171             match = nearestblock.ancestor(function (node) {
172                 return node === boundary;
173             }, false);
175             if (!match) {
176                 nearestblock = false;
177             }
178         }
180         // No valid block element - make one.
181         if (!nearestblock) {
182             // There is no block node in the content, wrap the content in a p and use that.
183             newcontent = Y.Node.create('<p></p>');
184             boundary.get('childNodes').each(function (child) {
185                 newcontent.append(child.remove());
186             });
187             boundary.append(newcontent);
188             nearestblock = newcontent;
189         }
191         // Guaranteed to have a valid block level element contained in the contenteditable region.
192         // Change the tag to the new block level tag.
193         if (blocktag && blocktag !== '') {
194             // Change the block level node for a new one.
195             replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
196             // Copy all attributes.
197             replacement.setAttrs(nearestblock.getAttrs());
198             // Copy all children.
199             nearestblock.get('childNodes').each(function (child) {
200                 child.remove();
201                 replacement.append(child);
202             });
204             nearestblock.replace(replacement);
205             nearestblock = replacement;
206         }
208         // Set the attributes on the block level tag.
209         if (attributes) {
210             nearestblock.setAttrs(attributes);
211         }
213         // Change the selection to the modified block. This makes sense when we might apply multiple styles
214         // to the block.
215         var selection = this.getSelectionFromNode(nearestblock);
216         this.setSelection(selection);
218         return nearestblock;
219     }
221 };
223 Y.Base.mix(Y.M.editor_atto.Editor, [EditorStyling]);