+
(showFloating = false)}
+ let:asReference
+>
+
(dropdown = createDropdown(event.detail.span))}
on:click={() => {
if (modified) {
- dropdown.toggle();
+ showFloating = !showFloating;
}
}}
>
{@html revertIcon}
-
-
- revert()}
- >
- {tr.deckConfigRevertButtonTooltip()}{@html revertIcon}
-
-
-
+
+
+ revert()}
+ >
+ {tr.deckConfigRevertButtonTooltip()}{@html revertIcon}
+
+
+
diff --git a/ts/editable/mathjax-element.ts b/ts/editable/mathjax-element.ts
index 1c1480e50..62d3ceb16 100644
--- a/ts/editable/mathjax-element.ts
+++ b/ts/editable/mathjax-element.ts
@@ -89,7 +89,7 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
break;
case "data-mathjax":
- if (!newValue) {
+ if (typeof newValue !== "string") {
return;
}
this.component?.$set({ mathjax: newValue });
diff --git a/ts/editor/CodeMirror.svelte b/ts/editor/CodeMirror.svelte
index 12d3e73b2..d482fd5ee 100644
--- a/ts/editor/CodeMirror.svelte
+++ b/ts/editor/CodeMirror.svelte
@@ -65,8 +65,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const editor = await editorPromise;
setupCodeMirror(editor, code);
editor.on("change", () => dispatch("change", editor.getValue()));
- editor.on("focus", () => dispatch("focus"));
- editor.on("blur", () => dispatch("blur"));
+ editor.on("focus", (codeMirror, event) =>
+ dispatch("focus", { codeMirror, event }),
+ );
+ editor.on("blur", (codeMirror, event) =>
+ dispatch("blur", { codeMirror, event }),
+ );
+ editor.on("keydown", (codeMirror, event) => {
+ if (event.code === "Tab") {
+ dispatch("tab", { codeMirror, event });
+ }
+ });
});
@@ -92,6 +101,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
:global(.CodeMirror) {
height: auto;
}
+
:global(.CodeMirror-wrap pre) {
word-break: break-word;
}
diff --git a/ts/editor/EditorField.svelte b/ts/editor/EditorField.svelte
index 9f903c824..f87104cd4 100644
--- a/ts/editor/EditorField.svelte
+++ b/ts/editor/EditorField.svelte
@@ -85,18 +85,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
onDestroy(() => api?.destroy());
-
editingArea.focus?.()}
- on:mouseenter
- on:mouseleave
->
-
+
-
+
+
{/if}
-
-
+
+
diff --git a/ts/editor/FieldsEditor.svelte b/ts/editor/FieldsEditor.svelte
index f38dd6785..2a7222088 100644
--- a/ts/editor/FieldsEditor.svelte
+++ b/ts/editor/FieldsEditor.svelte
@@ -12,12 +12,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
display: flex;
flex-direction: column;
flex-grow: 1;
- overflow-x: hidden;
-
- /* replace with "gap: 5px" once it's available
- - required: Chromium 84 (Qt6 only) and iOS 14.1 */
- > :global(*) {
- margin: 5px 0;
- }
}
diff --git a/ts/editor/HandleBackground.svelte b/ts/editor/HandleBackground.svelte
index 772b88640..4ba3acce6 100644
--- a/ts/editor/HandleBackground.svelte
+++ b/ts/editor/HandleBackground.svelte
@@ -3,29 +3,22 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
diff --git a/ts/editor/HandleControl.svelte b/ts/editor/HandleControl.svelte
index b7f9f08fa..152fc9572 100644
--- a/ts/editor/HandleControl.svelte
+++ b/ts/editor/HandleControl.svelte
@@ -23,7 +23,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
diff --git a/ts/editor/LabelContainer.svelte b/ts/editor/LabelContainer.svelte
index ad546461a..df28d2d5d 100644
--- a/ts/editor/LabelContainer.svelte
+++ b/ts/editor/LabelContainer.svelte
@@ -46,7 +46,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
to cover field borders on scroll */
left: -1px;
right: -1px;
- z-index: 3;
+ z-index: 10;
background: var(--label-color);
.clickable {
diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte
index 7aaa1d01f..03ff715cd 100644
--- a/ts/editor/NoteEditor.svelte
+++ b/ts/editor/NoteEditor.svelte
@@ -434,7 +434,10 @@ the AddCards dialog) should be implemented in the user of this component.
-
+
{
@@ -452,7 +455,10 @@ the AddCards dialog) should be implemented in the user of this component.
-
+
@@ -82,80 +86,95 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-
- createDropdown(event.detail.button)}
- >
- {@html listOptionsIcon}
-
+ (showFloating = false)}
+ let:asReference
+ >
+
+ (showFloating = !showFloating)}
+ >
+ {@html listOptionsIcon}
+
+
-
-
- {@html justifyLeftIcon}
+
+
+
+ {@html justifyLeftIcon}
- {@html justifyCenterIcon}
+ {@html justifyCenterIcon}
- {@html justifyRightIcon}
+ {@html justifyRightIcon}
- {@html justifyFullIcon}
-
+ {@html justifyFullIcon}
+
-
-
- {@html outdentIcon}
-
+
+
+ {@html outdentIcon}
+
-
+
-
- {@html indentIcon}
-
+
+ {@html indentIcon}
+
-
-
-
-
+
+
+
+
+
+
+
diff --git a/ts/editor/editor-toolbar/HighlightColorButton.svelte b/ts/editor/editor-toolbar/HighlightColorButton.svelte
index 4644fcece..cb052fdeb 100644
--- a/ts/editor/editor-toolbar/HighlightColorButton.svelte
+++ b/ts/editor/editor-toolbar/HighlightColorButton.svelte
@@ -11,10 +11,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "../../lib/ftl";
import { removeStyleProperties } from "../../lib/styling";
import { singleCallback } from "../../lib/typing";
+ import { chevronDown } from "../icons";
import { surrounder } from "../rich-text-input";
import ColorPicker from "./ColorPicker.svelte";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
- import { arrowIcon, highlightColorIcon } from "./icons";
+ import { highlightColorIcon } from "./icons";
import WithColorHelper from "./WithColorHelper.svelte";
export let color: string;
@@ -121,9 +122,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
tooltip={tr.editingChangeColor()}
{disabled}
widthMultiplier={0.5}
+ iconSize={120}
--border-right-radius="5px"
>
- {@html arrowIcon}
+ {@html chevronDown}
{
color = setColor(event);
diff --git a/ts/editor/editor-toolbar/LatexButton.svelte b/ts/editor/editor-toolbar/LatexButton.svelte
index 664244f59..5911c2d0e 100644
--- a/ts/editor/editor-toolbar/LatexButton.svelte
+++ b/ts/editor/editor-toolbar/LatexButton.svelte
@@ -3,8 +3,6 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
-
-
-
+ (showFloating = false)}
+ let:asReference
+>
+
+ (showFloating = !showFloating)}>
{@html functionIcon}
@@ -93,25 +87,29 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{#each dropdownItems as [callback, keyCombination, label]}
- {label}
+ {label}
{getPlatformString(keyCombination)}
-
{/each}
+
{tr.editingToggleMathjaxRendering()}
-
diff --git a/ts/editor/editor-toolbar/RemoveFormatButton.svelte b/ts/editor/editor-toolbar/RemoveFormatButton.svelte
index 6d8ece1ec..5a4ff3d53 100644
--- a/ts/editor/editor-toolbar/RemoveFormatButton.svelte
+++ b/ts/editor/editor-toolbar/RemoveFormatButton.svelte
@@ -7,21 +7,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import CheckBox from "../../components/CheckBox.svelte";
import DropdownItem from "../../components/DropdownItem.svelte";
- import DropdownMenu from "../../components/DropdownMenu.svelte";
- import { withButton } from "../../components/helpers";
import IconButton from "../../components/IconButton.svelte";
+ import Popover from "../../components/Popover.svelte";
import Shortcut from "../../components/Shortcut.svelte";
- import WithDropdown from "../../components/WithDropdown.svelte";
+ import WithFloating from "../../components/WithFloating.svelte";
import type { MatchType } from "../../domlib/surround";
import * as tr from "../../lib/ftl";
import { altPressed, shiftPressed } from "../../lib/keys";
import { getPlatformString } from "../../lib/shortcuts";
import { singleCallback } from "../../lib/typing";
+ import { chevronDown } from "../icons";
import { surrounder } from "../rich-text-input";
import type { RemoveFormat } from "./EditorToolbar.svelte";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
import { eraserIcon } from "./icons";
- import { arrowIcon } from "./icons";
const { removeFormats } = editorToolbarContext.get();
@@ -62,6 +61,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const keyCombination = "Control+R";
let disabled: boolean;
+ let showFloating = false;
onMount(() => {
const surroundElement = document.createElement("span");
@@ -114,34 +114,37 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-
-
+ (showFloating = false)}
+ let:asReference
+>
+
(showFloating = !showFloating)}
>
- {@html arrowIcon}
+ {@html chevronDown}
+
- event.preventDefault()}>
- {#each showFormats as format (format.name)}
- onItemClick(event, format)}>
-
- {format.name}
-
- {/each}
-
-
-
+
+ {#each showFormats as format (format.name)}
+ onItemClick(event, format)}>
+
+ {format.name}
+
+ {/each}
+
+
diff --git a/ts/editor/editor-toolbar/TextColorButton.svelte b/ts/editor/editor-toolbar/TextColorButton.svelte
index 98e30ebac..675162cc3 100644
--- a/ts/editor/editor-toolbar/TextColorButton.svelte
+++ b/ts/editor/editor-toolbar/TextColorButton.svelte
@@ -14,10 +14,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { removeStyleProperties } from "../../lib/styling";
import { singleCallback } from "../../lib/typing";
import { withFontColor } from "../helpers";
+ import { chevronDown } from "../icons";
import { surrounder } from "../rich-text-input";
import ColorPicker from "./ColorPicker.svelte";
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
- import { arrowIcon, textColorIcon } from "./icons";
+ import { textColorIcon } from "./icons";
import WithColorHelper from "./WithColorHelper.svelte";
export let color: string;
@@ -140,8 +141,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
tooltip="{tr.editingChangeColor()} ({getPlatformString(pickCombination)})"
{disabled}
widthMultiplier={0.5}
+ iconSize={120}
>
- {@html arrowIcon}
+ {@html chevronDown}
{
diff --git a/ts/editor/editor-toolbar/icons.ts b/ts/editor/editor-toolbar/icons.ts
index e00a2df79..e47fde20c 100644
--- a/ts/editor/editor-toolbar/icons.ts
+++ b/ts/editor/editor-toolbar/icons.ts
@@ -8,10 +8,13 @@ export { default as highlightColorIcon } from "@mdi/svg/svg/format-color-highlig
export { default as textColorIcon } from "@mdi/svg/svg/format-color-text.svg";
export { default as subscriptIcon } from "@mdi/svg/svg/format-subscript.svg";
export { default as superscriptIcon } from "@mdi/svg/svg/format-superscript.svg";
+export { default as functionIcon } from "@mdi/svg/svg/function-variant.svg";
+export { default as paperclipIcon } from "@mdi/svg/svg/paperclip.svg";
export { default as eraserIcon } from "bootstrap-icons/icons/eraser.svg";
export { default as justifyFullIcon } from "bootstrap-icons/icons/justify.svg";
export { default as olIcon } from "bootstrap-icons/icons/list-ol.svg";
export { default as ulIcon } from "bootstrap-icons/icons/list-ul.svg";
+export { default as micIcon } from "bootstrap-icons/icons/mic.svg";
export { default as justifyCenterIcon } from "bootstrap-icons/icons/text-center.svg";
export { default as indentIcon } from "bootstrap-icons/icons/text-indent-left.svg";
export { default as outdentIcon } from "bootstrap-icons/icons/text-indent-right.svg";
@@ -21,9 +24,3 @@ export { default as justifyRightIcon } from "bootstrap-icons/icons/text-right.sv
export { default as boldIcon } from "bootstrap-icons/icons/type-bold.svg";
export { default as italicIcon } from "bootstrap-icons/icons/type-italic.svg";
export { default as underlineIcon } from "bootstrap-icons/icons/type-underline.svg";
-export const arrowIcon =
- '';
-
-export { default as functionIcon } from "@mdi/svg/svg/function-variant.svg";
-export { default as paperclipIcon } from "@mdi/svg/svg/paperclip.svg";
-export { default as micIcon } from "bootstrap-icons/icons/mic.svg";
diff --git a/ts/editor/image-overlay/FloatButtons.svelte b/ts/editor/image-overlay/FloatButtons.svelte
index 570be1101..f75d82ae6 100644
--- a/ts/editor/image-overlay/FloatButtons.svelte
+++ b/ts/editor/image-overlay/FloatButtons.svelte
@@ -11,10 +11,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import IconButton from "../../components/IconButton.svelte";
import { directionKey } from "../../lib/context-keys";
import * as tr from "../../lib/ftl";
+ import { removeStyleProperties } from "../../lib/styling";
import { floatLeftIcon, floatNoneIcon, floatRightIcon } from "./icons";
export let image: HTMLImageElement;
+ $: floatStyle = getComputedStyle(image).float;
+
const direction = getContext>(directionKey);
const [inlineStartIcon, inlineEndIcon] =
$direction === "ltr"
@@ -27,7 +30,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{
image.style.float = "left";
@@ -38,22 +41,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{
- image.style.removeProperty("float");
-
- if (image.getAttribute("style")?.length === 0) {
- image.removeAttribute("style");
- }
-
+ // We shortly set to none, because simply unsetting float will not
+ // trigger floatStyle being reset
+ image.style.float = "none";
+ removeStyleProperties(image, "float");
setTimeout(() => dispatch("update"));
}}>{@html floatNoneIcon}
{
image.style.float = "right";
diff --git a/ts/editor/image-overlay/ImageHandle.svelte b/ts/editor/image-overlay/ImageOverlay.svelte
similarity index 75%
rename from ts/editor/image-overlay/ImageHandle.svelte
rename to ts/editor/image-overlay/ImageOverlay.svelte
index 1d412e758..901ec3b93 100644
--- a/ts/editor/image-overlay/ImageHandle.svelte
+++ b/ts/editor/image-overlay/ImageOverlay.svelte
@@ -5,15 +5,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-
+
{#if activeImage}
- {#await element then container}
-
createDropdown(event.detail.selection)}
+
+ {
+ const { reason, originalEvent } = detail;
+
+ if (reason === "outsideClick") {
+ // If the click is still in the overlay, we do not want
+ // to reset the handle either
+ if (!originalEvent.path.includes(imageOverlay)) {
+ await resetHandle();
+ }
+ } else {
+ await resetHandle();
+ }
+ }}
>
+
+
+ {
+ positionOverlay();
+ positionFloating();
+ }}
+ />
+
+ {
+ toggleActualSize();
+ positionOverlay();
+ }}
+ on:imageclear={() => {
+ clearActualSize();
+ positionOverlay();
+ }}
+ />
+
+
+
+
+
{
if (shrinkingDisabled) {
return;
}
toggleActualSize();
- updateSizesWithDimensions();
- dropdownObject.update();
+ positionOverlay();
}}
/>
-
+
{#if isSizeConstrained}
{tr.editingDoubleClickToExpand()}
{:else}
@@ -283,30 +309,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}}
on:pointermove={(event) => {
resize(event);
- updateSizesWithDimensions();
- dropdownObject.update();
}}
/>
-
- {/await}
-
-
-
- {
- toggleActualSize();
- updateSizesWithDimensions();
- dropdownObject.update();
- }}
- on:imageclear={() => {
- clearActualSize();
- updateSizesWithDimensions();
- dropdownObject.update();
- }}
- />
-
+
+
{/if}
-
+
diff --git a/ts/editor/image-overlay/index.ts b/ts/editor/image-overlay/index.ts
index f15585b35..53c9739b1 100644
--- a/ts/editor/image-overlay/index.ts
+++ b/ts/editor/image-overlay/index.ts
@@ -1,6 +1,6 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-import ImageHandle from "./ImageHandle.svelte";
+import ImageOverlay from "./ImageOverlay.svelte";
-export default ImageHandle;
+export default ImageOverlay;
diff --git a/ts/editor/mathjax-overlay/MathjaxButtons.svelte b/ts/editor/mathjax-overlay/MathjaxButtons.svelte
index a801572d2..b60259b76 100644
--- a/ts/editor/mathjax-overlay/MathjaxButtons.svelte
+++ b/ts/editor/mathjax-overlay/MathjaxButtons.svelte
@@ -8,18 +8,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import ButtonGroup from "../../components/ButtonGroup.svelte";
import ButtonToolbar from "../../components/ButtonToolbar.svelte";
import IconButton from "../../components/IconButton.svelte";
- import { hasBlockAttribute } from "../../lib/dom";
import * as tr from "../../lib/ftl";
import ClozeButtons from "../ClozeButtons.svelte";
import { blockIcon, deleteIcon, inlineIcon } from "./icons";
- export let element: Element;
-
- $: isBlock = hasBlockAttribute(element);
-
- function updateBlock() {
- element.setAttribute("block", String(isBlock));
- }
+ export let isBlock: boolean;
const dispatch = createEventDispatcher();
@@ -29,24 +22,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{
- isBlock = false;
- updateBlock();
- }}
- on:click
- --border-left-radius="5px">{@html inlineIcon} dispatch("setinline")}
+ --border-left-radius="5px"
>
+ {@html inlineIcon}
+
{
- isBlock = true;
- updateBlock();
- }}
- on:click
- --border-right-radius="5px">{@html blockIcon} dispatch("setblock")}
+ --border-right-radius="5px"
>
+ {@html blockIcon}
+
diff --git a/ts/editor/mathjax-overlay/MathjaxEditor.svelte b/ts/editor/mathjax-overlay/MathjaxEditor.svelte
index a341c38ca..7315afd11 100644
--- a/ts/editor/mathjax-overlay/MathjaxEditor.svelte
+++ b/ts/editor/mathjax-overlay/MathjaxEditor.svelte
@@ -10,6 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "../../lib/ftl";
import { noop } from "../../lib/functional";
import { getPlatformString } from "../../lib/shortcuts";
+ import { pageTheme } from "../../sveltelib/theme";
import { baseOptions, focusAndSetCaret, latex } from "../code-mirror";
import type { CodeMirrorAPI } from "../CodeMirror.svelte";
import CodeMirror from "../CodeMirror.svelte";
@@ -44,12 +45,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
onMount(async () => {
const editor = await codeMirror.editor;
- focusAndSetCaret(editor, position);
-
- if (selectAll) {
- editor.execCommand("selectAll");
- }
-
let direction: "start" | "end" | undefined = undefined;
editor.on(
@@ -86,16 +81,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
direction = undefined;
},
);
+
+ setTimeout(() => {
+ focusAndSetCaret(editor, position);
+
+ if (selectAll) {
+ editor.execCommand("selectAll");
+ }
+ });
});
-
+
code.set(mathjaxText)}
- on:blur
+ on:tab
/>
@@ -103,12 +106,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
diff --git a/ts/editor/mathjax-overlay/MathjaxOverlay.svelte b/ts/editor/mathjax-overlay/MathjaxOverlay.svelte
new file mode 100644
index 000000000..967fc8883
--- /dev/null
+++ b/ts/editor/mathjax-overlay/MathjaxOverlay.svelte
@@ -0,0 +1,244 @@
+
+
+
+
+ {#if activeImage && mathjaxElement}
+
+
+
+ {
+ placeHandle(false);
+ await resetHandle();
+ }}
+ on:moveoutend={async () => {
+ placeHandle(true);
+ await resetHandle();
+ }}
+ on:tab={async () => {
+ // Instead of resetting on blur, we reset on tab
+ // Otherwise, when clicking from Mathjax element to another,
+ // the user has to click twice (focus is called before blur?)
+ await resetHandle();
+ }}
+ let:editor={mathjaxEditor}
+ >
+ {
+ placeHandle(true);
+ await resetHandle();
+ }}
+ />
+
+ {
+ isBlock = false;
+ await updateBlockAttribute();
+ positionOverlay();
+ positionFloating();
+ }}
+ on:setblock={async () => {
+ isBlock = true;
+ await updateBlockAttribute();
+ positionOverlay();
+ positionFloating();
+ }}
+ on:delete={async () => {
+ placeCaretAfter(activeImage);
+ activeImage.remove();
+ await resetHandle();
+ }}
+ on:surround={async ({ detail }) => {
+ const editor = await mathjaxEditor.editor;
+ const { prefix, suffix } = detail;
+
+ editor.replaceSelection(
+ prefix + editor.getSelection() + suffix,
+ );
+ }}
+ />
+
+
+
+
+
+
+
+
+ {/if}
+
diff --git a/ts/editor/mathjax-overlay/index.ts b/ts/editor/mathjax-overlay/index.ts
index 6c825ad3e..2bc5967a8 100644
--- a/ts/editor/mathjax-overlay/index.ts
+++ b/ts/editor/mathjax-overlay/index.ts
@@ -1,6 +1,6 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-import MathjaxHandle from "./MathjaxHandle.svelte";
+import MathjaxOverlay from "./MathjaxOverlay.svelte";
-export default MathjaxHandle;
+export default MathjaxOverlay;
diff --git a/ts/editor/plain-text-input/PlainTextInput.svelte b/ts/editor/plain-text-input/PlainTextInput.svelte
index 96792d56a..99c5296e5 100644
--- a/ts/editor/plain-text-input/PlainTextInput.svelte
+++ b/ts/editor/plain-text-input/PlainTextInput.svelte
@@ -40,7 +40,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { storedToUndecorated, undecoratedToStored } from "./transform";
export let isDefault: boolean;
- export let hidden: boolean;
+ export let hidden = false;
export let richTextHidden: boolean;
const configuration = {
@@ -148,6 +148,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
class:is-default={isDefault}
class:alone={richTextHidden}
on:focusin={() => ($focusedInput = api)}
+ {hidden}
>
.plain-text-input {
- overflow: hidden;
-
border-top: 1px solid var(--border);
border-radius: 0 0 5px 5px;
@@ -170,13 +169,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
border-bottom: 1px solid var(--border);
border-radius: 5px 5px 0 0;
}
+
&.alone {
border: none;
border-radius: 5px;
}
+
:global(.CodeMirror) {
background: var(--code-bg);
}
+
:global(.CodeMirror-lines) {
padding: 8px 0;
}
diff --git a/ts/editor/rich-text-input/RichTextInput.svelte b/ts/editor/rich-text-input/RichTextInput.svelte
index 47be4117a..4aae9e477 100644
--- a/ts/editor/rich-text-input/RichTextInput.svelte
+++ b/ts/editor/rich-text-input/RichTextInput.svelte
@@ -77,7 +77,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import RichTextStyles from "./RichTextStyles.svelte";
import { fragmentToStored, storedToFragment } from "./transform";
- export let hidden: boolean;
+ export let hidden = false;
const { focusedInput } = noteEditorContext.get();
const { content, editingInputs } = editingAreaContext.get();
@@ -211,7 +211,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
setupLifecycleHooks(api);
-
+
diff --git a/ts/lib/typing.ts b/ts/lib/typing.ts
index 0b894ba29..1523eb1b8 100644
--- a/ts/lib/typing.ts
+++ b/ts/lib/typing.ts
@@ -6,6 +6,7 @@ export function assertUnreachable(x: never): never {
}
export type Callback = () => void;
+export type AsyncCallback = () => Promise;
export function singleCallback(...callbacks: Callback[]): Callback {
return () => {
diff --git a/ts/sveltelib/closing-click.ts b/ts/sveltelib/closing-click.ts
index 94f629cc6..99bf0d1bf 100644
--- a/ts/sveltelib/closing-click.ts
+++ b/ts/sveltelib/closing-click.ts
@@ -4,6 +4,8 @@
import type { Readable } from "svelte/store";
import { derived } from "svelte/store";
+import type { EventPredicateResult } from "./event-predicate";
+
/**
* Typically the right-sided mouse button.
*/
@@ -31,34 +33,43 @@ interface ClosingClickArgs {
function isClosingClick(
store: Readable,
{ reference, floating, inside, outside }: ClosingClickArgs,
-): Readable {
- function isTriggerClick(path: EventTarget[]): boolean {
- return (
- // Reference element was clicked, e.g. the button.
- // The reference element needs to handle opening/closing itself.
- !path.includes(reference) &&
- ((inside && path.includes(floating)) ||
- (outside && !path.includes(floating)))
- );
- }
-
- function shouldClose(event: MouseEvent): boolean {
- if (isSecondaryButton(event)) {
- return true;
+): Readable {
+ function isTriggerClick(path: EventTarget[]): string | false {
+ // Reference element was clicked, e.g. the button.
+ // The reference element needs to handle opening/closing itself.
+ if (path.includes(reference)) {
+ return false;
}
- if (isTriggerClick(event.composedPath())) {
- return true;
+ if (inside && path.includes(floating)) {
+ return "insideClick";
+ }
+
+ if (outside && !path.includes(floating)) {
+ return "outsideClick";
}
return false;
}
- return derived(store, (event: MouseEvent, set: (value: symbol) => void): void => {
- if (shouldClose(event)) {
- set(Symbol());
+ function shouldClose(event: MouseEvent): string | false {
+ if (isSecondaryButton(event)) {
+ return "secondaryButton";
}
- });
+
+ return isTriggerClick(event.composedPath());
+ }
+
+ return derived(
+ store,
+ (event: MouseEvent, set: (value: EventPredicateResult) => void): void => {
+ const reason = shouldClose(event);
+
+ if (reason) {
+ set({ reason, originalEvent: event });
+ }
+ },
+ );
}
export default isClosingClick;
diff --git a/ts/sveltelib/closing-keyup.ts b/ts/sveltelib/closing-keyup.ts
index dd0dc1075..187e0b36c 100644
--- a/ts/sveltelib/closing-keyup.ts
+++ b/ts/sveltelib/closing-keyup.ts
@@ -4,6 +4,8 @@
import type { Readable } from "svelte/store";
import { derived } from "svelte/store";
+import type { EventPredicateResult } from "./event-predicate";
+
interface ClosingKeyupArgs {
/**
* Clicking on the reference element should not close.
@@ -22,24 +24,26 @@ interface ClosingKeyupArgs {
function isClosingKeyup(
store: Readable,
_args: ClosingKeyupArgs,
-): Readable {
+): Readable {
// TODO there needs to be special treatment, whether the keyup happens
// inside the floating element or outside, but I'll defer until we actually
// use this for a popover with an input field
- function shouldClose(event: KeyboardEvent) {
+ function shouldClose(event: KeyboardEvent): string | false {
if (event.key === "Tab") {
// Allow Tab navigation.
return false;
}
- return true;
+ return "keyup";
}
return derived(
store,
- (event: KeyboardEvent, set: (value: symbol) => void): void => {
- if (shouldClose(event)) {
- set(Symbol());
+ (event: KeyboardEvent, set: (value: EventPredicateResult) => void): void => {
+ const reason = shouldClose(event);
+
+ if (reason) {
+ set({ reason, originalEvent: event });
}
},
);
diff --git a/ts/sveltelib/event-predicate.d.ts b/ts/sveltelib/event-predicate.d.ts
new file mode 100644
index 000000000..3961510c6
--- /dev/null
+++ b/ts/sveltelib/event-predicate.d.ts
@@ -0,0 +1,4 @@
+export interface EventPredicateResult {
+ reason: string;
+ originalEvent: Event;
+}
diff --git a/ts/sveltelib/event-store.ts b/ts/sveltelib/event-store.ts
index 58cf9da7d..3302b1710 100644
--- a/ts/sveltelib/event-store.ts
+++ b/ts/sveltelib/event-store.ts
@@ -21,7 +21,7 @@ function eventStore>(
target: T,
eventType: Exclude,
/**
- * Store need an initial value. This should probably be a freshly
+ * Store needs an initial value. This should probably be a freshly
* constructed event, e.g. `new MouseEvent("click")`.
*/
constructor: Init[K]>,
diff --git a/ts/sveltelib/portal.ts b/ts/sveltelib/portal.ts
index c3b63325f..8f045942f 100644
--- a/ts/sveltelib/portal.ts
+++ b/ts/sveltelib/portal.ts
@@ -9,10 +9,15 @@ function portal(
element: HTMLElement,
targetElement: Element = document.body,
): { update(target: Element): void; destroy(): void } {
- let target: Element = targetElement;
+ let target: Element;
async function update(newTarget: Element) {
target = newTarget;
+
+ if (!target) {
+ return;
+ }
+
target.append(element);
}
@@ -20,7 +25,7 @@ function portal(
element.remove();
}
- update(target);
+ update(targetElement);
return {
update,
diff --git a/ts/sveltelib/position.ts b/ts/sveltelib/position.ts
deleted file mode 100644
index 8038fafd9..000000000
--- a/ts/sveltelib/position.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright: Ankitects Pty Ltd and contributors
-// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-
-import type { Placement } from "@floating-ui/dom";
-import {
- arrow,
- autoUpdate,
- computePosition,
- inline,
- offset,
- shift,
-} from "@floating-ui/dom";
-
-export interface PositionArgs {
- /**
- * The floating element which is positioned relative to `reference`.
- */
- floating: HTMLElement | null;
- placement: Placement;
- arrow: HTMLElement;
-}
-
-function position(
- reference: HTMLElement,
- positionArgs: PositionArgs,
-): { update(args: PositionArgs): void; destroy(): void } {
- let args = positionArgs;
-
- async function updateInner(): Promise {
- const { x, y, middlewareData } = await computePosition(
- reference,
- args.floating!,
- {
- middleware: [
- inline(),
- offset(5),
- shift({ padding: 5 }),
- arrow({ element: args.arrow, padding: 5 }),
- ],
- placement: args.placement,
- },
- );
-
- let rotation: number;
- let arrowX: number | undefined;
- let arrowY: number | undefined;
-
- if (args.placement.startsWith("bottom")) {
- rotation = 45;
- arrowX = middlewareData.arrow?.x;
- arrowY = -5;
- } else if (args.placement.startsWith("left")) {
- rotation = 135;
- arrowX = args.floating!.offsetWidth - 5;
- arrowY = middlewareData.arrow?.y;
- } else if (args.placement.startsWith("top")) {
- rotation = 225;
- arrowX = middlewareData.arrow?.x;
- arrowY = args.floating!.offsetHeight - 5;
- } /* if (args.placement.startsWith("right")) */ else {
- rotation = 315;
- arrowX = -5;
- arrowY = middlewareData.arrow?.y;
- }
-
- Object.assign(args.arrow.style, {
- left: arrowX ? `${arrowX}px` : "",
- top: arrowY ? `${arrowY}px` : "",
- transform: `rotate(${rotation}deg)`,
- });
-
- Object.assign(args.floating!.style, {
- left: `${x}px`,
- top: `${y}px`,
- });
- }
-
- let cleanup: (() => void) | null = null;
-
- function destroy(): void {
- cleanup?.();
- cleanup = null;
-
- if (!args.floating) {
- return;
- }
-
- args.floating.style.removeProperty("left");
- args.floating.style.removeProperty("top");
- }
-
- function update(updateArgs: PositionArgs): void {
- destroy();
- args = updateArgs;
-
- if (!args.floating) {
- return;
- }
-
- cleanup = autoUpdate(reference, args.floating, updateInner);
- }
-
- update(args);
-
- return {
- update,
- destroy,
- };
-}
-
-export default position;
diff --git a/ts/sveltelib/position/auto-update.ts b/ts/sveltelib/position/auto-update.ts
new file mode 100644
index 000000000..176997110
--- /dev/null
+++ b/ts/sveltelib/position/auto-update.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 type { FloatingElement } from "@floating-ui/dom";
+import { autoUpdate as floatingUiAutoUpdate } from "@floating-ui/dom";
+import type { ActionReturn } from "svelte/action";
+
+import type { Callback } from "../../lib/typing";
+
+/**
+ * The interface of `autoUpdate` of floating-ui.
+ * This means PositioningCallback can be used with that, but also invoked as it is.
+ *
+ * @example ```
+ * // Invoke the positioning algorithm handily
+ * position(myReference, (_, _, callback) => {
+ * callback();
+ * })`
+ */
+export type PositioningCallback = (
+ reference: HTMLElement,
+ floating: FloatingElement,
+ position: Callback,
+) => Callback;
+
+/**
+ * The interface of a function that calls `computePosition` of floating-ui.
+ */
+export type PositionFunc = (
+ reference: HTMLElement,
+ callback: PositioningCallback,
+) => Callback;
+
+function autoUpdate(
+ reference: HTMLElement,
+ /**
+ * The method to position the floating element.
+ */
+ position: PositionFunc,
+): ActionReturn {
+ let cleanup: Callback;
+
+ function destroy() {
+ cleanup?.();
+ }
+
+ function update(position: PositionFunc): void {
+ destroy();
+ cleanup = position(reference, floatingUiAutoUpdate);
+ }
+
+ update(position);
+
+ return { destroy, update };
+}
+
+export default autoUpdate;
diff --git a/ts/sveltelib/position/index.ts b/ts/sveltelib/position/index.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/ts/sveltelib/position/position-algorithm.d.ts b/ts/sveltelib/position/position-algorithm.d.ts
new file mode 100644
index 000000000..b7b5afda3
--- /dev/null
+++ b/ts/sveltelib/position/position-algorithm.d.ts
@@ -0,0 +1,12 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import type { FloatingElement } from "@floating-ui/dom";
+
+/**
+ * The interface of a function that calls `computePosition` of floating-ui.
+ */
+export type PositionAlgorithm = (
+ reference: HTMLElement,
+ floating: FloatingElement,
+) => Promise;
diff --git a/ts/sveltelib/position/position-floating.ts b/ts/sveltelib/position/position-floating.ts
new file mode 100644
index 000000000..8915b0e59
--- /dev/null
+++ b/ts/sveltelib/position/position-floating.ts
@@ -0,0 +1,125 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import type {
+ ComputePositionConfig,
+ FloatingElement,
+ Middleware,
+ Placement,
+} from "@floating-ui/dom";
+import {
+ arrow,
+ autoPlacement,
+ computePosition,
+ hide,
+ inline,
+ offset,
+ shift,
+} from "@floating-ui/dom";
+
+import type { PositionAlgorithm } from "./position-algorithm";
+
+export interface PositionFloatingArgs {
+ placement: Placement | "auto";
+ arrow: HTMLElement;
+ shift: number;
+ offset: number;
+ inline: boolean;
+ hideIfEscaped: boolean;
+ hideIfReferenceHidden: boolean;
+ hideCallback: (reason: string) => void;
+}
+
+function positionFloating({
+ placement,
+ arrow: arrowElement,
+ shift: shiftArg,
+ offset: offsetArg,
+ inline: inlineArg,
+ hideIfEscaped,
+ hideIfReferenceHidden,
+ hideCallback,
+}: PositionFloatingArgs): PositionAlgorithm {
+ return async function (
+ reference: HTMLElement,
+ floating: FloatingElement,
+ ): Promise {
+ const middleware: Middleware[] = [
+ offset(offsetArg),
+ shift({ padding: shiftArg }),
+ arrow({ element: arrowElement, padding: 5 }),
+ ];
+
+ if (inlineArg) {
+ middleware.unshift(inline());
+ }
+
+ const computeArgs: Partial = {
+ middleware,
+ };
+
+ if (placement !== "auto") {
+ computeArgs.placement = placement;
+ } else {
+ middleware.push(autoPlacement());
+ }
+
+ if (hideIfEscaped) {
+ middleware.push(hide({ strategy: "escaped" }));
+ }
+
+ if (hideIfReferenceHidden) {
+ middleware.push(hide({ strategy: "referenceHidden" }));
+ }
+
+ const {
+ x,
+ y,
+ middlewareData,
+ placement: computedPlacement,
+ } = await computePosition(reference, floating, computeArgs);
+
+ if (middlewareData.hide?.escaped) {
+ return hideCallback("escaped");
+ }
+
+ if (middlewareData.hide?.referenceHidden) {
+ return hideCallback("referenceHidden");
+ }
+
+ Object.assign(floating.style, {
+ left: `${x}px`,
+ top: `${y}px`,
+ });
+
+ let rotation: number;
+ let arrowX: number | undefined;
+ let arrowY: number | undefined;
+
+ if (computedPlacement.startsWith("bottom")) {
+ rotation = 45;
+ arrowX = middlewareData.arrow?.x;
+ arrowY = -5;
+ } else if (computedPlacement.startsWith("left")) {
+ rotation = 135;
+ arrowX = floating.offsetWidth - 5;
+ arrowY = middlewareData.arrow?.y;
+ } else if (computedPlacement.startsWith("top")) {
+ rotation = 225;
+ arrowX = middlewareData.arrow?.x;
+ arrowY = floating.offsetHeight - 5;
+ } /* if (computedPlacement.startsWith("right")) */ else {
+ rotation = 315;
+ arrowX = -5;
+ arrowY = middlewareData.arrow?.y;
+ }
+
+ Object.assign(arrowElement.style, {
+ left: arrowX ? `${arrowX}px` : "",
+ top: arrowY ? `${arrowY}px` : "",
+ transform: `rotate(${rotation}deg)`,
+ });
+ };
+}
+
+export default positionFloating;
diff --git a/ts/sveltelib/position/position-overlay.ts b/ts/sveltelib/position/position-overlay.ts
new file mode 100644
index 000000000..c64cdad81
--- /dev/null
+++ b/ts/sveltelib/position/position-overlay.ts
@@ -0,0 +1,67 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import type {
+ ComputePositionConfig,
+ FloatingElement,
+ Middleware,
+} from "@floating-ui/dom";
+import { computePosition, inline, offset } from "@floating-ui/dom";
+
+import type { PositionAlgorithm } from "./position-algorithm";
+
+export interface PositionOverlayArgs {
+ padding: number;
+ inline: boolean;
+ hideCallback: (reason: string) => void;
+}
+
+function positionOverlay({
+ padding,
+ inline: inlineArg,
+ hideCallback,
+}: PositionOverlayArgs): PositionAlgorithm {
+ return async function (
+ reference: HTMLElement,
+ floating: FloatingElement,
+ ): Promise {
+ const middleware: Middleware[] = inlineArg ? [inline()] : [];
+
+ const { width, height } = reference.getBoundingClientRect();
+
+ middleware.push(
+ offset({
+ mainAxis: -(height + padding),
+ }),
+ );
+
+ const computeArgs: Partial = {
+ middleware,
+ };
+
+ const { x, y, middlewareData } = await computePosition(
+ reference,
+ floating,
+ computeArgs,
+ );
+
+ // console.log(x, y)
+
+ if (middlewareData.hide?.escaped) {
+ hideCallback("escaped");
+ }
+
+ if (middlewareData.hide?.referenceHidden) {
+ hideCallback("referenceHidden");
+ }
+
+ Object.assign(floating.style, {
+ left: `${x}px`,
+ top: `${y}px`,
+ width: `${width + 2 * padding}px`,
+ height: `${height + 2 * padding}px`,
+ });
+ };
+}
+
+export default positionOverlay;
diff --git a/ts/sveltelib/resize-store.ts b/ts/sveltelib/resize-store.ts
new file mode 100644
index 000000000..829e8ec7b
--- /dev/null
+++ b/ts/sveltelib/resize-store.ts
@@ -0,0 +1,45 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import type { Readable, Subscriber } from "svelte/store";
+import { readable } from "svelte/store";
+
+import type { Callback } from "../lib/typing";
+
+interface ResizeObserverArgs {
+ entries: ResizeObserverEntry[];
+ observer: ResizeObserver;
+}
+
+export type ResizeStore = Readable;
+
+/**
+ * A store wrapping a ResizeObserver. Automatically observes the target upon
+ * first/last subscriber.
+ *
+ * @remarks
+ * Should probably always be used in conjunction with `subscribeToUpdates`.
+ */
+function resizeStore(target: Element): ResizeStore {
+ let setter: (args: ResizeObserverArgs) => void;
+
+ const observer = new ResizeObserver(
+ (entries: ResizeObserverEntry[], observer: ResizeObserver): void =>
+ setter({
+ entries,
+ observer,
+ }),
+ );
+
+ return readable(
+ { entries: [], observer },
+ (set: Subscriber): Callback => {
+ setter = set;
+ observer.observe(target);
+
+ return () => observer.unobserve(target);
+ },
+ );
+}
+
+export default resizeStore;
diff --git a/ts/sveltelib/subscribe-trigger.ts b/ts/sveltelib/subscribe-trigger.ts
deleted file mode 100644
index 5464be0bd..000000000
--- a/ts/sveltelib/subscribe-trigger.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright: Ankitects Pty Ltd and contributors
-// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-
-import type { Readable, Writable } from "svelte/store";
-
-import { Callback, singleCallback } from "../lib/typing";
-import subscribeToUpdates from "./subscribe-updates";
-
-/**
- * The goal of this action is to turn itself inactive.
- * Once `active` is `true`, it will unsubscribe from `store`.
- *
- * @param active: If `active` is `true`, all stores will be subscribed to.
- * @param stores: If any `store` updates to a true value, active will be set to false.
- */
-function subscribeTrigger(
- active: Writable,
- ...stores: Readable[]
-): Callback {
- function shouldUnset(): void {
- active.set(false);
- }
-
- let destroy: Callback | null;
-
- function doDestroy(): void {
- destroy?.();
- destroy = null;
- }
-
- active.subscribe((value: boolean): void => {
- if (value && !destroy) {
- destroy = singleCallback(
- ...stores.map((store) => subscribeToUpdates(store, shouldUnset)),
- );
- } else if (!value) {
- doDestroy();
- }
- });
-
- return doDestroy;
-}
-
-export default subscribeTrigger;
diff --git a/ts/sveltelib/toggleable.ts b/ts/sveltelib/toggleable.ts
index a54cb7687..1d50b98b7 100644
--- a/ts/sveltelib/toggleable.ts
+++ b/ts/sveltelib/toggleable.ts
@@ -2,27 +2,36 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { Writable } from "svelte/store";
+import { writable } from "svelte/store";
-export interface Toggleable {
+export interface Toggleable extends Writable {
toggle: () => void;
on: () => void;
off: () => void;
}
-function toggleable(store: Writable): Toggleable {
+function toggleable(defaultValue: boolean): Toggleable {
+ const store = writable(defaultValue) as Toggleable;
+
function toggle(): void {
store.update((value) => !value);
}
+ store.toggle = toggle;
+
function on(): void {
store.set(true);
}
+ store.on = on;
+
function off(): void {
store.set(false);
}
- return { toggle, on, off };
+ store.off = off;
+
+ return store;
}
export default toggleable;
diff --git a/ts/sveltelib/tsconfig.json b/ts/sveltelib/tsconfig.json
index 156ec7f9f..43917a08a 100644
--- a/ts/sveltelib/tsconfig.json
+++ b/ts/sveltelib/tsconfig.json
@@ -1,5 +1,9 @@
{
"extends": "../tsconfig.json",
- "include": ["*"],
- "references": [{ "path": "../lib" }]
+ "include": ["*", "position/*"],
+ "references": [
+ {
+ "path": "../lib"
+ }
+ ]
}
diff --git a/ts/tag-editor/TagEditor.svelte b/ts/tag-editor/TagEditor.svelte
index ea5156fd1..d25c4b53b 100644
--- a/ts/tag-editor/TagEditor.svelte
+++ b/ts/tag-editor/TagEditor.svelte
@@ -426,7 +426,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
splitTag(index, detail.chosen.length, detail.chosen.length);
}}
let:createAutocomplete
- let:hide
>
{
if (activeName.length === 0) {
- hide?.();
+ show?.set(false);
}
}}
on:taginput={() => updateTagName(tag)}
diff --git a/ts/tag-editor/WithAutocomplete.svelte b/ts/tag-editor/WithAutocomplete.svelte
index e71aba826..0efbc1030 100644
--- a/ts/tag-editor/WithAutocomplete.svelte
+++ b/ts/tag-editor/WithAutocomplete.svelte
@@ -124,14 +124,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}
-
-
-
+ show.set(false)}
+>
+
+
diff --git a/ts/tag-editor/tag-options-button/TagsSelectedButton.svelte b/ts/tag-editor/tag-options-button/TagsSelectedButton.svelte
index 4ae80c8a2..601427671 100644
--- a/ts/tag-editor/tag-options-button/TagsSelectedButton.svelte
+++ b/ts/tag-editor/tag-options-button/TagsSelectedButton.svelte
@@ -16,20 +16,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const dispatch = createEventDispatcher();
+ let show = false;
+
const allShortcut = "Control+A";
const copyShortcut = "Control+C";
const removeShortcut = "Backspace";
-
-