From 55c64e5b54c5b9eec3f456cf9581f4c5e264769c Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sun, 27 Feb 2022 08:58:01 +0100 Subject: [PATCH] Fix Mathjax sometimes being deleted even though it's not selected (#1696) * Fix frame element being deleted, when a frame handle is deleted from while selected * Fix mixing up preceding/following --- ts/editable/frame-element.ts | 46 ++++++++++++++++-------------------- ts/editable/frame-handle.ts | 33 +++++++++++++++++++++----- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/ts/editable/frame-element.ts b/ts/editable/frame-element.ts index 8445f9106..37f446a0f 100644 --- a/ts/editable/frame-element.ts +++ b/ts/editable/frame-element.ts @@ -13,7 +13,7 @@ import { import { on } from "../lib/events"; import type { FrameHandle } from "./frame-handle"; import { - checkWhetherMovingIntoHandle, + checkHandles, frameElementTagName, FrameEnd, FrameStart, @@ -32,43 +32,39 @@ function restoreFrameHandles(mutations: MutationRecord[]): void { continue; } - /** - * In some rare cases, nodes might be inserted into the frame itself. - * For example after using execCommand. - */ - const placement = node.compareDocumentPosition(framed); + // In some rare cases, nodes might be inserted into the frame itself. + // For example after using execCommand. + const placement = framed.compareDocumentPosition(node); - if (placement & Node.DOCUMENT_POSITION_FOLLOWING) { - referenceNode = moveChildOutOfElement(frameElement, node, "afterend"); - continue; - } else if (placement & Node.DOCUMENT_POSITION_PRECEDING) { + if (placement & Node.DOCUMENT_POSITION_PRECEDING) { referenceNode = moveChildOutOfElement( frameElement, node, "beforebegin", ); - continue; + } else if (placement & Node.DOCUMENT_POSITION_FOLLOWING) { + referenceNode = moveChildOutOfElement(frameElement, node, "afterend"); } } for (const node of mutation.removedNodes) { - if ( - /* avoid triggering when (un)mounting whole frame */ - mutations.length === 1 && - nodeIsElement(node) && - isFrameHandle(node) - ) { - /* When deleting from _outer_ position in FrameHandle to _inner_ position */ - frameElement.remove(); + if (!isFrameHandle(node)) { continue; } if ( - nodeIsElement(node) && - isFrameHandle(node) && - frameElement.isConnected && - !frameElement.block + /* avoid triggering when (un)mounting whole frame */ + mutations.length === 1 && + !node.partiallySelected ) { + // Similar to a "movein", this could be considered a + // "deletein" event and could get some special treatment, e.g. + // first highlight the entire frame-element. + frameElement.remove(); + continue; + } + + if (frameElement.isConnected && !frameElement.block) { frameElement.refreshHandles(); continue; } @@ -248,7 +244,7 @@ function checkIfInsertingLineBreakAdjacentToBlockFrame() { } function onSelectionChange() { - checkWhetherMovingIntoHandle(); + checkHandles(); checkIfInsertingLineBreakAdjacentToBlockFrame(); } @@ -259,7 +255,7 @@ document.addEventListener("selectionchange", onSelectionChange); * * * - * + * * */ export function frameElement(element: HTMLElement, block: boolean): FrameElement { diff --git a/ts/editable/frame-handle.ts b/ts/editable/frame-handle.ts index 7d8223a57..5392579cf 100644 --- a/ts/editable/frame-handle.ts +++ b/ts/editable/frame-handle.ts @@ -120,6 +120,14 @@ export abstract class FrameHandle extends HTMLElement { return ["data-frames"]; } + /** + * When a deletion is trigger with a FrameHandle selected, it will be treated + * differently depending on whether it is selected: + * - If partially selected, it should be restored (unless the frame element + * is also selected). + * - Otherwise, it should be deleted along with the frame element. + */ + partiallySelected = false; frames?: string; constructor() { @@ -273,15 +281,28 @@ export class FrameEnd extends FrameHandle { } } -export function checkWhetherMovingIntoHandle(): void { +function checkWhetherMovingIntoHandle(selection: Selection, handle: FrameHandle): void { + if (selection.anchorNode === handle.firstChild && isSelectionCollapsed(selection)) { + handle.notifyMoveIn(selection.anchorOffset); + } +} + +function checkWhetherSelectingHandle(selection: Selection, handle: FrameHandle): void { + handle.partiallySelected = + handle.firstChild && !isSelectionCollapsed(selection) + ? selection.containsNode(handle.firstChild) + : false; +} + +export function checkHandles(): void { for (const handle of handles) { const selection = getSelection(handle)!; - if ( - selection.anchorNode === handle.firstChild && - isSelectionCollapsed(selection) - ) { - handle.notifyMoveIn(selection.anchorOffset); + if (selection.rangeCount === 0) { + return; } + + checkWhetherMovingIntoHandle(selection, handle); + checkWhetherSelectingHandle(selection, handle); } }