wysihtml-rails 0.5.0.beta11 → 0.5.0.beta12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8a424f7a7451c2ed2ee295f44fd26f3b8e50cad5
4
- data.tar.gz: 699e6c52b926edc5bf8ac1eb437018af88d6cf62
3
+ metadata.gz: 64171b06f3c46b4089c9c548bb95a507de396ea4
4
+ data.tar.gz: 7559f9afd8744d42fc5d180e8df237055b35dd5d
5
5
  SHA512:
6
- metadata.gz: cb041671d6dbfe31ae9d63dfd177dd1d68a034d9ff2725cba285d80f06901395bea1845d5610fe4a08e004c68f482440346a867ef600febbc2cee0f96a2e0634
7
- data.tar.gz: 0b1f3ca29b5e4f6466df2d59084c6b7f35a2e6c4a03714a1df865eea1084b31190858800d98d5c43e6e9533da5274b50b30255b977ca7da7edc20c3bb56e63a2
6
+ metadata.gz: 0c6f3f07ac3e718c699fd894e94812c6cc06da6f099c750bf4355d85f2c4e1b1c2484f035ffe663e11c7ddfb3c09fe0cbf78c60846dc34718f369535c7cc2de8
7
+ data.tar.gz: efecb729c9262b867e0dcc0b7aaa6b85f1470f0d51e145e03aa571d641df54f19f4b523d12fc6b4a5dfd644d2e585721476fc8ef3575778d0c412382bfbdb751
@@ -1,5 +1,5 @@
1
1
  module Wysihtml
2
2
  module Rails
3
- VERSION = "0.5.0.beta11"
3
+ VERSION = "0.5.0.beta12"
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license wysihtml v0.5.0-beta11
2
+ * @license wysihtml v0.5.0-beta12
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-beta11",
13
+ version: "0.5.0-beta12",
14
14
 
15
15
  // namespaces
16
16
  commands: {},
@@ -5890,6 +5890,20 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
5890
5890
  })(wysihtml5);
5891
5891
  ;// TODO: Refactor dom tree traversing here
5892
5892
  (function(wysihtml5) {
5893
+
5894
+ // Finds parents of a node, returning the outermost node first in Array
5895
+ // if contain node is given parents search is stopped at the container
5896
+ function parents(node, container) {
5897
+ var nodes = [node], n = node;
5898
+
5899
+ // iterate parents while parent exists and it is not container element
5900
+ while((container && n && n !== container) || (!container && n)) {
5901
+ nodes.unshift(n);
5902
+ n = n.parentNode;
5903
+ }
5904
+ return nodes;
5905
+ }
5906
+
5893
5907
  wysihtml5.dom.domNode = function(node) {
5894
5908
  var defaultNodeTypes = [wysihtml5.ELEMENT_NODE, wysihtml5.TEXT_NODE];
5895
5909
 
@@ -5951,6 +5965,30 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
5951
5965
  return nextNode;
5952
5966
  },
5953
5967
 
5968
+ // Finds the common acnestor container of two nodes
5969
+ // If container given stops search at the container
5970
+ // If no common ancestor found returns null
5971
+ // var node = wysihtml5.dom.domNode(element).commonAncestor(node2, container);
5972
+ commonAncestor: function(node2, container) {
5973
+ var parents1 = parents(node, container),
5974
+ parents2 = parents(node2, container);
5975
+
5976
+ // Ensure we have found a common ancestor, which will be the first one if anything
5977
+ if (parents1[0] != parents2[0]) {
5978
+ return null;
5979
+ }
5980
+
5981
+ // Traverse up the hierarchy of parents until we reach where they're no longer
5982
+ // the same. Then return previous which was the common ancestor.
5983
+ for (var i = 0; i < parents1.length; i++) {
5984
+ if (parents1[i] != parents2[i]) {
5985
+ return parents1[i - 1];
5986
+ }
5987
+ }
5988
+
5989
+ return null;
5990
+ },
5991
+
5954
5992
  // Traverses a node for last children and their chidren (including itself), and finds the last node that has no children.
5955
5993
  // Array of classes for forced last-leaves (ex: uneditable-container) can be defined (options = {leafClasses: [...]})
5956
5994
  // Useful for finding the actually visible element before cursor
@@ -7443,9 +7481,10 @@ wysihtml5.dom.removeEmptyTextNodes = function(node) {
7443
7481
  childNodes = wysihtml5.lang.array(node.childNodes).get(),
7444
7482
  childNodesLength = childNodes.length,
7445
7483
  i = 0;
7484
+
7446
7485
  for (; i<childNodesLength; i++) {
7447
7486
  childNode = childNodes[i];
7448
- if (childNode.nodeType === wysihtml5.TEXT_NODE && childNode.data === "") {
7487
+ if (childNode.nodeType === wysihtml5.TEXT_NODE && (/^[\n\r]*$/).test(childNode.data)) {
7449
7488
  childNode.parentNode.removeChild(childNode);
7450
7489
  }
7451
7490
  }
@@ -7913,7 +7952,7 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
7913
7952
 
7914
7953
  // initiates an allready existent contenteditable
7915
7954
  _bindElement: function(contentEditable) {
7916
- contentEditable.className = (contentEditable.className && contentEditable.className !== '') ? contentEditable.className + " wysihtml5-sandbox" : "wysihtml5-sandbox";
7955
+ contentEditable.className = contentEditable.className ? contentEditable.className + " wysihtml5-sandbox" : "wysihtml5-sandbox";
7917
7956
  this._loadElement(contentEditable, true);
7918
7957
  return contentEditable;
7919
7958
  },
@@ -9430,71 +9469,140 @@ wysihtml5.quirks.ensureProperClearing = (function() {
9430
9469
 
9431
9470
  };
9432
9471
  ;(function(wysihtml5) {
9433
- var RGBA_REGEX = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
9434
- RGB_REGEX = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
9435
- HEX6_REGEX = /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
9436
- HEX3_REGEX = /^#([0-9a-f])([0-9a-f])([0-9a-f])/i;
9472
+
9473
+ // List of supported color format parsing methods
9474
+ // If radix is not defined 10 is expected as default
9475
+ var colorParseMethods = {
9476
+ rgba : {
9477
+ regex: /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
9478
+ name: "rgba"
9479
+ },
9480
+ rgb : {
9481
+ regex: /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
9482
+ name: "rgb"
9483
+ },
9484
+ hex6 : {
9485
+ regex: /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
9486
+ name: "hex",
9487
+ radix: 16
9488
+ },
9489
+ hex3 : {
9490
+ regex: /^#([0-9a-f])([0-9a-f])([0-9a-f])/i,
9491
+ name: "hex",
9492
+ radix: 16
9493
+ }
9494
+ },
9495
+ // Takes a style key name as an argument and makes a regex that can be used to the match key:value pair from style string
9496
+ makeParamRegExp = function (p) {
9497
+ return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+", "gi");
9498
+ };
9437
9499
 
9438
- var param_REGX = function (p) {
9439
- return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+" , "gi");
9440
- };
9500
+ // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns suitable parsing method for it
9501
+ function getColorParseMethod (colorStr) {
9502
+ var prop, colorTypeConf;
9503
+
9504
+ for (prop in colorParseMethods) {
9505
+ if (!colorParseMethods.hasOwnProperty(prop)) { continue; }
9506
+
9507
+ colorTypeConf = colorParseMethods[prop];
9508
+
9509
+ if (colorTypeConf.regex.test(colorStr)) {
9510
+ return colorTypeConf;
9511
+ }
9512
+ }
9513
+ }
9514
+
9515
+ // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns the type of that color format "hex", "rgb", "rgba".
9516
+ function getColorFormat (colorStr) {
9517
+ var type = getColorParseMethod(colorStr);
9441
9518
 
9519
+ return type ? type.name : undefined;
9520
+ }
9521
+
9522
+ // Public API functions for styleParser
9442
9523
  wysihtml5.quirks.styleParser = {
9443
9524
 
9444
- parseColor: function(stylesStr, paramName) {
9445
- var paramRegex = param_REGX(paramName),
9446
- params = stylesStr.match(paramRegex),
9447
- radix = 10,
9448
- str, colorMatch;
9525
+ // Takes color string value as an argument and returns suitable parsing method for it
9526
+ getColorParseMethod : getColorParseMethod,
9449
9527
 
9450
- if (params) {
9451
- for (var i = params.length; i--;) {
9452
- params[i] = wysihtml5.lang.string(params[i].split(':')[1]).trim();
9453
- }
9454
- str = params[params.length-1];
9455
-
9456
- if (RGBA_REGEX.test(str)) {
9457
- colorMatch = str.match(RGBA_REGEX);
9458
- } else if (RGB_REGEX.test(str)) {
9459
- colorMatch = str.match(RGB_REGEX);
9460
- } else if (HEX6_REGEX.test(str)) {
9461
- colorMatch = str.match(HEX6_REGEX);
9462
- radix = 16;
9463
- } else if (HEX3_REGEX.test(str)) {
9464
- colorMatch = str.match(HEX3_REGEX);
9465
- colorMatch.shift();
9466
- colorMatch.push(1);
9467
- return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
9468
- return (idx < 3) ? (parseInt(d, 16) * 16) + parseInt(d, 16): parseFloat(d);
9469
- });
9470
- }
9528
+ // Takes color string value as an argument and returns the type of that color format "hex", "rgb", "rgba".
9529
+ getColorFormat : getColorFormat,
9530
+
9531
+ /* Parses a color string to and array of [red, green, blue, alpha].
9532
+ * paramName: optional argument to parse color value directly from style string parameter
9533
+ *
9534
+ * Examples:
9535
+ * var colorArray = wysihtml5.quirks.styleParser.parseColor("#ABC"); // [170, 187, 204, 1]
9536
+ * var colorArray = wysihtml5.quirks.styleParser.parseColor("#AABBCC"); // [170, 187, 204, 1]
9537
+ * var colorArray = wysihtml5.quirks.styleParser.parseColor("rgb(1,2,3)"); // [1, 2, 3, 1]
9538
+ * var colorArray = wysihtml5.quirks.styleParser.parseColor("rgba(1,2,3,0.5)"); // [1, 2, 3, 0.5]
9539
+ *
9540
+ * var colorArray = wysihtml5.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "background-color"); // [170, 187, 204, 1]
9541
+ * var colorArray = wysihtml5.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "color"); // [0, 0, 0, 1]
9542
+ */
9543
+ parseColor : function (stylesStr, paramName) {
9544
+ var paramsRegex, params, colorType, colorMatch, radix,
9545
+ colorStr = stylesStr;
9471
9546
 
9472
- if (colorMatch) {
9473
- colorMatch.shift();
9474
- if (!colorMatch[3]) {
9475
- colorMatch.push(1);
9476
- }
9477
- return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
9478
- return (idx < 3) ? parseInt(d, radix): parseFloat(d);
9479
- });
9480
- }
9547
+ if (paramName) {
9548
+ paramsRegex = makeParamRegExp(paramName);
9549
+
9550
+ if (!(params = stylesStr.match(paramsRegex))) { return false; }
9551
+
9552
+ params = params.pop().split(":")[1];
9553
+ colorStr = wysihtml5.lang.string(params).trim();
9481
9554
  }
9482
- return false;
9555
+
9556
+ if (!(colorType = getColorParseMethod(colorStr))) { return false; }
9557
+ if (!(colorMatch = colorStr.match(colorType.regex))) { return false; }
9558
+
9559
+ radix = colorType.radix || 10;
9560
+
9561
+ if (colorType === colorParseMethods.hex3) {
9562
+ colorMatch.shift();
9563
+ colorMatch.push(1);
9564
+ return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
9565
+ return (idx < 3) ? (parseInt(d, radix) * radix) + parseInt(d, radix): parseFloat(d);
9566
+ });
9567
+ }
9568
+
9569
+ colorMatch.shift();
9570
+
9571
+ if (!colorMatch[3]) {
9572
+ colorMatch.push(1);
9573
+ }
9574
+
9575
+ return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
9576
+ return (idx < 3) ? parseInt(d, radix): parseFloat(d);
9577
+ });
9483
9578
  },
9484
9579
 
9485
- unparseColor: function(val, props) {
9486
- if (props) {
9487
- if (props == "hex") {
9488
- return (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
9489
- } else if (props == "hash") {
9490
- return "#" + (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
9491
- } else if (props == "rgb") {
9492
- return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
9493
- } else if (props == "rgba") {
9494
- return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
9495
- } else if (props == "csv") {
9496
- return val[0] + "," + val[1] + "," + val[2] + "," + val[3];
9497
- }
9580
+ /* Takes rgba color array [r,g,b,a] as a value and formats it to color string with given format type
9581
+ * If no format is given, rgba/rgb is returned based on alpha value
9582
+ *
9583
+ * Example:
9584
+ * var colorStr = wysihtml5.quirks.styleParser.unparseColor([170, 187, 204, 1], "hash"); // "#AABBCC"
9585
+ * var colorStr = wysihtml5.quirks.styleParser.unparseColor([170, 187, 204, 1], "hex"); // "AABBCC"
9586
+ * var colorStr = wysihtml5.quirks.styleParser.unparseColor([170, 187, 204, 1], "csv"); // "170, 187, 204, 1"
9587
+ * var colorStr = wysihtml5.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgba"); // "rgba(170,187,204,1)"
9588
+ * var colorStr = wysihtml5.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgb"); // "rgb(170,187,204)"
9589
+ *
9590
+ * var colorStr = wysihtml5.quirks.styleParser.unparseColor([170, 187, 204, 0.5]); // "rgba(170,187,204,0.5)"
9591
+ * var colorStr = wysihtml5.quirks.styleParser.unparseColor([170, 187, 204, 1]); // "rgb(170,187,204)"
9592
+ */
9593
+ unparseColor: function(val, colorFormat) {
9594
+ var hexRadix = 16;
9595
+
9596
+ if (colorFormat === "hex") {
9597
+ return (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase();
9598
+ } else if (colorFormat === "hash") {
9599
+ return "#" + (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase();
9600
+ } else if (colorFormat === "rgb") {
9601
+ return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
9602
+ } else if (colorFormat === "rgba") {
9603
+ return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
9604
+ } else if (colorFormat === "csv") {
9605
+ return val[0] + "," + val[1] + "," + val[2] + "," + val[3];
9498
9606
  }
9499
9607
 
9500
9608
  if (val[3] && val[3] !== 1) {
@@ -9504,10 +9612,11 @@ wysihtml5.quirks.ensureProperClearing = (function() {
9504
9612
  }
9505
9613
  },
9506
9614
 
9615
+ // Parses font size value from style string
9507
9616
  parseFontSize: function(stylesStr) {
9508
- var params = stylesStr.match(param_REGX('font-size'));
9617
+ var params = stylesStr.match(makeParamRegExp("font-size"));
9509
9618
  if (params) {
9510
- return wysihtml5.lang.string(params[params.length - 1].split(':')[1]).trim();
9619
+ return wysihtml5.lang.string(params[params.length - 1].split(":")[1]).trim();
9511
9620
  }
9512
9621
  return false;
9513
9622
  }
@@ -9546,6 +9655,50 @@ wysihtml5.quirks.ensureProperClearing = (function() {
9546
9655
  return ret;
9547
9656
  }
9548
9657
 
9658
+ function getWebkitSelectionFixNode(container) {
9659
+ var blankNode = document.createElement('span');
9660
+
9661
+ var placeholderRemover = function(event) {
9662
+ // Self-destructs the caret and keeps the text inserted into it by user
9663
+ var lastChild;
9664
+
9665
+ container.removeEventListener('mouseup', placeholderRemover);
9666
+ container.removeEventListener('keydown', placeholderRemover);
9667
+ container.removeEventListener('touchstart', placeholderRemover);
9668
+ container.removeEventListener('focus', placeholderRemover);
9669
+ container.removeEventListener('blur', placeholderRemover);
9670
+ container.removeEventListener('paste', delayedPlaceholderRemover);
9671
+ container.removeEventListener('drop', delayedPlaceholderRemover);
9672
+ container.removeEventListener('beforepaste', delayedPlaceholderRemover);
9673
+
9674
+ if (blankNode && blankNode.parentNode) {
9675
+ blankNode.parentNode.removeChild(blankNode);
9676
+ }
9677
+ },
9678
+ delayedPlaceholderRemover = function (event) {
9679
+ if (blankNode && blankNode.parentNode) {
9680
+ setTimeout(placeholderRemover, 0);
9681
+ }
9682
+ };
9683
+
9684
+ blankNode.appendChild(document.createTextNode(wysihtml5.INVISIBLE_SPACE));
9685
+ blankNode.className = '_wysihtml5-temp-caret-fix';
9686
+ blankNode.style.display = 'block';
9687
+ blankNode.style.minWidth = '1px';
9688
+ blankNode.style.height = '0px';
9689
+
9690
+ container.addEventListener('mouseup', placeholderRemover);
9691
+ container.addEventListener('keydown', placeholderRemover);
9692
+ container.addEventListener('touchstart', placeholderRemover);
9693
+ container.addEventListener('focus', placeholderRemover);
9694
+ container.addEventListener('blur', placeholderRemover);
9695
+ container.addEventListener('paste', delayedPlaceholderRemover);
9696
+ container.addEventListener('drop', delayedPlaceholderRemover);
9697
+ container.addEventListener('beforepaste', delayedPlaceholderRemover);
9698
+
9699
+ return blankNode;
9700
+ }
9701
+
9549
9702
  // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon
9550
9703
  // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection
9551
9704
  function expandRangeToSurround(range) {
@@ -10218,6 +10371,20 @@ wysihtml5.quirks.ensureProperClearing = (function() {
10218
10371
  }
10219
10372
  },
10220
10373
 
10374
+ canAppendChild: function (node) {
10375
+ var anchorNode, anchorNodeTagNameLower,
10376
+ voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"],
10377
+ range = this.getRange();
10378
+
10379
+ anchorNode = node || range.startContainer;
10380
+
10381
+ if (anchorNode) {
10382
+ anchorNodeTagNameLower = (anchorNode.tagName || anchorNode.nodeName).toLowerCase();
10383
+ }
10384
+
10385
+ return voidElements.indexOf(anchorNodeTagNameLower) === -1;
10386
+ },
10387
+
10221
10388
  splitElementAtCaret: function (element, insertNode) {
10222
10389
  var sel = this.getSelection(),
10223
10390
  range, contentAfterRangeStart,
@@ -10598,6 +10765,52 @@ wysihtml5.quirks.ensureProperClearing = (function() {
10598
10765
  return (selection && selection.anchorNode && selection.focusNode) ? selection : null;
10599
10766
  },
10600
10767
 
10768
+
10769
+
10770
+ // Webkit has an ancient error of not selecting all contents when uneditable block element is first or last in editable area
10771
+ selectAll: function() {
10772
+ var range = this.createRange(),
10773
+ composer = this.composer,
10774
+ that = this,
10775
+ blankEndNode = getWebkitSelectionFixNode(this.composer.element),
10776
+ blankStartNode = getWebkitSelectionFixNode(this.composer.element),
10777
+ s;
10778
+
10779
+ var doSelect = function() {
10780
+ range.setStart(composer.element, 0);
10781
+ range.setEnd(composer.element, composer.element.childNodes.length);
10782
+ s = that.setSelection(range);
10783
+ };
10784
+
10785
+ var notSelected = function() {
10786
+ return !s || (s.nativeSelection && s.nativeSelection.type && (s.nativeSelection.type === "Caret" || s.nativeSelection.type === "None"));
10787
+ }
10788
+
10789
+ wysihtml5.dom.removeInvisibleSpaces(this.composer.element);
10790
+ doSelect();
10791
+
10792
+ if (this.composer.element.firstChild && notSelected()) {
10793
+ // Try fixing end
10794
+ this.composer.element.appendChild(blankEndNode);
10795
+ doSelect();
10796
+
10797
+ if (notSelected()) {
10798
+ // Remove end fix
10799
+ blankEndNode.parentNode.removeChild(blankEndNode);
10800
+
10801
+ // Try fixing beginning
10802
+ this.composer.element.insertBefore(blankStartNode, this.composer.element.firstChild);
10803
+ doSelect();
10804
+
10805
+ if (notSelected()) {
10806
+ // Try fixing both
10807
+ this.composer.element.appendChild(blankEndNode);
10808
+ doSelect();
10809
+ }
10810
+ }
10811
+ }
10812
+ },
10813
+
10601
10814
  createRange: function() {
10602
10815
  return rangy.createRange(this.doc);
10603
10816
  },
@@ -10656,6 +10869,25 @@ wysihtml5.quirks.ensureProperClearing = (function() {
10656
10869
  return (wysihtml5.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false;
10657
10870
  },
10658
10871
 
10872
+ isInThisEditable: function() {
10873
+ var sel = this.getSelection(),
10874
+ fnode = sel.focusNode,
10875
+ anode = sel.anchorNode;
10876
+
10877
+ // In IE node contains will not work for textnodes, thus taking parentNode
10878
+ if (fnode && fnode.nodeType !== 1) {
10879
+ fnode = fnode.parentNode;
10880
+ }
10881
+
10882
+ if (anode && anode.nodeType !== 1) {
10883
+ anode = anode.parentNode;
10884
+ }
10885
+
10886
+ return anode && fnode &&
10887
+ (wysihtml5.dom.contains(this.composer.element, fnode) || this.composer.element === fnode) &&
10888
+ (wysihtml5.dom.contains(this.composer.element, anode) || this.composer.element === anode);
10889
+ },
10890
+
10659
10891
  deselect: function() {
10660
10892
  var sel = this.getSelection();
10661
10893
  sel && sel.removeAllRanges();
@@ -11509,12 +11741,12 @@ wysihtml5.Commands = Base.extend(
11509
11741
  exec: function(composer, command, size) {
11510
11742
  size = size.size || size;
11511
11743
  if (!(/^\s*$/).test(size)) {
11512
- wysihtml5.commands.formatInline.exec(composer, command, {styleProperty: "fontSize", styleValue: size, toggle: true});
11744
+ wysihtml5.commands.formatInline.exec(composer, command, {styleProperty: "fontSize", styleValue: size, toggle: false});
11513
11745
  }
11514
11746
  },
11515
11747
 
11516
11748
  state: function(composer, command, size) {
11517
- return wysihtml5.commands.formatInline.state(composer, command, {styleProperty: "fontSize", styleValue: size});
11749
+ return wysihtml5.commands.formatInline.state(composer, command, {styleProperty: "fontSize", styleValue: size || undefined});
11518
11750
  },
11519
11751
 
11520
11752
  remove: function(composer, command) {
@@ -11522,15 +11754,14 @@ wysihtml5.Commands = Base.extend(
11522
11754
  },
11523
11755
 
11524
11756
  stateValue: function(composer, command) {
11525
- var st = this.state(composer, command),
11526
- styleStr, fontsizeMatches,
11527
- val = false;
11757
+ var styleStr,
11758
+ st = this.state(composer, command);
11528
11759
 
11529
11760
  if (st && wysihtml5.lang.object(st).isArray()) {
11530
11761
  st = st[0];
11531
11762
  }
11532
11763
  if (st) {
11533
- styleStr = st.getAttribute('style');
11764
+ styleStr = st.getAttribute("style");
11534
11765
  if (styleStr) {
11535
11766
  return wysihtml5.quirks.styleParser.parseFontSize(styleStr);
11536
11767
  }
@@ -11562,12 +11793,15 @@ wysihtml5.Commands = Base.extend(
11562
11793
 
11563
11794
  wysihtml5.commands.foreColorStyle = {
11564
11795
  exec: function(composer, command, color) {
11565
- var colorVals = wysihtml5.quirks.styleParser.parseColor("color:" + (color.color || color), "color"),
11566
- colString;
11796
+ var colorVals, colString;
11797
+
11798
+ if (!color) { return; }
11799
+
11800
+ colorVals = wysihtml5.quirks.styleParser.parseColor("color:" + (color.color || color), "color");
11567
11801
 
11568
11802
  if (colorVals) {
11569
- colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(', ') : "rgba(" + colorVals.join(', ')) + ')';
11570
- wysihtml5.commands.formatInline.exec(composer, command, {styleProperty: 'color', styleValue: colString});
11803
+ colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(", ") : "rgba(" + colorVals.join(', ')) + ')';
11804
+ wysihtml5.commands.formatInline.exec(composer, command, {styleProperty: "color", styleValue: colString});
11571
11805
  }
11572
11806
  },
11573
11807
 
@@ -11577,14 +11811,14 @@ wysihtml5.Commands = Base.extend(
11577
11811
 
11578
11812
 
11579
11813
  if (colorVals) {
11580
- colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(', ') : "rgba(" + colorVals.join(', ')) + ')';
11814
+ colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(", ") : "rgba(" + colorVals.join(', ')) + ')';
11581
11815
  }
11582
11816
 
11583
- return wysihtml5.commands.formatInline.state(composer, command, {styleProperty: 'color', styleValue: colString});
11817
+ return wysihtml5.commands.formatInline.state(composer, command, {styleProperty: "color", styleValue: colString});
11584
11818
  },
11585
11819
 
11586
11820
  remove: function(composer, command) {
11587
- return wysihtml5.commands.formatInline.remove(composer, command, {styleProperty: 'color'});
11821
+ return wysihtml5.commands.formatInline.remove(composer, command, {styleProperty: "color"});
11588
11822
  },
11589
11823
 
11590
11824
  stateValue: function(composer, command, props) {
@@ -11597,7 +11831,7 @@ wysihtml5.Commands = Base.extend(
11597
11831
  }
11598
11832
 
11599
11833
  if (st) {
11600
- colorStr = st.getAttribute('style');
11834
+ colorStr = st.getAttribute("style");
11601
11835
  if (colorStr) {
11602
11836
  val = wysihtml5.quirks.styleParser.parseColor(colorStr, "color");
11603
11837
  return wysihtml5.quirks.styleParser.unparseColor(val, props);
@@ -11675,6 +11909,14 @@ wysihtml5.Commands = Base.extend(
11675
11909
  BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote",
11676
11910
  INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u";
11677
11911
 
11912
+ function correctOptionsForSimilarityCheck(options) {
11913
+ return {
11914
+ nodeName: options.nodeName || null,
11915
+ className: (!options.classRegExp) ? options.className || null : null,
11916
+ classRegExp: options.classRegExp || null,
11917
+ styleProperty: options.styleProperty || null
11918
+ };
11919
+ }
11678
11920
 
11679
11921
  // Removes empty block level elements
11680
11922
  function cleanup(composer) {
@@ -11684,7 +11926,7 @@ wysihtml5.Commands = Base.extend(
11684
11926
  elements = wysihtml5.lang.array(allElements).without(uneditables);
11685
11927
 
11686
11928
  for (var i = elements.length; i--;) {
11687
- if (elements[i].innerHTML === "") {
11929
+ if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "") {
11688
11930
  elements[i].parentNode.removeChild(elements[i]);
11689
11931
  }
11690
11932
  }
@@ -11811,7 +12053,7 @@ wysihtml5.Commands = Base.extend(
11811
12053
 
11812
12054
  for (var i = contentBlocks.length; i--;) {
11813
12055
  if (!contentBlocks[i].nextSibling || contentBlocks[i].nextSibling.nodeType !== 1 || contentBlocks[i].nextSibling.nodeName !== 'BR') {
11814
- if ((contentBlocks[i].innerHTML || contentBlocks[i].nodeValue).trim() !== "") {
12056
+ if ((contentBlocks[i].innerHTML || contentBlocks[i].nodeValue || '').trim() !== '') {
11815
12057
  contentBlocks[i].parentNode.insertBefore(contentBlocks[i].ownerDocument.createElement('BR'), contentBlocks[i].nextSibling);
11816
12058
  }
11817
12059
  }
@@ -11877,8 +12119,10 @@ wysihtml5.Commands = Base.extend(
11877
12119
  rangeStartContainer = r.startContainer,
11878
12120
  content = r.extractContents(),
11879
12121
  fragment = composer.doc.createDocumentFragment(),
12122
+ similarOptions = defaultOptions ? correctOptionsForSimilarityCheck(defaultOptions) : null,
12123
+ similarOuterBlock = similarOptions ? wysihtml5.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
11880
12124
  splitAllBlocks = !defaultOptions || (defaultName === "BLOCKQUOTE" && defaultOptions.nodeName && defaultOptions.nodeName === "BLOCKQUOTE"),
11881
- firstOuterBlock = findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
12125
+ firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
11882
12126
  wrapper, blocks, children;
11883
12127
 
11884
12128
  if (options && options.nodeName && options.nodeName === "BLOCKQUOTE") {
@@ -12003,7 +12247,7 @@ wysihtml5.Commands = Base.extend(
12003
12247
 
12004
12248
  if (composer.selection.isCollapsed()) {
12005
12249
  parent = wysihtml5.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, {
12006
- query: BLOCK_ELEMENTS
12250
+ query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (options && options.nodeName ? options.nodeName.toLowerCase() : 'div'),
12007
12251
  }, null, composer.element);
12008
12252
  if (parent) {
12009
12253
  bookmark = rangy.saveSelection(composer.win);
@@ -12016,7 +12260,7 @@ wysihtml5.Commands = Base.extend(
12016
12260
  }
12017
12261
  }
12018
12262
 
12019
- // And get all selection ranges of current composer and iterat
12263
+ // And get all selection ranges of current composer and iterate
12020
12264
  ranges = composer.selection.getOwnRanges();
12021
12265
  for (var i = ranges.length; i--;) {
12022
12266
  newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, getParentBlockNodeName(ranges[i].startContainer, composer), composer));
@@ -12026,6 +12270,13 @@ wysihtml5.Commands = Base.extend(
12026
12270
 
12027
12271
  // Remove empty block elements that may be left behind
12028
12272
  cleanup(composer);
12273
+ // If cleanup removed some new block elements. remove them from array too
12274
+ for (var e = newBlockElements.length; e--;) {
12275
+ if (!newBlockElements[e].parentNode) {
12276
+ newBlockElements.splice(e, 1);
12277
+ }
12278
+ }
12279
+
12029
12280
  // Restore correct selection
12030
12281
  if (bookmark) {
12031
12282
  rangy.restoreSelection(bookmark);
@@ -12081,8 +12332,9 @@ wysihtml5.Commands = Base.extend(
12081
12332
  wysihtml5.commands.formatCode = {
12082
12333
 
12083
12334
  exec: function(composer, command, classname) {
12084
- var pre = this.state(composer),
12335
+ var pre = this.state(composer)[0],
12085
12336
  code, range, selectedNodes;
12337
+
12086
12338
  if (pre) {
12087
12339
  // caret is already within a <pre><code>...</code></pre>
12088
12340
  composer.selection.executeAndRestore(function() {
@@ -12111,12 +12363,13 @@ wysihtml5.Commands = Base.extend(
12111
12363
  },
12112
12364
 
12113
12365
  state: function(composer) {
12114
- var selectedNode = composer.selection.getSelectedNode();
12366
+ var selectedNode = composer.selection.getSelectedNode(), node;
12115
12367
  if (selectedNode && selectedNode.nodeName && selectedNode.nodeName == "PRE"&&
12116
12368
  selectedNode.firstChild && selectedNode.firstChild.nodeName && selectedNode.firstChild.nodeName == "CODE") {
12117
- return selectedNode;
12369
+ return [selectedNode];
12118
12370
  } else {
12119
- return wysihtml5.dom.getParentElement(selectedNode, { query: "pre code" });
12371
+ node = wysihtml5.dom.getParentElement(selectedNode, { query: "pre code" });
12372
+ return node ? [node.parentNode] : false;
12120
12373
  }
12121
12374
  }
12122
12375
  };
@@ -12260,9 +12513,7 @@ wysihtml5.Commands = Base.extend(
12260
12513
  }
12261
12514
 
12262
12515
  function updateFormatOfElement(element, options) {
12263
- var attr, newNode, a, newAttributes, nodeNameQuery;
12264
-
12265
-
12516
+ var attr, newNode, a, newAttributes, nodeNameQuery, nodeQueryMatch;
12266
12517
 
12267
12518
  if (options.className) {
12268
12519
  if (options.toggle !== false && element.classList.contains(options.className)) {
@@ -12297,30 +12548,19 @@ wysihtml5.Commands = Base.extend(
12297
12548
  updateElementAttributes(element, newAttributes, options.toggle);
12298
12549
  }
12299
12550
 
12300
- // Handle similar semanticallys ame elements (queryAliasMap)
12551
+
12552
+ // Handle similar semantically same elements (queryAliasMap)
12301
12553
  nodeNameQuery = options.nodeName ? queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase() : null;
12554
+ nodeQueryMatch = nodeNameQuery ? wysihtml5.dom.domNode(element).test({ query: nodeNameQuery }) : false;
12302
12555
 
12303
- if ((options.nodeName && wysihtml5.dom.domNode(element).test({ query: nodeNameQuery })) || (!options.nodeName && element.nodeName === defaultTag)) {
12304
-
12305
-
12306
- if (hasNoClass(element) && hasNoStyle(element) && hasNoAttributes(element)) {
12556
+ // Unwrap element if no attributes present and node name given
12557
+ // or no attributes and if no nodename set but node is the default
12558
+ if (!options.nodeName || options.nodeName === defaultTag || nodeQueryMatch) {
12559
+ if (
12560
+ ((options.toggle !== false && nodeQueryMatch) || (!options.nodeName && element.nodeName === defaultTag)) &&
12561
+ hasNoClass(element) && hasNoStyle(element) && hasNoAttributes(element)
12562
+ ) {
12307
12563
  wysihtml5.dom.unwrap(element);
12308
- } else if (!options.nodeName) {
12309
- newNode = element.ownerDocument.createElement(defaultTag);
12310
-
12311
- // pass present attributes
12312
- attr = wysihtml5.dom.getAttributes(element);
12313
- for (a in attr) {
12314
- if (attr.hasOwnProperty(a)) {
12315
- newNode.setAttribute(a, attr[a]);
12316
- }
12317
- }
12318
-
12319
- while (element.firstChild) {
12320
- newNode.appendChild(element.firstChild);
12321
- }
12322
- element.parentNode.insertBefore(newNode, element);
12323
- element.parentNode.removeChild(element);
12324
12564
  }
12325
12565
 
12326
12566
  }
@@ -12425,35 +12665,39 @@ wysihtml5.Commands = Base.extend(
12425
12665
  partial = false,
12426
12666
  node, range, caretNode;
12427
12667
 
12428
- if (searchNodes.length === 0 && composer.selection.isCollapsed()) {
12429
- caretNode = composer.selection.getSelection().anchorNode;
12430
- if (!caretNode) {
12431
- // selection not in editor
12432
- return {
12433
- nodes: [],
12434
- partial: false
12435
- };
12436
- }
12437
- if (caretNode.nodeType === 3) {
12438
- searchNodes = [caretNode];
12668
+ if (composer.selection.isInThisEditable()) {
12669
+
12670
+ if (searchNodes.length === 0 && composer.selection.isCollapsed()) {
12671
+ caretNode = composer.selection.getSelection().anchorNode;
12672
+ if (!caretNode) {
12673
+ // selection not in editor
12674
+ return {
12675
+ nodes: [],
12676
+ partial: false
12677
+ };
12678
+ }
12679
+ if (caretNode.nodeType === 3) {
12680
+ searchNodes = [caretNode];
12681
+ }
12439
12682
  }
12440
- }
12441
12683
 
12442
- // Handle collapsed selection caret
12443
- if (!searchNodes.length) {
12444
- range = composer.selection.getOwnRanges()[0];
12445
- if (range) {
12446
- searchNodes = [range.endContainer];
12684
+ // Handle collapsed selection caret
12685
+ if (!searchNodes.length) {
12686
+ range = composer.selection.getOwnRanges()[0];
12687
+ if (range) {
12688
+ searchNodes = [range.endContainer];
12689
+ }
12447
12690
  }
12448
- }
12449
12691
 
12450
- for (var i = 0, maxi = searchNodes.length; i < maxi; i++) {
12451
- node = findSimilarTextNodeWrapper(searchNodes[i], options, composer.element, exact);
12452
- if (node) {
12453
- nodes.push(node);
12454
- } else {
12455
- partial = true;
12692
+ for (var i = 0, maxi = searchNodes.length; i < maxi; i++) {
12693
+ node = findSimilarTextNodeWrapper(searchNodes[i], options, composer.element, exact);
12694
+ if (node) {
12695
+ nodes.push(node);
12696
+ } else {
12697
+ partial = true;
12698
+ }
12456
12699
  }
12700
+
12457
12701
  }
12458
12702
 
12459
12703
  return {
@@ -12774,9 +13018,7 @@ wysihtml5.Commands = Base.extend(
12774
13018
 
12775
13019
  state: function(composer, command, options) {
12776
13020
  options = fixOptions(options);
12777
-
12778
13021
  var nodes = getState(composer, options, true).nodes;
12779
-
12780
13022
  return (nodes.length === 0) ? false : nodes;
12781
13023
  }
12782
13024
  };
@@ -14635,14 +14877,14 @@ wysihtml5.views.View = Base.extend(
14635
14877
  // Deletion with caret in the beginning of headings needs special attention
14636
14878
  // Heading does not concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
14637
14879
  var fixDeleteInTheBeginnigOfHeading = function(composer) {
14638
- var selection = composer.selection;
14880
+ var selection = composer.selection,
14881
+ prevNode = selection.getPreviousNode();
14639
14882
 
14640
14883
  if (selection.caretIsFirstInSelection() &&
14641
- selection.getPreviousNode() &&
14642
- selection.getPreviousNode().nodeName &&
14643
- (/^H\d$/gi).test(selection.getPreviousNode().nodeName)
14884
+ prevNode &&
14885
+ prevNode.nodeType === 1 &&
14886
+ (/block/).test(composer.win.getComputedStyle(prevNode).display)
14644
14887
  ) {
14645
- var prevNode = selection.getPreviousNode();
14646
14888
  if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
14647
14889
  // If heading is empty remove the heading node
14648
14890
  prevNode.parentNode.removeChild(prevNode);
@@ -14650,20 +14892,23 @@ wysihtml5.views.View = Base.extend(
14650
14892
  } else {
14651
14893
  if (prevNode.lastChild) {
14652
14894
  var selNode = prevNode.lastChild,
14653
- curNode = wysihtml5.dom.getParentElement(selection.getSelectedNode(), { query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote" }, false, composer.element);
14654
- if (prevNode) {
14895
+ selectedNode = selection.getSelectedNode(),
14896
+ commonAncestorNode = wysihtml5.dom.domNode(prevNode).commonAncestor(selectedNode, composer.element);
14897
+ curNode = commonAncestorNode ? wysihtml5.dom.getParentElement(selectedNode, {
14898
+ query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
14899
+ }, false, commonAncestorNode) : null;
14900
+
14655
14901
  if (curNode) {
14656
14902
  while (curNode.firstChild) {
14657
14903
  prevNode.appendChild(curNode.firstChild);
14658
14904
  }
14659
14905
  selection.setAfter(selNode);
14660
14906
  return true;
14661
- } else if (selection.getSelectedNode().nodeType === 3) {
14662
- prevNode.appendChild(selection.getSelectedNode());
14907
+ } else if (selectedNode.nodeType === 3) {
14908
+ prevNode.appendChild(selectedNode);
14663
14909
  selection.setAfter(selNode);
14664
14910
  return true;
14665
14911
  }
14666
- }
14667
14912
  }
14668
14913
  }
14669
14914
  }
@@ -14675,23 +14920,17 @@ wysihtml5.views.View = Base.extend(
14675
14920
  element = composer.element;
14676
14921
 
14677
14922
  if (selection.isCollapsed()) {
14678
- if (selection.caretIsInTheBeginnig('li')) {
14679
- // delete in the beginnig of LI will outdent not delete
14923
+ if (fixDeleteInTheBeginnigOfHeading(composer)) {
14680
14924
  event.preventDefault();
14681
- composer.commands.exec('outdentList');
14682
- } else {
14683
- if (fixDeleteInTheBeginnigOfHeading(composer)) {
14684
- event.preventDefault();
14685
- return;
14686
- }
14687
- if (fixLastBrDeletionInTable(composer)) {
14688
- event.preventDefault();
14689
- return;
14690
- }
14691
- if (handleUneditableDeletion(composer)) {
14692
- event.preventDefault();
14693
- return;
14694
- }
14925
+ return;
14926
+ }
14927
+ if (fixLastBrDeletionInTable(composer)) {
14928
+ event.preventDefault();
14929
+ return;
14930
+ }
14931
+ if (handleUneditableDeletion(composer)) {
14932
+ event.preventDefault();
14933
+ return;
14695
14934
  }
14696
14935
  } else {
14697
14936
  if (selection.containsUneditable()) {
@@ -14701,11 +14940,15 @@ wysihtml5.views.View = Base.extend(
14701
14940
  }
14702
14941
  };
14703
14942
 
14704
- var handleTabKeyDown = function(composer, element) {
14943
+ var handleTabKeyDown = function(composer, element, shiftKey) {
14705
14944
  if (!composer.selection.isCollapsed()) {
14706
14945
  composer.selection.deleteContents();
14707
14946
  } else if (composer.selection.caretIsInTheBeginnig('li')) {
14708
- if (composer.commands.exec('indentList')) return;
14947
+ if (shiftKey) {
14948
+ if (composer.commands.exec('outdentList')) return;
14949
+ } else {
14950
+ if (composer.commands.exec('indentList')) return;
14951
+ }
14709
14952
  }
14710
14953
 
14711
14954
  // Is &emsp; close enough to tab. Could not find enough counter arguments for now.
@@ -14721,9 +14964,9 @@ wysihtml5.views.View = Base.extend(
14721
14964
 
14722
14965
  // Listens to "drop", "paste", "mouseup", "focus", "keyup" events and fires
14723
14966
  var handleUserInteraction = function (event) {
14724
- this.parent.fire("beforeinteraction").fire("beforeinteraction:composer");
14967
+ this.parent.fire("beforeinteraction", event).fire("beforeinteraction:composer", event);
14725
14968
  setTimeout((function() {
14726
- this.parent.fire("interaction").fire("interaction:composer");
14969
+ this.parent.fire("interaction", event).fire("interaction:composer", event);
14727
14970
  }).bind(this), 0);
14728
14971
  };
14729
14972
 
@@ -14837,6 +15080,13 @@ wysihtml5.views.View = Base.extend(
14837
15080
  command = shortcuts[keyCode],
14838
15081
  target, parent;
14839
15082
 
15083
+ // Select all (meta/ctrl + a)
15084
+ if ((event.ctrlKey || event.metaKey) && keyCode === 65) {
15085
+ this.selection.selectAll();
15086
+ event.preventDefault();
15087
+ return;
15088
+ }
15089
+
14840
15090
  // Shortcut logic
14841
15091
  if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
14842
15092
  this.commands.exec(command);
@@ -14859,16 +15109,16 @@ wysihtml5.views.View = Base.extend(
14859
15109
  if (parent.nodeName === "A" && !parent.firstChild) {
14860
15110
  parent.parentNode.removeChild(parent);
14861
15111
  }
14862
- setTimeout(function() {
15112
+ setTimeout((function() {
14863
15113
  wysihtml5.quirks.redraw(this.element);
14864
- }, 0);
15114
+ }).bind(this), 0);
14865
15115
  }
14866
15116
  }
14867
15117
 
14868
15118
  if (this.config.handleTabKey && keyCode === wysihtml5.TAB_KEY) {
14869
15119
  // TAB key handling
14870
15120
  event.preventDefault();
14871
- handleTabKeyDown(this, this.element);
15121
+ handleTabKeyDown(this, this.element, event.shiftKey);
14872
15122
  }
14873
15123
 
14874
15124
  };
@@ -15227,6 +15477,8 @@ wysihtml5.views.View = Base.extend(
15227
15477
  // Whether toolbar is displayed after init by script automatically.
15228
15478
  // Can be set to false if toolobar is set to display only on editable area focus
15229
15479
  showToolbarAfterInit: true,
15480
+ // With default toolbar it shows dialogs in toolbar when their related text format state becomes active (click on link in text opens link dialogue)
15481
+ showToolbarDialogsOnSelection: true,
15230
15482
  // Whether urls, entered by the user should automatically become clickable-links
15231
15483
  autoLink: true,
15232
15484
  // Includes table editing events and cell selection tracking
@@ -15400,8 +15652,7 @@ wysihtml5.views.View = Base.extend(
15400
15652
  * - Observes for paste and drop
15401
15653
  */
15402
15654
  _initParser: function() {
15403
- var oldHtml,
15404
- cleanHtml;
15655
+ var oldHtml;
15405
15656
 
15406
15657
  if (wysihtml5.browser.supportsModernPaste()) {
15407
15658
  this.on("paste:composer", function(event) {
@@ -15510,28 +15761,18 @@ wysihtml5.views.View = Base.extend(
15510
15761
  callbackWrapper(event);
15511
15762
  }
15512
15763
  if (keyCode === wysihtml5.ESCAPE_KEY) {
15513
- that.fire("cancel");
15514
- that.hide();
15764
+ that.cancel();
15515
15765
  }
15516
15766
  });
15517
15767
 
15518
15768
  dom.delegate(this.container, "[data-wysihtml5-dialog-action=save]", "click", callbackWrapper);
15519
15769
 
15520
15770
  dom.delegate(this.container, "[data-wysihtml5-dialog-action=cancel]", "click", function(event) {
15521
- that.fire("cancel");
15522
- that.hide();
15771
+ that.cancel();
15523
15772
  event.preventDefault();
15524
15773
  event.stopPropagation();
15525
15774
  });
15526
15775
 
15527
- var formElements = this.container.querySelectorAll(SELECTOR_FORM_ELEMENTS),
15528
- i = 0,
15529
- length = formElements.length,
15530
- _clearInterval = function() { clearInterval(that.interval); };
15531
- for (; i<length; i++) {
15532
- dom.observe(formElements[i], "change", _clearInterval);
15533
- }
15534
-
15535
15776
  this._observed = true;
15536
15777
  },
15537
15778
 
@@ -15597,25 +15838,25 @@ wysihtml5.views.View = Base.extend(
15597
15838
  }
15598
15839
  },
15599
15840
 
15841
+ update: function (elementToChange) {
15842
+ this.elementToChange = elementToChange ? elementToChange : this.elementToChange;
15843
+ this._interpolate();
15844
+ },
15845
+
15600
15846
  /**
15601
15847
  * Show the dialog element
15602
15848
  */
15603
15849
  show: function(elementToChange) {
15604
- if (dom.hasClass(this.link, CLASS_NAME_OPENED)) {
15605
- return;
15606
- }
15850
+ var firstField = this.container.querySelector(SELECTOR_FORM_ELEMENTS);
15607
15851
 
15608
- var that = this,
15609
- firstField = this.container.querySelector(SELECTOR_FORM_ELEMENTS);
15610
- this.elementToChange = elementToChange;
15611
15852
  this._observe();
15612
- this._interpolate();
15613
- if (elementToChange) {
15614
- this.interval = setInterval(function() { that._interpolate(true); }, 500);
15615
- }
15853
+ this.update(elementToChange);
15854
+
15616
15855
  dom.addClass(this.link, CLASS_NAME_OPENED);
15617
15856
  this.container.style.display = "";
15857
+ this.isOpen = true;
15618
15858
  this.fire("show");
15859
+
15619
15860
  if (firstField && !elementToChange) {
15620
15861
  try {
15621
15862
  firstField.focus();
@@ -15626,15 +15867,24 @@ wysihtml5.views.View = Base.extend(
15626
15867
  /**
15627
15868
  * Hide the dialog element
15628
15869
  */
15629
- hide: function() {
15630
- clearInterval(this.interval);
15870
+ _hide: function(focus) {
15631
15871
  this.elementToChange = null;
15632
15872
  dom.removeClass(this.link, CLASS_NAME_OPENED);
15633
15873
  this.container.style.display = "none";
15874
+ this.isOpen = false;
15875
+ },
15876
+
15877
+ hide: function() {
15878
+ this._hide();
15879
+ this.fire("hide");
15880
+ },
15881
+
15882
+ cancel: function() {
15883
+ this._hide();
15634
15884
  this.fire("cancel");
15635
15885
  }
15636
15886
  });
15637
- })(wysihtml5);
15887
+ })(wysihtml5); //jshint ignore:line
15638
15888
  ;/**
15639
15889
  * Converts speech-to-text and inserts this into the editor
15640
15890
  * As of now (2011/03/25) this only is supported in Chrome >= 11
@@ -15817,8 +16067,7 @@ wysihtml5.views.View = Base.extend(
15817
16067
  _getDialog: function(link, command) {
15818
16068
  var that = this,
15819
16069
  dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
15820
- dialog,
15821
- caretBookmark;
16070
+ dialog, caretBookmark;
15822
16071
 
15823
16072
  if (dialogElement) {
15824
16073
  if (wysihtml5.toolbar["Dialog_" + command]) {
@@ -15829,7 +16078,6 @@ wysihtml5.views.View = Base.extend(
15829
16078
 
15830
16079
  dialog.on("show", function() {
15831
16080
  caretBookmark = that.composer.selection.getBookmark();
15832
-
15833
16081
  that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
15834
16082
  });
15835
16083
 
@@ -15838,14 +16086,27 @@ wysihtml5.views.View = Base.extend(
15838
16086
  that.composer.selection.setBookmark(caretBookmark);
15839
16087
  }
15840
16088
  that._execCommand(command, attributes);
15841
-
15842
16089
  that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
16090
+ that._hideAllDialogs();
16091
+ that._preventInstantFocus();
16092
+ caretBookmark = undefined;
16093
+
15843
16094
  });
15844
16095
 
15845
16096
  dialog.on("cancel", function() {
15846
- that.editor.focus(false);
16097
+ if (caretBookmark) {
16098
+ that.composer.selection.setBookmark(caretBookmark);
16099
+ }
15847
16100
  that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
16101
+ caretBookmark = undefined;
16102
+ that._preventInstantFocus();
15848
16103
  });
16104
+
16105
+ dialog.on("hide", function() {
16106
+ that.editor.fire("hide:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
16107
+ caretBookmark = undefined;
16108
+ });
16109
+
15849
16110
  }
15850
16111
  return dialog;
15851
16112
  },
@@ -15861,14 +16122,7 @@ wysihtml5.views.View = Base.extend(
15861
16122
  return;
15862
16123
  }
15863
16124
 
15864
- var commandObj = this.commandMapping[command + ":" + commandValue];
15865
-
15866
- // Show dialog when available
15867
- if (commandObj && commandObj.dialog && !commandObj.state) {
15868
- commandObj.dialog.show();
15869
- } else {
15870
- this._execCommand(command, commandValue);
15871
- }
16125
+ this._execCommand(command, commandValue);
15872
16126
  },
15873
16127
 
15874
16128
  _execCommand: function(command, commandValue) {
@@ -15918,10 +16172,19 @@ wysihtml5.views.View = Base.extend(
15918
16172
  dom.delegate(container, "[data-wysihtml5-command], [data-wysihtml5-action]", "mousedown", function(event) { event.preventDefault(); });
15919
16173
 
15920
16174
  dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) {
15921
- var link = this,
16175
+ var state,
16176
+ link = this,
15922
16177
  command = link.getAttribute("data-wysihtml5-command"),
15923
- commandValue = link.getAttribute("data-wysihtml5-command-value");
15924
- that.execCommand(command, commandValue);
16178
+ commandValue = link.getAttribute("data-wysihtml5-command-value"),
16179
+ commandObj = that.commandMapping[command + ":" + commandValue];
16180
+
16181
+ if (commandValue || !commandObj.dialog) {
16182
+ that.execCommand(command, commandValue);
16183
+ } else {
16184
+ state = getCommandState(that.composer, commandObj);
16185
+ commandObj.dialog.show(state);
16186
+ }
16187
+
15925
16188
  event.preventDefault();
15926
16189
  });
15927
16190
 
@@ -15931,21 +16194,26 @@ wysihtml5.views.View = Base.extend(
15931
16194
  event.preventDefault();
15932
16195
  });
15933
16196
 
15934
- editor.on("interaction:composer", function() {
16197
+ editor.on("interaction:composer", function(event) {
16198
+ if (!that.preventFocus) {
15935
16199
  that._updateLinkStates();
16200
+ }
15936
16201
  });
15937
16202
 
15938
- editor.on("focus:composer", function() {
15939
- that.bookmark = null;
15940
- });
16203
+ this.container.ownerDocument.addEventListener("click", function(event) {
16204
+ if (!wysihtml5.dom.contains(that.container, event.target) && !wysihtml5.dom.contains(that.composer.element, event.target)) {
16205
+ that._updateLinkStates();
16206
+ that._preventInstantFocus();
16207
+ }
16208
+ }, false);
15941
16209
 
15942
16210
  if (this.editor.config.handleTables) {
15943
- editor.on("tableselect:composer", function() {
15944
- that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "";
15945
- });
15946
- editor.on("tableunselect:composer", function() {
15947
- that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "none";
15948
- });
16211
+ editor.on("tableselect:composer", function() {
16212
+ that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "";
16213
+ });
16214
+ editor.on("tableunselect:composer", function() {
16215
+ that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "none";
16216
+ });
15949
16217
  }
15950
16218
 
15951
16219
  editor.on("change_view", function(currentView) {
@@ -15962,15 +16230,28 @@ wysihtml5.views.View = Base.extend(
15962
16230
  });
15963
16231
  },
15964
16232
 
16233
+ _hideAllDialogs: function() {
16234
+ var commandMapping = this.commandMapping;
16235
+ for (var i in commandMapping) {
16236
+ if (commandMapping[i].dialog) {
16237
+ commandMapping[i].dialog.hide();
16238
+ }
16239
+ }
16240
+ },
16241
+
16242
+ _preventInstantFocus: function() {
16243
+ this.preventFocus = true;
16244
+ setTimeout(function() {
16245
+ this.preventFocus = false;
16246
+ }.bind(this),0);
16247
+ },
16248
+
15965
16249
  _updateLinkStates: function() {
15966
16250
 
15967
- var commandMapping = this.commandMapping,
15968
- commandblankMapping = this.commandblankMapping,
15969
- actionMapping = this.actionMapping,
15970
- i,
15971
- state,
15972
- action,
15973
- command;
16251
+ var i, state, action, command, displayDialogAttributeValue,
16252
+ commandMapping = this.commandMapping,
16253
+ composer = this.composer,
16254
+ actionMapping = this.actionMapping;
15974
16255
  // every millisecond counts... this is executed quite often
15975
16256
  for (i in commandMapping) {
15976
16257
  command = commandMapping[i];
@@ -16003,18 +16284,21 @@ wysihtml5.views.View = Base.extend(
16003
16284
  if (command.group) {
16004
16285
  dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
16005
16286
  }
16006
- if (command.dialog) {
16007
- if (typeof(state) === "object" || wysihtml5.lang.object(state).isArray()) {
16008
-
16009
- if (!command.dialog.multiselect && wysihtml5.lang.object(state).isArray()) {
16010
- // Grab first and only object/element in state array, otherwise convert state into boolean
16011
- // to avoid showing a dialog for multiple selected elements which may have different attributes
16012
- // eg. when two links with different href are selected, the state will be an array consisting of both link elements
16013
- // but the dialog interface can only update one
16014
- state = state.length === 1 ? state[0] : true;
16015
- command.state = state;
16287
+ // commands with fixed value can not have a dialog.
16288
+ if (command.dialog && (typeof command.value === "undefined" || command.value === null)) {
16289
+ if (state && typeof state === "object") {
16290
+ state = getCommandState(composer, command);
16291
+ command.state = state;
16292
+
16293
+ // If dialog has dataset.showdialogonselection set as true,
16294
+ // Dialog displays on text state becoming active regardless of clobal showToolbarDialogsOnSelection options value
16295
+ displayDialogAttributeValue = command.dialog.container.dataset ? command.dialog.container.dataset.showdialogonselection : false;
16296
+
16297
+ if (composer.config.showToolbarDialogsOnSelection || displayDialogAttributeValue) {
16298
+ command.dialog.show(state);
16299
+ } else {
16300
+ command.dialog.update(state);
16016
16301
  }
16017
- command.dialog.show(state);
16018
16302
  } else {
16019
16303
  command.dialog.hide();
16020
16304
  }
@@ -16028,7 +16312,8 @@ wysihtml5.views.View = Base.extend(
16028
16312
  if (command.group) {
16029
16313
  dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
16030
16314
  }
16031
- if (command.dialog) {
16315
+ // commands with fixed value can not have a dialog.
16316
+ if (command.dialog && !command.value) {
16032
16317
  command.dialog.hide();
16033
16318
  }
16034
16319
  }
@@ -16058,6 +16343,20 @@ wysihtml5.views.View = Base.extend(
16058
16343
  }
16059
16344
  });
16060
16345
 
16346
+ function getCommandState (composer, command) {
16347
+ var state = composer.commands.state(command.name, command.value);
16348
+
16349
+ // Grab first and only object/element in state array, otherwise convert state into boolean
16350
+ // to avoid showing a dialog for multiple selected elements which may have different attributes
16351
+ // eg. when two links with different href are selected, the state will be an array consisting of both link elements
16352
+ // but the dialog interface can only update one
16353
+ if (!command.dialog.multiselect && wysihtml5.lang.object(state).isArray()) {
16354
+ state = state.length === 1 ? state[0] : true;
16355
+ }
16356
+
16357
+ return state;
16358
+ }
16359
+
16061
16360
  })(wysihtml5);
16062
16361
  ;(function(wysihtml5) {
16063
16362
  wysihtml5.toolbar.Dialog_createTable = wysihtml5.toolbar.Dialog.extend({
@@ -16067,8 +16366,7 @@ wysihtml5.views.View = Base.extend(
16067
16366
  });
16068
16367
  })(wysihtml5);
16069
16368
  ;(function(wysihtml5) {
16070
- var dom = wysihtml5.dom,
16071
- SELECTOR_FIELDS = "[data-wysihtml5-dialog-field]",
16369
+ var SELECTOR_FIELDS = "[data-wysihtml5-dialog-field]",
16072
16370
  ATTRIBUTE_FIELDS = "data-wysihtml5-dialog-field";
16073
16371
 
16074
16372
  wysihtml5.toolbar.Dialog_foreColorStyle = wysihtml5.toolbar.Dialog.extend({
@@ -16087,16 +16385,15 @@ wysihtml5.views.View = Base.extend(
16087
16385
  },
16088
16386
 
16089
16387
  _interpolate: function(avoidHiddenFields) {
16090
- var field,
16091
- fieldName,
16092
- newValue,
16388
+ var field, colourMode,
16389
+ styleParser = wysihtml5.quirks.styleParser,
16093
16390
  focusedElement = document.querySelector(":focus"),
16094
16391
  fields = this.container.querySelectorAll(SELECTOR_FIELDS),
16095
16392
  length = fields.length,
16096
16393
  i = 0,
16097
16394
  firstElement = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
16098
- colorStr = (firstElement) ? firstElement.getAttribute('style') : null,
16099
- color = (colorStr) ? wysihtml5.quirks.styleParser.parseColor(colorStr, "color") : null;
16395
+ colourStr = (firstElement) ? firstElement.getAttribute("style") : null,
16396
+ colour = (colourStr) ? styleParser.parseColor(colourStr, "color") : null;
16100
16397
 
16101
16398
  for (; i<length; i++) {
16102
16399
  field = fields[i];
@@ -16109,14 +16406,13 @@ wysihtml5.views.View = Base.extend(
16109
16406
  continue;
16110
16407
  }
16111
16408
  if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") {
16112
- if (color) {
16113
- if (color[3] && color[3] != 1) {
16114
- field.value = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ");";
16115
- } else {
16116
- field.value = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ");";
16117
- }
16409
+ colourMode = (field.dataset.colormode || "rgb").toLowerCase();
16410
+ colourMode = colourMode === "hex" ? "hash" : colourMode;
16411
+
16412
+ if (colour) {
16413
+ field.value = styleParser.unparseColor(colour, colourMode);
16118
16414
  } else {
16119
- field.value = "rgb(0,0,0);";
16415
+ field.value = styleParser.unparseColor([0, 0, 0], colourMode);
16120
16416
  }
16121
16417
  }
16122
16418
  }
@@ -16147,6 +16443,5 @@ wysihtml5.views.View = Base.extend(
16147
16443
  field.value = size;
16148
16444
  }
16149
16445
  }
16150
-
16151
16446
  });
16152
16447
  })(wysihtml5);