Skip to content

Commit 399f5cb

Browse files
authored
feat(docs): format landing-page code snippets with Prism (#43)
The per-skill landing pages now let Prism format their code blocks. SKILL.md fenced code is tokenized server-side via the installed prismjs package, so every snippet on a skill page picks up the shared Prism theme, and the custom code-snippet styling (accent border, sunken panel) is scoped to the home page. The Prism theme is vendored from the npm dependency and inlined into each page.
1 parent fc30c96 commit 399f5cb

9 files changed

Lines changed: 108 additions & 20 deletions

File tree

_data/lib/skill-metadata.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@
66
* `docs/tests/` exercise these directly.
77
*/
88
import MarkdownIt from "markdown-it";
9+
import Prism from "prismjs";
10+
import loadLanguages from "prismjs/components/index.js";
11+
12+
// Grammars for the fenced-code languages that appear in SKILL.md bodies. Loading
13+
// `bash` also registers its `shell` alias. `bash` doubles as the fallback grammar
14+
// for unknown/unlabeled fences (see `highlightFence`), so it must stay loaded.
15+
const FALLBACK_LANGUAGE = "bash";
16+
loadLanguages([FALLBACK_LANGUAGE, "json", "markdown", "yaml"]);
17+
18+
/**
19+
* Prism-highlight a fenced code block into the same `pre.language-*` markup the
20+
* site's `{% highlight %}` shortcode emits, so the shared Prism theme formats the
21+
* SKILL.md code snippets rendered on the per-skill landing pages. Unknown or
22+
* unlabeled languages fall back to `bash` — most SKILL.md fences are shell
23+
* commands — so every block is tokenized and picks up the Prism styling.
24+
*
25+
* @param {string} code
26+
* @param {string} lang
27+
* @returns {string}
28+
*/
29+
function highlightFence(code, lang) {
30+
const language = lang && Prism.languages[lang] ? lang : FALLBACK_LANGUAGE;
31+
const body = Prism.highlight(code, Prism.languages[language], language);
32+
return `<pre class="language-${language}"><code class="language-${language}">${body}</code></pre>`;
33+
}
934

1035
/** Default branch used when building GitHub `blob`/raw URLs for skill assets. */
1136
export const DEFAULT_BRANCH = "main";
@@ -52,6 +77,7 @@ const renderToken = (tokens, idx, options, _env, self) => self.renderToken(token
5277
*/
5378
export function createSkillRenderer() {
5479
const md = new MarkdownIt({ html: true, linkify: true, typographer: true });
80+
md.set({ highlight: (code, lang) => highlightFence(code, lang) });
5581
const defaultLinkOpen = md.renderer.rules.link_open ?? renderToken;
5682

5783
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {

_includes/foundation.njk

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@
3535
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
3636
<link href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Mono&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap&family=Josefin+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
3737

38-
<link href="https://unpkg.com/prismjs@1.20.0/themes/prism.css" rel="stylesheet" />
3938
<script src="https://kit.fontawesome.com/42e99b4632.js" crossorigin="anonymous"></script>
4039

40+
{# Prism syntax-highlighting themes, vendored from the installed `prismjs`
41+
package via Eleventy passthrough copy (see `eleventy.config.js`). Light and
42+
dark variants are swapped by `prefers-color-scheme` so code snippets track
43+
the page's light/dark mode. Loaded before the site styles so those can
44+
override them. #}
45+
<link href="/assets/prism.css" rel="stylesheet" media="(prefers-color-scheme: light)" />
46+
<link href="/assets/prism-dark.css" rel="stylesheet" media="(prefers-color-scheme: dark)" />
47+
4148
<style>
4249
:root {
4350
color-scheme: {{ theme.colorScheme | default('light dark') }};

_includes/styles.css

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,23 +122,6 @@ p {
122122
line-height: 1.8;
123123
}
124124

125-
pre {
126-
background: var(--theme--surface--color--sunken);
127-
border-inline-start: 4px solid var(--base);
128-
border-end-end-radius: .2em;
129-
border-start-end-radius: .2em;
130-
font-size: 0.8em !important;
131-
box-shadow: inset 1px 1px 0 hsl(0deg 0% 0% / 0.04), inset -1px -1px 0 hsl(0deg 0% 0% / 0.04);
132-
133-
/* Breathing room so code doesn't sit flush against the left accent border. */
134-
padding: 0.2rem .4rem;
135-
136-
/* Long lines (install commands, code samples) scroll inside the block
137-
instead of stretching the page past the viewport on narrow screens. */
138-
max-inline-size: 100%;
139-
overflow-x: auto;
140-
}
141-
142125
code {
143126
font-family: var(--theme--code--FontFamily);
144127
line-height: 1.4;
@@ -155,6 +138,28 @@ code {
155138
overflow-wrap: break-word;
156139
}
157140

141+
/* Prism's themes add a text-shadow to code — a white "emboss" in the default
142+
light theme — that reads as a halo on our card and prose surfaces (the code
143+
block background is themed separately from Prism's own). Drop it so snippets
144+
render flat and crisp in both light and dark mode. `text-shadow` inherits, so
145+
this also clears it from the token spans. */
146+
:is(pre, code)[class*="language-"] {
147+
text-shadow: none;
148+
}
149+
150+
/* The site owns the code-block container so light and dark match: a flat surface
151+
panel — the same color as inline `code` — with a thin subtle border and gentle
152+
rounding. The Prism themes then supply only the token colors, so the light
153+
theme's panel and prism-dark's brown panel, thick border, and inset shadow are
154+
all overridden here. Components that own their code area (`.hero-terminal pre`,
155+
`.card pre`) restyle it below. */
156+
pre[class*="language-"] {
157+
background: var(--theme--surface--color);
158+
border: 1px solid var(--theme--ui--color--subtle);
159+
border-radius: 0.4em;
160+
box-shadow: none;
161+
}
162+
158163
/* Copy-to-clipboard affordance injected around every <pre> by copy-code.js. */
159164
.code-block {
160165
position: relative;
@@ -589,6 +594,15 @@ li {
589594
background: var(--theme--surface--color);
590595
}
591596

597+
/* Code snippets in a card carry the brand accent bar instead of the full
598+
border, with the leading corners squared so the bar reads as a clean edge. */
599+
pre[class*="language-"] {
600+
border: none;
601+
border-inline-start: 4px solid var(--base);
602+
border-start-start-radius: 0;
603+
border-end-start-radius: 0;
604+
}
605+
592606
:is(header, .header) :is(h2, h3, h4, h5, h6):not([class]) {
593607
font-size: calc(1.2em + 1cqi);
594608
}

docs/skill.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
.skill-hero {
22
padding-block: 2.5rem 2.25rem;
33
border-block-end: 1px solid var(--theme--ui--color--subtle);
4-
background-image: linear-gradient(180deg, var(--theme--surface--color--accent), transparent);
4+
5+
/* Soft glow that fades delicately outward, matching the home page header
6+
(docs/styles.css) rather than a flat top-to-bottom wash. Anchored toward the
7+
start edge so the glow sits behind this hero's left-aligned content — the way
8+
the home glow sits behind its centered content — instead of leaving the
9+
top-left corner in the faint zone. */
10+
background-image: radial-gradient(
11+
75% 85% at 20% 0%,
12+
var(--theme--surface--color--accent),
13+
transparent 70%
14+
);
515

616
> * + * {
717
margin-block-start: 1.25rem;

docs/styles.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,14 @@ body > header {
123123

124124
pre {
125125
margin: 0;
126-
border-inline-start: none;
126+
/* The terminal frame owns the border, the rounded corners (via
127+
overflow: hidden), and the background. Clear Prism's own border, radius,
128+
shadow, and panel color so the code area fills the frame flush instead of
129+
showing a mismatched inner box with a tighter border-radius. */
130+
border: none;
131+
border-radius: 0;
127132
box-shadow: none;
133+
background: var(--theme--surface--color--sunken);
128134
}
129135
}
130136

docs/tests/skill-metadata.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,20 @@ test("wraps tables in a horizontal scroll container", () => {
106106
assert.match(html, /<div class="table-scroll">\s*<table>/);
107107
assert.match(html, /<\/table>\s*<\/div>/);
108108
});
109+
110+
test("Prism-highlights fenced code into a language-tagged block", () => {
111+
const md = createSkillRenderer();
112+
const html = md.render("```bash\nnpm install foo\n```\n");
113+
assert.match(html, /<pre class="language-bash"><code class="language-bash">/);
114+
// Tokenized by Prism so the shared theme colors the snippet.
115+
assert.match(html, /<span class="token function">npm<\/span>/);
116+
});
117+
118+
test("unlabeled fences fall back to a tokenized bash block", () => {
119+
const md = createSkillRenderer();
120+
const html = md.render("```\nnpm install foo && echo done\n```\n");
121+
assert.match(html, /<pre class="language-bash"><code class="language-bash">/);
122+
// Tokenized as bash so shell operators are still colored, and HTML is escaped.
123+
assert.match(html, /<span class="token function">npm<\/span>/);
124+
assert.doesNotMatch(html, /class="language-none"/);
125+
});

eleventy.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,11 @@ export default async function ( config ) {
6262
"assets/*": "assets",
6363
"skills/*/assets": "assets",
6464
"skills/dreamlight-valley/references/sprites/*": "sprites",
65+
// Prism syntax-highlighting themes, served from the installed `prismjs`
66+
// dependency and linked from the base template (`_includes/foundation.njk`):
67+
// the default light theme, plus a dark theme loaded under a dark-mode media
68+
// query.
69+
"node_modules/prismjs/themes/prism.css": "assets/prism.css",
70+
"node_modules/prismjs/themes/prism-dark.css": "assets/prism-dark.css",
6571
});
6672
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"listr2": "^10.2.1",
9090
"markdown-it": "^14.1.1",
9191
"pinst": "^3.0.0",
92+
"prismjs": "^1.30.0",
9293
"text-table": "^0.2.0",
9394
"yargs": "^18.0.0"
9495
},

yarn.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ __metadata:
177177
listr2: "npm:^10.2.1"
178178
markdown-it: "npm:^14.1.1"
179179
pinst: "npm:^3.0.0"
180+
prismjs: "npm:^1.30.0"
180181
text-table: "npm:^0.2.0"
181182
yargs: "npm:^18.0.0"
182183
bin:

0 commit comments

Comments
 (0)