From 3598aa3085f89f47c5d857275a09997d3badc578 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 21 Mar 2026 17:50:58 +0100 Subject: [PATCH] bug-fixes --- nip66-cron/index.mjs | 1 - src/PageManager.tsx | 18 +- .../CreateWalletGuideToast/index.tsx | 2 +- src/components/Embedded/EmbeddedNote.tsx | 155 +++++++++++++++++- src/components/Note/index.tsx | 22 +-- .../PostEditor/PostRelaySelector.tsx | 45 ++++- .../TooManyRelaysAlertDialog/index.tsx | 2 +- src/components/WebPreview/index.tsx | 28 +++- src/constants.ts | 1 - src/contexts/secondary-page-context.tsx | 23 +++ src/hooks/useFetchWebMetadata.tsx | 11 +- src/i18n/locales/ar.ts | 8 + src/i18n/locales/de.ts | 8 + src/i18n/locales/en.ts | 8 + src/i18n/locales/es.ts | 8 + src/i18n/locales/fa.ts | 8 + src/i18n/locales/fr.ts | 8 + src/i18n/locales/hi.ts | 8 + src/i18n/locales/it.ts | 8 + src/i18n/locales/ja.ts | 8 + src/i18n/locales/ko.ts | 8 + src/i18n/locales/pl.ts | 8 + src/i18n/locales/pt-BR.ts | 8 + src/i18n/locales/pt-PT.ts | 8 + src/i18n/locales/ru.ts | 8 + src/i18n/locales/th.ts | 8 + src/i18n/locales/zh.ts | 8 + src/lib/note-renderable-kinds.ts | 23 +++ 28 files changed, 404 insertions(+), 55 deletions(-) create mode 100644 src/contexts/secondary-page-context.tsx create mode 100644 src/lib/note-renderable-kinds.ts diff --git a/nip66-cron/index.mjs b/nip66-cron/index.mjs index dc6c2ca0..0dac4c59 100644 --- a/nip66-cron/index.mjs +++ b/nip66-cron/index.mjs @@ -64,7 +64,6 @@ const DEFAULT_RELAYS_TO_MONITOR = [ 'wss://relay.nsec.app', 'wss://bucket.coracle.social', 'wss://spatia-arcana.com', - 'wss://sendit.nosflare.com', 'wss://nostr-pub.wellorder.net', 'wss://pyramid.fiatjaf.com/', 'wss://nostr.lopp.social/', diff --git a/src/PageManager.tsx b/src/PageManager.tsx index a2dc0c4e..b4c187ec 100644 --- a/src/PageManager.tsx +++ b/src/PageManager.tsx @@ -42,6 +42,7 @@ import { normalizeUrl } from './lib/url' import modalManager from './services/modal-manager.service' import { routes } from './routes' import { useScreenSize } from './providers/ScreenSizeProvider' +import { SecondaryPageContext, useSecondaryPage } from '@/contexts/secondary-page-context' /** Lazy-loaded so PageManager does not synchronously import SpellsPage (avoids HMR cycle: SpellsPage → PrimaryPageLayout → PageManager → SpellsPage). */ const SpellsPageLazy = lazy(() => import('./pages/primary/SpellsPage')) @@ -78,13 +79,6 @@ type TPrimaryPageContext = { display: boolean } -type TSecondaryPageContext = { - push: (url: string) => void - pop: () => void - currentIndex: number - navigateToPrimaryPage: (page: TPrimaryPageName, props?: object) => void -} - type TStackItem = { index: number url: string @@ -203,8 +197,6 @@ function mergePrimaryPageEntry( export const PrimaryPageContext = createContext(undefined) -const SecondaryPageContext = createContext(undefined) - const PrimaryNoteViewContext = createContext<{ setPrimaryNoteView: (view: ReactNode | null, type?: 'note' | 'settings' | 'settings-sub' | 'profile' | 'hashtag' | 'relay' | 'following' | 'mute' | 'others-relay-settings') => void primaryViewType: 'note' | 'settings' | 'settings-sub' | 'profile' | 'hashtag' | 'relay' | 'following' | 'mute' | 'others-relay-settings' | null @@ -228,13 +220,7 @@ export function usePrimaryPage() { return context } -export function useSecondaryPage() { - const context = useContext(SecondaryPageContext) - if (!context) { - throw new Error('useSecondaryPage must be used within a SecondaryPageContext.Provider') - } - return context -} +export { useSecondaryPage } export function usePrimaryNoteView() { const context = useContext(PrimaryNoteViewContext) diff --git a/src/components/CreateWalletGuideToast/index.tsx b/src/components/CreateWalletGuideToast/index.tsx index 3a6aae5d..7d485f58 100644 --- a/src/components/CreateWalletGuideToast/index.tsx +++ b/src/components/CreateWalletGuideToast/index.tsx @@ -1,5 +1,5 @@ import { toWallet } from '@/lib/link' -import { useSecondaryPage } from '@/PageManager' +import { useSecondaryPage } from '@/contexts/secondary-page-context' import { useNostr } from '@/providers/NostrProvider' import storage from '@/services/local-storage.service' import { useEffect } from 'react' diff --git a/src/components/Embedded/EmbeddedNote.tsx b/src/components/Embedded/EmbeddedNote.tsx index 6776785b..852e78ad 100644 --- a/src/components/Embedded/EmbeddedNote.tsx +++ b/src/components/Embedded/EmbeddedNote.tsx @@ -1,14 +1,16 @@ import { Skeleton } from '@/components/ui/skeleton' import { FAST_READ_RELAY_URLS, SEARCHABLE_RELAY_URLS, ExtendedKind } from '@/constants' +import { isRenderableNoteKind } from '@/lib/note-renderable-kinds' import { useFetchEvent } from '@/hooks' import { normalizeUrl } from '@/lib/url' import { cn } from '@/lib/utils' import client from '@/services/client.service' import { useTranslation } from 'react-i18next' -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Event, nip19 } from 'nostr-tools' import ClientSelect from '../ClientSelect' import MainNoteCard from '../NoteCard/MainNoteCard' +import UnknownNote from '../Note/UnknownNote' import { Button } from '../ui/button' import { EmbeddedCalendarEvent } from './EmbeddedCalendarEvent' import { Search } from 'lucide-react' @@ -44,14 +46,140 @@ function canSearchOnExternalRelays(noteId: string): boolean { } } -export function EmbeddedNote({ - noteId, +export type EmbeddedNoteIdValidation = + | { valid: true } + | { + valid: false + reason: 'empty' | 'invalid_hex' | 'invalid_bech32' | 'wrong_nip19_type' + decodedType?: string + } + +/** + * Only hex (64), note1, nevent1, and naddr1 are valid embedded note targets. + * Malformed bech32, wrong kinds (npub, …), or bad hex length fail before fetch/search UI. + */ +export function validateEmbeddedNotePointer(noteId: string): EmbeddedNoteIdValidation { + const s = noteId.trim() + if (!s) return { valid: false, reason: 'empty' } + + if (/^[0-9a-f]{64}$/i.test(s)) return { valid: true } + + if (/^[0-9a-f]+$/i.test(s)) { + return { valid: false, reason: 'invalid_hex' } + } + + const looksLikeNostrBech32 = + s.startsWith('n') && s.includes('1') && /^[a-z0-9]+$/i.test(s) && s.length >= 10 + + if (looksLikeNostrBech32) { + try { + const { type } = nip19.decode(s) + if (type === 'note' || type === 'nevent' || type === 'naddr') return { valid: true } + return { valid: false, reason: 'wrong_nip19_type', decodedType: type } + } catch { + return { valid: false, reason: 'invalid_bech32' } + } + } + + try { + const { type } = nip19.decode(s) + if (type === 'note' || type === 'nevent' || type === 'naddr') return { valid: true } + return { valid: false, reason: 'wrong_nip19_type', decodedType: type } + } catch { + return { valid: false, reason: 'invalid_bech32' } + } +} + +export function EmbeddedNote({ + noteId, className, - containingEvent -}: { + containingEvent +}: { noteId: string className?: string - containingEvent?: Event // Event that contains this embedded note - use its author's relays and relay hints + containingEvent?: Event +}) { + const validation = useMemo(() => validateEmbeddedNotePointer(noteId), [noteId]) + if (!validation.valid) { + return ( + + ) + } + return ( + + ) +} + +function EmbeddedNoteInvalid({ + noteId, + className, + validation +}: { + noteId: string + className?: string + validation: Exclude +}) { + const { t } = useTranslation() + const trimmed = noteId.trim() + const isNsecLike = /^nsec1/i.test(trimmed) || validation.decodedType === 'nsec' + const preview = + trimmed.length > 96 ? `${trimmed.slice(0, 96)}…` : trimmed || '—' + + let message: string + switch (validation.reason) { + case 'empty': + message = t('embeddedNoteInvalidEmpty') + break + case 'invalid_hex': + message = t('embeddedNoteInvalidHex') + break + case 'wrong_nip19_type': + message = t('embeddedNoteInvalidWrongKind', { + type: validation.decodedType ?? 'unknown' + }) + break + case 'invalid_bech32': + default: + message = t('embeddedNoteInvalidBech32') + break + } + + return ( +
e.stopPropagation()} + data-embedded-note-invalid + > +
+
{t('Invalid embedded note reference')}
+

{message}

+ {validation.reason !== 'empty' && !isNsecLike && ( +
+            {preview}
+          
+ )} + +
+
+ ) +} + +function EmbeddedNoteContent({ + noteId, + className, + containingEvent +}: { + noteId: string + className?: string + containingEvent?: Event }) { const { event, isFetching } = useFetchEvent(noteId) const [retryEvent, setRetryEvent] = useState(undefined) @@ -118,6 +246,21 @@ export function EmbeddedNote({ ) } + if (!isRenderableNoteKind(finalEvent.kind)) { + return ( +
e.stopPropagation()} + > + +
+ ) + } + // Otherwise, render as regular embedded note return (
e.stopPropagation()}> diff --git a/src/components/Note/index.tsx b/src/components/Note/index.tsx index 712423f0..c9c6696b 100644 --- a/src/components/Note/index.tsx +++ b/src/components/Note/index.tsx @@ -1,5 +1,6 @@ import { useSmartNoteNavigation } from '@/PageManager' -import { ExtendedKind, SUPPORTED_KINDS } from '@/constants' +import { ExtendedKind } from '@/constants' +import { isRenderableNoteKind } from '@/lib/note-renderable-kinds' import { getParentBech32Id, isNsfwEvent } from '@/lib/event' import { toNote } from '@/lib/link' import logger from '@/lib/logger' @@ -117,24 +118,7 @@ export default function Note({ let content: React.ReactNode - const supportedKindsList = [ - ...SUPPORTED_KINDS, - kinds.CommunityDefinition, - kinds.LiveEvent, - ExtendedKind.GROUP_METADATA, - ExtendedKind.PUBLIC_MESSAGE, - ExtendedKind.ZAP_REQUEST, - ExtendedKind.ZAP_RECEIPT, - ExtendedKind.PUBLICATION_CONTENT, // Only for rendering embedded content, not in feeds - ExtendedKind.FOLLOW_PACK, // Follow-pack feed + embedded previews - ExtendedKind.CITATION_INTERNAL, // Citations for rendering - ExtendedKind.CITATION_EXTERNAL, - ExtendedKind.CITATION_HARDCOPY, - ExtendedKind.CITATION_PROMPT - ] - - - if (!supportedKindsList.includes(event.kind)) { + if (!isRenderableNoteKind(event.kind)) { logger.debug('Note component - rendering UnknownNote for unsupported kind:', event.kind) content = } else if (mutePubkeySet.has(event.pubkey) && !showMuted) { diff --git a/src/components/PostEditor/PostRelaySelector.tsx b/src/components/PostEditor/PostRelaySelector.tsx index 743227a7..02591877 100644 --- a/src/components/PostEditor/PostRelaySelector.tsx +++ b/src/components/PostEditor/PostRelaySelector.tsx @@ -1,3 +1,4 @@ +import { NOSTR_URI_FOR_REPLY_PUBKEYS_REGEX } from '@/lib/content-patterns' import { simplifyUrl, isLocalNetworkUrl, normalizeUrl } from '@/lib/url' import { useCurrentRelays } from '@/providers/CurrentRelaysProvider' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' @@ -71,6 +72,21 @@ export default function PostRelaySelector({ return false }, [_parentEvent]) + /** + * Relay selection only cares about nostr:… mentions in the draft (see relay-selection.service). + * Depending on full `postContent` re-ran the heavy relay effect on every keystroke. + */ + const contentRelaySignature = useMemo(() => { + if (isDiscussionReply) return '' + if (isPublicMessage && mentions.length > 0) { + // PM recipients come from `mentions` when set; content is ignored by selection service + return '' + } + const matches = [...postContent.matchAll(NOSTR_URI_FOR_REPLY_PUBKEYS_REGEX)].map((m) => m[0]) + if (!matches.length) return '' + return [...new Set(matches)].sort().join('\n') + }, [postContent, isDiscussionReply, isPublicMessage, mentions]) + // Memoize arrays to prevent unnecessary re-renders const memoizedFavoriteRelays = useMemo(() => favoriteRelays, [favoriteRelays]) const memoizedBlockedRelays = useMemo(() => blockedRelays, [blockedRelays]) @@ -164,7 +180,19 @@ export default function PostRelaySelector({ } updateRelaySelection() - }, [memoizedOpenFrom, _parentEvent, memoizedFavoriteRelays, memoizedBlockedRelays, memoizedRelaySets, isPublicMessage, pubkey, relayList, isDiscussionReply, postContent, mentions]) + }, [ + memoizedOpenFrom, + _parentEvent, + memoizedFavoriteRelays, + memoizedBlockedRelays, + memoizedRelaySets, + isPublicMessage, + pubkey, + relayList, + isDiscussionReply, + contentRelaySignature, + mentions + ]) // Separate effect for mention changes in non-discussion replies useEffect(() => { @@ -253,7 +281,20 @@ export default function PostRelaySelector({ updateRelaySelection() } - }, [mentions, isDiscussionReply, memoizedFavoriteRelays, memoizedBlockedRelays, memoizedRelaySets, _parentEvent, isPublicMessage, pubkey, relayList, memoizedOpenFrom, previousSelectableCount, hasManualSelection, postContent]) + }, [ + mentions, + isDiscussionReply, + memoizedFavoriteRelays, + memoizedBlockedRelays, + memoizedRelaySets, + _parentEvent, + isPublicMessage, + pubkey, + relayList, + memoizedOpenFrom, + previousSelectableCount, + hasManualSelection + ]) // Update description when selected relays change due to manual selection useEffect(() => { diff --git a/src/components/TooManyRelaysAlertDialog/index.tsx b/src/components/TooManyRelaysAlertDialog/index.tsx index 18c23b02..7d3672c2 100644 --- a/src/components/TooManyRelaysAlertDialog/index.tsx +++ b/src/components/TooManyRelaysAlertDialog/index.tsx @@ -16,7 +16,7 @@ import { DrawerTitle } from '@/components/ui/drawer' import { toRelaySettings } from '@/lib/link' -import { useSecondaryPage } from '@/PageManager' +import { useSecondaryPage } from '@/contexts/secondary-page-context' import { useNostr } from '@/providers/NostrProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import storage from '@/services/local-storage.service' diff --git a/src/components/WebPreview/index.tsx b/src/components/WebPreview/index.tsx index 84efdb97..fce47315 100644 --- a/src/components/WebPreview/index.tsx +++ b/src/components/WebPreview/index.tsx @@ -7,6 +7,7 @@ import { extractBookMetadata } from '@/lib/bookstr-parser' import { cn } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' +import { Skeleton } from '@/components/ui/skeleton' import { ExternalLink } from 'lucide-react' import { nip19, kinds } from 'nostr-tools' import { useMemo, useEffect, useState } from 'react' @@ -459,15 +460,28 @@ export default function WebPreview({ url, className }: { url: string; className? return null } - // Always try to fetch OG data for standalone hyperlinks (except internal jumble links) - // Check if we have any opengraph data (title, description, or image) + // Prefer the page's own Open Graph / meta when the fetch returns anything useful. const hasOpengraphData = !isInternalJumbleLink && (title || description || image) - // Show enhanced fallback link card if: - // 1. No OG data available, OR - // 2. A nostr identifier was detected (we want to show the detailed nostr card even with OG data) - // Note: We always attempt to fetch OG data via useFetchWebMetadata hook above - if (!hasOpengraphData || nostrIdentifier) { + // While OG is loading for external URLs, avoid flashing the nostr / hostname fallback. + if (!isInternalJumbleLink && ogLoading) { + return ( +
e.stopPropagation()} + > + +
+ + + +
+
+ ) + } + + // Nostr-enhanced cards only when the target page did not provide usable preview metadata. + if (!hasOpengraphData) { // Enhanced card for event URLs (always show if nostr identifier detected, even while loading) if (nostrType === 'naddr' || nostrType === 'nevent' || nostrType === 'note') { const eventTypeName = fetchedEvent ? getEventTypeName(fetchedEvent.kind) : null diff --git a/src/constants.ts b/src/constants.ts index fb694860..ab152c17 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -183,7 +183,6 @@ export const SEARCHABLE_RELAY_URLS = [ 'wss://relay.nsec.app', 'wss://bucket.coracle.social', 'wss://spatia-arcana.com', - 'wss://sendit.nosflare.com', 'wss://nostr-pub.wellorder.net', 'wss://pyramid.fiatjaf.com/', 'wss://nostr.lopp.social/', diff --git a/src/contexts/secondary-page-context.tsx b/src/contexts/secondary-page-context.tsx new file mode 100644 index 00000000..3ba459e3 --- /dev/null +++ b/src/contexts/secondary-page-context.tsx @@ -0,0 +1,23 @@ +import { createContext, useContext } from 'react' + +/** + * Lives in a dedicated module so lazy chunks (e.g. TooManyRelaysAlertDialog) share the same + * context instance as PageManager. Importing from PageManager into those chunks can duplicate + * the module and break Provider matching (useSecondaryPage throws "must be used within Provider"). + */ +export type SecondaryPageContextValue = { + push: (url: string) => void + pop: () => void + currentIndex: number + navigateToPrimaryPage: (page: string, props?: object) => void +} + +export const SecondaryPageContext = createContext(undefined) + +export function useSecondaryPage(): SecondaryPageContextValue { + const context = useContext(SecondaryPageContext) + if (!context) { + throw new Error('useSecondaryPage must be used within a SecondaryPageContext.Provider') + } + return context +} diff --git a/src/hooks/useFetchWebMetadata.tsx b/src/hooks/useFetchWebMetadata.tsx index 0605f0e6..3dd2d5a5 100644 --- a/src/hooks/useFetchWebMetadata.tsx +++ b/src/hooks/useFetchWebMetadata.tsx @@ -6,14 +6,20 @@ import { isLikelyWebPageUrl } from '@/lib/url' export function useFetchWebMetadata(url: string) { const [metadata, setMetadata] = useState({}) + const [ogLoading, setOgLoading] = useState(() => Boolean(url && isLikelyWebPageUrl(url))) useEffect(() => { if (!url || !isLikelyWebPageUrl(url)) { + setMetadata({}) + setOgLoading(false) return } logger.debug('[useFetchWebMetadata] Fetching OG metadata', { url }) + setOgLoading(true) + setMetadata({}) + webService.fetchWebMetadata(url) .then((metadata) => { logger.debug('[useFetchWebMetadata] Received metadata', { url, hasTitle: !!metadata.title, hasDescription: !!metadata.description, hasImage: !!metadata.image }) @@ -22,7 +28,10 @@ export function useFetchWebMetadata(url: string) { .catch((error) => { logger.debug('[useFetchWebMetadata] Failed to fetch metadata', { url, error }) }) + .finally(() => { + setOgLoading(false) + }) }, [url]) - return metadata + return { ...metadata, ogLoading } } diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index f9331425..1dd51c7e 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -393,6 +393,14 @@ export default { 'Seen on': 'شوهد على', 'Temporarily display this reply': 'عرض هذا الرد مؤقتاً', 'Note not found': 'لم يتم العثور على الملاحظة', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index ecef5a05..311a7feb 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -402,6 +402,14 @@ export default { 'Seen on': 'Gesehen auf', 'Temporarily display this reply': 'Antwort vorübergehend anzeigen', 'Note not found': 'Die Notiz wurde nicht gefunden', + 'Invalid embedded note reference': 'Ungültige eingebettete Notiz-Referenz', + embeddedNoteInvalidEmpty: 'Dieser eingebettete Link ist leer.', + embeddedNoteInvalidHex: + 'Keine gültige Hex-Event-ID (es werden genau 64 hexadezimale Zeichen erwartet).', + embeddedNoteInvalidBech32: + 'Keine gültige Nostr-ID (Bech32 konnte nicht gelesen werden). Tippfehler oder abgeschnittene Adresse?', + embeddedNoteInvalidWrongKind: + 'Dies ist eine {{type}}-Adresse. Eingebettete Notizen brauchen note1, nevent1, naddr1 oder 64 Zeichen Hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 1d9260b1..38243534 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -394,6 +394,14 @@ export default { 'Seen on': 'Seen on', 'Temporarily display this reply': 'Temporarily display this reply', 'Note not found': 'Note not found', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index a17bdf68..efa4b5a6 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -397,6 +397,14 @@ export default { 'Seen on': 'Visto en', 'Temporarily display this reply': 'Mostrar temporalmente esta respuesta', 'Note not found': 'No se encontró la nota', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index edde7754..b3fe52cd 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -396,6 +396,14 @@ export default { 'Seen on': 'دیده شده در', 'Temporarily display this reply': 'نمایش موقت این پاسخ', 'Note not found': 'یادداشت یافت نشد', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index d2ab7cb2..13ed5aaf 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -396,6 +396,14 @@ export default { 'Seen on': 'Vu sur', 'Temporarily display this reply': 'Afficher temporairement cette réponse', 'Note not found': 'Note introuvable', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 6de4d731..72e963f2 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -397,6 +397,14 @@ export default { 'Seen on': 'पर देखा गया', 'Temporarily display this reply': 'इस उत्तर को अस्थायी रूप से प्रदर्शित करें', 'Note not found': 'नोट नहीं मिला', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 175b10e6..47466886 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -397,6 +397,14 @@ export default { 'Seen on': 'Visto su', 'Temporarily display this reply': 'Mostra temporaneamente questa replica', 'Note not found': 'Non è stata trovata la nota', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index 0ba7e205..ad38418e 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -394,6 +394,14 @@ export default { 'Seen on': '見た', 'Temporarily display this reply': 'この返信を一時的に表示', 'Note not found': 'ノートが見つかりません', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 26aebfa9..a23f8bfc 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -393,6 +393,14 @@ export default { 'Seen on': '출처', 'Temporarily display this reply': '이 답글 임시 표시', 'Note not found': '노트를 찾을 수 없음', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 509085ca..277a573a 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -394,6 +394,14 @@ export default { 'Seen on': 'Widziany na', 'Temporarily display this reply': 'Tymczasowo wyświetl tę odpowiedź', 'Note not found': 'Nie znaleziono wpisu', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index 16be4e45..05550545 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -396,6 +396,14 @@ export default { 'Seen on': 'Visto em', 'Temporarily display this reply': 'Exibir temporariamente esta resposta', 'Note not found': 'Nota não encontrada', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index 8f165b20..def61094 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -396,6 +396,14 @@ export default { 'Seen on': 'Visto em', 'Temporarily display this reply': 'Exibir temporariamente esta resposta', 'Note not found': 'Nota não encontrada', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 74e8df17..69982869 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -397,6 +397,14 @@ export default { 'Seen on': 'Просмотрено на', 'Temporarily display this reply': 'Временно отобразить этот ответ', 'Note not found': 'Заметка не найдена', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index f9466ce3..b97a8194 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -393,6 +393,14 @@ export default { 'Seen on': 'เห็นเมื่อ', 'Temporarily display this reply': 'แสดงการตอบกลับนี้ชั่วคราว', 'Note not found': 'ไม่พบโน้ต', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 4abae285..923b006a 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -392,6 +392,14 @@ export default { 'Seen on': '来自', 'Temporarily display this reply': '临时显示此回复', 'Note not found': '未找到该笔记', + 'Invalid embedded note reference': 'Invalid embedded note reference', + embeddedNoteInvalidEmpty: 'This embedded link is empty.', + embeddedNoteInvalidHex: + 'This is not a valid hex event id (expected exactly 64 hexadecimal characters).', + embeddedNoteInvalidBech32: + 'This is not a valid Nostr id (bech32 decode failed). It may be mistyped or truncated.', + embeddedNoteInvalidWrongKind: + 'This is a {{type}} id. Embedded notes must use note1, nevent1, naddr1, or 64-character hex.', 'The note was not found on your relays or default relays.': 'The note was not found on your relays or default relays.', "Try searching author's relays": "Try searching author's relays", diff --git a/src/lib/note-renderable-kinds.ts b/src/lib/note-renderable-kinds.ts new file mode 100644 index 00000000..3e153d1a --- /dev/null +++ b/src/lib/note-renderable-kinds.ts @@ -0,0 +1,23 @@ +import { ExtendedKind, SUPPORTED_KINDS } from '@/constants' +import { kinds } from 'nostr-tools' + +/** Kinds the main `Note` component renders with a dedicated UI (not `UnknownNote`). */ +const RENDERABLE_NOTE_KINDS = new Set([ + ...SUPPORTED_KINDS, + kinds.CommunityDefinition, + kinds.LiveEvent, + ExtendedKind.GROUP_METADATA, + ExtendedKind.PUBLIC_MESSAGE, + ExtendedKind.ZAP_REQUEST, + ExtendedKind.ZAP_RECEIPT, + ExtendedKind.PUBLICATION_CONTENT, + ExtendedKind.FOLLOW_PACK, + ExtendedKind.CITATION_INTERNAL, + ExtendedKind.CITATION_EXTERNAL, + ExtendedKind.CITATION_HARDCOPY, + ExtendedKind.CITATION_PROMPT +]) + +export function isRenderableNoteKind(kind: number): boolean { + return RENDERABLE_NOTE_KINDS.has(kind) +}