Browse Source

fix formatting of superchats

imwald
Silberengel 3 weeks ago
parent
commit
11b0748539
  1. 23
      src/components/Note/Superchat.tsx
  2. 6
      src/components/Note/SuperchatCommentMarkdown.tsx
  3. 15
      src/components/Note/SuperchatPaymentMethodLabel.tsx
  4. 17
      src/components/Note/Zap.tsx
  5. 9
      src/lib/payto-registry.ts
  6. 1
      src/lib/payto.ts
  7. 11
      src/lib/superchat-ui.ts

23
src/components/Note/Superchat.tsx

@ -1,6 +1,7 @@
import { useFetchEvent } from '@/hooks' import { useFetchEvent } from '@/hooks'
import { usePaymentAttestationStatus } from '@/hooks/usePaymentAttestationStatus' import { usePaymentAttestationStatus } from '@/hooks/usePaymentAttestationStatus'
import { openNoteFromFetchOrCache } from '@/lib/navigation-related-events' import { openNoteFromFetchOrCache } from '@/lib/navigation-related-events'
import { formatAmount } from '@/lib/lightning'
import { parsePaytoTagType } from '@/lib/payto' import { parsePaytoTagType } from '@/lib/payto'
import { relayHintsFromEventTags } from '@/lib/relay-list-builder' import { relayHintsFromEventTags } from '@/lib/relay-list-builder'
import { getPaymentNotificationInfo, getSuperchatReferenceFetchId } from '@/lib/superchat' import { getPaymentNotificationInfo, getSuperchatReferenceFetchId } from '@/lib/superchat'
@ -61,11 +62,12 @@ export default function Superchat({
) )
} }
const { senderPubkey, recipientPubkey, comment } = info const { senderPubkey, recipientPubkey, comment, amountSats } = info
const { attested } = usePaymentAttestationStatus(event, recipientPubkey) const { attested } = usePaymentAttestationStatus(event, recipientPubkey)
const hasThreadTarget = Boolean(targetEvent || referencedFetchId) const hasThreadTarget = Boolean(targetEvent || referencedFetchId)
const isNotification = variant === 'notification' const isNotification = variant === 'notification'
const isProfileWall = variant === 'profileWall' const isProfileWall = variant === 'profileWall'
const showAmount = isNotification && amountSats > 0
const showAsSuperchat = isProfileWall || attested const showAsSuperchat = isProfileWall || attested
const hasTarget = isNotification && (hasThreadTarget || Boolean(recipientPubkey)) const hasTarget = isNotification && (hasThreadTarget || Boolean(recipientPubkey))
const hasMetaLine = const hasMetaLine =
@ -82,7 +84,8 @@ export default function Superchat({
} }
return ( return (
<div className={cn('text-sm text-muted-foreground', className)}> <div className={cn('min-w-0', className)}>
<div className="text-sm text-muted-foreground">
{hasMetaLine ? ( {hasMetaLine ? (
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-sm"> <div className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-sm">
{isProfileWall ? ( {isProfileWall ? (
@ -95,7 +98,6 @@ export default function Superchat({
/> />
<SuperchatPaymentMethodLabel <SuperchatPaymentMethodLabel
paytoType={paytoType} paytoType={paytoType}
iconOnly
className="shrink-0" className="shrink-0"
imgClassName="size-5" imgClassName="size-5"
/> />
@ -139,26 +141,41 @@ export default function Superchat({
imgClassName="size-5" imgClassName="size-5"
/> />
<span className={cn('text-xl', superchatTitleClass)}>{t('Superchat')}</span> <span className={cn('text-xl', superchatTitleClass)}>{t('Superchat')}</span>
{showAmount ? (
<span className="text-xl font-bold tabular-nums tracking-tight text-foreground">
{formatAmount(amountSats)} {t('sats')}
</span>
) : null}
</> </>
) : ( ) : (
<>
<SuperchatPaymentMethodLabel <SuperchatPaymentMethodLabel
paytoType={paytoType} paytoType={paytoType}
className="px-2.5 py-1.5 text-lg" className="px-2.5 py-1.5 text-lg"
imgClassName="size-5" imgClassName="size-5"
/> />
{showAmount ? (
<span className="text-lg font-bold tabular-nums tracking-tight text-foreground">
{formatAmount(amountSats)} {t('sats')}
</span>
) : null}
</>
)} )}
</div> </div>
) : null} ) : null}
</div>
{comment ? ( {comment ? (
<SuperchatCommentMarkdown event={event} comment={comment} className="mt-2" /> <SuperchatCommentMarkdown event={event} comment={comment} className="mt-2" />
) : null} ) : null}
{isNotification ? ( {isNotification ? (
<div className="text-sm text-muted-foreground">
<TurnIntoSuperchatButton <TurnIntoSuperchatButton
event={event} event={event}
prominent prominent
attestationRecipientPubkey={recipientPubkey} attestationRecipientPubkey={recipientPubkey}
className="mt-3" className="mt-3"
/> />
</div>
) : null} ) : null}
</div> </div>
) )

6
src/components/Note/SuperchatCommentMarkdown.tsx

@ -1,7 +1,8 @@
import MarkdownArticle from './MarkdownArticle/MarkdownArticle' import { superchatCommentBodyClass } from '@/lib/superchat-ui'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { Event } from 'nostr-tools' import { Event } from 'nostr-tools'
import { useMemo } from 'react' import { useMemo } from 'react'
import MarkdownArticle from './MarkdownArticle/MarkdownArticle'
export default function SuperchatCommentMarkdown({ export default function SuperchatCommentMarkdown({
event, event,
@ -23,8 +24,7 @@ export default function SuperchatCommentMarkdown({
hideMetadata hideMetadata
lazyMedia={false} lazyMedia={false}
className={cn( className={cn(
'prose-xl max-w-none text-foreground', superchatCommentBodyClass,
'[&_p]:text-[1.6875rem] [&_p]:font-semibold [&_p]:leading-snug [&_p]:text-foreground',
'[&_a]:text-[hsl(var(--uri-link))] [&_a:hover]:text-[hsl(var(--primary))]', '[&_a]:text-[hsl(var(--uri-link))] [&_a:hover]:text-[hsl(var(--primary))]',
'[&_strong]:text-foreground [&_em]:text-foreground', '[&_strong]:text-foreground [&_em]:text-foreground',
className className

15
src/components/Note/SuperchatPaymentMethodLabel.tsx

@ -1,36 +1,37 @@
import { getCanonicalPaytoType, getPaytoEditorTypeLabel } from '@/lib/payto' import { getCanonicalPaytoType, getPaytoEditorTypeLabel, paytoTypeHasDisplayIcon } from '@/lib/payto'
import PaytoTypeIcon from '@/components/PaytoTypeIcon' import PaytoTypeIcon from '@/components/PaytoTypeIcon'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
export default function SuperchatPaymentMethodLabel({ export default function SuperchatPaymentMethodLabel({
paytoType, paytoType,
className, className,
imgClassName, imgClassName
iconOnly = false
}: { }: {
/** Canonical or alias payto type (`lightning`, `monero`, `geyser`, …). */ /** Canonical or alias payto type (`lightning`, `monero`, `geyser`, …). */
paytoType: string paytoType: string
className?: string className?: string
imgClassName?: string imgClassName?: string
/** Profile wall: icon only (label in `title` for hover). */ /** @deprecated Icon-only when a logo/lightning/symbol exists; text label only as fallback. */
iconOnly?: boolean iconOnly?: boolean
}) { }) {
const canonical = getCanonicalPaytoType(paytoType) const canonical = getCanonicalPaytoType(paytoType)
const label = getPaytoEditorTypeLabel(canonical) const label = getPaytoEditorTypeLabel(canonical)
const showIconOnly = paytoTypeHasDisplayIcon(canonical)
return ( return (
<span <span
title={iconOnly ? label : undefined} title={showIconOnly ? label : undefined}
aria-label={showIconOnly ? label : undefined}
className={cn( className={cn(
'inline-flex shrink-0 items-center rounded-md border border-border/60 bg-muted/40', 'inline-flex shrink-0 items-center rounded-md border border-border/60 bg-muted/40',
iconOnly showIconOnly
? 'p-1.5 leading-none text-muted-foreground' ? 'p-1.5 leading-none text-muted-foreground'
: 'gap-1.5 px-2 py-1 text-sm font-semibold leading-none text-muted-foreground', : 'gap-1.5 px-2 py-1 text-sm font-semibold leading-none text-muted-foreground',
className className
)} )}
> >
<PaytoTypeIcon type={paytoType} imgClassName={imgClassName} /> <PaytoTypeIcon type={paytoType} imgClassName={imgClassName} />
{iconOnly ? null : <span className="truncate">{label}</span>} {showIconOnly ? null : <span className="truncate">{label}</span>}
</span> </span>
) )
} }

17
src/components/Note/Zap.tsx

@ -90,6 +90,7 @@ export default function Zap({
const isNotification = variant === 'notification' const isNotification = variant === 'notification'
const isProfileWall = variant === 'profileWall' const isProfileWall = variant === 'profileWall'
const showAmount = isNotification && amount != null && amount > 0
const showAsSuperchat = isProfileWall || attested const showAsSuperchat = isProfileWall || attested
const hasMetaLine = const hasMetaLine =
isProfileWall || isProfileWall ||
@ -97,7 +98,8 @@ export default function Zap({
((recipientPubkey && recipientPubkey !== senderPubkey) || isEventZap || isProfileZap)) ((recipientPubkey && recipientPubkey !== senderPubkey) || isEventZap || isProfileZap))
return ( return (
<div className={cn('text-sm text-muted-foreground', className)}> <div className={cn('min-w-0', className)}>
<div className="text-sm text-muted-foreground">
{hasMetaLine ? ( {hasMetaLine ? (
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-sm"> <div className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-sm">
{isProfileWall ? ( {isProfileWall ? (
@ -108,14 +110,8 @@ export default function Zap({
showAt showAt
className="min-w-0 font-medium text-foreground/85 hover:text-foreground" className="min-w-0 font-medium text-foreground/85 hover:text-foreground"
/> />
{amount != null ? (
<span className="shrink-0 text-sm font-bold tabular-nums tracking-tight text-foreground">
{formatAmount(amount)} {t('sats')}
</span>
) : null}
<SuperchatPaymentMethodLabel <SuperchatPaymentMethodLabel
paytoType={paytoType} paytoType={paytoType}
iconOnly
className="shrink-0" className="shrink-0"
imgClassName="size-5" imgClassName="size-5"
/> />
@ -163,7 +159,7 @@ export default function Zap({
imgClassName="size-5" imgClassName="size-5"
/> />
<span className={cn('text-xl', superchatTitleClass)}>{t('Superchat')}</span> <span className={cn('text-xl', superchatTitleClass)}>{t('Superchat')}</span>
{amount != null ? ( {showAmount ? (
<span className="text-xl font-bold tabular-nums tracking-tight text-foreground"> <span className="text-xl font-bold tabular-nums tracking-tight text-foreground">
{formatAmount(amount)} {t('sats')} {formatAmount(amount)} {t('sats')}
</span> </span>
@ -173,7 +169,7 @@ export default function Zap({
<> <>
<ZapIcon className="size-5 shrink-0 text-primary" aria-hidden /> <ZapIcon className="size-5 shrink-0 text-primary" aria-hidden />
<span className="text-lg font-semibold text-foreground">{t('Zap')}</span> <span className="text-lg font-semibold text-foreground">{t('Zap')}</span>
{amount != null ? ( {showAmount ? (
<span className="text-lg font-bold tabular-nums tracking-tight text-foreground"> <span className="text-lg font-bold tabular-nums tracking-tight text-foreground">
{formatAmount(amount)} {t('sats')} {formatAmount(amount)} {t('sats')}
</span> </span>
@ -182,16 +178,19 @@ export default function Zap({
)} )}
</div> </div>
) : null} ) : null}
</div>
{comment ? ( {comment ? (
<SuperchatCommentMarkdown event={event} comment={comment} className="mt-2" /> <SuperchatCommentMarkdown event={event} comment={comment} className="mt-2" />
) : null} ) : null}
{isNotification ? ( {isNotification ? (
<div className="text-sm text-muted-foreground">
<TurnIntoSuperchatButton <TurnIntoSuperchatButton
event={event} event={event}
prominent prominent
attestationRecipientPubkey={attestationRecipientPubkey} attestationRecipientPubkey={attestationRecipientPubkey}
className="mt-3" className="mt-3"
/> />
</div>
) : null} ) : null}
</div> </div>
) )

9
src/lib/payto-registry.ts

@ -156,6 +156,15 @@ export function getPaytoIconChar(type: string): string | null {
return getPaytoTypeRecord(type)?.symbol ?? null return getPaytoTypeRecord(type)?.symbol ?? null
} }
/** True when {@link PaytoTypeIcon} renders lightning, logo, or symbol — not the unknown fallback. */
export function paytoTypeHasDisplayIcon(type: string): boolean {
const canonical = getCanonicalPaytoType(type)
if (isLightningPaytoType(canonical)) return true
if (getPaytoLogoPath(canonical)) return true
if (getPaytoIconChar(canonical) != null) return true
return false
}
export function isLightningPaytoType(type: string): boolean { export function isLightningPaytoType(type: string): boolean {
const canonical = getCanonicalPaytoType(type) const canonical = getCanonicalPaytoType(type)
return canonical === 'lightning' || canonical === 'bip353' return canonical === 'lightning' || canonical === 'bip353'

1
src/lib/payto.ts

@ -11,6 +11,7 @@ export {
getPaytoEditorTypeLabel, getPaytoEditorTypeLabel,
getPaytoIconChar, getPaytoIconChar,
getPaytoLogoPath, getPaytoLogoPath,
paytoTypeHasDisplayIcon,
getPaytoLogoUrl, getPaytoLogoUrl,
getPaytoTypeInfo, getPaytoTypeInfo,
isKnownPaytoType, isKnownPaytoType,

11
src/lib/superchat-ui.ts

@ -29,3 +29,14 @@ export const superchatSatsLeadingHighlightClass = 'text-amber-600 dark:text-yell
/** Lightning bolt accent (zap address rows, payto icons). */ /** Lightning bolt accent (zap address rows, payto icons). */
export const superchatLightningAccentClass = 'text-amber-600 dark:text-yellow-400' export const superchatLightningAccentClass = 'text-amber-600 dark:text-yellow-400'
/**
* Superchat / zap comment body (thread + profile wall).
* MarkdownArticle uses `div[role="paragraph"]`, not `<p>`; sizes must not inherit parent `text-sm`.
*/
export const superchatCommentBodyClass =
'border-l-[3px] border-amber-700 pl-3.5 dark:border-amber-300 ' +
'max-w-none text-[1.3125rem] font-medium leading-snug text-foreground ' +
'[&_[role=paragraph]]:text-[1.3125rem] [&_[role=paragraph]]:font-medium [&_[role=paragraph]]:leading-snug ' +
'[&_p]:text-[1.3125rem] [&_p]:font-medium [&_p]:leading-snug ' +
'prose-p:text-[1.3125rem] prose-p:font-medium prose-p:leading-snug'

Loading…
Cancel
Save