fix(creator-studio): delete selected hidden blocks reliably
This commit is contained in:
1 parent
304c59c8f3
commit
d391b0fd00
4 files changed
+124
-21
No files matched your search
@@ -59,11 +59,25 @@ function topLevelBlock(node: Node, body: HTMLElement): Element | null {
|
||||
return current?.parentElement === body ? current : null;
|
||||
}
|
||||
|
||||
function selectedTopLevelBlock(range: Range, body: HTMLElement): Element | null {
|
||||
if (range.startContainer !== body || range.endContainer !== body || range.endOffset !== range.startOffset + 1) {
|
||||
return null;
|
||||
}
|
||||
const node = body.childNodes[range.startOffset];
|
||||
return node?.nodeType === 1 && isStructuralBlock(node as Element) ? node as Element : null;
|
||||
}
|
||||
|
||||
function selectBlock(editor: any, event: any, block: Element): void {
|
||||
event.preventDefault();
|
||||
editor.selection.select(block);
|
||||
editor.nodeChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Backspace / Delete 删除相邻的块级元素。
|
||||
*
|
||||
* 光标位于正文块边界时删除相邻的结构块;位于结构块自身的首尾时删除
|
||||
* 整个块。选区中的文字和块内非边界位置交给 TinyMCE 正常处理。
|
||||
* 整个块。整块选区直接删除;文字选区和块内非边界位置交给 TinyMCE 处理。
|
||||
*/
|
||||
export function removeAdjacentBlock(editor: any, event: any): void {
|
||||
const key = event.key || (event.keyCode === 8 ? 'Backspace' : event.keyCode === 46 ? 'Delete' : '');
|
||||
@@ -72,7 +86,7 @@ export function removeAdjacentBlock(editor: any, event: any): void {
|
||||
return;
|
||||
}
|
||||
const selection = editor.selection;
|
||||
if (!selection || !selection.isCollapsed()) {
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
const body = editor.getBody() as HTMLElement;
|
||||
@@ -80,18 +94,27 @@ export function removeAdjacentBlock(editor: any, event: any): void {
|
||||
if (!body || !range || !body.contains(range.startContainer)) {
|
||||
return;
|
||||
}
|
||||
const collapsed = selection.isCollapsed();
|
||||
const selected = collapsed ? null : selectedTopLevelBlock(range, body);
|
||||
if (!collapsed && !selected) {
|
||||
return;
|
||||
}
|
||||
const backward = key === 'Backspace';
|
||||
const current = topLevelBlock(range.startContainer, body);
|
||||
let target: Element | null = null;
|
||||
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)) {
|
||||
commitDeletion(editor, event, () => {
|
||||
const offset = Array.prototype.indexOf.call(body.childNodes, current);
|
||||
editor.dom.remove(current);
|
||||
selection.setCursorLocation(body, offset);
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -110,7 +133,7 @@ export function removeAdjacentBlock(editor: any, event: any): void {
|
||||
}
|
||||
target = isStructuralBlock(current)
|
||||
? current : backward ? current.previousElementSibling : current.nextElementSibling;
|
||||
} else if (range.startContainer === body) {
|
||||
} else if (!selected && range.startContainer === body) {
|
||||
let offset = range.startOffset + (backward ? -1 : 0);
|
||||
const step = backward ? -1 : 1;
|
||||
while (body.childNodes[offset]?.nodeType === 3 && !body.childNodes[offset].textContent?.trim()) {
|
||||
@@ -121,8 +144,12 @@ export function removeAdjacentBlock(editor: any, event: any): void {
|
||||
if (!target || !isStructuralBlock(target)) {
|
||||
return;
|
||||
}
|
||||
if (!selected && target.matches('.tinymce-hide')) {
|
||||
selectBlock(editor, event, target);
|
||||
return;
|
||||
}
|
||||
commitDeletion(editor, event, () => {
|
||||
if (target === current) {
|
||||
if (target === current || selected) {
|
||||
const previous = target.previousElementSibling;
|
||||
const next = target.nextElementSibling;
|
||||
const destination = backward
|
||||
|
||||
@@ -136,12 +136,12 @@ export const HIDE_BLOCKS: Record<HideKind, { open: string; close: string; label:
|
||||
*
|
||||
* .hide-before 里的短代码文本通过 CSS 的 font-size:0 隐藏,只显示 data-jio-hide-label;
|
||||
* .hide-after 与前台投稿编辑器一致地保持 display:none。两者都留在 DOM 里,保存时
|
||||
* 不需要额外的序列化转换。
|
||||
* 不需要额外的序列化转换。外层不可编辑,只有正文子容器可编辑。
|
||||
*/
|
||||
export function hiddenContentMarkup(kind: HideKind, innerHtml: string): string {
|
||||
const def = HIDE_BLOCKS[kind];
|
||||
return (
|
||||
`<div class="tinymce-hide" data-jio-hide-type="${kind}">` +
|
||||
`<div class="tinymce-hide" data-jio-hide-type="${kind}" contenteditable="false">` +
|
||||
`<p class="hide-before" data-jio-hide-label="${escapeHtml(def.label)}">${escapeHtml(def.open)}</p>` +
|
||||
`<div contenteditable="true">${normalizeInner(innerHtml)}</div>` +
|
||||
`<p class="hide-after">${escapeHtml(def.close)}</p>` +
|
||||
|
||||
@@ -154,6 +154,10 @@ export function hydrateBlocks(html: string): string {
|
||||
container.innerHTML = html;
|
||||
|
||||
container.querySelectorAll('pre').forEach((pre) => hydrateCodeBlock(pre));
|
||||
container.querySelectorAll<HTMLElement>('.tinymce-hide').forEach((block) => {
|
||||
block.setAttribute('contenteditable', 'false');
|
||||
block.querySelector(':scope > div')?.setAttribute('contenteditable', 'true');
|
||||
});
|
||||
container.querySelectorAll('p').forEach((paragraph) => {
|
||||
const match = paragraph.textContent?.trim().match(/^\[jio_math display="1"\]([\s\S]*?)\[\/jio_math\]$/);
|
||||
if (!match) {
|
||||
|
||||
Reference in new issue
Block a user