From cd33e1b05f9ff20e0d92513bde586fe9833c067b Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 19 Apr 2021 16:03:47 +0200 Subject: [PATCH 01/18] Add CSS for making empty paragraph elements visible --- ts/editor/editable.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ts/editor/editable.scss b/ts/editor/editable.scss index 9cd7186f3..7489a48a4 100644 --- a/ts/editor/editable.scss +++ b/ts/editor/editable.scss @@ -16,6 +16,16 @@ anki-editable { } } +p { + margin-top: 0; + margin-bottom: 1rem; + + &:empty::after { + content: "\a"; + white-space: pre; + } +} + :host-context(.nightMode) * { @include scrollbar.night-mode; } From 83d5d72777e9cd252cd57999c9dcd7621078f979 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 19 Apr 2021 16:04:20 +0200 Subject: [PATCH 02/18] Generalize inListItem to getAnchorElement --- qt/aqt/editor.py | 8 +------- ts/editor-toolbar/index.ts | 2 ++ ts/editor/inputHandlers.ts | 36 ++++++++++++++++++++++++++---------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 97b416c1b..efb3ef5b9 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -184,13 +184,7 @@ $editorToolbar.addButtonGroup({{ else "" ) - self.web.eval( - f""" -$editorToolbar = document.getElementById("editorToolbar"); -{lefttopbtns_js} -{righttopbtns_js} -""" - ) + self.web.eval(f"{lefttopbtns_js} {righttopbtns_js}") # Top buttons ###################################################################### diff --git a/ts/editor-toolbar/index.ts b/ts/editor-toolbar/index.ts index b411cf8f8..ff744851b 100644 --- a/ts/editor-toolbar/index.ts +++ b/ts/editor-toolbar/index.ts @@ -58,6 +58,8 @@ class EditorToolbar extends HTMLElement { }); connectedCallback(): void { + globalThis.$editorToolbar = this; + setupI18n({ modules: [ModuleName.EDITING] }).then(() => { const buttons = writable([ getNotetypeGroup(), diff --git a/ts/editor/inputHandlers.ts b/ts/editor/inputHandlers.ts index f4a929763..a5786ffaf 100644 --- a/ts/editor/inputHandlers.ts +++ b/ts/editor/inputHandlers.ts @@ -5,18 +5,34 @@ import { EditingArea } from "./editingArea"; import { caretToEnd, nodeIsElement } from "./helpers"; import { triggerChangeTimer } from "./changeTimer"; -function inListItem(currentField: EditingArea): boolean { - const anchor = currentField.getSelection()!.anchorNode!; +const getAnchorParent = ( + predicate: (element: Element) => element is T +) => (currentField: EditingArea): T | null => { + const anchor = currentField.getSelection()?.anchorNode; - let inList = false; - let n = nodeIsElement(anchor) ? anchor : anchor.parentElement; - while (n) { - inList = inList || window.getComputedStyle(n).display == "list-item"; - n = n.parentElement; + if (!anchor) { + return null; } - return inList; -} + let anchorParent: T | null = null; + let element = nodeIsElement(anchor) ? anchor : anchor.parentElement; + + while (element) { + anchorParent = anchorParent || (predicate(element) ? element : null); + element = element.parentElement; + } + + return anchorParent; +}; + +const getListItem = getAnchorParent( + (element: Element): element is HTMLLIElement => + window.getComputedStyle(element).display === "list-item" +); + +const getParagraph = getAnchorParent( + (element: Element): element is HTMLParamElement => element.tagName === "P" +); export function onInput(event: Event): void { // make sure IME changes get saved @@ -35,7 +51,7 @@ export function onKey(evt: KeyboardEvent): void { } // prefer
instead of
- if (evt.code === "Enter" && !inListItem(currentField)) { + if (evt.code === "Enter" && !getListItem(currentField)) { evt.preventDefault(); document.execCommand("insertLineBreak"); } From 5bc8385f8b31fed6adee26a1304ad3c49626a658 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 19 Apr 2021 16:09:25 +0200 Subject: [PATCH 03/18] Don't use
s when inside of paragraph --- ts/editor/inputHandlers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/editor/inputHandlers.ts b/ts/editor/inputHandlers.ts index a5786ffaf..8864aa26b 100644 --- a/ts/editor/inputHandlers.ts +++ b/ts/editor/inputHandlers.ts @@ -51,7 +51,7 @@ export function onKey(evt: KeyboardEvent): void { } // prefer
instead of
- if (evt.code === "Enter" && !getListItem(currentField)) { + if (evt.code === "Enter" && !getListItem(currentField) && !getParagraph(currentField)) { evt.preventDefault(); document.execCommand("insertLineBreak"); } From fcb2ab28e39db43b2d6966c293c682269af39a10 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 19 Apr 2021 16:31:35 +0200 Subject: [PATCH 04/18] Add paragraph button --- ts/editor-toolbar/BUILD.bazel | 1 + ts/editor-toolbar/formatBlock.ts | 16 ++++++++++++++-- ts/editor-toolbar/formatInline.ts | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ts/editor-toolbar/BUILD.bazel b/ts/editor-toolbar/BUILD.bazel index b8bf53313..a52e9b3ea 100644 --- a/ts/editor-toolbar/BUILD.bazel +++ b/ts/editor-toolbar/BUILD.bazel @@ -77,6 +77,7 @@ copy_bootstrap_icons( "mic.svg", # block formatting + "paragraph.svg", "list-ul.svg", "list-ol.svg", "text-paragraph.svg", diff --git a/ts/editor-toolbar/formatBlock.ts b/ts/editor-toolbar/formatBlock.ts index 38339d7b0..f0229ea28 100644 --- a/ts/editor-toolbar/formatBlock.ts +++ b/ts/editor-toolbar/formatBlock.ts @@ -15,6 +15,7 @@ import type { IconButtonProps } from "./IconButton"; import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; +import paragraphIcon from "./paragraph.svg"; import ulIcon from "./list-ul.svg"; import olIcon from "./list-ol.svg"; import listOptionsIcon from "./text-paragraph.svg"; @@ -109,6 +110,12 @@ const iconButton = dynamicComponent(IconButt export function getFormatBlockGroup(): DynamicSvelteComponent & ButtonGroupProps { + const paragraphButton = commandIconButton({ + icon: paragraphIcon, + command: "formatBlock", + tooltip: tr.editingUnorderedList(), + }); + const ulButton = commandIconButton({ icon: ulIcon, command: "insertUnorderedList", @@ -131,7 +138,12 @@ export function getFormatBlockGroup(): DynamicSvelteComponent Date: Mon, 19 Apr 2021 19:56:01 +0200 Subject: [PATCH 05/18] Generalize commandIconButton functionality of setting active state --- ts/editor-toolbar/CommandIconButton.svelte | 52 +++++++++++++--------- ts/editor-toolbar/formatBlock.ts | 11 +++-- ts/editor/inputHandlers.ts | 6 ++- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/ts/editor-toolbar/CommandIconButton.svelte b/ts/editor-toolbar/CommandIconButton.svelte index 42343fb19..96a5885b1 100644 --- a/ts/editor-toolbar/CommandIconButton.svelte +++ b/ts/editor-toolbar/CommandIconButton.svelte @@ -5,19 +5,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {@html icon} diff --git a/ts/editor-toolbar/formatBlock.ts b/ts/editor-toolbar/formatBlock.ts index f0229ea28..e18b0d74c 100644 --- a/ts/editor-toolbar/formatBlock.ts +++ b/ts/editor-toolbar/formatBlock.ts @@ -113,7 +113,11 @@ export function getFormatBlockGroup(): DynamicSvelteComponent { + document.execCommand("formatBlock", false, "p"); + }, tooltip: tr.editingUnorderedList(), + activatable: false, }); const ulButton = commandIconButton({ @@ -139,11 +143,6 @@ export function getFormatBlockGroup(): DynamicSvelteComponent instead of
- if (evt.code === "Enter" && !getListItem(currentField) && !getParagraph(currentField)) { + if ( + evt.code === "Enter" && + !getListItem(currentField) && + !getParagraph(currentField) + ) { evt.preventDefault(); document.execCommand("insertLineBreak"); } From b1de09516218dab84931e7c5136d48d92fe8668d Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Mon, 19 Apr 2021 23:51:09 +0200 Subject: [PATCH 06/18] Include editor-toolbar as a library in editor --- qt/aqt/data/web/css/BUILD.bazel | 9 ---- qt/aqt/data/web/js/BUILD.bazel | 9 ---- qt/aqt/editor.py | 2 - ts/compile_sass.bzl | 2 +- ts/editor-toolbar/BUILD.bazel | 70 +++++++------------------- ts/editor-toolbar/index.ts | 5 +- ts/editor/BUILD.bazel | 42 ++++++++++++++-- ts/editor/editingArea.ts | 7 ++- ts/editor/{editor.scss => fields.scss} | 0 ts/editor/focusHandlers.ts | 7 ++- ts/editor/index.ts | 12 ++--- ts/editor/inputHandlers.ts | 7 ++- ts/html-filter/BUILD.bazel | 2 +- ts/svelte/svelte.bzl | 4 +- ts/tsconfig.json | 3 +- 15 files changed, 81 insertions(+), 100 deletions(-) rename ts/editor/{editor.scss => fields.scss} (100%) diff --git a/qt/aqt/data/web/css/BUILD.bazel b/qt/aqt/data/web/css/BUILD.bazel index cb92658a0..e29acf7ed 100644 --- a/qt/aqt/data/web/css/BUILD.bazel +++ b/qt/aqt/data/web/css/BUILD.bazel @@ -26,20 +26,11 @@ copy_files_into_group( package = "//ts/editor", ) -copy_files_into_group( - name = "editor-toolbar", - srcs = [ - "editor-toolbar.css", - ], - package = "//ts/editor-toolbar", -) - filegroup( name = "css", srcs = [ "css_local", "editor", - "editor-toolbar", ], visibility = ["//qt:__subpackages__"], ) diff --git a/qt/aqt/data/web/js/BUILD.bazel b/qt/aqt/data/web/js/BUILD.bazel index 83f4718fc..b138bd31b 100644 --- a/qt/aqt/data/web/js/BUILD.bazel +++ b/qt/aqt/data/web/js/BUILD.bazel @@ -37,20 +37,11 @@ copy_files_into_group( package = "//ts/editor", ) -copy_files_into_group( - name = "editor-toolbar", - srcs = [ - "editor-toolbar.js", - ], - package = "//ts/editor-toolbar", -) - filegroup( name = "js", srcs = [ "aqt_es5", "editor", - "editor-toolbar", "mathjax.js", "//qt/aqt/data/web/js/vendor", ], diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index efb3ef5b9..420653512 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -141,13 +141,11 @@ class Editor: _html % (bgcol, tr.editing_show_duplicates()), css=[ "css/editor.css", - "css/editor-toolbar.css", ], js=[ "js/vendor/jquery.min.js", "js/vendor/protobuf.min.js", "js/editor.js", - "js/editor-toolbar.js", ], context=self, default_css=False, diff --git a/ts/compile_sass.bzl b/ts/compile_sass.bzl index 4e0090dac..c8d96cfb3 100644 --- a/ts/compile_sass.bzl +++ b/ts/compile_sass.bzl @@ -1,6 +1,6 @@ load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") -def compile_sass(group, srcs, visibility, deps): +def compile_sass(group, srcs, deps = [], visibility = ["//visibility:private"]): css_files = [] for scss_file in srcs: base = scss_file.replace(".scss", "") diff --git a/ts/editor-toolbar/BUILD.bazel b/ts/editor-toolbar/BUILD.bazel index a52e9b3ea..b4831e813 100644 --- a/ts/editor-toolbar/BUILD.bazel +++ b/ts/editor-toolbar/BUILD.bazel @@ -17,6 +17,7 @@ compile_svelte( "//ts/sass:button_mixins_lib", "//ts/sass/bootstrap", ], + visibility = ["//visibility:public"], ) compile_sass( @@ -26,42 +27,11 @@ compile_sass( "legacy.scss", ], group = "local_css", - visibility = ["//visibility:public"], deps = [ "//ts/sass:button_mixins_lib", "//ts/sass/bootstrap", ], -) - -ts_library( - name = "index", - srcs = ["index.ts"], - deps = [ - "EditorToolbar", - "lib", - "//ts/lib", - "//ts/sveltelib", - "@npm//svelte", - "@npm//svelte2tsx", - ], -) - -ts_library( - name = "lib", - srcs = glob( - ["*.ts"], - exclude = ["index.ts"], - ), - deps = [ - "//ts:image_module_support", - "//ts/lib", - "//ts/lib:backend_proto", - "//ts/sveltelib", - "@npm//@popperjs/core", - "@npm//@types/bootstrap", - "@npm//bootstrap", - "@npm//svelte", - ], + visibility = ["//visibility:public"], ) copy_bootstrap_icons( @@ -88,6 +58,7 @@ copy_bootstrap_icons( "text-indent-left.svg", "text-indent-right.svg", ], + visibility = ["//visibility:public"], ) copy_mdi_icons( @@ -99,35 +70,28 @@ copy_mdi_icons( "code-brackets.svg", "xml.svg", ], + visibility = ["//visibility:public"], ) -esbuild( +ts_library( name = "editor-toolbar", - srcs = [ - "//ts:protobuf-shim.js", - ], - args = [ - "--global-name=editorToolbar", - "--inject:$(location //ts:protobuf-shim.js)", - "--resolve-extensions=.mjs,.js", - "--loader:.svg=text", - "--log-level=warning", - ], - entry_point = "index.ts", - external = [ - "protobufjs/light", - ], - output_css = "editor-toolbar.css", + module_name = "editor-toolbar", + srcs = glob( + ["*.ts"], + exclude = ["*.test.ts"], + ), + tsconfig = "//ts:tsconfig.json", visibility = ["//visibility:public"], deps = [ "//ts/lib", "//ts/lib:backend_proto", "//ts:image_module_support", - "index", - "bootstrap-icons", - "mdi-icons", - "local_css", - ] + svelte_names, + "//ts/sveltelib", + "@npm//@popperjs/core", + "@npm//@types/bootstrap", + "@npm//bootstrap", + "@npm//svelte", + ], ) # Tests diff --git a/ts/editor-toolbar/index.ts b/ts/editor-toolbar/index.ts index ff744851b..6fa1b5437 100644 --- a/ts/editor-toolbar/index.ts +++ b/ts/editor-toolbar/index.ts @@ -202,9 +202,10 @@ class EditorToolbar extends HTMLElement { customElements.define("anki-editor-toolbar", EditorToolbar); -/* Exports for editor - * @ts-expect-error */ +/* Exports for editor/ +/* @ts-expect-error */ export { updateActiveButtons, clearActiveButtons } from "./CommandIconButton.svelte"; +/* @ts-expect-error */ export { enableButtons, disableButtons } from "./EditorToolbar.svelte"; /* Exports for add-ons */ diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index bdcf754fa..d7da845d2 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -8,7 +8,17 @@ load("//ts:compile_sass.bzl", "compile_sass") compile_sass( srcs = [ "editable.scss", - "editor.scss", + ], + group = "editable_scss", + visibility = ["//visibility:public"], + deps = [ + "//ts/sass:scrollbar_lib", + ], +) + +compile_sass( + srcs = [ + "fields.scss", ], group = "base_css", visibility = ["//visibility:public"], @@ -26,6 +36,7 @@ ts_library( deps = [ "//ts:image_module_support", "//ts/html-filter", + "//ts/editor-toolbar", ], ) @@ -36,17 +47,42 @@ copy_bootstrap_icons( esbuild( name = "editor", + srcs = [ + "//ts:protobuf-shim.js", + ], args = [ "--loader:.svg=text", + "--inject:$(location //ts:protobuf-shim.js)", "--resolve-extensions=.mjs,.js", "--log-level=warning", ], + output_css = "editor.css", + external = [ + "protobufjs/light", + ], entry_point = "index_wrapper.ts", visibility = ["//visibility:public"], deps = [ "base_css", - ":bootstrap-icons", - ":editor_ts", + "bootstrap-icons", + "editor_ts", + "//ts/editor-toolbar:local_css", + "//ts/editor-toolbar:bootstrap-icons", + "//ts/editor-toolbar:mdi-icons", + "//ts/editor-toolbar:ButtonDropdown", + "//ts/editor-toolbar:ButtonGroup", + "//ts/editor-toolbar:IconButton", + "//ts/editor-toolbar:LabelButton", + "//ts/editor-toolbar:SelectButton", + "//ts/editor-toolbar:SelectOption", + "//ts/editor-toolbar:RawButton", + "//ts/editor-toolbar:EditorToolbar", + "//ts/editor-toolbar:CommandIconButton", + "//ts/editor-toolbar:WithDropdownMenu", + "//ts/editor-toolbar:DropdownItem", + "//ts/editor-toolbar:DropdownMenu", + "//ts/editor-toolbar:SquareButton", + "//ts/editor-toolbar:ColorPicker", ], ) diff --git a/ts/editor/editingArea.ts b/ts/editor/editingArea.ts index 5002ba817..a87161683 100644 --- a/ts/editor/editingArea.ts +++ b/ts/editor/editingArea.ts @@ -3,6 +3,7 @@ import type { Editable } from "./editable"; +import { updateActiveButtons } from "editor-toolbar"; import { bridgeCommand } from "./lib"; import { onInput, onKey, onKeyUp } from "./inputHandlers"; import { onFocus, onBlur } from "./focusHandlers"; @@ -59,8 +60,7 @@ export class EditingArea extends HTMLDivElement { this.addEventListener("paste", onPaste); this.addEventListener("copy", onCutOrCopy); this.addEventListener("oncut", onCutOrCopy); - // @ts-expect-error - this.addEventListener("mouseup", editorToolbar.updateActiveButtons); + this.addEventListener("mouseup", updateActiveButtons); const baseStyleSheet = this.baseStyle.sheet as CSSStyleSheet; baseStyleSheet.insertRule("anki-editable {}", 0); @@ -75,8 +75,7 @@ export class EditingArea extends HTMLDivElement { this.removeEventListener("paste", onPaste); this.removeEventListener("copy", onCutOrCopy); this.removeEventListener("oncut", onCutOrCopy); - // @ts-expect-error - this.removeEventListener("mouseup", editorToolbar.updateActiveButtons); + this.removeEventListener("mouseup", updateActiveButtons); } initialize(color: string, content: string): void { diff --git a/ts/editor/editor.scss b/ts/editor/fields.scss similarity index 100% rename from ts/editor/editor.scss rename to ts/editor/fields.scss diff --git a/ts/editor/focusHandlers.ts b/ts/editor/focusHandlers.ts index fb9f44b8c..6bcadd8bd 100644 --- a/ts/editor/focusHandlers.ts +++ b/ts/editor/focusHandlers.ts @@ -1,6 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import { enableButtons, disableButtons } from "editor-toolbar"; import type { EditingArea } from "./editingArea"; import { saveField } from "./changeTimer"; @@ -10,8 +11,7 @@ export function onFocus(evt: FocusEvent): void { const currentField = evt.currentTarget as EditingArea; currentField.focusEditable(); bridgeCommand(`focus:${currentField.ord}`); - // @ts-expect-error - editorToolbar.enableButtons(); + enableButtons(); } export function onBlur(evt: FocusEvent): void { @@ -19,6 +19,5 @@ export function onBlur(evt: FocusEvent): void { const currentFieldUnchanged = previousFocus === document.activeElement; saveField(previousFocus, currentFieldUnchanged ? "key" : "blur"); - // @ts-expect-error - editorToolbar.disableButtons(); + disableButtons(); } diff --git a/ts/editor/index.ts b/ts/editor/index.ts index c7e2121b8..0297963e1 100644 --- a/ts/editor/index.ts +++ b/ts/editor/index.ts @@ -2,6 +2,8 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { filterHTML } from "html-filter"; +import { updateActiveButtons, disableButtons } from "editor-toolbar"; +import "./fields.css"; import { caretToEnd } from "./helpers"; import { saveField } from "./changeTimer"; @@ -24,6 +26,7 @@ declare global { } } +import "editor-toolbar"; customElements.define("anki-editable", Editable); customElements.define("anki-editing-area", EditingArea, { extends: "div" }); customElements.define("anki-label-container", LabelContainer, { extends: "div" }); @@ -41,8 +44,7 @@ export function focusField(n: number): void { if (field) { field.editingArea.focusEditable(); caretToEnd(field.editingArea); - // @ts-expect-error - editorToolbar.updateActiveButtons(); + updateActiveButtons(); } } @@ -122,8 +124,7 @@ export function setFields(fields: [string, string][]): void { if (!getCurrentField()) { // when initial focus of the window is not on editor (e.g. browser) - // @ts-expect-error - editorToolbar.disableButtons(); + disableButtons(); } } @@ -158,7 +159,6 @@ export function setFormat(cmd: string, arg?: any, nosave: boolean = false): void document.execCommand(cmd, false, arg); if (!nosave) { saveField(getCurrentField() as EditingArea, "key"); - // @ts-expect-error - editorToolbar.updateActiveButtons(); + updateActiveButtons(); } } diff --git a/ts/editor/inputHandlers.ts b/ts/editor/inputHandlers.ts index eed5e72e4..d9a5eb857 100644 --- a/ts/editor/inputHandlers.ts +++ b/ts/editor/inputHandlers.ts @@ -1,6 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import { updateActiveButtons } from "editor-toolbar"; import { EditingArea } from "./editingArea"; import { caretToEnd, nodeIsElement } from "./helpers"; import { triggerChangeTimer } from "./changeTimer"; @@ -37,8 +38,7 @@ const getParagraph = getAnchorParent( export function onInput(event: Event): void { // make sure IME changes get saved triggerChangeTimer(event.currentTarget as EditingArea); - // @ts-ignore - editorToolbar.updateActiveButtons(); + updateActiveButtons(); } export function onKey(evt: KeyboardEvent): void { @@ -89,8 +89,7 @@ globalThis.addEventListener("keydown", (evt: KeyboardEvent) => { const newFocusTarget = evt.target; if (newFocusTarget instanceof EditingArea) { caretToEnd(newFocusTarget); - // @ts-ignore - editorToolbar.updateActiveButtons(); + updateActiveButtons(); } }, { once: true } diff --git a/ts/html-filter/BUILD.bazel b/ts/html-filter/BUILD.bazel index d4fed4512..cf5e71d7e 100644 --- a/ts/html-filter/BUILD.bazel +++ b/ts/html-filter/BUILD.bazel @@ -5,11 +5,11 @@ load("//ts:eslint.bzl", "eslint_test") ts_library( name = "html-filter", + module_name = "html-filter", srcs = glob( ["*.ts"], exclude = ["*.test.ts"], ), - module_name = "html-filter", tsconfig = "//ts:tsconfig.json", visibility = ["//visibility:public"], deps = [], diff --git a/ts/svelte/svelte.bzl b/ts/svelte/svelte.bzl index 9b9b1c9e6..149447286 100644 --- a/ts/svelte/svelte.bzl +++ b/ts/svelte/svelte.bzl @@ -63,17 +63,19 @@ svelte = rule( }, ) -def compile_svelte(name, srcs, deps = []): +def compile_svelte(name, srcs, deps = [], visibility = ["//visibility:private"]): for src in srcs: svelte( name = src.replace(".svelte", ""), entry_point = src, deps = deps, + visibility = visibility, ) native.filegroup( name = name, srcs = srcs, + visibility = visibility, ) def svelte_check(name = "svelte_check", srcs = []): diff --git a/ts/tsconfig.json b/ts/tsconfig.json index 1895c002b..bc98946fd 100644 --- a/ts/tsconfig.json +++ b/ts/tsconfig.json @@ -8,7 +8,8 @@ "paths": { "anki/*": ["../bazel-bin/ts/lib/*"], "sveltelib/*": ["../bazel-bin/ts/sveltelib/*"], - "html-filter/*": ["../bazel-bin/ts/html-filter/*"] + "html-filter/*": ["../bazel-bin/ts/html-filter/*"], + "editor-toolbar/*": ["../bazel-bin/ts/editor-toolbar/*"] }, "importsNotUsedAsValues": "error", "outDir": "dist", From 9803bb19ca1824a6d01cf88b51b3c2c9055e9a72 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 20 Apr 2021 02:07:15 +0200 Subject: [PATCH 07/18] Move button implementations from editor-toolbar to editor --- ts/editor-toolbar/BUILD.bazel | 41 ------------- ts/editor-toolbar/editorToolbar.d.ts | 5 ++ ts/editor-toolbar/index.ts | 61 +++++-------------- ts/editor/BUILD.bazel | 47 ++++++++++++-- ts/editor/addons.ts | 22 +++++++ ts/{editor-toolbar => editor}/cloze.ts | 4 +- ts/{editor-toolbar => editor}/color.scss | 0 ts/{editor-toolbar => editor}/color.ts | 12 ++-- ts/{editor-toolbar => editor}/formatBlock.ts | 20 +++--- ts/{editor-toolbar => editor}/formatInline.ts | 8 +-- ts/editor/index.ts | 34 +++++++++++ ts/{editor-toolbar => editor}/notetype.ts | 8 +-- ts/{editor-toolbar => editor}/template.ts | 20 +++--- 13 files changed, 156 insertions(+), 126 deletions(-) create mode 100644 ts/editor-toolbar/editorToolbar.d.ts create mode 100644 ts/editor/addons.ts rename ts/{editor-toolbar => editor}/cloze.ts (91%) rename ts/{editor-toolbar => editor}/color.scss (100%) rename ts/{editor-toolbar => editor}/color.ts (81%) rename ts/{editor-toolbar => editor}/formatBlock.ts (85%) rename ts/{editor-toolbar => editor}/formatInline.ts (88%) rename ts/{editor-toolbar => editor}/notetype.ts (81%) rename ts/{editor-toolbar => editor}/template.ts (84%) diff --git a/ts/editor-toolbar/BUILD.bazel b/ts/editor-toolbar/BUILD.bazel index b4831e813..92419a273 100644 --- a/ts/editor-toolbar/BUILD.bazel +++ b/ts/editor-toolbar/BUILD.bazel @@ -4,7 +4,6 @@ load("//ts:prettier.bzl", "prettier_test") load("//ts:eslint.bzl", "eslint_test") load("//ts:esbuild.bzl", "esbuild") load("//ts:compile_sass.bzl", "compile_sass") -load("//ts:vendor.bzl", "copy_bootstrap_icons", "copy_mdi_icons") svelte_files = glob(["*.svelte"]) @@ -23,7 +22,6 @@ compile_svelte( compile_sass( srcs = [ "bootstrap.scss", - "color.scss", "legacy.scss", ], group = "local_css", @@ -34,45 +32,6 @@ compile_sass( visibility = ["//visibility:public"], ) -copy_bootstrap_icons( - name = "bootstrap-icons", - icons = [ - # inline formatting - "type-bold.svg", - "type-italic.svg", - "type-underline.svg", - "eraser.svg", - "square-fill.svg", - "paperclip.svg", - "mic.svg", - - # block formatting - "paragraph.svg", - "list-ul.svg", - "list-ol.svg", - "text-paragraph.svg", - "justify.svg", - "text-left.svg", - "text-right.svg", - "text-center.svg", - "text-indent-left.svg", - "text-indent-right.svg", - ], - visibility = ["//visibility:public"], -) - -copy_mdi_icons( - name = "mdi-icons", - icons = [ - "format-superscript.svg", - "format-subscript.svg", - "function-variant.svg", - "code-brackets.svg", - "xml.svg", - ], - visibility = ["//visibility:public"], -) - ts_library( name = "editor-toolbar", module_name = "editor-toolbar", diff --git a/ts/editor-toolbar/editorToolbar.d.ts b/ts/editor-toolbar/editorToolbar.d.ts new file mode 100644 index 000000000..d26bebd5e --- /dev/null +++ b/ts/editor-toolbar/editorToolbar.d.ts @@ -0,0 +1,5 @@ +import type { EditorToolbar } from "."; + +declare global { + var $editorToolbar: EditorToolbar; +} diff --git a/ts/editor-toolbar/index.ts b/ts/editor-toolbar/index.ts index 6fa1b5437..aab58bcca 100644 --- a/ts/editor-toolbar/index.ts +++ b/ts/editor-toolbar/index.ts @@ -11,15 +11,8 @@ import { Writable, writable } from "svelte/store"; import EditorToolbarSvelte from "./EditorToolbar.svelte"; -import { setupI18n, ModuleName } from "anki/i18n"; - import "./bootstrap.css"; -import { getNotetypeGroup } from "./notetype"; -import { getFormatInlineGroup } from "./formatInline"; -import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock"; -import { getColorGroup } from "./color"; -import { getTemplateGroup, getTemplateMenus } from "./template"; import { Identifiable, search, add, insert } from "./identifiable"; interface Hideable { @@ -45,7 +38,7 @@ let buttonsResolve: ( ) => void; let menusResolve: (value: Writable) => void; -class EditorToolbar extends HTMLElement { +export class EditorToolbar extends HTMLElement { component?: SvelteComponentDev; buttonsPromise: Promise< @@ -60,30 +53,20 @@ class EditorToolbar extends HTMLElement { connectedCallback(): void { globalThis.$editorToolbar = this; - setupI18n({ modules: [ModuleName.EDITING] }).then(() => { - const buttons = writable([ - getNotetypeGroup(), - getFormatInlineGroup(), - getFormatBlockGroup(), - getColorGroup(), - getTemplateGroup(), - ]); - const menus = writable([...getTemplateMenus(), ...getFormatBlockMenus()]); + const buttons = writable([]); + const menus = writable([]); - this.component = new EditorToolbarSvelte({ - target: this, - props: { - buttons, - menus, - nightMode: document.documentElement.classList.contains( - "night-mode" - ), - }, - }); - - buttonsResolve(buttons); - menusResolve(menus); + this.component = new EditorToolbarSvelte({ + target: this, + props: { + buttons, + menus, + nightMode: document.documentElement.classList.contains("night-mode"), + }, }); + + buttonsResolve(buttons); + menusResolve(menus); } updateButtonGroup( @@ -202,20 +185,8 @@ class EditorToolbar extends HTMLElement { customElements.define("anki-editor-toolbar", EditorToolbar); -/* Exports for editor/ -/* @ts-expect-error */ +/* Exports for editor */ +// @ts-expect-error export { updateActiveButtons, clearActiveButtons } from "./CommandIconButton.svelte"; -/* @ts-expect-error */ +// @ts-expect-error export { enableButtons, disableButtons } from "./EditorToolbar.svelte"; - -/* Exports for add-ons */ -export { default as RawButton } from "./RawButton.svelte"; -export { default as LabelButton } from "./LabelButton.svelte"; -export { default as IconButton } from "./IconButton.svelte"; -export { default as CommandIconButton } from "./CommandIconButton.svelte"; -export { default as SelectButton } from "./SelectButton.svelte"; - -export { default as DropdownMenu } from "./DropdownMenu.svelte"; -export { default as DropdownItem } from "./DropdownItem.svelte"; -export { default as ButtonDropdown } from "./DropdownMenu.svelte"; -export { default as WithDropdownMenu } from "./WithDropdownMenu.svelte"; diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index d7da845d2..c4edeedd1 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_library") load("//ts:prettier.bzl", "prettier_test") load("//ts:eslint.bzl", "eslint_test") load("//ts:esbuild.bzl", "esbuild") -load("//ts:vendor.bzl", "copy_bootstrap_icons") +load("//ts:vendor.bzl", "copy_bootstrap_icons", "copy_mdi_icons") load("//ts:compile_sass.bzl", "compile_sass") compile_sass( @@ -19,6 +19,7 @@ compile_sass( compile_sass( srcs = [ "fields.scss", + "color.scss", ], group = "base_css", visibility = ["//visibility:public"], @@ -35,14 +36,53 @@ ts_library( tsconfig = "//ts:tsconfig.json", deps = [ "//ts:image_module_support", + "//ts/lib", + "//ts/sveltelib", "//ts/html-filter", "//ts/editor-toolbar", + "@npm//svelte", ], ) copy_bootstrap_icons( name = "bootstrap-icons", - icons = ["pin-angle.svg"], + icons = [ + "pin-angle.svg", + + # inline formatting + "type-bold.svg", + "type-italic.svg", + "type-underline.svg", + "eraser.svg", + "square-fill.svg", + "paperclip.svg", + "mic.svg", + + # block formatting + "paragraph.svg", + "list-ul.svg", + "list-ol.svg", + "text-paragraph.svg", + "justify.svg", + "text-left.svg", + "text-right.svg", + "text-center.svg", + "text-indent-left.svg", + "text-indent-right.svg", + ], + visibility = ["//visibility:public"], +) + +copy_mdi_icons( + name = "mdi-icons", + icons = [ + "format-superscript.svg", + "format-subscript.svg", + "function-variant.svg", + "code-brackets.svg", + "xml.svg", + ], + visibility = ["//visibility:public"], ) esbuild( @@ -65,10 +105,9 @@ esbuild( deps = [ "base_css", "bootstrap-icons", + "mdi-icons", "editor_ts", "//ts/editor-toolbar:local_css", - "//ts/editor-toolbar:bootstrap-icons", - "//ts/editor-toolbar:mdi-icons", "//ts/editor-toolbar:ButtonDropdown", "//ts/editor-toolbar:ButtonGroup", "//ts/editor-toolbar:IconButton", diff --git a/ts/editor/addons.ts b/ts/editor/addons.ts new file mode 100644 index 000000000..b275e233d --- /dev/null +++ b/ts/editor/addons.ts @@ -0,0 +1,22 @@ +import { default as RawButton } from "editor-toolbar/RawButton.svelte"; +import { default as LabelButton } from "editor-toolbar/LabelButton.svelte"; +import { default as IconButton } from "editor-toolbar/IconButton.svelte"; +import { default as CommandIconButton } from "editor-toolbar/CommandIconButton.svelte"; +import { default as SelectButton } from "editor-toolbar/SelectButton.svelte"; + +import { default as DropdownMenu } from "editor-toolbar/DropdownMenu.svelte"; +import { default as DropdownItem } from "editor-toolbar/DropdownItem.svelte"; +import { default as ButtonDropdown } from "editor-toolbar/DropdownMenu.svelte"; +import { default as WithDropdownMenu } from "editor-toolbar/WithDropdownMenu.svelte"; + +export const editorToolbar = { + RawButton, + LabelButton, + IconButton, + CommandIconButton, + SelectButton, + DropdownMenu, + DropdownItem, + ButtonDropdown, + WithDropdownMenu, +}; diff --git a/ts/editor-toolbar/cloze.ts b/ts/editor/cloze.ts similarity index 91% rename from ts/editor-toolbar/cloze.ts rename to ts/editor/cloze.ts index 55fd8ea60..ffa5f5101 100644 --- a/ts/editor-toolbar/cloze.ts +++ b/ts/editor/cloze.ts @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import IconButton from "./IconButton.svelte"; -import type { IconButtonProps } from "./IconButton"; +import IconButton from "editor-toolbar/IconButton.svelte"; +import type { IconButtonProps } from "editor-toolbar/IconButton"; import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; diff --git a/ts/editor-toolbar/color.scss b/ts/editor/color.scss similarity index 100% rename from ts/editor-toolbar/color.scss rename to ts/editor/color.scss diff --git a/ts/editor-toolbar/color.ts b/ts/editor/color.ts similarity index 81% rename from ts/editor-toolbar/color.ts rename to ts/editor/color.ts index 2ed6f932b..04d18e82f 100644 --- a/ts/editor-toolbar/color.ts +++ b/ts/editor/color.ts @@ -1,11 +1,11 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import IconButton from "./IconButton.svelte"; -import type { IconButtonProps } from "./IconButton"; -import ColorPicker from "./ColorPicker.svelte"; -import type { ColorPickerProps } from "./ColorPicker"; -import ButtonGroup from "./ButtonGroup.svelte"; -import type { ButtonGroupProps } from "./ButtonGroup"; +import IconButton from "editor-toolbar/IconButton.svelte"; +import type { IconButtonProps } from "editor-toolbar/IconButton"; +import ColorPicker from "editor-toolbar/ColorPicker.svelte"; +import type { ColorPickerProps } from "editor-toolbar/ColorPicker"; +import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; diff --git a/ts/editor-toolbar/formatBlock.ts b/ts/editor/formatBlock.ts similarity index 85% rename from ts/editor-toolbar/formatBlock.ts rename to ts/editor/formatBlock.ts index e18b0d74c..0de457c20 100644 --- a/ts/editor-toolbar/formatBlock.ts +++ b/ts/editor/formatBlock.ts @@ -1,16 +1,16 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import ButtonGroup from "./ButtonGroup.svelte"; -import type { ButtonGroupProps } from "./ButtonGroup"; -import ButtonDropdown from "./ButtonDropdown.svelte"; -import type { ButtonDropdownProps } from "./ButtonDropdown"; -import WithDropdownMenu from "./WithDropdownMenu.svelte"; -import type { WithDropdownMenuProps } from "./WithDropdownMenu"; +import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; +import ButtonDropdown from "editor-toolbar/ButtonDropdown.svelte"; +import type { ButtonDropdownProps } from "editor-toolbar/ButtonDropdown"; +import WithDropdownMenu from "editor-toolbar/WithDropdownMenu.svelte"; +import type { WithDropdownMenuProps } from "editor-toolbar/WithDropdownMenu"; -import CommandIconButton from "./CommandIconButton.svelte"; -import type { CommandIconButtonProps } from "./CommandIconButton"; -import IconButton from "./IconButton.svelte"; -import type { IconButtonProps } from "./IconButton"; +import CommandIconButton from "editor-toolbar/CommandIconButton.svelte"; +import type { CommandIconButtonProps } from "editor-toolbar/CommandIconButton"; +import IconButton from "editor-toolbar/IconButton.svelte"; +import type { IconButtonProps } from "editor-toolbar/IconButton"; import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; diff --git a/ts/editor-toolbar/formatInline.ts b/ts/editor/formatInline.ts similarity index 88% rename from ts/editor-toolbar/formatInline.ts rename to ts/editor/formatInline.ts index 2629d00f7..4ae1bddf3 100644 --- a/ts/editor-toolbar/formatInline.ts +++ b/ts/editor/formatInline.ts @@ -1,9 +1,9 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import CommandIconButton from "./CommandIconButton.svelte"; -import type { CommandIconButtonProps } from "./CommandIconButton"; -import ButtonGroup from "./ButtonGroup.svelte"; -import type { ButtonGroupProps } from "./ButtonGroup"; +import CommandIconButton from "editor-toolbar/CommandIconButton.svelte"; +import type { CommandIconButtonProps } from "editor-toolbar/CommandIconButton"; +import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; diff --git a/ts/editor/index.ts b/ts/editor/index.ts index 0297963e1..b08025a39 100644 --- a/ts/editor/index.ts +++ b/ts/editor/index.ts @@ -3,6 +3,8 @@ import { filterHTML } from "html-filter"; import { updateActiveButtons, disableButtons } from "editor-toolbar"; +import { setupI18n, ModuleName } from "anki/i18n"; + import "./fields.css"; import { caretToEnd } from "./helpers"; @@ -17,6 +19,8 @@ export { setNoteId, getNoteId } from "./noteId"; export { saveNow } from "./changeTimer"; export { wrap, wrapIntoText } from "./wrap"; +export * from "./addons"; + declare global { interface Selection { modify(s: string, t: string, u: string): void; @@ -162,3 +166,33 @@ export function setFormat(cmd: string, arg?: any, nosave: boolean = false): void updateActiveButtons(); } } + +////////// EDITOR TOOLBAR + +import { getNotetypeGroup } from "./notetype"; +import { getFormatInlineGroup } from "./formatInline"; +import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock"; +import { getColorGroup } from "./color"; +import { getTemplateGroup, getTemplateMenus } from "./template"; + +const i18n = setupI18n({ modules: [ModuleName.EDITING] }); + +document.addEventListener("DOMContentLoaded", () => { + i18n.then(() => { + $editorToolbar.buttonsPromise.then((buttons) => { + buttons.update(() => [ + getNotetypeGroup(), + getFormatInlineGroup(), + getFormatBlockGroup(), + getColorGroup(), + getTemplateGroup(), + ]); + return buttons; + }); + + $editorToolbar.menusPromise.then((menus) => { + menus.update(() => [...getFormatBlockMenus(), ...getTemplateMenus()]); + return menus; + }); + }); +}); diff --git a/ts/editor-toolbar/notetype.ts b/ts/editor/notetype.ts similarity index 81% rename from ts/editor-toolbar/notetype.ts rename to ts/editor/notetype.ts index 80a14095d..f90152ea2 100644 --- a/ts/editor-toolbar/notetype.ts +++ b/ts/editor/notetype.ts @@ -1,9 +1,9 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import LabelButton from "./LabelButton.svelte"; -import type { LabelButtonProps } from "./LabelButton"; -import ButtonGroup from "./ButtonGroup.svelte"; -import type { ButtonGroupProps } from "./ButtonGroup"; +import LabelButton from "editor-toolbar/LabelButton.svelte"; +import type { LabelButtonProps } from "editor-toolbar/LabelButton"; +import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import { bridgeCommand } from "anki/bridgecommand"; diff --git a/ts/editor-toolbar/template.ts b/ts/editor/template.ts similarity index 84% rename from ts/editor-toolbar/template.ts rename to ts/editor/template.ts index 90ca847a2..956dc5e04 100644 --- a/ts/editor-toolbar/template.ts +++ b/ts/editor/template.ts @@ -1,15 +1,15 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import IconButton from "./IconButton.svelte"; -import type { IconButtonProps } from "./IconButton"; -import DropdownMenu from "./DropdownMenu.svelte"; -import type { DropdownMenuProps } from "./DropdownMenu"; -import DropdownItem from "./DropdownItem.svelte"; -import type { DropdownItemProps } from "./DropdownItem"; -import WithDropdownMenu from "./WithDropdownMenu.svelte"; -import type { WithDropdownMenuProps } from "./WithDropdownMenu"; -import ButtonGroup from "./ButtonGroup.svelte"; -import type { ButtonGroupProps } from "./ButtonGroup"; +import IconButton from "editor-toolbar/IconButton.svelte"; +import type { IconButtonProps } from "editor-toolbar/IconButton"; +import DropdownMenu from "editor-toolbar/DropdownMenu.svelte"; +import type { DropdownMenuProps } from "editor-toolbar/DropdownMenu"; +import DropdownItem from "editor-toolbar/DropdownItem.svelte"; +import type { DropdownItemProps } from "editor-toolbar/DropdownItem"; +import WithDropdownMenu from "editor-toolbar/WithDropdownMenu.svelte"; +import type { WithDropdownMenuProps } from "editor-toolbar/WithDropdownMenu"; +import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; import { bridgeCommand } from "anki/bridgecommand"; import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; From 893028b2df3040ab5d5d94c0a80d97094c02a290 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 20 Apr 2021 03:24:08 +0200 Subject: [PATCH 08/18] Make indent outdent only work for list items + make paragraph show its active state --- ts/editor-toolbar/BUILD.bazel | 1 - ts/editor-toolbar/CommandIconButton.d.ts | 8 ++++- ts/editor-toolbar/CommandIconButton.svelte | 21 ++++++------ ts/editor-toolbar/IconButton.svelte | 7 ++-- ts/editor/formatBlock.ts | 39 +++++++++++++++++----- ts/editor/formatInline.ts | 20 ++++++----- ts/editor/helpers.ts | 28 ++++++++++++++++ ts/editor/inputHandlers.ts | 35 ++----------------- ts/editor/template.ts | 8 ++--- 9 files changed, 96 insertions(+), 71 deletions(-) diff --git a/ts/editor-toolbar/BUILD.bazel b/ts/editor-toolbar/BUILD.bazel index 92419a273..4aea9880c 100644 --- a/ts/editor-toolbar/BUILD.bazel +++ b/ts/editor-toolbar/BUILD.bazel @@ -44,7 +44,6 @@ ts_library( deps = [ "//ts/lib", "//ts/lib:backend_proto", - "//ts:image_module_support", "//ts/sveltelib", "@npm//@popperjs/core", "@npm//@types/bootstrap", diff --git a/ts/editor-toolbar/CommandIconButton.d.ts b/ts/editor-toolbar/CommandIconButton.d.ts index 2fe43d032..a64bfd4e6 100644 --- a/ts/editor-toolbar/CommandIconButton.d.ts +++ b/ts/editor-toolbar/CommandIconButton.d.ts @@ -5,6 +5,12 @@ export interface CommandIconButtonProps { className?: string; tooltip: string; icon: string; + command: string; - activatable?: boolean; + onClick: (event: MouseEvent) => void; + + onUpdate: (event: Event) => boolean; + + disables?: boolean; + dropdownToggle?: boolean; } diff --git a/ts/editor-toolbar/CommandIconButton.svelte b/ts/editor-toolbar/CommandIconButton.svelte index 96a5885b1..b78025274 100644 --- a/ts/editor-toolbar/CommandIconButton.svelte +++ b/ts/editor-toolbar/CommandIconButton.svelte @@ -9,17 +9,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html type ActiveMap = Map; const updateMap = new Map() as UpdateMap; - const activeMap = writable(new Map() as ActiveMap); + const activeMap = new Map() as ActiveMap; + const activeStore = writable(activeMap); function updateButton(key: string, event: MouseEvent): void { - activeMap.update( + activeStore.update( (map: ActiveMap): ActiveMap => new Map([...map, [key, updateMap.get(key)(event)]]) ); } function updateButtons(callback: (key: string) => boolean): void { - activeMap.update( + activeStore.update( (map: ActiveMap): ActiveMap => { const newMap = new Map() as ActiveMap; @@ -50,7 +51,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let icon: string; export let command: string; - export let onClick = () => { + export let onClick = (_event: MouseEvent) => { document.execCommand(command); }; @@ -59,19 +60,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html updateButton(command, event); } - export let activatable = true; export let onUpdate = (_event: Event) => document.queryCommandState(command); updateMap.set(command, onUpdate); let active = false; - if (activatable) { - activeMap.subscribe((map: ActiveMap): (() => void) => { - active = Boolean(map.get(command)); - return () => map.delete(command); - }); - } + activeStore.subscribe((map: ActiveMap): (() => void) => { + active = Boolean(map.get(command)); + return () => map.delete(command); + }); + activeMap.set(command, active); export let disables = true; export let dropdownToggle = false; diff --git a/ts/editor-toolbar/IconButton.svelte b/ts/editor-toolbar/IconButton.svelte index ff9e0a0d4..849b33575 100644 --- a/ts/editor-toolbar/IconButton.svelte +++ b/ts/editor-toolbar/IconButton.svelte @@ -8,11 +8,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let id: string; export let className = ""; export let tooltip: string; + export let icon: string; + + export let onClick: (event: MouseEvent) => void; + export let disables = true; export let dropdownToggle = false; - - export let icon = ""; - export let onClick: (event: MouseEvent) => void; diff --git a/ts/editor/formatBlock.ts b/ts/editor/formatBlock.ts index 0de457c20..1d2b4b753 100644 --- a/ts/editor/formatBlock.ts +++ b/ts/editor/formatBlock.ts @@ -12,9 +12,13 @@ import type { CommandIconButtonProps } from "editor-toolbar/CommandIconButton"; import IconButton from "editor-toolbar/IconButton.svelte"; import type { IconButtonProps } from "editor-toolbar/IconButton"; +import type { EditingArea } from "./editingArea"; + import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; +import { getListItem, getParagraph } from "./helpers"; + import paragraphIcon from "./paragraph.svg"; import ulIcon from "./list-ul.svg"; import olIcon from "./list-ol.svg"; @@ -33,6 +37,8 @@ const commandIconButton = dynamicComponent< CommandIconButtonProps >(CommandIconButton); +const iconButton = dynamicComponent(IconButton); + const buttonGroup = dynamicComponent(ButtonGroup); const buttonDropdown = dynamicComponent( ButtonDropdown @@ -43,6 +49,25 @@ const withDropdownMenu = dynamicComponent< WithDropdownMenuProps >(WithDropdownMenu); +const outdentListItem = () => { + const currentField = document.activeElement as EditingArea; + if (getListItem(currentField.shadowRoot!)) { + document.execCommand("outdent"); + } +}; + +const indentListItem = () => { + const currentField = document.activeElement as EditingArea; + if (getListItem(currentField.shadowRoot!)) { + document.execCommand("indent"); + } +}; + +const checkForParagraph = (): boolean => { + const currentField = document.activeElement as EditingArea; + return Boolean(getParagraph(currentField.shadowRoot!)); +}; + export function getFormatBlockMenus(): (DynamicSvelteComponent & ButtonDropdownProps)[] { const justifyLeftButton = commandIconButton({ @@ -79,18 +104,16 @@ export function getFormatBlockMenus(): (DynamicSvelteComponent(IconButton); - export function getFormatBlockGroup(): DynamicSvelteComponent & ButtonGroupProps { const paragraphButton = commandIconButton({ @@ -116,8 +137,8 @@ export function getFormatBlockGroup(): DynamicSvelteComponent { document.execCommand("formatBlock", false, "p"); }, + onUpdate: checkForParagraph, tooltip: tr.editingUnorderedList(), - activatable: false, }); const ulButton = commandIconButton({ diff --git a/ts/editor/formatInline.ts b/ts/editor/formatInline.ts index 4ae1bddf3..ffa0bea66 100644 --- a/ts/editor/formatInline.ts +++ b/ts/editor/formatInline.ts @@ -2,6 +2,8 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import CommandIconButton from "editor-toolbar/CommandIconButton.svelte"; import type { CommandIconButtonProps } from "editor-toolbar/CommandIconButton"; +import IconButton from "editor-toolbar/IconButton.svelte"; +import type { IconButtonProps } from "editor-toolbar/IconButton"; import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; @@ -19,45 +21,47 @@ const commandIconButton = dynamicComponent< typeof CommandIconButton, CommandIconButtonProps >(CommandIconButton); +const iconButton = dynamicComponent(IconButton); const buttonGroup = dynamicComponent(ButtonGroup); export function getFormatInlineGroup(): DynamicSvelteComponent & ButtonGroupProps { const boldButton = commandIconButton({ icon: boldIcon, - command: "bold", tooltip: tr.editingBoldTextCtrlandb(), + command: "bold", }); const italicButton = commandIconButton({ icon: italicIcon, - command: "italic", tooltip: tr.editingItalicTextCtrlandi(), + command: "italic", }); const underlineButton = commandIconButton({ icon: underlineIcon, - command: "underline", tooltip: tr.editingUnderlineTextCtrlandu(), + command: "underline", }); const superscriptButton = commandIconButton({ icon: superscriptIcon, - command: "superscript", tooltip: tr.editingSuperscriptCtrlandand(), + command: "superscript", }); const subscriptButton = commandIconButton({ icon: subscriptIcon, - command: "subscript", tooltip: tr.editingSubscriptCtrland(), + command: "subscript", }); - const removeFormatButton = commandIconButton({ + const removeFormatButton = iconButton({ icon: eraserIcon, - command: "removeFormat", - activatable: false, tooltip: tr.editingRemoveFormattingCtrlandr(), + onClick: () => { + document.execCommand("removeFormat"); + }, }); return buttonGroup({ diff --git a/ts/editor/helpers.ts b/ts/editor/helpers.ts index bfed2ed7a..5f5ac5abf 100644 --- a/ts/editor/helpers.ts +++ b/ts/editor/helpers.ts @@ -77,3 +77,31 @@ export function caretToEnd(currentField: EditingArea): void { selection.removeAllRanges(); selection.addRange(range); } + +const getAnchorParent = ( + predicate: (element: Element) => element is T +) => (currentField: DocumentOrShadowRoot): T | null => { + const anchor = currentField.getSelection()?.anchorNode; + + if (!anchor) { + return null; + } + + let anchorParent: T | null = null; + let element = nodeIsElement(anchor) ? anchor : anchor.parentElement; + + while (element) { + anchorParent = anchorParent || (predicate(element) ? element : null); + element = element.parentElement; + } + + return anchorParent; +}; + +const isListItem = (element: Element): element is HTMLLIElement => + window.getComputedStyle(element).display === "list-item"; +const isParagraph = (element: Element): element is HTMLParamElement => + element.tagName === "P"; + +export const getListItem = getAnchorParent(isListItem); +export const getParagraph = getAnchorParent(isParagraph); diff --git a/ts/editor/inputHandlers.ts b/ts/editor/inputHandlers.ts index d9a5eb857..613233969 100644 --- a/ts/editor/inputHandlers.ts +++ b/ts/editor/inputHandlers.ts @@ -3,38 +3,9 @@ import { updateActiveButtons } from "editor-toolbar"; import { EditingArea } from "./editingArea"; -import { caretToEnd, nodeIsElement } from "./helpers"; +import { caretToEnd, nodeIsElement, getListItem, getParagraph } from "./helpers"; import { triggerChangeTimer } from "./changeTimer"; -const getAnchorParent = ( - predicate: (element: Element) => element is T -) => (currentField: EditingArea): T | null => { - const anchor = currentField.getSelection()?.anchorNode; - - if (!anchor) { - return null; - } - - let anchorParent: T | null = null; - let element = nodeIsElement(anchor) ? anchor : anchor.parentElement; - - while (element) { - anchorParent = anchorParent || (predicate(element) ? element : null); - element = element.parentElement; - } - - return anchorParent; -}; - -const getListItem = getAnchorParent( - (element: Element): element is HTMLLIElement => - window.getComputedStyle(element).display === "list-item" -); - -const getParagraph = getAnchorParent( - (element: Element): element is HTMLParamElement => element.tagName === "P" -); - export function onInput(event: Event): void { // make sure IME changes get saved triggerChangeTimer(event.currentTarget as EditingArea); @@ -53,8 +24,8 @@ export function onKey(evt: KeyboardEvent): void { // prefer
instead of
if ( evt.code === "Enter" && - !getListItem(currentField) && - !getParagraph(currentField) + !getListItem(currentField.shadowRoot!) && + !getParagraph(currentField.shadowRoot!) ) { evt.preventDefault(); document.execCommand("insertLineBreak"); diff --git a/ts/editor/template.ts b/ts/editor/template.ts index 956dc5e04..99b503fcd 100644 --- a/ts/editor/template.ts +++ b/ts/editor/template.ts @@ -15,6 +15,8 @@ import { bridgeCommand } from "anki/bridgecommand"; import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; +import { wrap } from "./wrap"; + import paperclipIcon from "./paperclip.svg"; import micIcon from "./mic.svg"; import functionIcon from "./function-variant.svg"; @@ -95,19 +97,16 @@ export function getTemplateMenus(): (DynamicSvelteComponent DropdownMenuProps)[] { const mathjaxMenuItems = [ dropdownItem({ - // @ts-expect-error onClick: () => wrap("\\(", "\\)"), label: tr.editingMathjaxInline(), endLabel: "Ctrl+M, M", }), dropdownItem({ - // @ts-expect-error onClick: () => wrap("\\[", "\\]"), label: tr.editingMathjaxBlock(), endLabel: "Ctrl+M, E", }), dropdownItem({ - // @ts-expect-error onClick: () => wrap("\\(\\ce{", "}\\)"), label: tr.editingMathjaxChemistry(), endLabel: "Ctrl+M, C", @@ -116,19 +115,16 @@ export function getTemplateMenus(): (DynamicSvelteComponent const latexMenuItems = [ dropdownItem({ - // @ts-expect-error onClick: () => wrap("[latex]", "[/latex]"), label: tr.editingLatex(), endLabel: "Ctrl+T, T", }), dropdownItem({ - // @ts-expect-error onClick: () => wrap("[$]", "[/$]"), label: tr.editingLatexEquation(), endLabel: "Ctrl+T, E", }), dropdownItem({ - // @ts-expect-error onClick: () => wrap("[$$]", "[/$$]"), label: tr.editingLatexMathEnv(), endLabel: "Ctrl+T, M", From dcb6a1105315912a054b81c4af62fd5e898b4c22 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 20 Apr 2021 03:55:59 +0200 Subject: [PATCH 09/18] Improve behavior of paragraph command and add tooltip --- ftl/core/editing.ftl | 1 + ts/editor/formatBlock.ts | 18 ++++++++++++++---- ts/editor/helpers.ts | 5 +++++ ts/editor/inputHandlers.ts | 5 ++--- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/ftl/core/editing.ftl b/ftl/core/editing.ftl index 19e59632e..e88d261f7 100644 --- a/ftl/core/editing.ftl +++ b/ftl/core/editing.ftl @@ -29,6 +29,7 @@ editing-mathjax-inline = MathJax inline editing-media = Media editing-ordered-list = Ordered list editing-outdent = Decrease indent +editing-paragraph = Paragraph editing-paste = Paste editing-record-audio-f5 = Record audio (F5) editing-remove-formatting-ctrlandr = Remove formatting (Ctrl+R) diff --git a/ts/editor/formatBlock.ts b/ts/editor/formatBlock.ts index 1d2b4b753..316f5ff62 100644 --- a/ts/editor/formatBlock.ts +++ b/ts/editor/formatBlock.ts @@ -63,6 +63,18 @@ const indentListItem = () => { } }; +const toggleParagraph = (): void => { + const currentField = document.activeElement as EditingArea; + const paragraph = getParagraph(currentField.shadowRoot!); + + if (!paragraph) { + document.execCommand("formatBlock", false, "p"); + } else { + paragraph.insertAdjacentElement("beforeend", document.createElement("br")); + paragraph.replaceWith(...paragraph.childNodes); + } +}; + const checkForParagraph = (): boolean => { const currentField = document.activeElement as EditingArea; return Boolean(getParagraph(currentField.shadowRoot!)); @@ -134,11 +146,9 @@ export function getFormatBlockGroup(): DynamicSvelteComponent { - document.execCommand("formatBlock", false, "p"); - }, + onClick: toggleParagraph, onUpdate: checkForParagraph, - tooltip: tr.editingUnorderedList(), + tooltip: tr.editingParagraph(), }); const ulButton = commandIconButton({ diff --git a/ts/editor/helpers.ts b/ts/editor/helpers.ts index 5f5ac5abf..e8db7b316 100644 --- a/ts/editor/helpers.ts +++ b/ts/editor/helpers.ts @@ -102,6 +102,11 @@ const isListItem = (element: Element): element is HTMLLIElement => window.getComputedStyle(element).display === "list-item"; const isParagraph = (element: Element): element is HTMLParamElement => element.tagName === "P"; +const isBlockElement = ( + element: Element +): element is HTMLLIElement & HTMLParamElement => + isListItem(element) || isBlockElement(element); export const getListItem = getAnchorParent(isListItem); export const getParagraph = getAnchorParent(isParagraph); +export const getBlockElement = getAnchorParent(isBlockElement); diff --git a/ts/editor/inputHandlers.ts b/ts/editor/inputHandlers.ts index 613233969..6d938f86c 100644 --- a/ts/editor/inputHandlers.ts +++ b/ts/editor/inputHandlers.ts @@ -3,7 +3,7 @@ import { updateActiveButtons } from "editor-toolbar"; import { EditingArea } from "./editingArea"; -import { caretToEnd, nodeIsElement, getListItem, getParagraph } from "./helpers"; +import { caretToEnd, nodeIsElement, getBlockElement } from "./helpers"; import { triggerChangeTimer } from "./changeTimer"; export function onInput(event: Event): void { @@ -24,8 +24,7 @@ export function onKey(evt: KeyboardEvent): void { // prefer
instead of
if ( evt.code === "Enter" && - !getListItem(currentField.shadowRoot!) && - !getParagraph(currentField.shadowRoot!) + !getBlockElement(currentField.shadowRoot!) !== evt.shiftKey ) { evt.preventDefault(); document.execCommand("insertLineBreak"); From 37ea39f77917a23bb0e9994db9af73835267398c Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 20 Apr 2021 14:23:28 +0200 Subject: [PATCH 10/18] Move dynamic component initialization logic from editor to editor-toolbar --- ts/editor-toolbar/dynamicComponents.ts | 57 ++++++++++++++++++++++++++ ts/editor/cloze.ts | 10 ++--- ts/editor/color.ts | 13 ++---- ts/editor/formatBlock.ts | 37 +++++------------ ts/editor/formatInline.ts | 20 ++++----- ts/editor/notetype.ts | 10 ++--- ts/editor/template.ts | 32 +++++---------- ts/tsconfig.json | 9 +++- 8 files changed, 103 insertions(+), 85 deletions(-) create mode 100644 ts/editor-toolbar/dynamicComponents.ts diff --git a/ts/editor-toolbar/dynamicComponents.ts b/ts/editor-toolbar/dynamicComponents.ts new file mode 100644 index 000000000..72fc9de30 --- /dev/null +++ b/ts/editor-toolbar/dynamicComponents.ts @@ -0,0 +1,57 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import LabelButton from "./LabelButton.svelte"; +import type { LabelButtonProps } from "./LabelButton"; +import IconButton from "./IconButton.svelte"; +import type { IconButtonProps } from "./IconButton"; +import CommandIconButton from "./CommandIconButton.svelte"; +import type { CommandIconButtonProps } from "./CommandIconButton"; +import ColorPicker from "./ColorPicker.svelte"; +import type { ColorPickerProps } from "./ColorPicker"; +import ButtonGroup from "./ButtonGroup.svelte"; +import type { ButtonGroupProps } from "./ButtonGroup"; + +import ButtonDropdown from "./ButtonDropdown.svelte"; +import type { ButtonDropdownProps } from "./ButtonDropdown"; +import DropdownMenu from "./DropdownMenu.svelte"; +import type { DropdownMenuProps } from "./DropdownMenu"; +import DropdownItem from "./DropdownItem.svelte"; +import type { DropdownItemProps } from "./DropdownItem"; +import WithDropdownMenu from "./WithDropdownMenu.svelte"; +import type { WithDropdownMenuProps } from "./WithDropdownMenu"; + +import { dynamicComponent } from "sveltelib/dynamicComponent"; + +export const labelButton = dynamicComponent( + LabelButton +); +export const iconButton = dynamicComponent( + IconButton +); +export const commandIconButton = dynamicComponent< + typeof CommandIconButton, + CommandIconButtonProps +>(CommandIconButton); +export const colorPicker = dynamicComponent( + ColorPicker +); + +export const buttonGroup = dynamicComponent( + ButtonGroup +); +export const buttonDropdown = dynamicComponent< + typeof ButtonDropdown, + ButtonDropdownProps +>(ButtonDropdown); + +export const dropdownMenu = dynamicComponent( + DropdownMenu +); +export const dropdownItem = dynamicComponent( + DropdownItem +); + +export const withDropdownMenu = dynamicComponent< + typeof WithDropdownMenu, + WithDropdownMenuProps +>(WithDropdownMenu); diff --git a/ts/editor/cloze.ts b/ts/editor/cloze.ts index ffa5f5101..112c393e6 100644 --- a/ts/editor/cloze.ts +++ b/ts/editor/cloze.ts @@ -1,18 +1,20 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import IconButton from "editor-toolbar/IconButton.svelte"; +import type IconButton from "editor-toolbar/IconButton.svelte"; import type { IconButtonProps } from "editor-toolbar/IconButton"; +import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent"; -import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; +import { iconButton } from "editor-toolbar/dynamicComponents"; import bracketsIcon from "./code-brackets.svg"; +import { forEditorField } from "."; + const clozePattern = /\{\{c(\d+)::/gu; function getCurrentHighestCloze(increment: boolean): number { let highest = 0; - // @ts-expect-error forEditorField([], (field) => { const matches = field.editingArea.editable.fieldHTML.matchAll(clozePattern); highest = Math.max( @@ -35,8 +37,6 @@ function onCloze(event: MouseEvent): void { wrap(`{{c${highestCloze}::`, "}}"); } -const iconButton = dynamicComponent(IconButton); - export function getClozeButton(): DynamicSvelteComponent & IconButtonProps { return iconButton({ diff --git a/ts/editor/color.ts b/ts/editor/color.ts index 04d18e82f..2e3a36d55 100644 --- a/ts/editor/color.ts +++ b/ts/editor/color.ts @@ -1,13 +1,10 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import IconButton from "editor-toolbar/IconButton.svelte"; -import type { IconButtonProps } from "editor-toolbar/IconButton"; -import ColorPicker from "editor-toolbar/ColorPicker.svelte"; -import type { ColorPickerProps } from "editor-toolbar/ColorPicker"; -import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; +import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent"; -import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; +import { iconButton, colorPicker, buttonGroup } from "editor-toolbar/dynamicComponents"; import * as tr from "anki/i18n"; import squareFillIcon from "./square-fill.svg"; @@ -27,10 +24,6 @@ function wrapWithForecolor(color: string): void { document.execCommand("forecolor", false, color); } -const iconButton = dynamicComponent(IconButton); -const colorPicker = dynamicComponent(ColorPicker); -const buttonGroup = dynamicComponent(ButtonGroup); - export function getColorGroup(): DynamicSvelteComponent & ButtonGroupProps { const forecolorButton = iconButton({ diff --git a/ts/editor/formatBlock.ts b/ts/editor/formatBlock.ts index 316f5ff62..785971aff 100644 --- a/ts/editor/formatBlock.ts +++ b/ts/editor/formatBlock.ts @@ -1,21 +1,21 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; -import ButtonDropdown from "editor-toolbar/ButtonDropdown.svelte"; +import type ButtonDropdown from "editor-toolbar/ButtonDropdown.svelte"; import type { ButtonDropdownProps } from "editor-toolbar/ButtonDropdown"; -import WithDropdownMenu from "editor-toolbar/WithDropdownMenu.svelte"; -import type { WithDropdownMenuProps } from "editor-toolbar/WithDropdownMenu"; - -import CommandIconButton from "editor-toolbar/CommandIconButton.svelte"; -import type { CommandIconButtonProps } from "editor-toolbar/CommandIconButton"; -import IconButton from "editor-toolbar/IconButton.svelte"; -import type { IconButtonProps } from "editor-toolbar/IconButton"; +import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent"; import type { EditingArea } from "./editingArea"; -import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; +import { + commandIconButton, + iconButton, + buttonGroup, + buttonDropdown, + withDropdownMenu, +} from "editor-toolbar/dynamicComponents"; import { getListItem, getParagraph } from "./helpers"; @@ -32,23 +32,6 @@ import justifyCenterIcon from "./text-center.svg"; import indentIcon from "./text-indent-left.svg"; import outdentIcon from "./text-indent-right.svg"; -const commandIconButton = dynamicComponent< - typeof CommandIconButton, - CommandIconButtonProps ->(CommandIconButton); - -const iconButton = dynamicComponent(IconButton); - -const buttonGroup = dynamicComponent(ButtonGroup); -const buttonDropdown = dynamicComponent( - ButtonDropdown -); - -const withDropdownMenu = dynamicComponent< - typeof WithDropdownMenu, - WithDropdownMenuProps ->(WithDropdownMenu); - const outdentListItem = () => { const currentField = document.activeElement as EditingArea; if (getListItem(currentField.shadowRoot!)) { diff --git a/ts/editor/formatInline.ts b/ts/editor/formatInline.ts index ffa0bea66..09d8ee3b9 100644 --- a/ts/editor/formatInline.ts +++ b/ts/editor/formatInline.ts @@ -1,14 +1,15 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import CommandIconButton from "editor-toolbar/CommandIconButton.svelte"; -import type { CommandIconButtonProps } from "editor-toolbar/CommandIconButton"; -import IconButton from "editor-toolbar/IconButton.svelte"; -import type { IconButtonProps } from "editor-toolbar/IconButton"; -import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; +import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent"; -import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import * as tr from "anki/i18n"; +import { + commandIconButton, + iconButton, + buttonGroup, +} from "editor-toolbar/dynamicComponents"; import boldIcon from "./type-bold.svg"; import italicIcon from "./type-italic.svg"; @@ -17,13 +18,6 @@ import superscriptIcon from "./format-superscript.svg"; import subscriptIcon from "./format-subscript.svg"; import eraserIcon from "./eraser.svg"; -const commandIconButton = dynamicComponent< - typeof CommandIconButton, - CommandIconButtonProps ->(CommandIconButton); -const iconButton = dynamicComponent(IconButton); -const buttonGroup = dynamicComponent(ButtonGroup); - export function getFormatInlineGroup(): DynamicSvelteComponent & ButtonGroupProps { const boldButton = commandIconButton({ diff --git a/ts/editor/notetype.ts b/ts/editor/notetype.ts index f90152ea2..d11aefa79 100644 --- a/ts/editor/notetype.ts +++ b/ts/editor/notetype.ts @@ -1,16 +1,12 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import LabelButton from "editor-toolbar/LabelButton.svelte"; -import type { LabelButtonProps } from "editor-toolbar/LabelButton"; -import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; +import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent"; -import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; import { bridgeCommand } from "anki/bridgecommand"; import * as tr from "anki/i18n"; - -const labelButton = dynamicComponent(LabelButton); -const buttonGroup = dynamicComponent(ButtonGroup); +import { labelButton, buttonGroup } from "editor-toolbar/dynamicComponents"; export function getNotetypeGroup(): DynamicSvelteComponent & ButtonGroupProps { diff --git a/ts/editor/template.ts b/ts/editor/template.ts index 99b503fcd..0f958cfe7 100644 --- a/ts/editor/template.ts +++ b/ts/editor/template.ts @@ -1,18 +1,19 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import IconButton from "editor-toolbar/IconButton.svelte"; -import type { IconButtonProps } from "editor-toolbar/IconButton"; -import DropdownMenu from "editor-toolbar/DropdownMenu.svelte"; +import type DropdownMenu from "editor-toolbar/DropdownMenu.svelte"; import type { DropdownMenuProps } from "editor-toolbar/DropdownMenu"; -import DropdownItem from "editor-toolbar/DropdownItem.svelte"; -import type { DropdownItemProps } from "editor-toolbar/DropdownItem"; -import WithDropdownMenu from "editor-toolbar/WithDropdownMenu.svelte"; -import type { WithDropdownMenuProps } from "editor-toolbar/WithDropdownMenu"; -import ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; +import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent"; import { bridgeCommand } from "anki/bridgecommand"; -import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent"; +import { + iconButton, + withDropdownMenu, + dropdownMenu, + dropdownItem, + buttonGroup, +} from "editor-toolbar/dynamicComponents"; import * as tr from "anki/i18n"; import { wrap } from "./wrap"; @@ -38,19 +39,6 @@ function onHtmlEdit(): void { const mathjaxMenuId = "mathjaxMenu"; -const iconButton = dynamicComponent(IconButton); -const withDropdownMenu = dynamicComponent< - typeof WithDropdownMenu, - WithDropdownMenuProps ->(WithDropdownMenu); -const dropdownMenu = dynamicComponent( - DropdownMenu -); -const dropdownItem = dynamicComponent( - DropdownItem -); -const buttonGroup = dynamicComponent(ButtonGroup); - export function getTemplateGroup(): DynamicSvelteComponent & ButtonGroupProps { const attachmentButton = iconButton({ diff --git a/ts/tsconfig.json b/ts/tsconfig.json index bc98946fd..63afaad58 100644 --- a/ts/tsconfig.json +++ b/ts/tsconfig.json @@ -3,7 +3,14 @@ "compilerOptions": { "target": "es6", "module": "es6", - "lib": ["es2017", "es2019.array", "es2018.promise", "dom", "dom.iterable"], + "lib": [ + "es2017", + "es2020.string", + "es2019.array", + "es2018.promise", + "dom", + "dom.iterable" + ], "baseUrl": ".", "paths": { "anki/*": ["../bazel-bin/ts/lib/*"], From 731f9d109f0abb0325b84f48cc02f32e47db5438 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 20 Apr 2021 15:32:02 +0200 Subject: [PATCH 11/18] Move toolbar initialization into toolbar.ts --- ts/editor-toolbar/editorToolbar.d.ts | 4 +-- ts/editor/index.ts | 29 ++----------------- ts/editor/toolbar.ts | 43 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 ts/editor/toolbar.ts diff --git a/ts/editor-toolbar/editorToolbar.d.ts b/ts/editor-toolbar/editorToolbar.d.ts index d26bebd5e..4fd9afb11 100644 --- a/ts/editor-toolbar/editorToolbar.d.ts +++ b/ts/editor-toolbar/editorToolbar.d.ts @@ -1,5 +1,5 @@ import type { EditorToolbar } from "."; -declare global { - var $editorToolbar: EditorToolbar; +declare namespace globalThis { + const $editorToolbar: EditorToolbar; } diff --git a/ts/editor/index.ts b/ts/editor/index.ts index b08025a39..a08fcbc3c 100644 --- a/ts/editor/index.ts +++ b/ts/editor/index.ts @@ -14,6 +14,7 @@ import { EditorField } from "./editorField"; import { LabelContainer } from "./labelContainer"; import { EditingArea } from "./editingArea"; import { Editable } from "./editable"; +import { initToolbar } from "./toolbar"; export { setNoteId, getNoteId } from "./noteId"; export { saveNow } from "./changeTimer"; @@ -167,32 +168,6 @@ export function setFormat(cmd: string, arg?: any, nosave: boolean = false): void } } -////////// EDITOR TOOLBAR - -import { getNotetypeGroup } from "./notetype"; -import { getFormatInlineGroup } from "./formatInline"; -import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock"; -import { getColorGroup } from "./color"; -import { getTemplateGroup, getTemplateMenus } from "./template"; - const i18n = setupI18n({ modules: [ModuleName.EDITING] }); -document.addEventListener("DOMContentLoaded", () => { - i18n.then(() => { - $editorToolbar.buttonsPromise.then((buttons) => { - buttons.update(() => [ - getNotetypeGroup(), - getFormatInlineGroup(), - getFormatBlockGroup(), - getColorGroup(), - getTemplateGroup(), - ]); - return buttons; - }); - - $editorToolbar.menusPromise.then((menus) => { - menus.update(() => [...getFormatBlockMenus(), ...getTemplateMenus()]); - return menus; - }); - }); -}); +initToolbar(i18n); diff --git a/ts/editor/toolbar.ts b/ts/editor/toolbar.ts new file mode 100644 index 000000000..e2eaca08e --- /dev/null +++ b/ts/editor/toolbar.ts @@ -0,0 +1,43 @@ +import type { ToolbarItem } from "editor-toolbar/types"; +import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; +import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; +import type { Writable } from "svelte/store"; + +import { getNotetypeGroup } from "./notetype"; +import { getFormatInlineGroup } from "./formatInline"; +import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock"; +import { getColorGroup } from "./color"; +import { getTemplateGroup, getTemplateMenus } from "./template"; + +export function initToolbar(i18n: Promise): void { + document.addEventListener("DOMContentLoaded", () => { + i18n.then(() => { + globalThis.$editorToolbar.buttonsPromise.then( + ( + buttons: Writable< + (ToolbarItem & ButtonGroupProps)[] + > + ): Writable<(ToolbarItem & ButtonGroupProps)[]> => { + buttons.update(() => [ + getNotetypeGroup(), + getFormatInlineGroup(), + getFormatBlockGroup(), + getColorGroup(), + getTemplateGroup(), + ]); + return buttons; + } + ); + + globalThis.$editorToolbar.menusPromise.then( + (menus: Writable): Writable => { + menus.update(() => [ + ...getFormatBlockMenus(), + ...getTemplateMenus(), + ]); + return menus; + } + ); + }); + }); +} From 66b3096926638c160f6409c2b233ca11617f48b7 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 20 Apr 2021 15:41:26 +0200 Subject: [PATCH 12/18] Remove last ts-expect-error --- ts/editor/cloze.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ts/editor/cloze.ts b/ts/editor/cloze.ts index 112c393e6..9d3be3530 100644 --- a/ts/editor/cloze.ts +++ b/ts/editor/cloze.ts @@ -10,6 +10,7 @@ import { iconButton } from "editor-toolbar/dynamicComponents"; import bracketsIcon from "./code-brackets.svg"; import { forEditorField } from "."; +import { wrap } from "./wrap"; const clozePattern = /\{\{c(\d+)::/gu; function getCurrentHighestCloze(increment: boolean): number { @@ -32,8 +33,6 @@ function getCurrentHighestCloze(increment: boolean): number { function onCloze(event: MouseEvent): void { const highestCloze = getCurrentHighestCloze(!event.altKey); - - // @ts-expect-error wrap(`{{c${highestCloze}::`, "}}"); } From 488fb1af85a4fb4ce4c1f8284b68ba62f23f9809 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 20 Apr 2021 15:43:59 +0200 Subject: [PATCH 13/18] Add copyright headers --- ts/editor/addons.ts | 2 ++ ts/editor/toolbar.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ts/editor/addons.ts b/ts/editor/addons.ts index b275e233d..4cd3af6be 100644 --- a/ts/editor/addons.ts +++ b/ts/editor/addons.ts @@ -1,3 +1,5 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { default as RawButton } from "editor-toolbar/RawButton.svelte"; import { default as LabelButton } from "editor-toolbar/LabelButton.svelte"; import { default as IconButton } from "editor-toolbar/IconButton.svelte"; diff --git a/ts/editor/toolbar.ts b/ts/editor/toolbar.ts index e2eaca08e..d03e20490 100644 --- a/ts/editor/toolbar.ts +++ b/ts/editor/toolbar.ts @@ -1,3 +1,5 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import type { ToolbarItem } from "editor-toolbar/types"; import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte"; import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup"; From 72b33bf36110c186189a1557dd633910ca922646 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 21 Apr 2021 13:18:31 +0200 Subject: [PATCH 14/18] Gather editor-toolbar svelte components into filegroup --- ts/editor-toolbar/BUILD.bazel | 6 ++++++ ts/editor/BUILD.bazel | 15 +-------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/ts/editor-toolbar/BUILD.bazel b/ts/editor-toolbar/BUILD.bazel index 4aea9880c..975f48ec4 100644 --- a/ts/editor-toolbar/BUILD.bazel +++ b/ts/editor-toolbar/BUILD.bazel @@ -9,6 +9,12 @@ svelte_files = glob(["*.svelte"]) svelte_names = [f.replace(".svelte", "") for f in svelte_files] +filegroup( + name = "svelte_components", + srcs = svelte_names, + visibility = ["//visibility:public"], +) + compile_svelte( name = "svelte", srcs = svelte_files, diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index c4edeedd1..1b1b391fa 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -108,20 +108,7 @@ esbuild( "mdi-icons", "editor_ts", "//ts/editor-toolbar:local_css", - "//ts/editor-toolbar:ButtonDropdown", - "//ts/editor-toolbar:ButtonGroup", - "//ts/editor-toolbar:IconButton", - "//ts/editor-toolbar:LabelButton", - "//ts/editor-toolbar:SelectButton", - "//ts/editor-toolbar:SelectOption", - "//ts/editor-toolbar:RawButton", - "//ts/editor-toolbar:EditorToolbar", - "//ts/editor-toolbar:CommandIconButton", - "//ts/editor-toolbar:WithDropdownMenu", - "//ts/editor-toolbar:DropdownItem", - "//ts/editor-toolbar:DropdownMenu", - "//ts/editor-toolbar:SquareButton", - "//ts/editor-toolbar:ColorPicker", + "//ts/editor-toolbar:svelte_components", ], ) From 64db04f1bb8d1edf78fd8c011309771378c851e4 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 21 Apr 2021 14:18:44 +0200 Subject: [PATCH 15/18] Prefer exec over matchAll --- ts/editor/cloze.ts | 14 +++++++++----- ts/tsconfig.json | 9 +-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/ts/editor/cloze.ts b/ts/editor/cloze.ts index 9d3be3530..8a3a6491a 100644 --- a/ts/editor/cloze.ts +++ b/ts/editor/cloze.ts @@ -17,11 +17,15 @@ function getCurrentHighestCloze(increment: boolean): number { let highest = 0; forEditorField([], (field) => { - const matches = field.editingArea.editable.fieldHTML.matchAll(clozePattern); - highest = Math.max( - highest, - ...[...matches].map((match: RegExpMatchArray): number => Number(match[1])) - ); + const fieldHTML = field.editingArea.editable.fieldHTML; + const matches: number[] = []; + let match: RegExpMatchArray | null = null; + + while ((match = clozePattern.exec(fieldHTML))) { + matches.push(Number(match[1])); + } + + highest = Math.max(highest, ...matches); }); if (increment) { diff --git a/ts/tsconfig.json b/ts/tsconfig.json index 63afaad58..bc98946fd 100644 --- a/ts/tsconfig.json +++ b/ts/tsconfig.json @@ -3,14 +3,7 @@ "compilerOptions": { "target": "es6", "module": "es6", - "lib": [ - "es2017", - "es2020.string", - "es2019.array", - "es2018.promise", - "dom", - "dom.iterable" - ], + "lib": ["es2017", "es2019.array", "es2018.promise", "dom", "dom.iterable"], "baseUrl": ".", "paths": { "anki/*": ["../bazel-bin/ts/lib/*"], From 1f97f5208e62827d063a724b7e0bb6220ace4edd Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 21 Apr 2021 14:36:24 +0200 Subject: [PATCH 16/18] Remove paragraph button --- ts/editor/BUILD.bazel | 1 - ts/editor/formatBlock.ts | 30 ++---------------------------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index 1b1b391fa..92ce959a2 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -59,7 +59,6 @@ copy_bootstrap_icons( "mic.svg", # block formatting - "paragraph.svg", "list-ul.svg", "list-ol.svg", "text-paragraph.svg", diff --git a/ts/editor/formatBlock.ts b/ts/editor/formatBlock.ts index 785971aff..9810ee1d4 100644 --- a/ts/editor/formatBlock.ts +++ b/ts/editor/formatBlock.ts @@ -17,9 +17,8 @@ import { withDropdownMenu, } from "editor-toolbar/dynamicComponents"; -import { getListItem, getParagraph } from "./helpers"; +import { getListItem } from "./helpers"; -import paragraphIcon from "./paragraph.svg"; import ulIcon from "./list-ul.svg"; import olIcon from "./list-ol.svg"; import listOptionsIcon from "./text-paragraph.svg"; @@ -46,23 +45,6 @@ const indentListItem = () => { } }; -const toggleParagraph = (): void => { - const currentField = document.activeElement as EditingArea; - const paragraph = getParagraph(currentField.shadowRoot!); - - if (!paragraph) { - document.execCommand("formatBlock", false, "p"); - } else { - paragraph.insertAdjacentElement("beforeend", document.createElement("br")); - paragraph.replaceWith(...paragraph.childNodes); - } -}; - -const checkForParagraph = (): boolean => { - const currentField = document.activeElement as EditingArea; - return Boolean(getParagraph(currentField.shadowRoot!)); -}; - export function getFormatBlockMenus(): (DynamicSvelteComponent & ButtonDropdownProps)[] { const justifyLeftButton = commandIconButton({ @@ -126,14 +108,6 @@ export function getFormatBlockMenus(): (DynamicSvelteComponent & ButtonGroupProps { - const paragraphButton = commandIconButton({ - icon: paragraphIcon, - command: "formatBlock", - onClick: toggleParagraph, - onUpdate: checkForParagraph, - tooltip: tr.editingParagraph(), - }); - const ulButton = commandIconButton({ icon: ulIcon, command: "insertUnorderedList", @@ -157,6 +131,6 @@ export function getFormatBlockGroup(): DynamicSvelteComponent Date: Wed, 21 Apr 2021 14:40:16 +0200 Subject: [PATCH 17/18] Fix isBlockElement() --- ts/editor/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/editor/helpers.ts b/ts/editor/helpers.ts index e8db7b316..cffcb1480 100644 --- a/ts/editor/helpers.ts +++ b/ts/editor/helpers.ts @@ -105,7 +105,7 @@ const isParagraph = (element: Element): element is HTMLParamElement => const isBlockElement = ( element: Element ): element is HTMLLIElement & HTMLParamElement => - isListItem(element) || isBlockElement(element); + isListItem(element) || isParagraph(element); export const getListItem = getAnchorParent(isListItem); export const getParagraph = getAnchorParent(isParagraph); From fe4d44da48523dced3f5bc7272a6fbd4c0cf8750 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 21 Apr 2021 15:16:02 +0200 Subject: [PATCH 18/18] Remove paragraph fluent string --- ftl/core/editing.ftl | 1 - 1 file changed, 1 deletion(-) diff --git a/ftl/core/editing.ftl b/ftl/core/editing.ftl index e88d261f7..19e59632e 100644 --- a/ftl/core/editing.ftl +++ b/ftl/core/editing.ftl @@ -29,7 +29,6 @@ editing-mathjax-inline = MathJax inline editing-media = Media editing-ordered-list = Ordered list editing-outdent = Decrease indent -editing-paragraph = Paragraph editing-paste = Paste editing-record-audio-f5 = Record audio (F5) editing-remove-formatting-ctrlandr = Remove formatting (Ctrl+R)