mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00

* Fix field not being restored after Undo, if field also has focus * Execute moveCaretToEnd after undoing a change - Otherwise the caret might be placed in seemingly random positions * Fix wording of comments * Remove autofocus functionality of EditingArea - instead await a tick in focusField - We used the autofocus prop for the initial focus setting when opening the editor. However it seems that awaiting tick in focusField also does the trick.
186 lines
4.6 KiB
Svelte
186 lines
4.6 KiB
Svelte
<!--
|
|
Copyright: Ankitects Pty Ltd and contributors
|
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
-->
|
|
<script context="module" lang="ts">
|
|
import type { CodeMirror as CodeMirrorType } from "../code-mirror";
|
|
import type { EditingInputAPI } from "../EditingArea.svelte";
|
|
|
|
export interface PlainTextInputAPI extends EditingInputAPI {
|
|
name: "plain-text";
|
|
moveCaretToEnd(): void;
|
|
toggle(): boolean;
|
|
getEditor(): CodeMirrorType.Editor;
|
|
}
|
|
|
|
export const parsingInstructions: string[] = [];
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
import { onMount, tick } from "svelte";
|
|
import { writable } from "svelte/store";
|
|
|
|
import { pageTheme } from "../../sveltelib/theme";
|
|
import { baseOptions, gutterOptions, htmlanki } from "../code-mirror";
|
|
import type { CodeMirrorAPI } from "../CodeMirror.svelte";
|
|
import CodeMirror from "../CodeMirror.svelte";
|
|
import { context as decoratedElementsContext } from "../DecoratedElements.svelte";
|
|
import { context as editingAreaContext } from "../EditingArea.svelte";
|
|
|
|
export let hidden = false;
|
|
|
|
const configuration = {
|
|
mode: htmlanki,
|
|
...baseOptions,
|
|
...gutterOptions,
|
|
};
|
|
|
|
const { editingInputs, content } = editingAreaContext.get();
|
|
const decoratedElements = decoratedElementsContext.get();
|
|
const code = writable($content);
|
|
|
|
function adjustInputHTML(html: string): string {
|
|
for (const component of decoratedElements) {
|
|
html = component.toUndecorated(html);
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
const parser = new DOMParser();
|
|
|
|
function removeTag(element: HTMLElement, tagName: string): void {
|
|
for (const elem of element.getElementsByTagName(tagName)) {
|
|
elem.remove();
|
|
}
|
|
}
|
|
|
|
function parseAsHTML(html: string): string {
|
|
const doc = parser.parseFromString(
|
|
parsingInstructions.join("") + html,
|
|
"text/html",
|
|
);
|
|
const body = doc.body;
|
|
|
|
removeTag(body, "script");
|
|
removeTag(body, "link");
|
|
removeTag(body, "style");
|
|
|
|
return doc.body.innerHTML;
|
|
}
|
|
|
|
function adjustOutputHTML(html: string): string {
|
|
for (const component of decoratedElements) {
|
|
html = component.toStored(html);
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
let codeMirror: CodeMirrorAPI;
|
|
|
|
function focus(): void {
|
|
codeMirror?.editor.focus();
|
|
}
|
|
|
|
function moveCaretToEnd(): void {
|
|
codeMirror?.editor.setCursor(codeMirror.editor.lineCount(), 0);
|
|
}
|
|
|
|
function refocus(): void {
|
|
(codeMirror?.editor as any).display.input.blur();
|
|
focus();
|
|
moveCaretToEnd();
|
|
}
|
|
|
|
function toggle(): boolean {
|
|
hidden = !hidden;
|
|
return hidden;
|
|
}
|
|
|
|
function getEditor(): CodeMirrorType.Editor {
|
|
return codeMirror?.editor;
|
|
}
|
|
|
|
export const api = {
|
|
name: "plain-text",
|
|
focus,
|
|
focusable: !hidden,
|
|
moveCaretToEnd,
|
|
refocus,
|
|
toggle,
|
|
getEditor,
|
|
} as PlainTextInputAPI;
|
|
|
|
function pushUpdate(): void {
|
|
api.focusable = !hidden;
|
|
$editingInputs = $editingInputs;
|
|
}
|
|
|
|
function refresh() {
|
|
codeMirror.editor.refresh();
|
|
}
|
|
|
|
$: {
|
|
hidden;
|
|
tick().then(refresh);
|
|
pushUpdate();
|
|
}
|
|
|
|
onMount(() => {
|
|
$editingInputs.push(api);
|
|
$editingInputs = $editingInputs;
|
|
|
|
const unsubscribeFromEditingArea = content.subscribe((value: string): void => {
|
|
const adjusted = adjustInputHTML(value);
|
|
code.set(adjusted);
|
|
});
|
|
|
|
const unsubscribeToEditingArea = code.subscribe((value: string): void => {
|
|
const parsed = parseAsHTML(value);
|
|
content.set(adjustOutputHTML(parsed));
|
|
});
|
|
|
|
return () => {
|
|
unsubscribeFromEditingArea();
|
|
unsubscribeToEditingArea();
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div
|
|
class="plain-text-input"
|
|
class:light-theme={!$pageTheme.isDark}
|
|
class:hidden
|
|
on:focusin
|
|
on:focusout
|
|
>
|
|
<CodeMirror
|
|
{configuration}
|
|
{code}
|
|
bind:api={codeMirror}
|
|
on:change={({ detail: html }) => code.set(parseAsHTML(html))}
|
|
/>
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
.plain-text-input {
|
|
overflow-x: hidden;
|
|
|
|
:global(.CodeMirror) {
|
|
border-radius: 0 0 5px 5px;
|
|
}
|
|
|
|
:global(.CodeMirror-lines) {
|
|
padding: 6px 0;
|
|
}
|
|
|
|
&.hidden {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.light-theme :global(.CodeMirror) {
|
|
border-top: 1px solid #ddd;
|
|
}
|
|
</style>
|