3
Copyright 2012 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('editor-bidi', function(Y) {
11
* Plugin for Editor to support BiDirectional (bidi) text operations.
12
* @class Plugin.EditorBidi
16
* @submodule editor-bidi
20
var EditorBidi = function() {
21
EditorBidi.superclass.constructor.apply(this, arguments);
22
}, HOST = 'host', DIR = 'dir', BODY = 'BODY', NODE_CHANGE = 'nodeChange',
23
B_C_CHANGE = 'bidiContextChange', FIRST_P = BODY + ' > p', STYLE = 'style';
25
Y.extend(EditorBidi, Y.Base, {
27
* Place holder for the last direction when checking for a switch
29
* @property lastDirection
33
* Tells us that an initial bidi check has already been performed
35
* @property firstEvent
40
* Method checks to see if the direction of the text has changed based on a nodeChange event.
42
* @method _checkForChange
44
_checkForChange: function() {
45
var host = this.get(HOST),
46
inst = host.getInstance(),
47
sel = new inst.EditorSelection(),
50
if (sel.isCollapsed) {
51
node = EditorBidi.blockParent(sel.focusNode);
53
direction = node.getStyle('direction');
54
if (direction !== this.lastDirection) {
55
host.fire(B_C_CHANGE, { changedTo: direction });
56
this.lastDirection = direction;
60
host.fire(B_C_CHANGE, { changedTo: 'select' });
61
this.lastDirection = null;
66
* Checked for a change after a specific nodeChange event has been fired.
68
* @method _afterNodeChange
70
_afterNodeChange: function(e) {
71
// If this is the first event ever, or an event that can result in a context change
72
if (this.firstEvent || EditorBidi.EVENTS[e.changedType]) {
73
this._checkForChange();
74
this.firstEvent = false;
79
* Checks for a direction change after a mouseup occurs.
81
* @method _afterMouseUp
83
_afterMouseUp: function(e) {
84
this._checkForChange();
85
this.firstEvent = false;
87
initializer: function() {
88
var host = this.get(HOST);
90
this.firstEvent = true;
92
host.after(NODE_CHANGE, Y.bind(this._afterNodeChange, this));
93
host.after('dom:mouseup', Y.bind(this._afterMouseUp, this));
97
* The events to check for a direction change on
102
'backspace-up': true,
104
'pagedown-down': true,
115
* More elements may be needed. BODY *must* be in the list to take care of the special case.
117
* blockParent could be changed to use inst.EditorSelection.BLOCKS
118
* instead, but that would make Y.Plugin.EditorBidi.blockParent
119
* unusable in non-RTE contexts (it being usable is a nice
124
//BLOCKS: Y.EditorSelection.BLOCKS+',LI,HR,' + BODY,
125
BLOCKS: Y.EditorSelection.BLOCKS,
127
* Template for creating a block element
129
* @property DIV_WRAPPER
131
DIV_WRAPPER: '<DIV></DIV>',
133
* Returns a block parent for a given element
135
* @method blockParent
137
blockParent: function(node, wrap) {
138
var parent = node, divNode, firstChild;
141
parent = Y.one(BODY);
144
if (!parent.test(EditorBidi.BLOCKS)) {
145
parent = parent.ancestor(EditorBidi.BLOCKS);
147
if (wrap && parent.test(BODY)) {
148
// This shouldn't happen if the RTE handles everything
149
// according to spec: we should get to a P before BODY. But
150
// we don't want to set the direction of BODY even if that
151
// happens, so we wrap everything in a DIV.
153
// The code is based on YUI3's Y.EditorSelection._wrapBlock function.
154
divNode = Y.Node.create(EditorBidi.DIV_WRAPPER);
155
parent.get('children').each(function(node, index) {
159
divNode.append(node);
162
firstChild.replace(divNode);
163
divNode.prepend(firstChild);
169
* The data key to store on the node.
171
* @property _NODE_SELECTED
173
_NODE_SELECTED: 'bidiSelected',
175
* Generates a list of all the block parents of the current NodeList
179
addParents: function(nodeArray) {
180
var i, parent, addParent;
182
for (i = 0; i < nodeArray.length; i += 1) {
183
nodeArray[i].setData(EditorBidi._NODE_SELECTED, true);
186
// This works automagically, since new parents added get processed
187
// later themselves. So if there's a node early in the process that
188
// we haven't discovered some of its siblings yet, thus resulting in
189
// its parent not added, the parent will be added later, since those
190
// siblings will be added to the array and then get processed.
191
for (i = 0; i < nodeArray.length; i += 1) {
192
parent = nodeArray[i].get('parentNode');
194
// Don't add the parent if the parent is the BODY element.
195
// We don't want to change the direction of BODY. Also don't
196
// do it if the parent is already in the list.
197
if (!parent.test(BODY) && !parent.getData(EditorBidi._NODE_SELECTED)) {
199
parent.get('children').some(function(sibling) {
200
if (!sibling.getData(EditorBidi._NODE_SELECTED)) {
202
return true; // stop more processing
206
nodeArray.push(parent);
207
parent.setData(EditorBidi._NODE_SELECTED, true);
212
for (i = 0; i < nodeArray.length; i += 1) {
213
nodeArray[i].clearData(EditorBidi._NODE_SELECTED);
238
* Regex for testing/removing text-align style from an element
240
* @property RE_TEXT_ALIGN
242
RE_TEXT_ALIGN: /text-align:\s*\w*\s*;/,
244
* Method to test a node's style attribute for text-align and removing it.
246
* @method removeTextAlign
248
removeTextAlign: function(n) {
250
if (n.getAttribute(STYLE).match(EditorBidi.RE_TEXT_ALIGN)) {
251
n.setAttribute(STYLE, n.getAttribute(STYLE).replace(EditorBidi.RE_TEXT_ALIGN, ''));
253
if (n.hasAttribute('align')) {
254
n.removeAttribute('align');
261
Y.namespace('Plugin');
263
Y.Plugin.EditorBidi = EditorBidi;
266
* bidi execCommand override for setting the text direction of a node.
267
* This property is added to the `Y.Plugin.ExecCommands.COMMANDS`
270
* @for Plugin.ExecCommand
273
//TODO -- This should not add this command unless the plugin is added to the instance..
274
Y.Plugin.ExecCommand.COMMANDS.bidi = function(cmd, direction) {
275
var inst = this.getInstance(),
276
sel = new inst.EditorSelection(),
277
ns = this.get(HOST).get(HOST).editorBidi,
279
selected, selectedBlocks, dir;
282
Y.error('bidi execCommand is not available without the EditorBiDi plugin.');
286
inst.EditorSelection.filterBlocks();
288
if (sel.isCollapsed) { // No selection
289
block = EditorBidi.blockParent(sel.anchorNode);
291
block = inst.one('body').one(inst.EditorSelection.BLOCKS);
293
//Remove text-align attribute if it exists
294
block = EditorBidi.removeTextAlign(block);
296
//If no direction is set, auto-detect the proper setting to make it "toggle"
297
dir = block.getAttribute(DIR);
298
if (!dir || dir == 'ltr') {
304
block.setAttribute(DIR, direction);
306
var b = block.all('br.yui-cursor');
307
if (b.size() === 1 && block.get('childNodes').size() == 1) {
312
} else { // some text is selected
313
selected = sel.getSelected();
315
selected.each(function(node) {
316
selectedBlocks.push(EditorBidi.blockParent(node));
318
selectedBlocks = inst.all(EditorBidi.addParents(selectedBlocks));
319
selectedBlocks.each(function(n) {
321
//Remove text-align attribute if it exists
322
n = EditorBidi.removeTextAlign(n);
324
dir = n.getAttribute(DIR);
325
if (!dir || dir == 'ltr') {
331
n.setAttribute(DIR, d);
333
returnValue = selectedBlocks;
335
ns._checkForChange();
342
}, '3.5.0' ,{skinnable:false, requires:['editor-base']});