From 1ec3741934f95d1411022e5dd86d76d01cd4ece4 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 11 Mar 2022 06:48:49 +0100 Subject: [PATCH] Fix outstanding tag editor issues (#1717) * Start using WithFloating for SelectedTagBadge * Adjust arrow on WithFloating for all directions * Move TagOptionsBadge to its own sub directory * Show autocomplete menu via WithFloating * Have WithFloating return asReference instead of initializing its own reference element * Add html: overflow: hidden for editor * Replace ButtonToolbar with generic div * Move scroll logic into autocomplete item + restrict Popover width to 95vw * Fix autocomplete menu after pressing enter after selecting - should not trigger an autocomplete choose * Overlap TagInput perfectly with Tag * Satisfy formatter * Fix autocompletion item scrolling too much * Remove unused Tag.svelte focusable prop * Remove console.log * Fix floating arrow is a diamond in dark mode * Set autocompletion menu to 80vw --- ftl/core/editing.ftl | 4 + ts/components/Popover.svelte | 1 + ts/components/WithFloating.svelte | 72 ++++--- ts/editor/NoteEditor.svelte | 2 +- ts/editor/editor-base.scss | 4 + ts/editor/editor-toolbar/LatexButton.svelte | 20 +- ts/editor/tag-editor/AutocompleteItem.svelte | 24 +-- ts/editor/tag-editor/SelectedTagBadge.svelte | 80 -------- ts/editor/tag-editor/Tag.svelte | 2 +- ts/editor/tag-editor/TagEditor.svelte | 193 +++++++----------- ts/editor/tag-editor/TagInput.svelte | 6 + ts/editor/tag-editor/TagOptionsBadge.svelte | 24 --- ts/editor/tag-editor/TagSpacer.svelte | 13 ++ ts/editor/tag-editor/TagStickyBadge.svelte | 15 -- ts/editor/tag-editor/WithAutocomplete.svelte | 174 ++++++++-------- ts/editor/tag-editor/icons.ts | 3 - .../TagAddButton.svelte} | 35 ++-- .../TagOptionsButton.svelte | 29 +++ .../TagsSelectedButton.svelte | 77 +++++++ .../tag-editor/tag-options-button/icons.ts | 8 + .../tag-editor/tag-options-button/index.ts | 4 + ts/editor/tsconfig.json | 3 +- ts/sveltelib/position.ts | 41 +++- 23 files changed, 433 insertions(+), 401 deletions(-) delete mode 100644 ts/editor/tag-editor/SelectedTagBadge.svelte delete mode 100644 ts/editor/tag-editor/TagOptionsBadge.svelte create mode 100644 ts/editor/tag-editor/TagSpacer.svelte delete mode 100644 ts/editor/tag-editor/TagStickyBadge.svelte rename ts/editor/tag-editor/{AddTagBadge.svelte => tag-options-button/TagAddButton.svelte} (60%) create mode 100644 ts/editor/tag-editor/tag-options-button/TagOptionsButton.svelte create mode 100644 ts/editor/tag-editor/tag-options-button/TagsSelectedButton.svelte create mode 100644 ts/editor/tag-editor/tag-options-button/icons.ts create mode 100644 ts/editor/tag-editor/tag-options-button/index.ts 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/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} - - + +
+ {#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, {