diff --git a/app/assets/javascripts/active_admin/editor/config.js.erb b/app/assets/javascripts/active_admin/editor/config.js.erb
index 49a0014..21056e5 100644
--- a/app/assets/javascripts/active_admin/editor/config.js.erb
+++ b/app/assets/javascripts/active_admin/editor/config.js.erb
@@ -10,4 +10,5 @@
config.spinner = '<%= asset_path 'active_admin/editor/loader.gif' %>'
config.uploads_enabled = <%= ActiveAdmin::Editor.configuration.s3_configured? %>
config.parserRules = <%= ActiveAdmin::Editor.configuration.parser_rules.to_json %>
+ config.cleanUp = <%= ActiveAdmin::Editor.configuration.clean_up.to_json %>
})(window)
diff --git a/app/assets/javascripts/active_admin/editor/editor.js b/app/assets/javascripts/active_admin/editor/editor.js
index 11365b6..2282de4 100644
--- a/app/assets/javascripts/active_admin/editor/editor.js
+++ b/app/assets/javascripts/active_admin/editor/editor.js
@@ -77,7 +77,8 @@
this._editor = new wysihtml5.Editor(this.$textarea.attr('id'), {
toolbar: this.$toolbar.attr('id'),
stylesheets: config.stylesheets,
- parserRules: config.parserRules
+ parserRules: config.parserRules,
+ cleanUp: config.cleanUp
})
}
diff --git a/lib/active_admin/editor/config.rb b/lib/active_admin/editor/config.rb
index 1478296..a0b8cd1 100644
--- a/lib/active_admin/editor/config.rb
+++ b/lib/active_admin/editor/config.rb
@@ -22,12 +22,11 @@ class Configuration
# The s3 bucket to store uploads.
attr_accessor :s3_bucket
- # Base directory to store the uploaded files in the bucket. Defaults to
- # 'uploads'.
- attr_accessor :storage_dir
-
# wysiwyg stylesheets that get included in the backend and the frontend.
- attr_accessor :stylesheets
+ attr_writer :stylesheets
+
+ # wysihtml5 cleanUp options
+ attr_writer :clean_up
def storage_dir
@storage_dir ||= 'uploads'
@@ -40,6 +39,10 @@ def storage_dir=(dir)
def stylesheets
@stylesheets ||= [ 'active_admin/editor/wysiwyg.css' ]
end
+
+ def clean_up
+ @clean_up.nil? ? true : !!@clean_up
+ end
def s3_configured?
aws_access_key_id.present? &&
diff --git a/lib/generators/active_admin/editor/templates/active_admin_editor.rb b/lib/generators/active_admin/editor/templates/active_admin_editor.rb
index 3815a08..8bf0d16 100644
--- a/lib/generators/active_admin/editor/templates/active_admin_editor.rb
+++ b/lib/generators/active_admin/editor/templates/active_admin_editor.rb
@@ -3,4 +3,5 @@
# config.aws_access_key_id = ''
# config.aws_access_secret = ''
# config.storage_dir = 'uploads'
+ # config.clean_up = false
end
diff --git a/spec/lib/config_spec.rb b/spec/lib/config_spec.rb
index 7d28f1e..be728da 100644
--- a/spec/lib/config_spec.rb
+++ b/spec/lib/config_spec.rb
@@ -15,6 +15,7 @@
its(:aws_access_secret) { should be_nil }
its(:s3_bucket) { should be_nil }
its(:storage_dir) { should eq 'uploads' }
+ its(:clean_up) { should be_true }
end
describe '.s3_configured?' do
diff --git a/vendor/assets/javascripts/wysihtml5.js b/vendor/assets/javascripts/wysihtml5.js
index 939dca9..1a66197 100755
--- a/vendor/assets/javascripts/wysihtml5.js
+++ b/vendor/assets/javascripts/wysihtml5.js
@@ -1,5 +1,5 @@
/**
- * @license wysihtml5 v0.3.0
+ * @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",
+ 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 && navigator.platform.substr(0, 3) === "Mac";
+ },
/**
* Whether the browser inserts a
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 (
+ * foo + *
bar
+ * + */ + createsNestedInvalidMarkupAfterPaste: function() { + return isWebKit; } }; })();wysihtml5.lang.array = function(arr) { @@ -3794,28 +3798,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" || - 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