diff --git a/sample/html/out/document-util.html b/sample/html/out/document-util.html
index 1f26d39d5..dfc017530 100644
--- a/sample/html/out/document-util.html
+++ b/sample/html/out/document-util.html
@@ -335,6 +335,34 @@
+ It is judged whether it is a node related to the text style.
+ (strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label)
+
@@ -2111,6 +2139,7 @@
util
getAttributesToString
getByteLength
isWysiwygDiv
+
isTextStyleElement
isFormatElement
isRangeFormatElement
isFreeFormatElement
diff --git a/src/assets/css/suneditor.css b/src/assets/css/suneditor.css
index 8d6d44009..140cf3b2f 100755
--- a/src/assets/css/suneditor.css
+++ b/src/assets/css/suneditor.css
@@ -100,6 +100,9 @@
.sun-editor .se-btn-primary:hover, .sun-editor .se-btn-primary:focus {color:#000; background-color:#80bdff; border-color:#3f9dff; outline:0 none;}
.sun-editor .se-btn-primary:active {color:#fff; background-color:#3f9dff; border-color:#4592ff; -webkit-box-shadow:inset 0 3px 5px #4592ff; box-shadow:inset 0 3px 5px #4592ff;}
+/** --- Input focus effect */
+.sun-editor input:focus, .sun-editor select:focus, .sun-editor textarea:focus {border-color:#80bdff !important; border-width:1px; border-style:solid; outline:0; -webkit-box-shadow:0 0 0 0.2rem #c7deff; box-shadow:0 0 0 0.2rem #c7deff;}
+
/* se-btn button active*/
.sun-editor .se-btn:enabled.active {color:#4592ff; outline:0 none;}
.sun-editor .se-btn:enabled.active:hover, .sun-editor .se-btn:enabled.active:focus {background-color:#e1e1e1; border-color:#d1d1d1; outline:0 none;}
@@ -203,7 +206,6 @@
/* submenu layer - form group (color selector) */
.sun-editor .se-submenu-form-group {display:flex; width:100%; height:auto; padding:4px;}
.sun-editor .se-submenu-form-group input {flex:auto; display:inline-block; width:auto; height:33px; color:#555; font-size:12px; margin:1px 0 1px 0; padding:0; border-radius:0.25rem; border:1px solid #ccc;}
-.sun-editor .se-submenu-form-group input:focus {border-color:#80bdff; outline:0; -webkit-box-shadow:0 0 0 0.2rem #c7deff; box-shadow:0 0 0 0.2rem #c7deff;}
.sun-editor .se-submenu-form-group button {float:right; width:34px; height:34px; margin:0 0 0 4px !important;}
.sun-editor .se-submenu-form-group button.se-btn {border:1px solid #ccc;}
.sun-editor .se-submenu-form-group > div {position:relative;}
@@ -263,7 +265,6 @@
.sun-editor .se-dialog .se-dialog-inner .se-dialog-form .se-input-select {display:inline-block; width:auto; height:34px; font-size:14px; text-align:center; line-height:1.42857143; color:#000; border:1px solid #ccc; border-radius:4px; transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;}
.sun-editor .se-dialog .se-dialog-inner .se-dialog-form .se-input-control {display:inline-block; width:70px; height:34px; font-size:14px; text-align:center; line-height:1.42857143; color:#000; border:1px solid #ccc; border-radius:4px; transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;}
.sun-editor .se-dialog .se-dialog-inner .se-dialog-form .se-input-form {display:block; width:100%; height:34px; font-size:14px; line-height:1.42857143; padding:0 4px; color:#000; border:1px solid #ccc; border-radius:4px; transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;}
-.sun-editor .se-dialog .se-dialog-inner .se-dialog-form input:focus, .sun-editor .se-dialog .se-dialog-inner .se-dialog-form select:focus, .sun-editor .se-dialog .se-dialog-inner .se-dialog-form textarea:focus {border-color:#80bdff; outline:0; -webkit-box-shadow:0 0 0 0.2rem #c7deff; box-shadow:0 0 0 0.2rem #c7deff;}
.sun-editor .se-dialog .se-dialog-inner .se-dialog-form .se-video-ratio {width:70px; margin-left:4px;}
.sun-editor .se-dialog .se-dialog-inner .se-dialog-form a {color:#004cff;}
/* dialog - revert button */
@@ -284,7 +285,7 @@
/** --- controller ---------------------------------------------------------- */
.sun-editor .se-controller .se-arrow.se-arrow-up {border-bottom-color:rgba(0, 0, 0, .25);}
-.sun-editor .se-controller {position:absolute; display:none; overflow:visible; z-index:4; border:1px solid rgba(0, 0, 0, .25); border-radius:4px; text-align:start; text-decoration:none; text-shadow:none; text-transform:none; letter-spacing:normal; word-break:normal; word-spacing:normal; word-wrap:normal; white-space:normal; background-color:#fff; -webkit-background-clip:padding-box; background-clip:padding-box; -webkit-box-shadow:0 5px 10px rgba(0, 0, 0, .2); box-shadow:0 5px 10px rgba(0, 0, 0, .2); line-break:auto;}
+.sun-editor .se-controller {position:absolute; display:none; overflow:visible; z-index:5; border:1px solid rgba(0, 0, 0, .25); border-radius:4px; text-align:start; text-decoration:none; text-shadow:none; text-transform:none; letter-spacing:normal; word-break:normal; word-spacing:normal; word-wrap:normal; white-space:normal; background-color:#fff; -webkit-background-clip:padding-box; background-clip:padding-box; -webkit-box-shadow:0 5px 10px rgba(0, 0, 0, .2); box-shadow:0 5px 10px rgba(0, 0, 0, .2); line-break:auto;}
/* controller - button group */
.sun-editor .se-controller .se-btn-group {position:relative; display:flex; vertical-align:middle; padding:2px; top:0; left:0;}
diff --git a/src/lib/core.js b/src/lib/core.js
index 9a3d55f90..2642e7e1b 100755
--- a/src/lib/core.js
+++ b/src/lib/core.js
@@ -593,10 +593,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
},
/**
- * @description Use focus method of document
- * @private
+ * @description Focus to wysiwyg area using "native focus function"
*/
- _nativeFocus: function () {
+ nativeFocus: function () {
const caption = util.getParentElement(this.getSelectionNode(), 'figcaption');
if (caption) {
caption.focus();
@@ -614,7 +613,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
if (context.element.wysiwygFrame.style.display === 'none') return;
if (options.iframe) {
- this._nativeFocus();
+ this.nativeFocus();
} else {
try {
const range = this.getRange();
@@ -628,7 +627,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
this.setRange(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
}
} catch (e) {
- this._nativeFocus();
+ this.nativeFocus();
}
}
@@ -650,10 +649,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
} else if (videoComponent) {
this.selectComponent(videoComponent, 'video');
}
- } else {
+ } else if (focusEl) {
focusEl = util.getChildElement(focusEl, function (current) { return current.childNodes.length === 0 || current.nodeType === 3; }, true);
- if (!focusEl) this._nativeFocus();
+ if (!focusEl) this.nativeFocus();
else this.setRange(focusEl, focusEl.textContent.length, focusEl, focusEl.textContent.length);
+ } else {
+ this.nativeFocus();
}
},
@@ -675,7 +676,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
range.setStart(startCon, startOff);
range.setEnd(endCon, endOff);
} catch (error) {
- this._nativeFocus();
+ this.nativeFocus();
}
const selection = this.getSelection();
@@ -1103,14 +1104,24 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
* @returns {undefined|Object}
*/
insertNode: function (oNode, afterNode) {
+ const isComp = util.isFormatElement(oNode) || util.isRangeFormatElement(oNode) || util.isComponent(oNode);
+
+ if (!afterNode && isComp) {
+ const r = this.removeNode();
+ if (r.container.nodeType === 3 || util.isBreak(r.container)) {
+ const depthFormat = util.getParentElement(r.container, function (current) { return this.isRangeFormatElement(current); }.bind(util));
+ afterNode = util.splitElement(r.container, r.offset, !depthFormat ? 0 : util.getElementDepth(depthFormat) + 1);
+ if (afterNode) afterNode = afterNode.previousSibling;
+ }
+ }
+
const range = this.getRange();
const startCon = range.startContainer;
const startOff = range.startOffset;
const endCon = range.endContainer;
const endOff = range.endOffset;
const commonCon = range.commonAncestorContainer;
- let parentNode = null;
- let originAfter = null;
+ let parentNode, originAfter = null;
if (!afterNode) {
parentNode = startCon;
@@ -1153,11 +1164,36 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
if (!this.isEdgePoint(startCon, startOff)) removeNode = startCon.splitText(startOff);
parentNode.removeChild(removeNode);
+ if (parentNode.childNodes.length === 0 && isComp) {
+ parentNode.innerHTML = '
';
+ }
}
else {
- this.removeNode();
- parentNode = commonCon;
- afterNode = endCon;
+ const removedTag = this.removeNode();
+ const container = removedTag.container;
+ const prevContainer = removedTag.prevContainer;
+ if (container && container.childNodes.length === 0 && isComp) {
+ if (util.isFormatElement(container)) {
+ container.innerHTML = '
';
+ } else if (util.isRangeFormatElement(container)) {
+ container.innerHTML = '
';
+ }
+ }
+
+ if (!isComp && prevContainer) {
+ parentNode = prevContainer.nodeType === 3 ? prevContainer.parentNode : prevContainer;
+ if (parentNode.contains(container)) {
+ afterNode = container;
+ while (afterNode.parentNode === parentNode) {
+ afterNode = afterNode.parentNode;
+ }
+ } else {
+ afterNode = null;
+ }
+ } else {
+ parentNode = isComp ? commonCon : container;
+ afterNode = isComp ? endCon : null;
+ }
while (afterNode && afterNode.parentNode !== commonCon) {
afterNode = afterNode.parentNode;
@@ -1175,6 +1211,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
// --- insert node ---
try {
if (util.isFormatElement(oNode) || util.isRangeFormatElement(oNode) || (!util.isListCell(parentNode) && util.isComponent(oNode))) {
+ const oldParent = parentNode;
if (util.isList(afterNode)) {
parentNode = afterNode;
afterNode = null;
@@ -1185,12 +1222,18 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
parentNode = rangeCon ? container : container.parentNode;
afterNode = rangeCon ? null : container.nextSibling;
}
+
+ if (oldParent.childNodes.length === 0 && parentNode !== oldParent) util.removeItem(oldParent);
}
if (afterNode === commonCon && util.isBreak(afterNode) && !util.isBreak(oNode)) {
afterNode = afterNode.nextSibling;
}
+ if (isComp && !util.isRangeFormatElement(parentNode) && !util.isListCell(parentNode) && !util.isWysiwygDiv(parentNode)) {
+ afterNode = parentNode.nextElementSibling;
+ parentNode = parentNode.parentNode;
+ }
parentNode.insertBefore(oNode, afterNode);
} catch (e) {
parentNode.appendChild(oNode);
@@ -1278,7 +1321,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
}
} else {
if (childNodes.length === 0) {
- if (util.isFormatElement(commonCon) || util.isRangeFormatElement(commonCon) || util.isFormatElement(commonCon)) {
+ if (util.isFormatElement(commonCon) || util.isRangeFormatElement(commonCon) || util.isWysiwygDiv(commonCon)) {
return {
container: commonCon,
offset: 0
@@ -1366,6 +1409,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
}
container = endCon && endCon.parentNode ? endCon : startCon && startCon.parentNode ? startCon : (range.endContainer || range.startContainer);
+
+ if (!util.isWysiwygDiv(container)) {
+ const rc = util.removeItemAllParents(container);
+ if (rc) container = rc.sc || rc.ec || context.element.wysiwyg;
+ }
// set range
this.setRange(container, offset, container, offset);
@@ -1374,7 +1422,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
return {
container: container,
- offset: offset
+ offset: offset,
+ prevContainer: startCon && startCon.parentNode ? startCon : null
};
},
@@ -3258,8 +3307,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
container = pNode.firstChild;
offset = container.textContent.length;
} else if (util.onlyZeroWidthSpace(container)) {
- container = pNode;
- offset = 0;
+ container = newInnerNode;
+ offset = 1;
}
// node change
@@ -3333,12 +3382,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
this.focus();
break;
case 'codeView':
- this.toggleCodeView();
util.toggleClass(target, 'active');
+ this.toggleCodeView();
break;
case 'fullScreen':
- this.toggleFullScreen(target);
util.toggleClass(target, 'active');
+ this.toggleFullScreen(target);
break;
case 'indent':
case 'outdent':
@@ -3361,8 +3410,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
this.preview();
break;
case 'showBlocks':
- this.toggleDisplayBlocks();
util.toggleClass(target, 'active');
+ this.toggleDisplayBlocks();
break;
case 'save':
if (typeof options.callBackSave === 'function') {
@@ -3497,7 +3546,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
}
}
- this._nativeFocus();
+ this.nativeFocus();
// history stack
this.history.push(false);
@@ -3807,6 +3856,42 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
}
},
+ /**
+ * @description Returns HTML string according to tag type and configuration.
+ * Use only "cleanHTML", "convertContentsForEditor"
+ * @param {Node} node Node
+ * @param {Boolean} requireFormat If true, text nodes that do not have a format node is wrapped with the format tag.
+ * @private
+ */
+ _makeLine: function (node, requireFormat) {
+ // element
+ if (node.nodeType === 1) {
+ if (util._notAllowedTags(node)) return '';
+ if (!requireFormat || (util.isFormatElement(node) || util.isRangeFormatElement(node) || util.isComponent(node) || util.isMedia(node))) {
+ return node.outerHTML;
+ } else {
+ return '
' + node.outerHTML + '
';
+ }
+ }
+ // text
+ if (node.nodeType === 3) {
+ if (!requireFormat) return node.textContent;
+ const textArray = node.textContent.split(/\n/g);
+ let html = '';
+ for (let i = 0, tLen = textArray.length, text; i < tLen; i++) {
+ text = textArray[i].trim();
+ if (text.length > 0) html += '
' + text + '
';
+ }
+ return html;
+ }
+ // comments
+ if (node.nodeType === 8 && this._allowHTMLComments) {
+ return '<__comment__>' + node.textContent.trim() + '';
+ }
+
+ return '';
+ },
+
/**
* @description Gets the clean HTML code for editor
* @param {String} html HTML string
@@ -3815,19 +3900,25 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
* @returns {String}
*/
cleanHTML: function (html, whitelist) {
- const tagsAllowed = new this._w.RegExp('^(meta|script|link|style|[a-z]+\:[a-z]+)$', 'i');
- const domTree = this._d.createRange().createContextualFragment(html).childNodes;
+ const dom = this._d.createRange().createContextualFragment(html);
+ util._consistencyCheckOfHTML(dom);
+
+ const domTree = dom.childNodes;
let cleanHTML = '';
+ let requireFormat = false;
for (let i = 0, len = domTree.length, t; i < len; i++) {
t = domTree[i];
- if (t.nodeType === 8 && this._allowHTMLComments) {
- cleanHTML += '<__comment__>' + t.textContent.trim() + '';
- } else if (!tagsAllowed.test(t.nodeName)) {
- cleanHTML += t.nodeType === 1 ? t.outerHTML : t.nodeType === 3 ? t.textContent : '';
+ if (t.nodeType === 1 && !util.isTextStyleElement(t) && !util.isBreak(t) && !util._notAllowedTags(t)) {
+ requireFormat = true;
+ break;
}
}
+ for (let i = 0, len = domTree.length; i < len; i++) {
+ cleanHTML += this._makeLine(domTree[i], requireFormat);
+ }
+
cleanHTML = cleanHTML
.replace(/\n/g, '')
.replace(/<(script|style).*>(\n|.)*<\/(script|style)>/g, '')
@@ -3862,39 +3953,18 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
* @returns {String}
*/
convertContentsForEditor: function (contents) {
+ const dom = this._d.createRange().createContextualFragment(contents);
+ util._consistencyCheckOfHTML(dom);
+
let returnHTML = '';
- const domTree = this._d.createRange().createContextualFragment(contents).childNodes;
-
- for (let i = 0, len = domTree.length, baseHtml, t; i < len; i++) {
- t = domTree[i];
-
- if (t.nodeType === 8) {
- if (this._allowHTMLComments) {
- baseHtml = '<__comment__>' + t.textContent.trim() + '';
- } else {
- continue;
- }
- } else {
- baseHtml = t.outerHTML || t.textContent;
- }
-
- if (t.nodeType === 3) {
- const textArray = baseHtml.split(/\n/g);
- let text = '';
- for (let t = 0, tLen = textArray.length; t < tLen; t++) {
- text = textArray[t].trim();
- if (text.length > 0) returnHTML += '
' + text + '
';
- }
- } else {
- returnHTML += util.htmlRemoveWhiteSpace(baseHtml);
- }
+ const domTree = dom.childNodes;
+ for (let i = 0, len = domTree.length; i < len; i++) {
+ returnHTML += this._makeLine(domTree[i], true);
}
- if (returnHTML.length === 0) {
- contents = util._HTMLConvertor(contents);
- return '
' + (contents.length > 0 ? contents : '
') + '
';
- }
+ if (returnHTML.length === 0) return '
';
+ returnHTML = util.htmlRemoveWhiteSpace(returnHTML);
returnHTML = returnHTML
.replace(this.editorTagsWhitelistRegExp, '')
.replace(/\n/g, '')
@@ -4247,10 +4317,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
if (rangeEl) {
format = util.createElement(formatName || 'P');
format.innerHTML = rangeEl.innerHTML;
- if (format.childNodes.length === 0) format.innerHTML = '
';
+ if (util.onlyZeroWidthSpace(format)) format.innerHTML = util.zeroWidthSpace;
rangeEl.innerHTML = format.outerHTML;
- format = rangeEl.firstElementChild;
+ format = rangeEl.firstChild;
focusNode = util.getEdgeChildNodes(format, null).sc;
if (!focusNode) {
@@ -4268,10 +4338,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
if (commonCon.childNodes.length === 1 && util.isBreak(commonCon.firstChild)) {
br = commonCon.firstChild;
} else {
- br = util.createElement('BR');
+ br = util.createTextNode(util.zeroWidthSpace);
commonCon.appendChild(br);
}
- this.setRange(br, 0, br, 0);
+ this.setRange(br, 1, br, 1);
}
this.execCommand('formatBlock', false, (formatName || 'P'));
@@ -4294,6 +4364,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
}
offset = focusNode.nodeType === 3 ? focusNode.textContent.length : 1;
+ this.effectNode = null;
this.setRange(focusNode, offset, focusNode, offset);
}
};
@@ -4456,7 +4527,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
let target = e.target;
if (core._bindControllersOff) e.stopPropagation();
- if (/^input|textarea$/i.test(target.nodeName)) {
+ if (/^(input|textarea|select|option)$/i.test(target.nodeName)) {
core._antiBlur = false;
} else {
e.preventDefault();
@@ -4482,7 +4553,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
},
onClick_toolbar: function (e) {
- e.preventDefault();
+ if (/^(input|textarea|select|option)$/i.test(e.target.nodeName)) {
+ core._antiBlur = false;
+ } else {
+ e.preventDefault();
+ }
e.stopPropagation();
let target = e.target;
@@ -4567,13 +4642,13 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
}
}
- core._editorRange();
_w.setTimeout(core._editorRange.bind(core));
+ core._editorRange();
const selectionNode = core.getSelectionNode();
const formatEl = util.getFormatElement(selectionNode);
const rangeEl = util.getRangeFormatElement(selectionNode);
- if (core.getRange().collapsed && (!formatEl || formatEl === rangeEl) && targetElement.getAttribute('contenteditable') !== 'false') {
+ if ((!formatEl || formatEl === rangeEl) && targetElement.getAttribute('contenteditable') !== 'false') {
if (util.isList(rangeEl)) {
const oLi = util.createElement('LI');
const prevLi = selectionNode.nextElementSibling;
@@ -4584,7 +4659,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
}
e.preventDefault();
core.focus();
- } else {
+ } else {
event._applyTagEffects();
}
@@ -4755,18 +4830,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
onInput_wysiwyg: function (e) {
core._editorRange();
- const range = core.getRange();
- const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
- const data = (e.data === null ? '' : e.data === undefined ? ' ' : e.data) || '';
-
+ const data = (e.data === null ? '' : e.data === undefined ? ' ' : e.data) || '';
if (!core._charCount(data)) {
e.preventDefault();
e.stopPropagation();
}
- // component check
- if (selectRange || !data) core._checkComponents();
-
// history stack
core.history.push(true);
@@ -4781,7 +4850,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
const alt = e.altKey;
core.submenuOff();
- if (!event._directionKeyCode.test(keyCode)) _w.setTimeout(core._resourcesStateChange);
+
if (core._isBalloon) {
event._hideToolbar();
}
@@ -4831,7 +4900,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
while (attrs[0]) {
formatEl.removeAttribute(attrs[0].name);
}
- core._nativeFocus();
+ core.nativeFocus();
return false;
}
@@ -5356,10 +5425,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
core.setRange(selectionNode, so < 0 ? 0 : so, selectionNode, eo < 0 ? 0 : eo);
}
- const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
core._charCount('');
- // component check
- if (selectRange) core._checkComponents();
+
// history stack
core.history.push(true);
@@ -5920,12 +5987,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
try {
const parseDocument = core._parser.parseFromString(html, 'text/html');
const children = parseDocument.body.childNodes;
- let c, a;
+ let c, a, t;
while ((c = children[0])) {
- core.insertNode(c, a);
+ t = core.insertNode(c, a);
a = c;
}
- const offset = a.nodeType === 3 ? a.textContent.length : 1;
+ const offset = a.nodeType === 3 ? (t.endOffset || a.textContent.length): 1;
core.setRange(a, offset, a, offset);
} catch (error) {
core.execCommand('insertHTML', false, html);
@@ -5942,6 +6009,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _ic
}
}
+ core.effectNode = null;
core.focus();
// history stack
diff --git a/src/lib/util.js b/src/lib/util.js
index 9b1d20495..4dc624b57 100755
--- a/src/lib/util.js
+++ b/src/lib/util.js
@@ -251,6 +251,16 @@ const util = {
return false;
},
+ /**
+ * @description It is judged whether it is a node related to the text style.
+ * (strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label)
+ * @param {Element|String} element The element to check
+ * @returns {Boolean}
+ */
+ isTextStyleElement: function (element) {
+ return element && element.nodeType !== 3 && /^(strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label)$/i.test(element.nodeName);
+ },
+
/**
* @description It is judged whether it is the format element (P, DIV, H[1-6], PRE, LI)
* Format element also contain "free format Element"
@@ -258,8 +268,7 @@ const util = {
* @returns {Boolean}
*/
isFormatElement: function (element) {
- if (element && element.nodeType === 1 && (/^(P|DIV|H[1-6]|PRE|LI|TD|TH)$/i.test(element.nodeName) || this.hasClass(element, '(\\s|^)__se__format__replace_.+(\\s|$)|(\\s|^)__se__format__free_.+(\\s|$)')) && !this.isComponent(element) && !this.isWysiwygDiv(element)) return true;
- return false;
+ return (element && element.nodeType === 1 && (/^(P|DIV|H[1-6]|PRE|LI|TD|TH)$/i.test(element.nodeName) || this.hasClass(element, '(\\s|^)__se__format__replace_.+(\\s|$)|(\\s|^)__se__format__free_.+(\\s|$)')) && !this.isComponent(element) && !this.isWysiwygDiv(element));
},
/**
@@ -269,8 +278,7 @@ const util = {
* @returns {Boolean}
*/
isRangeFormatElement: function (element) {
- if (element && element.nodeType === 1 && (/^(BLOCKQUOTE|OL|UL|FIGCAPTION|TABLE|THEAD|TBODY|TR|TH|TD)$/i.test(element.nodeName) || this.hasClass(element, '(\\s|^)__se__format__range_.+(\\s|$)'))) return true;
- return false;
+ return (element && element.nodeType === 1 && (/^(BLOCKQUOTE|OL|UL|FIGCAPTION|TABLE|THEAD|TBODY|TR|TH|TD)$/i.test(element.nodeName) || this.hasClass(element, '(\\s|^)__se__format__range_.+(\\s|$)')));
},
/**
@@ -281,8 +289,7 @@ const util = {
* @returns {Boolean}
*/
isFreeFormatElement: function (element) {
- if (element && element.nodeType === 1 && (/^PRE$/i.test(element.nodeName) || this.hasClass(element, '(\\s|^)__se__format__free_.+(\\s|$)')) && !this.isComponent(element) && !this.isWysiwygDiv(element)) return true;
- return false;
+ return (element && element.nodeType === 1 && (/^PRE$/i.test(element.nodeName) || this.hasClass(element, '(\\s|^)__se__format__free_.+(\\s|$)')) && !this.isComponent(element) && !this.isWysiwygDiv(element));
},
/**
@@ -1042,7 +1049,7 @@ const util = {
* @returns {Element}
*/
detachNestedList: function (baseNode, all) {
- const rNode = this.__deleteNestedList(baseNode);
+ const rNode = this._deleteNestedList(baseNode);
let rangeElement, cNodes;
if (rNode) {
@@ -1065,7 +1072,7 @@ const util = {
}
for (let i = 0, len = rChildren.length; i < len; i++) {
- this.__deleteNestedList(rChildren[i]);
+ this._deleteNestedList(rChildren[i]);
}
if (rNode) {
@@ -1080,7 +1087,7 @@ const util = {
* @description Sub function of util.detachNestedList method.
* @private
*/
- __deleteNestedList: function (baseNode) {
+ _deleteNestedList: function (baseNode) {
const baseParent = baseNode.parentNode;
let sibling = baseParent;
let parent = sibling.parentNode;
@@ -1418,7 +1425,7 @@ const util = {
* @private
*/
_isIgnoreNodeChange: function (element) {
- return element.nodeType !== 3 && (element.getAttribute('contenteditable') === 'false' || !/^(strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label)$/i.test(typeof element === 'string' ? element : element.nodeName));
+ return element.nodeType !== 3 && (element.getAttribute('contenteditable') === 'false' || !this.isTextStyleElement(element));
},
/**
@@ -1441,6 +1448,15 @@ const util = {
return element.nodeType !== 3 && (this.isComponent(element) || /^(br|input|select|canvas|img|iframe|audio|video)$/i.test(typeof element === 'string' ? element : element.nodeName));
},
+ /**
+ * @description Check not Allowed tags
+ * @param {Element} element Element to check
+ * @private
+ */
+ _notAllowedTags: function (element) {
+ return /^(meta|script|link|style|[a-z]+\:[a-z]+)$/i.test(element.nodeName);
+ },
+
/**
* @description Create whitelist RegExp object.
* Return RegExp format: new RegExp("<\\/?(" + (?!\\b list[i] \\b) + ")[^>^<])+>", "g")
@@ -1459,6 +1475,60 @@ const util = {
regStr += '[^>^<])+>';
return new RegExp(regStr, 'g');
+ },
+
+ /**
+ * @description Fix tags that do not fit the editor format.
+ * @param {DOCUMENT_FRAGMENT_NODE} documentFragment Document fragment (nodeType === 11)
+ * @private
+ */
+ _consistencyCheckOfHTML: function (documentFragment) {
+ // wrong position
+ const wrongTags = this.getListChildren(documentFragment, function (current) {
+ return (this.isFormatElement(current) || this.isComponent(current)) && current.parentNode.nodeType === 1 && !this.isRangeFormatElement(current.parentNode);
+ }.bind(this));
+
+ const checkTags = [];
+ for (let i = 0, len = wrongTags.length, t, tp; i < len; i++) {
+ t = wrongTags[i];
+ tp = t.parentNode;
+ tp.parentNode.insertBefore(t, tp);
+ checkTags.push(tp);
+ }
+
+ for (let i = 0, len = checkTags.length, t; i < len; i++) {
+ t = checkTags[i];
+ if (this.onlyZeroWidthSpace(t.textContent.trim())) {
+ this.removeItem(t);
+ }
+ }
+
+ // remove empty tags
+ const emptyTags = this.getListChildren(documentFragment, function (current) {
+ return (!this.isTable(current) && !this.isListCell(current)) && (this.isFormatElement(current) || this.isRangeFormatElement(current) || this.isTextStyleElement(current)) && current.childNodes.length === 0 && !util.getParentElement(current, '.katex');
+ }.bind(this));
+
+ for (let i in emptyTags) {
+ this.removeItem(emptyTags[i]);
+ }
+
+ // wrong list
+ const wrongList = this.getListChildren(documentFragment, function (current) {
+ return this.isList(current.parentNode) && !this.isList(current) && !this.isListCell(current);
+ }.bind(this));
+
+ for (let i = 0, len = wrongList.length, t, tp, children; i < len; i++) {
+ t = wrongList[i];
+
+ tp = this.createElement('LI');
+ children = t.childNodes;
+ while (children[0]) {
+ tp.appendChild(children[0]);
+ }
+
+ t.parentNode.insertBefore(tp, t);
+ this.removeItem(t);
+ }
}
};
diff --git a/src/plugins/dialog/image.js b/src/plugins/dialog/image.js
index 9fe0972f6..dabd49eec 100644
--- a/src/plugins/dialog/image.js
+++ b/src/plugins/dialog/image.js
@@ -537,7 +537,7 @@ export default {
},
checkImagesInfo: function () {
- const images = this.context.element.wysiwyg.getElementsByTagName('IMG');
+ const images = [].slice.call(this.context.element.wysiwyg.getElementsByTagName('IMG'));
const imagePlugin = this.plugins.image;
const imagesInfo = this._variable._imagesInfo;
@@ -551,9 +551,18 @@ export default {
'size': img.getAttribute('data-file-size') || 0
});
}
+ return;
+ } else {
+ let infoUpdate = false;
+ for (let i = 0, len = imagesInfo.length; i < len; i++) {
+ if (images.indexOf(imagesInfo[i].element) === -1) {
+ infoUpdate = true;
+ break;
+ }
+ }
+ // pass
+ if (!infoUpdate) return;
}
- // pass
- return;
}
// check images
@@ -996,11 +1005,13 @@ export default {
const dataIndex = imageEl.getAttribute('data-index') * 1;
let focusEl = (imageContainer.previousElementSibling || imageContainer.nextElementSibling);
+ const emptyDiv = imageContainer.parentNode;
this.util.removeItem(imageContainer);
this.plugins.image.init.call(this);
-
this.controllersOff();
+ if (emptyDiv !== this.context.element.wysiwyg) this.util.removeItemAllParents(emptyDiv);
+
// focus
this.focusEdge(focusEl);
diff --git a/src/plugins/dialog/video.js b/src/plugins/dialog/video.js
index 5910b332f..fb12cc5d1 100644
--- a/src/plugins/dialog/video.js
+++ b/src/plugins/dialog/video.js
@@ -588,10 +588,14 @@ export default {
this._variable._videosCnt--;
const container = this.context.video._container;
let focusEl = (container.previousElementSibling || container.nextElementSibling);
+
+ const emptyDiv = container.parentNode;
this.util.removeItem(container);
this.plugins.video.init.call(this);
this.controllersOff();
+ if (emptyDiv !== this.context.element.wysiwyg) this.util.removeItemAllParents(emptyDiv);
+
// focus
this.focusEdge(focusEl);
diff --git a/src/plugins/submenu/table.js b/src/plugins/submenu/table.js
index 88aafab8f..00157657b 100644
--- a/src/plugins/submenu/table.js
+++ b/src/plugins/submenu/table.js
@@ -1384,8 +1384,11 @@ export default {
this.plugins.table.resizeTable.call(this);
break;
case 'remove':
+ const emptyDiv = contextTable._element.parentNode;
this.util.removeItem(contextTable._element);
this.controllersOff();
+
+ if (emptyDiv !== this.context.element.wysiwyg) this.util.removeItemAllParents(emptyDiv);
}
this.focus();
diff --git a/test/dev/suneditor_build_test.js b/test/dev/suneditor_build_test.js
index 90098973e..60c1266bd 100644
--- a/test/dev/suneditor_build_test.js
+++ b/test/dev/suneditor_build_test.js
@@ -26,7 +26,7 @@ const align = require('../../src/plugins/submenu/align')
suneditor.create("sample1", {
- plugins: [custom_plugin_dialog, custom_container, plugins.blockquote, plugins.link, plugins.table, plugins.textStyle],
+ plugins: [custom_plugin_dialog, custom_container, Resolutions, plugins.template, plugins.blockquote, plugins.link, plugins.table, plugins.textStyle],
// mode: "balloon",
// iframe: true,
width: '100%',
@@ -40,7 +40,7 @@ suneditor.create("sample1", {
buttonClass:'',
title:'Custom - Link',
dataDisplay:'dialog',
- innerHTML:''
+ innerHTML:'D'
},
{
name: 'custom_container',
@@ -48,9 +48,17 @@ suneditor.create("sample1", {
buttonClass:'',
title:'Custom - Container',
dataDisplay:'container',
- innerHTML:''
+ innerHTML:'C'
+ },
+ {
+ name: 'Resolutions',
+ dataCommand: 'Resolutions',
+ buttonClass:'',
+ title:'Resolutions - Container',
+ dataDisplay:'submenu',
+ innerHTML:'S'
},
- 'bold', 'italic'
+ 'bold', 'italic', 'template'
]
],
maxCharCount: 2000
@@ -77,7 +85,7 @@ let s1 = suneditor.create('editor', {
'preview', 'print', 'save', 'template']
],
icons: {
- undo: '',
+ undo: 'U',
bold: '
B'
},
width: '100%',
@@ -85,13 +93,13 @@ let s1 = suneditor.create('editor', {
defaultStyle: 'font-size: 10px;',
// fullPage: true,
// pasteTagsWhitelist: 'p|h[1-6]',
- attributesWhitelist: {
- table: "style",
- tbody: "style",
- thead: "style",
- tr: "style",
- td: "style"
- },
+ // attributesWhitelist: {
+ // table: "style",
+ // tbody: "style",
+ // thead: "style",
+ // tr: "style",
+ // td: "style"
+ // },
addTagsWhitelist: '//',
// addTagsWhitelist: '//',
formats: [
@@ -186,23 +194,24 @@ window.cm = CodeMirror
// });
window.sun_destroy1 = function () {
+ s1.destroy()
// s1.setDefaultStyle('font-family: cursive; font-size: 10px;');
- s1.setContents(''+
- ''+
- ''+
- '
'+
- '
'+
- '
'+
- '
'+
- ''+
- ''+
- ''+
- ''+
-
- ''+
- '')
+ // s1.setContents(''+
+ // ''+
+ // ''+
+ // '
'+
+ // '
'+
+ // '
'+
+ // '
'+
+ // ''+
+ // ''+
+ // ''+
+ // ''+
+
+ // ''+
+ // '')
}
window.sun_create1 = function () {
@@ -254,13 +263,13 @@ let ss = window.ss = suneditor.create(document.getElementById('editor1'), {
fullPage: true,
addTagsWhitelist: 'mark|canvas|label|select|option|input|nav|button',
imageUploadUrl: 'http://localhost:3000/files/upload',
- attributesWhitelist: {
- table: "style",
- tbody: "style",
- thead: "style",
- tr: "style",
- td: "style"
- },
+ // attributesWhitelist: {
+ // table: "style",
+ // tbody: "style",
+ // thead: "style",
+ // tr: "style",
+ // td: "style"
+ // },
templates: [
{
name: 'Template-1',
@@ -437,7 +446,7 @@ const editor = suneditor.init({
let s2 = window.s2 = editor.create(document.getElementById('editor2'), {
lang: lang.ru,
- mode: 'balloon',
+ mode: 'balloon-always',
// toolbarWidth: 500,
plugins: plugins,
// maxHeight: '400px',