Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit b7601fe123a4b6bcd243dfcf83c5270be8740c3d
parent ce51f06be88e5591c891663e54ff1262ede0167f
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 18:42:08 -0400

editor: "Show in header" on each social link, with its hint; a refused icon's save names why

The social-links editor (Settings, a site's form) gains one checkbox per row,
stored as `featured: true` only when checked, and beside it "With none checked,
the header shows the last four." The posted JSON is built by socialLinksJson
(pure, unit tested); the settings spec reads `featured` back. A refused SVG's
error names its reason class (an event handler attribute, a link to something
outside the icon, …) without echoing the markup. Existing labels, placeholders
and buttons are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/components/SocialLinksField.tsx | 34+++++++++++++++++++++++++---------
Aeditor/app/components/socialLinksJson.test.ts | 23+++++++++++++++++++++++
Aeditor/app/components/socialLinksJson.ts | 22++++++++++++++++++++++
Meditor/app/settings/actions.ts | 3++-
Meditor/app/sites/actions.ts | 3++-
Meditor/e2e/settings.spec.ts | 5++++-
6 files changed, 78 insertions(+), 12 deletions(-)

diff --git a/editor/app/components/SocialLinksField.tsx b/editor/app/components/SocialLinksField.tsx @@ -1,11 +1,17 @@ "use client"; import type { SocialLink } from "yt-dlp-transcript-common/lib/settings"; +import { socialLinksJson, type SocialRow } from "./socialLinksJson"; -export type SocialRow = { label: string; url: string; svg: string }; +export type { SocialRow }; export function toSocialRow(s: SocialLink): SocialRow { - return { label: s.label, url: s.url, svg: s.svg }; + return { + label: s.label, + url: s.url, + svg: s.svg, + ...(s.featured ? { featured: true } : {}), + }; } type Props = { @@ -32,13 +38,7 @@ export function SocialLinksField({ value, onChange, name }: Props) { const update = (idx: number, patch: Partial<SocialRow>) => onChange(value.map((row, i) => (i === idx ? { ...row, ...patch } : row))); - const json = JSON.stringify( - value.map((s) => ({ - label: s.label.trim(), - url: s.url.trim(), - svg: s.svg.trim(), - })), - ); + const json = socialLinksJson(value); return ( <> @@ -96,6 +96,22 @@ export function SocialLinksField({ value, onChange, name }: Props) { placeholder='<svg viewBox="0 0 24 24"><path d="…"/></svg>' className="rounded border border-border bg-card px-2 py-1 text-xs font-mono" /> + {/* A header shows at most four links: the ones checked here, or, + with none checked, the last four (common/lib/socialLinks.ts). */} + <div className="flex flex-wrap items-center gap-x-3 gap-y-1"> + <label className="flex items-center gap-2 text-sm"> + <input + type="checkbox" + checked={s.featured === true} + onChange={(e) => update(idx, { featured: e.target.checked })} + className="accent-brand" + /> + Show in header + </label> + <span className="text-xs text-muted-foreground"> + With none checked, the header shows the last four. + </span> + </div> </div> ))} <div> diff --git a/editor/app/components/socialLinksJson.test.ts b/editor/app/components/socialLinksJson.test.ts @@ -0,0 +1,23 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { parseSocialLinks } from "yt-dlp-transcript-common/lib/settingsSchema"; +import { socialLinksJson } from "./socialLinksJson"; + +const SVG = `<svg viewBox="0 0 24 24"><path d="M0 0"/></svg>`; + +test("Show in header: a checked row posts featured: true, an unchecked one no key", () => { + const posted = JSON.parse( + socialLinksJson([ + { label: " A ", url: " https://a.example ", svg: ` ${SVG} `, featured: true }, + { label: "B", url: "https://b.example", svg: SVG, featured: false }, + { label: "C", url: "https://c.example", svg: SVG }, + ]), + ); + assert.deepEqual(posted, [ + { label: "A", url: "https://a.example", svg: SVG, featured: true }, + { label: "B", url: "https://b.example", svg: SVG }, + { label: "C", url: "https://c.example", svg: SVG }, + ]); + // …and the actions' parser keeps exactly that. + assert.deepEqual(parseSocialLinks(posted), posted); +}); diff --git a/editor/app/components/socialLinksJson.ts b/editor/app/components/socialLinksJson.ts @@ -0,0 +1,22 @@ +// What the social-links editor posts: its rows, trimmed, as the JSON both the +// settings and the site actions parse with parseSocialLinks. Pure, so it has a +// unit test (socialLinksJson.test.ts) without a DOM. + +export type SocialRow = { + label: string; + url: string; + svg: string; + // "Show in header". Posted only when checked, the way the schema stores it. + featured?: boolean; +}; + +export function socialLinksJson(rows: readonly SocialRow[]): string { + return JSON.stringify( + rows.map((s) => ({ + label: s.label.trim(), + url: s.url.trim(), + svg: s.svg.trim(), + ...(s.featured ? { featured: true } : {}), + })), + ); +} diff --git a/editor/app/settings/actions.ts b/editor/app/settings/actions.ts @@ -11,6 +11,7 @@ import { RESUME_MARGIN_GB_DEFAULT, RESUME_MARGIN_GB_MAX, normalizeSocialSvg, + socialSvgProblem, parseSocialLinks, PARALLEL_TRANSCRIPTIONS_DEFAULT, SLEEP_BETWEEN_DOWNLOADS_MAX_SECONDS, @@ -172,7 +173,7 @@ export async function saveSettingsAction( if (svg === null) { return { ok: false, - error: `Social link "${link.label}" has an invalid SVG.`, + error: `Social link "${link.label}" has an invalid SVG: ${socialSvgProblem(link.svg)}.`, }; } socialLinks.push({ ...link, svg }); diff --git a/editor/app/sites/actions.ts b/editor/app/sites/actions.ts @@ -21,6 +21,7 @@ import { } from "yt-dlp-transcript-common/lib/site"; import { normalizeSocialSvg, + socialSvgProblem, parseSocialLinks, type SocialLink, } from "yt-dlp-transcript-common/lib/settings"; @@ -182,7 +183,7 @@ export async function saveSiteAction( if (svg === null) { return { ok: false, - error: `Social link "${link.label}" has an invalid SVG.`, + error: `Social link "${link.label}" has an invalid SVG: ${socialSvgProblem(link.svg)}.`, }; } socialLinks.push({ ...link, svg }); diff --git a/editor/e2e/settings.spec.ts b/editor/e2e/settings.spec.ts @@ -42,6 +42,7 @@ test("saves global default social links", async ({ page }) => { await page .getByPlaceholder(/<svg viewbox/i) .fill('<svg viewBox="0 0 24 24"><path d="M0 0h24v24H0z"/></svg>'); + await page.getByRole("checkbox", { name: "Show in header" }).check(); await page.getByRole("button", { name: /save settings/i }).click(); await expect( @@ -49,10 +50,12 @@ test("saves global default social links", async ({ page }) => { ).toBeVisible(); const saved = await readJson<{ - socialLinks?: { label: string; url: string; svg: string }[]; + socialLinks?: { label: string; url: string; svg: string; featured?: boolean }[]; }>("test-settings.json"); expect(saved.socialLinks).toHaveLength(1); expect(saved.socialLinks?.[0].label).toBe("GitHub"); + // "Show in header" is stored as featured: true. + expect(saved.socialLinks?.[0].featured).toBe(true); // SVG was normalized on save (fill="currentColor" injected). expect(saved.socialLinks?.[0].svg).toContain("currentColor"); });