suneditor 2.41.2 → 2.43.0

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.
Files changed (50) hide show
  1. package/README.md +116 -28
  2. package/dist/css/suneditor.min.css +1 -1
  3. package/dist/suneditor.min.js +2 -2
  4. package/package.json +4 -4
  5. package/src/assets/css/suneditor-contents.css +1 -1
  6. package/src/assets/css/suneditor.css +27 -7
  7. package/src/assets/defaultIcons.js +2 -0
  8. package/src/lang/Lang.d.ts +3 -1
  9. package/src/lang/ckb.js +2 -0
  10. package/src/lang/da.js +2 -0
  11. package/src/lang/de.js +2 -0
  12. package/src/lang/en.js +2 -0
  13. package/src/lang/es.js +2 -0
  14. package/src/lang/fr.js +10 -8
  15. package/src/lang/he.js +2 -0
  16. package/src/lang/it.js +2 -0
  17. package/src/lang/ja.js +2 -0
  18. package/src/lang/ko.js +2 -0
  19. package/src/lang/lv.js +2 -0
  20. package/src/lang/nl.js +2 -0
  21. package/src/lang/pl.js +2 -0
  22. package/src/lang/pt_br.js +2 -0
  23. package/src/lang/ro.js +2 -0
  24. package/src/lang/ru.js +2 -0
  25. package/src/lang/se.js +2 -0
  26. package/src/lang/ua.js +2 -0
  27. package/src/lang/zh_cn.js +3 -1
  28. package/src/lib/constructor.js +68 -16
  29. package/src/lib/context.js +5 -1
  30. package/src/lib/core.d.ts +88 -12
  31. package/src/lib/core.js +789 -212
  32. package/src/lib/util.d.ts +24 -1
  33. package/src/lib/util.js +105 -18
  34. package/src/options.d.ts +79 -9
  35. package/src/plugins/dialog/audio.js +5 -5
  36. package/src/plugins/dialog/image.js +30 -20
  37. package/src/plugins/dialog/link.js +1 -0
  38. package/src/plugins/dialog/video.js +16 -15
  39. package/src/plugins/fileBrowser/imageGallery.js +2 -3
  40. package/src/plugins/modules/_anchor.js +24 -15
  41. package/src/plugins/modules/component.d.ts +1 -1
  42. package/src/plugins/modules/fileBrowser.js +6 -1
  43. package/src/plugins/modules/fileManager.js +1 -3
  44. package/src/plugins/modules/resizing.js +11 -6
  45. package/src/plugins/submenu/align.js +32 -27
  46. package/src/plugins/submenu/font.js +1 -1
  47. package/src/plugins/submenu/fontSize.js +1 -1
  48. package/src/plugins/submenu/horizontalRule.js +19 -25
  49. package/src/plugins/submenu/list.js +13 -5
  50. package/src/plugins/submenu/template.js +5 -2
package/src/lib/core.js CHANGED
@@ -39,6 +39,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
39
39
  _d: _d,
40
40
  _w: _w,
41
41
  _parser: new _w.DOMParser(),
42
+ _prevRtl: options.rtl,
43
+ _editorHeight: 0,
44
+ _listCamel: options.__listCommonStyle,
45
+ _listKebab: util.camelToKebabCase(options.__listCommonStyle),
42
46
 
43
47
  /**
44
48
  * @description Document object of the iframe if created as an iframe || _d
@@ -82,7 +86,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
82
86
  /**
83
87
  * @description Computed style of the wysiwyg area (window.getComputedStyle(context.element.wysiwyg))
84
88
  */
85
- wwComputedStyle: _w.getComputedStyle(context.element.wysiwyg),
89
+ wwComputedStyle: null,
86
90
 
87
91
  /**
88
92
  * @description Notice object
@@ -222,6 +226,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
222
226
  */
223
227
  _htmlCheckWhitelistRegExp: null,
224
228
 
229
+ /**
230
+ * @description Tag blacklist RegExp object used in "_consistencyCheckOfHTML" method
231
+ * @private
232
+ */
233
+ _htmlCheckBlacklistRegExp: null,
234
+
225
235
  /**
226
236
  * @description RegExp when using check disallowd tags. (b, i, ins, strike, s)
227
237
  * @private
@@ -234,12 +244,24 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
234
244
  */
235
245
  editorTagsWhitelistRegExp: null,
236
246
 
247
+ /**
248
+ * @description Editor tags blacklist (RegExp object)
249
+ * util.createTagsBlacklist(options.tagsBlacklist)
250
+ */
251
+ editorTagsBlacklistRegExp: null,
252
+
237
253
  /**
238
254
  * @description Tag whitelist when pasting (RegExp object)
239
255
  * util.createTagsWhitelist(options.pasteTagsWhitelist)
240
256
  */
241
257
  pasteTagsWhitelistRegExp: null,
242
258
 
259
+ /**
260
+ * @description Tag blacklist when pasting (RegExp object)
261
+ * util.createTagsBlacklist(options.pasteTagsBlacklist)
262
+ */
263
+ pasteTagsBlacklistRegExp: null,
264
+
243
265
  /**
244
266
  * @description Boolean value of whether the editor has focus
245
267
  */
@@ -261,12 +283,24 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
261
283
  */
262
284
  _attributesWhitelistRegExp: null,
263
285
 
286
+ /**
287
+ * @description Attributes blacklist used by the cleanHTML method
288
+ * @private
289
+ */
290
+ _attributesBlacklistRegExp: null,
291
+
264
292
  /**
265
293
  * @description Attributes of tags whitelist used by the cleanHTML method
266
294
  * @private
267
295
  */
268
296
  _attributesTagsWhitelist: null,
269
297
 
298
+ /**
299
+ * @description Attributes of tags blacklist used by the cleanHTML method
300
+ * @private
301
+ */
302
+ _attributesTagsBlacklist: null,
303
+
270
304
  /**
271
305
  * @description binded controllersOff method
272
306
  * @private
@@ -414,6 +448,24 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
414
448
  */
415
449
  commandMap: null,
416
450
 
451
+ /**
452
+ * @description CSS properties related to style tags
453
+ * @private
454
+ */
455
+ _commandMapStyles: {
456
+ STRONG: ['font-weight'],
457
+ U: ['text-decoration'],
458
+ EM: ['font-style'],
459
+ DEL: ['text-decoration']
460
+ },
461
+
462
+ /**
463
+ * @description Contains pairs of all "data-commands" and "elements" setted in toolbar over time
464
+ * Used primarily to save and recover button states after the toolbar re-creation
465
+ * Updates each "_cachingButtons()" invocation
466
+ */
467
+ allCommandButtons: null,
468
+
417
469
  /**
418
470
  * @description Style button related to edit area
419
471
  * @property {Element} fullScreen fullScreen button element
@@ -471,6 +523,40 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
471
523
  _lineBreakDir: ''
472
524
  },
473
525
 
526
+ /**
527
+ * @description Save the current buttons states to "allCommandButtons" object
528
+ */
529
+ saveButtonStates: function () {
530
+ if (!this.allCommandButtons) this.allCommandButtons = {};
531
+
532
+ const currentButtons = this.context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]');
533
+ for (let i = 0, element, command; i < currentButtons.length; i++) {
534
+ element = currentButtons[i];
535
+ command = element.getAttribute('data-command');
536
+
537
+ this.allCommandButtons[command] = element;
538
+ }
539
+ },
540
+
541
+ /**
542
+ * @description Recover the current buttons states from "allCommandButtons" object
543
+ */
544
+ recoverButtonStates: function () {
545
+ if (this.allCommandButtons) {
546
+ const currentButtons = this.context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]');
547
+ for (let i = 0, button, command, oldButton; i < currentButtons.length; i++) {
548
+ button = currentButtons[i];
549
+ command = button.getAttribute('data-command');
550
+
551
+ oldButton = this.allCommandButtons[command];
552
+ if (oldButton) {
553
+ button.parentElement.replaceChild(oldButton, button);
554
+ if (this.context.tool[command]) this.context.tool[command] = oldButton;
555
+ }
556
+ }
557
+ }
558
+ },
559
+
474
560
  /**
475
561
  * @description If the plugin is not added, add the plugin and call the 'add' function.
476
562
  * If the plugin is added call callBack function.
@@ -559,7 +645,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
559
645
  },
560
646
 
561
647
  /**
562
- * @description Enabled submenu
648
+ * @description Enable submenu
563
649
  * @param {Element} element Submenu's button element to call
564
650
  */
565
651
  submenuOn: function (element) {
@@ -598,7 +684,19 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
598
684
  },
599
685
 
600
686
  /**
601
- * @description Enabled container
687
+ * @description Disable more layer
688
+ */
689
+ moreLayerOff: function() {
690
+ if (this._moreLayerActiveButton) {
691
+ const layer = context.element.toolbar.querySelector('.' + this._moreLayerActiveButton.getAttribute('data-command'));
692
+ layer.style.display = 'none';
693
+ util.removeClass(this._moreLayerActiveButton, 'on');
694
+ this._moreLayerActiveButton = null;
695
+ }
696
+ },
697
+
698
+ /**
699
+ * @description Enable container
602
700
  * @param {Element} element Container's button element to call
603
701
  */
604
702
  containerOn: function (element) {
@@ -868,14 +966,21 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
868
966
  * @description Focus to wysiwyg area using "native focus function"
869
967
  */
870
968
  nativeFocus: function () {
969
+ this.__focus();
970
+ this._editorRange();
971
+ },
972
+
973
+ /**
974
+ * @description Focus method
975
+ * @private
976
+ */
977
+ __focus: function () {
871
978
  const caption = util.getParentElement(this.getSelectionNode(), 'figcaption');
872
979
  if (caption) {
873
980
  caption.focus();
874
981
  } else {
875
982
  context.element.wysiwyg.focus();
876
983
  }
877
-
878
- this._editorRange();
879
984
  },
880
985
 
881
986
  /**
@@ -954,12 +1059,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
954
1059
  if (startOff > startCon.textContent.length) startOff = startCon.textContent.length;
955
1060
  if (endOff > endCon.textContent.length) endOff = endCon.textContent.length;
956
1061
  if (util.isFormatElement(startCon)) {
957
- startCon = startCon.childNodes[startOff] || startCon;
958
- startOff = 0;
1062
+ startCon = startCon.childNodes[startOff] || startCon.childNodes[startOff - 1] || startCon;
1063
+ startOff = startOff > 0 ? startCon.nodeType === 1 ? 1 : startCon.textContent ? startCon.textContent.length : 0 : 0;
959
1064
  }
960
1065
  if (util.isFormatElement(endCon)) {
961
- endCon = endCon.childNodes[endOff] || endCon;
962
- endOff = startOff > 1 ? startOff : 0;
1066
+ endCon = endCon.childNodes[endOff] || endCon.childNodes[endOff - 1] || endCon;
1067
+ endOff = endOff > 0 ? endCon.nodeType === 1 ? 1 : endCon.textContent ? endCon.textContent.length : 0 : 0;
963
1068
  }
964
1069
 
965
1070
  const range = this._wd.createRange();
@@ -980,8 +1085,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
980
1085
  }
981
1086
 
982
1087
  selection.addRange(range);
983
- this._editorRange();
984
- if (options.iframe) this.nativeFocus();
1088
+ this._rangeInfo(range, this.getSelection());
1089
+ if (options.iframe) this.__focus();
985
1090
 
986
1091
  return range;
987
1092
  },
@@ -1074,7 +1179,6 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1074
1179
  const selection = this.getSelection();
1075
1180
  if (!selection) return null;
1076
1181
  let range = null;
1077
- let selectionNode = null;
1078
1182
 
1079
1183
  if (selection.rangeCount > 0) {
1080
1184
  range = selection.getRangeAt(0);
@@ -1082,6 +1186,20 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1082
1186
  range = this._createDefaultRange();
1083
1187
  }
1084
1188
 
1189
+ if (util.isFormatElement(range.endContainer) && range.endOffset === 0) {
1190
+ range = this.setRange(range.startContainer, range.startOffset, range.startContainer, range.startContainer.length);
1191
+ }
1192
+
1193
+ this._rangeInfo(range, selection);
1194
+ },
1195
+
1196
+ /**
1197
+ * @description Set "range" and "selection" info.
1198
+ * @param {Object} range range object.
1199
+ * @param {Object} selection selection object.
1200
+ */
1201
+ _rangeInfo: function (range, selection) {
1202
+ let selectionNode = null;
1085
1203
  this._variable._range = range;
1086
1204
 
1087
1205
  if (range.collapsed) {
@@ -1145,19 +1263,19 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1145
1263
 
1146
1264
  if (util.isFormatElement(startCon)) {
1147
1265
  if (!startCon.childNodes[startOff]) {
1148
- startCon = startCon.lastChild;
1266
+ startCon = startCon.lastChild || startCon;
1149
1267
  startOff = startCon.textContent.length;
1150
1268
  } else {
1151
- startCon = startCon.childNodes[startOff];
1269
+ startCon = startCon.childNodes[startOff] || startCon;
1152
1270
  startOff = 0;
1153
1271
  }
1154
1272
  while (startCon && startCon.nodeType === 1 && startCon.firstChild) {
1155
- startCon = startCon.firstChild;
1273
+ startCon = startCon.firstChild || startCon;
1156
1274
  startOff = 0;
1157
1275
  }
1158
1276
  }
1159
1277
  if (util.isFormatElement(endCon)) {
1160
- endCon = endCon.childNodes[endOff] || endCon.lastChild;
1278
+ endCon = endCon.childNodes[endOff] || endCon.lastChild || endCon;
1161
1279
  while (endCon && endCon.nodeType === 1 && endCon.lastChild) {
1162
1280
  endCon = endCon.lastChild;
1163
1281
  }
@@ -1255,7 +1373,6 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1255
1373
  if (util.isWysiwygDiv(range.startContainer)) {
1256
1374
  const children = context.element.wysiwyg.children;
1257
1375
  if (children.length === 0) return [];
1258
-
1259
1376
  this.setRange(children[0], 0, children[children.length - 1], children[children.length - 1].textContent.trim().length);
1260
1377
  range = this.getRange();
1261
1378
  }
@@ -1397,7 +1514,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1397
1514
  * @returns {Element}
1398
1515
  */
1399
1516
  appendFormatTag: function (element, formatNode) {
1400
- if (!element.parentNode) return null;
1517
+ if (!element || !element.parentNode) return null;
1401
1518
 
1402
1519
  const currentFormatEl = util.getFormatElement(this.getSelectionNode(), null);
1403
1520
  let oFormat = null;
@@ -1451,9 +1568,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1451
1568
  if (formatEl && util.onlyZeroWidthSpace(formatEl)) util.removeItem(formatEl);
1452
1569
  }
1453
1570
 
1454
- this.setRange(element, 0, element, 0);
1455
-
1456
1571
  if (!notSelect) {
1572
+ this.setRange(element, 0, element, 0);
1573
+
1457
1574
  const fileComponentInfo = this.getFileComponent(element);
1458
1575
  if (fileComponentInfo) {
1459
1576
  this.selectComponent(fileComponentInfo.target, fileComponentInfo.pluginName);
@@ -1592,8 +1709,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1592
1709
  const startOff = range.startOffset;
1593
1710
  const endOff = range.endOffset;
1594
1711
  const formatRange = range.startContainer === commonCon && util.isFormatElement(commonCon);
1595
- const startCon = formatRange ? (commonCon.childNodes[startOff] || commonCon.childNodes[0]) : range.startContainer;
1596
- const endCon = formatRange ? (commonCon.childNodes[endOff] || commonCon.childNodes[commonCon.childNodes.length - 1]) : range.endContainer;
1712
+ const startCon = formatRange ? (commonCon.childNodes[startOff] || commonCon.childNodes[0] || range.startContainer) : range.startContainer;
1713
+ const endCon = formatRange ? (commonCon.childNodes[endOff] || commonCon.childNodes[commonCon.childNodes.length - 1] || range.endContainer) : range.endContainer;
1597
1714
  let parentNode, originAfter = null;
1598
1715
 
1599
1716
  if (!afterNode) {
@@ -1762,7 +1879,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1762
1879
  this.setRange(oNode, newRange.startOffset, oNode, newRange.endOffset);
1763
1880
 
1764
1881
  return newRange;
1765
- } else if (!util.isBreak(oNode) && util.isFormatElement(parentNode)) {
1882
+ } else if (!util.isBreak(oNode) && !util.isListCell(oNode) && util.isFormatElement(parentNode)) {
1766
1883
  let zeroWidth = null;
1767
1884
  if (!oNode.previousSibling || util.isBreak(oNode.previousSibling)) {
1768
1885
  zeroWidth = util.createTextNode(util.zeroWidthSpace);
@@ -2471,8 +2588,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2471
2588
  let endCon = range.endContainer;
2472
2589
  let endOff = range.endOffset;
2473
2590
 
2474
- if ((isRemoveFormat && range.collapsed && util.isFormatElement(startCon.parentNode) && util.isFormatElement(endCon.parentNode)) || (startCon === endCon && startCon.nodeType === 1 && util.isNonEditable(startCon))) {
2475
- return;
2591
+ if ((isRemoveFormat && range.collapsed && util.isFormatElement(startCon.parentNode)) || (startCon === endCon && startCon.nodeType === 1 && util.isNonEditable(startCon))) {
2592
+ const format = startCon.parentNode;
2593
+ if (!util.isListCell(format) || !util.getValues(format.style).some(function(k) { return this._listKebab.indexOf(k) > -1; }.bind(this))) return;
2476
2594
  }
2477
2595
 
2478
2596
  if (range.collapsed && !isRemoveFormat) {
@@ -2692,6 +2810,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2692
2810
 
2693
2811
  // one line
2694
2812
  if (oneLine) {
2813
+ this._resetCommonListCell(lineNodes[0], styleArray);
2695
2814
  const newRange = this._nodeChange_oneLine(lineNodes[0], newNode, validation, startCon, startOff, endCon, endOff, isRemoveFormat, isRemoveNode, range.collapsed, _removeCheck, _getMaintainedNode, _isMaintainedNode);
2696
2815
  start.container = newRange.startContainer;
2697
2816
  start.offset = newRange.startOffset;
@@ -2704,15 +2823,17 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2704
2823
  } else { // multi line
2705
2824
  // end
2706
2825
  if (endLength > 0) {
2826
+ this._resetCommonListCell(lineNodes[endLength], styleArray);
2707
2827
  newNode = appendNode.cloneNode(false);
2708
2828
  end = this._nodeChange_endLine(lineNodes[endLength], newNode, validation, endCon, endOff, isRemoveFormat, isRemoveNode, _removeCheck, _getMaintainedNode, _isMaintainedNode);
2709
2829
  }
2710
2830
 
2711
2831
  // mid
2712
2832
  for (let i = endLength - 1, newRange; i > 0; i--) {
2833
+ this._resetCommonListCell(lineNodes[i], styleArray);
2713
2834
  newNode = appendNode.cloneNode(false);
2714
2835
  newRange = this._nodeChange_middleLine(lineNodes[i], newNode, validation, isRemoveFormat, isRemoveNode, _removeCheck, end.container);
2715
- if (newRange.endContainer) {
2836
+ if (newRange.endContainer && newRange.ancestor.contains(newRange.endContainer)) {
2716
2837
  end.ancestor = null;
2717
2838
  end.container = newRange.endContainer;
2718
2839
  }
@@ -2720,6 +2841,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2720
2841
  }
2721
2842
 
2722
2843
  // start
2844
+ this._resetCommonListCell(lineNodes[0], styleArray);
2723
2845
  newNode = appendNode.cloneNode(false);
2724
2846
  start = this._nodeChange_startLine(lineNodes[0], newNode, validation, startCon, startOff, isRemoveFormat, isRemoveNode, _removeCheck, _getMaintainedNode, _isMaintainedNode, end.container);
2725
2847
 
@@ -2748,35 +2870,99 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2748
2870
  this.history.push(false);
2749
2871
  },
2750
2872
 
2873
+ _resetCommonListCell: function (el, styleArray) {
2874
+ if (!util.isListCell(el)) return;
2875
+ if (!styleArray) styleArray = this._listKebab;
2876
+
2877
+ const children = util.getArrayItem((el).childNodes, function (current) { return !util.isBreak(current); }, true);
2878
+ const elStyles = el.style;
2879
+
2880
+ const ec = [], ek = [], elKeys = util.getValues(elStyles);
2881
+ for (let i = 0, len = this._listKebab.length; i < len; i++) {
2882
+ if (elKeys.indexOf(this._listKebab[i]) > -1 && styleArray.indexOf(this._listKebab[i]) > -1) {
2883
+ ec.push(this._listCamel[i]);
2884
+ ek.push(this._listKebab[i]);
2885
+ }
2886
+ }
2887
+
2888
+ if (!ec.length) return;
2889
+
2890
+ // reset cell style---
2891
+ const refer = util.createElement('SPAN');
2892
+ for (let i = 0, len = ec.length; i < len; i++) {
2893
+ refer.style[ec[i]] = elStyles[ek[i]];
2894
+ elStyles.removeProperty(ek[i]);
2895
+ }
2896
+
2897
+ let sel = refer.cloneNode(false);
2898
+ let r = null;
2899
+ for (let i = 0, len = children.length, c, s; i < len; i++) {
2900
+ c = children[i];
2901
+ s = util.getValues(c.style);
2902
+ if (s.length === 0 || (ec.some(function (k) {return s.indexOf(k) === -1;}) && s.some(function(k) {ec.indexOf(k) > -1;}))) {
2903
+ r = c.nextSibling;
2904
+ sel.appendChild(c);
2905
+ } else if (sel.childNodes.length > 0) {
2906
+ el.insertBefore(sel, r);
2907
+ sel = refer.cloneNode(false);
2908
+ r = null;
2909
+ }
2910
+ }
2911
+
2912
+ if (sel.childNodes.length > 0) el.insertBefore(sel, r);
2913
+ if (!elStyles.length) el.removeAttribute('style');
2914
+ },
2915
+
2751
2916
  /**
2752
2917
  * @description If certain styles are applied to all child nodes of the list cell, the style of the list cell is also changed. (bold, color, size)
2753
2918
  * @param {Element} el List cell element. <li>
2754
2919
  * @param {Element|null} child Variable for recursive call. ("null" on the first call)
2920
+ * @param {Array|null} styleArray Style array
2755
2921
  * @private
2756
2922
  */
2757
2923
  _setCommonListStyle: function (el, child) {
2758
2924
  if (!util.isListCell(el)) return;
2759
- if (!child) el.removeAttribute('style');
2760
2925
 
2761
2926
  const children = util.getArrayItem((child || el).childNodes, function (current) { return !util.isBreak(current); }, true);
2762
- if (children[0] && children.length === 1){
2763
- child = children[0];
2764
- if (!child || child.nodeType !== 1) return;
2927
+ child = children[0];
2928
+
2929
+ if (!child || children.length > 1 || child.nodeType !== 1) return;
2930
+
2931
+ // set cell style---
2932
+ const commonStyleElements = [];
2933
+ const childStyle = child.style;
2934
+ const elStyle = el.style;
2765
2935
 
2766
- const childStyle = child.style;
2767
- const elStyle = el.style;
2936
+ // bold, italic
2937
+ if (options._textTagsMap[child.nodeName.toLowerCase()] === this._defaultCommand.bold.toLowerCase()) elStyle.fontWeight = 'bold'; // bold
2938
+ else if (childStyle.fontWeight) elStyle.fontWeight = childStyle.fontWeight;
2939
+ if (options._textTagsMap[child.nodeName.toLowerCase()] === this._defaultCommand.italic.toLowerCase()) elStyle.fontStyle = 'italic'; // italic
2940
+ else if (childStyle.fontStyle) elStyle.fontStyle = childStyle.fontStyle;
2768
2941
 
2769
- // bold, italic
2770
- if (options._textTagsMap[child.nodeName.toLowerCase()] === this._defaultCommand.bold.toLowerCase()) elStyle.fontWeight = 'bold'; // bold
2771
- else if (childStyle.fontWeight) elStyle.fontWeight = childStyle.fontWeight;
2772
- if (options._textTagsMap[child.nodeName.toLowerCase()] === this._defaultCommand.italic.toLowerCase()) elStyle.fontStyle = 'italic'; // italic
2773
- else if (childStyle.fontStyle) elStyle.fontStyle = childStyle.fontStyle;
2942
+ // styles
2943
+ const cKeys = util.getValues(childStyle);
2944
+ for (let i = 0, len = this._listCamel.length; i < len; i++) {
2945
+ if (cKeys.indexOf(this._listKebab[i]) > -1) {
2946
+ elStyle[this._listCamel[i]] = childStyle[this._listCamel[i]];
2947
+ childStyle.removeProperty(this._listKebab[i]);
2948
+ }
2949
+ }
2950
+
2951
+ // remove child
2952
+ if (!childStyle.length) commonStyleElements.push(child);
2774
2953
 
2775
- // styles
2776
- if (childStyle.color) elStyle.color = childStyle.color; // color
2777
- if (childStyle.fontSize) elStyle.fontSize = childStyle.fontSize; // size
2954
+ this._setCommonListStyle(el, child);
2778
2955
 
2779
- this._setCommonListStyle(el, child);
2956
+ // common style
2957
+ for (let i = 0, len = commonStyleElements.length, n, ch, p; i < len; i++) {
2958
+ n = commonStyleElements[i];
2959
+ ch = n.childNodes;
2960
+ p = n.parentNode;
2961
+ n = n.nextSibling;
2962
+ while (ch.length > 0) {
2963
+ p.insertBefore(ch[0], n);
2964
+ }
2965
+ util.removeItem(commonStyleElements[i]);
2780
2966
  }
2781
2967
  },
2782
2968
 
@@ -2869,6 +3055,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2869
3055
  util.copyTagAttributes(parentCon, newInnerNode);
2870
3056
 
2871
3057
  return {
3058
+ ancestor: element,
2872
3059
  startContainer: startCon,
2873
3060
  startOffset: startOff,
2874
3061
  endContainer: endCon,
@@ -3987,7 +4174,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
3987
4174
  return {
3988
4175
  ancestor: pNode,
3989
4176
  container: container,
3990
- offset: offset
4177
+ offset: container.nodeType === 1 && offset === 1 ? container.childNodes.length : offset
3991
4178
  };
3992
4179
  },
3993
4180
 
@@ -4000,19 +4187,27 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4000
4187
  actionCall: function (command, display, target) {
4001
4188
  // call plugins
4002
4189
  if (display) {
4003
- if (/more/i.test(display) && target !== this._moreLayerActiveButton) {
4004
- const layer = context.element.toolbar.querySelector('.' + command);
4005
- if (layer) {
4006
- if (this._moreLayerActiveButton) {
4007
- (context.element.toolbar.querySelector('.' + this._moreLayerActiveButton.getAttribute('data-command'))).style.display = 'none';
4008
- util.removeClass(this._moreLayerActiveButton, 'on');
4190
+ if (/more/i.test(display)) {
4191
+ if (target !== this._moreLayerActiveButton) {
4192
+ const layer = context.element.toolbar.querySelector('.' + command);
4193
+ if (layer) {
4194
+ if (this._moreLayerActiveButton) this.moreLayerOff();
4195
+
4196
+ this._moreLayerActiveButton = target;
4197
+ layer.style.display = 'block';
4198
+
4199
+ event._showToolbarBalloon();
4200
+ event._showToolbarInline();
4009
4201
  }
4010
4202
  util.addClass(target, 'on');
4011
- this._moreLayerActiveButton = target;
4012
- layer.style.display = 'block';
4203
+ } else {
4204
+ const layer = context.element.toolbar.querySelector('.' + this._moreLayerActiveButton.getAttribute('data-command'));
4205
+ if (layer) {
4206
+ this.moreLayerOff();
4013
4207
 
4014
- event._showToolbarBalloon();
4015
- event._showToolbarInline();
4208
+ event._showToolbarBalloon();
4209
+ event._showToolbarInline();
4210
+ }
4016
4211
  }
4017
4212
  return;
4018
4213
  }
@@ -4022,7 +4217,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4022
4217
  return;
4023
4218
  }
4024
4219
 
4025
- if (this.isReadOnly) return;
4220
+ if (this.isReadOnly && util.arrayIncludes(this.resizingDisabledButtons, target)) return;
4026
4221
  if (/submenu/.test(display) && (this._menuTray[command] === null || target !== this.submenuActiveButton)) {
4027
4222
  this.callPlugin(command, this.submenuOn.bind(this, target), target);
4028
4223
  return;
@@ -4039,17 +4234,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4039
4234
  this.commandHandler(target, command);
4040
4235
  }
4041
4236
 
4042
- if (/more/i.test(display)) {
4043
- const layer = context.element.toolbar.querySelector('.' + this._moreLayerActiveButton.getAttribute('data-command'));
4044
- if (layer) {
4045
- util.removeClass(this._moreLayerActiveButton, 'on');
4046
- this._moreLayerActiveButton = null;
4047
- layer.style.display = 'none';
4048
-
4049
- event._showToolbarBalloon();
4050
- event._showToolbarInline();
4051
- }
4052
- } else if (/submenu/.test(display)) {
4237
+ if (/submenu/.test(display)) {
4053
4238
  this.submenuOff();
4054
4239
  } else if (!/command/.test(display)) {
4055
4240
  this.submenuOff();
@@ -4073,6 +4258,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4073
4258
  case 'paste':
4074
4259
  break;
4075
4260
  case 'selectAll':
4261
+ this.containerOff();
4076
4262
  const wysiwyg = context.element.wysiwyg;
4077
4263
  let first = util.getChildElement(wysiwyg.firstChild, function (current) { return current.childNodes.length === 0 || current.nodeType === 3; }, false) || wysiwyg.firstChild;
4078
4264
  let last = util.getChildElement(wysiwyg.lastChild, function (current) { return current.childNodes.length === 0 || current.nodeType === 3; }, true) || wysiwyg.lastChild;
@@ -4124,6 +4310,15 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4124
4310
  case 'showBlocks':
4125
4311
  this.toggleDisplayBlocks();
4126
4312
  break;
4313
+ case 'dir':
4314
+ this.setDir(options.rtl ? 'ltr' : 'rtl');
4315
+ break;
4316
+ case 'dir_ltr':
4317
+ this.setDir('ltr');
4318
+ break;
4319
+ case 'dir_rtl':
4320
+ this.setDir('rtl');
4321
+ break;
4127
4322
  case 'save':
4128
4323
  if (typeof options.callBackSave === 'function') {
4129
4324
  options.callBackSave(this.getContents(false), this._variable.isChanged);
@@ -4150,7 +4345,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4150
4345
  removeNode = 'SUB';
4151
4346
  }
4152
4347
 
4153
- this.nodeChange(cmd, null, [removeNode], false);
4348
+ this.nodeChange(cmd, this._commandMapStyles[command] || null, [removeNode], false);
4154
4349
  this.focus();
4155
4350
  }
4156
4351
  },
@@ -4230,7 +4425,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4230
4425
  util.setDisabledButtons(!isCodeView, this.codeViewDisabledButtons);
4231
4426
 
4232
4427
  if (isCodeView) {
4233
- this._setCodeDataToEditor();
4428
+ if (!util.isNonEditable(context.element.wysiwygFrame)) this._setCodeDataToEditor();
4234
4429
  context.element.wysiwygFrame.scrollTop = 0;
4235
4430
  context.element.code.style.display = 'none';
4236
4431
  context.element.wysiwygFrame.style.display = 'block';
@@ -4256,14 +4451,18 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4256
4451
  util.removeClass(this._styleCommandMap.codeView, 'active');
4257
4452
 
4258
4453
  // history stack
4259
- this.history.push(false);
4260
- this.history._resetCachingButton();
4454
+ if (!util.isNonEditable(context.element.wysiwygFrame)) {
4455
+ this.history.push(false);
4456
+ this.history._resetCachingButton();
4457
+ }
4261
4458
  } else {
4262
4459
  this._setEditorDataToCodeView();
4263
4460
  this._variable._codeOriginCssText = this._variable._codeOriginCssText.replace(/(\s?display(\s+)?:(\s+)?)[a-zA-Z]+(?=;)/, 'display: block');
4264
4461
  this._variable._wysiwygOriginCssText = this._variable._wysiwygOriginCssText.replace(/(\s?display(\s+)?:(\s+)?)[a-zA-Z]+(?=;)/, 'display: none');
4265
4462
 
4266
- if (options.height === 'auto' && !options.codeMirrorEditor) context.element.code.style.height = context.element.code.scrollHeight > 0 ? (context.element.code.scrollHeight + 'px') : 'auto';
4463
+ if (this._variable.isFullScreen) context.element.code.style.height = '100%';
4464
+ else if (options.height === 'auto' && !options.codeMirrorEditor) context.element.code.style.height = context.element.code.scrollHeight > 0 ? (context.element.code.scrollHeight + 'px') : 'auto';
4465
+
4267
4466
  if (options.codeMirrorEditor) options.codeMirrorEditor.refresh();
4268
4467
 
4269
4468
  this._variable.isCodeView = true;
@@ -4309,7 +4508,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4309
4508
  }
4310
4509
  }
4311
4510
 
4312
- this._wd.head.innerHTML = parseDocument.head.innerHTML;
4511
+ let headers = parseDocument.head.innerHTML;
4512
+ if (!parseDocument.head.querySelector('link[rel="stylesheet"]') || (this.options.height === 'auto' && !parseDocument.head.querySelector('style'))) {
4513
+ headers += util._setIframeCssTags(this.options);
4514
+ }
4515
+
4516
+ this._wd.head.innerHTML = headers;
4313
4517
  this._wd.body.innerHTML = this.convertContentsForEditor(parseDocument.body.innerHTML);
4314
4518
 
4315
4519
  const attrs = parseDocument.body.attributes;
@@ -4333,7 +4537,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4333
4537
  * @private
4334
4538
  */
4335
4539
  _setEditorDataToCodeView: function () {
4336
- const codeContents = this.convertHTMLForCodeView(context.element.wysiwyg);
4540
+ const codeContents = this.convertHTMLForCodeView(context.element.wysiwyg, false);
4337
4541
  let codeValue = '';
4338
4542
 
4339
4543
  if (options.fullPage) {
@@ -4351,7 +4555,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4351
4555
 
4352
4556
  /**
4353
4557
  * @description Changes to full screen or default screen
4354
- * @param {Element} element full screen button
4558
+ * @param {Element|null} element full screen button
4355
4559
  */
4356
4560
  toggleFullScreen: function (element) {
4357
4561
  const topArea = context.element.topArea;
@@ -4361,6 +4565,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4361
4565
  const code = context.element.code;
4362
4566
  const _var = this._variable;
4363
4567
  this.controllersOff();
4568
+
4569
+ const wasToolbarHidden = (toolbar.style.display === 'none' || (this._isInline && !this._inlineToolbarAttr.isShow));
4364
4570
 
4365
4571
  if (!_var.isFullScreen) {
4366
4572
  _var.isFullScreen = true;
@@ -4406,7 +4612,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4406
4612
  _var.innerHeight_fullScreen = (_w.innerHeight - toolbar.offsetHeight);
4407
4613
  editorArea.style.height = (_var.innerHeight_fullScreen - options.fullScreenOffset) + 'px';
4408
4614
 
4409
- util.changeElement(element.firstElementChild, icons.reduction);
4615
+ if (element) util.changeElement(element.firstElementChild, icons.reduction);
4410
4616
 
4411
4617
  if (options.iframe && options.height === 'auto') {
4412
4618
  editorArea.style.overflow = 'auto';
@@ -4425,6 +4631,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4425
4631
  topArea.style.cssText = _var._originCssText;
4426
4632
  _d.body.style.overflow = _var._bodyOverflow;
4427
4633
 
4634
+ if (options.height === 'auto' && !options.codeMirrorEditor) event._codeViewAutoHeight();
4635
+
4428
4636
  if (!!options.toolbarContainer) options.toolbarContainer.appendChild(toolbar);
4429
4637
 
4430
4638
  if (options.stickyToolbar > -1) {
@@ -4443,12 +4651,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4443
4651
  if (!!options.toolbarContainer) util.removeClass(toolbar, 'se-toolbar-balloon');
4444
4652
 
4445
4653
  event.onScroll_window();
4446
- util.changeElement(element.firstElementChild, icons.expansion);
4654
+ if (element) util.changeElement(element.firstElementChild, icons.expansion);
4447
4655
 
4448
4656
  context.element.topArea.style.marginTop = '';
4449
4657
  util.removeClass(this._styleCommandMap.fullScreen, 'active');
4450
4658
  }
4451
4659
 
4660
+ if (wasToolbarHidden) functions.toolbar.hide();
4661
+
4452
4662
  // user event
4453
4663
  if (typeof functions.toggleFullScreen === 'function') functions.toggleFullScreen(this._variable.isFullScreen, this);
4454
4664
  },
@@ -4501,8 +4711,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4501
4711
  _w.setTimeout(function () {
4502
4712
  try {
4503
4713
  iframe.focus();
4504
- // IE or Edge
4505
- if (util.isIE_Edge || !!_d.documentMode || !!_w.StyleMedia) {
4714
+ // IE or Edge, Chromium
4715
+ if (util.isIE_Edge || util.isChromium || !!_d.documentMode || !!_w.StyleMedia) {
4506
4716
  try {
4507
4717
  iframe.contentWindow.document.execCommand('print', false, null);
4508
4718
  } catch (e) {
@@ -4571,6 +4781,70 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4571
4781
  }
4572
4782
  },
4573
4783
 
4784
+ /**
4785
+ * @description Set direction to "rtl" or "ltr".
4786
+ * @param {String} dir "rtl" or "ltr"
4787
+ */
4788
+ setDir: function (dir) {
4789
+ const rtl = dir === 'rtl';
4790
+ const changeDir = this._prevRtl !== rtl;
4791
+ this._prevRtl = options.rtl = rtl;
4792
+
4793
+ if (changeDir) {
4794
+ // align buttons
4795
+ if (this.plugins.align) {
4796
+ this.plugins.align.exchangeDir.call(this);
4797
+ }
4798
+ // indent buttons
4799
+ if (context.tool.indent) util.changeElement(context.tool.indent.firstElementChild, icons.indent);
4800
+ if (context.tool.outdent) util.changeElement(context.tool.outdent.firstElementChild, icons.outdent);
4801
+ }
4802
+
4803
+ const el = context.element;
4804
+ if (rtl) {
4805
+ util.addClass(el.topArea, 'se-rtl');
4806
+ util.addClass(el.wysiwygFrame, 'se-rtl');
4807
+ } else {
4808
+ util.removeClass(el.topArea, 'se-rtl');
4809
+ util.removeClass(el.wysiwygFrame, 'se-rtl');
4810
+ }
4811
+
4812
+ const lineNodes = util.getListChildren(el.wysiwyg, function (current) {
4813
+ return util.isFormatElement(current) && (current.style.marginRight || current.style.marginLeft || current.style.textAlign);
4814
+ });
4815
+
4816
+ for (let i = 0, len = lineNodes.length, n, l, r; i < len; i++) {
4817
+ n = lineNodes[i];
4818
+ // indent margin
4819
+ r = n.style.marginRight;
4820
+ l = n.style.marginLeft;
4821
+ if (r || l) {
4822
+ n.style.marginRight = l;
4823
+ n.style.marginLeft = r;
4824
+ }
4825
+ // text align
4826
+ r = n.style.textAlign;
4827
+ if (r === 'left') n.style.textAlign = 'right';
4828
+ else if (r === 'right') n.style.textAlign = 'left';
4829
+ }
4830
+
4831
+ const tool = context.tool;
4832
+ if (tool.dir) {
4833
+ util.changeTxt(tool.dir.querySelector('.se-tooltip-text'), lang.toolbar[options.rtl ? 'dir_ltr' : 'dir_rtl']);
4834
+ util.changeElement(tool.dir.firstElementChild, icons[options.rtl ? 'dir_ltr' : 'dir_rtl']);
4835
+ }
4836
+
4837
+ if (tool.dir_ltr) {
4838
+ if (rtl) util.removeClass(tool.dir_ltr, 'active');
4839
+ else util.addClass(tool.dir_ltr, 'active');
4840
+ }
4841
+
4842
+ if (tool.dir_rtl) {
4843
+ if (rtl) util.addClass(tool.dir_rtl, 'active');
4844
+ else util.removeClass(tool.dir_rtl, 'active');
4845
+ }
4846
+ },
4847
+
4574
4848
  /**
4575
4849
  * @description Sets the HTML string
4576
4850
  * @param {String|undefined} html HTML string
@@ -4586,7 +4860,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4586
4860
  // history stack
4587
4861
  this.history.push(false);
4588
4862
  } else {
4589
- const value = this.convertHTMLForCodeView(convertValue);
4863
+ const value = this.convertHTMLForCodeView(convertValue, false);
4590
4864
  this._setCodeView(value);
4591
4865
  }
4592
4866
  },
@@ -4607,7 +4881,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4607
4881
  * @returns {Object}
4608
4882
  */
4609
4883
  getContents: function (onlyContents) {
4610
- const contents = context.element.wysiwyg.innerHTML;
4884
+ const contents = this.convertHTMLForCodeView(context.element.wysiwyg, true);
4611
4885
  const renderHTML = util.createElement('DIV');
4612
4886
  renderHTML.innerHTML = contents;
4613
4887
 
@@ -4627,6 +4901,16 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4627
4901
  }
4628
4902
  },
4629
4903
 
4904
+ /**
4905
+ * @description Gets the current contents with containing parent div(div.sun-editor-editable).
4906
+ * <div class="sun-editor-editable">{contents}</div>
4907
+ * @param {Boolean} onlyContents Return only the contents of the body without headers when the "fullPage" option is true
4908
+ * @returns {Object}
4909
+ */
4910
+ getFullContents: function (onlyContents) {
4911
+ return '<div class="sun-editor-editable' + options.rtl ? ' se-rtl' : '' + '">' + this.getContents(onlyContents) + '</div>';
4912
+ },
4913
+
4630
4914
  /**
4631
4915
  * @description Returns HTML string according to tag type and configuration.
4632
4916
  * Use only "cleanHTML"
@@ -4690,7 +4974,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4690
4974
  .replace(/\n/g, '')
4691
4975
  .replace(/<(script|style)[\s\S]*>[\s\S]*<\/(script|style)>/gi, '')
4692
4976
  .replace(/<[a-z0-9]+\:[a-z0-9]+[^>^\/]*>[^>]*<\/[a-z0-9]+\:[a-z0-9]+>/gi, '')
4693
- .replace(this.editorTagsWhitelistRegExp, '');
4977
+ .replace(this.editorTagsWhitelistRegExp, '')
4978
+ .replace(this.editorTagsBlacklistRegExp, '');
4694
4979
  },
4695
4980
 
4696
4981
  /**
@@ -4705,18 +4990,28 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4705
4990
  if (/^<[a-z0-9]+\:[a-z0-9]+/i.test(m)) return m;
4706
4991
 
4707
4992
  let v = null;
4708
- const tAttr = this._attributesTagsWhitelist[t.match(/(?!<)[a-zA-Z0-9\-]+/)[0].toLowerCase()];
4709
- if (tAttr) v = m.match(tAttr);
4993
+ const tagName = t.match(/(?!<)[a-zA-Z0-9\-]+/)[0].toLowerCase();
4994
+
4995
+ // blacklist
4996
+ const bAttr = this._attributesTagsBlacklist[tagName];
4997
+ if (bAttr) m = m.replace(bAttr, '');
4998
+ else m = m.replace(this._attributesBlacklistRegExp, '');
4999
+
5000
+ // whitelist
5001
+ const wAttr = this._attributesTagsWhitelist[tagName];
5002
+ if (wAttr) v = m.match(wAttr);
4710
5003
  else v = m.match(this._attributesWhitelistRegExp);
4711
5004
 
5005
+ // anchor
4712
5006
  if (!lowLevelCheck || /<a\b/i.test(t)) {
4713
- const sv = m.match(/id\s*=\s*(?:"|')[^"']*(?:"|')/);
5007
+ const sv = m.match(/(?:(?:id|name)\s*=\s*(?:"|')[^"']*(?:"|'))/g);
4714
5008
  if (sv) {
4715
5009
  if (!v) v = [];
4716
5010
  v.push(sv[0]);
4717
5011
  }
4718
5012
  }
4719
5013
 
5014
+ // span
4720
5015
  if ((!lowLevelCheck || /<span/i.test(t)) && (!v || !/style=/i.test(v.toString()))) {
4721
5016
  const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
4722
5017
  if (sv) {
@@ -4725,6 +5020,29 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4725
5020
  }
4726
5021
  }
4727
5022
 
5023
+ // img
5024
+ if (/<img/i.test(t)) {
5025
+ let w = '', h = '';
5026
+ const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
5027
+ if (!v) v = [];
5028
+ if (sv) {
5029
+ w = sv[0].match(/width:(.+);/);
5030
+ w = util.getNumber(w ? w[1] : '', -1) || '';
5031
+ h = sv[0].match(/height:(.+);/);
5032
+ h = util.getNumber(h ? h[1] : '', -1) || '';
5033
+ }
5034
+
5035
+ if (!w || !h) {
5036
+ const avw = m.match(/width\s*=\s*((?:"|')[^"']*(?:"|'))/);
5037
+ const avh = m.match(/height\s*=\s*((?:"|')[^"']*(?:"|'))/);
5038
+ if (avw || avh) {
5039
+ w = !w ? util.getNumber(avw ? avw[1] : '') || '' : w;
5040
+ h = !h ? util.getNumber(avh ? avh[1] : '') || '' : h;
5041
+ }
5042
+ }
5043
+ v.push('data-origin="' + (w + ',' + h) + '"');
5044
+ }
5045
+
4728
5046
  if (v) {
4729
5047
  for (let i = 0, len = v.length; i < len; i++) {
4730
5048
  if (lowLevelCheck && /^class="(?!(__se__|se-|katex))/.test(v[i])) continue;
@@ -4740,14 +5058,16 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4740
5058
  * @param {String} html HTML string
4741
5059
  * @param {String|RegExp|null} whitelist Regular expression of allowed tags.
4742
5060
  * RegExp object is create by util.createTagsWhitelist method. (core.pasteTagsWhitelistRegExp)
5061
+ * @param {String|RegExp|null} blacklist Regular expression of disallowed tags.
5062
+ * RegExp object is create by util.createTagsBlacklist method. (core.pasteTagsBlacklistRegExp)
4743
5063
  * @returns {String}
4744
5064
  */
4745
- cleanHTML: function (html, whitelist) {
5065
+ cleanHTML: function (html, whitelist, blacklist) {
4746
5066
  html = this._deleteDisallowedTags(this._parser.parseFromString(html, 'text/html').body.innerHTML).replace(/(<[a-zA-Z0-9\-]+)[^>]*(?=>)/g, this._cleanTags.bind(this, true));
4747
5067
 
4748
5068
  const dom = _d.createRange().createContextualFragment(html);
4749
5069
  try {
4750
- util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, true);
5070
+ util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, true);
4751
5071
  } catch (error) {
4752
5072
  console.warn('[SUNEDITOR.cleanHTML.consistencyCheck.fail] ' + error);
4753
5073
  }
@@ -4783,7 +5103,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4783
5103
  }
4784
5104
 
4785
5105
  cleanHTML = util.htmlRemoveWhiteSpace(cleanHTML);
4786
- return this._tagConvertor(!cleanHTML ? html : !whitelist ? cleanHTML : cleanHTML.replace(typeof whitelist === 'string' ? util.createTagsWhitelist(whitelist) : whitelist, ''));
5106
+ if (!cleanHTML) {
5107
+ cleanHTML = html;
5108
+ } else {
5109
+ if (whitelist) cleanHTML = cleanHTML.replace(typeof whitelist === 'string' ? util.createTagsWhitelist(whitelist) : whitelist, '');
5110
+ if (blacklist) cleanHTML = cleanHTML.replace(typeof blacklist === 'string' ? util.createTagsBlacklist(blacklist) : blacklist, '');
5111
+ }
5112
+
5113
+ return this._tagConvertor(cleanHTML);
4787
5114
  },
4788
5115
 
4789
5116
  /**
@@ -4796,7 +5123,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4796
5123
  const dom = _d.createRange().createContextualFragment(contents);
4797
5124
 
4798
5125
  try {
4799
- util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, false);
5126
+ util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, false);
4800
5127
  } catch (error) {
4801
5128
  console.warn('[SUNEDITOR.convertContentsForEditor.consistencyCheck.fail] ' + error);
4802
5129
  }
@@ -4816,10 +5143,29 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4816
5143
  }
4817
5144
 
4818
5145
  const domTree = dom.childNodes;
4819
- let cleanHTML = '';
4820
- for (let i = 0, len = domTree.length; i < len; i++) {
4821
- cleanHTML += this._makeLine(domTree[i], true);
5146
+ let cleanHTML = '', p = null;
5147
+ for (let i = 0, t; i < domTree.length; i++) {
5148
+ t = domTree[i];
5149
+
5150
+ if (!util.isFormatElement(t) && !util.isComponent(t) && !util.isMedia(t)) {
5151
+ if (!p) p = util.createElement(options.defaultTag);
5152
+ p.appendChild(t);
5153
+ i--;
5154
+ if (domTree[i + 1] && !util.isFormatElement(domTree[i + 1])) {
5155
+ continue;
5156
+ } else {
5157
+ t = p;
5158
+ p = null;
5159
+ }
5160
+ }
5161
+
5162
+ if (p) {
5163
+ cleanHTML += this._makeLine(p, true);
5164
+ p = null;
5165
+ }
5166
+ cleanHTML += this._makeLine(t, true);
4822
5167
  }
5168
+ if (p) cleanHTML += this._makeLine(p, true);
4823
5169
 
4824
5170
  if (cleanHTML.length === 0) return '<' + options.defaultTag + '><br></' + options.defaultTag + '>';
4825
5171
 
@@ -4830,28 +5176,30 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4830
5176
  /**
4831
5177
  * @description Converts wysiwyg area element into a format that can be placed in an editor of code view mode
4832
5178
  * @param {Element|String} html WYSIWYG element (context.element.wysiwyg) or HTML string.
5179
+ * @param {Boolean} comp If true, does not line break and indentation of tags.
4833
5180
  * @returns {String}
4834
5181
  */
4835
- convertHTMLForCodeView: function (html) {
5182
+ convertHTMLForCodeView: function (html, comp) {
4836
5183
  let returnHTML = '';
4837
5184
  const wRegExp = _w.RegExp;
4838
5185
  const brReg = new wRegExp('^(BLOCKQUOTE|PRE|TABLE|THEAD|TBODY|TR|TH|TD|OL|UL|IMG|IFRAME|VIDEO|AUDIO|FIGURE|FIGCAPTION|HR|BR|CANVAS|SELECT)$', 'i');
4839
5186
  const wDoc = typeof html === 'string' ? _d.createRange().createContextualFragment(html) : html;
4840
5187
  const isFormat = function (current) { return this.isFormatElement(current) || this.isComponent(current); }.bind(util);
5188
+ const brChar = comp ? '' : '\n';
4841
5189
 
4842
- let indentSize = this._variable.codeIndent * 1;
5190
+ let indentSize = comp ? 0 : this._variable.codeIndent * 1;
4843
5191
  indentSize = indentSize > 0 ? new _w.Array(indentSize + 1).join(' ') : '';
4844
5192
 
4845
- (function recursionFunc (element, indent, lineBR) {
5193
+ (function recursionFunc (element, indent) {
4846
5194
  const children = element.childNodes;
4847
5195
  const elementRegTest = brReg.test(element.nodeName);
4848
5196
  const elementIndent = (elementRegTest ? indent : '');
4849
5197
 
4850
- for (let i = 0, len = children.length, node, br, nodeRegTest, tag, tagIndent; i < len; i++) {
5198
+ for (let i = 0, len = children.length, node, br, lineBR, nodeRegTest, tag, tagIndent; i < len; i++) {
4851
5199
  node = children[i];
4852
5200
  nodeRegTest = brReg.test(node.nodeName);
4853
- br = nodeRegTest ? '\n' : '';
4854
- lineBR = isFormat(node) && !elementRegTest && !/^(TH|TD)$/i.test(element.nodeName) ? '\n' : '';
5201
+ br = nodeRegTest ? brChar : '';
5202
+ lineBR = isFormat(node) && !elementRegTest && !/^(TH|TD)$/i.test(element.nodeName) ? brChar : '';
4855
5203
 
4856
5204
  if (node.nodeType === 8) {
4857
5205
  returnHTML += '\n<!-- ' + node.textContent.trim() + ' -->' + br;
@@ -4862,7 +5210,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4862
5210
  continue;
4863
5211
  }
4864
5212
  if (node.childNodes.length === 0) {
4865
- returnHTML += (/^HR$/i.test(node.nodeName) ? '\n' : '') + (/^PRE$/i.test(node.parentElement.nodeName) && /^BR$/i.test(node.nodeName) ? '' : elementIndent) + node.outerHTML + br;
5213
+ returnHTML += (/^HR$/i.test(node.nodeName) ? brChar : '') + (/^PRE$/i.test(node.parentElement.nodeName) && /^BR$/i.test(node.nodeName) ? '' : elementIndent) + node.outerHTML + br;
4866
5214
  continue;
4867
5215
  }
4868
5216
 
@@ -4873,12 +5221,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4873
5221
  tagIndent = elementIndent || nodeRegTest ? indent : '';
4874
5222
  returnHTML += (lineBR || (elementRegTest ? '' : br)) + tagIndent + node.outerHTML.match(wRegExp('<' + tag + '[^>]*>', 'i'))[0] + br;
4875
5223
  recursionFunc(node, indent + indentSize, '');
4876
- returnHTML += (/\n$/.test(returnHTML) ? tagIndent : '') + '</' + tag + '>' + (lineBR || br || elementRegTest ? '\n' : '' || /^(TH|TD)$/i.test(node.nodeName) ? '\n' : '');
5224
+ returnHTML += (/\n$/.test(returnHTML) ? tagIndent : '') + '</' + tag + '>' + (lineBR || br || elementRegTest ? brChar : '' || /^(TH|TD)$/i.test(node.nodeName) ? brChar : '');
4877
5225
  }
4878
5226
  }
4879
- }(wDoc, '', '\n'));
5227
+ }(wDoc, ''));
4880
5228
 
4881
- return returnHTML.trim() + '\n';
5229
+ return returnHTML.trim() + brChar;
4882
5230
  },
4883
5231
 
4884
5232
  /**
@@ -4980,6 +5328,36 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4980
5328
  return /byte/.test(charCounterType) ? util.getByteLength(content) : content.length;
4981
5329
  },
4982
5330
 
5331
+ /**
5332
+ * @description Reset buttons of the responsive toolbar.
5333
+ */
5334
+ resetResponsiveToolbar: function () {
5335
+ core.controllersOff();
5336
+
5337
+ const responsiveSize = event._responsiveButtonSize;
5338
+ if (responsiveSize) {
5339
+ let w = 0;
5340
+ if ((core._isBalloon || core._isInline) && options.toolbarWidth === 'auto') {
5341
+ w = context.element.topArea.offsetWidth;
5342
+ } else {
5343
+ w = context.element.toolbar.offsetWidth;
5344
+ }
5345
+
5346
+ let responsiveWidth = 'default';
5347
+ for (let i = 1, len = responsiveSize.length; i < len; i++) {
5348
+ if (w < responsiveSize[i]) {
5349
+ responsiveWidth = responsiveSize[i] + '';
5350
+ break;
5351
+ }
5352
+ }
5353
+
5354
+ if (event._responsiveCurrentSize !== responsiveWidth) {
5355
+ event._responsiveCurrentSize = responsiveWidth;
5356
+ functions.setToolbarButtons(event._responsiveButtons[responsiveWidth]);
5357
+ }
5358
+ }
5359
+ },
5360
+
4983
5361
  /**
4984
5362
  * @description Set the char count to charCounter element textContent.
4985
5363
  * @private
@@ -5058,9 +5436,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5058
5436
  if (activePlugins.indexOf(key) > -1) {
5059
5437
  plugins[key].active.call(this, null);
5060
5438
  } else if (commandMap.OUTDENT && /^OUTDENT$/i.test(key)) {
5061
- if (!this.isReadOnly) commandMap.OUTDENT.setAttribute('disabled', true);
5439
+ if (!util.isImportantDisabled(commandMap.OUTDENT)) commandMap.OUTDENT.setAttribute('disabled', true);
5062
5440
  } else if (commandMap.INDENT && /^INDENT$/i.test(key)) {
5063
- if (!this.isReadOnly) commandMap.INDENT.removeAttribute('disabled');
5441
+ if (!util.isImportantDisabled(commandMap.INDENT)) commandMap.INDENT.removeAttribute('disabled');
5064
5442
  } else {
5065
5443
  util.removeClass(commandMap[key], 'active');
5066
5444
  }
@@ -5078,6 +5456,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5078
5456
  this._ww = options.iframe ? context.element.wysiwygFrame.contentWindow : _w;
5079
5457
  this._wd = _d;
5080
5458
  this._charTypeHTML = options.charCounterType === 'byte-html';
5459
+ this.wwComputedStyle = _w.getComputedStyle(context.element.wysiwyg);
5460
+ this._editorHeight = context.element.wysiwygFrame.offsetHeight;
5081
5461
 
5082
5462
  if (!options.iframe && typeof _w.ShadowRoot === 'function') {
5083
5463
  let child = context.element.wysiwygFrame;
@@ -5103,30 +5483,56 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5103
5483
  this._disallowedTextTagsRegExp = disallowTextTags.length === 0 ? null : new wRegExp('(<\\/?)(' + disallowTextTags.join('|') + ')\\b\\s*([^>^<]+)?\\s*(?=>)', 'gi');
5104
5484
 
5105
5485
  // set whitelist
5486
+ const getRegList = function (str, str2) { return !str ? '^' : (str === '*' ? '[a-z-]+' : (!str2 ? str : (str + '|' + str2))); };
5487
+ // tags
5106
5488
  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';
5107
- this._allowHTMLComments = options._editorTagsWhitelist.indexOf('//') > -1;
5108
- this._htmlCheckWhitelistRegExp = new wRegExp('^(' + options._editorTagsWhitelist.replace('|//', '') + ')$', 'i');
5109
- this.editorTagsWhitelistRegExp = util.createTagsWhitelist(options._editorTagsWhitelist.replace('|//', '|<!--|-->'));
5110
- this.pasteTagsWhitelistRegExp = util.createTagsWhitelist(options.pasteTagsWhitelist);
5111
-
5489
+ this._allowHTMLComments = options._editorTagsWhitelist.indexOf('//') > -1 || options._editorTagsWhitelist === '*';
5490
+ // html check
5491
+ this._htmlCheckWhitelistRegExp = new wRegExp('^(' + getRegList(options._editorTagsWhitelist.replace('|//', ''), '') + ')$', 'i');
5492
+ this._htmlCheckBlacklistRegExp = new wRegExp('^(' + (options.tagsBlacklist || '^') + ')$', 'i');
5493
+ // tags
5494
+ this.editorTagsWhitelistRegExp = util.createTagsWhitelist(getRegList(options._editorTagsWhitelist.replace('|//', '|<!--|-->'), ''));
5495
+ this.editorTagsBlacklistRegExp = util.createTagsBlacklist(options.tagsBlacklist.replace('|//', '|<!--|-->'));
5496
+ // paste tags
5497
+ this.pasteTagsWhitelistRegExp = util.createTagsWhitelist(getRegList(options.pasteTagsWhitelist, ''));
5498
+ this.pasteTagsBlacklistRegExp = util.createTagsBlacklist(options.pasteTagsBlacklist);
5499
+ // attributes
5112
5500
  const regEndStr = '\\s*=\\s*(\")[^\"]*\\1';
5113
- const _attr = options.attributesWhitelist;
5114
- const tagsAttr = {};
5501
+ const _wAttr = options.attributesWhitelist;
5502
+ let tagsAttr = {};
5115
5503
  let allAttr = '';
5116
- if (!!_attr) {
5117
- for (let k in _attr) {
5118
- if (!util.hasOwn(_attr, k) || /^on[a-z]+$/i.test(_attr[k])) continue;
5504
+ if (!!_wAttr) {
5505
+ for (let k in _wAttr) {
5506
+ if (!util.hasOwn(_wAttr, k) || /^on[a-z]+$/i.test(_wAttr[k])) continue;
5119
5507
  if (k === 'all') {
5120
- allAttr = _attr[k] + '|';
5508
+ allAttr = getRegList(_wAttr[k], defaultAttr);
5121
5509
  } else {
5122
- tagsAttr[k] = new wRegExp('(?:' + _attr[k] + '|' + defaultAttr + ')' + regEndStr, 'ig');
5510
+ tagsAttr[k] = new wRegExp('\\s(?:' + getRegList(_wAttr[k], '') + ')' + regEndStr, 'ig');
5123
5511
  }
5124
5512
  }
5125
5513
  }
5126
-
5127
- this._attributesWhitelistRegExp = new wRegExp('(?:' + allAttr + defaultAttr + ')' + regEndStr, 'ig');
5514
+
5515
+ this._attributesWhitelistRegExp = new wRegExp('\\s(?:' + (allAttr || defaultAttr) + ')' + regEndStr, 'ig');
5128
5516
  this._attributesTagsWhitelist = tagsAttr;
5129
5517
 
5518
+ // blacklist
5519
+ const _bAttr = options.attributesBlacklist;
5520
+ tagsAttr = {};
5521
+ allAttr = '';
5522
+ if (!!_bAttr) {
5523
+ for (let k in _bAttr) {
5524
+ if (!util.hasOwn(_bAttr, k)) continue;
5525
+ if (k === 'all') {
5526
+ allAttr = getRegList(_bAttr[k], '');
5527
+ } else {
5528
+ tagsAttr[k] = new wRegExp('\\s(?:' + getRegList(_bAttr[k], '') + ')' + regEndStr, 'ig');
5529
+ }
5530
+ }
5531
+ }
5532
+
5533
+ this._attributesBlacklistRegExp = new wRegExp('\\s(?:' + (allAttr || '^') + ')' + regEndStr, 'ig');
5534
+ this._attributesTagsBlacklist = tagsAttr;
5535
+
5130
5536
  // set modes
5131
5537
  this._isInline = /inline/i.test(options.mode);
5132
5538
  this._isBalloon = /balloon|balloon-always/i.test(options.mode);
@@ -5154,7 +5560,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5154
5560
  if (!util.hasOwn(plugins, key)) continue;
5155
5561
  plugin = plugins[key];
5156
5562
  button = pluginCallButtons[key];
5157
- if (plugin.active && button) {
5563
+ if ((plugin.active || plugin.action) && button) {
5158
5564
  this.callPlugin(key, null, button);
5159
5565
  }
5160
5566
  if (typeof plugin.checkFileInfo === 'function' && typeof plugin.resetFileInfo === 'function') {
@@ -5180,8 +5586,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5180
5586
 
5181
5587
  this.managedTagsInfo.query = managedClass.toString();
5182
5588
  this._fileManager.queryString = this._fileManager.tags.join(',');
5183
- this._fileManager.regExp = new wRegExp('^(' + this._fileManager.tags.join('|') + ')$', 'i');
5184
- this._fileManager.pluginRegExp = new wRegExp('^(' + (filePluginRegExp.length === 0 ? 'undefined' : filePluginRegExp.join('|')) + ')$', 'i');
5589
+ this._fileManager.regExp = new wRegExp('^(' + (this._fileManager.tags.join('|') || '^') + ')$', 'i');
5590
+ this._fileManager.pluginRegExp = new wRegExp('^(' + (filePluginRegExp.length === 0 ? '^' : filePluginRegExp.join('|')) + ')$', 'i');
5185
5591
 
5186
5592
  // cache editor's element
5187
5593
  this._variable._originCssText = context.element.topArea.style.cssText;
@@ -5204,6 +5610,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5204
5610
  }
5205
5611
 
5206
5612
  this._initWysiwygArea(reload, _initHTML);
5613
+ this.setDir(options.rtl ? 'rtl' : 'ltr');
5207
5614
  },
5208
5615
 
5209
5616
  /**
@@ -5211,9 +5618,11 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5211
5618
  * @private
5212
5619
  */
5213
5620
  _cachingButtons: function () {
5214
- this.codeViewDisabledButtons = context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]:not([class~="se-code-view-enabled"])');
5621
+ this.codeViewDisabledButtons = context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]:not([class~="se-code-view-enabled"]):not([data-display="MORE"])');
5215
5622
  this.resizingDisabledButtons = context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]:not([class~="se-resizing-enabled"]):not([data-display="MORE"])');
5216
5623
 
5624
+ this.saveButtonStates();
5625
+
5217
5626
  const tool = context.tool;
5218
5627
  this.commandMap = {
5219
5628
  SUB: tool.subscript,
@@ -5271,7 +5680,21 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5271
5680
  */
5272
5681
  _iframeAutoHeight: function () {
5273
5682
  if (this._iframeAuto) {
5274
- _w.setTimeout(function () { context.element.wysiwygFrame.style.height = core._iframeAuto.offsetHeight + 'px'; });
5683
+ _w.setTimeout(function () {
5684
+ const h = core._iframeAuto.offsetHeight;
5685
+ context.element.wysiwygFrame.style.height = h + 'px';
5686
+ if (util.isIE) core.__callResizeFunction(h, null);
5687
+ });
5688
+ } else if (util.isIE) {
5689
+ core.__callResizeFunction(context.element.wysiwygFrame.offsetHeight, null);
5690
+ }
5691
+ },
5692
+
5693
+ __callResizeFunction(h, resizeObserverEntry) {
5694
+ h = h === -1 ? resizeObserverEntry.borderBoxSize[0].blockSize : h;
5695
+ if (this._editorHeight !== h) {
5696
+ if (typeof functions.onResizeEditor === 'function') functions.onResizeEditor(h, this._editorHeight, core, resizeObserverEntry);
5697
+ this._editorHeight = h;
5275
5698
  }
5276
5699
  },
5277
5700
 
@@ -5311,7 +5734,15 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5311
5734
  let focusNode, offset, format;
5312
5735
 
5313
5736
  const fileComponent = util.getParentElement(commonCon, util.isComponent);
5314
- if (fileComponent && !util.isTable(fileComponent)) return;
5737
+ if (fileComponent && !util.isTable(fileComponent)) {
5738
+ return;
5739
+ } else if (commonCon.nodeType === 1 && commonCon.getAttribute('data-se-embed') === 'true') {
5740
+ let el = commonCon.nextElementSibling;
5741
+ if (!util.isFormatElement(el)) el = this.appendFormatTag(commonCon, options.defaultTag);
5742
+ this.setRange(el.firstChild, 0, el.firstChild, 0);
5743
+ return;
5744
+ }
5745
+
5315
5746
  if ((util.isRangeFormatElement(startCon) || util.isWysiwygDiv(startCon)) && (util.isComponent(startCon.children[range.startOffset]) || util.isComponent(startCon.children[range.startOffset - 1]))) return;
5316
5747
  if (util.getParentElement(commonCon, util.isNotCheckingNode)) return null;
5317
5748
 
@@ -5407,6 +5838,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5407
5838
  this._resourcesStateChange();
5408
5839
 
5409
5840
  _w.setTimeout(function () {
5841
+ // observer
5842
+ if (event._resizeObserver) event._resizeObserver.observe(context.element.wysiwygFrame);
5843
+ if (event._toolbarObserver) event._toolbarObserver.observe(context.element._toolbarShadow);
5410
5844
  // user event
5411
5845
  if (typeof functions.onload === 'function') functions.onload(core, reload);
5412
5846
  });
@@ -5423,6 +5857,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5423
5857
  _top: contextEl.topArea,
5424
5858
  _relative: contextEl.relative,
5425
5859
  _toolBar: contextEl.toolbar,
5860
+ _toolbarShadow: contextEl._toolbarShadow,
5426
5861
  _menuTray: contextEl._menuTray,
5427
5862
  _editorArea: contextEl.editorArea,
5428
5863
  _wysiwygArea: contextEl.wysiwygFrame,
@@ -5525,7 +5960,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5525
5960
  break;
5526
5961
  }
5527
5962
 
5528
- if (!command) return false;
5963
+ if (!command) return keyStr === 'B'; // chromium - bold disabled
5529
5964
 
5530
5965
  core.commandHandler(core.commandMap[command], command);
5531
5966
  return true;
@@ -5566,9 +6001,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5566
6001
  }
5567
6002
  }
5568
6003
 
5569
- if (!core.isReadOnly && util.isFormatElement(element)) {
6004
+ if (util.isFormatElement(element)) {
5570
6005
  /* Outdent */
5571
- if (commandMapNodes.indexOf('OUTDENT') === -1 && commandMap.OUTDENT) {
6006
+ if (commandMapNodes.indexOf('OUTDENT') === -1 && commandMap.OUTDENT && !util.isImportantDisabled(commandMap.OUTDENT)) {
5572
6007
  if (util.isListCell(element) || (element.style[marginDir] && util.getNumber(element.style[marginDir], 0) > 0)) {
5573
6008
  commandMapNodes.push('OUTDENT');
5574
6009
  commandMap.OUTDENT.removeAttribute('disabled');
@@ -5576,7 +6011,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5576
6011
  }
5577
6012
 
5578
6013
  /* Indent */
5579
- if (commandMapNodes.indexOf('INDENT') === -1 && commandMap.INDENT) {
6014
+ if (commandMapNodes.indexOf('INDENT') === -1 && commandMap.INDENT && !util.isImportantDisabled(commandMap.INDENT)) {
5580
6015
  commandMapNodes.push('INDENT');
5581
6016
  if (util.isListCell(element) && !element.previousElementSibling) {
5582
6017
  commandMap.INDENT.setAttribute('disabled', true);
@@ -5589,7 +6024,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5589
6024
  }
5590
6025
 
5591
6026
  /** default active buttons [strong, ins, em, del, sub, sup] */
5592
- if (classOnCheck.test(nodeName)) {
6027
+ if (classOnCheck && classOnCheck.test(nodeName)) {
5593
6028
  commandMapNodes.push(nodeName);
5594
6029
  util.addClass(commandMap[nodeName], 'active');
5595
6030
  }
@@ -5663,6 +6098,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5663
6098
 
5664
6099
  onMouseDown_wysiwyg: function (e) {
5665
6100
  if (core.isReadOnly || util.isNonEditable(context.element.wysiwyg)) return;
6101
+ core._editorRange();
5666
6102
 
5667
6103
  // user event
5668
6104
  if (typeof functions.onMouseDown === 'function' && functions.onMouseDown(e, core) === false) return;
@@ -5708,7 +6144,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5708
6144
  }
5709
6145
 
5710
6146
  const figcaption = util.getParentElement(targetElement, 'FIGCAPTION');
5711
- if (util.isNonEditable(figcaption)) {
6147
+ if (figcaption && (util.isNonEditable(figcaption) || !figcaption.getAttribute("contenteditable"))) {
5712
6148
  e.preventDefault();
5713
6149
  figcaption.setAttribute('contenteditable', true);
5714
6150
  figcaption.focus();
@@ -6497,6 +6933,45 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6497
6933
 
6498
6934
  temp = !temp ? newFormat.firstChild : temp.appendChild(newFormat.firstChild);
6499
6935
  core.setRange(temp, 0, temp, 0);
6936
+ break;
6937
+ } else if (options.lineAttrReset && formatEl && !util.isListCell(formatEl)) {
6938
+ e.preventDefault();
6939
+ e.stopPropagation();
6940
+
6941
+ let newEl;
6942
+ if (!range.collapsed) {
6943
+ const isMultiLine = util.getFormatElement(range.startContainer, null) !== util.getFormatElement(range.endContainer, null);
6944
+ const r = core.removeNode();
6945
+ if (isMultiLine) {
6946
+ newEl = util.getFormatElement(r.container, null);
6947
+
6948
+ if (!r.prevContainer) {
6949
+ const newFormat = formatEl.cloneNode(false);
6950
+ newFormat.innerHTML = '<br>';
6951
+ newEl.parentNode.insertBefore(newFormat, newEl);
6952
+ } else if (newEl !== formatEl && newEl.nextElementSibling === formatEl) {
6953
+ newEl = formatEl;
6954
+ }
6955
+ } else {
6956
+ newEl = util.splitElement(r.container, r.offset, 0);
6957
+ }
6958
+ } else {
6959
+ if (util.onlyZeroWidthSpace(formatEl)) newEl = core.appendFormatTag(formatEl, formatEl.cloneNode(false));
6960
+ else newEl = util.splitElement(range.endContainer, range.endOffset, 0);
6961
+ }
6962
+
6963
+ const resetAttr = options.lineAttrReset === '*' ? null : options.lineAttrReset;
6964
+ const attrs = newEl.attributes;
6965
+ let i = 0;
6966
+ while (attrs[i]) {
6967
+ if (resetAttr && resetAttr.test(attrs[i].name)) {
6968
+ i++;
6969
+ continue;
6970
+ }
6971
+ newEl.removeAttribute(attrs[i].name);
6972
+ }
6973
+ core.setRange(newEl.firstChild, 0, newEl.firstChild, 0);
6974
+
6500
6975
  break;
6501
6976
  }
6502
6977
 
@@ -6742,7 +7217,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6742
7217
  onFocus_wysiwyg: function (e) {
6743
7218
  if (core._antiBlur) return;
6744
7219
  core.hasFocus = true;
6745
- event._applyTagEffects();
7220
+ _w.setTimeout(event._applyTagEffects);
6746
7221
 
6747
7222
  if (core._isInline) event._showToolbarInline();
6748
7223
 
@@ -6772,7 +7247,6 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6772
7247
  core.submenuOff();
6773
7248
  core.controllersOff();
6774
7249
 
6775
- const prevHeight = util.getNumber(context.element.wysiwygFrame.style.height, 0);
6776
7250
  core._variable.resizeClientY = e.clientY;
6777
7251
  context.element.resizeBackground.style.display = 'block';
6778
7252
 
@@ -6780,7 +7254,6 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6780
7254
  context.element.resizeBackground.style.display = 'none';
6781
7255
  _d.removeEventListener('mousemove', event._resize_editor);
6782
7256
  _d.removeEventListener('mouseup', closureFunc);
6783
- if (typeof functions.onResizeEditor === 'function') functions.onResizeEditor(util.getNumber(context.element.wysiwygFrame.style.height, 0), prevHeight, core);
6784
7257
  }
6785
7258
 
6786
7259
  _d.addEventListener('mousemove', event._resize_editor);
@@ -6789,35 +7262,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6789
7262
 
6790
7263
  _resize_editor: function (e) {
6791
7264
  const resizeInterval = context.element.editorArea.offsetHeight + (e.clientY - core._variable.resizeClientY);
6792
- context.element.wysiwygFrame.style.height = context.element.code.style.height = (resizeInterval < core._variable.minResizingSize ? core._variable.minResizingSize : resizeInterval) + 'px';
7265
+ const h = (resizeInterval < core._variable.minResizingSize ? core._variable.minResizingSize : resizeInterval);
7266
+ context.element.wysiwygFrame.style.height = context.element.code.style.height = h + 'px';
6793
7267
  core._variable.resizeClientY = e.clientY;
7268
+ if (util.isIE) core.__callResizeFunction(h, null);
6794
7269
  },
6795
7270
 
6796
7271
  onResize_window: function () {
6797
- core.controllersOff();
6798
-
6799
- const responsiveSize = event._responsiveButtonSize;
6800
- if (responsiveSize) {
6801
- let w = 0;
6802
- if ((core._isBalloon || core._isInline) && options.toolbarWidth === 'auto') {
6803
- w = context.element.topArea.offsetWidth;
6804
- } else {
6805
- w = context.element.toolbar.offsetWidth;
6806
- }
6807
-
6808
- let responsiveWidth = 'default';
6809
- for (let i = 1, len = responsiveSize.length; i < len; i++) {
6810
- if (w < responsiveSize[i]) {
6811
- responsiveWidth = responsiveSize[i] + '';
6812
- break;
6813
- }
6814
- }
6815
-
6816
- if (event._responsiveCurrentSize !== responsiveWidth) {
6817
- event._responsiveCurrentSize = responsiveWidth;
6818
- functions.setToolbarButtons(event._responsiveButtons[responsiveWidth]);
6819
- }
6820
- }
7272
+ if (util.isIE) core.resetResponsiveToolbar();
6821
7273
 
6822
7274
  if (context.element.toolbar.offsetWidth === 0) return;
6823
7275
 
@@ -6918,6 +7370,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6918
7370
  },
6919
7371
 
6920
7372
  _codeViewAutoHeight: function () {
7373
+ if (core._variable.isFullScreen) return;
6921
7374
  context.element.code.style.height = context.element.code.scrollHeight + 'px';
6922
7375
  },
6923
7376
 
@@ -6987,6 +7440,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6987
7440
  }
6988
7441
  },
6989
7442
 
7443
+ onSave_wysiwyg: function (content) {
7444
+ // user event
7445
+ if (typeof functions.onSave === 'function') {
7446
+ functions.onSave(content, core);
7447
+ return;
7448
+ }
7449
+ },
7450
+
6990
7451
  onCut_wysiwyg: function (e) {
6991
7452
  const clipboardData = util.isIE ? _w.clipboardData : e.clipboardData;
6992
7453
 
@@ -7088,7 +7549,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7088
7549
  } else {
7089
7550
  cleanData = (plainText === cleanData ? plainText : cleanData).replace(/\n/g, '<br>');
7090
7551
  }
7091
- cleanData = core.cleanHTML(cleanData, core.pasteTagsWhitelistRegExp);
7552
+ cleanData = core.cleanHTML(cleanData, core.pasteTagsWhitelistRegExp, core.pasteTagsBlacklistRegExp);
7092
7553
  } else {
7093
7554
  cleanData = util._HTMLConvertor(plainText).replace(/\n/g, '<br>');
7094
7555
  }
@@ -7121,11 +7582,39 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7121
7582
  }
7122
7583
 
7123
7584
  if (cleanData) {
7585
+ if (util.isListCell(util.getFormatElement(core.getSelectionNode(), null))) {
7586
+ const dom = (_d.createRange().createContextualFragment(cleanData));
7587
+ if (dom.childNodes[0].nodeType === 1) {
7588
+ cleanData = event._convertListCell(dom);
7589
+ }
7590
+ }
7124
7591
  functions.insertHTML(cleanData, true, false);
7125
7592
  return false;
7126
7593
  }
7127
7594
  },
7128
7595
 
7596
+ _convertListCell: function (dom) {
7597
+ const domTree = dom.childNodes;
7598
+ let html = '';
7599
+
7600
+ for (let i = 0, len = domTree.length, node; i < len; i++) {
7601
+ node = domTree[i];
7602
+ if (node.nodeType === 1) {
7603
+ if (util.isFormatElement(node)) {
7604
+ html += '<li>' +(node.innerHTML.trim() || '<br>') + '</li>';
7605
+ } else if (util.isRangeFormatElement(node) && !util.isTable(node)) {
7606
+ html += event._convertListCell(node);
7607
+ } else {
7608
+ html += '<li>' + node.outerHTML + '</li>';
7609
+ }
7610
+ } else {
7611
+ html += '<li>' + (node.textContent || '<br>') + '</li>';
7612
+ }
7613
+ }
7614
+
7615
+ return html;
7616
+ },
7617
+
7129
7618
  onMouseMove_wysiwyg: function (e) {
7130
7619
  if (core.isDisabled || core.isReadOnly) return false;
7131
7620
  const component = util.getParentElement(e.target, util.isComponent);
@@ -7196,8 +7685,15 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7196
7685
  core.history.push(false);
7197
7686
  },
7198
7687
 
7688
+ _resizeObserver: null,
7689
+ _toolbarObserver: null,
7199
7690
  _addEvent: function () {
7200
7691
  const eventWysiwyg = options.iframe ? core._ww : context.element.wysiwyg;
7692
+ if (!util.isIE) {
7693
+ this._resizeObserver = new _w.ResizeObserver(function(entries) {
7694
+ core.__callResizeFunction(-1, entries[0]);
7695
+ });
7696
+ }
7201
7697
 
7202
7698
  /** toolbar event */
7203
7699
  context.element.toolbar.addEventListener('mousedown', event._buttonsEventHandler, false);
@@ -7239,18 +7735,20 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7239
7735
 
7240
7736
  /** resizingBar */
7241
7737
  if (context.element.resizingBar) {
7242
- if (/\d+/.test(options.height)) {
7738
+ if (/\d+/.test(options.height) && options.resizeEnable) {
7243
7739
  context.element.resizingBar.addEventListener('mousedown', event.onMouseDown_resizingBar, false);
7244
7740
  } else {
7245
7741
  util.addClass(context.element.resizingBar, 'se-resizing-none');
7246
7742
  }
7247
7743
  }
7248
7744
 
7249
- /** window event */
7745
+ /** set response toolbar */
7250
7746
  event._setResponsiveToolbar();
7251
- _w.removeEventListener('resize', event.onResize_window);
7252
- _w.removeEventListener('scroll', event.onScroll_window);
7253
7747
 
7748
+ /** responsive toolbar observer */
7749
+ if (!util.isIE) this._toolbarObserver = new _w.ResizeObserver(core.resetResponsiveToolbar);
7750
+
7751
+ /** window event */
7254
7752
  _w.addEventListener('resize', event.onResize_window, false);
7255
7753
  if (options.stickyToolbar > -1) {
7256
7754
  _w.addEventListener('scroll', event.onScroll_window, false);
@@ -7283,7 +7781,6 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7283
7781
  event._lineBreakerBind = null;
7284
7782
 
7285
7783
  eventWysiwyg.removeEventListener('touchstart', event.onMouseDown_wysiwyg, {passive: true, useCapture: false});
7286
-
7287
7784
  eventWysiwyg.removeEventListener('focus', event.onFocus_wysiwyg);
7288
7785
  eventWysiwyg.removeEventListener('blur', event.onBlur_wysiwyg);
7289
7786
 
@@ -7295,6 +7792,16 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7295
7792
  context.element.resizingBar.removeEventListener('mousedown', event.onMouseDown_resizingBar);
7296
7793
  }
7297
7794
 
7795
+ if (event._resizeObserver) {
7796
+ event._resizeObserver.unobserve(context.element.wysiwygFrame);
7797
+ event._resizeObserver = null;
7798
+ }
7799
+
7800
+ if (event._toolbarObserver) {
7801
+ event._toolbarObserver.unobserve(context.element._toolbarShadow);
7802
+ event._toolbarObserver = null;
7803
+ }
7804
+
7298
7805
  _w.removeEventListener('resize', event.onResize_window);
7299
7806
  _w.removeEventListener('scroll', event.onScroll_window);
7300
7807
  },
@@ -7351,6 +7858,13 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7351
7858
  */
7352
7859
  onChange: null,
7353
7860
 
7861
+ /**
7862
+ * @description Event functions
7863
+ * @param {String} contents Current contents
7864
+ * @param {Object} core Core object
7865
+ */
7866
+ onSave: null,
7867
+
7354
7868
  /**
7355
7869
  * @description Event functions (drop, paste)
7356
7870
  * When false is returned, the default behavior is stopped.
@@ -7569,6 +8083,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7569
8083
  */
7570
8084
  onResizeEditor: null,
7571
8085
 
8086
+ /**
8087
+ * @description Called after the "setToolbarButtons" invocation.
8088
+ * Can be used to tweak buttons properties (useful for custom buttons)
8089
+ * @param {Array} buttonList Button list
8090
+ * @param {Object} core Core object
8091
+ */
8092
+ onSetToolbarButtons: null,
8093
+
7572
8094
  /**
7573
8095
  * @description Reset the buttons on the toolbar. (Editor is not reloaded)
7574
8096
  * You cannot set a new plugin for the button.
@@ -7577,10 +8099,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7577
8099
  setToolbarButtons: function (buttonList) {
7578
8100
  core.submenuOff();
7579
8101
  core.containerOff();
8102
+ core.moreLayerOff();
7580
8103
 
7581
8104
  const newToolbar = _Constructor._createToolBar(_d, buttonList, core.plugins, options);
7582
8105
  _responsiveButtons = newToolbar.responsiveButtons;
7583
- core._moreLayerActiveButton = null;
7584
8106
  event._setResponsiveToolbar();
7585
8107
 
7586
8108
  context.element.toolbar.replaceChild(newToolbar._buttonTray, context.element._buttonTray);
@@ -7589,32 +8111,15 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7589
8111
  context.element = newContext.element;
7590
8112
  context.tool = newContext.tool;
7591
8113
  if (options.iframe) context.element.wysiwyg = core._wd.body;
8114
+
8115
+ core.recoverButtonStates();
8116
+
7592
8117
  core._cachingButtons();
7593
8118
  core.history._resetCachingButton();
7594
8119
 
7595
- core.activePlugins = [];
7596
- const oldCallButtons = pluginCallButtons;
7597
- pluginCallButtons = newToolbar.pluginCallButtons;
7598
- let plugin, button, oldButton;
7599
- for (let key in pluginCallButtons) {
7600
- if (!util.hasOwn(pluginCallButtons, key)) continue;
7601
- plugin = plugins[key];
7602
- button = pluginCallButtons[key];
7603
- if (plugin.active && button) {
7604
- oldButton = oldCallButtons[key];
7605
- core.callPlugin(key, null, oldButton || button);
7606
- if (oldButton) {
7607
- button.parentElement.replaceChild(oldButton, button);
7608
- pluginCallButtons[key] = oldButton;
7609
- }
7610
- }
7611
- }
7612
-
7613
8120
  if (core.hasFocus) event._applyTagEffects();
7614
-
7615
- if (core._variable.isCodeView) util.addClass(core._styleCommandMap.codeView, 'active');
7616
- if (core._variable.isFullScreen) util.addClass(core._styleCommandMap.fullScreen, 'active');
7617
- if (util.hasClass(context.element.wysiwyg, 'se-show-block')) util.addClass(core._styleCommandMap.showBlocks, 'active');
8121
+ if (core.isReadOnly) util.setDisabledButtons(true, core.resizingDisabledButtons);
8122
+ if (typeof functions.onSetToolbarButtons === 'function') functions.onSetToolbarButtons(newToolbar._buttonTray.querySelectorAll('button'), core);
7618
8123
  },
7619
8124
 
7620
8125
  /**
@@ -7650,7 +8155,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7650
8155
  const _initHTML = el.wysiwyg.innerHTML;
7651
8156
 
7652
8157
  // set option
7653
- const cons = _Constructor._setOptions(mergeOptions, context, options);
8158
+ const cons = _Constructor._setOptions(mergeOptions, context, options);
7654
8159
 
7655
8160
  if (cons.callButtons) {
7656
8161
  pluginCallButtons = cons.callButtons;
@@ -7726,10 +8231,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7726
8231
  },
7727
8232
 
7728
8233
  /**
7729
- * @description Copying the contents of the editor to the original textarea
8234
+ * @description Copying the contents of the editor to the original textarea and execute onSave callback
7730
8235
  */
7731
8236
  save: function () {
7732
- context.element.originElement.value = core.getContents(false);
8237
+ const contents = core.getContents(false);
8238
+ context.element.originElement.value = contents;
8239
+ event.onSave_wysiwyg(contents, core);
7733
8240
  },
7734
8241
 
7735
8242
  /**
@@ -7822,8 +8329,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7822
8329
  * @param {Boolean} rangeSelection If true, range select the inserted node.
7823
8330
  */
7824
8331
  insertHTML: function (html, notCleaningData, checkCharCount, rangeSelection) {
8332
+ if (!context.element.wysiwygFrame.contains(core.getSelection().focusNode)) core.focus();
8333
+
7825
8334
  if (typeof html === 'string') {
7826
- if (!notCleaningData) html = core.cleanHTML(html, null);
8335
+ if (!notCleaningData) html = core.cleanHTML(html, null, null);
7827
8336
  try {
7828
8337
  const dom = _d.createRange().createContextualFragment(html);
7829
8338
  const domTree = dom.childNodes;
@@ -7900,10 +8409,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7900
8409
  const wysiwyg = context.element.wysiwyg;
7901
8410
  const children = temp.children;
7902
8411
  for (let i = 0, len = children.length; i < len; i++) {
7903
- wysiwyg.appendChild(children[i]);
8412
+ if (children[i]) {
8413
+ wysiwyg.appendChild(children[i]);
8414
+ }
7904
8415
  }
7905
8416
  } else {
7906
- core._setCodeView(core._getCodeView() + '\n' + core.convertHTMLForCodeView(convertValue));
8417
+ core._setCodeView(core._getCodeView() + '\n' + core.convertHTMLForCodeView(convertValue, false));
7907
8418
  }
7908
8419
 
7909
8420
  // history stack
@@ -7917,44 +8428,54 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7917
8428
  readOnly: function (value) {
7918
8429
  core.isReadOnly = value;
7919
8430
 
8431
+ util.setDisabledButtons(!!value, core.resizingDisabledButtons);
8432
+
7920
8433
  if (value) {
8434
+ /** off menus */
8435
+ core.controllersOff();
8436
+ if (core.submenuActiveButton && core.submenuActiveButton.disabled) core.submenuOff();
8437
+ if (core._moreLayerActiveButton && core._moreLayerActiveButton.disabled) core.moreLayerOff();
8438
+ if (core.containerActiveButton && core.containerActiveButton.disabled) core.containerOff();
8439
+ if (core.modalForm) core.plugins.dialog.close.call(core);
8440
+
7921
8441
  context.element.code.setAttribute("readOnly", "true");
8442
+ util.addClass(context.element.wysiwygFrame, 'se-read-only');
7922
8443
  } else {
7923
8444
  context.element.code.removeAttribute("readOnly");
8445
+ util.removeClass(context.element.wysiwygFrame, 'se-read-only');
7924
8446
  }
7925
8447
 
7926
- util.setDisabledButtons(!!value, core.resizingDisabledButtons);
7927
8448
  if (options.codeMirrorEditor) options.codeMirrorEditor.setOption('readOnly', !!value);
7928
8449
  },
7929
8450
 
7930
8451
  /**
7931
8452
  * @description Disable the suneditor
7932
8453
  */
7933
- disabled: function () {
7934
- context.tool.cover.style.display = 'block';
7935
- context.element.wysiwyg.setAttribute('contenteditable', false);
7936
- core.isDisabled = true;
8454
+ disable: function () {
8455
+ this.toolbar.disable();
8456
+ this.wysiwyg.disable();
8457
+ },
7937
8458
 
7938
- if (options.codeMirrorEditor) {
7939
- options.codeMirrorEditor.setOption('readOnly', true);
7940
- } else {
7941
- context.element.code.setAttribute('disabled', 'disabled');
7942
- }
8459
+ /**
8460
+ * @description Provided for backward compatibility and will be removed in 3.0.0 version
8461
+ */
8462
+ disabled: function () {
8463
+ this.disable();
7943
8464
  },
7944
8465
 
7945
8466
  /**
7946
8467
  * @description Enable the suneditor
7947
8468
  */
7948
- enabled: function () {
7949
- context.tool.cover.style.display = 'none';
7950
- context.element.wysiwyg.setAttribute('contenteditable', true);
7951
- core.isDisabled = false;
8469
+ enable: function () {
8470
+ this.toolbar.enable();
8471
+ this.wysiwyg.enable();
8472
+ },
7952
8473
 
7953
- if (options.codeMirrorEditor) {
7954
- options.codeMirrorEditor.setOption('readOnly', false);
7955
- } else {
7956
- context.element.code.removeAttribute('disabled');
7957
- }
8474
+ /**
8475
+ * @description Provided for backward compatibility and will be removed in 3.0.0 version
8476
+ */
8477
+ enabled: function () {
8478
+ this.enable();
7958
8479
  },
7959
8480
 
7960
8481
  /**
@@ -8011,17 +8532,36 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
8011
8532
  /**
8012
8533
  * @description Disable the toolbar
8013
8534
  */
8014
- disabled: function () {
8535
+ disable: function () {
8536
+ /** off menus */
8537
+ core.submenuOff();
8538
+ core.moreLayerOff();
8539
+ core.containerOff();
8540
+
8015
8541
  context.tool.cover.style.display = 'block';
8016
8542
  },
8017
8543
 
8544
+ /**
8545
+ * @description Provided for backward compatibility and will be removed in 3.0.0 version
8546
+ */
8547
+ disabled: function () {
8548
+ this.disable();
8549
+ },
8550
+
8018
8551
  /**
8019
8552
  * @description Enable the toolbar
8020
8553
  */
8021
- enabled: function () {
8554
+ enable: function () {
8022
8555
  context.tool.cover.style.display = 'none';
8023
8556
  },
8024
8557
 
8558
+ /**
8559
+ * @description Provided for backward compatibility and will be removed in 3.0.0 version
8560
+ */
8561
+ enabled: function () {
8562
+ this.enable();
8563
+ },
8564
+
8025
8565
  /**
8026
8566
  * @description Show the toolbar
8027
8567
  */
@@ -8045,7 +8585,44 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
8045
8585
  context.element._stickyDummy.style.display = 'none';
8046
8586
  }
8047
8587
  },
8048
- }
8588
+ },
8589
+
8590
+ /**
8591
+ * @description Wysiwyg methods
8592
+ */
8593
+ wysiwyg: {
8594
+ /**
8595
+ * @description Disable the wysiwyg area
8596
+ */
8597
+ disable: function () {
8598
+ /** off menus */
8599
+ core.controllersOff();
8600
+ if (core.modalForm) core.plugins.dialog.close.call(core);
8601
+
8602
+ context.element.wysiwyg.setAttribute('contenteditable', false);
8603
+ core.isDisabled = true;
8604
+
8605
+ if (options.codeMirrorEditor) {
8606
+ options.codeMirrorEditor.setOption('readOnly', true);
8607
+ } else {
8608
+ context.element.code.setAttribute('disabled', 'disabled');
8609
+ }
8610
+ },
8611
+
8612
+ /**
8613
+ * @description Enable the wysiwyg area
8614
+ */
8615
+ enable: function () {
8616
+ context.element.wysiwyg.setAttribute('contenteditable', true);
8617
+ core.isDisabled = false;
8618
+
8619
+ if (options.codeMirrorEditor) {
8620
+ options.codeMirrorEditor.setOption('readOnly', false);
8621
+ } else {
8622
+ context.element.code.removeAttribute('disabled');
8623
+ }
8624
+ },
8625
+ }
8049
8626
  };
8050
8627
 
8051
8628
  /************ Core init ************/