/** * # 模块 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); }