UNPKG

@rr0/cms

Version:

RR0 Content Management System (CMS)

120 lines (119 loc) 5.18 kB
import { DomReplaceCommand } from "ssg-api"; import * as path from "path"; import * as fs from "fs"; import { imageSizeFromFile } from "image-size/fromFile"; /** * Register images (`<img>` tags) required in an HTML file. */ export class ImageCommand extends DomReplaceCommand { constructor(outBaseDir, maxWidth, maxHeight, baseUrl = "") { super("img:not(.raw)", undefined); this.outBaseDir = outBaseDir; this.maxWidth = maxWidth; this.maxHeight = maxHeight; this.baseUrl = baseUrl; } async createReplacer(context) { return { replace: async (imgEl) => { const src = imgEl.src; const imgParentEl = imgEl.parentElement; if (imgParentEl.tagName === "FIGURE") { const captionEl = imgParentEl.querySelector("figcaption"); if (captionEl) { const caption = captionEl.textContent; if (!imgEl.alt) { imgEl.alt = caption.replace(/\n+/g, "").trim(); } } else { const caption = imgEl.alt; if (caption) { const newCaptionEl = imgParentEl.ownerDocument.createElement("figcaption"); newCaptionEl.textContent = caption; imgParentEl.appendChild(newCaptionEl); } } } context.debug(context.file.name, "requires image", src); try { let isExternal = src.startsWith("http"); let isAbsolute = src.startsWith("/"); if (isAbsolute) { imgEl.src = this.baseUrl + src; } let imgPath = isExternal ? src : isAbsolute ? path.join(".", src) : path.join(path.dirname(context.file.name), src); imgEl.loading = "lazy"; if (!isExternal && !imgEl.width && !imgEl.height) { const dimensions = await imageSizeFromFile(imgPath); let width = dimensions.width; let height = dimensions.height; if (width > this.maxWidth) { const ratio = this.maxWidth / width; width = this.maxWidth; height *= ratio; } if (height > this.maxHeight) { const ratio = this.maxHeight / height; height = this.maxHeight; width *= ratio; } imgEl.width = width; imgEl.height = height; imgEl.setAttribute("onclick", `this.classList.contains('zoomed') ? document.exitFullscreen() && this.classList.toggle('zoomed', false): this.classList.toggle('zoomed', true) && this.requestFullscreen()`); context.images.add(src); } } catch (e) { context.warn("Could not determine size of image ", src, e); } return imgEl; } }; } async postExecute(context) { const imagesUrls = context.images; if (imagesUrls.size > 0) { for (const imageUrl of imagesUrls) { this.handleImage(context, imageUrl); } imagesUrls.clear(); } } handleImage(context, imageUrl) { const inputFile = context.file.name; if (imageUrl) { const isLocal = !imageUrl.startsWith("http"); if (isLocal) { const contextDir = path.dirname(inputFile); const isAbsolute = path.isAbsolute(imageUrl); const inFile = isAbsolute ? path.resolve("." + imageUrl) : path.resolve(path.join(contextDir, imageUrl)); const outBaseDir = isAbsolute ? this.outBaseDir : path.join(this.outBaseDir, contextDir); const outRel = path.join(outBaseDir, imageUrl); context.debug("Copying", imageUrl, "to", outRel); const outFile = path.resolve(outRel); try { const outDir = path.dirname(outFile); if (!fs.existsSync(outDir)) { fs.mkdirSync(outDir, { recursive: true }); } fs.copyFileSync(inFile, outFile); } catch (e) { if (e.code === "ENOENT") { context.warn(`File ${inFile} does not exist`); } else { throw e; } } } else { context.warn(`File ${imageUrl} is external; will not copy it.`); } } else { context.warn(`Empty image src in ${inputFile}`); } } }