Browse Source

Successful implementation of event search and userBadges.

master
Silberengel 10 months ago
parent
commit
8c88dc43c7
  1. 1
      .gitignore
  2. 2
      README.md
  3. 5713
      package-lock.json
  4. 7
      src/lib/components/Login.svelte
  5. 4
      src/lib/components/PublicationHeader.svelte
  6. 4
      src/lib/components/blog/BlogHeader.svelte
  7. 4
      src/lib/components/util/ArticleNav.svelte
  8. 8
      src/lib/components/util/CardActions.svelte
  9. 8
      src/lib/components/util/Details.svelte
  10. 191
      src/lib/components/util/InlineProfile.svelte
  11. 4
      src/lib/consts.ts
  12. 12
      src/lib/snippets/UserSnippets.svelte
  13. 22
      src/lib/utils/nostrUtils.ts
  14. 1
      src/routes/[...catchall]/+page.svelte
  15. 6
      src/routes/about/+page.svelte
  16. 86
      src/routes/events/+page.svelte
  17. 2
      test_data/AsciidocFiles/21lessons.adoc
  18. 2
      test_data/AsciidocFiles/Rauhnaechte.adoc
  19. 4
      tests/integration/markupIntegration.test.ts
  20. 8
      tests/integration/markupTestfile.md

1
.gitignore vendored

@ -8,7 +8,6 @@ node_modules @@ -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

2
README.md

@ -120,4 +120,4 @@ npx playwright test @@ -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).
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).

5713
package-lock.json generated

File diff suppressed because it is too large Load Diff

7
src/lib/components/Login.svelte

@ -1,13 +1,10 @@ @@ -1,13 +1,10 @@
<script lang='ts'>
import { type NDKUserProfile } from '@nostr-dev-kit/ndk';
import { activePubkey, loginWithExtension, logout, ndkInstance, ndkSignedIn, persistLogin } from '$lib/ndk';
import { Avatar, Button, Popover, Tooltip } from 'flowbite-svelte';
import { activePubkey, loginWithExtension, ndkInstance, ndkSignedIn, persistLogin } from '$lib/ndk';
import { Avatar, Button, Popover } from 'flowbite-svelte';
import Profile from "$components/util/Profile.svelte";
let profile = $state<NDKUserProfile | null>(null);
let pfp = $derived(profile?.image);
let username = $derived(profile?.name);
let tag = $derived(profile?.name);
let npub = $state<string | undefined >(undefined);
let signInFailed = $state<boolean>(false);

4
src/lib/components/PublicationHeader.svelte

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
import { standardRelays } from '../consts';
import { Card, Img } from "flowbite-svelte";
import CardActions from "$components/util/CardActions.svelte";
import InlineProfile from "$components/util/InlineProfile.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
const { event } = $props<{ event: NDKEvent }>();
@ -46,7 +46,7 @@ @@ -46,7 +46,7 @@
<h3 class='text-base font-normal'>
by
{#if authorPubkey != null}
<InlineProfile pubkey={authorPubkey} name={author} />
{@render userBadge(authorPubkey, author)}
{:else}
{author}
{/if}

4
src/lib/components/blog/BlogHeader.svelte

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
import type { NDKEvent } from '@nostr-dev-kit/ndk';
import { scale } from 'svelte/transition';
import { Card, Img } from "flowbite-svelte";
import InlineProfile from "$components/util/InlineProfile.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import Interactions from "$components/util/Interactions.svelte";
import { quintOut } from "svelte/easing";
import CardActions from "$components/util/CardActions.svelte";
@ -38,7 +38,7 @@ @@ -38,7 +38,7 @@
<div class='space-y-4'>
<div class="flex flex-row justify-between my-2">
<div class="flex flex-col">
<InlineProfile pubkey={authorPubkey} name={author} />
{@render userBadge(authorPubkey, author)}
<span class='text-gray-500'>{publishedAt()}</span>
</div>
<CardActions event={event} />

4
src/lib/components/util/ArticleNav.svelte

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
import { BookOutline, CaretLeftOutline, CloseOutline, GlobeOutline } from "flowbite-svelte-icons";
import { Button } from "flowbite-svelte";
import { publicationColumnVisibility } from "$lib/stores";
import InlineProfile from "$components/util/InlineProfile.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { onDestroy, onMount } from "svelte";
@ -131,7 +131,7 @@ @@ -131,7 +131,7 @@
{/if}
</div>
<div class="flex flex-grow text justify-center items-center">
<p class="max-w-[60vw] line-ellipsis"><b class="text-nowrap">{title}</b> <span class="whitespace-nowrap">by <InlineProfile pubkey={pubkey} name={author} /></span></p>
<p class="max-w-[60vw] line-ellipsis"><b class="text-nowrap">{title}</b> <span class="whitespace-nowrap">by {@render userBadge(pubkey, author)}</span></p>
</div>
<div class="flex justify-end items-center space-x-2 md:min-w-52 min-w-8">
{#if $publicationColumnVisibility.inner}

8
src/lib/components/util/CardActions.svelte

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
import { Button, Modal, Popover } from "flowbite-svelte";
import { standardRelays, FeedType } from "$lib/consts";
import { neventEncode, naddrEncode } from "$lib/utils";
import InlineProfile from "$components/util/InlineProfile.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { feedType } from "$lib/stores";
import { inboxRelays, ndkSignedIn } from "$lib/ndk";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
@ -83,7 +83,7 @@ @@ -83,7 +83,7 @@
function getIdentifier(type: 'nevent' | 'naddr'): string {
const encodeFn = type === 'nevent' ? neventEncode : naddrEncode;
const identifier = encodeFn(event, activeRelays);
console.debug(`[CardActions] ${type} identifier for event ${event.id}:`, identifier);
console.debug("[CardActions] ${type} identifier for event ${event.id}:", identifier);
return identifier;
}
@ -165,7 +165,7 @@ @@ -165,7 +165,7 @@
<h1 class="text-3xl font-bold mt-5">{title || 'Untitled'}</h1>
<h2 class="text-base font-bold">by
{#if originalAuthor}
<InlineProfile pubkey={originalAuthor} />
{@render userBadge(originalAuthor, author)}
{:else}
{author || 'Unknown'}
{/if}
@ -183,7 +183,7 @@ @@ -183,7 +183,7 @@
{/if}
<div class="flex flex-row">
<h4 class='text-base font-normal mt-2'>Index author: <InlineProfile pubkey={event.pubkey} /></h4>
<h4 class='text-base font-normal mt-2'>Index author: {@render userBadge(event.pubkey, author)}</h4>
</div>
<div class="flex flex-col pb-4 space-y-1">

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<script lang="ts">
import InlineProfile from "$components/util/InlineProfile.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import CardActions from "$components/util/CardActions.svelte";
import Interactions from "$components/util/Interactions.svelte";
import { P } from "flowbite-svelte";
@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
<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'><InlineProfile pubkey={event.pubkey} /></P>
<P class='text-base font-normal'>{@render userBadge(event.pubkey, author)}</P>
<CardActions event={event}></CardActions>
</div>
{/if}
@ -46,7 +46,7 @@ @@ -46,7 +46,7 @@
<h2 class="text-base font-bold">
by
{#if originalAuthor !== null}
<InlineProfile pubkey={originalAuthor} name={author} />
{@render userBadge(originalAuthor, author)}
{:else}
{author}
{/if}
@ -80,7 +80,7 @@ @@ -80,7 +80,7 @@
{:else}
<span>Author:</span>
{/if}
<InlineProfile pubkey={event.pubkey} />
{@render userBadge(event.pubkey, author)}
</h4>
</div>

191
src/lib/components/util/InlineProfile.svelte

@ -1,60 +1,183 @@ @@ -1,60 +1,183 @@
<script lang='ts'>
import { Avatar } from 'flowbite-svelte';
import { type NDKUserProfile } from "@nostr-dev-kit/ndk";
import NDK, { type NDKUserProfile } from "@nostr-dev-kit/ndk";
import { ndkInstance } from '$lib/ndk';
import { userBadge } from '$lib/snippets/UserSnippets.svelte';
let { pubkey, name = null } = $props();
const externalProfileDestination = './events?id='
// Component configuration types
type AvatarSize = 'sm' | 'md' | 'lg';
let loading = $state(true);
let anon = $state(false);
let npub = $state('');
// Component props interface
interface $$Props {
pubkey: string; // Required: The Nostr public key of the user
name?: string | null; // Optional: Display name override
showAvatar?: boolean; // Optional: Whether to show the avatar (default: true)
avatarSize?: AvatarSize; // Optional: Size of the avatar (default: 'md')
}
// Destructure and set default props
let {
pubkey,
name = null,
showAvatar = true,
avatarSize = 'md' as AvatarSize
} = $props();
console.log('[InlineProfile] Initialized with props:', {
pubkey,
name,
showAvatar,
avatarSize
});
// Constants
const EXTERNAL_PROFILE_DESTINATION = './events?id=';
// Component state type definition
type ProfileState = {
loading: boolean; // Whether we're currently loading the profile
error: string | null; // Any error that occurred during loading
profile: NDKUserProfile | null; // The user's profile data
npub: string; // The user's npub (bech32 encoded pubkey)
};
// Initialize component state
let state = $state<ProfileState>({
loading: true,
error: null,
profile: null,
npub: ''
});
// Derived values from state
const pfp = $derived(state.profile?.image);
const username = $derived(state.profile?.name);
const isAnonymous = $derived(!state.profile?.name && !name);
// Log derived values reactively when they change
$effect(() => {
console.log('[InlineProfile] Derived values updated:', {
pfp,
username,
isAnonymous,
hasProfile: !!state.profile,
hasNpub: !!state.npub,
profileState: {
loading: state.loading,
error: state.error,
hasProfile: !!state.profile,
npub: state.npub
}
});
});
let profile = $state<NDKUserProfile | null>(null);
let pfp = $derived(profile?.image);
let username = $derived(profile?.name);
// Avatar size classes mapping
const avatarClasses: Record<AvatarSize, string> = {
sm: 'h-5 w-5',
md: 'h-7 w-7',
lg: 'h-9 w-9'
};
/**
* Fetches user data from NDK
* @param pubkey - The Nostr public key to fetch data for
*/
async function fetchUserData(pubkey: string) {
let user;
user = $ndkInstance
.getUser({ pubkey: pubkey ?? undefined });
console.log('[InlineProfile] fetchUserData called with pubkey:', pubkey);
npub = user.npub;
if (!pubkey) {
console.warn('[InlineProfile] No pubkey provided to fetchUserData');
state.error = 'No pubkey provided';
state.loading = false;
return;
}
try {
console.log('[InlineProfile] Getting NDK instance');
const ndk = $ndkInstance as NDK;
console.log('[InlineProfile] Creating NDK user object');
const user = ndk.getUser({ pubkey });
console.log('[InlineProfile] Getting npub');
state.npub = user.npub;
console.log('[InlineProfile] Got npub:', state.npub);
user.fetchProfile()
.then(userProfile => {
profile = userProfile;
if (!profile?.name) anon = true;
loading = false;
console.log('[InlineProfile] Fetching user profile');
const userProfile = await user.fetchProfile();
console.log('[InlineProfile] Got user profile:', {
name: userProfile?.name,
displayName: userProfile?.displayName,
nip05: userProfile?.nip05,
hasImage: !!userProfile?.image
});
state.profile = userProfile;
state.loading = false;
} catch (error) {
console.error('[InlineProfile] Error fetching user data:', error);
state.error = error instanceof Error ? error.message : 'Failed to fetch profile';
state.loading = false;
}
}
// Fetch data when component mounts
/**
* Shortens an npub string for display
* @param long - The npub string to shorten
* @returns Shortened npub string
*/
function shortenNpub(long: string | undefined): string {
if (!long) return '';
const shortened = `${long.slice(0, 8)}…${long.slice(-4)}`;
console.log('[InlineProfile] Shortened npub:', { original: long, shortened });
return shortened;
}
// Effect to fetch user data when pubkey changes
$effect(() => {
console.log('[InlineProfile] Effect triggered, pubkey:', pubkey);
if (pubkey) {
fetchUserData(pubkey);
} else {
console.warn('[InlineProfile] No pubkey available for effect');
}
});
function shortenNpub(long: string|undefined) {
if (!long) return '';
return long.slice(0, 8) + '…' + long.slice(-4);
}
</script>
{#if loading}
<!-- Component Template -->
{#if state.loading}
<!-- Loading state -->
<span class="animate-pulse" title="Loading profile...">
{name ?? '…'}
{:else if anon }
{@render userBadge(npub, username)}
{:else if npub }
<a href={externalProfileDestination + npub} title={name ?? username}>
<Avatar rounded
class='h-7 w-7 mx-1 cursor-pointer inline bg-transparent'
</span>
{:else if state.error}
<!-- Error state -->
<span class="text-red-500" title={state.error}>
{name ?? shortenNpub(pubkey)}
</span>
{:else if isAnonymous}
<!-- Anonymous user state -->
{@render userBadge(pubkey, name)}
{:else if state.npub}
<!-- Authenticated user with profile -->
<a
href={EXTERNAL_PROFILE_DESTINATION + state.npub}
title={name ?? username}
class="inline-flex items-center hover:opacity-80 transition-opacity"
>
{#if showAvatar}
<Avatar
rounded
class={`${avatarClasses[avatarSize]} mx-1 cursor-pointer inline bg-transparent`}
src={pfp}
alt={username} />
{@render userBadge(npub, username)}
alt={username ?? 'User avatar'}
/>
{/if}
{@render userBadge(pubkey, name)}
</a>
{:else}
{name ?? pubkey}
<!-- Fallback state -->
<span title="No profile data available">
{name ?? shortenNpub(pubkey)}
</span>
{/if}

4
src/lib/consts.ts

@ -9,7 +9,9 @@ export const fallbackRelays = [ @@ -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 {

12
src/lib/snippets/UserSnippets.svelte

@ -1,15 +1,19 @@ @@ -1,15 +1,19 @@
<script module lang='ts'>
import { createProfileLink, createProfileLinkWithVerification } from '$lib/utils/nostrUtils';
import { createProfileLink, createProfileLinkWithVerification, toNpub } from '$lib/utils/nostrUtils';
export { userBadge };
</script>
{#snippet userBadge(identifier: string, displayText: string | undefined)}
{#await createProfileLinkWithVerification(identifier, displayText)}
{@html createProfileLink(identifier, displayText)}
{#if toNpub(identifier)}
{#await createProfileLinkWithVerification(toNpub(identifier) as string, displayText)}
{@html createProfileLink(toNpub(identifier) as string, displayText)}
{:then html}
{@html html}
{:catch}
{@html createProfileLink(identifier, displayText)}
{@html createProfileLink(toNpub(identifier) as string, displayText)}
{/await}
{:else}
{displayText ?? ''}
{/if}
{/snippet}

22
src/lib/utils/nostrUtils.ts

@ -105,7 +105,7 @@ export function createProfileLink(identifier: string, displayText: string | unde @@ -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 `<a href=""./events?id=${escapedId}"" class="npub-badge" target="_blank">@${escapedText}</a>`;
return `<a href="./events?id=${escapedId}" class="npub-badge" target="_blank">@${escapedText}</a>`;
}
/**
@ -149,9 +149,9 @@ export async function createProfileLinkWithVerification(identifier: string, disp @@ -149,9 +149,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="https://njump.me/${escapedId}" target="_blank">@${displayIdentifier}</a>${graduationCapSvg}</span>`;
return `<span class="npub-badge"><a href="./events?id=${escapedId}" target="_blank">@${displayIdentifier}</a>${graduationCapSvg}</span>`;
case 'standard':
return `<span class="npub-badge"><a href="https://njump.me/${escapedId}" target="_blank">@${displayIdentifier}</a>${badgeCheckSvg}</span>`;
return `<span class="npub-badge"><a href="./events?id=${escapedId}" target="_blank">@${displayIdentifier}</a>${badgeCheckSvg}</span>`;
}
}
/**
@ -358,3 +358,19 @@ export async function fetchEventWithFallback( @@ -358,3 +358,19 @@ export async function fetchEventWithFallback(
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;
}
}

1
src/routes/[...catchall]/+page.svelte

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
<script lang="ts">
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { Button, P } from 'flowbite-svelte';
</script>

6
src/routes/about/+page.svelte

@ -20,15 +20,15 @@ @@ -20,15 +20,15 @@
>
{/if}
</div>
<Img src="/screenshots/old_books.jpg" alt="Alexandria icon" />
<Img src="./screenshots/old_books.jpg" alt="Alexandria icon" />
<P class="mb-3">
Alexandria is a reader and writer for <A
href="/publication?d=gitcitadel-project-documentation-curated-publications-specification-7-by-stella-v-1"
href="./publication?d=gitcitadel-project-documentation-curated-publications-specification-7-by-stella-v-1"
>curated publications</A
> (in Asciidoc), wiki pages (Asciidoc), and will eventually also support long-form
articles (markup). It is produced by the <A
href="/publication?d=gitcitadel-project-documentation-gitcitadel-project-1-by-stella-v-1"
href="./publication?d=gitcitadel-project-documentation-by-stella-v-1"
>GitCitadel project team</A
>.
</P>

86
src/routes/events/+page.svelte

@ -11,7 +11,8 @@ @@ -11,7 +11,8 @@
import { getMimeTags, getEventType } from "$lib/utils/mime";
import { page } from "$app/stores";
import { nip19 } from 'nostr-tools';
import InlineProfile from '$lib/components/util/InlineProfile.svelte';
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { toNpub } from "$lib/utils/nostrUtils";
let searchQuery = $state("");
let event = $state<NDKEvent | null>(null);
@ -39,50 +40,72 @@ @@ -39,50 +40,72 @@
error = null;
event = null;
console.log('[Events] searchEvent called with query:', searchQuery);
// Clean the query
let cleanedQuery = searchQuery.trim().replace(/^nostr:/, '');
let filterOrId: any = cleanedQuery;
console.log('[Events] Cleaned query:', cleanedQuery);
// If it's a 64-char hex, try as event id first, then as pubkey (profile)
if (/^[a-f0-9]{64}$/i.test(cleanedQuery)) {
// Try as event id
filterOrId = cleanedQuery;
event = await fetchEventWithFallback($ndkInstance, filterOrId, 10000);
if (!event) {
// Try as pubkey (profile event)
filterOrId = { kinds: [0], authors: [cleanedQuery] };
event = await fetchEventWithFallback($ndkInstance, filterOrId, 10000);
}
// ... handle not found, etc.
return;
} else if (/^(nevent|note|naddr|npub|nprofile)[a-z0-9]+$/i.test(cleanedQuery)) {
try {
let filterOrId: any = searchQuery.trim();
// Try to decode bech32 (nevent, naddr, note, npub, nprofile)
if (/^(nevent|note|naddr|npub|nprofile)[a-z0-9]+$/i.test(searchQuery.trim())) {
try {
const decoded = nip19.decode(searchQuery.trim());
const decoded = nip19.decode(cleanedQuery);
console.log('[Events] Decoded NIP-19:', decoded);
if (decoded.type === 'nevent') {
switch (decoded.type) {
case 'nevent':
filterOrId = decoded.data.id;
} else if (decoded.type === 'note') {
break;
case 'note':
filterOrId = decoded.data;
} else if (decoded.type === 'naddr') {
break;
case 'naddr':
filterOrId = {
kinds: [decoded.data.kind],
authors: [decoded.data.pubkey],
'#d': [decoded.data.identifier],
};
} else if (decoded.type === 'nprofile') {
// Fetch kind 0 (profile) event for pubkey
break;
case 'nprofile':
filterOrId = {
kinds: [0],
authors: [decoded.data.pubkey],
};
} else if (decoded.type === 'npub') {
// Fetch kind 0 (profile) event for pubkey
break;
case 'npub':
filterOrId = {
kinds: [0],
authors: [decoded.data],
};
break;
default:
filterOrId = cleanedQuery;
}
console.log('[Events] Using filterOrId:', filterOrId);
} catch (e) {
console.error('[Events] Invalid Nostr identifier:', searchQuery, e);
console.error('[Events] Invalid Nostr identifier:', cleanedQuery, e);
error = 'Invalid Nostr identifier.';
loading = false;
return;
}
}
// Optionally: handle NIP-05 here if you want
// else if (cleanedQuery.includes('@')) { ... }
try {
// Use our new utility function to fetch the event
console.log('[Events] Fetching event with filterOrId:', filterOrId);
event = await fetchEventWithFallback($ndkInstance, filterOrId);
console.log('Searching for event:', filterOrId);
event = await fetchEventWithFallback($ndkInstance, filterOrId, 10000);
if (!event) {
console.warn('[Events] Event not found for filterOrId:', filterOrId);
@ -103,19 +126,19 @@ @@ -103,19 +126,19 @@
if (eventType === 'addressable') {
const dTag = event.getMatchingTags('d')[0]?.[1];
if (dTag) {
return `/publication?id=${event.id}`;
return './publication?id=${event.id}';
}
}
if (event.kind === 30818) {
return `/wiki?id=${event.id}`;
return './wiki?id=${event.id}';
}
const nevent = neventEncode(event, standardRelays);
return `https://njump.me/${nevent}`;
return './events?id=${nevent}';
}
function getEventTypeDisplay(event: NDKEvent): string {
const [mTag, MTag] = getMimeTags(event.kind || 0);
return MTag[1].split('/')[1] || `Event Kind ${event.kind}`;
return MTag[1].split('/')[1] || 'Event Kind ${event.kind}';
}
function getEventTitle(event: NDKEvent): string {
@ -158,7 +181,7 @@ @@ -158,7 +181,7 @@
tags: [["d", dtag]],
};
const naddr = naddrEncode(fakeEvent as any, standardRelays);
return `<a href='./events?id=${naddr}' class='text-primary-600 underline' target='_blank'>${match}</a>`;
return "<a href='./events?id=${naddr}' class='text-primary-600 underline' target='_blank'>${match}</a>";
} catch {
return match;
}
@ -168,7 +191,7 @@ @@ -168,7 +191,7 @@
json = json.replace(EVENT_ID_REGEX, (match) => {
try {
const nevent = neventEncode({ id: match, kind: 1 } as NDKEvent, standardRelays);
return `<a href='./events?id=${nevent}' class='text-primary-600 underline' target='_blank'>${match}</a>`;
return "<a href='./events?id=${nevent}' class='text-primary-600 underline' target='_blank'>${match}</a>";
} catch {
return match;
}
@ -184,12 +207,12 @@ @@ -184,12 +207,12 @@
if (tag[0] === 'a' && tag.length > 1) {
const [kind, pubkey, d] = tag[1].split(':');
// Use type assertion as any to satisfy NDKEvent signature for naddrEncode
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 "<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>";
} else if (tag[0] === 'e' && tag.length > 1) {
// Use type assertion as any to satisfy NDKEvent signature for neventEncode
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 "<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>";
} 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>";
}
}
@ -238,7 +261,7 @@ @@ -238,7 +261,7 @@
</div>
<P class="mb-3">
Use this page to view any event (npub, nprofile, nevent, naddr, or hexID).
Use this page to view any event (npub, nprofile, nevent, naddr, note, pubkey, or eventID).
</P>
<div class="flex gap-2">
@ -264,7 +287,7 @@ @@ -264,7 +287,7 @@
href={"https://njump.me/" + encodeURIComponent(searchQuery.trim())}
target="_blank"
rel="noopener"
>njump</a>.
>Njump</a>.
</div>
{/if}
</div>
@ -285,8 +308,11 @@ @@ -285,8 +308,11 @@
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{profile.name}</h2>
{/if}
<div class="flex items-center space-x-2">
<span class="text-gray-600 dark:text-gray-400">Author:</span>
<InlineProfile pubkey={event.pubkey} />
{#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}
</div>
<div class="flex items-center space-x-2">
<span class="text-gray-600 dark:text-gray-400">Kind:</span>

2
test_data/AsciidocFiles/21lessons.adoc

@ -1372,7 +1372,7 @@ Thanks to the countless authors and content producers who influenced my thinking @@ -1372,7 +1372,7 @@ Thanks to the countless authors and content producers who influenced my thinking
Last but not least, thanks to all the bitcoin maximalists, shitcoin minimalists, shills, bots, and shitposters which reside in the beautiful garden that is Bitcoin twitter. And finally, thank _you_ for reading this. I hope you enjoyed it as much as I did enjoy writing it.
Feel free to reach out to me https://twitter.com/dergigi[on X] or https://njump.me/npub1dergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsh9xzpc[on Nostr] nostr:npub1dergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsh9xzpc. My DMs are open.
Feel free to reach out to me https://twitter.com/dergigi[on X] or https://next-alexandria.gitcitadel.eu/events?id=npub1dergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsh9xzpc[on Nostr] nostr:npub1dergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsh9xzpc. My DMs are open.
== Thank You

2
test_data/AsciidocFiles/Rauhnaechte.adoc

@ -157,7 +157,7 @@ Namesake: *Silvester* @@ -157,7 +157,7 @@ Namesake: *Silvester*
The seventh Rauhnacht, associated with the month of July and the namesake _Silvester_, carries the theme of _Preparation for What’s to Come._
It represents the gateway — the transition from a past phase into a new one. Since the introduction of the Gregorian calendar, this transition has been celebrated by many on December 31st, a day dedicated to the Roman bishop Silvester. His death commemorates the end of Christian persecution and the establishment of Christianity as a state religion. (For spiritual insights on Christ consciousness, I recommend https://njump.me/naddr1qvzqqqr4gupzp35mw8w9vn7ux59vmhle98e96usz4s28pjr53psgh4ke3epxhfmrqyvhwumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdshszythwden5te0dehhxarj9emkjmn99uqp2d3ctaynzefcvex5vamxvf04sunzfu6nqdgtaz38t[this article I recently shared]).
It represents the gateway — the transition from a past phase into a new one. Since the introduction of the Gregorian calendar, this transition has been celebrated by many on December 31st, a day dedicated to the Roman bishop Silvester. His death commemorates the end of Christian persecution and the establishment of Christianity as a state religion. (For spiritual insights on Christ consciousness, I recommend https://next-alexandria.gitcitadel.eu/events?id=naddr1qvzqqqr4gupzp35mw8w9vn7ux59vmhle98e96usz4s28pjr53psgh4ke3epxhfmrqyvhwumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdshszythwden5te0dehhxarj9emkjmn99uqp2d3ctaynzefcvex5vamxvf04sunzfu6nqdgtaz38t[this article I recently shared]).
Every transition holds the opportunity to change, reshape, and perceive life with fresh eyes. Use this day to prepare for the new year:

4
tests/integration/markupIntegration.test.ts

@ -33,7 +33,7 @@ describe('Markup Integration Test', () => { @@ -33,7 +33,7 @@ describe('Markup Integration Test', () => {
expect(output).toMatch(/<a[^>]+href="https:\/\/github.com\/nostrability\/nostrability\/issues\/146"/);
// Hashtags
expect(output).toContain('text-primary-600');
// Nostr identifiers (should be njump.me links)
// Nostr identifiers (should be Alexandria links)
expect(output).toContain('./events?id=npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z');
// Wikilinks
expect(output).toContain('wikilink');
@ -76,7 +76,7 @@ describe('Markup Integration Test', () => { @@ -76,7 +76,7 @@ describe('Markup Integration Test', () => {
expect(output).toMatch(/<a[^>]+href="https:\/\/github.com\/nostrability\/nostrability\/issues\/146"/);
// Hashtags
expect(output).toContain('text-primary-600');
// Nostr identifiers (should be njump.me links)
// Nostr identifiers (should be Alexandria links)
expect(output).toContain('./events?id=npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z');
// Wikilinks
expect(output).toContain('wikilink');

8
tests/integration/markupTestfile.md

@ -111,16 +111,16 @@ https://upload.wikimedia.org/wikipedia/commons/f/f1/Heart_coraz%C3%B3n.svg @@ -111,16 +111,16 @@ https://upload.wikimedia.org/wikipedia/commons/f/f1/Heart_coraz%C3%B3n.svg
This is an implementation of [Nostr-flavored markup](https://github.com/nostrability/nostrability/issues/146) for #gitstuff issue notes.
You can even turn Alexandria URLs into embedded events, if they have hexids or bech32 addresses:
http://localhost:4173/publication?id=nevent1qqstjcyerjx4laxlxc70cwzuxf3u9kkzuhdhgtu8pwrzvh7k5d5zdngpzemhxue69uhhyetvv9ujumn0wd68ytnzv9hxgq3qm3xdppkd0njmrqe2ma8a6ys39zvgp5k8u22mev8xsnqp4nh80srq0ylvuw
https://next-alexandria.gitcitadel.eu/events?id=nevent1qqstjcyerjx4laxlxc70cwzuxf3u9kkzuhdhgtu8pwrzvh7k5d5zdngpzemhxue69uhhyetvv9ujumn0wd68ytnzv9hxgq3qm3xdppkd0njmrqe2ma8a6ys39zvgp5k8u22mev8xsnqp4nh80srq0ylvuw
But not if they have d-tags:
http://next-alexandria.gitcitadel.eu/publication?d=relay-test-thecitadel-by-unknown-v-1
https://next-alexandria.gitcitadel.eu/publication?d=relay-test-thecitadel-by-unknown-v-1
And within a markup tag: [markup link title](http://alexandria.gitcitadel.com/publication?id=84ad65f7a321404f55d97c2208dd3686c41724e6c347d3ee53cfe16f67cdfb7c).
And within a markup tag: [markup link title](https://next-alexandria.gitcitadel.com/publication?id=84ad65f7a321404f55d97c2208dd3686c41724e6c347d3ee53cfe16f67cdfb7c).
And to localhost: http://localhost:4173/publication?id=c36b54991e459221f444612d88ea94ef5bb4a1b93863ef89b1328996746f6d25
http://localhost:4173/profile?id=nprofile1qqs99d9qw67th0wr5xh05de4s9k0wjvnkxudkgptq8yg83vtulad30gxyk5sf
https://next-alexandria.gitcitadel.eu/events?id=nprofile1qqs99d9qw67th0wr5xh05de4s9k0wjvnkxudkgptq8yg83vtulad30gxyk5sf
You can even include code inline, like `<div class="leather min-h-full w-full flex flex-col items-center">` or

Loading…
Cancel
Save