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; --base-font-size: 14px;
} }
[dir=rtl] { [dir="rtl"] {
.form-select { .form-select {
/* flip <select>'s arrow */ /* 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 WithShortcut from "../components/WithShortcut.svelte";
import * as tr from "../lib/ftl"; import * as tr from "../lib/ftl";
import { wrapInternal } from "../lib/wrap";
import { withButton } from "../components/helpers"; import { withButton } from "../components/helpers";
import { ellipseIcon } from "./icons"; import { ellipseIcon } from "./icons";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { getNoteEditor } from "./OldEditorAdapter.svelte"; import { getNoteEditor } from "./OldEditorAdapter.svelte";
import type { RichTextInputAPI } from "./RichTextInput.svelte";
const noteEditor = getNoteEditor(); const noteEditor = getNoteEditor();
const { focusInRichText, activeInput } = noteEditor; 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); 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")); const highestCloze = getCurrentHighestCloze(!event.getModifierState("Alt"));
$activeInput?.surround(`{{c${highestCloze}::`, "}}"); const richText = await richTextAPI.element;
wrapInternal(richText, `{{c${highestCloze}::`, "}}", false);
} }
$: disabled = !$focusInRichText; $: 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 { export interface EditingInputAPI {
readonly name: string; readonly name: string;
focusable: boolean;
focus(): void; focus(): void;
moveCaretToEnd(): void;
refocus(): void; refocus(): void;
focusable: boolean;
moveCaretToEnd(): void;
} }
export interface EditingAreaAPI { 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"> <script context="module" lang="ts">
import type { EditingInputAPI } from "./EditingArea.svelte"; import type { EditingInputAPI } from "./EditingArea.svelte";
import CodeMirror from "./CodeMirror.svelte";
export interface PlainTextInputAPI extends EditingInputAPI { export interface PlainTextInputAPI extends EditingInputAPI {
name: "plain-text"; name: "plain-text";
moveCaretToEnd(): void; moveCaretToEnd(): void;
toggle(): boolean; toggle(): boolean;
surround(before: string, after: string): void; getEditor(): CodeMirror.Editor;
} }
</script> </script>
<script lang="ts"> <script lang="ts">
import CodeMirror from "./CodeMirror.svelte";
import type { CodeMirrorAPI } from "./CodeMirror.svelte"; import type { CodeMirrorAPI } from "./CodeMirror.svelte";
import { tick, onMount } from "svelte"; import { tick, onMount } from "svelte";
import { writable } from "svelte/store"; 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); codeMirror?.editor.setCursor(codeMirror.editor.lineCount(), 0);
} }
function surround(before: string, after: string): void { function toggle(): boolean {
const selection = codeMirror?.editor.getSelection(); hidden = !hidden;
codeMirror?.editor.replaceSelection(before + selection + after); return hidden;
}
function getEditor(): CodeMirror.Editor {
return codeMirror?.editor;
} }
export const api = { export const api = {
@ -99,11 +103,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
focusable: !hidden, focusable: !hidden,
moveCaretToEnd, moveCaretToEnd,
refocus, refocus,
toggle(): boolean { toggle,
hidden = !hidden; getEditor,
return hidden;
},
surround,
} as PlainTextInputAPI; } as PlainTextInputAPI;
function pushUpdate(): void { 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; moveCaretToEnd(): void;
refocus(): void; refocus(): void;
toggle(): boolean; toggle(): boolean;
surround(before: string, after: string): void;
preventResubscription(): () => void; preventResubscription(): () => void;
getTriggerOnNextInsert(): OnNextInsertTrigger; 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 { getEditingArea } from "./EditingArea.svelte";
import { promiseWithResolver } from "../lib/promise"; import { promiseWithResolver } from "../lib/promise";
import { bridgeCommand } from "../lib/bridgecommand"; import { bridgeCommand } from "../lib/bridgecommand";
import { wrapInternal } from "../lib/wrap";
import { on } from "../lib/events"; import { on } from "../lib/events";
import { nodeStore } from "../sveltelib/node-store"; import { nodeStore } from "../sveltelib/node-store";
import type { DecoratedElement } from "../editable/decorated"; 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(); richText.focus();
}); });
}, },
moveCaretToEnd,
focusable: !hidden, focusable: !hidden,
toggle(): boolean { toggle(): boolean {
hidden = !hidden; hidden = !hidden;
return hidden; return hidden;
}, },
surround(before: string, after: string) { moveCaretToEnd,
richTextPromise.then((richText) =>
wrapInternal(richText.getRootNode() as any, before, after, false),
);
},
preventResubscription, preventResubscription,
getTriggerOnNextInsert, 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 * as tr from "../lib/ftl";
import { bridgeCommand } from "../lib/bridgecommand"; import { bridgeCommand } from "../lib/bridgecommand";
import { wrapInternal } from "../lib/wrap";
import { getNoteEditor } from "./OldEditorAdapter.svelte"; import { getNoteEditor } from "./OldEditorAdapter.svelte";
import { appendInParentheses } from "./helpers"; import { appendInParentheses } from "./helpers";
import { withButton } from "../components/helpers"; import { withButton } from "../components/helpers";
import { paperclipIcon, micIcon, functionIcon } from "./icons"; import { paperclipIcon, micIcon, functionIcon } from "./icons";
import type { RichTextInputAPI } from "./RichTextInput.svelte";
export let api = {}; export let api = {};
const { focusInRichText, activeInput } = getNoteEditor(); const { focusInRichText, activeInput } = getNoteEditor();
@ -30,7 +32,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bridgeCommand("record"); bridgeCommand("record");
} }
$: richTextAPI = $activeInput as RichTextInputAPI;
$: disabled = !$focusInRichText; $: disabled = !$focusInRichText;
async function surround(front: string, back: string): Promise<void> {
const element = await richTextAPI.element;
wrapInternal(element, front, back, false);
}
</script> </script>
<ButtonGroup {api}> <ButtonGroup {api}>
@ -83,10 +91,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
> >
<DropdownItem <DropdownItem
on:click={() => on:click={() =>
$activeInput?.surround( surround("<anki-mathjax focusonmount>", "</anki-mathjax>")}
"<anki-mathjax focusonmount>",
"</anki-mathjax>",
)}
on:mount={withButton(createShortcut)} on:mount={withButton(createShortcut)}
> >
{tr.editingMathjaxInline()} {tr.editingMathjaxInline()}
@ -101,7 +106,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
> >
<DropdownItem <DropdownItem
on:click={() => on:click={() =>
$activeInput?.surround( surround(
'<anki-mathjax block="true" focusonmount>', '<anki-mathjax block="true" focusonmount>',
"</anki-matjax>", "</anki-matjax>",
)} )}
@ -119,7 +124,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
> >
<DropdownItem <DropdownItem
on:click={() => on:click={() =>
$activeInput?.surround( surround(
"<anki-mathjax focusonmount>\\ce{", "<anki-mathjax focusonmount>\\ce{",
"}</anki-mathjax>", "}</anki-mathjax>",
)} )}
@ -136,7 +141,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:shortcutLabel let:shortcutLabel
> >
<DropdownItem <DropdownItem
on:click={() => $activeInput?.surround("[latex]", "[/latex]")} on:click={() => surround("[latex]", "[/latex]")}
on:mount={withButton(createShortcut)} on:mount={withButton(createShortcut)}
> >
{tr.editingLatex()} {tr.editingLatex()}
@ -150,7 +155,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:shortcutLabel let:shortcutLabel
> >
<DropdownItem <DropdownItem
on:click={() => $activeInput?.surround("[$]", "[/$]")} on:click={() => surround("[$]", "[/$]")}
on:mount={withButton(createShortcut)} on:mount={withButton(createShortcut)}
> >
{tr.editingLatexEquation()} {tr.editingLatexEquation()}
@ -164,7 +169,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:shortcutLabel let:shortcutLabel
> >
<DropdownItem <DropdownItem
on:click={() => $activeInput?.surround("[$$]", "[/$$]")} on:click={() => surround("[$$]", "[/$$]")}
on:mount={withButton(createShortcut)} on:mount={withButton(createShortcut)}
> >
{tr.editingLatexMathEnv()} {tr.editingLatexMathEnv()}

View file

@ -92,5 +92,21 @@ async function setupNoteEditor(): Promise<NoteEditorAPI> {
return api as 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 const noteEditorPromise = setupNoteEditor();
export { editorToolbar } from "./EditorToolbar.svelte"; export { editorToolbar } from "./EditorToolbar.svelte";

View file

@ -10,6 +10,7 @@ import {
checkModifiers, checkModifiers,
checkIfInputKey, checkIfInputKey,
} from "./keys"; } from "./keys";
import { on } from "./events";
const keyCodeLookup = { const keyCodeLookup = {
Backspace: 8, Backspace: 8,
@ -83,7 +84,6 @@ function removeTrailing(modifier: string): string {
return modifier.substring(0, modifier.length - 1); return modifier.substring(0, modifier.length - 1);
} }
// function checkModifiers(event: KeyboardEvent, modifiers: string[]): boolean {
function separateRequiredOptionalModifiers( function separateRequiredOptionalModifiers(
modifiers: string[], modifiers: string[],
): [Modifier[], Modifier[]] { ): [Modifier[], Modifier[]] {
@ -97,11 +97,12 @@ function separateRequiredOptionalModifiers(
} }
const check = const check =
(keyCode: number, modifiers: string[]) => (keyCode: number, requiredModifiers: Modifier[], optionalModifiers: Modifier[]) =>
(event: KeyboardEvent): boolean => { (event: KeyboardEvent): boolean => {
const [required, optional] = separateRequiredOptionalModifiers(modifiers); return (
checkKey(event, keyCode) &&
return checkKey(event, keyCode) && checkModifiers(required, optional)(event); checkModifiers(requiredModifiers, optionalModifiers)(event)
);
}; };
function keyToCode(key: string): number { function keyToCode(key: string): number {
@ -112,9 +113,11 @@ function keyCombinationToCheck(
keyCombination: string[], keyCombination: string[],
): (event: KeyboardEvent) => boolean { ): (event: KeyboardEvent) => boolean {
const keyCode = keyToCode(keyCombination[keyCombination.length - 1]); 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( function innerShortcut(
@ -123,23 +126,20 @@ function innerShortcut(
callback: (event: KeyboardEvent) => void, callback: (event: KeyboardEvent) => void,
...checks: ((event: KeyboardEvent) => boolean)[] ...checks: ((event: KeyboardEvent) => boolean)[]
): void { ): void {
let interval: number;
if (checks.length === 0) { if (checks.length === 0) {
callback(lastEvent); return 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);
}
};
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] = const [check, ...restChecks] =
splitKeyCombinationString(keyCombinationString).map(keyCombinationToCheck); splitKeyCombinationString(keyCombinationString).map(keyCombinationToCheck);
const handler = (event: KeyboardEvent): void => { function handler(event: KeyboardEvent): void {
if (check(event)) { if (check(event)) {
innerShortcut(target, event, callback, ...restChecks); innerShortcut(target, event, callback, ...restChecks);
} }
}; }
target.addEventListener("keydown", handler as EventListener); return on(target, "keydown", handler);
return (): void => target.removeEventListener("keydown", handler as EventListener);
} }
registerPackage("anki/shortcuts", { registerPackage("anki/shortcuts", {

View file

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