fix(creator-studio): unify block and inline marker deletion

This commit is contained in:
kate522 committed 2026-11-10 20:10:48 +08:00
1 parent d391b0fd00
commit 30ceab4615
3 files changed
+193 -47

No files matched your search

+79 -39
View File
@@ -9,6 +9,7 @@
/** 按键删除时作为整体处理的正文顶层块。 */
const STRUCTURAL_SELECTOR = 'table,pre,hr,figure,ul,ol,.quote_q,.tinymce-hide,.new-dplayer,.wp-block-embed';
const WRAPPED_MEDIA_SELECTOR = '.jio-math--block,img,iframe,video,audio,.jio-file-link';
const INLINE_ATOM_SELECTOR = '.jio-footnote-ref,.jio-math:not(.jio-math--block)';
const NON_TEXT_CONTENT_SELECTOR = 'img,iframe,video,audio,table,pre,hr,figure,.jio-math,.jio-footnote-ref,.jio-file-link,.new-dplayer';
function isStructuralBlock(node: Element): boolean {
@@ -27,6 +28,9 @@ function isStructuralBlock(node: Element): boolean {
}
function isAtEdge(range: Range, container: Element, backward: boolean): boolean {
if (container.tagName === 'PRE' && container.childNodes.length === 1 && container.firstChild?.nodeName === 'BR') {
return true;
}
const remainder = range.cloneRange();
remainder.selectNodeContents(container);
if (backward) {
@@ -67,6 +71,46 @@ function selectedTopLevelBlock(range: Range, body: HTMLElement): Element | null
return node?.nodeType === 1 && isStructuralBlock(node as Element) ? node as Element : null;
}
function selectedInlineAtom(range: Range, body: HTMLElement): Element | null {
if (range.startContainer !== range.endContainer || range.endOffset !== range.startOffset + 1) {
return null;
}
const node = range.startContainer.childNodes[range.startOffset];
return node?.nodeType === 1 && body.contains(node) && (node as Element).matches(INLINE_ATOM_SELECTOR)
? node as Element : null;
}
function adjacentNode(range: Range, body: HTMLElement, backward: boolean): Node | null {
let container = range.startContainer;
let offset = range.startOffset;
while (body.contains(container)) {
if (container.nodeType === 3) {
if (backward ? offset > 0 : offset < (container.textContent || '').length) {
return null;
}
const parent = container.parentNode;
if (!parent) return null;
offset = Array.prototype.indexOf.call(parent.childNodes, container) + (backward ? 0 : 1);
container = parent;
continue;
}
const candidate = container.childNodes[offset + (backward ? -1 : 0)];
if (candidate) {
if ((candidate.nodeType === 3 && !candidate.textContent?.length)
|| (candidate.nodeType === 1 && (candidate as Element).matches('br[data-mce-bogus]'))) {
offset += backward ? -1 : 1;
continue;
}
return candidate;
}
if (container === body || !container.parentNode) return null;
const parent = container.parentNode;
offset = Array.prototype.indexOf.call(parent.childNodes, container) + (backward ? 0 : 1);
container = parent;
}
return null;
}
function selectBlock(editor: any, event: any, block: Element): void {
event.preventDefault();
editor.selection.select(block);
@@ -76,8 +120,8 @@ function selectBlock(editor: any, event: any, block: Element): void {
/**
* Backspace / Delete 删除相邻的块级元素。
*
* 光标位于正文块边界时删除相邻的结构块;位于结构块自身的首尾时删除
* 整个块。整块选区直接删除;文字选区和块内非边界位置交给 TinyMCE 处理。
* 光标位于结构块边界时先选中目标,再按一次删除;行内注释与公式同理。
* 文字选区和块内非边界位置交给 TinyMCE 处理。
*/
export function removeAdjacentBlock(editor: any, event: any): void {
const key = event.key || (event.keyCode === 8 ? 'Backspace' : event.keyCode === 46 ? 'Delete' : '');
@@ -96,25 +140,34 @@ export function removeAdjacentBlock(editor: any, event: any): void {
}
const collapsed = selection.isCollapsed();
const selected = collapsed ? null : selectedTopLevelBlock(range, body);
if (!collapsed && !selected) {
const selectedInline = collapsed ? null : selectedInlineAtom(range, body);
if (!collapsed && !selected && !selectedInline) {
return;
}
const backward = key === 'Backspace';
if (selectedInline) {
commitDeletion(editor, event, () => {
const parent = selectedInline.parentNode;
const offset = Array.prototype.indexOf.call(parent.childNodes, selectedInline);
editor.dom.remove(selectedInline);
selection.setCursorLocation(parent, offset);
});
return;
}
if (collapsed) {
const adjacent = adjacentNode(range, body, backward);
if (adjacent?.nodeType === 1 && (adjacent as Element).matches(INLINE_ATOM_SELECTOR)) {
selectBlock(editor, event, adjacent as Element);
return;
}
}
const current = selected ? null : topLevelBlock(range.startContainer, body);
let target: Element | null = selected;
if (current) {
if (isBlankParagraph(current)) {
const adjacent = backward ? current.previousElementSibling : current.nextElementSibling;
if (adjacent && isStructuralBlock(adjacent) && isAtEdge(range, current, backward)) {
if (adjacent.matches('.tinymce-hide')) {
selectBlock(editor, event, adjacent);
} else {
commitDeletion(editor, event, () => {
const offset = Array.prototype.indexOf.call(body.childNodes, current);
editor.dom.remove(current);
selection.setCursorLocation(body, offset);
});
}
selectBlock(editor, event, adjacent);
}
return;
}
@@ -144,38 +197,25 @@ export function removeAdjacentBlock(editor: any, event: any): void {
if (!target || !isStructuralBlock(target)) {
return;
}
if (!selected && target.matches('.tinymce-hide')) {
if (!selected) {
selectBlock(editor, event, target);
return;
}
commitDeletion(editor, event, () => {
if (target === current || selected) {
const previous = target.previousElementSibling;
const next = target.nextElementSibling;
const destination = backward
? [previous, next].find((item) => item && !isStructuralBlock(item))
: [next, previous].find((item) => item && !isStructuralBlock(item));
if (destination) {
editor.dom.remove(target);
selection.setCursorLocation(destination, destination === previous ? destination.childNodes.length : 0);
} else {
const paragraph = body.ownerDocument.createElement('p');
paragraph.appendChild(body.ownerDocument.createElement('br'));
body.insertBefore(paragraph, target);
editor.dom.remove(target);
selection.setCursorLocation(paragraph, 0);
}
} else {
const next = target.nextSibling;
const previous = target.previousElementSibling;
const next = target.nextElementSibling;
const destination = backward
? [previous, next].find((item) => item && !isStructuralBlock(item))
: [next, previous].find((item) => item && !isStructuralBlock(item));
if (destination) {
editor.dom.remove(target);
if (!current && !Array.from(body.children).some((child) => !isStructuralBlock(child))) {
const paragraph = body.ownerDocument.createElement('p');
paragraph.appendChild(body.ownerDocument.createElement('br'));
body.insertBefore(paragraph, next);
selection.setCursorLocation(paragraph, 0);
} else {
selection.setRng(range);
}
selection.setCursorLocation(destination, destination === previous ? destination.childNodes.length : 0);
} else {
const paragraph = body.ownerDocument.createElement('p');
paragraph.appendChild(body.ownerDocument.createElement('br'));
body.insertBefore(paragraph, target);
editor.dom.remove(target);
selection.setCursorLocation(paragraph, 0);
}
});
}
@@ -88,7 +88,7 @@ export function mathMarkup({ formula, display }: MathOptions): string {
const attrs = `data-jio-math-source="${escapeHtml(formula.trim())}" data-jio-math-display="${display ? '1' : '0'}"`;
return display
? `<p><span class="jio-math jio-math--block" ${attrs} contenteditable="false">${body}</span></p>`
: `<span class="jio-math" ${attrs}>${body}</span>`;
: `<span class="jio-math" ${attrs} contenteditable="false">${body}</span>`;
}
/**