mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
Fix the caret flushing behavior when moving out of a Mathjax element (#1605)
* Fix the caret flushing behavior when moving out of a Mathjax element * Fix typing issue and add docs for ContentEditableAPI
This commit is contained in:
parent
0bb04e2951
commit
f90895995f
3 changed files with 54 additions and 49 deletions
|
@ -10,9 +10,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { updateAllState } from "../components/WithState.svelte";
|
import { updateAllState } from "../components/WithState.svelte";
|
||||||
import actionList from "../sveltelib/action-list";
|
import actionList from "../sveltelib/action-list";
|
||||||
import contentEditableAPI, {
|
import {
|
||||||
saveLocation,
|
customFocusHandling,
|
||||||
initialFocusHandling,
|
|
||||||
preventBuiltinContentEditableShortcuts,
|
preventBuiltinContentEditableShortcuts,
|
||||||
} from "./content-editable";
|
} from "./content-editable";
|
||||||
import type { ContentEditableAPI } from "./content-editable";
|
import type { ContentEditableAPI } from "./content-editable";
|
||||||
|
@ -33,14 +32,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
export let api: Partial<ContentEditableAPI>;
|
export let api: Partial<ContentEditableAPI>;
|
||||||
|
|
||||||
Object.assign(api, contentEditableAPI);
|
const { setupFocusHandling, flushCaret } = customFocusHandling();
|
||||||
|
|
||||||
|
Object.assign(api, { flushCaret });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<anki-editable
|
<anki-editable
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
use:resolve
|
use:resolve
|
||||||
use:saveLocation
|
use:setupFocusHandling
|
||||||
use:initialFocusHandling
|
|
||||||
use:preventBuiltinContentEditableShortcuts
|
use:preventBuiltinContentEditableShortcuts
|
||||||
use:mirrorAction={mirrorOptions}
|
use:mirrorAction={mirrorOptions}
|
||||||
use:managerAction={{}}
|
use:managerAction={{}}
|
||||||
|
|
|
@ -9,16 +9,6 @@ import { isApplePlatform } from "../lib/platform";
|
||||||
import { bridgeCommand } from "../lib/bridgecommand";
|
import { bridgeCommand } from "../lib/bridgecommand";
|
||||||
import type { SelectionLocation } from "../domlib/location";
|
import type { SelectionLocation } from "../domlib/location";
|
||||||
|
|
||||||
const locationEvents: (() => void)[] = [];
|
|
||||||
|
|
||||||
function flushLocation(): void {
|
|
||||||
let removeEvent: (() => void) | undefined;
|
|
||||||
|
|
||||||
while ((removeEvent = locationEvents.pop())) {
|
|
||||||
removeEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function safePlaceCaretAfterContent(editable: HTMLElement): void {
|
function safePlaceCaretAfterContent(editable: HTMLElement): void {
|
||||||
/**
|
/**
|
||||||
* Workaround: If you try to invoke an IME after calling
|
* Workaround: If you try to invoke an IME after calling
|
||||||
|
@ -44,37 +34,53 @@ function onFocus(location: SelectionLocation | null): () => void {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBlur(this: HTMLElement): void {
|
interface CustomFocusHandlingAPI {
|
||||||
prepareFocusHandling(this, saveSelection(this));
|
setupFocusHandling(element: HTMLElement): { destroy(): void };
|
||||||
|
flushCaret(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareFocusHandling(
|
export function customFocusHandling(): CustomFocusHandlingAPI {
|
||||||
editable: HTMLElement,
|
const focusHandlingEvents: (() => void)[] = [];
|
||||||
latestLocation: SelectionLocation | null = null,
|
|
||||||
): void {
|
|
||||||
const removeOnFocus = on(editable, "focus", onFocus(latestLocation), {
|
|
||||||
once: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
locationEvents.push(
|
function flushEvents(): void {
|
||||||
removeOnFocus,
|
let removeEvent: (() => void) | undefined;
|
||||||
on(editable, "pointerdown", removeOnFocus, { once: true }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initialFocusHandling(editable: HTMLElement): void {
|
while ((removeEvent = focusHandlingEvents.pop())) {
|
||||||
prepareFocusHandling(editable);
|
removeEvent();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Must execute before DOMMirror */
|
function prepareFocusHandling(
|
||||||
export function saveLocation(editable: HTMLElement): { destroy(): void } {
|
editable: HTMLElement,
|
||||||
const removeOnBlur = on(editable, "blur", onBlur);
|
latestLocation: SelectionLocation | null = null,
|
||||||
|
): void {
|
||||||
|
const off = on(editable, "focus", onFocus(latestLocation), { once: true });
|
||||||
|
|
||||||
|
focusHandlingEvents.push(off, on(editable, "pointerdown", off, { once: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must execute before DOMMirror.
|
||||||
|
*/
|
||||||
|
function onBlur(this: HTMLElement): void {
|
||||||
|
prepareFocusHandling(this, saveSelection(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupFocusHandling(editable: HTMLElement): { destroy(): void } {
|
||||||
|
prepareFocusHandling(editable);
|
||||||
|
const off = on(editable, "blur", onBlur);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
flushEvents();
|
||||||
|
off();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
destroy() {
|
setupFocusHandling,
|
||||||
removeOnBlur();
|
flushCaret: flushEvents,
|
||||||
flushLocation();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,11 +97,10 @@ export function preventBuiltinContentEditableShortcuts(editable: HTMLElement): v
|
||||||
/** API */
|
/** API */
|
||||||
|
|
||||||
export interface ContentEditableAPI {
|
export interface ContentEditableAPI {
|
||||||
flushLocation(): void;
|
/**
|
||||||
|
* Can be used to turn off the caret restoring functionality of
|
||||||
|
* the ContentEditable. Can be used when you want to set the caret
|
||||||
|
* yourself.
|
||||||
|
*/
|
||||||
|
flushCaret(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentEditableApi: ContentEditableAPI = {
|
|
||||||
flushLocation,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default contentEditableApi;
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { Mathjax } from "../../editable/mathjax-element";
|
import { Mathjax } from "../../editable/mathjax-element";
|
||||||
|
|
||||||
const { container, api } = getRichTextInput();
|
const { container, api } = getRichTextInput();
|
||||||
const { flushLocation, preventResubscription } = api;
|
const { flushCaret, preventResubscription } = api;
|
||||||
|
|
||||||
const code = writable("");
|
const code = writable("");
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let selectAll = false;
|
let selectAll = false;
|
||||||
|
|
||||||
function placeHandle(after: boolean): void {
|
function placeHandle(after: boolean): void {
|
||||||
flushLocation();
|
flushCaret();
|
||||||
|
|
||||||
if (after) {
|
if (after) {
|
||||||
(mathjaxElement as any).placeCaretAfter();
|
(mathjaxElement as any).placeCaretAfter();
|
||||||
|
|
Loading…
Reference in a new issue