wysihtml-rails 0.5.0.beta13 → 0.5.0.beta14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a79f93a11f62b9a5968cc94d9c2c7f8351cb9b7e
4
- data.tar.gz: 49601e86e3255bcc8bbc4b15f79f2d828110da70
3
+ metadata.gz: 79b9d17d508350d2026a68b4290a18f50cf80f86
4
+ data.tar.gz: 258e583602cb8db2d76294344b1d4e30360e2cf0
5
5
  SHA512:
6
- metadata.gz: b86153a882e2772ac3082297ab25a72e863d8a528573c49e2e5676137575afc7d033aa019ec0453b8dafda8f0b9e112a4ee3aa4d7f5e58994c780f40de6565e8
7
- data.tar.gz: b73e798b9da47ad9080e49c9adf227839dd453f57e80149d4f2ec1b7f8afcf24640d7d83b293eb0cc4ab534aff941dbe46700d3e62bc4d4cfcb23f3582f4d859
6
+ metadata.gz: 079c292c5b9ac8de4dd6cb8f93f4e52d8d341bbdd7f5c58cb438f38293fb47398b46472372e241bcf56fd70f9777603a4ac48b8b7cfa9e07bd940ac254be6ad1
7
+ data.tar.gz: 1c4fff75b28fdfa298e9d37b8e657bb908f0bc088df289673bd43ec1890d717b4b987f23ddd61d685c00cf356393e5b4cc2b4b841809c33f24f4cd6c74a9fdd8
@@ -1,5 +1,5 @@
1
1
  module Wysihtml
2
2
  module Rails
3
- VERSION = "0.5.0.beta13"
3
+ VERSION = "0.5.0.beta14"
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license wysihtml v0.5.0-beta13
2
+ * @license wysihtml v0.5.0-beta14
3
3
  * https://github.com/Voog/wysihtml
4
4
  *
5
5
  * Author: Christopher Blum (https://github.com/tiff)
@@ -10,7 +10,7 @@
10
10
  *
11
11
  */
12
12
  var wysihtml5 = {
13
- version: "0.5.0-beta13",
13
+ version: "0.5.0-beta14",
14
14
 
15
15
  // namespaces
16
16
  commands: {},
@@ -422,6 +422,18 @@ var wysihtml5 = {
422
422
  prevTxt = texts.shift(),
423
423
  curText = prevTxt ? texts.shift() : null;
424
424
 
425
+ if (felement && felement.nodeType === 3) {
426
+ fnode = felement;
427
+ foffset = felement.nodeValue.length;
428
+ felement = undefined;
429
+ }
430
+
431
+ if (aelement && aelement.nodeType === 3) {
432
+ anode = aelement;
433
+ aoffset = 0;
434
+ aelement = undefined;
435
+ }
436
+
425
437
  if ((anode === fnode && foffset < aoffset) || (anode !== fnode && (anode.compareDocumentPosition(fnode) & Node.DOCUMENT_POSITION_PRECEDING) && !(anode.compareDocumentPosition(fnode) & Node.DOCUMENT_POSITION_CONTAINS))) {
426
438
  fnode = [anode, anode = fnode][0];
427
439
  foffset = [aoffset, aoffset = foffset][0];
@@ -463,9 +475,18 @@ var wysihtml5 = {
463
475
  };
464
476
  Node.prototype.normalize = nf;
465
477
  };
466
-
467
- if ("Node" in window && "normalize" in Node.prototype && normalizeHasCaretError()) {
468
- normalizeFix();
478
+
479
+ var F = function() {
480
+ window.removeEventListener("load", F);
481
+ if ("Node" in window && "normalize" in Node.prototype && normalizeHasCaretError()) {
482
+ normalizeFix();
483
+ }
484
+ };
485
+
486
+ if (doc.readyState !== "complete") {
487
+ window.addEventListener("load", F);
488
+ } else {
489
+ F();
469
490
  }
470
491
  };
471
492
 
@@ -4139,181 +4160,2110 @@ wysihtml5.polyfills(window, document);
4139
4160
  range = api.createRange(this.win.document);
4140
4161
  range.setStartAndEnd(node, offset);
4141
4162
  }
4142
- this.setSingleRange(range, this.isBackward());
4143
- };
4144
- }
4163
+ this.setSingleRange(range, this.isBackward());
4164
+ };
4165
+ }
4166
+
4167
+ selProto.setStart = createStartOrEndSetter(true);
4168
+ selProto.setEnd = createStartOrEndSetter(false);
4169
+
4170
+ // Add select() method to Range prototype. Any existing selection will be removed.
4171
+ api.rangePrototype.select = function(direction) {
4172
+ getSelection( this.getDocument() ).setSingleRange(this, direction);
4173
+ };
4174
+
4175
+ selProto.changeEachRange = function(func) {
4176
+ var ranges = [];
4177
+ var backward = this.isBackward();
4178
+
4179
+ this.eachRange(function(range) {
4180
+ func(range);
4181
+ ranges.push(range);
4182
+ });
4183
+
4184
+ this.removeAllRanges();
4185
+ if (backward && ranges.length == 1) {
4186
+ this.addRange(ranges[0], "backward");
4187
+ } else {
4188
+ this.setRanges(ranges);
4189
+ }
4190
+ };
4191
+
4192
+ selProto.containsNode = function(node, allowPartial) {
4193
+ return this.eachRange( function(range) {
4194
+ return range.containsNode(node, allowPartial);
4195
+ }, true ) || false;
4196
+ };
4197
+
4198
+ selProto.getBookmark = function(containerNode) {
4199
+ return {
4200
+ backward: this.isBackward(),
4201
+ rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
4202
+ };
4203
+ };
4204
+
4205
+ selProto.moveToBookmark = function(bookmark) {
4206
+ var selRanges = [];
4207
+ for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
4208
+ range = api.createRange(this.win);
4209
+ range.moveToBookmark(rangeBookmark);
4210
+ selRanges.push(range);
4211
+ }
4212
+ if (bookmark.backward) {
4213
+ this.setSingleRange(selRanges[0], "backward");
4214
+ } else {
4215
+ this.setRanges(selRanges);
4216
+ }
4217
+ };
4218
+
4219
+ selProto.saveRanges = function() {
4220
+ return {
4221
+ backward: this.isBackward(),
4222
+ ranges: this.callMethodOnEachRange("cloneRange")
4223
+ };
4224
+ };
4225
+
4226
+ selProto.restoreRanges = function(selRanges) {
4227
+ this.removeAllRanges();
4228
+ for (var i = 0, range; range = selRanges.ranges[i]; ++i) {
4229
+ this.addRange(range, (selRanges.backward && i == 0));
4230
+ }
4231
+ };
4232
+
4233
+ selProto.toHtml = function() {
4234
+ var rangeHtmls = [];
4235
+ this.eachRange(function(range) {
4236
+ rangeHtmls.push( DomRange.toHtml(range) );
4237
+ });
4238
+ return rangeHtmls.join("");
4239
+ };
4240
+
4241
+ if (features.implementsTextRange) {
4242
+ selProto.getNativeTextRange = function() {
4243
+ var sel, textRange;
4244
+ if ( (sel = this.docSelection) ) {
4245
+ var range = sel.createRange();
4246
+ if (isTextRange(range)) {
4247
+ return range;
4248
+ } else {
4249
+ throw module.createError("getNativeTextRange: selection is a control selection");
4250
+ }
4251
+ } else if (this.rangeCount > 0) {
4252
+ return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
4253
+ } else {
4254
+ throw module.createError("getNativeTextRange: selection contains no range");
4255
+ }
4256
+ };
4257
+ }
4258
+
4259
+ function inspect(sel) {
4260
+ var rangeInspects = [];
4261
+ var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
4262
+ var focus = new DomPosition(sel.focusNode, sel.focusOffset);
4263
+ var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
4264
+
4265
+ if (typeof sel.rangeCount != "undefined") {
4266
+ for (var i = 0, len = sel.rangeCount; i < len; ++i) {
4267
+ rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
4268
+ }
4269
+ }
4270
+ return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
4271
+ ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
4272
+ }
4273
+
4274
+ selProto.getName = function() {
4275
+ return "WrappedSelection";
4276
+ };
4277
+
4278
+ selProto.inspect = function() {
4279
+ return inspect(this);
4280
+ };
4281
+
4282
+ selProto.detach = function() {
4283
+ actOnCachedSelection(this.win, "delete");
4284
+ deleteProperties(this);
4285
+ };
4286
+
4287
+ WrappedSelection.detachAll = function() {
4288
+ actOnCachedSelection(null, "deleteAll");
4289
+ };
4290
+
4291
+ WrappedSelection.inspect = inspect;
4292
+ WrappedSelection.isDirectionBackward = isDirectionBackward;
4293
+
4294
+ api.Selection = WrappedSelection;
4295
+
4296
+ api.selectionPrototype = selProto;
4297
+
4298
+ api.addShimListener(function(win) {
4299
+ if (typeof win.getSelection == "undefined") {
4300
+ win.getSelection = function() {
4301
+ return getSelection(win);
4302
+ };
4303
+ }
4304
+ win = null;
4305
+ });
4306
+ });
4307
+
4308
+
4309
+ /*----------------------------------------------------------------------------------------------------------------*/
4310
+
4311
+ // Wait for document to load before initializing
4312
+ var docReady = false;
4313
+
4314
+ var loadHandler = function(e) {
4315
+ if (!docReady) {
4316
+ docReady = true;
4317
+ if (!api.initialized && api.config.autoInitialize) {
4318
+ init();
4319
+ }
4320
+ }
4321
+ };
4322
+
4323
+ if (isBrowser) {
4324
+ // Test whether the document has already been loaded and initialize immediately if so
4325
+ if (document.readyState == "complete") {
4326
+ loadHandler();
4327
+ } else {
4328
+ if (isHostMethod(document, "addEventListener")) {
4329
+ document.addEventListener("DOMContentLoaded", loadHandler, false);
4330
+ }
4331
+
4332
+ // Add a fallback in case the DOMContentLoaded event isn't supported
4333
+ addListener(window, "load", loadHandler);
4334
+ }
4335
+ }
4336
+
4337
+ return api;
4338
+ }, this);;/**
4339
+ * Text range module for Rangy.
4340
+ * Text-based manipulation and searching of ranges and selections.
4341
+ *
4342
+ * Features
4343
+ *
4344
+ * - Ability to move range boundaries by character or word offsets
4345
+ * - Customizable word tokenizer
4346
+ * - Ignores text nodes inside <script> or <style> elements or those hidden by CSS display and visibility properties
4347
+ * - Range findText method to search for text or regex within the page or within a range. Flags for whole words and case
4348
+ * sensitivity
4349
+ * - Selection and range save/restore as text offsets within a node
4350
+ * - Methods to return visible text within a range or selection
4351
+ * - innerText method for elements
4352
+ *
4353
+ * References
4354
+ *
4355
+ * https://www.w3.org/Bugs/Public/show_bug.cgi?id=13145
4356
+ * http://aryeh.name/spec/innertext/innertext.html
4357
+ * http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html
4358
+ *
4359
+ * Part of Rangy, a cross-browser JavaScript range and selection library
4360
+ * https://github.com/timdown/rangy
4361
+ *
4362
+ * Depends on Rangy core.
4363
+ *
4364
+ * Copyright 2015, Tim Down
4365
+ * Licensed under the MIT license.
4366
+ * Version: 1.3.0
4367
+ * Build date: 10 May 2015
4368
+ */
4369
+
4370
+ /**
4371
+ * Problem: handling of trailing spaces before line breaks is handled inconsistently between browsers.
4372
+ *
4373
+ * First, a <br>: this is relatively simple. For the following HTML:
4374
+ *
4375
+ * 1 <br>2
4376
+ *
4377
+ * - IE and WebKit render the space, include it in the selection (i.e. when the content is selected and pasted into a
4378
+ * textarea, the space is present) and allow the caret to be placed after it.
4379
+ * - Firefox does not acknowledge the space in the selection but it is possible to place the caret after it.
4380
+ * - Opera does not render the space but has two separate caret positions on either side of the space (left and right
4381
+ * arrow keys show this) and includes the space in the selection.
4382
+ *
4383
+ * The other case is the line break or breaks implied by block elements. For the following HTML:
4384
+ *
4385
+ * <p>1 </p><p>2<p>
4386
+ *
4387
+ * - WebKit does not acknowledge the space in any way
4388
+ * - Firefox, IE and Opera as per <br>
4389
+ *
4390
+ * One more case is trailing spaces before line breaks in elements with white-space: pre-line. For the following HTML:
4391
+ *
4392
+ * <p style="white-space: pre-line">1
4393
+ * 2</p>
4394
+ *
4395
+ * - Firefox and WebKit include the space in caret positions
4396
+ * - IE does not support pre-line up to and including version 9
4397
+ * - Opera ignores the space
4398
+ * - Trailing space only renders if there is a non-collapsed character in the line
4399
+ *
4400
+ * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be
4401
+ * feature-tested
4402
+ */
4403
+ (function(factory, root) {
4404
+ if (typeof define == "function" && define.amd) {
4405
+ // AMD. Register as an anonymous module with a dependency on Rangy.
4406
+ define(["./rangy-core"], factory);
4407
+ } else if (typeof module != "undefined" && typeof exports == "object") {
4408
+ // Node/CommonJS style
4409
+ module.exports = factory( require("rangy") );
4410
+ } else {
4411
+ // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
4412
+ factory(root.rangy);
4413
+ }
4414
+ })(function(rangy) {
4415
+ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) {
4416
+ var UNDEF = "undefined";
4417
+ var CHARACTER = "character", WORD = "word";
4418
+ var dom = api.dom, util = api.util;
4419
+ var extend = util.extend;
4420
+ var createOptions = util.createOptions;
4421
+ var getBody = dom.getBody;
4422
+
4423
+
4424
+ var spacesRegex = /^[ \t\f\r\n]+$/;
4425
+ var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/;
4426
+ var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/;
4427
+ var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/;
4428
+ var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/;
4429
+
4430
+ var defaultLanguage = "en";
4431
+
4432
+ var isDirectionBackward = api.Selection.isDirectionBackward;
4433
+
4434
+ // Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit,
4435
+ // but not other browsers). Also test whether trailing spaces before <br> elements are collapsed.
4436
+ var trailingSpaceInBlockCollapses = false;
4437
+ var trailingSpaceBeforeBrCollapses = false;
4438
+ var trailingSpaceBeforeBlockCollapses = false;
4439
+ var trailingSpaceBeforeLineBreakInPreLineCollapses = true;
4440
+
4441
+ (function() {
4442
+ var el = dom.createTestElement(document, "<p>1 </p><p></p>", true);
4443
+ var p = el.firstChild;
4444
+ var sel = api.getSelection();
4445
+ sel.collapse(p.lastChild, 2);
4446
+ sel.setStart(p.firstChild, 0);
4447
+ trailingSpaceInBlockCollapses = ("" + sel).length == 1;
4448
+
4449
+ el.innerHTML = "1 <br />";
4450
+ sel.collapse(el, 2);
4451
+ sel.setStart(el.firstChild, 0);
4452
+ trailingSpaceBeforeBrCollapses = ("" + sel).length == 1;
4453
+
4454
+ el.innerHTML = "1 <p>1</p>";
4455
+ sel.collapse(el, 2);
4456
+ sel.setStart(el.firstChild, 0);
4457
+ trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1;
4458
+
4459
+ dom.removeNode(el);
4460
+ sel.removeAllRanges();
4461
+ })();
4462
+
4463
+ /*----------------------------------------------------------------------------------------------------------------*/
4464
+
4465
+ // This function must create word and non-word tokens for the whole of the text supplied to it
4466
+ function defaultTokenizer(chars, wordOptions) {
4467
+ var word = chars.join(""), result, tokenRanges = [];
4468
+
4469
+ function createTokenRange(start, end, isWord) {
4470
+ tokenRanges.push( { start: start, end: end, isWord: isWord } );
4471
+ }
4472
+
4473
+ // Match words and mark characters
4474
+ var lastWordEnd = 0, wordStart, wordEnd;
4475
+ while ( (result = wordOptions.wordRegex.exec(word)) ) {
4476
+ wordStart = result.index;
4477
+ wordEnd = wordStart + result[0].length;
4478
+
4479
+ // Create token for non-word characters preceding this word
4480
+ if (wordStart > lastWordEnd) {
4481
+ createTokenRange(lastWordEnd, wordStart, false);
4482
+ }
4483
+
4484
+ // Get trailing space characters for word
4485
+ if (wordOptions.includeTrailingSpace) {
4486
+ while ( nonLineBreakWhiteSpaceRegex.test(chars[wordEnd]) ) {
4487
+ ++wordEnd;
4488
+ }
4489
+ }
4490
+ createTokenRange(wordStart, wordEnd, true);
4491
+ lastWordEnd = wordEnd;
4492
+ }
4493
+
4494
+ // Create token for trailing non-word characters, if any exist
4495
+ if (lastWordEnd < chars.length) {
4496
+ createTokenRange(lastWordEnd, chars.length, false);
4497
+ }
4498
+
4499
+ return tokenRanges;
4500
+ }
4501
+
4502
+ function convertCharRangeToToken(chars, tokenRange) {
4503
+ var tokenChars = chars.slice(tokenRange.start, tokenRange.end);
4504
+ var token = {
4505
+ isWord: tokenRange.isWord,
4506
+ chars: tokenChars,
4507
+ toString: function() {
4508
+ return tokenChars.join("");
4509
+ }
4510
+ };
4511
+ for (var i = 0, len = tokenChars.length; i < len; ++i) {
4512
+ tokenChars[i].token = token;
4513
+ }
4514
+ return token;
4515
+ }
4516
+
4517
+ function tokenize(chars, wordOptions, tokenizer) {
4518
+ var tokenRanges = tokenizer(chars, wordOptions);
4519
+ var tokens = [];
4520
+ for (var i = 0, tokenRange; tokenRange = tokenRanges[i++]; ) {
4521
+ tokens.push( convertCharRangeToToken(chars, tokenRange) );
4522
+ }
4523
+ return tokens;
4524
+ }
4525
+
4526
+ var defaultCharacterOptions = {
4527
+ includeBlockContentTrailingSpace: true,
4528
+ includeSpaceBeforeBr: true,
4529
+ includeSpaceBeforeBlock: true,
4530
+ includePreLineTrailingSpace: true,
4531
+ ignoreCharacters: ""
4532
+ };
4533
+
4534
+ function normalizeIgnoredCharacters(ignoredCharacters) {
4535
+ // Check if character is ignored
4536
+ var ignoredChars = ignoredCharacters || "";
4537
+
4538
+ // Normalize ignored characters into a string consisting of characters in ascending order of character code
4539
+ var ignoredCharsArray = (typeof ignoredChars == "string") ? ignoredChars.split("") : ignoredChars;
4540
+ ignoredCharsArray.sort(function(char1, char2) {
4541
+ return char1.charCodeAt(0) - char2.charCodeAt(0);
4542
+ });
4543
+
4544
+ /// Convert back to a string and remove duplicates
4545
+ return ignoredCharsArray.join("").replace(/(.)\1+/g, "$1");
4546
+ }
4547
+
4548
+ var defaultCaretCharacterOptions = {
4549
+ includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses,
4550
+ includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses,
4551
+ includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses,
4552
+ includePreLineTrailingSpace: true
4553
+ };
4554
+
4555
+ var defaultWordOptions = {
4556
+ "en": {
4557
+ wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi,
4558
+ includeTrailingSpace: false,
4559
+ tokenizer: defaultTokenizer
4560
+ }
4561
+ };
4562
+
4563
+ var defaultFindOptions = {
4564
+ caseSensitive: false,
4565
+ withinRange: null,
4566
+ wholeWordsOnly: false,
4567
+ wrap: false,
4568
+ direction: "forward",
4569
+ wordOptions: null,
4570
+ characterOptions: null
4571
+ };
4572
+
4573
+ var defaultMoveOptions = {
4574
+ wordOptions: null,
4575
+ characterOptions: null
4576
+ };
4577
+
4578
+ var defaultExpandOptions = {
4579
+ wordOptions: null,
4580
+ characterOptions: null,
4581
+ trim: false,
4582
+ trimStart: true,
4583
+ trimEnd: true
4584
+ };
4585
+
4586
+ var defaultWordIteratorOptions = {
4587
+ wordOptions: null,
4588
+ characterOptions: null,
4589
+ direction: "forward"
4590
+ };
4591
+
4592
+ function createWordOptions(options) {
4593
+ var lang, defaults;
4594
+ if (!options) {
4595
+ return defaultWordOptions[defaultLanguage];
4596
+ } else {
4597
+ lang = options.language || defaultLanguage;
4598
+ defaults = {};
4599
+ extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]);
4600
+ extend(defaults, options);
4601
+ return defaults;
4602
+ }
4603
+ }
4604
+
4605
+ function createNestedOptions(optionsParam, defaults) {
4606
+ var options = createOptions(optionsParam, defaults);
4607
+ if (defaults.hasOwnProperty("wordOptions")) {
4608
+ options.wordOptions = createWordOptions(options.wordOptions);
4609
+ }
4610
+ if (defaults.hasOwnProperty("characterOptions")) {
4611
+ options.characterOptions = createOptions(options.characterOptions, defaultCharacterOptions);
4612
+ }
4613
+ return options;
4614
+ }
4615
+
4616
+ /*----------------------------------------------------------------------------------------------------------------*/
4617
+
4618
+ /* DOM utility functions */
4619
+ var getComputedStyleProperty = dom.getComputedStyleProperty;
4620
+
4621
+ // Create cachable versions of DOM functions
4622
+
4623
+ // Test for old IE's incorrect display properties
4624
+ var tableCssDisplayBlock;
4625
+ (function() {
4626
+ var table = document.createElement("table");
4627
+ var body = getBody(document);
4628
+ body.appendChild(table);
4629
+ tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block");
4630
+ body.removeChild(table);
4631
+ })();
4632
+
4633
+ var defaultDisplayValueForTag = {
4634
+ table: "table",
4635
+ caption: "table-caption",
4636
+ colgroup: "table-column-group",
4637
+ col: "table-column",
4638
+ thead: "table-header-group",
4639
+ tbody: "table-row-group",
4640
+ tfoot: "table-footer-group",
4641
+ tr: "table-row",
4642
+ td: "table-cell",
4643
+ th: "table-cell"
4644
+ };
4645
+
4646
+ // Corrects IE's "block" value for table-related elements
4647
+ function getComputedDisplay(el, win) {
4648
+ var display = getComputedStyleProperty(el, "display", win);
4649
+ var tagName = el.tagName.toLowerCase();
4650
+ return (display == "block" &&
4651
+ tableCssDisplayBlock &&
4652
+ defaultDisplayValueForTag.hasOwnProperty(tagName)) ?
4653
+ defaultDisplayValueForTag[tagName] : display;
4654
+ }
4655
+
4656
+ function isHidden(node) {
4657
+ var ancestors = getAncestorsAndSelf(node);
4658
+ for (var i = 0, len = ancestors.length; i < len; ++i) {
4659
+ if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") {
4660
+ return true;
4661
+ }
4662
+ }
4663
+
4664
+ return false;
4665
+ }
4666
+
4667
+ function isVisibilityHiddenTextNode(textNode) {
4668
+ var el;
4669
+ return textNode.nodeType == 3 &&
4670
+ (el = textNode.parentNode) &&
4671
+ getComputedStyleProperty(el, "visibility") == "hidden";
4672
+ }
4673
+
4674
+ /*----------------------------------------------------------------------------------------------------------------*/
4675
+
4676
+
4677
+ // "A block node is either an Element whose "display" property does not have
4678
+ // resolved value "inline" or "inline-block" or "inline-table" or "none", or a
4679
+ // Document, or a DocumentFragment."
4680
+ function isBlockNode(node) {
4681
+ return node &&
4682
+ ((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node))) ||
4683
+ node.nodeType == 9 || node.nodeType == 11);
4684
+ }
4685
+
4686
+ function getLastDescendantOrSelf(node) {
4687
+ var lastChild = node.lastChild;
4688
+ return lastChild ? getLastDescendantOrSelf(lastChild) : node;
4689
+ }
4690
+
4691
+ function containsPositions(node) {
4692
+ return dom.isCharacterDataNode(node) ||
4693
+ !/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName);
4694
+ }
4695
+
4696
+ function getAncestors(node) {
4697
+ var ancestors = [];
4698
+ while (node.parentNode) {
4699
+ ancestors.unshift(node.parentNode);
4700
+ node = node.parentNode;
4701
+ }
4702
+ return ancestors;
4703
+ }
4704
+
4705
+ function getAncestorsAndSelf(node) {
4706
+ return getAncestors(node).concat([node]);
4707
+ }
4708
+
4709
+ function nextNodeDescendants(node) {
4710
+ while (node && !node.nextSibling) {
4711
+ node = node.parentNode;
4712
+ }
4713
+ if (!node) {
4714
+ return null;
4715
+ }
4716
+ return node.nextSibling;
4717
+ }
4718
+
4719
+ function nextNode(node, excludeChildren) {
4720
+ if (!excludeChildren && node.hasChildNodes()) {
4721
+ return node.firstChild;
4722
+ }
4723
+ return nextNodeDescendants(node);
4724
+ }
4725
+
4726
+ function previousNode(node) {
4727
+ var previous = node.previousSibling;
4728
+ if (previous) {
4729
+ node = previous;
4730
+ while (node.hasChildNodes()) {
4731
+ node = node.lastChild;
4732
+ }
4733
+ return node;
4734
+ }
4735
+ var parent = node.parentNode;
4736
+ if (parent && parent.nodeType == 1) {
4737
+ return parent;
4738
+ }
4739
+ return null;
4740
+ }
4741
+
4742
+ // Adpated from Aryeh's code.
4743
+ // "A whitespace node is either a Text node whose data is the empty string; or
4744
+ // a Text node whose data consists only of one or more tabs (0x0009), line
4745
+ // feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
4746
+ // parent is an Element whose resolved value for "white-space" is "normal" or
4747
+ // "nowrap"; or a Text node whose data consists only of one or more tabs
4748
+ // (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
4749
+ // parent is an Element whose resolved value for "white-space" is "pre-line"."
4750
+ function isWhitespaceNode(node) {
4751
+ if (!node || node.nodeType != 3) {
4752
+ return false;
4753
+ }
4754
+ var text = node.data;
4755
+ if (text === "") {
4756
+ return true;
4757
+ }
4758
+ var parent = node.parentNode;
4759
+ if (!parent || parent.nodeType != 1) {
4760
+ return false;
4761
+ }
4762
+ var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
4763
+
4764
+ return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace)) ||
4765
+ (/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line");
4766
+ }
4767
+
4768
+ // Adpated from Aryeh's code.
4769
+ // "node is a collapsed whitespace node if the following algorithm returns
4770
+ // true:"
4771
+ function isCollapsedWhitespaceNode(node) {
4772
+ // "If node's data is the empty string, return true."
4773
+ if (node.data === "") {
4774
+ return true;
4775
+ }
4776
+
4777
+ // "If node is not a whitespace node, return false."
4778
+ if (!isWhitespaceNode(node)) {
4779
+ return false;
4780
+ }
4781
+
4782
+ // "Let ancestor be node's parent."
4783
+ var ancestor = node.parentNode;
4784
+
4785
+ // "If ancestor is null, return true."
4786
+ if (!ancestor) {
4787
+ return true;
4788
+ }
4789
+
4790
+ // "If the "display" property of some ancestor of node has resolved value "none", return true."
4791
+ if (isHidden(node)) {
4792
+ return true;
4793
+ }
4794
+
4795
+ return false;
4796
+ }
4797
+
4798
+ function isCollapsedNode(node) {
4799
+ var type = node.nodeType;
4800
+ return type == 7 /* PROCESSING_INSTRUCTION */ ||
4801
+ type == 8 /* COMMENT */ ||
4802
+ isHidden(node) ||
4803
+ /^(script|style)$/i.test(node.nodeName) ||
4804
+ isVisibilityHiddenTextNode(node) ||
4805
+ isCollapsedWhitespaceNode(node);
4806
+ }
4807
+
4808
+ function isIgnoredNode(node, win) {
4809
+ var type = node.nodeType;
4810
+ return type == 7 /* PROCESSING_INSTRUCTION */ ||
4811
+ type == 8 /* COMMENT */ ||
4812
+ (type == 1 && getComputedDisplay(node, win) == "none");
4813
+ }
4814
+
4815
+ /*----------------------------------------------------------------------------------------------------------------*/
4816
+
4817
+ // Possibly overengineered caching system to prevent repeated DOM calls slowing everything down
4818
+
4819
+ function Cache() {
4820
+ this.store = {};
4821
+ }
4822
+
4823
+ Cache.prototype = {
4824
+ get: function(key) {
4825
+ return this.store.hasOwnProperty(key) ? this.store[key] : null;
4826
+ },
4827
+
4828
+ set: function(key, value) {
4829
+ return this.store[key] = value;
4830
+ }
4831
+ };
4832
+
4833
+ var cachedCount = 0, uncachedCount = 0;
4834
+
4835
+ function createCachingGetter(methodName, func, objProperty) {
4836
+ return function(args) {
4837
+ var cache = this.cache;
4838
+ if (cache.hasOwnProperty(methodName)) {
4839
+ cachedCount++;
4840
+ return cache[methodName];
4841
+ } else {
4842
+ uncachedCount++;
4843
+ var value = func.call(this, objProperty ? this[objProperty] : this, args);
4844
+ cache[methodName] = value;
4845
+ return value;
4846
+ }
4847
+ };
4848
+ }
4849
+
4850
+ /*----------------------------------------------------------------------------------------------------------------*/
4851
+
4852
+ function NodeWrapper(node, session) {
4853
+ this.node = node;
4854
+ this.session = session;
4855
+ this.cache = new Cache();
4856
+ this.positions = new Cache();
4857
+ }
4858
+
4859
+ var nodeProto = {
4860
+ getPosition: function(offset) {
4861
+ var positions = this.positions;
4862
+ return positions.get(offset) || positions.set(offset, new Position(this, offset));
4863
+ },
4864
+
4865
+ toString: function() {
4866
+ return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]";
4867
+ }
4868
+ };
4869
+
4870
+ NodeWrapper.prototype = nodeProto;
4871
+
4872
+ var EMPTY = "EMPTY",
4873
+ NON_SPACE = "NON_SPACE",
4874
+ UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE",
4875
+ COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE",
4876
+ TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK",
4877
+ TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK",
4878
+ TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR",
4879
+ PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK",
4880
+ TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR",
4881
+ INCLUDED_TRAILING_LINE_BREAK_AFTER_BR = "INCLUDED_TRAILING_LINE_BREAK_AFTER_BR";
4882
+
4883
+ extend(nodeProto, {
4884
+ isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"),
4885
+ getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"),
4886
+ getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"),
4887
+ containsPositions: createCachingGetter("containsPositions", containsPositions, "node"),
4888
+ isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"),
4889
+ isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"),
4890
+ getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"),
4891
+ isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"),
4892
+ isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"),
4893
+ next: createCachingGetter("nextPos", nextNode, "node"),
4894
+ previous: createCachingGetter("previous", previousNode, "node"),
4895
+
4896
+ getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) {
4897
+ var spaceRegex = null, collapseSpaces = false;
4898
+ var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace");
4899
+ var preLine = (cssWhitespace == "pre-line");
4900
+ if (preLine) {
4901
+ spaceRegex = spacesMinusLineBreaksRegex;
4902
+ collapseSpaces = true;
4903
+ } else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") {
4904
+ spaceRegex = spacesRegex;
4905
+ collapseSpaces = true;
4906
+ }
4907
+
4908
+ return {
4909
+ node: textNode,
4910
+ text: textNode.data,
4911
+ spaceRegex: spaceRegex,
4912
+ collapseSpaces: collapseSpaces,
4913
+ preLine: preLine
4914
+ };
4915
+ }, "node"),
4916
+
4917
+ hasInnerText: createCachingGetter("hasInnerText", function(el, backward) {
4918
+ var session = this.session;
4919
+ var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1);
4920
+ var firstPosInEl = session.getPosition(el, 0);
4921
+
4922
+ var pos = backward ? posAfterEl : firstPosInEl;
4923
+ var endPos = backward ? firstPosInEl : posAfterEl;
4924
+
4925
+ /*
4926
+ <body><p>X </p><p>Y</p></body>
4927
+
4928
+ Positions:
4929
+
4930
+ body:0:""
4931
+ p:0:""
4932
+ text:0:""
4933
+ text:1:"X"
4934
+ text:2:TRAILING_SPACE_IN_BLOCK
4935
+ text:3:COLLAPSED_SPACE
4936
+ p:1:""
4937
+ body:1:"\n"
4938
+ p:0:""
4939
+ text:0:""
4940
+ text:1:"Y"
4941
+
4942
+ A character is a TRAILING_SPACE_IN_BLOCK iff:
4943
+
4944
+ - There is no uncollapsed character after it within the visible containing block element
4945
+
4946
+ A character is a TRAILING_SPACE_BEFORE_BR iff:
4947
+
4948
+ - There is no uncollapsed character after it preceding a <br> element
4949
+
4950
+ An element has inner text iff
4951
+
4952
+ - It is not hidden
4953
+ - It contains an uncollapsed character
4954
+
4955
+ All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render.
4956
+ */
4957
+
4958
+ while (pos !== endPos) {
4959
+ pos.prepopulateChar();
4960
+ if (pos.isDefinitelyNonEmpty()) {
4961
+ return true;
4962
+ }
4963
+ pos = backward ? pos.previousVisible() : pos.nextVisible();
4964
+ }
4965
+
4966
+ return false;
4967
+ }, "node"),
4968
+
4969
+ isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) {
4970
+ // Ensure that a block element containing a <br> is considered to have inner text
4971
+ var brs = el.getElementsByTagName("br");
4972
+ for (var i = 0, len = brs.length; i < len; ++i) {
4973
+ if (!isCollapsedNode(brs[i])) {
4974
+ return true;
4975
+ }
4976
+ }
4977
+ return this.hasInnerText();
4978
+ }, "node"),
4979
+
4980
+ getTrailingSpace: createCachingGetter("trailingSpace", function(el) {
4981
+ if (el.tagName.toLowerCase() == "br") {
4982
+ return "";
4983
+ } else {
4984
+ switch (this.getComputedDisplay()) {
4985
+ case "inline":
4986
+ var child = el.lastChild;
4987
+ while (child) {
4988
+ if (!isIgnoredNode(child)) {
4989
+ return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : "";
4990
+ }
4991
+ child = child.previousSibling;
4992
+ }
4993
+ break;
4994
+ case "inline-block":
4995
+ case "inline-table":
4996
+ case "none":
4997
+ case "table-column":
4998
+ case "table-column-group":
4999
+ break;
5000
+ case "table-cell":
5001
+ return "\t";
5002
+ default:
5003
+ return this.isRenderedBlock(true) ? "\n" : "";
5004
+ }
5005
+ }
5006
+ return "";
5007
+ }, "node"),
5008
+
5009
+ getLeadingSpace: createCachingGetter("leadingSpace", function(el) {
5010
+ switch (this.getComputedDisplay()) {
5011
+ case "inline":
5012
+ case "inline-block":
5013
+ case "inline-table":
5014
+ case "none":
5015
+ case "table-column":
5016
+ case "table-column-group":
5017
+ case "table-cell":
5018
+ break;
5019
+ default:
5020
+ return this.isRenderedBlock(false) ? "\n" : "";
5021
+ }
5022
+ return "";
5023
+ }, "node")
5024
+ });
5025
+
5026
+ /*----------------------------------------------------------------------------------------------------------------*/
5027
+
5028
+ function Position(nodeWrapper, offset) {
5029
+ this.offset = offset;
5030
+ this.nodeWrapper = nodeWrapper;
5031
+ this.node = nodeWrapper.node;
5032
+ this.session = nodeWrapper.session;
5033
+ this.cache = new Cache();
5034
+ }
5035
+
5036
+ function inspectPosition() {
5037
+ return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]";
5038
+ }
5039
+
5040
+ var positionProto = {
5041
+ character: "",
5042
+ characterType: EMPTY,
5043
+ isBr: false,
5044
+
5045
+ /*
5046
+ This method:
5047
+ - Fully populates positions that have characters that can be determined independently of any other characters.
5048
+ - Populates most types of space positions with a provisional character. The character is finalized later.
5049
+ */
5050
+ prepopulateChar: function() {
5051
+ var pos = this;
5052
+ if (!pos.prepopulatedChar) {
5053
+ var node = pos.node, offset = pos.offset;
5054
+ var visibleChar = "", charType = EMPTY;
5055
+ var finalizedChar = false;
5056
+ if (offset > 0) {
5057
+ if (node.nodeType == 3) {
5058
+ var text = node.data;
5059
+ var textChar = text.charAt(offset - 1);
5060
+
5061
+ var nodeInfo = pos.nodeWrapper.getTextNodeInfo();
5062
+ var spaceRegex = nodeInfo.spaceRegex;
5063
+ if (nodeInfo.collapseSpaces) {
5064
+ if (spaceRegex.test(textChar)) {
5065
+ // "If the character at position is from set, append a single space (U+0020) to newdata and advance
5066
+ // position until the character at position is not from set."
5067
+
5068
+ // We also need to check for the case where we're in a pre-line and we have a space preceding a
5069
+ // line break, because such spaces are collapsed in some browsers
5070
+ if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) {
5071
+ } else if (nodeInfo.preLine && text.charAt(offset) === "\n") {
5072
+ visibleChar = " ";
5073
+ charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK;
5074
+ } else {
5075
+ visibleChar = " ";
5076
+ //pos.checkForFollowingLineBreak = true;
5077
+ charType = COLLAPSIBLE_SPACE;
5078
+ }
5079
+ } else {
5080
+ visibleChar = textChar;
5081
+ charType = NON_SPACE;
5082
+ finalizedChar = true;
5083
+ }
5084
+ } else {
5085
+ visibleChar = textChar;
5086
+ charType = UNCOLLAPSIBLE_SPACE;
5087
+ finalizedChar = true;
5088
+ }
5089
+ } else {
5090
+ var nodePassed = node.childNodes[offset - 1];
5091
+ if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) {
5092
+ if (nodePassed.tagName.toLowerCase() == "br") {
5093
+ visibleChar = "\n";
5094
+ pos.isBr = true;
5095
+ charType = COLLAPSIBLE_SPACE;
5096
+ finalizedChar = false;
5097
+ } else {
5098
+ pos.checkForTrailingSpace = true;
5099
+ }
5100
+ }
5101
+
5102
+ // Check the leading space of the next node for the case when a block element follows an inline
5103
+ // element or text node. In that case, there is an implied line break between the two nodes.
5104
+ if (!visibleChar) {
5105
+ var nextNode = node.childNodes[offset];
5106
+ if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) {
5107
+ pos.checkForLeadingSpace = true;
5108
+ }
5109
+ }
5110
+ }
5111
+ }
5112
+
5113
+ pos.prepopulatedChar = true;
5114
+ pos.character = visibleChar;
5115
+ pos.characterType = charType;
5116
+ pos.isCharInvariant = finalizedChar;
5117
+ }
5118
+ },
5119
+
5120
+ isDefinitelyNonEmpty: function() {
5121
+ var charType = this.characterType;
5122
+ return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE;
5123
+ },
5124
+
5125
+ // Resolve leading and trailing spaces, which may involve prepopulating other positions
5126
+ resolveLeadingAndTrailingSpaces: function() {
5127
+ if (!this.prepopulatedChar) {
5128
+ this.prepopulateChar();
5129
+ }
5130
+ if (this.checkForTrailingSpace) {
5131
+ var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace();
5132
+ if (trailingSpace) {
5133
+ this.isTrailingSpace = true;
5134
+ this.character = trailingSpace;
5135
+ this.characterType = COLLAPSIBLE_SPACE;
5136
+ }
5137
+ this.checkForTrailingSpace = false;
5138
+ }
5139
+ if (this.checkForLeadingSpace) {
5140
+ var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();
5141
+ if (leadingSpace) {
5142
+ this.isLeadingSpace = true;
5143
+ this.character = leadingSpace;
5144
+ this.characterType = COLLAPSIBLE_SPACE;
5145
+ }
5146
+ this.checkForLeadingSpace = false;
5147
+ }
5148
+ },
5149
+
5150
+ getPrecedingUncollapsedPosition: function(characterOptions) {
5151
+ var pos = this, character;
5152
+ while ( (pos = pos.previousVisible()) ) {
5153
+ character = pos.getCharacter(characterOptions);
5154
+ if (character !== "") {
5155
+ return pos;
5156
+ }
5157
+ }
5158
+
5159
+ return null;
5160
+ },
5161
+
5162
+ getCharacter: function(characterOptions) {
5163
+ this.resolveLeadingAndTrailingSpaces();
5164
+
5165
+ var thisChar = this.character, returnChar;
5166
+
5167
+ // Check if character is ignored
5168
+ var ignoredChars = normalizeIgnoredCharacters(characterOptions.ignoreCharacters);
5169
+ var isIgnoredCharacter = (thisChar !== "" && ignoredChars.indexOf(thisChar) > -1);
5170
+
5171
+ // Check if this position's character is invariant (i.e. not dependent on character options) and return it
5172
+ // if so
5173
+ if (this.isCharInvariant) {
5174
+ returnChar = isIgnoredCharacter ? "" : thisChar;
5175
+ return returnChar;
5176
+ }
5177
+
5178
+ var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace, ignoredChars].join("_");
5179
+ var cachedChar = this.cache.get(cacheKey);
5180
+ if (cachedChar !== null) {
5181
+ return cachedChar;
5182
+ }
5183
+
5184
+ // We need to actually get the character now
5185
+ var character = "";
5186
+ var collapsible = (this.characterType == COLLAPSIBLE_SPACE);
5187
+
5188
+ var nextPos, previousPos;
5189
+ var gotPreviousPos = false;
5190
+ var pos = this;
5191
+
5192
+ function getPreviousPos() {
5193
+ if (!gotPreviousPos) {
5194
+ previousPos = pos.getPrecedingUncollapsedPosition(characterOptions);
5195
+ gotPreviousPos = true;
5196
+ }
5197
+ return previousPos;
5198
+ }
5199
+
5200
+ // Disallow a collapsible space that is followed by a line break or is the last character
5201
+ if (collapsible) {
5202
+ // Allow a trailing space that we've previously determined should be included
5203
+ if (this.type == INCLUDED_TRAILING_LINE_BREAK_AFTER_BR) {
5204
+ character = "\n";
5205
+ }
5206
+ // Disallow a collapsible space that follows a trailing space or line break, or is the first character,
5207
+ // or follows a collapsible included space
5208
+ else if (thisChar == " " &&
5209
+ (!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n" || (previousPos.character == " " && previousPos.characterType == COLLAPSIBLE_SPACE))) {
5210
+ }
5211
+ // Allow a leading line break unless it follows a line break
5212
+ else if (thisChar == "\n" && this.isLeadingSpace) {
5213
+ if (getPreviousPos() && previousPos.character != "\n") {
5214
+ character = "\n";
5215
+ } else {
5216
+ }
5217
+ } else {
5218
+ nextPos = this.nextUncollapsed();
5219
+ if (nextPos) {
5220
+ if (nextPos.isBr) {
5221
+ this.type = TRAILING_SPACE_BEFORE_BR;
5222
+ } else if (nextPos.isTrailingSpace && nextPos.character == "\n") {
5223
+ this.type = TRAILING_SPACE_IN_BLOCK;
5224
+ } else if (nextPos.isLeadingSpace && nextPos.character == "\n") {
5225
+ this.type = TRAILING_SPACE_BEFORE_BLOCK;
5226
+ }
5227
+
5228
+ if (nextPos.character == "\n") {
5229
+ if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) {
5230
+ } else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) {
5231
+ } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) {
5232
+ } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) {
5233
+ } else if (thisChar == "\n") {
5234
+ if (nextPos.isTrailingSpace) {
5235
+ if (this.isTrailingSpace) {
5236
+ } else if (this.isBr) {
5237
+ nextPos.type = TRAILING_LINE_BREAK_AFTER_BR;
5238
+
5239
+ if (getPreviousPos() && previousPos.isLeadingSpace && !previousPos.isTrailingSpace && previousPos.character == "\n") {
5240
+ nextPos.character = "";
5241
+ } else {
5242
+ nextPos.type = INCLUDED_TRAILING_LINE_BREAK_AFTER_BR;
5243
+ }
5244
+ }
5245
+ } else {
5246
+ character = "\n";
5247
+ }
5248
+ } else if (thisChar == " ") {
5249
+ character = " ";
5250
+ } else {
5251
+ }
5252
+ } else {
5253
+ character = thisChar;
5254
+ }
5255
+ } else {
5256
+ }
5257
+ }
5258
+ }
5259
+
5260
+ if (ignoredChars.indexOf(character) > -1) {
5261
+ character = "";
5262
+ }
5263
+
5264
+
5265
+ this.cache.set(cacheKey, character);
5266
+
5267
+ return character;
5268
+ },
5269
+
5270
+ equals: function(pos) {
5271
+ return !!pos && this.node === pos.node && this.offset === pos.offset;
5272
+ },
5273
+
5274
+ inspect: inspectPosition,
5275
+
5276
+ toString: function() {
5277
+ return this.character;
5278
+ }
5279
+ };
5280
+
5281
+ Position.prototype = positionProto;
5282
+
5283
+ extend(positionProto, {
5284
+ next: createCachingGetter("nextPos", function(pos) {
5285
+ var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
5286
+ if (!node) {
5287
+ return null;
5288
+ }
5289
+ var nextNode, nextOffset, child;
5290
+ if (offset == nodeWrapper.getLength()) {
5291
+ // Move onto the next node
5292
+ nextNode = node.parentNode;
5293
+ nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0;
5294
+ } else {
5295
+ if (nodeWrapper.isCharacterDataNode()) {
5296
+ nextNode = node;
5297
+ nextOffset = offset + 1;
5298
+ } else {
5299
+ child = node.childNodes[offset];
5300
+ // Go into the children next, if children there are
5301
+ if (session.getNodeWrapper(child).containsPositions()) {
5302
+ nextNode = child;
5303
+ nextOffset = 0;
5304
+ } else {
5305
+ nextNode = node;
5306
+ nextOffset = offset + 1;
5307
+ }
5308
+ }
5309
+ }
5310
+
5311
+ return nextNode ? session.getPosition(nextNode, nextOffset) : null;
5312
+ }),
5313
+
5314
+ previous: createCachingGetter("previous", function(pos) {
5315
+ var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
5316
+ var previousNode, previousOffset, child;
5317
+ if (offset == 0) {
5318
+ previousNode = node.parentNode;
5319
+ previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0;
5320
+ } else {
5321
+ if (nodeWrapper.isCharacterDataNode()) {
5322
+ previousNode = node;
5323
+ previousOffset = offset - 1;
5324
+ } else {
5325
+ child = node.childNodes[offset - 1];
5326
+ // Go into the children next, if children there are
5327
+ if (session.getNodeWrapper(child).containsPositions()) {
5328
+ previousNode = child;
5329
+ previousOffset = dom.getNodeLength(child);
5330
+ } else {
5331
+ previousNode = node;
5332
+ previousOffset = offset - 1;
5333
+ }
5334
+ }
5335
+ }
5336
+ return previousNode ? session.getPosition(previousNode, previousOffset) : null;
5337
+ }),
5338
+
5339
+ /*
5340
+ Next and previous position moving functions that filter out
5341
+
5342
+ - Hidden (CSS visibility/display) elements
5343
+ - Script and style elements
5344
+ */
5345
+ nextVisible: createCachingGetter("nextVisible", function(pos) {
5346
+ var next = pos.next();
5347
+ if (!next) {
5348
+ return null;
5349
+ }
5350
+ var nodeWrapper = next.nodeWrapper, node = next.node;
5351
+ var newPos = next;
5352
+ if (nodeWrapper.isCollapsed()) {
5353
+ // We're skipping this node and all its descendants
5354
+ newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1);
5355
+ }
5356
+ return newPos;
5357
+ }),
5358
+
5359
+ nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) {
5360
+ var nextPos = pos;
5361
+ while ( (nextPos = nextPos.nextVisible()) ) {
5362
+ nextPos.resolveLeadingAndTrailingSpaces();
5363
+ if (nextPos.character !== "") {
5364
+ return nextPos;
5365
+ }
5366
+ }
5367
+ return null;
5368
+ }),
5369
+
5370
+ previousVisible: createCachingGetter("previousVisible", function(pos) {
5371
+ var previous = pos.previous();
5372
+ if (!previous) {
5373
+ return null;
5374
+ }
5375
+ var nodeWrapper = previous.nodeWrapper, node = previous.node;
5376
+ var newPos = previous;
5377
+ if (nodeWrapper.isCollapsed()) {
5378
+ // We're skipping this node and all its descendants
5379
+ newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex());
5380
+ }
5381
+ return newPos;
5382
+ })
5383
+ });
5384
+
5385
+ /*----------------------------------------------------------------------------------------------------------------*/
5386
+
5387
+ var currentSession = null;
5388
+
5389
+ var Session = (function() {
5390
+ function createWrapperCache(nodeProperty) {
5391
+ var cache = new Cache();
5392
+
5393
+ return {
5394
+ get: function(node) {
5395
+ var wrappersByProperty = cache.get(node[nodeProperty]);
5396
+ if (wrappersByProperty) {
5397
+ for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) {
5398
+ if (wrapper.node === node) {
5399
+ return wrapper;
5400
+ }
5401
+ }
5402
+ }
5403
+ return null;
5404
+ },
5405
+
5406
+ set: function(nodeWrapper) {
5407
+ var property = nodeWrapper.node[nodeProperty];
5408
+ var wrappersByProperty = cache.get(property) || cache.set(property, []);
5409
+ wrappersByProperty.push(nodeWrapper);
5410
+ }
5411
+ };
5412
+ }
5413
+
5414
+ var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID");
5415
+
5416
+ function Session() {
5417
+ this.initCaches();
5418
+ }
5419
+
5420
+ Session.prototype = {
5421
+ initCaches: function() {
5422
+ this.elementCache = uniqueIDSupported ? (function() {
5423
+ var elementsCache = new Cache();
5424
+
5425
+ return {
5426
+ get: function(el) {
5427
+ return elementsCache.get(el.uniqueID);
5428
+ },
5429
+
5430
+ set: function(elWrapper) {
5431
+ elementsCache.set(elWrapper.node.uniqueID, elWrapper);
5432
+ }
5433
+ };
5434
+ })() : createWrapperCache("tagName");
5435
+
5436
+ // Store text nodes keyed by data, although we may need to truncate this
5437
+ this.textNodeCache = createWrapperCache("data");
5438
+ this.otherNodeCache = createWrapperCache("nodeName");
5439
+ },
5440
+
5441
+ getNodeWrapper: function(node) {
5442
+ var wrapperCache;
5443
+ switch (node.nodeType) {
5444
+ case 1:
5445
+ wrapperCache = this.elementCache;
5446
+ break;
5447
+ case 3:
5448
+ wrapperCache = this.textNodeCache;
5449
+ break;
5450
+ default:
5451
+ wrapperCache = this.otherNodeCache;
5452
+ break;
5453
+ }
5454
+
5455
+ var wrapper = wrapperCache.get(node);
5456
+ if (!wrapper) {
5457
+ wrapper = new NodeWrapper(node, this);
5458
+ wrapperCache.set(wrapper);
5459
+ }
5460
+ return wrapper;
5461
+ },
5462
+
5463
+ getPosition: function(node, offset) {
5464
+ return this.getNodeWrapper(node).getPosition(offset);
5465
+ },
5466
+
5467
+ getRangeBoundaryPosition: function(range, isStart) {
5468
+ var prefix = isStart ? "start" : "end";
5469
+ return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]);
5470
+ },
5471
+
5472
+ detach: function() {
5473
+ this.elementCache = this.textNodeCache = this.otherNodeCache = null;
5474
+ }
5475
+ };
5476
+
5477
+ return Session;
5478
+ })();
5479
+
5480
+ /*----------------------------------------------------------------------------------------------------------------*/
5481
+
5482
+ function startSession() {
5483
+ endSession();
5484
+ return (currentSession = new Session());
5485
+ }
5486
+
5487
+ function getSession() {
5488
+ return currentSession || startSession();
5489
+ }
5490
+
5491
+ function endSession() {
5492
+ if (currentSession) {
5493
+ currentSession.detach();
5494
+ }
5495
+ currentSession = null;
5496
+ }
5497
+
5498
+ /*----------------------------------------------------------------------------------------------------------------*/
5499
+
5500
+ // Extensions to the rangy.dom utility object
5501
+
5502
+ extend(dom, {
5503
+ nextNode: nextNode,
5504
+ previousNode: previousNode
5505
+ });
5506
+
5507
+ /*----------------------------------------------------------------------------------------------------------------*/
5508
+
5509
+ function createCharacterIterator(startPos, backward, endPos, characterOptions) {
5510
+
5511
+ // Adjust the end position to ensure that it is actually reached
5512
+ if (endPos) {
5513
+ if (backward) {
5514
+ if (isCollapsedNode(endPos.node)) {
5515
+ endPos = startPos.previousVisible();
5516
+ }
5517
+ } else {
5518
+ if (isCollapsedNode(endPos.node)) {
5519
+ endPos = endPos.nextVisible();
5520
+ }
5521
+ }
5522
+ }
5523
+
5524
+ var pos = startPos, finished = false;
5525
+
5526
+ function next() {
5527
+ var charPos = null;
5528
+ if (backward) {
5529
+ charPos = pos;
5530
+ if (!finished) {
5531
+ pos = pos.previousVisible();
5532
+ finished = !pos || (endPos && pos.equals(endPos));
5533
+ }
5534
+ } else {
5535
+ if (!finished) {
5536
+ charPos = pos = pos.nextVisible();
5537
+ finished = !pos || (endPos && pos.equals(endPos));
5538
+ }
5539
+ }
5540
+ if (finished) {
5541
+ pos = null;
5542
+ }
5543
+ return charPos;
5544
+ }
5545
+
5546
+ var previousTextPos, returnPreviousTextPos = false;
5547
+
5548
+ return {
5549
+ next: function() {
5550
+ if (returnPreviousTextPos) {
5551
+ returnPreviousTextPos = false;
5552
+ return previousTextPos;
5553
+ } else {
5554
+ var pos, character;
5555
+ while ( (pos = next()) ) {
5556
+ character = pos.getCharacter(characterOptions);
5557
+ if (character) {
5558
+ previousTextPos = pos;
5559
+ return pos;
5560
+ }
5561
+ }
5562
+ return null;
5563
+ }
5564
+ },
5565
+
5566
+ rewind: function() {
5567
+ if (previousTextPos) {
5568
+ returnPreviousTextPos = true;
5569
+ } else {
5570
+ throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound.");
5571
+ }
5572
+ },
5573
+
5574
+ dispose: function() {
5575
+ startPos = endPos = null;
5576
+ }
5577
+ };
5578
+ }
5579
+
5580
+ var arrayIndexOf = Array.prototype.indexOf ?
5581
+ function(arr, val) {
5582
+ return arr.indexOf(val);
5583
+ } :
5584
+ function(arr, val) {
5585
+ for (var i = 0, len = arr.length; i < len; ++i) {
5586
+ if (arr[i] === val) {
5587
+ return i;
5588
+ }
5589
+ }
5590
+ return -1;
5591
+ };
5592
+
5593
+ // Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next()
5594
+ // is called and there is no more tokenized text
5595
+ function createTokenizedTextProvider(pos, characterOptions, wordOptions) {
5596
+ var forwardIterator = createCharacterIterator(pos, false, null, characterOptions);
5597
+ var backwardIterator = createCharacterIterator(pos, true, null, characterOptions);
5598
+ var tokenizer = wordOptions.tokenizer;
5599
+
5600
+ // Consumes a word and the whitespace beyond it
5601
+ function consumeWord(forward) {
5602
+ var pos, textChar;
5603
+ var newChars = [], it = forward ? forwardIterator : backwardIterator;
5604
+
5605
+ var passedWordBoundary = false, insideWord = false;
5606
+
5607
+ while ( (pos = it.next()) ) {
5608
+ textChar = pos.character;
5609
+
5610
+
5611
+ if (allWhiteSpaceRegex.test(textChar)) {
5612
+ if (insideWord) {
5613
+ insideWord = false;
5614
+ passedWordBoundary = true;
5615
+ }
5616
+ } else {
5617
+ if (passedWordBoundary) {
5618
+ it.rewind();
5619
+ break;
5620
+ } else {
5621
+ insideWord = true;
5622
+ }
5623
+ }
5624
+ newChars.push(pos);
5625
+ }
5626
+
5627
+
5628
+ return newChars;
5629
+ }
5630
+
5631
+ // Get initial word surrounding initial position and tokenize it
5632
+ var forwardChars = consumeWord(true);
5633
+ var backwardChars = consumeWord(false).reverse();
5634
+ var tokens = tokenize(backwardChars.concat(forwardChars), wordOptions, tokenizer);
5635
+
5636
+ // Create initial token buffers
5637
+ var forwardTokensBuffer = forwardChars.length ?
5638
+ tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : [];
5639
+
5640
+ var backwardTokensBuffer = backwardChars.length ?
5641
+ tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : [];
5642
+
5643
+ function inspectBuffer(buffer) {
5644
+ var textPositions = ["[" + buffer.length + "]"];
5645
+ for (var i = 0; i < buffer.length; ++i) {
5646
+ textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")");
5647
+ }
5648
+ return textPositions;
5649
+ }
5650
+
5651
+
5652
+ return {
5653
+ nextEndToken: function() {
5654
+ var lastToken, forwardChars;
5655
+
5656
+ // If we're down to the last token, consume character chunks until we have a word or run out of
5657
+ // characters to consume
5658
+ while ( forwardTokensBuffer.length == 1 &&
5659
+ !(lastToken = forwardTokensBuffer[0]).isWord &&
5660
+ (forwardChars = consumeWord(true)).length > 0) {
5661
+
5662
+ // Merge trailing non-word into next word and tokenize
5663
+ forwardTokensBuffer = tokenize(lastToken.chars.concat(forwardChars), wordOptions, tokenizer);
5664
+ }
5665
+
5666
+ return forwardTokensBuffer.shift();
5667
+ },
5668
+
5669
+ previousStartToken: function() {
5670
+ var lastToken, backwardChars;
5671
+
5672
+ // If we're down to the last token, consume character chunks until we have a word or run out of
5673
+ // characters to consume
5674
+ while ( backwardTokensBuffer.length == 1 &&
5675
+ !(lastToken = backwardTokensBuffer[0]).isWord &&
5676
+ (backwardChars = consumeWord(false)).length > 0) {
5677
+
5678
+ // Merge leading non-word into next word and tokenize
5679
+ backwardTokensBuffer = tokenize(backwardChars.reverse().concat(lastToken.chars), wordOptions, tokenizer);
5680
+ }
5681
+
5682
+ return backwardTokensBuffer.pop();
5683
+ },
5684
+
5685
+ dispose: function() {
5686
+ forwardIterator.dispose();
5687
+ backwardIterator.dispose();
5688
+ forwardTokensBuffer = backwardTokensBuffer = null;
5689
+ }
5690
+ };
5691
+ }
5692
+
5693
+ function movePositionBy(pos, unit, count, characterOptions, wordOptions) {
5694
+ var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token;
5695
+ if (count !== 0) {
5696
+ var backward = (count < 0);
5697
+
5698
+ switch (unit) {
5699
+ case CHARACTER:
5700
+ charIterator = createCharacterIterator(pos, backward, null, characterOptions);
5701
+ while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) {
5702
+ ++unitsMoved;
5703
+ newPos = currentPos;
5704
+ }
5705
+ nextPos = currentPos;
5706
+ charIterator.dispose();
5707
+ break;
5708
+ case WORD:
5709
+ var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions);
5710
+ var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken;
5711
+
5712
+ while ( (token = next()) && unitsMoved < absCount ) {
5713
+ if (token.isWord) {
5714
+ ++unitsMoved;
5715
+ newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1];
5716
+ }
5717
+ }
5718
+ break;
5719
+ default:
5720
+ throw new Error("movePositionBy: unit '" + unit + "' not implemented");
5721
+ }
5722
+
5723
+ // Perform any necessary position tweaks
5724
+ if (backward) {
5725
+ newPos = newPos.previousVisible();
5726
+ unitsMoved = -unitsMoved;
5727
+ } else if (newPos && newPos.isLeadingSpace && !newPos.isTrailingSpace) {
5728
+ // Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space
5729
+ // before a block element (for example, the line break between "1" and "2" in the following HTML:
5730
+ // "1<p>2</p>") is considered to be attached to the position immediately before the block element, which
5731
+ // corresponds with a different selection position in most browsers from the one we want (i.e. at the
5732
+ // start of the contents of the block element). We get round this by advancing the position returned to
5733
+ // the last possible equivalent visible position.
5734
+ if (unit == WORD) {
5735
+ charIterator = createCharacterIterator(pos, false, null, characterOptions);
5736
+ nextPos = charIterator.next();
5737
+ charIterator.dispose();
5738
+ }
5739
+ if (nextPos) {
5740
+ newPos = nextPos.previousVisible();
5741
+ }
5742
+ }
5743
+ }
5744
+
5745
+
5746
+ return {
5747
+ position: newPos,
5748
+ unitsMoved: unitsMoved
5749
+ };
5750
+ }
5751
+
5752
+ function createRangeCharacterIterator(session, range, characterOptions, backward) {
5753
+ var rangeStart = session.getRangeBoundaryPosition(range, true);
5754
+ var rangeEnd = session.getRangeBoundaryPosition(range, false);
5755
+ var itStart = backward ? rangeEnd : rangeStart;
5756
+ var itEnd = backward ? rangeStart : rangeEnd;
5757
+
5758
+ return createCharacterIterator(itStart, !!backward, itEnd, characterOptions);
5759
+ }
5760
+
5761
+ function getRangeCharacters(session, range, characterOptions) {
5762
+
5763
+ var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos;
5764
+ while ( (pos = it.next()) ) {
5765
+ chars.push(pos);
5766
+ }
5767
+
5768
+ it.dispose();
5769
+ return chars;
5770
+ }
5771
+
5772
+ function isWholeWord(startPos, endPos, wordOptions) {
5773
+ var range = api.createRange(startPos.node);
5774
+ range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset);
5775
+ return !range.expand("word", { wordOptions: wordOptions });
5776
+ }
5777
+
5778
+ function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) {
5779
+ var backward = isDirectionBackward(findOptions.direction);
5780
+ var it = createCharacterIterator(
5781
+ initialPos,
5782
+ backward,
5783
+ initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward),
5784
+ findOptions.characterOptions
5785
+ );
5786
+ var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex;
5787
+ var result, insideRegexMatch;
5788
+ var returnValue = null;
5789
+
5790
+ function handleMatch(startIndex, endIndex) {
5791
+ var startPos = chars[startIndex].previousVisible();
5792
+ var endPos = chars[endIndex - 1];
5793
+ var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions));
5794
+
5795
+ return {
5796
+ startPos: startPos,
5797
+ endPos: endPos,
5798
+ valid: valid
5799
+ };
5800
+ }
5801
+
5802
+ while ( (pos = it.next()) ) {
5803
+ currentChar = pos.character;
5804
+ if (!isRegex && !findOptions.caseSensitive) {
5805
+ currentChar = currentChar.toLowerCase();
5806
+ }
5807
+
5808
+ if (backward) {
5809
+ chars.unshift(pos);
5810
+ text = currentChar + text;
5811
+ } else {
5812
+ chars.push(pos);
5813
+ text += currentChar;
5814
+ }
5815
+
5816
+ if (isRegex) {
5817
+ result = searchTerm.exec(text);
5818
+ if (result) {
5819
+ matchStartIndex = result.index;
5820
+ matchEndIndex = matchStartIndex + result[0].length;
5821
+ if (insideRegexMatch) {
5822
+ // Check whether the match is now over
5823
+ if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) {
5824
+ returnValue = handleMatch(matchStartIndex, matchEndIndex);
5825
+ break;
5826
+ }
5827
+ } else {
5828
+ insideRegexMatch = true;
5829
+ }
5830
+ }
5831
+ } else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) {
5832
+ returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length);
5833
+ break;
5834
+ }
5835
+ }
5836
+
5837
+ // Check whether regex match extends to the end of the range
5838
+ if (insideRegexMatch) {
5839
+ returnValue = handleMatch(matchStartIndex, matchEndIndex);
5840
+ }
5841
+ it.dispose();
5842
+
5843
+ return returnValue;
5844
+ }
5845
+
5846
+ function createEntryPointFunction(func) {
5847
+ return function() {
5848
+ var sessionRunning = !!currentSession;
5849
+ var session = getSession();
5850
+ var args = [session].concat( util.toArray(arguments) );
5851
+ var returnValue = func.apply(this, args);
5852
+ if (!sessionRunning) {
5853
+ endSession();
5854
+ }
5855
+ return returnValue;
5856
+ };
5857
+ }
5858
+
5859
+ /*----------------------------------------------------------------------------------------------------------------*/
5860
+
5861
+ // Extensions to the Rangy Range object
5862
+
5863
+ function createRangeBoundaryMover(isStart, collapse) {
5864
+ /*
5865
+ Unit can be "character" or "word"
5866
+ Options:
5867
+
5868
+ - includeTrailingSpace
5869
+ - wordRegex
5870
+ - tokenizer
5871
+ - collapseSpaceBeforeLineBreak
5872
+ */
5873
+ return createEntryPointFunction(
5874
+ function(session, unit, count, moveOptions) {
5875
+ if (typeof count == UNDEF) {
5876
+ count = unit;
5877
+ unit = CHARACTER;
5878
+ }
5879
+ moveOptions = createNestedOptions(moveOptions, defaultMoveOptions);
5880
+
5881
+ var boundaryIsStart = isStart;
5882
+ if (collapse) {
5883
+ boundaryIsStart = (count >= 0);
5884
+ this.collapse(!boundaryIsStart);
5885
+ }
5886
+ var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, moveOptions.characterOptions, moveOptions.wordOptions);
5887
+ var newPos = moveResult.position;
5888
+ this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset);
5889
+ return moveResult.unitsMoved;
5890
+ }
5891
+ );
5892
+ }
5893
+
5894
+ function createRangeTrimmer(isStart) {
5895
+ return createEntryPointFunction(
5896
+ function(session, characterOptions) {
5897
+ characterOptions = createOptions(characterOptions, defaultCharacterOptions);
5898
+ var pos;
5899
+ var it = createRangeCharacterIterator(session, this, characterOptions, !isStart);
5900
+ var trimCharCount = 0;
5901
+ while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) {
5902
+ ++trimCharCount;
5903
+ }
5904
+ it.dispose();
5905
+ var trimmed = (trimCharCount > 0);
5906
+ if (trimmed) {
5907
+ this[isStart ? "moveStart" : "moveEnd"](
5908
+ "character",
5909
+ isStart ? trimCharCount : -trimCharCount,
5910
+ { characterOptions: characterOptions }
5911
+ );
5912
+ }
5913
+ return trimmed;
5914
+ }
5915
+ );
5916
+ }
5917
+
5918
+ extend(api.rangePrototype, {
5919
+ moveStart: createRangeBoundaryMover(true, false),
5920
+
5921
+ moveEnd: createRangeBoundaryMover(false, false),
5922
+
5923
+ move: createRangeBoundaryMover(true, true),
5924
+
5925
+ trimStart: createRangeTrimmer(true),
5926
+
5927
+ trimEnd: createRangeTrimmer(false),
5928
+
5929
+ trim: createEntryPointFunction(
5930
+ function(session, characterOptions) {
5931
+ var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions);
5932
+ return startTrimmed || endTrimmed;
5933
+ }
5934
+ ),
5935
+
5936
+ expand: createEntryPointFunction(
5937
+ function(session, unit, expandOptions) {
5938
+ var moved = false;
5939
+ expandOptions = createNestedOptions(expandOptions, defaultExpandOptions);
5940
+ var characterOptions = expandOptions.characterOptions;
5941
+ if (!unit) {
5942
+ unit = CHARACTER;
5943
+ }
5944
+ if (unit == WORD) {
5945
+ var wordOptions = expandOptions.wordOptions;
5946
+ var startPos = session.getRangeBoundaryPosition(this, true);
5947
+ var endPos = session.getRangeBoundaryPosition(this, false);
5948
+
5949
+ var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
5950
+ var startToken = startTokenizedTextProvider.nextEndToken();
5951
+ var newStartPos = startToken.chars[0].previousVisible();
5952
+ var endToken, newEndPos;
5953
+
5954
+ if (this.collapsed) {
5955
+ endToken = startToken;
5956
+ } else {
5957
+ var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions);
5958
+ endToken = endTokenizedTextProvider.previousStartToken();
5959
+ }
5960
+ newEndPos = endToken.chars[endToken.chars.length - 1];
5961
+
5962
+ if (!newStartPos.equals(startPos)) {
5963
+ this.setStart(newStartPos.node, newStartPos.offset);
5964
+ moved = true;
5965
+ }
5966
+ if (newEndPos && !newEndPos.equals(endPos)) {
5967
+ this.setEnd(newEndPos.node, newEndPos.offset);
5968
+ moved = true;
5969
+ }
5970
+
5971
+ if (expandOptions.trim) {
5972
+ if (expandOptions.trimStart) {
5973
+ moved = this.trimStart(characterOptions) || moved;
5974
+ }
5975
+ if (expandOptions.trimEnd) {
5976
+ moved = this.trimEnd(characterOptions) || moved;
5977
+ }
5978
+ }
5979
+
5980
+ return moved;
5981
+ } else {
5982
+ return this.moveEnd(CHARACTER, 1, expandOptions);
5983
+ }
5984
+ }
5985
+ ),
5986
+
5987
+ text: createEntryPointFunction(
5988
+ function(session, characterOptions) {
5989
+ return this.collapsed ?
5990
+ "" : getRangeCharacters(session, this, createOptions(characterOptions, defaultCharacterOptions)).join("");
5991
+ }
5992
+ ),
5993
+
5994
+ selectCharacters: createEntryPointFunction(
5995
+ function(session, containerNode, startIndex, endIndex, characterOptions) {
5996
+ var moveOptions = { characterOptions: characterOptions };
5997
+ if (!containerNode) {
5998
+ containerNode = getBody( this.getDocument() );
5999
+ }
6000
+ this.selectNodeContents(containerNode);
6001
+ this.collapse(true);
6002
+ this.moveStart("character", startIndex, moveOptions);
6003
+ this.collapse(true);
6004
+ this.moveEnd("character", endIndex - startIndex, moveOptions);
6005
+ }
6006
+ ),
6007
+
6008
+ // Character indexes are relative to the start of node
6009
+ toCharacterRange: createEntryPointFunction(
6010
+ function(session, containerNode, characterOptions) {
6011
+ if (!containerNode) {
6012
+ containerNode = getBody( this.getDocument() );
6013
+ }
6014
+ var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode);
6015
+ var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1);
6016
+ var rangeBetween = this.cloneRange();
6017
+ var startIndex, endIndex;
6018
+ if (rangeStartsBeforeNode) {
6019
+ rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex);
6020
+ startIndex = -rangeBetween.text(characterOptions).length;
6021
+ } else {
6022
+ rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset);
6023
+ startIndex = rangeBetween.text(characterOptions).length;
6024
+ }
6025
+ endIndex = startIndex + this.text(characterOptions).length;
6026
+
6027
+ return {
6028
+ start: startIndex,
6029
+ end: endIndex
6030
+ };
6031
+ }
6032
+ ),
6033
+
6034
+ findText: createEntryPointFunction(
6035
+ function(session, searchTermParam, findOptions) {
6036
+ // Set up options
6037
+ findOptions = createNestedOptions(findOptions, defaultFindOptions);
6038
+
6039
+ // Create word options if we're matching whole words only
6040
+ if (findOptions.wholeWordsOnly) {
6041
+ // We don't ever want trailing spaces for search results
6042
+ findOptions.wordOptions.includeTrailingSpace = false;
6043
+ }
6044
+
6045
+ var backward = isDirectionBackward(findOptions.direction);
6046
+
6047
+ // Create a range representing the search scope if none was provided
6048
+ var searchScopeRange = findOptions.withinRange;
6049
+ if (!searchScopeRange) {
6050
+ searchScopeRange = api.createRange();
6051
+ searchScopeRange.selectNodeContents(this.getDocument());
6052
+ }
6053
+
6054
+ // Examine and prepare the search term
6055
+ var searchTerm = searchTermParam, isRegex = false;
6056
+ if (typeof searchTerm == "string") {
6057
+ if (!findOptions.caseSensitive) {
6058
+ searchTerm = searchTerm.toLowerCase();
6059
+ }
6060
+ } else {
6061
+ isRegex = true;
6062
+ }
6063
+
6064
+ var initialPos = session.getRangeBoundaryPosition(this, !backward);
6065
+
6066
+ // Adjust initial position if it lies outside the search scope
6067
+ var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset);
6068
+
6069
+ if (comparison === -1) {
6070
+ initialPos = session.getRangeBoundaryPosition(searchScopeRange, true);
6071
+ } else if (comparison === 1) {
6072
+ initialPos = session.getRangeBoundaryPosition(searchScopeRange, false);
6073
+ }
6074
+
6075
+ var pos = initialPos;
6076
+ var wrappedAround = false;
6077
+
6078
+ // Try to find a match and ignore invalid ones
6079
+ var findResult;
6080
+ while (true) {
6081
+ findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions);
6082
+
6083
+ if (findResult) {
6084
+ if (findResult.valid) {
6085
+ this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset);
6086
+ return true;
6087
+ } else {
6088
+ // We've found a match that is not a whole word, so we carry on searching from the point immediately
6089
+ // after the match
6090
+ pos = backward ? findResult.startPos : findResult.endPos;
6091
+ }
6092
+ } else if (findOptions.wrap && !wrappedAround) {
6093
+ // No result found but we're wrapping around and limiting the scope to the unsearched part of the range
6094
+ searchScopeRange = searchScopeRange.cloneRange();
6095
+ pos = session.getRangeBoundaryPosition(searchScopeRange, !backward);
6096
+ searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward);
6097
+ wrappedAround = true;
6098
+ } else {
6099
+ // Nothing found and we can't wrap around, so we're done
6100
+ return false;
6101
+ }
6102
+ }
6103
+ }
6104
+ ),
6105
+
6106
+ pasteHtml: function(html) {
6107
+ this.deleteContents();
6108
+ if (html) {
6109
+ var frag = this.createContextualFragment(html);
6110
+ var lastChild = frag.lastChild;
6111
+ this.insertNode(frag);
6112
+ this.collapseAfter(lastChild);
6113
+ }
6114
+ }
6115
+ });
4145
6116
 
4146
- selProto.setStart = createStartOrEndSetter(true);
4147
- selProto.setEnd = createStartOrEndSetter(false);
6117
+ /*----------------------------------------------------------------------------------------------------------------*/
4148
6118
 
4149
- // Add select() method to Range prototype. Any existing selection will be removed.
4150
- api.rangePrototype.select = function(direction) {
4151
- getSelection( this.getDocument() ).setSingleRange(this, direction);
4152
- };
6119
+ // Extensions to the Rangy Selection object
4153
6120
 
4154
- selProto.changeEachRange = function(func) {
4155
- var ranges = [];
4156
- var backward = this.isBackward();
6121
+ function createSelectionTrimmer(methodName) {
6122
+ return createEntryPointFunction(
6123
+ function(session, characterOptions) {
6124
+ var trimmed = false;
6125
+ this.changeEachRange(function(range) {
6126
+ trimmed = range[methodName](characterOptions) || trimmed;
6127
+ });
6128
+ return trimmed;
6129
+ }
6130
+ );
6131
+ }
4157
6132
 
4158
- this.eachRange(function(range) {
4159
- func(range);
4160
- ranges.push(range);
4161
- });
6133
+ extend(api.selectionPrototype, {
6134
+ expand: createEntryPointFunction(
6135
+ function(session, unit, expandOptions) {
6136
+ this.changeEachRange(function(range) {
6137
+ range.expand(unit, expandOptions);
6138
+ });
6139
+ }
6140
+ ),
6141
+
6142
+ move: createEntryPointFunction(
6143
+ function(session, unit, count, options) {
6144
+ var unitsMoved = 0;
6145
+ if (this.focusNode) {
6146
+ this.collapse(this.focusNode, this.focusOffset);
6147
+ var range = this.getRangeAt(0);
6148
+ if (!options) {
6149
+ options = {};
6150
+ }
6151
+ options.characterOptions = createOptions(options.characterOptions, defaultCaretCharacterOptions);
6152
+ unitsMoved = range.move(unit, count, options);
6153
+ this.setSingleRange(range);
6154
+ }
6155
+ return unitsMoved;
6156
+ }
6157
+ ),
4162
6158
 
4163
- this.removeAllRanges();
4164
- if (backward && ranges.length == 1) {
4165
- this.addRange(ranges[0], "backward");
4166
- } else {
4167
- this.setRanges(ranges);
4168
- }
4169
- };
6159
+ trimStart: createSelectionTrimmer("trimStart"),
6160
+ trimEnd: createSelectionTrimmer("trimEnd"),
6161
+ trim: createSelectionTrimmer("trim"),
4170
6162
 
4171
- selProto.containsNode = function(node, allowPartial) {
4172
- return this.eachRange( function(range) {
4173
- return range.containsNode(node, allowPartial);
4174
- }, true ) || false;
4175
- };
6163
+ selectCharacters: createEntryPointFunction(
6164
+ function(session, containerNode, startIndex, endIndex, direction, characterOptions) {
6165
+ var range = api.createRange(containerNode);
6166
+ range.selectCharacters(containerNode, startIndex, endIndex, characterOptions);
6167
+ this.setSingleRange(range, direction);
6168
+ }
6169
+ ),
4176
6170
 
4177
- selProto.getBookmark = function(containerNode) {
4178
- return {
4179
- backward: this.isBackward(),
4180
- rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
4181
- };
4182
- };
6171
+ saveCharacterRanges: createEntryPointFunction(
6172
+ function(session, containerNode, characterOptions) {
6173
+ var ranges = this.getAllRanges(), rangeCount = ranges.length;
6174
+ var rangeInfos = [];
4183
6175
 
4184
- selProto.moveToBookmark = function(bookmark) {
4185
- var selRanges = [];
4186
- for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
4187
- range = api.createRange(this.win);
4188
- range.moveToBookmark(rangeBookmark);
4189
- selRanges.push(range);
4190
- }
4191
- if (bookmark.backward) {
4192
- this.setSingleRange(selRanges[0], "backward");
4193
- } else {
4194
- this.setRanges(selRanges);
4195
- }
4196
- };
6176
+ var backward = rangeCount == 1 && this.isBackward();
4197
6177
 
4198
- selProto.saveRanges = function() {
4199
- return {
4200
- backward: this.isBackward(),
4201
- ranges: this.callMethodOnEachRange("cloneRange")
4202
- };
4203
- };
6178
+ for (var i = 0, len = ranges.length; i < len; ++i) {
6179
+ rangeInfos[i] = {
6180
+ characterRange: ranges[i].toCharacterRange(containerNode, characterOptions),
6181
+ backward: backward,
6182
+ characterOptions: characterOptions
6183
+ };
6184
+ }
4204
6185
 
4205
- selProto.restoreRanges = function(selRanges) {
4206
- this.removeAllRanges();
4207
- for (var i = 0, range; range = selRanges.ranges[i]; ++i) {
4208
- this.addRange(range, (selRanges.backward && i == 0));
4209
- }
4210
- };
6186
+ return rangeInfos;
6187
+ }
6188
+ ),
4211
6189
 
4212
- selProto.toHtml = function() {
4213
- var rangeHtmls = [];
4214
- this.eachRange(function(range) {
4215
- rangeHtmls.push( DomRange.toHtml(range) );
4216
- });
4217
- return rangeHtmls.join("");
4218
- };
6190
+ restoreCharacterRanges: createEntryPointFunction(
6191
+ function(session, containerNode, saved) {
6192
+ this.removeAllRanges();
6193
+ for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) {
6194
+ rangeInfo = saved[i];
6195
+ characterRange = rangeInfo.characterRange;
6196
+ range = api.createRange(containerNode);
6197
+ range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions);
6198
+ this.addRange(range, rangeInfo.backward);
6199
+ }
6200
+ }
6201
+ ),
4219
6202
 
4220
- if (features.implementsTextRange) {
4221
- selProto.getNativeTextRange = function() {
4222
- var sel, textRange;
4223
- if ( (sel = this.docSelection) ) {
4224
- var range = sel.createRange();
4225
- if (isTextRange(range)) {
4226
- return range;
4227
- } else {
4228
- throw module.createError("getNativeTextRange: selection is a control selection");
6203
+ text: createEntryPointFunction(
6204
+ function(session, characterOptions) {
6205
+ var rangeTexts = [];
6206
+ for (var i = 0, len = this.rangeCount; i < len; ++i) {
6207
+ rangeTexts[i] = this.getRangeAt(i).text(characterOptions);
4229
6208
  }
4230
- } else if (this.rangeCount > 0) {
4231
- return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
4232
- } else {
4233
- throw module.createError("getNativeTextRange: selection contains no range");
6209
+ return rangeTexts.join("");
4234
6210
  }
4235
- };
4236
- }
6211
+ )
6212
+ });
4237
6213
 
4238
- function inspect(sel) {
4239
- var rangeInspects = [];
4240
- var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
4241
- var focus = new DomPosition(sel.focusNode, sel.focusOffset);
4242
- var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
6214
+ /*----------------------------------------------------------------------------------------------------------------*/
4243
6215
 
4244
- if (typeof sel.rangeCount != "undefined") {
4245
- for (var i = 0, len = sel.rangeCount; i < len; ++i) {
4246
- rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
4247
- }
4248
- }
4249
- return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
4250
- ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
4251
- }
6216
+ // Extensions to the core rangy object
4252
6217
 
4253
- selProto.getName = function() {
4254
- return "WrappedSelection";
6218
+ api.innerText = function(el, characterOptions) {
6219
+ var range = api.createRange(el);
6220
+ range.selectNodeContents(el);
6221
+ var text = range.text(characterOptions);
6222
+ return text;
4255
6223
  };
4256
6224
 
4257
- selProto.inspect = function() {
4258
- return inspect(this);
4259
- };
6225
+ api.createWordIterator = function(startNode, startOffset, iteratorOptions) {
6226
+ var session = getSession();
6227
+ iteratorOptions = createNestedOptions(iteratorOptions, defaultWordIteratorOptions);
6228
+ var startPos = session.getPosition(startNode, startOffset);
6229
+ var tokenizedTextProvider = createTokenizedTextProvider(startPos, iteratorOptions.characterOptions, iteratorOptions.wordOptions);
6230
+ var backward = isDirectionBackward(iteratorOptions.direction);
4260
6231
 
4261
- selProto.detach = function() {
4262
- actOnCachedSelection(this.win, "delete");
4263
- deleteProperties(this);
4264
- };
6232
+ return {
6233
+ next: function() {
6234
+ return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken();
6235
+ },
4265
6236
 
4266
- WrappedSelection.detachAll = function() {
4267
- actOnCachedSelection(null, "deleteAll");
6237
+ dispose: function() {
6238
+ tokenizedTextProvider.dispose();
6239
+ this.next = function() {};
6240
+ }
6241
+ };
4268
6242
  };
4269
6243
 
4270
- WrappedSelection.inspect = inspect;
4271
- WrappedSelection.isDirectionBackward = isDirectionBackward;
6244
+ /*----------------------------------------------------------------------------------------------------------------*/
4272
6245
 
4273
- api.Selection = WrappedSelection;
6246
+ api.noMutation = function(func) {
6247
+ var session = getSession();
6248
+ func(session);
6249
+ endSession();
6250
+ };
4274
6251
 
4275
- api.selectionPrototype = selProto;
6252
+ api.noMutation.createEntryPointFunction = createEntryPointFunction;
4276
6253
 
4277
- api.addShimListener(function(win) {
4278
- if (typeof win.getSelection == "undefined") {
4279
- win.getSelection = function() {
4280
- return getSelection(win);
4281
- };
4282
- }
4283
- win = null;
4284
- });
6254
+ api.textRange = {
6255
+ isBlockNode: isBlockNode,
6256
+ isCollapsedWhitespaceNode: isCollapsedWhitespaceNode,
6257
+
6258
+ createPosition: createEntryPointFunction(
6259
+ function(session, node, offset) {
6260
+ return session.getPosition(node, offset);
6261
+ }
6262
+ )
6263
+ };
4285
6264
  });
4286
-
4287
-
4288
- /*----------------------------------------------------------------------------------------------------------------*/
4289
-
4290
- // Wait for document to load before initializing
4291
- var docReady = false;
4292
-
4293
- var loadHandler = function(e) {
4294
- if (!docReady) {
4295
- docReady = true;
4296
- if (!api.initialized && api.config.autoInitialize) {
4297
- init();
4298
- }
4299
- }
4300
- };
4301
-
4302
- if (isBrowser) {
4303
- // Test whether the document has already been loaded and initialize immediately if so
4304
- if (document.readyState == "complete") {
4305
- loadHandler();
4306
- } else {
4307
- if (isHostMethod(document, "addEventListener")) {
4308
- document.addEventListener("DOMContentLoaded", loadHandler, false);
4309
- }
4310
-
4311
- // Add a fallback in case the DOMContentLoaded event isn't supported
4312
- addListener(window, "load", loadHandler);
4313
- }
4314
- }
4315
-
4316
- return api;
6265
+
6266
+ return rangy;
4317
6267
  }, this);;/**
4318
6268
  * Selection save and restore module for Rangy.
4319
6269
  * Saves and restores user selections using marker invisible elements in the DOM.
@@ -4730,7 +6680,11 @@ wysihtml5.browser = (function() {
4730
6680
  if (navigator.appName == 'Microsoft Internet Explorer') {
4731
6681
  re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
4732
6682
  } else if (navigator.appName == 'Netscape') {
4733
- re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
6683
+ if (navigator.userAgent.indexOf("Trident") > -1) {
6684
+ re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
6685
+ } else if ((/Edge\/(\d+)./i).test(navigator.userAgent)) {
6686
+ re = /Edge\/(\d+)./i;
6687
+ }
4734
6688
  }
4735
6689
 
4736
6690
  if (re && re.exec(navigator.userAgent) != null) {
@@ -4813,7 +6767,7 @@ wysihtml5.browser = (function() {
4813
6767
  * Firefox sometimes shows a huge caret in the beginning after focusing
4814
6768
  */
4815
6769
  displaysCaretInEmptyContentEditableCorrectly: function() {
4816
- return isIE();
6770
+ return isIE(12, ">");
4817
6771
  },
4818
6772
 
4819
6773
  /**
@@ -4885,8 +6839,8 @@ wysihtml5.browser = (function() {
4885
6839
  // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
4886
6840
  // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
4887
6841
  // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
4888
- "insertUnorderedList": isIE(9, ">="),
4889
- "insertOrderedList": isIE(9, ">=")
6842
+ "insertUnorderedList": isIE(9, ">=") || isIE(12, "<="),
6843
+ "insertOrderedList": isIE(9, ">=")|| isIE(12, "<=")
4890
6844
  };
4891
6845
 
4892
6846
  // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
@@ -5033,7 +6987,7 @@ wysihtml5.browser = (function() {
5033
6987
  * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
5034
6988
  */
5035
6989
  doesAsyncFocus: function() {
5036
- return isIE();
6990
+ return isIE(12, ">");
5037
6991
  },
5038
6992
 
5039
6993
  /**
@@ -7953,6 +9907,10 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
7953
9907
  }
7954
9908
  },
7955
9909
 
9910
+ destroy: function() {
9911
+
9912
+ },
9913
+
7956
9914
  // creates a new contenteditable and initiates it
7957
9915
  _createElement: function() {
7958
9916
  var element = doc.createElement("div");
@@ -10555,6 +12513,9 @@ wysihtml5.quirks.ensureProperClearing = (function() {
10555
12513
  this._selectLine_W3C();
10556
12514
  } else if (this.doc.selection) {
10557
12515
  this._selectLine_MSIE();
12516
+ } else {
12517
+ // For IE Edge as it ditched the old api and did not fully implement the new one (as expected)
12518
+ this._selectLineUniversal();
10558
12519
  }
10559
12520
  },
10560
12521
 
@@ -10562,9 +12523,20 @@ wysihtml5.quirks.ensureProperClearing = (function() {
10562
12523
  * See https://developer.mozilla.org/en/DOM/Selection/modify
10563
12524
  */
10564
12525
  _selectLine_W3C: function() {
10565
- var selection = this.win.getSelection();
12526
+ var selection = this.win.getSelection(),
12527
+ initialBoundry = [selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset];
12528
+
10566
12529
  selection.modify("move", "left", "lineboundary");
10567
12530
  selection.modify("extend", "right", "lineboundary");
12531
+
12532
+ // IF lineboundary extending did not change selection try universal fallback (FF fails sometimes without a reason)
12533
+ if (selection.anchorNode === initialBoundry[0] &&
12534
+ selection.anchorOffset === initialBoundry[1] &&
12535
+ selection.focusNode === initialBoundry[2] &&
12536
+ selection.focusOffset === initialBoundry[3]
12537
+ ) {
12538
+ this._selectLineUniversal();
12539
+ }
10568
12540
  },
10569
12541
 
10570
12542
  // collapses selection to current line beginning or end
@@ -10584,8 +12556,75 @@ wysihtml5.quirks.ensureProperClearing = (function() {
10584
12556
  }
10585
12557
  },
10586
12558
 
12559
+ getRangeRect: function(r) {
12560
+ var textNode = this.doc.createTextNode("i"),
12561
+ testNode = this.doc.createTextNode("i"),
12562
+ rect, cr;
12563
+
12564
+ /*testNode.style.visibility = "hidden";
12565
+ testNode.style.width = "0px";
12566
+ testNode.style.display = "inline-block";
12567
+ testNode.style.overflow = "hidden";
12568
+ testNode.appendChild(textNode);*/
12569
+
12570
+ if (r.collapsed) {
12571
+ r.insertNode(testNode);
12572
+ r.selectNode(testNode);
12573
+ rect = r.nativeRange.getBoundingClientRect();
12574
+ r.deleteContents();
12575
+
12576
+ } else {
12577
+ rect = r.nativeRange.getBoundingClientRect();
12578
+ }
12579
+
12580
+ return rect;
12581
+
12582
+ },
12583
+
12584
+ _selectLineUniversal: function() {
12585
+ var s = this.getSelection(),
12586
+ r = s.getRangeAt(0),
12587
+ rect,
12588
+ startRange, endRange, testRange,
12589
+ count = 0,
12590
+ amount, testRect, found;
12591
+
12592
+ startRange = r.cloneRange();
12593
+ endRange = r.cloneRange();
12594
+
12595
+ if (r.collapsed) {
12596
+ r.expand('word', 1);
12597
+ rect = r.nativeRange.getBoundingClientRect();
12598
+ }
12599
+
12600
+ do {
12601
+ amount = r.moveStart('character', -1);
12602
+ testRect = r.nativeRange.getBoundingClientRect();
12603
+ if (!testRect || Math.floor(testRect.top) !== Math.floor(rect.top)) {
12604
+ r.moveStart('character', 1);
12605
+ found = true;
12606
+ }
12607
+ count++;
12608
+ } while (amount !== 0 && !found && count < 2000);
12609
+
12610
+ count = 0;
12611
+ found = false;
12612
+ rect = r.nativeRange.getBoundingClientRect();
12613
+ do {
12614
+ amount = r.moveEnd('character', 1);
12615
+ testRect = r.nativeRange.getBoundingClientRect();
12616
+ if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) {
12617
+ r.moveEnd('character', -1);
12618
+ found = true;
12619
+ }
12620
+ count++;
12621
+ } while (amount !== 0 && !found && count < 2000);
12622
+
12623
+ r.select();
12624
+ },
12625
+
10587
12626
  _selectLine_MSIE: function() {
10588
- var range = this.doc.selection.createRange(),
12627
+ var range = this.doc.selection && this.doc.selection.createRange ? this.doc.selection.createRange() : this.doc.createRange(),
10589
12628
  rangeTop = range.boundingTop,
10590
12629
  scrollWidth = this.doc.body.scrollWidth,
10591
12630
  rangeBottom,
@@ -10594,6 +12633,8 @@ wysihtml5.quirks.ensureProperClearing = (function() {
10594
12633
  i,
10595
12634
  j;
10596
12635
 
12636
+ window.r = range;
12637
+
10597
12638
  if (!range.moveToPoint) {
10598
12639
  return;
10599
12640
  }
@@ -12290,16 +14331,19 @@ wysihtml5.Commands = Base.extend(
12290
14331
 
12291
14332
  // Restore correct selection
12292
14333
  if (bookmark) {
14334
+ wysihtml5.dom.removeInvisibleSpaces(composer.element);
12293
14335
  rangy.restoreSelection(bookmark);
12294
14336
  } else {
14337
+ wysihtml5.dom.removeInvisibleSpaces(composer.element);
14338
+ // Set selection to beging inside first created block element (beginning of it) and end inside (and after content) of last block element
14339
+ // TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway
12295
14340
  range = composer.selection.createRange();
12296
- range.setStartBefore(newBlockElements[0]);
12297
- range.setEndAfter(newBlockElements[newBlockElements.length - 1]);
12298
- composer.selection.setSelection(range);
14341
+ range.setStart(newBlockElements[0], 0);
14342
+ var lastEl = newBlockElements[newBlockElements.length - 1],
14343
+ lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 : lastEl.length || 0;
14344
+ range.setEnd(lastEl, lastOffset);
14345
+ range.select();
12299
14346
  }
12300
-
12301
- wysihtml5.dom.removeInvisibleSpaces(composer.element);
12302
-
12303
14347
  },
12304
14348
 
12305
14349
  // If properties as null is passed returns status describing all block level elements
@@ -13327,27 +15371,33 @@ wysihtml5.Commands = Base.extend(
13327
15371
  };
13328
15372
 
13329
15373
  var createListFallback = function(nodeName, composer) {
13330
- // Fallback for Create list
13331
- composer.selection.executeAndRestoreRangy(function() {
13332
- var tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
13333
- tempElement = composer.selection.deblockAndSurround({
13334
- "nodeName": "div",
13335
- "className": tempClassName
13336
- }),
13337
- isEmpty, list;
15374
+ var sel;
13338
15375
 
13339
- // This space causes new lists to never break on enter
13340
- var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
13341
- tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "");
15376
+ if (!composer.selection.isCollapsed()) {
15377
+ sel = rangy.saveSelection(composer.win);
15378
+ }
13342
15379
 
13343
- if (tempElement) {
13344
- isEmpty = wysihtml5.lang.array(["", "<br>", wysihtml5.INVISIBLE_SPACE]).contains(tempElement.innerHTML);
13345
- list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.classNames.uneditableContainer);
13346
- if (isEmpty) {
13347
- composer.selection.selectNode(list.querySelector("li"), true);
13348
- }
15380
+ // Fallback for Create list
15381
+ var tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
15382
+ tempElement = composer.selection.deblockAndSurround({
15383
+ "nodeName": "div",
15384
+ "className": tempClassName
15385
+ }),
15386
+ isEmpty, list;
15387
+
15388
+ // This space causes new lists to never break on enter
15389
+ var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
15390
+ tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "");
15391
+ if (tempElement) {
15392
+ isEmpty = (/^(\s|(<br>))+$/i).test(tempElement.innerHTML);
15393
+ list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.classNames.uneditableContainer);
15394
+ if (sel) {
15395
+ rangy.restoreSelection(sel);
15396
+ }
15397
+ if (isEmpty) {
15398
+ composer.selection.selectNode(list.querySelector("li"), true);
13349
15399
  }
13350
- });
15400
+ }
13351
15401
  };
13352
15402
 
13353
15403
  return {
@@ -13357,7 +15407,6 @@ wysihtml5.Commands = Base.extend(
13357
15407
  selectedNode = composer.selection.getSelectedNode(),
13358
15408
  list = findListEl(selectedNode, nodeName, composer);
13359
15409
 
13360
-
13361
15410
  if (!list.el) {
13362
15411
  if (composer.commands.support(cmd)) {
13363
15412
  doc.execCommand(cmd, false, null);
@@ -13527,6 +15576,24 @@ wysihtml5.Commands = Base.extend(
13527
15576
  };
13528
15577
 
13529
15578
  })(wysihtml5);
15579
+ ;(function(wysihtml5) {
15580
+
15581
+ var nodeOptions = {
15582
+ styleProperty: "textAlign",
15583
+ styleValue: "justify",
15584
+ toggle: true
15585
+ };
15586
+
15587
+ wysihtml5.commands.alignJustifyStyle = {
15588
+ exec: function(composer, command) {
15589
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", nodeOptions);
15590
+ },
15591
+
15592
+ state: function(composer, command) {
15593
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", nodeOptions);
15594
+ }
15595
+ };
15596
+ })(wysihtml5);
13530
15597
  ;(function(wysihtml5){
13531
15598
  wysihtml5.commands.redo = {
13532
15599
  exec: function(composer) {
@@ -14408,8 +16475,12 @@ wysihtml5.views.View = Base.extend(
14408
16475
  var that = this,
14409
16476
  supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
14410
16477
  supportsAutoLinking = browser.doesAutoLinkingInContentEditable();
16478
+
14411
16479
  if (supportsDisablingOfAutoLinking) {
14412
- this.commands.exec("autoUrlDetect", false);
16480
+ // I have no idea why IE edge deletes element content here when calling the command,
16481
+ var tmpHTML = this.element.innerHTML;
16482
+ this.commands.exec("AutoUrlDetect", false, false);
16483
+ this.element.innerHTML = tmpHTML;
14413
16484
  }
14414
16485
 
14415
16486
  if (!this.config.autoLink) {
@@ -15092,7 +17163,7 @@ wysihtml5.views.View = Base.extend(
15092
17163
  target, parent;
15093
17164
 
15094
17165
  // Select all (meta/ctrl + a)
15095
- if ((event.ctrlKey || event.metaKey) && keyCode === 65) {
17166
+ if ((event.ctrlKey || event.metaKey) && !event.altKey && keyCode === 65) {
15096
17167
  this.selection.selectAll();
15097
17168
  event.preventDefault();
15098
17169
  return;
@@ -15152,9 +17223,10 @@ wysihtml5.views.View = Base.extend(
15152
17223
  // If present enableObjectResizing and enableInlineTableEditing command should be called with false to prevent native table handlers
15153
17224
  var initTableHandling = function () {
15154
17225
  var hideHandlers = function () {
17226
+ window.removeEventListener('load', hideHandlers);
15155
17227
  this.doc.execCommand("enableObjectResizing", false, "false");
15156
17228
  this.doc.execCommand("enableInlineTableEditing", false, "false");
15157
- },
17229
+ }.bind(this),
15158
17230
  iframeInitiator = (function() {
15159
17231
  hideHandlers.call(this);
15160
17232
  removeListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
@@ -15167,9 +17239,7 @@ wysihtml5.views.View = Base.extend(
15167
17239
  if (this.sandbox.getIframe) {
15168
17240
  addListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
15169
17241
  } else {
15170
- setTimeout((function() {
15171
- hideHandlers.call(this);
15172
- }).bind(this), 0);
17242
+ window.addEventListener('load', hideHandlers);
15173
17243
  }
15174
17244
  }
15175
17245
  this.tableSelection = wysihtml5.quirks.tableCellsSelection(this.element, this.parent);
@@ -15643,6 +17713,16 @@ wysihtml5.views.View = Base.extend(
15643
17713
  return this.currentView.hasPlaceholderSet();
15644
17714
  },
15645
17715
 
17716
+ destroy: function() {
17717
+ if (this.composer && this.composer.sandbox) {
17718
+ this.composer.sandbox.destroy();
17719
+ }
17720
+ if (this.toolbar) {
17721
+ this.toolbar.destroy();
17722
+ }
17723
+ this.off();
17724
+ },
17725
+
15646
17726
  parse: function(htmlOrElement, clearInternals) {
15647
17727
  var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
15648
17728
  var returnValue = this.config.parser(htmlOrElement, {
@@ -16211,12 +18291,15 @@ wysihtml5.views.View = Base.extend(
16211
18291
  }
16212
18292
  });
16213
18293
 
16214
- this.container.ownerDocument.addEventListener("click", function(event) {
18294
+ this._ownerDocumentClick = function(event) {
16215
18295
  if (!wysihtml5.dom.contains(that.container, event.target) && !wysihtml5.dom.contains(that.composer.element, event.target)) {
16216
18296
  that._updateLinkStates();
16217
18297
  that._preventInstantFocus();
16218
18298
  }
16219
- }, false);
18299
+ };
18300
+
18301
+ this.container.ownerDocument.addEventListener("click", this._ownerDocumentClick, false);
18302
+ this.editor.on("destroy:composer", this.destroy.bind(this));
16220
18303
 
16221
18304
  if (this.editor.config.handleTables) {
16222
18305
  editor.on("tableselect:composer", function() {
@@ -16241,6 +18324,10 @@ wysihtml5.views.View = Base.extend(
16241
18324
  });
16242
18325
  },
16243
18326
 
18327
+ destroy: function() {
18328
+ this.container.ownerDocument.removeEventListener("click", this._ownerDocumentClick, false);
18329
+ },
18330
+
16244
18331
  _hideAllDialogs: function() {
16245
18332
  var commandMapping = this.commandMapping;
16246
18333
  for (var i in commandMapping) {