Browse Source

Moving things around again

master
Nuša Pukšič 6 months ago committed by buttercat1791
parent
commit
42215020ca
  1. 4
      src/app.css
  2. 22
      src/app.html
  3. 112
      src/lib/a/cards/AProfilePreview.svelte
  4. 8
      src/lib/a/primitives/AAlert.svelte
  5. 6
      src/lib/a/reader/ATechBlock.svelte
  6. 3
      src/lib/a/reader/ATechToggle.svelte
  7. 2
      src/lib/components/CommentViewer.svelte
  8. 232
      src/lib/components/EventDetails.svelte
  9. 15
      src/lib/components/EventSearch.svelte
  10. 12
      src/lib/components/util/ArticleNav.svelte
  11. 10
      src/lib/components/util/CopyToClipboard.svelte
  12. 17
      src/lib/stores/techStore.ts
  13. 2
      src/routes/about/+page.svelte
  14. 2
      src/routes/contact/+page.svelte
  15. 92
      src/routes/events/+page.svelte
  16. 34
      src/routes/events/compose/+page.svelte
  17. 7
      src/routes/profile/+page.svelte
  18. 2
      src/routes/start/+page.svelte

4
src/app.css

@ -454,6 +454,10 @@ @@ -454,6 +454,10 @@
@apply fill-primary-600 dark:fill-primary-500;
}
}
[data-tech="off"] .tech-detail {
@apply !hidden;
}
}
@layer components {

22
src/app.html

@ -1,10 +1,30 @@ @@ -1,10 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-tech="off">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png?v=2" />
<meta name="viewport" content="width=device-width" />
<!-- Apply saved theme ASAP to avoid flash -->
<script>
try {
const t = localStorage.getItem('theme');
if (t) document.documentElement.dataset.theme = t;
} catch (_) {
/* no-op */
}
</script>
<!-- Apply saved tech toggle ASAP; default is off -->
<script>
try {
const v = localStorage.getItem('alexandria/showTech');
document.documentElement.dataset.tech = v === 'true' ? 'on' : 'off';
} catch (_) {
/* no-op */
}
</script>
<!-- MathJax for math rendering -->
<script>
window.MathJax = {

112
src/lib/a/cards/AProfilePreview.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import { Card, Heading, P, Button, Modal, Avatar } from 'flowbite-svelte';
import { Card, Heading, P, Button, Modal, Avatar, Dropdown, DropdownItem } from 'flowbite-svelte';
import { ChevronDownOutline } from 'flowbite-svelte-icons';
import AAlert from '$lib/a/primitives/AAlert.svelte';
import CopyToClipboard from '$lib/components/util/CopyToClipboard.svelte';
import { goto } from '$app/navigation';
@ -37,7 +38,7 @@ @@ -37,7 +38,7 @@
loading?: boolean;
error?: string | null;
isOwn?: boolean;
event?: NDKEvent;
event: NDKEvent;
communityStatusMap?: Record<string, boolean>;
}>();
@ -149,72 +150,46 @@ @@ -149,72 +150,46 @@
<Card size="xl" class="main-leather p-0 overflow-hidden rounded-lg border border-primary-200 dark:border-primary-700">
{#if props.profile?.banner}
{#if props.event}
<div class="w-full bg-primary-200 dark:bg-primary-800 relative">
<LazyImage src={props.profile.banner} alt="Profile banner" eventId={props.event.id} className="w-full h-60 object-cover" />
</div>
{:else}
<div class="w-full h-60 bg-primary-200 dark:bg-primary-800 relative">
<img src={props.profile.banner} alt="Banner" class="w-full h-full object-cover" loading="lazy" onerror={hideOnError} />
</div>
{/if}
{:else if props.event}
<div class="w-full bg-primary-200 dark:bg-primary-800 relative">
<LazyImage src={props.profile.banner} alt="Profile banner" eventId={props.event.id} className="w-full h-60 object-cover" />
</div>
{:else}
<div class="w-full h-60" style={`background-color: ${generateDarkPastelColor(props.event.id)};`}></div>
{/if}
<div class={`p-6 ${props.profile?.banner || props.event ? 'pt-6' : 'pt-6'} flex flex-col gap-4 relative`}>
<Avatar size="xl" border src={props.profile?.picture} alt="Avatar" class="absolute w-fit top-[-56px]" />
<div class={`p-6 flex flex-col gap-4 relative`}>
<Avatar size="xl" border src={props.profile?.picture ?? null} alt="Avatar" class="absolute w-fit top-[-56px]" />
<div class="flex flex-col gap-3 mt-14">
<Heading tag="h1" class="h-leather mb-2">{displayName()}</Heading>
{#if props.user?.npub}
<CopyToClipboard displayText={shortNpub()} copyText={props.user.npub} />
{/if}
<div class="min-w-0 mt-14">
{#if props.event}
<div class="flex items-center gap-2 min-w-0">
<div class="min-w-0 flex-1">
{@render userBadge(
toNpub(props.event.pubkey) as string,
props.profile?.displayName || props.profile?.display_name || props.profile?.name || props.event.pubkey,
ndk,
)}
</div>
{#if props.profile?.nip05}
<span class="px-2 py-0.5 !mb-0 rounded bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 text-xs">{props.profile.nip05}</span>
{/if}
{#if communityStatus === true}
<div class="flex-shrink-0 w-4 h-4 bg-yellow-100 dark:bg-yellow-900 rounded-full flex items-center justify-center" title="Has posted to the community">
<svg class="w-3 h-3 text-yellow-600 dark:text-yellow-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
</div>
{:else if communityStatus === false}
<div class="flex-shrink-0 w-4 h-4"></div>
{/if}
{#if isInUserLists === true}
<div class="flex-shrink-0 w-4 h-4 bg-red-100 dark:bg-red-900 rounded-full flex items-center justify-center" title="In your lists (follows, etc.)">
<svg class="w-3 h-3 text-red-600 dark:text-red-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
</div>
{:else if isInUserLists === false}
<div class="flex-shrink-0 w-4 h-4"></div>
{/if}
</div>
{:else}
<Heading tag="h1" class="h-leather mb-2">{displayName()}</Heading>
{/if}
<div class="flex flex-row flex-wrap items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mt-1">
{#if props.user?.npub}
<CopyToClipboard displayText={shortNpub()} copyText={props.user.npub} />
{/if}
{#if props.profile?.nip05}
<span class="px-2 py-0.5 !mb-0 rounded bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 text-xs">{props.profile.nip05}</span>
{/if}
{#if props.profile?.lud16}
<Button color="alternative" class="!mb-0 !py-0.5 !px-2 rounded" size="xs" onclick={() => (lnModalOpen = true)}> {props.profile.lud16}</Button>
{/if}
</div>
</div>
{#if props.profile?.about}
{#if props.event}
<div class="prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100 break-words overflow-wrap-anywhere min-w-0">
{@render basicMarkup(props.profile.about, ndk)}
</div>
{:else}
<P class="whitespace-pre-wrap break-words leading-relaxed">{props.profile.about}</P>
{/if}
<div class="prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100 break-words overflow-wrap-anywhere min-w-0">
{@render basicMarkup(props.profile.about, ndk)}
</div>
{/if}
<div class="flex flex-wrap gap-4 text-sm">
@ -223,35 +198,22 @@ @@ -223,35 +198,22 @@
{/if}
</div>
{#if props.event}
<div class="mt-4">
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">Identifiers:</h4>
<div class="flex flex-col gap-2 min-w-0">
{#each getIdentifiers(props.event, props.profile) as identifier}
<div class="flex items-center gap-2 min-w-0">
<span class="text-gray-600 dark:text-gray-400 flex-shrink-0">{identifier.label}:</span>
<div class="flex-1 min-w-0 flex items-center gap-2">
{#if identifier.link}
<button class="font-mono text-sm text-primary-700 dark:text-primary-300 hover:text-primary-900 dark:hover:text-primary-100 break-all cursor-pointer bg-transparent border-none p-0 text-left" onclick={() => navigateToIdentifier(identifier.link!)}>
{identifier.value}
</button>
{:else}
<span class="font-mono text-sm text-gray-900 dark:text-gray-100 break-all">{identifier.value}</span>
{/if}
<CopyToClipboard displayText="" copyText={identifier.value} />
</div>
</div>
{/each}
</div>
</div>
{/if}
{#if props.isOwn}
<div class="flex flex-row justify-end gap-4 text-sm">
<div class="flex flex-row flex-wrap justify-end gap-4 text-sm">
{#if props.profile?.lud16}
<Button color="alternative" size="xs" onclick={() => (lnModalOpen = true)}> {props.profile.lud16}</Button>
{/if}
<Button size="xs" color="alternative">Identifiers <ChevronDownOutline class="ms-2 h-6 w-6" /></Button>
<Dropdown simple>
{#each getIdentifiers(props.event, props.profile) as identifier}
<DropdownItem><CopyToClipboard displayText={identifier.label} copyText={identifier.value} /></DropdownItem>
{/each}
</Dropdown>
{#if props.isOwn}
<Button class="!mb-0" size="xs" onclick={() => goto('/profile/notifications')}>Notifications</Button>
<Button class="!mb-0" size="xs" onclick={() => goto('/profile/my-notes')}>My notes</Button>
</div>
{/if}
{/if}
</div>
{#if props.loading}
<AAlert color="primary">Loading profile…</AAlert>
@ -268,7 +230,7 @@ @@ -268,7 +230,7 @@
<div>
<div class="flex flex-col items-center">
{@render userBadge(
props.event ? (toNpub(props.event.pubkey) as string) : (props.user?.npub || ''),
props.user?.npub ?? toNpub(props.event.pubkey),
props.profile?.displayName || props.profile?.display_name || props.profile?.name || (props.event?.pubkey || ''),
ndk,
)}

8
src/lib/a/primitives/AAlert.svelte

@ -1,13 +1,19 @@ @@ -1,13 +1,19 @@
<script lang="ts">
import { Alert } from "flowbite-svelte";
let { color, dismissable, children } = $props<{
let { color, dismissable, children, title } = $props<{
color?: string;
dismissable?: boolean;
children?: any;
title?: any;
}>();
</script>
<Alert {color} {dismissable} class="alert-leather mb-4">
{#if title}
<div class="flex">
<span class="text-lg font-medium">{@render title()}</span>
</div>
{/if}
{@render children()}
</Alert>

6
src/lib/a/reader/ATechBlock.svelte

@ -1,18 +1,16 @@ @@ -1,18 +1,16 @@
<script lang="ts">
import { showTech } from '$lib/stores/techStore.ts';
let revealed = false;
let revealed = $state(false);
let { title = 'Technical details', className = '' , content} = $props();
let hidden = $derived(!$showTech && !revealed);
</script>
{#if hidden}
<div class="rounded-md border border-dashed border-muted/40 bg-surface/60 px-3 py-2 flex items-center gap-3 {className}">
<div class="rounded-md border border-dashed border-muted/40 bg-surface/60 px-3 py-2 my-6 flex items-center gap-3 {className}">
<span class="text-xs opacity-70">{title} hidden</span>
<button class="ml-auto text-sm underline hover:no-underline" onclick={() => revealed = true}>Reveal this block</button>
</div>
{:else}
<div class="rounded-md border border-muted/20 bg-surface p-3 {className}">
{@render content()}
</div>
{/if}

3
src/lib/a/reader/ATechToggle.svelte

@ -2,10 +2,9 @@ @@ -2,10 +2,9 @@
import { showTech } from '$lib/stores/techStore.ts';
import { Toggle, P } from "flowbite-svelte";
let label = 'Show technical details';
$: checked = $showTech;
</script>
<div class="inline-flex items-center gap-2 select-none my-3">
<Toggle {checked} ontoggle={() => $showTech = checked} aria-label={label} />
<Toggle bind:checked={$showTech} aria-label={label} />
<P class="text-sm">{label}</P>
</div>

2
src/lib/components/CommentViewer.svelte

@ -219,7 +219,7 @@ @@ -219,7 +219,7 @@
if (!isFetching) {
fetchComments();
}
}, 2000); // Wait 2 seconds before retry
}, 10000); // Wait 10 seconds before retry
}
});

232
src/lib/components/EventDetails.svelte

@ -22,6 +22,9 @@ @@ -22,6 +22,9 @@
import { getNdkContext } from "$lib/ndk";
import type { UserProfile } from "$lib/models/user_profile";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
import ATechBlock from "$lib/a/reader/ATechBlock.svelte";
import { Accordion, AccordionItem, Heading } from "flowbite-svelte";
import RelayActions from "$components/RelayActions.svelte";
const {
event,
@ -302,6 +305,10 @@ @@ -302,6 +305,10 @@
return ids;
}
function navigateToIdentifier(link: string) {
goto(link);
}
onMount(() => {
function handleInternalLinkClick(event: MouseEvent) {
const target = event.target as HTMLElement;
@ -323,38 +330,38 @@ @@ -323,38 +330,38 @@
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100 break-words">
{@render basicMarkup(getEventTitle(event), ndk)}
</h2>
{/if}
<div class="flex items-center space-x-2 min-w-0">
{#if toNpub(event.pubkey)}
<div class="flex items-center space-x-2 min-w-0">
{#if toNpub(event.pubkey)}
<span class="text-gray-600 dark:text-gray-400 min-w-0"
>Author: {@render userBadge(
toNpub(event.pubkey) || '',
profile?.display_name || undefined,
ndk,
)}</span
>Author: {@render userBadge(
toNpub(event.pubkey) || '',
profile?.display_name || undefined,
ndk,
)}</span
>
{:else}
{:else}
<span class="text-gray-600 dark:text-gray-400 min-w-0 break-words"
>Author: {profile?.display_name || event.pubkey}</span
>Author: {profile?.display_name || event.pubkey}</span
>
{/if}
</div>
{/if}
</div>
<div class="flex items-center space-x-2 min-w-0">
<span class="text-gray-700 dark:text-gray-300 flex-shrink-0">Kind:</span>
<span class="font-mono flex-shrink-0">{event.kind}</span>
<span class="text-gray-700 dark:text-gray-300 flex-shrink-0"
<div class="flex items-center space-x-2 min-w-0">
<span class="text-gray-700 dark:text-gray-300 flex-shrink-0">Kind:</span>
<span class="font-mono flex-shrink-0">{event.kind}</span>
<span class="text-gray-700 dark:text-gray-300 flex-shrink-0"
>({getEventTypeDisplay(event)})</span
>
</div>
>
</div>
<div class="flex flex-col space-y-1 min-w-0">
<span class="text-gray-700 dark:text-gray-300">Summary:</span>
<div class="prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100 break-words overflow-wrap-anywhere min-w-0">
{@render basicMarkup(getEventSummary(event), ndk)}
<div class="flex flex-col space-y-1 min-w-0">
<span class="text-gray-700 dark:text-gray-300">Summary:</span>
<div class="prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100 break-words overflow-wrap-anywhere min-w-0">
{@render basicMarkup(getEventSummary(event), ndk)}
</div>
</div>
</div>
{/if}
<!-- Containing Publications -->
<ContainingIndexes {event} />
@ -424,119 +431,80 @@ @@ -424,119 +431,80 @@
<AProfilePreview event={event} profile={profile} communityStatusMap={communityStatusMap} />
{/if}
<!-- Raw Event JSON -->
<details
class="relative w-full max-w-2xl md:max-w-full bg-primary-50 dark:bg-primary-900 rounded p-4 overflow-hidden"
>
<summary
class="cursor-pointer font-semibold text-primary-700 dark:text-primary-300 mb-2"
>
Show details
</summary>
<ATechBlock>
{#snippet content()}
<Heading tag="h3" class="h-leather my-6">
Technical details
</Heading>
<!-- Identifiers Section -->
<div class="mb-4 max-w-full overflow-hidden">
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">Identifiers:</h4>
<div class="flex flex-col gap-2 min-w-0">
{#each getIdentifiers(event, profile) as identifier}
<div class="flex items-center gap-2 min-w-0">
<span class="text-gray-600 dark:text-gray-400 flex-shrink-0">{identifier.label}:</span>
<div class="flex-1 min-w-0 flex items-center gap-2">
{#if identifier.link}
<a
href={identifier.link}
class="font-mono text-sm text-primary-700 dark:text-primary-300 hover:text-primary-900 dark:hover:text-primary-100 break-all cursor-pointer"
title={identifier.value}
>
{identifier.value.slice(0, 20)}...{identifier.value.slice(-8)}
</a>
{:else}
<span class="font-mono text-sm text-gray-900 dark:text-gray-100 break-all" title={identifier.value}>
{identifier.value.slice(0, 20)}...{identifier.value.slice(-8)}
</span>
{/if}
<Accordion flush class="w-full">
<AccordionItem open={false} >
{#snippet header()}Identifiers{/snippet}
{#if event}
<div class="flex flex-col gap-2">
{#each getIdentifiers(event, profile) as identifier}
<div class="grid grid-cols-[max-content_minmax(0,1fr)_max-content] items-start gap-2 min-w-0">
<span class="min-w-24 text-gray-600 dark:text-gray-400">{identifier.label}:</span>
<div class="min-w-0">
{#if identifier.link}
<button class="font-mono text-sm text-primary-700 dark:text-primary-300 hover:text-primary-900 dark:hover:text-primary-100 break-all cursor-pointer bg-transparent border-none p-0 text-left"
onclick={() => navigateToIdentifier(identifier.link)}>
{identifier.value}
</button>
{:else}
<span class="font-mono text-sm text-gray-900 dark:text-gray-100 break-all">{identifier.value}</span>
{/if}
</div>
<div class="justify-self-end">
<CopyToClipboard displayText="" copyText={identifier.value} />
</div>
</div>
{/each}
</div>
{/if}
</AccordionItem>
<!-- Event Tags Section -->
{#if event.tags && event.tags.length}
<AccordionItem open={false}>
{#snippet header()}
Tags
{/snippet}
<div class="flex flex-wrap gap-2 break-words min-w-0">
{#each event.tags as tag}
{@const tagInfo = getTagButtonInfo(tag)}
{#if tagInfo.text && tagInfo.gotoValue}
<button
onclick={() => handleTagGoto(tagInfo.gotoValue || "")}
class="text-primary-700 dark:text-primary-300 cursor-pointer bg-transparent border-none p-0 text-left hover:text-primary-900 dark:hover:text-primary-100 break-all max-w-full"
>
{tagInfo.text}
</button>
{/if}
{/each}
</div>
</AccordionItem>
{/if}
<AccordionItem open={false} contentClass="relative">
{#snippet header()}Event JSON{/snippet}
<div class="absolute top-5 right-0 z-10">
<CopyToClipboard
displayText=""
copyText={identifier.value}
copyText={JSON.stringify(event.rawEvent(), null, 2)}
/>
</div>
</div>
{/each}
</div>
</div>
<!-- Event Tags Section -->
{#if event.tags && event.tags.length}
<div class="mb-4 max-w-full overflow-hidden">
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">Event Tags:</h4>
<div class="flex flex-wrap gap-2 break-words min-w-0">
{#each event.tags as tag}
{@const tagInfo = getTagButtonInfo(tag)}
{#if tagInfo.text && tagInfo.gotoValue}
<button
onclick={() => {
// Handle different types of gotoValue
if (
tagInfo.gotoValue!.startsWith("naddr") ||
tagInfo.gotoValue!.startsWith("nevent") ||
tagInfo.gotoValue!.startsWith("npub") ||
tagInfo.gotoValue!.startsWith("nprofile") ||
tagInfo.gotoValue!.startsWith("note")
) {
// For naddr, nevent, npub, nprofile, note - navigate directly
goto(`/events?id=${tagInfo.gotoValue!}`);
} else if (tagInfo.gotoValue!.startsWith("/")) {
// For relative URLs - navigate directly
goto(tagInfo.gotoValue!);
} else if (tagInfo.gotoValue!.startsWith("d:")) {
// For d-tag searches - navigate to d-tag search
const dTag = tagInfo.gotoValue!.substring(2);
goto(`/events?d=${encodeURIComponent(dTag)}`);
} else if (tagInfo.gotoValue!.startsWith("t:")) {
// For t-tag searches - navigate to t-tag search
const tTag = tagInfo.gotoValue!.substring(2);
goto(`/events?t=${encodeURIComponent(tTag)}`);
} else if (/^[0-9a-fA-F]{64}$/.test(tagInfo.gotoValue!)) {
// AI-NOTE: E-tag navigation may cause comment feed update issues
// When navigating to a new event via e-tag, the CommentViewer component
// may experience timing issues that result in:
// - Empty comment feeds even when comments exist
// - UI flashing between different thread views
// - Delayed comment loading
// This is likely due to race conditions between event prop changes
// and comment fetching in the CommentViewer component.
navigateToEvent(tagInfo.gotoValue!);
} else {
// For other cases, try direct navigation
goto(`/events?id=${tagInfo.gotoValue!}`);
}
}}
class="text-primary-700 dark:text-primary-300 cursor-pointer bg-transparent border-none p-0 text-left hover:text-primary-900 dark:hover:text-primary-100 break-all max-w-full"
>
{tagInfo.text}
</button>
{/if}
{/each}
</div>
</div>
{/if}
{#if event}
<pre class="p-4 wrap-break-word bg-highlight dark:bg-primary-900">
<code class="text-wrap">{JSON.stringify(event.rawEvent(), null, 2)}</code>
</pre>
{/if}
</AccordionItem>
<!-- Raw Event JSON Section -->
<div class="mb-4 max-w-full overflow-hidden">
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">Raw Event JSON:</h4>
<div class="relative min-w-0">
<div class="absolute top-0 right-0 z-10">
<CopyToClipboard
displayText=""
copyText={JSON.stringify(event.rawEvent(), null, 2)}
/>
</div>
<pre
class="overflow-x-auto text-xs bg-highlight dark:bg-primary-900 rounded p-4 mt-2 font-mono break-words whitespace-pre-wrap min-w-0"
style="line-height: 1.7; font-size: 1rem;">
{JSON.stringify(event.rawEvent(), null, 2)}
</pre>
</div>
</div>
</details>
<AccordionItem open={true}>
{#snippet header()}Relay Info{/snippet}
<RelayActions {event} />
</AccordionItem>
</Accordion>
{/snippet}
</ATechBlock>
</div>

15
src/lib/components/EventSearch.svelte

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
import { getMatchingTags, toNpub } from "$lib/utils/nostrUtils";
import { isEventId } from "$lib/utils/nostr_identifiers";
import type { SearchType } from "$lib/models/search_type";
import { AAlert } from "$lib/a";
// Props definition
let {
@ -903,21 +904,15 @@ @@ -903,21 +904,15 @@
<!-- Error Display -->
{#if showError}
<div
class="p-4 mb-4 text-sm text-red-700 bg-red-100 rounded-lg"
role="alert"
>
<AAlert color="red">
{localError || error}
</div>
</AAlert>
{/if}
<!-- Success Display -->
{#if showSuccess}
<div
class="p-4 mb-4 text-sm text-green-700 bg-green-100 rounded-lg"
role="alert"
>
<AAlert color="green">
{getResultMessage()}
</div>
</AAlert>
{/if}
</div>

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

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
GlobeOutline,
ChartOutline,
} from "flowbite-svelte-icons";
import { Button } from "flowbite-svelte";
import { Button, P } from "flowbite-svelte";
import { publicationColumnVisibility } from "$lib/stores";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
@ -152,7 +152,7 @@ @@ -152,7 +152,7 @@
</script>
<nav
class="Navbar navbar-leather flex fixed top-[100px] sm:top-[106px] w-full min-h-[70px] px-2 sm:px-4 py-2.5 z-10 transition-transform duration-300 {isVisible
class="Navbar navbar-leather flex fixed top-[100px] sm:top-[92px] w-full min-h-[70px] px-2 sm:px-4 py-2.5 z-10 transition-transform duration-300 {isVisible
? 'translate-y-0'
: '-translate-y-full'}"
>
@ -191,14 +191,14 @@ @@ -191,14 +191,14 @@
{/if}
</div>
<div class="flex flex-col flex-grow text justify-center items-center">
<p class="max-w-[60vw] line-ellipsis">
<P class="max-w-[60vw] line-ellipsis">
<b class="text-nowrap">{title}</b>
</p>
<p>
</P>
<P>
<span class="whitespace-nowrap"
>by {@render userBadge(pubkey, author, ndk)}</span
>
</p>
</P>
</div>
<div class="flex justify-end items-center space-x-2 md:min-w-52 min-w-8">
{#if $publicationColumnVisibility.inner}

10
src/lib/components/util/CopyToClipboard.svelte

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
<script lang="ts">
import { Button } from "flowbite-svelte";
import {
ClipboardCheckOutline,
ClipboardCleanOutline,
@ -42,15 +43,12 @@ @@ -42,15 +43,12 @@
}
</script>
<button class="btn-leather w-full text-left" onclick={copyToClipboard}>
<button class="btn-leather w-full text-left dark:text-primary-100 p-1 rounded-xs cursor-pointer" onclick={copyToClipboard}>
{#if copied}
<ClipboardCheckOutline class="inline mr-2" /> Copied!
{:else}
{#if icon === ClipboardCleanOutline}
<ClipboardCleanOutline class="inline mr-2" />
{:else if icon === ClipboardCheckOutline}
<ClipboardCheckOutline class="inline mr-2" />
{/if}
{@const TheIcon = icon}
<TheIcon class="inline { displayText !== '' ? 'mr-2' : ''}" />
{displayText}
{/if}
</button>

17
src/lib/stores/techStore.ts

@ -1,5 +1,16 @@ @@ -1,5 +1,16 @@
import { writable } from 'svelte/store';
const KEY='showTech';
const initial = typeof localStorage!=='undefined' ? localStorage.getItem(KEY)==='true' : true;
const KEY = 'alexandria/showTech';
// Default false unless explicitly set to 'true' in localStorage
const initial = typeof localStorage !== 'undefined'
? localStorage.getItem(KEY) === 'true'
: false;
export const showTech = writable<boolean>(initial);
showTech.subscribe(v=>{ if(typeof document!=='undefined'){ document.documentElement.dataset.tech = v ? 'on' : 'off'; localStorage.setItem(KEY,String(v)); } });
showTech.subscribe(v => {
if (typeof document !== 'undefined') {
document.documentElement.dataset.tech = v ? 'on' : 'off';
localStorage.setItem(KEY, String(v));
}
});

2
src/routes/about/+page.svelte

@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
const ndk = getNdkContext();
</script>
<div class="w-full max-w-3xl flex flex-col self-center mb-3">
<div class="w-full max-w-3xl flex flex-col self-center mb-3 px-2">
<div class="flex flex-col justify-between items-center mb-4">
<Heading tag="h1" class="h-leather mb-2"
>About the Library of Alexandria</Heading

2
src/routes/contact/+page.svelte

@ -261,7 +261,7 @@ @@ -261,7 +261,7 @@
});
</script>
<div class="w-full max-w-3xl flex flex-col self-center">
<div class="w-full max-w-3xl flex flex-col self-center mb-3 px-2">
<Heading tag="h1" class="h-leather mb-2">Contact GitCitadel</Heading>
<P class="my-3">

92
src/routes/events/+page.svelte

@ -1,11 +1,10 @@ @@ -1,11 +1,10 @@
<script lang="ts">
import { Heading, P } from "flowbite-svelte";
import { Heading, P, List, Li } from "flowbite-svelte";
import { page } from "$app/stores";
import { goto } from "$app/navigation";
import type { NDKEvent } from "$lib/utils/nostrUtils";
import EventSearch from "$lib/components/EventSearch.svelte";
import EventDetails from "$lib/components/EventDetails.svelte";
import RelayActions from "$lib/components/RelayActions.svelte";
import CommentBox from "$lib/components/CommentBox.svelte";
import CommentViewer from "$lib/components/CommentViewer.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
@ -14,7 +13,6 @@ @@ -14,7 +13,6 @@
toNpub,
getUserMetadata,
} from "$lib/utils/nostrUtils";
import EventInput from "$lib/components/EventInput.svelte";
import CopyToClipboard from "$lib/components/util/CopyToClipboard.svelte";
import { neventEncode, naddrEncode } from "$lib/utils";
import { activeInboxRelays, getNdkContext } from "$lib/ndk";
@ -33,6 +31,7 @@ @@ -33,6 +31,7 @@
import type { SearchType } from "$lib/models/search_type";
import { clearAllCaches } from "$lib/utils/cache_manager";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
import { AAlert } from "$lib/a";
// AI-NOTE: Add cache clearing function for testing second-order search
// This can be called from browser console: window.clearCache()
@ -506,27 +505,25 @@ @@ -506,27 +505,25 @@
<P class="mb-3">
Search and explore Nostr events across the network. Find events by:
</P>
<ul
class="mb-3 list-disc list-inside space-y-1 text-sm text-gray-700 dark:text-gray-300"
>
<li>
<List class="mb-3 list-disc">
<Li>
<strong>Event identifiers:</strong> nevent, note, naddr, npub, nprofile,
pubkey, or event ID
</li>
<li><strong>NIP-05 addresses:</strong> username@domain.com</li>
<li>
</Li>
<Li><strong>NIP-05 addresses:</strong> username@domain.com</Li>
<Li>
<strong>Profile names:</strong> Search by display name or username (use
"n:" prefix for exact matches)
</li>
<li>
</Li>
<Li>
<strong>D-tags:</strong> Find events with specific d-tags using "d:tag-name"
</li>
<li>
</Li>
<Li>
<strong>T-tags:</strong> Find events tagged with specific topics using
"t:topic"
</li>
</ul>
<P class="mb-3 text-sm text-gray-600 dark:text-gray-400">
</Li>
</List>
<P class="mb-3 text-sm text-muted">
The page shows primary search results, second-order references
(replies, quotes, mentions), and related tagged events. Click any
event to view details, comments, and relay information.
@ -545,11 +542,9 @@ @@ -545,11 +542,9 @@
/>
{#if secondOrderSearchMessage}
<div
class="mt-4 p-4 text-sm text-blue-700 bg-blue-100 dark:bg-blue-900 dark:text-blue-200 rounded-lg"
>
<AAlert color="blue">
{secondOrderSearchMessage}
</div>
</AAlert>
{/if}
{#if searchResults.length > 0}
@ -1318,36 +1313,6 @@ @@ -1318,36 +1313,6 @@
</div>
</div>
{/if}
{#if !event && searchResults.length === 0 && secondOrderResults.length === 0 && tTagResults.length === 0 && !searchValue && !searchInProgress}
<div class="mt-8 w-full">
<Heading tag="h2" class="h-leather mb-4"
>Publish Nostr Event</Heading
>
<P class="mb-4">
Create and publish new Nostr events to the network. This form
supports various event kinds including:
</P>
<ul
class="mb-6 list-disc list-inside space-y-1 text-sm text-gray-700 dark:text-gray-300"
>
<li>
<strong>Kind 30040:</strong> Publication indexes that organize AsciiDoc
content into structured publications
</li>
<li>
<strong>Kind 30041:</strong> Individual section content for publications
</li>
<li>
<strong>Other kinds:</strong> Standard Nostr events with custom tags
and content
</li>
</ul>
<div class="w-full flex justify-center">
<EventInput />
</div>
</div>
{/if}
</div>
</div>
@ -1392,26 +1357,17 @@ @@ -1392,26 +1357,17 @@
<div class="min-w-0 overflow-hidden">
<EventDetails {event} {profile} communityStatusMap={communityStatus} />
</div>
<div class="min-w-0 overflow-hidden">
<RelayActions {event} />
</div>
<div class="min-w-0 overflow-hidden">
<div class="flex flex-col space-y-6">
<CommentViewer {event} />
</div>
{#if user?.signedIn}
<div class="mt-8 min-w-0 overflow-hidden">
<Heading tag="h3" class="h-leather mb-4 break-words"
>Add Comment</Heading
>
{#if user?.signedIn}
<CommentBox {event} {userRelayPreference} />
</div>
{:else}
<div class="mt-8 p-4 bg-gray-200 dark:bg-gray-700 rounded-lg min-w-0">
<P>Please sign in to add comments.</P>
</div>
{/if}
{:else}
<AAlert color="blue">
Please sign in to add comments.
</AAlert>
{/if}
</div>
</div>
{/if}
</div>

34
src/routes/events/compose/+page.svelte

@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
<script lang="ts">
import { Heading, P } from "flowbite-svelte";
import { Heading, P, List, Li } from "flowbite-svelte";
import EventInput from "$components/EventInput.svelte";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk.ts";
import { userStore } from "$lib/stores/userStore.ts";
import { AAlert } from "$lib/a";
// AI-NOTE: 2025-01-24 - Reactive effect to log relay configuration when stores change - non-blocking approach
$effect.pre(() => {
@ -27,18 +28,39 @@ @@ -27,18 +28,39 @@
<div class="main-leather flex flex-col space-y-6">
<Heading tag="h1" class="h-leather mb-2">Compose Event</Heading>
<P class="mb-3">
<P class="my-3">
Use this page to compose and publish various types of events to the Nostr network.
You can create notes, articles, and other event types depending on your needs.
</P>
<P class="mb-4">
Create and publish new Nostr events to the network. This form
supports various event kinds including:
</P>
<List
class="mb-6 list-disc list-inside space-y-1"
>
<Li>
<strong>Kind 30040:</strong> Publication indexes that organize AsciiDoc
content into structured publications
</Li>
<Li>
<strong>Kind 30041:</strong> Individual section content for publications
</Li>
<Li>
<strong>Other kinds:</strong> Standard Nostr events with custom tags
and content
</Li>
</List>
{#if $userStore.signedIn}
<EventInput />
{:else}
<div class="p-6 bg-gray-200 dark:bg-gray-700 rounded-lg text-center">
<Heading tag="h3" class="h-leather mb-4">Sign In Required</Heading>
<P>Please sign in to compose and publish events to the Nostr network.</P>
</div>
<AAlert color="blue">
{#snippet title()}Sign In Required{/snippet}
Please sign in to compose and publish events to the Nostr network.
</AAlert>
{/if}
</div>
</div>

7
src/routes/profile/+page.svelte

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import { getUserMetadata } from "$lib/utils/nostrUtils";
import type { NDKEvent } from "$lib/utils/nostrUtils";
import { getNdkContext } from "$lib/ndk.ts";
import { Heading } from "flowbite-svelte";
import { Heading, P } from "flowbite-svelte";
import ATechToggle from "$lib/a/reader/ATechToggle.svelte";
// State
@ -82,8 +82,9 @@ @@ -82,8 +82,9 @@
{:else}
<div class="w-full flex justify-center">
<div class="flex flex-col w-full max-w-5xl my-6 px-4 mx-auto gap-6">
<AProfilePreview user={user} profile={profile} loading={loading} error={error} isOwn={!!user?.signedIn && (!profileEvent?.pubkey || profileEvent.pubkey === user.pubkey)} />
{#if profileEvent}
<AProfilePreview event={profileEvent} user={user} profile={profile} loading={loading} error={error} isOwn={!!user?.signedIn && (!profileEvent?.pubkey || profileEvent.pubkey === user.pubkey)} />
{/if}
<div class="mt-6">
<Heading tag="h3" class="h-leather mb-4">
Settings

2
src/routes/start/+page.svelte

@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
const isVersionKnown = appVersion !== "development";
</script>
<div class="w-full max-w-2xl flex flex-col self-center">
<div class="w-full max-w-3xl flex flex-col self-center mb-3 px-2">
<Heading tag="h1" class="h-leather mb-2"
>Getting Started with Alexandria</Heading
>

Loading…
Cancel
Save