spoiler veil for author too; veil history comments
Some checks failed
CI / Check / Test (push) Has been cancelled
CI / Check / Test (pull_request) Has been cancelled

This commit is contained in:
2026-10-25 19:50:11 +01:00
parent 21b6ff8f60
commit d694df4d8a
11 changed files with 18 additions and 31 deletions

View File

@@ -45,9 +45,8 @@
{% if let Some(wm) = entry.review().watch_medium() %}
<span class="watch-medium">{{ wm.label() }}</span>
{% endif %}
{% let author = ctx.is_current_user(entry.review().user_id().value()) %}
{% if let Some(comment) = entry.review().comment() %}
{% if !author && entry.review().spoilers() %}
{% if entry.review().spoilers() %}
<details class="comment spoiler">
<summary style="cursor:pointer;opacity:.75">Contains spoilers</summary>
{{ comment.value() }}

View File

@@ -129,9 +129,8 @@
<span class="star {% if filled %}filled{% else %}empty{% endif %}">★</span>
{% endfor %}
</div>
{% let author = ctx.is_current_user(entry.review().user_id().value()) %}
{% if let Some(comment) = entry.review().comment() %}
{% if !author && entry.review().spoilers() %}
{% if entry.review().spoilers() %}
<details class="comment spoiler">
<summary style="cursor:pointer;opacity:.75">Contains spoilers</summary>
{{ comment.value() }}

View File

@@ -219,9 +219,8 @@
<span class="star {% if filled %}filled{% else %}empty{% endif %}">★</span>
{% endfor %}
</div>
{% let author = ctx.is_current_user(entry.review().user_id().value()) %}
{% if let Some(comment) = entry.review().comment() %}
{% if !author && entry.review().spoilers() %}
{% if entry.review().spoilers() %}
<details class="comment spoiler">
<summary style="cursor:pointer;opacity:.75">Contains spoilers</summary>
{{ comment.value() }}

View File

@@ -10,12 +10,10 @@ import type { SocialReviewDto } from "@/features/movies"
type CommunityReviewsProps = {
reviews: { items: SocialReviewDto[] }
/** Authenticated viewer id — own reviews are never veiled. */
currentUserId?: string
onShowDetail?: (review: SocialReviewDto) => void
}
export function CommunityReviews({ reviews, currentUserId, onShowDetail }: CommunityReviewsProps) {
export function CommunityReviews({ reviews, onShowDetail }: CommunityReviewsProps) {
const { t } = useTranslation()
return (
@@ -26,7 +24,7 @@ export function CommunityReviews({ reviews, currentUserId, onShowDetail }: Commu
) : (
<div className="space-y-2">
{reviews.items.map((r, i) => {
const veiled = !!r.spoilers && currentUserId !== r.user_id
const veiled = !!r.spoilers
return (
<Card key={i} size="sm">
<CardHeader>

View File

@@ -94,7 +94,6 @@ export function FeedTab() {
onLoadMore={loadMore}
renderItem={(entry) => {
const isOwn = entry.user_id === auth?.user_id
const veil = !isOwn && !!entry.review.spoilers
const card = (
<ReviewCard
movie={entry.movie}
@@ -103,7 +102,6 @@ export function FeedTab() {
userId={entry.user_id}
isFederated={entry.is_federated}
actorUrl={entry.actor_url}
veil={veil}
onEdit={isOwn ? () => setEditingEntry(entry) : undefined}
onShowDetail={entry.review.comment ? () => setDetailEntry(entry) : undefined}
/>
@@ -141,7 +139,6 @@ export function FeedTab() {
movie={detailEntry.movie}
review={detailEntry.review}
userName={detailEntry.user_display_name}
veil={detailEntry.user_id !== auth?.user_id}
/>
)}
</div>

View File

@@ -18,7 +18,6 @@ import { TimeAgo } from "@/components/time-ago"
import { WATCH_MEDIUMS } from "@/lib/watch-mediums"
import { ReviewDetailSheet } from "@/components/review-detail-sheet"
import { DiaryCalendar } from "@/components/diary-calendar"
import { useAuth } from "@/components/auth-provider"
import type { DiaryEntryDto } from "@/lib/api/common"
import { tmdbProfileUrl } from "@/lib/api/client"
import type { UserProfileResponse } from "@/features/users"
@@ -151,7 +150,6 @@ function StatCell({ label, value }: { label: string; value: string | number }) {
function DiaryTab({ sortBy, userId, search }: { sortBy: string; userId?: string; search?: string }) {
const { t } = useTranslation()
const { auth } = useAuth()
const { data, isPending, hasNextPage, isFetchingNextPage, fetchNextPage } =
useInfiniteDiary({ sort_by: sortBy, user_id: userId })
const items = data?.pages.flatMap((p) => p.items) ?? []
@@ -181,7 +179,7 @@ function DiaryTab({ sortBy, userId, search }: { sortBy: string; userId?: string;
comment={e.review.comment}
subtitle={<><TimeAgo date={e.review.watched_at} /></>}
variant="compact"
spoilers={userId && userId === auth?.user_id ? undefined : e.review.spoilers}
spoilers={e.review.spoilers}
onShowDetail={e.review.comment ? () => setDetailEntry(e) : undefined}
/>
)}
@@ -192,7 +190,6 @@ function DiaryTab({ sortBy, userId, search }: { sortBy: string; userId?: string;
onOpenChange={(open) => !open && setDetailEntry(null)}
movie={detailEntry.movie}
review={detailEntry.review}
veil={!!userId && userId !== auth?.user_id}
/>
)}
</>

View File

@@ -17,13 +17,12 @@ type ReviewCardProps = {
userId?: string
isFederated?: boolean
actorUrl?: string
/** Blur the comment until revealed — set for every viewer except the author. */
veil?: boolean
onEdit?: () => void
onShowDetail?: () => void
}
export function ReviewCard({ movie, review, userName, userId, isFederated, actorUrl, veil, onEdit, onShowDetail }: ReviewCardProps) {
export function ReviewCard({ movie, review, userName, userId, isFederated, actorUrl, onEdit, onShowDetail }: ReviewCardProps) {
const veil = !!review.spoilers
const showDetail = veil ? undefined : onShowDetail
const card = (
<Card size="sm">

View File

@@ -13,11 +13,9 @@ type ReviewDetailSheetProps = {
movie: MovieDto
review: ReviewDto
userName?: string
/** Blur the comment until revealed — set for every viewer except the author. */
veil?: boolean
}
export function ReviewDetailSheet({ open, onOpenChange, movie, review, userName, veil }: ReviewDetailSheetProps) {
export function ReviewDetailSheet({ open, onOpenChange, movie, review, userName }: ReviewDetailSheetProps) {
return (
<Drawer open={open} onOpenChange={onOpenChange}>
<DrawerContent className="mx-auto max-w-lg">
@@ -43,7 +41,7 @@ export function ReviewDetailSheet({ open, onOpenChange, movie, review, userName,
</div>
{review.comment && (
veil && review.spoilers ? (
review.spoilers ? (
<SpoilerVeil content={review.comment} className="text-sm leading-relaxed" />
) : (
<p className="select-text whitespace-pre-wrap text-sm leading-relaxed">

View File

@@ -10,8 +10,8 @@ type SpoilerVeilProps = {
}
/**
* Hides review text behind a blur with a reveal button — shown to everyone
* except the review's author. Once revealed, the text stays visible.
* Hides review text behind a blur with a reveal button — shown to every
* viewer, the author included. Once revealed, the text stays visible.
*/
export function SpoilerVeil({ content, className }: SpoilerVeilProps) {
const { t } = useTranslation()

View File

@@ -1,6 +1,7 @@
import { useTranslation } from "react-i18next"
import { TrendingUp } from "lucide-react"
import { StarDisplay } from "@/components/star-display"
import { SpoilerVeil } from "@/components/spoiler-veil"
import { shortDate } from "@/lib/date"
import type { ReviewHistoryResponse } from "@/features/movies"
@@ -24,7 +25,11 @@ export function ViewingHistory({ history }: { history: ReviewHistoryResponse })
<div>
<p className="text-sm font-medium">{shortDate(v.watched_at)}</p>
{v.comment && (
v.spoilers ? (
<SpoilerVeil content={v.comment} className="mt-0.5 text-xs text-muted-foreground line-clamp-1" />
) : (
<p className="mt-0.5 text-xs text-muted-foreground line-clamp-1">{v.comment}</p>
)
)}
</div>
<StarDisplay rating={v.rating} size="xs" />

View File

@@ -12,7 +12,6 @@ import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
import { posterUrl, tmdbProfileUrl } from "@/lib/api/client"
import { useAuth } from "@/components/auth-provider"
import { useMovie, useMovieHistory, useMovieProfile } from "@/features/movies"
import { useDocumentTitle } from "@/hooks/use-document-title"
import {
@@ -30,7 +29,6 @@ function MovieDetailPage() {
const { t } = useTranslation()
const { id } = Route.useParams()
const { data, isPending } = useMovie(id)
const { auth } = useAuth()
const { data: profile } = useMovieProfile(id)
const { data: history } = useMovieHistory(id)
useDocumentTitle(data?.movie.title)
@@ -109,7 +107,6 @@ function MovieDetailPage() {
<CommunityReviews
reviews={reviews}
currentUserId={auth?.user_id}
onShowDetail={setDetailReview}
/>
@@ -122,7 +119,6 @@ function MovieDetailPage() {
movie={movie}
review={{ id: "", rating: detailReview.rating, comment: detailReview.comment, watched_at: detailReview.watched_at, watch_medium: detailReview.watch_medium, spoilers: detailReview.spoilers }}
userName={detailReview.user_display}
veil={detailReview.user_id !== auth?.user_id}
/>
)}
</div>