Browse Source

display embedded follow packs, when embedded

remove publications from feed
imwald
Silberengel 3 months ago
parent
commit
b289bd1b72
  1. 2
      package.json
  2. 92
      src/components/ContentPreview/FollowPackPreview.tsx
  3. 5
      src/components/ContentPreview/index.tsx
  4. 6
      src/components/Note/index.tsx
  5. 3
      src/constants.ts
  6. 11
      src/providers/KindFilterProvider.tsx
  7. 22
      src/services/local-storage.service.ts

2
package.json

@ -1,6 +1,6 @@ @@ -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",

92
src/components/ContentPreview/FollowPackPreview.tsx

@ -0,0 +1,92 @@ @@ -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 (
<div className={cn('border rounded-lg p-3 bg-muted/30', className)}>
<div className="flex items-center gap-1 mb-2">
<span className="text-muted-foreground">[{t('Follow Pack')}]</span>
<span className="font-semibold text-sm">{title}</span>
</div>
{description && (
<div className="text-sm text-muted-foreground mb-3 line-clamp-2">
{description}
</div>
)}
<div className="flex items-center gap-3 mb-3">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Users className="size-4" />
<span>{t('{{count}} profiles', { count: packPubkeys.length })}</span>
</div>
{packPubkeys.length > 0 && (
<div className="flex -space-x-2">
{packPubkeys.slice(0, 5).map((pubkey) => (
<SimpleUserAvatar
key={pubkey}
userId={pubkey}
size="small"
className="border-2 border-background"
/>
))}
{packPubkeys.length > 5 && (
<div className="size-7 rounded-full border-2 border-background bg-muted flex items-center justify-center text-xs text-muted-foreground">
+{packPubkeys.length - 5}
</div>
)}
</div>
)}
</div>
<Button
variant="outline"
size="sm"
onClick={handleOpenInViewer}
className="w-full"
>
<ExternalLink className="size-4 mr-2" />
{t('Open in Follow Pack Viewer')}
</Button>
</div>
)
}

5
src/components/ContentPreview/index.tsx

@ -19,6 +19,7 @@ import ZapPreview from './ZapPreview' @@ -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({ @@ -121,5 +122,9 @@ export default function ContentPreview({
return <ApplicationHandlerRecommendation event={event} className={className} />
}
if (event.kind === ExtendedKind.FOLLOW_PACK) {
return <FollowPackPreview event={event} className={className} />
}
return <div className={className}>[{t('Cannot handle event of kind k', { k: event.kind })}]</div>
}

6
src/components/Note/index.tsx

@ -39,6 +39,7 @@ import VideoNote from './VideoNote' @@ -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({ @@ -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({ @@ -173,6 +175,8 @@ export default function Note({
content = <MarkdownArticle className="mt-2" event={event} hideMetadata={true} />
} else if (event.kind === ExtendedKind.ZAP_REQUEST || event.kind === ExtendedKind.ZAP_RECEIPT) {
content = <Zap className="mt-2" event={event} />
} else if (event.kind === ExtendedKind.FOLLOW_PACK) {
content = <FollowPackPreview className="mt-2" event={event} />
} 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 = <MarkdownArticle className="mt-2" event={event} hideMetadata={true} />

3
src/constants.ts

@ -163,7 +163,8 @@ export const ExtendedKind = { @@ -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 = [

11
src/providers/KindFilterProvider.tsx

@ -1,6 +1,6 @@ @@ -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 = () => { @@ -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<number[]>(
storedShowKinds.length > 0 ? storedShowKinds : defaultShowKinds

22
src/services/local-storage.service.ts

@ -181,8 +181,13 @@ class LocalStorageService { @@ -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 { @@ -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'

Loading…
Cancel
Save