vendor/assets/javascripts/wysihtml5x.js in wysihtml5x-rails-0.4.0 vs vendor/assets/javascripts/wysihtml5x.js in wysihtml5x-rails-0.4.1
- old
+ new
@@ -1,41 +1,42 @@
/**
- * @license wysihtml5x v0.4.0
+ * @license wysihtml5x v0.4.1
* 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.0",
-
+ version: "0.4.1",
+
// 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 2012, Tim Down
Licensed under the MIT license.
@@ -125,147 +126,147 @@
h=c.getBody(h).createControlRange();for(var D,G=false,P=0,X=d.length;P<X;++P){D=d.item(P);if(D!==b||G)h.add(d.item(P));else G=true}h.select();n(this)}else ba(this,b)}:function(b){ba(this,b)};var aa;if(!J&&W&&l.features.implementsDomRange){aa=function(b){var d=false;if(b.anchorNode)d=c.comparePoints(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset)==1;return d};g.isBackwards=function(){return aa(this)}}else aa=g.isBackwards=function(){return false};g.toString=function(){for(var b=[],d=0,h=this.rangeCount;d<
h;++d)b[d]=""+this._ranges[d];return b.join("")};g.collapse=function(b,d){q(this,b);var h=l.createRange(c.getDocument(b));h.collapseToPoint(b,d);this.removeAllRanges();this.addRange(h);this.isCollapsed=true};g.collapseToStart=function(){if(this.rangeCount){var b=this._ranges[0];this.collapse(b.startContainer,b.startOffset)}else throw new L("INVALID_STATE_ERR");};g.collapseToEnd=function(){if(this.rangeCount){var b=this._ranges[this.rangeCount-1];this.collapse(b.endContainer,b.endOffset)}else throw new L("INVALID_STATE_ERR");
};g.selectAllChildren=function(b){q(this,b);var d=l.createRange(c.getDocument(b));d.selectNodeContents(b);this.removeAllRanges();this.addRange(d)};g.deleteFromDocument=function(){if(M&&V&&this.docSelection.type=="Control"){for(var b=this.docSelection.createRange(),d;b.length;){d=b.item(0);b.remove(d);d.parentNode.removeChild(d)}this.refresh()}else if(this.rangeCount){b=this.getAllRanges();this.removeAllRanges();d=0;for(var h=b.length;d<h;++d)b[d].deleteContents();this.addRange(b[h-1])}};g.getAllRanges=
function(){return this._ranges.slice(0)};g.setSingleRange=function(b){this.setRanges([b])};g.containsNode=function(b,d){for(var h=0,D=this._ranges.length;h<D;++h)if(this._ranges[h].containsNode(b,d))return true;return false};g.toHtml=function(){var b="";if(this.rangeCount){b=k.getRangeDocument(this._ranges[0]).createElement("div");for(var d=0,h=this._ranges.length;d<h;++d)b.appendChild(this._ranges[d].cloneContents());b=b.innerHTML}return b};g.getName=function(){return"WrappedSelection"};g.inspect=
function(){return v(this)};g.detach=function(){this.win=this.anchorNode=this.focusNode=this.win._rangySelection=null};x.inspect=v;l.Selection=x;l.selectionPrototype=g;l.addCreateMissingNativeApiListener(function(b){if(typeof b.getSelection=="undefined")b.getSelection=function(){return l.getSelection(this)};b=null})});/*
- Base.js, version 1.1a
- Copyright 2006-2010, Dean Edwards
- License: http://www.opensource.org/licenses/mit-license.php
+ Base.js, version 1.1a
+ Copyright 2006-2010, Dean Edwards
+ License: http://www.opensource.org/licenses/mit-license.php
*/
var Base = function() {
- // dummy
+ // 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);
+ 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;
+ 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]);
+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;
- }
+ }
+ }
+ // 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]);
- }
+ 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());
- }
+ 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,
@@ -274,23 +275,23 @@
isIE = userAgent.indexOf("MSIE") !== -1 && userAgent.indexOf("Opera") === -1,
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];
}
-
+
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
@@ -310,23 +311,23 @@
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
@@ -361,11 +362,11 @@
* 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";
@@ -393,11 +394,11 @@
* 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
*
@@ -432,11 +433,11 @@
// 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 || isWebKit,
"insertOrderedList": isIE || isWebKit
};
-
+
// Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
var supported = {
"insertHTML": isGecko
};
@@ -541,23 +542,23 @@
* See https://developer.mozilla.org/en/DOM/Selection/modify
*/
supportsSelectionModify: function() {
return "getSelection" in window && "modify" in window.getSelection();
},
-
+
// Returns if there is a way for setting selection to expand a line
supportsSelectLine: function () {
return (this.supportsSelectionModify() || document.selection) ? true : false;
},
-
+
/**
* 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
@@ -568,72 +569,73 @@
*/
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 && (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);
}
};
-})();wysihtml5.lang.array = function(arr) {
+})();
+wysihtml5.lang.array = function(arr) {
return {
/**
* Check whether a given object exists in an array
*
* @example
@@ -641,11 +643,11 @@
* // => true
*/
contains: function(needle) {
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
@@ -660,11 +662,11 @@
if (arr[i] === needle) { return i; }
}
return -1;
}
},
-
+
/**
* Substract one array from another
*
* @example
* wysihtml5.lang.array([1, 2, 3, 4]).without([3, 4]);
@@ -680,14 +682,14 @@
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() {
@@ -697,11 +699,11 @@
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
@@ -722,11 +724,12 @@
}
return A;
}
}
};
-};wysihtml5.lang.Dispatcher = Base.extend(
+};
+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);
@@ -751,31 +754,32 @@
// 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) {
+});
+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 }
@@ -784,15 +788,15 @@
for (var i in otherObj) {
obj[i] = otherObj[i];
}
return this;
},
-
+
get: function() {
return obj;
},
-
+
/**
* @example
* wysihtml5.lang.object({ foo: 1 }).clone();
* // => { foo: 1 }
*/
@@ -802,21 +806,22 @@
for (i in obj) {
newObj[i] = obj[i];
}
return newObj;
},
-
+
/**
* @example
* wysihtml5.lang.object([]).isArray();
* // => true
*/
isArray: function() {
return Object.prototype.toString.call(obj) === "[object Array]";
}
};
-};(function() {
+};
+(function() {
var WHITE_SPACE_START = /^\s+/,
WHITE_SPACE_END = /\s+$/,
ENTITY_REG_EXP = /[&<>"]/g,
ENTITY_MAP = {
'&': '&',
@@ -833,11 +838,11 @@
* // => "foo"
*/
trim: function() {
return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, "");
},
-
+
/**
* @example
* wysihtml5.lang.string("Hello #{name}").interpolate({ name: "Christopher" });
* // => "Hello Christopher"
*/
@@ -845,11 +850,11 @@
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"
*/
@@ -858,22 +863,23 @@
by: function(replace) {
return str.split(search).join(replace);
}
};
},
-
+
/**
* @example
* wysihtml5.lang.string("hello<br>").escapeHTML();
* // => "hello<br>"
*/
escapeHTML: function() {
return str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
}
};
};
-})();/**
+})();
+/**
* 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
*
@@ -898,11 +904,11 @@
*/
URL_REG_EXP = /((https?:\/\/|www\.)[^\s<]{3,})/gi,
TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i,
MAX_DISPLAY_LENGTH = 100,
BRACKETS = { ")": "(", "]": "[", "}": "{" };
-
+
function autoLink(element) {
if (_hasParentThatShouldBeIgnored(element)) {
return element;
}
@@ -910,11 +916,11 @@
element = element.ownerDocument.body;
}
return _parseNode(element);
}
-
+
/**
* This is basically a rebuild of
* the rails auto_link_urls text helper
*/
function _convertUrlsToLinks(str) {
@@ -934,15 +940,15 @@
}
// 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) {
@@ -950,31 +956,31 @@
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) {
var nodeName;
while (node.parentNode) {
node = node.parentNode;
nodeName = node.nodeName;
@@ -984,65 +990,66 @@
return false;
}
}
return false;
}
-
+
function _parseNode(element) {
if (IGNORE_URLS_IN.contains(element.nodeName)) {
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]);
}
-
+
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) {
+})(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() {
@@ -1058,11 +1065,12 @@
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
@@ -1090,21 +1098,21 @@
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,
@@ -1114,11 +1122,11 @@
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") {
@@ -1126,53 +1134,54 @@
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
+ * 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]"),
@@ -1197,131 +1206,134 @@
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
+ * 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
+ * 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);/**
+})(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);/**
+
+})(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) {
@@ -1330,22 +1342,22 @@
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");
@@ -1357,11 +1369,12 @@
_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)
@@ -1373,45 +1386,45 @@
* 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 _getParentElementWithNodeName(node, nodeName, levels) {
while (levels-- && node && node.nodeName !== "BODY") {
if (_isSameNodeName(node.nodeName, nodeName)) {
return node;
}
node = node.parentNode;
}
return null;
}
-
+
function _getParentElementWithNodeNameAndClassName(node, nodeName, className, classRegExp, levels) {
while (levels-- && node && node.nodeName !== "BODY") {
if (_isElement(node) &&
_isSameNodeName(node.nodeName, nodeName) &&
_hasClassName(node, className, classRegExp)) {
@@ -1419,11 +1432,11 @@
}
node = node.parentNode;
}
return null;
}
-
+
return function(node, matchingSet, levels) {
levels = levels || 50; // Go max 50 nodes upwards from current node
if (matchingSet.className || matchingSet.classRegExp) {
return _getParentElementWithNodeNameAndClassName(
node, matchingSet.nodeName, matchingSet.className, matchingSet.classRegExp, levels
@@ -1448,33 +1461,33 @@
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
@@ -1505,11 +1518,12 @@
return returnValue;
}
}
};
};
-})();/**
+})();
+/**
* 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
@@ -1518,25 +1532,26 @@
* 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
@@ -1549,11 +1564,11 @@
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);
@@ -1571,33 +1586,34 @@
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) {
+};
+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 {
@@ -1606,24 +1622,25 @@
head.appendChild(styleElement);
}
}
}
};
-};/**
+};
+/**
* 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 {
@@ -1640,11 +1657,11 @@
handler.call(element, event);
};
element.attachEvent("on" + eventName, handlerWrapper);
}
}
-
+
return {
stop: function() {
var eventName,
i = 0,
length = eventNames.length;
@@ -1710,11 +1727,11 @@
* });
* // => '<p class="red">foo</p><p>bar</p>'
*/
wysihtml5.dom.parse = (function() {
-
+
/**
* 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
*
@@ -1728,111 +1745,112 @@
DEFAULT_NODE_NAME = "span",
WHITE_SPACE_REG_EXP = /\s+/,
defaultRules = { tags: {}, classes: {} },
currentRules = {},
uneditableClass = false;
-
+
/**
* 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",
element,
newNode,
firstChild;
-
+
if (config.uneditableClass) {
uneditableClass = config.uneditableClass;
}
-
+
if (isString) {
element = wysihtml5.dom.getAsDom(elementOrHtml, context);
} else {
element = elementOrHtml;
}
-
+
while (element.firstChild) {
firstChild = element.firstChild;
newNode = _convert(firstChild, config.cleanUp);
element.removeChild(firstChild);
if (newNode) {
fragment.appendChild(newNode);
}
}
-
+
// Clear element contents
element.innerHTML = "";
-
+
// Insert new DOM tree
element.appendChild(fragment);
-
+
return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element;
}
-
+
function _convert(oldNode, cleanUp) {
var oldNodeType = oldNode.nodeType,
oldChilds = oldNode.childNodes,
oldChildsLength = oldChilds.length,
method = NODE_TYPE_MAPPING[oldNodeType],
i = 0,
fragment,
newNode,
newChild;
-
+
if (uneditableClass && oldNodeType === 1 && wysihtml5.dom.hasClass(oldNode, uneditableClass)) {
return oldNode;
- }
-
+ }
+
newNode = method && method(oldNode);
-
+
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--;) {
+ newChild = _convert(oldChilds[i], cleanUp);
+ if (newChild) {
+ fragment.insertBefore(newChild, fragment.firstChild);
+ }
+ }
+
// 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.firstChild !== oldNode) {
-
- // add space as first when unwraping non-textflow elements
- fragment.appendChild(oldNode.ownerDocument.createTextNode(" "));
+ ]).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(" "));
+ }
}
-
-
- for (i=0; i<oldChildsLength; i++) {
- newChild = _convert(oldChilds[i], cleanUp);
- if (newChild) {
- fragment.appendChild(newChild);
- }
- }
+
if (fragment.normalize) {
fragment.normalize();
}
return fragment;
} else {
return null;
}
}
-
+
for (i=0; i<oldChildsLength; i++) {
newChild = _convert(oldChilds[i], cleanUp);
if (newChild) {
newNode.appendChild(newChild);
}
}
-
+
// Cleanup senseless <span> elements
if (cleanUp &&
newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME &&
(!newNode.childNodes.length ||
((/^\s*$/gi).test(newNode.innerHTML) && oldNode.className !== "_wysihtml5-temp-placeholder") ||
@@ -1845,38 +1863,38 @@
if (fragment.normalize) {
fragment.normalize();
}
return fragment;
}
-
+
if (newNode.normalize) {
newNode.normalize();
}
return newNode;
}
-
+
function _handleElement(oldNode) {
var rule,
newNode,
tagRules = currentRules.tags,
nodeName = oldNode.nodeName.toLowerCase(),
scopeName = oldNode.scopeName;
-
-
+
+
/**
* 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
@@ -1894,25 +1912,25 @@
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;
}
-
+
// tests if type condition is met or node should be removed/unwrapped
-
+
if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type)) {
return (rule.remove_action && rule.remove_action == "unwrap") ? false : null;
}
-
+
rule = typeof(rule) === "string" ? { rename_tag: rule } : rule;
} else if (oldNode.firstChild) {
rule = { rename_tag: DEFAULT_NODE_NAME };
} else {
// Remove empty unknown elements
@@ -1920,80 +1938,80 @@
}
newNode = oldNode.ownerDocument.createElement(rule.rename_tag || nodeName);
_handleAttributes(oldNode, newNode, rule);
_handleStyles(oldNode, newNode, rule);
oldNode = null;
-
+
if (newNode.normalize) { newNode.normalize(); }
return newNode;
}
-
+
function _testTypes(oldNode, rules, types) {
var definition, type;
-
+
// do not interfere with placeholder span or pasting caret position is not maintained
if (oldNode.nodeName === "SPAN" && oldNode.className === "_wysihtml5-temp-placeholder") {
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 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 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 || styleProp[1].replace(/\s/g, '').toLowerCase() === definition.styles[s]) {
return true;
}
}
}
}
}
}
-
+
// test for attributes in general against regex match
if (definition.attrs) {
for (a in definition.attrs) {
if (definition.attrs.hasOwnProperty(a)) {
attr = _getAttribute(oldNode, a);
@@ -2005,11 +2023,11 @@
}
}
}
return false;
}
-
+
function _handleStyles(oldNode, newNode, rule) {
var s;
if(rule && rule.keep_styles) {
for (s in rule.keep_styles) {
if (rule.keep_styles.hasOwnProperty(s)) {
@@ -2026,11 +2044,11 @@
}
}
}
}
}
-
+
function _handleAttributes(oldNode, newNode, rule) {
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
setAttributes = rule.set_attributes, // attributes to set on the current node
@@ -2046,15 +2064,15 @@
currentClass,
newClass,
attributeName,
newAttributeValue,
method;
-
+
if (setAttributes) {
attributes = wysihtml5.lang.object(setAttributes).clone();
}
-
+
if (checkAttributes) {
for (attributeName in checkAttributes) {
method = attributeCheckMethods[checkAttributes[attributeName]];
if (!method) {
continue;
@@ -2066,15 +2084,15 @@
attributes[attributeName] = newAttributeValue;
}
}
}
}
-
+
if (setClass) {
classes.push(setClass);
}
-
+
if (addClass) {
for (attributeName in addClass) {
method = addClassMethods[addClass[attributeName]];
if (!method) {
continue;
@@ -2083,14 +2101,14 @@
if (typeof(newClass) === "string") {
classes.push(newClass);
}
}
}
-
+
// make sure that wysihtml5 temp class doesn't get stripped out
allowedClasses["_wysihtml5-temp-placeholder"] = 1;
-
+
// add old classes last
oldClasses = oldNode.getAttribute("class");
if (oldClasses) {
classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
}
@@ -2099,34 +2117,34 @@
currentClass = classes[i];
if (allowedClasses[currentClass]) {
newClasses.push(currentClass);
}
}
-
+
// remove duplicate entries and preserve class specificity
newClassesLength = newClasses.length;
while (newClassesLength--) {
currentClass = newClasses[newClassesLength];
if (!wysihtml5.lang.array(newUniqueClasses).contains(currentClass)) {
newUniqueClasses.unshift(currentClass);
}
}
-
+
if (newUniqueClasses.length) {
attributes["class"] = newUniqueClasses.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);
@@ -2134,11 +2152,11 @@
if (typeof(attributes.height) !== "undefined") {
newNode.setAttribute("height", attributes.height);
}
}
}
-
+
/**
* IE gives wrong results for hasAttribute/getAttribute, for example:
* var td = document.createElement("td");
* td.getAttribute("rowspan"); // => "1" in IE
*
@@ -2157,17 +2175,17 @@
} else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
// Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
var outerHTML = node.outerHTML.toLowerCase(),
// TODO: This might not work for attributes without value: <input disabled>
hasAttribute = outerHTML.indexOf(" " + attributeName + "=") != -1;
-
+
return hasAttribute ? node.getAttribute(attributeName) : null;
} else{
return node.getAttribute(attributeName);
}
}
-
+
/**
* Check whether the given node is a proper loaded image
* FIXME: Returns undefined when unknown (Chrome, Safari)
*/
function _isLoadedImage(node) {
@@ -2177,25 +2195,25 @@
if (node.complete && node.readyState === "complete") {
return true;
}
}
}
-
+
var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
function _handleText(oldNode) {
var nextSibling = oldNode.nextSibling;
if (nextSibling && nextSibling.nodeType === wysihtml5.TEXT_NODE) {
// Concatenate text nodes
nextSibling.data = oldNode.data + nextSibling.data;
} 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);
- }
+ }
}
-
-
+
+
// ------------ attribute checks ------------ \\
var attributeCheckMethods = {
url: (function() {
var REG_EXP = /^https?:\/\//i;
return function(attributeValue) {
@@ -2229,30 +2247,36 @@
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;
+ };
})()
};
-
+
// ------------ class converter (converts an html attribute to a class name) ------------ \\
var addClassMethods = {
align_img: (function() {
var mapping = {
left: "wysiwyg-float-left",
@@ -2260,11 +2284,11 @@
};
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",
@@ -2272,11 +2296,11 @@
};
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",
@@ -2284,11 +2308,11 @@
};
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",
@@ -2302,11 +2326,11 @@
return function(attributeValue) {
return mapping[String(attributeValue).charAt(0)];
};
})()
};
-
+
return parse;
})();
/**
* Checks for empty text node childs and removes them
*
@@ -2358,13 +2382,14 @@
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>
@@ -2375,16 +2400,16 @@
*/
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);
@@ -2414,35 +2439,35 @@
*/
(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 = list.previousElementSibling || list.previousSibling,
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);
}
@@ -2456,11 +2481,11 @@
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")) {
@@ -2478,13 +2503,14 @@
}
}
list.parentNode.replaceChild(fragment, list);
}
-
+
dom.resolveList = resolveList;
-})(wysihtml5.dom);/**
+})(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, ...)
@@ -2530,25 +2556,25 @@
*/
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;
@@ -2580,11 +2606,11 @@
* 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
+ * - 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
@@ -2657,11 +2683,11 @@
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!
+ // 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;
@@ -2672,11 +2698,11 @@
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
+ // 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;
@@ -2733,49 +2759,49 @@
}
}
});
})(wysihtml5);
(function(wysihtml5) {
- var doc = document;
+ 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 {
+ } 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;
@@ -2794,15 +2820,15 @@
*/
this.loaded = true;
// Trigger the callback
setTimeout(function() { that.callback(that); }, 0);
},
-
- _getHtml: function(templateVars) {
+
+ _getHtml: function(templateVars) {
return '';
}
-
+
});
})(wysihtml5);
(function() {
var mapping = {
"className": "class"
@@ -2814,11 +2840,12 @@
element.setAttribute(mapping[i] || i, attributes[i]);
}
}
};
};
-})();wysihtml5.dom.setStyles = function(styles) {
+})();
+wysihtml5.dom.setStyles = function(styles) {
return {
on: function(element) {
var style = element.style;
if (typeof(styles) === "string") {
style.cssText += ";" + styles;
@@ -2832,11 +2859,12 @@
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
@@ -2928,17 +2956,18 @@
} 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);
}
-};(function(wysihtml5) {
-
+};
+(function(wysihtml5) {
+
var api = wysihtml5.dom;
var MapCell = function(cell) {
this.el = cell;
this.isColspan= false;
@@ -2959,11 +2988,11 @@
} 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);
@@ -2971,19 +3000,19 @@
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()) {
@@ -2992,16 +3021,16 @@
}
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);
@@ -3010,27 +3039,27 @@
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 map = [];
var tableRows = this.getTableRows(),
ridx, row, cells, cidx, cell,
c,
cspan, rspan;
@@ -3041,11 +3070,11 @@
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
+ // 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');
@@ -3055,61 +3084,61 @@
} 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;
@@ -3117,26 +3146,26 @@
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;
@@ -3144,40 +3173,40 @@
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++) {
@@ -3185,35 +3214,35 @@
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();
@@ -3222,86 +3251,103 @@
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() {
+
+ 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) {
+ 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;
+ 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++) {
@@ -3321,11 +3367,11 @@
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++) {
@@ -3334,25 +3380,25 @@
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));
@@ -3361,34 +3407,34 @@
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);
@@ -3398,42 +3444,26 @@
}
this.splitRowToCells(thisCell);
}
}
},
-
+
// merges cells from start cell (defined in creating obj) to "to" cell
merge: function(to) {
if (this.rectify()) {
- 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;
- }
- if (this.canMerge()) {
+ 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) {
+ if (rowspan > 1) {
this.map[row][col].el.setAttribute('rowspan', rowspan);
}
- if (colspan > 1) {
+ 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()))) {
@@ -3449,20 +3479,20 @@
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);
@@ -3480,11 +3510,11 @@
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) {
@@ -3499,11 +3529,40 @@
} 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();
@@ -3518,11 +3577,11 @@
}
}
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 {
@@ -3530,11 +3589,11 @@
}
} 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++) {
@@ -3543,11 +3602,11 @@
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':
@@ -3558,45 +3617,45 @@
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');
-
+ 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':
+ case 'below':
insertAfter(this.getRealRowEl(true), newRow);
break;
- case 'above':
+ 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);
@@ -3606,71 +3665,71 @@
} 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":
+ 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
+ // 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
+
+ // 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];
@@ -3680,33 +3739,33 @@
}
}
}
}
},
-
+
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":
+ 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;
}
@@ -3719,75 +3778,91 @@
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;
-};/**
+ return ret;
+};
+/**
* Fix most common html formatting misbehaviors of browsers implementation when inserting
* content via copy & paste contentEditable
*
* @author Christopher Blum
*/
@@ -3795,15 +3870,15 @@
// TODO: We probably need more rules here
var defaultRules = {
// When pasting underlined links <a> into a contentEditable, IE thinks, it has to insert <u> to keep the styling
"a u": wysihtml5.dom.replaceWithChildNodes
};
-
+
function cleanPastedHTML(elementOrHtml, rules, context) {
rules = rules || defaultRules;
context = context || elementOrHtml.ownerDocument || document;
-
+
var element,
isString = typeof(elementOrHtml) === "string",
method,
matches,
matchesLength,
@@ -3812,27 +3887,28 @@
if (isString) {
element = wysihtml5.dom.getAsDom(elementOrHtml, context);
} else {
element = elementOrHtml;
}
-
+
for (i in rules) {
matches = element.querySelectorAll(i);
method = rules[i];
matchesLength = matches.length;
for (; j<matchesLength; j++) {
method(matches[j]);
}
}
-
+
matches = elementOrHtml = rules = null;
-
+
return isString ? element.innerHTML : element;
}
-
+
return cleanPastedHTML;
-})();/**
+})();
+/**
* 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);
@@ -3867,11 +3943,11 @@
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;
@@ -3880,170 +3956,169 @@
urlToSearch = wysihtml5.lang.string(url).replace("~").by(TILDE_ESCAPED);
innerHTML = wysihtml5.lang.string(innerHTML).replace(urlToSearch).by(url);
}
return innerHTML;
};
-})(wysihtml5);/**
+})(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() {
-
- var dom = wysihtml5.dom,
- select = {
- table: null,
- start: null,
- end: null,
- select: selectCells
- },
- editable = null,
- selection_class = "wysiwyg-tmp-selected-cell",
- moveHandler = null,
- upHandler = null,
- editor = null;
-
- function init (element, edit) {
- editable = element;
- editor = edit;
-
- dom.observe(editable, "mousedown", function(event) {
- var target = event.target,
- nodeName = target.nodeName;
- if (nodeName == "TD" || nodeName == "TH") {
- handleSelectionMousedown(target);
- }
-
- });
-
- return select;
- }
-
- function handleSelectionMousedown (target) {
- select.start = target;
- select.end = 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);
- }
- }
-
- // 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);
- }
+})(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);
}
- }
- }
-
- function addSelections (cells) {
- for (var i = 0; i < cells.length; i++) {
- dom.addClass(cells[i], selection_class);
+ });
+
+ return select;
}
- }
-
- function handleMouseMove (event) {
- var curTable = null,
- cell = dom.getParentElement(event.target, { nodeName: ["TD","TH"] }),
- selectedCells;
-
- if (cell && select.table && select.start) {
- curTable = dom.getParentElement(cell, { nodeName: ["TABLE"] });
- if (curTable && curTable === select.table) {
+
+ function handleSelectionMousedown (target) {
+ select.start = target;
+ select.end = target;
+ select.cells = [target];
+ select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
+
+ if (select.table) {
removeCellSelections();
- select.end = cell;
- selectedCells = dom.table.getCellsBetween(select.start, cell);
- addSelections(selectedCells);
+ dom.addClass(target, selection_class);
+ moveHandler = dom.observe(editable, "mousemove", handleMouseMove);
+ upHandler = dom.observe(editable, "mouseup", handleMouseUp);
}
}
- }
-
- 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");
+
+ // 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 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();
+ }
+
+ 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"] });
+
+ if (cell && select.table && select.start) {
+ curTable = dom.getParentElement(cell, { nodeName: ["TABLE"] });
+ if (curTable && curTable === select.table) {
+ removeCellSelections();
+ select.end = cell;
+ select.cells = dom.table.getCellsBetween(select.start, cell);
+ if (select.cells.length > 1) {
+ editor.composer.selection.deselect();
+ }
+ addSelections(select.cells);
+ }
+ }
+ }
+
+ function handleMouseUp (event) {
+ moveHandler.stop();
+ upHandler.stop();
editor.fire("tableselect").fire("tableselect:composer");
- }
-
- return init;
+ 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) {
+
+ 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)) {
@@ -4055,11 +4130,11 @@
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);
}
@@ -4068,11 +4143,11 @@
});
}
}
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") {
@@ -4083,60 +4158,61 @@
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) {
+ if (params) {
return wysihtml5.lang.string(params[params.length - 1].split(':')[1]).trim();
}
return false;
}
};
-
-})(wysihtml5);/**
+
+})(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;
}
-
+
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
*/
@@ -4179,11 +4255,11 @@
* @example
* selection.setBefore(myElement);
*/
setAfter: function(node) {
var range = rangy.createRange(this.doc);
-
+
range.setStartAfter(node);
range.setEndAfter(node);
return this.setSelection(range);
},
@@ -4249,22 +4325,22 @@
} else {
range = this.getRange(this.doc);
return range ? range.commonAncestorContainer : this.doc.body;
}
},
-
+
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) {
@@ -4272,49 +4348,49 @@
});
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
@@ -4327,112 +4403,108 @@
parent = node.parentNode;
if (parent !== this.contain) {
ret = this.getPreviousNode(parent, ignoreEmpty);
}
}
-
+
return (ret !== this.contain) ? ret : false;
},
-
-
-
+
+
+
caretIsInTheBeginnig: function() {
var selection = this.getSelection(),
node = selection.anchorNode,
offset = selection.anchorOffset;
-
+
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: 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(this.doc),
+ 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).before(caretPlaceholder[0]);
newRange.setStartAfter(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();
}
@@ -4487,17 +4559,17 @@
newRange = rangy.createRange(this.doc);
try { newRange.setStart(rangeBackup.startContainer, rangeBackup.startOffset); } catch(e1) {}
try { newRange.setEnd(rangeBackup.endContainer, rangeBackup.endOffset); } catch(e2) {}
try { this.setSelection(newRange); } catch(e3) {}
},
-
+
set: function(node, offset) {
var newRange = rangy.createRange(this.doc);
newRange.setStart(node, offset || 0);
this.setSelection(newRange);
},
-
+
/**
* Insert html at the caret position and move the cursor after the inserted html
*
* @param {String} html HTML string to insert
* @example
@@ -4505,11 +4577,11 @@
*/
insertHTML: function(html) {
var range = rangy.createRange(this.doc),
node = range.createContextualFragment(html),
lastChild = node.lastChild;
-
+
this.insertNode(node);
if (lastChild) {
this.setAfter(lastChild);
}
},
@@ -4537,11 +4609,11 @@
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;
@@ -4556,30 +4628,30 @@
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) {
+
+ while (tempElements.firstChild) {
firstChild = tempElements.firstChild;
if (firstChild.nodeType == 1 && wysihtml5.dom.hasClass(firstChild, nodeOptions.className)) {
while (firstChild.firstChild) {
tempElement.appendChild(firstChild.firstChild);
}
@@ -4590,11 +4662,11 @@
}
}
} else {
tempElement = null;
}
-
+
return tempElement;
},
/**
* Scroll the current caret position into the view
@@ -4704,76 +4776,94 @@
return range.getNodes([nodeType], filter);
} else {
return [];
}
},
-
+
fixRangeOverflow: function(range) {
-
- if (this.contain && 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);
- }
-
- }
+ 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));
+ }
}
-
+ }
},
-
- getRange: function() {
+
+ _endOffsetForNode: function(node) {
+ var range = document.createRange()
+ range.selectNodeContents(node)
+ return range.endOffset;
+ },
+
+ _detectInlineRangeProblems: function(range) {
+ position = range.startContainer.compareDocumentPosition(range.endContainer);
+ return (
+ range.endOffset == 0 &&
+ position & Node.DOCUMENT_POSITION_FOLLOWING
+ );
+ },
+
+ getRange: function(dontFix) {
var selection = this.getSelection(),
range = selection && selection.rangeCount && selection.getRangeAt(0);
- this.fixRangeOverflow(range);
-
+
+ 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 (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:
+ 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:
@@ -4796,21 +4886,25 @@
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;
+ },
+
+ 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/
*
@@ -4818,43 +4912,43 @@
* - 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;
@@ -4866,11 +4960,11 @@
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(';');
@@ -4884,20 +4978,20 @@
} 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();
+ elStyle = elStyle.replace(/\s/gi, '').toLowerCase();
regexes.push(new RegExp("(^|\\s|;)" + style.replace(/\s/gi, '').replace(/([\(\)])/gi, "\\$1").toLowerCase().replace(";", ";?"), "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(";", ";?"), "gi"));
}
}
@@ -4905,32 +4999,32 @@
if (elStyle.match(regexes[j])) {
return regexes[j];
}
}
}
-
+
return false;
};
-
+
function removeOrChangeStyle(el, style, regExp) {
-
+
var exactRegex = getMatchingStyleRegexp(el, style);
-
+
/*new RegExp("(^|\\s|;)" + style.replace(/\s/gi, '').replace(/([\(\)])/gi, "\\$1").toLowerCase().replace(";", ";?"), "gi"),
elStyle = el.getAttribute('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) {
@@ -5002,11 +5096,11 @@
}
rangy.dom.insertAfter(newNode, descendantNode);
}
return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, rangy.dom.getNodeIndex(newNode));
}
-
+
function Merge(firstNode) {
this.isElementMerge = (firstNode.nodeType == wysihtml5.ELEMENT_NODE);
this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
this.textNodes = [this.firstTextNode];
}
@@ -5059,24 +5153,25 @@
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 && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssClassMatch) {
+ 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 && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssStyleMatch) {
+
+ 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;
@@ -5136,11 +5231,11 @@
// 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";
@@ -5157,11 +5252,11 @@
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);
@@ -5179,11 +5274,11 @@
},
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);
}
} else {
var el = this.createContainer(rangy.dom.getDocument(textNode));
@@ -5202,47 +5297,47 @@
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);
range.setEndAfter(ancestor);
}
if (ancestorRange.isPointInRange(range.startContainer, range.startOffset) && isSplitPoint(range.startContainer, range.startOffset)) {
ancestor = splitNodeAt(ancestor, range.startContainer, range.startOffset);
}
}
-
+
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--;) {
+ 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;
@@ -5250,26 +5345,26 @@
textNode = textNodes[i];
if (!this.getAncestorWithClass(textNode)) {
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;
-
+
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]);
@@ -5278,12 +5373,12 @@
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];
ancestorWithClass = this.getAncestorWithClass(textNode);
ancestorWithStyle = this.getAncestorWithStyle(textNode);
@@ -5292,11 +5387,11 @@
} else if (ancestorWithStyle) {
this.undoToTextNode(textNode, range[ri], false, ancestorWithStyle);
}
}
}
-
+
if (len == 1) {
this.selectNode(range[ri], textNodes[0]);
} else {
range[ri].setStart(textNodes[0], 0);
textNode = textNodes[textNodes.length - 1];
@@ -5304,14 +5399,14 @@
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);
@@ -5326,11 +5421,11 @@
} 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);
@@ -5341,21 +5436,21 @@
},
isAppliedToRange: function(range) {
var ancestors = [],
ancestor, styleAncestor, textNodes;
-
+
for (var ri = range.length; ri--;) {
textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
if (!textNodes.length) {
ancestor = this.getAncestorWithClass(range[ri].startContainer);
if (!ancestor) {
ancestor = this.getAncestorWithStyle(range[ri].startContainer);
}
return ancestor ? [ancestor] : false;
}
-
+
for (var i = 0, len = textNodes.length, selectedText; i < len; ++i) {
selectedText = this.getTextSelectedByRange(textNodes[i], range[ri]);
ancestor = this.getAncestorWithClass(textNodes[i]);
if (!ancestor) {
ancestor = this.getAncestorWithStyle(textNodes[i]);
@@ -5363,11 +5458,11 @@
if (!(selectedText != "" && !ancestor)) {
ancestors.push(ancestor);
}
}
}
-
+
return (ancestors.length) ? ancestors : false;
},
toggleRange: function(range) {
if (this.isAppliedToRange(range)) {
@@ -5377,36 +5472,37 @@
}
}
};
wysihtml5.selection.HTMLApplier = HTMLApplier;
-
-})(wysihtml5, rangy);/**
+
+})(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", ...)
@@ -5416,27 +5512,27 @@
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")
@@ -5459,11 +5555,11 @@
} 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;
@@ -5471,11 +5567,11 @@
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");
},
@@ -5536,39 +5632,39 @@
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" });
@@ -5589,13 +5685,14 @@
state: function(composer, command) {
return wysihtml5.commands.formatInline.state(composer, command, "A");
}
};
-})(wysihtml5);(function(wysihtml5) {
+})(wysihtml5);
+(function(wysihtml5) {
var dom = wysihtml5.dom;
-
+
function _removeFormat(composer, anchors) {
var length = anchors.length,
i = 0,
anchor,
codeElement,
@@ -5613,20 +5710,20 @@
} 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);
@@ -5636,18 +5733,19 @@
state: function(composer, command) {
return wysihtml5.commands.formatInline.state(composer, command, "A");
}
};
-})(wysihtml5);/**
+})(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);
},
@@ -5657,11 +5755,11 @@
};
})(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);
@@ -5669,16 +5767,16 @@
},
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');
@@ -5687,40 +5785,42 @@
}
}
return false;
}
};
-})(wysihtml5);/**
+})(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);/**
+})(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] + ');';
}
@@ -5729,19 +5829,19 @@
},
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");
@@ -5749,61 +5849,63 @@
}
}
}
return false;
}
-
+
};
-})(wysihtml5);/* In case background adjustment to any color defined by user is preferred, we cannot use classes and must use inline styles. */
+})(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,
+ 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) {
+})(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", "BLOCKQUOTE", "DIV"];
-
+
/**
* Remove similiar classes (based on classRegExp)
* and add the desired class name
*/
function _addClass(element, className, classRegExp) {
@@ -5934,60 +6036,60 @@
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++) {
_removeLineBreakBeforeAndAfter(surroundedNodes[i]);
_removeLastChildIfLineBreak(surroundedNodes[i]);
}
-
+
// rethink restoring selection
// composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
}
function _hasClasses(element) {
return !!wysihtml5.lang.string(element.className).trim();
}
-
+
wysihtml5.commands.formatBlock = {
exec: function(composer, command, nodeName, className, classRegExp) {
var doc = composer.doc,
blockElements = this.state(composer, command, nodeName, className, classRegExp),
useLineBreaks = composer.config.useLineBreaks,
defaultNodeName = useLineBreaks ? "DIV" : "P",
selectedNodes, classRemoveAction, blockRenameFound;
nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
-
+
if (blockElements.length) {
composer.selection.executeAndRestoreSimple(function() {
for (var b = blockElements.length; b--;) {
if (classRegExp) {
classRemoveAction = _removeClass(blockElements[b], classRegExp);
}
-
+
if (classRemoveAction && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
// dont rename or remove element when just setting block formating class
return;
}
-
+
var hasClasses = _hasClasses(blockElements[b]);
-
+
if (!hasClasses && (useLineBreaks || nodeName === "P")) {
// Insert a line break afterwards and beforewards when there are siblings
// that are not of type line break or block element
_addLineBreakBeforeAndAfter(blockElements[b]);
dom.replaceWithChildNodes(blockElements[b]);
@@ -5995,14 +6097,14 @@
// 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.executeAndRestoreSimple(function() {
for (var n = selectedNodes.length; n--;) {
@@ -6018,17 +6120,17 @@
blockElement = dom.renameElement(blockElement, nodeName);
}
if (className) {
_addClass(blockElement, className, classRegExp);
}
-
+
blockRenameFound = true;
}
}
-
+
});
-
+
if (blockRenameFound) {
return;
}
}
@@ -6042,23 +6144,23 @@
// Native command does not create elements from selecton boundaries.
// Not quite user expected behaviour
if (composer.commands.support(command)) {
_execCommand(doc, composer, command, nodeName || defaultNodeName, className);
return;
- }
+ }
}
-
+
},
state: function(composer, command, nodeName, className, classRegExp) {
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,
@@ -6071,26 +6173,27 @@
if (parents.length == 0) {
return false;
}
return parents;
}
-
-
+
+
};
-})(wysihtml5);/**
+})(wysihtml5);
+/**
* 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>
*
@@ -6116,32 +6219,32 @@
"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) {
var identifier = tagName + ":" + className;
if (cssStyle) {
- identifier += ":" + cssStyle
+ identifier += ":" + cssStyle;
}
if (!htmlApplier[identifier]) {
htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true, cssStyle, styleRegExp);
}
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).toggleRange(ownRanges);
@@ -6159,22 +6262,22 @@
} else {
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()) {
var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
composer.selection.executeAndRestoreSimple(function() {
var parent = state_element.parentNode;
composer.selection.selectNode(state_element, true);
- wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true);
+ 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);
}
},
@@ -6194,19 +6297,20 @@
if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
return false;
}
ownRanges = composer.selection.getOwnRanges();
-
+
if (ownRanges.length == 0) {
return false;
}
-
+
return _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp).isAppliedToRange(ownRanges);
}
};
-})(wysihtml5);wysihtml5.commands.insertHTML = {
+})(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);
@@ -6217,16 +6321,16 @@
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" });
@@ -6256,11 +6360,11 @@
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);
@@ -6312,13 +6416,14 @@
}
return imagesInSelection[0];
}
};
-})(wysihtml5);(function(wysihtml5) {
+})(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()) {
@@ -6331,25 +6436,34 @@
state: function() {
return false;
}
};
-})(wysihtml5);wysihtml5.commands.insertOrderedList = {
+})(wysihtml5);
+wysihtml5.commands.insertOrderedList = {
exec: function(composer, command) {
var doc = composer.doc,
selectedNode = composer.selection.getSelectedNode(),
list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }),
otherList = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }),
tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
isEmpty,
tempElement;
-
+
+ // do not count list elements outside of composer
+ if (list && !composer.element.contains(list)) {
+ list = null
+ }
+ if (otherList && !composer.element.contains(otherList)) {
+ otherList = null
+ }
+
if (!list && !otherList && composer.commands.support(command)) {
doc.execCommand(command, false, null);
return;
}
-
+
if (list) {
// Unwrap list
// <ol><li>foo</li><li>bar</li></ol>
// becomes:
// foo<br>bar<br>
@@ -6379,30 +6493,41 @@
composer.selection.selectNode(list.querySelector("li"), true);
}
}
}
},
-
+
state: function(composer) {
- var selectedNode = composer.selection.getSelectedNode();
- return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" });
+ var selectedNode = composer.selection.getSelectedNode(),
+ node = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" });
+
+ return (composer.element.contains(node) ? node : false);
}
-};wysihtml5.commands.insertUnorderedList = {
+};
+wysihtml5.commands.insertUnorderedList = {
exec: function(composer, command) {
var doc = composer.doc,
selectedNode = composer.selection.getSelectedNode(),
list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }),
otherList = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }),
tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
isEmpty,
tempElement;
-
+
+ // do not count list elements outside of composer
+ if (list && !composer.element.contains(list)) {
+ list = null
+ }
+ if (otherList && !composer.element.contains(otherList)) {
+ otherList = null
+ }
+
if (!list && !otherList && composer.commands.support(command)) {
doc.execCommand(command, false, null);
return;
}
-
+
if (list) {
// Unwrap list
// <ul><li>foo</li><li>bar</li></ul>
// becomes:
// foo<br>bar<br>
@@ -6432,16 +6557,19 @@
composer.selection.selectNode(list.querySelector("li"), true);
}
}
}
},
-
+
state: function(composer) {
- var selectedNode = composer.selection.getSelectedNode();
- return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" });
+ var selectedNode = composer.selection.getSelectedNode(),
+ node = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" });
+
+ return (composer.element.contains(node) ? node : false);
}
-};wysihtml5.commands.italic = {
+};
+wysihtml5.commands.italic = {
exec: function(composer, command) {
wysihtml5.commands.formatInline.execWithToggle(composer, command, "i");
},
state: function(composer, command) {
@@ -6450,53 +6578,57 @@
// chrome: <i>, <em>, <blockquote>, ...
// ie: <i>, <em>
// opera: only <i>
return wysihtml5.commands.formatInline.state(composer, command, "i");
}
-};(function(wysihtml5) {
+};
+(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) {
+})(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) {
+})(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) {
+})(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);
},
@@ -6511,50 +6643,59 @@
},
state: function(composer) {
return false;
}
-};wysihtml5.commands.underline = {
+};
+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 = {
+};
+wysihtml5.commands.undo = {
exec: function(composer) {
return composer.undoManager.undo();
},
state: function(composer) {
return false;
}
-};wysihtml5.commands.createTable = {
+};
+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) {
- html = "<table><tbody>";
+ 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> </td>";
}
html += '</tr>';
}
html += "</tbody></table>";
composer.commands.exec("insertHTML", html);
//composer.selection.insertHTML(html);
- }
-
-
+ }
+
+
},
state: function(composer, command) {
return false;
}
-};wysihtml5.commands.mergeTableCells = {
+};
+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 {
@@ -6579,14 +6720,15 @@
return [start];
}
}
return false;
}
-};wysihtml5.commands.addTableCells = {
+};
+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") {
@@ -6599,38 +6741,39 @@
},
state: function(composer, command) {
return false;
}
-};wysihtml5.commands.deleteTableCells = {
+};
+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);
@@ -6638,11 +6781,12 @@
},
state: function(composer, command) {
return false;
}
-};/**
+};
+/**
* Undo Manager for wysihtml5
* slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
*/
(function(wysihtml5) {
var Z_KEY = 90,
@@ -6653,87 +6797,87 @@
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();
}
});
-
+
// Now this is very hacky:
// These days browsers don't offer a undo/redo event which we could hook into
// to be notified when the user hits undo/redo in the contextmenu.
// Therefore we simply insert two elements as soon as the contextmenu gets opened.
// The last element being inserted will be immediately be removed again by a exexCommand("undo")
// => When the second element appears in the dom tree then we know the user clicked "redo" in the context menu
// => When the first element disappears from the dom tree then we know the user clicked "undo" in the context menu
-
+
// TODO: unexpected behaviour. Tends to undo on contextmenu showing in chrome on newly inserted blocks
/*if (wysihtml5.browser.hasUndoInContextMenu()) {
var interval, observed, cleanUp = function() {
cleanTempElements(doc);
clearInterval(interval);
};
-
+
dom.observe(this.element, "contextmenu", function() {
cleanUp();
that.composer.selection.executeAndRestoreSimple(function() {
if (that.element.lastChild) {
that.composer.selection.setAfter(that.element.lastChild);
@@ -6761,108 +6905,108 @@
dom.observe(document, "mousedown", cleanUp);
dom.observe(doc, ["mousedown", "paste", "cut", "copy"], cleanUp);
}
});
}*/
-
+
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();
-
+
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++;
-
+
var range = this.composer.selection.getRange(),
node = (range && range.startContainer) ? range.startContainer : this.element,
offset = (range && range.startOffset) ? range.startOffset : 0,
element,
position;
-
+
if (node.nodeType === wysihtml5.ELEMENT_NODE) {
element = node;
} else {
element = node.parentNode;
position = this.getChildNodeIndex(element, node);
}
-
+
element.setAttribute(DATA_ATTR_OFFSET, offset);
if (typeof(position) !== "undefined") {
element.setAttribute(DATA_ATTR_NODE, position);
}
-
+
var clone = this.element.cloneNode(!!currentHtml);
this.historyDom.push(clone);
this.historyStr.push(currentHtml);
-
+
element.removeAttribute(DATA_ATTR_OFFSET);
element.removeAttribute(DATA_ATTR_NODE);
},
-
+
undo: function() {
this.transact();
-
+
if (!this.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 {
@@ -6870,29 +7014,29 @@
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);
@@ -6907,11 +7051,11 @@
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) {
@@ -6923,38 +7067,39 @@
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) {
+});
+(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
@@ -6978,11 +7123,11 @@
this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : this.CARET_HACK;
},
getValue: function(parse) {
var value = this.isEmpty() ? "" : wysihtml5.quirks.getCorrectInnerHTML(this.element);
-
+
if (parse) {
value = this.parent.parse(value);
}
return value;
@@ -6990,25 +7135,25 @@
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();
}
@@ -7037,13 +7182,13 @@
// 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 {
@@ -7066,14 +7211,14 @@
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 {
@@ -7086,24 +7231,24 @@
}
},
_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";
@@ -7121,79 +7266,79 @@
this.textarea = this.parent.textarea;
this.element.innerHTML = this.textarea.getValue(true);
} 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() {
@@ -7216,11 +7361,11 @@
that.selection.executeAndRestore(function(startContainer, endContainer) {
dom.autoLink(endContainer.parentNode);
});
}
});
-
+
dom.observe(this.element, "blur", function() {
dom.autoLink(that.element);
});
}
@@ -7270,65 +7415,65 @@
});
},
_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) {
+ 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 = "";
@@ -7340,39 +7485,39 @@
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;
}
@@ -7387,15 +7532,15 @@
adjust(selectedNode);
}
}, 0);
return;
}
-
+
if (that.config.useLineBreaks && keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
event.preventDefault();
that.commands.exec("insertLineBreak");
-
+
}
});
}
});
})(wysihtml5);
@@ -7443,16 +7588,16 @@
"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: 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)
@@ -7472,136 +7617,137 @@
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);/**
+})(wysihtml5);
+/**
* Taking care of events
* - Simulating 'change' event on contentEditable element
* - Handling drag & drop logic
* - Catch paste events
* - Dispatch proprietary newword:composer event
@@ -7616,11 +7762,11 @@
shortcuts = {
"66": "bold", // B
"73": "italic", // I
"85": "underline" // U
};
-
+
wysihtml5.views.Composer.prototype.observe = function() {
var that = this,
state = this.getValue(),
container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
element = this.element,
@@ -7641,20 +7787,20 @@
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) {
this.tableSelection = wysihtml5.quirks.tableCellsSelection(element, that.parent);
}
// --------- Focus & blur logic ---------
@@ -7701,36 +7847,36 @@
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();
@@ -7744,39 +7890,39 @@
}
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) {
-
+
if (that.selection.isCollapsed()) {
if (that.selection.caretIsInTheBeginnig()) {
event.preventDefault();
} else {
var beforeUneditable = that.selection.caretIsBeforeUneditable();
if (beforeUneditable) {
event.preventDefault();
-
+
// TODO: take the how to delete around uneditable out of here
// merge node with previous node from uneditable
var prevNode = that.selection.getPreviousNode(beforeUneditable, true),
curNode = that.selection.getSelectedNode();
-
- if (curNode.nodeType !== 1 && curNode.parentNode !== element) { curNode = curNode.parentNode; }
+
+ 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 {
@@ -7802,11 +7948,11 @@
}
} else if (that.selection.containsUneditable()) {
event.preventDefault();
that.selection.deleteContents();
}
-
+
}
});
// --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
dom.observe(element, "keydown", function(event) {
@@ -7824,11 +7970,11 @@
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(this.iframe, "focus", function() {
setTimeout(function() {
if (that.doc.querySelector(":focus") !== that.element) {
@@ -7841,17 +7987,17 @@
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") {
@@ -7862,16 +8008,17 @@
title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
target.setAttribute("title", title);
}
});
};
-})(wysihtml5);/**
+})(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;
@@ -7962,53 +8109,53 @@
});
})(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) {
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",
@@ -8017,23 +8164,24 @@
/**
* 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
*
@@ -8062,24 +8210,24 @@
* 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
+ // 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
+ // Includes table editing events and cell selection tracking
handleTables: true,
// Object which includes parser rules to apply when html gets inserted via copy & paste
// See parser_rules/*.js for examples
parserRules: { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
// Parser method to use when the user inserts content via copy & paste
@@ -8100,64 +8248,64 @@
cleanUp: true,
// Whether to use div instead of secure iframe
contentEditableMode: false,
xingAlert: false,
// Classname of container that editor should not touch and pass through
- // Pass false to disable
+ // Pass false to disable
uneditableContainerClassname: "wysihtml5-uneditable-container"
};
-
+
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);
-
-
+
+
if (this.config.xingAlert) {
try { console.log("Heya! This page is using wysihtml5 for rich text editing. Check out https://github.com/xing/wysihtml5");} catch(e) {}
}
},
-
+
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() {
@@ -8169,19 +8317,19 @@
return this.currentView.getValue(parse);
},
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) {
@@ -8194,27 +8342,27 @@
*/
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) {
var parseContext = (this.config.contentEditableMode) ? document : this.composer.sandbox.getDocument();
var returnValue = this.config.parser(htmlOrElement, {
"rules": this.config.parserRules,
"cleanUp": this.config.cleanUp,
@@ -8224,10 +8372,10 @@
if (typeof(htmlOrElement) === "object") {
wysihtml5.quirks.redraw(htmlOrElement);
}
return returnValue;
},
-
+
/**
* Prepare html parser logic
* - Observes for paste and drop
*/
_initParser: function() {