File: firststrong.js

package info (click to toggle)
aseba-plugin-blockly 20180211%2Bgit-2
  • links: PTS
  • area: non-free
  • in suites: buster
  • size: 64,472 kB
  • sloc: xml: 7,976; python: 2,314; sh: 261; lisp: 24; makefile: 10
file content (334 lines) | stat: -rw-r--r-- 11,153 bytes parent folder | download | duplicates (2)
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A plugin to enable the First Strong Bidi algorithm.  The First
 * Strong algorithm as a heuristic used to automatically set paragraph direction
 * depending on its content.
 *
 * In the documentation below, a 'paragraph' is the local element which we
 * evaluate as a whole for purposes of determining directionality. It may be a
 * block-level element (e.g. <div>) or a whole list (e.g. <ul>).
 *
 * This implementation is based on, but is not identical to, the original
 * First Strong algorithm defined in Unicode
 * @see http://www.unicode.org/reports/tr9/
 * The central difference from the original First Strong algorithm is that this
 * implementation decides the paragraph direction based on the first strong
 * character that is <em>typed</em> into the paragraph, regardless of its
 * location in the paragraph, as opposed to the original algorithm where it is
 * the first character in the paragraph <em>by location</em>, regardless of
 * whether other strong characters already appear in the paragraph, further its
 * start.
 *
 * <em>Please note</em> that this plugin does not perform the direction change
 * itself. Rather, it fires editor commands upon the key up event when a
 * direction change needs to be performed; {@code goog.editor.Command.DIR_RTL}
 * or {@code goog.editor.Command.DIR_RTL}.
 *
 */

goog.provide('goog.editor.plugins.FirstStrong');

goog.require('goog.dom.NodeType');
goog.require('goog.dom.TagIterator');
goog.require('goog.dom.TagName');
goog.require('goog.editor.Command');
goog.require('goog.editor.Field');
goog.require('goog.editor.Plugin');
goog.require('goog.editor.node');
goog.require('goog.editor.range');
goog.require('goog.i18n.bidi');
goog.require('goog.i18n.uChar');
goog.require('goog.iter');
goog.require('goog.userAgent');



/**
 * First Strong plugin.
 * @constructor
 * @extends {goog.editor.Plugin}
 * @final
 */
goog.editor.plugins.FirstStrong = function() {
  goog.editor.plugins.FirstStrong.base(this, 'constructor');

  /**
   * Indicates whether or not the cursor is in a paragraph we have not yet
   * finished evaluating for directionality. This is set to true whenever the
   * cursor is moved, and set to false after seeing a strong character in the
   * paragraph the cursor is currently in.
   *
   * @type {boolean}
   * @private
   */
  this.isNewBlock_ = true;

  /**
   * Indicates whether or not the current paragraph the cursor is in should be
   * set to Right-To-Left directionality.
   *
   * @type {boolean}
   * @private
   */
  this.switchToRtl_ = false;

  /**
   * Indicates whether or not the current paragraph the cursor is in should be
   * set to Left-To-Right directionality.
   *
   * @type {boolean}
   * @private
   */
  this.switchToLtr_ = false;
};
goog.inherits(goog.editor.plugins.FirstStrong, goog.editor.Plugin);


/** @override */
goog.editor.plugins.FirstStrong.prototype.getTrogClassId = function() {
  return 'FirstStrong';
};


/** @override */
goog.editor.plugins.FirstStrong.prototype.queryCommandValue = function(
    command) {
  return false;
};


/** @override */
goog.editor.plugins.FirstStrong.prototype.handleSelectionChange = function(
    e, node) {
  this.isNewBlock_ = true;
  return false;
};


/**
 * The name of the attribute which records the input text.
 *
 * @type {string}
 * @const
 */
goog.editor.plugins.FirstStrong.INPUT_ATTRIBUTE = 'fs-input';


/** @override */
goog.editor.plugins.FirstStrong.prototype.handleKeyPress = function(e) {
  if (goog.editor.Field.SELECTION_CHANGE_KEYCODES[e.keyCode]) {
    // Key triggered selection change event (e.g. on ENTER) is throttled and a
    // later LTR/RTL strong keypress may come before it. Need to capture it.
    this.isNewBlock_ = true;
    return false;  // A selection-changing key is not LTR/RTL strong.
  }
  if (!this.isNewBlock_) {
    return false;  // We've already determined this paragraph's direction.
  }
  // Ignore non-character key press events.
  if (e.ctrlKey || e.metaKey) {
    return false;
  }
  var newInput = goog.i18n.uChar.fromCharCode(e.charCode);

  // IME's may return 0 for the charCode, which is a legitimate, non-Strong
  // charCode, or they may return an illegal charCode (for which newInput will
  // be false).
  if (!newInput || !e.charCode) {
    var browserEvent = e.getBrowserEvent();
    if (browserEvent) {
      if (goog.userAgent.IE && browserEvent['getAttribute']) {
        newInput = browserEvent['getAttribute'](
            goog.editor.plugins.FirstStrong.INPUT_ATTRIBUTE);
      } else {
        newInput =
            browserEvent[goog.editor.plugins.FirstStrong.INPUT_ATTRIBUTE];
      }
    }
  }

  if (!newInput) {
    return false;  // Unrecognized key.
  }

  var isLtr = goog.i18n.bidi.isLtrChar(newInput);
  var isRtl = !isLtr && goog.i18n.bidi.isRtlChar(newInput);
  if (!isLtr && !isRtl) {
    return false;  // This character cannot change anything (it is not Strong).
  }
  // This character is Strongly LTR or Strongly RTL. We might switch direction
  // on it now, but in any case we do not need to check any more characters in
  // this paragraph after it.
  this.isNewBlock_ = false;

  // Are there no Strong characters already in the paragraph?
  if (this.isNeutralBlock_()) {
    this.switchToRtl_ = isRtl;
    this.switchToLtr_ = isLtr;
  }
  return false;
};


/**
 * Calls the flip directionality commands.  This is done here so things go into
 * the redo-undo stack at the expected order; fist enter the input, then flip
 * directionality.
 * @override
 */
goog.editor.plugins.FirstStrong.prototype.handleKeyUp = function(e) {
  if (this.switchToRtl_) {
    var field = this.getFieldObject();
    field.dispatchChange(true);
    field.execCommand(goog.editor.Command.DIR_RTL);
    this.switchToRtl_ = false;
  } else if (this.switchToLtr_) {
    var field = this.getFieldObject();
    field.dispatchChange(true);
    field.execCommand(goog.editor.Command.DIR_LTR);
    this.switchToLtr_ = false;
  }
  return false;
};


/**
 * @return {Element} The lowest Block element ancestor of the node where the
 *     next character will be placed.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.getBlockAncestor_ = function() {
  var start = this.getFieldObject().getRange().getStartNode();
  // Go up in the DOM until we reach a Block element.
  while (!goog.editor.plugins.FirstStrong.isBlock_(start)) {
    start = start.parentNode;
  }
  return /** @type {Element} */ (start);
};


/**
 * @return {boolean} Whether the paragraph where the next character will be
 *     entered contains only non-Strong characters.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.isNeutralBlock_ = function() {
  var root = this.getBlockAncestor_();
  // The exact node with the cursor location. Simply calling getStartNode() on
  // the range only returns the containing block node.
  var cursor =
      goog.editor.range.getDeepEndPoint(this.getFieldObject().getRange(), false)
          .node;

  // In FireFox the BR tag also represents a change in paragraph if not inside a
  // list. So we need special handling to only look at the sub-block between
  // BR elements.
  var blockFunction = (goog.userAgent.GECKO && !this.isList_(root)) ?
      goog.editor.plugins.FirstStrong.isGeckoBlock_ :
      goog.editor.plugins.FirstStrong.isBlock_;
  var paragraph = this.getTextAround_(root, cursor, blockFunction);
  // Not using {@code goog.i18n.bidi.isNeutralText} as it contains additional,
  // unwanted checks to the content.
  return !goog.i18n.bidi.hasAnyLtr(paragraph) &&
      !goog.i18n.bidi.hasAnyRtl(paragraph);
};


/**
 * Checks if an element is a list element ('UL' or 'OL').
 *
 * @param {Element} element The element to test.
 * @return {boolean} Whether the element is a list element ('UL' or 'OL').
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.isList_ = function(element) {
  if (!element) {
    return false;
  }
  var tagName = element.tagName;
  return tagName == goog.dom.TagName.UL || tagName == goog.dom.TagName.OL;
};


/**
 * Returns the text within the local paragraph around the cursor.
 * Notice that for GECKO a BR represents a pargraph change despite not being a
 * block element.
 *
 * @param {Element} root The first block element ancestor of the node the cursor
 *     is in.
 * @param {Node} cursorLocation Node where the cursor currently is, marking the
 *     paragraph whose text we will return.
 * @param {function(Node): boolean} isParagraphBoundary The function to
 *     determine if a node represents the start or end of the paragraph.
 * @return {string} the text in the paragraph around the cursor location.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.getTextAround_ = function(
    root, cursorLocation, isParagraphBoundary) {
  // The buffer where we're collecting the text.
  var buffer = [];
  // Have we reached the cursor yet, or are we still before it?
  var pastCursorLocation = false;

  if (root && cursorLocation) {
    goog.iter.some(new goog.dom.TagIterator(root), function(node) {
      if (node == cursorLocation) {
        pastCursorLocation = true;
      } else if (isParagraphBoundary(node)) {
        if (pastCursorLocation) {
          // This is the end of the paragraph containing the cursor. We're done.
          return true;
        } else {
          // All we collected so far does not count; it was in a previous
          // paragraph that did not contain the cursor.
          buffer = [];
        }
      }
      if (node.nodeType == goog.dom.NodeType.TEXT) {
        buffer.push(node.nodeValue);
      }
      return false;  // Keep going.
    });
  }
  return buffer.join('');
};


/**
 * @param {Node} node Node to check.
 * @return {boolean} Does the given node represent a Block element? Notice we do
 *     not consider list items as Block elements in the algorithm.
 * @private
 */
goog.editor.plugins.FirstStrong.isBlock_ = function(node) {
  return !!node && goog.editor.node.isBlockTag(node) &&
      /** @type {!Element} */ (node).tagName != goog.dom.TagName.LI;
};


/**
 * @param {Node} node Node to check.
 * @return {boolean} Does the given node represent a Block element from the
 *     point of view of FireFox? Notice we do not consider list items as Block
 *     elements in the algorithm.
 * @private
 */
goog.editor.plugins.FirstStrong.isGeckoBlock_ = function(node) {
  return !!node &&
      (/** @type {!Element} */ (node).tagName == goog.dom.TagName.BR ||
       goog.editor.plugins.FirstStrong.isBlock_(node));
};