/**
* # 模块 Elementor 图片 WP 尺寸变体解析(仅服务端)
* 原站 image widget 的 image_size=medium/thumbnail/... 时 WP 渲染 -300x272 这类裁剪/缩放
* 变体文件;dump 的 image.url 只存原图。这里在服务端按 uploads 目录里实际存在的
* `-x.` 变体择优改写 image.url(选宽度 ≤ 目标里最大的;无变体保持原图)。
*/
import { readdirSync } from "node:fs";
import { join, dirname } from "node:path";
/** # 常量 WP 尺寸名 → 目标宽度(Elementor 常用档位) **/
const SIZE_TARGET: Record = {
thumbnail: 150,
medium: 300,
medium_large: 768,
large: 1024,
};
/** # 成员变量 目录列表缓存(同一目录只扫一次盘) **/
const dirCache = new Map();
/** # 成员变量 变体解析结果缓存(url+size → 最终 url) **/
const resultCache = new Map();
/** # 函数 对外:媒体 URL 的 WP 尺寸变体解析(服务端;无变体返回原 URL)
* 参数 url 原图 URL;targetWidth WP 档位目标宽(150/300/600/768/1024)
*/
export function resolveMediaVariantUrl(url: string, targetWidth: number): string {
if (typeof window !== "undefined" || !url?.includes("/uploads/")) return url;
return findVariant(url, targetWidth) ?? url;
}
/** # 函数 查找某原图在给定目标宽度下的最佳变体 URL(无则返回 null)
* 参数 url 原图 URL(本地 /uploads/... 或原站绝对地址);targetWidth WP 档位目标宽
*/
function findVariant(url: string, targetWidth: number): string | null {
// 归一化到本地 /uploads/ 路径(dump 里可能存原站绝对地址)
const up = url.indexOf("/uploads/");
if (up === -1) return null;
const localPath = url.slice(up); // /uploads/2022/06/Group-201.png
const m = localPath.match(/^(.*\/)([^/]+)\.(\w+)$/);
if (!m) return null;
const [, dir, base, ext] = m;
const fsDir = join("public", dir);
let files = dirCache.get(fsDir);
if (!files) {
try {
files = readdirSync(fsDir);
} catch {
files = [];
}
dirCache.set(fsDir, files);
}
// 候选:-x.;取宽度 ≤ 目标里最大的(同宽取更矮的,WP medium 缩放优先于裁剪)
const prefix = `${base}-`;
let best: { w: number; h: number; file: string } | null = null;
for (const f of files) {
const fm = f.match(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\d+)x(\\d+)\\.${ext}$`));
if (!fm) continue;
const w = Number(fm[1]);
const h = Number(fm[2]);
if (w > targetWidth) continue;
if (!best || w > best.w || (w === best.w && h < best.h)) best = { w, h, file: f };
}
return best ? `${dir}${best.file}` : null;
}
/** # 函数 就地解析文档内 image widget 的尺寸变体(服务端 loader 调用;浏览器端空操作)
* 参数 doc elementor 文档(数组或 {elements} 包装),image.url 被改写为变体地址
*/
export function resolveImageVariants(doc: unknown): void {
if (typeof window !== "undefined") return;
const walk = (el: unknown) => {
if (Array.isArray(el)) {
for (const x of el) walk(x);
return;
}
if (!el || typeof el !== "object") return;
const e = el as { id?: unknown; widgetType?: unknown; settings?: Record; elements?: unknown[] };
if (e.widgetType === "image") {
const st = e.settings ?? {};
const target = SIZE_TARGET[String(st.image_size ?? "")];
const img = st.image as { url?: string } | undefined;
if (target && img?.url && img.url.includes("/uploads/")) {
const key = `${img.url}|${target}`;
let resolved = resultCache.get(key);
if (resolved === undefined) {
resolved = findVariant(img.url, target) ?? "";
resultCache.set(key, resolved);
}
if (resolved) img.url = resolved;
}
}
for (const c of Array.isArray(e.elements) ? e.elements : []) walk(c);
};
walk(doc);
}