UNPKG

@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
'use strict'; 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