wysihtml-rails 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  module Wysihtml
2
2
  module Rails
3
- VERSION = "0.5.3"
3
+ VERSION = "0.5.4"
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license wysihtml v0.5.3
2
+ * @license wysihtml v0.5.4
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.3",
13
+ version: "0.5.4",
14
14
 
15
15
  // namespaces
16
16
  commands: {},
@@ -7938,11 +7938,6 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
7938
7938
  return nodes;
7939
7939
  }
7940
7940
 
7941
- // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
7942
- function isBookmark(n) {
7943
- return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary');
7944
- }
7945
-
7946
7941
  wysihtml5.dom.domNode = function(node) {
7947
7942
  var defaultNodeTypes = [wysihtml5.ELEMENT_NODE, wysihtml5.TEXT_NODE];
7948
7943
 
@@ -7951,7 +7946,12 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
7951
7946
  is: {
7952
7947
  emptyTextNode: function(ignoreWhitespace) {
7953
7948
  var regx = ignoreWhitespace ? (/^\s*$/g) : (/^[\r\n]*$/g);
7954
- return node.nodeType === wysihtml5.TEXT_NODE && (regx).test(node.data);
7949
+ return node && node.nodeType === wysihtml5.TEXT_NODE && (regx).test(node.data);
7950
+ },
7951
+
7952
+ // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
7953
+ rangyBookmark: function() {
7954
+ return node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary');
7955
7955
  },
7956
7956
 
7957
7957
  visible: function() {
@@ -7990,7 +7990,7 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
7990
7990
  }
7991
7991
 
7992
7992
  if (
7993
- isBookmark(prevNode) || // is Rangy temporary boomark element (bypass)
7993
+ wysihtml5.dom.domNode(prevNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass)
7994
7994
  (!wysihtml5.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
7995
7995
  (options && options.ignoreBlankTexts && wysihtml5.dom.domNode(prevNode).is.emptyTextNode(true)) // Blank text nodes bypassed if set
7996
7996
  ) {
@@ -8010,7 +8010,7 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
8010
8010
  }
8011
8011
 
8012
8012
  if (
8013
- isBookmark(nextNode) || // is Rangy temporary boomark element (bypass)
8013
+ wysihtml5.dom.domNode(nextNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass)
8014
8014
  (!wysihtml5.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
8015
8015
  (options && options.ignoreBlankTexts && wysihtml5.dom.domNode(nextNode).is.emptyTextNode(true)) // blank text nodes bypassed if set
8016
8016
  ) {
@@ -11762,7 +11762,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
11762
11762
  }
11763
11763
  };
11764
11764
 
11765
- blankNode.appendChild(document.createTextNode(wysihtml5.INVISIBLE_SPACE));
11765
+ blankNode.appendChild(container.ownerDocument.createTextNode(wysihtml5.INVISIBLE_SPACE));
11766
11766
  blankNode.className = '_wysihtml5-temp-caret-fix';
11767
11767
  blankNode.style.display = 'block';
11768
11768
  blankNode.style.minWidth = '1px';
@@ -12198,10 +12198,17 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12198
12198
  nextNode = caretNode.nextSibling;
12199
12199
  }
12200
12200
  } else {
12201
- caretNode = r[0].startContainer;
12201
+ if (r[0].startOffset === 0 && r[0].startContainer.previousSibling) {
12202
+ caretNode = r[0].startContainer.previousSibling;
12203
+ if (caretNode.nodeType === 3) {
12204
+ offset = caretNode.data.length;
12205
+ }
12206
+ } else {
12207
+ caretNode = r[0].startContainer;
12208
+ offset = r[0].startOffset;
12209
+ }
12202
12210
  prevNode = caretNode.previousSibling;
12203
12211
  nextNode = caretNode.nextSibling;
12204
- offset = r[0].startOffset;
12205
12212
  }
12206
12213
 
12207
12214
  return {
@@ -12647,8 +12654,8 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12647
12654
  if (wysihtml5.browser.supportsSelectionModify()) {
12648
12655
  this._selectLine_W3C();
12649
12656
  } else if (r.nativeRange && r.nativeRange.getBoundingClientRect) {
12650
- // For IE Edge as it ditched the old api and did not fully implement the new one (as expected)*/
12651
- this._selectLineUniversal();
12657
+ // For IE Edge as it ditched the old api and did not fully implement the new one (as expected)
12658
+ this._selectLineUniversal();
12652
12659
  }
12653
12660
  },
12654
12661
 
@@ -12664,7 +12671,6 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12664
12671
  } else {
12665
12672
  return node.data && node.data.length || 0;
12666
12673
  }
12667
- // body...
12668
12674
  },
12669
12675
  anode = s.anchorNode.nodeType === 1 ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
12670
12676
  fnode = s.focusNode.nodeType === 1 ? s.focusNode.childNodes[s.focusOffset] : s.focusNode;
@@ -12773,7 +12779,16 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12773
12779
  r.moveEnd('character', 1);
12774
12780
  } else if (r.startContainer.nodeType === 1 && r.startContainer.childNodes[r.startOffset] && r.startContainer.childNodes[r.startOffset].nodeType === 3 && r.startContainer.childNodes[r.startOffset].data.length > 0) {
12775
12781
  r.moveEnd('character', 1);
12776
- } else if (r.startOffset > 0 && ( r.startContainer.nodeType === 3 || (r.startContainer.nodeType === 1 && !isLineBreakingElement(prevNode(r.startContainer.childNodes[r.startOffset - 1]))))) {
12782
+ } else if (
12783
+ r.startOffset > 0 &&
12784
+ (
12785
+ r.startContainer.nodeType === 3 ||
12786
+ (
12787
+ r.startContainer.nodeType === 1 &&
12788
+ !isLineBreakingElement(prevNode(r.startContainer.childNodes[r.startOffset - 1]))
12789
+ )
12790
+ )
12791
+ ) {
12777
12792
  r.moveStart('character', -1);
12778
12793
  }
12779
12794
  }
@@ -12783,6 +12798,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12783
12798
 
12784
12799
  // Is probably just empty line as can not be expanded
12785
12800
  rect = r.nativeRange.getBoundingClientRect();
12801
+ // If startnode is not line break allready move the start position of range by -1 character until clientRect top changes;
12786
12802
  do {
12787
12803
  amount = r.moveStart('character', -1);
12788
12804
  testRect = r.nativeRange.getBoundingClientRect();
@@ -12793,31 +12809,31 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12793
12809
  }
12794
12810
  count++;
12795
12811
  } while (amount !== 0 && !found && count < 2000);
12796
-
12797
12812
  count = 0;
12798
12813
  found = false;
12799
12814
  rect = r.nativeRange.getBoundingClientRect();
12800
- do {
12801
- amount = r.moveEnd('character', 1);
12802
- testRect = r.nativeRange.getBoundingClientRect();
12803
- if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) {
12804
- r.moveEnd('character', -1);
12805
-
12806
- // Fix a IE line end marked by linebreak element although caret is before it
12807
- // If causes problems should be changed to be applied only to IE
12808
- if (r.endContainer && r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] && r.endContainer.childNodes[r.endOffset].nodeType === 1 && r.endContainer.childNodes[r.endOffset].nodeName === "BR" && r.endContainer.childNodes[r.endOffset].previousSibling) {
12809
- if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 1) {
12810
- r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.childNodes.length);
12811
- } else if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 3) {
12812
- r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.data.length);
12815
+
12816
+ if (r.endContainer !== this.contain || (this.contain.lastChild && this.contain.childNodes[r.endOffset] !== this.contain.lastChild)) {
12817
+ do {
12818
+ amount = r.moveEnd('character', 1);
12819
+ testRect = r.nativeRange.getBoundingClientRect();
12820
+ if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) {
12821
+ r.moveEnd('character', -1);
12822
+
12823
+ // Fix a IE line end marked by linebreak element although caret is before it
12824
+ // If causes problems should be changed to be applied only to IE
12825
+ if (r.endContainer && r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] && r.endContainer.childNodes[r.endOffset].nodeType === 1 && r.endContainer.childNodes[r.endOffset].nodeName === "BR" && r.endContainer.childNodes[r.endOffset].previousSibling) {
12826
+ if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 1) {
12827
+ r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.childNodes.length);
12828
+ } else if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 3) {
12829
+ r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.data.length);
12830
+ }
12813
12831
  }
12832
+ found = true;
12814
12833
  }
12815
-
12816
- found = true;
12817
- }
12818
- count++;
12819
- } while (amount !== 0 && !found && count < 2000);
12820
-
12834
+ count++;
12835
+ } while (amount !== 0 && !found && count < 2000);
12836
+ }
12821
12837
  r.select();
12822
12838
  this.includeRangyRangeHelpers();
12823
12839
  },
@@ -14159,7 +14175,7 @@ wysihtml5.Commands = Base.extend(
14159
14175
  nbIdx;
14160
14176
 
14161
14177
  for (var i = elements.length; i--;) {
14162
- if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "") {
14178
+ if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "" && (newBlockElements.length === 0 || elements[i] !== newBlockElements[newBlockElements.length - 1])) {
14163
14179
  // If cleanup removes some new block elements. remove them from newblocks array too
14164
14180
  nbIdx = wysihtml5.lang.array(newBlockElements).indexOf(elements[i]);
14165
14181
  if (nbIdx > -1) {
@@ -14496,8 +14512,13 @@ wysihtml5.Commands = Base.extend(
14496
14512
  }
14497
14513
  composer.selection.splitElementAtCaret(outerInlines.parent, fragment);
14498
14514
  } else {
14499
- // Otherwise just insert
14515
+ var fc = fragment.firstChild,
14516
+ lc = fragment.lastChild;
14517
+
14500
14518
  range.insertNode(fragment);
14519
+ // restore range position as it might get lost in webkit sometimes
14520
+ range.setStartBefore(fc);
14521
+ range.setEndAfter(lc);
14501
14522
  }
14502
14523
  }
14503
14524
  }
@@ -14626,7 +14647,21 @@ wysihtml5.Commands = Base.extend(
14626
14647
  startNode = getRangeNode(r.startContainer, r.startOffset),
14627
14648
  endNode = getRangeNode(r.endContainer, r.endOffset),
14628
14649
  prevNode = (r.startContainer === startNode && startNode.nodeType === 3 && !isWhitespaceBefore(startNode, r.startOffset)) ? startNode : wysihtml5.dom.domNode(startNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}),
14629
- nextNode = ((r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] === endNode) || (r.endContainer === endNode && endNode.nodeType === 3 && !isWhitespaceAfter(endNode, r.endOffset))) ? endNode : wysihtml5.dom.domNode(getRangeNode(r.endContainer, r.endOffset)).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
14650
+ nextNode = (
14651
+ (
14652
+ r.endContainer.nodeType === 1 &&
14653
+ r.endContainer.childNodes[r.endOffset] === endNode &&
14654
+ (
14655
+ endNode.nodeType === 1 ||
14656
+ !isWhitespaceAfter(endNode, r.endOffset) &&
14657
+ !wysihtml5.dom.domNode(endNode).is.rangyBookmark()
14658
+ )
14659
+ ) || (
14660
+ r.endContainer === endNode &&
14661
+ endNode.nodeType === 3 &&
14662
+ !isWhitespaceAfter(endNode, r.endOffset)
14663
+ )
14664
+ ) ? endNode : wysihtml5.dom.domNode(endNode).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
14630
14665
  content = r.extractContents(),
14631
14666
  fragment = composer.doc.createDocumentFragment(),
14632
14667
  similarOuterBlock = similarOptions ? wysihtml5.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
@@ -14635,6 +14670,11 @@ wysihtml5.Commands = Base.extend(
14635
14670
  wrapper, blocks, children,
14636
14671
  firstc, lastC;
14637
14672
 
14673
+ if (wysihtml5.dom.domNode(nextNode).is.rangyBookmark()) {
14674
+ endNode = nextNode;
14675
+ nextNode = endNode.nextSibling;
14676
+ }
14677
+
14638
14678
  trimBlankTextsAndBreaks(content);
14639
14679
 
14640
14680
  if (options && options.nodeName === "BLOCKQUOTE") {
@@ -14684,6 +14724,16 @@ wysihtml5.Commands = Base.extend(
14684
14724
  }
14685
14725
  injectFragmentToRange(fragment, r, composer, firstOuterBlock);
14686
14726
  removeSurroundingLineBreaks(prevNode, nextNode, composer);
14727
+
14728
+ // Fix webkit madness by inserting linebreak rangy after cursor marker to blank last block
14729
+ // (if it contains rangy bookmark, so selection can be restored later correctly)
14730
+ if (blocks.length > 0 &&
14731
+ (
14732
+ typeof blocks[blocks.length - 1].lastChild === "undefined" || wysihtml5.dom.domNode(blocks[blocks.length - 1].lastChild).is.rangyBookmark()
14733
+ )
14734
+ ) {
14735
+ blocks[blocks.length - 1].appendChild(composer.doc.createElement('br'));
14736
+ }
14687
14737
  return blocks;
14688
14738
  }
14689
14739
 
@@ -15832,7 +15882,9 @@ wysihtml5.Commands = Base.extend(
15832
15882
  for (var i = innerLists.length; i--;) {
15833
15883
  wysihtml5.dom.resolveList(innerLists[i], composer.config.useLineBreaks);
15834
15884
  }
15835
- wysihtml5.dom.resolveList(el, composer.config.useLineBreaks);
15885
+ if (innerLists.length === 0) {
15886
+ wysihtml5.dom.resolveList(el, composer.config.useLineBreaks);
15887
+ }
15836
15888
  }
15837
15889
  });
15838
15890
  };
@@ -15900,8 +15952,34 @@ wysihtml5.Commands = Base.extend(
15900
15952
  exec: function(composer, command, nodeName) {
15901
15953
  var doc = composer.doc,
15902
15954
  cmd = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList",
15903
- selectedNode = composer.selection.getSelectedNode(),
15904
- list = findListEl(selectedNode, nodeName, composer);
15955
+ s = composer.selection.getSelection(),
15956
+ anode = s.anchorNode.nodeType === 1 && s.anchorNode.firstChild ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
15957
+ fnode = s.focusNode.nodeType === 1 && s.focusNode.firstChild ? s.focusNode.childNodes[s.focusOffset] || s.focusNode.lastChild : s.focusNode,
15958
+ selectedNode, list;
15959
+
15960
+ if (s.isBackwards()) {
15961
+ // swap variables
15962
+ anode = [fnode, fnode = anode][0];
15963
+ }
15964
+
15965
+ if (wysihtml5.dom.domNode(fnode).is.emptyTextNode(true) && fnode) {
15966
+ fnode = wysihtml5.dom.domNode(fnode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
15967
+ }
15968
+ if (wysihtml5.dom.domNode(anode).is.emptyTextNode(true) && anode) {
15969
+ anode = wysihtml5.dom.domNode(anode).next({nodeTypes: [1,3], ignoreBlankTexts: true});
15970
+ }
15971
+
15972
+ if (anode && fnode) {
15973
+ if (anode === fnode) {
15974
+ selectedNode = anode;
15975
+ } else {
15976
+ selectedNode = wysihtml5.dom.domNode(anode).commonAncestor(fnode, composer.element);
15977
+ }
15978
+ } else {
15979
+ selectedNode = composer.selection.getSelectedNode();
15980
+ }
15981
+
15982
+ list = findListEl(selectedNode, nodeName, composer);
15905
15983
 
15906
15984
  if (!list.el) {
15907
15985
  if (composer.commands.support(cmd)) {
@@ -17390,155 +17468,248 @@ wysihtml5.views.View = Base.extend(
17390
17468
  "73": "italic", // I
17391
17469
  "85": "underline" // U
17392
17470
  };
17471
+
17472
+ var actions = {
17393
17473
 
17394
- // Adds multiple eventlisteners to target, bound to one callback
17395
- // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
17396
- var addListeners = function (target, events, callback) {
17397
- for(var i = 0, max = events.length; i < max; i++) {
17398
- target.addEventListener(events[i], callback, false);
17399
- }
17400
- };
17474
+ // Adds multiple eventlisteners to target, bound to one callback
17475
+ // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
17476
+ addListeners: function (target, events, callback) {
17477
+ for(var i = 0, max = events.length; i < max; i++) {
17478
+ target.addEventListener(events[i], callback, false);
17479
+ }
17480
+ },
17401
17481
 
17402
- // Removes multiple eventlisteners from target, bound to one callback
17403
- // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
17404
- var removeListeners = function (target, events, callback) {
17405
- for(var i = 0, max = events.length; i < max; i++) {
17406
- target.removeEventListener(events[i], callback, false);
17407
- }
17408
- };
17482
+ // Removes multiple eventlisteners from target, bound to one callback
17483
+ // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
17484
+ removeListeners: function (target, events, callback) {
17485
+ for(var i = 0, max = events.length; i < max; i++) {
17486
+ target.removeEventListener(events[i], callback, false);
17487
+ }
17488
+ },
17409
17489
 
17410
- // Override for giving user ability to delete last line break in table cell
17411
- var fixLastBrDeletionInTable = function(composer, force) {
17412
- if (composer.selection.caretIsLastInSelection()) {
17413
- var sel = composer.selection.getSelection(),
17414
- aNode = sel.anchorNode;
17415
- if (aNode && aNode.nodeType === 1 && (wysihtml5.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) {
17416
- var nextNode = aNode.childNodes[sel.anchorOffset];
17417
- if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") {
17418
- nextNode.parentNode.removeChild(nextNode);
17419
- return true;
17490
+ // Override for giving user ability to delete last line break in table cell
17491
+ fixLastBrDeletionInTable: function(composer, force) {
17492
+ if (composer.selection.caretIsLastInSelection()) {
17493
+ var sel = composer.selection.getSelection(),
17494
+ aNode = sel.anchorNode;
17495
+ if (aNode && aNode.nodeType === 1 && (wysihtml5.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) {
17496
+ var nextNode = aNode.childNodes[sel.anchorOffset];
17497
+ if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") {
17498
+ nextNode.parentNode.removeChild(nextNode);
17499
+ return true;
17500
+ }
17420
17501
  }
17421
17502
  }
17422
- }
17423
- return false;
17424
- };
17503
+ return false;
17504
+ },
17425
17505
 
17426
- // If found an uneditable before caret then notify it before deletion
17427
- var handleUneditableDeletion = function(composer) {
17428
- var before = composer.selection.getBeforeSelection(true);
17429
- if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) {
17430
- if (fixLastBrDeletionInTable(composer, true)) {
17506
+ // If found an uneditable before caret then notify it before deletion
17507
+ handleUneditableDeletion: function(composer) {
17508
+ var before = composer.selection.getBeforeSelection(true);
17509
+ if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) {
17510
+ if (actions.fixLastBrDeletionInTable(composer, true)) {
17511
+ return true;
17512
+ }
17513
+ try {
17514
+ var ev = new CustomEvent("wysihtml5:uneditable:delete", {bubbles: true, cancelable: false});
17515
+ before.node.dispatchEvent(ev);
17516
+ } catch (err) {}
17517
+ before.node.parentNode.removeChild(before.node);
17431
17518
  return true;
17432
17519
  }
17433
- try {
17434
- var ev = new CustomEvent("wysihtml5:uneditable:delete", {bubbles: true, cancelable: false});
17435
- before.node.dispatchEvent(ev);
17436
- } catch (err) {}
17437
- before.node.parentNode.removeChild(before.node);
17438
- return true;
17439
- }
17440
- return false;
17441
- };
17520
+ return false;
17521
+ },
17442
17522
 
17443
- // Deletion with caret in the beginning of headings and other block elvel elements needs special attention
17444
- // Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
17445
- var fixDeleteInTheBeginningOfBlock = function(composer) {
17446
- var selection = composer.selection,
17447
- prevNode = selection.getPreviousNode();
17448
-
17449
- if (selection.caretIsFirstInSelection() &&
17450
- prevNode &&
17451
- prevNode.nodeType === 1 &&
17452
- (/block/).test(composer.win.getComputedStyle(prevNode).display) &&
17453
- !domNode(prevNode).test({
17454
- query: "ol, ul, table, tr, dl"
17455
- })
17456
- ) {
17457
- if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
17458
- // If heading is empty remove the heading node
17459
- prevNode.parentNode.removeChild(prevNode);
17460
- return true;
17461
- } else {
17462
- if (prevNode.lastChild) {
17463
- var selNode = prevNode.lastChild,
17464
- selectedNode = selection.getSelectedNode(),
17465
- commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element);
17466
- curNode = selectedNode.nodeType === 3 ? selectedNode : wysihtml5.dom.getParentElement(selectedNode, {
17467
- query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
17468
- }, false, commonAncestorNode || composer.element);
17469
-
17470
- if (curNode) {
17471
- domNode(curNode).transferContentTo(prevNode, true);
17472
- selection.setAfter(selNode);
17523
+ // Deletion with caret in the beginning of headings and other block elvel elements needs special attention
17524
+ // Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
17525
+ fixDeleteInTheBeginningOfBlock: function(composer) {
17526
+ var selection = composer.selection,
17527
+ prevNode = selection.getPreviousNode();
17528
+
17529
+ if (selection.caretIsFirstInSelection() &&
17530
+ prevNode &&
17531
+ prevNode.nodeType === 1 &&
17532
+ (/block/).test(composer.win.getComputedStyle(prevNode).display) &&
17533
+ !domNode(prevNode).test({
17534
+ query: "ol, ul, table, tr, dl"
17535
+ })
17536
+ ) {
17537
+ if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
17538
+ // If heading is empty remove the heading node
17539
+ prevNode.parentNode.removeChild(prevNode);
17540
+ return true;
17541
+ } else {
17542
+ if (prevNode.lastChild) {
17543
+ var selNode = prevNode.lastChild,
17544
+ selectedNode = selection.getSelectedNode(),
17545
+ commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element),
17546
+ curNode = wysihtml5.dom.getParentElement(selectedNode, {
17547
+ query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
17548
+ }, false, commonAncestorNode || composer.element);
17549
+
17550
+ if (curNode) {
17551
+ domNode(curNode).transferContentTo(prevNode, true);
17552
+ selection.setAfter(selNode);
17553
+ return true;
17554
+ }
17555
+ }
17556
+ }
17557
+ }
17558
+ return false;
17559
+ },
17560
+
17561
+ /* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */
17562
+ /* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */
17563
+ fixDeleteInTheBeginningOfLi: function(composer) {
17564
+ if (wysihtml5.browser.hasLiDeletingProblem()) {
17565
+ var selection = composer.selection.getSelection(),
17566
+ aNode = selection.anchorNode,
17567
+ listNode, prevNode, firstNode,
17568
+ isInBeginnig = composer.selection.caretIsFirstInSelection();
17569
+
17570
+ // Fix caret at the beginnig of first textNode in LI
17571
+ if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) {
17572
+ aNode = aNode.parentNode;
17573
+ isInBeginnig = true;
17574
+ }
17575
+
17576
+ if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") {
17577
+ prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
17578
+ if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) {
17579
+ prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
17580
+ }
17581
+ if (prevNode) {
17582
+ firstNode = aNode.firstChild;
17583
+ domNode(aNode).transferContentTo(prevNode, true);
17584
+ if (firstNode) {
17585
+ composer.selection.setBefore(firstNode);
17586
+ } else if (prevNode) {
17587
+ if (prevNode.nodeType === 1) {
17588
+ if (prevNode.lastChild) {
17589
+ composer.selection.setAfter(prevNode.lastChild);
17590
+ } else {
17591
+ composer.selection.selectNode(prevNode);
17592
+ }
17593
+ } else {
17594
+ composer.selection.setAfter(prevNode);
17595
+ }
17596
+ }
17473
17597
  return true;
17474
17598
  }
17475
17599
  }
17476
17600
  }
17477
- }
17478
- return false;
17479
- };
17601
+ return false;
17602
+ },
17603
+
17604
+ // Table management
17605
+ // If present enableObjectResizing and enableInlineTableEditing command should be called with false to prevent native table handlers
17606
+ initTableHandling: function() {
17607
+ var hideHandlers = function() {
17608
+ window.removeEventListener('load', hideHandlers);
17609
+ this.doc.execCommand("enableObjectResizing", false, "false");
17610
+ this.doc.execCommand("enableInlineTableEditing", false, "false");
17611
+ }.bind(this),
17612
+ iframeInitiator = (function() {
17613
+ hideHandlers.call(this);
17614
+ actions.removeListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
17615
+ }).bind(this);
17616
+
17617
+ if( this.doc.execCommand &&
17618
+ wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") &&
17619
+ wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing"))
17620
+ {
17621
+ if (this.sandbox.getIframe) {
17622
+ actions.addListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
17623
+ } else {
17624
+ window.addEventListener('load', hideHandlers);
17625
+ }
17626
+ }
17627
+ this.tableSelection = wysihtml5.quirks.tableCellsSelection(this.element, this.parent);
17628
+ },
17629
+
17630
+ // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature)
17631
+ // Returns true if some corrections is applied so events know when to prevent default
17632
+ doLineBreaksModeEnterWithCaret: function(composer) {
17633
+ var breakNodes = "p, pre, div, blockquote",
17634
+ caretInfo, parent, txtNode,
17635
+ ret = false;
17636
+
17637
+ caretInfo = composer.selection.getNodesNearCaret();
17638
+ if (caretInfo) {
17639
+
17640
+ if (caretInfo.caretNode || caretInfo.nextNode) {
17641
+ parent = dom.getParentElement(caretInfo.caretNode || caretInfo.nextNode, { query: breakNodes }, 2);
17642
+ if (parent === composer.element) {
17643
+ parent = undefined;
17644
+ }
17645
+ }
17646
+
17647
+ if (parent && caretInfo.caretNode) {
17648
+ if (domNode(caretInfo.caretNode).is.lineBreak()) {
17649
+
17650
+ if (composer.config.doubleLineBreakEscapesBlock) {
17651
+ // Double enter (enter on blank line) exits block element in useLineBreaks mode.
17652
+ ret = true;
17653
+ caretInfo.caretNode.parentNode.removeChild(caretInfo.caretNode);
17654
+
17655
+ // Ensure surplous line breaks are not added to preceding element
17656
+ if (domNode(caretInfo.nextNode).is.lineBreak()) {
17657
+ caretInfo.nextNode.parentNode.removeChild(caretInfo.nextNode);
17658
+ }
17480
17659
 
17481
- /* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */
17482
- /* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */
17483
- var fixDeleteInTheBeginningOfLi = function(composer) {
17484
- if (wysihtml5.browser.hasLiDeletingProblem()) {
17485
- var selection = composer.selection.getSelection(),
17486
- aNode = selection.anchorNode,
17487
- listNode, prevNode, firstNode,
17488
- isInBeginnig = composer.selection.caretIsFirstInSelection();
17489
-
17490
- // Fix caret at the beginnig of first textNode in LI
17491
- if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) {
17492
- aNode = aNode.parentNode;
17493
- isInBeginnig = true;
17494
- }
17495
-
17496
- if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") {
17497
- prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
17498
- if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) {
17499
- prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
17500
- }
17501
- if (prevNode) {
17502
- firstNode = aNode.firstChild;
17503
- domNode(aNode).transferContentTo(prevNode, true);
17504
- if (firstNode) {
17505
- composer.selection.setBefore(firstNode);
17506
- } else if (prevNode) {
17507
- if (prevNode.nodeType === 1) {
17508
- if (prevNode.lastChild) {
17509
- composer.selection.setAfter(prevNode.lastChild);
17660
+ var brNode = composer.doc.createElement('br');
17661
+ if (domNode(caretInfo.nextNode).is.lineBreak() && caretInfo.nextNode === parent.lastChild) {
17662
+ parent.parentNode.insertBefore(brNode, parent.nextSibling);
17510
17663
  } else {
17511
- composer.selection.selectNode(prevNode);
17664
+ composer.selection.splitElementAtCaret(parent, brNode);
17512
17665
  }
17513
- } else {
17514
- composer.selection.setAfter(prevNode);
17666
+
17667
+ // Ensure surplous blank lines are not added to preceding element
17668
+ if (caretInfo.nextNode && caretInfo.nextNode.nodeType === 3) {
17669
+ // Replaces blank lines at the beginning of textnode
17670
+ caretInfo.nextNode.data = caretInfo.nextNode.data.replace(/^ *[\r\n]+/, '');
17671
+ }
17672
+ composer.selection.setBefore(brNode);
17515
17673
  }
17674
+
17675
+ } else if (caretInfo.caretNode.nodeType === 3 && wysihtml5.browser.hasCaretBlockElementIssue() && caretInfo.textOffset === caretInfo.caretNode.data.length && !caretInfo.nextNode) {
17676
+
17677
+ // This fixes annoying webkit issue when you press enter at the end of a block then seemingly nothing happens.
17678
+ // in reality one line break is generated and cursor is reported after it, but when entering something cursor jumps before the br
17679
+ ret = true;
17680
+ var br1 = composer.doc.createElement('br'),
17681
+ br2 = composer.doc.createElement('br'),
17682
+ f = composer.doc.createDocumentFragment();
17683
+ f.appendChild(br1);
17684
+ f.appendChild(br2);
17685
+ composer.selection.insertNode(f);
17686
+ composer.selection.setBefore(br2);
17687
+
17516
17688
  }
17517
- return true;
17518
17689
  }
17519
17690
  }
17691
+ return ret;
17520
17692
  }
17521
- return false;
17522
- }
17693
+ };
17523
17694
 
17524
17695
  var handleDeleteKeyPress = function(event, composer) {
17525
17696
  var selection = composer.selection,
17526
17697
  element = composer.element;
17527
17698
 
17528
17699
  if (selection.isCollapsed()) {
17529
- if (handleUneditableDeletion(composer)) {
17700
+ if (actions.handleUneditableDeletion(composer)) {
17530
17701
  event.preventDefault();
17531
17702
  return;
17532
17703
  }
17533
- if (fixDeleteInTheBeginningOfLi(composer)) {
17704
+ if (actions.fixDeleteInTheBeginningOfLi(composer)) {
17534
17705
  event.preventDefault();
17535
17706
  return;
17536
17707
  }
17537
- if (fixDeleteInTheBeginningOfBlock(composer)) {
17708
+ if (actions.fixDeleteInTheBeginningOfBlock(composer)) {
17538
17709
  event.preventDefault();
17539
17710
  return;
17540
17711
  }
17541
- if (fixLastBrDeletionInTable(composer)) {
17712
+ if (actions.fixLastBrDeletionInTable(composer)) {
17542
17713
  event.preventDefault();
17543
17714
  return;
17544
17715
  }
@@ -17558,59 +17729,8 @@ wysihtml5.views.View = Base.extend(
17558
17729
  caretInfo, parent, txtNode;
17559
17730
 
17560
17731
  if (composer.selection.isCollapsed()) {
17561
- caretInfo = composer.selection.getNodesNearCaret();
17562
- if (caretInfo) {
17563
-
17564
- if (caretInfo.caretNode || caretInfo.nextNode) {
17565
- parent = dom.getParentElement(caretInfo.caretNode || caretInfo.nextNode, { query: breakNodes }, 2);
17566
- if (parent === composer.element) {
17567
- parent = undefined;
17568
- }
17569
- }
17570
-
17571
- if (parent && caretInfo.caretNode) {
17572
- if (domNode(caretInfo.caretNode).is.lineBreak()) {
17573
-
17574
- if (composer.config.doubleLineBreakEscapesBlock) {
17575
- // Double enter (enter on blank line) exits block element in useLineBreaks mode.
17576
- event.preventDefault();
17577
- caretInfo.caretNode.parentNode.removeChild(caretInfo.caretNode);
17578
-
17579
- // Ensure surplous line breaks are not added to preceding element
17580
- if (domNode(caretInfo.nextNode).is.lineBreak()) {
17581
- caretInfo.nextNode.parentNode.removeChild(caretInfo.nextNode);
17582
- }
17583
-
17584
- var brNode = composer.doc.createElement('br');
17585
- if (domNode(caretInfo.nextNode).is.lineBreak() && caretInfo.nextNode === parent.lastChild) {
17586
- parent.parentNode.insertBefore(brNode, parent.nextSibling);
17587
- } else {
17588
- composer.selection.splitElementAtCaret(parent, brNode);
17589
- }
17590
-
17591
- // Ensure surplous blank lines are not added to preceding element
17592
- if (caretInfo.nextNode && caretInfo.nextNode.nodeType === 3) {
17593
- // Replaces blank lines at the beginning of textnode
17594
- caretInfo.nextNode.data = caretInfo.nextNode.data.replace(/^ *[\r\n]+/, '');
17595
- }
17596
- composer.selection.setBefore(brNode);
17597
- }
17598
-
17599
- } else if (caretInfo.caretNode.nodeType === 3 && wysihtml5.browser.hasCaretBlockElementIssue() && caretInfo.textOffset === caretInfo.caretNode.data.length && !caretInfo.nextNode) {
17600
-
17601
- // This fixes annoying webkit issue when you press enter at the end of a block then seemingly nothing happens.
17602
- // in reality one line break is generated and cursor is reported after it, but when entering something cursor jumps before the br
17603
- event.preventDefault();
17604
- var br1 = composer.doc.createElement('br'),
17605
- br2 = composer.doc.createElement('br'),
17606
- f = composer.doc.createDocumentFragment();
17607
- f.appendChild(br1);
17608
- f.appendChild(br2);
17609
- composer.selection.insertNode(f);
17610
- composer.selection.setBefore(br2);
17611
-
17612
- }
17613
- }
17732
+ if (actions.doLineBreaksModeEnterWithCaret(composer)) {
17733
+ event.preventDefault();
17614
17734
  }
17615
17735
  }
17616
17736
  }
@@ -17797,31 +17917,10 @@ wysihtml5.views.View = Base.extend(
17797
17917
  }).bind(this), 0);
17798
17918
  };
17799
17919
 
17800
- // Table management
17801
- // If present enableObjectResizing and enableInlineTableEditing command should be called with false to prevent native table handlers
17802
- var initTableHandling = function () {
17803
- var hideHandlers = function () {
17804
- window.removeEventListener('load', hideHandlers);
17805
- this.doc.execCommand("enableObjectResizing", false, "false");
17806
- this.doc.execCommand("enableInlineTableEditing", false, "false");
17807
- }.bind(this),
17808
- iframeInitiator = (function() {
17809
- hideHandlers.call(this);
17810
- removeListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
17811
- }).bind(this);
17812
-
17813
- if( this.doc.execCommand &&
17814
- wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") &&
17815
- wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing"))
17816
- {
17817
- if (this.sandbox.getIframe) {
17818
- addListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
17819
- } else {
17820
- window.addEventListener('load', hideHandlers);
17821
- }
17822
- }
17823
- this.tableSelection = wysihtml5.quirks.tableCellsSelection(this.element, this.parent);
17824
- };
17920
+
17921
+
17922
+ // Testing requires actions to be accessible from out of scope
17923
+ wysihtml5.views.Composer.prototype.observeActions = actions;
17825
17924
 
17826
17925
  wysihtml5.views.Composer.prototype.observe = function() {
17827
17926
  var that = this,
@@ -17847,14 +17946,14 @@ wysihtml5.views.View = Base.extend(
17847
17946
  // --------- User interactions --
17848
17947
  if (this.config.handleTables) {
17849
17948
  // If handleTables option is true, table handling functions are bound
17850
- initTableHandling.call(this);
17949
+ actions.initTableHandling.call(this);
17851
17950
  }
17852
17951
 
17853
- addListeners(focusBlurElement, ["drop", "paste", "mouseup", "focus", "keyup"], handleUserInteraction.bind(this));
17952
+ actions.addListeners(focusBlurElement, ["drop", "paste", "mouseup", "focus", "keyup"], handleUserInteraction.bind(this));
17854
17953
  focusBlurElement.addEventListener("focus", handleFocus.bind(this), false);
17855
17954
  focusBlurElement.addEventListener("blur", handleBlur.bind(this), false);
17856
17955
 
17857
- addListeners(this.element, ["drop", "paste", "beforepaste"], handlePaste.bind(this), false);
17956
+ actions.addListeners(this.element, ["drop", "paste", "beforepaste"], handlePaste.bind(this), false);
17858
17957
  this.element.addEventListener("copy", handleCopy.bind(this), false);
17859
17958
  this.element.addEventListener("mousedown", handleMouseDown.bind(this), false);
17860
17959
  this.element.addEventListener("click", handleClick.bind(this), false);