diff --git a/source/package.json b/source/package.json index c8b068a..2c6b161 100644 --- a/source/package.json +++ b/source/package.json @@ -37,7 +37,7 @@ "class-variance-authority": "0.7.1", "clsx": "2.1.1", "dompurify": "3.4.17", - "katex": "0.16.47", + "katex": "0.18.2", "lucide-react": "1.31.0", "mermaid": "11.17.2", "radix-ui": "1.6.7", diff --git a/source/pnpm-lock.yaml b/source/pnpm-lock.yaml index 8af2166..7bed4bc 100644 --- a/source/pnpm-lock.yaml +++ b/source/pnpm-lock.yaml @@ -5,6 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: + katex: 0.18.2 miniflare>sharp: 0.35.4 importers: @@ -21,8 +22,8 @@ importers: specifier: 3.4.17 version: 3.4.17 katex: - specifier: 0.16.47 - version: 0.16.47 + specifier: 0.18.2 + version: 0.18.2 lucide-react: specifier: 1.31.0 version: 1.31.0(react@19.2.6) @@ -2154,8 +2155,8 @@ packages: jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - katex@0.16.47: - resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + katex@0.18.2: + resolution: {integrity: sha512-3snve4y0SXTMequLim1FMiPvLGElySam0blN5xQZBqEY3lOJz1TnuaaiugnBZBqHzlSAGmFTYL7MCQkORuLKCA==} hasBin: true keyv@4.5.4: @@ -5252,7 +5253,7 @@ snapshots: es6-error: 4.1.1 matcher: 3.0.0 roarr: 2.15.4 - semver: 7.7.4 + semver: 7.8.5 serialize-error: 7.0.1 optional: true @@ -5493,7 +5494,7 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - katex@0.16.47: + katex@0.18.2: dependencies: commander: 8.3.0 @@ -5826,7 +5827,7 @@ snapshots: dompurify: 3.4.17 es-toolkit: 1.52.0 fastdom: 1.0.12 - katex: 0.16.47 + katex: 0.18.2 khroma: 2.1.0 marked: 16.4.2 roughjs: 4.6.6 @@ -5915,7 +5916,7 @@ snapshots: dependencies: '@types/katex': 0.16.8 devlop: 1.1.0 - katex: 0.16.47 + katex: 0.18.2 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 @@ -6081,11 +6082,11 @@ snapshots: node-abi@4.35.0: dependencies: - semver: 7.7.4 + semver: 7.8.5 node-api-version@0.2.1: dependencies: - semver: 7.7.4 + semver: 7.8.5 node-gyp@12.4.0: dependencies: @@ -6094,7 +6095,7 @@ snapshots: graceful-fs: 4.2.11 nopt: 9.0.0 proc-log: 6.1.0 - semver: 7.7.4 + semver: 7.8.5 tar: 7.5.22 tinyglobby: 0.2.17 undici: 6.28.1 @@ -6365,7 +6366,7 @@ snapshots: '@types/katex': 0.16.8 hast-util-from-html-isomorphic: 2.0.0 hast-util-to-text: 4.0.2 - katex: 0.16.47 + katex: 0.18.2 unist-util-visit-parents: 6.0.2 vfile: 6.0.3 diff --git a/source/pnpm-workspace.yaml b/source/pnpm-workspace.yaml index 1b07c85..8eef7d7 100644 --- a/source/pnpm-workspace.yaml +++ b/source/pnpm-workspace.yaml @@ -20,4 +20,6 @@ packageImportMethod: clone-or-copy verifyDepsBeforeRun: false optimisticRepeatInstall: false overrides: + # All Markdown and Mermaid math renderers must use the patched KaTeX build. + katex: 0.18.2 miniflare>sharp: 0.35.4 diff --git a/source/scripts/smoke-desktop.mjs b/source/scripts/smoke-desktop.mjs index 09fa6af..12a90f5 100644 --- a/source/scripts/smoke-desktop.mjs +++ b/source/scripts/smoke-desktop.mjs @@ -11,7 +11,7 @@ await mkdir(results, { recursive: true }); await mkdir(path.join(fixture, 'images')); await writeFile(path.join(fixture, 'images', '本地 图片.png'), Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAF0lEQVR4nGN82ejPQAgwEVQxqmgAFAEAEL0B0TGFd2QAAAAASUVORK5CYII=', 'base64')); const filename = path.join(fixture, '中文 file.md'); -await writeFile(filename, '# Desktop smoke test\n\n离线阅读验证。SearchNeedle is here. SearchNeedle is here again.\n\n![local image](images/本地%20图片.png)\n\n## Features\n\n| A | B |\n|---|---|\n| one | two |\n\nInline $E=mc^2$.\n\n```js\nconst local = true;\n```\n\n```mermaid\ngraph TD\n A[Local file] --> B[Desktop reader]\n```\n\n\n'); +await writeFile(filename, '# Desktop smoke test\n\n离线阅读验证。SearchNeedle is here. SearchNeedle is here again.\n\n![local image](images/本地%20图片.png)\n\n## Features\n\n| A | B |\n|---|---|\n| one | two |\n\nInline $E=mc^2$.\n\n$$\n\\frac{1}{2} + \\sqrt{x}\n$$\n\n```js\nconst local = true;\n```\n\n```mermaid\ngraph TD\n A[Local file] --> B[Desktop reader]\n```\n\n\n'); const executable = process.env.MODU_EXECUTABLE; const launchArgs = executable ? [filename] : ['.', filename]; let app; @@ -24,7 +24,7 @@ try { page.on('request', request => { if (/^https?:/.test(request.url())) network.push(request.url()); }); await page.getByRole('heading', { name: 'Desktop smoke test', exact: true }).waitFor(); assert.equal(await page.evaluate(() => typeof window.require), 'undefined'); - assert.equal(await page.evaluate(() => window.injected), undefined); + assert.equal(await page.evaluate(() => window.__moduUntrustedMarkdownExecuted), undefined); const prefs = await app.evaluate(({ BrowserWindow }) => { const p = BrowserWindow.getAllWindows()[0].webContents.getLastWebPreferences(); return { sandbox: p.sandbox, contextIsolation: p.contextIsolation, nodeIntegration: p.nodeIntegration }; @@ -32,7 +32,12 @@ try { assert.deepEqual(prefs, { sandbox: true, contextIsolation: true, nodeIntegration: false }); await page.waitForFunction(() => { const image = document.querySelector('article img'); return image?.complete && image.naturalWidth > 0; }); assert.equal(await page.locator('article table').count(), 1); - assert.equal(await page.locator('article .katex').count(), 1); + assert.equal(await page.locator('article .katex').count(), 2); + assert.equal(await page.locator('article .katex-display').count(), 1); + assert.equal(await page.locator('article .katex-error').count(), 0); + assert.equal(await page.locator('article .katex-mathml').count(), 2); + await page.evaluate(() => document.fonts.ready); + assert.equal(await page.locator('article .katex-html').first().evaluate(el => getComputedStyle(el).fontFamily.includes('KaTeX')), true); await page.locator('.mermaid-diagram, .diagram-block').first().scrollIntoViewIfNeeded().catch(() => {}); await page.locator('article .mermaid-rendered svg, article .mermaid-diagram svg, article .diagram-block svg').first().waitFor({ timeout: 30000 }); assert.match((await page.locator(".diagram-svg").textContent()) || "", /Local file/); @@ -63,8 +68,10 @@ try { const secondFile = path.join(fixture, 'second.md'); await writeFile(secondFile, '# Second instance\n\nOpened from the operating system.'); const childArgs = executable ? [secondFile] : ['.', secondFile]; + // Playwright may launch Electron through cmd.exe on Windows. + const childExecutable = executable || await app.evaluate(() => process.execPath); await new Promise((resolve, reject) => { - const child = spawn(executable || app.process().spawnfile, childArgs, { env: { ...process.env, NODE_ENV: 'test', MODU_TEST_USER_DATA: path.join(fixture, 'profile') }, stdio: 'ignore' }); + const child = spawn(childExecutable, childArgs, { env: { ...process.env, NODE_ENV: 'test', MODU_TEST_USER_DATA: path.join(fixture, 'profile') }, stdio: 'ignore' }); const timer = setTimeout(() => { child.kill(); reject(new Error('Second instance did not exit')); }, 20000); child.on('error', reject); child.on('exit', code => { clearTimeout(timer); code === 0 ? resolve() : reject(new Error(`Second instance exit ${code}`)); }); });