UNPKG

vitepress-plugin-pagefind

Version:

vitepress plugin, Offline full-text search based on pagefind implementation.

281 lines (278 loc) 8.55 kB
// src/index.ts import { Buffer } from "node:buffer"; import path from "node:path"; import { fileURLToPath } from "node:url"; import fs from "node:fs"; import process from "node:process"; import { stringify } from "javascript-stringify"; import { getFileLastModifyTime, grayMatter, joinPath } from "@sugarat/theme-shared"; // src/node.ts import { execSync } from "node:child_process"; import { withBase } from "@sugarat/theme-shared"; var ignoreSelectors = [ // 侧边栏内容 "div.aside", // 标题锚点 "a.header-anchor" ]; async function buildEnd(pagefindOps, siteConfig) { const ignore = [ ...new Set(ignoreSelectors.concat(pagefindOps?.excludeSelector || [])) ]; const { log } = console; log(); log("=== pagefind: https://pagefind.app/ ==="); let command = `npx pagefind --site "${siteConfig.outDir}"`; if (ignore.length) { command += ` --exclude-selectors "${ignore.join(", ")}"`; } if (typeof pagefindOps.forceLanguage === "string") { command += ` --force-language ${pagefindOps.forceLanguage}`; } if (pagefindOps.indexingCommand) { command = pagefindOps.indexingCommand; } log(command); log(); execSync(command, { stdio: "inherit" }); } function getPagefindHead(base) { return [ [ "script", {}, `import('${withBase(base || "", "/pagefind/pagefind.js")}') .then((module) => { window.__pagefind__ = module module.init() }) .catch(() => { // console.log('not load /pagefind/pagefind.js') })` ] ]; } function chineseSearchOptimize(input) { const segmenter = new Intl.Segmenter("zh-CN", { granularity: "word" }); const result = []; for (const it of segmenter.segment(input)) { if (it.isWordLike) { result.push(it.segment); } } return result.join(" "); } // src/utils/index.ts function formatDate(d, fmt = "yyyy-MM-dd hh:mm:ss") { if (!(d instanceof Date)) { d = new Date(d); } const o = { "M+": d.getMonth() + 1, // 月份 "d+": d.getDate(), // 日 "h+": d.getHours(), // 小时 "m+": d.getMinutes(), // 分 "s+": d.getSeconds(), // 秒 "q+": Math.floor((d.getMonth() + 3) / 3), // 季度 "S": d.getMilliseconds() // 毫秒 }; if (/(y+)/.test(fmt)) { fmt = fmt.replace( RegExp.$1, `${d.getFullYear()}`.substr(4 - RegExp.$1.length) ); } for (const k in o) { if (new RegExp(`(${k})`).test(fmt)) fmt = fmt.replace( RegExp.$1, RegExp.$1.length === 1 ? o[k] : `00${o[k]}`.substr(`${o[k]}`.length) ); } return fmt; } function formatShowDate(date, lang) { const source = +new Date(date); const now = +/* @__PURE__ */ new Date(); const diff = now - source; const oneSeconds = 1e3; const oneMinute = oneSeconds * 60; const oneHour = oneMinute * 60; const oneDay = oneHour * 24; const oneWeek = oneDay * 7; const langMap = { "zh-cn": { justNow: "\u521A\u521A", secondsAgo: "\u79D2\u524D", minutesAgo: "\u5206\u949F\u524D", hoursAgo: "\u5C0F\u65F6\u524D", daysAgo: "\u5929\u524D", weeksAgo: "\u5468\u524D" }, "en-us": { justNow: " just now", secondsAgo: " seconds ago", minutesAgo: " minutes ago", hoursAgo: " hours ago", daysAgo: " days ago", weeksAgo: " weeks ago" } }; const mapValue = langMap[lang.toLowerCase()] || langMap["en-us"]; if (diff < 10) { return mapValue.justNow; } if (diff < oneMinute) { return `${Math.floor(diff / oneSeconds)}${mapValue.secondsAgo}`; } if (diff < oneHour) { return `${Math.floor(diff / oneMinute)}${mapValue.minutesAgo}`; } if (diff < oneDay) { return `${Math.floor(diff / oneHour)}${mapValue.hoursAgo}`; } if (diff < oneWeek) { return `${Math.floor(diff / oneDay)}${mapValue.daysAgo}`; } return formatDate(new Date(date), "yyyy-MM-dd"); } // src/index.ts function isESM() { return typeof __filename === "undefined" || typeof __dirname === "undefined"; } function getDirname() { return isESM() ? path.dirname(fileURLToPath(import.meta.url)) : __dirname; } var aliasSearchVueFile = `${getDirname()}/../src/Search.vue`; function meta2string(frontmatter) { return `base64:${Buffer.from(encodeURIComponent(JSON.stringify(frontmatter))).toString("base64")}`; } function pagefindPlugin(searchConfig = {}) { const virtualModuleId = "virtual:pagefind"; const resolvedVirtualModuleId = `\0${virtualModuleId}`; let resolveConfig; let vitepressConfig; let dynamicRoutes; const pluginOps = { name: "vitepress-plugin-pagefind", config: () => { return { resolve: { alias: { "./VPNavBarSearch.vue": aliasSearchVueFile } } }; }, configResolved(config) { if (searchConfig.manual) { return; } if (resolveConfig) { return; } resolveConfig = config; vitepressConfig = config.vitepress; dynamicRoutes = vitepressConfig.dynamicRoutes; if (!vitepressConfig) { return; } const selfBuildEnd = vitepressConfig.buildEnd; vitepressConfig.buildEnd = async (siteConfig) => { await selfBuildEnd?.(siteConfig); await buildEnd(searchConfig, siteConfig); const okMark = "\x1B[32m\u2713\x1B[0m"; console.log(`${okMark} generating pagefind Indexing...`); }; const selfTransformHead = vitepressConfig.transformHead; vitepressConfig.transformHead = async (ctx) => { const selfHead = await Promise.resolve(selfTransformHead?.(ctx)) || []; return selfHead.concat(getPagefindHead(ctx.siteData.base)); }; }, resolveId(id) { if (id === virtualModuleId) { return resolvedVirtualModuleId; } }, load(id) { if (id !== resolvedVirtualModuleId) return; return ` import { ref } from 'vue' export const searchConfig = ${stringify(searchConfig)} `; }, // 添加检索的内容标识 async transform(code, id, options) { if (!id.includes(".md")) { return code; } let { searchParams, pathname, protocol } = new URL(id, "file:"); pathname = decodeURIComponent(pathname); if (!pathname.endsWith(".md")) { return code; } const isWindows = process.platform === "win32"; const fullPath = isWindows ? `${protocol}${pathname}` : pathname; const _dynamicRoutes = Array.isArray(dynamicRoutes) ? dynamicRoutes : dynamicRoutes?.routes || []; const dynamicRoute = _dynamicRoutes.find((route) => fullPath.toLowerCase() === route.fullPath.toLowerCase()); const isDynamicRoute = !!dynamicRoute; const filepath = isDynamicRoute ? joinPath(vitepressConfig.srcDir, `/${dynamicRoute.route}`) : pathname; const isExist = fs.existsSync(filepath); if (!isExist) { this.warn(`${id}: not parse ${filepath} please contact the author for assistance`); return code; } if (!searchParams.size || searchParams.has("lang.ts")) { const fileContent = await fs.promises.readFile(filepath, "utf-8"); const { data: frontmatter, content } = grayMatter(fileContent, { excerpt: true }); if (!content.trim()) { return code; } if (frontmatter["pagefind-indexed"] === false) { return code; } const attrs = { "data-pagefind-body": true }; if (!searchConfig.manual) { frontmatter.date = +new Date(frontmatter.date || await getFileLastModifyTime(id)); if (typeof searchConfig.filter === "function") { attrs["data-pagefind-meta"] = meta2string(frontmatter); } } if (!code.includes(options?.ssr ? "_push(`" : '_createElementBlock("div", null')) { if (!code.includes(`${id}?vue&type=script&setup=true&lang.ts`)) { this.warn(`${options?.ssr ? "SSR" : "Client"} ${id} may not be a valid file, will not be indexed, please contact the author for assistance`); } return code; } if (options?.ssr) { return code.replace("_push(`", `Object.assign(_attrs, ${stringify(attrs)});_push(\``); } return code.replace('_createElementBlock("div", null', `_createElementBlock("div", ${stringify(attrs)}`); } return code; } }; return pluginOps; } export { chineseSearchOptimize, formatShowDate, meta2string, pagefindPlugin }; //# sourceMappingURL=index.mjs.map