feat(link-preview): frontend interface for Telegram-style URL preview

Adds the front-end side of the link-preview feature so the back-end
team has a fixed contract to implement against.

- docs/link-preview.md: full spec for the `/api/link-preview` proxy
  and the preferred inline-on-Post integration. Covers caching, SSRF
  guards, metadata-extraction precedence, provider quirks, and the
  front-end rendering rules. Scope is the first URL only.
- types/post.ts: new `LinkPreview` type and optional `linkPreview`
  field on `Post`.
- LinkPreviewCard: clickable card with a themeColor accent bar,
  siteName / title / description (line-clamped), and an optional
  1.91:1 thumbnail. Whole card is an `<a target="_blank">` to the
  canonical URL.
- MessageBubble: render the card between the bubble body and the
  timestamp, with padding that matches visual vs. text-only bubbles.
- mockPosts: example `linkPreview` payloads on p-005 and p-010 so
  the visual works when running with VITE_USE_MOCK_POSTS=true,
  and so the back-end has concrete reference values.
This commit is contained in:
TerryM
2026-05-30 01:40:00 +08:00
parent 09d887dd52
commit 29dc71d2dd
5 changed files with 371 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ import { ImageBubble } from "./bubbles/ImageBubble";
import { ImageWithTextBubble } from "./bubbles/ImageWithTextBubble";
import { AlbumBubble } from "./bubbles/AlbumBubble";
import { VideoBubble } from "./bubbles/VideoBubble";
import { LinkPreviewCard } from "./LinkPreviewCard";
import { formatDateTime } from "./utils/formatTime";
type BubbleComponent = ComponentType<{ post: Post }>;
@@ -41,6 +42,11 @@ export function MessageBubble({ post }: { post: Post }) {
}`}
>
<Bubble post={post} />
{post.linkPreview ? (
<div className={isVisual ? "px-4 pt-3" : "mt-3"}>
<LinkPreviewCard preview={post.linkPreview} />
</div>
) : null}
<time
dateTime={post.publishedAt}
className={`block text-right text-[12px] leading-[19px] text-[#A8A9AE] ${