wysihtml-rails 0.5.0.beta13 → 0.5.0.beta14

Sign up to get free protection for your applications and to get access to all the features.
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) {