11/**
2- * @license wysihtml5x v0.5.0-beta6
3- * https://github.com/Edicy/wysihtml5
2+ * @license wysihtml v0.5.0-beta7
3+ * https://github.com/Voog/wysihtml
44 *
55 * Author: Christopher Blum (https://github.com/tiff)
66 * Secondary author of extended features: Oliver Pulges (https://github.com/pulges)
1010 *
1111 */
1212var wysihtml5 = {
13- version: "0.5.0-beta6 ",
13+ version: "0.5.0-beta7 ",
1414
1515 // namespaces
1616 commands: {},
@@ -5081,9 +5081,17 @@ wysihtml5.browser = (function() {
50815081 * wysihtml5.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
50825082 * // => { foo: 1, bar: 2, baz: 3 }
50835083 */
5084- merge: function(otherObj) {
5084+ merge: function(otherObj, deep ) {
50855085 for (var i in otherObj) {
5086- obj[i] = otherObj[i];
5086+ if (deep && wysihtml5.lang.object(otherObj[i]).isPlainObject() && (typeof obj[i] === "undefined" || wysihtml5.lang.object(obj[i]).isPlainObject())) {
5087+ if (typeof obj[i] === "undefined") {
5088+ obj[i] = wysihtml5.lang.object(otherObj[i]).clone(true);
5089+ } else {
5090+ wysihtml5.lang.object(obj[i]).merge(wysihtml5.lang.object(otherObj[i]).clone(true));
5091+ }
5092+ } else {
5093+ obj[i] = wysihtml5.lang.object(otherObj[i]).isPlainObject() ? wysihtml5.lang.object(otherObj[i]).clone(true) : otherObj[i];
5094+ }
50875095 }
50885096 return this;
50895097 },
@@ -5138,7 +5146,7 @@ wysihtml5.browser = (function() {
51385146 },
51395147
51405148 isPlainObject: function () {
5141- return Object.prototype.toString.call(obj) === '[object Object]';
5149+ return obj && Object.prototype.toString.call(obj) === '[object Object]';
51425150 }
51435151 };
51445152};
@@ -6696,11 +6704,9 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
66966704 newAttributeValue;
66976705
66986706 if (method) {
6699- if (attributeValue || (attributeName === "alt" && nodeName == "IMG")) {
6700- newAttributeValue = method(attributeValue);
6701- if (typeof(newAttributeValue) === "string") {
6702- return newAttributeValue;
6703- }
6707+ newAttributeValue = method(attributeValue, nodeName);
6708+ if (typeof(newAttributeValue) === "string") {
6709+ return newAttributeValue;
67046710 }
67056711 }
67066712
@@ -6935,9 +6941,13 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
69356941
69366942 alt: (function() {
69376943 var REG_EXP = /[^ a-z0-9_\-]/gi;
6938- return function(attributeValue) {
6944+ return function(attributeValue, nodeName ) {
69396945 if (!attributeValue) {
6940- return "";
6946+ if (nodeName === "IMG") {
6947+ return "";
6948+ } else {
6949+ return null;
6950+ }
69416951 }
69426952 return attributeValue.replace(REG_EXP, "");
69436953 };
@@ -6963,6 +6973,9 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
69636973
69646974 any: (function() {
69656975 return function(attributeValue) {
6976+ if (!attributeValue) {
6977+ return null;
6978+ }
69666979 return attributeValue;
69676980 };
69686981 })()
@@ -7320,6 +7333,9 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
73207333 constructor: function(readyCallback, config) {
73217334 this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
73227335 this.config = wysihtml5.lang.object({}).merge(config).get();
7336+ if (!this.config.className) {
7337+ this.config.className = "wysihtml5-sandbox";
7338+ }
73237339 this.editableArea = this._createIframe();
73247340 },
73257341
@@ -7374,7 +7390,7 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
73747390 _createIframe: function() {
73757391 var that = this,
73767392 iframe = doc.createElement("iframe");
7377- iframe.className = "wysihtml5-sandbox" ;
7393+ iframe.className = this.config.className ;
73787394 wysihtml5.dom.setAttributes({
73797395 "security": "restricted",
73807396 "allowtransparency": "true",
@@ -7537,6 +7553,9 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
75377553 constructor: function(readyCallback, config, contentEditable) {
75387554 this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
75397555 this.config = wysihtml5.lang.object({}).merge(config).get();
7556+ if (!this.config.className) {
7557+ this.config.className = "wysihtml5-sandbox";
7558+ }
75407559 if (contentEditable) {
75417560 this.element = this._bindElement(contentEditable);
75427561 } else {
@@ -7547,7 +7566,7 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
75477566 // creates a new contenteditable and initiates it
75487567 _createElement: function() {
75497568 var element = doc.createElement("div");
7550- element.className = "wysihtml5-sandbox" ;
7569+ element.className = this.config.className ;
75517570 this._loadElement(element);
75527571 return element;
75537572 },
@@ -7626,8 +7645,8 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
76267645 * wysihtml.dom.simulatePlaceholder(this, composer, "Foobar");
76277646 */
76287647(function(dom) {
7629- dom.simulatePlaceholder = function(editor, view, placeholderText) {
7630- var CLASS_NAME = " placeholder",
7648+ dom.simulatePlaceholder = function(editor, view, placeholderText, placeholderClassName ) {
7649+ var CLASS_NAME = placeholderClassName || "wysihtml5- placeholder",
76317650 unset = function() {
76327651 var composerIsVisible = view.element.offsetWidth > 0 && view.element.offsetHeight > 0;
76337652 if (view.hasPlaceholderSet()) {
@@ -11355,7 +11374,7 @@ wysihtml5.Commands = Base.extend(
1135511374 function cleanup(composer) {
1135611375 var container = composer.element,
1135711376 allElements = container.querySelectorAll(BLOCK_ELEMENTS),
11358- uneditables = container.querySelectorAll(composer.config.uneditableContainerClassname ),
11377+ uneditables = container.querySelectorAll(composer.config.classNames.uneditableContainer ),
1135911378 elements = wysihtml5.lang.array(allElements).without(uneditables);
1136011379
1136111380 for (var i = elements.length; i--;) {
@@ -11627,7 +11646,7 @@ wysihtml5.Commands = Base.extend(
1162711646 state = this.state(composer, command, options);
1162811647 if (state) {
1162911648 bookmark = rangy.saveSelection(composer.win);
11630- for (var j in state) {
11649+ for (var j = 0, jmax = state.length; j < jmax; j++ ) {
1163111650 removeOptionsFromElement(state[j], options, composer);
1163211651 }
1163311652 }
@@ -11650,7 +11669,7 @@ wysihtml5.Commands = Base.extend(
1165011669 composer.selection.selectLine();
1165111670 }
1165211671 }
11653-
11672+
1165411673 // And get all selection ranges of current composer and iterat
1165511674 ranges = composer.selection.getOwnRanges();
1165611675 for (var i = ranges.length; i--;) {
@@ -12212,7 +12231,7 @@ wysihtml5.Commands = Base.extend(
1221212231
1221312232 if (tempElement) {
1221412233 isEmpty = wysihtml5.lang.array(["", "<br>", wysihtml5.INVISIBLE_SPACE]).contains(tempElement.innerHTML);
12215- list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.uneditableContainerClassname );
12234+ list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.classNames.uneditableContainer );
1221612235 if (isEmpty) {
1221712236 composer.selection.selectNode(list.querySelector("li"), true);
1221812237 }
@@ -12441,7 +12460,7 @@ wysihtml5.Commands = Base.extend(
1244112460 for (row = 0; row < value.rows; row ++) {
1244212461 html += '<tr>';
1244312462 for (col = 0; col < value.cols; col ++) {
12444- html += "<td></td>";
12463+ html += "<td><br>< /td>";
1244512464 }
1244612465 html += '</tr>';
1244712466 }
@@ -13123,14 +13142,17 @@ wysihtml5.views.View = Base.extend(
1312313142
1312413143 _initContentEditableArea: function() {
1312513144 var that = this;
13126-
1312713145 if (this.config.noTextarea) {
1312813146 this.sandbox = new dom.ContentEditableArea(function() {
1312913147 that._create();
13130- }, {}, this.editableArea);
13148+ }, {
13149+ className: this.config.classNames.sandbox
13150+ }, this.editableArea);
1313113151 } else {
1313213152 this.sandbox = new dom.ContentEditableArea(function() {
1313313153 that._create();
13154+ }, {
13155+ className: this.config.classNames.sandbox
1313413156 });
1313513157 this.editableArea = this.sandbox.getContentEditable();
1313613158 dom.insert(this.editableArea).after(this.textarea.element);
@@ -13140,11 +13162,11 @@ wysihtml5.views.View = Base.extend(
1314013162
1314113163 _initSandbox: function() {
1314213164 var that = this;
13143-
1314413165 this.sandbox = new dom.Sandbox(function() {
1314513166 that._create();
1314613167 }, {
13147- stylesheets: this.config.stylesheets
13168+ stylesheets: this.config.stylesheets,
13169+ className: this.config.classNames.sandbox
1314813170 });
1314913171 this.editableArea = this.sandbox.getIframe();
1315013172
@@ -13178,7 +13200,7 @@ wysihtml5.views.View = Base.extend(
1317813200 }
1317913201
1318013202 // Make sure our selection handler is ready
13181- this.selection = new wysihtml5.Selection(this.parent, this.element, this.config.uneditableContainerClassname );
13203+ this.selection = new wysihtml5.Selection(this.parent, this.element, this.config.classNames.uneditableContainer );
1318213204
1318313205 // Make sure commands dispatcher is ready
1318413206 this.commands = new wysihtml5.Commands(this.parent);
@@ -13189,7 +13211,7 @@ wysihtml5.views.View = Base.extend(
1318913211 ]).from(this.textarea.element).to(this.element);
1319013212 }
1319113213
13192- dom.addClass(this.element, this.config.composerClassName );
13214+ dom.addClass(this.element, this.config.classNames.composer );
1319313215 //
1319413216 // Make the editor look like the original textarea, by syncing styles
1319513217 if (this.config.style && !this.config.contentEditableMode) {
@@ -13215,7 +13237,7 @@ wysihtml5.views.View = Base.extend(
1321513237 ? this.config.placeholder
1321613238 : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder"));
1321713239 if (placeholderText) {
13218- dom.simulatePlaceholder(this.parent, this, placeholderText);
13240+ dom.simulatePlaceholder(this.parent, this, placeholderText, this.config.classNames.placeholder );
1321913241 }
1322013242
1322113243 // Make sure that the browser avoids using inline styles whenever possible
@@ -13267,7 +13289,7 @@ wysihtml5.views.View = Base.extend(
1326713289 this.parent.on("newword:composer", function() {
1326813290 if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
1326913291 var nodeWithSelection = that.selection.getSelectedNode(),
13270- uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname ),
13292+ uneditables = that.element.querySelectorAll("." + that.config.classNames.uneditableContainer ),
1327113293 isInUneditable = false;
1327213294
1327313295 for (var i = uneditables.length; i--;) {
@@ -13276,12 +13298,12 @@ wysihtml5.views.View = Base.extend(
1327613298 }
1327713299 }
1327813300
13279- if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.uneditableContainerClassname ]);
13301+ if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.classNames.uneditableContainer ]);
1328013302 }
1328113303 });
1328213304
1328313305 dom.observe(this.element, "blur", function() {
13284- dom.autoLink(that.element, [that.config.uneditableContainerClassname ]);
13306+ dom.autoLink(that.element, [that.config.classNames.uneditableContainer ]);
1328513307 });
1328613308 }
1328713309
@@ -13716,7 +13738,7 @@ wysihtml5.views.View = Base.extend(
1371613738 // If found an uneditable before caret then notify it before deletion
1371713739 var handleUneditableDeletion = function(composer) {
1371813740 var before = composer.selection.getBeforeSelection(true);
13719- if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.uneditableContainerClassname )) {
13741+ if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer )) {
1372013742 if (fixLastBrDeletionInTable(composer, true)) {
1372113743 return true;
1372213744 }
@@ -13881,7 +13903,7 @@ wysihtml5.views.View = Base.extend(
1388113903 // Make sure that images are selected when clicking on them
1388213904 var target = event.target,
1388313905 allImages = this.element.querySelectorAll('img'),
13884- notMyImages = this.element.querySelectorAll('.' + this.config.uneditableContainerClassname + ' img'),
13906+ notMyImages = this.element.querySelectorAll('.' + this.config.classNames.uneditableContainer + ' img'),
1388513907 myImages = wysihtml5.lang.array(allImages).without(notMyImages);
1388613908
1388713909 if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) {
@@ -13911,10 +13933,10 @@ wysihtml5.views.View = Base.extend(
1391113933 };
1391213934
1391313935 var handleClick = function(event) {
13914- if (this.config.uneditableContainerClassname ) {
13936+ if (this.config.classNames.uneditableContainer ) {
1391513937 // If uneditables is configured, makes clicking on uneditable move caret after clicked element (so it can be deleted like text)
1391613938 // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour
13917- var uneditable = wysihtml5.dom.getParentElement(event.target, { query: "." + this.config.uneditableContainerClassname }, false, this.element);
13939+ var uneditable = wysihtml5.dom.getParentElement(event.target, { query: "." + this.config.classNames.uneditableContainer }, false, this.element);
1391813940 if (uneditable) {
1391913941 this.selection.setAfter(uneditable);
1392013942 }
@@ -14338,10 +14360,6 @@ wysihtml5.views.View = Base.extend(
1433814360 pasteParserRulesets: null,
1433914361 // Parser method to use when the user inserts content
1434014362 parser: wysihtml5.dom.parse,
14341- // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
14342- composerClassName: "wysihtml5-editor",
14343- // Class name to add to the body when the wysihtml5 editor is supported
14344- bodyClassName: "wysihtml5-supported",
1434514363 // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
1434614364 useLineBreaks: true,
1434714365 // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
@@ -14354,9 +14372,18 @@ wysihtml5.views.View = Base.extend(
1435414372 cleanUp: true,
1435514373 // Whether to use div instead of secure iframe
1435614374 contentEditableMode: false,
14357- // Classname of container that editor should not touch and pass through
14358- // Pass false to disable
14359- uneditableContainerClassname: "wysihtml5-uneditable-container",
14375+ classNames: {
14376+ // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
14377+ composer: "wysihtml5-editor",
14378+ // Class name to add to the body when the wysihtml5 editor is supported
14379+ body: "wysihtml5-supported",
14380+ // classname added to editable area element (iframe/div) on creation
14381+ sandbox: "wysihtml5-sandbox",
14382+ // class on editable area with placeholder
14383+ placeholder: "wysihtml5-placeholder",
14384+ // Classname of container that editor should not touch and pass through
14385+ uneditableContainer: "wysihtml5-uneditable-container"
14386+ },
1436014387 // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste)
1436114388 // Also copied source is based directly on selection -
1436214389 // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection).
@@ -14368,9 +14395,14 @@ wysihtml5.views.View = Base.extend(
1436814395 /** @scope wysihtml5.Editor.prototype */ {
1436914396 constructor: function(editableElement, config) {
1437014397 this.editableElement = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
14371- this.config = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
14398+ this.config = wysihtml5.lang.object({}).merge(defaultConfig).merge(config, true ).get();
1437214399 this._isCompatible = wysihtml5.browser.supported();
1437314400
14401+ // make sure that rules override instead of extend
14402+ if (config && config.parserRules) {
14403+ this.config.parserRules = wysihtml5.lang.object(config.parserRules).clone(true);
14404+ }
14405+
1437414406 if (this.editableElement.nodeName.toLowerCase() != "textarea") {
1437514407 this.config.contentEditableMode = true;
1437614408 this.config.noTextarea = true;
@@ -14388,7 +14420,7 @@ wysihtml5.views.View = Base.extend(
1438814420 }
1438914421
1439014422 // Add class name to body, to indicate that the editor is supported
14391- wysihtml5.dom.addClass(document.body, this.config.bodyClassName );
14423+ wysihtml5.dom.addClass(document.body, this.config.classNames.body );
1439214424
1439314425 this.composer = new wysihtml5.views.Composer(this, this.editableElement, this.config);
1439414426 this.currentView = this.composer;
@@ -14474,7 +14506,7 @@ wysihtml5.views.View = Base.extend(
1447414506 "rules": this.config.parserRules,
1447514507 "cleanUp": this.config.cleanUp,
1447614508 "context": parseContext,
14477- "uneditableClass": this.config.uneditableContainerClassname ,
14509+ "uneditableClass": this.config.classNames.uneditableContainer ,
1447814510 "clearInternals" : clearInternals
1447914511 });
1448014512 if (typeof(htmlOrElement) === "object") {
@@ -14520,7 +14552,7 @@ wysihtml5.views.View = Base.extend(
1452014552 var cleanHtml = wysihtml5.quirks.cleanPastedHTML(oldHtml, {
1452114553 "referenceNode": this.composer.element,
1452214554 "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
14523- "uneditableClass": this.config.uneditableContainerClassname
14555+ "uneditableClass": this.config.classNames.uneditableContainer
1452414556 });
1452514557 this.composer.selection.deleteContents();
1452614558 this.composer.selection.insertHTML(cleanHtml);
0 commit comments