Skip to content
Browse files

Remove 0.3.0 release candidates and add 0.4.0pre

1 parent 890d673 commit 4b0b64c6d5ed7b9af698dbe2bd3f383d44106728 @tiff tiff committed Nov 1, 2012
View
9,462 dist/wysihtml5-0.3.0_rc1.js
0 additions, 9,462 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
258 dist/wysihtml5-0.3.0_rc1.min.js
0 additions, 258 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
260 dist/wysihtml5-0.3.0_rc2.min.js
0 additions, 260 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
1,716 dist/wysihtml5-0.3.0_rc2.js → dist/wysihtml5-0.4.0pre.js
@@ -1,5 +1,5 @@
/**
- * @license wysihtml5 v0.3.0_rc2
+ * @license wysihtml5 v0.4.0pre
* https://github.com/xing/wysihtml5
*
* Author: Christopher Blum (https://github.com/tiff)
@@ -9,7 +9,7 @@
*
*/
var wysihtml5 = {
- version: "0.3.0_rc2",
+ version: "0.4.0pre",
// namespaces
commands: {},
@@ -3395,7 +3395,11 @@ wysihtml5.browser = (function() {
isOpera = userAgent.indexOf("Opera/") !== -1;
function iosVersion(userAgent) {
- return ((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [, 0])[1];
+ return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [, 0])[1];
+ }
+
+ function androidVersion(userAgent) {
+ return +(userAgent.match(/android (\d+)/) || [, 0])[1];
}
return {
@@ -3419,8 +3423,7 @@ wysihtml5.browser = (function() {
// document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
hasQuerySelectorSupport = document.querySelector && document.querySelectorAll,
// contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
- isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
-
+ isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
return hasContentEditableSupport
&& hasEditingApiSupport
&& hasQuerySelectorSupport
@@ -3432,8 +3435,11 @@ wysihtml5.browser = (function() {
},
isIos: function() {
- var userAgent = this.USER_AGENT.toLowerCase();
- return userAgent.indexOf("webkit") !== -1 && userAgent.indexOf("mobile") !== -1;
+ return (/ipad|iphone|ipod/i).test(this.USER_AGENT);
+ },
+
+ isAndroid: function() {
+ return this.USER_AGENT.indexOf("Android") !== -1;
},
/**
@@ -3463,7 +3469,7 @@ wysihtml5.browser = (function() {
* Firefox sometimes shows a huge caret in the beginning after focusing
*/
displaysCaretInEmptyContentEditableCorrectly: function() {
- return !isGecko;
+ return isIE;
},
/**
@@ -3474,6 +3480,13 @@ wysihtml5.browser = (function() {
hasCurrentStyleProperty: function() {
return "currentStyle" in testElement;
},
+
+ /**
+ * Firefox on OSX navigates through history when hitting CMD + Arrow right/left
+ */
+ hasHistoryIssue: function() {
+ return isGecko;
+ },
/**
* Whether the browser inserts a <br> when pressing enter in a contentEditable element
@@ -3499,30 +3512,8 @@ wysihtml5.browser = (function() {
supportsEventsInIframeCorrectly: function() {
return !isOpera;
},
-
- /**
- * Chrome & Safari only fire the ondrop/ondragend/... events when the ondragover event is cancelled
- * with event.preventDefault
- * Firefox 3.6 fires those events anyway, but the mozilla doc says that the dragover/dragenter event needs
- * to be cancelled
- */
- firesOnDropOnlyWhenOnDragOverIsCancelled: function() {
- return isWebKit || isGecko;
- },
/**
- * Whether the browser supports the event.dataTransfer property in a proper way
- */
- supportsDataTransfer: function() {
- try {
- // Firefox doesn't support dataTransfer in a safe way, it doesn't strip script code in the html payload (like Chrome does)
- return isWebKit && (window.Clipboard || window.DataTransfer).prototype.getData;
- } catch(e) {
- return false;
- }
- },
-
- /**
* Everything below IE9 doesn't know how to treat HTML5 tags
*
* @param {Object} context The document object on which to check HTML5 support
@@ -3557,8 +3548,8 @@ wysihtml5.browser = (function() {
// When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
// converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
// IE and Opera act a bit different here as they convert the entire content of the current block element into a list
- "insertUnorderedList": isIE || isOpera,
- "insertOrderedList": isIE || isOpera
+ "insertUnorderedList": isIE || isWebKit,
+ "insertOrderedList": isIE || isWebKit
};
// Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
@@ -3631,14 +3622,6 @@ wysihtml5.browser = (function() {
},
/**
- * When the caret is in an empty list (<ul><li>|</li></ul>) which is the first child in an contentEditable container
- * pressing backspace doesn't remove the entire list as done in other browsers
- */
- clearsListsInContentEditableCorrectly: function() {
- return isGecko || isIE || isWebKit;
- },
-
- /**
* All browsers except Safari and Chrome automatically scroll the range/caret position into view
*/
autoScrollsToCaret: function() {
@@ -3679,14 +3662,6 @@ wysihtml5.browser = (function() {
},
/**
- * Whether the browser supports the classList object for fast className manipulation
- * See https://developer.mozilla.org/en/DOM/element.classList
- */
- supportsClassList: function() {
- return "classList" in testElement;
- },
-
- /**
* Opera needs a white space after a <br> in order to position the caret correctly
*/
needsSpaceAfterLineBreak: function() {
@@ -3733,6 +3708,23 @@ wysihtml5.browser = (function() {
hasUndoInContextMenu: function() {
return isGecko || isChrome || isOpera;
+ },
+
+ /**
+ * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
+ * is used (regardless if rangy or native)
+ * This especially happens when the caret is positioned right after a <br> because then
+ * insertNode() will insert the node right before the <br>
+ */
+ hasInsertNodeIssue: function() {
+ return isOpera;
+ },
+
+ /**
+ * IE 8+9 don't fire the focus event of the <body> when the iframe gets focused (even though the caret gets set into the <body>)
+ */
+ hasIframeFocusIssue: function() {
+ return isIE;
}
};
})();wysihtml5.lang.array = function(arr) {
@@ -3794,28 +3786,14 @@ wysihtml5.browser = (function() {
};
};wysihtml5.lang.Dispatcher = Base.extend(
/** @scope wysihtml5.lang.Dialog.prototype */ {
- observe: function(eventName, handler) {
+ on: function(eventName, handler) {
this.events = this.events || {};
this.events[eventName] = this.events[eventName] || [];
this.events[eventName].push(handler);
return this;
},
- on: function() {
- return this.observe.apply(this, wysihtml5.lang.array(arguments).get());
- },
-
- fire: function(eventName, payload) {
- this.events = this.events || {};
- var handlers = this.events[eventName] || [],
- i = 0;
- for (; i<handlers.length; i++) {
- handlers[i].call(this, payload);
- }
- return this;
- },
-
- stopObserving: function(eventName, handler) {
+ off: function(eventName, handler) {
this.events = this.events || {};
var i = 0,
handlers,
@@ -3834,6 +3812,26 @@ wysihtml5.browser = (function() {
this.events = {};
}
return this;
+ },
+
+ fire: function(eventName, payload) {
+ this.events = this.events || {};
+ var handlers = this.events[eventName] || [],
+ i = 0;
+ for (; i<handlers.length; i++) {
+ handlers[i].call(this, payload);
+ }
+ return this;
+ },
+
+ // deprecated, use .on()
+ observe: function() {
+ return this.on.apply(this, arguments);
+ },
+
+ // deprecated, use .off()
+ stopObserving: function() {
+ return this.off.apply(this, arguments);
}
});wysihtml5.lang.object = function(obj) {
return {
@@ -3913,7 +3911,7 @@ wysihtml5.browser = (function() {
by: function(replace) {
return str.split(search).join(replace);
}
- }
+ };
}
};
};
@@ -4058,12 +4056,12 @@ wysihtml5.browser = (function() {
// Reveal url reg exp to the outside
wysihtml5.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
})(wysihtml5);(function(wysihtml5) {
- var supportsClassList = wysihtml5.browser.supportsClassList(),
- api = wysihtml5.dom;
+ var api = wysihtml5.dom;
api.addClass = function(element, className) {
- if (supportsClassList) {
- return element.classList.add(className);
+ var classList = element.classList;
+ if (classList) {
+ return classList.add(className);
}
if (api.hasClass(element, className)) {
return;
@@ -4072,16 +4070,18 @@ wysihtml5.browser = (function() {
};
api.removeClass = function(element, className) {
- if (supportsClassList) {
- return element.classList.remove(className);
+ var classList = element.classList;
+ if (classList) {
+ return classList.remove(className);
}
element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ");
};
api.hasClass = function(element, className) {
- if (supportsClassList) {
- return element.classList.contains(className);
+ var classList = element.classList;
+ if (classList) {
+ return classList.contains(className);
}
var elementClassName = element.className;
@@ -4148,18 +4148,38 @@ wysihtml5.dom.convertToList = (function() {
var doc = element.ownerDocument,
list = _createList(doc, listType),
- childNodes = wysihtml5.lang.array(element.childNodes).get(),
- childNodesLength = childNodes.length,
+ lineBreaks = element.querySelectorAll("br"),
+ lineBreaksLength = lineBreaks.length,
+ childNodes,
+ childNodesLength,
childNode,
+ lineBreak,
+ parentNode,
isBlockElement,
isLineBreak,
currentListItem,
- i = 0;
- for (; i<childNodesLength; i++) {
- currentListItem = currentListItem || _createListItem(doc, list);
- childNode = childNodes[i];
- isBlockElement = wysihtml5.dom.getStyle("display").from(childNode) === "block";
- isLineBreak = childNode.nodeName === "BR";
+ i;
+
+ // First find <br> at the end of inline elements and move them behind them
+ for (i=0; i<lineBreaksLength; i++) {
+ lineBreak = lineBreaks[i];
+ while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) {
+ if (wysihtml5.dom.getStyle("display").from(parentNode) === "block") {
+ parentNode.removeChild(lineBreak);
+ break;
+ }
+ wysihtml5.dom.insert(lineBreak).after(lineBreak.parentNode);
+ }
+ }
+
+ childNodes = wysihtml5.lang.array(element.childNodes).get();
+ childNodesLength = childNodes.length;
+
+ for (i=0; i<childNodesLength; i++) {
+ currentListItem = currentListItem || _createListItem(doc, list);
+ childNode = childNodes[i];
+ isBlockElement = wysihtml5.dom.getStyle("display").from(childNode) === "block";
+ isLineBreak = childNode.nodeName === "BR";
if (isBlockElement) {
// Append blockElement to current <li> if empty, otherwise create a new one
@@ -4178,6 +4198,10 @@ wysihtml5.dom.convertToList = (function() {
currentListItem.appendChild(childNode);
}
+ if (childNodes.length === 0) {
+ _createListItem(doc, list);
+ }
+
element.parentNode.replaceChild(list, element);
return list;
}
@@ -4208,7 +4232,7 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
length = attributesToCopy.length;
for (; i<length; i++) {
attribute = attributesToCopy[i];
- if (elementToCopyFrom[attribute]) {
+ if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") {
elementToCopyTo[attribute] = elementToCopyFrom[attribute];
}
}
@@ -4242,6 +4266,8 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
* Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set
* So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then
* its computed css width will be 198px
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=520992
*/
var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"];
@@ -4496,7 +4522,7 @@ wysihtml5.dom.getStyle = (function() {
// Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
if (currentStyle) {
try {
- return currentStyle[camelizedProperty];
+ return currentStyle[camelizedProperty];
} catch(e) {
//ie will occasionally fail for unknown reasons. swallowing exception
}
@@ -4603,19 +4629,24 @@ wysihtml5.dom.insert = function(elementToInsert) {
return {
into: function(doc) {
- var head = doc.head || doc.getElementsByTagName("head")[0],
- styleElement = doc.createElement("style");
-
+ var styleElement = doc.createElement("style");
styleElement.type = "text/css";
-
+
if (styleElement.styleSheet) {
styleElement.styleSheet.cssText = rules;
} else {
styleElement.appendChild(doc.createTextNode(rules));
}
-
- if (head) {
- head.appendChild(styleElement);
+
+ var link = doc.querySelector("head link");
+ if (link) {
+ link.parentNode.insertBefore(styleElement, link);
+ return;
+ } else {
+ var head = doc.querySelector("head");
+ if (head) {
+ head.appendChild(styleElement);
+ }
}
}
};
@@ -4782,9 +4813,10 @@ wysihtml5.dom.parse = (function() {
var oldNodeType = oldNode.nodeType,
oldChilds = oldNode.childNodes,
oldChildsLength = oldChilds.length,
- newNode,
method = NODE_TYPE_MAPPING[oldNodeType],
- i = 0;
+ i = 0,
+ newNode,
+ newChild;
newNode = method && method(oldNode);
@@ -4813,7 +4845,6 @@ wysihtml5.dom.parse = (function() {
function _handleElement(oldNode) {
var rule,
newNode,
- endTag,
tagRules = currentRules.tags,
nodeName = oldNode.nodeName.toLowerCase(),
scopeName = oldNode.scopeName;
@@ -5041,6 +5072,30 @@ wysihtml5.dom.parse = (function() {
});
};
})(),
+
+ src: (function() {
+ var REG_EXP = /^(\/|https?:\/\/)/i;
+ return function(attributeValue) {
+ if (!attributeValue || !attributeValue.match(REG_EXP)) {
+ return null;
+ }
+ return attributeValue.replace(REG_EXP, function(match) {
+ return match.toLowerCase();
+ });
+ };
+ })(),
+
+ href: (function() {
+ var REG_EXP = /^(\/|https?:\/\/|mailto:)/i;
+ return function(attributeValue) {
+ if (!attributeValue || !attributeValue.match(REG_EXP)) {
+ return null;
+ }
+ return attributeValue.replace(REG_EXP, function(match) {
+ return match.toLowerCase();
+ });
+ };
+ })(),
alt: (function() {
var REG_EXP = /[^ a-z0-9_\-]/gi;
@@ -5116,7 +5171,8 @@ wysihtml5.dom.parse = (function() {
};
return parse;
-})();/**
+})();
+/**
* Checks for empty text node childs and removes them
*
* @param {Element} node The element in which to cleanup
@@ -5235,38 +5291,58 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
element.appendChild(lineBreak);
}
- function resolveList(list) {
- if (list.nodeName !== "MENU" && list.nodeName !== "UL" && list.nodeName !== "OL") {
+ function resolveList(list, useLineBreaks) {
+ if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
return;
}
var doc = list.ownerDocument,
fragment = doc.createDocumentFragment(),
- previousSibling = list.previousSibling,
+ previousSibling = list.previousElementSibling || list.previousSibling,
firstChild,
lastChild,
isLastChild,
shouldAppendLineBreak,
+ paragraph,
listItem;
- if (previousSibling && !_isBlockElement(previousSibling)) {
- _appendLineBreak(fragment);
- }
-
- while (listItem = list.firstChild) {
- lastChild = listItem.lastChild;
- while (firstChild = listItem.firstChild) {
- isLastChild = firstChild === lastChild;
- // This needs to be done before appending it to the fragment, as it otherwise will loose style information
- shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
- fragment.appendChild(firstChild);
- if (shouldAppendLineBreak) {
- _appendLineBreak(fragment);
+ if (useLineBreaks) {
+ // Insert line break if list is after a non-block element
+ if (previousSibling && !_isBlockElement(previousSibling)) {
+ _appendLineBreak(fragment);
+ }
+
+ while (listItem = (list.firstElementChild || list.firstChild)) {
+ lastChild = listItem.lastChild;
+ while (firstChild = listItem.firstChild) {
+ isLastChild = firstChild === lastChild;
+ // This needs to be done before appending it to the fragment, as it otherwise will lose style information
+ shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
+ fragment.appendChild(firstChild);
+ if (shouldAppendLineBreak) {
+ _appendLineBreak(fragment);
+ }
}
+
+ listItem.parentNode.removeChild(listItem);
+ }
+ } else {
+ while (listItem = (list.firstElementChild || list.firstChild)) {
+ if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) {
+ while (firstChild = listItem.firstChild) {
+ fragment.appendChild(firstChild);
+ }
+ } else {
+ paragraph = doc.createElement("p");
+ while (firstChild = listItem.firstChild) {
+ paragraph.appendChild(firstChild);
+ }
+ fragment.appendChild(paragraph);
+ }
+ listItem.parentNode.removeChild(listItem);
}
-
- listItem.parentNode.removeChild(listItem);
}
+
list.parentNode.replaceChild(fragment, list);
}
@@ -5315,7 +5391,7 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
"XMLHttpRequest", "XDomainRequest"
],
/**
- * Properties to unset/proetect on the document object
+ * Properties to unset/protect on the document object
*/
documentProperties = [
"referrer",
@@ -5534,7 +5610,7 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
element.setAttribute(mapping[i] || i, attributes[i]);
}
}
- }
+ };
};
})();wysihtml5.dom.setStyles = function(styles) {
return {
@@ -5575,21 +5651,23 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
if (view.hasPlaceholderSet()) {
view.clear();
}
+ view.placeholderSet = false;
dom.removeClass(view.element, CLASS_NAME);
},
set = function() {
if (view.isEmpty()) {
+ view.placeholderSet = true;
view.setValue(placeholderText);
dom.addClass(view.element, CLASS_NAME);
}
};
editor
- .observe("set_placeholder", set)
- .observe("unset_placeholder", unset)
- .observe("focus:composer", unset)
- .observe("paste:composer", unset)
- .observe("blur:composer", set);
+ .on("set_placeholder", set)
+ .on("unset_placeholder", unset)
+ .on("focus:composer", unset)
+ .on("paste:composer", unset)
+ .on("blur:composer", set);
set();
};
@@ -5675,79 +5753,22 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
* @exaple
* wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
*/
-(function(wysihtml5) {
- var dom = wysihtml5.dom;
-
- wysihtml5.quirks.ensureProperClearing = (function() {
- var clearIfNecessary = function(event) {
- var element = this;
- setTimeout(function() {
- var innerHTML = element.innerHTML.toLowerCase();
- if (innerHTML == "<p>&nbsp;</p>" ||
- innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
- element.innerHTML = "";
- }
- }, 0);
- };
-
- return function(composer) {
- dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
- };
- })();
-
-
-
- /**
- * In Opera when the caret is in the first and only item of a list (<ul><li>|</li></ul>) and the list is the first child of the contentEditable element, it's impossible to delete the list by hitting backspace
- *
- * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
- * @exaple
- * wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
- */
- wysihtml5.quirks.ensureProperClearingOfLists = (function() {
- var ELEMENTS_THAT_CONTAIN_LI = ["OL", "UL", "MENU"];
-
- var clearIfNecessary = function(element, contentEditableElement) {
- if (!contentEditableElement.firstChild || !wysihtml5.lang.array(ELEMENTS_THAT_CONTAIN_LI).contains(contentEditableElement.firstChild.nodeName)) {
- return;
- }
-
- var list = dom.getParentElement(element, { nodeName: ELEMENTS_THAT_CONTAIN_LI });
- if (!list) {
- return;
- }
-
- var listIsFirstChildOfContentEditable = list == contentEditableElement.firstChild;
- if (!listIsFirstChildOfContentEditable) {
- return;
- }
-
- var hasOnlyOneListItem = list.childNodes.length <= 1;
- if (!hasOnlyOneListItem) {
- return;
- }
-
- var onlyListItemIsEmpty = list.firstChild ? list.firstChild.innerHTML === "" : true;
- if (!onlyListItemIsEmpty) {
- return;
- }
-
- list.parentNode.removeChild(list);
- };
-
- return function(composer) {
- dom.observe(composer.element, "keydown", function(event) {
- if (event.keyCode !== wysihtml5.BACKSPACE_KEY) {
- return;
- }
-
- var element = composer.selection.getSelectedNode();
- clearIfNecessary(element, composer.element);
- });
- };
- })();
+wysihtml5.quirks.ensureProperClearing = (function() {
+ var clearIfNecessary = function() {
+ var element = this;
+ setTimeout(function() {
+ var innerHTML = element.innerHTML.toLowerCase();
+ if (innerHTML == "<p>&nbsp;</p>" ||
+ innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
+ element.innerHTML = "";
+ }
+ }, 0);
+ };
-})(wysihtml5);
+ return function(composer) {
+ wysihtml5.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
+ };
+})();
// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
//
// In Firefox this:
@@ -5778,83 +5799,6 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
return innerHTML;
};
})(wysihtml5);/**
- * Some browsers don't insert line breaks when hitting return in a contentEditable element
- * - Opera & IE insert new <p> on return
- * - Chrome & Safari insert new <div> on return
- * - Firefox inserts <br> on return (yippie!)
- *
- * @param {Element} element
- *
- * @example
- * wysihtml5.quirks.insertLineBreakOnReturn(element);
- */
-(function(wysihtml5) {
- var dom = wysihtml5.dom,
- USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
- LIST_TAGS = ["UL", "OL", "MENU"];
-
- wysihtml5.quirks.insertLineBreakOnReturn = function(composer) {
- function unwrap(selectedNode) {
- var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
- if (!parentElement) {
- return;
- }
-
- var invisibleSpace = document.createTextNode(wysihtml5.INVISIBLE_SPACE);
- dom.insert(invisibleSpace).before(parentElement);
- dom.replaceWithChildNodes(parentElement);
- composer.selection.selectNode(invisibleSpace);
- }
-
- function keyDown(event) {
- var keyCode = event.keyCode;
- if (event.shiftKey || (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY)) {
- return;
- }
-
- var element = event.target,
- selectedNode = composer.selection.getSelectedNode(),
- blockElement = dom.getParentElement(selectedNode, { nodeName: USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS }, 4);
- if (blockElement) {
- // Some browsers create <p> elements after leaving a list
- // check after keydown of backspace and return whether a <p> got inserted and unwrap it
- if (blockElement.nodeName === "LI" && (keyCode === wysihtml5.ENTER_KEY || keyCode === wysihtml5.BACKSPACE_KEY)) {
- setTimeout(function() {
- var selectedNode = composer.selection.getSelectedNode(),
- list,
- div;
- if (!selectedNode) {
- return;
- }
-
- list = dom.getParentElement(selectedNode, {
- nodeName: LIST_TAGS
- }, 2);
-
- if (list) {
- return;
- }
-
- unwrap(selectedNode);
- }, 0);
- } else if (blockElement.nodeName.match(/H[1-6]/) && keyCode === wysihtml5.ENTER_KEY) {
- setTimeout(function() {
- unwrap(composer.selection.getSelectedNode());
- }, 0);
- }
- return;
- }
-
- if (keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
- composer.commands.exec("insertLineBreak");
- event.preventDefault();
- }
- }
-
- // keypress doesn't fire when you hit backspace
- dom.observe(composer.element.ownerDocument, "keydown", keyDown);
- };
-})(wysihtml5);/**
* Force rerendering of a given element
* Needed to fix display misbehaviors of IE
*
@@ -5965,7 +5909,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
* @example
* selection.selectNode(document.getElementById("my-image"));
*/
- selectNode: function(node) {
+ selectNode: function(node, avoidInvisibleSpace) {
var range = rangy.createRange(this.doc),
isElement = node.nodeType === wysihtml5.ELEMENT_NODE,
canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
@@ -5974,7 +5918,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
displayStyle = dom.getStyle("display").from(node),
isBlockElement = (displayStyle === "block" || displayStyle === "list-item");
- if (isEmpty && isElement && canHaveHTML) {
+ if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) {
// Make sure that caret is visible in node by inserting a zero width no breaking space
try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
}
@@ -6028,8 +5972,12 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
oldScrollTop = restoreScrollPosition && body.scrollTop,
oldScrollLeft = restoreScrollPosition && body.scrollLeft,
className = "_wysihtml5-temp-placeholder",
- placeholderHTML = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
+ placeholderHtml = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
range = this.getRange(this.doc),
+ caretPlaceholder,
+ newCaretPlaceholder,
+ nextSibling,
+ node,
newRange;
// Nothing selected, execute and say goodbye
@@ -6038,21 +5986,34 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
return;
}
- var node = range.createContextualFragment(placeholderHTML);
- range.insertNode(node);
+ if (wysihtml5.browser.hasInsertNodeIssue()) {
+ this.doc.execCommand("insertHTML", false, placeholderHtml);
+ } else {
+ node = range.createContextualFragment(placeholderHtml);
+ range.insertNode(node);
+ }
// Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
try {
method(range.startContainer, range.endContainer);
- } catch(e3) {
- setTimeout(function() { throw e3; }, 0);
+ } catch(e) {
+ setTimeout(function() { throw e; }, 0);
}
caretPlaceholder = this.doc.querySelector("." + className);
if (caretPlaceholder) {
newRange = rangy.createRange(this.doc);
- newRange.selectNode(caretPlaceholder);
- newRange.deleteContents();
+ nextSibling = caretPlaceholder.nextSibling;
+ // Opera is so fucked up when you wanna set focus before a <br>
+ if (wysihtml5.browser.hasInsertNodeIssue() && nextSibling && nextSibling.nodeName === "BR") {
+ newCaretPlaceholder = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
+ dom.insert(newCaretPlaceholder).after(caretPlaceholder);
+ newRange.setStartBefore(newCaretPlaceholder);
+ newRange.setEndBefore(newCaretPlaceholder);
+ } else {
+ newRange.selectNode(caretPlaceholder);
+ newRange.deleteContents();
+ }
this.setSelection(newRange);
} else {
// fallback for when all hell breaks loose
@@ -6067,7 +6028,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
// Remove it again, just to make sure that the placeholder is definitely out of the dom tree
try {
caretPlaceholder.parentNode.removeChild(caretPlaceholder);
- } catch(e4) {}
+ } catch(e2) {}
},
/**
@@ -6112,7 +6073,13 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
try { newRange.setEnd(rangeBackup.endContainer, rangeBackup.endOffset); } catch(e2) {}
try { this.setSelection(newRange); } catch(e3) {}
},
-
+
+ set: function(node, offset) {
+ var newRange = rangy.createRange(this.doc);
+ newRange.setStart(node, offset || 0);
+ this.setSelection(newRange);
+ },
+
/**
* Insert html at the caret position and move the cursor after the inserted html
*
@@ -6175,6 +6142,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
*/
scrollIntoView: function() {
var doc = this.doc,
+ tolerance = 5, // px
hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
tempElement = doc._wysihtml5ScrollIntoViewElement = doc._wysihtml5ScrollIntoViewElement || (function() {
var element = doc.createElement("span");
@@ -6188,7 +6156,7 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
this.insertNode(tempElement);
offsetTop = _getCumulativeOffsetTop(tempElement);
tempElement.parentNode.removeChild(tempElement);
- if (offsetTop > doc.body.scrollTop) {
+ if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) {
doc.body.scrollTop = offsetTop;
}
}
@@ -6218,7 +6186,6 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
_selectLine_MSIE: function() {
var range = this.doc.selection.createRange(),
rangeTop = range.boundingTop,
- rangeHeight = range.boundingHeight,
scrollWidth = this.doc.body.scrollWidth,
rangeBottom,
rangeEnd,
@@ -6761,20 +6728,24 @@ wysihtml5.Commands = Base.extend(
*/
exec: function(command, value) {
var obj = wysihtml5.commands[command],
- method = obj && obj.exec;
+ args = wysihtml5.lang.array(arguments).get(),
+ method = obj && obj.exec,
+ result = null;
this.editor.fire("beforecommand:composer");
if (method) {
- return method.call(obj, this.composer, command, value);
+ args.unshift(this.composer);
+ result = method.apply(obj, args);
} else {
try {
// try/catch for buggy firefox
- return this.doc.execCommand(command, false, value);
+ result = this.doc.execCommand(command, false, value);
} catch(e) {}
}
this.editor.fire("aftercommand:composer");
+ return result;
},
/**
@@ -6789,9 +6760,11 @@ wysihtml5.Commands = Base.extend(
*/
state: function(command, commandValue) {
var obj = wysihtml5.commands[command],
+ args = wysihtml5.lang.array(arguments).get(),
method = obj && obj.state;
if (method) {
- return method.call(obj, this.composer, command, commandValue);
+ args.unshift(this.composer);
+ return method.apply(obj, args);
} else {
try {
// try/catch for buggy firefox
@@ -6800,52 +6773,22 @@ wysihtml5.Commands = Base.extend(
return false;
}
}
- },
-
- /**
- * Get the current command's value
- *
- * @param {String} command The command string which to check (eg. "formatBlock")
- * @return {String} The command value
- * @example
- * var currentBlockElement = commands.value("formatBlock");
- */
- value: function(command) {
- var obj = wysihtml5.commands[command],
- method = obj && obj.value;
- if (method) {
- return method.call(obj, this.composer, command);
- } else {
- try {
- // try/catch for buggy firefox
- return this.doc.queryCommandValue(command);
- } catch(e) {
- return null;
- }
- }
}
-});(function(wysihtml5) {
- var undef;
-
- wysihtml5.commands.bold = {
- exec: function(composer, command) {
- return wysihtml5.commands.formatInline.exec(composer, command, "b");
- },
-
- state: function(composer, command, color) {
- // element.ownerDocument.queryCommandState("bold") results:
- // firefox: only <b>
- // chrome: <b>, <strong>, <h1>, <h2>, ...
- // ie: <b>, <strong>
- // opera: <b>, <strong>
- return wysihtml5.commands.formatInline.state(composer, command, "b");
- },
+});
+wysihtml5.commands.bold = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatInline.exec(composer, command, "b");
+ },
- value: function() {
- return undef;
- }
- };
-})(wysihtml5);
+ state: function(composer, command) {
+ // element.ownerDocument.queryCommandState("bold") results:
+ // firefox: only <b>
+ // chrome: <b>, <strong>, <h1>, <h2>, ...
+ // ie: <b>, <strong>
+ // opera: <b>, <strong>
+ return wysihtml5.commands.formatInline.state(composer, command, "b");
+ }
+};
(function(wysihtml5) {
var undef,
@@ -6905,10 +6848,10 @@ wysihtml5.Commands = Base.extend(
hasElementChild = !!anchor.querySelector("*");
isEmpty = textContent === "" || textContent === wysihtml5.INVISIBLE_SPACE;
if (!hasElementChild && isEmpty) {
- dom.setTextContent(anchor, anchor.href);
+ dom.setTextContent(anchor, attributes.text || anchor.href);
whiteSpace = doc.createTextNode(" ");
composer.selection.setAfter(anchor);
- composer.selection.insertNode(whiteSpace);
+ dom.insert(whiteSpace).after(anchor);
elementToSetCaretAfter = whiteSpace;
}
}
@@ -6945,10 +6888,6 @@ wysihtml5.Commands = Base.extend(
state: function(composer, command) {
return wysihtml5.commands.formatInline.state(composer, command, "A");
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);/**
@@ -6958,7 +6897,7 @@ wysihtml5.Commands = Base.extend(
*/
(function(wysihtml5) {
var undef,
- REG_EXP = /wysiwyg-font-size-[a-z]+/g;
+ REG_EXP = /wysiwyg-font-size-[0-9a-z\-]+/g;
wysihtml5.commands.fontSize = {
exec: function(composer, command, size) {
@@ -6980,8 +6919,7 @@ wysihtml5.Commands = Base.extend(
* Instead we set a css class
*/
(function(wysihtml5) {
- var undef,
- REG_EXP = /wysiwyg-color-[a-z]+/g;
+ var REG_EXP = /wysiwyg-color-[0-9a-z]+/g;
wysihtml5.commands.foreColor = {
exec: function(composer, command, color) {
@@ -6990,20 +6928,14 @@ wysihtml5.Commands = Base.extend(
state: function(composer, command, color) {
return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);(function(wysihtml5) {
- var undef,
- dom = wysihtml5.dom,
- DEFAULT_NODE_NAME = "DIV",
+ var dom = wysihtml5.dom,
// Following elements are grouped
// when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
// instead of creating a H4 within a H1 which would result in semantically invalid html
- BLOCK_ELEMENTS_GROUP = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", DEFAULT_NODE_NAME];
+ BLOCK_ELEMENTS_GROUP = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", "DIV"];
/**
* Remove similiar classes (based on classRegExp)
@@ -7140,7 +7072,7 @@ wysihtml5.Commands = Base.extend(
composer.selection.surround(element);
_removeLineBreakBeforeAndAfter(element);
_removeLastChildIfLineBreak(element);
- composer.selection.selectNode(element);
+ composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
}
function _hasClasses(element) {
@@ -7149,26 +7081,28 @@ wysihtml5.Commands = Base.extend(
wysihtml5.commands.formatBlock = {
exec: function(composer, command, nodeName, className, classRegExp) {
- var doc = composer.doc,
- blockElement = this.state(composer, command, nodeName, className, classRegExp),
+ var doc = composer.doc,
+ blockElement = this.state(composer, command, nodeName, className, classRegExp),
+ useLineBreaks = composer.config.useLineBreaks,
+ defaultNodeName = useLineBreaks ? "DIV" : "P",
selectedNode;
nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
-
+
if (blockElement) {
composer.selection.executeAndRestoreSimple(function() {
if (classRegExp) {
_removeClass(blockElement, classRegExp);
}
var hasClasses = _hasClasses(blockElement);
- if (!hasClasses && blockElement.nodeName === (nodeName || DEFAULT_NODE_NAME)) {
+ if (!hasClasses && (useLineBreaks || nodeName === "P")) {
// Insert a line break afterwards and beforewards when there are siblings
// that are not of type line break or block element
_addLineBreakBeforeAndAfter(blockElement);
dom.replaceWithChildNodes(blockElement);
- } else if (hasClasses) {
- // Make sure that styling is kept by renaming the element to <div> and copying over the class name
- dom.renameElement(blockElement, DEFAULT_NODE_NAME);
+ } else {
+ // Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
+ dom.renameElement(blockElement, nodeName === "P" ? "DIV" : defaultNodeName);
}
});
return;
@@ -7182,7 +7116,7 @@ wysihtml5.Commands = Base.extend(
});
if (blockElement) {
- composer.selection.executeAndRestoreSimple(function() {
+ composer.selection.executeAndRestore(function() {
// Rename current block element to new block element and add class
if (nodeName) {
blockElement = dom.renameElement(blockElement, nodeName);
@@ -7196,11 +7130,11 @@ wysihtml5.Commands = Base.extend(
}
if (composer.commands.support(command)) {
- _execCommand(doc, command, nodeName || DEFAULT_NODE_NAME, className);
+ _execCommand(doc, command, nodeName || defaultNodeName, className);
return;
}
- blockElement = doc.createElement(nodeName || DEFAULT_NODE_NAME);
+ blockElement = doc.createElement(nodeName || defaultNodeName);
if (className) {
blockElement.className = className;
}
@@ -7215,10 +7149,6 @@ wysihtml5.Commands = Base.extend(
className: className,
classRegExp: classRegExp
});
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);/**
@@ -7255,8 +7185,7 @@ wysihtml5.Commands = Base.extend(
* <span>ab|c</span> de|<b>fgh</b>
*/
(function(wysihtml5) {
- var undef,
- // Treat <b> as <strong> and vice versa
+ var // Treat <b> as <strong> and vice versa
ALIAS_MAPPING = {
"strong": "b",
"em": "i",
@@ -7310,33 +7239,22 @@ wysihtml5.Commands = Base.extend(
}
return _getApplier(tagName, className, classRegExp).isAppliedToRange(range);
- },
-
- value: function() {
- return undef;
}
};
-})(wysihtml5);(function(wysihtml5) {
- var undef;
-
- wysihtml5.commands.insertHTML = {
- exec: function(composer, command, html) {
- if (composer.commands.support(command)) {
- composer.doc.execCommand(command, false, html);
- } else {
- composer.selection.insertHTML(html);
- }
- },
-
- state: function() {
- return false;
- },
-
- value: function() {
- return undef;
+})(wysihtml5);wysihtml5.commands.insertHTML = {
+ exec: function(composer, command, html) {
+ if (composer.commands.support(command)) {
+ composer.doc.execCommand(command, false, html);
+ } else {
+ composer.selection.insertHTML(html);
}
- };
-})(wysihtml5);(function(wysihtml5) {
+ },
+
+ state: function() {
+ return false;
+ }
+};
+(function(wysihtml5) {
var NODE_NAME = "IMG";
wysihtml5.commands.insertImage = {
@@ -7378,9 +7296,12 @@ wysihtml5.Commands = Base.extend(
}
image = doc.createElement(NODE_NAME);
-
+
for (i in value) {
- image[i] = value[i];
+ if (i === "className") {
+ i = "class";
+ }
+ image.setAttribute(i, value[i]);
}
composer.selection.insertNode(image);
@@ -7432,16 +7353,10 @@ wysihtml5.Commands = Base.extend(
}
return imagesInSelection[0];
- },
-
- value: function(composer) {
- var image = this.state(composer);
- return image && image.src;
}
};
})(wysihtml5);(function(wysihtml5) {
- var undef,
- LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
+ var LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
wysihtml5.commands.insertLineBreak = {
exec: function(composer, command) {
@@ -7457,158 +7372,122 @@ wysihtml5.Commands = Base.extend(
state: function() {
return false;
- },
-
- value: function() {
- return undef;
}
};
-})(wysihtml5);(function(wysihtml5) {
- var undef;
-
- wysihtml5.commands.insertOrderedList = {
- exec: function(composer, command) {
- var doc = composer.doc,
- selectedNode,
- isEmpty,
- tempElement,
- list;
-
- if (composer.commands.support(command)) {
- doc.execCommand(command, false, null);
- } else {
- selectedNode = composer.selection.getSelectedNode();
- list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: ["UL", "OL"] }, 4);
- if (!list) {
- tempElement = doc.createElement("span");
- composer.selection.surround(tempElement);
- isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE;
- composer.selection.executeAndRestoreSimple(function() {
- list = wysihtml5.dom.convertToList(tempElement, "ol");
- });
-
- if (isEmpty) {
- composer.selection.selectNode(list.querySelector("li"));
- }
- return;
- }
-
- composer.selection.executeAndRestoreSimple(function() {
- if (list.nodeName === "OL") {
- // Unwrap list
- // <ol><li>foo</li><li>bar</li></ol>
- // becomes:
- // foo<br>bar<br>
- wysihtml5.dom.resolveList(list);
- } else if (list.nodeName === "UL" || list.nodeName === "MENU") {
- // Turn an unordered list into an ordered list
- // <ul><li>foo</li><li>bar</li></ul>
- // becomes:
- // <ol><li>foo</li><li>bar</li></ol>
- wysihtml5.dom.renameElement(list, "ol");
- }
- });
- }
- },
-
- state: function(composer, command) {
- try {
- return composer.doc.queryCommandState(command);
- } catch(e) {
- return false;
+})(wysihtml5);wysihtml5.commands.insertOrderedList = {
+ exec: function(composer, command) {
+ var doc = composer.doc,
+ selectedNode = composer.selection.getSelectedNode(),
+ list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }),
+ otherList = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }),
+ tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
+ isEmpty,
+ tempElement;
+
+ if (!list && !otherList && composer.commands.support(command)) {
+ doc.execCommand(command, false, null);
+ return;
+ }
+
+ if (list) {
+ // Unwrap list
+ // <ol><li>foo</li><li>bar</li></ol>
+ // becomes:
+ // foo<br>bar<br>
+ composer.selection.executeAndRestore(function() {
+ wysihtml5.dom.resolveList(list, composer.config.useLineBreaks);
+ });
+ } else if (otherList) {
+ // Turn an unordered list into an ordered list
+ // <ul><li>foo</li><li>bar</li></ul>
+ // becomes:
+ // <ol><li>foo</li><li>bar</li></ol>
+ composer.selection.executeAndRestore(function() {
+ wysihtml5.dom.renameElement(otherList, "ol");
+ });
+ } else {
+ // Create list
+ composer.commands.exec("formatBlock", "div", tempClassName);
+ tempElement = doc.querySelector("." + tempClassName);
+ isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
+ composer.selection.executeAndRestore(function() {
+ list = wysihtml5.dom.convertToList(tempElement, "ol");
+ });
+ if (isEmpty) {
+ composer.selection.selectNode(list.querySelector("li"), true);
}
- },
-
- value: function() {
- return undef;
}
- };
-})(wysihtml5);(function(wysihtml5) {
- var undef;
+ },
- wysihtml5.commands.insertUnorderedList = {
- exec: function(composer, command) {
- var doc = composer.doc,
- selectedNode,
- isEmpty,
- tempElement,
- list;
-
- if (composer.commands.support(command)) {
- doc.execCommand(command, false, null);
- } else {
- selectedNode = composer.selection.getSelectedNode();
- list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: ["UL", "OL"] });
-
- if (!list) {
- tempElement = doc.createElement("span");
- composer.selection.surround(tempElement);
- isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE;
- composer.selection.executeAndRestoreSimple(function() {
- list = wysihtml5.dom.convertToList(tempElement, "ul");
- });
-
- if (isEmpty) {
- composer.selection.selectNode(list.querySelector("li"));
- }
- return;
- }
-
- composer.selection.executeAndRestoreSimple(function() {
- if (list.nodeName === "UL") {
- // Unwrap list
- // <ul><li>foo</li><li>bar</li></ul>
- // becomes:
- // foo<br>bar<br>
- wysihtml5.dom.resolveList(list);
- } else if (list.nodeName === "OL" || list.nodeName === "MENU") {
- // Turn an ordered list into an unordered list
- // <ol><li>foo</li><li>bar</li></ol>
- // becomes:
- // <ul><li>foo</li><li>bar</li></ul>
- wysihtml5.dom.renameElement(list, "ul");
- }
- });
- }
- },
-
- state: function(composer, command) {
- try {
- return composer.doc.queryCommandState(command);
- } catch(e) {
- return false;
+ state: function(composer) {
+ var selectedNode = composer.selection.getSelectedNode();
+ return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" });
+ }
+};wysihtml5.commands.insertUnorderedList = {
+ exec: function(composer, command) {
+ var doc = composer.doc,
+ selectedNode = composer.selection.getSelectedNode(),
+ list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }),
+ otherList = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }),
+ tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
+ isEmpty,
+ tempElement;
+
+ if (!list && !otherList && composer.commands.support(command)) {
+ doc.execCommand(command, false, null);
+ return;
+ }
+
+ if (list) {
+ // Unwrap list
+ // <ul><li>foo</li><li>bar</li></ul>
+ // becomes:
+ // foo<br>bar<br>
+ composer.selection.executeAndRestore(function() {
+ wysihtml5.dom.resolveList(list, composer.config.useLineBreaks);
+ });
+ } else if (otherList) {
+ // Turn an ordered list into an unordered list
+ // <ol><li>foo</li><li>bar</li></ol>
+ // becomes:
+ // <ul><li>foo</li><li>bar</li></ul>
+ composer.selection.executeAndRestore(function() {
+ wysihtml5.dom.renameElement(otherList, "ul");
+ });
+ } else {
+ // Create list
+ composer.commands.exec("formatBlock", "div", tempClassName);
+ tempElement = doc.querySelector("." + tempClassName);
+ isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
+ composer.selection.executeAndRestore(function() {
+ list = wysihtml5.dom.convertToList(tempElement, "ul");
+ });
+ if (isEmpty) {
+ composer.selection.selectNode(list.querySelector("li"), true);
}
- },
-
- value: function() {
- return undef;
}
- };
-})(wysihtml5);(function(wysihtml5) {
- var undef;
+ },
- wysihtml5.commands.italic = {
- exec: function(composer, command) {
- return wysihtml5.commands.formatInline.exec(composer, command, "i");
- },
-
- state: function(composer, command, color) {
- // element.ownerDocument.queryCommandState("italic") results:
- // firefox: only <i>
- // chrome: <i>, <em>, <blockquote>, ...
- // ie: <i>, <em>
- // opera: only <i>
- return wysihtml5.commands.formatInline.state(composer, command, "i");
- },
+ state: function(composer) {
+ var selectedNode = composer.selection.getSelectedNode();
+ return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" });
+ }
+};wysihtml5.commands.italic = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatInline.exec(composer, command, "i");
+ },
- value: function() {
- return undef;
- }
- };
-})(wysihtml5);(function(wysihtml5) {
- var undef,
- CLASS_NAME = "wysiwyg-text-align-center",
- REG_EXP = /wysiwyg-text-align-[a-z]+/g;
+ state: function(composer, command) {
+ // element.ownerDocument.queryCommandState("italic") results:
+ // firefox: only <i>
+ // chrome: <i>, <em>, <blockquote>, ...
+ // ie: <i>, <em>
+ // opera: only <i>
+ return wysihtml5.commands.formatInline.state(composer, command, "i");
+ }
+};(function(wysihtml5) {
+ var CLASS_NAME = "wysiwyg-text-align-center",
+ REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
wysihtml5.commands.justifyCenter = {
exec: function(composer, command) {
@@ -7617,16 +7496,11 @@ wysihtml5.Commands = Base.extend(
state: function(composer, command) {
return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);(function(wysihtml5) {
- var undef,
- CLASS_NAME = "wysiwyg-text-align-left",
- REG_EXP = /wysiwyg-text-align-[a-z]+/g;
+ var CLASS_NAME = "wysiwyg-text-align-left",
+ REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
wysihtml5.commands.justifyLeft = {
exec: function(composer, command) {
@@ -7635,16 +7509,11 @@ wysihtml5.Commands = Base.extend(
state: function(composer, command) {
return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);(function(wysihtml5) {
- var undef,
- CLASS_NAME = "wysiwyg-text-align-right",
- REG_EXP = /wysiwyg-text-align-[a-z]+/g;
+ var CLASS_NAME = "wysiwyg-text-align-right",
+ REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
wysihtml5.commands.justifyRight = {
exec: function(composer, command) {
@@ -7653,31 +7522,47 @@ wysihtml5.Commands = Base.extend(
state: function(composer, command) {
return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);(function(wysihtml5) {
- var undef,
- REG_EXP = /wysiwyg-text-decoration-underline/g,
- CLASS_NAME = "wysiwyg-text-decoration-underline";
+ var CLASS_NAME = "wysiwyg-text-align-justify",
+ REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
- wysihtml5.commands.underline = {
+ wysihtml5.commands.justifyFull = {
exec: function(composer, command) {
- return wysihtml5.commands.formatInline.exec(composer, command, "span", CLASS_NAME, REG_EXP);
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
},
state: function(composer, command) {
- return wysihtml5.commands.formatInline.state(composer, command, "span", CLASS_NAME, REG_EXP);
- },
-
- value: function() {
- return undef;
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
}
};
-})(wysihtml5);/**
+})(wysihtml5);
+wysihtml5.commands.redo = {
+ exec: function(composer) {
+ return composer.undoManager.redo();
+ },
+
+ state: function(composer) {
+ return false;
+ }
+};wysihtml5.commands.underline = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatInline.exec(composer, command, "u");
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatInline.state(composer, command, "u");
+ }
+};wysihtml5.commands.undo = {
+ exec: function(composer) {
+ return composer.undoManager.undo();
+ },
+
+ state: function(composer) {
+ return false;
+ }
+};/**
* Undo Manager for wysihtml5
* slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
*/
@@ -7686,7 +7571,9 @@ wysihtml5.Commands = Base.extend(
Y_KEY = 89,
BACKSPACE_KEY = 8,
DELETE_KEY = 46,
- MAX_HISTORY_ENTRIES = 40,
+ MAX_HISTORY_ENTRIES = 25,
+ DATA_ATTR_NODE = "data-wysihtml5-selection-node",
+ DATA_ATTR_OFFSET = "data-wysihtml5-selection-offset",
UNDO_HTML = '<span id="_wysihtml5-undo" class="_wysihtml5-temp">' + wysihtml5.INVISIBLE_SPACE + '</span>',
REDO_HTML = '<span id="_wysihtml5-redo" class="_wysihtml5-temp">' + wysihtml5.INVISIBLE_SPACE + '</span>',
dom = wysihtml5.dom;
@@ -7704,13 +7591,14 @@ wysihtml5.Commands = Base.extend(
this.editor = editor;
this.composer = editor.composer;
this.element = this.composer.element;
- this.history = [this.composer.getValue()];
- this.position = 1;
- // Undo manager currently only supported in browsers who have the insertHTML command (not IE)
- if (this.composer.commands.support("insertHTML")) {
- this._observe();
- }
+ this.position = 0;
+ this.historyStr = [];
+ this.historyDom = [];
+
+ this.transact();
+
+ this._observe();
},
_observe: function() {
@@ -7797,56 +7685,134 @@ wysihtml5.Commands = Base.extend(
}
this.editor
- .observe("newword:composer", function() {
+ .on("newword:composer", function() {
that.transact();
})
- .observe("beforecommand:composer", function() {
+ .on("beforecommand:composer", function() {
that.transact();
});
},
transact: function() {
- var previousHtml = this.history[this.position - 1],
- currentHtml = this.composer.getValue();
+ var previousHtml = this.historyStr[this.position - 1],
+ currentHtml = this.composer.getValue();
- if (currentHtml == previousHtml) {
+ if (currentHtml === previousHtml) {
return;
}
- var length = this.history.length = this.position;
+ var length = this.historyStr.length = this.historyDom.length = this.position;
if (length > MAX_HISTORY_ENTRIES) {
- this.history.shift();
+ this.historyStr.shift();
+ this.historyDom.shift();
this.position--;
}
this.position++;
- this.history.push(currentHtml);
+
+ var range = this.composer.selection.getRange(),
+ node = range.startContainer || this.element,
+ offset = range.startOffset || 0,
+ element,
+ position;
+
+ if (node.nodeType === wysihtml5.ELEMENT_NODE) {
+ element = node;
+ } else {
+ element = node.parentNode;
+ position = this.getChildNodeIndex(element, node);
+ }
+
+ element.setAttribute(DATA_ATTR_OFFSET, offset);
+ if (typeof(position) !== "undefined") {
+ element.setAttribute(DATA_ATTR_NODE, position);
+ }
+
+ var clone = this.element.cloneNode(!!currentHtml);
+ this.historyDom.push(clone);
+ this.historyStr.push(currentHtml);
+
+ element.removeAttribute(DATA_ATTR_OFFSET);
+ element.removeAttribute(DATA_ATTR_NODE);
},
undo: function() {
this.transact();
- if (this.position <= 1) {
+ if (!this.undoPossible()) {
return;
}
- this.set(this.history[--this.position - 1]);
+ this.set(this.historyDom[--this.position - 1]);
this.editor.fire("undo:composer");
},
redo: function() {
- if (this.position >= this.history.length) {
+ if (!this.redoPossible()) {
return;
}
- this.set(this.history[++this.position - 1]);
+ this.set(this.historyDom[++this.position - 1]);
this.editor.fire("redo:composer");
},
- set: function(html) {
- this.composer.setValue(html);
- this.editor.focus(true);
+ undoPossible: function() {
+ return this.position > 1;
+ },
+
+ redoPossible: function() {
+ return this.position < this.historyStr.length;
+ },
+
+ set: function(historyEntry) {
+ this.element.innerHTML = "";
+
+ var i = 0,
+ childNodes = historyEntry.childNodes,
+ length = historyEntry.childNodes.length;
+
+ for (; i<length; i++) {
+ this.element.appendChild(childNodes[i].cloneNode(true));
+ }
+
+ // Restore selection
+ var offset,
+ node,
+ position;
+
+ if (historyEntry.hasAttribute(DATA_ATTR_OFFSET)) {
+ offset = historyEntry.getAttribute(DATA_ATTR_OFFSET);
+ position = historyEntry.getAttribute(DATA_ATTR_NODE);
+ node = this.element;
+ } else {
+ node = this.element.querySelector("[" + DATA_ATTR_OFFSET + "]") || this.element;
+ offset = node.getAttribute(DATA_ATTR_OFFSET);
+ position = node.getAttribute(DATA_ATTR_NODE);
+ node.removeAttribute(DATA_ATTR_OFFSET);
+ node.removeAttribute(DATA_ATTR_NODE);
+ }
+
+ if (position !== null) {
+ node = this.getChildNodeByIndex(node, +position);
+ }
+
+ this.composer.selection.set(node, offset);
+ },
+
+ getChildNodeIndex: function(parent, child) {
+ var i = 0,
+ childNodes = parent.childNodes,
+ length = childNodes.length;
+ for (; i<length; i++) {
+ if (childNodes[i] === child) {
+ return i;
+ }
+ }
+ },
+
+ getChildNodeByIndex: function(parent, index) {
+ return parent.childNodes[index];
}
});
})(wysihtml5);
@@ -7865,8 +7831,8 @@ wysihtml5.views.View = Base.extend(
_observeViewChange: function() {
var that = this;
- this.parent.observe("beforeload", function() {
- that.parent.observe("change_view", function(view) {
+ this.parent.on("beforeload", function() {
+ that.parent.on("change_view", function(view) {
if (view === that.name) {
that.parent.currentView = that;
that.show();
@@ -7942,15 +7908,22 @@ wysihtml5.views.View = Base.extend(
if (parse) {
html = this.parent.parse(html);
}
- this.element.innerHTML = html;
+
+ try {
+ this.element.innerHTML = html;
+ } catch (e) {
+ this.element.innerText = html;
+ }
},
show: function() {
this.iframe.style.display = this._displayStyle || "";
-
- // Firefox needs this, otherwise contentEditable becomes uneditable
- this.disable();
- this.enable();
+
+ if (!this.textarea.element.disabled) {
+ // Firefox needs this, otherwise contentEditable becomes uneditable
+ this.disable();
+ this.enable();
+ }
},
hide: function() {
@@ -7962,13 +7935,13 @@ wysihtml5.views.View = Base.extend(
},
disable: function() {
+ this.parent.fire("disable:composer");
this.element.removeAttribute("contentEditable");
- this.base();
},
enable: function() {
+ this.parent.fire("enable:composer");
this.element.setAttribute("contentEditable", "true");
- this.base();
},
focus: function(setToEnd) {
@@ -7996,16 +7969,16 @@ wysihtml5.views.View = Base.extend(
},
hasPlaceholderSet: function() {
- return this.getTextContent() == this.textarea.element.getAttribute("placeholder");
+ return this.getTextContent() == this.textarea.element.getAttribute("placeholder") && this.placeholderSet;
},
isEmpty: function() {
- var innerHTML = this.element.innerHTML,
- elementsWithVisualValue = "blockquote, ul, ol, img, embed, object, table, iframe, svg, video, audio, button, input, select, textarea";
- return innerHTML === "" ||
- innerHTML === this.CARET_HACK ||
- this.hasPlaceholderSet() ||
- (this.getTextContent() === "" && !this.element.querySelector(elementsWithVisualValue));
+ var innerHTML = this.element.innerHTML.toLowerCase();
+ return innerHTML === "" ||
+ innerHTML === "<br>" ||
+ innerHTML === "<p></p>" ||
+ innerHTML === "<p><br></p>" ||
+ this.hasPlaceholderSet();
},
_initSandbox: function() {
@@ -8017,17 +7990,18 @@ wysihtml5.views.View = Base.extend(
stylesheets: this.config.stylesheets
});
this.iframe = this.sandbox.getIframe();
-
- // Create hidden field which tells the server after submit, that the user used an wysiwyg editor
- var hiddenField = document.createElement("input");
- hiddenField.type = "hidden";
- hiddenField.name = "_wysihtml5_mode";
- hiddenField.value = 1;
-
- // Store reference to current wysihtml5 instance on the textarea element
+
var textareaElement = this.textarea.element;
dom.insert(this.iframe).after(textareaElement);
- dom.insert(hiddenField).after(textareaElement);
+
+ // Create hidden field which tells the server after submit, that the user used an wysiwyg editor
+ if (textareaElement.form) {
+ var hiddenField = document.createElement("input");
+ hiddenField.type = "hidden";
+ hiddenField.name = "_wysihtml5_mode";
+ hiddenField.value = 1;
+ dom.insert(hiddenField).after(textareaElement);
+ }
},
_create: function() {
@@ -8037,33 +8011,38 @@ wysihtml5.views.View = Base.extend(
this.element = this.doc.body;
this.textarea = this.parent.textarea;
this.element.innerHTML = this.textarea.getValue(true);
- this.enable();
// Make sure our selection handler is ready
this.selection = new wysihtml5.Selection(this.parent);
// Make sure commands dispatcher is ready
this.commands = new wysihtml5.Commands(this.parent);
-
+
dom.copyAttributes([
"className", "spellcheck", "title", "lang", "dir", "accessKey"
]).from(this.textarea.element).to(this.element);
dom.addClass(this.element, this.config.composerClassName);
-
- // Make the editor look like the original textarea, by syncing styles
+ //
+ // // Make the editor look like the original textarea, by syncing styles
if (this.config.style) {
this.style();
}
-
+
this.observe();
-
+
var name = this.config.name;
if (name) {
dom.addClass(this.element, name);
dom.addClass(this.iframe, name);
}
-
+
+ this.enable();
+
+ if (this.textarea.element.disabled) {
+ this.disable();
+ }
+
// Simulate html5 placeholder attribute on contentEditable element
var placeholderText = typeof(this.config.placeholder) === "string"
? this.config.placeholder
@@ -8074,35 +8053,31 @@ wysihtml5.views.View = Base.extend(
// Make sure that the browser avoids using inline styles whenever possible
this.commands.exec("styleWithCSS", false);
-
+
this._initAutoLinking();
this._initObjectResizing();
this._initUndoManager();
-
+ this._initLineBreaking();
+
// Simulate html5 autofocus on contentEditable element
- if (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) {
- setTimeout(function() { that.focus(); }, 100);
+ // This doesn't work on IOS (5.1.1)
+ if ((this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) {
+ setTimeout(function() { that.focus(true); }, 100);
}
-
- wysihtml5.quirks.insertLineBreakOnReturn(this);
-
+
// IE sometimes leaves a single paragraph, which can't be removed by the user
if (!browser.clearsContentEditableCorrectly()) {
wysihtml5.quirks.ensureProperClearing(this);
}
-
- if (!browser.clearsListsInContentEditableCorrectly()) {
- wysihtml5.quirks.ensureProperClearingOfLists(this);
- }
-
+
// Set up a sync that makes sure that textarea and editor have the same content
if (this.initSync && this.config.sync) {
this.initSync();
}
-
+
// Okay hide the textarea, we are ready to go
this.textarea.hide();
-
+
// Fire global (before-)load event
this.parent.fire("beforeload").fire("load");
},
@@ -8122,10 +8097,16 @@ wysihtml5.views.View = Base.extend(
// 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.observe("newword:composer", function() {
- that.selection.executeAndRestore(function(startContainer, endContainer) {
- dom.autoLink(endContainer.parentNode);
- });
+ this.parent.on("newword:composer", function() {
+ if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
+ that.selection.executeAndRestore(function(startContainer, endContainer) {
+ dom.autoLink(endContainer.parentNode);
+ });
+ }
+ });
+
+ dom.observe(this.element, "blur", function() {
+ dom.autoLink(that.element);
});
}
@@ -8176,42 +8157,120 @@ wysihtml5.views.View = Base.extend(
},
_initObjectResizing: function() {
- var properties = ["width", "height"],
- propertiesLength = properties.length,
- element = this.element;
+ this.commands.exec("enableObjectResizing", true);
- this.commands.exec("enableObjectResizing", this.config.allowObjectResizing);
-
- if (this.config.allowObjectResizing) {
- // IE sets inline styles after resizing objects
- // The following lines make sure that the width/height css properties
- // are copied over to the width/height attributes
- if (browser.supportsEvent("resizeend")) {
- dom.observe(element, "resizeend", function(event) {
- var target = event.target || event.srcElement,
- style = target.style,
- i = 0,
- property;
- for(; i<propertiesLength; i++) {
- property = properties[i];
- if (style[property]) {
- target.setAttribute(property, parseInt(style[property], 10));
- style[property] = "";
- }
+ // IE sets inline styles after resizing objects
+ // The following lines make sure that the width/height css properties
+ // are copied over to the width/height attributes
+ if (browser.supportsEvent("resizeend")) {
+ var properties = ["width", "height"],
+ propertiesLength = properties.length,
+ element = this.element;
+
+ dom.observe(element, "resizeend", function(event) {
+ var target = event.target || event.srcElement,
+ style = target.style,
+ i = 0,
+ property;
+
+ if (target.nodeName !== "IMG") {
+ return;
+ }
+
+ for (; i<propertiesLength; i++) {
+ property = properties[i];
+ if (style[property]) {
+ target.setAttribute(property, parseInt(style[property], 10));
+ style[property] = "";
}
- // After resizing IE sometimes forgets to remove the old resize handles
- wysihtml5.quirks.redraw(element);
- });
- }
- } else {
- if (browser.supportsEvent("resizestart")) {
- dom.observe(element, "resizestart", function(event) { event.preventDefault(); });
- }
+ }
+
+ // After resizing IE sometimes forgets to remove the old resize handles
+ wysihtml5.quirks.redraw(element);
+ });
}
},