feat(editor,quick-entry): page editor with splits, links, attachments and one-line quick entry

This commit is contained in:
hh committed 2026-10-16 03:25:02 +00:00
1 parent 135a2d9328
commit 7e19684cff
22 files changed
+4067 -10

No files matched your search

+32 -5
View File
@@ -1,3 +1,5 @@
import { getContext, setContext, untrack } from "svelte";
export interface Binding {
group?: string;
hidden?: boolean;
@@ -11,6 +13,16 @@ const SEQUENCE_MS = 1200;
const EDITABLE =
"input, textarea, select, [contenteditable=''], [contenteditable='true']";
function baseKey(event: KeyboardEvent): string {
if (event.altKey && event.code.startsWith("Key")) {
return event.code.slice(3).toLowerCase();
}
if (event.altKey && event.code.startsWith("Digit")) {
return event.code.slice(5);
}
return event.key === " " ? "Space" : event.key;
}
function token(event: KeyboardEvent): string {
const parts: string[] = [];
if (event.ctrlKey || event.metaKey) {
@@ -19,7 +31,7 @@ function token(event: KeyboardEvent): string {
if (event.altKey) {
parts.push("alt");
}
const key = event.key === " " ? "Space" : event.key;
const key = baseKey(event);
if (event.shiftKey && key.length > 1) {
parts.push("shift");
}
@@ -32,16 +44,24 @@ export function isEditable(target: EventTarget | null): boolean {
}
class Keys {
layers = $state<Binding[][]>([]);
layers = $state.raw<Binding[][]>([]);
pending = $state("");
sheetOpen = $state(false);
private timer: ReturnType<typeof setTimeout> | undefined;
all = $derived(this.layers.flat());
register(bindings: Binding[]): () => void {
private readonly depths = new WeakMap<Binding[], number>();
register(bindings: Binding[], depth = 0): () => void {
const layer = bindings;
this.layers = [...this.layers, layer];
this.depths.set(layer, depth);
const above = this.layers.findIndex(
(l) => (this.depths.get(l) ?? 0) > depth
);
const next = [...this.layers];
next.splice(above < 0 ? next.length : above, 0, layer);
this.layers = next;
return () => {
this.layers = this.layers.filter((l) => l !== layer);
};
@@ -99,8 +119,15 @@ class Keys {
export const keys = new Keys();
const DEPTH = Symbol("keys-depth");
export function useKeys(bindings: () => Binding[]) {
$effect(() => keys.register(bindings()));
const depth = getContext<number | undefined>(DEPTH) ?? 0;
setContext(DEPTH, depth + 1);
$effect(() => {
const list = bindings();
return untrack(() => keys.register(list, depth));
});
}
const APPLE = /Mac|iPhone|iPad/;