Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/editor/plugins/firststrong.js
2868 views
1
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview A plugin to enable the First Strong Bidi algorithm. The First
17
* Strong algorithm as a heuristic used to automatically set paragraph direction
18
* depending on its content.
19
*
20
* In the documentation below, a 'paragraph' is the local element which we
21
* evaluate as a whole for purposes of determining directionality. It may be a
22
* block-level element (e.g. <div>) or a whole list (e.g. <ul>).
23
*
24
* This implementation is based on, but is not identical to, the original
25
* First Strong algorithm defined in Unicode
26
* @see http://www.unicode.org/reports/tr9/
27
* The central difference from the original First Strong algorithm is that this
28
* implementation decides the paragraph direction based on the first strong
29
* character that is <em>typed</em> into the paragraph, regardless of its
30
* location in the paragraph, as opposed to the original algorithm where it is
31
* the first character in the paragraph <em>by location</em>, regardless of
32
* whether other strong characters already appear in the paragraph, further its
33
* start.
34
*
35
* <em>Please note</em> that this plugin does not perform the direction change
36
* itself. Rather, it fires editor commands upon the key up event when a
37
* direction change needs to be performed; {@code goog.editor.Command.DIR_RTL}
38
* or {@code goog.editor.Command.DIR_RTL}.
39
*
40
*/
41
42
goog.provide('goog.editor.plugins.FirstStrong');
43
44
goog.require('goog.dom.NodeType');
45
goog.require('goog.dom.TagIterator');
46
goog.require('goog.dom.TagName');
47
goog.require('goog.editor.Command');
48
goog.require('goog.editor.Field');
49
goog.require('goog.editor.Plugin');
50
goog.require('goog.editor.node');
51
goog.require('goog.editor.range');
52
goog.require('goog.i18n.bidi');
53
goog.require('goog.i18n.uChar');
54
goog.require('goog.iter');
55
goog.require('goog.userAgent');
56
57
58
59
/**
60
* First Strong plugin.
61
* @constructor
62
* @extends {goog.editor.Plugin}
63
* @final
64
*/
65
goog.editor.plugins.FirstStrong = function() {
66
goog.editor.plugins.FirstStrong.base(this, 'constructor');
67
68
/**
69
* Indicates whether or not the cursor is in a paragraph we have not yet
70
* finished evaluating for directionality. This is set to true whenever the
71
* cursor is moved, and set to false after seeing a strong character in the
72
* paragraph the cursor is currently in.
73
*
74
* @type {boolean}
75
* @private
76
*/
77
this.isNewBlock_ = true;
78
79
/**
80
* Indicates whether or not the current paragraph the cursor is in should be
81
* set to Right-To-Left directionality.
82
*
83
* @type {boolean}
84
* @private
85
*/
86
this.switchToRtl_ = false;
87
88
/**
89
* Indicates whether or not the current paragraph the cursor is in should be
90
* set to Left-To-Right directionality.
91
*
92
* @type {boolean}
93
* @private
94
*/
95
this.switchToLtr_ = false;
96
};
97
goog.inherits(goog.editor.plugins.FirstStrong, goog.editor.Plugin);
98
99
100
/** @override */
101
goog.editor.plugins.FirstStrong.prototype.getTrogClassId = function() {
102
return 'FirstStrong';
103
};
104
105
106
/** @override */
107
goog.editor.plugins.FirstStrong.prototype.queryCommandValue = function(
108
command) {
109
return false;
110
};
111
112
113
/** @override */
114
goog.editor.plugins.FirstStrong.prototype.handleSelectionChange = function(
115
e, node) {
116
this.isNewBlock_ = true;
117
return false;
118
};
119
120
121
/**
122
* The name of the attribute which records the input text.
123
*
124
* @type {string}
125
* @const
126
*/
127
goog.editor.plugins.FirstStrong.INPUT_ATTRIBUTE = 'fs-input';
128
129
130
/** @override */
131
goog.editor.plugins.FirstStrong.prototype.handleKeyPress = function(e) {
132
if (goog.editor.Field.SELECTION_CHANGE_KEYCODES[e.keyCode]) {
133
// Key triggered selection change event (e.g. on ENTER) is throttled and a
134
// later LTR/RTL strong keypress may come before it. Need to capture it.
135
this.isNewBlock_ = true;
136
return false; // A selection-changing key is not LTR/RTL strong.
137
}
138
if (!this.isNewBlock_) {
139
return false; // We've already determined this paragraph's direction.
140
}
141
// Ignore non-character key press events.
142
if (e.ctrlKey || e.metaKey) {
143
return false;
144
}
145
var newInput = goog.i18n.uChar.fromCharCode(e.charCode);
146
147
// IME's may return 0 for the charCode, which is a legitimate, non-Strong
148
// charCode, or they may return an illegal charCode (for which newInput will
149
// be false).
150
if (!newInput || !e.charCode) {
151
var browserEvent = e.getBrowserEvent();
152
if (browserEvent) {
153
if (goog.userAgent.IE && browserEvent['getAttribute']) {
154
newInput = browserEvent['getAttribute'](
155
goog.editor.plugins.FirstStrong.INPUT_ATTRIBUTE);
156
} else {
157
newInput =
158
browserEvent[goog.editor.plugins.FirstStrong.INPUT_ATTRIBUTE];
159
}
160
}
161
}
162
163
if (!newInput) {
164
return false; // Unrecognized key.
165
}
166
167
var isLtr = goog.i18n.bidi.isLtrChar(newInput);
168
var isRtl = !isLtr && goog.i18n.bidi.isRtlChar(newInput);
169
if (!isLtr && !isRtl) {
170
return false; // This character cannot change anything (it is not Strong).
171
}
172
// This character is Strongly LTR or Strongly RTL. We might switch direction
173
// on it now, but in any case we do not need to check any more characters in
174
// this paragraph after it.
175
this.isNewBlock_ = false;
176
177
// Are there no Strong characters already in the paragraph?
178
if (this.isNeutralBlock_()) {
179
this.switchToRtl_ = isRtl;
180
this.switchToLtr_ = isLtr;
181
}
182
return false;
183
};
184
185
186
/**
187
* Calls the flip directionality commands. This is done here so things go into
188
* the redo-undo stack at the expected order; fist enter the input, then flip
189
* directionality.
190
* @override
191
*/
192
goog.editor.plugins.FirstStrong.prototype.handleKeyUp = function(e) {
193
if (this.switchToRtl_) {
194
var field = this.getFieldObject();
195
field.dispatchChange(true);
196
field.execCommand(goog.editor.Command.DIR_RTL);
197
this.switchToRtl_ = false;
198
} else if (this.switchToLtr_) {
199
var field = this.getFieldObject();
200
field.dispatchChange(true);
201
field.execCommand(goog.editor.Command.DIR_LTR);
202
this.switchToLtr_ = false;
203
}
204
return false;
205
};
206
207
208
/**
209
* @return {Element} The lowest Block element ancestor of the node where the
210
* next character will be placed.
211
* @private
212
*/
213
goog.editor.plugins.FirstStrong.prototype.getBlockAncestor_ = function() {
214
var start = this.getFieldObject().getRange().getStartNode();
215
// Go up in the DOM until we reach a Block element.
216
while (!goog.editor.plugins.FirstStrong.isBlock_(start)) {
217
start = start.parentNode;
218
}
219
return /** @type {Element} */ (start);
220
};
221
222
223
/**
224
* @return {boolean} Whether the paragraph where the next character will be
225
* entered contains only non-Strong characters.
226
* @private
227
*/
228
goog.editor.plugins.FirstStrong.prototype.isNeutralBlock_ = function() {
229
var root = this.getBlockAncestor_();
230
// The exact node with the cursor location. Simply calling getStartNode() on
231
// the range only returns the containing block node.
232
var cursor =
233
goog.editor.range.getDeepEndPoint(this.getFieldObject().getRange(), false)
234
.node;
235
236
// In FireFox the BR tag also represents a change in paragraph if not inside a
237
// list. So we need special handling to only look at the sub-block between
238
// BR elements.
239
var blockFunction = (goog.userAgent.GECKO && !this.isList_(root)) ?
240
goog.editor.plugins.FirstStrong.isGeckoBlock_ :
241
goog.editor.plugins.FirstStrong.isBlock_;
242
var paragraph = this.getTextAround_(root, cursor, blockFunction);
243
// Not using {@code goog.i18n.bidi.isNeutralText} as it contains additional,
244
// unwanted checks to the content.
245
return !goog.i18n.bidi.hasAnyLtr(paragraph) &&
246
!goog.i18n.bidi.hasAnyRtl(paragraph);
247
};
248
249
250
/**
251
* Checks if an element is a list element ('UL' or 'OL').
252
*
253
* @param {Element} element The element to test.
254
* @return {boolean} Whether the element is a list element ('UL' or 'OL').
255
* @private
256
*/
257
goog.editor.plugins.FirstStrong.prototype.isList_ = function(element) {
258
if (!element) {
259
return false;
260
}
261
var tagName = element.tagName;
262
return tagName == goog.dom.TagName.UL || tagName == goog.dom.TagName.OL;
263
};
264
265
266
/**
267
* Returns the text within the local paragraph around the cursor.
268
* Notice that for GECKO a BR represents a pargraph change despite not being a
269
* block element.
270
*
271
* @param {Element} root The first block element ancestor of the node the cursor
272
* is in.
273
* @param {Node} cursorLocation Node where the cursor currently is, marking the
274
* paragraph whose text we will return.
275
* @param {function(Node): boolean} isParagraphBoundary The function to
276
* determine if a node represents the start or end of the paragraph.
277
* @return {string} the text in the paragraph around the cursor location.
278
* @private
279
*/
280
goog.editor.plugins.FirstStrong.prototype.getTextAround_ = function(
281
root, cursorLocation, isParagraphBoundary) {
282
// The buffer where we're collecting the text.
283
var buffer = [];
284
// Have we reached the cursor yet, or are we still before it?
285
var pastCursorLocation = false;
286
287
if (root && cursorLocation) {
288
goog.iter.some(new goog.dom.TagIterator(root), function(node) {
289
if (node == cursorLocation) {
290
pastCursorLocation = true;
291
} else if (isParagraphBoundary(node)) {
292
if (pastCursorLocation) {
293
// This is the end of the paragraph containing the cursor. We're done.
294
return true;
295
} else {
296
// All we collected so far does not count; it was in a previous
297
// paragraph that did not contain the cursor.
298
buffer = [];
299
}
300
}
301
if (node.nodeType == goog.dom.NodeType.TEXT) {
302
buffer.push(node.nodeValue);
303
}
304
return false; // Keep going.
305
});
306
}
307
return buffer.join('');
308
};
309
310
311
/**
312
* @param {Node} node Node to check.
313
* @return {boolean} Does the given node represent a Block element? Notice we do
314
* not consider list items as Block elements in the algorithm.
315
* @private
316
*/
317
goog.editor.plugins.FirstStrong.isBlock_ = function(node) {
318
return !!node && goog.editor.node.isBlockTag(node) &&
319
/** @type {!Element} */ (node).tagName != goog.dom.TagName.LI;
320
};
321
322
323
/**
324
* @param {Node} node Node to check.
325
* @return {boolean} Does the given node represent a Block element from the
326
* point of view of FireFox? Notice we do not consider list items as Block
327
* elements in the algorithm.
328
* @private
329
*/
330
goog.editor.plugins.FirstStrong.isGeckoBlock_ = function(node) {
331
return !!node &&
332
(/** @type {!Element} */ (node).tagName == goog.dom.TagName.BR ||
333
goog.editor.plugins.FirstStrong.isBlock_(node));
334
};
335
336