import UserAvatar from '@/components/UserAvatar' import Username from '@/components/Username' import { Separator } from '@/components/ui/separator' import { cn } from '@/lib/utils' import { formatPubkey } from '@/lib/pubkey' import { getSpellName } from '@/services/spell.service' import { FAUX_SPELL_ORDER } from '@/constants' import { Check, Star } from 'lucide-react' import type { Event } from 'nostr-tools' import type { TFunction } from 'i18next' import { encodeFollowSetSpellId, fauxSpellLabelKey, FAUX_SPELL_ICON, FOLLOW_SET_SPELL_ROW_ICON, getFollowSetDTag, labelFollowSetEvent } from './fauxSpellConfig' function spellPickerPrimaryAndSecondary( spell: Event, accountPubkey: string | undefined, labelFor: (e: Event) => string, options?: { omitAuthorNpub?: boolean } ) { const primary = labelFor(spell) const isOwn = !!(accountPubkey && spell.pubkey === accountPubkey) const shortTitle = primary.trim().length < 4 const secondaryParts: string[] = [] if (!isOwn && !options?.omitAuthorNpub) secondaryParts.push(formatPubkey(spell.pubkey)) if (shortTitle) secondaryParts.push(`${spell.id.slice(0, 8)}…`) return { primary, secondary: secondaryParts.length > 0 ? secondaryParts.join(' · ') : null } } export function groupSpellsByPubkeySorted(spells: Event[]): { pubkey: string; spells: Event[] }[] { const map = new Map() for (const s of spells) { const list = map.get(s.pubkey) if (list) list.push(s) else map.set(s.pubkey, [s]) } for (const list of map.values()) { list.sort((a, b) => getSpellName(a).localeCompare(getSpellName(b), undefined, { sensitivity: 'base' }) ) } return [...map.entries()] .sort(([a], [b]) => a.localeCompare(b)) .map(([pk, list]) => ({ pubkey: pk, spells: list })) } function SpellSheetAuthorHeader({ userId }: { userId: string }) { return (
) } function SpellSheetOptionRow({ spell, selected, accountPubkey, labelFor, onPick, groupedUnderAuthor = false, starred = false, onToggleStar, starTitleAdd, starTitleRemove, t }: { spell: Event selected: boolean accountPubkey: string | undefined labelFor: (e: Event) => string onPick: (e: Event) => void groupedUnderAuthor?: boolean starred?: boolean onToggleStar?: (spell: Event) => void starTitleAdd?: string starTitleRemove?: string t: TFunction }) { const { primary, secondary } = spellPickerPrimaryAndSecondary(spell, accountPubkey, labelFor, { omitAuthorNpub: groupedUnderAuthor }) return (
{onToggleStar ? ( ) : null}
) } export type SpellPickerContentProps = { t: TFunction pubkey: string | null | undefined selectedSpell: Event | null selectedFauxSpell: string | null favoriteSpellSet: Set starredSpellsForPicker: Event[] ownSpells: Event[] followSpells: Event[] otherSpells: Event[] followSetListEvents: Event[] spellMenuLabel: (spell: Event) => string spellStarAddTitle: string spellStarRemoveTitle: string pickSpell: (spell: Event | null) => void pickFauxSpell: (name: string | null) => void clearSpellSelection: () => void toggleFavoriteSpell: (spell: Event) => void } export function SpellPickerContent({ t, pubkey, selectedSpell, selectedFauxSpell, favoriteSpellSet, starredSpellsForPicker, ownSpells, followSpells, otherSpells, followSetListEvents, spellMenuLabel, spellStarAddTitle, spellStarRemoveTitle, pickSpell, pickFauxSpell, clearSpellSelection, toggleFavoriteSpell }: SpellPickerContentProps) { const followSpellGroups = groupSpellsByPubkeySorted(followSpells) const otherSpellGroups = groupSpellsByPubkeySorted(otherSpells) return ( <> {starredSpellsForPicker.length > 0 ? ( <>

{t('Starred spells')}

{starredSpellsForPicker.map((spell) => ( getSpellName(e)} onPick={pickSpell} starred t={t} onToggleStar={(s) => void toggleFavoriteSpell(s)} starTitleAdd={spellStarAddTitle} starTitleRemove={spellStarRemoveTitle} /> ))} ) : null} {FAUX_SPELL_ORDER.flatMap((name) => { if ( (name === 'notifications' || name === 'following' || name === 'heatMap' || name === 'bookmarks' || name === 'interests') && !pubkey ) { return [] } const Icon = FAUX_SPELL_ICON[name] const selected = selectedFauxSpell === name const builtinRow = ( ) if (name !== 'following' || !pubkey || followSetListEvents.length === 0) { return [builtinRow] } const setRows = followSetListEvents.flatMap((ev) => { const d = getFollowSetDTag(ev) if (!d) return [] const spellId = encodeFollowSetSpellId(d) const setSelected = selectedFauxSpell === spellId return [ ] }) return [builtinRow, ...setRows] })} {ownSpells.length > 0 ? ( <>

{t('spellPickerSectionYours')}

{ownSpells.map((spell) => ( void toggleFavoriteSpell(s)} starTitleAdd={spellStarAddTitle} starTitleRemove={spellStarRemoveTitle} /> ))} ) : null} {followSpells.length > 0 ? ( <>

{t('Spells from follows', { count: followSpells.length })}

{followSpellGroups.map(({ pubkey: authorPk, spells: groupSpells }) => (
{groupSpells.map((spell) => ( void toggleFavoriteSpell(s)} starTitleAdd={spellStarAddTitle} starTitleRemove={spellStarRemoveTitle} /> ))}
))} ) : null} {otherSpells.length > 0 ? ( <>

{t('Other spells', { count: otherSpells.length })}

{otherSpellGroups.map(({ pubkey: authorPk, spells: groupSpells }) => (
{groupSpells.map((spell) => ( void toggleFavoriteSpell(s)} starTitleAdd={spellStarAddTitle} starTitleRemove={spellStarRemoveTitle} /> ))}
))} ) : null} ) }