@nolebase/vitepress-plugin-page-properties
Version:
A VitePress plugin that renders frontmatter as page properties, and makes them editable.
248 lines (232 loc) • 8.76 kB
JavaScript
;
const node_path = require('node:path');
const node_process = require('node:process');
const GrayMatter = require('gray-matter');
const vite = require('vite');
const node_fs = require('node:fs');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const GrayMatter__default = /*#__PURE__*/_interopDefaultCompat(GrayMatter);
function pathEquals(path, equals) {
return vite.normalizePath(path) === vite.normalizePath(equals);
}
function pathStartsWith(path, startsWith) {
return vite.normalizePath(path).startsWith(vite.normalizePath(startsWith));
}
function pathEndsWith(path, startsWith) {
return vite.normalizePath(path).endsWith(vite.normalizePath(startsWith));
}
function PagePropertiesMarkdownSection(options) {
const {
excludes = ["index.md"],
exclude = () => false
} = options ?? {};
let root = "";
return {
name: "@nolebase/vitepress-plugin-page-properties-markdown-section",
// May set to 'pre' since end user may use vitepress wrapped vite plugin to
// specify the plugins, which may cause this plugin to be executed after
// vitepress or the other markdown processing plugins.
enforce: "pre",
configResolved(config) {
root = config.root ?? "";
},
transform(code, id) {
function idEndsWith(endsWith) {
return pathEndsWith(node_path.relative(root, id), endsWith);
}
function idEquals(equals) {
return pathEquals(node_path.relative(root, id), equals);
}
function idStartsWith(startsWith) {
return pathStartsWith(node_path.relative(root, id), startsWith);
}
const context = {
helpers: {
pathEndsWith,
pathEquals,
pathStartsWith,
idEndsWith,
idEquals,
idStartsWith
}
};
if (!id.endsWith(".md"))
return null;
if (excludes.includes(node_path.relative(root, id)))
return null;
if (exclude(id, context))
return null;
const targetComponent = node_process.env.NODE_ENV === "development" ? TemplatePagePropertiesEditor() : TemplatePageProperties();
const parsedMarkdownContent = GrayMatter__default(code);
if ("nolebase" in parsedMarkdownContent.data && "pageProperties" in parsedMarkdownContent.data.nolebase && !parsedMarkdownContent.data.nolebase.pageProperties)
return null;
if ("pageProperties" in parsedMarkdownContent.data && !parsedMarkdownContent.data.pageProperties)
return null;
const hasFrontmatter = Object.keys(parsedMarkdownContent.data).length > 0;
const headingMatch = parsedMarkdownContent.content.match(/^# .*/m);
if (!headingMatch || !headingMatch[0] || headingMatch.index === void 0) {
if (!hasFrontmatter)
return `${targetComponent}
${code}`;
return `${GrayMatter__default.stringify(`${targetComponent}
${parsedMarkdownContent.content}`, parsedMarkdownContent.data)}`;
}
const headingPart = parsedMarkdownContent.content.slice(0, headingMatch.index + headingMatch[0].length);
const contentPart = parsedMarkdownContent.content.slice(headingMatch.index + headingMatch[0].length);
if (!hasFrontmatter)
return `${headingPart}
${targetComponent}
${contentPart}`;
return `${GrayMatter__default.stringify(`${headingPart}
${targetComponent}
${contentPart}`, parsedMarkdownContent.data)}`;
}
};
}
function TemplatePagePropertiesEditor() {
return `
<NolebasePagePropertiesEditor />
`;
}
function TemplatePageProperties() {
return `
<NolebasePageProperties />
`;
}
const languageHandlers = {
japanese: {
regex: /\p{Script=Hiragana}|\p{Script=Katakana}/gu,
// Match Japanese characters
wordsPerMinute: 400
// Hypothetical average reading speed for Japanese
},
chinese: {
regex: /\p{Script=Han}/gu,
// Match Chinese characters
wordsPerMinute: 300
// Average reading speed for Chinese
},
latinCyrillic: {
regex: /[\p{Script=Latin}\p{Script=Cyrillic}\p{Mark}\p{Punctuation}\p{Number}]+/gu,
// Match Latin and Cyrillic characters
wordsPerMinute: 160
// Average reading speed for English and similar languages
}
};
function countWordsByLanguage(content) {
return Object.keys(languageHandlers).reduce((accumulator, language) => {
const match = content.match(languageHandlers[language].regex);
accumulator[language] = match ? match.length : 0;
return accumulator;
}, {});
}
function calculateWordsCountAndReadingTime(content) {
const wordsCounts = countWordsByLanguage(content);
const totalWords = Object.values(wordsCounts).reduce((sum, count) => sum + count, 0);
const totalMinutes = Object.entries(wordsCounts).reduce((sum, [language, count]) => {
return sum + count / languageHandlers[language].wordsPerMinute;
}, 0);
return {
readingTime: Math.ceil(totalMinutes),
wordsCount: totalWords
};
}
const VirtualModuleID = "virtual:nolebase-page-properties";
const ResolvedVirtualModuleId = `\0${VirtualModuleID}`;
function normalizeWithRelative(from, path) {
return vite.normalizePath(node_path.relative(from, path)).toLowerCase();
}
function PageProperties() {
let _config;
let srcDir = "";
const calculatedPagePropertiesActualData = {};
const knownMarkdownFiles = /* @__PURE__ */ new Set();
return {
name: "@nolebase/vitepress-plugin-page-properties",
// May set to 'pre' since end user may use vitepress wrapped vite plugin to
// specify the plugins, which may cause this plugin to be executed after
// vitepress or the other markdown processing plugins.
enforce: "pre",
config: () => ({
optimizeDeps: {
exclude: [
"@nolebase/vitepress-plugin-page-properties/client"
]
},
ssr: {
noExternal: [
"@nolebase/vitepress-plugin-page-properties"
]
}
}),
configResolved(config) {
_config = config;
srcDir = _config.vitepress.srcDir;
},
resolveId(id) {
if (id === VirtualModuleID)
return ResolvedVirtualModuleId;
},
load(id) {
if (id !== ResolvedVirtualModuleId)
return null;
return `export default ${JSON.stringify(calculatedPagePropertiesActualData)}`;
},
transform(code, id) {
if (!id.endsWith(".md"))
return null;
const parsedContent = GrayMatter__default(code);
calculatedPagePropertiesActualData[normalizeWithRelative(srcDir, id)] = calculateWordsCountAndReadingTime(parsedContent.content);
},
configureServer(server) {
compatibleConfigureServer(server, (_, env) => {
env.hot.on("nolebase-page-properties:client-mounted", async (data) => {
if (!data || typeof data !== "object")
return;
if (!("page" in data && "filePath" in data.page))
return;
const toMarkdownFilePath = data.page.filePath;
if (node_path.extname(data.page.filePath) !== ".md")
return;
if (!knownMarkdownFiles.has(toMarkdownFilePath.toLowerCase())) {
try {
const exists = await node_fs.existsSync(toMarkdownFilePath);
if (!exists)
return;
const stat = await node_fs.lstatSync(toMarkdownFilePath);
if (!stat.isFile())
return;
knownMarkdownFiles.add(toMarkdownFilePath.toLowerCase());
} catch {
return;
}
}
if (!knownMarkdownFiles.has(toMarkdownFilePath.toLowerCase()))
return;
const content = await node_fs.readFileSync(toMarkdownFilePath, "utf-8");
const parsedContent = GrayMatter__default(content);
calculatedPagePropertiesActualData[toMarkdownFilePath] = calculateWordsCountAndReadingTime(parsedContent.content);
const virtualModule = env.moduleGraph.getModuleById(ResolvedVirtualModuleId);
if (!virtualModule)
return;
env.moduleGraph.invalidateModule(virtualModule);
env.hot.send({
type: "custom",
event: "nolebase-page-properties:updated",
data: calculatedPagePropertiesActualData
});
});
});
}
};
}
function compatibleConfigureServer(server, registerHandler) {
if ("environments" in server && typeof server.environments === "object" && server.environments != null) {
Object.entries(server.environments).forEach(([name, env]) => registerHandler(name, env));
} else {
registerHandler("server", server);
}
}
exports.PageProperties = PageProperties;
exports.PagePropertiesMarkdownSection = PagePropertiesMarkdownSection;
//# sourceMappingURL=index.cjs.map