From c7e20032fe2f134354e0946bc6ede0b004fa68d4 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 15:05:06 +1000 Subject: [PATCH 1/3] feat(shortcodes): repo cards, code importer, gist, swatches Add github/gitlab/huggingface shortcodes sharing one .repo-card component, each auto-fetching live stats at build time with manual override and graceful fallback if the API call fails. Add a code shortcode to import a local file or remote URL (line-range + language override) instead of pasting code inline, a gist shortcode (Hugo's built-in was removed in v0.156), and a swatches shortcode for labelled colour chips. Demoed in exampleSite's hello-world post. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01K5qPo8jfbbrvWKrWQyZ8iK --- assets/css/main.css | 30 +++++++++++++++++ exampleSite/content/posts/hello-world.md | 24 ++++++++++++++ layouts/shortcodes/code.html | 40 ++++++++++++++++++++++ layouts/shortcodes/gist.html | 21 ++++++++++++ layouts/shortcodes/github.html | 37 +++++++++++++++++++++ layouts/shortcodes/gitlab.html | 38 +++++++++++++++++++++ layouts/shortcodes/huggingface.html | 42 ++++++++++++++++++++++++ layouts/shortcodes/swatches.html | 10 ++++++ 8 files changed, 242 insertions(+) create mode 100644 layouts/shortcodes/code.html create mode 100644 layouts/shortcodes/gist.html create mode 100644 layouts/shortcodes/github.html create mode 100644 layouts/shortcodes/gitlab.html create mode 100644 layouts/shortcodes/huggingface.html create mode 100644 layouts/shortcodes/swatches.html diff --git a/assets/css/main.css b/assets/css/main.css index 0d51eb4..b811763 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -412,6 +412,25 @@ main { padding-bottom: 6rem; } .post__body h3 { margin-top: 2rem; font-size: 1.1rem; } .post__body img { max-width: 100%; height: auto; } +/* ---- repo-card shortcodes: github, gitlab, huggingface ---- */ +.post__body .repo-card { text-decoration: none; } +.repo-card { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.9rem 1rem; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); +} +.repo-card:hover { border-color: var(--link); } +.repo-card:hover .repo-card__name { color: var(--link); } +.repo-card__icon { flex: none; margin-top: 0.15rem; color: var(--muted); font-size: 1.1rem; line-height: 1; } +.repo-card__main { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; } +.repo-card__name { font-family: var(--mono); font-weight: 600; } +.repo-card__desc { color: var(--muted); font-size: 0.9rem; } +.repo-card__meta { margin-left: auto; flex: none; display: flex; gap: 0.75rem; font-family: var(--mono); font-size: 0.8rem; color: var(--muted); white-space: nowrap; } + /* figures (markdown images) */ .post-figure { margin: 2rem 0; } .post-figure img { display: block; width: 100%; border: 1px solid var(--border); border-radius: 4px; } @@ -423,6 +442,16 @@ main { padding-bottom: 6rem; } text-align: center; } +/* code shortcode: imported file/URL, source path shown above the block */ +.code-import figcaption { margin-bottom: 0.4rem; font-family: var(--mono); font-size: 0.75rem; color: var(--muted); } +.code-import figcaption a:hover { color: var(--link); } + +/* swatches shortcode */ +.swatches { display: flex; flex-wrap: wrap; gap: 1rem; margin: 1.5rem 0; } +.swatches__item { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; } +.swatches__chip { display: block; width: 3rem; height: 3rem; border-radius: 6px; border: 1px solid var(--border); } +.swatches__label { font-family: var(--mono); font-size: 0.75rem; color: var(--muted); } + /* math (MathML) */ .post__body math { font-size: 1.05em; } .post__body math[display="block"] { @@ -476,6 +505,7 @@ main { padding-bottom: 6rem; } font-family: var(--mono); font-size: 0.85rem; } +.post__body iframe { border-radius: 6px; } .post__body code { font-family: var(--mono); font-size: 0.9em; } .post__body :not(pre) > code { padding: 0.1em 0.35em; diff --git a/exampleSite/content/posts/hello-world.md b/exampleSite/content/posts/hello-world.md index f11afd9..6c28b83 100644 --- a/exampleSite/content/posts/hello-world.md +++ b/exampleSite/content/posts/hello-world.md @@ -18,6 +18,18 @@ pop of colour. Inline `code` looks like this. > A blockquote sits quietly to one side. +## A repo + +{{< github "gohugoio/hugo" >}} + +{{< gitlab "gitlab-org/gitlab" >}} + +{{< huggingface "bert-base-uncased" >}} + +## Swatches + +{{< swatches "#64748b" "#3b82f6" "#06b6d4" >}} + ## A diagram ```mermaid @@ -41,3 +53,15 @@ Inline, like $E = mc^2$, sits in the sentence. Display math gets its own line: $$\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$$ That's it. + +## Imported code + +{{< code file="assets/js/console.js" lines="1-8" >}} + +## A gist + +{{< gist "octocat" "6cad326836d38bd3a7ae" >}} + +## A video + +{{< youtube dQw4w9WgXcQ >}} diff --git a/layouts/shortcodes/code.html b/layouts/shortcodes/code.html new file mode 100644 index 0000000..35642f2 --- /dev/null +++ b/layouts/shortcodes/code.html @@ -0,0 +1,40 @@ +{{- /* {{< code file="assets/js/console.js" >}} or {{< code url="https://raw.githubusercontent.com/…" >}} + Imports code from a local file or remote URL instead of pasting it inline. + Optional: lang="go" (else guessed from the extension), lines="12-34" (1-indexed, inclusive). */ -}} +{{- $file := .Get "file" -}} +{{- $url := .Get "url" -}} +{{- if not (or $file $url) }}{{ errorf "code shortcode: file or url required, e.g. {{}} (%s)" .Position }}{{ end -}} +{{- if and $file $url }}{{ errorf "code shortcode: pass file or url, not both (%s)" .Position }}{{ end -}} + +{{- $lang := .Get "lang" -}} +{{- $label := "" -}} +{{- $content := "" -}} +{{- if $file -}} + {{- $content = os.ReadFile $file -}} + {{- $label = $file -}} + {{- if not $lang }}{{ $lang = path.Ext $file | strings.TrimPrefix "." }}{{ end -}} +{{- else -}} + {{- $label = $url -}} + {{- $try := try (resources.GetRemote $url) -}} + {{- with $try -}} + {{- if .Err }}{{ errorf "code shortcode: failed to fetch %s: %s (%s)" $url .Err $.Position }} + {{- else -}} + {{- $content = .Value.Content -}} + {{- if not $lang }}{{ $lang = path.Ext $url | strings.TrimPrefix "." }}{{ end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- with .Get "lines" -}} + {{- $parts := strings.Split . "-" -}} + {{- $start := int (index $parts 0) -}} + {{- $end := $start -}} + {{- if gt (len $parts) 1 }}{{ $end = int (index $parts 1) }}{{ end -}} + {{- $all := strings.Split $content "\n" -}} + {{- $content = delimit (after (sub $start 1) (first $end $all)) "\n" -}} +{{- end -}} + +
+
{{ if $url }}{{ $label }}{{ else }}{{ $label }}{{ end }}
+ {{ transform.Highlight $content $lang }} +
diff --git a/layouts/shortcodes/gist.html b/layouts/shortcodes/gist.html new file mode 100644 index 0000000..0c55990 --- /dev/null +++ b/layouts/shortcodes/gist.html @@ -0,0 +1,21 @@ +{{- /* {{< gist "user" "gist-id" >}} — embeds a GitHub gist. + Hugo's built-in gist shortcode was removed in v0.156; this fetches the + gist via the GitHub API at build time and renders it with the site's own + Chroma highlighting instead of GitHub's unstyled diff --git a/layouts/shortcodes/npm.html b/layouts/shortcodes/npm.html new file mode 100644 index 0000000..fae55dc --- /dev/null +++ b/layouts/shortcodes/npm.html @@ -0,0 +1,37 @@ +{{- /* {{< npm "left-pad" >}} — card linking to an npm package. + Auto-fetches description/version/license from the registry at build time; + pass desc="…" version="…" license="…" to override (or skip the network call). */ -}} +{{- $pkg := .Get 0 -}} +{{- with .Get "pkg" }}{{ $pkg = . }}{{ end -}} +{{- if not $pkg }}{{ errorf "npm shortcode: package name required, e.g. {{}} (%s)" .Position }}{{ end -}} + +{{- $desc := .Get "desc" -}} +{{- $version := .Get "version" -}} +{{- $license := .Get "license" -}} +{{- if not (and $desc $version $license) -}} + {{- $try := try (resources.GetRemote (printf "https://registry.npmjs.org/%s" $pkg)) -}} + {{- with $try -}} + {{- if not .Err -}} + {{- with .Value -}} + {{- $data := transform.Unmarshal .Content -}} + {{- if not $desc }}{{ $desc = $data.description }}{{ end -}} + {{- if not $version }}{{ $version = index $data "dist-tags" "latest" }}{{ end -}} + {{- if not $license }}{{ $license = $data.license }}{{ end -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + + + {{ $pkg }} + {{- with $desc }}{{ . }}{{ end }} + + {{- if or $version $license }} + + {{- with $version }}v{{ . }}{{ end }} + {{- with $license }}{{ . }}{{ end }} + + {{- end }} + diff --git a/layouts/shortcodes/pypi.html b/layouts/shortcodes/pypi.html new file mode 100644 index 0000000..fb749d6 --- /dev/null +++ b/layouts/shortcodes/pypi.html @@ -0,0 +1,37 @@ +{{- /* {{< pypi "requests" >}} — card linking to a PyPI package. + Auto-fetches summary/version/license at build time; pass desc="…" + version="…" license="…" to override (or skip the network call). */ -}} +{{- $pkg := .Get 0 -}} +{{- with .Get "pkg" }}{{ $pkg = . }}{{ end -}} +{{- if not $pkg }}{{ errorf "pypi shortcode: package name required, e.g. {{}} (%s)" .Position }}{{ end -}} + +{{- $desc := .Get "desc" -}} +{{- $version := .Get "version" -}} +{{- $license := .Get "license" -}} +{{- if not (and $desc $version $license) -}} + {{- $try := try (resources.GetRemote (printf "https://pypi.org/pypi/%s/json" $pkg)) -}} + {{- with $try -}} + {{- if not .Err -}} + {{- with .Value -}} + {{- $data := (transform.Unmarshal .Content).info -}} + {{- if not $desc }}{{ $desc = $data.summary }}{{ end -}} + {{- if not $version }}{{ $version = $data.version }}{{ end -}} + {{- if not $license }}{{ $license = $data.license }}{{ end -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + + + {{ $pkg }} + {{- with $desc }}{{ . }}{{ end }} + + {{- if or $version $license }} + + {{- with $version }}v{{ . }}{{ end }} + {{- with $license }}{{ . }}{{ end }} + + {{- end }} +