Skip to content

Commit

Permalink
Fixes #154 All class main element class names including placeholder s…
Browse files Browse the repository at this point in the history
…hould be configurable.
  • Loading branch information
Oliver Pulges committed Mar 6, 2015
1 parent cf01599 commit 7801f57
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/commands/formatBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -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--;) {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/insertList.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ wysihtml5.commands.insertList = (function(wysihtml5) {

if (tempElement) {
isEmpty = wysihtml5.lang.array(["", "<br>", 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);
}
Expand Down
5 changes: 4 additions & 1 deletion src/dom/contenteditable_area.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
},
Expand Down
5 changes: 4 additions & 1 deletion src/dom/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
},

Expand Down Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions src/dom/simulate_placeholder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
32 changes: 21 additions & 11 deletions src/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <br> for line breaks, set this to false to use <p>
useLineBreaks: true,
// Array (or single string) of stylesheet urls to be loaded in the editor's iframe
Expand All @@ -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).
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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);
Expand Down
23 changes: 13 additions & 10 deletions src/views/composer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();

Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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--;) {
Expand All @@ -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]);
});
}

Expand Down
8 changes: 4 additions & 4 deletions src/views/composer.observe.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
}
Expand Down
14 changes: 8 additions & 6 deletions test/editor_contenteditablemode_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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 = '<img>';
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();
});
});
Expand All @@ -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() {
Expand Down
14 changes: 8 additions & 6 deletions test/editor_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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 = '<img>';
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();
Expand All @@ -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() {
Expand Down

0 comments on commit 7801f57

Please sign in to comment.