Files
novaconium/App/pages/blog/code-highlighting/index.twig
T
code cb64836901 Add syntax highlighting on code blocks, plus a Code Highlighting post
Colors <pre><code> blocks site-wide via vendored highlight.js v11.11.1
(pinned to that stable tag, not main, which tracks an in-progress
11.0.0-beta1), auto-detected and restricted to
configure({ languages: ['php', 'bash', 'xml', 'css', 'python',
'javascript', 'yaml', 'json', 'ini'] }) - no per-block markup needed for
the ~60 existing code blocks across the site. css/python/javascript ship
in the core bundle; yaml/json/ini (ini covers .env-style files too)
don't and are vendored as separate per-language files.

Themes swap with the existing dark/light toggle: ir-black (dark) +
github (light, resolving the entry's own open question), via the same
data-theme-driven mechanism as the main palette
(syntax-highlight-init.twig/syntax-highlight.twig, mirroring
theme-init.twig/nav.twig's split) - a MutationObserver swaps the theme
link live without touching the existing toggle button's click handler.

Twig-syntax code blocks have no highlight.js grammar and are marked
class="nohighlight" by hand (15 blocks across 9 files, found by grepping
for literal {% %}/{{ }} syntax rather than guessing) rather than
force-matched into the restricted candidate set, which would color them
wrong instead of leaving them plain.

One correction to the original backlog entry's suggested approach: it
suggested vendoring highlight.js under novaconium/vendor/ next to Twig.
That would have silently 404ed on every request - Twig is server-side
PHP, never fetched by a browser, but highlight.js's .js/.css files are,
and only public/ is web-reachable. Vendored to public/vendor/highlightjs/
instead; documented in AGENTS.md and a new upgrading-highlightjs doc,
since public/ isn't touched by the usual novaconium/-swap framework
update workflow, so a future highlight.js bump won't propagate to
existing projects automatically the way it does for everything else
under novaconium/.

Caught two real bugs via testing rather than review: hljs.highlightAll()
silently no-ops if called before the document finishes parsing rather
than deferring itself, and a bash example starting with the word "php"
auto-detects as PHP, not bash.

Also adds App/pages/blog/code-highlighting/ - a new blog post
demonstrating the feature with a verified worked example in each of the
nine languages, plus how to force a language via an explicit
language-<name> class when auto-detection isn't enough.

Closes the "Syntax highlighting on code blocks" backlog item in
novaconium/ISSUES.md.
2026-07-14 19:00:48 +00:00

91 lines
4.3 KiB
Twig

{% extends layout %}
{% import '_layout/icons.twig' as icons %}
{% block title %}Code Highlighting{% endblock %}
{% block description %}How syntax highlighting works on this site, with worked examples in bash, HTML, CSS, YAML, Python, JavaScript, JSON, and INI/env.{% endblock %}
{% block robots %}index, follow{% endblock %}
{% block tags %}highlighting, reference{% endblock %}
{% block canonical %}{{ request_path|default('/') }}{% endblock %}
{% block og_type %}article{% endblock %}
{% block og_title %}{{ block('title') }}{% endblock %}
{% block og_description %}{{ block('description') }}{% endblock %}
{% block og_url %}{{ block('canonical') }}{% endblock %}
{% block twitter_card %}summary{% endblock %}
{% block twitter_title %}{{ block('title') }}{% endblock %}
{% block twitter_description %}{{ block('description') }}{% endblock %}
{% block blog_content %}
<h1>Code Highlighting</h1>
<p>Every <code>&lt;pre&gt;&lt;code&gt;</code> block on this site gets colored automatically via vendored <a href="https://highlightjs.org/">highlight.js</a> — see <a class="icon-link" href="/admin/docs/styling">{{ icons.book() }}Styling</a> for the mechanism and <a class="icon-link" href="/admin/docs/upgrading-highlightjs">{{ icons.book() }}Upgrading highlight.js</a> for what's vendored. This post is a plain reference: how to write a code block, and one worked example in each language this site highlights.</p>
<h2>How to use it</h2>
<p>Write a normal code block — nothing extra required, the language is auto-detected:</p>
<pre><code class="nohighlight">&lt;pre&gt;&lt;code&gt;your code here&lt;/code&gt;&lt;/pre&gt;</code></pre>
<p>Auto-detection is restricted to the languages this site actually uses (see <code>hljs.configure(...)</code> in <code>novaconium/pages/_layout/syntax-highlight.twig</code>) so it doesn't misfire trying to match dozens of unrelated bundled languages against a short snippet. If a snippet is ambiguous, or ever gets detected as the wrong language, force it with an explicit <code>language-&lt;name&gt;</code> class instead of relying on auto-detection:</p>
<pre><code class="nohighlight">&lt;pre&gt;&lt;code class="language-yaml"&gt;your code here&lt;/code&gt;&lt;/pre&gt;</code></pre>
<p>A code block written in Twig template syntax (<code>{{ '{%' }} ... {{ '%}' }}</code>, <code>{{ '{{' }} ... {{ '}}' }}</code>) has no highlight.js grammar to match — mark those <code>class="nohighlight"</code> instead, the same way the two snippets above are marked (they're showing literal HTML as plain text, not being highlighted as HTML themselves). See <a class="icon-link" href="/blog/twig-syntax-guide">{{ icons.book() }}the Twig Syntax Guide</a> for Twig's own syntax, written the same way.</p>
<h2>Bash</h2>
<pre><code>#!/bin/bash
for f in App/pages/blog/*/index.twig; do
echo "Post: $f"
done</code></pre>
<h2>HTML</h2>
<pre><code>&lt;article class="post"&gt;
&lt;h1&gt;Hello, World!&lt;/h1&gt;
&lt;p&gt;A short excerpt.&lt;/p&gt;
&lt;/article&gt;</code></pre>
<h2>CSS</h2>
<pre><code>.post-list li {
border-bottom: 1px solid var(--border-color);
padding: 0.75rem 0;
}</code></pre>
<h2>YAML</h2>
<pre><code>site:
name: Novaconium Website
theme: dark
tags:
- php
- twig
- highlighting</code></pre>
<h2>Python</h2>
<pre><code>def excerpt(text, length=140):
return text[:length].rsplit(" ", 1)[0] + "..."</code></pre>
<h2>JavaScript</h2>
<pre><code>function toggleTheme() {
const html = document.documentElement;
const next = html.dataset.theme === "light" ? "dark" : "light";
html.setAttribute("data-theme", next);
}</code></pre>
<h2>JSON</h2>
<pre><code>{
"title": "Code Highlighting",
"tags": ["highlighting", "reference"],
"published": true
}</code></pre>
<h2>INI / .env</h2>
<pre><code>; App/config.php equivalent, .ini-style
[matomo]
url = https://matomo.example.com/
site_id = 1</code></pre>
<p>YAML, JSON, and INI aren't part of highlight.js's core bundle the way PHP/Bash/CSS/Python/JavaScript/XML are — they're vendored as three separate files under <code>public/vendor/highlightjs/languages/</code>, loaded after the core bundle. See <a class="icon-link" href="/admin/docs/upgrading-highlightjs">{{ icons.book() }}Upgrading highlight.js</a> for how to add another language the same way.</p>
{% endblock %}