v4: a description shows as text where a list quotes it — a playlist's card printed its description as written, so one holding an image showed "", and so did a user's line in the search results. Both now go through markdownToPlainText (shared/markdown.ts), as the chat's reply previews do: the markup goes, a link keeps its text, an image is left out, and a description of nothing but an image leaves no line. The playlist's page and the profile render the Markdown as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Flattens the Markdown users write into one line of plain text, where a
|
||||
* quote of it can't render markup: the chat's reply previews and the digest
|
||||
* email's quotes of a comment or a note.
|
||||
* quote of it can't render markup: the chat's reply previews, the digest
|
||||
* email's quotes of a comment or a note, and the line of a description on a
|
||||
* playlist's card and on a user in the search results.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { Tooltip } from "../shared/Tooltip.tsx";
|
||||
import { CategoryChips } from "../category/CategoryChips.tsx";
|
||||
import { userPath } from "../../../shared/paths.ts";
|
||||
import { markdownToPlainText } from "../../../shared/markdown.ts";
|
||||
|
||||
/** Passed to {@link PlaylistCard} by each list of playlists. */
|
||||
export interface PlaylistCardProps {
|
||||
@@ -53,6 +54,10 @@ export function PlaylistCard(
|
||||
const unread = !isOwner && isRecent(playlist.createdAt) &&
|
||||
!isPlaylistVisited(playlist.id);
|
||||
const showBadge = !privateBadgeOnly || (!!isOwner && !playlist.isPublic);
|
||||
// The playlist's page renders the description's Markdown; the card has
|
||||
// room for its text only, and none at all when it is a lone image.
|
||||
const description = playlist.description &&
|
||||
markdownToPlainText(playlist.description, "");
|
||||
|
||||
function handleNavigate() {
|
||||
markPlaylistVisited(playlist.id);
|
||||
@@ -91,8 +96,8 @@ export function PlaylistCard(
|
||||
{unread && <span className="unread-dot" aria-hidden="true" />}
|
||||
{playlist.title}
|
||||
</Link>
|
||||
{playlist.description && (
|
||||
<p className="playlist-card-description">{playlist.description}</p>
|
||||
{description && (
|
||||
<p className="playlist-card-description">{description}</p>
|
||||
)}
|
||||
<div className="playlist-card-meta">
|
||||
{showBadge && (
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
userPath,
|
||||
} from "../../shared/paths.ts";
|
||||
import { NotFound } from "./NotFound.tsx";
|
||||
import { markdownToPlainText } from "../../shared/markdown.ts";
|
||||
|
||||
/**
|
||||
* Held by `Search` for a query, from the first page of `SearchResults`. The
|
||||
@@ -297,21 +298,27 @@ export function Search(): React.JSX.Element {
|
||||
? <p className="search-page-empty">{t`No users match "${q}".`}</p>
|
||||
: (
|
||||
<ul className="user-results">
|
||||
{results.users.map((u) => (
|
||||
<li key={u.id}>
|
||||
<Link
|
||||
to={userPath(u.username)}
|
||||
className="user-result-item"
|
||||
>
|
||||
<span className="user-result-name">@{u.username}</span>
|
||||
{u.description && (
|
||||
<span className="user-result-description">
|
||||
{u.description}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
{results.users.map((u) => {
|
||||
// The profile renders the description's Markdown; this
|
||||
// line has room for its text only.
|
||||
const description = u.description &&
|
||||
markdownToPlainText(u.description, "");
|
||||
return (
|
||||
<li key={u.id}>
|
||||
<Link
|
||||
to={userPath(u.username)}
|
||||
className="user-result-item"
|
||||
>
|
||||
<span className="user-result-name">@{u.username}</span>
|
||||
{description && (
|
||||
<span className="user-result-description">
|
||||
{description}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -38,6 +38,34 @@ describe("PlaylistCard", () => {
|
||||
expect(currentPath()).toBe("/@ann/playlists/mix");
|
||||
});
|
||||
|
||||
test("the description shows as text, without its Markdown or its images", () => {
|
||||
const withText = renderWithContexts(
|
||||
<ul>
|
||||
<PlaylistCard
|
||||
playlist={makePlaylist({
|
||||
description:
|
||||
"**Late** [tunes](https://x.y) ",
|
||||
})}
|
||||
/>
|
||||
</ul>,
|
||||
);
|
||||
expect(
|
||||
withText.container.querySelector(".playlist-card-description"),
|
||||
).toHaveTextContent(/^Late tunes$/);
|
||||
withText.unmount();
|
||||
|
||||
const imageOnly = renderWithContexts(
|
||||
<ul>
|
||||
<PlaylistCard
|
||||
playlist={makePlaylist({ description: "" })}
|
||||
/>
|
||||
</ul>,
|
||||
);
|
||||
expect(
|
||||
imageOnly.container.querySelector(".playlist-card-description"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test("the owner doesn't see their own name, or an unread dot", () => {
|
||||
const p = makePlaylist({ ownerUsername: "me", createdAt: new Date() });
|
||||
const { container } = renderWithContexts(
|
||||
|
||||
Reference in New Issue
Block a user