suneditor 3.0.0-beta.1 → 3.0.0-beta.3

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 (64) hide show
  1. package/CONTRIBUTING.md +166 -29
  2. package/README.md +13 -1
  3. package/dist/suneditor.min.css +1 -1
  4. package/dist/suneditor.min.js +1 -1
  5. package/package.json +13 -5
  6. package/src/assets/{variables.css → design/color.css} +45 -59
  7. package/src/assets/design/index.css +3 -0
  8. package/src/assets/design/size.css +35 -0
  9. package/src/assets/design/typography.css +37 -0
  10. package/src/assets/suneditor-contents.css +1 -1
  11. package/src/assets/suneditor.css +40 -28
  12. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +4 -2
  13. package/src/core/base/eventHandlers/handler_ww_key_input.js +16 -16
  14. package/src/core/base/eventHandlers/handler_ww_mouse.js +1 -1
  15. package/src/core/base/eventManager.js +75 -32
  16. package/src/core/class/char.js +3 -2
  17. package/src/core/class/component.js +38 -14
  18. package/src/core/class/format.js +13 -2
  19. package/src/core/class/html.js +58 -26
  20. package/src/core/class/menu.js +19 -0
  21. package/src/core/class/selection.js +1 -8
  22. package/src/core/class/toolbar.js +2 -1
  23. package/src/core/class/ui.js +3 -1
  24. package/src/core/editor.js +125 -59
  25. package/src/core/section/actives.js +73 -10
  26. package/src/core/section/constructor.js +144 -55
  27. package/src/core/section/documentType.js +2 -2
  28. package/src/events.js +25 -1
  29. package/src/helper/converter.js +23 -1
  30. package/src/helper/dom/domCheck.js +12 -2
  31. package/src/modules/Controller.js +25 -5
  32. package/src/modules/Figure.js +6 -6
  33. package/src/modules/FileManager.js +1 -1
  34. package/src/plugins/command/fileUpload.js +3 -3
  35. package/src/plugins/dropdown/formatBlock.js +4 -13
  36. package/src/plugins/dropdown/table.js +51 -18
  37. package/src/plugins/modal/audio.js +2 -2
  38. package/src/plugins/modal/embed.js +2 -2
  39. package/src/plugins/modal/image.js +3 -3
  40. package/src/plugins/modal/math.js +2 -2
  41. package/src/plugins/modal/video.js +1 -1
  42. package/src/plugins/popup/anchor.js +1 -1
  43. package/src/suneditor.js +1 -1
  44. package/src/themes/dark.css +88 -45
  45. package/types/core/base/eventManager.d.ts +23 -0
  46. package/types/core/class/char.d.ts +2 -1
  47. package/types/core/class/component.d.ts +13 -3
  48. package/types/core/class/format.d.ts +8 -1
  49. package/types/core/class/html.d.ts +8 -0
  50. package/types/core/class/menu.d.ts +8 -0
  51. package/types/core/class/ui.d.ts +1 -1
  52. package/types/core/editor.d.ts +7 -2
  53. package/types/core/section/constructor.d.ts +167 -149
  54. package/types/events.d.ts +3 -0
  55. package/types/helper/converter.d.ts +9 -0
  56. package/types/helper/dom/domCheck.d.ts +7 -0
  57. package/types/helper/index.d.ts +2 -0
  58. package/types/index.d.ts +1 -1
  59. package/types/plugins/dropdown/formatBlock.d.ts +2 -2
  60. package/types/plugins/dropdown/table.d.ts +1 -0
  61. package/.eslintignore +0 -7
  62. package/.eslintrc.json +0 -81
  63. /package/src/assets/icons/{_default.js → defaultIcons.js} +0 -0
  64. /package/types/assets/icons/{_default.d.ts → defaultIcons.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { env, converter, dom, numbers } from '../helper';
2
- import Constructor, { InitOptions, UpdateButton, CreateShortcuts, CreateStatusbar, RO_UNAVAILABD } from './section/constructor';
2
+ import Constructor, { InitOptions, UpdateButton, CreateShortcuts, CreateStatusbar, OPTION_FRAME_FIXED_FLAG, OPTION_FIXED_FLAG } from './section/constructor';
3
3
  import { UpdateStatusbarContext } from './section/context';
4
4
  import { BASIC_COMMANDS, ACTIVE_EVENT_COMMANDS, SELECT_ALL, DIR_BTN_ACTIVE, SAVE, COPY_FORMAT, FONT_STYLE, PAGE_BREAK } from './section/actives';
5
5
  import History from './base/history';
@@ -383,11 +383,17 @@ function Editor(multiTargets, options) {
383
383
  this._notHideToolbar = false;
384
384
 
385
385
  /**
386
- * @description Variables for controlling focus and blur events
386
+ * @description Variables for controlling blur events
387
387
  * @type {boolean}
388
388
  */
389
389
  this._preventBlur = false;
390
390
 
391
+ /**
392
+ * @description Variables for controlling focus events
393
+ * @type {boolean}
394
+ */
395
+ this._preventFocus = false;
396
+
391
397
  /**
392
398
  * @description Variables for controlling selection change events
393
399
  */
@@ -762,37 +768,39 @@ Editor.prototype = {
762
768
  * @param {EditorInitOptions_editor} newOptions Options
763
769
  */
764
770
  resetOptions(newOptions) {
765
- const _keys = Object.keys;
766
771
  this.viewer.codeView(false);
767
772
  this.viewer.showBlocks(false);
768
773
 
769
- const newOptionKeys = _keys(newOptions);
774
+ const rootDiff = new Map();
775
+ const frameRoots = this.frameRoots;
776
+ const newRoots = [];
777
+ const newRootKeys = new Map();
778
+
779
+ // frame roots
780
+ const nRoot = {};
781
+ for (const k in newOptions) {
782
+ if (OPTION_FRAME_FIXED_FLAG[k] === undefined) continue;
783
+ nRoot[k] = newOptions[k];
784
+ delete newOptions[k];
785
+ }
786
+ for (const rootKey of frameRoots.keys()) {
787
+ newOptions[rootKey || ''] = { ...nRoot, ...newOptions[rootKey || ''] };
788
+ }
789
+
790
+ // check reoption validation
791
+ const newOptionKeys = Object.keys(newOptions);
770
792
  CheckResetKeys(newOptionKeys, this.plugins, '');
771
793
  if (newOptionKeys.length === 0) return;
772
794
 
795
+ if (frameRoots.size === 1) {
796
+ newOptionKeys.unshift(null);
797
+ }
798
+
773
799
  // option merge
774
- const rootDiff = {};
775
- const rootKeys = this.rootKeys;
776
- const frameRoots = this.frameRoots;
777
- const newRoots = [];
778
- const newRootKeys = {};
779
- this._originOptions = [newOptions, this._originOptions].reduce(function (init, option) {
800
+ const _originOptions = [this._originOptions, newOptions].reduce((init, option) => {
780
801
  for (const key in option) {
781
- if (rootKeys.includes(key) && option[key]) {
782
- const nro = option[key];
783
- const newKeys = _keys(nro);
784
- CheckResetKeys(newKeys, null, key + '.');
785
- if (newKeys.length === 0) continue;
786
-
787
- rootDiff[key] = new Map();
788
- const o = frameRoots.get(key).get('options').get('_origin');
789
- for (const rk in nro) {
790
- const roV = nro[rk];
791
- if (!newKeys.includes(rk) || o[rk] === roV) continue;
792
- rootDiff[key].set(GetResetDiffKey(rk), true);
793
- o[rk] = roV;
794
- }
795
- newRoots.push((newRootKeys[key] = { options: o }));
802
+ if (frameRoots.has(key || null)) {
803
+ RestoreFrameOptions(key, option, frameRoots, rootDiff, newRootKeys, newRoots);
796
804
  } else {
797
805
  init[key] = option[key];
798
806
  }
@@ -802,17 +810,25 @@ Editor.prototype = {
802
810
 
803
811
  // init options
804
812
  const options = this.options;
805
- const newMap = InitOptions(this._originOptions, newRoots, this.plugins).o;
806
- /** --------- root start --------- */
807
- for (let i = 0, k; (k = newOptionKeys[i]); i++) {
808
- if (newRootKeys[k]) {
809
- const diff = rootDiff[k];
813
+ const newO = InitOptions(_originOptions, newRoots, this.plugins);
814
+ const newOptionMap = newO.o;
815
+ const newFrameMap = newO.frameMap;
816
+ /** --------- [root start] --------- */
817
+ for (let i = 0, len = newOptionKeys.length, k; i < len; i++) {
818
+ k = newOptionKeys[i] || null;
819
+
820
+ if (newRootKeys.has(k)) {
821
+ const diff = rootDiff.get(k);
810
822
  const fc = frameRoots.get(k);
811
823
  const originOptions = fc.get('options');
812
- const newRootOptions = newRootKeys[k].options;
824
+ const newRootOptions = newFrameMap.get(k);
813
825
 
814
- // statusbar
815
- if (diff.has('statusbar')) {
826
+ // --- set options : fc ---
827
+ fc.set('options', newRootOptions);
828
+
829
+ // statusbar-changed
830
+ if (diff.has('statusbar-changed')) {
831
+ // statusbar
816
832
  dom.utils.removeItem(fc.get('statusbar'));
817
833
  if (newRootOptions.get('statusbar')) {
818
834
  const statusbar = CreateStatusbar(newRootOptions, null).statusbar;
@@ -824,6 +840,10 @@ Editor.prototype = {
824
840
  newRootOptions.set('__statusbarEvent', null);
825
841
  UpdateStatusbarContext(null, fc);
826
842
  }
843
+ // charCounter
844
+ if (fc.get('statusbar')) {
845
+ this.char.display(fc);
846
+ }
827
847
  }
828
848
 
829
849
  // iframe's options
@@ -834,6 +854,7 @@ Editor.prototype = {
834
854
  for (const origin_k in originAttr) frame.removeAttribute(origin_k, originAttr[origin_k]);
835
855
  for (const new_k in newAttr) frame.setAttribute(new_k, newAttr[new_k]);
836
856
  }
857
+
837
858
  if (diff.has('iframe_cssFileName')) {
838
859
  const docHead = fc.get('_wd').head;
839
860
  const links = docHead.getElementsByTagName('link');
@@ -844,11 +865,12 @@ Editor.prototype = {
844
865
  while (newLinks[0]) docHead.insertBefore(newLinks[0], sTag);
845
866
  }
846
867
 
847
- // --- options set ---
848
- fc.set('options', newRootOptions);
868
+ if (diff.has('placeholder')) {
869
+ fc.get('placeholder').textContent = newRootOptions.get('placeholder');
870
+ }
849
871
 
850
872
  // frame styles
851
- this.ui.setEditorStyle(newRootOptions.get('_defaultStyles'), fc);
873
+ this.ui.setEditorStyle(newRootOptions.get('editorStyle'), fc);
852
874
 
853
875
  // frame attributes
854
876
  const frame = fc.get('wysiwyg');
@@ -859,25 +881,47 @@ Editor.prototype = {
859
881
 
860
882
  continue;
861
883
  }
862
- /** --------- root end --------- */
884
+ /** --------- [root end] --------- */
863
885
 
864
- options.set(k, newMap.get(k));
886
+ // --- set options ---
887
+ options.set(k, newOptionMap.get(k));
865
888
 
866
- /** apply option */
867
- // history delay time
868
- if (k === 'historyStackDelayTime') {
869
- this.history.resetDelayTime(options.get('historyStackDelayTime'));
870
- continue;
871
- }
872
- // set dir
873
- if (k === 'textDirection') {
874
- this.setDir(options.get('_rtl') ? 'ltr' : 'rtl');
875
- continue;
889
+ /** Options that require a function call */
890
+ switch (k) {
891
+ case 'theme': {
892
+ this.ui.setTheme(options.get('theme'));
893
+ break;
894
+ }
895
+ case 'events': {
896
+ const events = options.get('events');
897
+ for (const name in events) {
898
+ this.events[name] = events[name];
899
+ }
900
+ break;
901
+ }
902
+ case 'autoStyleify': {
903
+ this.html.__resetAutoStyleify(options.get('autoStyleify'));
904
+ break;
905
+ }
906
+ case 'textDirection': {
907
+ this.setDir(options.get('_rtl') ? 'ltr' : 'rtl');
908
+ break;
909
+ }
910
+ case 'historyStackDelayTime': {
911
+ this.history.resetDelayTime(options.get('historyStackDelayTime'));
912
+ break;
913
+ }
914
+ case 'defaultLineBreakFormat': {
915
+ this.format.__resetBrLineBreak(options.get('defaultLineBreakFormat'));
916
+ }
876
917
  }
877
918
  }
878
919
 
879
920
  /** apply options */
880
- // toolbar
921
+ // _origin
922
+ this._originOptions = _originOptions;
923
+
924
+ // --- [toolbar] ---
881
925
  const toolbar = this.context.get('toolbar.main');
882
926
  // width
883
927
  if (/inline|balloon/i.test(options.get('mode')) && newOptionKeys.includes('toolbar_width')) {
@@ -886,6 +930,8 @@ Editor.prototype = {
886
930
  // hide
887
931
  if (options.get('toolbar_hide')) {
888
932
  toolbar.style.display = 'none';
933
+ } else {
934
+ toolbar.style.display = '';
889
935
  }
890
936
  // shortcuts hint
891
937
  if (options.get('shortcutsHint')) {
@@ -894,11 +940,6 @@ Editor.prototype = {
894
940
  dom.utils.addClass(toolbar, 'se-shortcut-hide');
895
941
  }
896
942
 
897
- // theme
898
- if (this._originOptions.theme !== (newOptions.theme ?? this._originOptions.theme)) {
899
- this.ui.setTheme(newOptions.theme);
900
- }
901
-
902
943
  this.effectNode = null;
903
944
  this._setFrameInfo(this.frameRoots.get(this.status.rootKey));
904
945
  },
@@ -971,7 +1012,7 @@ Editor.prototype = {
971
1012
 
972
1013
  const fileComponentInfo = this.component.get(focusEl);
973
1014
  if (fileComponentInfo) {
974
- this.component.select(fileComponentInfo.target, fileComponentInfo.pluginName, false);
1015
+ this.component.select(fileComponentInfo.target, fileComponentInfo.pluginName);
975
1016
  } else if (focusEl) {
976
1017
  if (focusEl.nodeType !== 3) {
977
1018
  focusEl = dom.query.getEdgeChild(
@@ -1444,7 +1485,7 @@ Editor.prototype = {
1444
1485
 
1445
1486
  /**
1446
1487
  * @private
1447
- * @description Caches shortcut keys for commands.
1488
+ * @description Caches custom(starts with "_") shortcut keys for commands.
1448
1489
  */
1449
1490
  __cachingShortcuts() {
1450
1491
  const shortcuts = this.options.get('shortcuts');
@@ -1689,16 +1730,41 @@ Editor.prototype = {
1689
1730
  Constructor: Editor
1690
1731
  };
1691
1732
 
1733
+ function RestoreFrameOptions(key, option, frameRoots, rootDiff, newRootKeys, newRoots) {
1734
+ const nro = option[key];
1735
+ const newKeys = Object.keys(nro);
1736
+ CheckResetKeys(newKeys, null, key + '.');
1737
+ if (newKeys.length === 0) return false;
1738
+
1739
+ const rootKey = key || null;
1740
+ rootDiff.set(rootKey, new Map());
1741
+
1742
+ const o = frameRoots.get(rootKey).get('options').get('_origin');
1743
+ const no = {};
1744
+ const hasOwn = Object.prototype.hasOwnProperty;
1745
+ for (const rk in nro) {
1746
+ if (!hasOwn.call(OPTION_FRAME_FIXED_FLAG, rk)) continue;
1747
+ const roV = nro[rk];
1748
+ if (!newKeys.includes(rk) || o[rk] === roV) continue;
1749
+ rootDiff.get(rootKey).set(GetResetDiffKey(rk), true);
1750
+ no[rk] = roV;
1751
+ }
1752
+
1753
+ const newO = { ...o, ...no };
1754
+ newRootKeys.set(rootKey, new Map(Object.entries(newO)));
1755
+ newRoots.push({ key: rootKey, options: newO });
1756
+ }
1757
+
1692
1758
  function GetResetDiffKey(key) {
1693
- if (/^statusbar/i.test(key)) return 'statusbar';
1759
+ if (/^statusbar|^charCounter/.test(key)) return 'statusbar-changed';
1694
1760
  return key;
1695
1761
  }
1696
1762
 
1697
1763
  function CheckResetKeys(keys, plugins, root) {
1698
1764
  for (let i = 0, len = keys.length, k; i < len; i++) {
1699
1765
  k = keys[i];
1700
- if (RO_UNAVAILABD.includes(k) || (plugins && plugins[k])) {
1701
- console.warn(`[SUNEDITOR.warn.resetOptions] "[${root + k}]" options not available in resetOptions have no effect.`);
1766
+ if (OPTION_FIXED_FLAG[k] === 'fixed' || OPTION_FRAME_FIXED_FLAG[k] === 'fixed' || (plugins && plugins[k])) {
1767
+ console.warn(`[SUNEDITOR.warn.resetOptions] The "[${root + k}]" option cannot be changed after the editor is created.`);
1702
1768
  keys.splice(i--, 1);
1703
1769
  len--;
1704
1770
  }
@@ -31,6 +31,35 @@ const __RemoveCopyformt = function (ww, button) {
31
31
  return true;
32
32
  };
33
33
 
34
+ /**
35
+ * @private
36
+ * @description Finds the first and last child elements in a selection area.
37
+ * @param {Element} selectArea Selection area element
38
+ * @returns {{ first: Node, last: Node}} Object containing the first and last child elements
39
+ */
40
+ const __findFirstAndLast = function (selectArea) {
41
+ const isContentLess = dom.check.isContentLess;
42
+ const isTable = dom.check.isTable;
43
+ const first =
44
+ dom.query.getEdgeChild(
45
+ dom.query.getEdgeChild(selectArea, (current) => !isContentLess(current), false),
46
+ (current) => {
47
+ return current.childNodes.length === 0 || current.nodeType === 3 || isTable(current);
48
+ },
49
+ false
50
+ ) || selectArea.firstChild;
51
+ const last =
52
+ dom.query.getEdgeChild(
53
+ selectArea.lastChild,
54
+ (current) => {
55
+ return current.childNodes.length === 0 || current.nodeType === 3 || isTable(current);
56
+ },
57
+ true
58
+ ) || selectArea.lastChild;
59
+
60
+ return { first, last };
61
+ };
62
+
34
63
  /**
35
64
  * @description List of commands that trigger active event handling in the editor.
36
65
  * - These commands typically apply inline formatting or structural changes.
@@ -52,26 +81,60 @@ export const BASIC_COMMANDS = ACTIVE_EVENT_COMMANDS.concat(['undo', 'redo', 'sav
52
81
  export function SELECT_ALL(editor) {
53
82
  editor.ui._offCurrentController();
54
83
  editor.menu.containerOff();
55
- const figcaption = dom.query.getParentElement(editor.selection.getNode(), 'FIGCAPTION');
56
- const selectArea = figcaption || editor.frameContext.get('wysiwyg');
57
84
 
58
- let first = dom.query.getEdgeChild(selectArea.firstChild, (current) => current.childNodes.length === 0 || current.nodeType === 3 || dom.check.isTable(current), false) || selectArea.firstChild;
59
- let last = dom.query.getEdgeChild(selectArea.lastChild, (current) => current.childNodes.length === 0 || current.nodeType === 3 || dom.check.isTable(current), true) || selectArea.lastChild;
85
+ // check all tags
86
+ const ww = editor.frameContext.get('wysiwyg');
87
+ let prevScopeTag = null;
88
+ let prevScopeTagName = '';
89
+ const scopeSelectionTags = editor.options.get('scopeSelectionTags');
90
+ const range = editor.selection.getRange();
91
+ if (!range.collapsed) {
92
+ let commonNode = range.commonAncestorContainer;
93
+ let commonNodeName = commonNode.nodeName?.toLowerCase();
94
+
95
+ while (commonNode && ((!commonNode.nextSibling && !commonNode.previousSibling && !scopeSelectionTags.includes(commonNodeName)) || dom.check.isContentLess(commonNodeName)) && commonNode !== ww) {
96
+ commonNode = commonNode.parentElement;
97
+ commonNodeName = commonNode.nodeName?.toLowerCase();
98
+ }
99
+
100
+ if (scopeSelectionTags.includes(commonNodeName)) {
101
+ prevScopeTag = commonNode;
102
+ prevScopeTagName = commonNodeName;
103
+ }
104
+ }
105
+
106
+ // select all
107
+ const scopeTagList = scopeSelectionTags.filter((tagName) => tagName !== prevScopeTagName);
108
+ const scopeBaseTag = dom.query.getParentElement(prevScopeTag || editor.selection.getNode(), (current) => scopeTagList.includes(current.nodeName?.toLowerCase()));
109
+
110
+ let selectArea = scopeBaseTag || ww;
111
+ let { first, last } = __findFirstAndLast(selectArea);
112
+
113
+ if (!first || !last) return;
114
+
115
+ const isZeroWidth = dom.check.isZeroWidth;
116
+ while (isZeroWidth(first) && isZeroWidth(last) && selectArea !== ww) {
117
+ selectArea = selectArea.parentElement;
118
+ ({ first, last } = __findFirstAndLast(dom.query.getParentElement(selectArea, (current) => scopeTagList.includes(current.nodeName?.toLowerCase())) || ww));
119
+ }
60
120
 
61
121
  if (!first || !last) return;
62
122
 
63
- if (dom.check.isMedia(first) || editor.component.is(first.parentElement) || dom.check.isTableElements(first)) {
64
- const info = editor.component.get(first) || editor.component.get(first.parentElement);
123
+ let info = null;
124
+ if (dom.check.isMedia(first) || (info = editor.component.get(first.parentElement)) || dom.check.isTableElements(first)) {
65
125
  const br = dom.utils.createElement('BR');
66
126
  const format = dom.utils.createElement(editor.options.get('defaultLine'), null, br);
67
127
  first = info ? info.container || info.cover : first;
68
- first.parentNode.insertBefore(format, first);
128
+ first.parentElement.insertBefore(format, first);
69
129
  first = br;
70
130
  }
71
131
 
72
- if (dom.check.isMedia(last) || editor.component.is(last.parentElement) || dom.check.isTableElements(last)) {
73
- last = dom.utils.createElement('BR');
74
- selectArea.appendChild(dom.utils.createElement(editor.options.get('defaultLine'), null, last));
132
+ if (dom.check.isMedia(last) || (info = editor.component.get(last.parentElement)) || dom.check.isTableElements(last)) {
133
+ const br = dom.utils.createElement('BR');
134
+ const format = dom.utils.createElement(editor.options.get('defaultLine'), null, br);
135
+ last = info ? info.container || info.cover : last;
136
+ last.parentElement.appendChild(format);
137
+ last = br;
75
138
  }
76
139
 
77
140
  editor.toolbar._showBalloon(editor.selection.setRange(first, 0, last, last.textContent.length));