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:
Henrik Giesel 2021-11-05 02:29:02 +01:00 committed by GitHub
parent 896d11546b
commit c9bd39c6a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 197 additions and 99 deletions

View file

@ -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)

View file

@ -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")); '
) )

View file

@ -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>

View file

@ -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>

View file

@ -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();

View file

@ -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> = {};
Object.assign(
api,
set({
content, content,
editingInputs: inputsStore, editingInputs: inputsStore,
focus, focus,
refocus, refocus,
}); }),
);
onMount(() => { onMount(() => {
if (autofocus) { if (autofocus) {

View file

@ -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>

View file

@ -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>

View file

@ -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 {
if (typeof n === "number") {
focusTo = n; focusTo = n;
fields[focusTo].editingArea?.refocus();
fieldApis[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(
api,
set({
currentField, currentField,
activeInput, activeInput,
focusInRichText, focusInRichText,
toolbar: toolbarPromise, toolbar: toolbar as EditorToolbarAPI,
}, fields,
{ }),
fields: { get: () => fieldApis },
},
),
); );
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={() => {

View file

@ -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}>

View file

@ -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>

View file

@ -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
View 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];
},
});
}

View file

@ -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();

View file

@ -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);
} }