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
@@ -3,6 +3,18 @@ export default Link;
3
3
  export type LinkOptions = {
4
4
  /**
5
5
  * - The URL endpoint for file uploads.
6
+ * - The server must return:
7
+ * ```js
8
+ * {
9
+ * "result": [
10
+ * {
11
+ * "url": "https://example.com/file.pdf",
12
+ * "name": "file.pdf",
13
+ * "size": 1048576
14
+ * }
15
+ * ]
16
+ * }
17
+ * ```
6
18
  */
7
19
  uploadUrl?: string;
8
20
  /**
@@ -28,6 +40,18 @@ export type LinkPluginOptions = Omit<LinkOptions & import('../../modules/ui/Moda
28
40
  /**
29
41
  * @typedef {Object} LinkOptions
30
42
  * @property {string} [uploadUrl] - The URL endpoint for file uploads.
43
+ * - The server must return:
44
+ * ```js
45
+ * {
46
+ * "result": [
47
+ * {
48
+ * "url": "https://example.com/file.pdf",
49
+ * "name": "file.pdf",
50
+ * "size": 1048576
51
+ * }
52
+ * ]
53
+ * }
54
+ * ```
31
55
  * @property {Object<string, string>} [uploadHeaders] - Additional headers for file upload requests.
32
56
  * @property {number} [uploadSizeLimit] - The total file upload size limit in bytes.
33
57
  * @property {number} [uploadSingleSizeLimit] - The single file upload size limit in bytes.
@@ -87,7 +111,10 @@ declare class Link extends PluginModal {
87
111
  * - Default `rel` values auto-applied by condition.
88
112
  * `default` is always applied, `check_new_window` when "Open in new window" is checked, `check_bookmark` for bookmark links.
89
113
  * ```js
90
- * { relList: ['nofollow', 'noreferrer', 'noopener'], defaultRel: { default: 'noopener', check_new_window: 'noreferrer' } }
114
+ * {
115
+ * relList: ['nofollow', 'noreferrer', 'noopener'],
116
+ * defaultRel: { default: 'noopener', check_new_window: 'noreferrer' }
117
+ * }
91
118
  * ```
92
119
  */
93
120
  defaultRel?: {
@@ -32,6 +32,18 @@ export type VideoPluginOptions = {
32
32
  createUrlInput?: boolean;
33
33
  /**
34
34
  * - The URL endpoint for video file uploads.
35
+ * - The server must return:
36
+ * ```js
37
+ * {
38
+ * "result": [
39
+ * {
40
+ * "url": "https://example.com/video.mp4",
41
+ * "name": "video.mp4",
42
+ * "size": 5242880
43
+ * }
44
+ * ]
45
+ * }
46
+ * ```
35
47
  */
36
48
  uploadUrl?: string;
37
49
  /**
@@ -155,6 +167,18 @@ export type VideoState = {
155
167
  * @property {boolean} [createUrlInput] - Whether to create a URL input element for video embedding.
156
168
  * - Defaults to `true`. Always `true` when `createFileInput` is `false`.
157
169
  * @property {string} [uploadUrl] - The URL endpoint for video file uploads.
170
+ * - The server must return:
171
+ * ```js
172
+ * {
173
+ * "result": [
174
+ * {
175
+ * "url": "https://example.com/video.mp4",
176
+ * "name": "video.mp4",
177
+ * "size": 5242880
178
+ * }
179
+ * ]
180
+ * }
181
+ * ```
158
182
  * @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the video upload request.
159
183
  * @property {number} [uploadSizeLimit] - The total upload size limit for videos in bytes.
160
184
  * @property {number} [uploadSingleSizeLimit] - The single file upload size limit for videos in bytes.
@@ -306,7 +306,7 @@ declare global {
306
306
  | 'fileUpload'
307
307
  | 'list_bulleted'
308
308
  | 'list_numbered'
309
- | 'mention'
309
+ | 'autocomplete'
310
310
  | 'align'
311
311
  | 'font'
312
312
  | 'fontColor'
@@ -1,251 +0,0 @@
1
- import { PluginField } from '../../interfaces';
2
- import { Controller } from '../../modules/contract';
3
- import { ApiManager } from '../../modules/manager';
4
- import { SelectMenu } from '../../modules/ui';
5
- import { dom, converter } from '../../helper';
6
-
7
- const { debounce } = converter;
8
-
9
- /**
10
- * @typedef {Object} MentionPluginOptions
11
- * @property {string} [triggerText="@"] - The character that triggers the mention list.
12
- * @property {number} [limitSize=5] - The number of items to display in the mention list
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
15
- * @property {Array<{key: string, name: string, url: string}>} [data] - Static mention data (used instead of API).
16
- * ```js
17
- * // data
18
- * [{ key: 'john', name: 'John Doe', url: '/users/john' }]
19
- * ```
20
- * @property {string} [apiUrl] - The URL to call the mention list
21
- * @property {Object<string, string>} [apiHeaders] - The headers to send with the API call
22
- * @property {boolean} [useCachingData=true] - Whether to cache the mention list data
23
- * @property {boolean} [useCachingFieldData=true] - Whether to cache the mention list data in the field
24
- */
25
-
26
- /**
27
- * @class
28
- * @description Mention Plugin
29
- * - A plugin that provides a mention feature using `@` or a custom trigger character.
30
- * - Displays a mention list when the trigger character is typed.
31
- * - Supports fetching mention data from an API or a predefined data array.
32
- * - Uses caching for optimized performance.
33
- */
34
- class Mention extends PluginField {
35
- static key = 'mention';
36
- static className = '';
37
-
38
- #lastAtPos = 0;
39
- #anchorOffset = 0;
40
- #anchorNode = null;
41
-
42
- /**
43
- * @constructor
44
- * @param {SunEditor.Kernel} kernel - The Kernel instance
45
- * @param {MentionPluginOptions} pluginOptions
46
- */
47
- constructor(kernel, pluginOptions) {
48
- super(kernel);
49
- // plugin basic properties
50
- this.title = this.$.lang.mention;
51
- this.icon = 'mention';
52
-
53
- // members
54
- this.triggerText = pluginOptions.triggerText || '@';
55
- this.limitSize = pluginOptions.limitSize || 5;
56
- this.searchStartLength = pluginOptions.searchStartLength || 0;
57
- this.delayTime = typeof pluginOptions.delayTime === 'number' ? pluginOptions.delayTime : 200;
58
- this.directData = pluginOptions.data;
59
- this.apiUrl = pluginOptions.apiUrl?.replace(/\s/g, '').replace(/\{limitSize\}/i, String(this.limitSize)) || '';
60
- // members - api, caching
61
- this.apiManager = new ApiManager(this, this.$, { headers: pluginOptions.apiHeaders });
62
- this.cachingData = (pluginOptions.useCachingData ?? true) ? new Map() : null;
63
- this.cachingFieldData = (pluginOptions.useCachingFieldData ?? true) ? [] : null;
64
-
65
- // controller
66
- const controllerEl = CreateHTML_controller();
67
- this.selectMenu = new SelectMenu(this.$, { position: 'right-bottom', dir: 'ltr', closeMethod: () => this.controller.close() });
68
- this.controller = new Controller(
69
- this,
70
- this.$,
71
- controllerEl,
72
- {
73
- position: 'bottom',
74
- initMethod: () => {
75
- this.apiManager.cancel();
76
- this.selectMenu.close();
77
- },
78
- },
79
- null,
80
- );
81
- this.selectMenu.on(controllerEl.firstElementChild, this.#SelectMention.bind(this));
82
-
83
- // onInput debounce
84
- this.onInput = debounce(this.onInput.bind(this), this.delayTime);
85
- }
86
-
87
- /**
88
- * @hook Editor.EventManager
89
- * @type {SunEditor.Hook.Event.OnInputAsync}
90
- */
91
- async onInput() {
92
- if (!this.directData) {
93
- this.apiManager.cancel();
94
- }
95
-
96
- const sel = this.$.selection.get();
97
- if (!sel.rangeCount) {
98
- this.selectMenu.close();
99
- return;
100
- }
101
-
102
- const anchorNode = sel.anchorNode;
103
- const anchorOffset = sel.anchorOffset;
104
- const textBeforeCursor = anchorNode.textContent.substring(0, anchorOffset);
105
- const lastAtPos = textBeforeCursor.lastIndexOf(this.triggerText);
106
-
107
- if (lastAtPos > -1) {
108
- const mentionQuery = textBeforeCursor.substring(lastAtPos + 1, anchorOffset);
109
- const beforeText = textBeforeCursor[lastAtPos - 1]?.trim();
110
- if (!/\s/.test(mentionQuery) && (!beforeText || dom.check.isZeroWidth(beforeText))) {
111
- if (mentionQuery.length < this.searchStartLength) return;
112
-
113
- const anchorParent = anchorNode.parentNode;
114
- if (dom.check.isAnchor(anchorParent) && !anchorParent.getAttribute('data-se-mention')) {
115
- return;
116
- }
117
-
118
- try {
119
- await this.#createMentionList(mentionQuery, anchorNode);
120
- this.#lastAtPos = lastAtPos;
121
- this.#anchorNode = anchorNode;
122
- this.#anchorOffset = anchorOffset;
123
- return;
124
- } catch (error) {
125
- console.warn('[SUNEDITOR.mention.api.file] ', error);
126
- }
127
- }
128
- }
129
-
130
- this.selectMenu.close();
131
- }
132
-
133
- /**
134
- * @description Generates the mention list based on user input.
135
- * - Fetches data from cache, direct data, or an API.
136
- * - Creates and opens the mention dropdown.
137
- * - Caches the fetched data for future use.
138
- * @param {string} value - The mention query text.
139
- * @param {Node} targetNode - The node where the mention is triggered.
140
- * @returns {Promise<boolean>} - Returns `true` if the mention list is displayed, `false` otherwise.
141
- */
142
- async #createMentionList(value, targetNode) {
143
- const limit = this.limitSize;
144
- const lowerValue = value.toLowerCase();
145
- let response = null;
146
- if (this.cachingData) {
147
- response = this.cachingData.get(value);
148
- }
149
-
150
- if (!response) {
151
- if (this.directData) {
152
- response = this.directData.filter((item) => item.key.toLowerCase().startsWith(lowerValue)).slice(0, limit);
153
- } else {
154
- const xmlHttp = await this.apiManager.asyncCall({ method: 'GET', url: this.#createUrl(value) });
155
- response = JSON.parse(xmlHttp.responseText);
156
- }
157
- }
158
-
159
- if (this.cachingFieldData) {
160
- const uniqueKeys = new Set();
161
- response = this.cachingFieldData
162
- .concat(response)
163
- .filter(({ key }) => {
164
- if (uniqueKeys.has(key)) return false;
165
- uniqueKeys.add(key);
166
- return key.toLowerCase().startsWith(lowerValue);
167
- })
168
- .slice(0, limit);
169
- }
170
-
171
- if (!response?.length) {
172
- this.selectMenu.close();
173
- return false;
174
- }
175
-
176
- const list = [];
177
- const menus = [];
178
- for (let i = 0, len = response.length, v; i < len; i++) {
179
- v = response[i];
180
- list.push(v);
181
- menus.push(`<div class="se-mention-item"><span>${v.key}</span><span>${v.name}</span></div>`);
182
- }
183
-
184
- if (list.length === 0) {
185
- this.selectMenu.close();
186
- return false;
187
- } else {
188
- // controller open
189
- this.controller.open(targetNode, null, { isWWTarget: true, initMethod: null, addOffset: null });
190
- // select menu create
191
- this.selectMenu.create(list, menus);
192
- this.selectMenu.open();
193
- this.selectMenu.setItem(0);
194
- if (this.cachingData) this.cachingData.set(value, list);
195
- return true;
196
- }
197
- }
198
-
199
- /**
200
- * @description Constructs the API request URL with the mention query.
201
- * @param {string} key - The mention query text.
202
- * @returns {string} - The formatted API request URL.
203
- */
204
- #createUrl(key) {
205
- return this.apiUrl.replace(/\{key\}/i, key);
206
- }
207
-
208
- /**
209
- * @description Inserts a mention link into the editor when a user selects a mention from the list.
210
- * @param {{ key: string, name: string, url: string }} item - The selected mention item.
211
- * @returns {boolean} Returns `false` if insertion fails, otherwise completes execution.
212
- */
213
- #SelectMention(item) {
214
- if (!item) return false;
215
-
216
- let oA = null;
217
- const { key, name, url } = item;
218
- const anchorParent = this.#anchorNode.parentNode;
219
-
220
- if (dom.check.isAnchor(anchorParent)) {
221
- oA = anchorParent;
222
- oA.setAttribute('data-se-mention', key);
223
- oA.setAttribute('href', url);
224
- oA.setAttribute('title', name);
225
- oA.textContent = this.triggerText + key;
226
- } else {
227
- this.$.selection.setRange(this.#anchorNode, this.#lastAtPos, this.#anchorNode, this.#anchorOffset);
228
- oA = dom.utils.createElement('A', { 'data-se-mention': key, href: url, title: name, target: '_blank' }, this.triggerText + key);
229
- if (!this.$.html.insertNode(oA, { afterNode: null, skipCharCount: false })) return false;
230
- }
231
-
232
- this.selectMenu.close();
233
-
234
- const space = dom.utils.createTextNode('\u00A0');
235
- oA.parentNode.insertBefore(space, oA.nextSibling);
236
- this.$.selection.setRange(space, 1, space, 1);
237
-
238
- if (this.cachingFieldData && !this.cachingFieldData.some((data) => data.key === item.key)) {
239
- this.cachingFieldData.push(item);
240
- }
241
- }
242
- }
243
-
244
- /**
245
- * @returns {HTMLElement}
246
- */
247
- function CreateHTML_controller() {
248
- return dom.utils.createElement('DIV', { class: 'se-controller se-empty-controller' }, '<div></div>');
249
- }
250
-
251
- export default Mention;
@@ -1,104 +0,0 @@
1
- import type {} from '../../typedef';
2
- export default Mention;
3
- export type MentionPluginOptions = {
4
- /**
5
- * - The character that triggers the mention list.
6
- */
7
- triggerText?: string;
8
- /**
9
- * - The number of items to display in the mention list
10
- */
11
- limitSize?: number;
12
- /**
13
- * - The number of characters to start searching for the mention list
14
- */
15
- searchStartLength?: number;
16
- /**
17
- * - The time to wait before displaying the mention list
18
- */
19
- delayTime?: number;
20
- /**
21
- * - Static mention data (used instead of API).
22
- * ```js
23
- * // data
24
- * [{ key: 'john', name: 'John Doe', url: '/users/john' }]
25
- * ```
26
- */
27
- data?: Array<{
28
- key: string;
29
- name: string;
30
- url: string;
31
- }>;
32
- /**
33
- * - The URL to call the mention list
34
- */
35
- apiUrl?: string;
36
- /**
37
- * - The headers to send with the API call
38
- */
39
- apiHeaders?: {
40
- [x: string]: string;
41
- };
42
- /**
43
- * - Whether to cache the mention list data
44
- */
45
- useCachingData?: boolean;
46
- /**
47
- * - Whether to cache the mention list data in the field
48
- */
49
- useCachingFieldData?: boolean;
50
- };
51
- /**
52
- * @typedef {Object} MentionPluginOptions
53
- * @property {string} [triggerText="@"] - The character that triggers the mention list.
54
- * @property {number} [limitSize=5] - The number of items to display in the mention list
55
- * @property {number} [searchStartLength=0] - The number of characters to start searching for the mention list
56
- * @property {number} [delayTime=200] - The time to wait before displaying the mention list
57
- * @property {Array<{key: string, name: string, url: string}>} [data] - Static mention data (used instead of API).
58
- * ```js
59
- * // data
60
- * [{ key: 'john', name: 'John Doe', url: '/users/john' }]
61
- * ```
62
- * @property {string} [apiUrl] - The URL to call the mention list
63
- * @property {Object<string, string>} [apiHeaders] - The headers to send with the API call
64
- * @property {boolean} [useCachingData=true] - Whether to cache the mention list data
65
- * @property {boolean} [useCachingFieldData=true] - Whether to cache the mention list data in the field
66
- */
67
- /**
68
- * @class
69
- * @description Mention Plugin
70
- * - A plugin that provides a mention feature using `@` or a custom trigger character.
71
- * - Displays a mention list when the trigger character is typed.
72
- * - Supports fetching mention data from an API or a predefined data array.
73
- * - Uses caching for optimized performance.
74
- */
75
- declare class Mention extends PluginField {
76
- /**
77
- * @constructor
78
- * @param {SunEditor.Kernel} kernel - The Kernel instance
79
- * @param {MentionPluginOptions} pluginOptions
80
- */
81
- constructor(kernel: SunEditor.Kernel, pluginOptions: MentionPluginOptions);
82
- title: any;
83
- triggerText: string;
84
- limitSize: number;
85
- searchStartLength: number;
86
- delayTime: number;
87
- directData: {
88
- key: string;
89
- name: string;
90
- url: string;
91
- }[];
92
- apiUrl: string;
93
- apiManager: ApiManager;
94
- cachingData: Map<any, any>;
95
- cachingFieldData: any[];
96
- selectMenu: SelectMenu;
97
- controller: Controller;
98
- onInput(params: SunEditor.HookParams.InputWithData): Promise<void>;
99
- #private;
100
- }
101
- import { PluginField } from '../../interfaces';
102
- import { Controller } from '../../modules/contract';
103
- import { ApiManager } from '../../modules/manager';
104
- import { SelectMenu } from '../../modules/ui';