Skip to content

Commit 475e888

Browse files
committed
update
1 parent 68c8434 commit 475e888

2 files changed

Lines changed: 128 additions & 22 deletions

File tree

app/src/modal.js

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,52 @@
1-
import { marked } from 'marked';
1+
const MARKED_CDN_URL = 'https://cdn.jsdelivr.net/npm/marked@16.4.1/lib/marked.esm.js';
22

3-
marked.setOptions({
4-
breaks: true,
5-
gfm: true,
6-
});
3+
let markedPromise = null;
4+
5+
function resolveMarkedInstance() {
6+
if (markedPromise) {
7+
return markedPromise;
8+
}
9+
10+
markedPromise = (async () => {
11+
const existing = globalThis.marked;
12+
if (existing && typeof existing.parse === 'function') {
13+
return existing;
14+
}
15+
16+
try {
17+
const module = await import('marked');
18+
const candidate = module.marked ?? module.default ?? module;
19+
if (candidate && typeof candidate.parse === 'function') {
20+
return candidate;
21+
}
22+
} catch (error) {
23+
console.warn('Falling back to CDN marked import', error);
24+
}
25+
26+
const fallback = await import(MARKED_CDN_URL);
27+
const candidate = fallback.marked ?? fallback.default ?? fallback;
28+
if (!candidate || typeof candidate.parse !== 'function') {
29+
throw new Error('Unable to load marked parser');
30+
}
31+
32+
return candidate;
33+
})()
34+
.then((instance) => {
35+
if (typeof instance.setOptions === 'function') {
36+
instance.setOptions({
37+
breaks: true,
38+
gfm: true,
39+
});
40+
}
41+
return instance;
42+
})
43+
.catch((error) => {
44+
markedPromise = null;
45+
throw error;
46+
});
47+
48+
return markedPromise;
49+
}
750

851
const FOCUSABLE_SELECTOR = [
952
'a[href]',
@@ -42,24 +85,32 @@ function normalizeToText(value) {
4285

4386
function renderMarkdown(target, markdown) {
4487
if (!target) {
45-
return false;
88+
return Promise.resolve(false);
4689
}
4790

4891
const text = typeof markdown === 'string' ? markdown.trim() : '';
4992
if (!text) {
50-
return false;
93+
return Promise.resolve(false);
5194
}
5295

53-
const html = marked.parse(text);
54-
if (!html) {
55-
return false;
56-
}
96+
return resolveMarkedInstance()
97+
.then((instance) => {
98+
const parser = typeof instance.parse === 'function' ? instance.parse.bind(instance) : null;
99+
const html = parser ? parser(text) : '';
100+
if (!html) {
101+
return false;
102+
}
57103

58-
target.innerHTML = html;
59-
target.querySelectorAll('ul').forEach((list) => {
60-
list.classList.add('modal__list');
61-
});
62-
return true;
104+
target.innerHTML = html;
105+
target.querySelectorAll('ul').forEach((list) => {
106+
list.classList.add('modal__list');
107+
});
108+
return true;
109+
})
110+
.catch((error) => {
111+
console.error('Failed to render markdown content', error);
112+
return false;
113+
});
63114
}
64115

65116
function getFocusableElements(container) {
@@ -223,6 +274,7 @@ export function createModalController(root) {
223274
}
224275

225276
content.innerHTML = '';
277+
content.hidden = true;
226278

227279
const listItems = Array.isArray(card.details)
228280
? card.details
@@ -254,10 +306,27 @@ export function createModalController(root) {
254306
|| '';
255307
}
256308

257-
const rendered = renderMarkdown(content, markdownSource);
258-
if (!rendered && markdownSource) {
259-
content.textContent = markdownSource;
260-
}
309+
const fallbackText = markdownSource || detailsTextForFallback || summaryTextCandidate || '';
310+
311+
renderMarkdown(content, markdownSource)
312+
.then((rendered) => {
313+
if (rendered) {
314+
content.hidden = false;
315+
return;
316+
}
317+
318+
if (fallbackText) {
319+
content.textContent = fallbackText;
320+
content.hidden = false;
321+
}
322+
})
323+
.catch(() => {
324+
if (fallbackText) {
325+
content.textContent = fallbackText;
326+
content.hidden = false;
327+
}
328+
});
329+
261330
if (card.backgroundColor) {
262331
dialog.style.background = card.backgroundColor;
263332
} else {

app/tests/modal.spec.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,41 @@
1-
import { describe, expect, it } from 'vitest';
1+
import {
2+
afterEach,
3+
beforeEach,
4+
describe,
5+
expect,
6+
it,
7+
} from 'vitest';
28
import { createModalController } from '../src/modal.js';
39

410
describe('modal controller', () => {
5-
it('renders list items when details is an array', () => {
11+
beforeEach(() => {
12+
globalThis.marked = {
13+
parse(markdown) {
14+
if (typeof markdown !== 'string' || !markdown.trim()) {
15+
return '';
16+
}
17+
18+
const items = markdown
19+
.split('\n')
20+
.map((line) => line.replace(/^\s*(?:[-*+]\s+|\d+\.\s+)/, '').trim())
21+
.filter(Boolean);
22+
23+
if (items.length === 0) {
24+
return '';
25+
}
26+
27+
const renderedItems = items.map((item) => `<li>${item}</li>`).join('');
28+
return `<ul>${renderedItems}</ul>`;
29+
},
30+
setOptions: () => {},
31+
};
32+
});
33+
34+
afterEach(() => {
35+
delete globalThis.marked;
36+
});
37+
38+
it('renders list items when details is an array', async () => {
639
const root = document.createElement('div');
740
const controller = createModalController(root);
841

@@ -13,6 +46,10 @@ describe('modal controller', () => {
1346
details: ['First detail', 'Second detail'],
1447
});
1548

49+
await new Promise((resolve) => {
50+
setTimeout(resolve, 0);
51+
});
52+
1653
const content = root.querySelector('.modal__content');
1754
const list = content?.querySelector('ul.modal__list');
1855
expect(list).not.toBeNull();

0 commit comments

Comments
 (0)