From 1e907771d35a71c11d5d51cea1101b3587097fcd Mon Sep 17 00:00:00 2001 From: limina1 Date: Fri, 25 Jul 2025 18:02:14 -0400 Subject: [PATCH 1/8] feat: publish array of notes --- src/routes/new/compose/+page.svelte | 42 ++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/routes/new/compose/+page.svelte b/src/routes/new/compose/+page.svelte index 5c5ff5c..73eb646 100644 --- a/src/routes/new/compose/+page.svelte +++ b/src/routes/new/compose/+page.svelte @@ -4,15 +4,15 @@ import ZettelEditor from "$lib/components/ZettelEditor.svelte"; import { goto } from "$app/navigation"; import { nip19 } from "nostr-tools"; - import { publishZettel } from "$lib/services/publisher"; + import { publishMultipleZettels } from "$lib/services/publisher"; let content = $state(""); let showPreview = $state(false); let isPublishing = $state(false); - let publishResult = $state<{ - success: boolean; - eventId?: string; - error?: string; + let publishResults = $state<{ + successCount: number; + total: number; + errors: string[]; } | null>(null); // Handle content changes from ZettelEditor @@ -27,20 +27,23 @@ async function handlePublish() { isPublishing = true; - publishResult = null; + publishResults = null; - const result = await publishZettel({ + const results = await publishMultipleZettels({ content, - onSuccess: (eventId) => { - publishResult = { success: true, eventId }; - const nevent = nip19.neventEncode({ id: eventId }); - goto(`/events?id=${nevent}`); - }, onError: (error) => { - publishResult = { success: false, error }; + // Only used for catastrophic errors + publishResults = { successCount: 0, total: 0, errors: [error] }; }, }); + const successCount = results.filter(r => r.success).length; + const errors = results.filter(r => !r.success && r.error).map(r => r.error!); + publishResults = { + successCount, + total: results.length, + errors, + }; isPublishing = false; } @@ -81,16 +84,19 @@ - {#if publishResult} - {#if publishResult.success} + {#if publishResults} + {#if publishResults.successCount === publishResults.total} Success! - Event published successfully. Event ID: {publishResult.eventId} + {publishResults.successCount} events published. {:else} - Error! - {publishResult.error} + Some events failed to publish. + {publishResults.successCount} of {publishResults.total} events published.
+ {#each publishResults.errors as error} +
{error}
+ {/each}
{/if} {/if} From d154d14e8fa177101845e504e96040c7ec8d4ffa Mon Sep 17 00:00:00 2001 From: limina1 Date: Fri, 25 Jul 2025 18:57:47 -0400 Subject: [PATCH 2/8] feat: implement multi-note publishing with tag extraction and NIP-19 encoding - Add publishMultipleZettels function that publishes all AsciiDoc sections as separate events - Update compose page to use multi-note publishing instead of single note - Add console logging for e/a tags, nevent, and naddr for debugging - Fix NDKFilter import issues by creating custom Filter type - Display count of published events instead of redirecting to events page --- src/lib/services/publisher.ts | 91 +++++++++++++++++++++++++++++++++++ src/lib/utils/event_search.ts | 5 +- src/lib/utils/nostrUtils.ts | 5 +- src/lib/utils/search_types.ts | 18 ++++++- 4 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/lib/services/publisher.ts b/src/lib/services/publisher.ts index 4bfc033..3d5e9fe 100644 --- a/src/lib/services/publisher.ts +++ b/src/lib/services/publisher.ts @@ -3,6 +3,7 @@ import { ndkInstance } from "../ndk.ts"; import { getMimeTags } from "../utils/mime.ts"; import { parseAsciiDocSections } from "../utils/ZettelParser.ts"; import { NDKRelaySet, NDKEvent } from "@nostr-dev-kit/ndk"; +import { nip19 } from "nostr-tools"; export interface PublishResult { success: boolean; @@ -103,6 +104,96 @@ export async function publishZettel( } } +/** + * Publishes all AsciiDoc sections as separate Nostr events + * @param options - Publishing options + * @returns Promise resolving to array of publish results + */ +export async function publishMultipleZettels( + options: PublishOptions, +): Promise { + const { content, kind = 30041, onError } = options; + + if (!content.trim()) { + const error = 'Please enter some content'; + onError?.(error); + return [{ success: false, error }]; + } + + const ndk = get(ndkInstance); + if (!ndk?.activeUser) { + const error = 'Please log in first'; + onError?.(error); + return [{ success: false, error }]; + } + + try { + const sections = parseAsciiDocSections(content, 2); + if (sections.length === 0) { + throw new Error('No valid sections found in content'); + } + + const allRelayUrls = Array.from(ndk.pool?.relays.values() || []).map((r) => r.url); + if (allRelayUrls.length === 0) { + throw new Error('No relays available in NDK pool'); + } + const relaySet = NDKRelaySet.fromRelayUrls(allRelayUrls, ndk); + + const results: PublishResult[] = []; + const publishedEvents: NDKEvent[] = []; + for (const section of sections) { + const title = section.title; + const cleanContent = section.content; + const sectionTags = section.tags || []; + const dTag = generateDTag(title); + const [mTag, MTag] = getMimeTags(kind); + const tags: string[][] = [["d", dTag], mTag, MTag, ["title", title]]; + if (sectionTags) { + tags.push(...sectionTags); + } + const ndkEvent = new NDKEvent(ndk); + ndkEvent.kind = kind; + ndkEvent.created_at = Math.floor(Date.now() / 1000); + ndkEvent.tags = tags; + ndkEvent.content = cleanContent; + ndkEvent.pubkey = ndk.activeUser.pubkey; + try { + await ndkEvent.sign(); + const publishedToRelays = await ndkEvent.publish(relaySet); + if (publishedToRelays.size > 0) { + results.push({ success: true, eventId: ndkEvent.id }); + publishedEvents.push(ndkEvent); + } else { + results.push({ success: false, error: 'Failed to publish to any relays' }); + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error'; + results.push({ success: false, error: errorMessage }); + } + } + // Debug: extract and log 'e' and 'a' tags from all published events + publishedEvents.forEach(ev => { + // Extract d-tag from tags + const dTagEntry = ev.tags.find(t => t[0] === 'd'); + const dTag = dTagEntry ? dTagEntry[1] : ''; + const aTag = `${ev.kind}:${ev.pubkey}:${dTag}`; + console.log(`Event ${ev.id} tags:`); + console.log(' e:', ev.id); + console.log(' a:', aTag); + // Print nevent and naddr using nip19 + const nevent = nip19.neventEncode({ id: ev.id }); + const naddr = nip19.naddrEncode({ kind: ev.kind, pubkey: ev.pubkey, identifier: dTag }); + console.log(' nevent:', nevent); + console.log(' naddr:', naddr); + }); + return results; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + onError?.(errorMessage); + return [{ success: false, error: errorMessage }]; + } +} + function generateDTag(title: string): string { return title .toLowerCase() diff --git a/src/lib/utils/event_search.ts b/src/lib/utils/event_search.ts index 25319c0..1d5537d 100644 --- a/src/lib/utils/event_search.ts +++ b/src/lib/utils/event_search.ts @@ -1,7 +1,8 @@ import { ndkInstance } from "../ndk.ts"; import { fetchEventWithFallback } from "./nostrUtils.ts"; import { nip19 } from "nostr-tools"; -import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk"; +import { NDKEvent } from "@nostr-dev-kit/ndk"; +import type { Filter } from "./search_types.ts"; import { get } from "svelte/store"; import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts"; import { TIMEOUTS, VALIDATION } from "./search_constants.ts"; @@ -12,7 +13,7 @@ import { TIMEOUTS, VALIDATION } from "./search_constants.ts"; export async function searchEvent(query: string): Promise { // Clean the query and normalize to lowercase const cleanedQuery = query.replace(/^nostr:/, "").toLowerCase(); - let filterOrId: NDKFilter | string = cleanedQuery; + let filterOrId: Filter | string = cleanedQuery; // If it's a valid hex string, try as event id first, then as pubkey (profile) if ( diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 91d3309..14b04d8 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -3,7 +3,8 @@ import { nip19 } from "nostr-tools"; import { ndkInstance } from "../ndk.ts"; import { npubCache } from "./npubCache.ts"; import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk"; -import type { NDKFilter, NDKKind, NostrEvent } from "@nostr-dev-kit/ndk"; +import type { NDKKind, NostrEvent } from "@nostr-dev-kit/ndk"; +import type { Filter } from "./search_types.ts"; import { communityRelays, secondaryRelays } from "../consts.ts"; import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts"; import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk"; @@ -436,7 +437,7 @@ Promise.prototype.withTimeout = function ( */ export async function fetchEventWithFallback( ndk: NDK, - filterOrId: string | NDKFilter, + filterOrId: string | Filter, timeoutMs: number = 3000, ): Promise { // Use both inbox and outbox relays for better event discovery diff --git a/src/lib/utils/search_types.ts b/src/lib/utils/search_types.ts index 134ceff..c187c4e 100644 --- a/src/lib/utils/search_types.ts +++ b/src/lib/utils/search_types.ts @@ -1,4 +1,18 @@ -import { NDKEvent, NDKFilter, NDKSubscription } from "@nostr-dev-kit/ndk"; +import { NDKEvent, NDKSubscription } from "@nostr-dev-kit/ndk"; + +/** + * Nostr filter interface + */ +export interface Filter { + ids?: string[]; + authors?: string[]; + kinds?: number[]; + since?: number; + until?: number; + limit?: number; + search?: string; + [key: string]: any; +} /** * Extended NostrProfile interface for search results @@ -45,7 +59,7 @@ export type SearchSubscriptionType = "d" | "t" | "n"; * Search filter configuration */ export interface SearchFilter { - filter: NDKFilter; + filter: Filter; subscriptionType: string; } From 96e2e161aaad49d53105a8fbf4e53657f77f90a0 Mon Sep 17 00:00:00 2001 From: silberengel Date: Sun, 27 Jul 2025 09:56:48 +0200 Subject: [PATCH 3/8] fixed package errors and async debounce --- deno.lock | 426 +-------------------------- package-lock.json | 711 ++++++++++++++++++++++------------------------ package.json | 5 +- src/lib/utils.ts | 26 ++ vite.config.ts | 12 + 5 files changed, 379 insertions(+), 801 deletions(-) diff --git a/deno.lock b/deno.lock index 35676a3..ef86772 100644 --- a/deno.lock +++ b/deno.lock @@ -8,11 +8,6 @@ "npm:@nostr-dev-kit/ndk@^2.14.32": "2.14.32_nostr-tools@2.15.1__typescript@5.8.3_typescript@5.8.3", "npm:@playwright/test@^1.54.1": "1.54.1", "npm:@popperjs/core@2.11": "2.11.8", - "npm:@sveltejs/adapter-auto@^6.0.1": "6.0.1_@sveltejs+kit@2.25.1__@sveltejs+vite-plugin-svelte@6.1.0___svelte@5.36.8____acorn@8.15.0___vite@7.0.5____@types+node@24.0.15____picomatch@4.0.3___@types+node@24.0.15__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__acorn@8.15.0__@types+node@24.0.15_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15", - "npm:@sveltejs/adapter-node@^5.2.13": "5.2.13_@sveltejs+kit@2.25.1__@sveltejs+vite-plugin-svelte@6.1.0___svelte@5.36.8____acorn@8.15.0___vite@7.0.5____@types+node@24.0.15____picomatch@4.0.3___@types+node@24.0.15__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__acorn@8.15.0__@types+node@24.0.15_rollup@4.45.1_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15", - "npm:@sveltejs/adapter-static@3": "3.0.8_@sveltejs+kit@2.25.1__@sveltejs+vite-plugin-svelte@6.1.0___svelte@5.36.8____acorn@8.15.0___vite@7.0.5____@types+node@24.0.15____picomatch@4.0.3___@types+node@24.0.15__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__acorn@8.15.0__@types+node@24.0.15_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15", - "npm:@sveltejs/kit@^2.25.0": "2.25.1_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_acorn@8.15.0_@types+node@24.0.15", - "npm:@sveltejs/vite-plugin-svelte@^6.1.0": "6.1.0_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15", "npm:@tailwindcss/forms@0.5": "0.5.10_tailwindcss@3.4.17__postcss@8.5.6", "npm:@tailwindcss/typography@0.5": "0.5.16_tailwindcss@3.4.17__postcss@8.5.6", "npm:@types/d3@^7.4.3": "7.4.3", @@ -50,9 +45,7 @@ "npm:tailwind-merge@^3.3.1": "3.3.1", "npm:tailwindcss@^3.4.17": "3.4.17_postcss@8.5.6", "npm:tslib@2.8": "2.8.1", - "npm:typescript@^5.8.3": "5.8.3", - "npm:vite@^7.0.5": "7.0.5_@types+node@24.0.15_picomatch@4.0.3", - "npm:vitest@^3.1.3": "3.2.4_@types+node@24.0.15_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3" + "npm:typescript@^5.8.3": "5.8.3" }, "npm": { "@alloc/quick-lru@5.2.0": { @@ -441,38 +434,9 @@ ], "bin": true }, - "@polka/url@1.0.0-next.29": { - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" - }, "@popperjs/core@2.11.8": { "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, - "@rollup/plugin-commonjs@28.0.6_rollup@4.45.1_picomatch@4.0.3": { - "integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==", - "dependencies": [ - "@rollup/pluginutils", - "commondir", - "estree-walker@2.0.2", - "fdir", - "is-reference@1.2.1", - "magic-string", - "picomatch@4.0.3", - "rollup" - ], - "optionalPeers": [ - "rollup" - ] - }, - "@rollup/plugin-json@6.1.0_rollup@4.45.1": { - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "dependencies": [ - "@rollup/pluginutils", - "rollup" - ], - "optionalPeers": [ - "rollup" - ] - }, "@rollup/plugin-node-resolve@15.3.1": { "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", "dependencies": [ @@ -483,25 +447,11 @@ "resolve" ] }, - "@rollup/plugin-node-resolve@16.0.1_rollup@4.45.1": { - "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", - "dependencies": [ - "@rollup/pluginutils", - "@types/resolve", - "deepmerge", - "is-module", - "resolve", - "rollup" - ], - "optionalPeers": [ - "rollup" - ] - }, "@rollup/pluginutils@5.2.0_rollup@4.45.1": { "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", "dependencies": [ "@types/estree", - "estree-walker@2.0.2", + "estree-walker", "picomatch@4.0.3", "rollup" ], @@ -639,71 +589,6 @@ "acorn@8.15.0" ] }, - "@sveltejs/adapter-auto@6.0.1_@sveltejs+kit@2.25.1__@sveltejs+vite-plugin-svelte@6.1.0___svelte@5.36.8____acorn@8.15.0___vite@7.0.5____@types+node@24.0.15____picomatch@4.0.3___@types+node@24.0.15__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__acorn@8.15.0__@types+node@24.0.15_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15": { - "integrity": "sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ==", - "dependencies": [ - "@sveltejs/kit" - ] - }, - "@sveltejs/adapter-node@5.2.13_@sveltejs+kit@2.25.1__@sveltejs+vite-plugin-svelte@6.1.0___svelte@5.36.8____acorn@8.15.0___vite@7.0.5____@types+node@24.0.15____picomatch@4.0.3___@types+node@24.0.15__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__acorn@8.15.0__@types+node@24.0.15_rollup@4.45.1_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15": { - "integrity": "sha512-yS2TVFmIrxjGhYaV5/iIUrJ3mJl6zjaYn0lBD70vTLnYvJeqf3cjvLXeXCUCuYinhSBoyF4DpfGla49BnIy7sQ==", - "dependencies": [ - "@rollup/plugin-commonjs", - "@rollup/plugin-json", - "@rollup/plugin-node-resolve@16.0.1_rollup@4.45.1", - "@sveltejs/kit", - "rollup" - ] - }, - "@sveltejs/adapter-static@3.0.8_@sveltejs+kit@2.25.1__@sveltejs+vite-plugin-svelte@6.1.0___svelte@5.36.8____acorn@8.15.0___vite@7.0.5____@types+node@24.0.15____picomatch@4.0.3___@types+node@24.0.15__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__acorn@8.15.0__@types+node@24.0.15_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15": { - "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", - "dependencies": [ - "@sveltejs/kit" - ] - }, - "@sveltejs/kit@2.25.1_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_acorn@8.15.0_@types+node@24.0.15": { - "integrity": "sha512-8H+fxDEp7Xq6tLFdrGdS5fLu6ONDQQ9DgyjboXpChubuFdfH9QoFX09ypssBpyNkJNZFt9eW3yLmXIc9CesPCA==", - "dependencies": [ - "@sveltejs/acorn-typescript", - "@sveltejs/vite-plugin-svelte", - "@types/cookie", - "acorn@8.15.0", - "cookie", - "devalue", - "esm-env", - "kleur", - "magic-string", - "mrmime", - "sade", - "set-cookie-parser", - "sirv", - "svelte", - "vite" - ], - "bin": true - }, - "@sveltejs/vite-plugin-svelte-inspector@5.0.0_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___@types+node@24.0.15___picomatch@4.0.3__@types+node@24.0.15_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15": { - "integrity": "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==", - "dependencies": [ - "@sveltejs/vite-plugin-svelte", - "debug", - "svelte", - "vite" - ] - }, - "@sveltejs/vite-plugin-svelte@6.1.0_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15": { - "integrity": "sha512-+U6lz1wvGEG/BvQyL4z/flyNdQ9xDNv5vrh+vWBWTHaebqT0c9RNggpZTo/XSPoHsSCWBlYaTlRX8pZ9GATXCw==", - "dependencies": [ - "@sveltejs/vite-plugin-svelte-inspector", - "debug", - "deepmerge", - "kleur", - "magic-string", - "svelte", - "vite", - "vitefu" - ] - }, "@svgdotjs/svg.draggable.js@3.0.6_@svgdotjs+svg.js@3.2.4": { "integrity": "sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==", "dependencies": [ @@ -749,15 +634,6 @@ "tailwindcss" ] }, - "@types/chai@5.2.2": { - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", - "dependencies": [ - "@types/deep-eql" - ] - }, - "@types/cookie@0.6.0": { - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" - }, "@types/d3-array@3.2.1": { "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" }, @@ -918,9 +794,6 @@ "@types/d3-zoom" ] }, - "@types/deep-eql@4.0.2": { - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==" - }, "@types/estree@1.0.8": { "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, @@ -957,64 +830,6 @@ "@types/resolve@1.20.2": { "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" }, - "@vitest/expect@3.2.4": { - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "dependencies": [ - "@types/chai", - "@vitest/spy", - "@vitest/utils", - "chai", - "tinyrainbow" - ] - }, - "@vitest/mocker@3.2.4_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15": { - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dependencies": [ - "@vitest/spy", - "estree-walker@3.0.3", - "magic-string", - "vite" - ], - "optionalPeers": [ - "vite" - ] - }, - "@vitest/pretty-format@3.2.4": { - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "dependencies": [ - "tinyrainbow" - ] - }, - "@vitest/runner@3.2.4": { - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "dependencies": [ - "@vitest/utils", - "pathe", - "strip-literal" - ] - }, - "@vitest/snapshot@3.2.4": { - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "dependencies": [ - "@vitest/pretty-format", - "magic-string", - "pathe" - ] - }, - "@vitest/spy@3.2.4": { - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "dependencies": [ - "tinyspy" - ] - }, - "@vitest/utils@3.2.4": { - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", - "dependencies": [ - "@vitest/pretty-format", - "loupe", - "tinyrainbow" - ] - }, "@yr/monotone-cubic-spline@1.0.3": { "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" }, @@ -1119,9 +934,6 @@ "assert-never@1.4.0": { "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==" }, - "assertion-error@2.0.1": { - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==" - }, "async@3.2.6": { "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" }, @@ -1185,9 +997,6 @@ ], "bin": true }, - "cac@6.7.14": { - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==" - }, "call-bind-apply-helpers@1.0.2": { "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": [ @@ -1214,16 +1023,6 @@ "caniuse-lite@1.0.30001727": { "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==" }, - "chai@5.2.1": { - "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", - "dependencies": [ - "assertion-error", - "check-error", - "deep-eql", - "loupe", - "pathval" - ] - }, "chalk@4.1.2": { "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": [ @@ -1240,9 +1039,6 @@ "is-regex" ] }, - "check-error@2.1.1": { - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==" - }, "chokidar@3.6.0": { "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": [ @@ -1301,9 +1097,6 @@ "commander@7.2.0": { "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" }, - "commondir@1.0.1": { - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, "concat-map@0.0.1": { "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, @@ -1314,9 +1107,6 @@ "@babel/types" ] }, - "cookie@0.6.0": { - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" - }, "cross-spawn@7.0.6": { "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": [ @@ -1544,9 +1334,6 @@ "decamelize@1.2.0": { "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" }, - "deep-eql@5.0.2": { - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==" - }, "deep-is@0.1.4": { "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, @@ -1559,9 +1346,6 @@ "robust-predicates" ] }, - "devalue@5.1.1": { - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==" - }, "dexie@4.0.11": { "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==" }, @@ -1613,48 +1397,12 @@ "es-errors@1.3.0": { "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, - "es-module-lexer@1.7.0": { - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" - }, "es-object-atoms@1.1.1": { "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dependencies": [ "es-errors" ] }, - "esbuild@0.25.7": { - "integrity": "sha512-daJB0q2dmTzo90L9NjRaohhRWrCzYxWNFTjEi72/h+p5DcY3yn4MacWfDakHmaBaDzDiuLJsCh0+6LK/iX+c+Q==", - "optionalDependencies": [ - "@esbuild/aix-ppc64", - "@esbuild/android-arm", - "@esbuild/android-arm64", - "@esbuild/android-x64", - "@esbuild/darwin-arm64", - "@esbuild/darwin-x64", - "@esbuild/freebsd-arm64", - "@esbuild/freebsd-x64", - "@esbuild/linux-arm", - "@esbuild/linux-arm64", - "@esbuild/linux-ia32", - "@esbuild/linux-loong64", - "@esbuild/linux-mips64el", - "@esbuild/linux-ppc64", - "@esbuild/linux-riscv64", - "@esbuild/linux-s390x", - "@esbuild/linux-x64", - "@esbuild/netbsd-arm64", - "@esbuild/netbsd-x64", - "@esbuild/openbsd-arm64", - "@esbuild/openbsd-x64", - "@esbuild/openharmony-arm64", - "@esbuild/sunos-x64", - "@esbuild/win32-arm64", - "@esbuild/win32-ia32", - "@esbuild/win32-x64" - ], - "scripts": true, - "bin": true - }, "escalade@3.2.0": { "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, @@ -1770,18 +1518,9 @@ "estree-walker@2.0.2": { "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, - "estree-walker@3.0.3": { - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dependencies": [ - "@types/estree" - ] - }, "esutils@2.0.3": { "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, - "expect-type@1.2.2": { - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==" - }, "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, @@ -1861,7 +1600,7 @@ "flowbite-datepicker@1.3.2": { "integrity": "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==", "dependencies": [ - "@rollup/plugin-node-resolve@15.3.1", + "@rollup/plugin-node-resolve", "flowbite@2.5.2" ] }, @@ -2128,12 +1867,6 @@ "is-promise@2.2.2": { "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, - "is-reference@1.2.1": { - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dependencies": [ - "@types/estree" - ] - }, "is-reference@3.0.3": { "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "dependencies": [ @@ -2178,9 +1911,6 @@ "js-stringify@1.0.2": { "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==" }, - "js-tokens@9.0.1": { - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==" - }, "js-yaml@4.1.0": { "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": [ @@ -2210,9 +1940,6 @@ "json-buffer" ] }, - "kleur@4.1.5": { - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" - }, "known-css-properties@0.37.0": { "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==" }, @@ -2262,9 +1989,6 @@ "lodash.merge@4.6.2": { "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "loupe@3.1.4": { - "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==" - }, "lru-cache@10.4.3": { "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, @@ -2318,9 +2042,6 @@ "mri@1.2.0": { "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" }, - "mrmime@2.0.1": { - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==" - }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, @@ -2463,12 +2184,6 @@ "minipass" ] }, - "pathe@2.0.3": { - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" - }, - "pathval@2.0.1": { - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==" - }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, @@ -2820,9 +2535,6 @@ "set-blocking@2.0.0": { "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, - "set-cookie-parser@2.7.1": { - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" - }, "shebang-command@2.0.0": { "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dependencies": [ @@ -2832,20 +2544,9 @@ "shebang-regex@3.0.0": { "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "siginfo@2.0.0": { - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" - }, "signal-exit@4.1.0": { "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" }, - "sirv@3.0.1": { - "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", - "dependencies": [ - "@polka/url", - "mrmime", - "totalist" - ] - }, "skin-tone@2.0.0": { "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "dependencies": [ @@ -2858,12 +2559,6 @@ "source-map@0.6.1": { "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, - "stackback@0.0.2": { - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" - }, - "std-env@3.9.0": { - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==" - }, "string-width@4.2.3": { "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": [ @@ -2895,12 +2590,6 @@ "strip-json-comments@3.1.1": { "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, - "strip-literal@3.0.0": { - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "dependencies": [ - "js-tokens" - ] - }, "sucrase@3.35.0": { "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dependencies": [ @@ -2964,7 +2653,7 @@ "clsx", "esm-env", "esrap", - "is-reference@3.0.3", + "is-reference", "locate-character", "magic-string", "zimmerframe" @@ -3069,28 +2758,6 @@ "any-promise" ] }, - "tinybench@2.9.0": { - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==" - }, - "tinyexec@0.3.2": { - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==" - }, - "tinyglobby@0.2.14_picomatch@4.0.3": { - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dependencies": [ - "fdir", - "picomatch@4.0.3" - ] - }, - "tinypool@1.1.1": { - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==" - }, - "tinyrainbow@2.0.0": { - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==" - }, - "tinyspy@4.0.3": { - "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==" - }, "to-regex-range@5.0.1": { "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dependencies": [ @@ -3100,9 +2767,6 @@ "token-stream@1.0.0": { "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" }, - "totalist@3.0.1": { - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" - }, "ts-interface-checker@0.1.13": { "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, @@ -3159,78 +2823,6 @@ "util-deprecate@1.0.2": { "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "vite-node@3.2.4_@types+node@24.0.15": { - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dependencies": [ - "cac", - "debug", - "es-module-lexer", - "pathe", - "vite" - ], - "bin": true - }, - "vite@7.0.5_@types+node@24.0.15_picomatch@4.0.3": { - "integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==", - "dependencies": [ - "@types/node@24.0.15", - "esbuild", - "fdir", - "picomatch@4.0.3", - "postcss", - "rollup", - "tinyglobby" - ], - "optionalDependencies": [ - "fsevents@2.3.3" - ], - "optionalPeers": [ - "@types/node@24.0.15" - ], - "bin": true - }, - "vitefu@1.1.1_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3_@types+node@24.0.15": { - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "dependencies": [ - "vite" - ], - "optionalPeers": [ - "vite" - ] - }, - "vitest@3.2.4_@types+node@24.0.15_vite@7.0.5__@types+node@24.0.15__picomatch@4.0.3": { - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dependencies": [ - "@types/chai", - "@types/node@24.0.15", - "@vitest/expect", - "@vitest/mocker", - "@vitest/pretty-format", - "@vitest/runner", - "@vitest/snapshot", - "@vitest/spy", - "@vitest/utils", - "chai", - "debug", - "expect-type", - "magic-string", - "pathe", - "picomatch@4.0.3", - "std-env", - "tinybench", - "tinyexec", - "tinyglobby", - "tinypool", - "tinyrainbow", - "vite", - "vite-node", - "why-is-node-running" - ], - "optionalPeers": [ - "@types/node@24.0.15" - ], - "bin": true - }, "void-elements@3.1.0": { "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" }, @@ -3244,14 +2836,6 @@ ], "bin": true }, - "why-is-node-running@2.3.0": { - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dependencies": [ - "siginfo", - "stackback" - ], - "bin": true - }, "with@7.0.2": { "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", "dependencies": [ @@ -3421,7 +3005,7 @@ "npm:tailwindcss@^3.4.17", "npm:tslib@2.8", "npm:typescript@^5.8.3", - "npm:vite@^7.0.5", + "npm:vite@^6.3.5", "npm:vitest@^3.1.3" ] } diff --git a/package-lock.json b/package-lock.json index f256933..0fe9c01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,15 @@ { "name": "alexandria", - "version": "0.0.6", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "alexandria", - "version": "0.0.6", + "version": "0.0.2", "dependencies": { + "@noble/curves": "^1.9.4", + "@noble/hashes": "^1.8.0", "@nostr-dev-kit/ndk": "^2.14.32", "@nostr-dev-kit/ndk-cache-dexie": "2.6.x", "@popperjs/core": "2.11.x", @@ -24,33 +26,34 @@ "qrcode": "^1.5.4" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@sveltejs/adapter-auto": "3.x", + "@playwright/test": "^1.54.1", + "@sveltejs/adapter-auto": "^6.0.1", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/adapter-static": "3.x", "@sveltejs/kit": "^2.25.0", - "@sveltejs/vite-plugin-svelte": "5.x", + "@sveltejs/vite-plugin-svelte": "^6.1.0", "@types/d3": "^7.4.3", "@types/he": "1.2.x", - "@types/node": "22.x", + "@types/mathjax": "^0.0.40", + "@types/node": "^24.0.15", "@types/qrcode": "^1.5.5", - "autoprefixer": "10.x", - "eslint-plugin-svelte": "2.x", + "autoprefixer": "^10.4.21", + "eslint-plugin-svelte": "^3.11.0", "flowbite": "2.x", "flowbite-svelte": "0.48.x", "flowbite-svelte-icons": "2.1.x", "playwright": "^1.50.1", - "postcss": "8.x", + "postcss": "^8.5.6", "postcss-load-config": "6.x", - "prettier": "3.x", - "prettier-plugin-svelte": "3.x", - "svelte": "5.x", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "svelte": "^5.36.8", "svelte-check": "4.x", - "tailwind-merge": "^3.3.0", - "tailwindcss": "3.x", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^3.4.17", "tslib": "2.8.x", - "typescript": "5.8.x", - "vite": "6.x", + "typescript": "^5.8.3", + "vite": "^6.3.5", "vitest": "^3.1.3" } }, @@ -153,9 +156,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -165,9 +168,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", - "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", "cpu": [ "ppc64" ], @@ -181,9 +184,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", - "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", "cpu": [ "arm" ], @@ -197,9 +200,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", - "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", "cpu": [ "arm64" ], @@ -213,9 +216,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", - "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", "cpu": [ "x64" ], @@ -229,9 +232,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", - "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", "cpu": [ "arm64" ], @@ -245,9 +248,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", - "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", "cpu": [ "x64" ], @@ -261,9 +264,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", - "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", "cpu": [ "arm64" ], @@ -277,9 +280,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", - "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", "cpu": [ "x64" ], @@ -293,9 +296,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", - "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", "cpu": [ "arm" ], @@ -309,9 +312,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", - "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", "cpu": [ "arm64" ], @@ -325,9 +328,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", - "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", "cpu": [ "ia32" ], @@ -341,9 +344,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", - "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", "cpu": [ "loong64" ], @@ -357,9 +360,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", - "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", "cpu": [ "mips64el" ], @@ -373,9 +376,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", - "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", "cpu": [ "ppc64" ], @@ -389,9 +392,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", - "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", "cpu": [ "riscv64" ], @@ -405,9 +408,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", - "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", "cpu": [ "s390x" ], @@ -421,9 +424,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", - "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", "cpu": [ "x64" ], @@ -437,9 +440,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", - "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", "cpu": [ "arm64" ], @@ -453,9 +456,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", - "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", "cpu": [ "x64" ], @@ -469,9 +472,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", - "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", "cpu": [ "arm64" ], @@ -485,9 +488,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", - "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", "cpu": [ "x64" ], @@ -501,9 +504,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", - "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", "cpu": [ "arm64" ], @@ -517,9 +520,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", - "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", "cpu": [ "x64" ], @@ -533,9 +536,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", - "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", "cpu": [ "arm64" ], @@ -549,9 +552,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", - "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", "cpu": [ "ia32" ], @@ -565,9 +568,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", - "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", "cpu": [ "x64" ], @@ -683,9 +686,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "dev": true, "peer": true, "engines": { @@ -706,9 +709,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "peer": true, "dependencies": { @@ -1168,9 +1171,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", - "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.3.tgz", + "integrity": "sha512-8oQkCTve4H4B4JpmD2FV7fV2ZPTxJHN//bRhCqPUU8v6c5APlxteAXyc7BFaEb4aGpUzrPLU4PoAcGhwmRzZTA==", "cpu": [ "arm" ], @@ -1181,9 +1184,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", - "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.3.tgz", + "integrity": "sha512-StOsmdXHU2hx3UFTTs6yYxCSwSIgLsfjUBICXyWj625M32OOjakXlaZuGKL+jA3Nvv35+hMxrm/64eCoT07SYQ==", "cpu": [ "arm64" ], @@ -1194,9 +1197,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", - "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.3.tgz", + "integrity": "sha512-6CfLF3eqKhCdhK0GUnR5ZS99OFz+dtOeB/uePznLKxjCsk5QjT/V0eSEBb4vj+o/ri3i35MseSEQHCLLAgClVw==", "cpu": [ "arm64" ], @@ -1207,9 +1210,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", - "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.3.tgz", + "integrity": "sha512-QLWyWmAJG9elNTNLdcSXUT/M+J7DhEmvs1XPHYcgYkse3UHf9iWTJ+yTPlKMIetiQnNi+cNp+gY4gvjDpREfKw==", "cpu": [ "x64" ], @@ -1220,9 +1223,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", - "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.3.tgz", + "integrity": "sha512-ZOvBq+5nL0yrZIEo1eq6r7MPvkJ8kC1XATS/yHvcq3WbDNKNKBQ1uIF4hicyzDMoJt72G+sn1nKsFXpifZyRDA==", "cpu": [ "arm64" ], @@ -1233,9 +1236,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", - "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.3.tgz", + "integrity": "sha512-AYvGR07wecEnyYSovyJ71pTOulbNvsrpRpK6i/IM1b0UGX1vFx51afYuPYPxnvE9aCl5xPnhQicEvdIMxClRgQ==", "cpu": [ "x64" ], @@ -1246,9 +1249,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", - "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.3.tgz", + "integrity": "sha512-Yx8Cp38tfRRToVLuIWzBHV25/QPzpUreOPIiUuNV7KahNPurYg2pYQ4l7aYnvpvklO1riX4643bXLvDsYSBIrA==", "cpu": [ "arm" ], @@ -1259,9 +1262,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", - "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.3.tgz", + "integrity": "sha512-4dIYRNxlXGDKnO6qgcda6LxnObPO6r1OBU9HG8F9pAnHHLtfbiOqCzDvkeHknx+5mfFVH4tWOl+h+cHylwsPWA==", "cpu": [ "arm" ], @@ -1272,9 +1275,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", - "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.3.tgz", + "integrity": "sha512-M6uVlWKmhLN7LguLDu6396K1W5IBlAaRonjlHQgc3s4dOGceu0FeBuvbXiUPYvup/6b5Ln7IEX7XNm68DN4vrg==", "cpu": [ "arm64" ], @@ -1285,9 +1288,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", - "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.3.tgz", + "integrity": "sha512-emaYiOTQJUd6fC9a6jcw9zIWtzaUiuBC+vomggaM4In2iOra/lA6IMHlqZqQZr08NYXrOPMVigreLMeSAwv3Uw==", "cpu": [ "arm64" ], @@ -1298,9 +1301,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", - "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.3.tgz", + "integrity": "sha512-3P77T5AQ4UfVRJSrTKLiUZDJ6XsxeP80027bp6mOFh8sevSD038mYuIYFiUtrSJxxgFb+NgRJFF9oIa0rlUsmg==", "cpu": [ "loong64" ], @@ -1310,10 +1313,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", - "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.45.3.tgz", + "integrity": "sha512-/VPH3ZVeSlmCBPhZdx/+4dMXDjaGMhDsWOBo9EwSkGbw2+OAqaslL53Ao2OqCxR0GgYjmmssJ+OoG+qYGE7IBg==", "cpu": [ "ppc64" ], @@ -1324,9 +1327,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", - "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.3.tgz", + "integrity": "sha512-Hs5if0PjROl1MGMmZX3xMAIfqcGxQE2SJWUr/CpDQsOQn43Wq4IvXXxUMWtiY/BrzdqCCJlRgJ5DKxzS3qWkCw==", "cpu": [ "riscv64" ], @@ -1337,9 +1340,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", - "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.3.tgz", + "integrity": "sha512-Qm0WOwh3Lk388+HJFl1ILGbd2iOoQf6yl4fdGqOjBzEA+5JYbLcwd+sGsZjs5pkt8Cr/1G42EiXmlRp9ZeTvFA==", "cpu": [ "riscv64" ], @@ -1350,9 +1353,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", - "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.3.tgz", + "integrity": "sha512-VJdknTaYw+TqXzlh9c7vaVMh/fV2sU8Khfk4a9vAdYXJawpjf6z3U1k7vDWx2IQ9ZOPoOPxgVpDfYOYhxD7QUA==", "cpu": [ "s390x" ], @@ -1363,9 +1366,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", - "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.3.tgz", + "integrity": "sha512-SUDXU5YabLAMl86FpupSQQEWzVG8X0HM+Q/famnJusbPiUgQnTGuSxtxg4UAYgv1ZmRV1nioYYXsgtSokU/7+Q==", "cpu": [ "x64" ], @@ -1376,9 +1379,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", - "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.3.tgz", + "integrity": "sha512-ezmqknOUFgZMN6wW+Avlo4sXF3Frswd+ncrwMz4duyZ5Eqd+dAYgJ+A1MY+12LNZ7XDhCiijJceueYvtnzdviw==", "cpu": [ "x64" ], @@ -1389,9 +1392,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", - "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.3.tgz", + "integrity": "sha512-1YfXoUEE++gIW66zNB9Twd0Ua5xCXpfYppFUxVT/Io5ZT3fO6Se+C/Jvmh3usaIHHyi53t3kpfjydO2GAy5eBA==", "cpu": [ "arm64" ], @@ -1402,9 +1405,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", - "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.3.tgz", + "integrity": "sha512-Iok2YA3PvC163rVZf2Zy81A0g88IUcSPeU5pOilcbICXre2EP1mxn1Db/l09Z/SK1vdSLtpJXAnwGuMOyf5O9g==", "cpu": [ "ia32" ], @@ -1415,9 +1418,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", - "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.3.tgz", + "integrity": "sha512-HwHCH5GQTOeGYP5wBEBXFVhfQecwRl24Rugoqhh8YwGarsU09bHhOKuqlyW4ZolZCan3eTUax7UJbGSmKSM51A==", "cpu": [ "x64" ], @@ -1541,13 +1544,10 @@ } }, "node_modules/@sveltejs/adapter-auto": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz", - "integrity": "sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-6.0.1.tgz", + "integrity": "sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ==", "dev": true, - "dependencies": { - "import-meta-resolve": "^4.1.0" - }, "peerDependencies": { "@sveltejs/kit": "^2.0.0" } @@ -1577,9 +1577,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.25.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.25.1.tgz", - "integrity": "sha512-8H+fxDEp7Xq6tLFdrGdS5fLu6ONDQQ9DgyjboXpChubuFdfH9QoFX09ypssBpyNkJNZFt9eW3yLmXIc9CesPCA==", + "version": "2.26.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.26.1.tgz", + "integrity": "sha512-FwDhHAoXYUGnYndrrEzEYcKdYWpSoRKq4kli29oMe83hLri4/DOGQk3xUgwjDo0LKpSmj5M/Sj29/Ug3wO0Cbg==", "dev": true, "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", @@ -1608,41 +1608,41 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", - "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.0.tgz", + "integrity": "sha512-+U6lz1wvGEG/BvQyL4z/flyNdQ9xDNv5vrh+vWBWTHaebqT0c9RNggpZTo/XSPoHsSCWBlYaTlRX8pZ9GATXCw==", "dev": true, "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0-next.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", - "vitefu": "^1.0.6" + "vitefu": "^1.1.1" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { "svelte": "^5.0.0", - "vite": "^6.0.0" + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", - "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.0.tgz", + "integrity": "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==", "dev": true, "dependencies": { - "debug": "^4.3.7" + "debug": "^4.4.1" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", - "vite": "^6.0.0" + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@tailwindcss/forms": { @@ -1969,13 +1969,19 @@ "dev": true, "peer": true }, + "node_modules/@types/mathjax": { + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@types/mathjax/-/mathjax-0.0.40.tgz", + "integrity": "sha512-rHusx08LCg92WJxrsM3SPjvLTSvK5C+gealtSuhKbEOcUZfWlwigaFoPLf6Dfxhg4oryN5qP9Sj7zOQ4HYXINw==", + "dev": true + }, "node_modules/@types/node": { - "version": "22.16.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.4.tgz", - "integrity": "sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "dev": true, "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/qrcode": { @@ -3160,9 +3166,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.187", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", - "integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==", + "version": "1.5.191", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz", + "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==", "dev": true }, "node_modules/emoji-regex": { @@ -3209,9 +3215,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", - "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "dev": true, "hasInstallScript": true, "bin": { @@ -3221,32 +3227,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.6", - "@esbuild/android-arm": "0.25.6", - "@esbuild/android-arm64": "0.25.6", - "@esbuild/android-x64": "0.25.6", - "@esbuild/darwin-arm64": "0.25.6", - "@esbuild/darwin-x64": "0.25.6", - "@esbuild/freebsd-arm64": "0.25.6", - "@esbuild/freebsd-x64": "0.25.6", - "@esbuild/linux-arm": "0.25.6", - "@esbuild/linux-arm64": "0.25.6", - "@esbuild/linux-ia32": "0.25.6", - "@esbuild/linux-loong64": "0.25.6", - "@esbuild/linux-mips64el": "0.25.6", - "@esbuild/linux-ppc64": "0.25.6", - "@esbuild/linux-riscv64": "0.25.6", - "@esbuild/linux-s390x": "0.25.6", - "@esbuild/linux-x64": "0.25.6", - "@esbuild/netbsd-arm64": "0.25.6", - "@esbuild/netbsd-x64": "0.25.6", - "@esbuild/openbsd-arm64": "0.25.6", - "@esbuild/openbsd-x64": "0.25.6", - "@esbuild/openharmony-arm64": "0.25.6", - "@esbuild/sunos-x64": "0.25.6", - "@esbuild/win32-arm64": "0.25.6", - "@esbuild/win32-ia32": "0.25.6", - "@esbuild/win32-x64": "0.25.6" + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, "node_modules/escalade": { @@ -3271,9 +3277,9 @@ } }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, "peer": true, "dependencies": { @@ -3283,8 +3289,8 @@ "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -3331,47 +3337,31 @@ } } }, - "node_modules/eslint-compat-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", - "dev": true, - "dependencies": { - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, "node_modules/eslint-plugin-svelte": { - "version": "2.46.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz", - "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.11.0.tgz", + "integrity": "sha512-KliWlkieHyEa65aQIkRwUFfHzT5Cn4u3BQQsu3KlkJOs7c1u7ryn84EWaOjEzilbKgttT4OfBURA8Uc4JBSQIw==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@jridgewell/sourcemap-codec": "^1.4.15", - "eslint-compat-utils": "^0.5.1", + "@eslint-community/eslint-utils": "^4.6.1", + "@jridgewell/sourcemap-codec": "^1.5.0", "esutils": "^2.0.3", - "known-css-properties": "^0.35.0", - "postcss": "^8.4.38", + "globals": "^16.0.0", + "known-css-properties": "^0.37.0", + "postcss": "^8.4.49", "postcss-load-config": "^3.1.4", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.1.0", - "semver": "^7.6.2", - "svelte-eslint-parser": "^0.43.0" + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.3.0" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "eslint": "^8.57.1 || ^9.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { @@ -3380,6 +3370,18 @@ } } }, + "node_modules/eslint-plugin-svelte/node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-svelte/node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -3418,25 +3420,11 @@ } } }, - "node_modules/eslint-plugin-svelte/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -3453,7 +3441,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3472,7 +3459,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "peer": true, "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -4080,16 +4066,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4358,9 +4334,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", - "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", "dev": true }, "node_modules/levn": { @@ -4450,9 +4426,9 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/loupe": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", - "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", "dev": true }, "node_modules/lru-cache": { @@ -4643,9 +4619,9 @@ } }, "node_modules/nostr-tools": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.15.1.tgz", - "integrity": "sha512-LpetHDR9ltnkpJDkva/SONgyKBbsoV+5yLB8DWc0/U3lCWGtoWJw6Nbc2vR2Ai67RIQYrBQeZLyMlhwVZRK/9A==", + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.15.2.tgz", + "integrity": "sha512-utmqVVS4HMDiwhIgI6Cr+KqA4aUhF3Sb755iO/qCiqxc5H9JW/9Z3N1RO/jKWpjP6q/Vx0lru7IYuiPvk+2/ng==", "dependencies": { "@noble/ciphers": "^0.5.1", "@noble/curves": "1.2.0", @@ -5112,19 +5088,29 @@ } }, "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=18.0" }, "peerDependencies": { - "postcss": "^8.3.3" + "postcss": "^8.4.31" } }, "node_modules/postcss-scss": { @@ -5557,9 +5543,9 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", - "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.3.tgz", + "integrity": "sha512-STwyHZF3G+CrmZhB+qDiROq9s8B5PrOCYN6dtmOvwz585XBnyeHk1GTEhHJtUVb355/9uZhOazyVclTt5uahzA==", "dev": true, "dependencies": { "@types/estree": "1.0.8" @@ -5572,26 +5558,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.45.1", - "@rollup/rollup-android-arm64": "4.45.1", - "@rollup/rollup-darwin-arm64": "4.45.1", - "@rollup/rollup-darwin-x64": "4.45.1", - "@rollup/rollup-freebsd-arm64": "4.45.1", - "@rollup/rollup-freebsd-x64": "4.45.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", - "@rollup/rollup-linux-arm-musleabihf": "4.45.1", - "@rollup/rollup-linux-arm64-gnu": "4.45.1", - "@rollup/rollup-linux-arm64-musl": "4.45.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-musl": "4.45.1", - "@rollup/rollup-linux-s390x-gnu": "4.45.1", - "@rollup/rollup-linux-x64-gnu": "4.45.1", - "@rollup/rollup-linux-x64-musl": "4.45.1", - "@rollup/rollup-win32-arm64-msvc": "4.45.1", - "@rollup/rollup-win32-ia32-msvc": "4.45.1", - "@rollup/rollup-win32-x64-msvc": "4.45.1", + "@rollup/rollup-android-arm-eabi": "4.45.3", + "@rollup/rollup-android-arm64": "4.45.3", + "@rollup/rollup-darwin-arm64": "4.45.3", + "@rollup/rollup-darwin-x64": "4.45.3", + "@rollup/rollup-freebsd-arm64": "4.45.3", + "@rollup/rollup-freebsd-x64": "4.45.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.3", + "@rollup/rollup-linux-arm-musleabihf": "4.45.3", + "@rollup/rollup-linux-arm64-gnu": "4.45.3", + "@rollup/rollup-linux-arm64-musl": "4.45.3", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.3", + "@rollup/rollup-linux-ppc64-gnu": "4.45.3", + "@rollup/rollup-linux-riscv64-gnu": "4.45.3", + "@rollup/rollup-linux-riscv64-musl": "4.45.3", + "@rollup/rollup-linux-s390x-gnu": "4.45.3", + "@rollup/rollup-linux-x64-gnu": "4.45.3", + "@rollup/rollup-linux-x64-musl": "4.45.3", + "@rollup/rollup-win32-arm64-msvc": "4.45.3", + "@rollup/rollup-win32-ia32-msvc": "4.45.3", + "@rollup/rollup-win32-x64-msvc": "4.45.3", "fsevents": "~2.3.2" } }, @@ -5919,9 +5905,9 @@ } }, "node_modules/svelte": { - "version": "5.36.8", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.36.8.tgz", - "integrity": "sha512-8JbZWQu96hMjH/oYQPxXW6taeC6Awl6muGHeZzJTxQx7NGRQ/J9wN1hkzRKLOlSDlbS2igiFg7p5xyTp5uXG3A==", + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.37.0.tgz", + "integrity": "sha512-BAHgWdKncZ4F1DVBrkKAvelx2Nv3mR032ca8/yj9Gxf5s9zzK1uGXiZTjCFDvmO2e9KQfcR2lEkVjw+ZxExJow==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -5967,19 +5953,20 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", - "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.0.tgz", + "integrity": "sha512-VCgMHKV7UtOGcGLGNFSbmdm6kEKjtzo5nnpGU/mnx4OsFY6bZ7QwRF5DUx+Hokw5Lvdyo8dpk8B1m8mliomrNg==", "dev": true, "dependencies": { - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "postcss": "^8.4.39", - "postcss-scss": "^4.0.9" + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" @@ -5993,49 +5980,17 @@ } } }, - "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/svelte-eslint-parser/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/svelte-eslint-parser/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=4" } }, "node_modules/svelte/node_modules/is-reference": { @@ -6456,9 +6411,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "dev": true }, "node_modules/unicode-emoji-modifier-base": { diff --git a/package.json b/package.json index 647d2c6..2225a7a 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite dev", + "dev:node": "node --version && vite dev", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", @@ -14,8 +15,8 @@ "test": "vitest" }, "dependencies": { - "@noble/hashes": "^1.8.0", "@noble/curves": "^1.9.4", + "@noble/hashes": "^1.8.0", "@nostr-dev-kit/ndk": "^2.14.32", "@nostr-dev-kit/ndk-cache-dexie": "2.6.x", "@popperjs/core": "2.11.x", @@ -59,7 +60,7 @@ "tailwindcss": "^3.4.17", "tslib": "2.8.x", "typescript": "^5.8.3", - "vite": "^7.0.5", + "vite": "^6.3.5", "vitest": "^3.1.3" } } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index ca992d0..c59d574 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -187,3 +187,29 @@ export function debounce unknown>( timeout = setTimeout(later, wait); }; } + +/** + * Creates a debounced async function that delays invoking func until after wait milliseconds have elapsed + * since the last time the debounced function was invoked. + * @param func The async function to debounce + * @param wait The number of milliseconds to delay + * @returns A debounced version of the async function + */ +export function debounceAsync( + func: (query: string) => Promise, + wait: number, +): (query: string) => void { + let timeout: ReturnType | undefined; + + return function executedFunction(query: string) { + const later = () => { + timeout = undefined; + func(query); + }; + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(later, wait); + }; +} diff --git a/vite.config.ts b/vite.config.ts index fc4ccc3..82206c3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -43,4 +43,16 @@ export default defineConfig({ // Expose the app version as a global variable "import.meta.env.APP_VERSION": JSON.stringify(getAppVersionString()), }, + optimizeDeps: { + esbuildOptions: { + define: { + global: 'globalThis', + }, + }, + }, + server: { + fs: { + allow: ['..'], + }, + }, }); From 4ac2ab8eb0d45d660dddddce3cbf52334b8e357a Mon Sep 17 00:00:00 2001 From: silberengel Date: Sun, 27 Jul 2025 09:58:17 +0200 Subject: [PATCH 4/8] updated event form to handle complex, empty, and skeleton 30040 creation added informative text to compose notes page --- src/lib/components/EventInput.svelte | 161 +++++- src/lib/components/ZettelEditor.svelte | 29 ++ .../publications/PublicationFeed.svelte | 4 +- src/lib/utils/asciidoc_metadata.ts | 489 ++++++++++++++++++ src/lib/utils/event_input_utils.ts | 344 +++++++----- .../advancedAsciidoctorPostProcessor.ts | 4 +- src/lib/utils/network_detection.ts | 2 +- tests/unit/eventInput30040.test.ts | 446 ++++++++++++++++ tests/unit/metadataExtraction.test.ts | 183 +++++++ 9 files changed, 1513 insertions(+), 149 deletions(-) create mode 100644 src/lib/utils/asciidoc_metadata.ts create mode 100644 tests/unit/eventInput30040.test.ts create mode 100644 tests/unit/metadataExtraction.test.ts diff --git a/src/lib/components/EventInput.svelte b/src/lib/components/EventInput.svelte index 7834a11..a98d524 100644 --- a/src/lib/components/EventInput.svelte +++ b/src/lib/components/EventInput.svelte @@ -12,6 +12,11 @@ analyze30040Event, get30040FixGuidance, } from "$lib/utils/event_input_utils"; + import { + extractDocumentMetadata, + metadataToTags, + removeMetadataFromContent + } from "$lib/utils/asciidoc_metadata"; import { get } from "svelte/store"; import { ndkInstance } from "$lib/ndk"; import { userPubkey } from "$lib/stores/authStore.Svelte"; @@ -24,7 +29,7 @@ import { goto } from "$app/navigation"; import { WebSocketPool } from "$lib/data_structures/websocket_pool"; - let kind = $state(30023); + let kind = $state(30040); let tags = $state<[string, string][]>([]); let content = $state(""); let createdAt = $state(Math.floor(Date.now() / 1000)); @@ -39,14 +44,29 @@ let dTagManuallyEdited = $state(false); let dTagError = $state(""); let lastPublishedEventId = $state(null); + let showWarning = $state(false); + let warningMessage = $state(""); + let pendingPublish = $state(false); + let extractedMetadata = $state<[string, string][]>([]); /** * Extracts the first Markdown/AsciiDoc header as the title. */ function extractTitleFromContent(content: string): string { // Match Markdown (# Title) or AsciiDoc (= Title) headers - const match = content.match(/^(#|=)\s*(.+)$/m); - return match ? match[2].trim() : ""; + // Look for document title (=) first, then fall back to section headers (==) + const documentMatch = content.match(/^=\s*(.+)$/m); + if (documentMatch) { + return documentMatch[1].trim(); + } + + // If no document title, look for the first section header + const sectionMatch = content.match(/^==\s*(.+)$/m); + if (sectionMatch) { + return sectionMatch[1].trim(); + } + + return ""; } function handleContentInput(e: Event) { @@ -56,6 +76,22 @@ console.log("Content input - extracted title:", extracted); title = extracted; } + + // Extract metadata from AsciiDoc content for 30040 and 30041 events + if (kind === 30040 || kind === 30041) { + try { + const { metadata } = extractDocumentMetadata(content); + const metadataTags = metadataToTags(metadata); + extractedMetadata = metadataTags; + console.log("Extracted metadata:", metadata); + console.log("Metadata tags:", metadataTags); + } catch (error) { + console.error("Error extracting metadata:", error); + extractedMetadata = []; + } + } else { + extractedMetadata = []; + } } function handleTitleInput(e: Event) { @@ -92,12 +128,24 @@ tags = tags.filter((_, i) => i !== index); } + function addExtractedTag(key: string, value: string): void { + // Check if tag already exists + const existingIndex = tags.findIndex(([k]) => k === key); + if (existingIndex >= 0) { + // Update existing tag + tags = tags.map((t, i) => (i === existingIndex ? [key, value] : t)); + } else { + // Add new tag + tags = [...tags, [key, value]]; + } + } + function isValidKind(kind: number | string): boolean { const n = Number(kind); return Number.isInteger(n) && n >= 0 && n <= 65535; } - function validate(): { valid: boolean; reason?: string } { + function validate(): { valid: boolean; reason?: string; warning?: string } { const currentUserPubkey = get(userPubkey as any); const userState = get(userStore); @@ -113,6 +161,7 @@ if (kind === 30040) { const v = validate30040EventSet(content); if (!v.valid) return v; + if (v.warning) return { valid: true, warning: v.warning }; } if (kind === 30041 || kind === 30818) { const v = validateAsciiDoc(content); @@ -124,10 +173,26 @@ function handleSubmit(e: Event) { e.preventDefault(); dTagError = ""; + error = null; // Clear any previous errors + if (requiresDTag(kind) && (!dTag || dTag.trim() === "")) { dTagError = "A d-tag is required."; return; } + + const validation = validate(); + if (!validation.valid) { + error = validation.reason || "Validation failed."; + return; + } + + if (validation.warning) { + warningMessage = validation.warning; + showWarning = true; + pendingPublish = true; + return; + } + handlePublish(); } @@ -235,8 +300,14 @@ eventTags = [...eventTags, ["title", titleValue]]; } + // For AsciiDoc events, remove metadata from content + let finalContent = content; + if (kind === 30040 || kind === 30041) { + finalContent = removeMetadataFromContent(content); + } + // Prefix Nostr addresses before publishing - const prefixedContent = prefixNostrAddresses(content); + const prefixedContent = prefixNostrAddresses(finalContent); // Create event with proper serialization const eventData = { @@ -330,6 +401,9 @@ } } }; + + // Send the event to the relay + ws.send(JSON.stringify(["EVENT", signedEvent])); }); if (published) break; } catch (e) { @@ -391,6 +465,18 @@ goto(`/events?id=${encodeURIComponent(lastPublishedEventId)}`); } } + + function confirmWarning() { + showWarning = false; + pendingPublish = false; + handlePublish(); + } + + function cancelWarning() { + showWarning = false; + pendingPublish = false; + warningMessage = ""; + }
{/if} - {#if kind === 30040} + {#if Number(kind) === 30040}
30040 - Publication Index: {get30040EventDescription()} @@ -423,6 +509,36 @@
+ + + {#if extractedMetadata.length > 0} +
+

+ Extracted Metadata (from AsciiDoc header) +

+
+ {#each extractedMetadata as [key, value], i} +
+ {key}: + + +
+ {/each} +
+
+ {/if} +
{#each tags as [key, value], i}
@@ -525,6 +641,31 @@
{/if} - {/if} - -
+ {/if} + +
+ + {#if showWarning} +
+
+

Warning

+

{warningMessage}

+
+ + +
+
+
+ {/if} diff --git a/src/lib/components/ZettelEditor.svelte b/src/lib/components/ZettelEditor.svelte index 1934293..b8c9d4d 100644 --- a/src/lib/components/ZettelEditor.svelte +++ b/src/lib/components/ZettelEditor.svelte @@ -51,6 +51,35 @@ Note content here...
+ +
+
+
+ + + +
+
+

+ Note-Taking Tool +

+

+ This editor is for creating individual notes (30041 events) only. Each section becomes a separate note event. + To create structured publications with a 30040 index event that ties multiple notes together, + use the Events form. +

+ +
+
+
+
+
+
-
-

- Note-Taking Tool -

-

- This editor is for creating individual notes (30041 events) only. Each section becomes a separate note event. - To create structured publications with a 30040 index event that ties multiple notes together, - use the Events form. -

- + {:else} + +
+
+
+ + + +
+
+

+ Note-Taking Tool +

+

+ This editor is for creating individual notes (30041 events) only. Each section becomes a separate note event. + You can add metadata like author, version, publication date, summary, and tags using AsciiDoc attributes. + To create structured publications with a 30040 index event that ties multiple notes together, + use the Events form. +

+
-
+ {/if}
- {#if showPreview} + {#if showPreview && !hasPublicationHeader}

- {#if index < parsedSections.length - 1} - -
- -
-
- {#if section.tags && section.tags.length > 0} - {#each section.tags as tag} -
- {tag[0]}: - {tag[1]} -
- {/each} - {:else} - No tags +
+ +
+
+ {#if section.tags && section.tags.length > 0} + {#each section.tags as tag} +
- {/if} -
+ {tag[0]}: + {tag[1]} +
+ {/each} + {:else} + No tags + {/if}
+
- + {#if index < parsedSections.length - 1} +
@@ -184,8 +308,8 @@ Note content here... Event Boundary
-
- {/if} + {/if} +

{/each}
@@ -198,7 +322,6 @@ Note content here... ? "s" : ""}
- Note: Currently only the first event will be published. {/if} diff --git a/src/lib/utils/ZettelParser.ts b/src/lib/utils/ZettelParser.ts index fea2e08..2796d47 100644 --- a/src/lib/utils/ZettelParser.ts +++ b/src/lib/utils/ZettelParser.ts @@ -32,21 +32,40 @@ export function parseZettelSection(section: string): ZettelSection { const lines = section.split("\n"); let title = "Untitled"; const contentLines: string[] = []; - let inHeader = true; let tags: string[][] = []; tags = extractTags(section); - for (const line of lines) { + // Find the section title first + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; const trimmed = line.trim(); - if (inHeader && trimmed.startsWith("==")) { + if (trimmed.startsWith("==")) { title = trimmed.replace(/^==+/, "").trim(); - continue; - } else if (inHeader && trimmed.startsWith(":")) { - continue; + + // Process header metadata (everything after title until blank line) + let j = i + 1; + while (j < lines.length && lines[j].trim() !== "") { + const headerLine = lines[j].trim(); + if (headerLine.startsWith(":")) { + // This is metadata, already handled by extractTags + j++; + } else { + // This is header content (like author name), skip from content + j++; + } + } + + // Skip the blank line + if (j < lines.length && lines[j].trim() === "") { + j++; + } + + // Everything after the blank line is content + for (let k = j; k < lines.length; k++) { + contentLines.push(lines[k]); + } + break; } - - inHeader = false; - contentLines.push(line); } return { @@ -69,6 +88,7 @@ export function parseAsciiDocSections( /** * Extracts tag names and values from the content. * :tagname: tagvalue // tags are optional + * Also handles AsciiDoc author line convention * @param content The AsciiDoc string. * @returns Array of tags. */ @@ -76,30 +96,46 @@ export function extractTags(content: string): string[][] { const tags: string[][] = []; const lines = content.split("\n"); - for (const line of lines) { + // Find the section title and process header metadata + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; const trimmed = line.trim(); - if (trimmed.startsWith(":")) { - // Parse AsciiDoc attribute format: :tagname: value - const match = trimmed.match(/^:([^:]+):\s*(.*)$/); - if (match) { - const tagName = match[1].trim(); - const tagValue = match[2].trim(); + + if (trimmed.startsWith("==")) { + // Process header metadata (everything after title until blank line) + let j = i + 1; + while (j < lines.length && lines[j].trim() !== "") { + const headerLine = lines[j].trim(); + + if (headerLine.startsWith(":")) { + // Parse AsciiDoc attribute format: :tagname: value + const match = headerLine.match(/^:([^:]+):\s*(.*)$/); + if (match) { + const tagName = match[1].trim(); + const tagValue = match[2].trim(); - // Special handling for tags attribute - if (tagName === "tags") { - // Split comma-separated values and create individual "t" tags - const tagValues = tagValue - .split(",") - .map((v) => v.trim()) - .filter((v) => v.length > 0); - for (const value of tagValues) { - tags.push(["t", value]); + // Special handling for tags attribute + if (tagName === "tags") { + // Split comma-separated values and create individual "t" tags + const tagValues = tagValue + .split(",") + .map((v) => v.trim()) + .filter((v) => v.length > 0); + for (const value of tagValues) { + tags.push(["t", value]); + } + } else { + // Regular attribute becomes a tag + tags.push([tagName, tagValue]); + } } } else { - // Regular attribute becomes a tag - tags.push([tagName, tagValue]); + // This is header content (like author name) + tags.push(["author", headerLine]); } + j++; } + break; } } diff --git a/src/routes/new/compose/+page.svelte b/src/routes/new/compose/+page.svelte index 73eb646..33704a7 100644 --- a/src/routes/new/compose/+page.svelte +++ b/src/routes/new/compose/+page.svelte @@ -5,6 +5,7 @@ import { goto } from "$app/navigation"; import { nip19 } from "nostr-tools"; import { publishMultipleZettels } from "$lib/services/publisher"; + import { parseAsciiDocSections } from "$lib/utils/ZettelParser"; let content = $state(""); let showPreview = $state(false); @@ -13,6 +14,8 @@ successCount: number; total: number; errors: string[]; + successfulEvents: Array<{ eventId: string; title: string }>; + failedEvents: Array<{ title: string; error: string; sectionIndex: number }>; } | null>(null); // Handle content changes from ZettelEditor @@ -33,19 +36,91 @@ content, onError: (error) => { // Only used for catastrophic errors - publishResults = { successCount: 0, total: 0, errors: [error] }; + publishResults = { successCount: 0, total: 0, errors: [error], successfulEvents: [], failedEvents: [] }; }, }); const successCount = results.filter(r => r.success).length; const errors = results.filter(r => !r.success && r.error).map(r => r.error!); + + // Extract successful events with their titles + const sections = parseAsciiDocSections(content, 2); + const successfulEvents = results + .filter(r => r.success && r.eventId) + .map((r, index) => ({ + eventId: r.eventId!, + title: sections[index]?.title || `Note ${index + 1}` + })); + + // Extract failed events with their titles and errors + const failedEvents = results + .map((r, index) => ({ result: r, index })) + .filter(({ result }) => !result.success) + .map(({ result, index }) => ({ + title: sections[index]?.title || `Note ${index + 1}`, + error: result.error || 'Unknown error', + sectionIndex: index + })); + publishResults = { successCount, total: results.length, errors, + successfulEvents, + failedEvents, }; isPublishing = false; } + + async function retryFailedEvent(sectionIndex: number) { + if (!publishResults) return; + + isPublishing = true; + + // Get the specific section content + const sections = parseAsciiDocSections(content, 2); + const section = sections[sectionIndex]; + if (!section) return; + + // Reconstruct the section content for publishing + const sectionContent = `== ${section.title}\n\n${section.content}`; + + try { + const result = await publishMultipleZettels({ + content: sectionContent, + onError: (error) => { + console.error('Retry failed:', error); + }, + }); + + if (result[0]?.success && result[0]?.eventId) { + // Update the successful events list + const newSuccessfulEvent = { + eventId: result[0].eventId, + title: section.title + }; + + // Remove from failed events + const updatedFailedEvents = publishResults.failedEvents.filter( + (_, index) => index !== sectionIndex + ); + + // Add to successful events + const updatedSuccessfulEvents = [...publishResults.successfulEvents, newSuccessfulEvent]; + + publishResults = { + ...publishResults, + successCount: publishResults.successCount + 1, + successfulEvents: updatedSuccessfulEvents, + failedEvents: updatedFailedEvents, + }; + } + } catch (error) { + console.error('Retry failed:', error); + } + + isPublishing = false; + } @@ -89,14 +164,71 @@ Success! {publishResults.successCount} events published. + {#if publishResults.successfulEvents.length > 0} +
+ Published events: +
+ {#each publishResults.successfulEvents as event} + {@const nevent = nip19.neventEncode({ id: event.eventId })} + + {/each} +
+
+ {/if}
{:else} Some events failed to publish. - {publishResults.successCount} of {publishResults.total} events published.
- {#each publishResults.errors as error} -
{error}
- {/each} + {publishResults.successCount} of {publishResults.total} events published. + + {#if publishResults.successfulEvents.length > 0} +
+ Successfully published: +
+ {#each publishResults.successfulEvents as event} + {@const nevent = nip19.neventEncode({ id: event.eventId })} + + {/each} +
+
+ {/if} + + {#if publishResults.failedEvents.length > 0} +
+ Failed to publish: +
+ {#each publishResults.failedEvents as failedEvent, index} +
+
{failedEvent.title}
+
{failedEvent.error}
+ +
+ {/each} +
+
+ {/if}
{/if} {/if} diff --git a/tests/unit/ZettelEditor.test.ts b/tests/unit/ZettelEditor.test.ts new file mode 100644 index 0000000..3490286 --- /dev/null +++ b/tests/unit/ZettelEditor.test.ts @@ -0,0 +1,429 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import type { AsciiDocMetadata } from "../../src/lib/utils/asciidoc_metadata"; + +// Mock all Svelte components and dependencies +vi.mock("flowbite-svelte", () => ({ + Textarea: vi.fn().mockImplementation((props) => { + return { + $$render: () => ``, + $$bind: { value: props.bind, oninput: props.oninput } + }; + }), + Button: vi.fn().mockImplementation((props) => { + return { + $$render: () => ``, + $$bind: { onclick: props.onclick } + }; + }) +})); + +vi.mock("flowbite-svelte-icons", () => ({ + EyeOutline: vi.fn().mockImplementation(() => ({ + $$render: () => `` + })) +})); + +vi.mock("asciidoctor", () => ({ + default: vi.fn(() => ({ + convert: vi.fn((content, options) => { + // Mock AsciiDoctor conversion - return simple HTML + return content.replace(/^==\s+(.+)$/gm, '

$1

') + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/\*(.+?)\*/g, '$1'); + }) + })) +})); + +// Mock sessionStorage +const mockSessionStorage = { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), +}; +Object.defineProperty(global, 'sessionStorage', { + value: mockSessionStorage, + writable: true +}); + +// Mock window object for DOM manipulation +Object.defineProperty(global, 'window', { + value: { + sessionStorage: mockSessionStorage, + document: { + querySelector: vi.fn(), + createElement: vi.fn(), + } + }, + writable: true +}); + +// Mock DOM methods +const mockQuerySelector = vi.fn(); +const mockCreateElement = vi.fn(); +const mockAddEventListener = vi.fn(); +const mockRemoveEventListener = vi.fn(); + +Object.defineProperty(global, 'document', { + value: { + querySelector: mockQuerySelector, + createElement: mockCreateElement, + addEventListener: mockAddEventListener, + removeEventListener: mockRemoveEventListener, + }, + writable: true +}); + +describe("ZettelEditor Component Logic", () => { + let mockOnContentChange: ReturnType; + let mockOnPreviewToggle: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + mockOnContentChange = vi.fn(); + mockOnPreviewToggle = vi.fn(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("Publication Format Detection Logic", () => { + it("should detect document header format", () => { + const contentWithDocumentHeader = "= Document Title\n\n== Section 1\nContent"; + + // Test the regex pattern used in the component + const hasDocumentHeader = contentWithDocumentHeader.match(/^=\s+/m); + expect(hasDocumentHeader).toBeTruthy(); + }); + + it("should detect index card format", () => { + const contentWithIndexCard = "index card\n\n== Section 1\nContent"; + + // Test the logic used in the component + const lines = contentWithIndexCard.split(/\r?\n/); + let hasIndexCard = false; + for (const line of lines) { + if (line.trim().toLowerCase() === 'index card') { + hasIndexCard = true; + break; + } + } + expect(hasIndexCard).toBe(true); + }); + + it("should not detect publication format for normal section content", () => { + const normalContent = "== Section 1\nContent\n\n== Section 2\nMore content"; + + // Test the logic used in the component + const lines = normalContent.split(/\r?\n/); + let hasPublicationHeader = false; + for (const line of lines) { + if (line.match(/^=\s+(.+)$/)) { + hasPublicationHeader = true; + break; + } + if (line.trim().toLowerCase() === 'index card') { + hasPublicationHeader = true; + break; + } + } + expect(hasPublicationHeader).toBe(false); + }); + }); + + describe("Content Parsing Logic", () => { + it("should parse sections with document header", () => { + const content = "== Section 1\n:author: Test Author\n\nContent 1"; + + // Test the parsing logic + const hasDocumentHeader = content.match(/^=\s+/m); + expect(hasDocumentHeader).toBeFalsy(); // This content doesn't have a document header + + // Test section splitting logic + const sectionStrings = content.split(/(?=^==\s+)/gm).filter((section: string) => section.trim()); + expect(sectionStrings).toHaveLength(1); + expect(sectionStrings[0]).toContain("== Section 1"); + }); + + it("should parse sections without document header", () => { + const content = "== Section 1\nContent 1"; + + // Test the parsing logic + const hasDocumentHeader = content.match(/^=\s+/m); + expect(hasDocumentHeader).toBeFalsy(); + + // Test section splitting logic + const sectionStrings = content.split(/(?=^==\s+)/gm).filter((section: string) => section.trim()); + expect(sectionStrings).toHaveLength(1); + expect(sectionStrings[0]).toContain("== Section 1"); + }); + + it("should handle empty content", () => { + const content = ""; + const hasDocumentHeader = content.match(/^=\s+/m); + expect(hasDocumentHeader).toBeFalsy(); + }); + }); + + describe("Content Conversion Logic", () => { + it("should convert document title to section title", () => { + const contentWithDocumentHeader = "= Document Title\n\n== Section 1\nContent"; + + // Test the conversion logic + let convertedContent = contentWithDocumentHeader.replace(/^=\s+(.+)$/gm, '== $1'); + convertedContent = convertedContent.replace(/^index card$/gim, ''); + const finalContent = convertedContent.replace(/\n\s*\n\s*\n/g, '\n\n'); + + expect(finalContent).toBe("== Document Title\n\n== Section 1\nContent"); + }); + + it("should remove index card line", () => { + const contentWithIndexCard = "index card\n\n== Section 1\nContent"; + + // Test the conversion logic + let convertedContent = contentWithIndexCard.replace(/^=\s+(.+)$/gm, '== $1'); + convertedContent = convertedContent.replace(/^index card$/gim, ''); + const finalContent = convertedContent.replace(/\n\s*\n\s*\n/g, '\n\n'); + + expect(finalContent).toBe("\n\n== Section 1\nContent"); + }); + + it("should clean up double newlines", () => { + const contentWithExtraNewlines = "= Document Title\n\n\n== Section 1\nContent"; + + // Test the conversion logic + let convertedContent = contentWithExtraNewlines.replace(/^=\s+(.+)$/gm, '== $1'); + convertedContent = convertedContent.replace(/^index card$/gim, ''); + const finalContent = convertedContent.replace(/\n\s*\n\s*\n/g, '\n\n'); + + expect(finalContent).toBe("== Document Title\n\n== Section 1\nContent"); + }); + }); + + describe("SessionStorage Integration", () => { + it("should store content in sessionStorage when switching to publication editor", () => { + const contentWithDocumentHeader = "= Document Title\n\n== Section 1\nContent"; + + // Test the sessionStorage logic + mockSessionStorage.setItem('zettelEditorContent', contentWithDocumentHeader); + mockSessionStorage.setItem('zettelEditorSource', 'publication-format'); + + expect(mockSessionStorage.setItem).toHaveBeenCalledWith('zettelEditorContent', contentWithDocumentHeader); + expect(mockSessionStorage.setItem).toHaveBeenCalledWith('zettelEditorSource', 'publication-format'); + }); + }); + + describe("Event Count Logic", () => { + it("should calculate correct event count for single section", () => { + const sections = [{ title: "Section 1", content: "Content 1", tags: [] }]; + const eventCount = sections.length; + const eventText = `${eventCount} event${eventCount !== 1 ? "s" : ""}`; + + expect(eventCount).toBe(1); + expect(eventText).toBe("1 event"); + }); + + it("should calculate correct event count for multiple sections", () => { + const sections = [ + { title: "Section 1", content: "Content 1", tags: [] }, + { title: "Section 2", content: "Content 2", tags: [] } + ]; + const eventCount = sections.length; + const eventText = `${eventCount} event${eventCount !== 1 ? "s" : ""}`; + + expect(eventCount).toBe(2); + expect(eventText).toBe("2 events"); + }); + }); + + describe("Tag Processing Logic", () => { + it("should process tags correctly", () => { + // Mock the metadataToTags function + const mockMetadataToTags = vi.fn().mockReturnValue([["author", "Test Author"]]); + + const mockMetadata = { title: "Section 1", author: "Test Author" } as AsciiDocMetadata; + const tags = mockMetadataToTags(mockMetadata); + + expect(tags).toEqual([["author", "Test Author"]]); + expect(mockMetadataToTags).toHaveBeenCalledWith(mockMetadata); + }); + + it("should handle empty tags", () => { + // Mock the metadataToTags function + const mockMetadataToTags = vi.fn().mockReturnValue([]); + + const mockMetadata = { title: "Section 1" } as AsciiDocMetadata; + const tags = mockMetadataToTags(mockMetadata); + + expect(tags).toEqual([]); + }); + }); + + describe("AsciiDoctor Processing", () => { + it("should process AsciiDoc content correctly", () => { + // Mock the asciidoctor conversion + const mockConvert = vi.fn((content, options) => { + return content.replace(/^==\s+(.+)$/gm, '

$1

') + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/\*(.+?)\*/g, '$1'); + }); + + const content = "== Test Section\n\nThis is **bold** and *italic* text."; + const processedContent = mockConvert(content, { + standalone: false, + doctype: "article", + attributes: { + showtitle: true, + sectids: true, + }, + }); + + expect(processedContent).toContain('

Test Section

'); + expect(processedContent).toContain('bold'); + expect(processedContent).toContain('italic'); + }); + }); + + describe("Error Handling", () => { + it("should handle parsing errors gracefully", () => { + // Mock a function that might throw an error + const mockParseFunction = vi.fn().mockImplementation(() => { + throw new Error("Parsing error"); + }); + + const content = "== Section 1\nContent 1"; + + // Should not throw error when called + expect(() => { + try { + mockParseFunction(content); + } catch (error) { + // Expected error, but should be handled gracefully + } + }).not.toThrow(); + }); + + it("should handle empty content without errors", () => { + const content = ""; + const hasDocumentHeader = content.match(/^=\s+/m); + expect(hasDocumentHeader).toBeFalsy(); + }); + }); + + describe("Component Props Interface", () => { + it("should have correct prop types", () => { + // Test that the component props interface is correctly defined + const expectedProps = { + content: "", + placeholder: "Default placeholder", + showPreview: false, + onContentChange: vi.fn(), + onPreviewToggle: vi.fn(), + }; + + expect(expectedProps).toHaveProperty('content'); + expect(expectedProps).toHaveProperty('placeholder'); + expect(expectedProps).toHaveProperty('showPreview'); + expect(expectedProps).toHaveProperty('onContentChange'); + expect(expectedProps).toHaveProperty('onPreviewToggle'); + }); + }); + + describe("Utility Function Integration", () => { + it("should integrate with ZettelParser utilities", () => { + // Mock the parseAsciiDocSections function + const mockParseAsciiDocSections = vi.fn().mockReturnValue([ + { title: "Section 1", content: "Content 1", tags: [] } + ]); + + const content = "== Section 1\nContent 1"; + const sections = mockParseAsciiDocSections(content, 2); + + expect(sections).toHaveLength(1); + expect(sections[0].title).toBe("Section 1"); + }); + + it("should integrate with asciidoc_metadata utilities", () => { + // Mock the utility functions + const mockExtractDocumentMetadata = vi.fn().mockReturnValue({ + metadata: { title: "Document Title" } as AsciiDocMetadata, + content: "Document content" + }); + + const mockExtractSectionMetadata = vi.fn().mockReturnValue({ + metadata: { title: "Section Title" } as AsciiDocMetadata, + content: "Section content", + title: "Section Title" + }); + + const documentContent = "= Document Title\nDocument content"; + const sectionContent = "== Section Title\nSection content"; + + const documentResult = mockExtractDocumentMetadata(documentContent); + const sectionResult = mockExtractSectionMetadata(sectionContent); + + expect(documentResult.metadata.title).toBe("Document Title"); + expect(sectionResult.title).toBe("Section Title"); + }); + }); + + describe("Content Validation", () => { + it("should validate content structure", () => { + const validContent = "== Section 1\nContent here\n\n== Section 2\nMore content"; + const invalidContent = "Just some text without sections"; + + // Test section detection + const validSections = validContent.split(/(?=^==\s+)/gm).filter((section: string) => section.trim()); + const invalidSections = invalidContent.split(/(?=^==\s+)/gm).filter((section: string) => section.trim()); + + expect(validSections.length).toBeGreaterThan(0); + // The invalid content will have one section (the entire content) since it doesn't start with == + expect(invalidSections.length).toBe(1); + }); + + it("should handle mixed content types", () => { + const mixedContent = "= Document Title\n\n== Section 1\nContent\n\n== Section 2\nMore content"; + + // Test document header detection + const hasDocumentHeader = mixedContent.match(/^=\s+/m); + expect(hasDocumentHeader).toBeTruthy(); + + // Test section extraction + const sections = mixedContent.split(/(?=^==\s+)/gm).filter((section: string) => section.trim()); + expect(sections.length).toBeGreaterThan(0); + }); + }); + + describe("String Manipulation", () => { + it("should handle string replacements correctly", () => { + const originalContent = "= Title\n\n== Section\nContent"; + + // Test various string manipulations + const convertedContent = originalContent + .replace(/^=\s+(.+)$/gm, '== $1') + .replace(/^index card$/gim, '') + .replace(/\n\s*\n\s*\n/g, '\n\n'); + + expect(convertedContent).toBe("== Title\n\n== Section\nContent"); + }); + + it("should handle edge cases in string manipulation", () => { + const edgeCases = [ + "= Title\n\n\n== Section\nContent", // Multiple newlines + "index card\n\n== Section\nContent", // Index card + "= Title\nindex card\n== Section\nContent", // Both + ]; + + edgeCases.forEach(content => { + const converted = content + .replace(/^=\s+(.+)$/gm, '== $1') + .replace(/^index card$/gim, '') + .replace(/\n\s*\n\s*\n/g, '\n\n'); + + expect(converted).toBeDefined(); + expect(typeof converted).toBe('string'); + }); + }); + }); +}); \ No newline at end of file From b1d66ebd79d24e2c23c89a316a87f930f908bbfa Mon Sep 17 00:00:00 2001 From: silberengel Date: Mon, 28 Jul 2025 00:22:26 +0200 Subject: [PATCH 7/8] interim checkin --- package-lock.json | 170 +++--- src/lib/components/EventInput.svelte | 48 +- src/lib/components/ZettelEditor.svelte | 47 +- src/lib/services/publisher.ts | 20 +- src/lib/utils/asciidoc_metadata.ts | 730 +++++++++++++------------ src/routes/new/compose/+page.svelte | 12 +- tests/unit/metadataExtraction.test.ts | 149 ++++- 7 files changed, 646 insertions(+), 530 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0fe9c01..ad65282 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2565,18 +2565,37 @@ } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "devOptional": true, + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { - "readdirp": "^4.0.1" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 8.10.0" }, "funding": { "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/cliui": { @@ -3420,6 +3439,15 @@ } } }, + "node_modules/eslint-plugin-svelte/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -5474,16 +5502,25 @@ } }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "devOptional": true, + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, "engines": { - "node": ">= 14.18.0" + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/require-directory": { @@ -5952,6 +5989,34 @@ "typescript": ">=5.0.0" } }, + "node_modules/svelte-check/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/svelte-check/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/svelte-eslint-parser": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.0.tgz", @@ -6139,51 +6204,6 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tailwindcss/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tailwindcss/node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", @@ -6230,28 +6250,6 @@ "node": ">=4" } }, - "node_modules/tailwindcss/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6792,12 +6790,14 @@ } }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14.6" } }, "node_modules/yargs": { diff --git a/src/lib/components/EventInput.svelte b/src/lib/components/EventInput.svelte index 2768205..0519692 100644 --- a/src/lib/components/EventInput.svelte +++ b/src/lib/components/EventInput.svelte @@ -14,6 +14,7 @@ } from "$lib/utils/event_input_utils"; import { extractDocumentMetadata, + extractSmartMetadata, metadataToTags, removeMetadataFromContent } from "$lib/utils/asciidoc_metadata"; @@ -65,60 +66,45 @@ sessionStorage.removeItem('zettelEditorContent'); sessionStorage.removeItem('zettelEditorSource'); - // Extract title from content - const extracted = extractTitleFromContent(content); - if (extracted) { - title = extracted; + // Extract title and metadata using the standardized parser + const { metadata } = extractSmartMetadata(content); + if (metadata.title) { + title = metadata.title; titleManuallyEdited = false; dTagManuallyEdited = false; } - // For content from ZettelEditor, don't extract any metadata - // since ZettelEditor content never has document metadata + // Extract metadata for 30040 and 30041 events if (kind === 30040 || kind === 30041) { - extractedMetadata = []; + extractedMetadata = metadataToTags(metadata); } } }); /** - * Extracts the first Markdown/AsciiDoc header as the title. + * Extracts the first Markdown/AsciiDoc header as the title using the standardized parser. */ function extractTitleFromContent(content: string): string { - // Match Markdown (# Title) or AsciiDoc (= Title) headers - // Look for document title (=) first, then fall back to section headers (==) - const documentMatch = content.match(/^=\s*(.+)$/m); - if (documentMatch) { - const title = documentMatch[1].trim(); - // Only return the title if it's not empty (malformed titles like "=|" will be empty) - if (title) { - return title; - } - } - - // If no valid document title, look for the first section header - const sectionMatch = content.match(/^==\s*(.+)$/m); - if (sectionMatch) { - return sectionMatch[1].trim(); - } - - return ""; + const { metadata } = extractSmartMetadata(content); + return metadata.title || ""; } function handleContentInput(e: Event) { content = (e.target as HTMLTextAreaElement).value; + + // Extract title and metadata using the standardized parser + const { metadata } = extractSmartMetadata(content); + if (!titleManuallyEdited) { - const extracted = extractTitleFromContent(content); - console.log("Content input - extracted title:", extracted); - title = extracted; + console.log("Content input - extracted title:", metadata.title); + title = metadata.title || ""; // Reset dTagManuallyEdited when title changes so d-tag can be auto-generated dTagManuallyEdited = false; } // Extract metadata from AsciiDoc content for 30040 and 30041 events if (kind === 30040 || kind === 30041) { - // Don't extract metadata - let users add tags manually - extractedMetadata = []; + extractedMetadata = metadataToTags(metadata); } else { extractedMetadata = []; } diff --git a/src/lib/components/ZettelEditor.svelte b/src/lib/components/ZettelEditor.svelte index 9e33343..da96f74 100644 --- a/src/lib/components/ZettelEditor.svelte +++ b/src/lib/components/ZettelEditor.svelte @@ -2,17 +2,12 @@ import { Textarea, Button } from "flowbite-svelte"; import { EyeOutline } from "flowbite-svelte-icons"; import { - parseAsciiDocSections, - type ZettelSection, - } from "$lib/utils/ZettelParser"; - import { - extractDocumentMetadata, - extractSectionMetadata, - parseAsciiDocWithMetadata, - type AsciiDocMetadata, - metadataToTags, - } from "$lib/utils/asciidoc_metadata"; - import asciidoctor from "asciidoctor"; + extractSmartMetadata, + parseAsciiDocWithMetadata, + type AsciiDocMetadata, + metadataToTags, +} from "$lib/utils/asciidoc_metadata"; +import asciidoctor from "asciidoctor"; // Component props let { @@ -45,34 +40,20 @@ Note content here... onPreviewToggle?: (show: boolean) => void; }>(); - // Initialize AsciiDoctor processor - const asciidoctorProcessor = asciidoctor(); - - // Parse sections for preview using the new metadata service + // Parse sections for preview using the smart metadata service let parsedSections = $derived.by(() => { if (!content.trim()) return []; - // Check if content starts with a document header (level 0 header) - const hasDocumentHeader = content.match(/^=\s+/m); + // Use smart metadata extraction that handles both document headers and section-only content + const { metadata: docMetadata } = extractSmartMetadata(content); - let sections; - if (hasDocumentHeader) { - // Use the proper metadata service for documents with headers - const parsed = parseAsciiDocWithMetadata(content); - sections = parsed.sections; - } else { - // For content that starts directly with sections, split manually - const sectionStrings = content.split(/(?=^==\s+)/gm).filter((section: string) => section.trim()); - sections = sectionStrings.map((sectionString: string) => { - const { metadata, content, title } = extractSectionMetadata(sectionString); - return { metadata, content, title }; - }); - } + // Parse the content using the standardized parser + const parsed = parseAsciiDocWithMetadata(content); // Debug logging - console.log("Parsed sections:", sections); + console.log("Parsed sections:", parsed.sections); - return sections.map((section: { metadata: AsciiDocMetadata; content: string; title: string }) => { + return parsed.sections.map((section: { metadata: AsciiDocMetadata; content: string; title: string }) => { // Use only section metadata for each section // Don't combine with document metadata to avoid overriding section-specific metadata const tags = metadataToTags(section.metadata); @@ -259,7 +240,7 @@ Note content here...
- {@html asciidoctorProcessor.convert( + {@html asciidoctor().convert( `== ${section.title}\n\n${section.content}`, { standalone: false, diff --git a/src/lib/services/publisher.ts b/src/lib/services/publisher.ts index 3d5e9fe..98b63f4 100644 --- a/src/lib/services/publisher.ts +++ b/src/lib/services/publisher.ts @@ -1,7 +1,7 @@ import { get } from "svelte/store"; import { ndkInstance } from "../ndk.ts"; import { getMimeTags } from "../utils/mime.ts"; -import { parseAsciiDocSections } from "../utils/ZettelParser.ts"; +import { parseAsciiDocWithMetadata, metadataToTags } from "../utils/asciidoc_metadata.ts"; import { NDKRelaySet, NDKEvent } from "@nostr-dev-kit/ndk"; import { nip19 } from "nostr-tools"; @@ -44,18 +44,18 @@ export async function publishZettel( } try { - // Parse content into sections - const sections = parseAsciiDocSections(content, 2); + // Parse content into sections using the standardized parser + const parsed = parseAsciiDocWithMetadata(content); - if (sections.length === 0) { + if (parsed.sections.length === 0) { throw new Error("No valid sections found in content"); } // For now, publish only the first section - const firstSection = sections[0]; + const firstSection = parsed.sections[0]; const title = firstSection.title; const cleanContent = firstSection.content; - const sectionTags = firstSection.tags || []; + const sectionTags = metadataToTags(firstSection.metadata); // Generate d-tag and create event const dTag = generateDTag(title); @@ -128,8 +128,8 @@ export async function publishMultipleZettels( } try { - const sections = parseAsciiDocSections(content, 2); - if (sections.length === 0) { + const parsed = parseAsciiDocWithMetadata(content); + if (parsed.sections.length === 0) { throw new Error('No valid sections found in content'); } @@ -141,10 +141,10 @@ export async function publishMultipleZettels( const results: PublishResult[] = []; const publishedEvents: NDKEvent[] = []; - for (const section of sections) { + for (const section of parsed.sections) { const title = section.title; const cleanContent = section.content; - const sectionTags = section.tags || []; + const sectionTags = metadataToTags(section.metadata); const dTag = generateDTag(title); const [mTag, MTag] = getMimeTags(kind); const tags: string[][] = [["d", dTag], mTag, MTag, ["title", title]]; diff --git a/src/lib/utils/asciidoc_metadata.ts b/src/lib/utils/asciidoc_metadata.ts index 69e0e1e..6d6754c 100644 --- a/src/lib/utils/asciidoc_metadata.ts +++ b/src/lib/utils/asciidoc_metadata.ts @@ -1,18 +1,14 @@ /** - * AsciiDoc Metadata Extraction Service + * AsciiDoc Metadata Extraction Service using Asciidoctor * - * Extracts metadata from AsciiDoc document headers and section headers, - * mapping them to Nostr event tags according to NKBIP-01 specification. - * - * Document header structure: - * = Document Title - * Author Name - * version, date, revision info - * :attribute: value - * - * The first empty line marks the end of the header and start of the document body. + * Thin wrapper around Asciidoctor's built-in metadata extraction capabilities. + * Leverages the existing Pharos parser to avoid duplication. */ +// @ts-ignore +import Processor from "asciidoctor"; +import type { Document } from "asciidoctor"; + export interface AsciiDocMetadata { title?: string; authors?: string[]; @@ -30,7 +26,6 @@ export interface AsciiDocMetadata { autoUpdate?: 'yes' | 'ask' | 'no'; } -// Sections use the same metadata structure as documents export type SectionMetadata = AsciiDocMetadata; export interface ParsedAsciiDoc { @@ -43,448 +38,463 @@ export interface ParsedAsciiDoc { }>; } +// Shared attribute mapping based on Asciidoctor standard attributes +const ATTRIBUTE_MAP: Record = { + // Standard Asciidoctor attributes + 'author': 'authors', + 'description': 'summary', + 'keywords': 'tags', + 'revnumber': 'version', + 'revdate': 'publicationDate', + 'revremark': 'edition', + 'title': 'title', + + // Custom attributes for Alexandria + 'published_by': 'publishedBy', + 'publisher': 'publisher', + 'summary': 'summary', + 'image': 'coverImage', + 'cover': 'coverImage', + 'isbn': 'isbn', + 'source': 'source', + 'type': 'type', + 'auto-update': 'autoUpdate', + 'version': 'version', + 'edition': 'edition', + 'published_on': 'publicationDate', + 'date': 'publicationDate', + 'version-label': 'version', +}; + /** - * Shared function to parse metadata from attribute entries - * @param metadata The metadata object to populate - * @param key The attribute key - * @param value The attribute value + * Creates an Asciidoctor processor instance */ -function parseMetadataAttribute(metadata: AsciiDocMetadata, key: string, value: string): void { - switch (key.toLowerCase()) { - case 'author': - // Accumulate multiple authors - if (!metadata.authors) { - metadata.authors = []; - } - metadata.authors.push(value); - break; - case 'version': - // Only set version if not already set from revision line - if (!metadata.version) { - metadata.version = value; - } - break; - case 'edition': - metadata.edition = value; - break; - case 'published_on': - case 'date': - metadata.publicationDate = value; - break; - case 'published_by': - case 'publisher': - // Only set publishedBy if not already set from revision line - if (!metadata.publishedBy) { - metadata.publishedBy = value; - } - break; - case 'summary': - case 'description': - // Accumulate multiple summaries/descriptions - if (!metadata.summary) { - metadata.summary = value; +function createProcessor() { + return Processor(); +} + +/** + * Extracts tags from attributes, combining tags and keywords + */ +function extractTagsFromAttributes(attributes: Record): string[] { + const tags: string[] = []; + const attrTags = attributes['tags']; + const attrKeywords = attributes['keywords']; + + if (attrTags && typeof attrTags === 'string') { + tags.push(...attrTags.split(',').map(tag => tag.trim())); + } + + if (attrKeywords && typeof attrKeywords === 'string') { + tags.push(...attrKeywords.split(',').map(tag => tag.trim())); + } + + return [...new Set(tags)]; // Remove duplicates +} + +/** + * Maps attributes to metadata with special handling for authors and tags + */ +function mapAttributesToMetadata(attributes: Record, metadata: AsciiDocMetadata, isDocument: boolean = false): void { + for (const [key, value] of Object.entries(attributes)) { + const metadataKey = ATTRIBUTE_MAP[key.toLowerCase()]; + if (metadataKey && value && typeof value === 'string') { + if (metadataKey === 'authors' && isDocument) { + // Skip author mapping for documents since it's handled manually + continue; + } else if (metadataKey === 'authors' && !isDocument) { + // For sections, append author to existing authors array + if (!metadata.authors) { + metadata.authors = []; + } + metadata.authors.push(value); + } else if (metadataKey === 'tags') { + // Skip tags mapping since it's handled by extractTagsFromAttributes + continue; } else { - // If we already have a summary, append this one - metadata.summary = metadata.summary + ' ' + value; + (metadata as any)[metadataKey] = value; } - break; - case 'image': - case 'cover': - metadata.coverImage = value; - break; - case 'isbn': - metadata.isbn = value; - break; - case 'source': - metadata.source = value; - break; - case 'type': - metadata.type = value; - break; - case 'auto-update': - if (value === 'yes' || value === 'ask' || value === 'no') { - metadata.autoUpdate = value; - } - break; - case 'tags': - case 'keywords': - // Accumulate multiple tag sets - if (!metadata.tags) { - metadata.tags = []; - } - const newTags = value.split(',').map(tag => tag.trim()); - metadata.tags.push(...newTags); - break; + } } } /** - * Shared function to extract metadata from header lines - * @param lines The lines to process - * @param startLine The starting line index - * @param metadata The metadata object to populate - * @returns The index of the line after the header metadata + * Extracts authors from header line (document or section) */ -function extractHeaderMetadata(lines: string[], startLine: number, metadata: AsciiDocMetadata): number { - let currentLine = startLine; - - // Process the next two lines for author and revision info - let processedLines = 0; - for (let i = 0; i < 2 && currentLine + i < lines.length; i++) { - const line = lines[currentLine + i]; - - // Skip empty lines - if (line.trim() === '') { - continue; +function extractAuthorsFromHeader(sourceContent: string, isSection: boolean = false): string[] { + const authors: string[] = []; + const lines = sourceContent.split(/\r?\n/); + const headerPattern = isSection ? /^==\s+/ : /^=\s+/; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.match(headerPattern)) { + // Found title line, check subsequent lines for authors + let j = i + 1; + while (j < lines.length) { + const authorLine = lines[j]; + + // Stop if we hit a blank line or content that's not an author + if (authorLine.trim() === '') { + break; + } + + if (authorLine.includes('<') && !authorLine.startsWith(':')) { + // This is an author line like "John Doe " + const authorName = authorLine.split('<')[0].trim(); + if (authorName) { + authors.push(authorName); + } + } else if (isSection && authorLine.match(/^[A-Za-z\s]+$/) && authorLine.trim() !== '' && authorLine.trim().split(/\s+/).length <= 2) { + // This is a simple author name without email (for sections) + authors.push(authorLine.trim()); + } else if (authorLine.startsWith(':')) { + // This is an attribute line, skip it - attributes are handled by mapAttributesToMetadata + // Don't break here, continue to next line + } else { + // Not an author line, stop looking + break; + } + + j++; + } + break; } + } + + return authors; +} - // Skip attribute lines (they'll be processed later) - if (line.startsWith(':')) { - continue; +/** + * Strips header and attribute lines from content + */ +function stripHeaderAndAttributes(content: string, isSection: boolean = false): string { + const lines = content.split(/\r?\n/); + let contentStart = 0; + const headerPattern = isSection ? /^==\s+/ : /^=\s+/; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // Skip title line, author line, revision line, and attribute lines + if (!line.match(headerPattern) && !line.includes('<') && !line.match(/^.+,\s*.+:\s*.+$/) && + !line.match(/^:[^:]+:\s*.+$/) && line.trim() !== '') { + contentStart = i; + break; } + } - // Check if this is an author line (contains ) - if (line.includes('<') && line.includes('>')) { - const authorMatch = line.match(/^(.+?)\s*<(.+?)>$/); - if (authorMatch) { - const authorName = authorMatch[1].trim(); - metadata.authors = [authorName]; - processedLines++; - continue; - } + // Filter out all attribute lines and author lines from the content + const contentLines = lines.slice(contentStart); + const filteredLines = contentLines.filter(line => { + // Skip attribute lines + if (line.match(/^:[^:]+:\s*.+$/)) { + return false; } - - // Check if this is a revision line (contains version, date, revision info) - const revisionMatch = line.match(/^(.+?),\s*(.+?),\s*(.+)$/); - if (revisionMatch) { - metadata.version = revisionMatch[1].trim(); - metadata.publicationDate = revisionMatch[2].trim(); - metadata.publishedBy = revisionMatch[3].trim(); - processedLines++; - continue; + // Skip author lines (simple names without email) + if (isSection && line.match(/^[A-Za-z\s]+$/) && line.trim() !== '' && line.trim().split(/\s+/).length <= 2) { + return false; } + return true; + }); + + // Remove extra blank lines and normalize newlines + return filteredLines.join('\n').replace(/\n\s*\n\s*\n/g, '\n\n').replace(/\n\s*\n/g, '\n').trim(); +} - // If it's not author or revision, it might be a simple author name - if (!metadata.authors) { - metadata.authors = [line.trim()]; - processedLines++; +/** + * Parses attributes from section content + */ +function parseSectionAttributes(sectionContent: string): Record { + const attributes: Record = {}; + const lines = sectionContent.split(/\r?\n/); + + for (const line of lines) { + const match = line.match(/^:([^:]+):\s*(.+)$/); + if (match) { + const [, key, value] = match; + attributes[key.trim()] = value.trim(); } } + + return attributes; +} - // Move past the author/revision lines that were actually processed - currentLine += processedLines; - - // Process attribute entries (lines starting with :) - while (currentLine < lines.length) { - const line = lines[currentLine]; - - // Empty line marks the end of the header - if (line.trim() === '') { - break; - } - // Check for attribute entries - const attrMatch = line.match(/^:([^:]+):\s*(.+)$/); - if (attrMatch) { - const key = attrMatch[1].trim(); - const value = attrMatch[2].trim(); - parseMetadataAttribute(metadata, key, value); - } - currentLine++; - } - return currentLine; -} /** - * Extracts metadata from AsciiDoc document header - * @param content The full AsciiDoc content - * @returns Object containing metadata and cleaned content + * Extracts metadata from AsciiDoc document using Asciidoctor */ export function extractDocumentMetadata(inputContent: string): { metadata: AsciiDocMetadata; content: string; } { - const lines = inputContent.split(/\r?\n/); + const asciidoctor = createProcessor(); + const document = asciidoctor.load(inputContent, { standalone: false }) as Document; + const metadata: AsciiDocMetadata = {}; - let headerEndIndex = -1; - let currentLine = 0; + const attributes = document.getAttributes(); - // Find the document title (first line starting with =) - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const titleMatch = line.match(/^=\s+(.+)$/); - if (titleMatch) { - metadata.title = titleMatch[1].trim(); - currentLine = i + 1; - break; - } - } + // Extract basic metadata + const title = document.getTitle(); + if (title) metadata.title = title; - // If no document title found, return empty metadata - if (!metadata.title) { - return { metadata: {}, content: inputContent }; + // Handle multiple authors - combine header line and attributes + const authors = extractAuthorsFromHeader(document.getSource()); + + // Get authors from attributes (but avoid duplicates) + const attrAuthor = attributes['author']; + if (attrAuthor && typeof attrAuthor === 'string' && !authors.includes(attrAuthor)) { + authors.push(attrAuthor); } - - // Check if this is an index card format (title followed immediately by "index card") - if (currentLine < lines.length && lines[currentLine].trim() === 'index card') { - // This is index card format - content starts immediately after title - headerEndIndex = currentLine; - } else { - // Extract header metadata using shared function - currentLine = extractHeaderMetadata(lines, currentLine, metadata); - - // If we didn't find an empty line, the header ends at the first section - if (currentLine < lines.length && lines[currentLine].trim() === '') { - headerEndIndex = currentLine + 1; // Skip the empty line - } else { - for (let i = currentLine; i < lines.length; i++) { - if (lines[i].match(/^==\s+/)) { - headerEndIndex = i; - break; - } - } - // If no section found and no empty line, the header ends at the current line - if (headerEndIndex === -1) { - headerEndIndex = currentLine; - } - } + + if (authors.length > 0) { + metadata.authors = [...new Set(authors)]; // Remove duplicates } - // If still no header end found, use the entire content - if (headerEndIndex === -1) { - headerEndIndex = lines.length; - } + // Extract revision info + const revisionNumber = document.getRevisionNumber(); + if (revisionNumber) metadata.version = revisionNumber; + + const revisionRemark = document.getRevisionRemark(); + if (revisionRemark) metadata.publishedBy = revisionRemark; + + const revisionDate = document.getRevisionDate(); + if (revisionDate) metadata.publicationDate = revisionDate; - // Extract the content (everything after the header) - let content = lines.slice(headerEndIndex).join('\n'); + // Map attributes to metadata (but skip version and publishedBy if we already have them from revision) + mapAttributesToMetadata(attributes, metadata, true); - // Remove metadata attributes from sections in the content - content = content.replace(/^:([^:]+):\s*(.+)$/gm, ''); + // If we got version from revision, don't override it with attribute + if (revisionNumber) { + metadata.version = revisionNumber; + } + + // If we got publishedBy from revision, don't override it with attribute + if (revisionRemark) { + metadata.publishedBy = revisionRemark; + } + // Handle tags and keywords + const tags = extractTagsFromAttributes(attributes); + if (tags.length > 0) { + metadata.tags = tags; + } + + const content = stripHeaderAndAttributes(document.getSource()); return { metadata, content }; } /** - * Extracts metadata from a section header - * @param sectionContent The section content including its header - * @returns Object containing section metadata and cleaned content + * Extracts metadata from a section using Asciidoctor */ export function extractSectionMetadata(inputSectionContent: string): { metadata: SectionMetadata; content: string; title: string; } { - const lines = inputSectionContent.split(/\r?\n/); - const metadata: SectionMetadata = {}; - let title = ''; - let headerEndIndex = -1; - let currentLine = 0; - - // Find the section title (first line starting with ==) - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const titleMatch = line.match(/^==\s+(.+)$/); - if (titleMatch) { - title = titleMatch[1].trim(); - metadata.title = title; - currentLine = i + 1; - break; - } - } - - // If no section title found, return empty metadata - if (!title) { + const asciidoctor = createProcessor(); + const document = asciidoctor.load(`= Temp\n\n${inputSectionContent}`, { standalone: false }) as Document; + const sections = document.getSections(); + + if (sections.length === 0) { return { metadata: {}, content: inputSectionContent, title: '' }; } - // Extract header metadata using shared function - currentLine = extractHeaderMetadata(lines, currentLine, metadata); + const section = sections[0]; + const title = section.getTitle() || ''; + const metadata: SectionMetadata = { title }; + + // Parse attributes from the section content + const attributes = parseSectionAttributes(inputSectionContent); - // If we didn't find an empty line, the header ends at the next section - if (currentLine < lines.length && lines[currentLine].trim() === '') { - headerEndIndex = currentLine + 1; // Skip the empty line - } else { - for (let i = currentLine; i < lines.length; i++) { - if (lines[i].match(/^==\s+/)) { - headerEndIndex = i; - break; - } - } + // Extract authors from section content + const authors = extractAuthorsFromHeader(inputSectionContent, true); + if (authors.length > 0) { + metadata.authors = authors; } - // If still no header end found, use the entire content - if (headerEndIndex === -1) { - headerEndIndex = lines.length; - } + // Map attributes to metadata (sections can have authors) + mapAttributesToMetadata(attributes, metadata, false); - // Extract the content (everything after the header) - const content = lines.slice(headerEndIndex).join('\n'); + // Handle tags and keywords + const tags = extractTagsFromAttributes(attributes); + if (tags.length > 0) { + metadata.tags = tags; + } + const content = stripHeaderAndAttributes(inputSectionContent, true); return { metadata, content, title }; } /** - * Splits AsciiDoc content into sections and extracts metadata from each - * @param content The full AsciiDoc content - * @returns Object containing document metadata and sections with their metadata + * Parses AsciiDoc content into sections with metadata */ export function parseAsciiDocWithMetadata(content: string): ParsedAsciiDoc { - // First extract document metadata + const asciidoctor = createProcessor(); + const document = asciidoctor.load(content, { standalone: false }) as Document; const { metadata: docMetadata } = extractDocumentMetadata(content); - // Find the document header end to get the content after the header + // Parse the original content to find section attributes const lines = content.split(/\r?\n/); - let currentLine = 0; - - // Find the document title - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const titleMatch = line.match(/^=\s+(.+)$/); - if (titleMatch) { - currentLine = i + 1; - break; - } - } - - // Extract header metadata to find where content starts - const tempMetadata: AsciiDocMetadata = {}; - currentLine = extractHeaderMetadata(lines, currentLine, tempMetadata); - - // Get the content after the header (including sections with metadata) - const docContent = lines.slice(currentLine).join('\n'); - - // Split into sections - const sections = splitAsciiDocSections(docContent); + const sectionsWithMetadata: Array<{ + metadata: SectionMetadata; + content: string; + title: string; + }> = []; + let currentSection: string | null = null; + let currentSectionContent: string[] = []; - // Extract metadata from each section - const sectionsWithMetadata = sections.map(section => { - return extractSectionMetadata(section); - }); - - return { - metadata: docMetadata, - content: docContent, - sections: sectionsWithMetadata - }; -} - -/** - * Splits AsciiDoc content into sections at each '==' header - * @param content The AsciiDoc content (without document header) - * @returns Array of section strings - */ -function splitAsciiDocSections(content: string): string[] { - const lines = content.split(/\r?\n/); - const sections: string[] = []; - let currentSection: string[] = []; - let inSection = false; - for (const line of lines) { - // Check if this is a section header if (line.match(/^==\s+/)) { - // Save the previous section if we have one - if (inSection && currentSection.length > 0) { - sections.push(currentSection.join('\n').trim()); - currentSection = []; + // Save previous section if exists + if (currentSection) { + const sectionContent = currentSectionContent.join('\n'); + sectionsWithMetadata.push(extractSectionMetadata(sectionContent)); } // Start new section - currentSection = [line]; - inSection = true; - } else if (inSection) { - // Add line to current section - currentSection.push(line); + currentSection = line; + currentSectionContent = [line]; + } else if (currentSection) { + currentSectionContent.push(line); } } - - // Add the last section - if (currentSection.length > 0) { - sections.push(currentSection.join('\n').trim()); + + // Save the last section + if (currentSection) { + const sectionContent = currentSectionContent.join('\n'); + sectionsWithMetadata.push(extractSectionMetadata(sectionContent)); } - return sections; + return { + metadata: docMetadata, + content: document.getSource(), + sections: sectionsWithMetadata + }; } /** * Converts metadata to Nostr event tags - * @param metadata The metadata object - * @returns Array of [tag, value] pairs */ export function metadataToTags(metadata: AsciiDocMetadata | SectionMetadata): [string, string][] { const tags: [string, string][] = []; - // Don't add title to tags since it has its own dedicated field - // if (metadata.title) { - // tags.push(['title', metadata.title]); - // } - - if (metadata.authors && metadata.authors.length > 0) { - metadata.authors.forEach(author => { - tags.push(['author', author]); - }); - } - - if (metadata.version) { - tags.push(['version', metadata.version]); + if (metadata.title) tags.push(['title', metadata.title]); + if (metadata.authors?.length) { + metadata.authors.forEach(author => tags.push(['author', author])); } - - if (metadata.edition) { - tags.push(['edition', metadata.edition]); + if (metadata.version) tags.push(['version', metadata.version]); + if (metadata.edition) tags.push(['edition', metadata.edition]); + if (metadata.publicationDate) tags.push(['published_on', metadata.publicationDate]); + if (metadata.publishedBy) tags.push(['published_by', metadata.publishedBy]); + if (metadata.summary) tags.push(['summary', metadata.summary]); + if (metadata.coverImage) tags.push(['image', metadata.coverImage]); + if (metadata.isbn) tags.push(['i', metadata.isbn]); + if (metadata.source) tags.push(['source', metadata.source]); + if (metadata.type) tags.push(['type', metadata.type]); + if (metadata.autoUpdate) tags.push(['auto-update', metadata.autoUpdate]); + if (metadata.tags?.length) { + metadata.tags.forEach(tag => tags.push(['t', tag])); } - if (metadata.publicationDate) { - tags.push(['published_on', metadata.publicationDate]); - } - - if (metadata.publishedBy) { - tags.push(['published_by', metadata.publishedBy]); - } - - if (metadata.summary) { - tags.push(['summary', metadata.summary]); - } - - if (metadata.coverImage) { - tags.push(['image', metadata.coverImage]); - } - - if (metadata.isbn) { - tags.push(['i', metadata.isbn]); - } + return tags; +} - if (metadata.source) { - tags.push(['source', metadata.source]); - } +/** + * Removes metadata from AsciiDoc content + */ +export function removeMetadataFromContent(content: string): string { + const { content: cleanedContent } = extractDocumentMetadata(content); + return cleanedContent; +} - if (metadata.type) { - tags.push(['type', metadata.type]); +/** + * Extracts metadata from content that only contains sections (no document header) + * This is useful when content flows from ZettelEditor to EventInput + */ +export function extractMetadataFromSectionsOnly(content: string): { + metadata: AsciiDocMetadata; + content: string; +} { + const lines = content.split(/\r?\n/); + const sections: Array<{ + metadata: SectionMetadata; + content: string; + title: string; + }> = []; + + let currentSection: string | null = null; + let currentSectionContent: string[] = []; + + // Parse sections from the content + for (const line of lines) { + if (line.match(/^==\s+/)) { + // Save previous section if exists + if (currentSection) { + const sectionContent = currentSectionContent.join('\n'); + sections.push(extractSectionMetadata(sectionContent)); + } + + // Start new section + currentSection = line; + currentSectionContent = [line]; + } else if (currentSection) { + currentSectionContent.push(line); + } } - - if (metadata.autoUpdate) { - tags.push(['auto-update', metadata.autoUpdate]); + + // Save the last section + if (currentSection) { + const sectionContent = currentSectionContent.join('\n'); + sections.push(extractSectionMetadata(sectionContent)); } - - if (metadata.tags && metadata.tags.length > 0) { - metadata.tags.forEach(tag => { - tags.push(['t', tag]); - }); + + // For section-only content, we don't have document metadata + // Return the first section's title as the document title if available + const metadata: AsciiDocMetadata = {}; + if (sections.length > 0 && sections[0].title) { + metadata.title = sections[0].title; } - - return tags; + + return { metadata, content }; } /** - * Removes metadata from AsciiDoc content, leaving only the actual content - * @param content The full AsciiDoc content - * @returns Cleaned content without metadata + * Smart metadata extraction that handles both document headers and section-only content */ -export function removeMetadataFromContent(content: string): string { - const { content: docContent } = extractDocumentMetadata(content); - - // Remove metadata attributes from sections in the content - const cleanedContent = docContent.replace(/^:([^:]+):\s*(.+)$/gm, ''); +export function extractSmartMetadata(content: string): { + metadata: AsciiDocMetadata; + content: string; +} { + // Check if content has a document header + const hasDocumentHeader = content.match(/^=\s+/m); - return cleanedContent; + if (hasDocumentHeader) { + // Check if it's a minimal document header (just title, no other metadata) + const lines = content.split(/\r?\n/); + const titleLine = lines.find(line => line.match(/^=\s+/)); + const hasOtherMetadata = lines.some(line => + line.includes('<') || // author line + line.match(/^.+,\s*.+:\s*.+$/) // revision line + ); + + if (hasOtherMetadata) { + // Full document with metadata - use standard extraction + return extractDocumentMetadata(content); + } else { + // Minimal document header (just title) - preserve the title line for 30040 events + const title = titleLine?.replace(/^=\s+/, '').trim(); + const metadata: AsciiDocMetadata = {}; + if (title) { + metadata.title = title; + } + + // Keep the title line in content for 30040 events + return { metadata, content }; + } + } else { + return extractMetadataFromSectionsOnly(content); + } } \ No newline at end of file diff --git a/src/routes/new/compose/+page.svelte b/src/routes/new/compose/+page.svelte index 33704a7..a62ffe8 100644 --- a/src/routes/new/compose/+page.svelte +++ b/src/routes/new/compose/+page.svelte @@ -5,7 +5,7 @@ import { goto } from "$app/navigation"; import { nip19 } from "nostr-tools"; import { publishMultipleZettels } from "$lib/services/publisher"; - import { parseAsciiDocSections } from "$lib/utils/ZettelParser"; + import { parseAsciiDocWithMetadata } from "$lib/utils/asciidoc_metadata"; let content = $state(""); let showPreview = $state(false); @@ -44,12 +44,12 @@ const errors = results.filter(r => !r.success && r.error).map(r => r.error!); // Extract successful events with their titles - const sections = parseAsciiDocSections(content, 2); + const parsed = parseAsciiDocWithMetadata(content); const successfulEvents = results .filter(r => r.success && r.eventId) .map((r, index) => ({ eventId: r.eventId!, - title: sections[index]?.title || `Note ${index + 1}` + title: parsed.sections[index]?.title || `Note ${index + 1}` })); // Extract failed events with their titles and errors @@ -57,7 +57,7 @@ .map((r, index) => ({ result: r, index })) .filter(({ result }) => !result.success) .map(({ result, index }) => ({ - title: sections[index]?.title || `Note ${index + 1}`, + title: parsed.sections[index]?.title || `Note ${index + 1}`, error: result.error || 'Unknown error', sectionIndex: index })); @@ -78,8 +78,8 @@ isPublishing = true; // Get the specific section content - const sections = parseAsciiDocSections(content, 2); - const section = sections[sectionIndex]; + const parsed = parseAsciiDocWithMetadata(content); + const section = parsed.sections[sectionIndex]; if (!section) return; // Reconstruct the section content for publishing diff --git a/tests/unit/metadataExtraction.test.ts b/tests/unit/metadataExtraction.test.ts index 145f23e..65a50b8 100644 --- a/tests/unit/metadataExtraction.test.ts +++ b/tests/unit/metadataExtraction.test.ts @@ -3,16 +3,16 @@ import { extractDocumentMetadata, extractSectionMetadata, parseAsciiDocWithMetadata, - metadataToTags + metadataToTags, + extractSmartMetadata } from "../../src/lib/utils/asciidoc_metadata.ts"; describe("AsciiDoc Metadata Extraction", () => { const testContent = `= Test Document with Metadata John Doe -1.0, 2024-01-15, Alexandria Test +1.0, 2024-01-15: Alexandria Test :summary: This is a test document for metadata extraction :author: Jane Smith -:version: 2.0 :published_on: 2024-01-15 :published_by: Alexandria Project :type: article @@ -78,6 +78,53 @@ This is the content of the first section.`; expect(content).toBe("This is the content of the first section."); }); + it("extractSectionMetadata should extract standalone author names and remove them from content", () => { + const sectionContent = `== Section Header1 +Stella +:description: Some summary + +Some context text`; + + const { metadata, content, title } = extractSectionMetadata(sectionContent); + + expect(title).toBe("Section Header1"); + expect(metadata.authors).toEqual(["Stella"]); + expect(metadata.summary).toBe("Some summary"); + expect(content.trim()).toBe("Some context text"); + }); + + it("extractSectionMetadata should handle multiple standalone author names", () => { + const sectionContent = `== Section Header1 +Stella +:author: John Doe +:description: Some summary + +Some context text`; + + const { metadata, content, title } = extractSectionMetadata(sectionContent); + + expect(title).toBe("Section Header1"); + expect(metadata.authors).toEqual(["Stella", "John Doe"]); + expect(metadata.summary).toBe("Some summary"); + expect(content.trim()).toBe("Some context text"); + }); + + it("extractSectionMetadata should not extract non-author lines as authors", () => { + const sectionContent = `== Section Header1 +Stella +This is not an author line +:description: Some summary + +Some context text`; + + const { metadata, content, title } = extractSectionMetadata(sectionContent); + + expect(title).toBe("Section Header1"); + expect(metadata.authors).toEqual(["Stella"]); + expect(metadata.summary).toBe("Some summary"); + expect(content.trim()).toBe("This is not an author line\nSome context text"); + }); + it("parseAsciiDocWithMetadata should parse complete document", () => { const parsed = parseAsciiDocWithMetadata(testContent); @@ -132,7 +179,7 @@ index card`; const contentWithKeywords = `= Test Document :keywords: keyword1, keyword2, keyword3 -Content here.`; +Some content here.`; const { metadata } = extractDocumentMetadata(contentWithKeywords); @@ -144,7 +191,7 @@ Content here.`; :tags: tag1, tag2 :keywords: keyword1, keyword2 -Content here.`; +Some content here.`; const { metadata } = extractDocumentMetadata(contentWithBoth); @@ -180,4 +227,96 @@ Content here.`; expect(summaryMetadata.summary).toBe("This is a summary"); expect(descriptionMetadata.summary).toBe("This is a description"); }); + + describe('Smart metadata extraction', () => { + it('should handle section-only content correctly', () => { + const sectionOnlyContent = `== First Section +:author: Section Author +:description: This is the first section +:tags: section1, content + +This is the content of the first section. + +== Second Section +:summary: This is the second section +:type: chapter + +This is the content of the second section.`; + + const { metadata, content } = extractSmartMetadata(sectionOnlyContent); + + // Should extract title from first section + expect(metadata.title).toBe('First Section'); + + // Should not have document-level metadata since there's no document header + expect(metadata.authors).toBeUndefined(); + expect(metadata.version).toBeUndefined(); + expect(metadata.publicationDate).toBeUndefined(); + + // Content should be preserved + expect(content).toBe(sectionOnlyContent); + }); + + it('should handle minimal document header (just title) correctly', () => { + const minimalDocumentHeader = `= Test Document + +== First Section +:author: Section Author +:description: This is the first section + +This is the content of the first section. + +== Second Section +:summary: This is the second section +:type: chapter + +This is the content of the second section.`; + + const { metadata, content } = extractSmartMetadata(minimalDocumentHeader); + + // Should extract title from document header + expect(metadata.title).toBe('Test Document'); + + // Should not have document-level metadata since there's no other metadata + expect(metadata.authors).toBeUndefined(); + // Note: version might be set from section attributes like :type: chapter + expect(metadata.publicationDate).toBeUndefined(); + + // Content should preserve the title line for 30040 events + expect(content).toContain('= Test Document'); + expect(content).toContain('== First Section'); + expect(content).toContain('== Second Section'); + }); + + it('should handle document with full header correctly', () => { + const documentWithHeader = `= Test Document +John Doe +1.0, 2024-01-15: Alexandria Test +:summary: This is a test document +:author: Jane Smith + +== First Section +:author: Section Author +:description: This is the first section + +This is the content.`; + + const { metadata, content } = extractSmartMetadata(documentWithHeader); + + // Should extract document-level metadata + expect(metadata.title).toBe('Test Document'); + expect(metadata.authors).toEqual(['John Doe', 'Jane Smith']); + expect(metadata.version).toBe('1.0'); + expect(metadata.publishedBy).toBe('Alexandria Test'); + expect(metadata.publicationDate).toBe('2024-01-15'); + expect(metadata.summary).toBe('This is a test document'); + + // Content should be cleaned + expect(content).not.toContain('= Test Document'); + expect(content).not.toContain('John Doe '); + expect(content).not.toContain('1.0, 2024-01-15: Alexandria Test'); + expect(content).not.toContain(':summary: This is a test document'); + expect(content).not.toContain(':author: Jane Smith'); + }); + }); }); \ No newline at end of file From 0ada8c2b0a6dab51c1a67d6620513666b31822a9 Mon Sep 17 00:00:00 2001 From: silberengel Date: Sat, 2 Aug 2025 02:44:27 +0200 Subject: [PATCH 8/8] fixed build --- package-lock.json | 216 +++++++++--------- src/lib/utils/network_detection.ts | 2 - .../publication/[type]/[identifier]/+page.ts | 2 +- 3 files changed, 113 insertions(+), 107 deletions(-) diff --git a/package-lock.json b/package-lock.json index 37b6de5..95e0f71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,7 @@ "tailwindcss": "^3.4.17", "tslib": "2.8.x", "typescript": "^5.8.3", - "vite": "^7.0.5", + "vite": "^6.3.5", "vitest": "^3.1.3" } }, @@ -2783,37 +2783,19 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" } }, "node_modules/cliui": { @@ -3729,15 +3711,6 @@ } } }, - "node_modules/eslint-plugin-svelte/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -5980,25 +5953,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/require-directory": { @@ -6506,38 +6471,10 @@ "typescript": ">=5.0.0" } }, - "node_modules/svelte-check/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/svelte-check/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/svelte-eslint-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.0.tgz", - "integrity": "sha512-VCgMHKV7UtOGcGLGNFSbmdm6kEKjtzo5nnpGU/mnx4OsFY6bZ7QwRF5DUx+Hokw5Lvdyo8dpk8B1m8mliomrNg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.1.tgz", + "integrity": "sha512-0Iztj5vcOVOVkhy1pbo5uA9r+d3yaVoE5XPc9eABIWDOSJZ2mOsZ4D+t45rphWCOr0uMw3jtSG2fh2e7GvKnPg==", "dev": true, "license": "MIT", "dependencies": { @@ -6734,6 +6671,54 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tailwindcss/node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", @@ -6782,6 +6767,30 @@ "node": ">=4" } }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -7032,24 +7041,24 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", - "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.40.0", - "tinyglobby": "^0.2.14" + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -7058,14 +7067,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", - "less": "^4.0.0", + "less": "*", "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -7367,14 +7376,13 @@ } }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "bin": { - "yaml": "bin.mjs" - }, + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 14.6" + "node": ">= 6" } }, "node_modules/yargs": { diff --git a/src/lib/utils/network_detection.ts b/src/lib/utils/network_detection.ts index 1278503..b7a7315 100644 --- a/src/lib/utils/network_detection.ts +++ b/src/lib/utils/network_detection.ts @@ -157,8 +157,6 @@ export function startNetworkMonitoring( ): () => void { let lastCondition: NetworkCondition | null = null; let intervalId: ReturnType | null = null; - // deno-lint-ignore no-explicit-any - let intervalId: any = null; const checkNetwork = async () => { try { diff --git a/src/routes/publication/[type]/[identifier]/+page.ts b/src/routes/publication/[type]/[identifier]/+page.ts index 6de9d27..810a4fb 100644 --- a/src/routes/publication/[type]/[identifier]/+page.ts +++ b/src/routes/publication/[type]/[identifier]/+page.ts @@ -4,7 +4,7 @@ import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; import { browser } from "$app/environment"; -export const load: PageLoad = async ({ params }) => { +export const load: PageLoad = async ({ params }: { params: { type: string; identifier: string } }) => { const { type, identifier } = params; // Only fetch on the client side where WebSocket is available