suneditor 3.0.6 → 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 (58) 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/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/shortcuts.js +1 -1
  12. package/src/core/schema/options.js +1 -1
  13. package/src/core/section/constructor.js +2 -2
  14. package/src/helper/index.js +3 -0
  15. package/src/helper/msOffice.js +849 -0
  16. package/src/interfaces/plugins.js +1 -1
  17. package/src/langs/ckb.js +1 -0
  18. package/src/langs/cs.js +1 -0
  19. package/src/langs/da.js +1 -0
  20. package/src/langs/de.js +1 -0
  21. package/src/langs/en.js +1 -1
  22. package/src/langs/es.js +1 -0
  23. package/src/langs/fa.js +1 -0
  24. package/src/langs/fr.js +1 -0
  25. package/src/langs/he.js +1 -0
  26. package/src/langs/hu.js +1 -0
  27. package/src/langs/it.js +1 -0
  28. package/src/langs/ja.js +1 -0
  29. package/src/langs/km.js +1 -0
  30. package/src/langs/ko.js +1 -0
  31. package/src/langs/lv.js +1 -0
  32. package/src/langs/nl.js +1 -0
  33. package/src/langs/pl.js +1 -0
  34. package/src/langs/pt_br.js +1 -0
  35. package/src/langs/ro.js +1 -0
  36. package/src/langs/ru.js +1 -0
  37. package/src/langs/se.js +1 -0
  38. package/src/langs/tr.js +1 -0
  39. package/src/langs/uk.js +1 -0
  40. package/src/langs/ur.js +1 -0
  41. package/src/langs/zh_cn.js +1 -0
  42. package/src/modules/contract/Browser.js +1 -0
  43. package/src/plugins/dropdown/layout.js +1 -1
  44. package/src/plugins/dropdown/template.js +2 -1
  45. package/src/plugins/field/autocomplete.js +346 -0
  46. package/src/plugins/index.js +3 -3
  47. package/src/typedef.js +1 -1
  48. package/types/core/logic/shell/shortcuts.d.ts +2 -2
  49. package/types/core/schema/options.d.ts +2 -2
  50. package/types/helper/index.d.ts +4 -0
  51. package/types/helper/msOffice.d.ts +11 -0
  52. package/types/interfaces/plugins.d.ts +1 -1
  53. package/types/langs/_Lang.d.ts +1 -2
  54. package/types/plugins/field/autocomplete.d.ts +177 -0
  55. package/types/plugins/index.d.ts +3 -3
  56. package/types/typedef.d.ts +1 -1
  57. package/src/plugins/field/mention.js +0 -251
  58. package/types/plugins/field/mention.d.ts +0 -104
@@ -54,7 +54,7 @@ export type ShortcutInfo = {
54
54
  */
55
55
  r: Array<string>;
56
56
  /**
57
- * - Whether the event was triggered by a text input (e.g., mention like
57
+ * - Whether the event was triggered by a text input (e.g., autocomplete like
58
58
  */
59
59
  textTrigger: string;
60
60
  };
@@ -73,7 +73,7 @@ export type ShortcutInfo = {
73
73
  * @property {string} type - Plugin's type. (`command`, `dropdown`, `modal`, `browser`, `input`, `field`, `popup`).
74
74
  * @property {Node} button - The plugin command button.
75
75
  * @property {Array<string>} r - An array of key codes generated with the reverseButtons option, used to reverse the action for a specific key combination.
76
- * @property {string} textTrigger - Whether the event was triggered by a text input (e.g., mention like @ab).
76
+ * @property {string} textTrigger - Whether the event was triggered by a text input (e.g., autocomplete like @ab).
77
77
  */
78
78
  /**
79
79
  * @description Shortcuts class
@@ -525,6 +525,7 @@ export namespace DEFAULTS {
525
525
  * @property {import('../../plugins/dropdown/align.js').AlignPluginOptions} [align]
526
526
  * @property {import('../../plugins/modal/audio.js').AudioPluginOptions} [audio]
527
527
  * @property {import('../../plugins/browser/audioGallery.js').AudioGalleryPluginOptions} [audioGallery]
528
+ * @property {import('../../plugins/field/autocomplete.js').AutocompletePluginOptions} [autocomplete]
528
529
  * @property {import('../../plugins/dropdown/backgroundColor.js').BackgroundColorPluginOptions} [backgroundColor]
529
530
  * @property {import('../../plugins/dropdown/blockStyle.js').BlockStylePluginOptions} [blockStyle]
530
531
  * @property {import('../../plugins/command/codeBlock.js').CodeBlockPluginOptions} [codeBlock]
@@ -544,7 +545,6 @@ export namespace DEFAULTS {
544
545
  * @property {import('../../plugins/dropdown/lineHeight.js').LineHeightPluginOptions} [lineHeight]
545
546
  * @property {import('../../plugins/modal/link.js').LinkPluginOptions} [link]
546
547
  * @property {import('../../plugins/modal/math.js').MathPluginOptions} [math]
547
- * @property {import('../../plugins/field/mention.js').MentionPluginOptions} [mention]
548
548
  * @property {import('../../plugins/dropdown/paragraphStyle.js').ParagraphStylePluginOptions} [paragraphStyle]
549
549
  * @property {import('../../plugins/dropdown/table/index.js').TablePluginOptions} [table]
550
550
  * @property {import('../../plugins/dropdown/template.js').TemplatePluginOptions} [template]
@@ -1429,6 +1429,7 @@ export type EditorBaseOptions = {
1429
1429
  align?: import('../../plugins/dropdown/align.js').AlignPluginOptions;
1430
1430
  audio?: import('../../plugins/modal/audio.js').AudioPluginOptions;
1431
1431
  audioGallery?: import('../../plugins/browser/audioGallery.js').AudioGalleryPluginOptions;
1432
+ autocomplete?: import('../../plugins/field/autocomplete.js').AutocompletePluginOptions;
1432
1433
  backgroundColor?: import('../../plugins/dropdown/backgroundColor.js').BackgroundColorPluginOptions;
1433
1434
  blockStyle?: import('../../plugins/dropdown/blockStyle.js').BlockStylePluginOptions;
1434
1435
  codeBlock?: import('../../plugins/command/codeBlock.js').CodeBlockPluginOptions;
@@ -1448,7 +1449,6 @@ export type EditorBaseOptions = {
1448
1449
  lineHeight?: import('../../plugins/dropdown/lineHeight.js').LineHeightPluginOptions;
1449
1450
  link?: import('../../plugins/modal/link.js').LinkPluginOptions;
1450
1451
  math?: import('../../plugins/modal/math.js').MathPluginOptions;
1451
- mention?: import('../../plugins/field/mention.js').MentionPluginOptions;
1452
1452
  paragraphStyle?: import('../../plugins/dropdown/paragraphStyle.js').ParagraphStylePluginOptions;
1453
1453
  table?: import('../../plugins/dropdown/table/index.js').TablePluginOptions;
1454
1454
  template?: import('../../plugins/dropdown/template.js').TemplatePluginOptions;
@@ -174,6 +174,9 @@ export const markdown: {
174
174
  jsonToMarkdown: typeof import('./markdown').jsonToMarkdown;
175
175
  markdownToHtml: typeof import('./markdown').markdownToHtml;
176
176
  };
177
+ export const msOffice: {
178
+ cleanHTML: typeof import('./msOffice').cleanHTML;
179
+ };
177
180
  declare namespace _default {
178
181
  export { env };
179
182
  export { unicode };
@@ -183,5 +186,6 @@ declare namespace _default {
183
186
  export { keyCodeMap };
184
187
  export { clipboard };
185
188
  export { markdown };
189
+ export { msOffice };
186
190
  }
187
191
  export default _default;
@@ -0,0 +1,11 @@
1
+ import type {} from '../typedef';
2
+ /**
3
+ * @description Converts MS Word/Excel/OneNote HTML clipboard data to clean, standards-compliant HTML.
4
+ * @param {string} html Raw HTML string from MS Office clipboard
5
+ * @returns {string} Cleaned HTML string
6
+ */
7
+ export function cleanHTML(html: string): string;
8
+ declare namespace _default {
9
+ export { cleanHTML };
10
+ }
11
+ export default _default;
@@ -92,7 +92,7 @@ export class PluginDropdownFree extends Base {
92
92
  * These plugins typically respond to input events in the wysiwyg area
93
93
  *
94
94
  * **Commonly used hooks:**
95
- * - `onInput()` - Responds to input events in the editor (See: `mention` plugin)
95
+ * - `onInput()` - Responds to input events in the editor (See: `autocomplete` plugin)
96
96
  * - Other event hooks can be used as needed (`onKeydown`, `onClick`, etc.)
97
97
  *
98
98
  * Child classes MAY optionally implement event hook methods
@@ -38,7 +38,6 @@ export type _Lang = {
38
38
  caption: string;
39
39
  cellProperties: string;
40
40
  center: string;
41
- clear: string;
42
41
  close: string;
43
42
  codeView: string;
44
43
  color: string;
@@ -119,7 +118,7 @@ export type _Lang = {
119
118
  math_modal_title: string;
120
119
  maxSize: string;
121
120
  mediaGallery: string;
122
- mention: string;
121
+ autocomplete: string;
123
122
  menu_bordered: string;
124
123
  menu_code: string;
125
124
  menu_neon: string;
@@ -0,0 +1,177 @@
1
+ import type {} from '../../typedef';
2
+ export default Autocomplete;
3
+ export type AutocompleteTriggerConfig = {
4
+ /**
5
+ * - Static data array. Each item must have a `key` field. Mutually exclusive with `apiUrl`.
6
+ * ```js
7
+ * // data
8
+ * [{ key: 'john', name: 'John Doe', url: '/users/john' }]
9
+ * ```
10
+ */
11
+ data?: Array<{
12
+ key: string;
13
+ [x: string]: any;
14
+ }>;
15
+ /**
16
+ * - API endpoint URL. Supports `{key}` and `{limitSize}` placeholders. Mutually exclusive with `data`.
17
+ */
18
+ apiUrl?: string;
19
+ /**
20
+ * - HTTP headers for the API request.
21
+ */
22
+ apiHeaders?: {
23
+ [x: string]: string;
24
+ };
25
+ /**
26
+ * - Transforms parsed JSON response into an array of data items.
27
+ */
28
+ transformResponse?: (
29
+ arg0: any,
30
+ arg1: XMLHttpRequest,
31
+ ) => Array<{
32
+ key: string;
33
+ }>;
34
+ /**
35
+ * - Override global `limitSize` for this trigger.
36
+ */
37
+ limitSize?: number;
38
+ /**
39
+ * - Override global `searchStartLength` for this trigger.
40
+ */
41
+ searchStartLength?: number;
42
+ /**
43
+ * - Override global `useCachingData` for this trigger.
44
+ */
45
+ useCachingData?: boolean;
46
+ /**
47
+ * - Override global `useCachingFieldData` for this trigger.
48
+ */
49
+ useCachingFieldData?: boolean;
50
+ /**
51
+ * - Custom dropdown item renderer. Receives `(item, triggerText)`, returns HTML string.
52
+ */
53
+ renderItem?: (
54
+ arg0: {
55
+ key: string;
56
+ [x: string]: any;
57
+ },
58
+ arg1: string,
59
+ ) => string;
60
+ /**
61
+ * - Custom selection handler. Returns:
62
+ * - `string`: inserted as text node
63
+ * - `Element`: inserted as-is
64
+ * - `{tag, attrs, text}`: creates element via `dom.utils.createElement`
65
+ */
66
+ onSelect?: (
67
+ arg0: {
68
+ key: string;
69
+ [x: string]: any;
70
+ },
71
+ arg1: string,
72
+ ) =>
73
+ | string
74
+ | Element
75
+ | {
76
+ tag: string;
77
+ attrs?: any;
78
+ text?: string;
79
+ };
80
+ };
81
+ export type AutocompletePluginOptions = {
82
+ /**
83
+ * - Debounce delay in ms before processing input.
84
+ */
85
+ delayTime?: number;
86
+ /**
87
+ * - Maximum number of items to display in the dropdown.
88
+ */
89
+ limitSize?: number;
90
+ /**
91
+ * - Minimum input length before triggering search.
92
+ */
93
+ searchStartLength?: number;
94
+ /**
95
+ * - Whether to cache query responses per trigger.
96
+ */
97
+ useCachingData?: boolean;
98
+ /**
99
+ * - Whether to cache selected items for priority display.
100
+ */
101
+ useCachingFieldData?: boolean;
102
+ /**
103
+ * - Per-trigger configurations keyed by trigger character.
104
+ * ```js
105
+ * // triggers
106
+ * {
107
+ * '@': { data: [...], renderItem: (item) => `...` },
108
+ * '#': { apiUrl: '/api/tags?q={key}' }
109
+ * }
110
+ * ```
111
+ */
112
+ triggers: {
113
+ [x: string]: AutocompleteTriggerConfig;
114
+ };
115
+ };
116
+ /**
117
+ * @typedef {Object} AutocompleteTriggerConfig
118
+ * @property {Array<{key: string, [x: string]: any}>} [data] - Static data array. Each item must have a `key` field. Mutually exclusive with `apiUrl`.
119
+ * ```js
120
+ * // data
121
+ * [{ key: 'john', name: 'John Doe', url: '/users/john' }]
122
+ * ```
123
+ * @property {string} [apiUrl] - API endpoint URL. Supports `{key}` and `{limitSize}` placeholders. Mutually exclusive with `data`.
124
+ * @property {Object<string, string>} [apiHeaders] - HTTP headers for the API request.
125
+ * @property {function(Object, XMLHttpRequest): Array<{key: string}>} [transformResponse] - Transforms parsed JSON response into an array of data items.
126
+ * @property {number} [limitSize] - Override global `limitSize` for this trigger.
127
+ * @property {number} [searchStartLength] - Override global `searchStartLength` for this trigger.
128
+ * @property {boolean} [useCachingData] - Override global `useCachingData` for this trigger.
129
+ * @property {boolean} [useCachingFieldData] - Override global `useCachingFieldData` for this trigger.
130
+ * @property {function({key: string, [x: string]: any}, string): string} [renderItem] - Custom dropdown item renderer. Receives `(item, triggerText)`, returns HTML string.
131
+ * @property {function({key: string, [x: string]: any}, string): (string|Element|{tag: string, attrs?: Object, text?: string})} [onSelect] - Custom selection handler. Returns:
132
+ * - `string`: inserted as text node
133
+ * - `Element`: inserted as-is
134
+ * - `{tag, attrs, text}`: creates element via `dom.utils.createElement`
135
+ */
136
+ /**
137
+ * @typedef {Object} AutocompletePluginOptions
138
+ * @property {number} [delayTime=120] - Debounce delay in ms before processing input.
139
+ * @property {number} [limitSize=5] - Maximum number of items to display in the dropdown.
140
+ * @property {number} [searchStartLength=0] - Minimum input length before triggering search.
141
+ * @property {boolean} [useCachingData=true] - Whether to cache query responses per trigger.
142
+ * @property {boolean} [useCachingFieldData=true] - Whether to cache selected items for priority display.
143
+ * @property {Object<string, AutocompleteTriggerConfig>} triggers - Per-trigger configurations keyed by trigger character.
144
+ * ```js
145
+ * // triggers
146
+ * {
147
+ * '@': { data: [...], renderItem: (item) => `...` },
148
+ * '#': { apiUrl: '/api/tags?q={key}' }
149
+ * }
150
+ * ```
151
+ */
152
+ /**
153
+ * @class
154
+ * @description Autocomplete Plugin
155
+ * - A generic autocomplete plugin supporting multiple trigger characters.
156
+ * - Each trigger can have its own data source, rendering, and selection behavior.
157
+ * - Supports static data arrays and API-based data fetching.
158
+ * - Uses per-trigger caching for optimized performance.
159
+ */
160
+ declare class Autocomplete extends PluginField {
161
+ /**
162
+ * @constructor
163
+ * @param {SunEditor.Kernel} kernel - The Kernel instance
164
+ * @param {AutocompletePluginOptions} pluginOptions
165
+ */
166
+ constructor(kernel: SunEditor.Kernel, pluginOptions: AutocompletePluginOptions);
167
+ title: any;
168
+ triggerContexts: Map<any, any>;
169
+ sortedTriggers: any[];
170
+ selectMenu: SelectMenu;
171
+ controller: Controller;
172
+ onInput(params: SunEditor.HookParams.InputWithData): Promise<void>;
173
+ #private;
174
+ }
175
+ import { PluginField } from '../../interfaces';
176
+ import { Controller } from '../../modules/contract';
177
+ import { SelectMenu } from '../../modules/ui';
@@ -6,7 +6,7 @@ declare namespace _default {
6
6
  export { fileUpload };
7
7
  export { list_bulleted };
8
8
  export { list_numbered };
9
- export { mention };
9
+ export { autocomplete };
10
10
  export { align };
11
11
  export { font };
12
12
  export { fontColor };
@@ -61,7 +61,7 @@ import paragraphStyle from './dropdown/paragraphStyle';
61
61
  import table from './dropdown/table';
62
62
  import template from './dropdown/template';
63
63
  import textStyle from './dropdown/textStyle';
64
- import mention from './field/mention';
64
+ import autocomplete from './field/autocomplete';
65
65
  import fontSize from './input/fontSize';
66
66
  import pageNavigator from './input/pageNavigator';
67
67
  import audio from './modal/audio';
@@ -77,6 +77,7 @@ export {
77
77
  anchor,
78
78
  audio,
79
79
  audioGallery,
80
+ autocomplete,
80
81
  backgroundColor,
81
82
  blockquote,
82
83
  blockStyle,
@@ -100,7 +101,6 @@ export {
100
101
  list_bulleted,
101
102
  list_numbered,
102
103
  math,
103
- mention,
104
104
  pageNavigator,
105
105
  paragraphStyle,
106
106
  table,
@@ -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=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';