feat(admin): per-feed Subscribe chips with copy/open/validate for RSS/Atom/JSON

Replace the stacked RSS/Atom URL rows in the dashboard with a compact
"Subscribe" chip block exposing all three feed formats — including JSON
Feed, previously absent from the admin UI. Each chip carries copy, open,
and validate actions; validation links to the W3C Feed Validator (RSS/Atom)
and validator.jsonfeed.org (JSON). The Table view's RSS+Atom columns fold
into a single Formats column.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-24 23:08:27 +02:00
parent 1a4a479190
commit b3a979fd03
7 changed files with 322 additions and 89 deletions
+32
View File
@@ -13,6 +13,10 @@ export function feedAtomUrl(feedId: string, env: Env): string {
return `${baseUrl(env)}/atom/${feedId}`;
}
export function feedJsonUrl(feedId: string, env: Env): string {
return `${baseUrl(env)}/json/${feedId}`;
}
export function feedUrl(
format: "rss" | "atom",
feedId: string,
@@ -21,6 +25,34 @@ export function feedUrl(
return format === "rss" ? feedRssUrl(feedId, env) : feedAtomUrl(feedId, env);
}
export type FeedFormat = "rss" | "atom" | "json";
export function feedFormatUrl(
format: FeedFormat,
feedId: string,
env: Env,
): string {
if (format === "atom") return feedAtomUrl(feedId, env);
if (format === "json") return feedJsonUrl(feedId, env);
return feedRssUrl(feedId, env);
}
/**
* Link to a third-party validator for the public feed URL: the W3C Feed
* Validator for RSS/Atom, validator.jsonfeed.org for JSON Feed. Used in the
* admin UI so an operator can confirm a feed parses in real readers.
*/
export function feedValidatorUrl(
format: FeedFormat,
feedId: string,
env: Env,
): string {
const encoded = encodeURIComponent(feedFormatUrl(format, feedId, env));
return format === "json"
? `https://validator.jsonfeed.org/?url=${encoded}`
: `https://validator.w3.org/feed/check.cgi?url=${encoded}`;
}
export function feedEmailAddress(mailboxId: string, env: Env): string {
// The mailbox→address shape lives on the VO; this edge only resolves the domain.
return MailboxId.unchecked(mailboxId).emailAddress(