suneditor 3.3.1 → 3.3.3
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 +3 -2
- package/src/assets/suneditor.css +19 -3
- package/src/core/editor.js +6 -1
- package/src/core/event/effects/keydown.registry.js +14 -9
- package/src/core/event/effects/ruleHelpers.js +119 -1
- package/src/core/event/eventOrchestrator.js +17 -6
- package/src/core/event/handlers/handler_ww_key.js +2 -0
- package/src/core/event/rules/keydown.rule.backspace.js +10 -47
- package/src/core/event/rules/keydown.rule.delete.js +28 -66
- package/src/core/logic/dom/format.js +1 -1
- package/src/core/logic/dom/html.js +54 -5
- package/src/core/logic/dom/nodeTransform.js +5 -3
- package/src/core/logic/panel/blockHandle.js +42 -7
- package/src/core/logic/shell/component.js +3 -1
- package/src/core/logic/shell/history.js +32 -3
- package/src/core/logic/shell/ui.js +6 -1
- package/src/core/schema/options.js +4 -3
- package/src/events.js +1 -1
- package/src/helper/dom/domQuery.js +10 -4
- package/src/helper/googleDocs.js +40 -0
- package/src/helper/index.js +3 -0
- package/src/modules/contract/Modal.js +1 -0
- package/src/plugins/command/codeBlock.js +9 -1
- package/types/core/event/effects/ruleHelpers.d.ts +56 -0
- package/types/core/logic/panel/blockHandle.d.ts +5 -0
- package/types/core/schema/options.d.ts +6 -5
- package/types/events.d.ts +2 -2
- package/types/helper/dom/domQuery.d.ts +4 -2
- package/types/helper/googleDocs.d.ts +19 -0
- package/types/helper/index.d.ts +5 -0
|
@@ -106,8 +106,9 @@ class NodeTransform {
|
|
|
106
106
|
|
|
107
107
|
if (temp) {
|
|
108
108
|
if (dom.check.isListCell(newEl) && dom.check.isList(temp) && temp.firstElementChild) {
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const firstCell = temp.firstElementChild;
|
|
110
|
+
while (firstCell.firstChild) newEl.appendChild(firstCell.firstChild);
|
|
111
|
+
dom.utils.removeItem(firstCell);
|
|
111
112
|
if (temp.children.length > 0) newEl.appendChild(temp);
|
|
112
113
|
} else {
|
|
113
114
|
newEl.appendChild(temp);
|
|
@@ -300,7 +301,8 @@ class NodeTransform {
|
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
} else {
|
|
303
|
-
child.
|
|
304
|
+
while (next.firstChild) child.appendChild(next.firstChild);
|
|
305
|
+
child.normalize();
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
dom.utils.removeItem(next);
|
|
@@ -54,6 +54,10 @@ class BlockHandle {
|
|
|
54
54
|
#pendingMouseY = undefined;
|
|
55
55
|
/** @type {number|null} */
|
|
56
56
|
#hideTimer = null;
|
|
57
|
+
/** @type {boolean} */
|
|
58
|
+
#keyboardLock = false;
|
|
59
|
+
/** @type {number|undefined} */
|
|
60
|
+
#lastMouseY = undefined;
|
|
57
61
|
/** @type {HTMLElement[]} */
|
|
58
62
|
#hoverLines = [];
|
|
59
63
|
|
|
@@ -123,8 +127,6 @@ class BlockHandle {
|
|
|
123
127
|
em.addEvent(wrapper, 'dragover', this.#onDragOver.bind(this));
|
|
124
128
|
em.addEvent(wrapper, 'drop', this.#onDrop.bind(this));
|
|
125
129
|
}
|
|
126
|
-
|
|
127
|
-
em.addEvent(this.#$.frameContext.get('eventWysiwyg'), 'keydown', this.#onWrapperKeyDown.bind(this), true);
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
/**
|
|
@@ -161,13 +163,24 @@ class BlockHandle {
|
|
|
161
163
|
this.#cancelHide();
|
|
162
164
|
// Lock handle while action menu is open or dragging
|
|
163
165
|
if (this.#actionMenu?.isOpen || this.#isDragging) return;
|
|
166
|
+
|
|
167
|
+
if (this.#keyboardLock) {
|
|
168
|
+
if (mouseY === this.#lastMouseY) return;
|
|
169
|
+
this.#keyboardLock = false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const framePending = this.#pendingTarget !== null;
|
|
164
173
|
this.#pendingTarget = eventTarget;
|
|
174
|
+
this.#lastMouseY = mouseY;
|
|
165
175
|
this.#pendingMouseY = mouseY;
|
|
166
|
-
if (
|
|
176
|
+
if (framePending) return;
|
|
177
|
+
|
|
167
178
|
this.#rafId = _w.requestAnimationFrame(() => {
|
|
168
179
|
this.#rafId = null;
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
const target = this.#pendingTarget;
|
|
181
|
+
this.#pendingTarget = null;
|
|
182
|
+
if (target) {
|
|
183
|
+
this.#doPosition(target, this.#pendingMouseY);
|
|
171
184
|
}
|
|
172
185
|
});
|
|
173
186
|
}
|
|
@@ -321,6 +334,7 @@ class BlockHandle {
|
|
|
321
334
|
if (!this.#$) return;
|
|
322
335
|
if (this.#isDragging) return;
|
|
323
336
|
|
|
337
|
+
this.#keyboardLock = false;
|
|
324
338
|
this.#cancelHide();
|
|
325
339
|
|
|
326
340
|
// Don't reposition while action menu is open
|
|
@@ -494,6 +508,24 @@ class BlockHandle {
|
|
|
494
508
|
|
|
495
509
|
const totalOffset = indent + centeringExtra;
|
|
496
510
|
|
|
511
|
+
const toolbarEl = this.#$.context?.get('toolbar_main');
|
|
512
|
+
if (toolbarEl) {
|
|
513
|
+
const tRect = toolbarEl.getBoundingClientRect();
|
|
514
|
+
const handleWidth = this.#handle.offsetWidth || 0;
|
|
515
|
+
const hTop = firstLineCenterVP - handleHeight / 2;
|
|
516
|
+
const hLeft = isRtl ? areaRect.right - totalOffset - handleWidth : areaRect.left + totalOffset;
|
|
517
|
+
if (
|
|
518
|
+
tRect.height > 0 &&
|
|
519
|
+
hTop < tRect.bottom - 1 &&
|
|
520
|
+
hTop + handleHeight > tRect.top + 1 &&
|
|
521
|
+
hLeft < tRect.right &&
|
|
522
|
+
hLeft + handleWidth > tRect.left
|
|
523
|
+
) {
|
|
524
|
+
this.#hideHandle();
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
497
529
|
this.#handle.style.top = top + 'px';
|
|
498
530
|
if (isRtl) {
|
|
499
531
|
this.#handle.style.left = '';
|
|
@@ -628,10 +660,13 @@ class BlockHandle {
|
|
|
628
660
|
}
|
|
629
661
|
|
|
630
662
|
/**
|
|
663
|
+
* @internal
|
|
631
664
|
* @description Editor keyboard activity (typing, Enter, etc.)
|
|
632
665
|
*/
|
|
633
|
-
|
|
634
|
-
this.#
|
|
666
|
+
hideOnKeyDown() {
|
|
667
|
+
if (this.#actionMenu?.isOpen) return;
|
|
668
|
+
this.#keyboardLock = true;
|
|
669
|
+
this.hideNow();
|
|
635
670
|
}
|
|
636
671
|
|
|
637
672
|
/**
|
|
@@ -136,6 +136,8 @@ class Component {
|
|
|
136
136
|
|
|
137
137
|
if (insertBehavior === undefined) insertBehavior = this.#options.get('componentInsertBehavior');
|
|
138
138
|
|
|
139
|
+
if (this.isSelected) this.__deselect();
|
|
140
|
+
|
|
139
141
|
const r = this.#$.html.remove();
|
|
140
142
|
const isInline = this.isInline(element);
|
|
141
143
|
this.#$.selection.getRangeAndAddLine(this.#$.selection.getRange(), r.container);
|
|
@@ -195,7 +197,7 @@ class Component {
|
|
|
195
197
|
|
|
196
198
|
const targetElement = /** @type {HTMLElement} */ (oNode || element);
|
|
197
199
|
|
|
198
|
-
if (scrollTo) this.#$.selection.scrollTo(targetElement, { behavior: 'auto' });
|
|
200
|
+
if (scrollTo) this.#$.selection.scrollTo(targetElement, { behavior: 'auto', noFocus: true });
|
|
199
201
|
if (insertBehavior !== null) this.applyInsertBehavior(element, oNode, insertBehavior);
|
|
200
202
|
|
|
201
203
|
return targetElement;
|
|
@@ -1,7 +1,36 @@
|
|
|
1
|
-
import { _w } from '../../../helper/env';
|
|
1
|
+
import { _w, _d } from '../../../helper/env';
|
|
2
2
|
import { getNodeFromPath, getNodePath } from '../../../helper/dom/domQuery';
|
|
3
3
|
import { numbers } from '../../../helper';
|
|
4
4
|
|
|
5
|
+
const TRANSIENT_UI_CLASSES = [
|
|
6
|
+
'se-component-selected',
|
|
7
|
+
'se-figure-over-selected',
|
|
8
|
+
'se-block-hover',
|
|
9
|
+
'se-pre-code-focus',
|
|
10
|
+
];
|
|
11
|
+
const TRANSIENT_UI_CLASS_RE = new RegExp(TRANSIENT_UI_CLASSES.join('|'));
|
|
12
|
+
const TRANSIENT_UI_CLASS_SELECTOR = '.' + TRANSIENT_UI_CLASSES.join(', .');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @description Strips transient UI-state classes (selection/hover markers) from a snapshot's HTML.
|
|
16
|
+
* - only while a marker is actually present in the serialized content.
|
|
17
|
+
* @param {string} html Serialized wysiwyg content
|
|
18
|
+
* @returns {string} The cleaned content
|
|
19
|
+
*/
|
|
20
|
+
function cleanSnapshot(html) {
|
|
21
|
+
if (!TRANSIENT_UI_CLASS_RE.test(html)) return html;
|
|
22
|
+
|
|
23
|
+
const t = _d.createElement('template');
|
|
24
|
+
t.innerHTML = html;
|
|
25
|
+
|
|
26
|
+
const marked = t.content.querySelectorAll(TRANSIENT_UI_CLASS_SELECTOR);
|
|
27
|
+
for (let i = 0; i < marked.length; i++) {
|
|
28
|
+
marked[i].classList.remove(...TRANSIENT_UI_CLASSES);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return t.innerHTML;
|
|
32
|
+
}
|
|
33
|
+
|
|
5
34
|
/**
|
|
6
35
|
* @description History stack closure
|
|
7
36
|
* @param {SunEditor.Kernel} kernel
|
|
@@ -206,7 +235,7 @@ export default function History(kernel) {
|
|
|
206
235
|
$.pluginManager.checkFileInfo(false);
|
|
207
236
|
|
|
208
237
|
const fc = frameRoots.get(rootKey);
|
|
209
|
-
const current = fc.get('wysiwyg').innerHTML;
|
|
238
|
+
const current = cleanSnapshot(fc.get('wysiwyg').innerHTML);
|
|
210
239
|
const root = rootStack[rootKey];
|
|
211
240
|
if (!current || (root.value[root.index] && current === root.value[root.index].content)) return;
|
|
212
241
|
if (stack.length > stackIndex + 1) refreshRoots(root);
|
|
@@ -319,7 +348,7 @@ export default function History(kernel) {
|
|
|
319
348
|
*/
|
|
320
349
|
overwrite(rootKey) {
|
|
321
350
|
setStack(
|
|
322
|
-
frameRoots.get(rootKey || store.get('rootKey')).get('wysiwyg').innerHTML,
|
|
351
|
+
cleanSnapshot(frameRoots.get(rootKey || store.get('rootKey')).get('wysiwyg').innerHTML),
|
|
323
352
|
null,
|
|
324
353
|
store.get('rootKey'),
|
|
325
354
|
0,
|
|
@@ -959,7 +959,12 @@ class UIManager {
|
|
|
959
959
|
this._updatePlaceholder(fc);
|
|
960
960
|
// document type page
|
|
961
961
|
if (fc.has('documentType_use_page')) {
|
|
962
|
-
|
|
962
|
+
const mirror = fc.get('documentTypePageMirror');
|
|
963
|
+
const frag = mirror.ownerDocument.createDocumentFragment();
|
|
964
|
+
for (let n = fc.get('wysiwyg').firstChild; n; n = n.nextSibling) {
|
|
965
|
+
frag.appendChild(n.cloneNode(true));
|
|
966
|
+
}
|
|
967
|
+
mirror.replaceChildren(frag);
|
|
963
968
|
fc.get('documentType').rePage(true);
|
|
964
969
|
}
|
|
965
970
|
}
|
|
@@ -71,6 +71,7 @@ export const DEFAULTS = {
|
|
|
71
71
|
'@text': 'font-family|font-size|color|background-color|width|height',
|
|
72
72
|
'@line': 'text-align|margin|margin-left|margin-right|line-height',
|
|
73
73
|
'@component': 'width|height|min-width',
|
|
74
|
+
li: 'font-family|font-size|color|background-color|font-weight|font-style',
|
|
74
75
|
'table|th|td':
|
|
75
76
|
'border|border-[a-z]+|color|background-color|text-align|float|font-weight|text-decoration|font-style|vertical-align',
|
|
76
77
|
'table|td': 'width',
|
|
@@ -457,7 +458,7 @@ export const DEFAULTS = {
|
|
|
457
458
|
* - Value is a pipe-delimited list of allowed style names.
|
|
458
459
|
* - Resolution order when filtering an element: `@component` (for `.se-component` containers) → explicit tag entry → `@line` (for formatLine elements) → `@text` (for textStyleTags).
|
|
459
460
|
* - `@component` guards the inline sizing (`width`/`height`/`min-width`) the editor writes on a media component's container for percentage-based sizes; keep these so a clean() round-trip does not reset the component to full width.
|
|
460
|
-
* - An explicit tag entry **
|
|
461
|
+
* - An explicit tag entry is **merged** with its category default when the tag belongs to one (`@line` for formatLine elements, else `@text` for textStyleTags) — the entry adds styles on top of the category's.
|
|
461
462
|
* - Merged with {@link DEFAULTS.TAG_STYLES}; user-supplied keys win.
|
|
462
463
|
* ```js
|
|
463
464
|
* {
|
|
@@ -465,8 +466,8 @@ export const DEFAULTS = {
|
|
|
465
466
|
* '@text': 'color|font-size|background-color', // default for span, b, i, em, ...
|
|
466
467
|
* '@line': 'text-align|margin|line-height', // default for p, h1-h6, div, li, ...
|
|
467
468
|
* 'table|td': 'border|color|background-color', // per-tag whitelist
|
|
468
|
-
*
|
|
469
|
-
*
|
|
469
|
+
* div: 'color', // merged with the `@line` default (div is a line element)
|
|
470
|
+
* hr: 'border-top',
|
|
470
471
|
* }
|
|
471
472
|
* }
|
|
472
473
|
* ```
|
package/src/events.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* @property {Event} event - event object
|
|
18
18
|
* @property {string} data - drop data
|
|
19
19
|
* @property {boolean} maxCharCount - is max char count
|
|
20
|
-
* @property {string} from - `"SE"`|`"MS"`|`""` - source
|
|
20
|
+
* @property {string} from - `"SE"`|`"MS"`|`"GOOGLE"`|`""` - source
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
// --- media
|
|
@@ -513,7 +513,8 @@ export function getEdgeChildNodes(first, last) {
|
|
|
513
513
|
|
|
514
514
|
/**
|
|
515
515
|
* @template {Node} T
|
|
516
|
-
* @description Gets the previous sibling last child. If there is no sibling, then it'll take it from the closest ancestor with child
|
|
516
|
+
* @description Gets the previous sibling last child. If there is no sibling, then it'll take it from the closest ancestor with child.
|
|
517
|
+
* - Components (image, table, etc.) are treated as a single tag and not traversed into.
|
|
517
518
|
* @param {Node} node Reference element
|
|
518
519
|
* @param {?Node} [ceiling] Highest boundary allowed
|
|
519
520
|
* @returns {T|null} Not found: `null`
|
|
@@ -533,14 +534,17 @@ export function getPreviousDeepestNode(node, ceiling) {
|
|
|
533
534
|
|
|
534
535
|
if (domCheck.isNonEditable(previousNode)) return /** @type {T} */ (/** @type {unknown} */ (previousNode));
|
|
535
536
|
|
|
536
|
-
while (previousNode
|
|
537
|
+
while (!domCheck.isComponentContainer(previousNode) && previousNode.lastChild) {
|
|
538
|
+
previousNode = previousNode.lastChild;
|
|
539
|
+
}
|
|
537
540
|
|
|
538
541
|
return /** @type {T} */ (/** @type {unknown} */ (previousNode));
|
|
539
542
|
}
|
|
540
543
|
|
|
541
544
|
/**
|
|
542
545
|
* @template {Node} T
|
|
543
|
-
* @description Gets the next sibling first child. If there is no sibling, then it'll take it from the closest ancestor with child
|
|
546
|
+
* @description Gets the next sibling first child. If there is no sibling, then it'll take it from the closest ancestor with child.
|
|
547
|
+
* - Components (image, table, etc.) are treated as a single tag and not traversed into.
|
|
544
548
|
* @param {Node} node Reference element
|
|
545
549
|
* @param {?Node} [ceiling] Highest boundary allowed
|
|
546
550
|
* @returns {T|null} Not found: `null`
|
|
@@ -560,7 +564,9 @@ export function getNextDeepestNode(node, ceiling) {
|
|
|
560
564
|
|
|
561
565
|
if (domCheck.isNonEditable(nextNode)) return /** @type {T} */ (/** @type {unknown} */ (nextNode));
|
|
562
566
|
|
|
563
|
-
while (nextNode
|
|
567
|
+
while (!domCheck.isComponentContainer(nextNode) && nextNode.firstChild) {
|
|
568
|
+
nextNode = nextNode.firstChild;
|
|
569
|
+
}
|
|
564
570
|
|
|
565
571
|
return /** @type {T} */ (/** @type {unknown} */ (nextNode));
|
|
566
572
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Cleans Google Docs clipboard HTML.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Matches the Google Docs clipboard wrapper id */
|
|
6
|
+
const _RE_GUID = /id=["']?docs-internal-guid-/i;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description Whether the HTML string is a Google Docs clipboard payload.
|
|
10
|
+
* @param {string} html HTML string
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export function isGoogleDocs(html) {
|
|
14
|
+
return _RE_GUID.test(html);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @description Removes the Google Docs clipboard wrapper tags, keeping their children.
|
|
19
|
+
* - Only the guid-carrying inline wrappers (`b`/`span`) are unwrapped; real formatting tags inside are untouched.
|
|
20
|
+
* @param {string} html HTML string
|
|
21
|
+
* @returns {string} HTML string
|
|
22
|
+
*/
|
|
23
|
+
export function cleanHTML(html) {
|
|
24
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
25
|
+
const wrappers = doc.body.querySelectorAll('b[id^="docs-internal-guid-"], span[id^="docs-internal-guid-"]');
|
|
26
|
+
if (wrappers.length === 0) return html;
|
|
27
|
+
|
|
28
|
+
for (let i = 0, len = wrappers.length, w; i < len; i++) {
|
|
29
|
+
w = wrappers[i];
|
|
30
|
+
while (w.firstChild) w.parentNode.insertBefore(w.firstChild, w);
|
|
31
|
+
w.parentNode.removeChild(w);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return doc.body.innerHTML;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
isGoogleDocs,
|
|
39
|
+
cleanHTML,
|
|
40
|
+
};
|
package/src/helper/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import KeyCodeMap from './keyCodeMap';
|
|
|
7
7
|
import Clipboard from './clipboard';
|
|
8
8
|
import Markdown from './markdown';
|
|
9
9
|
import MSOffice from './msOffice';
|
|
10
|
+
import GoogleDocs from './googleDocs';
|
|
10
11
|
|
|
11
12
|
export const env = Env;
|
|
12
13
|
export const unicode = Unicode;
|
|
@@ -17,6 +18,7 @@ export const keyCodeMap = KeyCodeMap;
|
|
|
17
18
|
export const clipboard = Clipboard;
|
|
18
19
|
export const markdown = Markdown;
|
|
19
20
|
export const msOffice = MSOffice;
|
|
21
|
+
export const googleDocs = GoogleDocs;
|
|
20
22
|
|
|
21
23
|
export default {
|
|
22
24
|
env,
|
|
@@ -28,4 +30,5 @@ export default {
|
|
|
28
30
|
clipboard,
|
|
29
31
|
markdown,
|
|
30
32
|
msOffice,
|
|
33
|
+
googleDocs,
|
|
31
34
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PluginCommand, PluginDropdown } from '../../interfaces';
|
|
2
|
-
import { converter, dom } from '../../helper';
|
|
2
|
+
import { converter, dom, env } from '../../helper';
|
|
3
3
|
import { Controller } from '../../modules/contract';
|
|
4
4
|
import { SelectMenu } from '../../modules/ui';
|
|
5
5
|
|
|
@@ -133,6 +133,14 @@ class CodeBlock extends PluginCommand {
|
|
|
133
133
|
this.#hoverSelectMenu.on(this.#hoverButton, this.#onHoverSelect.bind(this));
|
|
134
134
|
this.#buildHoverMenu('');
|
|
135
135
|
|
|
136
|
+
this.$.eventManager.addEvent(containerEl, 'mousedown', (e) => {
|
|
137
|
+
if (env.isMobile || this.#hoverSelectMenu.form?.contains(dom.query.getEventTarget(e))) {
|
|
138
|
+
this.$.store.set('_preventBlur', true);
|
|
139
|
+
} else {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
136
144
|
// selectMenu
|
|
137
145
|
this.$.eventManager.addEvent(this.#hoverButton, 'click', (e) => {
|
|
138
146
|
e.preventDefault();
|
|
@@ -51,3 +51,59 @@ export function isRtlBidiMismatch(
|
|
|
51
51
|
detectedEdge: 'front' | 'end',
|
|
52
52
|
doc: Document,
|
|
53
53
|
): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* @description Whether the caret sits on a bare `<br>` that stands at the front/end edge of its `line`.
|
|
56
|
+
* @param {Range} range - The current range
|
|
57
|
+
* @param {Node} selectionNode - Current selection node
|
|
58
|
+
* @param {'front'|'end'} edge - Edge to test: `front` for Backspace, `end` for Delete
|
|
59
|
+
* @returns {boolean} `true` if the caret is on an edge `<br>`
|
|
60
|
+
*/
|
|
61
|
+
export function isEdgeBreakCaret(range: Range, selectionNode: Node, edge: 'front' | 'end'): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* @description The previous/next element in document order — crossing in and out of blocks (list, quote).
|
|
64
|
+
* - Out of list-cell ancestors (a nested list), stopping at a closure block (table cell).
|
|
65
|
+
* @param {EventPorts['format']} format - Format module
|
|
66
|
+
* @param {?HTMLElement} line - The caret's `line` element
|
|
67
|
+
* @param {'front'|'end'} edge - `front` for the previous element, `end` for the next one
|
|
68
|
+
* @returns {?HTMLElement} The adjacent element, or `null` at the document edge
|
|
69
|
+
*/
|
|
70
|
+
export function getAdjacentElement(
|
|
71
|
+
format: EventPorts['format'],
|
|
72
|
+
line: HTMLElement | null,
|
|
73
|
+
edge: 'front' | 'end',
|
|
74
|
+
): HTMLElement | null;
|
|
75
|
+
/**
|
|
76
|
+
* @description The previous/next `line` in document order — {@link getAdjacentElement} filtered to lines.
|
|
77
|
+
* - `null` means either the document edge or a non-`line` neighbour (a component); use
|
|
78
|
+
* {@link getAdjacentElement} when the two must be told apart.
|
|
79
|
+
* @param {EventPorts['format']} format - Format module
|
|
80
|
+
* @param {?HTMLElement} line - The caret's `line` element
|
|
81
|
+
* @param {'front'|'end'} edge - `front` for the previous line, `end` for the next one
|
|
82
|
+
* @returns {?HTMLElement} The adjacent line, or `null`
|
|
83
|
+
*/
|
|
84
|
+
export function getAdjacentLine(
|
|
85
|
+
format: EventPorts['format'],
|
|
86
|
+
line: HTMLElement | null,
|
|
87
|
+
edge: 'front' | 'end',
|
|
88
|
+
): HTMLElement | null;
|
|
89
|
+
/**
|
|
90
|
+
* @description The neighbouring `line` an empty line collapses into, or `null` when there is nothing to merge.
|
|
91
|
+
* - A cell owning a nested list belongs to {@link getNestedListTarget} instead.
|
|
92
|
+
* @param {EventPorts['format']} format - Format module
|
|
93
|
+
* @param {?HTMLElement} formatEl - The caret's `line` element
|
|
94
|
+
* @param {'front'|'end'} edge - `front` for Backspace (previous line), `end` for Delete (next line)
|
|
95
|
+
* @returns {?HTMLElement} The neighbouring line to merge into, or `null`
|
|
96
|
+
*/
|
|
97
|
+
export function getEmptyLineMergeTarget(
|
|
98
|
+
format: EventPorts['format'],
|
|
99
|
+
formatEl: HTMLElement | null,
|
|
100
|
+
edge: 'front' | 'end',
|
|
101
|
+
): HTMLElement | null;
|
|
102
|
+
/**
|
|
103
|
+
* @description The nested list a list-cell Backspace/Delete would lift, or `null` when there is none.
|
|
104
|
+
* - The rules gate their list branch on it so the branch can't claim the key with nothing to do.
|
|
105
|
+
* @param {HTMLElement} formatEl - The caret's list cell
|
|
106
|
+
* @param {HTMLElement} rangeEl - The list (`UL`/`OL`) the cell belongs to
|
|
107
|
+
* @returns {?HTMLElement} The element carrying the nested list, or `null`
|
|
108
|
+
*/
|
|
109
|
+
export function getNestedListTarget(formatEl: HTMLElement, rangeEl: HTMLElement): HTMLElement | null;
|
|
@@ -47,5 +47,10 @@ declare class BlockHandle {
|
|
|
47
47
|
* @description Cleanup — remove listeners, destroy menus, null references.
|
|
48
48
|
*/
|
|
49
49
|
destroy(): void;
|
|
50
|
+
/**
|
|
51
|
+
* @internal
|
|
52
|
+
* @description Editor keyboard activity (typing, Enter, etc.)
|
|
53
|
+
*/
|
|
54
|
+
hideOnKeyDown(): void;
|
|
50
55
|
#private;
|
|
51
56
|
}
|
|
@@ -28,6 +28,7 @@ export namespace DEFAULTS {
|
|
|
28
28
|
'@text': string;
|
|
29
29
|
'@line': string;
|
|
30
30
|
'@component': string;
|
|
31
|
+
li: string;
|
|
31
32
|
'table|th|td': string;
|
|
32
33
|
'table|td': string;
|
|
33
34
|
tr: string;
|
|
@@ -400,7 +401,7 @@ export namespace DEFAULTS {
|
|
|
400
401
|
* - Value is a pipe-delimited list of allowed style names.
|
|
401
402
|
* - Resolution order when filtering an element: `@component` (for `.se-component` containers) → explicit tag entry → `@line` (for formatLine elements) → `@text` (for textStyleTags).
|
|
402
403
|
* - `@component` guards the inline sizing (`width`/`height`/`min-width`) the editor writes on a media component's container for percentage-based sizes; keep these so a clean() round-trip does not reset the component to full width.
|
|
403
|
-
* - An explicit tag entry **
|
|
404
|
+
* - An explicit tag entry is **merged** with its category default when the tag belongs to one (`@line` for formatLine elements, else `@text` for textStyleTags) — the entry adds styles on top of the category's.
|
|
404
405
|
* - Merged with {@link DEFAULTS.TAG_STYLES}; user-supplied keys win.
|
|
405
406
|
* ```js
|
|
406
407
|
* {
|
|
@@ -408,8 +409,8 @@ export namespace DEFAULTS {
|
|
|
408
409
|
* '@text': 'color|font-size|background-color', // default for span, b, i, em, ...
|
|
409
410
|
* '@line': 'text-align|margin|line-height', // default for p, h1-h6, div, li, ...
|
|
410
411
|
* 'table|td': 'border|color|background-color', // per-tag whitelist
|
|
411
|
-
*
|
|
412
|
-
*
|
|
412
|
+
* div: 'color', // merged with the `@line` default (div is a line element)
|
|
413
|
+
* hr: 'border-top',
|
|
413
414
|
* }
|
|
414
415
|
* }
|
|
415
416
|
* ```
|
|
@@ -1254,7 +1255,7 @@ export type EditorBaseOptions = {
|
|
|
1254
1255
|
* - Value is a pipe-delimited list of allowed style names.
|
|
1255
1256
|
* - Resolution order when filtering an element: `@component` (for `.se-component` containers) → explicit tag entry → `@line` (for formatLine elements) → `@text` (for textStyleTags).
|
|
1256
1257
|
* - `@component` guards the inline sizing (`width`/`height`/`min-width`) the editor writes on a media component's container for percentage-based sizes; keep these so a clean() round-trip does not reset the component to full width.
|
|
1257
|
-
* - An explicit tag entry **
|
|
1258
|
+
* - An explicit tag entry is **merged** with its category default when the tag belongs to one (`@line` for formatLine elements, else `@text` for textStyleTags) — the entry adds styles on top of the category's.
|
|
1258
1259
|
* - Merged with {@link DEFAULTS.TAG_STYLES}; user-supplied keys win.
|
|
1259
1260
|
* ```js
|
|
1260
1261
|
* {
|
|
@@ -1262,7 +1263,7 @@ export type EditorBaseOptions = {
|
|
|
1262
1263
|
* '@text': 'color|font-size|background-color', // default for span, b, i, em, ...
|
|
1263
1264
|
* '@line': 'text-align|margin|line-height', // default for p, h1-h6, div, li, ...
|
|
1264
1265
|
* 'table|td': 'border|color|background-color', // per-tag whitelist
|
|
1265
|
-
* div: 'color',
|
|
1266
|
+
* div: 'color', // merged with the `@line` default (div is a line element)
|
|
1266
1267
|
* hr: 'border-top',
|
|
1267
1268
|
* }
|
|
1268
1269
|
* }
|
package/types/events.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type ClipboardEvent = {
|
|
|
35
35
|
*/
|
|
36
36
|
maxCharCount: boolean;
|
|
37
37
|
/**
|
|
38
|
-
* - `"SE"`|`"MS"`|`""` - source
|
|
38
|
+
* - `"SE"`|`"MS"`|`"GOOGLE"`|`""` - source
|
|
39
39
|
*/
|
|
40
40
|
from: string;
|
|
41
41
|
};
|
|
@@ -289,7 +289,7 @@ export type EventHandlers = {
|
|
|
289
289
|
* @property {Event} event - event object
|
|
290
290
|
* @property {string} data - drop data
|
|
291
291
|
* @property {boolean} maxCharCount - is max char count
|
|
292
|
-
* @property {string} from - `"SE"`|`"MS"`|`""` - source
|
|
292
|
+
* @property {string} from - `"SE"`|`"MS"`|`"GOOGLE"`|`""` - source
|
|
293
293
|
*/
|
|
294
294
|
/**
|
|
295
295
|
* @typedef {Object} FileManagementInfo
|
|
@@ -198,7 +198,8 @@ export function getEdgeChildNodes(
|
|
|
198
198
|
};
|
|
199
199
|
/**
|
|
200
200
|
* @template {Node} T
|
|
201
|
-
* @description Gets the previous sibling last child. If there is no sibling, then it'll take it from the closest ancestor with child
|
|
201
|
+
* @description Gets the previous sibling last child. If there is no sibling, then it'll take it from the closest ancestor with child.
|
|
202
|
+
* - Components (image, table, etc.) are treated as a single tag and not traversed into.
|
|
202
203
|
* @param {Node} node Reference element
|
|
203
204
|
* @param {?Node} [ceiling] Highest boundary allowed
|
|
204
205
|
* @returns {T|null} Not found: `null`
|
|
@@ -206,7 +207,8 @@ export function getEdgeChildNodes(
|
|
|
206
207
|
export function getPreviousDeepestNode<T extends Node>(node: Node, ceiling?: Node | null): T | null;
|
|
207
208
|
/**
|
|
208
209
|
* @template {Node} T
|
|
209
|
-
* @description Gets the next sibling first child. If there is no sibling, then it'll take it from the closest ancestor with child
|
|
210
|
+
* @description Gets the next sibling first child. If there is no sibling, then it'll take it from the closest ancestor with child.
|
|
211
|
+
* - Components (image, table, etc.) are treated as a single tag and not traversed into.
|
|
210
212
|
* @param {Node} node Reference element
|
|
211
213
|
* @param {?Node} [ceiling] Highest boundary allowed
|
|
212
214
|
* @returns {T|null} Not found: `null`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type {} from '../typedef';
|
|
2
|
+
/**
|
|
3
|
+
* @description Whether the HTML string is a Google Docs clipboard payload.
|
|
4
|
+
* @param {string} html HTML string
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export function isGoogleDocs(html: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* @description Removes the Google Docs clipboard wrapper tags, keeping their children.
|
|
10
|
+
* - Only the guid-carrying inline wrappers (`b`/`span`) are unwrapped; real formatting tags inside are untouched.
|
|
11
|
+
* @param {string} html HTML string
|
|
12
|
+
* @returns {string} HTML string
|
|
13
|
+
*/
|
|
14
|
+
export function cleanHTML(html: string): string;
|
|
15
|
+
declare namespace _default {
|
|
16
|
+
export { isGoogleDocs };
|
|
17
|
+
export { cleanHTML };
|
|
18
|
+
}
|
|
19
|
+
export default _default;
|
package/types/helper/index.d.ts
CHANGED
|
@@ -178,6 +178,10 @@ export const markdown: {
|
|
|
178
178
|
export const msOffice: {
|
|
179
179
|
cleanHTML: typeof import('./msOffice').cleanHTML;
|
|
180
180
|
};
|
|
181
|
+
export const googleDocs: {
|
|
182
|
+
isGoogleDocs: typeof import('./googleDocs').isGoogleDocs;
|
|
183
|
+
cleanHTML: typeof import('./googleDocs').cleanHTML;
|
|
184
|
+
};
|
|
181
185
|
declare namespace _default {
|
|
182
186
|
export { env };
|
|
183
187
|
export { unicode };
|
|
@@ -188,5 +192,6 @@ declare namespace _default {
|
|
|
188
192
|
export { clipboard };
|
|
189
193
|
export { markdown };
|
|
190
194
|
export { msOffice };
|
|
195
|
+
export { googleDocs };
|
|
191
196
|
}
|
|
192
197
|
export default _default;
|