diff --git a/site/astro.config.mjs b/site/astro.config.mjs index 20512b87..b6a54196 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -29,6 +29,7 @@ export default defineConfig({ components: { Footer: './src/components/Footer.astro', PageTitle: './src/components/PageTitle.astro', + Search: './src/components/Search.astro', }, head: [ // Google Analytics (carried over from the Docusaurus site, anonymized IP). diff --git a/site/src/components/HomeHero.astro b/site/src/components/HomeHero.astro index 4d5dad99..9d3f683e 100644 --- a/site/src/components/HomeHero.astro +++ b/site/src/components/HomeHero.astro @@ -2,12 +2,12 @@ // Living Terminal hero: code window + a terminal pane that types `curl` // and streams Echo's JSON response. Animation is client-side, with a // static final-state fallback for reduced-motion / no-JS. -import { starsLabel } from '../data/github.ts'; +import { starsLabel, echoVersion } from '../data/github.ts'; ---
- Echo v5.2 — now released + Echo {echoVersion} — now released

Build fast Go APIs.
Without the bloat.

A high-performance, minimalist Go web framework — a zero-allocation diff --git a/site/src/components/Search.astro b/site/src/components/Search.astro new file mode 100644 index 00000000..05a9b895 --- /dev/null +++ b/site/src/components/Search.astro @@ -0,0 +1,52 @@ +--- +import Default from '@astrojs/starlight/components/Search.astro'; +--- + + + diff --git a/site/src/data/github.ts b/site/src/data/github.ts index e052c8d4..ab9e961d 100644 --- a/site/src/data/github.ts +++ b/site/src/data/github.ts @@ -15,7 +15,7 @@ async function fetchStars(): Promise { const token = process.env.GITHUB_TOKEN; if (token) headers.Authorization = `Bearer ${token}`; - const res = await fetch(`https://api.github.com/repos/${REPO}`, { headers }); + const res = await fetch(`https://api.github.com/repos/${REPO}`, { headers, signal: AbortSignal.timeout(5000) }); if (!res.ok) throw new Error(`GitHub API responded ${res.status}`); const data = await res.json(); return typeof data.stargazers_count === 'number' ? data.stargazers_count : FALLBACK_STARS; @@ -29,3 +29,30 @@ export const stars = await fetchStars(); /** e.g. 32412 -> "32.4k" */ export const starsLabel = stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(stars); + +const FALLBACK_VERSION = 'v5'; + +// Latest released echo/v5, via the Go module proxy — canonical and v5-only +// (avoids GitHub "latest release" occasionally pointing at a v4 patch). +async function fetchLatestVersion(): Promise { + try { + const res = await fetch('https://proxy.golang.org/github.com/labstack/echo/v5/@latest', { + signal: AbortSignal.timeout(5000), + }); + if (!res.ok) throw new Error(`Go proxy responded ${res.status}`); + const data = await res.json(); + const v = typeof data.Version === 'string' ? data.Version.trim() : ''; + // Require a real "vX..." tag — an empty/garbage value would silently render a blank badge. + if (!/^v\d/.test(v)) { + console.warn(`[github] unexpected version ${JSON.stringify(data.Version)}; using fallback ${FALLBACK_VERSION}`); + return FALLBACK_VERSION; + } + return v; + } catch (e) { + console.warn(`[github] version fetch failed; using fallback ${FALLBACK_VERSION}:`, e); + return FALLBACK_VERSION; + } +} + +/** e.g. "v5.2.1" */ +export const echoVersion = await fetchLatestVersion(); diff --git a/site/src/styles/terminal.css b/site/src/styles/terminal.css index 55c8d3dd..b68a6e19 100644 --- a/site/src/styles/terminal.css +++ b/site/src/styles/terminal.css @@ -87,6 +87,34 @@ /* search box (chrome) */ site-search button { border-radius: 8px !important; } +/* Empty search modal: a command-palette launchpad (quick links + shortcut + footer) injected by Search.astro, shown only before a query exists. Pagefind + marks the results drawer .pagefind-ui__hidden when empty. */ +.echo-search-empty { display: none; } +.pagefind-ui:has(.pagefind-ui__drawer.pagefind-ui__hidden) .echo-search-empty { display: block; } +.echo-search-empty { margin-top: 16px; } +.ess-group + .ess-group { margin-top: 14px; } +.ess-label { + font-family: var(--sl-font-mono); font-size: 0.66rem; letter-spacing: 0.12em; + text-transform: uppercase; color: var(--sl-color-gray-4); padding: 0 4px 6px; +} +.ess-link { + display: flex; align-items: center; gap: 11px; padding: 9px 12px; border-radius: 9px; + text-decoration: none; color: var(--sl-color-white); font-size: 0.9rem; line-height: 1; +} +.ess-link i { font-size: 1.05rem; color: var(--echo-cyan-text); width: 1.2em; text-align: center; } +.ess-link .ess-sec { margin-left: auto; font-family: var(--sl-font-mono); font-size: 0.7rem; color: var(--sl-color-gray-4); } +.ess-link:hover, .ess-link:focus-visible { background: var(--echo-soft); outline: none; } +.ess-foot { + display: flex; gap: 18px; justify-content: center; flex-wrap: wrap; + margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--sl-color-hairline); + font-family: var(--sl-font-mono); font-size: 0.72rem; color: var(--sl-color-gray-4); +} +.ess-foot kbd { + font-family: var(--sl-font-mono); font-size: 0.72rem; background: var(--sl-color-bg-inline-code); + border: 1px solid var(--sl-color-hairline); border-radius: 5px; padding: 1px 6px; margin-right: 5px; +} + /* code blocks — let Expressive Code round the whole frame as one unit (title bar: rounded top, code body: rounded bottom). Don't force the inner

 corners or it detaches from the title bar into a second box. */