- Anyone who can see a play can read its comments; the player and the people they follow can write. The author, the player and admins can delete; comments can be reported (admins see the text). - Notifications: "commented on" for the player, "replied on" for earlier commenters, each with its own setting. Reaction and comment notifications now open the play. - A 💬 chip with the count next to reactions on the feed and recent plays, opening the thread on the app (/play/[id]) and the website. Threads refresh every few seconds while open. - Blocking removes comments between the two people. Plays with comments survive history trimming. - Privacy page updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y2iUsKEWDuRGjK5FPmcUP
67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import { Reactions } from './Reactions';
|
|
import { SERVICE_LABELS, openUrl, timeAgo, type ProfileDTO, type RecentPlayDTO, type StreamingService } from '@musicshare/shared';
|
|
|
|
export function RecentPlays({
|
|
plays,
|
|
preferred,
|
|
attribution = {},
|
|
ownerId,
|
|
}: {
|
|
plays: RecentPlayDTO[];
|
|
/** Whose plays these are (they can't react to their own). */
|
|
ownerId: string;
|
|
preferred: StreamingService | null | undefined;
|
|
attribution?: ProfileDTO['attribution'];
|
|
}) {
|
|
const credits = (['lastfm', 'listenbrainz'] as const).filter((s) => attribution[s] && plays.some((p) => p.source === s));
|
|
if (plays.length === 0) return null;
|
|
return (
|
|
<section className="card">
|
|
<h2>Recently played</h2>
|
|
<ul className="recent">
|
|
{plays.map((p, i) => {
|
|
const target = preferred ? openUrl(p.track, preferred) : null;
|
|
const row = (
|
|
<>
|
|
{p.track.artworkUrl ? <img className="artwork-sm" src={p.track.artworkUrl} alt="" /> : <div className="artwork-sm artwork-empty">♪</div>}
|
|
<span className="recent-text">
|
|
<span className="recent-title">{p.track.title}</span>
|
|
<span className="muted">{p.track.artist}</span>
|
|
</span>
|
|
<span className="muted recent-time">{timeAgo(p.playedAt)}</span>
|
|
</>
|
|
);
|
|
return (
|
|
<li key={p.listenId ?? `${p.playedAt}-${i}`}>
|
|
{target ? (
|
|
<a
|
|
className="recent-row"
|
|
href={target.url}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
title={`${target.exact ? 'Open' : 'Search'} in ${SERVICE_LABELS[preferred!]}`}
|
|
>
|
|
{row}
|
|
</a>
|
|
) : (
|
|
<div className="recent-row">{row}</div>
|
|
)}
|
|
<Reactions listenId={p.listenId} reactions={p.reactions} ownerId={ownerId} commentCount={p.commentCount ?? 0} />
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
{credits.map((s) => (
|
|
<p key={s} className="muted credit">
|
|
Some plays via{' '}
|
|
<a href={attribution[s]} target="_blank" rel="noreferrer">
|
|
{s === 'lastfm' ? 'Last.fm' : 'ListenBrainz'}
|
|
</a>
|
|
</p>
|
|
))}
|
|
</section>
|
|
);
|
|
}
|