mirrored from git://git.moodle.org/moodle.git
/
commands.js
177 lines (150 loc) · 6.67 KB
/
commands.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// 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();
// Save save selection and editor contents.
this.saveSelection();
this.updateOriginal();
}, 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]);
// Save save selection and editor contents.
this.saveSelection();
this.updateOriginal();
},
/**
* 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;
var 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]);