Browse Source

bug-fixes

imwald
Silberengel 7 days ago
parent
commit
15905310fc
  1. 16
      src/hooks/useLibraryPublications.ts
  2. 1
      src/i18n/locales/de.ts
  3. 1
      src/i18n/locales/en.ts
  4. 17
      src/lib/library-publication-index.test.ts
  5. 17
      src/lib/library-publication-index.ts

16
src/hooks/useLibraryPublications.ts

@ -23,6 +23,7 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import type { Event } from 'nostr-tools' import type { Event } from 'nostr-tools'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
const SEARCH_DEBOUNCE_MS = 300 const SEARCH_DEBOUNCE_MS = 300
const RELAY_SEARCH_TIMEOUT_MS = 30_000 const RELAY_SEARCH_TIMEOUT_MS = 30_000
@ -53,6 +54,7 @@ const EMPTY_ENGAGEMENT: PublicationEngagementMaps = {
const EMPTY_BOOKLIST_TARGETS = { addresses: new Set<string>(), eventIds: new Set<string>() } const EMPTY_BOOKLIST_TARGETS = { addresses: new Set<string>(), eventIds: new Set<string>() }
export function useLibraryPublications(isActive: boolean) { export function useLibraryPublications(isActive: boolean) {
const { t } = useTranslation()
const { pubkey, bookmarkListEvent } = useNostr() const { pubkey, bookmarkListEvent } = useNostr()
const { favoriteRelays, blockedRelays } = useFavoriteRelays() const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const [entries, setEntries] = useState<LibraryPublicationEntry[]>([]) const [entries, setEntries] = useState<LibraryPublicationEntry[]>([])
@ -374,14 +376,22 @@ export function useLibraryPublications(isActive: boolean) {
setSearchResults(entries) setSearchResults(entries)
} catch (e) { } catch (e) {
const message = e instanceof Error ? e.message : 'Relay search failed' const message = e instanceof Error ? e.message : 'Relay search failed'
setError(message) const local = await searchLibraryPublications(q, { indexEvents, engagement }, searchAxis)
if (local.length > 0) {
setSearchResults(local)
setError(null)
} else {
setError(
message === 'Relay search timed out' ? t('Library relay search timed out') : message
)
}
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
logger.warn('[Library] relay search failed', { message }) logger.warn('[Library] relay search failed', { message, localFallback: local.length })
} }
} finally { } finally {
setRelaySearchLoading(false) setRelaySearchLoading(false)
} }
}, [searchQuery, searchAxis, pubkey, indexEvents, engagement, blockedRelays]) }, [searchQuery, searchAxis, pubkey, indexEvents, engagement, blockedRelays, t])
const mineFilterOpts = useMemo( const mineFilterOpts = useMemo(
() => ({ () => ({

1
src/i18n/locales/de.ts

@ -1658,6 +1658,7 @@ export default {
'Library search scope author': 'Suche nach Autor', 'Library search scope author': 'Suche nach Autor',
'Library search scope dtag': 'Suche nach d-Tag', 'Library search scope dtag': 'Suche nach d-Tag',
'Library search commit hint': 'Enter drücken oder einen Suchtyp auswählen', 'Library search commit hint': 'Enter drücken oder einen Suchtyp auswählen',
'Library relay search timed out': 'Relay-Suche abgelaufen. Treffer aus dem lokalen Bibliotheks-Cache werden angezeigt, sofern vorhanden.',
'Library show only my publications': 'Meine Publikationen', 'Library show only my publications': 'Meine Publikationen',
'Library empty': 'Noch keine Publikationen auf deinen Relays gefunden.', 'Library empty': 'Noch keine Publikationen auf deinen Relays gefunden.',
'Library empty filtered': 'Keine Publikationen entsprechen den Filtern.', 'Library empty filtered': 'Keine Publikationen entsprechen den Filtern.',

1
src/i18n/locales/en.ts

@ -1681,6 +1681,7 @@ export default {
'Library search scope author': 'Searching by author', 'Library search scope author': 'Searching by author',
'Library search scope dtag': 'Searching by d-tag', 'Library search scope dtag': 'Searching by d-tag',
'Library search commit hint': 'Press Enter or choose a search type below', 'Library search commit hint': 'Press Enter or choose a search type below',
'Library relay search timed out': 'Relay search timed out. Showing matches from your local library cache when available.',
'Library show only my publications': 'My publications', 'Library show only my publications': 'My publications',
'Library empty': 'No publications found on your relays yet.', 'Library empty': 'No publications found on your relays yet.',
'Library empty filtered': 'No publications match your filters.', 'Library empty filtered': 'No publications match your filters.',

17
src/lib/library-publication-index.test.ts

@ -282,7 +282,24 @@ describe('library-publication-index', () => {
expect(byAuthor).toHaveLength(1) expect(byAuthor).toHaveLength(1)
expect(filterEventsForPublicationRelaySearchAxis([root], 'title', 'charlotte')).toHaveLength(0) expect(filterEventsForPublicationRelaySearchAxis([root], 'title', 'charlotte')).toHaveLength(0)
expect(filterEventsForPublicationRelaySearchAxis([root], 'author', 'charlotte')).toHaveLength(1)
expect(publicationMetadataTagMatchesQuery(root, 'title', 'Jane Eyre')).toBe(true) expect(publicationMetadataTagMatchesQuery(root, 'title', 'Jane Eyre')).toBe(true)
expect(publicationMetadataTagMatchesQuery(root, 'author', 'Brontë')).toBe(true)
})
it('author and title axes match partial metadata text but d-tag stays exact', () => {
const root = indexEvent('faust', [`30041:${PK}:intro`])
root.tags = [
['d', 'faust-part-one'],
['title', 'Faust: Der Tragödie erster Teil'],
['author', 'Johann Wolfgang von Goethe'],
['a', `30041:${PK}:intro`]
]
expect(publicationMetadataTagMatchesQuery(root, 'author', 'goethe')).toBe(true)
expect(publicationMetadataTagMatchesQuery(root, 'title', 'tragödie')).toBe(true)
expect(publicationMetadataTagMatchesQuery(root, 'd', 'faust')).toBe(false)
expect(publicationMetadataTagMatchesQuery(root, 'd', 'faust-part-one')).toBe(true)
}) })
it('searchLibraryPublications respects author axis and keeps separate cache keys', async () => { it('searchLibraryPublications respects author axis and keeps separate cache keys', async () => {

17
src/lib/library-publication-index.ts

@ -1857,7 +1857,7 @@ export function publicationQueryDTagVariants(query: string): string[] {
return [...seen] return [...seen]
} }
/** Normalized needles for exact publication metadata tag match (d / title / author). */ /** Normalized needles for publication metadata tag match (d / title / author). */
export function publicationQueryNeedles(query: string): string[] { export function publicationQueryNeedles(query: string): string[] {
const raw = normalizeGeneralSearchQuery(query.trim()) const raw = normalizeGeneralSearchQuery(query.trim())
if (!raw) return [] if (!raw) return []
@ -1870,13 +1870,19 @@ export function publicationQueryNeedles(query: string): string[] {
return [...new Set([lower, normalized, hyphen].filter(Boolean))] return [...new Set([lower, normalized, hyphen].filter(Boolean))]
} }
function publicationTagValueMatchesNeedles(tagValue: string, needles: string[]): boolean { function publicationTagValueMatchesNeedles(
tagValue: string,
needles: string[],
exactOnly: boolean
): boolean {
const val = tagValue.trim().toLowerCase() const val = tagValue.trim().toLowerCase()
const valSpaced = val.replace(/-/g, ' ').replace(/\s+/g, ' ').trim() const valSpaced = val.replace(/-/g, ' ').replace(/\s+/g, ' ').trim()
for (const needle of needles) { for (const needle of needles) {
if (val === needle) return true if (!needle) continue
const needleSpaced = needle.replace(/-/g, ' ').replace(/\s+/g, ' ').trim() const needleSpaced = needle.replace(/-/g, ' ').replace(/\s+/g, ' ').trim()
if (valSpaced === needleSpaced) return true if (val === needle || valSpaced === needleSpaced) return true
if (exactOnly || needle.length < 2) continue
if (val.includes(needle) || valSpaced.includes(needleSpaced)) return true
} }
return false return false
} }
@ -1888,10 +1894,11 @@ export function publicationMetadataTagMatchesQuery(
): boolean { ): boolean {
const needles = publicationQueryNeedles(query) const needles = publicationQueryNeedles(query)
if (needles.length === 0) return false if (needles.length === 0) return false
const exactOnly = tagName === 'd'
for (const tag of event.tags ?? []) { for (const tag of event.tags ?? []) {
if ((tag[0] || '').toLowerCase() !== tagName) continue if ((tag[0] || '').toLowerCase() !== tagName) continue
const value = tag[1]?.trim() const value = tag[1]?.trim()
if (value && publicationTagValueMatchesNeedles(value, needles)) return true if (value && publicationTagValueMatchesNeedles(value, needles, exactOnly)) return true
} }
return false return false
} }

Loading…
Cancel
Save