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,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Stateless block boundary resolver for block handle positioning.
|
|
3
|
+
* Maps any DOM node inside the editor to its owning block-level element.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isWysiwygFrame, isTableCell, isComponentContainer } from '../../../helper/dom/domCheck';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} BlockInfo
|
|
10
|
+
* @property {HTMLElement} element - The resolved block-level DOM element
|
|
11
|
+
* @property {string} type - Block type: 'p', 'heading', 'list', 'blockquote', 'pre', 'figure', 'table', 'div'
|
|
12
|
+
* @property {number} depth - Nesting level from wysiwyg root (0 = top-level)
|
|
13
|
+
* @property {HTMLElement|null} parent - Parent block element, or null if top-level
|
|
14
|
+
* @property {{ prev: HTMLElement|null, next: HTMLElement|null }} siblings - Adjacent blocks at same depth
|
|
15
|
+
* @property {DOMRect} rect - getBoundingClientRect() of the element (caller handles scroll/iframe offset)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} FormatAPI
|
|
20
|
+
* @property {(node: Node, validation?: (current: *) => boolean) => HTMLElement|null} getLine
|
|
21
|
+
* @property {(element: Node, validation?: (current: *) => boolean) => HTMLElement|null} getBlock
|
|
22
|
+
* @property {(element: Node) => boolean} isLine
|
|
23
|
+
* @property {(element: Node) => boolean} isBlock
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const HEADING_RE = /^H[1-6]$/;
|
|
27
|
+
const LIST_RE = /^(UL|OL)$/;
|
|
28
|
+
const LIST_ITEM_RE = /^LI$/;
|
|
29
|
+
const TABLE_INNER_RE = /^(THEAD|TBODY|TR|TD|TH)$/;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @description Classify the block type from a resolved element.
|
|
33
|
+
* @param {HTMLElement} el
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
function classifyType(el) {
|
|
37
|
+
const tag = el.nodeName;
|
|
38
|
+
if (tag === 'P' || tag === 'DIV') return 'p';
|
|
39
|
+
if (HEADING_RE.test(tag)) return 'heading';
|
|
40
|
+
if (tag === 'LI') return 'list-item';
|
|
41
|
+
if (LIST_RE.test(tag)) return 'list';
|
|
42
|
+
if (tag === 'BLOCKQUOTE') return 'blockquote';
|
|
43
|
+
if (tag === 'PRE') return 'pre';
|
|
44
|
+
if (tag === 'FIGURE') return 'figure';
|
|
45
|
+
if (tag === 'TABLE') return 'table';
|
|
46
|
+
return 'p'; // fallback for custom format elements
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @description Walk up from a table-internal node (td, th, tr, thead, tbody) to the table element.
|
|
51
|
+
* @param {Node} node
|
|
52
|
+
* @returns {HTMLElement|null}
|
|
53
|
+
*/
|
|
54
|
+
function resolveToTable(node) {
|
|
55
|
+
let el = node;
|
|
56
|
+
while (el && !isWysiwygFrame(el)) {
|
|
57
|
+
if (el.nodeName === 'TABLE') return /** @type {HTMLElement} */ (el);
|
|
58
|
+
el = el.parentNode;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @description Check if the node is inside or is a component (.se-component, .se-flex-component)
|
|
65
|
+
* @param {Node} node
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
function isInsideComponent(node) {
|
|
69
|
+
let el = node;
|
|
70
|
+
while (el && !isWysiwygFrame(el)) {
|
|
71
|
+
if (el.nodeType === 1 && isComponentContainer(/** @type {Element} */ (el))) return true;
|
|
72
|
+
el = el.parentNode;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @description Count block-level ancestors between element and wysiwyg root.
|
|
79
|
+
* @param {HTMLElement} element
|
|
80
|
+
* @param {FormatAPI} format
|
|
81
|
+
* @returns {{ depth: number, parent: HTMLElement|null }}
|
|
82
|
+
*/
|
|
83
|
+
function computeDepth(element, format) {
|
|
84
|
+
let depth = 0;
|
|
85
|
+
let parent = null;
|
|
86
|
+
let node = element.parentNode;
|
|
87
|
+
|
|
88
|
+
while (node && !isWysiwygFrame(node)) {
|
|
89
|
+
if (node.nodeType === 1 && (format.isBlock(node) || format.isLine(node))) {
|
|
90
|
+
if (!parent) parent = /** @type {HTMLElement} */ (node);
|
|
91
|
+
depth++;
|
|
92
|
+
}
|
|
93
|
+
node = node.parentNode;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { depth, parent };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @description Find the previous and next sibling block elements at the same level.
|
|
101
|
+
* @param {HTMLElement} element
|
|
102
|
+
* @returns {{ prev: HTMLElement|null, next: HTMLElement|null }}
|
|
103
|
+
*/
|
|
104
|
+
function computeSiblings(element) {
|
|
105
|
+
const prev = element.previousElementSibling;
|
|
106
|
+
const next = element.nextElementSibling;
|
|
107
|
+
return {
|
|
108
|
+
prev: /** @type {HTMLElement|null} */ (prev),
|
|
109
|
+
next: /** @type {HTMLElement|null} */ (next),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @description For a UL/OL, find the closest direct child LI by mouse Y.
|
|
115
|
+
* Recurses into the matched LI for deeper nesting.
|
|
116
|
+
* @param {HTMLElement} list - UL or OL element
|
|
117
|
+
* @param {number} mouseY
|
|
118
|
+
* @returns {HTMLElement|null}
|
|
119
|
+
*/
|
|
120
|
+
function resolveListChildLI(list, mouseY) {
|
|
121
|
+
const children = list.children;
|
|
122
|
+
let closest = null;
|
|
123
|
+
let closestDist = Infinity;
|
|
124
|
+
for (let i = 0; i < children.length; i++) {
|
|
125
|
+
const child = children[i];
|
|
126
|
+
if (!LIST_ITEM_RE.test(child.nodeName)) continue;
|
|
127
|
+
const r = child.getBoundingClientRect();
|
|
128
|
+
if (mouseY >= r.top && mouseY <= r.bottom) {
|
|
129
|
+
// Exact hit — recurse for deeper nesting
|
|
130
|
+
return resolveNestedLI(/** @type {HTMLElement} */ (child), mouseY) || /** @type {HTMLElement} */ (child);
|
|
131
|
+
}
|
|
132
|
+
const dist = Math.min(Math.abs(mouseY - r.top), Math.abs(mouseY - r.bottom));
|
|
133
|
+
if (dist < closestDist) {
|
|
134
|
+
closestDist = dist;
|
|
135
|
+
closest = /** @type {HTMLElement} */ (child);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return closest;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @description For an LI with nested sub-lists, find the closest child LI by mouse Y.
|
|
143
|
+
* Only resolves to a child when mouseY falls within the nested list region.
|
|
144
|
+
* @param {HTMLElement} li
|
|
145
|
+
* @param {number} mouseY
|
|
146
|
+
* @returns {HTMLElement|null}
|
|
147
|
+
*/
|
|
148
|
+
function resolveNestedLI(li, mouseY) {
|
|
149
|
+
const nestedList = li.querySelector(':scope > ul, :scope > ol');
|
|
150
|
+
if (!nestedList) return null;
|
|
151
|
+
|
|
152
|
+
const nestedRect = nestedList.getBoundingClientRect();
|
|
153
|
+
// Mouse is above the nested list → stays on parent LI's own content
|
|
154
|
+
if (mouseY < nestedRect.top) return null;
|
|
155
|
+
|
|
156
|
+
const childLIs = nestedList.children;
|
|
157
|
+
let closest = null;
|
|
158
|
+
let closestDist = Infinity;
|
|
159
|
+
for (let i = 0; i < childLIs.length; i++) {
|
|
160
|
+
const child = childLIs[i];
|
|
161
|
+
if (!LIST_ITEM_RE.test(child.nodeName)) continue;
|
|
162
|
+
const r = child.getBoundingClientRect();
|
|
163
|
+
if (mouseY >= r.top && mouseY <= r.bottom) {
|
|
164
|
+
// Exact hit — recurse for deeper nesting
|
|
165
|
+
return resolveNestedLI(/** @type {HTMLElement} */ (child), mouseY) || /** @type {HTMLElement} */ (child);
|
|
166
|
+
}
|
|
167
|
+
const dist = Math.min(Math.abs(mouseY - r.top), Math.abs(mouseY - r.bottom));
|
|
168
|
+
if (dist < closestDist) {
|
|
169
|
+
closestDist = dist;
|
|
170
|
+
closest = /** @type {HTMLElement} */ (child);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return closest;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @description Resolve any DOM node inside the editor to its owning block-level element.
|
|
178
|
+
* @param {Node|null} node - Any DOM node inside the editor
|
|
179
|
+
* @param {FormatAPI} format - Injected format methods (getLine, getBlock, isLine, isBlock)
|
|
180
|
+
* @param {HTMLElement} wysiwygFrame - The wysiwyg root element
|
|
181
|
+
* @param {number} [mouseY] - Mouse clientY for nested list resolution
|
|
182
|
+
* @returns {BlockInfo|null} Block information, or null if node is outside editable area
|
|
183
|
+
*/
|
|
184
|
+
export function resolveBlock(node, format, wysiwygFrame, mouseY) {
|
|
185
|
+
if (!node || !wysiwygFrame) return null;
|
|
186
|
+
|
|
187
|
+
// Text node → use parent element
|
|
188
|
+
if (node.nodeType === 3) {
|
|
189
|
+
node = node.parentNode;
|
|
190
|
+
if (!node) return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Already at wysiwyg root
|
|
194
|
+
if (isWysiwygFrame(node)) return null;
|
|
195
|
+
|
|
196
|
+
// Skip components (images, videos, etc.) — they have their own interaction
|
|
197
|
+
if (isInsideComponent(node)) return null;
|
|
198
|
+
|
|
199
|
+
let resolved = null;
|
|
200
|
+
|
|
201
|
+
// Table cell or table-internal → resolve to TABLE
|
|
202
|
+
if (TABLE_INNER_RE.test(node.nodeName)) {
|
|
203
|
+
resolved = resolveToTable(node);
|
|
204
|
+
}
|
|
205
|
+
// List container (UL/OL) → keep as-is, resolveListChildLI will pick the right LI
|
|
206
|
+
else if (LIST_RE.test(node.nodeName)) {
|
|
207
|
+
resolved = /** @type {HTMLElement} */ (node);
|
|
208
|
+
}
|
|
209
|
+
// List item → resolve to LI itself (each list item gets its own handle)
|
|
210
|
+
else if (LIST_ITEM_RE.test(node.nodeName)) {
|
|
211
|
+
resolved = /** @type {HTMLElement} */ (node);
|
|
212
|
+
}
|
|
213
|
+
// Check if inside a table cell — walk up to table
|
|
214
|
+
else if (isTableCell(node.parentNode)) {
|
|
215
|
+
resolved = resolveToTable(node);
|
|
216
|
+
} else {
|
|
217
|
+
// Try getLine first (finds P, H1-H6, PRE, LI, etc.)
|
|
218
|
+
const line = format.getLine(node, null);
|
|
219
|
+
if (line) {
|
|
220
|
+
resolved = line;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// If no line found, try getBlock (finds BLOCKQUOTE, UL, OL, TABLE, etc.)
|
|
224
|
+
if (!resolved) {
|
|
225
|
+
const block = format.getBlock(node, null);
|
|
226
|
+
if (block) {
|
|
227
|
+
// For table internals, resolve up to TABLE
|
|
228
|
+
if (TABLE_INNER_RE.test(block.nodeName)) {
|
|
229
|
+
resolved = resolveToTable(block);
|
|
230
|
+
} else {
|
|
231
|
+
resolved = block;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// If nothing resolved, try walking up manually to find any block-level element
|
|
238
|
+
if (!resolved) {
|
|
239
|
+
let el = /** @type {Node} */ (node);
|
|
240
|
+
while (el && !isWysiwygFrame(el)) {
|
|
241
|
+
if (el.nodeType === 1 && el.parentNode && isWysiwygFrame(el.parentNode)) {
|
|
242
|
+
resolved = /** @type {HTMLElement} */ (el);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
el = el.parentNode;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!resolved) return null;
|
|
250
|
+
|
|
251
|
+
// Final component check on resolved element
|
|
252
|
+
if (isInsideComponent(resolved)) return null;
|
|
253
|
+
|
|
254
|
+
// For UL/OL, resolve to the closest child LI by mouse Y.
|
|
255
|
+
// For LI with nested sub-lists, find the deepest child LI.
|
|
256
|
+
if (mouseY !== undefined) {
|
|
257
|
+
if (LIST_RE.test(resolved.nodeName)) {
|
|
258
|
+
const childLI = resolveListChildLI(/** @type {HTMLElement} */ (resolved), mouseY);
|
|
259
|
+
if (childLI) resolved = childLI;
|
|
260
|
+
} else if (LIST_ITEM_RE.test(resolved.nodeName)) {
|
|
261
|
+
const nested = resolveNestedLI(/** @type {HTMLElement} */ (resolved), mouseY);
|
|
262
|
+
if (nested) resolved = nested;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const { depth, parent } = computeDepth(resolved, format);
|
|
267
|
+
const siblings = computeSiblings(resolved);
|
|
268
|
+
const type = classifyType(resolved);
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
element: resolved,
|
|
272
|
+
type,
|
|
273
|
+
depth,
|
|
274
|
+
parent,
|
|
275
|
+
siblings,
|
|
276
|
+
rect: resolved.getBoundingClientRect(),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -54,7 +54,8 @@ class Finder {
|
|
|
54
54
|
Finder.#highlightStyleInjected = true;
|
|
55
55
|
const style = _d.createElement('style');
|
|
56
56
|
style.textContent =
|
|
57
|
-
'::highlight(se-find-match){background-color:var(--se-find-match-color,rgba(255,213,0,.4));color:inherit}' +
|
|
57
|
+
'::highlight(se-find-match){background-color:var(--se-find-match-color,rgba(255,213,0,.4));color:inherit}' +
|
|
58
|
+
'::highlight(se-find-current){background-color:var(--se-find-current-color,rgba(255,150,50,.7));color:inherit}';
|
|
58
59
|
_d.head.appendChild(style);
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -99,7 +100,9 @@ class Finder {
|
|
|
99
100
|
|
|
100
101
|
// Update sticky top (responsive resize, more layer, etc.)
|
|
101
102
|
if (env.isResizeObserverSupported) {
|
|
102
|
-
this.#resizeObserver = new ResizeObserver(() => this.#updateStickyTop()).observe(
|
|
103
|
+
this.#resizeObserver = new ResizeObserver(() => this.#updateStickyTop()).observe(
|
|
104
|
+
this.#$.context.get('toolbar_main'),
|
|
105
|
+
);
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
}
|
|
@@ -157,9 +160,11 @@ class Finder {
|
|
|
157
160
|
const stickyTop = this.#$.options.get('_toolbar_sticky');
|
|
158
161
|
if (this.#store.mode.isBottom) {
|
|
159
162
|
this.#panel.style.top = 'auto';
|
|
160
|
-
this.#panel.style.bottom =
|
|
163
|
+
this.#panel.style.bottom =
|
|
164
|
+
stickyTop >= 0 ? stickyTop + this.#$.context.get('toolbar_main').offsetHeight + 'px' : '0px';
|
|
161
165
|
} else {
|
|
162
|
-
this.#panel.style.top =
|
|
166
|
+
this.#panel.style.top =
|
|
167
|
+
stickyTop >= 0 ? stickyTop + this.#$.context.get('toolbar_main').offsetHeight + 'px' : '0px';
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
170
|
|
|
@@ -753,7 +758,9 @@ class Finder {
|
|
|
753
758
|
// Mark fallback: update active mark
|
|
754
759
|
this.#updateCurrentMarkHighlight();
|
|
755
760
|
|
|
756
|
-
const currentMark = this.#markElements.find(
|
|
761
|
+
const currentMark = this.#markElements.find(
|
|
762
|
+
(m) => m.getAttribute('data-se-find-idx') === String(this.#currentIndex),
|
|
763
|
+
);
|
|
757
764
|
if (currentMark) {
|
|
758
765
|
this.#$.selection.scrollTo(currentMark, { behavior: 'auto', noFocus: true });
|
|
759
766
|
}
|
|
@@ -780,7 +787,10 @@ class Finder {
|
|
|
780
787
|
const freshMatches = this.#findAllMatches(this.#searchTerm, wysiwyg);
|
|
781
788
|
if (this.#currentIndex >= freshMatches.length) return;
|
|
782
789
|
|
|
783
|
-
this.#replaceRange(
|
|
790
|
+
this.#replaceRange(
|
|
791
|
+
freshMatches[this.#currentIndex],
|
|
792
|
+
replaceText ?? (this.#replaceInput ? this.#replaceInput.value : ''),
|
|
793
|
+
);
|
|
784
794
|
this.#$.history.push(false);
|
|
785
795
|
|
|
786
796
|
// Re-search
|
|
@@ -43,6 +43,8 @@ class Menu {
|
|
|
43
43
|
// members
|
|
44
44
|
/** @type {Object<string, HTMLElement>} */
|
|
45
45
|
this.targetMap = {};
|
|
46
|
+
/** @type {Object<string, Array<DropdownItem>>} Structured items keyed by plugin name */
|
|
47
|
+
this.itemsMap = {};
|
|
46
48
|
this.index = -1;
|
|
47
49
|
this.menus = [];
|
|
48
50
|
// dropdown
|
|
@@ -79,19 +81,37 @@ class Menu {
|
|
|
79
81
|
* @description Method for managing dropdown element.
|
|
80
82
|
* - You must add the `dropdown` element using this method at custom plugin.
|
|
81
83
|
* @param {{key: string, type: string}} classObj Class object
|
|
82
|
-
* @param {Node}
|
|
84
|
+
* @param {Node|Array<DropdownItem>} menuOrItems Dropdown element or array of standardized items
|
|
85
|
+
* @param {Object} [options] Options when passing items array
|
|
86
|
+
* @param {string} [options.className] Additional CSS class for the wrapper element
|
|
87
|
+
* @param {string} [options.prependHTML] HTML to prepend inside the `<ul>` (e.g. default/sub-list items)
|
|
88
|
+
* @returns {HTMLElement} The registered menu element
|
|
83
89
|
*/
|
|
84
|
-
initDropdownTarget({ key, type },
|
|
85
|
-
if (key)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
initDropdownTarget({ key, type }, menuOrItems, options) {
|
|
91
|
+
if (!key) throw Error("[SUNEDITOR.init.fail] The plugin's key is not added.");
|
|
92
|
+
|
|
93
|
+
let menu;
|
|
94
|
+
if (Array.isArray(menuOrItems)) {
|
|
95
|
+
menu = CreateDropdownMenu(menuOrItems, options);
|
|
96
|
+
|
|
97
|
+
const allButtons = menu.querySelectorAll('.se-list-inner li > button');
|
|
98
|
+
const offset = allButtons.length - menuOrItems.length;
|
|
99
|
+
for (let i = 0; i < menuOrItems.length; i++) {
|
|
100
|
+
menuOrItems[i]._element = /** @type {HTMLElement} */ (allButtons[offset + i]) || null;
|
|
89
101
|
}
|
|
90
|
-
this
|
|
91
|
-
this.targetMap[key] = /** @type {HTMLElement} */ (menu);
|
|
102
|
+
this.itemsMap[key] = menuOrItems;
|
|
92
103
|
} else {
|
|
93
|
-
|
|
104
|
+
menu = /** @type {HTMLElement} */ (menuOrItems);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!IsFree(type)) {
|
|
108
|
+
menu.setAttribute('data-key', key);
|
|
109
|
+
this.#dropdownCommands.push(key);
|
|
94
110
|
}
|
|
111
|
+
|
|
112
|
+
this.#context.get('menuTray').appendChild(menu);
|
|
113
|
+
this.targetMap[key] = menu;
|
|
114
|
+
return menu;
|
|
95
115
|
}
|
|
96
116
|
|
|
97
117
|
/**
|
|
@@ -102,7 +122,9 @@ class Menu {
|
|
|
102
122
|
this.#removeGlobalEvent();
|
|
103
123
|
const moreBtn = this.#checkMoreLayer(button);
|
|
104
124
|
if (moreBtn) {
|
|
105
|
-
const target = dom.query
|
|
125
|
+
const target = dom.query
|
|
126
|
+
.getParentElement(moreBtn, '.se-btn-tray')
|
|
127
|
+
.querySelector('[data-command="' + moreBtn.getAttribute('data-ref') + '"]');
|
|
106
128
|
if (target) {
|
|
107
129
|
this.#$.commandDispatcher.runFromTarget(target);
|
|
108
130
|
this.dropdownOn(button);
|
|
@@ -123,13 +145,31 @@ class Menu {
|
|
|
123
145
|
}
|
|
124
146
|
|
|
125
147
|
this.#context.get('menuTray').showPopover?.();
|
|
126
|
-
this.#bindClose_dropdown_mouse = this.#eventManager.addGlobalEvent(
|
|
148
|
+
this.#bindClose_dropdown_mouse = this.#eventManager.addGlobalEvent(
|
|
149
|
+
'mousedown',
|
|
150
|
+
this.#globalEventHandler.mousedown,
|
|
151
|
+
false,
|
|
152
|
+
);
|
|
127
153
|
if (this.#dropdownCommands.includes(dropdownName)) {
|
|
128
154
|
this.menus = converter.nodeListToArray(menu.querySelectorAll('[data-command]'));
|
|
129
155
|
if (this.menus.length > 0) {
|
|
130
|
-
this.#bindClose_dropdown_key = this.#eventManager.addGlobalEvent(
|
|
131
|
-
|
|
132
|
-
|
|
156
|
+
this.#bindClose_dropdown_key = this.#eventManager.addGlobalEvent(
|
|
157
|
+
'keydown',
|
|
158
|
+
this.#globalEventHandler.keydown,
|
|
159
|
+
false,
|
|
160
|
+
);
|
|
161
|
+
this.#bindMenu_mousemove = this.#eventManager.addEvent(
|
|
162
|
+
menu,
|
|
163
|
+
'mousemove',
|
|
164
|
+
this.#globalEventHandler.mousemove,
|
|
165
|
+
false,
|
|
166
|
+
);
|
|
167
|
+
this.#bindMenu_mouseout = this.#eventManager.addEvent(
|
|
168
|
+
menu,
|
|
169
|
+
'mouseout',
|
|
170
|
+
this.#globalEventHandler.mouseout,
|
|
171
|
+
false,
|
|
172
|
+
);
|
|
133
173
|
}
|
|
134
174
|
}
|
|
135
175
|
|
|
@@ -202,7 +242,8 @@ class Menu {
|
|
|
202
242
|
this.#removeGlobalEvent();
|
|
203
243
|
|
|
204
244
|
this.currentContainerActiveButton = /** @type {HTMLButtonElement} */ (button);
|
|
205
|
-
const containerName = (this.currentContainerName =
|
|
245
|
+
const containerName = (this.currentContainerName =
|
|
246
|
+
this.currentContainerActiveButton.getAttribute('data-command'));
|
|
206
247
|
this.currentContainer = this.targetMap[containerName];
|
|
207
248
|
|
|
208
249
|
if (isMobile) {
|
|
@@ -212,7 +253,11 @@ class Menu {
|
|
|
212
253
|
}
|
|
213
254
|
|
|
214
255
|
this.#context.get('menuTray').showPopover?.();
|
|
215
|
-
this.#bindClose_cons_mouse = this.#eventManager.addGlobalEvent(
|
|
256
|
+
this.#bindClose_cons_mouse = this.#eventManager.addGlobalEvent(
|
|
257
|
+
'mousedown',
|
|
258
|
+
this.#globalEventHandler.containerDown,
|
|
259
|
+
false,
|
|
260
|
+
);
|
|
216
261
|
|
|
217
262
|
if (this.#$.plugins[containerName].on) this.#$.plugins[containerName].on(button);
|
|
218
263
|
this.#store.set('_preventBlur', true);
|
|
@@ -245,7 +290,13 @@ class Menu {
|
|
|
245
290
|
* @param {HTMLElement} menu Menu element
|
|
246
291
|
*/
|
|
247
292
|
__resetMenuPosition(element, menu) {
|
|
248
|
-
this.#$.offset.setRelPosition(
|
|
293
|
+
this.#$.offset.setRelPosition(
|
|
294
|
+
menu,
|
|
295
|
+
this.#contextProvider.carrierWrapper,
|
|
296
|
+
element.parentElement,
|
|
297
|
+
dom.query.getParentElement(element, '.se-toolbar'),
|
|
298
|
+
{ preferUp: this.#store.mode.isBottom },
|
|
299
|
+
);
|
|
249
300
|
}
|
|
250
301
|
|
|
251
302
|
/**
|
|
@@ -270,7 +321,13 @@ class Menu {
|
|
|
270
321
|
menu.style.height = '';
|
|
271
322
|
dom.utils.addClass(element.parentElement.children, 'on');
|
|
272
323
|
|
|
273
|
-
this.#$.offset.setRelPosition(
|
|
324
|
+
this.#$.offset.setRelPosition(
|
|
325
|
+
menu,
|
|
326
|
+
this.#contextProvider.carrierWrapper,
|
|
327
|
+
element.parentElement,
|
|
328
|
+
dom.query.getParentElement(element, '.se-toolbar'),
|
|
329
|
+
{ preferUp: this.#store.mode.isBottom },
|
|
330
|
+
);
|
|
274
331
|
|
|
275
332
|
menu.style.visibility = '';
|
|
276
333
|
|
|
@@ -463,4 +520,48 @@ function IsFree(type) {
|
|
|
463
520
|
return /free$/.test(type);
|
|
464
521
|
}
|
|
465
522
|
|
|
523
|
+
/**
|
|
524
|
+
* @typedef {Object} DropdownItem
|
|
525
|
+
* @property {string} command - `data-command` attribute value
|
|
526
|
+
* @property {string} [value] - `data-value` attribute value
|
|
527
|
+
* @property {string} title - Title text (used for `title` and `aria-label`)
|
|
528
|
+
* @property {string} innerHTML - Button inner HTML content
|
|
529
|
+
* @property {string} [className] - Additional CSS class for the `<button>`
|
|
530
|
+
* @property {Object<string, string>} [attrs] - Extra data attributes (e.g. `{ 'data-class': 'xxx' }`)
|
|
531
|
+
* @property {HTMLElement} [_element] - Rendered button element (set by `initDropdownTarget`)
|
|
532
|
+
*/
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* @description Creates a dropdown menu element from standardized item objects.
|
|
536
|
+
* @param {Array<DropdownItem>} items - Menu items
|
|
537
|
+
* @param {Object} [options]
|
|
538
|
+
* @param {string} [options.className] - Additional class for the wrapper `<div>`
|
|
539
|
+
* @param {string} [options.prependHTML] - HTML to prepend inside the `<ul>` (e.g. default value items)
|
|
540
|
+
* @returns {HTMLElement}
|
|
541
|
+
*/
|
|
542
|
+
function CreateDropdownMenu(items, options = {}) {
|
|
543
|
+
let html = '';
|
|
544
|
+
for (let i = 0, len = items.length; i < len; i++) {
|
|
545
|
+
const item = items[i];
|
|
546
|
+
let attrStr = '';
|
|
547
|
+
if (item.attrs) {
|
|
548
|
+
const keys = Object.keys(item.attrs);
|
|
549
|
+
for (let a = 0; a < keys.length; a++) {
|
|
550
|
+
attrStr += ` ${keys[a]}="${item.attrs[keys[a]]}"`;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
html += /*html*/ `
|
|
554
|
+
<li>
|
|
555
|
+
<button type="button" class="se-btn se-btn-list${item.className ? ' ' + item.className : ''}" data-command="${item.command}"${item.value !== undefined ? ` data-value="${item.value}"` : ''} title="${item.title}" aria-label="${item.title}"${attrStr}>${item.innerHTML}</button>
|
|
556
|
+
</li>`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return dom.utils.createElement(
|
|
560
|
+
'DIV',
|
|
561
|
+
{ class: 'se-dropdown se-list-layer' + (options.className ? ' ' + options.className : '') },
|
|
562
|
+
/*html*/ `<div class="se-list-inner"><ul class="se-list-basic">${options.prependHTML || ''}${html}</ul></div>`,
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export { CreateDropdownMenu };
|
|
466
567
|
export default Menu;
|