Skip to content

Commit 80ebb6f

Browse files
committed
fix markup init js
1 parent 9e2dbb5 commit 80ebb6f

File tree

4 files changed

+123
-118
lines changed

4 files changed

+123
-118
lines changed

web_src/js/markup/asciicast.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
export async function initMarkupRenderAsciicast(elMarkup: HTMLElement): Promise<void> {
2-
const el = elMarkup.querySelector('.asciinema-player-container');
3-
if (!el) return;
1+
import {queryElems} from '../utils/dom.ts';
42

5-
const [player] = await Promise.all([
6-
// @ts-expect-error: module exports no types
7-
import(/* webpackChunkName: "asciinema-player" */'asciinema-player'),
8-
import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'),
9-
]);
3+
export async function initMarkupRenderAsciicast(elMarkup: HTMLElement): Promise<void> {
4+
queryElems(elMarkup, '.asciinema-player-container', async (el) => {
5+
const [player] = await Promise.all([
6+
// @ts-expect-error: module exports no types
7+
import(/* webpackChunkName: "asciinema-player" */'asciinema-player'),
8+
import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'),
9+
]);
1010

11-
player.create(el.getAttribute('data-asciinema-player-src'), el, {
12-
// poster (a preview frame) to display until the playback is started.
13-
// Set it to 1 hour (also means the end if the video is shorter) to make the preview frame show more.
14-
poster: 'npt:1:0:0',
11+
player.create(el.getAttribute('data-asciinema-player-src'), el, {
12+
// poster (a preview frame) to display until the playback is started.
13+
// Set it to 1 hour (also means the end if the video is shorter) to make the preview frame show more.
14+
poster: 'npt:1:0:0',
15+
});
1516
});
1617
}

web_src/js/markup/codecopy.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {svg} from '../svg.ts';
2+
import {queryElems} from '../utils/dom.ts';
23

34
export function makeCodeCopyButton(): HTMLButtonElement {
45
const button = document.createElement('button');
@@ -8,11 +9,12 @@ export function makeCodeCopyButton(): HTMLButtonElement {
89
}
910

1011
export function initMarkupCodeCopy(elMarkup: HTMLElement): void {
11-
const el = elMarkup.querySelector('.code-block code'); // .markup .code-block code
12-
if (!el || !el.textContent) return;
13-
14-
const btn = makeCodeCopyButton();
15-
// remove final trailing newline introduced during HTML rendering
16-
btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
17-
el.after(btn);
12+
// .markup .code-block code
13+
queryElems(elMarkup, '.code-block code', (el) => {
14+
if (!el.textContent) return;
15+
const btn = makeCodeCopyButton();
16+
// remove final trailing newline introduced during HTML rendering
17+
btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
18+
el.after(btn);
19+
});
1820
}

web_src/js/markup/math.ts

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {displayError} from './common.ts';
2+
import {queryElems} from '../utils/dom.ts';
23

34
function targetElement(el: Element): {target: Element, displayAsBlock: boolean} {
45
// The target element is either the parent "code block with loading indicator", or itself
@@ -12,35 +13,35 @@ function targetElement(el: Element): {target: Element, displayAsBlock: boolean}
1213
}
1314

1415
export async function initMarkupCodeMath(elMarkup: HTMLElement): Promise<void> {
15-
const el = elMarkup.querySelector('code.language-math'); // .markup code.language-math'
16-
if (!el) return;
16+
// .markup code.language-math'
17+
queryElems(elMarkup, 'code.language-math', async (el) => {
18+
const [{default: katex}] = await Promise.all([
19+
import(/* webpackChunkName: "katex" */'katex'),
20+
import(/* webpackChunkName: "katex" */'katex/dist/katex.css'),
21+
]);
1722

18-
const [{default: katex}] = await Promise.all([
19-
import(/* webpackChunkName: "katex" */'katex'),
20-
import(/* webpackChunkName: "katex" */'katex/dist/katex.css'),
21-
]);
23+
const MAX_CHARS = 1000;
24+
const MAX_SIZE = 25;
25+
const MAX_EXPAND = 1000;
2226

23-
const MAX_CHARS = 1000;
24-
const MAX_SIZE = 25;
25-
const MAX_EXPAND = 1000;
27+
const {target, displayAsBlock} = targetElement(el);
28+
if (target.hasAttribute('data-render-done')) return;
29+
const source = el.textContent;
2630

27-
const {target, displayAsBlock} = targetElement(el);
28-
if (target.hasAttribute('data-render-done')) return;
29-
const source = el.textContent;
30-
31-
if (source.length > MAX_CHARS) {
32-
displayError(target, new Error(`Math source of ${source.length} characters exceeds the maximum allowed length of ${MAX_CHARS}.`));
33-
return;
34-
}
35-
try {
36-
const tempEl = document.createElement(displayAsBlock ? 'p' : 'span');
37-
katex.render(source, tempEl, {
38-
maxSize: MAX_SIZE,
39-
maxExpand: MAX_EXPAND,
40-
displayMode: displayAsBlock, // katex: true for display (block) mode, false for inline mode
41-
});
42-
target.replaceWith(tempEl);
43-
} catch (error) {
44-
displayError(target, error);
45-
}
31+
if (source.length > MAX_CHARS) {
32+
displayError(target, new Error(`Math source of ${source.length} characters exceeds the maximum allowed length of ${MAX_CHARS}.`));
33+
return;
34+
}
35+
try {
36+
const tempEl = document.createElement(displayAsBlock ? 'p' : 'span');
37+
katex.render(source, tempEl, {
38+
maxSize: MAX_SIZE,
39+
maxExpand: MAX_EXPAND,
40+
displayMode: displayAsBlock, // katex: true for display (block) mode, false for inline mode
41+
});
42+
target.replaceWith(tempEl);
43+
} catch (error) {
44+
displayError(target, error);
45+
}
46+
});
4647
}

web_src/js/markup/mermaid.ts

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {isDarkTheme} from '../utils.ts';
22
import {makeCodeCopyButton} from './codecopy.ts';
33
import {displayError} from './common.ts';
4+
import {queryElems} from '../utils/dom.ts';
45

56
const {mermaidMaxSourceCharacters} = window.config;
67

@@ -11,77 +12,77 @@ body {margin: 0; padding: 0; overflow: hidden}
1112
blockquote, dd, dl, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {margin: 0}`;
1213

1314
export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise<void> {
14-
const el = elMarkup.querySelector('code.language-mermaid'); // .markup code.language-mermaid
15-
if (!el) return;
16-
17-
const {default: mermaid} = await import(/* webpackChunkName: "mermaid" */'mermaid');
18-
19-
mermaid.initialize({
20-
startOnLoad: false,
21-
theme: isDarkTheme() ? 'dark' : 'neutral',
22-
securityLevel: 'strict',
23-
suppressErrorRendering: true,
24-
});
25-
26-
const pre = el.closest('pre');
27-
if (pre.hasAttribute('data-render-done')) return;
28-
29-
const source = el.textContent;
30-
if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) {
31-
displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`));
32-
return;
33-
}
34-
35-
try {
36-
await mermaid.parse(source);
37-
} catch (err) {
38-
displayError(pre, err);
39-
return;
40-
}
41-
42-
try {
43-
// can't use bindFunctions here because we can't cross the iframe boundary. This
44-
// means js-based interactions won't work but they aren't intended to work either
45-
const {svg} = await mermaid.render('mermaid', source);
46-
47-
const iframe = document.createElement('iframe');
48-
iframe.classList.add('markup-content-iframe', 'tw-invisible');
49-
iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`;
50-
51-
const mermaidBlock = document.createElement('div');
52-
mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden');
53-
mermaidBlock.append(iframe);
54-
55-
const btn = makeCodeCopyButton();
56-
btn.setAttribute('data-clipboard-text', source);
57-
mermaidBlock.append(btn);
58-
59-
const updateIframeHeight = () => {
60-
const body = iframe.contentWindow?.document?.body;
61-
if (body) {
62-
iframe.style.height = `${body.clientHeight}px`;
63-
}
64-
};
65-
66-
iframe.addEventListener('load', () => {
67-
pre.replaceWith(mermaidBlock);
68-
mermaidBlock.classList.remove('tw-hidden');
69-
updateIframeHeight();
70-
setTimeout(() => { // avoid flash of iframe background
71-
mermaidBlock.classList.remove('is-loading');
72-
iframe.classList.remove('tw-invisible');
73-
}, 0);
74-
75-
// update height when element's visibility state changes, for example when the diagram is inside
76-
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
77-
// would initially set a incorrect height and the correct height is set during this callback.
78-
(new IntersectionObserver(() => {
79-
updateIframeHeight();
80-
}, {root: document.documentElement})).observe(iframe);
15+
// .markup code.language-mermaid
16+
queryElems(elMarkup, 'code.language-mermaid', async (el) => {
17+
const {default: mermaid} = await import(/* webpackChunkName: "mermaid" */'mermaid');
18+
19+
mermaid.initialize({
20+
startOnLoad: false,
21+
theme: isDarkTheme() ? 'dark' : 'neutral',
22+
securityLevel: 'strict',
23+
suppressErrorRendering: true,
8124
});
8225

83-
document.body.append(mermaidBlock);
84-
} catch (err) {
85-
displayError(pre, err);
86-
}
26+
const pre = el.closest('pre');
27+
if (pre.hasAttribute('data-render-done')) return;
28+
29+
const source = el.textContent;
30+
if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) {
31+
displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`));
32+
return;
33+
}
34+
35+
try {
36+
await mermaid.parse(source);
37+
} catch (err) {
38+
displayError(pre, err);
39+
return;
40+
}
41+
42+
try {
43+
// can't use bindFunctions here because we can't cross the iframe boundary. This
44+
// means js-based interactions won't work but they aren't intended to work either
45+
const {svg} = await mermaid.render('mermaid', source);
46+
47+
const iframe = document.createElement('iframe');
48+
iframe.classList.add('markup-content-iframe', 'tw-invisible');
49+
iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`;
50+
51+
const mermaidBlock = document.createElement('div');
52+
mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden');
53+
mermaidBlock.append(iframe);
54+
55+
const btn = makeCodeCopyButton();
56+
btn.setAttribute('data-clipboard-text', source);
57+
mermaidBlock.append(btn);
58+
59+
const updateIframeHeight = () => {
60+
const body = iframe.contentWindow?.document?.body;
61+
if (body) {
62+
iframe.style.height = `${body.clientHeight}px`;
63+
}
64+
};
65+
66+
iframe.addEventListener('load', () => {
67+
pre.replaceWith(mermaidBlock);
68+
mermaidBlock.classList.remove('tw-hidden');
69+
updateIframeHeight();
70+
setTimeout(() => { // avoid flash of iframe background
71+
mermaidBlock.classList.remove('is-loading');
72+
iframe.classList.remove('tw-invisible');
73+
}, 0);
74+
75+
// update height when element's visibility state changes, for example when the diagram is inside
76+
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
77+
// would initially set a incorrect height and the correct height is set during this callback.
78+
(new IntersectionObserver(() => {
79+
updateIframeHeight();
80+
}, {root: document.documentElement})).observe(iframe);
81+
});
82+
83+
document.body.append(mermaidBlock);
84+
} catch (err) {
85+
displayError(pre, err);
86+
}
87+
});
8788
}

0 commit comments

Comments
 (0)