From ace11f54356865cab5e81b8e6a7cd479d33c417e Mon Sep 17 00:00:00 2001
From: Tima Maslyuchenko
+ * foo
+ * bar
when pressing enter in a contentEditable element
@@ -3499,29 +3512,7 @@ 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
*
@@ -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 (
,
)
// 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 || isWebKit,
- "insertOrderedList": isIE || isOpera || isWebKit
+ "insertUnorderedList": isIE || isWebKit,
+ "insertOrderedList": isIE || isWebKit
};
// Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
@@ -3630,14 +3621,6 @@ wysihtml5.browser = (function() {
return isGecko || isIE || isOpera;
},
- /**
- * When the caret is in an empty list (
) 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
*/
@@ -3678,14 +3661,6 @@ wysihtml5.browser = (function() {
return "getSelection" in window && "modify" in window.getSelection();
},
- /**
- * 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
in order to position the caret correctly
*/
@@ -3733,6 +3708,35 @@ 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
because then
+ * insertNode() will insert the node right before the
+ */
+ hasInsertNodeIssue: function() {
+ return isOpera;
+ },
+
+ /**
+ * IE 8+9 don't fire the focus event of the when the iframe gets focused (even though the caret gets set into the )
+ */
+ hasIframeFocusIssue: function() {
+ return isIE;
+ },
+
+ /**
+ * Chrome + Safari create invalid nested markup after paste
+ *
+ *
" || - innerHTML == "
") { - 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 (
" || + innerHTML == "
") { + 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: @@ -5797,83 +5840,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
on return - * - Chrome & Safari insert new
elements after leaving a list - // check after keydown of backspace and return whether a
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
@@ -5985,7 +5951,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"),
@@ -5994,7 +5960,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) {}
}
@@ -6048,8 +6014,12 @@ wysihtml5.quirks.cleanPastedHTML = (function() {
oldScrollTop = restoreScrollPosition && body.scrollTop,
oldScrollLeft = restoreScrollPosition && body.scrollLeft,
className = "_wysihtml5-temp-placeholder",
- placeholderHTML = '' + wysihtml5.INVISIBLE_SPACE + '',
+ placeholderHtml = '' + wysihtml5.INVISIBLE_SPACE + '',
range = this.getRange(this.doc),
+ caretPlaceholder,
+ newCaretPlaceholder,
+ nextSibling,
+ node,
newRange;
// Nothing selected, execute and say goodbye
@@ -6058,21 +6028,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 and copying over the class name
+ dom.renameElement(blockElement, nodeName === "P" ? "DIV" : defaultNodeName);
}
});
return;
@@ -7209,7 +7153,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);
@@ -7223,11 +7167,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;
}
@@ -7242,10 +7186,6 @@ wysihtml5.Commands = Base.extend(
className: className,
classRegExp: classRegExp
});
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);/**
@@ -7282,8 +7222,7 @@ wysihtml5.Commands = Base.extend(
* ab|c de|fgh
*/
(function(wysihtml5) {
- var undef,
- // Treat as and vice versa
+ var // Treat as and vice versa
ALIAS_MAPPING = {
"strong": "b",
"em": "i",
@@ -7337,33 +7276,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 = {
@@ -7383,7 +7311,6 @@ wysihtml5.Commands = Base.extend(
var doc = composer.doc,
image = this.state(composer),
textNode,
- i,
parent;
if (image) {
@@ -7405,9 +7332,9 @@ wysihtml5.Commands = Base.extend(
}
image = doc.createElement(NODE_NAME);
-
- for (i in value) {
- image[i] = value[i];
+
+ for (var i in value) {
+ image.setAttribute(i === "className" ? "class" : i, value[i]);
}
composer.selection.insertNode(image);
@@ -7459,16 +7386,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 = "
+ useLineBreaks: true,
// Array (or single string) of stylesheet urls to be loaded in the editor's iframe
stylesheets: [],
// Placeholder text to use, defaults to the placeholder attribute on the textarea element
placeholderText: undef,
- // Whether the composer should allow the user to manually resize images, tables etc.
- allowObjectResizing: true,
// Whether the rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5)
- supportTouchDevices: true
+ supportTouchDevices: true,
+ // Whether senseless elements (empty or without attributes) should be removed/replaced with their content
+ cleanUp: true
};
wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend(
@@ -9426,7 +9533,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
this._initParser();
}
- this.observe("beforeload", function() {
+ this.on("beforeload", function() {
this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer);
if (this.config.toolbar) {
this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar);
@@ -9452,9 +9559,12 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
},
setValue: function(html, parse) {
+ this.fire("unset_placeholder");
+
if (!html) {
return this.clear();
}
+
this.currentView.setValue(html, parse);
return this;
},
@@ -9489,7 +9599,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
},
parse: function(htmlOrElement) {
- var returnValue = this.config.parser(htmlOrElement, this.config.parserRules, this.composer.sandbox.getDocument(), true);
+ var returnValue = this.config.parser(htmlOrElement, this.config.parserRules, this.composer.sandbox.getDocument(), this.config.cleanUp);
if (typeof(htmlOrElement) === "object") {
wysihtml5.quirks.redraw(htmlOrElement);
}
@@ -9501,7 +9611,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
* - Observes for paste and drop
*/
_initParser: function() {
- this.observe("paste:composer", function() {
+ this.on("paste:composer", function() {
var keepScrollPosition = true,
that = this;
that.composer.selection.executeAndRestore(function() {
@@ -9509,13 +9619,6 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
that.parse(that.composer.element);
}, keepScrollPosition);
});
-
- this.observe("paste:textarea", function() {
- var value = this.textarea.getValue(),
- newValue;
- newValue = this.parse(value);
- this.textarea.setValue(newValue);
- });
}
});
})(wysihtml5);
+ 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
@@ -6087,7 +6070,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) {}
},
/**
@@ -6132,7 +6115,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
*
@@ -6195,6 +6184,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");
@@ -6208,7 +6198,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;
}
}
@@ -6238,7 +6228,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,
@@ -6826,53 +6815,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
- // chrome: , , ,
, ...
- // ie: ,
- // opera: ,
- 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
+ // chrome: , ,
,
, ...
+ // ie: ,
+ // opera: ,
+ return wysihtml5.commands.formatInline.state(composer, command, "b");
+ }
+};
(function(wysihtml5) {
var undef,
@@ -6935,7 +6893,7 @@ wysihtml5.Commands = Base.extend(
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;
}
}
@@ -6972,10 +6930,6 @@ wysihtml5.Commands = Base.extend(
state: function(composer, command) {
return wysihtml5.commands.formatInline.state(composer, command, "A");
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);/**
@@ -6984,8 +6938,7 @@ wysihtml5.Commands = Base.extend(
* Instead we set a css class
*/
(function(wysihtml5) {
- var undef,
- REG_EXP = /wysiwyg-font-size-[a-z\-]+/g;
+ var REG_EXP = /wysiwyg-font-size-[0-9a-z\-]+/g;
wysihtml5.commands.fontSize = {
exec: function(composer, command, size) {
@@ -6994,10 +6947,6 @@ wysihtml5.Commands = Base.extend(
state: function(composer, command, size) {
return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
- },
-
- value: function() {
- return undef;
}
};
})(wysihtml5);
@@ -7007,8 +6956,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) {
@@ -7017,20 +6965,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)
@@ -7167,7 +7109,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) {
@@ -7176,26 +7118,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
" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
+ var LINE_BREAK = "
" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
wysihtml5.commands.insertLineBreak = {
exec: function(composer, command) {
@@ -7484,151 +7405,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 = 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 (composer.commands.support(command)) {
- doc.execCommand(command, false, null);
- return;
- }
-
- if (list) {
- // Unwrap list
- //
- // becomes:
- // foo
bar
- composer.selection.executeAndRestoreSimple(function() {
- wysihtml5.dom.resolveList(list);
- });
- } else if (otherList) {
- // Turn an unordered list into an ordered list
- //
- // becomes:
- //
- composer.selection.executeAndRestoreSimple(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;
- composer.selection.executeAndRestoreSimple(function() {
- list = wysihtml5.dom.convertToList(tempElement, "ol");
- });
- if (isEmpty) {
- composer.selection.selectNode(list.querySelector("li"));
- }
- }
- },
+})(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;
- state: function(composer) {
- var selectedNode = composer.selection.getSelectedNode();
- return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" });
- },
-
- value: function() {
- return undef;
+ if (!list && !otherList && composer.commands.support(command)) {
+ doc.execCommand(command, false, null);
+ return;
}
- };
-})(wysihtml5);(function(wysihtml5) {
- var undef;
-
- 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 (composer.commands.support(command)) {
- doc.execCommand(command, false, null);
- return;
- }
-
- if (list) {
- // Unwrap list
- //
- // becomes:
- // foo
bar
- composer.selection.executeAndRestoreSimple(function() {
- wysihtml5.dom.resolveList(list);
- });
- } else if (otherList) {
- // Turn an ordered list into an unordered list
- //
- // becomes:
- //
- composer.selection.executeAndRestoreSimple(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;
- composer.selection.executeAndRestoreSimple(function() {
- list = wysihtml5.dom.convertToList(tempElement, "ul");
- });
- if (isEmpty) {
- composer.selection.selectNode(list.querySelector("li"));
- }
+
+ if (list) {
+ // Unwrap list
+ //
+ // becomes:
+ // foo
bar
+ composer.selection.executeAndRestore(function() {
+ wysihtml5.dom.resolveList(list, composer.config.useLineBreaks);
+ });
+ } else if (otherList) {
+ // Turn an unordered list into an ordered list
+ //
+ // becomes:
+ //
+ 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 === "
";
+ composer.selection.executeAndRestore(function() {
+ list = wysihtml5.dom.convertToList(tempElement, "ol");
+ });
+ if (isEmpty) {
+ composer.selection.selectNode(list.querySelector("li"), true);
}
- },
+ }
+ },
+
+ 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;
- state: function(composer) {
- var selectedNode = composer.selection.getSelectedNode();
- return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" });
- },
-
- value: function() {
- return undef;
+ if (!list && !otherList && composer.commands.support(command)) {
+ doc.execCommand(command, false, null);
+ return;
}
- };
-})(wysihtml5);(function(wysihtml5) {
- var undef;
+
+ if (list) {
+ // Unwrap list
+ //
+ // becomes:
+ // foo
bar
+ composer.selection.executeAndRestore(function() {
+ wysihtml5.dom.resolveList(list, composer.config.useLineBreaks);
+ });
+ } else if (otherList) {
+ // Turn an ordered list into an unordered list
+ //
+ // becomes:
+ //
+ 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 === "
";
+ composer.selection.executeAndRestore(function() {
+ list = wysihtml5.dom.convertToList(tempElement, "ul");
+ });
+ if (isEmpty) {
+ composer.selection.selectNode(list.querySelector("li"), true);
+ }
+ }
+ },
- 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
- // chrome: , , , ...
- // ie: ,
- // opera: only
- 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
+ // chrome: , ,
, ...
+ // ie: ,
+ // opera: only
+ 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) {
@@ -7637,16 +7529,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) {
@@ -7655,16 +7542,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) {
@@ -7673,28 +7555,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;
- wysihtml5.commands.underline = {
+ var CLASS_NAME = "wysiwyg-text-align-justify",
+ REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
+
+ wysihtml5.commands.justifyFull = {
exec: function(composer, command) {
- return wysihtml5.commands.formatInline.exec(composer, command, "u");
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
},
state: function(composer, command) {
- return wysihtml5.commands.formatInline.state(composer, command, "u");
- },
-
- 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
*/
@@ -7703,7 +7604,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 = '' + wysihtml5.INVISIBLE_SPACE + '',
REDO_HTML = '' + wysihtml5.INVISIBLE_SPACE + '',
dom = wysihtml5.dom;
@@ -7721,13 +7624,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() {
@@ -7814,56 +7718,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