suneditor 3.0.0-alpha.9 → 3.0.0-beta.2

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 (315) hide show
  1. package/CONTRIBUTING.md +170 -22
  2. package/{LICENSE.txt → LICENSE} +9 -9
  3. package/README.md +168 -30
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +47 -21
  7. package/src/assets/design/color.css +121 -0
  8. package/src/assets/design/index.css +3 -0
  9. package/src/assets/design/size.css +35 -0
  10. package/src/assets/design/typography.css +37 -0
  11. package/src/assets/icons/defaultIcons.js +232 -0
  12. package/src/assets/suneditor-contents.css +181 -46
  13. package/src/assets/suneditor.css +1403 -650
  14. package/src/core/base/eventHandlers/handler_toolbar.js +35 -14
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +23 -4
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +49 -10
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +422 -224
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +83 -36
  19. package/src/core/base/eventManager.js +520 -179
  20. package/src/core/base/history.js +95 -41
  21. package/src/core/class/char.js +26 -11
  22. package/src/core/class/component.js +345 -137
  23. package/src/core/class/format.js +683 -519
  24. package/src/core/class/html.js +485 -305
  25. package/src/core/class/menu.js +133 -47
  26. package/src/core/class/nodeTransform.js +90 -71
  27. package/src/core/class/offset.js +408 -92
  28. package/src/core/class/selection.js +216 -106
  29. package/src/core/class/shortcuts.js +68 -8
  30. package/src/core/class/toolbar.js +106 -116
  31. package/src/core/class/ui.js +422 -0
  32. package/src/core/class/viewer.js +178 -74
  33. package/src/core/editor.js +496 -389
  34. package/src/core/section/actives.js +123 -27
  35. package/src/core/section/constructor.js +615 -206
  36. package/src/core/section/context.js +28 -23
  37. package/src/core/section/documentType.js +561 -0
  38. package/src/editorInjector/_classes.js +19 -5
  39. package/src/editorInjector/_core.js +71 -7
  40. package/src/editorInjector/index.js +63 -1
  41. package/src/events.js +622 -0
  42. package/src/helper/clipboard.js +59 -0
  43. package/src/helper/converter.js +202 -26
  44. package/src/helper/dom/domCheck.js +304 -0
  45. package/src/helper/dom/domQuery.js +669 -0
  46. package/src/helper/dom/domUtils.js +557 -0
  47. package/src/helper/dom/index.js +12 -0
  48. package/src/helper/env.js +46 -56
  49. package/src/helper/index.js +10 -4
  50. package/src/helper/keyCodeMap.js +183 -0
  51. package/src/helper/numbers.js +12 -8
  52. package/src/helper/unicode.js +9 -5
  53. package/src/langs/ckb.js +74 -4
  54. package/src/langs/cs.js +72 -2
  55. package/src/langs/da.js +73 -3
  56. package/src/langs/de.js +73 -4
  57. package/src/langs/en.js +23 -3
  58. package/src/langs/es.js +73 -4
  59. package/src/langs/fa.js +75 -3
  60. package/src/langs/fr.js +73 -3
  61. package/src/langs/he.js +73 -4
  62. package/src/langs/hu.js +230 -0
  63. package/src/langs/index.js +7 -3
  64. package/src/langs/it.js +70 -1
  65. package/src/langs/ja.js +72 -4
  66. package/src/langs/km.js +230 -0
  67. package/src/langs/ko.js +22 -2
  68. package/src/langs/lv.js +74 -5
  69. package/src/langs/nl.js +73 -4
  70. package/src/langs/pl.js +73 -4
  71. package/src/langs/pt_br.js +70 -1
  72. package/src/langs/ro.js +74 -5
  73. package/src/langs/ru.js +73 -4
  74. package/src/langs/se.js +73 -4
  75. package/src/langs/tr.js +73 -1
  76. package/src/langs/{ua.js → uk.js} +75 -6
  77. package/src/langs/ur.js +77 -8
  78. package/src/langs/zh_cn.js +74 -5
  79. package/src/modules/ApiManager.js +77 -54
  80. package/src/modules/Browser.js +667 -0
  81. package/src/modules/ColorPicker.js +162 -102
  82. package/src/modules/Controller.js +273 -142
  83. package/src/modules/Figure.js +925 -484
  84. package/src/modules/FileManager.js +121 -69
  85. package/src/modules/HueSlider.js +113 -61
  86. package/src/modules/Modal.js +291 -122
  87. package/src/modules/ModalAnchorEditor.js +383 -234
  88. package/src/modules/SelectMenu.js +270 -168
  89. package/src/modules/_DragHandle.js +2 -1
  90. package/src/modules/index.js +3 -3
  91. package/src/plugins/browser/audioGallery.js +83 -0
  92. package/src/plugins/browser/fileBrowser.js +103 -0
  93. package/src/plugins/browser/fileGallery.js +83 -0
  94. package/src/plugins/browser/imageGallery.js +81 -0
  95. package/src/plugins/browser/videoGallery.js +103 -0
  96. package/src/plugins/command/blockquote.js +40 -27
  97. package/src/plugins/command/exportPDF.js +134 -0
  98. package/src/plugins/command/fileUpload.js +229 -162
  99. package/src/plugins/command/list_bulleted.js +83 -47
  100. package/src/plugins/command/list_numbered.js +83 -47
  101. package/src/plugins/dropdown/align.js +66 -54
  102. package/src/plugins/dropdown/backgroundColor.js +63 -49
  103. package/src/plugins/dropdown/font.js +71 -47
  104. package/src/plugins/dropdown/fontColor.js +63 -48
  105. package/src/plugins/dropdown/formatBlock.js +70 -33
  106. package/src/plugins/dropdown/hr.js +92 -51
  107. package/src/plugins/dropdown/layout.js +37 -26
  108. package/src/plugins/dropdown/lineHeight.js +54 -38
  109. package/src/plugins/dropdown/list.js +60 -45
  110. package/src/plugins/dropdown/paragraphStyle.js +51 -30
  111. package/src/plugins/dropdown/table.js +2003 -813
  112. package/src/plugins/dropdown/template.js +38 -26
  113. package/src/plugins/dropdown/textStyle.js +43 -31
  114. package/src/plugins/field/mention.js +147 -86
  115. package/src/plugins/index.js +32 -6
  116. package/src/plugins/input/fontSize.js +161 -108
  117. package/src/plugins/input/pageNavigator.js +70 -0
  118. package/src/plugins/modal/audio.js +358 -173
  119. package/src/plugins/modal/drawing.js +531 -0
  120. package/src/plugins/modal/embed.js +886 -0
  121. package/src/plugins/modal/image.js +674 -362
  122. package/src/plugins/modal/link.js +100 -71
  123. package/src/plugins/modal/math.js +367 -167
  124. package/src/plugins/modal/video.js +691 -335
  125. package/src/plugins/popup/anchor.js +222 -0
  126. package/src/suneditor.js +50 -13
  127. package/src/themes/dark.css +122 -0
  128. package/src/typedef.js +130 -0
  129. package/types/assets/icons/defaultIcons.d.ts +153 -0
  130. package/types/core/base/eventHandlers/handler_toolbar.d.ts +41 -0
  131. package/types/core/base/eventHandlers/handler_ww_clipboard.d.ts +40 -0
  132. package/types/core/base/eventHandlers/handler_ww_dragDrop.d.ts +35 -0
  133. package/types/core/base/eventHandlers/handler_ww_key_input.d.ts +45 -0
  134. package/types/core/base/eventHandlers/handler_ww_mouse.d.ts +39 -0
  135. package/types/core/base/eventManager.d.ts +385 -0
  136. package/types/core/base/history.d.ts +81 -0
  137. package/types/core/class/char.d.ts +60 -0
  138. package/types/core/class/component.d.ts +212 -0
  139. package/types/core/class/format.d.ts +616 -0
  140. package/types/core/class/html.d.ts +422 -0
  141. package/types/core/class/menu.d.ts +126 -0
  142. package/types/core/class/nodeTransform.d.ts +93 -0
  143. package/types/core/class/offset.d.ts +522 -0
  144. package/types/core/class/selection.d.ts +188 -0
  145. package/types/core/class/shortcuts.d.ts +142 -0
  146. package/types/core/class/toolbar.d.ts +189 -0
  147. package/types/core/class/ui.d.ts +164 -0
  148. package/types/core/class/viewer.d.ts +140 -0
  149. package/types/core/editor.d.ts +610 -0
  150. package/types/core/section/actives.d.ts +46 -0
  151. package/types/core/section/constructor.d.ts +777 -0
  152. package/types/core/section/context.d.ts +45 -0
  153. package/types/core/section/documentType.d.ts +178 -0
  154. package/types/editorInjector/_classes.d.ts +41 -0
  155. package/types/editorInjector/_core.d.ts +92 -0
  156. package/types/editorInjector/index.d.ts +71 -0
  157. package/types/events.d.ts +273 -0
  158. package/types/helper/clipboard.d.ts +12 -0
  159. package/types/helper/converter.d.ts +197 -0
  160. package/types/helper/dom/domCheck.d.ts +189 -0
  161. package/types/helper/dom/domQuery.d.ts +223 -0
  162. package/types/helper/dom/domUtils.d.ts +226 -0
  163. package/types/helper/dom/index.d.ts +9 -0
  164. package/types/helper/env.d.ts +132 -0
  165. package/types/helper/index.d.ts +174 -0
  166. package/types/helper/keyCodeMap.d.ts +110 -0
  167. package/types/helper/numbers.d.ts +46 -0
  168. package/types/helper/unicode.d.ts +28 -0
  169. package/types/index.d.ts +120 -0
  170. package/{typings/Lang.d.ts → types/langs/_Lang.d.ts} +173 -103
  171. package/types/langs/ckb.d.ts +3 -0
  172. package/types/langs/cs.d.ts +3 -0
  173. package/types/langs/da.d.ts +3 -0
  174. package/types/langs/de.d.ts +3 -0
  175. package/types/langs/en.d.ts +3 -0
  176. package/types/langs/es.d.ts +3 -0
  177. package/types/langs/fa.d.ts +3 -0
  178. package/types/langs/fr.d.ts +3 -0
  179. package/types/langs/he.d.ts +3 -0
  180. package/types/langs/hu.d.ts +3 -0
  181. package/types/langs/index.d.ts +54 -0
  182. package/types/langs/it.d.ts +3 -0
  183. package/types/langs/ja.d.ts +3 -0
  184. package/types/langs/km.d.ts +3 -0
  185. package/types/langs/ko.d.ts +3 -0
  186. package/types/langs/lv.d.ts +3 -0
  187. package/types/langs/nl.d.ts +3 -0
  188. package/types/langs/pl.d.ts +3 -0
  189. package/types/langs/pt_br.d.ts +3 -0
  190. package/types/langs/ro.d.ts +3 -0
  191. package/types/langs/ru.d.ts +3 -0
  192. package/types/langs/se.d.ts +3 -0
  193. package/types/langs/tr.d.ts +3 -0
  194. package/types/langs/uk.d.ts +3 -0
  195. package/types/langs/ur.d.ts +3 -0
  196. package/types/langs/zh_cn.d.ts +3 -0
  197. package/types/modules/ApiManager.d.ts +125 -0
  198. package/types/modules/Browser.d.ts +326 -0
  199. package/types/modules/ColorPicker.d.ts +131 -0
  200. package/types/modules/Controller.d.ts +251 -0
  201. package/types/modules/Figure.d.ts +517 -0
  202. package/types/modules/FileManager.d.ts +202 -0
  203. package/types/modules/HueSlider.d.ts +136 -0
  204. package/types/modules/Modal.d.ts +111 -0
  205. package/types/modules/ModalAnchorEditor.d.ts +236 -0
  206. package/types/modules/SelectMenu.d.ts +194 -0
  207. package/types/modules/_DragHandle.d.ts +7 -0
  208. package/types/modules/index.d.ts +26 -0
  209. package/types/plugins/browser/audioGallery.d.ts +55 -0
  210. package/types/plugins/browser/fileBrowser.d.ts +64 -0
  211. package/types/plugins/browser/fileGallery.d.ts +55 -0
  212. package/types/plugins/browser/imageGallery.d.ts +51 -0
  213. package/types/plugins/browser/videoGallery.d.ts +57 -0
  214. package/types/plugins/command/blockquote.d.ts +28 -0
  215. package/types/plugins/command/exportPDF.d.ts +46 -0
  216. package/types/plugins/command/fileUpload.d.ts +156 -0
  217. package/types/plugins/command/list_bulleted.d.ts +46 -0
  218. package/types/plugins/command/list_numbered.d.ts +46 -0
  219. package/types/plugins/dropdown/align.d.ts +60 -0
  220. package/types/plugins/dropdown/backgroundColor.d.ts +63 -0
  221. package/types/plugins/dropdown/font.d.ts +54 -0
  222. package/types/plugins/dropdown/fontColor.d.ts +63 -0
  223. package/types/plugins/dropdown/formatBlock.d.ts +54 -0
  224. package/types/plugins/dropdown/hr.d.ts +71 -0
  225. package/types/plugins/dropdown/layout.d.ts +40 -0
  226. package/types/plugins/dropdown/lineHeight.d.ts +50 -0
  227. package/types/plugins/dropdown/list.d.ts +39 -0
  228. package/types/plugins/dropdown/paragraphStyle.d.ts +54 -0
  229. package/types/plugins/dropdown/table.d.ts +627 -0
  230. package/types/plugins/dropdown/template.d.ts +40 -0
  231. package/types/plugins/dropdown/textStyle.d.ts +41 -0
  232. package/types/plugins/field/mention.d.ts +102 -0
  233. package/types/plugins/index.d.ts +107 -0
  234. package/types/plugins/input/fontSize.d.ts +170 -0
  235. package/types/plugins/input/pageNavigator.d.ts +28 -0
  236. package/types/plugins/modal/audio.d.ts +269 -0
  237. package/types/plugins/modal/drawing.d.ts +246 -0
  238. package/types/plugins/modal/embed.d.ts +387 -0
  239. package/types/plugins/modal/image.d.ts +451 -0
  240. package/types/plugins/modal/link.d.ts +128 -0
  241. package/types/plugins/modal/math.d.ts +193 -0
  242. package/types/plugins/modal/video.d.ts +485 -0
  243. package/types/plugins/popup/anchor.d.ts +56 -0
  244. package/types/suneditor.d.ts +51 -0
  245. package/types/typedef.d.ts +233 -0
  246. package/.eslintignore +0 -7
  247. package/.eslintrc.json +0 -64
  248. package/src/assets/icons/_default.js +0 -194
  249. package/src/core/base/events.js +0 -320
  250. package/src/core/class/notice.js +0 -42
  251. package/src/helper/domUtils.js +0 -1177
  252. package/src/modules/FileBrowser.js +0 -271
  253. package/src/plugins/command/exportPdf.js +0 -168
  254. package/src/plugins/fileBrowser/imageGallery.js +0 -81
  255. package/src/themes/test.css +0 -61
  256. package/typings/CommandPlugin.d.ts +0 -8
  257. package/typings/DialogPlugin.d.ts +0 -20
  258. package/typings/FileBrowserPlugin.d.ts +0 -30
  259. package/typings/Module.d.ts +0 -15
  260. package/typings/Plugin.d.ts +0 -42
  261. package/typings/SubmenuPlugin.d.ts +0 -8
  262. package/typings/_classes.d.ts +0 -17
  263. package/typings/_colorPicker.d.ts +0 -60
  264. package/typings/_core.d.ts +0 -55
  265. package/typings/align.d.ts +0 -5
  266. package/typings/audio.d.ts +0 -5
  267. package/typings/backgroundColor.d.ts +0 -5
  268. package/typings/blockquote.d.ts +0 -5
  269. package/typings/char.d.ts +0 -39
  270. package/typings/component.d.ts +0 -38
  271. package/typings/context.d.ts +0 -39
  272. package/typings/converter.d.ts +0 -33
  273. package/typings/dialog.d.ts +0 -28
  274. package/typings/domUtils.d.ts +0 -361
  275. package/typings/editor.d.ts +0 -7
  276. package/typings/editor.ts +0 -542
  277. package/typings/env.d.ts +0 -70
  278. package/typings/eventManager.d.ts +0 -37
  279. package/typings/events.d.ts +0 -262
  280. package/typings/fileBrowser.d.ts +0 -42
  281. package/typings/fileManager.d.ts +0 -67
  282. package/typings/font.d.ts +0 -5
  283. package/typings/fontColor.d.ts +0 -5
  284. package/typings/fontSize.d.ts +0 -5
  285. package/typings/format.d.ts +0 -191
  286. package/typings/formatBlock.d.ts +0 -5
  287. package/typings/history.d.ts +0 -48
  288. package/typings/horizontalRule.d.ts +0 -5
  289. package/typings/image.d.ts +0 -5
  290. package/typings/imageGallery.d.ts +0 -5
  291. package/typings/index.d.ts +0 -21
  292. package/typings/index.modules.d.ts +0 -11
  293. package/typings/index.plugins.d.ts +0 -58
  294. package/typings/lineHeight.d.ts +0 -5
  295. package/typings/link.d.ts +0 -5
  296. package/typings/list.d.ts +0 -5
  297. package/typings/math.d.ts +0 -5
  298. package/typings/mediaContainer.d.ts +0 -25
  299. package/typings/mention.d.ts +0 -5
  300. package/typings/node.d.ts +0 -57
  301. package/typings/notice.d.ts +0 -16
  302. package/typings/numbers.d.ts +0 -29
  303. package/typings/offset.d.ts +0 -24
  304. package/typings/options.d.ts +0 -589
  305. package/typings/paragraphStyle.d.ts +0 -5
  306. package/typings/resizing.d.ts +0 -141
  307. package/typings/selection.d.ts +0 -94
  308. package/typings/shortcuts.d.ts +0 -13
  309. package/typings/suneditor.d.ts +0 -9
  310. package/typings/table.d.ts +0 -5
  311. package/typings/template.d.ts +0 -5
  312. package/typings/textStyle.d.ts +0 -5
  313. package/typings/toolbar.d.ts +0 -32
  314. package/typings/unicode.d.ts +0 -25
  315. package/typings/video.d.ts +0 -5
@@ -0,0 +1,667 @@
1
+ import CoreInjector from '../editorInjector/_core';
2
+ import { dom, keyCodeMap } from '../helper';
3
+ import ApiManager from './ApiManager';
4
+
5
+ /**
6
+ * @typedef {Object} BrowserFile
7
+ * @property {string} [src=""] - Source url
8
+ * @property {string} [name=""] - File name | Folder name
9
+ * @property {string=} thumbnail - Thumbnail url
10
+ * @property {string=} alt - Image alt
11
+ * @property {Array<string>|string=} tag - Tag name list
12
+ * @property {string=} type - Type (image, video, audio, etc.)
13
+ * @property {string=} frame - Frame name (iframe, video, etc.)
14
+ * @property {BrowserFile | string=} _data - The folder's contents or an API URL.
15
+ * @property {boolean=} default - Whether this folder is the default selection.
16
+ * @property {Object<string, *>=} meta - Metadata
17
+ */
18
+
19
+ /**
20
+ * @typedef BrowserParams
21
+ * @property {string} title - File browser window title. Required. Can be overridden in browser.
22
+ * @property {string=} className - Class name of the file browser. Optional. Default: ''.
23
+ * @property {Object<string, *>|Array<*>=} data - direct data without server calls
24
+ * @property {string=} url - File server url. Required. Can be overridden in browser.
25
+ * @property {Object<string, string>=} headers - File server http header. Required. Can be overridden in browser.
26
+ * @property {(target: Node) => void} selectorHandler - Function that actions when an item is clicked. Required. Can be overridden in browser.
27
+ * @property {boolean=} useSearch - Whether to use the search function. Optional. Default: true.
28
+ * @property {string=} searchUrl - File server search url. Optional. Can be overridden in browser.
29
+ * @property {Object<string, string>=} searchUrlHeader - File server search http header. Optional. Can be overridden in browser.
30
+ * @property {string=} listClass - Class name of list div. Required. Can be overridden in browser.
31
+ * @property {(item: BrowserFile) => string=} drawItemHandler - Function that defines the HTML of a file item. Required. Can be overridden in browser.
32
+ * @property {Array<*>=} props - "props" argument to "drawItemHandler" function. Optional. Can be overridden in browser.
33
+ * @property {number=} columnSize - Number of "div.se-file-item-column" to be created. Optional. Can be overridden in browser. Default: 4.
34
+ * @property {((item: BrowserFile) => string)=} thumbnail - Default thumbnail
35
+ */
36
+
37
+ /**
38
+ * @class
39
+ * @description File browser plugin
40
+ */
41
+ class Browser extends CoreInjector {
42
+ /**
43
+ * @constructor
44
+ * @param {*} inst The instance object that called the constructor.
45
+ * @param {BrowserParams} params Browser options
46
+ */
47
+ constructor(inst, params) {
48
+ super(inst.editor);
49
+
50
+ // create HTML
51
+ this.useSearch = params.useSearch ?? true;
52
+ const browserFrame = dom.utils.createElement('DIV', { class: 'se-browser sun-editor-common' + (params.className ? ` ${params.className}` : '') });
53
+ const contentHTML = CreateHTMLInfos(inst.editor, this.useSearch);
54
+ const content = contentHTML.html;
55
+
56
+ // members
57
+ this.kind = inst.constructor.key || inst.constructor.name;
58
+ this.inst = inst;
59
+ this.area = browserFrame;
60
+ this.header = contentHTML.header;
61
+ this.titleArea = contentHTML.titleArea;
62
+ this.tagArea = contentHTML.tagArea;
63
+ this.body = contentHTML.body;
64
+ this.list = contentHTML.list;
65
+ this.side = contentHTML.side;
66
+ this.wrapper = contentHTML.wrapper;
67
+ this._loading = contentHTML._loading;
68
+
69
+ this.title = params.title;
70
+ this.listClass = params.listClass || 'se-preview-list';
71
+ this.directData = params.data;
72
+ this.url = params.url;
73
+ this.urlHeader = params.headers;
74
+ this.searchUrl = params.searchUrl;
75
+ this.searchUrlHeader = params.searchUrlHeader;
76
+ this.drawItemHandler = (params.drawItemHandler || DrawItems).bind({ thumbnail: params.thumbnail, props: params.props || [] });
77
+ this.selectorHandler = params.selectorHandler;
78
+ this.columnSize = params.columnSize || 4;
79
+ this.folderDefaultPath = '';
80
+ this.closeArrow = this.icons.menu_arrow_right;
81
+ this.openArrow = this.icons.menu_arrow_down;
82
+ this.icon_folder = this.icons.side_menu_folder_item;
83
+ this.icon_folder_item = this.icons.side_menu_folder;
84
+ this.icon_item = this.icons.side_menu_item;
85
+
86
+ /**
87
+ * @type {Array<BrowserFile>}
88
+ */
89
+ this.items = [];
90
+ /**
91
+ * @type {Object<string, {name: string, meta: Object<string, *>}>}
92
+ */
93
+ this.folders = {};
94
+ /**
95
+ * @type {Object<string, {key?: string, name?: string, children?: *}>}
96
+ */
97
+ this.tree = {};
98
+ /**
99
+ * @type {BrowserFile}
100
+ */
101
+ this.data = {};
102
+ this.selectedTags = [];
103
+ this.keyword = '';
104
+ this.sideInner = null;
105
+ this._closeSignal = false;
106
+ this._bindClose = null;
107
+ this.__globalEventHandler = (e) => {
108
+ if (!keyCodeMap.isEsc(e.code)) return;
109
+ this.close();
110
+ };
111
+ // api manager
112
+ this.apiManager = new ApiManager(this, { method: 'GET' });
113
+
114
+ // init
115
+ browserFrame.appendChild(dom.utils.createElement('DIV', { class: 'se-browser-back' }));
116
+ browserFrame.appendChild(content);
117
+ this.carrierWrapper.appendChild(browserFrame);
118
+
119
+ this.eventManager.addEvent(this.tagArea, 'click', this.#OnClickTag.bind(this));
120
+ this.eventManager.addEvent(this.list, 'click', this.#OnClickFile.bind(this));
121
+ this.eventManager.addEvent(this.side, 'click', this.#OnClickSide.bind(this));
122
+ this.eventManager.addEvent(content, 'mousedown', this.#OnMouseDown_browser.bind(this));
123
+ this.eventManager.addEvent(content, 'click', this.#OnClick_browser.bind(this));
124
+ this.eventManager.addEvent(browserFrame.querySelector('form.se-browser-search-form'), 'submit', this.#Search.bind(this));
125
+ this.eventManager.addEvent((this.sideOpenBtn = /** @type {HTMLButtonElement} */ (browserFrame.querySelector('.se-side-open-btn'))), 'click', this.#SideOpen.bind(this));
126
+ this.eventManager.addEvent([this.header, browserFrame.querySelector('.se-browser-main')], 'mousedown', this.#SideClose.bind(this));
127
+ }
128
+
129
+ /**
130
+ * @description Open a file browser plugin
131
+ * @param {Object} [params={}]
132
+ * @param {string=} params.listClass - Class name of list div. If not, use "this.listClass".
133
+ * @param {string=} params.title - File browser window title. If not, use "this.title".
134
+ * @param {string=} params.url - File server url. If not, use "this.url".
135
+ * @param {Object<string, string>=} params.urlHeader - File server http header. If not, use "this.urlHeader".
136
+ */
137
+ open(params) {
138
+ if (!params) params = {};
139
+ this.__addGlobalEvent();
140
+
141
+ const listClassName = params.listClass || this.listClass;
142
+ if (!dom.utils.hasClass(this.list, listClassName)) {
143
+ this.list.className = 'se-browser-list ' + listClassName;
144
+ }
145
+
146
+ this.titleArea.textContent = params.title || this.title;
147
+ this.area.style.display = 'block';
148
+ this.editor.opendBrowser = this;
149
+ this.closeArrow = this.options.get('_rtl') ? this.icons.menu_arrow_left : this.icons.menu_arrow_right;
150
+
151
+ if (this.directData) {
152
+ this.__drowItems(this.directData);
153
+ } else {
154
+ this._drawFileList(params.url || this.url, params.urlHeader || this.urlHeader, false);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * @description Close a browser plugin
160
+ * - The plugin's "init" method is called.
161
+ */
162
+ close() {
163
+ this.__removeGlobalEvent();
164
+ this.apiManager.cancel();
165
+
166
+ this.area.style.display = 'none';
167
+ this.selectedTags = [];
168
+ this.items = [];
169
+ this.folders = {};
170
+ this.tree = {};
171
+ this.data = {};
172
+ this.keyword = '';
173
+ this.list.innerHTML = this.tagArea.innerHTML = this.titleArea.textContent = '';
174
+ this.editor.opendBrowser = null;
175
+ this.sideInner = null;
176
+
177
+ if (typeof this.inst.init === 'function') this.inst.init();
178
+ }
179
+
180
+ /**
181
+ * @description Search files
182
+ * @param {string} keyword - Search keyword
183
+ */
184
+ search(keyword) {
185
+ if (this.searchUrl) {
186
+ this.keyword = keyword;
187
+ this._drawFileList(this.searchUrl + '?keyword=' + keyword, this.searchUrlHeader, false);
188
+ } else {
189
+ this.keyword = keyword.toLowerCase();
190
+ this._drawListItem(this.items, false);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * @description Filter items by tag
196
+ * @param {Array<BrowserFile>} items - Items to filter
197
+ * @returns {Array<BrowserFile>}
198
+ */
199
+ tagfilter(items) {
200
+ const selectedTags = this.selectedTags;
201
+ return selectedTags.length === 0 ? items : items.filter((item) => !Array.isArray(item.tag) || item.tag.some((tag) => selectedTags.includes(tag)));
202
+ }
203
+
204
+ /**
205
+ * @description Show file browser loading box
206
+ */
207
+ showBrowserLoading() {
208
+ this._loading.style.display = 'block';
209
+ }
210
+
211
+ /**
212
+ * @description Close file browser loading box
213
+ */
214
+ closeBrowserLoading() {
215
+ this._loading.style.display = 'none';
216
+ }
217
+
218
+ /**
219
+ * @private
220
+ * @description Fetches the file list from the server.
221
+ * @param {string} url - The file server URL.
222
+ * @param {Object<string, string>} urlHeader - The HTTP headers for the request.
223
+ * @param {boolean} pageLoading - Indicates if this is a paginated request.
224
+ */
225
+ _drawFileList(url, urlHeader, pageLoading) {
226
+ this.apiManager.call({ method: 'GET', url, headers: urlHeader, callBack: this.#CallBackGet.bind(this), errorCallBack: this.#CallBackError.bind(this) });
227
+ if (!pageLoading) {
228
+ this.sideOpenBtn.style.display = 'none';
229
+ this.showBrowserLoading();
230
+ }
231
+ }
232
+
233
+ /**
234
+ * @private
235
+ * @description Updates the displayed list of file items.
236
+ * @param {Array<BrowserFile>} items - The file items to display.
237
+ * @param {boolean} update - Whether to update the tags.
238
+ */
239
+ _drawListItem(items, update) {
240
+ const keyword = this.keyword;
241
+ items = this.tagfilter(items).filter((item) => item.name.toLowerCase().indexOf(keyword) > -1);
242
+
243
+ const _tags = [];
244
+ const len = items.length;
245
+ const columnSize = this.columnSize;
246
+ const splitSize = columnSize <= 1 ? 1 : Math.round(len / columnSize) || 1;
247
+ const drawItemHandler = this.drawItemHandler;
248
+
249
+ let tagsHTML = '';
250
+ let listHTML = '<div class="se-file-item-column">';
251
+ let columns = 1;
252
+ for (let i = 0, item, tags; i < len; i++) {
253
+ item = items[i];
254
+ tags = !item.tag ? [] : typeof item.tag === 'string' ? item.tag.split(',') : item.tag;
255
+ tags = item.tag = tags.map((v) => v.trim());
256
+ listHTML += drawItemHandler(item);
257
+
258
+ if ((i + 1) % splitSize === 0 && columns < columnSize && i + 1 < len) {
259
+ columns++;
260
+ listHTML += '</div><div class="se-file-item-column">';
261
+ }
262
+
263
+ if (update && tags.length > 0) {
264
+ for (let t = 0, tLen = tags.length, tag; t < tLen; t++) {
265
+ tag = tags[t];
266
+ if (tag && !_tags.includes(tag)) {
267
+ _tags.push(tag);
268
+ tagsHTML += `<a title="${tag}" aria-label="${tag}">${tag}</a>`;
269
+ }
270
+ }
271
+ }
272
+ }
273
+ listHTML += '</div>';
274
+
275
+ this.list.innerHTML = listHTML;
276
+
277
+ if (update) {
278
+ this.items = items;
279
+ this.tagArea.innerHTML = tagsHTML;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * @private
285
+ * @description Adds a global event listener for closing the browser.
286
+ */
287
+ __addGlobalEvent() {
288
+ this.__removeGlobalEvent();
289
+ this._bindClose = this.eventManager.addGlobalEvent('keydown', this.__globalEventHandler, true);
290
+ }
291
+
292
+ /**
293
+ * @private
294
+ * @description Removes the global event listener for closing the browser.
295
+ */
296
+ __removeGlobalEvent() {
297
+ if (this._bindClose) this._bindClose = this.eventManager.removeGlobalEvent(this._bindClose);
298
+ }
299
+
300
+ /**
301
+ * @private
302
+ * @description Renders the file items or folder structure from data.
303
+ * @param {BrowserFile[]|BrowserFile} data - The data representing the file structure.
304
+ * @returns {boolean} True if rendering was successful, false otherwise.
305
+ */
306
+ __drowItems(data) {
307
+ if (Array.isArray(data)) {
308
+ if (data.length > 0) {
309
+ this._drawListItem(data, true);
310
+ }
311
+ return true;
312
+ } else if (typeof data === 'object') {
313
+ this.sideOpenBtn.style.display = '';
314
+ this.__parseFolderData(data);
315
+
316
+ this.side.innerHTML = '';
317
+ const sideInner = (this.sideInner = dom.utils.createElement('div', null));
318
+ this.__createFolderList(this.tree, sideInner);
319
+ this.side.appendChild(sideInner);
320
+
321
+ if (this.folderDefaultPath) {
322
+ const openFolder = /** @type {HTMLButtonElement} */ (sideInner.querySelector(`[data-command="${this.folderDefaultPath}"]`));
323
+ openFolder.click();
324
+ if (this.folderDefaultPath.includes('/')) {
325
+ dom.utils.removeClass(openFolder.parentElement, 'se-menu-hidden');
326
+ openFolder.parentElement.previousElementSibling.querySelector('button').innerHTML = this.openArrow;
327
+ }
328
+ }
329
+
330
+ return true;
331
+ }
332
+ return false;
333
+ }
334
+
335
+ /**
336
+ * @private
337
+ * @description Parses folder data into a structured format.
338
+ * @param {BrowserFile} data - The folder data.
339
+ * @param {string} [path] - The current path in the folder hierarchy.
340
+ */
341
+ __parseFolderData(data, path) {
342
+ let current = this.tree;
343
+
344
+ // _data
345
+ if (data._data) {
346
+ this.data[path] = data._data;
347
+ if (!this.folderDefaultPath || data.default) {
348
+ this.folderDefaultPath = path;
349
+ }
350
+
351
+ const parts = path.split('/');
352
+ const len = parts.length - 1;
353
+ parts.forEach((part, index) => {
354
+ if (!current[part]) {
355
+ current[part] = { children: {} };
356
+ }
357
+
358
+ if (index === len) {
359
+ current[part].key = path;
360
+ current[part].name = this.folders[path].name;
361
+ } else {
362
+ current = current[part].children;
363
+ }
364
+ });
365
+ } else if (path) {
366
+ current[path] = { name: this.folders[path].name, children: {} };
367
+ }
368
+
369
+ // create folders, file path
370
+ Object.entries(data).forEach(([key, value]) => {
371
+ if (key === '_data' || !value || typeof value !== 'object') return;
372
+
373
+ const v = /** @type {BrowserFile} */ (value);
374
+ const currentPath = path ? `${path}/${key}` : key;
375
+
376
+ this.folders[currentPath] = {
377
+ name: v.name || key,
378
+ meta: v.meta || {}
379
+ };
380
+
381
+ this.__parseFolderData(v, currentPath);
382
+ });
383
+ }
384
+
385
+ /**
386
+ * @private
387
+ * @description Creates a nested folder list from parsed data.
388
+ * @param {BrowserFile[]|BrowserFile} folderData - The structured folder data.
389
+ * @param {HTMLElement} parentElement - The parent element to append folder structure to.
390
+ */
391
+ __createFolderList(folderData, parentElement) {
392
+ for (const key in folderData) {
393
+ const item = folderData[key];
394
+ if (!item) continue;
395
+
396
+ if (Object.keys(item.children).length > 0) {
397
+ const folderLabel = dom.utils.createElement(
398
+ 'div',
399
+ item.key ? { 'data-command': item.key, 'aria-label': item.name } : null,
400
+ `<span class="se-menu-icon">${item.key ? this.icon_folder : this.icon_folder_item}</span><span>${item.name}</span>`
401
+ );
402
+ const folderDiv = dom.utils.createElement('div', { class: 'se-menu-folder' }, folderLabel);
403
+
404
+ folderLabel.insertBefore(dom.utils.createElement('button', null, this.closeArrow), folderLabel.firstElementChild);
405
+ const childContainer = document.createElement('div');
406
+ dom.utils.addClass(childContainer, 'se-menu-child|se-menu-hidden');
407
+ this.__createFolderList(item.children, childContainer);
408
+ folderDiv.appendChild(childContainer);
409
+
410
+ parentElement.appendChild(folderDiv);
411
+ } else {
412
+ const folderLabel = dom.utils.createElement('div', { 'data-command': item.key, 'aria-label': item.name, class: 'se-menu-folder-item' }, `<span class="se-menu-icon">${this.icon_item}</span><span>${item.name}</span>`);
413
+ if (parentElement === this.sideInner) {
414
+ const folderDiv = dom.utils.createElement('div', { class: 'se-menu-folder' }, folderLabel);
415
+ parentElement.appendChild(folderDiv);
416
+ } else {
417
+ parentElement.appendChild(folderLabel);
418
+ }
419
+ }
420
+ }
421
+ }
422
+
423
+ /**
424
+ * @param {XMLHttpRequest} xmlHttp - XMLHttpRequest object.
425
+ */
426
+ #CallBackGet(xmlHttp) {
427
+ try {
428
+ const res = JSON.parse(xmlHttp.responseText);
429
+ const data = res.result;
430
+ if (this.__drowItems(data)) return;
431
+
432
+ if (res.nullMessage) {
433
+ this.list.innerHTML = res.nullMessage;
434
+ }
435
+ } catch (e) {
436
+ throw Error(`[SUNEDITOR.browser.drawList.fail] cause: "${e.message}"`);
437
+ } finally {
438
+ this.closeBrowserLoading();
439
+ this.body.style.maxHeight = dom.utils.getClientSize().h - this.header.offsetHeight - 50 + 'px';
440
+ }
441
+ }
442
+
443
+ /**
444
+ * @param {*} res - response data.
445
+ * @param {XMLHttpRequest} xmlHttp - XMLHttpRequest object.
446
+ */
447
+ #CallBackError(res, xmlHttp) {
448
+ this.closeBrowserLoading();
449
+ throw Error(`[SUNEDITOR.browser.get.serverException] status: ${xmlHttp.status}, response: ${res.errorMessage || xmlHttp.responseText}`);
450
+ }
451
+
452
+ /**
453
+ * @param {MouseEvent} e - Event object
454
+ */
455
+ #OnClickTag(e) {
456
+ const eventTarget = dom.query.getEventTarget(e);
457
+ if (!dom.check.isAnchor(eventTarget)) return;
458
+
459
+ const tagName = eventTarget.textContent;
460
+ const selectTag = this.tagArea.querySelector('a[title="' + tagName + '"]');
461
+ const sTagIndex = this.selectedTags.indexOf(tagName);
462
+
463
+ if (sTagIndex > -1) {
464
+ this.selectedTags.splice(sTagIndex, 1);
465
+ dom.utils.removeClass(selectTag, 'on');
466
+ } else {
467
+ this.selectedTags.push(tagName);
468
+ dom.utils.addClass(selectTag, 'on');
469
+ }
470
+
471
+ this._drawListItem(this.items, false);
472
+ }
473
+
474
+ /**
475
+ * @param {MouseEvent} e - Event object
476
+ */
477
+ #OnClickFile(e) {
478
+ const eventTarget = dom.query.getEventTarget(e);
479
+
480
+ e.preventDefault();
481
+ e.stopPropagation();
482
+
483
+ if (eventTarget === this.list) return;
484
+
485
+ const target = dom.query.getCommandTarget(eventTarget);
486
+ if (!target) return;
487
+
488
+ this.close();
489
+ this.selectorHandler(target);
490
+ }
491
+
492
+ /**
493
+ * @param {MouseEvent} e - Event object
494
+ */
495
+ #OnClickSide(e) {
496
+ const eventTarget = dom.query.getEventTarget(e);
497
+ e.stopPropagation();
498
+
499
+ if (/^button$/i.test(eventTarget.nodeName)) {
500
+ const childContainer = eventTarget.parentElement.parentElement.querySelector('.se-menu-child');
501
+ if (dom.utils.hasClass(childContainer, 'se-menu-hidden')) {
502
+ dom.utils.removeClass(childContainer, 'se-menu-hidden');
503
+ eventTarget.innerHTML = this.openArrow;
504
+ } else {
505
+ dom.utils.addClass(childContainer, 'se-menu-hidden');
506
+ eventTarget.innerHTML = this.closeArrow;
507
+ }
508
+ return;
509
+ }
510
+
511
+ const cmdTarget = dom.query.getCommandTarget(eventTarget);
512
+ if (!cmdTarget || dom.utils.hasClass(cmdTarget, 'active')) return;
513
+
514
+ const data = this.data[cmdTarget.getAttribute('data-command')];
515
+
516
+ dom.utils.removeClass(this.side.querySelectorAll('.active'), 'active');
517
+ dom.utils.addClass([cmdTarget, dom.query.getParentElement(cmdTarget, '.se-menu-folder')], 'active');
518
+ this.tagArea.innerHTML = '';
519
+
520
+ if (typeof data === 'string') {
521
+ this._drawFileList(data, this.urlHeader, true);
522
+ } else {
523
+ this._drawListItem(data, false);
524
+ }
525
+ }
526
+
527
+ /**
528
+ * @param {MouseEvent} e - Event object
529
+ */
530
+ #OnMouseDown_browser(e) {
531
+ const eventTarget = dom.query.getEventTarget(e);
532
+ if (/se-browser-inner/.test(eventTarget.className)) {
533
+ this._closeSignal = true;
534
+ } else {
535
+ this._closeSignal = false;
536
+ }
537
+ }
538
+
539
+ /**
540
+ * @param {MouseEvent} e - Event object
541
+ */
542
+ #OnClick_browser(e) {
543
+ const eventTarget = dom.query.getEventTarget(e);
544
+ e.stopPropagation();
545
+
546
+ if (/close/.test(eventTarget.getAttribute('data-command')) || this._closeSignal) {
547
+ this.close();
548
+ }
549
+ }
550
+
551
+ /**
552
+ * @param {SubmitEvent} e - Event object
553
+ */
554
+ #Search(e) {
555
+ const eventTarget = /** @type {HTMLElement} */ (e.currentTarget);
556
+ e.preventDefault();
557
+ this.search(/** @type {HTMLInputElement} */ (eventTarget.querySelector('input[type="text"]')).value);
558
+ }
559
+
560
+ /**
561
+ * @param {MouseEvent} e - Event object
562
+ */
563
+ #SideOpen(e) {
564
+ const eventTarget = dom.query.getEventTarget(e);
565
+ if (dom.utils.hasClass(eventTarget, 'active')) {
566
+ dom.utils.removeClass(this.side, 'se-side-show');
567
+ dom.utils.removeClass(eventTarget, 'active');
568
+ } else {
569
+ dom.utils.addClass(this.side, 'se-side-show');
570
+ dom.utils.addClass(eventTarget, 'active');
571
+ }
572
+ }
573
+
574
+ /**
575
+ * @param {MouseEvent} e - Event object
576
+ */
577
+ #SideClose({ target }) {
578
+ if (target === this.sideOpenBtn) return;
579
+ if (dom.utils.hasClass(this.sideOpenBtn, 'active')) {
580
+ dom.utils.removeClass(this.side, 'se-side-show');
581
+ dom.utils.removeClass(this.sideOpenBtn, 'active');
582
+ }
583
+ }
584
+ }
585
+
586
+ /**
587
+ * @private
588
+ * @param {__se__EditorCore} editor - editor instance
589
+ * @param {boolean} useSearch - Whether to use the search function
590
+ * @returns {{ html: HTMLElement, header: HTMLElement, titleArea: HTMLElement, tagArea: HTMLElement, body: HTMLElement, list: HTMLElement, side: HTMLElement, wrapper: HTMLElement, _loading: HTMLElement }} HTML
591
+ */
592
+ function CreateHTMLInfos(editor, useSearch) {
593
+ const lang = editor.lang;
594
+ const icons = editor.icons;
595
+ const htmlString = /*html*/ `
596
+ <div class="se-browser-content">
597
+ <div class="se-browser-header">
598
+ <button type="button" data-command="close" class="se-btn se-browser-close" class="close" title="${lang.close}" aria-label="${lang.close}">
599
+ ${icons.cancel}
600
+ </button>
601
+ <span class="se-browser-title"></span>
602
+ </div>
603
+ <div class="se-browser-wrapper">
604
+ <div class="se-browser-side"></div>
605
+ <div class="se-browser-main">
606
+ <div class="se-browser-bar">
607
+ <div class="se-browser-search">
608
+ <button class="se-btn se-side-open-btn">${icons.side_menu_hamburger}</button>
609
+ ${
610
+ useSearch
611
+ ? /*html*/ `
612
+ <form class="se-browser-search-form">
613
+ <input type="text" class="se-input-form" placeholder="${lang.search}" aria-label="${lang.search}">
614
+ <button type="submit" class="se-btn" title="${lang.search}" aria-label="${lang.search}">${icons.search}</button>
615
+ </form>`
616
+ : ''
617
+ }
618
+ </div>
619
+ </div>
620
+ <div class="se-browser-body">
621
+ <div class="se-browser-tags"></div>
622
+ <div class="se-loading-box sun-editor-common"><div class="se-loading-effect"></div></div>
623
+ <div class="se-browser-menus"></div>
624
+ <div class="se-browser-list"></div>
625
+ </div>
626
+ </div>
627
+ </div>
628
+ </div>`;
629
+
630
+ const content = dom.utils.createElement('DIV', { class: 'se-browser-inner' }, htmlString);
631
+
632
+ return {
633
+ html: content,
634
+ header: content.querySelector('.se-browser-header'),
635
+ titleArea: content.querySelector('.se-browser-title'),
636
+ tagArea: content.querySelector('.se-browser-tags'),
637
+ body: content.querySelector('.se-browser-body'),
638
+ list: content.querySelector('.se-browser-list'),
639
+ side: content.querySelector('.se-browser-side'),
640
+ wrapper: content.querySelector('.se-browser-wrapper'),
641
+ _loading: content.querySelector('.se-loading-box')
642
+ };
643
+ }
644
+
645
+ /**
646
+ * @private
647
+ * @this {{ thumbnail: ((...args: *) => *), props: Array<*> }}
648
+ * @description Define the HTML of the item to be put in "div.se-file-item-column".
649
+ * - Format: [ { src: "image src", name: "name(@option)", alt: "image alt(@option)", tag: "tag name(@option)" } ]
650
+ * @param {BrowserFile} item Item of the response data's array
651
+ */
652
+ function DrawItems(item) {
653
+ const srcName = item.src.split('/').pop();
654
+ const thumbnail = item.thumbnail || '';
655
+ const src = thumbnail || item.src;
656
+ const customProps = this.props?.map((v) => `data-${v}="${item[v]}"`).join(' ') || '';
657
+ const attrs = `data-type="${item.type}" data-command="${item.src}" data-name="${item.name || srcName}" data-thumbnail="${thumbnail}" data-extension="${item.src.split('.').pop()}" ${customProps}`;
658
+ const props = `class="${thumbnail || 'se-browser-empty-image'}" src="${src}" alt="${item.alt || srcName}" ${attrs}`;
659
+ return /*html*/ `
660
+ <div class="se-file-item-img">
661
+ ${this.thumbnail && !thumbnail && item.type !== 'image' ? `<div class="se-browser-empty-thumbnail" ${props}>${this.thumbnail(item)}</div>` : `<img class="${thumbnail || 'se-browser-empty-image'}" ${props}>`}
662
+ <div class="se-file-name-image se-file-name-back"></div>
663
+ <div class="se-file-name-image">${item.name || srcName}</div>
664
+ </div>`;
665
+ }
666
+
667
+ export default Browser;