Compare commits

...

4 commits

Author SHA1 Message Date
ad796dcb57
feat: focus filter bar when pressing F
All checks were successful
Visitenbuch CI / test (push) Successful in 2m21s
Visitenbuch CI / release (push) Has been skipped
2024-05-18 16:30:06 +02:00
9ed5f15b9e
fix: Filterbar does not exclude present filters from URL, text filters dont confirm when defocused 2024-05-18 16:10:31 +02:00
f4f03ab491
fix: humanDate capitalization 2024-05-18 02:00:42 +02:00
34e54fa4af
fix: dont create entry executions if entry is only postponed 2024-05-18 01:33:18 +02:00
10 changed files with 78 additions and 25 deletions

View file

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import { onMount } from "svelte";
import { Debouncer } from "$lib/shared/util"; import { Debouncer } from "$lib/shared/util";
@ -192,8 +193,23 @@
searchDebounce.now(); searchDebounce.now();
} }
} }
function onWindowKeyup(e: KeyboardEvent): void {
// Dont catch keybinds when inputting text
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
if (e.key === "f") {
e.preventDefault();
focusInput();
}
}
onMount(() => {
if (hiddenIds.size === 0) activeFilters = activeFilters;
});
</script> </script>
<svelte:window on:keyup={onWindowKeyup} />
<div class="filterbar-outer"> <div class="filterbar-outer">
<div class="filterbar-inner input input-sm input-bordered"> <div class="filterbar-inner input input-sm input-bordered">
{#each activeFilters as fdata, i} {#each activeFilters as fdata, i}

View file

@ -41,6 +41,15 @@
} }
} : undefined; } : undefined;
function acceptTextInput(e: Event): void {
// @ts-expect-error Event is from HTML input
if (e.target?.value) {
// @ts-expect-error Input value is checked
fdata.selection = { id: null, name: e.target.value };
}
stopEditing(true);
}
$: if (fdata.editing && autocomplete) { $: if (fdata.editing && autocomplete) {
autocomplete.open(); autocomplete.open();
} }
@ -118,14 +127,10 @@
}} }}
on:keypress={(e) => { on:keypress={(e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
// @ts-expect-error Input value is checked acceptTextInput(e);
if (e.target?.value) {
// @ts-expect-error Input value is checked
fdata.selection = { id: null, name: e.target.value };
}
stopEditing(true);
} }
}} }}
on:blur={acceptTextInput}
/> />
{/if} {/if}
{:else} {:else}

View file

@ -1,6 +1,21 @@
<script lang="ts">
import { humanDate } from "$lib/shared/util";
function dateIn(n: number): string {
const date = new Date();
date.setDate(date.getDate() + n);
return humanDate(date);
}
</script>
<div class="join"> <div class="join">
{#each { length: 4 } as _, i} {#each { length: 4 } as _, i}
<button name="todo" class="join-item btn btn-sm" type="submit" value={i}> <button
name="todo"
class="join-item btn btn-sm"
title={i > 0 ? `Eintrag auf ${dateIn(i)} verschieben` : undefined}
type="submit"
value={i}>
{#if i === 0} {#if i === 0}
Notiz Notiz
{:else} {:else}

View file

@ -130,6 +130,10 @@ class SearchQueryComponents {
* Supported search syntax: * Supported search syntax:
* - Negative query `-word` * - Negative query `-word`
* - Exact query `"word"` * - Exact query `"word"`
*
* The last word in a search query is prefix-matched (i.e.
* the search returns all results starting with the given characters).
* This allows for meaningful results in Search-as-you-type applications.
*/ */
export function parseSearchQuery(q: string): SearchQueryComponents { export function parseSearchQuery(q: string): SearchQueryComponents {
const regexpParts = /(-)?(?:"([^"]*)"|([^"\s]+))(?:\s|$)/g; const regexpParts = /(-)?(?:"([^"]*)"|([^"\s]+))(?:\s|$)/g;

View file

@ -77,6 +77,13 @@ function dateDiffInDays(a: Date, b: Date): number {
return Math.round((ts2 - ts1) / MS_PER_DAY); return Math.round((ts2 - ts1) / MS_PER_DAY);
} }
/** Format a date with a human-readable format
* - If the date is within +/- 3 days, output a textual format ("heute", "morgen", "vor 2 Tagen")
* - Otherwise, format it as DD.MM.YYYY (or DD.MM.YYYY, hh:mm if `time=true`)
*
* @param [time=false] Enable time display
* @param [cap=false] Enable capitalized format
*/
export function humanDate(date: Date | string, time = false, cap = false): string { export function humanDate(date: Date | string, time = false, cap = false): string {
const now = new Date(); const now = new Date();
const dt = coerceDate(date); const dt = coerceDate(date);
@ -96,7 +103,7 @@ export function humanDate(date: Date | string, time = false, cap = false): strin
if (diffDays !== 0) { if (diffDays !== 0) {
if (diffDays === 1) return outstr("morgen"); if (diffDays === 1) return outstr("morgen");
if (diffDays === -1) return outstr("gestern"); if (diffDays === -1) return outstr("gestern");
return intl.format(diffDays, "day"); return outstr(intl.format(diffDays, "day"));
} }
if (time) { if (time) {

View file

@ -158,7 +158,11 @@ export function defaultVisitUrl(): string {
}, URL_VISIT); }, URL_VISIT);
} }
export async function moveEntryTodoDate(id: number, nTodoDays: number, init?: TRPCClientInit) { export async function moveEntryTodoDate(
id:number,
nTodoDays: number,
init?: TRPCClientInit,
): Promise<Date | null> {
if (nTodoDays > 0) { if (nTodoDays > 0) {
const entry = await trpc(init).entry.get.query(id); const entry = await trpc(init).entry.get.query(id);
const newDate = new Date(); const newDate = new Date();
@ -171,5 +175,7 @@ export async function moveEntryTodoDate(id: number, nTodoDays: number, init?: TR
date: utcDateToYMD(newDate), date: utcDateToYMD(newDate),
}, },
}); });
return newDate;
} }
return null;
} }

View file

@ -5,7 +5,7 @@ import { superValidate, message } from "sveltekit-superforms";
import { ZUrlEntityId } from "$lib/shared/model/validation"; import { ZUrlEntityId } from "$lib/shared/model/validation";
import { trpc } from "$lib/shared/trpc"; import { trpc } from "$lib/shared/trpc";
import { loadWrap, moveEntryTodoDate } from "$lib/shared/util"; import { humanDate, loadWrap, moveEntryTodoDate } from "$lib/shared/util";
import { SchemaNewExecution } from "./schema"; import { SchemaNewExecution } from "./schema";
@ -22,18 +22,21 @@ export const actions: Actions = {
const done = todoDays === null; const done = todoDays === null;
const nTodoDays = todoDays ? parseInt(todoDays.toString()) : 0; const nTodoDays = todoDays ? parseInt(todoDays.toString()) : 0;
await loadWrap(async () => { if (form.data.text.length > 0) {
await trpc(event).entry.newExecution.mutate({ await trpc(event).entry.newExecution.mutate({
id, id,
old_execution_id: form.data.old_execution_id, old_execution_id: form.data.old_execution_id,
execution: { text: form.data.text, done: todoDays === null }, execution: { text: form.data.text, done: todoDays === null },
}); });
await moveEntryTodoDate(id, nTodoDays, event);
});
if (nTodoDays > 0) {
return message(form, `Eintrag um ${nTodoDays} Tage in die Zukunft verschoben`);
} }
const newTodoDate = await moveEntryTodoDate(id, nTodoDays, event);
if (newTodoDate) {
return message(form, `Eintrag auf ${humanDate(newTodoDate)} verschoben`);
}
if (form.data.text.length > 0) {
return message(form, done ? "Eintrag erledigt" : "Eintrag mit Notiz versehen"); return message(form, done ? "Eintrag erledigt" : "Eintrag mit Notiz versehen");
}
return { form };
}), }),
}; };

View file

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from "./$types"; import type { PageData } from "./$types";
import { superForm } from "sveltekit-superforms"; import { superForm } from "sveltekit-superforms";
@ -33,7 +32,7 @@
label="Eintrag erledigen" label="Eintrag erledigen"
bind:value={$form.text} bind:value={$form.text}
> >
<div class="row c-vlight gap-2"> <div class="row c-vlight gap-2 flex-wrap">
<button class="btn btn-sm btn-primary" type="submit">Erledigt</button> <button class="btn btn-sm btn-primary" type="submit">Erledigt</button>
<EntryTodoButton /> <EntryTodoButton />
</div> </div>

View file

@ -5,7 +5,7 @@ import { superValidate } from "sveltekit-superforms";
import { ZUrlEntityId } from "$lib/shared/model/validation"; import { ZUrlEntityId } from "$lib/shared/model/validation";
import { trpc } from "$lib/shared/trpc"; import { trpc } from "$lib/shared/trpc";
import { loadWrap, moveEntryTodoDate } from "$lib/shared/util"; import { loadWrap } from "$lib/shared/util";
import { SchemaNewExecution } from "../schema"; import { SchemaNewExecution } from "../schema";
@ -29,9 +29,6 @@ export const actions: Actions = {
old_execution_id: form.data.old_execution_id, old_execution_id: form.data.old_execution_id,
}); });
const nTodoDays = todoDays ? parseInt(todoDays.toString()) : 0;
await moveEntryTodoDate(id, nTodoDays, event);
redirect(302, `/entry/${id}`); redirect(302, `/entry/${id}`);
}), }),
}; };

View file

@ -7,7 +7,6 @@
import { superformConfig } from "$lib/shared/util"; import { superformConfig } from "$lib/shared/util";
import EntryBody from "$lib/components/entry/EntryBody.svelte"; import EntryBody from "$lib/components/entry/EntryBody.svelte";
import EntryTodoButton from "$lib/components/ui/EntryTodoButton.svelte";
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte"; import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
import { SchemaNewExecution } from "../schema"; import { SchemaNewExecution } from "../schema";
@ -32,7 +31,9 @@
> >
<div class="row c-vlight gap-2"> <div class="row c-vlight gap-2">
<button class="btn btn-sm btn-primary" type="submit">Speichern</button> <button class="btn btn-sm btn-primary" type="submit">Speichern</button>
<EntryTodoButton /> <button name="todo" class="join-item btn btn-sm" type="submit" value="0">
Notiz
</button>
</div> </div>
</MarkdownInput> </MarkdownInput>
<input name="old_execution_id" type="hidden" bind:value={$form.old_execution_id} /> <input name="old_execution_id" type="hidden" bind:value={$form.old_execution_id} />