diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte
index 52489e5..87bc44b 100644
--- a/src/lib/components/publications/Publication.svelte
+++ b/src/lib/components/publications/Publication.svelte
@@ -249,7 +249,7 @@
{#if isLoading}
Loading...
{:else if !isDone}
- loadMore(1)}>Show More
+ loadMore(1)}>Show More
{:else}
You've reached the end of the publication.
diff --git a/src/lib/components/publications/PublicationFeed.svelte b/src/lib/components/publications/PublicationFeed.svelte
index 48e4eba..0a2593b 100644
--- a/src/lib/components/publications/PublicationFeed.svelte
+++ b/src/lib/components/publications/PublicationFeed.svelte
@@ -7,15 +7,19 @@
import { onMount, onDestroy } from "svelte";
import {
getMatchingTags,
+ toNpub,
} from "$lib/utils/nostrUtils";
import { WebSocketPool } from "$lib/data_structures/websocket_pool";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { searchCache } from "$lib/utils/searchCache";
import { indexEventCache } from "$lib/utils/indexEventCache";
import { isValidNip05Address } from "$lib/utils/search_utility";
+ import { userStore } from "$lib/stores/userStore.ts";
+ import { nip19 } from "nostr-tools";
const props = $props<{
searchQuery?: string;
+ showOnlyMyPublications?: boolean;
onEventCountUpdate?: (counts: { displayed: number; total: number }) => void;
}>();
@@ -27,6 +31,7 @@
let loading: boolean = $state(true);
let hasInitialized = $state(false);
let fallbackTimeout: ReturnType | null = null;
+ let gridContainer: HTMLElement;
// Relay management
let allRelays: string[] = $state([]);
@@ -35,6 +40,42 @@
// Event management
let allIndexEvents: NDKEvent[] = $state([]);
+ // Calculate the number of columns based on window width
+ let columnCount = $state(1);
+ let publicationsToDisplay = $state(10);
+
+ // Update column count and publications when window resizes
+ $effect(() => {
+ if (typeof window !== 'undefined') {
+ const width = window.innerWidth;
+ let newColumnCount = 1;
+ if (width >= 1280) newColumnCount = 4; // xl:grid-cols-4
+ else if (width >= 1024) newColumnCount = 3; // lg:grid-cols-3
+ else if (width >= 768) newColumnCount = 2; // md:grid-cols-2
+
+ if (columnCount !== newColumnCount) {
+ columnCount = newColumnCount;
+ publicationsToDisplay = newColumnCount * 10;
+
+ // Update the view immediately when column count changes
+ if (allIndexEvents.length > 0) {
+ let source = allIndexEvents;
+
+ // Apply user filter first
+ source = filterEventsByUser(source);
+
+ // Then apply search filter if query exists
+ if (props.searchQuery?.trim()) {
+ source = filterEventsBySearch(source);
+ }
+
+ eventsInView = source.slice(0, publicationsToDisplay);
+ endOfFeed = eventsInView.length >= source.length;
+ }
+ }
+ }
+ });
+
// Initialize relays and fetch events
async function initializeAndFetch() {
if (!ndk) {
@@ -56,6 +97,17 @@
if (newRelays.length === 0) {
console.debug('[PublicationFeed] No relays available, waiting...');
+ // Set up a retry mechanism when relays become available
+ const unsubscribe = activeInboxRelays.subscribe((relays) => {
+ if (relays.length > 0 && !hasInitialized) {
+ console.debug('[PublicationFeed] Relays now available, retrying initialization');
+ unsubscribe();
+ setTimeout(() => {
+ hasInitialized = true;
+ initializeAndFetch();
+ }, 1000);
+ }
+ });
return;
}
@@ -121,8 +173,8 @@
`[PublicationFeed] Using cached index events (${cachedEvents.length} events)`,
);
allIndexEvents = cachedEvents;
- eventsInView = allIndexEvents.slice(0, 30);
- endOfFeed = allIndexEvents.length <= 30;
+ eventsInView = allIndexEvents.slice(0, publicationsToDisplay);
+ endOfFeed = allIndexEvents.length <= publicationsToDisplay;
loading = false;
return;
}
@@ -210,8 +262,8 @@
allIndexEvents.sort((a, b) => b.created_at! - a.created_at!);
// Update the view immediately with new events
- eventsInView = allIndexEvents.slice(0, 30);
- endOfFeed = allIndexEvents.length <= 30;
+ eventsInView = allIndexEvents.slice(0, publicationsToDisplay);
+ endOfFeed = allIndexEvents.length <= publicationsToDisplay;
console.debug(`[PublicationFeed] Updated view with ${newEvents.length} new events from ${relay}, total: ${allIndexEvents.length}`);
}
@@ -236,15 +288,109 @@
indexEventCache.set(allRelays, allIndexEvents);
// Final update to ensure we have the latest view
- eventsInView = allIndexEvents.slice(0, 30);
- endOfFeed = allIndexEvents.length <= 30;
+ eventsInView = allIndexEvents.slice(0, publicationsToDisplay);
+ endOfFeed = allIndexEvents.length <= publicationsToDisplay;
loading = false;
}
+ // Function to convert various Nostr identifiers to npub using the utility function
+ const convertToNpub = (input: string): string | null => {
+ const result = toNpub(input);
+ if (!result) {
+ console.debug("[PublicationFeed] Failed to convert to npub:", input);
+ }
+ return result;
+ };
+
+ // Function to filter events by npub (author or p tags)
+ const filterEventsByNpub = (events: NDKEvent[], npub: string): NDKEvent[] => {
+ try {
+ const decoded = nip19.decode(npub);
+ if (decoded.type !== 'npub') {
+ console.debug("[PublicationFeed] Invalid npub format:", npub);
+ return events;
+ }
+
+ const pubkey = decoded.data.toLowerCase();
+ console.debug("[PublicationFeed] Filtering events for npub:", npub, "pubkey:", pubkey);
+
+ const filtered = events.filter((event) => {
+ // Check if user is the author of the event
+ const eventPubkey = event.pubkey.toLowerCase();
+ const isAuthor = eventPubkey === pubkey;
+
+ // Check if user is listed in "p" tags (participants/contributors)
+ const pTags = getMatchingTags(event, "p");
+ const isInPTags = pTags.some(tag => tag[1]?.toLowerCase() === pubkey);
+
+ const matches = isAuthor || isInPTags;
+
+ if (matches) {
+ console.debug("[PublicationFeed] Event matches npub filter:", {
+ id: event.id,
+ eventPubkey,
+ searchPubkey: pubkey,
+ isAuthor,
+ isInPTags,
+ pTags: pTags.map(tag => tag[1])
+ });
+ }
+ return matches;
+ });
+
+ console.debug("[PublicationFeed] Events after npub filtering:", filtered.length);
+ return filtered;
+ } catch (error) {
+ console.debug("[PublicationFeed] Error filtering by npub:", npub, error);
+ return events;
+ }
+ };
+
+ // Function to filter events by current user's pubkey
+ const filterEventsByUser = (events: NDKEvent[]) => {
+ if (!props.showOnlyMyPublications) return events;
+
+ const currentUser = $userStore;
+ if (!currentUser.signedIn || !currentUser.pubkey) {
+ console.debug("[PublicationFeed] User not signed in or no pubkey, showing all events");
+ return events;
+ }
+
+ const userPubkey = currentUser.pubkey.toLowerCase();
+ console.debug("[PublicationFeed] Filtering events for user:", userPubkey);
+
+ const filtered = events.filter((event) => {
+ // Check if user is the author of the event
+ const eventPubkey = event.pubkey.toLowerCase();
+ const isAuthor = eventPubkey === userPubkey;
+
+ // Check if user is listed in "p" tags (participants/contributors)
+ const pTags = getMatchingTags(event, "p");
+ const isInPTags = pTags.some(tag => tag[1]?.toLowerCase() === userPubkey);
+
+ const matches = isAuthor || isInPTags;
+
+ if (matches) {
+ console.debug("[PublicationFeed] Event matches user filter:", {
+ id: event.id,
+ eventPubkey,
+ userPubkey,
+ isAuthor,
+ isInPTags,
+ pTags: pTags.map(tag => tag[1])
+ });
+ }
+ return matches;
+ });
+
+ console.debug("[PublicationFeed] Events after user filtering:", filtered.length);
+ return filtered;
+ };
+
// Function to filter events based on search query
const filterEventsBySearch = (events: NDKEvent[]) => {
if (!props.searchQuery) return events;
- const query = props.searchQuery.toLowerCase();
+ const query = props.searchQuery.trim();
console.debug(
"[PublicationFeed] Filtering events with query:",
query,
@@ -261,6 +407,27 @@
return cachedResult.events;
}
+ // AI-NOTE: Check if the query is a Nostr identifier (npub, hex, nprofile)
+ const npub = convertToNpub(query);
+ if (npub) {
+ console.debug("[PublicationFeed] Query is a Nostr identifier, filtering by npub:", npub);
+ const filtered = filterEventsByNpub(events, npub);
+
+ // Cache the filtered results
+ const result = {
+ events: filtered,
+ secondOrder: [],
+ tTagEvents: [],
+ eventIds: new Set(),
+ addresses: new Set(),
+ searchType: "publication",
+ searchTerm: query,
+ };
+ searchCache.set("publication", query, result);
+
+ return filtered;
+ }
+
// Check if the query is a NIP-05 address
const isNip05Query = isValidNip05Address(query);
console.debug("[PublicationFeed] Is NIP-05 query:", isNip05Query);
@@ -276,7 +443,7 @@
// For NIP-05 queries, only match against NIP-05 tags
if (isNip05Query) {
- const matches = nip05 === query;
+ const matches = nip05 === query.toLowerCase();
if (matches) {
console.debug("[PublicationFeed] Event matches NIP-05 search:", {
id: event.id,
@@ -288,11 +455,12 @@
}
// For regular queries, match against all fields
+ const queryLower = query.toLowerCase();
const matches =
- title.includes(query) ||
- authorName.includes(query) ||
- authorPubkey.includes(query) ||
- nip05.includes(query);
+ title.includes(queryLower) ||
+ authorName.includes(queryLower) ||
+ authorPubkey.includes(queryLower) ||
+ nip05.includes(queryLower);
if (matches) {
console.debug("[PublicationFeed] Event matches search:", {
id: event.id,
@@ -323,21 +491,37 @@
// Debounced search function
const debouncedSearch = debounceAsync(async (query: string) => {
- console.debug("[PublicationFeed] Search query changed:", query);
+ console.debug("[PublicationFeed] Search query or user filter changed:", query);
+ let filtered = allIndexEvents;
+
+ // Apply user filter first
+ filtered = filterEventsByUser(filtered);
+
+ // Then apply search filter if query exists
if (query && query.trim()) {
- const filtered = filterEventsBySearch(allIndexEvents);
- eventsInView = filtered.slice(0, 30);
- endOfFeed = filtered.length <= 30;
- } else {
- eventsInView = allIndexEvents.slice(0, 30);
- endOfFeed = allIndexEvents.length <= 30;
+ filtered = filterEventsBySearch(filtered);
}
+
+ eventsInView = filtered.slice(0, publicationsToDisplay);
+ endOfFeed = filtered.length <= publicationsToDisplay;
}, 300);
+ // AI-NOTE: Watch for changes in search query and user filter
$effect(() => {
+ // Trigger search when either search query or user filter changes
+ // Also watch for changes in user store to update filter when user logs in/out
debouncedSearch(props.searchQuery);
});
+ // AI-NOTE: Watch for changes in the user filter checkbox
+ $effect(() => {
+ // Trigger filtering when the user filter checkbox changes
+ // Access both props to ensure the effect runs when either changes
+ const searchQuery = props.searchQuery;
+ const showOnlyMyPublications = props.showOnlyMyPublications;
+ debouncedSearch(searchQuery);
+ });
+
// Emit event count updates
$effect(() => {
if (props.onEventCountUpdate) {
@@ -351,10 +535,17 @@
async function loadMorePublications() {
loadingMore = true;
const current = eventsInView.length;
- let source = props.searchQuery.trim()
- ? filterEventsBySearch(allIndexEvents)
- : allIndexEvents;
- eventsInView = source.slice(0, current + 30);
+ let source = allIndexEvents;
+
+ // Apply user filter first
+ source = filterEventsByUser(source);
+
+ // Then apply search filter if query exists
+ if (props.searchQuery.trim()) {
+ source = filterEventsBySearch(source);
+ }
+
+ eventsInView = source.slice(0, current + publicationsToDisplay);
endOfFeed = eventsInView.length >= source.length;
loadingMore = false;
}
@@ -388,14 +579,57 @@
cleanup();
});
- onMount(async () => {
+ onMount(() => {
console.debug('[PublicationFeed] onMount called');
// The effect will handle fetching when relays become available
+
+ // Add window resize listener for responsive updates
+ const handleResize = () => {
+ if (typeof window !== 'undefined') {
+ const width = window.innerWidth;
+ let newColumnCount = 1;
+ if (width >= 1280) newColumnCount = 4; // xl:grid-cols-4
+ else if (width >= 1024) newColumnCount = 3; // lg:grid-cols-3
+ else if (width >= 768) newColumnCount = 2; // md:grid-cols-2
+
+ if (columnCount !== newColumnCount) {
+ columnCount = newColumnCount;
+ publicationsToDisplay = newColumnCount * 10;
+
+ // Update the view immediately when column count changes
+ if (allIndexEvents.length > 0) {
+ let source = allIndexEvents;
+
+ // Apply user filter first
+ source = filterEventsByUser(source);
+
+ // Then apply search filter if query exists
+ if (props.searchQuery?.trim()) {
+ source = filterEventsBySearch(source);
+ }
+
+ eventsInView = source.slice(0, publicationsToDisplay);
+ endOfFeed = eventsInView.length >= source.length;
+ }
+ }
+ }
+ };
+
+ window.addEventListener('resize', handleResize);
+
+ // Initial calculation
+ handleResize();
+
+ // Cleanup function
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
});
{#if loading && eventsInView.length === 0}
diff --git a/src/lib/components/publications/PublicationHeader.svelte b/src/lib/components/publications/PublicationHeader.svelte
index c1c6222..5cab792 100644
--- a/src/lib/components/publications/PublicationHeader.svelte
+++ b/src/lib/components/publications/PublicationHeader.svelte
@@ -35,7 +35,7 @@
let title: string = $derived(event.getMatchingTags("title")[0]?.[1]);
let author: string = $derived(
- event.getMatchingTags(event, "author")[0]?.[1] ?? "unknown",
+ event.getMatchingTags("author")[0]?.[1] ?? "unknown",
);
let version: string = $derived(
event.getMatchingTags("version")[0]?.[1] ?? "1",
diff --git a/src/lib/components/util/Details.svelte b/src/lib/components/util/Details.svelte
index 5ce8b28..ad5c423 100644
--- a/src/lib/components/util/Details.svelte
+++ b/src/lib/components/util/Details.svelte
@@ -62,7 +62,7 @@
{@render userBadge(event.pubkey, author)}
{@render userBadge(event.pubkey, undefined)}
diff --git a/src/lib/components/util/Profile.svelte b/src/lib/components/util/Profile.svelte
index cc5ff4a..d39c286 100644
--- a/src/lib/components/util/Profile.svelte
+++ b/src/lib/components/util/Profile.svelte
@@ -21,7 +21,7 @@
import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { onMount } from "svelte";
import { getUserMetadata } from "$lib/utils/nostrUtils";
- import { activeInboxRelays } from "$lib/ndk";
+ import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
let { pubkey, isNav = false } = $props<{ pubkey?: string, isNav?: boolean }>();
@@ -187,6 +187,23 @@
try {
console.log("Refreshing profile for npub:", userState.npub);
+ // Check if we have relays available
+ const inboxRelays = get(activeInboxRelays);
+ const outboxRelays = get(activeOutboxRelays);
+
+ if (inboxRelays.length === 0 && outboxRelays.length === 0) {
+ console.log("Profile: No relays available, will retry when relays become available");
+ // Set up a retry mechanism when relays become available
+ const unsubscribe = activeInboxRelays.subscribe((relays) => {
+ if (relays.length > 0 && !isRefreshingProfile) {
+ console.log("Profile: Relays now available, retrying profile fetch");
+ unsubscribe();
+ setTimeout(() => refreshProfile(), 1000);
+ }
+ });
+ return;
+ }
+
// Try using NDK's built-in profile fetching first
const ndk = get(ndkInstance);
if (ndk && userState.ndkUser) {
diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts
index 6871044..c507b9f 100644
--- a/src/lib/data_structures/publication_tree.ts
+++ b/src/lib/data_structures/publication_tree.ts
@@ -2,6 +2,10 @@ import { Lazy } from "./lazy.ts";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import type NDK from "@nostr-dev-kit/ndk";
import { fetchEventById } from "../utils/websocket_utils.ts";
+import { fetchEventWithFallback, NDKRelaySetFromNDK } from "../utils/nostrUtils.ts";
+import { get } from "svelte/store";
+import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts";
+import { searchRelays, secondaryRelays } from "../consts.ts";
enum PublicationTreeNodeType {
Branch,
@@ -685,24 +689,108 @@ export class PublicationTree implements AsyncIterable
{
if (!event) {
const [kind, pubkey, dTag] = address.split(":");
- const fetchedEvent = await this.#ndk.fetchEvent({
+
+ // AI-NOTE: 2025-01-24 - Enhanced event fetching with comprehensive fallback
+ // First try to fetch using the enhanced fetchEventWithFallback function
+ // which includes search relay fallback logic
+ return fetchEventWithFallback(this.#ndk, {
kinds: [parseInt(kind)],
authors: [pubkey],
"#d": [dTag],
- });
-
- // Cache the event if found
- if (fetchedEvent) {
- this.#eventCache.set(address, fetchedEvent);
- event = fetchedEvent;
- }
+ }, 5000) // 5 second timeout for publication events
+ .then(fetchedEvent => {
+ if (fetchedEvent) {
+ // Cache the event if found
+ this.#eventCache.set(address, fetchedEvent);
+ event = fetchedEvent;
+ }
+
+ if (!event) {
+ console.warn(
+ `[PublicationTree] Event with address ${address} not found on primary relays, trying search relays.`,
+ );
+
+ // If still not found, try a more aggressive search using search relays
+ return this.#trySearchRelayFallback(address, kind, pubkey, dTag, parentNode);
+ }
+
+ return this.#buildNodeFromEvent(event, address, parentNode);
+ })
+ .catch(error => {
+ console.warn(`[PublicationTree] Error fetching event for address ${address}:`, error);
+
+ // Try search relay fallback even on error
+ return this.#trySearchRelayFallback(address, kind, pubkey, dTag, parentNode);
+ });
}
- if (!event) {
- console.debug(
- `[PublicationTree] Event with address ${address} not found.`,
- );
+ return Promise.resolve(this.#buildNodeFromEvent(event, address, parentNode));
+ }
+ /**
+ * AI-NOTE: 2025-01-24 - Aggressive search relay fallback for publication events
+ * This method tries to find events on search relays when they're not found on primary relays
+ */
+ async #trySearchRelayFallback(
+ address: string,
+ kind: string,
+ pubkey: string,
+ dTag: string,
+ parentNode: PublicationTreeNode
+ ): Promise {
+ try {
+ console.log(`[PublicationTree] Trying search relay fallback for address: ${address}`);
+
+ // Get current relay configuration
+ const inboxRelays = get(activeInboxRelays);
+ const outboxRelays = get(activeOutboxRelays);
+
+ // Create a comprehensive relay set including search relays
+ const allRelays = [...inboxRelays, ...outboxRelays, ...searchRelays, ...secondaryRelays];
+ const uniqueRelays = [...new Set(allRelays)]; // Remove duplicates
+
+ console.log(`[PublicationTree] Trying ${uniqueRelays.length} relays for fallback search:`, uniqueRelays);
+
+ // Try each relay individually with a shorter timeout
+ for (const relay of uniqueRelays) {
+ try {
+ const relaySet = NDKRelaySetFromNDK.fromRelayUrls([relay], this.#ndk);
+
+ const fetchedEvent = await this.#ndk.fetchEvent({
+ kinds: [parseInt(kind)],
+ authors: [pubkey],
+ "#d": [dTag],
+ }, undefined, relaySet).withTimeout(3000); // 3 second timeout per relay
+
+ if (fetchedEvent) {
+ console.log(`[PublicationTree] Found event ${fetchedEvent.id} on search relay: ${relay}`);
+
+ // Cache the event
+ this.#eventCache.set(address, fetchedEvent);
+ this.#events.set(address, fetchedEvent);
+
+ return this.#buildNodeFromEvent(fetchedEvent, address, parentNode);
+ }
+ } catch (error) {
+ console.debug(`[PublicationTree] Failed to fetch from relay ${relay}:`, error);
+ continue; // Try next relay
+ }
+ }
+
+ // If we get here, the event was not found on any relay
+ console.warn(`[PublicationTree] Event with address ${address} not found on any relay after fallback search.`);
+
+ return {
+ type: PublicationTreeNodeType.Leaf,
+ status: PublicationTreeNodeStatus.Error,
+ address,
+ parent: parentNode,
+ children: [],
+ };
+
+ } catch (error) {
+ console.error(`[PublicationTree] Error in search relay fallback for ${address}:`, error);
+
return {
type: PublicationTreeNodeType.Leaf,
status: PublicationTreeNodeStatus.Error,
@@ -711,7 +799,17 @@ export class PublicationTree implements AsyncIterable {
children: [],
};
}
+ }
+ /**
+ * AI-NOTE: 2025-01-24 - Helper method to build a node from an event
+ * This extracts the common logic for building nodes from events
+ */
+ #buildNodeFromEvent(
+ event: NDKEvent,
+ address: string,
+ parentNode: PublicationTreeNode
+ ): PublicationTreeNode {
this.#events.set(address, event);
const childAddresses = event.tags
@@ -754,14 +852,11 @@ export class PublicationTree implements AsyncIterable {
}
});
- const resolvedAddresses = await Promise.all(eTagPromises);
- const validAddresses = resolvedAddresses.filter(addr => addr !== null) as string[];
-
- console.debug(`[PublicationTree] Resolved ${validAddresses.length} valid addresses from e-tags:`, validAddresses);
-
- if (validAddresses.length > 0) {
- childAddresses.push(...validAddresses);
- }
+ // Note: We can't await here since this is a synchronous method
+ // The e-tag resolution will happen when the children are processed
+ // For now, we'll add the e-tags as potential child addresses
+ const eTagAddresses = eTags.map(tag => tag[1]);
+ childAddresses.push(...eTagAddresses);
}
const node: PublicationTreeNode = {
@@ -772,10 +867,13 @@ export class PublicationTree implements AsyncIterable {
children: [],
};
+ // Add children asynchronously
const childPromises = childAddresses.map(address =>
this.addEventByAddress(address, event)
);
- await Promise.all(childPromises);
+ Promise.all(childPromises).catch(error => {
+ console.warn(`[PublicationTree] Error adding children for ${address}:`, error);
+ });
this.#nodeResolvedObservers.forEach((observer) => observer(address));
diff --git a/src/lib/stores/userStore.ts b/src/lib/stores/userStore.ts
index df73ab7..1e58f42 100644
--- a/src/lib/stores/userStore.ts
+++ b/src/lib/stores/userStore.ts
@@ -288,14 +288,20 @@ export async function loginWithAmber(amberSigner: NDKSigner, user: NDKUser) {
*/
export async function loginWithNpub(pubkeyOrNpub: string) {
const ndk = get(ndkInstance);
- if (!ndk) throw new Error("NDK not initialized");
- // Only clear previous login state after successful login
+ if (!ndk) {
+ throw new Error("NDK not initialized");
+ }
+
let hexPubkey: string;
- if (pubkeyOrNpub.startsWith("npub")) {
+ if (pubkeyOrNpub.startsWith("npub1")) {
try {
- hexPubkey = nip19.decode(pubkeyOrNpub).data as string;
+ const decoded = nip19.decode(pubkeyOrNpub);
+ if (decoded.type !== "npub") {
+ throw new Error("Invalid npub format");
+ }
+ hexPubkey = decoded.data;
} catch (e) {
- console.error("Failed to decode hex pubkey from npub:", pubkeyOrNpub, e);
+ console.error("Failed to decode npub:", pubkeyOrNpub, e);
throw e;
}
} else {
@@ -313,6 +319,18 @@ export async function loginWithNpub(pubkeyOrNpub: string) {
const user = ndk.getUser({ npub });
let profile: NostrProfile | null = null;
+
+ // First, update relay stores to ensure we have relays available
+ try {
+ console.debug('[userStore.ts] loginWithNpub: Updating relay stores for authenticated user');
+ await updateActiveRelayStores(ndk);
+ } catch (error) {
+ console.warn('[userStore.ts] loginWithNpub: Failed to update relay stores:', error);
+ }
+
+ // Wait a moment for relay stores to be properly initialized
+ await new Promise(resolve => setTimeout(resolve, 500));
+
try {
profile = await getUserMetadata(npub, true); // Force fresh fetch
console.log("Login with npub - fetched profile:", profile);
@@ -344,14 +362,6 @@ export async function loginWithNpub(pubkeyOrNpub: string) {
userStore.set(userState);
userPubkey.set(user.pubkey);
- // Update relay stores with the new user's relays
- try {
- console.debug('[userStore.ts] loginWithNpub: Updating relay stores for authenticated user');
- await updateActiveRelayStores(ndk);
- } catch (error) {
- console.warn('[userStore.ts] loginWithNpub: Failed to update relay stores:', error);
- }
-
clearLogin();
localStorage.removeItem("alexandria/logout/flag");
persistLogin(user, "npub");
diff --git a/src/lib/utils/markup/basicMarkupParser.ts b/src/lib/utils/markup/basicMarkupParser.ts
index 2d70c41..d4b35bd 100644
--- a/src/lib/utils/markup/basicMarkupParser.ts
+++ b/src/lib/utils/markup/basicMarkupParser.ts
@@ -295,10 +295,19 @@ function processBasicFormatting(content: string): string {
Image
-
-
+
+
Reveal Image
+
+
+
+
+
+