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.
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +1 -1
- package/src/assets/suneditor.css +53 -5
- package/src/core/editor.js +20 -3
- package/src/core/event/eventOrchestrator.js +2 -1
- package/src/core/event/handlers/handler_ww_key.js +2 -2
- package/src/core/logic/dom/html.js +23 -1
- package/src/core/logic/dom/offset.js +24 -1
- package/src/core/logic/panel/viewer.js +6 -4
- package/src/core/logic/shell/_commandExecutor.js +38 -1
- package/src/core/logic/shell/shortcuts.js +1 -1
- package/src/core/schema/options.js +1 -1
- package/src/core/section/constructor.js +3 -3
- package/src/helper/index.js +3 -0
- package/src/helper/msOffice.js +849 -0
- package/src/interfaces/plugins.js +1 -1
- package/src/langs/ckb.js +1 -0
- package/src/langs/cs.js +1 -0
- package/src/langs/da.js +1 -0
- package/src/langs/de.js +1 -0
- package/src/langs/en.js +1 -1
- package/src/langs/es.js +1 -0
- package/src/langs/fa.js +1 -0
- package/src/langs/fr.js +1 -0
- package/src/langs/he.js +1 -0
- package/src/langs/hu.js +1 -0
- package/src/langs/it.js +1 -0
- package/src/langs/ja.js +1 -0
- package/src/langs/km.js +1 -0
- package/src/langs/ko.js +1 -0
- package/src/langs/lv.js +1 -0
- package/src/langs/nl.js +1 -0
- package/src/langs/pl.js +1 -0
- package/src/langs/pt_br.js +1 -0
- package/src/langs/ro.js +1 -0
- package/src/langs/ru.js +1 -0
- package/src/langs/se.js +1 -0
- package/src/langs/tr.js +1 -0
- package/src/langs/uk.js +1 -0
- package/src/langs/ur.js +1 -0
- package/src/langs/zh_cn.js +1 -0
- package/src/modules/contract/Browser.js +99 -10
- package/src/modules/ui/ModalAnchorEditor.js +16 -1
- package/src/plugins/browser/audioGallery.js +13 -0
- package/src/plugins/browser/fileBrowser.js +22 -0
- package/src/plugins/browser/fileGallery.js +14 -0
- package/src/plugins/browser/imageGallery.js +14 -0
- package/src/plugins/browser/videoGallery.js +14 -0
- package/src/plugins/command/fileUpload.js +12 -0
- package/src/plugins/dropdown/layout.js +1 -1
- package/src/plugins/dropdown/template.js +2 -1
- package/src/plugins/field/autocomplete.js +346 -0
- package/src/plugins/index.js +3 -3
- package/src/plugins/modal/audio.js +12 -0
- package/src/plugins/modal/embed.js +12 -0
- package/src/plugins/modal/image/index.js +12 -0
- package/src/plugins/modal/link.js +12 -0
- package/src/plugins/modal/video/index.js +12 -0
- package/src/typedef.js +1 -1
- package/types/core/logic/shell/shortcuts.d.ts +2 -2
- package/types/core/schema/options.d.ts +2 -2
- package/types/helper/index.d.ts +4 -0
- package/types/helper/msOffice.d.ts +11 -0
- package/types/interfaces/plugins.d.ts +1 -1
- package/types/langs/_Lang.d.ts +1 -1
- package/types/modules/contract/Browser.d.ts +32 -0
- package/types/modules/ui/ModalAnchorEditor.d.ts +32 -2
- package/types/plugins/browser/audioGallery.d.ts +26 -0
- package/types/plugins/browser/fileBrowser.d.ts +45 -0
- package/types/plugins/browser/fileGallery.d.ts +28 -0
- package/types/plugins/browser/imageGallery.d.ts +28 -0
- package/types/plugins/browser/videoGallery.d.ts +28 -0
- package/types/plugins/command/fileUpload.d.ts +24 -0
- package/types/plugins/field/autocomplete.d.ts +177 -0
- package/types/plugins/index.d.ts +3 -3
- package/types/plugins/modal/audio.d.ts +24 -0
- package/types/plugins/modal/embed.d.ts +24 -0
- package/types/plugins/modal/image/index.d.ts +24 -0
- package/types/plugins/modal/link.d.ts +28 -1
- package/types/plugins/modal/video/index.d.ts +24 -0
- package/types/typedef.d.ts +1 -1
- package/src/plugins/field/mention.js +0 -251
- package/types/plugins/field/mention.d.ts +0 -104
|
@@ -0,0 +1,346 @@
|
|
|
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
|
+
* @description Default render function for dropdown items.
|
|
11
|
+
* @param {{key: string, name?: string}} item - The data item.
|
|
12
|
+
* @returns {string} HTML string for the dropdown item.
|
|
13
|
+
*/
|
|
14
|
+
function defaultRenderItem(item) {
|
|
15
|
+
return `<div class="se-autocomplete-item"><span>${item.key}</span>${item.name ? `<span>${item.name}</span>` : ''}</div>`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @description Default select handler. Creates a span element with the trigger text + key.
|
|
20
|
+
* @param {{key: string}} item - The selected data item.
|
|
21
|
+
* @param {string} triggerText - The trigger character.
|
|
22
|
+
* @returns {{tag: string, attrs: Object, text: string}} Descriptor for element creation.
|
|
23
|
+
*/
|
|
24
|
+
function defaultOnSelect(item, triggerText) {
|
|
25
|
+
return {
|
|
26
|
+
tag: 'span',
|
|
27
|
+
attrs: { 'data-se-autocomplete': triggerText + item.key },
|
|
28
|
+
text: triggerText + item.key,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} AutocompleteTriggerConfig
|
|
34
|
+
* @property {Array<{key: string, [x: string]: any}>} [data] - Static data array. Each item must have a `key` field. Mutually exclusive with `apiUrl`.
|
|
35
|
+
* ```js
|
|
36
|
+
* // data
|
|
37
|
+
* [{ key: 'john', name: 'John Doe', url: '/users/john' }]
|
|
38
|
+
* ```
|
|
39
|
+
* @property {string} [apiUrl] - API endpoint URL. Supports `{key}` and `{limitSize}` placeholders. Mutually exclusive with `data`.
|
|
40
|
+
* @property {Object<string, string>} [apiHeaders] - HTTP headers for the API request.
|
|
41
|
+
* @property {function(Object, XMLHttpRequest): Array<{key: string}>} [transformResponse] - Transforms parsed JSON response into an array of data items.
|
|
42
|
+
* @property {number} [limitSize] - Override global `limitSize` for this trigger.
|
|
43
|
+
* @property {number} [searchStartLength] - Override global `searchStartLength` for this trigger.
|
|
44
|
+
* @property {boolean} [useCachingData] - Override global `useCachingData` for this trigger.
|
|
45
|
+
* @property {boolean} [useCachingFieldData] - Override global `useCachingFieldData` for this trigger.
|
|
46
|
+
* @property {function({key: string, [x: string]: any}, string): string} [renderItem] - Custom dropdown item renderer. Receives `(item, triggerText)`, returns HTML string.
|
|
47
|
+
* @property {function({key: string, [x: string]: any}, string): (string|Element|{tag: string, attrs?: Object, text?: string})} [onSelect] - Custom selection handler. Returns:
|
|
48
|
+
* - `string`: inserted as text node
|
|
49
|
+
* - `Element`: inserted as-is
|
|
50
|
+
* - `{tag, attrs, text}`: creates element via `dom.utils.createElement`
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {Object} AutocompletePluginOptions
|
|
55
|
+
* @property {number} [delayTime=120] - Debounce delay in ms before processing input.
|
|
56
|
+
* @property {number} [limitSize=5] - Maximum number of items to display in the dropdown.
|
|
57
|
+
* @property {number} [searchStartLength=0] - Minimum input length before triggering search.
|
|
58
|
+
* @property {boolean} [useCachingData=true] - Whether to cache query responses per trigger.
|
|
59
|
+
* @property {boolean} [useCachingFieldData=true] - Whether to cache selected items for priority display.
|
|
60
|
+
* @property {Object<string, AutocompleteTriggerConfig>} triggers - Per-trigger configurations keyed by trigger character.
|
|
61
|
+
* ```js
|
|
62
|
+
* // triggers
|
|
63
|
+
* {
|
|
64
|
+
* '@': { data: [...], renderItem: (item) => `...` },
|
|
65
|
+
* '#': { apiUrl: '/api/tags?q={key}' }
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @class
|
|
72
|
+
* @description Autocomplete Plugin
|
|
73
|
+
* - A generic autocomplete plugin supporting multiple trigger characters.
|
|
74
|
+
* - Each trigger can have its own data source, rendering, and selection behavior.
|
|
75
|
+
* - Supports static data arrays and API-based data fetching.
|
|
76
|
+
* - Uses per-trigger caching for optimized performance.
|
|
77
|
+
*/
|
|
78
|
+
class Autocomplete extends PluginField {
|
|
79
|
+
static key = 'autocomplete';
|
|
80
|
+
static className = '';
|
|
81
|
+
|
|
82
|
+
#lastTriggerPos = 0;
|
|
83
|
+
#anchorOffset = 0;
|
|
84
|
+
#anchorNode = null;
|
|
85
|
+
#activeTrigger = null;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @constructor
|
|
89
|
+
* @param {SunEditor.Kernel} kernel - The Kernel instance
|
|
90
|
+
* @param {AutocompletePluginOptions} pluginOptions
|
|
91
|
+
*/
|
|
92
|
+
constructor(kernel, pluginOptions) {
|
|
93
|
+
super(kernel);
|
|
94
|
+
this.title = this.$.lang.autocomplete;
|
|
95
|
+
this.icon = 'autocomplete';
|
|
96
|
+
|
|
97
|
+
// global defaults
|
|
98
|
+
const limitSize = pluginOptions.limitSize || 5;
|
|
99
|
+
const searchStartLength = pluginOptions.searchStartLength || 0;
|
|
100
|
+
const delayTime = typeof pluginOptions.delayTime === 'number' ? pluginOptions.delayTime : 120;
|
|
101
|
+
const useCachingData = pluginOptions.useCachingData ?? true;
|
|
102
|
+
const useCachingFieldData = pluginOptions.useCachingFieldData ?? true;
|
|
103
|
+
|
|
104
|
+
// build trigger contexts
|
|
105
|
+
this.triggerContexts = new Map();
|
|
106
|
+
const triggers = pluginOptions.triggers || {};
|
|
107
|
+
for (const [triggerChar, config] of Object.entries(triggers)) {
|
|
108
|
+
const triggerLimit = config.limitSize ?? limitSize;
|
|
109
|
+
this.triggerContexts.set(triggerChar, {
|
|
110
|
+
trigger: triggerChar,
|
|
111
|
+
limitSize: triggerLimit,
|
|
112
|
+
searchStartLength: config.searchStartLength ?? searchStartLength,
|
|
113
|
+
directData: config.data || null,
|
|
114
|
+
apiUrl: config.apiUrl?.replace(/\s/g, '').replace(/\{limitSize\}/i, String(triggerLimit)) || '',
|
|
115
|
+
apiHeaders: config.apiHeaders || null,
|
|
116
|
+
transformResponse: config.transformResponse || null,
|
|
117
|
+
renderItem: config.renderItem || defaultRenderItem,
|
|
118
|
+
onSelect: config.onSelect || defaultOnSelect,
|
|
119
|
+
apiManager: config.apiUrl ? new ApiManager(this, this.$, { headers: config.apiHeaders }) : null,
|
|
120
|
+
cachingData: (config.useCachingData ?? useCachingData) ? new Map() : null,
|
|
121
|
+
cachingFieldData: (config.useCachingFieldData ?? useCachingFieldData) ? [] : null,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// sort triggers by length descending (longest match first)
|
|
126
|
+
this.sortedTriggers = [...this.triggerContexts.keys()].sort((a, b) => b.length - a.length);
|
|
127
|
+
|
|
128
|
+
// controller
|
|
129
|
+
const controllerEl = CreateHTML_controller();
|
|
130
|
+
this.selectMenu = new SelectMenu(this.$, { position: 'right-bottom', dir: 'ltr', closeMethod: () => this.controller.close() });
|
|
131
|
+
this.controller = new Controller(
|
|
132
|
+
this,
|
|
133
|
+
this.$,
|
|
134
|
+
controllerEl,
|
|
135
|
+
{
|
|
136
|
+
position: 'bottom',
|
|
137
|
+
initMethod: () => {
|
|
138
|
+
this.#cancelActiveApi();
|
|
139
|
+
this.selectMenu.close();
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
null,
|
|
143
|
+
);
|
|
144
|
+
this.selectMenu.on(controllerEl.firstElementChild, this.#onSelectItem.bind(this));
|
|
145
|
+
|
|
146
|
+
// onInput debounce
|
|
147
|
+
this.onInput = debounce(this.onInput.bind(this), delayTime);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @description Cancels the active trigger's in-flight API request.
|
|
152
|
+
*/
|
|
153
|
+
#cancelActiveApi() {
|
|
154
|
+
if (this.#activeTrigger?.apiManager) {
|
|
155
|
+
this.#activeTrigger.apiManager.cancel();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @hook Editor.EventManager
|
|
161
|
+
* @type {SunEditor.Hook.Event.OnInputAsync}
|
|
162
|
+
*/
|
|
163
|
+
async onInput() {
|
|
164
|
+
this.#cancelActiveApi();
|
|
165
|
+
|
|
166
|
+
const sel = this.$.selection.get();
|
|
167
|
+
if (!sel.rangeCount) {
|
|
168
|
+
this.selectMenu.close();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const anchorNode = sel.anchorNode;
|
|
173
|
+
const anchorOffset = sel.anchorOffset;
|
|
174
|
+
const textBeforeCursor = anchorNode.textContent.substring(0, anchorOffset);
|
|
175
|
+
|
|
176
|
+
// find matching trigger (longest first)
|
|
177
|
+
for (const trigger of this.sortedTriggers) {
|
|
178
|
+
const lastPos = textBeforeCursor.lastIndexOf(trigger);
|
|
179
|
+
if (lastPos === -1) continue;
|
|
180
|
+
|
|
181
|
+
const query = textBeforeCursor.substring(lastPos + trigger.length, anchorOffset);
|
|
182
|
+
const beforeText = textBeforeCursor[lastPos - 1]?.trim();
|
|
183
|
+
|
|
184
|
+
if (!/\s/.test(query) && (!beforeText || dom.check.isZeroWidth(beforeText))) {
|
|
185
|
+
const ctx = this.triggerContexts.get(trigger);
|
|
186
|
+
if (query.length < ctx.searchStartLength) return;
|
|
187
|
+
|
|
188
|
+
const anchorParent = anchorNode.parentNode;
|
|
189
|
+
if (dom.check.isAnchor(anchorParent) && !anchorParent.getAttribute('data-se-autocomplete')) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
this.#activeTrigger = ctx;
|
|
195
|
+
await this.#createList(ctx, query, anchorNode);
|
|
196
|
+
this.#lastTriggerPos = lastPos;
|
|
197
|
+
this.#anchorNode = anchorNode;
|
|
198
|
+
this.#anchorOffset = anchorOffset;
|
|
199
|
+
return;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.warn('[SUNEDITOR.autocomplete.api] ', error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.selectMenu.close();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @description Generates the autocomplete dropdown list.
|
|
213
|
+
* @param {Object} ctx - The trigger context.
|
|
214
|
+
* @param {string} value - The query text after the trigger.
|
|
215
|
+
* @param {Node} targetNode - The node where the trigger was detected.
|
|
216
|
+
* @returns {Promise<boolean>}
|
|
217
|
+
*/
|
|
218
|
+
async #createList(ctx, value, targetNode) {
|
|
219
|
+
const limit = ctx.limitSize;
|
|
220
|
+
const lowerValue = value.toLowerCase();
|
|
221
|
+
let response = null;
|
|
222
|
+
|
|
223
|
+
if (ctx.cachingData) {
|
|
224
|
+
response = ctx.cachingData.get(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!response) {
|
|
228
|
+
if (ctx.directData) {
|
|
229
|
+
response = ctx.directData.filter((item) => item.key.toLowerCase().startsWith(lowerValue)).slice(0, limit);
|
|
230
|
+
} else {
|
|
231
|
+
const xmlHttp = await ctx.apiManager.asyncCall({ method: 'GET', url: this.#createUrl(ctx, value) });
|
|
232
|
+
const json = JSON.parse(xmlHttp.responseText);
|
|
233
|
+
response = ctx.transformResponse ? ctx.transformResponse(json, xmlHttp) : json;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (ctx.cachingFieldData) {
|
|
238
|
+
const uniqueKeys = new Set();
|
|
239
|
+
response = ctx.cachingFieldData
|
|
240
|
+
.concat(response)
|
|
241
|
+
.filter(({ key }) => {
|
|
242
|
+
if (uniqueKeys.has(key)) return false;
|
|
243
|
+
uniqueKeys.add(key);
|
|
244
|
+
return key.toLowerCase().startsWith(lowerValue);
|
|
245
|
+
})
|
|
246
|
+
.slice(0, limit);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!response?.length) {
|
|
250
|
+
this.selectMenu.close();
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const list = [];
|
|
255
|
+
const menus = [];
|
|
256
|
+
for (let i = 0, len = response.length, v; i < len; i++) {
|
|
257
|
+
v = response[i];
|
|
258
|
+
list.push(v);
|
|
259
|
+
menus.push(ctx.renderItem(v, ctx.trigger));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// controller open
|
|
263
|
+
this.controller.open(targetNode, null, { isWWTarget: true, initMethod: null, addOffset: null });
|
|
264
|
+
// select menu create
|
|
265
|
+
this.selectMenu.create(list, menus);
|
|
266
|
+
this.selectMenu.open();
|
|
267
|
+
this.selectMenu.setItem(0);
|
|
268
|
+
if (ctx.cachingData) ctx.cachingData.set(value, list);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @description Constructs the API request URL with the query value.
|
|
274
|
+
* @param {Object} ctx - The trigger context.
|
|
275
|
+
* @param {string} key - The query text.
|
|
276
|
+
* @returns {string}
|
|
277
|
+
*/
|
|
278
|
+
#createUrl(ctx, key) {
|
|
279
|
+
return ctx.apiUrl.replace(/\{key\}/i, key);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @description Handles item selection from the dropdown.
|
|
284
|
+
* @param {{key: string, [x: string]: any}} item - The selected data item.
|
|
285
|
+
* @returns {boolean}
|
|
286
|
+
*/
|
|
287
|
+
#onSelectItem(item) {
|
|
288
|
+
if (!item) return false;
|
|
289
|
+
|
|
290
|
+
const ctx = this.#activeTrigger;
|
|
291
|
+
if (!ctx) return false;
|
|
292
|
+
|
|
293
|
+
const result = ctx.onSelect(item, ctx.trigger);
|
|
294
|
+
let insertedNode = null;
|
|
295
|
+
|
|
296
|
+
const anchorParent = this.#anchorNode.parentNode;
|
|
297
|
+
|
|
298
|
+
if (typeof result === 'string') {
|
|
299
|
+
// plain text insertion
|
|
300
|
+
this.$.selection.setRange(this.#anchorNode, this.#lastTriggerPos, this.#anchorNode, this.#anchorOffset);
|
|
301
|
+
insertedNode = dom.utils.createTextNode(result);
|
|
302
|
+
if (!this.$.html.insertNode(insertedNode, { afterNode: null, skipCharCount: false })) return false;
|
|
303
|
+
} else {
|
|
304
|
+
// element insertion (descriptor or DOM element)
|
|
305
|
+
let element;
|
|
306
|
+
if (result?.nodeType) {
|
|
307
|
+
element = result;
|
|
308
|
+
} else if (result?.tag) {
|
|
309
|
+
element = dom.utils.createElement(result.tag.toUpperCase(), result.attrs || {}, result.text || '');
|
|
310
|
+
} else {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (anchorParent.getAttribute?.('data-se-autocomplete')) {
|
|
315
|
+
// update existing autocomplete element in-place
|
|
316
|
+
for (const attr of [...anchorParent.attributes]) anchorParent.removeAttribute(attr.name);
|
|
317
|
+
for (const attr of [...element.attributes]) anchorParent.setAttribute(attr.name, attr.value);
|
|
318
|
+
anchorParent.textContent = element.textContent;
|
|
319
|
+
insertedNode = anchorParent;
|
|
320
|
+
} else {
|
|
321
|
+
this.$.selection.setRange(this.#anchorNode, this.#lastTriggerPos, this.#anchorNode, this.#anchorOffset);
|
|
322
|
+
if (!this.$.html.insertNode(element, { afterNode: null, skipCharCount: false })) return false;
|
|
323
|
+
insertedNode = element;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.selectMenu.close();
|
|
328
|
+
|
|
329
|
+
const space = dom.utils.createTextNode('\u00A0');
|
|
330
|
+
insertedNode.parentNode.insertBefore(space, insertedNode.nextSibling);
|
|
331
|
+
this.$.selection.setRange(space, 1, space, 1);
|
|
332
|
+
|
|
333
|
+
if (ctx.cachingFieldData && !ctx.cachingFieldData.some((data) => data.key === item.key)) {
|
|
334
|
+
ctx.cachingFieldData.push(item);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @returns {HTMLElement}
|
|
341
|
+
*/
|
|
342
|
+
function CreateHTML_controller() {
|
|
343
|
+
return dom.utils.createElement('DIV', { class: 'se-controller se-empty-controller' }, '<div></div>');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export default Autocomplete;
|
package/src/plugins/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import list_bulleted from './command/list_bulleted';
|
|
|
7
7
|
import list_numbered from './command/list_numbered';
|
|
8
8
|
|
|
9
9
|
// field
|
|
10
|
-
import
|
|
10
|
+
import autocomplete from './field/autocomplete';
|
|
11
11
|
|
|
12
12
|
// dropdown
|
|
13
13
|
import align from './dropdown/align';
|
|
@@ -54,7 +54,7 @@ export {
|
|
|
54
54
|
fileUpload,
|
|
55
55
|
list_bulleted,
|
|
56
56
|
list_numbered,
|
|
57
|
-
|
|
57
|
+
autocomplete,
|
|
58
58
|
align,
|
|
59
59
|
font,
|
|
60
60
|
fontColor,
|
|
@@ -91,7 +91,7 @@ export default {
|
|
|
91
91
|
fileUpload,
|
|
92
92
|
list_bulleted,
|
|
93
93
|
list_numbered,
|
|
94
|
-
|
|
94
|
+
autocomplete,
|
|
95
95
|
align,
|
|
96
96
|
font,
|
|
97
97
|
fontColor,
|
|
@@ -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.
|
package/src/typedef.js
CHANGED
|
@@ -268,7 +268,7 @@
|
|
|
268
268
|
* @typedef {"bold"|"underline"|"italic"|"strike"|"subscript"|"superscript"|"removeFormat"|"copyFormat"|"indent"|"outdent"|"fullScreen"|"showBlocks"|"codeView"|"markdownView"|"undo"|"redo"|"preview"|"print"|"copy"|"dir"|"dir_ltr"|"dir_rtl"|"finder"|"save"|"newDocument"|"selectAll"|"pageBreak"|"pageUp"|"pageDown"|"pageNavigator"} SunEditor.UI.ButtonCommand
|
|
269
269
|
*
|
|
270
270
|
* Plugin buttons available in the toolbar
|
|
271
|
-
* @typedef {"blockquote"|"codeBlock"|"exportPDF"|"fileUpload"|"list_bulleted"|"list_numbered"|"
|
|
271
|
+
* @typedef {"blockquote"|"codeBlock"|"exportPDF"|"fileUpload"|"list_bulleted"|"list_numbered"|"autocomplete"|"align"|"font"|"fontColor"|"backgroundColor"|"list"|"table"|"blockStyle"|"hr"|"layout"|"lineHeight"|"template"|"paragraphStyle"|"textStyle"|"link"|"image"|"video"|"audio"|"embed"|"math"|"drawing"|"imageGallery"|"videoGallery"|"audioGallery"|"fileGallery"|"fileBrowser"|"fontSize"|"pageNavigator"|"anchor"} SunEditor.UI.ButtonPlugin
|
|
272
272
|
*
|
|
273
273
|
* Single button item in the toolbar (includes special controls and custom strings)
|
|
274
274
|
* @typedef {SunEditor.UI.ButtonCommand|SunEditor.UI.ButtonPlugin|SunEditor.UI.ButtonSpecial|string} SunEditor.UI.ButtonItem
|
|
@@ -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.,
|
|
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.,
|
|
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;
|
package/types/helper/index.d.ts
CHANGED
|
@@ -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: `
|
|
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
|
package/types/langs/_Lang.d.ts
CHANGED
|
@@ -84,6 +84,19 @@ export type BrowserParams = {
|
|
|
84
84
|
useSearch?: boolean;
|
|
85
85
|
/**
|
|
86
86
|
* - File server search url. Optional. Can be overridden in browser.
|
|
87
|
+
* - Requested as `searchUrl + '?keyword=' + keyword`. The server must return:
|
|
88
|
+
* ```js
|
|
89
|
+
* {
|
|
90
|
+
* "result": [
|
|
91
|
+
* {
|
|
92
|
+
* "src": "https://example.com/file.jpg",
|
|
93
|
+
* "name": "file.jpg",
|
|
94
|
+
* "thumbnail": "https://example.com/file_thumb.jpg",
|
|
95
|
+
* "tag": ["photo"]
|
|
96
|
+
* }
|
|
97
|
+
* ]
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
87
100
|
*/
|
|
88
101
|
searchUrl?: string;
|
|
89
102
|
/**
|
|
@@ -113,6 +126,10 @@ export type BrowserParams = {
|
|
|
113
126
|
* - Optional. Can be overridden in browser. Default: 4.
|
|
114
127
|
*/
|
|
115
128
|
columnSize?: number;
|
|
129
|
+
/**
|
|
130
|
+
* - Initial folder expand depth. `1` expands the first level, `Infinity` expands all. Default: `1`.
|
|
131
|
+
*/
|
|
132
|
+
expand?: number;
|
|
116
133
|
/**
|
|
117
134
|
* - Default thumbnail
|
|
118
135
|
*/
|
|
@@ -142,6 +159,19 @@ export type BrowserParams = {
|
|
|
142
159
|
* @property {(target: Node) => void} selectorHandler - Function that actions when an item is clicked. Required. Can be overridden in browser.
|
|
143
160
|
* @property {boolean} [useSearch] - Whether to use the search function. Optional. Default: `true`.
|
|
144
161
|
* @property {string} [searchUrl] - File server search url. Optional. Can be overridden in browser.
|
|
162
|
+
* - Requested as `searchUrl + '?keyword=' + keyword`. The server must return:
|
|
163
|
+
* ```js
|
|
164
|
+
* {
|
|
165
|
+
* "result": [
|
|
166
|
+
* {
|
|
167
|
+
* "src": "https://example.com/file.jpg",
|
|
168
|
+
* "name": "file.jpg",
|
|
169
|
+
* "thumbnail": "https://example.com/file_thumb.jpg",
|
|
170
|
+
* "tag": ["photo"]
|
|
171
|
+
* }
|
|
172
|
+
* ]
|
|
173
|
+
* }
|
|
174
|
+
* ```
|
|
145
175
|
* @property {Object<string, string>} [searchUrlHeader] - File server search http header. Optional. Can be overridden in browser.
|
|
146
176
|
* @property {string} [listClass] - Class name of list div. Required. Can be overridden in browser.
|
|
147
177
|
* @property {(item: BrowserFile) => string} [drawItemHandler] - Function that returns HTML string for rendering each file item. Required. Can be overridden in browser.
|
|
@@ -152,6 +182,7 @@ export type BrowserParams = {
|
|
|
152
182
|
* @property {Array<*>} [props] - `props` argument to `drawItemHandler` function. Optional. Can be overridden in browser.
|
|
153
183
|
* @property {number} [columnSize] - Number of `div.se-file-item-column` to be created.
|
|
154
184
|
* - Optional. Can be overridden in browser. Default: 4.
|
|
185
|
+
* @property {number} [expand=1] - Initial folder expand depth. `1` expands the first level, `Infinity` expands all. Default: `1`.
|
|
155
186
|
* @property {((item: BrowserFile) => string)} [thumbnail] - Default thumbnail
|
|
156
187
|
*/
|
|
157
188
|
/**
|
|
@@ -206,6 +237,7 @@ declare class Browser {
|
|
|
206
237
|
drawItemHandler: any;
|
|
207
238
|
selectorHandler: (target: Node) => void;
|
|
208
239
|
columnSize: number;
|
|
240
|
+
expand: number;
|
|
209
241
|
folderDefaultPath: string;
|
|
210
242
|
closeArrow: any;
|
|
211
243
|
openArrow: any;
|