From b289bd1b7235d833311cd32fa9fc379f21a85e0e Mon Sep 17 00:00:00 2001 From: Silberengel Date: Thu, 4 Dec 2025 06:01:25 +0100 Subject: [PATCH] display embedded follow packs, when embedded remove publications from feed --- package.json | 2 +- .../ContentPreview/FollowPackPreview.tsx | 92 +++++++++++++++++++ src/components/ContentPreview/index.tsx | 5 + src/components/Note/index.tsx | 6 +- src/constants.ts | 3 +- src/providers/KindFilterProvider.tsx | 11 ++- src/services/local-storage.service.ts | 22 ++++- 7 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 src/components/ContentPreview/FollowPackPreview.tsx diff --git a/package.json b/package.json index 512f467..e712708 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jumble-imwald", - "version": "14.8", + "version": "15.0.0", "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/ContentPreview/FollowPackPreview.tsx b/src/components/ContentPreview/FollowPackPreview.tsx new file mode 100644 index 0000000..444c3ff --- /dev/null +++ b/src/components/ContentPreview/FollowPackPreview.tsx @@ -0,0 +1,92 @@ +import { getPubkeysFromPTags } from '@/lib/tag' +import { cn } from '@/lib/utils' +import { Event } from 'nostr-tools' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { SimpleUserAvatar } from '../UserAvatar' +import { Users, ExternalLink } from 'lucide-react' +import { Button } from '../ui/button' +import { toFollowPacks } from '@/lib/link' +import { useSecondaryPage } from '@/PageManager' + +export default function FollowPackPreview({ + event, + className +}: { + event: Event + className?: string +}) { + const { t } = useTranslation() + const { push } = useSecondaryPage() + + const packPubkeys = useMemo(() => getPubkeysFromPTags(event.tags), [event.tags]) + + const getPackTitle = (pack: Event): string => { + const titleTag = pack.tags.find(tag => tag[0] === 'title' || tag[0] === 'name') + return titleTag?.[1] || t('Follow Pack') + } + + const getPackDescription = (pack: Event): string => { + const descTag = pack.tags.find(tag => tag[0] === 'description' || tag[0] === 'd') + return descTag?.[1] || '' + } + + const title = getPackTitle(event) + const description = getPackDescription(event) + + const handleOpenInViewer = (e: React.MouseEvent) => { + e.stopPropagation() + push(toFollowPacks()) + } + + return ( +
+
+ [{t('Follow Pack')}] + {title} +
+ + {description && ( +
+ {description} +
+ )} + +
+
+ + {t('{{count}} profiles', { count: packPubkeys.length })} +
+ + {packPubkeys.length > 0 && ( +
+ {packPubkeys.slice(0, 5).map((pubkey) => ( + + ))} + {packPubkeys.length > 5 && ( +
+ +{packPubkeys.length - 5} +
+ )} +
+ )} +
+ + +
+ ) +} + diff --git a/src/components/ContentPreview/index.tsx b/src/components/ContentPreview/index.tsx index 8772acc..2639400 100644 --- a/src/components/ContentPreview/index.tsx +++ b/src/components/ContentPreview/index.tsx @@ -19,6 +19,7 @@ import ZapPreview from './ZapPreview' import DiscussionNote from '../DiscussionNote' import ApplicationHandlerInfo from '../ApplicationHandlerInfo' import ApplicationHandlerRecommendation from '../ApplicationHandlerRecommendation' +import FollowPackPreview from './FollowPackPreview' export default function ContentPreview({ event, @@ -121,5 +122,9 @@ export default function ContentPreview({ return } + if (event.kind === ExtendedKind.FOLLOW_PACK) { + return + } + return
[{t('Cannot handle event of kind k', { k: event.kind })}]
} diff --git a/src/components/Note/index.tsx b/src/components/Note/index.tsx index 5d40f7d..68ba780 100644 --- a/src/components/Note/index.tsx +++ b/src/components/Note/index.tsx @@ -39,6 +39,7 @@ import VideoNote from './VideoNote' import RelayReview from './RelayReview' import Zap from './Zap' import CitationCard from '@/components/CitationCard' +import FollowPackPreview from '../ContentPreview/FollowPackPreview' export default function Note({ event, @@ -78,7 +79,8 @@ export default function Note({ ExtendedKind.PUBLIC_MESSAGE, ExtendedKind.ZAP_REQUEST, ExtendedKind.ZAP_RECEIPT, - ExtendedKind.PUBLICATION_CONTENT // Only for rendering embedded content, not in feeds + ExtendedKind.PUBLICATION_CONTENT, // Only for rendering embedded content, not in feeds + ExtendedKind.FOLLOW_PACK // Only for rendering embedded content, not in feeds ] @@ -173,6 +175,8 @@ export default function Note({ content = } else if (event.kind === ExtendedKind.ZAP_REQUEST || event.kind === ExtendedKind.ZAP_RECEIPT) { content = + } else if (event.kind === ExtendedKind.FOLLOW_PACK) { + content = } else if (event.kind === kinds.ShortTextNote || event.kind === ExtendedKind.COMMENT || event.kind === ExtendedKind.VOICE_COMMENT) { // Plain text notes use MarkdownArticle for proper markdown rendering content = diff --git a/src/constants.ts b/src/constants.ts index ec87b3b..5d2ef09 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -163,7 +163,8 @@ export const ExtendedKind = { // NIP-89 Application Handlers APPLICATION_HANDLER_RECOMMENDATION: 31989, APPLICATION_HANDLER_INFO: 31990, - PAYMENT_INFO: 10133 + PAYMENT_INFO: 10133, + FOLLOW_PACK: 39089 } export const SUPPORTED_KINDS = [ diff --git a/src/providers/KindFilterProvider.tsx b/src/providers/KindFilterProvider.tsx index 27e8784..ff5acf8 100644 --- a/src/providers/KindFilterProvider.tsx +++ b/src/providers/KindFilterProvider.tsx @@ -1,6 +1,6 @@ import { createContext, useContext, useState } from 'react' import storage from '@/services/local-storage.service' -import { SUPPORTED_KINDS } from '@/constants' +import { SUPPORTED_KINDS, ExtendedKind } from '@/constants' import { kinds } from 'nostr-tools' type TKindFilterContext = { @@ -19,8 +19,13 @@ export const useKindFilter = () => { } export function KindFilterProvider({ children }: { children: React.ReactNode }) { - // Ensure we always have a default value - show all supported kinds except reposts - const defaultShowKinds = SUPPORTED_KINDS.filter(kind => kind !== kinds.Repost) + // Ensure we always have a default value - show all supported kinds except reposts, publications, and publication content + // Publications (30040) and Publication Content (30041) should only be embedded, not shown in feeds + const defaultShowKinds = SUPPORTED_KINDS.filter( + kind => kind !== kinds.Repost && + kind !== ExtendedKind.PUBLICATION && + kind !== ExtendedKind.PUBLICATION_CONTENT + ) const storedShowKinds = storage.getShowKinds() const [showKinds, setShowKinds] = useState( storedShowKinds.length > 0 ? storedShowKinds : defaultShowKinds diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index 3c8eb8a..b52cf7b 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -181,8 +181,13 @@ class LocalStorageService { const showKindsStr = window.localStorage.getItem(StorageKey.SHOW_KINDS) if (!showKindsStr) { - // Default: show all supported kinds except reposts - this.showKinds = SUPPORTED_KINDS.filter(kind => kind !== kinds.Repost) + // Default: show all supported kinds except reposts, publications, and publication content + // Publications (30040) and Publication Content (30041) should only be embedded, not shown in feeds + this.showKinds = SUPPORTED_KINDS.filter( + kind => kind !== kinds.Repost && + kind !== ExtendedKind.PUBLICATION && + kind !== ExtendedKind.PUBLICATION_CONTENT + ) } else { const showKindsVersionStr = window.localStorage.getItem(StorageKey.SHOW_KINDS_VERSION) const showKindsVersion = showKindsVersionStr ? parseInt(showKindsVersionStr) : 0 @@ -219,10 +224,21 @@ class LocalStorageService { showKinds.splice(pubContentIndex, 1) } } + if (showKindsVersion < 6) { + // Remove publications and publication content from existing users' filters (should only be embedded, not in feeds) + const pubIndex = showKinds.indexOf(ExtendedKind.PUBLICATION) + if (pubIndex !== -1) { + showKinds.splice(pubIndex, 1) + } + const pubContentIndex = showKinds.indexOf(ExtendedKind.PUBLICATION_CONTENT) + if (pubContentIndex !== -1) { + showKinds.splice(pubContentIndex, 1) + } + } this.showKinds = showKinds } window.localStorage.setItem(StorageKey.SHOW_KINDS, JSON.stringify(this.showKinds)) - window.localStorage.setItem(StorageKey.SHOW_KINDS_VERSION, '5') + window.localStorage.setItem(StorageKey.SHOW_KINDS_VERSION, '6') this.hideContentMentioningMutedUsers = window.localStorage.getItem(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS) === 'true'