'use strict'; const node_fs = require('node:fs'); const posix = require('node:path/posix'); const colorette = require('colorette'); const glob = require('glob'); const vite = require('vite'); const defaultMapGlobPatterns = [ "**/.vitepress/cache/@nolebase/vitepress-plugin-thumbnail-hash/thumbhashes/map.json", "**/thumbhashes/map.json" ]; const logModulePrefix = `${colorette.cyan(`@nolebase/markdown-it-unlazy-img`)}${colorette.gray(":")}`; const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i; function ensureThumbhashMap(options, cachedMap) { if (cachedMap) return cachedMap; if ("map" in options) return options.map; if ("mapFilePath" in options) return JSON.parse(node_fs.readFileSync(options.mapFilePath, "utf-8")); if (!("mapGlobPatterns" in options)) throw new Error("either thumbhash.map, thumbhash.mapFilePath, or thumbhash.mapGlobPatterns is required"); let mapGlobPatterns = []; if (Array.isArray(options.mapGlobPatterns)) mapGlobPatterns = options.mapGlobPatterns; else mapGlobPatterns = [options.mapGlobPatterns]; mapGlobPatterns = mapGlobPatterns.filter((pattern) => typeof pattern === "string" && pattern.trim() !== ""); if (mapGlobPatterns.length === 0) mapGlobPatterns = defaultMapGlobPatterns; let foundThumbhashMapPath = ""; for (const pattern of mapGlobPatterns) { const matchedFiles = glob.globSync( pattern, { ignore: "node_modules/**", nodir: true } ); if (matchedFiles.length === 0) continue; if (!matchedFiles[0]) continue; foundThumbhashMapPath = matchedFiles[0]; } return JSON.parse(node_fs.readFileSync(foundThumbhashMapPath, "utf-8")); } const UnlazyImages = () => { return (md, options) => { const { thumbhash = { mapGlobPatterns: defaultMapGlobPatterns }, imgElementTag = "UnLazyImage" } = options ?? {}; if (!thumbhash) throw new Error("thumbhash is required"); let thumbhashMap; if ("map" in thumbhash) thumbhashMap = thumbhash.map; const imageRule = md.renderer.rules.image; md.renderer.rules.image = (tokens, idx, mdOptions, env, self) => { thumbhashMap = ensureThumbhashMap(thumbhash, thumbhashMap); if (!env.path && !env.relativePath) throw new Error("env.path and env.relativePath are required"); const token = tokens[idx]; const imgSrc = token.attrGet("src"); if (!imgSrc) return imageRule(tokens, idx, mdOptions, env, self); if (EXTERNAL_URL_RE.test(imgSrc)) return imageRule(tokens, idx, mdOptions, env, self); if (![".png", ".jpg", ".jpeg"].some((ext) => imgSrc.endsWith(ext))) { if (options?.logFormatNotSupportedWarning) { console.warn(`${logModulePrefix} ${colorette.yellow("[WARN]")} unsupported image format for ${imgSrc}`); } return imageRule(tokens, idx, mdOptions, env, self); } let resolvedImgSrc = decodeURIComponent(imgSrc); const props = { src: imgSrc, alt: token.attrGet("alt") || "", thumbhash: void 0 }; token.attrs?.forEach(([name, value]) => { if (name === "src" || name === "alt") return; props[name] = value; }); if (!/^\.?\//.test(resolvedImgSrc)) { props.src = `./${decodeURIComponent(resolvedImgSrc)}`; } if (resolvedImgSrc.startsWith("/")) { resolvedImgSrc = resolvedImgSrc.slice(1); } else { const relativePathDir = vite.normalizePath(posix.dirname(env.relativePath)); resolvedImgSrc = posix.join(relativePathDir, resolvedImgSrc); if (resolvedImgSrc.startsWith("/")) resolvedImgSrc = resolvedImgSrc.slice(1); } const matchedThumbhashData = thumbhashMap?.[resolvedImgSrc]; if (!matchedThumbhashData) { console.warn(`${logModulePrefix} ${colorette.yellow(`[WARN]`)} thumbhash data not found for ${resolvedImgSrc}`); return imageRule(tokens, idx, mdOptions, env, self); } props.thumbhash = matchedThumbhashData.dataBase64; props.placeholderSrc = matchedThumbhashData.dataUrl; props.width = matchedThumbhashData.originalWidth.toString(); props.height = matchedThumbhashData.originalHeight.toString(); props.autoSizes = "true"; return `<${imgElementTag} ${Object.entries(props).map(([name, value]) => `${name}="${value}"`).join(" ")} />`; }; }; }; exports.EXTERNAL_URL_RE = EXTERNAL_URL_RE; exports.UnlazyImages = UnlazyImages;