@rr0/cms
Version:
RR0 Content Management System (CMS)
120 lines (119 loc) • 5.18 kB
JavaScript
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}`);
}
}
}