diff --git a/ts/editor-toolbar/cloze.ts b/ts/editor-toolbar/cloze.ts index 5fe0f55f7..76f5925a9 100644 --- a/ts/editor-toolbar/cloze.ts +++ b/ts/editor-toolbar/cloze.ts @@ -33,14 +33,13 @@ function onCloze(event: MouseEvent): void { wrap(`{{c${highestCloze}::`, "}}"); } -const iconButton = dynamicComponent(IconButton); -export const clozeButton = iconButton( - { +const iconButton = dynamicComponent(IconButton); + +export function getClozeButton() { + return iconButton({ id: "cloze", icon: bracketsIcon, onClick: onCloze, - }, - { - tooltip: tr.editingClozeDeletionCtrlandshiftandc, - } -); + tooltip: tr.editingClozeDeletionCtrlandshiftandc(), + }); +} diff --git a/ts/editor-toolbar/color.ts b/ts/editor-toolbar/color.ts index 35f52cf0e..85681c7a6 100644 --- a/ts/editor-toolbar/color.ts +++ b/ts/editor-toolbar/color.ts @@ -25,35 +25,27 @@ function wrapWithForecolor(color: string): void { document.execCommand("forecolor", false, color); } -const iconButton = dynamicComponent(IconButton); -const forecolorButton = iconButton( - { +const iconButton = dynamicComponent(IconButton); +const colorPicker = dynamicComponent(ColorPicker); +const buttonGroup = dynamicComponent(ButtonGroup); + +export function getColorGroup() { + const forecolorButton = iconButton({ icon: squareFillIcon, className: "forecolor", onClick: () => wrapWithForecolor(getForecolor()), - }, - { - tooltip: tr.editingSetForegroundColourF7, - } -); + tooltip: tr.editingSetForegroundColourF7(), + }); -const colorPicker = dynamicComponent(ColorPicker); -const colorpickerButton = colorPicker( - { + const colorpickerButton = colorPicker({ className: "rainbow", onChange: ({ currentTarget }) => setForegroundColor((currentTarget as HTMLInputElement).value), - }, - { - tooltip: tr.editingChangeColourF8, - } -); + tooltip: tr.editingChangeColourF8(), + }); -const buttonGroup = dynamicComponent(ButtonGroup); -export const colorGroup = buttonGroup( - { + return buttonGroup({ id: "color", buttons: [forecolorButton, colorpickerButton], - }, - {} -); + }); +} diff --git a/ts/editor-toolbar/format.ts b/ts/editor-toolbar/format.ts index 1a3fa55a2..2aeb8d4f6 100644 --- a/ts/editor-toolbar/format.ts +++ b/ts/editor-toolbar/format.ts @@ -13,72 +13,51 @@ import superscriptIcon from "./format-superscript.svg"; import subscriptIcon from "./format-subscript.svg"; import eraserIcon from "./eraser.svg"; -const commandIconButton = dynamicComponent(CommandIconButton); +const commandIconButton = dynamicComponent< + typeof CommandIconButton, + CommandIconButtonProps +>(CommandIconButton); +const buttonGroup = dynamicComponent(ButtonGroup); -const boldButton = commandIconButton( - { +export function getFormatGroup() { + const boldButton = commandIconButton({ icon: boldIcon, command: "bold", - }, - { - tooltip: tr.editingBoldTextCtrlandb, - } -); + tooltip: tr.editingBoldTextCtrlandb(), + }); -const italicButton = commandIconButton( - { + const italicButton = commandIconButton({ icon: italicIcon, command: "italic", - }, - { - tooltip: tr.editingItalicTextCtrlandi, - } -); + tooltip: tr.editingItalicTextCtrlandi(), + }); -const underlineButton = commandIconButton( - { + const underlineButton = commandIconButton({ icon: underlineIcon, command: "underline", - }, - { - tooltip: tr.editingUnderlineTextCtrlandu, - } -); + tooltip: tr.editingUnderlineTextCtrlandu(), + }); -const superscriptButton = commandIconButton( - { + const superscriptButton = commandIconButton({ icon: superscriptIcon, command: "superscript", - }, - { - tooltip: tr.editingSuperscriptCtrlandand, - } -); + tooltip: tr.editingSuperscriptCtrlandand(), + }); -const subscriptButton = commandIconButton( - { + const subscriptButton = commandIconButton({ icon: subscriptIcon, command: "subscript", - }, - { - tooltip: tr.editingSubscriptCtrland, - } -); + tooltip: tr.editingSubscriptCtrland(), + }); -const removeFormatButton = commandIconButton( - { + const removeFormatButton = commandIconButton({ icon: eraserIcon, command: "removeFormat", activatable: false, - }, - { - tooltip: tr.editingRemoveFormattingCtrlandr, - } -); + tooltip: tr.editingRemoveFormattingCtrlandr(), + }); -const buttonGroup = dynamicComponent(ButtonGroup); -export const formatGroup = buttonGroup( - { + return buttonGroup({ id: "format", buttons: [ boldButton, @@ -88,6 +67,5 @@ export const formatGroup = buttonGroup( subscriptButton, removeFormatButton, ], - }, - {} -); + }); +} diff --git a/ts/editor-toolbar/index.ts b/ts/editor-toolbar/index.ts index 188c0dd86..e61b904be 100644 --- a/ts/editor-toolbar/index.ts +++ b/ts/editor-toolbar/index.ts @@ -5,17 +5,17 @@ import ButtonGroup from "./ButtonGroup.svelte"; import type { ButtonGroupProps } from "./ButtonGroup"; import { dynamicComponent } from "sveltelib/dynamicComponent"; -import { writable } from "svelte/store"; +import { Writable, writable } from "svelte/store"; import EditorToolbarSvelte from "./EditorToolbar.svelte"; import { checkNightMode } from "anki/nightmode"; import { setupI18n, ModuleName } from "anki/i18n"; -import { notetypeGroup } from "./notetype"; -import { formatGroup } from "./format"; -import { colorGroup } from "./color"; -import { templateGroup, templateMenus } from "./template"; +import { getNotetypeGroup } from "./notetype"; +import { getFormatGroup } from "./format"; +import { getColorGroup } from "./color"; +import { getTemplateGroup, getTemplateMenus } from "./template"; import { Identifiable, search, add, insert } from "./identifiable"; @@ -35,17 +35,26 @@ function toggleComponent(component: Hideable) { component.hidden = !component.hidden; } -const defaultButtons = [notetypeGroup, formatGroup, colorGroup, templateGroup]; -const defaultMenus = [...templateMenus]; +const buttonGroup = dynamicComponent(ButtonGroup); class EditorToolbar extends HTMLElement { component?: SvelteComponent; - buttons = writable(defaultButtons); - menus = writable(defaultMenus); + buttons?: Writable< + (DynamicSvelteComponent & ButtonGroupProps)[] + >; + menus?: Writable; connectedCallback(): void { setupI18n({ modules: [ModuleName.EDITING] }).then(() => { + this.buttons = writable([ + getNotetypeGroup(), + getFormatGroup(), + getColorGroup(), + getTemplateGroup(), + ]); + this.menus = writable([...getTemplateMenus()]); + this.component = new EditorToolbarSvelte({ target: this, props: { @@ -58,27 +67,24 @@ class EditorToolbar extends HTMLElement { } updateButtonGroup( - update: (component: DynamicSvelteComponent & T) => void, + update: ( + component: DynamicSvelteComponent & ButtonGroupProps & T + ) => void, group: string | number ): void { - this.buttons.update( - ( - buttonGroups: (DynamicSvelteComponent & - ButtonGroupProps)[] - ) => { - const foundGroup = search(buttonGroups, group); + this.buttons?.update((buttonGroups) => { + const foundGroup = search(buttonGroups, group); - if (foundGroup) { - update( - foundGroup as DynamicSvelteComponent & - ButtonGroupProps & - T - ); - } - - return buttonGroups; + if (foundGroup) { + update( + foundGroup as DynamicSvelteComponent & + ButtonGroupProps & + T + ); } - ); + + return buttonGroups; + }); } showButtonGroup(group: string | number): void { @@ -94,29 +100,17 @@ class EditorToolbar extends HTMLElement { } insertButtonGroup(newGroup: ButtonGroupProps, group: string | number = 0) { - const buttonGroup = dynamicComponent(ButtonGroup); - this.buttons.update( - ( - buttonGroups: (DynamicSvelteComponent & - ButtonGroupProps)[] - ) => { - const newButtonGroup = buttonGroup(newGroup, {}); - return insert(buttonGroups, newButtonGroup, group); - } - ); + this.buttons?.update((buttonGroups) => { + const newButtonGroup = buttonGroup(newGroup); + return insert(buttonGroups, newButtonGroup, group); + }); } addButtonGroup(newGroup: ButtonGroupProps, group: string | number = -1) { - const buttonGroup = dynamicComponent(ButtonGroup); - this.buttons.update( - ( - buttonGroups: (DynamicSvelteComponent & - ButtonGroupProps)[] - ) => { - const newButtonGroup = buttonGroup(newGroup, {}); - return add(buttonGroups, newButtonGroup, group); - } - ); + this.buttons?.update((buttonGroups) => { + const newButtonGroup = buttonGroup(newGroup); + return add(buttonGroups, newButtonGroup, group); + }); } updateButton( @@ -124,22 +118,16 @@ class EditorToolbar extends HTMLElement { group: string | number, button: string | number ): void { - this.updateButtonGroup( - ( - foundGroup: DynamicSvelteComponent & - ButtonGroupProps - ) => { - const foundButton = search( - foundGroup.buttons as (DynamicSvelteComponent & Identifiable)[], - button - ); + this.updateButtonGroup((foundGroup) => { + const foundButton = search( + foundGroup.buttons as (DynamicSvelteComponent & Identifiable)[], + button + ); - if (foundButton) { - update(foundButton as DynamicSvelteComponent & T); - } - }, - group - ); + if (foundButton) { + update(foundButton as DynamicSvelteComponent & T); + } + }, group); } showButton(group: string | number, button: string | number): void { @@ -159,18 +147,13 @@ class EditorToolbar extends HTMLElement { group: string | number, button: string | number = 0 ) { - this.updateButtonGroup( - ( - component: DynamicSvelteComponent & ButtonGroupProps - ) => { - component.buttons = insert( - component.buttons as (DynamicSvelteComponent & Identifiable)[], - newButton, - button - ); - }, - group - ); + this.updateButtonGroup((component) => { + component.buttons = insert( + component.buttons as (DynamicSvelteComponent & Identifiable)[], + newButton, + button + ); + }, group); } addButton( @@ -178,18 +161,13 @@ class EditorToolbar extends HTMLElement { group: string | number, button: string | number = -1 ) { - this.updateButtonGroup( - ( - component: DynamicSvelteComponent & ButtonGroupProps - ) => { - component.buttons = add( - component.buttons as (DynamicSvelteComponent & Identifiable)[], - newButton, - button - ); - }, - group - ); + this.updateButtonGroup((component) => { + component.buttons = add( + component.buttons as (DynamicSvelteComponent & Identifiable)[], + newButton, + button + ); + }, group); } } diff --git a/ts/editor-toolbar/notetype.ts b/ts/editor-toolbar/notetype.ts index 32a0c5633..b78f1b56b 100644 --- a/ts/editor-toolbar/notetype.ts +++ b/ts/editor-toolbar/notetype.ts @@ -7,34 +7,26 @@ import { dynamicComponent } from "sveltelib/dynamicComponent"; import { bridgeCommand } from "anki/bridgecommand"; import * as tr from "anki/i18n"; -const labelButton = dynamicComponent(LabelButton); -const fieldsButton = labelButton( - { +const labelButton = dynamicComponent(LabelButton); +const buttonGroup = dynamicComponent(ButtonGroup); + +export function getNotetypeGroup() { + const fieldsButton = labelButton({ onClick: () => bridgeCommand("fields"), disables: false, - }, - { - label: () => `${tr.editingFields()}...`, - tooltip: tr.editingCustomizeFields, - } -); + label: `${tr.editingFields()}...`, + tooltip: tr.editingCustomizeFields(), + }); -const cardsButton = labelButton( - { + const cardsButton = labelButton({ onClick: () => bridgeCommand("cards"), disables: false, - }, - { - label: () => `${tr.editingCards()}...`, - tooltip: tr.editingCustomizeCardTemplatesCtrlandl, - } -); + label: `${tr.editingCards()}...`, + tooltip: tr.editingCustomizeCardTemplatesCtrlandl(), + }); -const buttonGroup = dynamicComponent(ButtonGroup); -export const notetypeGroup = buttonGroup( - { + return buttonGroup({ id: "notetype", buttons: [fieldsButton, cardsButton], - }, - {} -); + }); +} diff --git a/ts/editor-toolbar/template.ts b/ts/editor-toolbar/template.ts index 2fae678d7..683b66c0b 100644 --- a/ts/editor-toolbar/template.ts +++ b/ts/editor-toolbar/template.ts @@ -18,7 +18,7 @@ import micIcon from "./mic.svg"; import functionIcon from "./function-variant.svg"; import xmlIcon from "./xml.svg"; -import { clozeButton } from "./cloze"; +import { getClozeButton } from "./cloze"; function onAttachment(): void { bridgeCommand("attach"); @@ -32,105 +32,89 @@ function onHtmlEdit(): void { bridgeCommand("htmlEdit"); } -const iconButton = dynamicComponent(IconButton); - -const withDropdownMenu = dynamicComponent(WithDropdownMenu); -const dropdownMenu = dynamicComponent(DropdownMenu); -const dropdownItem = dynamicComponent(DropdownItem); - -const attachmentButton = iconButton( - { - icon: paperclipIcon, - onClick: onAttachment, - }, - { - tooltip: tr.editingAttachPicturesaudiovideoF3, - } -); - -const recordButton = iconButton( - { icon: micIcon, onClick: onRecord }, - { - tooltip: tr.editingRecordAudioF5, - } -); - -const mathjaxButton = iconButton>( - { - icon: functionIcon, - }, - {} -); - const mathjaxMenuId = "mathjaxMenu"; -const mathjaxMenu = dropdownMenu( - { - id: mathjaxMenuId, - menuItems: [ - dropdownItem( - { - // @ts-expect-error - onClick: () => wrap("\\(", "\\)"), - tooltip: "test", - endLabel: "test", - }, - { label: tr.editingMathjaxInline } - ), - dropdownItem( - { - // @ts-expect-error - onClick: () => wrap("\\[", "\\]"), - tooltip: "test", - endLabel: "test", - }, - { label: tr.editingMathjaxBlock } - ), - dropdownItem( - { - // @ts-expect-error - onClick: () => wrap("\\(\\ce{", "}\\)"), - tooltip: "test", - endLabel: "test", - }, - { label: tr.editingMathjaxChemistry } - ), - ], - }, - {} +const iconButton = dynamicComponent(IconButton); +const withDropdownMenu = dynamicComponent< + typeof WithDropdownMenu, + WithDropdownMenuProps +>(WithDropdownMenu); +const dropdownMenu = dynamicComponent( + DropdownMenu ); +const dropdownItem = dynamicComponent( + DropdownItem +); +const buttonGroup = dynamicComponent(ButtonGroup); -const mathjaxButtonWithMenu = withDropdownMenu( - { +export function getTemplateGroup() { + const attachmentButton = iconButton({ + icon: paperclipIcon, + onClick: onAttachment, + tooltip: tr.editingAttachPicturesaudiovideoF3(), + }); + + const recordButton = iconButton({ + icon: micIcon, + onClick: onRecord, + tooltip: tr.editingRecordAudioF5(), + }); + + const mathjaxButton = iconButton({ + icon: functionIcon, + foo: 5, + }); + + const mathjaxButtonWithMenu = withDropdownMenu({ button: mathjaxButton, menuId: mathjaxMenuId, - }, - {} -); + }); -const htmlButton = iconButton( - { + const htmlButton = iconButton({ icon: xmlIcon, onClick: onHtmlEdit, - }, - { tooltip: tr.editingHtmlEditor, - } -); + }); -const buttonGroup = dynamicComponent(ButtonGroup); -export const templateGroup = buttonGroup( - { + return buttonGroup({ id: "template", buttons: [ attachmentButton, recordButton, - clozeButton, + getClozeButton(), mathjaxButtonWithMenu, htmlButton, ], - }, - {} -); + }); +} -export const templateMenus = [mathjaxMenu]; +export function getTemplateMenus() { + const mathjaxMenu = dropdownMenu({ + id: mathjaxMenuId, + menuItems: [ + dropdownItem({ + // @ts-expect-error + onClick: () => wrap("\\(", "\\)"), + tooltip: "test", + endLabel: "test", + label: tr.editingMathjaxInline(), + }), + dropdownItem({ + // @ts-expect-error + onClick: () => wrap("\\[", "\\]"), + tooltip: "test", + endLabel: "test", + label: tr.editingMathjaxBlock(), + }), + dropdownItem({ + // @ts-expect-error + onClick: () => wrap("\\(\\ce{", "}\\)"), + tooltip: "test", + endLabel: "test", + label: tr.editingMathjaxChemistry(), + }), + ], + }); + + return [mathjaxMenu]; +} diff --git a/ts/sveltelib/dynamicComponent.ts b/ts/sveltelib/dynamicComponent.ts index e3ac9c34f..0752f3004 100644 --- a/ts/sveltelib/dynamicComponent.ts +++ b/ts/sveltelib/dynamicComponent.ts @@ -6,26 +6,11 @@ export interface DynamicSvelteComponent< component: T; } -export const dynamicComponent = ( - component: Comp -) => < - Props extends NonNullable[0]["props"]>, - Lazy extends string = never +export const dynamicComponent = < + Comp extends typeof SvelteComponentDev, + DefaultProps = NonNullable[0]["props"]> >( - props: Omit, - lazyProps: { - [Property in keyof Pick]: () => Pick[Property]; - } -): DynamicSvelteComponent & Props => { - const dynamicComponent = { component, ...props }; - - for (const property in lazyProps) { - const get = lazyProps[property]; - const propertyDescriptor: TypedPropertyDescriptor< - Pick[Extract, string>] - > = { get, enumerable: true }; - Object.defineProperty(dynamicComponent, property, propertyDescriptor); - } - - return dynamicComponent as DynamicSvelteComponent & Props; + component: Comp +) => (props: Props): DynamicSvelteComponent & Props => { + return { component, ...props }; };