Browse Source

Breaking things...

master
Nuša Pukšič 7 months ago committed by buttercat1791
parent
commit
47133bd39e
  1. 3331
      deno.lock
  2. 32
      src/app.css
  3. 14
      src/lib/a/forms/ACommentForm.svelte
  4. 1
      src/lib/a/index.ts
  5. 8
      src/lib/a/nav/ANavbar.svelte
  6. 39
      src/lib/a/primitives/AButton.svelte
  7. 11
      src/lib/a/reader/AReaderToolbar.svelte
  8. 212
      src/lib/components/CommentBox.svelte
  9. 31
      src/lib/components/EventDetails.svelte
  10. 48
      src/lib/components/Notifications.svelte
  11. 19
      src/lib/components/RelayStatus.svelte
  12. 3
      src/lib/nav/site-nav.ts
  13. 18
      src/lib/stores/themeStore.ts
  14. 4
      src/routes/[...catchall]/+page.svelte
  15. 12
      src/routes/about/+page.svelte
  16. 7
      src/routes/about/relay-stats/+page.svelte
  17. 10
      src/routes/contact/+page.svelte
  18. 4
      src/routes/events/compose/+page.svelte
  19. 6
      src/routes/profile/+page.svelte
  20. 13
      src/routes/profile/notifications/+page.svelte
  21. 10
      src/routes/start/+page.svelte
  22. 5
      src/styles/a/primitives.css

3331
deno.lock

File diff suppressed because it is too large Load Diff

32
src/app.css

@ -8,7 +8,8 @@
@import "./styles/publications.css"; @import "./styles/publications.css";
@import "./styles/visualize.css"; @import "./styles/visualize.css";
@import "./styles/asciidoc.css"; @import "./styles/asciidoc.css";
@import "./theme-tokens.css"; @import "theme-tokens.css";
@import "./styles/a/primitives.css";
@layer theme, base, components, utilities; @layer theme, base, components, utilities;
@ -208,12 +209,7 @@
div.note-leather, div.note-leather,
p.note-leather, p.note-leather,
section.note-leather { section.note-leather {
<<<<<<< HEAD
@apply bg-primary-0 dark:bg-primary-1000 text-gray-900 dark:text-gray-100
p-2 rounded;
=======
@apply bg-primary-50 dark:bg-primary-1000 text-gray-900 dark:text-gray-100 p-2 rounded; @apply bg-primary-50 dark:bg-primary-1000 text-gray-900 dark:text-gray-100 p-2 rounded;
>>>>>>> 470a478 (Update, explode and break styles and components)
} }
.edit div.note-leather:hover:not(:has(.note-leather:hover)), .edit div.note-leather:hover:not(:has(.note-leather:hover)),
@ -265,12 +261,7 @@
} }
div.modal-leather > div { div.modal-leather > div {
<<<<<<< HEAD
@apply bg-primary-0 dark:bg-primary-950 border-b-[1px] border-primary-100
dark:border-primary-600;
=======
@apply bg-primary-50 dark:bg-primary-950 border-b-[1px] border-primary-100 dark:border-primary-600; @apply bg-primary-50 dark:bg-primary-950 border-b-[1px] border-primary-100 dark:border-primary-600;
>>>>>>> 470a478 (Update, explode and break styles and components)
} }
div.modal-leather > div > h1, div.modal-leather > div > h1,
@ -284,13 +275,7 @@
} }
div.modal-leather button { div.modal-leather button {
<<<<<<< HEAD
@apply bg-primary-0 hover:bg-primary-0 dark:bg-primary-950
dark:hover:bg-primary-950 text-gray-900 hover:text-primary-600
dark:text-gray-100 dark:hover:text-primary-400;
=======
@apply bg-primary-50 hover:bg-primary-50 dark:bg-primary-950 dark:hover:bg-primary-950 text-gray-900 hover:text-primary-600 dark:text-gray-100 dark:hover:text-primary-400; @apply bg-primary-50 hover:bg-primary-50 dark:bg-primary-950 dark:hover:bg-primary-950 text-gray-900 hover:text-primary-600 dark:text-gray-100 dark:hover:text-primary-400;
>>>>>>> 470a478 (Update, explode and break styles and components)
} }
/* Navbar */ /* Navbar */
@ -490,13 +475,7 @@
/* Tooltip */ /* Tooltip */
.tooltip-leather { .tooltip-leather {
<<<<<<< HEAD
@apply fixed p-4 rounded shadow-lg bg-primary-0 dark:bg-primary-1000
text-gray-900 dark:text-gray-100 border border-gray-200
dark:border-gray-700 transition-colors duration-200;
=======
@apply fixed p-4 rounded shadow-lg bg-primary-50 dark:bg-primary-1000 text-gray-900 dark:text-gray-100 border border-gray-200 dark:border-gray-700 transition-colors duration-200; @apply fixed p-4 rounded shadow-lg bg-primary-50 dark:bg-primary-1000 text-gray-900 dark:text-gray-100 border border-gray-200 dark:border-gray-700 transition-colors duration-200;
>>>>>>> 470a478 (Update, explode and break styles and components)
max-width: 400px; max-width: 400px;
z-index: 1000; z-index: 1000;
} }
@ -740,3 +719,10 @@
text-indent: 0 !important; text-indent: 0 !important;
} }
} }
.icon-wiki {
font-size: 20px;
line-height: 20px;
vertical-align: text-bottom;
font-weight: 500;
}

14
src/lib/a/forms/ACommentForm.svelte

@ -5,19 +5,18 @@
Quote, Link2, Image, Hash, Quote, Link2, Image, Hash,
List, ListOrdered List, ListOrdered
} from "@lucide/svelte"; } from "@lucide/svelte";
import { userPubkey } from "$lib/stores/authStore.Svelte"; import { userStore } from "$lib/stores/userStore.ts";
import { parseBasicmarkup } from "$lib/utils/markup/basicMarkupParser.ts"; import { parseBasicmarkup } from "$lib/utils/markup/basicMarkupParser.ts";
let { let {
content = "", // make content bindable
content = $bindable(""),
extensions, extensions,
profile,
isSubmitting = false, isSubmitting = false,
onSubmit = () => {}, onSubmit = () => {},
} = $props<{ } = $props<{
content?: string; content?: string;
extensions?: any; extensions?: any;
profile?: any;
isSubmitting?: boolean; isSubmitting?: boolean;
onSubmit?: (content: string) => Promise<void>; onSubmit?: (content: string) => Promise<void>;
}>(); }>();
@ -124,9 +123,6 @@
</ToolbarGroup> </ToolbarGroup>
</Toolbar> </Toolbar>
{/snippet} {/snippet}
{#snippet addon()}
{@render profile()}
{/snippet}
{#snippet footer()} {#snippet footer()}
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<div class="flex flex-row flex-wrap gap-3 !m-0"> <div class="flex flex-row flex-wrap gap-3 !m-0">
@ -134,9 +130,9 @@
<Button size="xs" color="alternative" class="!m-0" onclick={clearForm}>Clear</Button> <Button size="xs" color="alternative" class="!m-0" onclick={clearForm}>Clear</Button>
</div> </div>
<Button <Button
disabled={isSubmitting || !content.trim() || !$userPubkey} disabled={isSubmitting || !content.trim() || !$userStore.signedIn}
type="submit"> type="submit">
{#if !$userPubkey} {#if !$userStore.signedIn}
Not Signed In Not Signed In
{:else if isSubmitting} {:else if isSubmitting}
Publishing... Publishing...

1
src/lib/a/index.ts

@ -1,4 +1,3 @@
export { default as AButton } from './primitives/AButton.svelte';
export { default as AInput } from './primitives/AInput.svelte'; export { default as AInput } from './primitives/AInput.svelte';
export { default as ACard } from './primitives/ACard.svelte'; export { default as ACard } from './primitives/ACard.svelte';
export { default as ASwitch } from './primitives/ASwitch.svelte'; export { default as ASwitch } from './primitives/ASwitch.svelte';

8
src/lib/a/nav/ANavbar.svelte

@ -16,6 +16,9 @@
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { ChevronDownOutline } from "flowbite-svelte-icons"; import { ChevronDownOutline } from "flowbite-svelte-icons";
import { AThemeToggleMini } from "$lib/a"; import { AThemeToggleMini } from "$lib/a";
import { getNdkContext } from "$lib/ndk.ts";
const ndk = getNdkContext();
let { let {
currentPath = "", currentPath = "",
@ -29,7 +32,7 @@
if (item.href) { if (item.href) {
goto(item.href); goto(item.href);
} else if (item.id === 'logout') { } else if (item.id === 'logout') {
logoutUser(); logoutUser(ndk);
} }
} }
@ -51,7 +54,7 @@
<h1>Alexandria</h1> <h1>Alexandria</h1>
</NavBrand> </NavBrand>
<div class="flex md:order-2"> <div class="flex md:order-2">
<Profile isNav={true} pubkey={userState?.npub || undefined} /> <Profile isNav={true} />
<NavHamburger /> <NavHamburger />
</div> </div>
<NavUl class="order-1" activeUrl={currentPath}> <NavUl class="order-1" activeUrl={currentPath}>
@ -75,7 +78,6 @@
{/if} {/if}
{/each} {/each}
<NavLi> <NavLi>
<DarkMode class="btn-leather p-0" />
</NavLi> </NavLi>
<AThemeToggleMini /> <AThemeToggleMini />
</NavUl> </NavUl>

39
src/lib/a/primitives/AButton.svelte

@ -1,39 +0,0 @@
<script lang="ts">
import { cva, twMerge } from '$lib/styles/cva';
let {
variant = 'solid',
size = 'md',
as = 'button',
disabled = false,
class: className = '',
// common attrs (no $$restProps in runes)
href = undefined as string | undefined,
target = undefined as string | undefined,
rel = undefined as string | undefined,
type = 'button',
onclick = undefined as undefined | ((e:MouseEvent)=>void)
} = $props();
const styles = cva(
'inline-flex items-center justify-center font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-primary/40 transition',
{ variants: { variant:
{ solid: 'bg-primary text-[rgb(var(--color-primary-contrast,255 255 255))] hover:bg-primary/90',
outline: 'border border-primary text-primary hover:bg-primary/10',
ghost: 'text-primary hover:bg-primary/10' },
size: { sm: 'h-8 px-3 text-sm', md: 'h-10 px-4', lg: 'h-12 px-5 text-lg' } },
defaultVariants: { variant: 'solid', size: 'md' } }
);
</script>
<svelte:element
this={as}
class={twMerge(styles({ variant, size }), className)}
disabled={as === 'button' ? disabled : undefined}
href={as === 'a' ? href : undefined}
target={as === 'a' ? target : undefined}
rel={as === 'a' ? rel : undefined}
type={as === 'button' ? type : undefined}
onclick={onclick}
>
<slot />
</svelte:element>

11
src/lib/a/reader/AReaderToolbar.svelte

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { theme, setTheme } from '$lib/theme/theme-store'; import { theme, setTheme } from '$lib/stores/themeStore.ts';
import AButton from '../primitives/AButton.svelte';
let size = 16; let size = 16;
let line = 1.7; let line = 1.7;
function applySize() { function applySize() {
@ -37,12 +36,12 @@
</select> </select>
<div class="mx-2 h-6 w-px bg-muted/30" /> <div class="mx-2 h-6 w-px bg-muted/30" />
<label class="text-sm opacity-70">Text size</label> <label class="text-sm opacity-70">Text size</label>
<AButton variant="outline" size="sm" on:click={decSize}>−</AButton> <Button variant="outline" size="sm" on:click={decSize}>−</Button>
<span class="text-sm w-8 text-center">{size}px</span> <span class="text-sm w-8 text-center">{size}px</span>
<AButton variant="outline" size="sm" on:click={incSize}>+</AButton> <Button variant="outline" size="sm" on:click={incSize}>+</Button>
<div class="mx-2 h-6 w-px bg-muted/30" /> <div class="mx-2 h-6 w-px bg-muted/30" />
<label class="text-sm opacity-70">Line height</label> <label class="text-sm opacity-70">Line height</label>
<AButton variant="outline" size="sm" on:click={decLine}>−</AButton> <Button variant="outline" size="sm" on:click={decLine}>−</Button>
<span class="text-sm w-10 text-center">{line}</span> <span class="text-sm w-10 text-center">{line}</span>
<AButton variant="outline" size="sm" on:click={incLine}>+</AButton> <Button variant="outline" size="sm" on:click={incLine}>+</Button>
</div> </div>

212
src/lib/components/CommentBox.svelte

@ -1,10 +1,14 @@
<script lang="ts"> <script lang="ts">
import { Button, Textarea, Alert, Modal, Input } from "flowbite-svelte"; import { Button, Alert, Modal, Input, ToolbarButton } from "flowbite-svelte";
import { UserOutline } from "flowbite-svelte-icons"; import { UserOutline } from "flowbite-svelte-icons";
import { nip19 } from "nostr-tools"; import { nip19 } from "nostr-tools";
import { toNpub } from "$lib/utils/nostrUtils"; import { toNpub } from "$lib/utils/nostrUtils";
import { searchProfiles } from "$lib/utils/search_utility"; import { searchProfiles } from "$lib/utils/search_utility";
import type { NostrProfile } from "$lib/utils/search_types"; import type {
NostrProfile,
} from "$lib/utils/search_utility";
import { userStore } from "$lib/stores/userStore"; import { userStore } from "$lib/stores/userStore";
import type { NDKEvent } from "$lib/utils/nostrUtils"; import type { NDKEvent } from "$lib/utils/nostrUtils";
import { import {
@ -18,6 +22,8 @@
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { activeInboxRelays, activeOutboxRelays, getNdkContext } from "$lib/ndk"; import { activeInboxRelays, activeOutboxRelays, getNdkContext } from "$lib/ndk";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte"; import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
import { ACommentForm } from "$lib/a";
import { AtSign } from "@lucide/svelte";
const props = $props<{ const props = $props<{
event: NDKEvent; event: NDKEvent;
@ -155,7 +161,7 @@
async function handleSubmit( async function handleSubmit(
useOtherRelays = false, useOtherRelays = false,
useSecondaryRelays = false, useSecondaryRelays = false
) { ) {
isSubmitting = true; isSubmitting = true;
error = null; error = null;
@ -372,16 +378,21 @@
} }
</script> </script>
{#snippet commentExtensions()}
<ToolbarButton title="Mention" color="dark" size="md" onclick={() => { showMentionModal = true; }}><AtSign size={24} /></ToolbarButton>
<ToolbarButton title="Insert Wikilink" color="dark" size="md" onclick={() => { showWikilinkModal = true; }}>
<span class="icon-wiki">[[ ]]</span>
</ToolbarButton>
{/snippet}
<div class="w-full space-y-4"> <div class="w-full space-y-4">
<div class="flex flex-wrap gap-2">
{#each markupButtons as button} <ACommentForm
<Button size="xs" onclick={button.action}>{button.label}</Button> bind:content={content}
{/each} {isSubmitting}
<Button size="xs" color="alternative" onclick={removeFormatting} onSubmit={() => handleSubmit()}
>Remove Formatting</Button extensions={commentExtensions}
> />
<Button size="xs" color="alternative" onclick={clearForm}>Clear</Button>
</div>
<!-- Mention Modal --> <!-- Mention Modal -->
<Modal <Modal
@ -436,81 +447,85 @@
> >
<ul class="space-y-1 p-2"> <ul class="space-y-1 p-2">
{#each mentionResults as profile} {#each mentionResults as profile}
<button <li>
type="button" <div
class="w-full text-left cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 p-2 rounded flex items-center gap-3" role="button"
onclick={() => selectMention(profile)} tabindex="0"
> class="w-full text-left cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 p-2 rounded flex items-center gap-3"
{#if profile.isInUserLists} onclick={() => selectMention(profile)}
<div onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); selectMention(profile); } }}
class="flex-shrink-0 w-6 h-6 bg-red-100 dark:bg-red-900 rounded-full flex items-center justify-center" >
title="In your lists" {#if profile.isInUserLists}
> <div
<svg class="flex-shrink-0 w-6 h-6 bg-red-100 dark:bg-red-900 rounded-full flex items-center justify-center"
class="w-4 h-4 text-red-600 dark:text-red-400" title="In your lists"
fill="currentColor"
viewBox="0 0 24 24"
> >
<path <svg
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" class="w-4 h-4 text-red-600 dark:text-red-400"
/> fill="currentColor"
</svg> viewBox="0 0 24 24"
</div> >
{:else if profile.pubkey && communityStatus[profile.pubkey]} <path
<div 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"
class="flex-shrink-0 w-6 h-6 bg-yellow-100 dark:bg-yellow-900 rounded-full flex items-center justify-center" />
title="Has posted to the community" </svg>
> </div>
<svg {:else if profile.pubkey && communityStatus[profile.pubkey]}
class="w-4 h-4 text-yellow-600 dark:text-yellow-400" <div
fill="currentColor" class="flex-shrink-0 w-6 h-6 bg-yellow-100 dark:bg-yellow-900 rounded-full flex items-center justify-center"
viewBox="0 0 24 24" title="Has posted to the community"
> >
<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}
<div class="flex-shrink-0 w-6 h-6"></div>
{/if}
{#if profile.picture}
<img
src={profile.picture}
alt="Profile"
class="w-8 h-8 rounded-full object-cover flex-shrink-0"
/>
{:else}
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex-shrink-0 flex items-center justify-center">
<UserOutline class="w-4 h-4 text-gray-600 dark:text-gray-300" />
</div>
{/if}
<div class="flex flex-col text-left min-w-0 flex-1">
<span class="font-semibold truncate">
{profile.displayName || profile.name || "anon"}
</span>
{#if profile.nip05}
<span class="text-xs text-gray-500 flex items-center gap-1">
<svg <svg
class="inline w-4 h-4 text-primary-500" class="w-4 h-4 text-yellow-600 dark:text-yellow-400"
fill="none" fill="currentColor"
stroke="currentColor"
stroke-width="2"
viewBox="0 0 24 24" viewBox="0 0 24 24"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M5 13l4 4L19 7"
/></svg
> >
{profile.nip05} <path
</span> 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}
<div class="flex-shrink-0 w-6 h-6"></div>
{/if}
{#if profile.picture}
<img
src={profile.picture}
alt="Profile"
class="w-8 h-8 rounded-full object-cover flex-shrink-0"
/>
{:else}
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex-shrink-0 flex items-center justify-center">
<UserOutline class="w-4 h-4 text-gray-600 dark:text-gray-300" />
</div>
{/if} {/if}
<span class="text-xs text-gray-400 font-mono truncate" <div class="flex flex-col text-left min-w-0 flex-1">
>{shortenNpub(profile.pubkey)}</span <span class="font-semibold truncate">
> {profile.displayName || profile.name || "anon"}
</span>
{#if profile.nip05}
<span class="text-xs text-gray-500 flex items-center gap-1">
<svg
class="inline w-4 h-4 text-primary-500"
fill="none"
stroke="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="M5 13l4 4L19 7"
/></svg
>
{profile.nip05}
</span>
{/if}
<span class="text-xs text-gray-400 font-mono truncate"
>{shortenNpub(profile.pubkey)}</span
>
</div>
</div> </div>
</button> </li>
{/each} {/each}
</ul> </ul>
</div> </div>
@ -605,43 +620,6 @@
</Alert> </Alert>
{/if} {/if}
<div class="flex flex-col sm:flex-row justify-end items-end sm:items-center gap-4">
{#if userProfile}
<div class="flex items-center gap-2 text-sm min-w-0 flex-shrink">
{#if userProfile.picture}
<img
src={userProfile.picture}
alt={userProfile.name || "Profile"}
class="w-8 h-8 rounded-full object-cover flex-shrink-0"
onerror={(e) => (e.target as HTMLImageElement).style.display = 'none'}
/>
{:else}
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center flex-shrink-0">
<UserOutline class="w-4 h-4 text-gray-600 dark:text-gray-300" />
</div>
{/if}
<span class="text-gray-900 dark:text-gray-100 truncate">
{userProfile.displayName ||
userProfile.name ||
"anon"}
</span>
</div>
{/if}
<Button
onclick={() => handleSubmit()}
disabled={isSubmitting || !content.trim() || !$userStore.pubkey}
class="w-auto min-w-[120px]"
>
{#if !$userStore.pubkey}
Not Signed In
{:else if isSubmitting}
Publishing...
{:else}
Post Comment
{/if}
</Button>
</div>
{#if !$userStore.pubkey} {#if !$userStore.pubkey}
<Alert color="yellow" class="mt-4"> <Alert color="yellow" class="mt-4">
Please sign in to post comments. Your comments will be signed with your Please sign in to post comments. Your comments will be signed with your

31
src/lib/components/EventDetails.svelte

@ -218,6 +218,32 @@
return { text: `${tag[0]}:${tag[1]}` }; return { text: `${tag[0]}:${tag[1]}` };
} }
// Navigation for tag buttons (moved out of template)
function handleTagGoto(value: string) {
if (!value) return;
if (
value.startsWith("naddr") ||
value.startsWith("nevent") ||
value.startsWith("npub") ||
value.startsWith("nprofile") ||
value.startsWith("note")
) {
goto(`/events?id=${value}`);
} else if (value.startsWith("/")) {
goto(value);
} else if (value.startsWith("d:")) {
const dTag = value.substring(2);
goto(`/events?d=${encodeURIComponent(dTag)}`);
} else if (value.startsWith("t:")) {
const tTag = value.substring(2);
goto(`/events?t=${encodeURIComponent(tTag)}`);
} else if (/^[0-9a-fA-F]{64}$/.test(value)) {
navigateToEvent(value);
} else {
goto(`/events?id=${value}`);
}
}
$effect(() => { $effect(() => {
if (!event?.pubkey) { if (!event?.pubkey) {
authorDisplayName = undefined; authorDisplayName = undefined;
@ -301,11 +327,6 @@
</h2> </h2>
{/if} {/if}
<!-- Notifications (for profile events) -->
{#if event.kind === 0}
<Notifications {event} />
{/if}
<div class="flex items-center space-x-2 min-w-0"> <div class="flex items-center space-x-2 min-w-0">
{#if toNpub(event.pubkey)} {#if toNpub(event.pubkey)}
<span class="text-gray-600 dark:text-gray-400 min-w-0" <span class="text-gray-600 dark:text-gray-400 min-w-0"

48
src/lib/components/Notifications.svelte

@ -26,10 +26,19 @@
import { getNdkContext } from "$lib/ndk"; import { getNdkContext } from "$lib/ndk";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte"; import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
const { event } = $props<{ event: NDKEvent }>();
const ndk = getNdkContext(); const ndk = getNdkContext();
// Helper: hide broken images (avoid TS assertions in template)
function hideImg(e: Event) {
const el = e.target as HTMLImageElement | null;
if (el) el.style.display = 'none';
}
// Mode typing and setter to avoid TS in template
type Mode = "to-me" | "from-me" | "public-messages";
const modes: Mode[] = ["to-me", "from-me", "public-messages"];
function setNotificationMode(m: Mode) { notificationMode = m; }
// Handle navigation events from quoted messages // Handle navigation events from quoted messages
$effect(() => { $effect(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
@ -688,8 +697,9 @@
// Check if user is viewing their own profile // Check if user is viewing their own profile
$effect(() => { $effect(() => {
if ($userStore.signedIn && $userStore.pubkey && event.pubkey) { // Only operate for a logged-in user; treat the logged-in user's profile as the source
isOwnProfile = $userStore.pubkey.toLowerCase() === event.pubkey.toLowerCase(); if ($userStore.signedIn && $userStore.pubkey) {
isOwnProfile = true;
} else { } else {
isOwnProfile = false; isOwnProfile = false;
} }
@ -839,24 +849,24 @@
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<Heading tag="h3" class="h-leather">Notifications</Heading> <Heading tag="h3" class="h-leather">Notifications</Heading>
<div class="flex items-center gap-3"> <div class="flex flex-row items-center gap-3">
<!-- New Message Button --> <!-- New Message Button -->
<Button <Button
color="primary" color="primary"
size="sm" size="sm"
onclick={() => openNewMessageModal()} onclick={() => openNewMessageModal()}
class="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium" class="flex !mb-0 items-center gap-1.5 px-3 py-1.5 text-sm font-medium"
> >
New Message New Message
</Button> </Button>
<!-- Mode toggle --> <!-- Mode toggle -->
<div class="flex bg-gray-300 dark:bg-gray-700 rounded-lg p-1"> <div class="flex flex-row bg-gray-300 dark:bg-gray-700 rounded-lg p-1">
{#each ["to-me", "from-me", "public-messages"] as mode} {#each modes as mode}
{@const modeLabel = mode === "to-me" ? "To Me" : mode === "from-me" ? "From Me" : "Public Messages"} {@const modeLabel = mode === "to-me" ? "To Me" : mode === "from-me" ? "From Me" : "Public Messages"}
<button <button
class="mode-toggle-button px-3 py-1 text-sm font-medium rounded-md {notificationMode === mode ? 'active' : 'inactive'}" class={`mode-toggle-button px-3 py-1 text-sm !mb-0 font-medium rounded-md ${notificationMode === mode ? 'active' : 'inactive'}`}
onclick={() => notificationMode = mode as "to-me" | "from-me" | "public-messages"} onclick={() => setNotificationMode(mode)}
> >
{modeLabel} {modeLabel}
</button> </button>
@ -912,7 +922,7 @@
src={authorProfile.picture} src={authorProfile.picture}
alt="Author avatar" alt="Author avatar"
class="w-10 h-10 rounded-full object-cover border border-gray-200 dark:border-gray-600" class="w-10 h-10 rounded-full object-cover border border-gray-200 dark:border-gray-600"
onerror={(e) => (e.target as HTMLImageElement).style.display = 'none'} onerror={hideImg}
/> />
{:else} {:else}
<div class="profile-picture-fallback w-10 h-10 rounded-full flex items-center justify-center border border-gray-200 dark:border-gray-600"> <div class="profile-picture-fallback w-10 h-10 rounded-full flex items-center justify-center border border-gray-200 dark:border-gray-600">
@ -944,7 +954,7 @@
</button> </button>
<!-- Filter button --> <!-- Filter button -->
<button <button
class="filter-button w-6 h-6 border border-gray-400 dark:border-gray-500 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full flex items-center justify-center text-xs transition-colors {filteredByUser === message.pubkey ? 'filter-button-active bg-gray-200 dark:bg-gray-600 border-gray-500 dark:border-gray-400' : ''}" class={`filter-button w-6 h-6 border border-gray-400 dark:border-gray-500 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full flex items-center justify-center text-xs transition-colors ${filteredByUser === message.pubkey ? 'filter-button-active bg-gray-200 dark:bg-gray-600 border-gray-500 dark:border-gray-400' : ''}`}
onclick={() => filterByUser(message.pubkey)} onclick={() => filterByUser(message.pubkey)}
title="Filter by this user" title="Filter by this user"
aria-label="Filter by this user" aria-label="Filter by this user"
@ -1075,7 +1085,7 @@
src={authorProfile.picture} src={authorProfile.picture}
alt="Author avatar" alt="Author avatar"
class="w-10 h-10 rounded-full object-cover border border-gray-200 dark:border-gray-600" class="w-10 h-10 rounded-full object-cover border border-gray-200 dark:border-gray-600"
onerror={(e) => (e.target as HTMLImageElement).style.display = 'none'} onerror={hideImg}
/> />
{:else} {:else}
<div class="profile-picture-fallback w-10 h-10 rounded-full flex items-center justify-center border border-gray-200 dark:border-gray-600"> <div class="profile-picture-fallback w-10 h-10 rounded-full flex items-center justify-center border border-gray-200 dark:border-gray-600">
@ -1301,7 +1311,7 @@
color="primary" color="primary"
onclick={sendNewMessage} onclick={sendNewMessage}
disabled={isComposingMessage || selectedRecipients.length === 0 || !newMessageContent.trim()} disabled={isComposingMessage || selectedRecipients.length === 0 || !newMessageContent.trim()}
class="flex items-center gap-2 {isComposingMessage || selectedRecipients.length === 0 || !newMessageContent.trim() ? 'button-disabled' : ''}" class={`flex items-center gap-2 ${isComposingMessage || selectedRecipients.length === 0 || !newMessageContent.trim() ? 'button-disabled' : ''}`}
> >
{#if isComposingMessage} {#if isComposingMessage}
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div> <div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
@ -1326,7 +1336,7 @@
placeholder="Search display name, name, NIP-05, or npub..." placeholder="Search display name, name, NIP-05, or npub..."
bind:value={recipientSearch} bind:value={recipientSearch}
bind:this={recipientSearchInput} bind:this={recipientSearchInput}
class="search-input w-full rounded-lg border border-gray-300 bg-gray-50 text-gray-900 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 p-2.5 {recipientLoading ? 'pr-10' : ''}" class={`search-input w-full rounded-lg border border-gray-300 bg-gray-50 text-gray-900 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 ${recipientLoading ? 'pr-10' : ''}`}
/> />
{#if recipientLoading} {#if recipientLoading}
<div class="absolute inset-y-0 right-0 flex items-center pr-3"> <div class="absolute inset-y-0 right-0 flex items-center pr-3">
@ -1347,16 +1357,14 @@
selectRecipient(profile); selectRecipient(profile);
}} }}
disabled={isAlreadySelected} disabled={isAlreadySelected}
class="recipient-selection-button w-full flex items-center gap-3 p-3 text-left bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 {isAlreadySelected ? 'opacity-50 cursor-not-allowed' : ''}" class={`recipient-selection-button w-full flex items-center gap-3 p-3 text-left bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 ${isAlreadySelected ? 'opacity-50 cursor-not-allowed' : ''}`}
> >
{#if profile.picture} {#if profile.picture}
<img <img
src={profile.picture} src={profile.picture}
alt="Profile" alt="Profile"
class="w-8 h-8 rounded-full object-cover border border-gray-200 dark:border-gray-600 flex-shrink-0" class="w-8 h-8 rounded-full object-cover border border-gray-200 dark:border-gray-600 flex-shrink-0"
onerror={(e) => { onerror={hideImg}
(e.target as HTMLImageElement).style.display = 'none';
}}
/> />
{:else} {:else}
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex-shrink-0 flex items-center justify-center"> <div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex-shrink-0 flex items-center justify-center">
@ -1423,4 +1431,4 @@
</div> </div>
</div> </div>
</Modal> </Modal>
{/if} {/if}

19
src/lib/components/RelayStatus.svelte

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Button, Alert } from "flowbite-svelte"; import { Button, Alert, Heading } from "flowbite-svelte";
import { import {
ndkSignedIn, ndkSignedIn,
testRelayConnection, testRelayConnection,
@ -8,6 +8,7 @@
} from "$lib/ndk"; } from "$lib/ndk";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { activeInboxRelays, activeOutboxRelays, getNdkContext } from "$lib/ndk"; import { activeInboxRelays, activeOutboxRelays, getNdkContext } from "$lib/ndk";
import { AAlert } from '$lib/a/index.ts';
const ndk = getNdkContext(); const ndk = getNdkContext();
@ -116,27 +117,27 @@
} }
</script> </script>
<div class="space-y-4"> <div class="space-y-4 w-full max-w-3xl flex self-center">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h3 class="text-lg font-medium">Relay Connection Status</h3> <Heading tag="h1">Relay Connection Status</Heading>
<Button size="sm" onclick={runRelayTests} disabled={testing}> <Button size="sm" onclick={runRelayTests} disabled={testing}>
{testing ? "Testing..." : "Refresh"} {testing ? "Testing..." : "Refresh"}
</Button> </Button>
</div> </div>
{#if !$ndkSignedIn} {#if !$ndkSignedIn}
<Alert color="yellow"> <AAlert color="yellow">
<span class="font-medium">Anonymous Mode</span> <span class="font-medium">Anonymous Mode</span>
<p class="mt-1 text-sm"> <p class="mt-1 text-sm">
You are not signed in. Some relays require authentication and may not be You are not signed in. Some relays require authentication and may not be
accessible. Sign in to access all relays. accessible. Sign in to access all relays.
</p> </p>
</Alert> </AAlert>
{/if} {/if}
<div class="space-y-2"> <div class="flex flex-col space-y-2">
{#each relayStatuses as status} {#each relayStatuses as status}
<div class="flex items-center justify-between p-3"> <div class="flex flex-row items-center justify-between p-3">
<div class="flex-1"> <div class="flex-1">
<div class="font-medium">{status.url}</div> <div class="font-medium">{status.url}</div>
<div class="text-sm {getStatusColor(status)}"> <div class="text-sm {getStatusColor(status)}">
@ -154,11 +155,11 @@
</div> </div>
{#if relayStatuses.some((s) => s.requiresAuth && !$ndkSignedIn)} {#if relayStatuses.some((s) => s.requiresAuth && !$ndkSignedIn)}
<Alert color="orange"> <AAlert color="orange">
<span class="font-medium">Authentication Required</span> <span class="font-medium">Authentication Required</span>
<p class="mt-1 text-sm"> <p class="mt-1 text-sm">
Some relays require authentication. Sign in to access these relays. Some relays require authentication. Sign in to access these relays.
</p> </p>
</Alert> </AAlert>
{/if} {/if}
</div> </div>

3
src/lib/nav/site-nav.ts

@ -26,7 +26,8 @@ export const siteNav: NavItem[] = [
{ title: 'Onboarding', children: [{ title: 'Getting Started', href: '/start' }] }, { title: 'Onboarding', children: [{ title: 'Getting Started', href: '/start' }] },
{ title: 'Project', children: [ { title: 'Project', children: [
{ title: 'About', href: '/about' }, { title: 'About', href: '/about' },
{ title: 'Contact', href: '/contact' } { title: 'Contact', href: '/contact' },
{ title: 'Relay Status', href: '/about/relay-stats' }
] } ] }
] ]
} }

18
src/lib/stores/themeStore.ts

@ -0,0 +1,18 @@
import { writable } from 'svelte/store';
const KEY = 'theme';
const initial =
(typeof localStorage !== 'undefined' && localStorage.getItem(KEY)) ||
'light';
export const theme = writable(initial);
theme.subscribe(v => {
if (typeof document !== 'undefined') {
document.documentElement.dataset.theme = String(v);
localStorage.setItem(KEY, String(v));
}
});
export const setTheme = (t: string) => theme.set(t);

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

@ -10,8 +10,8 @@
<P class="note-leather mb-6" <P class="note-leather mb-6"
>The page you are looking for does not exist or has been moved.</P >The page you are looking for does not exist or has been moved.</P
> >
<div class="flex space-x-4"> <div class="flex flex-row space-x-4">
<Button class="btn-leather !w-fit" onclick={() => goto("/")} <Button class="btn-leather !w-fit !mb-0" onclick={() => goto("/")}
>Return to Home</Button >Return to Home</Button
> >
<Button <Button

12
src/routes/about/+page.svelte

@ -12,15 +12,14 @@
const ndk = getNdkContext(); const ndk = getNdkContext();
</script> </script>
<div class="w-full flex justify-center"> <div class="w-full max-w-3xl flex self-center">
<main class="main-leather flex flex-col space-y-6 max-w-2xl w-full my-6 px-4"> <div class="flex justify-between items-center mb-4">
<div class="flex justify-between items-center">
<Heading tag="h1" class="h-leather mb-2" <Heading tag="h1" class="h-leather mb-2"
>About the Library of Alexandria</Heading >About the Library of Alexandria</Heading
> >
{#if isVersionKnown} {#if isVersionKnown}
<span <span
class="text-sm bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded text-nowrap" class="mt-2 text-sm bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded text-nowrap"
>Version: {appVersion}</span >Version: {appVersion}</span
> >
{/if} {/if}
@ -62,9 +61,4 @@
target="_blank">homepage</A target="_blank">homepage</A
> and find out more about us, and the many projects we are working on. > and find out more about us, and the many projects we are working on.
</P> </P>
<div class="border-t pt-6">
<RelayStatus />
</div>
</main>
</div> </div>

7
src/routes/about/relay-stats/+page.svelte

@ -0,0 +1,7 @@
<script lang="ts">
import RelayStatus from "$lib/components/RelayStatus.svelte";
</script>
<div class="w-full flex justify-center">
<RelayStatus />
</div>

10
src/routes/contact/+page.svelte

@ -290,13 +290,10 @@
}); });
</script> </script>
<div class="w-full flex justify-center"> <div class="w-full max-w-3xl flex self-center">
<main
class="main-leather flex flex-col space-y-6 max-w-3xl w-full my-6 px-6 sm:px-4"
>
<Heading tag="h1" class="h-leather mb-2">Contact GitCitadel</Heading> <Heading tag="h1" class="h-leather mb-2">Contact GitCitadel</Heading>
<P class="mb-3"> <P class="my-3">
Make sure that you follow us on <A Make sure that you follow us on <A
href="https://github.com/ShadowySupercode/gitcitadel" href="https://github.com/ShadowySupercode/gitcitadel"
target="_blank">GitHub</A target="_blank">GitHub</A
@ -318,7 +315,7 @@
<Heading tag="h2" class="h-leather mt-4 mb-2">Submit an issue</Heading> <Heading tag="h2" class="h-leather mt-4 mb-2">Submit an issue</Heading>
<P class="mb-3"> <P class="my-3">
If you are logged into the Alexandria web application (using the button at If you are logged into the Alexandria web application (using the button at
the top-right of the window), then you can use the form, below, to submit the top-right of the window), then you can use the form, below, to submit
an issue, that will appear on our repo page. an issue, that will appear on our repo page.
@ -577,7 +574,6 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi
</div> </div>
{/if} {/if}
</form> </form>
</main>
</div> </div>
<!-- Confirmation Dialog --> <!-- Confirmation Dialog -->

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

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Heading, P } from "flowbite-svelte"; import { Heading, P } from "flowbite-svelte";
import EventInput from "$components/EventInput.svelte"; import EventInput from "$components/EventInput.svelte";
import { userPubkey, isLoggedIn } from "$lib/stores/authStore.Svelte.js";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk.ts"; import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk.ts";
import { userStore } from "$lib/stores/userStore.ts";
// AI-NOTE: 2025-01-24 - Reactive effect to log relay configuration when stores change - non-blocking approach // AI-NOTE: 2025-01-24 - Reactive effect to log relay configuration when stores change - non-blocking approach
$effect.pre(() => { $effect.pre(() => {
@ -32,7 +32,7 @@
You can create notes, articles, and other event types depending on your needs. You can create notes, articles, and other event types depending on your needs.
</P> </P>
{#if isLoggedIn && userPubkey} {#if $userStore.signedIn}
<EventInput /> <EventInput />
{:else} {:else}
<div class="p-6 bg-gray-200 dark:bg-gray-700 rounded-lg text-center"> <div class="p-6 bg-gray-200 dark:bg-gray-700 rounded-lg text-center">

6
src/routes/profile/+page.svelte

@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { Heading, P } from "flowbite-svelte"; import { Heading, P } from "flowbite-svelte";
import { AAlert } from "$lib/a"; import { AAlert, ACommentForm } from "$lib/a";
import CommentBox from "$lib/components/CommentBox.svelte"; import CommentBox from "$lib/components/CommentBox.svelte";
import CommentViewer from "$lib/components/CommentViewer.svelte"; import CommentViewer from "$lib/components/CommentViewer.svelte";
import CopyToClipboard from "$lib/components/util/CopyToClipboard.svelte"; import CopyToClipboard from "$lib/components/util/CopyToClipboard.svelte";
import { userStore } from "$lib/stores/userStore"; import { userStore } from "$lib/stores/userStore";
import { getUserMetadata } from "$lib/utils/nostrUtils"; import { getUserMetadata } from "$lib/utils/nostrUtils";
import type { NDKEvent } from "$lib/utils/nostrUtils"; import type { NDKEvent } from "$lib/utils/nostrUtils";
import { ndkInstance } from "$lib/ndk"; import { getNdkContext } from "$lib/ndk.ts";
// State // State
let user = $state($userStore); let user = $state($userStore);
@ -34,7 +34,7 @@
loading = true; loading = true;
error = null; error = null;
try { try {
const ndk = $ndkInstance; const ndk = getNdkContext();
if (!ndk) { if (!ndk) {
throw new Error('NDK not initialized'); throw new Error('NDK not initialized');
} }

13
src/routes/profile/notifications/+page.svelte

@ -0,0 +1,13 @@
<script lang="ts">
import Notifications from "$lib/components/Notifications.svelte";
import { AAlert } from "$lib/a";
import { userStore } from "$lib/stores/userStore";
</script>
<div class="w-full max-w-3xl mx-auto mt-10 px-4">
{#if $userStore?.signedIn}
<Notifications />
{:else}
<AAlert color="blue">Please log in to view your notifications.</AAlert>
{/if}
</div>

10
src/routes/start/+page.svelte

@ -7,8 +7,7 @@
const isVersionKnown = appVersion !== "development"; const isVersionKnown = appVersion !== "development";
</script> </script>
<div class="w-full flex justify-center"> <div class="w-full max-w-2xl flex self-center">
<main class="main-leather flex flex-col space-y-6 max-w-2xl w-full my-6 px-4">
<Heading tag="h1" class="h-leather mb-2" <Heading tag="h1" class="h-leather mb-2"
>Getting Started with Alexandria</Heading >Getting Started with Alexandria</Heading
> >
@ -96,7 +95,7 @@
> >
</P> </P>
<div class="flex justify-center my-4"> <div class="flex flex-col items-center space-y-4 my-4">
<Img <Img
src="/screenshots/JaneEyre.png" src="/screenshots/JaneEyre.png"
alt="Jane Eyre, by Charlotte Brontë" alt="Jane Eyre, by Charlotte Brontë"
@ -132,7 +131,7 @@
> >
</P> </P>
<div class="flex justify-center my-4"> <div class="flex flex-col items-center space-y-4 my-4">
<Img <Img
src="/screenshots/ResearchPaper.png" src="/screenshots/ResearchPaper.png"
alt="Research paper" alt="Research paper"
@ -152,7 +151,7 @@
>. >.
</P> </P>
<div class="flex justify-center my-4"> <div class="flex flex-col items-center space-y-4 my-4">
<Img <Img
src="/screenshots/Documentation.png" src="/screenshots/Documentation.png"
alt="Documentation" alt="Documentation"
@ -178,5 +177,4 @@
to other wiki pages, creating a web of knowledge that can be navigated and to other wiki pages, creating a web of knowledge that can be navigated and
explored. explored.
</P> </P>
</main>
</div> </div>

5
src/styles/a/primitives.css

@ -0,0 +1,5 @@
@layer components {
.alert-leather {
@apply border border-s-4;
}
}
Loading…
Cancel
Save