mirror of
https://github.com/litruv/lit.ruv.wtf.git
synced 2026-07-24 02:36:02 +10:00
feat: Implement intrinsic image scaling in Markdown renderer
This commit is contained in:
@@ -115,9 +115,9 @@ export class MarkdownRenderer {
|
|||||||
// Standalone image
|
// Standalone image
|
||||||
const imgBlockMatch = block.match(/^!\[([^\]]*)\]\((.+)\)$/);
|
const imgBlockMatch = block.match(/^!\[([^\]]*)\]\((.+)\)$/);
|
||||||
if (imgBlockMatch) {
|
if (imgBlockMatch) {
|
||||||
const { src, caption, imageStyle } = MarkdownRenderer.#parseImageMeta(imgBlockMatch[1], imgBlockMatch[2]);
|
const { src, caption, imageAttributes } = MarkdownRenderer.#parseImageMeta(imgBlockMatch[1], imgBlockMatch[2]);
|
||||||
if (src) {
|
if (src) {
|
||||||
return `<figure class="md-figure"><img class="md-img" src="${src}" alt="${caption}"${imageStyle} />${caption ? `<figcaption class="md-figcaption">${caption}</figcaption>` : ""}</figure>`;
|
return `<figure class="md-figure"><img class="md-img" src="${src}" alt="${caption}"${imageAttributes} />${caption ? `<figcaption class="md-figcaption">${caption}</figcaption>` : ""}</figure>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,9 +351,9 @@ export class MarkdownRenderer {
|
|||||||
out = out.replace(
|
out = out.replace(
|
||||||
/!\[([^\]]*)\]\((.+?)\)/g,
|
/!\[([^\]]*)\]\((.+?)\)/g,
|
||||||
(_, altText, rawMeta) => {
|
(_, altText, rawMeta) => {
|
||||||
const { src, caption } = MarkdownRenderer.#parseImageMeta(altText, rawMeta);
|
const { src, caption, imageAttributes } = MarkdownRenderer.#parseImageMeta(altText, rawMeta);
|
||||||
if (!src) return MarkdownRenderer.#escape(altText);
|
if (!src) return MarkdownRenderer.#escape(altText);
|
||||||
return `<img class="md-img md-img--inline" src="${src}" alt="${caption}" />`;
|
return `<img class="md-img md-img--inline" src="${src}" alt="${caption}"${imageAttributes} />`;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -408,16 +408,44 @@ export class MarkdownRenderer {
|
|||||||
return null;
|
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.
|
* Parses markdown image metadata including optional title and size token.
|
||||||
*
|
*
|
||||||
* Supported format: 
|
* Supported format: 
|
||||||
* - 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.
|
* - Title text is used as the visible caption when present.
|
||||||
*
|
*
|
||||||
* @param {string} rawAlt
|
* @param {string} rawAlt
|
||||||
* @param {string} rawMeta
|
* @param {string} rawMeta
|
||||||
* @returns {{ src: string | null, caption: string, imageStyle: string }}
|
* @returns {{ src: string | null, caption: string, imageAttributes: string }}
|
||||||
*/
|
*/
|
||||||
static #parseImageMeta(rawAlt, rawMeta) {
|
static #parseImageMeta(rawAlt, rawMeta) {
|
||||||
const meta = rawMeta.trim();
|
const meta = rawMeta.trim();
|
||||||
@@ -426,7 +454,7 @@ export class MarkdownRenderer {
|
|||||||
return {
|
return {
|
||||||
src: null,
|
src: null,
|
||||||
caption: MarkdownRenderer.#escape(rawAlt),
|
caption: MarkdownRenderer.#escape(rawAlt),
|
||||||
imageStyle: "",
|
imageAttributes: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,12 +463,12 @@ export class MarkdownRenderer {
|
|||||||
const trimmedAlt = rawAlt.trim();
|
const trimmedAlt = rawAlt.trim();
|
||||||
const sizeScale = MarkdownRenderer.#parseImageSizeScale(trimmedAlt);
|
const sizeScale = MarkdownRenderer.#parseImageSizeScale(trimmedAlt);
|
||||||
const caption = titleCaption || (sizeScale === null ? MarkdownRenderer.#escape(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 {
|
return {
|
||||||
src,
|
src,
|
||||||
caption,
|
caption,
|
||||||
imageStyle,
|
imageAttributes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -433,6 +433,7 @@ export class NodeRenderer {
|
|||||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
target.innerHTML = MarkdownRenderer.render(text);
|
target.innerHTML = MarkdownRenderer.render(text);
|
||||||
|
MarkdownRenderer.applyImageScale(target);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
target.innerHTML = `<p class="md-error">Failed to load content.</p>`;
|
target.innerHTML = `<p class="md-error">Failed to load content.</p>`;
|
||||||
console.error(`[NodeRenderer] Could not load markdown "${src}":`, err);
|
console.error(`[NodeRenderer] Could not load markdown "${src}":`, err);
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export class BlogPostNode extends NodeBase {
|
|||||||
// Import and use MarkdownRenderer directly
|
// Import and use MarkdownRenderer directly
|
||||||
const { MarkdownRenderer } = await import('../MarkdownRenderer.js');
|
const { MarkdownRenderer } = await import('../MarkdownRenderer.js');
|
||||||
contentDiv.innerHTML = MarkdownRenderer.render(post.content);
|
contentDiv.innerHTML = MarkdownRenderer.render(post.content);
|
||||||
|
MarkdownRenderer.applyImageScale(contentDiv);
|
||||||
|
|
||||||
// Add copy button handlers
|
// Add copy button handlers
|
||||||
contentDiv.querySelectorAll('.md-copy-btn').forEach(btn => {
|
contentDiv.querySelectorAll('.md-copy-btn').forEach(btn => {
|
||||||
|
|||||||
Reference in New Issue
Block a user