wysihtml-rails 0.5.0.beta11 → 0.5.0.beta12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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);