Refactor text attribute button components (#3012)

* Factor out common code in bold/italic/underline/sub/sup buttons

* Update exclusiveNames' states on click

* Update exclusiveNames' states on keyboard shortcut
This commit is contained in:
Lucas Scharenbroch 2024-02-18 00:09:05 -06:00 committed by GitHub
parent f09fbea3b9
commit 97dcb776de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 133 additions and 346 deletions

View file

@ -4,20 +4,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "@tslib/ftl";
import { getPlatformString } from "@tslib/shortcuts";
import { removeStyleProperties } from "@tslib/styling";
import { singleCallback } from "@tslib/typing";
import { onMount } from "svelte";
import IconButton from "../../components/IconButton.svelte";
import Shortcut from "../../components/Shortcut.svelte";
import WithState from "../../components/WithState.svelte";
import type { MatchType } from "../../domlib/surround";
import { surrounder } from "../rich-text-input";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
import { boldIcon } from "./icons";
const surroundElement = document.createElement("b");
import TextAttributeButton from "./TextAttributeButton.svelte";
function matcher(element: HTMLElement | SVGElement, match: MatchType): void {
if (element.tagName === "B" || element.tagName === "STRONG") {
@ -36,62 +27,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
});
}
}
const key = "bold";
const format = {
surroundElement,
matcher,
};
const namedFormat = {
key,
name: tr.editingBoldText(),
show: true,
active: true,
};
const { removeFormats } = editorToolbarContext.get();
removeFormats.update((formats) => [...formats, namedFormat]);
async function updateStateFromActiveInput(): Promise<boolean> {
return disabled ? false : surrounder.isSurrounded(key);
}
function makeBold(): void {
surrounder.surround(key);
}
const keyCombination = "Control+B";
let disabled: boolean;
onMount(() =>
singleCallback(
surrounder.active.subscribe((value) => (disabled = !value)),
surrounder.registerFormat(key, format),
),
);
</script>
<WithState {key} update={updateStateFromActiveInput} let:state={active} let:updateState>
<IconButton
tooltip="{tr.editingBoldText()} ({getPlatformString(keyCombination)})"
{active}
{disabled}
on:click={(event) => {
makeBold();
updateState(event);
}}
>
{@html boldIcon}
</IconButton>
<Shortcut
{keyCombination}
on:action={(event) => {
makeBold();
updateState(event);
}}
/>
</WithState>
<TextAttributeButton
tagName="b"
{matcher}
key="bold"
tooltip={tr.editingBoldText()}
keyCombination="Control+B"
>
{@html boldIcon}
</TextAttributeButton>

View file

@ -4,20 +4,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "@tslib/ftl";
import { getPlatformString } from "@tslib/shortcuts";
import { removeStyleProperties } from "@tslib/styling";
import { singleCallback } from "@tslib/typing";
import { onMount } from "svelte";
import IconButton from "../../components/IconButton.svelte";
import Shortcut from "../../components/Shortcut.svelte";
import WithState from "../../components/WithState.svelte";
import type { MatchType } from "../../domlib/surround";
import { surrounder } from "../rich-text-input";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
import { italicIcon } from "./icons";
const surroundElement = document.createElement("i");
import TextAttributeButton from "./TextAttributeButton.svelte";
function matcher(element: HTMLElement | SVGElement, match: MatchType): void {
if (element.tagName === "I" || element.tagName === "EM") {
@ -35,62 +26,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
});
}
}
const key = "italic";
const format = {
surroundElement,
matcher,
};
const namedFormat = {
key,
name: tr.editingItalicText(),
show: true,
active: true,
};
const { removeFormats } = editorToolbarContext.get();
removeFormats.update((formats) => [...formats, namedFormat]);
async function updateStateFromActiveInput(): Promise<boolean> {
return disabled ? false : surrounder.isSurrounded(key);
}
function makeItalic(): void {
surrounder.surround(key);
}
const keyCombination = "Control+I";
let disabled: boolean;
onMount(() =>
singleCallback(
surrounder.active.subscribe((value) => (disabled = !value)),
surrounder.registerFormat(key, format),
),
);
</script>
<WithState {key} update={updateStateFromActiveInput} let:state={active} let:updateState>
<IconButton
tooltip="{tr.editingItalicText()} ({getPlatformString(keyCombination)})"
{active}
{disabled}
on:click={(event) => {
makeItalic();
updateState(event);
}}
>
{@html italicIcon}
</IconButton>
<Shortcut
{keyCombination}
on:action={(event) => {
makeItalic();
updateState(event);
}}
/>
</WithState>
<TextAttributeButton
tagName="i"
{matcher}
key="italic"
tooltip={tr.editingItalicText()}
keyCombination="Control+I"
>
{@html italicIcon}
</TextAttributeButton>

View file

@ -4,21 +4,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "@tslib/ftl";
import { getPlatformString } from "@tslib/shortcuts";
import { removeStyleProperties } from "@tslib/styling";
import { singleCallback } from "@tslib/typing";
import { onMount } from "svelte";
import IconButton from "../../components/IconButton.svelte";
import Shortcut from "../../components/Shortcut.svelte";
import WithState from "../../components/WithState.svelte";
import { updateStateByKey } from "../../components/WithState.svelte";
import type { MatchType } from "../../domlib/surround";
import { surrounder } from "../rich-text-input";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
import { subscriptIcon } from "./icons";
const surroundElement = document.createElement("sub");
import TextAttributeButton from "./TextAttributeButton.svelte";
export function matcher(element: HTMLElement | SVGElement, match: MatchType): void {
if (element.tagName === "SUB") {
@ -36,64 +26,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
});
}
}
const key = "subscript";
const format = {
surroundElement,
matcher,
};
const namedFormat = {
key,
name: tr.editingSubscript(),
show: true,
active: true,
};
const { removeFormats } = editorToolbarContext.get();
removeFormats.update((formats) => [...formats, namedFormat]);
async function updateStateFromActiveInput(): Promise<boolean> {
return disabled ? false : surrounder.isSurrounded(key);
}
function makeSub(): void {
surrounder.surround(key, ["superscript"]);
}
const keyCombination = "Control+Shift+=";
let disabled: boolean;
onMount(() =>
singleCallback(
surrounder.active.subscribe((value) => (disabled = !value)),
surrounder.registerFormat(key, format),
),
);
</script>
<WithState {key} update={updateStateFromActiveInput} let:state={active} let:updateState>
<IconButton
tooltip="{tr.editingSubscript()} ({getPlatformString(keyCombination)})"
{active}
{disabled}
on:click={(event) => {
makeSub();
updateState(event);
updateStateByKey("superscript", event);
}}
>
{@html subscriptIcon}
</IconButton>
<Shortcut
{keyCombination}
on:action={(event) => {
makeSub();
updateState(event);
updateStateByKey("superscript", event);
}}
/>
</WithState>
<TextAttributeButton
tagName="sub"
{matcher}
key="subscript"
tooltip={tr.editingSubscript()}
keyCombination="Control+Shift+="
exclusiveNames={["superscript"]}
>
{@html subscriptIcon}
</TextAttributeButton>

View file

@ -4,21 +4,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "@tslib/ftl";
import { getPlatformString } from "@tslib/shortcuts";
import { removeStyleProperties } from "@tslib/styling";
import { singleCallback } from "@tslib/typing";
import { onMount } from "svelte";
import IconButton from "../../components/IconButton.svelte";
import Shortcut from "../../components/Shortcut.svelte";
import WithState from "../../components/WithState.svelte";
import { updateStateByKey } from "../../components/WithState.svelte";
import type { MatchType } from "../../domlib/surround";
import { surrounder } from "../rich-text-input";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
import { superscriptIcon } from "./icons";
const surroundElement = document.createElement("sup");
import TextAttributeButton from "./TextAttributeButton.svelte";
export function matcher(element: HTMLElement | SVGElement, match: MatchType): void {
if (element.tagName === "SUP") {
@ -36,64 +26,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
});
}
}
const key = "superscript";
export const format = {
surroundElement,
matcher,
};
const namedFormat = {
key,
name: tr.editingSuperscript(),
show: true,
active: true,
};
const { removeFormats } = editorToolbarContext.get();
removeFormats.update((formats) => [...formats, namedFormat]);
async function updateStateFromActiveInput(): Promise<boolean> {
return disabled ? false : surrounder.isSurrounded(key);
}
function makeSuper(): void {
surrounder.surround(key, ["subscript"]);
}
const keyCombination = "Control+=";
let disabled: boolean;
onMount(() =>
singleCallback(
surrounder.active.subscribe((value) => (disabled = !value)),
surrounder.registerFormat(key, format),
),
);
</script>
<WithState {key} update={updateStateFromActiveInput} let:state={active} let:updateState>
<IconButton
tooltip="{tr.editingSuperscript()} ({getPlatformString(keyCombination)})"
{active}
{disabled}
on:click={(event) => {
makeSuper();
updateState(event);
updateStateByKey("subscript", event);
}}
>
{@html superscriptIcon}
</IconButton>
<Shortcut
{keyCombination}
on:action={(event) => {
makeSuper();
updateState(event);
updateStateByKey("subscript", event);
}}
/>
</WithState>
<TextAttributeButton
tagName="sup"
{matcher}
key="superscript"
tooltip={tr.editingSuperscript()}
keyCombination="Control+="
exclusiveNames={["subscript"]}
>
{@html superscriptIcon}
</TextAttributeButton>

View file

@ -0,0 +1,81 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { getPlatformString } from "@tslib/shortcuts";
import { singleCallback } from "@tslib/typing";
import { onMount } from "svelte";
import IconButton from "../../components/IconButton.svelte";
import Shortcut from "../../components/Shortcut.svelte";
import WithState, { updateStateByKey } from "../../components/WithState.svelte";
import type { MatchType } from "../../domlib/surround";
import { surrounder } from "../rich-text-input";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
export let tagName;
export let matcher: (element: HTMLElement | SVGElement, match: MatchType) => void;
export let key: string;
export let tooltip: string;
export let keyCombination: string;
export let exclusiveNames: string[] = [];
const surroundElement = document.createElement(tagName);
const format = {
surroundElement,
matcher,
};
const namedFormat = {
key,
name: tooltip,
show: true,
active: true,
};
const { removeFormats } = editorToolbarContext.get();
removeFormats.update((formats) => [...formats, namedFormat]);
async function updateStateFromActiveInput(): Promise<boolean> {
return disabled ? false : surrounder.isSurrounded(key);
}
function applyAttribute(): void {
surrounder.surround(key, exclusiveNames);
}
let disabled: boolean;
onMount(() =>
singleCallback(
surrounder.active.subscribe((value) => (disabled = !value)),
surrounder.registerFormat(key, format),
),
);
</script>
<WithState {key} update={updateStateFromActiveInput} let:state={active} let:updateState>
<IconButton
tooltip="{tooltip} ({getPlatformString(keyCombination)})"
{active}
{disabled}
on:click={(event) => {
applyAttribute();
updateState(event);
exclusiveNames.map((name) => updateStateByKey(name, event));
}}
>
<slot />
</IconButton>
<Shortcut
{keyCombination}
on:action={(event) => {
applyAttribute();
updateState(event);
exclusiveNames.map((name) => updateStateByKey(name, event));
}}
/>
</WithState>

View file

@ -4,86 +4,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "@tslib/ftl";
import { getPlatformString } from "@tslib/shortcuts";
import { singleCallback } from "@tslib/typing";
import { onMount } from "svelte";
import IconButton from "../../components/IconButton.svelte";
import Shortcut from "../../components/Shortcut.svelte";
import WithState from "../../components/WithState.svelte";
import type { MatchType } from "../../domlib/surround";
import { surrounder } from "../rich-text-input";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
import { underlineIcon } from "./icons";
const surroundElement = document.createElement("u");
import TextAttributeButton from "./TextAttributeButton.svelte";
function matcher(element: HTMLElement | SVGElement, match: MatchType): void {
if (element.tagName === "U") {
return match.remove();
}
}
function clearer() {
return false;
}
const key = "underline";
const format = {
surroundElement,
matcher,
clearer,
};
const namedFormat = {
key,
name: tr.editingUnderlineText(),
show: true,
active: true,
};
const { removeFormats } = editorToolbarContext.get();
removeFormats.update((formats) => [...formats, namedFormat]);
async function updateStateFromActiveInput(): Promise<boolean> {
return disabled ? false : surrounder.isSurrounded(key);
}
function makeUnderline(): void {
surrounder.surround(key);
}
const keyCombination = "Control+U";
let disabled: boolean;
onMount(() =>
singleCallback(
surrounder.active.subscribe((value) => (disabled = !value)),
surrounder.registerFormat(key, format),
),
);
</script>
<WithState {key} update={updateStateFromActiveInput} let:state={active} let:updateState>
<IconButton
tooltip="{tr.editingUnderlineText()} ({getPlatformString(keyCombination)})"
{active}
{disabled}
on:click={(event) => {
makeUnderline();
updateState(event);
}}
>
{@html underlineIcon}
</IconButton>
<Shortcut
{keyCombination}
on:action={(event) => {
makeUnderline();
updateState(event);
}}
/>
</WithState>
<TextAttributeButton
tagName="u"
{matcher}
key="underline"
tooltip={tr.editingUnderlineText()}
keyCombination="Control+U"
>
{@html underlineIcon}
</TextAttributeButton>