diff --git a/package-lock.json b/package-lock.json
index c3d0a73..9db557f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,8 @@
"dompurify": "^3.2.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",
@@ -2013,6 +2015,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",
@@ -2049,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 8109812..9fd4004 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,8 @@
"dompurify": "^3.2.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..4b03610 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 };
@@ -123,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) {
@@ -159,7 +162,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/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/share/ShareApp.tsx b/src/share/ShareApp.tsx
index 338f60f..62d5fa4 100644
--- a/src/share/ShareApp.tsx
+++ b/src/share/ShareApp.tsx
@@ -1,6 +1,8 @@
// 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 'highlight.js/styles/github.css';
import { fetchShareContent, fetchShareMeta, buildAssetUrl, parseShareUrl, type ShareRef, type ShareMetaResponse } from './api';
type ViewerState =
@@ -121,7 +123,7 @@ export function ShareApp() {
-
+
diff --git a/src/share/share.css b/src/share/share.css
index 7b76e05..b49dc6b 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: 861px;
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 {
@@ -89,40 +84,17 @@ 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) {
.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;
}
}
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
)}
>