feat(editor,quick-entry): page editor with splits, links, attachments and one-line quick entry
This commit is contained in:
1 parent
135a2d9328
commit
7e19684cff
22 files changed
+4067
-10
No files matched your search
@@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
import Calendar from "@lucide/svelte/icons/calendar";
|
||||
import { untrack } from "svelte";
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
import { addDays, today } from "#lib/dates.js";
|
||||
import { formatDate } from "#lib/format.js";
|
||||
import { parseDate } from "./parse.js";
|
||||
import { INPUT } from "./styles.js";
|
||||
|
||||
let {
|
||||
value = $bindable(""),
|
||||
ref = $bindable(null),
|
||||
invalid = false,
|
||||
class: className = "",
|
||||
...rest
|
||||
}: Omit<HTMLInputAttributes, "value"> & {
|
||||
value?: string;
|
||||
ref?: HTMLInputElement | null;
|
||||
invalid?: boolean;
|
||||
} = $props();
|
||||
|
||||
let text = $state(value);
|
||||
let picker = $state<HTMLInputElement | null>(null);
|
||||
|
||||
const parsed = $derived(parseDate(text, today()));
|
||||
const shown = $derived(parsed ?? value);
|
||||
const label = $derived(
|
||||
shown
|
||||
? `${formatDate(shown, "day")}${shown.slice(0, 4) === today().slice(0, 4) ? "" : ` ${shown.slice(0, 4)}`}`
|
||||
: ""
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
const next = value;
|
||||
untrack(() => {
|
||||
if (next && next !== parseDate(text)) {
|
||||
text = next;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function handleInput(event: Event & { currentTarget: HTMLInputElement }) {
|
||||
text = event.currentTarget.value;
|
||||
const next = parseDate(text);
|
||||
if (next) {
|
||||
value = next;
|
||||
}
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
if (parsed) {
|
||||
text = parsed;
|
||||
value = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
function handleKey(event: KeyboardEvent) {
|
||||
if (
|
||||
(event.key === "ArrowUp" || event.key === "ArrowDown") &&
|
||||
!(event.altKey || event.ctrlKey || event.metaKey)
|
||||
) {
|
||||
event.preventDefault();
|
||||
const step =
|
||||
(event.key === "ArrowUp" ? 1 : -1) * (event.shiftKey ? 7 : 1);
|
||||
value = addDays(parsed ?? value ?? today(), step);
|
||||
text = value;
|
||||
}
|
||||
}
|
||||
|
||||
function openPicker() {
|
||||
picker?.showPicker?.();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative {className}">
|
||||
<input
|
||||
bind:this={ref}
|
||||
{...rest}
|
||||
aria-invalid={invalid || (text.trim() !== "" && !parsed) || undefined}
|
||||
autocomplete="off"
|
||||
class="{INPUT} num pr-28"
|
||||
onblur={handleBlur}
|
||||
oninput={handleInput}
|
||||
onkeydown={handleKey}
|
||||
spellcheck="false"
|
||||
value={text}
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 right-9 flex items-center text-[12px] text-cladd-fg-soft"
|
||||
>{label}</span
|
||||
>
|
||||
<button
|
||||
aria-label="Pick a date"
|
||||
class="absolute inset-y-0 right-0 grid w-8 place-items-center text-cladd-fg-soft hover:text-cladd-fg"
|
||||
onclick={openPicker}
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<Calendar class="size-3.5" />
|
||||
</button>
|
||||
<input
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute right-0 bottom-0 h-0 w-0 opacity-0"
|
||||
onchange={({ currentTarget }) => {
|
||||
({ value } = currentTarget);
|
||||
text = value;
|
||||
}}
|
||||
tabindex="-1"
|
||||
type="date"
|
||||
{value}
|
||||
bind:this={picker}
|
||||
>
|
||||
</div>
|
||||
Reference in new issue
Block a user