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
)}
>