From 1635633efd0e9d5039341d59c13e5bc73bea5e68 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 18 May 2025 23:40:22 +0200 Subject: [PATCH 01/17] wiki disambiguation page alpha --- src/lib/components/WikiCard.svelte | 38 +++ src/lib/utils/markup/basicMarkupParser.ts | 2 +- src/lib/utils/nostrUtils.ts | 5 +- src/lib/wiki.ts | 100 ++++++++ src/routes/wiki/+page.svelte | 274 ++++++++++++++++++++++ src/routes/wiki/+page.ts | 25 ++ 6 files changed, 441 insertions(+), 3 deletions(-) create mode 100644 src/lib/components/WikiCard.svelte create mode 100644 src/lib/wiki.ts create mode 100644 src/routes/wiki/+page.svelte create mode 100644 src/routes/wiki/+page.ts diff --git a/src/lib/components/WikiCard.svelte b/src/lib/components/WikiCard.svelte new file mode 100644 index 0000000..e608591 --- /dev/null +++ b/src/lib/components/WikiCard.svelte @@ -0,0 +1,38 @@ + + + + + \ No newline at end of file diff --git a/src/lib/utils/markup/basicMarkupParser.ts b/src/lib/utils/markup/basicMarkupParser.ts index cbd843b..d0d6ff1 100644 --- a/src/lib/utils/markup/basicMarkupParser.ts +++ b/src/lib/utils/markup/basicMarkupParser.ts @@ -142,7 +142,7 @@ function replaceWikilinks(text: string): string { return text.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, (_match, target, label) => { const normalized = normalizeDTag(target.trim()); const display = (label || target).trim(); - const url = `./publication?d=${normalized}`; + const url = `./wiki?d=${normalized}`; // Output as a clickable with the [[display]] format and matching link colors return `${display}`; }); diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index f702d24..c59e84b 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -24,7 +24,7 @@ function escapeHtml(text: string): string { /** * Get user metadata for a nostr identifier (npub or nprofile) */ -export async function getUserMetadata(identifier: string): Promise<{name?: string, displayName?: string}> { +export async function getUserMetadata(identifier: string): Promise<{name?: string, displayName?: string, nip05?: string}> { // Remove nostr: prefix if present const cleanId = identifier.replace(/^nostr:/, ''); @@ -73,7 +73,8 @@ export async function getUserMetadata(identifier: string): Promise<{name?: strin const metadata = { name: profile.name || fallback.name, - displayName: profile.displayName + displayName: profile.displayName, + nip05: profile.nip05 }; npubCache.set(cleanId, metadata); diff --git a/src/lib/wiki.ts b/src/lib/wiki.ts new file mode 100644 index 0000000..d545ffa --- /dev/null +++ b/src/lib/wiki.ts @@ -0,0 +1,100 @@ +import Asciidoctor from 'asciidoctor'; +import { parseBasicmarkup } from './utils/markup/basicMarkupParser'; +import { getUserMetadata } from './utils/nostrUtils'; +import { get } from 'svelte/store'; +import { ndkInstance } from '$lib/ndk'; +import { nip19 } from 'nostr-tools'; +import type { NDKEvent } from '@nostr-dev-kit/ndk'; + +async function fetchWikiEventById(id: string): Promise { + const ndk = get(ndkInstance); + if (!ndk) return null; + + let eventId = id; + // If bech32, decode to hex + if (id.startsWith('nevent') || id.startsWith('note') || id.startsWith('naddr')) { + try { + const decoded = nip19.decode(id); + if (decoded.type === 'nevent') { + eventId = decoded.data.id; + } else if (decoded.type === 'note') { + eventId = decoded.data; + } + } catch { + return null; + } + } + + // Fetch the event by id (hex) + const event = await ndk.fetchEvent({ ids: [eventId] }); + // Only return if it's a wiki event (kind 30818) + if (event && event.kind === 30818) { + return event; + } + return null; +} + +async function fetchWikiEventsByDTag(dtag: string): Promise { + const ndk = get(ndkInstance); + if (!ndk) return []; + + // Query for kind 30818 events with the given d-tag + const events = await ndk.fetchEvents({ + kinds: [30818], + '#d': [dtag] + }); + + // Convert Set to Array and return + return Array.from(events); +} + +// Placeholder: Fetch profile name for a pubkey (kind 0 event) +async function getProfileName(pubkey: string): Promise { + if (!pubkey) return 'unknown'; + const metadata = await getUserMetadata(pubkey); + return metadata.displayName || metadata.name || pubkey.slice(0, 10); +} + +export async function getWikiPageById(id: string) { + const event = await fetchWikiEventById(id); + if (!event) return null; + const pubhex = event.pubkey || ''; + const author = await getProfileName(pubhex); + const titleTag = event.tags?.find((tag: string[]) => tag[0] === 'title'); + const title = titleTag ? titleTag[1] : 'Untitled'; + const asciidoctor = Asciidoctor(); + const asciidocHtml = asciidoctor.convert(event.content).toString(); + // Optionally log for debugging: + // console.log('AsciiDoc HTML:', asciidocHtml); + const html = await parseBasicmarkup(asciidocHtml); + return { title, author, pubhex, html }; +} + +export async function searchWikiPagesByDTag(dtag: string) { + const events = await fetchWikiEventsByDTag(dtag); + // Return array of { title, pubhex, eventId, summary, nip05 } + return Promise.all(events.map(async (event: any) => { + const pubhex = event.pubkey || ''; + // Get title from 't' tag + const titleTag = event.tags?.find((tag: string[]) => tag[0] === 't'); + const title = titleTag ? titleTag[1] : 'Untitled'; + // Get summary from 'summary' tag + const summaryTag = event.tags?.find((tag: string[]) => tag[0] === 'summary'); + const summary = summaryTag ? summaryTag[1] : ''; + + // Get user metadata including NIP-05 + const metadata = await getUserMetadata(pubhex); + const nip05 = metadata.nip05 || ''; + + // Construct human-readable URL + const urlPath = nip05 ? `${dtag}/${nip05}` : `${dtag}*${pubhex}`; + + return { + title, + pubhex, + eventId: event.id, + summary, + urlPath + }; + })); +} \ No newline at end of file diff --git a/src/routes/wiki/+page.svelte b/src/routes/wiki/+page.svelte new file mode 100644 index 0000000..3cc7587 --- /dev/null +++ b/src/routes/wiki/+page.svelte @@ -0,0 +1,274 @@ + + +
+
+ { + if (wikiPage) { + wikiPage = null; + wikiContent = null; + } + fetchResults(search); + }} + autocomplete="off" + class="w-full px-6 py-4 rounded-2xl border border-primary-200 shadow bg-primary-50 focus:outline-none focus:ring-2 focus:ring-primary-400 text-lg transition" + /> +
+ + {#if loading} +
+
+

Loading wiki content...

+
+ {:else if error} +
+

{error}

+
+ {:else if wikiPage && wikiContent} +
+

{wikiContent.title}

+
+ by +
+ {#if wikiPage.hashtags.length} +
+ {#each wikiPage.hashtags as tag} + #{tag} + {/each} +
+ {/if} + {#if wikiPage.summary} +
{wikiPage.summary}
+ {/if} +
+ {@html wikiContent.html} +
+
+ {:else if !search} +
+

+ Welcome to the Alexandria Wiki! +

+

+ Use the search bar above to find wiki pages on any topic. + Alexandria wiki pages are stored on Nostr relays and can be collaboratively added to by anyone with a Nostr key. + Search by topic, and you'll see all relevant wiki pages, each signed by its author. +

+
+ {:else if results.length === 0} +

No entries found for this topic.

+ {:else} + + {/if} +
+ +
{@html '

Hello

This is a test.

'}
\ No newline at end of file diff --git a/src/routes/wiki/+page.ts b/src/routes/wiki/+page.ts new file mode 100644 index 0000000..c009f61 --- /dev/null +++ b/src/routes/wiki/+page.ts @@ -0,0 +1,25 @@ +import type { Load } from '@sveltejs/kit'; +import { getWikiPageById, searchWikiPagesByDTag } from '../../lib/wiki'; + +export const load: Load = async ({ url }) => { + const id = url.searchParams.get('id'); + const d = url.searchParams.get('d'); + + if (d) { + // Disambiguation/search page for d-tag + const results = await searchWikiPagesByDTag(d); + return { disambiguation: true, results, dtag: d }; + } + + if (id) { + // Single wiki page by event id (bech32 or hex) + const page = await getWikiPageById(id); + if (!page) { + return { status: 404, error: 'Wiki page not found.' }; + } + return { disambiguation: false, page }; + } + + // No query: show only the search bar + return { disambiguation: true, results: [], dtag: '' }; +}; \ No newline at end of file From 512ea92c3a2c1e1b0684d28404b558ac5a3f960d Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 19 May 2025 09:44:11 +0200 Subject: [PATCH 02/17] interim commit --- README.md | 10 +- src/lib/components/Navigation.svelte | 5 +- src/lib/components/PublicationFeed.svelte | 78 ++-- src/lib/components/WikiCard.svelte | 27 +- src/lib/components/util/CardActions.svelte | 52 ++- src/lib/components/util/InlineProfile.svelte | 6 +- src/lib/components/util/Profile.svelte | 2 +- src/lib/consts.ts | 2 +- src/lib/utils/markup/MarkupInfo.md | 2 +- src/lib/utils/mime.ts | 2 +- src/lib/utils/nostrUtils.ts | 65 +++- src/lib/wiki.ts | 159 ++++++-- src/routes/about/+page.svelte | 2 +- src/routes/contact/+page.svelte | 2 +- src/routes/events/+page.svelte | 367 +++++++++++++++++++ src/routes/wiki/+page.svelte | 249 +++++++------ tests/integration/markupIntegration.test.ts | 4 +- tests/unit/advancedMarkupParser.test.ts | 2 +- tests/unit/basicMarkupParser.test.ts | 2 +- 19 files changed, 816 insertions(+), 222 deletions(-) create mode 100644 src/routes/events/+page.svelte diff --git a/README.md b/README.md index b7cffbb..f82fb12 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ # Alexandria Alexandria is a reader and writer for curated publications, including e-books. -For a thorough introduction, please refer to our [project documention](https://next-alexandria.gitcitadel.eu/publication?d=gitcitadel-project-documentation-by-stella-v-1), viewable on Alexandria, or to the Alexandria [About page](https://next-alexandria.gitcitadel.eu/about). +For a thorough introduction, please refer to our [project documention](./publication?d=gitcitadel-project-documentation-by-stella-v-1), viewable on Alexandria, or to the Alexandria [About page](./about). ## Issues and Patches -If you would like to suggest a feature or report a bug, please use the [Alexandria Contact page](https://next-alexandria.gitcitadel.eu/contact). +If you would like to suggest a feature or report a bug, please use the [Alexandria Contact page](./contact). -You can also contact us [on Nostr](https://njump.me/nprofile1qqsggm4l0xs23qfjwnkfwf6fqcs66s3lz637gaxhl4nwd2vtle8rnfqprfmhxue69uhhg6r9vehhyetnwshxummnw3erztnrdaks5zhueg), directly. +You can also contact us [on Nostr](./events?id=nprofile1qqsggm4l0xs23qfjwnkfwf6fqcs66s3lz637gaxhl4nwd2vtle8rnfqprfmhxue69uhhg6r9vehhyetnwshxummnw3erztnrdaks5zhueg), directly. ## Developing @@ -73,7 +73,7 @@ To run the container, in detached mode (-d): docker run -d --rm --name=gc-alexandria -p 4174:80 gc-alexandria ``` -The container is then viewable on your [local machine](http://localhost:4174). +The container is then viewable on your [local machine](./). If you want to see the container process (assuming it's the last process to start), enter: @@ -118,4 +118,4 @@ npx playwright test ## Markup Support -Alexandria supports both Markdown and AsciiDoc markup for different content types. For a detailed list of supported tags and features in the basic and advanced markdown parsers, as well as information about AsciiDoc usage for publications and wikis, see [MarkupInfo.md](src/lib/utils/markup/MarkupInfo.md). \ No newline at end of file +Alexandria supports both Markdown and AsciiDoc markup for different content types. For a detailed list of supported tags and features in the basic and advanced markdown parsers, as well as information about AsciiDoc usage for publications and wikis, see [MarkupInfo.md](./src/lib/utils/markup/MarkupInfo.md). \ No newline at end of file diff --git a/src/lib/components/Navigation.svelte b/src/lib/components/Navigation.svelte index fab2c7d..986f246 100644 --- a/src/lib/components/Navigation.svelte +++ b/src/lib/components/Navigation.svelte @@ -11,7 +11,6 @@ let { class: className = "" } = $props(); - let leftMenuOpen = $state(false); @@ -25,7 +24,9 @@ - Publish + Publications + Wiki + Events Visualize Getting Started About diff --git a/src/lib/components/PublicationFeed.svelte b/src/lib/components/PublicationFeed.svelte index a6c28e8..56cc4e1 100644 --- a/src/lib/components/PublicationFeed.svelte +++ b/src/lib/components/PublicationFeed.svelte @@ -2,6 +2,7 @@ import { indexKind } from '$lib/consts'; import { ndkInstance } from '$lib/ndk'; import { filterValidIndexEvents } from '$lib/utils'; + import { fetchEventWithFallback } from '$lib/utils/nostrUtils'; import { NDKRelaySet, type NDKEvent } from '@nostr-dev-kit/ndk'; import { Button, P, Skeleton, Spinner } from 'flowbite-svelte'; import ArticleHeader from './PublicationHeader.svelte'; @@ -20,40 +21,57 @@ async function getEvents( before: number | undefined = undefined, ): Promise { - let eventSet = await $ndkInstance.fetchEvents( - { + try { + // First try to fetch a single event to verify we can connect to the relays + const testEvent = await fetchEventWithFallback($ndkInstance, { kinds: [indexKind], - limit: 16, - until: before, - }, - { - groupable: false, - skipVerification: false, - skipValidation: false, - }, - NDKRelaySet.fromRelayUrls(relays, $ndkInstance) - ); - eventSet = filterValidIndexEvents(eventSet); - - let eventArray = Array.from(eventSet); - eventArray?.sort((a, b) => b.created_at! - a.created_at!); - - if (!eventArray) { - return; - } + limit: 1, + until: before + }); - endOfFeed = eventArray?.at(eventArray.length - 1)?.id === eventsInView?.at(eventsInView.length - 1)?.id; + if (!testEvent) { + console.warn('No events found in initial fetch'); + return; + } - if (endOfFeed) { - return; - } + // If we found an event, proceed with fetching the full set + let eventSet = await $ndkInstance.fetchEvents( + { + kinds: [indexKind], + limit: 16, + until: before, + }, + { + groupable: false, + skipVerification: false, + skipValidation: false, + }, + NDKRelaySet.fromRelayUrls(relays, $ndkInstance) + ); + eventSet = filterValidIndexEvents(eventSet); + + let eventArray = Array.from(eventSet); + eventArray?.sort((a, b) => b.created_at! - a.created_at!); + + if (!eventArray) { + return; + } - const eventMap = new Map([...eventsInView, ...eventArray].map(event => [event.id, event])); - const allEvents = Array.from(eventMap.values()); - const uniqueIds = new Set(allEvents.map(event => event.id)); - eventsInView = Array.from(uniqueIds) - .map(id => eventMap.get(id)) - .filter(event => event != null) as NDKEvent[]; + endOfFeed = eventArray?.at(eventArray.length - 1)?.id === eventsInView?.at(eventsInView.length - 1)?.id; + + if (endOfFeed) { + return; + } + + const eventMap = new Map([...eventsInView, ...eventArray].map(event => [event.id, event])); + const allEvents = Array.from(eventMap.values()); + const uniqueIds = new Set(allEvents.map(event => event.id)); + eventsInView = Array.from(uniqueIds) + .map(id => eventMap.get(id)) + .filter(event => event != null) as NDKEvent[]; + } catch (err) { + console.error('Error fetching events:', err); + } } const getSkeletonIds = (): string[] => { diff --git a/src/lib/components/WikiCard.svelte b/src/lib/components/WikiCard.svelte index e608591..ae84c7f 100644 --- a/src/lib/components/WikiCard.svelte +++ b/src/lib/components/WikiCard.svelte @@ -2,14 +2,19 @@ import { Card } from "flowbite-svelte"; import InlineProfile from "$components/util/InlineProfile.svelte"; - let { title, pubhex, eventId, summary, urlPath, hashtags = [] } = $props<{ - title: string; - pubhex: string; - eventId: string; - summary: string; - urlPath: string; - hashtags?: string[]; - }>(); + export let title: string; + export let pubhex: string; + export let eventId: string; + export let summary: string; + export let urlPath: string; + export let hashtags: string[] = []; + export let html: string = ''; + + let expanded = false; + $: preview = html.slice(0, 250); + + // Logging for debug + console.log('WikiCard props:', { title, pubhex, eventId, summary, urlPath, hashtags }); @@ -32,6 +37,12 @@ {/if} +
+ {@html expanded ? html : preview} + {#if !expanded && html.length > 250} + + {/if} +
diff --git a/src/lib/components/util/CardActions.svelte b/src/lib/components/util/CardActions.svelte index aa15174..a1e3a3a 100644 --- a/src/lib/components/util/CardActions.svelte +++ b/src/lib/components/util/CardActions.svelte @@ -11,6 +11,7 @@ import { standardRelays } from "$lib/consts"; import { neventEncode, naddrEncode } from "$lib/utils"; import InlineProfile from "$components/util/InlineProfile.svelte"; + import { goto } from "$app/navigation"; let { event } = $props(); @@ -78,10 +79,41 @@ } function viewDetails() { - console.log('Details'); detailsModalOpen = true; } + // --- Custom JSON pretty-printer with NIP-33 address hyperlinking --- + /** + * Returns HTML for pretty-printed JSON, with NIP-33 addresses as links to /events?id=naddr1... + */ + function jsonWithNaddrLinks(obj: any): string { + const NIP33_REGEX = /\b(\d{5}:[a-f0-9]{64}:[a-zA-Z0-9._-]+)\b/g; + function replacer(_key: string, value: any) { + return value; + } + // Stringify with 2-space indent + let json = JSON.stringify(obj, replacer, 2); + // Replace NIP-33 addresses with links + json = json.replace(NIP33_REGEX, (match) => { + try { + const [kind, pubkey, dtag] = match.split(":"); + // Compose a fake event for naddrEncode + const fakeEvent = { + kind: parseInt(kind), + pubkey, + tags: [["d", dtag]], + }; + const naddr = naddrEncode(fakeEvent as any, standardRelays); + return `${match}`; + } catch { + return match; + } + }); + // Escape < and > for HTML safety, but allow our tags + json = json.replace(/&/g, '&').replace(//g, '>'); + json = json.replace(/<a /g, ''); + return json; + }
@@ -127,22 +159,11 @@ {/if} -
  • - -
  • {/if} - - -
    -
    {JSON.stringify(event.rawEvent(), null, 2)}
    -
    -
    diff --git a/src/lib/components/util/InlineProfile.svelte b/src/lib/components/util/InlineProfile.svelte index 4b9efe3..9ed9d5b 100644 --- a/src/lib/components/util/InlineProfile.svelte +++ b/src/lib/components/util/InlineProfile.svelte @@ -5,7 +5,7 @@ let { pubkey, title = null } = $props(); - const externalProfileDestination = 'https://njump.me/' + const externalProfileDestination = './events?id=' let loading = $state(true); let anon = $state(false); let npub = $state(''); @@ -45,9 +45,9 @@ {#if loading} {title ?? '…'} {:else if anon } - {shortenNpub(npub)} + {shortenNpub(npub)} {:else if npub } - + = 30000 && kind < 40000) { return 'addressable'; diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index c59e84b..123a19a 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -2,6 +2,9 @@ import { get } from 'svelte/store'; import { nip19 } from 'nostr-tools'; import { ndkInstance } from '$lib/ndk'; import { npubCache } from './npubCache'; +import NDK, { NDKEvent, NDKRelaySet } from "@nostr-dev-kit/ndk"; +import type { NDKFilter, NDKKind } from "@nostr-dev-kit/ndk"; +import { standardRelays, bootstrapRelays } from "$lib/consts"; // Regular expressions for Nostr identifiers - match the entire identifier including any prefix export const NOSTR_PROFILE_REGEX = /(?@${escapedText}`; + return `@${escapedText}`; } /** @@ -110,7 +113,7 @@ function createNoteLink(identifier: string): string { const escapedId = escapeHtml(cleanId); const escapedText = escapeHtml(shortId); - return `${escapedText}`; + return `${escapedText}`; } /** @@ -180,4 +183,62 @@ export async function getNpubFromNip05(nip05: string): Promise { console.error('Error getting npub from nip05:', error); return null; } +} + +/** + * Fetches an event using a two-step relay strategy: + * 1. First tries standard relays with timeout + * 2. Falls back to all relays if not found + * Always wraps result as NDKEvent + */ +export async function fetchEventWithFallback( + ndk: NDK, + filterOrId: string | NDKFilter, + timeoutMs: number = 3000 +): Promise { + const allRelays = Array.from(new Set([...standardRelays, ...bootstrapRelays])); + const relaySets = [ + NDKRelaySet.fromRelayUrls(standardRelays, ndk), + NDKRelaySet.fromRelayUrls(allRelays, ndk) + ]; + + async function withTimeout(promise: Promise): Promise { + return Promise.race([ + promise, + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeoutMs)) + ]); + } + + try { + let found: NDKEvent | null = null; + + // Try standard relays first + if (typeof filterOrId === 'string' && /^[0-9a-f]{64}$/i.test(filterOrId)) { + found = await withTimeout(ndk.fetchEvent({ ids: [filterOrId] }, undefined, relaySets[0])); + if (!found) { + // Fallback to all relays + found = await withTimeout(ndk.fetchEvent({ ids: [filterOrId] }, undefined, relaySets[1])); + } + } else { + const filter = typeof filterOrId === 'string' ? { ids: [filterOrId] } : filterOrId; + const results = await withTimeout(ndk.fetchEvents(filter, undefined, relaySets[0])); + found = results instanceof Set ? Array.from(results)[0] as NDKEvent : null; + if (!found) { + // Fallback to all relays + const fallbackResults = await withTimeout(ndk.fetchEvents(filter, undefined, relaySets[1])); + found = fallbackResults instanceof Set ? Array.from(fallbackResults)[0] as NDKEvent : null; + } + } + + if (!found) { + console.warn('Event not found after timeout. Some relays may be offline or slow.'); + return null; + } + + // Always wrap as NDKEvent + return found instanceof NDKEvent ? found : new NDKEvent(ndk, found); + } catch (err) { + console.error('Error in fetchEventWithFallback:', err); + return null; + } } \ No newline at end of file diff --git a/src/lib/wiki.ts b/src/lib/wiki.ts index d545ffa..42a3142 100644 --- a/src/lib/wiki.ts +++ b/src/lib/wiki.ts @@ -1,17 +1,24 @@ -import Asciidoctor from 'asciidoctor'; import { parseBasicmarkup } from './utils/markup/basicMarkupParser'; -import { getUserMetadata } from './utils/nostrUtils'; +import { getUserMetadata, fetchEventWithFallback } from './utils/nostrUtils'; import { get } from 'svelte/store'; import { ndkInstance } from '$lib/ndk'; import { nip19 } from 'nostr-tools'; import type { NDKEvent } from '@nostr-dev-kit/ndk'; +import type NDK from '@nostr-dev-kit/ndk'; +import Pharos from '$lib/parser.ts'; +import { wikiKind } from './consts'; -async function fetchWikiEventById(id: string): Promise { +/** + * Fetch a single wiki event by id (hex or bech32). + */ +export async function fetchWikiEventById(id: string): Promise { const ndk = get(ndkInstance); - if (!ndk) return null; + if (!ndk) { + console.warn('NDK instance not found in fetchWikiEventById'); + return null; + } let eventId = id; - // If bech32, decode to hex if (id.startsWith('nevent') || id.startsWith('note') || id.startsWith('naddr')) { try { const decoded = nip19.decode(id); @@ -20,75 +27,139 @@ async function fetchWikiEventById(id: string): Promise { } else if (decoded.type === 'note') { eventId = decoded.data; } - } catch { + } catch (e) { + console.error('Failed to decode id in fetchWikiEventById:', e); return null; } } - // Fetch the event by id (hex) - const event = await ndk.fetchEvent({ ids: [eventId] }); - // Only return if it's a wiki event (kind 30818) - if (event && event.kind === 30818) { + const event = await fetchEventWithFallback(ndk, eventId); + if (event && event.kind === wikiKind) { + console.log('Fetched wiki event:', event); return event; } + console.warn('No wiki event found for id:', eventId); return null; } -async function fetchWikiEventsByDTag(dtag: string): Promise { +/** + * Fetch all wiki events by d-tag. + */ +export async function fetchWikiEventsByDTag(dtag: string): Promise { const ndk = get(ndkInstance); - if (!ndk) return []; + if (!ndk) { + console.warn('NDK instance not found in fetchWikiEventsByDTag'); + return []; + } - // Query for kind 30818 events with the given d-tag - const events = await ndk.fetchEvents({ - kinds: [30818], + const event = await fetchEventWithFallback(ndk, { + kinds: [wikiKind], '#d': [dtag] }); + + if (!event) { + console.warn(`No wiki events found for dtag: ${dtag}`); + return []; + } - // Convert Set to Array and return - return Array.from(events); + // For d-tag queries, we want to get all matching events, not just the first one + const events = await ndk.fetchEvents({ + kinds: [wikiKind], + '#d': [dtag] + }); + + const arr = Array.from(events); + console.log(`Fetched ${arr.length} wiki events for dtag:`, dtag); + return arr; } -// Placeholder: Fetch profile name for a pubkey (kind 0 event) -async function getProfileName(pubkey: string): Promise { +/** + * Get a display name for a pubkey. + */ +export async function getProfileName(pubkey: string): Promise { if (!pubkey) return 'unknown'; const metadata = await getUserMetadata(pubkey); return metadata.displayName || metadata.name || pubkey.slice(0, 10); } -export async function getWikiPageById(id: string) { - const event = await fetchWikiEventById(id); - if (!event) return null; +/** + * Fetch and parse a wiki page by event id or nevent. + */ +export async function getWikiPageById(id: string, ndk: NDK) { + console.log('getWikiPageById: fetching wiki page for id', id); + if (!id) { + console.error('getWikiPageById: id is undefined'); + return null; + } + + let event; + try { + event = await fetchEventWithFallback(ndk, id); + if (!event) { + console.error('getWikiPageById: No event found for id:', id); + return null; + } + if (event.kind !== wikiKind) { + console.error('getWikiPageById: Event found but kind !== wikiKind:', event); + return null; + } + if (!event.content) { + console.error('getWikiPageById: Event has no content:', event); + return null; + } + if (!event.tags) { + console.error('getWikiPageById: Event has no tags:', event); + return null; + } + } catch (err) { + console.error('getWikiPageById: Exception fetching event:', err, 'id:', id); + return null; + } + const pubhex = event.pubkey || ''; - const author = await getProfileName(pubhex); - const titleTag = event.tags?.find((tag: string[]) => tag[0] === 'title'); + const titleTag = event.tags.find((tag: string[]) => tag[0] === 'title'); const title = titleTag ? titleTag[1] : 'Untitled'; - const asciidoctor = Asciidoctor(); - const asciidocHtml = asciidoctor.convert(event.content).toString(); - // Optionally log for debugging: - // console.log('AsciiDoc HTML:', asciidocHtml); - const html = await parseBasicmarkup(asciidocHtml); - return { title, author, pubhex, html }; + const summaryTag = event.tags.find((tag: string[]) => tag[0] === 'summary'); + const summary = summaryTag ? summaryTag[1] : ''; + const hashtags = event.tags.filter((tag: string[]) => tag[0] === 't').map((tag: string[]) => tag[1]) || []; + + let asciidoc = event.content; + if (!/^=\s/m.test(asciidoc)) { + console.warn('getWikiPageById: No document header found, prepending fake header for title:', title); + asciidoc = `= ${title}\n\n` + asciidoc; + } + + let html = ''; + try { + const pharos = new Pharos(ndk); + pharos.parse(asciidoc); + const pharosHtml = pharos.getHtml(); + html = await parseBasicmarkup(pharosHtml); + if (!html) { + console.error('getWikiPageById: Parsed HTML is empty for id:', id, 'event:', event); + } + } catch (err) { + console.error('getWikiPageById: Error parsing content:', err, 'event:', event); + return null; + } + + return { title, pubhex, eventId: event.id, summary, hashtags, html }; } +/** + * Search wiki pages by d-tag. + */ export async function searchWikiPagesByDTag(dtag: string) { const events = await fetchWikiEventsByDTag(dtag); - // Return array of { title, pubhex, eventId, summary, nip05 } - return Promise.all(events.map(async (event: any) => { + return Promise.all(events.map(async (event: NDKEvent) => { const pubhex = event.pubkey || ''; - // Get title from 't' tag const titleTag = event.tags?.find((tag: string[]) => tag[0] === 't'); const title = titleTag ? titleTag[1] : 'Untitled'; - // Get summary from 'summary' tag const summaryTag = event.tags?.find((tag: string[]) => tag[0] === 'summary'); const summary = summaryTag ? summaryTag[1] : ''; - - // Get user metadata including NIP-05 const metadata = await getUserMetadata(pubhex); const nip05 = metadata.nip05 || ''; - - // Construct human-readable URL const urlPath = nip05 ? `${dtag}/${nip05}` : `${dtag}*${pubhex}`; - return { title, pubhex, @@ -97,4 +168,14 @@ export async function searchWikiPagesByDTag(dtag: string) { urlPath }; })); +} + +/** + * Parse wiki content using Pharos and basic markup parser. + */ +export async function parseWikiContent(content: string, ndk: NDK): Promise { + const pharos = new Pharos(ndk); + pharos.parse(content); + const pharosHtml = pharos.getHtml(); + return await parseBasicmarkup(pharosHtml); } \ No newline at end of file diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte index 8f6a911..f449b74 100644 --- a/src/routes/about/+page.svelte +++ b/src/routes/about/+page.svelte @@ -45,7 +45,7 @@

    We are easiest to contact over our Nostr address GitCitadel. Or, you can visit us on our

    - You can contact us on Nostr GitCitadel or you can view submitted issues on the Alexandria repo page. + You can contact us on Nostr GitCitadel or you can view submitted issues on the Alexandria repo page.

    Submit an issue diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte new file mode 100644 index 0000000..bb1584d --- /dev/null +++ b/src/routes/events/+page.svelte @@ -0,0 +1,367 @@ + + +
    +
    +
    + Events +
    + +

    + Use this page to view any event (npub, nprofile, nevent, naddr, or hexID). +

    + +
    + e.key === 'Enter' && searchEvent()} + /> + +
    + + {#if error} + + {/if} + + {#if event} +
    + +
    + {neventEncode(event, standardRelays)} +
    + + +
    +

    {getEventTitle(event)}

    +
    + Author: + +
    +
    + Kind: + {event.kind} + ({getEventTypeDisplay(event)}) +
    + {#if getEventSummary(event)} +
    + Summary: +

    {getEventSummary(event)}

    +
    + {/if} + {#if getEventHashtags(event).length} +
    + Tags: +
    + {#each getEventHashtags(event) as tag} + #{tag} + {/each} +
    +
    + {/if} + + +
    + Content: + {#if event.kind === 0} + {#if profile} +
    + {#if profile.name} +
    Name: {profile.name}
    + {/if} + {#if profile.display_name} +
    Display Name: {profile.display_name}
    + {/if} + {#if profile.about} +
    About: {profile.about}
    + {/if} + {#if profile.picture} +
    + Picture: + Profile +
    + {/if} + {#if profile.banner} +
    + Banner: + Banner +
    + {/if} + {#if profile.website} +
    + Website: + {profile.website} +
    + {/if} + {#if profile.lud16} +
    + Lightning Address: {profile.lud16} +
    + {/if} + {#if profile.nip05} +
    + NIP-05: {profile.nip05} +
    + {/if} + +
    + {:else} +
    {event.content}
    + {/if} + {:else} +
    + {@html showFullContent ? parsedContent : contentPreview} + {#if !showFullContent && parsedContent.length > 250} + + {/if} +
    + {/if} +
    + + + {#if event.tags && event.tags.length} +
    + Event Tags: +
    + {#each event.tags as tag} + {@html renderTag(tag)} + {/each} +
    +
    + {/if} + + +
    + + Show Raw Event JSON + +
    +{JSON.stringify(event.rawEvent(), null, 2)}
    +            
    +
    +
    +
    + {/if} +
    +
    diff --git a/src/routes/wiki/+page.svelte b/src/routes/wiki/+page.svelte index 3cc7587..58abf1b 100644 --- a/src/routes/wiki/+page.svelte +++ b/src/routes/wiki/+page.svelte @@ -4,9 +4,12 @@ import { goto } from '$app/navigation'; import { onMount } from 'svelte'; import { ndkInstance } from '$lib/ndk'; - import { nip19 } from 'nostr-tools'; - import { getWikiPageById } from '$lib/wiki'; import { page } from '$app/stores'; + import { getWikiPageById, getProfileName } from '$lib/wiki'; + import { type NDKEvent } from '@nostr-dev-kit/ndk'; + import { neventEncode } from '$lib/utils'; + import { processNostrIdentifiers } from '$lib/utils/nostrUtils'; + import { standardRelays, wikiKind } from '$lib/consts'; // @ts-ignore Svelte linter false positive: hashtags is used in the template let { } = $props<{ @@ -18,21 +21,26 @@ hashtags?: string[]; }>(); - type WikiCardResult = { + type WikiPage = { title: string; pubhex: string; eventId: string; summary: string; - urlPath: string; hashtags: string[]; + html: string; }; - let search = $state(''); - let results: WikiCardResult[] = $state([]); + let searchInput = $state(''); + let results: WikiPage[] = $state([]); let loading = $state(false); - let wikiPage: WikiCardResult | null = $state(null); + let wikiPage: WikiPage | null = $state(null); let wikiContent: { title: string; author: string; pubhex: string; html: string } | null = $state(null); let error = $state(null); + let expandedContent = $state(false); + let contentPreview = $derived(() => { + if (!wikiPage) return ''; + return wikiPage.html.slice(0, 250); + }); function normalize(str: string) { return str.toLowerCase().replace(/[-_]/g, ' ').replace(/\s+/g, ' ').trim(); @@ -45,42 +53,48 @@ return; } loading = true; - const ndk = $ndkInstance; - if (!ndk) { - results = []; - loading = false; - return; - } - const events = await ndk.fetchEvents({ kinds: [30818] }); - const normQuery = normalize(query); - - // 1. Filter by title - let filtered = Array.from(events).filter((event: any) => { - const titleTag = event.tags?.find((tag: string[]) => tag[0] === 'title'); - const title = titleTag && titleTag[1]?.trim() ? titleTag[1] : 'Untitled'; - return normalize(title).includes(normQuery); - }); + error = null; + try { + const ndk = $ndkInstance; + if (!ndk) { + results = []; + error = 'NDK instance not available'; + loading = false; + return; + } + const events = await ndk.fetchEvents({ kinds: [wikiKind] }); + const normQuery = normalize(query); - // 2. If no title matches, filter by hashtags - if (filtered.length === 0) { - filtered = Array.from(events).filter((event: any) => { - // Find all tags that are hashtags (tag[0] === '#') - const hashtags = event.tags?.filter((tag: string[]) => tag[0] === '#').map((tag: string[]) => tag[1]) || []; - return hashtags.some((hashtag: string) => normalize(hashtag).includes(normQuery)); + // Filter by title or hashtags + let filtered = Array.from(events).filter((event: NDKEvent) => { + const titleTag = event.tags?.find((tag: string[]) => tag[0] === 'title'); + const title = titleTag && titleTag[1]?.trim() ? titleTag[1] : 'Untitled'; + const hashtags = event.tags?.filter((tag: string[]) => tag[0] === 't').map((tag: string[]) => tag[1]) || []; + + return normalize(title).includes(normQuery) || + hashtags.some((hashtag: string) => normalize(hashtag).includes(normQuery)); }); - } - results = await Promise.all(filtered.map(async (event: any) => { - const pubhex = event.pubkey || ''; - const titleTag = event.tags?.find((tag: string[]) => tag[0] === 'title'); - const title = titleTag && titleTag[1]?.trim() ? titleTag[1] : 'Untitled'; - const summaryTag = event.tags?.find((tag: string[]) => tag[0] === 'summary'); - const summary = summaryTag ? summaryTag[1] : ''; - const hashtags = event.tags?.filter((tag: string[]) => tag[0] === 't').map((tag: string[]) => tag[1]) || []; - const nevent = nip19.neventEncode({ id: event.id, relays: [] }); - return { title, pubhex, eventId: event.id, summary, urlPath: nevent, hashtags }; - })); - loading = false; + const pages = await Promise.all(filtered.map(async (event: NDKEvent) => { + const pageData = await getWikiPageById(event.id, ndk); + if (pageData) { + // Process Nostr identifiers in the HTML content + pageData.html = await processNostrIdentifiers(pageData.html); + } + if (event && typeof event.getMatchingTags !== 'function') { + console.error('Fetched event is not an NDKEvent:', event); + } + return pageData as WikiPage | null; + })); + + results = pages.filter((page): page is WikiPage => page !== null); + } catch (e) { + error = 'Error searching wiki pages'; + results = []; + console.error('fetchResults: Exception:', e); + } finally { + loading = false; + } } async function fetchWikiPageById(id: string) { @@ -91,69 +105,65 @@ if (!ndk) { wikiPage = null; wikiContent = null; + console.error('fetchWikiPageById: NDK instance not available'); return; } - let eventId = id; - if (id.startsWith('nevent')) { - const decoded = nip19.decode(id); - if (typeof decoded === 'string') { - eventId = decoded; - } else if (typeof decoded === 'object' && 'data' in decoded && typeof decoded.data === 'object' && 'id' in decoded.data) { - eventId = decoded.data.id; - } + if (!id) { + console.error('fetchWikiPageById: id is undefined'); + return; } - const event = await ndk.fetchEvent({ ids: [eventId] }); - if (event) { - const pubhex = event.pubkey || ''; - const titleTag = event.tags?.find((tag: string[]) => tag[0] === 'title'); - const title = titleTag && titleTag[1]?.trim() ? titleTag[1] : 'Untitled'; - const summaryTag = event.tags?.find((tag: string[]) => tag[0] === 'summary'); - const summary = summaryTag ? summaryTag[1] : ''; - const hashtags = event.tags?.filter((tag: string[]) => tag[0] === 't').map((tag: string[]) => tag[1]) || []; - wikiPage = { title, pubhex, eventId: event.id, summary, urlPath: id, hashtags }; - - // Fetch the full wiki content - const content = await getWikiPageById(id); - if (content) { - // const html = await parseBasicmarkup(asciidocHtml); - const html = content.html; - console.log('Final HTML:', html); - wikiContent = { - title: content.title, - author: content.author, - pubhex: content.pubhex, - html: html - }; - } else { - error = 'Failed to load wiki content'; + console.log('fetchWikiPageById: fetching wiki page for id', id); + const pageData = await getWikiPageById(id, ndk); + if (pageData) { + // Process Nostr identifiers in the HTML content + const processedHtml = await processNostrIdentifiers(pageData.html); + wikiPage = { + title: pageData.title, + pubhex: pageData.pubhex, + eventId: pageData.eventId, + summary: pageData.summary, + hashtags: pageData.hashtags, + html: processedHtml, + }; + wikiContent = { + title: pageData.title, + author: await getProfileName(pageData.pubhex), + pubhex: pageData.pubhex, + html: processedHtml + }; + if (!wikiPage.html) { + console.error('fetchWikiPageById: wikiPage.html is empty for id', id, wikiPage); } + console.log('wikiPage.html:', wikiPage?.html); } else { wikiPage = null; wikiContent = null; error = 'Wiki page not found'; + console.error('fetchWikiPageById: Wiki page not found for id', id); } } catch (e) { - console.error('Error fetching wiki page:', e); error = 'Error loading wiki page'; wikiPage = null; wikiContent = null; + console.error('fetchWikiPageById: Exception:', e); } finally { loading = false; } } - // Debounced effect for search + // Clear wikiPage if searching $effect(() => { - if (search && wikiPage) { + if (searchInput && wikiPage) { wikiPage = null; } }); + // Watch for ?id= in the URL and load the wiki page if present $effect(() => { const id = $page.url.searchParams.get('id'); if (id) { fetchWikiPageById(id); - search = ''; + searchInput = ''; results = []; } }); @@ -162,6 +172,20 @@ goto(`/wiki?id=${encodeURIComponent(urlPath)}`); } + function getNevent(eventId: string): string { + try { + const event = { id: eventId, kind: wikiKind } as NDKEvent; + return neventEncode(event, standardRelays); + } catch (e) { + console.error('Error encoding nevent:', e); + return eventId; + } + } + + function handleProfileClick(pubkey: string) { + goto(`/profile?pubkey=${pubkey}`); + } + onMount(() => { const params = new URLSearchParams(window.location.search); const d = params.get('d'); @@ -169,14 +193,14 @@ if (id) { wikiPage = null; fetchWikiPageById(id); - search = ''; + searchInput = ''; results = []; } else if (d) { - search = d; + searchInput = d; wikiPage = null; - fetchResults(search); + fetchResults(searchInput); } else { - search = ''; + searchInput = ''; results = []; wikiPage = null; } @@ -188,13 +212,13 @@ { if (wikiPage) { wikiPage = null; wikiContent = null; } - fetchResults(search); + fetchResults(searchInput); }} autocomplete="off" class="w-full px-6 py-4 rounded-2xl border border-primary-200 shadow bg-primary-50 focus:outline-none focus:ring-2 focus:ring-primary-400 text-lg transition" @@ -212,9 +236,15 @@ {:else if wikiPage && wikiContent}
    -

    {wikiContent.title}

    +
    {getNevent(wikiPage.eventId)}
    +

    {wikiPage.title}

    - by + by
    {#if wikiPage.hashtags.length}
    @@ -227,10 +257,23 @@
    {wikiPage.summary}
    {/if}
    - {@html wikiContent.html} + {#if wikiPage.html && wikiPage.html.trim().length > 0} + {#if event && typeof event.getMatchingTags === 'function'} + {@html wikiPage.html} + {:else if event} +
    Fetched event is not a valid NDKEvent. See console for details.
    + {/if} + {:else} +
    + No content found for this wiki page. +
    +              {JSON.stringify(wikiPage, null, 2)}
    +            
    +
    + {/if}
    - {:else if !search} + {:else if !searchInput}

    Welcome to the Alexandria Wiki! @@ -244,31 +287,17 @@ {:else if results.length === 0}

    No entries found for this topic.

    {:else} - - -
    {@html '

    Hello

    This is a test.

    '}
    \ No newline at end of file +
    \ No newline at end of file diff --git a/tests/integration/markupIntegration.test.ts b/tests/integration/markupIntegration.test.ts index 0bc7443..091f648 100644 --- a/tests/integration/markupIntegration.test.ts +++ b/tests/integration/markupIntegration.test.ts @@ -34,7 +34,7 @@ describe('Markup Integration Test', () => { // Hashtags expect(output).toContain('text-primary-600'); // Nostr identifiers (should be njump.me links) - expect(output).toContain('https://njump.me/npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z'); + expect(output).toContain('./events?id=npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z'); // Wikilinks expect(output).toContain('wikilink'); // YouTube iframe @@ -77,7 +77,7 @@ describe('Markup Integration Test', () => { // Hashtags expect(output).toContain('text-primary-600'); // Nostr identifiers (should be njump.me links) - expect(output).toContain('https://njump.me/npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z'); + expect(output).toContain('./events?id=npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z'); // Wikilinks expect(output).toContain('wikilink'); // YouTube iframe diff --git a/tests/unit/advancedMarkupParser.test.ts b/tests/unit/advancedMarkupParser.test.ts index 6e64327..0d868d1 100644 --- a/tests/unit/advancedMarkupParser.test.ts +++ b/tests/unit/advancedMarkupParser.test.ts @@ -69,7 +69,7 @@ describe('Advanced Markup Parser', () => { it('parses nostr identifiers', async () => { const input = 'npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; const output = await parseAdvancedmarkup(input); - expect(output).toContain('https://njump.me/npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); + expect(output).toContain('./events?id=npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); }); it('parses emoji shortcodes', async () => { diff --git a/tests/unit/basicMarkupParser.test.ts b/tests/unit/basicMarkupParser.test.ts index 45e0d46..4025b65 100644 --- a/tests/unit/basicMarkupParser.test.ts +++ b/tests/unit/basicMarkupParser.test.ts @@ -70,7 +70,7 @@ describe('Basic Markup Parser', () => { it('parses nostr identifiers', async () => { const input = 'npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; const output = await parseBasicmarkup(input); - expect(output).toContain('https://njump.me/npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); + expect(output).toContain('./events?id=npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); }); it('parses emoji shortcodes', async () => { From 7dd411c43f5538ce3452aca2fe3d9f170de29fd7 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 19 May 2025 10:47:16 +0200 Subject: [PATCH 03/17] publication, wiki, and universal event viewer implemented. still buggy --- src/lib/components/util/InlineProfile.svelte | 10 +- src/lib/wiki.ts | 11 +- src/routes/events/+page.svelte | 124 ++++++++++++------- src/routes/wiki/+page.svelte | 32 ++++- 4 files changed, 119 insertions(+), 58 deletions(-) diff --git a/src/lib/components/util/InlineProfile.svelte b/src/lib/components/util/InlineProfile.svelte index 9ed9d5b..5daa74f 100644 --- a/src/lib/components/util/InlineProfile.svelte +++ b/src/lib/components/util/InlineProfile.svelte @@ -3,7 +3,7 @@ import { type NDKUserProfile } from "@nostr-dev-kit/ndk"; import { ndkInstance } from '$lib/ndk'; - let { pubkey, title = null } = $props(); + let { pubkey, name = null } = $props(); const externalProfileDestination = './events?id=' let loading = $state(true); @@ -43,11 +43,11 @@ {#if loading} - {title ?? '…'} + {name ?? '…'} {:else if anon } - {shortenNpub(npub)} + {shortenNpub(npub)} {:else if npub } - + {username ?? shortenNpub(npub)} {:else} - {title ?? pubkey} + {name ?? pubkey} {/if} \ No newline at end of file diff --git a/src/lib/wiki.ts b/src/lib/wiki.ts index 42a3142..f329590 100644 --- a/src/lib/wiki.ts +++ b/src/lib/wiki.ts @@ -132,18 +132,21 @@ export async function getWikiPageById(id: string, ndk: NDK) { let html = ''; try { const pharos = new Pharos(ndk); + console.log('Pharos instance:', pharos); pharos.parse(asciidoc); const pharosHtml = pharos.getHtml(); - html = await parseBasicmarkup(pharosHtml); - if (!html) { - console.error('getWikiPageById: Parsed HTML is empty for id:', id, 'event:', event); + console.log('AsciiDoc:', asciidoc); + console.log('Pharos HTML:', pharosHtml); + html = await parseBasicmarkup(pharosHtml ?? ''); + if (!html || html.trim() === '') { + console.error('getWikiPageById: Parsed HTML is empty for id:', id, 'event:', event, 'asciidoc:', asciidoc, 'pharosHtml:', pharosHtml); } } catch (err) { console.error('getWikiPageById: Error parsing content:', err, 'event:', event); return null; } - return { title, pubhex, eventId: event.id, summary, hashtags, html }; + return { title, pubhex, eventId: event.id, summary, hashtags, html, content: event.content }; } /** diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte index bb1584d..b471ae3 100644 --- a/src/routes/events/+page.svelte +++ b/src/routes/events/+page.svelte @@ -78,6 +78,9 @@ error = 'Event not found'; } else { console.log('[Events] Event found:', event); + if (typeof event.getMatchingTags !== 'function') { + event = new NDKEvent(event.ndk || $ndkInstance, event); + } } } catch (err) { console.error('[Events] Error fetching event:', err, 'Query:', searchQuery); @@ -190,8 +193,7 @@ } }); - $: if (event && event.kind !== 0) { - // Only parse for non-profile events + $: if (event && event.kind !== 0 && event.content) { parseBasicmarkup(event.content).then(html => { parsedContent = html; contentPreview = html.slice(0, 250); @@ -201,6 +203,10 @@ $: profile = event && event.kind === 0 ? (() => { try { return JSON.parse(event.content); } catch { return null; } })() : null; + + $: profileTitle = event && event.kind === 0 && profile && profile.name + ? profile.name + : null;
    @@ -242,7 +248,7 @@
    {/if} - {#if event} + {#if event && typeof event.getMatchingTags === 'function'}
    @@ -251,7 +257,11 @@
    -

    {getEventTitle(event)}

    + {#if event.kind !== 0 && getEventTitle(event)} +

    {getEventTitle(event)}

    + {:else if event.kind === 0 && profile && profile.name} +

    {profile.name}

    + {/if}
    Author: @@ -283,45 +293,63 @@ Content: {#if event.kind === 0} {#if profile} -
    - {#if profile.name} -
    Name: {profile.name}
    - {/if} - {#if profile.display_name} -
    Display Name: {profile.display_name}
    - {/if} - {#if profile.about} -
    About: {profile.about}
    - {/if} - {#if profile.picture} -
    - Picture: - Profile -
    - {/if} - {#if profile.banner} -
    - Banner: - Banner -
    - {/if} - {#if profile.website} -
    - Website: - {profile.website} -
    - {/if} - {#if profile.lud16} -
    - Lightning Address: {profile.lud16} -
    - {/if} - {#if profile.nip05} -
    - NIP-05: {profile.nip05} -
    - {/if} - +
    +
    + {#if profile.name} +
    +
    Name:
    +
    {profile.name}
    +
    + {/if} + {#if profile.display_name} +
    +
    Display Name:
    +
    {profile.display_name}
    +
    + {/if} + {#if profile.about} +
    +
    About:
    +
    {profile.about}
    +
    + {/if} + {#if profile.picture} +
    +
    Picture:
    +
    + Profile +
    +
    + {/if} + {#if profile.banner} +
    +
    Banner:
    +
    + Banner +
    +
    + {/if} + {#if profile.website} +
    +
    Website:
    +
    + {profile.website} +
    +
    + {/if} + {#if profile.lud16} +
    +
    Lightning Address:
    +
    {profile.lud16}
    +
    + {/if} + {#if profile.nip05} +
    +
    NIP-05:
    +
    {profile.nip05}
    +
    + {/if} +
    {:else}
    {event.content}
    @@ -362,6 +390,16 @@
    + {#if !getEventTitle(event) && !event.content} +
    + No title or content available for this event. +
    +            {JSON.stringify(event.rawEvent(), null, 2)}
    +          
    +
    + {/if} + {:else if event} +
    Fetched event is not a valid NDKEvent. See console for details.
    {/if}
    diff --git a/src/routes/wiki/+page.svelte b/src/routes/wiki/+page.svelte index 58abf1b..466de75 100644 --- a/src/routes/wiki/+page.svelte +++ b/src/routes/wiki/+page.svelte @@ -10,7 +10,8 @@ import { neventEncode } from '$lib/utils'; import { processNostrIdentifiers } from '$lib/utils/nostrUtils'; import { standardRelays, wikiKind } from '$lib/consts'; - + import Pharos from '$lib/parser'; + import { parseBasicmarkup } from '$lib/utils/markup/basicMarkupParser'; // @ts-ignore Svelte linter false positive: hashtags is used in the template let { } = $props<{ title: string; @@ -28,6 +29,7 @@ summary: string; hashtags: string[]; html: string; + content: string; }; let searchInput = $state(''); @@ -124,6 +126,7 @@ summary: pageData.summary, hashtags: pageData.hashtags, html: processedHtml, + content: pageData.content, }; wikiContent = { title: pageData.title, @@ -205,6 +208,22 @@ wikiPage = null; } }); + + (async () => { + let html = ''; + try { + const pharos = new Pharos($ndkInstance); + pharos.parse('= Test\n\nHello world'); + const pharosHtml = pharos.getHtml(); + if (!pharosHtml || pharosHtml.trim() === '') { + console.error('Pharos failed to parse AsciiDoc:', '= Test\n\nHello world'); + } + html = await parseBasicmarkup(pharosHtml ?? ''); + console.log('Test parse result:', html); + } catch (err) { + console.error('Pharos parse error:', err); + } + })();
    @@ -258,14 +277,15 @@ {/if}
    {#if wikiPage.html && wikiPage.html.trim().length > 0} - {#if event && typeof event.getMatchingTags === 'function'} - {@html wikiPage.html} - {:else if event} -
    Fetched event is not a valid NDKEvent. See console for details.
    - {/if} + {@html wikiPage.html} {:else}
    No content found for this wiki page. + {#if wikiPage.content} +
    +                {wikiPage.content}
    +              
    + {/if}
                   {JSON.stringify(wikiPage, null, 2)}
                 
    From 39419d38f4b06208469dd4a39ffdde5d4b6b0b66 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 20 May 2025 06:17:34 +0200 Subject: [PATCH 04/17] Remove wiki stuff and update readme for issue #173 --- README.md | 12 +- src/lib/components/Navigation.svelte | 2 - src/lib/components/WikiCard.svelte | 49 ---- src/lib/wiki.ts | 184 --------------- src/routes/wiki/+page.svelte | 323 --------------------------- src/routes/wiki/+page.ts | 25 --- 6 files changed, 7 insertions(+), 588 deletions(-) delete mode 100644 src/lib/components/WikiCard.svelte delete mode 100644 src/lib/wiki.ts delete mode 100644 src/routes/wiki/+page.svelte delete mode 100644 src/routes/wiki/+page.ts diff --git a/README.md b/README.md index f82fb12..1438b73 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,15 @@ # Alexandria Alexandria is a reader and writer for curated publications, including e-books. -For a thorough introduction, please refer to our [project documention](./publication?d=gitcitadel-project-documentation-by-stella-v-1), viewable on Alexandria, or to the Alexandria [About page](./about). +For a thorough introduction, please refer to our [project documention](https://next-alexandria.gitcitadel.eu/publication?d=gitcitadel-project-documentation-by-stella-v-1), viewable on Alexandria, or to the Alexandria [About page](https://next-alexandria.gitcitadel.eu/about). + +It also contains a [universal event viewer](https://next-alexandria.gitcitadel.eu/events), with which you can search our relays, some aggregator relays, and your own relay list, to find and view event data. ## Issues and Patches -If you would like to suggest a feature or report a bug, please use the [Alexandria Contact page](./contact). +If you would like to suggest a feature or report a bug, please use the [Alexandria Contact page](https://next-alexandria.gitcitadel.eu/contact). -You can also contact us [on Nostr](./events?id=nprofile1qqsggm4l0xs23qfjwnkfwf6fqcs66s3lz637gaxhl4nwd2vtle8rnfqprfmhxue69uhhg6r9vehhyetnwshxummnw3erztnrdaks5zhueg), directly. +You can also contact us [on Nostr](https://next-alexandria.gitcitadel.eu/events?id=nprofile1qqsggm4l0xs23qfjwnkfwf6fqcs66s3lz637gaxhl4nwd2vtle8rnfqprfmhxue69uhhg6r9vehhyetnwshxummnw3erztnrdaks5zhueg), directly. ## Developing @@ -73,7 +75,7 @@ To run the container, in detached mode (-d): docker run -d --rm --name=gc-alexandria -p 4174:80 gc-alexandria ``` -The container is then viewable on your [local machine](./). +The container is then viewable on your [local machine](http://localhost:4174). If you want to see the container process (assuming it's the last process to start), enter: @@ -118,4 +120,4 @@ npx playwright test ## Markup Support -Alexandria supports both Markdown and AsciiDoc markup for different content types. For a detailed list of supported tags and features in the basic and advanced markdown parsers, as well as information about AsciiDoc usage for publications and wikis, see [MarkupInfo.md](./src/lib/utils/markup/MarkupInfo.md). \ No newline at end of file +Alexandria supports both Markdown and AsciiDoc markup for different content types. For a detailed list of supported tags and features in the basic and advanced markdown parsers, as well as information about AsciiDoc usage for publications and wikis, see [MarkupInfo.md](https://next-alexandria.gitcitadel.eu/src/lib/utils/markup/MarkupInfo.md). \ No newline at end of file diff --git a/src/lib/components/Navigation.svelte b/src/lib/components/Navigation.svelte index 986f246..d8503bd 100644 --- a/src/lib/components/Navigation.svelte +++ b/src/lib/components/Navigation.svelte @@ -10,7 +10,6 @@ import Login from "./Login.svelte"; let { class: className = "" } = $props(); - @@ -25,7 +24,6 @@
    Publications - Wiki Events Visualize Getting Started diff --git a/src/lib/components/WikiCard.svelte b/src/lib/components/WikiCard.svelte deleted file mode 100644 index ae84c7f..0000000 --- a/src/lib/components/WikiCard.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/lib/wiki.ts b/src/lib/wiki.ts deleted file mode 100644 index f329590..0000000 --- a/src/lib/wiki.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { parseBasicmarkup } from './utils/markup/basicMarkupParser'; -import { getUserMetadata, fetchEventWithFallback } from './utils/nostrUtils'; -import { get } from 'svelte/store'; -import { ndkInstance } from '$lib/ndk'; -import { nip19 } from 'nostr-tools'; -import type { NDKEvent } from '@nostr-dev-kit/ndk'; -import type NDK from '@nostr-dev-kit/ndk'; -import Pharos from '$lib/parser.ts'; -import { wikiKind } from './consts'; - -/** - * Fetch a single wiki event by id (hex or bech32). - */ -export async function fetchWikiEventById(id: string): Promise { - const ndk = get(ndkInstance); - if (!ndk) { - console.warn('NDK instance not found in fetchWikiEventById'); - return null; - } - - let eventId = id; - if (id.startsWith('nevent') || id.startsWith('note') || id.startsWith('naddr')) { - try { - const decoded = nip19.decode(id); - if (decoded.type === 'nevent') { - eventId = decoded.data.id; - } else if (decoded.type === 'note') { - eventId = decoded.data; - } - } catch (e) { - console.error('Failed to decode id in fetchWikiEventById:', e); - return null; - } - } - - const event = await fetchEventWithFallback(ndk, eventId); - if (event && event.kind === wikiKind) { - console.log('Fetched wiki event:', event); - return event; - } - console.warn('No wiki event found for id:', eventId); - return null; -} - -/** - * Fetch all wiki events by d-tag. - */ -export async function fetchWikiEventsByDTag(dtag: string): Promise { - const ndk = get(ndkInstance); - if (!ndk) { - console.warn('NDK instance not found in fetchWikiEventsByDTag'); - return []; - } - - const event = await fetchEventWithFallback(ndk, { - kinds: [wikiKind], - '#d': [dtag] - }); - - if (!event) { - console.warn(`No wiki events found for dtag: ${dtag}`); - return []; - } - - // For d-tag queries, we want to get all matching events, not just the first one - const events = await ndk.fetchEvents({ - kinds: [wikiKind], - '#d': [dtag] - }); - - const arr = Array.from(events); - console.log(`Fetched ${arr.length} wiki events for dtag:`, dtag); - return arr; -} - -/** - * Get a display name for a pubkey. - */ -export async function getProfileName(pubkey: string): Promise { - if (!pubkey) return 'unknown'; - const metadata = await getUserMetadata(pubkey); - return metadata.displayName || metadata.name || pubkey.slice(0, 10); -} - -/** - * Fetch and parse a wiki page by event id or nevent. - */ -export async function getWikiPageById(id: string, ndk: NDK) { - console.log('getWikiPageById: fetching wiki page for id', id); - if (!id) { - console.error('getWikiPageById: id is undefined'); - return null; - } - - let event; - try { - event = await fetchEventWithFallback(ndk, id); - if (!event) { - console.error('getWikiPageById: No event found for id:', id); - return null; - } - if (event.kind !== wikiKind) { - console.error('getWikiPageById: Event found but kind !== wikiKind:', event); - return null; - } - if (!event.content) { - console.error('getWikiPageById: Event has no content:', event); - return null; - } - if (!event.tags) { - console.error('getWikiPageById: Event has no tags:', event); - return null; - } - } catch (err) { - console.error('getWikiPageById: Exception fetching event:', err, 'id:', id); - return null; - } - - const pubhex = event.pubkey || ''; - const titleTag = event.tags.find((tag: string[]) => tag[0] === 'title'); - const title = titleTag ? titleTag[1] : 'Untitled'; - const summaryTag = event.tags.find((tag: string[]) => tag[0] === 'summary'); - const summary = summaryTag ? summaryTag[1] : ''; - const hashtags = event.tags.filter((tag: string[]) => tag[0] === 't').map((tag: string[]) => tag[1]) || []; - - let asciidoc = event.content; - if (!/^=\s/m.test(asciidoc)) { - console.warn('getWikiPageById: No document header found, prepending fake header for title:', title); - asciidoc = `= ${title}\n\n` + asciidoc; - } - - let html = ''; - try { - const pharos = new Pharos(ndk); - console.log('Pharos instance:', pharos); - pharos.parse(asciidoc); - const pharosHtml = pharos.getHtml(); - console.log('AsciiDoc:', asciidoc); - console.log('Pharos HTML:', pharosHtml); - html = await parseBasicmarkup(pharosHtml ?? ''); - if (!html || html.trim() === '') { - console.error('getWikiPageById: Parsed HTML is empty for id:', id, 'event:', event, 'asciidoc:', asciidoc, 'pharosHtml:', pharosHtml); - } - } catch (err) { - console.error('getWikiPageById: Error parsing content:', err, 'event:', event); - return null; - } - - return { title, pubhex, eventId: event.id, summary, hashtags, html, content: event.content }; -} - -/** - * Search wiki pages by d-tag. - */ -export async function searchWikiPagesByDTag(dtag: string) { - const events = await fetchWikiEventsByDTag(dtag); - return Promise.all(events.map(async (event: NDKEvent) => { - const pubhex = event.pubkey || ''; - const titleTag = event.tags?.find((tag: string[]) => tag[0] === 't'); - const title = titleTag ? titleTag[1] : 'Untitled'; - const summaryTag = event.tags?.find((tag: string[]) => tag[0] === 'summary'); - const summary = summaryTag ? summaryTag[1] : ''; - const metadata = await getUserMetadata(pubhex); - const nip05 = metadata.nip05 || ''; - const urlPath = nip05 ? `${dtag}/${nip05}` : `${dtag}*${pubhex}`; - return { - title, - pubhex, - eventId: event.id, - summary, - urlPath - }; - })); -} - -/** - * Parse wiki content using Pharos and basic markup parser. - */ -export async function parseWikiContent(content: string, ndk: NDK): Promise { - const pharos = new Pharos(ndk); - pharos.parse(content); - const pharosHtml = pharos.getHtml(); - return await parseBasicmarkup(pharosHtml); -} \ No newline at end of file diff --git a/src/routes/wiki/+page.svelte b/src/routes/wiki/+page.svelte deleted file mode 100644 index 466de75..0000000 --- a/src/routes/wiki/+page.svelte +++ /dev/null @@ -1,323 +0,0 @@ - - -
    -
    - { - if (wikiPage) { - wikiPage = null; - wikiContent = null; - } - fetchResults(searchInput); - }} - autocomplete="off" - class="w-full px-6 py-4 rounded-2xl border border-primary-200 shadow bg-primary-50 focus:outline-none focus:ring-2 focus:ring-primary-400 text-lg transition" - /> -
    - - {#if loading} -
    -
    -

    Loading wiki content...

    -
    - {:else if error} -
    -

    {error}

    -
    - {:else if wikiPage && wikiContent} -
    -
    {getNevent(wikiPage.eventId)}
    -

    {wikiPage.title}

    -
    - by -
    - {#if wikiPage.hashtags.length} -
    - {#each wikiPage.hashtags as tag} - #{tag} - {/each} -
    - {/if} - {#if wikiPage.summary} -
    {wikiPage.summary}
    - {/if} -
    - {#if wikiPage.html && wikiPage.html.trim().length > 0} - {@html wikiPage.html} - {:else} -
    - No content found for this wiki page. - {#if wikiPage.content} -
    -                {wikiPage.content}
    -              
    - {/if} -
    -              {JSON.stringify(wikiPage, null, 2)}
    -            
    -
    - {/if} -
    -
    - {:else if !searchInput} -
    -

    - Welcome to the Alexandria Wiki! -

    -

    - Use the search bar above to find wiki pages on any topic. - Alexandria wiki pages are stored on Nostr relays and can be collaboratively added to by anyone with a Nostr key. - Search by topic, and you'll see all relevant wiki pages, each signed by its author. -

    -
    - {:else if results.length === 0} -

    No entries found for this topic.

    - {:else} -
    - {#each results as result} - - {/each} -
    - {/if} -
    \ No newline at end of file diff --git a/src/routes/wiki/+page.ts b/src/routes/wiki/+page.ts deleted file mode 100644 index c009f61..0000000 --- a/src/routes/wiki/+page.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Load } from '@sveltejs/kit'; -import { getWikiPageById, searchWikiPagesByDTag } from '../../lib/wiki'; - -export const load: Load = async ({ url }) => { - const id = url.searchParams.get('id'); - const d = url.searchParams.get('d'); - - if (d) { - // Disambiguation/search page for d-tag - const results = await searchWikiPagesByDTag(d); - return { disambiguation: true, results, dtag: d }; - } - - if (id) { - // Single wiki page by event id (bech32 or hex) - const page = await getWikiPageById(id); - if (!page) { - return { status: 404, error: 'Wiki page not found.' }; - } - return { disambiguation: false, page }; - } - - // No query: show only the search bar - return { disambiguation: true, results: [], dtag: '' }; -}; \ No newline at end of file From 165bb3c62d6874c8f106bb52135ae130323acb3b Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 20 May 2025 06:34:43 +0200 Subject: [PATCH 05/17] fix linter errors --- src/lib/components/PublicationHeader.svelte | 2 +- src/lib/components/blog/BlogHeader.svelte | 2 +- src/lib/components/util/ArticleNav.svelte | 2 +- src/lib/components/util/Details.svelte | 2 +- src/routes/visualize/+page.svelte | 3 --- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib/components/PublicationHeader.svelte b/src/lib/components/PublicationHeader.svelte index c7f9e15..a82f188 100644 --- a/src/lib/components/PublicationHeader.svelte +++ b/src/lib/components/PublicationHeader.svelte @@ -45,7 +45,7 @@

    by {#if authorPubkey != null} - + {:else} {author} {/if} diff --git a/src/lib/components/blog/BlogHeader.svelte b/src/lib/components/blog/BlogHeader.svelte index f7fc25b..dcc9779 100644 --- a/src/lib/components/blog/BlogHeader.svelte +++ b/src/lib/components/blog/BlogHeader.svelte @@ -38,7 +38,7 @@
    - + {publishedAt()}
    diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index 8665ece..061d83a 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -131,7 +131,7 @@ {/if}
    -

    {title} by

    +

    {title} by

    {#if $publicationColumnVisibility.inner} diff --git a/src/lib/components/util/Details.svelte b/src/lib/components/util/Details.svelte index 989cac4..4299161 100644 --- a/src/lib/components/util/Details.svelte +++ b/src/lib/components/util/Details.svelte @@ -46,7 +46,7 @@

    by {#if originalAuthor !== null} - + {:else} {author} {/if} diff --git a/src/routes/visualize/+page.svelte b/src/routes/visualize/+page.svelte index 142eeb5..629199f 100644 --- a/src/routes/visualize/+page.svelte +++ b/src/routes/visualize/+page.svelte @@ -11,9 +11,6 @@ import type { NDKEvent } from "@nostr-dev-kit/ndk"; import { filterValidIndexEvents } from "$lib/utils"; import { networkFetchLimit } from "$lib/state"; - import { CogSolid } from "flowbite-svelte-icons"; - import { Button } from "flowbite-svelte"; - import Settings from "$lib/navigator/EventNetwork/Settings.svelte"; // Configuration const DEBUG = false; // Set to true to enable debug logging From 1522fa17ce89cc4a58452242bc0d1290cf16d30c Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 20 May 2025 06:57:09 +0200 Subject: [PATCH 06/17] remove nip33 and replace with naddr --- src/lib/components/util/CardActions.svelte | 10 +++++----- src/routes/events/+page.svelte | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/components/util/CardActions.svelte b/src/lib/components/util/CardActions.svelte index eee1415..bbc2b73 100644 --- a/src/lib/components/util/CardActions.svelte +++ b/src/lib/components/util/CardActions.svelte @@ -82,19 +82,19 @@ detailsModalOpen = true; } - // --- Custom JSON pretty-printer with NIP-33 address hyperlinking --- + // --- Custom JSON pretty-printer with naddr hyperlinking --- /** - * Returns HTML for pretty-printed JSON, with NIP-33 addresses as links to /events?id=naddr1... + * Returns HTML for pretty-printed JSON, with naddrs as links to /events?id=naddr1... */ function jsonWithNaddrLinks(obj: any): string { - const NIP33_REGEX = /\b(\d{5}:[a-f0-9]{64}:[a-zA-Z0-9._-]+)\b/g; + const NADDR_REGEX = /\b(\d{5}:[a-f0-9]{64}:[a-zA-Z0-9._-]+)\b/g; function replacer(_key: string, value: any) { return value; } // Stringify with 2-space indent let json = JSON.stringify(obj, replacer, 2); - // Replace NIP-33 addresses with links - json = json.replace(NIP33_REGEX, (match) => { + // Replace naddr addresses with links + json = json.replace(NADDR_REGEX, (match) => { try { const [kind, pubkey, dtag] = match.split(":"); // Compose a fake event for naddrEncode diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte index b471ae3..b24a96b 100644 --- a/src/routes/events/+page.svelte +++ b/src/routes/events/+page.svelte @@ -127,10 +127,10 @@ } /** - * Returns HTML for pretty-printed JSON, with NIP-33 addresses and event IDs as links + * Returns HTML for pretty-printed JSON, with naddr addresses and event IDs as links */ function jsonWithLinks(obj: any): string { - const NIP33_REGEX = /\b(\d{5}:[a-f0-9]{64}:[a-zA-Z0-9._-]+)\b/g; + const NADDR_REGEX = /\b(\d{5}:[a-f0-9]{64}:[a-zA-Z0-9._-]+)\b/g; const EVENT_ID_REGEX = /\b([0-9a-f]{64})\b/g; function replacer(_key: string, value: any) { @@ -139,8 +139,8 @@ // Stringify with 2-space indent let json = JSON.stringify(obj, replacer, 2); - // Replace NIP-33 addresses with links - json = json.replace(NIP33_REGEX, (match) => { + // Replace addresses with links + json = json.replace(NADDR_REGEX, (match) => { try { const [kind, pubkey, dtag] = match.split(":"); // Compose a fake event for naddrEncode From e4e33df73633f4461030ec267e82c324decd7d3c Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 20 May 2025 07:01:41 +0200 Subject: [PATCH 07/17] Renamed publication eventid to address --- src/lib/components/PublicationFeed.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/PublicationFeed.svelte b/src/lib/components/PublicationFeed.svelte index 56cc4e1..ef3448f 100644 --- a/src/lib/components/PublicationFeed.svelte +++ b/src/lib/components/PublicationFeed.svelte @@ -63,9 +63,9 @@ return; } - const eventMap = new Map([...eventsInView, ...eventArray].map(event => [event.id, event])); + const eventMap = new Map([...eventsInView, ...eventArray].map(event => [event.tagAddress(), event])); const allEvents = Array.from(eventMap.values()); - const uniqueIds = new Set(allEvents.map(event => event.id)); + const uniqueIds = new Set(allEvents.map(event => event.tagAddress())); eventsInView = Array.from(uniqueIds) .map(id => eventMap.get(id)) .filter(event => event != null) as NDKEvent[]; From f5ae449e3693643a0d2c57ed961ed5ee555e6437 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 20 May 2025 08:54:14 +0200 Subject: [PATCH 08/17] added timeout to existing functions, revamped timeout and cardactions and fixed asciidoc browser console error --- README.md | 2 +- src/lib/components/PublicationHeader.svelte | 1 + src/lib/components/util/CardActions.svelte | 161 ++++++++---------- .../components/util/CopyToClipboard.svelte | 30 +++- src/lib/parser.ts | 45 +++++ src/lib/utils/nostrUtils.ts | 118 ++++++++++--- 6 files changed, 235 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 1438b73..7b89069 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ To run the container, in detached mode (-d): docker run -d --rm --name=gc-alexandria -p 4174:80 gc-alexandria ``` -The container is then viewable on your [local machine](http://localhost:4174). +The container is then viewable on your [local machine](http://localhost:4173). If you want to see the container process (assuming it's the last process to start), enter: diff --git a/src/lib/components/PublicationHeader.svelte b/src/lib/components/PublicationHeader.svelte index a82f188..2f28ccc 100644 --- a/src/lib/components/PublicationHeader.svelte +++ b/src/lib/components/PublicationHeader.svelte @@ -29,6 +29,7 @@ let image: string = $derived(event.getMatchingTags('image')[0]?.[1] ?? null); let authorPubkey: string = $derived(event.getMatchingTags('p')[0]?.[1] ?? null); + console.log("PublicationHeader event:", event); {#if title != null && href != null} diff --git a/src/lib/components/util/CardActions.svelte b/src/lib/components/util/CardActions.svelte index bbc2b73..f7cc569 100644 --- a/src/lib/components/util/CardActions.svelte +++ b/src/lib/components/util/CardActions.svelte @@ -1,17 +1,21 @@
    @@ -142,22 +133,18 @@
  • - +
  • - +
  • @@ -214,7 +201,7 @@
    Identifier: {identifier}
    {/if} View Event Details diff --git a/src/lib/components/util/CopyToClipboard.svelte b/src/lib/components/util/CopyToClipboard.svelte index d0bbba3..cb49a6b 100644 --- a/src/lib/components/util/CopyToClipboard.svelte +++ b/src/lib/components/util/CopyToClipboard.svelte @@ -1,27 +1,41 @@ - diff --git a/src/lib/parser.ts b/src/lib/parser.ts index d877985..85286d4 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -152,6 +152,10 @@ export default class Pharos { } parse(content: string, options?: ProcessorOptions | undefined): void { + + // Ensure the content is valid AsciiDoc and has a header and the doctype book + content = ensureAsciiDocHeader(content); + try { this.html = this.asciidoctor.convert(content, { 'extension_registry': this.pharosExtensions, @@ -1119,3 +1123,44 @@ export const tocUpdate = writable(0); // Whenever you update the publication tree, call: tocUpdate.update(n => n + 1); + +function ensureAsciiDocHeader(content: string): string { + const lines = content.split(/\r?\n/); + let headerIndex = -1; + let hasDoctype = false; + + // Find the first non-empty line as header + for (let i = 0; i < lines.length; i++) { + if (lines[i].trim() === '') continue; + if (lines[i].trim().startsWith('=')) { + headerIndex = i; + console.debug('[Pharos] AsciiDoc document header:', lines[i].trim()); + break; + } else { + throw new Error('AsciiDoc document is missing a header at the top.'); + } + } + + if (headerIndex === -1) { + throw new Error('AsciiDoc document is missing a header.'); + } + + // Check for doctype in the next non-empty line after header + let nextLine = headerIndex + 1; + while (nextLine < lines.length && lines[nextLine].trim() === '') { + nextLine++; + } + if (nextLine < lines.length && lines[nextLine].trim().startsWith(':doctype:')) { + hasDoctype = true; + } + + // Insert doctype immediately after header if not present + if (!hasDoctype) { + lines.splice(headerIndex + 1, 0, ':doctype: book'); + } + + // Log the state of the lines before returning + console.debug('[Pharos] AsciiDoc lines after header/doctype normalization:', lines.slice(0, 5)); + + return lines.join('\n'); +} diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 123a19a..6cd5df3 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -185,6 +185,55 @@ export async function getNpubFromNip05(nip05: string): Promise { } } +/** + * Generic utility function to add a timeout to any promise + * Can be used in two ways: + * 1. Method style: promise.withTimeout(5000) + * 2. Function style: withTimeout(promise, 5000) + * + * @param thisOrPromise Either the promise to timeout (function style) or the 'this' context (method style) + * @param timeoutMsOrPromise Timeout duration in milliseconds (function style) or the promise (method style) + * @returns The promise result if completed before timeout, otherwise throws an error + * @throws Error with message 'Timeout' if the promise doesn't resolve within timeoutMs + */ +export function withTimeout( + thisOrPromise: Promise | number, + timeoutMsOrPromise?: number | Promise +): Promise { + // Handle method-style call (promise.withTimeout(5000)) + if (typeof thisOrPromise === 'number') { + const timeoutMs = thisOrPromise; + const promise = timeoutMsOrPromise as Promise; + return Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), timeoutMs) + ) + ]); + } + + // Handle function-style call (withTimeout(promise, 5000)) + const promise = thisOrPromise; + const timeoutMs = timeoutMsOrPromise as number; + return Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), timeoutMs) + ) + ]); +} + +// Add the method to Promise prototype +declare global { + interface Promise { + withTimeout(timeoutMs: number): Promise; + } +} + +Promise.prototype.withTimeout = function(this: Promise, timeoutMs: number): Promise { + return withTimeout(timeoutMs, this); +}; + /** * Fetches an event using a two-step relay strategy: * 1. First tries standard relays with timeout @@ -196,42 +245,59 @@ export async function fetchEventWithFallback( filterOrId: string | NDKFilter, timeoutMs: number = 3000 ): Promise { - const allRelays = Array.from(new Set([...standardRelays, ...bootstrapRelays])); + // Get user relays if logged in + const userRelays = ndk.activeUser ? + Array.from(ndk.pool?.relays.values() || []) + .filter(r => r.status === 1) // Only use connected relays + .map(r => r.url) : + []; + + // Create three relay sets in priority order const relaySets = [ - NDKRelaySet.fromRelayUrls(standardRelays, ndk), - NDKRelaySet.fromRelayUrls(allRelays, ndk) + NDKRelaySet.fromRelayUrls(standardRelays, ndk), // 1. Standard relays + NDKRelaySet.fromRelayUrls(userRelays, ndk), // 2. User relays (if logged in) + NDKRelaySet.fromRelayUrls(bootstrapRelays, ndk) // 3. Bootstrap relays (last resort) ]; - async function withTimeout(promise: Promise): Promise { - return Promise.race([ - promise, - new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeoutMs)) - ]); - } - try { let found: NDKEvent | null = null; + const triedRelaySets: string[] = []; - // Try standard relays first - if (typeof filterOrId === 'string' && /^[0-9a-f]{64}$/i.test(filterOrId)) { - found = await withTimeout(ndk.fetchEvent({ ids: [filterOrId] }, undefined, relaySets[0])); - if (!found) { - // Fallback to all relays - found = await withTimeout(ndk.fetchEvent({ ids: [filterOrId] }, undefined, relaySets[1])); - } - } else { - const filter = typeof filterOrId === 'string' ? { ids: [filterOrId] } : filterOrId; - const results = await withTimeout(ndk.fetchEvents(filter, undefined, relaySets[0])); - found = results instanceof Set ? Array.from(results)[0] as NDKEvent : null; - if (!found) { - // Fallback to all relays - const fallbackResults = await withTimeout(ndk.fetchEvents(filter, undefined, relaySets[1])); - found = fallbackResults instanceof Set ? Array.from(fallbackResults)[0] as NDKEvent : null; + // Helper function to try fetching from a relay set + async function tryFetchFromRelaySet(relaySet: NDKRelaySet, setName: string): Promise { + if (relaySet.relays.size === 0) return null; + triedRelaySets.push(setName); + + if (typeof filterOrId === 'string' && /^[0-9a-f]{64}$/i.test(filterOrId)) { + return await ndk.fetchEvent({ ids: [filterOrId] }, undefined, relaySet).withTimeout(timeoutMs); + } else { + const filter = typeof filterOrId === 'string' ? { ids: [filterOrId] } : filterOrId; + const results = await ndk.fetchEvents(filter, undefined, relaySet).withTimeout(timeoutMs); + return results instanceof Set ? Array.from(results)[0] as NDKEvent : null; } } + // Try each relay set in order + for (const [index, relaySet] of relaySets.entries()) { + const setName = index === 0 ? 'standard relays' : + index === 1 ? 'user relays' : + 'bootstrap relays'; + + found = await tryFetchFromRelaySet(relaySet, setName); + if (found) break; + } + if (!found) { - console.warn('Event not found after timeout. Some relays may be offline or slow.'); + const timeoutSeconds = timeoutMs / 1000; + const relayUrls = relaySets.map((set, i) => { + const setName = i === 0 ? 'standard relays' : + i === 1 ? 'user relays' : + 'bootstrap relays'; + const urls = Array.from(set.relays).map(r => r.url); + return urls.length > 0 ? `${setName} (${urls.join(', ')})` : null; + }).filter(Boolean).join(', then '); + + console.warn(`Event not found after ${timeoutSeconds}s timeout. Tried ${relayUrls}. Some relays may be offline or slow.`); return null; } From 03e4d769bb70e12bcc10113ec7413ad52890a6aa Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 20 May 2025 09:01:06 +0200 Subject: [PATCH 09/17] implemented $effect and $state --- src/routes/events/+page.svelte | 63 ++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte index b24a96b..077d83f 100644 --- a/src/routes/events/+page.svelte +++ b/src/routes/events/+page.svelte @@ -13,13 +13,24 @@ import { nip19 } from 'nostr-tools'; import InlineProfile from '$lib/components/util/InlineProfile.svelte'; - let searchQuery = ""; - let event: NDKEvent | null = null; - let loading = false; - let error: string | null = null; - let showFullContent = false; - let contentPreview = ''; - let parsedContent = ''; + let searchQuery = $state(""); + let event = $state(null); + let loading = $state(false); + let error = $state(null); + let showFullContent = $state(false); + let parsedContent = $state(''); + let contentPreview = $state(''); + let profile = $state<{ + name?: string; + display_name?: string; + about?: string; + picture?: string; + banner?: string; + website?: string; + lud16?: string; + nip05?: string; + } | null>(null); + let profileTitle = $state(null); async function searchEvent() { if (!searchQuery.trim()) return; @@ -193,20 +204,34 @@ } }); - $: if (event && event.kind !== 0 && event.content) { - parseBasicmarkup(event.content).then(html => { - parsedContent = html; - contentPreview = html.slice(0, 250); - }); - } + $effect(() => { + if (event && event.kind !== 0 && event.content) { + parseBasicmarkup(event.content).then(html => { + parsedContent = html; + contentPreview = html.slice(0, 250); + }); + } + }); - $: profile = event && event.kind === 0 - ? (() => { try { return JSON.parse(event.content); } catch { return null; } })() - : null; + $effect(() => { + if (event && event.kind === 0) { + try { + profile = JSON.parse(event.content); + } catch { + profile = null; + } + } else { + profile = null; + } + }); - $: profileTitle = event && event.kind === 0 && profile && profile.name - ? profile.name - : null; + $effect(() => { + if (event && event.kind === 0 && profile && profile.name) { + profileTitle = profile.name; + } else { + profileTitle = null; + } + });
    From a49afeac844cbb6e6962211db7b08f4265bb6bd2 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 20 May 2025 09:28:37 +0200 Subject: [PATCH 10/17] got rid of redundant check and added logging to cardactions event hyperlink --- src/lib/components/util/CardActions.svelte | 6 ++++-- src/routes/events/+page.svelte | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/components/util/CardActions.svelte b/src/lib/components/util/CardActions.svelte index f7cc569..9422c41 100644 --- a/src/lib/components/util/CardActions.svelte +++ b/src/lib/components/util/CardActions.svelte @@ -82,7 +82,9 @@ */ function getIdentifier(type: 'nevent' | 'naddr'): string { const encodeFn = type === 'nevent' ? neventEncode : naddrEncode; - return encodeFn(event, activeRelays); + const identifier = encodeFn(event, activeRelays); + console.debug(`[CardActions] ${type} identifier for event ${event.id}:`, identifier); + return identifier; } /** @@ -201,7 +203,7 @@
    Identifier: {identifier}
    {/if}
    View Event Details diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte index 077d83f..4b7b373 100644 --- a/src/routes/events/+page.svelte +++ b/src/routes/events/+page.svelte @@ -89,9 +89,6 @@ error = 'Event not found'; } else { console.log('[Events] Event found:', event); - if (typeof event.getMatchingTags !== 'function') { - event = new NDKEvent(event.ndk || $ndkInstance, event); - } } } catch (err) { console.error('[Events] Error fetching event:', err, 'Query:', searchQuery); From 5a92ed9016f8e4bca31bcd21e2f84790625dde54 Mon Sep 17 00:00:00 2001 From: silberengel Date: Tue, 20 May 2025 13:39:11 +0200 Subject: [PATCH 11/17] Changed "bootstrapRelays" to "fallbackRelays", since that's how we use them. Added more relays to the list. Moved Events further to the right in the Nav bar, as it's a utility and not part of the core feature set. --- package-lock.json | 178 +++++++++++++-------------- src/lib/components/Navigation.svelte | 2 +- src/lib/consts.ts | 10 +- src/lib/ndk.ts | 6 +- src/lib/utils/nostrUtils.ts | 8 +- 5 files changed, 102 insertions(+), 102 deletions(-) diff --git a/package-lock.json b/package-lock.json index c324fcf..acc4d6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2717,19 +2717,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, - "license": "MIT", + "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": { @@ -3698,6 +3716,15 @@ "node": ">=4" } }, + "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.3.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", @@ -5840,17 +5867,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, - "license": "MIT", + "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": { @@ -6333,6 +6368,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": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", @@ -6566,54 +6629,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==", - "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", @@ -6662,30 +6677,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==", - "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", @@ -7341,13 +7332,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, - "license": "ISC", + "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/Navigation.svelte b/src/lib/components/Navigation.svelte index e46d32c..4fefd1a 100644 --- a/src/lib/components/Navigation.svelte +++ b/src/lib/components/Navigation.svelte @@ -24,9 +24,9 @@
    Publications - Events Visualize Getting Started + Events About Contact diff --git a/src/lib/consts.ts b/src/lib/consts.ts index 1772061..dab6298 100644 --- a/src/lib/consts.ts +++ b/src/lib/consts.ts @@ -1,8 +1,16 @@ export const wikiKind = 30818; export const indexKind = 30040; export const zettelKinds = [ 30041, 30818 ]; +export const communityRelay = [ 'wss://theforest.nostr1.com' ]; export const standardRelays = [ 'wss://thecitadel.nostr1.com', 'wss://theforest.nostr1.com' ]; -export const bootstrapRelays = [ 'wss://purplepag.es', 'wss://relay.noswhere.com' , 'wss://relay.damus.io', 'wss://relay.nostr.band']; +export const fallbackRelays = [ + 'wss://purplepag.es', + 'wss://indexer.coracle.social', + 'wss://relay.noswhere.com', + 'wss://relay.damus.io', + 'wss://relay.nostr.band', + 'wss://relay.lumina.rocks' +]; export enum FeedType { StandardRelays = 'standard', diff --git a/src/lib/ndk.ts b/src/lib/ndk.ts index 9a78f34..d5a0f79 100644 --- a/src/lib/ndk.ts +++ b/src/lib/ndk.ts @@ -1,6 +1,6 @@ import NDK, { NDKNip07Signer, NDKRelay, NDKRelayAuthPolicies, NDKRelaySet, NDKUser } from '@nostr-dev-kit/ndk'; import { get, writable, type Writable } from 'svelte/store'; -import { bootstrapRelays, FeedType, loginStorageKey, standardRelays } from './consts'; +import { fallbackRelays, FeedType, loginStorageKey, standardRelays } from './consts'; import { feedType } from './stores'; export const ndkInstance: Writable = writable(); @@ -199,7 +199,7 @@ export function logout(user: NDKUser): void { async function getUserPreferredRelays( ndk: NDK, user: NDKUser, - bootstraps: readonly string[] = bootstrapRelays + fallbacks: readonly string[] = fallbackRelays ): Promise<[Set, Set]> { const relayList = await ndk.fetchEvent( { @@ -211,7 +211,7 @@ async function getUserPreferredRelays( skipVerification: false, skipValidation: false, }, - NDKRelaySet.fromRelayUrls(bootstraps, ndk), + NDKRelaySet.fromRelayUrls(fallbacks, ndk), ); const inboxRelays = new Set(); diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 6cd5df3..c4dade5 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -4,7 +4,7 @@ import { ndkInstance } from '$lib/ndk'; import { npubCache } from './npubCache'; import NDK, { NDKEvent, NDKRelaySet } from "@nostr-dev-kit/ndk"; import type { NDKFilter, NDKKind } from "@nostr-dev-kit/ndk"; -import { standardRelays, bootstrapRelays } from "$lib/consts"; +import { standardRelays, fallbackRelays } from "$lib/consts"; // Regular expressions for Nostr identifiers - match the entire identifier including any prefix export const NOSTR_PROFILE_REGEX = /(? { const setName = i === 0 ? 'standard relays' : i === 1 ? 'user relays' : - 'bootstrap relays'; + 'fallback relays'; const urls = Array.from(set.relays).map(r => r.url); return urls.length > 0 ? `${setName} (${urls.join(', ')})` : null; }).filter(Boolean).join(', then '); From 8c88dc43c7ad4563faf1fdcea87890c36ed00b85 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 20 May 2025 23:15:24 +0200 Subject: [PATCH 12/17] Successful implementation of event search and userBadges. --- .gitignore | 1 - README.md | 2 +- package-lock.json | 5713 ++++++++++++++++++ src/lib/components/Login.svelte | 7 +- src/lib/components/PublicationHeader.svelte | 4 +- src/lib/components/blog/BlogHeader.svelte | 4 +- src/lib/components/util/ArticleNav.svelte | 4 +- src/lib/components/util/CardActions.svelte | 8 +- src/lib/components/util/Details.svelte | 8 +- src/lib/components/util/InlineProfile.svelte | 195 +- src/lib/consts.ts | 4 +- src/lib/snippets/UserSnippets.svelte | 20 +- src/lib/utils/nostrUtils.ts | 22 +- src/routes/[...catchall]/+page.svelte | 1 - src/routes/about/+page.svelte | 8 +- src/routes/events/+page.svelte | 102 +- test_data/AsciidocFiles/21lessons.adoc | 2 +- test_data/AsciidocFiles/Rauhnaechte.adoc | 2 +- tests/integration/markupIntegration.test.ts | 4 +- tests/integration/markupTestfile.md | 8 +- 20 files changed, 5999 insertions(+), 120 deletions(-) create mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index dcca1fb..ef18a0a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* -package-lock.json # tests /tests/e2e/html-report/*.html diff --git a/README.md b/README.md index 7b89069..ed56197 100644 --- a/README.md +++ b/README.md @@ -120,4 +120,4 @@ npx playwright test ## Markup Support -Alexandria supports both Markdown and AsciiDoc markup for different content types. For a detailed list of supported tags and features in the basic and advanced markdown parsers, as well as information about AsciiDoc usage for publications and wikis, see [MarkupInfo.md](https://next-alexandria.gitcitadel.eu/src/lib/utils/markup/MarkupInfo.md). \ No newline at end of file +Alexandria supports both Markdown and AsciiDoc markup for different content types. For a detailed list of supported tags and features in the basic and advanced markdown parsers, as well as information about AsciiDoc usage for publications and wikis, see [MarkupInfo.md](./src/lib/utils/markup/MarkupInfo.md). \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5d74252 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5713 @@ +{ + "name": "alexandria", + "version": "0.0.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "alexandria", + "version": "0.0.6", + "dependencies": { + "@nostr-dev-kit/ndk": "2.11.x", + "@nostr-dev-kit/ndk-cache-dexie": "2.5.x", + "@popperjs/core": "2.11.x", + "@tailwindcss/forms": "0.5.x", + "@tailwindcss/typography": "0.5.x", + "asciidoctor": "3.0.x", + "d3": "^7.9.0", + "he": "1.2.x", + "highlight.js": "^11.11.1", + "node-emoji": "^2.2.0", + "nostr-tools": "2.10.x" + }, + "devDependencies": { + "@playwright/test": "^1.50.1", + "@sveltejs/adapter-auto": "3.x", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/adapter-static": "3.x", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "4.x", + "@types/d3": "^7.4.3", + "@types/he": "1.2.x", + "@types/node": "22.x", + "autoprefixer": "10.x", + "eslint-plugin-svelte": "2.x", + "flowbite": "2.x", + "flowbite-svelte": "0.x", + "flowbite-svelte-icons": "2.1.x", + "playwright": "^1.50.1", + "postcss": "8.x", + "postcss-load-config": "6.x", + "prettier": "3.x", + "prettier-plugin-svelte": "3.x", + "svelte": "5.x", + "svelte-check": "4.x", + "tailwind-merge": "^3.3.0", + "tailwindcss": "3.x", + "tslib": "2.8.x", + "typescript": "5.7.x", + "vite": "5.x", + "vitest": "^3.1.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@asciidoctor/cli": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "yargs": "17.3.1" + }, + "bin": { + "asciidoctor": "bin/asciidoctor", + "asciidoctorjs": "bin/asciidoctor" + }, + "engines": { + "node": ">=16", + "npm": ">=8.0.0" + }, + "peerDependencies": { + "@asciidoctor/core": ">=2 <4" + } + }, + "node_modules/@asciidoctor/core": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "@asciidoctor/opal-runtime": "3.0.1", + "unxhr": "1.2.0" + }, + "engines": { + "node": ">=16", + "npm": ">=8" + } + }, + "node_modules/@asciidoctor/opal-runtime": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "glob": "8.1.0", + "unxhr": "1.2.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.27.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@noble/ciphers": { + "version": "0.5.3", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/secp256k1": { + "version": "2.2.3", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nostr-dev-kit/ndk": { + "version": "2.11.2", + "license": "MIT", + "dependencies": { + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@noble/secp256k1": "^2.1.0", + "@scure/base": "^1.1.9", + "debug": "^4.3.6", + "light-bolt11-decoder": "^3.2.0", + "nostr-tools": "^2.7.1", + "tseep": "^1.2.2", + "typescript-lru-cache": "^2.0.0", + "utf8-buffer": "^1.0.0", + "websocket-polyfill": "^0.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@nostr-dev-kit/ndk-cache-dexie": { + "version": "2.5.15", + "license": "MIT", + "dependencies": { + "@nostr-dev-kit/ndk": "2.12.2", + "debug": "^4.3.7", + "dexie": "^4.0.8", + "nostr-tools": "^2.4.0", + "typescript-lru-cache": "^2.0.0" + } + }, + "node_modules/@nostr-dev-kit/ndk-cache-dexie/node_modules/@nostr-dev-kit/ndk": { + "version": "2.12.2", + "license": "MIT", + "dependencies": { + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@noble/secp256k1": "^2.1.0", + "@scure/base": "^1.1.9", + "debug": "^4.3.6", + "light-bolt11-decoder": "^3.2.0", + "nostr-tools": "^2.7.1", + "tseep": "^1.2.2", + "typescript-lru-cache": "^2.0.0", + "utf8-buffer": "^1.0.0", + "websocket-polyfill": "^0.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@scure/base": { + "version": "1.2.5", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.1", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@scure/base": { + "version": "1.1.9", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@scure/base": { + "version": "1.1.9", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "import-meta-resolve": "^4.1.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.2.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.1.0", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3 || ^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", + "debug": "^4.3.7", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.12", + "vitefu": "^1.0.3" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0-next.96 || ^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0", + "svelte": "^5.0.0-next.96 || ^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.10", + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/he": { + "version": "1.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/node": { + "version": "22.15.18", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.3", + "@vitest/utils": "3.1.3", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.3", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.3", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.3", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/apexcharts": { + "version": "3.54.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/aria-query": { + "version": "5.3.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "license": "MIT" + }, + "node_modules/asciidoctor": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "@asciidoctor/cli": "4.0.0", + "@asciidoctor/core": "3.0.4", + "ejs": "^3.1.2", + "handlebars": "^4.7.6", + "nunjucks": "^3.2.1", + "pug": "^3.0.0" + }, + "bin": { + "asciidoctor": "bin/asciidoctor", + "asciidoctorjs": "bin/asciidoctor" + }, + "engines": { + "node": ">=16", + "npm": ">=8" + } + }, + "node_modules/assert-never": { + "version": "1.4.0", + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/async": { + "version": "3.2.6", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bufferutil": { + "version": "4.0.9", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001718", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "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/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/cliui": { + "version": "7.0.4", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "license": "MIT" + }, + "node_modules/constantinople": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/d": { + "version": "1.0.2", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/devalue": { + "version": "5.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/dexie": { + "version": "4.0.11", + "license": "Apache-2.0" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/doctypes": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.155", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.27.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "2.46.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "eslint-compat-utils": "^0.5.1", + "esutils": "^2.0.3", + "known-css-properties": "^0.35.0", + "postcss": "^8.4.38", + "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" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-svelte/node_modules/lilconfig": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-svelte/node_modules/postcss-load-config": { + "version": "3.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-svelte/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/esniff": { + "version": "2.0.1", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "1.4.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.4.4", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/flowbite": { + "version": "2.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.3", + "flowbite-datepicker": "^1.3.0", + "mini-svg-data-uri": "^1.4.3" + } + }, + "node_modules/flowbite-datepicker": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-node-resolve": "^15.2.3", + "flowbite": "^2.0.0" + } + }, + "node_modules/flowbite-datepicker/node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/flowbite-svelte": { + "version": "0.48.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.13", + "apexcharts": "^3.54.1", + "flowbite": "^3.1.2", + "tailwind-merge": "^3.0.1" + }, + "peerDependencies": { + "svelte": "^3.55.1 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/flowbite-svelte-icons": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "peerDependencies": { + "svelte": "^5.0.0", + "tailwind-merge": "^3.0.0" + } + }, + "node_modules/flowbite-svelte/node_modules/flowbite": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.3", + "flowbite-datepicker": "^1.3.1", + "mini-svg-data-uri": "^1.4.3", + "postcss": "^8.5.1" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/internmap": { + "version": "2.0.3", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-expression/node_modules/acorn": { + "version": "7.4.1", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.35.0", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/light-bolt11-decoder": { + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "@scure/base": "1.1.1" + } + }, + "node_modules/light-bolt11-decoder/node_modules/@scure/base": { + "version": "1.1.1", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "license": "MIT" + }, + "node_modules/locate-character": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "license": "MIT" + }, + "node_modules/next-tick": { + "version": "1.1.0", + "license": "ISC" + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nostr-tools": { + "version": "2.10.4", + "license": "Unlicense", + "dependencies": { + "@noble/ciphers": "^0.5.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "optionalDependencies": { + "nostr-wasm": "0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nostr-tools/node_modules/@noble/curves": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/hashes": { + "version": "1.3.1", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@scure/base": { + "version": "1.1.1", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/nostr-wasm": { + "version": "0.1.0", + "license": "MIT", + "optional": true + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "license": "BSD-2-Clause", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/playwright": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.4.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/pug": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "pug-code-gen": "^3.0.3", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "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/readdirp/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/require-directory": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.40.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.2", + "@rollup/rollup-android-arm64": "4.40.2", + "@rollup/rollup-darwin-arm64": "4.40.2", + "@rollup/rollup-darwin-x64": "4.40.2", + "@rollup/rollup-freebsd-arm64": "4.40.2", + "@rollup/rollup-freebsd-x64": "4.40.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", + "@rollup/rollup-linux-arm-musleabihf": "4.40.2", + "@rollup/rollup-linux-arm64-gnu": "4.40.2", + "@rollup/rollup-linux-arm64-musl": "4.40.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-musl": "4.40.2", + "@rollup/rollup-linux-s390x-gnu": "4.40.2", + "@rollup/rollup-linux-x64-gnu": "4.40.2", + "@rollup/rollup-linux-x64-musl": "4.40.2", + "@rollup/rollup-win32-arm64-msvc": "4.40.2", + "@rollup/rollup-win32-ia32-msvc": "4.40.2", + "@rollup/rollup-win32-x64-msvc": "4.40.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "license": "BSD-3-Clause" + }, + "node_modules/sade": { + "version": "1.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "5.30.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^1.4.6", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "0.43.0", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "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", + "dev": true, + "license": "Apache-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/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "dev": true, + "license": "MIT" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config": { + "version": "4.0.2", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/totalist": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "license": "Apache-2.0" + }, + "node_modules/tseep": { + "version": "1.3.1", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tstl": { + "version": "2.5.16", + "license": "MIT" + }, + "node_modules/type": { + "version": "2.7.3", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-lru-cache": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unxhr": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">=8.11" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/utf8-buffer": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.19", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitefu": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.3", + "@vitest/mocker": "3.1.3", + "@vitest/pretty-format": "^3.1.3", + "@vitest/runner": "3.1.3", + "@vitest/snapshot": "3.1.3", + "@vitest/spy": "3.1.3", + "@vitest/utils": "3.1.3", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.13", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.1.3", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.1.3", + "@vitest/ui": "3.1.3", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/websocket": { + "version": "1.0.35", + "license": "Apache-2.0", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.63", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket-polyfill": { + "version": "0.0.3", + "dependencies": { + "tstl": "^2.0.7", + "websocket": "^1.0.28" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/with": { + "version": "7.0.2", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "license": "MIT", + "engines": { + "node": ">=0.10.32" + } + }, + "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/yargs": { + "version": "17.3.1", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + } + } +} diff --git a/src/lib/components/Login.svelte b/src/lib/components/Login.svelte index afa83a5..e0d1171 100644 --- a/src/lib/components/Login.svelte +++ b/src/lib/components/Login.svelte @@ -1,13 +1,10 @@ -{#if loading} - {name ?? '…'} -{:else if anon } - {@render userBadge(npub, username)} -{:else if npub } - - - {@render userBadge(npub, username)} + +{#if state.loading} + + + {name ?? '…'} + +{:else if state.error} + + + {name ?? shortenNpub(pubkey)} + +{:else if isAnonymous} + + {@render userBadge(pubkey, name)} +{:else if state.npub} + + + {#if showAvatar} + + {/if} + {@render userBadge(pubkey, name)} {:else} - {name ?? pubkey} + + + {name ?? shortenNpub(pubkey)} + {/if} \ No newline at end of file diff --git a/src/lib/consts.ts b/src/lib/consts.ts index dab6298..7866064 100644 --- a/src/lib/consts.ts +++ b/src/lib/consts.ts @@ -9,7 +9,9 @@ export const fallbackRelays = [ 'wss://relay.noswhere.com', 'wss://relay.damus.io', 'wss://relay.nostr.band', - 'wss://relay.lumina.rocks' + 'wss://relay.lumina.rocks', + 'wss://nostr.wine', + 'wss://nostr.land' ]; export enum FeedType { diff --git a/src/lib/snippets/UserSnippets.svelte b/src/lib/snippets/UserSnippets.svelte index 352a6f2..d8c960e 100644 --- a/src/lib/snippets/UserSnippets.svelte +++ b/src/lib/snippets/UserSnippets.svelte @@ -1,15 +1,19 @@ {#snippet userBadge(identifier: string, displayText: string | undefined)} - {#await createProfileLinkWithVerification(identifier, displayText)} - {@html createProfileLink(identifier, displayText)} - {:then html} - {@html html} - {:catch} - {@html createProfileLink(identifier, displayText)} - {/await} + {#if toNpub(identifier)} + {#await createProfileLinkWithVerification(toNpub(identifier) as string, displayText)} + {@html createProfileLink(toNpub(identifier) as string, displayText)} + {:then html} + {@html html} + {:catch} + {@html createProfileLink(toNpub(identifier) as string, displayText)} + {/await} + {:else} + {displayText ?? ''} + {/if} {/snippet} diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 06296f1..01c451d 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -105,7 +105,7 @@ export function createProfileLink(identifier: string, displayText: string | unde const defaultText = `${cleanId.slice(0, 8)}...${cleanId.slice(-4)}`; const escapedText = escapeHtml(displayText || defaultText); - return `@${escapedText}`; + return `@${escapedText}`; } /** @@ -149,9 +149,9 @@ export async function createProfileLinkWithVerification(identifier: string, disp const type = nip05.endsWith('edu') ? 'edu' : 'standard'; switch (type) { case 'edu': - return `@${displayIdentifier}${graduationCapSvg}`; + return `@${displayIdentifier}${graduationCapSvg}`; case 'standard': - return `@${displayIdentifier}${badgeCheckSvg}`; + return `@${displayIdentifier}${badgeCheckSvg}`; } } /** @@ -357,4 +357,20 @@ export async function fetchEventWithFallback( console.error('Error in fetchEventWithFallback:', err); return null; } +} + +/** + * Converts a hex pubkey to npub, or returns npub if already encoded. + */ +export function toNpub(pubkey: string | undefined): string | null { + if (!pubkey) return null; + try { + if (/^[a-f0-9]{64}$/i.test(pubkey)) { + return nip19.npubEncode(pubkey); + } + if (pubkey.startsWith('npub1')) return pubkey; + return null; + } catch { + return null; + } } \ No newline at end of file diff --git a/src/routes/[...catchall]/+page.svelte b/src/routes/[...catchall]/+page.svelte index dd838c9..18be414 100644 --- a/src/routes/[...catchall]/+page.svelte +++ b/src/routes/[...catchall]/+page.svelte @@ -1,5 +1,4 @@ diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte index abb07ff..a6badf3 100644 --- a/src/routes/about/+page.svelte +++ b/src/routes/about/+page.svelte @@ -1,5 +1,5 @@ + +
    + {#if event.kind !== 0 && getEventTitle(event)} +

    {getEventTitle(event)}

    + {:else if event.kind === 0 && profile && profile.name} +

    {profile.name}

    + {/if} + +
    + {#if toNpub(event.pubkey)} + Author: {@render userBadge(toNpub(event.pubkey) as string, profile?.display_name || event.pubkey)} + {:else} + Author: {profile?.display_name || event.pubkey} + {/if} +
    + +
    + Kind: + {event.kind} + ({getEventTypeDisplay(event)}) +
    + + {#if getEventSummary(event)} +
    + Summary: +

    {getEventSummary(event)}

    +
    + {/if} + + {#if getEventHashtags(event).length} +
    + Tags: +
    + {#each getEventHashtags(event) as tag} + #{tag} + {/each} +
    +
    + {/if} + + +
    + Content: + {#if event.kind === 0} + {#if profile} +
    +
    + {#if profile.name} +
    +
    Name:
    +
    {profile.name}
    +
    + {/if} + {#if profile.display_name} +
    +
    Display Name:
    +
    {profile.display_name}
    +
    + {/if} + {#if profile.about} +
    +
    About:
    +
    {profile.about}
    +
    + {/if} + {#if profile.picture} +
    +
    Picture:
    +
    + Profile { (e.target as HTMLImageElement).src = '/favicon.png'; }} /> +
    +
    + {/if} + {#if profile.banner} +
    +
    Banner:
    +
    + Banner { (e.target as HTMLImageElement).src = '/favicon.png'; }} /> +
    +
    + {/if} + {#if profile.website} +
    +
    Website:
    +
    + {profile.website} +
    +
    + {/if} + {#if profile.lud16} +
    +
    Lightning Address:
    +
    {profile.lud16}
    +
    + {/if} + {#if profile.nip05} +
    +
    NIP-05:
    +
    {profile.nip05}
    +
    + {/if} +
    +
    + {:else} +
    {event.content}
    + {/if} + {:else} +
    + {@html showFullContent ? parsedContent : contentPreview} + {#if !showFullContent && parsedContent.length > 250} + + {/if} +
    + {/if} +
    + + + {#if event.tags && event.tags.length} +
    + Event Tags: +
    + {#each event.tags as tag} + {@html renderTag(tag)} + {/each} +
    +
    + {/if} + + +
    + + Show Raw Event JSON + +
    +      {JSON.stringify(event.rawEvent(), null, 2)}
    +    
    +
    +
    \ No newline at end of file diff --git a/src/lib/components/EventSearch.svelte b/src/lib/components/EventSearch.svelte new file mode 100644 index 0000000..14f8e40 --- /dev/null +++ b/src/lib/components/EventSearch.svelte @@ -0,0 +1,218 @@ + + +
    +
    + e.key === 'Enter' && searchEvent(true)} + /> + +
    + + {#if localError || error} + + {/if} + +
    +
    + {#each Object.entries(relayStatuses) as [relay, status]} + + {/each} +
    + {#if !foundEvent && Object.values(relayStatuses).some(s => s === 'pending')} +
    Searching relays...
    + {/if} + {#if !foundEvent && !searching && Object.values(relayStatuses).every(s => s !== 'pending')} +
    Event not found on any relay.
    + {/if} +
    +
    \ No newline at end of file diff --git a/src/lib/components/PublicationFeed.svelte b/src/lib/components/PublicationFeed.svelte index ef3448f..c9224c6 100644 --- a/src/lib/components/PublicationFeed.svelte +++ b/src/lib/components/PublicationFeed.svelte @@ -2,84 +2,107 @@ import { indexKind } from '$lib/consts'; import { ndkInstance } from '$lib/ndk'; import { filterValidIndexEvents } from '$lib/utils'; - import { fetchEventWithFallback } from '$lib/utils/nostrUtils'; import { NDKRelaySet, type NDKEvent } from '@nostr-dev-kit/ndk'; import { Button, P, Skeleton, Spinner } from 'flowbite-svelte'; import ArticleHeader from './PublicationHeader.svelte'; import { onMount } from 'svelte'; - let { relays } = $props<{ relays: string[] }>(); + let { relays, fallbackRelays } = $props<{ relays: string[], fallbackRelays: string[] }>(); let eventsInView: NDKEvent[] = $state([]); let loadingMore: boolean = $state(false); let endOfFeed: boolean = $state(false); + let relayStatuses = $state>({}); + let loading: boolean = $state(true); let cutoffTimestamp: number = $derived( eventsInView?.at(eventsInView.length - 1)?.created_at ?? new Date().getTime() ); - async function getEvents( - before: number | undefined = undefined, - ): Promise { - try { - // First try to fetch a single event to verify we can connect to the relays - const testEvent = await fetchEventWithFallback($ndkInstance, { - kinds: [indexKind], - limit: 1, - until: before - }); - - if (!testEvent) { - console.warn('No events found in initial fetch'); - return; - } - - // If we found an event, proceed with fetching the full set - let eventSet = await $ndkInstance.fetchEvents( - { - kinds: [indexKind], - limit: 16, - until: before, - }, - { - groupable: false, - skipVerification: false, - skipValidation: false, - }, - NDKRelaySet.fromRelayUrls(relays, $ndkInstance) + async function getEvents(before: number | undefined = undefined) { + loading = true; + const ndk = $ndkInstance; + const primaryRelays: string[] = relays; + const fallback: string[] = fallbackRelays.filter((r: string) => !primaryRelays.includes(r)); + relayStatuses = Object.fromEntries(primaryRelays.map((r: string) => [r, 'pending'])); + let allEvents: NDKEvent[] = []; + // First, try primary relays + await Promise.all( + primaryRelays.map(async (relay: string) => { + try { + const relaySet = NDKRelaySet.fromRelayUrls([relay], ndk); + let eventSet = await ndk.fetchEvents( + { + kinds: [indexKind], + limit: 16, + until: before, + }, + { + groupable: false, + skipVerification: false, + skipValidation: false, + }, + relaySet + ).withTimeout(2500); + eventSet = filterValidIndexEvents(eventSet); + const eventArray = Array.from(eventSet); + if (eventArray.length > 0) { + allEvents = allEvents.concat(eventArray); + relayStatuses = { ...relayStatuses, [relay]: 'found' }; + } else { + relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; + } + } catch { + relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; + } + }) + ); + // If no events found, try fallback relays + if (allEvents.length === 0 && fallback.length > 0) { + relayStatuses = { ...relayStatuses, ...Object.fromEntries(fallback.map((r: string) => [r, 'pending'])) }; + await Promise.all( + fallback.map(async (relay: string) => { + try { + const relaySet = NDKRelaySet.fromRelayUrls([relay], ndk); + let eventSet = await ndk.fetchEvents( + { + kinds: [indexKind], + limit: 16, + until: before, + }, + { + groupable: false, + skipVerification: false, + skipValidation: false, + }, + relaySet + ).withTimeout(2500); + eventSet = filterValidIndexEvents(eventSet); + const eventArray = Array.from(eventSet); + if (eventArray.length > 0) { + allEvents = allEvents.concat(eventArray); + relayStatuses = { ...relayStatuses, [relay]: 'found' }; + } else { + relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; + } + } catch { + relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; + } + }) ); - eventSet = filterValidIndexEvents(eventSet); - - let eventArray = Array.from(eventSet); - eventArray?.sort((a, b) => b.created_at! - a.created_at!); - - if (!eventArray) { - return; - } - - endOfFeed = eventArray?.at(eventArray.length - 1)?.id === eventsInView?.at(eventsInView.length - 1)?.id; - - if (endOfFeed) { - return; - } - - const eventMap = new Map([...eventsInView, ...eventArray].map(event => [event.tagAddress(), event])); - const allEvents = Array.from(eventMap.values()); - const uniqueIds = new Set(allEvents.map(event => event.tagAddress())); - eventsInView = Array.from(uniqueIds) - .map(id => eventMap.get(id)) - .filter(event => event != null) as NDKEvent[]; - } catch (err) { - console.error('Error fetching events:', err); } + // Deduplicate and sort + const eventMap = new Map([...eventsInView, ...allEvents].map(event => [event.tagAddress(), event])); + const uniqueEvents = Array.from(eventMap.values()); + uniqueEvents.sort((a, b) => b.created_at! - a.created_at!); + eventsInView = uniqueEvents; + endOfFeed = false; // Could add logic to detect end + loading = false; } const getSkeletonIds = (): string[] => { const skeletonHeight = 124; // The height of the skeleton component in pixels. - - // Determine the number of skeletons to display based on the height of the screen. const skeletonCount = Math.floor(window.innerHeight / skeletonHeight) - 2; - const skeletonIds = []; for (let i = 0; i < skeletonCount; i++) { skeletonIds.push(`skeleton-${i}`); @@ -99,7 +122,12 @@
    - {#if eventsInView.length === 0} +
    + {#each Object.entries(relayStatuses) as [relay, status]} + {relay}: {status} + {/each} +
    + {#if loading && eventsInView.length === 0} {#each getSkeletonIds() as id} {/each} diff --git a/src/lib/components/PublicationHeader.svelte b/src/lib/components/PublicationHeader.svelte index dc29d47..9e71f4e 100644 --- a/src/lib/components/PublicationHeader.svelte +++ b/src/lib/components/PublicationHeader.svelte @@ -24,7 +24,7 @@ ); let title: string = $derived(event.getMatchingTags('title')[0]?.[1]); - let author: string = $derived(event.getMatchingTags('author')[0]?.[1] ?? 'unknown'); + let author: string = $derived(getMatchingTags(event, 'author')[0]?.[1] ?? 'unknown'); let version: string = $derived(event.getMatchingTags('version')[0]?.[1] ?? '1'); let image: string = $derived(event.getMatchingTags('image')[0]?.[1] ?? null); let authorPubkey: string = $derived(event.getMatchingTags('p')[0]?.[1] ?? null); diff --git a/src/lib/components/PublicationSection.svelte b/src/lib/components/PublicationSection.svelte index de23463..6c2586a 100644 --- a/src/lib/components/PublicationSection.svelte +++ b/src/lib/components/PublicationSection.svelte @@ -5,6 +5,7 @@ import { TextPlaceholder } from "flowbite-svelte"; import { getContext } from "svelte"; import type { Asciidoctor, Document } from "asciidoctor"; + import { getMatchingTags } from '$lib/utils/nostrUtils'; let { address, @@ -109,7 +110,7 @@ {:then [leafTitle, leafContent, leafHierarchy, publicationType, divergingBranches]} {#each divergingBranches as [branch, depth]} - {@render sectionHeading(branch.getMatchingTags('title')[0]?.[1] ?? '', depth)} + {@render sectionHeading(getMatchingTags(branch, 'title')[0]?.[1] ?? '', depth)} {/each} {#if leafTitle} {@const leafDepth = leafHierarchy.length - 1} diff --git a/src/lib/components/RelayActions.svelte b/src/lib/components/RelayActions.svelte new file mode 100644 index 0000000..666b872 --- /dev/null +++ b/src/lib/components/RelayActions.svelte @@ -0,0 +1,243 @@ + + +
    + + + {#if $ndkInstance?.activeUser} + + {/if} +
    + +{#if foundRelays.length > 0} +
    + Found on {foundRelays.length} relay(s): +
    + {#each foundRelays as relay} + + {/each} +
    +
    +{/if} + +{#if broadcastSuccess} +
    + Event broadcast successfully to: +
    + {#each getConnectedRelays() as relay} + + {/each} +
    +
    +{/if} + +{#if broadcastError} +
    + {broadcastError} +
    +{/if} + +
    + Found on: +
    + {#each getEventRelays(event) as relay} + + {/each} +
    +
    + +{#if showRelayModal} +
    +
    + +

    Relay Search Results

    +
    + {#each Object.entries({ + 'Standard Relays': standardRelays, + 'User Relays': Array.from($ndkInstance?.pool?.relays.values() || []).map(r => r.url), + 'Fallback Relays': fallbackRelays + }) as [groupName, groupRelays]} + {#if groupRelays.length > 0} +
    +

    + {groupName} +

    + {#each groupRelays as relay} + + {/each} +
    + {/if} + {/each} +
    +
    + +
    +
    +
    +{/if} \ No newline at end of file diff --git a/src/lib/components/RelayDisplay.svelte b/src/lib/components/RelayDisplay.svelte new file mode 100644 index 0000000..1161f7c --- /dev/null +++ b/src/lib/components/RelayDisplay.svelte @@ -0,0 +1,59 @@ + + + + +
    + relay icon { (e.target as HTMLImageElement).src = '/favicon.png'; }} + /> + {relay} + {#if showStatus && status} + {#if status === 'pending'} + + + + + {:else if status === 'found'} + + {:else} + + {/if} + {/if} +
    \ No newline at end of file diff --git a/src/lib/components/blog/BlogHeader.svelte b/src/lib/components/blog/BlogHeader.svelte index e264a3e..a91d0a4 100644 --- a/src/lib/components/blog/BlogHeader.svelte +++ b/src/lib/components/blog/BlogHeader.svelte @@ -10,7 +10,7 @@ const { rootId, event, onBlogUpdate, active = true } = $props<{ rootId: string, event: NDKEvent, onBlogUpdate?: any, active: boolean }>(); let title: string = $derived(event.getMatchingTags('title')[0]?.[1]); - let author: string = $derived(event.getMatchingTags('author')[0]?.[1] ?? 'unknown'); + let author: string = $derived(getMatchingTags(event, 'author')[0]?.[1] ?? 'unknown'); let image: string = $derived(event.getMatchingTags('image')[0]?.[1] ?? null); let authorPubkey: string = $derived(event.getMatchingTags('p')[0]?.[1] ?? null); let hashtags: string = $derived(event.getMatchingTags('t') ?? null); diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index 8d1ee40..b6ac5d7 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -17,7 +17,7 @@ }>(); let title: string = $derived(indexEvent.getMatchingTags('title')[0]?.[1]); - let author: string = $derived(indexEvent.getMatchingTags('author')[0]?.[1] ?? 'unknown'); + let author: string = $derived(indexgetMatchingTags(event, 'author')[0]?.[1] ?? 'unknown'); let pubkey: string = $derived(indexEvent.getMatchingTags('p')[0]?.[1] ?? null); let isLeaf: boolean = $derived(indexEvent.kind === 30041); diff --git a/src/lib/components/util/Details.svelte b/src/lib/components/util/Details.svelte index f228e2e..e776e9d 100644 --- a/src/lib/components/util/Details.svelte +++ b/src/lib/components/util/Details.svelte @@ -3,25 +3,26 @@ import CardActions from "$components/util/CardActions.svelte"; import Interactions from "$components/util/Interactions.svelte"; import { P } from "flowbite-svelte"; + import { getMatchingTags } from '$lib/utils/nostrUtils'; // isModal // - don't show interactions in modal view // - don't show all the details when _not_ in modal view let { event, isModal = false } = $props(); - let title: string = $derived(event.getMatchingTags('title')[0]?.[1]); - let author: string = $derived(event.getMatchingTags('author')[0]?.[1] ?? 'unknown'); - let version: string = $derived(event.getMatchingTags('version')[0]?.[1] ?? '1'); - let image: string = $derived(event.getMatchingTags('image')[0]?.[1] ?? null); - let originalAuthor: string = $derived(event.getMatchingTags('p')[0]?.[1] ?? null); - let summary: string = $derived(event.getMatchingTags('summary')[0]?.[1] ?? null); - let type: string = $derived(event.getMatchingTags('type')[0]?.[1] ?? null); - let language: string = $derived(event.getMatchingTags('l')[0]?.[1] ?? null); - let source: string = $derived(event.getMatchingTags('source')[0]?.[1] ?? null); - let publisher: string = $derived(event.getMatchingTags('published_by')[0]?.[1] ?? null); - let identifier: string = $derived(event.getMatchingTags('i')[0]?.[1] ?? null); - let hashtags: [] = $derived(event.getMatchingTags('t') ?? []); - let rootId: string = $derived(event.getMatchingTags('d')[0]?.[1] ?? null); + let title: string = $derived(getMatchingTags(event, 'title')[0]?.[1]); + let author: string = $derived(getMatchingTags(event, 'author')[0]?.[1] ?? 'unknown'); + let version: string = $derived(getMatchingTags(event, 'version')[0]?.[1] ?? '1'); + let image: string = $derived(getMatchingTags(event, 'image')[0]?.[1] ?? null); + let originalAuthor: string = $derived(getMatchingTags(event, 'p')[0]?.[1] ?? null); + let summary: string = $derived(getMatchingTags(event, 'summary')[0]?.[1] ?? null); + let type: string = $derived(getMatchingTags(event, 'type')[0]?.[1] ?? null); + let language: string = $derived(getMatchingTags(event, 'l')[0]?.[1] ?? null); + let source: string = $derived(getMatchingTags(event, 'source')[0]?.[1] ?? null); + let publisher: string = $derived(getMatchingTags(event, 'published_by')[0]?.[1] ?? null); + let identifier: string = $derived(getMatchingTags(event, 'i')[0]?.[1] ?? null); + let hashtags: string[] = $derived(getMatchingTags(event, 't').map(tag => tag[1])); + let rootId: string = $derived(getMatchingTags(event, 'd')[0]?.[1] ?? null); let kind = $derived(event.kind); @@ -67,7 +68,7 @@ {#if hashtags.length}
    {#each hashtags as tag} - #{tag[1]} + #{tag} {/each}
    {/if} diff --git a/src/lib/navigator/EventNetwork/NodeTooltip.svelte b/src/lib/navigator/EventNetwork/NodeTooltip.svelte index 4aedc8e..2f8d577 100644 --- a/src/lib/navigator/EventNetwork/NodeTooltip.svelte +++ b/src/lib/navigator/EventNetwork/NodeTooltip.svelte @@ -7,6 +7,7 @@ @@ -264,190 +53,10 @@ Use this page to view any event (npub, nprofile, nevent, naddr, note, pubkey, or eventID).

    -
    - e.key === 'Enter' && searchEvent()} - /> - -
    - - {#if error} - - {/if} - - {#if event && typeof event.getMatchingTags === 'function'} -
    - -
    - {neventEncode(event, standardRelays)} -
    - - -
    - {#if event.kind !== 0 && getEventTitle(event)} -

    {getEventTitle(event)}

    - {:else if event.kind === 0 && profile && profile.name} -

    {profile.name}

    - {/if} -
    - {#if toNpub(event.pubkey)} - Author: {@render userBadge(toNpub(event.pubkey) as string, profile?.display_name || event.pubkey)} - {:else} - Author: {profile?.display_name || event.pubkey} - {/if} -
    -
    - Kind: - {event.kind} - ({getEventTypeDisplay(event)}) -
    - {#if getEventSummary(event)} -
    - Summary: -

    {getEventSummary(event)}

    -
    - {/if} - {#if getEventHashtags(event).length} -
    - Tags: -
    - {#each getEventHashtags(event) as tag} - #{tag} - {/each} -
    -
    - {/if} - - -
    - Content: - {#if event.kind === 0} - {#if profile} -
    -
    - {#if profile.name} -
    -
    Name:
    -
    {profile.name}
    -
    - {/if} - {#if profile.display_name} -
    -
    Display Name:
    -
    {profile.display_name}
    -
    - {/if} - {#if profile.about} -
    -
    About:
    -
    {profile.about}
    -
    - {/if} - {#if profile.picture} -
    -
    Picture:
    -
    - Profile -
    -
    - {/if} - {#if profile.banner} -
    -
    Banner:
    -
    - Banner -
    -
    - {/if} - {#if profile.website} -
    -
    Website:
    -
    - {profile.website} -
    -
    - {/if} - {#if profile.lud16} -
    -
    Lightning Address:
    -
    {profile.lud16}
    -
    - {/if} - {#if profile.nip05} -
    -
    NIP-05:
    -
    {profile.nip05}
    -
    - {/if} -
    -
    - {:else} -
    {event.content}
    - {/if} - {:else} -
    - {@html showFullContent ? parsedContent : contentPreview} - {#if !showFullContent && parsedContent.length > 250} - - {/if} -
    - {/if} -
    - - - {#if event.tags && event.tags.length} -
    - Event Tags: -
    - {#each event.tags as tag} - {@html renderTag(tag)} - {/each} -
    -
    - {/if} - - -
    - - Show Raw Event JSON - -
    -{JSON.stringify(event.rawEvent(), null, 2)}
    -            
    -
    -
    -
    - {#if !getEventTitle(event) && !event.content} -
    - No title or content available for this event. -
    -            {JSON.stringify(event.rawEvent(), null, 2)}
    -          
    -
    - {/if} - {:else if event} -
    Fetched event is not a valid NDKEvent. See console for details.
    + + {#if event} + + {/if}
    diff --git a/src/routes/publication/+page.ts b/src/routes/publication/+page.ts index e3d6bf5..b100f70 100644 --- a/src/routes/publication/+page.ts +++ b/src/routes/publication/+page.ts @@ -3,6 +3,7 @@ import type { Load } from '@sveltejs/kit'; import type { NDKEvent } from '@nostr-dev-kit/ndk'; import { nip19 } from 'nostr-tools'; import { getActiveRelays } from '$lib/ndk'; +import { getMatchingTags } from '$lib/utils/nostrUtils'; /** * Decodes an naddr identifier and returns a filter object @@ -96,7 +97,7 @@ export const load: Load = async ({ url, parent }: { url: URL; parent: () => Prom ? await fetchEventById(ndk, id) : await fetchEventByDTag(ndk, dTag!); - const publicationType = indexEvent?.getMatchingTags('type')[0]?.[1]; + const publicationType = getMatchingTags(indexEvent, 'type')[0]?.[1]; const fetchPromise = parser.fetch(indexEvent); return { From c201135d9ad935f9768dab880aa91a8bc2d432f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Wed, 21 May 2025 20:31:43 +0200 Subject: [PATCH 14/17] Moved profile into a card, added lnurl and QR code --- package-lock.json | 218 +++++++++++++++++- package.json | 5 +- src/app.css | 1 + src/lib/components/EventDetails.svelte | 76 +----- src/lib/components/Preview.svelte | 2 +- src/lib/components/Publication.svelte | 2 +- .../{blog => cards}/BlogHeader.svelte | 0 src/lib/components/cards/ProfileHeader.svelte | 113 +++++++++ .../components/util/CopyToClipboard.svelte | 5 +- src/lib/components/util/QrCode.svelte | 17 ++ src/styles/events.css | 5 + 11 files changed, 368 insertions(+), 76 deletions(-) rename src/lib/components/{blog => cards}/BlogHeader.svelte (100%) create mode 100644 src/lib/components/cards/ProfileHeader.svelte create mode 100644 src/lib/components/util/QrCode.svelte create mode 100644 src/styles/events.css diff --git a/package-lock.json b/package-lock.json index 5d74252..b631ab9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,13 @@ "@tailwindcss/forms": "0.5.x", "@tailwindcss/typography": "0.5.x", "asciidoctor": "3.0.x", + "bech32": "^2.0.0", "d3": "^7.9.0", "he": "1.2.x", "highlight.js": "^11.11.1", "node-emoji": "^2.2.0", - "nostr-tools": "2.10.x" + "nostr-tools": "2.10.x", + "qrcode": "^1.5.4" }, "devDependencies": { "@playwright/test": "^1.50.1", @@ -30,6 +32,7 @@ "@types/d3": "^7.4.3", "@types/he": "1.2.x", "@types/node": "22.x", + "@types/qrcode": "^1.5.5", "autoprefixer": "10.x", "eslint-plugin-svelte": "2.x", "flowbite": "2.x", @@ -1230,6 +1233,16 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/qrcode": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz", + "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "dev": true, @@ -1561,6 +1574,12 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "license": "MIT", @@ -1673,6 +1692,15 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "license": "MIT", @@ -2236,6 +2264,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "dev": true, @@ -2278,6 +2315,12 @@ "version": "1.2.2", "license": "Apache-2.0" }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dlv": { "version": "1.1.3", "license": "MIT" @@ -3910,6 +3953,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "license": "BlueOak-1.0.0" @@ -3928,9 +3980,7 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4045,6 +4095,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/postcss": { "version": "8.5.3", "funding": [ @@ -4381,6 +4440,141 @@ "node": ">=6" } }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qrcode/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "funding": [ @@ -4437,6 +4631,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/resolve": { "version": "1.22.10", "license": "MIT", @@ -4565,6 +4765,12 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "dev": true, @@ -5567,6 +5773,12 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, "node_modules/why-is-node-running": { "version": "2.3.0", "dev": true, diff --git a/package.json b/package.json index 06ba05f..787d2e7 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,13 @@ "@tailwindcss/forms": "0.5.x", "@tailwindcss/typography": "0.5.x", "asciidoctor": "3.0.x", + "bech32": "^2.0.0", "d3": "^7.9.0", "he": "1.2.x", "highlight.js": "^11.11.1", "node-emoji": "^2.2.0", - "nostr-tools": "2.10.x" + "nostr-tools": "2.10.x", + "qrcode": "^1.5.4" }, "devDependencies": { "@playwright/test": "^1.50.1", @@ -36,6 +38,7 @@ "@types/d3": "^7.4.3", "@types/he": "1.2.x", "@types/node": "22.x", + "@types/qrcode": "^1.5.5", "autoprefixer": "10.x", "eslint-plugin-svelte": "2.x", "flowbite": "2.x", diff --git a/src/app.css b/src/app.css index 32e232c..21e1a48 100644 --- a/src/app.css +++ b/src/app.css @@ -2,6 +2,7 @@ @import './styles/scrollbar.css'; @import './styles/publications.css'; @import './styles/visualize.css'; +@import "./styles/events.css"; /* Custom styles */ @layer base { diff --git a/src/lib/components/EventDetails.svelte b/src/lib/components/EventDetails.svelte index 463dcfa..d4ad2e1 100644 --- a/src/lib/components/EventDetails.svelte +++ b/src/lib/components/EventDetails.svelte @@ -6,8 +6,8 @@ import { neventEncode, naddrEncode, nprofileEncode } from "$lib/utils"; import { standardRelays } from "$lib/consts"; import type { NDKEvent } from '$lib/utils/nostrUtils'; - import { onMount } from "svelte"; import { getMatchingTags } from '$lib/utils/nostrUtils'; + import ProfileHeader from "$components/cards/ProfileHeader.svelte"; const { event, profile = null } = $props<{ event: NDKEvent; @@ -68,8 +68,6 @@
    {#if event.kind !== 0 && getEventTitle(event)}

    {getEventTitle(event)}

    - {:else if event.kind === 0 && profile && profile.name} -

    {profile.name}

    {/if}
    @@ -106,71 +104,8 @@
    - Content: - {#if event.kind === 0} - {#if profile} -
    -
    - {#if profile.name} -
    -
    Name:
    -
    {profile.name}
    -
    - {/if} - {#if profile.display_name} -
    -
    Display Name:
    -
    {profile.display_name}
    -
    - {/if} - {#if profile.about} -
    -
    About:
    -
    {profile.about}
    -
    - {/if} - {#if profile.picture} -
    -
    Picture:
    -
    - Profile { (e.target as HTMLImageElement).src = '/favicon.png'; }} /> -
    -
    - {/if} - {#if profile.banner} -
    -
    Banner:
    -
    - Banner { (e.target as HTMLImageElement).src = '/favicon.png'; }} /> -
    -
    - {/if} - {#if profile.website} -
    -
    Website:
    -
    - {profile.website} -
    -
    - {/if} - {#if profile.lud16} -
    -
    Lightning Address:
    -
    {profile.lud16}
    -
    - {/if} - {#if profile.nip05} -
    -
    NIP-05:
    -
    {profile.nip05}
    -
    - {/if} -
    -
    - {:else} -
    {event.content}
    - {/if} - {:else} + {#if event.kind !== 0} + Content:
    {@html showFullContent ? parsedContent : contentPreview} {#if !showFullContent && parsedContent.length > 250} @@ -180,6 +115,11 @@ {/if}
    + + {#if event.kind === 0} + + {/if} + {#if event.tags && event.tags.length}
    diff --git a/src/lib/components/Preview.svelte b/src/lib/components/Preview.svelte index d25ac71..e7a3f29 100644 --- a/src/lib/components/Preview.svelte +++ b/src/lib/components/Preview.svelte @@ -4,7 +4,7 @@ import { CaretDownSolid, CaretUpSolid, EditOutline } from 'flowbite-svelte-icons'; import Self from './Preview.svelte'; import { contentParagraph, sectionHeading } from '$lib/snippets/PublicationSnippets.svelte'; - import BlogHeader from "./blog/BlogHeader.svelte"; + import BlogHeader from "$components/cards/BlogHeader.svelte"; // TODO: Fix move between parents. diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index 5df2fe4..f7e026f 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -18,7 +18,7 @@ import type { PublicationTree } from "$lib/data_structures/publication_tree"; import Details from "$components/util/Details.svelte"; import { publicationColumnVisibility } from "$lib/stores"; - import BlogHeader from "$components/blog/BlogHeader.svelte"; + import BlogHeader from "$components/cards/BlogHeader.svelte"; import Interactions from "$components/util/Interactions.svelte"; import TocToggle from "$components/util/TocToggle.svelte"; import { pharosInstance } from '$lib/parser'; diff --git a/src/lib/components/blog/BlogHeader.svelte b/src/lib/components/cards/BlogHeader.svelte similarity index 100% rename from src/lib/components/blog/BlogHeader.svelte rename to src/lib/components/cards/BlogHeader.svelte diff --git a/src/lib/components/cards/ProfileHeader.svelte b/src/lib/components/cards/ProfileHeader.svelte new file mode 100644 index 0000000..bee6f48 --- /dev/null +++ b/src/lib/components/cards/ProfileHeader.svelte @@ -0,0 +1,113 @@ + + +{#if profile} + +
    + {#if profile.banner} +
    + Profile banner { (e.target as HTMLImageElement).style.display = 'none';}} /> +
    + {/if} +
    + {#if profile.picture} + Profile avatar { (e.target as HTMLImageElement).src = '/favicon.png'; }} /> + {/if} + {@render userBadge(toNpub(event.pubkey) as string, profile.displayName || profile.name || event.pubkey)} +
    +
    +
    +
    + {#if profile.name} +
    +
    Name:
    +
    {profile.name}
    +
    + {/if} + {#if profile.displayName} +
    +
    Display Name:
    +
    {profile.displayName}
    +
    + {/if} + {#if profile.about} +
    +
    About:
    +
    {profile.about}
    +
    + {/if} + {#if profile.website} +
    +
    Website:
    +
    + {profile.website} +
    +
    + {/if} + {#if profile.nip05} +
    +
    NIP-05:
    +
    {profile.nip05}
    +
    + {/if} + {#if profile.lud16} +
    +
    Lightning Address:
    +
    +
    + {/if} +
    +
    +
    +
    +
    + + + {#if profile.lud16} +
    +
    + {@render userBadge(toNpub(event.pubkey) as string, profile?.displayName || profile.name || event.pubkey)} +

    {profile.lud16}

    +
    +
    +

    Scan the QR code or copy the address

    + {#if lnurl} +

    + +

    + + {:else} +

    Couldn't generate address.

    + {/if} +
    +
    + {/if} +
    +{/if} \ No newline at end of file diff --git a/src/lib/components/util/CopyToClipboard.svelte b/src/lib/components/util/CopyToClipboard.svelte index cb49a6b..19c9850 100644 --- a/src/lib/components/util/CopyToClipboard.svelte +++ b/src/lib/components/util/CopyToClipboard.svelte @@ -1,11 +1,12 @@ + + diff --git a/src/styles/events.css b/src/styles/events.css new file mode 100644 index 0000000..9e8c202 --- /dev/null +++ b/src/styles/events.css @@ -0,0 +1,5 @@ +@layer components { + canvas.qr-code { + @apply block mx-auto my-4; + } +} \ No newline at end of file From 3c6daa123172672cdeba0633c440370fd16c9d86 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Wed, 21 May 2025 22:27:18 +0200 Subject: [PATCH 15/17] landing page columns and search bar. Event comment box. bugfixes --- .vscode/settings.json | 18 +- src/lib/components/CommentBox.svelte | 317 +++++++++++++++++++ src/lib/components/EventDetails.svelte | 5 +- src/lib/components/EventSearch.svelte | 39 --- src/lib/components/PublicationFeed.svelte | 90 ++++-- src/lib/components/PublicationHeader.svelte | 2 +- src/lib/components/RelayActions.svelte | 53 ---- src/lib/components/util/ArticleNav.svelte | 3 +- src/lib/components/util/InlineProfile.svelte | 183 ----------- src/lib/stores/relayStore.ts | 4 + src/lib/utils.ts | 26 ++ src/lib/utils/nostrUtils.ts | 34 ++ src/routes/+page.svelte | 27 +- src/routes/events/+page.svelte | 23 +- 14 files changed, 499 insertions(+), 325 deletions(-) create mode 100644 src/lib/components/CommentBox.svelte delete mode 100644 src/lib/components/util/InlineProfile.svelte create mode 100644 src/lib/stores/relayStore.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 65737f7..e06c2f4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,14 @@ { - "editor.tabSize": 2, -"files.associations": { - "*.css": "postcss" -} -} \ No newline at end of file + "css.validate": false, + "tailwindCSS.includeLanguages": { + "svelte": "html", + "typescript": "javascript", + "javascript": "javascript" + }, + "editor.quickSuggestions": { + "strings": true + }, + "files.associations": { + "*.svelte": "svelte" + } +} \ No newline at end of file diff --git a/src/lib/components/CommentBox.svelte b/src/lib/components/CommentBox.svelte new file mode 100644 index 0000000..c46f902 --- /dev/null +++ b/src/lib/components/CommentBox.svelte @@ -0,0 +1,317 @@ + + +
    +
    + {#each markupButtons as button} + + {/each} + + +
    + +
    +
    +