Skip to content

Commit f23f933

Browse files
committed
fix static code highlighting in md
1 parent 796327f commit f23f933

File tree

3 files changed

+65
-53
lines changed

3 files changed

+65
-53
lines changed

src/runtime/stdlib/md.ts

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,72 @@ import type {TemplateRenderer} from "./template.js";
33

44
const mi = MarkdownIt({html: true, linkify: true, typographer: true});
55

6-
export const md: TemplateRenderer & {document?: Document} = (template, ...values) => {
7-
const {document = window.document} = md;
8-
let source = template[0];
9-
let fragment: DocumentFragment | null = null;
10-
let partIndex = -1;
11-
const parts: Node[] = [];
6+
export function MarkdownRenderer({
7+
document = window.document
8+
}: {
9+
document?: Document;
10+
} = {}): TemplateRenderer<HTMLElement> {
11+
return function (template, ...values) {
12+
let source = template[0];
13+
let fragment: DocumentFragment | null = null;
14+
let partIndex = -1;
15+
const parts: Node[] = [];
1216

13-
// Concatenate the text using comments as placeholders.
14-
for (let i = 0, n = values.length; i < n; ++i) {
15-
const value = values[i];
16-
if (value instanceof Node) {
17-
parts[++partIndex] = value;
18-
source += `<!--o:${partIndex}-->`;
19-
} else if (Array.isArray(value)) {
20-
for (const node of value) {
21-
if (node instanceof Node) {
22-
if (fragment === null) {
23-
parts[++partIndex] = fragment = document.createDocumentFragment();
24-
source += `<!--o:${partIndex}-->`;
17+
// Concatenate the text using comments as placeholders.
18+
for (let i = 0, n = values.length; i < n; ++i) {
19+
const value = values[i];
20+
if (value instanceof Node) {
21+
parts[++partIndex] = value;
22+
source += `<!--o:${partIndex}-->`;
23+
} else if (Array.isArray(value)) {
24+
for (const node of value) {
25+
if (node instanceof Node) {
26+
if (fragment === null) {
27+
parts[++partIndex] = fragment = document.createDocumentFragment();
28+
source += `<!--o:${partIndex}-->`;
29+
}
30+
fragment.appendChild(node);
31+
} else {
32+
fragment = null;
33+
source += node;
2534
}
26-
fragment.appendChild(node);
27-
} else {
28-
fragment = null;
29-
source += node;
3035
}
36+
fragment = null;
37+
} else {
38+
source += value;
3139
}
32-
fragment = null;
33-
} else {
34-
source += value;
40+
source += template[i + 1];
3541
}
36-
source += template[i + 1];
37-
}
3842

39-
// Render the text.
40-
const root = document.createElement("div");
41-
root.innerHTML = mi.render(source);
43+
// Render the text.
44+
const root = document.createElement("div");
45+
root.innerHTML = mi.render(source);
4246

43-
// Walk the rendered content to replace comment placeholders.
44-
if (++partIndex > 0) {
45-
const nodes = new Array<Comment>(partIndex);
46-
const walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT, null);
47-
while (walker.nextNode()) {
48-
const node = walker.currentNode as Comment;
49-
if (/^o:\d+$/.test(node.nodeValue!)) {
50-
nodes[+node.nodeValue!.slice(2)] = node;
47+
// Walk the rendered content to replace comment placeholders.
48+
if (++partIndex > 0) {
49+
const nodes = new Array<Comment>(partIndex);
50+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT, null);
51+
while (walker.nextNode()) {
52+
const node = walker.currentNode as Comment;
53+
if (/^o:\d+$/.test(node.nodeValue!)) {
54+
nodes[+node.nodeValue!.slice(2)] = node;
55+
}
56+
}
57+
for (let i = 0; i < partIndex; ++i) {
58+
const node = nodes[i];
59+
node.parentNode?.replaceChild(parts[i], node);
5160
}
5261
}
53-
for (let i = 0; i < partIndex; ++i) {
54-
const node = nodes[i];
55-
node.parentNode?.replaceChild(parts[i], node);
56-
}
57-
}
5862

59-
const codes = root.querySelectorAll<HTMLElement>("code[class^=language-]");
60-
if (codes.length > 0) {
61-
import("./highlight.js").then(({highlight}) => codes.forEach(highlight));
62-
}
63+
return root;
64+
};
65+
}
66+
67+
let renderer: TemplateRenderer<HTMLElement> | undefined;
6368

69+
export const md: TemplateRenderer = (template, ...values) => {
70+
const root = (renderer ??= MarkdownRenderer())(template, ...values);
71+
const codes = root.querySelectorAll<HTMLElement>("code[class^=language-]");
72+
if (codes.length > 0) import("./highlight.js").then(({highlight}) => codes.forEach(highlight));
6473
return root.childNodes.length === 1 ? root.removeChild(root.firstChild!) : root;
6574
};

src/runtime/stdlib/template.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22

3-
export type TemplateRenderer = (template: readonly string[], ...values: any[]) => Node;
4-
export type RawTemplateRenderer = (template: {raw: readonly string[]}, ...values: any[]) => Node;
5-
export type AsyncRawTemplateRenderer = (template: {raw: readonly string[]}, ...values: any[]) => Promise<Node>;
3+
export type TemplateRenderer<T = Node> = (template: readonly string[], ...values: any[]) => T;
4+
export type RawTemplateRenderer<T = Node> = (template: {raw: readonly string[]}, ...values: any[]) => T;
5+
export type AsyncRawTemplateRenderer<T = Node> = (template: {raw: readonly string[]}, ...values: any[]) => Promise<T>;

src/vite/observable.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {Sourcemap} from "../javascript/sourcemap.js";
1010
import {transpile} from "../javascript/transpile.js";
1111
import {parseTemplate} from "../javascript/template.js";
1212
import {highlight} from "../runtime/stdlib/highlight.js";
13-
import {md} from "../runtime/stdlib/md.js";
13+
import {MarkdownRenderer} from "../runtime/stdlib/md.js";
1414

1515
export function observable({
1616
window = new JSDOM().window,
@@ -35,6 +35,7 @@ export function observable({
3535
const tsource = await readFile(template, "utf-8");
3636
const document = parser.parseFromString(tsource, "text/html");
3737
const statics = new Set<Cell>();
38+
const md = MarkdownRenderer({document});
3839

3940
const version = (await import("../../package.json", {with: {type: "json"}})).default.version;
4041
let generator = document.querySelector("meta[name=generator]");
@@ -54,10 +55,12 @@ export function observable({
5455
div.id = `cell-${id}`;
5556
div.className = "observablehq observablehq--cell";
5657
if (mode === "md") {
57-
md.document = document;
5858
const template = parseTemplate(value);
5959
if (!template.expressions.length) statics.add(cell);
60-
div.appendChild(md([stripExpressions(template, value)]));
60+
const content = md([stripExpressions(template, value)]);
61+
const codes = content.querySelectorAll<HTMLElement>("code[class^=language-]");
62+
await Promise.all(Array.from(codes, highlight));
63+
div.appendChild(content);
6164
} else if (mode === "html") {
6265
const template = parseTemplate(value);
6366
if (!template.expressions.length) statics.add(cell);

0 commit comments

Comments
 (0)