wysihtml-rails 0.5.3 → 0.5.4

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