mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32:22 -04:00
Several editor fixes (#1478)
* Declare toolbar api via modifiable property * Fix addon buttons * Assign editing areas in a synchronous way * Restore the Tab shortcut that moves the caret to end - moveCaretToEnd is called twice now: The moveCaretToEnd calls in *TextInput causes the caret to be moved to the end when switching back from the window and back. - To fix this, I will need the code for saving and restoring locations from #1377 * Restore selections in the PlainTextInput * Improve type safety of destroyable - clearable-array was renamed to destroyable
This commit is contained in:
parent
896d11546b
commit
c9bd39c6a2
15 changed files with 197 additions and 99 deletions
|
@ -408,7 +408,7 @@ class Browser(QMainWindow):
|
||||||
def add_preview_button(editor: Editor) -> None:
|
def add_preview_button(editor: Editor) -> None:
|
||||||
editor._links["preview"] = lambda _editor: self.onTogglePreview()
|
editor._links["preview"] = lambda _editor: self.onTogglePreview()
|
||||||
editor.web.eval(
|
editor.web.eval(
|
||||||
"noteEditorPromise.then(noteEditor => noteEditor.toolbar.then(toolbar => toolbar.notetypeButtons.appendButton({ component: editorToolbar.PreviewButton, id: 'preview' })));",
|
"noteEditorPromise.then(noteEditor => noteEditor.toolbar.notetypeButtons.appendButton({ component: editorToolbar.PreviewButton, id: 'preview' }));",
|
||||||
)
|
)
|
||||||
|
|
||||||
gui_hooks.editor_did_init.append(add_preview_button)
|
gui_hooks.editor_did_init.append(add_preview_button)
|
||||||
|
|
|
@ -137,7 +137,7 @@ class Editor:
|
||||||
gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)
|
gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)
|
||||||
|
|
||||||
lefttopbtns_defs = [
|
lefttopbtns_defs = [
|
||||||
f"noteEditorPromise.then((noteEditor) => noteEditor.toolbar.then((toolbar) => toolbar.notetypeButtons.appendButton({{ component: editorToolbar.Raw, props: {{ html: {json.dumps(button)} }} }}, -1)));"
|
f"noteEditorPromise.then((noteEditor) => noteEditor.toolbar.notetypeButtons.appendButton({{ component: editorToolbar.Raw, props: {{ html: {json.dumps(button)} }} }}, -1));"
|
||||||
for button in lefttopbtns
|
for button in lefttopbtns
|
||||||
]
|
]
|
||||||
lefttopbtns_js = "\n".join(lefttopbtns_defs)
|
lefttopbtns_js = "\n".join(lefttopbtns_defs)
|
||||||
|
@ -150,7 +150,7 @@ class Editor:
|
||||||
righttopbtns_defs = ", ".join([json.dumps(button) for button in righttopbtns])
|
righttopbtns_defs = ", ".join([json.dumps(button) for button in righttopbtns])
|
||||||
righttopbtns_js = (
|
righttopbtns_js = (
|
||||||
f"""
|
f"""
|
||||||
noteEditorPromise.then(noteEditor => noteEditor.toolbar.then((toolbar) => toolbar.toolbar.appendGroup({{
|
noteEditorPromise.then(noteEditor => noteEditor.toolbar.toolbar.appendGroup({{
|
||||||
component: editorToolbar.AddonButtons,
|
component: editorToolbar.AddonButtons,
|
||||||
id: "addons",
|
id: "addons",
|
||||||
props: {{ buttons: [ {righttopbtns_defs} ] }},
|
props: {{ buttons: [ {righttopbtns_defs} ] }},
|
||||||
|
@ -1323,11 +1323,11 @@ gui_hooks.editor_will_munge_html.append(reverse_url_quoting)
|
||||||
def set_cloze_button(editor: Editor) -> None:
|
def set_cloze_button(editor: Editor) -> None:
|
||||||
if editor.note.note_type()["type"] == MODEL_CLOZE:
|
if editor.note.note_type()["type"] == MODEL_CLOZE:
|
||||||
editor.web.eval(
|
editor.web.eval(
|
||||||
'noteEditorPromise.then((noteEditor) => noteEditor.toolbar.then((toolbar) => toolbar.templateButtons.showButton("cloze"))); '
|
'noteEditorPromise.then((noteEditor) => noteEditor.toolbar.templateButtons.showButton("cloze")); '
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
editor.web.eval(
|
editor.web.eval(
|
||||||
'noteEditorPromise.then((noteEditor) => noteEditor.toolbar.then(({ templateButtons }) => templateButtons.hideButton("cloze"))); '
|
'noteEditorPromise.then((noteEditor) => noteEditor.toolbar.templateButtons.hideButton("cloze")); '
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,27 @@
|
||||||
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
|
||||||
-->
|
-->
|
||||||
|
<script context="module" lang="ts">
|
||||||
|
import type { SvelteComponent } from "./registration";
|
||||||
|
import type { Identifier } from "./identifier";
|
||||||
|
|
||||||
|
export interface ButtonGroupAPI {
|
||||||
|
insertButton(button: SvelteComponent, position: Identifier): void;
|
||||||
|
appendButton(button: SvelteComponent, position: Identifier): void;
|
||||||
|
showButton(position: Identifier): void;
|
||||||
|
hideButton(position: Identifier): void;
|
||||||
|
toggleButton(position: Identifier): void;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ButtonGroupItem from "./ButtonGroupItem.svelte";
|
import ButtonGroupItem from "./ButtonGroupItem.svelte";
|
||||||
import { setContext } from "svelte";
|
import { setContext } from "svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { buttonGroupKey } from "./context-keys";
|
import { buttonGroupKey } from "./context-keys";
|
||||||
import type { Identifier } from "./identifier";
|
|
||||||
import { insertElement, appendElement } from "./identifier";
|
import { insertElement, appendElement } from "./identifier";
|
||||||
import type { ButtonRegistration } from "./buttons";
|
import type { ButtonRegistration } from "./buttons";
|
||||||
import { ButtonPosition } from "./buttons";
|
import { ButtonPosition } from "./buttons";
|
||||||
import type { SvelteComponent } from "./registration";
|
|
||||||
import { makeInterface } from "./registration";
|
import { makeInterface } from "./registration";
|
||||||
|
|
||||||
export let id: string | undefined = undefined;
|
export let id: string | undefined = undefined;
|
||||||
|
@ -57,10 +68,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
setContext(buttonGroupKey, registerComponent);
|
setContext(buttonGroupKey, registerComponent);
|
||||||
|
|
||||||
export let api: Record<string, unknown> | undefined = undefined;
|
export let api: Partial<ButtonGroupAPI> | undefined = undefined;
|
||||||
let buttonGroupRef: HTMLDivElement;
|
let buttonGroupRef: HTMLDivElement;
|
||||||
|
|
||||||
$: if (api && buttonGroupRef) {
|
function createApi(): void {
|
||||||
const { addComponent, updateRegistration } =
|
const { addComponent, updateRegistration } =
|
||||||
getDynamicInterface(buttonGroupRef);
|
getDynamicInterface(buttonGroupRef);
|
||||||
|
|
||||||
|
@ -89,7 +100,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
showButton,
|
showButton,
|
||||||
hideButton,
|
hideButton,
|
||||||
toggleButton,
|
toggleButton,
|
||||||
});
|
} as ButtonGroupAPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (api && buttonGroupRef) {
|
||||||
|
createApi();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,26 @@
|
||||||
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
|
||||||
-->
|
-->
|
||||||
|
<script context="module" lang="ts">
|
||||||
|
import type { Identifier } from "./identifier";
|
||||||
|
import type { SvelteComponent } from "./registration";
|
||||||
|
|
||||||
|
export interface ButtonToolbarAPI {
|
||||||
|
insertGroup(button: SvelteComponent, position: Identifier): void;
|
||||||
|
appendGroup(button: SvelteComponent, position: Identifier): void;
|
||||||
|
showGroup(position: Identifier): void;
|
||||||
|
hideGroup(position: Identifier): void;
|
||||||
|
toggleGroup(position: Identifier): void;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, setContext } from "svelte";
|
import { getContext, setContext } from "svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import Item from "./Item.svelte";
|
import Item from "./Item.svelte";
|
||||||
|
import type { Registration } from "./registration";
|
||||||
import { sectionKey, nightModeKey } from "./context-keys";
|
import { sectionKey, nightModeKey } from "./context-keys";
|
||||||
import type { Identifier } from "./identifier";
|
|
||||||
import { insertElement, appendElement } from "./identifier";
|
import { insertElement, appendElement } from "./identifier";
|
||||||
import type { SvelteComponent, Registration } from "./registration";
|
|
||||||
import { makeInterface } from "./registration";
|
import { makeInterface } from "./registration";
|
||||||
|
|
||||||
export let id: string | undefined = undefined;
|
export let id: string | undefined = undefined;
|
||||||
|
@ -39,10 +51,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
setContext(sectionKey, registerComponent);
|
setContext(sectionKey, registerComponent);
|
||||||
|
|
||||||
export let api: Record<string, unknown> | undefined = undefined;
|
export let api: Partial<ButtonToolbarAPI> | undefined = undefined;
|
||||||
let buttonToolbarRef: HTMLDivElement;
|
let buttonToolbarRef: HTMLDivElement;
|
||||||
|
|
||||||
$: if (buttonToolbarRef && api) {
|
function createApi(): void {
|
||||||
const { addComponent, updateRegistration } =
|
const { addComponent, updateRegistration } =
|
||||||
getDynamicInterface(buttonToolbarRef);
|
getDynamicInterface(buttonToolbarRef);
|
||||||
|
|
||||||
|
@ -74,6 +86,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if (buttonToolbarRef && api) {
|
||||||
|
createApi();
|
||||||
|
}
|
||||||
|
|
||||||
const nightMode = getContext<boolean>(nightModeKey);
|
const nightMode = getContext<boolean>(nightModeKey);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -44,14 +44,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
// TODO passing in the tabindex option does not do anything: bug?
|
// TODO passing in the tabindex option does not do anything: bug?
|
||||||
codeMirror.getInputField().tabIndex = 0;
|
codeMirror.getInputField().tabIndex = 0;
|
||||||
|
|
||||||
|
let ranges: CodeMirrorLib.Range[] | null = null;
|
||||||
|
|
||||||
codeMirror.on("change", () => dispatch("change", codeMirror.getValue()));
|
codeMirror.on("change", () => dispatch("change", codeMirror.getValue()));
|
||||||
|
codeMirror.on("mousedown", () => {
|
||||||
|
ranges = null;
|
||||||
|
});
|
||||||
codeMirror.on("focus", () => {
|
codeMirror.on("focus", () => {
|
||||||
|
if (ranges) {
|
||||||
|
codeMirror.setSelections(ranges);
|
||||||
|
}
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
dispatch("focus");
|
|
||||||
});
|
});
|
||||||
codeMirror.on("blur", () => {
|
codeMirror.on("blur", () => {
|
||||||
|
ranges = codeMirror.listSelections();
|
||||||
subscribe();
|
subscribe();
|
||||||
dispatch("blur");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
subscribe();
|
subscribe();
|
||||||
|
|
|
@ -10,6 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
focusable: boolean;
|
focusable: boolean;
|
||||||
focus(): void;
|
focus(): void;
|
||||||
|
moveCaretToEnd(): void;
|
||||||
refocus(): void;
|
refocus(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +49,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let focusTrap: HTMLInputElement;
|
let focusTrap: HTMLInputElement;
|
||||||
|
|
||||||
const inputsStore = writable<EditingInputAPI[]>([]);
|
const inputsStore = writable<EditingInputAPI[]>([]);
|
||||||
|
|
||||||
$: editingInputs = $inputsStore;
|
$: editingInputs = $inputsStore;
|
||||||
|
|
||||||
function getAvailableInput(): EditingInputAPI | undefined {
|
function getAvailableInput(): EditingInputAPI | undefined {
|
||||||
|
@ -111,12 +111,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = set({
|
export let api: Partial<EditingAreaAPI> = {};
|
||||||
content,
|
|
||||||
editingInputs: inputsStore,
|
Object.assign(
|
||||||
focus,
|
api,
|
||||||
refocus,
|
set({
|
||||||
});
|
content,
|
||||||
|
editingInputs: inputsStore,
|
||||||
|
focus,
|
||||||
|
refocus,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (autofocus) {
|
if (autofocus) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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 { EditingAreaAPI } from "./EditingArea.svelte";
|
import type { EditingAreaAPI } from "./EditingArea.svelte";
|
||||||
import contextProperty from "../sveltelib/context-property";
|
import contextProperty from "../sveltelib/context-property";
|
||||||
|
import type { Readable } from "svelte/store";
|
||||||
|
|
||||||
export interface FieldData {
|
export interface FieldData {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -14,10 +15,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditorFieldAPI {
|
export interface EditorFieldAPI {
|
||||||
element: HTMLElement;
|
element: Promise<HTMLElement>;
|
||||||
index: number;
|
direction: Readable<"ltr" | "rtl">;
|
||||||
direction: "ltr" | "rtl";
|
editingArea: EditingAreaAPI;
|
||||||
editingArea?: EditingAreaAPI;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = Symbol("editorField");
|
const key = Symbol("editorField");
|
||||||
|
@ -32,43 +32,46 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import LabelName from "./LabelName.svelte";
|
import LabelName from "./LabelName.svelte";
|
||||||
import FieldState from "./FieldState.svelte";
|
import FieldState from "./FieldState.svelte";
|
||||||
|
|
||||||
import { setContext } from "svelte";
|
import { onDestroy, setContext } from "svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { directionKey } from "../lib/context-keys";
|
import { directionKey } from "../lib/context-keys";
|
||||||
|
import { promiseWithResolver } from "../lib/promise";
|
||||||
|
import type { Destroyable } from "./destroyable";
|
||||||
|
|
||||||
export let content: Writable<string>;
|
export let content: Writable<string>;
|
||||||
export let field: FieldData;
|
export let field: FieldData;
|
||||||
export let autofocus = false;
|
export let autofocus = false;
|
||||||
|
|
||||||
const directionStore = writable();
|
export let api: (Partial<EditorFieldAPI> & Destroyable) | undefined = undefined;
|
||||||
|
|
||||||
|
const directionStore = writable<"ltr" | "rtl">();
|
||||||
setContext(directionKey, directionStore);
|
setContext(directionKey, directionStore);
|
||||||
|
|
||||||
$: $directionStore = field.direction;
|
$: $directionStore = field.direction;
|
||||||
|
|
||||||
let editorField: HTMLElement;
|
const editingArea: Partial<EditingAreaAPI> = {};
|
||||||
|
const [element, elementResolve] = promiseWithResolver<HTMLElement>();
|
||||||
|
|
||||||
export const api = set(
|
const editorFieldApi = set({
|
||||||
Object.create(
|
element,
|
||||||
{},
|
direction: directionStore,
|
||||||
{
|
editingArea: editingArea as EditingAreaAPI,
|
||||||
element: {
|
});
|
||||||
get: () => editorField,
|
|
||||||
},
|
if (api) {
|
||||||
direction: {
|
Object.assign(api, editorFieldApi);
|
||||||
get: () => $directionStore,
|
}
|
||||||
},
|
|
||||||
},
|
onDestroy(() => api?.destroy());
|
||||||
) as EditorFieldAPI,
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={editorField}
|
use:elementResolve
|
||||||
class="editor-field"
|
class="editor-field"
|
||||||
on:focusin
|
on:focusin
|
||||||
on:focusout
|
on:focusout
|
||||||
on:click={() => api.editingArea?.focus()}
|
on:click={() => editingArea.focus?.()}
|
||||||
>
|
>
|
||||||
<LabelContainer>
|
<LabelContainer>
|
||||||
<LabelName>{field.name}</LabelName>
|
<LabelName>{field.name}</LabelName>
|
||||||
|
@ -79,7 +82,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{autofocus}
|
{autofocus}
|
||||||
fontFamily={field.fontFamily}
|
fontFamily={field.fontFamily}
|
||||||
fontSize={field.fontSize}
|
fontSize={field.fontSize}
|
||||||
bind:api={api.editingArea}
|
api={editingArea}
|
||||||
>
|
>
|
||||||
<slot name="editing-inputs" />
|
<slot name="editing-inputs" />
|
||||||
</EditingArea>
|
</EditingArea>
|
||||||
|
|
|
@ -6,6 +6,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import "./legacy.css";
|
import "./legacy.css";
|
||||||
import { updateAllState, resetAllState } from "../components/WithState.svelte";
|
import { updateAllState, resetAllState } from "../components/WithState.svelte";
|
||||||
|
|
||||||
|
import type { ButtonGroupAPI } from "../components/ButtonGroup.svelte";
|
||||||
|
import type { ButtonToolbarAPI } from "../components/ButtonToolbar.svelte";
|
||||||
|
|
||||||
export function updateActiveButtons(event: Event) {
|
export function updateActiveButtons(event: Event) {
|
||||||
updateAllState(event);
|
updateAllState(event);
|
||||||
}
|
}
|
||||||
|
@ -15,12 +18,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditorToolbarAPI {
|
export interface EditorToolbarAPI {
|
||||||
toolbar: any;
|
toolbar: ButtonToolbarAPI;
|
||||||
notetypeButtons: any;
|
notetypeButtons: ButtonGroupAPI;
|
||||||
formatInlineButtons: any;
|
formatInlineButtons: ButtonGroupAPI;
|
||||||
formatBlockButtons: any;
|
formatBlockButtons: ButtonGroupAPI;
|
||||||
colorButtons: any;
|
colorButtons: ButtonGroupAPI;
|
||||||
templateButtons: any;
|
templateButtons: ButtonGroupAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Our dynamic components */
|
/* Our dynamic components */
|
||||||
|
@ -57,14 +60,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
const colorButtons = {};
|
const colorButtons = {};
|
||||||
const templateButtons = {};
|
const templateButtons = {};
|
||||||
|
|
||||||
export const api = {
|
export let api: Partial<EditorToolbarAPI> = {};
|
||||||
|
|
||||||
|
Object.assign(api, {
|
||||||
toolbar,
|
toolbar,
|
||||||
notetypeButtons,
|
notetypeButtons,
|
||||||
formatInlineButtons,
|
formatInlineButtons,
|
||||||
formatBlockButtons,
|
formatBlockButtons,
|
||||||
colorButtons,
|
colorButtons,
|
||||||
templateButtons,
|
templateButtons,
|
||||||
};
|
} as EditorToolbarAPI);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<StickyHeader>
|
<StickyHeader>
|
||||||
|
|
|
@ -8,11 +8,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type { PlainTextInputAPI } from "./PlainTextInput.svelte";
|
import type { PlainTextInputAPI } from "./PlainTextInput.svelte";
|
||||||
import type { EditorToolbarAPI } from "./EditorToolbar.svelte";
|
import type { EditorToolbarAPI } from "./EditorToolbar.svelte";
|
||||||
|
|
||||||
|
import { registerShortcut } from "../lib/shortcuts";
|
||||||
import contextProperty from "../sveltelib/context-property";
|
import contextProperty from "../sveltelib/context-property";
|
||||||
|
import { writable, get } from "svelte/store";
|
||||||
|
|
||||||
export interface NoteEditorAPI {
|
export interface NoteEditorAPI {
|
||||||
fields: EditorFieldAPI[];
|
fields: EditorFieldAPI[];
|
||||||
currentField: Writable<EditorFieldAPI>;
|
currentField: Writable<EditorFieldAPI | null>;
|
||||||
activeInput: Writable<RichTextInputAPI | PlainTextInputAPI | null>;
|
activeInput: Writable<RichTextInputAPI | PlainTextInputAPI | null>;
|
||||||
focusInRichText: Writable<boolean>;
|
focusInRichText: Writable<boolean>;
|
||||||
toolbar: EditorToolbarAPI;
|
toolbar: EditorToolbarAPI;
|
||||||
|
@ -22,6 +24,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
const [set, getNoteEditor, hasNoteEditor] = contextProperty<NoteEditorAPI>(key);
|
const [set, getNoteEditor, hasNoteEditor] = contextProperty<NoteEditorAPI>(key);
|
||||||
|
|
||||||
export { getNoteEditor, hasNoteEditor };
|
export { getNoteEditor, hasNoteEditor };
|
||||||
|
|
||||||
|
const activeInput = writable<RichTextInputAPI | PlainTextInputAPI | null>(null);
|
||||||
|
const currentField = writable<EditorFieldAPI | null>(null);
|
||||||
|
|
||||||
|
function updateFocus() {
|
||||||
|
get(activeInput)?.moveCaretToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerShortcut(
|
||||||
|
() => document.addEventListener("focusin", updateFocus, { once: true }),
|
||||||
|
"Shift?+Tab",
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -49,12 +63,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { writable, get } from "svelte/store";
|
|
||||||
import { bridgeCommand } from "../lib/bridgecommand";
|
import { bridgeCommand } from "../lib/bridgecommand";
|
||||||
import { isApplePlatform } from "../lib/platform";
|
import { isApplePlatform } from "../lib/platform";
|
||||||
import { promiseWithResolver } from "../lib/promise";
|
|
||||||
import { ChangeTimer } from "./change-timer";
|
import { ChangeTimer } from "./change-timer";
|
||||||
import { alertIcon } from "./icons";
|
import { alertIcon } from "./icons";
|
||||||
|
import { clearableArray } from "./destroyable";
|
||||||
|
|
||||||
function quoteFontFamily(fontFamily: string): string {
|
function quoteFontFamily(fontFamily: string): string {
|
||||||
// generic families (e.g. sans-serif) must not be quoted
|
// generic families (e.g. sans-serif) must not be quoted
|
||||||
|
@ -105,6 +118,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let fonts: [string, number, boolean][] = [];
|
let fonts: [string, number, boolean][] = [];
|
||||||
let richTextsHidden: boolean[] = [];
|
let richTextsHidden: boolean[] = [];
|
||||||
let plainTextsHidden: boolean[] = [];
|
let plainTextsHidden: boolean[] = [];
|
||||||
|
let fields = clearableArray<EditorFieldAPI>();
|
||||||
|
|
||||||
export function setFonts(fs: [string, number, boolean][]): void {
|
export function setFonts(fs: [string, number, boolean][]): void {
|
||||||
fonts = fs;
|
fonts = fs;
|
||||||
|
@ -115,9 +129,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
let focusTo: number = 0;
|
let focusTo: number = 0;
|
||||||
export function focusField(n: number): void {
|
export function focusField(n: number): void {
|
||||||
focusTo = n;
|
if (typeof n === "number") {
|
||||||
|
focusTo = n;
|
||||||
fieldApis[focusTo]?.editingArea?.refocus();
|
fields[focusTo].editingArea?.refocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let textColor: string = "black";
|
let textColor: string = "black";
|
||||||
|
@ -192,8 +207,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
bridgeCommand("toggleStickyAll", (values: boolean[]) => (stickies = values));
|
bridgeCommand("toggleStickyAll", (values: boolean[]) => (stickies = values));
|
||||||
}
|
}
|
||||||
|
|
||||||
import { registerShortcut } from "../lib/shortcuts";
|
|
||||||
|
|
||||||
let deregisterSticky: () => void;
|
let deregisterSticky: () => void;
|
||||||
export function activateStickyShortcuts() {
|
export function activateStickyShortcuts() {
|
||||||
deregisterSticky = registerShortcut(toggleStickyAll, "Shift+F9");
|
deregisterSticky = registerShortcut(toggleStickyAll, "Shift+F9");
|
||||||
|
@ -218,31 +231,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let plainTextInputs: PlainTextInput[] = [];
|
let plainTextInputs: PlainTextInput[] = [];
|
||||||
$: plainTextInputs = plainTextInputs.filter(Boolean);
|
$: plainTextInputs = plainTextInputs.filter(Boolean);
|
||||||
|
|
||||||
let editorFields: EditorField[] = [];
|
|
||||||
$: fieldApis = editorFields.filter(Boolean).map((field) => field.api);
|
|
||||||
|
|
||||||
const currentField = writable<EditorFieldAPI | null>(null);
|
|
||||||
const activeInput = writable<RichTextInputAPI | PlainTextInputAPI | null>(null);
|
|
||||||
const focusInRichText = writable<boolean>(false);
|
const focusInRichText = writable<boolean>(false);
|
||||||
|
|
||||||
const [toolbarPromise, toolbarResolve] = promiseWithResolver<EditorToolbarAPI>();
|
let toolbar: Partial<EditorToolbarAPI> = {};
|
||||||
let toolbar: EditorToolbarAPI;
|
|
||||||
$: if (toolbar) {
|
|
||||||
toolbarResolve(toolbar);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const api = set(
|
export let api = {};
|
||||||
Object.create(
|
|
||||||
{
|
Object.assign(
|
||||||
currentField,
|
api,
|
||||||
activeInput,
|
set({
|
||||||
focusInRichText,
|
currentField,
|
||||||
toolbar: toolbarPromise,
|
activeInput,
|
||||||
},
|
focusInRichText,
|
||||||
{
|
toolbar: toolbar as EditorToolbarAPI,
|
||||||
fields: { get: () => fieldApis },
|
fields,
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -255,7 +258,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<NoteEditor>
|
<NoteEditor>
|
||||||
<FieldsEditor>
|
<FieldsEditor>
|
||||||
<EditorToolbar {size} {wrap} {textColor} {highlightColor} bind:api={toolbar} />
|
<EditorToolbar {size} {wrap} {textColor} {highlightColor} api={toolbar} />
|
||||||
|
|
||||||
{#if hint}
|
{#if hint}
|
||||||
<Absolute bottom right --margin="10px">
|
<Absolute bottom right --margin="10px">
|
||||||
|
@ -274,9 +277,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{field}
|
{field}
|
||||||
content={fieldStores[index]}
|
content={fieldStores[index]}
|
||||||
autofocus={index === focusTo}
|
autofocus={index === focusTo}
|
||||||
bind:this={editorFields[index]}
|
api={fields[index]}
|
||||||
on:focusin={() => {
|
on:focusin={() => {
|
||||||
$currentField = api.fields[index];
|
$currentField = fields[index];
|
||||||
bridgeCommand(`focus:${index}`);
|
bridgeCommand(`focus:${index}`);
|
||||||
}}
|
}}
|
||||||
on:focusout={() => {
|
on:focusout={() => {
|
||||||
|
|
|
@ -22,9 +22,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
off = !off;
|
off = !off;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() =>
|
function shortcut(element: HTMLElement): void {
|
||||||
registerShortcut(toggle, keyCombination, editorField.element as HTMLElement),
|
registerShortcut(toggle, keyCombination, element);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
onMount(() => editorField.element.then(shortcut));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class:highlighted={!off} on:click|stopPropagation={toggle}>
|
<span class:highlighted={!off} on:click|stopPropagation={toggle}>
|
||||||
|
|
|
@ -148,7 +148,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{configuration}
|
{configuration}
|
||||||
{code}
|
{code}
|
||||||
bind:api={codeMirror}
|
bind:api={codeMirror}
|
||||||
on:focus={moveCaretToEnd}
|
|
||||||
on:change={({ detail: html }) => code.set(parseAsHTML(html))}
|
on:change={({ detail: html }) => code.set(parseAsHTML(html))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,16 +20,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
export let index: number;
|
export let index: number;
|
||||||
|
|
||||||
function toggleSticky() {
|
function toggle() {
|
||||||
bridgeCommand(`toggleSticky:${index}`, (value: boolean) => {
|
bridgeCommand(`toggleSticky:${index}`, (value: boolean) => {
|
||||||
active = value;
|
active = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => registerShortcut(toggleSticky, keyCombination, editorField.element));
|
function shortcut(element: HTMLElement): void {
|
||||||
|
registerShortcut(toggle, keyCombination, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => editorField.element.then(shortcut));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class:highlighted={active} on:click|stopPropagation={toggleSticky}>
|
<span class:highlighted={active} on:click|stopPropagation={toggle}>
|
||||||
<Badge
|
<Badge
|
||||||
tooltip="{tr.editingToggleSticky()} ({getPlatformString(keyCombination)})"
|
tooltip="{tr.editingToggleSticky()} ({getPlatformString(keyCombination)})"
|
||||||
widthMultiplier={0.7}>{@html icon}</Badge
|
widthMultiplier={0.7}>{@html icon}</Badge
|
||||||
|
|
35
ts/editor/destroyable.ts
Normal file
35
ts/editor/destroyable.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
export interface Destroyable {
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearableArray<T>(): (T & Destroyable)[] {
|
||||||
|
const list: (T & Destroyable)[] = [];
|
||||||
|
|
||||||
|
return new Proxy(list, {
|
||||||
|
get: function (target: (T & Destroyable)[], prop: string | symbol) {
|
||||||
|
if (!isNaN(Number(prop)) && !target[prop]) {
|
||||||
|
const item = {} as T & Destroyable;
|
||||||
|
|
||||||
|
const destroy = (): void => {
|
||||||
|
const index = list.indexOf(item);
|
||||||
|
list.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
target[prop] = new Proxy(item, {
|
||||||
|
get: function (target: T & Destroyable, prop: string | symbol) {
|
||||||
|
if (prop === "destroy") {
|
||||||
|
return destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return target[prop];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return target[prop];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -75,8 +75,11 @@ async function setupNoteEditor(): Promise<NoteEditorAPI> {
|
||||||
|
|
||||||
await i18n;
|
await i18n;
|
||||||
|
|
||||||
|
const api: Partial<NoteEditorAPI> = {};
|
||||||
|
|
||||||
const noteEditor = new OldEditorAdapter({
|
const noteEditor = new OldEditorAdapter({
|
||||||
target: document.body,
|
target: document.body,
|
||||||
|
props: { api: api as NoteEditorAPI },
|
||||||
context,
|
context,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,7 +98,7 @@ async function setupNoteEditor(): Promise<NoteEditorAPI> {
|
||||||
setNoteId: noteEditor.setNoteId,
|
setNoteId: noteEditor.setNoteId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return noteEditor.api;
|
return api as NoteEditorAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const noteEditorPromise = setupNoteEditor();
|
export const noteEditorPromise = setupNoteEditor();
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid var(--medium-border);
|
border: 1px solid var(--medium-border);
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
margin-left: -1px;
|
||||||
|
|
||||||
/* !important cannot be used with @include */
|
/* !important cannot be used with @include */
|
||||||
border-top-left-radius: var(--border-left-radius) !important;
|
border-top-left-radius: var(--border-left-radius) !important;
|
||||||
|
@ -13,8 +14,6 @@
|
||||||
border-top-right-radius: var(--border-right-radius) !important;
|
border-top-right-radius: var(--border-right-radius) !important;
|
||||||
border-bottom-right-radius: var(--border-right-radius) !important;
|
border-bottom-right-radius: var(--border-right-radius) !important;
|
||||||
|
|
||||||
margin-left: -1px;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #ebebeb;
|
background-color: #ebebeb;
|
||||||
}
|
}
|
||||||
|
@ -27,10 +26,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.nightMode & {
|
.nightMode & {
|
||||||
|
background: linear-gradient(0deg, #555555 0%, #656565 100%);
|
||||||
|
border-width: 0px;
|
||||||
margin-left: 1px;
|
margin-left: 1px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #7a7a7a;
|
background: #7a7a7a;
|
||||||
border-color: #7a7a7a;
|
border-color: #7a7a7a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@
|
||||||
|
|
||||||
.topbut {
|
.topbut {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: baseline;
|
||||||
width: calc(var(--buttons-size) - 12px);
|
width: calc(var(--buttons-size) - 12px);
|
||||||
height: calc(var(--buttons-size) - 12px);
|
height: calc(var(--buttons-size) - 12px);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue