From 452a73cec2307bcc5ded66bdef4ffb8df7d4024b Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 9 Dec 2024 08:13:58 -0600 Subject: [PATCH 01/18] Configure for static site generation Build currently fails because some routes are dynamic. --- package-lock.json | 11 +++++++++++ package.json | 1 + src/routes/+layout.ts | 1 + svelte.config.js | 10 ++++++++-- 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/routes/+layout.ts diff --git a/package-lock.json b/package-lock.json index 212fe6c..d09c1bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ }, "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", + "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.4.3", "@types/he": "^1.2.3", "@types/markdown-it": "^13.0.7", @@ -658,6 +659,16 @@ "@sveltejs/kit": "^2.0.0" } }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz", + "integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, "node_modules/@sveltejs/kit": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.0.tgz", diff --git a/package.json b/package.json index 99658c9..5f65bae 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", + "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.4.3", "@types/he": "^1.2.3", "@types/markdown-it": "^13.0.7", diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..189f71e --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1 @@ +export const prerender = true; diff --git a/svelte.config.js b/svelte.config.js index 1f5ee97..aeddb48 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-auto'; +import adapter from '@sveltejs/adapter-static'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ @@ -11,7 +11,13 @@ const config = { // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // If your environment is not supported or you settled on a specific environment, switch out the adapter. // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter(), + adapter: adapter({ + pages: 'build', + assets: 'build', + fallback: null, // TODO: Create a 404.html page. + precompress: false, + strict: true, + }), alias: { $lib: 'src/lib', $components: 'src/lib/components', From eaffca1539c381f9b7de1e06a5b29ced8a19b8b3 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 9 Dec 2024 08:41:43 -0600 Subject: [PATCH 02/18] Fix parameter name --- src/routes/[id]/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/[id]/+page.svelte b/src/routes/[id]/+page.svelte index ac0fe07..81ebaef 100644 --- a/src/routes/[id]/+page.svelte +++ b/src/routes/[id]/+page.svelte @@ -8,5 +8,5 @@
-
+
From 2917c2f7ee2b238e7e5a5b0ff38c5462efc11343 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 9 Dec 2024 09:06:23 -0600 Subject: [PATCH 03/18] Make page viewer route prerenderable --- src/routes/[id]/+page.svelte | 12 ------------ src/routes/[id]/+page.ts | 26 -------------------------- src/routes/article/+page.svelte | 26 ++++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 38 deletions(-) delete mode 100644 src/routes/[id]/+page.svelte delete mode 100644 src/routes/[id]/+page.ts create mode 100644 src/routes/article/+page.svelte diff --git a/src/routes/[id]/+page.svelte b/src/routes/[id]/+page.svelte deleted file mode 100644 index 81ebaef..0000000 --- a/src/routes/[id]/+page.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - -
-
-
diff --git a/src/routes/[id]/+page.ts b/src/routes/[id]/+page.ts deleted file mode 100644 index 504be65..0000000 --- a/src/routes/[id]/+page.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getNdkInstance, ndk } from '$lib/ndk'; -import type { NDKEvent } from '@nostr-dev-kit/ndk'; -import { error } from '@sveltejs/kit'; - -// MichaelJ - 23 July 2024 - Disable server-side rendering so that the load function can use the -// browser's local storage to retrieve saved relays and the cache adapter for the NDK instance. -export const ssr = false; - -export const load = async ({ params }) => { - const ndk = getNdkInstance(); - const { id } = params; - - let event: NDKEvent | null | undefined; - - try { - event = await ndk.fetchEvent(id); - } catch (err) { - console.error(err); - } - - if (!event) { - error(404, 'No event found with the given ID.'); - } - - return { event }; -}; diff --git a/src/routes/article/+page.svelte b/src/routes/article/+page.svelte new file mode 100644 index 0000000..b49baf7 --- /dev/null +++ b/src/routes/article/+page.svelte @@ -0,0 +1,26 @@ + + +
+
+
From e1af2f03df169c2c9dc7d9a46ce54b09088f080f Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sun, 15 Dec 2024 08:58:40 -0600 Subject: [PATCH 04/18] Bump patch version in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f65bae..e25ff0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alexandria", - "version": "0.0.5", + "version": "0.0.6", "private": true, "type": "module", "scripts": { From 558aa6c2088320d297b55c1d1bac58e2d3ffe505 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sun, 15 Dec 2024 08:59:26 -0600 Subject: [PATCH 05/18] Use static routes for article viewer --- src/lib/components/ArticleHeader.svelte | 4 +-- src/routes/article/+page.svelte | 26 --------------- src/routes/d/[tag]/+page.svelte | 20 ------------ src/routes/d/[tag]/+page.ts | 11 ------- src/routes/publication/+page.svelte | 42 +++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 59 deletions(-) delete mode 100644 src/routes/article/+page.svelte delete mode 100644 src/routes/d/[tag]/+page.svelte delete mode 100644 src/routes/d/[tag]/+page.ts create mode 100644 src/routes/publication/+page.svelte diff --git a/src/lib/components/ArticleHeader.svelte b/src/lib/components/ArticleHeader.svelte index 0e758cd..a5d054e 100644 --- a/src/lib/components/ArticleHeader.svelte +++ b/src/lib/components/ArticleHeader.svelte @@ -20,9 +20,9 @@ const d = event.getMatchingTags('d')[0][1]; if (d != null) { - href = `d/${d}`; + href = `publication?d=${d}`; } else { - href = neventEncode(event, relays); + href = `publication?id=${neventEncode(event, relays)}`; } } catch (e) { console.warn(e); diff --git a/src/routes/article/+page.svelte b/src/routes/article/+page.svelte deleted file mode 100644 index b49baf7..0000000 --- a/src/routes/article/+page.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - -
-
-
diff --git a/src/routes/d/[tag]/+page.svelte b/src/routes/d/[tag]/+page.svelte deleted file mode 100644 index 5534c5c..0000000 --- a/src/routes/d/[tag]/+page.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -
- {#await getIndexEvent(data.event.d)} - - {:then index} -
- {/await} -
diff --git a/src/routes/d/[tag]/+page.ts b/src/routes/d/[tag]/+page.ts deleted file mode 100644 index 79712f2..0000000 --- a/src/routes/d/[tag]/+page.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { PageLoad } from './$types'; - -export const load: PageLoad = async ({ params }) => { - const { tag } = params; - - return { - event: { - d: tag, - } - }; -}; diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte new file mode 100644 index 0000000..0f288e6 --- /dev/null +++ b/src/routes/publication/+page.svelte @@ -0,0 +1,42 @@ + + +
+ {#await event} + + {:then ev} +
+ {/await} +
From 132b3433456e5e84f6481ca416e8de2b9e2ca766 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sun, 15 Dec 2024 09:21:39 -0600 Subject: [PATCH 06/18] Change outdated comment --- svelte.config.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/svelte.config.js b/svelte.config.js index aeddb48..4a3459b 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -8,9 +8,7 @@ const config = { preprocess: [vitePreprocess()], kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. + // Static adapter adapter: adapter({ pages: 'build', assets: 'build', From 401bd7b27cec71ffbdb466299e3addfa1fde943a Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sun, 15 Dec 2024 12:57:57 -0600 Subject: [PATCH 07/18] Only handle 30041 events as zettels --- src/lib/consts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/consts.ts b/src/lib/consts.ts index fbe3477..2b114fd 100644 --- a/src/lib/consts.ts +++ b/src/lib/consts.ts @@ -1,6 +1,6 @@ export const wikiKind = 30818; export const indexKind = 30040; -export const zettelKinds = [ 1, 30024, 30041, 30818]; +export const zettelKinds = [ 30041 ]; export const standardRelays = [ "wss://thecitadel.nostr1.com", "wss://relay.noswhere.com" ]; export enum FeedType { From fdefbcb1f967ddda98d9c075a353806501f5d365 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sun, 15 Dec 2024 12:58:45 -0600 Subject: [PATCH 08/18] Enable parser to fetch publications from relays --- src/lib/parser.ts | 79 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/lib/parser.ts b/src/lib/parser.ts index 0c0b0a8..874bb34 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -12,6 +12,7 @@ import asciidoctor, { } from 'asciidoctor'; import he from 'he'; import { writable, type Writable } from 'svelte/store'; +import { indexKind, zettelKinds } from './consts'; interface IndexMetadata { authors?: string[]; @@ -154,6 +155,28 @@ export default class Pharos { } } + /** + * Fetches and parses the event tree for a publication given the event or event ID of the + * publication's root index. + * @param event The event or event ID of the publication's root index. + */ + async fetch(event: NDKEvent | string): Promise { + let content: string; + + if (typeof event === 'string') { + const index = await this.ndk.fetchEvent({ ids: [event] }); + if (!index) { + throw new Error('Failed to fetch publication.'); + } + + content = await this.getPublicationContent(index); + } else { + content = await this.getPublicationContent(event); + } + + this.parse(content); + } + /** * Generates and stores Nostr events from the parsed AsciiDoc document. The events can be * modified via the parser's API and retrieved via the `getEvents()` method. @@ -558,6 +581,62 @@ export default class Pharos { } } + /** + * Uses the NDK to crawl the event tree of a publication and return its content as a string. + * @param event The root index event of the publication. + * @returns The content of the publication as a string. + * @remarks This function does a depth-first crawl of the event tree using the relays specified + * on the NDK instance. + */ + private async getPublicationContent(event: NDKEvent, depth: number = 0): Promise { + let content: string = ''; + + // Format title into AsciiDoc header. + const title = event.getMatchingTags('title')[0][1]; + let titleLevel = ''; + for (let i = 0; i <= depth; i++) { + titleLevel += '='; + } + content += `${titleLevel} ${title}\n\n`; + + // TODO: Deprecate `e` tags in favor of `a` tags required by NIP-62. + let tags = event.getMatchingTags('a'); + if (tags.length === 0) { + tags = event.getMatchingTags('e'); + } + + // Base case: The event is a zettel. + if (zettelKinds.includes(event.kind ?? -1)) { + content += event.content; + return content; + } + + // Recursive case: The event is an index. + const childEvents = await Promise.all( + tags.map(tag => this.ndk.fetchEventFromTag(tag, event)) + ); + + // Michael J - 15 December 2024 - This could be further parallelized by recursively fetching + // children of index events before processing them for content. We won't make that change now, + // as it would increase complexity, but if performance suffers, we can revisit this option. + const childContentPromises: Promise[] = []; + for (let i = 0; i < childEvents.length; i++) { + const childEvent = childEvents[i]; + + if (!childEvent) { + console.warn(`NDK could not find event ${tags[i][1]}.`); + continue; + } + + childContentPromises.push(this.getPublicationContent(childEvent, depth + 1)); + } + + const childContents = await Promise.all(childContentPromises); + content += childContents.join('\n\n'); + + return content; + } + // #endregion // #region NDKEvent Generation From af5ae819160ac6f10c9b0f52e2297ddcdf90a2b3 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sun, 15 Dec 2024 12:59:35 -0600 Subject: [PATCH 09/18] Use Preview component for reader view --- src/lib/components/Article.svelte | 116 +++++++++++++----------------- 1 file changed, 48 insertions(+), 68 deletions(-) diff --git a/src/lib/components/Article.svelte b/src/lib/components/Article.svelte index 032865c..2935342 100644 --- a/src/lib/components/Article.svelte +++ b/src/lib/components/Article.svelte @@ -3,36 +3,24 @@ import type { NDKEvent } from '@nostr-dev-kit/ndk'; import { page } from '$app/stores'; import { Button, Heading, Sidebar, SidebarGroup, SidebarItem, SidebarWrapper, Skeleton, TextPlaceholder, Tooltip } from 'flowbite-svelte'; - import showdown from 'showdown'; import { onMount } from 'svelte'; import { BookOutline } from 'flowbite-svelte-icons'; - import { zettelKinds } from '../consts'; + import Pharos, { parser } from '$lib/parser'; + import Preview from './Preview.svelte'; export let index: NDKEvent | null | undefined; + $parser ??= new Pharos($ndk); + $: activeHash = $page.url.hash; - const getEvents = async (index?: NDKEvent | null | undefined): Promise> => { - if (index == null) { - // TODO: Add error handling. + const getContentRoot = async (index?: NDKEvent | null | undefined): Promise => { + if (!index) { + return null; } - const eventIds = index!.getMatchingTags('e').map((value) => value[1]); - const events = await $ndk.fetchEvents( - { - // @ts-ignore - kinds: zettelKinds, - ids: eventIds, - }, - { - groupable: false, - skipVerification: false, - skipValidation: false - } - ); - - console.debug(`Fetched ${events.size} events from ${eventIds.length} references.`); - return events; + await $parser.fetch(index); + return $parser.getRootIndexId(); }; function normalizeHashPath(str: string): string { @@ -104,62 +92,54 @@ window.removeEventListener('click', hideTocOnClick); }; }); - - const converter = new showdown.Converter(); -{#await getEvents(index)} +{#await getContentRoot(index)} -{:then events} - {#if showTocButton && !showToc} - - - Show Table of Contents - - {/if} - {#if showToc} - - - - {#each events as event} - - {/each} - - - +{:then rootId} + {#if rootId} + {#if showTocButton && !showToc} + + + Show Table of Contents + + {/if} + + +
+ +
+ {:else} + {/if} -
- {#each events as event} -
- - {event.getMatchingTags('title')[0][1]} - - {@html converter.makeHtml(event.content)} -
- {/each} -
{/await}