suneditor 3.0.0-beta.2 → 3.0.0-beta.20

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 (177) hide show
  1. package/CONTRIBUTING.md +186 -184
  2. package/LICENSE +21 -21
  3. package/README.md +157 -180
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +126 -123
  7. package/src/assets/design/color.css +131 -121
  8. package/src/assets/design/index.css +3 -3
  9. package/src/assets/design/size.css +37 -35
  10. package/src/assets/design/typography.css +37 -37
  11. package/src/assets/icons/defaultIcons.js +247 -232
  12. package/src/assets/suneditor-contents.css +779 -778
  13. package/src/assets/suneditor.css +43 -35
  14. package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
  19. package/src/core/base/eventManager.js +1550 -1484
  20. package/src/core/base/history.js +355 -355
  21. package/src/core/class/char.js +163 -162
  22. package/src/core/class/component.js +856 -842
  23. package/src/core/class/format.js +3433 -3422
  24. package/src/core/class/html.js +1927 -1890
  25. package/src/core/class/menu.js +357 -346
  26. package/src/core/class/nodeTransform.js +424 -424
  27. package/src/core/class/offset.js +858 -891
  28. package/src/core/class/selection.js +710 -620
  29. package/src/core/class/shortcuts.js +98 -98
  30. package/src/core/class/toolbar.js +438 -430
  31. package/src/core/class/ui.js +424 -422
  32. package/src/core/class/viewer.js +750 -750
  33. package/src/core/editor.js +1810 -1708
  34. package/src/core/section/actives.js +268 -241
  35. package/src/core/section/constructor.js +1348 -1661
  36. package/src/core/section/context.js +102 -102
  37. package/src/core/section/documentType.js +582 -561
  38. package/src/core/section/options.js +367 -0
  39. package/src/core/util/instanceCheck.js +59 -0
  40. package/src/editorInjector/_classes.js +36 -36
  41. package/src/editorInjector/_core.js +92 -92
  42. package/src/editorInjector/index.js +75 -75
  43. package/src/events.js +634 -622
  44. package/src/helper/clipboard.js +59 -59
  45. package/src/helper/converter.js +586 -564
  46. package/src/helper/dom/domCheck.js +304 -304
  47. package/src/helper/dom/domQuery.js +677 -669
  48. package/src/helper/dom/domUtils.js +618 -557
  49. package/src/helper/dom/index.js +12 -12
  50. package/src/helper/env.js +249 -240
  51. package/src/helper/index.js +25 -25
  52. package/src/helper/keyCodeMap.js +183 -183
  53. package/src/helper/numbers.js +72 -72
  54. package/src/helper/unicode.js +47 -47
  55. package/src/langs/ckb.js +231 -231
  56. package/src/langs/cs.js +231 -231
  57. package/src/langs/da.js +231 -231
  58. package/src/langs/de.js +231 -231
  59. package/src/langs/en.js +230 -230
  60. package/src/langs/es.js +231 -231
  61. package/src/langs/fa.js +231 -231
  62. package/src/langs/fr.js +231 -231
  63. package/src/langs/he.js +231 -231
  64. package/src/langs/hu.js +230 -230
  65. package/src/langs/index.js +28 -28
  66. package/src/langs/it.js +231 -231
  67. package/src/langs/ja.js +230 -230
  68. package/src/langs/km.js +230 -230
  69. package/src/langs/ko.js +230 -230
  70. package/src/langs/lv.js +231 -231
  71. package/src/langs/nl.js +231 -231
  72. package/src/langs/pl.js +231 -231
  73. package/src/langs/pt_br.js +231 -231
  74. package/src/langs/ro.js +231 -231
  75. package/src/langs/ru.js +231 -231
  76. package/src/langs/se.js +231 -231
  77. package/src/langs/tr.js +231 -231
  78. package/src/langs/uk.js +231 -231
  79. package/src/langs/ur.js +231 -231
  80. package/src/langs/zh_cn.js +231 -231
  81. package/src/modules/ApiManager.js +191 -191
  82. package/src/modules/Browser.js +669 -667
  83. package/src/modules/ColorPicker.js +364 -362
  84. package/src/modules/Controller.js +474 -454
  85. package/src/modules/Figure.js +1620 -1617
  86. package/src/modules/FileManager.js +359 -359
  87. package/src/modules/HueSlider.js +577 -565
  88. package/src/modules/Modal.js +346 -346
  89. package/src/modules/ModalAnchorEditor.js +643 -643
  90. package/src/modules/SelectMenu.js +549 -549
  91. package/src/modules/_DragHandle.js +17 -17
  92. package/src/modules/index.js +14 -14
  93. package/src/plugins/browser/audioGallery.js +83 -83
  94. package/src/plugins/browser/fileBrowser.js +103 -103
  95. package/src/plugins/browser/fileGallery.js +83 -83
  96. package/src/plugins/browser/imageGallery.js +81 -81
  97. package/src/plugins/browser/videoGallery.js +103 -103
  98. package/src/plugins/command/blockquote.js +61 -60
  99. package/src/plugins/command/exportPDF.js +134 -134
  100. package/src/plugins/command/fileUpload.js +456 -456
  101. package/src/plugins/command/list_bulleted.js +149 -148
  102. package/src/plugins/command/list_numbered.js +152 -151
  103. package/src/plugins/dropdown/align.js +157 -155
  104. package/src/plugins/dropdown/backgroundColor.js +108 -104
  105. package/src/plugins/dropdown/font.js +141 -137
  106. package/src/plugins/dropdown/fontColor.js +109 -105
  107. package/src/plugins/dropdown/formatBlock.js +170 -178
  108. package/src/plugins/dropdown/hr.js +152 -152
  109. package/src/plugins/dropdown/layout.js +83 -83
  110. package/src/plugins/dropdown/lineHeight.js +131 -130
  111. package/src/plugins/dropdown/list.js +123 -122
  112. package/src/plugins/dropdown/paragraphStyle.js +138 -138
  113. package/src/plugins/dropdown/table.js +4110 -4000
  114. package/src/plugins/dropdown/template.js +83 -83
  115. package/src/plugins/dropdown/textStyle.js +149 -149
  116. package/src/plugins/field/mention.js +242 -242
  117. package/src/plugins/index.js +120 -120
  118. package/src/plugins/input/fontSize.js +414 -410
  119. package/src/plugins/input/pageNavigator.js +71 -70
  120. package/src/plugins/modal/audio.js +677 -677
  121. package/src/plugins/modal/drawing.js +537 -531
  122. package/src/plugins/modal/embed.js +886 -886
  123. package/src/plugins/modal/image.js +1377 -1376
  124. package/src/plugins/modal/link.js +248 -240
  125. package/src/plugins/modal/math.js +563 -563
  126. package/src/plugins/modal/video.js +1226 -1226
  127. package/src/plugins/popup/anchor.js +224 -222
  128. package/src/suneditor.js +114 -107
  129. package/src/themes/dark.css +132 -122
  130. package/src/typedef.js +132 -130
  131. package/types/assets/icons/defaultIcons.d.ts +8 -0
  132. package/types/core/base/eventManager.d.ts +29 -4
  133. package/types/core/class/char.d.ts +2 -1
  134. package/types/core/class/component.d.ts +1 -2
  135. package/types/core/class/format.d.ts +8 -1
  136. package/types/core/class/html.d.ts +8 -0
  137. package/types/core/class/menu.d.ts +8 -0
  138. package/types/core/class/offset.d.ts +24 -26
  139. package/types/core/class/selection.d.ts +2 -0
  140. package/types/core/class/toolbar.d.ts +6 -0
  141. package/types/core/class/ui.d.ts +1 -1
  142. package/types/core/editor.d.ts +34 -12
  143. package/types/core/section/constructor.d.ts +5 -638
  144. package/types/core/section/documentType.d.ts +12 -2
  145. package/types/core/section/options.d.ts +740 -0
  146. package/types/core/util/instanceCheck.d.ts +50 -0
  147. package/types/editorInjector/_core.d.ts +5 -5
  148. package/types/editorInjector/index.d.ts +2 -2
  149. package/types/events.d.ts +2 -0
  150. package/types/helper/converter.d.ts +9 -0
  151. package/types/helper/dom/domQuery.d.ts +5 -5
  152. package/types/helper/dom/domUtils.d.ts +8 -0
  153. package/types/helper/env.d.ts +6 -1
  154. package/types/helper/index.d.ts +4 -1
  155. package/types/index.d.ts +122 -120
  156. package/types/langs/_Lang.d.ts +194 -194
  157. package/types/modules/ColorPicker.d.ts +5 -1
  158. package/types/modules/Controller.d.ts +8 -4
  159. package/types/modules/Figure.d.ts +2 -1
  160. package/types/modules/HueSlider.d.ts +4 -1
  161. package/types/modules/SelectMenu.d.ts +1 -1
  162. package/types/plugins/command/blockquote.d.ts +1 -0
  163. package/types/plugins/command/list_bulleted.d.ts +1 -0
  164. package/types/plugins/command/list_numbered.d.ts +1 -0
  165. package/types/plugins/dropdown/align.d.ts +1 -0
  166. package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
  167. package/types/plugins/dropdown/font.d.ts +1 -0
  168. package/types/plugins/dropdown/fontColor.d.ts +1 -0
  169. package/types/plugins/dropdown/formatBlock.d.ts +3 -2
  170. package/types/plugins/dropdown/lineHeight.d.ts +1 -0
  171. package/types/plugins/dropdown/list.d.ts +1 -0
  172. package/types/plugins/dropdown/table.d.ts +6 -0
  173. package/types/plugins/input/fontSize.d.ts +1 -0
  174. package/types/plugins/modal/drawing.d.ts +4 -0
  175. package/types/plugins/modal/link.d.ts +32 -15
  176. package/types/suneditor.d.ts +13 -9
  177. package/types/typedef.d.ts +8 -0
@@ -1,1661 +1,1348 @@
1
- import _icons from '../../assets/icons/defaultIcons';
2
- import _defaultLang from '../../langs/en';
3
- import { CreateContext, CreateFrameContext } from './context';
4
- import { dom, numbers, converter, env } from '../../helper';
5
-
6
- const _d = env._d;
7
- const DEFAULT_BUTTON_LIST = [
8
- ['undo', 'redo'],
9
- '|',
10
- ['bold', 'underline', 'italic', 'strike', '|', 'subscript', 'superscript'],
11
- '|',
12
- ['removeFormat'],
13
- '|',
14
- ['outdent', 'indent'],
15
- '|',
16
- ['fullScreen', 'showBlocks', 'codeView'],
17
- '|',
18
- ['preview', 'print']
19
- ];
20
-
21
- const REQUIRED_FORMAT_LINE = 'div';
22
- const REQUIRED_ELEMENT_WHITELIST = 'br|div';
23
- const DEFAULT_ELEMENT_WHITELIST =
24
- 'p|pre|blockquote|h1|h2|h3|h4|h5|h6|ol|ul|li|hr|figure|figcaption|img|iframe|audio|video|source|table|thead|tbody|tr|th|td|caption|a|b|strong|var|i|em|u|ins|s|span|strike|del|sub|sup|code|svg|path|details|summary';
25
- const DEFAULT_TEXT_STYLE_TAGS = 'strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label|code|summary';
26
-
27
- /* scopeSelectionTags */
28
- const DEFAULT_SCOPE_SELECTION_TAGS = 'td|table|li|ol|ul|pre|figcaption|blockquote|dl|dt|dd';
29
-
30
- const _video_audio_attr = '|controls|autoplay|loop|muted|poster|preload|playsinline|volume|crossorigin|disableRemotePlayback|controlsList';
31
- const _iframe_attr = '|allowfullscreen|sandbox|loading|allow|referrerpolicy|frameborder|scrolling';
32
- const DEFAULT_ATTRIBUTE_WHITELIST = 'contenteditable|target|href|title|download|rel|src|alt|class|type|colspan|rowspan' + _video_audio_attr + _iframe_attr;
33
-
34
- const DEFAULT_FORMAT_LINE = 'P|H[1-6]|LI|TH|TD|DETAILS';
35
- const DEFAULT_FORMAT_BR_LINE = 'PRE';
36
- const DEFAULT_FORMAT_CLOSURE_BR_LINE = '';
37
- const DEFAULT_FORMAT_BLOCK = 'BLOCKQUOTE|OL|UL|FIGCAPTION|TABLE|THEAD|TBODY|TR|CAPTION|DETAILS';
38
- const DEFAULT_FORMAT_CLOSURE_BLOCK = 'TH|TD';
39
-
40
- const DEFAULT_ALLOWED_EMPTY_NODE_LIST = '.se-component, pre, blockquote, hr, li, table, img, iframe, video, audio, canvas, details';
41
-
42
- const DEFAULT_SIZE_UNITS = ['px', 'pt', 'em', 'rem'];
43
-
44
- const DEFAULT_CLASS_NAME = '^__se__|^se-|^katex|^MathJax';
45
- const DEFAULT_CLASS_MJX = 'mjx-container|mjx-math|mjx-mrow|mjx-mi|mjx-mo|mjx-mn|mjx-msup|mjx-mfrac|mjx-munderover';
46
- const DEFAULT_EXTRA_TAG_MAP = { script: false, style: false, meta: false, link: false, '[a-z]+:[a-z]+': false };
47
-
48
- const DEFAULT_TAG_STYLES = {
49
- 'table|th|td': 'border|border-[a-z]+|background-color|text-align|float|font-weight|text-decoration|font-style',
50
- 'ol|ul': 'list-style-type'
51
- };
52
- const DEFAULT_TEXT_STYLES = 'font-family|font-size|color|background-color';
53
- const DEFAULT_LINE_STYLES = 'text-align|margin-left|margin-right|line-height';
54
- const DEFAULT_CONTENT_STYLES =
55
- 'background|background-clip|background-color|' +
56
- 'border|border-bottom|border-collapse|border-color|border-image|border-left-width|border-radius|border-right-width|border-spacing|border-style|border-top|border-width|' +
57
- 'box-shadow|box-sizing|' +
58
- 'caption-side|color|content|' +
59
- 'direction|display|' +
60
- 'float|font|font-family|font-size|font-style|font-weight|' +
61
- 'height|' +
62
- 'left|letter-spacing|line-height|list-style-position|list-style-type|' +
63
- 'margin|margin-block-end|margin-block-start|margin-bottom|margin-inline-end|margin-inline-start|margin-left|margin-right|margin-top|max-width|min-width|' +
64
- 'outline|overflow|' +
65
- 'position|padding|padding-bottom|padding-inline-start|padding-left|padding-right|padding-top|' +
66
- 'page-break-before|page-break-after|page-break-inside|' +
67
- 'rotate|rotateX|rotateY|' +
68
- 'table-layout|text-align|text-decoration|text-shadow|text-transform|top|' +
69
- 'text-indent|text-rendering|' +
70
- 'vertical-align|visibility|' +
71
- 'white-space|width|word-break|word-wrap';
72
-
73
- const RETAIN_STYLE_MODE = ['repeat', 'always', 'none'];
74
-
75
- /**
76
- * @typedef {Object} EditorFrameOptions
77
- * @property {string} [value=""] - Initial value for the editor.
78
- * @property {string} [placeholder=""] - Placeholder text.
79
- * @property {Object<string, string>} [editableFrameAttributes={}] - Attributes for the editable frame[.sun-editor-editable]. (e.g. [key]: value)
80
- * @property {string} [width="100%"] - Width for the editor.
81
- * @property {string} [minWidth=""] - Min width for the editor.
82
- * @property {string} [maxWidth=""] - Max width for the editor.
83
- * @property {string} [height="auto"] - Height for the editor.
84
- * @property {string} [minHeight=""] - Min height for the editor.
85
- * @property {string} [maxHeight=""] - Max height for the editor.
86
- * @property {string} [editorStyle=""] - Style string of the top frame of the editor. (e.g. "border: 1px solid #ccc;").
87
- * @property {boolean} [iframe=false] - Content will be placed in an iframe and isolated from the rest of the page.
88
- * @property {boolean} [iframe_fullPage=false] - Allows the usage of HTML, HEAD, BODY tags and DOCTYPE declaration on the "iframe".
89
- * @property {Object<string, string>} [iframe_attributes={}] - Attributes of the "iframe". (e.g. {'scrolling': 'no'})
90
- * @property {string} [iframe_cssFileName="suneditor"] - Name or Array of the CSS file to apply inside the iframe.
91
- * - You can also use regular expressions.
92
- * - Applied by searching by filename in the link tag of document,
93
- * - or put the URL value (".css" can be omitted).
94
- * @property {boolean} [statusbar=true] - Enables the status bar.
95
- * @property {boolean} [statusbar_showPathLabel=true] - Displays the current node structure to status bar.
96
- * @property {boolean} [statusbar_resizeEnable=true] - Enables resize function of bottom status bar
97
- * @property {boolean} [charCounter=false] - Shows the number of characters in the editor.
98
- * - If the maxCharCount option has a value, it becomes true.
99
- * @property {number} [charCounter_max] - The maximum number of characters allowed to be inserted into the editor.
100
- * @property {string} [charCounter_label] - Text to be displayed in the "charCounter" area of the bottom bar. (e.g. "Characters : 20/200")
101
- * @property {"char"|"byte"|"byte-html"} [charCounter_type="char"] - Defines the calculation method of the "charCounter" option.
102
- * - 'char': Characters length.
103
- * - 'byte': Binary data size of characters.
104
- * - 'byte-html': Binary data size of the full HTML string.
105
- */
106
-
107
- /**
108
- * @typedef {Object} EditorBaseOptions
109
- * @property {Object<string, *>|Array<Object<string, *>>} [plugins] - Plugin configuration.
110
- * @property {Array<string>} [excludedPlugins] - Plugin configuration.
111
- * @property {Array<string[]|string>} [buttonList] - List of toolbar buttons, grouped by sub-arrays.
112
- * @property {boolean} [v2Migration=false] - Enables migration mode for SunEditor v2.
113
- * @property {boolean|{tagFilter: boolean, formatFilter: boolean, classFilter: boolean, styleNodeFilter: boolean, attrFilter: boolean, styleFilter: boolean}} [strictMode=true] - Enables strict filtering of tags, attributes, and styles.
114
- * @property {"classic"|"inline"|"balloon"|"balloon-always"} [mode="classic"] - Toolbar mode: "classic", "inline", "balloon", "balloon-always".
115
- * @property {string} [type=""] - Editor type: "document:header,page".
116
- * @property {string} [theme=""] - Editor theme.
117
- * @property {Object<string, string>} [lang] - Language configuration.
118
- * @property {Array<string>} [fontSizeUnits=["px", "pt", "em", "rem"]] - Allowed font size units.
119
- * @property {string} [allowedClassName] - Allowed class names.
120
- * @property {boolean} [closeModalOutsideClick=false] - Closes modals when clicking outside.
121
- * @property {boolean} [copyFormatKeepOn=false] - Keeps the format of the copied content.
122
- * @property {boolean} [syncTabIndent=true] - Synchronizes tab indent with spaces.
123
- * @property {boolean} [tabDisable=false] - Disables tab key input.
124
- * @property {boolean} [autoLinkify] - Automatically converts URLs into hyperlinks. ("Link" plugin required)
125
- * @property {Array<string>} [autoStyleify=["bold", "underline", "italic", "strike"]] - Styles applied automatically on text input.
126
- * @property {Object<string, string|number>} [scrollToOptions={behavior: "auto", block: "nearest"}] - Configuration for scroll behavior when navigating editor content.
127
- * @property {Object<string, string|number>} [componentScrollToOptions={behavior: "smooth", block: "center"}] - Configuration for scroll behavior when navigating components.
128
- * @property {"repeat"|"always"|"none"} [retainStyleMode="repeat"] - This option determines how inline elements (such as <span>, <strong>, etc.) are handled when deleting text.
129
- * - "repeat": Inline styles are retained unless the backspace key is repeatedly pressed. If the user continuously presses backspace, the styles will eventually be removed.
130
- * - "none": Inline styles are not retained at all. When deleting text, the associated inline elements are immediately removed along with it.
131
- * - "always": Inline styles persist indefinitely unless explicitly removed. Even if all text inside an inline element is deleted, the element itself remains until manually removed.
132
- * @property {Object<string, boolean>} [allowedExtraTags={script: false, style: false, meta: false, link: false, "[a-z]+:[a-z]+": false}] - Specifies extra allowed or disallowed tags.
133
- * @property {Object<string, (...args: *) => *>} [events={}] - Custom event handlers.
134
- * @property {string} [__textStyleTags="strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label|code|summary"] - The basic tags that serves as the base for "textStyleTags"
135
- * @property {string} [textStyleTags="strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label|code|summary"] - Additional text style tags.
136
- * @property {Object<string, string>} [convertTextTags={bold: "strong", underline: "u", italic: "em", strike: "del", subscript: "sub", superscript: "sup"}] - Maps text styles to specific HTML tags.
137
- * @property {Object<string, string>} [__tagStyles={'table|th|td': 'border|border-[a-z]+|background-color|text-align|float|font-weight|text-decoration|font-style', 'ol|ul': 'list-style-type'}] - The basic tags that serves as the base for "tagStyles"
138
- * @property {Object<string, string>} [tagStyles={}] - Specifies allowed styles for HTML tags.
139
- * @property {string} [spanStyles="font-family|font-size|color|background-color"] - Specifies allowed styles for the "span" tag.
140
- * @property {string} [lineStyles="text-align|margin-left|margin-right|line-height"] - Specifies allowed styles for the "line" element (p..).
141
- * @property {string} [textDirection="ltr"] - Text direction: "ltr" or "rtl".
142
- * @property {Array<string>} [reverseButtons=['indent-outdent']] - An array of command pairs whose shortcut icons should be opposite each other, depending on the "textDirection" mode.
143
- * @property {number} [historyStackDelayTime=400] - Delay time for history stack updates (ms).
144
- * @property {string} [lineAttrReset=""] - Line properties that should be reset when changing lines (e.g. "id|name").
145
- * @property {string} [printClass=""] - Class name for printing.
146
- * @property {string} [defaultLine="p"] - Default line element when inserting new lines.
147
- * @property {"line"|"br"} [defaultLineBreakFormat="line"] - Specifies the default line break format.
148
- * - [Recommended] "line" : is a line break that is divided into general tags.
149
- * - [Not recommended] "br" : Line breaks are treated as <br> on the same line. (like shift+enter)
150
- * - Line breaks are handled as <br> within "line".
151
- * - You can create a new "line" by entering a line break twice in a row.
152
- * - Formats that include "line", such as "Quote", still operate on a "line" basis.
153
- * - ● suneditor processes work in "line" units.
154
- * - When set to "br", performance may decrease when editing a lot of data.
155
- * @property {Array<string>} [scopeSelectionTags=["td", "table", "li", "ol", "ul", "pre", "figcaption", "blockquote", "dl", "dt", "dd"]] - Tags treated as whole units when selecting all content.
156
- * @property {string} [__defaultElementWhitelist="br|div"] - Default allowed HTML elements. The default values are maintained.
157
- * @property {string} [elementWhitelist=""] - Allowed HTML elements. Delimiter: "|" (e.g. "p|div", "*").
158
- * @property {string} [elementBlacklist=""] - Disallowed HTML elements. Delimiter: "|" (e.g. "script|style").
159
- * @property {string} [__defaultAttributeWhitelist] - Allowed attributes. Delimiter: "|" (e.g. "href|target").
160
- * @property {Object<string, string>} [attributeWhitelist=""] - Allowed attributes. (e.g. {a: "href|target", img: "src|alt"}).
161
- * @property {Object<string, string>} [attributeBlacklist=""] - Disallowed attributes. (e.g. {a: "href|target", img: "src|alt"}).
162
- * @property {string} [__defaultFormatLine="P|DIV|H[1-6]|LI|TH|TD|DETAILS"] - Overrides the editor's default "line" element.
163
- * @property {string} [formatLine="P|DIV|H[1-6]|LI|TH|TD|DETAILS"] - Specifies the editor's "line" elements.
164
- * - (P, DIV, H[1-6], PRE, LI | class="__se__format__line_xxx")
165
- * - "line" element also contain "brLine" element
166
- * @property {string} [__defaultFormatBrLine="PRE"] - Overrides the editor's default "brLine" element.
167
- * @property {string} [formatBrLine="PRE"] - Specifies the editor's "brLine" elements. (e.g. "PRE").
168
- * - (PRE | class="__se__format__br_line_xxx")
169
- * - "brLine" elements is included in the "line" element.
170
- * - "brLine" elements's line break is "BR" tag.
171
- * ※ Entering the Enter key in the space on the last line ends "brLine" and appends "line".
172
- * @property {string} [__defaultFormatClosureBrLine=""] - Overrides the editor's default "closureBrLine" element.
173
- * @property {string} [formatClosureBrLine=""] - Specifies the editor's "closureBrLine" elements.
174
- * - (class="__se__format__br_line__closure_xxx")
175
- * - "closureBrLine" elements is included in the "brLine".
176
- * - "closureBrLine" elements's line break is "BR" tag.
177
- * - ※ You cannot exit this format with the Enter key or Backspace key.
178
- * - Use it only in special cases. ([ex] format of table cells)
179
- * @property {string} [__defaultFormatBlock="BLOCKQUOTE|OL|UL|FIGCAPTION|TABLE|THEAD|TBODY|TR|CAPTION|DETAILS"] - Overrides the editor's default "block" element.
180
- * @property {string} [formatBlock="BLOCKQUOTE|OL|UL|FIGCAPTION|TABLE|THEAD|TBODY|TR|CAPTION|DETAILS"] - Specifies the editor's "block" elements.
181
- * - (BLOCKQUOTE, OL, UL, FIGCAPTION, TABLE, THEAD, TBODY, TR, TH, TD | class="__se__format__block_xxx")
182
- * - "block" is wrap the "line" and "component"
183
- * @property {string} [__defaultFormatClosureBlock="TH|TD"] - Overrides the editor's default "closureBlock" element.
184
- * @property {string} [formatClosureBlock="TH|TD"] - Specifies the editor's "closureBlock" elements.
185
- * - (TH, TD | class="__se__format__block_closure_xxx")
186
- * - "closureBlock" elements is included in the "block".
187
- * - "closureBlock" element is wrap the "line" and "component"
188
- * - ※ You cannot exit this format with the Enter key or Backspace key.
189
- * - ※ Use it only in special cases. ([ex] format of table cells)
190
- * @property {string} [allowedEmptyTags=".se-component, pre, blockquote, hr, li, table, img, iframe, video, audio, canvas, details"] - Allowed empty tags.
191
- * @property {number|string} [toolbar_width="auto"] - Toolbar width.
192
- * @property {Element|string} [toolbar_container] - Container element for the toolbar.
193
- * @property {number} [toolbar_sticky=0] - Enables sticky toolbar with optional offset.
194
- * @property {boolean} [toolbar_hide=false] - Hides toolbar initially.
195
- * @property {Object} [subToolbar] - Sub-toolbar configuration.
196
- * @property {Array<Array<string>>} [subToolbar.buttonList] - List of Sub-toolbar buttons, grouped by sub-arrays.
197
- * @property {"balloon"|"balloon-always"} [subToolbar.mode="balloon"] - Sub-toolbar mode: "balloon", "balloon-always".
198
- * @property {number|string} [subToolbar.width="auto"] - Sub-toolbar width.
199
- * @property {Element|string} [statusbar_container] - Container element for the status bar.
200
- * @property {boolean} [shortcutsHint=true] - Displays shortcut hints in tooltips.
201
- * @property {boolean} [shortcutsDisable=false] - Disables keyboard shortcuts.
202
- * @property {Object<string, Array<string>>} [shortcuts] - Custom keyboard shortcuts.
203
- * @property {number} [fullScreenOffset=0] - Offset applied when entering fullscreen mode.
204
- * @property {string} [previewTemplate] - Custom template for preview mode.
205
- * @property {string} [printTemplate] - Custom template for print mode.
206
- * @property {boolean} [componentAutoSelect=false] - Enables automatic selection of inserted components.
207
- * @property {string} [defaultUrlProtocol] - Default URL protocol for links.
208
- * @property {string} [allUsedStyles] - Specifies additional styles to the list of allowed styles. Delimiter: "|" (e.g. "color|background-color").
209
- * @property {Object<"copy", number>} [toastMessageTime] - {"copy": 1500} - Duration for displaying toast messages.
210
- * @property {Object<string, string>} [icons] - Overrides the default icons.
211
- * @property {string} [freeCodeViewMode=false] - Enables free code view mode.
212
- * @property {boolean} [__lineFormatFilter=true] - Line format filter configuration.
213
- * @property {boolean} [__pluginRetainFilter=true] - Plugin retain filter configuration.
214
- * @property {Array<string>} [__listCommonStyle=["fontSize", "color", "fontFamily", "fontWeight", "fontStyle"]] - Defines the list of styles that are applied directly to the `<li>` element
215
- * - when a text style is applied to the entire list item.
216
- * - For example, when changing the font size or color of a list item (`<li>`),
217
- * - these styles will be applied to the `<li>` tag instead of wrapping the content inside additional tags.
218
- * @property {Object<string, *>} [externalLibs] - External libraries like CodeMirror or MathJax.
219
- *
220
- * @property {Object<string, *>} [Dynamic_pluginOptions] - Dynamic plugin options, where the key is the plugin name and the value is its configuration.
221
- */
222
-
223
- /**
224
- * @typedef {EditorBaseOptions & EditorFrameOptions} EditorInitOptions
225
- */
226
-
227
- /**
228
- * @description For all EditorInitOptions keys, only boolean | null values are allowed.
229
- * - 'fixed' → Immutable / null → Resettable.
230
- * @type {Partial<Record<keyof EditorInitOptions, "fixed" | true>>}
231
- */
232
- export const OPTION_FIXED_FLAG = {
233
- value: 'fixed',
234
- placeholder: 'fixed',
235
- editableFrameAttributes: null,
236
- width: null,
237
- minWidth: null,
238
- maxWidth: null,
239
- height: null,
240
- minHeight: null,
241
- maxHeight: null,
242
- editorStyle: null,
243
- iframe: 'fixed',
244
- iframe_fullPage: null,
245
- iframe_attributes: null,
246
- iframe_cssFileName: null,
247
- statusbar: null,
248
- statusbar_showPathLabel: null,
249
- statusbar_resizeEnable: null,
250
- charCounter: null,
251
- charCounter_max: null,
252
- charCounter_label: null,
253
- charCounter_type: null,
254
- plugins: null,
255
- excludedPlugins: null,
256
- buttonList: 'fixed',
257
- v2Migration: null,
258
- strictMode: null,
259
- mode: 'fixed',
260
- type: 'fixed',
261
- theme: null,
262
- lang: 'fixed',
263
- fontSizeUnits: 'fixed',
264
- allowedClassName: null,
265
- closeModalOutsideClick: null,
266
- copyFormatKeepOn: null,
267
- syncTabIndent: null,
268
- tabDisable: null,
269
- autoLinkify: null,
270
- autoStyleify: null,
271
- scrollToOptions: null,
272
- componentScrollToOptions: null,
273
- retainStyleMode: null,
274
- allowedExtraTags: null,
275
- events: null,
276
- __textStyleTags: 'fixed',
277
- textStyleTags: 'fixed',
278
- convertTextTags: 'fixed',
279
- __tagStyles: null,
280
- tagStyles: 'fixed',
281
- spanStyles: 'fixed',
282
- lineStyles: 'fixed',
283
- textDirection: null,
284
- reverseButtons: null,
285
- historyStackDelayTime: null,
286
- lineAttrReset: null,
287
- printClass: null,
288
- defaultLine: 'fixed',
289
- defaultLineBreakFormat: null,
290
- scopeSelectionTags: null,
291
- __defaultElementWhitelist: 'fixed',
292
- elementWhitelist: 'fixed',
293
- elementBlacklist: 'fixed',
294
- __defaultAttributeWhitelist: 'fixed',
295
- attributeWhitelist: 'fixed',
296
- attributeBlacklist: 'fixed',
297
- __defaultFormatLine: null,
298
- formatLine: 'fixed',
299
- __defaultFormatBrLine: null,
300
- formatBrLine: 'fixed',
301
- __defaultFormatClosureBrLine: 'fixed',
302
- formatClosureBrLine: 'fixed',
303
- __defaultFormatBlock: null,
304
- formatBlock: 'fixed',
305
- __defaultFormatClosureBlock: null,
306
- formatClosureBlock: 'fixed',
307
- allowedEmptyTags: null,
308
- toolbar_width: null,
309
- toolbar_container: 'fixed',
310
- toolbar_sticky: null,
311
- toolbar_hide: null,
312
- subToolbar: 'fixed',
313
- statusbar_container: 'fixed',
314
- shortcutsHint: null,
315
- shortcutsDisable: 'fixed',
316
- shortcuts: 'fixed',
317
- fullScreenOffset: null,
318
- previewTemplate: null,
319
- printTemplate: null,
320
- componentAutoSelect: null,
321
- defaultUrlProtocol: null,
322
- allUsedStyles: null,
323
- toastMessageTime: null,
324
- icons: 'fixed',
325
- freeCodeViewMode: null,
326
- __lineFormatFilter: null,
327
- __pluginRetainFilter: null,
328
- __listCommonStyle: 'fixed',
329
- externalLibs: 'fixed'
330
- };
331
-
332
- /**
333
- * @description Creates a new SunEditor instance with specified options.
334
- * @param {Array<{target: Element, key: *, options: EditorFrameOptions}>} editorTargets - Target element or multi-root object.
335
- * @param {EditorInitOptions} options - Configuration options for the editor.
336
- * @returns {Object<string, *>} - SunEditor instance with context, options, and DOM elements.
337
- */
338
- function Constructor(editorTargets, options) {
339
- if (typeof options !== 'object') options = {};
340
-
341
- /** --- Plugins ------------------------------------------------------------------------------------------ */
342
- const plugins = {};
343
- if (options.plugins) {
344
- const excludedPlugins = options.excludedPlugins || [];
345
- const originPlugins = options.plugins;
346
- const pluginsValues = (Array.isArray(originPlugins) ? originPlugins : Object.keys(originPlugins)).filter((name) => !excludedPlugins.includes(name)).map((name) => originPlugins[name]);
347
-
348
- for (let i = 0, len = pluginsValues.length, p; i < len; i++) {
349
- p = pluginsValues[i].default || pluginsValues[i];
350
- plugins[p.key] = p;
351
- }
352
- }
353
-
354
- /** --- options --------------------------------------------------------------- */
355
- const optionMap = InitOptions(options, editorTargets, plugins);
356
- const o = optionMap.o;
357
- const icons = optionMap.i;
358
- const lang = optionMap.l;
359
- const loadingBox = dom.utils.createElement('DIV', { class: 'se-loading-box sun-editor-common' }, '<div class="se-loading-effect"></div>');
360
-
361
- /** --- carrier wrapper --------------------------------------------------------------- */
362
- const editor_carrier_wrapper = dom.utils.createElement('DIV', { class: 'sun-editor sun-editor-carrier-wrapper sun-editor-common' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
363
- // menuTray
364
- const menuTray = dom.utils.createElement('DIV', { class: 'se-menu-tray' });
365
- editor_carrier_wrapper.appendChild(menuTray);
366
- // focus temp element
367
- const focusTemp = /** @type {HTMLInputElement} */ (
368
- dom.utils.createElement('INPUT', {
369
- class: '__se__focus__temp__',
370
- style: 'position: fixed !important; top: -10000px !important; left: -10000px !important; display: block !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important;'
371
- })
372
- );
373
- focusTemp.tabIndex = 0;
374
- editor_carrier_wrapper.appendChild(focusTemp);
375
-
376
- // modal
377
- const modal = dom.utils.createElement('DIV', { class: 'se-modal se-modal-area sun-editor-common' });
378
- const modal_back = dom.utils.createElement('DIV', { class: 'se-modal-back' });
379
- const modal_inner = dom.utils.createElement('DIV', { class: 'se-modal-inner' });
380
- modal.appendChild(modal_back);
381
- modal.appendChild(modal_inner);
382
- editor_carrier_wrapper.appendChild(modal);
383
-
384
- // alert
385
- const alert = dom.utils.createElement('DIV', { class: 'se-alert se-modal-area sun-editor-common', style: 'display: none;' });
386
- const alert_back = dom.utils.createElement('DIV', { class: 'se-modal-back' });
387
- const alert_inner = dom.utils.createElement('DIV', { class: 'se-modal-inner' });
388
- alert.appendChild(alert_back);
389
- alert.appendChild(alert_inner);
390
- editor_carrier_wrapper.appendChild(alert);
391
-
392
- // loding box, resizing back
393
- editor_carrier_wrapper.appendChild(dom.utils.createElement('DIV', { class: 'se-back-wrapper' }));
394
- editor_carrier_wrapper.appendChild(loadingBox.cloneNode(true));
395
-
396
- // drag cursor
397
- const dragCursor = dom.utils.createElement('DIV', { class: 'se-drag-cursor' });
398
- editor_carrier_wrapper.appendChild(dragCursor);
399
-
400
- // set carrier wrapper
401
- _d.body.appendChild(editor_carrier_wrapper);
402
-
403
- /** --- toolbar --------------------------------------------------------------- */
404
- let subbar = null,
405
- sub_main = null;
406
- const tool_bar_main = CreateToolBar(optionMap.buttons, plugins, o, icons, lang, false);
407
- const toolbar = tool_bar_main.element;
408
- toolbar.style.visibility = 'hidden';
409
- // toolbar mode
410
- if (/inline/i.test(o.get('mode'))) {
411
- toolbar.className += ' se-toolbar-inline';
412
- toolbar.style.width = o.get('toolbar_width');
413
- } else if (/balloon/i.test(o.get('mode'))) {
414
- toolbar.className += ' se-toolbar-balloon';
415
- toolbar.style.width = o.get('toolbar_width');
416
- toolbar.appendChild(dom.utils.createElement('DIV', { class: 'se-arrow' }));
417
- }
418
-
419
- /** --- subToolbar --------------------------------------------------------------- */
420
- if (optionMap.subButtons) {
421
- sub_main = CreateToolBar(optionMap.subButtons, plugins, o, icons, lang, false);
422
- subbar = sub_main.element;
423
- subbar.style.visibility = 'hidden';
424
- // subbar mode must be balloon-*
425
- subbar.className += ' se-toolbar-balloon se-toolbar-sub';
426
- subbar.style.width = o.get('toolbar.sub_width');
427
- subbar.appendChild(dom.utils.createElement('DIV', { class: 'se-arrow' }));
428
- }
429
-
430
- /** frame - root set - start -------------------------------------------------------------- */
431
- const rootId = editorTargets[0].key || null;
432
- const rootKeys = [];
433
- const frameRoots = new Map();
434
- const statusbarContainer = optionMap.statusbarContainer;
435
- let default_status_bar = null;
436
- for (let i = 0, len = editorTargets.length; i < len; i++) {
437
- const editTarget = editorTargets[i];
438
- const to = optionMap.frameMap.get(editTarget.key);
439
- const top_div = dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
440
- const container = dom.utils.createElement('DIV', { class: 'se-container' });
441
- const editor_div = dom.utils.createElement('DIV', { class: 'se-wrapper' + (o.get('type') === 'document' ? ' se-type-document' : '') });
442
-
443
- container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-shadow' }));
444
-
445
- // init element
446
- const initElements = _initTargetElements(editTarget.key, o, top_div, to);
447
- const bottomBar = initElements.bottomBar;
448
- const statusbar = bottomBar.statusbar;
449
- const wysiwyg_div = initElements.wysiwygFrame;
450
- const placeholder_span = initElements.placeholder;
451
- let textarea = initElements.codeView;
452
-
453
- // line breaker
454
- const line_breaker_t = dom.utils.createElement('DIV', { class: 'se-line-breaker-component se-line-breaker-component-t', title: lang.insertLine }, icons.line_break);
455
- const line_breaker_b = dom.utils.createElement('DIV', { class: 'se-line-breaker-component se-line-breaker-component-b', title: lang.insertLine }, icons.line_break);
456
-
457
- editor_div.appendChild(line_breaker_t);
458
- editor_div.appendChild(line_breaker_b);
459
-
460
- // append container
461
- if (placeholder_span) editor_div.appendChild(placeholder_span);
462
- container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-sticky-dummy' }));
463
- container.appendChild(editor_div);
464
-
465
- // statusbar
466
- if (statusbar) {
467
- if (statusbarContainer) {
468
- if (!default_status_bar) {
469
- statusbarContainer.appendChild(dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') }, statusbar));
470
- default_status_bar = statusbar;
471
- }
472
- } else {
473
- container.appendChild(statusbar);
474
- }
475
- }
476
-
477
- // loading bar
478
- container.appendChild(loadingBox.cloneNode(true));
479
-
480
- // root key
481
- const key = editTarget.key || null;
482
-
483
- // code view - wrapper
484
- const codeWrapper = dom.utils.createElement('DIV', { class: 'se-code-wrapper' }, textarea);
485
- codeWrapper.style.setProperty('display', 'none', 'important');
486
- editor_div.appendChild(codeWrapper);
487
-
488
- // check code mirror
489
- const codeMirrorEl = _checkCodeMirror(o, to, textarea);
490
- // not used code mirror
491
- if (textarea === codeMirrorEl) {
492
- // add line nubers
493
- const codeNumbers = dom.utils.createElement('TEXTAREA', { class: 'se-code-view-line', readonly: 'true' }, null);
494
- codeWrapper.insertBefore(codeNumbers, textarea);
495
- } else {
496
- textarea = codeMirrorEl;
497
- }
498
-
499
- // document type
500
- const documentTypeInner = { inner: null, page: null, pageMirror: null };
501
- if (o.get('_type_options').includes('header')) {
502
- documentTypeInner.inner = dom.utils.createElement('DIV', { class: 'se-document-lines', style: `height: ${to.get('height')};` }, '<div class="se-document-lines-inner"></div>');
503
- }
504
- if (o.get('_type_options').includes('page')) {
505
- documentTypeInner.page = dom.utils.createElement('DIV', { class: 'se-document-page' }, null);
506
- documentTypeInner.pageMirror = dom.utils.createElement(
507
- 'DIV',
508
- {
509
- class: 'sun-editor-editable se-document-page-mirror-a4',
510
- style: `position: absolute; width: 21cm; columns: 21cm; border: 0; overflow: hidden; height: auto; top: -10000px; left: -10000px;`
511
- },
512
- null
513
- );
514
- }
515
-
516
- // set container
517
- top_div.appendChild(container);
518
- rootKeys.push(key);
519
- frameRoots.set(key, CreateFrameContext({ target: editTarget.target, key: editTarget.key, options: to }, top_div, wysiwyg_div, codeWrapper, textarea, default_status_bar || statusbar, documentTypeInner, key));
520
- }
521
- /** frame - root set - end -------------------------------------------------------------- */
522
-
523
- // toolbar container
524
- const toolbar_container = o.get('toolbar_container');
525
- if (toolbar_container) {
526
- const top_div = dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
527
- const container = dom.utils.createElement('DIV', { class: 'se-container' });
528
- container.appendChild(toolbar);
529
- if (subbar) container.appendChild(subbar);
530
- top_div.appendChild(container);
531
- toolbar_container.appendChild(top_div);
532
- toolbar_container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-sticky-dummy' }));
533
- } else {
534
- const rootContainer = frameRoots.get(rootId).get('container');
535
- rootContainer.insertBefore(toolbar, rootContainer.firstElementChild);
536
- if (subbar) rootContainer.insertBefore(subbar, rootContainer.firstElementChild);
537
- }
538
-
539
- return {
540
- context: CreateContext(toolbar, toolbar_container, menuTray, subbar, statusbarContainer),
541
- carrierWrapper: editor_carrier_wrapper,
542
- options: o,
543
- plugins: plugins,
544
- icons: icons,
545
- lang: lang,
546
- value: optionMap.v,
547
- rootId: rootId,
548
- rootKeys: rootKeys,
549
- frameRoots: frameRoots,
550
- pluginCallButtons: tool_bar_main.pluginCallButtons,
551
- responsiveButtons: tool_bar_main.responsiveButtons,
552
- pluginCallButtons_sub: sub_main ? sub_main.pluginCallButtons : [],
553
- responsiveButtons_sub: sub_main ? sub_main.responsiveButtons : []
554
- };
555
- }
556
-
557
- /**
558
- * @description Create shortcuts desc span.
559
- * @param {string} command Command string
560
- * @param {Array<string>} values options.shortcuts[command]
561
- * @param {Element|null} button Command button element
562
- * @param {Map<string, *>} keyMap Map to store shortcut key info
563
- * @param {Array} rc "_reverseCommandArray" option
564
- * @param {Array} reverseKeys Reverse key array
565
- */
566
- export function CreateShortcuts(command, button, values, keyMap, rc, reverseKeys) {
567
- if (!values || values.length < 2) return;
568
- const tooptip = button?.querySelector('.se-tooltip-text');
569
-
570
- for (let i = 0, a, v, c, s, edge, space, enter, textTrigger, plugin, method, t, k, r, _i; i < values.length; i += 2 + _i) {
571
- _i = 0;
572
- a = values[i].split('+');
573
-
574
- plugin = null;
575
- method = a[a.length - 1].trim?.();
576
- if (method.startsWith('~')) {
577
- plugin = command;
578
- method = a.pop().trim().substring(1);
579
- } else if (method.startsWith('p~')) {
580
- const a_ = a.pop().trim().substring(2).split('.');
581
- plugin = a_[0];
582
- method = a_[1];
583
- } else if (method.startsWith('$')) {
584
- _i = 1;
585
- method = values[i + 2];
586
- } else {
587
- method = '';
588
- }
589
-
590
- c = s = edge = space = enter = textTrigger = v = null;
591
- for (const a_ of a) {
592
- switch (a_.trim()) {
593
- case 'c':
594
- c = true;
595
- break;
596
- case '!':
597
- edge = true;
598
- break;
599
- case 's':
600
- s = true;
601
- break;
602
- case '_':
603
- space = true;
604
- break;
605
- case '=':
606
- textTrigger = true;
607
- break;
608
- case '/':
609
- enter = true;
610
- break;
611
- default:
612
- v = a_;
613
- }
614
- }
615
-
616
- k = c ? v + (s ? '1000' : '') : v;
617
- if (!keyMap.has(k)) {
618
- r = rc.indexOf(command);
619
- r = r === -1 ? '' : numbers.isOdd(r) ? rc[r + 1] : rc[r - 1];
620
- if (r) reverseKeys.push(k);
621
-
622
- keyMap.set(k, { c, s, edge, space, enter, textTrigger, plugin, command, method, r, type: button?.getAttribute('data-type'), button, key: k });
623
- }
624
-
625
- if (!(t = values[i + 1])) continue;
626
- if (tooptip) _addTooltip(tooptip, s, t);
627
- }
628
- }
629
-
630
- function _addTooltip(tooptipBtn, shift, shortcut) {
631
- tooptipBtn.appendChild(dom.utils.createElement('SPAN', { class: 'se-shortcut' }, env.cmdIcon + (shift ? env.shiftIcon : '') + '+<span class="se-shortcut-key">' + shortcut + '</span>'));
632
- }
633
-
634
- /**
635
- * @private
636
- * @description Returns a new object with merge "a" and "b"
637
- * @param {Object<*, *>} a object
638
- * @param {Object<*, *>} b object
639
- * @returns {Object<*, *>} new object
640
- */
641
- function _mergeObject(a, b) {
642
- return [a, b].reduce((_default, _new) => {
643
- for (const key in _new) {
644
- _default[key] = (_new[key] || '').toLowerCase();
645
- }
646
- return _default;
647
- }, {});
648
- }
649
-
650
- /**
651
- * @description Initialize options
652
- * @param {EditorInitOptions} options Configuration options for the editor.
653
- * @param {Array<{target: Element, key: *, options: EditorFrameOptions}>} editorTargets Target textarea
654
- * @param {Object<string, *>} plugins Plugins object
655
- * @returns {{o: Map<string, *>, i: Object<string, string>, l: Object<string, string>, v: string, buttons: Array<string[]|string>, subButtons: Array<string[]|string>, statusbarContainer: Element|null, frameMap: Map<*, *>}}
656
- * - o: options
657
- * - i: icons
658
- * - l: lang
659
- * - v: value
660
- * - buttons: Toolbar button list
661
- * - subButtons: Sub-Toolbar button list
662
- * - statusbarContainer: statusbar container
663
- * - frameMap: converted options map
664
- */
665
- export function InitOptions(options, editorTargets, plugins) {
666
- const buttonList = options.buttonList || DEFAULT_BUTTON_LIST;
667
- const o = new Map();
668
-
669
- /** Multi root */
670
- if (editorTargets.length > 1) {
671
- if (!options.toolbar_container && !/inline|balloon/i.test(options.mode)) throw Error('[SUNEDITOR.create.fail] In multi root, The "mode" option cannot be "classic" without using the "toolbar_container" option.');
672
- }
673
-
674
- // migration data-.+
675
- o.set('v2Migration', !!options.v2Migration);
676
-
677
- /** Base */
678
- o.set('buttons', new Set(buttonList.toString().split(',')));
679
- const modeValue = options.strictMode !== false;
680
- o.set('strictMode', {
681
- tagFilter: modeValue,
682
- formatFilter: modeValue,
683
- classFilter: modeValue,
684
- styleNodeFilter: modeValue,
685
- attrFilter: modeValue,
686
- styleFilter: modeValue,
687
- ...(typeof options.strictMode === 'boolean' ? {} : options.strictMode)
688
- });
689
- o.set('freeCodeViewMode', !!options.freeCodeViewMode);
690
- o.set('__lineFormatFilter', options.__lineFormatFilter ?? true);
691
- o.set('__pluginRetainFilter', options.__pluginRetainFilter ?? true);
692
- o.set('mode', options.mode || 'classic'); // classic, inline, balloon, balloon-always
693
- o.set('type', options.type?.split(':')[0] || ''); // document:header,page
694
- o.set('theme', options.theme || '');
695
- o.set('_themeClass', options.theme ? ` se-theme-${options.theme}` : '');
696
- o.set('_type_options', options.type?.split(':')[1] || '');
697
- o.set('externalLibs', options.externalLibs || {});
698
- o.set('fontSizeUnits', Array.isArray(options.fontSizeUnits) && options.fontSizeUnits.length > 0 ? options.fontSizeUnits.map((v) => v.toLowerCase()) : DEFAULT_SIZE_UNITS);
699
- o.set('allowedClassName', new RegExp(`${options.allowedClassName && typeof options.allowedClassName === 'string' ? options.allowedClassName + '|' : ''}${DEFAULT_CLASS_NAME}`));
700
- o.set('closeModalOutsideClick', !!options.closeModalOutsideClick);
701
-
702
- // format
703
- o.set('copyFormatKeepOn', !!options.copyFormatKeepOn);
704
- o.set('syncTabIndent', options.syncTabIndent ?? true);
705
-
706
- // auto convert on paste
707
- o.set('autoLinkify', options.autoLinkify ?? !!plugins.link);
708
- o.set('autoStyleify', Array.isArray(options.autoStyleify) ? options.autoStyleify : ['bold', 'underline', 'italic', 'strike']);
709
-
710
- // scroll options
711
- o.set('scrollToOptions', { behavior: 'auto', block: 'nearest', ...options.scrollToOptions });
712
- o.set('componentScrollToOptions', { behavior: 'smooth', block: 'center', ...options.componentScrollToOptions });
713
-
714
- let retainStyleMode = options.retainStyleMode;
715
- if (typeof retainStyleMode === 'string' && !RETAIN_STYLE_MODE.includes(retainStyleMode)) {
716
- console.error(`Invalid retainStyleMode: ${retainStyleMode}. Valid options are ${RETAIN_STYLE_MODE.join(', ')}. Using default 'once'.`);
717
- retainStyleMode = 'repeat';
718
- }
719
- o.set('retainStyleMode', retainStyleMode);
720
-
721
- const allowedExtraTags = { ...DEFAULT_EXTRA_TAG_MAP, ...options.allowedExtraTags, '-': true };
722
- const extraKeys = Object.keys(allowedExtraTags);
723
- const allowedKeys = extraKeys.filter((k) => allowedExtraTags[k]).join('|');
724
- const disallowedKeys = extraKeys.filter((k) => !allowedExtraTags[k]).join('|');
725
- o.set('_allowedExtraTag', allowedKeys);
726
- o.set('_disallowedExtraTag', disallowedKeys);
727
-
728
- o.set('events', options.events || {});
729
-
730
- // text style tags
731
- o.set('textStyleTags', (typeof options.__textStyleTags === 'string' ? options.__textStyleTags : DEFAULT_TEXT_STYLE_TAGS) + (options.textStyleTags ? '|' + options.textStyleTags : ''));
732
- const textTags = _mergeObject(
733
- {
734
- bold: 'strong',
735
- underline: 'u',
736
- italic: 'em',
737
- strike: 'del',
738
- subscript: 'sub',
739
- superscript: 'sup'
740
- },
741
- options.convertTextTags || {}
742
- );
743
- o.set('convertTextTags', textTags);
744
- o.set('_textStyleTags', Object.values(textTags).concat(['span', 'li']));
745
- o.set(
746
- 'tagStyles',
747
- [{ ...DEFAULT_TAG_STYLES, ...(options.__tagStyles || {}) }, options.tagStyles || {}].reduce((_default, _new) => {
748
- for (const key in _new) {
749
- _default[key] = _new[key];
750
- }
751
- return _default;
752
- }, {})
753
- );
754
- o.set('_textStylesRegExp', new RegExp(`\\s*[^-a-zA-Z](${DEFAULT_TEXT_STYLES}${options.spanStyles ? '|' + options.spanStyles : ''})\\s*:[^;]+(?!;)*`, 'gi'));
755
- o.set('_lineStylesRegExp', new RegExp(`\\s*[^-a-zA-Z](${DEFAULT_LINE_STYLES}${options.lineStyles ? '|' + options.lineStyles : ''})\\s*:[^;]+(?!;)*`, 'gi'));
756
- o.set('_defaultStyleTagMap', {
757
- strong: textTags.bold,
758
- b: textTags.bold,
759
- u: textTags.underline,
760
- ins: textTags.underline,
761
- em: textTags.italic,
762
- i: textTags.italic,
763
- del: textTags.strike,
764
- strike: textTags.strike,
765
- s: textTags.strike,
766
- sub: textTags.subscript,
767
- sup: textTags.superscript
768
- });
769
- o.set(
770
- '_styleCommandMap',
771
- _mergeObject(converter.swapKeyValue(textTags), {
772
- strong: 'bold',
773
- b: 'bold',
774
- u: 'underline',
775
- ins: 'underline',
776
- em: 'italic',
777
- i: 'italic',
778
- del: 'strike',
779
- strike: 'strike',
780
- s: 'strike',
781
- sub: 'subscript',
782
- sup: 'superscript'
783
- })
784
- );
785
- o.set('_defaultTagCommand', {
786
- bold: textTags.bold,
787
- underline: textTags.underline,
788
- italic: textTags.italic,
789
- strike: textTags.strike,
790
- subscript: textTags.sub,
791
- superscript: textTags.sup
792
- });
793
- // text direction
794
- o.set('textDirection', typeof options.textDirection !== 'string' ? 'ltr' : options.textDirection);
795
- o.set('_rtl', o.get('textDirection') === 'rtl');
796
- // An array of key codes generated with the reverseButtons option, used to reverse the action for a specific key combination.
797
- o.set('reverseCommands', ['indent-outdent'].concat(options.reverseButtons || []));
798
- o.set('_reverseCommandArray', ('-' + o.get('reverseCommands').join('-')).split('-'));
799
- if (numbers.isEven(o.get('_reverseCommandArray').length)) {
800
- console.warn('[SUNEDITOR.create.warning] The "reverseCommands" option is invalid, Shortcuts key may not work properly.');
801
- }
802
-
803
- // etc
804
- o.set('historyStackDelayTime', typeof options.historyStackDelayTime === 'number' ? options.historyStackDelayTime : 400);
805
- o.set('_editableClass', 'sun-editor-editable' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') + (o.get('type') === 'document' ? ' se-type-document-editable' : ''));
806
- o.set('lineAttrReset', ['id'].concat(options.lineAttrReset && typeof options.lineAttrReset === 'string' ? options.lineAttrReset.toLowerCase().split('|') : []));
807
- o.set('printClass', typeof options.printClass === 'string' ? options.printClass + ' ' + o.get('_editableClass') : null);
808
-
809
- /** whitelist, blacklist */
810
- // default line
811
- o.set('defaultLine', typeof options.defaultLine === 'string' && options.defaultLine.length > 0 ? options.defaultLine : 'p');
812
- o.set('_defaultBrLineBreak', options.defaultLineBreakFormat === 'br');
813
- o.set('scopeSelectionTags', options.scopeSelectionTags || DEFAULT_SCOPE_SELECTION_TAGS.split('|'));
814
- // element
815
- const elw = (typeof options.elementWhitelist === 'string' ? options.elementWhitelist : '').toLowerCase();
816
- const mjxEls = o.get('externalLibs').mathjax ? DEFAULT_CLASS_MJX + '|' : '';
817
- o.set('elementWhitelist', elw + (elw ? '|' : '') + mjxEls + o.get('_allowedExtraTag'));
818
- const elb = _createBlacklist((typeof options.elementBlacklist === 'string' ? options.elementBlacklist : '').toLowerCase(), o.get('defaultLine'));
819
- o.set('elementBlacklist', elb + (elb ? '|' : '') + o.get('_disallowedExtraTag'));
820
- // attribute
821
- o.set('attributeWhitelist', !options.attributeWhitelist || typeof options.attributeWhitelist !== 'object' ? null : options.attributeWhitelist);
822
- o.set('attributeBlacklist', !options.attributeBlacklist || typeof options.attributeBlacklist !== 'object' ? null : options.attributeBlacklist);
823
- // format tag
824
- o.set(
825
- 'formatClosureBrLine',
826
- _createFormatInfo(
827
- options.formatClosureBrLine,
828
- (options.__defaultFormatClosureBrLine = typeof options.__defaultFormatClosureBrLine === 'string' ? options.__defaultFormatClosureBrLine : DEFAULT_FORMAT_CLOSURE_BR_LINE).toLowerCase(),
829
- o.get('elementBlacklist')
830
- )
831
- );
832
- o.set(
833
- 'formatBrLine',
834
- _createFormatInfo(
835
- (options.formatBrLine || '') + '|' + o.get('formatClosureBrLine').str,
836
- (options.__defaultFormatBrLine = typeof options.__defaultFormatBrLine === 'string' ? options.__defaultFormatBrLine : DEFAULT_FORMAT_BR_LINE).toLowerCase(),
837
- o.get('elementBlacklist')
838
- )
839
- );
840
- o.set(
841
- 'formatLine',
842
- _createFormatInfo(
843
- REQUIRED_FORMAT_LINE + '|' + (options.formatLine || '') + '|' + o.get('formatBrLine').str,
844
- (options.__defaultFormatLine = typeof options.__defaultFormatLine === 'string' ? options.__defaultFormatLine : DEFAULT_FORMAT_LINE).toLowerCase(),
845
- o.get('elementBlacklist')
846
- )
847
- );
848
-
849
- // Error - default line
850
- if (!o.get('formatLine').reg.test(o.get('defaultLine'))) {
851
- throw Error(`[SUNEDITOR.create.fail] The "defaultLine(${o.get('defaultLine')})" option must be included in the "formatLine(${o.get('formatLine').str})" option.`);
852
- }
853
-
854
- o.set(
855
- 'formatClosureBlock',
856
- _createFormatInfo(
857
- options.formatClosureBlock,
858
- (options.__defaultFormatClosureBlock = typeof options.__defaultFormatClosureBlock === 'string' ? options.__defaultFormatClosureBlock : DEFAULT_FORMAT_CLOSURE_BLOCK).toLowerCase(),
859
- o.get('elementBlacklist')
860
- )
861
- );
862
- o.set(
863
- 'formatBlock',
864
- _createFormatInfo(
865
- (options.formatBlock || '') + '|' + o.get('formatClosureBlock').str,
866
- (options.__defaultFormatBlock = typeof options.__defaultFormatBlock === 'string' ? options.__defaultFormatBlock : DEFAULT_FORMAT_BLOCK).toLowerCase(),
867
- o.get('elementBlacklist')
868
- )
869
- );
870
-
871
- o.set('allowedEmptyTags', DEFAULT_ALLOWED_EMPTY_NODE_LIST + (options.allowedEmptyTags ? ', ' + options.allowedEmptyTags : ''));
872
-
873
- /** __defaults */
874
- o.set('__defaultElementWhitelist', REQUIRED_ELEMENT_WHITELIST + '|' + (typeof options.__defaultElementWhitelist === 'string' ? options.__defaultElementWhitelist : DEFAULT_ELEMENT_WHITELIST).toLowerCase());
875
- o.set('__defaultAttributeWhitelist', (typeof options.__defaultAttributeWhitelist === 'string' ? options.__defaultAttributeWhitelist : DEFAULT_ATTRIBUTE_WHITELIST).toLowerCase());
876
- // --- create element whitelist (__defaultElementWhiteList + elementWhitelist + format[line, BrLine, Block, Closureblock, ClosureBrLine] - elementBlacklist)
877
- o.set('_editorElementWhitelist', o.get('elementWhitelist') === '*' ? '*' : _createWhitelist(o));
878
-
879
- /** Toolbar */
880
- o.set('toolbar_width', options.toolbar_width ? (numbers.is(options.toolbar_width) ? options.toolbar_width + 'px' : options.toolbar_width) : 'auto');
881
- o.set('toolbar_container', options.toolbar_container && !/inline/i.test(o.get('mode')) ? (typeof options.toolbar_container === 'string' ? _d.querySelector(options.toolbar_container) : options.toolbar_container) : null);
882
- o.set('toolbar_sticky', /balloon/i.test(o.get('mode')) ? -1 : options.toolbar_sticky === undefined ? 0 : numbers.is(options.toolbar_sticky) ? numbers.get(options.toolbar_sticky, 0) : -1);
883
- o.set('toolbar_hide', !!options.toolbar_hide);
884
-
885
- /** subToolbar */
886
- let subButtons = null;
887
- const subbar = options.subToolbar;
888
- if (subbar?.buttonList?.length > 0) {
889
- if (/balloon/.test(o.get('mode'))) {
890
- console.warn('[SUNEDITOR.create.subToolbar.fail] When the "mode" option is "balloon-*", the "subToolbar" option is omitted.');
891
- } else {
892
- o.set('_subMode', subbar.mode || 'balloon');
893
- o.set('toolbar.sub_width', subbar.width ? (numbers.is(subbar.width) ? subbar.width + 'px' : subbar.width) : 'auto');
894
- subButtons = o.get('_rtl') ? subbar.buttonList.reverse() : subbar.buttonList;
895
- o.set('buttons_sub', new Set(subButtons.toString().split(',')));
896
- }
897
- }
898
-
899
- /** root options */
900
- const frameMap = new Map();
901
- for (let i = 0, len = editorTargets.length; i < len; i++) {
902
- frameMap.set(editorTargets[i].key, InitFrameOptions(editorTargets[i].options || {}, options));
903
- }
904
-
905
- /** Key actions */
906
- o.set('tabDisable', !!options.tabDisable);
907
- o.set('shortcutsHint', options.shortcutsHint === undefined ? true : !!options.shortcutsHint);
908
- const shortcuts = !(options.shortcutsDisable === undefined ? true : !!options.shortcutsDisable)
909
- ? {}
910
- : [
911
- {
912
- // default command
913
- selectAll: ['c+KeyA', 'A'],
914
- bold: ['c+KeyB', 'B'],
915
- strike: ['c+s+KeyS', 'S'],
916
- underline: ['c+KeyU', 'U'],
917
- italic: ['c+KeyI', 'I'],
918
- redo: ['c+KeyY', 'Y', 'c+s+KeyZ', 'Z'],
919
- undo: ['c+KeyZ', 'Z'],
920
- indent: ['c+BracketRight', ']'],
921
- outdent: ['c+BracketLeft', '['],
922
- save: ['c+KeyS', 'S'],
923
- // plugins
924
- link: ['c+KeyK', 'K'],
925
- hr: ['!+---+=+~shortcut', ''],
926
- list_numbered: ['!+1.+_+~shortcut', ''],
927
- list_bulleted: ['!+*.+_+~shortcut', ''],
928
- // custom
929
- _h1: ['c+s+Digit1+p~formatBlock.createHeader', ''],
930
- _h2: ['c+s+Digit2+p~formatBlock.createHeader', ''],
931
- _h3: ['c+s+Digit3+p~formatBlock.createHeader', '']
932
- },
933
- options.shortcuts || {}
934
- ].reduce((_default, _new) => {
935
- for (const key in _new) {
936
- _default[key] = _new[key];
937
- }
938
- return _default;
939
- }, {});
940
- o.set('shortcuts', shortcuts);
941
-
942
- /** View */
943
- o.set('fullScreenOffset', options.fullScreenOffset === undefined ? 0 : numbers.is(options.fullScreenOffset) ? numbers.get(options.fullScreenOffset, 0) : 0);
944
- o.set('previewTemplate', typeof options.previewTemplate === 'string' ? options.previewTemplate : null);
945
- o.set('printTemplate', typeof options.printTemplate === 'string' ? options.printTemplate : null);
946
-
947
- /** --- Media select */
948
- o.set('componentAutoSelect', options.componentAutoSelect === undefined ? false : !!options.componentAutoSelect);
949
-
950
- /** --- Url input protocol */
951
- o.set('defaultUrlProtocol', typeof options.defaultUrlProtocol === 'string' ? options.defaultUrlProtocol : null);
952
-
953
- /** External library */
954
- // CodeMirror
955
- const cm = o.get('externalLibs').codeMirror;
956
- if (cm) {
957
- o.set('codeMirror', cm);
958
- if (cm.EditorView) {
959
- o.set('codeMirror6Editor', true);
960
- } else if (cm.src) {
961
- o.set('codeMirror5Editor', true);
962
- } else {
963
- console.warn('[SUNEDITOR.options.externalLibs.codeMirror.fail] The codeMirror option is set incorrectly.');
964
- o.set('codeMirror', null);
965
- }
966
- }
967
-
968
- /** Private options */
969
- o.set('__listCommonStyle', options.__listCommonStyle || ['fontSize', 'color', 'fontFamily', 'fontWeight', 'fontStyle']);
970
-
971
- /** --- Icons ------------------------------------------------------------------------------------------ */
972
- const icons =
973
- !options.icons || typeof options.icons !== 'object'
974
- ? _icons
975
- : [_icons, options.icons].reduce((_default, _new) => {
976
- for (const key in _new) {
977
- _default[key] = _new[key];
978
- }
979
- return _default;
980
- }, {});
981
- o.set('icons', icons);
982
-
983
- /** Create all used styles */
984
- const allUsedStyles = new Set(DEFAULT_CONTENT_STYLES.split('|'));
985
- const _ss = options.spanStyles?.split('|') || [];
986
- const _ls = o.get('__listCommonStyle');
987
- const _dts = DEFAULT_TEXT_STYLES.split('|');
988
- for (let i = 0, len = _dts.length; i < len; i++) {
989
- allUsedStyles.add(_dts[i]);
990
- }
991
- for (const _ts of Object.values(o.get('tagStyles'))) {
992
- const _tss = _ts.split('|');
993
- for (let i = 0, len = _tss.length; i < len; i++) {
994
- allUsedStyles.add(_tss[i]);
995
- }
996
- }
997
- for (let i = 0, len = _ss.length; i < len; i++) {
998
- allUsedStyles.add(_ss[i]);
999
- }
1000
- for (let i = 0, len = _ls.length; i < len; i++) {
1001
- allUsedStyles.add(_ls[i]);
1002
- }
1003
- const _aus = (typeof options.allUsedStyles === 'string' ? options.allUsedStyles.split('|') : options.allUsedStyles) || [];
1004
- for (let i = 0, len = _aus.length; i < len; i++) {
1005
- allUsedStyles.add(_aus[i]);
1006
- }
1007
- o.set('allUsedStyles', allUsedStyles);
1008
- o.set('toastMessageTime', { copy: 1500, ...options.toastMessageTime });
1009
-
1010
- return {
1011
- o: o,
1012
- i: icons,
1013
- l: /** @type {Object<string, string>} */ (options.lang || _defaultLang),
1014
- v: (options.value = typeof options.value === 'string' ? options.value : null),
1015
- buttons: o.get('_rtl') ? buttonList.reverse() : buttonList,
1016
- subButtons: subButtons,
1017
- statusbarContainer: typeof options.statusbar_container === 'string' ? _d.querySelector(options.statusbar_container) : options.statusbar_container,
1018
- frameMap: frameMap
1019
- };
1020
- }
1021
-
1022
- /**
1023
- * @description Create a context object for the editor frame.
1024
- * @param {Map<string, *>} targetOptions - editor.frameOptions
1025
- * @param {HTMLElement} statusbar - statusbar element
1026
- * @returns {{statusbar: HTMLElement, navigation: HTMLElement, charWrapper: HTMLElement, charCounter: HTMLElement}}
1027
- */
1028
- export function CreateStatusbar(targetOptions, statusbar) {
1029
- let navigation = null;
1030
- let charWrapper = null;
1031
- let charCounter = null;
1032
-
1033
- if (targetOptions.get('statusbar')) {
1034
- statusbar = statusbar || dom.utils.createElement('DIV', { class: 'se-status-bar sun-editor-common' });
1035
-
1036
- /** navigation */
1037
- navigation = statusbar.querySelector('.se-navigation') || dom.utils.createElement('DIV', { class: 'se-navigation sun-editor-common' });
1038
- statusbar.appendChild(navigation);
1039
-
1040
- /** char counter */
1041
- if (targetOptions.get('charCounter')) {
1042
- charWrapper = statusbar.querySelector('.se-char-counter-wrapper') || dom.utils.createElement('DIV', { class: 'se-char-counter-wrapper' });
1043
-
1044
- if (targetOptions.get('charCounter_label')) {
1045
- const charLabel = charWrapper.querySelector('.se-char-label') || dom.utils.createElement('SPAN', { class: 'se-char-label' });
1046
- charLabel.textContent = targetOptions.get('charCounter_label');
1047
- charWrapper.appendChild(charLabel);
1048
- }
1049
-
1050
- charCounter = charWrapper.querySelector('.se-char-counter') || dom.utils.createElement('SPAN', { class: 'se-char-counter' });
1051
- charCounter.textContent = '0';
1052
- charWrapper.appendChild(charCounter);
1053
-
1054
- if (targetOptions.get('charCounter_max') > 0) {
1055
- const char_max = charWrapper.querySelector('.se-char-max') || dom.utils.createElement('SPAN', { class: 'se-char-max' });
1056
- char_max.textContent = ' / ' + targetOptions.get('charCounter_max');
1057
- charWrapper.appendChild(char_max);
1058
- }
1059
-
1060
- statusbar.appendChild(charWrapper);
1061
- }
1062
- }
1063
-
1064
- return {
1065
- statusbar: statusbar,
1066
- navigation: /** @type {HTMLElement} */ (navigation),
1067
- charWrapper: /** @type {HTMLElement} */ (charWrapper),
1068
- charCounter: /** @type {HTMLElement} */ (charCounter)
1069
- };
1070
- }
1071
-
1072
- /**
1073
- * @description Initialize options.
1074
- * @param {EditorFrameOptions} o - Target options
1075
- * @param {EditorInitOptions} origin - Full options
1076
- * @returns {Map<string, *>}
1077
- */
1078
- function InitFrameOptions(o, origin) {
1079
- const fo = new Map();
1080
-
1081
- fo.set('_origin', o);
1082
- const barContainer = origin.statusbar_container;
1083
-
1084
- // members
1085
- const value = o.value === undefined ? origin.value : o.value;
1086
- const placeholder = o.placeholder === undefined ? origin.placeholder : o.placeholder;
1087
- const editableFrameAttributes = o.editableFrameAttributes === undefined ? origin.editableFrameAttributes : o.editableFrameAttributes;
1088
- const width = o.width === undefined ? origin.width : o.width;
1089
- const minWidth = o.minWidth === undefined ? origin.minWidth : o.minWidth;
1090
- const maxWidth = o.maxWidth === undefined ? origin.maxWidth : o.maxWidth;
1091
- const height = o.height === undefined ? origin.height : o.height;
1092
- const minHeight = o.minHeight === undefined ? origin.minHeight : o.minHeight;
1093
- const maxHeight = o.maxHeight === undefined ? origin.maxHeight : o.maxHeight;
1094
- const editorStyle = o.editorStyle === undefined ? origin.editorStyle : o.editorStyle;
1095
- const iframe = o.iframe === undefined ? origin.iframe : o.iframe;
1096
- const iframe_fullPage = o.iframe_fullPage === undefined ? origin.iframe_fullPage : o.iframe_fullPage;
1097
- const iframe_attributes = o.iframe_attributes === undefined ? origin.iframe_attributes : o.iframe_attributes;
1098
- const iframe_cssFileName = o.iframe_cssFileName === undefined ? origin.iframe_cssFileName : o.iframe_cssFileName;
1099
- const statusbar = barContainer || o.statusbar === undefined ? origin.statusbar : o.statusbar;
1100
- const statusbar_showPathLabel = barContainer || o.statusbar_showPathLabel === undefined ? origin.statusbar_showPathLabel : o.statusbar_showPathLabel;
1101
- const statusbar_resizeEnable = barContainer ? false : o.statusbar_resizeEnable === undefined ? origin.statusbar_resizeEnable : o.statusbar_resizeEnable;
1102
- const charCounter = barContainer || o.charCounter === undefined ? origin.charCounter : o.charCounter;
1103
- const charCounter_max = barContainer || o.charCounter_max === undefined ? origin.charCounter_max : o.charCounter_max;
1104
- const charCounter_label = barContainer || o.charCounter_label === undefined ? origin.charCounter_label : o.charCounter_label;
1105
- const charCounter_type = barContainer || o.charCounter_type === undefined ? origin.charCounter_type : o.charCounter_type;
1106
-
1107
- // value
1108
- fo.set('value', value);
1109
- fo.set('placeholder', placeholder);
1110
- fo.set('editableFrameAttributes', editableFrameAttributes || {});
1111
- // styles
1112
- fo.set('width', width ? (numbers.is(width) ? width + 'px' : width) : '100%');
1113
- fo.set('minWidth', (numbers.is(minWidth) ? minWidth + 'px' : minWidth) || '');
1114
- fo.set('maxWidth', (numbers.is(maxWidth) ? maxWidth + 'px' : maxWidth) || '');
1115
- fo.set('height', height ? (numbers.is(height) ? height + 'px' : height) : 'auto');
1116
- fo.set('minHeight', (numbers.is(minHeight) ? minHeight + 'px' : minHeight) || '');
1117
- fo.set('maxHeight', (numbers.is(maxHeight) ? maxHeight + 'px' : maxHeight) || '');
1118
- fo.set('_defaultStyles', converter._setDefaultOptionStyle(fo, typeof editorStyle === 'string' ? editorStyle : ''));
1119
- // iframe
1120
- fo.set('iframe', !!(iframe_fullPage || iframe));
1121
- fo.set('iframe_fullPage', !!iframe_fullPage);
1122
- fo.set('iframe_attributes', iframe_attributes || {});
1123
- fo.set('iframe_cssFileName', iframe ? (typeof iframe_cssFileName === 'string' ? [iframe_cssFileName] : iframe_cssFileName || ['suneditor']) : null);
1124
- // status bar
1125
- const hasStatusbar = statusbar === undefined ? true : !!statusbar;
1126
- fo.set('statusbar', hasStatusbar);
1127
- fo.set('statusbar_showPathLabel', !hasStatusbar ? false : typeof statusbar_showPathLabel === 'boolean' ? statusbar_showPathLabel : true);
1128
- fo.set('statusbar_resizeEnable', !hasStatusbar ? false : statusbar_resizeEnable === undefined ? true : !!statusbar_resizeEnable);
1129
- // status bar - character count
1130
- fo.set('charCounter', charCounter_max > 0 ? true : typeof charCounter === 'boolean' ? charCounter : false);
1131
- fo.set('charCounter_max', numbers.is(charCounter_max) && charCounter_max > -1 ? charCounter_max * 1 : null);
1132
- fo.set('charCounter_label', typeof charCounter_label === 'string' ? charCounter_label.trim() : null);
1133
- fo.set('charCounter_type', typeof charCounter_type === 'string' ? charCounter_type : 'char');
1134
-
1135
- return fo;
1136
- }
1137
-
1138
- /**
1139
- * @private
1140
- * @description Initialize property of suneditor elements
1141
- * @param {string} key - The key of the editor frame
1142
- * @param {Map<string, *>} options - options
1143
- * @param {HTMLElement} topDiv - top div
1144
- * @param {Map<string, *>} targetOptions - editor.frameOptions
1145
- * @returns {{bottomBar: ReturnType<CreateStatusbar>, wysiwygFrame: HTMLElement, codeView: HTMLElement, placeholder: HTMLElement}}
1146
- */
1147
- function _initTargetElements(key, options, topDiv, targetOptions) {
1148
- const editorStyles = targetOptions.get('_defaultStyles');
1149
- /** top div */
1150
- topDiv.style.cssText = editorStyles.top;
1151
-
1152
- /** editor */
1153
- // wysiwyg div or iframe
1154
- const wysiwygDiv = dom.utils.createElement(!targetOptions.get('iframe') ? 'DIV' : 'IFRAME', {
1155
- class: 'se-wrapper-inner se-wrapper-wysiwyg',
1156
- 'data-root-key': key
1157
- });
1158
-
1159
- if (!targetOptions.get('iframe')) {
1160
- wysiwygDiv.setAttribute('contenteditable', 'true');
1161
- wysiwygDiv.setAttribute('scrolling', 'auto');
1162
- wysiwygDiv.className += ' ' + options.get('_editableClass');
1163
- wysiwygDiv.style.cssText = editorStyles.frame + editorStyles.editor;
1164
- } else {
1165
- const frameAttrs = targetOptions.get('iframe_attributes');
1166
- for (const frameKey in frameAttrs) {
1167
- wysiwygDiv.setAttribute(frameKey, frameAttrs[frameKey]);
1168
- }
1169
-
1170
- const iframeWW = /** @type {HTMLIFrameElement} */ (wysiwygDiv);
1171
- iframeWW.allowFullscreen = true;
1172
- iframeWW.frameBorder = '0';
1173
- iframeWW.style.cssText = editorStyles.frame;
1174
- }
1175
-
1176
- // textarea for code view
1177
- const textarea = dom.utils.createElement('TEXTAREA', { class: 'se-wrapper-inner se-code-viewer', style: editorStyles.frame });
1178
- let placeholder = null;
1179
- if (targetOptions.get('placeholder')) {
1180
- placeholder = dom.utils.createElement('SPAN', { class: 'se-placeholder' });
1181
- placeholder.textContent = targetOptions.get('placeholder');
1182
- }
1183
-
1184
- return {
1185
- bottomBar: CreateStatusbar(targetOptions, null),
1186
- wysiwygFrame: wysiwygDiv,
1187
- codeView: textarea,
1188
- placeholder: placeholder
1189
- };
1190
- }
1191
-
1192
- /**
1193
- * @private
1194
- * @description Check the CodeMirror option to apply the CodeMirror and return the CodeMirror element.
1195
- * @param {Map<string, *>} options options
1196
- * @param {HTMLElement} textarea textarea element
1197
- */
1198
- function _checkCodeMirror(options, targetOptions, textarea) {
1199
- let cmeditor = null;
1200
- let hasCodeMirror = false;
1201
-
1202
- if (options.get('codeMirror6Editor')) {
1203
- const codeMirror = options.get('codeMirror');
1204
- const codeStyles = textarea.style.cssText;
1205
- const cm = new codeMirror.EditorView({
1206
- parent: textarea.parentElement,
1207
- extensions: codeMirror.extensions,
1208
- state: codeMirror.state
1209
- });
1210
-
1211
- targetOptions.set('codeMirror6Editor', cm);
1212
- cmeditor = cm.dom;
1213
- cmeditor.style.cssText = codeStyles;
1214
- hasCodeMirror = true;
1215
- } else if (options.get('codeMirror5Editor')) {
1216
- const codeMirror = options.get('codeMirror');
1217
- const cmOptions = [
1218
- {
1219
- mode: 'htmlmixed',
1220
- htmlMode: true,
1221
- lineNumbers: true,
1222
- lineWrapping: true
1223
- },
1224
- codeMirror.options || {}
1225
- ].reduce((init, option) => {
1226
- for (const key in option) {
1227
- init[key] = option[key];
1228
- }
1229
- return init;
1230
- }, {});
1231
-
1232
- if (targetOptions.get('height') === 'auto') {
1233
- cmOptions.viewportMargin = Infinity;
1234
- cmOptions.height = 'auto';
1235
- }
1236
-
1237
- const codeStyles = textarea.style.cssText;
1238
- const cm = codeMirror.src.fromTextArea(textarea, cmOptions);
1239
- targetOptions.set('codeMirror5Editor', cm);
1240
- cmeditor = cm.display.wrapper;
1241
- cmeditor.style.cssText = codeStyles;
1242
- hasCodeMirror = true;
1243
- }
1244
-
1245
- options.set('hasCodeMirror', hasCodeMirror);
1246
- if (cmeditor) {
1247
- dom.utils.removeItem(textarea);
1248
- cmeditor.className += ' se-code-viewer-mirror';
1249
- return cmeditor;
1250
- }
1251
-
1252
- return textarea;
1253
- }
1254
-
1255
- /**
1256
- * @private
1257
- * @description create blacklist
1258
- * @param {string} blacklist blacklist
1259
- * @param {string} defaultLine options.get('defaultLine')
1260
- * @returns {string}
1261
- */
1262
- function _createBlacklist(blacklist, defaultLine) {
1263
- defaultLine = defaultLine.toLowerCase();
1264
- return blacklist
1265
- .split('|')
1266
- .filter(function (v) {
1267
- if (v !== defaultLine) {
1268
- return true;
1269
- } else {
1270
- console.warn(`[SUNEDITOR.constructor.createBlacklist.warn] defaultLine("<${defaultLine}>") cannot be included in the blacklist and will be removed.`);
1271
- return false;
1272
- }
1273
- })
1274
- .join('|');
1275
- }
1276
-
1277
- /**
1278
- * @private
1279
- * @description create formats regexp object.
1280
- * @param {string} value value
1281
- * @param {string} defaultValue default value
1282
- * @param {string} blacklist blacklist
1283
- * @returns {{reg: RegExp, str: string}}
1284
- */
1285
- function _createFormatInfo(value, defaultValue, blacklist) {
1286
- const blist = blacklist.split('|');
1287
- const str = (defaultValue + '|' + (typeof value === 'string' ? value.toLowerCase() : ''))
1288
- .replace(/^\||\|$/g, '')
1289
- .split('|')
1290
- .filter((v) => v && !blist.includes(v))
1291
- .join('|');
1292
- return {
1293
- reg: new RegExp(`^(${str})$`, 'i'),
1294
- str: str
1295
- };
1296
- }
1297
-
1298
- /**
1299
- * @private
1300
- * @description create whitelist or blacklist.
1301
- * @param {Map<string, *>} o options
1302
- * @returns {string} whitelist
1303
- */
1304
- function _createWhitelist(o) {
1305
- const blacklist = o.get('elementBlacklist').split('|');
1306
- const whitelist = (o.get('__defaultElementWhitelist') + '|' + o.get('elementWhitelist') + '|' + o.get('formatLine').str + '|' + o.get('formatBrLine').str + '|' + o.get('formatClosureBlock').str + '|' + o.get('formatClosureBrLine').str)
1307
- .replace(/(^\||\|$)/g, '')
1308
- .split('|')
1309
- .filter((v, i, a) => v && a.indexOf(v) === i && !blacklist.includes(v));
1310
-
1311
- return whitelist.join('|');
1312
- }
1313
-
1314
- /**
1315
- * @private
1316
- * @description Suneditor's Default button list
1317
- * @param {Map<string, *>} options options
1318
- */
1319
- function _defaultButtons(options, icons, lang) {
1320
- const isRTL = options.get('_rtl');
1321
- return {
1322
- bold: ['', lang.bold, 'bold', '', icons.bold],
1323
- underline: ['', lang.underline, 'underline', '', icons.underline],
1324
- italic: ['', lang.italic, 'italic', '', icons.italic],
1325
- strike: ['', lang.strike, 'strike', '', icons.strike],
1326
- subscript: ['', lang.subscript, 'subscript', '', icons.subscript],
1327
- superscript: ['', lang.superscript, 'superscript', '', icons.superscript],
1328
- removeFormat: ['', lang.removeFormat, 'removeFormat', '', icons.remove_format],
1329
- copyFormat: ['', lang.copyFormat, 'copyFormat', '', icons.format_paint],
1330
- indent: ['se-icon-flip-rtl', lang.indent, 'indent', '', isRTL ? icons.outdent : icons.indent],
1331
- outdent: ['se-icon-flip-rtl', lang.outdent, 'outdent', '', isRTL ? icons.indent : icons.outdent],
1332
- fullScreen: ['se-code-view-enabled se-component-enabled', lang.fullScreen, 'fullScreen', '', icons.expansion],
1333
- showBlocks: ['', lang.showBlocks, 'showBlocks', '', icons.show_blocks],
1334
- codeView: ['se-code-view-enabled se-component-enabled', lang.codeView, 'codeView', '', icons.code_view],
1335
- undo: ['se-component-enabled', lang.undo, 'undo', '', icons.undo],
1336
- redo: ['se-component-enabled', lang.redo, 'redo', '', icons.redo],
1337
- preview: ['se-component-enabled', lang.preview, 'preview', '', icons.preview],
1338
- print: ['se-component-enabled', lang.print, 'print', '', icons.print],
1339
- copy: ['', lang.copy, 'copy', '', icons.copy],
1340
- dir: ['', lang[isRTL ? 'dir_ltr' : 'dir_rtl'], 'dir', '', icons[isRTL ? 'dir_ltr' : 'dir_rtl']],
1341
- dir_ltr: ['', lang.dir_ltr, 'dir_ltr', '', icons.dir_ltr],
1342
- dir_rtl: ['', lang.dir_rtl, 'dir_rtl', '', icons.dir_rtl],
1343
- save: ['se-component-enabled', lang.save, 'save', '', icons.save],
1344
- newDocument: ['se-component-enabled', lang.newDocument, 'newDocument', '', icons.new_document],
1345
- selectAll: ['se-component-enabled', lang.selectAll, 'selectAll', '', icons.select_all],
1346
- pageBreak: ['se-component-enabled', lang.pageBreak, 'pageBreak', '', icons.page_break],
1347
- // document type buttons
1348
- pageUp: ['se-component-enabled', lang.pageUp, 'pageUp', '', icons.page_up],
1349
- pageDown: ['se-component-enabled', lang.pageDown, 'pageDown', '', icons.page_down],
1350
- pageNavigator: ['se-component-enabled', '', 'pageNavigator', 'input', '']
1351
- };
1352
- }
1353
-
1354
- /**
1355
- * @private
1356
- * @description Create a group div containing each module
1357
- * @returns {{div: Element, ul: Element}}
1358
- */
1359
- function _createModuleGroup() {
1360
- const oUl = dom.utils.createElement('UL', { class: 'se-menu-list' });
1361
- const oDiv = dom.utils.createElement('DIV', { class: 'se-btn-module se-btn-module-border' }, oUl);
1362
-
1363
- return {
1364
- div: oDiv,
1365
- ul: oUl
1366
- };
1367
- }
1368
-
1369
- /**
1370
- * @private
1371
- * @description Create a button element
1372
- * @param {string} className className in button
1373
- * @param {string} title Title in button
1374
- * @param {string} dataCommand The data-command property of the button
1375
- * @param {"command"|"dropdown"|"field"|"browser"|"input"|"modal"|"popup"} dataType The data-type property of the button
1376
- * @param {string} innerHTML Html in button
1377
- * @param {string} _disabled Button disabled
1378
- * @param {Object<string, string>} icons Icons
1379
- * @returns {{li: HTMLElement, button: HTMLElement}}
1380
- */
1381
- function _createButton(className, title, dataCommand, dataType, innerHTML, _disabled, icons) {
1382
- if (!innerHTML) innerHTML = '';
1383
-
1384
- const oLi = dom.utils.createElement('LI');
1385
- const label = title || '';
1386
- const isDiv = /^INPUT|FIELD$/i.test(dataType);
1387
- const oButton = /** @type {HTMLButtonElement} */ (
1388
- 'se-toolbar-separator-vertical' === className
1389
- ? dom.utils.createElement('DIV', { class: className, tabindex: '-1' }, null)
1390
- : dom.utils.createElement(isDiv ? 'DIV' : 'BUTTON', {
1391
- class: 'se-toolbar-btn se-btn se-tooltip' + (className ? ' ' + className : ''),
1392
- 'data-command': dataCommand,
1393
- 'data-type': dataType,
1394
- 'aria-label': label.replace(/<span .+<\/span>/, ''),
1395
- tabindex: '-1'
1396
- })
1397
- );
1398
-
1399
- if (!isDiv) {
1400
- oButton.setAttribute('type', 'button');
1401
- }
1402
-
1403
- if (/^default\./i.test(innerHTML)) {
1404
- innerHTML = icons[innerHTML.replace(/^default\./i, '')];
1405
- }
1406
- if (/^text\./i.test(innerHTML)) {
1407
- innerHTML = innerHTML.replace(/^text\./i, '');
1408
- oButton.className += ' se-btn-more-text';
1409
- }
1410
-
1411
- if (_disabled) oButton.disabled = true;
1412
-
1413
- if (/^FIELD$/i.test(dataType)) dom.utils.addClass(oLi, 'se-toolbar-hidden-btn');
1414
-
1415
- if (label) innerHTML += dom.utils.createTooltipInner(label);
1416
- if (innerHTML) oButton.innerHTML = innerHTML;
1417
-
1418
- oLi.appendChild(oButton);
1419
-
1420
- return {
1421
- li: oLi,
1422
- button: oButton
1423
- };
1424
- }
1425
-
1426
- /**
1427
- * @description Update a button state, attributes, and icons
1428
- * @param {HTMLElement|null} element Button element
1429
- * @param {Object<string, *>} plugin Plugin
1430
- * @param {Object<string, string>} icons Icons
1431
- * @param {Object<string, string>} lang lang
1432
- */
1433
- export function UpdateButton(element, plugin, icons, lang) {
1434
- if (!element) return;
1435
-
1436
- const noneInner = plugin.inner === false;
1437
-
1438
- if (plugin.inner?.nodeType === 1) {
1439
- element.appendChild(plugin.inner);
1440
- } else {
1441
- element.innerHTML = noneInner
1442
- ? ''
1443
- : (plugin.inner || icons[plugin.icon] || plugin.icon || '<span class="se-icon-text">!</span>') + '<span class="se-tooltip-inner"><span class="se-tooltip-text">' + (lang[plugin.title] || plugin.title) + '</span></span>';
1444
- }
1445
-
1446
- element.setAttribute('aria-label', plugin.title);
1447
-
1448
- if (plugin.type) {
1449
- element.setAttribute('data-type', plugin.type);
1450
- }
1451
-
1452
- if (plugin.className) {
1453
- element.className += ' ' + plugin.className;
1454
- }
1455
-
1456
- // side, replace button
1457
- if (plugin.afterItem) {
1458
- dom.utils.addClass(plugin.afterItem, 'se-toolbar-btn');
1459
- element.parentElement.appendChild(plugin.afterItem);
1460
-
1461
- dom.utils.addClass(element, 'se-side-btn-a');
1462
- dom.utils.addClass(plugin.afterItem, 'se-side-btn-after');
1463
- }
1464
- if (plugin.beforeItem) {
1465
- dom.utils.addClass(plugin.beforeItem, 'se-toolbar-btn');
1466
- element.parentElement.insertBefore(plugin.beforeItem, element);
1467
-
1468
- if (plugin.afterItem) {
1469
- dom.utils.addClass(element, 'se-side-btn');
1470
- dom.utils.removeClass(element, 'se-side-btn-a');
1471
- } else {
1472
- dom.utils.addClass(element, 'se-side-btn-b');
1473
- }
1474
- dom.utils.addClass(plugin.beforeItem, 'se-side-btn-before');
1475
- }
1476
- if (plugin.replaceButton) {
1477
- element.parentElement.appendChild(plugin.replaceButton);
1478
- element.style.display = 'none';
1479
- }
1480
-
1481
- if (!plugin.replaceButton && /^INPUT$/i.test(element.getAttribute('data-type'))) {
1482
- const inputTarget = element.querySelector('input');
1483
- if (inputTarget) {
1484
- dom.utils.addClass(inputTarget, 'se-toolbar-btn');
1485
- inputTarget.setAttribute('data-command', element.getAttribute('data-command'));
1486
- inputTarget.setAttribute('data-type', element.getAttribute('data-type'));
1487
- if (element.hasAttribute('disabled')) inputTarget.disabled = true;
1488
- }
1489
- }
1490
- }
1491
-
1492
- /**
1493
- * @description Create editor HTML
1494
- * @param {Array} buttonList option.buttonList
1495
- * @param {?Object<string, *>} plugins Plugins
1496
- * @param {Map<string, *>} options options
1497
- * @param {Object<string, string>} icons icons
1498
- * @param {Object<string, string>} lang lang
1499
- * @param {boolean} isUpdate Is update
1500
- * @returns {{element: HTMLElement, pluginCallButtons: Object<string, Array<HTMLElement>>, responsiveButtons: Array<HTMLElement>, buttonTray: HTMLElement, updateButtons: Array<{button: HTMLElement, plugin: *, key: string}>}}}
1501
- */
1502
- export function CreateToolBar(buttonList, plugins, options, icons, lang, isUpdate) {
1503
- /** create button list */
1504
- buttonList = JSON.parse(JSON.stringify(buttonList));
1505
- const defaultButtonList = _defaultButtons(options, icons, lang);
1506
- /** @type {Object<string, Array<HTMLElement>>} */
1507
- const pluginCallButtons = {};
1508
- const responsiveButtons = [];
1509
- const updateButtons = [];
1510
-
1511
- let modules = null;
1512
- let button = null;
1513
- let plugin = null;
1514
- let moduleElement = null;
1515
- let buttonElement = null;
1516
- // let vertical = false;
1517
- const moreLayer = dom.utils.createElement('DIV', { class: 'se-toolbar-more-layer' });
1518
- const buttonTray = dom.utils.createElement('DIV', { class: 'se-btn-tray' });
1519
- const separator_vertical = dom.utils.createElement('DIV', { class: 'se-toolbar-separator-vertical' });
1520
-
1521
- buttonGroupLoop: for (let i = 0, more, moreContainer, moreCommand, buttonGroup, align; i < buttonList.length; i++) {
1522
- more = false;
1523
- align = '';
1524
- buttonGroup = buttonList[i];
1525
- moduleElement = _createModuleGroup();
1526
-
1527
- // button object
1528
- if (typeof buttonGroup === 'object') {
1529
- // buttons loop
1530
- for (let j = 0, moreButton; j < buttonGroup.length; j++) {
1531
- button = buttonGroup[j];
1532
- moreButton = false;
1533
- plugin = plugins[button];
1534
-
1535
- if (/^%\d+/.test(button) && j === 0) {
1536
- buttonGroup[0] = button.replace(/[^\d]/g, '');
1537
- responsiveButtons.push(buttonGroup);
1538
- buttonList.splice(i--, 1);
1539
- continue buttonGroupLoop;
1540
- }
1541
- if (typeof plugin === 'function') {
1542
- modules = [plugin.className, plugin.title, button, plugin.type, plugin.innerHTML, plugin._disabled];
1543
- } else if (typeof plugin === 'object') {
1544
- const originFnc = plugin.constructor;
1545
- modules = [plugin.className || originFnc.className, plugin.title || originFnc.title, button, plugin.type || originFnc.type, plugin.innerHTML || originFnc.innerHTML, plugin._disabled || originFnc._disabled];
1546
- } else {
1547
- // align
1548
- if (/^-/.test(button)) {
1549
- align = button.substring(1);
1550
- moduleElement.div.className += ' module-float-' + align;
1551
- continue;
1552
- }
1553
-
1554
- // rtl fix
1555
- if (/^#/.test(button)) {
1556
- const option = button.substring(1);
1557
- if (option === 'fix') moduleElement.ul.className += ' se-menu-dir-fix';
1558
- continue;
1559
- }
1560
-
1561
- // more button
1562
- if (/^:/.test(button)) {
1563
- moreButton = true;
1564
- const matched = button.match(/^:([^-]+)-([^-]+)/);
1565
- moreCommand = '__se__more_' + i;
1566
- const title = matched[1].trim();
1567
- const innerHTML = matched[2].trim();
1568
- modules = ['se-btn-more', /^lang\./i.test(title) ? lang[title.replace(/^lang\./i, '')] : title, moreCommand, 'MORE', innerHTML];
1569
- } else if (button === '|') {
1570
- // separator vertical
1571
- modules = ['se-toolbar-separator-vertical', '', '', 'separator', ''];
1572
- } else {
1573
- // default command
1574
- if (button === 'copy' && !env.isClipboardSupported) {
1575
- console.warn('[SUNEDITOR.constructor.warn] Clipboard is not supported in this browser. : [copy] button is not rendered.');
1576
- continue;
1577
- }
1578
- modules = defaultButtonList[button];
1579
- }
1580
-
1581
- if (!modules) {
1582
- if (!plugin) throw Error(`[SUNEDITOR.create.toolbar.fail] The button name of a plugin that does not exist. [${button}]`);
1583
- plugin = typeof plugin === 'object' ? plugin.constructor : plugin;
1584
- modules = [plugin.className, plugin.title, plugin.key, plugin.type, plugin.innerHTML, plugin._disabled];
1585
- }
1586
- }
1587
-
1588
- buttonElement = _createButton(modules[0], modules[1], modules[2], modules[3], modules[4], modules[5], icons);
1589
- (more ? moreContainer : moduleElement.ul).appendChild(buttonElement.li);
1590
-
1591
- if (plugin) {
1592
- if (pluginCallButtons[button]) {
1593
- pluginCallButtons[button].push(buttonElement.button);
1594
- } else {
1595
- pluginCallButtons[button] = [buttonElement.button];
1596
- }
1597
-
1598
- if (isUpdate) {
1599
- updateButtons.push({ button: buttonElement.button, plugin, key: button });
1600
- }
1601
- }
1602
-
1603
- // more button
1604
- if (moreButton) {
1605
- more = true;
1606
- moreContainer = dom.utils.createElement('DIV');
1607
- moreContainer.className = 'se-more-layer ' + moreCommand;
1608
- moreContainer.setAttribute('data-ref', moreCommand);
1609
- moreContainer.innerHTML = '<div class="se-more-form"><ul class="se-menu-list"' + (align ? ' style="float: ' + align + ';"' : '') + '></ul></div>';
1610
- moreLayer.appendChild(moreContainer);
1611
- moreContainer = moreContainer.firstElementChild.firstElementChild;
1612
- }
1613
- }
1614
-
1615
- // if (vertical) {
1616
- // const sv = separator_vertical.cloneNode(false);
1617
- // buttonTray.appendChild(sv);
1618
- // }
1619
-
1620
- buttonTray.appendChild(moduleElement.div);
1621
- // vertical = true;
1622
- } else if (buttonGroup === '|') {
1623
- // // separator vertical
1624
- const sv = separator_vertical.cloneNode(false);
1625
- buttonTray.appendChild(sv);
1626
- continue;
1627
- } else if (/^\/$/.test(buttonGroup)) {
1628
- /** line break */
1629
- const enterDiv = dom.utils.createElement('DIV', { class: 'se-btn-module-enter' });
1630
- buttonTray.appendChild(enterDiv);
1631
- // vertical = false;
1632
- }
1633
- }
1634
-
1635
- switch (buttonTray.children.length) {
1636
- case 0:
1637
- buttonTray.style.display = 'none';
1638
- break;
1639
- case 1:
1640
- dom.utils.removeClass(buttonTray.firstElementChild, 'se-btn-module-border');
1641
- break;
1642
- }
1643
-
1644
- if (moreLayer.children.length > 0) buttonTray.appendChild(moreLayer);
1645
- if (responsiveButtons.length > 0) responsiveButtons.unshift(buttonList);
1646
-
1647
- // rendering toolbar
1648
- const tool_bar = dom.utils.createElement('DIV', { class: 'se-toolbar sun-editor-common' + (!options.get('shortcutsHint') ? ' se-shortcut-hide' : '') }, buttonTray);
1649
-
1650
- if (options.get('toolbar_hide')) tool_bar.style.display = 'none';
1651
-
1652
- return {
1653
- element: tool_bar,
1654
- pluginCallButtons,
1655
- responsiveButtons,
1656
- buttonTray,
1657
- updateButtons
1658
- };
1659
- }
1660
-
1661
- export default Constructor;
1
+ import _icons from '../../assets/icons/defaultIcons';
2
+ import _defaultLang from '../../langs/en';
3
+ import { CreateContext, CreateFrameContext } from './context';
4
+ import { dom, numbers, converter, env } from '../../helper';
5
+ import { DEFAULTS } from './options';
6
+
7
+ const _d = env._d;
8
+
9
+ /**
10
+ * @typedef {import('./options').EditorFrameOptions} EditorFrameOptions
11
+ */
12
+
13
+ /**
14
+ * @typedef {import('./options').EditorInitOptions} EditorInitOptions
15
+ */
16
+
17
+ /**
18
+ * @description Creates a new SunEditor instance with specified options.
19
+ * @param {Array<{target: Element, key: *, options: EditorFrameOptions}>} editorTargets - Target element or multi-root object.
20
+ * @param {EditorInitOptions} options - Configuration options for the editor.
21
+ * @returns {Object<string, *>} - SunEditor instance with context, options, and DOM elements.
22
+ */
23
+ function Constructor(editorTargets, options) {
24
+ if (typeof options !== 'object') options = {};
25
+
26
+ /** --- Plugins ------------------------------------------------------------------------------------------ */
27
+ const plugins = {};
28
+ if (options.plugins) {
29
+ const excludedPlugins = options.excludedPlugins || [];
30
+ const originPlugins = options.plugins;
31
+ const pluginsValues = (Array.isArray(originPlugins) ? originPlugins : Object.keys(originPlugins)).filter((name) => !excludedPlugins.includes(name)).map((name) => originPlugins[name]);
32
+
33
+ for (let i = 0, len = pluginsValues.length, p; i < len; i++) {
34
+ p = pluginsValues[i].default || pluginsValues[i];
35
+ plugins[p.key] = p;
36
+ }
37
+ }
38
+
39
+ /** --- options --------------------------------------------------------------- */
40
+ const optionMap = InitOptions(options, editorTargets, plugins);
41
+ const o = optionMap.o;
42
+ const icons = optionMap.i;
43
+ const lang = optionMap.l;
44
+ const loadingBox = dom.utils.createElement('DIV', { class: 'se-loading-box sun-editor-common' }, '<div class="se-loading-effect"></div>');
45
+
46
+ /** --- carrier wrapper --------------------------------------------------------------- */
47
+ const editor_carrier_wrapper = dom.utils.createElement('DIV', { class: 'sun-editor sun-editor-carrier-wrapper sun-editor-common' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
48
+ // menuTray
49
+ const menuTray = dom.utils.createElement('DIV', { class: 'se-menu-tray' });
50
+ editor_carrier_wrapper.appendChild(menuTray);
51
+ // focus temp element
52
+ const focusTemp = /** @type {HTMLInputElement} */ (
53
+ dom.utils.createElement('INPUT', {
54
+ class: '__se__focus__temp__',
55
+ style: 'position: fixed !important; top: -10000px !important; left: -10000px !important; display: block !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important;'
56
+ })
57
+ );
58
+ focusTemp.tabIndex = 0;
59
+ editor_carrier_wrapper.appendChild(focusTemp);
60
+
61
+ // modal
62
+ const modal = dom.utils.createElement('DIV', { class: 'se-modal se-modal-area sun-editor-common' });
63
+ const modal_back = dom.utils.createElement('DIV', { class: 'se-modal-back' });
64
+ const modal_inner = dom.utils.createElement('DIV', { class: 'se-modal-inner' });
65
+ modal.appendChild(modal_back);
66
+ modal.appendChild(modal_inner);
67
+ editor_carrier_wrapper.appendChild(modal);
68
+
69
+ // alert
70
+ const alert = dom.utils.createElement('DIV', { class: 'se-alert se-modal-area sun-editor-common', style: 'display: none;' });
71
+ const alert_back = dom.utils.createElement('DIV', { class: 'se-modal-back' });
72
+ const alert_inner = dom.utils.createElement('DIV', { class: 'se-modal-inner' });
73
+ alert.appendChild(alert_back);
74
+ alert.appendChild(alert_inner);
75
+ editor_carrier_wrapper.appendChild(alert);
76
+
77
+ // loding box, resizing back
78
+ editor_carrier_wrapper.appendChild(dom.utils.createElement('DIV', { class: 'se-back-wrapper' }));
79
+ editor_carrier_wrapper.appendChild(loadingBox.cloneNode(true));
80
+
81
+ // drag cursor
82
+ const dragCursor = dom.utils.createElement('DIV', { class: 'se-drag-cursor' });
83
+ editor_carrier_wrapper.appendChild(dragCursor);
84
+
85
+ // set carrier wrapper
86
+ _d.body.appendChild(editor_carrier_wrapper);
87
+
88
+ /** --- toolbar --------------------------------------------------------------- */
89
+ let subbar = null,
90
+ sub_main = null;
91
+ const tool_bar_main = CreateToolBar(optionMap.buttons, plugins, o, icons, lang, false);
92
+ const toolbar = tool_bar_main.element;
93
+ toolbar.style.visibility = 'hidden';
94
+ // toolbar mode
95
+ if (/inline/i.test(o.get('mode'))) {
96
+ toolbar.className += ' se-toolbar-inline';
97
+ toolbar.style.width = o.get('toolbar_width');
98
+ } else if (/balloon/i.test(o.get('mode'))) {
99
+ toolbar.className += ' se-toolbar-balloon';
100
+ toolbar.style.width = o.get('toolbar_width');
101
+ toolbar.appendChild(dom.utils.createElement('DIV', { class: 'se-arrow' }));
102
+ }
103
+
104
+ /** --- subToolbar --------------------------------------------------------------- */
105
+ if (optionMap.subButtons) {
106
+ sub_main = CreateToolBar(optionMap.subButtons, plugins, o, icons, lang, false);
107
+ subbar = sub_main.element;
108
+ subbar.style.visibility = 'hidden';
109
+ // subbar mode must be balloon-*
110
+ subbar.className += ' se-toolbar-balloon se-toolbar-sub';
111
+ subbar.style.width = o.get('toolbar.sub_width');
112
+ subbar.appendChild(dom.utils.createElement('DIV', { class: 'se-arrow' }));
113
+ }
114
+
115
+ /** frame - root set - start -------------------------------------------------------------- */
116
+ const rootId = editorTargets[0].key || null;
117
+ const rootKeys = [];
118
+ const frameRoots = new Map();
119
+ const statusbarContainer = optionMap.statusbarContainer;
120
+ let default_status_bar = null;
121
+ for (let i = 0, len = editorTargets.length; i < len; i++) {
122
+ const editTarget = editorTargets[i];
123
+ const to = optionMap.frameMap.get(editTarget.key);
124
+ const top_div = dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
125
+ const container = dom.utils.createElement('DIV', { class: 'se-container' });
126
+ const editor_div = dom.utils.createElement('DIV', { class: 'se-wrapper' + (o.get('type') === 'document' ? ' se-type-document' : '') + (o.get('_type_options').includes('header') ? ' se-type-document-header' : '') });
127
+
128
+ container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-shadow' }));
129
+
130
+ // init element
131
+ const initElements = _initTargetElements(editTarget.key, o, top_div, to);
132
+ const bottomBar = initElements.bottomBar;
133
+ const statusbar = bottomBar.statusbar;
134
+ const wysiwyg_div = initElements.wysiwygFrame;
135
+ const placeholder_span = initElements.placeholder;
136
+ let textarea = initElements.codeView;
137
+
138
+ // line breaker
139
+ const line_breaker_t = dom.utils.createElement('DIV', { class: 'se-line-breaker-component se-line-breaker-component-t', title: lang.insertLine }, icons.line_break);
140
+ const line_breaker_b = dom.utils.createElement('DIV', { class: 'se-line-breaker-component se-line-breaker-component-b', title: lang.insertLine }, icons.line_break);
141
+
142
+ editor_div.appendChild(line_breaker_t);
143
+ editor_div.appendChild(line_breaker_b);
144
+
145
+ // append container
146
+ if (placeholder_span) editor_div.appendChild(placeholder_span);
147
+ container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-sticky-dummy' }));
148
+ container.appendChild(editor_div);
149
+
150
+ // statusbar
151
+ if (statusbar) {
152
+ if (statusbarContainer) {
153
+ if (!default_status_bar) {
154
+ statusbarContainer.appendChild(dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') }, statusbar));
155
+ default_status_bar = statusbar;
156
+ }
157
+ } else {
158
+ container.appendChild(statusbar);
159
+ }
160
+ }
161
+
162
+ // loading bar
163
+ container.appendChild(loadingBox.cloneNode(true));
164
+
165
+ // root key
166
+ const key = editTarget.key || null;
167
+
168
+ // code view - wrapper
169
+ const codeWrapper = dom.utils.createElement('DIV', { class: 'se-code-wrapper' }, textarea);
170
+ codeWrapper.style.setProperty('display', 'none', 'important');
171
+ editor_div.appendChild(codeWrapper);
172
+
173
+ // check code mirror
174
+ const codeMirrorEl = _checkCodeMirror(o, to, textarea);
175
+ // not used code mirror
176
+ if (textarea === codeMirrorEl) {
177
+ // add line nubers
178
+ const codeNumbers = dom.utils.createElement('TEXTAREA', { class: 'se-code-view-line', readonly: 'true' }, null);
179
+ codeWrapper.insertBefore(codeNumbers, textarea);
180
+ } else {
181
+ textarea = codeMirrorEl;
182
+ }
183
+
184
+ // document type
185
+ const documentTypeInner = { inner: null, page: null, pageMirror: null };
186
+ if (o.get('_type_options').includes('header')) {
187
+ documentTypeInner.inner = dom.utils.createElement('DIV', { class: 'se-document-lines', style: `height: ${to.get('height')};` }, '<div class="se-document-lines-inner"></div>');
188
+ }
189
+ if (o.get('_type_options').includes('page')) {
190
+ documentTypeInner.page = dom.utils.createElement('DIV', { class: 'se-document-page' }, null);
191
+ documentTypeInner.pageMirror = dom.utils.createElement(
192
+ 'DIV',
193
+ {
194
+ class: 'sun-editor-editable se-document-page-mirror-a4',
195
+ style: `position: absolute; width: 21cm; columns: 21cm; border: 0; overflow: hidden; height: auto; top: -10000px; left: -10000px;`
196
+ },
197
+ null
198
+ );
199
+ }
200
+
201
+ // set container
202
+ top_div.appendChild(container);
203
+ rootKeys.push(key);
204
+ frameRoots.set(key, CreateFrameContext({ target: editTarget.target, key: editTarget.key, options: to }, top_div, wysiwyg_div, codeWrapper, textarea, default_status_bar || statusbar, documentTypeInner, key));
205
+ }
206
+ /** frame - root set - end -------------------------------------------------------------- */
207
+
208
+ // toolbar container
209
+ const toolbar_container = o.get('toolbar_container');
210
+ if (toolbar_container) {
211
+ const top_div = dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
212
+ const container = dom.utils.createElement('DIV', { class: 'se-container' });
213
+ container.appendChild(toolbar);
214
+ if (subbar) container.appendChild(subbar);
215
+ top_div.appendChild(container);
216
+ toolbar_container.appendChild(top_div);
217
+ toolbar_container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-sticky-dummy' }));
218
+ } else {
219
+ const rootContainer = frameRoots.get(rootId).get('container');
220
+ rootContainer.insertBefore(toolbar, rootContainer.firstElementChild);
221
+ if (subbar) rootContainer.insertBefore(subbar, rootContainer.firstElementChild);
222
+ }
223
+
224
+ return {
225
+ context: CreateContext(toolbar, toolbar_container, menuTray, subbar, statusbarContainer),
226
+ carrierWrapper: editor_carrier_wrapper,
227
+ options: o,
228
+ plugins: plugins,
229
+ icons: icons,
230
+ lang: lang,
231
+ value: optionMap.v,
232
+ rootId: rootId,
233
+ rootKeys: rootKeys,
234
+ frameRoots: frameRoots,
235
+ pluginCallButtons: tool_bar_main.pluginCallButtons,
236
+ responsiveButtons: tool_bar_main.responsiveButtons,
237
+ pluginCallButtons_sub: sub_main ? sub_main.pluginCallButtons : [],
238
+ responsiveButtons_sub: sub_main ? sub_main.responsiveButtons : []
239
+ };
240
+ }
241
+
242
+ /**
243
+ * @description Create shortcuts desc span.
244
+ * @param {string} command Command string
245
+ * @param {Array<string>} values options.shortcuts[command]
246
+ * @param {Element|null} button Command button element
247
+ * @param {Map<string, *>} keyMap Map to store shortcut key info
248
+ * @param {Array} rc "_reverseCommandArray" option
249
+ * @param {Array} reverseKeys Reverse key array
250
+ */
251
+ export function CreateShortcuts(command, button, values, keyMap, rc, reverseKeys) {
252
+ if (!values || values.length < 2) return;
253
+ const tooptip = button?.querySelector('.se-tooltip-text');
254
+
255
+ for (let i = 0, a, v, c, s, edge, space, enter, textTrigger, plugin, method, t, k, r, _i; i < values.length; i += 2 + _i) {
256
+ _i = 0;
257
+ a = values[i].split('+');
258
+
259
+ plugin = null;
260
+ method = a[a.length - 1].trim?.();
261
+ if (method.startsWith('~')) {
262
+ // plugin key, method
263
+ plugin = command;
264
+ method = a.pop().trim().substring(1);
265
+ } else if (method.startsWith('$~')) {
266
+ // custom key, plugin method
267
+ const a_ = a.pop().trim().substring(2).split('.');
268
+ plugin = a_[0];
269
+ method = a_[1];
270
+ } else if (method.startsWith('$')) {
271
+ // directly method
272
+ _i = 1;
273
+ method = values[i + 2];
274
+ } else {
275
+ method = '';
276
+ }
277
+
278
+ c = s = edge = space = enter = textTrigger = v = null;
279
+ for (const a_ of a) {
280
+ switch (a_.trim()) {
281
+ case 'c':
282
+ c = true;
283
+ break;
284
+ case '!':
285
+ edge = true;
286
+ break;
287
+ case 's':
288
+ s = true;
289
+ break;
290
+ case '_':
291
+ space = true;
292
+ break;
293
+ case '=':
294
+ textTrigger = true;
295
+ break;
296
+ case '/':
297
+ enter = true;
298
+ break;
299
+ default:
300
+ v = a_;
301
+ }
302
+ }
303
+
304
+ v = v.split('|');
305
+ for (let j = 0, len = v.length; j < len; j++) {
306
+ k = c ? v[j] + (s ? '1000' : '') : v[j];
307
+ if (!keyMap.has(k)) {
308
+ r = rc.indexOf(command);
309
+ r = r === -1 ? '' : numbers.isOdd(r) ? rc[r + 1] : rc[r - 1];
310
+ if (r) reverseKeys.push(k);
311
+
312
+ keyMap.set(k, { c, s, edge, space, enter, textTrigger, plugin, command, method, r, type: button?.getAttribute('data-type'), button, key: k });
313
+ }
314
+ }
315
+
316
+ if (!(t = values[i + 1])) continue;
317
+ if (tooptip) _addTooltip(tooptip, s, t);
318
+ }
319
+ }
320
+
321
+ function _addTooltip(tooptipBtn, shift, shortcut) {
322
+ tooptipBtn.appendChild(dom.utils.createElement('SPAN', { class: 'se-shortcut' }, env.cmdIcon + (shift ? env.shiftIcon : '') + '+<span class="se-shortcut-key">' + shortcut + '</span>'));
323
+ }
324
+
325
+ /**
326
+ * @private
327
+ * @description Returns a new object with merge "a" and "b"
328
+ * @param {Object<*, *>} a object
329
+ * @param {Object<*, *>} b object
330
+ * @returns {Object<*, *>} new object
331
+ */
332
+ function _mergeObject(a, b) {
333
+ return [a, b].reduce((_default, _new) => {
334
+ for (const key in _new) {
335
+ _default[key] = (_new[key] || '').toLowerCase();
336
+ }
337
+ return _default;
338
+ }, {});
339
+ }
340
+
341
+ /**
342
+ * @description Initialize options
343
+ * @param {EditorInitOptions} options Configuration options for the editor.
344
+ * @param {Array<{target: Element, key: *, options: EditorFrameOptions}>} editorTargets Target textarea
345
+ * @param {Object<string, *>} plugins Plugins object
346
+ * @returns {{o: Map<string, *>, i: Object<string, string>, l: Object<string, string>, v: string, buttons: Array<string[]|string>, subButtons: Array<string[]|string>, statusbarContainer: Element|null, frameMap: Map<*, *>}}
347
+ * - o: options
348
+ * - i: icons
349
+ * - l: lang
350
+ * - v: value
351
+ * - buttons: Toolbar button list
352
+ * - subButtons: Sub-Toolbar button list
353
+ * - statusbarContainer: statusbar container
354
+ * - frameMap: converted options map
355
+ */
356
+ export function InitOptions(options, editorTargets, plugins) {
357
+ const buttonList = options.buttonList || DEFAULTS.BUTTON_LIST;
358
+ const o = new Map();
359
+
360
+ /** Multi root */
361
+ if (editorTargets.length > 1) {
362
+ if (!options.toolbar_container && !/inline|balloon/i.test(options.mode)) throw Error('[SUNEDITOR.create.fail] In multi root, The "mode" option cannot be "classic" without using the "toolbar_container" option.');
363
+ }
364
+
365
+ // migration data-.+
366
+ o.set('v2Migration', !!options.v2Migration);
367
+
368
+ /** Base */
369
+ o.set('buttons', new Set(buttonList.toString().split(',')));
370
+ const modeValue = options.strictMode !== false;
371
+ o.set('strictMode', {
372
+ tagFilter: modeValue,
373
+ formatFilter: modeValue,
374
+ classFilter: modeValue,
375
+ textStyleTagFilter: modeValue,
376
+ attrFilter: modeValue,
377
+ styleFilter: modeValue,
378
+ ...(typeof options.strictMode === 'boolean' ? {} : options.strictMode)
379
+ });
380
+ o.set('freeCodeViewMode', !!options.freeCodeViewMode);
381
+ o.set('__lineFormatFilter', options.__lineFormatFilter ?? true);
382
+ o.set('__pluginRetainFilter', options.__pluginRetainFilter ?? true);
383
+ o.set('mode', options.mode || 'classic'); // classic, inline, balloon, balloon-always
384
+ o.set('type', options.type?.split(':')[0] || ''); // document:header,page
385
+ o.set('theme', options.theme || '');
386
+ o.set('_themeClass', options.theme ? ` se-theme-${options.theme}` : '');
387
+ o.set('_type_options', options.type?.split(':')[1] || '');
388
+ o.set('externalLibs', options.externalLibs || {});
389
+ o.set('fontSizeUnits', Array.isArray(options.fontSizeUnits) && options.fontSizeUnits.length > 0 ? options.fontSizeUnits.map((v) => v.toLowerCase()) : DEFAULTS.SIZE_UNITS);
390
+ o.set('allowedClassName', new RegExp(`${options.allowedClassName && typeof options.allowedClassName === 'string' ? options.allowedClassName + '|' : ''}${DEFAULTS.CLASS_NAME}`));
391
+ o.set('closeModalOutsideClick', !!options.closeModalOutsideClick);
392
+
393
+ // format
394
+ o.set('copyFormatKeepOn', !!options.copyFormatKeepOn);
395
+ o.set('syncTabIndent', options.syncTabIndent ?? true);
396
+
397
+ // auto convert on paste
398
+ o.set('autoLinkify', options.autoLinkify ?? !!plugins.link);
399
+ o.set('autoStyleify', Array.isArray(options.autoStyleify) ? options.autoStyleify : ['bold', 'underline', 'italic', 'strike']);
400
+
401
+ let retainStyleMode = options.retainStyleMode;
402
+ if (typeof retainStyleMode === 'string' && !DEFAULTS.RETAIN_STYLE_MODE.includes(retainStyleMode)) {
403
+ console.error(`Invalid retainStyleMode: ${retainStyleMode}. Valid options are ${DEFAULTS.RETAIN_STYLE_MODE.join(', ')}. Using default 'once'.`);
404
+ retainStyleMode = 'repeat';
405
+ }
406
+ o.set('retainStyleMode', retainStyleMode);
407
+
408
+ const allowedExtraTags = { ...DEFAULTS.EXTRA_TAG_MAP, ...options.allowedExtraTags, '-': true };
409
+ const extraKeys = Object.keys(allowedExtraTags);
410
+ const allowedKeys = extraKeys.filter((k) => allowedExtraTags[k]).join('|');
411
+ const disallowedKeys = extraKeys.filter((k) => !allowedExtraTags[k]).join('|');
412
+ o.set('_allowedExtraTag', allowedKeys);
413
+ o.set('_disallowedExtraTag', disallowedKeys);
414
+
415
+ o.set('events', options.events || {});
416
+
417
+ // text style tags
418
+ o.set('textStyleTags', (typeof options.__textStyleTags === 'string' ? options.__textStyleTags : DEFAULTS.TEXT_STYLE_TAGS) + (options.textStyleTags ? '|' + options.textStyleTags : ''));
419
+ const textTags = _mergeObject(
420
+ {
421
+ bold: 'strong',
422
+ underline: 'u',
423
+ italic: 'em',
424
+ strike: 'del',
425
+ subscript: 'sub',
426
+ superscript: 'sup'
427
+ },
428
+ options.convertTextTags || {}
429
+ );
430
+ o.set('convertTextTags', textTags);
431
+ o.set('_textStyleTags', Object.values(textTags).concat(['span', 'li']));
432
+ o.set(
433
+ 'tagStyles',
434
+ [{ ...DEFAULTS.TAG_STYLES, ...(options.__tagStyles || {}) }, options.tagStyles || {}].reduce((_default, _new) => {
435
+ for (const key in _new) {
436
+ _default[key] = _new[key];
437
+ }
438
+ return _default;
439
+ }, {})
440
+ );
441
+ o.set('_textStylesRegExp', new RegExp(`\\s*[^-a-zA-Z](${DEFAULTS.SPAN_STYLES}${options.spanStyles ? '|' + options.spanStyles : ''})\\s*:[^;]+(?!;)*`, 'gi'));
442
+ o.set('_lineStylesRegExp', new RegExp(`\\s*[^-a-zA-Z](${DEFAULTS.LINE_STYLES}${options.lineStyles ? '|' + options.lineStyles : ''})\\s*:[^;]+(?!;)*`, 'gi'));
443
+ o.set('_defaultStyleTagMap', {
444
+ strong: textTags.bold,
445
+ b: textTags.bold,
446
+ u: textTags.underline,
447
+ ins: textTags.underline,
448
+ em: textTags.italic,
449
+ i: textTags.italic,
450
+ del: textTags.strike,
451
+ strike: textTags.strike,
452
+ s: textTags.strike,
453
+ sub: textTags.subscript,
454
+ sup: textTags.superscript
455
+ });
456
+ o.set(
457
+ '_styleCommandMap',
458
+ _mergeObject(converter.swapKeyValue(textTags), {
459
+ strong: 'bold',
460
+ b: 'bold',
461
+ u: 'underline',
462
+ ins: 'underline',
463
+ em: 'italic',
464
+ i: 'italic',
465
+ del: 'strike',
466
+ strike: 'strike',
467
+ s: 'strike',
468
+ sub: 'subscript',
469
+ sup: 'superscript'
470
+ })
471
+ );
472
+ o.set('_defaultTagCommand', {
473
+ bold: textTags.bold,
474
+ underline: textTags.underline,
475
+ italic: textTags.italic,
476
+ strike: textTags.strike,
477
+ subscript: textTags.sub,
478
+ superscript: textTags.sup
479
+ });
480
+ // text direction
481
+ o.set('textDirection', typeof options.textDirection !== 'string' ? 'ltr' : options.textDirection);
482
+ o.set('_rtl', o.get('textDirection') === 'rtl');
483
+ // An array of key codes generated with the reverseButtons option, used to reverse the action for a specific key combination.
484
+ o.set('reverseCommands', ['indent-outdent'].concat(options.reverseButtons || []));
485
+ o.set('_reverseCommandArray', ('-' + o.get('reverseCommands').join('-')).split('-'));
486
+ if (numbers.isEven(o.get('_reverseCommandArray').length)) {
487
+ console.warn('[SUNEDITOR.create.warning] The "reverseCommands" option is invalid, Shortcuts key may not work properly.');
488
+ }
489
+
490
+ // etc
491
+ o.set('historyStackDelayTime', typeof options.historyStackDelayTime === 'number' ? options.historyStackDelayTime : 400);
492
+ o.set('_editableClass', 'sun-editor-editable' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') + (o.get('type') === 'document' ? ' se-type-document-editable-a4' : ''));
493
+ o.set('lineAttrReset', ['id'].concat(options.lineAttrReset && typeof options.lineAttrReset === 'string' ? options.lineAttrReset.toLowerCase().split('|') : []));
494
+ o.set('printClass', typeof options.printClass === 'string' ? options.printClass + ' ' + o.get('_editableClass') : null);
495
+
496
+ /** whitelist, blacklist */
497
+ // default line
498
+ o.set('defaultLine', typeof options.defaultLine === 'string' && options.defaultLine.length > 0 ? options.defaultLine : 'p');
499
+ o.set('defaultLineBreakFormat', options.defaultLineBreakFormat || 'line');
500
+ o.set('scopeSelectionTags', options.scopeSelectionTags || DEFAULTS.SCOPE_SELECTION_TAGS);
501
+ // element
502
+ const elw = (typeof options.elementWhitelist === 'string' ? options.elementWhitelist : '').toLowerCase();
503
+ const mjxEls = o.get('externalLibs').mathjax ? DEFAULTS.CLASS_MJX + '|' : '';
504
+ o.set('elementWhitelist', elw + (elw ? '|' : '') + mjxEls + o.get('_allowedExtraTag'));
505
+ const elb = _createBlacklist((typeof options.elementBlacklist === 'string' ? options.elementBlacklist : '').toLowerCase(), o.get('defaultLine'));
506
+ o.set('elementBlacklist', elb + (elb ? '|' : '') + o.get('_disallowedExtraTag'));
507
+ // attribute
508
+ o.set('attributeWhitelist', !options.attributeWhitelist || typeof options.attributeWhitelist !== 'object' ? null : options.attributeWhitelist);
509
+ o.set('attributeBlacklist', !options.attributeBlacklist || typeof options.attributeBlacklist !== 'object' ? null : options.attributeBlacklist);
510
+ // format tag
511
+ o.set(
512
+ 'formatClosureBrLine',
513
+ _createFormatInfo(
514
+ options.formatClosureBrLine,
515
+ (options.__defaultFormatClosureBrLine = typeof options.__defaultFormatClosureBrLine === 'string' ? options.__defaultFormatClosureBrLine : DEFAULTS.FORMAT_CLOSURE_BR_LINE).toLowerCase(),
516
+ o.get('elementBlacklist')
517
+ )
518
+ );
519
+ o.set(
520
+ 'formatBrLine',
521
+ _createFormatInfo(
522
+ (options.formatBrLine || '') + '|' + o.get('formatClosureBrLine').str,
523
+ (options.__defaultFormatBrLine = typeof options.__defaultFormatBrLine === 'string' ? options.__defaultFormatBrLine : DEFAULTS.FORMAT_BR_LINE).toLowerCase(),
524
+ o.get('elementBlacklist')
525
+ )
526
+ );
527
+ o.set(
528
+ 'formatLine',
529
+ _createFormatInfo(
530
+ DEFAULTS.REQUIRED_FORMAT_LINE + '|' + (options.formatLine || '') + '|' + o.get('formatBrLine').str,
531
+ (options.__defaultFormatLine = typeof options.__defaultFormatLine === 'string' ? options.__defaultFormatLine : DEFAULTS.FORMAT_LINE).toLowerCase(),
532
+ o.get('elementBlacklist')
533
+ )
534
+ );
535
+
536
+ // Error - default line
537
+ if (!o.get('formatLine').reg.test(o.get('defaultLine'))) {
538
+ throw Error(`[SUNEDITOR.create.fail] The "defaultLine(${o.get('defaultLine')})" option must be included in the "formatLine(${o.get('formatLine').str})" option.`);
539
+ }
540
+
541
+ o.set(
542
+ 'formatClosureBlock',
543
+ _createFormatInfo(
544
+ options.formatClosureBlock,
545
+ (options.__defaultFormatClosureBlock = typeof options.__defaultFormatClosureBlock === 'string' ? options.__defaultFormatClosureBlock : DEFAULTS.FORMAT_CLOSURE_BLOCK).toLowerCase(),
546
+ o.get('elementBlacklist')
547
+ )
548
+ );
549
+ o.set(
550
+ 'formatBlock',
551
+ _createFormatInfo(
552
+ (options.formatBlock || '') + '|' + o.get('formatClosureBlock').str,
553
+ (options.__defaultFormatBlock = typeof options.__defaultFormatBlock === 'string' ? options.__defaultFormatBlock : DEFAULTS.FORMAT_BLOCK).toLowerCase(),
554
+ o.get('elementBlacklist')
555
+ )
556
+ );
557
+
558
+ o.set('allowedEmptyTags', DEFAULTS.ALLOWED_EMPTY_NODE_LIST + (options.allowedEmptyTags ? ', ' + options.allowedEmptyTags : ''));
559
+
560
+ /** __defaults */
561
+ o.set('__defaultElementWhitelist', DEFAULTS.REQUIRED_ELEMENT_WHITELIST + '|' + (typeof options.__defaultElementWhitelist === 'string' ? options.__defaultElementWhitelist : DEFAULTS.ELEMENT_WHITELIST).toLowerCase());
562
+ o.set('__defaultAttributeWhitelist', (typeof options.__defaultAttributeWhitelist === 'string' ? options.__defaultAttributeWhitelist : DEFAULTS.ATTRIBUTE_WHITELIST).toLowerCase());
563
+ // --- create element whitelist (__defaultElementWhiteList + elementWhitelist + format[line, BrLine, Block, Closureblock, ClosureBrLine] - elementBlacklist)
564
+ o.set('_editorElementWhitelist', o.get('elementWhitelist') === '*' ? '*' : _createWhitelist(o));
565
+
566
+ /** Toolbar */
567
+ o.set('toolbar_width', options.toolbar_width ? (numbers.is(options.toolbar_width) ? options.toolbar_width + 'px' : options.toolbar_width) : 'auto');
568
+ o.set('toolbar_container', options.toolbar_container && !/inline/i.test(o.get('mode')) ? (typeof options.toolbar_container === 'string' ? _d.querySelector(options.toolbar_container) : options.toolbar_container) : null);
569
+ o.set('toolbar_sticky', /balloon/i.test(o.get('mode')) ? -1 : options.toolbar_sticky === undefined ? 0 : numbers.is(options.toolbar_sticky) ? numbers.get(options.toolbar_sticky, 0) : -1);
570
+ o.set('toolbar_hide', !!options.toolbar_hide);
571
+
572
+ /** subToolbar */
573
+ let subButtons = null;
574
+ const subbar = options.subToolbar;
575
+ if (subbar?.buttonList?.length > 0) {
576
+ if (/balloon/.test(o.get('mode'))) {
577
+ console.warn('[SUNEDITOR.create.subToolbar.fail] When the "mode" option is "balloon-*", the "subToolbar" option is omitted.');
578
+ } else {
579
+ o.set('_subMode', subbar.mode || 'balloon');
580
+ o.set('toolbar.sub_width', subbar.width ? (numbers.is(subbar.width) ? subbar.width + 'px' : subbar.width) : 'auto');
581
+ subButtons = o.get('_rtl') ? subbar.buttonList.reverse() : subbar.buttonList;
582
+ o.set('buttons_sub', new Set(subButtons.toString().split(',')));
583
+ }
584
+ }
585
+
586
+ /** root options */
587
+ const frameMap = new Map();
588
+ for (let i = 0, len = editorTargets.length; i < len; i++) {
589
+ frameMap.set(editorTargets[i].key, InitFrameOptions(editorTargets[i].options || {}, options));
590
+ }
591
+
592
+ /** Key actions */
593
+ o.set('tabDisable', !!options.tabDisable);
594
+ o.set('shortcutsHint', options.shortcutsHint === undefined ? true : !!options.shortcutsHint);
595
+ const shortcuts = !(options.shortcutsDisable === undefined ? true : !!options.shortcutsDisable)
596
+ ? {}
597
+ : [
598
+ {
599
+ // default command
600
+ selectAll: ['c+KeyA', 'A'],
601
+ bold: ['c+KeyB', 'B'],
602
+ strike: ['c+s+KeyS', 'S'],
603
+ underline: ['c+KeyU', 'U'],
604
+ italic: ['c+KeyI', 'I'],
605
+ redo: ['c+KeyY', 'Y', 'c+s+KeyZ', 'Z'],
606
+ undo: ['c+KeyZ', 'Z'],
607
+ indent: ['c+BracketRight', ']'],
608
+ outdent: ['c+BracketLeft', '['],
609
+ save: ['c+KeyS', 'S'],
610
+ // plugins
611
+ link: ['c+KeyK', 'K'],
612
+ hr: ['!+---+=+~shortcut', ''],
613
+ list_numbered: ['!+1.+_+~shortcut', ''],
614
+ list_bulleted: ['!+*.+_+~shortcut', ''],
615
+ // custom
616
+ _h1: ['c+s+Digit1|Numpad1+$~formatBlock.applyHeaderByShortcut', ''],
617
+ _h2: ['c+s+Digit2|Numpad2+$~formatBlock.applyHeaderByShortcut', ''],
618
+ _h3: ['c+s+Digit3|Numpad3+$~formatBlock.applyHeaderByShortcut', '']
619
+ },
620
+ options.shortcuts || {}
621
+ ].reduce((_default, _new) => {
622
+ for (const key in _new) {
623
+ _default[key] = _new[key];
624
+ }
625
+ return _default;
626
+ }, {});
627
+ o.set('shortcuts', shortcuts);
628
+
629
+ /** View */
630
+ o.set('fullScreenOffset', options.fullScreenOffset === undefined ? 0 : numbers.is(options.fullScreenOffset) ? numbers.get(options.fullScreenOffset, 0) : 0);
631
+ o.set('previewTemplate', typeof options.previewTemplate === 'string' ? options.previewTemplate : null);
632
+ o.set('printTemplate', typeof options.printTemplate === 'string' ? options.printTemplate : null);
633
+
634
+ /** --- Media select */
635
+ o.set('componentAutoSelect', options.componentAutoSelect === undefined ? false : !!options.componentAutoSelect);
636
+
637
+ /** --- Url input protocol */
638
+ o.set('defaultUrlProtocol', typeof options.defaultUrlProtocol === 'string' ? options.defaultUrlProtocol : null);
639
+
640
+ /** External library */
641
+ // CodeMirror
642
+ const cm = o.get('externalLibs').codeMirror;
643
+ if (cm) {
644
+ o.set('codeMirror', cm);
645
+ if (cm.EditorView) {
646
+ o.set('codeMirror6Editor', true);
647
+ } else if (cm.src) {
648
+ o.set('codeMirror5Editor', true);
649
+ } else {
650
+ console.warn('[SUNEDITOR.options.externalLibs.codeMirror.fail] The codeMirror option is set incorrectly.');
651
+ o.set('codeMirror', null);
652
+ }
653
+ }
654
+
655
+ /** Private options */
656
+ o.set('__listCommonStyle', options.__listCommonStyle || ['fontSize', 'color', 'fontFamily', 'fontWeight', 'fontStyle']);
657
+
658
+ /** --- Icons ------------------------------------------------------------------------------------------ */
659
+ const icons =
660
+ !options.icons || typeof options.icons !== 'object'
661
+ ? _icons
662
+ : [_icons, options.icons].reduce((_default, _new) => {
663
+ for (const key in _new) {
664
+ _default[key] = _new[key];
665
+ }
666
+ return _default;
667
+ }, {});
668
+ o.set('icons', icons);
669
+
670
+ /** Create all used styles */
671
+ const allUsedStyles = new Set(DEFAULTS.CONTENT_STYLES.split('|'));
672
+ const _ss = options.spanStyles?.split('|') || [];
673
+ const _ls = o.get('__listCommonStyle');
674
+ const _dts = DEFAULTS.SPAN_STYLES.split('|');
675
+ for (let i = 0, len = _dts.length; i < len; i++) {
676
+ allUsedStyles.add(_dts[i]);
677
+ }
678
+ for (const _ts of Object.values(o.get('tagStyles'))) {
679
+ const _tss = _ts.split('|');
680
+ for (let i = 0, len = _tss.length; i < len; i++) {
681
+ allUsedStyles.add(_tss[i]);
682
+ }
683
+ }
684
+ for (let i = 0, len = _ss.length; i < len; i++) {
685
+ allUsedStyles.add(_ss[i]);
686
+ }
687
+ for (let i = 0, len = _ls.length; i < len; i++) {
688
+ allUsedStyles.add(_ls[i]);
689
+ }
690
+ const _aus = (typeof options.allUsedStyles === 'string' ? options.allUsedStyles.split('|') : options.allUsedStyles) || [];
691
+ for (let i = 0, len = _aus.length; i < len; i++) {
692
+ allUsedStyles.add(_aus[i]);
693
+ }
694
+ o.set('allUsedStyles', allUsedStyles);
695
+ o.set('toastMessageTime', { copy: 1500, ...options.toastMessageTime });
696
+
697
+ return {
698
+ o: o,
699
+ i: icons,
700
+ l: /** @type {Object<string, string>} */ (options.lang || _defaultLang),
701
+ v: (options.value = typeof options.value === 'string' ? options.value : null),
702
+ buttons: o.get('_rtl') ? buttonList.reverse() : buttonList,
703
+ subButtons: subButtons,
704
+ statusbarContainer: typeof options.statusbar_container === 'string' ? _d.querySelector(options.statusbar_container) : options.statusbar_container,
705
+ frameMap: frameMap
706
+ };
707
+ }
708
+
709
+ /**
710
+ * @description Create a context object for the editor frame.
711
+ * @param {Map<string, *>} targetOptions - editor.frameOptions
712
+ * @param {HTMLElement} statusbar - statusbar element
713
+ * @returns {{statusbar: HTMLElement, navigation: HTMLElement, charWrapper: HTMLElement, charCounter: HTMLElement}}
714
+ */
715
+ export function CreateStatusbar(targetOptions, statusbar) {
716
+ let navigation = null;
717
+ let charWrapper = null;
718
+ let charCounter = null;
719
+
720
+ if (targetOptions.get('statusbar')) {
721
+ statusbar = statusbar || dom.utils.createElement('DIV', { class: 'se-status-bar sun-editor-common' });
722
+
723
+ /** navigation */
724
+ navigation = statusbar.querySelector('.se-navigation') || dom.utils.createElement('DIV', { class: 'se-navigation sun-editor-common' });
725
+ statusbar.appendChild(navigation);
726
+
727
+ /** char counter */
728
+ if (targetOptions.get('charCounter')) {
729
+ charWrapper = statusbar.querySelector('.se-char-counter-wrapper') || dom.utils.createElement('DIV', { class: 'se-char-counter-wrapper' });
730
+
731
+ if (targetOptions.get('charCounter_label')) {
732
+ const charLabel = charWrapper.querySelector('.se-char-label') || dom.utils.createElement('SPAN', { class: 'se-char-label' });
733
+ charLabel.textContent = targetOptions.get('charCounter_label');
734
+ charWrapper.appendChild(charLabel);
735
+ }
736
+
737
+ charCounter = charWrapper.querySelector('.se-char-counter') || dom.utils.createElement('SPAN', { class: 'se-char-counter' });
738
+ charCounter.textContent = '0';
739
+ charWrapper.appendChild(charCounter);
740
+
741
+ if (targetOptions.get('charCounter_max') > 0) {
742
+ const char_max = charWrapper.querySelector('.se-char-max') || dom.utils.createElement('SPAN', { class: 'se-char-max' });
743
+ char_max.textContent = ' / ' + targetOptions.get('charCounter_max');
744
+ charWrapper.appendChild(char_max);
745
+ }
746
+
747
+ statusbar.appendChild(charWrapper);
748
+ }
749
+ }
750
+
751
+ return {
752
+ statusbar: statusbar,
753
+ navigation: /** @type {HTMLElement} */ (navigation),
754
+ charWrapper: /** @type {HTMLElement} */ (charWrapper),
755
+ charCounter: /** @type {HTMLElement} */ (charCounter)
756
+ };
757
+ }
758
+
759
+ /**
760
+ * @description Initialize options.
761
+ * @param {EditorFrameOptions} o - Target options
762
+ * @param {EditorInitOptions} origin - Full options
763
+ * @returns {Map<string, *>}
764
+ */
765
+ function InitFrameOptions(o, origin) {
766
+ const fo = new Map();
767
+
768
+ fo.set('_origin', o);
769
+ const barContainer = origin.statusbar_container;
770
+
771
+ // members
772
+ const value = o.value === undefined ? origin.value : o.value;
773
+ const placeholder = o.placeholder === undefined ? origin.placeholder : o.placeholder;
774
+ const editableFrameAttributes = o.editableFrameAttributes === undefined ? origin.editableFrameAttributes : o.editableFrameAttributes;
775
+ const width = o.width === undefined ? origin.width : o.width;
776
+ const minWidth = o.minWidth === undefined ? origin.minWidth : o.minWidth;
777
+ const maxWidth = o.maxWidth === undefined ? origin.maxWidth : o.maxWidth;
778
+ const height = o.height === undefined ? origin.height : o.height;
779
+ const minHeight = o.minHeight === undefined ? origin.minHeight : o.minHeight;
780
+ const maxHeight = o.maxHeight === undefined ? origin.maxHeight : o.maxHeight;
781
+ const editorStyle = o.editorStyle === undefined ? origin.editorStyle : o.editorStyle;
782
+ const iframe = o.iframe === undefined ? origin.iframe : o.iframe;
783
+ const iframe_fullPage = o.iframe_fullPage === undefined ? origin.iframe_fullPage : o.iframe_fullPage;
784
+ const iframe_attributes = o.iframe_attributes === undefined ? origin.iframe_attributes : o.iframe_attributes;
785
+ const iframe_cssFileName = o.iframe_cssFileName === undefined ? origin.iframe_cssFileName : o.iframe_cssFileName;
786
+ const statusbar = barContainer || o.statusbar === undefined ? origin.statusbar : o.statusbar;
787
+ const statusbar_showPathLabel = barContainer || o.statusbar_showPathLabel === undefined ? origin.statusbar_showPathLabel : o.statusbar_showPathLabel;
788
+ const statusbar_resizeEnable = barContainer ? false : o.statusbar_resizeEnable === undefined ? origin.statusbar_resizeEnable : o.statusbar_resizeEnable;
789
+ const charCounter = barContainer || o.charCounter === undefined ? origin.charCounter : o.charCounter;
790
+ const charCounter_max = barContainer || o.charCounter_max === undefined ? origin.charCounter_max : o.charCounter_max;
791
+ const charCounter_label = barContainer || o.charCounter_label === undefined ? origin.charCounter_label : o.charCounter_label;
792
+ const charCounter_type = barContainer || o.charCounter_type === undefined ? origin.charCounter_type : o.charCounter_type;
793
+
794
+ // value
795
+ fo.set('value', value);
796
+ fo.set('placeholder', placeholder);
797
+ fo.set('editableFrameAttributes', { spellcheck: 'false', ...editableFrameAttributes });
798
+ // styles
799
+ fo.set('width', width ? (numbers.is(width) ? width + 'px' : width) : '100%');
800
+ fo.set('minWidth', (numbers.is(minWidth) ? minWidth + 'px' : minWidth) || '');
801
+ fo.set('maxWidth', (numbers.is(maxWidth) ? maxWidth + 'px' : maxWidth) || '');
802
+ fo.set('height', height ? (numbers.is(height) ? height + 'px' : height) : 'auto');
803
+ fo.set('minHeight', (numbers.is(minHeight) ? minHeight + 'px' : minHeight) || '');
804
+ fo.set('maxHeight', (numbers.is(maxHeight) ? maxHeight + 'px' : maxHeight) || '');
805
+ fo.set('editorStyle', editorStyle);
806
+ fo.set('_defaultStyles', converter._setDefaultOptionStyle(fo, typeof editorStyle === 'string' ? editorStyle : ''));
807
+ // iframe
808
+ fo.set('iframe', !!(iframe_fullPage || iframe));
809
+ fo.set('iframe_fullPage', !!iframe_fullPage);
810
+ fo.set('iframe_attributes', iframe_attributes || {});
811
+ fo.set('iframe_cssFileName', iframe ? (typeof iframe_cssFileName === 'string' ? [iframe_cssFileName] : iframe_cssFileName || ['suneditor']) : null);
812
+ // status bar
813
+ const hasStatusbar = statusbar === undefined ? true : !!statusbar;
814
+ fo.set('statusbar', hasStatusbar);
815
+ fo.set('statusbar_showPathLabel', !hasStatusbar ? false : typeof statusbar_showPathLabel === 'boolean' ? statusbar_showPathLabel : true);
816
+ fo.set('statusbar_resizeEnable', !hasStatusbar ? false : statusbar_resizeEnable === undefined ? true : !!statusbar_resizeEnable);
817
+ // status bar - character count
818
+ fo.set('charCounter', charCounter_max > 0 ? true : typeof charCounter === 'boolean' ? charCounter : false);
819
+ fo.set('charCounter_max', numbers.is(charCounter_max) && charCounter_max > -1 ? charCounter_max * 1 : null);
820
+ fo.set('charCounter_label', typeof charCounter_label === 'string' ? charCounter_label.trim() : null);
821
+ fo.set('charCounter_type', typeof charCounter_type === 'string' ? charCounter_type : 'char');
822
+
823
+ return fo;
824
+ }
825
+
826
+ /**
827
+ * @private
828
+ * @description Initialize property of suneditor elements
829
+ * @param {string} key - The key of the editor frame
830
+ * @param {Map<string, *>} options - options
831
+ * @param {HTMLElement} topDiv - top div
832
+ * @param {Map<string, *>} targetOptions - editor.frameOptions
833
+ * @returns {{bottomBar: ReturnType<CreateStatusbar>, wysiwygFrame: HTMLElement, codeView: HTMLElement, placeholder: HTMLElement}}
834
+ */
835
+ function _initTargetElements(key, options, topDiv, targetOptions) {
836
+ const editorStyles = targetOptions.get('_defaultStyles');
837
+ /** top div */
838
+ topDiv.style.cssText = editorStyles.top;
839
+
840
+ /** editor */
841
+ // wysiwyg div or iframe
842
+ const wysiwygDiv = dom.utils.createElement(!targetOptions.get('iframe') ? 'DIV' : 'IFRAME', {
843
+ class: 'se-wrapper-inner se-wrapper-wysiwyg' + (options.get('type') === 'document' ? ' se-type-document-iframe-a4' : ''),
844
+ 'data-root-key': key
845
+ });
846
+
847
+ if (!targetOptions.get('iframe')) {
848
+ wysiwygDiv.setAttribute('contenteditable', 'true');
849
+ wysiwygDiv.setAttribute('scrolling', 'auto');
850
+ wysiwygDiv.className += ' ' + options.get('_editableClass');
851
+ wysiwygDiv.style.cssText = editorStyles.frame + editorStyles.editor;
852
+ } else {
853
+ const frameAttrs = targetOptions.get('iframe_attributes');
854
+ for (const frameKey in frameAttrs) {
855
+ wysiwygDiv.setAttribute(frameKey, frameAttrs[frameKey]);
856
+ }
857
+
858
+ const iframeWW = /** @type {HTMLIFrameElement} */ (wysiwygDiv);
859
+ iframeWW.allowFullscreen = true;
860
+ iframeWW.frameBorder = '0';
861
+ iframeWW.style.cssText = editorStyles.frame;
862
+ }
863
+
864
+ // textarea for code view
865
+ const textarea = dom.utils.createElement('TEXTAREA', { class: 'se-wrapper-inner se-code-viewer', style: editorStyles.frame });
866
+ const placeholder = dom.utils.createElement('SPAN', { class: 'se-placeholder' });
867
+ if (targetOptions.get('placeholder')) {
868
+ placeholder.textContent = targetOptions.get('placeholder');
869
+ }
870
+
871
+ return {
872
+ bottomBar: CreateStatusbar(targetOptions, null),
873
+ wysiwygFrame: wysiwygDiv,
874
+ codeView: textarea,
875
+ placeholder: placeholder
876
+ };
877
+ }
878
+
879
+ /**
880
+ * @private
881
+ * @description Check the CodeMirror option to apply the CodeMirror and return the CodeMirror element.
882
+ * @param {Map<string, *>} options options
883
+ * @param {HTMLElement} textarea textarea element
884
+ */
885
+ function _checkCodeMirror(options, targetOptions, textarea) {
886
+ let cmeditor = null;
887
+ let hasCodeMirror = false;
888
+
889
+ if (options.get('codeMirror6Editor')) {
890
+ const codeMirror = options.get('codeMirror');
891
+ const codeStyles = textarea.style.cssText;
892
+ const cm = new codeMirror.EditorView({
893
+ parent: textarea.parentElement,
894
+ extensions: codeMirror.extensions,
895
+ state: codeMirror.state
896
+ });
897
+
898
+ targetOptions.set('codeMirror6Editor', cm);
899
+ cmeditor = cm.dom;
900
+ cmeditor.style.cssText = codeStyles;
901
+ hasCodeMirror = true;
902
+ } else if (options.get('codeMirror5Editor')) {
903
+ const codeMirror = options.get('codeMirror');
904
+ const cmOptions = [
905
+ {
906
+ mode: 'htmlmixed',
907
+ htmlMode: true,
908
+ lineNumbers: true,
909
+ lineWrapping: true
910
+ },
911
+ codeMirror.options || {}
912
+ ].reduce((init, option) => {
913
+ for (const key in option) {
914
+ init[key] = option[key];
915
+ }
916
+ return init;
917
+ }, {});
918
+
919
+ if (targetOptions.get('height') === 'auto') {
920
+ cmOptions.viewportMargin = Infinity;
921
+ cmOptions.height = 'auto';
922
+ }
923
+
924
+ const codeStyles = textarea.style.cssText;
925
+ const cm = codeMirror.src.fromTextArea(textarea, cmOptions);
926
+ targetOptions.set('codeMirror5Editor', cm);
927
+ cmeditor = cm.display.wrapper;
928
+ cmeditor.style.cssText = codeStyles;
929
+ hasCodeMirror = true;
930
+ }
931
+
932
+ options.set('hasCodeMirror', hasCodeMirror);
933
+ if (cmeditor) {
934
+ dom.utils.removeItem(textarea);
935
+ cmeditor.className += ' se-code-viewer-mirror';
936
+ return cmeditor;
937
+ }
938
+
939
+ return textarea;
940
+ }
941
+
942
+ /**
943
+ * @private
944
+ * @description create blacklist
945
+ * @param {string} blacklist blacklist
946
+ * @param {string} defaultLine options.get('defaultLine')
947
+ * @returns {string}
948
+ */
949
+ function _createBlacklist(blacklist, defaultLine) {
950
+ defaultLine = defaultLine.toLowerCase();
951
+ return blacklist
952
+ .split('|')
953
+ .filter(function (v) {
954
+ if (v !== defaultLine) {
955
+ return true;
956
+ } else {
957
+ console.warn(`[SUNEDITOR.constructor.createBlacklist.warn] defaultLine("<${defaultLine}>") cannot be included in the blacklist and will be removed.`);
958
+ return false;
959
+ }
960
+ })
961
+ .join('|');
962
+ }
963
+
964
+ /**
965
+ * @private
966
+ * @description create formats regexp object.
967
+ * @param {string} value value
968
+ * @param {string} defaultValue default value
969
+ * @param {string} blacklist blacklist
970
+ * @returns {{reg: RegExp, str: string}}
971
+ */
972
+ function _createFormatInfo(value, defaultValue, blacklist) {
973
+ const blist = blacklist.split('|');
974
+ const str = (defaultValue + '|' + (typeof value === 'string' ? value.toLowerCase() : ''))
975
+ .replace(/^\||\|$/g, '')
976
+ .split('|')
977
+ .filter((v) => v && !blist.includes(v))
978
+ .join('|');
979
+ return {
980
+ reg: new RegExp(`^(${str})$`, 'i'),
981
+ str: str
982
+ };
983
+ }
984
+
985
+ /**
986
+ * @private
987
+ * @description create whitelist or blacklist.
988
+ * @param {Map<string, *>} o options
989
+ * @returns {string} whitelist
990
+ */
991
+ function _createWhitelist(o) {
992
+ const blacklist = o.get('elementBlacklist').split('|');
993
+ const whitelist = (o.get('__defaultElementWhitelist') + '|' + o.get('elementWhitelist') + '|' + o.get('formatLine').str + '|' + o.get('formatBrLine').str + '|' + o.get('formatClosureBlock').str + '|' + o.get('formatClosureBrLine').str)
994
+ .replace(/(^\||\|$)/g, '')
995
+ .split('|')
996
+ .filter((v, i, a) => v && a.indexOf(v) === i && !blacklist.includes(v));
997
+
998
+ return whitelist.join('|');
999
+ }
1000
+
1001
+ /**
1002
+ * @private
1003
+ * @description Suneditor's Default button list
1004
+ * @param {Map<string, *>} options options
1005
+ */
1006
+ function _defaultButtons(options, icons, lang) {
1007
+ const isRTL = options.get('_rtl');
1008
+ return {
1009
+ bold: ['', lang.bold, 'bold', '', icons.bold],
1010
+ underline: ['', lang.underline, 'underline', '', icons.underline],
1011
+ italic: ['', lang.italic, 'italic', '', icons.italic],
1012
+ strike: ['', lang.strike, 'strike', '', icons.strike],
1013
+ subscript: ['', lang.subscript, 'subscript', '', icons.subscript],
1014
+ superscript: ['', lang.superscript, 'superscript', '', icons.superscript],
1015
+ removeFormat: ['', lang.removeFormat, 'removeFormat', '', icons.remove_format],
1016
+ copyFormat: ['', lang.copyFormat, 'copyFormat', '', icons.format_paint],
1017
+ indent: ['se-icon-flip-rtl', lang.indent, 'indent', '', isRTL ? icons.outdent : icons.indent],
1018
+ outdent: ['se-icon-flip-rtl', lang.outdent, 'outdent', '', isRTL ? icons.indent : icons.outdent],
1019
+ fullScreen: ['se-code-view-enabled se-component-enabled', lang.fullScreen, 'fullScreen', '', icons.expansion],
1020
+ showBlocks: ['', lang.showBlocks, 'showBlocks', '', icons.show_blocks],
1021
+ codeView: ['se-code-view-enabled se-component-enabled', lang.codeView, 'codeView', '', icons.code_view],
1022
+ undo: ['se-component-enabled', lang.undo, 'undo', '', icons.undo],
1023
+ redo: ['se-component-enabled', lang.redo, 'redo', '', icons.redo],
1024
+ preview: ['se-component-enabled', lang.preview, 'preview', '', icons.preview],
1025
+ print: ['se-component-enabled', lang.print, 'print', '', icons.print],
1026
+ copy: ['', lang.copy, 'copy', '', icons.copy],
1027
+ dir: ['', lang[isRTL ? 'dir_ltr' : 'dir_rtl'], 'dir', '', icons[isRTL ? 'dir_ltr' : 'dir_rtl']],
1028
+ dir_ltr: ['', lang.dir_ltr, 'dir_ltr', '', icons.dir_ltr],
1029
+ dir_rtl: ['', lang.dir_rtl, 'dir_rtl', '', icons.dir_rtl],
1030
+ save: ['se-component-enabled', lang.save, 'save', '', icons.save],
1031
+ newDocument: ['se-component-enabled', lang.newDocument, 'newDocument', '', icons.new_document],
1032
+ selectAll: ['se-component-enabled', lang.selectAll, 'selectAll', '', icons.select_all],
1033
+ pageBreak: ['se-component-enabled', lang.pageBreak, 'pageBreak', '', icons.page_break],
1034
+ // document type buttons
1035
+ pageUp: ['se-component-enabled', lang.pageUp, 'pageUp', '', icons.page_up],
1036
+ pageDown: ['se-component-enabled', lang.pageDown, 'pageDown', '', icons.page_down],
1037
+ pageNavigator: ['se-component-enabled', '', 'pageNavigator', 'input', '']
1038
+ };
1039
+ }
1040
+
1041
+ /**
1042
+ * @private
1043
+ * @description Create a group div containing each module
1044
+ * @returns {{div: Element, ul: Element}}
1045
+ */
1046
+ function _createModuleGroup() {
1047
+ const oUl = dom.utils.createElement('UL', { class: 'se-menu-list' });
1048
+ const oDiv = dom.utils.createElement('DIV', { class: 'se-btn-module se-btn-module-border' }, oUl);
1049
+
1050
+ return {
1051
+ div: oDiv,
1052
+ ul: oUl
1053
+ };
1054
+ }
1055
+
1056
+ /**
1057
+ * @private
1058
+ * @description Create a button element
1059
+ * @param {string} className className in button
1060
+ * @param {string} title Title in button
1061
+ * @param {string} dataCommand The data-command property of the button
1062
+ * @param {"command"|"dropdown"|"field"|"browser"|"input"|"modal"|"popup"} dataType The data-type property of the button
1063
+ * @param {string} innerHTML Html in button
1064
+ * @param {string} _disabled Button disabled
1065
+ * @param {Object<string, string>} icons Icons
1066
+ * @returns {{li: HTMLElement, button: HTMLElement}}
1067
+ */
1068
+ function _createButton(className, title, dataCommand, dataType, innerHTML, _disabled, icons) {
1069
+ if (!innerHTML) innerHTML = '';
1070
+
1071
+ const oLi = dom.utils.createElement('LI');
1072
+ const label = title || '';
1073
+ const isDiv = /^INPUT|FIELD$/i.test(dataType);
1074
+ const oButton = /** @type {HTMLButtonElement} */ (
1075
+ 'se-toolbar-separator-vertical' === className
1076
+ ? dom.utils.createElement('DIV', { class: className, tabindex: '-1' }, null)
1077
+ : dom.utils.createElement(isDiv ? 'DIV' : 'BUTTON', {
1078
+ class: 'se-toolbar-btn se-btn se-tooltip' + (className ? ' ' + className : ''),
1079
+ 'data-command': dataCommand,
1080
+ 'data-type': dataType,
1081
+ 'aria-label': label.replace(/<span .+<\/span>/, ''),
1082
+ tabindex: '-1'
1083
+ })
1084
+ );
1085
+
1086
+ if (!isDiv) {
1087
+ oButton.setAttribute('type', 'button');
1088
+ }
1089
+
1090
+ if (/^default\./i.test(innerHTML)) {
1091
+ innerHTML = icons[innerHTML.replace(/^default\./i, '')];
1092
+ }
1093
+ if (/^text\./i.test(innerHTML)) {
1094
+ innerHTML = innerHTML.replace(/^text\./i, '');
1095
+ oButton.className += ' se-btn-more-text';
1096
+ }
1097
+
1098
+ if (_disabled) oButton.disabled = true;
1099
+
1100
+ if (/^FIELD$/i.test(dataType)) dom.utils.addClass(oLi, 'se-toolbar-hidden-btn');
1101
+
1102
+ if (label) innerHTML += dom.utils.createTooltipInner(label);
1103
+ if (innerHTML) oButton.innerHTML = innerHTML;
1104
+
1105
+ oLi.appendChild(oButton);
1106
+
1107
+ return {
1108
+ li: oLi,
1109
+ button: oButton
1110
+ };
1111
+ }
1112
+
1113
+ /**
1114
+ * @description Update a button state, attributes, and icons
1115
+ * @param {HTMLElement|null} element Button element
1116
+ * @param {Object<string, *>} plugin Plugin
1117
+ * @param {Object<string, string>} icons Icons
1118
+ * @param {Object<string, string>} lang lang
1119
+ */
1120
+ export function UpdateButton(element, plugin, icons, lang) {
1121
+ if (!element) return;
1122
+
1123
+ const noneInner = plugin.inner === false;
1124
+
1125
+ if (plugin.inner?.nodeType === 1) {
1126
+ element.appendChild(plugin.inner);
1127
+ } else {
1128
+ element.innerHTML = noneInner
1129
+ ? ''
1130
+ : (plugin.inner || icons[plugin.icon] || plugin.icon || '<span class="se-icon-text">!</span>') + '<span class="se-tooltip-inner"><span class="se-tooltip-text">' + (lang[plugin.title] || plugin.title) + '</span></span>';
1131
+ }
1132
+
1133
+ element.setAttribute('aria-label', plugin.title);
1134
+
1135
+ if (plugin.type) {
1136
+ element.setAttribute('data-type', plugin.type);
1137
+ }
1138
+
1139
+ if (plugin.className) {
1140
+ element.className += ' ' + plugin.className;
1141
+ }
1142
+
1143
+ // side, replace button
1144
+ if (plugin.afterItem) {
1145
+ dom.utils.addClass(plugin.afterItem, 'se-toolbar-btn');
1146
+ element.parentElement.appendChild(plugin.afterItem);
1147
+
1148
+ dom.utils.addClass(element, 'se-side-btn-a');
1149
+ dom.utils.addClass(plugin.afterItem, 'se-side-btn-after');
1150
+ }
1151
+ if (plugin.beforeItem) {
1152
+ dom.utils.addClass(plugin.beforeItem, 'se-toolbar-btn');
1153
+ element.parentElement.insertBefore(plugin.beforeItem, element);
1154
+
1155
+ if (plugin.afterItem) {
1156
+ dom.utils.addClass(element, 'se-side-btn');
1157
+ dom.utils.removeClass(element, 'se-side-btn-a');
1158
+ } else {
1159
+ dom.utils.addClass(element, 'se-side-btn-b');
1160
+ }
1161
+ dom.utils.addClass(plugin.beforeItem, 'se-side-btn-before');
1162
+ }
1163
+ if (plugin.replaceButton) {
1164
+ element.parentElement.appendChild(plugin.replaceButton);
1165
+ element.style.display = 'none';
1166
+ }
1167
+
1168
+ if (!plugin.replaceButton && /^INPUT$/i.test(element.getAttribute('data-type'))) {
1169
+ const inputTarget = element.querySelector('input');
1170
+ if (inputTarget) {
1171
+ dom.utils.addClass(inputTarget, 'se-toolbar-btn');
1172
+ inputTarget.setAttribute('data-command', element.getAttribute('data-command'));
1173
+ inputTarget.setAttribute('data-type', element.getAttribute('data-type'));
1174
+ if (element.hasAttribute('disabled')) inputTarget.disabled = true;
1175
+ }
1176
+ }
1177
+ }
1178
+
1179
+ /**
1180
+ * @description Create editor HTML
1181
+ * @param {Array} buttonList option.buttonList
1182
+ * @param {?Object<string, *>} plugins Plugins
1183
+ * @param {Map<string, *>} options options
1184
+ * @param {Object<string, string>} icons icons
1185
+ * @param {Object<string, string>} lang lang
1186
+ * @param {boolean} isUpdate Is update
1187
+ * @returns {{element: HTMLElement, pluginCallButtons: Object<string, Array<HTMLElement>>, responsiveButtons: Array<HTMLElement>, buttonTray: HTMLElement, updateButtons: Array<{button: HTMLElement, plugin: *, key: string}>}}}
1188
+ */
1189
+ export function CreateToolBar(buttonList, plugins, options, icons, lang, isUpdate) {
1190
+ /** create button list */
1191
+ buttonList = JSON.parse(JSON.stringify(buttonList));
1192
+ const defaultButtonList = _defaultButtons(options, icons, lang);
1193
+ /** @type {Object<string, Array<HTMLElement>>} */
1194
+ const pluginCallButtons = {};
1195
+ const responsiveButtons = [];
1196
+ const updateButtons = [];
1197
+
1198
+ let modules = null;
1199
+ let button = null;
1200
+ let plugin = null;
1201
+ let moduleElement = null;
1202
+ let buttonElement = null;
1203
+ // let vertical = false;
1204
+ const moreLayer = dom.utils.createElement('DIV', { class: 'se-toolbar-more-layer' });
1205
+ const buttonTray = dom.utils.createElement('DIV', { class: 'se-btn-tray' });
1206
+ const separator_vertical = dom.utils.createElement('DIV', { class: 'se-toolbar-separator-vertical' });
1207
+
1208
+ buttonGroupLoop: for (let i = 0, more, moreContainer, moreCommand, buttonGroup, align; i < buttonList.length; i++) {
1209
+ more = false;
1210
+ align = '';
1211
+ buttonGroup = buttonList[i];
1212
+ moduleElement = _createModuleGroup();
1213
+
1214
+ // button object
1215
+ if (typeof buttonGroup === 'object') {
1216
+ // buttons loop
1217
+ for (let j = 0, moreButton; j < buttonGroup.length; j++) {
1218
+ button = buttonGroup[j];
1219
+ moreButton = false;
1220
+ plugin = plugins[button];
1221
+
1222
+ if (/^%\d+/.test(button) && j === 0) {
1223
+ buttonGroup[0] = button.replace(/[^\d]/g, '');
1224
+ responsiveButtons.push(buttonGroup);
1225
+ buttonList.splice(i--, 1);
1226
+ continue buttonGroupLoop;
1227
+ }
1228
+ if (typeof plugin === 'function') {
1229
+ modules = [plugin.className, plugin.title, button, plugin.type, plugin.innerHTML, plugin._disabled];
1230
+ } else if (typeof plugin === 'object') {
1231
+ const originFnc = plugin.constructor;
1232
+ modules = [plugin.className || originFnc.className, plugin.title || originFnc.title, button, plugin.type || originFnc.type, plugin.innerHTML || originFnc.innerHTML, plugin._disabled || originFnc._disabled];
1233
+ } else {
1234
+ // align
1235
+ if (/^-/.test(button)) {
1236
+ align = button.substring(1);
1237
+ moduleElement.div.className += ' module-float-' + align;
1238
+ continue;
1239
+ }
1240
+
1241
+ // rtl fix
1242
+ if (/^#/.test(button)) {
1243
+ const option = button.substring(1);
1244
+ if (option === 'fix') moduleElement.ul.className += ' se-menu-dir-fix';
1245
+ continue;
1246
+ }
1247
+
1248
+ // more button
1249
+ if (/^:/.test(button)) {
1250
+ moreButton = true;
1251
+ const matched = button.match(/^:([^-]+)-([^-]+)/);
1252
+ moreCommand = '__se__more_' + i;
1253
+ const title = matched[1].trim();
1254
+ const innerHTML = matched[2].trim();
1255
+ modules = ['se-btn-more', /^lang\./i.test(title) ? lang[title.replace(/^lang\./i, '')] : title, moreCommand, 'MORE', innerHTML];
1256
+ } else if (button === '|') {
1257
+ // separator vertical
1258
+ modules = ['se-toolbar-separator-vertical', '', '', 'separator', ''];
1259
+ } else {
1260
+ // default command
1261
+ if (button === 'copy' && !env.isClipboardSupported) {
1262
+ console.warn('[SUNEDITOR.constructor.warn] Clipboard is not supported in this browser. : [copy] button is not rendered.');
1263
+ continue;
1264
+ }
1265
+ modules = defaultButtonList[button];
1266
+ }
1267
+
1268
+ if (!modules) {
1269
+ if (!plugin) throw Error(`[SUNEDITOR.create.toolbar.fail] The button name of a plugin that does not exist. [${button}]`);
1270
+ plugin = typeof plugin === 'object' ? plugin.constructor : plugin;
1271
+ modules = [plugin.className, plugin.title, plugin.key, plugin.type, plugin.innerHTML, plugin._disabled];
1272
+ }
1273
+ }
1274
+
1275
+ buttonElement = _createButton(modules[0], modules[1], modules[2], modules[3], modules[4], modules[5], icons);
1276
+ (more ? moreContainer : moduleElement.ul).appendChild(buttonElement.li);
1277
+
1278
+ if (plugin) {
1279
+ if (pluginCallButtons[button]) {
1280
+ pluginCallButtons[button].push(buttonElement.button);
1281
+ } else {
1282
+ pluginCallButtons[button] = [buttonElement.button];
1283
+ }
1284
+
1285
+ if (isUpdate) {
1286
+ updateButtons.push({ button: buttonElement.button, plugin, key: button });
1287
+ }
1288
+ }
1289
+
1290
+ // more button
1291
+ if (moreButton) {
1292
+ more = true;
1293
+ moreContainer = dom.utils.createElement('DIV');
1294
+ moreContainer.className = 'se-more-layer ' + moreCommand;
1295
+ moreContainer.setAttribute('data-ref', moreCommand);
1296
+ moreContainer.innerHTML = '<div class="se-more-form"><ul class="se-menu-list"' + (align ? ' style="float: ' + align + ';"' : '') + '></ul></div>';
1297
+ moreLayer.appendChild(moreContainer);
1298
+ moreContainer = moreContainer.firstElementChild.firstElementChild;
1299
+ }
1300
+ }
1301
+
1302
+ // if (vertical) {
1303
+ // const sv = separator_vertical.cloneNode(false);
1304
+ // buttonTray.appendChild(sv);
1305
+ // }
1306
+
1307
+ buttonTray.appendChild(moduleElement.div);
1308
+ // vertical = true;
1309
+ } else if (buttonGroup === '|') {
1310
+ // // separator vertical
1311
+ const sv = separator_vertical.cloneNode(false);
1312
+ buttonTray.appendChild(sv);
1313
+ continue;
1314
+ } else if (/^\/$/.test(buttonGroup)) {
1315
+ /** line break */
1316
+ const enterDiv = dom.utils.createElement('DIV', { class: 'se-btn-module-enter' });
1317
+ buttonTray.appendChild(enterDiv);
1318
+ // vertical = false;
1319
+ }
1320
+ }
1321
+
1322
+ switch (buttonTray.children.length) {
1323
+ case 0:
1324
+ buttonTray.style.display = 'none';
1325
+ break;
1326
+ case 1:
1327
+ dom.utils.removeClass(buttonTray.firstElementChild, 'se-btn-module-border');
1328
+ break;
1329
+ }
1330
+
1331
+ if (moreLayer.children.length > 0) buttonTray.appendChild(moreLayer);
1332
+ if (responsiveButtons.length > 0) responsiveButtons.unshift(buttonList);
1333
+
1334
+ // rendering toolbar
1335
+ const tool_bar = dom.utils.createElement('DIV', { class: 'se-toolbar sun-editor-common' + (!options.get('shortcutsHint') ? ' se-shortcut-hide' : '') }, buttonTray);
1336
+
1337
+ if (options.get('toolbar_hide')) tool_bar.style.display = 'none';
1338
+
1339
+ return {
1340
+ element: tool_bar,
1341
+ pluginCallButtons,
1342
+ responsiveButtons,
1343
+ buttonTray,
1344
+ updateButtons
1345
+ };
1346
+ }
1347
+
1348
+ export default Constructor;