aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5
diff options
context:
space:
mode:
Diffstat (limited to 'public/bower_components/admin-lte/plugins/bootstrap-wysihtml5')
-rw-r--r--public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js14975
-rw-r--r--public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js8
-rw-r--r--public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.css117
-rw-r--r--public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css3
4 files changed, 15103 insertions, 0 deletions
diff --git a/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js b/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js
new file mode 100644
index 0000000..acccf91
--- /dev/null
+++ b/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js
@@ -0,0 +1,14975 @@
+// TODO: in future try to replace most inline compability checks with polyfills for code readability
+
+// element.textContent polyfill.
+// Unsupporting browsers: IE8
+
+if (Object.defineProperty && Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(Element.prototype, "textContent") && !Object.getOwnPropertyDescriptor(Element.prototype, "textContent").get) {
+ (function() {
+ var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText");
+ Object.defineProperty(Element.prototype, "textContent",
+ {
+ get: function() {
+ return innerText.get.call(this);
+ },
+ set: function(s) {
+ return innerText.set.call(this, s);
+ }
+ }
+ );
+ })();
+}
+
+// isArray polyfill for ie8
+if(!Array.isArray) {
+ Array.isArray = function(arg) {
+ return Object.prototype.toString.call(arg) === '[object Array]';
+ };
+};/**
+ * @license wysihtml5x v0.4.15
+ * https://github.com/Edicy/wysihtml5
+ *
+ * Author: Christopher Blum (https://github.com/tiff)
+ * Secondary author of extended features: Oliver Pulges (https://github.com/pulges)
+ *
+ * Copyright (C) 2012 XING AG
+ * Licensed under the MIT license (MIT)
+ *
+ */
+var wysihtml5 = {
+ version: "0.4.15",
+
+ // namespaces
+ commands: {},
+ dom: {},
+ quirks: {},
+ toolbar: {},
+ lang: {},
+ selection: {},
+ views: {},
+
+ INVISIBLE_SPACE: "\uFEFF",
+
+ EMPTY_FUNCTION: function() {},
+
+ ELEMENT_NODE: 1,
+ TEXT_NODE: 3,
+
+ BACKSPACE_KEY: 8,
+ ENTER_KEY: 13,
+ ESCAPE_KEY: 27,
+ SPACE_KEY: 32,
+ DELETE_KEY: 46
+};
+;/**
+ * Rangy, a cross-browser JavaScript range and selection library
+ * http://code.google.com/p/rangy/
+ *
+ * Copyright 2014, Tim Down
+ * Licensed under the MIT license.
+ * Version: 1.3alpha.20140804
+ * Build date: 4 August 2014
+ */
+
+(function(factory, global) {
+ if (typeof define == "function" && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(factory);
+/*
+ TODO: look into this properly.
+
+ } else if (typeof exports == "object") {
+ // Node/CommonJS style for Browserify
+ module.exports = factory;
+*/
+ } else {
+ // No AMD or CommonJS support so we place Rangy in a global variable
+ global.rangy = factory();
+ }
+})(function() {
+
+ var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
+
+ // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
+ // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
+ var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
+ "commonAncestorContainer"];
+
+ // Minimal set of methods required for DOM Level 2 Range compliance
+ var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
+ "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
+ "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
+
+ var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
+
+ // Subset of TextRange's full set of methods that we're interested in
+ var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",
+ "setEndPoint", "getBoundingClientRect"];
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Trio of functions taken from Peter Michaux's article:
+ // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
+ function isHostMethod(o, p) {
+ var t = typeof o[p];
+ return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
+ }
+
+ function isHostObject(o, p) {
+ return !!(typeof o[p] == OBJECT && o[p]);
+ }
+
+ function isHostProperty(o, p) {
+ return typeof o[p] != UNDEFINED;
+ }
+
+ // Creates a convenience function to save verbose repeated calls to tests functions
+ function createMultiplePropertyTest(testFunc) {
+ return function(o, props) {
+ var i = props.length;
+ while (i--) {
+ if (!testFunc(o, props[i])) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
+
+ // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
+ var areHostMethods = createMultiplePropertyTest(isHostMethod);
+ var areHostObjects = createMultiplePropertyTest(isHostObject);
+ var areHostProperties = createMultiplePropertyTest(isHostProperty);
+
+ function isTextRange(range) {
+ return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
+ }
+
+ function getBody(doc) {
+ return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
+ }
+
+ var modules = {};
+
+ var api = {
+ version: "1.3alpha.20140804",
+ initialized: false,
+ supported: true,
+
+ util: {
+ isHostMethod: isHostMethod,
+ isHostObject: isHostObject,
+ isHostProperty: isHostProperty,
+ areHostMethods: areHostMethods,
+ areHostObjects: areHostObjects,
+ areHostProperties: areHostProperties,
+ isTextRange: isTextRange,
+ getBody: getBody
+ },
+
+ features: {},
+
+ modules: modules,
+ config: {
+ alertOnFail: true,
+ alertOnWarn: false,
+ preferTextRange: false,
+ autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize
+ }
+ };
+
+ function consoleLog(msg) {
+ if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
+ window.console.log(msg);
+ }
+ }
+
+ function alertOrLog(msg, shouldAlert) {
+ if (shouldAlert) {
+ window.alert(msg);
+ } else {
+ consoleLog(msg);
+ }
+ }
+
+ function fail(reason) {
+ api.initialized = true;
+ api.supported = false;
+ alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail);
+ }
+
+ api.fail = fail;
+
+ function warn(msg) {
+ alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
+ }
+
+ api.warn = warn;
+
+ // Add utility extend() method
+ if ({}.hasOwnProperty) {
+ api.util.extend = function(obj, props, deep) {
+ var o, p;
+ for (var i in props) {
+ if (props.hasOwnProperty(i)) {
+ o = obj[i];
+ p = props[i];
+ if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
+ api.util.extend(o, p, true);
+ }
+ obj[i] = p;
+ }
+ }
+ // Special case for toString, which does not show up in for...in loops in IE <= 8
+ if (props.hasOwnProperty("toString")) {
+ obj.toString = props.toString;
+ }
+ return obj;
+ };
+ } else {
+ fail("hasOwnProperty not supported");
+ }
+
+ // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
+ (function() {
+ var el = document.createElement("div");
+ el.appendChild(document.createElement("span"));
+ var slice = [].slice;
+ var toArray;
+ try {
+ if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
+ toArray = function(arrayLike) {
+ return slice.call(arrayLike, 0);
+ };
+ }
+ } catch (e) {}
+
+ if (!toArray) {
+ toArray = function(arrayLike) {
+ var arr = [];
+ for (var i = 0, len = arrayLike.length; i < len; ++i) {
+ arr[i] = arrayLike[i];
+ }
+ return arr;
+ };
+ }
+
+ api.util.toArray = toArray;
+ })();
+
+
+ // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
+ // normalization of event properties
+ var addListener;
+ if (isHostMethod(document, "addEventListener")) {
+ addListener = function(obj, eventType, listener) {
+ obj.addEventListener(eventType, listener, false);
+ };
+ } else if (isHostMethod(document, "attachEvent")) {
+ addListener = function(obj, eventType, listener) {
+ obj.attachEvent("on" + eventType, listener);
+ };
+ } else {
+ fail("Document does not have required addEventListener or attachEvent method");
+ }
+
+ api.util.addListener = addListener;
+
+ var initListeners = [];
+
+ function getErrorDesc(ex) {
+ return ex.message || ex.description || String(ex);
+ }
+
+ // Initialization
+ function init() {
+ if (api.initialized) {
+ return;
+ }
+ var testRange;
+ var implementsDomRange = false, implementsTextRange = false;
+
+ // First, perform basic feature tests
+
+ if (isHostMethod(document, "createRange")) {
+ testRange = document.createRange();
+ if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
+ implementsDomRange = true;
+ }
+ }
+
+ var body = getBody(document);
+ if (!body || body.nodeName.toLowerCase() != "body") {
+ fail("No body element found");
+ return;
+ }
+
+ if (body && isHostMethod(body, "createTextRange")) {
+ testRange = body.createTextRange();
+ if (isTextRange(testRange)) {
+ implementsTextRange = true;
+ }
+ }
+
+ if (!implementsDomRange && !implementsTextRange) {
+ fail("Neither Range nor TextRange are available");
+ return;
+ }
+
+ api.initialized = true;
+ api.features = {
+ implementsDomRange: implementsDomRange,
+ implementsTextRange: implementsTextRange
+ };
+
+ // Initialize modules
+ var module, errorMessage;
+ for (var moduleName in modules) {
+ if ( (module = modules[moduleName]) instanceof Module ) {
+ module.init(module, api);
+ }
+ }
+
+ // Call init listeners
+ for (var i = 0, len = initListeners.length; i < len; ++i) {
+ try {
+ initListeners[i](api);
+ } catch (ex) {
+ errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
+ consoleLog(errorMessage);
+ }
+ }
+ }
+
+ // Allow external scripts to initialize this library in case it's loaded after the document has loaded
+ api.init = init;
+
+ // Execute listener immediately if already initialized
+ api.addInitListener = function(listener) {
+ if (api.initialized) {
+ listener(api);
+ } else {
+ initListeners.push(listener);
+ }
+ };
+
+ var shimListeners = [];
+
+ api.addShimListener = function(listener) {
+ shimListeners.push(listener);
+ };
+
+ function shim(win) {
+ win = win || window;
+ init();
+
+ // Notify listeners
+ for (var i = 0, len = shimListeners.length; i < len; ++i) {
+ shimListeners[i](win);
+ }
+ }
+
+ api.shim = api.createMissingNativeApi = shim;
+
+ function Module(name, dependencies, initializer) {
+ this.name = name;
+ this.dependencies = dependencies;
+ this.initialized = false;
+ this.supported = false;
+ this.initializer = initializer;
+ }
+
+ Module.prototype = {
+ init: function() {
+ var requiredModuleNames = this.dependencies || [];
+ for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {
+ moduleName = requiredModuleNames[i];
+
+ requiredModule = modules[moduleName];
+ if (!requiredModule || !(requiredModule instanceof Module)) {
+ throw new Error("required module '" + moduleName + "' not found");
+ }
+
+ requiredModule.init();
+
+ if (!requiredModule.supported) {
+ throw new Error("required module '" + moduleName + "' not supported");
+ }
+ }
+
+ // Now run initializer
+ this.initializer(this);
+ },
+
+ fail: function(reason) {
+ this.initialized = true;
+ this.supported = false;
+ throw new Error("Module '" + this.name + "' failed to load: " + reason);
+ },
+
+ warn: function(msg) {
+ api.warn("Module " + this.name + ": " + msg);
+ },
+
+ deprecationNotice: function(deprecated, replacement) {
+ api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use " +
+ replacement + " instead");
+ },
+
+ createError: function(msg) {
+ return new Error("Error in Rangy " + this.name + " module: " + msg);
+ }
+ };
+
+ function createModule(isCore, name, dependencies, initFunc) {
+ var newModule = new Module(name, dependencies, function(module) {
+ if (!module.initialized) {
+ module.initialized = true;
+ try {
+ initFunc(api, module);
+ module.supported = true;
+ } catch (ex) {
+ var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
+ consoleLog(errorMessage);
+ }
+ }
+ });
+ modules[name] = newModule;
+ }
+
+ api.createModule = function(name) {
+ // Allow 2 or 3 arguments (second argument is an optional array of dependencies)
+ var initFunc, dependencies;
+ if (arguments.length == 2) {
+ initFunc = arguments[1];
+ dependencies = [];
+ } else {
+ initFunc = arguments[2];
+ dependencies = arguments[1];
+ }
+
+ var module = createModule(false, name, dependencies, initFunc);
+
+ // Initialize the module immediately if the core is already initialized
+ if (api.initialized) {
+ module.init();
+ }
+ };
+
+ api.createCoreModule = function(name, dependencies, initFunc) {
+ createModule(true, name, dependencies, initFunc);
+ };
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately
+
+ function RangePrototype() {}
+ api.RangePrototype = RangePrototype;
+ api.rangePrototype = new RangePrototype();
+
+ function SelectionPrototype() {}
+ api.selectionPrototype = new SelectionPrototype();
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Wait for document to load before running tests
+
+ var docReady = false;
+
+ var loadHandler = function(e) {
+ if (!docReady) {
+ docReady = true;
+ if (!api.initialized && api.config.autoInitialize) {
+ init();
+ }
+ }
+ };
+
+ // Test whether we have window and document objects that we will need
+ if (typeof window == UNDEFINED) {
+ fail("No window found");
+ return;
+ }
+ if (typeof document == UNDEFINED) {
+ fail("No document found");
+ return;
+ }
+
+ if (isHostMethod(document, "addEventListener")) {
+ document.addEventListener("DOMContentLoaded", loadHandler, false);
+ }
+
+ // Add a fallback in case the DOMContentLoaded event isn't supported
+ addListener(window, "load", loadHandler);
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // DOM utility methods used by Rangy
+ api.createCoreModule("DomUtil", [], function(api, module) {
+ var UNDEF = "undefined";
+ var util = api.util;
+
+ // Perform feature tests
+ if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
+ module.fail("document missing a Node creation method");
+ }
+
+ if (!util.isHostMethod(document, "getElementsByTagName")) {
+ module.fail("document missing getElementsByTagName method");
+ }
+
+ var el = document.createElement("div");
+ if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
+ !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
+ module.fail("Incomplete Element implementation");
+ }
+
+ // innerHTML is required for Range's createContextualFragment method
+ if (!util.isHostProperty(el, "innerHTML")) {
+ module.fail("Element is missing innerHTML property");
+ }
+
+ var textNode = document.createTextNode("test");
+ if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
+ !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
+ !util.areHostProperties(textNode, ["data"]))) {
+ module.fail("Incomplete Text Node implementation");
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
+ // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
+ // contains just the document as a single element and the value searched for is the document.
+ var arrayContains = /*Array.prototype.indexOf ?
+ function(arr, val) {
+ return arr.indexOf(val) > -1;
+ }:*/
+
+ function(arr, val) {
+ var i = arr.length;
+ while (i--) {
+ if (arr[i] === val) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
+ function isHtmlNamespace(node) {
+ var ns;
+ return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
+ }
+
+ function parentElement(node) {
+ var parent = node.parentNode;
+ return (parent.nodeType == 1) ? parent : null;
+ }
+
+ function getNodeIndex(node) {
+ var i = 0;
+ while( (node = node.previousSibling) ) {
+ ++i;
+ }
+ return i;
+ }
+
+ function getNodeLength(node) {
+ switch (node.nodeType) {
+ case 7:
+ case 10:
+ return 0;
+ case 3:
+ case 8:
+ return node.length;
+ default:
+ return node.childNodes.length;
+ }
+ }
+
+ function getCommonAncestor(node1, node2) {
+ var ancestors = [], n;
+ for (n = node1; n; n = n.parentNode) {
+ ancestors.push(n);
+ }
+
+ for (n = node2; n; n = n.parentNode) {
+ if (arrayContains(ancestors, n)) {
+ return n;
+ }
+ }
+
+ return null;
+ }
+
+ function isAncestorOf(ancestor, descendant, selfIsAncestor) {
+ var n = selfIsAncestor ? descendant : descendant.parentNode;
+ while (n) {
+ if (n === ancestor) {
+ return true;
+ } else {
+ n = n.parentNode;
+ }
+ }
+ return false;
+ }
+
+ function isOrIsAncestorOf(ancestor, descendant) {
+ return isAncestorOf(ancestor, descendant, true);
+ }
+
+ function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
+ var p, n = selfIsAncestor ? node : node.parentNode;
+ while (n) {
+ p = n.parentNode;
+ if (p === ancestor) {
+ return n;
+ }
+ n = p;
+ }
+ return null;
+ }
+
+ function isCharacterDataNode(node) {
+ var t = node.nodeType;
+ return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
+ }
+
+ function isTextOrCommentNode(node) {
+ if (!node) {
+ return false;
+ }
+ var t = node.nodeType;
+ return t == 3 || t == 8 ; // Text or Comment
+ }
+
+ function insertAfter(node, precedingNode) {
+ var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
+ if (nextNode) {
+ parent.insertBefore(node, nextNode);
+ } else {
+ parent.appendChild(node);
+ }
+ return node;
+ }
+
+ // Note that we cannot use splitText() because it is bugridden in IE 9.
+ function splitDataNode(node, index, positionsToPreserve) {
+ var newNode = node.cloneNode(false);
+ newNode.deleteData(0, index);
+ node.deleteData(index, node.length - index);
+ insertAfter(newNode, node);
+
+ // Preserve positions
+ if (positionsToPreserve) {
+ for (var i = 0, position; position = positionsToPreserve[i++]; ) {
+ // Handle case where position was inside the portion of node after the split point
+ if (position.node == node && position.offset > index) {
+ position.node = newNode;
+ position.offset -= index;
+ }
+ // Handle the case where the position is a node offset within node's parent
+ else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
+ ++position.offset;
+ }
+ }
+ }
+ return newNode;
+ }
+
+ function getDocument(node) {
+ if (node.nodeType == 9) {
+ return node;
+ } else if (typeof node.ownerDocument != UNDEF) {
+ return node.ownerDocument;
+ } else if (typeof node.document != UNDEF) {
+ return node.document;
+ } else if (node.parentNode) {
+ return getDocument(node.parentNode);
+ } else {
+ throw module.createError("getDocument: no document found for node");
+ }
+ }
+
+ function getWindow(node) {
+ var doc = getDocument(node);
+ if (typeof doc.defaultView != UNDEF) {
+ return doc.defaultView;
+ } else if (typeof doc.parentWindow != UNDEF) {
+ return doc.parentWindow;
+ } else {
+ throw module.createError("Cannot get a window object for node");
+ }
+ }
+
+ function getIframeDocument(iframeEl) {
+ if (typeof iframeEl.contentDocument != UNDEF) {
+ return iframeEl.contentDocument;
+ } else if (typeof iframeEl.contentWindow != UNDEF) {
+ return iframeEl.contentWindow.document;
+ } else {
+ throw module.createError("getIframeDocument: No Document object found for iframe element");
+ }
+ }
+
+ function getIframeWindow(iframeEl) {
+ if (typeof iframeEl.contentWindow != UNDEF) {
+ return iframeEl.contentWindow;
+ } else if (typeof iframeEl.contentDocument != UNDEF) {
+ return iframeEl.contentDocument.defaultView;
+ } else {
+ throw module.createError("getIframeWindow: No Window object found for iframe element");
+ }
+ }
+
+ // This looks bad. Is it worth it?
+ function isWindow(obj) {
+ return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
+ }
+
+ function getContentDocument(obj, module, methodName) {
+ var doc;
+
+ if (!obj) {
+ doc = document;
+ }
+
+ // Test if a DOM node has been passed and obtain a document object for it if so
+ else if (util.isHostProperty(obj, "nodeType")) {
+ doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ?
+ getIframeDocument(obj) : getDocument(obj);
+ }
+
+ // Test if the doc parameter appears to be a Window object
+ else if (isWindow(obj)) {
+ doc = obj.document;
+ }
+
+ if (!doc) {
+ throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
+ }
+
+ return doc;
+ }
+
+ function getRootContainer(node) {
+ var parent;
+ while ( (parent = node.parentNode) ) {
+ node = parent;
+ }
+ return node;
+ }
+
+ function comparePoints(nodeA, offsetA, nodeB, offsetB) {
+ // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
+ var nodeC, root, childA, childB, n;
+ if (nodeA == nodeB) {
+ // Case 1: nodes are the same
+ return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
+ } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
+ // Case 2: node C (container B or an ancestor) is a child node of A
+ return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
+ } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
+ // Case 3: node C (container A or an ancestor) is a child node of B
+ return getNodeIndex(nodeC) < offsetB ? -1 : 1;
+ } else {
+ root = getCommonAncestor(nodeA, nodeB);
+ if (!root) {
+ throw new Error("comparePoints error: nodes have no common ancestor");
+ }
+
+ // Case 4: containers are siblings or descendants of siblings
+ childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
+ childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
+
+ if (childA === childB) {
+ // This shouldn't be possible
+ throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
+ } else {
+ n = root.firstChild;
+ while (n) {
+ if (n === childA) {
+ return -1;
+ } else if (n === childB) {
+ return 1;
+ }
+ n = n.nextSibling;
+ }
+ }
+ }
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
+ var crashyTextNodes = false;
+
+ function isBrokenNode(node) {
+ var n;
+ try {
+ n = node.parentNode;
+ return false;
+ } catch (e) {
+ return true;
+ }
+ }
+
+ (function() {
+ var el = document.createElement("b");
+ el.innerHTML = "1";
+ var textNode = el.firstChild;
+ el.innerHTML = "<br>";
+ crashyTextNodes = isBrokenNode(textNode);
+
+ api.features.crashyTextNodes = crashyTextNodes;
+ })();
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ function inspectNode(node) {
+ if (!node) {
+ return "[No node]";
+ }
+ if (crashyTextNodes && isBrokenNode(node)) {
+ return "[Broken node]";
+ }
+ if (isCharacterDataNode(node)) {
+ return '"' + node.data + '"';
+ }
+ if (node.nodeType == 1) {
+ var idAttr = node.id ? ' id="' + node.id + '"' : "";
+ return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]";
+ }
+ return node.nodeName;
+ }
+
+ function fragmentFromNodeChildren(node) {
+ var fragment = getDocument(node).createDocumentFragment(), child;
+ while ( (child = node.firstChild) ) {
+ fragment.appendChild(child);
+ }
+ return fragment;
+ }
+
+ var getComputedStyleProperty;
+ if (typeof window.getComputedStyle != UNDEF) {
+ getComputedStyleProperty = function(el, propName) {
+ return getWindow(el).getComputedStyle(el, null)[propName];
+ };
+ } else if (typeof document.documentElement.currentStyle != UNDEF) {
+ getComputedStyleProperty = function(el, propName) {
+ return el.currentStyle[propName];
+ };
+ } else {
+ module.fail("No means of obtaining computed style properties found");
+ }
+
+ function NodeIterator(root) {
+ this.root = root;
+ this._next = root;
+ }
+
+ NodeIterator.prototype = {
+ _current: null,
+
+ hasNext: function() {
+ return !!this._next;
+ },
+
+ next: function() {
+ var n = this._current = this._next;
+ var child, next;
+ if (this._current) {
+ child = n.firstChild;
+ if (child) {
+ this._next = child;
+ } else {
+ next = null;
+ while ((n !== this.root) && !(next = n.nextSibling)) {
+ n = n.parentNode;
+ }
+ this._next = next;
+ }
+ }
+ return this._current;
+ },
+
+ detach: function() {
+ this._current = this._next = this.root = null;
+ }
+ };
+
+ function createIterator(root) {
+ return new NodeIterator(root);
+ }
+
+ function DomPosition(node, offset) {
+ this.node = node;
+ this.offset = offset;
+ }
+
+ DomPosition.prototype = {
+ equals: function(pos) {
+ return !!pos && this.node === pos.node && this.offset == pos.offset;
+ },
+
+ inspect: function() {
+ return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
+ },
+
+ toString: function() {
+ return this.inspect();
+ }
+ };
+
+ function DOMException(codeName) {
+ this.code = this[codeName];
+ this.codeName = codeName;
+ this.message = "DOMException: " + this.codeName;
+ }
+
+ DOMException.prototype = {
+ INDEX_SIZE_ERR: 1,
+ HIERARCHY_REQUEST_ERR: 3,
+ WRONG_DOCUMENT_ERR: 4,
+ NO_MODIFICATION_ALLOWED_ERR: 7,
+ NOT_FOUND_ERR: 8,
+ NOT_SUPPORTED_ERR: 9,
+ INVALID_STATE_ERR: 11,
+ INVALID_NODE_TYPE_ERR: 24
+ };
+
+ DOMException.prototype.toString = function() {
+ return this.message;
+ };
+
+ api.dom = {
+ arrayContains: arrayContains,
+ isHtmlNamespace: isHtmlNamespace,
+ parentElement: parentElement,
+ getNodeIndex: getNodeIndex,
+ getNodeLength: getNodeLength,
+ getCommonAncestor: getCommonAncestor,
+ isAncestorOf: isAncestorOf,
+ isOrIsAncestorOf: isOrIsAncestorOf,
+ getClosestAncestorIn: getClosestAncestorIn,
+ isCharacterDataNode: isCharacterDataNode,
+ isTextOrCommentNode: isTextOrCommentNode,
+ insertAfter: insertAfter,
+ splitDataNode: splitDataNode,
+ getDocument: getDocument,
+ getWindow: getWindow,
+ getIframeWindow: getIframeWindow,
+ getIframeDocument: getIframeDocument,
+ getBody: util.getBody,
+ isWindow: isWindow,
+ getContentDocument: getContentDocument,
+ getRootContainer: getRootContainer,
+ comparePoints: comparePoints,
+ isBrokenNode: isBrokenNode,
+ inspectNode: inspectNode,
+ getComputedStyleProperty: getComputedStyleProperty,
+ fragmentFromNodeChildren: fragmentFromNodeChildren,
+ createIterator: createIterator,
+ DomPosition: DomPosition
+ };
+
+ api.DOMException = DOMException;
+ });
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Pure JavaScript implementation of DOM Range
+ api.createCoreModule("DomRange", ["DomUtil"], function(api, module) {
+ var dom = api.dom;
+ var util = api.util;
+ var DomPosition = dom.DomPosition;
+ var DOMException = api.DOMException;
+
+ var isCharacterDataNode = dom.isCharacterDataNode;
+ var getNodeIndex = dom.getNodeIndex;
+ var isOrIsAncestorOf = dom.isOrIsAncestorOf;
+ var getDocument = dom.getDocument;
+ var comparePoints = dom.comparePoints;
+ var splitDataNode = dom.splitDataNode;
+ var getClosestAncestorIn = dom.getClosestAncestorIn;
+ var getNodeLength = dom.getNodeLength;
+ var arrayContains = dom.arrayContains;
+ var getRootContainer = dom.getRootContainer;
+ var crashyTextNodes = api.features.crashyTextNodes;
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Utility functions
+
+ function isNonTextPartiallySelected(node, range) {
+ return (node.nodeType != 3) &&
+ (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
+ }
+
+ function getRangeDocument(range) {
+ return range.document || getDocument(range.startContainer);
+ }
+
+ function getBoundaryBeforeNode(node) {
+ return new DomPosition(node.parentNode, getNodeIndex(node));
+ }
+
+ function getBoundaryAfterNode(node) {
+ return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
+ }
+
+ function insertNodeAtPosition(node, n, o) {
+ var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
+ if (isCharacterDataNode(n)) {
+ if (o == n.length) {
+ dom.insertAfter(node, n);
+ } else {
+ n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
+ }
+ } else if (o >= n.childNodes.length) {
+ n.appendChild(node);
+ } else {
+ n.insertBefore(node, n.childNodes[o]);
+ }
+ return firstNodeInserted;
+ }
+
+ function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
+ assertRangeValid(rangeA);
+ assertRangeValid(rangeB);
+
+ if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
+ throw new DOMException("WRONG_DOCUMENT_ERR");
+ }
+
+ var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
+ endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
+
+ return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
+ }
+
+ function cloneSubtree(iterator) {
+ var partiallySelected;
+ for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
+ partiallySelected = iterator.isPartiallySelectedSubtree();
+ node = node.cloneNode(!partiallySelected);
+ if (partiallySelected) {
+ subIterator = iterator.getSubtreeIterator();
+ node.appendChild(cloneSubtree(subIterator));
+ subIterator.detach();
+ }
+
+ if (node.nodeType == 10) { // DocumentType
+ throw new DOMException("HIERARCHY_REQUEST_ERR");
+ }
+ frag.appendChild(node);
+ }
+ return frag;
+ }
+
+ function iterateSubtree(rangeIterator, func, iteratorState) {
+ var it, n;
+ iteratorState = iteratorState || { stop: false };
+ for (var node, subRangeIterator; node = rangeIterator.next(); ) {
+ if (rangeIterator.isPartiallySelectedSubtree()) {
+ if (func(node) === false) {
+ iteratorState.stop = true;
+ return;
+ } else {
+ // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
+ // the node selected by the Range.
+ subRangeIterator = rangeIterator.getSubtreeIterator();
+ iterateSubtree(subRangeIterator, func, iteratorState);
+ subRangeIterator.detach();
+ if (iteratorState.stop) {
+ return;
+ }
+ }
+ } else {
+ // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
+ // descendants
+ it = dom.createIterator(node);
+ while ( (n = it.next()) ) {
+ if (func(n) === false) {
+ iteratorState.stop = true;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ function deleteSubtree(iterator) {
+ var subIterator;
+ while (iterator.next()) {
+ if (iterator.isPartiallySelectedSubtree()) {
+ subIterator = iterator.getSubtreeIterator();
+ deleteSubtree(subIterator);
+ subIterator.detach();
+ } else {
+ iterator.remove();
+ }
+ }
+ }
+
+ function extractSubtree(iterator) {
+ for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
+
+ if (iterator.isPartiallySelectedSubtree()) {
+ node = node.cloneNode(false);
+ subIterator = iterator.getSubtreeIterator();
+ node.appendChild(extractSubtree(subIterator));
+ subIterator.detach();
+ } else {
+ iterator.remove();
+ }
+ if (node.nodeType == 10) { // DocumentType
+ throw new DOMException("HIERARCHY_REQUEST_ERR");
+ }
+ frag.appendChild(node);
+ }
+ return frag;
+ }
+
+ function getNodesInRange(range, nodeTypes, filter) {
+ var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
+ var filterExists = !!filter;
+ if (filterNodeTypes) {
+ regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
+ }
+
+ var nodes = [];
+ iterateSubtree(new RangeIterator(range, false), function(node) {
+ if (filterNodeTypes && !regex.test(node.nodeType)) {
+ return;
+ }
+ if (filterExists && !filter(node)) {
+ return;
+ }
+ // Don't include a boundary container if it is a character data node and the range does not contain any
+ // of its character data. See issue 190.
+ var sc = range.startContainer;
+ if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {
+ return;
+ }
+
+ var ec = range.endContainer;
+ if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) {
+ return;
+ }
+
+ nodes.push(node);
+ });
+ return nodes;
+ }
+
+ function inspect(range) {
+ var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
+ return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
+ dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
+
+ function RangeIterator(range, clonePartiallySelectedTextNodes) {
+ this.range = range;
+ this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
+
+
+ if (!range.collapsed) {
+ this.sc = range.startContainer;
+ this.so = range.startOffset;
+ this.ec = range.endContainer;
+ this.eo = range.endOffset;
+ var root = range.commonAncestorContainer;
+
+ if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
+ this.isSingleCharacterDataNode = true;
+ this._first = this._last = this._next = this.sc;
+ } else {
+ this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
+ this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
+ this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
+ this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
+ }
+ }
+ }
+
+ RangeIterator.prototype = {
+ _current: null,
+ _next: null,
+ _first: null,
+ _last: null,
+ isSingleCharacterDataNode: false,
+
+ reset: function() {
+ this._current = null;
+ this._next = this._first;
+ },
+
+ hasNext: function() {
+ return !!this._next;
+ },
+
+ next: function() {
+ // Move to next node
+ var current = this._current = this._next;
+ if (current) {
+ this._next = (current !== this._last) ? current.nextSibling : null;
+
+ // Check for partially selected text nodes
+ if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
+ if (current === this.ec) {
+ (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
+ }
+ if (this._current === this.sc) {
+ (current = current.cloneNode(true)).deleteData(0, this.so);
+ }
+ }
+ }
+
+ return current;
+ },
+
+ remove: function() {
+ var current = this._current, start, end;
+
+ if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
+ start = (current === this.sc) ? this.so : 0;
+ end = (current === this.ec) ? this.eo : current.length;
+ if (start != end) {
+ current.deleteData(start, end - start);
+ }
+ } else {
+ if (current.parentNode) {
+ current.parentNode.removeChild(current);
+ } else {
+ }
+ }
+ },
+
+ // Checks if the current node is partially selected
+ isPartiallySelectedSubtree: function() {
+ var current = this._current;
+ return isNonTextPartiallySelected(current, this.range);
+ },
+
+ getSubtreeIterator: function() {
+ var subRange;
+ if (this.isSingleCharacterDataNode) {
+ subRange = this.range.cloneRange();
+ subRange.collapse(false);
+ } else {
+ subRange = new Range(getRangeDocument(this.range));
+ var current = this._current;
+ var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
+
+ if (isOrIsAncestorOf(current, this.sc)) {
+ startContainer = this.sc;
+ startOffset = this.so;
+ }
+ if (isOrIsAncestorOf(current, this.ec)) {
+ endContainer = this.ec;
+ endOffset = this.eo;
+ }
+
+ updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
+ }
+ return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
+ },
+
+ detach: function() {
+ this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
+ }
+ };
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
+ var rootContainerNodeTypes = [2, 9, 11];
+ var readonlyNodeTypes = [5, 6, 10, 12];
+ var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
+ var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
+
+ function createAncestorFinder(nodeTypes) {
+ return function(node, selfIsAncestor) {
+ var t, n = selfIsAncestor ? node : node.parentNode;
+ while (n) {
+ t = n.nodeType;
+ if (arrayContains(nodeTypes, t)) {
+ return n;
+ }
+ n = n.parentNode;
+ }
+ return null;
+ };
+ }
+
+ var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
+ var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
+ var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
+
+ function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
+ if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
+ throw new DOMException("INVALID_NODE_TYPE_ERR");
+ }
+ }
+
+ function assertValidNodeType(node, invalidTypes) {
+ if (!arrayContains(invalidTypes, node.nodeType)) {
+ throw new DOMException("INVALID_NODE_TYPE_ERR");
+ }
+ }
+
+ function assertValidOffset(node, offset) {
+ if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
+ throw new DOMException("INDEX_SIZE_ERR");
+ }
+ }
+
+ function assertSameDocumentOrFragment(node1, node2) {
+ if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
+ throw new DOMException("WRONG_DOCUMENT_ERR");
+ }
+ }
+
+ function assertNodeNotReadOnly(node) {
+ if (getReadonlyAncestor(node, true)) {
+ throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
+ }
+ }
+
+ function assertNode(node, codeName) {
+ if (!node) {
+ throw new DOMException(codeName);
+ }
+ }
+
+ function isOrphan(node) {
+ return (crashyTextNodes && dom.isBrokenNode(node)) ||
+ !arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
+ }
+
+ function isValidOffset(node, offset) {
+ return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
+ }
+
+ function isRangeValid(range) {
+ return (!!range.startContainer && !!range.endContainer &&
+ !isOrphan(range.startContainer) &&
+ !isOrphan(range.endContainer) &&
+ isValidOffset(range.startContainer, range.startOffset) &&
+ isValidOffset(range.endContainer, range.endOffset));
+ }
+
+ function assertRangeValid(range) {
+ if (!isRangeValid(range)) {
+ throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
+ }
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Test the browser's innerHTML support to decide how to implement createContextualFragment
+ var styleEl = document.createElement("style");
+ var htmlParsingConforms = false;
+ try {
+ styleEl.innerHTML = "<b>x</b>";
+ htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
+ } catch (e) {
+ // IE 6 and 7 throw
+ }
+
+ api.features.htmlParsingConforms = htmlParsingConforms;
+
+ var createContextualFragment = htmlParsingConforms ?
+
+ // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
+ // discussion and base code for this implementation at issue 67.
+ // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
+ // Thanks to Aleks Williams.
+ function(fragmentStr) {
+ // "Let node the context object's start's node."
+ var node = this.startContainer;
+ var doc = getDocument(node);
+
+ // "If the context object's start's node is null, raise an INVALID_STATE_ERR
+ // exception and abort these steps."
+ if (!node) {
+ throw new DOMException("INVALID_STATE_ERR");
+ }
+
+ // "Let element be as follows, depending on node's interface:"
+ // Document, Document Fragment: null
+ var el = null;
+
+ // "Element: node"
+ if (node.nodeType == 1) {
+ el = node;
+
+ // "Text, Comment: node's parentElement"
+ } else if (isCharacterDataNode(node)) {
+ el = dom.parentElement(node);
+ }
+
+ // "If either element is null or element's ownerDocument is an HTML document
+ // and element's local name is "html" and element's namespace is the HTML
+ // namespace"
+ if (el === null || (
+ el.nodeName == "HTML" &&
+ dom.isHtmlNamespace(getDocument(el).documentElement) &&
+ dom.isHtmlNamespace(el)
+ )) {
+
+ // "let element be a new Element with "body" as its local name and the HTML
+ // namespace as its namespace.""
+ el = doc.createElement("body");
+ } else {
+ el = el.cloneNode(false);
+ }
+
+ // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
+ // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
+ // "In either case, the algorithm must be invoked with fragment as the input
+ // and element as the context element."
+ el.innerHTML = fragmentStr;
+
+ // "If this raises an exception, then abort these steps. Otherwise, let new
+ // children be the nodes returned."
+
+ // "Let fragment be a new DocumentFragment."
+ // "Append all new children to fragment."
+ // "Return fragment."
+ return dom.fragmentFromNodeChildren(el);
+ } :
+
+ // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
+ // previous versions of Rangy used (with the exception of using a body element rather than a div)
+ function(fragmentStr) {
+ var doc = getRangeDocument(this);
+ var el = doc.createElement("body");
+ el.innerHTML = fragmentStr;
+
+ return dom.fragmentFromNodeChildren(el);
+ };
+
+ function splitRangeBoundaries(range, positionsToPreserve) {
+ assertRangeValid(range);
+
+ var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
+ var startEndSame = (sc === ec);
+
+ if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
+ splitDataNode(ec, eo, positionsToPreserve);
+ }
+
+ if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
+ sc = splitDataNode(sc, so, positionsToPreserve);
+ if (startEndSame) {
+ eo -= so;
+ ec = sc;
+ } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
+ eo++;
+ }
+ so = 0;
+ }
+ range.setStartAndEnd(sc, so, ec, eo);
+ }
+
+ function rangeToHtml(range) {
+ assertRangeValid(range);
+ var container = range.commonAncestorContainer.parentNode.cloneNode(false);
+ container.appendChild( range.cloneContents() );
+ return container.innerHTML;
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
+ "commonAncestorContainer"];
+
+ var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
+ var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
+
+ util.extend(api.rangePrototype, {
+ compareBoundaryPoints: function(how, range) {
+ assertRangeValid(this);
+ assertSameDocumentOrFragment(this.startContainer, range.startContainer);
+
+ var nodeA, offsetA, nodeB, offsetB;
+ var prefixA = (how == e2s || how == s2s) ? "start" : "end";
+ var prefixB = (how == s2e || how == s2s) ? "start" : "end";
+ nodeA = this[prefixA + "Container"];
+ offsetA = this[prefixA + "Offset"];
+ nodeB = range[prefixB + "Container"];
+ offsetB = range[prefixB + "Offset"];
+ return comparePoints(nodeA, offsetA, nodeB, offsetB);
+ },
+
+ insertNode: function(node) {
+ assertRangeValid(this);
+ assertValidNodeType(node, insertableNodeTypes);
+ assertNodeNotReadOnly(this.startContainer);
+
+ if (isOrIsAncestorOf(node, this.startContainer)) {
+ throw new DOMException("HIERARCHY_REQUEST_ERR");
+ }
+
+ // No check for whether the container of the start of the Range is of a type that does not allow
+ // children of the type of node: the browser's DOM implementation should do this for us when we attempt
+ // to add the node
+
+ var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
+ this.setStartBefore(firstNodeInserted);
+ },
+
+ cloneContents: function() {
+ assertRangeValid(this);
+
+ var clone, frag;
+ if (this.collapsed) {
+ return getRangeDocument(this).createDocumentFragment();
+ } else {
+ if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
+ clone = this.startContainer.cloneNode(true);
+ clone.data = clone.data.slice(this.startOffset, this.endOffset);
+ frag = getRangeDocument(this).createDocumentFragment();
+ frag.appendChild(clone);
+ return frag;
+ } else {
+ var iterator = new RangeIterator(this, true);
+ clone = cloneSubtree(iterator);
+ iterator.detach();
+ }
+ return clone;
+ }
+ },
+
+ canSurroundContents: function() {
+ assertRangeValid(this);
+ assertNodeNotReadOnly(this.startContainer);
+ assertNodeNotReadOnly(this.endContainer);
+
+ // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
+ // no non-text nodes.
+ var iterator = new RangeIterator(this, true);
+ var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
+ (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
+ iterator.detach();
+ return !boundariesInvalid;
+ },
+
+ surroundContents: function(node) {
+ assertValidNodeType(node, surroundNodeTypes);
+
+ if (!this.canSurroundContents()) {
+ throw new DOMException("INVALID_STATE_ERR");
+ }
+
+ // Extract the contents
+ var content = this.extractContents();
+
+ // Clear the children of the node
+ if (node.hasChildNodes()) {
+ while (node.lastChild) {
+ node.removeChild(node.lastChild);
+ }
+ }
+
+ // Insert the new node and add the extracted contents
+ insertNodeAtPosition(node, this.startContainer, this.startOffset);
+ node.appendChild(content);
+
+ this.selectNode(node);
+ },
+
+ cloneRange: function() {
+ assertRangeValid(this);
+ var range = new Range(getRangeDocument(this));
+ var i = rangeProperties.length, prop;
+ while (i--) {
+ prop = rangeProperties[i];
+ range[prop] = this[prop];
+ }
+ return range;
+ },
+
+ toString: function() {
+ assertRangeValid(this);
+ var sc = this.startContainer;
+ if (sc === this.endContainer && isCharacterDataNode(sc)) {
+ return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
+ } else {
+ var textParts = [], iterator = new RangeIterator(this, true);
+ iterateSubtree(iterator, function(node) {
+ // Accept only text or CDATA nodes, not comments
+ if (node.nodeType == 3 || node.nodeType == 4) {
+ textParts.push(node.data);
+ }
+ });
+ iterator.detach();
+ return textParts.join("");
+ }
+ },
+
+ // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
+ // been removed from Mozilla.
+
+ compareNode: function(node) {
+ assertRangeValid(this);
+
+ var parent = node.parentNode;
+ var nodeIndex = getNodeIndex(node);
+
+ if (!parent) {
+ throw new DOMException("NOT_FOUND_ERR");
+ }
+
+ var startComparison = this.comparePoint(parent, nodeIndex),
+ endComparison = this.comparePoint(parent, nodeIndex + 1);
+
+ if (startComparison < 0) { // Node starts before
+ return (endComparison > 0) ? n_b_a : n_b;
+ } else {
+ return (endComparison > 0) ? n_a : n_i;
+ }
+ },
+
+ comparePoint: function(node, offset) {
+ assertRangeValid(this);
+ assertNode(node, "HIERARCHY_REQUEST_ERR");
+ assertSameDocumentOrFragment(node, this.startContainer);
+
+ if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
+ return -1;
+ } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
+ return 1;
+ }
+ return 0;
+ },
+
+ createContextualFragment: createContextualFragment,
+
+ toHtml: function() {
+ return rangeToHtml(this);
+ },
+
+ // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
+ // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
+ intersectsNode: function(node, touchingIsIntersecting) {
+ assertRangeValid(this);
+ assertNode(node, "NOT_FOUND_ERR");
+ if (getDocument(node) !== getRangeDocument(this)) {
+ return false;
+ }
+
+ var parent = node.parentNode, offset = getNodeIndex(node);
+ assertNode(parent, "NOT_FOUND_ERR");
+
+ var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
+ endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
+
+ return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
+ },
+
+ isPointInRange: function(node, offset) {
+ assertRangeValid(this);
+ assertNode(node, "HIERARCHY_REQUEST_ERR");
+ assertSameDocumentOrFragment(node, this.startContainer);
+
+ return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
+ (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
+ },
+
+ // The methods below are non-standard and invented by me.
+
+ // Sharing a boundary start-to-end or end-to-start does not count as intersection.
+ intersectsRange: function(range) {
+ return rangesIntersect(this, range, false);
+ },
+
+ // Sharing a boundary start-to-end or end-to-start does count as intersection.
+ intersectsOrTouchesRange: function(range) {
+ return rangesIntersect(this, range, true);
+ },
+
+ intersection: function(range) {
+ if (this.intersectsRange(range)) {
+ var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
+ endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
+
+ var intersectionRange = this.cloneRange();
+ if (startComparison == -1) {
+ intersectionRange.setStart(range.startContainer, range.startOffset);
+ }
+ if (endComparison == 1) {
+ intersectionRange.setEnd(range.endContainer, range.endOffset);
+ }
+ return intersectionRange;
+ }
+ return null;
+ },
+
+ union: function(range) {
+ if (this.intersectsOrTouchesRange(range)) {
+ var unionRange = this.cloneRange();
+ if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
+ unionRange.setStart(range.startContainer, range.startOffset);
+ }
+ if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
+ unionRange.setEnd(range.endContainer, range.endOffset);
+ }
+ return unionRange;
+ } else {
+ throw new DOMException("Ranges do not intersect");
+ }
+ },
+
+ containsNode: function(node, allowPartial) {
+ if (allowPartial) {
+ return this.intersectsNode(node, false);
+ } else {
+ return this.compareNode(node) == n_i;
+ }
+ },
+
+ containsNodeContents: function(node) {
+ return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
+ },
+
+ containsRange: function(range) {
+ var intersection = this.intersection(range);
+ return intersection !== null && range.equals(intersection);
+ },
+
+ containsNodeText: function(node) {
+ var nodeRange = this.cloneRange();
+ nodeRange.selectNode(node);
+ var textNodes = nodeRange.getNodes([3]);
+ if (textNodes.length > 0) {
+ nodeRange.setStart(textNodes[0], 0);
+ var lastTextNode = textNodes.pop();
+ nodeRange.setEnd(lastTextNode, lastTextNode.length);
+ return this.containsRange(nodeRange);
+ } else {
+ return this.containsNodeContents(node);
+ }
+ },
+
+ getNodes: function(nodeTypes, filter) {
+ assertRangeValid(this);
+ return getNodesInRange(this, nodeTypes, filter);
+ },
+
+ getDocument: function() {
+ return getRangeDocument(this);
+ },
+
+ collapseBefore: function(node) {
+ this.setEndBefore(node);
+ this.collapse(false);
+ },
+
+ collapseAfter: function(node) {
+ this.setStartAfter(node);
+ this.collapse(true);
+ },
+
+ getBookmark: function(containerNode) {
+ var doc = getRangeDocument(this);
+ var preSelectionRange = api.createRange(doc);
+ containerNode = containerNode || dom.getBody(doc);
+ preSelectionRange.selectNodeContents(containerNode);
+ var range = this.intersection(preSelectionRange);
+ var start = 0, end = 0;
+ if (range) {
+ preSelectionRange.setEnd(range.startContainer, range.startOffset);
+ start = preSelectionRange.toString().length;
+ end = start + range.toString().length;
+ }
+
+ return {
+ start: start,
+ end: end,
+ containerNode: containerNode
+ };
+ },
+
+ moveToBookmark: function(bookmark) {
+ var containerNode = bookmark.containerNode;
+ var charIndex = 0;
+ this.setStart(containerNode, 0);
+ this.collapse(true);
+ var nodeStack = [containerNode], node, foundStart = false, stop = false;
+ var nextCharIndex, i, childNodes;
+
+ while (!stop && (node = nodeStack.pop())) {
+ if (node.nodeType == 3) {
+ nextCharIndex = charIndex + node.length;
+ if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
+ this.setStart(node, bookmark.start - charIndex);
+ foundStart = true;
+ }
+ if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
+ this.setEnd(node, bookmark.end - charIndex);
+ stop = true;
+ }
+ charIndex = nextCharIndex;
+ } else {
+ childNodes = node.childNodes;
+ i = childNodes.length;
+ while (i--) {
+ nodeStack.push(childNodes[i]);
+ }
+ }
+ }
+ },
+
+ getName: function() {
+ return "DomRange";
+ },
+
+ equals: function(range) {
+ return Range.rangesEqual(this, range);
+ },
+
+ isValid: function() {
+ return isRangeValid(this);
+ },
+
+ inspect: function() {
+ return inspect(this);
+ },
+
+ detach: function() {
+ // In DOM4, detach() is now a no-op.
+ }
+ });
+
+ function copyComparisonConstantsToObject(obj) {
+ obj.START_TO_START = s2s;
+ obj.START_TO_END = s2e;
+ obj.END_TO_END = e2e;
+ obj.END_TO_START = e2s;
+
+ obj.NODE_BEFORE = n_b;
+ obj.NODE_AFTER = n_a;
+ obj.NODE_BEFORE_AND_AFTER = n_b_a;
+ obj.NODE_INSIDE = n_i;
+ }
+
+ function copyComparisonConstants(constructor) {
+ copyComparisonConstantsToObject(constructor);
+ copyComparisonConstantsToObject(constructor.prototype);
+ }
+
+ function createRangeContentRemover(remover, boundaryUpdater) {
+ return function() {
+ assertRangeValid(this);
+
+ var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
+
+ var iterator = new RangeIterator(this, true);
+
+ // Work out where to position the range after content removal
+ var node, boundary;
+ if (sc !== root) {
+ node = getClosestAncestorIn(sc, root, true);
+ boundary = getBoundaryAfterNode(node);
+ sc = boundary.node;
+ so = boundary.offset;
+ }
+
+ // Check none of the range is read-only
+ iterateSubtree(iterator, assertNodeNotReadOnly);
+
+ iterator.reset();
+
+ // Remove the content
+ var returnValue = remover(iterator);
+ iterator.detach();
+
+ // Move to the new position
+ boundaryUpdater(this, sc, so, sc, so);
+
+ return returnValue;
+ };
+ }
+
+ function createPrototypeRange(constructor, boundaryUpdater) {
+ function createBeforeAfterNodeSetter(isBefore, isStart) {
+ return function(node) {
+ assertValidNodeType(node, beforeAfterNodeTypes);
+ assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
+
+ var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
+ (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
+ };
+ }
+
+ function setRangeStart(range, node, offset) {
+ var ec = range.endContainer, eo = range.endOffset;
+ if (node !== range.startContainer || offset !== range.startOffset) {
+ // Check the root containers of the range and the new boundary, and also check whether the new boundary
+ // is after the current end. In either case, collapse the range to the new position
+ if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
+ ec = node;
+ eo = offset;
+ }
+ boundaryUpdater(range, node, offset, ec, eo);
+ }
+ }
+
+ function setRangeEnd(range, node, offset) {
+ var sc = range.startContainer, so = range.startOffset;
+ if (node !== range.endContainer || offset !== range.endOffset) {
+ // Check the root containers of the range and the new boundary, and also check whether the new boundary
+ // is after the current end. In either case, collapse the range to the new position
+ if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
+ sc = node;
+ so = offset;
+ }
+ boundaryUpdater(range, sc, so, node, offset);
+ }
+ }
+
+ // Set up inheritance
+ var F = function() {};
+ F.prototype = api.rangePrototype;
+ constructor.prototype = new F();
+
+ util.extend(constructor.prototype, {
+ setStart: function(node, offset) {
+ assertNoDocTypeNotationEntityAncestor(node, true);
+ assertValidOffset(node, offset);
+
+ setRangeStart(this, node, offset);
+ },
+
+ setEnd: function(node, offset) {
+ assertNoDocTypeNotationEntityAncestor(node, true);
+ assertValidOffset(node, offset);
+
+ setRangeEnd(this, node, offset);
+ },
+
+ /**
+ * Convenience method to set a range's start and end boundaries. Overloaded as follows:
+ * - Two parameters (node, offset) creates a collapsed range at that position
+ * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
+ * startOffset and ending at endOffset
+ * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
+ * startNode and ending at endOffset in endNode
+ */
+ setStartAndEnd: function() {
+ var args = arguments;
+ var sc = args[0], so = args[1], ec = sc, eo = so;
+
+ switch (args.length) {
+ case 3:
+ eo = args[2];
+ break;
+ case 4:
+ ec = args[2];
+ eo = args[3];
+ break;
+ }
+
+ boundaryUpdater(this, sc, so, ec, eo);
+ },
+
+ setBoundary: function(node, offset, isStart) {
+ this["set" + (isStart ? "Start" : "End")](node, offset);
+ },
+
+ setStartBefore: createBeforeAfterNodeSetter(true, true),
+ setStartAfter: createBeforeAfterNodeSetter(false, true),
+ setEndBefore: createBeforeAfterNodeSetter(true, false),
+ setEndAfter: createBeforeAfterNodeSetter(false, false),
+
+ collapse: function(isStart) {
+ assertRangeValid(this);
+ if (isStart) {
+ boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
+ } else {
+ boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
+ }
+ },
+
+ selectNodeContents: function(node) {
+ assertNoDocTypeNotationEntityAncestor(node, true);
+
+ boundaryUpdater(this, node, 0, node, getNodeLength(node));
+ },
+
+ selectNode: function(node) {
+ assertNoDocTypeNotationEntityAncestor(node, false);
+ assertValidNodeType(node, beforeAfterNodeTypes);
+
+ var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
+ boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
+ },
+
+ extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
+
+ deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
+
+ canSurroundContents: function() {
+ assertRangeValid(this);
+ assertNodeNotReadOnly(this.startContainer);
+ assertNodeNotReadOnly(this.endContainer);
+
+ // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
+ // no non-text nodes.
+ var iterator = new RangeIterator(this, true);
+ var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) ||
+ (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
+ iterator.detach();
+ return !boundariesInvalid;
+ },
+
+ splitBoundaries: function() {
+ splitRangeBoundaries(this);
+ },
+
+ splitBoundariesPreservingPositions: function(positionsToPreserve) {
+ splitRangeBoundaries(this, positionsToPreserve);
+ },
+
+ normalizeBoundaries: function() {
+ assertRangeValid(this);
+
+ var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
+
+ var mergeForward = function(node) {
+ var sibling = node.nextSibling;
+ if (sibling && sibling.nodeType == node.nodeType) {
+ ec = node;
+ eo = node.length;
+ node.appendData(sibling.data);
+ sibling.parentNode.removeChild(sibling);
+ }
+ };
+
+ var mergeBackward = function(node) {
+ var sibling = node.previousSibling;
+ if (sibling && sibling.nodeType == node.nodeType) {
+ sc = node;
+ var nodeLength = node.length;
+ so = sibling.length;
+ node.insertData(0, sibling.data);
+ sibling.parentNode.removeChild(sibling);
+ if (sc == ec) {
+ eo += so;
+ ec = sc;
+ } else if (ec == node.parentNode) {
+ var nodeIndex = getNodeIndex(node);
+ if (eo == nodeIndex) {
+ ec = node;
+ eo = nodeLength;
+ } else if (eo > nodeIndex) {
+ eo--;
+ }
+ }
+ }
+ };
+
+ var normalizeStart = true;
+
+ if (isCharacterDataNode(ec)) {
+ if (ec.length == eo) {
+ mergeForward(ec);
+ }
+ } else {
+ if (eo > 0) {
+ var endNode = ec.childNodes[eo - 1];
+ if (endNode && isCharacterDataNode(endNode)) {
+ mergeForward(endNode);
+ }
+ }
+ normalizeStart = !this.collapsed;
+ }
+
+ if (normalizeStart) {
+ if (isCharacterDataNode(sc)) {
+ if (so == 0) {
+ mergeBackward(sc);
+ }
+ } else {
+ if (so < sc.childNodes.length) {
+ var startNode = sc.childNodes[so];
+ if (startNode && isCharacterDataNode(startNode)) {
+ mergeBackward(startNode);
+ }
+ }
+ }
+ } else {
+ sc = ec;
+ so = eo;
+ }
+
+ boundaryUpdater(this, sc, so, ec, eo);
+ },
+
+ collapseToPoint: function(node, offset) {
+ assertNoDocTypeNotationEntityAncestor(node, true);
+ assertValidOffset(node, offset);
+ this.setStartAndEnd(node, offset);
+ }
+ });
+
+ copyComparisonConstants(constructor);
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Updates commonAncestorContainer and collapsed after boundary change
+ function updateCollapsedAndCommonAncestor(range) {
+ range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
+ range.commonAncestorContainer = range.collapsed ?
+ range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
+ }
+
+ function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
+ range.startContainer = startContainer;
+ range.startOffset = startOffset;
+ range.endContainer = endContainer;
+ range.endOffset = endOffset;
+ range.document = dom.getDocument(startContainer);
+
+ updateCollapsedAndCommonAncestor(range);
+ }
+
+ function Range(doc) {
+ this.startContainer = doc;
+ this.startOffset = 0;
+ this.endContainer = doc;
+ this.endOffset = 0;
+ this.document = doc;
+ updateCollapsedAndCommonAncestor(this);
+ }
+
+ createPrototypeRange(Range, updateBoundaries);
+
+ util.extend(Range, {
+ rangeProperties: rangeProperties,
+ RangeIterator: RangeIterator,
+ copyComparisonConstants: copyComparisonConstants,
+ createPrototypeRange: createPrototypeRange,
+ inspect: inspect,
+ toHtml: rangeToHtml,
+ getRangeDocument: getRangeDocument,
+ rangesEqual: function(r1, r2) {
+ return r1.startContainer === r2.startContainer &&
+ r1.startOffset === r2.startOffset &&
+ r1.endContainer === r2.endContainer &&
+ r1.endOffset === r2.endOffset;
+ }
+ });
+
+ api.DomRange = Range;
+ });
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Wrappers for the browser's native DOM Range and/or TextRange implementation
+ api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) {
+ var WrappedRange, WrappedTextRange;
+ var dom = api.dom;
+ var util = api.util;
+ var DomPosition = dom.DomPosition;
+ var DomRange = api.DomRange;
+ var getBody = dom.getBody;
+ var getContentDocument = dom.getContentDocument;
+ var isCharacterDataNode = dom.isCharacterDataNode;
+
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ if (api.features.implementsDomRange) {
+ // This is a wrapper around the browser's native DOM Range. It has two aims:
+ // - Provide workarounds for specific browser bugs
+ // - provide convenient extensions, which are inherited from Rangy's DomRange
+
+ (function() {
+ var rangeProto;
+ var rangeProperties = DomRange.rangeProperties;
+
+ function updateRangeProperties(range) {
+ var i = rangeProperties.length, prop;
+ while (i--) {
+ prop = rangeProperties[i];
+ range[prop] = range.nativeRange[prop];
+ }
+ // Fix for broken collapsed property in IE 9.
+ range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
+ }
+
+ function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
+ var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
+ var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
+ var nativeRangeDifferent = !range.equals(range.nativeRange);
+
+ // Always set both boundaries for the benefit of IE9 (see issue 35)
+ if (startMoved || endMoved || nativeRangeDifferent) {
+ range.setEnd(endContainer, endOffset);
+ range.setStart(startContainer, startOffset);
+ }
+ }
+
+ var createBeforeAfterNodeSetter;
+
+ WrappedRange = function(range) {
+ if (!range) {
+ throw module.createError("WrappedRange: Range must be specified");
+ }
+ this.nativeRange = range;
+ updateRangeProperties(this);
+ };
+
+ DomRange.createPrototypeRange(WrappedRange, updateNativeRange);
+
+ rangeProto = WrappedRange.prototype;
+
+ rangeProto.selectNode = function(node) {
+ this.nativeRange.selectNode(node);
+ updateRangeProperties(this);
+ };
+
+ rangeProto.cloneContents = function() {
+ return this.nativeRange.cloneContents();
+ };
+
+ // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
+ // insertNode() is never delegated to the native range.
+
+ rangeProto.surroundContents = function(node) {
+ this.nativeRange.surroundContents(node);
+ updateRangeProperties(this);
+ };
+
+ rangeProto.collapse = function(isStart) {
+ this.nativeRange.collapse(isStart);
+ updateRangeProperties(this);
+ };
+
+ rangeProto.cloneRange = function() {
+ return new WrappedRange(this.nativeRange.cloneRange());
+ };
+
+ rangeProto.refresh = function() {
+ updateRangeProperties(this);
+ };
+
+ rangeProto.toString = function() {
+ return this.nativeRange.toString();
+ };
+
+ // Create test range and node for feature detection
+
+ var testTextNode = document.createTextNode("test");
+ getBody(document).appendChild(testTextNode);
+ var range = document.createRange();
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
+ // correct for it
+
+ range.setStart(testTextNode, 0);
+ range.setEnd(testTextNode, 0);
+
+ try {
+ range.setStart(testTextNode, 1);
+
+ rangeProto.setStart = function(node, offset) {
+ this.nativeRange.setStart(node, offset);
+ updateRangeProperties(this);
+ };
+
+ rangeProto.setEnd = function(node, offset) {
+ this.nativeRange.setEnd(node, offset);
+ updateRangeProperties(this);
+ };
+
+ createBeforeAfterNodeSetter = function(name) {
+ return function(node) {
+ this.nativeRange[name](node);
+ updateRangeProperties(this);
+ };
+ };
+
+ } catch(ex) {
+
+ rangeProto.setStart = function(node, offset) {
+ try {
+ this.nativeRange.setStart(node, offset);
+ } catch (ex) {
+ this.nativeRange.setEnd(node, offset);
+ this.nativeRange.setStart(node, offset);
+ }
+ updateRangeProperties(this);
+ };
+
+ rangeProto.setEnd = function(node, offset) {
+ try {
+ this.nativeRange.setEnd(node, offset);
+ } catch (ex) {
+ this.nativeRange.setStart(node, offset);
+ this.nativeRange.setEnd(node, offset);
+ }
+ updateRangeProperties(this);
+ };
+
+ createBeforeAfterNodeSetter = function(name, oppositeName) {
+ return function(node) {
+ try {
+ this.nativeRange[name](node);
+ } catch (ex) {
+ this.nativeRange[oppositeName](node);
+ this.nativeRange[name](node);
+ }
+ updateRangeProperties(this);
+ };
+ };
+ }
+
+ rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
+ rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
+ rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
+ rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
+ // whether the native implementation can be trusted
+ rangeProto.selectNodeContents = function(node) {
+ this.setStartAndEnd(node, 0, dom.getNodeLength(node));
+ };
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
+ // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
+
+ range.selectNodeContents(testTextNode);
+ range.setEnd(testTextNode, 3);
+
+ var range2 = document.createRange();
+ range2.selectNodeContents(testTextNode);
+ range2.setEnd(testTextNode, 4);
+ range2.setStart(testTextNode, 2);
+
+ if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
+ range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
+ // This is the wrong way round, so correct for it
+
+ rangeProto.compareBoundaryPoints = function(type, range) {
+ range = range.nativeRange || range;
+ if (type == range.START_TO_END) {
+ type = range.END_TO_START;
+ } else if (type == range.END_TO_START) {
+ type = range.START_TO_END;
+ }
+ return this.nativeRange.compareBoundaryPoints(type, range);
+ };
+ } else {
+ rangeProto.compareBoundaryPoints = function(type, range) {
+ return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
+ };
+ }
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107.
+
+ var el = document.createElement("div");
+ el.innerHTML = "123";
+ var textNode = el.firstChild;
+ var body = getBody(document);
+ body.appendChild(el);
+
+ range.setStart(textNode, 1);
+ range.setEnd(textNode, 2);
+ range.deleteContents();
+
+ if (textNode.data == "13") {
+ // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
+ // extractContents()
+ rangeProto.deleteContents = function() {
+ this.nativeRange.deleteContents();
+ updateRangeProperties(this);
+ };
+
+ rangeProto.extractContents = function() {
+ var frag = this.nativeRange.extractContents();
+ updateRangeProperties(this);
+ return frag;
+ };
+ } else {
+ }
+
+ body.removeChild(el);
+ body = null;
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Test for existence of createContextualFragment and delegate to it if it exists
+ if (util.isHostMethod(range, "createContextualFragment")) {
+ rangeProto.createContextualFragment = function(fragmentStr) {
+ return this.nativeRange.createContextualFragment(fragmentStr);
+ };
+ }
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Clean up
+ getBody(document).removeChild(testTextNode);
+
+ rangeProto.getName = function() {
+ return "WrappedRange";
+ };
+
+ api.WrappedRange = WrappedRange;
+
+ api.createNativeRange = function(doc) {
+ doc = getContentDocument(doc, module, "createNativeRange");
+ return doc.createRange();
+ };
+ })();
+ }
+
+ if (api.features.implementsTextRange) {
+ /*
+ This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
+ method. For example, in the following (where pipes denote the selection boundaries):
+
+ <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
+
+ var range = document.selection.createRange();
+ alert(range.parentElement().id); // Should alert "ul" but alerts "b"
+
+ This method returns the common ancestor node of the following:
+ - the parentElement() of the textRange
+ - the parentElement() of the textRange after calling collapse(true)
+ - the parentElement() of the textRange after calling collapse(false)
+ */
+ var getTextRangeContainerElement = function(textRange) {
+ var parentEl = textRange.parentElement();
+ var range = textRange.duplicate();
+ range.collapse(true);
+ var startEl = range.parentElement();
+ range = textRange.duplicate();
+ range.collapse(false);
+ var endEl = range.parentElement();
+ var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
+
+ return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
+ };
+
+ var textRangeIsCollapsed = function(textRange) {
+ return textRange.compareEndPoints("StartToEnd", textRange) == 0;
+ };
+
+ // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started
+ // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/)
+ // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange
+ // bugs, handling for inputs and images, plus optimizations.
+ var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
+ var workingRange = textRange.duplicate();
+ workingRange.collapse(isStart);
+ var containerElement = workingRange.parentElement();
+
+ // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
+ // check for that
+ if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
+ containerElement = wholeRangeContainerElement;
+ }
+
+
+ // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
+ // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
+ if (!containerElement.canHaveHTML) {
+ var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
+ return {
+ boundaryPosition: pos,
+ nodeInfo: {
+ nodeIndex: pos.offset,
+ containerElement: pos.node
+ }
+ };
+ }
+
+ var workingNode = dom.getDocument(containerElement).createElement("span");
+
+ // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
+ // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
+ if (workingNode.parentNode) {
+ workingNode.parentNode.removeChild(workingNode);
+ }
+
+ var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
+ var previousNode, nextNode, boundaryPosition, boundaryNode;
+ var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
+ var childNodeCount = containerElement.childNodes.length;
+ var end = childNodeCount;
+
+ // Check end first. Code within the loop assumes that the endth child node of the container is definitely
+ // after the range boundary.
+ var nodeIndex = end;
+
+ while (true) {
+ if (nodeIndex == childNodeCount) {
+ containerElement.appendChild(workingNode);
+ } else {
+ containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
+ }
+ workingRange.moveToElementText(workingNode);
+ comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
+ if (comparison == 0 || start == end) {
+ break;
+ } else if (comparison == -1) {
+ if (end == start + 1) {
+ // We know the endth child node is after the range boundary, so we must be done.
+ break;
+ } else {
+ start = nodeIndex;
+ }
+ } else {
+ end = (end == start + 1) ? start : nodeIndex;
+ }
+ nodeIndex = Math.floor((start + end) / 2);
+ containerElement.removeChild(workingNode);
+ }
+
+
+ // We've now reached or gone past the boundary of the text range we're interested in
+ // so have identified the node we want
+ boundaryNode = workingNode.nextSibling;
+
+ if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
+ // This is a character data node (text, comment, cdata). The working range is collapsed at the start of
+ // the node containing the text range's boundary, so we move the end of the working range to the
+ // boundary point and measure the length of its text to get the boundary's offset within the node.
+ workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
+
+ var offset;
+
+ if (/[\r\n]/.test(boundaryNode.data)) {
+ /*
+ For the particular case of a boundary within a text node containing rendered line breaks (within a
+ <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in
+ IE. The facts:
+
+ - Each line break is represented as \r in the text node's data/nodeValue properties
+ - Each line break is represented as \r\n in the TextRange's 'text' property
+ - The 'text' property of the TextRange does not contain trailing line breaks
+
+ To get round the problem presented by the final fact above, we can use the fact that TextRange's
+ moveStart() and moveEnd() methods return the actual number of characters moved, which is not
+ necessarily the same as the number of characters it was instructed to move. The simplest approach is
+ to use this to store the characters moved when moving both the start and end of the range to the
+ start of the document body and subtracting the start offset from the end offset (the
+ "move-negative-gazillion" method). However, this is extremely slow when the document is large and
+ the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
+ the end of the document) has the same problem.
+
+ Another approach that works is to use moveStart() to move the start boundary of the range up to the
+ end boundary one character at a time and incrementing a counter with the value returned by the
+ moveStart() call. However, the check for whether the start boundary has reached the end boundary is
+ expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
+ by the location of the range within the document).
+
+ The approach used below is a hybrid of the two methods above. It uses the fact that a string
+ containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
+ be longer than the text of the TextRange, so the start of the range is moved that length initially
+ and then a character at a time to make up for any trailing line breaks not contained in the 'text'
+ property. This has good performance in most situations compared to the previous two methods.
+ */
+ var tempRange = workingRange.duplicate();
+ var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
+
+ offset = tempRange.moveStart("character", rangeLength);
+ while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
+ offset++;
+ tempRange.moveStart("character", 1);
+ }
+ } else {
+ offset = workingRange.text.length;
+ }
+ boundaryPosition = new DomPosition(boundaryNode, offset);
+ } else {
+
+ // If the boundary immediately follows a character data node and this is the end boundary, we should favour
+ // a position within that, and likewise for a start boundary preceding a character data node
+ previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
+ nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
+ if (nextNode && isCharacterDataNode(nextNode)) {
+ boundaryPosition = new DomPosition(nextNode, 0);
+ } else if (previousNode && isCharacterDataNode(previousNode)) {
+ boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
+ } else {
+ boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
+ }
+ }
+
+ // Clean up
+ workingNode.parentNode.removeChild(workingNode);
+
+ return {
+ boundaryPosition: boundaryPosition,
+ nodeInfo: {
+ nodeIndex: nodeIndex,
+ containerElement: containerElement
+ }
+ };
+ };
+
+ // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that
+ // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
+ // (http://code.google.com/p/ierange/)
+ var createBoundaryTextRange = function(boundaryPosition, isStart) {
+ var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
+ var doc = dom.getDocument(boundaryPosition.node);
+ var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
+ var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
+
+ if (nodeIsDataNode) {
+ boundaryNode = boundaryPosition.node;
+ boundaryParent = boundaryNode.parentNode;
+ } else {
+ childNodes = boundaryPosition.node.childNodes;
+ boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
+ boundaryParent = boundaryPosition.node;
+ }
+
+ // Position the range immediately before the node containing the boundary
+ workingNode = doc.createElement("span");
+
+ // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within
+ // the element rather than immediately before or after it
+ workingNode.innerHTML = "&#feff;";
+
+ // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
+ // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
+ if (boundaryNode) {
+ boundaryParent.insertBefore(workingNode, boundaryNode);
+ } else {
+ boundaryParent.appendChild(workingNode);
+ }
+
+ workingRange.moveToElementText(workingNode);
+ workingRange.collapse(!isStart);
+
+ // Clean up
+ boundaryParent.removeChild(workingNode);
+
+ // Move the working range to the text offset, if required
+ if (nodeIsDataNode) {
+ workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
+ }
+
+ return workingRange;
+ };
+
+ /*------------------------------------------------------------------------------------------------------------*/
+
+ // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
+ // prototype
+
+ WrappedTextRange = function(textRange) {
+ this.textRange = textRange;
+ this.refresh();
+ };
+
+ WrappedTextRange.prototype = new DomRange(document);
+
+ WrappedTextRange.prototype.refresh = function() {
+ var start, end, startBoundary;
+
+ // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
+ var rangeContainerElement = getTextRangeContainerElement(this.textRange);
+
+ if (textRangeIsCollapsed(this.textRange)) {
+ end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
+ true).boundaryPosition;
+ } else {
+ startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
+ start = startBoundary.boundaryPosition;
+
+ // An optimization used here is that if the start and end boundaries have the same parent element, the
+ // search scope for the end boundary can be limited to exclude the portion of the element that precedes
+ // the start boundary
+ end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
+ startBoundary.nodeInfo).boundaryPosition;
+ }
+
+ this.setStart(start.node, start.offset);
+ this.setEnd(end.node, end.offset);
+ };
+
+ WrappedTextRange.prototype.getName = function() {
+ return "WrappedTextRange";
+ };
+
+ DomRange.copyComparisonConstants(WrappedTextRange);
+
+ var rangeToTextRange = function(range) {
+ if (range.collapsed) {
+ return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
+ } else {
+ var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
+ var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
+ var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
+ textRange.setEndPoint("StartToStart", startRange);
+ textRange.setEndPoint("EndToEnd", endRange);
+ return textRange;
+ }
+ };
+
+ WrappedTextRange.rangeToTextRange = rangeToTextRange;
+
+ WrappedTextRange.prototype.toTextRange = function() {
+ return rangeToTextRange(this);
+ };
+
+ api.WrappedTextRange = WrappedTextRange;
+
+ // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
+ // implementation to use by default.
+ if (!api.features.implementsDomRange || api.config.preferTextRange) {
+ // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
+ var globalObj = (function() { return this; })();
+ if (typeof globalObj.Range == "undefined") {
+ globalObj.Range = WrappedTextRange;
+ }
+
+ api.createNativeRange = function(doc) {
+ doc = getContentDocument(doc, module, "createNativeRange");
+ return getBody(doc).createTextRange();
+ };
+
+ api.WrappedRange = WrappedTextRange;
+ }
+ }
+
+ api.createRange = function(doc) {
+ doc = getContentDocument(doc, module, "createRange");
+ return new api.WrappedRange(api.createNativeRange(doc));
+ };
+
+ api.createRangyRange = function(doc) {
+ doc = getContentDocument(doc, module, "createRangyRange");
+ return new DomRange(doc);
+ };
+
+ api.createIframeRange = function(iframeEl) {
+ module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
+ return api.createRange(iframeEl);
+ };
+
+ api.createIframeRangyRange = function(iframeEl) {
+ module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
+ return api.createRangyRange(iframeEl);
+ };
+
+ api.addShimListener(function(win) {
+ var doc = win.document;
+ if (typeof doc.createRange == "undefined") {
+ doc.createRange = function() {
+ return api.createRange(doc);
+ };
+ }
+ doc = win = null;
+ });
+ });
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
+ // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
+ api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
+ api.config.checkSelectionRanges = true;
+
+ var BOOLEAN = "boolean";
+ var NUMBER = "number";
+ var dom = api.dom;
+ var util = api.util;
+ var isHostMethod = util.isHostMethod;
+ var DomRange = api.DomRange;
+ var WrappedRange = api.WrappedRange;
+ var DOMException = api.DOMException;
+ var DomPosition = dom.DomPosition;
+ var getNativeSelection;
+ var selectionIsCollapsed;
+ var features = api.features;
+ var CONTROL = "Control";
+ var getDocument = dom.getDocument;
+ var getBody = dom.getBody;
+ var rangesEqual = DomRange.rangesEqual;
+
+
+ // Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
+ // Boolean (true for backwards).
+ function isDirectionBackward(dir) {
+ return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
+ }
+
+ function getWindow(win, methodName) {
+ if (!win) {
+ return window;
+ } else if (dom.isWindow(win)) {
+ return win;
+ } else if (win instanceof WrappedSelection) {
+ return win.win;
+ } else {
+ var doc = dom.getContentDocument(win, module, methodName);
+ return dom.getWindow(doc);
+ }
+ }
+
+ function getWinSelection(winParam) {
+ return getWindow(winParam, "getWinSelection").getSelection();
+ }
+
+ function getDocSelection(winParam) {
+ return getWindow(winParam, "getDocSelection").document.selection;
+ }
+
+ function winSelectionIsBackward(sel) {
+ var backward = false;
+ if (sel.anchorNode) {
+ backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
+ }
+ return backward;
+ }
+
+ // Test for the Range/TextRange and Selection features required
+ // Test for ability to retrieve selection
+ var implementsWinGetSelection = isHostMethod(window, "getSelection"),
+ implementsDocSelection = util.isHostObject(document, "selection");
+
+ features.implementsWinGetSelection = implementsWinGetSelection;
+ features.implementsDocSelection = implementsDocSelection;
+
+ var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
+
+ if (useDocumentSelection) {
+ getNativeSelection = getDocSelection;
+ api.isSelectionValid = function(winParam) {
+ var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
+
+ // Check whether the selection TextRange is actually contained within the correct document
+ return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
+ };
+ } else if (implementsWinGetSelection) {
+ getNativeSelection = getWinSelection;
+ api.isSelectionValid = function() {
+ return true;
+ };
+ } else {
+ module.fail("Neither document.selection or window.getSelection() detected.");
+ }
+
+ api.getNativeSelection = getNativeSelection;
+
+ var testSelection = getNativeSelection();
+ var testRange = api.createNativeRange(document);
+ var body = getBody(document);
+
+ // Obtaining a range from a selection
+ var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
+ ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
+
+ features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
+
+ // Test for existence of native selection extend() method
+ var selectionHasExtend = isHostMethod(testSelection, "extend");
+ features.selectionHasExtend = selectionHasExtend;
+
+ // Test if rangeCount exists
+ var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
+ features.selectionHasRangeCount = selectionHasRangeCount;
+
+ var selectionSupportsMultipleRanges = false;
+ var collapsedNonEditableSelectionsSupported = true;
+
+ var addRangeBackwardToNative = selectionHasExtend ?
+ function(nativeSelection, range) {
+ var doc = DomRange.getRangeDocument(range);
+ var endRange = api.createRange(doc);
+ endRange.collapseToPoint(range.endContainer, range.endOffset);
+ nativeSelection.addRange(getNativeRange(endRange));
+ nativeSelection.extend(range.startContainer, range.startOffset);
+ } : null;
+
+ if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
+ typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
+
+ (function() {
+ // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
+ // performed on the current document's selection. See issue 109.
+
+ // Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
+ // because initialization usually happens when the document loads, but could be a problem for a script that
+ // loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
+ // selection.
+ var sel = window.getSelection();
+ if (sel) {
+ // Store the current selection
+ var originalSelectionRangeCount = sel.rangeCount;
+ var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
+ var originalSelectionRanges = [];
+ var originalSelectionBackward = winSelectionIsBackward(sel);
+ for (var i = 0; i < originalSelectionRangeCount; ++i) {
+ originalSelectionRanges[i] = sel.getRangeAt(i);
+ }
+
+ // Create some test elements
+ var body = getBody(document);
+ var testEl = body.appendChild( document.createElement("div") );
+ testEl.contentEditable = "false";
+ var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
+
+ // Test whether the native selection will allow a collapsed selection within a non-editable element
+ var r1 = document.createRange();
+
+ r1.setStart(textNode, 1);
+ r1.collapse(true);
+ sel.addRange(r1);
+ collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
+ sel.removeAllRanges();
+
+ // Test whether the native selection is capable of supporting multiple ranges.
+ if (!selectionHasMultipleRanges) {
+ // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a
+ // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's
+ // nothing we can do about this while retaining the feature test so we have to resort to a browser
+ // sniff. I'm not happy about it. See
+ // https://code.google.com/p/chromium/issues/detail?id=399791
+ var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /);
+ if (chromeMatch && parseInt(chromeMatch[1]) >= 36) {
+ selectionSupportsMultipleRanges = false;
+ } else {
+ var r2 = r1.cloneRange();
+ r1.setStart(textNode, 0);
+ r2.setEnd(textNode, 3);
+ r2.setStart(textNode, 2);
+ sel.addRange(r1);
+ sel.addRange(r2);
+ selectionSupportsMultipleRanges = (sel.rangeCount == 2);
+ }
+ }
+
+ // Clean up
+ body.removeChild(testEl);
+ sel.removeAllRanges();
+
+ for (i = 0; i < originalSelectionRangeCount; ++i) {
+ if (i == 0 && originalSelectionBackward) {
+ if (addRangeBackwardToNative) {
+ addRangeBackwardToNative(sel, originalSelectionRanges[i]);
+ } else {
+ api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend");
+ sel.addRange(originalSelectionRanges[i]);
+ }
+ } else {
+ sel.addRange(originalSelectionRanges[i]);
+ }
+ }
+ }
+ })();
+ }
+
+ features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
+ features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
+
+ // ControlRanges
+ var implementsControlRange = false, testControlRange;
+
+ if (body && isHostMethod(body, "createControlRange")) {
+ testControlRange = body.createControlRange();
+ if (util.areHostProperties(testControlRange, ["item", "add"])) {
+ implementsControlRange = true;
+ }
+ }
+ features.implementsControlRange = implementsControlRange;
+
+ // Selection collapsedness
+ if (selectionHasAnchorAndFocus) {
+ selectionIsCollapsed = function(sel) {
+ return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
+ };
+ } else {
+ selectionIsCollapsed = function(sel) {
+ return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
+ };
+ }
+
+ function updateAnchorAndFocusFromRange(sel, range, backward) {
+ var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
+ sel.anchorNode = range[anchorPrefix + "Container"];
+ sel.anchorOffset = range[anchorPrefix + "Offset"];
+ sel.focusNode = range[focusPrefix + "Container"];
+ sel.focusOffset = range[focusPrefix + "Offset"];
+ }
+
+ function updateAnchorAndFocusFromNativeSelection(sel) {
+ var nativeSel = sel.nativeSelection;
+ sel.anchorNode = nativeSel.anchorNode;
+ sel.anchorOffset = nativeSel.anchorOffset;
+ sel.focusNode = nativeSel.focusNode;
+ sel.focusOffset = nativeSel.focusOffset;
+ }
+
+ function updateEmptySelection(sel) {
+ sel.anchorNode = sel.focusNode = null;
+ sel.anchorOffset = sel.focusOffset = 0;
+ sel.rangeCount = 0;
+ sel.isCollapsed = true;
+ sel._ranges.length = 0;
+ }
+
+ function getNativeRange(range) {
+ var nativeRange;
+ if (range instanceof DomRange) {
+ nativeRange = api.createNativeRange(range.getDocument());
+ nativeRange.setEnd(range.endContainer, range.endOffset);
+ nativeRange.setStart(range.startContainer, range.startOffset);
+ } else if (range instanceof WrappedRange) {
+ nativeRange = range.nativeRange;
+ } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
+ nativeRange = range;
+ }
+ return nativeRange;
+ }
+
+ function rangeContainsSingleElement(rangeNodes) {
+ if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
+ return false;
+ }
+ for (var i = 1, len = rangeNodes.length; i < len; ++i) {
+ if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function getSingleElementFromRange(range) {
+ var nodes = range.getNodes();
+ if (!rangeContainsSingleElement(nodes)) {
+ throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
+ }
+ return nodes[0];
+ }
+
+ // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
+ function isTextRange(range) {
+ return !!range && typeof range.text != "undefined";
+ }
+
+ function updateFromTextRange(sel, range) {
+ // Create a Range from the selected TextRange
+ var wrappedRange = new WrappedRange(range);
+ sel._ranges = [wrappedRange];
+
+ updateAnchorAndFocusFromRange(sel, wrappedRange, false);
+ sel.rangeCount = 1;
+ sel.isCollapsed = wrappedRange.collapsed;
+ }
+
+ function updateControlSelection(sel) {
+ // Update the wrapped selection based on what's now in the native selection
+ sel._ranges.length = 0;
+ if (sel.docSelection.type == "None") {
+ updateEmptySelection(sel);
+ } else {
+ var controlRange = sel.docSelection.createRange();
+ if (isTextRange(controlRange)) {
+ // This case (where the selection type is "Control" and calling createRange() on the selection returns
+ // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
+ // ControlRange have been removed from the ControlRange and removed from the document.
+ updateFromTextRange(sel, controlRange);
+ } else {
+ sel.rangeCount = controlRange.length;
+ var range, doc = getDocument(controlRange.item(0));
+ for (var i = 0; i < sel.rangeCount; ++i) {
+ range = api.createRange(doc);
+ range.selectNode(controlRange.item(i));
+ sel._ranges.push(range);
+ }
+ sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
+ updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
+ }
+ }
+ }
+
+ function addRangeToControlSelection(sel, range) {
+ var controlRange = sel.docSelection.createRange();
+ var rangeElement = getSingleElementFromRange(range);
+
+ // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
+ // contained by the supplied range
+ var doc = getDocument(controlRange.item(0));
+ var newControlRange = getBody(doc).createControlRange();
+ for (var i = 0, len = controlRange.length; i < len; ++i) {
+ newControlRange.add(controlRange.item(i));
+ }
+ try {
+ newControlRange.add(rangeElement);
+ } catch (ex) {
+ throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
+ }
+ newControlRange.select();
+
+ // Update the wrapped selection based on what's now in the native selection
+ updateControlSelection(sel);
+ }
+
+ var getSelectionRangeAt;
+
+ if (isHostMethod(testSelection, "getRangeAt")) {
+ // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
+ // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
+ // lesson to us all, especially me.
+ getSelectionRangeAt = function(sel, index) {
+ try {
+ return sel.getRangeAt(index);
+ } catch (ex) {
+ return null;
+ }
+ };
+ } else if (selectionHasAnchorAndFocus) {
+ getSelectionRangeAt = function(sel) {
+ var doc = getDocument(sel.anchorNode);
+ var range = api.createRange(doc);
+ range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
+
+ // Handle the case when the selection was selected backwards (from the end to the start in the
+ // document)
+ if (range.collapsed !== this.isCollapsed) {
+ range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
+ }
+
+ return range;
+ };
+ }
+
+ function WrappedSelection(selection, docSelection, win) {
+ this.nativeSelection = selection;
+ this.docSelection = docSelection;
+ this._ranges = [];
+ this.win = win;
+ this.refresh();
+ }
+
+ WrappedSelection.prototype = api.selectionPrototype;
+
+ function deleteProperties(sel) {
+ sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
+ sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
+ sel.detached = true;
+ }
+
+ var cachedRangySelections = [];
+
+ function actOnCachedSelection(win, action) {
+ var i = cachedRangySelections.length, cached, sel;
+ while (i--) {
+ cached = cachedRangySelections[i];
+ sel = cached.selection;
+ if (action == "deleteAll") {
+ deleteProperties(sel);
+ } else if (cached.win == win) {
+ if (action == "delete") {
+ cachedRangySelections.splice(i, 1);
+ return true;
+ } else {
+ return sel;
+ }
+ }
+ }
+ if (action == "deleteAll") {
+ cachedRangySelections.length = 0;
+ }
+ return null;
+ }
+
+ var getSelection = function(win) {
+ // Check if the parameter is a Rangy Selection object
+ if (win && win instanceof WrappedSelection) {
+ win.refresh();
+ return win;
+ }
+
+ win = getWindow(win, "getNativeSelection");
+
+ var sel = actOnCachedSelection(win);
+ var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
+ if (sel) {
+ sel.nativeSelection = nativeSel;
+ sel.docSelection = docSel;
+ sel.refresh();
+ } else {
+ sel = new WrappedSelection(nativeSel, docSel, win);
+ cachedRangySelections.push( { win: win, selection: sel } );
+ }
+ return sel;
+ };
+
+ api.getSelection = getSelection;
+
+ api.getIframeSelection = function(iframeEl) {
+ module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
+ return api.getSelection(dom.getIframeWindow(iframeEl));
+ };
+
+ var selProto = WrappedSelection.prototype;
+
+ function createControlSelection(sel, ranges) {
+ // Ensure that the selection becomes of type "Control"
+ var doc = getDocument(ranges[0].startContainer);
+ var controlRange = getBody(doc).createControlRange();
+ for (var i = 0, el, len = ranges.length; i < len; ++i) {
+ el = getSingleElementFromRange(ranges[i]);
+ try {
+ controlRange.add(el);
+ } catch (ex) {
+ throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
+ }
+ }
+ controlRange.select();
+
+ // Update the wrapped selection based on what's now in the native selection
+ updateControlSelection(sel);
+ }
+
+ // Selecting a range
+ if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
+ selProto.removeAllRanges = function() {
+ this.nativeSelection.removeAllRanges();
+ updateEmptySelection(this);
+ };
+
+ var addRangeBackward = function(sel, range) {
+ addRangeBackwardToNative(sel.nativeSelection, range);
+ sel.refresh();
+ };
+
+ if (selectionHasRangeCount) {
+ selProto.addRange = function(range, direction) {
+ if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
+ addRangeToControlSelection(this, range);
+ } else {
+ if (isDirectionBackward(direction) && selectionHasExtend) {
+ addRangeBackward(this, range);
+ } else {
+ var previousRangeCount;
+ if (selectionSupportsMultipleRanges) {
+ previousRangeCount = this.rangeCount;
+ } else {
+ this.removeAllRanges();
+ previousRangeCount = 0;
+ }
+ // Clone the native range so that changing the selected range does not affect the selection.
+ // This is contrary to the spec but is the only way to achieve consistency between browsers. See
+ // issue 80.
+ this.nativeSelection.addRange(getNativeRange(range).cloneRange());
+
+ // Check whether adding the range was successful
+ this.rangeCount = this.nativeSelection.rangeCount;
+
+ if (this.rangeCount == previousRangeCount + 1) {
+ // The range was added successfully
+
+ // Check whether the range that we added to the selection is reflected in the last range extracted from
+ // the selection
+ if (api.config.checkSelectionRanges) {
+ var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
+ if (nativeRange && !rangesEqual(nativeRange, range)) {
+ // Happens in WebKit with, for example, a selection placed at the start of a text node
+ range = new WrappedRange(nativeRange);
+ }
+ }
+ this._ranges[this.rangeCount - 1] = range;
+ updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
+ this.isCollapsed = selectionIsCollapsed(this);
+ } else {
+ // The range was not added successfully. The simplest thing is to refresh
+ this.refresh();
+ }
+ }
+ }
+ };
+ } else {
+ selProto.addRange = function(range, direction) {
+ if (isDirectionBackward(direction) && selectionHasExtend) {
+ addRangeBackward(this, range);
+ } else {
+ this.nativeSelection.addRange(getNativeRange(range));
+ this.refresh();
+ }
+ };
+ }
+
+ selProto.setRanges = function(ranges) {
+ if (implementsControlRange && implementsDocSelection && ranges.length > 1) {
+ createControlSelection(this, ranges);
+ } else {
+ this.removeAllRanges();
+ for (var i = 0, len = ranges.length; i < len; ++i) {
+ this.addRange(ranges[i]);
+ }
+ }
+ };
+ } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
+ implementsControlRange && useDocumentSelection) {
+
+ selProto.removeAllRanges = function() {
+ // Added try/catch as fix for issue #21
+ try {
+ this.docSelection.empty();
+
+ // Check for empty() not working (issue #24)
+ if (this.docSelection.type != "None") {
+ // Work around failure to empty a control selection by instead selecting a TextRange and then
+ // calling empty()
+ var doc;
+ if (this.anchorNode) {
+ doc = getDocument(this.anchorNode);
+ } else if (this.docSelection.type == CONTROL) {
+ var controlRange = this.docSelection.createRange();
+ if (controlRange.length) {
+ doc = getDocument( controlRange.item(0) );
+ }
+ }
+ if (doc) {
+ var textRange = getBody(doc).createTextRange();
+ textRange.select();
+ this.docSelection.empty();
+ }
+ }
+ } catch(ex) {}
+ updateEmptySelection(this);
+ };
+
+ selProto.addRange = function(range) {
+ if (this.docSelection.type == CONTROL) {
+ addRangeToControlSelection(this, range);
+ } else {
+ api.WrappedTextRange.rangeToTextRange(range).select();
+ this._ranges[0] = range;
+ this.rangeCount = 1;
+ this.isCollapsed = this._ranges[0].collapsed;
+ updateAnchorAndFocusFromRange(this, range, false);
+ }
+ };
+
+ selProto.setRanges = function(ranges) {
+ this.removeAllRanges();
+ var rangeCount = ranges.length;
+ if (rangeCount > 1) {
+ createControlSelection(this, ranges);
+ } else if (rangeCount) {
+ this.addRange(ranges[0]);
+ }
+ };
+ } else {
+ module.fail("No means of selecting a Range or TextRange was found");
+ return false;
+ }
+
+ selProto.getRangeAt = function(index) {
+ if (index < 0 || index >= this.rangeCount) {
+ throw new DOMException("INDEX_SIZE_ERR");
+ } else {
+ // Clone the range to preserve selection-range independence. See issue 80.
+ return this._ranges[index].cloneRange();
+ }
+ };
+
+ var refreshSelection;
+
+ if (useDocumentSelection) {
+ refreshSelection = function(sel) {
+ var range;
+ if (api.isSelectionValid(sel.win)) {
+ range = sel.docSelection.createRange();
+ } else {
+ range = getBody(sel.win.document).createTextRange();
+ range.collapse(true);
+ }
+
+ if (sel.docSelection.type == CONTROL) {
+ updateControlSelection(sel);
+ } else if (isTextRange(range)) {
+ updateFromTextRange(sel, range);
+ } else {
+ updateEmptySelection(sel);
+ }
+ };
+ } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
+ refreshSelection = function(sel) {
+ if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
+ updateControlSelection(sel);
+ } else {
+ sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
+ if (sel.rangeCount) {
+ for (var i = 0, len = sel.rangeCount; i < len; ++i) {
+ sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
+ }
+ updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
+ sel.isCollapsed = selectionIsCollapsed(sel);
+ } else {
+ updateEmptySelection(sel);
+ }
+ }
+ };
+ } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
+ refreshSelection = function(sel) {
+ var range, nativeSel = sel.nativeSelection;
+ if (nativeSel.anchorNode) {
+ range = getSelectionRangeAt(nativeSel, 0);
+ sel._ranges = [range];
+ sel.rangeCount = 1;
+ updateAnchorAndFocusFromNativeSelection(sel);
+ sel.isCollapsed = selectionIsCollapsed(sel);
+ } else {
+ updateEmptySelection(sel);
+ }
+ };
+ } else {
+ module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
+ return false;
+ }
+
+ selProto.refresh = function(checkForChanges) {
+ var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
+ var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
+
+ refreshSelection(this);
+ if (checkForChanges) {
+ // Check the range count first
+ var i = oldRanges.length;
+ if (i != this._ranges.length) {
+ return true;
+ }
+
+ // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
+ // ranges after this
+ if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
+ return true;
+ }
+
+ // Finally, compare each range in turn
+ while (i--) {
+ if (!rangesEqual(oldRanges[i], this._ranges[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ // Removal of a single range
+ var removeRangeManually = function(sel, range) {
+ var ranges = sel.getAllRanges();
+ sel.removeAllRanges();
+ for (var i = 0, len = ranges.length; i < len; ++i) {
+ if (!rangesEqual(range, ranges[i])) {
+ sel.addRange(ranges[i]);
+ }
+ }
+ if (!sel.rangeCount) {
+ updateEmptySelection(sel);
+ }
+ };
+
+ if (implementsControlRange && implementsDocSelection) {
+ selProto.removeRange = function(range) {
+ if (this.docSelection.type == CONTROL) {
+ var controlRange = this.docSelection.createRange();
+ var rangeElement = getSingleElementFromRange(range);
+
+ // Create a new ControlRange containing all the elements in the selected ControlRange minus the
+ // element contained by the supplied range
+ var doc = getDocument(controlRange.item(0));
+ var newControlRange = getBody(doc).createControlRange();
+ var el, removed = false;
+ for (var i = 0, len = controlRange.length; i < len; ++i) {
+ el = controlRange.item(i);
+ if (el !== rangeElement || removed) {
+ newControlRange.add(controlRange.item(i));
+ } else {
+ removed = true;
+ }
+ }
+ newControlRange.select();
+
+ // Update the wrapped selection based on what's now in the native selection
+ updateControlSelection(this);
+ } else {
+ removeRangeManually(this, range);
+ }
+ };
+ } else {
+ selProto.removeRange = function(range) {
+ removeRangeManually(this, range);
+ };
+ }
+
+ // Detecting if a selection is backward
+ var selectionIsBackward;
+ if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
+ selectionIsBackward = winSelectionIsBackward;
+
+ selProto.isBackward = function() {
+ return selectionIsBackward(this);
+ };
+ } else {
+ selectionIsBackward = selProto.isBackward = function() {
+ return false;
+ };
+ }
+
+ // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
+ selProto.isBackwards = selProto.isBackward;
+
+ // Selection stringifier
+ // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
+ // The current spec does not yet define this method.
+ selProto.toString = function() {
+ var rangeTexts = [];
+ for (var i = 0, len = this.rangeCount; i < len; ++i) {
+ rangeTexts[i] = "" + this._ranges[i];
+ }
+ return rangeTexts.join("");
+ };
+
+ function assertNodeInSameDocument(sel, node) {
+ if (sel.win.document != getDocument(node)) {
+ throw new DOMException("WRONG_DOCUMENT_ERR");
+ }
+ }
+
+ // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
+ selProto.collapse = function(node, offset) {
+ assertNodeInSameDocument(this, node);
+ var range = api.createRange(node);
+ range.collapseToPoint(node, offset);
+ this.setSingleRange(range);
+ this.isCollapsed = true;
+ };
+
+ selProto.collapseToStart = function() {
+ if (this.rangeCount) {
+ var range = this._ranges[0];
+ this.collapse(range.startContainer, range.startOffset);
+ } else {
+ throw new DOMException("INVALID_STATE_ERR");
+ }
+ };
+
+ selProto.collapseToEnd = function() {
+ if (this.rangeCount) {
+ var range = this._ranges[this.rangeCount - 1];
+ this.collapse(range.endContainer, range.endOffset);
+ } else {
+ throw new DOMException("INVALID_STATE_ERR");
+ }
+ };
+
+ // The spec is very specific on how selectAllChildren should be implemented so the native implementation is
+ // never used by Rangy.
+ selProto.selectAllChildren = function(node) {
+ assertNodeInSameDocument(this, node);
+ var range = api.createRange(node);
+ range.selectNodeContents(node);
+ this.setSingleRange(range);
+ };
+
+ selProto.deleteFromDocument = function() {
+ // Sepcial behaviour required for IE's control selections
+ if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
+ var controlRange = this.docSelection.createRange();
+ var element;
+ while (controlRange.length) {
+ element = controlRange.item(0);
+ controlRange.remove(element);
+ element.parentNode.removeChild(element);
+ }
+ this.refresh();
+ } else if (this.rangeCount) {
+ var ranges = this.getAllRanges();
+ if (ranges.length) {
+ this.removeAllRanges();
+ for (var i = 0, len = ranges.length; i < len; ++i) {
+ ranges[i].deleteContents();
+ }
+ // The spec says nothing about what the selection should contain after calling deleteContents on each
+ // range. Firefox moves the selection to where the final selected range was, so we emulate that
+ this.addRange(ranges[len - 1]);
+ }
+ }
+ };
+
+ // The following are non-standard extensions
+ selProto.eachRange = function(func, returnValue) {
+ for (var i = 0, len = this._ranges.length; i < len; ++i) {
+ if ( func( this.getRangeAt(i) ) ) {
+ return returnValue;
+ }
+ }
+ };
+
+ selProto.getAllRanges = function() {
+ var ranges = [];
+ this.eachRange(function(range) {
+ ranges.push(range);
+ });
+ return ranges;
+ };
+
+ selProto.setSingleRange = function(range, direction) {
+ this.removeAllRanges();
+ this.addRange(range, direction);
+ };
+
+ selProto.callMethodOnEachRange = function(methodName, params) {
+ var results = [];
+ this.eachRange( function(range) {
+ results.push( range[methodName].apply(range, params) );
+ } );
+ return results;
+ };
+
+ function createStartOrEndSetter(isStart) {
+ return function(node, offset) {
+ var range;
+ if (this.rangeCount) {
+ range = this.getRangeAt(0);
+ range["set" + (isStart ? "Start" : "End")](node, offset);
+ } else {
+ range = api.createRange(this.win.document);
+ range.setStartAndEnd(node, offset);
+ }
+ this.setSingleRange(range, this.isBackward());
+ };
+ }
+
+ selProto.setStart = createStartOrEndSetter(true);
+ selProto.setEnd = createStartOrEndSetter(false);
+
+ // Add select() method to Range prototype. Any existing selection will be removed.
+ api.rangePrototype.select = function(direction) {
+ getSelection( this.getDocument() ).setSingleRange(this, direction);
+ };
+
+ selProto.changeEachRange = function(func) {
+ var ranges = [];
+ var backward = this.isBackward();
+
+ this.eachRange(function(range) {
+ func(range);
+ ranges.push(range);
+ });
+
+ this.removeAllRanges();
+ if (backward && ranges.length == 1) {
+ this.addRange(ranges[0], "backward");
+ } else {
+ this.setRanges(ranges);
+ }
+ };
+
+ selProto.containsNode = function(node, allowPartial) {
+ return this.eachRange( function(range) {
+ return range.containsNode(node, allowPartial);
+ }, true ) || false;
+ };
+
+ selProto.getBookmark = function(containerNode) {
+ return {
+ backward: this.isBackward(),
+ rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
+ };
+ };
+
+ selProto.moveToBookmark = function(bookmark) {
+ var selRanges = [];
+ for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
+ range = api.createRange(this.win);
+ range.moveToBookmark(rangeBookmark);
+ selRanges.push(range);
+ }
+ if (bookmark.backward) {
+ this.setSingleRange(selRanges[0], "backward");
+ } else {
+ this.setRanges(selRanges);
+ }
+ };
+
+ selProto.toHtml = function() {
+ var rangeHtmls = [];
+ this.eachRange(function(range) {
+ rangeHtmls.push( DomRange.toHtml(range) );
+ });
+ return rangeHtmls.join("");
+ };
+
+ if (features.implementsTextRange) {
+ selProto.getNativeTextRange = function() {
+ var sel, textRange;
+ if ( (sel = this.docSelection) ) {
+ var range = sel.createRange();
+ if (isTextRange(range)) {
+ return range;
+ } else {
+ throw module.createError("getNativeTextRange: selection is a control selection");
+ }
+ } else if (this.rangeCount > 0) {
+ return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
+ } else {
+ throw module.createError("getNativeTextRange: selection contains no range");
+ }
+ };
+ }
+
+ function inspect(sel) {
+ var rangeInspects = [];
+ var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
+ var focus = new DomPosition(sel.focusNode, sel.focusOffset);
+ var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
+
+ if (typeof sel.rangeCount != "undefined") {
+ for (var i = 0, len = sel.rangeCount; i < len; ++i) {
+ rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
+ }
+ }
+ return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
+ ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
+ }
+
+ selProto.getName = function() {
+ return "WrappedSelection";
+ };
+
+ selProto.inspect = function() {
+ return inspect(this);
+ };
+
+ selProto.detach = function() {
+ actOnCachedSelection(this.win, "delete");
+ deleteProperties(this);
+ };
+
+ WrappedSelection.detachAll = function() {
+ actOnCachedSelection(null, "deleteAll");
+ };
+
+ WrappedSelection.inspect = inspect;
+ WrappedSelection.isDirectionBackward = isDirectionBackward;
+
+ api.Selection = WrappedSelection;
+
+ api.selectionPrototype = selProto;
+
+ api.addShimListener(function(win) {
+ if (typeof win.getSelection == "undefined") {
+ win.getSelection = function() {
+ return getSelection(win);
+ };
+ }
+ win = null;
+ });
+ });
+
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ return api;
+}, this);;/**
+ * Selection save and restore module for Rangy.
+ * Saves and restores user selections using marker invisible elements in the DOM.
+ *
+ * Part of Rangy, a cross-browser JavaScript range and selection library
+ * http://code.google.com/p/rangy/
+ *
+ * Depends on Rangy core.
+ *
+ * Copyright 2014, Tim Down
+ * Licensed under the MIT license.
+ * Version: 1.3alpha.20140804
+ * Build date: 4 August 2014
+ */
+(function(factory, global) {
+ if (typeof define == "function" && define.amd) {
+ // AMD. Register as an anonymous module with a dependency on Rangy.
+ define(["rangy"], factory);
+ /*
+ } else if (typeof exports == "object") {
+ // Node/CommonJS style for Browserify
+ module.exports = factory;
+ */
+ } else {
+ // No AMD or CommonJS support so we use the rangy global variable
+ factory(global.rangy);
+ }
+})(function(rangy) {
+ rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
+ var dom = api.dom;
+
+ var markerTextChar = "\ufeff";
+
+ function gEBI(id, doc) {
+ return (doc || document).getElementById(id);
+ }
+
+ function insertRangeBoundaryMarker(range, atStart) {
+ var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
+ var markerEl;
+ var doc = dom.getDocument(range.startContainer);
+
+ // Clone the Range and collapse to the appropriate boundary point
+ var boundaryRange = range.cloneRange();
+ boundaryRange.collapse(atStart);
+
+ // Create the marker element containing a single invisible character using DOM methods and insert it
+ markerEl = doc.createElement("span");
+ markerEl.id = markerId;
+ markerEl.style.lineHeight = "0";
+ markerEl.style.display = "none";
+ markerEl.className = "rangySelectionBoundary";
+ markerEl.appendChild(doc.createTextNode(markerTextChar));
+
+ boundaryRange.insertNode(markerEl);
+ return markerEl;
+ }
+
+ function setRangeBoundary(doc, range, markerId, atStart) {
+ var markerEl = gEBI(markerId, doc);
+ if (markerEl) {
+ range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
+ markerEl.parentNode.removeChild(markerEl);
+ } else {
+ module.warn("Marker element has been removed. Cannot restore selection.");
+ }
+ }
+
+ function compareRanges(r1, r2) {
+ return r2.compareBoundaryPoints(r1.START_TO_START, r1);
+ }
+
+ function saveRange(range, backward) {
+ var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
+
+ if (range.collapsed) {
+ endEl = insertRangeBoundaryMarker(range, false);
+ return {
+ document: doc,
+ markerId: endEl.id,
+ collapsed: true
+ };
+ } else {
+ endEl = insertRangeBoundaryMarker(range, false);
+ startEl = insertRangeBoundaryMarker(range, true);
+
+ return {
+ document: doc,
+ startMarkerId: startEl.id,
+ endMarkerId: endEl.id,
+ collapsed: false,
+ backward: backward,
+ toString: function() {
+ return "original text: '" + text + "', new text: '" + range.toString() + "'";
+ }
+ };
+ }
+ }
+
+ function restoreRange(rangeInfo, normalize) {
+ var doc = rangeInfo.document;
+ if (typeof normalize == "undefined") {
+ normalize = true;
+ }
+ var range = api.createRange(doc);
+ if (rangeInfo.collapsed) {
+ var markerEl = gEBI(rangeInfo.markerId, doc);
+ if (markerEl) {
+ markerEl.style.display = "inline";
+ var previousNode = markerEl.previousSibling;
+
+ // Workaround for issue 17
+ if (previousNode && previousNode.nodeType == 3) {
+ markerEl.parentNode.removeChild(markerEl);
+ range.collapseToPoint(previousNode, previousNode.length);
+ } else {
+ range.collapseBefore(markerEl);
+ markerEl.parentNode.removeChild(markerEl);
+ }
+ } else {
+ module.warn("Marker element has been removed. Cannot restore selection.");
+ }
+ } else {
+ setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
+ setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
+ }
+
+ if (normalize) {
+ range.normalizeBoundaries();
+ }
+
+ return range;
+ }
+
+ function saveRanges(ranges, backward) {
+ var rangeInfos = [], range, doc;
+
+ // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
+ ranges = ranges.slice(0);
+ ranges.sort(compareRanges);
+
+ for (var i = 0, len = ranges.length; i < len; ++i) {
+ rangeInfos[i] = saveRange(ranges[i], backward);
+ }
+
+ // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
+ // between its markers
+ for (i = len - 1; i >= 0; --i) {
+ range = ranges[i];
+ doc = api.DomRange.getRangeDocument(range);
+ if (range.collapsed) {
+ range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
+ } else {
+ range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
+ range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
+ }
+ }
+
+ return rangeInfos;
+ }
+
+ function saveSelection(win) {
+ if (!api.isSelectionValid(win)) {
+ module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
+ return null;
+ }
+ var sel = api.getSelection(win);
+ var ranges = sel.getAllRanges();
+ var backward = (ranges.length == 1 && sel.isBackward());
+
+ var rangeInfos = saveRanges(ranges, backward);
+
+ // Ensure current selection is unaffected
+ if (backward) {
+ sel.setSingleRange(ranges[0], "backward");
+ } else {
+ sel.setRanges(ranges);
+ }
+
+ return {
+ win: win,
+ rangeInfos: rangeInfos,
+ restored: false
+ };
+ }
+
+ function restoreRanges(rangeInfos) {
+ var ranges = [];
+
+ // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
+ // normalization affecting previously restored ranges.
+ var rangeCount = rangeInfos.length;
+
+ for (var i = rangeCount - 1; i >= 0; i--) {
+ ranges[i] = restoreRange(rangeInfos[i], true);
+ }
+
+ return ranges;
+ }
+
+ function restoreSelection(savedSelection, preserveDirection) {
+ if (!savedSelection.restored) {
+ var rangeInfos = savedSelection.rangeInfos;
+ var sel = api.getSelection(savedSelection.win);
+ var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
+
+ if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
+ sel.removeAllRanges();
+ sel.addRange(ranges[0], true);
+ } else {
+ sel.setRanges(ranges);
+ }
+
+ savedSelection.restored = true;
+ }
+ }
+
+ function removeMarkerElement(doc, markerId) {
+ var markerEl = gEBI(markerId, doc);
+ if (markerEl) {
+ markerEl.parentNode.removeChild(markerEl);
+ }
+ }
+
+ function removeMarkers(savedSelection) {
+ var rangeInfos = savedSelection.rangeInfos;
+ for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
+ rangeInfo = rangeInfos[i];
+ if (rangeInfo.collapsed) {
+ removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
+ } else {
+ removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
+ removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
+ }
+ }
+ }
+
+ api.util.extend(api, {
+ saveRange: saveRange,
+ restoreRange: restoreRange,
+ saveRanges: saveRanges,
+ restoreRanges: restoreRanges,
+ saveSelection: saveSelection,
+ restoreSelection: restoreSelection,
+ removeMarkerElement: removeMarkerElement,
+ removeMarkers: removeMarkers
+ });
+ });
+
+}, this);;/*
+ Base.js, version 1.1a
+ Copyright 2006-2010, Dean Edwards
+ License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+var Base = function() {
+ // dummy
+};
+
+Base.extend = function(_instance, _static) { // subclass
+ var extend = Base.prototype.extend;
+
+ // build the prototype
+ Base._prototyping = true;
+ var proto = new this;
+ extend.call(proto, _instance);
+ proto.base = function() {
+ // call this method from any other method to invoke that method's ancestor
+ };
+ delete Base._prototyping;
+
+ // create the wrapper for the constructor function
+ //var constructor = proto.constructor.valueOf(); //-dean
+ var constructor = proto.constructor;
+ var klass = proto.constructor = function() {
+ if (!Base._prototyping) {
+ if (this._constructing || this.constructor == klass) { // instantiation
+ this._constructing = true;
+ constructor.apply(this, arguments);
+ delete this._constructing;
+ } else if (arguments[0] != null) { // casting
+ return (arguments[0].extend || extend).call(arguments[0], proto);
+ }
+ }
+ };
+
+ // build the class interface
+ klass.ancestor = this;
+ klass.extend = this.extend;
+ klass.forEach = this.forEach;
+ klass.implement = this.implement;
+ klass.prototype = proto;
+ klass.toString = this.toString;
+ klass.valueOf = function(type) {
+ //return (type == "object") ? klass : constructor; //-dean
+ return (type == "object") ? klass : constructor.valueOf();
+ };
+ extend.call(klass, _static);
+ // class initialisation
+ if (typeof klass.init == "function") klass.init();
+ return klass;
+};
+
+Base.prototype = {
+ extend: function(source, value) {
+ if (arguments.length > 1) { // extending with a name/value pair
+ var ancestor = this[source];
+ if (ancestor && (typeof value == "function") && // overriding a method?
+ // the valueOf() comparison is to avoid circular references
+ (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
+ /\bbase\b/.test(value)) {
+ // get the underlying method
+ var method = value.valueOf();
+ // override
+ value = function() {
+ var previous = this.base || Base.prototype.base;
+ this.base = ancestor;
+ var returnValue = method.apply(this, arguments);
+ this.base = previous;
+ return returnValue;
+ };
+ // point to the underlying method
+ value.valueOf = function(type) {
+ return (type == "object") ? value : method;
+ };
+ value.toString = Base.toString;
+ }
+ this[source] = value;
+ } else if (source) { // extending with an object literal
+ var extend = Base.prototype.extend;
+ // if this object has a customised extend method then use it
+ if (!Base._prototyping && typeof this != "function") {
+ extend = this.extend || extend;
+ }
+ var proto = {toSource: null};
+ // do the "toString" and other methods manually
+ var hidden = ["constructor", "toString", "valueOf"];
+ // if we are prototyping then include the constructor
+ var i = Base._prototyping ? 0 : 1;
+ while (key = hidden[i++]) {
+ if (source[key] != proto[key]) {
+ extend.call(this, key, source[key]);
+
+ }
+ }
+ // copy each of the source object's properties to this object
+ for (var key in source) {
+ if (!proto[key]) extend.call(this, key, source[key]);
+ }
+ }
+ return this;
+ }
+};
+
+// initialise
+Base = Base.extend({
+ constructor: function() {
+ this.extend(arguments[0]);
+ }
+}, {
+ ancestor: Object,
+ version: "1.1",
+
+ forEach: function(object, block, context) {
+ for (var key in object) {
+ if (this.prototype[key] === undefined) {
+ block.call(context, object[key], key, object);
+ }
+ }
+ },
+
+ implement: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ if (typeof arguments[i] == "function") {
+ // if it's a function, call it
+ arguments[i](this.prototype);
+ } else {
+ // add the interface using the extend method
+ this.prototype.extend(arguments[i]);
+ }
+ }
+ return this;
+ },
+
+ toString: function() {
+ return String(this.valueOf());
+ }
+});;/**
+ * Detect browser support for specific features
+ */
+wysihtml5.browser = (function() {
+ var userAgent = navigator.userAgent,
+ testElement = document.createElement("div"),
+ // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
+ isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1,
+ isWebKit = userAgent.indexOf("AppleWebKit/") !== -1,
+ isChrome = userAgent.indexOf("Chrome/") !== -1,
+ isOpera = userAgent.indexOf("Opera/") !== -1;
+
+ function iosVersion(userAgent) {
+ return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1];
+ }
+
+ function androidVersion(userAgent) {
+ return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1];
+ }
+
+ function isIE(version, equation) {
+ var rv = -1,
+ re;
+
+ if (navigator.appName == 'Microsoft Internet Explorer') {
+ re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
+ } else if (navigator.appName == 'Netscape') {
+ re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
+ }
+
+ if (re && re.exec(navigator.userAgent) != null) {
+ rv = parseFloat(RegExp.$1);
+ }
+
+ if (rv === -1) { return false; }
+ if (!version) { return true; }
+ if (!equation) { return version === rv; }
+ if (equation === "<") { return version < rv; }
+ if (equation === ">") { return version > rv; }
+ if (equation === "<=") { return version <= rv; }
+ if (equation === ">=") { return version >= rv; }
+ }
+
+ return {
+ // Static variable needed, publicly accessible, to be able override it in unit tests
+ USER_AGENT: userAgent,
+
+ /**
+ * Exclude browsers that are not capable of displaying and handling
+ * contentEditable as desired:
+ * - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
+ * - IE < 8 create invalid markup and crash randomly from time to time
+ *
+ * @return {Boolean}
+ */
+ supported: function() {
+ var userAgent = this.USER_AGENT.toLowerCase(),
+ // Essential for making html elements editable
+ hasContentEditableSupport = "contentEditable" in testElement,
+ // Following methods are needed in order to interact with the contentEditable area
+ hasEditingApiSupport = document.execCommand && document.queryCommandSupported && document.queryCommandState,
+ // 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) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
+ return hasContentEditableSupport
+ && hasEditingApiSupport
+ && hasQuerySelectorSupport
+ && !isIncompatibleMobileBrowser;
+ },
+
+ isTouchDevice: function() {
+ return this.supportsEvent("touchmove");
+ },
+
+ isIos: function() {
+ return (/ipad|iphone|ipod/i).test(this.USER_AGENT);
+ },
+
+ isAndroid: function() {
+ return this.USER_AGENT.indexOf("Android") !== -1;
+ },
+
+ /**
+ * Whether the browser supports sandboxed iframes
+ * Currently only IE 6+ offers such feature <iframe security="restricted">
+ *
+ * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx
+ * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx
+ *
+ * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage)
+ */
+ supportsSandboxedIframes: function() {
+ return isIE();
+ },
+
+ /**
+ * IE6+7 throw a mixed content warning when the src of an iframe
+ * is empty/unset or about:blank
+ * window.querySelector is implemented as of IE8
+ */
+ throwsMixedContentWarningWhenIframeSrcIsEmpty: function() {
+ return !("querySelector" in document);
+ },
+
+ /**
+ * Whether the caret is correctly displayed in contentEditable elements
+ * Firefox sometimes shows a huge caret in the beginning after focusing
+ */
+ displaysCaretInEmptyContentEditableCorrectly: function() {
+ return isIE();
+ },
+
+ /**
+ * Opera and IE are the only browsers who offer the css value
+ * in the original unit, thx to the currentStyle object
+ * All other browsers provide the computed style in px via window.getComputedStyle
+ */
+ 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 <br> when pressing enter in a contentEditable element
+ */
+ insertsLineBreaksOnReturn: function() {
+ return isGecko;
+ },
+
+ supportsPlaceholderAttributeOn: function(element) {
+ return "placeholder" in element;
+ },
+
+ supportsEvent: function(eventName) {
+ return "on" + eventName in testElement || (function() {
+ testElement.setAttribute("on" + eventName, "return;");
+ return typeof(testElement["on" + eventName]) === "function";
+ })();
+ },
+
+ /**
+ * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe
+ */
+ supportsEventsInIframeCorrectly: function() {
+ return !isOpera;
+ },
+
+ /**
+ * Everything below IE9 doesn't know how to treat HTML5 tags
+ *
+ * @param {Object} context The document object on which to check HTML5 support
+ *
+ * @example
+ * wysihtml5.browser.supportsHTML5Tags(document);
+ */
+ supportsHTML5Tags: function(context) {
+ var element = context.createElement("div"),
+ html5 = "<article>foo</article>";
+ element.innerHTML = html5;
+ return element.innerHTML.toLowerCase() === html5;
+ },
+
+ /**
+ * Checks whether a document supports a certain queryCommand
+ * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree
+ * in oder to report correct results
+ *
+ * @param {Object} doc Document object on which to check for a query command
+ * @param {String} command The query command to check for
+ * @return {Boolean}
+ *
+ * @example
+ * wysihtml5.browser.supportsCommand(document, "bold");
+ */
+ supportsCommand: (function() {
+ // Following commands are supported but contain bugs in some browsers
+ var buggyCommands = {
+ // formatBlock fails with some tags (eg. <blockquote>)
+ "formatBlock": isIE(10, "<="),
+ // 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(),
+ "insertOrderedList": isIE()
+ };
+
+ // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
+ var supported = {
+ "insertHTML": isGecko
+ };
+
+ return function(doc, command) {
+ var isBuggy = buggyCommands[command];
+ if (!isBuggy) {
+ // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
+ try {
+ return doc.queryCommandSupported(command);
+ } catch(e1) {}
+
+ try {
+ return doc.queryCommandEnabled(command);
+ } catch(e2) {
+ return !!supported[command];
+ }
+ }
+ return false;
+ };
+ })(),
+
+ /**
+ * IE: URLs starting with:
+ * www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://,
+ * nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url:
+ * will automatically be auto-linked when either the user inserts them via copy&paste or presses the
+ * space bar when the caret is directly after such an url.
+ * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll
+ * (related blog post on msdn
+ * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx).
+ */
+ doesAutoLinkingInContentEditable: function() {
+ return isIE();
+ },
+
+ /**
+ * As stated above, IE auto links urls typed into contentEditable elements
+ * Since IE9 it's possible to prevent this behavior
+ */
+ canDisableAutoLinking: function() {
+ return this.supportsCommand(document, "AutoUrlDetect");
+ },
+
+ /**
+ * IE leaves an empty paragraph in the contentEditable element after clearing it
+ * Chrome/Safari sometimes an empty <div>
+ */
+ clearsContentEditableCorrectly: function() {
+ return isGecko || isOpera || isWebKit;
+ },
+
+ /**
+ * IE gives wrong results for getAttribute
+ */
+ supportsGetAttributeCorrectly: function() {
+ var td = document.createElement("td");
+ return td.getAttribute("rowspan") != "1";
+ },
+
+ /**
+ * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them.
+ * Chrome and Safari both don't support this
+ */
+ canSelectImagesInContentEditable: function() {
+ return isGecko || isIE() || isOpera;
+ },
+
+ /**
+ * All browsers except Safari and Chrome automatically scroll the range/caret position into view
+ */
+ autoScrollsToCaret: function() {
+ return !isWebKit;
+ },
+
+ /**
+ * Check whether the browser automatically closes tags that don't need to be opened
+ */
+ autoClosesUnclosedTags: function() {
+ var clonedTestElement = testElement.cloneNode(false),
+ returnValue,
+ innerHTML;
+
+ clonedTestElement.innerHTML = "<p><div></div>";
+ innerHTML = clonedTestElement.innerHTML.toLowerCase();
+ returnValue = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
+
+ // Cache result by overwriting current function
+ this.autoClosesUnclosedTags = function() { return returnValue; };
+
+ return returnValue;
+ },
+
+ /**
+ * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists
+ */
+ supportsNativeGetElementsByClassName: function() {
+ return String(document.getElementsByClassName).indexOf("[native code]") !== -1;
+ },
+
+ /**
+ * As of now (19.04.2011) only supported by Firefox 4 and Chrome
+ * See https://developer.mozilla.org/en/DOM/Selection/modify
+ */
+ supportsSelectionModify: function() {
+ return "getSelection" in window && "modify" in window.getSelection();
+ },
+
+ /**
+ * Opera needs a white space after a <br> in order to position the caret correctly
+ */
+ needsSpaceAfterLineBreak: function() {
+ return isOpera;
+ },
+
+ /**
+ * Whether the browser supports the speech api on the given element
+ * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
+ *
+ * @example
+ * var input = document.createElement("input");
+ * if (wysihtml5.browser.supportsSpeechApiOn(input)) {
+ * // ...
+ * }
+ */
+ supportsSpeechApiOn: function(input) {
+ var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [undefined, 0];
+ return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input);
+ },
+
+ /**
+ * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest
+ * See https://connect.microsoft.com/ie/feedback/details/650112
+ * or try the POC http://tifftiff.de/ie9_crash/
+ */
+ crashesWhenDefineProperty: function(property) {
+ return isIE(9) && (property === "XMLHttpRequest" || property === "XDomainRequest");
+ },
+
+ /**
+ * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
+ */
+ doesAsyncFocus: function() {
+ return isIE();
+ },
+
+ /**
+ * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document
+ */
+ hasProblemsSettingCaretAfterImg: function() {
+ return isIE();
+ },
+
+ 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();
+ },
+
+ /**
+ * Chrome + Safari create invalid nested markup after paste
+ *
+ * <p>
+ * foo
+ * <p>bar</p> <!-- BOO! -->
+ * </p>
+ */
+ createsNestedInvalidMarkupAfterPaste: function() {
+ return isWebKit;
+ },
+
+ supportsMutationEvents: function() {
+ return ("MutationEvent" in window);
+ },
+
+ /**
+ IE (at least up to 11) does not support clipboardData on event.
+ It is on window but cannot return text/html
+ Should actually check for clipboardData on paste event, but cannot in firefox
+ */
+ supportsModenPaste: function () {
+ return !("clipboardData" in window);
+ }
+ };
+})();
+;wysihtml5.lang.array = function(arr) {
+ return {
+ /**
+ * Check whether a given object exists in an array
+ *
+ * @example
+ * wysihtml5.lang.array([1, 2]).contains(1);
+ * // => true
+ *
+ * Can be used to match array with array. If intersection is found true is returned
+ */
+ contains: function(needle) {
+ if (Array.isArray(needle)) {
+ for (var i = needle.length; i--;) {
+ if (wysihtml5.lang.array(arr).indexOf(needle[i]) !== -1) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return wysihtml5.lang.array(arr).indexOf(needle) !== -1;
+ }
+ },
+
+ /**
+ * Check whether a given object exists in an array and return index
+ * If no elelemt found returns -1
+ *
+ * @example
+ * wysihtml5.lang.array([1, 2]).indexOf(2);
+ * // => 1
+ */
+ indexOf: function(needle) {
+ if (arr.indexOf) {
+ return arr.indexOf(needle);
+ } else {
+ for (var i=0, length=arr.length; i<length; i++) {
+ if (arr[i] === needle) { return i; }
+ }
+ return -1;
+ }
+ },
+
+ /**
+ * Substract one array from another
+ *
+ * @example
+ * wysihtml5.lang.array([1, 2, 3, 4]).without([3, 4]);
+ * // => [1, 2]
+ */
+ without: function(arrayToSubstract) {
+ arrayToSubstract = wysihtml5.lang.array(arrayToSubstract);
+ var newArr = [],
+ i = 0,
+ length = arr.length;
+ for (; i<length; i++) {
+ if (!arrayToSubstract.contains(arr[i])) {
+ newArr.push(arr[i]);
+ }
+ }
+ return newArr;
+ },
+
+ /**
+ * Return a clean native array
+ *
+ * Following will convert a Live NodeList to a proper Array
+ * @example
+ * var childNodes = wysihtml5.lang.array(document.body.childNodes).get();
+ */
+ get: function() {
+ var i = 0,
+ length = arr.length,
+ newArray = [];
+ for (; i<length; i++) {
+ newArray.push(arr[i]);
+ }
+ return newArray;
+ },
+
+ /**
+ * Creates a new array with the results of calling a provided function on every element in this array.
+ * optionally this can be provided as second argument
+ *
+ * @example
+ * var childNodes = wysihtml5.lang.array([1,2,3,4]).map(function (value, index, array) {
+ return value * 2;
+ * });
+ * // => [2,4,6,8]
+ */
+ map: function(callback, thisArg) {
+ if (Array.prototype.map) {
+ return arr.map(callback, thisArg);
+ } else {
+ var len = arr.length >>> 0,
+ A = new Array(len),
+ i = 0;
+ for (; i < len; i++) {
+ A[i] = callback.call(thisArg, arr[i], i, arr);
+ }
+ return A;
+ }
+ },
+
+ /* ReturnS new array without duplicate entries
+ *
+ * @example
+ * var uniq = wysihtml5.lang.array([1,2,3,2,1,4]).unique();
+ * // => [1,2,3,4]
+ */
+ unique: function() {
+ var vals = [],
+ max = arr.length,
+ idx = 0;
+
+ while (idx < max) {
+ if (!wysihtml5.lang.array(vals).contains(arr[idx])) {
+ vals.push(arr[idx]);
+ }
+ idx++;
+ }
+ return vals;
+ }
+
+ };
+};
+;wysihtml5.lang.Dispatcher = Base.extend(
+ /** @scope wysihtml5.lang.Dialog.prototype */ {
+ on: function(eventName, handler) {
+ this.events = this.events || {};
+ this.events[eventName] = this.events[eventName] || [];
+ this.events[eventName].push(handler);
+ return this;
+ },
+
+ off: function(eventName, handler) {
+ this.events = this.events || {};
+ var i = 0,
+ handlers,
+ newHandlers;
+ if (eventName) {
+ handlers = this.events[eventName] || [],
+ newHandlers = [];
+ for (; i<handlers.length; i++) {
+ if (handlers[i] !== handler && handler) {
+ newHandlers.push(handlers[i]);
+ }
+ }
+ this.events[eventName] = newHandlers;
+ } else {
+ // Clean up all events
+ 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 {
+ /**
+ * @example
+ * wysihtml5.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
+ * // => { foo: 1, bar: 2, baz: 3 }
+ */
+ merge: function(otherObj) {
+ for (var i in otherObj) {
+ obj[i] = otherObj[i];
+ }
+ return this;
+ },
+
+ get: function() {
+ return obj;
+ },
+
+ /**
+ * @example
+ * wysihtml5.lang.object({ foo: 1 }).clone();
+ * // => { foo: 1 }
+ *
+ * v0.4.14 adds options for deep clone : wysihtml5.lang.object({ foo: 1 }).clone(true);
+ */
+ clone: function(deep) {
+ var newObj = {},
+ i;
+
+ if (obj === null || !wysihtml5.lang.object(obj).isPlainObject()) {
+ return obj;
+ }
+
+ for (i in obj) {
+ if(obj.hasOwnProperty(i)) {
+ if (deep) {
+ newObj[i] = wysihtml5.lang.object(obj[i]).clone(deep);
+ } else {
+ newObj[i] = obj[i];
+ }
+ }
+ }
+ return newObj;
+ },
+
+ /**
+ * @example
+ * wysihtml5.lang.object([]).isArray();
+ * // => true
+ */
+ isArray: function() {
+ return Object.prototype.toString.call(obj) === "[object Array]";
+ },
+
+ /**
+ * @example
+ * wysihtml5.lang.object(function() {}).isFunction();
+ * // => true
+ */
+ isFunction: function() {
+ return Object.prototype.toString.call(obj) === '[object Function]';
+ },
+
+ isPlainObject: function () {
+ return Object.prototype.toString.call(obj) === '[object Object]';
+ }
+ };
+};
+;(function() {
+ var WHITE_SPACE_START = /^\s+/,
+ WHITE_SPACE_END = /\s+$/,
+ ENTITY_REG_EXP = /[&<>\t"]/g,
+ ENTITY_MAP = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': "&quot;",
+ '\t':"&nbsp; "
+ };
+ wysihtml5.lang.string = function(str) {
+ str = String(str);
+ return {
+ /**
+ * @example
+ * wysihtml5.lang.string(" foo ").trim();
+ * // => "foo"
+ */
+ trim: function() {
+ return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, "");
+ },
+
+ /**
+ * @example
+ * wysihtml5.lang.string("Hello #{name}").interpolate({ name: "Christopher" });
+ * // => "Hello Christopher"
+ */
+ interpolate: function(vars) {
+ for (var i in vars) {
+ str = this.replace("#{" + i + "}").by(vars[i]);
+ }
+ return str;
+ },
+
+ /**
+ * @example
+ * wysihtml5.lang.string("Hello Tom").replace("Tom").with("Hans");
+ * // => "Hello Hans"
+ */
+ replace: function(search) {
+ return {
+ by: function(replace) {
+ return str.split(search).join(replace);
+ }
+ };
+ },
+
+ /**
+ * @example
+ * wysihtml5.lang.string("hello<br>").escapeHTML();
+ * // => "hello&lt;br&gt;"
+ */
+ escapeHTML: function(linebreaks, convertSpaces) {
+ var html = str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
+ if (linebreaks) {
+ html = html.replace(/(?:\r\n|\r|\n)/g, '<br />');
+ }
+ if (convertSpaces) {
+ html = html.replace(/ /gi, "&nbsp; ");
+ }
+ return html;
+ }
+ };
+ };
+})();
+;/**
+ * Find urls in descendant text nodes of an element and auto-links them
+ * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
+ *
+ * @param {Element} element Container element in which to search for urls
+ *
+ * @example
+ * <div id="text-container">Please click here: www.google.com</div>
+ * <script>wysihtml5.dom.autoLink(document.getElementById("text-container"));</script>
+ */
+(function(wysihtml5) {
+ var /**
+ * Don't auto-link urls that are contained in the following elements:
+ */
+ IGNORE_URLS_IN = wysihtml5.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]),
+ /**
+ * revision 1:
+ * /(\S+\.{1}[^\s\,\.\!]+)/g
+ *
+ * revision 2:
+ * /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim
+ *
+ * put this in the beginning if you don't wan't to match within a word
+ * (^|[\>\(\{\[\s\>])
+ */
+ URL_REG_EXP = /((https?:\/\/|www\.)[^\s<]{3,})/gi,
+ TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i,
+ MAX_DISPLAY_LENGTH = 100,
+ BRACKETS = { ")": "(", "]": "[", "}": "{" };
+
+ function autoLink(element, ignoreInClasses) {
+ if (_hasParentThatShouldBeIgnored(element, ignoreInClasses)) {
+ return element;
+ }
+
+ if (element === element.ownerDocument.documentElement) {
+ element = element.ownerDocument.body;
+ }
+
+ return _parseNode(element, ignoreInClasses);
+ }
+
+ /**
+ * This is basically a rebuild of
+ * the rails auto_link_urls text helper
+ */
+ function _convertUrlsToLinks(str) {
+ return str.replace(URL_REG_EXP, function(match, url) {
+ var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "",
+ opening = BRACKETS[punctuation];
+ url = url.replace(TRAILING_CHAR_REG_EXP, "");
+
+ if (url.split(opening).length > url.split(punctuation).length) {
+ url = url + punctuation;
+ punctuation = "";
+ }
+ var realUrl = url,
+ displayUrl = url;
+ if (url.length > MAX_DISPLAY_LENGTH) {
+ displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "...";
+ }
+ // Add http prefix if necessary
+ if (realUrl.substr(0, 4) === "www.") {
+ realUrl = "http://" + realUrl;
+ }
+
+ return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation;
+ });
+ }
+
+ /**
+ * Creates or (if already cached) returns a temp element
+ * for the given document object
+ */
+ function _getTempElement(context) {
+ var tempElement = context._wysihtml5_tempElement;
+ if (!tempElement) {
+ tempElement = context._wysihtml5_tempElement = context.createElement("div");
+ }
+ return tempElement;
+ }
+
+ /**
+ * Replaces the original text nodes with the newly auto-linked dom tree
+ */
+ function _wrapMatchesInNode(textNode) {
+ var parentNode = textNode.parentNode,
+ nodeValue = wysihtml5.lang.string(textNode.data).escapeHTML(),
+ tempElement = _getTempElement(parentNode.ownerDocument);
+
+ // We need to insert an empty/temporary <span /> to fix IE quirks
+ // Elsewise IE would strip white space in the beginning
+ tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(nodeValue);
+ tempElement.removeChild(tempElement.firstChild);
+
+ while (tempElement.firstChild) {
+ // inserts tempElement.firstChild before textNode
+ parentNode.insertBefore(tempElement.firstChild, textNode);
+ }
+ parentNode.removeChild(textNode);
+ }
+
+ function _hasParentThatShouldBeIgnored(node, ignoreInClasses) {
+ var nodeName;
+ while (node.parentNode) {
+ node = node.parentNode;
+ nodeName = node.nodeName;
+ if (node.className && wysihtml5.lang.array(node.className.split(' ')).contains(ignoreInClasses)) {
+ return true;
+ }
+ if (IGNORE_URLS_IN.contains(nodeName)) {
+ return true;
+ } else if (nodeName === "body") {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ function _parseNode(element, ignoreInClasses) {
+ if (IGNORE_URLS_IN.contains(element.nodeName)) {
+ return;
+ }
+
+ if (element.className && wysihtml5.lang.array(element.className.split(' ')).contains(ignoreInClasses)) {
+ return;
+ }
+
+ if (element.nodeType === wysihtml5.TEXT_NODE && element.data.match(URL_REG_EXP)) {
+ _wrapMatchesInNode(element);
+ return;
+ }
+
+ var childNodes = wysihtml5.lang.array(element.childNodes).get(),
+ childNodesLength = childNodes.length,
+ i = 0;
+
+ for (; i<childNodesLength; i++) {
+ _parseNode(childNodes[i], ignoreInClasses);
+ }
+
+ return element;
+ }
+
+ wysihtml5.dom.autoLink = autoLink;
+
+ // Reveal url reg exp to the outside
+ wysihtml5.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
+})(wysihtml5);
+;(function(wysihtml5) {
+ var api = wysihtml5.dom;
+
+ api.addClass = function(element, className) {
+ var classList = element.classList;
+ if (classList) {
+ return classList.add(className);
+ }
+ if (api.hasClass(element, className)) {
+ return;
+ }
+ element.className += " " + className;
+ };
+
+ api.removeClass = function(element, 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) {
+ var classList = element.classList;
+ if (classList) {
+ return classList.contains(className);
+ }
+
+ var elementClassName = element.className;
+ return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+ };
+})(wysihtml5);
+;wysihtml5.dom.contains = (function() {
+ var documentElement = document.documentElement;
+ if (documentElement.contains) {
+ return function(container, element) {
+ if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
+ element = element.parentNode;
+ }
+ return container !== element && container.contains(element);
+ };
+ } else if (documentElement.compareDocumentPosition) {
+ return function(container, element) {
+ // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
+ return !!(container.compareDocumentPosition(element) & 16);
+ };
+ }
+})();
+;/**
+ * Converts an HTML fragment/element into a unordered/ordered list
+ *
+ * @param {Element} element The element which should be turned into a list
+ * @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
+ * @return {Element} The created list
+ *
+ * @example
+ * <!-- Assume the following dom: -->
+ * <span id="pseudo-list">
+ * eminem<br>
+ * dr. dre
+ * <div>50 Cent</div>
+ * </span>
+ *
+ * <script>
+ * wysihtml5.dom.convertToList(document.getElementById("pseudo-list"), "ul");
+ * </script>
+ *
+ * <!-- Will result in: -->
+ * <ul>
+ * <li>eminem</li>
+ * <li>dr. dre</li>
+ * <li>50 Cent</li>
+ * </ul>
+ */
+wysihtml5.dom.convertToList = (function() {
+ function _createListItem(doc, list) {
+ var listItem = doc.createElement("li");
+ list.appendChild(listItem);
+ return listItem;
+ }
+
+ function _createList(doc, type) {
+ return doc.createElement(type);
+ }
+
+ function convertToList(element, listType, uneditableClass) {
+ if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") {
+ // Already a list
+ return element;
+ }
+
+ var doc = element.ownerDocument,
+ list = _createList(doc, listType),
+ lineBreaks = element.querySelectorAll("br"),
+ lineBreaksLength = lineBreaks.length,
+ childNodes,
+ childNodesLength,
+ childNode,
+ lineBreak,
+ parentNode,
+ isBlockElement,
+ isLineBreak,
+ currentListItem,
+ 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";
+
+ // consider uneditable as an inline element
+ if (isBlockElement && (!uneditableClass || !wysihtml5.dom.hasClass(childNode, uneditableClass))) {
+ // Append blockElement to current <li> if empty, otherwise create a new one
+ currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem;
+ currentListItem.appendChild(childNode);
+ currentListItem = null;
+ continue;
+ }
+
+ if (isLineBreak) {
+ // Only create a new list item in the next iteration when the current one has already content
+ currentListItem = currentListItem.firstChild ? null : currentListItem;
+ continue;
+ }
+
+ currentListItem.appendChild(childNode);
+ }
+
+ if (childNodes.length === 0) {
+ _createListItem(doc, list);
+ }
+
+ element.parentNode.replaceChild(list, element);
+ return list;
+ }
+
+ return convertToList;
+})();
+;/**
+ * Copy a set of attributes from one element to another
+ *
+ * @param {Array} attributesToCopy List of attributes which should be copied
+ * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
+ * copy the attributes from., this again returns an object which provides a method named "to" which can be invoked
+ * with the element where to copy the attributes to (see example)
+ *
+ * @example
+ * var textarea = document.querySelector("textarea"),
+ * div = document.querySelector("div[contenteditable=true]"),
+ * anotherDiv = document.querySelector("div.preview");
+ * wysihtml5.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv);
+ *
+ */
+wysihtml5.dom.copyAttributes = function(attributesToCopy) {
+ return {
+ from: function(elementToCopyFrom) {
+ return {
+ to: function(elementToCopyTo) {
+ var attribute,
+ i = 0,
+ length = attributesToCopy.length;
+ for (; i<length; i++) {
+ attribute = attributesToCopy[i];
+ if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") {
+ elementToCopyTo[attribute] = elementToCopyFrom[attribute];
+ }
+ }
+ return { andTo: arguments.callee };
+ }
+ };
+ }
+ };
+};
+;/**
+ * Copy a set of styles from one element to another
+ * Please note that this only works properly across browsers when the element from which to copy the styles
+ * is in the dom
+ *
+ * Interesting article on how to copy styles
+ *
+ * @param {Array} stylesToCopy List of styles which should be copied
+ * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
+ * copy the styles from., this again returns an object which provides a method named "to" which can be invoked
+ * with the element where to copy the styles to (see example)
+ *
+ * @example
+ * var textarea = document.querySelector("textarea"),
+ * div = document.querySelector("div[contenteditable=true]"),
+ * anotherDiv = document.querySelector("div.preview");
+ * wysihtml5.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv);
+ *
+ */
+(function(dom) {
+
+ /**
+ * 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"];
+
+ var shouldIgnoreBoxSizingBorderBox = function(element) {
+ if (hasBoxSizingBorderBox(element)) {
+ return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth;
+ }
+ return false;
+ };
+
+ var hasBoxSizingBorderBox = function(element) {
+ var i = 0,
+ length = BOX_SIZING_PROPERTIES.length;
+ for (; i<length; i++) {
+ if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") {
+ return BOX_SIZING_PROPERTIES[i];
+ }
+ }
+ };
+
+ dom.copyStyles = function(stylesToCopy) {
+ return {
+ from: function(element) {
+ if (shouldIgnoreBoxSizingBorderBox(element)) {
+ stylesToCopy = wysihtml5.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES);
+ }
+
+ var cssText = "",
+ length = stylesToCopy.length,
+ i = 0,
+ property;
+ for (; i<length; i++) {
+ property = stylesToCopy[i];
+ cssText += property + ":" + dom.getStyle(property).from(element) + ";";
+ }
+
+ return {
+ to: function(element) {
+ dom.setStyles(cssText).on(element);
+ return { andTo: arguments.callee };
+ }
+ };
+ }
+ };
+ };
+})(wysihtml5.dom);
+;/**
+ * Event Delegation
+ *
+ * @example
+ * wysihtml5.dom.delegate(document.body, "a", "click", function() {
+ * // foo
+ * });
+ */
+(function(wysihtml5) {
+
+ wysihtml5.dom.delegate = function(container, selector, eventName, handler) {
+ return wysihtml5.dom.observe(container, eventName, function(event) {
+ var target = event.target,
+ match = wysihtml5.lang.array(container.querySelectorAll(selector));
+
+ while (target && target !== container) {
+ if (match.contains(target)) {
+ handler.call(target, event);
+ break;
+ }
+ target = target.parentNode;
+ }
+ });
+ };
+
+})(wysihtml5);
+;// TODO: Refactor dom tree traversing here
+(function(wysihtml5) {
+ wysihtml5.dom.domNode = function(node) {
+ var defaultNodeTypes = [wysihtml5.ELEMENT_NODE, wysihtml5.TEXT_NODE];
+
+ var _isBlankText = function(node) {
+ return node.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/g).test(node.data);
+ };
+
+ return {
+
+ // var node = wysihtml5.dom.domNode(element).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
+ prev: function(options) {
+ var prevNode = node.previousSibling,
+ types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
+
+ if (!prevNode) {
+ return null;
+ }
+
+ if (
+ (!wysihtml5.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
+ (options && options.ignoreBlankTexts && _isBlankText(prevNode)) // Blank text nodes bypassed if set
+ ) {
+ return wysihtml5.dom.domNode(prevNode).prev(options);
+ }
+
+ return prevNode;
+ },
+
+ // var node = wysihtml5.dom.domNode(element).next({nodeTypes: [1,3], ignoreBlankTexts: true});
+ next: function(options) {
+ var nextNode = node.nextSibling,
+ types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
+
+ if (!nextNode) {
+ return null;
+ }
+
+ if (
+ (!wysihtml5.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
+ (options && options.ignoreBlankTexts && _isBlankText(nextNode)) // blank text nodes bypassed if set
+ ) {
+ return wysihtml5.dom.domNode(nextNode).next(options);
+ }
+
+ return nextNode;
+ }
+
+
+
+ };
+ };
+})(wysihtml5);;/**
+ * Returns the given html wrapped in a div element
+ *
+ * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
+ * when inserted via innerHTML
+ *
+ * @param {String} html The html which should be wrapped in a dom element
+ * @param {Obejct} [context] Document object of the context the html belongs to
+ *
+ * @example
+ * wysihtml5.dom.getAsDom("<article>foo</article>");
+ */
+wysihtml5.dom.getAsDom = (function() {
+
+ var _innerHTMLShiv = function(html, context) {
+ var tempElement = context.createElement("div");
+ tempElement.style.display = "none";
+ context.body.appendChild(tempElement);
+ // IE throws an exception when trying to insert <frameset></frameset> via innerHTML
+ try { tempElement.innerHTML = html; } catch(e) {}
+ context.body.removeChild(tempElement);
+ return tempElement;
+ };
+
+ /**
+ * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element
+ */
+ var _ensureHTML5Compatibility = function(context) {
+ if (context._wysihtml5_supportsHTML5Tags) {
+ return;
+ }
+ for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) {
+ context.createElement(HTML5_ELEMENTS[i]);
+ }
+ context._wysihtml5_supportsHTML5Tags = true;
+ };
+
+
+ /**
+ * List of html5 tags
+ * taken from http://simon.html5.org/html5-elements
+ */
+ var HTML5_ELEMENTS = [
+ "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption",
+ "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress",
+ "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr"
+ ];
+
+ return function(html, context) {
+ context = context || document;
+ var tempElement;
+ if (typeof(html) === "object" && html.nodeType) {
+ tempElement = context.createElement("div");
+ tempElement.appendChild(html);
+ } else if (wysihtml5.browser.supportsHTML5Tags(context)) {
+ tempElement = context.createElement("div");
+ tempElement.innerHTML = html;
+ } else {
+ _ensureHTML5Compatibility(context);
+ tempElement = _innerHTMLShiv(html, context);
+ }
+ return tempElement;
+ };
+})();
+;/**
+ * Walks the dom tree from the given node up until it finds a match
+ * Designed for optimal performance.
+ *
+ * @param {Element} node The from which to check the parent nodes
+ * @param {Object} matchingSet Object to match against (possible properties: nodeName, className, classRegExp)
+ * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50)
+ * @return {null|Element} Returns the first element that matched the desiredNodeName(s)
+ * @example
+ * var listElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: ["MENU", "UL", "OL"] });
+ * // ... or ...
+ * var unorderedListElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: "UL" });
+ * // ... or ...
+ * var coloredElement = wysihtml5.dom.getParentElement(myTextNode, { nodeName: "SPAN", className: "wysiwyg-color-red", classRegExp: /wysiwyg-color-[a-z]/g });
+ */
+wysihtml5.dom.getParentElement = (function() {
+
+ function _isSameNodeName(nodeName, desiredNodeNames) {
+ if (!desiredNodeNames || !desiredNodeNames.length) {
+ return true;
+ }
+
+ if (typeof(desiredNodeNames) === "string") {
+ return nodeName === desiredNodeNames;
+ } else {
+ return wysihtml5.lang.array(desiredNodeNames).contains(nodeName);
+ }
+ }
+
+ function _isElement(node) {
+ return node.nodeType === wysihtml5.ELEMENT_NODE;
+ }
+
+ function _hasClassName(element, className, classRegExp) {
+ var classNames = (element.className || "").match(classRegExp) || [];
+ if (!className) {
+ return !!classNames.length;
+ }
+ return classNames[classNames.length - 1] === className;
+ }
+
+ function _hasStyle(element, cssStyle, styleRegExp) {
+ var styles = (element.getAttribute('style') || "").match(styleRegExp) || [];
+ if (!cssStyle) {
+ return !!styles.length;
+ }
+ return styles[styles.length - 1] === cssStyle;
+ }
+
+ return function(node, matchingSet, levels, container) {
+ var findByStyle = (matchingSet.cssStyle || matchingSet.styleRegExp),
+ findByClass = (matchingSet.className || matchingSet.classRegExp);
+
+ levels = levels || 50; // Go max 50 nodes upwards from current node
+
+ while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) {
+ if (_isElement(node) && _isSameNodeName(node.nodeName, matchingSet.nodeName) &&
+ (!findByStyle || _hasStyle(node, matchingSet.cssStyle, matchingSet.styleRegExp)) &&
+ (!findByClass || _hasClassName(node, matchingSet.className, matchingSet.classRegExp))
+ ) {
+ return node;
+ }
+ node = node.parentNode;
+ }
+ return null;
+ };
+})();
+;/**
+ * Get element's style for a specific css property
+ *
+ * @param {Element} element The element on which to retrieve the style
+ * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
+ *
+ * @example
+ * wysihtml5.dom.getStyle("display").from(document.body);
+ * // => "block"
+ */
+wysihtml5.dom.getStyle = (function() {
+ var stylePropertyMapping = {
+ "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"
+ },
+ REG_EXP_CAMELIZE = /\-[a-z]/g;
+
+ function camelize(str) {
+ return str.replace(REG_EXP_CAMELIZE, function(match) {
+ return match.charAt(1).toUpperCase();
+ });
+ }
+
+ return function(property) {
+ return {
+ from: function(element) {
+ if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
+ return;
+ }
+
+ var doc = element.ownerDocument,
+ camelizedProperty = stylePropertyMapping[property] || camelize(property),
+ style = element.style,
+ currentStyle = element.currentStyle,
+ styleValue = style[camelizedProperty];
+ if (styleValue) {
+ return styleValue;
+ }
+
+ // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant
+ // window.getComputedStyle, since it returns css property values in their original unit:
+ // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle
+ // gives you the original "50%".
+ // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
+ if (currentStyle) {
+ try {
+ return currentStyle[camelizedProperty];
+ } catch(e) {
+ //ie will occasionally fail for unknown reasons. swallowing exception
+ }
+ }
+
+ var win = doc.defaultView || doc.parentWindow,
+ needsOverflowReset = (property === "height" || property === "width") && element.nodeName === "TEXTAREA",
+ originalOverflow,
+ returnValue;
+
+ if (win.getComputedStyle) {
+ // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars
+ // therfore we remove and restore the scrollbar and calculate the value in between
+ if (needsOverflowReset) {
+ originalOverflow = style.overflow;
+ style.overflow = "hidden";
+ }
+ returnValue = win.getComputedStyle(element, null).getPropertyValue(property);
+ if (needsOverflowReset) {
+ style.overflow = originalOverflow || "";
+ }
+ return returnValue;
+ }
+ }
+ };
+ };
+})();
+;wysihtml5.dom.getTextNodes = function(node, ingoreEmpty){
+ var all = [];
+ for (node=node.firstChild;node;node=node.nextSibling){
+ if (node.nodeType == 3) {
+ if (!ingoreEmpty || !(/^\s*$/).test(node.innerText || node.textContent)) {
+ all.push(node);
+ }
+ } else {
+ all = all.concat(wysihtml5.dom.getTextNodes(node, ingoreEmpty));
+ }
+ }
+ return all;
+};;/**
+ * High performant way to check whether an element with a specific tag name is in the given document
+ * Optimized for being heavily executed
+ * Unleashes the power of live node lists
+ *
+ * @param {Object} doc The document object of the context where to check
+ * @param {String} tagName Upper cased tag name
+ * @example
+ * wysihtml5.dom.hasElementWithTagName(document, "IMG");
+ */
+wysihtml5.dom.hasElementWithTagName = (function() {
+ var LIVE_CACHE = {},
+ DOCUMENT_IDENTIFIER = 1;
+
+ function _getDocumentIdentifier(doc) {
+ return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
+ }
+
+ return function(doc, tagName) {
+ var key = _getDocumentIdentifier(doc) + ":" + tagName,
+ cacheEntry = LIVE_CACHE[key];
+ if (!cacheEntry) {
+ cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName);
+ }
+
+ return cacheEntry.length > 0;
+ };
+})();
+;/**
+ * High performant way to check whether an element with a specific class name is in the given document
+ * Optimized for being heavily executed
+ * Unleashes the power of live node lists
+ *
+ * @param {Object} doc The document object of the context where to check
+ * @param {String} tagName Upper cased tag name
+ * @example
+ * wysihtml5.dom.hasElementWithClassName(document, "foobar");
+ */
+(function(wysihtml5) {
+ var LIVE_CACHE = {},
+ DOCUMENT_IDENTIFIER = 1;
+
+ function _getDocumentIdentifier(doc) {
+ return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
+ }
+
+ wysihtml5.dom.hasElementWithClassName = function(doc, className) {
+ // getElementsByClassName is not supported by IE<9
+ // but is sometimes mocked via library code (which then doesn't return live node lists)
+ if (!wysihtml5.browser.supportsNativeGetElementsByClassName()) {
+ return !!doc.querySelector("." + className);
+ }
+
+ var key = _getDocumentIdentifier(doc) + ":" + className,
+ cacheEntry = LIVE_CACHE[key];
+ if (!cacheEntry) {
+ cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className);
+ }
+
+ return cacheEntry.length > 0;
+ };
+})(wysihtml5);
+;wysihtml5.dom.insert = function(elementToInsert) {
+ return {
+ after: function(element) {
+ element.parentNode.insertBefore(elementToInsert, element.nextSibling);
+ },
+
+ before: function(element) {
+ element.parentNode.insertBefore(elementToInsert, element);
+ },
+
+ into: function(element) {
+ element.appendChild(elementToInsert);
+ }
+ };
+};
+;wysihtml5.dom.insertCSS = function(rules) {
+ rules = rules.join("\n");
+
+ return {
+ into: function(doc) {
+ var styleElement = doc.createElement("style");
+ styleElement.type = "text/css";
+
+ if (styleElement.styleSheet) {
+ styleElement.styleSheet.cssText = rules;
+ } else {
+ styleElement.appendChild(doc.createTextNode(rules));
+ }
+
+ 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);
+ }
+ }
+ }
+ };
+};
+;// TODO: Refactor dom tree traversing here
+(function(wysihtml5) {
+ wysihtml5.dom.lineBreaks = function(node) {
+
+ function _isLineBreak(n) {
+ return n.nodeName === "BR";
+ }
+
+ /**
+ * Checks whether the elment causes a visual line break
+ * (<br> or block elements)
+ */
+ function _isLineBreakOrBlockElement(element) {
+ if (_isLineBreak(element)) {
+ return true;
+ }
+
+ if (wysihtml5.dom.getStyle("display").from(element) === "block") {
+ return true;
+ }
+
+ return false;
+ }
+
+ return {
+
+ /* wysihtml5.dom.lineBreaks(element).add();
+ *
+ * Adds line breaks before and after the given node if the previous and next siblings
+ * aren't already causing a visual line break (block element or <br>)
+ */
+ add: function(options) {
+ var doc = node.ownerDocument,
+ nextSibling = wysihtml5.dom.domNode(node).next({ignoreBlankTexts: true}),
+ previousSibling = wysihtml5.dom.domNode(node).prev({ignoreBlankTexts: true});
+
+ if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) {
+ wysihtml5.dom.insert(doc.createElement("br")).after(node);
+ }
+ if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) {
+ wysihtml5.dom.insert(doc.createElement("br")).before(node);
+ }
+ },
+
+ /* wysihtml5.dom.lineBreaks(element).remove();
+ *
+ * Removes line breaks before and after the given node
+ */
+ remove: function(options) {
+ var nextSibling = wysihtml5.dom.domNode(node).next({ignoreBlankTexts: true}),
+ previousSibling = wysihtml5.dom.domNode(node).prev({ignoreBlankTexts: true});
+
+ if (nextSibling && _isLineBreak(nextSibling)) {
+ nextSibling.parentNode.removeChild(nextSibling);
+ }
+ if (previousSibling && _isLineBreak(previousSibling)) {
+ previousSibling.parentNode.removeChild(previousSibling);
+ }
+ }
+ };
+ };
+})(wysihtml5);;/**
+ * Method to set dom events
+ *
+ * @example
+ * wysihtml5.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
+ */
+wysihtml5.dom.observe = function(element, eventNames, handler) {
+ eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames;
+
+ var handlerWrapper,
+ eventName,
+ i = 0,
+ length = eventNames.length;
+
+ for (; i<length; i++) {
+ eventName = eventNames[i];
+ if (element.addEventListener) {
+ element.addEventListener(eventName, handler, false);
+ } else {
+ handlerWrapper = function(event) {
+ if (!("target" in event)) {
+ event.target = event.srcElement;
+ }
+ event.preventDefault = event.preventDefault || function() {
+ this.returnValue = false;
+ };
+ event.stopPropagation = event.stopPropagation || function() {
+ this.cancelBubble = true;
+ };
+ handler.call(element, event);
+ };
+ element.attachEvent("on" + eventName, handlerWrapper);
+ }
+ }
+
+ return {
+ stop: function() {
+ var eventName,
+ i = 0,
+ length = eventNames.length;
+ for (; i<length; i++) {
+ eventName = eventNames[i];
+ if (element.removeEventListener) {
+ element.removeEventListener(eventName, handler, false);
+ } else {
+ element.detachEvent("on" + eventName, handlerWrapper);
+ }
+ }
+ }
+ };
+};
+;/**
+ * HTML Sanitizer
+ * Rewrites the HTML based on given rules
+ *
+ * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
+ * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
+ * be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the
+ * desired substitution.
+ * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing
+ *
+ * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element.
+ *
+ * @example
+ * var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>';
+ * wysihtml5.dom.parse(userHTML, {
+ * tags {
+ * p: "div", // Rename p tags to div tags
+ * font: "span" // Rename font tags to span tags
+ * div: true, // Keep them, also possible (same result when passing: "div" or true)
+ * script: undefined // Remove script elements
+ * }
+ * });
+ * // => <div><div><span>foo bar</span></div></div>
+ *
+ * var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>';
+ * wysihtml5.dom.parse(userHTML);
+ * // => '<span><span><span><span>I'm a table!</span></span></span></span>'
+ *
+ * var userHTML = '<div>foobar<br>foobar</div>';
+ * wysihtml5.dom.parse(userHTML, {
+ * tags: {
+ * div: undefined,
+ * br: true
+ * }
+ * });
+ * // => ''
+ *
+ * var userHTML = '<div class="red">foo</div><div class="pink">bar</div>';
+ * wysihtml5.dom.parse(userHTML, {
+ * classes: {
+ * red: 1,
+ * green: 1
+ * },
+ * tags: {
+ * div: {
+ * rename_tag: "p"
+ * }
+ * }
+ * });
+ * // => '<p class="red">foo</p><p>bar</p>'
+ */
+
+wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
+ /* TODO: Currently escaped module pattern as otherwise folloowing default swill be shared among multiple editors.
+ * Refactor whole code as this method while workind is kind of awkward too */
+
+ /**
+ * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
+ * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the
+ * node isn't closed
+ *
+ * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML.
+ */
+ var NODE_TYPE_MAPPING = {
+ "1": _handleElement,
+ "3": _handleText,
+ "8": _handleComment
+ },
+ // Rename unknown tags to this
+ DEFAULT_NODE_NAME = "span",
+ WHITE_SPACE_REG_EXP = /\s+/,
+ defaultRules = { tags: {}, classes: {} },
+ currentRules = {};
+
+ /**
+ * Iterates over all childs of the element, recreates them, appends them into a document fragment
+ * which later replaces the entire body content
+ */
+ function parse(elementOrHtml, config) {
+ wysihtml5.lang.object(currentRules).merge(defaultRules).merge(config.rules).get();
+
+ var context = config.context || elementOrHtml.ownerDocument || document,
+ fragment = context.createDocumentFragment(),
+ isString = typeof(elementOrHtml) === "string",
+ clearInternals = false,
+ element,
+ newNode,
+ firstChild;
+
+ if (config.clearInternals === true) {
+ clearInternals = true;
+ }
+
+ if (isString) {
+ element = wysihtml5.dom.getAsDom(elementOrHtml, context);
+ } else {
+ element = elementOrHtml;
+ }
+
+ if (currentRules.selectors) {
+ _applySelectorRules(element, currentRules.selectors);
+ }
+
+ while (element.firstChild) {
+ firstChild = element.firstChild;
+ newNode = _convert(firstChild, config.cleanUp, clearInternals, config.uneditableClass);
+ if (newNode) {
+ fragment.appendChild(newNode);
+ }
+ if (firstChild !== newNode) {
+ element.removeChild(firstChild);
+ }
+ }
+
+ if (config.unjoinNbsps) {
+ // replace joined non-breakable spaces with unjoined
+ var txtnodes = wysihtml5.dom.getTextNodes(fragment);
+ for (var n = txtnodes.length; n--;) {
+ txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
+ }
+ }
+
+ // Clear element contents
+ element.innerHTML = "";
+
+ // Insert new DOM tree
+ element.appendChild(fragment);
+
+ return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element;
+ }
+
+ function _convert(oldNode, cleanUp, clearInternals, uneditableClass) {
+ var oldNodeType = oldNode.nodeType,
+ oldChilds = oldNode.childNodes,
+ oldChildsLength = oldChilds.length,
+ method = NODE_TYPE_MAPPING[oldNodeType],
+ i = 0,
+ fragment,
+ newNode,
+ newChild;
+
+ // Passes directly elemets with uneditable class
+ if (uneditableClass && oldNodeType === 1 && wysihtml5.dom.hasClass(oldNode, uneditableClass)) {
+ return oldNode;
+ }
+
+ newNode = method && method(oldNode, clearInternals);
+
+ // Remove or unwrap node in case of return value null or false
+ if (!newNode) {
+ if (newNode === false) {
+ // false defines that tag should be removed but contents should remain (unwrap)
+ fragment = oldNode.ownerDocument.createDocumentFragment();
+
+ for (i = oldChildsLength; i--;) {
+ if (oldChilds[i]) {
+ newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
+ if (newChild) {
+ if (oldChilds[i] === newChild) {
+ i--;
+ }
+ fragment.insertBefore(newChild, fragment.firstChild);
+ }
+ }
+ }
+
+ if (wysihtml5.dom.getStyle("display").from(oldNode) === "block") {
+ fragment.appendChild(oldNode.ownerDocument.createElement("br"));
+ }
+
+ // TODO: try to minimize surplus spaces
+ if (wysihtml5.lang.array([
+ "div", "pre", "p",
+ "table", "td", "th",
+ "ul", "ol", "li",
+ "dd", "dl",
+ "footer", "header", "section",
+ "h1", "h2", "h3", "h4", "h5", "h6"
+ ]).contains(oldNode.nodeName.toLowerCase()) && oldNode.parentNode.lastChild !== oldNode) {
+ // add space at first when unwraping non-textflow elements
+ if (!oldNode.nextSibling || oldNode.nextSibling.nodeType !== 3 || !(/^\s/).test(oldNode.nextSibling.nodeValue)) {
+ fragment.appendChild(oldNode.ownerDocument.createTextNode(" "));
+ }
+ }
+
+ if (fragment.normalize) {
+ fragment.normalize();
+ }
+ return fragment;
+ } else {
+ // Remove
+ return null;
+ }
+ }
+
+ // Converts all childnodes
+ for (i=0; i<oldChildsLength; i++) {
+ if (oldChilds[i]) {
+ newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
+ if (newChild) {
+ if (oldChilds[i] === newChild) {
+ i--;
+ }
+ newNode.appendChild(newChild);
+ }
+ }
+ }
+
+ // Cleanup senseless <span> elements
+ if (cleanUp &&
+ newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME &&
+ (!newNode.childNodes.length ||
+ ((/^\s*$/gi).test(newNode.innerHTML) && (clearInternals || (oldNode.className !== "_wysihtml5-temp-placeholder" && oldNode.className !== "rangySelectionBoundary"))) ||
+ !newNode.attributes.length)
+ ) {
+ fragment = newNode.ownerDocument.createDocumentFragment();
+ while (newNode.firstChild) {
+ fragment.appendChild(newNode.firstChild);
+ }
+ if (fragment.normalize) {
+ fragment.normalize();
+ }
+ return fragment;
+ }
+
+ if (newNode.normalize) {
+ newNode.normalize();
+ }
+ return newNode;
+ }
+
+ function _applySelectorRules (element, selectorRules) {
+ var sel, method, els;
+
+ for (sel in selectorRules) {
+ if (selectorRules.hasOwnProperty(sel)) {
+ if (wysihtml5.lang.object(selectorRules[sel]).isFunction()) {
+ method = selectorRules[sel];
+ } else if (typeof(selectorRules[sel]) === "string" && elementHandlingMethods[selectorRules[sel]]) {
+ method = elementHandlingMethods[selectorRules[sel]];
+ }
+ els = element.querySelectorAll(sel);
+ for (var i = els.length; i--;) {
+ method(els[i]);
+ }
+ }
+ }
+ }
+
+ function _handleElement(oldNode, clearInternals) {
+ var rule,
+ newNode,
+ tagRules = currentRules.tags,
+ nodeName = oldNode.nodeName.toLowerCase(),
+ scopeName = oldNode.scopeName,
+ renameTag;
+
+ /**
+ * We already parsed that element
+ * ignore it! (yes, this sometimes happens in IE8 when the html is invalid)
+ */
+ if (oldNode._wysihtml5) {
+ return null;
+ }
+ oldNode._wysihtml5 = 1;
+
+ if (oldNode.className === "wysihtml5-temp") {
+ return null;
+ }
+
+ /**
+ * IE is the only browser who doesn't include the namespace in the
+ * nodeName, that's why we have to prepend it by ourselves
+ * scopeName is a proprietary IE feature
+ * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx
+ */
+ if (scopeName && scopeName != "HTML") {
+ nodeName = scopeName + ":" + nodeName;
+ }
+ /**
+ * Repair node
+ * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags
+ * A <p> doesn't need to be closed according HTML4-5 spec, we simply replace it with a <div> to preserve its content and layout
+ */
+ if ("outerHTML" in oldNode) {
+ if (!wysihtml5.browser.autoClosesUnclosedTags() &&
+ oldNode.nodeName === "P" &&
+ oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") {
+ nodeName = "div";
+ }
+ }
+
+ if (nodeName in tagRules) {
+ rule = tagRules[nodeName];
+ if (!rule || rule.remove) {
+ return null;
+ } else if (rule.unwrap) {
+ return false;
+ }
+ rule = typeof(rule) === "string" ? { rename_tag: rule } : rule;
+ } else if (oldNode.firstChild) {
+ rule = { rename_tag: DEFAULT_NODE_NAME };
+ } else {
+ // Remove empty unknown elements
+ return null;
+ }
+
+ // tests if type condition is met or node should be removed/unwrapped/renamed
+ if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) {
+ if (rule.remove_action) {
+ if (rule.remove_action === "unwrap") {
+ return false;
+ } else if (rule.remove_action === "rename") {
+ renameTag = rule.remove_action_rename_to || DEFAULT_NODE_NAME;
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ newNode = oldNode.ownerDocument.createElement(renameTag || rule.rename_tag || nodeName);
+ _handleAttributes(oldNode, newNode, rule, clearInternals);
+ _handleStyles(oldNode, newNode, rule);
+
+ oldNode = null;
+
+ if (newNode.normalize) { newNode.normalize(); }
+ return newNode;
+ }
+
+ function _testTypes(oldNode, rules, types, clearInternals) {
+ var definition, type;
+
+ // do not interfere with placeholder span or pasting caret position is not maintained
+ if (oldNode.nodeName === "SPAN" && !clearInternals && (oldNode.className === "_wysihtml5-temp-placeholder" || oldNode.className === "rangySelectionBoundary")) {
+ return true;
+ }
+
+ for (type in types) {
+ if (types.hasOwnProperty(type) && rules.type_definitions && rules.type_definitions[type]) {
+ definition = rules.type_definitions[type];
+ if (_testType(oldNode, definition)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ function array_contains(a, obj) {
+ var i = a.length;
+ while (i--) {
+ if (a[i] === obj) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function _testType(oldNode, definition) {
+
+ var nodeClasses = oldNode.getAttribute("class"),
+ nodeStyles = oldNode.getAttribute("style"),
+ classesLength, s, s_corrected, a, attr, currentClass, styleProp;
+
+ // test for methods
+ if (definition.methods) {
+ for (var m in definition.methods) {
+ if (definition.methods.hasOwnProperty(m) && typeCeckMethods[m]) {
+
+ if (typeCeckMethods[m](oldNode)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ // test for classes, if one found return true
+ if (nodeClasses && definition.classes) {
+ nodeClasses = nodeClasses.replace(/^\s+/g, '').replace(/\s+$/g, '').split(WHITE_SPACE_REG_EXP);
+ classesLength = nodeClasses.length;
+ for (var i = 0; i < classesLength; i++) {
+ if (definition.classes[nodeClasses[i]]) {
+ return true;
+ }
+ }
+ }
+
+ // test for styles, if one found return true
+ if (nodeStyles && definition.styles) {
+
+ nodeStyles = nodeStyles.split(';');
+ for (s in definition.styles) {
+ if (definition.styles.hasOwnProperty(s)) {
+ for (var sp = nodeStyles.length; sp--;) {
+ styleProp = nodeStyles[sp].split(':');
+
+ if (styleProp[0].replace(/\s/g, '').toLowerCase() === s) {
+ if (definition.styles[s] === true || definition.styles[s] === 1 || wysihtml5.lang.array(definition.styles[s]).contains(styleProp[1].replace(/\s/g, '').toLowerCase()) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // test for attributes in general against regex match
+ if (definition.attrs) {
+ for (a in definition.attrs) {
+ if (definition.attrs.hasOwnProperty(a)) {
+ attr = wysihtml5.dom.getAttribute(oldNode, a);
+ if (typeof(attr) === "string") {
+ if (attr.search(definition.attrs[a]) > -1) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ function _handleStyles(oldNode, newNode, rule) {
+ var s, v;
+ if(rule && rule.keep_styles) {
+ for (s in rule.keep_styles) {
+ if (rule.keep_styles.hasOwnProperty(s)) {
+ v = (s === "float") ? oldNode.style.styleFloat || oldNode.style.cssFloat : oldNode.style[s];
+ // value can be regex and if so should match or style skipped
+ if (rule.keep_styles[s] instanceof RegExp && !(rule.keep_styles[s].test(v))) {
+ continue;
+ }
+ if (s === "float") {
+ // IE compability
+ newNode.style[(oldNode.style.styleFloat) ? 'styleFloat': 'cssFloat'] = v;
+ } else if (oldNode.style[s]) {
+ newNode.style[s] = v;
+ }
+ }
+ }
+ }
+ };
+
+ function _getAttributesBeginningWith(beginning, attributes) {
+ var returnAttributes = [];
+ for (var attr in attributes) {
+ if (attributes.hasOwnProperty(attr) && attr.indexOf(beginning) === 0) {
+ returnAttributes.push(attr);
+ }
+ }
+ return returnAttributes;
+ }
+
+ function _checkAttribute(attributeName, attributeValue, methodName, nodeName) {
+ var method = attributeCheckMethods[methodName],
+ newAttributeValue;
+
+ if (method) {
+ if (attributeValue || (attributeName === "alt" && nodeName == "IMG")) {
+ newAttributeValue = method(attributeValue);
+ if (typeof(newAttributeValue) === "string") {
+ return newAttributeValue;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ function _checkAttributes(oldNode, local_attributes) {
+ var globalAttributes = wysihtml5.lang.object(currentRules.attributes || {}).clone(), // global values for check/convert values of attributes
+ checkAttributes = wysihtml5.lang.object(globalAttributes).merge( wysihtml5.lang.object(local_attributes || {}).clone()).get(),
+ attributes = {},
+ oldAttributes = wysihtml5.dom.getAttributes(oldNode),
+ attributeName, newValue, matchingAttributes;
+
+ for (attributeName in checkAttributes) {
+ if ((/\*$/).test(attributeName)) {
+
+ matchingAttributes = _getAttributesBeginningWith(attributeName.slice(0,-1), oldAttributes);
+ for (var i = 0, imax = matchingAttributes.length; i < imax; i++) {
+
+ newValue = _checkAttribute(matchingAttributes[i], oldAttributes[matchingAttributes[i]], checkAttributes[attributeName], oldNode.nodeName);
+ if (newValue !== false) {
+ attributes[matchingAttributes[i]] = newValue;
+ }
+ }
+ } else {
+ newValue = _checkAttribute(attributeName, oldAttributes[attributeName], checkAttributes[attributeName], oldNode.nodeName);
+ if (newValue !== false) {
+ attributes[attributeName] = newValue;
+ }
+ }
+ }
+
+ return attributes;
+ }
+
+ // TODO: refactor. Too long to read
+ function _handleAttributes(oldNode, newNode, rule, clearInternals) {
+ var attributes = {}, // fresh new set of attributes to set on newNode
+ setClass = rule.set_class, // classes to set
+ addClass = rule.add_class, // add classes based on existing attributes
+ addStyle = rule.add_style, // add styles based on existing attributes
+ setAttributes = rule.set_attributes, // attributes to set on the current node
+ allowedClasses = currentRules.classes,
+ i = 0,
+ classes = [],
+ styles = [],
+ newClasses = [],
+ oldClasses = [],
+ classesLength,
+ newClassesLength,
+ currentClass,
+ newClass,
+ attributeName,
+ method;
+
+ if (setAttributes) {
+ attributes = wysihtml5.lang.object(setAttributes).clone();
+ }
+
+ // check/convert values of attributes
+ attributes = wysihtml5.lang.object(attributes).merge(_checkAttributes(oldNode, rule.check_attributes)).get();
+
+ if (setClass) {
+ classes.push(setClass);
+ }
+
+ if (addClass) {
+ for (attributeName in addClass) {
+ method = addClassMethods[addClass[attributeName]];
+ if (!method) {
+ continue;
+ }
+ newClass = method(wysihtml5.dom.getAttribute(oldNode, attributeName));
+ if (typeof(newClass) === "string") {
+ classes.push(newClass);
+ }
+ }
+ }
+
+ if (addStyle) {
+ for (attributeName in addStyle) {
+ method = addStyleMethods[addStyle[attributeName]];
+ if (!method) {
+ continue;
+ }
+
+ newStyle = method(wysihtml5.dom.getAttribute(oldNode, attributeName));
+ if (typeof(newStyle) === "string") {
+ styles.push(newStyle);
+ }
+ }
+ }
+
+
+ if (typeof(allowedClasses) === "string" && allowedClasses === "any" && oldNode.getAttribute("class")) {
+ if (currentRules.classes_blacklist) {
+ oldClasses = oldNode.getAttribute("class");
+ if (oldClasses) {
+ classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
+ }
+
+ classesLength = classes.length;
+ for (; i<classesLength; i++) {
+ currentClass = classes[i];
+ if (!currentRules.classes_blacklist[currentClass]) {
+ newClasses.push(currentClass);
+ }
+ }
+
+ if (newClasses.length) {
+ attributes["class"] = wysihtml5.lang.array(newClasses).unique().join(" ");
+ }
+
+ } else {
+ attributes["class"] = oldNode.getAttribute("class");
+ }
+ } else {
+ // make sure that wysihtml5 temp class doesn't get stripped out
+ if (!clearInternals) {
+ allowedClasses["_wysihtml5-temp-placeholder"] = 1;
+ allowedClasses["_rangySelectionBoundary"] = 1;
+ allowedClasses["wysiwyg-tmp-selected-cell"] = 1;
+ }
+
+ // add old classes last
+ oldClasses = oldNode.getAttribute("class");
+ if (oldClasses) {
+ classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
+ }
+ classesLength = classes.length;
+ for (; i<classesLength; i++) {
+ currentClass = classes[i];
+ if (allowedClasses[currentClass]) {
+ newClasses.push(currentClass);
+ }
+ }
+
+ if (newClasses.length) {
+ attributes["class"] = wysihtml5.lang.array(newClasses).unique().join(" ");
+ }
+ }
+
+ // remove table selection class if present
+ if (attributes["class"] && clearInternals) {
+ attributes["class"] = attributes["class"].replace("wysiwyg-tmp-selected-cell", "");
+ if ((/^\s*$/g).test(attributes["class"])) {
+ delete attributes["class"];
+ }
+ }
+
+ if (styles.length) {
+ attributes["style"] = wysihtml5.lang.array(styles).unique().join(" ");
+ }
+
+ // set attributes on newNode
+ for (attributeName in attributes) {
+ // Setting attributes can cause a js error in IE under certain circumstances
+ // eg. on a <img> under https when it's new attribute value is non-https
+ // TODO: Investigate this further and check for smarter handling
+ try {
+ newNode.setAttribute(attributeName, attributes[attributeName]);
+ } catch(e) {}
+ }
+
+ // IE8 sometimes loses the width/height attributes when those are set before the "src"
+ // so we make sure to set them again
+ if (attributes.src) {
+ if (typeof(attributes.width) !== "undefined") {
+ newNode.setAttribute("width", attributes.width);
+ }
+ if (typeof(attributes.height) !== "undefined") {
+ newNode.setAttribute("height", attributes.height);
+ }
+ }
+ }
+
+ var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
+ function _handleText(oldNode) {
+ var nextSibling = oldNode.nextSibling;
+ if (nextSibling && nextSibling.nodeType === wysihtml5.TEXT_NODE) {
+ // Concatenate text nodes
+ nextSibling.data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(INVISIBLE_SPACE_REG_EXP, "");
+ } else {
+ // \uFEFF = wysihtml5.INVISIBLE_SPACE (used as a hack in certain rich text editing situations)
+ var data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "");
+ return oldNode.ownerDocument.createTextNode(data);
+ }
+ }
+
+ function _handleComment(oldNode) {
+ if (currentRules.comments) {
+ return oldNode.ownerDocument.createComment(oldNode.nodeValue);
+ }
+ }
+
+ // ------------ attribute checks ------------ \\
+ var attributeCheckMethods = {
+ url: (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();
+ });
+ };
+ })(),
+
+ 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;
+ return function(attributeValue) {
+ if (!attributeValue) {
+ return "";
+ }
+ return attributeValue.replace(REG_EXP, "");
+ };
+ })(),
+
+ numbers: (function() {
+ var REG_EXP = /\D/g;
+ return function(attributeValue) {
+ attributeValue = (attributeValue || "").replace(REG_EXP, "");
+ return attributeValue || null;
+ };
+ })(),
+
+ any: (function() {
+ return function(attributeValue) {
+ return attributeValue;
+ };
+ })()
+ };
+
+ // ------------ style converter (converts an html attribute to a style) ------------ \\
+ var addStyleMethods = {
+ align_text: (function() {
+ var mapping = {
+ left: "text-align: left;",
+ right: "text-align: right;",
+ center: "text-align: center;"
+ };
+ return function(attributeValue) {
+ return mapping[String(attributeValue).toLowerCase()];
+ };
+ })(),
+ };
+
+ // ------------ class converter (converts an html attribute to a class name) ------------ \\
+ var addClassMethods = {
+ align_img: (function() {
+ var mapping = {
+ left: "wysiwyg-float-left",
+ right: "wysiwyg-float-right"
+ };
+ return function(attributeValue) {
+ return mapping[String(attributeValue).toLowerCase()];
+ };
+ })(),
+
+ align_text: (function() {
+ var mapping = {
+ left: "wysiwyg-text-align-left",
+ right: "wysiwyg-text-align-right",
+ center: "wysiwyg-text-align-center",
+ justify: "wysiwyg-text-align-justify"
+ };
+ return function(attributeValue) {
+ return mapping[String(attributeValue).toLowerCase()];
+ };
+ })(),
+
+ clear_br: (function() {
+ var mapping = {
+ left: "wysiwyg-clear-left",
+ right: "wysiwyg-clear-right",
+ both: "wysiwyg-clear-both",
+ all: "wysiwyg-clear-both"
+ };
+ return function(attributeValue) {
+ return mapping[String(attributeValue).toLowerCase()];
+ };
+ })(),
+
+ size_font: (function() {
+ var mapping = {
+ "1": "wysiwyg-font-size-xx-small",
+ "2": "wysiwyg-font-size-small",
+ "3": "wysiwyg-font-size-medium",
+ "4": "wysiwyg-font-size-large",
+ "5": "wysiwyg-font-size-x-large",
+ "6": "wysiwyg-font-size-xx-large",
+ "7": "wysiwyg-font-size-xx-large",
+ "-": "wysiwyg-font-size-smaller",
+ "+": "wysiwyg-font-size-larger"
+ };
+ return function(attributeValue) {
+ return mapping[String(attributeValue).charAt(0)];
+ };
+ })()
+ };
+
+ // checks if element is possibly visible
+ var typeCeckMethods = {
+ has_visible_contet: (function() {
+ var txt,
+ isVisible = false,
+ visibleElements = ['img', 'video', 'picture', 'br', 'script', 'noscript',
+ 'style', 'table', 'iframe', 'object', 'embed', 'audio',
+ 'svg', 'input', 'button', 'select','textarea', 'canvas'];
+
+ return function(el) {
+
+ // has visible innertext. so is visible
+ txt = (el.innerText || el.textContent).replace(/\s/g, '');
+ if (txt && txt.length > 0) {
+ return true;
+ }
+
+ // matches list of visible dimensioned elements
+ for (var i = visibleElements.length; i--;) {
+ if (el.querySelector(visibleElements[i])) {
+ return true;
+ }
+ }
+
+ // try to measure dimesions in last resort. (can find only of elements in dom)
+ if (el.offsetWidth && el.offsetWidth > 0 && el.offsetHeight && el.offsetHeight > 0) {
+ return true;
+ }
+
+ return false;
+ };
+ })()
+ };
+
+ var elementHandlingMethods = {
+ unwrap: function (element) {
+ wysihtml5.dom.unwrap(element);
+ },
+
+ remove: function (element) {
+ element.parentNode.removeChild(element);
+ }
+ };
+
+ return parse(elementOrHtml_current, config_current);
+};
+;/**
+ * Checks for empty text node childs and removes them
+ *
+ * @param {Element} node The element in which to cleanup
+ * @example
+ * wysihtml5.dom.removeEmptyTextNodes(element);
+ */
+wysihtml5.dom.removeEmptyTextNodes = function(node) {
+ var childNode,
+ childNodes = wysihtml5.lang.array(node.childNodes).get(),
+ childNodesLength = childNodes.length,
+ i = 0;
+ for (; i<childNodesLength; i++) {
+ childNode = childNodes[i];
+ if (childNode.nodeType === wysihtml5.TEXT_NODE && childNode.data === "") {
+ childNode.parentNode.removeChild(childNode);
+ }
+ }
+};
+;/**
+ * Renames an element (eg. a <div> to a <p>) and keeps its childs
+ *
+ * @param {Element} element The list element which should be renamed
+ * @param {Element} newNodeName The desired tag name
+ *
+ * @example
+ * <!-- Assume the following dom: -->
+ * <ul id="list">
+ * <li>eminem</li>
+ * <li>dr. dre</li>
+ * <li>50 Cent</li>
+ * </ul>
+ *
+ * <script>
+ * wysihtml5.dom.renameElement(document.getElementById("list"), "ol");
+ * </script>
+ *
+ * <!-- Will result in: -->
+ * <ol>
+ * <li>eminem</li>
+ * <li>dr. dre</li>
+ * <li>50 Cent</li>
+ * </ol>
+ */
+wysihtml5.dom.renameElement = function(element, newNodeName) {
+ var newElement = element.ownerDocument.createElement(newNodeName),
+ firstChild;
+ while (firstChild = element.firstChild) {
+ newElement.appendChild(firstChild);
+ }
+ wysihtml5.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
+ element.parentNode.replaceChild(newElement, element);
+ return newElement;
+};
+;/**
+ * Takes an element, removes it and replaces it with it's childs
+ *
+ * @param {Object} node The node which to replace with it's child nodes
+ * @example
+ * <div id="foo">
+ * <span>hello</span>
+ * </div>
+ * <script>
+ * // Remove #foo and replace with it's children
+ * wysihtml5.dom.replaceWithChildNodes(document.getElementById("foo"));
+ * </script>
+ */
+wysihtml5.dom.replaceWithChildNodes = function(node) {
+ if (!node.parentNode) {
+ return;
+ }
+
+ if (!node.firstChild) {
+ node.parentNode.removeChild(node);
+ return;
+ }
+
+ var fragment = node.ownerDocument.createDocumentFragment();
+ while (node.firstChild) {
+ fragment.appendChild(node.firstChild);
+ }
+ node.parentNode.replaceChild(fragment, node);
+ node = fragment = null;
+};
+;/**
+ * Unwraps an unordered/ordered list
+ *
+ * @param {Element} element The list element which should be unwrapped
+ *
+ * @example
+ * <!-- Assume the following dom: -->
+ * <ul id="list">
+ * <li>eminem</li>
+ * <li>dr. dre</li>
+ * <li>50 Cent</li>
+ * </ul>
+ *
+ * <script>
+ * wysihtml5.dom.resolveList(document.getElementById("list"));
+ * </script>
+ *
+ * <!-- Will result in: -->
+ * eminem<br>
+ * dr. dre<br>
+ * 50 Cent<br>
+ */
+(function(dom) {
+ function _isBlockElement(node) {
+ return dom.getStyle("display").from(node) === "block";
+ }
+
+ function _isLineBreak(node) {
+ return node.nodeName === "BR";
+ }
+
+ function _appendLineBreak(element) {
+ var lineBreak = element.ownerDocument.createElement("br");
+ element.appendChild(lineBreak);
+ }
+
+ function resolveList(list, useLineBreaks) {
+ if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
+ return;
+ }
+
+ var doc = list.ownerDocument,
+ fragment = doc.createDocumentFragment(),
+ previousSibling = wysihtml5.dom.domNode(list).prev({ignoreBlankTexts: true}),
+ firstChild,
+ lastChild,
+ isLastChild,
+ shouldAppendLineBreak,
+ paragraph,
+ listItem;
+
+ if (useLineBreaks) {
+ // Insert line break if list is after a non-block element
+ if (previousSibling && !_isBlockElement(previousSibling) && !_isLineBreak(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);
+ }
+ }
+
+ list.parentNode.replaceChild(fragment, list);
+ }
+
+ dom.resolveList = resolveList;
+})(wysihtml5.dom);
+;/**
+ * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way
+ *
+ * Browser Compatibility:
+ * - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted"
+ * - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...)
+ *
+ * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons:
+ * - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'")
+ * - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...)
+ * - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire
+ * - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe
+ * can do anything as if the sandbox attribute wasn't set
+ *
+ * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready
+ * @param {Object} [config] Optional parameters
+ *
+ * @example
+ * new wysihtml5.dom.Sandbox(function(sandbox) {
+ * sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">';
+ * });
+ */
+(function(wysihtml5) {
+ var /**
+ * Default configuration
+ */
+ doc = document,
+ /**
+ * Properties to unset/protect on the window object
+ */
+ windowProperties = [
+ "parent", "top", "opener", "frameElement", "frames",
+ "localStorage", "globalStorage", "sessionStorage", "indexedDB"
+ ],
+ /**
+ * Properties on the window object which are set to an empty function
+ */
+ windowProperties2 = [
+ "open", "close", "openDialog", "showModalDialog",
+ "alert", "confirm", "prompt",
+ "openDatabase", "postMessage",
+ "XMLHttpRequest", "XDomainRequest"
+ ],
+ /**
+ * Properties to unset/protect on the document object
+ */
+ documentProperties = [
+ "referrer",
+ "write", "open", "close"
+ ];
+
+ wysihtml5.dom.Sandbox = Base.extend(
+ /** @scope wysihtml5.dom.Sandbox.prototype */ {
+
+ constructor: function(readyCallback, config) {
+ this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
+ this.config = wysihtml5.lang.object({}).merge(config).get();
+ this.editableArea = this._createIframe();
+ },
+
+ insertInto: function(element) {
+ if (typeof(element) === "string") {
+ element = doc.getElementById(element);
+ }
+
+ element.appendChild(this.editableArea);
+ },
+
+ getIframe: function() {
+ return this.editableArea;
+ },
+
+ getWindow: function() {
+ this._readyError();
+ },
+
+ getDocument: function() {
+ this._readyError();
+ },
+
+ destroy: function() {
+ var iframe = this.getIframe();
+ iframe.parentNode.removeChild(iframe);
+ },
+
+ _readyError: function() {
+ throw new Error("wysihtml5.Sandbox: Sandbox iframe isn't loaded yet");
+ },
+
+ /**
+ * Creates the sandbox iframe
+ *
+ * Some important notes:
+ * - We can't use HTML5 sandbox for now:
+ * setting it causes that the iframe's dom can't be accessed from the outside
+ * Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom
+ * But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired.
+ * In order to make this happen we need to set the "allow-scripts" flag.
+ * A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all.
+ * - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document)
+ * - IE needs to have the security="restricted" attribute set before the iframe is
+ * inserted into the dom tree
+ * - Believe it or not but in IE "security" in document.createElement("iframe") is false, even
+ * though it supports it
+ * - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore
+ * - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely
+ * on the onreadystatechange event
+ */
+ _createIframe: function() {
+ var that = this,
+ iframe = doc.createElement("iframe");
+ iframe.className = "wysihtml5-sandbox";
+ wysihtml5.dom.setAttributes({
+ "security": "restricted",
+ "allowtransparency": "true",
+ "frameborder": 0,
+ "width": 0,
+ "height": 0,
+ "marginwidth": 0,
+ "marginheight": 0
+ }).on(iframe);
+
+ // Setting the src like this prevents ssl warnings in IE6
+ if (wysihtml5.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) {
+ iframe.src = "javascript:'<html></html>'";
+ }
+
+ iframe.onload = function() {
+ iframe.onreadystatechange = iframe.onload = null;
+ that._onLoadIframe(iframe);
+ };
+
+ iframe.onreadystatechange = function() {
+ if (/loaded|complete/.test(iframe.readyState)) {
+ iframe.onreadystatechange = iframe.onload = null;
+ that._onLoadIframe(iframe);
+ }
+ };
+
+ return iframe;
+ },
+
+ /**
+ * Callback for when the iframe has finished loading
+ */
+ _onLoadIframe: function(iframe) {
+ // don't resume when the iframe got unloaded (eg. by removing it from the dom)
+ if (!wysihtml5.dom.contains(doc.documentElement, iframe)) {
+ return;
+ }
+
+ var that = this,
+ iframeWindow = iframe.contentWindow,
+ iframeDocument = iframe.contentWindow.document,
+ charset = doc.characterSet || doc.charset || "utf-8",
+ sandboxHtml = this._getHtml({
+ charset: charset,
+ stylesheets: this.config.stylesheets
+ });
+
+ // Create the basic dom tree including proper DOCTYPE and charset
+ iframeDocument.open("text/html", "replace");
+ iframeDocument.write(sandboxHtml);
+ iframeDocument.close();
+
+ this.getWindow = function() { return iframe.contentWindow; };
+ this.getDocument = function() { return iframe.contentWindow.document; };
+
+ // Catch js errors and pass them to the parent's onerror event
+ // addEventListener("error") doesn't work properly in some browsers
+ // TODO: apparently this doesn't work in IE9!
+ iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
+ throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
+ };
+
+ if (!wysihtml5.browser.supportsSandboxedIframes()) {
+ // Unset a bunch of sensitive variables
+ // Please note: This isn't hack safe!
+ // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information
+ // IE is secure though, which is the most important thing, since IE is the only browser, who
+ // takes over scripts & styles into contentEditable elements when copied from external websites
+ // or applications (Microsoft Word, ...)
+ var i, length;
+ for (i=0, length=windowProperties.length; i<length; i++) {
+ this._unset(iframeWindow, windowProperties[i]);
+ }
+ for (i=0, length=windowProperties2.length; i<length; i++) {
+ this._unset(iframeWindow, windowProperties2[i], wysihtml5.EMPTY_FUNCTION);
+ }
+ for (i=0, length=documentProperties.length; i<length; i++) {
+ this._unset(iframeDocument, documentProperties[i]);
+ }
+ // This doesn't work in Safari 5
+ // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit
+ this._unset(iframeDocument, "cookie", "", true);
+ }
+
+ this.loaded = true;
+
+ // Trigger the callback
+ setTimeout(function() { that.callback(that); }, 0);
+ },
+
+ _getHtml: function(templateVars) {
+ var stylesheets = templateVars.stylesheets,
+ html = "",
+ i = 0,
+ length;
+ stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets;
+ if (stylesheets) {
+ length = stylesheets.length;
+ for (; i<length; i++) {
+ html += '<link rel="stylesheet" href="' + stylesheets[i] + '">';
+ }
+ }
+ templateVars.stylesheets = html;
+
+ return wysihtml5.lang.string(
+ '<!DOCTYPE html><html><head>'
+ + '<meta charset="#{charset}">#{stylesheets}</head>'
+ + '<body></body></html>'
+ ).interpolate(templateVars);
+ },
+
+ /**
+ * Method to unset/override existing variables
+ * @example
+ * // Make cookie unreadable and unwritable
+ * this._unset(document, "cookie", "", true);
+ */
+ _unset: function(object, property, value, setter) {
+ try { object[property] = value; } catch(e) {}
+
+ try { object.__defineGetter__(property, function() { return value; }); } catch(e) {}
+ if (setter) {
+ try { object.__defineSetter__(property, function() {}); } catch(e) {}
+ }
+
+ if (!wysihtml5.browser.crashesWhenDefineProperty(property)) {
+ try {
+ var config = {
+ get: function() { return value; }
+ };
+ if (setter) {
+ config.set = function() {};
+ }
+ Object.defineProperty(object, property, config);
+ } catch(e) {}
+ }
+ }
+ });
+})(wysihtml5);
+;(function(wysihtml5) {
+ var doc = document;
+ wysihtml5.dom.ContentEditableArea = Base.extend({
+ getContentEditable: function() {
+ return this.element;
+ },
+
+ getWindow: function() {
+ return this.element.ownerDocument.defaultView;
+ },
+
+ getDocument: function() {
+ return this.element.ownerDocument;
+ },
+
+ constructor: function(readyCallback, config, contentEditable) {
+ this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
+ this.config = wysihtml5.lang.object({}).merge(config).get();
+ if (contentEditable) {
+ this.element = this._bindElement(contentEditable);
+ } else {
+ this.element = this._createElement();
+ }
+ },
+
+ // creates a new contenteditable and initiates it
+ _createElement: function() {
+ var element = doc.createElement("div");
+ element.className = "wysihtml5-sandbox";
+ this._loadElement(element);
+ return element;
+ },
+
+ // initiates an allready existent contenteditable
+ _bindElement: function(contentEditable) {
+ contentEditable.className = (contentEditable.className && contentEditable.className != '') ? contentEditable.className + " wysihtml5-sandbox" : "wysihtml5-sandbox";
+ this._loadElement(contentEditable, true);
+ return contentEditable;
+ },
+
+ _loadElement: function(element, contentExists) {
+ var that = this;
+ if (!contentExists) {
+ var sandboxHtml = this._getHtml();
+ element.innerHTML = sandboxHtml;
+ }
+
+ this.getWindow = function() { return element.ownerDocument.defaultView; };
+ this.getDocument = function() { return element.ownerDocument; };
+
+ // Catch js errors and pass them to the parent's onerror event
+ // addEventListener("error") doesn't work properly in some browsers
+ // TODO: apparently this doesn't work in IE9!
+ // TODO: figure out and bind the errors logic for contenteditble mode
+ /*iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
+ throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
+ }
+ */
+ this.loaded = true;
+ // Trigger the callback
+ setTimeout(function() { that.callback(that); }, 0);
+ },
+
+ _getHtml: function(templateVars) {
+ return '';
+ }
+
+ });
+})(wysihtml5);
+;(function() {
+ var mapping = {
+ "className": "class"
+ };
+ wysihtml5.dom.setAttributes = function(attributes) {
+ return {
+ on: function(element) {
+ for (var i in attributes) {
+ element.setAttribute(mapping[i] || i, attributes[i]);
+ }
+ }
+ };
+ };
+})();
+;wysihtml5.dom.setStyles = function(styles) {
+ return {
+ on: function(element) {
+ var style = element.style;
+ if (typeof(styles) === "string") {
+ style.cssText += ";" + styles;
+ return;
+ }
+ for (var i in styles) {
+ if (i === "float") {
+ style.cssFloat = styles[i];
+ style.styleFloat = styles[i];
+ } else {
+ style[i] = styles[i];
+ }
+ }
+ }
+ };
+};
+;/**
+ * Simulate HTML5 placeholder attribute
+ *
+ * Needed since
+ * - div[contentEditable] elements don't support it
+ * - older browsers (such as IE8 and Firefox 3.6) don't support it at all
+ *
+ * @param {Object} parent Instance of main wysihtml5.Editor class
+ * @param {Element} view Instance of wysihtml5.views.* class
+ * @param {String} placeholderText
+ *
+ * @example
+ * wysihtml.dom.simulatePlaceholder(this, composer, "Foobar");
+ */
+(function(dom) {
+ dom.simulatePlaceholder = function(editor, view, placeholderText) {
+ var CLASS_NAME = "placeholder",
+ unset = function() {
+ var composerIsVisible = view.element.offsetWidth > 0 && view.element.offsetHeight > 0;
+ if (view.hasPlaceholderSet()) {
+ view.clear();
+ view.element.focus();
+ if (composerIsVisible ) {
+ setTimeout(function() {
+ var sel = view.selection.getSelection();
+ if (!sel.focusNode || !sel.anchorNode) {
+ view.selection.selectNode(view.element.firstChild || view.element);
+ }
+ }, 0);
+ }
+ }
+ 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
+ .on("set_placeholder", set)
+ .on("unset_placeholder", unset)
+ .on("focus:composer", unset)
+ .on("paste:composer", unset)
+ .on("blur:composer", set);
+
+ set();
+ };
+})(wysihtml5.dom);
+;(function(dom) {
+ var documentElement = document.documentElement;
+ if ("textContent" in documentElement) {
+ dom.setTextContent = function(element, text) {
+ element.textContent = text;
+ };
+
+ dom.getTextContent = function(element) {
+ return element.textContent;
+ };
+ } else if ("innerText" in documentElement) {
+ dom.setTextContent = function(element, text) {
+ element.innerText = text;
+ };
+
+ dom.getTextContent = function(element) {
+ return element.innerText;
+ };
+ } else {
+ dom.setTextContent = function(element, text) {
+ element.nodeValue = text;
+ };
+
+ dom.getTextContent = function(element) {
+ return element.nodeValue;
+ };
+ }
+})(wysihtml5.dom);
+
+;/**
+ * Get a set of attribute from one element
+ *
+ * IE gives wrong results for hasAttribute/getAttribute, for example:
+ * var td = document.createElement("td");
+ * td.getAttribute("rowspan"); // => "1" in IE
+ *
+ * Therefore we have to check the element's outerHTML for the attribute
+*/
+
+wysihtml5.dom.getAttribute = function(node, attributeName) {
+ var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
+ attributeName = attributeName.toLowerCase();
+ var nodeName = node.nodeName;
+ if (nodeName == "IMG" && attributeName == "src" && wysihtml5.dom.isLoadedImage(node) === true) {
+ // Get 'src' attribute value via object property since this will always contain the
+ // full absolute url (http://...)
+ // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
+ // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
+ return node.src;
+ } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
+ // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
+ var outerHTML = node.outerHTML.toLowerCase(),
+ // TODO: This might not work for attributes without value: <input disabled>
+ hasAttribute = outerHTML.indexOf(" " + attributeName + "=") != -1;
+
+ return hasAttribute ? node.getAttribute(attributeName) : null;
+ } else{
+ return node.getAttribute(attributeName);
+ }
+};
+;/**
+ * Get all attributes of an element
+ *
+ * IE gives wrong results for hasAttribute/getAttribute, for example:
+ * var td = document.createElement("td");
+ * td.getAttribute("rowspan"); // => "1" in IE
+ *
+ * Therefore we have to check the element's outerHTML for the attribute
+*/
+
+wysihtml5.dom.getAttributes = function(node) {
+ var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly(),
+ nodeName = node.nodeName,
+ attributes = [],
+ attr;
+
+ for (attr in node.attributes) {
+ if ((node.attributes.hasOwnProperty && node.attributes.hasOwnProperty(attr)) || (!node.attributes.hasOwnProperty && Object.prototype.hasOwnProperty.call(node.attributes, attr))) {
+ if (node.attributes[attr].specified) {
+ if (nodeName == "IMG" && node.attributes[attr].name.toLowerCase() == "src" && wysihtml5.dom.isLoadedImage(node) === true) {
+ attributes['src'] = node.src;
+ } else if (wysihtml5.lang.array(['rowspan', 'colspan']).contains(node.attributes[attr].name.toLowerCase()) && HAS_GET_ATTRIBUTE_BUG) {
+ if (node.attributes[attr].value !== 1) {
+ attributes[node.attributes[attr].name] = node.attributes[attr].value;
+ }
+ } else {
+ attributes[node.attributes[attr].name] = node.attributes[attr].value;
+ }
+ }
+ }
+ }
+ return attributes;
+};;/**
+ * Check whether the given node is a proper loaded image
+ * FIXME: Returns undefined when unknown (Chrome, Safari)
+*/
+
+wysihtml5.dom.isLoadedImage = function (node) {
+ try {
+ return node.complete && !node.mozMatchesSelector(":-moz-broken");
+ } catch(e) {
+ if (node.complete && node.readyState === "complete") {
+ return true;
+ }
+ }
+};
+;(function(wysihtml5) {
+
+ var api = wysihtml5.dom;
+
+ var MapCell = function(cell) {
+ this.el = cell;
+ this.isColspan= false;
+ this.isRowspan= false;
+ this.firstCol= true;
+ this.lastCol= true;
+ this.firstRow= true;
+ this.lastRow= true;
+ this.isReal= true;
+ this.spanCollection= [];
+ this.modified = false;
+ };
+
+ var TableModifyerByCell = function (cell, table) {
+ if (cell) {
+ this.cell = cell;
+ this.table = api.getParentElement(cell, { nodeName: ["TABLE"] });
+ } else if (table) {
+ this.table = table;
+ this.cell = this.table.querySelectorAll('th, td')[0];
+ }
+ };
+
+ function queryInList(list, query) {
+ var ret = [],
+ q;
+ for (var e = 0, len = list.length; e < len; e++) {
+ q = list[e].querySelectorAll(query);
+ if (q) {
+ for(var i = q.length; i--; ret.unshift(q[i]));
+ }
+ }
+ return ret;
+ }
+
+ function removeElement(el) {
+ el.parentNode.removeChild(el);
+ }
+
+ function insertAfter(referenceNode, newNode) {
+ referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
+ }
+
+ function nextNode(node, tag) {
+ var element = node.nextSibling;
+ while (element.nodeType !=1) {
+ element = element.nextSibling;
+ if (!tag || tag == element.tagName.toLowerCase()) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ TableModifyerByCell.prototype = {
+
+ addSpannedCellToMap: function(cell, map, r, c, cspan, rspan) {
+ var spanCollect = [],
+ rmax = r + ((rspan) ? parseInt(rspan, 10) - 1 : 0),
+ cmax = c + ((cspan) ? parseInt(cspan, 10) - 1 : 0);
+
+ for (var rr = r; rr <= rmax; rr++) {
+ if (typeof map[rr] == "undefined") { map[rr] = []; }
+ for (var cc = c; cc <= cmax; cc++) {
+ map[rr][cc] = new MapCell(cell);
+ map[rr][cc].isColspan = (cspan && parseInt(cspan, 10) > 1);
+ map[rr][cc].isRowspan = (rspan && parseInt(rspan, 10) > 1);
+ map[rr][cc].firstCol = cc == c;
+ map[rr][cc].lastCol = cc == cmax;
+ map[rr][cc].firstRow = rr == r;
+ map[rr][cc].lastRow = rr == rmax;
+ map[rr][cc].isReal = cc == c && rr == r;
+ map[rr][cc].spanCollection = spanCollect;
+
+ spanCollect.push(map[rr][cc]);
+ }
+ }
+ },
+
+ setCellAsModified: function(cell) {
+ cell.modified = true;
+ if (cell.spanCollection.length > 0) {
+ for (var s = 0, smax = cell.spanCollection.length; s < smax; s++) {
+ cell.spanCollection[s].modified = true;
+ }
+ }
+ },
+
+ setTableMap: function() {
+ var map = [];
+ var tableRows = this.getTableRows(),
+ ridx, row, cells, cidx, cell,
+ c,
+ cspan, rspan;
+
+ for (ridx = 0; ridx < tableRows.length; ridx++) {
+ row = tableRows[ridx];
+ cells = this.getRowCells(row);
+ c = 0;
+ if (typeof map[ridx] == "undefined") { map[ridx] = []; }
+ for (cidx = 0; cidx < cells.length; cidx++) {
+ cell = cells[cidx];
+
+ // If cell allready set means it is set by col or rowspan,
+ // so increase cols index until free col is found
+ while (typeof map[ridx][c] != "undefined") { c++; }
+
+ cspan = api.getAttribute(cell, 'colspan');
+ rspan = api.getAttribute(cell, 'rowspan');
+
+ if (cspan || rspan) {
+ this.addSpannedCellToMap(cell, map, ridx, c, cspan, rspan);
+ c = c + ((cspan) ? parseInt(cspan, 10) : 1);
+ } else {
+ map[ridx][c] = new MapCell(cell);
+ c++;
+ }
+ }
+ }
+ this.map = map;
+ return map;
+ },
+
+ getRowCells: function(row) {
+ var inlineTables = this.table.querySelectorAll('table'),
+ inlineCells = (inlineTables) ? queryInList(inlineTables, 'th, td') : [],
+ allCells = row.querySelectorAll('th, td'),
+ tableCells = (inlineCells.length > 0) ? wysihtml5.lang.array(allCells).without(inlineCells) : allCells;
+
+ return tableCells;
+ },
+
+ getTableRows: function() {
+ var inlineTables = this.table.querySelectorAll('table'),
+ inlineRows = (inlineTables) ? queryInList(inlineTables, 'tr') : [],
+ allRows = this.table.querySelectorAll('tr'),
+ tableRows = (inlineRows.length > 0) ? wysihtml5.lang.array(allRows).without(inlineRows) : allRows;
+
+ return tableRows;
+ },
+
+ getMapIndex: function(cell) {
+ var r_length = this.map.length,
+ c_length = (this.map && this.map[0]) ? this.map[0].length : 0;
+
+ for (var r_idx = 0;r_idx < r_length; r_idx++) {
+ for (var c_idx = 0;c_idx < c_length; c_idx++) {
+ if (this.map[r_idx][c_idx].el === cell) {
+ return {'row': r_idx, 'col': c_idx};
+ }
+ }
+ }
+ return false;
+ },
+
+ getElementAtIndex: function(idx) {
+ this.setTableMap();
+ if (this.map[idx.row] && this.map[idx.row][idx.col] && this.map[idx.row][idx.col].el) {
+ return this.map[idx.row][idx.col].el;
+ }
+ return null;
+ },
+
+ getMapElsTo: function(to_cell) {
+ var els = [];
+ this.setTableMap();
+ this.idx_start = this.getMapIndex(this.cell);
+ this.idx_end = this.getMapIndex(to_cell);
+
+ // switch indexes if start is bigger than end
+ if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
+ var temp_idx = this.idx_start;
+ this.idx_start = this.idx_end;
+ this.idx_end = temp_idx;
+ }
+ if (this.idx_start.col > this.idx_end.col) {
+ var temp_cidx = this.idx_start.col;
+ this.idx_start.col = this.idx_end.col;
+ this.idx_end.col = temp_cidx;
+ }
+
+ if (this.idx_start != null && this.idx_end != null) {
+ for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
+ for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
+ els.push(this.map[row][col].el);
+ }
+ }
+ }
+ return els;
+ },
+
+ orderSelectionEnds: function(secondcell) {
+ this.setTableMap();
+ this.idx_start = this.getMapIndex(this.cell);
+ this.idx_end = this.getMapIndex(secondcell);
+
+ // switch indexes if start is bigger than end
+ if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
+ var temp_idx = this.idx_start;
+ this.idx_start = this.idx_end;
+ this.idx_end = temp_idx;
+ }
+ if (this.idx_start.col > this.idx_end.col) {
+ var temp_cidx = this.idx_start.col;
+ this.idx_start.col = this.idx_end.col;
+ this.idx_end.col = temp_cidx;
+ }
+
+ return {
+ "start": this.map[this.idx_start.row][this.idx_start.col].el,
+ "end": this.map[this.idx_end.row][this.idx_end.col].el
+ };
+ },
+
+ createCells: function(tag, nr, attrs) {
+ var doc = this.table.ownerDocument,
+ frag = doc.createDocumentFragment(),
+ cell;
+ for (var i = 0; i < nr; i++) {
+ cell = doc.createElement(tag);
+
+ if (attrs) {
+ for (var attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ cell.setAttribute(attr, attrs[attr]);
+ }
+ }
+ }
+
+ // add non breaking space
+ cell.appendChild(document.createTextNode("\u00a0"));
+
+ frag.appendChild(cell);
+ }
+ return frag;
+ },
+
+ // Returns next real cell (not part of spanned cell unless first) on row if selected index is not real. I no real cells -1 will be returned
+ correctColIndexForUnreals: function(col, row) {
+ var r = this.map[row],
+ corrIdx = -1;
+ for (var i = 0, max = col; i < col; i++) {
+ if (r[i].isReal){
+ corrIdx++;
+ }
+ }
+ return corrIdx;
+ },
+
+ getLastNewCellOnRow: function(row, rowLimit) {
+ var cells = this.getRowCells(row),
+ cell, idx;
+
+ for (var cidx = 0, cmax = cells.length; cidx < cmax; cidx++) {
+ cell = cells[cidx];
+ idx = this.getMapIndex(cell);
+ if (idx === false || (typeof rowLimit != "undefined" && idx.row != rowLimit)) {
+ return cell;
+ }
+ }
+ return null;
+ },
+
+ removeEmptyTable: function() {
+ var cells = this.table.querySelectorAll('td, th');
+ if (!cells || cells.length == 0) {
+ removeElement(this.table);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ // Splits merged cell on row to unique cells
+ splitRowToCells: function(cell) {
+ if (cell.isColspan) {
+ var colspan = parseInt(api.getAttribute(cell.el, 'colspan') || 1, 10),
+ cType = cell.el.tagName.toLowerCase();
+ if (colspan > 1) {
+ var newCells = this.createCells(cType, colspan -1);
+ insertAfter(cell.el, newCells);
+ }
+ cell.el.removeAttribute('colspan');
+ }
+ },
+
+ getRealRowEl: function(force, idx) {
+ var r = null,
+ c = null;
+
+ idx = idx || this.idx;
+
+ for (var cidx = 0, cmax = this.map[idx.row].length; cidx < cmax; cidx++) {
+ c = this.map[idx.row][cidx];
+ if (c.isReal) {
+ r = api.getParentElement(c.el, { nodeName: ["TR"] });
+ if (r) {
+ return r;
+ }
+ }
+ }
+
+ if (r === null && force) {
+ r = api.getParentElement(this.map[idx.row][idx.col].el, { nodeName: ["TR"] }) || null;
+ }
+
+ return r;
+ },
+
+ injectRowAt: function(row, col, colspan, cType, c) {
+ var r = this.getRealRowEl(false, {'row': row, 'col': col}),
+ new_cells = this.createCells(cType, colspan);
+
+ if (r) {
+ var n_cidx = this.correctColIndexForUnreals(col, row);
+ if (n_cidx >= 0) {
+ insertAfter(this.getRowCells(r)[n_cidx], new_cells);
+ } else {
+ r.insertBefore(new_cells, r.firstChild);
+ }
+ } else {
+ var rr = this.table.ownerDocument.createElement('tr');
+ rr.appendChild(new_cells);
+ insertAfter(api.getParentElement(c.el, { nodeName: ["TR"] }), rr);
+ }
+ },
+
+ canMerge: function(to) {
+ this.to = to;
+ this.setTableMap();
+ this.idx_start = this.getMapIndex(this.cell);
+ this.idx_end = this.getMapIndex(this.to);
+
+ // switch indexes if start is bigger than end
+ if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
+ var temp_idx = this.idx_start;
+ this.idx_start = this.idx_end;
+ this.idx_end = temp_idx;
+ }
+ if (this.idx_start.col > this.idx_end.col) {
+ var temp_cidx = this.idx_start.col;
+ this.idx_start.col = this.idx_end.col;
+ this.idx_end.col = temp_cidx;
+ }
+
+ for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
+ for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
+ if (this.map[row][col].isColspan || this.map[row][col].isRowspan) {
+ return false;
+ }
+ }
+ }
+ return true;
+ },
+
+ decreaseCellSpan: function(cell, span) {
+ var nr = parseInt(api.getAttribute(cell.el, span), 10) - 1;
+ if (nr >= 1) {
+ cell.el.setAttribute(span, nr);
+ } else {
+ cell.el.removeAttribute(span);
+ if (span == 'colspan') {
+ cell.isColspan = false;
+ }
+ if (span == 'rowspan') {
+ cell.isRowspan = false;
+ }
+ cell.firstCol = true;
+ cell.lastCol = true;
+ cell.firstRow = true;
+ cell.lastRow = true;
+ cell.isReal = true;
+ }
+ },
+
+ removeSurplusLines: function() {
+ var row, cell, ridx, rmax, cidx, cmax, allRowspan;
+
+ this.setTableMap();
+ if (this.map) {
+ ridx = 0;
+ rmax = this.map.length;
+ for (;ridx < rmax; ridx++) {
+ row = this.map[ridx];
+ allRowspan = true;
+ cidx = 0;
+ cmax = row.length;
+ for (; cidx < cmax; cidx++) {
+ cell = row[cidx];
+ if (!(api.getAttribute(cell.el, "rowspan") && parseInt(api.getAttribute(cell.el, "rowspan"), 10) > 1 && cell.firstRow !== true)) {
+ allRowspan = false;
+ break;
+ }
+ }
+ if (allRowspan) {
+ cidx = 0;
+ for (; cidx < cmax; cidx++) {
+ this.decreaseCellSpan(row[cidx], 'rowspan');
+ }
+ }
+ }
+
+ // remove rows without cells
+ var tableRows = this.getTableRows();
+ ridx = 0;
+ rmax = tableRows.length;
+ for (;ridx < rmax; ridx++) {
+ row = tableRows[ridx];
+ if (row.childNodes.length == 0 && (/^\s*$/.test(row.textContent || row.innerText))) {
+ removeElement(row);
+ }
+ }
+ }
+ },
+
+ fillMissingCells: function() {
+ var r_max = 0,
+ c_max = 0,
+ prevcell = null;
+
+ this.setTableMap();
+ if (this.map) {
+
+ // find maximal dimensions of broken table
+ r_max = this.map.length;
+ for (var ridx = 0; ridx < r_max; ridx++) {
+ if (this.map[ridx].length > c_max) { c_max = this.map[ridx].length; }
+ }
+
+ for (var row = 0; row < r_max; row++) {
+ for (var col = 0; col < c_max; col++) {
+ if (this.map[row] && !this.map[row][col]) {
+ if (col > 0) {
+ this.map[row][col] = new MapCell(this.createCells('td', 1));
+ prevcell = this.map[row][col-1];
+ if (prevcell && prevcell.el && prevcell.el.parent) { // if parent does not exist element is removed from dom
+ insertAfter(this.map[row][col-1].el, this.map[row][col].el);
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ rectify: function() {
+ if (!this.removeEmptyTable()) {
+ this.removeSurplusLines();
+ this.fillMissingCells();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ unmerge: function() {
+ if (this.rectify()) {
+ this.setTableMap();
+ this.idx = this.getMapIndex(this.cell);
+
+ if (this.idx) {
+ var thisCell = this.map[this.idx.row][this.idx.col],
+ colspan = (api.getAttribute(thisCell.el, "colspan")) ? parseInt(api.getAttribute(thisCell.el, "colspan"), 10) : 1,
+ cType = thisCell.el.tagName.toLowerCase();
+
+ if (thisCell.isRowspan) {
+ var rowspan = parseInt(api.getAttribute(thisCell.el, "rowspan"), 10);
+ if (rowspan > 1) {
+ for (var nr = 1, maxr = rowspan - 1; nr <= maxr; nr++){
+ this.injectRowAt(this.idx.row + nr, this.idx.col, colspan, cType, thisCell);
+ }
+ }
+ thisCell.el.removeAttribute('rowspan');
+ }
+ this.splitRowToCells(thisCell);
+ }
+ }
+ },
+
+ // merges cells from start cell (defined in creating obj) to "to" cell
+ merge: function(to) {
+ if (this.rectify()) {
+ if (this.canMerge(to)) {
+ var rowspan = this.idx_end.row - this.idx_start.row + 1,
+ colspan = this.idx_end.col - this.idx_start.col + 1;
+
+ for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
+ for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
+
+ if (row == this.idx_start.row && col == this.idx_start.col) {
+ if (rowspan > 1) {
+ this.map[row][col].el.setAttribute('rowspan', rowspan);
+ }
+ if (colspan > 1) {
+ this.map[row][col].el.setAttribute('colspan', colspan);
+ }
+ } else {
+ // transfer content
+ if (!(/^\s*<br\/?>\s*$/.test(this.map[row][col].el.innerHTML.toLowerCase()))) {
+ this.map[this.idx_start.row][this.idx_start.col].el.innerHTML += ' ' + this.map[row][col].el.innerHTML;
+ }
+ removeElement(this.map[row][col].el);
+ }
+ }
+ }
+ this.rectify();
+ } else {
+ if (window.console) {
+ console.log('Do not know how to merge allready merged cells.');
+ }
+ }
+ }
+ },
+
+ // Decreases rowspan of a cell if it is done on first cell of rowspan row (real cell)
+ // Cell is moved to next row (if it is real)
+ collapseCellToNextRow: function(cell) {
+ var cellIdx = this.getMapIndex(cell.el),
+ newRowIdx = cellIdx.row + 1,
+ newIdx = {'row': newRowIdx, 'col': cellIdx.col};
+
+ if (newRowIdx < this.map.length) {
+
+ var row = this.getRealRowEl(false, newIdx);
+ if (row !== null) {
+ var n_cidx = this.correctColIndexForUnreals(newIdx.col, newIdx.row);
+ if (n_cidx >= 0) {
+ insertAfter(this.getRowCells(row)[n_cidx], cell.el);
+ } else {
+ var lastCell = this.getLastNewCellOnRow(row, newRowIdx);
+ if (lastCell !== null) {
+ insertAfter(lastCell, cell.el);
+ } else {
+ row.insertBefore(cell.el, row.firstChild);
+ }
+ }
+ if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
+ cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
+ } else {
+ cell.el.removeAttribute('rowspan');
+ }
+ }
+ }
+ },
+
+ // Removes a cell when removing a row
+ // If is rowspan cell then decreases the rowspan
+ // and moves cell to next row if needed (is first cell of rowspan)
+ removeRowCell: function(cell) {
+ if (cell.isReal) {
+ if (cell.isRowspan) {
+ this.collapseCellToNextRow(cell);
+ } else {
+ removeElement(cell.el);
+ }
+ } else {
+ if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
+ cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
+ } else {
+ cell.el.removeAttribute('rowspan');
+ }
+ }
+ },
+
+ getRowElementsByCell: function() {
+ var cells = [];
+ this.setTableMap();
+ this.idx = this.getMapIndex(this.cell);
+ if (this.idx !== false) {
+ var modRow = this.map[this.idx.row];
+ for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
+ if (modRow[cidx].isReal) {
+ cells.push(modRow[cidx].el);
+ }
+ }
+ }
+ return cells;
+ },
+
+ getColumnElementsByCell: function() {
+ var cells = [];
+ this.setTableMap();
+ this.idx = this.getMapIndex(this.cell);
+ if (this.idx !== false) {
+ for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
+ if (this.map[ridx][this.idx.col] && this.map[ridx][this.idx.col].isReal) {
+ cells.push(this.map[ridx][this.idx.col].el);
+ }
+ }
+ }
+ return cells;
+ },
+
+ // Removes the row of selected cell
+ removeRow: function() {
+ var oldRow = api.getParentElement(this.cell, { nodeName: ["TR"] });
+ if (oldRow) {
+ this.setTableMap();
+ this.idx = this.getMapIndex(this.cell);
+ if (this.idx !== false) {
+ var modRow = this.map[this.idx.row];
+ for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
+ if (!modRow[cidx].modified) {
+ this.setCellAsModified(modRow[cidx]);
+ this.removeRowCell(modRow[cidx]);
+ }
+ }
+ }
+ removeElement(oldRow);
+ }
+ },
+
+ removeColCell: function(cell) {
+ if (cell.isColspan) {
+ if (parseInt(api.getAttribute(cell.el, 'colspan'), 10) > 2) {
+ cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) - 1);
+ } else {
+ cell.el.removeAttribute('colspan');
+ }
+ } else if (cell.isReal) {
+ removeElement(cell.el);
+ }
+ },
+
+ removeColumn: function() {
+ this.setTableMap();
+ this.idx = this.getMapIndex(this.cell);
+ if (this.idx !== false) {
+ for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
+ if (!this.map[ridx][this.idx.col].modified) {
+ this.setCellAsModified(this.map[ridx][this.idx.col]);
+ this.removeColCell(this.map[ridx][this.idx.col]);
+ }
+ }
+ }
+ },
+
+ // removes row or column by selected cell element
+ remove: function(what) {
+ if (this.rectify()) {
+ switch (what) {
+ case 'row':
+ this.removeRow();
+ break;
+ case 'column':
+ this.removeColumn();
+ break;
+ }
+ this.rectify();
+ }
+ },
+
+ addRow: function(where) {
+ var doc = this.table.ownerDocument;
+
+ this.setTableMap();
+ this.idx = this.getMapIndex(this.cell);
+ if (where == "below" && api.getAttribute(this.cell, 'rowspan')) {
+ this.idx.row = this.idx.row + parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1;
+ }
+
+ if (this.idx !== false) {
+ var modRow = this.map[this.idx.row],
+ newRow = doc.createElement('tr');
+
+ for (var ridx = 0, rmax = modRow.length; ridx < rmax; ridx++) {
+ if (!modRow[ridx].modified) {
+ this.setCellAsModified(modRow[ridx]);
+ this.addRowCell(modRow[ridx], newRow, where);
+ }
+ }
+
+ switch (where) {
+ case 'below':
+ insertAfter(this.getRealRowEl(true), newRow);
+ break;
+ case 'above':
+ var cr = api.getParentElement(this.map[this.idx.row][this.idx.col].el, { nodeName: ["TR"] });
+ if (cr) {
+ cr.parentNode.insertBefore(newRow, cr);
+ }
+ break;
+ }
+ }
+ },
+
+ addRowCell: function(cell, row, where) {
+ var colSpanAttr = (cell.isColspan) ? {"colspan" : api.getAttribute(cell.el, 'colspan')} : null;
+ if (cell.isReal) {
+ if (where != 'above' && cell.isRowspan) {
+ cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el,'rowspan'), 10) + 1);
+ } else {
+ row.appendChild(this.createCells('td', 1, colSpanAttr));
+ }
+ } else {
+ if (where != 'above' && cell.isRowspan && cell.lastRow) {
+ row.appendChild(this.createCells('td', 1, colSpanAttr));
+ } else if (c.isRowspan) {
+ cell.el.attr('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) + 1);
+ }
+ }
+ },
+
+ add: function(where) {
+ if (this.rectify()) {
+ if (where == 'below' || where == 'above') {
+ this.addRow(where);
+ }
+ if (where == 'before' || where == 'after') {
+ this.addColumn(where);
+ }
+ }
+ },
+
+ addColCell: function (cell, ridx, where) {
+ var doAdd,
+ cType = cell.el.tagName.toLowerCase();
+
+ // defines add cell vs expand cell conditions
+ // true means add
+ switch (where) {
+ case "before":
+ doAdd = (!cell.isColspan || cell.firstCol);
+ break;
+ case "after":
+ doAdd = (!cell.isColspan || cell.lastCol || (cell.isColspan && c.el == this.cell));
+ break;
+ }
+
+ if (doAdd){
+ // adds a cell before or after current cell element
+ switch (where) {
+ case "before":
+ cell.el.parentNode.insertBefore(this.createCells(cType, 1), cell.el);
+ break;
+ case "after":
+ insertAfter(cell.el, this.createCells(cType, 1));
+ break;
+ }
+
+ // handles if cell has rowspan
+ if (cell.isRowspan) {
+ this.handleCellAddWithRowspan(cell, ridx+1, where);
+ }
+
+ } else {
+ // expands cell
+ cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) + 1);
+ }
+ },
+
+ addColumn: function(where) {
+ var row, modCell;
+
+ this.setTableMap();
+ this.idx = this.getMapIndex(this.cell);
+ if (where == "after" && api.getAttribute(this.cell, 'colspan')) {
+ this.idx.col = this.idx.col + parseInt(api.getAttribute(this.cell, 'colspan'), 10) - 1;
+ }
+
+ if (this.idx !== false) {
+ for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++ ) {
+ row = this.map[ridx];
+ if (row[this.idx.col]) {
+ modCell = row[this.idx.col];
+ if (!modCell.modified) {
+ this.setCellAsModified(modCell);
+ this.addColCell(modCell, ridx , where);
+ }
+ }
+ }
+ }
+ },
+
+ handleCellAddWithRowspan: function (cell, ridx, where) {
+ var addRowsNr = parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1,
+ crow = api.getParentElement(cell.el, { nodeName: ["TR"] }),
+ cType = cell.el.tagName.toLowerCase(),
+ cidx, temp_r_cells,
+ doc = this.table.ownerDocument,
+ nrow;
+
+ for (var i = 0; i < addRowsNr; i++) {
+ cidx = this.correctColIndexForUnreals(this.idx.col, (ridx + i));
+ crow = nextNode(crow, 'tr');
+ if (crow) {
+ if (cidx > 0) {
+ switch (where) {
+ case "before":
+ temp_r_cells = this.getRowCells(crow);
+ if (cidx > 0 && this.map[ridx + i][this.idx.col].el != temp_r_cells[cidx] && cidx == temp_r_cells.length - 1) {
+ insertAfter(temp_r_cells[cidx], this.createCells(cType, 1));
+ } else {
+ temp_r_cells[cidx].parentNode.insertBefore(this.createCells(cType, 1), temp_r_cells[cidx]);
+ }
+
+ break;
+ case "after":
+ insertAfter(this.getRowCells(crow)[cidx], this.createCells(cType, 1));
+ break;
+ }
+ } else {
+ crow.insertBefore(this.createCells(cType, 1), crow.firstChild);
+ }
+ } else {
+ nrow = doc.createElement('tr');
+ nrow.appendChild(this.createCells(cType, 1));
+ this.table.appendChild(nrow);
+ }
+ }
+ }
+ };
+
+ api.table = {
+ getCellsBetween: function(cell1, cell2) {
+ var c1 = new TableModifyerByCell(cell1);
+ return c1.getMapElsTo(cell2);
+ },
+
+ addCells: function(cell, where) {
+ var c = new TableModifyerByCell(cell);
+ c.add(where);
+ },
+
+ removeCells: function(cell, what) {
+ var c = new TableModifyerByCell(cell);
+ c.remove(what);
+ },
+
+ mergeCellsBetween: function(cell1, cell2) {
+ var c1 = new TableModifyerByCell(cell1);
+ c1.merge(cell2);
+ },
+
+ unmergeCell: function(cell) {
+ var c = new TableModifyerByCell(cell);
+ c.unmerge();
+ },
+
+ orderSelectionEnds: function(cell, cell2) {
+ var c = new TableModifyerByCell(cell);
+ return c.orderSelectionEnds(cell2);
+ },
+
+ indexOf: function(cell) {
+ var c = new TableModifyerByCell(cell);
+ c.setTableMap();
+ return c.getMapIndex(cell);
+ },
+
+ findCell: function(table, idx) {
+ var c = new TableModifyerByCell(null, table);
+ return c.getElementAtIndex(idx);
+ },
+
+ findRowByCell: function(cell) {
+ var c = new TableModifyerByCell(cell);
+ return c.getRowElementsByCell();
+ },
+
+ findColumnByCell: function(cell) {
+ var c = new TableModifyerByCell(cell);
+ return c.getColumnElementsByCell();
+ },
+
+ canMerge: function(cell1, cell2) {
+ var c = new TableModifyerByCell(cell1);
+ return c.canMerge(cell2);
+ }
+ };
+
+
+
+})(wysihtml5);
+;// does a selector query on element or array of elements
+
+wysihtml5.dom.query = function(elements, query) {
+ var ret = [],
+ q;
+
+ if (elements.nodeType) {
+ elements = [elements];
+ }
+
+ for (var e = 0, len = elements.length; e < len; e++) {
+ q = elements[e].querySelectorAll(query);
+ if (q) {
+ for(var i = q.length; i--; ret.unshift(q[i]));
+ }
+ }
+ return ret;
+};
+;wysihtml5.dom.compareDocumentPosition = (function() {
+ var documentElement = document.documentElement;
+ if (documentElement.compareDocumentPosition) {
+ return function(container, element) {
+ return container.compareDocumentPosition(element);
+ };
+ } else {
+ return function( container, element ) {
+ // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license
+ var thisOwner, otherOwner;
+
+ if( container.nodeType === 9) // Node.DOCUMENT_NODE
+ thisOwner = container;
+ else
+ thisOwner = container.ownerDocument;
+
+ if( element.nodeType === 9) // Node.DOCUMENT_NODE
+ otherOwner = element;
+ else
+ otherOwner = element.ownerDocument;
+
+ if( container === element ) return 0;
+ if( container === element.ownerDocument ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
+ if( container.ownerDocument === element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
+ if( thisOwner !== otherOwner ) return 1; // Node.DOCUMENT_POSITION_DISCONNECTED;
+
+ // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
+ if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml5.lang.array(container.childNodes).indexOf( element ) !== -1)
+ return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
+
+ if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml5.lang.array(element.childNodes).indexOf( container ) !== -1)
+ return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
+
+ var point = container;
+ var parents = [ ];
+ var previous = null;
+ while( point ) {
+ if( point == element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
+ parents.push( point );
+ point = point.parentNode;
+ }
+ point = element;
+ previous = null;
+ while( point ) {
+ if( point == container ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
+ var location_index = wysihtml5.lang.array(parents).indexOf( point );
+ if( location_index !== -1) {
+ var smallest_common_ancestor = parents[ location_index ];
+ var this_index = wysihtml5.lang.array(smallest_common_ancestor.childNodes).indexOf( parents[location_index - 1]);//smallest_common_ancestor.childNodes.toArray().indexOf( parents[location_index - 1] );
+ var other_index = wysihtml5.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous );
+ if( this_index > other_index ) {
+ return 2; //Node.DOCUMENT_POSITION_PRECEDING;
+ }
+ else {
+ return 4; //Node.DOCUMENT_POSITION_FOLLOWING;
+ }
+ }
+ previous = point;
+ point = point.parentNode;
+ }
+ return 1; //Node.DOCUMENT_POSITION_DISCONNECTED;
+ };
+ }
+})();
+;wysihtml5.dom.unwrap = function(node) {
+ if (node.parentNode) {
+ while (node.lastChild) {
+ wysihtml5.dom.insert(node.lastChild).after(node);
+ }
+ node.parentNode.removeChild(node);
+ }
+};;/*
+ * Methods for fetching pasted html before it gets inserted into content
+**/
+
+/* Modern event.clipboardData driven approach.
+ * Advantage is that it does not have to loose selection or modify dom to catch the data.
+ * IE does not support though.
+**/
+wysihtml5.dom.getPastedHtml = function(event) {
+ var html;
+ if (event.clipboardData) {
+ if (wysihtml5.lang.array(event.clipboardData.types).contains('text/html')) {
+ html = event.clipboardData.getData('text/html');
+ } else if (wysihtml5.lang.array(event.clipboardData.types).contains('text/plain')) {
+ html = wysihtml5.lang.string(event.clipboardData.getData('text/plain')).escapeHTML(true, true);
+ }
+ }
+ return html;
+};
+
+/* Older temprorary contenteditable as paste source catcher method for fallbacks */
+wysihtml5.dom.getPastedHtmlWithDiv = function (composer, f) {
+ var selBookmark = composer.selection.getBookmark(),
+ doc = composer.element.ownerDocument,
+ cleanerDiv = doc.createElement('DIV');
+
+ doc.body.appendChild(cleanerDiv);
+
+ cleanerDiv.style.width = "1px";
+ cleanerDiv.style.height = "1px";
+ cleanerDiv.style.overflow = "hidden";
+
+ cleanerDiv.setAttribute('contenteditable', 'true');
+ cleanerDiv.focus();
+
+ setTimeout(function () {
+ composer.selection.setBookmark(selBookmark);
+ f(cleanerDiv.innerHTML);
+ cleanerDiv.parentNode.removeChild(cleanerDiv);
+ }, 0);
+};;/**
+ * Fix most common html formatting misbehaviors of browsers implementation when inserting
+ * content via copy & paste contentEditable
+ *
+ * @author Christopher Blum
+ */
+wysihtml5.quirks.cleanPastedHTML = (function() {
+
+ var styleToRegex = function (styleStr) {
+ var trimmedStr = wysihtml5.lang.string(styleStr).trim(),
+ escapedStr = trimmedStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+
+ return new RegExp("^((?!^" + escapedStr + "$).)*$", "i");
+ };
+
+ var extendRulesWithStyleExceptions = function (rules, exceptStyles) {
+ var newRules = wysihtml5.lang.object(rules).clone(true),
+ tag, style;
+
+ for (tag in newRules.tags) {
+
+ if (newRules.tags.hasOwnProperty(tag)) {
+ if (newRules.tags[tag].keep_styles) {
+ for (style in newRules.tags[tag].keep_styles) {
+ if (newRules.tags[tag].keep_styles.hasOwnProperty(style)) {
+ if (exceptStyles[style]) {
+ newRules.tags[tag].keep_styles[style] = styleToRegex(exceptStyles[style]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return newRules;
+ };
+
+ var pickRuleset = function(ruleset, html) {
+ var pickedSet, defaultSet;
+
+ if (!ruleset) {
+ return null;
+ }
+
+ for (var i = 0, max = ruleset.length; i < max; i++) {
+ if (!ruleset[i].condition) {
+ defaultSet = ruleset[i].set;
+ }
+ if (ruleset[i].condition && ruleset[i].condition.test(html)) {
+ return ruleset[i].set;
+ }
+ }
+
+ return defaultSet;
+ };
+
+ return function(html, options) {
+ var exceptStyles = {
+ 'color': wysihtml5.dom.getStyle("color").from(options.referenceNode),
+ 'fontSize': wysihtml5.dom.getStyle("font-size").from(options.referenceNode)
+ },
+ rules = extendRulesWithStyleExceptions(pickRuleset(options.rules, html) || {}, exceptStyles),
+ newHtml;
+
+ newHtml = wysihtml5.dom.parse(html, {
+ "rules": rules,
+ "cleanUp": true, // <span> elements, empty or without attributes, should be removed/replaced with their content
+ "context": options.referenceNode.ownerDocument,
+ "uneditableClass": options.uneditableClass,
+ "clearInternals" : true, // don't paste temprorary selection and other markings
+ "unjoinNbsps" : true
+ });
+
+ return newHtml;
+ };
+
+})();;/**
+ * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
+ *
+ * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
+ * @exaple
+ * wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
+ */
+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);
+ };
+
+ return function(composer) {
+ wysihtml5.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
+ };
+})();
+;// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
+//
+// In Firefox this:
+// var d = document.createElement("div");
+// d.innerHTML ='<a href="~"></a>';
+// d.innerHTML;
+// will result in:
+// <a href="%7E"></a>
+// which is wrong
+(function(wysihtml5) {
+ var TILDE_ESCAPED = "%7E";
+ wysihtml5.quirks.getCorrectInnerHTML = function(element) {
+ var innerHTML = element.innerHTML;
+ if (innerHTML.indexOf(TILDE_ESCAPED) === -1) {
+ return innerHTML;
+ }
+
+ var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"),
+ url,
+ urlToSearch,
+ length,
+ i;
+ for (i=0, length=elementsWithTilde.length; i<length; i++) {
+ url = elementsWithTilde[i].href || elementsWithTilde[i].src;
+ urlToSearch = wysihtml5.lang.string(url).replace("~").by(TILDE_ESCAPED);
+ innerHTML = wysihtml5.lang.string(innerHTML).replace(urlToSearch).by(url);
+ }
+ return innerHTML;
+ };
+})(wysihtml5);
+;/**
+ * Force rerendering of a given element
+ * Needed to fix display misbehaviors of IE
+ *
+ * @param {Element} element The element object which needs to be rerendered
+ * @example
+ * wysihtml5.quirks.redraw(document.body);
+ */
+(function(wysihtml5) {
+ var CLASS_NAME = "wysihtml5-quirks-redraw";
+
+ wysihtml5.quirks.redraw = function(element) {
+ wysihtml5.dom.addClass(element, CLASS_NAME);
+ wysihtml5.dom.removeClass(element, CLASS_NAME);
+
+ // Following hack is needed for firefox to make sure that image resize handles are properly removed
+ try {
+ var doc = element.ownerDocument;
+ doc.execCommand("italic", false, null);
+ doc.execCommand("italic", false, null);
+ } catch(e) {}
+ };
+})(wysihtml5);
+;wysihtml5.quirks.tableCellsSelection = function(editable, editor) {
+
+ var dom = wysihtml5.dom,
+ select = {
+ table: null,
+ start: null,
+ end: null,
+ cells: null,
+ select: selectCells
+ },
+ selection_class = "wysiwyg-tmp-selected-cell",
+ moveHandler = null,
+ upHandler = null;
+
+ function init () {
+
+ dom.observe(editable, "mousedown", function(event) {
+ var target = wysihtml5.dom.getParentElement(event.target, { nodeName: ["TD", "TH"] });
+ if (target) {
+ handleSelectionMousedown(target);
+ }
+ });
+
+ return select;
+ }
+
+ function handleSelectionMousedown (target) {
+ select.start = target;
+ select.end = target;
+ select.cells = [target];
+ select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
+
+ if (select.table) {
+ removeCellSelections();
+ dom.addClass(target, selection_class);
+ moveHandler = dom.observe(editable, "mousemove", handleMouseMove);
+ upHandler = dom.observe(editable, "mouseup", handleMouseUp);
+ editor.fire("tableselectstart").fire("tableselectstart:composer");
+ }
+ }
+
+ // remove all selection classes
+ function removeCellSelections () {
+ if (editable) {
+ var selectedCells = editable.querySelectorAll('.' + selection_class);
+ if (selectedCells.length > 0) {
+ for (var i = 0; i < selectedCells.length; i++) {
+ dom.removeClass(selectedCells[i], selection_class);
+ }
+ }
+ }
+ }
+
+ function addSelections (cells) {
+ for (var i = 0; i < cells.length; i++) {
+ dom.addClass(cells[i], selection_class);
+ }
+ }
+
+ function handleMouseMove (event) {
+ var curTable = null,
+ cell = dom.getParentElement(event.target, { nodeName: ["TD","TH"] }),
+ oldEnd;
+
+ if (cell && select.table && select.start) {
+ curTable = dom.getParentElement(cell, { nodeName: ["TABLE"] });
+ if (curTable && curTable === select.table) {
+ removeCellSelections();
+ oldEnd = select.end;
+ select.end = cell;
+ select.cells = dom.table.getCellsBetween(select.start, cell);
+ if (select.cells.length > 1) {
+ editor.composer.selection.deselect();
+ }
+ addSelections(select.cells);
+ if (select.end !== oldEnd) {
+ editor.fire("tableselectchange").fire("tableselectchange:composer");
+ }
+ }
+ }
+ }
+
+ function handleMouseUp (event) {
+ moveHandler.stop();
+ upHandler.stop();
+ editor.fire("tableselect").fire("tableselect:composer");
+ setTimeout(function() {
+ bindSideclick();
+ },0);
+ }
+
+ function bindSideclick () {
+ var sideClickHandler = dom.observe(editable.ownerDocument, "click", function(event) {
+ sideClickHandler.stop();
+ if (dom.getParentElement(event.target, { nodeName: ["TABLE"] }) != select.table) {
+ removeCellSelections();
+ select.table = null;
+ select.start = null;
+ select.end = null;
+ editor.fire("tableunselect").fire("tableunselect:composer");
+ }
+ });
+ }
+
+ function selectCells (start, end) {
+ select.start = start;
+ select.end = end;
+ select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
+ selectedCells = dom.table.getCellsBetween(select.start, select.end);
+ addSelections(selectedCells);
+ bindSideclick();
+ editor.fire("tableselect").fire("tableselect:composer");
+ }
+
+ return init();
+
+};
+;(function(wysihtml5) {
+ var RGBA_REGEX = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
+ RGB_REGEX = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
+ HEX6_REGEX = /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
+ HEX3_REGEX = /^#([0-9a-f])([0-9a-f])([0-9a-f])/i;
+
+ var param_REGX = function (p) {
+ return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+" , "gi");
+ };
+
+ wysihtml5.quirks.styleParser = {
+
+ parseColor: function(stylesStr, paramName) {
+ var paramRegex = param_REGX(paramName),
+ params = stylesStr.match(paramRegex),
+ radix = 10,
+ str, colorMatch;
+
+ if (params) {
+ for (var i = params.length; i--;) {
+ params[i] = wysihtml5.lang.string(params[i].split(':')[1]).trim();
+ }
+ str = params[params.length-1];
+
+ if (RGBA_REGEX.test(str)) {
+ colorMatch = str.match(RGBA_REGEX);
+ } else if (RGB_REGEX.test(str)) {
+ colorMatch = str.match(RGB_REGEX);
+ } else if (HEX6_REGEX.test(str)) {
+ colorMatch = str.match(HEX6_REGEX);
+ radix = 16;
+ } else if (HEX3_REGEX.test(str)) {
+ colorMatch = str.match(HEX3_REGEX);
+ colorMatch.shift();
+ colorMatch.push(1);
+ return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
+ return (idx < 3) ? (parseInt(d, 16) * 16) + parseInt(d, 16): parseFloat(d);
+ });
+ }
+
+ if (colorMatch) {
+ colorMatch.shift();
+ if (!colorMatch[3]) {
+ colorMatch.push(1);
+ }
+ return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
+ return (idx < 3) ? parseInt(d, radix): parseFloat(d);
+ });
+ }
+ }
+ return false;
+ },
+
+ unparseColor: function(val, props) {
+ if (props) {
+ if (props == "hex") {
+ return (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
+ } else if (props == "hash") {
+ return "#" + (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
+ } else if (props == "rgb") {
+ return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
+ } else if (props == "rgba") {
+ return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
+ } else if (props == "csv") {
+ return val[0] + "," + val[1] + "," + val[2] + "," + val[3];
+ }
+ }
+
+ if (val[3] && val[3] !== 1) {
+ return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
+ } else {
+ return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
+ }
+ },
+
+ parseFontSize: function(stylesStr) {
+ var params = stylesStr.match(param_REGX('font-size'));
+ if (params) {
+ return wysihtml5.lang.string(params[params.length - 1].split(':')[1]).trim();
+ }
+ return false;
+ }
+ };
+
+})(wysihtml5);
+;/**
+ * Selection API
+ *
+ * @example
+ * var selection = new wysihtml5.Selection(editor);
+ */
+(function(wysihtml5) {
+ var dom = wysihtml5.dom;
+
+ function _getCumulativeOffsetTop(element) {
+ var top = 0;
+ if (element.parentNode) {
+ do {
+ top += element.offsetTop || 0;
+ element = element.offsetParent;
+ } while (element);
+ }
+ return top;
+ }
+
+ // Provides the depth of ``descendant`` relative to ``ancestor``
+ function getDepth(ancestor, descendant) {
+ var ret = 0;
+ while (descendant !== ancestor) {
+ ret++;
+ descendant = descendant.parentNode;
+ if (!descendant)
+ throw new Error("not a descendant of ancestor!");
+ }
+ return ret;
+ }
+
+ // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon
+ // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection
+ function expandRangeToSurround(range) {
+ if (range.canSurroundContents()) return;
+
+ var common = range.commonAncestorContainer,
+ start_depth = getDepth(common, range.startContainer),
+ end_depth = getDepth(common, range.endContainer);
+
+ while(!range.canSurroundContents()) {
+ // In the following branches, we cannot just decrement the depth variables because the setStartBefore/setEndAfter may move the start or end of the range more than one level relative to ``common``. So we need to recompute the depth.
+ if (start_depth > end_depth) {
+ range.setStartBefore(range.startContainer);
+ start_depth = getDepth(common, range.startContainer);
+ }
+ else {
+ range.setEndAfter(range.endContainer);
+ end_depth = getDepth(common, range.endContainer);
+ }
+ }
+ }
+
+ wysihtml5.Selection = Base.extend(
+ /** @scope wysihtml5.Selection.prototype */ {
+ constructor: function(editor, contain, unselectableClass) {
+ // Make sure that our external range library is initialized
+ window.rangy.init();
+
+ this.editor = editor;
+ this.composer = editor.composer;
+ this.doc = this.composer.doc;
+ this.contain = contain;
+ this.unselectableClass = unselectableClass || false;
+ },
+
+ /**
+ * Get the current selection as a bookmark to be able to later restore it
+ *
+ * @return {Object} An object that represents the current selection
+ */
+ getBookmark: function() {
+ var range = this.getRange();
+ if (range) expandRangeToSurround(range);
+ return range && range.cloneRange();
+ },
+
+ /**
+ * Restore a selection retrieved via wysihtml5.Selection.prototype.getBookmark
+ *
+ * @param {Object} bookmark An object that represents the current selection
+ */
+ setBookmark: function(bookmark) {
+ if (!bookmark) {
+ return;
+ }
+
+ this.setSelection(bookmark);
+ },
+
+ /**
+ * Set the caret in front of the given node
+ *
+ * @param {Object} node The element or text node where to position the caret in front of
+ * @example
+ * selection.setBefore(myElement);
+ */
+ setBefore: function(node) {
+ var range = rangy.createRange(this.doc);
+ range.setStartBefore(node);
+ range.setEndBefore(node);
+ return this.setSelection(range);
+ },
+
+ /**
+ * Set the caret after the given node
+ *
+ * @param {Object} node The element or text node where to position the caret in front of
+ * @example
+ * selection.setBefore(myElement);
+ */
+ setAfter: function(node) {
+ var range = rangy.createRange(this.doc);
+
+ range.setStartAfter(node);
+ range.setEndAfter(node);
+ return this.setSelection(range);
+ },
+
+ /**
+ * Ability to select/mark nodes
+ *
+ * @param {Element} node The node/element to select
+ * @example
+ * selection.selectNode(document.getElementById("my-image"));
+ */
+ 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"),
+ content = isElement ? node.innerHTML : node.data,
+ isEmpty = (content === "" || content === wysihtml5.INVISIBLE_SPACE),
+ displayStyle = dom.getStyle("display").from(node),
+ isBlockElement = (displayStyle === "block" || displayStyle === "list-item");
+
+ 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) {}
+ }
+
+ if (canHaveHTML) {
+ range.selectNodeContents(node);
+ } else {
+ range.selectNode(node);
+ }
+
+ if (canHaveHTML && isEmpty && isElement) {
+ range.collapse(isBlockElement);
+ } else if (canHaveHTML && isEmpty) {
+ range.setStartAfter(node);
+ range.setEndAfter(node);
+ }
+
+ this.setSelection(range);
+ },
+
+ /**
+ * Get the node which contains the selection
+ *
+ * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
+ * @return {Object} The node that contains the caret
+ * @example
+ * var nodeThatContainsCaret = selection.getSelectedNode();
+ */
+ getSelectedNode: function(controlRange) {
+ var selection,
+ range;
+
+ if (controlRange && this.doc.selection && this.doc.selection.type === "Control") {
+ range = this.doc.selection.createRange();
+ if (range && range.length) {
+ return range.item(0);
+ }
+ }
+
+ selection = this.getSelection(this.doc);
+ if (selection.focusNode === selection.anchorNode) {
+ return selection.focusNode;
+ } else {
+ range = this.getRange(this.doc);
+ return range ? range.commonAncestorContainer : this.doc.body;
+ }
+ },
+
+ fixSelBorders: function() {
+ var range = this.getRange();
+ expandRangeToSurround(range);
+ this.setSelection(range);
+ },
+
+ getSelectedOwnNodes: function(controlRange) {
+ var selection,
+ ranges = this.getOwnRanges(),
+ ownNodes = [];
+
+ for (var i = 0, maxi = ranges.length; i < maxi; i++) {
+ ownNodes.push(ranges[i].commonAncestorContainer || this.doc.body);
+ }
+ return ownNodes;
+ },
+
+ findNodesInSelection: function(nodeTypes) {
+ var ranges = this.getOwnRanges(),
+ nodes = [], curNodes;
+ for (var i = 0, maxi = ranges.length; i < maxi; i++) {
+ curNodes = ranges[i].getNodes([1], function(node) {
+ return wysihtml5.lang.array(nodeTypes).contains(node.nodeName);
+ });
+ nodes = nodes.concat(curNodes);
+ }
+ return nodes;
+ },
+
+ containsUneditable: function() {
+ var uneditables = this.getOwnUneditables(),
+ selection = this.getSelection();
+
+ for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
+ if (selection.containsNode(uneditables[i])) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ deleteContents: function() {
+ var ranges = this.getOwnRanges();
+ for (var i = ranges.length; i--;) {
+ ranges[i].deleteContents();
+ }
+ this.setSelection(ranges[0]);
+ },
+
+ getPreviousNode: function(node, ignoreEmpty) {
+ if (!node) {
+ var selection = this.getSelection();
+ node = selection.anchorNode;
+ }
+
+ if (node === this.contain) {
+ return false;
+ }
+
+ var ret = node.previousSibling,
+ parent;
+
+ if (ret === this.contain) {
+ return false;
+ }
+
+ if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) {
+ // do not count comments and other node types
+ ret = this.getPreviousNode(ret, ignoreEmpty);
+ } else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) {
+ // do not count empty textnodes as previus nodes
+ ret = this.getPreviousNode(ret, ignoreEmpty);
+ } else if (ignoreEmpty && ret && ret.nodeType === 1 && !wysihtml5.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) && (/^[\s]*$/).test(ret.innerHTML)) {
+ // Do not count empty nodes if param set.
+ // Contenteditable tends to bypass and delete these silently when deleting with caret
+ ret = this.getPreviousNode(ret, ignoreEmpty);
+ } else if (!ret && node !== this.contain) {
+ parent = node.parentNode;
+ if (parent !== this.contain) {
+ ret = this.getPreviousNode(parent, ignoreEmpty);
+ }
+ }
+
+ return (ret !== this.contain) ? ret : false;
+ },
+
+ getSelectionParentsByTag: function(tagName) {
+ var nodes = this.getSelectedOwnNodes(),
+ curEl, parents = [];
+
+ for (var i = 0, maxi = nodes.length; i < maxi; i++) {
+ curEl = (nodes[i].nodeName && nodes[i].nodeName === 'LI') ? nodes[i] : wysihtml5.dom.getParentElement(nodes[i], { nodeName: ['LI']}, false, this.contain);
+ if (curEl) {
+ parents.push(curEl);
+ }
+ }
+ return (parents.length) ? parents : null;
+ },
+
+ getRangeToNodeEnd: function() {
+ if (this.isCollapsed()) {
+ var range = this.getRange(),
+ sNode = range.startContainer,
+ pos = range.startOffset,
+ lastR = rangy.createRange(this.doc);
+
+ lastR.selectNodeContents(sNode);
+ lastR.setStart(sNode, pos);
+ return lastR;
+ }
+ },
+
+ caretIsLastInSelection: function() {
+ var r = rangy.createRange(this.doc),
+ s = this.getSelection(),
+ endc = this.getRangeToNodeEnd().cloneContents(),
+ endtxt = endc.textContent;
+
+ return (/^\s*$/).test(endtxt);
+ },
+
+ caretIsFirstInSelection: function() {
+ var r = rangy.createRange(this.doc),
+ s = this.getSelection(),
+ range = this.getRange(),
+ startNode = range.startContainer;
+
+ if (startNode.nodeType === wysihtml5.TEXT_NODE) {
+ return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
+ } else {
+ r.selectNodeContents(this.getRange().commonAncestorContainer);
+ r.collapse(true);
+ return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
+ }
+ },
+
+ caretIsInTheBeginnig: function(ofNode) {
+ var selection = this.getSelection(),
+ node = selection.anchorNode,
+ offset = selection.anchorOffset;
+ if (ofNode) {
+ return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml5.dom.getParentElement(node.parentNode, { nodeName: ofNode }, 1)));
+ } else {
+ return (offset === 0 && !this.getPreviousNode(node, true));
+ }
+ },
+
+ caretIsBeforeUneditable: function() {
+ var selection = this.getSelection(),
+ node = selection.anchorNode,
+ offset = selection.anchorOffset;
+
+ if (offset === 0) {
+ var prevNode = this.getPreviousNode(node, true);
+ if (prevNode) {
+ var uneditables = this.getOwnUneditables();
+ for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
+ if (prevNode === uneditables[i]) {
+ return uneditables[i];
+ }
+ }
+ }
+ }
+ return false;
+ },
+
+ // TODO: Figure out a method from following 2 that would work universally
+ executeAndRestoreRangy: function(method, restoreScrollPosition) {
+ var win = this.doc.defaultView || this.doc.parentWindow,
+ sel = rangy.saveSelection(win);
+
+ if (!sel) {
+ method();
+ } else {
+ try {
+ method();
+ } catch(e) {
+ setTimeout(function() { throw e; }, 0);
+ }
+ }
+ rangy.restoreSelection(sel);
+ },
+
+ // TODO: has problems in chrome 12. investigate block level and uneditable area inbetween
+ executeAndRestore: function(method, restoreScrollPosition) {
+ var body = this.doc.body,
+ oldScrollTop = restoreScrollPosition && body.scrollTop,
+ oldScrollLeft = restoreScrollPosition && body.scrollLeft,
+ className = "_wysihtml5-temp-placeholder",
+ placeholderHtml = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
+ range = this.getRange(true),
+ caretPlaceholder,
+ newCaretPlaceholder,
+ nextSibling, prevSibling,
+ node, node2, range2,
+ newRange;
+
+ // Nothing selected, execute and say goodbye
+ if (!range) {
+ method(body, body);
+ return;
+ }
+
+ if (!range.collapsed) {
+ range2 = range.cloneRange();
+ node2 = range2.createContextualFragment(placeholderHtml);
+ range2.collapse(false);
+ range2.insertNode(node2);
+ range2.detach();
+ }
+
+ node = range.createContextualFragment(placeholderHtml);
+ range.insertNode(node);
+
+ if (node2) {
+ caretPlaceholder = this.contain.querySelectorAll("." + className);
+ range.setStartBefore(caretPlaceholder[0]);
+ range.setEndAfter(caretPlaceholder[caretPlaceholder.length -1]);
+ }
+ this.setSelection(range);
+
+ // 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(e) {
+ setTimeout(function() { throw e; }, 0);
+ }
+ caretPlaceholder = this.contain.querySelectorAll("." + className);
+ if (caretPlaceholder && caretPlaceholder.length) {
+ newRange = rangy.createRange(this.doc);
+ nextSibling = caretPlaceholder[0].nextSibling;
+ if (caretPlaceholder.length > 1) {
+ prevSibling = caretPlaceholder[caretPlaceholder.length -1].previousSibling;
+ }
+ if (prevSibling && nextSibling) {
+ newRange.setStartBefore(nextSibling);
+ newRange.setEndAfter(prevSibling);
+ } else {
+ newCaretPlaceholder = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
+ dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]);
+ newRange.setStartBefore(newCaretPlaceholder);
+ newRange.setEndAfter(newCaretPlaceholder);
+ }
+ this.setSelection(newRange);
+ for (var i = caretPlaceholder.length; i--;) {
+ caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]);
+ }
+
+ } else {
+ // fallback for when all hell breaks loose
+ this.contain.focus();
+ }
+
+ if (restoreScrollPosition) {
+ body.scrollTop = oldScrollTop;
+ body.scrollLeft = oldScrollLeft;
+ }
+
+ // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
+ try {
+ caretPlaceholder.parentNode.removeChild(caretPlaceholder);
+ } catch(e2) {}
+ },
+
+ 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
+ *
+ * @param {String} html HTML string to insert
+ * @example
+ * selection.insertHTML("<p>foobar</p>");
+ */
+ insertHTML: function(html) {
+ var range = rangy.createRange(this.doc),
+ node = this.doc.createElement('DIV'),
+ fragment = this.doc.createDocumentFragment(),
+ lastChild;
+
+ node.innerHTML = html;
+ lastChild = node.lastChild;
+
+ while (node.firstChild) {
+ fragment.appendChild(node.firstChild);
+ }
+ this.insertNode(fragment);
+
+ if (lastChild) {
+ this.setAfter(lastChild);
+ }
+ },
+
+ /**
+ * Insert a node at the caret position and move the cursor behind it
+ *
+ * @param {Object} node HTML string to insert
+ * @example
+ * selection.insertNode(document.createTextNode("foobar"));
+ */
+ insertNode: function(node) {
+ var range = this.getRange();
+ if (range) {
+ range.insertNode(node);
+ }
+ },
+
+ /**
+ * Wraps current selection with the given node
+ *
+ * @param {Object} node The node to surround the selected elements with
+ */
+ surround: function(nodeOptions) {
+ var ranges = this.getOwnRanges(),
+ node, nodes = [];
+ if (ranges.length == 0) {
+ return nodes;
+ }
+
+ for (var i = ranges.length; i--;) {
+ node = this.doc.createElement(nodeOptions.nodeName);
+ nodes.push(node);
+ if (nodeOptions.className) {
+ node.className = nodeOptions.className;
+ }
+ if (nodeOptions.cssStyle) {
+ node.setAttribute('style', nodeOptions.cssStyle);
+ }
+ try {
+ // This only works when the range boundaries are not overlapping other elements
+ ranges[i].surroundContents(node);
+ this.selectNode(node);
+ } catch(e) {
+ // fallback
+ node.appendChild(ranges[i].extractContents());
+ ranges[i].insertNode(node);
+ }
+ }
+ return nodes;
+ },
+
+ deblockAndSurround: function(nodeOptions) {
+ var tempElement = this.doc.createElement('div'),
+ range = rangy.createRange(this.doc),
+ tempDivElements,
+ tempElements,
+ firstChild;
+
+ tempElement.className = nodeOptions.className;
+
+ this.composer.commands.exec("formatBlock", nodeOptions.nodeName, nodeOptions.className);
+ tempDivElements = this.contain.querySelectorAll("." + nodeOptions.className);
+ if (tempDivElements[0]) {
+ tempDivElements[0].parentNode.insertBefore(tempElement, tempDivElements[0]);
+
+ range.setStartBefore(tempDivElements[0]);
+ range.setEndAfter(tempDivElements[tempDivElements.length - 1]);
+ tempElements = range.extractContents();
+
+ while (tempElements.firstChild) {
+ firstChild = tempElements.firstChild;
+ if (firstChild.nodeType == 1 && wysihtml5.dom.hasClass(firstChild, nodeOptions.className)) {
+ while (firstChild.firstChild) {
+ tempElement.appendChild(firstChild.firstChild);
+ }
+ if (firstChild.nodeName !== "BR") { tempElement.appendChild(this.doc.createElement('br')); }
+ tempElements.removeChild(firstChild);
+ } else {
+ tempElement.appendChild(firstChild);
+ }
+ }
+ } else {
+ tempElement = null;
+ }
+
+ return tempElement;
+ },
+
+ /**
+ * Scroll the current caret position into the view
+ * FIXME: This is a bit hacky, there might be a smarter way of doing this
+ *
+ * @example
+ * selection.scrollIntoView();
+ */
+ 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");
+ // The element needs content in order to be able to calculate it's position properly
+ element.innerHTML = wysihtml5.INVISIBLE_SPACE;
+ return element;
+ })(),
+ offsetTop;
+
+ if (hasScrollBars) {
+ this.insertNode(tempElement);
+ offsetTop = _getCumulativeOffsetTop(tempElement);
+ tempElement.parentNode.removeChild(tempElement);
+ if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) {
+ doc.body.scrollTop = offsetTop;
+ }
+ }
+ },
+
+ /**
+ * Select line where the caret is in
+ */
+ selectLine: function() {
+ if (wysihtml5.browser.supportsSelectionModify()) {
+ this._selectLine_W3C();
+ } else if (this.doc.selection) {
+ this._selectLine_MSIE();
+ }
+ },
+
+ /**
+ * See https://developer.mozilla.org/en/DOM/Selection/modify
+ */
+ _selectLine_W3C: function() {
+ var win = this.doc.defaultView,
+ selection = win.getSelection();
+ selection.modify("move", "left", "lineboundary");
+ selection.modify("extend", "right", "lineboundary");
+ },
+
+ _selectLine_MSIE: function() {
+ var range = this.doc.selection.createRange(),
+ rangeTop = range.boundingTop,
+ scrollWidth = this.doc.body.scrollWidth,
+ rangeBottom,
+ rangeEnd,
+ measureNode,
+ i,
+ j;
+
+ if (!range.moveToPoint) {
+ return;
+ }
+
+ if (rangeTop === 0) {
+ // Don't know why, but when the selection ends at the end of a line
+ // range.boundingTop is 0
+ measureNode = this.doc.createElement("span");
+ this.insertNode(measureNode);
+ rangeTop = measureNode.offsetTop;
+ measureNode.parentNode.removeChild(measureNode);
+ }
+
+ rangeTop += 1;
+
+ for (i=-10; i<scrollWidth; i+=2) {
+ try {
+ range.moveToPoint(i, rangeTop);
+ break;
+ } catch(e1) {}
+ }
+
+ // Investigate the following in order to handle multi line selections
+ // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
+ rangeBottom = rangeTop;
+ rangeEnd = this.doc.selection.createRange();
+ for (j=scrollWidth; j>=0; j--) {
+ try {
+ rangeEnd.moveToPoint(j, rangeBottom);
+ break;
+ } catch(e2) {}
+ }
+
+ range.setEndPoint("EndToEnd", rangeEnd);
+ range.select();
+ },
+
+ getText: function() {
+ var selection = this.getSelection();
+ return selection ? selection.toString() : "";
+ },
+
+ getNodes: function(nodeType, filter) {
+ var range = this.getRange();
+ if (range) {
+ return range.getNodes([nodeType], filter);
+ } else {
+ return [];
+ }
+ },
+
+ fixRangeOverflow: function(range) {
+ if (this.contain && this.contain.firstChild && range) {
+ var containment = range.compareNode(this.contain);
+ if (containment !== 2) {
+ if (containment === 1) {
+ range.setStartBefore(this.contain.firstChild);
+ }
+ if (containment === 0) {
+ range.setEndAfter(this.contain.lastChild);
+ }
+ if (containment === 3) {
+ range.setStartBefore(this.contain.firstChild);
+ range.setEndAfter(this.contain.lastChild);
+ }
+ } else if (this._detectInlineRangeProblems(range)) {
+ var previousElementSibling = range.endContainer.previousElementSibling;
+ if (previousElementSibling) {
+ range.setEnd(previousElementSibling, this._endOffsetForNode(previousElementSibling));
+ }
+ }
+ }
+ },
+
+ _endOffsetForNode: function(node) {
+ var range = document.createRange();
+ range.selectNodeContents(node);
+ return range.endOffset;
+ },
+
+ _detectInlineRangeProblems: function(range) {
+ var position = dom.compareDocumentPosition(range.startContainer, range.endContainer);
+ return (
+ range.endOffset == 0 &&
+ position & 4 //Node.DOCUMENT_POSITION_FOLLOWING
+ );
+ },
+
+ getRange: function(dontFix) {
+ var selection = this.getSelection(),
+ range = selection && selection.rangeCount && selection.getRangeAt(0);
+
+ if (dontFix !== true) {
+ this.fixRangeOverflow(range);
+ }
+
+ return range;
+ },
+
+ getOwnUneditables: function() {
+ var allUneditables = dom.query(this.contain, '.' + this.unselectableClass),
+ deepUneditables = dom.query(allUneditables, '.' + this.unselectableClass);
+
+ return wysihtml5.lang.array(allUneditables).without(deepUneditables);
+ },
+
+ // Returns an array of ranges that belong only to this editable
+ // Needed as uneditable block in contenteditabel can split range into pieces
+ // If manipulating content reverse loop is usually needed as manipulation can shift subsequent ranges
+ getOwnRanges: function() {
+ var ranges = [],
+ r = this.getRange(),
+ tmpRanges;
+
+ if (r) { ranges.push(r); }
+
+ if (this.unselectableClass && this.contain && r) {
+ var uneditables = this.getOwnUneditables(),
+ tmpRange;
+ if (uneditables.length > 0) {
+ for (var i = 0, imax = uneditables.length; i < imax; i++) {
+ tmpRanges = [];
+ for (var j = 0, jmax = ranges.length; j < jmax; j++) {
+ if (ranges[j]) {
+ switch (ranges[j].compareNode(uneditables[i])) {
+ case 2:
+ // all selection inside uneditable. remove
+ break;
+ case 3:
+ //section begins before and ends after uneditable. spilt
+ tmpRange = ranges[j].cloneRange();
+ tmpRange.setEndBefore(uneditables[i]);
+ tmpRanges.push(tmpRange);
+
+ tmpRange = ranges[j].cloneRange();
+ tmpRange.setStartAfter(uneditables[i]);
+ tmpRanges.push(tmpRange);
+ break;
+ default:
+ // in all other cases uneditable does not touch selection. dont modify
+ tmpRanges.push(ranges[j]);
+ }
+ }
+ ranges = tmpRanges;
+ }
+ }
+ }
+ }
+ return ranges;
+ },
+
+ getSelection: function() {
+ return rangy.getSelection(this.doc.defaultView || this.doc.parentWindow);
+ },
+
+ setSelection: function(range) {
+ var win = this.doc.defaultView || this.doc.parentWindow,
+ selection = rangy.getSelection(win);
+ return selection.setSingleRange(range);
+ },
+
+ createRange: function() {
+ return rangy.createRange(this.doc);
+ },
+
+ isCollapsed: function() {
+ return this.getSelection().isCollapsed;
+ },
+
+ getHtml: function() {
+ return this.getSelection().toHtml();
+ },
+
+ isEndToEndInNode: function(nodeNames) {
+ var range = this.getRange(),
+ parentElement = range.commonAncestorContainer,
+ startNode = range.startContainer,
+ endNode = range.endContainer;
+
+
+ if (parentElement.nodeType === wysihtml5.TEXT_NODE) {
+ parentElement = parentElement.parentNode;
+ }
+
+ if (startNode.nodeType === wysihtml5.TEXT_NODE && !(/^\s*$/).test(startNode.data.substr(range.startOffset))) {
+ return false;
+ }
+
+ if (endNode.nodeType === wysihtml5.TEXT_NODE && !(/^\s*$/).test(endNode.data.substr(range.endOffset))) {
+ return false;
+ }
+
+ while (startNode && startNode !== parentElement) {
+ if (startNode.nodeType !== wysihtml5.TEXT_NODE && !wysihtml5.dom.contains(parentElement, startNode)) {
+ return false;
+ }
+ if (wysihtml5.dom.domNode(startNode).prev({ignoreBlankTexts: true})) {
+ return false;
+ }
+ startNode = startNode.parentNode;
+ }
+
+ while (endNode && endNode !== parentElement) {
+ if (endNode.nodeType !== wysihtml5.TEXT_NODE && !wysihtml5.dom.contains(parentElement, endNode)) {
+ return false;
+ }
+ if (wysihtml5.dom.domNode(endNode).next({ignoreBlankTexts: true})) {
+ return false;
+ }
+ endNode = endNode.parentNode;
+ }
+
+ return (wysihtml5.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false;
+ },
+
+ deselect: function() {
+ var sel = this.getSelection();
+ sel && sel.removeAllRanges();
+ }
+ });
+
+})(wysihtml5);
+;/**
+ * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
+ * http://code.google.com/p/rangy/
+ *
+ * changed in order to be able ...
+ * - to use custom tags
+ * - to detect and replace similar css classes via reg exp
+ */
+(function(wysihtml5, rangy) {
+ var defaultTagName = "span";
+
+ var REG_EXP_WHITE_SPACE = /\s+/g;
+
+ function hasClass(el, cssClass, regExp) {
+ if (!el.className) {
+ return false;
+ }
+
+ var matchingClassNames = el.className.match(regExp) || [];
+ return matchingClassNames[matchingClassNames.length - 1] === cssClass;
+ }
+
+ function hasStyleAttr(el, regExp) {
+ if (!el.getAttribute || !el.getAttribute('style')) {
+ return false;
+ }
+ var matchingStyles = el.getAttribute('style').match(regExp);
+ return (el.getAttribute('style').match(regExp)) ? true : false;
+ }
+
+ function addStyle(el, cssStyle, regExp) {
+ if (el.getAttribute('style')) {
+ removeStyle(el, regExp);
+ if (el.getAttribute('style') && !(/^\s*$/).test(el.getAttribute('style'))) {
+ el.setAttribute('style', cssStyle + ";" + el.getAttribute('style'));
+ } else {
+ el.setAttribute('style', cssStyle);
+ }
+ } else {
+ el.setAttribute('style', cssStyle);
+ }
+ }
+
+ function addClass(el, cssClass, regExp) {
+ if (el.className) {
+ removeClass(el, regExp);
+ el.className += " " + cssClass;
+ } else {
+ el.className = cssClass;
+ }
+ }
+
+ function removeClass(el, regExp) {
+ if (el.className) {
+ el.className = el.className.replace(regExp, "");
+ }
+ }
+
+ function removeStyle(el, regExp) {
+ var s,
+ s2 = [];
+ if (el.getAttribute('style')) {
+ s = el.getAttribute('style').split(';');
+ for (var i = s.length; i--;) {
+ if (!s[i].match(regExp) && !(/^\s*$/).test(s[i])) {
+ s2.push(s[i]);
+ }
+ }
+ if (s2.length) {
+ el.setAttribute('style', s2.join(';'));
+ } else {
+ el.removeAttribute('style');
+ }
+ }
+ }
+
+ function getMatchingStyleRegexp(el, style) {
+ var regexes = [],
+ sSplit = style.split(';'),
+ elStyle = el.getAttribute('style');
+
+ if (elStyle) {
+ elStyle = elStyle.replace(/\s/gi, '').toLowerCase();
+ regexes.push(new RegExp("(^|\\s|;)" + style.replace(/\s/gi, '').replace(/([\(\)])/gi, "\\$1").toLowerCase().replace(";", ";?").replace(/rgb\\\((\d+),(\d+),(\d+)\\\)/gi, "\\s?rgb\\($1,\\s?$2,\\s?$3\\)"), "gi"));
+
+ for (var i = sSplit.length; i-- > 0;) {
+ if (!(/^\s*$/).test(sSplit[i])) {
+ regexes.push(new RegExp("(^|\\s|;)" + sSplit[i].replace(/\s/gi, '').replace(/([\(\)])/gi, "\\$1").toLowerCase().replace(";", ";?").replace(/rgb\\\((\d+),(\d+),(\d+)\\\)/gi, "\\s?rgb\\($1,\\s?$2,\\s?$3\\)"), "gi"));
+ }
+ }
+ for (var j = 0, jmax = regexes.length; j < jmax; j++) {
+ if (elStyle.match(regexes[j])) {
+ return regexes[j];
+ }
+ }
+ }
+
+ return false;
+ }
+
+ function isMatchingAllready(node, tags, style, className) {
+ if (style) {
+ return getMatchingStyleRegexp(node, style);
+ } else if (className) {
+ return wysihtml5.dom.hasClass(node, className);
+ } else {
+ return rangy.dom.arrayContains(tags, node.tagName.toLowerCase());
+ }
+ }
+
+ function areMatchingAllready(nodes, tags, style, className) {
+ for (var i = nodes.length; i--;) {
+ if (!isMatchingAllready(nodes[i], tags, style, className)) {
+ return false;
+ }
+ }
+ return nodes.length ? true : false;
+ }
+
+ function removeOrChangeStyle(el, style, regExp) {
+
+ var exactRegex = getMatchingStyleRegexp(el, style);
+ if (exactRegex) {
+ // adding same style value on property again removes style
+ removeStyle(el, exactRegex);
+ return "remove";
+ } else {
+ // adding new style value changes value
+ addStyle(el, style, regExp);
+ return "change";
+ }
+ }
+
+ function hasSameClasses(el1, el2) {
+ return el1.className.replace(REG_EXP_WHITE_SPACE, " ") == el2.className.replace(REG_EXP_WHITE_SPACE, " ");
+ }
+
+ function replaceWithOwnChildren(el) {
+ var parent = el.parentNode;
+ while (el.firstChild) {
+ parent.insertBefore(el.firstChild, el);
+ }
+ parent.removeChild(el);
+ }
+
+ function elementsHaveSameNonClassAttributes(el1, el2) {
+ if (el1.attributes.length != el2.attributes.length) {
+ return false;
+ }
+ for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
+ attr1 = el1.attributes[i];
+ name = attr1.name;
+ if (name != "class") {
+ attr2 = el2.attributes.getNamedItem(name);
+ if (attr1.specified != attr2.specified) {
+ return false;
+ }
+ if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ function isSplitPoint(node, offset) {
+ if (rangy.dom.isCharacterDataNode(node)) {
+ if (offset == 0) {
+ return !!node.previousSibling;
+ } else if (offset == node.length) {
+ return !!node.nextSibling;
+ } else {
+ return true;
+ }
+ }
+
+ return offset > 0 && offset < node.childNodes.length;
+ }
+
+ function splitNodeAt(node, descendantNode, descendantOffset, container) {
+ var newNode;
+ if (rangy.dom.isCharacterDataNode(descendantNode)) {
+ if (descendantOffset == 0) {
+ descendantOffset = rangy.dom.getNodeIndex(descendantNode);
+ descendantNode = descendantNode.parentNode;
+ } else if (descendantOffset == descendantNode.length) {
+ descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1;
+ descendantNode = descendantNode.parentNode;
+ } else {
+ newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset);
+ }
+ }
+ if (!newNode) {
+ if (!container || descendantNode !== container) {
+
+ newNode = descendantNode.cloneNode(false);
+ if (newNode.id) {
+ newNode.removeAttribute("id");
+ }
+ var child;
+ while ((child = descendantNode.childNodes[descendantOffset])) {
+ newNode.appendChild(child);
+ }
+ rangy.dom.insertAfter(newNode, descendantNode);
+
+ }
+ }
+ return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, rangy.dom.getNodeIndex(newNode), container);
+ }
+
+ function Merge(firstNode) {
+ this.isElementMerge = (firstNode.nodeType == wysihtml5.ELEMENT_NODE);
+ this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
+ this.textNodes = [this.firstTextNode];
+ }
+
+ Merge.prototype = {
+ doMerge: function() {
+ var textBits = [], textNode, parent, text;
+ for (var i = 0, len = this.textNodes.length; i < len; ++i) {
+ textNode = this.textNodes[i];
+ parent = textNode.parentNode;
+ textBits[i] = textNode.data;
+ if (i) {
+ parent.removeChild(textNode);
+ if (!parent.hasChildNodes()) {
+ parent.parentNode.removeChild(parent);
+ }
+ }
+ }
+ this.firstTextNode.data = text = textBits.join("");
+ return text;
+ },
+
+ getLength: function() {
+ var i = this.textNodes.length, len = 0;
+ while (i--) {
+ len += this.textNodes[i].length;
+ }
+ return len;
+ },
+
+ toString: function() {
+ var textBits = [];
+ for (var i = 0, len = this.textNodes.length; i < len; ++i) {
+ textBits[i] = "'" + this.textNodes[i].data + "'";
+ }
+ return "[Merge(" + textBits.join(",") + ")]";
+ }
+ };
+
+ function HTMLApplier(tagNames, cssClass, similarClassRegExp, normalize, cssStyle, similarStyleRegExp, container) {
+ this.tagNames = tagNames || [defaultTagName];
+ this.cssClass = cssClass || ((cssClass === false) ? false : "");
+ this.similarClassRegExp = similarClassRegExp;
+ this.cssStyle = cssStyle || "";
+ this.similarStyleRegExp = similarStyleRegExp;
+ this.normalize = normalize;
+ this.applyToAnyTagName = false;
+ this.container = container;
+ }
+
+ HTMLApplier.prototype = {
+ getAncestorWithClass: function(node) {
+ var cssClassMatch;
+ while (node) {
+ cssClassMatch = this.cssClass ? hasClass(node, this.cssClass, this.similarClassRegExp) : (this.cssStyle !== "") ? false : true;
+ if (node.nodeType == wysihtml5.ELEMENT_NODE && node.getAttribute("contenteditable") != "false" && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssClassMatch) {
+ return node;
+ }
+ node = node.parentNode;
+ }
+ return false;
+ },
+
+ // returns parents of node with given style attribute
+ getAncestorWithStyle: function(node) {
+ var cssStyleMatch;
+ while (node) {
+ cssStyleMatch = this.cssStyle ? hasStyleAttr(node, this.similarStyleRegExp) : false;
+
+ if (node.nodeType == wysihtml5.ELEMENT_NODE && node.getAttribute("contenteditable") != "false" && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssStyleMatch) {
+ return node;
+ }
+ node = node.parentNode;
+ }
+ return false;
+ },
+
+ getMatchingAncestor: function(node) {
+ var ancestor = this.getAncestorWithClass(node),
+ matchType = false;
+
+ if (!ancestor) {
+ ancestor = this.getAncestorWithStyle(node);
+ if (ancestor) {
+ matchType = "style";
+ }
+ } else {
+ if (this.cssStyle) {
+ matchType = "class";
+ }
+ }
+
+ return {
+ "element": ancestor,
+ "type": matchType
+ };
+ },
+
+ // Normalizes nodes after applying a CSS class to a Range.
+ postApply: function(textNodes, range) {
+ var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
+
+ var merges = [], currentMerge;
+
+ var rangeStartNode = firstNode, rangeEndNode = lastNode;
+ var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
+
+ var textNode, precedingTextNode;
+
+ for (var i = 0, len = textNodes.length; i < len; ++i) {
+ textNode = textNodes[i];
+ precedingTextNode = null;
+ if (textNode && textNode.parentNode) {
+ precedingTextNode = this.getAdjacentMergeableTextNode(textNode.parentNode, false);
+ }
+ if (precedingTextNode) {
+ if (!currentMerge) {
+ currentMerge = new Merge(precedingTextNode);
+ merges.push(currentMerge);
+ }
+ currentMerge.textNodes.push(textNode);
+ if (textNode === firstNode) {
+ rangeStartNode = currentMerge.firstTextNode;
+ rangeStartOffset = rangeStartNode.length;
+ }
+ if (textNode === lastNode) {
+ rangeEndNode = currentMerge.firstTextNode;
+ rangeEndOffset = currentMerge.getLength();
+ }
+ } else {
+ currentMerge = null;
+ }
+ }
+ // Test whether the first node after the range needs merging
+ if(lastNode && lastNode.parentNode) {
+ var nextTextNode = this.getAdjacentMergeableTextNode(lastNode.parentNode, true);
+ if (nextTextNode) {
+ if (!currentMerge) {
+ currentMerge = new Merge(lastNode);
+ merges.push(currentMerge);
+ }
+ currentMerge.textNodes.push(nextTextNode);
+ }
+ }
+ // Do the merges
+ if (merges.length) {
+ for (i = 0, len = merges.length; i < len; ++i) {
+ merges[i].doMerge();
+ }
+ // Set the range boundaries
+ range.setStart(rangeStartNode, rangeStartOffset);
+ range.setEnd(rangeEndNode, rangeEndOffset);
+ }
+ },
+
+ getAdjacentMergeableTextNode: function(node, forward) {
+ var isTextNode = (node.nodeType == wysihtml5.TEXT_NODE);
+ var el = isTextNode ? node.parentNode : node;
+ var adjacentNode;
+ var propName = forward ? "nextSibling" : "previousSibling";
+ if (isTextNode) {
+ // Can merge if the node's previous/next sibling is a text node
+ adjacentNode = node[propName];
+ if (adjacentNode && adjacentNode.nodeType == wysihtml5.TEXT_NODE) {
+ return adjacentNode;
+ }
+ } else {
+ // Compare element with its sibling
+ adjacentNode = el[propName];
+ if (adjacentNode && this.areElementsMergeable(node, adjacentNode)) {
+ return adjacentNode[forward ? "firstChild" : "lastChild"];
+ }
+ }
+ return null;
+ },
+
+ areElementsMergeable: function(el1, el2) {
+ return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase())
+ && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase())
+ && hasSameClasses(el1, el2)
+ && elementsHaveSameNonClassAttributes(el1, el2);
+ },
+
+ createContainer: function(doc) {
+ var el = doc.createElement(this.tagNames[0]);
+ if (this.cssClass) {
+ el.className = this.cssClass;
+ }
+ if (this.cssStyle) {
+ el.setAttribute('style', this.cssStyle);
+ }
+ return el;
+ },
+
+ applyToTextNode: function(textNode) {
+ var parent = textNode.parentNode;
+ if (parent.childNodes.length == 1 && rangy.dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
+
+ if (this.cssClass) {
+ addClass(parent, this.cssClass, this.similarClassRegExp);
+ }
+ if (this.cssStyle) {
+ addStyle(parent, this.cssStyle, this.similarStyleRegExp);
+ }
+ } else {
+ var el = this.createContainer(rangy.dom.getDocument(textNode));
+ textNode.parentNode.insertBefore(el, textNode);
+ el.appendChild(textNode);
+ }
+ },
+
+ isRemovable: function(el) {
+ return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) &&
+ wysihtml5.lang.string(el.className).trim() === "" &&
+ (
+ !el.getAttribute('style') ||
+ wysihtml5.lang.string(el.getAttribute('style')).trim() === ""
+ );
+ },
+
+ undoToTextNode: function(textNode, range, ancestorWithClass, ancestorWithStyle) {
+ var styleMode = (ancestorWithClass) ? false : true,
+ ancestor = ancestorWithClass || ancestorWithStyle,
+ styleChanged = false;
+ if (!range.containsNode(ancestor)) {
+ // Split out the portion of the ancestor from which we can remove the CSS class
+ var ancestorRange = range.cloneRange();
+ ancestorRange.selectNode(ancestor);
+
+ if (ancestorRange.isPointInRange(range.endContainer, range.endOffset) && isSplitPoint(range.endContainer, range.endOffset)) {
+ splitNodeAt(ancestor, range.endContainer, range.endOffset, this.container);
+ range.setEndAfter(ancestor);
+ }
+ if (ancestorRange.isPointInRange(range.startContainer, range.startOffset) && isSplitPoint(range.startContainer, range.startOffset)) {
+ ancestor = splitNodeAt(ancestor, range.startContainer, range.startOffset, this.container);
+ }
+ }
+
+ if (!styleMode && this.similarClassRegExp) {
+ removeClass(ancestor, this.similarClassRegExp);
+ }
+
+ if (styleMode && this.similarStyleRegExp) {
+ styleChanged = (removeOrChangeStyle(ancestor, this.cssStyle, this.similarStyleRegExp) === "change");
+ }
+ if (this.isRemovable(ancestor) && !styleChanged) {
+ replaceWithOwnChildren(ancestor);
+ }
+ },
+
+ applyToRange: function(range) {
+ var textNodes;
+ for (var ri = range.length; ri--;) {
+ textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
+
+ if (!textNodes.length) {
+ try {
+ var node = this.createContainer(range[ri].endContainer.ownerDocument);
+ range[ri].surroundContents(node);
+ this.selectNode(range[ri], node);
+ return;
+ } catch(e) {}
+ }
+
+ range[ri].splitBoundaries();
+ textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
+ if (textNodes.length) {
+ var textNode;
+
+ for (var i = 0, len = textNodes.length; i < len; ++i) {
+ textNode = textNodes[i];
+ if (!this.getMatchingAncestor(textNode).element) {
+ this.applyToTextNode(textNode);
+ }
+ }
+
+ range[ri].setStart(textNodes[0], 0);
+ textNode = textNodes[textNodes.length - 1];
+ range[ri].setEnd(textNode, textNode.length);
+
+ if (this.normalize) {
+ this.postApply(textNodes, range[ri]);
+ }
+ }
+
+ }
+ },
+
+ undoToRange: function(range) {
+ var textNodes, textNode, ancestorWithClass, ancestorWithStyle, ancestor;
+ for (var ri = range.length; ri--;) {
+
+ textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
+ if (textNodes.length) {
+ range[ri].splitBoundaries();
+ textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
+ } else {
+ var doc = range[ri].endContainer.ownerDocument,
+ node = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
+ range[ri].insertNode(node);
+ range[ri].selectNode(node);
+ textNodes = [node];
+ }
+
+ for (var i = 0, len = textNodes.length; i < len; ++i) {
+ if (range[ri].isValid()) {
+ textNode = textNodes[i];
+
+ ancestor = this.getMatchingAncestor(textNode);
+ if (ancestor.type === "style") {
+ this.undoToTextNode(textNode, range[ri], false, ancestor.element);
+ } else if (ancestor.element) {
+ this.undoToTextNode(textNode, range[ri], ancestor.element);
+ }
+ }
+ }
+
+ if (len == 1) {
+ this.selectNode(range[ri], textNodes[0]);
+ } else {
+ range[ri].setStart(textNodes[0], 0);
+ textNode = textNodes[textNodes.length - 1];
+ range[ri].setEnd(textNode, textNode.length);
+
+ if (this.normalize) {
+ this.postApply(textNodes, range[ri]);
+ }
+ }
+
+ }
+ },
+
+ selectNode: function(range, node) {
+ var isElement = node.nodeType === wysihtml5.ELEMENT_NODE,
+ canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : true,
+ content = isElement ? node.innerHTML : node.data,
+ isEmpty = (content === "" || content === wysihtml5.INVISIBLE_SPACE);
+
+ if (isEmpty && isElement && canHaveHTML) {
+ // Make sure that caret is visible in node by inserting a zero width no breaking space
+ try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
+ }
+ range.selectNodeContents(node);
+ if (isEmpty && isElement) {
+ range.collapse(false);
+ } else if (isEmpty) {
+ range.setStartAfter(node);
+ range.setEndAfter(node);
+ }
+ },
+
+ getTextSelectedByRange: function(textNode, range) {
+ var textRange = range.cloneRange();
+ textRange.selectNodeContents(textNode);
+
+ var intersectionRange = textRange.intersection(range);
+ var text = intersectionRange ? intersectionRange.toString() : "";
+ textRange.detach();
+
+ return text;
+ },
+
+ isAppliedToRange: function(range) {
+ var ancestors = [],
+ appliedType = "full",
+ ancestor, styleAncestor, textNodes;
+
+ for (var ri = range.length; ri--;) {
+
+ textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
+ if (!textNodes.length) {
+ ancestor = this.getMatchingAncestor(range[ri].startContainer).element;
+
+ return (ancestor) ? {
+ "elements": [ancestor],
+ "coverage": appliedType
+ } : false;
+ }
+
+ for (var i = 0, len = textNodes.length, selectedText; i < len; ++i) {
+ selectedText = this.getTextSelectedByRange(textNodes[i], range[ri]);
+ ancestor = this.getMatchingAncestor(textNodes[i]).element;
+ if (ancestor && selectedText != "") {
+ ancestors.push(ancestor);
+
+ if (wysihtml5.dom.getTextNodes(ancestor, true).length === 1) {
+ appliedType = "full";
+ } else if (appliedType === "full") {
+ appliedType = "inline";
+ }
+ } else if (!ancestor) {
+ appliedType = "partial";
+ }
+ }
+
+ }
+
+ return (ancestors.length) ? {
+ "elements": ancestors,
+ "coverage": appliedType
+ } : false;
+ },
+
+ toggleRange: function(range) {
+ var isApplied = this.isAppliedToRange(range),
+ parentsExactMatch;
+
+ if (isApplied) {
+ if (isApplied.coverage === "full") {
+ this.undoToRange(range);
+ } else if (isApplied.coverage === "inline") {
+ parentsExactMatch = areMatchingAllready(isApplied.elements, this.tagNames, this.cssStyle, this.cssClass);
+ this.undoToRange(range);
+ if (!parentsExactMatch) {
+ this.applyToRange(range);
+ }
+ } else {
+ // partial
+ if (!areMatchingAllready(isApplied.elements, this.tagNames, this.cssStyle, this.cssClass)) {
+ this.undoToRange(range);
+ }
+ this.applyToRange(range);
+ }
+ } else {
+ this.applyToRange(range);
+ }
+ }
+ };
+
+ wysihtml5.selection.HTMLApplier = HTMLApplier;
+
+})(wysihtml5, rangy);
+;/**
+ * Rich Text Query/Formatting Commands
+ *
+ * @example
+ * var commands = new wysihtml5.Commands(editor);
+ */
+wysihtml5.Commands = Base.extend(
+ /** @scope wysihtml5.Commands.prototype */ {
+ constructor: function(editor) {
+ this.editor = editor;
+ this.composer = editor.composer;
+ this.doc = this.composer.doc;
+ },
+
+ /**
+ * Check whether the browser supports the given command
+ *
+ * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
+ * @example
+ * commands.supports("createLink");
+ */
+ support: function(command) {
+ return wysihtml5.browser.supportsCommand(this.doc, command);
+ },
+
+ /**
+ * Check whether the browser supports the given command
+ *
+ * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList")
+ * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...)
+ * @example
+ * commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
+ */
+ exec: function(command, value) {
+ var obj = wysihtml5.commands[command],
+ args = wysihtml5.lang.array(arguments).get(),
+ method = obj && obj.exec,
+ result = null;
+
+ this.editor.fire("beforecommand:composer");
+
+ if (method) {
+ args.unshift(this.composer);
+ result = method.apply(obj, args);
+ } else {
+ try {
+ // try/catch for buggy firefox
+ result = this.doc.execCommand(command, false, value);
+ } catch(e) {}
+ }
+
+ this.editor.fire("aftercommand:composer");
+ return result;
+ },
+
+ /**
+ * Check whether the current command is active
+ * If the caret is within a bold text, then calling this with command "bold" should return true
+ *
+ * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
+ * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src)
+ * @return {Boolean} Whether the command is active
+ * @example
+ * var isCurrentSelectionBold = commands.state("bold");
+ */
+ state: function(command, commandValue) {
+ var obj = wysihtml5.commands[command],
+ args = wysihtml5.lang.array(arguments).get(),
+ method = obj && obj.state;
+ if (method) {
+ args.unshift(this.composer);
+ return method.apply(obj, args);
+ } else {
+ try {
+ // try/catch for buggy firefox
+ return this.doc.queryCommandState(command);
+ } catch(e) {
+ return false;
+ }
+ }
+ },
+
+ /* Get command state parsed value if command has stateValue parsing function */
+ stateValue: function(command) {
+ var obj = wysihtml5.commands[command],
+ args = wysihtml5.lang.array(arguments).get(),
+ method = obj && obj.stateValue;
+ if (method) {
+ args.unshift(this.composer);
+ return method.apply(obj, args);
+ } else {
+ return false;
+ }
+ }
+});
+;wysihtml5.commands.bold = {
+ exec: function(composer, command) {
+ wysihtml5.commands.formatInline.execWithToggle(composer, command, "b");
+ },
+
+ 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,
+ NODE_NAME = "A",
+ dom = wysihtml5.dom;
+
+ function _format(composer, attributes) {
+ var doc = composer.doc,
+ tempClass = "_wysihtml5-temp-" + (+new Date()),
+ tempClassRegExp = /non-matching-class/g,
+ i = 0,
+ length,
+ anchors,
+ anchor,
+ hasElementChild,
+ isEmpty,
+ elementToSetCaretAfter,
+ textContent,
+ whiteSpace,
+ j;
+ wysihtml5.commands.formatInline.exec(composer, undef, NODE_NAME, tempClass, tempClassRegExp, undef, undef, true, true);
+ anchors = doc.querySelectorAll(NODE_NAME + "." + tempClass);
+ length = anchors.length;
+ for (; i<length; i++) {
+ anchor = anchors[i];
+ anchor.removeAttribute("class");
+ for (j in attributes) {
+ // Do not set attribute "text" as it is meant for setting string value if created link has no textual data
+ if (j !== "text") {
+ anchor.setAttribute(j, attributes[j]);
+ }
+ }
+ }
+
+ elementToSetCaretAfter = anchor;
+ if (length === 1) {
+ textContent = dom.getTextContent(anchor);
+ hasElementChild = !!anchor.querySelector("*");
+ isEmpty = textContent === "" || textContent === wysihtml5.INVISIBLE_SPACE;
+ if (!hasElementChild && isEmpty) {
+ dom.setTextContent(anchor, attributes.text || anchor.href);
+ whiteSpace = doc.createTextNode(" ");
+ composer.selection.setAfter(anchor);
+ dom.insert(whiteSpace).after(anchor);
+ elementToSetCaretAfter = whiteSpace;
+ }
+ }
+ composer.selection.setAfter(elementToSetCaretAfter);
+ }
+
+ // Changes attributes of links
+ function _changeLinks(composer, anchors, attributes) {
+ var oldAttrs;
+ for (var a = anchors.length; a--;) {
+
+ // Remove all old attributes
+ oldAttrs = anchors[a].attributes;
+ for (var oa = oldAttrs.length; oa--;) {
+ anchors[a].removeAttribute(oldAttrs.item(oa).name);
+ }
+
+ // Set new attributes
+ for (var j in attributes) {
+ if (attributes.hasOwnProperty(j)) {
+ anchors[a].setAttribute(j, attributes[j]);
+ }
+ }
+
+ }
+ }
+
+ wysihtml5.commands.createLink = {
+ /**
+ * TODO: Use HTMLApplier or formatInline here
+ *
+ * Turns selection into a link
+ * If selection is already a link, it just changes the attributes
+ *
+ * @example
+ * // either ...
+ * wysihtml5.commands.createLink.exec(composer, "createLink", "http://www.google.de");
+ * // ... or ...
+ * wysihtml5.commands.createLink.exec(composer, "createLink", { href: "http://www.google.de", target: "_blank" });
+ */
+ exec: function(composer, command, value) {
+ var anchors = this.state(composer, command);
+ if (anchors) {
+ // Selection contains links then change attributes of these links
+ composer.selection.executeAndRestore(function() {
+ _changeLinks(composer, anchors, value);
+ });
+ } else {
+ // Create links
+ value = typeof(value) === "object" ? value : { href: value };
+ _format(composer, value);
+ }
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatInline.state(composer, command, "A");
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var dom = wysihtml5.dom;
+
+ function _removeFormat(composer, anchors) {
+ var length = anchors.length,
+ i = 0,
+ anchor,
+ codeElement,
+ textContent;
+ for (; i<length; i++) {
+ anchor = anchors[i];
+ codeElement = dom.getParentElement(anchor, { nodeName: "code" });
+ textContent = dom.getTextContent(anchor);
+
+ // if <a> contains url-like text content, rename it to <code> to prevent re-autolinking
+ // else replace <a> with its childNodes
+ if (textContent.match(dom.autoLink.URL_REG_EXP) && !codeElement) {
+ // <code> element is used to prevent later auto-linking of the content
+ codeElement = dom.renameElement(anchor, "code");
+ } else {
+ dom.replaceWithChildNodes(anchor);
+ }
+ }
+ }
+
+ wysihtml5.commands.removeLink = {
+ /*
+ * If selection is a link, it removes the link and wraps it with a <code> element
+ * The <code> element is needed to avoid auto linking
+ *
+ * @example
+ * wysihtml5.commands.createLink.exec(composer, "removeLink");
+ */
+
+ exec: function(composer, command) {
+ var anchors = this.state(composer, command);
+ if (anchors) {
+ composer.selection.executeAndRestore(function() {
+ _removeFormat(composer, anchors);
+ });
+ }
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatInline.state(composer, command, "A");
+ }
+ };
+})(wysihtml5);
+;/**
+ * document.execCommand("fontSize") will create either inline styles (firefox, chrome) or use font tags
+ * which we don't want
+ * Instead we set a css class
+ */
+(function(wysihtml5) {
+ var REG_EXP = /wysiwyg-font-size-[0-9a-z\-]+/g;
+
+ wysihtml5.commands.fontSize = {
+ exec: function(composer, command, size) {
+ wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
+ },
+
+ state: function(composer, command, size) {
+ return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;/* In case font size adjustment to any number defined by user is preferred, we cannot use classes and must use inline styles. */
+(function(wysihtml5) {
+ var REG_EXP = /(\s|^)font-size\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.fontSizeStyle = {
+ exec: function(composer, command, size) {
+ size = (typeof(size) == "object") ? size.size : size;
+ if (!(/^\s*$/).test(size)) {
+ wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, "font-size:" + size, REG_EXP);
+ }
+ },
+
+ state: function(composer, command, size) {
+ return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "font-size", REG_EXP);
+ },
+
+ stateValue: function(composer, command) {
+ var st = this.state(composer, command),
+ styleStr, fontsizeMatches,
+ val = false;
+
+ if (st && wysihtml5.lang.object(st).isArray()) {
+ st = st[0];
+ }
+ if (st) {
+ styleStr = st.getAttribute('style');
+ if (styleStr) {
+ return wysihtml5.quirks.styleParser.parseFontSize(styleStr);
+ }
+ }
+ return false;
+ }
+ };
+})(wysihtml5);
+;/**
+ * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
+ * which we don't want
+ * Instead we set a css class
+ */
+(function(wysihtml5) {
+ var REG_EXP = /wysiwyg-color-[0-9a-z]+/g;
+
+ wysihtml5.commands.foreColor = {
+ exec: function(composer, command, color) {
+ wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
+ },
+
+ state: function(composer, command, color) {
+ return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;/**
+ * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
+ * which we don't want
+ * Instead we set a css class
+ */
+(function(wysihtml5) {
+ var REG_EXP = /(\s|^)color\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.foreColorStyle = {
+ exec: function(composer, command, color) {
+ var colorVals = wysihtml5.quirks.styleParser.parseColor((typeof(color) == "object") ? "color:" + color.color : "color:" + color, "color"),
+ colString;
+
+ if (colorVals) {
+ colString = "color: rgb(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ');';
+ if (colorVals[3] !== 1) {
+ colString += "color: rgba(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ',' + colorVals[3] + ');';
+ }
+ wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, colString, REG_EXP);
+ }
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "color", REG_EXP);
+ },
+
+ stateValue: function(composer, command, props) {
+ var st = this.state(composer, command),
+ colorStr;
+
+ if (st && wysihtml5.lang.object(st).isArray()) {
+ st = st[0];
+ }
+
+ if (st) {
+ colorStr = st.getAttribute('style');
+ if (colorStr) {
+ if (colorStr) {
+ val = wysihtml5.quirks.styleParser.parseColor(colorStr, "color");
+ return wysihtml5.quirks.styleParser.unparseColor(val, props);
+ }
+ }
+ }
+ return false;
+ }
+
+ };
+})(wysihtml5);
+;/* In case background adjustment to any color defined by user is preferred, we cannot use classes and must use inline styles. */
+(function(wysihtml5) {
+ var REG_EXP = /(\s|^)background-color\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.bgColorStyle = {
+ exec: function(composer, command, color) {
+ var colorVals = wysihtml5.quirks.styleParser.parseColor((typeof(color) == "object") ? "background-color:" + color.color : "background-color:" + color, "background-color"),
+ colString;
+
+ if (colorVals) {
+ colString = "background-color: rgb(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ');';
+ if (colorVals[3] !== 1) {
+ colString += "background-color: rgba(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ',' + colorVals[3] + ');';
+ }
+ wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, colString, REG_EXP);
+ }
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "background-color", REG_EXP);
+ },
+
+ stateValue: function(composer, command, props) {
+ var st = this.state(composer, command),
+ colorStr,
+ val = false;
+
+ if (st && wysihtml5.lang.object(st).isArray()) {
+ st = st[0];
+ }
+
+ if (st) {
+ colorStr = st.getAttribute('style');
+ if (colorStr) {
+ val = wysihtml5.quirks.styleParser.parseColor(colorStr, "background-color");
+ return wysihtml5.quirks.styleParser.unparseColor(val, props);
+ }
+ }
+ return false;
+ }
+
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ 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", "PRE", "DIV"];
+
+ /**
+ * Remove similiar classes (based on classRegExp)
+ * and add the desired class name
+ */
+ function _addClass(element, className, classRegExp) {
+ if (element.className) {
+ _removeClass(element, classRegExp);
+ element.className = wysihtml5.lang.string(element.className + " " + className).trim();
+ } else {
+ element.className = className;
+ }
+ }
+
+ function _addStyle(element, cssStyle, styleRegExp) {
+ _removeStyle(element, styleRegExp);
+ if (element.getAttribute('style')) {
+ element.setAttribute('style', wysihtml5.lang.string(element.getAttribute('style') + " " + cssStyle).trim());
+ } else {
+ element.setAttribute('style', cssStyle);
+ }
+ }
+
+ function _removeClass(element, classRegExp) {
+ var ret = classRegExp.test(element.className);
+ element.className = element.className.replace(classRegExp, "");
+ if (wysihtml5.lang.string(element.className).trim() == '') {
+ element.removeAttribute('class');
+ }
+ return ret;
+ }
+
+ function _removeStyle(element, styleRegExp) {
+ var ret = styleRegExp.test(element.getAttribute('style'));
+ element.setAttribute('style', (element.getAttribute('style') || "").replace(styleRegExp, ""));
+ if (wysihtml5.lang.string(element.getAttribute('style') || "").trim() == '') {
+ element.removeAttribute('style');
+ }
+ return ret;
+ }
+
+ function _removeLastChildIfLineBreak(node) {
+ var lastChild = node.lastChild;
+ if (lastChild && _isLineBreak(lastChild)) {
+ lastChild.parentNode.removeChild(lastChild);
+ }
+ }
+
+ function _isLineBreak(node) {
+ return node.nodeName === "BR";
+ }
+
+ /**
+ * Execute native query command
+ * and if necessary modify the inserted node's className
+ */
+ function _execCommand(doc, composer, command, nodeName, className) {
+ var ranges = composer.selection.getOwnRanges();
+ for (var i = ranges.length; i--;){
+ composer.selection.getSelection().removeAllRanges();
+ composer.selection.setSelection(ranges[i]);
+ if (className) {
+ var eventListener = dom.observe(doc, "DOMNodeInserted", function(event) {
+ var target = event.target,
+ displayStyle;
+ if (target.nodeType !== wysihtml5.ELEMENT_NODE) {
+ return;
+ }
+ displayStyle = dom.getStyle("display").from(target);
+ if (displayStyle.substr(0, 6) !== "inline") {
+ // Make sure that only block elements receive the given class
+ target.className += " " + className;
+ }
+ });
+ }
+ doc.execCommand(command, false, nodeName);
+
+ if (eventListener) {
+ eventListener.stop();
+ }
+ }
+ }
+
+ function _selectionWrap(composer, options) {
+ if (composer.selection.isCollapsed()) {
+ composer.selection.selectLine();
+ }
+
+ var surroundedNodes = composer.selection.surround(options);
+ for (var i = 0, imax = surroundedNodes.length; i < imax; i++) {
+ wysihtml5.dom.lineBreaks(surroundedNodes[i]).remove();
+ _removeLastChildIfLineBreak(surroundedNodes[i]);
+ }
+
+ // rethink restoring selection
+ // composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
+ }
+
+ function _hasClasses(element) {
+ return !!wysihtml5.lang.string(element.className).trim();
+ }
+
+ function _hasStyles(element) {
+ return !!wysihtml5.lang.string(element.getAttribute('style') || '').trim();
+ }
+
+ wysihtml5.commands.formatBlock = {
+ exec: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
+ var doc = composer.doc,
+ blockElements = this.state(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp),
+ useLineBreaks = composer.config.useLineBreaks,
+ defaultNodeName = useLineBreaks ? "DIV" : "P",
+ selectedNodes, classRemoveAction, blockRenameFound, styleRemoveAction, blockElement;
+ nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
+
+ if (blockElements.length) {
+ composer.selection.executeAndRestoreRangy(function() {
+ for (var b = blockElements.length; b--;) {
+ if (classRegExp) {
+ classRemoveAction = _removeClass(blockElements[b], classRegExp);
+ }
+ if (styleRegExp) {
+ styleRemoveAction = _removeStyle(blockElements[b], styleRegExp);
+ }
+
+ if ((styleRemoveAction || classRemoveAction) && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
+ // dont rename or remove element when just setting block formating class or style
+ return;
+ }
+
+ var hasClasses = _hasClasses(blockElements[b]),
+ hasStyles = _hasStyles(blockElements[b]);
+
+ if (!hasClasses && !hasStyles && (useLineBreaks || nodeName === "P")) {
+ // Insert a line break afterwards and beforewards when there are siblings
+ // that are not of type line break or block element
+ wysihtml5.dom.lineBreaks(blockElements[b]).add();
+ dom.replaceWithChildNodes(blockElements[b]);
+ } else {
+ // Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
+ dom.renameElement(blockElements[b], nodeName === "P" ? "DIV" : defaultNodeName);
+ }
+ }
+ });
+
+ return;
+ }
+
+ // Find similiar block element and rename it (<h2 class="foo"></h2> => <h1 class="foo"></h1>)
+ if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
+ selectedNodes = composer.selection.findNodesInSelection(BLOCK_ELEMENTS_GROUP).concat(composer.selection.getSelectedOwnNodes());
+ composer.selection.executeAndRestoreRangy(function() {
+ for (var n = selectedNodes.length; n--;) {
+ blockElement = dom.getParentElement(selectedNodes[n], {
+ nodeName: BLOCK_ELEMENTS_GROUP
+ });
+ if (blockElement == composer.element) {
+ blockElement = null;
+ }
+ if (blockElement) {
+ // Rename current block element to new block element and add class
+ if (nodeName) {
+ blockElement = dom.renameElement(blockElement, nodeName);
+ }
+ if (className) {
+ _addClass(blockElement, className, classRegExp);
+ }
+ if (cssStyle) {
+ _addStyle(blockElement, cssStyle, styleRegExp);
+ }
+ blockRenameFound = true;
+ }
+ }
+
+ });
+
+ if (blockRenameFound) {
+ return;
+ }
+ }
+
+ _selectionWrap(composer, {
+ "nodeName": (nodeName || defaultNodeName),
+ "className": className || null,
+ "cssStyle": cssStyle || null
+ });
+ },
+
+ state: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
+ var nodes = composer.selection.getSelectedOwnNodes(),
+ parents = [],
+ parent;
+
+ nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
+
+ //var selectedNode = composer.selection.getSelectedNode();
+ for (var i = 0, maxi = nodes.length; i < maxi; i++) {
+ parent = dom.getParentElement(nodes[i], {
+ nodeName: nodeName,
+ className: className,
+ classRegExp: classRegExp,
+ cssStyle: cssStyle,
+ styleRegExp: styleRegExp
+ });
+ if (parent && wysihtml5.lang.array(parents).indexOf(parent) == -1) {
+ parents.push(parent);
+ }
+ }
+ if (parents.length == 0) {
+ return false;
+ }
+ return parents;
+ }
+
+
+ };
+})(wysihtml5);
+;/* Formats block for as a <pre><code class="classname"></code></pre> block
+ * Useful in conjuction for sytax highlight utility: highlight.js
+ *
+ * Usage:
+ *
+ * editorInstance.composer.commands.exec("formatCode", "language-html");
+*/
+
+wysihtml5.commands.formatCode = {
+
+ exec: function(composer, command, classname) {
+ var pre = this.state(composer),
+ code, range, selectedNodes;
+ if (pre) {
+ // caret is already within a <pre><code>...</code></pre>
+ composer.selection.executeAndRestore(function() {
+ code = pre.querySelector("code");
+ wysihtml5.dom.replaceWithChildNodes(pre);
+ if (code) {
+ wysihtml5.dom.replaceWithChildNodes(code);
+ }
+ });
+ } else {
+ // Wrap in <pre><code>...</code></pre>
+ range = composer.selection.getRange();
+ selectedNodes = range.extractContents();
+ pre = composer.doc.createElement("pre");
+ code = composer.doc.createElement("code");
+
+ if (classname) {
+ code.className = classname;
+ }
+
+ pre.appendChild(code);
+ code.appendChild(selectedNodes);
+ range.insertNode(pre);
+ composer.selection.selectNode(pre);
+ }
+ },
+
+ state: function(composer) {
+ var selectedNode = composer.selection.getSelectedNode();
+ if (selectedNode && selectedNode.nodeName && selectedNode.nodeName == "PRE"&&
+ selectedNode.firstChild && selectedNode.firstChild.nodeName && selectedNode.firstChild.nodeName == "CODE") {
+ return selectedNode;
+ } else {
+ return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "CODE" }) && wysihtml5.dom.getParentElement(selectedNode, { nodeName: "PRE" });
+ }
+ }
+};;/**
+ * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
+ *
+ * #1 caret in unformatted text:
+ * abcdefg|
+ * output:
+ * abcdefg<b>|</b>
+ *
+ * #2 unformatted text selected:
+ * abc|deg|h
+ * output:
+ * abc<b>|deg|</b>h
+ *
+ * #3 unformatted text selected across boundaries:
+ * ab|c <span>defg|h</span>
+ * output:
+ * ab<b>|c </b><span><b>defg</b>|h</span>
+ *
+ * #4 formatted text entirely selected
+ * <b>|abc|</b>
+ * output:
+ * |abc|
+ *
+ * #5 formatted text partially selected
+ * <b>ab|c|</b>
+ * output:
+ * <b>ab</b>|c|
+ *
+ * #6 formatted text selected across boundaries
+ * <span>ab|c</span> <b>de|fgh</b>
+ * output:
+ * <span>ab|c</span> de|<b>fgh</b>
+ */
+(function(wysihtml5) {
+ var // Treat <b> as <strong> and vice versa
+ ALIAS_MAPPING = {
+ "strong": "b",
+ "em": "i",
+ "b": "strong",
+ "i": "em"
+ },
+ htmlApplier = {};
+
+ function _getTagNames(tagName) {
+ var alias = ALIAS_MAPPING[tagName];
+ return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
+ }
+
+ function _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, container) {
+ var identifier = tagName;
+
+ if (className) {
+ identifier += ":" + className;
+ }
+ if (cssStyle) {
+ identifier += ":" + cssStyle;
+ }
+
+ if (!htmlApplier[identifier]) {
+ htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true, cssStyle, styleRegExp, container);
+ }
+
+ return htmlApplier[identifier];
+ }
+
+ wysihtml5.commands.formatInline = {
+ exec: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, dontRestoreSelect, noCleanup) {
+ var range = composer.selection.createRange(),
+ ownRanges = composer.selection.getOwnRanges();
+
+ if (!ownRanges || ownRanges.length == 0) {
+ return false;
+ }
+ composer.selection.getSelection().removeAllRanges();
+
+ _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).toggleRange(ownRanges);
+
+ if (!dontRestoreSelect) {
+ range.setStart(ownRanges[0].startContainer, ownRanges[0].startOffset);
+ range.setEnd(
+ ownRanges[ownRanges.length - 1].endContainer,
+ ownRanges[ownRanges.length - 1].endOffset
+ );
+ composer.selection.setSelection(range);
+ composer.selection.executeAndRestore(function() {
+ if (!noCleanup) {
+ composer.cleanUp();
+ }
+ }, true, true);
+ } else if (!noCleanup) {
+ composer.cleanUp();
+ }
+ },
+
+ // Executes so that if collapsed caret is in a state and executing that state it should unformat that state
+ // It is achieved by selecting the entire state element before executing.
+ // This works on built in contenteditable inline format commands
+ execWithToggle: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
+ var that = this;
+
+ if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) &&
+ composer.selection.isCollapsed() &&
+ !composer.selection.caretIsLastInSelection() &&
+ !composer.selection.caretIsFirstInSelection()
+ ) {
+ var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
+ composer.selection.executeAndRestoreRangy(function() {
+ var parent = state_element.parentNode;
+ composer.selection.selectNode(state_element, true);
+ wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
+ });
+ } else {
+ if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && !composer.selection.isCollapsed()) {
+ composer.selection.executeAndRestoreRangy(function() {
+ wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
+ });
+ } else {
+ wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
+ }
+ }
+ },
+
+ state: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
+ var doc = composer.doc,
+ aliasTagName = ALIAS_MAPPING[tagName] || tagName,
+ ownRanges, isApplied;
+
+ // Check whether the document contains a node with the desired tagName
+ if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
+ !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
+ return false;
+ }
+
+ // Check whether the document contains a node with the desired className
+ if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
+ return false;
+ }
+
+ ownRanges = composer.selection.getOwnRanges();
+
+ if (!ownRanges || ownRanges.length === 0) {
+ return false;
+ }
+
+ isApplied = _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).isAppliedToRange(ownRanges);
+
+ return (isApplied && isApplied.elements) ? isApplied.elements : false;
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+
+ wysihtml5.commands.insertBlockQuote = {
+ exec: function(composer, command) {
+ var state = this.state(composer, command),
+ endToEndParent = composer.selection.isEndToEndInNode(['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P']),
+ prevNode, nextNode;
+
+ composer.selection.executeAndRestore(function() {
+ if (state) {
+ if (composer.config.useLineBreaks) {
+ wysihtml5.dom.lineBreaks(state).add();
+ }
+ wysihtml5.dom.unwrap(state);
+ } else {
+ if (composer.selection.isCollapsed()) {
+ composer.selection.selectLine();
+ }
+
+ if (endToEndParent) {
+ var qouteEl = endToEndParent.ownerDocument.createElement('blockquote');
+ wysihtml5.dom.insert(qouteEl).after(endToEndParent);
+ qouteEl.appendChild(endToEndParent);
+ } else {
+ composer.selection.surround({nodeName: "blockquote"});
+ }
+ }
+ });
+ },
+ state: function(composer, command) {
+ var selectedNode = composer.selection.getSelectedNode(),
+ node = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "BLOCKQUOTE" }, false, composer.element);
+
+ return (node) ? node : false;
+ }
+ };
+
+})(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);
+ }
+ },
+
+ state: function() {
+ return false;
+ }
+};
+;(function(wysihtml5) {
+ var NODE_NAME = "IMG";
+
+ wysihtml5.commands.insertImage = {
+ /**
+ * Inserts an <img>
+ * If selection is already an image link, it removes it
+ *
+ * @example
+ * // either ...
+ * wysihtml5.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg");
+ * // ... or ...
+ * wysihtml5.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" });
+ */
+ exec: function(composer, command, value) {
+ value = typeof(value) === "object" ? value : { src: value };
+
+ var doc = composer.doc,
+ image = this.state(composer),
+ textNode,
+ parent;
+
+ if (image) {
+ // Image already selected, set the caret before it and delete it
+ composer.selection.setBefore(image);
+ parent = image.parentNode;
+ parent.removeChild(image);
+
+ // and it's parent <a> too if it hasn't got any other relevant child nodes
+ wysihtml5.dom.removeEmptyTextNodes(parent);
+ if (parent.nodeName === "A" && !parent.firstChild) {
+ composer.selection.setAfter(parent);
+ parent.parentNode.removeChild(parent);
+ }
+
+ // firefox and ie sometimes don't remove the image handles, even though the image got removed
+ wysihtml5.quirks.redraw(composer.element);
+ return;
+ }
+
+ image = doc.createElement(NODE_NAME);
+
+ for (var i in value) {
+ image.setAttribute(i === "className" ? "class" : i, value[i]);
+ }
+
+ composer.selection.insertNode(image);
+ if (wysihtml5.browser.hasProblemsSettingCaretAfterImg()) {
+ textNode = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
+ composer.selection.insertNode(textNode);
+ composer.selection.setAfter(textNode);
+ } else {
+ composer.selection.setAfter(image);
+ }
+ },
+
+ state: function(composer) {
+ var doc = composer.doc,
+ selectedNode,
+ text,
+ imagesInSelection;
+
+ if (!wysihtml5.dom.hasElementWithTagName(doc, NODE_NAME)) {
+ return false;
+ }
+
+ selectedNode = composer.selection.getSelectedNode();
+ if (!selectedNode) {
+ return false;
+ }
+
+ if (selectedNode.nodeName === NODE_NAME) {
+ // This works perfectly in IE
+ return selectedNode;
+ }
+
+ if (selectedNode.nodeType !== wysihtml5.ELEMENT_NODE) {
+ return false;
+ }
+
+ text = composer.selection.getText();
+ text = wysihtml5.lang.string(text).trim();
+ if (text) {
+ return false;
+ }
+
+ imagesInSelection = composer.selection.getNodes(wysihtml5.ELEMENT_NODE, function(node) {
+ return node.nodeName === "IMG";
+ });
+
+ if (imagesInSelection.length !== 1) {
+ return false;
+ }
+
+ return imagesInSelection[0];
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
+
+ wysihtml5.commands.insertLineBreak = {
+ exec: function(composer, command) {
+ if (composer.commands.support(command)) {
+ composer.doc.execCommand(command, false, null);
+ if (!wysihtml5.browser.autoScrollsToCaret()) {
+ composer.selection.scrollIntoView();
+ }
+ } else {
+ composer.commands.exec("insertHTML", LINE_BREAK);
+ }
+ },
+
+ state: function() {
+ return false;
+ }
+ };
+})(wysihtml5);
+;wysihtml5.commands.insertOrderedList = {
+ exec: function(composer, command) {
+ wysihtml5.commands.insertList.exec(composer, command, "OL");
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.insertList.state(composer, command, "OL");
+ }
+};
+;wysihtml5.commands.insertUnorderedList = {
+ exec: function(composer, command) {
+ wysihtml5.commands.insertList.exec(composer, command, "UL");
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.insertList.state(composer, command, "UL");
+ }
+};
+;wysihtml5.commands.insertList = (function(wysihtml5) {
+
+ var isNode = function(node, name) {
+ if (node && node.nodeName) {
+ if (typeof name === 'string') {
+ name = [name];
+ }
+ for (var n = name.length; n--;) {
+ if (node.nodeName === name[n]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ var findListEl = function(node, nodeName, composer) {
+ var ret = {
+ el: null,
+ other: false
+ };
+
+ if (node) {
+ var parentLi = wysihtml5.dom.getParentElement(node, { nodeName: "LI" }),
+ otherNodeName = (nodeName === "UL") ? "OL" : "UL";
+
+ if (isNode(node, nodeName)) {
+ ret.el = node;
+ } else if (isNode(node, otherNodeName)) {
+ ret = {
+ el: node,
+ other: true
+ };
+ } else if (parentLi) {
+ if (isNode(parentLi.parentNode, nodeName)) {
+ ret.el = parentLi.parentNode;
+ } else if (isNode(parentLi.parentNode, otherNodeName)) {
+ ret = {
+ el : parentLi.parentNode,
+ other: true
+ };
+ }
+ }
+ }
+
+ // do not count list elements outside of composer
+ if (ret.el && !composer.element.contains(ret.el)) {
+ ret.el = null;
+ }
+
+ return ret;
+ };
+
+ var handleSameTypeList = function(el, nodeName, composer) {
+ var otherNodeName = (nodeName === "UL") ? "OL" : "UL",
+ otherLists, innerLists;
+ // Unwrap list
+ // <ul><li>foo</li><li>bar</li></ul>
+ // becomes:
+ // foo<br>bar<br>
+ composer.selection.executeAndRestore(function() {
+ var otherLists = getListsInSelection(otherNodeName, composer);
+ if (otherLists.length) {
+ for (var l = otherLists.length; l--;) {
+ wysihtml5.dom.renameElement(otherLists[l], nodeName.toLowerCase());
+ }
+ } else {
+ innerLists = getListsInSelection(['OL', 'UL'], composer);
+ for (var i = innerLists.length; i--;) {
+ wysihtml5.dom.resolveList(innerLists[i], composer.config.useLineBreaks);
+ }
+ wysihtml5.dom.resolveList(el, composer.config.useLineBreaks);
+ }
+ });
+ };
+
+ var handleOtherTypeList = function(el, nodeName, composer) {
+ var otherNodeName = (nodeName === "UL") ? "OL" : "UL";
+ // 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>
+ // Also rename other lists in selection
+ composer.selection.executeAndRestore(function() {
+ var renameLists = [el].concat(getListsInSelection(otherNodeName, composer));
+
+ // All selection inner lists get renamed too
+ for (var l = renameLists.length; l--;) {
+ wysihtml5.dom.renameElement(renameLists[l], nodeName.toLowerCase());
+ }
+ });
+ };
+
+ var getListsInSelection = function(nodeName, composer) {
+ var ranges = composer.selection.getOwnRanges(),
+ renameLists = [];
+
+ for (var r = ranges.length; r--;) {
+ renameLists = renameLists.concat(ranges[r].getNodes([1], function(node) {
+ return isNode(node, nodeName);
+ }));
+ }
+
+ return renameLists;
+ };
+
+ var createListFallback = function(nodeName, composer) {
+ // Fallback for Create list
+ composer.selection.executeAndRestoreRangy(function() {
+ var tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
+ tempElement = composer.selection.deblockAndSurround({
+ "nodeName": "div",
+ "className": tempClassName
+ }),
+ isEmpty, list;
+
+ // This space causes new lists to never break on enter
+ var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
+ tempElement.innerHTML = tempElement.innerHTML.replace(INVISIBLE_SPACE_REG_EXP, "");
+
+ if (tempElement) {
+ isEmpty = wysihtml5.lang.array(["", "<br>", wysihtml5.INVISIBLE_SPACE]).contains(tempElement.innerHTML);
+ list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.uneditableContainerClassname);
+ if (isEmpty) {
+ composer.selection.selectNode(list.querySelector("li"), true);
+ }
+ }
+ });
+ };
+
+ return {
+ exec: function(composer, command, nodeName) {
+ var doc = composer.doc,
+ cmd = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList",
+ selectedNode = composer.selection.getSelectedNode(),
+ list = findListEl(selectedNode, nodeName, composer);
+
+ if (!list.el) {
+ if (composer.commands.support(cmd)) {
+ doc.execCommand(cmd, false, null);
+ } else {
+ createListFallback(nodeName, composer);
+ }
+ } else if (list.other) {
+ handleOtherTypeList(list.el, nodeName, composer);
+ } else {
+ handleSameTypeList(list.el, nodeName, composer);
+ }
+ },
+
+ state: function(composer, command, nodeName) {
+ var selectedNode = composer.selection.getSelectedNode(),
+ list = findListEl(selectedNode, nodeName, composer);
+
+ return (list.el && !list.other) ? list.el : false;
+ }
+ };
+
+})(wysihtml5);;wysihtml5.commands.italic = {
+ exec: function(composer, command) {
+ wysihtml5.commands.formatInline.execWithToggle(composer, command, "i");
+ },
+
+ 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) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var CLASS_NAME = "wysiwyg-text-align-left",
+ REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
+
+ wysihtml5.commands.justifyLeft = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var CLASS_NAME = "wysiwyg-text-align-right",
+ REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
+
+ wysihtml5.commands.justifyRight = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ 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.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var STYLE_STR = "text-align: right;",
+ REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.alignRightStyle = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var STYLE_STR = "text-align: left;",
+ REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.alignLeftStyle = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var STYLE_STR = "text-align: center;",
+ REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.alignCenterStyle = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;wysihtml5.commands.redo = {
+ exec: function(composer) {
+ return composer.undoManager.redo();
+ },
+
+ state: function(composer) {
+ return false;
+ }
+};
+;wysihtml5.commands.underline = {
+ exec: function(composer, command) {
+ wysihtml5.commands.formatInline.execWithToggle(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;
+ }
+};
+;wysihtml5.commands.createTable = {
+ exec: function(composer, command, value) {
+ var col, row, html;
+ if (value && value.cols && value.rows && parseInt(value.cols, 10) > 0 && parseInt(value.rows, 10) > 0) {
+ if (value.tableStyle) {
+ html = "<table style=\"" + value.tableStyle + "\">";
+ } else {
+ html = "<table>";
+ }
+ html += "<tbody>";
+ for (row = 0; row < value.rows; row ++) {
+ html += '<tr>';
+ for (col = 0; col < value.cols; col ++) {
+ html += "<td>&nbsp;</td>";
+ }
+ html += '</tr>';
+ }
+ html += "</tbody></table>";
+ composer.commands.exec("insertHTML", html);
+ //composer.selection.insertHTML(html);
+ }
+
+
+ },
+
+ state: function(composer, command) {
+ return false;
+ }
+};
+;wysihtml5.commands.mergeTableCells = {
+ exec: function(composer, command) {
+ if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
+ if (this.state(composer, command)) {
+ wysihtml5.dom.table.unmergeCell(composer.tableSelection.start);
+ } else {
+ wysihtml5.dom.table.mergeCellsBetween(composer.tableSelection.start, composer.tableSelection.end);
+ }
+ }
+ },
+
+ state: function(composer, command) {
+ if (composer.tableSelection) {
+ var start = composer.tableSelection.start,
+ end = composer.tableSelection.end;
+ if (start && end && start == end &&
+ ((
+ wysihtml5.dom.getAttribute(start, "colspan") &&
+ parseInt(wysihtml5.dom.getAttribute(start, "colspan"), 10) > 1
+ ) || (
+ wysihtml5.dom.getAttribute(start, "rowspan") &&
+ parseInt(wysihtml5.dom.getAttribute(start, "rowspan"), 10) > 1
+ ))
+ ) {
+ return [start];
+ }
+ }
+ return false;
+ }
+};
+;wysihtml5.commands.addTableCells = {
+ exec: function(composer, command, value) {
+ if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
+
+ // switches start and end if start is bigger than end (reverse selection)
+ var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end);
+ if (value == "before" || value == "above") {
+ wysihtml5.dom.table.addCells(tableSelect.start, value);
+ } else if (value == "after" || value == "below") {
+ wysihtml5.dom.table.addCells(tableSelect.end, value);
+ }
+ setTimeout(function() {
+ composer.tableSelection.select(tableSelect.start, tableSelect.end);
+ },0);
+ }
+ },
+
+ state: function(composer, command) {
+ return false;
+ }
+};
+;wysihtml5.commands.deleteTableCells = {
+ exec: function(composer, command, value) {
+ if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
+ var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end),
+ idx = wysihtml5.dom.table.indexOf(tableSelect.start),
+ selCell,
+ table = composer.tableSelection.table;
+
+ wysihtml5.dom.table.removeCells(tableSelect.start, value);
+ setTimeout(function() {
+ // move selection to next or previous if not present
+ selCell = wysihtml5.dom.table.findCell(table, idx);
+
+ if (!selCell){
+ if (value == "row") {
+ selCell = wysihtml5.dom.table.findCell(table, {
+ "row": idx.row - 1,
+ "col": idx.col
+ });
+ }
+
+ if (value == "column") {
+ selCell = wysihtml5.dom.table.findCell(table, {
+ "row": idx.row,
+ "col": idx.col - 1
+ });
+ }
+ }
+ if (selCell) {
+ composer.tableSelection.select(selCell, selCell);
+ }
+ }, 0);
+
+ }
+ },
+
+ state: function(composer, command) {
+ return false;
+ }
+};
+;wysihtml5.commands.indentList = {
+ exec: function(composer, command, value) {
+ var listEls = composer.selection.getSelectionParentsByTag('LI');
+ if (listEls) {
+ return this.tryToPushLiLevel(listEls, composer.selection);
+ }
+ return false;
+ },
+
+ state: function(composer, command) {
+ return false;
+ },
+
+ tryToPushLiLevel: function(liNodes, selection) {
+ var listTag, list, prevLi, liNode, prevLiList,
+ found = false;
+
+ selection.executeAndRestoreRangy(function() {
+
+ for (var i = liNodes.length; i--;) {
+ liNode = liNodes[i];
+ listTag = (liNode.parentNode.nodeName === 'OL') ? 'OL' : 'UL';
+ list = liNode.ownerDocument.createElement(listTag);
+ prevLi = wysihtml5.dom.domNode(liNode).prev({nodeTypes: [wysihtml5.ELEMENT_NODE]});
+ prevLiList = (prevLi) ? prevLi.querySelector('ul, ol') : null;
+
+ if (prevLi) {
+ if (prevLiList) {
+ prevLiList.appendChild(liNode);
+ } else {
+ list.appendChild(liNode);
+ prevLi.appendChild(list);
+ }
+ found = true;
+ }
+ }
+
+ });
+ return found;
+ }
+};
+;wysihtml5.commands.outdentList = {
+ exec: function(composer, command, value) {
+ var listEls = composer.selection.getSelectionParentsByTag('LI');
+ if (listEls) {
+ return this.tryToPullLiLevel(listEls, composer);
+ }
+ return false;
+ },
+
+ state: function(composer, command) {
+ return false;
+ },
+
+ tryToPullLiLevel: function(liNodes, composer) {
+ var listNode, outerListNode, outerLiNode, list, prevLi, liNode, afterList,
+ found = false,
+ that = this;
+
+ composer.selection.executeAndRestoreRangy(function() {
+
+ for (var i = liNodes.length; i--;) {
+ liNode = liNodes[i];
+ if (liNode.parentNode) {
+ listNode = liNode.parentNode;
+
+ if (listNode.tagName === 'OL' || listNode.tagName === 'UL') {
+ found = true;
+
+ outerListNode = wysihtml5.dom.getParentElement(listNode.parentNode, { nodeName: ['OL', 'UL']}, false, composer.element);
+ outerLiNode = wysihtml5.dom.getParentElement(listNode.parentNode, { nodeName: ['LI']}, false, composer.element);
+
+ if (outerListNode && outerLiNode) {
+
+ if (liNode.nextSibling) {
+ afterList = that.getAfterList(listNode, liNode);
+ liNode.appendChild(afterList);
+ }
+ outerListNode.insertBefore(liNode, outerLiNode.nextSibling);
+
+ } else {
+
+ if (liNode.nextSibling) {
+ afterList = that.getAfterList(listNode, liNode);
+ liNode.appendChild(afterList);
+ }
+
+ for (var j = liNode.childNodes.length; j--;) {
+ listNode.parentNode.insertBefore(liNode.childNodes[j], listNode.nextSibling);
+ }
+
+ listNode.parentNode.insertBefore(document.createElement('br'), listNode.nextSibling);
+ liNode.parentNode.removeChild(liNode);
+
+ }
+
+ // cleanup
+ if (listNode.childNodes.length === 0) {
+ listNode.parentNode.removeChild(listNode);
+ }
+ }
+ }
+ }
+
+ });
+ return found;
+ },
+
+ getAfterList: function(listNode, liNode) {
+ var nodeName = listNode.nodeName,
+ newList = document.createElement(nodeName);
+
+ while (liNode.nextSibling) {
+ newList.appendChild(liNode.nextSibling);
+ }
+ return newList;
+ }
+
+};;/**
+ * Undo Manager for wysihtml5
+ * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
+ */
+(function(wysihtml5) {
+ var Z_KEY = 90,
+ Y_KEY = 89,
+ BACKSPACE_KEY = 8,
+ DELETE_KEY = 46,
+ 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;
+
+ function cleanTempElements(doc) {
+ var tempElement;
+ while (tempElement = doc.querySelector("._wysihtml5-temp")) {
+ tempElement.parentNode.removeChild(tempElement);
+ }
+ }
+
+ wysihtml5.UndoManager = wysihtml5.lang.Dispatcher.extend(
+ /** @scope wysihtml5.UndoManager.prototype */ {
+ constructor: function(editor) {
+ this.editor = editor;
+ this.composer = editor.composer;
+ this.element = this.composer.element;
+
+ this.position = 0;
+ this.historyStr = [];
+ this.historyDom = [];
+
+ this.transact();
+
+ this._observe();
+ },
+
+ _observe: function() {
+ var that = this,
+ doc = this.composer.sandbox.getDocument(),
+ lastKey;
+
+ // Catch CTRL+Z and CTRL+Y
+ dom.observe(this.element, "keydown", function(event) {
+ if (event.altKey || (!event.ctrlKey && !event.metaKey)) {
+ return;
+ }
+
+ var keyCode = event.keyCode,
+ isUndo = keyCode === Z_KEY && !event.shiftKey,
+ isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY);
+
+ if (isUndo) {
+ that.undo();
+ event.preventDefault();
+ } else if (isRedo) {
+ that.redo();
+ event.preventDefault();
+ }
+ });
+
+ // Catch delete and backspace
+ dom.observe(this.element, "keydown", function(event) {
+ var keyCode = event.keyCode;
+ if (keyCode === lastKey) {
+ return;
+ }
+
+ lastKey = keyCode;
+
+ if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) {
+ that.transact();
+ }
+ });
+
+ this.editor
+ .on("newword:composer", function() {
+ that.transact();
+ })
+
+ .on("beforecommand:composer", function() {
+ that.transact();
+ });
+ },
+
+ transact: function() {
+ var previousHtml = this.historyStr[this.position - 1],
+ currentHtml = this.composer.getValue(false, false),
+ composerIsVisible = this.element.offsetWidth > 0 && this.element.offsetHeight > 0,
+ range, node, offset, element, position;
+
+ if (currentHtml === previousHtml) {
+ return;
+ }
+
+ var length = this.historyStr.length = this.historyDom.length = this.position;
+ if (length > MAX_HISTORY_ENTRIES) {
+ this.historyStr.shift();
+ this.historyDom.shift();
+ this.position--;
+ }
+
+ this.position++;
+
+ if (composerIsVisible) {
+ // Do not start saving selection if composer is not visible
+ range = this.composer.selection.getRange();
+ node = (range && range.startContainer) ? range.startContainer : this.element;
+ offset = (range && range.startOffset) ? range.startOffset : 0;
+
+ 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);
+
+ if (element) {
+ element.removeAttribute(DATA_ATTR_OFFSET);
+ element.removeAttribute(DATA_ATTR_NODE);
+ }
+
+ },
+
+ undo: function() {
+ this.transact();
+
+ if (!this.undoPossible()) {
+ return;
+ }
+
+ this.set(this.historyDom[--this.position - 1]);
+ this.editor.fire("undo:composer");
+ },
+
+ redo: function() {
+ if (!this.redoPossible()) {
+ return;
+ }
+
+ this.set(this.historyDom[++this.position - 1]);
+ this.editor.fire("redo:composer");
+ },
+
+ 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);
+;/**
+ * TODO: the following methods still need unit test coverage
+ */
+wysihtml5.views.View = Base.extend(
+ /** @scope wysihtml5.views.View.prototype */ {
+ constructor: function(parent, textareaElement, config) {
+ this.parent = parent;
+ this.element = textareaElement;
+ this.config = config;
+ if (!this.config.noTextarea) {
+ this._observeViewChange();
+ }
+ },
+
+ _observeViewChange: function() {
+ var that = this;
+ this.parent.on("beforeload", function() {
+ that.parent.on("change_view", function(view) {
+ if (view === that.name) {
+ that.parent.currentView = that;
+ that.show();
+ // Using tiny delay here to make sure that the placeholder is set before focusing
+ setTimeout(function() { that.focus(); }, 0);
+ } else {
+ that.hide();
+ }
+ });
+ });
+ },
+
+ focus: function() {
+ if (this.element.ownerDocument.querySelector(":focus") === this.element) {
+ return;
+ }
+
+ try { this.element.focus(); } catch(e) {}
+ },
+
+ hide: function() {
+ this.element.style.display = "none";
+ },
+
+ show: function() {
+ this.element.style.display = "";
+ },
+
+ disable: function() {
+ this.element.setAttribute("disabled", "disabled");
+ },
+
+ enable: function() {
+ this.element.removeAttribute("disabled");
+ }
+});
+;(function(wysihtml5) {
+ var dom = wysihtml5.dom,
+ browser = wysihtml5.browser;
+
+ wysihtml5.views.Composer = wysihtml5.views.View.extend(
+ /** @scope wysihtml5.views.Composer.prototype */ {
+ name: "composer",
+
+ // Needed for firefox in order to display a proper caret in an empty contentEditable
+ CARET_HACK: "<br>",
+
+ constructor: function(parent, editableElement, config) {
+ this.base(parent, editableElement, config);
+ if (!this.config.noTextarea) {
+ this.textarea = this.parent.textarea;
+ } else {
+ this.editableArea = editableElement;
+ }
+ if (this.config.contentEditableMode) {
+ this._initContentEditableArea();
+ } else {
+ this._initSandbox();
+ }
+ },
+
+ clear: function() {
+ this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : this.CARET_HACK;
+ },
+
+ getValue: function(parse, clearInternals) {
+ var value = this.isEmpty() ? "" : wysihtml5.quirks.getCorrectInnerHTML(this.element);
+ if (parse !== false) {
+ value = this.parent.parse(value, (clearInternals === false) ? false : true);
+ }
+
+ return value;
+ },
+
+ setValue: function(html, parse) {
+ if (parse) {
+ html = this.parent.parse(html);
+ }
+
+ try {
+ this.element.innerHTML = html;
+ } catch (e) {
+ this.element.innerText = html;
+ }
+ },
+
+ cleanUp: function() {
+ this.parent.parse(this.element);
+ },
+
+ show: function() {
+ this.editableArea.style.display = this._displayStyle || "";
+
+ if (!this.config.noTextarea && !this.textarea.element.disabled) {
+ // Firefox needs this, otherwise contentEditable becomes uneditable
+ this.disable();
+ this.enable();
+ }
+ },
+
+ hide: function() {
+ this._displayStyle = dom.getStyle("display").from(this.editableArea);
+ if (this._displayStyle === "none") {
+ this._displayStyle = null;
+ }
+ this.editableArea.style.display = "none";
+ },
+
+ disable: function() {
+ this.parent.fire("disable:composer");
+ this.element.removeAttribute("contentEditable");
+ },
+
+ enable: function() {
+ this.parent.fire("enable:composer");
+ this.element.setAttribute("contentEditable", "true");
+ },
+
+ focus: function(setToEnd) {
+ // IE 8 fires the focus event after .focus()
+ // This is needed by our simulate_placeholder.js to work
+ // therefore we clear it ourselves this time
+ if (wysihtml5.browser.doesAsyncFocus() && this.hasPlaceholderSet()) {
+ this.clear();
+ }
+
+ this.base();
+
+ var lastChild = this.element.lastChild;
+ if (setToEnd && lastChild && this.selection) {
+ if (lastChild.nodeName === "BR") {
+ this.selection.setBefore(this.element.lastChild);
+ } else {
+ this.selection.setAfter(this.element.lastChild);
+ }
+ }
+ },
+
+ getTextContent: function() {
+ return dom.getTextContent(this.element);
+ },
+
+ hasPlaceholderSet: function() {
+ return this.getTextContent() == ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")) && this.placeholderSet;
+ },
+
+ isEmpty: function() {
+ var innerHTML = this.element.innerHTML.toLowerCase();
+ return (/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i).test(innerHTML) ||
+ innerHTML === "" ||
+ innerHTML === "<br>" ||
+ innerHTML === "<p></p>" ||
+ innerHTML === "<p><br></p>" ||
+ this.hasPlaceholderSet();
+ },
+
+ _initContentEditableArea: function() {
+ var that = this;
+
+ if (this.config.noTextarea) {
+ this.sandbox = new dom.ContentEditableArea(function() {
+ that._create();
+ }, {}, this.editableArea);
+ } else {
+ this.sandbox = new dom.ContentEditableArea(function() {
+ that._create();
+ });
+ this.editableArea = this.sandbox.getContentEditable();
+ dom.insert(this.editableArea).after(this.textarea.element);
+ this._createWysiwygFormField();
+ }
+ },
+
+ _initSandbox: function() {
+ var that = this;
+
+ this.sandbox = new dom.Sandbox(function() {
+ that._create();
+ }, {
+ stylesheets: this.config.stylesheets
+ });
+ this.editableArea = this.sandbox.getIframe();
+
+ var textareaElement = this.textarea.element;
+ dom.insert(this.editableArea).after(textareaElement);
+
+ this._createWysiwygFormField();
+ },
+
+ // Creates hidden field which tells the server after submit, that the user used an wysiwyg editor
+ _createWysiwygFormField: function() {
+ if (this.textarea.element.form) {
+ var hiddenField = document.createElement("input");
+ hiddenField.type = "hidden";
+ hiddenField.name = "_wysihtml5_mode";
+ hiddenField.value = 1;
+ dom.insert(hiddenField).after(this.textarea.element);
+ }
+ },
+
+ _create: function() {
+ var that = this;
+ this.doc = this.sandbox.getDocument();
+ this.element = (this.config.contentEditableMode) ? this.sandbox.getContentEditable() : this.doc.body;
+ if (!this.config.noTextarea) {
+ this.textarea = this.parent.textarea;
+ this.element.innerHTML = this.textarea.getValue(true, false);
+ } else {
+ this.cleanUp(); // cleans contenteditable on initiation as it may contain html
+ }
+
+ // Make sure our selection handler is ready
+ this.selection = new wysihtml5.Selection(this.parent, this.element, this.config.uneditableContainerClassname);
+
+ // Make sure commands dispatcher is ready
+ this.commands = new wysihtml5.Commands(this.parent);
+
+ if (!this.config.noTextarea) {
+ 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
+ if (this.config.style && !this.config.contentEditableMode) {
+ this.style();
+ }
+
+ this.observe();
+
+ var name = this.config.name;
+ if (name) {
+ dom.addClass(this.element, name);
+ if (!this.config.contentEditableMode) { dom.addClass(this.editableArea, name); }
+ }
+
+ this.enable();
+
+ if (!this.config.noTextarea && this.textarea.element.disabled) {
+ this.disable();
+ }
+
+ // Simulate html5 placeholder attribute on contentEditable element
+ var placeholderText = typeof(this.config.placeholder) === "string"
+ ? this.config.placeholder
+ : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder"));
+ if (placeholderText) {
+ dom.simulatePlaceholder(this.parent, this, placeholderText);
+ }
+
+ // 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
+ // This doesn't work on IOS (5.1.1)
+ if (!this.config.noTextarea && (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) {
+ setTimeout(function() { that.focus(true); }, 100);
+ }
+
+ // IE sometimes leaves a single paragraph, which can't be removed by the user
+ if (!browser.clearsContentEditableCorrectly()) {
+ wysihtml5.quirks.ensureProperClearing(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
+ if (!this.config.noTextarea) { this.textarea.hide(); }
+
+ // Fire global (before-)load event
+ this.parent.fire("beforeload").fire("load");
+ },
+
+ _initAutoLinking: function() {
+ var that = this,
+ supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
+ supportsAutoLinking = browser.doesAutoLinkingInContentEditable();
+ if (supportsDisablingOfAutoLinking) {
+ this.commands.exec("autoUrlDetect", false);
+ }
+
+ if (!this.config.autoLink) {
+ return;
+ }
+
+ // Only do the auto linking by ourselves when the browser doesn't support auto linking
+ // OR when he supports auto linking but we were able to turn it off (IE9+)
+ if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
+ this.parent.on("newword:composer", function() {
+ if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
+ that.selection.executeAndRestore(function(startContainer, endContainer) {
+ var uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname),
+ isInUneditable = false;
+
+ for (var i = uneditables.length; i--;) {
+ if (wysihtml5.dom.contains(uneditables[i], endContainer)) {
+ isInUneditable = true;
+ }
+ }
+
+ if (!isInUneditable) dom.autoLink(endContainer.parentNode, [that.config.uneditableContainerClassname]);
+ });
+ }
+ });
+
+ dom.observe(this.element, "blur", function() {
+ dom.autoLink(that.element, [that.config.uneditableContainerClassname]);
+ });
+ }
+
+ // Assuming we have the following:
+ // <a href="http://www.google.de">http://www.google.de</a>
+ // If a user now changes the url in the innerHTML we want to make sure that
+ // it's synchronized with the href attribute (as long as the innerHTML is still a url)
+ var // Use a live NodeList to check whether there are any links in the document
+ links = this.sandbox.getDocument().getElementsByTagName("a"),
+ // The autoLink helper method reveals a reg exp to detect correct urls
+ urlRegExp = dom.autoLink.URL_REG_EXP,
+ getTextContent = function(element) {
+ var textContent = wysihtml5.lang.string(dom.getTextContent(element)).trim();
+ if (textContent.substr(0, 4) === "www.") {
+ textContent = "http://" + textContent;
+ }
+ return textContent;
+ };
+
+ dom.observe(this.element, "keydown", function(event) {
+ if (!links.length) {
+ return;
+ }
+
+ var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument),
+ link = dom.getParentElement(selectedNode, { nodeName: "A" }, 4),
+ textContent;
+
+ if (!link) {
+ return;
+ }
+
+ textContent = getTextContent(link);
+ // keydown is fired before the actual content is changed
+ // therefore we set a timeout to change the href
+ setTimeout(function() {
+ var newTextContent = getTextContent(link);
+ if (newTextContent === textContent) {
+ return;
+ }
+
+ // Only set href when new href looks like a valid url
+ if (newTextContent.match(urlRegExp)) {
+ link.setAttribute("href", newTextContent);
+ }
+ }, 0);
+ });
+ },
+
+ _initObjectResizing: function() {
+ this.commands.exec("enableObjectResizing", true);
+
+ // 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);
+ });
+ }
+ },
+
+ _initUndoManager: function() {
+ this.undoManager = new wysihtml5.UndoManager(this.parent);
+ },
+
+ _initLineBreaking: function() {
+ var that = this,
+ USE_NATIVE_LINE_BREAK_INSIDE_TAGS = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
+ LIST_TAGS = ["UL", "OL", "MENU"];
+
+ function adjust(selectedNode) {
+ var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
+ if (parentElement && dom.contains(that.element, parentElement)) {
+ that.selection.executeAndRestore(function() {
+ if (that.config.useLineBreaks) {
+ dom.replaceWithChildNodes(parentElement);
+ } else if (parentElement.nodeName !== "P") {
+ dom.renameElement(parentElement, "p");
+ }
+ });
+ }
+ }
+
+ if (!this.config.useLineBreaks) {
+ dom.observe(this.element, ["focus", "keydown"], function() {
+ if (that.isEmpty()) {
+ var paragraph = that.doc.createElement("P");
+ that.element.innerHTML = "";
+ that.element.appendChild(paragraph);
+ if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
+ paragraph.innerHTML = "<br>";
+ that.selection.setBefore(paragraph.firstChild);
+ } else {
+ that.selection.selectNode(paragraph, true);
+ }
+ }
+ });
+ }
+
+ // Under certain circumstances Chrome + Safari create nested <p> or <hX> tags after paste
+ // Inserting an invisible white space in front of it fixes the issue
+ // This is too hacky and causes selection not to replace content on paste in chrome
+ /* if (browser.createsNestedInvalidMarkupAfterPaste()) {
+ dom.observe(this.element, "paste", function(event) {
+ var invisibleSpace = that.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
+ that.selection.insertNode(invisibleSpace);
+ });
+ }*/
+
+
+ dom.observe(this.element, "keydown", function(event) {
+ var keyCode = event.keyCode;
+
+ if (event.shiftKey) {
+ return;
+ }
+
+ if (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY) {
+ return;
+ }
+ var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { nodeName: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
+ if (blockElement) {
+ setTimeout(function() {
+ // Unwrap paragraph after leaving a list or a H1-6
+ var selectedNode = that.selection.getSelectedNode(),
+ list;
+
+ if (blockElement.nodeName === "LI") {
+ if (!selectedNode) {
+ return;
+ }
+
+ list = dom.getParentElement(selectedNode, { nodeName: LIST_TAGS }, 2);
+
+ if (!list) {
+ adjust(selectedNode);
+ }
+ }
+
+ if (keyCode === wysihtml5.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
+ adjust(selectedNode);
+ }
+ }, 0);
+ return;
+ }
+
+ if (that.config.useLineBreaks && keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
+ event.preventDefault();
+ that.commands.exec("insertLineBreak");
+
+ }
+ });
+ }
+ });
+})(wysihtml5);
+;(function(wysihtml5) {
+ var dom = wysihtml5.dom,
+ doc = document,
+ win = window,
+ HOST_TEMPLATE = doc.createElement("div"),
+ /**
+ * Styles to copy from textarea to the composer element
+ */
+ TEXT_FORMATTING = [
+ "background-color",
+ "color", "cursor",
+ "font-family", "font-size", "font-style", "font-variant", "font-weight",
+ "line-height", "letter-spacing",
+ "text-align", "text-decoration", "text-indent", "text-rendering",
+ "word-break", "word-wrap", "word-spacing"
+ ],
+ /**
+ * Styles to copy from textarea to the iframe
+ */
+ BOX_FORMATTING = [
+ "background-color",
+ "border-collapse",
+ "border-bottom-color", "border-bottom-style", "border-bottom-width",
+ "border-left-color", "border-left-style", "border-left-width",
+ "border-right-color", "border-right-style", "border-right-width",
+ "border-top-color", "border-top-style", "border-top-width",
+ "clear", "display", "float",
+ "margin-bottom", "margin-left", "margin-right", "margin-top",
+ "outline-color", "outline-offset", "outline-width", "outline-style",
+ "padding-left", "padding-right", "padding-top", "padding-bottom",
+ "position", "top", "left", "right", "bottom", "z-index",
+ "vertical-align", "text-align",
+ "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing",
+ "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow",
+ "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius",
+ "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius",
+ "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius",
+ "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius",
+ "width", "height"
+ ],
+ ADDITIONAL_CSS_RULES = [
+ "html { height: 100%; }",
+ "body { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }",
+ "body > p:first-child { margin-top: 0; }",
+ "._wysihtml5-temp { display: none; }",
+ wysihtml5.browser.isGecko ?
+ "body.placeholder { color: graytext !important; }" :
+ "body.placeholder { color: #a9a9a9 !important; }",
+ // Ensure that user see's broken images and can delete them
+ "img:-moz-broken { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
+ ];
+
+ /**
+ * With "setActive" IE offers a smart way of focusing elements without scrolling them into view:
+ * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx
+ *
+ * Other browsers need a more hacky way: (pssst don't tell my mama)
+ * In order to prevent the element being scrolled into view when focusing it, we simply
+ * move it out of the scrollable area, focus it, and reset it's position
+ */
+ var focusWithoutScrolling = function(element) {
+ if (element.setActive) {
+ // Following line could cause a js error when the textarea is invisible
+ // See https://github.com/xing/wysihtml5/issues/9
+ try { element.setActive(); } catch(e) {}
+ } else {
+ var elementStyle = element.style,
+ originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop,
+ originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft,
+ originalStyles = {
+ position: elementStyle.position,
+ top: elementStyle.top,
+ left: elementStyle.left,
+ WebkitUserSelect: elementStyle.WebkitUserSelect
+ };
+
+ dom.setStyles({
+ position: "absolute",
+ top: "-99999px",
+ left: "-99999px",
+ // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother
+ WebkitUserSelect: "none"
+ }).on(element);
+
+ element.focus();
+
+ dom.setStyles(originalStyles).on(element);
+
+ if (win.scrollTo) {
+ // Some browser extensions unset this method to prevent annoyances
+ // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100
+ // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1
+ win.scrollTo(originalScrollLeft, originalScrollTop);
+ }
+ }
+ };
+
+
+ wysihtml5.views.Composer.prototype.style = function() {
+ var that = this,
+ originalActiveElement = doc.querySelector(":focus"),
+ textareaElement = this.textarea.element,
+ hasPlaceholder = textareaElement.hasAttribute("placeholder"),
+ originalPlaceholder = hasPlaceholder && textareaElement.getAttribute("placeholder"),
+ originalDisplayValue = textareaElement.style.display,
+ originalDisabled = textareaElement.disabled,
+ displayValueForCopying;
+
+ this.focusStylesHost = HOST_TEMPLATE.cloneNode(false);
+ this.blurStylesHost = HOST_TEMPLATE.cloneNode(false);
+ this.disabledStylesHost = HOST_TEMPLATE.cloneNode(false);
+
+ // Remove placeholder before copying (as the placeholder has an affect on the computed style)
+ if (hasPlaceholder) {
+ textareaElement.removeAttribute("placeholder");
+ }
+
+ if (textareaElement === originalActiveElement) {
+ textareaElement.blur();
+ }
+
+ // enable for copying styles
+ textareaElement.disabled = false;
+
+ // set textarea to display="none" to get cascaded styles via getComputedStyle
+ textareaElement.style.display = displayValueForCopying = "none";
+
+ if ((textareaElement.getAttribute("rows") && dom.getStyle("height").from(textareaElement) === "auto") ||
+ (textareaElement.getAttribute("cols") && dom.getStyle("width").from(textareaElement) === "auto")) {
+ textareaElement.style.display = displayValueForCopying = originalDisplayValue;
+ }
+
+ // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) ---------
+ dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.editableArea).andTo(this.blurStylesHost);
+
+ // --------- editor styles ---------
+ dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost);
+
+ // --------- apply standard rules ---------
+ dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument);
+
+ // --------- :disabled styles ---------
+ textareaElement.disabled = true;
+ dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
+ dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
+ textareaElement.disabled = originalDisabled;
+
+ // --------- :focus styles ---------
+ textareaElement.style.display = originalDisplayValue;
+ focusWithoutScrolling(textareaElement);
+ textareaElement.style.display = displayValueForCopying;
+
+ dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost);
+ dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost);
+
+ // reset textarea
+ textareaElement.style.display = originalDisplayValue;
+
+ dom.copyStyles(["display"]).from(textareaElement).to(this.editableArea);
+
+ // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus
+ // this is needed for when the change_view event is fired where the iframe is hidden and then
+ // the blur event fires and re-displays it
+ var boxFormattingStyles = wysihtml5.lang.array(BOX_FORMATTING).without(["display"]);
+
+ // --------- restore focus ---------
+ if (originalActiveElement) {
+ originalActiveElement.focus();
+ } else {
+ textareaElement.blur();
+ }
+
+ // --------- restore placeholder ---------
+ if (hasPlaceholder) {
+ textareaElement.setAttribute("placeholder", originalPlaceholder);
+ }
+
+ // --------- Sync focus/blur styles ---------
+ this.parent.on("focus:composer", function() {
+ dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.editableArea);
+ dom.copyStyles(TEXT_FORMATTING) .from(that.focusStylesHost).to(that.element);
+ });
+
+ this.parent.on("blur:composer", function() {
+ dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
+ dom.copyStyles(TEXT_FORMATTING) .from(that.blurStylesHost).to(that.element);
+ });
+
+ this.parent.observe("disable:composer", function() {
+ dom.copyStyles(boxFormattingStyles) .from(that.disabledStylesHost).to(that.editableArea);
+ dom.copyStyles(TEXT_FORMATTING) .from(that.disabledStylesHost).to(that.element);
+ });
+
+ this.parent.observe("enable:composer", function() {
+ dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
+ dom.copyStyles(TEXT_FORMATTING) .from(that.blurStylesHost).to(that.element);
+ });
+
+ return this;
+ };
+})(wysihtml5);
+;/**
+ * Taking care of events
+ * - Simulating 'change' event on contentEditable element
+ * - Handling drag & drop logic
+ * - Catch paste events
+ * - Dispatch proprietary newword:composer event
+ * - Keyboard shortcuts
+ */
+(function(wysihtml5) {
+ var dom = wysihtml5.dom,
+ browser = wysihtml5.browser,
+ /**
+ * Map keyCodes to query commands
+ */
+ shortcuts = {
+ "66": "bold", // B
+ "73": "italic", // I
+ "85": "underline" // U
+ };
+
+ var deleteAroundEditable = function(selection, uneditable, element) {
+ // merge node with previous node from uneditable
+ var prevNode = selection.getPreviousNode(uneditable, true),
+ curNode = selection.getSelectedNode();
+
+ if (curNode.nodeType !== 1 && curNode.parentNode !== element) { curNode = curNode.parentNode; }
+ if (prevNode) {
+ if (curNode.nodeType == 1) {
+ var first = curNode.firstChild;
+
+ if (prevNode.nodeType == 1) {
+ while (curNode.firstChild) {
+ prevNode.appendChild(curNode.firstChild);
+ }
+ } else {
+ while (curNode.firstChild) {
+ uneditable.parentNode.insertBefore(curNode.firstChild, uneditable);
+ }
+ }
+ if (curNode.parentNode) {
+ curNode.parentNode.removeChild(curNode);
+ }
+ selection.setBefore(first);
+ } else {
+ if (prevNode.nodeType == 1) {
+ prevNode.appendChild(curNode);
+ } else {
+ uneditable.parentNode.insertBefore(curNode, uneditable);
+ }
+ selection.setBefore(curNode);
+ }
+ }
+ };
+
+ var handleDeleteKeyPress = function(event, selection, element, composer) {
+ if (selection.isCollapsed()) {
+ if (selection.caretIsInTheBeginnig('LI')) {
+ event.preventDefault();
+ composer.commands.exec('outdentList');
+ } else if (selection.caretIsInTheBeginnig()) {
+ event.preventDefault();
+ } else {
+
+ if (selection.caretIsFirstInSelection() &&
+ selection.getPreviousNode() &&
+ selection.getPreviousNode().nodeName &&
+ (/^H\d$/gi).test(selection.getPreviousNode().nodeName)
+ ) {
+ var prevNode = selection.getPreviousNode();
+ event.preventDefault();
+ if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
+ // heading is empty
+ prevNode.parentNode.removeChild(prevNode);
+ } else {
+ var range = prevNode.ownerDocument.createRange();
+ range.selectNodeContents(prevNode);
+ range.collapse(false);
+ selection.setSelection(range);
+ }
+ }
+
+ var beforeUneditable = selection.caretIsBeforeUneditable();
+ // Do a special delete if caret would delete uneditable
+ if (beforeUneditable) {
+ event.preventDefault();
+ deleteAroundEditable(selection, beforeUneditable, element);
+ }
+ }
+ } else {
+ if (selection.containsUneditable()) {
+ event.preventDefault();
+ selection.deleteContents();
+ }
+ }
+ };
+
+ var handleTabKeyDown = function(composer, element) {
+ if (!composer.selection.isCollapsed()) {
+ composer.selection.deleteContents();
+ } else if (composer.selection.caretIsInTheBeginnig('LI')) {
+ if (composer.commands.exec('indentList')) return;
+ }
+
+ // Is &emsp; close enough to tab. Could not find enough counter arguments for now.
+ composer.commands.exec("insertHTML", "&emsp;");
+ };
+
+ wysihtml5.views.Composer.prototype.observe = function() {
+ var that = this,
+ state = this.getValue(false, false),
+ container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
+ element = this.element,
+ focusBlurElement = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? element : this.sandbox.getWindow(),
+ pasteEvents = ["drop", "paste", "beforepaste"],
+ interactionEvents = ["drop", "paste", "mouseup", "focus", "keyup"];
+
+ // --------- destroy:composer event ---------
+ dom.observe(container, "DOMNodeRemoved", function() {
+ clearInterval(domNodeRemovedInterval);
+ that.parent.fire("destroy:composer");
+ });
+
+ // DOMNodeRemoved event is not supported in IE 8
+ if (!browser.supportsMutationEvents()) {
+ var domNodeRemovedInterval = setInterval(function() {
+ if (!dom.contains(document.documentElement, container)) {
+ clearInterval(domNodeRemovedInterval);
+ that.parent.fire("destroy:composer");
+ }
+ }, 250);
+ }
+
+ // --------- User interaction tracking --
+
+ dom.observe(focusBlurElement, interactionEvents, function() {
+ setTimeout(function() {
+ that.parent.fire("interaction").fire("interaction:composer");
+ }, 0);
+ });
+
+
+ if (this.config.handleTables) {
+ if(!this.tableClickHandle && this.doc.execCommand && wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") && wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing")) {
+ if (this.sandbox.getIframe) {
+ this.tableClickHandle = dom.observe(container , ["focus", "mouseup", "mouseover"], function() {
+ that.doc.execCommand("enableObjectResizing", false, "false");
+ that.doc.execCommand("enableInlineTableEditing", false, "false");
+ that.tableClickHandle.stop();
+ });
+ } else {
+ setTimeout(function() {
+ that.doc.execCommand("enableObjectResizing", false, "false");
+ that.doc.execCommand("enableInlineTableEditing", false, "false");
+ }, 0);
+ }
+ }
+ this.tableSelection = wysihtml5.quirks.tableCellsSelection(element, that.parent);
+ }
+
+ // --------- Focus & blur logic ---------
+ dom.observe(focusBlurElement, "focus", function(event) {
+ that.parent.fire("focus", event).fire("focus:composer", event);
+
+ // Delay storing of state until all focus handler are fired
+ // especially the one which resets the placeholder
+ setTimeout(function() { state = that.getValue(false, false); }, 0);
+ });
+
+ dom.observe(focusBlurElement, "blur", function(event) {
+ if (state !== that.getValue(false, false)) {
+ //create change event if supported (all except IE8)
+ var changeevent = event;
+ if(typeof Object.create == 'function') {
+ changeevent = Object.create(event, { type: { value: 'change' } });
+ }
+ that.parent.fire("change", changeevent).fire("change:composer", changeevent);
+ }
+ that.parent.fire("blur", event).fire("blur:composer", event);
+ });
+
+ // --------- Drag & Drop logic ---------
+ dom.observe(element, "dragenter", function() {
+ that.parent.fire("unset_placeholder");
+ });
+
+ dom.observe(element, pasteEvents, function(event) {
+ that.parent.fire(event.type, event).fire(event.type + ":composer", event);
+ });
+
+
+ if (this.config.copyedFromMarking) {
+ // If supported the copied source is based directly on selection
+ // Very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection.
+ dom.observe(element, "copy", function(event) {
+ if (event.clipboardData) {
+ event.clipboardData.setData("text/html", that.config.copyedFromMarking + that.selection.getHtml());
+ event.preventDefault();
+ }
+ that.parent.fire(event.type, event).fire(event.type + ":composer", event);
+ });
+ }
+
+ // --------- neword event ---------
+ dom.observe(element, "keyup", function(event) {
+ var keyCode = event.keyCode;
+ if (keyCode === wysihtml5.SPACE_KEY || keyCode === wysihtml5.ENTER_KEY) {
+ that.parent.fire("newword:composer");
+ }
+ });
+
+ this.parent.on("paste:composer", function() {
+ setTimeout(function() { that.parent.fire("newword:composer"); }, 0);
+ });
+
+ // --------- Make sure that images are selected when clicking on them ---------
+ if (!browser.canSelectImagesInContentEditable()) {
+ dom.observe(element, "mousedown", function(event) {
+ var target = event.target;
+ var allImages = element.querySelectorAll('img'),
+ notMyImages = element.querySelectorAll('.' + that.config.uneditableContainerClassname + ' img'),
+ myImages = wysihtml5.lang.array(allImages).without(notMyImages);
+
+ if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) {
+ that.selection.selectNode(target);
+ }
+ });
+ }
+
+ if (!browser.canSelectImagesInContentEditable()) {
+ dom.observe(element, "drop", function(event) {
+ // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
+ setTimeout(function() {
+ that.selection.getSelection().removeAllRanges();
+ }, 0);
+ });
+ }
+
+ if (browser.hasHistoryIssue() && browser.supportsSelectionModify()) {
+ dom.observe(element, "keydown", function(event) {
+ if (!event.metaKey && !event.ctrlKey) {
+ return;
+ }
+
+ var keyCode = event.keyCode,
+ win = element.ownerDocument.defaultView,
+ selection = win.getSelection();
+
+ if (keyCode === 37 || keyCode === 39) {
+ if (keyCode === 37) {
+ selection.modify("extend", "left", "lineboundary");
+ if (!event.shiftKey) {
+ selection.collapseToStart();
+ }
+ }
+ if (keyCode === 39) {
+ selection.modify("extend", "right", "lineboundary");
+ if (!event.shiftKey) {
+ selection.collapseToEnd();
+ }
+ }
+ event.preventDefault();
+ }
+ });
+ }
+
+ // --------- Shortcut logic ---------
+ dom.observe(element, "keydown", function(event) {
+ var keyCode = event.keyCode,
+ command = shortcuts[keyCode];
+ if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
+ that.commands.exec(command);
+ event.preventDefault();
+ }
+ if (keyCode === 8) {
+ // delete key
+ handleDeleteKeyPress(event, that.selection, element, that);
+ } else if (that.config.handleTabKey && keyCode === 9) {
+ event.preventDefault();
+ handleTabKeyDown(that, element);
+ }
+ });
+
+ // --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
+ dom.observe(element, "keydown", function(event) {
+ var target = that.selection.getSelectedNode(true),
+ keyCode = event.keyCode,
+ parent;
+ if (target && target.nodeName === "IMG" && (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY)) { // 8 => backspace, 46 => delete
+ parent = target.parentNode;
+ // delete the <img>
+ parent.removeChild(target);
+ // and it's parent <a> too if it hasn't got any other child nodes
+ if (parent.nodeName === "A" && !parent.firstChild) {
+ parent.parentNode.removeChild(parent);
+ }
+
+ setTimeout(function() { wysihtml5.quirks.redraw(element); }, 0);
+ event.preventDefault();
+ }
+ });
+
+ // --------- IE 8+9 focus the editor when the iframe is clicked (without actually firing the 'focus' event on the <body>) ---------
+ if (!this.config.contentEditableMode && browser.hasIframeFocusIssue()) {
+ dom.observe(container, "focus", function() {
+ setTimeout(function() {
+ if (that.doc.querySelector(":focus") !== that.element) {
+ that.focus();
+ }
+ }, 0);
+ });
+
+ dom.observe(this.element, "blur", function() {
+ setTimeout(function() {
+ that.selection.getSelection().removeAllRanges();
+ }, 0);
+ });
+ }
+
+ // --------- Show url in tooltip when hovering links or images ---------
+ var titlePrefixes = {
+ IMG: "Image: ",
+ A: "Link: "
+ };
+
+ dom.observe(element, "mouseover", function(event) {
+ var target = event.target,
+ nodeName = target.nodeName,
+ title;
+ if (nodeName !== "A" && nodeName !== "IMG") {
+ return;
+ }
+ var hasTitle = target.hasAttribute("title");
+ if(!hasTitle){
+ title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
+ target.setAttribute("title", title);
+ }
+ });
+ };
+})(wysihtml5);
+;/**
+ * Class that takes care that the value of the composer and the textarea is always in sync
+ */
+(function(wysihtml5) {
+ var INTERVAL = 400;
+
+ wysihtml5.views.Synchronizer = Base.extend(
+ /** @scope wysihtml5.views.Synchronizer.prototype */ {
+
+ constructor: function(editor, textarea, composer) {
+ this.editor = editor;
+ this.textarea = textarea;
+ this.composer = composer;
+
+ this._observe();
+ },
+
+ /**
+ * Sync html from composer to textarea
+ * Takes care of placeholders
+ * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea
+ */
+ fromComposerToTextarea: function(shouldParseHtml) {
+ this.textarea.setValue(wysihtml5.lang.string(this.composer.getValue(false, false)).trim(), shouldParseHtml);
+ },
+
+ /**
+ * Sync value of textarea to composer
+ * Takes care of placeholders
+ * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer
+ */
+ fromTextareaToComposer: function(shouldParseHtml) {
+ var textareaValue = this.textarea.getValue(false, false);
+ if (textareaValue) {
+ this.composer.setValue(textareaValue, shouldParseHtml);
+ } else {
+ this.composer.clear();
+ this.editor.fire("set_placeholder");
+ }
+ },
+
+ /**
+ * Invoke syncing based on view state
+ * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea
+ */
+ sync: function(shouldParseHtml) {
+ if (this.editor.currentView.name === "textarea") {
+ this.fromTextareaToComposer(shouldParseHtml);
+ } else {
+ this.fromComposerToTextarea(shouldParseHtml);
+ }
+ },
+
+ /**
+ * Initializes interval-based syncing
+ * also makes sure that on-submit the composer's content is synced with the textarea
+ * immediately when the form gets submitted
+ */
+ _observe: function() {
+ var interval,
+ that = this,
+ form = this.textarea.element.form,
+ startInterval = function() {
+ interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL);
+ },
+ stopInterval = function() {
+ clearInterval(interval);
+ interval = null;
+ };
+
+ startInterval();
+
+ if (form) {
+ // If the textarea is in a form make sure that after onreset and onsubmit the composer
+ // has the correct state
+ wysihtml5.dom.observe(form, "submit", function() {
+ that.sync(true);
+ });
+ wysihtml5.dom.observe(form, "reset", function() {
+ setTimeout(function() { that.fromTextareaToComposer(); }, 0);
+ });
+ }
+
+ this.editor.on("change_view", function(view) {
+ if (view === "composer" && !interval) {
+ that.fromTextareaToComposer(true);
+ startInterval();
+ } else if (view === "textarea") {
+ that.fromComposerToTextarea(true);
+ stopInterval();
+ }
+ });
+
+ this.editor.on("destroy:composer", stopInterval);
+ }
+ });
+})(wysihtml5);
+;wysihtml5.views.Textarea = wysihtml5.views.View.extend(
+ /** @scope wysihtml5.views.Textarea.prototype */ {
+ name: "textarea",
+
+ constructor: function(parent, textareaElement, config) {
+ this.base(parent, textareaElement, config);
+
+ this._observe();
+ },
+
+ clear: function() {
+ this.element.value = "";
+ },
+
+ getValue: function(parse) {
+ var value = this.isEmpty() ? "" : this.element.value;
+ if (parse !== false) {
+ value = this.parent.parse(value);
+ }
+ return value;
+ },
+
+ setValue: function(html, parse) {
+ if (parse) {
+ html = this.parent.parse(html);
+ }
+ this.element.value = html;
+ },
+
+ cleanUp: function() {
+ var html = this.parent.parse(this.element.value);
+ this.element.value = html;
+ },
+
+ hasPlaceholderSet: function() {
+ var supportsPlaceholder = wysihtml5.browser.supportsPlaceholderAttributeOn(this.element),
+ placeholderText = this.element.getAttribute("placeholder") || null,
+ value = this.element.value,
+ isEmpty = !value;
+ return (supportsPlaceholder && isEmpty) || (value === placeholderText);
+ },
+
+ isEmpty: function() {
+ return !wysihtml5.lang.string(this.element.value).trim() || this.hasPlaceholderSet();
+ },
+
+ _observe: function() {
+ var element = this.element,
+ parent = this.parent,
+ eventMapping = {
+ focusin: "focus",
+ focusout: "blur"
+ },
+ /**
+ * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events
+ * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai
+ */
+ events = wysihtml5.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"];
+
+ parent.on("beforeload", function() {
+ wysihtml5.dom.observe(element, events, function(event) {
+ var eventName = eventMapping[event.type] || event.type;
+ parent.fire(eventName).fire(eventName + ":textarea");
+ });
+
+ wysihtml5.dom.observe(element, ["paste", "drop"], function() {
+ setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0);
+ });
+ });
+ }
+});
+;/**
+ * WYSIHTML5 Editor
+ *
+ * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
+ * @param {Object} [config] See defaultConfig object below for explanation of each individual config option
+ *
+ * @events
+ * load
+ * beforeload (for internal use only)
+ * focus
+ * focus:composer
+ * focus:textarea
+ * blur
+ * blur:composer
+ * blur:textarea
+ * change
+ * change:composer
+ * change:textarea
+ * paste
+ * paste:composer
+ * paste:textarea
+ * newword:composer
+ * destroy:composer
+ * undo:composer
+ * redo:composer
+ * beforecommand:composer
+ * aftercommand:composer
+ * enable:composer
+ * disable:composer
+ * change_view
+ */
+(function(wysihtml5) {
+ var undef;
+
+ var defaultConfig = {
+ // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body
+ name: undef,
+ // Whether the editor should look like the textarea (by adopting styles)
+ style: true,
+ // Id of the toolbar element, pass falsey value if you don't want any toolbar logic
+ toolbar: undef,
+ // Whether toolbar is displayed after init by script automatically.
+ // Can be set to false if toolobar is set to display only on editable area focus
+ showToolbarAfterInit: true,
+ // Whether urls, entered by the user should automatically become clickable-links
+ autoLink: true,
+ // Includes table editing events and cell selection tracking
+ handleTables: true,
+ // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
+ handleTabKey: true,
+ // Object which includes parser rules to apply when html gets cleaned
+ // See parser_rules/*.js for examples
+ parserRules: { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
+ // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead
+ pasteParserRulesets: null,
+ // Parser method to use when the user inserts content
+ parser: wysihtml5.dom.parse,
+ // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
+ composerClassName: "wysihtml5-editor",
+ // Class name to add to the body when the wysihtml5 editor is supported
+ bodyClassName: "wysihtml5-supported",
+ // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
+ 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 rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5)
+ supportTouchDevices: true,
+ // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content
+ cleanUp: true,
+ // Whether to use div instead of secure iframe
+ contentEditableMode: false,
+ // Classname of container that editor should not touch and pass through
+ // Pass false to disable
+ uneditableContainerClassname: "wysihtml5-uneditable-container",
+ // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste)
+ // Also copied source is based directly on selection -
+ // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection).
+ // If falsy value is passed source override is also disabled
+ copyedFromMarking: '<meta name="copied-from" content="wysihtml5">'
+ };
+
+ wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend(
+ /** @scope wysihtml5.Editor.prototype */ {
+ constructor: function(editableElement, config) {
+ this.editableElement = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
+ this.config = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
+ this._isCompatible = wysihtml5.browser.supported();
+
+ if (this.editableElement.nodeName.toLowerCase() != "textarea") {
+ this.config.contentEditableMode = true;
+ this.config.noTextarea = true;
+ }
+ if (!this.config.noTextarea) {
+ this.textarea = new wysihtml5.views.Textarea(this, this.editableElement, this.config);
+ this.currentView = this.textarea;
+ }
+
+ // Sort out unsupported/unwanted browsers here
+ if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml5.browser.isTouchDevice())) {
+ var that = this;
+ setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
+ return;
+ }
+
+ // Add class name to body, to indicate that the editor is supported
+ wysihtml5.dom.addClass(document.body, this.config.bodyClassName);
+
+ this.composer = new wysihtml5.views.Composer(this, this.editableElement, this.config);
+ this.currentView = this.composer;
+
+ if (typeof(this.config.parser) === "function") {
+ this._initParser();
+ }
+
+ this.on("beforeload", this.handleBeforeLoad);
+ },
+
+ handleBeforeLoad: function() {
+ if (!this.config.noTextarea) {
+ 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, this.config.showToolbarAfterInit);
+ }
+ },
+
+ isCompatible: function() {
+ return this._isCompatible;
+ },
+
+ clear: function() {
+ this.currentView.clear();
+ return this;
+ },
+
+ getValue: function(parse, clearInternals) {
+ return this.currentView.getValue(parse, clearInternals);
+ },
+
+ setValue: function(html, parse) {
+ this.fire("unset_placeholder");
+
+ if (!html) {
+ return this.clear();
+ }
+
+ this.currentView.setValue(html, parse);
+ return this;
+ },
+
+ cleanUp: function() {
+ this.currentView.cleanUp();
+ },
+
+ focus: function(setToEnd) {
+ this.currentView.focus(setToEnd);
+ return this;
+ },
+
+ /**
+ * Deactivate editor (make it readonly)
+ */
+ disable: function() {
+ this.currentView.disable();
+ return this;
+ },
+
+ /**
+ * Activate editor
+ */
+ enable: function() {
+ this.currentView.enable();
+ return this;
+ },
+
+ isEmpty: function() {
+ return this.currentView.isEmpty();
+ },
+
+ hasPlaceholderSet: function() {
+ return this.currentView.hasPlaceholderSet();
+ },
+
+ parse: function(htmlOrElement, clearInternals) {
+ var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
+ var returnValue = this.config.parser(htmlOrElement, {
+ "rules": this.config.parserRules,
+ "cleanUp": this.config.cleanUp,
+ "context": parseContext,
+ "uneditableClass": this.config.uneditableContainerClassname,
+ "clearInternals" : clearInternals
+ });
+ if (typeof(htmlOrElement) === "object") {
+ wysihtml5.quirks.redraw(htmlOrElement);
+ }
+ return returnValue;
+ },
+
+ /**
+ * Prepare html parser logic
+ * - Observes for paste and drop
+ */
+ _initParser: function() {
+ var that = this,
+ oldHtml,
+ cleanHtml;
+
+ if (wysihtml5.browser.supportsModenPaste()) {
+ this.on("paste:composer", function(event) {
+ event.preventDefault();
+ oldHtml = wysihtml5.dom.getPastedHtml(event);
+ if (oldHtml) {
+ that._cleanAndPaste(oldHtml);
+ }
+ });
+
+ } else {
+ this.on("beforepaste:composer", function(event) {
+ event.preventDefault();
+ wysihtml5.dom.getPastedHtmlWithDiv(that.composer, function(pastedHTML) {
+ if (pastedHTML) {
+ that._cleanAndPaste(pastedHTML);
+ }
+ });
+ });
+
+ }
+ },
+
+ _cleanAndPaste: function (oldHtml) {
+ var cleanHtml = wysihtml5.quirks.cleanPastedHTML(oldHtml, {
+ "referenceNode": this.composer.element,
+ "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
+ "uneditableClass": this.config.uneditableContainerClassname
+ });
+ this.composer.selection.deleteContents();
+ this.composer.selection.insertHTML(cleanHtml);
+ }
+ });
+})(wysihtml5);
+;/**
+ * Toolbar Dialog
+ *
+ * @param {Element} link The toolbar link which causes the dialog to show up
+ * @param {Element} container The dialog container
+ *
+ * @example
+ * <!-- Toolbar link -->
+ * <a data-wysihtml5-command="insertImage">insert an image</a>
+ *
+ * <!-- Dialog -->
+ * <div data-wysihtml5-dialog="insertImage" style="display: none;">
+ * <label>
+ * URL: <input data-wysihtml5-dialog-field="src" value="http://">
+ * </label>
+ * <label>
+ * Alternative text: <input data-wysihtml5-dialog-field="alt" value="">
+ * </label>
+ * </div>
+ *
+ * <script>
+ * var dialog = new wysihtml5.toolbar.Dialog(
+ * document.querySelector("[data-wysihtml5-command='insertImage']"),
+ * document.querySelector("[data-wysihtml5-dialog='insertImage']")
+ * );
+ * dialog.observe("save", function(attributes) {
+ * // do something
+ * });
+ * </script>
+ */
+(function(wysihtml5) {
+ var dom = wysihtml5.dom,
+ CLASS_NAME_OPENED = "wysihtml5-command-dialog-opened",
+ SELECTOR_FORM_ELEMENTS = "input, select, textarea",
+ SELECTOR_FIELDS = "[data-wysihtml5-dialog-field]",
+ ATTRIBUTE_FIELDS = "data-wysihtml5-dialog-field";
+
+
+ wysihtml5.toolbar.Dialog = wysihtml5.lang.Dispatcher.extend(
+ /** @scope wysihtml5.toolbar.Dialog.prototype */ {
+ constructor: function(link, container) {
+ this.link = link;
+ this.container = container;
+ },
+
+ _observe: function() {
+ if (this._observed) {
+ return;
+ }
+
+ var that = this,
+ callbackWrapper = function(event) {
+ var attributes = that._serialize();
+ if (attributes == that.elementToChange) {
+ that.fire("edit", attributes);
+ } else {
+ that.fire("save", attributes);
+ }
+ that.hide();
+ event.preventDefault();
+ event.stopPropagation();
+ };
+
+ dom.observe(that.link, "click", function() {
+ if (dom.hasClass(that.link, CLASS_NAME_OPENED)) {
+ setTimeout(function() { that.hide(); }, 0);
+ }
+ });
+
+ dom.observe(this.container, "keydown", function(event) {
+ var keyCode = event.keyCode;
+ if (keyCode === wysihtml5.ENTER_KEY) {
+ callbackWrapper(event);
+ }
+ if (keyCode === wysihtml5.ESCAPE_KEY) {
+ that.fire("cancel");
+ that.hide();
+ }
+ });
+
+ dom.delegate(this.container, "[data-wysihtml5-dialog-action=save]", "click", callbackWrapper);
+
+ dom.delegate(this.container, "[data-wysihtml5-dialog-action=cancel]", "click", function(event) {
+ that.fire("cancel");
+ that.hide();
+ event.preventDefault();
+ event.stopPropagation();
+ });
+
+ var formElements = this.container.querySelectorAll(SELECTOR_FORM_ELEMENTS),
+ i = 0,
+ length = formElements.length,
+ _clearInterval = function() { clearInterval(that.interval); };
+ for (; i<length; i++) {
+ dom.observe(formElements[i], "change", _clearInterval);
+ }
+
+ this._observed = true;
+ },
+
+ /**
+ * Grabs all fields in the dialog and puts them in key=>value style in an object which
+ * then gets returned
+ */
+ _serialize: function() {
+ var data = this.elementToChange || {},
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
+ length = fields.length,
+ i = 0;
+
+ for (; i<length; i++) {
+ data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
+ }
+ return data;
+ },
+
+ /**
+ * Takes the attributes of the "elementToChange"
+ * and inserts them in their corresponding dialog input fields
+ *
+ * Assume the "elementToChange" looks like this:
+ * <a href="http://www.google.com" target="_blank">foo</a>
+ *
+ * and we have the following dialog:
+ * <input type="text" data-wysihtml5-dialog-field="href" value="">
+ * <input type="text" data-wysihtml5-dialog-field="target" value="">
+ *
+ * after calling _interpolate() the dialog will look like this
+ * <input type="text" data-wysihtml5-dialog-field="href" value="http://www.google.com">
+ * <input type="text" data-wysihtml5-dialog-field="target" value="_blank">
+ *
+ * Basically it adopted the attribute values into the corresponding input fields
+ *
+ */
+ _interpolate: function(avoidHiddenFields) {
+ var field,
+ fieldName,
+ newValue,
+ focusedElement = document.querySelector(":focus"),
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
+ length = fields.length,
+ i = 0;
+ for (; i<length; i++) {
+ field = fields[i];
+
+ // Never change elements where the user is currently typing in
+ if (field === focusedElement) {
+ continue;
+ }
+
+ // Don't update hidden fields
+ // See https://github.com/xing/wysihtml5/pull/14
+ if (avoidHiddenFields && field.type === "hidden") {
+ continue;
+ }
+
+ fieldName = field.getAttribute(ATTRIBUTE_FIELDS);
+ newValue = (this.elementToChange && typeof(this.elementToChange) !== 'boolean') ? (this.elementToChange.getAttribute(fieldName) || "") : field.defaultValue;
+ field.value = newValue;
+ }
+ },
+
+ /**
+ * Show the dialog element
+ */
+ show: function(elementToChange) {
+ if (dom.hasClass(this.link, CLASS_NAME_OPENED)) {
+ return;
+ }
+
+ var that = this,
+ firstField = this.container.querySelector(SELECTOR_FORM_ELEMENTS);
+ this.elementToChange = elementToChange;
+ this._observe();
+ this._interpolate();
+ if (elementToChange) {
+ this.interval = setInterval(function() { that._interpolate(true); }, 500);
+ }
+ dom.addClass(this.link, CLASS_NAME_OPENED);
+ this.container.style.display = "";
+ this.fire("show");
+ if (firstField && !elementToChange) {
+ try {
+ firstField.focus();
+ } catch(e) {}
+ }
+ },
+
+ /**
+ * Hide the dialog element
+ */
+ hide: function() {
+ clearInterval(this.interval);
+ this.elementToChange = null;
+ dom.removeClass(this.link, CLASS_NAME_OPENED);
+ this.container.style.display = "none";
+ this.fire("hide");
+ }
+ });
+})(wysihtml5);
+;/**
+ * Converts speech-to-text and inserts this into the editor
+ * As of now (2011/03/25) this only is supported in Chrome >= 11
+ *
+ * Note that it sends the recorded audio to the google speech recognition api:
+ * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec
+ *
+ * Current HTML5 draft can be found here
+ * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html
+ *
+ * "Accessing Google Speech API Chrome 11"
+ * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
+ */
+(function(wysihtml5) {
+ var dom = wysihtml5.dom;
+
+ var linkStyles = {
+ position: "relative"
+ };
+
+ var wrapperStyles = {
+ left: 0,
+ margin: 0,
+ opacity: 0,
+ overflow: "hidden",
+ padding: 0,
+ position: "absolute",
+ top: 0,
+ zIndex: 1
+ };
+
+ var inputStyles = {
+ cursor: "inherit",
+ fontSize: "50px",
+ height: "50px",
+ marginTop: "-25px",
+ outline: 0,
+ padding: 0,
+ position: "absolute",
+ right: "-4px",
+ top: "50%"
+ };
+
+ var inputAttributes = {
+ "x-webkit-speech": "",
+ "speech": ""
+ };
+
+ wysihtml5.toolbar.Speech = function(parent, link) {
+ var input = document.createElement("input");
+ if (!wysihtml5.browser.supportsSpeechApiOn(input)) {
+ link.style.display = "none";
+ return;
+ }
+ var lang = parent.editor.textarea.element.getAttribute("lang");
+ if (lang) {
+ inputAttributes.lang = lang;
+ }
+
+ var wrapper = document.createElement("div");
+
+ wysihtml5.lang.object(wrapperStyles).merge({
+ width: link.offsetWidth + "px",
+ height: link.offsetHeight + "px"
+ });
+
+ dom.insert(input).into(wrapper);
+ dom.insert(wrapper).into(link);
+
+ dom.setStyles(inputStyles).on(input);
+ dom.setAttributes(inputAttributes).on(input);
+
+ dom.setStyles(wrapperStyles).on(wrapper);
+ dom.setStyles(linkStyles).on(link);
+
+ var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange";
+ dom.observe(input, eventName, function() {
+ parent.execCommand("insertText", input.value);
+ input.value = "";
+ });
+
+ dom.observe(input, "click", function(event) {
+ if (dom.hasClass(link, "wysihtml5-command-disabled")) {
+ event.preventDefault();
+ }
+
+ event.stopPropagation();
+ });
+ };
+})(wysihtml5);
+;/**
+ * Toolbar
+ *
+ * @param {Object} parent Reference to instance of Editor instance
+ * @param {Element} container Reference to the toolbar container element
+ *
+ * @example
+ * <div id="toolbar">
+ * <a data-wysihtml5-command="createLink">insert link</a>
+ * <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">insert h1</a>
+ * </div>
+ *
+ * <script>
+ * var toolbar = new wysihtml5.toolbar.Toolbar(editor, document.getElementById("toolbar"));
+ * </script>
+ */
+(function(wysihtml5) {
+ var CLASS_NAME_COMMAND_DISABLED = "wysihtml5-command-disabled",
+ CLASS_NAME_COMMANDS_DISABLED = "wysihtml5-commands-disabled",
+ CLASS_NAME_COMMAND_ACTIVE = "wysihtml5-command-active",
+ CLASS_NAME_ACTION_ACTIVE = "wysihtml5-action-active",
+ dom = wysihtml5.dom;
+
+ wysihtml5.toolbar.Toolbar = Base.extend(
+ /** @scope wysihtml5.toolbar.Toolbar.prototype */ {
+ constructor: function(editor, container, showOnInit) {
+ this.editor = editor;
+ this.container = typeof(container) === "string" ? document.getElementById(container) : container;
+ this.composer = editor.composer;
+
+ this._getLinks("command");
+ this._getLinks("action");
+
+ this._observe();
+ if (showOnInit) { this.show(); }
+
+ if (editor.config.classNameCommandDisabled != null) {
+ CLASS_NAME_COMMAND_DISABLED = editor.config.classNameCommandDisabled;
+ }
+ if (editor.config.classNameCommandsDisabled != null) {
+ CLASS_NAME_COMMANDS_DISABLED = editor.config.classNameCommandsDisabled;
+ }
+ if (editor.config.classNameCommandActive != null) {
+ CLASS_NAME_COMMAND_ACTIVE = editor.config.classNameCommandActive;
+ }
+ if (editor.config.classNameActionActive != null) {
+ CLASS_NAME_ACTION_ACTIVE = editor.config.classNameActionActive;
+ }
+
+ var speechInputLinks = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),
+ length = speechInputLinks.length,
+ i = 0;
+ for (; i<length; i++) {
+ new wysihtml5.toolbar.Speech(this, speechInputLinks[i]);
+ }
+ },
+
+ _getLinks: function(type) {
+ var links = this[type + "Links"] = wysihtml5.lang.array(this.container.querySelectorAll("[data-wysihtml5-" + type + "]")).get(),
+ length = links.length,
+ i = 0,
+ mapping = this[type + "Mapping"] = {},
+ link,
+ group,
+ name,
+ value,
+ dialog;
+ for (; i<length; i++) {
+ link = links[i];
+ name = link.getAttribute("data-wysihtml5-" + type);
+ value = link.getAttribute("data-wysihtml5-" + type + "-value");
+ group = this.container.querySelector("[data-wysihtml5-" + type + "-group='" + name + "']");
+ dialog = this._getDialog(link, name);
+
+ mapping[name + ":" + value] = {
+ link: link,
+ group: group,
+ name: name,
+ value: value,
+ dialog: dialog,
+ state: false
+ };
+ }
+ },
+
+ _getDialog: function(link, command) {
+ var that = this,
+ dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
+ dialog,
+ caretBookmark;
+
+ if (dialogElement) {
+ if (wysihtml5.toolbar["Dialog_" + command]) {
+ dialog = new wysihtml5.toolbar["Dialog_" + command](link, dialogElement);
+ } else {
+ dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
+ }
+
+ dialog.on("show", function() {
+ caretBookmark = that.composer.selection.getBookmark();
+
+ that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
+ });
+
+ dialog.on("save", function(attributes) {
+ if (caretBookmark) {
+ that.composer.selection.setBookmark(caretBookmark);
+ }
+ that._execCommand(command, attributes);
+
+ that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
+ });
+
+ dialog.on("cancel", function() {
+ that.editor.focus(false);
+ that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
+ });
+ }
+ return dialog;
+ },
+
+ /**
+ * @example
+ * var toolbar = new wysihtml5.Toolbar();
+ * // Insert a <blockquote> element or wrap current selection in <blockquote>
+ * toolbar.execCommand("formatBlock", "blockquote");
+ */
+ execCommand: function(command, commandValue) {
+ if (this.commandsDisabled) {
+ return;
+ }
+
+ var commandObj = this.commandMapping[command + ":" + commandValue];
+
+ // Show dialog when available
+ if (commandObj && commandObj.dialog && !commandObj.state) {
+ commandObj.dialog.show();
+ } else {
+ this._execCommand(command, commandValue);
+ }
+ },
+
+ _execCommand: function(command, commandValue) {
+ // Make sure that composer is focussed (false => don't move caret to the end)
+ this.editor.focus(false);
+
+ this.composer.commands.exec(command, commandValue);
+ this._updateLinkStates();
+ },
+
+ execAction: function(action) {
+ var editor = this.editor;
+ if (action === "change_view") {
+ if (editor.textarea) {
+ if (editor.currentView === editor.textarea) {
+ editor.fire("change_view", "composer");
+ } else {
+ editor.fire("change_view", "textarea");
+ }
+ }
+ }
+ if (action == "showSource") {
+ editor.fire("showSource");
+ }
+ },
+
+ _observe: function() {
+ var that = this,
+ editor = this.editor,
+ container = this.container,
+ links = this.commandLinks.concat(this.actionLinks),
+ length = links.length,
+ i = 0;
+
+ for (; i<length; i++) {
+ // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied
+ // (you know, a:link { ... } doesn't match anchors with missing href attribute)
+ if (links[i].nodeName === "A") {
+ dom.setAttributes({
+ href: "javascript:;",
+ unselectable: "on"
+ }).on(links[i]);
+ } else {
+ dom.setAttributes({ unselectable: "on" }).on(links[i]);
+ }
+ }
+
+ // Needed for opera and chrome
+ dom.delegate(container, "[data-wysihtml5-command], [data-wysihtml5-action]", "mousedown", function(event) { event.preventDefault(); });
+
+ dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) {
+ var link = this,
+ command = link.getAttribute("data-wysihtml5-command"),
+ commandValue = link.getAttribute("data-wysihtml5-command-value");
+ that.execCommand(command, commandValue);
+ event.preventDefault();
+ });
+
+ dom.delegate(container, "[data-wysihtml5-action]", "click", function(event) {
+ var action = this.getAttribute("data-wysihtml5-action");
+ that.execAction(action);
+ event.preventDefault();
+ });
+
+ editor.on("interaction:composer", function() {
+ that._updateLinkStates();
+ });
+
+ editor.on("focus:composer", function() {
+ that.bookmark = null;
+ });
+
+ if (this.editor.config.handleTables) {
+ editor.on("tableselect:composer", function() {
+ that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "";
+ });
+ editor.on("tableunselect:composer", function() {
+ that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "none";
+ });
+ }
+
+ editor.on("change_view", function(currentView) {
+ // Set timeout needed in order to let the blur event fire first
+ if (editor.textarea) {
+ setTimeout(function() {
+ that.commandsDisabled = (currentView !== "composer");
+ that._updateLinkStates();
+ if (that.commandsDisabled) {
+ dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED);
+ } else {
+ dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED);
+ }
+ }, 0);
+ }
+ });
+ },
+
+ _updateLinkStates: function() {
+
+ var commandMapping = this.commandMapping,
+ actionMapping = this.actionMapping,
+ i,
+ state,
+ action,
+ command;
+ // every millisecond counts... this is executed quite often
+ for (i in commandMapping) {
+ command = commandMapping[i];
+ if (this.commandsDisabled) {
+ state = false;
+ dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
+ if (command.group) {
+ dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
+ }
+ if (command.dialog) {
+ command.dialog.hide();
+ }
+ } else {
+ state = this.composer.commands.state(command.name, command.value);
+ dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED);
+ if (command.group) {
+ dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED);
+ }
+ }
+ if (command.state === state) {
+ continue;
+ }
+
+ command.state = state;
+ if (state) {
+ dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
+ if (command.group) {
+ dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
+ }
+ if (command.dialog) {
+ if (typeof(state) === "object" || wysihtml5.lang.object(state).isArray()) {
+
+ if (!command.dialog.multiselect && wysihtml5.lang.object(state).isArray()) {
+ // Grab first and only object/element in state array, otherwise convert state into boolean
+ // to avoid showing a dialog for multiple selected elements which may have different attributes
+ // eg. when two links with different href are selected, the state will be an array consisting of both link elements
+ // but the dialog interface can only update one
+ state = state.length === 1 ? state[0] : true;
+ command.state = state;
+ }
+ command.dialog.show(state);
+ } else {
+ command.dialog.hide();
+ }
+ }
+ } else {
+ dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
+ if (command.group) {
+ dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
+ }
+ if (command.dialog) {
+ command.dialog.hide();
+ }
+ }
+ }
+
+ for (i in actionMapping) {
+ action = actionMapping[i];
+
+ if (action.name === "change_view") {
+ action.state = this.editor.currentView === this.editor.textarea;
+ if (action.state) {
+ dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE);
+ } else {
+ dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE);
+ }
+ }
+ }
+ },
+
+ show: function() {
+ this.container.style.display = "";
+ },
+
+ hide: function() {
+ this.container.style.display = "none";
+ }
+ });
+
+})(wysihtml5);
+;(function(wysihtml5) {
+ wysihtml5.toolbar.Dialog_createTable = wysihtml5.toolbar.Dialog.extend({
+ show: function(elementToChange) {
+ this.base(elementToChange);
+
+ }
+
+ });
+})(wysihtml5);
+;(function(wysihtml5) {
+ var dom = wysihtml5.dom,
+ SELECTOR_FIELDS = "[data-wysihtml5-dialog-field]",
+ ATTRIBUTE_FIELDS = "data-wysihtml5-dialog-field";
+
+ wysihtml5.toolbar.Dialog_foreColorStyle = wysihtml5.toolbar.Dialog.extend({
+ multiselect: true,
+
+ _serialize: function() {
+ var data = {},
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
+ length = fields.length,
+ i = 0;
+
+ for (; i<length; i++) {
+ data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
+ }
+ return data;
+ },
+
+ _interpolate: function(avoidHiddenFields) {
+ var field,
+ fieldName,
+ newValue,
+ focusedElement = document.querySelector(":focus"),
+ fields = this.container.querySelectorAll(SELECTOR_FIELDS),
+ length = fields.length,
+ i = 0,
+ firstElement = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
+ colorStr = (firstElement) ? firstElement.getAttribute('style') : null,
+ color = (colorStr) ? wysihtml5.quirks.styleParser.parseColor(colorStr, "color") : null;
+
+ for (; i<length; i++) {
+ field = fields[i];
+ // Never change elements where the user is currently typing in
+ if (field === focusedElement) {
+ continue;
+ }
+ // Don't update hidden fields3
+ if (avoidHiddenFields && field.type === "hidden") {
+ continue;
+ }
+ if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") {
+ if (color) {
+ if (color[3] && color[3] != 1) {
+ field.value = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ");";
+ } else {
+ field.value = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ");";
+ }
+ } else {
+ field.value = "rgb(0,0,0);";
+ }
+ }
+ }
+ }
+
+ });
+})(wysihtml5);
+;(function(wysihtml5) {
+ var dom = wysihtml5.dom,
+ SELECTOR_FIELDS = "[data-wysihtml5-dialog-field]",
+ ATTRIBUTE_FIELDS = "data-wysihtml5-dialog-field";
+
+ wysihtml5.toolbar.Dialog_fontSizeStyle = wysihtml5.toolbar.Dialog.extend({
+ multiselect: true,
+
+ _serialize: function() {
+ return {"size" : this.container.querySelector('[data-wysihtml5-dialog-field="size"]').value};
+ },
+
+ _interpolate: function(avoidHiddenFields) {
+ var focusedElement = document.querySelector(":focus"),
+ field = this.container.querySelector("[data-wysihtml5-dialog-field='size']"),
+ firstElement = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
+ styleStr = (firstElement) ? firstElement.getAttribute('style') : null,
+ size = (styleStr) ? wysihtml5.quirks.styleParser.parseFontSize(styleStr) : null;
+
+ if (field && field !== focusedElement && size && !(/^\s*$/).test(size)) {
+ field.value = size;
+ }
+ }
+
+ });
+})(wysihtml5);
+/*!
+
+ handlebars v1.3.0
+
+Copyright (C) 2011 by Yehuda Katz
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+@license
+*/
+var Handlebars=function(){var a=function(){"use strict";function a(a){this.string=a}var b;return a.prototype.toString=function(){return""+this.string},b=a}(),b=function(a){"use strict";function b(a){return h[a]||"&amp;"}function c(a,b){for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&(a[c]=b[c])}function d(a){return a instanceof g?a.toString():a||0===a?(a=""+a,j.test(a)?a.replace(i,b):a):""}function e(a){return a||0===a?m(a)&&0===a.length?!0:!1:!0}var f={},g=a,h={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},i=/[&<>"'`]/g,j=/[&<>"'`]/;f.extend=c;var k=Object.prototype.toString;f.toString=k;var l=function(a){return"function"==typeof a};l(/x/)&&(l=function(a){return"function"==typeof a&&"[object Function]"===k.call(a)});var l;f.isFunction=l;var m=Array.isArray||function(a){return a&&"object"==typeof a?"[object Array]"===k.call(a):!1};return f.isArray=m,f.escapeExpression=d,f.isEmpty=e,f}(a),c=function(){"use strict";function a(a,b){var d;b&&b.firstLine&&(d=b.firstLine,a+=" - "+d+":"+b.firstColumn);for(var e=Error.prototype.constructor.call(this,a),f=0;f<c.length;f++)this[c[f]]=e[c[f]];d&&(this.lineNumber=d,this.column=b.firstColumn)}var b,c=["description","fileName","lineNumber","message","name","number","stack"];return a.prototype=new Error,b=a}(),d=function(a,b){"use strict";function c(a,b){this.helpers=a||{},this.partials=b||{},d(this)}function d(a){a.registerHelper("helperMissing",function(a){if(2===arguments.length)return void 0;throw new h("Missing helper: '"+a+"'")}),a.registerHelper("blockHelperMissing",function(b,c){var d=c.inverse||function(){},e=c.fn;return m(b)&&(b=b.call(this)),b===!0?e(this):b===!1||null==b?d(this):l(b)?b.length>0?a.helpers.each(b,c):d(this):e(b)}),a.registerHelper("each",function(a,b){var c,d=b.fn,e=b.inverse,f=0,g="";if(m(a)&&(a=a.call(this)),b.data&&(c=q(b.data)),a&&"object"==typeof a)if(l(a))for(var h=a.length;h>f;f++)c&&(c.index=f,c.first=0===f,c.last=f===a.length-1),g+=d(a[f],{data:c});else for(var i in a)a.hasOwnProperty(i)&&(c&&(c.key=i,c.index=f,c.first=0===f),g+=d(a[i],{data:c}),f++);return 0===f&&(g=e(this)),g}),a.registerHelper("if",function(a,b){return m(a)&&(a=a.call(this)),!b.hash.includeZero&&!a||g.isEmpty(a)?b.inverse(this):b.fn(this)}),a.registerHelper("unless",function(b,c){return a.helpers["if"].call(this,b,{fn:c.inverse,inverse:c.fn,hash:c.hash})}),a.registerHelper("with",function(a,b){return m(a)&&(a=a.call(this)),g.isEmpty(a)?void 0:b.fn(a)}),a.registerHelper("log",function(b,c){var d=c.data&&null!=c.data.level?parseInt(c.data.level,10):1;a.log(d,b)})}function e(a,b){p.log(a,b)}var f={},g=a,h=b,i="1.3.0";f.VERSION=i;var j=4;f.COMPILER_REVISION=j;var k={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:">= 1.0.0"};f.REVISION_CHANGES=k;var l=g.isArray,m=g.isFunction,n=g.toString,o="[object Object]";f.HandlebarsEnvironment=c,c.prototype={constructor:c,logger:p,log:e,registerHelper:function(a,b,c){if(n.call(a)===o){if(c||b)throw new h("Arg not supported with multiple helpers");g.extend(this.helpers,a)}else c&&(b.not=c),this.helpers[a]=b},registerPartial:function(a,b){n.call(a)===o?g.extend(this.partials,a):this.partials[a]=b}};var p={methodMap:{0:"debug",1:"info",2:"warn",3:"error"},DEBUG:0,INFO:1,WARN:2,ERROR:3,level:3,log:function(a,b){if(p.level<=a){var c=p.methodMap[a];"undefined"!=typeof console&&console[c]&&console[c].call(console,b)}}};f.logger=p,f.log=e;var q=function(a){var b={};return g.extend(b,a),b};return f.createFrame=q,f}(b,c),e=function(a,b,c){"use strict";function d(a){var b=a&&a[0]||1,c=m;if(b!==c){if(c>b){var d=n[c],e=n[b];throw new l("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version ("+d+") or downgrade your runtime to an older version ("+e+").")}throw new l("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version ("+a[1]+").")}}function e(a,b){if(!b)throw new l("No environment passed to template");var c=function(a,c,d,e,f,g){var h=b.VM.invokePartial.apply(this,arguments);if(null!=h)return h;if(b.compile){var i={helpers:e,partials:f,data:g};return f[c]=b.compile(a,{data:void 0!==g},b),f[c](d,i)}throw new l("The partial "+c+" could not be compiled when running in runtime-only mode")},d={escapeExpression:k.escapeExpression,invokePartial:c,programs:[],program:function(a,b,c){var d=this.programs[a];return c?d=g(a,b,c):d||(d=this.programs[a]=g(a,b)),d},merge:function(a,b){var c=a||b;return a&&b&&a!==b&&(c={},k.extend(c,b),k.extend(c,a)),c},programWithDepth:b.VM.programWithDepth,noop:b.VM.noop,compilerInfo:null};return function(c,e){e=e||{};var f,g,h=e.partial?e:b;e.partial||(f=e.helpers,g=e.partials);var i=a.call(d,h,c,f,g,e.data);return e.partial||b.VM.checkRevision(d.compilerInfo),i}}function f(a,b,c){var d=Array.prototype.slice.call(arguments,3),e=function(a,e){return e=e||{},b.apply(this,[a,e.data||c].concat(d))};return e.program=a,e.depth=d.length,e}function g(a,b,c){var d=function(a,d){return d=d||{},b(a,d.data||c)};return d.program=a,d.depth=0,d}function h(a,b,c,d,e,f){var g={partial:!0,helpers:d,partials:e,data:f};if(void 0===a)throw new l("The partial "+b+" could not be found");return a instanceof Function?a(c,g):void 0}function i(){return""}var j={},k=a,l=b,m=c.COMPILER_REVISION,n=c.REVISION_CHANGES;return j.checkRevision=d,j.template=e,j.programWithDepth=f,j.program=g,j.invokePartial=h,j.noop=i,j}(b,c,d),f=function(a,b,c,d,e){"use strict";var f,g=a,h=b,i=c,j=d,k=e,l=function(){var a=new g.HandlebarsEnvironment;return j.extend(a,g),a.SafeString=h,a.Exception=i,a.Utils=j,a.VM=k,a.template=function(b){return k.template(b,a)},a},m=l();return m.create=l,f=m}(d,a,c,b,e);return f}();this["wysihtml5"] = this["wysihtml5"] || {};
+this["wysihtml5"]["tpl"] = this["wysihtml5"]["tpl"] || {};
+
+this["wysihtml5"]["tpl"]["blockquote"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "btn-"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
+ return buffer;
+ }
+
+function program3(depth0,data) {
+
+
+ return " \n <span class=\"fa fa-quote-left\"></span>\n ";
+ }
+
+function program5(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-quote\"></span>\n ";
+ }
+
+ buffer += "<li>\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"blockquote\" data-wysihtml5-display-format-name=\"false\" tabindex=\"-1\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </a>\n</li>\n";
+ return buffer;
+ });
+
+this["wysihtml5"]["tpl"]["color"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "btn-"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
+ return buffer;
+ }
+
+ buffer += "<li class=\"dropdown\">\n <a class=\"btn btn-default dropdown-toggle ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\" data-toggle=\"dropdown\" tabindex=\"-1\">\n <span class=\"current-color\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.black)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</span>\n <b class=\"caret\"></b>\n </a>\n <ul class=\"dropdown-menu\">\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"black\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"black\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.black)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"silver\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"silver\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.silver)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"gray\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"gray\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.gray)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"maroon\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"maroon\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.maroon)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"red\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"red\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.red)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"purple\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"purple\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.purple)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"green\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"green\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.green)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"olive\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"olive\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.olive)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"navy\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"navy\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.navy)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"blue\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"blue\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.blue)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"orange\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"orange\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.orange)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n </ul>\n</li>\n";
+ return buffer;
+ });
+
+this["wysihtml5"]["tpl"]["emphasis"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "btn-"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
+ return buffer;
+ }
+
+function program3(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"small\" title=\"CTRL+S\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.small)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a>\n ";
+ return buffer;
+ }
+
+ buffer += "<li>\n <div class=\"btn-group\">\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"bold\" title=\"CTRL+B\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.bold)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a>\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"italic\" title=\"CTRL+I\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.italic)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a>\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"underline\" title=\"CTRL+U\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.underline)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a>\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.small), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </div>\n</li>\n";
+ return buffer;
+ });
+
+this["wysihtml5"]["tpl"]["font-styles"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "btn-"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
+ return buffer;
+ }
+
+function program3(depth0,data) {
+
+
+ return "\n <span class=\"fa fa-font\"></span>\n ";
+ }
+
+function program5(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-font\"></span>\n ";
+ }
+
+ buffer += "<li class=\"dropdown\">\n <a class=\"btn btn-default dropdown-toggle ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\" data-toggle=\"dropdown\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n <span class=\"current-font\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.normal)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</span>\n <b class=\"caret\"></b>\n </a>\n <ul class=\"dropdown-menu\">\n <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"p\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.normal)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h1\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h1)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h2\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h2)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h3\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h3)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h4\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h4)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h5\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h5)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h6\" tabindex=\"-1\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h6)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a></li>\n </ul>\n</li>\n";
+ return buffer;
+ });
+
+this["wysihtml5"]["tpl"]["html"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "btn-"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
+ return buffer;
+ }
+
+function program3(depth0,data) {
+
+
+ return "\n <span class=\"fa fa-pencil\"></span>\n ";
+ }
+
+function program5(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-pencil\"></span>\n ";
+ }
+
+ buffer += "<li>\n <div class=\"btn-group\">\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-action=\"change_view\" title=\""
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.html)),stack1 == null || stack1 === false ? stack1 : stack1.edit)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "\" tabindex=\"-1\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </a>\n </div>\n</li>\n";
+ return buffer;
+ });
+
+this["wysihtml5"]["tpl"]["image"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+
+ return "modal-sm";
+ }
+
+function program3(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "btn-"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
+ return buffer;
+ }
+
+function program5(depth0,data) {
+
+
+ return "\n <span class=\"fa fa-file-image-o\"></span>\n ";
+ }
+
+function program7(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-picture\"></span>\n ";
+ }
+
+ buffer += "<li>\n <div class=\"bootstrap-wysihtml5-insert-image-modal modal fade\" data-wysihtml5-dialog=\"insertImage\">\n <div class=\"modal-dialog ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.smallmodals), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <a class=\"close\" data-dismiss=\"modal\">&times;</a>\n <h3>"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.image)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</h3>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <input value=\"http://\" class=\"bootstrap-wysihtml5-insert-image-url form-control\" data-wysihtml5-dialog-field=\"src\">\n </div> \n </div>\n <div class=\"modal-footer\">\n <a class=\"btn btn-default\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"cancel\" href=\"#\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.image)),stack1 == null || stack1 === false ? stack1 : stack1.cancel)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a>\n <a class=\"btn btn-primary\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"save\" href=\"#\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.image)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a>\n </div>\n </div>\n </div>\n </div>\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"insertImage\" title=\""
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.image)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "\" tabindex=\"-1\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </a>\n</li>\n";
+ return buffer;
+ });
+
+this["wysihtml5"]["tpl"]["link"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+
+ return "modal-sm";
+ }
+
+function program3(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "btn-"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
+ return buffer;
+ }
+
+function program5(depth0,data) {
+
+
+ return "\n <span class=\"fa fa-share-square-o\"></span>\n ";
+ }
+
+function program7(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-share\"></span>\n ";
+ }
+
+ buffer += "<li>\n <div class=\"bootstrap-wysihtml5-insert-link-modal modal fade\" data-wysihtml5-dialog=\"createLink\">\n <div class=\"modal-dialog ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.smallmodals), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <a class=\"close\" data-dismiss=\"modal\">&times;</a>\n <h3>"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</h3>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <input value=\"http://\" class=\"bootstrap-wysihtml5-insert-link-url form-control\" data-wysihtml5-dialog-field=\"href\">\n </div> \n <div class=\"checkbox\">\n <label> \n <input type=\"checkbox\" class=\"bootstrap-wysihtml5-insert-link-target\" checked>"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.target)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <a class=\"btn btn-default\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"cancel\" href=\"#\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.cancel)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a>\n <a href=\"#\" class=\"btn btn-primary\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"save\">"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "</a>\n </div>\n </div>\n </div>\n </div>\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"createLink\" title=\""
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "\" tabindex=\"-1\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </a>\n</li>\n";
+ return buffer;
+ });
+
+this["wysihtml5"]["tpl"]["lists"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
+ this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = "", stack1;
+ buffer += "btn-"
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
+ return buffer;
+ }
+
+function program3(depth0,data) {
+
+
+ return "\n <span class=\"fa fa-list-ul\"></span>\n ";
+ }
+
+function program5(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-list\"></span>\n ";
+ }
+
+function program7(depth0,data) {
+
+
+ return "\n <span class=\"fa fa-list-ol\"></span>\n ";
+ }
+
+function program9(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-th-list\"></span>\n ";
+ }
+
+function program11(depth0,data) {
+
+
+ return "\n <span class=\"fa fa-outdent\"></span>\n ";
+ }
+
+function program13(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-indent-right\"></span>\n ";
+ }
+
+function program15(depth0,data) {
+
+
+ return "\n <span class=\"fa fa-indent\"></span>\n ";
+ }
+
+function program17(depth0,data) {
+
+
+ return "\n <span class=\"glyphicon glyphicon-indent-left\"></span>\n ";
+ }
+
+ buffer += "<li>\n <div class=\"btn-group\">\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"insertUnorderedList\" title=\""
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.lists)),stack1 == null || stack1 === false ? stack1 : stack1.unordered)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "\" tabindex=\"-1\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </a>\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"insertOrderedList\" title=\""
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.lists)),stack1 == null || stack1 === false ? stack1 : stack1.ordered)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "\" tabindex=\"-1\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(9, program9, data),fn:self.program(7, program7, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </a>\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"Outdent\" title=\""
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.lists)),stack1 == null || stack1 === false ? stack1 : stack1.outdent)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "\" tabindex=\"-1\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(13, program13, data),fn:self.program(11, program11, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </a>\n <a class=\"btn ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " btn-default\" data-wysihtml5-command=\"Indent\" title=\""
+ + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.lists)),stack1 == null || stack1 === false ? stack1 : stack1.indent)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ + "\" tabindex=\"-1\">\n ";
+ stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(17, program17, data),fn:self.program(15, program15, data),data:data});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </a>\n </div>\n</li>\n";
+ return buffer;
+ });(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define('bootstrap.wysihtml5', ['jquery', 'wysihtml5', 'bootstrap', 'bootstrap.wysihtml5.templates', 'bootstrap.wysihtml5.commands'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery, wysihtml5); // jshint ignore:line
+ }
+}(function ($, wysihtml5) {
+ 'use strict';
+ var bsWysihtml5 = function($, wysihtml5) {
+
+ var templates = function(key, locale, options) {
+ if(wysihtml5.tpl[key]) {
+ return wysihtml5.tpl[key]({locale: locale, options: options});
+ }
+ };
+
+ var Wysihtml5 = function(el, options) {
+ this.el = el;
+ var toolbarOpts = $.extend(true, {}, defaultOptions, options);
+ for(var t in toolbarOpts.customTemplates) {
+ if (toolbarOpts.customTemplates.hasOwnProperty(t)) {
+ wysihtml5.tpl[t] = toolbarOpts.customTemplates[t];
+ }
+ }
+ this.toolbar = this.createToolbar(el, toolbarOpts);
+ this.editor = this.createEditor(toolbarOpts);
+ };
+
+ Wysihtml5.prototype = {
+
+ constructor: Wysihtml5,
+
+ createEditor: function(options) {
+ options = options || {};
+
+ // Add the toolbar to a clone of the options object so multiple instances
+ // of the WYISYWG don't break because 'toolbar' is already defined
+ options = $.extend(true, {}, options);
+ options.toolbar = this.toolbar[0];
+
+ this.initializeEditor(this.el[0], options);
+ },
+
+
+ initializeEditor: function(el, options) {
+ var editor = new wysihtml5.Editor(this.el[0], options);
+
+ editor.on('beforeload', this.syncBootstrapDialogEvents);
+ editor.on('beforeload', this.loadParserRules);
+
+ // #30 - body is in IE 10 not created by default, which leads to nullpointer
+ // 2014/02/13 - adapted to wysihtml5-0.4, does not work in IE
+ if(editor.composer.editableArea.contentDocument) {
+ this.addMoreShortcuts(editor,
+ editor.composer.editableArea.contentDocument.body || editor.composer.editableArea.contentDocument,
+ options.shortcuts);
+ } else {
+ this.addMoreShortcuts(editor, editor.composer.editableArea, options.shortcuts);
+ }
+
+ if(options && options.events) {
+ for(var eventName in options.events) {
+ if (options.events.hasOwnProperty(eventName)) {
+ editor.on(eventName, options.events[eventName]);
+ }
+ }
+ }
+
+ return editor;
+ },
+
+ loadParserRules: function() {
+ if($.type(this.config.parserRules) === 'string') {
+ $.ajax({
+ dataType: 'json',
+ url: this.config.parserRules,
+ context: this,
+ error: function (jqXHR, textStatus, errorThrown) {
+ console.log(errorThrown);
+ },
+ success: function (parserRules) {
+ this.config.parserRules = parserRules;
+ console.log('parserrules loaded');
+ }
+ });
+ }
+
+ if(this.config.pasteParserRulesets && $.type(this.config.pasteParserRulesets) === 'string') {
+ $.ajax({
+ dataType: 'json',
+ url: this.config.pasteParserRulesets,
+ context: this,
+ error: function (jqXHR, textStatus, errorThrown) {
+ console.log(errorThrown);
+ },
+ success: function (pasteParserRulesets) {
+ this.config.pasteParserRulesets = pasteParserRulesets;
+ }
+ });
+ }
+ },
+
+ //sync wysihtml5 events for dialogs with bootstrap events
+ syncBootstrapDialogEvents: function() {
+ var editor = this;
+ $.map(this.toolbar.commandMapping, function(value) {
+ return [value];
+ }).filter(function(commandObj) {
+ return commandObj.dialog;
+ }).map(function(commandObj) {
+ return commandObj.dialog;
+ }).forEach(function(dialog) {
+ dialog.on('show', function() {
+ $(this.container).modal('show');
+ });
+ dialog.on('hide', function() {
+ $(this.container).modal('hide');
+ setTimeout(editor.composer.focus, 0);
+ });
+ $(dialog.container).on('shown.bs.modal', function () {
+ $(this).find('input, select, textarea').first().focus();
+ });
+ });
+ this.on('change_view', function() {
+ $(this.toolbar.container.children).find('a.btn').not('[data-wysihtml5-action="change_view"]').toggleClass('disabled');
+ });
+ },
+
+ createToolbar: function(el, options) {
+ var self = this;
+ var toolbar = $('<ul/>', {
+ 'class' : 'wysihtml5-toolbar',
+ 'style': 'display:none'
+ });
+ var culture = options.locale || defaultOptions.locale || 'en';
+ if(!locale.hasOwnProperty(culture)) {
+ console.debug('Locale \'' + culture + '\' not found. Available locales are: ' + Object.keys(locale) + '. Falling back to \'en\'.');
+ culture = 'en';
+ }
+ var localeObject = $.extend(true, {}, locale.en, locale[culture]);
+ for(var key in options.toolbar) {
+ if(options.toolbar[key]) {
+ toolbar.append(templates(key, localeObject, options));
+ }
+ }
+
+ toolbar.find('a[data-wysihtml5-command="formatBlock"]').click(function(e) {
+ var target = e.delegateTarget || e.target || e.srcElement,
+ el = $(target),
+ showformat = el.data('wysihtml5-display-format-name'),
+ formatname = el.data('wysihtml5-format-name') || el.html();
+ if(showformat === undefined || showformat === 'true') {
+ self.toolbar.find('.current-font').text(formatname);
+ }
+ });
+
+ toolbar.find('a[data-wysihtml5-command="foreColor"]').click(function(e) {
+ var target = e.target || e.srcElement;
+ var el = $(target);
+ self.toolbar.find('.current-color').text(el.html());
+ });
+
+ this.el.before(toolbar);
+
+ return toolbar;
+ },
+
+ addMoreShortcuts: function(editor, el, shortcuts) {
+ /* some additional shortcuts */
+ wysihtml5.dom.observe(el, 'keydown', function(event) {
+ var keyCode = event.keyCode,
+ command = shortcuts[keyCode];
+ if ((event.ctrlKey || event.metaKey || event.altKey) && command && wysihtml5.commands[command]) {
+ var commandObj = editor.toolbar.commandMapping[command + ':null'];
+ if (commandObj && commandObj.dialog && !commandObj.state) {
+ commandObj.dialog.show();
+ } else {
+ wysihtml5.commands[command].exec(editor.composer, command);
+ }
+ event.preventDefault();
+ }
+ });
+ }
+ };
+
+ // these define our public api
+ var methods = {
+ resetDefaults: function() {
+ $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache);
+ },
+ bypassDefaults: function(options) {
+ return this.each(function () {
+ var $this = $(this);
+ $this.data('wysihtml5', new Wysihtml5($this, options));
+ });
+ },
+ shallowExtend: function (options) {
+ var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}, $(this).data());
+ var that = this;
+ return methods.bypassDefaults.apply(that, [settings]);
+ },
+ deepExtend: function(options) {
+ var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {});
+ var that = this;
+ return methods.bypassDefaults.apply(that, [settings]);
+ },
+ init: function(options) {
+ var that = this;
+ return methods.shallowExtend.apply(that, [options]);
+ }
+ };
+
+ $.fn.wysihtml5 = function ( method ) {
+ if ( methods[method] ) {
+ return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
+ } else if ( typeof method === 'object' || ! method ) {
+ return methods.init.apply( this, arguments );
+ } else {
+ $.error( 'Method ' + method + ' does not exist on jQuery.wysihtml5' );
+ }
+ };
+
+ $.fn.wysihtml5.Constructor = Wysihtml5;
+
+ var defaultOptions = $.fn.wysihtml5.defaultOptions = {
+ toolbar: {
+ 'font-styles': true,
+ 'color': false,
+ 'emphasis': {
+ 'small': true
+ },
+ 'blockquote': true,
+ 'lists': true,
+ 'html': false,
+ 'link': true,
+ 'image': true,
+ 'smallmodals': false
+ },
+ useLineBreaks: false,
+ parserRules: {
+ classes: {
+ 'wysiwyg-color-silver' : 1,
+ 'wysiwyg-color-gray' : 1,
+ 'wysiwyg-color-white' : 1,
+ 'wysiwyg-color-maroon' : 1,
+ 'wysiwyg-color-red' : 1,
+ 'wysiwyg-color-purple' : 1,
+ 'wysiwyg-color-fuchsia' : 1,
+ 'wysiwyg-color-green' : 1,
+ 'wysiwyg-color-lime' : 1,
+ 'wysiwyg-color-olive' : 1,
+ 'wysiwyg-color-yellow' : 1,
+ 'wysiwyg-color-navy' : 1,
+ 'wysiwyg-color-blue' : 1,
+ 'wysiwyg-color-teal' : 1,
+ 'wysiwyg-color-aqua' : 1,
+ 'wysiwyg-color-orange' : 1
+ },
+ tags: {
+ 'b': {},
+ 'i': {},
+ 'strong': {},
+ 'em': {},
+ 'p': {},
+ 'br': {},
+ 'ol': {},
+ 'ul': {},
+ 'li': {},
+ 'h1': {},
+ 'h2': {},
+ 'h3': {},
+ 'h4': {},
+ 'h5': {},
+ 'h6': {},
+ 'blockquote': {},
+ 'u': 1,
+ 'img': {
+ 'check_attributes': {
+ 'width': 'numbers',
+ 'alt': 'alt',
+ 'src': 'url',
+ 'height': 'numbers'
+ }
+ },
+ 'a': {
+ 'check_attributes': {
+ 'href': 'url'
+ },
+ 'set_attributes': {
+ 'target': '_blank',
+ 'rel': 'nofollow'
+ }
+ },
+ 'span': 1,
+ 'div': 1,
+ 'small': 1,
+ 'code': 1,
+ 'pre': 1
+ }
+ },
+ locale: 'en',
+ shortcuts: {
+ '83': 'small',// S
+ '75': 'createLink'// K
+ }
+ };
+
+ if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') {
+ $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions);
+ }
+
+ var locale = $.fn.wysihtml5.locale = {};
+ };
+ bsWysihtml5($, wysihtml5);
+}));
+(function(wysihtml5) {
+ wysihtml5.commands.small = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatInline.exec(composer, command, "small");
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatInline.state(composer, command, "small");
+ }
+ };
+})(wysihtml5);
+
+/**
+ * English translation for bootstrap-wysihtml5
+ */
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define('bootstrap.wysihtml5.en-US', ['jquery', 'bootstrap.wysihtml5'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+ $.fn.wysihtml5.locale.en = $.fn.wysihtml5.locale['en-US'] = {
+ font_styles: {
+ normal: 'Normal text',
+ h1: 'Heading 1',
+ h2: 'Heading 2',
+ h3: 'Heading 3',
+ h4: 'Heading 4',
+ h5: 'Heading 5',
+ h6: 'Heading 6'
+ },
+ emphasis: {
+ bold: 'Bold',
+ italic: 'Italic',
+ underline: 'Underline',
+ small: 'Small'
+ },
+ lists: {
+ unordered: 'Unordered list',
+ ordered: 'Ordered list',
+ outdent: 'Outdent',
+ indent: 'Indent'
+ },
+ link: {
+ insert: 'Insert link',
+ cancel: 'Cancel',
+ target: 'Open link in new window'
+ },
+ image: {
+ insert: 'Insert image',
+ cancel: 'Cancel'
+ },
+ html: {
+ edit: 'Edit HTML'
+ },
+ colours: {
+ black: 'Black',
+ silver: 'Silver',
+ gray: 'Grey',
+ maroon: 'Maroon',
+ red: 'Red',
+ purple: 'Purple',
+ green: 'Green',
+ olive: 'Olive',
+ navy: 'Navy',
+ blue: 'Blue',
+ orange: 'Orange'
+ }
+ };
+}));
diff --git a/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js b/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js
new file mode 100644
index 0000000..58765c0
--- /dev/null
+++ b/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js
@@ -0,0 +1,8 @@
+/*! bootstrap3-wysihtml5-bower 2014-09-26 */
+var wysihtml5,Base,Handlebars;Object.defineProperty&&Object.getOwnPropertyDescriptor&&Object.getOwnPropertyDescriptor(Element.prototype,"textContent")&&!Object.getOwnPropertyDescriptor(Element.prototype,"textContent").get&&!function(){var a=Object.getOwnPropertyDescriptor(Element.prototype,"innerText");Object.defineProperty(Element.prototype,"textContent",{get:function(){return a.get.call(this)},set:function(b){return a.set.call(this,b)}})}(),Array.isArray||(Array.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)}),wysihtml5={version:"0.4.15",commands:{},dom:{},quirks:{},toolbar:{},lang:{},selection:{},views:{},INVISIBLE_SPACE:"",EMPTY_FUNCTION:function(){},ELEMENT_NODE:1,TEXT_NODE:3,BACKSPACE_KEY:8,ENTER_KEY:13,ESCAPE_KEY:27,SPACE_KEY:32,DELETE_KEY:46},function(a,b){"function"==typeof define&&define.amd?define(a):b.rangy=a()}(function(){function a(a,b){var c=typeof a[b];return c==x||!(c!=w||!a[b])||"unknown"==c}function b(a,b){return!(typeof a[b]!=w||!a[b])}function c(a,b){return typeof a[b]!=y}function d(a){return function(b,c){for(var d=c.length;d--;)if(!a(b,c[d]))return!1;return!0}}function e(a){return a&&D(a,C)&&F(a,B)}function f(a){return b(a,"body")?a.body:a.getElementsByTagName("body")[0]}function g(c){b(window,"console")&&a(window.console,"log")&&window.console.log(c)}function h(a,b){b?window.alert(a):g(a)}function i(a){H.initialized=!0,H.supported=!1,h("Rangy is not supported on this page in your browser. Reason: "+a,H.config.alertOnFail)}function j(a){h("Rangy warning: "+a,H.config.alertOnWarn)}function k(a){return a.message||a.description||a+""}function l(){var b,c,d,h,j,l,m,o,p;if(!H.initialized){if(c=!1,d=!1,a(document,"createRange")&&(b=document.createRange(),D(b,A)&&F(b,z)&&(c=!0)),h=f(document),!h||"body"!=h.nodeName.toLowerCase())return i("No body element found"),void 0;if(h&&a(h,"createTextRange")&&(b=h.createTextRange(),e(b)&&(d=!0)),!c&&!d)return i("Neither Range nor TextRange are available"),void 0;H.initialized=!0,H.features={implementsDomRange:c,implementsTextRange:d};for(m in G)(j=G[m])instanceof n&&j.init(j,H);for(o=0,p=s.length;p>o;++o)try{s[o](H)}catch(q){l="Rangy init listener threw an exception. Continuing. Detail: "+k(q),g(l)}}}function m(a){a=a||window,l();for(var b=0,c=t.length;c>b;++b)t[b](a)}function n(a,b,c){this.name=a,this.dependencies=b,this.initialized=!1,this.supported=!1,this.initializer=c}function o(a,b,c,d){var e=new n(b,c,function(a){if(!a.initialized){a.initialized=!0;try{d(H,a),a.supported=!0}catch(c){var e="Module '"+b+"' failed to load: "+k(c);g(e)}}});G[b]=e}function p(){}function q(){}var r,s,t,u,v,w="object",x="function",y="undefined",z=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],A=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],B=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],C=["collapse","compareEndPoints","duplicate","moveToElementText","parentElement","select","setEndPoint","getBoundingClientRect"],D=d(a),E=d(b),F=d(c),G={},H={version:"1.3alpha.20140804",initialized:!1,supported:!0,util:{isHostMethod:a,isHostObject:b,isHostProperty:c,areHostMethods:D,areHostObjects:E,areHostProperties:F,isTextRange:e,getBody:f},features:{},modules:G,config:{alertOnFail:!0,alertOnWarn:!1,preferTextRange:!1,autoInitialize:typeof rangyAutoInitialize==y?!0:rangyAutoInitialize}};return H.fail=i,H.warn=j,{}.hasOwnProperty?H.util.extend=function(a,b,c){var d,e,f;for(f in b)b.hasOwnProperty(f)&&(d=a[f],e=b[f],c&&null!==d&&"object"==typeof d&&null!==e&&"object"==typeof e&&H.util.extend(d,e,!0),a[f]=e);return b.hasOwnProperty("toString")&&(a.toString=b.toString),a}:i("hasOwnProperty not supported"),function(){var a,b,c=document.createElement("div");c.appendChild(document.createElement("span")),a=[].slice;try{1==a.call(c.childNodes,0)[0].nodeType&&(b=function(b){return a.call(b,0)})}catch(d){}b||(b=function(a){var b,c,d=[];for(b=0,c=a.length;c>b;++b)d[b]=a[b];return d}),H.util.toArray=b}(),a(document,"addEventListener")?r=function(a,b,c){a.addEventListener(b,c,!1)}:a(document,"attachEvent")?r=function(a,b,c){a.attachEvent("on"+b,c)}:i("Document does not have required addEventListener or attachEvent method"),H.util.addListener=r,s=[],H.init=l,H.addInitListener=function(a){H.initialized?a(H):s.push(a)},t=[],H.addShimListener=function(a){t.push(a)},H.shim=H.createMissingNativeApi=m,n.prototype={init:function(){var a,b,c,d,e=this.dependencies||[];for(a=0,b=e.length;b>a;++a){if(d=e[a],c=G[d],!(c&&c instanceof n))throw Error("required module '"+d+"' not found");if(c.init(),!c.supported)throw Error("required module '"+d+"' not supported")}this.initializer(this)},fail:function(a){throw this.initialized=!0,this.supported=!1,Error("Module '"+this.name+"' failed to load: "+a)},warn:function(a){H.warn("Module "+this.name+": "+a)},deprecationNotice:function(a,b){H.warn("DEPRECATED: "+a+" in module "+this.name+"is deprecated. Please use "+b+" instead")},createError:function(a){return Error("Error in Rangy "+this.name+" module: "+a)}},H.createModule=function(a){var b,c,d;2==arguments.length?(b=arguments[1],c=[]):(b=arguments[2],c=arguments[1]),d=o(!1,a,c,b),H.initialized&&d.init()},H.createCoreModule=function(a,b,c){o(!0,a,b,c)},H.RangePrototype=p,H.rangePrototype=new p,H.selectionPrototype=new q,u=!1,v=function(){u||(u=!0,!H.initialized&&H.config.autoInitialize&&l())},typeof window==y?(i("No window found"),void 0):typeof document==y?(i("No document found"),void 0):(a(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",v,!1),r(window,"load",v),H.createCoreModule("DomUtil",[],function(a,b){function c(a){var b;return typeof a.namespaceURI==I||null===(b=a.namespaceURI)||"http://www.w3.org/1999/xhtml"==b}function d(a){var b=a.parentNode;return 1==b.nodeType?b:null}function e(a){for(var b=0;a=a.previousSibling;)++b;return b}function f(a){switch(a.nodeType){case 7:case 10:return 0;case 3:case 8:return a.length;default:return a.childNodes.length}}function g(a,b){var c,d=[];for(c=a;c;c=c.parentNode)d.push(c);for(c=b;c;c=c.parentNode)if(F(d,c))return c;return null}function h(a,b,c){for(var d=c?b:b.parentNode;d;){if(d===a)return!0;d=d.parentNode}return!1}function i(a,b){return h(a,b,!0)}function j(a,b,c){for(var d,e=c?a:a.parentNode;e;){if(d=e.parentNode,d===b)return e;e=d}return null}function k(a){var b=a.nodeType;return 3==b||4==b||8==b}function l(a){if(!a)return!1;var b=a.nodeType;return 3==b||8==b}function m(a,b){var c=b.nextSibling,d=b.parentNode;return c?d.insertBefore(a,c):d.appendChild(a),a}function n(a,b,c){var d,f,g=a.cloneNode(!1);if(g.deleteData(0,b),a.deleteData(b,a.length-b),m(g,a),c)for(d=0;f=c[d++];)f.node==a&&f.offset>b?(f.node=g,f.offset-=b):f.node==a.parentNode&&f.offset>e(a)&&++f.offset;return g}function o(a){if(9==a.nodeType)return a;if(typeof a.ownerDocument!=I)return a.ownerDocument;if(typeof a.document!=I)return a.document;if(a.parentNode)return o(a.parentNode);throw b.createError("getDocument: no document found for node")}function p(a){var c=o(a);if(typeof c.defaultView!=I)return c.defaultView;if(typeof c.parentWindow!=I)return c.parentWindow;throw b.createError("Cannot get a window object for node")}function q(a){if(typeof a.contentDocument!=I)return a.contentDocument;if(typeof a.contentWindow!=I)return a.contentWindow.document;throw b.createError("getIframeDocument: No Document object found for iframe element")}function r(a){if(typeof a.contentWindow!=I)return a.contentWindow;if(typeof a.contentDocument!=I)return a.contentDocument.defaultView;throw b.createError("getIframeWindow: No Window object found for iframe element")}function s(a){return a&&J.isHostMethod(a,"setTimeout")&&J.isHostObject(a,"document")}function t(a,b,c){var d;if(a?J.isHostProperty(a,"nodeType")?d=1==a.nodeType&&"iframe"==a.tagName.toLowerCase()?q(a):o(a):s(a)&&(d=a.document):d=document,!d)throw b.createError(c+"(): Parameter must be a Window object or DOM node");return d}function u(a){for(var b;b=a.parentNode;)a=b;return a}function v(a,c,d,f){var h,i,k,l,m;if(a==d)return c===f?0:f>c?-1:1;if(h=j(d,a,!0))return c<=e(h)?-1:1;if(h=j(a,d,!0))return e(h)<f?-1:1;if(i=g(a,d),!i)throw Error("comparePoints error: nodes have no common ancestor");if(k=a===i?i:j(a,i,!0),l=d===i?i:j(d,i,!0),k===l)throw b.createError("comparePoints got to case 4 and childA and childB are the same!");for(m=i.firstChild;m;){if(m===k)return-1;if(m===l)return 1;m=m.nextSibling}}function w(a){var b;try{return b=a.parentNode,!1}catch(c){return!0}}function x(a){if(!a)return"[No node]";if(G&&w(a))return"[Broken node]";if(k(a))return'"'+a.data+'"';if(1==a.nodeType){var b=a.id?' id="'+a.id+'"':"";return"<"+a.nodeName+b+">[index:"+e(a)+",length:"+a.childNodes.length+"]["+(a.innerHTML||"[innerHTML not supported]").slice(0,25)+"]"}return a.nodeName}function y(a){for(var b,c=o(a).createDocumentFragment();b=a.firstChild;)c.appendChild(b);return c}function z(a){this.root=a,this._next=a}function A(a){return new z(a)}function B(a,b){this.node=a,this.offset=b}function C(a){this.code=this[a],this.codeName=a,this.message="DOMException: "+this.codeName}var D,E,F,G,H,I="undefined",J=a.util;J.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||b.fail("document missing a Node creation method"),J.isHostMethod(document,"getElementsByTagName")||b.fail("document missing getElementsByTagName method"),D=document.createElement("div"),J.areHostMethods(D,["insertBefore","appendChild","cloneNode"]||!J.areHostObjects(D,["previousSibling","nextSibling","childNodes","parentNode"]))||b.fail("Incomplete Element implementation"),J.isHostProperty(D,"innerHTML")||b.fail("Element is missing innerHTML property"),E=document.createTextNode("test"),J.areHostMethods(E,["splitText","deleteData","insertData","appendData","cloneNode"]||!J.areHostObjects(D,["previousSibling","nextSibling","childNodes","parentNode"])||!J.areHostProperties(E,["data"]))||b.fail("Incomplete Text Node implementation"),F=function(a,b){for(var c=a.length;c--;)if(a[c]===b)return!0;return!1},G=!1,function(){var b,c=document.createElement("b");c.innerHTML="1",b=c.firstChild,c.innerHTML="<br>",G=w(b),a.features.crashyTextNodes=G}(),typeof window.getComputedStyle!=I?H=function(a,b){return p(a).getComputedStyle(a,null)[b]}:typeof document.documentElement.currentStyle!=I?H=function(a,b){return a.currentStyle[b]}:b.fail("No means of obtaining computed style properties found"),z.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var a,b,c=this._current=this._next;if(this._current)if(a=c.firstChild,a)this._next=a;else{for(b=null;c!==this.root&&!(b=c.nextSibling);)c=c.parentNode;this._next=b}return this._current},detach:function(){this._current=this._next=this.root=null}},B.prototype={equals:function(a){return!!a&&this.node===a.node&&this.offset==a.offset},inspect:function(){return"[DomPosition("+x(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},C.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11,INVALID_NODE_TYPE_ERR:24},C.prototype.toString=function(){return this.message},a.dom={arrayContains:F,isHtmlNamespace:c,parentElement:d,getNodeIndex:e,getNodeLength:f,getCommonAncestor:g,isAncestorOf:h,isOrIsAncestorOf:i,getClosestAncestorIn:j,isCharacterDataNode:k,isTextOrCommentNode:l,insertAfter:m,splitDataNode:n,getDocument:o,getWindow:p,getIframeWindow:r,getIframeDocument:q,getBody:J.getBody,isWindow:s,getContentDocument:t,getRootContainer:u,comparePoints:v,isBrokenNode:w,inspectNode:x,getComputedStyleProperty:H,fragmentFromNodeChildren:y,createIterator:A,DomPosition:B},a.DOMException=C}),H.createCoreModule("DomRange",["DomUtil"],function(a){function b(a,b){return 3!=a.nodeType&&(gb(a,b.startContainer)||gb(a,b.endContainer))}function c(a){return a.document||hb(a.startContainer)}function d(a){return new cb(a.parentNode,fb(a))}function e(a){return new cb(a.parentNode,fb(a)+1)}function f(a,b,c){var d=11==a.nodeType?a.firstChild:a;return eb(b)?c==b.length?ab.insertAfter(a,b):b.parentNode.insertBefore(a,0==c?b:jb(b,c)):c>=b.childNodes.length?b.appendChild(a):b.insertBefore(a,b.childNodes[c]),d}function g(a,b,d){if(y(a),y(b),c(b)!=c(a))throw new db("WRONG_DOCUMENT_ERR");var e=ib(a.startContainer,a.startOffset,b.endContainer,b.endOffset),f=ib(a.endContainer,a.endOffset,b.startContainer,b.startOffset);return d?0>=e&&f>=0:0>e&&f>0}function h(a){var b,d,e,f;for(e=c(a.range).createDocumentFragment();d=a.next();){if(b=a.isPartiallySelectedSubtree(),d=d.cloneNode(!b),b&&(f=a.getSubtreeIterator(),d.appendChild(h(f)),f.detach()),10==d.nodeType)throw new db("HIERARCHY_REQUEST_ERR");e.appendChild(d)}return e}function i(a,b,c){var d,e,f,g;for(c=c||{stop:!1};f=a.next();)if(a.isPartiallySelectedSubtree()){if(b(f)===!1)return c.stop=!0,void 0;if(g=a.getSubtreeIterator(),i(g,b,c),g.detach(),c.stop)return}else for(d=ab.createIterator(f);e=d.next();)if(b(e)===!1)return c.stop=!0,void 0}function j(a){for(var b;a.next();)a.isPartiallySelectedSubtree()?(b=a.getSubtreeIterator(),j(b),b.detach()):a.remove()}function k(a){for(var b,d,e=c(a.range).createDocumentFragment();b=a.next();){if(a.isPartiallySelectedSubtree()?(b=b.cloneNode(!1),d=a.getSubtreeIterator(),b.appendChild(k(d)),d.detach()):a.remove(),10==b.nodeType)throw new db("HIERARCHY_REQUEST_ERR");e.appendChild(b)}return e}function l(a,b,c){var d,e,f=!(!b||!b.length),g=!!c;return f&&(d=RegExp("^("+b.join("|")+")$")),e=[],i(new n(a,!1),function(b){var h,i;(!f||d.test(b.nodeType))&&(!g||c(b))&&(h=a.startContainer,b==h&&eb(h)&&a.startOffset==h.length||(i=a.endContainer,b==i&&eb(i)&&0==a.endOffset||e.push(b)))}),e}function m(a){var b=void 0===a.getName?"Range":a.getName();return"["+b+"("+ab.inspectNode(a.startContainer)+":"+a.startOffset+", "+ab.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function n(a,b){if(this.range=a,this.clonePartiallySelectedTextNodes=b,!a.collapsed){this.sc=a.startContainer,this.so=a.startOffset,this.ec=a.endContainer,this.eo=a.endOffset;var c=a.commonAncestorContainer;this.sc===this.ec&&eb(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc!==c||eb(this.sc)?kb(this.sc,c,!0):this.sc.childNodes[this.so],this._last=this.ec!==c||eb(this.ec)?kb(this.ec,c,!0):this.ec.childNodes[this.eo-1])}}function o(a){return function(b,c){for(var d,e=c?b:b.parentNode;e;){if(d=e.nodeType,mb(a,d))return e;e=e.parentNode}return null}}function p(a,b){if(P(a,b))throw new db("INVALID_NODE_TYPE_ERR")}function q(a,b){if(!mb(b,a.nodeType))throw new db("INVALID_NODE_TYPE_ERR")}function r(a,b){if(0>b||b>(eb(a)?a.length:a.childNodes.length))throw new db("INDEX_SIZE_ERR")}function s(a,b){if(N(a,!0)!==N(b,!0))throw new db("WRONG_DOCUMENT_ERR")}function t(a){if(O(a,!0))throw new db("NO_MODIFICATION_ALLOWED_ERR")}function u(a,b){if(!a)throw new db(b)}function v(a){return ob&&ab.isBrokenNode(a)||!mb(J,a.nodeType)&&!N(a,!0)}function w(a,b){return b<=(eb(a)?a.length:a.childNodes.length)}function x(a){return!!a.startContainer&&!!a.endContainer&&!v(a.startContainer)&&!v(a.endContainer)&&w(a.startContainer,a.startOffset)&&w(a.endContainer,a.endOffset)}function y(a){if(!x(a))throw Error("Range error: Range is no longer valid after DOM mutation ("+a.inspect()+")")}function z(a,b){var c,d,e,f,g;y(a),c=a.startContainer,d=a.startOffset,e=a.endContainer,f=a.endOffset,g=c===e,eb(e)&&f>0&&f<e.length&&jb(e,f,b),eb(c)&&d>0&&d<c.length&&(c=jb(c,d,b),g?(f-=d,e=c):e==c.parentNode&&f>=fb(c)&&f++,d=0),a.setStartAndEnd(c,d,e,f)}function A(a){y(a);var b=a.commonAncestorContainer.parentNode.cloneNode(!1);return b.appendChild(a.cloneContents()),b.innerHTML}function B(a){a.START_TO_START=U,a.START_TO_END=V,a.END_TO_END=W,a.END_TO_START=X,a.NODE_BEFORE=Y,a.NODE_AFTER=Z,a.NODE_BEFORE_AND_AFTER=$,a.NODE_INSIDE=_}function C(a){B(a),B(a.prototype)}function D(a,b){return function(){var c,d,f,g,h,j,k;return y(this),c=this.startContainer,d=this.startOffset,f=this.commonAncestorContainer,g=new n(this,!0),c!==f&&(h=kb(c,f,!0),j=e(h),c=j.node,d=j.offset),i(g,t),g.reset(),k=a(g),g.detach(),b(this,c,d,c,d),k}}function E(c,f){function g(a,b){return function(c){q(c,I),q(nb(c),J);var f=(a?d:e)(c);(b?h:i)(this,f.node,f.offset)}}function h(a,b,c){var d=a.endContainer,e=a.endOffset;(b!==a.startContainer||c!==a.startOffset)&&((nb(b)!=nb(d)||1==ib(b,c,d,e))&&(d=b,e=c),f(a,b,c,d,e))}function i(a,b,c){var d=a.startContainer,e=a.startOffset;(b!==a.endContainer||c!==a.endOffset)&&((nb(b)!=nb(d)||-1==ib(b,c,d,e))&&(d=b,e=c),f(a,d,e,b,c))}var l=function(){};l.prototype=a.rangePrototype,c.prototype=new l,bb.extend(c.prototype,{setStart:function(a,b){p(a,!0),r(a,b),h(this,a,b)},setEnd:function(a,b){p(a,!0),r(a,b),i(this,a,b)},setStartAndEnd:function(){var a=arguments,b=a[0],c=a[1],d=b,e=c;switch(a.length){case 3:e=a[2];break;case 4:d=a[2],e=a[3]}f(this,b,c,d,e)},setBoundary:function(a,b,c){this["set"+(c?"Start":"End")](a,b)},setStartBefore:g(!0,!0),setStartAfter:g(!1,!0),setEndBefore:g(!0,!1),setEndAfter:g(!1,!1),collapse:function(a){y(this),a?f(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):f(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(a){p(a,!0),f(this,a,0,a,lb(a))},selectNode:function(a){p(a,!1),q(a,I);var b=d(a),c=e(a);f(this,b.node,b.offset,c.node,c.offset)},extractContents:D(k,f),deleteContents:D(j,f),canSurroundContents:function(){var a,c;return y(this),t(this.startContainer),t(this.endContainer),a=new n(this,!0),c=a._first&&b(a._first,this)||a._last&&b(a._last,this),a.detach(),!c},splitBoundaries:function(){z(this)},splitBoundariesPreservingPositions:function(a){z(this,a)},normalizeBoundaries:function(){var a,b,c,d,e,g,h,i,j;y(this),a=this.startContainer,b=this.startOffset,c=this.endContainer,d=this.endOffset,e=function(a){var b=a.nextSibling;b&&b.nodeType==a.nodeType&&(c=a,d=a.length,a.appendData(b.data),b.parentNode.removeChild(b))},g=function(e){var f,g,h=e.previousSibling;h&&h.nodeType==e.nodeType&&(a=e,f=e.length,b=h.length,e.insertData(0,h.data),h.parentNode.removeChild(h),a==c?(d+=b,c=a):c==e.parentNode&&(g=fb(e),d==g?(c=e,d=f):d>g&&d--))},h=!0,eb(c)?c.length==d&&e(c):(d>0&&(i=c.childNodes[d-1],i&&eb(i)&&e(i)),h=!this.collapsed),h?eb(a)?0==b&&g(a):b<a.childNodes.length&&(j=a.childNodes[b],j&&eb(j)&&g(j)):(a=c,b=d),f(this,a,b,c,d)},collapseToPoint:function(a,b){p(a,!0),r(a,b),this.setStartAndEnd(a,b)}}),C(c)}function F(a){a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset,a.commonAncestorContainer=a.collapsed?a.startContainer:ab.getCommonAncestor(a.startContainer,a.endContainer)}function G(a,b,c,d,e){a.startContainer=b,a.startOffset=c,a.endContainer=d,a.endOffset=e,a.document=ab.getDocument(b),F(a)}function H(a){this.startContainer=a,this.startOffset=0,this.endContainer=a,this.endOffset=0,this.document=a,F(this)}var I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,$,_,ab=a.dom,bb=a.util,cb=ab.DomPosition,db=a.DOMException,eb=ab.isCharacterDataNode,fb=ab.getNodeIndex,gb=ab.isOrIsAncestorOf,hb=ab.getDocument,ib=ab.comparePoints,jb=ab.splitDataNode,kb=ab.getClosestAncestorIn,lb=ab.getNodeLength,mb=ab.arrayContains,nb=ab.getRootContainer,ob=a.features.crashyTextNodes;n.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:!1,reset:function(){this._current=null,this._next=this._first},hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next;return a&&(this._next=a!==this._last?a.nextSibling:null,eb(a)&&this.clonePartiallySelectedTextNodes&&(a===this.ec&&(a=a.cloneNode(!0)).deleteData(this.eo,a.length-this.eo),this._current===this.sc&&(a=a.cloneNode(!0)).deleteData(0,this.so))),a},remove:function(){var a,b,c=this._current;!eb(c)||c!==this.sc&&c!==this.ec?c.parentNode&&c.parentNode.removeChild(c):(a=c===this.sc?this.so:0,b=c===this.ec?this.eo:c.length,a!=b&&c.deleteData(a,b-a))},isPartiallySelectedSubtree:function(){var a=this._current;return b(a,this.range)},getSubtreeIterator:function(){var a,b,d,e,f,g;return this.isSingleCharacterDataNode?(a=this.range.cloneRange(),a.collapse(!1)):(a=new H(c(this.range)),b=this._current,d=b,e=0,f=b,g=lb(b),gb(b,this.sc)&&(d=this.sc,e=this.so),gb(b,this.ec)&&(f=this.ec,g=this.eo),G(a,d,e,f,g)),new n(a,this.clonePartiallySelectedTextNodes)},detach:function(){this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}},I=[1,3,4,5,7,8,10],J=[2,9,11],K=[5,6,10,12],L=[1,3,4,5,7,8,10,11],M=[1,3,4,5,7,8],N=o([9,11]),O=o(K),P=o([6,10,12]),Q=document.createElement("style"),R=!1;try{Q.innerHTML="<b>x</b>",R=3==Q.firstChild.nodeType}catch(pb){}a.features.htmlParsingConforms=R,S=R?function(a){var b,c=this.startContainer,d=hb(c);if(!c)throw new db("INVALID_STATE_ERR");return b=null,1==c.nodeType?b=c:eb(c)&&(b=ab.parentElement(c)),b=null===b||"HTML"==b.nodeName&&ab.isHtmlNamespace(hb(b).documentElement)&&ab.isHtmlNamespace(b)?d.createElement("body"):b.cloneNode(!1),b.innerHTML=a,ab.fragmentFromNodeChildren(b)}:function(a){var b=c(this),d=b.createElement("body");return d.innerHTML=a,ab.fragmentFromNodeChildren(d)},T=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],U=0,V=1,W=2,X=3,Y=0,Z=1,$=2,_=3,bb.extend(a.rangePrototype,{compareBoundaryPoints:function(a,b){var c,d,e,f,g,h;return y(this),s(this.startContainer,b.startContainer),g=a==X||a==U?"start":"end",h=a==V||a==U?"start":"end",c=this[g+"Container"],d=this[g+"Offset"],e=b[h+"Container"],f=b[h+"Offset"],ib(c,d,e,f)},insertNode:function(a){if(y(this),q(a,L),t(this.startContainer),gb(a,this.startContainer))throw new db("HIERARCHY_REQUEST_ERR");var b=f(a,this.startContainer,this.startOffset);this.setStartBefore(b)},cloneContents:function(){var a,b,d;return y(this),this.collapsed?c(this).createDocumentFragment():this.startContainer===this.endContainer&&eb(this.startContainer)?(a=this.startContainer.cloneNode(!0),a.data=a.data.slice(this.startOffset,this.endOffset),b=c(this).createDocumentFragment(),b.appendChild(a),b):(d=new n(this,!0),a=h(d),d.detach(),a)},canSurroundContents:function(){var a,c;return y(this),t(this.startContainer),t(this.endContainer),a=new n(this,!0),c=a._first&&b(a._first,this)||a._last&&b(a._last,this),a.detach(),!c},surroundContents:function(a){if(q(a,M),!this.canSurroundContents())throw new db("INVALID_STATE_ERR");var b=this.extractContents();if(a.hasChildNodes())for(;a.lastChild;)a.removeChild(a.lastChild);f(a,this.startContainer,this.startOffset),a.appendChild(b),this.selectNode(a)},cloneRange:function(){var a,b,d;for(y(this),a=new H(c(this)),b=T.length;b--;)d=T[b],a[d]=this[d];return a},toString:function(){var a,b,c;return y(this),a=this.startContainer,a===this.endContainer&&eb(a)?3==a.nodeType||4==a.nodeType?a.data.slice(this.startOffset,this.endOffset):"":(b=[],c=new n(this,!0),i(c,function(a){(3==a.nodeType||4==a.nodeType)&&b.push(a.data)}),c.detach(),b.join(""))},compareNode:function(a){var b,c,d,e;if(y(this),b=a.parentNode,c=fb(a),!b)throw new db("NOT_FOUND_ERR");return d=this.comparePoint(b,c),e=this.comparePoint(b,c+1),0>d?e>0?$:Y:e>0?Z:_},comparePoint:function(a,b){return y(this),u(a,"HIERARCHY_REQUEST_ERR"),s(a,this.startContainer),ib(a,b,this.startContainer,this.startOffset)<0?-1:ib(a,b,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:S,toHtml:function(){return A(this)},intersectsNode:function(a,b){var d,e,f,g;return y(this),u(a,"NOT_FOUND_ERR"),hb(a)!==c(this)?!1:(d=a.parentNode,e=fb(a),u(d,"NOT_FOUND_ERR"),f=ib(d,e,this.endContainer,this.endOffset),g=ib(d,e+1,this.startContainer,this.startOffset),b?0>=f&&g>=0:0>f&&g>0)},isPointInRange:function(a,b){return y(this),u(a,"HIERARCHY_REQUEST_ERR"),s(a,this.startContainer),ib(a,b,this.startContainer,this.startOffset)>=0&&ib(a,b,this.endContainer,this.endOffset)<=0},intersectsRange:function(a){return g(this,a,!1)},intersectsOrTouchesRange:function(a){return g(this,a,!0)},intersection:function(a){var b,c,d;return this.intersectsRange(a)?(b=ib(this.startContainer,this.startOffset,a.startContainer,a.startOffset),c=ib(this.endContainer,this.endOffset,a.endContainer,a.endOffset),d=this.cloneRange(),-1==b&&d.setStart(a.startContainer,a.startOffset),1==c&&d.setEnd(a.endContainer,a.endOffset),d):null},union:function(a){if(this.intersectsOrTouchesRange(a)){var b=this.cloneRange();return-1==ib(a.startContainer,a.startOffset,this.startContainer,this.startOffset)&&b.setStart(a.startContainer,a.startOffset),1==ib(a.endContainer,a.endOffset,this.endContainer,this.endOffset)&&b.setEnd(a.endContainer,a.endOffset),b}throw new db("Ranges do not intersect")},containsNode:function(a,b){return b?this.intersectsNode(a,!1):this.compareNode(a)==_},containsNodeContents:function(a){return this.comparePoint(a,0)>=0&&this.comparePoint(a,lb(a))<=0},containsRange:function(a){var b=this.intersection(a);return null!==b&&a.equals(b)},containsNodeText:function(a){var b,c,d=this.cloneRange();return d.selectNode(a),b=d.getNodes([3]),b.length>0?(d.setStart(b[0],0),c=b.pop(),d.setEnd(c,c.length),this.containsRange(d)):this.containsNodeContents(a)},getNodes:function(a,b){return y(this),l(this,a,b)},getDocument:function(){return c(this)},collapseBefore:function(a){this.setEndBefore(a),this.collapse(!1)},collapseAfter:function(a){this.setStartAfter(a),this.collapse(!0)},getBookmark:function(b){var d,e,f,g=c(this),h=a.createRange(g);return b=b||ab.getBody(g),h.selectNodeContents(b),d=this.intersection(h),e=0,f=0,d&&(h.setEnd(d.startContainer,d.startOffset),e=(""+h).length,f=e+(""+d).length),{start:e,end:f,containerNode:b}},moveToBookmark:function(a){var b,c,d,e,f,g,h,i=a.containerNode,j=0;for(this.setStart(i,0),this.collapse(!0),b=[i],d=!1,e=!1;!e&&(c=b.pop());)if(3==c.nodeType)f=j+c.length,!d&&a.start>=j&&a.start<=f&&(this.setStart(c,a.start-j),d=!0),d&&a.end>=j&&a.end<=f&&(this.setEnd(c,a.end-j),e=!0),j=f;else for(h=c.childNodes,g=h.length;g--;)b.push(h[g])},getName:function(){return"DomRange"},equals:function(a){return H.rangesEqual(this,a)},isValid:function(){return x(this)},inspect:function(){return m(this)},detach:function(){}}),E(H,G),bb.extend(H,{rangeProperties:T,RangeIterator:n,copyComparisonConstants:C,createPrototypeRange:E,inspect:m,toHtml:A,getRangeDocument:c,rangesEqual:function(a,b){return a.startContainer===b.startContainer&&a.startOffset===b.startOffset&&a.endContainer===b.endContainer&&a.endOffset===b.endOffset}}),a.DomRange=H}),H.createCoreModule("WrappedRange",["DomRange"],function(a,b){var c,d,e,f,g,h,i,j,k=a.dom,l=a.util,m=k.DomPosition,n=a.DomRange,o=k.getBody,p=k.getContentDocument,q=k.isCharacterDataNode;a.features.implementsDomRange&&!function(){function d(a){for(var b,c=s.length;c--;)b=s[c],a[b]=a.nativeRange[b];a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset}function e(a,b,c,d,e){var f=a.startContainer!==b||a.startOffset!=c,g=a.endContainer!==d||a.endOffset!=e,h=!a.equals(a.nativeRange);(f||g||h)&&(a.setEnd(d,e),a.setStart(b,c))}var f,g,h,i,j,m,q,r,s=n.rangeProperties;c=function(a){if(!a)throw b.createError("WrappedRange: Range must be specified");this.nativeRange=a,d(this)},n.createPrototypeRange(c,e),f=c.prototype,f.selectNode=function(a){this.nativeRange.selectNode(a),d(this)},f.cloneContents=function(){return this.nativeRange.cloneContents()},f.surroundContents=function(a){this.nativeRange.surroundContents(a),d(this)},f.collapse=function(a){this.nativeRange.collapse(a),d(this)},f.cloneRange=function(){return new c(this.nativeRange.cloneRange())},f.refresh=function(){d(this)},f.toString=function(){return""+this.nativeRange},h=document.createTextNode("test"),o(document).appendChild(h),i=document.createRange(),i.setStart(h,0),i.setEnd(h,0);try{i.setStart(h,1),f.setStart=function(a,b){this.nativeRange.setStart(a,b),d(this)},f.setEnd=function(a,b){this.nativeRange.setEnd(a,b),d(this)},g=function(a){return function(b){this.nativeRange[a](b),d(this)}}}catch(t){f.setStart=function(a,b){try{this.nativeRange.setStart(a,b)}catch(c){this.nativeRange.setEnd(a,b),this.nativeRange.setStart(a,b)}d(this)},f.setEnd=function(a,b){try{this.nativeRange.setEnd(a,b)}catch(c){this.nativeRange.setStart(a,b),this.nativeRange.setEnd(a,b)}d(this)},g=function(a,b){return function(c){try{this.nativeRange[a](c)}catch(e){this.nativeRange[b](c),this.nativeRange[a](c)}d(this)}}}f.setStartBefore=g("setStartBefore","setEndBefore"),f.setStartAfter=g("setStartAfter","setEndAfter"),f.setEndBefore=g("setEndBefore","setStartBefore"),f.setEndAfter=g("setEndAfter","setStartAfter"),f.selectNodeContents=function(a){this.setStartAndEnd(a,0,k.getNodeLength(a))},i.selectNodeContents(h),i.setEnd(h,3),j=document.createRange(),j.selectNodeContents(h),j.setEnd(h,4),j.setStart(h,2),f.compareBoundaryPoints=-1==i.compareBoundaryPoints(i.START_TO_END,j)&&1==i.compareBoundaryPoints(i.END_TO_START,j)?function(a,b){return b=b.nativeRange||b,a==b.START_TO_END?a=b.END_TO_START:a==b.END_TO_START&&(a=b.START_TO_END),this.nativeRange.compareBoundaryPoints(a,b)}:function(a,b){return this.nativeRange.compareBoundaryPoints(a,b.nativeRange||b)},m=document.createElement("div"),m.innerHTML="123",q=m.firstChild,r=o(document),r.appendChild(m),i.setStart(q,1),i.setEnd(q,2),i.deleteContents(),"13"==q.data&&(f.deleteContents=function(){this.nativeRange.deleteContents(),d(this)},f.extractContents=function(){var a=this.nativeRange.extractContents();return d(this),a}),r.removeChild(m),r=null,l.isHostMethod(i,"createContextualFragment")&&(f.createContextualFragment=function(a){return this.nativeRange.createContextualFragment(a)}),o(document).removeChild(h),f.getName=function(){return"WrappedRange"},a.WrappedRange=c,a.createNativeRange=function(a){return a=p(a,b,"createNativeRange"),a.createRange()}}(),a.features.implementsTextRange&&(e=function(a){var b,c,d,e=a.parentElement(),f=a.duplicate();return f.collapse(!0),b=f.parentElement(),f=a.duplicate(),f.collapse(!1),c=f.parentElement(),d=b==c?b:k.getCommonAncestor(b,c),d==e?d:k.getCommonAncestor(e,d)},f=function(a){return 0==a.compareEndPoints("StartToEnd",a)},g=function(a,b,c,d,e){var f,g,h,i,j,l,n,o,p,r,s,t,u,v,w,x,y=a.duplicate();if(y.collapse(c),f=y.parentElement(),k.isOrIsAncestorOf(b,f)||(f=b),!f.canHaveHTML)return g=new m(f.parentNode,k.getNodeIndex(f)),{boundaryPosition:g,nodeInfo:{nodeIndex:g.offset,containerElement:g.node}};for(h=k.getDocument(f).createElement("span"),h.parentNode&&h.parentNode.removeChild(h),j=c?"StartToStart":"StartToEnd",r=e&&e.containerElement==f?e.nodeIndex:0,s=f.childNodes.length,t=s,u=t;;){if(u==s?f.appendChild(h):f.insertBefore(h,f.childNodes[u]),y.moveToElementText(h),i=y.compareEndPoints(j,a),0==i||r==t)break;if(-1==i){if(t==r+1)break;r=u}else t=t==r+1?r:u;u=Math.floor((r+t)/2),f.removeChild(h)}if(p=h.nextSibling,-1==i&&p&&q(p)){if(y.setEndPoint(c?"EndToStart":"EndToEnd",a),/[\r\n]/.test(p.data))for(w=y.duplicate(),x=w.text.replace(/\r\n/g,"\r").length,v=w.moveStart("character",x);-1==(i=w.compareEndPoints("StartToEnd",w));)v++,w.moveStart("character",1);else v=y.text.length;o=new m(p,v)}else l=(d||!c)&&h.previousSibling,n=(d||c)&&h.nextSibling,o=n&&q(n)?new m(n,0):l&&q(l)?new m(l,l.data.length):new m(f,k.getNodeIndex(h));return h.parentNode.removeChild(h),{boundaryPosition:o,nodeInfo:{nodeIndex:u,containerElement:f}}},h=function(a,b){var c,d,e,f,g=a.offset,h=k.getDocument(a.node),i=o(h).createTextRange(),j=q(a.node);return j?(c=a.node,d=c.parentNode):(f=a.node.childNodes,c=g<f.length?f[g]:null,d=a.node),e=h.createElement("span"),e.innerHTML="&#feff;",c?d.insertBefore(e,c):d.appendChild(e),i.moveToElementText(e),i.collapse(!b),d.removeChild(e),j&&i[b?"moveStart":"moveEnd"]("character",g),i},d=function(a){this.textRange=a,this.refresh()},d.prototype=new n(document),d.prototype.refresh=function(){var a,b,c,d=e(this.textRange);
+f(this.textRange)?b=a=g(this.textRange,d,!0,!0).boundaryPosition:(c=g(this.textRange,d,!0,!1),a=c.boundaryPosition,b=g(this.textRange,d,!1,!1,c.nodeInfo).boundaryPosition),this.setStart(a.node,a.offset),this.setEnd(b.node,b.offset)},d.prototype.getName=function(){return"WrappedTextRange"},n.copyComparisonConstants(d),i=function(a){var b,c,d;return a.collapsed?h(new m(a.startContainer,a.startOffset),!0):(b=h(new m(a.startContainer,a.startOffset),!0),c=h(new m(a.endContainer,a.endOffset),!1),d=o(n.getRangeDocument(a)).createTextRange(),d.setEndPoint("StartToStart",b),d.setEndPoint("EndToEnd",c),d)},d.rangeToTextRange=i,d.prototype.toTextRange=function(){return i(this)},a.WrappedTextRange=d,(!a.features.implementsDomRange||a.config.preferTextRange)&&(j=function(){return this}(),void 0===j.Range&&(j.Range=d),a.createNativeRange=function(a){return a=p(a,b,"createNativeRange"),o(a).createTextRange()},a.WrappedRange=d)),a.createRange=function(c){return c=p(c,b,"createRange"),new a.WrappedRange(a.createNativeRange(c))},a.createRangyRange=function(a){return a=p(a,b,"createRangyRange"),new n(a)},a.createIframeRange=function(c){return b.deprecationNotice("createIframeRange()","createRange(iframeEl)"),a.createRange(c)},a.createIframeRangyRange=function(c){return b.deprecationNotice("createIframeRangyRange()","createRangyRange(iframeEl)"),a.createRangyRange(c)},a.addShimListener(function(b){var c=b.document;void 0===c.createRange&&(c.createRange=function(){return a.createRange(c)}),c=b=null})}),H.createCoreModule("WrappedSelection",["DomRange","WrappedRange"],function(a,b){function c(a){return"string"==typeof a?/^backward(s)?$/i.test(a):!!a}function d(a,c){if(a){if(A.isWindow(a))return a;if(a instanceof r)return a.win;var d=A.getContentDocument(a,b,c);return A.getWindow(d)}return window}function e(a){return d(a,"getWinSelection").getSelection()}function f(a){return d(a,"getDocSelection").document.selection}function g(a){var b=!1;return a.anchorNode&&(b=1==A.comparePoints(a.anchorNode,a.anchorOffset,a.focusNode,a.focusOffset)),b}function h(a,b,c){var d=c?"end":"start",e=c?"start":"end";a.anchorNode=b[d+"Container"],a.anchorOffset=b[d+"Offset"],a.focusNode=b[e+"Container"],a.focusOffset=b[e+"Offset"]}function i(a){var b=a.nativeSelection;a.anchorNode=b.anchorNode,a.anchorOffset=b.anchorOffset,a.focusNode=b.focusNode,a.focusOffset=b.focusOffset}function j(a){a.anchorNode=a.focusNode=null,a.anchorOffset=a.focusOffset=0,a.rangeCount=0,a.isCollapsed=!0,a._ranges.length=0}function k(b){var c;return b instanceof D?(c=a.createNativeRange(b.getDocument()),c.setEnd(b.endContainer,b.endOffset),c.setStart(b.startContainer,b.startOffset)):b instanceof E?c=b.nativeRange:J.implementsDomRange&&b instanceof A.getWindow(b.startContainer).Range&&(c=b),c}function l(a){if(!a.length||1!=a[0].nodeType)return!1;for(var b=1,c=a.length;c>b;++b)if(!A.isAncestorOf(a[0],a[b]))return!1;return!0}function m(a){var c=a.getNodes();if(!l(c))throw b.createError("getSingleElementFromRange: range "+a.inspect()+" did not consist of a single element");return c[0]}function n(a){return!!a&&void 0!==a.text}function o(a,b){var c=new E(b);a._ranges=[c],h(a,c,!1),a.rangeCount=1,a.isCollapsed=c.collapsed}function p(b){var c,d,e,f;if(b._ranges.length=0,"None"==b.docSelection.type)j(b);else if(c=b.docSelection.createRange(),n(c))o(b,c);else{for(b.rangeCount=c.length,e=L(c.item(0)),f=0;f<b.rangeCount;++f)d=a.createRange(e),d.selectNode(c.item(f)),b._ranges.push(d);b.isCollapsed=1==b.rangeCount&&b._ranges[0].collapsed,h(b,b._ranges[b.rangeCount-1],!1)}}function q(a,c){var d,e,f=a.docSelection.createRange(),g=m(c),h=L(f.item(0)),i=M(h).createControlRange();for(d=0,e=f.length;e>d;++d)i.add(f.item(d));try{i.add(g)}catch(j){throw b.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)")}i.select(),p(a)}function r(a,b,c){this.nativeSelection=a,this.docSelection=b,this._ranges=[],this.win=c,this.refresh()}function s(a){a.win=a.anchorNode=a.focusNode=a._ranges=null,a.rangeCount=a.anchorOffset=a.focusOffset=0,a.detached=!0}function t(a,b){for(var c,d,e=bb.length;e--;)if(c=bb[e],d=c.selection,"deleteAll"==b)s(d);else if(c.win==a)return"delete"==b?(bb.splice(e,1),!0):d;return"deleteAll"==b&&(bb.length=0),null}function u(a,c){var d,e,f,g=L(c[0].startContainer),h=M(g).createControlRange();for(d=0,f=c.length;f>d;++d){e=m(c[d]);try{h.add(e)}catch(i){throw b.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)")}}h.select(),p(a)}function v(a,b){if(a.win.document!=L(b))throw new F("WRONG_DOCUMENT_ERR")}function w(b){return function(c,d){var e;this.rangeCount?(e=this.getRangeAt(0),e["set"+(b?"Start":"End")](c,d)):(e=a.createRange(this.win.document),e.setStartAndEnd(c,d)),this.setSingleRange(e,this.isBackward())}}function x(a){var b,c,d=[],e=new G(a.anchorNode,a.anchorOffset),f=new G(a.focusNode,a.focusOffset),g="function"==typeof a.getName?a.getName():"Selection";if(void 0!==a.rangeCount)for(b=0,c=a.rangeCount;c>b;++b)d[b]=D.inspect(a.getRangeAt(b));return"["+g+"(Ranges: "+d.join(", ")+")(anchor: "+e.inspect()+", focus: "+f.inspect()+"]"}var y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,$,_,ab,bb,cb,db,eb,fb,gb,hb;if(a.config.checkSelectionRanges=!0,y="boolean",z="number",A=a.dom,B=a.util,C=B.isHostMethod,D=a.DomRange,E=a.WrappedRange,F=a.DOMException,G=A.DomPosition,J=a.features,K="Control",L=A.getDocument,M=A.getBody,N=D.rangesEqual,O=C(window,"getSelection"),P=B.isHostObject(document,"selection"),J.implementsWinGetSelection=O,J.implementsDocSelection=P,Q=P&&(!O||a.config.preferTextRange),Q?(H=f,a.isSelectionValid=function(a){var b=d(a,"isSelectionValid").document,c=b.selection;return"None"!=c.type||L(c.createRange().parentElement())==b}):O?(H=e,a.isSelectionValid=function(){return!0}):b.fail("Neither document.selection or window.getSelection() detected."),a.getNativeSelection=H,R=H(),S=a.createNativeRange(document),T=M(document),U=B.areHostProperties(R,["anchorNode","focusNode","anchorOffset","focusOffset"]),J.selectionHasAnchorAndFocus=U,V=C(R,"extend"),J.selectionHasExtend=V,W=typeof R.rangeCount==z,J.selectionHasRangeCount=W,X=!1,Y=!0,Z=V?function(b,c){var d=D.getRangeDocument(c),e=a.createRange(d);e.collapseToPoint(c.endContainer,c.endOffset),b.addRange(k(e)),b.extend(c.startContainer,c.startOffset)}:null,B.areHostMethods(R,["addRange","getRangeAt","removeAllRanges"])&&typeof R.rangeCount==z&&J.implementsDomRange&&!function(){var b,c,d,e,f,h,i,j,k,l,m,n=window.getSelection();if(n){for(b=n.rangeCount,c=b>1,d=[],e=g(n),f=0;b>f;++f)d[f]=n.getRangeAt(f);for(h=M(document),i=h.appendChild(document.createElement("div")),i.contentEditable="false",j=i.appendChild(document.createTextNode("   ")),k=document.createRange(),k.setStart(j,1),k.collapse(!0),n.addRange(k),Y=1==n.rangeCount,n.removeAllRanges(),c||(l=window.navigator.appVersion.match(/Chrome\/(.*?) /),l&&parseInt(l[1])>=36?X=!1:(m=k.cloneRange(),k.setStart(j,0),m.setEnd(j,3),m.setStart(j,2),n.addRange(k),n.addRange(m),X=2==n.rangeCount)),h.removeChild(i),n.removeAllRanges(),f=0;b>f;++f)0==f&&e?Z?Z(n,d[f]):(a.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"),n.addRange(d[f])):n.addRange(d[f])}}(),J.selectionSupportsMultipleRanges=X,J.collapsedNonEditableSelectionsSupported=Y,$=!1,T&&C(T,"createControlRange")&&(_=T.createControlRange(),B.areHostProperties(_,["item","add"])&&($=!0)),J.implementsControlRange=$,I=U?function(a){return a.anchorNode===a.focusNode&&a.anchorOffset===a.focusOffset}:function(a){return a.rangeCount?a.getRangeAt(a.rangeCount-1).collapsed:!1},C(R,"getRangeAt")?ab=function(a,b){try{return a.getRangeAt(b)}catch(c){return null}}:U&&(ab=function(b){var c=L(b.anchorNode),d=a.createRange(c);return d.setStartAndEnd(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset),d.collapsed!==this.isCollapsed&&d.setStartAndEnd(b.focusNode,b.focusOffset,b.anchorNode,b.anchorOffset),d}),r.prototype=a.selectionPrototype,bb=[],cb=function(a){var b,c,e;return a&&a instanceof r?(a.refresh(),a):(a=d(a,"getNativeSelection"),b=t(a),c=H(a),e=P?f(a):null,b?(b.nativeSelection=c,b.docSelection=e,b.refresh()):(b=new r(c,e,a),bb.push({win:a,selection:b})),b)},a.getSelection=cb,a.getIframeSelection=function(c){return b.deprecationNotice("getIframeSelection()","getSelection(iframeEl)"),a.getSelection(A.getIframeWindow(c))},db=r.prototype,!Q&&U&&B.areHostMethods(R,["removeAllRanges","addRange"]))db.removeAllRanges=function(){this.nativeSelection.removeAllRanges(),j(this)},eb=function(a,b){Z(a.nativeSelection,b),a.refresh()},db.addRange=W?function(b,d){var e,f;$&&P&&this.docSelection.type==K?q(this,b):c(d)&&V?eb(this,b):(X?e=this.rangeCount:(this.removeAllRanges(),e=0),this.nativeSelection.addRange(k(b).cloneRange()),this.rangeCount=this.nativeSelection.rangeCount,this.rangeCount==e+1?(a.config.checkSelectionRanges&&(f=ab(this.nativeSelection,this.rangeCount-1),f&&!N(f,b)&&(b=new E(f))),this._ranges[this.rangeCount-1]=b,h(this,b,hb(this.nativeSelection)),this.isCollapsed=I(this)):this.refresh())}:function(a,b){c(b)&&V?eb(this,a):(this.nativeSelection.addRange(k(a)),this.refresh())},db.setRanges=function(a){if($&&P&&a.length>1)u(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;c>b;++b)this.addRange(a[b])}};else{if(!(C(R,"empty")&&C(S,"select")&&$&&Q))return b.fail("No means of selecting a Range or TextRange was found"),!1;db.removeAllRanges=function(){var a,b,c;try{this.docSelection.empty(),"None"!=this.docSelection.type&&(this.anchorNode?a=L(this.anchorNode):this.docSelection.type==K&&(b=this.docSelection.createRange(),b.length&&(a=L(b.item(0)))),a&&(c=M(a).createTextRange(),c.select(),this.docSelection.empty()))}catch(d){}j(this)},db.addRange=function(b){this.docSelection.type==K?q(this,b):(a.WrappedTextRange.rangeToTextRange(b).select(),this._ranges[0]=b,this.rangeCount=1,this.isCollapsed=this._ranges[0].collapsed,h(this,b,!1))},db.setRanges=function(a){this.removeAllRanges();var b=a.length;b>1?u(this,a):b&&this.addRange(a[0])}}if(db.getRangeAt=function(a){if(0>a||a>=this.rangeCount)throw new F("INDEX_SIZE_ERR");return this._ranges[a].cloneRange()},Q)fb=function(b){var c;a.isSelectionValid(b.win)?c=b.docSelection.createRange():(c=M(b.win.document).createTextRange(),c.collapse(!0)),b.docSelection.type==K?p(b):n(c)?o(b,c):j(b)};else if(C(R,"getRangeAt")&&typeof R.rangeCount==z)fb=function(b){if($&&P&&b.docSelection.type==K)p(b);else if(b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount,b.rangeCount){for(var c=0,d=b.rangeCount;d>c;++c)b._ranges[c]=new a.WrappedRange(b.nativeSelection.getRangeAt(c));h(b,b._ranges[b.rangeCount-1],hb(b.nativeSelection)),b.isCollapsed=I(b)}else j(b)};else{if(!U||typeof R.isCollapsed!=y||typeof S.collapsed!=y||!J.implementsDomRange)return b.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;fb=function(a){var b,c=a.nativeSelection;c.anchorNode?(b=ab(c,0),a._ranges=[b],a.rangeCount=1,i(a),a.isCollapsed=I(a)):j(a)}}db.refresh=function(a){var b,c=a?this._ranges.slice(0):null,d=this.anchorNode,e=this.anchorOffset;if(fb(this),a){if(b=c.length,b!=this._ranges.length)return!0;if(this.anchorNode!=d||this.anchorOffset!=e)return!0;for(;b--;)if(!N(c[b],this._ranges[b]))return!0;return!1}},gb=function(a,b){var c,d,e=a.getAllRanges();for(a.removeAllRanges(),c=0,d=e.length;d>c;++c)N(b,e[c])||a.addRange(e[c]);a.rangeCount||j(a)},db.removeRange=$&&P?function(a){var b,c,d,e,f,g,h,i;if(this.docSelection.type==K){for(b=this.docSelection.createRange(),c=m(a),d=L(b.item(0)),e=M(d).createControlRange(),g=!1,h=0,i=b.length;i>h;++h)f=b.item(h),f!==c||g?e.add(b.item(h)):g=!0;e.select(),p(this)}else gb(this,a)}:function(a){gb(this,a)},!Q&&U&&J.implementsDomRange?(hb=g,db.isBackward=function(){return hb(this)}):hb=db.isBackward=function(){return!1},db.isBackwards=db.isBackward,db.toString=function(){var a,b,c=[];for(a=0,b=this.rangeCount;b>a;++a)c[a]=""+this._ranges[a];return c.join("")},db.collapse=function(b,c){v(this,b);var d=a.createRange(b);d.collapseToPoint(b,c),this.setSingleRange(d),this.isCollapsed=!0},db.collapseToStart=function(){if(!this.rangeCount)throw new F("INVALID_STATE_ERR");var a=this._ranges[0];this.collapse(a.startContainer,a.startOffset)},db.collapseToEnd=function(){if(!this.rangeCount)throw new F("INVALID_STATE_ERR");var a=this._ranges[this.rangeCount-1];this.collapse(a.endContainer,a.endOffset)},db.selectAllChildren=function(b){v(this,b);var c=a.createRange(b);c.selectNodeContents(b),this.setSingleRange(c)},db.deleteFromDocument=function(){var a,b,c,d,e;if($&&P&&this.docSelection.type==K){for(a=this.docSelection.createRange();a.length;)b=a.item(0),a.remove(b),b.parentNode.removeChild(b);this.refresh()}else if(this.rangeCount&&(c=this.getAllRanges(),c.length)){for(this.removeAllRanges(),d=0,e=c.length;e>d;++d)c[d].deleteContents();this.addRange(c[e-1])}},db.eachRange=function(a,b){for(var c=0,d=this._ranges.length;d>c;++c)if(a(this.getRangeAt(c)))return b},db.getAllRanges=function(){var a=[];return this.eachRange(function(b){a.push(b)}),a},db.setSingleRange=function(a,b){this.removeAllRanges(),this.addRange(a,b)},db.callMethodOnEachRange=function(a,b){var c=[];return this.eachRange(function(d){c.push(d[a].apply(d,b))}),c},db.setStart=w(!0),db.setEnd=w(!1),a.rangePrototype.select=function(a){cb(this.getDocument()).setSingleRange(this,a)},db.changeEachRange=function(a){var b=[],c=this.isBackward();this.eachRange(function(c){a(c),b.push(c)}),this.removeAllRanges(),c&&1==b.length?this.addRange(b[0],"backward"):this.setRanges(b)},db.containsNode=function(a,b){return this.eachRange(function(c){return c.containsNode(a,b)},!0)||!1},db.getBookmark=function(a){return{backward:this.isBackward(),rangeBookmarks:this.callMethodOnEachRange("getBookmark",[a])}},db.moveToBookmark=function(b){var c,d,e,f=[];for(c=0;d=b.rangeBookmarks[c++];)e=a.createRange(this.win),e.moveToBookmark(d),f.push(e);b.backward?this.setSingleRange(f[0],"backward"):this.setRanges(f)},db.toHtml=function(){var a=[];return this.eachRange(function(b){a.push(D.toHtml(b))}),a.join("")},J.implementsTextRange&&(db.getNativeTextRange=function(){var c,d;if(c=this.docSelection){if(d=c.createRange(),n(d))return d;throw b.createError("getNativeTextRange: selection is a control selection")}if(this.rangeCount>0)return a.WrappedTextRange.rangeToTextRange(this.getRangeAt(0));throw b.createError("getNativeTextRange: selection contains no range")}),db.getName=function(){return"WrappedSelection"},db.inspect=function(){return x(this)},db.detach=function(){t(this.win,"delete"),s(this)},r.detachAll=function(){t(null,"deleteAll")},r.inspect=x,r.isDirectionBackward=c,a.Selection=r,a.selectionPrototype=db,a.addShimListener(function(a){void 0===a.getSelection&&(a.getSelection=function(){return cb(a)}),a=null})}),H)},this),function(a,b){"function"==typeof define&&define.amd?define(["rangy"],a):a(b.rangy)}(function(a){a.createModule("SaveRestore",["WrappedRange"],function(a,b){function c(a,b){return(b||document).getElementById(a)}function d(a,b){var c,d="selectionBoundary_"+ +new Date+"_"+(""+Math.random()).slice(2),e=o.getDocument(a.startContainer),f=a.cloneRange();return f.collapse(b),c=e.createElement("span"),c.id=d,c.style.lineHeight="0",c.style.display="none",c.className="rangySelectionBoundary",c.appendChild(e.createTextNode(p)),f.insertNode(c),c}function e(a,d,e,f){var g=c(e,a);g?(d[f?"setStartBefore":"setEndBefore"](g),g.parentNode.removeChild(g)):b.warn("Marker element has been removed. Cannot restore selection.")}function f(a,b){return b.compareBoundaryPoints(a.START_TO_START,a)}function g(b,c){var e,f,g=a.DomRange.getRangeDocument(b),h=""+b;return b.collapsed?(f=d(b,!1),{document:g,markerId:f.id,collapsed:!0}):(f=d(b,!1),e=d(b,!0),{document:g,startMarkerId:e.id,endMarkerId:f.id,collapsed:!1,backward:c,toString:function(){return"original text: '"+h+"', new text: '"+b+"'"}})}function h(d,f){var g,h,i,j=d.document;return void 0===f&&(f=!0),g=a.createRange(j),d.collapsed?(h=c(d.markerId,j),h?(h.style.display="inline",i=h.previousSibling,i&&3==i.nodeType?(h.parentNode.removeChild(h),g.collapseToPoint(i,i.length)):(g.collapseBefore(h),h.parentNode.removeChild(h))):b.warn("Marker element has been removed. Cannot restore selection.")):(e(j,g,d.startMarkerId,!0),e(j,g,d.endMarkerId,!1)),f&&g.normalizeBoundaries(),g}function i(b,d){var e,h,i,j,k=[];for(b=b.slice(0),b.sort(f),i=0,j=b.length;j>i;++i)k[i]=g(b[i],d);for(i=j-1;i>=0;--i)e=b[i],h=a.DomRange.getRangeDocument(e),e.collapsed?e.collapseAfter(c(k[i].markerId,h)):(e.setEndBefore(c(k[i].endMarkerId,h)),e.setStartAfter(c(k[i].startMarkerId,h)));return k}function j(c){var d,e,f,g;return a.isSelectionValid(c)?(d=a.getSelection(c),e=d.getAllRanges(),f=1==e.length&&d.isBackward(),g=i(e,f),f?d.setSingleRange(e[0],"backward"):d.setRanges(e),{win:c,rangeInfos:g,restored:!1}):(b.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."),null)}function k(a){var b,c=[],d=a.length;for(b=d-1;b>=0;b--)c[b]=h(a[b],!0);return c}function l(b,c){var d,e,f,g;b.restored||(d=b.rangeInfos,e=a.getSelection(b.win),f=k(d),g=d.length,1==g&&c&&a.features.selectionHasExtend&&d[0].backward?(e.removeAllRanges(),e.addRange(f[0],!0)):e.setRanges(f),b.restored=!0)}function m(a,b){var d=c(b,a);d&&d.parentNode.removeChild(d)}function n(a){var b,c,d,e=a.rangeInfos;for(b=0,c=e.length;c>b;++b)d=e[b],d.collapsed?m(a.doc,d.markerId):(m(a.doc,d.startMarkerId),m(a.doc,d.endMarkerId))}var o=a.dom,p="";a.util.extend(a,{saveRange:g,restoreRange:h,saveRanges:i,restoreRanges:k,saveSelection:j,restoreSelection:l,removeMarkerElement:m,removeMarkers:n})})},this),Base=function(){},Base.extend=function(a,b){var c,d,e,f=Base.prototype.extend;return Base._prototyping=!0,c=new this,f.call(c,a),c.base=function(){},delete Base._prototyping,d=c.constructor,e=c.constructor=function(){if(!Base._prototyping)if(this._constructing||this.constructor==e)this._constructing=!0,d.apply(this,arguments),delete this._constructing;else if(null!=arguments[0])return(arguments[0].extend||f).call(arguments[0],c)},e.ancestor=this,e.extend=this.extend,e.forEach=this.forEach,e.implement=this.implement,e.prototype=c,e.toString=this.toString,e.valueOf=function(a){return"object"==a?e:d.valueOf()},f.call(e,b),"function"==typeof e.init&&e.init(),e},Base.prototype={extend:function(a,b){var c,d,e,f,g,h,i;if(arguments.length>1)c=this[a],!c||"function"!=typeof b||c.valueOf&&c.valueOf()==b.valueOf()||!/\bbase\b/.test(b)||(d=b.valueOf(),b=function(){var a,b=this.base||Base.prototype.base;return this.base=c,a=d.apply(this,arguments),this.base=b,a},b.valueOf=function(a){return"object"==a?b:d},b.toString=Base.toString),this[a]=b;else if(a){for(e=Base.prototype.extend,Base._prototyping||"function"==typeof this||(e=this.extend||e),f={toSource:null},g=["constructor","toString","valueOf"],h=Base._prototyping?0:1;i=g[h++];)a[i]!=f[i]&&e.call(this,i,a[i]);for(i in a)f[i]||e.call(this,i,a[i])}return this}},Base=Base.extend({constructor:function(){this.extend(arguments[0])}},{ancestor:Object,version:"1.1",forEach:function(a,b,c){for(var d in a)void 0===this.prototype[d]&&b.call(c,a[d],d,a)},implement:function(){for(var a=0;a<arguments.length;a++)"function"==typeof arguments[a]?arguments[a](this.prototype):this.prototype.extend(arguments[a]);return this},toString:function(){return this.valueOf()+""}}),wysihtml5.browser=function(){function a(a){return+(/ipad|iphone|ipod/.test(a)&&a.match(/ os (\d+).+? like mac os x/)||[void 0,0])[1]}function b(a){return+(a.match(/android (\d+)/)||[void 0,0])[1]}function c(a,b){var c,d=-1;return"Microsoft Internet Explorer"==navigator.appName?c=RegExp("MSIE ([0-9]{1,}[.0-9]{0,})"):"Netscape"==navigator.appName&&(c=RegExp("Trident/.*rv:([0-9]{1,}[.0-9]{0,})")),c&&null!=c.exec(navigator.userAgent)&&(d=parseFloat(RegExp.$1)),-1===d?!1:a?b?"<"===b?d>a:">"===b?a>d:"<="===b?d>=a:">="===b?a>=d:void 0:a===d:!0}var d=navigator.userAgent,e=document.createElement("div"),f=-1!==d.indexOf("Gecko")&&-1===d.indexOf("KHTML"),g=-1!==d.indexOf("AppleWebKit/"),h=-1!==d.indexOf("Chrome/"),i=-1!==d.indexOf("Opera/");return{USER_AGENT:d,supported:function(){var c=this.USER_AGENT.toLowerCase(),d="contentEditable"in e,f=document.execCommand&&document.queryCommandSupported&&document.queryCommandState,g=document.querySelector&&document.querySelectorAll,h=this.isIos()&&a(c)<5||this.isAndroid()&&b(c)<4||-1!==c.indexOf("opera mobi")||-1!==c.indexOf("hpwos/");return d&&f&&g&&!h},isTouchDevice:function(){return this.supportsEvent("touchmove")},isIos:function(){return/ipad|iphone|ipod/i.test(this.USER_AGENT)},isAndroid:function(){return-1!==this.USER_AGENT.indexOf("Android")},supportsSandboxedIframes:function(){return c()},throwsMixedContentWarningWhenIframeSrcIsEmpty:function(){return!("querySelector"in document)},displaysCaretInEmptyContentEditableCorrectly:function(){return c()},hasCurrentStyleProperty:function(){return"currentStyle"in e},hasHistoryIssue:function(){return f&&"Mac"===navigator.platform.substr(0,3)},insertsLineBreaksOnReturn:function(){return f},supportsPlaceholderAttributeOn:function(a){return"placeholder"in a},supportsEvent:function(a){return"on"+a in e||function(){return e.setAttribute("on"+a,"return;"),"function"==typeof e["on"+a]}()},supportsEventsInIframeCorrectly:function(){return!i},supportsHTML5Tags:function(a){var b=a.createElement("div"),c="<article>foo</article>";return b.innerHTML=c,b.innerHTML.toLowerCase()===c},supportsCommand:function(){var a={formatBlock:c(10,"<="),insertUnorderedList:c(),insertOrderedList:c()},b={insertHTML:f};return function(c,d){var e=a[d];if(!e){try{return c.queryCommandSupported(d)}catch(f){}try{return c.queryCommandEnabled(d)}catch(g){return!!b[d]}}return!1}}(),doesAutoLinkingInContentEditable:function(){return c()},canDisableAutoLinking:function(){return this.supportsCommand(document,"AutoUrlDetect")},clearsContentEditableCorrectly:function(){return f||i||g},supportsGetAttributeCorrectly:function(){var a=document.createElement("td");return"1"!=a.getAttribute("rowspan")},canSelectImagesInContentEditable:function(){return f||c()||i},autoScrollsToCaret:function(){return!g},autoClosesUnclosedTags:function(){var a,b,c=e.cloneNode(!1);return c.innerHTML="<p><div></div>",b=c.innerHTML.toLowerCase(),a="<p></p><div></div>"===b||"<p><div></div></p>"===b,this.autoClosesUnclosedTags=function(){return a},a},supportsNativeGetElementsByClassName:function(){return-1!==(document.getElementsByClassName+"").indexOf("[native code]")},supportsSelectionModify:function(){return"getSelection"in window&&"modify"in window.getSelection()},needsSpaceAfterLineBreak:function(){return i},supportsSpeechApiOn:function(a){var b=d.match(/Chrome\/(\d+)/)||[void 0,0];return b[1]>=11&&("onwebkitspeechchange"in a||"speech"in a)},crashesWhenDefineProperty:function(a){return c(9)&&("XMLHttpRequest"===a||"XDomainRequest"===a)},doesAsyncFocus:function(){return c()},hasProblemsSettingCaretAfterImg:function(){return c()},hasUndoInContextMenu:function(){return f||h||i},hasInsertNodeIssue:function(){return i},hasIframeFocusIssue:function(){return c()},createsNestedInvalidMarkupAfterPaste:function(){return g},supportsMutationEvents:function(){return"MutationEvent"in window},supportsModenPaste:function(){return!("clipboardData"in window)}}}(),wysihtml5.lang.array=function(a){return{contains:function(b){if(Array.isArray(b)){for(var c=b.length;c--;)if(-1!==wysihtml5.lang.array(a).indexOf(b[c]))return!0;return!1}return-1!==wysihtml5.lang.array(a).indexOf(b)},indexOf:function(b){if(a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},without:function(b){b=wysihtml5.lang.array(b);for(var c=[],d=0,e=a.length;e>d;d++)b.contains(a[d])||c.push(a[d]);return c},get:function(){for(var b=0,c=a.length,d=[];c>b;b++)d.push(a[b]);return d},map:function(b,c){if(Array.prototype.map)return a.map(b,c);for(var d=a.length>>>0,e=Array(d),f=0;d>f;f++)e[f]=b.call(c,a[f],f,a);return e},unique:function(){for(var b=[],c=a.length,d=0;c>d;)wysihtml5.lang.array(b).contains(a[d])||b.push(a[d]),d++;return b}}},wysihtml5.lang.Dispatcher=Base.extend({on:function(a,b){return this.events=this.events||{},this.events[a]=this.events[a]||[],this.events[a].push(b),this},off:function(a,b){this.events=this.events||{};var c,d,e=0;if(a){for(c=this.events[a]||[],d=[];e<c.length;e++)c[e]!==b&&b&&d.push(c[e]);this.events[a]=d}else this.events={};return this},fire:function(a,b){this.events=this.events||{};for(var c=this.events[a]||[],d=0;d<c.length;d++)c[d].call(this,b);return this},observe:function(){return this.on.apply(this,arguments)},stopObserving:function(){return this.off.apply(this,arguments)}}),wysihtml5.lang.object=function(a){return{merge:function(b){for(var c in b)a[c]=b[c];return this},get:function(){return a},clone:function(b){var c,d={};if(null===a||!wysihtml5.lang.object(a).isPlainObject())return a;for(c in a)a.hasOwnProperty(c)&&(d[c]=b?wysihtml5.lang.object(a[c]).clone(b):a[c]);return d},isArray:function(){return"[object Array]"===Object.prototype.toString.call(a)},isFunction:function(){return"[object Function]"===Object.prototype.toString.call(a)},isPlainObject:function(){return"[object Object]"===Object.prototype.toString.call(a)}}},function(){var a=/^\s+/,b=/\s+$/,c=/[&<>\t"]/g,d={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"," ":"&nbsp; "};wysihtml5.lang.string=function(e){return e+="",{trim:function(){return e.replace(a,"").replace(b,"")},interpolate:function(a){for(var b in a)e=this.replace("#{"+b+"}").by(a[b]);return e},replace:function(a){return{by:function(b){return e.split(a).join(b)}}},escapeHTML:function(a,b){var f=e.replace(c,function(a){return d[a]});return a&&(f=f.replace(/(?:\r\n|\r|\n)/g,"<br />")),b&&(f=f.replace(/ /gi,"&nbsp; ")),f}}}}(),function(a){function b(a,b){return f(a,b)?a:(a===a.ownerDocument.documentElement&&(a=a.ownerDocument.body),g(a,b))}function c(a){return a.replace(i,function(a,b){var c,d,e=(b.match(j)||[])[1]||"",f=l[e];return b=b.replace(j,""),b.split(f).length>b.split(e).length&&(b+=e,e=""),c=b,d=b,b.length>k&&(d=d.substr(0,k)+"..."),"www."===c.substr(0,4)&&(c="http://"+c),'<a href="'+c+'">'+d+"</a>"+e})}function d(a){var b=a._wysihtml5_tempElement;return b||(b=a._wysihtml5_tempElement=a.createElement("div")),b}function e(b){var e=b.parentNode,f=a.lang.string(b.data).escapeHTML(),g=d(e.ownerDocument);for(g.innerHTML="<span></span>"+c(f),g.removeChild(g.firstChild);g.firstChild;)e.insertBefore(g.firstChild,b);e.removeChild(b)}function f(b,c){for(var d;b.parentNode;){if(b=b.parentNode,d=b.nodeName,b.className&&a.lang.array(b.className.split(" ")).contains(c))return!0;if(h.contains(d))return!0;if("body"===d)return!1}return!1}function g(b,c){if(!(h.contains(b.nodeName)||b.className&&a.lang.array(b.className.split(" ")).contains(c))){if(b.nodeType===a.TEXT_NODE&&b.data.match(i))return e(b),void 0;for(var d=a.lang.array(b.childNodes).get(),f=d.length,j=0;f>j;j++)g(d[j],c);return b}}var h=a.lang.array(["CODE","PRE","A","SCRIPT","HEAD","TITLE","STYLE"]),i=/((https?:\/\/|www\.)[^\s<]{3,})/gi,j=/([^\w\/\-](,?))$/i,k=100,l={")":"(","]":"[","}":"{"};a.dom.autoLink=b,a.dom.autoLink.URL_REG_EXP=i}(wysihtml5),function(a){var b=a.dom;b.addClass=function(a,c){var d=a.classList;return d?d.add(c):(b.hasClass(a,c)||(a.className+=" "+c),void 0)},b.removeClass=function(a,b){var c=a.classList;return c?c.remove(b):(a.className=a.className.replace(RegExp("(^|\\s+)"+b+"(\\s+|$)")," "),void 0)},b.hasClass=function(a,b){var c,d=a.classList;return d?d.contains(b):(c=a.className,c.length>0&&(c==b||RegExp("(^|\\s)"+b+"(\\s|$)").test(c)))}}(wysihtml5),wysihtml5.dom.contains=function(){var a=document.documentElement;return a.contains?function(a,b){return b.nodeType!==wysihtml5.ELEMENT_NODE&&(b=b.parentNode),a!==b&&a.contains(b)}:a.compareDocumentPosition?function(a,b){return!!(16&a.compareDocumentPosition(b))}:void 0}(),wysihtml5.dom.convertToList=function(){function a(a,b){var c=a.createElement("li");return b.appendChild(c),c}function b(a,b){return a.createElement(b)}function c(c,d,e){if("UL"===c.nodeName||"OL"===c.nodeName||"MENU"===c.nodeName)return c;var f,g,h,i,j,k,l,m,n,o=c.ownerDocument,p=b(o,d),q=c.querySelectorAll("br"),r=q.length;for(n=0;r>n;n++)for(i=q[n];(j=i.parentNode)&&j!==c&&j.lastChild===i;){if("block"===wysihtml5.dom.getStyle("display").from(j)){j.removeChild(i);break}wysihtml5.dom.insert(i).after(i.parentNode)}for(f=wysihtml5.lang.array(c.childNodes).get(),g=f.length,n=0;g>n;n++)m=m||a(o,p),h=f[n],k="block"===wysihtml5.dom.getStyle("display").from(h),l="BR"===h.nodeName,!k||e&&wysihtml5.dom.hasClass(h,e)?l?m=m.firstChild?null:m:m.appendChild(h):(m=m.firstChild?a(o,p):m,m.appendChild(h),m=null);return 0===f.length&&a(o,p),c.parentNode.replaceChild(p,c),p}return c}(),wysihtml5.dom.copyAttributes=function(a){return{from:function(b){return{to:function(c){for(var d,e=0,f=a.length;f>e;e++)d=a[e],void 0!==b[d]&&""!==b[d]&&(c[d]=b[d]);return{andTo:arguments.callee}}}}}},function(a){var b=["-webkit-box-sizing","-moz-box-sizing","-ms-box-sizing","box-sizing"],c=function(b){return d(b)?parseInt(a.getStyle("width").from(b),10)<b.offsetWidth:!1},d=function(c){for(var d=0,e=b.length;e>d;d++)if("border-box"===a.getStyle(b[d]).from(c))return b[d]};a.copyStyles=function(d){return{from:function(e){c(e)&&(d=wysihtml5.lang.array(d).without(b));for(var f,g="",h=d.length,i=0;h>i;i++)f=d[i],g+=f+":"+a.getStyle(f).from(e)+";";return{to:function(b){return a.setStyles(g).on(b),{andTo:arguments.callee}}}}}}}(wysihtml5.dom),function(a){a.dom.delegate=function(b,c,d,e){return a.dom.observe(b,d,function(d){for(var f=d.target,g=a.lang.array(b.querySelectorAll(c));f&&f!==b;){if(g.contains(f)){e.call(f,d);break}f=f.parentNode}})}}(wysihtml5),function(a){a.dom.domNode=function(b){var c=[a.ELEMENT_NODE,a.TEXT_NODE],d=function(b){return b.nodeType===a.TEXT_NODE&&/^\s*$/g.test(b.data)};return{prev:function(e){var f=b.previousSibling,g=e&&e.nodeTypes?e.nodeTypes:c;return f?!a.lang.array(g).contains(f.nodeType)||e&&e.ignoreBlankTexts&&d(f)?a.dom.domNode(f).prev(e):f:null},next:function(e){var f=b.nextSibling,g=e&&e.nodeTypes?e.nodeTypes:c;return f?!a.lang.array(g).contains(f.nodeType)||e&&e.ignoreBlankTexts&&d(f)?a.dom.domNode(f).next(e):f:null}}}}(wysihtml5),wysihtml5.dom.getAsDom=function(){var a=function(a,b){var c=b.createElement("div");c.style.display="none",b.body.appendChild(c);try{c.innerHTML=a}catch(d){}return b.body.removeChild(c),c},b=function(a){if(!a._wysihtml5_supportsHTML5Tags){for(var b=0,d=c.length;d>b;b++)a.createElement(c[b]);a._wysihtml5_supportsHTML5Tags=!0}},c=["abbr","article","aside","audio","bdi","canvas","command","datalist","details","figcaption","figure","footer","header","hgroup","keygen","mark","meter","nav","output","progress","rp","rt","ruby","svg","section","source","summary","time","track","video","wbr"];return function(c,d){d=d||document;var e;return"object"==typeof c&&c.nodeType?(e=d.createElement("div"),e.appendChild(c)):wysihtml5.browser.supportsHTML5Tags(d)?(e=d.createElement("div"),e.innerHTML=c):(b(d),e=a(c,d)),e}}(),wysihtml5.dom.getParentElement=function(){function a(a,b){return b&&b.length?"string"==typeof b?a===b:wysihtml5.lang.array(b).contains(a):!0}function b(a){return a.nodeType===wysihtml5.ELEMENT_NODE}function c(a,b,c){var d=(a.className||"").match(c)||[];return b?d[d.length-1]===b:!!d.length}function d(a,b,c){var d=(a.getAttribute("style")||"").match(c)||[];return b?d[d.length-1]===b:!!d.length}return function(e,f,g,h){var i=f.cssStyle||f.styleRegExp,j=f.className||f.classRegExp;for(g=g||50;g--&&e&&"BODY"!==e.nodeName&&(!h||e!==h);){if(b(e)&&a(e.nodeName,f.nodeName)&&(!i||d(e,f.cssStyle,f.styleRegExp))&&(!j||c(e,f.className,f.classRegExp)))return e;e=e.parentNode}return null}}(),wysihtml5.dom.getStyle=function(){function a(a){return a.replace(c,function(a){return a.charAt(1).toUpperCase()
+})}var b={"float":"styleFloat"in document.createElement("div").style?"styleFloat":"cssFloat"},c=/\-[a-z]/g;return function(c){return{from:function(d){var e,f,g,h,i,j,k,l,m;if(d.nodeType===wysihtml5.ELEMENT_NODE){if(e=d.ownerDocument,f=b[c]||a(c),g=d.style,h=d.currentStyle,i=g[f],i)return i;if(h)try{return h[f]}catch(n){}return j=e.defaultView||e.parentWindow,k=("height"===c||"width"===c)&&"TEXTAREA"===d.nodeName,j.getComputedStyle?(k&&(l=g.overflow,g.overflow="hidden"),m=j.getComputedStyle(d,null).getPropertyValue(c),k&&(g.overflow=l||""),m):void 0}}}}}(),wysihtml5.dom.getTextNodes=function(a,b){var c=[];for(a=a.firstChild;a;a=a.nextSibling)3==a.nodeType?b&&/^\s*$/.test(a.innerText||a.textContent)||c.push(a):c=c.concat(wysihtml5.dom.getTextNodes(a,b));return c},wysihtml5.dom.hasElementWithTagName=function(){function a(a){return a._wysihtml5_identifier||(a._wysihtml5_identifier=c++)}var b={},c=1;return function(c,d){var e=a(c)+":"+d,f=b[e];return f||(f=b[e]=c.getElementsByTagName(d)),f.length>0}}(),function(a){function b(a){return a._wysihtml5_identifier||(a._wysihtml5_identifier=d++)}var c={},d=1;a.dom.hasElementWithClassName=function(d,e){if(!a.browser.supportsNativeGetElementsByClassName())return!!d.querySelector("."+e);var f=b(d)+":"+e,g=c[f];return g||(g=c[f]=d.getElementsByClassName(e)),g.length>0}}(wysihtml5),wysihtml5.dom.insert=function(a){return{after:function(b){b.parentNode.insertBefore(a,b.nextSibling)},before:function(b){b.parentNode.insertBefore(a,b)},into:function(b){b.appendChild(a)}}},wysihtml5.dom.insertCSS=function(a){return a=a.join("\n"),{into:function(b){var c,d,e=b.createElement("style");return e.type="text/css",e.styleSheet?e.styleSheet.cssText=a:e.appendChild(b.createTextNode(a)),c=b.querySelector("head link"),c?(c.parentNode.insertBefore(e,c),void 0):(d=b.querySelector("head"),d&&d.appendChild(e),void 0)}}},function(a){a.dom.lineBreaks=function(b){function c(a){return"BR"===a.nodeName}function d(b){return c(b)?!0:"block"===a.dom.getStyle("display").from(b)?!0:!1}return{add:function(){var c=b.ownerDocument,e=a.dom.domNode(b).next({ignoreBlankTexts:!0}),f=a.dom.domNode(b).prev({ignoreBlankTexts:!0});e&&!d(e)&&a.dom.insert(c.createElement("br")).after(b),f&&!d(f)&&a.dom.insert(c.createElement("br")).before(b)},remove:function(){var d=a.dom.domNode(b).next({ignoreBlankTexts:!0}),e=a.dom.domNode(b).prev({ignoreBlankTexts:!0});d&&c(d)&&d.parentNode.removeChild(d),e&&c(e)&&e.parentNode.removeChild(e)}}}}(wysihtml5),wysihtml5.dom.observe=function(a,b,c){b="string"==typeof b?[b]:b;for(var d,e,f=0,g=b.length;g>f;f++)e=b[f],a.addEventListener?a.addEventListener(e,c,!1):(d=function(b){"target"in b||(b.target=b.srcElement),b.preventDefault=b.preventDefault||function(){this.returnValue=!1},b.stopPropagation=b.stopPropagation||function(){this.cancelBubble=!0},c.call(a,b)},a.attachEvent("on"+e,d));return{stop:function(){for(var e,f=0,g=b.length;g>f;f++)e=b[f],a.removeEventListener?a.removeEventListener(e,c,!1):a.detachEvent("on"+e,d)}}},wysihtml5.dom.parse=function(a,b){function c(a,b){var c,f,g,h,i,j,k,l,m;for(wysihtml5.lang.object(t).merge(s).merge(b.rules).get(),c=b.context||a.ownerDocument||document,f=c.createDocumentFragment(),g="string"==typeof a,h=!1,b.clearInternals===!0&&(h=!0),i=g?wysihtml5.dom.getAsDom(a,c):a,t.selectors&&e(i,t.selectors);i.firstChild;)k=i.firstChild,j=d(k,b.cleanUp,h,b.uneditableClass),j&&f.appendChild(j),k!==j&&i.removeChild(k);if(b.unjoinNbsps)for(l=wysihtml5.dom.getTextNodes(f),m=l.length;m--;)l[m].nodeValue=l[m].nodeValue.replace(/([\S\u00A0])\u00A0/gi,"$1 ");return i.innerHTML="",i.appendChild(f),g?wysihtml5.quirks.getCorrectInnerHTML(i):i}function d(a,b,c,e){var f,g,h,i=a.nodeType,j=a.childNodes,k=j.length,l=p[i],m=0;if(e&&1===i&&wysihtml5.dom.hasClass(a,e))return a;if(g=l&&l(a,c),!g){if(g===!1){for(f=a.ownerDocument.createDocumentFragment(),m=k;m--;)j[m]&&(h=d(j[m],b,c,e),h&&(j[m]===h&&m--,f.insertBefore(h,f.firstChild)));return"block"===wysihtml5.dom.getStyle("display").from(a)&&f.appendChild(a.ownerDocument.createElement("br")),wysihtml5.lang.array(["div","pre","p","table","td","th","ul","ol","li","dd","dl","footer","header","section","h1","h2","h3","h4","h5","h6"]).contains(a.nodeName.toLowerCase())&&a.parentNode.lastChild!==a&&(a.nextSibling&&3===a.nextSibling.nodeType&&/^\s/.test(a.nextSibling.nodeValue)||f.appendChild(a.ownerDocument.createTextNode(" "))),f.normalize&&f.normalize(),f}return null}for(m=0;k>m;m++)j[m]&&(h=d(j[m],b,c,e),h&&(j[m]===h&&m--,g.appendChild(h)));if(b&&g.nodeName.toLowerCase()===q&&(!g.childNodes.length||/^\s*$/gi.test(g.innerHTML)&&(c||"_wysihtml5-temp-placeholder"!==a.className&&"rangySelectionBoundary"!==a.className)||!g.attributes.length)){for(f=g.ownerDocument.createDocumentFragment();g.firstChild;)f.appendChild(g.firstChild);return f.normalize&&f.normalize(),f}return g.normalize&&g.normalize(),g}function e(a,b){var c,d,e,f;for(c in b)if(b.hasOwnProperty(c))for(wysihtml5.lang.object(b[c]).isFunction()?d=b[c]:"string"==typeof b[c]&&z[b[c]]&&(d=z[b[c]]),e=a.querySelectorAll(c),f=e.length;f--;)d(e[f])}function f(a,b){var c,d,e,f=t.tags,h=a.nodeName.toLowerCase(),j=a.scopeName;if(a._wysihtml5)return null;if(a._wysihtml5=1,"wysihtml5-temp"===a.className)return null;if(j&&"HTML"!=j&&(h=j+":"+h),"outerHTML"in a&&(wysihtml5.browser.autoClosesUnclosedTags()||"P"!==a.nodeName||"</p>"===a.outerHTML.slice(-4).toLowerCase()||(h="div")),h in f){if(c=f[h],!c||c.remove)return null;if(c.unwrap)return!1;c="string"==typeof c?{rename_tag:c}:c}else{if(!a.firstChild)return null;c={rename_tag:q}}if(c.one_of_type&&!g(a,t,c.one_of_type,b)){if(!c.remove_action)return null;if("unwrap"===c.remove_action)return!1;if("rename"!==c.remove_action)return null;e=c.remove_action_rename_to||q}return d=a.ownerDocument.createElement(e||c.rename_tag||h),m(a,d,c,b),i(a,d,c),a=null,d.normalize&&d.normalize(),d}function g(a,b,c,d){var e,f;if("SPAN"===a.nodeName&&!d&&("_wysihtml5-temp-placeholder"===a.className||"rangySelectionBoundary"===a.className))return!0;for(f in c)if(c.hasOwnProperty(f)&&b.type_definitions&&b.type_definitions[f]&&(e=b.type_definitions[f],h(a,e)))return!0;return!1}function h(a,b){var c,d,e,f,g,h,i,j,k=a.getAttribute("class"),l=a.getAttribute("style");if(b.methods)for(h in b.methods)if(b.methods.hasOwnProperty(h)&&y[h]&&y[h](a))return!0;if(k&&b.classes)for(k=k.replace(/^\s+/g,"").replace(/\s+$/g,"").split(r),c=k.length,i=0;c>i;i++)if(b.classes[k[i]])return!0;if(l&&b.styles){l=l.split(";");for(d in b.styles)if(b.styles.hasOwnProperty(d))for(j=l.length;j--;)if(g=l[j].split(":"),g[0].replace(/\s/g,"").toLowerCase()===d&&(b.styles[d]===!0||1===b.styles[d]||wysihtml5.lang.array(b.styles[d]).contains(g[1].replace(/\s/g,"").toLowerCase())))return!0}if(b.attrs)for(e in b.attrs)if(b.attrs.hasOwnProperty(e)&&(f=wysihtml5.dom.getAttribute(a,e),"string"==typeof f&&f.search(b.attrs[e])>-1))return!0;return!1}function i(a,b,c){var d,e;if(c&&c.keep_styles)for(d in c.keep_styles)if(c.keep_styles.hasOwnProperty(d)){if(e="float"===d?a.style.styleFloat||a.style.cssFloat:a.style[d],c.keep_styles[d]instanceof RegExp&&!c.keep_styles[d].test(e))continue;"float"===d?b.style[a.style.styleFloat?"styleFloat":"cssFloat"]=e:a.style[d]&&(b.style[d]=e)}}function j(a,b){var c,d=[];for(c in b)b.hasOwnProperty(c)&&0===c.indexOf(a)&&d.push(c);return d}function k(a,b,c,d){var e,f=v[c];return f&&(b||"alt"===a&&"IMG"==d)&&(e=f(b),"string"==typeof e)?e:!1}function l(a,b){var c,d,e,f,g,h=wysihtml5.lang.object(t.attributes||{}).clone(),i=wysihtml5.lang.object(h).merge(wysihtml5.lang.object(b||{}).clone()).get(),l={},m=wysihtml5.dom.getAttributes(a);for(c in i)if(/\*$/.test(c))for(e=j(c.slice(0,-1),m),f=0,g=e.length;g>f;f++)d=k(e[f],m[e[f]],i[c],a.nodeName),d!==!1&&(l[e[f]]=d);else d=k(c,m[c],i[c],a.nodeName),d!==!1&&(l[c]=d);return l}function m(a,b,c,d){var e,f,g,h,i,j={},k=c.set_class,m=c.add_class,n=c.add_style,o=c.set_attributes,p=t.classes,q=0,s=[],u=[],v=[],y=[];if(o&&(j=wysihtml5.lang.object(o).clone()),j=wysihtml5.lang.object(j).merge(l(a,c.check_attributes)).get(),k&&s.push(k),m)for(h in m)i=x[m[h]],i&&(g=i(wysihtml5.dom.getAttribute(a,h)),"string"==typeof g&&s.push(g));if(n)for(h in n)i=w[n[h]],i&&(newStyle=i(wysihtml5.dom.getAttribute(a,h)),"string"==typeof newStyle&&u.push(newStyle));if("string"==typeof p&&"any"===p&&a.getAttribute("class"))if(t.classes_blacklist){for(y=a.getAttribute("class"),y&&(s=s.concat(y.split(r))),e=s.length;e>q;q++)f=s[q],t.classes_blacklist[f]||v.push(f);v.length&&(j["class"]=wysihtml5.lang.array(v).unique().join(" "))}else j["class"]=a.getAttribute("class");else{for(d||(p["_wysihtml5-temp-placeholder"]=1,p._rangySelectionBoundary=1,p["wysiwyg-tmp-selected-cell"]=1),y=a.getAttribute("class"),y&&(s=s.concat(y.split(r))),e=s.length;e>q;q++)f=s[q],p[f]&&v.push(f);v.length&&(j["class"]=wysihtml5.lang.array(v).unique().join(" "))}j["class"]&&d&&(j["class"]=j["class"].replace("wysiwyg-tmp-selected-cell",""),/^\s*$/g.test(j["class"])&&delete j["class"]),u.length&&(j.style=wysihtml5.lang.array(u).unique().join(" "));for(h in j)try{b.setAttribute(h,j[h])}catch(z){}j.src&&(void 0!==j.width&&b.setAttribute("width",j.width),void 0!==j.height&&b.setAttribute("height",j.height))}function n(a){var b,c=a.nextSibling;return c&&c.nodeType===wysihtml5.TEXT_NODE?(c.data=a.data.replace(u,"")+c.data.replace(u,""),void 0):(b=a.data.replace(u,""),a.ownerDocument.createTextNode(b))}function o(a){return t.comments?a.ownerDocument.createComment(a.nodeValue):void 0}var p={1:f,3:n,8:o},q="span",r=/\s+/,s={tags:{},classes:{}},t={},u=/\uFEFF/g,v={url:function(){var a=/^https?:\/\//i;return function(b){return b&&b.match(a)?b.replace(a,function(a){return a.toLowerCase()}):null}}(),src:function(){var a=/^(\/|https?:\/\/)/i;return function(b){return b&&b.match(a)?b.replace(a,function(a){return a.toLowerCase()}):null}}(),href:function(){var a=/^(#|\/|https?:\/\/|mailto:)/i;return function(b){return b&&b.match(a)?b.replace(a,function(a){return a.toLowerCase()}):null}}(),alt:function(){var a=/[^ a-z0-9_\-]/gi;return function(b){return b?b.replace(a,""):""}}(),numbers:function(){var a=/\D/g;return function(b){return b=(b||"").replace(a,""),b||null}}(),any:function(){return function(a){return a}}()},w={align_text:function(){var a={left:"text-align: left;",right:"text-align: right;",center:"text-align: center;"};return function(b){return a[(b+"").toLowerCase()]}}()},x={align_img:function(){var a={left:"wysiwyg-float-left",right:"wysiwyg-float-right"};return function(b){return a[(b+"").toLowerCase()]}}(),align_text:function(){var a={left:"wysiwyg-text-align-left",right:"wysiwyg-text-align-right",center:"wysiwyg-text-align-center",justify:"wysiwyg-text-align-justify"};return function(b){return a[(b+"").toLowerCase()]}}(),clear_br:function(){var a={left:"wysiwyg-clear-left",right:"wysiwyg-clear-right",both:"wysiwyg-clear-both",all:"wysiwyg-clear-both"};return function(b){return a[(b+"").toLowerCase()]}}(),size_font:function(){var a={1:"wysiwyg-font-size-xx-small",2:"wysiwyg-font-size-small",3:"wysiwyg-font-size-medium",4:"wysiwyg-font-size-large",5:"wysiwyg-font-size-x-large",6:"wysiwyg-font-size-xx-large",7:"wysiwyg-font-size-xx-large","-":"wysiwyg-font-size-smaller","+":"wysiwyg-font-size-larger"};return function(b){return a[(b+"").charAt(0)]}}()},y={has_visible_contet:function(){var a,b=["img","video","picture","br","script","noscript","style","table","iframe","object","embed","audio","svg","input","button","select","textarea","canvas"];return function(c){if(a=(c.innerText||c.textContent).replace(/\s/g,""),a&&a.length>0)return!0;for(var d=b.length;d--;)if(c.querySelector(b[d]))return!0;return c.offsetWidth&&c.offsetWidth>0&&c.offsetHeight&&c.offsetHeight>0?!0:!1}}()},z={unwrap:function(a){wysihtml5.dom.unwrap(a)},remove:function(a){a.parentNode.removeChild(a)}};return c(a,b)},wysihtml5.dom.removeEmptyTextNodes=function(a){for(var b,c=wysihtml5.lang.array(a.childNodes).get(),d=c.length,e=0;d>e;e++)b=c[e],b.nodeType===wysihtml5.TEXT_NODE&&""===b.data&&b.parentNode.removeChild(b)},wysihtml5.dom.renameElement=function(a,b){for(var c,d=a.ownerDocument.createElement(b);c=a.firstChild;)d.appendChild(c);return wysihtml5.dom.copyAttributes(["align","className"]).from(a).to(d),a.parentNode.replaceChild(d,a),d},wysihtml5.dom.replaceWithChildNodes=function(a){if(a.parentNode){if(!a.firstChild)return a.parentNode.removeChild(a),void 0;for(var b=a.ownerDocument.createDocumentFragment();a.firstChild;)b.appendChild(a.firstChild);a.parentNode.replaceChild(b,a),a=b=null}},function(a){function b(b){return"block"===a.getStyle("display").from(b)}function c(a){return"BR"===a.nodeName}function d(a){var b=a.ownerDocument.createElement("br");a.appendChild(b)}function e(a,e){if(a.nodeName.match(/^(MENU|UL|OL)$/)){var f,g,h,i,j,k,l=a.ownerDocument,m=l.createDocumentFragment(),n=wysihtml5.dom.domNode(a).prev({ignoreBlankTexts:!0});if(e)for(!n||b(n)||c(n)||d(m);k=a.firstElementChild||a.firstChild;){for(g=k.lastChild;f=k.firstChild;)h=f===g,i=h&&!b(f)&&!c(f),m.appendChild(f),i&&d(m);k.parentNode.removeChild(k)}else for(;k=a.firstElementChild||a.firstChild;){if(k.querySelector&&k.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6"))for(;f=k.firstChild;)m.appendChild(f);else{for(j=l.createElement("p");f=k.firstChild;)j.appendChild(f);m.appendChild(j)}k.parentNode.removeChild(k)}a.parentNode.replaceChild(m,a)}}a.resolveList=e}(wysihtml5.dom),function(a){var b=document,c=["parent","top","opener","frameElement","frames","localStorage","globalStorage","sessionStorage","indexedDB"],d=["open","close","openDialog","showModalDialog","alert","confirm","prompt","openDatabase","postMessage","XMLHttpRequest","XDomainRequest"],e=["referrer","write","open","close"];a.dom.Sandbox=Base.extend({constructor:function(b,c){this.callback=b||a.EMPTY_FUNCTION,this.config=a.lang.object({}).merge(c).get(),this.editableArea=this._createIframe()},insertInto:function(a){"string"==typeof a&&(a=b.getElementById(a)),a.appendChild(this.editableArea)},getIframe:function(){return this.editableArea},getWindow:function(){this._readyError()},getDocument:function(){this._readyError()},destroy:function(){var a=this.getIframe();a.parentNode.removeChild(a)},_readyError:function(){throw Error("wysihtml5.Sandbox: Sandbox iframe isn't loaded yet")},_createIframe:function(){var c=this,d=b.createElement("iframe");return d.className="wysihtml5-sandbox",a.dom.setAttributes({security:"restricted",allowtransparency:"true",frameborder:0,width:0,height:0,marginwidth:0,marginheight:0}).on(d),a.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()&&(d.src="javascript:'<html></html>'"),d.onload=function(){d.onreadystatechange=d.onload=null,c._onLoadIframe(d)},d.onreadystatechange=function(){/loaded|complete/.test(d.readyState)&&(d.onreadystatechange=d.onload=null,c._onLoadIframe(d))},d},_onLoadIframe:function(f){var g,h,i,j,k,l,m;if(a.dom.contains(b.documentElement,f)){if(g=this,h=f.contentWindow,i=f.contentWindow.document,j=b.characterSet||b.charset||"utf-8",k=this._getHtml({charset:j,stylesheets:this.config.stylesheets}),i.open("text/html","replace"),i.write(k),i.close(),this.getWindow=function(){return f.contentWindow},this.getDocument=function(){return f.contentWindow.document},h.onerror=function(a,b,c){throw Error("wysihtml5.Sandbox: "+a,b,c)},!a.browser.supportsSandboxedIframes()){for(l=0,m=c.length;m>l;l++)this._unset(h,c[l]);for(l=0,m=d.length;m>l;l++)this._unset(h,d[l],a.EMPTY_FUNCTION);for(l=0,m=e.length;m>l;l++)this._unset(i,e[l]);this._unset(i,"cookie","",!0)}this.loaded=!0,setTimeout(function(){g.callback(g)},0)}},_getHtml:function(b){var c,d=b.stylesheets,e="",f=0;if(d="string"==typeof d?[d]:d,d)for(c=d.length;c>f;f++)e+='<link rel="stylesheet" href="'+d[f]+'">';return b.stylesheets=e,a.lang.string('<!DOCTYPE html><html><head><meta charset="#{charset}">#{stylesheets}</head><body></body></html>').interpolate(b)},_unset:function(b,c,d,e){try{b[c]=d}catch(f){}try{b.__defineGetter__(c,function(){return d})}catch(f){}if(e)try{b.__defineSetter__(c,function(){})}catch(f){}if(!a.browser.crashesWhenDefineProperty(c))try{var g={get:function(){return d}};e&&(g.set=function(){}),Object.defineProperty(b,c,g)}catch(f){}}})}(wysihtml5),function(a){var b=document;a.dom.ContentEditableArea=Base.extend({getContentEditable:function(){return this.element},getWindow:function(){return this.element.ownerDocument.defaultView},getDocument:function(){return this.element.ownerDocument},constructor:function(b,c,d){this.callback=b||a.EMPTY_FUNCTION,this.config=a.lang.object({}).merge(c).get(),this.element=d?this._bindElement(d):this._createElement()},_createElement:function(){var a=b.createElement("div");return a.className="wysihtml5-sandbox",this._loadElement(a),a},_bindElement:function(a){return a.className=a.className&&""!=a.className?a.className+" wysihtml5-sandbox":"wysihtml5-sandbox",this._loadElement(a,!0),a},_loadElement:function(a,b){var c,d=this;b||(c=this._getHtml(),a.innerHTML=c),this.getWindow=function(){return a.ownerDocument.defaultView},this.getDocument=function(){return a.ownerDocument},this.loaded=!0,setTimeout(function(){d.callback(d)},0)},_getHtml:function(){return""}})}(wysihtml5),function(){var a={className:"class"};wysihtml5.dom.setAttributes=function(b){return{on:function(c){for(var d in b)c.setAttribute(a[d]||d,b[d])}}}}(),wysihtml5.dom.setStyles=function(a){return{on:function(b){var c,d=b.style;if("string"==typeof a)return d.cssText+=";"+a,void 0;for(c in a)"float"===c?(d.cssFloat=a[c],d.styleFloat=a[c]):d[c]=a[c]}}},function(a){a.simulatePlaceholder=function(b,c,d){var e="placeholder",f=function(){var b=c.element.offsetWidth>0&&c.element.offsetHeight>0;c.hasPlaceholderSet()&&(c.clear(),c.element.focus(),b&&setTimeout(function(){var a=c.selection.getSelection();a.focusNode&&a.anchorNode||c.selection.selectNode(c.element.firstChild||c.element)},0)),c.placeholderSet=!1,a.removeClass(c.element,e)},g=function(){c.isEmpty()&&(c.placeholderSet=!0,c.setValue(d),a.addClass(c.element,e))};b.on("set_placeholder",g).on("unset_placeholder",f).on("focus:composer",f).on("paste:composer",f).on("blur:composer",g),g()}}(wysihtml5.dom),function(a){var b=document.documentElement;"textContent"in b?(a.setTextContent=function(a,b){a.textContent=b},a.getTextContent=function(a){return a.textContent}):"innerText"in b?(a.setTextContent=function(a,b){a.innerText=b},a.getTextContent=function(a){return a.innerText}):(a.setTextContent=function(a,b){a.nodeValue=b},a.getTextContent=function(a){return a.nodeValue})}(wysihtml5.dom),wysihtml5.dom.getAttribute=function(a,b){var c,d,e,f=!wysihtml5.browser.supportsGetAttributeCorrectly();return b=b.toLowerCase(),c=a.nodeName,"IMG"==c&&"src"==b&&wysihtml5.dom.isLoadedImage(a)===!0?a.src:f&&"outerHTML"in a?(d=a.outerHTML.toLowerCase(),e=-1!=d.indexOf(" "+b+"="),e?a.getAttribute(b):null):a.getAttribute(b)},wysihtml5.dom.getAttributes=function(a){var b,c=!wysihtml5.browser.supportsGetAttributeCorrectly(),d=a.nodeName,e=[];for(b in a.attributes)(a.attributes.hasOwnProperty&&a.attributes.hasOwnProperty(b)||!a.attributes.hasOwnProperty&&Object.prototype.hasOwnProperty.call(a.attributes,b))&&a.attributes[b].specified&&("IMG"==d&&"src"==a.attributes[b].name.toLowerCase()&&wysihtml5.dom.isLoadedImage(a)===!0?e.src=a.src:wysihtml5.lang.array(["rowspan","colspan"]).contains(a.attributes[b].name.toLowerCase())&&c?1!==a.attributes[b].value&&(e[a.attributes[b].name]=a.attributes[b].value):e[a.attributes[b].name]=a.attributes[b].value);return e},wysihtml5.dom.isLoadedImage=function(a){try{return a.complete&&!a.mozMatchesSelector(":-moz-broken")}catch(b){if(a.complete&&"complete"===a.readyState)return!0}},function(a){function b(a,b){var c,d,e,f,g=[];for(d=0,e=a.length;e>d;d++)if(c=a[d].querySelectorAll(b),c)for(f=c.length;f--;g.unshift(c[f]));return g}function d(a){a.parentNode.removeChild(a)}function e(a,b){a.parentNode.insertBefore(b,a.nextSibling)}function f(a,b){for(var c=a.nextSibling;1!=c.nodeType;)if(c=c.nextSibling,!b||b==c.tagName.toLowerCase())return c;return null}var g=a.dom,h=function(a){this.el=a,this.isColspan=!1,this.isRowspan=!1,this.firstCol=!0,this.lastCol=!0,this.firstRow=!0,this.lastRow=!0,this.isReal=!0,this.spanCollection=[],this.modified=!1},i=function(a,b){a?(this.cell=a,this.table=g.getParentElement(a,{nodeName:["TABLE"]})):b&&(this.table=b,this.cell=this.table.querySelectorAll("th, td")[0])};i.prototype={addSpannedCellToMap:function(a,b,c,d,e,f){var g,i,j=[],k=c+(f?parseInt(f,10)-1:0),l=d+(e?parseInt(e,10)-1:0);for(g=c;k>=g;g++)for(void 0===b[g]&&(b[g]=[]),i=d;l>=i;i++)b[g][i]=new h(a),b[g][i].isColspan=e&&parseInt(e,10)>1,b[g][i].isRowspan=f&&parseInt(f,10)>1,b[g][i].firstCol=i==d,b[g][i].lastCol=i==l,b[g][i].firstRow=g==c,b[g][i].lastRow=g==k,b[g][i].isReal=i==d&&g==c,b[g][i].spanCollection=j,j.push(b[g][i])},setCellAsModified:function(a){if(a.modified=!0,a.spanCollection.length>0)for(var b=0,c=a.spanCollection.length;c>b;b++)a.spanCollection[b].modified=!0},setTableMap:function(){var a,b,c,d,e,f,i,j,k=[],l=this.getTableRows();for(a=0;a<l.length;a++)for(b=l[a],c=this.getRowCells(b),f=0,void 0===k[a]&&(k[a]=[]),d=0;d<c.length;d++){for(e=c[d];void 0!==k[a][f];)f++;i=g.getAttribute(e,"colspan"),j=g.getAttribute(e,"rowspan"),i||j?(this.addSpannedCellToMap(e,k,a,f,i,j),f+=i?parseInt(i,10):1):(k[a][f]=new h(e),f++)}return this.map=k,k},getRowCells:function(c){var d=this.table.querySelectorAll("table"),e=d?b(d,"th, td"):[],f=c.querySelectorAll("th, td"),g=e.length>0?a.lang.array(f).without(e):f;return g},getTableRows:function(){var c=this.table.querySelectorAll("table"),d=c?b(c,"tr"):[],e=this.table.querySelectorAll("tr"),f=d.length>0?a.lang.array(e).without(d):e;return f},getMapIndex:function(a){var b,c,d=this.map.length,e=this.map&&this.map[0]?this.map[0].length:0;for(b=0;d>b;b++)for(c=0;e>c;c++)if(this.map[b][c].el===a)return{row:b,col:c};return!1},getElementAtIndex:function(a){return this.setTableMap(),this.map[a.row]&&this.map[a.row][a.col]&&this.map[a.row][a.col].el?this.map[a.row][a.col].el:null},getMapElsTo:function(a){var b,c,d,e,f,g,h=[];if(this.setTableMap(),this.idx_start=this.getMapIndex(this.cell),this.idx_end=this.getMapIndex(a),(this.idx_start.row>this.idx_end.row||this.idx_start.row==this.idx_end.row&&this.idx_start.col>this.idx_end.col)&&(b=this.idx_start,this.idx_start=this.idx_end,this.idx_end=b),this.idx_start.col>this.idx_end.col&&(c=this.idx_start.col,this.idx_start.col=this.idx_end.col,this.idx_end.col=c),null!=this.idx_start&&null!=this.idx_end)for(d=this.idx_start.row,e=this.idx_end.row;e>=d;d++)for(f=this.idx_start.col,g=this.idx_end.col;g>=f;f++)h.push(this.map[d][f].el);return h},orderSelectionEnds:function(a){var b,c;return this.setTableMap(),this.idx_start=this.getMapIndex(this.cell),this.idx_end=this.getMapIndex(a),(this.idx_start.row>this.idx_end.row||this.idx_start.row==this.idx_end.row&&this.idx_start.col>this.idx_end.col)&&(b=this.idx_start,this.idx_start=this.idx_end,this.idx_end=b),this.idx_start.col>this.idx_end.col&&(c=this.idx_start.col,this.idx_start.col=this.idx_end.col,this.idx_end.col=c),{start:this.map[this.idx_start.row][this.idx_start.col].el,end:this.map[this.idx_end.row][this.idx_end.col].el}},createCells:function(a,b,c){var d,e,f,g=this.table.ownerDocument,h=g.createDocumentFragment();for(e=0;b>e;e++){if(d=g.createElement(a),c)for(f in c)c.hasOwnProperty(f)&&d.setAttribute(f,c[f]);d.appendChild(document.createTextNode(" ")),h.appendChild(d)}return h},correctColIndexForUnreals:function(a,b){var c,d,e=this.map[b],f=-1;for(c=0,d=a;a>c;c++)e[c].isReal&&f++;return f},getLastNewCellOnRow:function(a,b){var c,d,e,f,g=this.getRowCells(a);for(e=0,f=g.length;f>e;e++)if(c=g[e],d=this.getMapIndex(c),d===!1||void 0!==b&&d.row!=b)return c;return null},removeEmptyTable:function(){var a=this.table.querySelectorAll("td, th");return a&&0!=a.length?!1:(d(this.table),!0)},splitRowToCells:function(a){var b,c,d;a.isColspan&&(b=parseInt(g.getAttribute(a.el,"colspan")||1,10),c=a.el.tagName.toLowerCase(),b>1&&(d=this.createCells(c,b-1),e(a.el,d)),a.el.removeAttribute("colspan"))},getRealRowEl:function(a,b){var c,d,e=null,f=null;for(b=b||this.idx,c=0,d=this.map[b.row].length;d>c;c++)if(f=this.map[b.row][c],f.isReal&&(e=g.getParentElement(f.el,{nodeName:["TR"]}),e))return e;return null===e&&a&&(e=g.getParentElement(this.map[b.row][b.col].el,{nodeName:["TR"]})||null),e},injectRowAt:function(a,b,c,d,f){var h,i,j=this.getRealRowEl(!1,{row:a,col:b}),k=this.createCells(d,c);j?(h=this.correctColIndexForUnreals(b,a),h>=0?e(this.getRowCells(j)[h],k):j.insertBefore(k,j.firstChild)):(i=this.table.ownerDocument.createElement("tr"),i.appendChild(k),e(g.getParentElement(f.el,{nodeName:["TR"]}),i))},canMerge:function(a){var b,c,d,e,f,g;for(this.to=a,this.setTableMap(),this.idx_start=this.getMapIndex(this.cell),this.idx_end=this.getMapIndex(this.to),(this.idx_start.row>this.idx_end.row||this.idx_start.row==this.idx_end.row&&this.idx_start.col>this.idx_end.col)&&(b=this.idx_start,this.idx_start=this.idx_end,this.idx_end=b),this.idx_start.col>this.idx_end.col&&(c=this.idx_start.col,this.idx_start.col=this.idx_end.col,this.idx_end.col=c),d=this.idx_start.row,e=this.idx_end.row;e>=d;d++)for(f=this.idx_start.col,g=this.idx_end.col;g>=f;f++)if(this.map[d][f].isColspan||this.map[d][f].isRowspan)return!1;return!0},decreaseCellSpan:function(a,b){var c=parseInt(g.getAttribute(a.el,b),10)-1;c>=1?a.el.setAttribute(b,c):(a.el.removeAttribute(b),"colspan"==b&&(a.isColspan=!1),"rowspan"==b&&(a.isRowspan=!1),a.firstCol=!0,a.lastCol=!0,a.firstRow=!0,a.lastRow=!0,a.isReal=!0)},removeSurplusLines:function(){var a,b,c,e,f,h,i,j;if(this.setTableMap(),this.map){for(c=0,e=this.map.length;e>c;c++){for(a=this.map[c],i=!0,f=0,h=a.length;h>f;f++)if(b=a[f],!(g.getAttribute(b.el,"rowspan")&&parseInt(g.getAttribute(b.el,"rowspan"),10)>1&&b.firstRow!==!0)){i=!1;break}if(i)for(f=0;h>f;f++)this.decreaseCellSpan(a[f],"rowspan")}for(j=this.getTableRows(),c=0,e=j.length;e>c;c++)a=j[c],0==a.childNodes.length&&/^\s*$/.test(a.textContent||a.innerText)&&d(a)}},fillMissingCells:function(){var a,b,c,d=0,f=0,g=null;if(this.setTableMap(),this.map){for(d=this.map.length,a=0;d>a;a++)this.map[a].length>f&&(f=this.map[a].length);for(b=0;d>b;b++)for(c=0;f>c;c++)this.map[b]&&!this.map[b][c]&&c>0&&(this.map[b][c]=new h(this.createCells("td",1)),g=this.map[b][c-1],g&&g.el&&g.el.parent&&e(this.map[b][c-1].el,this.map[b][c].el))}},rectify:function(){return this.removeEmptyTable()?!1:(this.removeSurplusLines(),this.fillMissingCells(),!0)},unmerge:function(){var a,b,c,d,e,f;if(this.rectify()&&(this.setTableMap(),this.idx=this.getMapIndex(this.cell),this.idx)){if(a=this.map[this.idx.row][this.idx.col],b=g.getAttribute(a.el,"colspan")?parseInt(g.getAttribute(a.el,"colspan"),10):1,c=a.el.tagName.toLowerCase(),a.isRowspan){if(d=parseInt(g.getAttribute(a.el,"rowspan"),10),d>1)for(e=1,f=d-1;f>=e;e++)this.injectRowAt(this.idx.row+e,this.idx.col,b,c,a);a.el.removeAttribute("rowspan")}this.splitRowToCells(a)}},merge:function(a){var b,c,e,f,g,h;if(this.rectify())if(this.canMerge(a)){for(b=this.idx_end.row-this.idx_start.row+1,c=this.idx_end.col-this.idx_start.col+1,e=this.idx_start.row,f=this.idx_end.row;f>=e;e++)for(g=this.idx_start.col,h=this.idx_end.col;h>=g;g++)e==this.idx_start.row&&g==this.idx_start.col?(b>1&&this.map[e][g].el.setAttribute("rowspan",b),c>1&&this.map[e][g].el.setAttribute("colspan",c)):(/^\s*<br\/?>\s*$/.test(this.map[e][g].el.innerHTML.toLowerCase())||(this.map[this.idx_start.row][this.idx_start.col].el.innerHTML+=" "+this.map[e][g].el.innerHTML),d(this.map[e][g].el));this.rectify()}else window.console&&void 0},collapseCellToNextRow:function(a){var b,c,d,f=this.getMapIndex(a.el),h=f.row+1,i={row:h,col:f.col};h<this.map.length&&(b=this.getRealRowEl(!1,i),null!==b&&(c=this.correctColIndexForUnreals(i.col,i.row),c>=0?e(this.getRowCells(b)[c],a.el):(d=this.getLastNewCellOnRow(b,h),null!==d?e(d,a.el):b.insertBefore(a.el,b.firstChild)),parseInt(g.getAttribute(a.el,"rowspan"),10)>2?a.el.setAttribute("rowspan",parseInt(g.getAttribute(a.el,"rowspan"),10)-1):a.el.removeAttribute("rowspan")))},removeRowCell:function(a){a.isReal?a.isRowspan?this.collapseCellToNextRow(a):d(a.el):parseInt(g.getAttribute(a.el,"rowspan"),10)>2?a.el.setAttribute("rowspan",parseInt(g.getAttribute(a.el,"rowspan"),10)-1):a.el.removeAttribute("rowspan")},getRowElementsByCell:function(){var a,b,c,d=[];if(this.setTableMap(),this.idx=this.getMapIndex(this.cell),this.idx!==!1)for(a=this.map[this.idx.row],b=0,c=a.length;c>b;b++)a[b].isReal&&d.push(a[b].el);return d},getColumnElementsByCell:function(){var a,b,c=[];if(this.setTableMap(),this.idx=this.getMapIndex(this.cell),this.idx!==!1)for(a=0,b=this.map.length;b>a;a++)this.map[a][this.idx.col]&&this.map[a][this.idx.col].isReal&&c.push(this.map[a][this.idx.col].el);return c},removeRow:function(){var a,b,c,e=g.getParentElement(this.cell,{nodeName:["TR"]});if(e){if(this.setTableMap(),this.idx=this.getMapIndex(this.cell),this.idx!==!1)for(a=this.map[this.idx.row],b=0,c=a.length;c>b;b++)a[b].modified||(this.setCellAsModified(a[b]),this.removeRowCell(a[b]));d(e)}},removeColCell:function(a){a.isColspan?parseInt(g.getAttribute(a.el,"colspan"),10)>2?a.el.setAttribute("colspan",parseInt(g.getAttribute(a.el,"colspan"),10)-1):a.el.removeAttribute("colspan"):a.isReal&&d(a.el)},removeColumn:function(){if(this.setTableMap(),this.idx=this.getMapIndex(this.cell),this.idx!==!1)for(var a=0,b=this.map.length;b>a;a++)this.map[a][this.idx.col].modified||(this.setCellAsModified(this.map[a][this.idx.col]),this.removeColCell(this.map[a][this.idx.col]))},remove:function(a){if(this.rectify()){switch(a){case"row":this.removeRow();break;case"column":this.removeColumn()}this.rectify()}},addRow:function(a){var b,c,d,f,h,i=this.table.ownerDocument;if(this.setTableMap(),this.idx=this.getMapIndex(this.cell),"below"==a&&g.getAttribute(this.cell,"rowspan")&&(this.idx.row=this.idx.row+parseInt(g.getAttribute(this.cell,"rowspan"),10)-1),this.idx!==!1){for(b=this.map[this.idx.row],c=i.createElement("tr"),d=0,f=b.length;f>d;d++)b[d].modified||(this.setCellAsModified(b[d]),this.addRowCell(b[d],c,a));switch(a){case"below":e(this.getRealRowEl(!0),c);break;case"above":h=g.getParentElement(this.map[this.idx.row][this.idx.col].el,{nodeName:["TR"]}),h&&h.parentNode.insertBefore(c,h)}}},addRowCell:function(a,b,d){var e=a.isColspan?{colspan:g.getAttribute(a.el,"colspan")}:null;a.isReal?"above"!=d&&a.isRowspan?a.el.setAttribute("rowspan",parseInt(g.getAttribute(a.el,"rowspan"),10)+1):b.appendChild(this.createCells("td",1,e)):"above"!=d&&a.isRowspan&&a.lastRow?b.appendChild(this.createCells("td",1,e)):c.isRowspan&&a.el.attr("rowspan",parseInt(g.getAttribute(a.el,"rowspan"),10)+1)},add:function(a){this.rectify()&&(("below"==a||"above"==a)&&this.addRow(a),("before"==a||"after"==a)&&this.addColumn(a))},addColCell:function(a,b,d){var f,h=a.el.tagName.toLowerCase();switch(d){case"before":f=!a.isColspan||a.firstCol;break;case"after":f=!a.isColspan||a.lastCol||a.isColspan&&c.el==this.cell}if(f){switch(d){case"before":a.el.parentNode.insertBefore(this.createCells(h,1),a.el);break;case"after":e(a.el,this.createCells(h,1))}a.isRowspan&&this.handleCellAddWithRowspan(a,b+1,d)}else a.el.setAttribute("colspan",parseInt(g.getAttribute(a.el,"colspan"),10)+1)},addColumn:function(a){var b,c,d,e;if(this.setTableMap(),this.idx=this.getMapIndex(this.cell),"after"==a&&g.getAttribute(this.cell,"colspan")&&(this.idx.col=this.idx.col+parseInt(g.getAttribute(this.cell,"colspan"),10)-1),this.idx!==!1)for(d=0,e=this.map.length;e>d;d++)b=this.map[d],b[this.idx.col]&&(c=b[this.idx.col],c.modified||(this.setCellAsModified(c),this.addColCell(c,d,a)))},handleCellAddWithRowspan:function(a,b,c){var d,h,i,j,k=parseInt(g.getAttribute(this.cell,"rowspan"),10)-1,l=g.getParentElement(a.el,{nodeName:["TR"]}),m=a.el.tagName.toLowerCase(),n=this.table.ownerDocument;for(j=0;k>j;j++)if(d=this.correctColIndexForUnreals(this.idx.col,b+j),l=f(l,"tr"),l)if(d>0)switch(c){case"before":h=this.getRowCells(l),d>0&&this.map[b+j][this.idx.col].el!=h[d]&&d==h.length-1?e(h[d],this.createCells(m,1)):h[d].parentNode.insertBefore(this.createCells(m,1),h[d]);
+break;case"after":e(this.getRowCells(l)[d],this.createCells(m,1))}else l.insertBefore(this.createCells(m,1),l.firstChild);else i=n.createElement("tr"),i.appendChild(this.createCells(m,1)),this.table.appendChild(i)}},g.table={getCellsBetween:function(a,b){var c=new i(a);return c.getMapElsTo(b)},addCells:function(a,b){var c=new i(a);c.add(b)},removeCells:function(a,b){var c=new i(a);c.remove(b)},mergeCellsBetween:function(a,b){var c=new i(a);c.merge(b)},unmergeCell:function(a){var b=new i(a);b.unmerge()},orderSelectionEnds:function(a,b){var c=new i(a);return c.orderSelectionEnds(b)},indexOf:function(a){var b=new i(a);return b.setTableMap(),b.getMapIndex(a)},findCell:function(a,b){var c=new i(null,a);return c.getElementAtIndex(b)},findRowByCell:function(a){var b=new i(a);return b.getRowElementsByCell()},findColumnByCell:function(a){var b=new i(a);return b.getColumnElementsByCell()},canMerge:function(a,b){var c=new i(a);return c.canMerge(b)}}}(wysihtml5),wysihtml5.dom.query=function(a,b){var c,d,e,f,g=[];for(a.nodeType&&(a=[a]),d=0,e=a.length;e>d;d++)if(c=a[d].querySelectorAll(b),c)for(f=c.length;f--;g.unshift(c[f]));return g},wysihtml5.dom.compareDocumentPosition=function(){var a=document.documentElement;return a.compareDocumentPosition?function(a,b){return a.compareDocumentPosition(b)}:function(a,b){var c,d,e,f,g,h,i,j,k;if(c=9===a.nodeType?a:a.ownerDocument,d=9===b.nodeType?b:b.ownerDocument,a===b)return 0;if(a===b.ownerDocument)return 20;if(a.ownerDocument===b)return 10;if(c!==d)return 1;if(2===a.nodeType&&a.childNodes&&-1!==wysihtml5.lang.array(a.childNodes).indexOf(b))return 20;if(2===b.nodeType&&b.childNodes&&-1!==wysihtml5.lang.array(b.childNodes).indexOf(a))return 10;for(e=a,f=[],g=null;e;){if(e==b)return 10;f.push(e),e=e.parentNode}for(e=b,g=null;e;){if(e==a)return 20;if(h=wysihtml5.lang.array(f).indexOf(e),-1!==h)return i=f[h],j=wysihtml5.lang.array(i.childNodes).indexOf(f[h-1]),k=wysihtml5.lang.array(i.childNodes).indexOf(g),j>k?2:4;g=e,e=e.parentNode}return 1}}(),wysihtml5.dom.unwrap=function(a){if(a.parentNode){for(;a.lastChild;)wysihtml5.dom.insert(a.lastChild).after(a);a.parentNode.removeChild(a)}},wysihtml5.dom.getPastedHtml=function(a){var b;return a.clipboardData&&(wysihtml5.lang.array(a.clipboardData.types).contains("text/html")?b=a.clipboardData.getData("text/html"):wysihtml5.lang.array(a.clipboardData.types).contains("text/plain")&&(b=wysihtml5.lang.string(a.clipboardData.getData("text/plain")).escapeHTML(!0,!0))),b},wysihtml5.dom.getPastedHtmlWithDiv=function(a,b){var c=a.selection.getBookmark(),d=a.element.ownerDocument,e=d.createElement("DIV");d.body.appendChild(e),e.style.width="1px",e.style.height="1px",e.style.overflow="hidden",e.setAttribute("contenteditable","true"),e.focus(),setTimeout(function(){a.selection.setBookmark(c),b(e.innerHTML),e.parentNode.removeChild(e)},0)},wysihtml5.quirks.cleanPastedHTML=function(){var a=function(a){var b=wysihtml5.lang.string(a).trim(),c=b.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&");return RegExp("^((?!^"+c+"$).)*$","i")},b=function(b,c){var d,e,f=wysihtml5.lang.object(b).clone(!0);for(d in f.tags)if(f.tags.hasOwnProperty(d)&&f.tags[d].keep_styles)for(e in f.tags[d].keep_styles)f.tags[d].keep_styles.hasOwnProperty(e)&&c[e]&&(f.tags[d].keep_styles[e]=a(c[e]));return f},c=function(a,b){var c,d,e;if(!a)return null;for(d=0,e=a.length;e>d;d++)if(a[d].condition||(c=a[d].set),a[d].condition&&a[d].condition.test(b))return a[d].set;return c};return function(a,d){var e,f={color:wysihtml5.dom.getStyle("color").from(d.referenceNode),fontSize:wysihtml5.dom.getStyle("font-size").from(d.referenceNode)},g=b(c(d.rules,a)||{},f);return e=wysihtml5.dom.parse(a,{rules:g,cleanUp:!0,context:d.referenceNode.ownerDocument,uneditableClass:d.uneditableClass,clearInternals:!0,unjoinNbsps:!0}),e}}(),wysihtml5.quirks.ensureProperClearing=function(){var a=function(){var a=this;setTimeout(function(){var b=a.innerHTML.toLowerCase();("<p>&nbsp;</p>"==b||"<p>&nbsp;</p><p>&nbsp;</p>"==b)&&(a.innerHTML="")},0)};return function(b){wysihtml5.dom.observe(b.element,["cut","keydown"],a)}}(),function(a){var b="%7E";a.quirks.getCorrectInnerHTML=function(c){var d,e,f,g,h,i=c.innerHTML;if(-1===i.indexOf(b))return i;for(d=c.querySelectorAll("[href*='~'], [src*='~']"),h=0,g=d.length;g>h;h++)e=d[h].href||d[h].src,f=a.lang.string(e).replace("~").by(b),i=a.lang.string(i).replace(f).by(e);return i}}(wysihtml5),function(a){var b="wysihtml5-quirks-redraw";a.quirks.redraw=function(c){a.dom.addClass(c,b),a.dom.removeClass(c,b);try{var d=c.ownerDocument;d.execCommand("italic",!1,null),d.execCommand("italic",!1,null)}catch(e){}}}(wysihtml5),wysihtml5.quirks.tableCellsSelection=function(a,b){function c(){return k.observe(a,"mousedown",function(a){var b=wysihtml5.dom.getParentElement(a.target,{nodeName:["TD","TH"]});b&&d(b)}),l}function d(c){l.start=c,l.end=c,l.cells=[c],l.table=k.getParentElement(l.start,{nodeName:["TABLE"]}),l.table&&(e(),k.addClass(c,m),n=k.observe(a,"mousemove",g),o=k.observe(a,"mouseup",h),b.fire("tableselectstart").fire("tableselectstart:composer"))}function e(){var b,c;if(a&&(b=a.querySelectorAll("."+m),b.length>0))for(c=0;c<b.length;c++)k.removeClass(b[c],m)}function f(a){for(var b=0;b<a.length;b++)k.addClass(a[b],m)}function g(a){var c,d=null,g=k.getParentElement(a.target,{nodeName:["TD","TH"]});g&&l.table&&l.start&&(d=k.getParentElement(g,{nodeName:["TABLE"]}),d&&d===l.table&&(e(),c=l.end,l.end=g,l.cells=k.table.getCellsBetween(l.start,g),l.cells.length>1&&b.composer.selection.deselect(),f(l.cells),l.end!==c&&b.fire("tableselectchange").fire("tableselectchange:composer")))}function h(){n.stop(),o.stop(),b.fire("tableselect").fire("tableselect:composer"),setTimeout(function(){i()},0)}function i(){var c=k.observe(a.ownerDocument,"click",function(a){c.stop(),k.getParentElement(a.target,{nodeName:["TABLE"]})!=l.table&&(e(),l.table=null,l.start=null,l.end=null,b.fire("tableunselect").fire("tableunselect:composer"))})}function j(a,c){l.start=a,l.end=c,l.table=k.getParentElement(l.start,{nodeName:["TABLE"]}),selectedCells=k.table.getCellsBetween(l.start,l.end),f(selectedCells),i(),b.fire("tableselect").fire("tableselect:composer")}var k=wysihtml5.dom,l={table:null,start:null,end:null,cells:null,select:j},m="wysiwyg-tmp-selected-cell",n=null,o=null;return c()},function(a){var b=/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,c=/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,d=/^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,e=/^#([0-9a-f])([0-9a-f])([0-9a-f])/i,f=function(a){return RegExp("(^|\\s|;)"+a+"\\s*:\\s*[^;$]+","gi")};a.quirks.styleParser={parseColor:function(g,h){var i,j,k,l=f(h),m=g.match(l),n=10;if(m){for(k=m.length;k--;)m[k]=a.lang.string(m[k].split(":")[1]).trim();if(i=m[m.length-1],b.test(i))j=i.match(b);else if(c.test(i))j=i.match(c);else if(d.test(i))j=i.match(d),n=16;else if(e.test(i))return j=i.match(e),j.shift(),j.push(1),a.lang.array(j).map(function(a,b){return 3>b?16*parseInt(a,16)+parseInt(a,16):parseFloat(a)});if(j)return j.shift(),j[3]||j.push(1),a.lang.array(j).map(function(a,b){return 3>b?parseInt(a,n):parseFloat(a)})}return!1},unparseColor:function(a,b){if(b){if("hex"==b)return a[0].toString(16).toUpperCase()+a[1].toString(16).toUpperCase()+a[2].toString(16).toUpperCase();if("hash"==b)return"#"+a[0].toString(16).toUpperCase()+a[1].toString(16).toUpperCase()+a[2].toString(16).toUpperCase();if("rgb"==b)return"rgb("+a[0]+","+a[1]+","+a[2]+")";if("rgba"==b)return"rgba("+a[0]+","+a[1]+","+a[2]+","+a[3]+")";if("csv"==b)return a[0]+","+a[1]+","+a[2]+","+a[3]}return a[3]&&1!==a[3]?"rgba("+a[0]+","+a[1]+","+a[2]+","+a[3]+")":"rgb("+a[0]+","+a[1]+","+a[2]+")"},parseFontSize:function(b){var c=b.match(f("font-size"));return c?a.lang.string(c[c.length-1].split(":")[1]).trim():!1}}}(wysihtml5),function(a){function b(a){var b=0;if(a.parentNode)do b+=a.offsetTop||0,a=a.offsetParent;while(a);return b}function c(a,b){for(var c=0;b!==a;)if(c++,b=b.parentNode,!b)throw Error("not a descendant of ancestor!");return c}function d(a){if(!a.canSurroundContents())for(var b=a.commonAncestorContainer,d=c(b,a.startContainer),e=c(b,a.endContainer);!a.canSurroundContents();)d>e?(a.setStartBefore(a.startContainer),d=c(b,a.startContainer)):(a.setEndAfter(a.endContainer),e=c(b,a.endContainer))}var e=a.dom;a.Selection=Base.extend({constructor:function(a,b,c){window.rangy.init(),this.editor=a,this.composer=a.composer,this.doc=this.composer.doc,this.contain=b,this.unselectableClass=c||!1},getBookmark:function(){var a=this.getRange();return a&&d(a),a&&a.cloneRange()},setBookmark:function(a){a&&this.setSelection(a)},setBefore:function(a){var b=rangy.createRange(this.doc);return b.setStartBefore(a),b.setEndBefore(a),this.setSelection(b)},setAfter:function(a){var b=rangy.createRange(this.doc);return b.setStartAfter(a),b.setEndAfter(a),this.setSelection(b)},selectNode:function(b,c){var d=rangy.createRange(this.doc),f=b.nodeType===a.ELEMENT_NODE,g="canHaveHTML"in b?b.canHaveHTML:"IMG"!==b.nodeName,h=f?b.innerHTML:b.data,i=""===h||h===a.INVISIBLE_SPACE,j=e.getStyle("display").from(b),k="block"===j||"list-item"===j;if(i&&f&&g&&!c)try{b.innerHTML=a.INVISIBLE_SPACE}catch(l){}g?d.selectNodeContents(b):d.selectNode(b),g&&i&&f?d.collapse(k):g&&i&&(d.setStartAfter(b),d.setEndAfter(b)),this.setSelection(d)},getSelectedNode:function(a){var b,c;return a&&this.doc.selection&&"Control"===this.doc.selection.type&&(c=this.doc.selection.createRange(),c&&c.length)?c.item(0):(b=this.getSelection(this.doc),b.focusNode===b.anchorNode?b.focusNode:(c=this.getRange(this.doc),c?c.commonAncestorContainer:this.doc.body))},fixSelBorders:function(){var a=this.getRange();d(a),this.setSelection(a)},getSelectedOwnNodes:function(){var a,b,c=this.getOwnRanges(),d=[];for(a=0,b=c.length;b>a;a++)d.push(c[a].commonAncestorContainer||this.doc.body);return d},findNodesInSelection:function(b){var c,d,e,f=this.getOwnRanges(),g=[];for(d=0,e=f.length;e>d;d++)c=f[d].getNodes([1],function(c){return a.lang.array(b).contains(c.nodeName)}),g=g.concat(c);return g},containsUneditable:function(){var a,b,c=this.getOwnUneditables(),d=this.getSelection();for(a=0,b=c.length;b>a;a++)if(d.containsNode(c[a]))return!0;return!1},deleteContents:function(){var a,b=this.getOwnRanges();for(a=b.length;a--;)b[a].deleteContents();this.setSelection(b[0])},getPreviousNode:function(b,c){var d,e,f;return b||(d=this.getSelection(),b=d.anchorNode),b===this.contain?!1:(e=b.previousSibling,e===this.contain?!1:(e&&3!==e.nodeType&&1!==e.nodeType?e=this.getPreviousNode(e,c):e&&3===e.nodeType&&/^\s*$/.test(e.textContent)?e=this.getPreviousNode(e,c):c&&e&&1===e.nodeType&&!a.lang.array(["BR","HR","IMG"]).contains(e.nodeName)&&/^[\s]*$/.test(e.innerHTML)?e=this.getPreviousNode(e,c):e||b===this.contain||(f=b.parentNode,f!==this.contain&&(e=this.getPreviousNode(f,c))),e!==this.contain?e:!1))},getSelectionParentsByTag:function(){var b,c,d,e=this.getSelectedOwnNodes(),f=[];for(c=0,d=e.length;d>c;c++)b=e[c].nodeName&&"LI"===e[c].nodeName?e[c]:a.dom.getParentElement(e[c],{nodeName:["LI"]},!1,this.contain),b&&f.push(b);return f.length?f:null},getRangeToNodeEnd:function(){if(this.isCollapsed()){var a=this.getRange(),b=a.startContainer,c=a.startOffset,d=rangy.createRange(this.doc);return d.selectNodeContents(b),d.setStart(b,c),d}},caretIsLastInSelection:function(){var a=(rangy.createRange(this.doc),this.getSelection(),this.getRangeToNodeEnd().cloneContents()),b=a.textContent;return/^\s*$/.test(b)},caretIsFirstInSelection:function(){var b=rangy.createRange(this.doc),c=this.getSelection(),d=this.getRange(),e=d.startContainer;return e.nodeType===a.TEXT_NODE?this.isCollapsed()&&e.nodeType===a.TEXT_NODE&&/^\s*$/.test(e.data.substr(0,d.startOffset)):(b.selectNodeContents(this.getRange().commonAncestorContainer),b.collapse(!0),this.isCollapsed()&&(b.startContainer===c.anchorNode||b.endContainer===c.anchorNode)&&b.startOffset===c.anchorOffset)},caretIsInTheBeginnig:function(b){var c=this.getSelection(),d=c.anchorNode,e=c.anchorOffset;return b?0===e&&(d.nodeName&&d.nodeName===b.toUpperCase()||a.dom.getParentElement(d.parentNode,{nodeName:b},1)):0===e&&!this.getPreviousNode(d,!0)},caretIsBeforeUneditable:function(){var a,b,c,d,e=this.getSelection(),f=e.anchorNode,g=e.anchorOffset;if(0===g&&(a=this.getPreviousNode(f,!0),a))for(b=this.getOwnUneditables(),c=0,d=b.length;d>c;c++)if(a===b[c])return b[c];return!1},executeAndRestoreRangy:function(a){var b=this.doc.defaultView||this.doc.parentWindow,c=rangy.saveSelection(b);if(c)try{a()}catch(d){setTimeout(function(){throw d},0)}else a();rangy.restoreSelection(c)},executeAndRestore:function(b,c){var d,f,g,h,i,j,k,l,m,n=this.doc.body,o=c&&n.scrollTop,p=c&&n.scrollLeft,q="_wysihtml5-temp-placeholder",r='<span class="'+q+'">'+a.INVISIBLE_SPACE+"</span>",s=this.getRange(!0);if(!s)return b(n,n),void 0;s.collapsed||(k=s.cloneRange(),j=k.createContextualFragment(r),k.collapse(!1),k.insertNode(j),k.detach()),i=s.createContextualFragment(r),s.insertNode(i),j&&(d=this.contain.querySelectorAll("."+q),s.setStartBefore(d[0]),s.setEndAfter(d[d.length-1])),this.setSelection(s);try{b(s.startContainer,s.endContainer)}catch(t){setTimeout(function(){throw t},0)}if(d=this.contain.querySelectorAll("."+q),d&&d.length)for(l=rangy.createRange(this.doc),g=d[0].nextSibling,d.length>1&&(h=d[d.length-1].previousSibling),h&&g?(l.setStartBefore(g),l.setEndAfter(h)):(f=this.doc.createTextNode(a.INVISIBLE_SPACE),e.insert(f).after(d[0]),l.setStartBefore(f),l.setEndAfter(f)),this.setSelection(l),m=d.length;m--;)d[m].parentNode.removeChild(d[m]);else this.contain.focus();c&&(n.scrollTop=o,n.scrollLeft=p);try{d.parentNode.removeChild(d)}catch(u){}},set:function(a,b){var c=rangy.createRange(this.doc);c.setStart(a,b||0),this.setSelection(c)},insertHTML:function(a){var b,c=(rangy.createRange(this.doc),this.doc.createElement("DIV")),d=this.doc.createDocumentFragment();for(c.innerHTML=a,b=c.lastChild;c.firstChild;)d.appendChild(c.firstChild);this.insertNode(d),b&&this.setAfter(b)},insertNode:function(a){var b=this.getRange();b&&b.insertNode(a)},surround:function(a){var b,c,d=this.getOwnRanges(),e=[];if(0==d.length)return e;for(c=d.length;c--;){b=this.doc.createElement(a.nodeName),e.push(b),a.className&&(b.className=a.className),a.cssStyle&&b.setAttribute("style",a.cssStyle);try{d[c].surroundContents(b),this.selectNode(b)}catch(f){b.appendChild(d[c].extractContents()),d[c].insertNode(b)}}return e},deblockAndSurround:function(b){var c,d,e,f=this.doc.createElement("div"),g=rangy.createRange(this.doc);if(f.className=b.className,this.composer.commands.exec("formatBlock",b.nodeName,b.className),c=this.contain.querySelectorAll("."+b.className),c[0])for(c[0].parentNode.insertBefore(f,c[0]),g.setStartBefore(c[0]),g.setEndAfter(c[c.length-1]),d=g.extractContents();d.firstChild;)if(e=d.firstChild,1==e.nodeType&&a.dom.hasClass(e,b.className)){for(;e.firstChild;)f.appendChild(e.firstChild);"BR"!==e.nodeName&&f.appendChild(this.doc.createElement("br")),d.removeChild(e)}else f.appendChild(e);else f=null;return f},scrollIntoView:function(){var c,d=this.doc,e=5,f=d.documentElement.scrollHeight>d.documentElement.offsetHeight,g=d._wysihtml5ScrollIntoViewElement=d._wysihtml5ScrollIntoViewElement||function(){var b=d.createElement("span");return b.innerHTML=a.INVISIBLE_SPACE,b}();f&&(this.insertNode(g),c=b(g),g.parentNode.removeChild(g),c>=d.body.scrollTop+d.documentElement.offsetHeight-e&&(d.body.scrollTop=c))},selectLine:function(){a.browser.supportsSelectionModify()?this._selectLine_W3C():this.doc.selection&&this._selectLine_MSIE()},_selectLine_W3C:function(){var a=this.doc.defaultView,b=a.getSelection();b.modify("move","left","lineboundary"),b.modify("extend","right","lineboundary")},_selectLine_MSIE:function(){var a,b,c,d,e,f=this.doc.selection.createRange(),g=f.boundingTop,h=this.doc.body.scrollWidth;if(f.moveToPoint){for(0===g&&(c=this.doc.createElement("span"),this.insertNode(c),g=c.offsetTop,c.parentNode.removeChild(c)),g+=1,d=-10;h>d;d+=2)try{f.moveToPoint(d,g);break}catch(i){}for(a=g,b=this.doc.selection.createRange(),e=h;e>=0;e--)try{b.moveToPoint(e,a);break}catch(j){}f.setEndPoint("EndToEnd",b),f.select()}},getText:function(){var a=this.getSelection();return a?""+a:""},getNodes:function(a,b){var c=this.getRange();return c?c.getNodes([a],b):[]},fixRangeOverflow:function(a){var b,c;this.contain&&this.contain.firstChild&&a&&(b=a.compareNode(this.contain),2!==b?(1===b&&a.setStartBefore(this.contain.firstChild),0===b&&a.setEndAfter(this.contain.lastChild),3===b&&(a.setStartBefore(this.contain.firstChild),a.setEndAfter(this.contain.lastChild))):this._detectInlineRangeProblems(a)&&(c=a.endContainer.previousElementSibling,c&&a.setEnd(c,this._endOffsetForNode(c))))},_endOffsetForNode:function(a){var b=document.createRange();return b.selectNodeContents(a),b.endOffset},_detectInlineRangeProblems:function(a){var b=e.compareDocumentPosition(a.startContainer,a.endContainer);return 0==a.endOffset&&4&b},getRange:function(a){var b=this.getSelection(),c=b&&b.rangeCount&&b.getRangeAt(0);return a!==!0&&this.fixRangeOverflow(c),c},getOwnUneditables:function(){var b=e.query(this.contain,"."+this.unselectableClass),c=e.query(b,"."+this.unselectableClass);return a.lang.array(b).without(c)},getOwnRanges:function(){var a,b,c,d,e,f,g,h=[],i=this.getRange();if(i&&h.push(i),this.unselectableClass&&this.contain&&i&&(b=this.getOwnUneditables(),b.length>0))for(d=0,e=b.length;e>d;d++)for(a=[],f=0,g=h.length;g>f;f++){if(h[f])switch(h[f].compareNode(b[d])){case 2:break;case 3:c=h[f].cloneRange(),c.setEndBefore(b[d]),a.push(c),c=h[f].cloneRange(),c.setStartAfter(b[d]),a.push(c);break;default:a.push(h[f])}h=a}return h},getSelection:function(){return rangy.getSelection(this.doc.defaultView||this.doc.parentWindow)},setSelection:function(a){var b=this.doc.defaultView||this.doc.parentWindow,c=rangy.getSelection(b);return c.setSingleRange(a)},createRange:function(){return rangy.createRange(this.doc)},isCollapsed:function(){return this.getSelection().isCollapsed},getHtml:function(){return this.getSelection().toHtml()},isEndToEndInNode:function(b){var c=this.getRange(),d=c.commonAncestorContainer,e=c.startContainer,f=c.endContainer;if(d.nodeType===a.TEXT_NODE&&(d=d.parentNode),e.nodeType===a.TEXT_NODE&&!/^\s*$/.test(e.data.substr(c.startOffset)))return!1;if(f.nodeType===a.TEXT_NODE&&!/^\s*$/.test(f.data.substr(c.endOffset)))return!1;for(;e&&e!==d;){if(e.nodeType!==a.TEXT_NODE&&!a.dom.contains(d,e))return!1;if(a.dom.domNode(e).prev({ignoreBlankTexts:!0}))return!1;e=e.parentNode}for(;f&&f!==d;){if(f.nodeType!==a.TEXT_NODE&&!a.dom.contains(d,f))return!1;if(a.dom.domNode(f).next({ignoreBlankTexts:!0}))return!1;f=f.parentNode}return a.lang.array(b).contains(d.nodeName)?d:!1},deselect:function(){var a=this.getSelection();a&&a.removeAllRanges()}})}(wysihtml5),function(a,b){function c(a,b,c){if(!a.className)return!1;var d=a.className.match(c)||[];return d[d.length-1]===b}function d(a,b){if(!a.getAttribute||!a.getAttribute("style"))return!1;a.getAttribute("style").match(b);return a.getAttribute("style").match(b)?!0:!1}function e(a,b,c){a.getAttribute("style")?(h(a,c),a.getAttribute("style")&&!/^\s*$/.test(a.getAttribute("style"))?a.setAttribute("style",b+";"+a.getAttribute("style")):a.setAttribute("style",b)):a.setAttribute("style",b)}function f(a,b,c){a.className?(g(a,c),a.className+=" "+b):a.className=b}function g(a,b){a.className&&(a.className=a.className.replace(b,""))}function h(a,b){var c,d,e=[];if(a.getAttribute("style")){for(c=a.getAttribute("style").split(";"),d=c.length;d--;)c[d].match(b)||/^\s*$/.test(c[d])||e.push(c[d]);e.length?a.setAttribute("style",e.join(";")):a.removeAttribute("style")}}function i(a,b){var c,d,e,f=[],g=b.split(";"),h=a.getAttribute("style");if(h){for(h=h.replace(/\s/gi,"").toLowerCase(),f.push(RegExp("(^|\\s|;)"+b.replace(/\s/gi,"").replace(/([\(\)])/gi,"\\$1").toLowerCase().replace(";",";?").replace(/rgb\\\((\d+),(\d+),(\d+)\\\)/gi,"\\s?rgb\\($1,\\s?$2,\\s?$3\\)"),"gi")),c=g.length;c-->0;)/^\s*$/.test(g[c])||f.push(RegExp("(^|\\s|;)"+g[c].replace(/\s/gi,"").replace(/([\(\)])/gi,"\\$1").toLowerCase().replace(";",";?").replace(/rgb\\\((\d+),(\d+),(\d+)\\\)/gi,"\\s?rgb\\($1,\\s?$2,\\s?$3\\)"),"gi"));for(d=0,e=f.length;e>d;d++)if(h.match(f[d]))return f[d]}return!1}function j(c,d,e,f){return e?i(c,e):f?a.dom.hasClass(c,f):b.dom.arrayContains(d,c.tagName.toLowerCase())}function k(a,b,c,d){for(var e=a.length;e--;)if(!j(a[e],b,c,d))return!1;return a.length?!0:!1}function l(a,b,c){var d=i(a,b);return d?(h(a,d),"remove"):(e(a,b,c),"change")}function m(a,b){return a.className.replace(u," ")==b.className.replace(u," ")}function n(a){for(var b=a.parentNode;a.firstChild;)b.insertBefore(a.firstChild,a);b.removeChild(a)}function o(a,b){if(a.attributes.length!=b.attributes.length)return!1;for(var c,d,e,f=0,g=a.attributes.length;g>f;++f)if(c=a.attributes[f],e=c.name,"class"!=e){if(d=b.attributes.getNamedItem(e),c.specified!=d.specified)return!1;if(c.specified&&c.nodeValue!==d.nodeValue)return!1}return!0}function p(a,c){return b.dom.isCharacterDataNode(a)?0==c?!!a.previousSibling:c==a.length?!!a.nextSibling:!0:c>0&&c<a.childNodes.length}function q(a,c,d,e){var f,g;if(b.dom.isCharacterDataNode(c)&&(0==d?(d=b.dom.getNodeIndex(c),c=c.parentNode):d==c.length?(d=b.dom.getNodeIndex(c)+1,c=c.parentNode):f=b.dom.splitDataNode(c,d)),!(f||e&&c===e)){for(f=c.cloneNode(!1),f.id&&f.removeAttribute("id");g=c.childNodes[d];)f.appendChild(g);b.dom.insertAfter(f,c)}return c==a?f:q(a,f.parentNode,b.dom.getNodeIndex(f),e)}function r(b){this.isElementMerge=b.nodeType==a.ELEMENT_NODE,this.firstTextNode=this.isElementMerge?b.lastChild:b,this.textNodes=[this.firstTextNode]}function s(a,b,c,d,e,f,g){this.tagNames=a||[t],this.cssClass=b||(b===!1?!1:""),this.similarClassRegExp=c,this.cssStyle=e||"",this.similarStyleRegExp=f,this.normalize=d,this.applyToAnyTagName=!1,this.container=g}var t="span",u=/\s+/g;r.prototype={doMerge:function(){var a,b,c,d,e,f=[];for(d=0,e=this.textNodes.length;e>d;++d)a=this.textNodes[d],b=a.parentNode,f[d]=a.data,d&&(b.removeChild(a),b.hasChildNodes()||b.parentNode.removeChild(b));return this.firstTextNode.data=c=f.join(""),c},getLength:function(){for(var a=this.textNodes.length,b=0;a--;)b+=this.textNodes[a].length;return b},toString:function(){var a,b,c=[];for(a=0,b=this.textNodes.length;b>a;++a)c[a]="'"+this.textNodes[a].data+"'";return"[Merge("+c.join(",")+")]"}},s.prototype={getAncestorWithClass:function(d){for(var e;d;){if(e=this.cssClass?c(d,this.cssClass,this.similarClassRegExp):""!==this.cssStyle?!1:!0,d.nodeType==a.ELEMENT_NODE&&"false"!=d.getAttribute("contenteditable")&&b.dom.arrayContains(this.tagNames,d.tagName.toLowerCase())&&e)return d;d=d.parentNode}return!1},getAncestorWithStyle:function(c){for(var e;c;){if(e=this.cssStyle?d(c,this.similarStyleRegExp):!1,c.nodeType==a.ELEMENT_NODE&&"false"!=c.getAttribute("contenteditable")&&b.dom.arrayContains(this.tagNames,c.tagName.toLowerCase())&&e)return c;c=c.parentNode}return!1},getMatchingAncestor:function(a){var b=this.getAncestorWithClass(a),c=!1;return b?this.cssStyle&&(c="class"):(b=this.getAncestorWithStyle(a),b&&(c="style")),{element:b,type:c}},postApply:function(a,b){var c,d,e,f,g,h,i=a[0],j=a[a.length-1],k=[],l=i,m=j,n=0,o=j.length;for(f=0,g=a.length;g>f;++f)d=a[f],e=null,d&&d.parentNode&&(e=this.getAdjacentMergeableTextNode(d.parentNode,!1)),e?(c||(c=new r(e),k.push(c)),c.textNodes.push(d),d===i&&(l=c.firstTextNode,n=l.length),d===j&&(m=c.firstTextNode,o=c.getLength())):c=null;if(j&&j.parentNode&&(h=this.getAdjacentMergeableTextNode(j.parentNode,!0),h&&(c||(c=new r(j),k.push(c)),c.textNodes.push(h))),k.length){for(f=0,g=k.length;g>f;++f)k[f].doMerge();b.setStart(l,n),b.setEnd(m,o)}},getAdjacentMergeableTextNode:function(b,c){var d,e=b.nodeType==a.TEXT_NODE,f=e?b.parentNode:b,g=c?"nextSibling":"previousSibling";if(e){if(d=b[g],d&&d.nodeType==a.TEXT_NODE)return d}else if(d=f[g],d&&this.areElementsMergeable(b,d))return d[c?"firstChild":"lastChild"];return null},areElementsMergeable:function(a,c){return b.dom.arrayContains(this.tagNames,(a.tagName||"").toLowerCase())&&b.dom.arrayContains(this.tagNames,(c.tagName||"").toLowerCase())&&m(a,c)&&o(a,c)},createContainer:function(a){var b=a.createElement(this.tagNames[0]);return this.cssClass&&(b.className=this.cssClass),this.cssStyle&&b.setAttribute("style",this.cssStyle),b},applyToTextNode:function(a){var c,d=a.parentNode;1==d.childNodes.length&&b.dom.arrayContains(this.tagNames,d.tagName.toLowerCase())?(this.cssClass&&f(d,this.cssClass,this.similarClassRegExp),this.cssStyle&&e(d,this.cssStyle,this.similarStyleRegExp)):(c=this.createContainer(b.dom.getDocument(a)),a.parentNode.insertBefore(c,a),c.appendChild(a))},isRemovable:function(c){return b.dom.arrayContains(this.tagNames,c.tagName.toLowerCase())&&""===a.lang.string(c.className).trim()&&(!c.getAttribute("style")||""===a.lang.string(c.getAttribute("style")).trim())},undoToTextNode:function(a,b,c,d){var e,f=c?!1:!0,h=c||d,i=!1;b.containsNode(h)||(e=b.cloneRange(),e.selectNode(h),e.isPointInRange(b.endContainer,b.endOffset)&&p(b.endContainer,b.endOffset)&&(q(h,b.endContainer,b.endOffset,this.container),b.setEndAfter(h)),e.isPointInRange(b.startContainer,b.startOffset)&&p(b.startContainer,b.startOffset)&&(h=q(h,b.startContainer,b.startOffset,this.container))),!f&&this.similarClassRegExp&&g(h,this.similarClassRegExp),f&&this.similarStyleRegExp&&(i="change"===l(h,this.cssStyle,this.similarStyleRegExp)),this.isRemovable(h)&&!i&&n(h)},applyToRange:function(b){var c,d,e,f,g,h;for(d=b.length;d--;){if(c=b[d].getNodes([a.TEXT_NODE]),!c.length)try{return e=this.createContainer(b[d].endContainer.ownerDocument),b[d].surroundContents(e),this.selectNode(b[d],e),void 0}catch(i){}if(b[d].splitBoundaries(),c=b[d].getNodes([a.TEXT_NODE]),c.length){for(g=0,h=c.length;h>g;++g)f=c[g],this.getMatchingAncestor(f).element||this.applyToTextNode(f);b[d].setStart(c[0],0),f=c[c.length-1],b[d].setEnd(f,f.length),this.normalize&&this.postApply(c,b[d])}}},undoToRange:function(b){var c,d,e,f,g,h,i,j;for(f=b.length;f--;){for(c=b[f].getNodes([a.TEXT_NODE]),c.length?(b[f].splitBoundaries(),c=b[f].getNodes([a.TEXT_NODE])):(g=b[f].endContainer.ownerDocument,h=g.createTextNode(a.INVISIBLE_SPACE),b[f].insertNode(h),b[f].selectNode(h),c=[h]),i=0,j=c.length;j>i;++i)b[f].isValid()&&(d=c[i],e=this.getMatchingAncestor(d),"style"===e.type?this.undoToTextNode(d,b[f],!1,e.element):e.element&&this.undoToTextNode(d,b[f],e.element));1==j?this.selectNode(b[f],c[0]):(b[f].setStart(c[0],0),d=c[c.length-1],b[f].setEnd(d,d.length),this.normalize&&this.postApply(c,b[f]))}},selectNode:function(b,c){var d=c.nodeType===a.ELEMENT_NODE,e="canHaveHTML"in c?c.canHaveHTML:!0,f=d?c.innerHTML:c.data,g=""===f||f===a.INVISIBLE_SPACE;if(g&&d&&e)try{c.innerHTML=a.INVISIBLE_SPACE}catch(h){}b.selectNodeContents(c),g&&d?b.collapse(!1):g&&(b.setStartAfter(c),b.setEndAfter(c))},getTextSelectedByRange:function(a,b){var c,d,e=b.cloneRange();return e.selectNodeContents(a),c=e.intersection(b),d=c?""+c:"",e.detach(),d},isAppliedToRange:function(b){var c,d,e,f,g,h,i=[],j="full";for(e=b.length;e--;){if(d=b[e].getNodes([a.TEXT_NODE]),!d.length)return c=this.getMatchingAncestor(b[e].startContainer).element,c?{elements:[c],coverage:j}:!1;for(f=0,g=d.length;g>f;++f)h=this.getTextSelectedByRange(d[f],b[e]),c=this.getMatchingAncestor(d[f]).element,c&&""!=h?(i.push(c),1===a.dom.getTextNodes(c,!0).length?j="full":"full"===j&&(j="inline")):c||(j="partial")}return i.length?{elements:i,coverage:j}:!1},toggleRange:function(a){var b,c=this.isAppliedToRange(a);c?"full"===c.coverage?this.undoToRange(a):"inline"===c.coverage?(b=k(c.elements,this.tagNames,this.cssStyle,this.cssClass),this.undoToRange(a),b||this.applyToRange(a)):(k(c.elements,this.tagNames,this.cssStyle,this.cssClass)||this.undoToRange(a),this.applyToRange(a)):this.applyToRange(a)}},a.selection.HTMLApplier=s}(wysihtml5,rangy),wysihtml5.Commands=Base.extend({constructor:function(a){this.editor=a,this.composer=a.composer,this.doc=this.composer.doc},support:function(a){return wysihtml5.browser.supportsCommand(this.doc,a)},exec:function(a,b){var c=wysihtml5.commands[a],d=wysihtml5.lang.array(arguments).get(),e=c&&c.exec,f=null;if(this.editor.fire("beforecommand:composer"),e)d.unshift(this.composer),f=e.apply(c,d);else try{f=this.doc.execCommand(a,!1,b)}catch(g){}return this.editor.fire("aftercommand:composer"),f},state:function(a){var b=wysihtml5.commands[a],c=wysihtml5.lang.array(arguments).get(),d=b&&b.state;if(d)return c.unshift(this.composer),d.apply(b,c);try{return this.doc.queryCommandState(a)}catch(e){return!1}},stateValue:function(a){var b=wysihtml5.commands[a],c=wysihtml5.lang.array(arguments).get(),d=b&&b.stateValue;return d?(c.unshift(this.composer),d.apply(b,c)):!1}}),wysihtml5.commands.bold={exec:function(a,b){wysihtml5.commands.formatInline.execWithToggle(a,b,"b")},state:function(a,b){return wysihtml5.commands.formatInline.state(a,b,"b")}},function(a){function b(b,c){var g,h,i,j,k,l,m,n,o,p=b.doc,q="_wysihtml5-temp-"+ +new Date,r=/non-matching-class/g,s=0;for(a.commands.formatInline.exec(b,d,e,q,r,d,d,!0,!0),h=p.querySelectorAll(e+"."+q),g=h.length;g>s;s++){i=h[s],i.removeAttribute("class");for(o in c)"text"!==o&&i.setAttribute(o,c[o])}l=i,1===g&&(m=f.getTextContent(i),j=!!i.querySelector("*"),k=""===m||m===a.INVISIBLE_SPACE,!j&&k&&(f.setTextContent(i,c.text||i.href),n=p.createTextNode(" "),b.selection.setAfter(i),f.insert(n).after(i),l=n)),b.selection.setAfter(l)}function c(a,b,c){var d,e,f,g;for(e=b.length;e--;){for(d=b[e].attributes,f=d.length;f--;)b[e].removeAttribute(d.item(f).name);for(g in c)c.hasOwnProperty(g)&&b[e].setAttribute(g,c[g])}}var d,e="A",f=a.dom;a.commands.createLink={exec:function(a,d,e){var f=this.state(a,d);f?a.selection.executeAndRestore(function(){c(a,f,e)}):(e="object"==typeof e?e:{href:e},b(a,e))},state:function(b,c){return a.commands.formatInline.state(b,c,"A")}}}(wysihtml5),function(a){function b(a,b){for(var d,e,f,g=b.length,h=0;g>h;h++)d=b[h],e=c.getParentElement(d,{nodeName:"code"}),f=c.getTextContent(d),f.match(c.autoLink.URL_REG_EXP)&&!e?e=c.renameElement(d,"code"):c.replaceWithChildNodes(d)}var c=a.dom;a.commands.removeLink={exec:function(a,c){var d=this.state(a,c);d&&a.selection.executeAndRestore(function(){b(a,d)})},state:function(b,c){return a.commands.formatInline.state(b,c,"A")}}}(wysihtml5),function(a){var b=/wysiwyg-font-size-[0-9a-z\-]+/g;a.commands.fontSize={exec:function(c,d,e){a.commands.formatInline.execWithToggle(c,d,"span","wysiwyg-font-size-"+e,b)},state:function(c,d,e){return a.commands.formatInline.state(c,d,"span","wysiwyg-font-size-"+e,b)}}}(wysihtml5),function(a){var b=/(\s|^)font-size\s*:\s*[^;\s]+;?/gi;a.commands.fontSizeStyle={exec:function(c,d,e){e="object"==typeof e?e.size:e,/^\s*$/.test(e)||a.commands.formatInline.execWithToggle(c,d,"span",!1,!1,"font-size:"+e,b)},state:function(c,d){return a.commands.formatInline.state(c,d,"span",!1,!1,"font-size",b)},stateValue:function(b,c){var d,e=this.state(b,c);return e&&a.lang.object(e).isArray()&&(e=e[0]),e&&(d=e.getAttribute("style"),d)?a.quirks.styleParser.parseFontSize(d):!1}}}(wysihtml5),function(a){var b=/wysiwyg-color-[0-9a-z]+/g;a.commands.foreColor={exec:function(c,d,e){a.commands.formatInline.execWithToggle(c,d,"span","wysiwyg-color-"+e,b)},state:function(c,d,e){return a.commands.formatInline.state(c,d,"span","wysiwyg-color-"+e,b)}}}(wysihtml5),function(a){var b=/(\s|^)color\s*:\s*[^;\s]+;?/gi;a.commands.foreColorStyle={exec:function(c,d,e){var f,g=a.quirks.styleParser.parseColor("object"==typeof e?"color:"+e.color:"color:"+e,"color");g&&(f="color: rgb("+g[0]+","+g[1]+","+g[2]+");",1!==g[3]&&(f+="color: rgba("+g[0]+","+g[1]+","+g[2]+","+g[3]+");"),a.commands.formatInline.execWithToggle(c,d,"span",!1,!1,f,b))},state:function(c,d){return a.commands.formatInline.state(c,d,"span",!1,!1,"color",b)},stateValue:function(b,c,d){var e,f=this.state(b,c);return f&&a.lang.object(f).isArray()&&(f=f[0]),f&&(e=f.getAttribute("style"),e&&e)?(val=a.quirks.styleParser.parseColor(e,"color"),a.quirks.styleParser.unparseColor(val,d)):!1
+}}}(wysihtml5),function(a){var b=/(\s|^)background-color\s*:\s*[^;\s]+;?/gi;a.commands.bgColorStyle={exec:function(c,d,e){var f,g=a.quirks.styleParser.parseColor("object"==typeof e?"background-color:"+e.color:"background-color:"+e,"background-color");g&&(f="background-color: rgb("+g[0]+","+g[1]+","+g[2]+");",1!==g[3]&&(f+="background-color: rgba("+g[0]+","+g[1]+","+g[2]+","+g[3]+");"),a.commands.formatInline.execWithToggle(c,d,"span",!1,!1,f,b))},state:function(c,d){return a.commands.formatInline.state(c,d,"span",!1,!1,"background-color",b)},stateValue:function(b,c,d){var e,f=this.state(b,c),g=!1;return f&&a.lang.object(f).isArray()&&(f=f[0]),f&&(e=f.getAttribute("style"),e)?(g=a.quirks.styleParser.parseColor(e,"background-color"),a.quirks.styleParser.unparseColor(g,d)):!1}}}(wysihtml5),function(a){function b(b,c,e){b.className?(d(b,e),b.className=a.lang.string(b.className+" "+c).trim()):b.className=c}function c(b,c,d){e(b,d),b.getAttribute("style")?b.setAttribute("style",a.lang.string(b.getAttribute("style")+" "+c).trim()):b.setAttribute("style",c)}function d(b,c){var d=c.test(b.className);return b.className=b.className.replace(c,""),""==a.lang.string(b.className).trim()&&b.removeAttribute("class"),d}function e(b,c){var d=c.test(b.getAttribute("style"));return b.setAttribute("style",(b.getAttribute("style")||"").replace(c,"")),""==a.lang.string(b.getAttribute("style")||"").trim()&&b.removeAttribute("style"),d}function f(a){var b=a.lastChild;b&&g(b)&&b.parentNode.removeChild(b)}function g(a){return"BR"===a.nodeName}function h(b,c){var d,e,g;for(b.selection.isCollapsed()&&b.selection.selectLine(),d=b.selection.surround(c),e=0,g=d.length;g>e;e++)a.dom.lineBreaks(d[e]).remove(),f(d[e])}function i(b){return!!a.lang.string(b.className).trim()}function j(b){return!!a.lang.string(b.getAttribute("style")||"").trim()}var k=a.dom,l=["H1","H2","H3","H4","H5","H6","P","PRE","DIV"];a.commands.formatBlock={exec:function(f,g,m,n,o,p,q){var r,s,t,u,v,w=(f.doc,this.state(f,g,m,n,o,p,q)),x=f.config.useLineBreaks,y=x?"DIV":"P";return m="string"==typeof m?m.toUpperCase():m,w.length?(f.selection.executeAndRestoreRangy(function(){var b,c,f;for(b=w.length;b--;){if(o&&(s=d(w[b],o)),q&&(u=e(w[b],q)),(u||s)&&null===m&&w[b].nodeName!=y)return;c=i(w[b]),f=j(w[b]),c||f||!x&&"P"!==m?k.renameElement(w[b],"P"===m?"DIV":y):(a.dom.lineBreaks(w[b]).add(),k.replaceWithChildNodes(w[b]))}}),void 0):((null!==m&&!a.lang.array(l).contains(m)||(r=f.selection.findNodesInSelection(l).concat(f.selection.getSelectedOwnNodes()),f.selection.executeAndRestoreRangy(function(){for(var a=r.length;a--;)v=k.getParentElement(r[a],{nodeName:l}),v==f.element&&(v=null),v&&(m&&(v=k.renameElement(v,m)),n&&b(v,n,o),p&&c(v,p,q),t=!0)}),!t))&&h(f,{nodeName:m||y,className:n||null,cssStyle:p||null}),void 0)},state:function(b,c,d,e,f,g,h){var i,j,l,m=b.selection.getSelectedOwnNodes(),n=[];for(d="string"==typeof d?d.toUpperCase():d,j=0,l=m.length;l>j;j++)i=k.getParentElement(m[j],{nodeName:d,className:e,classRegExp:f,cssStyle:g,styleRegExp:h}),i&&-1==a.lang.array(n).indexOf(i)&&n.push(i);return 0==n.length?!1:n}}}(wysihtml5),wysihtml5.commands.formatCode={exec:function(a,b,c){var d,e,f,g=this.state(a);g?a.selection.executeAndRestore(function(){d=g.querySelector("code"),wysihtml5.dom.replaceWithChildNodes(g),d&&wysihtml5.dom.replaceWithChildNodes(d)}):(e=a.selection.getRange(),f=e.extractContents(),g=a.doc.createElement("pre"),d=a.doc.createElement("code"),c&&(d.className=c),g.appendChild(d),d.appendChild(f),e.insertNode(g),a.selection.selectNode(g))},state:function(a){var b=a.selection.getSelectedNode();return b&&b.nodeName&&"PRE"==b.nodeName&&b.firstChild&&b.firstChild.nodeName&&"CODE"==b.firstChild.nodeName?b:wysihtml5.dom.getParentElement(b,{nodeName:"CODE"})&&wysihtml5.dom.getParentElement(b,{nodeName:"PRE"})}},function(a){function b(a){var b=d[a];return b?[a.toLowerCase(),b.toLowerCase()]:[a.toLowerCase()]}function c(c,d,f,g,h,i){var j=c;return d&&(j+=":"+d),g&&(j+=":"+g),e[j]||(e[j]=new a.selection.HTMLApplier(b(c),d,f,!0,g,h,i)),e[j]}var d={strong:"b",em:"i",b:"strong",i:"em"},e={};a.commands.formatInline={exec:function(a,b,d,e,f,g,h,i,j){var k=a.selection.createRange(),l=a.selection.getOwnRanges();return l&&0!=l.length?(a.selection.getSelection().removeAllRanges(),c(d,e,f,g,h,a.element).toggleRange(l),i?j||a.cleanUp():(k.setStart(l[0].startContainer,l[0].startOffset),k.setEnd(l[l.length-1].endContainer,l[l.length-1].endOffset),a.selection.setSelection(k),a.selection.executeAndRestore(function(){j||a.cleanUp()},!0,!0)),void 0):!1},execWithToggle:function(b,c,d,e,f,g,h){var i,j=this;this.state(b,c,d,e,f,g,h)&&b.selection.isCollapsed()&&!b.selection.caretIsLastInSelection()&&!b.selection.caretIsFirstInSelection()?(i=j.state(b,c,d,e,f)[0],b.selection.executeAndRestoreRangy(function(){i.parentNode;b.selection.selectNode(i,!0),a.commands.formatInline.exec(b,c,d,e,f,g,h,!0,!0)})):this.state(b,c,d,e,f,g,h)&&!b.selection.isCollapsed()?b.selection.executeAndRestoreRangy(function(){a.commands.formatInline.exec(b,c,d,e,f,g,h,!0,!0)}):a.commands.formatInline.exec(b,c,d,e,f,g,h)},state:function(b,e,f,g,h,i,j){var k,l,m=b.doc,n=d[f]||f;return a.dom.hasElementWithTagName(m,f)||a.dom.hasElementWithTagName(m,n)?g&&!a.dom.hasElementWithClassName(m,g)?!1:(k=b.selection.getOwnRanges(),k&&0!==k.length?(l=c(f,g,h,i,j,b.element).isAppliedToRange(k),l&&l.elements?l.elements:!1):!1):!1}}}(wysihtml5),function(a){a.commands.insertBlockQuote={exec:function(b,c){var d=this.state(b,c),e=b.selection.isEndToEndInNode(["H1","H2","H3","H4","H5","H6","P"]);b.selection.executeAndRestore(function(){if(d)b.config.useLineBreaks&&a.dom.lineBreaks(d).add(),a.dom.unwrap(d);else if(b.selection.isCollapsed()&&b.selection.selectLine(),e){var c=e.ownerDocument.createElement("blockquote");a.dom.insert(c).after(e),c.appendChild(e)}else b.selection.surround({nodeName:"blockquote"})})},state:function(b){var c=b.selection.getSelectedNode(),d=a.dom.getParentElement(c,{nodeName:"BLOCKQUOTE"},!1,b.element);return d?d:!1}}}(wysihtml5),wysihtml5.commands.insertHTML={exec:function(a,b,c){a.commands.support(b)?a.doc.execCommand(b,!1,c):a.selection.insertHTML(c)},state:function(){return!1}},function(a){var b="IMG";a.commands.insertImage={exec:function(c,d,e){var f,g,h,i,j;if(e="object"==typeof e?e:{src:e},f=c.doc,g=this.state(c),g)return c.selection.setBefore(g),i=g.parentNode,i.removeChild(g),a.dom.removeEmptyTextNodes(i),"A"!==i.nodeName||i.firstChild||(c.selection.setAfter(i),i.parentNode.removeChild(i)),a.quirks.redraw(c.element),void 0;g=f.createElement(b);for(j in e)g.setAttribute("className"===j?"class":j,e[j]);c.selection.insertNode(g),a.browser.hasProblemsSettingCaretAfterImg()?(h=f.createTextNode(a.INVISIBLE_SPACE),c.selection.insertNode(h),c.selection.setAfter(h)):c.selection.setAfter(g)},state:function(c){var d,e,f,g=c.doc;return a.dom.hasElementWithTagName(g,b)?(d=c.selection.getSelectedNode(),d?d.nodeName===b?d:d.nodeType!==a.ELEMENT_NODE?!1:(e=c.selection.getText(),e=a.lang.string(e).trim(),e?!1:(f=c.selection.getNodes(a.ELEMENT_NODE,function(a){return"IMG"===a.nodeName}),1!==f.length?!1:f[0])):!1):!1}}}(wysihtml5),function(a){var b="<br>"+(a.browser.needsSpaceAfterLineBreak()?" ":"");a.commands.insertLineBreak={exec:function(c,d){c.commands.support(d)?(c.doc.execCommand(d,!1,null),a.browser.autoScrollsToCaret()||c.selection.scrollIntoView()):c.commands.exec("insertHTML",b)},state:function(){return!1}}}(wysihtml5),wysihtml5.commands.insertOrderedList={exec:function(a,b){wysihtml5.commands.insertList.exec(a,b,"OL")},state:function(a,b){return wysihtml5.commands.insertList.state(a,b,"OL")}},wysihtml5.commands.insertUnorderedList={exec:function(a,b){wysihtml5.commands.insertList.exec(a,b,"UL")},state:function(a,b){return wysihtml5.commands.insertList.state(a,b,"UL")}},wysihtml5.commands.insertList=function(a){var b=function(a,b){if(a&&a.nodeName){"string"==typeof b&&(b=[b]);for(var c=b.length;c--;)if(a.nodeName===b[c])return!0}return!1},c=function(c,d,e){var f,g,h={el:null,other:!1};return c&&(f=a.dom.getParentElement(c,{nodeName:"LI"}),g="UL"===d?"OL":"UL",b(c,d)?h.el=c:b(c,g)?h={el:c,other:!0}:f&&(b(f.parentNode,d)?h.el=f.parentNode:b(f.parentNode,g)&&(h={el:f.parentNode,other:!0}))),h.el&&!e.element.contains(h.el)&&(h.el=null),h},d=function(b,c,d){var e,g="UL"===c?"OL":"UL";d.selection.executeAndRestore(function(){var h,i,j=f(g,d);if(j.length)for(h=j.length;h--;)a.dom.renameElement(j[h],c.toLowerCase());else{for(e=f(["OL","UL"],d),i=e.length;i--;)a.dom.resolveList(e[i],d.config.useLineBreaks);a.dom.resolveList(b,d.config.useLineBreaks)}})},e=function(b,c,d){var e="UL"===c?"OL":"UL";d.selection.executeAndRestore(function(){var g,h=[b].concat(f(e,d));for(g=h.length;g--;)a.dom.renameElement(h[g],c.toLowerCase())})},f=function(a,c){var d,e=c.selection.getOwnRanges(),f=[];for(d=e.length;d--;)f=f.concat(e[d].getNodes([1],function(c){return b(c,a)}));return f},g=function(b,c){c.selection.executeAndRestoreRangy(function(){var d,e,f="_wysihtml5-temp-"+(new Date).getTime(),g=c.selection.deblockAndSurround({nodeName:"div",className:f}),h=/\uFEFF/g;g.innerHTML=g.innerHTML.replace(h,""),g&&(d=a.lang.array(["","<br>",a.INVISIBLE_SPACE]).contains(g.innerHTML),e=a.dom.convertToList(g,b.toLowerCase(),c.parent.config.uneditableContainerClassname),d&&c.selection.selectNode(e.querySelector("li"),!0))})};return{exec:function(a,b,f){var h=a.doc,i="OL"===f?"insertOrderedList":"insertUnorderedList",j=a.selection.getSelectedNode(),k=c(j,f,a);k.el?k.other?e(k.el,f,a):d(k.el,f,a):a.commands.support(i)?h.execCommand(i,!1,null):g(f,a)},state:function(a,b,d){var e=a.selection.getSelectedNode(),f=c(e,d,a);return f.el&&!f.other?f.el:!1}}}(wysihtml5),wysihtml5.commands.italic={exec:function(a,b){wysihtml5.commands.formatInline.execWithToggle(a,b,"i")},state:function(a,b){return wysihtml5.commands.formatInline.state(a,b,"i")}},function(a){var b="wysiwyg-text-align-center",c=/wysiwyg-text-align-[0-9a-z]+/g;a.commands.justifyCenter={exec:function(d){return a.commands.formatBlock.exec(d,"formatBlock",null,b,c)},state:function(d){return a.commands.formatBlock.state(d,"formatBlock",null,b,c)}}}(wysihtml5),function(a){var b="wysiwyg-text-align-left",c=/wysiwyg-text-align-[0-9a-z]+/g;a.commands.justifyLeft={exec:function(d){return a.commands.formatBlock.exec(d,"formatBlock",null,b,c)},state:function(d){return a.commands.formatBlock.state(d,"formatBlock",null,b,c)}}}(wysihtml5),function(a){var b="wysiwyg-text-align-right",c=/wysiwyg-text-align-[0-9a-z]+/g;a.commands.justifyRight={exec:function(d){return a.commands.formatBlock.exec(d,"formatBlock",null,b,c)},state:function(d){return a.commands.formatBlock.state(d,"formatBlock",null,b,c)}}}(wysihtml5),function(a){var b="wysiwyg-text-align-justify",c=/wysiwyg-text-align-[0-9a-z]+/g;a.commands.justifyFull={exec:function(d){return a.commands.formatBlock.exec(d,"formatBlock",null,b,c)},state:function(d){return a.commands.formatBlock.state(d,"formatBlock",null,b,c)}}}(wysihtml5),function(a){var b="text-align: right;",c=/(\s|^)text-align\s*:\s*[^;\s]+;?/gi;a.commands.alignRightStyle={exec:function(d){return a.commands.formatBlock.exec(d,"formatBlock",null,null,null,b,c)},state:function(d){return a.commands.formatBlock.state(d,"formatBlock",null,null,null,b,c)}}}(wysihtml5),function(a){var b="text-align: left;",c=/(\s|^)text-align\s*:\s*[^;\s]+;?/gi;a.commands.alignLeftStyle={exec:function(d){return a.commands.formatBlock.exec(d,"formatBlock",null,null,null,b,c)},state:function(d){return a.commands.formatBlock.state(d,"formatBlock",null,null,null,b,c)}}}(wysihtml5),function(a){var b="text-align: center;",c=/(\s|^)text-align\s*:\s*[^;\s]+;?/gi;a.commands.alignCenterStyle={exec:function(d){return a.commands.formatBlock.exec(d,"formatBlock",null,null,null,b,c)},state:function(d){return a.commands.formatBlock.state(d,"formatBlock",null,null,null,b,c)}}}(wysihtml5),wysihtml5.commands.redo={exec:function(a){return a.undoManager.redo()},state:function(){return!1}},wysihtml5.commands.underline={exec:function(a,b){wysihtml5.commands.formatInline.execWithToggle(a,b,"u")},state:function(a,b){return wysihtml5.commands.formatInline.state(a,b,"u")}},wysihtml5.commands.undo={exec:function(a){return a.undoManager.undo()},state:function(){return!1}},wysihtml5.commands.createTable={exec:function(a,b,c){var d,e,f;if(c&&c.cols&&c.rows&&parseInt(c.cols,10)>0&&parseInt(c.rows,10)>0){for(f=c.tableStyle?'<table style="'+c.tableStyle+'">':"<table>",f+="<tbody>",e=0;e<c.rows;e++){for(f+="<tr>",d=0;d<c.cols;d++)f+="<td>&nbsp;</td>";f+="</tr>"}f+="</tbody></table>",a.commands.exec("insertHTML",f)}},state:function(){return!1}},wysihtml5.commands.mergeTableCells={exec:function(a,b){a.tableSelection&&a.tableSelection.start&&a.tableSelection.end&&(this.state(a,b)?wysihtml5.dom.table.unmergeCell(a.tableSelection.start):wysihtml5.dom.table.mergeCellsBetween(a.tableSelection.start,a.tableSelection.end))},state:function(a){if(a.tableSelection){var b=a.tableSelection.start,c=a.tableSelection.end;if(b&&c&&b==c&&(wysihtml5.dom.getAttribute(b,"colspan")&&parseInt(wysihtml5.dom.getAttribute(b,"colspan"),10)>1||wysihtml5.dom.getAttribute(b,"rowspan")&&parseInt(wysihtml5.dom.getAttribute(b,"rowspan"),10)>1))return[b]}return!1}},wysihtml5.commands.addTableCells={exec:function(a,b,c){if(a.tableSelection&&a.tableSelection.start&&a.tableSelection.end){var d=wysihtml5.dom.table.orderSelectionEnds(a.tableSelection.start,a.tableSelection.end);"before"==c||"above"==c?wysihtml5.dom.table.addCells(d.start,c):("after"==c||"below"==c)&&wysihtml5.dom.table.addCells(d.end,c),setTimeout(function(){a.tableSelection.select(d.start,d.end)},0)}},state:function(){return!1}},wysihtml5.commands.deleteTableCells={exec:function(a,b,c){if(a.tableSelection&&a.tableSelection.start&&a.tableSelection.end){var d,e=wysihtml5.dom.table.orderSelectionEnds(a.tableSelection.start,a.tableSelection.end),f=wysihtml5.dom.table.indexOf(e.start),g=a.tableSelection.table;wysihtml5.dom.table.removeCells(e.start,c),setTimeout(function(){d=wysihtml5.dom.table.findCell(g,f),d||("row"==c&&(d=wysihtml5.dom.table.findCell(g,{row:f.row-1,col:f.col})),"column"==c&&(d=wysihtml5.dom.table.findCell(g,{row:f.row,col:f.col-1}))),d&&a.tableSelection.select(d,d)},0)}},state:function(){return!1}},wysihtml5.commands.indentList={exec:function(a){var b=a.selection.getSelectionParentsByTag("LI");return b?this.tryToPushLiLevel(b,a.selection):!1},state:function(){return!1},tryToPushLiLevel:function(a,b){var c,d,e,f,g,h=!1;return b.executeAndRestoreRangy(function(){for(var b=a.length;b--;)f=a[b],c="OL"===f.parentNode.nodeName?"OL":"UL",d=f.ownerDocument.createElement(c),e=wysihtml5.dom.domNode(f).prev({nodeTypes:[wysihtml5.ELEMENT_NODE]}),g=e?e.querySelector("ul, ol"):null,e&&(g?g.appendChild(f):(d.appendChild(f),e.appendChild(d)),h=!0)}),h}},wysihtml5.commands.outdentList={exec:function(a){var b=a.selection.getSelectionParentsByTag("LI");return b?this.tryToPullLiLevel(b,a):!1},state:function(){return!1},tryToPullLiLevel:function(a,b){var c,d,e,f,g,h=!1,i=this;return b.selection.executeAndRestoreRangy(function(){var j,k;for(j=a.length;j--;)if(f=a[j],f.parentNode&&(c=f.parentNode,"OL"===c.tagName||"UL"===c.tagName)){if(h=!0,d=wysihtml5.dom.getParentElement(c.parentNode,{nodeName:["OL","UL"]},!1,b.element),e=wysihtml5.dom.getParentElement(c.parentNode,{nodeName:["LI"]},!1,b.element),d&&e)f.nextSibling&&(g=i.getAfterList(c,f),f.appendChild(g)),d.insertBefore(f,e.nextSibling);else{for(f.nextSibling&&(g=i.getAfterList(c,f),f.appendChild(g)),k=f.childNodes.length;k--;)c.parentNode.insertBefore(f.childNodes[k],c.nextSibling);c.parentNode.insertBefore(document.createElement("br"),c.nextSibling),f.parentNode.removeChild(f)}0===c.childNodes.length&&c.parentNode.removeChild(c)}}),h},getAfterList:function(a,b){for(var c=a.nodeName,d=document.createElement(c);b.nextSibling;)d.appendChild(b.nextSibling);return d}},function(a){var b=90,c=89,d=8,e=46,f=25,g="data-wysihtml5-selection-node",h="data-wysihtml5-selection-offset",i=('<span id="_wysihtml5-undo" class="_wysihtml5-temp">'+a.INVISIBLE_SPACE+"</span>",'<span id="_wysihtml5-redo" class="_wysihtml5-temp">'+a.INVISIBLE_SPACE+"</span>",a.dom);a.UndoManager=a.lang.Dispatcher.extend({constructor:function(a){this.editor=a,this.composer=a.composer,this.element=this.composer.element,this.position=0,this.historyStr=[],this.historyDom=[],this.transact(),this._observe()},_observe:function(){{var a,f=this;this.composer.sandbox.getDocument()}i.observe(this.element,"keydown",function(a){if(!a.altKey&&(a.ctrlKey||a.metaKey)){var d=a.keyCode,e=d===b&&!a.shiftKey,g=d===b&&a.shiftKey||d===c;e?(f.undo(),a.preventDefault()):g&&(f.redo(),a.preventDefault())}}),i.observe(this.element,"keydown",function(b){var c=b.keyCode;c!==a&&(a=c,(c===d||c===e)&&f.transact())}),this.editor.on("newword:composer",function(){f.transact()}).on("beforecommand:composer",function(){f.transact()})},transact:function(){var b,c,d,e,i,j,k,l=this.historyStr[this.position-1],m=this.composer.getValue(!1,!1),n=this.element.offsetWidth>0&&this.element.offsetHeight>0;m!==l&&(j=this.historyStr.length=this.historyDom.length=this.position,j>f&&(this.historyStr.shift(),this.historyDom.shift(),this.position--),this.position++,n&&(b=this.composer.selection.getRange(),c=b&&b.startContainer?b.startContainer:this.element,d=b&&b.startOffset?b.startOffset:0,c.nodeType===a.ELEMENT_NODE?e=c:(e=c.parentNode,i=this.getChildNodeIndex(e,c)),e.setAttribute(h,d),void 0!==i&&e.setAttribute(g,i)),k=this.element.cloneNode(!!m),this.historyDom.push(k),this.historyStr.push(m),e&&(e.removeAttribute(h),e.removeAttribute(g)))},undo:function(){this.transact(),this.undoPossible()&&(this.set(this.historyDom[--this.position-1]),this.editor.fire("undo:composer"))},redo:function(){this.redoPossible()&&(this.set(this.historyDom[++this.position-1]),this.editor.fire("redo:composer"))},undoPossible:function(){return this.position>1},redoPossible:function(){return this.position<this.historyStr.length},set:function(a){var b,c,d,e,f,i;for(this.element.innerHTML="",b=0,c=a.childNodes,d=a.childNodes.length;d>b;b++)this.element.appendChild(c[b].cloneNode(!0));a.hasAttribute(h)?(e=a.getAttribute(h),i=a.getAttribute(g),f=this.element):(f=this.element.querySelector("["+h+"]")||this.element,e=f.getAttribute(h),i=f.getAttribute(g),f.removeAttribute(h),f.removeAttribute(g)),null!==i&&(f=this.getChildNodeByIndex(f,+i)),this.composer.selection.set(f,e)},getChildNodeIndex:function(a,b){for(var c=0,d=a.childNodes,e=d.length;e>c;c++)if(d[c]===b)return c},getChildNodeByIndex:function(a,b){return a.childNodes[b]}})}(wysihtml5),wysihtml5.views.View=Base.extend({constructor:function(a,b,c){this.parent=a,this.element=b,this.config=c,this.config.noTextarea||this._observeViewChange()},_observeViewChange:function(){var a=this;this.parent.on("beforeload",function(){a.parent.on("change_view",function(b){b===a.name?(a.parent.currentView=a,a.show(),setTimeout(function(){a.focus()},0)):a.hide()})})},focus:function(){if(this.element.ownerDocument.querySelector(":focus")!==this.element)try{this.element.focus()}catch(a){}},hide:function(){this.element.style.display="none"},show:function(){this.element.style.display=""},disable:function(){this.element.setAttribute("disabled","disabled")},enable:function(){this.element.removeAttribute("disabled")}}),function(a){var b=a.dom,c=a.browser;a.views.Composer=a.views.View.extend({name:"composer",CARET_HACK:"<br>",constructor:function(a,b,c){this.base(a,b,c),this.config.noTextarea?this.editableArea=b:this.textarea=this.parent.textarea,this.config.contentEditableMode?this._initContentEditableArea():this._initSandbox()},clear:function(){this.element.innerHTML=c.displaysCaretInEmptyContentEditableCorrectly()?"":this.CARET_HACK},getValue:function(b,c){var d=this.isEmpty()?"":a.quirks.getCorrectInnerHTML(this.element);return b!==!1&&(d=this.parent.parse(d,c===!1?!1:!0)),d},setValue:function(a,b){b&&(a=this.parent.parse(a));try{this.element.innerHTML=a}catch(c){this.element.innerText=a}},cleanUp:function(){this.parent.parse(this.element)},show:function(){this.editableArea.style.display=this._displayStyle||"",this.config.noTextarea||this.textarea.element.disabled||(this.disable(),this.enable())},hide:function(){this._displayStyle=b.getStyle("display").from(this.editableArea),"none"===this._displayStyle&&(this._displayStyle=null),this.editableArea.style.display="none"},disable:function(){this.parent.fire("disable:composer"),this.element.removeAttribute("contentEditable")},enable:function(){this.parent.fire("enable:composer"),this.element.setAttribute("contentEditable","true")},focus:function(b){a.browser.doesAsyncFocus()&&this.hasPlaceholderSet()&&this.clear(),this.base();var c=this.element.lastChild;b&&c&&this.selection&&("BR"===c.nodeName?this.selection.setBefore(this.element.lastChild):this.selection.setAfter(this.element.lastChild))},getTextContent:function(){return b.getTextContent(this.element)},hasPlaceholderSet:function(){return this.getTextContent()==(this.config.noTextarea?this.editableArea.getAttribute("data-placeholder"):this.textarea.element.getAttribute("placeholder"))&&this.placeholderSet},isEmpty:function(){var a=this.element.innerHTML.toLowerCase();return/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i.test(a)||""===a||"<br>"===a||"<p></p>"===a||"<p><br></p>"===a||this.hasPlaceholderSet()},_initContentEditableArea:function(){var a=this;this.config.noTextarea?this.sandbox=new b.ContentEditableArea(function(){a._create()},{},this.editableArea):(this.sandbox=new b.ContentEditableArea(function(){a._create()}),this.editableArea=this.sandbox.getContentEditable(),b.insert(this.editableArea).after(this.textarea.element),this._createWysiwygFormField())},_initSandbox:function(){var a,c=this;this.sandbox=new b.Sandbox(function(){c._create()},{stylesheets:this.config.stylesheets}),this.editableArea=this.sandbox.getIframe(),a=this.textarea.element,b.insert(this.editableArea).after(a),this._createWysiwygFormField()},_createWysiwygFormField:function(){if(this.textarea.element.form){var a=document.createElement("input");a.type="hidden",a.name="_wysihtml5_mode",a.value=1,b.insert(a).after(this.textarea.element)}},_create:function(){var d,e,f=this;this.doc=this.sandbox.getDocument(),this.element=this.config.contentEditableMode?this.sandbox.getContentEditable():this.doc.body,this.config.noTextarea?this.cleanUp():(this.textarea=this.parent.textarea,this.element.innerHTML=this.textarea.getValue(!0,!1)),this.selection=new a.Selection(this.parent,this.element,this.config.uneditableContainerClassname),this.commands=new a.Commands(this.parent),this.config.noTextarea||b.copyAttributes(["className","spellcheck","title","lang","dir","accessKey"]).from(this.textarea.element).to(this.element),b.addClass(this.element,this.config.composerClassName),this.config.style&&!this.config.contentEditableMode&&this.style(),this.observe(),d=this.config.name,d&&(b.addClass(this.element,d),this.config.contentEditableMode||b.addClass(this.editableArea,d)),this.enable(),!this.config.noTextarea&&this.textarea.element.disabled&&this.disable(),e="string"==typeof this.config.placeholder?this.config.placeholder:this.config.noTextarea?this.editableArea.getAttribute("data-placeholder"):this.textarea.element.getAttribute("placeholder"),e&&b.simulatePlaceholder(this.parent,this,e),this.commands.exec("styleWithCSS",!1),this._initAutoLinking(),this._initObjectResizing(),this._initUndoManager(),this._initLineBreaking(),this.config.noTextarea||!this.textarea.element.hasAttribute("autofocus")&&document.querySelector(":focus")!=this.textarea.element||c.isIos()||setTimeout(function(){f.focus(!0)},100),c.clearsContentEditableCorrectly()||a.quirks.ensureProperClearing(this),this.initSync&&this.config.sync&&this.initSync(),this.config.noTextarea||this.textarea.hide(),this.parent.fire("beforeload").fire("load")},_initAutoLinking:function(){var d,e,f,g=this,h=c.canDisableAutoLinking(),i=c.doesAutoLinkingInContentEditable();h&&this.commands.exec("autoUrlDetect",!1),this.config.autoLink&&((!i||i&&h)&&(this.parent.on("newword:composer",function(){b.getTextContent(g.element).match(b.autoLink.URL_REG_EXP)&&g.selection.executeAndRestore(function(c,d){var e,f=g.element.querySelectorAll("."+g.config.uneditableContainerClassname),h=!1;for(e=f.length;e--;)a.dom.contains(f[e],d)&&(h=!0);h||b.autoLink(d.parentNode,[g.config.uneditableContainerClassname])})}),b.observe(this.element,"blur",function(){b.autoLink(g.element,[g.config.uneditableContainerClassname])})),d=this.sandbox.getDocument().getElementsByTagName("a"),e=b.autoLink.URL_REG_EXP,f=function(c){var d=a.lang.string(b.getTextContent(c)).trim();return"www."===d.substr(0,4)&&(d="http://"+d),d},b.observe(this.element,"keydown",function(a){if(d.length){var c,h=g.selection.getSelectedNode(a.target.ownerDocument),i=b.getParentElement(h,{nodeName:"A"},4);i&&(c=f(i),setTimeout(function(){var a=f(i);a!==c&&a.match(e)&&i.setAttribute("href",a)},0))}}))},_initObjectResizing:function(){if(this.commands.exec("enableObjectResizing",!0),c.supportsEvent("resizeend")){var d=["width","height"],e=d.length,f=this.element;b.observe(f,"resizeend",function(b){var c,g=b.target||b.srcElement,h=g.style,i=0;if("IMG"===g.nodeName){for(;e>i;i++)c=d[i],h[c]&&(g.setAttribute(c,parseInt(h[c],10)),h[c]="");a.quirks.redraw(f)}})}},_initUndoManager:function(){this.undoManager=new a.UndoManager(this.parent)},_initLineBreaking:function(){function d(a){var c=b.getParentElement(a,{nodeName:["P","DIV"]},2);c&&b.contains(e.element,c)&&e.selection.executeAndRestore(function(){e.config.useLineBreaks?b.replaceWithChildNodes(c):"P"!==c.nodeName&&b.renameElement(c,"p")})}var e=this,f=["LI","P","H1","H2","H3","H4","H5","H6"],g=["UL","OL","MENU"];this.config.useLineBreaks||b.observe(this.element,["focus","keydown"],function(){if(e.isEmpty()){var a=e.doc.createElement("P");e.element.innerHTML="",e.element.appendChild(a),c.displaysCaretInEmptyContentEditableCorrectly()?e.selection.selectNode(a,!0):(a.innerHTML="<br>",e.selection.setBefore(a.firstChild))}}),b.observe(this.element,"keydown",function(c){var h,i=c.keyCode;if(!c.shiftKey&&(i===a.ENTER_KEY||i===a.BACKSPACE_KEY))return h=b.getParentElement(e.selection.getSelectedNode(),{nodeName:f},4),h?(setTimeout(function(){var c,f=e.selection.getSelectedNode();if("LI"===h.nodeName){if(!f)return;c=b.getParentElement(f,{nodeName:g},2),c||d(f)}i===a.ENTER_KEY&&h.nodeName.match(/^H[1-6]$/)&&d(f)},0),void 0):(e.config.useLineBreaks&&i===a.ENTER_KEY&&!a.browser.insertsLineBreaksOnReturn()&&(c.preventDefault(),e.commands.exec("insertLineBreak")),void 0)})}})}(wysihtml5),function(a){var b=a.dom,c=document,d=window,e=c.createElement("div"),f=["background-color","color","cursor","font-family","font-size","font-style","font-variant","font-weight","line-height","letter-spacing","text-align","text-decoration","text-indent","text-rendering","word-break","word-wrap","word-spacing"],g=["background-color","border-collapse","border-bottom-color","border-bottom-style","border-bottom-width","border-left-color","border-left-style","border-left-width","border-right-color","border-right-style","border-right-width","border-top-color","border-top-style","border-top-width","clear","display","float","margin-bottom","margin-left","margin-right","margin-top","outline-color","outline-offset","outline-width","outline-style","padding-left","padding-right","padding-top","padding-bottom","position","top","left","right","bottom","z-index","vertical-align","text-align","-webkit-box-sizing","-moz-box-sizing","-ms-box-sizing","box-sizing","-webkit-box-shadow","-moz-box-shadow","-ms-box-shadow","box-shadow","-webkit-border-top-right-radius","-moz-border-radius-topright","border-top-right-radius","-webkit-border-bottom-right-radius","-moz-border-radius-bottomright","border-bottom-right-radius","-webkit-border-bottom-left-radius","-moz-border-radius-bottomleft","border-bottom-left-radius","-webkit-border-top-left-radius","-moz-border-radius-topleft","border-top-left-radius","width","height"],h=["html { height: 100%; }","body { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }","body > p:first-child { margin-top: 0; }","._wysihtml5-temp { display: none; }",a.browser.isGecko?"body.placeholder { color: graytext !important; }":"body.placeholder { color: #a9a9a9 !important; }","img:-moz-broken { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"],i=function(a){if(a.setActive)try{a.setActive()}catch(e){}else{var f=a.style,g=c.documentElement.scrollTop||c.body.scrollTop,h=c.documentElement.scrollLeft||c.body.scrollLeft,i={position:f.position,top:f.top,left:f.left,WebkitUserSelect:f.WebkitUserSelect};b.setStyles({position:"absolute",top:"-99999px",left:"-99999px",WebkitUserSelect:"none"}).on(a),a.focus(),b.setStyles(i).on(a),d.scrollTo&&d.scrollTo(h,g)}};a.views.Composer.prototype.style=function(){var d,j,k=this,l=c.querySelector(":focus"),m=this.textarea.element,n=m.hasAttribute("placeholder"),o=n&&m.getAttribute("placeholder"),p=m.style.display,q=m.disabled;return this.focusStylesHost=e.cloneNode(!1),this.blurStylesHost=e.cloneNode(!1),this.disabledStylesHost=e.cloneNode(!1),n&&m.removeAttribute("placeholder"),m===l&&m.blur(),m.disabled=!1,m.style.display=d="none",(m.getAttribute("rows")&&"auto"===b.getStyle("height").from(m)||m.getAttribute("cols")&&"auto"===b.getStyle("width").from(m))&&(m.style.display=d=p),b.copyStyles(g).from(m).to(this.editableArea).andTo(this.blurStylesHost),b.copyStyles(f).from(m).to(this.element).andTo(this.blurStylesHost),b.insertCSS(h).into(this.element.ownerDocument),m.disabled=!0,b.copyStyles(g).from(m).to(this.disabledStylesHost),b.copyStyles(f).from(m).to(this.disabledStylesHost),m.disabled=q,m.style.display=p,i(m),m.style.display=d,b.copyStyles(g).from(m).to(this.focusStylesHost),b.copyStyles(f).from(m).to(this.focusStylesHost),m.style.display=p,b.copyStyles(["display"]).from(m).to(this.editableArea),j=a.lang.array(g).without(["display"]),l?l.focus():m.blur(),n&&m.setAttribute("placeholder",o),this.parent.on("focus:composer",function(){b.copyStyles(j).from(k.focusStylesHost).to(k.editableArea),b.copyStyles(f).from(k.focusStylesHost).to(k.element)}),this.parent.on("blur:composer",function(){b.copyStyles(j).from(k.blurStylesHost).to(k.editableArea),b.copyStyles(f).from(k.blurStylesHost).to(k.element)}),this.parent.observe("disable:composer",function(){b.copyStyles(j).from(k.disabledStylesHost).to(k.editableArea),b.copyStyles(f).from(k.disabledStylesHost).to(k.element)}),this.parent.observe("enable:composer",function(){b.copyStyles(j).from(k.blurStylesHost).to(k.editableArea),b.copyStyles(f).from(k.blurStylesHost).to(k.element)}),this}}(wysihtml5),function(a){var b=a.dom,c=a.browser,d={66:"bold",73:"italic",85:"underline"},e=function(a,b,c){var d,e=a.getPreviousNode(b,!0),f=a.getSelectedNode();if(1!==f.nodeType&&f.parentNode!==c&&(f=f.parentNode),e)if(1==f.nodeType){if(d=f.firstChild,1==e.nodeType)for(;f.firstChild;)e.appendChild(f.firstChild);else for(;f.firstChild;)b.parentNode.insertBefore(f.firstChild,b);f.parentNode&&f.parentNode.removeChild(f),a.setBefore(d)}else 1==e.nodeType?e.appendChild(f):b.parentNode.insertBefore(f,b),a.setBefore(f)},f=function(a,b,c,d){var f,g,h;b.isCollapsed()?b.caretIsInTheBeginnig("LI")?(a.preventDefault(),d.commands.exec("outdentList")):b.caretIsInTheBeginnig()?a.preventDefault():(b.caretIsFirstInSelection()&&b.getPreviousNode()&&b.getPreviousNode().nodeName&&/^H\d$/gi.test(b.getPreviousNode().nodeName)&&(f=b.getPreviousNode(),a.preventDefault(),/^\s*$/.test(f.textContent||f.innerText)?f.parentNode.removeChild(f):(g=f.ownerDocument.createRange(),g.selectNodeContents(f),g.collapse(!1),b.setSelection(g))),h=b.caretIsBeforeUneditable(),h&&(a.preventDefault(),e(b,h,c))):b.containsUneditable()&&(a.preventDefault(),b.deleteContents())},g=function(a){if(a.selection.isCollapsed()){if(a.selection.caretIsInTheBeginnig("LI")&&a.commands.exec("indentList"))return}else a.selection.deleteContents();
+a.commands.exec("insertHTML","&emsp;")};a.views.Composer.prototype.observe=function(){var e,h,i=this,j=this.getValue(!1,!1),k=this.sandbox.getIframe?this.sandbox.getIframe():this.sandbox.getContentEditable(),l=this.element,m=c.supportsEventsInIframeCorrectly()||this.sandbox.getContentEditable?l:this.sandbox.getWindow(),n=["drop","paste","beforepaste"],o=["drop","paste","mouseup","focus","keyup"];b.observe(k,"DOMNodeRemoved",function(){clearInterval(e),i.parent.fire("destroy:composer")}),c.supportsMutationEvents()||(e=setInterval(function(){b.contains(document.documentElement,k)||(clearInterval(e),i.parent.fire("destroy:composer"))},250)),b.observe(m,o,function(){setTimeout(function(){i.parent.fire("interaction").fire("interaction:composer")},0)}),this.config.handleTables&&(!this.tableClickHandle&&this.doc.execCommand&&a.browser.supportsCommand(this.doc,"enableObjectResizing")&&a.browser.supportsCommand(this.doc,"enableInlineTableEditing")&&(this.sandbox.getIframe?this.tableClickHandle=b.observe(k,["focus","mouseup","mouseover"],function(){i.doc.execCommand("enableObjectResizing",!1,"false"),i.doc.execCommand("enableInlineTableEditing",!1,"false"),i.tableClickHandle.stop()}):setTimeout(function(){i.doc.execCommand("enableObjectResizing",!1,"false"),i.doc.execCommand("enableInlineTableEditing",!1,"false")},0)),this.tableSelection=a.quirks.tableCellsSelection(l,i.parent)),b.observe(m,"focus",function(a){i.parent.fire("focus",a).fire("focus:composer",a),setTimeout(function(){j=i.getValue(!1,!1)},0)}),b.observe(m,"blur",function(a){if(j!==i.getValue(!1,!1)){var b=a;"function"==typeof Object.create&&(b=Object.create(a,{type:{value:"change"}})),i.parent.fire("change",b).fire("change:composer",b)}i.parent.fire("blur",a).fire("blur:composer",a)}),b.observe(l,"dragenter",function(){i.parent.fire("unset_placeholder")}),b.observe(l,n,function(a){i.parent.fire(a.type,a).fire(a.type+":composer",a)}),this.config.copyedFromMarking&&b.observe(l,"copy",function(a){a.clipboardData&&(a.clipboardData.setData("text/html",i.config.copyedFromMarking+i.selection.getHtml()),a.preventDefault()),i.parent.fire(a.type,a).fire(a.type+":composer",a)}),b.observe(l,"keyup",function(b){var c=b.keyCode;(c===a.SPACE_KEY||c===a.ENTER_KEY)&&i.parent.fire("newword:composer")}),this.parent.on("paste:composer",function(){setTimeout(function(){i.parent.fire("newword:composer")},0)}),c.canSelectImagesInContentEditable()||b.observe(l,"mousedown",function(b){var c=b.target,d=l.querySelectorAll("img"),e=l.querySelectorAll("."+i.config.uneditableContainerClassname+" img"),f=a.lang.array(d).without(e);"IMG"===c.nodeName&&a.lang.array(f).contains(c)&&i.selection.selectNode(c)}),c.canSelectImagesInContentEditable()||b.observe(l,"drop",function(){setTimeout(function(){i.selection.getSelection().removeAllRanges()},0)}),c.hasHistoryIssue()&&c.supportsSelectionModify()&&b.observe(l,"keydown",function(a){if(a.metaKey||a.ctrlKey){var b=a.keyCode,c=l.ownerDocument.defaultView,d=c.getSelection();(37===b||39===b)&&(37===b&&(d.modify("extend","left","lineboundary"),a.shiftKey||d.collapseToStart()),39===b&&(d.modify("extend","right","lineboundary"),a.shiftKey||d.collapseToEnd()),a.preventDefault())}}),b.observe(l,"keydown",function(a){var b=a.keyCode,c=d[b];(a.ctrlKey||a.metaKey)&&!a.altKey&&c&&(i.commands.exec(c),a.preventDefault()),8===b?f(a,i.selection,l,i):i.config.handleTabKey&&9===b&&(a.preventDefault(),g(i,l))}),b.observe(l,"keydown",function(b){var c,d=i.selection.getSelectedNode(!0),e=b.keyCode;!d||"IMG"!==d.nodeName||e!==a.BACKSPACE_KEY&&e!==a.DELETE_KEY||(c=d.parentNode,c.removeChild(d),"A"!==c.nodeName||c.firstChild||c.parentNode.removeChild(c),setTimeout(function(){a.quirks.redraw(l)},0),b.preventDefault())}),!this.config.contentEditableMode&&c.hasIframeFocusIssue()&&(b.observe(k,"focus",function(){setTimeout(function(){i.doc.querySelector(":focus")!==i.element&&i.focus()},0)}),b.observe(this.element,"blur",function(){setTimeout(function(){i.selection.getSelection().removeAllRanges()},0)})),h={IMG:"Image: ",A:"Link: "},b.observe(l,"mouseover",function(a){var b,c,d=a.target,e=d.nodeName;("A"===e||"IMG"===e)&&(c=d.hasAttribute("title"),c||(b=h[e]+(d.getAttribute("href")||d.getAttribute("src")),d.setAttribute("title",b)))})}}(wysihtml5),function(a){var b=400;a.views.Synchronizer=Base.extend({constructor:function(a,b,c){this.editor=a,this.textarea=b,this.composer=c,this._observe()},fromComposerToTextarea:function(b){this.textarea.setValue(a.lang.string(this.composer.getValue(!1,!1)).trim(),b)},fromTextareaToComposer:function(a){var b=this.textarea.getValue(!1,!1);b?this.composer.setValue(b,a):(this.composer.clear(),this.editor.fire("set_placeholder"))},sync:function(a){"textarea"===this.editor.currentView.name?this.fromTextareaToComposer(a):this.fromComposerToTextarea(a)},_observe:function(){var c,d=this,e=this.textarea.element.form,f=function(){c=setInterval(function(){d.fromComposerToTextarea()},b)},g=function(){clearInterval(c),c=null};f(),e&&(a.dom.observe(e,"submit",function(){d.sync(!0)}),a.dom.observe(e,"reset",function(){setTimeout(function(){d.fromTextareaToComposer()},0)})),this.editor.on("change_view",function(a){"composer"!==a||c?"textarea"===a&&(d.fromComposerToTextarea(!0),g()):(d.fromTextareaToComposer(!0),f())}),this.editor.on("destroy:composer",g)}})}(wysihtml5),wysihtml5.views.Textarea=wysihtml5.views.View.extend({name:"textarea",constructor:function(a,b,c){this.base(a,b,c),this._observe()},clear:function(){this.element.value=""},getValue:function(a){var b=this.isEmpty()?"":this.element.value;return a!==!1&&(b=this.parent.parse(b)),b},setValue:function(a,b){b&&(a=this.parent.parse(a)),this.element.value=a},cleanUp:function(){var a=this.parent.parse(this.element.value);this.element.value=a},hasPlaceholderSet:function(){var a=wysihtml5.browser.supportsPlaceholderAttributeOn(this.element),b=this.element.getAttribute("placeholder")||null,c=this.element.value,d=!c;return a&&d||c===b},isEmpty:function(){return!wysihtml5.lang.string(this.element.value).trim()||this.hasPlaceholderSet()},_observe:function(){var a=this.element,b=this.parent,c={focusin:"focus",focusout:"blur"},d=wysihtml5.browser.supportsEvent("focusin")?["focusin","focusout","change"]:["focus","blur","change"];b.on("beforeload",function(){wysihtml5.dom.observe(a,d,function(a){var d=c[a.type]||a.type;b.fire(d).fire(d+":textarea")}),wysihtml5.dom.observe(a,["paste","drop"],function(){setTimeout(function(){b.fire("paste").fire("paste:textarea")},0)})})}}),function(a){var b,c={name:b,style:!0,toolbar:b,showToolbarAfterInit:!0,autoLink:!0,handleTables:!0,handleTabKey:!0,parserRules:{tags:{br:{},span:{},div:{},p:{}},classes:{}},pasteParserRulesets:null,parser:a.dom.parse,composerClassName:"wysihtml5-editor",bodyClassName:"wysihtml5-supported",useLineBreaks:!0,stylesheets:[],placeholderText:b,supportTouchDevices:!0,cleanUp:!0,contentEditableMode:!1,uneditableContainerClassname:"wysihtml5-uneditable-container",copyedFromMarking:'<meta name="copied-from" content="wysihtml5">'};a.Editor=a.lang.Dispatcher.extend({constructor:function(b,d){if(this.editableElement="string"==typeof b?document.getElementById(b):b,this.config=a.lang.object({}).merge(c).merge(d).get(),this._isCompatible=a.browser.supported(),"textarea"!=this.editableElement.nodeName.toLowerCase()&&(this.config.contentEditableMode=!0,this.config.noTextarea=!0),this.config.noTextarea||(this.textarea=new a.views.Textarea(this,this.editableElement,this.config),this.currentView=this.textarea),!this._isCompatible||!this.config.supportTouchDevices&&a.browser.isTouchDevice()){var e=this;return setTimeout(function(){e.fire("beforeload").fire("load")},0),void 0}a.dom.addClass(document.body,this.config.bodyClassName),this.composer=new a.views.Composer(this,this.editableElement,this.config),this.currentView=this.composer,"function"==typeof this.config.parser&&this._initParser(),this.on("beforeload",this.handleBeforeLoad)},handleBeforeLoad:function(){this.config.noTextarea||(this.synchronizer=new a.views.Synchronizer(this,this.textarea,this.composer)),this.config.toolbar&&(this.toolbar=new a.toolbar.Toolbar(this,this.config.toolbar,this.config.showToolbarAfterInit))},isCompatible:function(){return this._isCompatible},clear:function(){return this.currentView.clear(),this},getValue:function(a,b){return this.currentView.getValue(a,b)},setValue:function(a,b){return this.fire("unset_placeholder"),a?(this.currentView.setValue(a,b),this):this.clear()},cleanUp:function(){this.currentView.cleanUp()},focus:function(a){return this.currentView.focus(a),this},disable:function(){return this.currentView.disable(),this},enable:function(){return this.currentView.enable(),this},isEmpty:function(){return this.currentView.isEmpty()},hasPlaceholderSet:function(){return this.currentView.hasPlaceholderSet()},parse:function(b,c){var d=this.config.contentEditableMode?document:this.composer?this.composer.sandbox.getDocument():null,e=this.config.parser(b,{rules:this.config.parserRules,cleanUp:this.config.cleanUp,context:d,uneditableClass:this.config.uneditableContainerClassname,clearInternals:c});return"object"==typeof b&&a.quirks.redraw(b),e},_initParser:function(){var b,c=this;a.browser.supportsModenPaste()?this.on("paste:composer",function(d){d.preventDefault(),b=a.dom.getPastedHtml(d),b&&c._cleanAndPaste(b)}):this.on("beforepaste:composer",function(b){b.preventDefault(),a.dom.getPastedHtmlWithDiv(c.composer,function(a){a&&c._cleanAndPaste(a)})})},_cleanAndPaste:function(b){var c=a.quirks.cleanPastedHTML(b,{referenceNode:this.composer.element,rules:this.config.pasteParserRulesets||[{set:this.config.parserRules}],uneditableClass:this.config.uneditableContainerClassname});this.composer.selection.deleteContents(),this.composer.selection.insertHTML(c)}})}(wysihtml5),function(a){var b=a.dom,c="wysihtml5-command-dialog-opened",d="input, select, textarea",e="[data-wysihtml5-dialog-field]",f="data-wysihtml5-dialog-field";a.toolbar.Dialog=a.lang.Dispatcher.extend({constructor:function(a,b){this.link=a,this.container=b},_observe:function(){var e,f,g,h,i,j;if(!this._observed){for(e=this,f=function(a){var b=e._serialize();b==e.elementToChange?e.fire("edit",b):e.fire("save",b),e.hide(),a.preventDefault(),a.stopPropagation()},b.observe(e.link,"click",function(){b.hasClass(e.link,c)&&setTimeout(function(){e.hide()},0)}),b.observe(this.container,"keydown",function(b){var c=b.keyCode;c===a.ENTER_KEY&&f(b),c===a.ESCAPE_KEY&&(e.fire("cancel"),e.hide())}),b.delegate(this.container,"[data-wysihtml5-dialog-action=save]","click",f),b.delegate(this.container,"[data-wysihtml5-dialog-action=cancel]","click",function(a){e.fire("cancel"),e.hide(),a.preventDefault(),a.stopPropagation()}),g=this.container.querySelectorAll(d),h=0,i=g.length,j=function(){clearInterval(e.interval)};i>h;h++)b.observe(g[h],"change",j);this._observed=!0}},_serialize:function(){for(var a=this.elementToChange||{},b=this.container.querySelectorAll(e),c=b.length,d=0;c>d;d++)a[b[d].getAttribute(f)]=b[d].value;return a},_interpolate:function(a){for(var b,c,d,g=document.querySelector(":focus"),h=this.container.querySelectorAll(e),i=h.length,j=0;i>j;j++)b=h[j],b!==g&&(a&&"hidden"===b.type||(c=b.getAttribute(f),d=this.elementToChange&&"boolean"!=typeof this.elementToChange?this.elementToChange.getAttribute(c)||"":b.defaultValue,b.value=d))},show:function(a){if(!b.hasClass(this.link,c)){var e=this,f=this.container.querySelector(d);if(this.elementToChange=a,this._observe(),this._interpolate(),a&&(this.interval=setInterval(function(){e._interpolate(!0)},500)),b.addClass(this.link,c),this.container.style.display="",this.fire("show"),f&&!a)try{f.focus()}catch(g){}}},hide:function(){clearInterval(this.interval),this.elementToChange=null,b.removeClass(this.link,c),this.container.style.display="none",this.fire("hide")}})}(wysihtml5),function(a){var b=a.dom,c={position:"relative"},d={left:0,margin:0,opacity:0,overflow:"hidden",padding:0,position:"absolute",top:0,zIndex:1},e={cursor:"inherit",fontSize:"50px",height:"50px",marginTop:"-25px",outline:0,padding:0,position:"absolute",right:"-4px",top:"50%"},f={"x-webkit-speech":"",speech:""};a.toolbar.Speech=function(g,h){var i,j,k,l=document.createElement("input");return a.browser.supportsSpeechApiOn(l)?(i=g.editor.textarea.element.getAttribute("lang"),i&&(f.lang=i),j=document.createElement("div"),a.lang.object(d).merge({width:h.offsetWidth+"px",height:h.offsetHeight+"px"}),b.insert(l).into(j),b.insert(j).into(h),b.setStyles(e).on(l),b.setAttributes(f).on(l),b.setStyles(d).on(j),b.setStyles(c).on(h),k="onwebkitspeechchange"in l?"webkitspeechchange":"speechchange",b.observe(l,k,function(){g.execCommand("insertText",l.value),l.value=""}),b.observe(l,"click",function(a){b.hasClass(h,"wysihtml5-command-disabled")&&a.preventDefault(),a.stopPropagation()}),void 0):(h.style.display="none",void 0)}}(wysihtml5),function(a){var b="wysihtml5-command-disabled",c="wysihtml5-commands-disabled",d="wysihtml5-command-active",e="wysihtml5-action-active",f=a.dom;a.toolbar.Toolbar=Base.extend({constructor:function(f,g,h){this.editor=f,this.container="string"==typeof g?document.getElementById(g):g,this.composer=f.composer,this._getLinks("command"),this._getLinks("action"),this._observe(),h&&this.show(),null!=f.config.classNameCommandDisabled&&(b=f.config.classNameCommandDisabled),null!=f.config.classNameCommandsDisabled&&(c=f.config.classNameCommandsDisabled),null!=f.config.classNameCommandActive&&(d=f.config.classNameCommandActive),null!=f.config.classNameActionActive&&(e=f.config.classNameActionActive);for(var i=this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),j=i.length,k=0;j>k;k++)new a.toolbar.Speech(this,i[k])},_getLinks:function(b){for(var c,d,e,f,g,h=this[b+"Links"]=a.lang.array(this.container.querySelectorAll("[data-wysihtml5-"+b+"]")).get(),i=h.length,j=0,k=this[b+"Mapping"]={};i>j;j++)c=h[j],e=c.getAttribute("data-wysihtml5-"+b),f=c.getAttribute("data-wysihtml5-"+b+"-value"),d=this.container.querySelector("[data-wysihtml5-"+b+"-group='"+e+"']"),g=this._getDialog(c,e),k[e+":"+f]={link:c,group:d,name:e,value:f,dialog:g,state:!1}},_getDialog:function(b,c){var d,e,f=this,g=this.container.querySelector("[data-wysihtml5-dialog='"+c+"']");return g&&(d=a.toolbar["Dialog_"+c]?new a.toolbar["Dialog_"+c](b,g):new a.toolbar.Dialog(b,g),d.on("show",function(){e=f.composer.selection.getBookmark(),f.editor.fire("show:dialog",{command:c,dialogContainer:g,commandLink:b})}),d.on("save",function(a){e&&f.composer.selection.setBookmark(e),f._execCommand(c,a),f.editor.fire("save:dialog",{command:c,dialogContainer:g,commandLink:b})}),d.on("cancel",function(){f.editor.focus(!1),f.editor.fire("cancel:dialog",{command:c,dialogContainer:g,commandLink:b})})),d},execCommand:function(a,b){if(!this.commandsDisabled){var c=this.commandMapping[a+":"+b];c&&c.dialog&&!c.state?c.dialog.show():this._execCommand(a,b)}},_execCommand:function(a,b){this.editor.focus(!1),this.composer.commands.exec(a,b),this._updateLinkStates()},execAction:function(a){var b=this.editor;"change_view"===a&&b.textarea&&(b.currentView===b.textarea?b.fire("change_view","composer"):b.fire("change_view","textarea")),"showSource"==a&&b.fire("showSource")},_observe:function(){for(var a=this,b=this.editor,d=this.container,e=this.commandLinks.concat(this.actionLinks),g=e.length,h=0;g>h;h++)"A"===e[h].nodeName?f.setAttributes({href:"javascript:;",unselectable:"on"}).on(e[h]):f.setAttributes({unselectable:"on"}).on(e[h]);f.delegate(d,"[data-wysihtml5-command], [data-wysihtml5-action]","mousedown",function(a){a.preventDefault()}),f.delegate(d,"[data-wysihtml5-command]","click",function(b){var c=this,d=c.getAttribute("data-wysihtml5-command"),e=c.getAttribute("data-wysihtml5-command-value");a.execCommand(d,e),b.preventDefault()}),f.delegate(d,"[data-wysihtml5-action]","click",function(b){var c=this.getAttribute("data-wysihtml5-action");a.execAction(c),b.preventDefault()}),b.on("interaction:composer",function(){a._updateLinkStates()}),b.on("focus:composer",function(){a.bookmark=null}),this.editor.config.handleTables&&(b.on("tableselect:composer",function(){a.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display=""}),b.on("tableunselect:composer",function(){a.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display="none"})),b.on("change_view",function(e){b.textarea&&setTimeout(function(){a.commandsDisabled="composer"!==e,a._updateLinkStates(),a.commandsDisabled?f.addClass(d,c):f.removeClass(d,c)},0)})},_updateLinkStates:function(){var c,g,h,i,j=this.commandMapping,k=this.actionMapping;for(c in j)i=j[c],this.commandsDisabled?(g=!1,f.removeClass(i.link,d),i.group&&f.removeClass(i.group,d),i.dialog&&i.dialog.hide()):(g=this.composer.commands.state(i.name,i.value),f.removeClass(i.link,b),i.group&&f.removeClass(i.group,b)),i.state!==g&&(i.state=g,g?(f.addClass(i.link,d),i.group&&f.addClass(i.group,d),i.dialog&&("object"==typeof g||a.lang.object(g).isArray()?(!i.dialog.multiselect&&a.lang.object(g).isArray()&&(g=1===g.length?g[0]:!0,i.state=g),i.dialog.show(g)):i.dialog.hide())):(f.removeClass(i.link,d),i.group&&f.removeClass(i.group,d),i.dialog&&i.dialog.hide()));for(c in k)h=k[c],"change_view"===h.name&&(h.state=this.editor.currentView===this.editor.textarea,h.state?f.addClass(h.link,e):f.removeClass(h.link,e))},show:function(){this.container.style.display=""},hide:function(){this.container.style.display="none"}})}(wysihtml5),function(a){a.toolbar.Dialog_createTable=a.toolbar.Dialog.extend({show:function(a){this.base(a)}})}(wysihtml5),function(a){var b=(a.dom,"[data-wysihtml5-dialog-field]"),c="data-wysihtml5-dialog-field";a.toolbar.Dialog_foreColorStyle=a.toolbar.Dialog.extend({multiselect:!0,_serialize:function(){for(var a={},d=this.container.querySelectorAll(b),e=d.length,f=0;e>f;f++)a[d[f].getAttribute(c)]=d[f].value;return a},_interpolate:function(d){for(var e,f=document.querySelector(":focus"),g=this.container.querySelectorAll(b),h=g.length,i=0,j=this.elementToChange?a.lang.object(this.elementToChange).isArray()?this.elementToChange[0]:this.elementToChange:null,k=j?j.getAttribute("style"):null,l=k?a.quirks.styleParser.parseColor(k,"color"):null;h>i;i++)e=g[i],e!==f&&(d&&"hidden"===e.type||"color"===e.getAttribute(c)&&(e.value=l?l[3]&&1!=l[3]?"rgba("+l[0]+","+l[1]+","+l[2]+","+l[3]+");":"rgb("+l[0]+","+l[1]+","+l[2]+");":"rgb(0,0,0);"))}})}(wysihtml5),function(a){a.dom;a.toolbar.Dialog_fontSizeStyle=a.toolbar.Dialog.extend({multiselect:!0,_serialize:function(){return{size:this.container.querySelector('[data-wysihtml5-dialog-field="size"]').value}},_interpolate:function(){var b=document.querySelector(":focus"),c=this.container.querySelector("[data-wysihtml5-dialog-field='size']"),d=this.elementToChange?a.lang.object(this.elementToChange).isArray()?this.elementToChange[0]:this.elementToChange:null,e=d?d.getAttribute("style"):null,f=e?a.quirks.styleParser.parseFontSize(e):null;c&&c!==b&&f&&!/^\s*$/.test(f)&&(c.value=f)}})}(wysihtml5),Handlebars=function(){var a=function(){"use strict";function a(a){this.string=a}var b;return a.prototype.toString=function(){return""+this.string},b=a}(),b=function(a){"use strict";function b(a){return k[a]||"&amp;"}function c(a,b){for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&(a[c]=b[c])}function d(a){return a instanceof j?""+a:a||0===a?(a=""+a,m.test(a)?a.replace(l,b):a):""}function e(a){return a||0===a?h(a)&&0===a.length?!0:!1:!0}var f,g,h,i={},j=a,k={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},l=/[&<>"'`]/g,m=/[&<>"'`]/;return i.extend=c,f=Object.prototype.toString,i.toString=f,g=function(a){return"function"==typeof a},g(/x/)&&(g=function(a){return"function"==typeof a&&"[object Function]"===f.call(a)}),i.isFunction=g,h=Array.isArray||function(a){return a&&"object"==typeof a?"[object Array]"===f.call(a):!1},i.isArray=h,i.escapeExpression=d,i.isEmpty=e,i}(a),c=function(){"use strict";function a(a,b){var d,e,f;for(b&&b.firstLine&&(d=b.firstLine,a+=" - "+d+":"+b.firstColumn),e=Error.prototype.constructor.call(this,a),f=0;f<c.length;f++)this[c[f]]=e[c[f]];d&&(this.lineNumber=d,this.column=b.firstColumn)}var b,c=["description","fileName","lineNumber","message","name","number","stack"];return a.prototype=Error(),b=a}(),d=function(a,b){"use strict";function c(a,b){this.helpers=a||{},this.partials=b||{},d(this)}function d(a){a.registerHelper("helperMissing",function(a){if(2===arguments.length)return void 0;throw new p("Missing helper: '"+a+"'")}),a.registerHelper("blockHelperMissing",function(b,c){var d=c.inverse||function(){},e=c.fn;return i(b)&&(b=b.call(this)),b===!0?e(this):b===!1||null==b?d(this):h(b)?b.length>0?a.helpers.each(b,c):d(this):e(b)}),a.registerHelper("each",function(a,b){var c,d,e,f=b.fn,g=b.inverse,j=0,k="";if(i(a)&&(a=a.call(this)),b.data&&(c=m(b.data)),a&&"object"==typeof a)if(h(a))for(d=a.length;d>j;j++)c&&(c.index=j,c.first=0===j,c.last=j===a.length-1),k+=f(a[j],{data:c});else for(e in a)a.hasOwnProperty(e)&&(c&&(c.key=e,c.index=j,c.first=0===j),k+=f(a[e],{data:c}),j++);return 0===j&&(k=g(this)),k}),a.registerHelper("if",function(a,b){return i(a)&&(a=a.call(this)),!b.hash.includeZero&&!a||o.isEmpty(a)?b.inverse(this):b.fn(this)}),a.registerHelper("unless",function(b,c){return a.helpers["if"].call(this,b,{fn:c.inverse,inverse:c.fn,hash:c.hash})}),a.registerHelper("with",function(a,b){return i(a)&&(a=a.call(this)),o.isEmpty(a)?void 0:b.fn(a)}),a.registerHelper("log",function(b,c){var d=c.data&&null!=c.data.level?parseInt(c.data.level,10):1;a.log(d,b)})}function e(a,b){l.log(a,b)}var f,g,h,i,j,k,l,m,n={},o=a,p=b,q="1.3.0";return n.VERSION=q,f=4,n.COMPILER_REVISION=f,g={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:">= 1.0.0"},n.REVISION_CHANGES=g,h=o.isArray,i=o.isFunction,j=o.toString,k="[object Object]",n.HandlebarsEnvironment=c,c.prototype={constructor:c,logger:l,log:e,registerHelper:function(a,b,c){if(j.call(a)===k){if(c||b)throw new p("Arg not supported with multiple helpers");o.extend(this.helpers,a)}else c&&(b.not=c),this.helpers[a]=b},registerPartial:function(a,b){j.call(a)===k?o.extend(this.partials,a):this.partials[a]=b}},l={methodMap:{0:"debug",1:"info",2:"warn",3:"error"},DEBUG:0,INFO:1,WARN:2,ERROR:3,level:3,log:function(a,b){if(l.level<=a){var c=l.methodMap[a];"undefined"!=typeof console&&console[c]&&console[c].call(console,b)}}},n.logger=l,n.log=e,m=function(a){var b={};return o.extend(b,a),b},n.createFrame=m,n}(b,c),e=function(a,b,c){"use strict";function d(a){var b,c,d=a&&a[0]||1,e=m;if(d!==e){if(e>d)throw b=n[e],c=n[d],new l("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version ("+b+") or downgrade your runtime to an older version ("+c+").");throw new l("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version ("+a[1]+").")}}function e(a,b){if(!b)throw new l("No environment passed to template");var c=function(a,c,d,e,f,g){var h,i=b.VM.invokePartial.apply(this,arguments);if(null!=i)return i;if(b.compile)return h={helpers:e,partials:f,data:g},f[c]=b.compile(a,{data:void 0!==g},b),f[c](d,h);throw new l("The partial "+c+" could not be compiled when running in runtime-only mode")},d={escapeExpression:k.escapeExpression,invokePartial:c,programs:[],program:function(a,b,c){var d=this.programs[a];return c?d=g(a,b,c):d||(d=this.programs[a]=g(a,b)),d},merge:function(a,b){var c=a||b;return a&&b&&a!==b&&(c={},k.extend(c,b),k.extend(c,a)),c},programWithDepth:b.VM.programWithDepth,noop:b.VM.noop,compilerInfo:null};return function(c,e){var f,g,h,i;return e=e||{},h=e.partial?e:b,e.partial||(f=e.helpers,g=e.partials),i=a.call(d,h,c,f,g,e.data),e.partial||b.VM.checkRevision(d.compilerInfo),i}}function f(a,b,c){var d=Array.prototype.slice.call(arguments,3),e=function(a,e){return e=e||{},b.apply(this,[a,e.data||c].concat(d))};return e.program=a,e.depth=d.length,e}function g(a,b,c){var d=function(a,d){return d=d||{},b(a,d.data||c)};return d.program=a,d.depth=0,d}function h(a,b,c,d,e,f){var g={partial:!0,helpers:d,partials:e,data:f};if(void 0===a)throw new l("The partial "+b+" could not be found");return a instanceof Function?a(c,g):void 0}function i(){return""}var j={},k=a,l=b,m=c.COMPILER_REVISION,n=c.REVISION_CHANGES;return j.checkRevision=d,j.template=e,j.programWithDepth=f,j.program=g,j.invokePartial=h,j.noop=i,j}(b,c,d),f=function(a,b,c,d,e){"use strict";var f,g=a,h=b,i=c,j=d,k=e,l=function(){var a=new g.HandlebarsEnvironment;return j.extend(a,g),a.SafeString=h,a.Exception=i,a.Utils=j,a.VM=k,a.template=function(b){return k.template(b,a)},a},m=l();return m.create=l,f=m}(d,a,c,b,e);return f}(),this.wysihtml5=this.wysihtml5||{},this.wysihtml5.tpl=this.wysihtml5.tpl||{},this.wysihtml5.tpl.blockquote=Handlebars.template(function(a,b,c,d,e){function f(a){var b,c="";return c+="btn-"+l((b=a&&a.options,b=null==b||b===!1?b:b.toolbar,b=null==b||b===!1?b:b.size,typeof b===k?b.apply(a):b)),c}function g(){return' \n <span class="fa fa-quote-left"></span>\n '}function h(){return'\n <span class="glyphicon glyphicon-quote"></span>\n '}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var i,j="",k="function",l=this.escapeExpression,m=this;return j+='<li>\n <a class="btn ',i=c["if"].call(b,(i=b&&b.options,i=null==i||i===!1?i:i.toolbar,null==i||i===!1?i:i.size),{hash:{},inverse:m.noop,fn:m.program(1,f,e),data:e}),(i||0===i)&&(j+=i),j+=' btn-default" data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="blockquote" data-wysihtml5-display-format-name="false" tabindex="-1">\n ',i=c["if"].call(b,(i=b&&b.options,i=null==i||i===!1?i:i.toolbar,null==i||i===!1?i:i.fa),{hash:{},inverse:m.program(5,h,e),fn:m.program(3,g,e),data:e}),(i||0===i)&&(j+=i),j+="\n </a>\n</li>\n",j}),this.wysihtml5.tpl.color=Handlebars.template(function(a,b,c,d,e){function f(a){var b,c="";return c+="btn-"+j((b=a&&a.options,b=null==b||b===!1?b:b.toolbar,b=null==b||b===!1?b:b.size,typeof b===i?b.apply(a):b)),c}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var g,h="",i="function",j=this.escapeExpression,k=this;return h+='<li class="dropdown">\n <a class="btn btn-default dropdown-toggle ',g=c["if"].call(b,(g=b&&b.options,g=null==g||g===!1?g:g.toolbar,null==g||g===!1?g:g.size),{hash:{},inverse:k.noop,fn:k.program(1,f,e),data:e}),(g||0===g)&&(h+=g),h+='" data-toggle="dropdown" tabindex="-1">\n <span class="current-color">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.black,typeof g===i?g.apply(b):g))+'</span>\n <b class="caret"></b>\n </a>\n <ul class="dropdown-menu">\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="black"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="black">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.black,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="silver"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="silver">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.silver,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="gray"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="gray">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.gray,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="maroon"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="maroon">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.maroon,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="red"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="red">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.red,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="purple"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="purple">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.purple,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="green"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="green">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.green,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="olive"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="olive">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.olive,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="navy"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="navy">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.navy,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="blue"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="blue">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.blue,typeof g===i?g.apply(b):g))+'</a></li>\n <li><div class="wysihtml5-colors" data-wysihtml5-command-value="orange"></div><a class="wysihtml5-colors-title" data-wysihtml5-command="foreColor" data-wysihtml5-command-value="orange">'+j((g=b&&b.locale,g=null==g||g===!1?g:g.colours,g=null==g||g===!1?g:g.orange,typeof g===i?g.apply(b):g))+"</a></li>\n </ul>\n</li>\n",h}),this.wysihtml5.tpl.emphasis=Handlebars.template(function(a,b,c,d,e){function f(a){var b,c="";return c+="btn-"+k((b=a&&a.options,b=null==b||b===!1?b:b.toolbar,b=null==b||b===!1?b:b.size,typeof b===j?b.apply(a):b)),c}function g(a,b){var d,e="";return e+='\n <a class="btn ',d=c["if"].call(a,(d=a&&a.options,d=null==d||d===!1?d:d.toolbar,null==d||d===!1?d:d.size),{hash:{},inverse:l.noop,fn:l.program(1,f,b),data:b}),(d||0===d)&&(e+=d),e+=' btn-default" data-wysihtml5-command="small" title="CTRL+S" tabindex="-1">'+k((d=a&&a.locale,d=null==d||d===!1?d:d.emphasis,d=null==d||d===!1?d:d.small,typeof d===j?d.apply(a):d))+"</a>\n ",e}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return i+='<li>\n <div class="btn-group">\n <a class="btn ',h=c["if"].call(b,(h=b&&b.options,h=null==h||h===!1?h:h.toolbar,null==h||h===!1?h:h.size),{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+=' btn-default" data-wysihtml5-command="bold" title="CTRL+B" tabindex="-1">'+k((h=b&&b.locale,h=null==h||h===!1?h:h.emphasis,h=null==h||h===!1?h:h.bold,typeof h===j?h.apply(b):h))+'</a>\n <a class="btn ',h=c["if"].call(b,(h=b&&b.options,h=null==h||h===!1?h:h.toolbar,null==h||h===!1?h:h.size),{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+=' btn-default" data-wysihtml5-command="italic" title="CTRL+I" tabindex="-1">'+k((h=b&&b.locale,h=null==h||h===!1?h:h.emphasis,h=null==h||h===!1?h:h.italic,typeof h===j?h.apply(b):h))+'</a>\n <a class="btn ',h=c["if"].call(b,(h=b&&b.options,h=null==h||h===!1?h:h.toolbar,null==h||h===!1?h:h.size),{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+=' btn-default" data-wysihtml5-command="underline" title="CTRL+U" tabindex="-1">'+k((h=b&&b.locale,h=null==h||h===!1?h:h.emphasis,h=null==h||h===!1?h:h.underline,typeof h===j?h.apply(b):h))+"</a>\n ",h=c["if"].call(b,(h=b&&b.options,h=null==h||h===!1?h:h.toolbar,h=null==h||h===!1?h:h.emphasis,null==h||h===!1?h:h.small),{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+="\n </div>\n</li>\n",i
+}),this.wysihtml5.tpl["font-styles"]=Handlebars.template(function(a,b,c,d,e){function f(a){var b,c="";return c+="btn-"+l((b=a&&a.options,b=null==b||b===!1?b:b.toolbar,b=null==b||b===!1?b:b.size,typeof b===k?b.apply(a):b)),c}function g(){return'\n <span class="fa fa-font"></span>\n '}function h(){return'\n <span class="glyphicon glyphicon-font"></span>\n '}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var i,j="",k="function",l=this.escapeExpression,m=this;return j+='<li class="dropdown">\n <a class="btn btn-default dropdown-toggle ',i=c["if"].call(b,(i=b&&b.options,i=null==i||i===!1?i:i.toolbar,null==i||i===!1?i:i.size),{hash:{},inverse:m.noop,fn:m.program(1,f,e),data:e}),(i||0===i)&&(j+=i),j+='" data-toggle="dropdown">\n ',i=c["if"].call(b,(i=b&&b.options,i=null==i||i===!1?i:i.toolbar,null==i||i===!1?i:i.fa),{hash:{},inverse:m.program(5,h,e),fn:m.program(3,g,e),data:e}),(i||0===i)&&(j+=i),j+='\n <span class="current-font">'+l((i=b&&b.locale,i=null==i||i===!1?i:i.font_styles,i=null==i||i===!1?i:i.normal,typeof i===k?i.apply(b):i))+'</span>\n <b class="caret"></b>\n </a>\n <ul class="dropdown-menu">\n <li><a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="p" tabindex="-1">'+l((i=b&&b.locale,i=null==i||i===!1?i:i.font_styles,i=null==i||i===!1?i:i.normal,typeof i===k?i.apply(b):i))+'</a></li>\n <li><a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1" tabindex="-1">'+l((i=b&&b.locale,i=null==i||i===!1?i:i.font_styles,i=null==i||i===!1?i:i.h1,typeof i===k?i.apply(b):i))+'</a></li>\n <li><a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h2" tabindex="-1">'+l((i=b&&b.locale,i=null==i||i===!1?i:i.font_styles,i=null==i||i===!1?i:i.h2,typeof i===k?i.apply(b):i))+'</a></li>\n <li><a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h3" tabindex="-1">'+l((i=b&&b.locale,i=null==i||i===!1?i:i.font_styles,i=null==i||i===!1?i:i.h3,typeof i===k?i.apply(b):i))+'</a></li>\n <li><a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h4" tabindex="-1">'+l((i=b&&b.locale,i=null==i||i===!1?i:i.font_styles,i=null==i||i===!1?i:i.h4,typeof i===k?i.apply(b):i))+'</a></li>\n <li><a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h5" tabindex="-1">'+l((i=b&&b.locale,i=null==i||i===!1?i:i.font_styles,i=null==i||i===!1?i:i.h5,typeof i===k?i.apply(b):i))+'</a></li>\n <li><a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h6" tabindex="-1">'+l((i=b&&b.locale,i=null==i||i===!1?i:i.font_styles,i=null==i||i===!1?i:i.h6,typeof i===k?i.apply(b):i))+"</a></li>\n </ul>\n</li>\n",j}),this.wysihtml5.tpl.html=Handlebars.template(function(a,b,c,d,e){function f(a){var b,c="";return c+="btn-"+l((b=a&&a.options,b=null==b||b===!1?b:b.toolbar,b=null==b||b===!1?b:b.size,typeof b===k?b.apply(a):b)),c}function g(){return'\n <span class="fa fa-pencil"></span>\n '}function h(){return'\n <span class="glyphicon glyphicon-pencil"></span>\n '}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var i,j="",k="function",l=this.escapeExpression,m=this;return j+='<li>\n <div class="btn-group">\n <a class="btn ',i=c["if"].call(b,(i=b&&b.options,i=null==i||i===!1?i:i.toolbar,null==i||i===!1?i:i.size),{hash:{},inverse:m.noop,fn:m.program(1,f,e),data:e}),(i||0===i)&&(j+=i),j+=' btn-default" data-wysihtml5-action="change_view" title="'+l((i=b&&b.locale,i=null==i||i===!1?i:i.html,i=null==i||i===!1?i:i.edit,typeof i===k?i.apply(b):i))+'" tabindex="-1">\n ',i=c["if"].call(b,(i=b&&b.options,i=null==i||i===!1?i:i.toolbar,null==i||i===!1?i:i.fa),{hash:{},inverse:m.program(5,h,e),fn:m.program(3,g,e),data:e}),(i||0===i)&&(j+=i),j+="\n </a>\n </div>\n</li>\n",j}),this.wysihtml5.tpl.image=Handlebars.template(function(a,b,c,d,e){function f(){return"modal-sm"}function g(a){var b,c="";return c+="btn-"+m((b=a&&a.options,b=null==b||b===!1?b:b.toolbar,b=null==b||b===!1?b:b.size,typeof b===l?b.apply(a):b)),c}function h(){return'\n <span class="fa fa-file-image-o"></span>\n '}function i(){return'\n <span class="glyphicon glyphicon-picture"></span>\n '}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var j,k="",l="function",m=this.escapeExpression,n=this;return k+='<li>\n <div class="bootstrap-wysihtml5-insert-image-modal modal fade" data-wysihtml5-dialog="insertImage">\n <div class="modal-dialog ',j=c["if"].call(b,(j=b&&b.options,j=null==j||j===!1?j:j.toolbar,null==j||j===!1?j:j.smallmodals),{hash:{},inverse:n.noop,fn:n.program(1,f,e),data:e}),(j||0===j)&&(k+=j),k+='">\n <div class="modal-content">\n <div class="modal-header">\n <a class="close" data-dismiss="modal">&times;</a>\n <h3>'+m((j=b&&b.locale,j=null==j||j===!1?j:j.image,j=null==j||j===!1?j:j.insert,typeof j===l?j.apply(b):j))+'</h3>\n </div>\n <div class="modal-body">\n <div class="form-group">\n <input value="http://" class="bootstrap-wysihtml5-insert-image-url form-control" data-wysihtml5-dialog-field="src">\n </div> \n </div>\n <div class="modal-footer">\n <a class="btn btn-default" data-dismiss="modal" data-wysihtml5-dialog-action="cancel" href="#">'+m((j=b&&b.locale,j=null==j||j===!1?j:j.image,j=null==j||j===!1?j:j.cancel,typeof j===l?j.apply(b):j))+'</a>\n <a class="btn btn-primary" data-dismiss="modal" data-wysihtml5-dialog-action="save" href="#">'+m((j=b&&b.locale,j=null==j||j===!1?j:j.image,j=null==j||j===!1?j:j.insert,typeof j===l?j.apply(b):j))+'</a>\n </div>\n </div>\n </div>\n </div>\n <a class="btn ',j=c["if"].call(b,(j=b&&b.options,j=null==j||j===!1?j:j.toolbar,null==j||j===!1?j:j.size),{hash:{},inverse:n.noop,fn:n.program(3,g,e),data:e}),(j||0===j)&&(k+=j),k+=' btn-default" data-wysihtml5-command="insertImage" title="'+m((j=b&&b.locale,j=null==j||j===!1?j:j.image,j=null==j||j===!1?j:j.insert,typeof j===l?j.apply(b):j))+'" tabindex="-1">\n ',j=c["if"].call(b,(j=b&&b.options,j=null==j||j===!1?j:j.toolbar,null==j||j===!1?j:j.fa),{hash:{},inverse:n.program(7,i,e),fn:n.program(5,h,e),data:e}),(j||0===j)&&(k+=j),k+="\n </a>\n</li>\n",k}),this.wysihtml5.tpl.link=Handlebars.template(function(a,b,c,d,e){function f(){return"modal-sm"}function g(a){var b,c="";return c+="btn-"+m((b=a&&a.options,b=null==b||b===!1?b:b.toolbar,b=null==b||b===!1?b:b.size,typeof b===l?b.apply(a):b)),c}function h(){return'\n <span class="fa fa-share-square-o"></span>\n '}function i(){return'\n <span class="glyphicon glyphicon-share"></span>\n '}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var j,k="",l="function",m=this.escapeExpression,n=this;return k+='<li>\n <div class="bootstrap-wysihtml5-insert-link-modal modal fade" data-wysihtml5-dialog="createLink">\n <div class="modal-dialog ',j=c["if"].call(b,(j=b&&b.options,j=null==j||j===!1?j:j.toolbar,null==j||j===!1?j:j.smallmodals),{hash:{},inverse:n.noop,fn:n.program(1,f,e),data:e}),(j||0===j)&&(k+=j),k+='">\n <div class="modal-content">\n <div class="modal-header">\n <a class="close" data-dismiss="modal">&times;</a>\n <h3>'+m((j=b&&b.locale,j=null==j||j===!1?j:j.link,j=null==j||j===!1?j:j.insert,typeof j===l?j.apply(b):j))+'</h3>\n </div>\n <div class="modal-body">\n <div class="form-group">\n <input value="http://" class="bootstrap-wysihtml5-insert-link-url form-control" data-wysihtml5-dialog-field="href">\n </div> \n <div class="checkbox">\n <label> \n <input type="checkbox" class="bootstrap-wysihtml5-insert-link-target" checked>'+m((j=b&&b.locale,j=null==j||j===!1?j:j.link,j=null==j||j===!1?j:j.target,typeof j===l?j.apply(b):j))+'\n </label>\n </div>\n </div>\n <div class="modal-footer">\n <a class="btn btn-default" data-dismiss="modal" data-wysihtml5-dialog-action="cancel" href="#">'+m((j=b&&b.locale,j=null==j||j===!1?j:j.link,j=null==j||j===!1?j:j.cancel,typeof j===l?j.apply(b):j))+'</a>\n <a href="#" class="btn btn-primary" data-dismiss="modal" data-wysihtml5-dialog-action="save">'+m((j=b&&b.locale,j=null==j||j===!1?j:j.link,j=null==j||j===!1?j:j.insert,typeof j===l?j.apply(b):j))+'</a>\n </div>\n </div>\n </div>\n </div>\n <a class="btn ',j=c["if"].call(b,(j=b&&b.options,j=null==j||j===!1?j:j.toolbar,null==j||j===!1?j:j.size),{hash:{},inverse:n.noop,fn:n.program(3,g,e),data:e}),(j||0===j)&&(k+=j),k+=' btn-default" data-wysihtml5-command="createLink" title="'+m((j=b&&b.locale,j=null==j||j===!1?j:j.link,j=null==j||j===!1?j:j.insert,typeof j===l?j.apply(b):j))+'" tabindex="-1">\n ',j=c["if"].call(b,(j=b&&b.options,j=null==j||j===!1?j:j.toolbar,null==j||j===!1?j:j.fa),{hash:{},inverse:n.program(7,i,e),fn:n.program(5,h,e),data:e}),(j||0===j)&&(k+=j),k+="\n </a>\n</li>\n",k}),this.wysihtml5.tpl.lists=Handlebars.template(function(a,b,c,d,e){function f(a){var b,c="";return c+="btn-"+r((b=a&&a.options,b=null==b||b===!1?b:b.toolbar,b=null==b||b===!1?b:b.size,typeof b===q?b.apply(a):b)),c}function g(){return'\n <span class="fa fa-list-ul"></span>\n '}function h(){return'\n <span class="glyphicon glyphicon-list"></span>\n '}function i(){return'\n <span class="fa fa-list-ol"></span>\n '}function j(){return'\n <span class="glyphicon glyphicon-th-list"></span>\n '}function k(){return'\n <span class="fa fa-outdent"></span>\n '}function l(){return'\n <span class="glyphicon glyphicon-indent-right"></span>\n '}function m(){return'\n <span class="fa fa-indent"></span>\n '}function n(){return'\n <span class="glyphicon glyphicon-indent-left"></span>\n '}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var o,p="",q="function",r=this.escapeExpression,s=this;return p+='<li>\n <div class="btn-group">\n <a class="btn ',o=c["if"].call(b,(o=b&&b.options,o=null==o||o===!1?o:o.toolbar,null==o||o===!1?o:o.size),{hash:{},inverse:s.noop,fn:s.program(1,f,e),data:e}),(o||0===o)&&(p+=o),p+=' btn-default" data-wysihtml5-command="insertUnorderedList" title="'+r((o=b&&b.locale,o=null==o||o===!1?o:o.lists,o=null==o||o===!1?o:o.unordered,typeof o===q?o.apply(b):o))+'" tabindex="-1">\n ',o=c["if"].call(b,(o=b&&b.options,o=null==o||o===!1?o:o.toolbar,null==o||o===!1?o:o.fa),{hash:{},inverse:s.program(5,h,e),fn:s.program(3,g,e),data:e}),(o||0===o)&&(p+=o),p+='\n </a>\n <a class="btn ',o=c["if"].call(b,(o=b&&b.options,o=null==o||o===!1?o:o.toolbar,null==o||o===!1?o:o.size),{hash:{},inverse:s.noop,fn:s.program(1,f,e),data:e}),(o||0===o)&&(p+=o),p+=' btn-default" data-wysihtml5-command="insertOrderedList" title="'+r((o=b&&b.locale,o=null==o||o===!1?o:o.lists,o=null==o||o===!1?o:o.ordered,typeof o===q?o.apply(b):o))+'" tabindex="-1">\n ',o=c["if"].call(b,(o=b&&b.options,o=null==o||o===!1?o:o.toolbar,null==o||o===!1?o:o.fa),{hash:{},inverse:s.program(9,j,e),fn:s.program(7,i,e),data:e}),(o||0===o)&&(p+=o),p+='\n </a>\n <a class="btn ',o=c["if"].call(b,(o=b&&b.options,o=null==o||o===!1?o:o.toolbar,null==o||o===!1?o:o.size),{hash:{},inverse:s.noop,fn:s.program(1,f,e),data:e}),(o||0===o)&&(p+=o),p+=' btn-default" data-wysihtml5-command="Outdent" title="'+r((o=b&&b.locale,o=null==o||o===!1?o:o.lists,o=null==o||o===!1?o:o.outdent,typeof o===q?o.apply(b):o))+'" tabindex="-1">\n ',o=c["if"].call(b,(o=b&&b.options,o=null==o||o===!1?o:o.toolbar,null==o||o===!1?o:o.fa),{hash:{},inverse:s.program(13,l,e),fn:s.program(11,k,e),data:e}),(o||0===o)&&(p+=o),p+='\n </a>\n <a class="btn ',o=c["if"].call(b,(o=b&&b.options,o=null==o||o===!1?o:o.toolbar,null==o||o===!1?o:o.size),{hash:{},inverse:s.noop,fn:s.program(1,f,e),data:e}),(o||0===o)&&(p+=o),p+=' btn-default" data-wysihtml5-command="Indent" title="'+r((o=b&&b.locale,o=null==o||o===!1?o:o.lists,o=null==o||o===!1?o:o.indent,typeof o===q?o.apply(b):o))+'" tabindex="-1">\n ',o=c["if"].call(b,(o=b&&b.options,o=null==o||o===!1?o:o.toolbar,null==o||o===!1?o:o.fa),{hash:{},inverse:s.program(17,n,e),fn:s.program(15,m,e),data:e}),(o||0===o)&&(p+=o),p+="\n </a>\n </div>\n</li>\n",p}),function(a){"use strict";"function"==typeof define&&define.amd?define("bootstrap.wysihtml5",["jquery","wysihtml5","bootstrap","bootstrap.wysihtml5.templates","bootstrap.wysihtml5.commands"],a):a(jQuery,wysihtml5)}(function(a,b){"use strict";var c=function(a,b){var c,d,e,f=function(a,c,d){return b.tpl[a]?b.tpl[a]({locale:c,options:d}):void 0},g=function(c,e){var f,g;this.el=c,f=a.extend(!0,{},d,e);for(g in f.customTemplates)f.customTemplates.hasOwnProperty(g)&&(b.tpl[g]=f.customTemplates[g]);this.toolbar=this.createToolbar(c,f),this.editor=this.createEditor(f)};g.prototype={constructor:g,createEditor:function(b){b=b||{},b=a.extend(!0,{},b),b.toolbar=this.toolbar[0],this.initializeEditor(this.el[0],b)},initializeEditor:function(a,c){var d,e=new b.Editor(this.el[0],c);if(e.on("beforeload",this.syncBootstrapDialogEvents),e.on("beforeload",this.loadParserRules),e.composer.editableArea.contentDocument?this.addMoreShortcuts(e,e.composer.editableArea.contentDocument.body||e.composer.editableArea.contentDocument,c.shortcuts):this.addMoreShortcuts(e,e.composer.editableArea,c.shortcuts),c&&c.events)for(d in c.events)c.events.hasOwnProperty(d)&&e.on(d,c.events[d]);return e},loadParserRules:function(){"string"===a.type(this.config.parserRules)&&a.ajax({dataType:"json",url:this.config.parserRules,context:this,error:function(a,b,c){void 0},success:function(a){this.config.parserRules=a,void 0}}),this.config.pasteParserRulesets&&"string"===a.type(this.config.pasteParserRulesets)&&a.ajax({dataType:"json",url:this.config.pasteParserRulesets,context:this,error:function(a,b,c){void 0},success:function(a){this.config.pasteParserRulesets=a}})},syncBootstrapDialogEvents:function(){var b=this;a.map(this.toolbar.commandMapping,function(a){return[a]}).filter(function(a){return a.dialog}).map(function(a){return a.dialog}).forEach(function(c){c.on("show",function(){a(this.container).modal("show")}),c.on("hide",function(){a(this.container).modal("hide"),setTimeout(b.composer.focus,0)}),a(c.container).on("shown.bs.modal",function(){a(this).find("input, select, textarea").first().focus()})}),this.on("change_view",function(){a(this.toolbar.container.children).find("a.btn").not('[data-wysihtml5-action="change_view"]').toggleClass("disabled")})},createToolbar:function(b,c){var g,h,i=this,j=a("<ul/>",{"class":"wysihtml5-toolbar",style:"display:none"}),k=c.locale||d.locale||"en";e.hasOwnProperty(k)||(void 0,k="en"),g=a.extend(!0,{},e.en,e[k]);for(h in c.toolbar)c.toolbar[h]&&j.append(f(h,g,c));return j.find('a[data-wysihtml5-command="formatBlock"]').click(function(b){var c=b.delegateTarget||b.target||b.srcElement,d=a(c),e=d.data("wysihtml5-display-format-name"),f=d.data("wysihtml5-format-name")||d.html();(void 0===e||"true"===e)&&i.toolbar.find(".current-font").text(f)}),j.find('a[data-wysihtml5-command="foreColor"]').click(function(b){var c=b.target||b.srcElement,d=a(c);i.toolbar.find(".current-color").text(d.html())}),this.el.before(j),j},addMoreShortcuts:function(a,c,d){b.dom.observe(c,"keydown",function(c){var e,f=c.keyCode,g=d[f];(c.ctrlKey||c.metaKey||c.altKey)&&g&&b.commands[g]&&(e=a.toolbar.commandMapping[g+":null"],e&&e.dialog&&!e.state?e.dialog.show():b.commands[g].exec(a.composer,g),c.preventDefault())})}},c={resetDefaults:function(){a.fn.wysihtml5.defaultOptions=a.extend(!0,{},a.fn.wysihtml5.defaultOptionsCache)},bypassDefaults:function(b){return this.each(function(){var c=a(this);c.data("wysihtml5",new g(c,b))})},shallowExtend:function(b){var d=a.extend({},a.fn.wysihtml5.defaultOptions,b||{},a(this).data()),e=this;return c.bypassDefaults.apply(e,[d])},deepExtend:function(b){var d=a.extend(!0,{},a.fn.wysihtml5.defaultOptions,b||{}),e=this;return c.bypassDefaults.apply(e,[d])},init:function(a){var b=this;return c.shallowExtend.apply(b,[a])}},a.fn.wysihtml5=function(b){return c[b]?c[b].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof b&&b?(a.error("Method "+b+" does not exist on jQuery.wysihtml5"),void 0):c.init.apply(this,arguments)},a.fn.wysihtml5.Constructor=g,d=a.fn.wysihtml5.defaultOptions={toolbar:{"font-styles":!0,color:!1,emphasis:{small:!0},blockquote:!0,lists:!0,html:!1,link:!0,image:!0,smallmodals:!1},useLineBreaks:!1,parserRules:{classes:{"wysiwyg-color-silver":1,"wysiwyg-color-gray":1,"wysiwyg-color-white":1,"wysiwyg-color-maroon":1,"wysiwyg-color-red":1,"wysiwyg-color-purple":1,"wysiwyg-color-fuchsia":1,"wysiwyg-color-green":1,"wysiwyg-color-lime":1,"wysiwyg-color-olive":1,"wysiwyg-color-yellow":1,"wysiwyg-color-navy":1,"wysiwyg-color-blue":1,"wysiwyg-color-teal":1,"wysiwyg-color-aqua":1,"wysiwyg-color-orange":1},tags:{b:{},i:{},strong:{},em:{},p:{},br:{},ol:{},ul:{},li:{},h1:{},h2:{},h3:{},h4:{},h5:{},h6:{},blockquote:{},u:1,img:{check_attributes:{width:"numbers",alt:"alt",src:"url",height:"numbers"}},a:{check_attributes:{href:"url"},set_attributes:{target:"_blank",rel:"nofollow"}},span:1,div:1,small:1,code:1,pre:1}},locale:"en",shortcuts:{83:"small",75:"createLink"}},void 0===a.fn.wysihtml5.defaultOptionsCache&&(a.fn.wysihtml5.defaultOptionsCache=a.extend(!0,{},a.fn.wysihtml5.defaultOptions)),e=a.fn.wysihtml5.locale={}};c(a,b)}),function(a){a.commands.small={exec:function(b,c){return a.commands.formatInline.exec(b,c,"small")},state:function(b,c){return a.commands.formatInline.state(b,c,"small")}}}(wysihtml5),function(a){"function"==typeof define&&define.amd?define("bootstrap.wysihtml5.en-US",["jquery","bootstrap.wysihtml5"],a):a(jQuery)}(function(a){a.fn.wysihtml5.locale.en=a.fn.wysihtml5.locale["en-US"]={font_styles:{normal:"Normal text",h1:"Heading 1",h2:"Heading 2",h3:"Heading 3",h4:"Heading 4",h5:"Heading 5",h6:"Heading 6"},emphasis:{bold:"Bold",italic:"Italic",underline:"Underline",small:"Small"},lists:{unordered:"Unordered list",ordered:"Ordered list",outdent:"Outdent",indent:"Indent"},link:{insert:"Insert link",cancel:"Cancel",target:"Open link in new window"},image:{insert:"Insert image",cancel:"Cancel"},html:{edit:"Edit HTML"},colours:{black:"Black",silver:"Silver",gray:"Grey",maroon:"Maroon",red:"Red",purple:"Purple",green:"Green",olive:"Olive",navy:"Navy",blue:"Blue",orange:"Orange"}}}); \ No newline at end of file
diff --git a/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.css b/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.css
new file mode 100644
index 0000000..51c58df
--- /dev/null
+++ b/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.css
@@ -0,0 +1,117 @@
+ul.wysihtml5-toolbar {
+ margin: 0;
+ padding: 0;
+ display: block;
+}
+
+ul.wysihtml5-toolbar::after {
+ clear: both;
+ display: table;
+ content: "";
+}
+
+ul.wysihtml5-toolbar > li {
+ float: left;
+ display: list-item;
+ list-style: none;
+ margin: 0 5px 10px 0;
+}
+
+ul.wysihtml5-toolbar a[data-wysihtml5-command=bold] {
+ font-weight: bold;
+}
+
+ul.wysihtml5-toolbar a[data-wysihtml5-command=italic] {
+ font-style: italic;
+}
+
+ul.wysihtml5-toolbar a[data-wysihtml5-command=underline] {
+ text-decoration: underline;
+}
+
+ul.wysihtml5-toolbar a.btn.wysihtml5-command-active {
+ background-image: none;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+ background-color: #E6E6E6;
+ background-color: #D9D9D9;
+ outline: 0;
+}
+
+ul.wysihtml5-commands-disabled .dropdown-menu {
+ display: none !important;
+}
+
+ul.wysihtml5-toolbar div.wysihtml5-colors {
+ display:block;
+ width: 50px;
+ height: 20px;
+ margin-top: 2px;
+ margin-left: 5px;
+ position: absolute;
+ pointer-events: none;
+}
+
+ul.wysihtml5-toolbar a.wysihtml5-colors-title {
+ padding-left: 70px;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="black"] {
+ background: black !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="silver"] {
+ background: silver !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="gray"] {
+ background: gray !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="maroon"] {
+ background: maroon !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="red"] {
+ background: red !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="purple"] {
+ background: purple !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="green"] {
+ background: green !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="olive"] {
+ background: olive !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="navy"] {
+ background: navy !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="blue"] {
+ background: blue !important;
+}
+
+ul.wysihtml5-toolbar div[data-wysihtml5-command-value="orange"] {
+ background: orange !important;
+}
+
+.glyphicon-quote:before {
+ content: "\201C";
+ font-family: Georgia, serif;
+ font-size: 50px;
+ position: absolute;
+ top: -4px;
+ left: -3px;
+ max-height: 100%;
+}
+
+.glyphicon-quote:after {
+ content: "\0000a0";
+}
+
diff --git a/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css b/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css
new file mode 100644
index 0000000..28f1831
--- /dev/null
+++ b/public/bower_components/admin-lte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css
@@ -0,0 +1,3 @@
+/*! bootstrap3-wysihtml5-bower 2014-09-26 */
+
+ul.wysihtml5-toolbar{margin:0;padding:0;display:block}ul.wysihtml5-toolbar::after{clear:both;display:table;content:""}ul.wysihtml5-toolbar>li{float:left;display:list-item;list-style:none;margin:0 5px 10px 0}ul.wysihtml5-toolbar a[data-wysihtml5-command=bold]{font-weight:700}ul.wysihtml5-toolbar a[data-wysihtml5-command=italic]{font-style:italic}ul.wysihtml5-toolbar a[data-wysihtml5-command=underline]{text-decoration:underline}ul.wysihtml5-toolbar a.btn.wysihtml5-command-active{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);background-color:#D9D9D9;outline:0}ul.wysihtml5-commands-disabled .dropdown-menu{display:none!important}ul.wysihtml5-toolbar div.wysihtml5-colors{display:block;width:50px;height:20px;margin-top:2px;margin-left:5px;position:absolute;pointer-events:none}ul.wysihtml5-toolbar a.wysihtml5-colors-title{padding-left:70px}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=black]{background:#000!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=silver]{background:silver!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=gray]{background:gray!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=maroon]{background:maroon!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=red]{background:red!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=purple]{background:purple!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=green]{background:green!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=olive]{background:olive!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=navy]{background:navy!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=blue]{background:#00f!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=orange]{background:orange!important}.glyphicon-quote:before{content:"\201C";font-family:Georgia,serif;font-size:50px;position:absolute;top:-4px;left:-3px;max-height:100%}.glyphicon-quote:after{content:"\0000a0"} \ No newline at end of file