mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-05 10:11:12 +02:00
feat(polls): add rich text support to poll renderers
- Add RichText rendering for poll questions in both feed and detail views - Add RichText rendering for poll options with support for mentions, links, emojis - Add RichText rendering for poll response option names - Fix icon shrinking by adding shrink-0 flex-shrink-0 classes to all icons - Pass poll event context to RichText for proper mention resolution
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
countVotes,
|
||||
getUniqueVoterCount,
|
||||
} from "@/lib/nip88-helpers";
|
||||
import { RichText } from "../RichText";
|
||||
|
||||
/**
|
||||
* Detail renderer for Kind 1068 - Poll (NIP-88)
|
||||
@@ -105,9 +106,9 @@ export function PollDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
{/* Poll Type Badge */}
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
{pollType === "multiplechoice" ? (
|
||||
<ListChecks className="size-5" />
|
||||
<ListChecks className="size-5 shrink-0 flex-shrink-0" />
|
||||
) : (
|
||||
<ListCheck className="size-5" />
|
||||
<ListCheck className="size-5 shrink-0 flex-shrink-0" />
|
||||
)}
|
||||
<span className="text-sm uppercase tracking-wide">
|
||||
{pollType === "multiplechoice"
|
||||
@@ -124,7 +125,11 @@ export function PollDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
|
||||
{/* Question */}
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{question || "Poll"}
|
||||
<RichText
|
||||
content={question || "Poll"}
|
||||
event={event}
|
||||
options={{ showMedia: false, showEventEmbeds: false }}
|
||||
/>
|
||||
</h1>
|
||||
|
||||
{/* Author */}
|
||||
@@ -137,7 +142,7 @@ export function PollDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
{/* Stats */}
|
||||
<div className="flex items-center gap-6 py-3 border-y border-border">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="size-4 text-muted-foreground" />
|
||||
<Users className="size-4 shrink-0 flex-shrink-0 text-muted-foreground" />
|
||||
<span className="font-medium">{voterCount}</span>
|
||||
<span className="text-muted-foreground">
|
||||
{voterCount === 1 ? "voter" : "voters"}
|
||||
@@ -145,7 +150,7 @@ export function PollDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
</div>
|
||||
{endsAt && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="size-4 text-muted-foreground" />
|
||||
<Clock className="size-4 shrink-0 flex-shrink-0 text-muted-foreground" />
|
||||
<span className="text-muted-foreground">
|
||||
{ended ? "Ended" : "Ends"} {endTimeText}
|
||||
</span>
|
||||
@@ -187,23 +192,27 @@ export function PollDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
{pollType === "multiplechoice" ? (
|
||||
<CheckCircle2
|
||||
className={`size-4 ${isWinner ? "text-primary" : "text-muted-foreground"}`}
|
||||
className={`size-4 shrink-0 flex-shrink-0 ${isWinner ? "text-primary" : "text-muted-foreground"}`}
|
||||
/>
|
||||
) : (
|
||||
<CircleDot
|
||||
className={`size-4 ${isWinner ? "text-primary" : "text-muted-foreground"}`}
|
||||
className={`size-4 shrink-0 flex-shrink-0 ${isWinner ? "text-primary" : "text-muted-foreground"}`}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={`font-medium ${isWinner ? "text-foreground" : ""}`}
|
||||
<div
|
||||
className={`font-medium min-w-0 flex-1 ${isWinner ? "text-foreground" : ""}`}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
<RichText
|
||||
content={option.label}
|
||||
event={event}
|
||||
options={{ showMedia: false, showEventEmbeds: false }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 text-sm shrink-0">
|
||||
<span
|
||||
className={`font-mono ${isWinner ? "font-bold" : "text-muted-foreground"}`}
|
||||
>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getPollEndsAt,
|
||||
isPollEnded,
|
||||
} from "@/lib/nip88-helpers";
|
||||
import { RichText } from "../RichText";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 1068 - Poll (NIP-88)
|
||||
@@ -43,9 +44,9 @@ export function PollRenderer({ event }: BaseEventProps) {
|
||||
{/* Poll Header */}
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
{pollType === "multiplechoice" ? (
|
||||
<ListChecks className="size-4" />
|
||||
<ListChecks className="size-4 shrink-0 flex-shrink-0" />
|
||||
) : (
|
||||
<ListCheck className="size-4" />
|
||||
<ListCheck className="size-4 shrink-0 flex-shrink-0" />
|
||||
)}
|
||||
<span className="text-xs uppercase tracking-wide">
|
||||
{pollType === "multiplechoice"
|
||||
@@ -60,7 +61,11 @@ export function PollRenderer({ event }: BaseEventProps) {
|
||||
event={event}
|
||||
className="text-base font-semibold text-foreground leading-tight"
|
||||
>
|
||||
{question || "Poll"}
|
||||
<RichText
|
||||
content={question || "Poll"}
|
||||
event={event}
|
||||
options={{ showMedia: false, showEventEmbeds: false }}
|
||||
/>
|
||||
</ClickableEventTitle>
|
||||
|
||||
{/* Options Preview */}
|
||||
@@ -72,11 +77,18 @@ export function PollRenderer({ event }: BaseEventProps) {
|
||||
className="flex items-center gap-2 text-sm text-muted-foreground"
|
||||
>
|
||||
{pollType === "multiplechoice" ? (
|
||||
<CheckCircle2 className="size-3.5 shrink-0" />
|
||||
<CheckCircle2 className="size-3.5 shrink-0 flex-shrink-0" />
|
||||
) : (
|
||||
<CircleDot className="size-3.5 shrink-0" />
|
||||
<CircleDot className="size-3.5 shrink-0 flex-shrink-0" />
|
||||
)}
|
||||
<span className="truncate">{option.label}</span>
|
||||
<div className="truncate min-w-0 flex-1">
|
||||
<RichText
|
||||
content={option.label}
|
||||
event={event}
|
||||
options={{ showMedia: false, showEventEmbeds: false }}
|
||||
className="truncate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{options.length > 4 && (
|
||||
@@ -90,7 +102,7 @@ export function PollRenderer({ event }: BaseEventProps) {
|
||||
{/* Deadline */}
|
||||
{endsAt && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Clock className="size-3" />
|
||||
<Clock className="size-3 shrink-0 flex-shrink-0" />
|
||||
{ended ? (
|
||||
<span>Ended {endTimeText}</span>
|
||||
) : (
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getPollOptions,
|
||||
getPollType,
|
||||
} from "@/lib/nip88-helpers";
|
||||
import { RichText } from "../RichText";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 1018 - Poll Response (NIP-88)
|
||||
@@ -50,19 +51,28 @@ export function PollResponseRenderer({ event, depth = 0 }: BaseEventProps) {
|
||||
return pollType === "singlechoice" ? labels.slice(0, 1) : labels;
|
||||
}, [selectedOptions, pollEvent, pollType]);
|
||||
|
||||
const displayText =
|
||||
displayedLabels.length > 0 ? displayedLabels.join(", ") : "unknown option";
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Vote indicator */}
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Vote className="size-4" />
|
||||
<Vote className="size-4 shrink-0 flex-shrink-0" />
|
||||
<span className="text-sm">
|
||||
Voted for:{" "}
|
||||
{displayedLabels.length > 0 ? (
|
||||
<span className="text-foreground font-medium">{displayText}</span>
|
||||
<span className="text-foreground font-medium">
|
||||
{displayedLabels.map((label, idx) => (
|
||||
<span key={idx}>
|
||||
<RichText
|
||||
content={label}
|
||||
event={pollEvent || event}
|
||||
options={{ showMedia: false, showEventEmbeds: false }}
|
||||
className="inline"
|
||||
/>
|
||||
{idx < displayedLabels.length - 1 && ", "}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
) : (
|
||||
<span className="italic">unknown option</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user