Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
a3bb50e769
  1. 23
      src/components/Embedded/EmbeddedNote.tsx
  2. 10
      src/components/PostEditor/PostRelaySelector.tsx
  3. 28
      src/components/ReplyNoteList/index.tsx
  4. 6
      src/constants.ts
  5. 11
      src/contexts/suppress-embedded-note-context.tsx
  6. 44
      src/lib/event.ts

23
src/components/Embedded/EmbeddedNote.tsx

@ -42,6 +42,20 @@ function hexEventIdFromNoteId(noteId: string): string | null { @@ -42,6 +42,20 @@ function hexEventIdFromNoteId(noteId: string): string | null {
}
}
/** For naddr (replaceable events), return coordinate kind:pubkey:identifier for suppression matching. */
function coordinateFromNoteId(noteId: string): string | null {
try {
const { type, data } = nip19.decode(noteId.trim())
if (type === 'naddr' && data) {
const id = data.identifier ?? ''
return `${data.kind}:${data.pubkey}:${id}`.toLowerCase()
}
return null
} catch {
return null
}
}
/** True if `fetchEventWithExternalRelays(noteId, …)` can build a REQ filter (hex, note, nevent, naddr). */
function canSearchOnExternalRelays(noteId: string): boolean {
if (hexEventIdFromNoteId(noteId)) return true
@ -61,10 +75,13 @@ export function EmbeddedNote({ @@ -61,10 +75,13 @@ export function EmbeddedNote({
className?: string
containingEvent?: Event
}) {
const suppressId = useSuppressEmbeddedNoteId()
const suppress = useSuppressEmbeddedNoteId()
const embeddedHexId = useMemo(() => hexEventIdFromNoteId(noteId), [noteId])
if (suppressId && embeddedHexId && embeddedHexId === suppressId.toLowerCase()) {
return null
const embeddedCoordinate = useMemo(() => coordinateFromNoteId(noteId), [noteId])
if (suppress) {
if (embeddedHexId && embeddedHexId === suppress.hexId.toLowerCase()) return null
if (suppress.coordinate && embeddedCoordinate && embeddedCoordinate === suppress.coordinate.toLowerCase())
return null
}
const validation = useMemo(() => validateEmbeddedNotePointer(noteId), [noteId])
if (!validation.valid) {

10
src/components/PostEditor/PostRelaySelector.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { KIND_1_BLOCKED_RELAY_URLS } from '@/constants'
import { NOSTR_URI_FOR_REPLY_PUBKEYS_REGEX } from '@/lib/content-patterns'
import { simplifyUrl, isLocalNetworkUrl, normalizeUrl } from '@/lib/url'
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
@ -92,7 +93,14 @@ export default function PostRelaySelector({ @@ -92,7 +93,14 @@ export default function PostRelaySelector({
// Memoize arrays to prevent unnecessary re-renders
const memoizedFavoriteRelays = useMemo(() => favoriteRelays, [favoriteRelays])
const memoizedBlockedRelays = useMemo(() => blockedRelays, [blockedRelays])
const memoizedBlockedRelays = useMemo(() => {
// For kind 1 replies and top-level posts, also block KIND_1_BLOCKED_RELAY_URLS
const isKind1Publish =
!isPublicMessage && (typeof _parentEvent?.kind === 'undefined' || _parentEvent?.kind === 1)
return isKind1Publish
? [...blockedRelays, ...KIND_1_BLOCKED_RELAY_URLS]
: blockedRelays
}, [blockedRelays, isPublicMessage, _parentEvent?.kind])
const memoizedRelaySets = useMemo(() => relaySets, [relaySets])
const memoizedOpenFrom = useMemo(() => openFrom, [openFrom])

28
src/components/ReplyNoteList/index.tsx

@ -209,6 +209,8 @@ function ReplyNoteList({ @@ -209,6 +209,8 @@ function ReplyNoteList({
}, [event.id, repliesMap, mutePubkeySet, hideContentMentioningMutedUsers, sort])
const replyIdSet = useMemo(() => new Set(replies.map((r) => r.id)), [replies])
/** Events that quote the note (from useQuoteEvents) — render with quote styling and without embedded quote. */
const quoteIdSet = useMemo(() => new Set(quoteEvents.map((e) => e.id)), [quoteEvents])
const mergedFeed = useMemo(() => {
if (!showQuotes) return replies
const quoteOnly = quoteEvents.filter((e) => !replyIdSet.has(e.id))
@ -383,18 +385,16 @@ function ReplyNoteList({ @@ -383,18 +385,16 @@ function ReplyNoteList({
const threadRelayHints = [
...new Set([...relayHintsFromEventTags(event), ...seenOn, ...fromBrowsingFeed])
]
let finalRelayUrls = await buildReplyReadRelayList(
const replyBlockedRelays = [
...(blockedRelays || []),
...E_TAG_FILTER_BLOCKED_RELAY_URLS
]
const finalRelayUrls = await buildReplyReadRelayList(
opAuthorPubkey,
userPubkey || undefined,
blockedRelays || [],
replyBlockedRelays,
threadRelayHints
)
const eTagBlockedSet = new Set(
E_TAG_FILTER_BLOCKED_RELAY_URLS.map((u) => normalizeUrl(u) || u)
)
finalRelayUrls = finalRelayUrls.filter(
(u) => !eTagBlockedSet.has(normalizeUrl(u) || u)
)
const filters: Filter[] = []
if (rootInfo.type === 'E') {
@ -585,7 +585,7 @@ function ReplyNoteList({ @@ -585,7 +585,7 @@ function ReplyNoteList({
)}
<div>
{mergedFeed.slice(0, showCount).map((item) => {
const isQuote = !replyIdSet.has(item.id)
const isQuote = quoteIdSet.has(item.id)
// Don't filter by trust until trust data is loaded - prevents replies from
// vanishing when wotSet is still empty (all non-self appear untrusted)
if (isTrustLoaded && hideUntrustedInteractions && !isUserTrusted(item.pubkey)) {
@ -606,9 +606,15 @@ function ReplyNoteList({ @@ -606,9 +606,15 @@ function ReplyNoteList({
: item.kind === kinds.LongFormArticle
? t('cited in article')
: t('quoted this note')
const hideQuotedNote = eventReferencesEventId(item, event.id)
const hideQuotedNote = eventReferencesEventId(item, event)
return (
<SuppressEmbeddedNoteContext.Provider key={item.id} value={event.id}>
<SuppressEmbeddedNoteContext.Provider
key={item.id}
value={{
hexId: event.id,
coordinate: isReplaceableEvent(event.kind) ? getReplaceableCoordinateFromEvent(event) : undefined
}}
>
<div
ref={(el) => (replyRefs.current[item.id] = el)}
className="scroll-mt-12 border-l-2 border-muted-foreground/40 pl-3 py-1 my-1 rounded-r"

6
src/constants.ts

@ -157,6 +157,12 @@ export const BOOKSTR_RELAY_URLS = [ @@ -157,6 +157,12 @@ export const BOOKSTR_RELAY_URLS = [
'wss://orly-relay.imwald.eu'
]
/**
* Block-list order (applied in sequence when building relay lists):
* 1. READ_ONLY never publish
* 2. KIND_1_BLOCKED skip for kind 1 read/write
* 3. E_TAG_FILTER_BLOCKED skip for reply/quote/stats fetches (#e, #a, #q filters)
*/
/** Relays that must never be used for publishing (read-only aggregators, etc.). */
export const READ_ONLY_RELAY_URLS = ['wss://aggr.nostr.land']

11
src/contexts/suppress-embedded-note-context.tsx

@ -1,8 +1,13 @@ @@ -1,8 +1,13 @@
import { createContext, useContext } from 'react'
/** When set, EmbeddedNote should not render notes whose id matches this (avoids redundancy when viewing "quotes of this note"). */
export const SuppressEmbeddedNoteContext = createContext<string | undefined>(undefined)
export type SuppressEmbeddedNoteValue = {
hexId: string
coordinate?: string
}
/** When set, EmbeddedNote should not render notes whose id/coordinate matches (avoids redundancy when viewing "quotes of this note"). */
export const SuppressEmbeddedNoteContext = createContext<SuppressEmbeddedNoteValue | undefined>(undefined)
export function useSuppressEmbeddedNoteId(): string | undefined {
export function useSuppressEmbeddedNoteId(): SuppressEmbeddedNoteValue | undefined {
return useContext(SuppressEmbeddedNoteContext)
}

44
src/lib/event.ts

@ -199,18 +199,38 @@ export function getRootEventHexId(event?: Event) { @@ -199,18 +199,38 @@ export function getRootEventHexId(event?: Event) {
return tag?.[1]
}
/** True if event references targetHexId as root, parent, or quoted (#q) — used to hide redundant preview when showing quotes of current note. */
export function eventReferencesEventId(event: Event | undefined, targetHexId: string): boolean {
if (!event || !targetHexId) return false
const target = targetHexId.toLowerCase()
const rootId = getRootETag(event)?.[1]?.toLowerCase()
if (rootId === target) return true
const parentId = getParentETag(event)?.[1]?.toLowerCase()
if (parentId === target) return true
const qTag = event.tags.find((t) => t[0] === 'q' || t[0] === 'Q')?.[1]?.toLowerCase()
if (qTag === target) return true
const eTags = event.tags.filter((t) => t[0] === 'e' || t[0] === 'E')
if (eTags.some((t) => t[1]?.toLowerCase() === target)) return true
/** True if event references target as root, parent, or quoted (#q, #a) — used to hide redundant preview when showing quotes of current note. */
export function eventReferencesEventId(
event: Event | undefined,
targetHexIdOrEvent: string | Event
): boolean {
if (!event) return false
const targetEvent = typeof targetHexIdOrEvent === 'object' ? targetHexIdOrEvent : undefined
const targetHexId =
typeof targetHexIdOrEvent === 'string'
? targetHexIdOrEvent.toLowerCase()
: targetHexIdOrEvent.id?.toLowerCase()
const targetCoordinate =
targetEvent && isReplaceableEvent(targetEvent.kind)
? getReplaceableCoordinateFromEvent(targetEvent)
: undefined
if (targetHexId) {
const rootId = getRootETag(event)?.[1]?.toLowerCase()
if (rootId === targetHexId) return true
const parentId = getParentETag(event)?.[1]?.toLowerCase()
if (parentId === targetHexId) return true
const qTag = event.tags.find((t) => t[0] === 'q' || t[0] === 'Q')?.[1]?.toLowerCase()
if (qTag === targetHexId) return true
const eTags = event.tags.filter((t) => t[0] === 'e' || t[0] === 'E')
if (eTags.some((t) => t[1]?.toLowerCase() === targetHexId)) return true
}
if (targetCoordinate) {
const aTags = event.tags.filter((t) => t[0] === 'a' || t[0] === 'A')
if (aTags.some((t) => t[1]?.toLowerCase() === targetCoordinate.toLowerCase())) return true
}
return false
}

Loading…
Cancel
Save