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. 45
      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. 58
      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 @@ @@ -263,11 +263,15 @@
{/if}
{#if success}
{@const s = success}
<Alert color="green" dismissable>
Comment published successfully to {success.relay}!
<a href="/events?id={nip19.neventEncode({ id: success.eventId })}" class="text-primary-600 dark:text-primary-500 hover:underline">
Comment published successfully to {s.relay}!
<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
</a>
</button>
</Alert>
{/if}

45
src/lib/components/EventDetails.svelte

@ -8,6 +8,8 @@ @@ -8,6 +8,8 @@
import type { NDKEvent } from '$lib/utils/nostrUtils';
import { getMatchingTags } from '$lib/utils/nostrUtils';
import ProfileHeader from "$components/cards/ProfileHeader.svelte";
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
const { event, profile = null, searchValue = null } = $props<{
event: NDKEvent;
@ -27,6 +29,12 @@ @@ -27,6 +29,12 @@
let showFullContent = $state(false);
let parsedContent = $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 {
return getMatchingTags(event, 'title')[0]?.[1] || 'Untitled';
@ -48,9 +56,9 @@ @@ -48,9 +56,9 @@
function renderTag(tag: string[]): string {
if (tag[0] === 'a' && tag.length > 1) {
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) {
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 {
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 @@ @@ -100,6 +108,21 @@
const norm = (s: string) => s.replace(/^nostr:/, '').toLowerCase();
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>
<div class="flex flex-col space-y-4">
@ -108,11 +131,19 @@ @@ -108,11 +131,19 @@
{/if}
<div class="flex items-center space-x-2">
{#if toNpub(event.pubkey)}
<span class="text-gray-600 dark:text-gray-400">Author: {@render userBadge(toNpub(event.pubkey) as string, profile?.display_name || event.pubkey)}</span>
{:else}
<span class="text-gray-600 dark:text-gray-400">Author: {profile?.display_name || event.pubkey}</span>
{/if}
<span class="text-gray-600 dark:text-gray-400">Author:
{#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}
unknown
{/if}
</span>
</div>
<div class="flex items-center space-x-2">

3
src/lib/components/LoginMenu.svelte

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
import { get } from 'svelte/store';
import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
// UI state
let isLoadingExtension: boolean = $state(false);
@ -230,7 +231,7 @@ @@ -230,7 +231,7 @@
<li>
<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'
onclick={() => window.location.href = `./events?id=${user.npub}`}
onclick={() => goto(`/events?id=${user.npub}`)}
type='button'
>
{user.npub ? shortenNpub(user.npub) : 'Unknown'}

1
src/lib/components/Preview.svelte

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

32
src/lib/components/PublicationHeader.svelte

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
import { Card, Img } from "flowbite-svelte";
import CardActions from "$components/util/CardActions.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { goto } from '$app/navigation';
const { event } = $props<{ event: NDKEvent }>();
@ -24,10 +25,14 @@ @@ -24,10 +25,14 @@
);
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 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>
{#if title != null && href != null}
@ -35,24 +40,37 @@ @@ -35,24 +40,37 @@
{#if image}
<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"/>
<!-- Index author badge over image -->
<div class="absolute top-2 left-2 z-10">
{@render userBadge(event.pubkey, '')}
</div>
</div>
{/if}
<div class='col flex flex-row flex-grow space-x-4'>
<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>
<h3 class='text-base font-normal'>
by
{#if authorPubkey != null}
{@render userBadge(authorPubkey, author)}
{#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}
{author}
unknown
{/if}
</h3>
{#if version != '1'}
<h3 class='text-base font-thin'>version: {version}</h3>
{/if}
</a>
</button>
</div>
<div class="flex flex-col justify-start items-center">
<CardActions event={event} />

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

@ -11,10 +11,8 @@ @@ -11,10 +11,8 @@
let { event, isModal = false } = $props();
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);
@ -25,6 +23,12 @@ @@ -25,6 +23,12 @@
let rootId: string = $derived(getMatchingTags(event, 'd')[0]?.[1] ?? null);
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>
@ -32,7 +36,8 @@ @@ -32,7 +36,8 @@
<div class="flex flex-col relative mb-2">
{#if !isModal}
<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>
</div>
{/if}
@ -46,10 +51,16 @@ @@ -46,10 +51,16 @@
<h1 class="text-3xl font-bold">{title}</h1>
<h2 class="text-base font-bold">
by
{#if originalAuthor !== null}
{@render userBadge(originalAuthor, author)}
{#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}
{author}
unknown
{/if}
</h2>
{#if version !== '1' }
@ -81,7 +92,7 @@ @@ -81,7 +92,7 @@
{:else}
<span>Author:</span>
{/if}
{@render userBadge(event.pubkey, author)}
{@render userBadge(event.pubkey, '')}
</h4>
</div>

58
src/lib/snippets/UserSnippets.svelte

@ -1,18 +1,58 @@ @@ -1,18 +1,58 @@
<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 };
</script>
{#snippet userBadge(identifier: string, displayText: string | undefined)}
{#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}
{@const npub = toNpub(identifier)}
{#if npub}
{#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}
<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}
<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}
{/if}
{:else}
{displayText ?? ''}
{/if}

2
src/lib/stores/userStore.ts

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

11
src/lib/utils/nostrUtils.ts

@ -46,11 +46,11 @@ function escapeHtml(text: string): string { @@ -46,11 +46,11 @@ function escapeHtml(text: string): string {
/**
* 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
const cleanId = identifier.replace(/^nostr:/, '');
if (npubCache.has(cleanId)) {
if (!force && npubCache.has(cleanId)) {
return npubCache.get(cleanId)!;
}
@ -111,7 +111,8 @@ export function createProfileLink(identifier: string, displayText: string | unde @@ -111,7 +111,8 @@ export function createProfileLink(identifier: string, displayText: string | unde
const defaultText = `${cleanId.slice(0, 8)}...${cleanId.slice(-4)}`;
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 @@ -167,9 +168,9 @@ export async function createProfileLinkWithVerification(identifier: string, disp
const type = nip05.endsWith('edu') ? 'edu' : 'standard';
switch (type) {
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':
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 @@ @@ -1,6 +1,7 @@
<script lang="ts">
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { Heading, Img, P, A } from "flowbite-svelte";
import { goto } from '$app/navigation';
// Get the git tag version from environment variables
const appVersion = import.meta.env.APP_VERSION || "development";
@ -34,9 +35,7 @@ @@ -34,9 +35,7 @@
</P>
<P class="mb-3">
Please submit support issues on the <A href="/contact"
>Alexandria contact page</A
> and follow us on <A
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
href="https://github.com/ShadowySupercode/gitcitadel"
target="_blank">GitHub</A
> and <A href="https://geyser.fund/project/gitcitadel" target="_blank"

9
src/routes/events/+page.svelte

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

21
src/routes/start/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import { Heading, Img, P, A } from "flowbite-svelte";
import { goto } from '$app/navigation';
// Get the git tag version from environment variables
const appVersion = import.meta.env.APP_VERSION || "development";
@ -15,7 +16,7 @@ @@ -15,7 +16,7 @@
<Heading tag="h2" class="h-leather mt-4 mb-2">Overview</Heading>
<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
hosted on the <A href="https://thecitadel.nostr1.com/" target="_blank"
>thecitadel document relay</A
@ -87,9 +88,9 @@ @@ -87,9 +88,9 @@
</P>
<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"
>Jane Eyre</A
>Jane Eyre</a
>
</P>
@ -123,9 +124,9 @@ @@ -123,9 +124,9 @@
</P>
<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"
>Less Partnering, Less Children, or Both?</A
>Less Partnering, Less Children, or Both?</a
>
</P>
@ -141,11 +142,11 @@ @@ -141,11 +142,11 @@
<Heading tag="h3" class="h-leather mb-3">For documentation</Heading>
<P class="mb-3">
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
>, as well as to store copies of our most interesting <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
>, as well as to store copies of our most interesting <a
href="/publication?d=gitcitadel-project-documentation-by-stella-v-1"
>technical specifications</A
>technical specifications</a
>.
</P>
@ -163,7 +164,7 @@ @@ -163,7 +164,7 @@
<P class="mb-3">
Alexandria now supports wiki pages (kind 30818), allowing for
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,
evolving content.
</P>

Loading…
Cancel
Save