From 87d55c5919d007faaf393995562ab0a121a19b9a Mon Sep 17 00:00:00 2001 From: Silberengel Date: Fri, 6 Feb 2026 21:36:08 +0100 Subject: [PATCH] bug-fixes --- src/app.css | 4 +- .../components/content/MetadataCard.svelte | 62 ++++- .../components/content/RichTextEditor.svelte | 7 +- .../find/SearchAddressableEvents.svelte | 188 ++++++++++++---- src/lib/components/layout/Header.svelte | 25 ++- .../components/layout/UnifiedSearch.svelte | 8 + src/lib/components/ui/IconButton.svelte | 2 +- src/lib/modules/feed/FeedPost.svelte | 5 + src/lib/modules/feed/HighlightCard.svelte | 6 +- src/routes/bookmarks/+page.svelte | 102 +++++++-- src/routes/cache/+page.svelte | 9 +- src/routes/discussions/+page.svelte | 5 +- src/routes/feed/+page.svelte | 5 +- src/routes/find/+page.svelte | 15 +- src/routes/relay/+page.svelte | 2 +- src/routes/replaceable/[d_tag]/+page.svelte | 212 ++++++++++++------ src/routes/repos/+page.svelte | 2 +- src/routes/repos/[naddr]/+page.svelte | 2 +- src/routes/rss/+page.svelte | 2 +- src/routes/settings/+page.svelte | 3 +- src/routes/topics/+page.svelte | 2 +- static/icons/bookmark.svg | 1 + static/icons/database.svg | 1 + static/icons/highlight.svg | 1 + static/icons/key.svg | 2 +- static/icons/settings.svg | 2 +- 26 files changed, 509 insertions(+), 166 deletions(-) create mode 100644 static/icons/bookmark.svg create mode 100644 static/icons/database.svg create mode 100644 static/icons/highlight.svg diff --git a/src/app.css b/src/app.css index 3e2dbf5..2eeb9eb 100644 --- a/src/app.css +++ b/src/app.css @@ -175,7 +175,7 @@ main { padding: 0.5rem; border: 1px solid var(--fog-border, #e5e7eb); border-radius: 0.375rem; - background: var(--fog-post, #ffffff); + background: var(--fog-surface, #f8fafc); color: var(--fog-text, #1f2937); text-decoration: none; transition: all 0.2s; @@ -187,7 +187,7 @@ main { :global(.dark) .write-button { border-color: var(--fog-dark-border, #374151); - background: var(--fog-dark-post, #1f2937); + background: var(--fog-dark-surface, #1e293b); color: var(--fog-dark-text, #f9fafb); } diff --git a/src/lib/components/content/MetadataCard.svelte b/src/lib/components/content/MetadataCard.svelte index f660a8b..4b2b9cf 100644 --- a/src/lib/components/content/MetadataCard.svelte +++ b/src/lib/components/content/MetadataCard.svelte @@ -65,9 +65,11 @@ ); const hasMetadata = $derived(image || description || summary || author || title); + const hasContent = $derived(event.content && event.content.trim().length > 0); + const shouldShowMetadata = $derived(hasMetadata || !hasContent); // Show metadata if it exists OR if there's no content -{#if hasMetadata} +{#if shouldShowMetadata}
{/if} @@ -176,4 +196,44 @@ :global(.dark) .metadata-author { color: var(--fog-dark-text-light, #9ca3af); } + + .metadata-tags { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--fog-border, #e5e7eb); + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + :global(.dark) .metadata-tags { + border-top-color: var(--fog-dark-border, #374151); + } + + .metadata-tag { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: baseline; + font-size: 0.875rem; + } + + .metadata-tag-name { + font-weight: 600; + color: var(--fog-text, #1f2937); + } + + :global(.dark) .metadata-tag-name { + color: var(--fog-dark-text, #f9fafb); + } + + .metadata-tag-value { + color: var(--fog-text-light, #6b7280); + font-family: monospace; + word-break: break-all; + } + + :global(.dark) .metadata-tag-value { + color: var(--fog-dark-text-light, #9ca3af); + } diff --git a/src/lib/components/content/RichTextEditor.svelte b/src/lib/components/content/RichTextEditor.svelte index 0bcebab..208ebb4 100644 --- a/src/lib/components/content/RichTextEditor.svelte +++ b/src/lib/components/content/RichTextEditor.svelte @@ -5,6 +5,7 @@ import MentionsAutocomplete from './MentionsAutocomplete.svelte'; import GifPicker from './GifPicker.svelte'; import EmojiPicker from './EmojiPicker.svelte'; + import Icon from '../ui/Icon.svelte'; interface Props { value: string; @@ -174,7 +175,7 @@ aria-label="Insert GIF" {disabled} > - GIF + - 📤 + {/if} diff --git a/src/lib/components/find/SearchAddressableEvents.svelte b/src/lib/components/find/SearchAddressableEvents.svelte index 9fc646f..87d73c4 100644 --- a/src/lib/components/find/SearchAddressableEvents.svelte +++ b/src/lib/components/find/SearchAddressableEvents.svelte @@ -5,7 +5,7 @@ import ProfileBadge from '../layout/ProfileBadge.svelte'; import RelayBadge from '../layout/RelayBadge.svelte'; import CacheBadge from '../layout/CacheBadge.svelte'; - import { getKindInfo } from '../../types/kind-lookup.js'; + import { getKindInfo, KIND_LOOKUP, isParameterizedReplaceableKind } from '../../types/kind-lookup.js'; import { nip19 } from 'nostr-tools'; import { goto } from '$app/navigation'; import type { NostrEvent } from '../../types/nostr.js'; @@ -21,6 +21,11 @@ const searchTags = ['d', 'T', 'C', 'title', 'author', 'summary', 'description']; const SEARCH_TIMEOUT = 10000; // 10 seconds const CACHE_SEARCH_DEBOUNCE = 500; // 500ms debounce for cache search + + // Get only the parameterized replaceable kinds that are actually defined (computed once) + const parameterizedKinds = Object.keys(KIND_LOOKUP) + .map(Number) + .filter(kind => isParameterizedReplaceableKind(kind)); // Map to track results by event ID to avoid duplicates const resultsMap = new Map(); @@ -154,15 +159,10 @@ } const queryLower = query.toLowerCase(); - // Fetch all cached events in kind range 30000-39999 - const kinds: number[] = []; - for (let kind = 30000; kind <= 39999; kind++) { - kinds.push(kind); - } // Get all cached events for these kinds const allCachedEvents: NostrEvent[] = []; - for (const kind of kinds) { + for (const kind of parameterizedKinds) { const cached = await getEventsByKind(kind, 1000); // Get up to 1000 per kind allCachedEvents.push(...cached); } @@ -179,24 +179,39 @@ } // Debounced cache search + let isSearchingCache = $state(false); + $effect(() => { + // Only react to searchQuery changes, not cacheResults + const query = searchQuery.trim(); + // Clear previous timeout if (cacheSearchTimeoutId) { clearTimeout(cacheSearchTimeoutId); + cacheSearchTimeoutId = null; } - if (searchQuery.trim()) { + if (query) { cacheSearchTimeoutId = setTimeout(() => { - searchCache(); + if (!isSearchingCache) { + isSearchingCache = true; + searchCache().finally(() => { + isSearchingCache = false; + }); + } }, CACHE_SEARCH_DEBOUNCE); } else { - cacheResults = []; - cacheResultsMap.clear(); + // Only clear if we're not currently searching + if (!isSearchingCache) { + cacheResults = []; + cacheResultsMap.clear(); + } } return () => { if (cacheSearchTimeoutId) { clearTimeout(cacheSearchTimeoutId); + cacheSearchTimeoutId = null; } }; }); @@ -268,17 +283,58 @@ } const queryLower = query.toLowerCase(); - // Fetch all events in kind range 30000-39999 - const kinds: number[] = []; - for (let kind = 30000; kind <= 39999; kind++) { - kinds.push(kind); - } - + // Normalize query: remove spaces, handle hyphens + const normalizedQuery = queryLower.replace(/\s+/g, '-'); + const queryWords = queryLower.split(/[\s-]+/).filter(w => w.length > 0); + const relays = relayManager.getProfileReadRelays(); + const allQueries: any[] = []; + + // Build queries with tag filters for better search results + // Only query the specific parameterized replaceable kinds we know about + + // Search by d-tag (most specific) - try both original and normalized + // Also try each word as a potential d-tag match + allQueries.push( + { kinds: parameterizedKinds, '#d': [queryLower], limit: 50 }, + { kinds: parameterizedKinds, '#d': [normalizedQuery], limit: 50 } + ); + for (const word of queryWords) { + if (word.length > 2) { // Only search for words longer than 2 chars + allQueries.push({ kinds: parameterizedKinds, '#d': [word], limit: 50 }); + } + } + + // Search by T-tag (topics/tags) for each word + for (const word of queryWords) { + if (word.length > 0) { + allQueries.push({ kinds: parameterizedKinds, '#T': [word], limit: 50 }); + } + } + + // Search by C-tag (categories) for each word + for (const word of queryWords) { + if (word.length > 0) { + allQueries.push({ kinds: parameterizedKinds, '#C': [word], limit: 50 }); + } + } + + // Search by title tag + allQueries.push({ kinds: parameterizedKinds, '#title': [queryLower], limit: 50 }); + for (const word of queryWords) { + if (word.length > 0) { + allQueries.push({ kinds: parameterizedKinds, '#title': [word], limit: 50 }); + } + } + + // Also do a broader search without tag filters to catch partial matches + // This will find events where the query appears in d-tag but not as exact match + allQueries.push({ kinds: parameterizedKinds, limit: 200 }); + // Use onUpdateWithRelay to process events as they arrive with relay info await nostrClient.fetchEvents( - [{ kinds, limit: 100 }], + allQueries, relays, { useCache: 'cache-first', @@ -366,7 +422,6 @@ cacheResults = []; resultsMap.clear(); cacheResultsMap.clear(); - eventRelayMap.clear(); searching = false; if (timeoutId) { clearTimeout(timeoutId); @@ -377,6 +432,10 @@ cacheSearchTimeoutId = null; } } + + export function hasActiveSearch(): boolean { + return searching || searchQuery.trim().length > 0 || results.length > 0 || cacheResults.length > 0; + }