MDL-49682 mod_forum: Correct arguments to format_message_text
[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 cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
92         cssApplier.toggleSelection();
93     },
95     /**
96      * Change the formatting for the current selection.
97      *
98      * This will set inline styles on the current selection.
99      *
100      * @method formatSelectionInlineStyle
101      * @param {Array} styles - Style attributes to set on the nodes.
102      */
103     formatSelectionInlineStyle: function(styles) {
104         var classname = this.PLACEHOLDER_CLASS;
105         var cssApplier = rangy.createCssClassApplier(classname, {normalize: true});
107         cssApplier.applyToSelection();
109         this.editor.all('.' + classname).each(function (node) {
110             node.removeClass(classname).setStyles(styles);
111         }, this);
113     },
115     /**
116      * Change the formatting for the current selection.
117      *
118      * Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
119      *
120      * @method formatSelectionBlock
121      * @param {String} [blocktag] Change the block level tag to this. Empty string, means do not change the tag.
122      * @param {Object} [attributes] The keys and values for attributes to be added/changed in the block tag.
123      * @return {Node|boolean} The Node that was formatted if a change was made, otherwise false.
124      */
125     formatSelectionBlock: function(blocktag, attributes) {
126         // First find the nearest ancestor of the selection that is a block level element.
127         var selectionparentnode = this.getSelectionParentNode(),
128             boundary,
129             cell,
130             nearestblock,
131             newcontent,
132             match,
133             replacement;
135         if (!selectionparentnode) {
136             // No selection, nothing to format.
137             return false;
138         }
140         boundary = this.editor;
142         selectionparentnode = Y.one(selectionparentnode);
144         // If there is a table cell in between the selectionparentnode and the boundary,
145         // move the boundary to the table cell.
146         // This is because we might have a table in a div, and we select some text in a cell,
147         // want to limit the change in style to the table cell, not the entire table (via the outer div).
148         cell = selectionparentnode.ancestor(function (node) {
149             var tagname = node.get('tagName');
150             if (tagname) {
151                 tagname = tagname.toLowerCase();
152             }
153             return (node === boundary) ||
154                    (tagname === 'td') ||
155                    (tagname === 'th');
156         }, true);
158         if (cell) {
159             // Limit the scope to the table cell.
160             boundary = cell;
161         }
163         nearestblock = selectionparentnode.ancestor(this.BLOCK_TAGS.join(', '), true);
164         if (nearestblock) {
165             // Check that the block is contained by the boundary.
166             match = nearestblock.ancestor(function (node) {
167                 return node === boundary;
168             }, false);
170             if (!match) {
171                 nearestblock = false;
172             }
173         }
175         // No valid block element - make one.
176         if (!nearestblock) {
177             // There is no block node in the content, wrap the content in a p and use that.
178             newcontent = Y.Node.create('<p></p>');
179             boundary.get('childNodes').each(function (child) {
180                 newcontent.append(child.remove());
181             });
182             boundary.append(newcontent);
183             nearestblock = newcontent;
184         }
186         // Guaranteed to have a valid block level element contained in the contenteditable region.
187         // Change the tag to the new block level tag.
188         if (blocktag && blocktag !== '') {
189             // Change the block level node for a new one.
190             replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
191             // Copy all attributes.
192             replacement.setAttrs(nearestblock.getAttrs());
193             // Copy all children.
194             nearestblock.get('childNodes').each(function (child) {
195                 child.remove();
196                 replacement.append(child);
197             });
199             nearestblock.replace(replacement);
200             nearestblock = replacement;
201         }
203         // Set the attributes on the block level tag.
204         if (attributes) {
205             nearestblock.setAttrs(attributes);
206         }
208         // Change the selection to the modified block. This makes sense when we might apply multiple styles
209         // to the block.
210         var selection = this.getSelectionFromNode(nearestblock);
211         this.setSelection(selection);
213         return nearestblock;
214     }
216 };
218 Y.Base.mix(Y.M.editor_atto.Editor, [EditorStyling]);