suneditor 3.0.6 → 3.1.1

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 (61) 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 +2 -2
  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/event/rules/keydown.rule.enter.js +2 -2
  9. package/src/core/logic/dom/format.js +5 -1
  10. package/src/core/logic/dom/html.js +23 -1
  11. package/src/core/logic/dom/offset.js +24 -1
  12. package/src/core/logic/panel/menu.js +74 -3
  13. package/src/core/logic/panel/viewer.js +6 -4
  14. package/src/core/logic/shell/shortcuts.js +1 -1
  15. package/src/core/schema/options.js +1 -1
  16. package/src/core/section/constructor.js +2 -2
  17. package/src/helper/index.js +3 -0
  18. package/src/helper/msOffice.js +849 -0
  19. package/src/interfaces/plugins.js +1 -1
  20. package/src/langs/ckb.js +1 -0
  21. package/src/langs/cs.js +1 -0
  22. package/src/langs/da.js +1 -0
  23. package/src/langs/de.js +1 -0
  24. package/src/langs/en.js +1 -1
  25. package/src/langs/es.js +1 -0
  26. package/src/langs/fa.js +1 -0
  27. package/src/langs/fr.js +1 -0
  28. package/src/langs/he.js +1 -0
  29. package/src/langs/hu.js +1 -0
  30. package/src/langs/it.js +1 -0
  31. package/src/langs/ja.js +1 -0
  32. package/src/langs/km.js +1 -0
  33. package/src/langs/ko.js +1 -0
  34. package/src/langs/lv.js +1 -0
  35. package/src/langs/nl.js +1 -0
  36. package/src/langs/pl.js +1 -0
  37. package/src/langs/pt_br.js +1 -0
  38. package/src/langs/ro.js +1 -0
  39. package/src/langs/ru.js +1 -0
  40. package/src/langs/se.js +1 -0
  41. package/src/langs/tr.js +1 -0
  42. package/src/langs/uk.js +1 -0
  43. package/src/langs/ur.js +1 -0
  44. package/src/langs/zh_cn.js +1 -0
  45. package/src/modules/contract/Browser.js +1 -0
  46. package/src/plugins/dropdown/layout.js +1 -1
  47. package/src/plugins/dropdown/template.js +2 -1
  48. package/src/plugins/field/autocomplete.js +383 -0
  49. package/src/plugins/index.js +3 -3
  50. package/src/typedef.js +1 -1
  51. package/types/core/logic/shell/shortcuts.d.ts +2 -2
  52. package/types/core/schema/options.d.ts +2 -2
  53. package/types/helper/index.d.ts +4 -0
  54. package/types/helper/msOffice.d.ts +11 -0
  55. package/types/interfaces/plugins.d.ts +1 -1
  56. package/types/langs/_Lang.d.ts +1 -2
  57. package/types/plugins/field/autocomplete.d.ts +251 -0
  58. package/types/plugins/index.d.ts +3 -3
  59. package/types/typedef.d.ts +1 -1
  60. package/src/plugins/field/mention.js +0 -251
  61. package/types/plugins/field/mention.d.ts +0 -104
@@ -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=120] - 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 : 120;
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=120] - 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';