From 77e129de7ab9002eec91e735e706ebd2a8c42308 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 23 Mar 2026 22:04:01 +0100 Subject: [PATCH] bug-fixes --- package-lock.json | 4 +- package.json | 2 +- src/components/Note/NotificationEventCard.tsx | 40 +++------- src/components/Note/index.tsx | 40 ++++++++-- src/components/SearchBar/index.tsx | 77 +++++++++++++++---- src/components/ui/select.tsx | 2 +- src/i18n/locales/de.ts | 2 +- src/i18n/locales/en.ts | 2 +- src/pages/primary/SpellsPage/index.tsx | 2 + 9 files changed, 114 insertions(+), 57 deletions(-) diff --git a/package-lock.json b/package-lock.json index cd570f65..67f4f81f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jumble-imwald", - "version": "19.3.1", + "version": "19.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jumble-imwald", - "version": "19.3.1", + "version": "19.3.3", "license": "MIT", "dependencies": { "@asciidoctor/core": "^3.0.4", diff --git a/package.json b/package.json index c9e91a93..03108a21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jumble-imwald", - "version": "19.3.1", + "version": "19.3.3", "description": "A user-friendly Nostr client focused on relay feed browsing and relay discovery, forked from Jumble", "private": true, "type": "module", diff --git a/src/components/Note/NotificationEventCard.tsx b/src/components/Note/NotificationEventCard.tsx index c55f9191..444bc5eb 100644 --- a/src/components/Note/NotificationEventCard.tsx +++ b/src/components/Note/NotificationEventCard.tsx @@ -1,42 +1,24 @@ import { ExtendedKind } from '@/constants' import { cn } from '@/lib/utils' import { Event, kinds } from 'nostr-tools' -import { useMemo } from 'react' import { useTranslation } from 'react-i18next' +/** Reaction `content` as display emoji/text (NIP-25); empty content → heart. */ +export function reactionDisplayEmoji(event: Event): string { + if (event.kind !== kinds.Reaction) return '' + const raw = event.content?.trim() ?? '' + if (!raw) return '❤️' + if (raw.length > 64) return `${raw.slice(0, 64)}…` + return raw +} + /** - * Compact card for interaction events in notification-style feeds (reactions, boosts, poll votes). - * The surrounding {@link Note} row still shows author + {@link ParentNotePreview} for the target. + * Compact card for interaction events in notification-style feeds (boosts, poll votes). + * Reactions use a one-line header in {@link Note} (emoji + user + blurb) instead of this card. */ export default function NotificationEventCard({ event, className }: { event: Event; className?: string }) { const { t } = useTranslation() - const reactionDisplay = useMemo(() => { - if (event.kind !== kinds.Reaction) return null - const raw = event.content?.trim() ?? '' - if (!raw) return '❤️' - if (raw.length > 64) return `${raw.slice(0, 64)}…` - return raw - }, [event.content, event.kind]) - - if (event.kind === kinds.Reaction) { - return ( -
-
- - {reactionDisplay} - -

{t('Notification reaction summary')}

-
-
- ) - } - if (event.kind === kinds.Repost) { return (
setShowMuted(true)} /> } else if (!defaultShowNsfw && isNsfwEvent(event) && !showNsfw) { content = setShowNsfw(true)} /> - } else if ( - event.kind === kinds.Reaction || - event.kind === kinds.Repost || - event.kind === ExtendedKind.POLL_RESPONSE - ) { + } else if (event.kind === kinds.Reaction) { + content = null + } else if (event.kind === kinds.Repost || event.kind === ExtendedKind.POLL_RESPONSE) { content = } else if (event.kind === kinds.Highlights) { // Try to render the Highlight component with error boundary @@ -275,8 +273,34 @@ export default function Note({ }} >
-
- {isSyntheticRssParent ? ( +
+ {event.kind === kinds.Reaction ? ( +
+ + {reactionDisplayEmoji(event)} + + +
+ + + + {t('Notification reaction summary')} + +
+ +
+ ) : isSyntheticRssParent ? ( <>
([]) const [selectedIndex, setSelectedIndex] = useState(-1) const searchInputRef = useRef(null) + const barContainerRef = useRef(null) + const [suggestPanelTop, setSuggestPanelTop] = useState(0) const normalizedUrl = useMemo(() => { if (['w', 'ws', 'ws:', 'ws:/', 'wss', 'wss:', 'wss:/'].includes(input)) { return undefined @@ -260,6 +263,24 @@ const SearchBar = forwardRef< } }, [displayList, list]) + const updateSuggestPanelGeometry = useCallback(() => { + const el = barContainerRef.current + if (!el) return + setSuggestPanelTop(el.getBoundingClientRect().bottom) + }, []) + + useLayoutEffect(() => { + if (!displayList || !list || !isSmallScreen) return + updateSuggestPanelGeometry() + const onScrollOrResize = () => updateSuggestPanelGeometry() + window.addEventListener('scroll', onScrollOrResize, true) + window.addEventListener('resize', onScrollOrResize) + return () => { + window.removeEventListener('scroll', onScrollOrResize, true) + window.removeEventListener('resize', onScrollOrResize) + } + }, [displayList, list, isSmallScreen, input, updateSuggestPanelGeometry]) + const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'Enter') { @@ -298,29 +319,57 @@ const SearchBar = forwardRef< [input, onSearch, selectableOptions, selectedIndex] ) + const suggestTopPx = Math.max(0, suggestPanelTop - 4) + const suggestionsPanel = list ? ( +
e.preventDefault()} + > +
{list}
+
+ ) : null + return ( -
- {displayList && list && ( +
+ {displayList && list && !isSmallScreen && ( <> + {suggestionsPanel}
e.preventDefault()} - > -
{list}
-
-
blur()} /> + className="fixed inset-0 z-40 w-full h-full" + onClick={() => blur()} + aria-hidden + /> + + )} + {displayList && list && isSmallScreen && ( + <> +
blur()} + aria-hidden + /> + {suggestionsPanel} )} {children} diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index 846b806c..4c2fb347 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -376,7 +376,7 @@ export default { Topics: 'Themen', 'Open in a': 'Öffnen in {{a}}', 'Cannot handle event of kind k': 'Ereignis des Typs {{k}} kann nicht verarbeitet werden', - 'Notification reaction summary': 'Hat auf die Notiz darüber reagiert.', + 'Notification reaction summary': 'hat auf diese Notiz reagiert.', 'Notification boost summary': 'Hat diese Notiz geboostet', 'Notification boost detail': 'Die Vorschau darüber ist der Originalbeitrag.', 'Notification poll vote summary': 'Hat an der Umfrage darüber teilgenommen.', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 293e72eb..80b0b148 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -369,7 +369,7 @@ export default { Topics: 'Topics', 'Open in a': 'Open in {{a}}', 'Cannot handle event of kind k': 'Cannot handle event of kind {{k}}', - 'Notification reaction summary': 'Reacted to the note above.', + 'Notification reaction summary': 'reacted to this note.', 'Notification boost summary': 'Boosted this note', 'Notification boost detail': 'The preview above is the original post.', 'Notification poll vote summary': 'Voted on the poll above.', diff --git a/src/pages/primary/SpellsPage/index.tsx b/src/pages/primary/SpellsPage/index.tsx index 26fd878c..5d4d3e25 100644 --- a/src/pages/primary/SpellsPage/index.tsx +++ b/src/pages/primary/SpellsPage/index.tsx @@ -320,6 +320,8 @@ const SpellsPage = forwardRef(function SpellsPage( setSelectedSpell(null) } else { urlFauxSpellInstrumentedRef.current = null + // URL / props no longer name a faux spell (e.g. bottom bar “Spells” → `/spells`) — leave the feed. + setSelectedFauxSpell(null) } }, [spellProp, logSpellFeedPickerSelection])