mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
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:
@@ -0,0 +1,58 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
feedRssUrl,
|
||||
feedAtomUrl,
|
||||
feedJsonUrl,
|
||||
feedFormatUrl,
|
||||
feedValidatorUrl,
|
||||
} from "./urls";
|
||||
import { Env } from "../types";
|
||||
|
||||
const env = { DOMAIN: "getmynews.app" } as Env;
|
||||
const feedId = "gAf6wiKyanpppcKX9o3B_Q";
|
||||
|
||||
describe("feed URL builders", () => {
|
||||
it("builds RSS/Atom/JSON feed URLs", () => {
|
||||
expect(feedRssUrl(feedId, env)).toBe(
|
||||
"https://getmynews.app/rss/gAf6wiKyanpppcKX9o3B_Q",
|
||||
);
|
||||
expect(feedAtomUrl(feedId, env)).toBe(
|
||||
"https://getmynews.app/atom/gAf6wiKyanpppcKX9o3B_Q",
|
||||
);
|
||||
expect(feedJsonUrl(feedId, env)).toBe(
|
||||
"https://getmynews.app/json/gAf6wiKyanpppcKX9o3B_Q",
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves a format to its feed URL", () => {
|
||||
expect(feedFormatUrl("rss", feedId, env)).toBe(feedRssUrl(feedId, env));
|
||||
expect(feedFormatUrl("atom", feedId, env)).toBe(feedAtomUrl(feedId, env));
|
||||
expect(feedFormatUrl("json", feedId, env)).toBe(feedJsonUrl(feedId, env));
|
||||
});
|
||||
});
|
||||
|
||||
describe("feedValidatorUrl", () => {
|
||||
it("points JSON feeds at validator.jsonfeed.org with the encoded feed URL", () => {
|
||||
expect(feedValidatorUrl("json", feedId, env)).toBe(
|
||||
"https://validator.jsonfeed.org/?url=" +
|
||||
encodeURIComponent(feedJsonUrl(feedId, env)),
|
||||
);
|
||||
});
|
||||
|
||||
it("points RSS and Atom feeds at the W3C feed validator", () => {
|
||||
expect(feedValidatorUrl("rss", feedId, env)).toBe(
|
||||
"https://validator.w3.org/feed/check.cgi?url=" +
|
||||
encodeURIComponent(feedRssUrl(feedId, env)),
|
||||
);
|
||||
expect(feedValidatorUrl("atom", feedId, env)).toBe(
|
||||
"https://validator.w3.org/feed/check.cgi?url=" +
|
||||
encodeURIComponent(feedAtomUrl(feedId, env)),
|
||||
);
|
||||
});
|
||||
|
||||
it("percent-encodes the feed URL so the validator query is well-formed", () => {
|
||||
const url = feedValidatorUrl("json", feedId, env);
|
||||
expect(url).toContain("https%3A%2F%2Fgetmynews.app%2Fjson%2F");
|
||||
expect(url).not.toContain("?url=https://");
|
||||
});
|
||||
});
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user