suneditor 2.41.0 → 2.42.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 (45) hide show
  1. package/README.md +110 -24
  2. package/dist/css/suneditor.min.css +1 -1
  3. package/dist/suneditor.min.js +2 -2
  4. package/package.json +1 -1
  5. package/src/assets/css/suneditor.css +25 -6
  6. package/src/assets/defaultIcons.js +2 -0
  7. package/src/lang/Lang.d.ts +3 -1
  8. package/src/lang/ckb.js +2 -0
  9. package/src/lang/da.js +2 -0
  10. package/src/lang/de.js +2 -0
  11. package/src/lang/en.js +2 -0
  12. package/src/lang/es.js +2 -0
  13. package/src/lang/fr.js +10 -8
  14. package/src/lang/he.js +2 -0
  15. package/src/lang/it.js +2 -0
  16. package/src/lang/ja.js +2 -0
  17. package/src/lang/ko.js +2 -0
  18. package/src/lang/lv.js +2 -0
  19. package/src/lang/nl.js +2 -0
  20. package/src/lang/pl.js +2 -0
  21. package/src/lang/pt_br.js +2 -0
  22. package/src/lang/ro.js +2 -0
  23. package/src/lang/ru.js +2 -0
  24. package/src/lang/se.js +2 -0
  25. package/src/lang/ua.js +2 -0
  26. package/src/lang/zh_cn.js +2 -0
  27. package/src/lib/constructor.js +50 -11
  28. package/src/lib/context.js +4 -1
  29. package/src/lib/core.d.ts +86 -11
  30. package/src/lib/core.js +621 -212
  31. package/src/lib/util.d.ts +24 -1
  32. package/src/lib/util.js +75 -17
  33. package/src/options.d.ts +63 -8
  34. package/src/plugins/dialog/audio.js +6 -5
  35. package/src/plugins/dialog/image.js +31 -20
  36. package/src/plugins/dialog/video.js +14 -13
  37. package/src/plugins/fileBrowser/imageGallery.js +2 -3
  38. package/src/plugins/modules/_anchor.js +6 -4
  39. package/src/plugins/modules/component.d.ts +1 -1
  40. package/src/plugins/modules/fileBrowser.js +6 -1
  41. package/src/plugins/modules/fileManager.js +1 -3
  42. package/src/plugins/modules/resizing.js +11 -6
  43. package/src/plugins/submenu/align.js +32 -27
  44. package/src/plugins/submenu/font.js +1 -1
  45. package/src/plugins/submenu/horizontalRule.js +19 -25
package/src/lib/core.js CHANGED
@@ -39,6 +39,7 @@ 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,
42
43
 
43
44
  /**
44
45
  * @description Document object of the iframe if created as an iframe || _d
@@ -82,7 +83,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
82
83
  /**
83
84
  * @description Computed style of the wysiwyg area (window.getComputedStyle(context.element.wysiwyg))
84
85
  */
85
- wwComputedStyle: _w.getComputedStyle(context.element.wysiwyg),
86
+ wwComputedStyle: null,
86
87
 
87
88
  /**
88
89
  * @description Notice object
@@ -202,12 +203,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
202
203
  /**
203
204
  * @description An array of buttons whose class name is not "se-code-view-enabled"
204
205
  */
205
- codeViewDisabledButtons: null,
206
+ codeViewDisabledButtons: [],
206
207
 
207
208
  /**
208
209
  * @description An array of buttons whose class name is not "se-resizing-enabled"
209
210
  */
210
- resizingDisabledButtons: null,
211
+ resizingDisabledButtons: [],
211
212
 
212
213
  /**
213
214
  * @description active more layer element in submenu
@@ -222,6 +223,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
222
223
  */
223
224
  _htmlCheckWhitelistRegExp: null,
224
225
 
226
+ /**
227
+ * @description Tag blacklist RegExp object used in "_consistencyCheckOfHTML" method
228
+ * @private
229
+ */
230
+ _htmlCheckBlacklistRegExp: null,
231
+
225
232
  /**
226
233
  * @description RegExp when using check disallowd tags. (b, i, ins, strike, s)
227
234
  * @private
@@ -234,12 +241,24 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
234
241
  */
235
242
  editorTagsWhitelistRegExp: null,
236
243
 
244
+ /**
245
+ * @description Editor tags blacklist (RegExp object)
246
+ * util.createTagsBlacklist(options.tagsBlacklist)
247
+ */
248
+ editorTagsBlacklistRegExp: null,
249
+
237
250
  /**
238
251
  * @description Tag whitelist when pasting (RegExp object)
239
252
  * util.createTagsWhitelist(options.pasteTagsWhitelist)
240
253
  */
241
254
  pasteTagsWhitelistRegExp: null,
242
255
 
256
+ /**
257
+ * @description Tag blacklist when pasting (RegExp object)
258
+ * util.createTagsBlacklist(options.pasteTagsBlacklist)
259
+ */
260
+ pasteTagsBlacklistRegExp: null,
261
+
243
262
  /**
244
263
  * @description Boolean value of whether the editor has focus
245
264
  */
@@ -261,12 +280,24 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
261
280
  */
262
281
  _attributesWhitelistRegExp: null,
263
282
 
283
+ /**
284
+ * @description Attributes blacklist used by the cleanHTML method
285
+ * @private
286
+ */
287
+ _attributesBlacklistRegExp: null,
288
+
264
289
  /**
265
290
  * @description Attributes of tags whitelist used by the cleanHTML method
266
291
  * @private
267
292
  */
268
293
  _attributesTagsWhitelist: null,
269
294
 
295
+ /**
296
+ * @description Attributes of tags blacklist used by the cleanHTML method
297
+ * @private
298
+ */
299
+ _attributesTagsBlacklist: null,
300
+
270
301
  /**
271
302
  * @description binded controllersOff method
272
303
  * @private
@@ -414,6 +445,24 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
414
445
  */
415
446
  commandMap: null,
416
447
 
448
+ /**
449
+ * @description CSS properties related to style tags
450
+ * @private
451
+ */
452
+ _commandMapStyles: {
453
+ STRONG: ['font-weight'],
454
+ U: ['text-decoration'],
455
+ EM: ['font-style'],
456
+ DEL: ['text-decoration']
457
+ },
458
+
459
+ /**
460
+ * @description Contains pairs of all "data-commands" and "elements" setted in toolbar over time
461
+ * Used primarily to save and recover button states after the toolbar re-creation
462
+ * Updates each "_cachingButtons()" invocation
463
+ */
464
+ allCommandButtons: null,
465
+
417
466
  /**
418
467
  * @description Style button related to edit area
419
468
  * @property {Element} fullScreen fullScreen button element
@@ -471,6 +520,40 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
471
520
  _lineBreakDir: ''
472
521
  },
473
522
 
523
+ /**
524
+ * @description Save the current buttons states to "allCommandButtons" object
525
+ */
526
+ saveButtonStates: function () {
527
+ if (!this.allCommandButtons) this.allCommandButtons = {};
528
+
529
+ const currentButtons = this.context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]');
530
+ for (let i = 0, element, command; i < currentButtons.length; i++) {
531
+ element = currentButtons[i];
532
+ command = element.getAttribute('data-command');
533
+
534
+ this.allCommandButtons[command] = element;
535
+ }
536
+ },
537
+
538
+ /**
539
+ * @description Recover the current buttons states from "allCommandButtons" object
540
+ */
541
+ recoverButtonStates: function () {
542
+ if (this.allCommandButtons) {
543
+ const currentButtons = this.context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]');
544
+ for (let i = 0, button, command, oldButton; i < currentButtons.length; i++) {
545
+ button = currentButtons[i];
546
+ command = button.getAttribute('data-command');
547
+
548
+ oldButton = this.allCommandButtons[command];
549
+ if (oldButton) {
550
+ button.parentElement.replaceChild(oldButton, button);
551
+ if (this.context.tool[command]) this.context.tool[command] = oldButton;
552
+ }
553
+ }
554
+ }
555
+ },
556
+
474
557
  /**
475
558
  * @description If the plugin is not added, add the plugin and call the 'add' function.
476
559
  * If the plugin is added call callBack function.
@@ -559,7 +642,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
559
642
  },
560
643
 
561
644
  /**
562
- * @description Enabled submenu
645
+ * @description Enable submenu
563
646
  * @param {Element} element Submenu's button element to call
564
647
  */
565
648
  submenuOn: function (element) {
@@ -598,7 +681,19 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
598
681
  },
599
682
 
600
683
  /**
601
- * @description Enabled container
684
+ * @description Disable more layer
685
+ */
686
+ moreLayerOff: function() {
687
+ if (this._moreLayerActiveButton) {
688
+ const layer = context.element.toolbar.querySelector('.' + this._moreLayerActiveButton.getAttribute('data-command'));
689
+ layer.style.display = 'none';
690
+ util.removeClass(this._moreLayerActiveButton, 'on');
691
+ this._moreLayerActiveButton = null;
692
+ }
693
+ },
694
+
695
+ /**
696
+ * @description Enable container
602
697
  * @param {Element} element Container's button element to call
603
698
  */
604
699
  containerOn: function (element) {
@@ -868,14 +963,21 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
868
963
  * @description Focus to wysiwyg area using "native focus function"
869
964
  */
870
965
  nativeFocus: function () {
966
+ this.__focus();
967
+ this._editorRange();
968
+ },
969
+
970
+ /**
971
+ * @description Focus method
972
+ * @private
973
+ */
974
+ __focus: function () {
871
975
  const caption = util.getParentElement(this.getSelectionNode(), 'figcaption');
872
976
  if (caption) {
873
977
  caption.focus();
874
978
  } else {
875
979
  context.element.wysiwyg.focus();
876
980
  }
877
-
878
- this._editorRange();
879
981
  },
880
982
 
881
983
  /**
@@ -980,8 +1082,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
980
1082
  }
981
1083
 
982
1084
  selection.addRange(range);
983
- this._editorRange();
984
- if (options.iframe) this.nativeFocus();
1085
+ this._rangeInfo(range, this.getSelection());
1086
+ if (options.iframe) this.__focus();
985
1087
 
986
1088
  return range;
987
1089
  },
@@ -993,21 +1095,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
993
1095
  this._variable._range = null;
994
1096
  this._variable._selectionNode = null;
995
1097
  if (this.hasFocus) this.getSelection().removeAllRanges();
996
-
997
- const commandMap = this.commandMap;
998
- const activePlugins = this.activePlugins;
999
- for (let key in commandMap) {
1000
- if (!util.hasOwn(commandMap, key)) continue;
1001
- if (activePlugins.indexOf(key) > -1) {
1002
- plugins[key].active.call(this, null);
1003
- } else if (commandMap.OUTDENT && /^OUTDENT$/i.test(key)) {
1004
- commandMap.OUTDENT.setAttribute('disabled', true);
1005
- } else if (commandMap.INDENT && /^INDENT$/i.test(key)) {
1006
- commandMap.INDENT.removeAttribute('disabled');
1007
- } else {
1008
- util.removeClass(commandMap[key], 'active');
1009
- }
1010
- }
1098
+ this._setKeyEffect([]);
1011
1099
  },
1012
1100
 
1013
1101
  /**
@@ -1088,7 +1176,6 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1088
1176
  const selection = this.getSelection();
1089
1177
  if (!selection) return null;
1090
1178
  let range = null;
1091
- let selectionNode = null;
1092
1179
 
1093
1180
  if (selection.rangeCount > 0) {
1094
1181
  range = selection.getRangeAt(0);
@@ -1096,6 +1183,20 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1096
1183
  range = this._createDefaultRange();
1097
1184
  }
1098
1185
 
1186
+ if (util.isFormatElement(range.endContainer) && range.endOffset === 0) {
1187
+ range = this.setRange(range.startContainer, range.startOffset, range.startContainer, range.startContainer.length);
1188
+ }
1189
+
1190
+ this._rangeInfo(range, selection);
1191
+ },
1192
+
1193
+ /**
1194
+ * @description Set "range" and "selection" info.
1195
+ * @param {Object} range range object.
1196
+ * @param {Object} selection selection object.
1197
+ */
1198
+ _rangeInfo: function (range, selection) {
1199
+ let selectionNode = null;
1099
1200
  this._variable._range = range;
1100
1201
 
1101
1202
  if (range.collapsed) {
@@ -1159,19 +1260,19 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1159
1260
 
1160
1261
  if (util.isFormatElement(startCon)) {
1161
1262
  if (!startCon.childNodes[startOff]) {
1162
- startCon = startCon.lastChild;
1263
+ startCon = startCon.lastChild || startCon;
1163
1264
  startOff = startCon.textContent.length;
1164
1265
  } else {
1165
- startCon = startCon.childNodes[startOff];
1266
+ startCon = startCon.childNodes[startOff] || startCon;
1166
1267
  startOff = 0;
1167
1268
  }
1168
1269
  while (startCon && startCon.nodeType === 1 && startCon.firstChild) {
1169
- startCon = startCon.firstChild;
1270
+ startCon = startCon.firstChild || startCon;
1170
1271
  startOff = 0;
1171
1272
  }
1172
1273
  }
1173
1274
  if (util.isFormatElement(endCon)) {
1174
- endCon = endCon.childNodes[endOff] || endCon.lastChild;
1275
+ endCon = endCon.childNodes[endOff] || endCon.lastChild || endCon;
1175
1276
  while (endCon && endCon.nodeType === 1 && endCon.lastChild) {
1176
1277
  endCon = endCon.lastChild;
1177
1278
  }
@@ -1269,7 +1370,6 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1269
1370
  if (util.isWysiwygDiv(range.startContainer)) {
1270
1371
  const children = context.element.wysiwyg.children;
1271
1372
  if (children.length === 0) return [];
1272
-
1273
1373
  this.setRange(children[0], 0, children[children.length - 1], children[children.length - 1].textContent.trim().length);
1274
1374
  range = this.getRange();
1275
1375
  }
@@ -1411,7 +1511,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1411
1511
  * @returns {Element}
1412
1512
  */
1413
1513
  appendFormatTag: function (element, formatNode) {
1414
- if (!element.parentNode) return null;
1514
+ if (!element || !element.parentNode) return null;
1415
1515
 
1416
1516
  const currentFormatEl = util.getFormatElement(this.getSelectionNode(), null);
1417
1517
  let oFormat = null;
@@ -1465,9 +1565,9 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1465
1565
  if (formatEl && util.onlyZeroWidthSpace(formatEl)) util.removeItem(formatEl);
1466
1566
  }
1467
1567
 
1468
- this.setRange(element, 0, element, 0);
1469
-
1470
1568
  if (!notSelect) {
1569
+ this.setRange(element, 0, element, 0);
1570
+
1471
1571
  const fileComponentInfo = this.getFileComponent(element);
1472
1572
  if (fileComponentInfo) {
1473
1573
  this.selectComponent(fileComponentInfo.target, fileComponentInfo.pluginName);
@@ -1606,8 +1706,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
1606
1706
  const startOff = range.startOffset;
1607
1707
  const endOff = range.endOffset;
1608
1708
  const formatRange = range.startContainer === commonCon && util.isFormatElement(commonCon);
1609
- const startCon = formatRange ? (commonCon.childNodes[startOff] || commonCon.childNodes[0]) : range.startContainer;
1610
- const endCon = formatRange ? (commonCon.childNodes[endOff] || commonCon.childNodes[commonCon.childNodes.length - 1]) : range.endContainer;
1709
+ const startCon = formatRange ? (commonCon.childNodes[startOff] || commonCon.childNodes[0] || range.startContainer) : range.startContainer;
1710
+ const endCon = formatRange ? (commonCon.childNodes[endOff] || commonCon.childNodes[commonCon.childNodes.length - 1] || range.endContainer) : range.endContainer;
1611
1711
  let parentNode, originAfter = null;
1612
1712
 
1613
1713
  if (!afterNode) {
@@ -2726,7 +2826,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
2726
2826
  for (let i = endLength - 1, newRange; i > 0; i--) {
2727
2827
  newNode = appendNode.cloneNode(false);
2728
2828
  newRange = this._nodeChange_middleLine(lineNodes[i], newNode, validation, isRemoveFormat, isRemoveNode, _removeCheck, end.container);
2729
- if (newRange.endContainer) {
2829
+ if (newRange.endContainer && newRange.ancestor.contains(newRange.endContainer)) {
2730
2830
  end.ancestor = null;
2731
2831
  end.container = newRange.endContainer;
2732
2832
  }
@@ -4001,7 +4101,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4001
4101
  return {
4002
4102
  ancestor: pNode,
4003
4103
  container: container,
4004
- offset: offset
4104
+ offset: container.nodeType === 1 && offset === 1 ? container.childNodes.length : offset
4005
4105
  };
4006
4106
  },
4007
4107
 
@@ -4014,19 +4114,27 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4014
4114
  actionCall: function (command, display, target) {
4015
4115
  // call plugins
4016
4116
  if (display) {
4017
- if (/more/i.test(display) && target !== this._moreLayerActiveButton) {
4018
- const layer = context.element.toolbar.querySelector('.' + command);
4019
- if (layer) {
4020
- if (this._moreLayerActiveButton) {
4021
- (context.element.toolbar.querySelector('.' + this._moreLayerActiveButton.getAttribute('data-command'))).style.display = 'none';
4022
- util.removeClass(this._moreLayerActiveButton, 'on');
4117
+ if (/more/i.test(display)) {
4118
+ if (target !== this._moreLayerActiveButton) {
4119
+ const layer = context.element.toolbar.querySelector('.' + command);
4120
+ if (layer) {
4121
+ if (this._moreLayerActiveButton) this.moreLayerOff();
4122
+
4123
+ this._moreLayerActiveButton = target;
4124
+ layer.style.display = 'block';
4125
+
4126
+ event._showToolbarBalloon();
4127
+ event._showToolbarInline();
4023
4128
  }
4024
4129
  util.addClass(target, 'on');
4025
- this._moreLayerActiveButton = target;
4026
- layer.style.display = 'block';
4130
+ } else {
4131
+ const layer = context.element.toolbar.querySelector('.' + this._moreLayerActiveButton.getAttribute('data-command'));
4132
+ if (layer) {
4133
+ this.moreLayerOff();
4027
4134
 
4028
- event._showToolbarBalloon();
4029
- event._showToolbarInline();
4135
+ event._showToolbarBalloon();
4136
+ event._showToolbarInline();
4137
+ }
4030
4138
  }
4031
4139
  return;
4032
4140
  }
@@ -4036,7 +4144,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4036
4144
  return;
4037
4145
  }
4038
4146
 
4039
- if (this.isReadOnly) return;
4147
+ if (this.isReadOnly && util.arrayIncludes(this.resizingDisabledButtons, target)) return;
4040
4148
  if (/submenu/.test(display) && (this._menuTray[command] === null || target !== this.submenuActiveButton)) {
4041
4149
  this.callPlugin(command, this.submenuOn.bind(this, target), target);
4042
4150
  return;
@@ -4053,17 +4161,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4053
4161
  this.commandHandler(target, command);
4054
4162
  }
4055
4163
 
4056
- if (/more/i.test(display)) {
4057
- const layer = context.element.toolbar.querySelector('.' + this._moreLayerActiveButton.getAttribute('data-command'));
4058
- if (layer) {
4059
- util.removeClass(this._moreLayerActiveButton, 'on');
4060
- this._moreLayerActiveButton = null;
4061
- layer.style.display = 'none';
4062
-
4063
- event._showToolbarBalloon();
4064
- event._showToolbarInline();
4065
- }
4066
- } else if (/submenu/.test(display)) {
4164
+ if (/submenu/.test(display)) {
4067
4165
  this.submenuOff();
4068
4166
  } else if (!/command/.test(display)) {
4069
4167
  this.submenuOff();
@@ -4087,6 +4185,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4087
4185
  case 'paste':
4088
4186
  break;
4089
4187
  case 'selectAll':
4188
+ this.containerOff();
4090
4189
  const wysiwyg = context.element.wysiwyg;
4091
4190
  let first = util.getChildElement(wysiwyg.firstChild, function (current) { return current.childNodes.length === 0 || current.nodeType === 3; }, false) || wysiwyg.firstChild;
4092
4191
  let last = util.getChildElement(wysiwyg.lastChild, function (current) { return current.childNodes.length === 0 || current.nodeType === 3; }, true) || wysiwyg.lastChild;
@@ -4138,6 +4237,15 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4138
4237
  case 'showBlocks':
4139
4238
  this.toggleDisplayBlocks();
4140
4239
  break;
4240
+ case 'dir':
4241
+ this.setDir(options.rtl ? 'ltr' : 'rtl');
4242
+ break;
4243
+ case 'dir_ltr':
4244
+ this.setDir('ltr');
4245
+ break;
4246
+ case 'dir_rtl':
4247
+ this.setDir('rtl');
4248
+ break;
4141
4249
  case 'save':
4142
4250
  if (typeof options.callBackSave === 'function') {
4143
4251
  options.callBackSave(this.getContents(false), this._variable.isChanged);
@@ -4164,7 +4272,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4164
4272
  removeNode = 'SUB';
4165
4273
  }
4166
4274
 
4167
- this.nodeChange(cmd, null, [removeNode], false);
4275
+ this.nodeChange(cmd, this._commandMapStyles[command] || null, [removeNode], false);
4168
4276
  this.focus();
4169
4277
  }
4170
4278
  },
@@ -4244,7 +4352,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4244
4352
  util.setDisabledButtons(!isCodeView, this.codeViewDisabledButtons);
4245
4353
 
4246
4354
  if (isCodeView) {
4247
- this._setCodeDataToEditor();
4355
+ if (!util.isNonEditable(context.element.wysiwygFrame)) this._setCodeDataToEditor();
4248
4356
  context.element.wysiwygFrame.scrollTop = 0;
4249
4357
  context.element.code.style.display = 'none';
4250
4358
  context.element.wysiwygFrame.style.display = 'block';
@@ -4270,14 +4378,18 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4270
4378
  util.removeClass(this._styleCommandMap.codeView, 'active');
4271
4379
 
4272
4380
  // history stack
4273
- this.history.push(false);
4274
- this.history._resetCachingButton();
4381
+ if (!util.isNonEditable(context.element.wysiwygFrame)) {
4382
+ this.history.push(false);
4383
+ this.history._resetCachingButton();
4384
+ }
4275
4385
  } else {
4276
4386
  this._setEditorDataToCodeView();
4277
4387
  this._variable._codeOriginCssText = this._variable._codeOriginCssText.replace(/(\s?display(\s+)?:(\s+)?)[a-zA-Z]+(?=;)/, 'display: block');
4278
4388
  this._variable._wysiwygOriginCssText = this._variable._wysiwygOriginCssText.replace(/(\s?display(\s+)?:(\s+)?)[a-zA-Z]+(?=;)/, 'display: none');
4279
4389
 
4280
- if (options.height === 'auto' && !options.codeMirrorEditor) context.element.code.style.height = context.element.code.scrollHeight > 0 ? (context.element.code.scrollHeight + 'px') : 'auto';
4390
+ if (this._variable.isFullScreen) context.element.code.style.height = '100%';
4391
+ else if (options.height === 'auto' && !options.codeMirrorEditor) context.element.code.style.height = context.element.code.scrollHeight > 0 ? (context.element.code.scrollHeight + 'px') : 'auto';
4392
+
4281
4393
  if (options.codeMirrorEditor) options.codeMirrorEditor.refresh();
4282
4394
 
4283
4395
  this._variable.isCodeView = true;
@@ -4299,6 +4411,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4299
4411
  }
4300
4412
 
4301
4413
  this._checkPlaceholder();
4414
+ if (this.isReadOnly) util.setDisabledButtons(true, this.resizingDisabledButtons);
4415
+
4302
4416
  // user event
4303
4417
  if (typeof functions.toggleCodeView === 'function') functions.toggleCodeView(this._variable.isCodeView, this);
4304
4418
  },
@@ -4321,7 +4435,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4321
4435
  }
4322
4436
  }
4323
4437
 
4324
- this._wd.head.innerHTML = parseDocument.head.innerHTML;
4438
+ let headers = parseDocument.head.innerHTML;
4439
+ if (!parseDocument.head.querySelector('link[rel="stylesheet"]') || (this.options.height === 'auto' && !parseDocument.head.querySelector('style'))) {
4440
+ headers += util._setIframeCssTags(this.options);
4441
+ }
4442
+
4443
+ this._wd.head.innerHTML = headers;
4325
4444
  this._wd.body.innerHTML = this.convertContentsForEditor(parseDocument.body.innerHTML);
4326
4445
 
4327
4446
  const attrs = parseDocument.body.attributes;
@@ -4345,7 +4464,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4345
4464
  * @private
4346
4465
  */
4347
4466
  _setEditorDataToCodeView: function () {
4348
- const codeContents = this.convertHTMLForCodeView(context.element.wysiwyg);
4467
+ const codeContents = this.convertHTMLForCodeView(context.element.wysiwyg, false);
4349
4468
  let codeValue = '';
4350
4469
 
4351
4470
  if (options.fullPage) {
@@ -4363,7 +4482,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4363
4482
 
4364
4483
  /**
4365
4484
  * @description Changes to full screen or default screen
4366
- * @param {Element} element full screen button
4485
+ * @param {Element|null} element full screen button
4367
4486
  */
4368
4487
  toggleFullScreen: function (element) {
4369
4488
  const topArea = context.element.topArea;
@@ -4373,6 +4492,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4373
4492
  const code = context.element.code;
4374
4493
  const _var = this._variable;
4375
4494
  this.controllersOff();
4495
+
4496
+ const wasToolbarHidden = (toolbar.style.display === 'none' || (this._isInline && !this._inlineToolbarAttr.isShow));
4376
4497
 
4377
4498
  if (!_var.isFullScreen) {
4378
4499
  _var.isFullScreen = true;
@@ -4418,7 +4539,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4418
4539
  _var.innerHeight_fullScreen = (_w.innerHeight - toolbar.offsetHeight);
4419
4540
  editorArea.style.height = (_var.innerHeight_fullScreen - options.fullScreenOffset) + 'px';
4420
4541
 
4421
- util.changeElement(element.firstElementChild, icons.reduction);
4542
+ if (element) util.changeElement(element.firstElementChild, icons.reduction);
4422
4543
 
4423
4544
  if (options.iframe && options.height === 'auto') {
4424
4545
  editorArea.style.overflow = 'auto';
@@ -4437,6 +4558,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4437
4558
  topArea.style.cssText = _var._originCssText;
4438
4559
  _d.body.style.overflow = _var._bodyOverflow;
4439
4560
 
4561
+ if (options.height === 'auto' && !options.codeMirrorEditor) event._codeViewAutoHeight();
4562
+
4440
4563
  if (!!options.toolbarContainer) options.toolbarContainer.appendChild(toolbar);
4441
4564
 
4442
4565
  if (options.stickyToolbar > -1) {
@@ -4455,12 +4578,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4455
4578
  if (!!options.toolbarContainer) util.removeClass(toolbar, 'se-toolbar-balloon');
4456
4579
 
4457
4580
  event.onScroll_window();
4458
- util.changeElement(element.firstElementChild, icons.expansion);
4581
+ if (element) util.changeElement(element.firstElementChild, icons.expansion);
4459
4582
 
4460
4583
  context.element.topArea.style.marginTop = '';
4461
4584
  util.removeClass(this._styleCommandMap.fullScreen, 'active');
4462
4585
  }
4463
4586
 
4587
+ if (wasToolbarHidden) functions.toolbar.hide();
4588
+
4464
4589
  // user event
4465
4590
  if (typeof functions.toggleFullScreen === 'function') functions.toggleFullScreen(this._variable.isFullScreen, this);
4466
4591
  },
@@ -4583,6 +4708,70 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4583
4708
  }
4584
4709
  },
4585
4710
 
4711
+ /**
4712
+ * @description Set direction to "rtl" or "ltr".
4713
+ * @param {String} dir "rtl" or "ltr"
4714
+ */
4715
+ setDir: function (dir) {
4716
+ const rtl = dir === 'rtl';
4717
+ const changeDir = this._prevRtl !== rtl;
4718
+ this._prevRtl = options.rtl = rtl;
4719
+
4720
+ if (changeDir) {
4721
+ // align buttons
4722
+ if (this.plugins.align) {
4723
+ this.plugins.align.exchangeDir.call(this);
4724
+ }
4725
+ // indent buttons
4726
+ if (context.tool.indent) util.changeElement(context.tool.indent.firstElementChild, icons.indent);
4727
+ if (context.tool.outdent) util.changeElement(context.tool.outdent.firstElementChild, icons.outdent);
4728
+ }
4729
+
4730
+ const el = context.element;
4731
+ if (rtl) {
4732
+ util.addClass(el.topArea, 'se-rtl');
4733
+ util.addClass(el.wysiwygFrame, 'se-rtl');
4734
+ } else {
4735
+ util.removeClass(el.topArea, 'se-rtl');
4736
+ util.removeClass(el.wysiwygFrame, 'se-rtl');
4737
+ }
4738
+
4739
+ const lineNodes = util.getListChildren(el.wysiwyg, function (current) {
4740
+ return util.isFormatElement(current) && (current.style.marginRight || current.style.marginLeft || current.style.textAlign);
4741
+ });
4742
+
4743
+ for (let i = 0, len = lineNodes.length, n, l, r; i < len; i++) {
4744
+ n = lineNodes[i];
4745
+ // indent margin
4746
+ r = n.style.marginRight;
4747
+ l = n.style.marginLeft;
4748
+ if (r || l) {
4749
+ n.style.marginRight = l;
4750
+ n.style.marginLeft = r;
4751
+ }
4752
+ // text align
4753
+ r = n.style.textAlign;
4754
+ if (r === 'left') n.style.textAlign = 'right';
4755
+ else if (r === 'right') n.style.textAlign = 'left';
4756
+ }
4757
+
4758
+ const tool = context.tool;
4759
+ if (tool.dir) {
4760
+ util.changeTxt(tool.dir.querySelector('.se-tooltip-text'), lang.toolbar[options.rtl ? 'dir_ltr' : 'dir_rtl']);
4761
+ util.changeElement(tool.dir.firstElementChild, icons[options.rtl ? 'dir_ltr' : 'dir_rtl']);
4762
+ }
4763
+
4764
+ if (tool.dir_ltr) {
4765
+ if (rtl) util.removeClass(tool.dir_ltr, 'active');
4766
+ else util.addClass(tool.dir_ltr, 'active');
4767
+ }
4768
+
4769
+ if (tool.dir_rtl) {
4770
+ if (rtl) util.addClass(tool.dir_rtl, 'active');
4771
+ else util.removeClass(tool.dir_rtl, 'active');
4772
+ }
4773
+ },
4774
+
4586
4775
  /**
4587
4776
  * @description Sets the HTML string
4588
4777
  * @param {String|undefined} html HTML string
@@ -4598,7 +4787,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4598
4787
  // history stack
4599
4788
  this.history.push(false);
4600
4789
  } else {
4601
- const value = this.convertHTMLForCodeView(convertValue);
4790
+ const value = this.convertHTMLForCodeView(convertValue, false);
4602
4791
  this._setCodeView(value);
4603
4792
  }
4604
4793
  },
@@ -4619,7 +4808,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4619
4808
  * @returns {Object}
4620
4809
  */
4621
4810
  getContents: function (onlyContents) {
4622
- const contents = context.element.wysiwyg.innerHTML;
4811
+ const contents = this.convertHTMLForCodeView(context.element.wysiwyg, true);
4623
4812
  const renderHTML = util.createElement('DIV');
4624
4813
  renderHTML.innerHTML = contents;
4625
4814
 
@@ -4702,7 +4891,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4702
4891
  .replace(/\n/g, '')
4703
4892
  .replace(/<(script|style)[\s\S]*>[\s\S]*<\/(script|style)>/gi, '')
4704
4893
  .replace(/<[a-z0-9]+\:[a-z0-9]+[^>^\/]*>[^>]*<\/[a-z0-9]+\:[a-z0-9]+>/gi, '')
4705
- .replace(this.editorTagsWhitelistRegExp, '');
4894
+ .replace(this.editorTagsWhitelistRegExp, '')
4895
+ .replace(this.editorTagsBlacklistRegExp, '');
4706
4896
  },
4707
4897
 
4708
4898
  /**
@@ -4717,18 +4907,28 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4717
4907
  if (/^<[a-z0-9]+\:[a-z0-9]+/i.test(m)) return m;
4718
4908
 
4719
4909
  let v = null;
4720
- const tAttr = this._attributesTagsWhitelist[t.match(/(?!<)[a-zA-Z0-9\-]+/)[0].toLowerCase()];
4721
- if (tAttr) v = m.match(tAttr);
4910
+ const tagName = t.match(/(?!<)[a-zA-Z0-9\-]+/)[0].toLowerCase();
4911
+
4912
+ // blacklist
4913
+ const bAttr = this._attributesTagsBlacklist[tagName];
4914
+ if (bAttr) m = m.replace(bAttr, '');
4915
+ else m = m.replace(this._attributesBlacklistRegExp, '');
4916
+
4917
+ // whitelist
4918
+ const wAttr = this._attributesTagsWhitelist[tagName];
4919
+ if (wAttr) v = m.match(wAttr);
4722
4920
  else v = m.match(this._attributesWhitelistRegExp);
4723
4921
 
4922
+ // anchor
4724
4923
  if (!lowLevelCheck || /<a\b/i.test(t)) {
4725
- const sv = m.match(/id\s*=\s*(?:"|')[^"']*(?:"|')/);
4924
+ const sv = m.match(/(?:(?:id|name)\s*=\s*(?:"|')[^"']*(?:"|'))/g);
4726
4925
  if (sv) {
4727
4926
  if (!v) v = [];
4728
4927
  v.push(sv[0]);
4729
4928
  }
4730
4929
  }
4731
4930
 
4931
+ // span
4732
4932
  if ((!lowLevelCheck || /<span/i.test(t)) && (!v || !/style=/i.test(v.toString()))) {
4733
4933
  const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
4734
4934
  if (sv) {
@@ -4737,10 +4937,33 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4737
4937
  }
4738
4938
  }
4739
4939
 
4940
+ // img
4941
+ if (/<img/i.test(t)) {
4942
+ let w = '', h = '';
4943
+ const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
4944
+ if (!v) v = [];
4945
+ if (sv) {
4946
+ w = sv[0].match(/width:(.+);/);
4947
+ w = util.getNumber(w ? w[1] : '', -1) || '';
4948
+ h = sv[0].match(/height:(.+);/);
4949
+ h = util.getNumber(h ? h[1] : '', -1) || '';
4950
+ }
4951
+
4952
+ if (!w || !h) {
4953
+ const avw = m.match(/width\s*=\s*((?:"|')[^"']*(?:"|'))/);
4954
+ const avh = m.match(/height\s*=\s*((?:"|')[^"']*(?:"|'))/);
4955
+ if (avw || avh) {
4956
+ w = !w ? util.getNumber(avw ? avw[1] : '') || '' : w;
4957
+ h = !h ? util.getNumber(avh ? avh[1] : '') || '' : h;
4958
+ }
4959
+ }
4960
+ v.push('data-origin="' + (w + ',' + h) + '"');
4961
+ }
4962
+
4740
4963
  if (v) {
4741
4964
  for (let i = 0, len = v.length; i < len; i++) {
4742
4965
  if (lowLevelCheck && /^class="(?!(__se__|se-|katex))/.test(v[i])) continue;
4743
- t += ' ' + (/^href\s*=\s*('|"|\s)*javascript\s*\:/i.test(v[i]) ? '' : v[i]);
4966
+ t += ' ' + (/^(?:href|src)\s*=\s*('|"|\s)*javascript\s*\:/i.test(v[i]) ? '' : v[i]);
4744
4967
  }
4745
4968
  }
4746
4969
 
@@ -4752,14 +4975,16 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4752
4975
  * @param {String} html HTML string
4753
4976
  * @param {String|RegExp|null} whitelist Regular expression of allowed tags.
4754
4977
  * RegExp object is create by util.createTagsWhitelist method. (core.pasteTagsWhitelistRegExp)
4978
+ * @param {String|RegExp|null} blacklist Regular expression of disallowed tags.
4979
+ * RegExp object is create by util.createTagsBlacklist method. (core.pasteTagsBlacklistRegExp)
4755
4980
  * @returns {String}
4756
4981
  */
4757
- cleanHTML: function (html, whitelist) {
4982
+ cleanHTML: function (html, whitelist, blacklist) {
4758
4983
  html = this._deleteDisallowedTags(this._parser.parseFromString(html, 'text/html').body.innerHTML).replace(/(<[a-zA-Z0-9\-]+)[^>]*(?=>)/g, this._cleanTags.bind(this, true));
4759
4984
 
4760
4985
  const dom = _d.createRange().createContextualFragment(html);
4761
4986
  try {
4762
- util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, true);
4987
+ util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, true);
4763
4988
  } catch (error) {
4764
4989
  console.warn('[SUNEDITOR.cleanHTML.consistencyCheck.fail] ' + error);
4765
4990
  }
@@ -4795,7 +5020,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4795
5020
  }
4796
5021
 
4797
5022
  cleanHTML = util.htmlRemoveWhiteSpace(cleanHTML);
4798
- return this._tagConvertor(!cleanHTML ? html : !whitelist ? cleanHTML : cleanHTML.replace(typeof whitelist === 'string' ? util.createTagsWhitelist(whitelist) : whitelist, ''));
5023
+ if (!cleanHTML) {
5024
+ cleanHTML = html;
5025
+ } else {
5026
+ if (whitelist) cleanHTML = cleanHTML.replace(typeof whitelist === 'string' ? util.createTagsWhitelist(whitelist) : whitelist, '');
5027
+ if (blacklist) cleanHTML = cleanHTML.replace(typeof blacklist === 'string' ? util.createTagsBlacklist(blacklist) : blacklist, '');
5028
+ }
5029
+
5030
+ return this._tagConvertor(cleanHTML);
4799
5031
  },
4800
5032
 
4801
5033
  /**
@@ -4808,7 +5040,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4808
5040
  const dom = _d.createRange().createContextualFragment(contents);
4809
5041
 
4810
5042
  try {
4811
- util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, false);
5043
+ util._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, false);
4812
5044
  } catch (error) {
4813
5045
  console.warn('[SUNEDITOR.convertContentsForEditor.consistencyCheck.fail] ' + error);
4814
5046
  }
@@ -4829,8 +5061,20 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4829
5061
 
4830
5062
  const domTree = dom.childNodes;
4831
5063
  let cleanHTML = '';
4832
- for (let i = 0, len = domTree.length; i < len; i++) {
4833
- cleanHTML += this._makeLine(domTree[i], true);
5064
+ for (let i = 0, t, p; i < domTree.length; i++) {
5065
+ t = domTree[i];
5066
+ if (!util.isFormatElement(t) && !util.isComponent(t) && !util.isMedia(t)) {
5067
+ if (!p) p = util.createElement(options.defaultTag);
5068
+ p.appendChild(t);
5069
+ i--;
5070
+ if (domTree[i + 1] && !util.isFormatElement(domTree[i + 1])) {
5071
+ continue;
5072
+ } else {
5073
+ t = p;
5074
+ p = null;
5075
+ }
5076
+ }
5077
+ cleanHTML += this._makeLine(t, true);
4834
5078
  }
4835
5079
 
4836
5080
  if (cleanHTML.length === 0) return '<' + options.defaultTag + '><br></' + options.defaultTag + '>';
@@ -4842,28 +5086,30 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4842
5086
  /**
4843
5087
  * @description Converts wysiwyg area element into a format that can be placed in an editor of code view mode
4844
5088
  * @param {Element|String} html WYSIWYG element (context.element.wysiwyg) or HTML string.
5089
+ * @param {Boolean} comp If true, does not line break and indentation of tags.
4845
5090
  * @returns {String}
4846
5091
  */
4847
- convertHTMLForCodeView: function (html) {
5092
+ convertHTMLForCodeView: function (html, comp) {
4848
5093
  let returnHTML = '';
4849
5094
  const wRegExp = _w.RegExp;
4850
5095
  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');
4851
5096
  const wDoc = typeof html === 'string' ? _d.createRange().createContextualFragment(html) : html;
4852
5097
  const isFormat = function (current) { return this.isFormatElement(current) || this.isComponent(current); }.bind(util);
5098
+ const brChar = comp ? '' : '\n';
4853
5099
 
4854
- let indentSize = this._variable.codeIndent * 1;
5100
+ let indentSize = comp ? 0 : this._variable.codeIndent * 1;
4855
5101
  indentSize = indentSize > 0 ? new _w.Array(indentSize + 1).join(' ') : '';
4856
5102
 
4857
- (function recursionFunc (element, indent, lineBR) {
5103
+ (function recursionFunc (element, indent) {
4858
5104
  const children = element.childNodes;
4859
5105
  const elementRegTest = brReg.test(element.nodeName);
4860
5106
  const elementIndent = (elementRegTest ? indent : '');
4861
5107
 
4862
- for (let i = 0, len = children.length, node, br, nodeRegTest, tag, tagIndent; i < len; i++) {
5108
+ for (let i = 0, len = children.length, node, br, lineBR, nodeRegTest, tag, tagIndent; i < len; i++) {
4863
5109
  node = children[i];
4864
5110
  nodeRegTest = brReg.test(node.nodeName);
4865
- br = nodeRegTest ? '\n' : '';
4866
- lineBR = isFormat(node) && !elementRegTest && !/^(TH|TD)$/i.test(element.nodeName) ? '\n' : '';
5111
+ br = nodeRegTest ? brChar : '';
5112
+ lineBR = isFormat(node) && !elementRegTest && !/^(TH|TD)$/i.test(element.nodeName) ? brChar : '';
4867
5113
 
4868
5114
  if (node.nodeType === 8) {
4869
5115
  returnHTML += '\n<!-- ' + node.textContent.trim() + ' -->' + br;
@@ -4874,7 +5120,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4874
5120
  continue;
4875
5121
  }
4876
5122
  if (node.childNodes.length === 0) {
4877
- returnHTML += (/^HR$/i.test(node.nodeName) ? '\n' : '') + (/^PRE$/i.test(node.parentElement.nodeName) && /^BR$/i.test(node.nodeName) ? '' : elementIndent) + node.outerHTML + br;
5123
+ returnHTML += (/^HR$/i.test(node.nodeName) ? brChar : '') + (/^PRE$/i.test(node.parentElement.nodeName) && /^BR$/i.test(node.nodeName) ? '' : elementIndent) + node.outerHTML + br;
4878
5124
  continue;
4879
5125
  }
4880
5126
 
@@ -4885,12 +5131,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
4885
5131
  tagIndent = elementIndent || nodeRegTest ? indent : '';
4886
5132
  returnHTML += (lineBR || (elementRegTest ? '' : br)) + tagIndent + node.outerHTML.match(wRegExp('<' + tag + '[^>]*>', 'i'))[0] + br;
4887
5133
  recursionFunc(node, indent + indentSize, '');
4888
- returnHTML += (/\n$/.test(returnHTML) ? tagIndent : '') + '</' + tag + '>' + (lineBR || br || elementRegTest ? '\n' : '' || /^(TH|TD)$/i.test(node.nodeName) ? '\n' : '');
5134
+ returnHTML += (/\n$/.test(returnHTML) ? tagIndent : '') + '</' + tag + '>' + (lineBR || br || elementRegTest ? brChar : '' || /^(TH|TD)$/i.test(node.nodeName) ? brChar : '');
4889
5135
  }
4890
5136
  }
4891
- }(wDoc, '', '\n'));
5137
+ }(wDoc, ''));
4892
5138
 
4893
- return returnHTML.trim() + '\n';
5139
+ return returnHTML.trim() + brChar;
4894
5140
  },
4895
5141
 
4896
5142
  /**
@@ -5057,6 +5303,28 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5057
5303
  return options.codeMirrorEditor ? options.codeMirrorEditor.getDoc().getValue() : context.element.code.value;
5058
5304
  },
5059
5305
 
5306
+ /**
5307
+ * @description remove class, display text.
5308
+ * @param {Array|null} ignoredList Igonred button list
5309
+ */
5310
+ _setKeyEffect: function (ignoredList) {
5311
+ const commandMap = this.commandMap;
5312
+ const activePlugins = this.activePlugins;
5313
+
5314
+ for (let key in commandMap) {
5315
+ if (ignoredList.indexOf(key) > -1 || !util.hasOwn(commandMap, key)) continue;
5316
+ if (activePlugins.indexOf(key) > -1) {
5317
+ plugins[key].active.call(this, null);
5318
+ } else if (commandMap.OUTDENT && /^OUTDENT$/i.test(key)) {
5319
+ if (!util.isImportantDisabled(commandMap.OUTDENT)) commandMap.OUTDENT.setAttribute('disabled', true);
5320
+ } else if (commandMap.INDENT && /^INDENT$/i.test(key)) {
5321
+ if (!util.isImportantDisabled(commandMap.INDENT)) commandMap.INDENT.removeAttribute('disabled');
5322
+ } else {
5323
+ util.removeClass(commandMap[key], 'active');
5324
+ }
5325
+ }
5326
+ },
5327
+
5060
5328
  /**
5061
5329
  * @description Initializ core variable
5062
5330
  * @param {Boolean} reload Is relooad?
@@ -5068,6 +5336,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5068
5336
  this._ww = options.iframe ? context.element.wysiwygFrame.contentWindow : _w;
5069
5337
  this._wd = _d;
5070
5338
  this._charTypeHTML = options.charCounterType === 'byte-html';
5339
+ this.wwComputedStyle = _w.getComputedStyle(context.element.wysiwyg);
5071
5340
 
5072
5341
  if (!options.iframe && typeof _w.ShadowRoot === 'function') {
5073
5342
  let child = context.element.wysiwygFrame;
@@ -5093,30 +5362,56 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5093
5362
  this._disallowedTextTagsRegExp = disallowTextTags.length === 0 ? null : new wRegExp('(<\\/?)(' + disallowTextTags.join('|') + ')\\b\\s*([^>^<]+)?\\s*(?=>)', 'gi');
5094
5363
 
5095
5364
  // set whitelist
5365
+ const getRegList = function (str, str2) { return !str ? '^' : (str === '*' ? '[a-z-]+' : (!str2 ? str : (str + '|' + str2))); };
5366
+ // tags
5096
5367
  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';
5097
- this._allowHTMLComments = options._editorTagsWhitelist.indexOf('//') > -1;
5098
- this._htmlCheckWhitelistRegExp = new wRegExp('^(' + options._editorTagsWhitelist.replace('|//', '') + ')$', 'i');
5099
- this.editorTagsWhitelistRegExp = util.createTagsWhitelist(options._editorTagsWhitelist.replace('|//', '|<!--|-->'));
5100
- this.pasteTagsWhitelistRegExp = util.createTagsWhitelist(options.pasteTagsWhitelist);
5101
-
5368
+ this._allowHTMLComments = options._editorTagsWhitelist.indexOf('//') > -1 || options._editorTagsWhitelist === '*';
5369
+ // html check
5370
+ this._htmlCheckWhitelistRegExp = new wRegExp('^(' + getRegList(options._editorTagsWhitelist.replace('|//', ''), '') + ')$', 'i');
5371
+ this._htmlCheckBlacklistRegExp = new wRegExp('^(' + (options.tagsBlacklist || '^') + ')$', 'i');
5372
+ // tags
5373
+ this.editorTagsWhitelistRegExp = util.createTagsWhitelist(getRegList(options._editorTagsWhitelist.replace('|//', '|<!--|-->'), ''));
5374
+ this.editorTagsBlacklistRegExp = util.createTagsBlacklist(options.tagsBlacklist.replace('|//', '|<!--|-->'));
5375
+ // paste tags
5376
+ this.pasteTagsWhitelistRegExp = util.createTagsWhitelist(getRegList(options.pasteTagsWhitelist, ''));
5377
+ this.pasteTagsBlacklistRegExp = util.createTagsBlacklist(options.pasteTagsBlacklist);
5378
+ // attributes
5102
5379
  const regEndStr = '\\s*=\\s*(\")[^\"]*\\1';
5103
- const _attr = options.attributesWhitelist;
5104
- const tagsAttr = {};
5380
+ const _wAttr = options.attributesWhitelist;
5381
+ let tagsAttr = {};
5105
5382
  let allAttr = '';
5106
- if (!!_attr) {
5107
- for (let k in _attr) {
5108
- if (!util.hasOwn(_attr, k) || /^on[a-z]+$/i.test(_attr[k])) continue;
5383
+ if (!!_wAttr) {
5384
+ for (let k in _wAttr) {
5385
+ if (!util.hasOwn(_wAttr, k) || /^on[a-z]+$/i.test(_wAttr[k])) continue;
5109
5386
  if (k === 'all') {
5110
- allAttr = _attr[k] + '|';
5387
+ allAttr = getRegList(_wAttr[k], defaultAttr);
5111
5388
  } else {
5112
- tagsAttr[k] = new wRegExp('(?:' + _attr[k] + '|' + defaultAttr + ')' + regEndStr, 'ig');
5389
+ tagsAttr[k] = new wRegExp('\\s(?:' + getRegList(_wAttr[k], '') + ')' + regEndStr, 'ig');
5113
5390
  }
5114
5391
  }
5115
5392
  }
5116
-
5117
- this._attributesWhitelistRegExp = new wRegExp('(?:' + allAttr + defaultAttr + ')' + regEndStr, 'ig');
5393
+
5394
+ this._attributesWhitelistRegExp = new wRegExp('\\s(?:' + (allAttr || defaultAttr) + ')' + regEndStr, 'ig');
5118
5395
  this._attributesTagsWhitelist = tagsAttr;
5119
5396
 
5397
+ // blacklist
5398
+ const _bAttr = options.attributesBlacklist;
5399
+ tagsAttr = {};
5400
+ allAttr = '';
5401
+ if (!!_bAttr) {
5402
+ for (let k in _bAttr) {
5403
+ if (!util.hasOwn(_bAttr, k)) continue;
5404
+ if (k === 'all') {
5405
+ allAttr = getRegList(_bAttr[k], '');
5406
+ } else {
5407
+ tagsAttr[k] = new wRegExp('\\s(?:' + getRegList(_bAttr[k], '') + ')' + regEndStr, 'ig');
5408
+ }
5409
+ }
5410
+ }
5411
+
5412
+ this._attributesBlacklistRegExp = new wRegExp('\\s(?:' + (allAttr || '^') + ')' + regEndStr, 'ig');
5413
+ this._attributesTagsBlacklist = tagsAttr;
5414
+
5120
5415
  // set modes
5121
5416
  this._isInline = /inline/i.test(options.mode);
5122
5417
  this._isBalloon = /balloon|balloon-always/i.test(options.mode);
@@ -5194,6 +5489,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5194
5489
  }
5195
5490
 
5196
5491
  this._initWysiwygArea(reload, _initHTML);
5492
+ this.setDir(options.rtl ? 'rtl' : 'ltr');
5197
5493
  },
5198
5494
 
5199
5495
  /**
@@ -5201,10 +5497,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5201
5497
  * @private
5202
5498
  */
5203
5499
  _cachingButtons: function () {
5204
- _w.setTimeout(function () {
5205
- this.codeViewDisabledButtons = context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]:not([class~="se-code-view-enabled"])');
5206
- this.resizingDisabledButtons = context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]:not([class~="se-resizing-enabled"]):not([data-display="MORE"])');
5207
- }.bind(this));
5500
+ this.codeViewDisabledButtons = context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]:not([class~="se-code-view-enabled"]):not([data-display="MORE"])');
5501
+ this.resizingDisabledButtons = context.element._buttonTray.querySelectorAll('.se-menu-list button[data-display]:not([class~="se-resizing-enabled"]):not([data-display="MORE"])');
5502
+
5503
+ this.saveButtonStates();
5208
5504
 
5209
5505
  const tool = context.tool;
5210
5506
  this.commandMap = {
@@ -5303,7 +5599,15 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5303
5599
  let focusNode, offset, format;
5304
5600
 
5305
5601
  const fileComponent = util.getParentElement(commonCon, util.isComponent);
5306
- if (fileComponent && !util.isTable(fileComponent)) return;
5602
+ if (fileComponent && !util.isTable(fileComponent)) {
5603
+ return;
5604
+ } else if (commonCon.nodeType === 1 && commonCon.getAttribute('data-se-embed') === 'true') {
5605
+ let el = commonCon.nextElementSibling;
5606
+ if (!util.isFormatElement(el)) el = this.appendFormatTag(commonCon, options.defaultTag);
5607
+ this.setRange(el.firstChild, 0, el.firstChild, 0);
5608
+ return;
5609
+ }
5610
+
5307
5611
  if ((util.isRangeFormatElement(startCon) || util.isWysiwygDiv(startCon)) && (util.isComponent(startCon.children[range.startOffset]) || util.isComponent(startCon.children[range.startOffset - 1]))) return;
5308
5612
  if (util.getParentElement(commonCon, util.isNotCheckingNode)) return null;
5309
5613
 
@@ -5549,16 +5853,18 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5549
5853
  currentNodes.push(nodeName);
5550
5854
 
5551
5855
  /* Active plugins */
5552
- for (let c = 0, name; c < cLen; c++) {
5553
- name = activePlugins[c];
5554
- if (commandMapNodes.indexOf(name) === -1 && plugins[name].active.call(core, element)) {
5555
- commandMapNodes.push(name);
5856
+ if (!core.isReadOnly) {
5857
+ for (let c = 0, name; c < cLen; c++) {
5858
+ name = activePlugins[c];
5859
+ if (commandMapNodes.indexOf(name) === -1 && plugins[name].active.call(core, element)) {
5860
+ commandMapNodes.push(name);
5861
+ }
5556
5862
  }
5557
5863
  }
5558
5864
 
5559
5865
  if (util.isFormatElement(element)) {
5560
5866
  /* Outdent */
5561
- if (commandMapNodes.indexOf('OUTDENT') === -1 && commandMap.OUTDENT) {
5867
+ if (commandMapNodes.indexOf('OUTDENT') === -1 && commandMap.OUTDENT && !util.isImportantDisabled(commandMap.OUTDENT)) {
5562
5868
  if (util.isListCell(element) || (element.style[marginDir] && util.getNumber(element.style[marginDir], 0) > 0)) {
5563
5869
  commandMapNodes.push('OUTDENT');
5564
5870
  commandMap.OUTDENT.removeAttribute('disabled');
@@ -5566,7 +5872,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5566
5872
  }
5567
5873
 
5568
5874
  /* Indent */
5569
- if (commandMapNodes.indexOf('INDENT') === -1 && commandMap.INDENT) {
5875
+ if (commandMapNodes.indexOf('INDENT') === -1 && commandMap.INDENT && !util.isImportantDisabled(commandMap.INDENT)) {
5570
5876
  commandMapNodes.push('INDENT');
5571
5877
  if (util.isListCell(element) && !element.previousElementSibling) {
5572
5878
  commandMap.INDENT.setAttribute('disabled', true);
@@ -5585,19 +5891,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5585
5891
  }
5586
5892
  }
5587
5893
 
5588
- /** remove class, display text */
5589
- for (let key in commandMap) {
5590
- if (commandMapNodes.indexOf(key) > -1 || !util.hasOwn(commandMap, key)) continue;
5591
- if (activePlugins.indexOf(key) > -1) {
5592
- plugins[key].active.call(core, null);
5593
- } else if (commandMap.OUTDENT && /^OUTDENT$/i.test(key)) {
5594
- commandMap.OUTDENT.setAttribute('disabled', true);
5595
- } else if (commandMap.INDENT && /^INDENT$/i.test(key)) {
5596
- commandMap.INDENT.removeAttribute('disabled');
5597
- } else {
5598
- util.removeClass(commandMap[key], 'active');
5599
- }
5600
- }
5894
+ core._setKeyEffect(commandMapNodes);
5601
5895
 
5602
5896
  /** save current nodes */
5603
5897
  core._variable.currentNodes = currentNodes.reverse();
@@ -5664,7 +5958,8 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5664
5958
  },
5665
5959
 
5666
5960
  onMouseDown_wysiwyg: function (e) {
5667
- if (util.isNonEditable(context.element.wysiwyg)) return;
5961
+ if (core.isReadOnly || util.isNonEditable(context.element.wysiwyg)) return;
5962
+ core._editorRange();
5668
5963
 
5669
5964
  // user event
5670
5965
  if (typeof functions.onMouseDown === 'function' && functions.onMouseDown(e, core) === false) return;
@@ -5687,12 +5982,16 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5687
5982
  },
5688
5983
 
5689
5984
  onClick_wysiwyg: function (e) {
5985
+ const targetElement = e.target;
5986
+
5690
5987
  if (core.isReadOnly) {
5691
5988
  e.preventDefault();
5989
+ if (util.isAnchor(targetElement)){
5990
+ _w.open(targetElement.href, targetElement.target);
5991
+ }
5692
5992
  return false;
5693
5993
  }
5694
5994
 
5695
- const targetElement = e.target;
5696
5995
  if (util.isNonEditable(context.element.wysiwyg)) return;
5697
5996
 
5698
5997
  // user event
@@ -5939,7 +6238,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5939
6238
  },
5940
6239
 
5941
6240
  onInput_wysiwyg: function (e) {
5942
- if (core.isReadOnly) {
6241
+ if (core.isReadOnly || core.isDisabled) {
5943
6242
  e.preventDefault();
5944
6243
  e.stopPropagation();
5945
6244
  core.history.go(core.history.getCurrentIndex());
@@ -5994,17 +6293,17 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
5994
6293
 
5995
6294
  _onShortcutKey: false,
5996
6295
  onKeyDown_wysiwyg: function (e) {
5997
- if (core.isReadOnly) {
5998
- e.preventDefault();
5999
- return false;
6000
- }
6001
-
6002
6296
  const keyCode = e.keyCode;
6003
6297
  const shift = e.shiftKey;
6004
6298
  const ctrl = e.ctrlKey || e.metaKey || keyCode === 91 || keyCode === 92 || keyCode === 224;
6005
6299
  const alt = e.altKey;
6006
6300
  event._IEisComposing = keyCode === 229;
6007
6301
 
6302
+ if (!ctrl && core.isReadOnly && !event._directionKeyCode.test(keyCode)) {
6303
+ e.preventDefault();
6304
+ return false;
6305
+ }
6306
+
6008
6307
  core.submenuOff();
6009
6308
 
6010
6309
  if (core._isBalloon) {
@@ -6282,13 +6581,18 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6282
6581
  }
6283
6582
 
6284
6583
  if (!selectRange && (core.isEdgePoint(range.endContainer, range.endOffset) || (selectionNode === formatEl ? !!formatEl.childNodes[range.startOffset] : false))) {
6285
- const sel = selectionNode === formatEl ? formatEl.childNodes[range.startOffset] : selectionNode;
6584
+ const sel = selectionNode === formatEl ? formatEl.childNodes[range.startOffset] || selectionNode : selectionNode;
6286
6585
  // delete nonEditable
6287
- if (util.isNonEditable(sel.nextSibling)) {
6586
+ if (sel && util.isNonEditable(sel.nextSibling)) {
6288
6587
  e.preventDefault();
6289
6588
  e.stopPropagation();
6290
6589
  util.removeItem(sel.nextSibling);
6291
6590
  break;
6591
+ } else if (util.isComponent(sel)) {
6592
+ e.preventDefault();
6593
+ e.stopPropagation();
6594
+ util.removeItem(sel);
6595
+ break;
6292
6596
  }
6293
6597
  }
6294
6598
 
@@ -6490,6 +6794,44 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6490
6794
 
6491
6795
  temp = !temp ? newFormat.firstChild : temp.appendChild(newFormat.firstChild);
6492
6796
  core.setRange(temp, 0, temp, 0);
6797
+ break;
6798
+ } else if (options.lineAttrReset && formatEl) {
6799
+ e.preventDefault();
6800
+ e.stopPropagation();
6801
+
6802
+ let newEl;
6803
+ if (!range.collapsed) {
6804
+ const isMultiLine = util.getFormatElement(range.startContainer, null) !== util.getFormatElement(range.endContainer, null);
6805
+ const r = core.removeNode();
6806
+ if (isMultiLine) {
6807
+ newEl = util.getFormatElement(r.container, null);
6808
+
6809
+ if (!r.prevContainer) {
6810
+ const newFormat = formatEl.cloneNode(false);
6811
+ newFormat.innerHTML = '<br>';
6812
+ newEl.parentNode.insertBefore(newFormat, newEl);
6813
+ } else if (newEl !== formatEl && newEl.nextElementSibling === formatEl) {
6814
+ newEl = formatEl;
6815
+ }
6816
+ } else {
6817
+ newEl = util.splitElement(r.container, r.offset, 0);
6818
+ }
6819
+ } else {
6820
+ newEl = util.splitElement(range.endContainer, range.endOffset, 0);
6821
+ }
6822
+
6823
+ const resetAttr = options.lineAttrReset === '*' ? null : options.lineAttrReset;
6824
+ const attrs = newEl.attributes;
6825
+ let i = 0;
6826
+ while (attrs[i]) {
6827
+ if (resetAttr && resetAttr.test(attrs[i].name)) {
6828
+ i++;
6829
+ continue;
6830
+ }
6831
+ newEl.removeAttribute(attrs[i].name);
6832
+ }
6833
+ core.setRange(newEl.firstChild, 0, newEl.firstChild, 0);
6834
+
6493
6835
  break;
6494
6836
  }
6495
6837
 
@@ -6651,13 +6993,19 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6651
6993
  },
6652
6994
 
6653
6995
  onKeyUp_wysiwyg: function (e) {
6654
- if (core.isReadOnly || event._onShortcutKey) return;
6655
- core._editorRange();
6996
+ if (event._onShortcutKey) return;
6656
6997
 
6657
- const range = core.getRange();
6998
+ core._editorRange();
6658
6999
  const keyCode = e.keyCode;
6659
7000
  const ctrl = e.ctrlKey || e.metaKey || keyCode === 91 || keyCode === 92 || keyCode === 224;
6660
7001
  const alt = e.altKey;
7002
+
7003
+ if (core.isReadOnly) {
7004
+ if (!ctrl && event._directionKeyCode.test(keyCode)) event._applyTagEffects();
7005
+ return;
7006
+ }
7007
+
7008
+ const range = core.getRange();
6661
7009
  let selectionNode = core.getSelectionNode();
6662
7010
 
6663
7011
  if (core._isBalloon && ((core._isBalloonAlways && keyCode !== 27) || !range.collapsed)) {
@@ -6743,21 +7091,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6743
7091
  core.controllersOff();
6744
7092
  if (core._isInline || core._isBalloon) event._hideToolbar();
6745
7093
 
6746
- // active class reset of buttons
6747
- const commandMap = core.commandMap;
6748
- const activePlugins = core.activePlugins;
6749
- for (let key in commandMap) {
6750
- if (!util.hasOwn(commandMap, key)) continue;
6751
- if (activePlugins.indexOf(key) > -1) {
6752
- plugins[key].active.call(core, null);
6753
- } else if (commandMap.OUTDENT && /^OUTDENT$/i.test(key)) {
6754
- commandMap.OUTDENT.setAttribute('disabled', true);
6755
- } else if (commandMap.INDENT && /^INDENT$/i.test(key)) {
6756
- commandMap.INDENT.removeAttribute('disabled');
6757
- } else {
6758
- util.removeClass(commandMap[key], 'active');
6759
- }
6760
- }
7094
+ core._setKeyEffect([]);
6761
7095
 
6762
7096
  core._variable.currentNodes = [];
6763
7097
  core._variable.currentNodesMap = [];
@@ -6919,6 +7253,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6919
7253
  },
6920
7254
 
6921
7255
  _codeViewAutoHeight: function () {
7256
+ if (core._variable.isFullScreen) return;
6922
7257
  context.element.code.style.height = context.element.code.scrollHeight + 'px';
6923
7258
  },
6924
7259
 
@@ -6933,13 +7268,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6933
7268
  const eCell = util.getRangeFormatElement(ec);
6934
7269
  const sIsCell = util.isCell(sCell);
6935
7270
  const eIsCell = util.isCell(eCell);
7271
+ const ancestor = range.commonAncestorContainer;
6936
7272
  if (((sIsCell && !sCell.previousElementSibling && !sCell.parentElement.previousElementSibling) || (eIsCell && !eCell.nextElementSibling && !eCell.parentElement.nextElementSibling)) && sCell !== eCell) {
6937
7273
  if (!sIsCell) {
6938
- util.removeItem(util.getParentElement(eCell, util.isComponent));
7274
+ util.removeItem(util.getParentElement(eCell, function(current) {return ancestor === current.parentNode;}));
6939
7275
  } else if (!eIsCell) {
6940
- util.removeItem(util.getParentElement(sCell, util.isComponent));
7276
+ util.removeItem(util.getParentElement(sCell, function(current) {return ancestor === current.parentNode;}));
6941
7277
  } else {
6942
- util.removeItem(util.getParentElement(sCell, util.isComponent));
7278
+ util.removeItem(util.getParentElement(sCell, function(current) {return ancestor === current.parentNode;}));
6943
7279
  core.nativeFocus();
6944
7280
  return true;
6945
7281
  }
@@ -6987,6 +7323,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
6987
7323
  }
6988
7324
  },
6989
7325
 
7326
+ onSave_wysiwyg: function (content) {
7327
+ // user event
7328
+ if (typeof functions.onSave === 'function') {
7329
+ functions.onSave(content, core);
7330
+ return;
7331
+ }
7332
+ },
7333
+
6990
7334
  onCut_wysiwyg: function (e) {
6991
7335
  const clipboardData = util.isIE ? _w.clipboardData : e.clipboardData;
6992
7336
 
@@ -7088,7 +7432,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7088
7432
  } else {
7089
7433
  cleanData = (plainText === cleanData ? plainText : cleanData).replace(/\n/g, '<br>');
7090
7434
  }
7091
- cleanData = core.cleanHTML(cleanData, core.pasteTagsWhitelistRegExp);
7435
+ cleanData = core.cleanHTML(cleanData, core.pasteTagsWhitelistRegExp, core.pasteTagsBlacklistRegExp);
7092
7436
  } else {
7093
7437
  cleanData = util._HTMLConvertor(plainText).replace(/\n/g, '<br>');
7094
7438
  }
@@ -7239,7 +7583,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7239
7583
 
7240
7584
  /** resizingBar */
7241
7585
  if (context.element.resizingBar) {
7242
- if (/\d+/.test(options.height)) {
7586
+ if (/\d+/.test(options.height) && options.resizeEnable) {
7243
7587
  context.element.resizingBar.addEventListener('mousedown', event.onMouseDown_resizingBar, false);
7244
7588
  } else {
7245
7589
  util.addClass(context.element.resizingBar, 'se-resizing-none');
@@ -7351,6 +7695,13 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7351
7695
  */
7352
7696
  onChange: null,
7353
7697
 
7698
+ /**
7699
+ * @description Event functions
7700
+ * @param {String} contents Current contents
7701
+ * @param {Object} core Core object
7702
+ */
7703
+ onSave: null,
7704
+
7354
7705
  /**
7355
7706
  * @description Event functions (drop, paste)
7356
7707
  * When false is returned, the default behavior is stopped.
@@ -7569,6 +7920,14 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7569
7920
  */
7570
7921
  onResizeEditor: null,
7571
7922
 
7923
+ /**
7924
+ * @description Called after the "setToolbarButtons" invocation.
7925
+ * Can be used to tweak buttons properties (useful for custom buttons)
7926
+ * @param {Array} buttonList Button list
7927
+ * @param {Object} core Core object
7928
+ */
7929
+ onSetToolbarButtons: null,
7930
+
7572
7931
  /**
7573
7932
  * @description Reset the buttons on the toolbar. (Editor is not reloaded)
7574
7933
  * You cannot set a new plugin for the button.
@@ -7577,10 +7936,10 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7577
7936
  setToolbarButtons: function (buttonList) {
7578
7937
  core.submenuOff();
7579
7938
  core.containerOff();
7939
+ core.moreLayerOff();
7580
7940
 
7581
7941
  const newToolbar = _Constructor._createToolBar(_d, buttonList, core.plugins, options);
7582
7942
  _responsiveButtons = newToolbar.responsiveButtons;
7583
- core._moreLayerActiveButton = null;
7584
7943
  event._setResponsiveToolbar();
7585
7944
 
7586
7945
  context.element.toolbar.replaceChild(newToolbar._buttonTray, context.element._buttonTray);
@@ -7589,32 +7948,15 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7589
7948
  context.element = newContext.element;
7590
7949
  context.tool = newContext.tool;
7591
7950
  if (options.iframe) context.element.wysiwyg = core._wd.body;
7951
+
7952
+ core.recoverButtonStates();
7953
+
7592
7954
  core._cachingButtons();
7593
7955
  core.history._resetCachingButton();
7594
7956
 
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
7957
  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');
7958
+ if (core.isReadOnly) util.setDisabledButtons(true, core.resizingDisabledButtons);
7959
+ if (typeof functions.onSetToolbarButtons === 'function') functions.onSetToolbarButtons(newToolbar._buttonTray.querySelectorAll('button'), core);
7618
7960
  },
7619
7961
 
7620
7962
  /**
@@ -7650,7 +7992,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7650
7992
  const _initHTML = el.wysiwyg.innerHTML;
7651
7993
 
7652
7994
  // set option
7653
- const cons = _Constructor._setOptions(mergeOptions, context, options);
7995
+ const cons = _Constructor._setOptions(mergeOptions, context, options);
7654
7996
 
7655
7997
  if (cons.callButtons) {
7656
7998
  pluginCallButtons = cons.callButtons;
@@ -7726,10 +8068,12 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7726
8068
  },
7727
8069
 
7728
8070
  /**
7729
- * @description Copying the contents of the editor to the original textarea
8071
+ * @description Copying the contents of the editor to the original textarea and execute onSave callback
7730
8072
  */
7731
8073
  save: function () {
7732
- context.element.originElement.value = core.getContents(false);
8074
+ const contents = core.getContents(false);
8075
+ context.element.originElement.value = contents;
8076
+ event.onSave_wysiwyg(contents, core);
7733
8077
  },
7734
8078
 
7735
8079
  /**
@@ -7823,7 +8167,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7823
8167
  */
7824
8168
  insertHTML: function (html, notCleaningData, checkCharCount, rangeSelection) {
7825
8169
  if (typeof html === 'string') {
7826
- if (!notCleaningData) html = core.cleanHTML(html, null);
8170
+ if (!notCleaningData) html = core.cleanHTML(html, null, null);
7827
8171
  try {
7828
8172
  const dom = _d.createRange().createContextualFragment(html);
7829
8173
  const domTree = dom.childNodes;
@@ -7903,7 +8247,7 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7903
8247
  wysiwyg.appendChild(children[i]);
7904
8248
  }
7905
8249
  } else {
7906
- core._setCodeView(core._getCodeView() + '\n' + core.convertHTMLForCodeView(convertValue));
8250
+ core._setCodeView(core._getCodeView() + '\n' + core.convertHTMLForCodeView(convertValue, false));
7907
8251
  }
7908
8252
 
7909
8253
  // history stack
@@ -7917,7 +8261,16 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7917
8261
  readOnly: function (value) {
7918
8262
  core.isReadOnly = value;
7919
8263
 
8264
+ util.setDisabledButtons(!!value, core.resizingDisabledButtons);
8265
+
7920
8266
  if (value) {
8267
+ /** off menus */
8268
+ core.controllersOff();
8269
+ if (core.submenuActiveButton && core.submenuActiveButton.disabled) core.submenuOff();
8270
+ if (core._moreLayerActiveButton && core._moreLayerActiveButton.disabled) core.moreLayerOff();
8271
+ if (core.containerActiveButton && core.containerActiveButton.disabled) core.containerOff();
8272
+ if (core.modalForm) core.plugins.dialog.close.call(core);
8273
+
7921
8274
  context.element.code.setAttribute("readOnly", "true");
7922
8275
  } else {
7923
8276
  context.element.code.removeAttribute("readOnly");
@@ -7929,31 +8282,31 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
7929
8282
  /**
7930
8283
  * @description Disable the suneditor
7931
8284
  */
7932
- disabled: function () {
7933
- context.tool.cover.style.display = 'block';
7934
- context.element.wysiwyg.setAttribute('contenteditable', false);
7935
- core.isDisabled = true;
8285
+ disable: function () {
8286
+ this.toolbar.disable();
8287
+ this.wysiwyg.disable();
8288
+ },
7936
8289
 
7937
- if (options.codeMirrorEditor) {
7938
- options.codeMirrorEditor.setOption('readOnly', true);
7939
- } else {
7940
- context.element.code.setAttribute('disabled', 'disabled');
7941
- }
8290
+ /**
8291
+ * @description Provided for backward compatibility and will be removed in 3.0.0 version
8292
+ */
8293
+ disabled: function () {
8294
+ this.disable();
7942
8295
  },
7943
8296
 
7944
8297
  /**
7945
8298
  * @description Enable the suneditor
7946
8299
  */
7947
- enabled: function () {
7948
- context.tool.cover.style.display = 'none';
7949
- context.element.wysiwyg.setAttribute('contenteditable', true);
7950
- core.isDisabled = false;
8300
+ enable: function () {
8301
+ this.toolbar.enable();
8302
+ this.wysiwyg.enable();
8303
+ },
7951
8304
 
7952
- if (options.codeMirrorEditor) {
7953
- options.codeMirrorEditor.setOption('readOnly', false);
7954
- } else {
7955
- context.element.code.removeAttribute('disabled');
7956
- }
8305
+ /**
8306
+ * @description Provided for backward compatibility and will be removed in 3.0.0 version
8307
+ */
8308
+ enabled: function () {
8309
+ this.enable();
7957
8310
  },
7958
8311
 
7959
8312
  /**
@@ -8010,17 +8363,36 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
8010
8363
  /**
8011
8364
  * @description Disable the toolbar
8012
8365
  */
8013
- disabled: function () {
8366
+ disable: function () {
8367
+ /** off menus */
8368
+ core.submenuOff();
8369
+ core.moreLayerOff();
8370
+ core.containerOff();
8371
+
8014
8372
  context.tool.cover.style.display = 'block';
8015
8373
  },
8016
8374
 
8375
+ /**
8376
+ * @description Provided for backward compatibility and will be removed in 3.0.0 version
8377
+ */
8378
+ disabled: function () {
8379
+ this.disable();
8380
+ },
8381
+
8017
8382
  /**
8018
8383
  * @description Enable the toolbar
8019
8384
  */
8020
- enabled: function () {
8385
+ enable: function () {
8021
8386
  context.tool.cover.style.display = 'none';
8022
8387
  },
8023
8388
 
8389
+ /**
8390
+ * @description Provided for backward compatibility and will be removed in 3.0.0 version
8391
+ */
8392
+ enabled: function () {
8393
+ this.enable();
8394
+ },
8395
+
8024
8396
  /**
8025
8397
  * @description Show the toolbar
8026
8398
  */
@@ -8044,7 +8416,44 @@ export default function (context, pluginCallButtons, plugins, lang, options, _re
8044
8416
  context.element._stickyDummy.style.display = 'none';
8045
8417
  }
8046
8418
  },
8047
- }
8419
+ },
8420
+
8421
+ /**
8422
+ * @description Wysiwyg methods
8423
+ */
8424
+ wysiwyg: {
8425
+ /**
8426
+ * @description Disable the wysiwyg area
8427
+ */
8428
+ disable: function () {
8429
+ /** off menus */
8430
+ core.controllersOff();
8431
+ if (core.modalForm) core.plugins.dialog.close.call(core);
8432
+
8433
+ context.element.wysiwyg.setAttribute('contenteditable', false);
8434
+ core.isDisabled = true;
8435
+
8436
+ if (options.codeMirrorEditor) {
8437
+ options.codeMirrorEditor.setOption('readOnly', true);
8438
+ } else {
8439
+ context.element.code.setAttribute('disabled', 'disabled');
8440
+ }
8441
+ },
8442
+
8443
+ /**
8444
+ * @description Enable the wysiwyg area
8445
+ */
8446
+ enable: function () {
8447
+ context.element.wysiwyg.setAttribute('contenteditable', true);
8448
+ core.isDisabled = false;
8449
+
8450
+ if (options.codeMirrorEditor) {
8451
+ options.codeMirrorEditor.setOption('readOnly', false);
8452
+ } else {
8453
+ context.element.code.removeAttribute('disabled');
8454
+ }
8455
+ },
8456
+ }
8048
8457
  };
8049
8458
 
8050
8459
  /************ Core init ************/