@@ -25,7 +25,7 @@ if(!Array.isArray) {
25
25
return Object.prototype.toString.call(arg) === '[object Array]';
26
26
};
27
27
};/**
28
- * @license wysihtml5x v0.4.15
28
+ * @license wysihtml5x v0.4.16
29
29
* https://github.com/Edicy/wysihtml5
30
30
*
31
31
* Author: Christopher Blum (https://github.com/tiff)
@@ -36,7 +36,7 @@ if(!Array.isArray) {
36
36
*
37
37
*/
38
38
var wysihtml5 = {
39
- version: "0.4.15 ",
39
+ version: "0.4.16 ",
40
40
41
41
// namespaces
42
42
commands: {},
@@ -5369,9 +5369,36 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
5369
5369
}
5370
5370
5371
5371
return nextNode;
5372
- }
5372
+ },
5373
+
5374
+ // Traverses a node for last children and their chidren (including itself), and finds the last node that has no children.
5375
+ // Array of classes for forced last-leaves (ex: uneditable-container) can be defined (options = {leafClasses: [...]})
5376
+ // Useful for finding the actually visible element before cursor
5377
+ lastLeafNode: function(options) {
5378
+ var lastChild;
5379
+
5380
+ // Returns non-element nodes
5381
+ if (node.nodeType !== 1) {
5382
+ return node;
5383
+ }
5373
5384
5385
+ // Returns if element is leaf
5386
+ lastChild = node.lastChild;
5387
+ if (!lastChild) {
5388
+ return node;
5389
+ }
5390
+
5391
+ // Returns if element is of of options.leafClasses leaf
5392
+ if (options && options.leafClasses) {
5393
+ for (var i = options.leafClasses.length; i--;) {
5394
+ if (wysihtml5.dom.hasClass(node, options.leafClasses[i])) {
5395
+ return node;
5396
+ }
5397
+ }
5398
+ }
5374
5399
5400
+ return wysihtml5.dom.domNode(lastChild).lastLeafNode(options);
5401
+ }
5375
5402
5376
5403
};
5377
5404
};
@@ -5494,8 +5521,13 @@ wysihtml5.dom.getParentElement = (function() {
5494
5521
5495
5522
levels = levels || 50; // Go max 50 nodes upwards from current node
5496
5523
5524
+ // make the matching class regex from class name if omitted
5525
+ if (findByClass && !matchingSet.classRegExp) {
5526
+ matchingSet.classRegExp = new RegExp(matchingSet.className);
5527
+ }
5528
+
5497
5529
while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) {
5498
- if (_isElement(node) && _isSameNodeName(node.nodeName, matchingSet.nodeName) &&
5530
+ if (_isElement(node) && (!matchingSet.nodeName || _isSameNodeName(node.nodeName, matchingSet.nodeName) ) &&
5499
5531
(!findByStyle || _hasStyle(node, matchingSet.cssStyle, matchingSet.styleRegExp)) &&
5500
5532
(!findByClass || _hasClassName(node, matchingSet.className, matchingSet.classRegExp))
5501
5533
) {
@@ -8922,15 +8954,25 @@ wysihtml5.quirks.ensureProperClearing = (function() {
8922
8954
return false;
8923
8955
},
8924
8956
8957
+ // deletes selection contents making sure uneditables/unselectables are not partially deleted
8925
8958
deleteContents: function() {
8926
- var ranges = this.getOwnRanges();
8927
- for (var i = ranges.length; i--;) {
8928
- ranges[i].deleteContents();
8959
+ var range = this.getRange(),
8960
+ startParent, endParent;
8961
+
8962
+ if (this.unselectableClass) {
8963
+ if ((startParent = wysihtml5.dom.getParentElement(range.startContainer, { className: this.unselectableClass }, false, this.contain))) {
8964
+ range.setStartBefore(startParent);
8965
+ }
8966
+ if ((endParent = wysihtml5.dom.getParentElement(range.endContainer, { className: this.unselectableClass }, false, this.contain))) {
8967
+ range.setEndAfter(endParent);
8968
+ }
8929
8969
}
8930
- this.setSelection(ranges[0]);
8970
+ range.deleteContents();
8971
+ this.setSelection(range);
8931
8972
},
8932
8973
8933
8974
getPreviousNode: function(node, ignoreEmpty) {
8975
+ var displayStyle;
8934
8976
if (!node) {
8935
8977
var selection = this.getSelection();
8936
8978
node = selection.anchorNode;
@@ -8951,12 +8993,19 @@ wysihtml5.quirks.ensureProperClearing = (function() {
8951
8993
// do not count comments and other node types
8952
8994
ret = this.getPreviousNode(ret, ignoreEmpty);
8953
8995
} else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) {
8954
- // do not count empty textnodes as previus nodes
8996
+ // do not count empty textnodes as previous nodes
8955
8997
ret = this.getPreviousNode(ret, ignoreEmpty);
8956
- } else if (ignoreEmpty && ret && ret.nodeType === 1 && !wysihtml5.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) && (/^[\s]*$/).test(ret.innerHTML) ) {
8998
+ } else if (ignoreEmpty && ret && ret.nodeType === 1) {
8957
8999
// Do not count empty nodes if param set.
8958
- // Contenteditable tends to bypass and delete these silently when deleting with caret
8959
- ret = this.getPreviousNode(ret, ignoreEmpty);
9000
+ // Contenteditable tends to bypass and delete these silently when deleting with caret when element is inline-like
9001
+ displayStyle = wysihtml5.dom.getStyle("display").from(ret);
9002
+ if (
9003
+ !wysihtml5.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) &&
9004
+ !wysihtml5.lang.array(["block", "inline-block", "flex", "list-item", "table"]).contains(displayStyle) &&
9005
+ (/^[\s]*$/).test(ret.innerHTML)
9006
+ ) {
9007
+ ret = this.getPreviousNode(ret, ignoreEmpty);
9008
+ }
8960
9009
} else if (!ret && node !== this.contain) {
8961
9010
parent = node.parentNode;
8962
9011
if (parent !== this.contain) {
@@ -9008,40 +9057,64 @@ wysihtml5.quirks.ensureProperClearing = (function() {
9008
9057
range = this.getRange(),
9009
9058
startNode = range.startContainer;
9010
9059
9011
- if (startNode.nodeType === wysihtml5.TEXT_NODE) {
9012
- return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
9013
- } else {
9014
- r.selectNodeContents(this.getRange().commonAncestorContainer);
9015
- r.collapse(true);
9016
- return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
9060
+ if (startNode) {
9061
+ if (startNode.nodeType === wysihtml5.TEXT_NODE) {
9062
+ return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
9063
+ } else {
9064
+ r.selectNodeContents(this.getRange().commonAncestorContainer);
9065
+ r.collapse(true);
9066
+ return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
9067
+ }
9017
9068
}
9018
9069
},
9019
9070
9020
9071
caretIsInTheBeginnig: function(ofNode) {
9021
9072
var selection = this.getSelection(),
9022
9073
node = selection.anchorNode,
9023
9074
offset = selection.anchorOffset;
9024
- if (ofNode) {
9075
+ if (ofNode && node ) {
9025
9076
return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml5.dom.getParentElement(node.parentNode, { nodeName: ofNode }, 1)));
9026
- } else {
9077
+ } else if (node) {
9027
9078
return (offset === 0 && !this.getPreviousNode(node, true));
9028
9079
}
9029
9080
},
9030
9081
9031
9082
caretIsBeforeUneditable: function() {
9032
9083
var selection = this.getSelection(),
9033
9084
node = selection.anchorNode,
9034
- offset = selection.anchorOffset;
9035
-
9036
- if (offset === 0) {
9037
- var prevNode = this.getPreviousNode(node, true);
9038
- if (prevNode) {
9039
- var uneditables = this.getOwnUneditables();
9040
- for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
9041
- if (prevNode === uneditables[i]) {
9042
- return uneditables[i];
9085
+ offset = selection.anchorOffset,
9086
+ childNodes = [],
9087
+ range, contentNodes, lastNode;
9088
+
9089
+ if (node) {
9090
+ if (offset === 0) {
9091
+ var prevNode = this.getPreviousNode(node, true),
9092
+ prevLeaf = prevNode ? wysihtml5.dom.domNode(prevNode).lastLeafNode((this.unselectableClass) ? {leafClasses: [this.unselectableClass]} : false) : null;
9093
+ if (prevLeaf) {
9094
+ var uneditables = this.getOwnUneditables();
9095
+ for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
9096
+ if (prevLeaf === uneditables[i]) {
9097
+ return uneditables[i];
9098
+ }
9099
+ }
9100
+ }
9101
+ } else {
9102
+ range = selection.getRangeAt(0);
9103
+ range.setStart(range.startContainer, range.startOffset - 1);
9104
+ // TODO: make getting children on range a separate funtion
9105
+ if (range) {
9106
+ contentNodes = range.getNodes([1,3]);
9107
+ for (var n = 0, max = contentNodes.length; n < max; n++) {
9108
+ if (contentNodes[n].parentNode && contentNodes[n].parentNode === node) {
9109
+ childNodes.push(contentNodes[n]);
9110
+ }
9043
9111
}
9044
9112
}
9113
+ lastNode = childNodes.length > 0 ? childNodes[childNodes.length -1] : null;
9114
+ if (lastNode && lastNode.nodeType === 1 && wysihtml5.dom.hasClass(lastNode, this.unselectableClass)) {
9115
+ return lastNode;
9116
+ }
9117
+
9045
9118
}
9046
9119
}
9047
9120
return false;
@@ -9495,6 +9568,10 @@ wysihtml5.quirks.ensureProperClearing = (function() {
9495
9568
return this.getSelection().toHtml();
9496
9569
},
9497
9570
9571
+ getPlainText: function () {
9572
+ return this.getSelection().toString();
9573
+ },
9574
+
9498
9575
isEndToEndInNode: function(nodeNames) {
9499
9576
var range = this.getRange(),
9500
9577
parentElement = range.commonAncestorContainer,
@@ -11998,11 +12075,11 @@ wysihtml5.views.View = Base.extend(
11998
12075
},
11999
12076
12000
12077
focus: function() {
12001
- if (this.element.ownerDocument.querySelector(":focus") === this.element) {
12078
+ if (this.element && this.element.ownerDocument && this.element .ownerDocument.querySelector(":focus") === this.element) {
12002
12079
return;
12003
12080
}
12004
12081
12005
- try { this.element. focus(); } catch(e) {}
12082
+ try { if( this.element) { this.element. focus(); } } catch(e) {}
12006
12083
},
12007
12084
12008
12085
hide: function() {
@@ -12285,18 +12362,17 @@ wysihtml5.views.View = Base.extend(
12285
12362
if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
12286
12363
this.parent.on("newword:composer", function() {
12287
12364
if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
12288
- that.selection.executeAndRestore(function(startContainer, endContainer) {
12289
- var uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname),
12290
- isInUneditable = false;
12365
+ var nodeWithSelection = that.selection.getSelectedNode(),
12366
+ uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname),
12367
+ isInUneditable = false;
12291
12368
12292
- for (var i = uneditables.length; i--;) {
12293
- if (wysihtml5.dom.contains(uneditables[i], endContainer)) {
12294
- isInUneditable = true;
12295
- }
12369
+ for (var i = uneditables.length; i--;) {
12370
+ if (wysihtml5.dom.contains(uneditables[i], nodeWithSelection)) {
12371
+ isInUneditable = true;
12296
12372
}
12373
+ }
12297
12374
12298
- if (!isInUneditable) dom.autoLink(endContainer.parentNode, [that.config.uneditableContainerClassname]);
12299
- });
12375
+ if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.uneditableContainerClassname]);
12300
12376
}
12301
12377
});
12302
12378
@@ -12766,7 +12842,13 @@ wysihtml5.views.View = Base.extend(
12766
12842
// Do a special delete if caret would delete uneditable
12767
12843
if (beforeUneditable) {
12768
12844
event.preventDefault();
12769
- deleteAroundEditable(selection, beforeUneditable, element);
12845
+ // If customevents present notify element of being deleted
12846
+ // TODO: Investigate if browser support can be extended
12847
+ try {
12848
+ var ev = new CustomEvent("wysihtml5:uneditable:delete");
12849
+ beforeUneditable.dispatchEvent(ev);
12850
+ } catch (err) {}
12851
+ beforeUneditable.parentNode.removeChild(beforeUneditable);
12770
12852
}
12771
12853
}
12772
12854
} else {
@@ -12877,6 +12959,7 @@ wysihtml5.views.View = Base.extend(
12877
12959
dom.observe(element, "copy", function(event) {
12878
12960
if (event.clipboardData) {
12879
12961
event.clipboardData.setData("text/html", that.config.copyedFromMarking + that.selection.getHtml());
12962
+ event.clipboardData.setData("text/plain", that.selection.getPlainText());
12880
12963
event.preventDefault();
12881
12964
}
12882
12965
that.parent.fire(event.type, event).fire(event.type + ":composer", event);
@@ -12909,6 +12992,17 @@ wysihtml5.views.View = Base.extend(
12909
12992
});
12910
12993
}
12911
12994
12995
+ // If uneditables configured makes click on uneditable moves caret after clicked element (so it can be deleted like text)
12996
+ // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour
12997
+ if (this.config.uneditableContainerClassname) {
12998
+ dom.observe(element, "click", function(event) {
12999
+ var uneditable = wysihtml5.dom.getParentElement(event.target, { className: that.config.uneditableContainerClassname }, false, that.element);
13000
+ if (uneditable) {
13001
+ that.selection.setAfter(uneditable);
13002
+ }
13003
+ });
13004
+ }
13005
+
12912
13006
if (!browser.canSelectImagesInContentEditable()) {
12913
13007
dom.observe(element, "drop", function(event) {
12914
13008
// TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
0 commit comments