rangy-rails 1.3alpha.804.0 → 1.3.1.pre.dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/lib/rangy-rails/version.rb +1 -1
- data/vendor/assets/javascripts/rangy-core.js +3157 -3050
- data/vendor/assets/javascripts/rangy-cssclassapplier.js +908 -790
- data/vendor/assets/javascripts/rangy-highlighter.js +550 -429
- data/vendor/assets/javascripts/rangy-selectionsaverestore.js +199 -182
- data/vendor/assets/javascripts/rangy-serializer.js +255 -236
- data/vendor/assets/javascripts/rangy-textrange.js +1565 -1547
- metadata +3 -5
- data/vendor/assets/javascripts/rangy-position.js +0 -524
@@ -20,14 +20,14 @@
|
|
20
20
|
* http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html
|
21
21
|
*
|
22
22
|
* Part of Rangy, a cross-browser JavaScript range and selection library
|
23
|
-
*
|
23
|
+
* https://github.com/timdown/rangy
|
24
24
|
*
|
25
25
|
* Depends on Rangy core.
|
26
26
|
*
|
27
|
-
* Copyright
|
27
|
+
* Copyright 2015, Tim Down
|
28
28
|
* Licensed under the MIT license.
|
29
|
-
* Version: 1.
|
30
|
-
* Build date:
|
29
|
+
* Version: 1.3.1-dev
|
30
|
+
* Build date: 20 May 2015
|
31
31
|
*/
|
32
32
|
|
33
33
|
/**
|
@@ -63,68 +63,109 @@
|
|
63
63
|
* Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be
|
64
64
|
* feature-tested
|
65
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
|
-
var
|
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
|
-
|
66
|
+
(function(factory, root) {
|
67
|
+
if (typeof define == "function" && define.amd) {
|
68
|
+
// AMD. Register as an anonymous module with a dependency on Rangy.
|
69
|
+
define(["./rangy-core"], factory);
|
70
|
+
} else if (typeof module != "undefined" && typeof exports == "object") {
|
71
|
+
// Node/CommonJS style
|
72
|
+
module.exports = factory( require("rangy") );
|
73
|
+
} else {
|
74
|
+
// No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
|
75
|
+
factory(root.rangy);
|
76
|
+
}
|
77
|
+
})(function(rangy) {
|
78
|
+
rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) {
|
79
|
+
var UNDEF = "undefined";
|
80
|
+
var CHARACTER = "character", WORD = "word";
|
81
|
+
var dom = api.dom, util = api.util;
|
82
|
+
var extend = util.extend;
|
83
|
+
var createOptions = util.createOptions;
|
84
|
+
var getBody = dom.getBody;
|
85
|
+
|
86
|
+
|
87
|
+
var spacesRegex = /^[ \t\f\r\n]+$/;
|
88
|
+
var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/;
|
89
|
+
var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/;
|
90
|
+
var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/;
|
91
|
+
var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/;
|
92
|
+
|
93
|
+
var defaultLanguage = "en";
|
94
|
+
|
95
|
+
var isDirectionBackward = api.Selection.isDirectionBackward;
|
96
|
+
|
97
|
+
// Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit,
|
98
|
+
// but not other browsers). Also test whether trailing spaces before <br> elements are collapsed.
|
99
|
+
var trailingSpaceInBlockCollapses = false;
|
100
|
+
var trailingSpaceBeforeBrCollapses = false;
|
101
|
+
var trailingSpaceBeforeBlockCollapses = false;
|
102
|
+
var trailingSpaceBeforeLineBreakInPreLineCollapses = true;
|
103
|
+
|
104
|
+
(function() {
|
105
|
+
var el = dom.createTestElement(document, "<p>1 </p><p></p>", true);
|
106
|
+
var p = el.firstChild;
|
107
|
+
var sel = api.getSelection();
|
108
|
+
sel.collapse(p.lastChild, 2);
|
109
|
+
sel.setStart(p.firstChild, 0);
|
110
|
+
trailingSpaceInBlockCollapses = ("" + sel).length == 1;
|
111
|
+
|
112
|
+
el.innerHTML = "1 <br />";
|
113
|
+
sel.collapse(el, 2);
|
114
|
+
sel.setStart(el.firstChild, 0);
|
115
|
+
trailingSpaceBeforeBrCollapses = ("" + sel).length == 1;
|
116
|
+
|
117
|
+
el.innerHTML = "1 <p>1</p>";
|
118
|
+
sel.collapse(el, 2);
|
119
|
+
sel.setStart(el.firstChild, 0);
|
120
|
+
trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1;
|
121
|
+
|
122
|
+
dom.removeNode(el);
|
123
|
+
sel.removeAllRanges();
|
124
|
+
})();
|
125
|
+
|
126
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
127
|
+
|
128
|
+
// This function must create word and non-word tokens for the whole of the text supplied to it
|
129
|
+
function defaultTokenizer(chars, wordOptions) {
|
130
|
+
var word = chars.join(""), result, tokenRanges = [];
|
131
|
+
|
132
|
+
function createTokenRange(start, end, isWord) {
|
133
|
+
tokenRanges.push( { start: start, end: end, isWord: isWord } );
|
134
|
+
}
|
135
|
+
|
136
|
+
// Match words and mark characters
|
137
|
+
var lastWordEnd = 0, wordStart, wordEnd;
|
138
|
+
while ( (result = wordOptions.wordRegex.exec(word)) ) {
|
139
|
+
wordStart = result.index;
|
140
|
+
wordEnd = wordStart + result[0].length;
|
141
|
+
|
142
|
+
// Create token for non-word characters preceding this word
|
143
|
+
if (wordStart > lastWordEnd) {
|
144
|
+
createTokenRange(lastWordEnd, wordStart, false);
|
145
|
+
}
|
146
|
+
|
147
|
+
// Get trailing space characters for word
|
148
|
+
if (wordOptions.includeTrailingSpace) {
|
149
|
+
while ( nonLineBreakWhiteSpaceRegex.test(chars[wordEnd]) ) {
|
150
|
+
++wordEnd;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
createTokenRange(wordStart, wordEnd, true);
|
154
|
+
lastWordEnd = wordEnd;
|
155
|
+
}
|
156
|
+
|
157
|
+
// Create token for trailing non-word characters, if any exist
|
158
|
+
if (lastWordEnd < chars.length) {
|
159
|
+
createTokenRange(lastWordEnd, chars.length, false);
|
160
|
+
}
|
161
|
+
|
162
|
+
return tokenRanges;
|
163
|
+
}
|
164
|
+
|
165
|
+
function convertCharRangeToToken(chars, tokenRange) {
|
166
|
+
var tokenChars = chars.slice(tokenRange.start, tokenRange.end);
|
126
167
|
var token = {
|
127
|
-
isWord: isWord,
|
168
|
+
isWord: tokenRange.isWord,
|
128
169
|
chars: tokenChars,
|
129
170
|
toString: function() {
|
130
171
|
return tokenChars.join("");
|
@@ -133,1780 +174,1757 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) {
|
|
133
174
|
for (var i = 0, len = tokenChars.length; i < len; ++i) {
|
134
175
|
tokenChars[i].token = token;
|
135
176
|
}
|
136
|
-
|
177
|
+
return token;
|
137
178
|
}
|
138
179
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
// Create token for non-word characters preceding this word
|
146
|
-
if (wordStart > lastWordEnd) {
|
147
|
-
createTokenFromRange(lastWordEnd, wordStart, false);
|
148
|
-
}
|
149
|
-
|
150
|
-
// Get trailing space characters for word
|
151
|
-
if (wordOptions.includeTrailingSpace) {
|
152
|
-
while (nonLineBreakWhiteSpaceRegex.test(chars[wordEnd])) {
|
153
|
-
++wordEnd;
|
154
|
-
}
|
180
|
+
function tokenize(chars, wordOptions, tokenizer) {
|
181
|
+
var tokenRanges = tokenizer(chars, wordOptions);
|
182
|
+
var tokens = [];
|
183
|
+
for (var i = 0, tokenRange; tokenRange = tokenRanges[i++]; ) {
|
184
|
+
tokens.push( convertCharRangeToToken(chars, tokenRange) );
|
155
185
|
}
|
156
|
-
|
157
|
-
lastWordEnd = wordEnd;
|
186
|
+
return tokens;
|
158
187
|
}
|
159
188
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
189
|
+
var defaultCharacterOptions = {
|
190
|
+
includeBlockContentTrailingSpace: true,
|
191
|
+
includeSpaceBeforeBr: true,
|
192
|
+
includeSpaceBeforeBlock: true,
|
193
|
+
includePreLineTrailingSpace: true,
|
194
|
+
ignoreCharacters: ""
|
195
|
+
};
|
164
196
|
|
165
|
-
|
166
|
-
|
197
|
+
function normalizeIgnoredCharacters(ignoredCharacters) {
|
198
|
+
// Check if character is ignored
|
199
|
+
var ignoredChars = ignoredCharacters || "";
|
167
200
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
};
|
174
|
-
|
175
|
-
var defaultCaretCharacterOptions = {
|
176
|
-
includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses,
|
177
|
-
includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses,
|
178
|
-
includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses,
|
179
|
-
includePreLineTrailingSpace: true
|
180
|
-
};
|
181
|
-
|
182
|
-
var defaultWordOptions = {
|
183
|
-
"en": {
|
184
|
-
wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi,
|
185
|
-
includeTrailingSpace: false,
|
186
|
-
tokenizer: defaultTokenizer
|
187
|
-
}
|
188
|
-
};
|
189
|
-
|
190
|
-
function createOptions(optionsParam, defaults) {
|
191
|
-
if (!optionsParam) {
|
192
|
-
return defaults;
|
193
|
-
} else {
|
194
|
-
var options = {};
|
195
|
-
extend(options, defaults);
|
196
|
-
extend(options, optionsParam);
|
197
|
-
return options;
|
198
|
-
}
|
199
|
-
}
|
201
|
+
// Normalize ignored characters into a string consisting of characters in ascending order of character code
|
202
|
+
var ignoredCharsArray = (typeof ignoredChars == "string") ? ignoredChars.split("") : ignoredChars;
|
203
|
+
ignoredCharsArray.sort(function(char1, char2) {
|
204
|
+
return char1.charCodeAt(0) - char2.charCodeAt(0);
|
205
|
+
});
|
200
206
|
|
201
|
-
|
202
|
-
|
203
|
-
if (!options) {
|
204
|
-
return defaultWordOptions[defaultLanguage];
|
205
|
-
} else {
|
206
|
-
lang = options.language || defaultLanguage;
|
207
|
-
defaults = {};
|
208
|
-
extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]);
|
209
|
-
extend(defaults, options);
|
210
|
-
return defaults;
|
207
|
+
/// Convert back to a string and remove duplicates
|
208
|
+
return ignoredCharsArray.join("").replace(/(.)\1+/g, "$1");
|
211
209
|
}
|
212
|
-
}
|
213
|
-
|
214
|
-
function createCharacterOptions(options) {
|
215
|
-
return createOptions(options, defaultCharacterOptions);
|
216
|
-
}
|
217
210
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
withinRange: null,
|
225
|
-
wholeWordsOnly: false,
|
226
|
-
wrap: false,
|
227
|
-
direction: "forward",
|
228
|
-
wordOptions: null,
|
229
|
-
characterOptions: null
|
230
|
-
};
|
231
|
-
|
232
|
-
var defaultMoveOptions = {
|
233
|
-
wordOptions: null,
|
234
|
-
characterOptions: null
|
235
|
-
};
|
236
|
-
|
237
|
-
var defaultExpandOptions = {
|
238
|
-
wordOptions: null,
|
239
|
-
characterOptions: null,
|
240
|
-
trim: false,
|
241
|
-
trimStart: true,
|
242
|
-
trimEnd: true
|
243
|
-
};
|
244
|
-
|
245
|
-
var defaultWordIteratorOptions = {
|
246
|
-
wordOptions: null,
|
247
|
-
characterOptions: null,
|
248
|
-
direction: "forward"
|
249
|
-
};
|
250
|
-
|
251
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
252
|
-
|
253
|
-
/* DOM utility functions */
|
254
|
-
var getComputedStyleProperty = dom.getComputedStyleProperty;
|
255
|
-
|
256
|
-
// Create cachable versions of DOM functions
|
257
|
-
|
258
|
-
// Test for old IE's incorrect display properties
|
259
|
-
var tableCssDisplayBlock;
|
260
|
-
(function() {
|
261
|
-
var table = document.createElement("table");
|
262
|
-
var body = getBody(document);
|
263
|
-
body.appendChild(table);
|
264
|
-
tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block");
|
265
|
-
body.removeChild(table);
|
266
|
-
})();
|
267
|
-
|
268
|
-
api.features.tableCssDisplayBlock = tableCssDisplayBlock;
|
269
|
-
|
270
|
-
var defaultDisplayValueForTag = {
|
271
|
-
table: "table",
|
272
|
-
caption: "table-caption",
|
273
|
-
colgroup: "table-column-group",
|
274
|
-
col: "table-column",
|
275
|
-
thead: "table-header-group",
|
276
|
-
tbody: "table-row-group",
|
277
|
-
tfoot: "table-footer-group",
|
278
|
-
tr: "table-row",
|
279
|
-
td: "table-cell",
|
280
|
-
th: "table-cell"
|
281
|
-
};
|
282
|
-
|
283
|
-
// Corrects IE's "block" value for table-related elements
|
284
|
-
function getComputedDisplay(el, win) {
|
285
|
-
var display = getComputedStyleProperty(el, "display", win);
|
286
|
-
var tagName = el.tagName.toLowerCase();
|
287
|
-
return (display == "block"
|
288
|
-
&& tableCssDisplayBlock
|
289
|
-
&& defaultDisplayValueForTag.hasOwnProperty(tagName))
|
290
|
-
? defaultDisplayValueForTag[tagName] : display;
|
291
|
-
}
|
211
|
+
var defaultCaretCharacterOptions = {
|
212
|
+
includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses,
|
213
|
+
includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses,
|
214
|
+
includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses,
|
215
|
+
includePreLineTrailingSpace: true
|
216
|
+
};
|
292
217
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
218
|
+
var defaultWordOptions = {
|
219
|
+
"en": {
|
220
|
+
wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi,
|
221
|
+
includeTrailingSpace: false,
|
222
|
+
tokenizer: defaultTokenizer
|
298
223
|
}
|
299
|
-
}
|
224
|
+
};
|
300
225
|
|
301
|
-
|
302
|
-
|
226
|
+
var defaultFindOptions = {
|
227
|
+
caseSensitive: false,
|
228
|
+
withinRange: null,
|
229
|
+
wholeWordsOnly: false,
|
230
|
+
wrap: false,
|
231
|
+
direction: "forward",
|
232
|
+
wordOptions: null,
|
233
|
+
characterOptions: null
|
234
|
+
};
|
303
235
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
&& getComputedStyleProperty(el, "visibility") == "hidden";
|
309
|
-
}
|
236
|
+
var defaultMoveOptions = {
|
237
|
+
wordOptions: null,
|
238
|
+
characterOptions: null
|
239
|
+
};
|
310
240
|
|
311
|
-
|
241
|
+
var defaultExpandOptions = {
|
242
|
+
wordOptions: null,
|
243
|
+
characterOptions: null,
|
244
|
+
trim: false,
|
245
|
+
trimStart: true,
|
246
|
+
trimEnd: true
|
247
|
+
};
|
312
248
|
|
249
|
+
var defaultWordIteratorOptions = {
|
250
|
+
wordOptions: null,
|
251
|
+
characterOptions: null,
|
252
|
+
direction: "forward"
|
253
|
+
};
|
313
254
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
255
|
+
function createWordOptions(options) {
|
256
|
+
var lang, defaults;
|
257
|
+
if (!options) {
|
258
|
+
return defaultWordOptions[defaultLanguage];
|
259
|
+
} else {
|
260
|
+
lang = options.language || defaultLanguage;
|
261
|
+
defaults = {};
|
262
|
+
extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]);
|
263
|
+
extend(defaults, options);
|
264
|
+
return defaults;
|
265
|
+
}
|
266
|
+
}
|
322
267
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
268
|
+
function createNestedOptions(optionsParam, defaults) {
|
269
|
+
var options = createOptions(optionsParam, defaults);
|
270
|
+
if (defaults.hasOwnProperty("wordOptions")) {
|
271
|
+
options.wordOptions = createWordOptions(options.wordOptions);
|
272
|
+
}
|
273
|
+
if (defaults.hasOwnProperty("characterOptions")) {
|
274
|
+
options.characterOptions = createOptions(options.characterOptions, defaultCharacterOptions);
|
275
|
+
}
|
276
|
+
return options;
|
277
|
+
}
|
327
278
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
279
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
280
|
+
|
281
|
+
/* DOM utility functions */
|
282
|
+
var getComputedStyleProperty = dom.getComputedStyleProperty;
|
283
|
+
|
284
|
+
// Create cachable versions of DOM functions
|
285
|
+
|
286
|
+
// Test for old IE's incorrect display properties
|
287
|
+
var tableCssDisplayBlock;
|
288
|
+
(function() {
|
289
|
+
var table = document.createElement("table");
|
290
|
+
var body = getBody(document);
|
291
|
+
body.appendChild(table);
|
292
|
+
tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block");
|
293
|
+
body.removeChild(table);
|
294
|
+
})();
|
295
|
+
|
296
|
+
var defaultDisplayValueForTag = {
|
297
|
+
table: "table",
|
298
|
+
caption: "table-caption",
|
299
|
+
colgroup: "table-column-group",
|
300
|
+
col: "table-column",
|
301
|
+
thead: "table-header-group",
|
302
|
+
tbody: "table-row-group",
|
303
|
+
tfoot: "table-footer-group",
|
304
|
+
tr: "table-row",
|
305
|
+
td: "table-cell",
|
306
|
+
th: "table-cell"
|
307
|
+
};
|
332
308
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
309
|
+
// Corrects IE's "block" value for table-related elements
|
310
|
+
function getComputedDisplay(el, win) {
|
311
|
+
var display = getComputedStyleProperty(el, "display", win);
|
312
|
+
var tagName = el.tagName.toLowerCase();
|
313
|
+
return (display == "block" &&
|
314
|
+
tableCssDisplayBlock &&
|
315
|
+
defaultDisplayValueForTag.hasOwnProperty(tagName)) ?
|
316
|
+
defaultDisplayValueForTag[tagName] : display;
|
338
317
|
}
|
339
|
-
return ancestors;
|
340
|
-
}
|
341
318
|
|
342
|
-
|
343
|
-
|
344
|
-
|
319
|
+
function isHidden(node) {
|
320
|
+
var ancestors = getAncestorsAndSelf(node);
|
321
|
+
for (var i = 0, len = ancestors.length; i < len; ++i) {
|
322
|
+
if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") {
|
323
|
+
return true;
|
324
|
+
}
|
325
|
+
}
|
345
326
|
|
346
|
-
|
347
|
-
while (node && !node.nextSibling) {
|
348
|
-
node = node.parentNode;
|
349
|
-
}
|
350
|
-
if (!node) {
|
351
|
-
return null;
|
327
|
+
return false;
|
352
328
|
}
|
353
|
-
return node.nextSibling;
|
354
|
-
}
|
355
329
|
|
356
|
-
|
357
|
-
|
358
|
-
return
|
330
|
+
function isVisibilityHiddenTextNode(textNode) {
|
331
|
+
var el;
|
332
|
+
return textNode.nodeType == 3 &&
|
333
|
+
(el = textNode.parentNode) &&
|
334
|
+
getComputedStyleProperty(el, "visibility") == "hidden";
|
359
335
|
}
|
360
|
-
return nextNodeDescendants(node);
|
361
|
-
}
|
362
336
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
return node
|
337
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
338
|
+
|
339
|
+
|
340
|
+
// "A block node is either an Element whose "display" property does not have
|
341
|
+
// resolved value "inline" or "inline-block" or "inline-table" or "none", or a
|
342
|
+
// Document, or a DocumentFragment."
|
343
|
+
function isBlockNode(node) {
|
344
|
+
return node &&
|
345
|
+
((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node))) ||
|
346
|
+
node.nodeType == 9 || node.nodeType == 11);
|
371
347
|
}
|
372
|
-
|
373
|
-
|
374
|
-
|
348
|
+
|
349
|
+
function getLastDescendantOrSelf(node) {
|
350
|
+
var lastChild = node.lastChild;
|
351
|
+
return lastChild ? getLastDescendantOrSelf(lastChild) : node;
|
375
352
|
}
|
376
|
-
return null;
|
377
|
-
}
|
378
353
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
|
383
|
-
// parent is an Element whose resolved value for "white-space" is "normal" or
|
384
|
-
// "nowrap"; or a Text node whose data consists only of one or more tabs
|
385
|
-
// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
|
386
|
-
// parent is an Element whose resolved value for "white-space" is "pre-line"."
|
387
|
-
function isWhitespaceNode(node) {
|
388
|
-
if (!node || node.nodeType != 3) {
|
389
|
-
return false;
|
354
|
+
function containsPositions(node) {
|
355
|
+
return dom.isCharacterDataNode(node) ||
|
356
|
+
!/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName);
|
390
357
|
}
|
391
|
-
|
392
|
-
|
393
|
-
|
358
|
+
|
359
|
+
function getAncestors(node) {
|
360
|
+
var ancestors = [];
|
361
|
+
while (node.parentNode) {
|
362
|
+
ancestors.unshift(node.parentNode);
|
363
|
+
node = node.parentNode;
|
364
|
+
}
|
365
|
+
return ancestors;
|
394
366
|
}
|
395
|
-
|
396
|
-
|
397
|
-
return
|
367
|
+
|
368
|
+
function getAncestorsAndSelf(node) {
|
369
|
+
return getAncestors(node).concat([node]);
|
398
370
|
}
|
399
|
-
var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
|
400
371
|
|
401
|
-
|
402
|
-
|
403
|
-
|
372
|
+
function nextNodeDescendants(node) {
|
373
|
+
while (node && !node.nextSibling) {
|
374
|
+
node = node.parentNode;
|
375
|
+
}
|
376
|
+
if (!node) {
|
377
|
+
return null;
|
378
|
+
}
|
379
|
+
return node.nextSibling;
|
380
|
+
}
|
404
381
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
if (node.data === "") {
|
411
|
-
return true;
|
382
|
+
function nextNode(node, excludeChildren) {
|
383
|
+
if (!excludeChildren && node.hasChildNodes()) {
|
384
|
+
return node.firstChild;
|
385
|
+
}
|
386
|
+
return nextNodeDescendants(node);
|
412
387
|
}
|
413
388
|
|
414
|
-
|
415
|
-
|
416
|
-
|
389
|
+
function previousNode(node) {
|
390
|
+
var previous = node.previousSibling;
|
391
|
+
if (previous) {
|
392
|
+
node = previous;
|
393
|
+
while (node.hasChildNodes()) {
|
394
|
+
node = node.lastChild;
|
395
|
+
}
|
396
|
+
return node;
|
397
|
+
}
|
398
|
+
var parent = node.parentNode;
|
399
|
+
if (parent && parent.nodeType == 1) {
|
400
|
+
return parent;
|
401
|
+
}
|
402
|
+
return null;
|
417
403
|
}
|
418
404
|
|
419
|
-
//
|
420
|
-
|
405
|
+
// Adpated from Aryeh's code.
|
406
|
+
// "A whitespace node is either a Text node whose data is the empty string; or
|
407
|
+
// a Text node whose data consists only of one or more tabs (0x0009), line
|
408
|
+
// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
|
409
|
+
// parent is an Element whose resolved value for "white-space" is "normal" or
|
410
|
+
// "nowrap"; or a Text node whose data consists only of one or more tabs
|
411
|
+
// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
|
412
|
+
// parent is an Element whose resolved value for "white-space" is "pre-line"."
|
413
|
+
function isWhitespaceNode(node) {
|
414
|
+
if (!node || node.nodeType != 3) {
|
415
|
+
return false;
|
416
|
+
}
|
417
|
+
var text = node.data;
|
418
|
+
if (text === "") {
|
419
|
+
return true;
|
420
|
+
}
|
421
|
+
var parent = node.parentNode;
|
422
|
+
if (!parent || parent.nodeType != 1) {
|
423
|
+
return false;
|
424
|
+
}
|
425
|
+
var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
|
421
426
|
|
422
|
-
|
423
|
-
|
424
|
-
return true;
|
427
|
+
return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace)) ||
|
428
|
+
(/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line");
|
425
429
|
}
|
426
430
|
|
427
|
-
//
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
+
// Adpated from Aryeh's code.
|
432
|
+
// "node is a collapsed whitespace node if the following algorithm returns
|
433
|
+
// true:"
|
434
|
+
function isCollapsedWhitespaceNode(node) {
|
435
|
+
// "If node's data is the empty string, return true."
|
436
|
+
if (node.data === "") {
|
437
|
+
return true;
|
438
|
+
}
|
431
439
|
|
432
|
-
|
433
|
-
|
440
|
+
// "If node is not a whitespace node, return false."
|
441
|
+
if (!isWhitespaceNode(node)) {
|
442
|
+
return false;
|
443
|
+
}
|
434
444
|
|
435
|
-
|
436
|
-
|
437
|
-
return type == 7 /* PROCESSING_INSTRUCTION */
|
438
|
-
|| type == 8 /* COMMENT */
|
439
|
-
|| isHidden(node)
|
440
|
-
|| /^(script|style)$/i.test(node.nodeName)
|
441
|
-
|| isVisibilityHiddenTextNode(node)
|
442
|
-
|| isCollapsedWhitespaceNode(node);
|
443
|
-
}
|
445
|
+
// "Let ancestor be node's parent."
|
446
|
+
var ancestor = node.parentNode;
|
444
447
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|| (type == 1 && getComputedDisplay(node, win) == "none");
|
450
|
-
}
|
448
|
+
// "If ancestor is null, return true."
|
449
|
+
if (!ancestor) {
|
450
|
+
return true;
|
451
|
+
}
|
451
452
|
|
452
|
-
|
453
|
+
// "If the "display" property of some ancestor of node has resolved value "none", return true."
|
454
|
+
if (isHidden(node)) {
|
455
|
+
return true;
|
456
|
+
}
|
453
457
|
|
454
|
-
|
458
|
+
return false;
|
459
|
+
}
|
455
460
|
|
456
|
-
|
457
|
-
|
458
|
-
|
461
|
+
function isCollapsedNode(node) {
|
462
|
+
var type = node.nodeType;
|
463
|
+
return type == 7 /* PROCESSING_INSTRUCTION */ ||
|
464
|
+
type == 8 /* COMMENT */ ||
|
465
|
+
isHidden(node) ||
|
466
|
+
/^(script|style)$/i.test(node.nodeName) ||
|
467
|
+
isVisibilityHiddenTextNode(node) ||
|
468
|
+
isCollapsedWhitespaceNode(node);
|
469
|
+
}
|
459
470
|
|
460
|
-
|
461
|
-
|
462
|
-
return
|
463
|
-
|
471
|
+
function isIgnoredNode(node, win) {
|
472
|
+
var type = node.nodeType;
|
473
|
+
return type == 7 /* PROCESSING_INSTRUCTION */ ||
|
474
|
+
type == 8 /* COMMENT */ ||
|
475
|
+
(type == 1 && getComputedDisplay(node, win) == "none");
|
476
|
+
}
|
477
|
+
|
478
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
464
479
|
|
465
|
-
|
466
|
-
|
480
|
+
// Possibly overengineered caching system to prevent repeated DOM calls slowing everything down
|
481
|
+
|
482
|
+
function Cache() {
|
483
|
+
this.store = {};
|
467
484
|
}
|
468
|
-
};
|
469
485
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
return cache[methodName];
|
478
|
-
} else {
|
479
|
-
uncachedCount++;
|
480
|
-
var value = func.call(this, objProperty ? this[objProperty] : this, args);
|
481
|
-
cache[methodName] = value;
|
482
|
-
return value;
|
486
|
+
Cache.prototype = {
|
487
|
+
get: function(key) {
|
488
|
+
return this.store.hasOwnProperty(key) ? this.store[key] : null;
|
489
|
+
},
|
490
|
+
|
491
|
+
set: function(key, value) {
|
492
|
+
return this.store[key] = value;
|
483
493
|
}
|
484
494
|
};
|
485
|
-
}
|
486
|
-
|
487
|
-
/*
|
488
|
-
api.report = function() {
|
489
|
-
console.log("Cached: " + cachedCount + ", uncached: " + uncachedCount);
|
490
|
-
};
|
491
|
-
*/
|
492
|
-
|
493
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
494
|
-
|
495
|
-
function NodeWrapper(node, session) {
|
496
|
-
this.node = node;
|
497
|
-
this.session = session;
|
498
|
-
this.cache = new Cache();
|
499
|
-
this.positions = new Cache();
|
500
|
-
}
|
501
495
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
return
|
506
|
-
|
496
|
+
var cachedCount = 0, uncachedCount = 0;
|
497
|
+
|
498
|
+
function createCachingGetter(methodName, func, objProperty) {
|
499
|
+
return function(args) {
|
500
|
+
var cache = this.cache;
|
501
|
+
if (cache.hasOwnProperty(methodName)) {
|
502
|
+
cachedCount++;
|
503
|
+
return cache[methodName];
|
504
|
+
} else {
|
505
|
+
uncachedCount++;
|
506
|
+
var value = func.call(this, objProperty ? this[objProperty] : this, args);
|
507
|
+
cache[methodName] = value;
|
508
|
+
return value;
|
509
|
+
}
|
510
|
+
};
|
511
|
+
}
|
512
|
+
|
513
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
507
514
|
|
508
|
-
|
509
|
-
|
515
|
+
function NodeWrapper(node, session) {
|
516
|
+
this.node = node;
|
517
|
+
this.session = session;
|
518
|
+
this.cache = new Cache();
|
519
|
+
this.positions = new Cache();
|
510
520
|
}
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK",
|
521
|
-
TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR",
|
522
|
-
PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK",
|
523
|
-
TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR";
|
524
|
-
|
525
|
-
extend(nodeProto, {
|
526
|
-
isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"),
|
527
|
-
getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"),
|
528
|
-
getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"),
|
529
|
-
containsPositions: createCachingGetter("containsPositions", containsPositions, "node"),
|
530
|
-
isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"),
|
531
|
-
isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"),
|
532
|
-
getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"),
|
533
|
-
isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"),
|
534
|
-
isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"),
|
535
|
-
next: createCachingGetter("nextPos", nextNode, "node"),
|
536
|
-
previous: createCachingGetter("previous", previousNode, "node"),
|
537
|
-
|
538
|
-
getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) {
|
539
|
-
var spaceRegex = null, collapseSpaces = false;
|
540
|
-
var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace");
|
541
|
-
var preLine = (cssWhitespace == "pre-line");
|
542
|
-
if (preLine) {
|
543
|
-
spaceRegex = spacesMinusLineBreaksRegex;
|
544
|
-
collapseSpaces = true;
|
545
|
-
} else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") {
|
546
|
-
spaceRegex = spacesRegex;
|
547
|
-
collapseSpaces = true;
|
521
|
+
|
522
|
+
var nodeProto = {
|
523
|
+
getPosition: function(offset) {
|
524
|
+
var positions = this.positions;
|
525
|
+
return positions.get(offset) || positions.set(offset, new Position(this, offset));
|
526
|
+
},
|
527
|
+
|
528
|
+
toString: function() {
|
529
|
+
return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]";
|
548
530
|
}
|
531
|
+
};
|
549
532
|
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
533
|
+
NodeWrapper.prototype = nodeProto;
|
534
|
+
|
535
|
+
var EMPTY = "EMPTY",
|
536
|
+
NON_SPACE = "NON_SPACE",
|
537
|
+
UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE",
|
538
|
+
COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE",
|
539
|
+
TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK",
|
540
|
+
TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK",
|
541
|
+
TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR",
|
542
|
+
PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK",
|
543
|
+
TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR",
|
544
|
+
INCLUDED_TRAILING_LINE_BREAK_AFTER_BR = "INCLUDED_TRAILING_LINE_BREAK_AFTER_BR";
|
545
|
+
|
546
|
+
extend(nodeProto, {
|
547
|
+
isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"),
|
548
|
+
getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"),
|
549
|
+
getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"),
|
550
|
+
containsPositions: createCachingGetter("containsPositions", containsPositions, "node"),
|
551
|
+
isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"),
|
552
|
+
isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"),
|
553
|
+
getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"),
|
554
|
+
isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"),
|
555
|
+
isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"),
|
556
|
+
next: createCachingGetter("nextPos", nextNode, "node"),
|
557
|
+
previous: createCachingGetter("previous", previousNode, "node"),
|
558
|
+
|
559
|
+
getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) {
|
560
|
+
var spaceRegex = null, collapseSpaces = false;
|
561
|
+
var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace");
|
562
|
+
var preLine = (cssWhitespace == "pre-line");
|
563
|
+
if (preLine) {
|
564
|
+
spaceRegex = spacesMinusLineBreaksRegex;
|
565
|
+
collapseSpaces = true;
|
566
|
+
} else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") {
|
567
|
+
spaceRegex = spacesRegex;
|
568
|
+
collapseSpaces = true;
|
569
|
+
}
|
570
|
+
|
571
|
+
return {
|
572
|
+
node: textNode,
|
573
|
+
text: textNode.data,
|
574
|
+
spaceRegex: spaceRegex,
|
575
|
+
collapseSpaces: collapseSpaces,
|
576
|
+
preLine: preLine
|
577
|
+
};
|
578
|
+
}, "node"),
|
558
579
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
580
|
+
hasInnerText: createCachingGetter("hasInnerText", function(el, backward) {
|
581
|
+
var session = this.session;
|
582
|
+
var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1);
|
583
|
+
var firstPosInEl = session.getPosition(el, 0);
|
563
584
|
|
564
|
-
|
565
|
-
|
585
|
+
var pos = backward ? posAfterEl : firstPosInEl;
|
586
|
+
var endPos = backward ? firstPosInEl : posAfterEl;
|
566
587
|
|
567
|
-
|
568
|
-
|
588
|
+
/*
|
589
|
+
<body><p>X </p><p>Y</p></body>
|
569
590
|
|
570
|
-
|
591
|
+
Positions:
|
571
592
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
593
|
+
body:0:""
|
594
|
+
p:0:""
|
595
|
+
text:0:""
|
596
|
+
text:1:"X"
|
597
|
+
text:2:TRAILING_SPACE_IN_BLOCK
|
598
|
+
text:3:COLLAPSED_SPACE
|
599
|
+
p:1:""
|
600
|
+
body:1:"\n"
|
601
|
+
p:0:""
|
602
|
+
text:0:""
|
603
|
+
text:1:"Y"
|
583
604
|
|
584
|
-
|
605
|
+
A character is a TRAILING_SPACE_IN_BLOCK iff:
|
585
606
|
|
586
|
-
|
607
|
+
- There is no uncollapsed character after it within the visible containing block element
|
587
608
|
|
588
|
-
|
609
|
+
A character is a TRAILING_SPACE_BEFORE_BR iff:
|
589
610
|
|
590
|
-
|
611
|
+
- There is no uncollapsed character after it preceding a <br> element
|
591
612
|
|
592
|
-
|
613
|
+
An element has inner text iff
|
593
614
|
|
594
|
-
|
595
|
-
|
615
|
+
- It is not hidden
|
616
|
+
- It contains an uncollapsed character
|
596
617
|
|
597
|
-
|
598
|
-
|
618
|
+
All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render.
|
619
|
+
*/
|
599
620
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
621
|
+
while (pos !== endPos) {
|
622
|
+
pos.prepopulateChar();
|
623
|
+
if (pos.isDefinitelyNonEmpty()) {
|
624
|
+
return true;
|
625
|
+
}
|
626
|
+
pos = backward ? pos.previousVisible() : pos.nextVisible();
|
604
627
|
}
|
605
|
-
pos = backward ? pos.previousVisible() : pos.nextVisible();
|
606
|
-
}
|
607
628
|
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
629
|
+
return false;
|
630
|
+
}, "node"),
|
631
|
+
|
632
|
+
isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) {
|
633
|
+
// Ensure that a block element containing a <br> is considered to have inner text
|
634
|
+
var brs = el.getElementsByTagName("br");
|
635
|
+
for (var i = 0, len = brs.length; i < len; ++i) {
|
636
|
+
if (!isCollapsedNode(brs[i])) {
|
637
|
+
return true;
|
638
|
+
}
|
617
639
|
}
|
618
|
-
|
619
|
-
|
620
|
-
}, "node"),
|
640
|
+
return this.hasInnerText();
|
641
|
+
}, "node"),
|
621
642
|
|
622
|
-
|
623
|
-
|
643
|
+
getTrailingSpace: createCachingGetter("trailingSpace", function(el) {
|
644
|
+
if (el.tagName.toLowerCase() == "br") {
|
645
|
+
return "";
|
646
|
+
} else {
|
647
|
+
switch (this.getComputedDisplay()) {
|
648
|
+
case "inline":
|
649
|
+
var child = el.lastChild;
|
650
|
+
while (child) {
|
651
|
+
if (!isIgnoredNode(child)) {
|
652
|
+
return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : "";
|
653
|
+
}
|
654
|
+
child = child.previousSibling;
|
655
|
+
}
|
656
|
+
break;
|
657
|
+
case "inline-block":
|
658
|
+
case "inline-table":
|
659
|
+
case "none":
|
660
|
+
case "table-column":
|
661
|
+
case "table-column-group":
|
662
|
+
break;
|
663
|
+
case "table-cell":
|
664
|
+
return "\t";
|
665
|
+
default:
|
666
|
+
return this.isRenderedBlock(true) ? "\n" : "";
|
667
|
+
}
|
668
|
+
}
|
624
669
|
return "";
|
625
|
-
}
|
670
|
+
}, "node"),
|
671
|
+
|
672
|
+
getLeadingSpace: createCachingGetter("leadingSpace", function(el) {
|
626
673
|
switch (this.getComputedDisplay()) {
|
627
674
|
case "inline":
|
628
|
-
var child = el.lastChild;
|
629
|
-
while (child) {
|
630
|
-
if (!isIgnoredNode(child)) {
|
631
|
-
return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : "";
|
632
|
-
}
|
633
|
-
child = child.previousSibling;
|
634
|
-
}
|
635
|
-
break;
|
636
675
|
case "inline-block":
|
637
676
|
case "inline-table":
|
638
677
|
case "none":
|
639
678
|
case "table-column":
|
640
679
|
case "table-column-group":
|
641
|
-
break;
|
642
680
|
case "table-cell":
|
643
|
-
|
681
|
+
break;
|
644
682
|
default:
|
645
|
-
return this.isRenderedBlock(
|
683
|
+
return this.isRenderedBlock(false) ? "\n" : "";
|
646
684
|
}
|
647
|
-
|
648
|
-
|
649
|
-
}
|
650
|
-
|
651
|
-
getLeadingSpace: createCachingGetter("leadingSpace", function(el) {
|
652
|
-
switch (this.getComputedDisplay()) {
|
653
|
-
case "inline":
|
654
|
-
case "inline-block":
|
655
|
-
case "inline-table":
|
656
|
-
case "none":
|
657
|
-
case "table-column":
|
658
|
-
case "table-column-group":
|
659
|
-
case "table-cell":
|
660
|
-
break;
|
661
|
-
default:
|
662
|
-
return this.isRenderedBlock(false) ? "\n" : "";
|
663
|
-
}
|
664
|
-
return "";
|
665
|
-
}, "node")
|
666
|
-
});
|
685
|
+
return "";
|
686
|
+
}, "node")
|
687
|
+
});
|
667
688
|
|
668
|
-
|
689
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
669
690
|
|
691
|
+
function Position(nodeWrapper, offset) {
|
692
|
+
this.offset = offset;
|
693
|
+
this.nodeWrapper = nodeWrapper;
|
694
|
+
this.node = nodeWrapper.node;
|
695
|
+
this.session = nodeWrapper.session;
|
696
|
+
this.cache = new Cache();
|
697
|
+
}
|
670
698
|
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
this.node = nodeWrapper.node;
|
675
|
-
this.session = nodeWrapper.session;
|
676
|
-
this.cache = new Cache();
|
677
|
-
}
|
699
|
+
function inspectPosition() {
|
700
|
+
return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]";
|
701
|
+
}
|
678
702
|
|
679
|
-
|
680
|
-
|
681
|
-
|
703
|
+
var positionProto = {
|
704
|
+
character: "",
|
705
|
+
characterType: EMPTY,
|
706
|
+
isBr: false,
|
682
707
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
708
|
+
/*
|
709
|
+
This method:
|
710
|
+
- Fully populates positions that have characters that can be determined independently of any other characters.
|
711
|
+
- Populates most types of space positions with a provisional character. The character is finalized later.
|
712
|
+
*/
|
713
|
+
prepopulateChar: function() {
|
714
|
+
var pos = this;
|
715
|
+
if (!pos.prepopulatedChar) {
|
716
|
+
var node = pos.node, offset = pos.offset;
|
717
|
+
var visibleChar = "", charType = EMPTY;
|
718
|
+
var finalizedChar = false;
|
719
|
+
if (offset > 0) {
|
720
|
+
if (node.nodeType == 3) {
|
721
|
+
var text = node.data;
|
722
|
+
var textChar = text.charAt(offset - 1);
|
723
|
+
|
724
|
+
var nodeInfo = pos.nodeWrapper.getTextNodeInfo();
|
725
|
+
var spaceRegex = nodeInfo.spaceRegex;
|
726
|
+
if (nodeInfo.collapseSpaces) {
|
727
|
+
if (spaceRegex.test(textChar)) {
|
728
|
+
// "If the character at position is from set, append a single space (U+0020) to newdata and advance
|
729
|
+
// position until the character at position is not from set."
|
730
|
+
|
731
|
+
// We also need to check for the case where we're in a pre-line and we have a space preceding a
|
732
|
+
// line break, because such spaces are collapsed in some browsers
|
733
|
+
if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) {
|
734
|
+
} else if (nodeInfo.preLine && text.charAt(offset) === "\n") {
|
735
|
+
visibleChar = " ";
|
736
|
+
charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK;
|
737
|
+
} else {
|
738
|
+
visibleChar = " ";
|
739
|
+
//pos.checkForFollowingLineBreak = true;
|
740
|
+
charType = COLLAPSIBLE_SPACE;
|
741
|
+
}
|
717
742
|
} else {
|
718
|
-
visibleChar =
|
719
|
-
|
720
|
-
|
743
|
+
visibleChar = textChar;
|
744
|
+
charType = NON_SPACE;
|
745
|
+
finalizedChar = true;
|
721
746
|
}
|
722
747
|
} else {
|
723
748
|
visibleChar = textChar;
|
724
|
-
charType =
|
749
|
+
charType = UNCOLLAPSIBLE_SPACE;
|
725
750
|
finalizedChar = true;
|
726
751
|
}
|
727
752
|
} else {
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
charType = COLLAPSIBLE_SPACE;
|
739
|
-
finalizedChar = false;
|
740
|
-
} else {
|
741
|
-
pos.checkForTrailingSpace = true;
|
753
|
+
var nodePassed = node.childNodes[offset - 1];
|
754
|
+
if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) {
|
755
|
+
if (nodePassed.tagName.toLowerCase() == "br") {
|
756
|
+
visibleChar = "\n";
|
757
|
+
pos.isBr = true;
|
758
|
+
charType = COLLAPSIBLE_SPACE;
|
759
|
+
finalizedChar = false;
|
760
|
+
} else {
|
761
|
+
pos.checkForTrailingSpace = true;
|
762
|
+
}
|
742
763
|
}
|
743
|
-
}
|
744
764
|
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
765
|
+
// Check the leading space of the next node for the case when a block element follows an inline
|
766
|
+
// element or text node. In that case, there is an implied line break between the two nodes.
|
767
|
+
if (!visibleChar) {
|
768
|
+
var nextNode = node.childNodes[offset];
|
769
|
+
if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) {
|
770
|
+
pos.checkForLeadingSpace = true;
|
771
|
+
}
|
751
772
|
}
|
752
773
|
}
|
753
774
|
}
|
754
|
-
}
|
755
775
|
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
776
|
+
pos.prepopulatedChar = true;
|
777
|
+
pos.character = visibleChar;
|
778
|
+
pos.characterType = charType;
|
779
|
+
pos.isCharInvariant = finalizedChar;
|
780
|
+
}
|
781
|
+
},
|
762
782
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
783
|
+
isDefinitelyNonEmpty: function() {
|
784
|
+
var charType = this.characterType;
|
785
|
+
return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE;
|
786
|
+
},
|
767
787
|
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
}
|
773
|
-
if (this.checkForTrailingSpace) {
|
774
|
-
var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace();
|
775
|
-
if (trailingSpace) {
|
776
|
-
this.isTrailingSpace = true;
|
777
|
-
this.character = trailingSpace;
|
778
|
-
this.characterType = COLLAPSIBLE_SPACE;
|
788
|
+
// Resolve leading and trailing spaces, which may involve prepopulating other positions
|
789
|
+
resolveLeadingAndTrailingSpaces: function() {
|
790
|
+
if (!this.prepopulatedChar) {
|
791
|
+
this.prepopulateChar();
|
779
792
|
}
|
780
|
-
this.checkForTrailingSpace
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
this.
|
793
|
+
if (this.checkForTrailingSpace) {
|
794
|
+
var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace();
|
795
|
+
if (trailingSpace) {
|
796
|
+
this.isTrailingSpace = true;
|
797
|
+
this.character = trailingSpace;
|
798
|
+
this.characterType = COLLAPSIBLE_SPACE;
|
799
|
+
}
|
800
|
+
this.checkForTrailingSpace = false;
|
788
801
|
}
|
789
|
-
this.checkForLeadingSpace
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
if (character !== "") {
|
798
|
-
return pos;
|
802
|
+
if (this.checkForLeadingSpace) {
|
803
|
+
var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();
|
804
|
+
if (leadingSpace) {
|
805
|
+
this.isLeadingSpace = true;
|
806
|
+
this.character = leadingSpace;
|
807
|
+
this.characterType = COLLAPSIBLE_SPACE;
|
808
|
+
}
|
809
|
+
this.checkForLeadingSpace = false;
|
799
810
|
}
|
800
|
-
}
|
811
|
+
},
|
801
812
|
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
// if so
|
810
|
-
if (this.isCharInvariant) {
|
811
|
-
return this.character;
|
812
|
-
}
|
813
|
-
|
814
|
-
var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace].join("_");
|
815
|
-
var cachedChar = this.cache.get(cacheKey);
|
816
|
-
if (cachedChar !== null) {
|
817
|
-
return cachedChar;
|
818
|
-
}
|
819
|
-
|
820
|
-
// We need to actually get the character
|
821
|
-
var character = "";
|
822
|
-
var collapsible = (this.characterType == COLLAPSIBLE_SPACE);
|
823
|
-
|
824
|
-
var nextPos, previousPos/* = this.getPrecedingUncollapsedPosition(characterOptions)*/;
|
825
|
-
var gotPreviousPos = false;
|
826
|
-
var pos = this;
|
827
|
-
|
828
|
-
function getPreviousPos() {
|
829
|
-
if (!gotPreviousPos) {
|
830
|
-
previousPos = pos.getPrecedingUncollapsedPosition(characterOptions);
|
831
|
-
gotPreviousPos = true;
|
813
|
+
getPrecedingUncollapsedPosition: function(characterOptions) {
|
814
|
+
var pos = this, character;
|
815
|
+
while ( (pos = pos.previousVisible()) ) {
|
816
|
+
character = pos.getCharacter(characterOptions);
|
817
|
+
if (character !== "") {
|
818
|
+
return pos;
|
819
|
+
}
|
832
820
|
}
|
833
|
-
return previousPos;
|
834
|
-
}
|
835
821
|
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
822
|
+
return null;
|
823
|
+
},
|
824
|
+
|
825
|
+
getCharacter: function(characterOptions) {
|
826
|
+
this.resolveLeadingAndTrailingSpaces();
|
827
|
+
|
828
|
+
var thisChar = this.character, returnChar;
|
829
|
+
|
830
|
+
// Check if character is ignored
|
831
|
+
var ignoredChars = normalizeIgnoredCharacters(characterOptions.ignoreCharacters);
|
832
|
+
var isIgnoredCharacter = (thisChar !== "" && ignoredChars.indexOf(thisChar) > -1);
|
833
|
+
|
834
|
+
// Check if this position's character is invariant (i.e. not dependent on character options) and return it
|
835
|
+
// if so
|
836
|
+
if (this.isCharInvariant) {
|
837
|
+
returnChar = isIgnoredCharacter ? "" : thisChar;
|
838
|
+
return returnChar;
|
839
|
+
}
|
840
|
+
|
841
|
+
var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace, ignoredChars].join("_");
|
842
|
+
var cachedChar = this.cache.get(cacheKey);
|
843
|
+
if (cachedChar !== null) {
|
844
|
+
return cachedChar;
|
845
|
+
}
|
846
|
+
|
847
|
+
// We need to actually get the character now
|
848
|
+
var character = "";
|
849
|
+
var collapsible = (this.characterType == COLLAPSIBLE_SPACE);
|
850
|
+
|
851
|
+
var nextPos, previousPos;
|
852
|
+
var gotPreviousPos = false;
|
853
|
+
var pos = this;
|
854
|
+
|
855
|
+
function getPreviousPos() {
|
856
|
+
if (!gotPreviousPos) {
|
857
|
+
previousPos = pos.getPrecedingUncollapsedPosition(characterOptions);
|
858
|
+
gotPreviousPos = true;
|
859
|
+
}
|
860
|
+
return previousPos;
|
841
861
|
}
|
842
|
-
|
843
|
-
|
844
|
-
|
862
|
+
|
863
|
+
// Disallow a collapsible space that is followed by a line break or is the last character
|
864
|
+
if (collapsible) {
|
865
|
+
// Allow a trailing space that we've previously determined should be included
|
866
|
+
if (this.type == INCLUDED_TRAILING_LINE_BREAK_AFTER_BR) {
|
845
867
|
character = "\n";
|
846
|
-
} else {
|
847
868
|
}
|
848
|
-
|
849
|
-
|
850
|
-
if (
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
869
|
+
// Disallow a collapsible space that follows a trailing space or line break, or is the first character,
|
870
|
+
// or follows a collapsible included space
|
871
|
+
else if (thisChar == " " &&
|
872
|
+
(!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n" || (previousPos.character == " " && previousPos.characterType == COLLAPSIBLE_SPACE))) {
|
873
|
+
}
|
874
|
+
// Allow a leading line break unless it follows a line break
|
875
|
+
else if (thisChar == "\n" && this.isLeadingSpace) {
|
876
|
+
if (getPreviousPos() && previousPos.character != "\n") {
|
877
|
+
character = "\n";
|
878
|
+
} else {
|
857
879
|
}
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
} else if (
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
880
|
+
} else {
|
881
|
+
nextPos = this.nextUncollapsed();
|
882
|
+
if (nextPos) {
|
883
|
+
if (nextPos.isBr) {
|
884
|
+
this.type = TRAILING_SPACE_BEFORE_BR;
|
885
|
+
} else if (nextPos.isTrailingSpace && nextPos.character == "\n") {
|
886
|
+
this.type = TRAILING_SPACE_IN_BLOCK;
|
887
|
+
} else if (nextPos.isLeadingSpace && nextPos.character == "\n") {
|
888
|
+
this.type = TRAILING_SPACE_BEFORE_BLOCK;
|
889
|
+
}
|
890
|
+
|
891
|
+
if (nextPos.character == "\n") {
|
892
|
+
if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) {
|
893
|
+
} else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) {
|
894
|
+
} else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) {
|
895
|
+
} else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) {
|
896
|
+
} else if (thisChar == "\n") {
|
897
|
+
if (nextPos.isTrailingSpace) {
|
898
|
+
if (this.isTrailingSpace) {
|
899
|
+
} else if (this.isBr) {
|
900
|
+
nextPos.type = TRAILING_LINE_BREAK_AFTER_BR;
|
901
|
+
|
902
|
+
if (getPreviousPos() && previousPos.isLeadingSpace && !previousPos.isTrailingSpace && previousPos.character == "\n") {
|
903
|
+
nextPos.character = "";
|
904
|
+
} else {
|
905
|
+
nextPos.type = INCLUDED_TRAILING_LINE_BREAK_AFTER_BR;
|
906
|
+
}
|
879
907
|
}
|
908
|
+
} else {
|
909
|
+
character = "\n";
|
880
910
|
}
|
911
|
+
} else if (thisChar == " ") {
|
912
|
+
character = " ";
|
881
913
|
} else {
|
882
|
-
character = "\n";
|
883
914
|
}
|
884
|
-
} else if (this.character === " ") {
|
885
|
-
character = " ";
|
886
915
|
} else {
|
916
|
+
character = thisChar;
|
887
917
|
}
|
888
918
|
} else {
|
889
|
-
character = this.character;
|
890
919
|
}
|
891
|
-
} else {
|
892
920
|
}
|
893
921
|
}
|
894
|
-
}
|
895
922
|
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
}
|
900
|
-
|
901
|
-
|
902
|
-
this.cache.set(cacheKey, character);
|
923
|
+
if (ignoredChars.indexOf(character) > -1) {
|
924
|
+
character = "";
|
925
|
+
}
|
903
926
|
|
904
|
-
return character;
|
905
|
-
},
|
906
927
|
|
907
|
-
|
908
|
-
return !!pos && this.node === pos.node && this.offset === pos.offset;
|
909
|
-
},
|
928
|
+
this.cache.set(cacheKey, character);
|
910
929
|
|
911
|
-
|
930
|
+
return character;
|
931
|
+
},
|
912
932
|
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
};
|
933
|
+
equals: function(pos) {
|
934
|
+
return !!pos && this.node === pos.node && this.offset === pos.offset;
|
935
|
+
},
|
917
936
|
|
918
|
-
|
937
|
+
inspect: inspectPosition,
|
919
938
|
|
920
|
-
|
921
|
-
|
922
|
-
var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
|
923
|
-
if (!node) {
|
924
|
-
return null;
|
939
|
+
toString: function() {
|
940
|
+
return this.character;
|
925
941
|
}
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
942
|
+
};
|
943
|
+
|
944
|
+
Position.prototype = positionProto;
|
945
|
+
|
946
|
+
extend(positionProto, {
|
947
|
+
next: createCachingGetter("nextPos", function(pos) {
|
948
|
+
var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
|
949
|
+
if (!node) {
|
950
|
+
return null;
|
951
|
+
}
|
952
|
+
var nextNode, nextOffset, child;
|
953
|
+
if (offset == nodeWrapper.getLength()) {
|
954
|
+
// Move onto the next node
|
955
|
+
nextNode = node.parentNode;
|
956
|
+
nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0;
|
935
957
|
} else {
|
936
|
-
|
937
|
-
// Go into the children next, if children there are
|
938
|
-
if (session.getNodeWrapper(child).containsPositions()) {
|
939
|
-
nextNode = child;
|
940
|
-
nextOffset = 0;
|
941
|
-
} else {
|
958
|
+
if (nodeWrapper.isCharacterDataNode()) {
|
942
959
|
nextNode = node;
|
943
960
|
nextOffset = offset + 1;
|
961
|
+
} else {
|
962
|
+
child = node.childNodes[offset];
|
963
|
+
// Go into the children next, if children there are
|
964
|
+
if (session.getNodeWrapper(child).containsPositions()) {
|
965
|
+
nextNode = child;
|
966
|
+
nextOffset = 0;
|
967
|
+
} else {
|
968
|
+
nextNode = node;
|
969
|
+
nextOffset = offset + 1;
|
970
|
+
}
|
944
971
|
}
|
945
972
|
}
|
946
|
-
}
|
947
973
|
|
948
|
-
|
949
|
-
|
974
|
+
return nextNode ? session.getPosition(nextNode, nextOffset) : null;
|
975
|
+
}),
|
950
976
|
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
} else {
|
958
|
-
if (nodeWrapper.isCharacterDataNode()) {
|
959
|
-
previousNode = node;
|
960
|
-
previousOffset = offset - 1;
|
977
|
+
previous: createCachingGetter("previous", function(pos) {
|
978
|
+
var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
|
979
|
+
var previousNode, previousOffset, child;
|
980
|
+
if (offset == 0) {
|
981
|
+
previousNode = node.parentNode;
|
982
|
+
previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0;
|
961
983
|
} else {
|
962
|
-
|
963
|
-
// Go into the children next, if children there are
|
964
|
-
if (session.getNodeWrapper(child).containsPositions()) {
|
965
|
-
previousNode = child;
|
966
|
-
previousOffset = dom.getNodeLength(child);
|
967
|
-
} else {
|
984
|
+
if (nodeWrapper.isCharacterDataNode()) {
|
968
985
|
previousNode = node;
|
969
986
|
previousOffset = offset - 1;
|
987
|
+
} else {
|
988
|
+
child = node.childNodes[offset - 1];
|
989
|
+
// Go into the children next, if children there are
|
990
|
+
if (session.getNodeWrapper(child).containsPositions()) {
|
991
|
+
previousNode = child;
|
992
|
+
previousOffset = dom.getNodeLength(child);
|
993
|
+
} else {
|
994
|
+
previousNode = node;
|
995
|
+
previousOffset = offset - 1;
|
996
|
+
}
|
970
997
|
}
|
971
998
|
}
|
972
|
-
|
973
|
-
|
974
|
-
}),
|
975
|
-
|
976
|
-
/*
|
977
|
-
Next and previous position moving functions that filter out
|
978
|
-
|
979
|
-
- Hidden (CSS visibility/display) elements
|
980
|
-
- Script and style elements
|
981
|
-
*/
|
982
|
-
nextVisible: createCachingGetter("nextVisible", function(pos) {
|
983
|
-
var next = pos.next();
|
984
|
-
if (!next) {
|
985
|
-
return null;
|
986
|
-
}
|
987
|
-
var nodeWrapper = next.nodeWrapper, node = next.node;
|
988
|
-
var newPos = next;
|
989
|
-
if (nodeWrapper.isCollapsed()) {
|
990
|
-
// We're skipping this node and all its descendants
|
991
|
-
newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1);
|
992
|
-
}
|
993
|
-
return newPos;
|
994
|
-
}),
|
995
|
-
|
996
|
-
nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) {
|
997
|
-
var nextPos = pos;
|
998
|
-
while ( (nextPos = nextPos.nextVisible()) ) {
|
999
|
-
nextPos.resolveLeadingAndTrailingSpaces();
|
1000
|
-
if (nextPos.character !== "") {
|
1001
|
-
return nextPos;
|
1002
|
-
}
|
1003
|
-
}
|
1004
|
-
return null;
|
1005
|
-
}),
|
999
|
+
return previousNode ? session.getPosition(previousNode, previousOffset) : null;
|
1000
|
+
}),
|
1006
1001
|
|
1007
|
-
|
1008
|
-
|
1009
|
-
if (!previous) {
|
1010
|
-
return null;
|
1011
|
-
}
|
1012
|
-
var nodeWrapper = previous.nodeWrapper, node = previous.node;
|
1013
|
-
var newPos = previous;
|
1014
|
-
if (nodeWrapper.isCollapsed()) {
|
1015
|
-
// We're skipping this node and all its descendants
|
1016
|
-
newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex());
|
1017
|
-
}
|
1018
|
-
return newPos;
|
1019
|
-
})
|
1020
|
-
});
|
1021
|
-
|
1022
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
1023
|
-
|
1024
|
-
var currentSession = null;
|
1025
|
-
|
1026
|
-
var Session = (function() {
|
1027
|
-
function createWrapperCache(nodeProperty) {
|
1028
|
-
var cache = new Cache();
|
1002
|
+
/*
|
1003
|
+
Next and previous position moving functions that filter out
|
1029
1004
|
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
return wrapper;
|
1037
|
-
}
|
1038
|
-
}
|
1039
|
-
}
|
1005
|
+
- Hidden (CSS visibility/display) elements
|
1006
|
+
- Script and style elements
|
1007
|
+
*/
|
1008
|
+
nextVisible: createCachingGetter("nextVisible", function(pos) {
|
1009
|
+
var next = pos.next();
|
1010
|
+
if (!next) {
|
1040
1011
|
return null;
|
1041
|
-
},
|
1042
|
-
|
1043
|
-
set: function(nodeWrapper) {
|
1044
|
-
var property = nodeWrapper.node[nodeProperty];
|
1045
|
-
var wrappersByProperty = cache.get(property) || cache.set(property, []);
|
1046
|
-
wrappersByProperty.push(nodeWrapper);
|
1047
1012
|
}
|
1048
|
-
|
1049
|
-
|
1013
|
+
var nodeWrapper = next.nodeWrapper, node = next.node;
|
1014
|
+
var newPos = next;
|
1015
|
+
if (nodeWrapper.isCollapsed()) {
|
1016
|
+
// We're skipping this node and all its descendants
|
1017
|
+
newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1);
|
1018
|
+
}
|
1019
|
+
return newPos;
|
1020
|
+
}),
|
1021
|
+
|
1022
|
+
nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) {
|
1023
|
+
var nextPos = pos;
|
1024
|
+
while ( (nextPos = nextPos.nextVisible()) ) {
|
1025
|
+
nextPos.resolveLeadingAndTrailingSpaces();
|
1026
|
+
if (nextPos.character !== "") {
|
1027
|
+
return nextPos;
|
1028
|
+
}
|
1029
|
+
}
|
1030
|
+
return null;
|
1031
|
+
}),
|
1050
1032
|
|
1051
|
-
|
1033
|
+
previousVisible: createCachingGetter("previousVisible", function(pos) {
|
1034
|
+
var previous = pos.previous();
|
1035
|
+
if (!previous) {
|
1036
|
+
return null;
|
1037
|
+
}
|
1038
|
+
var nodeWrapper = previous.nodeWrapper, node = previous.node;
|
1039
|
+
var newPos = previous;
|
1040
|
+
if (nodeWrapper.isCollapsed()) {
|
1041
|
+
// We're skipping this node and all its descendants
|
1042
|
+
newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex());
|
1043
|
+
}
|
1044
|
+
return newPos;
|
1045
|
+
})
|
1046
|
+
});
|
1052
1047
|
|
1053
|
-
|
1054
|
-
this.initCaches();
|
1055
|
-
}
|
1048
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1056
1049
|
|
1057
|
-
|
1058
|
-
initCaches: function() {
|
1059
|
-
this.elementCache = uniqueIDSupported ? (function() {
|
1060
|
-
var elementsCache = new Cache();
|
1050
|
+
var currentSession = null;
|
1061
1051
|
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
},
|
1052
|
+
var Session = (function() {
|
1053
|
+
function createWrapperCache(nodeProperty) {
|
1054
|
+
var cache = new Cache();
|
1066
1055
|
|
1067
|
-
|
1068
|
-
|
1056
|
+
return {
|
1057
|
+
get: function(node) {
|
1058
|
+
var wrappersByProperty = cache.get(node[nodeProperty]);
|
1059
|
+
if (wrappersByProperty) {
|
1060
|
+
for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) {
|
1061
|
+
if (wrapper.node === node) {
|
1062
|
+
return wrapper;
|
1063
|
+
}
|
1064
|
+
}
|
1069
1065
|
}
|
1070
|
-
|
1071
|
-
|
1066
|
+
return null;
|
1067
|
+
},
|
1072
1068
|
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1069
|
+
set: function(nodeWrapper) {
|
1070
|
+
var property = nodeWrapper.node[nodeProperty];
|
1071
|
+
var wrappersByProperty = cache.get(property) || cache.set(property, []);
|
1072
|
+
wrappersByProperty.push(nodeWrapper);
|
1073
|
+
}
|
1074
|
+
};
|
1075
|
+
}
|
1077
1076
|
|
1078
|
-
|
1079
|
-
var wrapperCache;
|
1080
|
-
switch (node.nodeType) {
|
1081
|
-
case 1:
|
1082
|
-
wrapperCache = this.elementCache;
|
1083
|
-
break;
|
1084
|
-
case 3:
|
1085
|
-
wrapperCache = this.textNodeCache;
|
1086
|
-
break;
|
1087
|
-
default:
|
1088
|
-
wrapperCache = this.otherNodeCache;
|
1089
|
-
break;
|
1090
|
-
}
|
1077
|
+
var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID");
|
1091
1078
|
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
wrapperCache.set(wrapper);
|
1096
|
-
}
|
1097
|
-
return wrapper;
|
1098
|
-
},
|
1079
|
+
function Session() {
|
1080
|
+
this.initCaches();
|
1081
|
+
}
|
1099
1082
|
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1083
|
+
Session.prototype = {
|
1084
|
+
initCaches: function() {
|
1085
|
+
this.elementCache = uniqueIDSupported ? (function() {
|
1086
|
+
var elementsCache = new Cache();
|
1103
1087
|
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1088
|
+
return {
|
1089
|
+
get: function(el) {
|
1090
|
+
return elementsCache.get(el.uniqueID);
|
1091
|
+
},
|
1108
1092
|
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1093
|
+
set: function(elWrapper) {
|
1094
|
+
elementsCache.set(elWrapper.node.uniqueID, elWrapper);
|
1095
|
+
}
|
1096
|
+
};
|
1097
|
+
})() : createWrapperCache("tagName");
|
1098
|
+
|
1099
|
+
// Store text nodes keyed by data, although we may need to truncate this
|
1100
|
+
this.textNodeCache = createWrapperCache("data");
|
1101
|
+
this.otherNodeCache = createWrapperCache("nodeName");
|
1102
|
+
},
|
1113
1103
|
|
1114
|
-
|
1115
|
-
|
1104
|
+
getNodeWrapper: function(node) {
|
1105
|
+
var wrapperCache;
|
1106
|
+
switch (node.nodeType) {
|
1107
|
+
case 1:
|
1108
|
+
wrapperCache = this.elementCache;
|
1109
|
+
break;
|
1110
|
+
case 3:
|
1111
|
+
wrapperCache = this.textNodeCache;
|
1112
|
+
break;
|
1113
|
+
default:
|
1114
|
+
wrapperCache = this.otherNodeCache;
|
1115
|
+
break;
|
1116
|
+
}
|
1116
1117
|
|
1117
|
-
|
1118
|
+
var wrapper = wrapperCache.get(node);
|
1119
|
+
if (!wrapper) {
|
1120
|
+
wrapper = new NodeWrapper(node, this);
|
1121
|
+
wrapperCache.set(wrapper);
|
1122
|
+
}
|
1123
|
+
return wrapper;
|
1124
|
+
},
|
1118
1125
|
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
}
|
1126
|
+
getPosition: function(node, offset) {
|
1127
|
+
return this.getNodeWrapper(node).getPosition(offset);
|
1128
|
+
},
|
1123
1129
|
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1130
|
+
getRangeBoundaryPosition: function(range, isStart) {
|
1131
|
+
var prefix = isStart ? "start" : "end";
|
1132
|
+
return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]);
|
1133
|
+
},
|
1134
|
+
|
1135
|
+
detach: function() {
|
1136
|
+
this.elementCache = this.textNodeCache = this.otherNodeCache = null;
|
1137
|
+
}
|
1138
|
+
};
|
1139
|
+
|
1140
|
+
return Session;
|
1141
|
+
})();
|
1127
1142
|
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1143
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1144
|
+
|
1145
|
+
function startSession() {
|
1146
|
+
endSession();
|
1147
|
+
return (currentSession = new Session());
|
1148
|
+
}
|
1149
|
+
|
1150
|
+
function getSession() {
|
1151
|
+
return currentSession || startSession();
|
1152
|
+
}
|
1153
|
+
|
1154
|
+
function endSession() {
|
1155
|
+
if (currentSession) {
|
1156
|
+
currentSession.detach();
|
1157
|
+
}
|
1158
|
+
currentSession = null;
|
1131
1159
|
}
|
1132
|
-
currentSession = null;
|
1133
|
-
}
|
1134
1160
|
|
1135
|
-
|
1161
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1136
1162
|
|
1137
|
-
|
1163
|
+
// Extensions to the rangy.dom utility object
|
1138
1164
|
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1165
|
+
extend(dom, {
|
1166
|
+
nextNode: nextNode,
|
1167
|
+
previousNode: previousNode
|
1168
|
+
});
|
1143
1169
|
|
1144
|
-
|
1170
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1145
1171
|
|
1146
|
-
|
1172
|
+
function createCharacterIterator(startPos, backward, endPos, characterOptions) {
|
1147
1173
|
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1174
|
+
// Adjust the end position to ensure that it is actually reached
|
1175
|
+
if (endPos) {
|
1176
|
+
if (backward) {
|
1177
|
+
if (isCollapsedNode(endPos.node)) {
|
1178
|
+
endPos = startPos.previousVisible();
|
1179
|
+
}
|
1180
|
+
} else {
|
1181
|
+
if (isCollapsedNode(endPos.node)) {
|
1182
|
+
endPos = endPos.nextVisible();
|
1183
|
+
}
|
1157
1184
|
}
|
1158
1185
|
}
|
1159
|
-
}
|
1160
1186
|
|
1161
|
-
|
1187
|
+
var pos = startPos, finished = false;
|
1162
1188
|
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1189
|
+
function next() {
|
1190
|
+
var charPos = null;
|
1191
|
+
if (backward) {
|
1192
|
+
charPos = pos;
|
1193
|
+
if (!finished) {
|
1194
|
+
pos = pos.previousVisible();
|
1195
|
+
finished = !pos || (endPos && pos.equals(endPos));
|
1196
|
+
}
|
1197
|
+
} else {
|
1198
|
+
if (!finished) {
|
1199
|
+
charPos = pos = pos.nextVisible();
|
1200
|
+
finished = !pos || (endPos && pos.equals(endPos));
|
1201
|
+
}
|
1170
1202
|
}
|
1171
|
-
|
1172
|
-
|
1173
|
-
charPos = pos = pos.nextVisible();
|
1174
|
-
finished = !pos || (endPos && pos.equals(endPos));
|
1203
|
+
if (finished) {
|
1204
|
+
pos = null;
|
1175
1205
|
}
|
1206
|
+
return charPos;
|
1176
1207
|
}
|
1177
|
-
if (finished) {
|
1178
|
-
pos = null;
|
1179
|
-
}
|
1180
|
-
return charPos;
|
1181
|
-
}
|
1182
1208
|
|
1183
|
-
|
1209
|
+
var previousTextPos, returnPreviousTextPos = false;
|
1184
1210
|
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1211
|
+
return {
|
1212
|
+
next: function() {
|
1213
|
+
if (returnPreviousTextPos) {
|
1214
|
+
returnPreviousTextPos = false;
|
1215
|
+
return previousTextPos;
|
1216
|
+
} else {
|
1217
|
+
var pos, character;
|
1218
|
+
while ( (pos = next()) ) {
|
1219
|
+
character = pos.getCharacter(characterOptions);
|
1220
|
+
if (character) {
|
1221
|
+
previousTextPos = pos;
|
1222
|
+
return pos;
|
1223
|
+
}
|
1197
1224
|
}
|
1225
|
+
return null;
|
1198
1226
|
}
|
1199
|
-
|
1200
|
-
}
|
1201
|
-
},
|
1227
|
+
},
|
1202
1228
|
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1229
|
+
rewind: function() {
|
1230
|
+
if (previousTextPos) {
|
1231
|
+
returnPreviousTextPos = true;
|
1232
|
+
} else {
|
1233
|
+
throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound.");
|
1234
|
+
}
|
1235
|
+
},
|
1210
1236
|
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1237
|
+
dispose: function() {
|
1238
|
+
startPos = endPos = null;
|
1239
|
+
}
|
1240
|
+
};
|
1241
|
+
}
|
1216
1242
|
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1243
|
+
var arrayIndexOf = Array.prototype.indexOf ?
|
1244
|
+
function(arr, val) {
|
1245
|
+
return arr.indexOf(val);
|
1246
|
+
} :
|
1247
|
+
function(arr, val) {
|
1248
|
+
for (var i = 0, len = arr.length; i < len; ++i) {
|
1249
|
+
if (arr[i] === val) {
|
1250
|
+
return i;
|
1251
|
+
}
|
1225
1252
|
}
|
1226
|
-
|
1227
|
-
|
1228
|
-
};
|
1253
|
+
return -1;
|
1254
|
+
};
|
1229
1255
|
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1256
|
+
// Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next()
|
1257
|
+
// is called and there is no more tokenized text
|
1258
|
+
function createTokenizedTextProvider(pos, characterOptions, wordOptions) {
|
1259
|
+
var forwardIterator = createCharacterIterator(pos, false, null, characterOptions);
|
1260
|
+
var backwardIterator = createCharacterIterator(pos, true, null, characterOptions);
|
1261
|
+
var tokenizer = wordOptions.tokenizer;
|
1236
1262
|
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1263
|
+
// Consumes a word and the whitespace beyond it
|
1264
|
+
function consumeWord(forward) {
|
1265
|
+
var pos, textChar;
|
1266
|
+
var newChars = [], it = forward ? forwardIterator : backwardIterator;
|
1241
1267
|
|
1242
|
-
|
1268
|
+
var passedWordBoundary = false, insideWord = false;
|
1243
1269
|
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1270
|
+
while ( (pos = it.next()) ) {
|
1271
|
+
textChar = pos.character;
|
1247
1272
|
|
1248
|
-
|
1249
|
-
if (
|
1250
|
-
insideWord
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
if (passedWordBoundary) {
|
1255
|
-
it.rewind();
|
1256
|
-
break;
|
1273
|
+
|
1274
|
+
if (allWhiteSpaceRegex.test(textChar)) {
|
1275
|
+
if (insideWord) {
|
1276
|
+
insideWord = false;
|
1277
|
+
passedWordBoundary = true;
|
1278
|
+
}
|
1257
1279
|
} else {
|
1258
|
-
|
1280
|
+
if (passedWordBoundary) {
|
1281
|
+
it.rewind();
|
1282
|
+
break;
|
1283
|
+
} else {
|
1284
|
+
insideWord = true;
|
1285
|
+
}
|
1259
1286
|
}
|
1287
|
+
newChars.push(pos);
|
1260
1288
|
}
|
1261
|
-
newChars.push(pos);
|
1262
|
-
}
|
1263
1289
|
|
1264
1290
|
|
1265
|
-
|
1266
|
-
|
1291
|
+
return newChars;
|
1292
|
+
}
|
1267
1293
|
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1294
|
+
// Get initial word surrounding initial position and tokenize it
|
1295
|
+
var forwardChars = consumeWord(true);
|
1296
|
+
var backwardChars = consumeWord(false).reverse();
|
1297
|
+
var tokens = tokenize(backwardChars.concat(forwardChars), wordOptions, tokenizer);
|
1272
1298
|
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1299
|
+
// Create initial token buffers
|
1300
|
+
var forwardTokensBuffer = forwardChars.length ?
|
1301
|
+
tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : [];
|
1276
1302
|
|
1277
|
-
|
1278
|
-
|
1303
|
+
var backwardTokensBuffer = backwardChars.length ?
|
1304
|
+
tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : [];
|
1279
1305
|
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1306
|
+
function inspectBuffer(buffer) {
|
1307
|
+
var textPositions = ["[" + buffer.length + "]"];
|
1308
|
+
for (var i = 0; i < buffer.length; ++i) {
|
1309
|
+
textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")");
|
1310
|
+
}
|
1311
|
+
return textPositions;
|
1284
1312
|
}
|
1285
|
-
return textPositions;
|
1286
|
-
}
|
1287
1313
|
|
1288
1314
|
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1315
|
+
return {
|
1316
|
+
nextEndToken: function() {
|
1317
|
+
var lastToken, forwardChars;
|
1292
1318
|
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1319
|
+
// If we're down to the last token, consume character chunks until we have a word or run out of
|
1320
|
+
// characters to consume
|
1321
|
+
while ( forwardTokensBuffer.length == 1 &&
|
1322
|
+
!(lastToken = forwardTokensBuffer[0]).isWord &&
|
1323
|
+
(forwardChars = consumeWord(true)).length > 0) {
|
1298
1324
|
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1325
|
+
// Merge trailing non-word into next word and tokenize
|
1326
|
+
forwardTokensBuffer = tokenize(lastToken.chars.concat(forwardChars), wordOptions, tokenizer);
|
1327
|
+
}
|
1302
1328
|
|
1303
|
-
|
1304
|
-
|
1329
|
+
return forwardTokensBuffer.shift();
|
1330
|
+
},
|
1305
1331
|
|
1306
|
-
|
1307
|
-
|
1332
|
+
previousStartToken: function() {
|
1333
|
+
var lastToken, backwardChars;
|
1308
1334
|
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1335
|
+
// If we're down to the last token, consume character chunks until we have a word or run out of
|
1336
|
+
// characters to consume
|
1337
|
+
while ( backwardTokensBuffer.length == 1 &&
|
1338
|
+
!(lastToken = backwardTokensBuffer[0]).isWord &&
|
1339
|
+
(backwardChars = consumeWord(false)).length > 0) {
|
1314
1340
|
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1341
|
+
// Merge leading non-word into next word and tokenize
|
1342
|
+
backwardTokensBuffer = tokenize(backwardChars.reverse().concat(lastToken.chars), wordOptions, tokenizer);
|
1343
|
+
}
|
1318
1344
|
|
1319
|
-
|
1320
|
-
|
1345
|
+
return backwardTokensBuffer.pop();
|
1346
|
+
},
|
1321
1347
|
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1348
|
+
dispose: function() {
|
1349
|
+
forwardIterator.dispose();
|
1350
|
+
backwardIterator.dispose();
|
1351
|
+
forwardTokensBuffer = backwardTokensBuffer = null;
|
1352
|
+
}
|
1353
|
+
};
|
1354
|
+
}
|
1329
1355
|
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
switch (unit) {
|
1336
|
-
case CHARACTER:
|
1337
|
-
charIterator = createCharacterIterator(pos, backward, null, characterOptions);
|
1338
|
-
while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) {
|
1339
|
-
++unitsMoved;
|
1340
|
-
newPos = currentPos;
|
1341
|
-
}
|
1342
|
-
nextPos = currentPos;
|
1343
|
-
charIterator.dispose();
|
1344
|
-
break;
|
1345
|
-
case WORD:
|
1346
|
-
var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions);
|
1347
|
-
var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken;
|
1356
|
+
function movePositionBy(pos, unit, count, characterOptions, wordOptions) {
|
1357
|
+
var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token;
|
1358
|
+
if (count !== 0) {
|
1359
|
+
var backward = (count < 0);
|
1348
1360
|
|
1349
|
-
|
1350
|
-
|
1361
|
+
switch (unit) {
|
1362
|
+
case CHARACTER:
|
1363
|
+
charIterator = createCharacterIterator(pos, backward, null, characterOptions);
|
1364
|
+
while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) {
|
1351
1365
|
++unitsMoved;
|
1352
|
-
newPos =
|
1366
|
+
newPos = currentPos;
|
1353
1367
|
}
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
// the last possible equivalent visible position.
|
1371
|
-
if (unit == WORD) {
|
1372
|
-
charIterator = createCharacterIterator(pos, false, null, characterOptions);
|
1373
|
-
nextPos = charIterator.next();
|
1374
|
-
charIterator.dispose();
|
1368
|
+
nextPos = currentPos;
|
1369
|
+
charIterator.dispose();
|
1370
|
+
break;
|
1371
|
+
case WORD:
|
1372
|
+
var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions);
|
1373
|
+
var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken;
|
1374
|
+
|
1375
|
+
while ( (token = next()) && unitsMoved < absCount ) {
|
1376
|
+
if (token.isWord) {
|
1377
|
+
++unitsMoved;
|
1378
|
+
newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1];
|
1379
|
+
}
|
1380
|
+
}
|
1381
|
+
break;
|
1382
|
+
default:
|
1383
|
+
throw new Error("movePositionBy: unit '" + unit + "' not implemented");
|
1375
1384
|
}
|
1376
|
-
|
1377
|
-
|
1385
|
+
|
1386
|
+
// Perform any necessary position tweaks
|
1387
|
+
if (backward) {
|
1388
|
+
newPos = newPos.previousVisible();
|
1389
|
+
unitsMoved = -unitsMoved;
|
1390
|
+
} else if (newPos && newPos.isLeadingSpace && !newPos.isTrailingSpace) {
|
1391
|
+
// Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space
|
1392
|
+
// before a block element (for example, the line break between "1" and "2" in the following HTML:
|
1393
|
+
// "1<p>2</p>") is considered to be attached to the position immediately before the block element, which
|
1394
|
+
// corresponds with a different selection position in most browsers from the one we want (i.e. at the
|
1395
|
+
// start of the contents of the block element). We get round this by advancing the position returned to
|
1396
|
+
// the last possible equivalent visible position.
|
1397
|
+
if (unit == WORD) {
|
1398
|
+
charIterator = createCharacterIterator(pos, false, null, characterOptions);
|
1399
|
+
nextPos = charIterator.next();
|
1400
|
+
charIterator.dispose();
|
1401
|
+
}
|
1402
|
+
if (nextPos) {
|
1403
|
+
newPos = nextPos.previousVisible();
|
1404
|
+
}
|
1378
1405
|
}
|
1379
1406
|
}
|
1380
|
-
}
|
1381
|
-
|
1382
1407
|
|
1383
|
-
return {
|
1384
|
-
position: newPos,
|
1385
|
-
unitsMoved: unitsMoved
|
1386
|
-
};
|
1387
|
-
}
|
1388
|
-
|
1389
|
-
function createRangeCharacterIterator(session, range, characterOptions, backward) {
|
1390
|
-
var rangeStart = session.getRangeBoundaryPosition(range, true);
|
1391
|
-
var rangeEnd = session.getRangeBoundaryPosition(range, false);
|
1392
|
-
var itStart = backward ? rangeEnd : rangeStart;
|
1393
|
-
var itEnd = backward ? rangeStart : rangeEnd;
|
1394
1408
|
|
1395
|
-
|
1396
|
-
|
1409
|
+
return {
|
1410
|
+
position: newPos,
|
1411
|
+
unitsMoved: unitsMoved
|
1412
|
+
};
|
1413
|
+
}
|
1397
1414
|
|
1398
|
-
|
1415
|
+
function createRangeCharacterIterator(session, range, characterOptions, backward) {
|
1416
|
+
var rangeStart = session.getRangeBoundaryPosition(range, true);
|
1417
|
+
var rangeEnd = session.getRangeBoundaryPosition(range, false);
|
1418
|
+
var itStart = backward ? rangeEnd : rangeStart;
|
1419
|
+
var itEnd = backward ? rangeStart : rangeEnd;
|
1399
1420
|
|
1400
|
-
|
1401
|
-
while ( (pos = it.next()) ) {
|
1402
|
-
chars.push(pos);
|
1421
|
+
return createCharacterIterator(itStart, !!backward, itEnd, characterOptions);
|
1403
1422
|
}
|
1404
1423
|
|
1405
|
-
|
1406
|
-
return chars;
|
1407
|
-
}
|
1424
|
+
function getRangeCharacters(session, range, characterOptions) {
|
1408
1425
|
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
range.detach();
|
1414
|
-
return returnVal;
|
1415
|
-
}
|
1426
|
+
var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos;
|
1427
|
+
while ( (pos = it.next()) ) {
|
1428
|
+
chars.push(pos);
|
1429
|
+
}
|
1416
1430
|
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
initialPos,
|
1421
|
-
backward,
|
1422
|
-
initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward),
|
1423
|
-
findOptions
|
1424
|
-
);
|
1425
|
-
var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex;
|
1426
|
-
var result, insideRegexMatch;
|
1427
|
-
var returnValue = null;
|
1428
|
-
|
1429
|
-
function handleMatch(startIndex, endIndex) {
|
1430
|
-
var startPos = chars[startIndex].previousVisible();
|
1431
|
-
var endPos = chars[endIndex - 1];
|
1432
|
-
var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions));
|
1431
|
+
it.dispose();
|
1432
|
+
return chars;
|
1433
|
+
}
|
1433
1434
|
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
};
|
1435
|
+
function isWholeWord(startPos, endPos, wordOptions) {
|
1436
|
+
var range = api.createRange(startPos.node);
|
1437
|
+
range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset);
|
1438
|
+
return !range.expand("word", { wordOptions: wordOptions });
|
1439
1439
|
}
|
1440
1440
|
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1441
|
+
function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) {
|
1442
|
+
var backward = isDirectionBackward(findOptions.direction);
|
1443
|
+
var it = createCharacterIterator(
|
1444
|
+
initialPos,
|
1445
|
+
backward,
|
1446
|
+
initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward),
|
1447
|
+
findOptions.characterOptions
|
1448
|
+
);
|
1449
|
+
var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex;
|
1450
|
+
var result, insideRegexMatch;
|
1451
|
+
var returnValue = null;
|
1452
|
+
|
1453
|
+
function handleMatch(startIndex, endIndex) {
|
1454
|
+
var startPos = chars[startIndex].previousVisible();
|
1455
|
+
var endPos = chars[endIndex - 1];
|
1456
|
+
var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions));
|
1446
1457
|
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
text += currentChar;
|
1458
|
+
return {
|
1459
|
+
startPos: startPos,
|
1460
|
+
endPos: endPos,
|
1461
|
+
valid: valid
|
1462
|
+
};
|
1453
1463
|
}
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1464
|
+
|
1465
|
+
while ( (pos = it.next()) ) {
|
1466
|
+
currentChar = pos.character;
|
1467
|
+
if (!isRegex && !findOptions.caseSensitive) {
|
1468
|
+
currentChar = currentChar.toLowerCase();
|
1469
|
+
}
|
1470
|
+
|
1471
|
+
if (backward) {
|
1472
|
+
chars.unshift(pos);
|
1473
|
+
text = currentChar + text;
|
1474
|
+
} else {
|
1475
|
+
chars.push(pos);
|
1476
|
+
text += currentChar;
|
1477
|
+
}
|
1478
|
+
|
1479
|
+
if (isRegex) {
|
1480
|
+
result = searchTerm.exec(text);
|
1481
|
+
if (result) {
|
1462
1482
|
matchStartIndex = result.index;
|
1463
1483
|
matchEndIndex = matchStartIndex + result[0].length;
|
1464
|
-
if (
|
1465
|
-
|
1466
|
-
|
1484
|
+
if (insideRegexMatch) {
|
1485
|
+
// Check whether the match is now over
|
1486
|
+
if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) {
|
1487
|
+
returnValue = handleMatch(matchStartIndex, matchEndIndex);
|
1488
|
+
break;
|
1489
|
+
}
|
1490
|
+
} else {
|
1491
|
+
insideRegexMatch = true;
|
1467
1492
|
}
|
1468
|
-
} else {
|
1469
|
-
insideRegexMatch = true;
|
1470
1493
|
}
|
1494
|
+
} else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) {
|
1495
|
+
returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length);
|
1496
|
+
break;
|
1471
1497
|
}
|
1472
|
-
} else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) {
|
1473
|
-
returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length);
|
1474
|
-
break;
|
1475
1498
|
}
|
1499
|
+
|
1500
|
+
// Check whether regex match extends to the end of the range
|
1501
|
+
if (insideRegexMatch) {
|
1502
|
+
returnValue = handleMatch(matchStartIndex, matchEndIndex);
|
1503
|
+
}
|
1504
|
+
it.dispose();
|
1505
|
+
|
1506
|
+
return returnValue;
|
1476
1507
|
}
|
1477
1508
|
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1509
|
+
function createEntryPointFunction(func) {
|
1510
|
+
return function() {
|
1511
|
+
var sessionRunning = !!currentSession;
|
1512
|
+
var session = getSession();
|
1513
|
+
var args = [session].concat( util.toArray(arguments) );
|
1514
|
+
var returnValue = func.apply(this, args);
|
1515
|
+
if (!sessionRunning) {
|
1516
|
+
endSession();
|
1517
|
+
}
|
1518
|
+
return returnValue;
|
1519
|
+
};
|
1481
1520
|
}
|
1482
|
-
it.dispose();
|
1483
1521
|
|
1484
|
-
|
1485
|
-
}
|
1522
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1486
1523
|
|
1487
|
-
|
1488
|
-
return function() {
|
1489
|
-
var sessionRunning = !!currentSession;
|
1490
|
-
var session = getSession();
|
1491
|
-
var args = [session].concat( util.toArray(arguments) );
|
1492
|
-
var returnValue = func.apply(this, args);
|
1493
|
-
if (!sessionRunning) {
|
1494
|
-
endSession();
|
1495
|
-
}
|
1496
|
-
return returnValue;
|
1497
|
-
};
|
1498
|
-
}
|
1524
|
+
// Extensions to the Rangy Range object
|
1499
1525
|
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
count = unit;
|
1518
|
-
unit = CHARACTER;
|
1519
|
-
}
|
1520
|
-
moveOptions = createOptions(moveOptions, defaultMoveOptions);
|
1521
|
-
var characterOptions = createCharacterOptions(moveOptions.characterOptions);
|
1522
|
-
var wordOptions = createWordOptions(moveOptions.wordOptions);
|
1523
|
-
|
1524
|
-
var boundaryIsStart = isStart;
|
1525
|
-
if (collapse) {
|
1526
|
-
boundaryIsStart = (count >= 0);
|
1527
|
-
this.collapse(!boundaryIsStart);
|
1528
|
-
}
|
1529
|
-
var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, characterOptions, wordOptions);
|
1530
|
-
var newPos = moveResult.position;
|
1531
|
-
this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset);
|
1532
|
-
return moveResult.unitsMoved;
|
1533
|
-
}
|
1534
|
-
);
|
1535
|
-
}
|
1526
|
+
function createRangeBoundaryMover(isStart, collapse) {
|
1527
|
+
/*
|
1528
|
+
Unit can be "character" or "word"
|
1529
|
+
Options:
|
1530
|
+
|
1531
|
+
- includeTrailingSpace
|
1532
|
+
- wordRegex
|
1533
|
+
- tokenizer
|
1534
|
+
- collapseSpaceBeforeLineBreak
|
1535
|
+
*/
|
1536
|
+
return createEntryPointFunction(
|
1537
|
+
function(session, unit, count, moveOptions) {
|
1538
|
+
if (typeof count == UNDEF) {
|
1539
|
+
count = unit;
|
1540
|
+
unit = CHARACTER;
|
1541
|
+
}
|
1542
|
+
moveOptions = createNestedOptions(moveOptions, defaultMoveOptions);
|
1536
1543
|
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1544
|
+
var boundaryIsStart = isStart;
|
1545
|
+
if (collapse) {
|
1546
|
+
boundaryIsStart = (count >= 0);
|
1547
|
+
this.collapse(!boundaryIsStart);
|
1548
|
+
}
|
1549
|
+
var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, moveOptions.characterOptions, moveOptions.wordOptions);
|
1550
|
+
var newPos = moveResult.position;
|
1551
|
+
this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset);
|
1552
|
+
return moveResult.unitsMoved;
|
1546
1553
|
}
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1554
|
+
);
|
1555
|
+
}
|
1556
|
+
|
1557
|
+
function createRangeTrimmer(isStart) {
|
1558
|
+
return createEntryPointFunction(
|
1559
|
+
function(session, characterOptions) {
|
1560
|
+
characterOptions = createOptions(characterOptions, defaultCharacterOptions);
|
1561
|
+
var pos;
|
1562
|
+
var it = createRangeCharacterIterator(session, this, characterOptions, !isStart);
|
1563
|
+
var trimCharCount = 0;
|
1564
|
+
while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) {
|
1565
|
+
++trimCharCount;
|
1566
|
+
}
|
1567
|
+
it.dispose();
|
1568
|
+
var trimmed = (trimCharCount > 0);
|
1569
|
+
if (trimmed) {
|
1570
|
+
this[isStart ? "moveStart" : "moveEnd"](
|
1571
|
+
"character",
|
1572
|
+
isStart ? trimCharCount : -trimCharCount,
|
1573
|
+
{ characterOptions: characterOptions }
|
1574
|
+
);
|
1575
|
+
}
|
1576
|
+
return trimmed;
|
1555
1577
|
}
|
1556
|
-
|
1557
|
-
|
1558
|
-
);
|
1559
|
-
}
|
1578
|
+
);
|
1579
|
+
}
|
1560
1580
|
|
1561
|
-
|
1562
|
-
|
1581
|
+
extend(api.rangePrototype, {
|
1582
|
+
moveStart: createRangeBoundaryMover(true, false),
|
1563
1583
|
|
1564
|
-
|
1584
|
+
moveEnd: createRangeBoundaryMover(false, false),
|
1565
1585
|
|
1566
|
-
|
1586
|
+
move: createRangeBoundaryMover(true, true),
|
1567
1587
|
|
1568
|
-
|
1588
|
+
trimStart: createRangeTrimmer(true),
|
1569
1589
|
|
1570
|
-
|
1590
|
+
trimEnd: createRangeTrimmer(false),
|
1571
1591
|
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
}
|
1577
|
-
),
|
1578
|
-
|
1579
|
-
expand: createEntryPointFunction(
|
1580
|
-
function(session, unit, expandOptions) {
|
1581
|
-
var moved = false;
|
1582
|
-
expandOptions = createOptions(expandOptions, defaultExpandOptions);
|
1583
|
-
var characterOptions = createCharacterOptions(expandOptions.characterOptions);
|
1584
|
-
if (!unit) {
|
1585
|
-
unit = CHARACTER;
|
1592
|
+
trim: createEntryPointFunction(
|
1593
|
+
function(session, characterOptions) {
|
1594
|
+
var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions);
|
1595
|
+
return startTrimmed || endTrimmed;
|
1586
1596
|
}
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
var
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
if (this.collapsed) {
|
1598
|
-
endToken = startToken;
|
1599
|
-
} else {
|
1600
|
-
var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions);
|
1601
|
-
endToken = endTokenizedTextProvider.previousStartToken();
|
1602
|
-
}
|
1603
|
-
newEndPos = endToken.chars[endToken.chars.length - 1];
|
1604
|
-
|
1605
|
-
if (!newStartPos.equals(startPos)) {
|
1606
|
-
this.setStart(newStartPos.node, newStartPos.offset);
|
1607
|
-
moved = true;
|
1608
|
-
}
|
1609
|
-
if (newEndPos && !newEndPos.equals(endPos)) {
|
1610
|
-
this.setEnd(newEndPos.node, newEndPos.offset);
|
1611
|
-
moved = true;
|
1597
|
+
),
|
1598
|
+
|
1599
|
+
expand: createEntryPointFunction(
|
1600
|
+
function(session, unit, expandOptions) {
|
1601
|
+
var moved = false;
|
1602
|
+
expandOptions = createNestedOptions(expandOptions, defaultExpandOptions);
|
1603
|
+
var characterOptions = expandOptions.characterOptions;
|
1604
|
+
if (!unit) {
|
1605
|
+
unit = CHARACTER;
|
1612
1606
|
}
|
1607
|
+
if (unit == WORD) {
|
1608
|
+
var wordOptions = expandOptions.wordOptions;
|
1609
|
+
var startPos = session.getRangeBoundaryPosition(this, true);
|
1610
|
+
var endPos = session.getRangeBoundaryPosition(this, false);
|
1611
|
+
|
1612
|
+
var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
|
1613
|
+
var startToken = startTokenizedTextProvider.nextEndToken();
|
1614
|
+
var newStartPos = startToken.chars[0].previousVisible();
|
1615
|
+
var endToken, newEndPos;
|
1616
|
+
|
1617
|
+
if (this.collapsed) {
|
1618
|
+
endToken = startToken;
|
1619
|
+
} else {
|
1620
|
+
var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions);
|
1621
|
+
endToken = endTokenizedTextProvider.previousStartToken();
|
1622
|
+
}
|
1623
|
+
newEndPos = endToken.chars[endToken.chars.length - 1];
|
1613
1624
|
|
1614
|
-
|
1615
|
-
|
1616
|
-
moved =
|
1625
|
+
if (!newStartPos.equals(startPos)) {
|
1626
|
+
this.setStart(newStartPos.node, newStartPos.offset);
|
1627
|
+
moved = true;
|
1617
1628
|
}
|
1618
|
-
if (
|
1619
|
-
|
1629
|
+
if (newEndPos && !newEndPos.equals(endPos)) {
|
1630
|
+
this.setEnd(newEndPos.node, newEndPos.offset);
|
1631
|
+
moved = true;
|
1620
1632
|
}
|
1621
|
-
}
|
1622
|
-
|
1623
|
-
return moved;
|
1624
|
-
} else {
|
1625
|
-
return this.moveEnd(CHARACTER, 1, expandOptions);
|
1626
|
-
}
|
1627
|
-
}
|
1628
|
-
),
|
1629
1633
|
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1634
|
+
if (expandOptions.trim) {
|
1635
|
+
if (expandOptions.trimStart) {
|
1636
|
+
moved = this.trimStart(characterOptions) || moved;
|
1637
|
+
}
|
1638
|
+
if (expandOptions.trimEnd) {
|
1639
|
+
moved = this.trimEnd(characterOptions) || moved;
|
1640
|
+
}
|
1641
|
+
}
|
1636
1642
|
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
containerNode = getBody( this.getDocument() );
|
1643
|
+
return moved;
|
1644
|
+
} else {
|
1645
|
+
return this.moveEnd(CHARACTER, 1, expandOptions);
|
1646
|
+
}
|
1642
1647
|
}
|
1643
|
-
|
1644
|
-
this.collapse(true);
|
1645
|
-
this.moveStart("character", startIndex, moveOptions);
|
1646
|
-
this.collapse(true);
|
1647
|
-
this.moveEnd("character", endIndex - startIndex, moveOptions);
|
1648
|
-
}
|
1649
|
-
),
|
1648
|
+
),
|
1650
1649
|
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
containerNode = getBody( this.getDocument() );
|
1656
|
-
}
|
1657
|
-
var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode);
|
1658
|
-
var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1);
|
1659
|
-
var rangeBetween = this.cloneRange();
|
1660
|
-
var startIndex, endIndex;
|
1661
|
-
if (rangeStartsBeforeNode) {
|
1662
|
-
rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex);
|
1663
|
-
startIndex = -rangeBetween.text(characterOptions).length;
|
1664
|
-
} else {
|
1665
|
-
rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset);
|
1666
|
-
startIndex = rangeBetween.text(characterOptions).length;
|
1650
|
+
text: createEntryPointFunction(
|
1651
|
+
function(session, characterOptions) {
|
1652
|
+
return this.collapsed ?
|
1653
|
+
"" : getRangeCharacters(session, this, createOptions(characterOptions, defaultCharacterOptions)).join("");
|
1667
1654
|
}
|
1668
|
-
|
1669
|
-
|
1670
|
-
return {
|
1671
|
-
start: startIndex,
|
1672
|
-
end: endIndex
|
1673
|
-
};
|
1674
|
-
}
|
1675
|
-
),
|
1655
|
+
),
|
1676
1656
|
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
// Create word options if we're matching whole words only
|
1683
|
-
if (findOptions.wholeWordsOnly) {
|
1684
|
-
findOptions.wordOptions = createWordOptions(findOptions.wordOptions);
|
1685
|
-
|
1686
|
-
// We don't ever want trailing spaces for search results
|
1687
|
-
findOptions.wordOptions.includeTrailingSpace = false;
|
1688
|
-
}
|
1689
|
-
|
1690
|
-
var backward = isDirectionBackward(findOptions.direction);
|
1691
|
-
|
1692
|
-
// Create a range representing the search scope if none was provided
|
1693
|
-
var searchScopeRange = findOptions.withinRange;
|
1694
|
-
if (!searchScopeRange) {
|
1695
|
-
searchScopeRange = api.createRange();
|
1696
|
-
searchScopeRange.selectNodeContents(this.getDocument());
|
1697
|
-
}
|
1698
|
-
|
1699
|
-
// Examine and prepare the search term
|
1700
|
-
var searchTerm = searchTermParam, isRegex = false;
|
1701
|
-
if (typeof searchTerm == "string") {
|
1702
|
-
if (!findOptions.caseSensitive) {
|
1703
|
-
searchTerm = searchTerm.toLowerCase();
|
1657
|
+
selectCharacters: createEntryPointFunction(
|
1658
|
+
function(session, containerNode, startIndex, endIndex, characterOptions) {
|
1659
|
+
var moveOptions = { characterOptions: characterOptions };
|
1660
|
+
if (!containerNode) {
|
1661
|
+
containerNode = getBody( this.getDocument() );
|
1704
1662
|
}
|
1705
|
-
|
1706
|
-
|
1663
|
+
this.selectNodeContents(containerNode);
|
1664
|
+
this.collapse(true);
|
1665
|
+
this.moveStart("character", startIndex, moveOptions);
|
1666
|
+
this.collapse(true);
|
1667
|
+
this.moveEnd("character", endIndex - startIndex, moveOptions);
|
1707
1668
|
}
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1669
|
+
),
|
1670
|
+
|
1671
|
+
// Character indexes are relative to the start of node
|
1672
|
+
toCharacterRange: createEntryPointFunction(
|
1673
|
+
function(session, containerNode, characterOptions) {
|
1674
|
+
if (!containerNode) {
|
1675
|
+
containerNode = getBody( this.getDocument() );
|
1676
|
+
}
|
1677
|
+
var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode);
|
1678
|
+
var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1);
|
1679
|
+
var rangeBetween = this.cloneRange();
|
1680
|
+
var startIndex, endIndex;
|
1681
|
+
if (rangeStartsBeforeNode) {
|
1682
|
+
rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex);
|
1683
|
+
startIndex = -rangeBetween.text(characterOptions).length;
|
1684
|
+
} else {
|
1685
|
+
rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset);
|
1686
|
+
startIndex = rangeBetween.text(characterOptions).length;
|
1687
|
+
}
|
1688
|
+
endIndex = startIndex + this.text(characterOptions).length;
|
1689
|
+
|
1690
|
+
return {
|
1691
|
+
start: startIndex,
|
1692
|
+
end: endIndex
|
1693
|
+
};
|
1718
1694
|
}
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1735
|
-
|
1695
|
+
),
|
1696
|
+
|
1697
|
+
findText: createEntryPointFunction(
|
1698
|
+
function(session, searchTermParam, findOptions) {
|
1699
|
+
// Set up options
|
1700
|
+
findOptions = createNestedOptions(findOptions, defaultFindOptions);
|
1701
|
+
|
1702
|
+
// Create word options if we're matching whole words only
|
1703
|
+
if (findOptions.wholeWordsOnly) {
|
1704
|
+
// We don't ever want trailing spaces for search results
|
1705
|
+
findOptions.wordOptions.includeTrailingSpace = false;
|
1706
|
+
}
|
1707
|
+
|
1708
|
+
var backward = isDirectionBackward(findOptions.direction);
|
1709
|
+
|
1710
|
+
// Create a range representing the search scope if none was provided
|
1711
|
+
var searchScopeRange = findOptions.withinRange;
|
1712
|
+
if (!searchScopeRange) {
|
1713
|
+
searchScopeRange = api.createRange();
|
1714
|
+
searchScopeRange.selectNodeContents(this.getDocument());
|
1715
|
+
}
|
1716
|
+
|
1717
|
+
// Examine and prepare the search term
|
1718
|
+
var searchTerm = searchTermParam, isRegex = false;
|
1719
|
+
if (typeof searchTerm == "string") {
|
1720
|
+
if (!findOptions.caseSensitive) {
|
1721
|
+
searchTerm = searchTerm.toLowerCase();
|
1736
1722
|
}
|
1737
|
-
} else if (findOptions.wrap && !wrappedAround) {
|
1738
|
-
// No result found but we're wrapping around and limiting the scope to the unsearched part of the range
|
1739
|
-
searchScopeRange = searchScopeRange.cloneRange();
|
1740
|
-
pos = session.getRangeBoundaryPosition(searchScopeRange, !backward);
|
1741
|
-
searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward);
|
1742
|
-
wrappedAround = true;
|
1743
1723
|
} else {
|
1744
|
-
|
1745
|
-
|
1724
|
+
isRegex = true;
|
1725
|
+
}
|
1726
|
+
|
1727
|
+
var initialPos = session.getRangeBoundaryPosition(this, !backward);
|
1728
|
+
|
1729
|
+
// Adjust initial position if it lies outside the search scope
|
1730
|
+
var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset);
|
1731
|
+
|
1732
|
+
if (comparison === -1) {
|
1733
|
+
initialPos = session.getRangeBoundaryPosition(searchScopeRange, true);
|
1734
|
+
} else if (comparison === 1) {
|
1735
|
+
initialPos = session.getRangeBoundaryPosition(searchScopeRange, false);
|
1736
|
+
}
|
1737
|
+
|
1738
|
+
var pos = initialPos;
|
1739
|
+
var wrappedAround = false;
|
1740
|
+
|
1741
|
+
// Try to find a match and ignore invalid ones
|
1742
|
+
var findResult;
|
1743
|
+
while (true) {
|
1744
|
+
findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions);
|
1745
|
+
|
1746
|
+
if (findResult) {
|
1747
|
+
if (findResult.valid) {
|
1748
|
+
this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset);
|
1749
|
+
return true;
|
1750
|
+
} else {
|
1751
|
+
// We've found a match that is not a whole word, so we carry on searching from the point immediately
|
1752
|
+
// after the match
|
1753
|
+
pos = backward ? findResult.startPos : findResult.endPos;
|
1754
|
+
}
|
1755
|
+
} else if (findOptions.wrap && !wrappedAround) {
|
1756
|
+
// No result found but we're wrapping around and limiting the scope to the unsearched part of the range
|
1757
|
+
searchScopeRange = searchScopeRange.cloneRange();
|
1758
|
+
pos = session.getRangeBoundaryPosition(searchScopeRange, !backward);
|
1759
|
+
searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward);
|
1760
|
+
wrappedAround = true;
|
1761
|
+
} else {
|
1762
|
+
// Nothing found and we can't wrap around, so we're done
|
1763
|
+
return false;
|
1764
|
+
}
|
1746
1765
|
}
|
1747
1766
|
}
|
1767
|
+
),
|
1768
|
+
|
1769
|
+
pasteHtml: function(html) {
|
1770
|
+
this.deleteContents();
|
1771
|
+
if (html) {
|
1772
|
+
var frag = this.createContextualFragment(html);
|
1773
|
+
var lastChild = frag.lastChild;
|
1774
|
+
this.insertNode(frag);
|
1775
|
+
this.collapseAfter(lastChild);
|
1776
|
+
}
|
1748
1777
|
}
|
1749
|
-
)
|
1750
|
-
|
1751
|
-
pasteHtml: function(html) {
|
1752
|
-
this.deleteContents();
|
1753
|
-
if (html) {
|
1754
|
-
var frag = this.createContextualFragment(html);
|
1755
|
-
var lastChild = frag.lastChild;
|
1756
|
-
this.insertNode(frag);
|
1757
|
-
this.collapseAfter(lastChild);
|
1758
|
-
}
|
1759
|
-
}
|
1760
|
-
});
|
1778
|
+
});
|
1761
1779
|
|
1762
|
-
|
1780
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1763
1781
|
|
1764
|
-
|
1782
|
+
// Extensions to the Rangy Selection object
|
1765
1783
|
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1784
|
+
function createSelectionTrimmer(methodName) {
|
1785
|
+
return createEntryPointFunction(
|
1786
|
+
function(session, characterOptions) {
|
1787
|
+
var trimmed = false;
|
1788
|
+
this.changeEachRange(function(range) {
|
1789
|
+
trimmed = range[methodName](characterOptions) || trimmed;
|
1790
|
+
});
|
1791
|
+
return trimmed;
|
1792
|
+
}
|
1793
|
+
);
|
1794
|
+
}
|
1777
1795
|
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1796
|
+
extend(api.selectionPrototype, {
|
1797
|
+
expand: createEntryPointFunction(
|
1798
|
+
function(session, unit, expandOptions) {
|
1799
|
+
this.changeEachRange(function(range) {
|
1800
|
+
range.expand(unit, expandOptions);
|
1801
|
+
});
|
1802
|
+
}
|
1803
|
+
),
|
1804
|
+
|
1805
|
+
move: createEntryPointFunction(
|
1806
|
+
function(session, unit, count, options) {
|
1807
|
+
var unitsMoved = 0;
|
1808
|
+
if (this.focusNode) {
|
1809
|
+
this.collapse(this.focusNode, this.focusOffset);
|
1810
|
+
var range = this.getRangeAt(0);
|
1811
|
+
if (!options) {
|
1812
|
+
options = {};
|
1813
|
+
}
|
1814
|
+
options.characterOptions = createOptions(options.characterOptions, defaultCaretCharacterOptions);
|
1815
|
+
unitsMoved = range.move(unit, count, options);
|
1816
|
+
this.setSingleRange(range);
|
1795
1817
|
}
|
1796
|
-
|
1797
|
-
unitsMoved = range.move(unit, count, options);
|
1798
|
-
this.setSingleRange(range);
|
1818
|
+
return unitsMoved;
|
1799
1819
|
}
|
1800
|
-
|
1801
|
-
}
|
1802
|
-
),
|
1820
|
+
),
|
1803
1821
|
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1822
|
+
trimStart: createSelectionTrimmer("trimStart"),
|
1823
|
+
trimEnd: createSelectionTrimmer("trimEnd"),
|
1824
|
+
trim: createSelectionTrimmer("trim"),
|
1807
1825
|
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
1826
|
+
selectCharacters: createEntryPointFunction(
|
1827
|
+
function(session, containerNode, startIndex, endIndex, direction, characterOptions) {
|
1828
|
+
var range = api.createRange(containerNode);
|
1829
|
+
range.selectCharacters(containerNode, startIndex, endIndex, characterOptions);
|
1830
|
+
this.setSingleRange(range, direction);
|
1831
|
+
}
|
1832
|
+
),
|
1815
1833
|
|
1816
|
-
|
1817
|
-
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1834
|
+
saveCharacterRanges: createEntryPointFunction(
|
1835
|
+
function(session, containerNode, characterOptions) {
|
1836
|
+
var ranges = this.getAllRanges(), rangeCount = ranges.length;
|
1837
|
+
var rangeInfos = [];
|
1838
|
+
|
1839
|
+
var backward = rangeCount == 1 && this.isBackward();
|
1840
|
+
|
1841
|
+
for (var i = 0, len = ranges.length; i < len; ++i) {
|
1842
|
+
rangeInfos[i] = {
|
1843
|
+
characterRange: ranges[i].toCharacterRange(containerNode, characterOptions),
|
1844
|
+
backward: backward,
|
1845
|
+
characterOptions: characterOptions
|
1846
|
+
};
|
1847
|
+
}
|
1848
|
+
|
1849
|
+
return rangeInfos;
|
1829
1850
|
}
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions);
|
1843
|
-
this.addRange(range, rangeInfo.backward);
|
1851
|
+
),
|
1852
|
+
|
1853
|
+
restoreCharacterRanges: createEntryPointFunction(
|
1854
|
+
function(session, containerNode, saved) {
|
1855
|
+
this.removeAllRanges();
|
1856
|
+
for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) {
|
1857
|
+
rangeInfo = saved[i];
|
1858
|
+
characterRange = rangeInfo.characterRange;
|
1859
|
+
range = api.createRange(containerNode);
|
1860
|
+
range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions);
|
1861
|
+
this.addRange(range, rangeInfo.backward);
|
1862
|
+
}
|
1844
1863
|
}
|
1845
|
-
|
1846
|
-
),
|
1864
|
+
),
|
1847
1865
|
|
1848
|
-
|
1849
|
-
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
1866
|
+
text: createEntryPointFunction(
|
1867
|
+
function(session, characterOptions) {
|
1868
|
+
var rangeTexts = [];
|
1869
|
+
for (var i = 0, len = this.rangeCount; i < len; ++i) {
|
1870
|
+
rangeTexts[i] = this.getRangeAt(i).text(characterOptions);
|
1871
|
+
}
|
1872
|
+
return rangeTexts.join("");
|
1853
1873
|
}
|
1854
|
-
|
1855
|
-
|
1856
|
-
)
|
1857
|
-
});
|
1874
|
+
)
|
1875
|
+
});
|
1858
1876
|
|
1859
|
-
|
1860
|
-
|
1861
|
-
// Extensions to the core rangy object
|
1862
|
-
|
1863
|
-
api.innerText = function(el, characterOptions) {
|
1864
|
-
var range = api.createRange(el);
|
1865
|
-
range.selectNodeContents(el);
|
1866
|
-
var text = range.text(characterOptions);
|
1867
|
-
range.detach();
|
1868
|
-
return text;
|
1869
|
-
};
|
1870
|
-
|
1871
|
-
api.createWordIterator = function(startNode, startOffset, iteratorOptions) {
|
1872
|
-
var session = getSession();
|
1873
|
-
iteratorOptions = createOptions(iteratorOptions, defaultWordIteratorOptions);
|
1874
|
-
var characterOptions = createCharacterOptions(iteratorOptions.characterOptions);
|
1875
|
-
var wordOptions = createWordOptions(iteratorOptions.wordOptions);
|
1876
|
-
var startPos = session.getPosition(startNode, startOffset);
|
1877
|
-
var tokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
|
1878
|
-
var backward = isDirectionBackward(iteratorOptions.direction);
|
1879
|
-
|
1880
|
-
return {
|
1881
|
-
next: function() {
|
1882
|
-
return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken();
|
1883
|
-
},
|
1877
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1884
1878
|
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1879
|
+
// Extensions to the core rangy object
|
1880
|
+
|
1881
|
+
api.innerText = function(el, characterOptions) {
|
1882
|
+
var range = api.createRange(el);
|
1883
|
+
range.selectNodeContents(el);
|
1884
|
+
var text = range.text(characterOptions);
|
1885
|
+
return text;
|
1889
1886
|
};
|
1890
|
-
};
|
1891
1887
|
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1888
|
+
api.createWordIterator = function(startNode, startOffset, iteratorOptions) {
|
1889
|
+
var session = getSession();
|
1890
|
+
iteratorOptions = createNestedOptions(iteratorOptions, defaultWordIteratorOptions);
|
1891
|
+
var startPos = session.getPosition(startNode, startOffset);
|
1892
|
+
var tokenizedTextProvider = createTokenizedTextProvider(startPos, iteratorOptions.characterOptions, iteratorOptions.wordOptions);
|
1893
|
+
var backward = isDirectionBackward(iteratorOptions.direction);
|
1894
|
+
|
1895
|
+
return {
|
1896
|
+
next: function() {
|
1897
|
+
return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken();
|
1898
|
+
},
|
1899
1899
|
|
1900
|
-
|
1900
|
+
dispose: function() {
|
1901
|
+
tokenizedTextProvider.dispose();
|
1902
|
+
this.next = function() {};
|
1903
|
+
}
|
1904
|
+
};
|
1905
|
+
};
|
1901
1906
|
|
1902
|
-
|
1903
|
-
isBlockNode: isBlockNode,
|
1904
|
-
isCollapsedWhitespaceNode: isCollapsedWhitespaceNode,
|
1907
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1905
1908
|
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1909
|
+
api.noMutation = function(func) {
|
1910
|
+
var session = getSession();
|
1911
|
+
func(session);
|
1912
|
+
endSession();
|
1913
|
+
};
|
1914
|
+
|
1915
|
+
api.noMutation.createEntryPointFunction = createEntryPointFunction;
|
1916
|
+
|
1917
|
+
api.textRange = {
|
1918
|
+
isBlockNode: isBlockNode,
|
1919
|
+
isCollapsedWhitespaceNode: isCollapsedWhitespaceNode,
|
1920
|
+
|
1921
|
+
createPosition: createEntryPointFunction(
|
1922
|
+
function(session, node, offset) {
|
1923
|
+
return session.getPosition(node, offset);
|
1924
|
+
}
|
1925
|
+
)
|
1926
|
+
};
|
1927
|
+
});
|
1928
|
+
|
1929
|
+
return rangy;
|
1930
|
+
}, this);
|