,, tags and for allow only `href` attribute and allow only `src` atrribute == '1.png'
* });
- * editor.setEditorValue('Sorry! Goodby mr. Freeman');
- * console.log(editor.getEditorValue()); //Sorry! Freeman
+ * editor.value = 'Sorry! Goodby mr. Freeman';
+ * console.log(editor.value); //Sorry! Freeman
* ```
*
* @example
@@ -54,17 +55,23 @@ import {Select} from "../modules/Selection";
declare module "../Config" {
interface Config {
cleanHTML: {
+ timeout: number;
replaceNBSP: boolean,
cleanOnPaste: boolean,
- allowTags: false|string|{[key:string]: string}
+ removeEmptyElements: boolean,
+ allowTags: false | string | {[key:string]: string},
+ denyTags: false | string | {[key:string]: string}
}
}
}
Config.prototype.cleanHTML = {
+ timeout: 0,
+ removeEmptyElements: true,
replaceNBSP: true,
cleanOnPaste: true,
- allowTags: false
+ allowTags: false,
+ denyTags: false,
};
Config.prototype.controls.eraser = {
@@ -77,27 +84,26 @@ Config.prototype.controls.eraser = {
* Clean HTML after removeFormat and insertHorizontalRule command
*/
export function cleanHTML(editor: Jodit) {
-
if (editor.options.cleanHTML.cleanOnPaste) {
editor.events.on('processPaste', (event: Event, html: string) => {
return cleanFromWord(html);
});
}
- if (editor.options.cleanHTML.allowTags) {
- const
- attributesReg = /([^\[]*)\[([^\]]+)]/,
- seperator = /[\s]*,[\s]*/,
- attrReg = /^(.*)[\s]*=[\s]*(.*)$/;
- let
- allowTagsHash: {[key: string]: any} = {};
+ const
+ attributesReg = /([^\[]*)\[([^\]]+)]/,
+ seperator = /[\s]*,[\s]*/,
+ attrReg = /^(.*)[\s]*=[\s]*(.*)$/;
+
+ const getHash = (tags: false | string | {[key:string]: string}): {[key: string]: any} | false=> {
+ const tagsHash: {[key: string]: any} = {};
- if (typeof editor.options.cleanHTML.allowTags === 'string') {
- editor.options.cleanHTML.allowTags.split(seperator).map((elm) => {
+ if (typeof tags === 'string') {
+ tags.split(seperator).map((elm) => {
elm = trim(elm);
- let attr = attributesReg.exec(elm),
+ let attr: RegExpExecArray | null = attributesReg.exec(elm),
allowAttributes: {[key: string]: string | boolean} = {},
- attributeMap = function (attr: string) {
+ attributeMap = (attr: string) => {
attr = trim(attr);
const val: Array | null = attrReg.exec(attr);
if (val) {
@@ -108,75 +114,121 @@ export function cleanHTML(editor: Jodit) {
};
if (attr) {
- let attr2 = attr[2].split(seperator);
+ const attr2: string[] = attr[2].split(seperator);
if (attr[1]) {
attr2.map(attributeMap);
- allowTagsHash[attr[1].toUpperCase()] = allowAttributes;
+ tagsHash[attr[1].toUpperCase()] = allowAttributes;
}
} else {
- allowTagsHash[elm.toUpperCase()] = true;
+ tagsHash[elm.toUpperCase()] = true;
}
});
- } else {
- allowTagsHash = editor.options.cleanHTML.allowTags;
+
+ return tagsHash;
}
- editor.events.on('beforeSetElementValue', (data: {value: string}) => {
- if (editor.getRealMode() === consts.MODE_WYSIWYG) {
- const div: HTMLElement = Dom.create('div', '', editor.editorDocument);
+ if (tags) {
+ Object.keys(tags).forEach((tagName: string) => {
+ tagsHash[tagName.toUpperCase()] = tags[tagName];
+ });
+ return tagsHash;
+ }
- let node: Element|null = null,
- remove: Element[] = [],
- removeAttrs: string[],
- i: number = 0;
+ return false;
+ };
- div.innerHTML = data.value;
+ let
+ current: Node | false,
+ allowTagsHash: {[key: string]: any} | false = getHash(editor.options.cleanHTML.allowTags),
+ denyTagsHash: {[key: string]: any} | false = getHash(editor.options.cleanHTML.denyTags);
- if (div.firstChild) {
- node = div.firstChild;
- }
+ const isRemovableNode = (node: Node): boolean => {
+ if (
+ node.nodeType !== Node.TEXT_NODE &&
+ (
+ (allowTagsHash && !allowTagsHash[node.nodeName]) ||
+ (denyTagsHash && denyTagsHash[node.nodeName])
+ )
+ ) {
+ return true;
+ }
- while (node) {
- if (node && node.nodeName) {
- if (!allowTagsHash[node.nodeName]) {
- remove.push(node);
- } else if (allowTagsHash[node.nodeName] !== true) {
- if (node.attributes && node.attributes.length) {
- removeAttrs = [];
- for (i = 0; i < node.attributes.length; i += 1) {
- if (!allowTagsHash[node.nodeName][node.attributes[i].name] ||
- (
- allowTagsHash[node.nodeName][node.attributes[i].name] !== true &&
- allowTagsHash[node.nodeName][node.attributes[i].name] !== node.attributes[i].value
- )
- ) {
- removeAttrs.push(node.attributes[i].name);
- }
- }
- for (i = 0; i <= removeAttrs.length; i += 1) {
- node.removeAttribute(removeAttrs[i]);
+ if (
+ editor.options.cleanHTML.removeEmptyElements &&
+ current &&
+ node.nodeType === Node.ELEMENT_NODE &&
+ node.nodeName.match(IS_SPAN) &&
+ trim((node).innerHTML).length === 0 &&
+ !Dom.isOrContains(node, current)
+ ) {
+ return true;
+ }
+
+ return false;
+ };
+
+
+ editor.events
+ .on('change afterSetMode afterInit mousedown keydown', debounce(() => {
+ if (!editor.isDestructed && editor.isEditorMode()) {
+ current = editor.selection.current();
+
+ let node: Node | null = null,
+ remove: Node[] = [],
+ work: boolean = false,
+ i: number = 0;
+
+ const checkNode = (node: Node | null): void => {
+ if (node) {
+ if (isRemovableNode(node)) {
+ remove.push(node);
+ return checkNode(node.nextSibling);
+ }
+
+ if (allowTagsHash && allowTagsHash[node.nodeName] !== true) {
+ if (node.attributes && node.attributes.length) {
+ const removeAttrs: string[] = [];
+ for (i = 0; i < node.attributes.length; i += 1) {
+ if (!allowTagsHash[node.nodeName][node.attributes[i].name] ||
+ (
+ allowTagsHash[node.nodeName][node.attributes[i].name] !== true &&
+ allowTagsHash[node.nodeName][node.attributes[i].name] !== node.attributes[i].value
+ )
+ ) {
+ removeAttrs.push(node.attributes[i].name);
}
}
- }
- }
- node = Dom.next(node, elm => !!elm, div, true);
- }
- let parent: Node|null;
+ if (removeAttrs.length) {
+ work = true;
+ }
- for (i = 0; i < remove.length; i += 1) {
- parent = remove[i].parentNode;
- if (remove[i] && parent) {
- parent.removeChild(remove[i]);
+ removeAttrs.forEach((attr: string) => {
+ (node).removeAttribute(attr);
+ });
+ }
}
+
+ checkNode(node.firstChild);
+ checkNode(node.nextSibling);
}
+ };
- data.value = div.innerHTML;
+ if (editor.editor.firstChild) {
+ node = editor.editor.firstChild;
}
- });
- }
- editor.events
+
+ checkNode(node);
+
+
+ remove.forEach((node: Node) => node.parentNode && node.parentNode.removeChild(node));
+
+ if (remove.length || work) {
+ editor.setEditorValue();
+ }
+ }
+ }, editor.options.cleanHTML.timeout))
// remove invisible spaces then they already not need
.on('keyup', () => {
if (editor.selection.isCollapsed()) {
diff --git a/src/plugins/enter.ts b/src/plugins/enter.ts
index 083b32acd..3aa423fae 100644
--- a/src/plugins/enter.ts
+++ b/src/plugins/enter.ts
@@ -119,12 +119,14 @@ export function enter(editor: Jodit) {
if (!currentBox && current && !Dom.prev(current, (elm: Node | null) => (Dom.isBlock(elm) || (!!elm && Dom.isImage(elm, editor.ownerWindow))), editor.editor)) {
let needWrap: Node = current;
+
Dom.up(needWrap, (node: Node) => {
if (node && node.hasChildNodes() && node !== editor.editor) {
needWrap = node;
}
}, editor.editor);
- currentBox = Dom.wrap(needWrap, editor.options.enter, editor);
+
+ currentBox = Dom.wrapInline(needWrap, editor.options.enter, editor);
range = sel.rangeCount ? sel.getRangeAt(0) : editor.editorDocument.createRange();
}
diff --git a/src/plugins/formatBlock.ts b/src/plugins/formatBlock.ts
index cb1e2a17f..e778f80ac 100644
--- a/src/plugins/formatBlock.ts
+++ b/src/plugins/formatBlock.ts
@@ -10,6 +10,7 @@ import * as consts from '../constants';
import {Config} from "../Config";
import {ToolbarButton, ControlType} from "../modules/ToolbarCollection";
import {markerInfo} from "../modules/Selection";
+import {INVISIBLE_SPACE} from "../constants";
Config.prototype.controls.paragraph = {
@@ -93,8 +94,8 @@ export function formatBlock(editor: Jodit) {
const selectionInfo: markerInfo[] = editor.selection.save();
let currentBox: HTMLElement | false = current ? Dom.up(current, Dom.isBlock, editor.editor) : false;
- if (!currentBox && current) {
- currentBox = Dom.wrap(current, editor.options.enter, editor);
+ if ((!currentBox || currentBox.nodeName === 'LI') && current) {
+ currentBox = Dom.wrapInline(current, editor.options.enter, editor);
}
if (!currentBox) {
@@ -103,12 +104,20 @@ export function formatBlock(editor: Jodit) {
}
if (!currentBox.tagName.match(/TD|TH|TBODY|TABLE|THEAD/i)) {
- Dom.replace(currentBox, third, true, false, editor.editorDocument);
+ if (
+ third === editor.options.enter.toLowerCase() &&
+ currentBox.parentNode &&
+ currentBox.parentNode.nodeName === 'LI'
+ ) {
+ Dom.unwrap(currentBox);
+ } else {
+ Dom.replace(currentBox, third, true, false, editor.editorDocument);
+ }
} else {
if (!editor.selection.isCollapsed()) {
editor.selection.applyCSS({}, third)
} else {
- Dom.wrap(current, third, editor);
+ Dom.wrapInline(current, third, editor);
}
}
diff --git a/src/plugins/indent.ts b/src/plugins/indent.ts
index b6af28aab..f4fa5d1ed 100644
--- a/src/plugins/indent.ts
+++ b/src/plugins/indent.ts
@@ -51,7 +51,7 @@ export function indent(editor: Jodit) {
let currentBox: HTMLElement|false = current ? Dom.up(current, Dom.isBlock, editor.editor) : false;
if (!currentBox && current) {
- currentBox = Dom.wrap(current, editor.options.enter, editor);
+ currentBox = Dom.wrapInline(current, editor.options.enter, editor);
}
if (!currentBox) {
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index 63732988f..b81a39231 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -4,8 +4,6 @@
* Copyright 2013-2018 Valeriy Chupurnov https://xdsoft.net
*/
-import {xpath} from "./xpath";
-
export {addNewLine} from "./addNewLine";
export {autofocus} from "./autofocus";
export {backspace} from "./backspace";
@@ -13,7 +11,6 @@ export {bold} from "./bold";
export {cleanHTML} from "./cleanHTML";
export {color} from "./color";
import "./copyformat";
-import {DragAndDrop} from "./DragAndDrop";
export {enter} from "./enter";
export {errorMessages} from "./errorMessages";
export {font} from "./font";
diff --git a/src/plugins/justify.ts b/src/plugins/justify.ts
index 1aa194a26..f38537c82 100644
--- a/src/plugins/justify.ts
+++ b/src/plugins/justify.ts
@@ -130,7 +130,7 @@ export function justify(editor: Jodit) {
if (!currentBox && current) {
- currentBox = Dom.wrap(current, editor.options.enter, editor);
+ currentBox = Dom.wrapInline(current, editor.options.enter, editor);
}
justify(currentBox);
diff --git a/src/plugins/resizer.ts b/src/plugins/resizer.ts
index 582e1ee62..c6456efd5 100644
--- a/src/plugins/resizer.ts
+++ b/src/plugins/resizer.ts
@@ -224,6 +224,7 @@ export function resizer(editor: Jodit) {
start_x = e.clientX;
start_y = e.clientY;
+ editor.events.fire('hidePopup');
editor.lock(LOCK_KEY);
});
});
diff --git a/test/bootstrap.js b/test/bootstrap.js
index ffc2b7267..5044cf8fa 100644
--- a/test/bootstrap.js
+++ b/test/bootstrap.js
@@ -295,3 +295,4 @@ function offset(el) {
});
})();
+
diff --git a/test/tests/commandsTest.js b/test/tests/commandsTest.js
index 3aaf7bdc3..7511bf477 100644
--- a/test/tests/commandsTest.js
+++ b/test/tests/commandsTest.js
@@ -16,6 +16,20 @@ describe('Commands Jodit Editor Tests', function() {
expect(editor.getEditorValue()).to.equal('testtest2');
});
+ describe('Exec formatBlock for one inline element', function () {
+ it('Should wrap this element and all nearest inine element in block', function () {
+ var jodit = new Jodit(appendTestArea());
+ jodit.value = 'stop post ice';
+ var range = jodit.editorDocument.createRange();
+ range.setStart(jodit.editor.firstChild, 0);
+ range.setEnd(jodit.editor.firstChild, 2);
+ jodit.selection.selectRange(range);
+
+ jodit.execCommand('formatBlock', false, 'h1');
+
+ expect(jodit.value).to.equal('stop post ice');
+ });
+ });
it('Try exec the command "formatBlock" in text node then selection is collapsed it should wrap it node in H1', function() {
var editor = new Jodit(appendTestArea());
@@ -108,7 +122,7 @@ describe('Commands Jodit Editor Tests', function() {
'2' +
'3' +
''
- )
+ );
editor.execCommand('formatBlock', false, 'p');
expect(editor.value).to.be.equal('' +
@@ -384,8 +398,13 @@ describe('Commands Jodit Editor Tests', function() {
});
});
describe('Exec bold for collapsed range and move cursor in another place', function () {
- it('Should remove stron element', function () {
- var editor = new Jodit(appendTestArea());
+ it('Should remove STRONG element', function () {
+ var editor = new Jodit(appendTestArea(), {
+ cleanHTML: {
+ timeout: 0
+ }
+ });
+
editor.value = 'testtest';
var range = editor.editorDocument.createRange();
range.setStart(editor.editor.firstChild, 4);
@@ -396,7 +415,7 @@ describe('Commands Jodit Editor Tests', function() {
expect(editor.value).to.be.equal('testtest');
range.setStart(editor.editor.lastChild, 2);
- range.collapse(true)
+ range.collapse(true);
editor.selection.selectRange(range);
simulateEvent('mousedown', 0, editor.editor)
expect(editor.value).to.be.equal('testtest');
diff --git a/test/tests/editorTest.js b/test/tests/editorTest.js
index bebd75f65..7c5cc5ef4 100644
--- a/test/tests/editorTest.js
+++ b/test/tests/editorTest.js
@@ -577,9 +577,13 @@ describe('Jodit Editor Tests', function() {
editor.destruct();
});
describe('Cursor position', function () {
- it('Set cursor after node', function () {
+ it('Should set cursor after node', function () {
var area = appendTestArea();
- var editor = new Jodit(area);
+ var editor = new Jodit(area, {
+ cleanHTML: {
+ removeEmptyElements: false,
+ }
+ });
var spans = [editor.editorDocument.createElement('span'), editor.editorDocument.createElement('span'), editor.editorDocument.createElement('span')];
editor.selection.insertNode(spans[0]);
diff --git a/test/tests/enterTest.js b/test/tests/enterTest.js
index 6dcccdd9c..946d02808 100644
--- a/test/tests/enterTest.js
+++ b/test/tests/enterTest.js
@@ -111,6 +111,7 @@ describe('Enter behavior Jodit Editor Tests', function() {
' | ' +
' |