From 6ccf75a077c414a985c2d1ba2942338054aeccdf Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 6 Jul 2021 23:37:49 +0200 Subject: [PATCH] Improve space behavior --- ts/editor/TagEditor.svelte | 6 +- ts/editor/TagInput.svelte | 116 ++++++++++++++++++++++++++----------- ts/editor/tags.ts | 8 +-- 3 files changed, 91 insertions(+), 39 deletions(-) diff --git a/ts/editor/TagEditor.svelte b/ts/editor/TagEditor.svelte index adf056485..da1488701 100644 --- a/ts/editor/TagEditor.svelte +++ b/ts/editor/TagEditor.svelte @@ -117,9 +117,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const splitOff = activeName.slice(end); activeName = current; + // await tag to update its name, so it can normalize correctly + await tick(); + appendTagAndFocusAt(index, splitOff); active = null; - await tick(); if (index === active) { @@ -273,7 +275,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } } - function onKeyup(_event: KeyboardEvent): void { + function onKeyup(): void { if (activeName.length === 0) { autocomplete.hide(); } diff --git a/ts/editor/TagInput.svelte b/ts/editor/TagInput.svelte index c21673c82..f3246ce2a 100644 --- a/ts/editor/TagInput.svelte +++ b/ts/editor/TagInput.svelte @@ -15,6 +15,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const dispatch = createEventDispatcher(); + function isCollapsed(): boolean { + return input.selectionStart === input.selectionEnd; + } + function caretAtStart(): boolean { return input.selectionStart === 0 && input.selectionEnd === 0; } @@ -34,10 +38,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html return name.length === 0; } - function normalize(): void { - name = normalizeTagname(name); - } - async function joinWithPreviousTag(event: Event): Promise { const length = input.value.length; dispatch("tagjoinprevious"); @@ -48,12 +48,27 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html event.preventDefault(); } - async function onBackspace(event: KeyboardEvent): Promise { + async function maybeDeleteDelimiter(event: Event, position: number): Promise { + if (position > name.length) { + return; + } + + const nameUptoCaret = name.slice(0, position); + + if (nameUptoCaret.endsWith("::")) { + name = name.slice(0, position - 2) + name.slice(position, name.length); + await tick(); + + setPosition(position - 2); + event.preventDefault(); + } + } + + function onBackspace(event: KeyboardEvent): void { if (caretAtStart()) { joinWithPreviousTag(event); - } else if (name.endsWith("::")) { - name = name.slice(0, -2); - event.preventDefault(); + } else { + maybeDeleteDelimiter(event, input.selectionStart!); } } @@ -67,19 +82,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html event.preventDefault(); } - async function onDelete(event: KeyboardEvent): Promise { + function onDelete(event: KeyboardEvent): void { if (caretAtEnd()) { joinWithNextTag(event); - } else if (name.endsWith("::")) { - name = name.slice(0, -2); - event.preventDefault(); + } else { + maybeDeleteDelimiter(event, input.selectionStart! + 2); } } - function onBlur(event: Event): void { - event.preventDefault(); + function onBlur(): void { + name = normalizeTagname(name); - normalize(); if (name.length === 0) { dispatch("tagdelete"); } @@ -88,54 +101,91 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } function onEnter(event: Event): void { - event.preventDefault(); dispatch("tagsplit", { start: input.selectionStart, end: input.selectionEnd }); event.preventDefault(); } + async function onDelimiter(event: Event, single: boolean = false): Promise { + event.preventDefault(); + + const positionStart = input.selectionStart!; + const positionEnd = input.selectionEnd!; + + const before = name.slice(0, positionStart); + if (before.endsWith("::")) { + event.stopPropagation(); + return; + } + + name = `${before}${single ? ":" : "::"}${name.slice(positionEnd, name.length)}`; + + await tick(); + setPosition(positionStart + (single ? 1 : 2)); + } + + function maybeMovePastDelimiter(event: Event, forwards: boolean): void { + const position = input.selectionStart!; + + const before = name.slice(0, position); + const after = name.slice(position, name.length); + + if (!forwards && before.endsWith("::")) { + setPosition(position - 2); + event.preventDefault(); + } else if (forwards && after.startsWith("::")) { + setPosition(position + 2); + event.preventDefault(); + } + } + function onKeydown(event: KeyboardEvent): void { switch (event.code) { case "Enter": onEnter(event); break; + case "Space": - // TODO - name += "::"; - event.preventDefault(); + onDelimiter(event); break; case "Backspace": - onBackspace(event); + if (isCollapsed()) { + onBackspace(event); + } break; + case "Delete": - onDelete(event); + if (isCollapsed()) { + onDelete(event); + } break; case "ArrowLeft": - if (!caretAtStart()) { - break; - } - normalize(); if (isEmpty()) { joinWithPreviousTag(event); - } else { - event.preventDefault(); + } else if (caretAtStart()) { dispatch("tagmoveprevious"); + event.preventDefault(); + } else if (isCollapsed()) { + maybeMovePastDelimiter(event, false); } break; case "ArrowRight": - if (!caretAtEnd()) { - break; - } if (isEmpty()) { joinWithNextTag(event); - } else { - event.preventDefault(); + } else if (caretAtEnd()) { dispatch("tagmovenext"); + event.preventDefault(); + } else if (isCollapsed()) { + maybeMovePastDelimiter(event, true); } break; } + + if (event.key === ":") { + onDelimiter(event, true); + } } function onPaste(event: ClipboardEvent): void { @@ -175,7 +225,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html tabindex="-1" size="1" on:focus - on:blur={onBlur} + on:blur|preventDefault={onBlur} on:keydown={onKeydown} on:keydown on:keyup diff --git a/ts/editor/tags.ts b/ts/editor/tags.ts index 5a246c3b1..293330a3d 100644 --- a/ts/editor/tags.ts +++ b/ts/editor/tags.ts @@ -5,10 +5,10 @@ export function normalizeTagname(tagname: string): string { let trimmed = tagname.trim(); while (true) { - if (trimmed.startsWith("::")) { - trimmed = trimmed.slice(2).trimStart(); - } else if (trimmed.endsWith("::")) { - trimmed = trimmed.slice(0, -2).trimEnd(); + if (trimmed.startsWith(":")) { + trimmed = trimmed.slice(1).trimStart(); + } else if (trimmed.endsWith(":")) { + trimmed = trimmed.slice(0, -1).trimEnd(); } else { return trimmed; }