mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Merge pull request #1150 from hgiesel/notetypechoose
Editor Toolbar cleanup
This commit is contained in:
commit
c71b684a94
28 changed files with 374 additions and 330 deletions
|
@ -386,13 +386,12 @@ class Browser(QMainWindow):
|
||||||
editor._links["preview"] = lambda _editor: self.onTogglePreview()
|
editor._links["preview"] = lambda _editor: self.onTogglePreview()
|
||||||
editor.web.eval(
|
editor.web.eval(
|
||||||
f"""
|
f"""
|
||||||
$editorToolbar.addButton({{
|
$editorToolbar.addButton(editorToolbar.labelButton({{
|
||||||
component: editorToolbar.LabelButton,
|
|
||||||
label: `{tr.actions_preview()}`,
|
label: `{tr.actions_preview()}`,
|
||||||
tooltip: `{tr.browsing_preview_selected_card(val=shortcut(preview_shortcut))}`,
|
tooltip: `{tr.browsing_preview_selected_card(val=shortcut(preview_shortcut))}`,
|
||||||
onClick: () => bridgeCommand("preview"),
|
onClick: () => bridgeCommand("preview"),
|
||||||
disables: false,
|
disables: false,
|
||||||
}}, "notetype");
|
}}), "notetype", -1);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ class Editor:
|
||||||
gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)
|
gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)
|
||||||
|
|
||||||
lefttopbtns_defs = [
|
lefttopbtns_defs = [
|
||||||
f"$editorToolbar.addButton({{ component: editorToolbar.RawButton, html: `{button}` }}, 'notetype');"
|
f"$editorToolbar.addButton(editorToolbar.rawButton({{ html: `{button}` }}), 'notetype', -1);"
|
||||||
for button in lefttopbtns
|
for button in lefttopbtns
|
||||||
]
|
]
|
||||||
lefttopbtns_js = "\n".join(lefttopbtns_defs)
|
lefttopbtns_js = "\n".join(lefttopbtns_defs)
|
||||||
|
@ -167,16 +167,16 @@ class Editor:
|
||||||
|
|
||||||
righttopbtns_defs = "\n".join(
|
righttopbtns_defs = "\n".join(
|
||||||
[
|
[
|
||||||
f"{{ component: editorToolbar.RawButton, html: `{button}` }},"
|
f"editorToolbar.rawButton({{ html: `{button}` }}),"
|
||||||
for button in righttopbtns
|
for button in righttopbtns
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
righttopbtns_js = (
|
righttopbtns_js = (
|
||||||
f"""
|
f"""
|
||||||
$editorToolbar.addButtonGroup({{
|
$editorToolbar.addButton(editorToolbar.buttonGroup({{
|
||||||
id: "addons",
|
id: "addons",
|
||||||
buttons: [ {righttopbtns_defs} ]
|
items: [ {righttopbtns_defs} ]
|
||||||
}});
|
}}), -1);
|
||||||
"""
|
"""
|
||||||
if righttopbtns_defs
|
if righttopbtns_defs
|
||||||
else ""
|
else ""
|
||||||
|
@ -1277,13 +1277,9 @@ gui_hooks.editor_will_munge_html.append(reverse_url_quoting)
|
||||||
|
|
||||||
def set_cloze_button(editor: Editor) -> None:
|
def set_cloze_button(editor: Editor) -> None:
|
||||||
if editor.note.model()["type"] == MODEL_CLOZE:
|
if editor.note.model()["type"] == MODEL_CLOZE:
|
||||||
editor.web.eval(
|
editor.web.eval('$editorToolbar.showButton("template", "cloze"); ')
|
||||||
'document.getElementById("editorToolbar").showButton("template", "cloze"); '
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
editor.web.eval(
|
editor.web.eval('$editorToolbar.hideButton("template", "cloze"); ')
|
||||||
'document.getElementById("editorToolbar").hideButton("template", "cloze"); '
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
gui_hooks.editor_did_load_note.append(set_cloze_button)
|
gui_hooks.editor_did_load_note.append(set_cloze_button)
|
||||||
|
|
2
ts/editor-toolbar/ButtonDropdown.d.ts
vendored
2
ts/editor-toolbar/ButtonDropdown.d.ts
vendored
|
@ -5,5 +5,5 @@ import type { ToolbarItem } from "./types";
|
||||||
export interface ButtonDropdownProps {
|
export interface ButtonDropdownProps {
|
||||||
id: string;
|
id: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
buttons: ToolbarItem[];
|
items: ToolbarItem[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let className = "";
|
export let className = "";
|
||||||
|
|
||||||
function extendClassName(className: string): string {
|
function extendClassName(className: string): string {
|
||||||
return `dropdown-menu btn-dropdown-menu py-1 mb-0 ${className}`;
|
return `dropdown-menu btn-dropdown-menu ${className}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let buttons: ToolbarItem[];
|
export let items: ToolbarItem[];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -22,6 +22,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
background-color: var(--window-bg);
|
background-color: var(--window-bg);
|
||||||
border-color: var(--medium-border);
|
border-color: var(--medium-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(ul.btn-dropdown-menu.show) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<ButtonGroup {id} className={extendClassName(className)} {buttons} />
|
<ButtonGroup {id} className={extendClassName(className)} {items} />
|
||||||
|
|
3
ts/editor-toolbar/ButtonGroup.d.ts
vendored
3
ts/editor-toolbar/ButtonGroup.d.ts
vendored
|
@ -5,5 +5,6 @@ import type { ToolbarItem } from "./types";
|
||||||
export interface ButtonGroupProps {
|
export interface ButtonGroupProps {
|
||||||
id: string;
|
id: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
buttons: ToolbarItem[];
|
items: ToolbarItem[];
|
||||||
|
fullWidth?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
export let id: string | undefined = undefined;
|
export let id: string | undefined = undefined;
|
||||||
export let className = "";
|
export let className = "";
|
||||||
export let buttons: ToolbarItem[];
|
export let items: ToolbarItem[];
|
||||||
|
|
||||||
function filterHidden({ hidden = false, ...props }) {
|
function filterHidden({ hidden = false, ...props }) {
|
||||||
return props;
|
return props;
|
||||||
|
@ -26,19 +26,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
flex-wrap: var(--toolbar-wrap);
|
flex-wrap: var(--toolbar-wrap);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
padding-inline-start: 0;
|
padding: calc(var(--toolbar-size) / 10);
|
||||||
margin: 0 0 calc(var(--toolbar-size) / 10);
|
margin: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.border-overlap-group {
|
&.border-overlap-group {
|
||||||
:global(button),
|
:global(button),
|
||||||
:global(select) {
|
:global(select) {
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.gap-group {
|
||||||
|
:global(button),
|
||||||
|
:global(select) {
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: inline-block;
|
display: contents;
|
||||||
|
|
||||||
> :global(button),
|
> :global(button),
|
||||||
> :global(select) {
|
> :global(select) {
|
||||||
|
@ -46,36 +53,31 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(1) {
|
&:nth-child(1) {
|
||||||
margin-left: calc(var(--toolbar-size) / 7.5);
|
|
||||||
|
|
||||||
> :global(button),
|
> :global(button),
|
||||||
> :global(select) {
|
> :global(select) {
|
||||||
/* default 0.25rem */
|
|
||||||
border-top-left-radius: calc(var(--toolbar-size) / 7.5);
|
border-top-left-radius: calc(var(--toolbar-size) / 7.5);
|
||||||
border-bottom-left-radius: calc(var(--toolbar-size) / 7.5);
|
border-bottom-left-radius: calc(var(--toolbar-size) / 7.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-last-child(1) {
|
&:nth-last-child(1) {
|
||||||
margin-right: calc(var(--toolbar-size) / 7.5);
|
|
||||||
|
|
||||||
> :global(button),
|
> :global(button),
|
||||||
> :global(select) {
|
> :global(select) {
|
||||||
border-top-right-radius: calc(var(--toolbar-size) / 7.5);
|
border-top-right-radius: calc(var(--toolbar-size) / 7.5);
|
||||||
border-bottom-right-radius: calc(var(--toolbar-size) / 7.5);
|
border-bottom-right-radius: calc(var(--toolbar-size) / 7.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.gap-item:not(:nth-child(1)) {
|
|
||||||
margin-left: 1px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<ul {id} class={className} class:border-overlap-group={!nightMode}>
|
<ul
|
||||||
{#each buttons as button}
|
{id}
|
||||||
|
class={className}
|
||||||
|
class:border-overlap-group={!nightMode}
|
||||||
|
class:gap-group={nightMode}>
|
||||||
|
{#each items as button}
|
||||||
{#if !button.hidden}
|
{#if !button.hidden}
|
||||||
<li class:gap-item={nightMode}>
|
<li>
|
||||||
<svelte:component this={button.component} {...filterHidden(button)} />
|
<svelte:component this={button.component} {...filterHidden(button)} />
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
2
ts/editor-toolbar/DropdownMenu.d.ts
vendored
2
ts/editor-toolbar/DropdownMenu.d.ts
vendored
|
@ -4,5 +4,5 @@ import type { ToolbarItem } from "./types";
|
||||||
|
|
||||||
export interface DropdownMenuProps {
|
export interface DropdownMenuProps {
|
||||||
id: string;
|
id: string;
|
||||||
menuItems: ToolbarItem[];
|
items: ToolbarItem[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
import type { ToolbarItem } from "./types";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { nightModeKey } from "./contextKeys";
|
import { nightModeKey } from "./contextKeys";
|
||||||
|
|
||||||
export let id: string;
|
export let id: string;
|
||||||
export let menuItems: DynamicSvelteComponent[];
|
export let items: ToolbarItem[];
|
||||||
|
|
||||||
const nightMode = getContext<boolean>(nightModeKey);
|
const nightMode = getContext<boolean>(nightModeKey);
|
||||||
</script>
|
</script>
|
||||||
|
@ -27,7 +27,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<ul {id} class="dropdown-menu" class:night-mode={nightMode}>
|
<ul {id} class="dropdown-menu" class:night-mode={nightMode}>
|
||||||
{#each menuItems as menuItem}
|
{#each items as menuItem}
|
||||||
<li>
|
<li>
|
||||||
<svelte:component this={menuItem.component} {...menuItem} />
|
<svelte:component this={menuItem.component} {...menuItem} />
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -19,16 +19,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import type { Readable } from "svelte/store";
|
import type { Readable } from "svelte/store";
|
||||||
import type { ToolbarItem } from "./types";
|
import type { ToolbarItem, IterableToolbarItem } from "./types";
|
||||||
import { setContext } from "svelte";
|
import { setContext } from "svelte";
|
||||||
import { disabledKey, nightModeKey } from "./contextKeys";
|
import { disabledKey, nightModeKey } from "./contextKeys";
|
||||||
|
|
||||||
import ButtonGroup from "./ButtonGroup.svelte";
|
import ButtonGroup from "./ButtonGroup.svelte";
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
|
||||||
|
|
||||||
export let buttons: Readable<
|
export let buttons: Readable<IterableToolbarItem[]>;
|
||||||
(ToolbarItem<typeof ButtonGroup> & ButtonGroupProps)[]
|
|
||||||
>;
|
|
||||||
export let menus: Readable<ToolbarItem[]>;
|
export let menus: Readable<ToolbarItem[]>;
|
||||||
|
|
||||||
$: _buttons = $buttons;
|
$: _buttons = $buttons;
|
||||||
|
@ -56,17 +53,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
|
|
||||||
/* Remove outermost marigns */
|
|
||||||
& > :global(ul) {
|
|
||||||
& > :global(li:nth-child(1)) {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > :global(li:nth-last-child(1)) {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -77,5 +63,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav {style}>
|
<nav {style}>
|
||||||
<ButtonGroup buttons={_buttons} className="mt-0" />
|
<ButtonGroup items={_buttons} className="p-0 mb-1" />
|
||||||
</nav>
|
</nav>
|
||||||
|
|
15
ts/editor-toolbar/SelectButton.d.ts
vendored
Normal file
15
ts/editor-toolbar/SelectButton.d.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
export interface Option {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
selected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectButtonProps {
|
||||||
|
id: string;
|
||||||
|
className?: string;
|
||||||
|
tooltip?: string;
|
||||||
|
disables: boolean;
|
||||||
|
options: Option[];
|
||||||
|
}
|
|
@ -4,16 +4,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import type { Readable } from "svelte/store";
|
import type { Readable } from "svelte/store";
|
||||||
|
import type { Option } from "./SelectButton";
|
||||||
import { onMount, createEventDispatcher, getContext } from "svelte";
|
import { onMount, createEventDispatcher, getContext } from "svelte";
|
||||||
import { disabledKey } from "./contextKeys";
|
import { disabledKey } from "./contextKeys";
|
||||||
import SelectOption from "./SelectOption.svelte";
|
import SelectOption from "./SelectOption.svelte";
|
||||||
|
|
||||||
interface Option {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
selected: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export let id: string;
|
export let id: string;
|
||||||
export let className = "";
|
export let className = "";
|
||||||
export let tooltip: string;
|
export let tooltip: string;
|
||||||
|
|
11
ts/editor-toolbar/WithLabel.d.ts
vendored
Normal file
11
ts/editor-toolbar/WithLabel.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
import type { ToolbarItem } from "./types";
|
||||||
|
|
||||||
|
export interface WithLabelProps {
|
||||||
|
id?: string;
|
||||||
|
className?: string;
|
||||||
|
|
||||||
|
button: ToolbarItem;
|
||||||
|
label: string;
|
||||||
|
}
|
27
ts/editor-toolbar/WithLabel.svelte
Normal file
27
ts/editor-toolbar/WithLabel.svelte
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="typescript">
|
||||||
|
import type { ToolbarItem } from "./types";
|
||||||
|
|
||||||
|
export let id: string;
|
||||||
|
export let className = "";
|
||||||
|
|
||||||
|
export let label: string;
|
||||||
|
export let button: ToolbarItem;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 calc(var(--toolbar-size) / 10);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||||
|
|
||||||
|
<label {id} class={className}>
|
||||||
|
<span class="me-1">{label}</span>
|
||||||
|
<svelte:component this={button.component} {...button} />
|
||||||
|
</label>
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
import RawButton from "./RawButton.svelte";
|
||||||
import LabelButton from "./LabelButton.svelte";
|
import LabelButton from "./LabelButton.svelte";
|
||||||
import type { LabelButtonProps } from "./LabelButton";
|
import type { LabelButtonProps } from "./LabelButton";
|
||||||
import IconButton from "./IconButton.svelte";
|
import IconButton from "./IconButton.svelte";
|
||||||
|
@ -8,6 +9,8 @@ import CommandIconButton from "./CommandIconButton.svelte";
|
||||||
import type { CommandIconButtonProps } from "./CommandIconButton";
|
import type { CommandIconButtonProps } from "./CommandIconButton";
|
||||||
import ColorPicker from "./ColorPicker.svelte";
|
import ColorPicker from "./ColorPicker.svelte";
|
||||||
import type { ColorPickerProps } from "./ColorPicker";
|
import type { ColorPickerProps } from "./ColorPicker";
|
||||||
|
import SelectButton from "./SelectButton.svelte";
|
||||||
|
import type { SelectButtonProps } from "./SelectButton";
|
||||||
import ButtonGroup from "./ButtonGroup.svelte";
|
import ButtonGroup from "./ButtonGroup.svelte";
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
import type { ButtonGroupProps } from "./ButtonGroup";
|
||||||
|
|
||||||
|
@ -23,8 +26,14 @@ import type { WithDropdownMenuProps } from "./WithDropdownMenu";
|
||||||
import WithShortcut from "./WithShortcut.svelte";
|
import WithShortcut from "./WithShortcut.svelte";
|
||||||
import type { WithShortcutProps } from "./WithShortcut";
|
import type { WithShortcutProps } from "./WithShortcut";
|
||||||
|
|
||||||
|
import WithLabel from "./WithLabel.svelte";
|
||||||
|
import type { WithLabelProps } from "./WithLabel";
|
||||||
|
|
||||||
import { dynamicComponent } from "sveltelib/dynamicComponent";
|
import { dynamicComponent } from "sveltelib/dynamicComponent";
|
||||||
|
|
||||||
|
export const rawButton = dynamicComponent<typeof RawButton, { html: string }>(
|
||||||
|
RawButton
|
||||||
|
);
|
||||||
export const labelButton = dynamicComponent<typeof LabelButton, LabelButtonProps>(
|
export const labelButton = dynamicComponent<typeof LabelButton, LabelButtonProps>(
|
||||||
LabelButton
|
LabelButton
|
||||||
);
|
);
|
||||||
|
@ -38,6 +47,9 @@ export const commandIconButton = dynamicComponent<
|
||||||
export const colorPicker = dynamicComponent<typeof ColorPicker, ColorPickerProps>(
|
export const colorPicker = dynamicComponent<typeof ColorPicker, ColorPickerProps>(
|
||||||
ColorPicker
|
ColorPicker
|
||||||
);
|
);
|
||||||
|
export const selectButton = dynamicComponent<typeof SelectButton, SelectButtonProps>(
|
||||||
|
SelectButton
|
||||||
|
);
|
||||||
|
|
||||||
export const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(
|
export const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(
|
||||||
ButtonGroup
|
ButtonGroup
|
||||||
|
@ -62,3 +74,5 @@ export const withDropdownMenu = dynamicComponent<
|
||||||
export const withShortcut = dynamicComponent<typeof WithShortcut, WithShortcutProps>(
|
export const withShortcut = dynamicComponent<typeof WithShortcut, WithShortcutProps>(
|
||||||
WithShortcut
|
WithShortcut
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const withLabel = dynamicComponent<typeof WithLabel, WithLabelProps>(WithLabel);
|
||||||
|
|
20
ts/editor-toolbar/hideable.ts
Normal file
20
ts/editor-toolbar/hideable.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
interface Hideable {
|
||||||
|
hidden?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showComponent<T extends Hideable>(component: T): T {
|
||||||
|
component.hidden = false;
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideComponent<T extends Hideable>(component: T): T {
|
||||||
|
component.hidden = true;
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleComponent<T extends Hideable>(component: T): T {
|
||||||
|
component.hidden = !component.hidden;
|
||||||
|
return component;
|
||||||
|
}
|
|
@ -4,43 +4,89 @@ export interface Identifiable {
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalize<T extends Identifiable>(
|
interface IterableIdentifiable<T extends Identifiable> extends Identifiable {
|
||||||
values: T[],
|
items: T[];
|
||||||
idOrIndex: string | number
|
|
||||||
): number {
|
|
||||||
if (typeof idOrIndex === "string") {
|
|
||||||
return values.findIndex((value) => value.id === idOrIndex);
|
|
||||||
} else {
|
|
||||||
return idOrIndex >= values.length ? -1 : idOrIndex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function search<T extends Identifiable>(
|
export type Identifier = string | number;
|
||||||
values: T[],
|
|
||||||
idOrIndex: string | number
|
function normalize<T extends Identifiable>(
|
||||||
): T | null {
|
iterable: IterableIdentifiable<T>,
|
||||||
const index = normalize(values, idOrIndex);
|
idOrIndex: Identifier
|
||||||
|
): number {
|
||||||
|
let normalizedIndex: number;
|
||||||
|
|
||||||
|
if (typeof idOrIndex === "string") {
|
||||||
|
normalizedIndex = iterable.items.findIndex((value) => value.id === idOrIndex);
|
||||||
|
} else if (idOrIndex < 0) {
|
||||||
|
normalizedIndex = iterable.items.length + idOrIndex;
|
||||||
|
} else {
|
||||||
|
normalizedIndex = idOrIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedIndex >= iterable.items.length ? -1 : normalizedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function search<T extends Identifiable>(values: T[], index: number): T | null {
|
||||||
return index >= 0 ? values[index] : null;
|
return index >= 0 ? values[index] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insert<T extends Identifiable>(
|
export function insert<T extends Identifiable>(
|
||||||
values: T[],
|
iterable: IterableIdentifiable<T> & T,
|
||||||
value: T,
|
value: T,
|
||||||
idOrIndex: string | number
|
idOrIndex: Identifier
|
||||||
): T[] {
|
): IterableIdentifiable<T> & T {
|
||||||
const index = normalize(values, idOrIndex);
|
const index = normalize(iterable, idOrIndex);
|
||||||
return index >= 0
|
|
||||||
? [...values.slice(0, index), value, ...values.slice(index)]
|
if (index >= 0) {
|
||||||
: values;
|
iterable.items = iterable.items.slice();
|
||||||
|
iterable.items.splice(index, 0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterable;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function add<T extends Identifiable>(
|
export function add<T extends Identifiable>(
|
||||||
values: T[],
|
iterable: IterableIdentifiable<T> & T,
|
||||||
value: T,
|
value: T,
|
||||||
idOrIndex: string | number
|
idOrIndex: Identifier
|
||||||
): T[] {
|
): IterableIdentifiable<T> & T {
|
||||||
const index = normalize(values, idOrIndex);
|
const index = normalize(iterable, idOrIndex);
|
||||||
return index >= 0
|
|
||||||
? [...values.slice(0, index + 1), value, ...values.slice(index + 1)]
|
if (index >= 0) {
|
||||||
: values;
|
iterable.items = iterable.items.slice();
|
||||||
|
iterable.items.splice(index + 1, 0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecursive<T>(component: Identifiable): component is IterableIdentifiable<T> {
|
||||||
|
return Boolean(Object.prototype.hasOwnProperty.call(component, "items"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateRecursive<T extends Identifiable>(
|
||||||
|
update: (component: T) => T,
|
||||||
|
component: T,
|
||||||
|
...identifiers: Identifier[]
|
||||||
|
): T {
|
||||||
|
if (identifiers.length === 0) {
|
||||||
|
return update(component);
|
||||||
|
} else if (isRecursive<T>(component)) {
|
||||||
|
const [identifier, ...restIdentifiers] = identifiers;
|
||||||
|
const normalizedIndex = normalize(component, identifier);
|
||||||
|
const foundComponent = search(component.items, normalizedIndex);
|
||||||
|
|
||||||
|
if (foundComponent) {
|
||||||
|
component.items[normalizedIndex] = updateRecursive(
|
||||||
|
update,
|
||||||
|
foundComponent as T,
|
||||||
|
...restIdentifiers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,31 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { ToolbarItem, IterableToolbarItem } from "./types";
|
||||||
import type { ToolbarItem } from "./types";
|
import type { Identifier } from "./identifiable";
|
||||||
|
|
||||||
import ButtonGroup from "./ButtonGroup.svelte";
|
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
|
||||||
|
|
||||||
import { dynamicComponent } from "sveltelib/dynamicComponent";
|
|
||||||
import { Writable, writable } from "svelte/store";
|
import { Writable, writable } from "svelte/store";
|
||||||
|
|
||||||
import EditorToolbarSvelte from "./EditorToolbar.svelte";
|
import EditorToolbarSvelte from "./EditorToolbar.svelte";
|
||||||
|
|
||||||
import "./bootstrap.css";
|
import "./bootstrap.css";
|
||||||
|
|
||||||
import { Identifiable, search, add, insert } from "./identifiable";
|
import { add, insert, updateRecursive } from "./identifiable";
|
||||||
|
import { showComponent, hideComponent, toggleComponent } from "./hideable";
|
||||||
|
|
||||||
interface Hideable {
|
let buttonsResolve: (value: Writable<IterableToolbarItem[]>) => void;
|
||||||
hidden?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showComponent(component: Hideable): void {
|
|
||||||
component.hidden = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideComponent(component: Hideable): void {
|
|
||||||
component.hidden = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleComponent(component: Hideable): void {
|
|
||||||
component.hidden = !component.hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
|
|
||||||
|
|
||||||
let buttonsResolve: (
|
|
||||||
value: Writable<(ToolbarItem<typeof ButtonGroup> & ButtonGroupProps)[]>
|
|
||||||
) => void;
|
|
||||||
let menusResolve: (value: Writable<ToolbarItem[]>) => void;
|
let menusResolve: (value: Writable<ToolbarItem[]>) => void;
|
||||||
|
|
||||||
export class EditorToolbar extends HTMLElement {
|
export class EditorToolbar extends HTMLElement {
|
||||||
component?: SvelteComponentDev;
|
private buttonsPromise: Promise<Writable<IterableToolbarItem[]>> = new Promise(
|
||||||
|
(resolve) => {
|
||||||
buttonsPromise: Promise<
|
buttonsResolve = resolve;
|
||||||
Writable<(ToolbarItem<typeof ButtonGroup> & ButtonGroupProps)[]>
|
}
|
||||||
> = new Promise((resolve) => {
|
);
|
||||||
buttonsResolve = resolve;
|
private menusPromise: Promise<Writable<ToolbarItem[]>> = new Promise(
|
||||||
});
|
(resolve): void => {
|
||||||
menusPromise: Promise<Writable<ToolbarItem[]>> = new Promise((resolve): void => {
|
menusResolve = resolve;
|
||||||
menusResolve = resolve;
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
globalThis.$editorToolbar = this;
|
globalThis.$editorToolbar = this;
|
||||||
|
@ -56,7 +33,7 @@ export class EditorToolbar extends HTMLElement {
|
||||||
const buttons = writable([]);
|
const buttons = writable([]);
|
||||||
const menus = writable([]);
|
const menus = writable([]);
|
||||||
|
|
||||||
this.component = new EditorToolbarSvelte({
|
new EditorToolbarSvelte({
|
||||||
target: this,
|
target: this,
|
||||||
props: {
|
props: {
|
||||||
buttons,
|
buttons,
|
||||||
|
@ -69,117 +46,89 @@ export class EditorToolbar extends HTMLElement {
|
||||||
menusResolve(menus);
|
menusResolve(menus);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateButtonGroup<T>(
|
|
||||||
update: (
|
|
||||||
component: ToolbarItem<typeof ButtonGroup> & ButtonGroupProps & T
|
|
||||||
) => void,
|
|
||||||
group: string | number
|
|
||||||
): void {
|
|
||||||
this.buttonsPromise.then((buttons) => {
|
|
||||||
buttons.update((buttonGroups) => {
|
|
||||||
const foundGroup = search(buttonGroups, group);
|
|
||||||
|
|
||||||
if (foundGroup) {
|
|
||||||
update(
|
|
||||||
foundGroup as ToolbarItem<typeof ButtonGroup> &
|
|
||||||
ButtonGroupProps &
|
|
||||||
T
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buttonGroups;
|
|
||||||
});
|
|
||||||
|
|
||||||
return buttons;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showButtonGroup(group: string | number): void {
|
|
||||||
this.updateButtonGroup<Hideable>(showComponent, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
hideButtonGroup(group: string | number): void {
|
|
||||||
this.updateButtonGroup<Hideable>(hideComponent, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleButtonGroup(group: string | number): void {
|
|
||||||
this.updateButtonGroup<Hideable>(toggleComponent, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
insertButtonGroup(newGroup: ButtonGroupProps, group: string | number = 0): void {
|
|
||||||
this.buttonsPromise.then((buttons) => {
|
|
||||||
buttons.update((buttonGroups) => {
|
|
||||||
const newButtonGroup = buttonGroup(newGroup);
|
|
||||||
return insert(buttonGroups, newButtonGroup, group);
|
|
||||||
});
|
|
||||||
|
|
||||||
return buttons;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addButtonGroup(newGroup: ButtonGroupProps, group: string | number = -1): void {
|
|
||||||
this.buttonsPromise.then((buttons) => {
|
|
||||||
buttons.update((buttonGroups) => {
|
|
||||||
const newButtonGroup = buttonGroup(newGroup);
|
|
||||||
return add(buttonGroups, newButtonGroup, group);
|
|
||||||
});
|
|
||||||
|
|
||||||
return buttons;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateButton(
|
updateButton(
|
||||||
update: (component: ToolbarItem) => void,
|
update: (component: ToolbarItem) => ToolbarItem,
|
||||||
group: string | number,
|
...identifiers: Identifier[]
|
||||||
button: string | number
|
|
||||||
): void {
|
): void {
|
||||||
this.updateButtonGroup((foundGroup) => {
|
this.buttonsPromise.then(
|
||||||
const foundButton = search(foundGroup.buttons, button);
|
(
|
||||||
|
buttons: Writable<IterableToolbarItem[]>
|
||||||
|
): Writable<IterableToolbarItem[]> => {
|
||||||
|
buttons.update(
|
||||||
|
(items: IterableToolbarItem[]): IterableToolbarItem[] =>
|
||||||
|
updateRecursive(
|
||||||
|
update,
|
||||||
|
({ items } as unknown) as ToolbarItem,
|
||||||
|
...identifiers
|
||||||
|
).items as IterableToolbarItem[]
|
||||||
|
);
|
||||||
|
|
||||||
if (foundButton) {
|
return buttons;
|
||||||
update(foundButton);
|
|
||||||
}
|
}
|
||||||
}, group);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
showButton(group: string | number, button: string | number): void {
|
showButton(...identifiers: Identifier[]): void {
|
||||||
this.updateButton(showComponent, group, button);
|
this.updateButton(showComponent, ...identifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
hideButton(group: string | number, button: string | number): void {
|
hideButton(...identifiers: Identifier[]): void {
|
||||||
this.updateButton(hideComponent, group, button);
|
this.updateButton(hideComponent, ...identifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleButton(group: string | number, button: string | number): void {
|
toggleButton(...identifiers: Identifier[]): void {
|
||||||
this.updateButton(toggleComponent, group, button);
|
this.updateButton(toggleComponent, ...identifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
insertButton(
|
insertButton(newButton: ToolbarItem, ...identifiers: Identifier[]): void {
|
||||||
newButton: ToolbarItem & Identifiable,
|
const initIdentifiers = identifiers.slice(0, -1);
|
||||||
group: string | number,
|
const lastIdentifier = identifiers[identifiers.length - 1];
|
||||||
button: string | number = 0
|
this.updateButton(
|
||||||
|
(component: ToolbarItem) =>
|
||||||
|
insert(component as IterableToolbarItem, newButton, lastIdentifier),
|
||||||
|
|
||||||
|
...initIdentifiers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addButton(newButton: ToolbarItem, ...identifiers: Identifier[]): void {
|
||||||
|
const initIdentifiers = identifiers.slice(0, -1);
|
||||||
|
const lastIdentifier = identifiers[identifiers.length - 1];
|
||||||
|
this.updateButton(
|
||||||
|
(component: ToolbarItem) =>
|
||||||
|
add(component as IterableToolbarItem, newButton, lastIdentifier),
|
||||||
|
...initIdentifiers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMenu(
|
||||||
|
update: (component: ToolbarItem) => ToolbarItem,
|
||||||
|
...identifiers: Identifier[]
|
||||||
): void {
|
): void {
|
||||||
this.updateButtonGroup((component) => {
|
this.menusPromise.then(
|
||||||
component.buttons = insert(
|
(menus: Writable<ToolbarItem[]>): Writable<ToolbarItem[]> => {
|
||||||
component.buttons as (ToolbarItem & Identifiable)[],
|
menus.update(
|
||||||
newButton,
|
(items: ToolbarItem[]): ToolbarItem[] =>
|
||||||
button
|
updateRecursive(
|
||||||
);
|
update,
|
||||||
}, group);
|
({ items } as unknown) as ToolbarItem,
|
||||||
|
...identifiers
|
||||||
|
).items as ToolbarItem[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return menus;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addButton(
|
addMenu(newMenu: ToolbarItem, ...identifiers: Identifier[]): void {
|
||||||
newButton: ToolbarItem & Identifiable,
|
const initIdentifiers = identifiers.slice(0, -1);
|
||||||
group: string | number,
|
const lastIdentifier = identifiers[identifiers.length - 1];
|
||||||
button: string | number = -1
|
this.updateMenu(
|
||||||
): void {
|
(component: ToolbarItem) =>
|
||||||
this.updateButtonGroup((component) => {
|
add(component as IterableToolbarItem, newMenu, lastIdentifier),
|
||||||
component.buttons = add(
|
...initIdentifiers
|
||||||
component.buttons as (ToolbarItem & Identifiable)[],
|
);
|
||||||
newButton,
|
|
||||||
button
|
|
||||||
);
|
|
||||||
}, group);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,15 @@
|
||||||
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { SvelteComponentDev } from "svelte/internal";
|
||||||
|
|
||||||
interface ToolbarItem<T extends typeof SvelteComponentDev = typeof SvelteComponentDev>
|
export interface ToolbarItem<
|
||||||
extends DynamicSvelteComponent<T> {
|
T extends typeof SvelteComponentDev = typeof SvelteComponentDev
|
||||||
|
> extends DynamicSvelteComponent<T> {
|
||||||
id?: string;
|
id?: string;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IterableToolbarItem<
|
||||||
|
T extends typeof SvelteComponentDev = typeof SvelteComponentDev
|
||||||
|
> extends ToolbarItem<T> {
|
||||||
|
items: ToolbarItem[];
|
||||||
|
}
|
|
@ -1,24 +1,35 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { default as RawButton } from "editor-toolbar/RawButton.svelte";
|
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
||||||
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 {
|
||||||
import { default as DropdownItem } from "editor-toolbar/DropdownItem.svelte";
|
buttonGroup,
|
||||||
import { default as ButtonDropdown } from "editor-toolbar/DropdownMenu.svelte";
|
rawButton,
|
||||||
import { default as WithDropdownMenu } from "editor-toolbar/WithDropdownMenu.svelte";
|
labelButton,
|
||||||
|
iconButton,
|
||||||
|
commandIconButton,
|
||||||
|
selectButton,
|
||||||
|
dropdownMenu,
|
||||||
|
dropdownItem,
|
||||||
|
buttonDropdown,
|
||||||
|
withDropdownMenu,
|
||||||
|
withLabel,
|
||||||
|
} from "editor-toolbar/dynamicComponents";
|
||||||
|
|
||||||
export const editorToolbar = {
|
export const editorToolbar: Record<
|
||||||
RawButton,
|
string,
|
||||||
LabelButton,
|
(props: Record<string, unknown>) => DynamicSvelteComponent
|
||||||
IconButton,
|
> = {
|
||||||
CommandIconButton,
|
buttonGroup,
|
||||||
SelectButton,
|
rawButton,
|
||||||
DropdownMenu,
|
labelButton,
|
||||||
DropdownItem,
|
iconButton,
|
||||||
ButtonDropdown,
|
commandIconButton,
|
||||||
WithDropdownMenu,
|
selectButton,
|
||||||
|
|
||||||
|
dropdownMenu,
|
||||||
|
dropdownItem,
|
||||||
|
buttonDropdown,
|
||||||
|
withDropdownMenu,
|
||||||
|
withLabel,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type WithShortcut from "editor-toolbar/WithShortcut.svelte";
|
import type { ToolbarItem } from "editor-toolbar/types";
|
||||||
import type { WithShortcutProps } from "editor-toolbar/WithShortcut";
|
|
||||||
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
|
||||||
|
|
||||||
import * as tr from "lib/i18n";
|
import * as tr from "lib/i18n";
|
||||||
import { iconButton, withShortcut } from "editor-toolbar/dynamicComponents";
|
import { iconButton, withShortcut } from "editor-toolbar/dynamicComponents";
|
||||||
|
@ -40,8 +38,7 @@ function onCloze(event: KeyboardEvent | MouseEvent): void {
|
||||||
wrap(`{{c${highestCloze}::`, "}}");
|
wrap(`{{c${highestCloze}::`, "}}");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClozeButton(): DynamicSvelteComponent<typeof WithShortcut> &
|
export function getClozeButton(): ToolbarItem {
|
||||||
WithShortcutProps {
|
|
||||||
return withShortcut({
|
return withShortcut({
|
||||||
id: "cloze",
|
id: "cloze",
|
||||||
shortcut: "Control+Shift+KeyC",
|
shortcut: "Control+Shift+KeyC",
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
import type { IterableToolbarItem } from "editor-toolbar/types";
|
||||||
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
|
||||||
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
iconButton,
|
iconButton,
|
||||||
|
@ -29,8 +27,7 @@ function wrapWithForecolor(color: string): void {
|
||||||
document.execCommand("forecolor", false, color);
|
document.execCommand("forecolor", false, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColorGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getColorGroup(): IterableToolbarItem {
|
||||||
ButtonGroupProps {
|
|
||||||
const forecolorButton = withShortcut({
|
const forecolorButton = withShortcut({
|
||||||
shortcut: "F7",
|
shortcut: "F7",
|
||||||
button: iconButton({
|
button: iconButton({
|
||||||
|
@ -52,6 +49,6 @@ export function getColorGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
|
|
||||||
return buttonGroup({
|
return buttonGroup({
|
||||||
id: "color",
|
id: "color",
|
||||||
buttons: [forecolorButton, colorpickerButton],
|
items: [forecolorButton, colorpickerButton],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,3 +59,7 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-basis-100 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
import type { IterableToolbarItem } from "editor-toolbar/types";
|
||||||
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
|
||||||
import type ButtonDropdown from "editor-toolbar/ButtonDropdown.svelte";
|
|
||||||
import type { ButtonDropdownProps } from "editor-toolbar/ButtonDropdown";
|
|
||||||
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
|
||||||
|
|
||||||
import type { EditingArea } from "./editingArea";
|
import type { EditingArea } from "./editingArea";
|
||||||
|
|
||||||
import * as tr from "lib/i18n";
|
import * as tr from "lib/i18n";
|
||||||
|
@ -45,8 +40,7 @@ const indentListItem = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getFormatBlockMenus(): (DynamicSvelteComponent<typeof ButtonDropdown> &
|
export function getFormatBlockMenus(): IterableToolbarItem[] {
|
||||||
ButtonDropdownProps)[] {
|
|
||||||
const justifyLeftButton = commandIconButton({
|
const justifyLeftButton = commandIconButton({
|
||||||
icon: justifyLeftIcon,
|
icon: justifyLeftIcon,
|
||||||
command: "justifyLeft",
|
command: "justifyLeft",
|
||||||
|
@ -73,7 +67,7 @@ export function getFormatBlockMenus(): (DynamicSvelteComponent<typeof ButtonDrop
|
||||||
|
|
||||||
const justifyGroup = buttonGroup({
|
const justifyGroup = buttonGroup({
|
||||||
id: "justify",
|
id: "justify",
|
||||||
buttons: [
|
items: [
|
||||||
justifyLeftButton,
|
justifyLeftButton,
|
||||||
justifyCenterButton,
|
justifyCenterButton,
|
||||||
justifyRightButton,
|
justifyRightButton,
|
||||||
|
@ -95,19 +89,18 @@ export function getFormatBlockMenus(): (DynamicSvelteComponent<typeof ButtonDrop
|
||||||
|
|
||||||
const indentationGroup = buttonGroup({
|
const indentationGroup = buttonGroup({
|
||||||
id: "indentation",
|
id: "indentation",
|
||||||
buttons: [outdentButton, indentButton],
|
items: [outdentButton, indentButton],
|
||||||
});
|
});
|
||||||
|
|
||||||
const formattingOptions = buttonDropdown({
|
const formattingOptions = buttonDropdown({
|
||||||
id: "listFormatting",
|
id: "listFormatting",
|
||||||
buttons: [justifyGroup, indentationGroup],
|
items: [justifyGroup, indentationGroup],
|
||||||
});
|
});
|
||||||
|
|
||||||
return [formattingOptions];
|
return [formattingOptions];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFormatBlockGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getFormatBlockGroup(): IterableToolbarItem {
|
||||||
ButtonGroupProps {
|
|
||||||
const ulButton = commandIconButton({
|
const ulButton = commandIconButton({
|
||||||
icon: ulIcon,
|
icon: ulIcon,
|
||||||
command: "insertUnorderedList",
|
command: "insertUnorderedList",
|
||||||
|
@ -131,6 +124,6 @@ export function getFormatBlockGroup(): DynamicSvelteComponent<typeof ButtonGroup
|
||||||
|
|
||||||
return buttonGroup({
|
return buttonGroup({
|
||||||
id: "blockFormatting",
|
id: "blockFormatting",
|
||||||
buttons: [ulButton, olButton, listFormatting],
|
items: [ulButton, olButton, listFormatting],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
import type { IterableToolbarItem } from "editor-toolbar/types";
|
||||||
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
|
||||||
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
|
||||||
|
|
||||||
import * as tr from "lib/i18n";
|
import * as tr from "lib/i18n";
|
||||||
import {
|
import {
|
||||||
|
@ -19,8 +17,7 @@ import superscriptIcon from "./format-superscript.svg";
|
||||||
import subscriptIcon from "./format-subscript.svg";
|
import subscriptIcon from "./format-subscript.svg";
|
||||||
import eraserIcon from "./eraser.svg";
|
import eraserIcon from "./eraser.svg";
|
||||||
|
|
||||||
export function getFormatInlineGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getFormatInlineGroup(): IterableToolbarItem {
|
||||||
ButtonGroupProps {
|
|
||||||
const boldButton = withShortcut({
|
const boldButton = withShortcut({
|
||||||
shortcut: "Control+KeyB",
|
shortcut: "Control+KeyB",
|
||||||
button: commandIconButton({
|
button: commandIconButton({
|
||||||
|
@ -79,7 +76,7 @@ export function getFormatInlineGroup(): DynamicSvelteComponent<typeof ButtonGrou
|
||||||
|
|
||||||
return buttonGroup({
|
return buttonGroup({
|
||||||
id: "inlineFormatting",
|
id: "inlineFormatting",
|
||||||
buttons: [
|
items: [
|
||||||
boldButton,
|
boldButton,
|
||||||
italicButton,
|
italicButton,
|
||||||
underlineButton,
|
underlineButton,
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
import type { IterableToolbarItem } from "editor-toolbar/types";
|
||||||
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
|
||||||
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
|
||||||
|
|
||||||
import { bridgeCommand } from "lib/bridgecommand";
|
import { bridgeCommand } from "lib/bridgecommand";
|
||||||
import * as tr from "lib/i18n";
|
import * as tr from "lib/i18n";
|
||||||
|
@ -12,8 +10,7 @@ import {
|
||||||
withShortcut,
|
withShortcut,
|
||||||
} from "editor-toolbar/dynamicComponents";
|
} from "editor-toolbar/dynamicComponents";
|
||||||
|
|
||||||
export function getNotetypeGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getNotetypeGroup(): IterableToolbarItem {
|
||||||
ButtonGroupProps {
|
|
||||||
const fieldsButton = labelButton({
|
const fieldsButton = labelButton({
|
||||||
onClick: () => bridgeCommand("fields"),
|
onClick: () => bridgeCommand("fields"),
|
||||||
disables: false,
|
disables: false,
|
||||||
|
@ -33,6 +30,6 @@ export function getNotetypeGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
|
|
||||||
return buttonGroup({
|
return buttonGroup({
|
||||||
id: "notetype",
|
id: "notetype",
|
||||||
buttons: [fieldsButton, cardsButton],
|
items: [fieldsButton, cardsButton],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type DropdownMenu from "editor-toolbar/DropdownMenu.svelte";
|
import type { IterableToolbarItem } from "editor-toolbar/types";
|
||||||
import type { DropdownMenuProps } from "editor-toolbar/DropdownMenu";
|
|
||||||
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
|
||||||
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
|
||||||
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
|
||||||
|
|
||||||
import { bridgeCommand } from "lib/bridgecommand";
|
import { bridgeCommand } from "lib/bridgecommand";
|
||||||
import {
|
import {
|
||||||
|
@ -40,8 +36,7 @@ function onHtmlEdit(): void {
|
||||||
|
|
||||||
const mathjaxMenuId = "mathjaxMenu";
|
const mathjaxMenuId = "mathjaxMenu";
|
||||||
|
|
||||||
export function getTemplateGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getTemplateGroup(): IterableToolbarItem {
|
||||||
ButtonGroupProps {
|
|
||||||
const attachmentButton = withShortcut({
|
const attachmentButton = withShortcut({
|
||||||
shortcut: "F3",
|
shortcut: "F3",
|
||||||
button: iconButton({
|
button: iconButton({
|
||||||
|
@ -80,7 +75,7 @@ export function getTemplateGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
|
|
||||||
return buttonGroup({
|
return buttonGroup({
|
||||||
id: "template",
|
id: "template",
|
||||||
buttons: [
|
items: [
|
||||||
attachmentButton,
|
attachmentButton,
|
||||||
recordButton,
|
recordButton,
|
||||||
getClozeButton(),
|
getClozeButton(),
|
||||||
|
@ -90,8 +85,7 @@ export function getTemplateGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTemplateMenus(): (DynamicSvelteComponent<typeof DropdownMenu> &
|
export function getTemplateMenus(): IterableToolbarItem[] {
|
||||||
DropdownMenuProps)[] {
|
|
||||||
const mathjaxMenuItems = [
|
const mathjaxMenuItems = [
|
||||||
withShortcut({
|
withShortcut({
|
||||||
shortcut: "Control+KeyM, KeyM",
|
shortcut: "Control+KeyM, KeyM",
|
||||||
|
@ -142,7 +136,7 @@ export function getTemplateMenus(): (DynamicSvelteComponent<typeof DropdownMenu>
|
||||||
|
|
||||||
const mathjaxMenu = dropdownMenu({
|
const mathjaxMenu = dropdownMenu({
|
||||||
id: mathjaxMenuId,
|
id: mathjaxMenuId,
|
||||||
menuItems: [...mathjaxMenuItems, ...latexMenuItems],
|
items: [...mathjaxMenuItems, ...latexMenuItems],
|
||||||
});
|
});
|
||||||
|
|
||||||
return [mathjaxMenu];
|
return [mathjaxMenu];
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
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 { getNotetypeGroup } from "./notetype";
|
||||||
import { getFormatInlineGroup } from "./formatInline";
|
import { getFormatInlineGroup } from "./formatInline";
|
||||||
import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock";
|
import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock";
|
||||||
|
@ -14,32 +9,18 @@ import { getTemplateGroup, getTemplateMenus } from "./template";
|
||||||
export function initToolbar(i18n: Promise<void>): void {
|
export function initToolbar(i18n: Promise<void>): void {
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
i18n.then(() => {
|
i18n.then(() => {
|
||||||
globalThis.$editorToolbar.buttonsPromise.then(
|
const buttons = [
|
||||||
(
|
getNotetypeGroup(),
|
||||||
buttons: Writable<
|
getFormatInlineGroup(),
|
||||||
(ToolbarItem<typeof ButtonGroup> & ButtonGroupProps)[]
|
getFormatBlockGroup(),
|
||||||
>
|
getColorGroup(),
|
||||||
): Writable<(ToolbarItem<typeof ButtonGroup> & ButtonGroupProps)[]> => {
|
getTemplateGroup(),
|
||||||
buttons.update(() => [
|
];
|
||||||
getNotetypeGroup(),
|
|
||||||
getFormatInlineGroup(),
|
|
||||||
getFormatBlockGroup(),
|
|
||||||
getColorGroup(),
|
|
||||||
getTemplateGroup(),
|
|
||||||
]);
|
|
||||||
return buttons;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
globalThis.$editorToolbar.menusPromise.then(
|
const menus = [...getFormatBlockMenus(), ...getTemplateMenus()];
|
||||||
(menus: Writable<ToolbarItem[]>): Writable<ToolbarItem[]> => {
|
|
||||||
menus.update(() => [
|
globalThis.$editorToolbar.updateButton(() => ({ items: buttons }));
|
||||||
...getFormatBlockMenus(),
|
globalThis.$editorToolbar.updateMenu(() => ({ items: menus }));
|
||||||
...getTemplateMenus(),
|
|
||||||
]);
|
|
||||||
return menus;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ $link-hover-decoration: none;
|
||||||
@import "ts/sass/bootstrap/bootstrap-reboot";
|
@import "ts/sass/bootstrap/bootstrap-reboot";
|
||||||
@import "ts/sass/bootstrap/bootstrap-utilities";
|
@import "ts/sass/bootstrap/bootstrap-utilities";
|
||||||
|
|
||||||
body, html {
|
body,
|
||||||
|
html {
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue