Files
website/src/components/VersionSelector.svelte
T
swaits cfa6cf2be2 feat: style release changelog with color-coded sections
Gives the GitHub-release-body markdown real typography on the
/flash page:

- `## [version]` becomes a bordered mono version heading
- `### Added / Changed / Fixed / Removed / Deprecated / Security`
  render as uppercase section headers, color-coded per
  Keep-a-Changelog semantics (green / yellow / blue / red)
- Nested bullet lists switch marker style (disc → circle → square)
- Inline `code`, fenced `pre`, hr, strong, em, blockquote all styled
- Proper vertical rhythm (line-height 1.65, section spacing)

To colorize the section headers, `VersionSelector`.renderBody now
post-processes marked's output to tag known h3 texts with a
`data-section` attribute that CSS targets.

Rules live in `src/styles/global.css` under `@layer base`, not in
the Svelte component's `<style>` block: the Astro + Svelte 5 build
pipeline was silently dropping the component's scoped CSS in the
production bundle (works in dev via HMR, but `dist/_astro/*.css`
shipped with zero `.changelog` rules), leaving the netlify site
completely unstyled. Moving to global CSS sidesteps that toolchain
issue; the `.changelog` parent selector is specific enough that no
scope leakage is possible.
2026-04-11 23:17:59 -06:00

85 lines
2.9 KiB
Svelte

<script lang="ts">
import type { Release } from "../lib/types";
import { marked } from "marked";
interface Props {
releases: Release[];
selected: Release | null;
onselect: (release: Release) => void;
loading: boolean;
error: string | null;
}
let { releases, selected, onselect, loading, error }: Props = $props();
// Tag Keep-a-Changelog section headings so CSS can color-code them
// (Added → green, Changed → yellow, Fixed → blue, etc.).
function renderBody(body: string): string {
const html = marked.parse(body) as string;
return html.replace(
/<h3>(Added|Changed|Fixed|Removed|Deprecated|Security)<\/h3>/g,
(_, text) => `<h3 data-section="${text.toLowerCase()}">${text}</h3>`,
);
}
let changelogHtml = $derived(selected?.body ? renderBody(selected.body) : "");
function handleChange(e: Event) {
const tag = (e.target as HTMLSelectElement).value;
const release = releases.find((r) => r.tag === tag);
if (release) onselect(release);
}
</script>
<div>
<h3 class="font-mono text-sm text-text-muted mb-3 uppercase tracking-wider">Select Version</h3>
{#if loading}
<div class="text-text-dim text-sm font-mono">Loading releases...</div>
{:else if error}
<div class="text-error text-sm">
{error}
<a
href="https://github.com/donglora/firmware/releases"
class="text-accent hover:text-accent-bright ml-2"
target="_blank"
rel="noopener noreferrer"
>
View on GitHub
</a>
</div>
{:else}
<div class="relative">
<select
class="w-full bg-bg-card border border-border rounded px-4 py-2 pr-10 font-mono text-sm text-text
focus:outline-none focus:border-accent appearance-none cursor-pointer"
value={selected?.tag ?? ""}
onchange={handleChange}
>
<option value="" disabled>Choose a version...</option>
{#each releases as release}
<option value={release.tag}>
{release.version}
{release.prerelease ? "(pre-release)" : ""}
— {release.date}
</option>
{/each}
</select>
<svg class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-text-muted" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
{#if selected && changelogHtml}
<div class="mt-4 bg-bg-card border border-border rounded p-5">
<h4 class="font-mono text-xs text-text-dim uppercase tracking-wider mb-4">Changelog</h4>
<!-- Styles live in src/styles/global.css — Svelte component <style>
scoping wasn't being emitted into the prod CSS bundle. -->
<div class="changelog text-sm text-text-muted">
{@html changelogHtml}
</div>
</div>
{/if}
{/if}
</div>