vendor/assets/javascripts/wysihtml5x.js in wysihtml5x-rails-0.4.13 vs vendor/assets/javascripts/wysihtml5x.js in wysihtml5x-rails-0.4.14
- 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.13
+ * @license wysihtml5x v0.4.14
* 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.13",
+ version: "0.4.14",
// namespaces
commands: {},
dom: {},
quirks: {},
@@ -4560,10 +4560,19 @@
return isWebKit;
},
supportsMutationEvents: function() {
return ("MutationEvent" in window);
+ },
+
+ /**
+ IE (at least up to 11) does not support clipboardData on event.
+ It is on window but cannot return text/html
+ Should actually check for clipboardData on paste event, but cannot in firefox
+ */
+ supportsModenPaste: function () {
+ return !("clipboardData" in window);
}
};
})();
;wysihtml5.lang.array = function(arr) {
return {
@@ -4761,16 +4770,29 @@
/**
* @example
* wysihtml5.lang.object({ foo: 1 }).clone();
* // => { foo: 1 }
+ *
+ * v0.4.14 adds options for deep clone : wysihtml5.lang.object({ foo: 1 }).clone(true);
*/
- clone: function() {
+ clone: function(deep) {
var newObj = {},
i;
+
+ if (obj === null || !wysihtml5.lang.object(obj).isPlainObject()) {
+ return obj;
+ }
+
for (i in obj) {
- newObj[i] = obj[i];
+ if(obj.hasOwnProperty(i)) {
+ if (deep) {
+ newObj[i] = wysihtml5.lang.object(obj[i]).clone(deep);
+ } else {
+ newObj[i] = obj[i];
+ }
+ }
}
return newObj;
},
/**
@@ -4778,22 +4800,36 @@
* wysihtml5.lang.object([]).isArray();
* // => true
*/
isArray: function() {
return Object.prototype.toString.call(obj) === "[object Array]";
+ },
+
+ /**
+ * @example
+ * wysihtml5.lang.object(function() {}).isFunction();
+ * // => true
+ */
+ isFunction: function() {
+ return Object.prototype.toString.call(obj) === '[object Function]';
+ },
+
+ isPlainObject: function () {
+ return Object.prototype.toString.call(obj) === '[object Object]';
}
};
};
;(function() {
var WHITE_SPACE_START = /^\s+/,
WHITE_SPACE_END = /\s+$/,
- ENTITY_REG_EXP = /[&<>"]/g,
+ ENTITY_REG_EXP = /[&<>\t"]/g,
ENTITY_MAP = {
'&': '&',
'<': '<',
'>': '>',
- '"': """
+ '"': """,
+ '\t':" "
};
wysihtml5.lang.string = function(str) {
str = String(str);
return {
/**
@@ -4833,12 +4869,19 @@
/**
* @example
* wysihtml5.lang.string("hello<br>").escapeHTML();
* // => "hello<br>"
*/
- escapeHTML: function() {
- return str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
+ escapeHTML: function(linebreaks, convertSpaces) {
+ var html = str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
+ if (linebreaks) {
+ html = html.replace(/(?:\r\n|\r|\n)/g, '<br />');
+ }
+ if (convertSpaces) {
+ html = html.replace(/ /gi, " ");
+ }
+ return html;
}
};
};
})();
;/**
@@ -5814,11 +5857,13 @@
* }
* });
* // => '<p class="red">foo</p><p>bar</p>'
*/
-wysihtml5.dom.parse = (function() {
+wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
+ /* TODO: Currently escaped module pattern as otherwise folloowing default swill be shared among multiple editors.
+ * Refactor whole code as this method while workind is kind of awkward too */
/**
* It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
* new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the
* node isn't closed
@@ -5832,12 +5877,11 @@
},
// Rename unknown tags to this
DEFAULT_NODE_NAME = "span",
WHITE_SPACE_REG_EXP = /\s+/,
defaultRules = { tags: {}, classes: {} },
- currentRules = {},
- uneditableClass = false;
+ currentRules = {};
/**
* Iterates over all childs of the element, recreates them, appends them into a document fragment
* which later replaces the entire body content
*/
@@ -5854,41 +5898,49 @@
if (config.clearInternals === true) {
clearInternals = true;
}
- if (config.uneditableClass) {
- uneditableClass = config.uneditableClass;
- }
-
if (isString) {
element = wysihtml5.dom.getAsDom(elementOrHtml, context);
} else {
element = elementOrHtml;
}
+ if (currentRules.selectors) {
+ _applySelectorRules(element, currentRules.selectors);
+ }
+
while (element.firstChild) {
firstChild = element.firstChild;
- newNode = _convert(firstChild, config.cleanUp, clearInternals);
+ newNode = _convert(firstChild, config.cleanUp, clearInternals, config.uneditableClass);
if (newNode) {
fragment.appendChild(newNode);
}
if (firstChild !== newNode) {
element.removeChild(firstChild);
}
}
+ if (config.unjoinNbsps) {
+ // replace joined non-breakable spaces with unjoined
+ var txtnodes = wysihtml5.dom.getTextNodes(fragment);
+ for (var n = txtnodes.length; n--;) {
+ txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
+ }
+ }
+
// Clear element contents
element.innerHTML = "";
// Insert new DOM tree
element.appendChild(fragment);
return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element;
}
- function _convert(oldNode, cleanUp, clearInternals) {
+ function _convert(oldNode, cleanUp, clearInternals, uneditableClass) {
var oldNodeType = oldNode.nodeType,
oldChilds = oldNode.childNodes,
oldChildsLength = oldChilds.length,
method = NODE_TYPE_MAPPING[oldNodeType],
i = 0,
@@ -5909,20 +5961,24 @@
// false defines that tag should be removed but contents should remain (unwrap)
fragment = oldNode.ownerDocument.createDocumentFragment();
for (i = oldChildsLength; i--;) {
if (oldChilds[i]) {
- newChild = _convert(oldChilds[i], cleanUp, clearInternals);
+ newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
if (newChild) {
if (oldChilds[i] === newChild) {
i--;
}
fragment.insertBefore(newChild, fragment.firstChild);
}
}
}
+ if (wysihtml5.dom.getStyle("display").from(oldNode) === "block") {
+ fragment.appendChild(oldNode.ownerDocument.createElement("br"));
+ }
+
// TODO: try to minimize surplus spaces
if (wysihtml5.lang.array([
"div", "pre", "p",
"table", "td", "th",
"ul", "ol", "li",
@@ -5947,11 +6003,11 @@
}
// Converts all childnodes
for (i=0; i<oldChildsLength; i++) {
if (oldChilds[i]) {
- newChild = _convert(oldChilds[i], cleanUp, clearInternals);
+ newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
if (newChild) {
if (oldChilds[i] === newChild) {
i--;
}
newNode.appendChild(newChild);
@@ -5980,16 +6036,35 @@
newNode.normalize();
}
return newNode;
}
+ function _applySelectorRules (element, selectorRules) {
+ var sel, method, els;
+
+ for (sel in selectorRules) {
+ if (selectorRules.hasOwnProperty(sel)) {
+ if (wysihtml5.lang.object(selectorRules[sel]).isFunction()) {
+ method = selectorRules[sel];
+ } else if (typeof(selectorRules[sel]) === "string" && elementHandlingMethods[selectorRules[sel]]) {
+ method = elementHandlingMethods[selectorRules[sel]];
+ }
+ els = element.querySelectorAll(sel);
+ for (var i = els.length; i--;) {
+ method(els[i]);
+ }
+ }
+ }
+ }
+
function _handleElement(oldNode, clearInternals) {
var rule,
newNode,
tagRules = currentRules.tags,
nodeName = oldNode.nodeName.toLowerCase(),
- scopeName = oldNode.scopeName;
+ scopeName = oldNode.scopeName,
+ renameTag;
/**
* We already parsed that element
* ignore it! (yes, this sometimes happens in IE8 when the html is invalid)
*/
@@ -6037,18 +6112,29 @@
} else {
// Remove empty unknown elements
return null;
}
- newNode = oldNode.ownerDocument.createElement(rule.rename_tag || nodeName);
- _handleAttributes(oldNode, newNode, rule, clearInternals);
- _handleStyles(oldNode, newNode, rule);
- // tests if type condition is met or node should be removed/unwrapped
+ // tests if type condition is met or node should be removed/unwrapped/renamed
if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) {
- return (rule.remove_action && rule.remove_action == "unwrap") ? false : null;
+ if (rule.remove_action) {
+ if (rule.remove_action === "unwrap") {
+ return false;
+ } else if (rule.remove_action === "rename") {
+ renameTag = rule.remove_action_rename_to || DEFAULT_NODE_NAME;
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
}
+ newNode = oldNode.ownerDocument.createElement(renameTag || rule.rename_tag || nodeName);
+ _handleAttributes(oldNode, newNode, rule, clearInternals);
+ _handleStyles(oldNode, newNode, rule);
+
oldNode = null;
if (newNode.normalize) { newNode.normalize(); }
return newNode;
}
@@ -6132,11 +6218,11 @@
// test for attributes in general against regex match
if (definition.attrs) {
for (a in definition.attrs) {
if (definition.attrs.hasOwnProperty(a)) {
- attr = _getAttribute(oldNode, a);
+ attr = wysihtml5.dom.getAttribute(oldNode, a);
if (typeof(attr) === "string") {
if (attr.search(definition.attrs[a]) > -1) {
return true;
}
}
@@ -6145,38 +6231,92 @@
}
return false;
}
function _handleStyles(oldNode, newNode, rule) {
- var s;
+ var s, v;
if(rule && rule.keep_styles) {
for (s in rule.keep_styles) {
if (rule.keep_styles.hasOwnProperty(s)) {
- if (s == "float") {
+ v = (s === "float") ? oldNode.style.styleFloat || oldNode.style.cssFloat : oldNode.style[s];
+ // value can be regex and if so should match or style skipped
+ if (rule.keep_styles[s] instanceof RegExp && !(rule.keep_styles[s].test(v))) {
+ continue;
+ }
+ if (s === "float") {
// IE compability
- if (oldNode.style.styleFloat) {
- newNode.style.styleFloat = oldNode.style.styleFloat;
- }
- if (oldNode.style.cssFloat) {
- newNode.style.cssFloat = oldNode.style.cssFloat;
- }
+ newNode.style[(oldNode.style.styleFloat) ? 'styleFloat': 'cssFloat'] = v;
} else if (oldNode.style[s]) {
- newNode.style[s] = oldNode.style[s];
+ newNode.style[s] = v;
}
}
}
}
+ };
+
+ function _getAttributesBeginningWith(beginning, attributes) {
+ var returnAttributes = [];
+ for (var attr in attributes) {
+ if (attributes.hasOwnProperty(attr) && attr.indexOf(beginning) === 0) {
+ returnAttributes.push(attr);
+ }
+ }
+ return returnAttributes;
}
+ function _checkAttribute(attributeName, attributeValue, methodName, nodeName) {
+ var method = attributeCheckMethods[methodName],
+ newAttributeValue;
+
+ if (method) {
+ if (attributeValue || (attributeName === "alt" && nodeName == "IMG")) {
+ newAttributeValue = method(attributeValue);
+ if (typeof(newAttributeValue) === "string") {
+ return newAttributeValue;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ function _checkAttributes(oldNode, local_attributes) {
+ var globalAttributes = wysihtml5.lang.object(currentRules.attributes || {}).clone(), // global values for check/convert values of attributes
+ checkAttributes = wysihtml5.lang.object(globalAttributes).merge( wysihtml5.lang.object(local_attributes || {}).clone()).get(),
+ attributes = {},
+ oldAttributes = wysihtml5.dom.getAttributes(oldNode),
+ attributeName, newValue, matchingAttributes;
+
+ for (attributeName in checkAttributes) {
+ if ((/\*$/).test(attributeName)) {
+
+ matchingAttributes = _getAttributesBeginningWith(attributeName.slice(0,-1), oldAttributes);
+ for (var i = 0, imax = matchingAttributes.length; i < imax; i++) {
+
+ newValue = _checkAttribute(matchingAttributes[i], oldAttributes[matchingAttributes[i]], checkAttributes[attributeName], oldNode.nodeName);
+ if (newValue !== false) {
+ attributes[matchingAttributes[i]] = newValue;
+ }
+ }
+ } else {
+ newValue = _checkAttribute(attributeName, oldAttributes[attributeName], checkAttributes[attributeName], oldNode.nodeName);
+ if (newValue !== false) {
+ attributes[attributeName] = newValue;
+ }
+ }
+ }
+
+ return attributes;
+ }
+
// TODO: refactor. Too long to read
function _handleAttributes(oldNode, newNode, rule, clearInternals) {
var attributes = {}, // fresh new set of attributes to set on newNode
setClass = rule.set_class, // classes to set
addClass = rule.add_class, // add classes based on existing attributes
addStyle = rule.add_style, // add styles based on existing attributes
setAttributes = rule.set_attributes, // attributes to set on the current node
- checkAttributes = rule.check_attributes, // check/convert values of attributes
allowedClasses = currentRules.classes,
i = 0,
classes = [],
styles = [],
newClasses = [],
@@ -6184,33 +6324,18 @@
classesLength,
newClassesLength,
currentClass,
newClass,
attributeName,
- newAttributeValue,
- method,
- oldAttribute;
+ method;
if (setAttributes) {
attributes = wysihtml5.lang.object(setAttributes).clone();
}
- if (checkAttributes) {
- for (attributeName in checkAttributes) {
- method = attributeCheckMethods[checkAttributes[attributeName]];
- if (!method) {
- continue;
- }
- oldAttribute = _getAttribute(oldNode, attributeName);
- if (oldAttribute || (attributeName === "alt" && oldNode.nodeName == "IMG")) {
- newAttributeValue = method(oldAttribute);
- if (typeof(newAttributeValue) === "string") {
- attributes[attributeName] = newAttributeValue;
- }
- }
- }
- }
+ // check/convert values of attributes
+ attributes = wysihtml5.lang.object(attributes).merge(_checkAttributes(oldNode, rule.check_attributes)).get();
if (setClass) {
classes.push(setClass);
}
@@ -6218,11 +6343,11 @@
for (attributeName in addClass) {
method = addClassMethods[addClass[attributeName]];
if (!method) {
continue;
}
- newClass = method(_getAttribute(oldNode, attributeName));
+ newClass = method(wysihtml5.dom.getAttribute(oldNode, attributeName));
if (typeof(newClass) === "string") {
classes.push(newClass);
}
}
}
@@ -6232,20 +6357,40 @@
method = addStyleMethods[addStyle[attributeName]];
if (!method) {
continue;
}
- newStyle = method(_getAttribute(oldNode, attributeName));
+ newStyle = method(wysihtml5.dom.getAttribute(oldNode, attributeName));
if (typeof(newStyle) === "string") {
styles.push(newStyle);
}
}
}
if (typeof(allowedClasses) === "string" && allowedClasses === "any" && oldNode.getAttribute("class")) {
- attributes["class"] = oldNode.getAttribute("class");
+ if (currentRules.classes_blacklist) {
+ oldClasses = oldNode.getAttribute("class");
+ if (oldClasses) {
+ classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
+ }
+
+ classesLength = classes.length;
+ for (; i<classesLength; i++) {
+ currentClass = classes[i];
+ if (!currentRules.classes_blacklist[currentClass]) {
+ newClasses.push(currentClass);
+ }
+ }
+
+ if (newClasses.length) {
+ attributes["class"] = wysihtml5.lang.array(newClasses).unique().join(" ");
+ }
+
+ } else {
+ attributes["class"] = oldNode.getAttribute("class");
+ }
} else {
// make sure that wysihtml5 temp class doesn't get stripped out
if (!clearInternals) {
allowedClasses["_wysihtml5-temp-placeholder"] = 1;
allowedClasses["_rangySelectionBoundary"] = 1;
@@ -6302,53 +6447,10 @@
newNode.setAttribute("height", attributes.height);
}
}
}
- /**
- * IE gives wrong results for hasAttribute/getAttribute, for example:
- * var td = document.createElement("td");
- * td.getAttribute("rowspan"); // => "1" in IE
- *
- * Therefore we have to check the element's outerHTML for the attribute
- */
- var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
- function _getAttribute(node, attributeName) {
- attributeName = attributeName.toLowerCase();
- var nodeName = node.nodeName;
- if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
- // Get 'src' attribute value via object property since this will always contain the
- // full absolute url (http://...)
- // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
- // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
- return node.src;
- } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
- // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
- var outerHTML = node.outerHTML.toLowerCase(),
- // TODO: This might not work for attributes without value: <input disabled>
- hasAttribute = outerHTML.indexOf(" " + attributeName + "=") != -1;
-
- return hasAttribute ? node.getAttribute(attributeName) : null;
- } else{
- return node.getAttribute(attributeName);
- }
- }
-
- /**
- * Check whether the given node is a proper loaded image
- * FIXME: Returns undefined when unknown (Chrome, Safari)
- */
- function _isLoadedImage(node) {
- try {
- return node.complete && !node.mozMatchesSelector(":-moz-broken");
- } catch(e) {
- if (node.complete && node.readyState === "complete") {
- return true;
- }
- }
- }
-
var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
function _handleText(oldNode) {
var nextSibling = oldNode.nextSibling;
if (nextSibling && nextSibling.nodeType === wysihtml5.TEXT_NODE) {
// Concatenate text nodes
@@ -6529,12 +6631,22 @@
return false;
};
})()
};
- return parse;
-})();
+ var elementHandlingMethods = {
+ unwrap: function (element) {
+ wysihtml5.dom.unwrap(element);
+ },
+
+ remove: function (element) {
+ element.parentNode.removeChild(element);
+ }
+ };
+
+ return parse(elementOrHtml_current, config_current);
+};
;/**
* Checks for empty text node childs and removes them
*
* @param {Element} node The element in which to cleanup
* @example
@@ -7157,11 +7269,11 @@
wysihtml5.dom.getAttribute = function(node, attributeName) {
var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
attributeName = attributeName.toLowerCase();
var nodeName = node.nodeName;
- if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
+ if (nodeName == "IMG" && attributeName == "src" && wysihtml5.dom.isLoadedImage(node) === true) {
// Get 'src' attribute value via object property since this will always contain the
// full absolute url (http://...)
// this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
// will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
return node.src;
@@ -7174,10 +7286,56 @@
return hasAttribute ? node.getAttribute(attributeName) : null;
} else{
return node.getAttribute(attributeName);
}
};
+;/**
+ * Get all attributes of an element
+ *
+ * IE gives wrong results for hasAttribute/getAttribute, for example:
+ * var td = document.createElement("td");
+ * td.getAttribute("rowspan"); // => "1" in IE
+ *
+ * Therefore we have to check the element's outerHTML for the attribute
+*/
+
+wysihtml5.dom.getAttributes = function(node) {
+ var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly(),
+ nodeName = node.nodeName,
+ attributes = [],
+ attr;
+
+ for (attr in node.attributes) {
+ if ((node.attributes.hasOwnProperty && node.attributes.hasOwnProperty(attr)) || (!node.attributes.hasOwnProperty && Object.prototype.hasOwnProperty.call(node.attributes, attr))) {
+ if (node.attributes[attr].specified) {
+ if (nodeName == "IMG" && node.attributes[attr].name.toLowerCase() == "src" && wysihtml5.dom.isLoadedImage(node) === true) {
+ attributes['src'] = node.src;
+ } else if (wysihtml5.lang.array(['rowspan', 'colspan']).contains(node.attributes[attr].name.toLowerCase()) && HAS_GET_ATTRIBUTE_BUG) {
+ if (node.attributes[attr].value !== 1) {
+ attributes[node.attributes[attr].name] = node.attributes[attr].value;
+ }
+ } else {
+ attributes[node.attributes[attr].name] = node.attributes[attr].value;
+ }
+ }
+ }
+ }
+ return attributes;
+};;/**
+ * Check whether the given node is a proper loaded image
+ * FIXME: Returns undefined when unknown (Chrome, Safari)
+*/
+
+wysihtml5.dom.isLoadedImage = function (node) {
+ try {
+ return node.complete && !node.mozMatchesSelector(":-moz-broken");
+ } catch(e) {
+ if (node.complete && node.readyState === "complete") {
+ return true;
+ }
+ }
+};
;(function(wysihtml5) {
var api = wysihtml5.dom;
var MapCell = function(cell) {
@@ -8141,63 +8299,127 @@
while (node.lastChild) {
wysihtml5.dom.insert(node.lastChild).after(node);
}
node.parentNode.removeChild(node);
}
+};;/*
+ * Methods for fetching pasted html before it gets inserted into content
+**/
+
+/* Modern event.clipboardData driven approach.
+ * Advantage is that it does not have to loose selection or modify dom to catch the data.
+ * IE does not support though.
+**/
+wysihtml5.dom.getPastedHtml = function(event) {
+ var html;
+ if (event.clipboardData) {
+ if (wysihtml5.lang.array(event.clipboardData.types).contains('text/html')) {
+ html = event.clipboardData.getData('text/html');
+ } else if (wysihtml5.lang.array(event.clipboardData.types).contains('text/plain')) {
+ html = wysihtml5.lang.string(event.clipboardData.getData('text/plain')).escapeHTML(true, true);
+ }
+ }
+ return html;
+};
+
+/* Older temprorary contenteditable as paste source catcher method for fallbacks */
+wysihtml5.dom.getPastedHtmlWithDiv = function (composer, f) {
+ var selBookmark = composer.selection.getBookmark(),
+ doc = composer.element.ownerDocument,
+ cleanerDiv = coc.createElement('DIV');
+
+ cleanerDiv.style.width = "1px";
+ cleanerDiv.style.height = "1px";
+ cleanerDiv.style.visibility = "hidden";
+ cleanerDiv.style.overflow = "hidden";
+
+ cleanerDiv.setAttribute('contenteditable', 'true');
+ doc.body.appendChild(cleanerDiv);
+ cleanerDiv.focus();
+
+ setTimeout(function () {
+ composer.selection.setBookmark(selBookmark);
+ f(cleanerDiv.innerHTML);
+ cleanerDiv.parentNode.removeChild(cleanerDiv);
+ }, 0);
};;/**
* Fix most common html formatting misbehaviors of browsers implementation when inserting
* content via copy & paste contentEditable
*
* @author Christopher Blum
*/
wysihtml5.quirks.cleanPastedHTML = (function() {
- // TODO: We probably need more rules here
- var defaultRules = {
- // When pasting underlined links <a> into a contentEditable, IE thinks, it has to insert <u> to keep the styling
- "a u": wysihtml5.dom.replaceWithChildNodes
+
+ var styleToRegex = function (styleStr) {
+ var trimmedStr = wysihtml5.lang.string(styleStr).trim(),
+ escapedStr = trimmedStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+
+ return new RegExp("^((?!^" + escapedStr + "$).)*$", "i");
};
- function cleanPastedHTML(elementOrHtml, rules, context) {
- rules = rules || defaultRules;
- context = context || elementOrHtml.ownerDocument || document;
+ var extendRulesWithStyleExceptions = function (rules, exceptStyles) {
+ var newRules = wysihtml5.lang.object(rules).clone(true),
+ tag, style;
- var element,
- isString = typeof(elementOrHtml) === "string",
- method,
- matches,
- matchesLength,
- i,
- j = 0, n;
- if (isString) {
- element = wysihtml5.dom.getAsDom(elementOrHtml, context);
- } else {
- element = elementOrHtml;
- }
+ for (tag in newRules.tags) {
- for (i in rules) {
- matches = element.querySelectorAll(i);
- method = rules[i];
- matchesLength = matches.length;
- for (; j<matchesLength; j++) {
- method(matches[j]);
+ if (newRules.tags.hasOwnProperty(tag)) {
+ if (newRules.tags[tag].keep_styles) {
+ for (style in newRules.tags[tag].keep_styles) {
+ if (newRules.tags[tag].keep_styles.hasOwnProperty(style)) {
+ if (exceptStyles[style]) {
+ newRules.tags[tag].keep_styles[style] = styleToRegex(exceptStyles[style]);
+ }
+ }
+ }
+ }
}
}
- // replace joined non-breakable spaces with unjoined
- var txtnodes = wysihtml5.dom.getTextNodes(element);
- for (n = txtnodes.length; n--;) {
- txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
+ return newRules;
+ };
+
+ var pickRuleset = function(ruleset, html) {
+ var pickedSet, defaultSet;
+
+ if (!ruleset) {
+ return null;
}
- matches = elementOrHtml = rules = null;
+ for (var i = 0, max = ruleset.length; i < max; i++) {
+ if (!ruleset[i].condition) {
+ defaultSet = ruleset[i].set;
+ }
+ if (ruleset[i].condition && ruleset[i].condition.test(html)) {
+ return ruleset[i].set;
+ }
+ }
- return isString ? element.innerHTML : element;
- }
+ return defaultSet;
+ };
- return cleanPastedHTML;
-})();
-;/**
+ return function(html, options) {
+ var exceptStyles = {
+ 'color': wysihtml5.dom.getStyle("color").from(options.referenceNode),
+ 'fontSize': wysihtml5.dom.getStyle("font-size").from(options.referenceNode)
+ },
+ rules = extendRulesWithStyleExceptions(pickRuleset(options.rules, html) || {}, exceptStyles),
+ newHtml;
+
+ newHtml = wysihtml5.dom.parse(html, {
+ "rules": rules,
+ "cleanUp": true, // <span> elements, empty or without attributes, should be removed/replaced with their content
+ "context": options.referenceNode.ownerDocument,
+ "uneditableClass": options.uneditableClass,
+ "clearInternals" : true, // don't paste temprorary selection and other markings
+ "unjoinNbsps" : true
+ });
+
+ return newHtml;
+ };
+
+})();;/**
* IE and Opera leave an empty paragraph in the contentEditable element after clearing it
*
* @param {Object} contentEditableElement The contentEditable element to observe for clearing events
* @exaple
* wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
@@ -8823,11 +9045,11 @@
}
}
return false;
},
- // TODO: Figure out a method from following 3 that would work universally
+ // TODO: Figure out a method from following 2 that would work universally
executeAndRestoreRangy: function(method, restoreScrollPosition) {
var win = this.doc.defaultView || this.doc.parentWindow,
sel = rangy.saveSelection(win);
if (!sel) {
@@ -8936,14 +9158,22 @@
* @example
* selection.insertHTML("<p>foobar</p>");
*/
insertHTML: function(html) {
var range = rangy.createRange(this.doc),
- node = range.createContextualFragment(html),
- lastChild = node.lastChild;
+ node = this.doc.createElement('DIV'),
+ fragment = this.doc.createDocumentFragment(),
+ lastChild;
- this.insertNode(node);
+ node.innerHTML = html;
+ lastChild = node.lastChild;
+
+ while (node.firstChild) {
+ fragment.appendChild(node.firstChild);
+ }
+ this.insertNode(fragment);
+
if (lastChild) {
this.setAfter(lastChild);
}
},
@@ -12558,11 +12788,11 @@
var that = this,
state = this.getValue(false, false),
container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
element = this.element,
focusBlurElement = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? element : this.sandbox.getWindow(),
- pasteEvents = ["drop", "paste"],
+ pasteEvents = ["drop", "paste", "beforepaste"],
interactionEvents = ["drop", "paste", "mouseup", "focus", "keyup"];
// --------- destroy:composer event ---------
dom.observe(container, "DOMNodeRemoved", function() {
clearInterval(domNodeRemovedInterval);
@@ -12631,13 +12861,13 @@
dom.observe(element, "dragenter", function() {
that.parent.fire("unset_placeholder");
});
dom.observe(element, pasteEvents, function(event) {
- setTimeout(function() {
+ //setTimeout(function() {
that.parent.fire(event.type, event).fire(event.type + ":composer", event);
- }, 0);
+ //}, 0);
});
// --------- neword event ---------
dom.observe(element, "keyup", function(event) {
var keyCode = event.keyCode;
@@ -12991,14 +13221,16 @@
autoLink: true,
// Includes table editing events and cell selection tracking
handleTables: true,
// Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
handleTabKey: true,
- // Object which includes parser rules to apply when html gets inserted via copy & paste
+ // Object which includes parser rules to apply when html gets cleaned
// See parser_rules/*.js for examples
parserRules: { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
- // Parser method to use when the user inserts content via copy & paste
+ // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead
+ pasteParserRulesets: null,
+ // Parser method to use when the user inserts content
parser: wysihtml5.dom.parse,
// Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
composerClassName: "wysihtml5-editor",
// Class name to add to the body when the wysihtml5 editor is supported
bodyClassName: "wysihtml5-supported",
@@ -13139,16 +13371,42 @@
/**
* Prepare html parser logic
* - Observes for paste and drop
*/
_initParser: function() {
- this.on("paste:composer", function() {
- var keepScrollPosition = true,
- that = this;
- that.composer.selection.executeAndRestore(function() {
- wysihtml5.quirks.cleanPastedHTML(that.composer.element);
- that.parse(that.composer.element);
- }, keepScrollPosition);
+ var that = this,
+ oldHtml,
+ cleanHtml;
+
+ if (wysihtml5.browser.supportsModenPaste()) {
+ this.on("paste:composer", function(event) {
+ event.preventDefault();
+ oldHtml = wysihtml5.dom.getPastedHtml(event);
+ if (oldHtml) {
+ that._cleanAndPaste(oldHtml);
+ }
+ });
+
+ } else {
+ this.on("beforepaste:composer", function(event) {
+ event.preventDefault();
+ wysihtml5.dom.getPastedHtmlWithDiv(that.composer, function(pastedHTML) {
+ if (pastedHTML) {
+ that._cleanAndPaste(pastedHTML);
+ }
+ });
+ });
+
+ }
+ },
+
+ _cleanAndPaste: function (oldHtml) {
+ var cleanHtml = wysihtml5.quirks.cleanPastedHTML(oldHtml, {
+ "referenceNode": this.composer.element,
+ "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
+ "uneditableClass": this.config.uneditableContainerClassname
});
+ this.composer.selection.deleteContents();
+ this.composer.selection.insertHTML(cleanHtml);
}
});
})(wysihtml5);