diff --git a/website/scripts/MarkdownRenderer.js b/website/scripts/MarkdownRenderer.js index 731d3ee..b1590c3 100644 --- a/website/scripts/MarkdownRenderer.js +++ b/website/scripts/MarkdownRenderer.js @@ -115,9 +115,9 @@ export class MarkdownRenderer { // Standalone image const imgBlockMatch = block.match(/^!\[([^\]]*)\]\((.+)\)$/); if (imgBlockMatch) { - const { src, caption, imageStyle } = MarkdownRenderer.#parseImageMeta(imgBlockMatch[1], imgBlockMatch[2]); + const { src, caption, imageAttributes } = MarkdownRenderer.#parseImageMeta(imgBlockMatch[1], imgBlockMatch[2]); if (src) { - return `
${caption}${caption ? `
${caption}
` : ""}
`; + return `
${caption}${caption ? `
${caption}
` : ""}
`; } } @@ -351,9 +351,9 @@ export class MarkdownRenderer { out = out.replace( /!\[([^\]]*)\]\((.+?)\)/g, (_, altText, rawMeta) => { - const { src, caption } = MarkdownRenderer.#parseImageMeta(altText, rawMeta); + const { src, caption, imageAttributes } = MarkdownRenderer.#parseImageMeta(altText, rawMeta); if (!src) return MarkdownRenderer.#escape(altText); - return `${caption}`; + return `${caption}`; } ); @@ -408,16 +408,44 @@ export class MarkdownRenderer { return null; } + /** + * Applies intrinsic pixel scaling to markdown images with a size token. + * + * @param {ParentNode} container + * @returns {void} + */ + static applyImageScale(container) { + container.querySelectorAll("img[data-md-scale]").forEach(img => { + const scale = parseFloat(img.getAttribute("data-md-scale") || ""); + if (!Number.isFinite(scale) || scale <= 0) return; + + const applyScale = () => { + if (!img.naturalWidth) return; + const targetWidthPx = Math.max(1, Math.round(img.naturalWidth * scale)); + img.style.width = `${targetWidthPx}px`; + img.style.height = "auto"; + img.style.maxWidth = "100%"; + }; + + if (img.complete) { + applyScale(); + return; + } + + img.addEventListener("load", applyScale, { once: true }); + }); + } + /** * Parses markdown image metadata including optional title and size token. * * Supported format: ![alt](url "title") - * - Numeric alt-only values (e.g. 0.50) are treated as width scale metadata. + * - Numeric alt-only values (e.g. 0.50) are treated as intrinsic size scale metadata. * - Title text is used as the visible caption when present. * * @param {string} rawAlt * @param {string} rawMeta - * @returns {{ src: string | null, caption: string, imageStyle: string }} + * @returns {{ src: string | null, caption: string, imageAttributes: string }} */ static #parseImageMeta(rawAlt, rawMeta) { const meta = rawMeta.trim(); @@ -426,7 +454,7 @@ export class MarkdownRenderer { return { src: null, caption: MarkdownRenderer.#escape(rawAlt), - imageStyle: "", + imageAttributes: "", }; } @@ -435,12 +463,12 @@ export class MarkdownRenderer { const trimmedAlt = rawAlt.trim(); const sizeScale = MarkdownRenderer.#parseImageSizeScale(trimmedAlt); const caption = titleCaption || (sizeScale === null ? MarkdownRenderer.#escape(trimmedAlt) : ""); - const imageStyle = sizeScale !== null ? ` style="width: ${sizeScale * 100}%;"` : ""; + const imageAttributes = sizeScale !== null ? ` data-md-scale="${sizeScale}"` : ""; return { src, caption, - imageStyle, + imageAttributes, }; } diff --git a/website/scripts/NodeRenderer.js b/website/scripts/NodeRenderer.js index a1c530a..2a214f3 100644 --- a/website/scripts/NodeRenderer.js +++ b/website/scripts/NodeRenderer.js @@ -433,6 +433,7 @@ export class NodeRenderer { if (!response.ok) throw new Error(`HTTP ${response.status}`); const text = await response.text(); target.innerHTML = MarkdownRenderer.render(text); + MarkdownRenderer.applyImageScale(target); } catch (err) { target.innerHTML = `

Failed to load content.

`; console.error(`[NodeRenderer] Could not load markdown "${src}":`, err); diff --git a/website/scripts/nodes/BlogPostNode.js b/website/scripts/nodes/BlogPostNode.js index c7a9a93..92598af 100644 --- a/website/scripts/nodes/BlogPostNode.js +++ b/website/scripts/nodes/BlogPostNode.js @@ -99,6 +99,7 @@ export class BlogPostNode extends NodeBase { // Import and use MarkdownRenderer directly const { MarkdownRenderer } = await import('../MarkdownRenderer.js'); contentDiv.innerHTML = MarkdownRenderer.render(post.content); + MarkdownRenderer.applyImageScale(contentDiv); // Add copy button handlers contentDiv.querySelectorAll('.md-copy-btn').forEach(btn => {