From 7801f573dd9ddfa46d4399a06cfbb02933f7e319 Mon Sep 17 00:00:00 2001 From: Oliver Pulges Date: Fri, 6 Mar 2015 13:38:30 +0200 Subject: [PATCH] Fixes #154 All class main element class names including placeholder should be configurable. --- src/commands/formatBlock.js | 2 +- src/commands/insertList.js | 2 +- src/dom/contenteditable_area.js | 5 +++- src/dom/sandbox.js | 5 +++- src/dom/simulate_placeholder.js | 4 ++-- src/editor.js | 32 ++++++++++++++++--------- src/views/composer.js | 23 ++++++++++-------- src/views/composer.observe.js | 8 +++---- test/editor_contenteditablemode_test.js | 14 ++++++----- test/editor_test.js | 14 ++++++----- 10 files changed, 66 insertions(+), 43 deletions(-) diff --git a/src/commands/formatBlock.js b/src/commands/formatBlock.js index ba8521f..12bf127 100644 --- a/src/commands/formatBlock.js +++ b/src/commands/formatBlock.js @@ -15,7 +15,7 @@ function cleanup(composer) { var container = composer.element, allElements = container.querySelectorAll(BLOCK_ELEMENTS), - uneditables = container.querySelectorAll(composer.config.uneditableContainerClassname), + uneditables = container.querySelectorAll(composer.config.classNames.uneditableContainer), elements = wysihtml5.lang.array(allElements).without(uneditables); for (var i = elements.length; i--;) { diff --git a/src/commands/insertList.js b/src/commands/insertList.js index ffb9c2d..68711f8 100644 --- a/src/commands/insertList.js +++ b/src/commands/insertList.js @@ -120,7 +120,7 @@ wysihtml5.commands.insertList = (function(wysihtml5) { if (tempElement) { isEmpty = wysihtml5.lang.array(["", "
", wysihtml5.INVISIBLE_SPACE]).contains(tempElement.innerHTML); - list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.uneditableContainerClassname); + list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.classNames.uneditableContainer); if (isEmpty) { composer.selection.selectNode(list.querySelector("li"), true); } diff --git a/src/dom/contenteditable_area.js b/src/dom/contenteditable_area.js index 64a6eaf..3c6a121 100644 --- a/src/dom/contenteditable_area.js +++ b/src/dom/contenteditable_area.js @@ -16,6 +16,9 @@ constructor: function(readyCallback, config, contentEditable) { this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION; this.config = wysihtml5.lang.object({}).merge(config).get(); + if (!this.config.className) { + this.config.className = "wysihtml5-sandbox"; + } if (contentEditable) { this.element = this._bindElement(contentEditable); } else { @@ -26,7 +29,7 @@ // creates a new contenteditable and initiates it _createElement: function() { var element = doc.createElement("div"); - element.className = "wysihtml5-sandbox"; + element.className = this.config.className; this._loadElement(element); return element; }, diff --git a/src/dom/sandbox.js b/src/dom/sandbox.js index 38a5417..3b55eec 100644 --- a/src/dom/sandbox.js +++ b/src/dom/sandbox.js @@ -55,6 +55,9 @@ constructor: function(readyCallback, config) { this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION; this.config = wysihtml5.lang.object({}).merge(config).get(); + if (!this.config.className) { + this.config.className = "wysihtml5-sandbox"; + } this.editableArea = this._createIframe(); }, @@ -109,7 +112,7 @@ _createIframe: function() { var that = this, iframe = doc.createElement("iframe"); - iframe.className = "wysihtml5-sandbox"; + iframe.className = this.config.className; wysihtml5.dom.setAttributes({ "security": "restricted", "allowtransparency": "true", diff --git a/src/dom/simulate_placeholder.js b/src/dom/simulate_placeholder.js index c805855..8a3e5df 100644 --- a/src/dom/simulate_placeholder.js +++ b/src/dom/simulate_placeholder.js @@ -13,8 +13,8 @@ * wysihtml.dom.simulatePlaceholder(this, composer, "Foobar"); */ (function(dom) { - dom.simulatePlaceholder = function(editor, view, placeholderText) { - var CLASS_NAME = "placeholder", + dom.simulatePlaceholder = function(editor, view, placeholderText, placeholderClassName) { + var CLASS_NAME = placeholderClassName || "wysihtml5-placeholder", unset = function() { var composerIsVisible = view.element.offsetWidth > 0 && view.element.offsetHeight > 0; if (view.hasPlaceholderSet()) { diff --git a/src/editor.js b/src/editor.js index f36d794..a047411 100644 --- a/src/editor.js +++ b/src/editor.js @@ -55,10 +55,6 @@ pasteParserRulesets: null, // Parser method to use when the user inserts content parser: wysihtml5.dom.parse, - // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option - composerClassName: "wysihtml5-editor", - // Class name to add to the body when the wysihtml5 editor is supported - bodyClassName: "wysihtml5-supported", // By default wysihtml5 will insert a
for line breaks, set this to false to use

useLineBreaks: true, // Array (or single string) of stylesheet urls to be loaded in the editor's iframe @@ -71,9 +67,18 @@ cleanUp: true, // Whether to use div instead of secure iframe contentEditableMode: false, - // Classname of container that editor should not touch and pass through - // Pass false to disable - uneditableContainerClassname: "wysihtml5-uneditable-container", + classNames: { + // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option + composer: "wysihtml5-editor", + // Class name to add to the body when the wysihtml5 editor is supported + body: "wysihtml5-supported", + // classname added to editable area element (iframe/div) on creation + sandbox: "wysihtml5-sandbox", + // class on editable area with placeholder + placeholder: "wysihtml5-placeholder", + // Classname of container that editor should not touch and pass through + uneditableContainer: "wysihtml5-uneditable-container" + }, // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste) // Also copied source is based directly on selection - // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection). @@ -85,9 +90,14 @@ /** @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.config = wysihtml5.lang.object({}).merge(defaultConfig).merge(config, true).get(); this._isCompatible = wysihtml5.browser.supported(); + // make sure that rules override instead of extend + if (config && config.parserRules) { + this.config.parserRules = wysihtml5.lang.object(config.parserRules).clone(true); + } + if (this.editableElement.nodeName.toLowerCase() != "textarea") { this.config.contentEditableMode = true; this.config.noTextarea = true; @@ -105,7 +115,7 @@ } // Add class name to body, to indicate that the editor is supported - wysihtml5.dom.addClass(document.body, this.config.bodyClassName); + wysihtml5.dom.addClass(document.body, this.config.classNames.body); this.composer = new wysihtml5.views.Composer(this, this.editableElement, this.config); this.currentView = this.composer; @@ -191,7 +201,7 @@ "rules": this.config.parserRules, "cleanUp": this.config.cleanUp, "context": parseContext, - "uneditableClass": this.config.uneditableContainerClassname, + "uneditableClass": this.config.classNames.uneditableContainer, "clearInternals" : clearInternals }); if (typeof(htmlOrElement) === "object") { @@ -237,7 +247,7 @@ var cleanHtml = wysihtml5.quirks.cleanPastedHTML(oldHtml, { "referenceNode": this.composer.element, "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}], - "uneditableClass": this.config.uneditableContainerClassname + "uneditableClass": this.config.classNames.uneditableContainer }); this.composer.selection.deleteContents(); this.composer.selection.insertHTML(cleanHtml); diff --git a/src/views/composer.js b/src/views/composer.js index 76f056e..081a51e 100644 --- a/src/views/composer.js +++ b/src/views/composer.js @@ -153,14 +153,17 @@ _initContentEditableArea: function() { var that = this; - if (this.config.noTextarea) { this.sandbox = new dom.ContentEditableArea(function() { that._create(); - }, {}, this.editableArea); + }, { + className: this.config.classNames.sandbox + }, this.editableArea); } else { this.sandbox = new dom.ContentEditableArea(function() { that._create(); + }, { + className: this.config.classNames.sandbox }); this.editableArea = this.sandbox.getContentEditable(); dom.insert(this.editableArea).after(this.textarea.element); @@ -170,11 +173,11 @@ _initSandbox: function() { var that = this; - this.sandbox = new dom.Sandbox(function() { that._create(); }, { - stylesheets: this.config.stylesheets + stylesheets: this.config.stylesheets, + className: this.config.classNames.sandbox }); this.editableArea = this.sandbox.getIframe(); @@ -208,7 +211,7 @@ } // Make sure our selection handler is ready - this.selection = new wysihtml5.Selection(this.parent, this.element, this.config.uneditableContainerClassname); + this.selection = new wysihtml5.Selection(this.parent, this.element, this.config.classNames.uneditableContainer); // Make sure commands dispatcher is ready this.commands = new wysihtml5.Commands(this.parent); @@ -219,7 +222,7 @@ ]).from(this.textarea.element).to(this.element); } - dom.addClass(this.element, this.config.composerClassName); + dom.addClass(this.element, this.config.classNames.composer); // // Make the editor look like the original textarea, by syncing styles if (this.config.style && !this.config.contentEditableMode) { @@ -245,7 +248,7 @@ ? this.config.placeholder : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")); if (placeholderText) { - dom.simulatePlaceholder(this.parent, this, placeholderText); + dom.simulatePlaceholder(this.parent, this, placeholderText, this.config.classNames.placeholder); } // Make sure that the browser avoids using inline styles whenever possible @@ -297,7 +300,7 @@ this.parent.on("newword:composer", function() { if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) { var nodeWithSelection = that.selection.getSelectedNode(), - uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname), + uneditables = that.element.querySelectorAll("." + that.config.classNames.uneditableContainer), isInUneditable = false; for (var i = uneditables.length; i--;) { @@ -306,12 +309,12 @@ } } - if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.uneditableContainerClassname]); + if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.classNames.uneditableContainer]); } }); dom.observe(this.element, "blur", function() { - dom.autoLink(that.element, [that.config.uneditableContainerClassname]); + dom.autoLink(that.element, [that.config.classNames.uneditableContainer]); }); } diff --git a/src/views/composer.observe.js b/src/views/composer.observe.js index d534726..e1f5b37 100644 --- a/src/views/composer.observe.js +++ b/src/views/composer.observe.js @@ -53,7 +53,7 @@ // If found an uneditable before caret then notify it before deletion var handleUneditableDeletion = function(composer) { var before = composer.selection.getBeforeSelection(true); - if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.uneditableContainerClassname)) { + if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) { if (fixLastBrDeletionInTable(composer, true)) { return true; } @@ -218,7 +218,7 @@ // Make sure that images are selected when clicking on them var target = event.target, allImages = this.element.querySelectorAll('img'), - notMyImages = this.element.querySelectorAll('.' + this.config.uneditableContainerClassname + ' img'), + notMyImages = this.element.querySelectorAll('.' + this.config.classNames.uneditableContainer + ' img'), myImages = wysihtml5.lang.array(allImages).without(notMyImages); if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) { @@ -248,10 +248,10 @@ }; var handleClick = function(event) { - if (this.config.uneditableContainerClassname) { + if (this.config.classNames.uneditableContainer) { // If uneditables is configured, makes clicking on uneditable move caret after clicked element (so it can be deleted like text) // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour - var uneditable = wysihtml5.dom.getParentElement(event.target, { query: "." + this.config.uneditableContainerClassname }, false, this.element); + var uneditable = wysihtml5.dom.getParentElement(event.target, { query: "." + this.config.classNames.uneditableContainer }, false, this.element); if (uneditable) { this.selection.setAfter(uneditable); } diff --git a/test/editor_contenteditablemode_test.js b/test/editor_contenteditablemode_test.js index a04fe81..b75a9d5 100644 --- a/test/editor_contenteditablemode_test.js +++ b/test/editor_contenteditablemode_test.js @@ -200,11 +200,11 @@ if (wysihtml5.browser.supported()) { var composerElement = that.editableArea; equal(wysihtml5.dom.getTextContent(composerElement), placeholderText, "Placeholder text correctly copied into textarea"); - ok(wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor got 'placeholder' css class"); + ok(wysihtml5.dom.hasClass(composerElement, "wysihtml5-placeholder"), "Editor got 'placeholder' css class"); ok(editor.hasPlaceholderSet(), "'hasPlaceholderSet' returns correct value when placeholder is actually set"); editor.fire("focus:composer"); equal(wysihtml5.dom.getTextContent(composerElement), "", "Editor is empty after focus"); - ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class"); + ok(!wysihtml5.dom.hasClass(composerElement, "wysihtml5-placeholder"), "Editor hasn't got 'placeholder' css class"); ok(!editor.hasPlaceholderSet(), "'hasPlaceholderSet' returns correct value when placeholder isn't actually set"); editor.fire("blur:composer"); equal(wysihtml5.dom.getTextContent(composerElement), placeholderText, "Editor restored placeholder text after unfocus"); @@ -213,14 +213,14 @@ if (wysihtml5.browser.supported()) { composerElement.innerHTML = "some content"; editor.fire("blur:composer"); equal(wysihtml5.dom.getTextContent(composerElement), "some content"); - ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class"); + ok(!wysihtml5.dom.hasClass(composerElement, "wysihtml5-placeholder"), "Editor hasn't got 'placeholder' css class"); editor.fire("focus:composer"); // Following html causes innerText and textContent to report an empty string var html = ''; composerElement.innerHTML = html; editor.fire("blur:composer"); equal(composerElement.innerHTML.toLowerCase(), html, "HTML hasn't been cleared even though the innerText and textContent properties indicate empty content."); - ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class"); + ok(!wysihtml5.dom.hasClass(composerElement, "wysihtml5-placeholder"), "Editor hasn't got 'placeholder' css class"); start(); }); }); @@ -233,8 +233,10 @@ if (wysihtml5.browser.supported()) { var editor = new wysihtml5.Editor(this.editableArea, { parserRules: { tags: { p: { rename_tag: "div" } } }, - bodyClassName: "editor-is-supported", - composerClassName: "editor" + classNames: { + body: "editor-is-supported", + composer: "editor" + } }); editor.on("load", function() { diff --git a/test/editor_test.js b/test/editor_test.js index 1e79859..582acc8 100644 --- a/test/editor_test.js +++ b/test/editor_test.js @@ -308,11 +308,11 @@ if (wysihtml5.browser.supported()) { editor.on("load", function() { var composerElement = that.getComposerElement(); equal(wysihtml5.dom.getTextContent(composerElement), placeholderText, "Placeholder text correctly copied into textarea"); - ok(wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor got 'placeholder' css class"); + ok(wysihtml5.dom.hasClass(composerElement, "wysihtml5-placeholder"), "Editor got 'placeholder' css class"); ok(editor.hasPlaceholderSet(), "'hasPlaceholderSet' returns correct value when placeholder is actually set"); editor.fire("focus:composer"); equal(wysihtml5.dom.getTextContent(composerElement), "", "Editor is empty after focus"); - ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class"); + ok(!wysihtml5.dom.hasClass(composerElement, "wysihtml5-placeholder"), "Editor hasn't got 'placeholder' css class"); ok(!editor.hasPlaceholderSet(), "'hasPlaceholderSet' returns correct value when placeholder isn't actually set"); editor.fire("blur:composer"); equal(wysihtml5.dom.getTextContent(composerElement), placeholderText, "Editor restored placeholder text after unfocus"); @@ -321,14 +321,14 @@ if (wysihtml5.browser.supported()) { composerElement.innerHTML = "some content"; editor.fire("blur:composer"); equal(wysihtml5.dom.getTextContent(composerElement), "some content"); - ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class"); + ok(!wysihtml5.dom.hasClass(composerElement, "wysihtml5-placeholder"), "Editor hasn't got 'placeholder' css class"); editor.fire("focus:composer"); // Following html causes innerText and textContent to report an empty string var html = ''; composerElement.innerHTML = html; editor.fire("blur:composer"); equal(composerElement.innerHTML.toLowerCase(), html, "HTML hasn't been cleared even though the innerText and textContent properties indicate empty content."); - ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class"); + ok(!wysihtml5.dom.hasClass(composerElement, "wysihtml5-placeholder"), "Editor hasn't got 'placeholder' css class"); setTimeout(function() { that.form.reset(); @@ -351,8 +351,10 @@ if (wysihtml5.browser.supported()) { var editor = new wysihtml5.Editor(this.textareaElement, { parserRules: { tags: { p: { rename_tag: "div" } } }, - bodyClassName: "editor-is-supported", - composerClassName: "editor" + classNames: { + body: "editor-is-supported", + composer: "editor" + } }); editor.on("load", function() {