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()) {