vuepress-plugin-full-text-search2
Version:
VuePress v2 plugin that adds full-text search box.
165 lines (161 loc) • 4.29 kB
JavaScript
// src/index.ts
import { path } from "@vuepress/utils";
import { fileURLToPath } from "url";
// src/prepare-search-index.ts
import { Parser } from "htmlparser2";
var HMR_CODE = `
if (import.meta.webpackHot) {
import.meta.webpackHot.accept()
if (__VUE_HMR_RUNTIME__[UPD_NAME]) {
__VUE_HMR_RUNTIME__[UPD_NAME](searchIndex)
}
}
if (import.meta.hot) {
import.meta.hot.accept(({ searchIndex }) => {
if (__VUE_HMR_RUNTIME__[UPD_NAME]) {
__VUE_HMR_RUNTIME__[UPD_NAME](searchIndex)
}
})
}
`;
async function prepareSearchIndex({
app
}) {
const searchIndex = [];
for (const page of app.pages) {
searchIndex.push({
path: page.path,
title: page.title,
pathLocale: page.pathLocale,
contents: extractPageContents(page)
});
}
let content = `
export const searchIndex = ${JSON.stringify(searchIndex, null, 2)}
export const UPD_NAME = 'update-vuepress-plugin-full-text-search2-search-index'
`;
if (app.env.isDev) {
content += HMR_CODE;
}
return app.writeTemp(
"internal/vuepress-plugin-full-text-search2-search-index.js",
content
);
}
function extractPageContents(page) {
const results = [];
const slugs = /* @__PURE__ */ new Map();
const headers = [...page.headers];
while (headers.length) {
const h = headers.shift();
slugs.set(h.slug, h.title);
headers.push(...h.children);
}
let ignoreElement = 0;
let withinHeader = 0;
let scope = {
header: "",
slug: "",
content: ""
};
results.push(scope);
const parser = new Parser({
ontext(text) {
if (ignoreElement) {
return;
}
const prop = withinHeader ? "header" : "content";
scope[prop] += text;
},
onopentag(name, attribute) {
if (ignoreElement || name === "script" || name === "style" || name === "div" && attribute.class === "line-numbers") {
ignoreElement++;
return;
}
if (withinHeader) {
withinHeader++;
return;
}
if (!/^h\d$/u.test(name)) {
return;
}
const id = attribute.id;
const title = slugs.get(id);
if (title) {
scope = {
header: title,
slug: id,
content: ""
};
results.push(scope);
ignoreElement++;
} else {
scope = {
header: "",
slug: id,
content: ""
};
results.push(scope);
withinHeader++;
}
},
onclosetag() {
if (ignoreElement) {
ignoreElement--;
return;
}
if (withinHeader) {
withinHeader--;
}
}
});
parser.parseComplete(page.contentRendered);
return results.map((p) => {
p.header = p.header.replace(/\s{2,}/g, " ").replace(/^#/g, "").trim();
p.content = p.content.replace(/\s{2,}/g, " ").trim();
return p;
}).filter((p) => p.content || p.header);
}
// src/index.ts
import * as chokidar from "chokidar";
var filename = typeof __filename !== "undefined" ? __filename : fileURLToPath(import.meta.url);
var dirname = path.dirname(filename);
var fullTextSearchPlugin = fullTextSearchPluginFunction;
var src_default = fullTextSearchPlugin;
function fullTextSearchPluginFunction(options = {}) {
return {
name: "vuepress-plugin-full-text-search2",
define: {
__SEARCH_LOCALES__: ("locales" in options ? options == null ? void 0 : options.locales : {}) ?? {}
},
clientConfigFile: path.resolve(dirname, "./client/clientConfig.js"),
// @ts-expect-error -- Backward compatibility for vuepress@<=2.0.0-beta.43
clientAppEnhanceFiles: path.resolve(
dirname,
"./client/clientAppEnhance.js"
),
onPrepared(app) {
prepareSearchIndex({ app });
},
onWatched: (app, watchers) => {
const searchIndexWatcher = chokidar.watch("internal/pageData/*", {
cwd: app.dir.temp(),
ignoreInitial: true
});
searchIndexWatcher.on("add", () => {
prepareSearchIndex({ app });
});
searchIndexWatcher.on("change", () => {
prepareSearchIndex({ app });
});
searchIndexWatcher.on("unlink", () => {
prepareSearchIndex({ app });
});
watchers.push(searchIndexWatcher);
}
};
}
export {
src_default as default,
fullTextSearchPlugin
};