feat: add CodeMirror syntax highlighting to section page code blocks

- Use CodeMirror in read-only mode for code examples
- Auto-detect CSS vs HTML based on content
- Clean up views when navigating between sections
- Add transparent background to blend with code-block container

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
2026-01-15 11:54:50 +01:00
parent a8410df42a
commit 301200abc2
2 changed files with 82 additions and 0 deletions

View File

@@ -6,6 +6,13 @@ import { initI18n, t, getLanguage, setLanguage, applyTranslations } from "./i18n
import { parseHash, updateHash, replaceHash, getShareableUrl, RouteType, navigateTo } from "./helpers/router.js";
import { sections, getSection, getModuleSection, getModulesBySection } from "./config/sections.js";
// CodeMirror imports for syntax highlighting
import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { oneDark } from "@codemirror/theme-one-dark";
import { html } from "@codemirror/lang-html";
import { css } from "@codemirror/lang-css";
// Simplified state - LessonEngine now manages lesson state and progress
const state = {
userSettings: {
@@ -16,6 +23,69 @@ const state = {
animationTimeout: null
};
// Track CodeMirror views for cleanup
let sectionCodeViews = [];
// Read-only CodeMirror theme for code examples
const readOnlyTheme = EditorView.theme(
{
"&": {
fontSize: "13px"
},
".cm-content": {
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
padding: "12px 0"
},
".cm-line": {
padding: "0 12px"
},
".cm-gutters": {
display: "none"
}
},
{ dark: true }
);
/**
* Highlight all code blocks in the section page using CodeMirror
*/
function highlightSectionCodeBlocks() {
// Clean up previous views
sectionCodeViews.forEach((view) => view.destroy());
sectionCodeViews = [];
// Find all code blocks in section page
const codeBlocks = elements.sectionIntro?.querySelectorAll(".code-block") || [];
codeBlocks.forEach((block) => {
const pre = block.querySelector("pre");
const code = block.querySelector("code");
if (!pre || !code) return;
const content = code.textContent || "";
// Detect language from content
const isHTML = content.includes("<") && content.includes(">");
const langExtension = isHTML ? html() : css();
// Create read-only CodeMirror view
const state = EditorState.create({
doc: content,
extensions: [langExtension, oneDark, readOnlyTheme, EditorState.readOnly.of(true), EditorView.lineWrapping]
});
const view = new EditorView({
state,
parent: block
});
// Remove original pre/code
pre.remove();
sectionCodeViews.push(view);
});
}
// DOM elements - updated for new layout
const elements = {
// Header
@@ -1272,6 +1342,8 @@ function showSectionPage(sectionId) {
// Inject educational content (includes integrated module links)
if (elements.sectionIntro && sectionContent[sectionId]) {
elements.sectionIntro.innerHTML = sectionContent[sectionId];
// Highlight code blocks with CodeMirror
highlightSectionCodeBlocks();
}
// Get modules for this section to calculate progress

View File

@@ -1911,6 +1911,7 @@ input:checked + .toggle-slider::before {
overflow: hidden;
}
/* Fallback styles for pre/code (before CodeMirror initializes) */
.code-block pre {
margin: 0;
padding: 1rem;
@@ -1925,6 +1926,15 @@ input:checked + .toggle-slider::before {
white-space: pre;
}
/* CodeMirror styles within code blocks */
.code-block .cm-editor {
background: transparent;
}
.code-block .cm-scroller {
overflow-x: auto;
}
.code-caption {
font-size: 0.75rem;
color: var(--light-text);