suneditor 3.0.5 → 3.0.6

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 (35) 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 +51 -3
  5. package/src/core/logic/shell/_commandExecutor.js +38 -1
  6. package/src/core/section/constructor.js +1 -1
  7. package/src/modules/contract/Browser.js +98 -10
  8. package/src/modules/ui/ModalAnchorEditor.js +16 -1
  9. package/src/plugins/browser/audioGallery.js +13 -0
  10. package/src/plugins/browser/fileBrowser.js +22 -0
  11. package/src/plugins/browser/fileGallery.js +14 -0
  12. package/src/plugins/browser/imageGallery.js +14 -0
  13. package/src/plugins/browser/videoGallery.js +14 -0
  14. package/src/plugins/command/fileUpload.js +12 -0
  15. package/src/plugins/field/mention.js +2 -2
  16. package/src/plugins/modal/audio.js +12 -0
  17. package/src/plugins/modal/embed.js +12 -0
  18. package/src/plugins/modal/image/index.js +12 -0
  19. package/src/plugins/modal/link.js +12 -0
  20. package/src/plugins/modal/video/index.js +12 -0
  21. package/types/langs/_Lang.d.ts +1 -0
  22. package/types/modules/contract/Browser.d.ts +32 -0
  23. package/types/modules/ui/ModalAnchorEditor.d.ts +32 -2
  24. package/types/plugins/browser/audioGallery.d.ts +26 -0
  25. package/types/plugins/browser/fileBrowser.d.ts +45 -0
  26. package/types/plugins/browser/fileGallery.d.ts +28 -0
  27. package/types/plugins/browser/imageGallery.d.ts +28 -0
  28. package/types/plugins/browser/videoGallery.d.ts +28 -0
  29. package/types/plugins/command/fileUpload.d.ts +24 -0
  30. package/types/plugins/field/mention.d.ts +1 -1
  31. package/types/plugins/modal/audio.d.ts +24 -0
  32. package/types/plugins/modal/embed.d.ts +24 -0
  33. package/types/plugins/modal/image/index.d.ts +24 -0
  34. package/types/plugins/modal/link.d.ts +28 -1
  35. package/types/plugins/modal/video/index.d.ts +24 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suneditor",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "Vanilla JavaScript based WYSIWYG web editor",
5
5
  "author": "Yi JiHong",
6
6
  "license": "MIT",
@@ -368,6 +368,26 @@
368
368
  z-index: 1;
369
369
  }
370
370
 
371
+ /** -- plain text button */
372
+ .sun-editor .se-btn.se-btn-plain {
373
+ background-color: transparent;
374
+ border-color: transparent;
375
+ box-shadow: none;
376
+ }
377
+ .sun-editor .se-btn.se-btn-plain:not(.on):not(.active):enabled:hover,
378
+ .sun-editor .se-btn.se-btn-plain:not(.on):not(.active):enabled:focus {
379
+ background-color: transparent;
380
+ border-color: transparent;
381
+ box-shadow: none;
382
+ }
383
+ .sun-editor .se-btn.se-btn-plain:not(.on):not(.active):enabled:active,
384
+ .sun-editor .se-btn.se-btn-plain:not(.on):not(.active):enabled.__se__active {
385
+ background-color: transparent;
386
+ border-color: transparent !important;
387
+ box-shadow: none;
388
+ outline: none !important;
389
+ }
390
+
371
391
  /** --- primary button */
372
392
  .sun-editor .se-btn-primary {
373
393
  background-color: var(--se-active-light2-color);
@@ -3232,11 +3252,14 @@
3232
3252
  margin-left: 0;
3233
3253
  margin-right: 0;
3234
3254
  }
3235
-
3236
- .sun-editor .se-browser .se-browser-search input {
3255
+ .sun-editor .se-browser .se-browser-search-input-wrap {
3256
+ position: relative;
3237
3257
  flex: auto;
3258
+ }
3259
+ .sun-editor .se-browser .se-browser-search-input-wrap input {
3260
+ width: 100%;
3261
+ padding: 3px 24px 3px 6px;
3238
3262
  background-color: transparent;
3239
- padding: 3px 6px;
3240
3263
  color: var(--se-main-font-color);
3241
3264
  text-decoration: none;
3242
3265
  border-radius: var(--se-border-radius);
@@ -3245,6 +3268,18 @@
3245
3268
  -moz-background-clip: padding;
3246
3269
  -webkit-background-clip: padding-box;
3247
3270
  background-clip: padding-box;
3271
+ box-sizing: border-box;
3272
+ }
3273
+ .sun-editor .se-browser .se-browser-search-clear {
3274
+ position: absolute;
3275
+ right: 2px;
3276
+ top: 50%;
3277
+ transform: translateY(-50%);
3278
+ padding: 0 2px;
3279
+ opacity: 0.35;
3280
+ }
3281
+ .sun-editor .se-browser .se-browser-search-clear:hover {
3282
+ opacity: 0.8;
3248
3283
  }
3249
3284
 
3250
3285
  .sun-editor .se-browser .se-browser-search button {
@@ -3396,6 +3431,12 @@
3396
3431
  opacity: 0.6;
3397
3432
  pointer-events: none;
3398
3433
  }
3434
+ .sun-editor .se-browser .se-file-name-image mark {
3435
+ background-color: var(--se-active-color);
3436
+ color: var(--se-active-font-color);
3437
+ padding: 0 1px;
3438
+ border-radius: 2px;
3439
+ }
3399
3440
 
3400
3441
  .sun-editor .se-browser .se-browser-list.se-preview-list .se-file-item-img > * {
3401
3442
  display: flex;
@@ -4148,6 +4189,13 @@
4148
4189
  margin: 2px 8px;
4149
4190
  flex-direction: row-reverse;
4150
4191
  }
4192
+ .sun-editor.se-rtl .se-browser .se-browser-search-input-wrap input {
4193
+ padding: 3px 6px 3px 24px;
4194
+ }
4195
+ .sun-editor.se-rtl .se-browser .se-browser-search-clear {
4196
+ right: auto;
4197
+ left: 2px;
4198
+ }
4151
4199
  .sun-editor.se-rtl .se-browser .se-browser-side .se-menu-icon {
4152
4200
  margin: 0 0 0 10px;
4153
4201
  }
@@ -59,12 +59,15 @@ export default class CommandExecutor {
59
59
  * @description Execute default command of command button
60
60
  */
61
61
  async execute(command, button) {
62
- if (this.#frameContext.get('isReadOnly') && !/copy|cut|selectAll|codeView|markdownView|fullScreen|print|preview|showBlocks|finder/.test(command)) return;
62
+ if (this.#frameContext.get('isReadOnly') && !/copy|cut|selectAll|selectAll_full|codeView|markdownView|fullScreen|print|preview|showBlocks|finder/.test(command)) return;
63
63
 
64
64
  switch (command) {
65
65
  case 'selectAll':
66
66
  this.#SELECT_ALL();
67
67
  break;
68
+ case 'selectAll_full':
69
+ this.#SELECT_ALL_FULL();
70
+ break;
68
71
  case 'copy': {
69
72
  const range = this.#$.selection.getRange();
70
73
  if (range.collapsed) break;
@@ -254,6 +257,40 @@ export default class CommandExecutor {
254
257
  this.#$.toolbar._showBalloon(this.#$.selection.setRange(first, 0, last, last.textContent.length));
255
258
  }
256
259
 
260
+ /**
261
+ * @description Selects all content in the entire editor without scope stepping.
262
+ */
263
+ #SELECT_ALL_FULL() {
264
+ this.#$.ui.offCurrentController();
265
+ this.#$.menu.containerOff();
266
+
267
+ const ww = this.#frameContext.get('wysiwyg');
268
+ let { first, last } = __findFirstAndLast(ww);
269
+
270
+ if (!first || !last) return;
271
+
272
+ let info = null;
273
+ if (dom.check.isMedia(first) || (info = this.#$.component.get(first)) || dom.check.isTableElements(first)) {
274
+ info ||= this.#$.component.get(first);
275
+ const br = dom.utils.createElement('BR');
276
+ const format = dom.utils.createElement(this.#options.get('defaultLine'), null, br);
277
+ first = info ? info.container || info.cover : first;
278
+ first.parentElement.insertBefore(format, first);
279
+ first = br;
280
+ }
281
+
282
+ if (dom.check.isMedia(last) || (info = this.#$.component.get(last)) || dom.check.isTableElements(last)) {
283
+ info ||= this.#$.component.get(last);
284
+ const br = dom.utils.createElement('BR');
285
+ const format = dom.utils.createElement(this.#options.get('defaultLine'), null, br);
286
+ last = info ? info.container || info.cover : last;
287
+ last.parentElement.appendChild(format);
288
+ last = br;
289
+ }
290
+
291
+ this.#$.toolbar._showBalloon(this.#$.selection.setRange(first, 0, last, last.textContent.length));
292
+ }
293
+
257
294
  /**
258
295
  * @description Saves the editor content.
259
296
  * @returns {Promise<void>}
@@ -1132,7 +1132,7 @@ function _defaultButtons(isRTL, icons, lang) {
1132
1132
  finder: ['se-component-enabled', lang.find, 'finder', '', icons.finder],
1133
1133
  save: ['se-component-enabled', lang.save, 'save', '', icons.save],
1134
1134
  newDocument: ['se-component-enabled', lang.newDocument, 'newDocument', '', icons.new_document],
1135
- selectAll: ['se-component-enabled', lang.selectAll, 'selectAll', '', icons.select_all],
1135
+ selectAll: ['se-component-enabled', lang.selectAll, 'selectAll_full', '', icons.select_all],
1136
1136
  pageBreak: ['se-component-enabled', lang.pageBreak, 'pageBreak', '', icons.page_break],
1137
1137
  // document type buttons
1138
1138
  pageUp: ['se-component-enabled', lang.pageUp, 'pageUp', '', icons.page_up],
@@ -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);
@@ -544,7 +599,7 @@ class Browser {
544
599
  if (typeof data === 'string') {
545
600
  this.#drawFileList(data, this.urlHeader, true);
546
601
  } else {
547
- this.#drawListItem(data, false);
602
+ this.#drawListItem(data, true);
548
603
  }
549
604
  }
550
605
 
@@ -576,9 +631,39 @@ class Browser {
576
631
  * @param {SubmitEvent} e - Event object
577
632
  */
578
633
  #Search(e) {
579
- const eventTarget = /** @type {HTMLElement} */ (e.currentTarget);
580
634
  e.preventDefault();
581
- this.search(/** @type {HTMLInputElement} */ (eventTarget.querySelector('input[type="text"]')).value);
635
+
636
+ const keyword = this.#searchInput.value;
637
+ this.search(keyword);
638
+
639
+ if (this.#searchClearBtn) this.#searchClearBtn.style.display = keyword ? '' : 'none';
640
+ }
641
+
642
+ /**
643
+ * @description Clears the search keyword and restores the current folder's item list.
644
+ */
645
+ #ClearSearch() {
646
+ if (this.#searchInput) this.#searchInput.value = '';
647
+ if (this.#searchClearBtn) this.#searchClearBtn.style.display = 'none';
648
+
649
+ this.keyword = '';
650
+ this.#drawListItem(this.items, false);
651
+ }
652
+
653
+ /**
654
+ * @description Highlights the search keyword in file name elements.
655
+ * @param {string} keyword - Lowercase keyword to highlight.
656
+ */
657
+ #highlightKeyword(keyword) {
658
+ const nameEls = this.list.querySelectorAll('.se-file-name-image:not(.se-file-name-back)');
659
+ for (let i = 0; i < nameEls.length; i++) {
660
+ const el = nameEls[i];
661
+ const text = el.textContent;
662
+ const idx = text.toLowerCase().indexOf(keyword);
663
+ if (idx > -1) {
664
+ el.innerHTML = text.substring(0, idx) + '<mark>' + text.substring(idx, idx + keyword.length) + '</mark>' + text.substring(idx + keyword.length);
665
+ }
666
+ }
582
667
  }
583
668
 
584
669
  /**
@@ -633,7 +718,10 @@ function CreateHTMLInfos($, useSearch) {
633
718
  useSearch
634
719
  ? /*html*/ `
635
720
  <form class="se-browser-search-form">
636
- <input type="text" class="se-input-form" placeholder="${lang.search}" aria-label="${lang.search}">
721
+ <div class="se-browser-search-input-wrap">
722
+ <input type="text" class="se-input-form" placeholder="${lang.search}" aria-label="${lang.search}">
723
+ <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>
724
+ </div>
637
725
  <button type="submit" class="se-btn" title="${lang.search}" aria-label="${lang.search}">${icons.search}</button>
638
726
  </form>`
639
727
  : ''
@@ -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
@@ -11,7 +11,7 @@ const { debounce } = converter;
11
11
  * @property {string} [triggerText="@"] - The character that triggers the mention list.
12
12
  * @property {number} [limitSize=5] - The number of items to display in the mention list
13
13
  * @property {number} [searchStartLength=0] - The number of characters to start searching for the mention list
14
- * @property {number} [delayTime=200] - The time to wait before displaying the mention list
14
+ * @property {number} [delayTime=120] - The time to wait before displaying the mention list
15
15
  * @property {Array<{key: string, name: string, url: string}>} [data] - Static mention data (used instead of API).
16
16
  * ```js
17
17
  * // data
@@ -54,7 +54,7 @@ class Mention extends PluginField {
54
54
  this.triggerText = pluginOptions.triggerText || '@';
55
55
  this.limitSize = pluginOptions.limitSize || 5;
56
56
  this.searchStartLength = pluginOptions.searchStartLength || 0;
57
- this.delayTime = typeof pluginOptions.delayTime === 'number' ? pluginOptions.delayTime : 200;
57
+ this.delayTime = typeof pluginOptions.delayTime === 'number' ? pluginOptions.delayTime : 120;
58
58
  this.directData = pluginOptions.data;
59
59
  this.apiUrl = pluginOptions.apiUrl?.replace(/\s/g, '').replace(/\{limitSize\}/i, String(this.limitSize)) || '';
60
60
  // members - api, caching
@@ -13,6 +13,18 @@ const { NO_EVENT, ON_OVER_COMPONENT } = env;
13
13
  * @property {boolean} [createUrlInput] - Whether to create a URL input element.
14
14
  * - Defaults to `true`. Always `true` when `createFileInput` is `false`.
15
15
  * @property {string} [uploadUrl] - The URL to which files will be uploaded.
16
+ * - The server must return:
17
+ * ```js
18
+ * {
19
+ * "result": [
20
+ * {
21
+ * "url": "https://example.com/audio.mp3",
22
+ * "name": "audio.mp3",
23
+ * "size": 3145728
24
+ * }
25
+ * ]
26
+ * }
27
+ * ```
16
28
  * @property {Object<string, string>} [uploadHeaders] - Headers to include in the file upload request.
17
29
  * @property {number} [uploadSizeLimit] - The total upload size limit in bytes.
18
30
  * @property {number} [uploadSingleSizeLimit] - The single file size limit in bytes.
@@ -11,6 +11,18 @@ const { _w, NO_EVENT } = env;
11
11
  * @property {string} [defaultHeight] - The default height of the embed element (numeric value or with unit).
12
12
  * @property {boolean} [percentageOnlySize=false] - Whether to allow only percentage-based sizing.
13
13
  * @property {string} [uploadUrl] - The URL for file uploads.
14
+ * - The server must return:
15
+ * ```js
16
+ * {
17
+ * "result": [
18
+ * {
19
+ * "url": "https://example.com/embed.html",
20
+ * "name": "embed.html",
21
+ * "size": 2048
22
+ * }
23
+ * ]
24
+ * }
25
+ * ```
14
26
  * @property {Object<string, string>} [uploadHeaders] - Headers to include in file upload requests.
15
27
  * @property {number} [uploadSizeLimit] - The total file upload size limit in bytes.
16
28
  * @property {number} [uploadSingleSizeLimit] - The single file upload size limit in bytes.
@@ -22,6 +22,18 @@ const { NO_EVENT } = env;
22
22
  * @property {boolean} [createUrlInput] - Whether to create a URL input element for image insertion.
23
23
  * - Defaults to `true`. Always `true` when `createFileInput` is `false`.
24
24
  * @property {string} [uploadUrl] - The URL endpoint for image file uploads.
25
+ * - The server must return:
26
+ * ```js
27
+ * {
28
+ * "result": [
29
+ * {
30
+ * "url": "https://example.com/image.jpg",
31
+ * "name": "image.jpg",
32
+ * "size": 123456
33
+ * }
34
+ * ]
35
+ * }
36
+ * ```
25
37
  * @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the file upload request.
26
38
  * ```js
27
39
  * { uploadUrl: '/api/upload/image', uploadHeaders: { Authorization: 'Bearer token' } }
@@ -6,6 +6,18 @@ import { dom, numbers } from '../../helper';
6
6
  /**
7
7
  * @typedef {Object} LinkOptions
8
8
  * @property {string} [uploadUrl] - The URL endpoint for file uploads.
9
+ * - The server must return:
10
+ * ```js
11
+ * {
12
+ * "result": [
13
+ * {
14
+ * "url": "https://example.com/file.pdf",
15
+ * "name": "file.pdf",
16
+ * "size": 1048576
17
+ * }
18
+ * ]
19
+ * }
20
+ * ```
9
21
  * @property {Object<string, string>} [uploadHeaders] - Additional headers for file upload requests.
10
22
  * @property {number} [uploadSizeLimit] - The total file upload size limit in bytes.
11
23
  * @property {number} [uploadSingleSizeLimit] - The single file upload size limit in bytes.
@@ -19,6 +19,18 @@ import { CreateHTML_modal } from './render/video.html';
19
19
  * @property {boolean} [createUrlInput] - Whether to create a URL input element for video embedding.
20
20
  * - Defaults to `true`. Always `true` when `createFileInput` is `false`.
21
21
  * @property {string} [uploadUrl] - The URL endpoint for video file uploads.
22
+ * - The server must return:
23
+ * ```js
24
+ * {
25
+ * "result": [
26
+ * {
27
+ * "url": "https://example.com/video.mp4",
28
+ * "name": "video.mp4",
29
+ * "size": 5242880
30
+ * }
31
+ * ]
32
+ * }
33
+ * ```
22
34
  * @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the video upload request.
23
35
  * @property {number} [uploadSizeLimit] - The total upload size limit for videos in bytes.
24
36
  * @property {number} [uploadSingleSizeLimit] - The single file upload size limit for videos in bytes.