From cf834feac12e95229853d9305cb05912cfa63729 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 3 Mar 2026 15:11:21 +0100 Subject: [PATCH 1/6] share viewer: use github-markdown-css for GitHub-matching rendering --- package-lock.json | 13 +++++++++++++ package.json | 1 + src/share/ShareApp.tsx | 3 ++- src/share/share.css | 20 ++++++-------------- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3d0a73..e709166 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "dompurify": "^3.2.7", "dotenv": "^16.4.5", "express": "^4.19.2", + "github-markdown-css": "^5.9.0", "jose": "^5.9.3", "katex": "^0.16.22", "lucide-react": "^0.546.0", @@ -2013,6 +2014,18 @@ "node": ">= 0.4" } }, + "node_modules/github-markdown-css": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.9.0.tgz", + "integrity": "sha512-tmT5sY+zvg2302XLYEfH2mtkViIM1SWf2nvYoF5N1ZsO0V6B2qZTiw3GOzw4vpjLygK/KG35qRlPFweHqfzz5w==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", diff --git a/package.json b/package.json index 8109812..0fbc819 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "dompurify": "^3.2.7", "dotenv": "^16.4.5", "express": "^4.19.2", + "github-markdown-css": "^5.9.0", "jose": "^5.9.3", "katex": "^0.16.22", "lucide-react": "^0.546.0", diff --git a/src/share/ShareApp.tsx b/src/share/ShareApp.tsx index 338f60f..e5e6b06 100644 --- a/src/share/ShareApp.tsx +++ b/src/share/ShareApp.tsx @@ -1,6 +1,7 @@ // Public share viewer entry that resolves the share ref and renders the note. import React, { useEffect, useMemo, useState } from 'react'; import { renderMarkdown } from '../lib/render-markdown'; +import 'github-markdown-css/github-markdown-light.css'; import { fetchShareContent, fetchShareMeta, buildAssetUrl, parseShareUrl, type ShareRef, type ShareMetaResponse } from './api'; type ViewerState = @@ -121,7 +122,7 @@ export function ShareApp() {

-
+
diff --git a/src/share/share.css b/src/share/share.css index 7b76e05..8521da2 100644 --- a/src/share/share.css +++ b/src/share/share.css @@ -89,20 +89,12 @@ body { display: block; } -.share-article img { - max-width: 100%; - display: block; - margin: 16px auto; - border-radius: 12px; -} - -.share-article a { - color: #0969da; - text-decoration: none; -} - -.share-article a:hover { - text-decoration: underline; +/* Fit github-markdown-css inside our card — neutralise its own box model */ +.share-content.markdown-body { + background: transparent; + font-family: inherit; + font-size: 1rem; + color: inherit; } @media (max-width: 600px) { From 8cb993da457baa045dbd1932cfc83c5d3d7de300 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 3 Mar 2026 16:42:24 +0100 Subject: [PATCH 2/6] share viewer: add syntax highlighting via highlight.js (github theme) --- package-lock.json | 10 ++++++++++ package.json | 1 + src/lib/render-markdown.ts | 15 ++++++++++++++- src/share/ShareApp.tsx | 1 + 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index e709166..9db557f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "github-markdown-css": "^5.9.0", + "highlight.js": "^11.11.1", "jose": "^5.9.3", "katex": "^0.16.22", "lucide-react": "^0.546.0", @@ -2062,6 +2063,15 @@ "node": ">= 0.4" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", diff --git a/package.json b/package.json index 0fbc819..9fd4004 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "github-markdown-css": "^5.9.0", + "highlight.js": "^11.11.1", "jose": "^5.9.3", "katex": "^0.16.22", "lucide-react": "^0.546.0", diff --git a/src/lib/render-markdown.ts b/src/lib/render-markdown.ts index 466ee3a..25673cf 100644 --- a/src/lib/render-markdown.ts +++ b/src/lib/render-markdown.ts @@ -4,6 +4,7 @@ import type { TokenizerAndRendererExtension } from 'marked'; import DOMPurify from 'dompurify'; import katex from 'katex'; import 'katex/dist/katex.min.css'; +import hljs from 'highlight.js'; export type { MarkdownRenderMode }; export { renderMarkdown }; @@ -159,7 +160,19 @@ function configureMarkedOnce() { } }, }; - marked.use({ extensions: [blockMathExtension, inlineMathExtension] }); + // Syntax-highlight fenced code blocks using highlight.js. + // Falls back to plain text for unknown languages. + const codeRenderer = { + code(code: string, infostring: string | undefined): string { + let lang = (infostring ?? '').split(/\s/)[0] ?? ''; + let valid = lang !== '' && hljs.getLanguage(lang) !== undefined; + let highlighted = (valid && lang !== '') + ? hljs.highlight(code, { language: lang as string }).value + : hljs.highlightAuto(code).value; + return `
${highlighted}
\n`; + }, + }; + marked.use({ extensions: [blockMathExtension, inlineMathExtension], renderer: codeRenderer }); markedConfigured = true; } diff --git a/src/share/ShareApp.tsx b/src/share/ShareApp.tsx index e5e6b06..62d5fa4 100644 --- a/src/share/ShareApp.tsx +++ b/src/share/ShareApp.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { renderMarkdown } from '../lib/render-markdown'; import 'github-markdown-css/github-markdown-light.css'; +import 'highlight.js/styles/github.css'; import { fetchShareContent, fetchShareMeta, buildAssetUrl, parseShareUrl, type ShareRef, type ShareMetaResponse } from './api'; type ViewerState = From ceb9919dc816f1e11aff0dff4bf41c9bd7fd01b5 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 3 Mar 2026 16:58:23 +0100 Subject: [PATCH 3/6] fix: don't parse currency amounts as inline math ($ followed by digit/comma) --- src/lib/render-markdown.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/render-markdown.ts b/src/lib/render-markdown.ts index 25673cf..4b03610 100644 --- a/src/lib/render-markdown.ts +++ b/src/lib/render-markdown.ts @@ -124,6 +124,8 @@ function configureMarkedOnce() { }, tokenizer(src) { if (src[0] !== '$' || src[1] === '$') return undefined; + // Don't match currency: $ followed by a digit or comma (e.g. $1,200) + if (src[1] !== undefined && /[\d,]/.test(src[1])) return undefined; let index = 1; let closing = -1; while (index < src.length) { From 95a1a638e4c2e708b313148fcd719c390c196f63 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 3 Mar 2026 17:02:52 +0100 Subject: [PATCH 4/6] share viewer: full-width white layout, drop gray backdrop card --- src/share/share.css | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/share/share.css b/src/share/share.css index 8521da2..2cf1609 100644 --- a/src/share/share.css +++ b/src/share/share.css @@ -2,7 +2,7 @@ body { margin: 0; font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - background: #f6f8fa; + background: #ffffff; color: #0f172a; } @@ -39,23 +39,18 @@ body { .share-main { flex: 1; - padding: clamp(16px, 5vw, 32px); + padding: clamp(24px, 5vw, 48px) clamp(16px, 8vw, 56px); display: flex; justify-content: center; align-items: flex-start; } .share-article { - max-width: clamp(720px, 70vw, 960px); + max-width: 800px; width: 100%; - background: #ffffff; - border: 1px solid #d0d7de; - border-radius: 20px; - padding: clamp(20px, 3.5vw, 32px); - box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08); display: flex; flex-direction: column; - gap: clamp(20px, 4vw, 32px); + gap: 24px; } .share-article-header { @@ -99,22 +94,7 @@ body { @media (max-width: 600px) { .share-main { - padding: 0; - justify-content: flex-start; - align-items: stretch; - } - - .share-article { - max-width: none; - border-radius: 0; - border: none; - box-shadow: none; - min-height: 100vh; - box-sizing: border-box; - } - - .share-content { - flex: 1; + padding: 16px; } } From 41a8866a4a954fa55be5907bc82cc66004008550 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 3 Mar 2026 17:05:15 +0100 Subject: [PATCH 5/6] share viewer: match GitHub content width (861px) --- src/share/share.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/share/share.css b/src/share/share.css index 2cf1609..b49dc6b 100644 --- a/src/share/share.css +++ b/src/share/share.css @@ -46,7 +46,7 @@ body { } .share-article { - max-width: 800px; + max-width: 861px; width: 100%; display: flex; flex-direction: column; From 96d49444201ca2c17df0dc60ec6dfc60d32df028 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 3 Mar 2026 17:53:51 +0100 Subject: [PATCH 6/6] editor preview: apply github-markdown-css + highlight.js styling --- src/main.tsx | 2 ++ src/styles.css | 51 ++++++----------------------------------------- src/ui/Editor.tsx | 2 +- 3 files changed, 9 insertions(+), 46 deletions(-) diff --git a/src/main.tsx b/src/main.tsx index 95bb6e6..40d5459 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import { App } from './App'; import './styles.css'; +import 'github-markdown-css/github-markdown-light.css'; +import 'highlight.js/styles/github.css'; const root = createRoot(document.getElementById('root')!); root.render(); diff --git a/src/styles.css b/src/styles.css index e0e27c0..c774b9d 100644 --- a/src/styles.css +++ b/src/styles.css @@ -878,53 +878,14 @@ textarea:focus-visible { .workspace-panels .preview { overflow: auto; - line-height: 1.68; - color: var(--text); -} - -.workspace-panels .preview h1, -.workspace-panels .preview h2, -.workspace-panels .preview h3, -.workspace-panels .preview h4 { - margin-top: 1.5em; - margin-bottom: 0.75em; - line-height: 1.25; -} - -.workspace-panels .preview h1 { - font-size: 2rem; -} - -.workspace-panels .preview h2 { - font-size: 1.6rem; -} - -.workspace-panels .preview h3 { - font-size: 1.3rem; -} - -.workspace-panels .preview p { - margin: 0 0 1em; } -.workspace-panels .preview pre { - background: var(--surface-subtle); - border-radius: 10px; - padding: 12px; - border: 1px solid var(--border); - overflow: auto; -} - -.workspace-panels .preview code { - font: var(--code-font); -} - -.workspace-panels .preview blockquote { - border-left: 4px solid var(--accent-border); - margin: 0 0 1em; - padding: 0.2em 1em; - background: rgba(9, 105, 218, 0.08); - border-radius: 0 8px 8px 0; +/* github-markdown-css provides all content styles; override only the + properties that would conflict with the card layout. */ +.workspace-panels .preview.markdown-body { + font-family: inherit; + font-size: 1rem; + color: inherit; } .empty-state { diff --git a/src/ui/Editor.tsx b/src/ui/Editor.tsx index 811e1f2..ec28a77 100644 --- a/src/ui/Editor.tsx +++ b/src/ui/Editor.tsx @@ -128,7 +128,7 @@ export function Editor({ doc, onChange, readOnly = false, slug, loadAsset, onImp )}