Add back wrap function (#1551)

* Remove surround from RichText and PlainText

* Export wrap
This commit is contained in:
Henrik Giesel 2021-12-13 05:00:35 +01:00 committed by GitHub
parent b714974464
commit 4966f3cc44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 83 additions and 62 deletions

View file

@ -69,9 +69,9 @@ samp {
--base-font-size: 14px;
}
[dir=rtl] {
[dir="rtl"] {
.form-select {
/* flip <select>'s arrow */
background-position: left .75rem center;
background-position: left 0.75rem center;
}
}
}

View file

@ -7,10 +7,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import WithShortcut from "../components/WithShortcut.svelte";
import * as tr from "../lib/ftl";
import { wrapInternal } from "../lib/wrap";
import { withButton } from "../components/helpers";
import { ellipseIcon } from "./icons";
import { get } from "svelte/store";
import { getNoteEditor } from "./OldEditorAdapter.svelte";
import type { RichTextInputAPI } from "./RichTextInput.svelte";
const noteEditor = getNoteEditor();
const { focusInRichText, activeInput } = noteEditor;
@ -40,9 +42,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
return Math.max(1, highest);
}
function onCloze(event: KeyboardEvent | MouseEvent): void {
$: richTextAPI = $activeInput as RichTextInputAPI;
async function onCloze(event: KeyboardEvent | MouseEvent): Promise<void> {
const highestCloze = getCurrentHighestCloze(!event.getModifierState("Alt"));
$activeInput?.surround(`{{c${highestCloze}::`, "}}");
const richText = await richTextAPI.element;
wrapInternal(richText, `{{c${highestCloze}::`, "}}", false);
}
$: disabled = !$focusInRichText;

View file

@ -8,10 +8,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export interface EditingInputAPI {
readonly name: string;
focusable: boolean;
focus(): void;
moveCaretToEnd(): void;
refocus(): void;
focusable: boolean;
moveCaretToEnd(): void;
}
export interface EditingAreaAPI {

View file

@ -4,17 +4,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script context="module" lang="ts">
import type { EditingInputAPI } from "./EditingArea.svelte";
import CodeMirror from "./CodeMirror.svelte";
export interface PlainTextInputAPI extends EditingInputAPI {
name: "plain-text";
moveCaretToEnd(): void;
toggle(): boolean;
surround(before: string, after: string): void;
getEditor(): CodeMirror.Editor;
}
</script>
<script lang="ts">
import CodeMirror from "./CodeMirror.svelte";
import type { CodeMirrorAPI } from "./CodeMirror.svelte";
import { tick, onMount } from "svelte";
import { writable } from "svelte/store";
@ -88,9 +88,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
codeMirror?.editor.setCursor(codeMirror.editor.lineCount(), 0);
}
function surround(before: string, after: string): void {
const selection = codeMirror?.editor.getSelection();
codeMirror?.editor.replaceSelection(before + selection + after);
function toggle(): boolean {
hidden = !hidden;
return hidden;
}
function getEditor(): CodeMirror.Editor {
return codeMirror?.editor;
}
export const api = {
@ -99,11 +103,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
focusable: !hidden,
moveCaretToEnd,
refocus,
toggle(): boolean {
hidden = !hidden;
return hidden;
},
surround,
toggle,
getEditor,
} as PlainTextInputAPI;
function pushUpdate(): void {

View file

@ -16,7 +16,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
moveCaretToEnd(): void;
refocus(): void;
toggle(): boolean;
surround(before: string, after: string): void;
preventResubscription(): () => void;
getTriggerOnNextInsert(): OnNextInsertTrigger;
}
@ -50,7 +49,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { getEditingArea } from "./EditingArea.svelte";
import { promiseWithResolver } from "../lib/promise";
import { bridgeCommand } from "../lib/bridgecommand";
import { wrapInternal } from "../lib/wrap";
import { on } from "../lib/events";
import { nodeStore } from "../sveltelib/node-store";
import type { DecoratedElement } from "../editable/decorated";
@ -198,17 +196,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
richText.focus();
});
},
moveCaretToEnd,
focusable: !hidden,
toggle(): boolean {
hidden = !hidden;
return hidden;
},
surround(before: string, after: string) {
richTextPromise.then((richText) =>
wrapInternal(richText.getRootNode() as any, before, after, false),
);
},
moveCaretToEnd,
preventResubscription,
getTriggerOnNextInsert,
};

View file

@ -14,10 +14,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "../lib/ftl";
import { bridgeCommand } from "../lib/bridgecommand";
import { wrapInternal } from "../lib/wrap";
import { getNoteEditor } from "./OldEditorAdapter.svelte";
import { appendInParentheses } from "./helpers";
import { withButton } from "../components/helpers";
import { paperclipIcon, micIcon, functionIcon } from "./icons";
import type { RichTextInputAPI } from "./RichTextInput.svelte";
export let api = {};
const { focusInRichText, activeInput } = getNoteEditor();
@ -30,7 +32,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bridgeCommand("record");
}
$: richTextAPI = $activeInput as RichTextInputAPI;
$: disabled = !$focusInRichText;
async function surround(front: string, back: string): Promise<void> {
const element = await richTextAPI.element;
wrapInternal(element, front, back, false);
}
</script>
<ButtonGroup {api}>
@ -83,10 +91,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
>
<DropdownItem
on:click={() =>
$activeInput?.surround(
"<anki-mathjax focusonmount>",
"</anki-mathjax>",
)}
surround("<anki-mathjax focusonmount>", "</anki-mathjax>")}
on:mount={withButton(createShortcut)}
>
{tr.editingMathjaxInline()}
@ -101,7 +106,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
>
<DropdownItem
on:click={() =>
$activeInput?.surround(
surround(
'<anki-mathjax block="true" focusonmount>',
"</anki-matjax>",
)}
@ -119,7 +124,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
>
<DropdownItem
on:click={() =>
$activeInput?.surround(
surround(
"<anki-mathjax focusonmount>\\ce{",
"}</anki-mathjax>",
)}
@ -136,7 +141,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:shortcutLabel
>
<DropdownItem
on:click={() => $activeInput?.surround("[latex]", "[/latex]")}
on:click={() => surround("[latex]", "[/latex]")}
on:mount={withButton(createShortcut)}
>
{tr.editingLatex()}
@ -150,7 +155,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:shortcutLabel
>
<DropdownItem
on:click={() => $activeInput?.surround("[$]", "[/$]")}
on:click={() => surround("[$]", "[/$]")}
on:mount={withButton(createShortcut)}
>
{tr.editingLatexEquation()}
@ -164,7 +169,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:shortcutLabel
>
<DropdownItem
on:click={() => $activeInput?.surround("[$$]", "[/$$]")}
on:click={() => surround("[$$]", "[/$$]")}
on:mount={withButton(createShortcut)}
>
{tr.editingLatexMathEnv()}

View file

@ -92,5 +92,21 @@ async function setupNoteEditor(): Promise<NoteEditorAPI> {
return api as NoteEditorAPI;
}
import { get } from "svelte/store";
import { wrapInternal } from "../lib/wrap";
import type { RichTextInputAPI } from "./RichTextInput.svelte";
export async function wrap(before: string, after: string): Promise<void> {
const noteEditor = await noteEditorPromise;
if (!get(noteEditor.focusInRichText)) {
return;
}
const activeInput = get(noteEditor.activeInput) as RichTextInputAPI;
const element = await activeInput.element;
wrapInternal(element, before, after, false);
}
export const noteEditorPromise = setupNoteEditor();
export { editorToolbar } from "./EditorToolbar.svelte";

View file

@ -10,6 +10,7 @@ import {
checkModifiers,
checkIfInputKey,
} from "./keys";
import { on } from "./events";
const keyCodeLookup = {
Backspace: 8,
@ -83,7 +84,6 @@ function removeTrailing(modifier: string): string {
return modifier.substring(0, modifier.length - 1);
}
// function checkModifiers(event: KeyboardEvent, modifiers: string[]): boolean {
function separateRequiredOptionalModifiers(
modifiers: string[],
): [Modifier[], Modifier[]] {
@ -97,11 +97,12 @@ function separateRequiredOptionalModifiers(
}
const check =
(keyCode: number, modifiers: string[]) =>
(keyCode: number, requiredModifiers: Modifier[], optionalModifiers: Modifier[]) =>
(event: KeyboardEvent): boolean => {
const [required, optional] = separateRequiredOptionalModifiers(modifiers);
return checkKey(event, keyCode) && checkModifiers(required, optional)(event);
return (
checkKey(event, keyCode) &&
checkModifiers(requiredModifiers, optionalModifiers)(event)
);
};
function keyToCode(key: string): number {
@ -112,9 +113,11 @@ function keyCombinationToCheck(
keyCombination: string[],
): (event: KeyboardEvent) => boolean {
const keyCode = keyToCode(keyCombination[keyCombination.length - 1]);
const modifiers = keyCombination.slice(0, -1);
const [required, optional] = separateRequiredOptionalModifiers(
keyCombination.slice(0, -1),
);
return check(keyCode, modifiers);
return check(keyCode, required, optional);
}
function innerShortcut(
@ -123,23 +126,20 @@ function innerShortcut(
callback: (event: KeyboardEvent) => void,
...checks: ((event: KeyboardEvent) => boolean)[]
): void {
let interval: number;
if (checks.length === 0) {
callback(lastEvent);
} else {
const [nextCheck, ...restChecks] = checks;
const handler = (event: KeyboardEvent): void => {
if (nextCheck(event)) {
innerShortcut(target, event, callback, ...restChecks);
clearTimeout(interval);
} else if (checkIfInputKey(event)) {
// Any non-modifier key will cancel the shortcut sequence
document.removeEventListener("keydown", handler);
}
};
return callback(lastEvent);
}
document.addEventListener("keydown", handler, { once: true });
const [nextCheck, ...restChecks] = checks;
const remove = on(document, "keydown", handler, { once: true });
function handler(event: KeyboardEvent): void {
if (nextCheck(event)) {
innerShortcut(target, event, callback, ...restChecks);
} else if (checkIfInputKey(event)) {
// Any non-modifier key will cancel the shortcut sequence
remove();
}
}
}
@ -151,14 +151,13 @@ export function registerShortcut(
const [check, ...restChecks] =
splitKeyCombinationString(keyCombinationString).map(keyCombinationToCheck);
const handler = (event: KeyboardEvent): void => {
function handler(event: KeyboardEvent): void {
if (check(event)) {
innerShortcut(target, event, callback, ...restChecks);
}
};
}
target.addEventListener("keydown", handler as EventListener);
return (): void => target.removeEventListener("keydown", handler as EventListener);
return on(target, "keydown", handler);
}
registerPackage("anki/shortcuts", {

View file

@ -1,6 +1,8 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { getSelection } from "./cross-browser";
function wrappedExceptForWhitespace(text: string, front: string, back: string): string {
const match = text.match(/^(\s*)([^]*?)(\s*)$/)!;
return match[1] + front + match[2] + back + match[3];
@ -15,12 +17,12 @@ function moveCursorPastPostfix(selection: Selection, postfix: string): void {
}
export function wrapInternal(
root: DocumentOrShadowRoot,
base: Element,
front: string,
back: string,
plainText: boolean,
): void {
const selection = root.getSelection()!;
const selection = getSelection(base)!;
const range = selection.getRangeAt(0);
const content = range.cloneContents();
const span = document.createElement("span");