vendor/assets/javascripts/wysihtml5x.js in wysihtml5x-rails-0.4.15 vs vendor/assets/javascripts/wysihtml5x.js in wysihtml5x-rails-0.4.16
- old
+ new
@@ -23,22 +23,22 @@
if(!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
};/**
- * @license wysihtml5x v0.4.15
+ * @license wysihtml5x v0.4.16
* https://github.com/Edicy/wysihtml5
*
* Author: Christopher Blum (https://github.com/tiff)
* Secondary author of extended features: Oliver Pulges (https://github.com/pulges)
*
* Copyright (C) 2012 XING AG
* Licensed under the MIT license (MIT)
*
*/
var wysihtml5 = {
- version: "0.4.15",
+ version: "0.4.16",
// namespaces
commands: {},
dom: {},
quirks: {},
@@ -5367,14 +5367,41 @@
) {
return wysihtml5.dom.domNode(nextNode).next(options);
}
return nextNode;
- }
+ },
+ // Traverses a node for last children and their chidren (including itself), and finds the last node that has no children.
+ // Array of classes for forced last-leaves (ex: uneditable-container) can be defined (options = {leafClasses: [...]})
+ // Useful for finding the actually visible element before cursor
+ lastLeafNode: function(options) {
+ var lastChild;
+ // Returns non-element nodes
+ if (node.nodeType !== 1) {
+ return node;
+ }
+ // Returns if element is leaf
+ lastChild = node.lastChild;
+ if (!lastChild) {
+ return node;
+ }
+
+ // Returns if element is of of options.leafClasses leaf
+ if (options && options.leafClasses) {
+ for (var i = options.leafClasses.length; i--;) {
+ if (wysihtml5.dom.hasClass(node, options.leafClasses[i])) {
+ return node;
+ }
+ }
+ }
+
+ return wysihtml5.dom.domNode(lastChild).lastLeafNode(options);
+ }
+
};
};
})(wysihtml5);;/**
* Returns the given html wrapped in a div element
*
@@ -5492,12 +5519,17 @@
var findByStyle = (matchingSet.cssStyle || matchingSet.styleRegExp),
findByClass = (matchingSet.className || matchingSet.classRegExp);
levels = levels || 50; // Go max 50 nodes upwards from current node
+ // make the matching class regex from class name if omitted
+ if (findByClass && !matchingSet.classRegExp) {
+ matchingSet.classRegExp = new RegExp(matchingSet.className);
+ }
+
while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) {
- if (_isElement(node) && _isSameNodeName(node.nodeName, matchingSet.nodeName) &&
+ if (_isElement(node) && (!matchingSet.nodeName || _isSameNodeName(node.nodeName, matchingSet.nodeName)) &&
(!findByStyle || _hasStyle(node, matchingSet.cssStyle, matchingSet.styleRegExp)) &&
(!findByClass || _hasClassName(node, matchingSet.className, matchingSet.classRegExp))
) {
return node;
}
@@ -8920,19 +8952,29 @@
}
return false;
},
+ // deletes selection contents making sure uneditables/unselectables are not partially deleted
deleteContents: function() {
- var ranges = this.getOwnRanges();
- for (var i = ranges.length; i--;) {
- ranges[i].deleteContents();
+ var range = this.getRange(),
+ startParent, endParent;
+
+ if (this.unselectableClass) {
+ if ((startParent = wysihtml5.dom.getParentElement(range.startContainer, { className: this.unselectableClass }, false, this.contain))) {
+ range.setStartBefore(startParent);
+ }
+ if ((endParent = wysihtml5.dom.getParentElement(range.endContainer, { className: this.unselectableClass }, false, this.contain))) {
+ range.setEndAfter(endParent);
+ }
}
- this.setSelection(ranges[0]);
+ range.deleteContents();
+ this.setSelection(range);
},
getPreviousNode: function(node, ignoreEmpty) {
+ var displayStyle;
if (!node) {
var selection = this.getSelection();
node = selection.anchorNode;
}
@@ -8949,16 +8991,23 @@
if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) {
// do not count comments and other node types
ret = this.getPreviousNode(ret, ignoreEmpty);
} else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) {
- // do not count empty textnodes as previus nodes
+ // do not count empty textnodes as previous nodes
ret = this.getPreviousNode(ret, ignoreEmpty);
- } else if (ignoreEmpty && ret && ret.nodeType === 1 && !wysihtml5.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) && (/^[\s]*$/).test(ret.innerHTML)) {
+ } else if (ignoreEmpty && ret && ret.nodeType === 1) {
// Do not count empty nodes if param set.
- // Contenteditable tends to bypass and delete these silently when deleting with caret
- ret = this.getPreviousNode(ret, ignoreEmpty);
+ // Contenteditable tends to bypass and delete these silently when deleting with caret when element is inline-like
+ displayStyle = wysihtml5.dom.getStyle("display").from(ret);
+ if (
+ !wysihtml5.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) &&
+ !wysihtml5.lang.array(["block", "inline-block", "flex", "list-item", "table"]).contains(displayStyle) &&
+ (/^[\s]*$/).test(ret.innerHTML)
+ ) {
+ ret = this.getPreviousNode(ret, ignoreEmpty);
+ }
} else if (!ret && node !== this.contain) {
parent = node.parentNode;
if (parent !== this.contain) {
ret = this.getPreviousNode(parent, ignoreEmpty);
}
@@ -9006,44 +9055,68 @@
var r = rangy.createRange(this.doc),
s = this.getSelection(),
range = this.getRange(),
startNode = range.startContainer;
- if (startNode.nodeType === wysihtml5.TEXT_NODE) {
- return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
- } else {
- r.selectNodeContents(this.getRange().commonAncestorContainer);
- r.collapse(true);
- return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
+ if (startNode) {
+ if (startNode.nodeType === wysihtml5.TEXT_NODE) {
+ return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
+ } else {
+ r.selectNodeContents(this.getRange().commonAncestorContainer);
+ r.collapse(true);
+ return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
+ }
}
},
caretIsInTheBeginnig: function(ofNode) {
var selection = this.getSelection(),
node = selection.anchorNode,
offset = selection.anchorOffset;
- if (ofNode) {
+ if (ofNode && node) {
return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml5.dom.getParentElement(node.parentNode, { nodeName: ofNode }, 1)));
- } else {
+ } else if (node) {
return (offset === 0 && !this.getPreviousNode(node, true));
}
},
caretIsBeforeUneditable: function() {
var selection = this.getSelection(),
node = selection.anchorNode,
- offset = selection.anchorOffset;
+ offset = selection.anchorOffset,
+ childNodes = [],
+ range, contentNodes, lastNode;
- if (offset === 0) {
- var prevNode = this.getPreviousNode(node, true);
- if (prevNode) {
- var uneditables = this.getOwnUneditables();
- for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
- if (prevNode === uneditables[i]) {
- return uneditables[i];
+ if (node) {
+ if (offset === 0) {
+ var prevNode = this.getPreviousNode(node, true),
+ prevLeaf = prevNode ? wysihtml5.dom.domNode(prevNode).lastLeafNode((this.unselectableClass) ? {leafClasses: [this.unselectableClass]} : false) : null;
+ if (prevLeaf) {
+ var uneditables = this.getOwnUneditables();
+ for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
+ if (prevLeaf === uneditables[i]) {
+ return uneditables[i];
+ }
}
}
+ } else {
+ range = selection.getRangeAt(0);
+ range.setStart(range.startContainer, range.startOffset - 1);
+ // TODO: make getting children on range a separate funtion
+ if (range) {
+ contentNodes = range.getNodes([1,3]);
+ for (var n = 0, max = contentNodes.length; n < max; n++) {
+ if (contentNodes[n].parentNode && contentNodes[n].parentNode === node) {
+ childNodes.push(contentNodes[n]);
+ }
+ }
+ }
+ lastNode = childNodes.length > 0 ? childNodes[childNodes.length -1] : null;
+ if (lastNode && lastNode.nodeType === 1 && wysihtml5.dom.hasClass(lastNode, this.unselectableClass)) {
+ return lastNode;
+ }
+
}
}
return false;
},
@@ -9493,10 +9566,14 @@
getHtml: function() {
return this.getSelection().toHtml();
},
+ getPlainText: function () {
+ return this.getSelection().toString();
+ },
+
isEndToEndInNode: function(nodeNames) {
var range = this.getRange(),
parentElement = range.commonAncestorContainer,
startNode = range.startContainer,
endNode = range.endContainer;
@@ -11996,15 +12073,15 @@
});
});
},
focus: function() {
- if (this.element.ownerDocument.querySelector(":focus") === this.element) {
+ if (this.element && this.element.ownerDocument && this.element.ownerDocument.querySelector(":focus") === this.element) {
return;
}
- try { this.element.focus(); } catch(e) {}
+ try { if(this.element) { this.element.focus(); } } catch(e) {}
},
hide: function() {
this.element.style.display = "none";
},
@@ -12283,22 +12360,21 @@
// Only do the auto linking by ourselves when the browser doesn't support auto linking
// OR when he supports auto linking but we were able to turn it off (IE9+)
if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
this.parent.on("newword:composer", function() {
if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
- that.selection.executeAndRestore(function(startContainer, endContainer) {
- var uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname),
- isInUneditable = false;
+ var nodeWithSelection = that.selection.getSelectedNode(),
+ uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname),
+ isInUneditable = false;
- for (var i = uneditables.length; i--;) {
- if (wysihtml5.dom.contains(uneditables[i], endContainer)) {
- isInUneditable = true;
- }
+ for (var i = uneditables.length; i--;) {
+ if (wysihtml5.dom.contains(uneditables[i], nodeWithSelection)) {
+ isInUneditable = true;
}
+ }
- if (!isInUneditable) dom.autoLink(endContainer.parentNode, [that.config.uneditableContainerClassname]);
- });
+ if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.uneditableContainerClassname]);
}
});
dom.observe(this.element, "blur", function() {
dom.autoLink(that.element, [that.config.uneditableContainerClassname]);
@@ -12764,11 +12840,17 @@
var beforeUneditable = selection.caretIsBeforeUneditable();
// Do a special delete if caret would delete uneditable
if (beforeUneditable) {
event.preventDefault();
- deleteAroundEditable(selection, beforeUneditable, element);
+ // If customevents present notify element of being deleted
+ // TODO: Investigate if browser support can be extended
+ try {
+ var ev = new CustomEvent("wysihtml5:uneditable:delete");
+ beforeUneditable.dispatchEvent(ev);
+ } catch (err) {}
+ beforeUneditable.parentNode.removeChild(beforeUneditable);
}
}
} else {
if (selection.containsUneditable()) {
event.preventDefault();
@@ -12875,10 +12957,11 @@
// If supported the copied source is based directly on selection
// Very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection.
dom.observe(element, "copy", function(event) {
if (event.clipboardData) {
event.clipboardData.setData("text/html", that.config.copyedFromMarking + that.selection.getHtml());
+ event.clipboardData.setData("text/plain", that.selection.getPlainText());
event.preventDefault();
}
that.parent.fire(event.type, event).fire(event.type + ":composer", event);
});
}
@@ -12903,9 +12986,20 @@
notMyImages = element.querySelectorAll('.' + that.config.uneditableContainerClassname + ' img'),
myImages = wysihtml5.lang.array(allImages).without(notMyImages);
if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) {
that.selection.selectNode(target);
+ }
+ });
+ }
+
+ // If uneditables configured makes click on uneditable moves caret after clicked element (so it can be deleted like text)
+ // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour
+ if (this.config.uneditableContainerClassname) {
+ dom.observe(element, "click", function(event) {
+ var uneditable = wysihtml5.dom.getParentElement(event.target, { className: that.config.uneditableContainerClassname }, false, that.element);
+ if (uneditable) {
+ that.selection.setAfter(uneditable);
}
});
}
if (!browser.canSelectImagesInContentEditable()) {