anki/ts/editor/surround.ts
Henrik Giesel 30bbbaf00b
Use eslint for sorting our imports (#1637)
* Make eslint sort our imports

* fix missing deps in eslint rule (dae)

Caught on Linux due to the stricter sandboxing

* Remove exports-last eslint rule (for now?)

* Adjust browserslist settings

- We use ResizeObserver which is not supported in browsers like KaiOS,
  Baidu or Android UC

* Raise minimum iOS version 13.4

- It's the first version that supports ResizeObserver

* Apply new eslint rules to sort imports
2022-02-04 18:36:34 +10:00

115 lines
3.4 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { get } from "svelte/store";
import type { ElementClearer, ElementMatcher } from "../domlib/surround";
import { findClosest, surroundNoSplitting, unsurround } from "../domlib/surround";
import { getRange, getSelection } from "../lib/cross-browser";
import type { RichTextInputAPI } from "./rich-text-input";
function isSurroundedInner(
range: AbstractRange,
base: HTMLElement,
matcher: ElementMatcher,
): boolean {
return Boolean(
findClosest(range.startContainer, base, matcher) ||
findClosest(range.endContainer, base, matcher),
);
}
function surroundAndSelect(
matches: boolean,
range: Range,
selection: Selection,
surroundElement: Element,
base: HTMLElement,
matcher: ElementMatcher,
clearer: ElementClearer,
): void {
const { surroundedRange } = matches
? unsurround(range, surroundElement, base, matcher, clearer)
: surroundNoSplitting(range, surroundElement, base, matcher, clearer);
selection.removeAllRanges();
selection.addRange(surroundedRange);
}
export interface GetSurrounderResult {
surroundCommand(
surroundElement: Element,
matcher: ElementMatcher,
clearer?: ElementClearer,
): Promise<void>;
isSurrounded(matcher: ElementMatcher): Promise<boolean>;
}
export function getSurrounder(richTextInput: RichTextInputAPI): GetSurrounderResult {
const { add, remove, active } = richTextInput.getTriggerOnNextInsert();
async function isSurrounded(matcher: ElementMatcher): Promise<boolean> {
const base = await richTextInput.element;
const selection = getSelection(base)!;
const range = getRange(selection);
if (!range) {
return false;
}
const isSurrounded = isSurroundedInner(range, base, matcher);
return get(active) ? !isSurrounded : isSurrounded;
}
async function surroundCommand(
surroundElement: Element,
matcher: ElementMatcher,
clearer: ElementClearer = () => false,
): Promise<void> {
const base = await richTextInput.element;
const selection = getSelection(base)!;
const range = getRange(selection);
if (!range) {
return;
} else if (range.collapsed) {
if (get(active)) {
remove();
} else {
add(async ({ node }: { node: Node }) => {
range.selectNode(node);
const matches = Boolean(findClosest(node, base, matcher));
surroundAndSelect(
matches,
range,
selection,
surroundElement,
base,
matcher,
clearer,
);
selection.collapseToEnd();
});
}
} else {
const matches = isSurroundedInner(range, base, matcher);
surroundAndSelect(
matches,
range,
selection,
surroundElement,
base,
matcher,
clearer,
);
}
}
return {
surroundCommand,
isSurrounded,
};
}