Browse Source

Fix the page redirection to sustain the Amber sessions

master
silberengel 8 months ago
parent
commit
c30bd1d519
  1. 10
      src/lib/components/CommentBox.svelte
  2. 41
      src/lib/components/EventDetails.svelte
  3. 3
      src/lib/components/LoginMenu.svelte
  4. 1
      src/lib/components/Preview.svelte
  5. 32
      src/lib/components/PublicationHeader.svelte
  6. 25
      src/lib/components/util/Details.svelte
  7. 52
      src/lib/snippets/UserSnippets.svelte
  8. 2
      src/lib/stores/userStore.ts
  9. 11
      src/lib/utils/nostrUtils.ts
  10. 5
      src/routes/about/+page.svelte
  11. 9
      src/routes/events/+page.svelte
  12. 21
      src/routes/start/+page.svelte

10
src/lib/components/CommentBox.svelte

@ -263,11 +263,15 @@
{/if} {/if}
{#if success} {#if success}
{@const s = success}
<Alert color="green" dismissable> <Alert color="green" dismissable>
Comment published successfully to {success.relay}! Comment published successfully to {s.relay}!
<a href="/events?id={nip19.neventEncode({ id: success.eventId })}" class="text-primary-600 dark:text-primary-500 hover:underline"> <button
class="text-primary-600 dark:text-primary-500 hover:underline bg-transparent border-none p-0 ml-2"
onclick={() => goto(`/events?id=${nip19.neventEncode({ id: s.eventId })}`)}
>
View your comment View your comment
</a> </button>
</Alert> </Alert>
{/if} {/if}

41
src/lib/components/EventDetails.svelte

@ -8,6 +8,8 @@
import type { NDKEvent } from '$lib/utils/nostrUtils'; import type { NDKEvent } from '$lib/utils/nostrUtils';
import { getMatchingTags } from '$lib/utils/nostrUtils'; import { getMatchingTags } from '$lib/utils/nostrUtils';
import ProfileHeader from "$components/cards/ProfileHeader.svelte"; import ProfileHeader from "$components/cards/ProfileHeader.svelte";
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
const { event, profile = null, searchValue = null } = $props<{ const { event, profile = null, searchValue = null } = $props<{
event: NDKEvent; event: NDKEvent;
@ -27,6 +29,12 @@
let showFullContent = $state(false); let showFullContent = $state(false);
let parsedContent = $state(''); let parsedContent = $state('');
let contentPreview = $state(''); let contentPreview = $state('');
let authorTag: string = $derived(getMatchingTags(event, 'author')[0]?.[1] ?? '');
let pTag: string = $derived(getMatchingTags(event, 'p')[0]?.[1] ?? '');
function isValidNostrPubkey(str: string): boolean {
return /^[a-f0-9]{64}$/i.test(str) || (str.startsWith('npub1') && str.length >= 59 && str.length <= 63);
}
function getEventTitle(event: NDKEvent): string { function getEventTitle(event: NDKEvent): string {
return getMatchingTags(event, 'title')[0]?.[1] || 'Untitled'; return getMatchingTags(event, 'title')[0]?.[1] || 'Untitled';
@ -48,9 +56,9 @@
function renderTag(tag: string[]): string { function renderTag(tag: string[]): string {
if (tag[0] === 'a' && tag.length > 1) { if (tag[0] === 'a' && tag.length > 1) {
const [kind, pubkey, d] = tag[1].split(':'); const [kind, pubkey, d] = tag[1].split(':');
return `<a href='/events?id=${naddrEncode({kind: +kind, pubkey, tags: [['d', d]], content: '', id: '', sig: ''} as any, standardRelays)}' class='underline text-primary-700'>a:${tag[1]}</a>`; return `<button class='underline text-primary-700 dark:text-primary-700 bg-transparent border-none p-0' onclick='goto("/events?id=${naddrEncode({kind: +kind, pubkey, tags: [["d", d]], content: '', id: '', sig: ''} as any, standardRelays)}")'>a:${tag[1]}</button>`;
} else if (tag[0] === 'e' && tag.length > 1) { } else if (tag[0] === 'e' && tag.length > 1) {
return `<a href='/events?id=${neventEncode({id: tag[1], kind: 1, content: '', tags: [], pubkey: '', sig: ''} as any, standardRelays)}' class='underline text-primary-700'>e:${tag[1]}</a>`; return `<button class='underline text-primary-700 dark:text-primary-700 bg-transparent border-none p-0' onclick='goto("/events?id=${neventEncode({id: tag[1], kind: 1, content: '', tags: [], pubkey: '', sig: ''} as any, standardRelays)}")'>e:${tag[1]}</button>`;
} else { } else {
return `<span class='bg-primary-50 text-primary-800 px-2 py-1 rounded text-xs font-mono'>${tag[0]}:${tag[1]}</span>`; return `<span class='bg-primary-50 text-primary-800 px-2 py-1 rounded text-xs font-mono'>${tag[0]}:${tag[1]}</span>`;
} }
@ -100,6 +108,21 @@
const norm = (s: string) => s.replace(/^nostr:/, '').toLowerCase(); const norm = (s: string) => s.replace(/^nostr:/, '').toLowerCase();
return norm(value) === norm(searchValue); return norm(value) === norm(searchValue);
} }
onMount(() => {
function handleInternalLinkClick(event: MouseEvent) {
const target = event.target as HTMLElement;
if (target.tagName === 'A') {
const href = (target as HTMLAnchorElement).getAttribute('href');
if (href && href.startsWith('/')) {
event.preventDefault();
goto(href);
}
}
}
document.addEventListener('click', handleInternalLinkClick);
return () => document.removeEventListener('click', handleInternalLinkClick);
});
</script> </script>
<div class="flex flex-col space-y-4"> <div class="flex flex-col space-y-4">
@ -108,11 +131,19 @@
{/if} {/if}
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
{#if toNpub(event.pubkey)} <span class="text-gray-600 dark:text-gray-400">Author:
<span class="text-gray-600 dark:text-gray-400">Author: {@render userBadge(toNpub(event.pubkey) as string, profile?.display_name || event.pubkey)}</span> {#if authorTag && pTag && isValidNostrPubkey(pTag)}
{authorTag} {@render userBadge(pTag, '')}
{:else if authorTag}
{authorTag}
{:else if pTag && isValidNostrPubkey(pTag)}
{@render userBadge(pTag, '')}
{:else if authorTag}
{authorTag}
{:else} {:else}
<span class="text-gray-600 dark:text-gray-400">Author: {profile?.display_name || event.pubkey}</span> unknown
{/if} {/if}
</span>
</div> </div>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">

3
src/lib/components/LoginMenu.svelte

@ -5,6 +5,7 @@
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { goto } from '$app/navigation';
// UI state // UI state
let isLoadingExtension: boolean = $state(false); let isLoadingExtension: boolean = $state(false);
@ -230,7 +231,7 @@
<li> <li>
<button <button
class='text-sm text-primary-600 dark:text-primary-400 underline hover:text-primary-400 dark:hover:text-primary-500 px-0 bg-transparent border-none cursor-pointer' class='text-sm text-primary-600 dark:text-primary-400 underline hover:text-primary-400 dark:hover:text-primary-500 px-0 bg-transparent border-none cursor-pointer'
onclick={() => window.location.href = `./events?id=${user.npub}`} onclick={() => goto(`/events?id=${user.npub}`)}
type='button' type='button'
> >
{user.npub ? shortenNpub(user.npub) : 'Unknown'} {user.npub ? shortenNpub(user.npub) : 'Unknown'}

1
src/lib/components/Preview.svelte

@ -6,6 +6,7 @@
import { contentParagraph, sectionHeading } from '$lib/snippets/PublicationSnippets.svelte'; import { contentParagraph, sectionHeading } from '$lib/snippets/PublicationSnippets.svelte';
import BlogHeader from "$components/cards/BlogHeader.svelte"; import BlogHeader from "$components/cards/BlogHeader.svelte";
import { getMatchingTags } from '$lib/utils/nostrUtils'; import { getMatchingTags } from '$lib/utils/nostrUtils';
import { onMount } from 'svelte';
// TODO: Fix move between parents. // TODO: Fix move between parents.

32
src/lib/components/PublicationHeader.svelte

@ -6,6 +6,7 @@
import { Card, Img } from "flowbite-svelte"; import { Card, Img } from "flowbite-svelte";
import CardActions from "$components/util/CardActions.svelte"; import CardActions from "$components/util/CardActions.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte"; import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { goto } from '$app/navigation';
const { event } = $props<{ event: NDKEvent }>(); const { event } = $props<{ event: NDKEvent }>();
@ -24,10 +25,14 @@
); );
let title: string = $derived(event.getMatchingTags('title')[0]?.[1]); let title: string = $derived(event.getMatchingTags('title')[0]?.[1]);
let author: string = $derived(event.getMatchingTags(event, 'author')[0]?.[1] ?? 'unknown'); let authorTag: string = $derived(event.getMatchingTags('author')[0]?.[1] ?? '');
let pTag: string = $derived(event.getMatchingTags('p')[0]?.[1] ?? '');
let version: string = $derived(event.getMatchingTags('version')[0]?.[1] ?? '1'); let version: string = $derived(event.getMatchingTags('version')[0]?.[1] ?? '1');
let image: string = $derived(event.getMatchingTags('image')[0]?.[1] ?? null); let image: string = $derived(event.getMatchingTags('image')[0]?.[1] ?? null);
let authorPubkey: string = $derived(event.getMatchingTags('p')[0]?.[1] ?? null);
function isValidNostrPubkey(str: string): boolean {
return /^[a-f0-9]{64}$/i.test(str) || (str.startsWith('npub1') && str.length >= 59 && str.length <= 63);
}
</script> </script>
{#if title != null && href != null} {#if title != null && href != null}
@ -35,24 +40,37 @@
{#if image} {#if image}
<div class="flex col justify-center align-middle max-h-36 max-w-24 overflow-hidden"> <div class="flex col justify-center align-middle max-h-36 max-w-24 overflow-hidden">
<Img src={image} class="rounded w-full h-full object-cover"/> <Img src={image} class="rounded w-full h-full object-cover"/>
<!-- Index author badge over image -->
<div class="absolute top-2 left-2 z-10">
{@render userBadge(event.pubkey, '')}
</div>
</div> </div>
{/if} {/if}
<div class='col flex flex-row flex-grow space-x-4'> <div class='col flex flex-row flex-grow space-x-4'>
<div class="flex flex-col flex-grow"> <div class="flex flex-col flex-grow">
<a href="/{href}" class='flex flex-col space-y-2'> <button
class="flex flex-col space-y-2 text-left w-full bg-transparent border-none p-0 hover:underline"
onclick={() => goto(`/${href}`)}
>
<h2 class='text-lg font-bold line-clamp-2' title="{title}">{title}</h2> <h2 class='text-lg font-bold line-clamp-2' title="{title}">{title}</h2>
<h3 class='text-base font-normal'> <h3 class='text-base font-normal'>
by by
{#if authorPubkey != null} {#if authorTag && pTag && isValidNostrPubkey(pTag)}
{@render userBadge(authorPubkey, author)} {authorTag} {@render userBadge(pTag, '')}
{:else if authorTag}
{authorTag}
{:else if pTag && isValidNostrPubkey(pTag)}
{@render userBadge(pTag, '')}
{:else if authorTag}
{authorTag}
{:else} {:else}
{author} unknown
{/if} {/if}
</h3> </h3>
{#if version != '1'} {#if version != '1'}
<h3 class='text-base font-thin'>version: {version}</h3> <h3 class='text-base font-thin'>version: {version}</h3>
{/if} {/if}
</a> </button>
</div> </div>
<div class="flex flex-col justify-start items-center"> <div class="flex flex-col justify-start items-center">
<CardActions event={event} /> <CardActions event={event} />

25
src/lib/components/util/Details.svelte

@ -11,10 +11,8 @@
let { event, isModal = false } = $props(); let { event, isModal = false } = $props();
let title: string = $derived(getMatchingTags(event, 'title')[0]?.[1]); 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 version: string = $derived(getMatchingTags(event, 'version')[0]?.[1] ?? '1');
let image: string = $derived(getMatchingTags(event, 'image')[0]?.[1] ?? null); 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 summary: string = $derived(getMatchingTags(event, 'summary')[0]?.[1] ?? null);
let type: string = $derived(getMatchingTags(event, 'type')[0]?.[1] ?? null); let type: string = $derived(getMatchingTags(event, 'type')[0]?.[1] ?? null);
let language: string = $derived(getMatchingTags(event, 'l')[0]?.[1] ?? null); let language: string = $derived(getMatchingTags(event, 'l')[0]?.[1] ?? null);
@ -25,6 +23,12 @@
let rootId: string = $derived(getMatchingTags(event, 'd')[0]?.[1] ?? null); let rootId: string = $derived(getMatchingTags(event, 'd')[0]?.[1] ?? null);
let kind = $derived(event.kind); let kind = $derived(event.kind);
let authorTag: string = $derived(getMatchingTags(event, 'author')[0]?.[1] ?? '');
let pTag: string = $derived(getMatchingTags(event, 'p')[0]?.[1] ?? '');
function isValidNostrPubkey(str: string): boolean {
return /^[a-f0-9]{64}$/i.test(str) || (str.startsWith('npub1') && str.length >= 59 && str.length <= 63);
}
</script> </script>
@ -32,7 +36,8 @@
<div class="flex flex-col relative mb-2"> <div class="flex flex-col relative mb-2">
{#if !isModal} {#if !isModal}
<div class="flex flex-row justify-between items-center"> <div class="flex flex-row justify-between items-center">
<P class='text-base font-normal'>{@render userBadge(event.pubkey, author)}</P> <!-- Index author badge -->
<P class='text-base font-normal'>{@render userBadge(event.pubkey, '')}</P>
<CardActions event={event}></CardActions> <CardActions event={event}></CardActions>
</div> </div>
{/if} {/if}
@ -46,10 +51,16 @@
<h1 class="text-3xl font-bold">{title}</h1> <h1 class="text-3xl font-bold">{title}</h1>
<h2 class="text-base font-bold"> <h2 class="text-base font-bold">
by by
{#if originalAuthor !== null} {#if authorTag && pTag && isValidNostrPubkey(pTag)}
{@render userBadge(originalAuthor, author)} {authorTag} {@render userBadge(pTag, '')}
{:else if authorTag}
{authorTag}
{:else if pTag && isValidNostrPubkey(pTag)}
{@render userBadge(pTag, '')}
{:else if authorTag}
{authorTag}
{:else} {:else}
{author} unknown
{/if} {/if}
</h2> </h2>
{#if version !== '1' } {#if version !== '1' }
@ -81,7 +92,7 @@
{:else} {:else}
<span>Author:</span> <span>Author:</span>
{/if} {/if}
{@render userBadge(event.pubkey, author)} {@render userBadge(event.pubkey, '')}
</h4> </h4>
</div> </div>

52
src/lib/snippets/UserSnippets.svelte

@ -1,18 +1,58 @@
<script module lang='ts'> <script module lang='ts'>
import { createProfileLink, createProfileLinkWithVerification, toNpub } from '$lib/utils/nostrUtils'; import { goto } from '$app/navigation';
import { createProfileLinkWithVerification, toNpub, getUserMetadata } from '$lib/utils/nostrUtils';
// Extend NostrProfile locally to allow display_name for legacy support
type NostrProfileWithLegacy = {
displayName?: string;
display_name?: string;
name?: string;
[key: string]: any;
};
export { userBadge }; export { userBadge };
</script> </script>
{#snippet userBadge(identifier: string, displayText: string | undefined)} {#snippet userBadge(identifier: string, displayText: string | undefined)}
{#if toNpub(identifier)} {@const npub = toNpub(identifier)}
{#await createProfileLinkWithVerification(toNpub(identifier) as string, displayText)} {#if npub}
{@html createProfileLink(toNpub(identifier) as string, displayText)} {#if !displayText || displayText.trim().toLowerCase() === 'unknown'}
{#await getUserMetadata(npub) then profile}
{@const p = profile as NostrProfileWithLegacy}
<span class="inline-flex items-center gap-0.5">
<button class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" onclick={() => goto(`/events?id=${npub}`)}>
@{p.displayName || p.display_name || p.name || npub.slice(0,8) + '...' + npub.slice(-4)}
</button>
</span>
{:catch}
<span class="inline-flex items-center gap-0.5">
<button class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" onclick={() => goto(`/events?id=${npub}`)}>
@{npub.slice(0,8) + '...' + npub.slice(-4)}
</button>
</span>
{/await}
{:else}
{#await createProfileLinkWithVerification(npub as string, displayText)}
<span class="inline-flex items-center gap-0.5">
<button class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" onclick={() => goto(`/events?id=${npub}`)}>
@{displayText}
</button>
</span>
{:then html} {:then html}
{@html html} <span class="inline-flex items-center gap-0.5">
<button class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" onclick={() => goto(`/events?id=${npub}`)}>
@{displayText}
</button>
{@html html.replace(/([\s\S]*<\/a>)/, '').trim()}
</span>
{:catch} {:catch}
{@html createProfileLink(toNpub(identifier) as string, displayText)} <span class="inline-flex items-center gap-0.5">
<button class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" onclick={() => goto(`/events?id=${npub}`)}>
@{displayText}
</button>
</span>
{/await} {/await}
{/if}
{:else} {:else}
{displayText ?? ''} {displayText ?? ''}
{/if} {/if}

2
src/lib/stores/userStore.ts

@ -168,7 +168,7 @@ export async function loginWithAmber(amberSigner: NDKSigner, user: NDKUser) {
if (!ndk) throw new Error('NDK not initialized'); if (!ndk) throw new Error('NDK not initialized');
// Only clear previous login state after successful login // Only clear previous login state after successful login
const npub = user.npub; const npub = user.npub;
const profile = await getUserMetadata(npub); const profile = await getUserMetadata(npub, true); // Force fresh fetch
const [persistedInboxes, persistedOutboxes] = getPersistedRelays(user); const [persistedInboxes, persistedOutboxes] = getPersistedRelays(user);
for (const relay of persistedInboxes) { for (const relay of persistedInboxes) {
ndk.addExplicitRelay(relay); ndk.addExplicitRelay(relay);

11
src/lib/utils/nostrUtils.ts

@ -46,11 +46,11 @@ function escapeHtml(text: string): string {
/** /**
* Get user metadata for a nostr identifier (npub or nprofile) * Get user metadata for a nostr identifier (npub or nprofile)
*/ */
export async function getUserMetadata(identifier: string): Promise<NostrProfile> { export async function getUserMetadata(identifier: string, force = false): Promise<NostrProfile> {
// Remove nostr: prefix if present // Remove nostr: prefix if present
const cleanId = identifier.replace(/^nostr:/, ''); const cleanId = identifier.replace(/^nostr:/, '');
if (npubCache.has(cleanId)) { if (!force && npubCache.has(cleanId)) {
return npubCache.get(cleanId)!; return npubCache.get(cleanId)!;
} }
@ -111,7 +111,8 @@ export function createProfileLink(identifier: string, displayText: string | unde
const defaultText = `${cleanId.slice(0, 8)}...${cleanId.slice(-4)}`; const defaultText = `${cleanId.slice(0, 8)}...${cleanId.slice(-4)}`;
const escapedText = escapeHtml(displayText || defaultText); const escapedText = escapeHtml(displayText || defaultText);
return `<a href="./events?id=${escapedId}" class="npub-badge" target="_blank">@${escapedText}</a>`; // Remove target="_blank" for internal navigation
return `<a href="./events?id=${escapedId}" class="npub-badge">@${escapedText}</a>`;
} }
/** /**
@ -167,9 +168,9 @@ export async function createProfileLinkWithVerification(identifier: string, disp
const type = nip05.endsWith('edu') ? 'edu' : 'standard'; const type = nip05.endsWith('edu') ? 'edu' : 'standard';
switch (type) { switch (type) {
case 'edu': case 'edu':
return `<span class="npub-badge"><a href="./events?id=${escapedId}" target="_blank">@${displayIdentifier}</a>${graduationCapSvg}</span>`; return `<span class="npub-badge"><a href="./events?id=${escapedId}">@${displayIdentifier}</a>${graduationCapSvg}</span>`;
case 'standard': case 'standard':
return `<span class="npub-badge"><a href="./events?id=${escapedId}" target="_blank">@${displayIdentifier}</a>${badgeCheckSvg}</span>`; return `<span class="npub-badge"><a href="./events?id=${escapedId}">@${displayIdentifier}</a>${badgeCheckSvg}</span>`;
} }
} }
/** /**

5
src/routes/about/+page.svelte

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { userBadge } from "$lib/snippets/UserSnippets.svelte"; import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { Heading, Img, P, A } from "flowbite-svelte"; import { Heading, Img, P, A } from "flowbite-svelte";
import { goto } from '$app/navigation';
// Get the git tag version from environment variables // Get the git tag version from environment variables
const appVersion = import.meta.env.APP_VERSION || "development"; const appVersion = import.meta.env.APP_VERSION || "development";
@ -34,9 +35,7 @@
</P> </P>
<P class="mb-3"> <P class="mb-3">
Please submit support issues on the <A href="/contact" Please submit support issues on the <button class="underline text-primary-700 bg-transparent border-none p-0" onclick={() => goto('/contact')}>Contact</button> page and follow us on <A
>Alexandria contact page</A
> and follow us on <A
href="https://github.com/ShadowySupercode/gitcitadel" href="https://github.com/ShadowySupercode/gitcitadel"
target="_blank">GitHub</A target="_blank">GitHub</A
> and <A href="https://geyser.fund/project/gitcitadel" target="_blank" > and <A href="https://geyser.fund/project/gitcitadel" target="_blank"

9
src/routes/events/+page.svelte

@ -39,11 +39,10 @@
} }
} }
onMount(async () => { // Use Svelte 5 idiomatic effect to update searchValue when $page.url.searchParams.get('id') changes
const id = $page.url.searchParams.get('id'); $effect(() => {
if (id) { const url = $page.url.searchParams;
searchValue = id; searchValue = url.get('id') ?? url.get('d');
}
}); });
</script> </script>

21
src/routes/start/+page.svelte

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Heading, Img, P, A } from "flowbite-svelte"; import { Heading, Img, P, A } from "flowbite-svelte";
import { goto } from '$app/navigation';
// Get the git tag version from environment variables // Get the git tag version from environment variables
const appVersion = import.meta.env.APP_VERSION || "development"; const appVersion = import.meta.env.APP_VERSION || "development";
@ -15,7 +16,7 @@
<Heading tag="h2" class="h-leather mt-4 mb-2">Overview</Heading> <Heading tag="h2" class="h-leather mt-4 mb-2">Overview</Heading>
<P class="mb-4"> <P class="mb-4">
Alexandria opens up to the <A href="./">landing page</A>, where the user Alexandria opens up to the <button class="underline text-primary-700 bg-transparent border-none p-0" onclick={() => goto('./')}>landing page</button>, where the user
can: login (top-right), select whether to only view the publications can: login (top-right), select whether to only view the publications
hosted on the <A href="https://thecitadel.nostr1.com/" target="_blank" hosted on the <A href="https://thecitadel.nostr1.com/" target="_blank"
>thecitadel document relay</A >thecitadel document relay</A
@ -87,9 +88,9 @@
</P> </P>
<P class="mb-3"> <P class="mb-3">
An example of a book is <A An example of a book is <a
href="/publication?d=jane-eyre-an-autobiography-by-charlotte-bront%C3%AB-v-3rd-edition" href="/publication?d=jane-eyre-an-autobiography-by-charlotte-bront%C3%AB-v-3rd-edition"
>Jane Eyre</A >Jane Eyre</a
> >
</P> </P>
@ -123,9 +124,9 @@
</P> </P>
<P class="mb-3"> <P class="mb-3">
An example of a research paper is <A An example of a research paper is <a
href="/publication?d=less-partnering-less-children-or-both-by-julia-hellstrand-v-1" href="/publication?d=less-partnering-less-children-or-both-by-julia-hellstrand-v-1"
>Less Partnering, Less Children, or Both?</A >Less Partnering, Less Children, or Both?</a
> >
</P> </P>
@ -141,11 +142,11 @@
<Heading tag="h3" class="h-leather mb-3">For documentation</Heading> <Heading tag="h3" class="h-leather mb-3">For documentation</Heading>
<P class="mb-3"> <P class="mb-3">
Our own team uses Alexandria to document the app, to display our <A Our own team uses Alexandria to document the app, to display our <a
href="/publication?d=the-gitcitadel-blog-by-stella-v-1">blog entries</A href="/publication?d=the-gitcitadel-blog-by-stella-v-1">blog entries</a
>, as well as to store copies of our most interesting <A >, as well as to store copies of our most interesting <a
href="/publication?d=gitcitadel-project-documentation-by-stella-v-1" href="/publication?d=gitcitadel-project-documentation-by-stella-v-1"
>technical specifications</A >technical specifications</a
>. >.
</P> </P>
@ -163,7 +164,7 @@
<P class="mb-3"> <P class="mb-3">
Alexandria now supports wiki pages (kind 30818), allowing for Alexandria now supports wiki pages (kind 30818), allowing for
collaborative knowledge bases and documentation. Wiki pages, such as this collaborative knowledge bases and documentation. Wiki pages, such as this
one about the <A href="/publication?d=sybil">Sybil utility</A> use the same one about the <button class="underline text-primary-700 bg-transparent border-none p-0" onclick={() => goto('/publication?d=sybil')}>Sybil utility</button> use the same
Asciidoc format as other publications but are specifically designed for interconnected, Asciidoc format as other publications but are specifically designed for interconnected,
evolving content. evolving content.
</P> </P>

Loading…
Cancel
Save