From 301200abc2a12e55d610f67f90d6ee230403b9c5 Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Thu, 15 Jan 2026 11:54:50 +0100 Subject: [PATCH] feat: add CodeMirror syntax highlighting to section page code blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- src/app.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.css | 10 ++++++++ 2 files changed, 82 insertions(+) diff --git a/src/app.js b/src/app.js index 1c54a89..53849cd 100644 --- a/src/app.js +++ b/src/app.js @@ -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 diff --git a/src/main.css b/src/main.css index 51c3c97..de0c7ba 100644 --- a/src/main.css +++ b/src/main.css @@ -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);