suneditor 3.0.5 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/suneditor.min.css +1 -1
  2. package/dist/suneditor.min.js +1 -1
  3. package/package.json +1 -1
  4. package/src/assets/suneditor.css +53 -5
  5. package/src/core/editor.js +20 -3
  6. package/src/core/event/eventOrchestrator.js +2 -1
  7. package/src/core/event/handlers/handler_ww_key.js +2 -2
  8. package/src/core/logic/dom/html.js +23 -1
  9. package/src/core/logic/dom/offset.js +24 -1
  10. package/src/core/logic/panel/viewer.js +6 -4
  11. package/src/core/logic/shell/_commandExecutor.js +38 -1
  12. package/src/core/logic/shell/shortcuts.js +1 -1
  13. package/src/core/schema/options.js +1 -1
  14. package/src/core/section/constructor.js +3 -3
  15. package/src/helper/index.js +3 -0
  16. package/src/helper/msOffice.js +849 -0
  17. package/src/interfaces/plugins.js +1 -1
  18. package/src/langs/ckb.js +1 -0
  19. package/src/langs/cs.js +1 -0
  20. package/src/langs/da.js +1 -0
  21. package/src/langs/de.js +1 -0
  22. package/src/langs/en.js +1 -1
  23. package/src/langs/es.js +1 -0
  24. package/src/langs/fa.js +1 -0
  25. package/src/langs/fr.js +1 -0
  26. package/src/langs/he.js +1 -0
  27. package/src/langs/hu.js +1 -0
  28. package/src/langs/it.js +1 -0
  29. package/src/langs/ja.js +1 -0
  30. package/src/langs/km.js +1 -0
  31. package/src/langs/ko.js +1 -0
  32. package/src/langs/lv.js +1 -0
  33. package/src/langs/nl.js +1 -0
  34. package/src/langs/pl.js +1 -0
  35. package/src/langs/pt_br.js +1 -0
  36. package/src/langs/ro.js +1 -0
  37. package/src/langs/ru.js +1 -0
  38. package/src/langs/se.js +1 -0
  39. package/src/langs/tr.js +1 -0
  40. package/src/langs/uk.js +1 -0
  41. package/src/langs/ur.js +1 -0
  42. package/src/langs/zh_cn.js +1 -0
  43. package/src/modules/contract/Browser.js +99 -10
  44. package/src/modules/ui/ModalAnchorEditor.js +16 -1
  45. package/src/plugins/browser/audioGallery.js +13 -0
  46. package/src/plugins/browser/fileBrowser.js +22 -0
  47. package/src/plugins/browser/fileGallery.js +14 -0
  48. package/src/plugins/browser/imageGallery.js +14 -0
  49. package/src/plugins/browser/videoGallery.js +14 -0
  50. package/src/plugins/command/fileUpload.js +12 -0
  51. package/src/plugins/dropdown/layout.js +1 -1
  52. package/src/plugins/dropdown/template.js +2 -1
  53. package/src/plugins/field/autocomplete.js +346 -0
  54. package/src/plugins/index.js +3 -3
  55. package/src/plugins/modal/audio.js +12 -0
  56. package/src/plugins/modal/embed.js +12 -0
  57. package/src/plugins/modal/image/index.js +12 -0
  58. package/src/plugins/modal/link.js +12 -0
  59. package/src/plugins/modal/video/index.js +12 -0
  60. package/src/typedef.js +1 -1
  61. package/types/core/logic/shell/shortcuts.d.ts +2 -2
  62. package/types/core/schema/options.d.ts +2 -2
  63. package/types/helper/index.d.ts +4 -0
  64. package/types/helper/msOffice.d.ts +11 -0
  65. package/types/interfaces/plugins.d.ts +1 -1
  66. package/types/langs/_Lang.d.ts +1 -1
  67. package/types/modules/contract/Browser.d.ts +32 -0
  68. package/types/modules/ui/ModalAnchorEditor.d.ts +32 -2
  69. package/types/plugins/browser/audioGallery.d.ts +26 -0
  70. package/types/plugins/browser/fileBrowser.d.ts +45 -0
  71. package/types/plugins/browser/fileGallery.d.ts +28 -0
  72. package/types/plugins/browser/imageGallery.d.ts +28 -0
  73. package/types/plugins/browser/videoGallery.d.ts +28 -0
  74. package/types/plugins/command/fileUpload.d.ts +24 -0
  75. package/types/plugins/field/autocomplete.d.ts +177 -0
  76. package/types/plugins/index.d.ts +3 -3
  77. package/types/plugins/modal/audio.d.ts +24 -0
  78. package/types/plugins/modal/embed.d.ts +24 -0
  79. package/types/plugins/modal/image/index.d.ts +24 -0
  80. package/types/plugins/modal/link.d.ts +28 -1
  81. package/types/plugins/modal/video/index.d.ts +24 -0
  82. package/types/typedef.d.ts +1 -1
  83. package/src/plugins/field/mention.js +0 -251
  84. package/types/plugins/field/mention.d.ts +0 -104
@@ -171,7 +171,7 @@ export class PluginDropdownFree extends Base {
171
171
  * These plugins typically respond to input events in the wysiwyg area
172
172
  *
173
173
  * **Commonly used hooks:**
174
- * - `onInput()` - Responds to input events in the editor (See: `mention` plugin)
174
+ * - `onInput()` - Responds to input events in the editor (See: `autocomplete` plugin)
175
175
  * - Other event hooks can be used as needed (`onKeydown`, `onClick`, etc.)
176
176
  *
177
177
  * Child classes MAY optionally implement event hook methods
package/src/langs/ckb.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'بیركاری',
135
135
  maxSize: 'گه‌وره‌ترین قه‌باره‌',
136
136
  mediaGallery: 'گەلەری میدیا',
137
+ autocomplete: 'تەواوکردنی ئۆتۆماتیکی',
137
138
  mention: 'تنويه ب',
138
139
  menu_bordered: 'لێواری هه‌بێت',
139
140
  menu_code: 'كۆد',
package/src/langs/cs.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matematika',
135
135
  maxSize: 'Max. velikost',
136
136
  mediaGallery: 'Galerie médií',
137
+ autocomplete: 'Automatické doplňování',
137
138
  mention: 'Zmínka',
138
139
  menu_bordered: 'Ohraničené',
139
140
  menu_code: 'Kód',
package/src/langs/da.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Math',
135
135
  maxSize: 'Max størrelse',
136
136
  mediaGallery: 'Mediegalleri',
137
+ autocomplete: 'Autofuldførelse',
137
138
  mention: 'Nævne',
138
139
  menu_bordered: 'Afgrænsningslinje',
139
140
  menu_code: 'Code',
package/src/langs/de.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Mathematik',
135
135
  maxSize: 'Maximale Größe',
136
136
  mediaGallery: 'Mediengalerie',
137
+ autocomplete: 'Autovervollständigung',
137
138
  mention: 'Erwähnen',
138
139
  menu_bordered: 'Umrandet',
139
140
  menu_code: 'Quellcode',
package/src/langs/en.js CHANGED
@@ -133,7 +133,7 @@
133
133
  math_modal_title: 'Math',
134
134
  maxSize: 'Max size',
135
135
  mediaGallery: 'Media gallery',
136
- mention: 'Mention',
136
+ autocomplete: 'Autocomplete',
137
137
  menu_bordered: 'Bordered',
138
138
  menu_code: 'Code',
139
139
  menu_neon: 'Neon',
package/src/langs/es.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matemáticas',
135
135
  maxSize: 'Tamaño máximo',
136
136
  mediaGallery: 'Galería de medios',
137
+ autocomplete: 'Autocompletar',
137
138
  mention: 'Mencionar',
138
139
  menu_bordered: 'Bordeado',
139
140
  menu_code: 'Código',
package/src/langs/fa.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'فرمول ریاضی',
135
135
  maxSize: 'حداکثر اندازه',
136
136
  mediaGallery: 'گالری رسانه',
137
+ autocomplete: 'تکمیل خودکار',
137
138
  mention: 'ذکر کردن',
138
139
  menu_bordered: 'لبه‌دار',
139
140
  menu_code: 'کُد',
package/src/langs/fr.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Math',
135
135
  maxSize: 'Taille max',
136
136
  mediaGallery: 'Galerie multimédia',
137
+ autocomplete: 'Saisie semi-automatique',
137
138
  mention: 'Mention',
138
139
  menu_bordered: 'Ligne de démarcation',
139
140
  menu_code: 'Code',
package/src/langs/he.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'נוסחה',
135
135
  maxSize: 'גודל מרבי',
136
136
  mediaGallery: 'גלריית מדיה',
137
+ autocomplete: 'השלמה אוטומטית',
137
138
  mention: 'הזכר',
138
139
  menu_bordered: 'בעל מיתאר',
139
140
  menu_code: 'קוד',
package/src/langs/hu.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matematika',
135
135
  maxSize: 'Maximális méret',
136
136
  mediaGallery: 'Média galéria',
137
+ autocomplete: 'Automatikus kiegészítés',
137
138
  mention: 'Említés',
138
139
  menu_bordered: 'Keretezett',
139
140
  menu_code: 'Kód',
package/src/langs/it.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matematica',
135
135
  maxSize: 'Dimensione massima',
136
136
  mediaGallery: 'Galleria multimediale',
137
+ autocomplete: 'Completamento automatico',
137
138
  mention: 'Menzione',
138
139
  menu_bordered: 'Bordato',
139
140
  menu_code: 'Codice',
package/src/langs/ja.js CHANGED
@@ -133,6 +133,7 @@
133
133
  math_modal_title: '数学',
134
134
  maxSize: '最大サイズ',
135
135
  mediaGallery: 'メディア ギャラリー',
136
+ autocomplete: 'オートコンプリート',
136
137
  mention: '言及する',
137
138
  menu_bordered: '境界線',
138
139
  menu_code: 'コード',
package/src/langs/km.js CHANGED
@@ -133,6 +133,7 @@
133
133
  math_modal_title: 'គណិត',
134
134
  maxSize: 'ទំហំធំ',
135
135
  mediaGallery: 'វិចិត្រសាលមេឌៀ',
136
+ autocomplete: 'បំពេញដោយស្វ័យប្រវត្តិ',
136
137
  mention: 'លើកឡើង',
137
138
  menu_bordered: 'មានស៊ុម',
138
139
  menu_code: 'កូដ',
package/src/langs/ko.js CHANGED
@@ -133,6 +133,7 @@
133
133
  math_modal_title: '수식',
134
134
  maxSize: '최대화',
135
135
  mediaGallery: '미디어 갤러리',
136
+ autocomplete: '자동 완성',
136
137
  mention: '멘션',
137
138
  menu_bordered: '경계선',
138
139
  menu_code: '코드',
package/src/langs/lv.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matemātika',
135
135
  maxSize: 'Maksimālais izmērs',
136
136
  mediaGallery: 'Mediju galerija',
137
+ autocomplete: 'Automātiskā pabeigšana',
137
138
  mention: 'Pieminēt',
138
139
  menu_bordered: 'Robežojās',
139
140
  menu_code: 'Kods',
package/src/langs/nl.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Wiskunde',
135
135
  maxSize: 'Maximale grootte',
136
136
  mediaGallery: 'Mediagalerij',
137
+ autocomplete: 'Automatisch aanvullen',
137
138
  mention: 'Vermelding',
138
139
  menu_bordered: 'Omlijnd',
139
140
  menu_code: 'Code',
package/src/langs/pl.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matematyczne',
135
135
  maxSize: 'Maksymalny rozmiar',
136
136
  mediaGallery: 'Galeria multimediów',
137
+ autocomplete: 'Automatyczne uzupełnianie',
137
138
  mention: 'Wzmianka',
138
139
  menu_bordered: 'Z obwódką',
139
140
  menu_code: 'Kod',
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matemática',
135
135
  maxSize: 'Tam máx',
136
136
  mediaGallery: 'Galeria de mídia',
137
+ autocomplete: 'Preenchimento automático',
137
138
  mention: 'Menção',
138
139
  menu_bordered: 'Com borda',
139
140
  menu_code: 'Código',
package/src/langs/ro.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matematică',
135
135
  maxSize: 'Dimensiune maximă',
136
136
  mediaGallery: 'Galeria media',
137
+ autocomplete: 'Completare automată',
137
138
  mention: 'Mentiune',
138
139
  menu_bordered: 'Mărginit',
139
140
  menu_code: 'Citat',
package/src/langs/ru.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'математический',
135
135
  maxSize: 'Ширина по размеру страницы',
136
136
  mediaGallery: 'Галерея мультимедиа',
137
+ autocomplete: 'Автозаполнение',
137
138
  mention: 'Упоминание',
138
139
  menu_bordered: 'Граничная Линия',
139
140
  menu_code: 'Код',
package/src/langs/se.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Math',
135
135
  maxSize: 'Maxstorlek',
136
136
  mediaGallery: 'Rel attribut',
137
+ autocomplete: 'Autoslutför',
137
138
  mention: 'Namn',
138
139
  menu_bordered: 'Avgränsningslinje',
139
140
  menu_code: 'Kod',
package/src/langs/tr.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Matematik',
135
135
  maxSize: 'En Büyük Boyut',
136
136
  mediaGallery: 'Medya galerisi',
137
+ autocomplete: 'Otomatik Tamamlama',
137
138
  mention: 'Belirtmek',
138
139
  menu_bordered: 'Çerçeveli',
139
140
  menu_code: 'Kod',
package/src/langs/uk.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'Формула',
135
135
  maxSize: 'Ширина за розміром сторінки',
136
136
  mediaGallery: 'Медіа галерея',
137
+ autocomplete: 'Автозаповнення',
137
138
  mention: 'Згадати',
138
139
  menu_bordered: 'З лініями',
139
140
  menu_code: 'Код',
package/src/langs/ur.js CHANGED
@@ -134,6 +134,7 @@
134
134
  math_modal_title: 'ریاضی',
135
135
  maxSize: 'زیادہ سے زیادہ سائز',
136
136
  mediaGallery: 'میڈیا گیلری',
137
+ autocomplete: 'خود بخود مکمل',
137
138
  mention: 'تذکرہ',
138
139
  menu_bordered: 'سرحدی',
139
140
  menu_code: 'کوڈ',
@@ -134,6 +134,7 @@
134
134
  math_modal_title: '数学',
135
135
  maxSize: '最大尺寸',
136
136
  mediaGallery: '媒体库',
137
+ autocomplete: '自动完成',
137
138
  mention: '提到',
138
139
  menu_bordered: '边界线',
139
140
  menu_code: '代码',
@@ -27,6 +27,19 @@ import ApiManager from '../manager/ApiManager';
27
27
  * @property {(target: Node) => void} selectorHandler - Function that actions when an item is clicked. Required. Can be overridden in browser.
28
28
  * @property {boolean} [useSearch] - Whether to use the search function. Optional. Default: `true`.
29
29
  * @property {string} [searchUrl] - File server search url. Optional. Can be overridden in browser.
30
+ * - Requested as `searchUrl + '?keyword=' + keyword`. The server must return:
31
+ * ```js
32
+ * {
33
+ * "result": [
34
+ * {
35
+ * "src": "https://example.com/file.jpg",
36
+ * "name": "file.jpg",
37
+ * "thumbnail": "https://example.com/file_thumb.jpg",
38
+ * "tag": ["photo"]
39
+ * }
40
+ * ]
41
+ * }
42
+ * ```
30
43
  * @property {Object<string, string>} [searchUrlHeader] - File server search http header. Optional. Can be overridden in browser.
31
44
  * @property {string} [listClass] - Class name of list div. Required. Can be overridden in browser.
32
45
  * @property {(item: BrowserFile) => string} [drawItemHandler] - Function that returns HTML string for rendering each file item. Required. Can be overridden in browser.
@@ -37,6 +50,7 @@ import ApiManager from '../manager/ApiManager';
37
50
  * @property {Array<*>} [props] - `props` argument to `drawItemHandler` function. Optional. Can be overridden in browser.
38
51
  * @property {number} [columnSize] - Number of `div.se-file-item-column` to be created.
39
52
  * - Optional. Can be overridden in browser. Default: 4.
53
+ * @property {number} [expand=1] - Initial folder expand depth. `1` expands the first level, `Infinity` expands all. Default: `1`.
40
54
  * @property {((item: BrowserFile) => string)} [thumbnail] - Default thumbnail
41
55
  */
42
56
 
@@ -50,6 +64,10 @@ class Browser {
50
64
  #loading;
51
65
  #globalEventHandler;
52
66
 
67
+ /** @type {Array<BrowserFile>} */
68
+ #allItems = [];
69
+ #searchInput;
70
+ #searchClearBtn;
53
71
  #closeSignal = false;
54
72
  #bindClose = null;
55
73
 
@@ -102,6 +120,7 @@ class Browser {
102
120
  this.drawItemHandler = (params.drawItemHandler || DrawItems).bind({ thumbnail: params.thumbnail, props: params.props || [] });
103
121
  this.selectorHandler = params.selectorHandler;
104
122
  this.columnSize = params.columnSize || 4;
123
+ this.expand = params.expand ?? 1;
105
124
  this.folderDefaultPath = '';
106
125
  this.closeArrow = this.#$.icons.menu_arrow_right;
107
126
  this.openArrow = this.#$.icons.menu_arrow_down;
@@ -139,9 +158,15 @@ class Browser {
139
158
  this.#$.eventManager.addEvent(this.side, 'click', this.#OnClickSide.bind(this));
140
159
  this.#$.eventManager.addEvent(content, 'mousedown', this.#OnMouseDown_browser.bind(this));
141
160
  this.#$.eventManager.addEvent(content, 'click', this.#OnClick_browser.bind(this));
142
- this.#$.eventManager.addEvent(browserFrame.querySelector('form.se-browser-search-form'), 'submit', this.#Search.bind(this));
143
161
  this.#$.eventManager.addEvent((this.sideOpenBtn = /** @type {HTMLButtonElement} */ (browserFrame.querySelector('.se-side-open-btn'))), 'click', this.#SideOpen.bind(this));
144
162
  this.#$.eventManager.addEvent([this.header, browserFrame.querySelector('.se-browser-main')], 'mousedown', this.#SideClose.bind(this));
163
+
164
+ // search
165
+ const searchForm = browserFrame.querySelector('form.se-browser-search-form');
166
+ this.#searchInput = /** @type {HTMLInputElement} */ (searchForm?.querySelector('input[type="text"]'));
167
+ this.#searchClearBtn = /** @type {HTMLButtonElement} */ (browserFrame.querySelector('.se-browser-search-clear'));
168
+ this.#$.eventManager.addEvent(searchForm, 'submit', this.#Search.bind(this));
169
+ this.#$.eventManager.addEvent(this.#searchClearBtn, 'click', this.#ClearSearch.bind(this));
145
170
  }
146
171
 
147
172
  /**
@@ -195,11 +220,16 @@ class Browser {
195
220
  this.area.style.display = 'none';
196
221
  this.selectedTags = [];
197
222
  this.items = [];
223
+ this.#allItems = [];
198
224
  this.folders = {};
199
225
  this.tree = {};
200
226
  this.data = {};
201
227
  this.keyword = '';
202
228
  this.list.innerHTML = this.tagArea.innerHTML = this.titleArea.textContent = '';
229
+
230
+ if (this.#searchInput) this.#searchInput.value = '';
231
+ if (this.#searchClearBtn) this.#searchClearBtn.style.display = 'none';
232
+
203
233
  this.#$.ui.opendBrowser = null;
204
234
  this.sideInner = null;
205
235
 
@@ -216,8 +246,25 @@ class Browser {
216
246
  this.#drawFileList(this.searchUrl + '?keyword=' + keyword, this.searchUrlHeader, false);
217
247
  } else {
218
248
  this.keyword = keyword.toLowerCase();
219
- this.#drawListItem(this.items, false);
249
+ this.#drawListItem(this.#allItems.length > 0 ? this.#allItems : this.items, false);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * @description Collects all file items from every folder in `this.data`.
255
+ * @returns {Array<BrowserFile>}
256
+ */
257
+ #collectAllItems() {
258
+ const all = [];
259
+ for (const key in this.data) {
260
+ const items = this.data[key];
261
+ if (Array.isArray(items)) {
262
+ for (let i = 0; i < items.length; i++) {
263
+ all.push(items[i]);
264
+ }
265
+ }
220
266
  }
267
+ return all;
221
268
  }
222
269
 
223
270
  /**
@@ -306,6 +353,10 @@ class Browser {
306
353
 
307
354
  this.list.innerHTML = listHTML;
308
355
 
356
+ if (keyword) {
357
+ this.#highlightKeyword(keyword);
358
+ }
359
+
309
360
  if (update) {
310
361
  this.items = items;
311
362
  this.tagArea.innerHTML = tagsHTML;
@@ -341,6 +392,7 @@ class Browser {
341
392
  } else if (typeof data === 'object') {
342
393
  this.sideOpenBtn.style.display = '';
343
394
  this.#parseFolderData(data);
395
+ this.#allItems = this.#collectAllItems();
344
396
 
345
397
  this.side.innerHTML = '';
346
398
  const sideInner = (this.sideInner = dom.utils.createElement('div', null));
@@ -412,8 +464,11 @@ class Browser {
412
464
  * @description Creates a nested folder list from parsed data.
413
465
  * @param {BrowserFile[]|BrowserFile} folderData - The structured folder data.
414
466
  * @param {HTMLElement} parentElement - The parent element to append folder structure to.
467
+ * @param {number} [depth=0] - Current depth level.
415
468
  */
416
- #createFolderList(folderData, parentElement) {
469
+ #createFolderList(folderData, parentElement, depth = 0) {
470
+ const expanded = depth < this.expand;
471
+
417
472
  for (const key in folderData) {
418
473
  const item = folderData[key];
419
474
  if (!item) continue;
@@ -426,10 +481,10 @@ class Browser {
426
481
  );
427
482
  const folderDiv = dom.utils.createElement('div', { class: 'se-menu-folder' }, folderLabel);
428
483
 
429
- folderLabel.insertBefore(dom.utils.createElement('button', null, this.closeArrow), folderLabel.firstElementChild);
484
+ folderLabel.insertBefore(dom.utils.createElement('button', null, expanded ? this.openArrow : this.closeArrow), folderLabel.firstElementChild);
430
485
  const childContainer = document.createElement('div');
431
- dom.utils.addClass(childContainer, 'se-menu-child|se-menu-hidden');
432
- this.#createFolderList(item.children, childContainer);
486
+ dom.utils.addClass(childContainer, expanded ? 'se-menu-child' : 'se-menu-child|se-menu-hidden');
487
+ this.#createFolderList(item.children, childContainer, depth + 1);
433
488
  folderDiv.appendChild(childContainer);
434
489
 
435
490
  parentElement.appendChild(folderDiv);
@@ -540,11 +595,12 @@ class Browser {
540
595
  dom.utils.removeClass(this.side.querySelectorAll('.active'), 'active');
541
596
  dom.utils.addClass([cmdTarget, dom.query.getParentElement(cmdTarget, '.se-menu-folder')], 'active');
542
597
  this.tagArea.innerHTML = '';
598
+ this.selectedTags = [];
543
599
 
544
600
  if (typeof data === 'string') {
545
601
  this.#drawFileList(data, this.urlHeader, true);
546
602
  } else {
547
- this.#drawListItem(data, false);
603
+ this.#drawListItem(data, true);
548
604
  }
549
605
  }
550
606
 
@@ -576,9 +632,39 @@ class Browser {
576
632
  * @param {SubmitEvent} e - Event object
577
633
  */
578
634
  #Search(e) {
579
- const eventTarget = /** @type {HTMLElement} */ (e.currentTarget);
580
635
  e.preventDefault();
581
- this.search(/** @type {HTMLInputElement} */ (eventTarget.querySelector('input[type="text"]')).value);
636
+
637
+ const keyword = this.#searchInput.value;
638
+ this.search(keyword);
639
+
640
+ if (this.#searchClearBtn) this.#searchClearBtn.style.display = keyword ? '' : 'none';
641
+ }
642
+
643
+ /**
644
+ * @description Clears the search keyword and restores the current folder's item list.
645
+ */
646
+ #ClearSearch() {
647
+ if (this.#searchInput) this.#searchInput.value = '';
648
+ if (this.#searchClearBtn) this.#searchClearBtn.style.display = 'none';
649
+
650
+ this.keyword = '';
651
+ this.#drawListItem(this.items, false);
652
+ }
653
+
654
+ /**
655
+ * @description Highlights the search keyword in file name elements.
656
+ * @param {string} keyword - Lowercase keyword to highlight.
657
+ */
658
+ #highlightKeyword(keyword) {
659
+ const nameEls = this.list.querySelectorAll('.se-file-name-image:not(.se-file-name-back)');
660
+ for (let i = 0; i < nameEls.length; i++) {
661
+ const el = nameEls[i];
662
+ const text = el.textContent;
663
+ const idx = text.toLowerCase().indexOf(keyword);
664
+ if (idx > -1) {
665
+ el.innerHTML = text.substring(0, idx) + '<mark>' + text.substring(idx, idx + keyword.length) + '</mark>' + text.substring(idx + keyword.length);
666
+ }
667
+ }
582
668
  }
583
669
 
584
670
  /**
@@ -633,7 +719,10 @@ function CreateHTMLInfos($, useSearch) {
633
719
  useSearch
634
720
  ? /*html*/ `
635
721
  <form class="se-browser-search-form">
636
- <input type="text" class="se-input-form" placeholder="${lang.search}" aria-label="${lang.search}">
722
+ <div class="se-browser-search-input-wrap">
723
+ <input type="text" class="se-input-form" placeholder="${lang.search}" aria-label="${lang.search}">
724
+ <button type="button" class="se-btn se-btn-plain se-browser-search-clear" title="${lang.cancel}" aria-label="${lang.cancel}" style="display:none">${icons.cancel}</button>
725
+ </div>
637
726
  <button type="submit" class="se-btn" title="${lang.search}" aria-label="${lang.search}">${icons.search}</button>
638
727
  </form>`
639
728
  : ''
@@ -13,9 +13,24 @@ const { _w, NO_EVENT } = env;
13
13
  * @property {{default?: string, check_new_window?: string, check_bookmark?: string}} [defaultRel={}] - Default `rel` values auto-applied by condition.
14
14
  * `default` is always applied, `check_new_window` when "Open in new window" is checked, `check_bookmark` for bookmark links.
15
15
  * ```js
16
- * { relList: ['nofollow', 'noreferrer', 'noopener'], defaultRel: { default: 'noopener', check_new_window: 'noreferrer' } }
16
+ * {
17
+ * relList: ['nofollow', 'noreferrer', 'noopener'],
18
+ * defaultRel: { default: 'noopener', check_new_window: 'noreferrer' }
19
+ * }
17
20
  * ```
18
21
  * @property {string} [uploadUrl] - File upload URL.
22
+ * - The server must return:
23
+ * ```js
24
+ * {
25
+ * "result": [
26
+ * {
27
+ * "url": "https://example.com/file.pdf",
28
+ * "name": "file.pdf",
29
+ * "size": 1048576
30
+ * }
31
+ * ]
32
+ * }
33
+ * ```
19
34
  * @property {Object<string, string>} [uploadHeaders] - File upload headers.
20
35
  * @property {number} [uploadSizeLimit] - File upload size limit.
21
36
  * @property {number} [uploadSingleSizeLimit] - File upload single size limit.
@@ -5,6 +5,19 @@ import { Browser } from '../../modules/contract';
5
5
  * @typedef {Object} AudioGalleryPluginOptions
6
6
  * @property {Array<SunEditor.Module.Browser.File>} [data] - Direct data without server calls
7
7
  * @property {string} [url] - Server request URL
8
+ * - The server must return:
9
+ * ```js
10
+ * {
11
+ * "result": [
12
+ * {
13
+ * "src": "https://example.com/audio.mp3",
14
+ * "name": "audio.mp3",
15
+ * "thumbnail": "https://example.com/audio_icon.png",
16
+ * "tag": ["music"]
17
+ * }
18
+ * ]
19
+ * }
20
+ * ```
8
21
  * @property {Object<string, string>} [headers] - Server request headers
9
22
  * @property {string|((item: SunEditor.Module.Browser.File) => string)} [thumbnail] - Default thumbnail
10
23
  */
@@ -5,8 +5,29 @@ import { Browser } from '../../modules/contract';
5
5
  * @typedef {Object} FileBrowserPluginOptions
6
6
  * @property {Object<string, *>|Array<*>} [data] - Direct data without server calls (bypasses URL fetch).
7
7
  * @property {string} [url] - Server request URL
8
+ * - The server must return a nested folder structure.
9
+ * - `_data`: array (inline) or string URL (lazy-loaded on folder click).
10
+ * - `"default": true` sets the initially selected folder.
11
+ * ```js
12
+ * {
13
+ * "result": {
14
+ * "root": {
15
+ * "name": "Root",
16
+ * "default": true,
17
+ * "_data": [
18
+ * { "src": "https://example.com/file1.pdf", "name": "file1.pdf" }
19
+ * ],
20
+ * "documents": {
21
+ * "name": "Documents",
22
+ * "_data": "https://api.example.com/files/documents"
23
+ * }
24
+ * }
25
+ * }
26
+ * }
27
+ * ```
8
28
  * @property {Object<string, string>} [headers] - Server request headers
9
29
  * @property {string|((item: SunEditor.Module.Browser.File) => string)} [thumbnail] - Default thumbnail URL or a function that returns a thumbnail URL per item.
30
+ * @property {number} [expand=1] - Initial folder expand depth. `1` expands the first level, `Infinity` expands all. Default: `1`.
10
31
  * @property {Array<string>} [props] - Additional tag names
11
32
  * ```js
12
33
  * { url: '/api/files', headers: { Authorization: 'Bearer token' }, thumbnail: (item) => item.thumbUrl }
@@ -48,6 +69,7 @@ class FileBrowser extends PluginBrowser {
48
69
  className: 'se-file-browser',
49
70
  thumbnail: typeof pluginOptions.thumbnail === 'function' ? pluginOptions.thumbnail : (item) => thumbnail[item.type] || defaultThumbnail,
50
71
  props: [...new Set((pluginOptions.props ?? []).concat(['frame']))],
72
+ expand: pluginOptions.expand,
51
73
  });
52
74
  }
53
75
 
@@ -5,6 +5,20 @@ import { Browser } from '../../modules/contract';
5
5
  * @typedef {Object} FileGalleryPluginOptions
6
6
  * @property {Array<SunEditor.Module.Browser.File>} [data] - Direct data without server calls
7
7
  * @property {string} [url] - Server request URL
8
+ * - The server must return:
9
+ * ```js
10
+ * {
11
+ * "result": [
12
+ * {
13
+ * "src": "https://example.com/doc.pdf",
14
+ * "name": "doc.pdf",
15
+ * "thumbnail": "https://example.com/pdf_icon.png",
16
+ * "type": "file", // video, image ..[plugin name]
17
+ * "tag": ["document"]
18
+ * }
19
+ * ]
20
+ * }
21
+ * ```
8
22
  * @property {Object<string, string>} [headers] - Server request headers
9
23
  * @property {string|((item: SunEditor.Module.Browser.File) => string)} [thumbnail] - Default thumbnail
10
24
  */
@@ -5,6 +5,20 @@ import { Browser } from '../../modules/contract';
5
5
  * @typedef ImageGalleryPluginOptions
6
6
  * @property {Array<*>} [data] - Direct data without server calls
7
7
  * @property {string} [url] - Server request URL
8
+ * - The server must return:
9
+ * ```js
10
+ * {
11
+ * "result": [
12
+ * {
13
+ * "src": "https://example.com/img.jpg",
14
+ * "name": "img.jpg",
15
+ * "thumbnail": "https://example.com/img_thumb.jpg",
16
+ * "alt": "description",
17
+ * "tag": ["nature"]
18
+ * }
19
+ * ]
20
+ * }
21
+ * ```
8
22
  * @property {Object<string, string>} [headers] - Server request headers
9
23
  */
10
24
 
@@ -5,6 +5,20 @@ import { Browser } from '../../modules/contract';
5
5
  * @typedef {Object} VideoGalleryPluginOptions
6
6
  * @property {Array<SunEditor.Module.Browser.File>} [data] - Direct data without server calls
7
7
  * @property {string} [url] - Server request URL
8
+ * - The server must return:
9
+ * ```js
10
+ * {
11
+ * "result": [
12
+ * {
13
+ * "src": "https://example.com/video.mp4",
14
+ * "name": "video.mp4",
15
+ * "thumbnail": "https://example.com/video_thumb.jpg",
16
+ * "frame": "video",
17
+ * "tag": ["tutorial"]
18
+ * }
19
+ * ]
20
+ * }
21
+ * ```
8
22
  * @property {Object<string, string>} [headers] - Server request headers
9
23
  * @property {string|((item: SunEditor.Module.Browser.File) => string)} [thumbnail] - Default thumbnail
10
24
  */
@@ -8,6 +8,18 @@ const { NO_EVENT } = env;
8
8
  /**
9
9
  * @typedef FileUploadPluginOptions
10
10
  * @property {string} uploadUrl - Server request URL for file upload
11
+ * - The server must return:
12
+ * ```js
13
+ * {
14
+ * "result": [
15
+ * {
16
+ * "url": "https://example.com/file.pdf",
17
+ * "name": "file.pdf",
18
+ * "size": 1048576
19
+ * }
20
+ * ]
21
+ * }
22
+ * ```
11
23
  * @property {Object<string, string>} [uploadHeaders] - Server request headers
12
24
  * @property {number} [uploadSizeLimit] - Total upload size limit in bytes
13
25
  * @property {number} [uploadSingleSizeLimit] - Single file size limit in bytes
@@ -75,7 +75,7 @@ function CreateHTML(layoutList) {
75
75
  t = layoutList[i];
76
76
  list += /*html*/ `
77
77
  <li>
78
- <button type="button" class="se-btn se-btn-list" data-value="${i}" title="${t.name}" aria-label="${t.name}">
78
+ <button type="button" class="se-btn se-btn-list" data-command="layout" data-value="${i}" title="${t.name}" aria-label="${t.name}">
79
79
  ${t.name}
80
80
  </button>
81
81
  </li>`;
@@ -74,7 +74,8 @@ function CreateHTML(templateList) {
74
74
  <li>
75
75
  <button
76
76
  type="button"
77
- class="se-btn se-btn-list"
77
+ class="se-btn se-btn-list"
78
+ data-command="template"
78
79
  data-value="${i}"
79
80
  title="${t.name}"
80
81
  aria-label="${t.name}"