vitepress-plugin-pagefind
Version:
vitepress plugin, Offline full-text search based on pagefind implementation.
281 lines (278 loc) • 8.55 kB
JavaScript
// 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