suneditor 2.43.6 → 2.43.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suneditor",
3
- "version": "2.43.6",
3
+ "version": "2.43.9",
4
4
  "description": "Pure JavaScript based WYSIWYG web editor",
5
5
  "author": "JiHong.Lee",
6
6
  "license": "MIT",
@@ -396,6 +396,16 @@ export default {
396
396
  * @private
397
397
  */
398
398
  _initOptions: function (element, options) {
399
+ const plugins = {};
400
+ if (options.plugins) {
401
+ const _plugins = options.plugins;
402
+ const pluginsValues = _plugins.length ? _plugins : Object.keys(_plugins).map(function(name) { return _plugins[name]; });
403
+ for (let i = 0, len = pluginsValues.length, p; i < len; i++) {
404
+ p = pluginsValues[i].default || pluginsValues[i];
405
+ plugins[p.name] = p;
406
+ }
407
+ }
408
+ options.plugins = plugins;
399
409
  /** Values */
400
410
  options.lang = options.lang || _defaultLang;
401
411
  options.value = typeof options.value === 'string' ? options.value : null;
@@ -450,6 +460,7 @@ export default {
450
460
  options.toolbarWidth = options.toolbarWidth ? (util.isNumber(options.toolbarWidth) ? options.toolbarWidth + 'px' : options.toolbarWidth) : 'auto';
451
461
  options.toolbarContainer = typeof options.toolbarContainer === 'string' ? document.querySelector(options.toolbarContainer) : options.toolbarContainer;
452
462
  options.stickyToolbar = (/balloon/i.test(options.mode) || !!options.toolbarContainer) ? -1 : options.stickyToolbar === undefined ? 0 : (/^\d+/.test(options.stickyToolbar) ? util.getNumber(options.stickyToolbar, 0) : -1);
463
+ options.hideToolbar = !!options.hideToolbar;
453
464
  options.fullScreenOffset = options.fullScreenOffset === undefined ? 0 : (/^\d+/.test(options.fullScreenOffset) ? util.getNumber(options.fullScreenOffset, 0) : 0);
454
465
  options.fullPage = !!options.fullPage;
455
466
  options.iframe = options.fullPage || !!options.iframe;
@@ -493,7 +504,7 @@ export default {
493
504
  options.className = (typeof options.className === 'string' && options.className.length > 0) ? ' ' + options.className : '';
494
505
  options.defaultStyle = typeof options.defaultStyle === 'string' ? options.defaultStyle : '';
495
506
  /** Defining menu items */
496
- options.font = !options.font ? null : options.font;
507
+ options.font = !options.font ? ['Arial', 'Comic Sans MS', 'Courier New', 'Impact', 'Georgia', 'tahoma', 'Trebuchet MS', 'Verdana'] : options.font;
497
508
  options.fontSize = !options.fontSize ? null : options.fontSize;
498
509
  options.formats = !options.formats ? null : options.formats;
499
510
  options.colorList = !options.colorList ? null : options.colorList;
@@ -756,12 +767,12 @@ export default {
756
767
  * @description Create editor HTML
757
768
  * @param {Array} doc document object
758
769
  * @param {Array} buttonList option.buttonList
759
- * @param {Array|Object|null} _plugins Plugins
770
+ * @param {Object|null} plugins Plugins
760
771
  * @param {Array} options options
761
772
  * @returns {Object} { element: (Element) Toolbar element, plugins: (Array|null) Plugins Array, pluginCallButtons: (Object), responsiveButtons: (Array) }
762
773
  * @private
763
774
  */
764
- _createToolBar: function (doc, buttonList, _plugins, options) {
775
+ _createToolBar: function (doc, buttonList, plugins, options) {
765
776
  const separator_vertical = doc.createElement('DIV');
766
777
  separator_vertical.className = 'se-toolbar-separator-vertical';
767
778
 
@@ -778,14 +789,6 @@ export default {
778
789
  const defaultButtonList = this._defaultButtons(options);
779
790
  const pluginCallButtons = {};
780
791
  const responsiveButtons = [];
781
- const plugins = {};
782
- if (_plugins) {
783
- const pluginsValues = _plugins.length ? _plugins : Object.keys(_plugins).map(function(name) { return _plugins[name]; });
784
- for (let i = 0, len = pluginsValues.length, p; i < len; i++) {
785
- p = pluginsValues[i].default || pluginsValues[i];
786
- plugins[p.name] = p;
787
- }
788
- }
789
792
 
790
793
  let module = null;
791
794
  let button = null;
@@ -926,6 +929,8 @@ export default {
926
929
  tool_cover.className = 'se-toolbar-cover';
927
930
  tool_bar.appendChild(tool_cover);
928
931
 
932
+ if (options.hideToolbar) tool_bar.style.display = 'none';
933
+
929
934
  return {
930
935
  'element': tool_bar,
931
936
  'plugins': plugins,
package/src/lib/core.js CHANGED
@@ -41,6 +41,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
41
41
  _parser: new _w.DOMParser(),
42
42
  _prevRtl: options.rtl,
43
43
  _editorHeight: 0,
44
+ _editorHeightPadding: 0,
44
45
  _listCamel: options.__listCommonStyle,
45
46
  _listKebab: util.camelToKebabCase(options.__listCommonStyle),
46
47
 
@@ -282,6 +283,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
282
283
  * @private
283
284
  */
284
285
  _attributesWhitelistRegExp: null,
286
+ _attributesWhitelistRegExp_all_data: null,
285
287
 
286
288
  /**
287
289
  * @description Attributes blacklist used by the cleanHTML method
@@ -468,6 +470,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
468
470
  */
469
471
  _styleCommandMap: null,
470
472
 
473
+ /**
474
+ * @private
475
+ */
476
+ _cleanStyleRegExp: {
477
+ span: new _w.RegExp('\s*(font-family|font-size|color|background-color)\s*:[^;]+(?!;)*', 'ig'),
478
+ format: new _w.RegExp('\s*(text-align|margin-left|margin-right)\s*:[^;]+(?!;)*', 'ig')
479
+ },
480
+
471
481
  /**
472
482
  * @description Variables used internally in editor operation
473
483
  * @property {Boolean} isCodeView State of code view
@@ -1500,7 +1510,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1500
1510
 
1501
1511
  const currentFormatEl = util.getFormatElement(this.getSelectionNode(), null);
1502
1512
  let oFormat = null;
1503
- if (util.isFreeFormatElement(currentFormatEl || element.parentNode)) {
1513
+ if (!util.isFormatElement(element) && util.isFreeFormatElement(currentFormatEl || element.parentNode)) {
1504
1514
  oFormat = util.createElement('BR');
1505
1515
  } else {
1506
1516
  const oFormatName = formatNode ? (typeof formatNode === 'string' ? formatNode : formatNode.nodeName) : (util.isFormatElement(currentFormatEl) && !util.isRangeFormatElement(currentFormatEl) && !util.isFreeFormatElement(currentFormatEl)) ? currentFormatEl.nodeName : options.defaultTag;
@@ -1691,7 +1701,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1691
1701
  const isEdge = this.isEdgePoint(range.endContainer, range.endOffset, 'end');
1692
1702
  const r = this.removeNode();
1693
1703
  const container = r.container;
1694
- const prevContainer = r.prevContainer;
1704
+ const prevContainer = (container === r.prevContainer && range.collapsed) ? null : r.prevContainer;
1695
1705
 
1696
1706
  if (insertListCell && prevContainer) {
1697
1707
  tempParentNode = prevContainer.nodeType === 3 ? prevContainer.parentNode : prevContainer;
@@ -1731,7 +1741,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1731
1741
  newCell.appendChild(line.lastElementChild);
1732
1742
  }
1733
1743
  if (newCell) {
1734
- tempParentNode.insertBefore(newCell, line.nextElementSibling);
1744
+ line.parentNode.insertBefore(newCell, line.nextElementSibling);
1735
1745
  tempAfterNode = afterNode = newCell;
1736
1746
  }
1737
1747
  }
@@ -5031,10 +5041,16 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5031
5041
  // element
5032
5042
  if (node.nodeType === 1) {
5033
5043
  if (util._disallowedTags(node)) return '';
5044
+
5045
+ const ch = util.getListChildNodes(node, function(current) { return util.isSpanWithoutAttr(current); }) || [];
5046
+ for (let i = ch.length - 1; i >= 0; i--) {
5047
+ ch[i].outerHTML = ch[i].innerHTML;
5048
+ }
5049
+
5034
5050
  if (!requireFormat || (util.isFormatElement(node) || util.isRangeFormatElement(node) || util.isComponent(node) || util.isMedia(node) || (util.isAnchor(node) && util.isMedia(node.firstElementChild)))) {
5035
- return node.outerHTML;
5051
+ return util.isSpanWithoutAttr(node) ? node.innerHTML : node.outerHTML;
5036
5052
  } else {
5037
- return '<' + defaultTag + '>' + node.outerHTML + '</' + defaultTag + '>';
5053
+ return '<' + defaultTag + '>' + (util.isSpanWithoutAttr(node) ? node.innerHTML : node.outerHTML) + '</' + defaultTag + '>';
5038
5054
  }
5039
5055
  }
5040
5056
  // text
@@ -5086,6 +5102,46 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5086
5102
  .replace(this.editorTagsBlacklistRegExp, '');
5087
5103
  },
5088
5104
 
5105
+ _cleanStyle: function (m, v, tagName) {
5106
+ const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
5107
+ if (sv) {
5108
+ if (!v) v = [];
5109
+ const style = sv[0].replace(/&quot;/g, '').match(this._cleanStyleRegExp[tagName]);
5110
+ if (style) {
5111
+ const allowedStyle = [];
5112
+ for (let i = 0, len = style.length, r; i < len; i++) {
5113
+ r = style[i].match(/(.+)(:)([^:]+$)/);
5114
+ if (r && !/inherit|initial/i.test(r[3])) {
5115
+ const k = util.kebabToCamelCase(r[1].trim());
5116
+ const v = this.wwComputedStyle[k].replace(/"/g, '');
5117
+ const c = r[3].trim();
5118
+ switch (k) {
5119
+ case 'fontFamily':
5120
+ if (!options.plugins.font || options.font.indexOf(c) === -1) continue;
5121
+ break;
5122
+ case 'fontSize':
5123
+ if (!options.plugins.fontSize) continue;
5124
+ break;
5125
+ case 'color':
5126
+ if (!options.plugins.fontColor) continue;
5127
+ break;
5128
+ case 'backgroundColor':
5129
+ if (!options.plugins.hiliteColor) continue;
5130
+ break;
5131
+ }
5132
+
5133
+ if (v !== c) {
5134
+ allowedStyle.push(r[0]);
5135
+ }
5136
+ }
5137
+ }
5138
+ if (allowedStyle.length > 0) v.push('style="' + allowedStyle.join(';') + '"');
5139
+ }
5140
+ }
5141
+
5142
+ return v;
5143
+ },
5144
+
5089
5145
  /**
5090
5146
  * @description Tag and tag attribute check RegExp function. (used by "cleanHTML" and "convertContentsForEditor")
5091
5147
  * @param {Boolean} lowLevelCheck Row level check
@@ -5102,34 +5158,38 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5102
5158
 
5103
5159
  // blacklist
5104
5160
  const bAttr = this._attributesTagsBlacklist[tagName];
5161
+ m = m.replace(/\s(?:on[a-z]+)\s*=\s*(")[^"]*\1/ig, '');
5105
5162
  if (bAttr) m = m.replace(bAttr, '');
5106
5163
  else m = m.replace(this._attributesBlacklistRegExp, '');
5107
5164
 
5108
5165
  // whitelist
5109
5166
  const wAttr = this._attributesTagsWhitelist[tagName];
5110
5167
  if (wAttr) v = m.match(wAttr);
5111
- else v = m.match(this._attributesWhitelistRegExp);
5168
+ else v = m.match(lowLevelCheck ? this._attributesWhitelistRegExp : this._attributesWhitelistRegExp_all_data);
5112
5169
 
5113
- // anchor
5114
- if (!lowLevelCheck || /<a\b/i.test(t)) {
5115
- const sv = m.match(/(?:(?:id|name)\s*=\s*(?:"|')[^"']*(?:"|'))/g);
5116
- if (sv) {
5117
- if (!v) v = [];
5118
- v.push(sv[0]);
5170
+ // attribute
5171
+ if (lowLevelCheck) {
5172
+ if (tagName === 'a') {
5173
+ const sv = m.match(/(?:(?:id|name)\s*=\s*(?:"|')[^"']*(?:"|'))/g);
5174
+ if (sv) {
5175
+ if (!v) v = [];
5176
+ v.push(sv[0]);
5177
+ }
5178
+ } else if (!v || !/style=/i.test(v.toString())) {
5179
+ if (tagName === 'span') {
5180
+ v = this._cleanStyle(m, v, 'span');
5181
+ } else if (/^(P|DIV|H[1-6]|PRE)$/i.test(tagName)) {
5182
+ v = this._cleanStyle(m, v, 'format');
5183
+ }
5119
5184
  }
5120
- }
5121
-
5122
- // span
5123
- if ((!lowLevelCheck || /<span/i.test(t)) && (!v || !/style=/i.test(v.toString()))) {
5185
+ } else {
5124
5186
  const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
5125
- if (sv) {
5126
- if (!v) v = [];
5127
- v.push(sv[0]);
5128
- }
5187
+ if (sv && !v) v = [sv[0]];
5188
+ else if (sv && !v.some(function (v) { return /^style/.test(v.trim()); })) v.push(sv[0]);
5129
5189
  }
5130
5190
 
5131
5191
  // img
5132
- if (/<img/i.test(t)) {
5192
+ if (tagName === 'img') {
5133
5193
  let w = '', h = '';
5134
5194
  const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
5135
5195
  if (!v) v = [];
@@ -5153,14 +5213,82 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5153
5213
 
5154
5214
  if (v) {
5155
5215
  for (let i = 0, len = v.length; i < len; i++) {
5156
- if (lowLevelCheck && /^class="(?!(__se__|se-|katex))/.test(v[i])) continue;
5157
- t += ' ' + (/^(?:href|src)\s*=\s*('|"|\s)*javascript\s*\:/i.test(v[i]) ? '' : v[i]);
5216
+ if (lowLevelCheck && /^class="(?!(__se__|se-|katex))/.test(v[i].trim())) continue;
5217
+ t += ' ' + (/^(?:href|src)\s*=\s*('|"|\s)*javascript\s*\:/i.test(v[i].trim()) ? '' : v[i]);
5158
5218
  }
5159
5219
  }
5160
5220
 
5161
5221
  return t;
5162
5222
  },
5163
5223
 
5224
+ /**
5225
+ * @description Determines if formatting is required and returns a domTree
5226
+ * @param {Element} dom documentFragment
5227
+ * @returns {Element}
5228
+ * @private
5229
+ */
5230
+ _editFormat: function (dom) {
5231
+ let value = '', f;
5232
+ const tempTree = dom.childNodes;
5233
+ for (let i = 0, len = tempTree.length, n; i < len; i++) {
5234
+ n = tempTree[i];
5235
+ if (!util.isFormatElement(n) && !util.isRangeFormatElement(n) && !util.isComponent(n) && !/meta/i.test(n.nodeName)) {
5236
+ if (!f) f = util.createElement(options.defaultTag);
5237
+ f.appendChild(n);
5238
+ i--; len--;
5239
+ } else {
5240
+ if (f) {
5241
+ value += f.outerHTML;
5242
+ f = null;
5243
+ }
5244
+ value += n.outerHTML;
5245
+ }
5246
+ }
5247
+
5248
+ if (f) value += f.outerHTML;
5249
+
5250
+ return _d.createRange().createContextualFragment(value);
5251
+ },
5252
+
5253
+ _convertListCell: function (domTree) {
5254
+ let html = '';
5255
+
5256
+ for (let i = 0, len = domTree.length, node; i < len; i++) {
5257
+ node = domTree[i];
5258
+ if (node.nodeType === 1) {
5259
+ if (util.isList(node)) {
5260
+ html += node.innerHTML;
5261
+ } else if (util.isListCell(node)) {
5262
+ html += node.outerHTML;
5263
+ } else if (util.isFormatElement(node)) {
5264
+ html += '<li>' +(node.innerHTML.trim() || '<br>') + '</li>';
5265
+ } else if (util.isRangeFormatElement(node) && !util.isTable(node)) {
5266
+ html += this._convertListCell(node);
5267
+ } else {
5268
+ html += '<li>' + node.outerHTML + '</li>';
5269
+ }
5270
+ } else {
5271
+ html += '<li>' + (node.textContent || '<br>') + '</li>';
5272
+ }
5273
+ }
5274
+
5275
+ return html;
5276
+ },
5277
+
5278
+ _isFormatData: function (domTree) {
5279
+ let requireFormat = false;
5280
+
5281
+ for (let i = 0, len = domTree.length, t; i < len; i++) {
5282
+ t = domTree[i];
5283
+ if (t.nodeType === 1 && !util.isTextStyleElement(t) && !util.isBreak(t) && !util._disallowedTags(t)) {
5284
+ requireFormat = true;
5285
+ break;
5286
+ }
5287
+ }
5288
+
5289
+ return requireFormat;
5290
+ },
5291
+
5164
5292
  /**
5165
5293
  * @description Gets the clean HTML code for editor
5166
5294
  * @param {String} html HTML string
@@ -5194,16 +5322,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5194
5322
  }
5195
5323
  }
5196
5324
 
5197
- const domTree = dom.childNodes;
5325
+ let domTree = dom.childNodes;
5198
5326
  let cleanHTML = '';
5199
- let requireFormat = false;
5327
+ const requireFormat = this._isFormatData(domTree);
5200
5328
 
5201
- for (let i = 0, len = domTree.length, t; i < len; i++) {
5202
- t = domTree[i];
5203
- if (t.nodeType === 1 && !util.isTextStyleElement(t) && !util.isBreak(t) && !util._disallowedTags(t)) {
5204
- requireFormat = true;
5205
- break;
5206
- }
5329
+ if(requireFormat) {
5330
+ domTree = this._editFormat(dom).childNodes;
5207
5331
  }
5208
5332
 
5209
5333
  for (let i = 0, len = domTree.length; i < len; i++) {
@@ -5566,6 +5690,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5566
5690
  this._charTypeHTML = options.charCounterType === 'byte-html';
5567
5691
  this.wwComputedStyle = _w.getComputedStyle(context.element.wysiwyg);
5568
5692
  this._editorHeight = context.element.wysiwygFrame.offsetHeight;
5693
+ this._editorHeightPadding = util.getNumber(this.wwComputedStyle.getPropertyValue('padding-top')) + util.getNumber(this.wwComputedStyle.getPropertyValue('padding-bottom'));
5569
5694
 
5570
5695
  if (!options.iframe && typeof _w.ShadowRoot === 'function') {
5571
5696
  let child = context.element.wysiwygFrame;
@@ -5593,7 +5718,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5593
5718
  // set whitelist
5594
5719
  const getRegList = function (str, str2) { return !str ? '^' : (str === '*' ? '[a-z-]+' : (!str2 ? str : (str + '|' + str2))); };
5595
5720
  // tags
5596
- const defaultAttr = 'contenteditable|colspan|rowspan|target|href|download|rel|src|alt|class|type|controls|data-format|data-size|data-file-size|data-file-name|data-origin|data-align|data-image-link|data-rotate|data-proportion|data-percentage|origin-size|data-exp|data-font-size';
5721
+ const defaultAttr = 'contenteditable|colspan|rowspan|target|href|download|rel|src|alt|class|type|controls|origin-size';
5722
+ const dataAttr = 'data-format|data-size|data-file-size|data-file-name|data-origin|data-align|data-image-link|data-rotate|data-proportion|data-percentage|data-exp|data-font-size';
5597
5723
  this._allowHTMLComments = options._editorTagsWhitelist.indexOf('//') > -1 || options._editorTagsWhitelist === '*';
5598
5724
  // html check
5599
5725
  this._htmlCheckWhitelistRegExp = new wRegExp('^(' + getRegList(options._editorTagsWhitelist.replace('|//', ''), '') + ')$', 'i');
@@ -5620,7 +5746,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5620
5746
  }
5621
5747
  }
5622
5748
 
5623
- this._attributesWhitelistRegExp = new wRegExp('\\s(?:' + (allAttr || defaultAttr) + ')' + regEndStr, 'ig');
5749
+ this._attributesWhitelistRegExp = new wRegExp('\\s(?:' + (allAttr || defaultAttr + '|' + dataAttr) + ')' + regEndStr, 'ig');
5750
+ this._attributesWhitelistRegExp_all_data = new wRegExp('\\s(?:' + ((allAttr || defaultAttr) + '|data-[a-z0-9\\-]+') + ')' + regEndStr, 'ig');
5624
5751
  this._attributesTagsWhitelist = tagsAttr;
5625
5752
 
5626
5753
  // blacklist
@@ -5799,7 +5926,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5799
5926
  },
5800
5927
 
5801
5928
  __callResizeFunction: function (h, resizeObserverEntry) {
5802
- h = h === -1 ? resizeObserverEntry.borderBoxSize[0].blockSize : h;
5929
+ h = h === -1 ? (resizeObserverEntry.borderBoxSize ? resizeObserverEntry.borderBoxSize[0].blockSize : (resizeObserverEntry.contentRect.height + this._editorHeightPadding)) : h;
5803
5930
  if (this._editorHeight !== h) {
5804
5931
  if (typeof functions.onResizeEditor === 'function') functions.onResizeEditor(h, this._editorHeight, core, resizeObserverEntry);
5805
5932
  this._editorHeight = h;
@@ -7691,39 +7818,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7691
7818
  }
7692
7819
 
7693
7820
  if (cleanData) {
7694
- if (util.isListCell(util.getFormatElement(core.getSelectionNode(), null))) {
7695
- const dom = (_d.createRange().createContextualFragment(cleanData));
7696
- const domTree = dom.childNodes;
7697
- if (domTree.length > 1 && domTree[0].nodeType === 1) cleanData = event._convertListCell(domTree);
7698
- }
7699
7821
  functions.insertHTML(cleanData, true, false);
7700
7822
  return false;
7701
7823
  }
7702
7824
  },
7703
7825
 
7704
- _convertListCell: function (domTree) {
7705
- let html = '';
7706
-
7707
- for (let i = 0, len = domTree.length, node; i < len; i++) {
7708
- node = domTree[i];
7709
- if (node.nodeType === 1) {
7710
- if (util.isListCell(node) || util.isList(node)) {
7711
- html += node.outerHTML;
7712
- } else if (util.isFormatElement(node)) {
7713
- html += '<li>' +(node.innerHTML.trim() || '<br>') + '</li>';
7714
- } else if (util.isRangeFormatElement(node) && !util.isTable(node)) {
7715
- html += event._convertListCell(node);
7716
- } else {
7717
- html += '<li>' + node.outerHTML + '</li>';
7718
- }
7719
- } else {
7720
- html += '<li>' + (node.textContent || '<br>') + '</li>';
7721
- }
7722
- }
7723
-
7724
- return html;
7725
- },
7726
-
7727
7826
  onMouseMove_wysiwyg: function (e) {
7728
7827
  if (core.isDisabled || core.isReadOnly) return false;
7729
7828
  const component = util.getParentElement(e.target, util.isComponent);
@@ -8439,10 +8538,16 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
8439
8538
  */
8440
8539
  insertHTML: function (html, notCleaningData, checkCharCount, rangeSelection) {
8441
8540
  if (!context.element.wysiwygFrame.contains(core.getSelection().focusNode)) core.focus();
8442
-
8541
+
8443
8542
  if (typeof html === 'string') {
8444
8543
  if (!notCleaningData) html = core.cleanHTML(html, null, null);
8445
8544
  try {
8545
+ if (util.isListCell(util.getFormatElement(core.getSelectionNode(), null))) {
8546
+ const dom = _d.createRange().createContextualFragment(html);
8547
+ const domTree = dom.childNodes;
8548
+ if (core._isFormatData(domTree)) html = core._convertListCell(domTree);
8549
+ }
8550
+
8446
8551
  const dom = _d.createRange().createContextualFragment(html);
8447
8552
  const domTree = dom.childNodes;
8448
8553
 
package/src/lib/util.d.ts CHANGED
@@ -42,6 +42,12 @@ declare interface util {
42
42
  */
43
43
  camelToKebabCase(param: string | string[]): string | string[],
44
44
 
45
+ /**
46
+ * @description Convert the KebabCase To the CamelCase.
47
+ * @param {String|Array} param [KebabCase string]
48
+ */
49
+ kebabToCamelCase(param: string | string[]): string | string[],
50
+
45
51
  /**
46
52
  * @description Create Element node
47
53
  * @param elementName Element name
@@ -351,6 +357,13 @@ declare interface util {
351
357
  */
352
358
  isEmptyLine(element: Element): boolean;
353
359
 
360
+ /**
361
+ * @description Check the span's attributes are empty.
362
+ * @param {Element} element Element node
363
+ * @returns {Boolean}
364
+ */
365
+ isSpanWithoutAttr(element: Element|null): boolean;
366
+
354
367
  /**
355
368
  * @description Check the node is a list (ol, ul)
356
369
  * @param node The element or element name to check
package/src/lib/util.js CHANGED
@@ -119,6 +119,19 @@ const util = {
119
119
  }
120
120
  },
121
121
 
122
+ /**
123
+ * @description Convert the KebabCase To the CamelCase.
124
+ * @param {String|Array} param [KebabCase string]
125
+ * @returns {String|Array}
126
+ */
127
+ kebabToCamelCase: function (param) {
128
+ if (typeof param === 'string') {
129
+ return param.replace(/-[a-zA-Z]/g, function (letter) { return letter.replace('-', '').toUpperCase(); });
130
+ } else {
131
+ return param.map(function(str) { return util.camelToKebabCase(str); });
132
+ }
133
+ },
134
+
122
135
  /**
123
136
  * @description Create Element node
124
137
  * @param {String} elementName Element name
@@ -763,6 +776,15 @@ const util = {
763
776
  return !element || !element.parentNode || (!element.querySelector('IMG, IFRAME, AUDIO, VIDEO, CANVAS, TABLE') && this.onlyZeroWidthSpace(element.textContent));
764
777
  },
765
778
 
779
+ /**
780
+ * @description Check the span's attributes are empty.
781
+ * @param {Element|null} element Element node
782
+ * @returns {Boolean}
783
+ */
784
+ isSpanWithoutAttr: function (element) {
785
+ return !!element && element.nodeType === 1 && /^SPAN$/i.test(element.nodeName) && !element.className && !element.style.cssText;
786
+ },
787
+
766
788
  /**
767
789
  * @description Check the node is a list (ol, ul)
768
790
  * @param {Node|String} node The element or element name to check
@@ -1375,12 +1397,33 @@ const util = {
1375
1397
  * @description Split all tags based on "baseNode"
1376
1398
  * Returns the last element of the splited tag.
1377
1399
  * @param {Node} baseNode Element or text node on which to base
1378
- * @param {Number|null} offset Text offset of "baseNode" (Only valid when "baseNode" is a text node)
1400
+ * @param {Number|Node|null} offset Text offset of "baseNode" (Only valid when "baseNode" is a text node)
1379
1401
  * @param {Number} depth The nesting depth of the element being split. (default: 0)
1380
1402
  * @returns {Element}
1381
1403
  */
1382
1404
  splitElement: function (baseNode, offset, depth) {
1383
1405
  if (this.isWysiwygDiv(baseNode)) return baseNode;
1406
+
1407
+ if (!!offset && !this.isNumber(offset)) {
1408
+ const children = baseNode.childNodes;
1409
+ let index = this.getPositionIndex(offset);
1410
+ const prev = baseNode.cloneNode(false);
1411
+ const next = baseNode.cloneNode(false);
1412
+ for (let i = 0, len = children.length; i < len; i++) {
1413
+ if (i < index) prev.appendChild(children[i]);
1414
+ else if (i > index) next.appendChild(children[i]);
1415
+ else continue;
1416
+ i--;
1417
+ len--;
1418
+ index--;
1419
+ }
1420
+
1421
+ if (prev.childNodes.length > 0) baseNode.parentNode.insertBefore(prev, baseNode);
1422
+ if (next.childNodes.length > 0) baseNode.parentNode.insertBefore(next, baseNode.nextElementSibling);
1423
+
1424
+ return baseNode;
1425
+ }
1426
+
1384
1427
  const bp = baseNode.parentNode;
1385
1428
  let index = 0, newEl, children, temp;
1386
1429
  let next = true;
@@ -454,7 +454,7 @@ export default {
454
454
  this.plugins.audio._setTagAttrs.call(this, element);
455
455
 
456
456
  // find component element
457
- const existElement = (this.util.isRangeFormatElement(element.parentNode) || this.util.isWysiwygDiv(element.parentNode)) ?
457
+ let existElement = (this.util.isRangeFormatElement(element.parentNode) || this.util.isWysiwygDiv(element.parentNode)) ?
458
458
  element : this.util.getFormatElement(element) || element;
459
459
 
460
460
  // clone element
@@ -464,12 +464,16 @@ export default {
464
464
  const container = this.plugins.component.set_container.call(this, cover, 'se-audio-container');
465
465
 
466
466
  try {
467
- if (this.util.isListCell(existElement) || this.util.isFormatElement(existElement)) {
468
- prevElement.parentNode.replaceChild(container, prevElement);
469
- } else if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
467
+ if (this.util.isListCell(existElement)) {
468
+ const refer = this.util.getParentElement(prevElement, function (current) { return current.parentNode === existElement; });
469
+ existElement.insertBefore(container, refer);
470
+ this.util.removeItem(prevElement);
471
+ this.util.removeEmptyNode(refer, null);
472
+ } else if (this.util.isFormatElement(existElement)) {
473
+ const refer = this.util.getParentElement(prevElement, function (current) { return current.parentNode === existElement; });
474
+ existElement = this.util.splitElement(existElement, refer);
470
475
  existElement.parentNode.insertBefore(container, existElement);
471
476
  this.util.removeItem(prevElement);
472
- // clean format tag
473
477
  this.util.removeEmptyNode(existElement, null);
474
478
  if (existElement.children.length === 0) existElement.innerHTML = this.util.htmlRemoveWhiteSpace(existElement.innerHTML);
475
479
  } else {
@@ -768,16 +768,20 @@ export default {
768
768
  }
769
769
 
770
770
  if (isNewContainer) {
771
- const existElement = (this.util.isRangeFormatElement(contextImage._element.parentNode) || this.util.isWysiwygDiv(contextImage._element.parentNode)) ?
771
+ let existElement = (this.util.isRangeFormatElement(contextImage._element.parentNode) || this.util.isWysiwygDiv(contextImage._element.parentNode)) ?
772
772
  contextImage._element :
773
773
  /^A$/i.test(contextImage._element.parentNode.nodeName) ? contextImage._element.parentNode : this.util.getFormatElement(contextImage._element) || contextImage._element;
774
774
 
775
- if (this.util.isListCell(existElement) || this.util.isFormatElement(existElement)) {
776
- contextImage._element.parentNode.replaceChild(container, contextImage._element);
777
- } else if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
775
+ if (this.util.isListCell(existElement)) {
776
+ const refer = this.util.getParentElement(contextImage._element, function (current) { return current.parentNode === existElement; });
777
+ existElement.insertBefore(container, refer);
778
+ this.util.removeItem(contextImage._element);
779
+ this.util.removeEmptyNode(refer, null);
780
+ } else if (this.util.isFormatElement(existElement)) {
781
+ const refer = this.util.getParentElement(contextImage._element, function (current) { return current.parentNode === existElement; });
782
+ existElement = this.util.splitElement(existElement, refer);
778
783
  existElement.parentNode.insertBefore(container, existElement);
779
784
  this.util.removeItem(contextImage._element);
780
- // clean format tag
781
785
  this.util.removeEmptyNode(existElement, null);
782
786
  if (existElement.children.length === 0) existElement.innerHTML = this.util.htmlRemoveWhiteSpace(existElement.innerHTML);
783
787
  } else {
@@ -645,7 +645,7 @@ export default {
645
645
  if (/^video$/i.test(oFrame.nodeName)) this.plugins.video._setTagAttrs.call(this, oFrame);
646
646
  else this.plugins.video._setIframeAttrs.call(this, oFrame);
647
647
 
648
- const existElement = (this.util.isRangeFormatElement(oFrame.parentNode) || this.util.isWysiwygDiv(oFrame.parentNode)) ?
648
+ let existElement = (this.util.isRangeFormatElement(oFrame.parentNode) || this.util.isWysiwygDiv(oFrame.parentNode)) ?
649
649
  oFrame : this.util.getFormatElement(oFrame) || oFrame;
650
650
 
651
651
  const prevFrame = oFrame;
@@ -671,12 +671,16 @@ export default {
671
671
  if (format) contextVideo._align = format.style.textAlign || format.style.float;
672
672
  this.plugins.video.setAlign.call(this, null, oFrame, cover, container);
673
673
 
674
- if (this.util.isListCell(existElement) || this.util.isFormatElement(existElement)) {
675
- prevFrame.parentNode.replaceChild(container, prevFrame);
676
- } else if (this.util.isFormatElement(existElement) && existElement.childNodes.length > 0) {
674
+ if (this.util.isListCell(existElement)) {
675
+ const refer = this.util.getParentElement(prevFrame, function (current) { return current.parentNode === existElement; });
676
+ existElement.insertBefore(container, refer);
677
+ this.util.removeItem(prevFrame);
678
+ this.util.removeEmptyNode(refer, null);
679
+ } else if (this.util.isFormatElement(existElement)) {
680
+ const refer = this.util.getParentElement(prevFrame, function (current) { return current.parentNode === existElement; });
681
+ existElement = this.util.splitElement(existElement, refer);
677
682
  existElement.parentNode.insertBefore(container, existElement);
678
683
  this.util.removeItem(prevFrame);
679
- // clean format tag
680
684
  this.util.removeEmptyNode(existElement, null);
681
685
  if (existElement.children.length === 0) existElement.innerHTML = this.util.htmlRemoveWhiteSpace(existElement.innerHTML);
682
686
  } else {