1 YUI.add('moodle-atto_indent-button', function (Y, NAME) {
3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * @package atto_indent
20 * @copyright 2013 Damyon Wiese <damyon@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 * @module moodle-atto_indent-button
29 * Atto text editor indent plugin.
31 * @namespace M.atto_indent
33 * @extends M.editor_atto.EditorPlugin
36 Y.namespace('M.atto_indent').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
37 initializer: function() {
40 icon: 'e/decrease_indent',
42 buttonName: 'outdent',
43 callback: this.outdent
47 icon: 'e/increase_indent',
55 * Indents the currently selected content.
60 // Save the current selection - we want to restore this.
61 var selection = window.rangy.saveSelection(),
62 blockquotes = this.editor.all('blockquote'),
63 count = blockquotes.size();
65 // Remove display:none from rangy markers so browser doesn't delete them.
66 this.editor.all('.rangySelectionBoundary').setStyle('display', null);
68 // Mark all existing block quotes in case the user has actually added some.
69 blockquotes.addClass('pre-existing');
71 // Run the indent command.
72 document.execCommand('indent', false, null);
74 // Fix indent list item.
75 this.fixupListItemsAfterIndent();
77 // Get all blockquotes, both existing and new.
78 blockquotes = this.editor.all('blockquote');
80 if (blockquotes.size() !== count) {
81 // There are new block quotes, the indent exec has wrapped some content in block quotes in order
82 // to indent the selected content.
83 // We don't want blockquotes, we're going to convert them to divs.
84 this.replaceBlockquote(this.editor);
85 // Finally restore the seelction. The content has changed - sometimes this works - but not always :(
86 window.rangy.restoreSelection(selection);
87 } else if (blockquotes.size() > 0) {
88 // There were no new blockquotes, this happens if the user is indenting/outdenting a list.
89 blockquotes.removeClass('pre-existing');
92 // Remove the selection markers - a clean up really.
93 window.rangy.removeMarkers(selection);
95 // Mark the text as having been updated.
100 * Outdents the currently selected content.
104 outdent: function() {
105 // Save the selection we will want to restore it.
106 var selection = window.rangy.saveSelection(),
107 blockquotes = this.editor.all('blockquote'),
108 count = blockquotes.size();
110 // Mark existing blockquotes so that we don't convert them later.
111 blockquotes.addClass('pre-existing');
113 // Replace all div indents with blockquote indents so that we can rely on the browser functionality.
114 this.replaceEditorIndents(this.editor);
116 // Restore the users selection - otherwise the next outdent operation won't work!
117 window.rangy.restoreSelection(selection);
118 // And save it once more.
119 selection = window.rangy.saveSelection();
122 document.execCommand('outdent', false, null);
124 // Get all blockquotes so that we can work out what happened.
125 blockquotes = this.editor.all('blockquote');
127 if (blockquotes.size() !== count) {
128 // The number of blockquotes hasn't changed.
129 // This occurs when the user has outdented a list item.
130 this.replaceBlockquote(this.editor);
131 window.rangy.restoreSelection(selection);
132 } else if (blockquotes.size() > 0) {
133 // The number of blockquotes is the same and is more than 0 we just need to clean up the class
134 // we added to mark pre-existing blockquotes.
135 blockquotes.removeClass('pre-existing');
138 // Clean up any left over selection markers.
139 window.rangy.removeMarkers(selection);
141 // Mark the text as having been updated.
146 * Replaces all blockquotes within an editor with div indents.
147 * @method replaceBlockquote
148 * @param Editor editor
150 replaceBlockquote: function(editor) {
151 editor.all('blockquote').setAttribute('data-iterate', true);
152 var blockquote = editor.one('blockquote'),
153 margindir = (Y.one('body.dir-ltr')) ? 'marginLeft' : 'marginRight';
155 blockquote.removeAttribute('data-iterate');
156 if (blockquote.hasClass('pre-existing')) {
157 blockquote.removeClass('pre-existing');
159 var clone = Y.Node.create('<div></div>')
160 .setAttrs(blockquote.getAttrs())
161 .setStyle(margindir, '30px')
162 .addClass('editor-indent');
163 // We use childNodes here because we are interested in both type 1 and 3 child nodes.
164 var children = blockquote.getDOMNode().childNodes;
167 while (typeof child !== "undefined") {
171 blockquote.replace(clone);
173 blockquote = editor.one('blockquote[data-iterate]');
178 * Replaces all div indents with blockquotes.
179 * @method replaceEditorIndents
180 * @param Editor editor
182 replaceEditorIndents: function(editor) {
183 // We use the editor-indent class because it is preserved between saves.
184 var indent = editor.one('.editor-indent');
186 var clone = Y.Node.create('<blockquote></blockquote>')
189 .removeClass('editor-indent');
190 // We use childNodes here because we are interested in both type 1 and 3 child nodes.
191 var children = indent.getDOMNode().childNodes;
194 while (typeof child !== "undefined") {
198 indent.replace(clone);
199 indent = editor.one('.editor-indent');
203 * Fixup for list item after indent.
205 * @method fixupListItemsAfterIndent
207 fixupListItemsAfterIndent: function() {
208 var selection = window.rangy.getSelection(),
209 rootelement = this.editor.getDOMNode(),
210 listelement = selection.anchorNode.parentElement;
212 listelement = listelement.closest('ol, ul');
213 if (!(listelement && rootelement.contains(listelement))) {
217 // We will move the child list into previous list item of the parent.
218 var previous = listelement.previousElementSibling;
219 if (previous && previous.tagName === 'LI') {
220 previous.appendChild(listelement);
221 selection.collapseToEnd();
227 }, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});