docs: highlight docs site code blocks

This commit is contained in:
Peter Steinberger 2026-05-07 04:20:45 +01:00
parent c68d285400
commit e3c4ea61e6
No known key found for this signature in database
3 changed files with 70 additions and 1 deletions

View File

@ -82,6 +82,12 @@ body:not(.home) .doc>h1:first-child{display:none}
.doc pre::-webkit-scrollbar{height:8px;width:8px}
.doc pre::-webkit-scrollbar-thumb{background:var(--code-scroll);border-radius:8px}
.doc pre code{display:block;background:transparent;border:0;color:inherit;padding:0;font-size:1em;white-space:pre}
.doc pre .hl-c{color:#7f9990;font-style:italic}
.doc pre .hl-s{color:#a6e3a1}
.doc pre .hl-v{color:#f9c779}
.doc pre .hl-f{color:#89c2d9}
.doc pre .hl-n{color:#f4a47a}
.doc pre .hl-k{color:#cba6f7}
.copy{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:34px;height:34px;background:rgba(255,255,255,.06);color:var(--code-fg);border:1px solid rgba(255,255,255,.18);border-radius:8px;padding:0;cursor:pointer;transition:background .15s,border-color .15s,color .15s,opacity .15s}
.copy:hover{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.3)}
.copy:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

View File

@ -0,0 +1,61 @@
import { escapeHtml } from "./docs-site-render.mjs";
export function highlight(lang, source) {
const normalized = (lang || "").toLowerCase();
if (normalized === "bash" || normalized === "sh" || normalized === "shell") {
return tokenize(source, BASH_RULES);
}
if (normalized === "json") return tokenize(source, JSON_RULES);
if (normalized === "sql") return tokenize(source, SQL_RULES);
return escapeHtml(source);
}
function tokenize(source, rules) {
let out = "";
let pending = "";
let i = 0;
outer: while (i < source.length) {
for (const [klass, regex] of rules) {
regex.lastIndex = i;
const match = regex.exec(source);
if (match && match.index === i) {
if (pending) {
out += escapeHtml(pending);
pending = "";
}
out += `<span class="hl-${klass}">${escapeHtml(match[0])}</span>`;
i = regex.lastIndex;
continue outer;
}
}
pending += source[i];
i += 1;
}
if (pending) out += escapeHtml(pending);
return out;
}
const BASH_RULES = [
["c", /#[^\n]*/y],
["s", /"(?:\\[\s\S]|[^"\\])*"/y],
["s", /'[^'\n]*'/y],
["v", /\$\{[^}\n]+\}|\$[A-Za-z_][A-Za-z0-9_]*|\$[0-9?#@*!$-]/y],
["f", /(?<=^|[\s=(\[])--?[A-Za-z][\w-]*/y],
["n", /(?<=^|[\s=(:,])\d+(?:\.\d+)?\b/y],
["k", /\b(?:if|then|else|elif|fi|while|do|done|for|in|case|esac|function|return|exit|local|export|readonly|set|source|alias|cd|read|exec|trap)\b/y],
];
const JSON_RULES = [
["s", /"(?:\\[\s\S]|[^"\\])*"(?=\s*:)/y, "key"],
["s", /"(?:\\[\s\S]|[^"\\])*"/y],
["n", /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/y],
["k", /\b(?:true|false|null)\b/y],
];
const SQL_RULES = [
["c", /--[^\n]*/y],
["c", /\/\*[\s\S]*?\*\//y],
["s", /'(?:''|[^'\n])*'/y],
["n", /\b\d+(?:\.\d+)?\b/y],
["k", /\b(?:SELECT|FROM|WHERE|AND|OR|NOT|NULL|IS|IN|LIKE|BETWEEN|JOIN|LEFT|RIGHT|INNER|OUTER|ON|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|INDEX|VIEW|DROP|ALTER|ADD|COLUMN|PRIMARY|KEY|FOREIGN|REFERENCES|UNIQUE|DEFAULT|AS|CASE|WHEN|THEN|ELSE|END|UNION|ALL|DISTINCT|COUNT|SUM|AVG|MIN|MAX|WITH|PRAGMA|VIRTUAL|USING|MATCH)\b/iy],
];

View File

@ -1,6 +1,8 @@
import fs from "node:fs";
import path from "node:path";
import { highlight } from "./docs-site-highlight.mjs";
export function markdownToHtml(markdown, currentRel, rewriteHref) {
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
const html = [];
@ -34,7 +36,7 @@ export function markdownToHtml(markdown, currentRel, rewriteHref) {
closeList();
flushBlockquote();
if (fence) {
html.push(`<pre><code class="language-${escapeAttr(fence.lang)}">${escapeHtml(fence.lines.join("\n"))}</code></pre>`);
html.push(`<pre><code class="language-${escapeAttr(fence.lang)}">${highlight(fence.lang, fence.lines.join("\n"))}</code></pre>`);
fence = null;
} else {
fence = { lang: fenceMatch[1] || "text", lines: [] };