diff --git a/ftl/core/editing.ftl b/ftl/core/editing.ftl
index 910669166..589091d01 100644
--- a/ftl/core/editing.ftl
+++ b/ftl/core/editing.ftl
@@ -41,6 +41,10 @@ editing-show-duplicates = Show Duplicates
editing-subscript = Subscript
editing-superscript = Superscript
editing-tags = Tags
+editing-tags-add = Add tag
+editing-tags-copy = Copy tags
+editing-tags-remove = Remove tags
+editing-tags-select-all = Select all tags
editing-text-color = Text color
editing-text-highlight-color = Text highlight color
editing-to-make-a-cloze-deletion-on = To make a cloze deletion on an existing note, you need to change it to a cloze type first, via 'Notes>Change Note Type'
diff --git a/ts/components/Popover.svelte b/ts/components/Popover.svelte
index d4f00514e..aa015eb52 100644
--- a/ts/components/Popover.svelte
+++ b/ts/components/Popover.svelte
@@ -17,6 +17,7 @@ Alternative to DropdownMenu that avoids Bootstrap
border-radius: 5px;
background-color: var(--frame-bg);
min-width: 1rem;
+ max-width: 95vw;
padding: 0.5rem 0;
font-size: 1rem;
diff --git a/ts/components/WithFloating.svelte b/ts/components/WithFloating.svelte
index 1e5094e4f..63a88688d 100644
--- a/ts/components/WithFloating.svelte
+++ b/ts/components/WithFloating.svelte
@@ -11,15 +11,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import isClosingKeyup from "../sveltelib/closing-keyup";
import { documentClick, documentKeyup } from "../sveltelib/event-store";
import portal from "../sveltelib/portal";
+ import type { PositionArgs } from "../sveltelib/position";
import position from "../sveltelib/position";
import subscribeTrigger from "../sveltelib/subscribe-trigger";
import { pageTheme } from "../sveltelib/theme";
import toggleable from "../sveltelib/toggleable";
- /** TODO at the moment we only dropdowns which are placed actually below the reference */
- const placement: Placement = "bottom";
-
+ export let placement: Placement = "bottom";
export let closeOnInsideClick = false;
+ export let keepOnKeyup = false;
/** This may be passed in for more fine-grained control */
export let show = writable(false);
@@ -30,45 +30,61 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const { toggle, on, off } = toggleable(show);
- onMount(() =>
- subscribeTrigger(
- show,
+ let args: PositionArgs;
+ $: args = {
+ floating: $show ? floating : null,
+ placement,
+ arrow,
+ };
+
+ let update: (args: PositionArgs) => void;
+ $: update?.(args);
+
+ function asReference(element: HTMLElement) {
+ const pos = position(element, args);
+ reference = element;
+ update = pos.update;
+
+ return {
+ destroy() {
+ pos.destroy();
+ },
+ };
+ }
+
+ onMount(() => {
+ const triggers = [
isClosingClick(documentClick, {
reference,
floating,
inside: closeOnInsideClick,
outside: true,
}),
- isClosingKeyup(documentKeyup, {
- reference,
- floating,
- }),
- ),
- );
+ ];
+
+ if (!keepOnKeyup) {
+ triggers.push(
+ isClosingKeyup(documentKeyup, {
+ reference,
+ floating,
+ }),
+ );
+ }
+
+ subscribeTrigger(show, ...triggers);
+ });
-
-
-
+
diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte
index 13e0a7b6b..957b07616 100644
--- a/ts/editor/NoteEditor.svelte
+++ b/ts/editor/NoteEditor.svelte
@@ -354,7 +354,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-
+
diff --git a/ts/editor/tag-editor/Tag.svelte b/ts/editor/tag-editor/Tag.svelte
index a43146c64..f76d6306b 100644
--- a/ts/editor/tag-editor/Tag.svelte
+++ b/ts/editor/tag-editor/Tag.svelte
@@ -11,7 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export { className as class };
export let tooltip: string | undefined = undefined;
- export let selected: boolean = false;
+ export let selected = false;
const dispatch = createEventDispatcher();
diff --git a/ts/editor/tag-editor/TagEditor.svelte b/ts/editor/tag-editor/TagEditor.svelte
index cd87d8199..c9dfd8533 100644
--- a/ts/editor/tag-editor/TagEditor.svelte
+++ b/ts/editor/tag-editor/TagEditor.svelte
@@ -5,15 +5,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
- {#if !wrap}
- tag.selected)}
+
+
- {/if}
-
-
- {#if wrap}
- tag.selected)}
- bind:badgeHeight
- on:tagselectall={selectAllTags}
- on:tagcopy={copySelectedTags}
- on:tagdelete={deleteSelectedTags}
- on:tagappend={appendEmptyTag}
- />
- {/if}
{#each tagTypes as tag, index (tag.id)}
-
+
{#if index === active}
-
- onAutocomplete(detail.selected)}
- on:choose={({ detail }) => onAutocomplete(detail.chosen)}
- let:createAutocomplete
- >
- {
- activeName = tag.name;
- autocomplete = createAutocomplete(activeInput);
- }}
- on:keydown={onKeydown}
- on:keyup={onKeyup}
- on:taginput={() => updateTagName(tag)}
- on:tagsplit={({ detail }) =>
- enterBehavior(index, detail.start, detail.end)}
- on:tagadd={() => insertTagKeepFocus(index)}
- on:tagdelete={() => deleteTagAt(index)}
- on:tagjoinprevious={() => joinWithPreviousTag(index)}
- on:tagjoinnext={() => joinWithNextTag(index)}
- on:tagmoveprevious={() => moveToPreviousTag(index)}
- on:tagmovenext={() => moveToNextTag(index)}
- on:tagaccept={() => {
- deleteTagIfNotUnique(tag, index);
- if (tag) {
- updateTagName(tag);
- }
- saveTags();
- decideNextActive();
- }}
- />
-
-
+
onAutocomplete(detail.selected)}
+ on:choose={({ detail }) => {
+ onAutocomplete(detail.chosen);
+ splitTag(index, detail.chosen.length, detail.chosen.length);
+ }}
+ let:createAutocomplete
+ let:hide
+ >
+ {
+ activeName = tag.name;
+ autocomplete = createAutocomplete();
+ }}
+ on:keydown={onKeydown}
+ on:keyup={() => {
+ if (activeName.length === 0) {
+ hide?.();
+ }
+ }}
+ on:taginput={() => updateTagName(tag)}
+ on:tagsplit={({ detail }) =>
+ splitTag(index, detail.start, detail.end)}
+ on:tagadd={() => insertTagKeepFocus(index)}
+ on:tagdelete={() => deleteTagAt(index)}
+ on:tagjoinprevious={() => joinWithPreviousTag(index)}
+ on:tagjoinnext={() => joinWithNextTag(index)}
+ on:tagmoveprevious={() => moveToPreviousTag(index)}
+ on:tagmovenext={() => moveToNextTag(index)}
+ on:tagaccept={() => {
+ deleteTagIfNotUnique(tag, index);
+ if (tag) {
+ updateTagName(tag);
+ }
+ saveTags();
+ decideNextActive();
+ }}
+ />
+
{/if}
{/each}
-
-
-
-
- SPACER
-
-
+
+
diff --git a/ts/editor/tag-editor/TagInput.svelte b/ts/editor/tag-editor/TagInput.svelte
index 96a0a58e5..a9f0f40d4 100644
--- a/ts/editor/tag-editor/TagInput.svelte
+++ b/ts/editor/tag-editor/TagInput.svelte
@@ -265,4 +265,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
border: none;
margin: 0;
}
+
+ .tag-input {
+ /* recreates positioning of Tag component
+ * so that the text does not move when accepting */
+ border-left: 1px solid transparent;
+ }
diff --git a/ts/editor/tag-editor/TagOptionsBadge.svelte b/ts/editor/tag-editor/TagOptionsBadge.svelte
deleted file mode 100644
index 8a8cb4e27..000000000
--- a/ts/editor/tag-editor/TagOptionsBadge.svelte
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
- {#if showSelectionsOptions}
-
- {:else}
-
- {/if}
-
diff --git a/ts/editor/tag-editor/TagSpacer.svelte b/ts/editor/tag-editor/TagSpacer.svelte
new file mode 100644
index 000000000..7dbdfd37a
--- /dev/null
+++ b/ts/editor/tag-editor/TagSpacer.svelte
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/ts/editor/tag-editor/TagStickyBadge.svelte b/ts/editor/tag-editor/TagStickyBadge.svelte
deleted file mode 100644
index d6ec16524..000000000
--- a/ts/editor/tag-editor/TagStickyBadge.svelte
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-{@html deleteIcon}
diff --git a/ts/editor/tag-editor/WithAutocomplete.svelte b/ts/editor/tag-editor/WithAutocomplete.svelte
index 33236d6a7..54112883d 100644
--- a/ts/editor/tag-editor/WithAutocomplete.svelte
+++ b/ts/editor/tag-editor/WithAutocomplete.svelte
@@ -3,42 +3,35 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
-
-
+
+
+
+
-
- {#each suggestionsItems as suggestion, index}
- {#if index === selected}
- setSelectedAndActive(index)}
- on:mouseup={() => {
- selectIndex(index);
- chooseSelected();
- }}
- on:mouseenter={(event) => selectIfMousedown(event, index)}
- on:mouseleave={() => (active = false)}
- >{suggestion}
- {:else}
- setSelectedAndActive(index)}
- on:mouseup={() => {
- selectIndex(index);
- chooseSelected();
- }}
- on:mouseenter={(event) => selectIfMousedown(event, index)}
- >{suggestion}
- {/if}
- {/each}
-
-
+
+
+
+
+
+
diff --git a/ts/editor/tag-editor/icons.ts b/ts/editor/tag-editor/icons.ts
index cf9c8625a..7781f29b2 100644
--- a/ts/editor/tag-editor/icons.ts
+++ b/ts/editor/tag-editor/icons.ts
@@ -3,7 +3,4 @@
///
-export { default as dotsIcon } from "@mdi/svg/svg/dots-vertical.svg";
-export { default as tagIcon } from "@mdi/svg/svg/tag.svg";
-export { default as addTagIcon } from "@mdi/svg/svg/tag-plus.svg";
export { default as deleteIcon } from "bootstrap-icons/icons/x.svg";
diff --git a/ts/editor/tag-editor/AddTagBadge.svelte b/ts/editor/tag-editor/tag-options-button/TagAddButton.svelte
similarity index 60%
rename from ts/editor/tag-editor/AddTagBadge.svelte
rename to ts/editor/tag-editor/tag-options-button/TagAddButton.svelte
index 41b152c5d..7a512bda1 100644
--- a/ts/editor/tag-editor/AddTagBadge.svelte
+++ b/ts/editor/tag-editor/tag-options-button/TagAddButton.svelte
@@ -5,35 +5,32 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-
-
+ dispatch("tagappend")}
+>
+
{@html tagIcon}
{@html addTagIcon}
-
-
-
+
+ dispatch("tagappend")} />
+
diff --git a/ts/editor/tag-editor/tag-options-button/TagsSelectedButton.svelte b/ts/editor/tag-editor/tag-options-button/TagsSelectedButton.svelte
new file mode 100644
index 000000000..40ea44566
--- /dev/null
+++ b/ts/editor/tag-editor/tag-options-button/TagsSelectedButton.svelte
@@ -0,0 +1,77 @@
+
+
+
+
+
+ {@html dotsIcon}
+
+
+
+ dispatch("tagselectall")}>
+ {tr.editingTagsSelectAll()} ({getPlatformString(allShortcut)})
+
+ dispatch("tagselectall")}
+ />
+
+ dispatch("tagcopy")}
+ >{tr.editingTagsCopy()} ({getPlatformString(copyShortcut)})
+ dispatch("tagcopy")} />
+
+ dispatch("tagdelete")}
+ >{tr.editingTagsRemove()} ({getPlatformString(
+ removeShortcut,
+ )})
+ dispatch("tagdelete")}
+ />
+
+
+
+
diff --git a/ts/editor/tag-editor/tag-options-button/icons.ts b/ts/editor/tag-editor/tag-options-button/icons.ts
new file mode 100644
index 000000000..76ed81605
--- /dev/null
+++ b/ts/editor/tag-editor/tag-options-button/icons.ts
@@ -0,0 +1,8 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+///
+
+export { default as dotsIcon } from "@mdi/svg/svg/dots-vertical.svg";
+export { default as tagIcon } from "@mdi/svg/svg/tag.svg";
+export { default as addTagIcon } from "@mdi/svg/svg/tag-plus.svg";
diff --git a/ts/editor/tag-editor/tag-options-button/index.ts b/ts/editor/tag-editor/tag-options-button/index.ts
new file mode 100644
index 000000000..34983089f
--- /dev/null
+++ b/ts/editor/tag-editor/tag-options-button/index.ts
@@ -0,0 +1,4 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+export { default as TagOptionsButton } from "./TagOptionsButton.svelte";
diff --git a/ts/editor/tsconfig.json b/ts/editor/tsconfig.json
index 35aa6a900..dd170c8f2 100644
--- a/ts/editor/tsconfig.json
+++ b/ts/editor/tsconfig.json
@@ -7,7 +7,8 @@
"plain-text-input/*",
"rich-text-input/*",
"editor-toolbar/*",
- "tag-editor/*"
+ "tag-editor/*",
+ "tag-editor/tag-options-button/*"
],
"references": [
{ "path": "../components" },
diff --git a/ts/sveltelib/position.ts b/ts/sveltelib/position.ts
index c96bf9c33..8038fafd9 100644
--- a/ts/sveltelib/position.ts
+++ b/ts/sveltelib/position.ts
@@ -2,9 +2,16 @@
// 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, offset, shift } from "@floating-ui/dom";
+import {
+ arrow,
+ autoUpdate,
+ computePosition,
+ inline,
+ offset,
+ shift,
+} from "@floating-ui/dom";
-interface PositionArgs {
+export interface PositionArgs {
/**
* The floating element which is positioned relative to `reference`.
*/
@@ -25,19 +32,41 @@ function position(
args.floating!,
{
middleware: [
+ inline(),
offset(5),
shift({ padding: 5 }),
- arrow({ element: args.arrow }),
+ arrow({ element: args.arrow, padding: 5 }),
],
placement: args.placement,
},
);
- const arrowX = middlewareData.arrow?.x ?? "";
+ 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}px`,
- top: `-5px`,
+ left: arrowX ? `${arrowX}px` : "",
+ top: arrowY ? `${arrowY}px` : "",
+ transform: `rotate(${rotation}deg)`,
});
Object.assign(args.floating!.style, {