diff --git a/import_map.json b/import_map.json index 7398dc6..daca291 100644 --- a/import_map.json +++ b/import_map.json @@ -13,6 +13,7 @@ "svelte": "npm:svelte@5.0.x", "flowbite": "npm:flowbite@2.2.x", "flowbite-svelte": "npm:flowbite-svelte@0.44.x", - "flowbite-svelte-icons": "npm:flowbite-svelte-icons@2.0.x" + "flowbite-svelte-icons": "npm:flowbite-svelte-icons@2.0.x", + "child_process": "node:child_process" } } \ No newline at end of file diff --git a/src/app.html b/src/app.html index da5c914..d025d7c 100644 --- a/src/app.html +++ b/src/app.html @@ -2,7 +2,7 @@ - + %sveltekit.head% diff --git a/src/lib/components/Login.svelte b/src/lib/components/Login.svelte index 2d77763..1456149 100644 --- a/src/lib/components/Login.svelte +++ b/src/lib/components/Login.svelte @@ -50,7 +50,7 @@
+
+
- {node.type} ({node.isContainer ? "30040" : "30041"}) + {node.type} ({node.kind})
-
- ID: {node.id} - {#if node.naddr} -
{node.naddr}
- {/if} - {#if node.nevent} -
{node.nevent}
- {/if} +
+ Author: {getAuthorTag(node)}
+ + {#if node.isContainer && getSummaryTag(node)} +
+ Summary: {truncateContent(getSummaryTag(node) || "", 200)} +
+ {/if} + {#if node.content}
- {node.content} + {truncateContent(node.content)}
{/if} {#if selected} @@ -41,4 +131,4 @@
{/if}
-
\ No newline at end of file + diff --git a/src/lib/navigator/EventNetwork/index.svelte b/src/lib/navigator/EventNetwork/index.svelte index 9246df3..6a7fa61 100644 --- a/src/lib/navigator/EventNetwork/index.svelte +++ b/src/lib/navigator/EventNetwork/index.svelte @@ -8,6 +8,7 @@ import { createSimulation, setupDragHandlers, applyGlobalLogGravity, applyConnectedGravity } from "./utils/forceSimulation"; import Legend from "./Legend.svelte"; import NodeTooltip from "./NodeTooltip.svelte"; + import type { NetworkNode, NetworkLink } from "./types"; let { events = [] } = $props<{ events?: NDKEvent[] }>(); @@ -90,14 +91,14 @@ function updateGraph() { if (!svg || !events?.length || !svgGroup) return; - const { nodes, links } = generateGraph(events, currentLevels); + const { nodes, links } = generateGraph(events, Number(currentLevels)); if (!nodes.length) return; // Stop any existing simulation if (simulation) simulation.stop(); // Create new simulation - simulation = createSimulation(nodes, links, nodeRadius, linkDistance); + simulation = createSimulation(nodes, links, Number(nodeRadius), Number(linkDistance)); const dragHandler = setupDragHandlers(simulation); // Update links @@ -303,6 +304,11 @@ updateGraph(); } }); + + function handleTooltipClose() { + tooltipVisible = false; + selectedNodeId = null; + }
{/if}
- - \ No newline at end of file diff --git a/src/lib/stores.ts b/src/lib/stores.ts index 04aa785..71f9fdc 100644 --- a/src/lib/stores.ts +++ b/src/lib/stores.ts @@ -3,6 +3,6 @@ import { FeedType } from "./consts"; export let idList = writable([]); -export let alexandriaKinds = readable([30040, 30041]); +export let alexandriaKinds = readable([30040, 30041, 30818]); export let feedType = writable(FeedType.StandardRelays); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 0045d4c..4cac70e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,15 +2,44 @@ import "../app.css"; import Navigation from "$lib/components/Navigation.svelte"; import { onMount } from "svelte"; + import { page } from "$app/stores"; // Compute viewport height. $: displayHeight = window.innerHeight; + // Get standard metadata for OpenGraph tags + let title = 'Library of Alexandria'; + let currentUrl = $page.url.href; + + // Get default image and summary for the Alexandria website + let image = '/screenshots/old_books.jpg'; + let summary = 'Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages.'; + onMount(() => { document.body.style.height = `${displayHeight}px`; }); + + + {title} + + + + + + + + + + + + + + + + +
diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte index 60d3738..ace2103 100644 --- a/src/routes/about/+page.svelte +++ b/src/routes/about/+page.svelte @@ -1,13 +1,23 @@
- About - +
+ About the Library of Alexandria + {#if isVersionKnown} + Version: {appVersion} + {/if} +
+ Alexandria icon +

- Alexandria is a reader and writer for curated publications (in Asciidoc), and will eventually also support long-form articles (Markdown) and wiki pages (Asciidoc). It is produced by the GitCitadel project team. + Alexandria is a reader and writer for curated publications (in Asciidoc), wiki pages (Asciidoc), and will eventually also support long-form articles (Markdown). It is produced by the GitCitadel project team.

@@ -34,11 +44,11 @@

- If you click on a card, which represents a 30040 index event, the associated reading view opens to the publication. The app then pulls all of the content events (30041s), in the order in which they are indexed, and displays them as a single document. + If you click on a card, which represents a 30040 index event, the associated reading view opens to the publication. The app then pulls all of the content events (30041s and 30818s for wiki pages), in the order in which they are indexed, and displays them as a single document.

- Each 30041 section is also a level in the table of contents, which can be accessed from the floating icon top-left in the reading view. This allows for navigation within the publication. (This functionality has been temporarily disabled.) + Each content section (30041 or 30818) is also a level in the table of contents, which can be accessed from the floating icon top-left in the reading view. This allows for navigation within the publication. (This functionality has been temporarily disabled.)

@@ -93,6 +103,16 @@
Documentation
+ + For wiki pages + +

+ Alexandria now supports wiki pages (kind 30818), allowing for collaborative knowledge bases and documentation. Wiki pages use the same Asciidoc format as other publications but are specifically designed for interconnected, evolving content. +

+ +

+ Wiki pages can be linked to from other publications and can contain links to other wiki pages, creating a web of knowledge that can be navigated and explored. +

diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index b566965..e6f532d 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -4,20 +4,62 @@ import type { PageData } from "./$types"; import { onDestroy, setContext } from "svelte"; import { PublicationTree } from "$lib/data_structures/publication_tree"; + import { page } from "$app/stores"; - let { data }: { data: PageData } = $props(); + // Extend the PageData type with the properties we need + interface ExtendedPageData extends PageData { + waitable: Promise; + publicationType: string; + indexEvent: NDKEvent; + parser: any; + } const publicationTree = new PublicationTree(data.publicationRootEvent, data.ndk); setContext('publicationTree', publicationTree); + let { data } = $props<{ data: ExtendedPageData }>(); + + // Get publication metadata for OpenGraph tags + let title = $derived(data.indexEvent?.getMatchingTags('title')[0]?.[1] || data.parser?.getIndexTitle(data.parser?.getRootIndexId()) || 'Alexandria Publication'); + let currentUrl = $page.url.href; + + // Get image and summary from the event tags if available + // If image unavailable, use the Alexandria default pic. + let image = $derived(data.indexEvent?.getMatchingTags('image')[0]?.[1] || '/screenshots/old_books.jpg'); + let summary = $derived(data.indexEvent?.getMatchingTags('summary')[0]?.[1] || 'Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages.'); + onDestroy(() => data.parser.reset()); + + + {title} + + + + + + + + + + + + + + + + +
{#await data.waitable} {:then} -
+
{/await}
diff --git a/src/routes/publication/+page.ts b/src/routes/publication/+page.ts index 72116ec..56b7fd1 100644 --- a/src/routes/publication/+page.ts +++ b/src/routes/publication/+page.ts @@ -1,6 +1,6 @@ import { error } from '@sveltejs/kit'; +import type { Load } from '@sveltejs/kit'; import type { NDKEvent } from '@nostr-dev-kit/ndk'; -import type { PageLoad } from './$types'; import { nip19 } from 'nostr-tools'; import { getActiveRelays } from '$lib/ndk.ts'; @@ -82,7 +82,7 @@ async function fetchEventByDTag(ndk: any, dTag: string): Promise { } } -export const load: PageLoad = async ({ url, parent }: { url: URL; parent: () => Promise }) => { +export const load: Load = async ({ url, parent }: { url: URL; parent: () => Promise }) => { const id = url.searchParams.get('id'); const dTag = url.searchParams.get('d'); const { ndk, parser } = await parent(); @@ -102,6 +102,6 @@ export const load: PageLoad = async ({ url, parent }: { url: URL; parent: () => return { waitable: fetchPromise, publicationType, - publicationRootEvent: indexEvent, + indexEvent, }; }; diff --git a/src/routes/visualize/+page.svelte b/src/routes/visualize/+page.svelte index 35b837d..f1f7d33 100644 --- a/src/routes/visualize/+page.svelte +++ b/src/routes/visualize/+page.svelte @@ -49,7 +49,7 @@ // Fetch the referenced content events const contentEvents = await $ndkInstance.fetchEvents( { - kinds: [30041], + kinds: [30041, 30818], ids: Array.from(contentEventIds), }, { @@ -79,44 +79,45 @@
-

Publication Network

- - - - {#if !loading && !error} - +
+

Publication Network

+ + + {#if !loading && !error} + + {/if} +
+ {#if !loading && !error && showSettings} - {#if showSettings} -
-
-

- Visualization Settings -

+
+
+

+ Visualization Settings +

-
- - Showing {events.length} events from {$networkFetchLimit} headers - - - -
+
+ + Showing {events.length} events from {$networkFetchLimit} headers + + +
- {/if} +
{/if} + {#if loading}
@@ -155,6 +156,6 @@
{:else} -
+
{/if} -
\ No newline at end of file +
diff --git a/static/favicon.png b/static/favicon.png index 825b9e6..7418972 100644 Binary files a/static/favicon.png and b/static/favicon.png differ diff --git a/static/screenshots/old_books.jpg b/static/screenshots/old_books.jpg new file mode 100644 index 0000000..933a0be Binary files /dev/null and b/static/screenshots/old_books.jpg differ diff --git a/vite.config.ts b/vite.config.ts index d723dc1..4dc4254 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,22 @@ import { sveltekit } from "@sveltejs/kit/vite"; import { defineConfig } from "vite"; +import { execSync } from "child_process"; + +// Function to get the latest git tag +function getAppVersionString() { + // if running in ci context, we can assume the package has been properly versioned + if (process.env.ALEXANDIRA_IS_CI_BUILD && process.env.npm_package_version && process.env.npm_package_version.trim() !== '') { + return process.env.npm_package_version; + } + + try { + // Get the latest git tag, assuming git is installed and tagged branch is available + const tag = execSync('git describe --tags --abbrev=0').toString().trim(); + return tag; + } catch (error) { + return 'development'; + } +} export default defineConfig({ plugins: [sveltekit()], @@ -11,5 +28,9 @@ export default defineConfig({ }, test: { include: ['./tests/unit/**/*.unit-test.js'] + }, + define: { + // Expose the app version as a global variable + 'import.meta.env.APP_VERSION': JSON.stringify(getAppVersionString()) } });