suneditor 3.1.3 → 3.2.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-contents.min.css +1 -1
- package/dist/suneditor.min.css +28 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +2 -1
- package/src/assets/design/color.css +12 -0
- package/src/assets/design/size.css +9 -2
- package/src/assets/icons/defaultIcons.js +96 -47
- package/src/assets/suneditor-contents.css +3 -2
- package/src/assets/suneditor.css +480 -31
- package/src/core/config/contextProvider.js +2 -1
- package/src/core/config/eventManager.js +41 -2
- package/src/core/config/optionProvider.js +23 -2
- package/src/core/editor.js +32 -14
- package/src/core/event/actions/index.js +76 -9
- package/src/core/event/effects/common.registry.js +2 -1
- package/src/core/event/effects/keydown.registry.js +169 -33
- package/src/core/event/effects/ruleHelpers.js +20 -6
- package/src/core/event/eventOrchestrator.js +136 -23
- package/src/core/event/handlers/handler_toolbar.js +4 -2
- package/src/core/event/handlers/handler_ww_dragDrop.js +10 -0
- package/src/core/event/handlers/handler_ww_input.js +9 -2
- package/src/core/event/handlers/handler_ww_key.js +86 -17
- package/src/core/event/handlers/handler_ww_mouse.js +45 -8
- package/src/core/event/ports.js +23 -5
- package/src/core/event/reducers/keydown.reducer.js +15 -2
- package/src/core/event/rules/keydown.rule.arrow.js +12 -2
- package/src/core/event/rules/keydown.rule.backspace.js +110 -15
- package/src/core/event/rules/keydown.rule.delete.js +62 -7
- package/src/core/event/rules/keydown.rule.enter.js +62 -31
- package/src/core/event/support/defaultLineManager.js +9 -2
- package/src/core/event/support/selectionState.js +13 -3
- package/src/core/kernel/coreKernel.js +1 -0
- package/src/core/logic/dom/char.js +13 -3
- package/src/core/logic/dom/format.js +171 -39
- package/src/core/logic/dom/html.js +357 -87
- package/src/core/logic/dom/inline.js +229 -34
- package/src/core/logic/dom/listFormat.js +111 -20
- package/src/core/logic/dom/nodeTransform.js +36 -11
- package/src/core/logic/dom/offset.js +116 -31
- package/src/core/logic/dom/selection.js +147 -29
- package/src/core/logic/panel/blockHandle.js +785 -0
- package/src/core/logic/panel/blockResolver.js +278 -0
- package/src/core/logic/panel/finder.js +16 -6
- package/src/core/logic/panel/menu.js +119 -18
- package/src/core/logic/panel/toolbar.js +75 -19
- package/src/core/logic/panel/viewer.js +76 -20
- package/src/core/logic/shell/_commandExecutor.js +44 -9
- package/src/core/logic/shell/commandDispatcher.js +46 -4
- package/src/core/logic/shell/component.js +102 -25
- package/src/core/logic/shell/focusManager.js +13 -3
- package/src/core/logic/shell/history.js +21 -6
- package/src/core/logic/shell/pluginManager.js +21 -13
- package/src/core/logic/shell/shortcuts.js +9 -1
- package/src/core/logic/shell/ui.js +139 -37
- package/src/core/schema/frameContext.js +34 -3
- package/src/core/schema/options.js +61 -19
- package/src/core/section/constructor.js +537 -103
- package/src/core/section/documentType.js +35 -6
- package/src/helper/converter.js +24 -7
- package/src/helper/dom/domCheck.js +25 -5
- package/src/helper/dom/domQuery.js +2 -1
- package/src/helper/dom/domUtils.js +17 -4
- package/src/helper/env.js +11 -2
- package/src/helper/keyCodeMap.js +6 -2
- package/src/helper/markdown.js +24 -5
- package/src/helper/msOffice.js +9 -2
- package/src/interfaces/plugins.js +1 -1
- package/src/langs/ckb.js +0 -1
- package/src/langs/cs.js +0 -1
- package/src/langs/da.js +0 -1
- package/src/langs/de.js +0 -1
- package/src/langs/en.js +0 -1
- package/src/langs/es.js +0 -1
- package/src/langs/fa.js +0 -1
- package/src/langs/fr.js +0 -1
- package/src/langs/he.js +0 -1
- package/src/langs/hu.js +0 -1
- package/src/langs/index.js +27 -1
- package/src/langs/it.js +0 -1
- package/src/langs/ja.js +0 -1
- package/src/langs/km.js +0 -1
- package/src/langs/ko.js +0 -1
- package/src/langs/lv.js +0 -1
- package/src/langs/nl.js +0 -1
- package/src/langs/pl.js +0 -1
- package/src/langs/pt_br.js +0 -1
- package/src/langs/ro.js +0 -1
- package/src/langs/ru.js +0 -1
- package/src/langs/se.js +0 -1
- package/src/langs/tr.js +0 -1
- package/src/langs/uk.js +0 -1
- package/src/langs/ur.js +0 -1
- package/src/langs/zh_cn.js +0 -1
- package/src/modules/contract/Browser.js +68 -23
- package/src/modules/contract/ColorPicker.js +12 -4
- package/src/modules/contract/Controller.js +37 -7
- package/src/modules/contract/Figure.js +159 -36
- package/src/modules/contract/HueSlider.js +27 -4
- package/src/modules/contract/Modal.js +62 -11
- package/src/modules/manager/FileManager.js +50 -8
- package/src/modules/ui/CommandMenu.js +597 -0
- package/src/modules/ui/ModalAnchorEditor.js +41 -9
- package/src/modules/ui/SelectMenu.js +481 -36
- package/src/modules/ui/index.js +1 -0
- package/src/plugins/browser/audioGallery.js +8 -1
- package/src/plugins/browser/fileBrowser.js +15 -2
- package/src/plugins/browser/fileGallery.js +8 -1
- package/src/plugins/browser/imageGallery.js +24 -3
- package/src/plugins/browser/videoGallery.js +19 -4
- package/src/plugins/command/blockquote.js +6 -1
- package/src/plugins/command/codeBlock.js +43 -5
- package/src/plugins/command/exportPDF.js +20 -4
- package/src/plugins/command/fileUpload.js +46 -11
- package/src/plugins/command/list_bulleted.js +6 -1
- package/src/plugins/command/list_numbered.js +6 -1
- package/src/plugins/dropdown/align.js +21 -33
- package/src/plugins/dropdown/backgroundColor.js +10 -2
- package/src/plugins/dropdown/blockStyle.js +17 -22
- package/src/plugins/dropdown/font.js +34 -34
- package/src/plugins/dropdown/hr.js +15 -43
- package/src/plugins/dropdown/layout.js +10 -27
- package/src/plugins/dropdown/lineHeight.js +10 -33
- package/src/plugins/dropdown/list.js +19 -25
- package/src/plugins/dropdown/paragraphStyle.js +16 -24
- package/src/plugins/dropdown/table/index.js +72 -17
- package/src/plugins/dropdown/table/render/table.html.js +3 -1
- package/src/plugins/dropdown/table/services/table.cell.js +33 -7
- package/src/plugins/dropdown/table/services/table.clipboard.js +5 -1
- package/src/plugins/dropdown/table/services/table.grid.js +40 -9
- package/src/plugins/dropdown/table/services/table.resize.js +55 -11
- package/src/plugins/dropdown/table/services/table.selection.js +27 -7
- package/src/plugins/dropdown/table/services/table.style.js +120 -20
- package/src/plugins/dropdown/template.js +13 -29
- package/src/plugins/dropdown/textStyle.js +22 -35
- package/src/plugins/field/autocomplete.js +8 -4
- package/src/plugins/field/slashCommand.js +297 -0
- package/src/plugins/index.js +3 -0
- package/src/plugins/input/fontSize.js +37 -8
- package/src/plugins/input/pageNavigator.js +7 -1
- package/src/plugins/modal/audio.js +71 -17
- package/src/plugins/modal/drawing.js +41 -11
- package/src/plugins/modal/embed.js +132 -22
- package/src/plugins/modal/image/index.js +115 -28
- package/src/plugins/modal/image/services/image.size.js +9 -2
- package/src/plugins/modal/image/services/image.upload.js +38 -4
- package/src/plugins/modal/link.js +9 -2
- package/src/plugins/modal/math.js +10 -2
- package/src/plugins/modal/video/index.js +112 -26
- package/src/plugins/modal/video/render/video.html.js +4 -1
- package/src/plugins/modal/video/services/video.size.js +15 -4
- package/src/plugins/modal/video/services/video.upload.js +10 -2
- package/src/plugins/popup/anchor.js +14 -2
- package/src/suneditor.js +8 -2
- package/src/themes/cobalt.css +12 -0
- package/src/themes/cream.css +12 -0
- package/src/themes/dark.css +12 -0
- package/src/themes/midnight.css +12 -0
- package/src/typedef.js +32 -21
- package/types/assets/icons/defaultIcons.d.ts +1 -0
- package/types/core/config/contextProvider.d.ts +7 -1
- package/types/core/config/eventManager.d.ts +37 -4
- package/types/core/config/optionProvider.d.ts +5 -1
- package/types/core/event/actions/index.d.ts +44 -6
- package/types/core/event/effects/keydown.registry.d.ts +37 -7
- package/types/core/event/effects/ruleHelpers.d.ts +6 -1
- package/types/core/event/eventOrchestrator.d.ts +6 -1
- package/types/core/event/handlers/handler_ww_clipboard.d.ts +15 -3
- package/types/core/event/handlers/handler_ww_dragDrop.d.ts +14 -2
- package/types/core/event/handlers/handler_ww_input.d.ts +10 -2
- package/types/core/event/handlers/handler_ww_key.d.ts +10 -2
- package/types/core/event/handlers/handler_ww_mouse.d.ts +25 -5
- package/types/core/event/ports.d.ts +7 -2
- package/types/core/event/rules/keydown.rule.backspace.d.ts +6 -1
- package/types/core/kernel/store.d.ts +4 -1
- package/types/core/logic/dom/format.d.ts +32 -14
- package/types/core/logic/dom/html.d.ts +11 -3
- package/types/core/logic/dom/nodeTransform.d.ts +6 -6
- package/types/core/logic/panel/blockHandle.d.ts +64 -0
- package/types/core/logic/panel/blockResolver.d.ts +50 -0
- package/types/core/logic/panel/menu.d.ts +72 -3
- package/types/core/logic/shell/commandDispatcher.d.ts +8 -0
- package/types/core/logic/shell/component.d.ts +5 -3
- package/types/core/logic/shell/shortcuts.d.ts +10 -1
- package/types/core/logic/shell/ui.d.ts +7 -1
- package/types/core/schema/context.d.ts +7 -1
- package/types/core/schema/frameContext.d.ts +36 -4
- package/types/core/schema/options.d.ts +110 -37
- package/types/core/section/constructor.d.ts +38 -1
- package/types/events.d.ts +208 -32
- package/types/helper/converter.d.ts +4 -2
- package/types/helper/dom/domCheck.d.ts +12 -2
- package/types/helper/dom/domQuery.d.ts +35 -7
- package/types/helper/dom/domUtils.d.ts +4 -1
- package/types/interfaces/plugins.d.ts +2 -2
- package/types/langs/_Lang.d.ts +0 -1
- package/types/modules/contract/Browser.d.ts +7 -7
- package/types/modules/contract/ColorPicker.d.ts +2 -2
- package/types/modules/contract/Controller.d.ts +7 -0
- package/types/modules/contract/Figure.d.ts +31 -4
- package/types/modules/ui/CommandMenu.d.ts +262 -0
- package/types/modules/ui/ModalAnchorEditor.d.ts +6 -1
- package/types/modules/ui/SelectMenu.d.ts +56 -3
- package/types/modules/ui/index.d.ts +1 -0
- package/types/plugins/browser/audioGallery.d.ts +17 -1
- package/types/plugins/browser/fileBrowser.d.ts +17 -1
- package/types/plugins/browser/fileGallery.d.ts +17 -1
- package/types/plugins/browser/imageGallery.d.ts +17 -1
- package/types/plugins/browser/videoGallery.d.ts +17 -1
- package/types/plugins/dropdown/align.d.ts +1 -1
- package/types/plugins/dropdown/blockStyle.d.ts +3 -1
- package/types/plugins/dropdown/table/index.d.ts +4 -1
- package/types/plugins/dropdown/table/services/table.clipboard.d.ts +5 -1
- package/types/plugins/dropdown/table/services/table.grid.d.ts +10 -2
- package/types/plugins/field/autocomplete.d.ts +0 -1
- package/types/plugins/field/slashCommand.d.ts +165 -0
- package/types/plugins/index.d.ts +3 -0
- package/types/plugins/modal/embed.d.ts +34 -0
- package/types/plugins/modal/link.d.ts +4 -1
- package/types/typedef.d.ts +48 -23
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { PluginField } from '../../interfaces';
|
|
2
|
+
import { Controller } from '../../modules/contract';
|
|
3
|
+
import { CommandMenu } from '../../modules/ui';
|
|
4
|
+
import { dom, converter, keyCodeMap } from '../../helper';
|
|
5
|
+
import { ResolveButton } from '../../core/section/constructor';
|
|
6
|
+
|
|
7
|
+
const { debounce } = converter;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} SlashCommandItem
|
|
11
|
+
* @property {string} key - Unique identifier for the item.
|
|
12
|
+
* @property {string} title - Display label.
|
|
13
|
+
* @property {string} [icon] - Icon key from `$.icons` (e.g. `'h1'`) or a raw HTML string (e.g. `'<svg>...</svg>'`).
|
|
14
|
+
* @property {string[]} [keywords] - Extra search terms used in addition to the title.
|
|
15
|
+
* @property {function(SunEditor.Deps, SlashCommandActionContext): void | Promise<void>} action - Invoked when the item is selected.
|
|
16
|
+
* The trigger string (e.g. `/hea`) is already removed from the editor and the caret restored to that position before `action` runs.
|
|
17
|
+
* The action owns its history entry — calling an L3 wrapper (`$.html.*`, `$.format.*`, `$.inline.apply`) auto-pushes; otherwise call `$.history.push(false)` manually.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} SlashCommandActionContext
|
|
22
|
+
* @property {string} triggerChar - The configured trigger character (e.g. `'/'`).
|
|
23
|
+
* @property {string} query - The text the user typed after the trigger (e.g. `'hea'`).
|
|
24
|
+
* @property {SlashCommandItem} item - The selected item.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} SlashCommandPluginOptions
|
|
29
|
+
* @property {string} [triggerChar='/'] - Character that opens the command menu. Single character recommended.
|
|
30
|
+
* @property {Array<SlashCommandItem | string>} items - Menu entries. Strings resolve via `ResolveButton`
|
|
31
|
+
* (plugin names, built-in commands like `'bold'`); objects are custom items with their own `action`.
|
|
32
|
+
* Required.
|
|
33
|
+
* @property {number} [delayTime=120] - Debounce delay (ms) before the input is inspected for the trigger.
|
|
34
|
+
* @property {number} [limitSize=10] - Maximum number of items shown in the dropdown.
|
|
35
|
+
* @property {string} [emptyMessage] - Message shown when no items match the query. If unset, the menu closes on no match.
|
|
36
|
+
* @property {function(SlashCommandItem, { icons: Object }): string} [renderItem] - Custom item HTML renderer.
|
|
37
|
+
* Applied only to custom item objects; plugin-name entries always render with the canonical BlockHandle row.
|
|
38
|
+
* ```js
|
|
39
|
+
* SUNEDITOR.create('#editor', {
|
|
40
|
+
* plugins: [slashCommand],
|
|
41
|
+
* slashCommand: {
|
|
42
|
+
* triggerChar: '/',
|
|
43
|
+
* items: [
|
|
44
|
+
* {
|
|
45
|
+
* key: 'h1',
|
|
46
|
+
* title: 'Heading 1',
|
|
47
|
+
* icon: 'h1',
|
|
48
|
+
* keywords: ['header', 'title'],
|
|
49
|
+
* action: ($) => $.format.applyBlock(document.createElement('H1')),
|
|
50
|
+
* },
|
|
51
|
+
* 'bold',
|
|
52
|
+
* 'image',
|
|
53
|
+
* 'blockStyle',
|
|
54
|
+
* ],
|
|
55
|
+
* },
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @class
|
|
62
|
+
* @description Slash Command plugin
|
|
63
|
+
* - Notion / Tiptap style "/" command menu. Triggered by the configured character (default `'/'`).
|
|
64
|
+
* - Menu rendering + dispatch is delegated to {@link CommandMenu}, which is also used by BlockHandle's
|
|
65
|
+
* action menu — both menus share the exact same row HTML and dispatch behavior.
|
|
66
|
+
* - SlashCommand-specific concerns kept here: input watching, debounce, trigger detection,
|
|
67
|
+
* trigger text deletion before dispatch, empty-state row.
|
|
68
|
+
*/
|
|
69
|
+
class SlashCommand extends PluginField {
|
|
70
|
+
static key = 'slashCommand';
|
|
71
|
+
static className = '';
|
|
72
|
+
|
|
73
|
+
#triggerChar;
|
|
74
|
+
#limitSize;
|
|
75
|
+
#emptyMessage;
|
|
76
|
+
|
|
77
|
+
/** @type {CommandMenu} */
|
|
78
|
+
#menu;
|
|
79
|
+
|
|
80
|
+
#lastTriggerPos = 0;
|
|
81
|
+
#anchorOffset = 0;
|
|
82
|
+
#anchorNode = null;
|
|
83
|
+
#internalClose = false;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @constructor
|
|
87
|
+
* @param {SunEditor.Kernel} kernel
|
|
88
|
+
* @param {SlashCommandPluginOptions} pluginOptions
|
|
89
|
+
*/
|
|
90
|
+
constructor(kernel, pluginOptions) {
|
|
91
|
+
super(kernel);
|
|
92
|
+
|
|
93
|
+
this.#triggerChar =
|
|
94
|
+
typeof pluginOptions.triggerChar === 'string' && pluginOptions.triggerChar.length > 0
|
|
95
|
+
? pluginOptions.triggerChar
|
|
96
|
+
: '/';
|
|
97
|
+
this.#limitSize =
|
|
98
|
+
typeof pluginOptions.limitSize === 'number' && pluginOptions.limitSize > 0 ? pluginOptions.limitSize : 10;
|
|
99
|
+
this.#emptyMessage = typeof pluginOptions.emptyMessage === 'string' ? pluginOptions.emptyMessage : '';
|
|
100
|
+
const delayTime = typeof pluginOptions.delayTime === 'number' ? pluginOptions.delayTime : 120;
|
|
101
|
+
|
|
102
|
+
this.#menu = new CommandMenu(this, this.$, {
|
|
103
|
+
items: Array.isArray(pluginOptions.items) ? pluginOptions.items : [],
|
|
104
|
+
resolveButton: ResolveButton,
|
|
105
|
+
renderCustomItem: typeof pluginOptions.renderItem === 'function' ? pluginOptions.renderItem : null,
|
|
106
|
+
selectMenuParams: {
|
|
107
|
+
position: 'bottom-left',
|
|
108
|
+
dir: 'ltr',
|
|
109
|
+
minWidth: '200px',
|
|
110
|
+
maxHeight: '320px',
|
|
111
|
+
closeMethod: () => this.#onMenuClose(),
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const controllerEl = CreateHTML_controller();
|
|
116
|
+
this.controller = new Controller(
|
|
117
|
+
this,
|
|
118
|
+
this.$,
|
|
119
|
+
controllerEl,
|
|
120
|
+
{
|
|
121
|
+
position: 'bottom',
|
|
122
|
+
escGuard: () => this.#menu.hasOpenSubPanel(),
|
|
123
|
+
},
|
|
124
|
+
null,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
this.#menu.attach(/** @type {HTMLElement} */ (controllerEl.firstElementChild), this.#onSelectItem.bind(this), {
|
|
128
|
+
class: 'se-block-action-menu se-slash-command-menu',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
this.onInput = debounce(this.onInput.bind(this), delayTime);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @hook Editor.EventManager
|
|
136
|
+
* @description ESC while the menu is still pending must not let that scheduled `onInput` re-open the menu after the dismiss.
|
|
137
|
+
* @type {SunEditor.Hook.Event.OnKeyDown}
|
|
138
|
+
* @param {SunEditor.HookParams.KeyEvent} params
|
|
139
|
+
*/
|
|
140
|
+
onKeyDown({ event }) {
|
|
141
|
+
if (keyCodeMap.isEsc(event.code)) /** @type {{ cancel?: () => void }} */ (this.onInput).cancel?.();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @hook Editor.EventManager
|
|
146
|
+
* @type {SunEditor.Hook.Event.OnInput}
|
|
147
|
+
*/
|
|
148
|
+
onInput() {
|
|
149
|
+
const items = this.#menu.getItems();
|
|
150
|
+
if (items.length === 0) return;
|
|
151
|
+
|
|
152
|
+
const sel = this.$.selection.get();
|
|
153
|
+
if (!sel.rangeCount) {
|
|
154
|
+
this.#closeMenu();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const anchorNode = sel.anchorNode;
|
|
159
|
+
const anchorOffset = sel.anchorOffset;
|
|
160
|
+
if (!anchorNode || typeof anchorNode.textContent !== 'string') {
|
|
161
|
+
this.#closeMenu();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const textBeforeCursor = anchorNode.textContent.substring(0, anchorOffset);
|
|
166
|
+
const trigger = this.#triggerChar;
|
|
167
|
+
const lastPos = textBeforeCursor.lastIndexOf(trigger);
|
|
168
|
+
if (lastPos === -1) {
|
|
169
|
+
this.#closeMenu();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const query = textBeforeCursor.substring(lastPos + trigger.length, anchorOffset);
|
|
174
|
+
// Reject when the query contains whitespace (user moved past the slash word).
|
|
175
|
+
if (/\s/.test(query)) {
|
|
176
|
+
this.#closeMenu();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Trigger must sit at the start of the line or be preceded by whitespace / zero-width.
|
|
181
|
+
const beforeChar = textBeforeCursor[lastPos - 1];
|
|
182
|
+
if (beforeChar && beforeChar.trim() !== '' && !dom.check.isZeroWidth(beforeChar)) {
|
|
183
|
+
this.#closeMenu();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const filtered = this.#menu.filter(query, this.#limitSize);
|
|
188
|
+
|
|
189
|
+
if (filtered.length === 0) {
|
|
190
|
+
if (this.#emptyMessage) {
|
|
191
|
+
this.#renderEmpty(anchorNode);
|
|
192
|
+
this.#cacheAnchor(anchorNode, lastPos, anchorOffset);
|
|
193
|
+
} else {
|
|
194
|
+
this.#closeMenu();
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.controller.open(anchorNode, null, { isWWTarget: true, initMethod: null, addOffset: null });
|
|
200
|
+
this.#menu.createRows(filtered);
|
|
201
|
+
this.#menu.open();
|
|
202
|
+
this.#menu.setItem(0);
|
|
203
|
+
this.#cacheAnchor(anchorNode, lastPos, anchorOffset);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @description Close the menu from the plugin itself (invalid query, or after a selection). Flags
|
|
208
|
+
* the close as internal so `#onMenuClose` does not treat it as a user dismiss.
|
|
209
|
+
*/
|
|
210
|
+
#closeMenu() {
|
|
211
|
+
this.#internalClose = true;
|
|
212
|
+
this.#menu.close();
|
|
213
|
+
this.#internalClose = false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @description SelectMenu `closeMethod`. On a user dismiss (ESC / outside-click) — i.e. not an
|
|
218
|
+
* internal close and not a selection — drop the typed "/query" and restore the caret to where the
|
|
219
|
+
* trigger was, leaving the editor in a clean state. Always tears down the controller.
|
|
220
|
+
*/
|
|
221
|
+
#onMenuClose() {
|
|
222
|
+
if (!this.#internalClose) this.#removeTrigger();
|
|
223
|
+
this.#anchorNode = null;
|
|
224
|
+
this.controller.close();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @description Remove the trigger + query text (`/hea`) and collapse the caret to the trigger
|
|
229
|
+
* position. No-op if the cached anchor is stale (text changed / node detached).
|
|
230
|
+
*/
|
|
231
|
+
#removeTrigger() {
|
|
232
|
+
const anchorNode = this.#anchorNode;
|
|
233
|
+
if (!anchorNode || !anchorNode.parentNode) return;
|
|
234
|
+
if (anchorNode.textContent?.[this.#lastTriggerPos] !== this.#triggerChar) return;
|
|
235
|
+
|
|
236
|
+
this.$.selection.setRange(anchorNode, this.#lastTriggerPos, anchorNode, this.#anchorOffset);
|
|
237
|
+
const range = this.$.selection.getRange();
|
|
238
|
+
if (range && !range.collapsed) this.$.html.remove();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @param {Node} anchorNode
|
|
243
|
+
* @param {number} lastPos
|
|
244
|
+
* @param {number} anchorOffset
|
|
245
|
+
*/
|
|
246
|
+
#cacheAnchor(anchorNode, lastPos, anchorOffset) {
|
|
247
|
+
this.#anchorNode = anchorNode;
|
|
248
|
+
this.#lastTriggerPos = lastPos;
|
|
249
|
+
this.#anchorOffset = anchorOffset;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @description Render the no-match row as a single non-clickable entry.
|
|
254
|
+
* @param {Node} targetNode
|
|
255
|
+
*/
|
|
256
|
+
#renderEmpty(targetNode) {
|
|
257
|
+
this.controller.open(targetNode, null, { isWWTarget: true, initMethod: null, addOffset: null });
|
|
258
|
+
this.#menu.selectMenu.create([null], [`<span class="se-slash-empty">${this.#emptyMessage}</span>`]);
|
|
259
|
+
this.#menu.open();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @description Click handler. Removes the trigger string from the editor, then hands the item
|
|
264
|
+
* to {@link CommandMenu#dispatch} — which routes to `plugin.open/show/action`,
|
|
265
|
+
* `commandDispatcher.run`, or the custom item's `action` callback.
|
|
266
|
+
* @param {import('../../modules/ui/CommandMenu.js').ResolvedItem | null} item
|
|
267
|
+
* @returns {boolean | undefined}
|
|
268
|
+
*/
|
|
269
|
+
#onSelectItem(item) {
|
|
270
|
+
if (!item) return false;
|
|
271
|
+
|
|
272
|
+
const anchorNode = this.#anchorNode;
|
|
273
|
+
if (!anchorNode) return false;
|
|
274
|
+
|
|
275
|
+
const triggerChar = this.#triggerChar;
|
|
276
|
+
const query = anchorNode.textContent.substring(this.#lastTriggerPos + triggerChar.length, this.#anchorOffset);
|
|
277
|
+
|
|
278
|
+
// Remove the trigger + query, leaving the caret at the trigger position so the action
|
|
279
|
+
// (insert block, run command, etc.) operates from a clean cursor.
|
|
280
|
+
this.$.selection.setRange(anchorNode, this.#lastTriggerPos, anchorNode, this.#anchorOffset);
|
|
281
|
+
const range = this.$.selection.getRange();
|
|
282
|
+
if (range && !range.collapsed) this.$.html.remove();
|
|
283
|
+
|
|
284
|
+
this.#closeMenu();
|
|
285
|
+
|
|
286
|
+
this.#menu.dispatch(item, { triggerChar, query, item: item.raw });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @returns {HTMLElement}
|
|
292
|
+
*/
|
|
293
|
+
function CreateHTML_controller() {
|
|
294
|
+
return dom.utils.createElement('DIV', { class: 'se-controller se-empty-controller' }, '<div></div>');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export default SlashCommand;
|
package/src/plugins/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import list_numbered from './command/list_numbered';
|
|
|
8
8
|
|
|
9
9
|
// field
|
|
10
10
|
import autocomplete from './field/autocomplete';
|
|
11
|
+
import slashCommand from './field/slashCommand';
|
|
11
12
|
|
|
12
13
|
// dropdown
|
|
13
14
|
import align from './dropdown/align';
|
|
@@ -55,6 +56,7 @@ export {
|
|
|
55
56
|
list_bulleted,
|
|
56
57
|
list_numbered,
|
|
57
58
|
autocomplete,
|
|
59
|
+
slashCommand,
|
|
58
60
|
align,
|
|
59
61
|
font,
|
|
60
62
|
fontColor,
|
|
@@ -92,6 +94,7 @@ export default {
|
|
|
92
94
|
list_bulleted,
|
|
93
95
|
list_numbered,
|
|
94
96
|
autocomplete,
|
|
97
|
+
slashCommand,
|
|
95
98
|
align,
|
|
96
99
|
font,
|
|
97
100
|
fontColor,
|
|
@@ -131,7 +131,9 @@ class FontSize extends PluginInput {
|
|
|
131
131
|
|
|
132
132
|
// create HTML
|
|
133
133
|
this.unitMap = { ...DEFAULT_UNIT_MAP, ...(pluginOptions.unitMap || {}) };
|
|
134
|
-
this.sizeUnit = /text/.test(pluginOptions.sizeUnit)
|
|
134
|
+
this.sizeUnit = /text/.test(pluginOptions.sizeUnit)
|
|
135
|
+
? ''
|
|
136
|
+
: pluginOptions.sizeUnit || this.$.options.get('fontSizeUnits')[0];
|
|
135
137
|
|
|
136
138
|
const unitMap = this.unitMap[this.sizeUnit || 'text'];
|
|
137
139
|
const menu = CreateHTML(this.$, unitMap, this.sizeUnit, pluginOptions.showDefaultSizeLabel);
|
|
@@ -152,25 +154,47 @@ class FontSize extends PluginInput {
|
|
|
152
154
|
if (showIncDec) {
|
|
153
155
|
this.beforeItem = dom.utils.createElement(
|
|
154
156
|
'button',
|
|
155
|
-
{
|
|
157
|
+
{
|
|
158
|
+
class: 'se-btn se-tooltip se-sub-btn',
|
|
159
|
+
type: 'button',
|
|
160
|
+
'data-command': FontSize.key,
|
|
161
|
+
'data-type': 'command',
|
|
162
|
+
'data-value': 'dec',
|
|
163
|
+
},
|
|
156
164
|
`${this.$.icons.minus}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.decrease}</span></span>`,
|
|
157
165
|
);
|
|
158
166
|
this.afterItem = dom.utils.createElement(
|
|
159
167
|
'button',
|
|
160
|
-
{
|
|
168
|
+
{
|
|
169
|
+
class: 'se-btn se-tooltip se-sub-btn',
|
|
170
|
+
type: 'button',
|
|
171
|
+
'data-command': FontSize.key,
|
|
172
|
+
'data-type': 'command',
|
|
173
|
+
'data-value': 'inc',
|
|
174
|
+
},
|
|
161
175
|
`${this.$.icons.plus}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.increase}</span></span>`,
|
|
162
176
|
);
|
|
163
177
|
} else if (!disableInput) {
|
|
164
178
|
this.afterItem = dom.utils.createElement(
|
|
165
179
|
'button',
|
|
166
|
-
{
|
|
180
|
+
{
|
|
181
|
+
class: 'se-btn se-tooltip se-sub-arrow-btn',
|
|
182
|
+
type: 'button',
|
|
183
|
+
'data-command': FontSize.key,
|
|
184
|
+
'data-type': 'dropdown',
|
|
185
|
+
},
|
|
167
186
|
`${this.$.icons.arrow_down}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.fontSize}</span></span>`,
|
|
168
187
|
);
|
|
169
188
|
this.$.menu.initDropdownTarget({ key: FontSize.key, type: 'dropdown' }, menu);
|
|
170
189
|
} else if (disableInput && !showIncDec) {
|
|
171
190
|
this.replaceButton = dom.utils.createElement(
|
|
172
191
|
'button',
|
|
173
|
-
{
|
|
192
|
+
{
|
|
193
|
+
class: 'se-btn se-tooltip se-btn-select se-btn-tool-font-size',
|
|
194
|
+
type: 'button',
|
|
195
|
+
'data-command': FontSize.key,
|
|
196
|
+
'data-type': 'dropdown',
|
|
197
|
+
},
|
|
174
198
|
`<span class="se-txt __se__font_size">${this.$.lang.fontSize}</span>${this.$.icons.arrow_down}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.fontSize}</span></span>`,
|
|
175
199
|
);
|
|
176
200
|
this.$.menu.initDropdownTarget({ key: FontSize.key, type: 'dropdown' }, menu);
|
|
@@ -268,7 +292,9 @@ class FontSize extends PluginInput {
|
|
|
268
292
|
const { max, min } = this.unitMap[unit];
|
|
269
293
|
value = value > max ? max : value < min ? min : value;
|
|
270
294
|
|
|
271
|
-
const newNode = dom.utils.createElement('SPAN', {
|
|
295
|
+
const newNode = dom.utils.createElement('SPAN', {
|
|
296
|
+
style: 'font-size: ' + this.#setSize(target, value + unit) + ';',
|
|
297
|
+
});
|
|
272
298
|
this.$.inline.apply(newNode, { stylesToModify: ['font-size'], nodesToRemove: null, strictRemove: null });
|
|
273
299
|
} finally {
|
|
274
300
|
this.isInputActive = false;
|
|
@@ -347,7 +373,8 @@ class FontSize extends PluginInput {
|
|
|
347
373
|
value: this.sizeUnit ? 0 : '',
|
|
348
374
|
};
|
|
349
375
|
|
|
350
|
-
const size =
|
|
376
|
+
const size =
|
|
377
|
+
typeof target === 'string' ? target : dom.check.isInputElement(target) ? target.value : target.textContent;
|
|
351
378
|
const splitValue = this.sizeUnit ? size.split(/(\d+)/) : [size, ''];
|
|
352
379
|
|
|
353
380
|
let unit = (splitValue.pop() || '').trim().toLowerCase();
|
|
@@ -375,7 +402,9 @@ class FontSize extends PluginInput {
|
|
|
375
402
|
if (dom.check.isInputElement(target)) {
|
|
376
403
|
return (target.value = String(value));
|
|
377
404
|
} else {
|
|
378
|
-
return (target.textContent = String(
|
|
405
|
+
return (target.textContent = String(
|
|
406
|
+
this.sizeUnit ? value : this.unitMap.text.list.find((v) => v.size === value)?.title || value,
|
|
407
|
+
));
|
|
379
408
|
}
|
|
380
409
|
}
|
|
381
410
|
}
|
|
@@ -67,7 +67,13 @@ class PageNavigator extends PluginInput {
|
|
|
67
67
|
* @returns {HTMLInputElement}
|
|
68
68
|
*/
|
|
69
69
|
function CreateInner() {
|
|
70
|
-
return /** @type {HTMLInputElement} */ (
|
|
70
|
+
return /** @type {HTMLInputElement} */ (
|
|
71
|
+
dom.utils.createElement(
|
|
72
|
+
'input',
|
|
73
|
+
{ type: 'number', class: 'se-not-arrow-text', placeholder: '1', value: '1', min: '1' },
|
|
74
|
+
null,
|
|
75
|
+
)
|
|
76
|
+
);
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
export default PageNavigator;
|
|
@@ -76,16 +76,30 @@ class Audio_ extends PluginModal {
|
|
|
76
76
|
|
|
77
77
|
// define plugin options
|
|
78
78
|
this.pluginOptions = {
|
|
79
|
-
defaultWidth: !pluginOptions.defaultWidth
|
|
80
|
-
|
|
79
|
+
defaultWidth: !pluginOptions.defaultWidth
|
|
80
|
+
? ''
|
|
81
|
+
: numbers.is(pluginOptions.defaultWidth)
|
|
82
|
+
? pluginOptions.defaultWidth + 'px'
|
|
83
|
+
: pluginOptions.defaultWidth,
|
|
84
|
+
defaultHeight: !pluginOptions.defaultHeight
|
|
85
|
+
? ''
|
|
86
|
+
: numbers.is(pluginOptions.defaultHeight)
|
|
87
|
+
? pluginOptions.defaultHeight + 'px'
|
|
88
|
+
: pluginOptions.defaultHeight,
|
|
81
89
|
createFileInput: !!pluginOptions.createFileInput,
|
|
82
|
-
createUrlInput:
|
|
90
|
+
createUrlInput:
|
|
91
|
+
pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput
|
|
92
|
+
? true
|
|
93
|
+
: pluginOptions.createUrlInput,
|
|
83
94
|
uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
|
|
84
95
|
uploadHeaders: pluginOptions.uploadHeaders || null,
|
|
85
96
|
uploadSizeLimit: numbers.get(pluginOptions.uploadSizeLimit, 0),
|
|
86
97
|
uploadSingleSizeLimit: numbers.get(pluginOptions.uploadSingleSizeLimit, 0),
|
|
87
98
|
allowMultiple: !!pluginOptions.allowMultiple,
|
|
88
|
-
acceptedFormats:
|
|
99
|
+
acceptedFormats:
|
|
100
|
+
typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*'
|
|
101
|
+
? 'audio/*'
|
|
102
|
+
: pluginOptions.acceptedFormats.trim() || 'audio/*',
|
|
89
103
|
audioTagAttributes: pluginOptions.audioTagAttributes || null,
|
|
90
104
|
insertBehavior: pluginOptions.insertBehavior,
|
|
91
105
|
};
|
|
@@ -124,7 +138,11 @@ class Audio_ extends PluginModal {
|
|
|
124
138
|
|
|
125
139
|
// init
|
|
126
140
|
if (this.audioInputFile) {
|
|
127
|
-
this.$.eventManager.addEvent(
|
|
141
|
+
this.$.eventManager.addEvent(
|
|
142
|
+
modalEl.querySelector('.se-modal-files-edge-button'),
|
|
143
|
+
'click',
|
|
144
|
+
this.#RemoveSelectedFiles.bind(this, this.audioUrlFile, this.preview),
|
|
145
|
+
);
|
|
128
146
|
if (this.audioUrlFile) {
|
|
129
147
|
this.$.eventManager.addEvent(this.audioInputFile, 'change', this.#FileInputChange.bind(this));
|
|
130
148
|
}
|
|
@@ -177,12 +195,15 @@ class Audio_ extends PluginModal {
|
|
|
177
195
|
*/
|
|
178
196
|
modalOn(isUpdate) {
|
|
179
197
|
if (!isUpdate) {
|
|
180
|
-
if (this.audioInputFile && this.pluginOptions.allowMultiple)
|
|
198
|
+
if (this.audioInputFile && this.pluginOptions.allowMultiple)
|
|
199
|
+
this.audioInputFile.setAttribute('multiple', 'multiple');
|
|
181
200
|
} else if (this.#element) {
|
|
182
201
|
this.#urlValue = this.preview.textContent = this.audioUrlFile.value = this.#element.src;
|
|
183
|
-
if (this.audioInputFile && this.pluginOptions.allowMultiple)
|
|
202
|
+
if (this.audioInputFile && this.pluginOptions.allowMultiple)
|
|
203
|
+
this.audioInputFile.removeAttribute('multiple');
|
|
184
204
|
} else {
|
|
185
|
-
if (this.audioInputFile && this.pluginOptions.allowMultiple)
|
|
205
|
+
if (this.audioInputFile && this.pluginOptions.allowMultiple)
|
|
206
|
+
this.audioInputFile.removeAttribute('multiple');
|
|
186
207
|
}
|
|
187
208
|
}
|
|
188
209
|
|
|
@@ -220,7 +241,8 @@ class Audio_ extends PluginModal {
|
|
|
220
241
|
controllerAction(target) {
|
|
221
242
|
switch (target.getAttribute('data-command')) {
|
|
222
243
|
case 'update':
|
|
223
|
-
if (this.audioUrlFile)
|
|
244
|
+
if (this.audioUrlFile)
|
|
245
|
+
this.#urlValue = this.preview.textContent = this.audioUrlFile.value = this.#element.src;
|
|
224
246
|
this.open();
|
|
225
247
|
break;
|
|
226
248
|
case 'copy': {
|
|
@@ -239,7 +261,13 @@ class Audio_ extends PluginModal {
|
|
|
239
261
|
* @type {SunEditor.Hook.Component.Select}
|
|
240
262
|
*/
|
|
241
263
|
componentSelect(target) {
|
|
242
|
-
this.figure.open(target, {
|
|
264
|
+
this.figure.open(target, {
|
|
265
|
+
nonResizing: true,
|
|
266
|
+
nonSizeInfo: true,
|
|
267
|
+
nonBorder: true,
|
|
268
|
+
figureTarget: true,
|
|
269
|
+
infoOnly: false,
|
|
270
|
+
});
|
|
243
271
|
this.#ready(target);
|
|
244
272
|
}
|
|
245
273
|
|
|
@@ -253,7 +281,11 @@ class Audio_ extends PluginModal {
|
|
|
253
281
|
const container = figure.container || element;
|
|
254
282
|
const focusEl = container.previousElementSibling || container.nextElementSibling;
|
|
255
283
|
|
|
256
|
-
const message = await this.$.eventManager.triggerEvent('onAudioDeleteBefore', {
|
|
284
|
+
const message = await this.$.eventManager.triggerEvent('onAudioDeleteBefore', {
|
|
285
|
+
element: element,
|
|
286
|
+
container: figure,
|
|
287
|
+
url: element.getAttribute('src'),
|
|
288
|
+
});
|
|
257
289
|
if (message === false) return;
|
|
258
290
|
|
|
259
291
|
const emptyDiv = container.parentNode;
|
|
@@ -293,7 +325,8 @@ class Audio_ extends PluginModal {
|
|
|
293
325
|
|
|
294
326
|
s = f.size;
|
|
295
327
|
if (singleSizeLimit > 0 && s > singleSizeLimit) {
|
|
296
|
-
const err =
|
|
328
|
+
const err =
|
|
329
|
+
'[SUNEDITOR.audioUpload.fail] Size of uploadable single file: ' + singleSizeLimit / 1000 + 'KB';
|
|
297
330
|
const message = await this.$.eventManager.triggerEvent('onAudioUploadError', {
|
|
298
331
|
error: err,
|
|
299
332
|
limitSize: singleSizeLimit,
|
|
@@ -313,7 +346,12 @@ class Audio_ extends PluginModal {
|
|
|
313
346
|
const limitSize = this.pluginOptions.uploadSizeLimit;
|
|
314
347
|
if (limitSize > 0 && fileSize + this.fileManager.getSize() > limitSize) {
|
|
315
348
|
const err = '[SUNEDITOR.audioUpload.fail] Size of uploadable total audios: ' + limitSize / 1000 + 'KB';
|
|
316
|
-
const message = await this.$.eventManager.triggerEvent('onAudioUploadError', {
|
|
349
|
+
const message = await this.$.eventManager.triggerEvent('onAudioUploadError', {
|
|
350
|
+
error: err,
|
|
351
|
+
limitSize,
|
|
352
|
+
currentSize: this.fileManager.getSize(),
|
|
353
|
+
uploadSize: fileSize,
|
|
354
|
+
});
|
|
317
355
|
|
|
318
356
|
this.$.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
319
357
|
|
|
@@ -396,7 +434,12 @@ class Audio_ extends PluginModal {
|
|
|
396
434
|
this.fileManager.setFileData(element, file);
|
|
397
435
|
element.src = src;
|
|
398
436
|
const figure = Figure.CreateContainer(element, 'se-flex-component');
|
|
399
|
-
if (
|
|
437
|
+
if (
|
|
438
|
+
!this.$.component.insert(figure.container, {
|
|
439
|
+
scrollTo: isLast ? true : false,
|
|
440
|
+
insertBehavior: isLast ? this.pluginOptions.insertBehavior : 'line',
|
|
441
|
+
})
|
|
442
|
+
) {
|
|
400
443
|
if (isLast) this.$.focusManager.focus();
|
|
401
444
|
return;
|
|
402
445
|
}
|
|
@@ -458,7 +501,9 @@ class Audio_ extends PluginModal {
|
|
|
458
501
|
const w = this.#defaultWidth;
|
|
459
502
|
const h = this.#defaultHeight;
|
|
460
503
|
/** @type {HTMLAudioElement} */
|
|
461
|
-
const oAudio = dom.utils.createElement('AUDIO', {
|
|
504
|
+
const oAudio = dom.utils.createElement('AUDIO', {
|
|
505
|
+
style: (w ? 'width:' + w + '; ' : '') + (h ? 'height:' + h + ';' : ''),
|
|
506
|
+
});
|
|
462
507
|
this.#setTagAttrs(oAudio);
|
|
463
508
|
return oAudio;
|
|
464
509
|
}
|
|
@@ -489,7 +534,13 @@ class Audio_ extends PluginModal {
|
|
|
489
534
|
if (!files) return;
|
|
490
535
|
|
|
491
536
|
const uploadFiles = this.modal.isUpdate ? [files[0]] : files;
|
|
492
|
-
this.fileManager.upload(
|
|
537
|
+
this.fileManager.upload(
|
|
538
|
+
this.pluginOptions.uploadUrl,
|
|
539
|
+
this.pluginOptions.uploadHeaders,
|
|
540
|
+
uploadFiles,
|
|
541
|
+
this.#UploadCallBack.bind(this, info),
|
|
542
|
+
this.#error.bind(this),
|
|
543
|
+
);
|
|
493
544
|
}
|
|
494
545
|
|
|
495
546
|
/**
|
|
@@ -555,7 +606,10 @@ class Audio_ extends PluginModal {
|
|
|
555
606
|
* @param {HTMLInputElement} target - The target element.
|
|
556
607
|
*/
|
|
557
608
|
#SetUrlInput(target) {
|
|
558
|
-
this.#urlValue =
|
|
609
|
+
this.#urlValue =
|
|
610
|
+
this.preview.textContent =
|
|
611
|
+
this.audioUrlFile.value =
|
|
612
|
+
target.getAttribute('data-command') || target.src;
|
|
559
613
|
this.audioUrlFile.focus();
|
|
560
614
|
}
|
|
561
615
|
|