mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 23:42:23 -04:00
Add back wrap function (#1551)
* Remove surround from RichText and PlainText * Export wrap
This commit is contained in:
parent
b714974464
commit
4966f3cc44
9 changed files with 83 additions and 62 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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()}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
return callback(lastEvent);
|
||||
}
|
||||
|
||||
const [nextCheck, ...restChecks] = checks;
|
||||
const handler = (event: KeyboardEvent): void => {
|
||||
const remove = on(document, "keydown", handler, { once: true });
|
||||
|
||||
function 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);
|
||||
remove();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handler, { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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", {
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue