diff --git a/apps/docs/image-sizes.spec.ts b/apps/docs/image-sizes.spec.ts index 4aef6d51..238bc1ec 100644 --- a/apps/docs/image-sizes.spec.ts +++ b/apps/docs/image-sizes.spec.ts @@ -1,4 +1,4 @@ -import {mkdirSync, mkdtempSync, writeFileSync} from 'node:fs'; +import {mkdirSync, mkdtempSync, symlinkSync, writeFileSync} from 'node:fs'; import {tmpdir} from 'node:os'; import {join} from 'node:path'; import {imageSize, readImageSizes} from './image-sizes'; @@ -42,6 +42,22 @@ describe('imageSize', () => { }); describe('readImageSizes', () => { + it('reads a directory whose path has a space', () => { + const dir = mkdtempSync(join(tmpdir(), 'image sizes ')); + writeFileSync(join(dir, 'a.png'), png(2, 1)); + expect(readImageSizes(dir)).toEqual({'/a.png': [2, 1]}); + }); + + it('returns nothing when there is no public directory', () => { + expect(readImageSizes(join(tmpdir(), 'image-sizes-missing-dir'))).toEqual({}); + }); + + it('fails when an image cannot be read, instead of leaving it out', () => { + const dir = mkdtempSync(join(tmpdir(), 'image-sizes-')); + symlinkSync(join(dir, 'gone.png'), join(dir, 'broken.png')); + expect(() => readImageSizes(dir)).toThrow(/ENOENT/); + }); + it('keys the sizes by URL path', () => { const dir = mkdtempSync(join(tmpdir(), 'image-sizes-')); mkdirSync(join(dir, 'images/panel'), {recursive: true}); diff --git a/apps/docs/image-sizes.ts b/apps/docs/image-sizes.ts index 8a46c6e3..0fe91c5c 100644 --- a/apps/docs/image-sizes.ts +++ b/apps/docs/image-sizes.ts @@ -1,4 +1,4 @@ -import {readFileSync, readdirSync, statSync} from 'node:fs'; +import {existsSync, readFileSync, readdirSync, statSync} from 'node:fs'; import {join, relative, sep} from 'node:path'; /** @@ -9,7 +9,11 @@ import {join, relative, sep} from 'node:path'; */ export type ImageSizes = Record; -/** The sizes of the PNG and JPEG files under `dir`, keyed by their URL path. */ +/** + * The sizes of the PNG and JPEG files under `dir`, keyed by their URL path. + * A missing `dir` gives none; a file that cannot be read fails the build + * rather than leaving its image without a size. + */ export function readImageSizes(dir: string): ImageSizes { const sizes: ImageSizes = {}; const walk = (current: string) => { @@ -22,11 +26,7 @@ export function readImageSizes(dir: string): ImageSizes { } } }; - try { - walk(dir); - } catch { - // no public/ directory - } + if (existsSync(dir)) walk(dir); return sizes; } diff --git a/apps/docs/src/marked-extensions/index.ts b/apps/docs/src/marked-extensions/index.ts index e4ee96d1..ba55d798 100644 --- a/apps/docs/src/marked-extensions/index.ts +++ b/apps/docs/src/marked-extensions/index.ts @@ -37,9 +37,7 @@ export async function getBuildExtensions(): Promise { ]); return [ // The pages are rendered here, so `` gets the sizes of the files in public/. - ...createNgmdRuntimeExtensions( - readImageSizes(new URL('../../public', import.meta.url).pathname), - ), + ...createNgmdRuntimeExtensions(readImageSizes(`${import.meta.dirname}/../../public`)), ngmdCodeImportExtension, ngmdCodeGroupExtension, ngmdCodeHighlightExtension, diff --git a/apps/docs/src/marked-extensions/marked-extensions.spec.ts b/apps/docs/src/marked-extensions/marked-extensions.spec.ts index eb9b086b..4679bb51 100644 --- a/apps/docs/src/marked-extensions/marked-extensions.spec.ts +++ b/apps/docs/src/marked-extensions/marked-extensions.spec.ts @@ -25,6 +25,13 @@ describe('ngmd-image', () => { expect(html).toContain('
Tom & Jerry
'); }); + it('finds the size of an image whose src has a query or fragment', () => { + const html = new Marked({ + extensions: [createNgmdImageExtension({'/images/a.png': [1280, 800]})], + }).parse('') as string; + expect(html).toContain('A { const html = new Marked({ extensions: [createNgmdImageExtension({'/images/a.png': [1280, 800]})], diff --git a/apps/docs/src/marked-extensions/ngmd-image.ts b/apps/docs/src/marked-extensions/ngmd-image.ts index 85920b8c..28235247 100644 --- a/apps/docs/src/marked-extensions/ngmd-image.ts +++ b/apps/docs/src/marked-extensions/ngmd-image.ts @@ -50,7 +50,8 @@ export function createNgmdImageExtension(sizes: NgmdImageSizes = {}) { const width = token.width?.trim() ?? ''; const maxWidth = /^\d+(\.\d+)?$/.test(width) ? `${width}px` : width; const style = maxWidth ? ` style="max-width: ${escapeHtml(maxWidth)}"` : ''; - const size = sizes[token.src]; + // A query or fragment (`/a.png?v=2`) still names the file `/a.png`. + const size = sizes[token.src.split(/[?#]/)[0]]; const dimensions = size ? ` width="${size[0]}" height="${size[1]}"` : ''; const caption = token.caption ? `
${escapeHtml(token.caption)}
` : ''; return `
${escapeHtml(token.alt)}${caption}
`;