@binarynoir/vitepress-blog 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +491 -2
- package/dist/chunk-DICBXOTS.js +1001 -0
- package/dist/chunk-DICBXOTS.js.map +1 -0
- package/dist/client.cjs +176 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +51 -0
- package/dist/client.d.ts +51 -0
- package/dist/client.js +139 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +930 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-BjbV6nDK.d.cts +190 -0
- package/dist/plugin-BjbV6nDK.d.ts +190 -0
- package/dist/style.css +418 -0
- package/dist/vitepress.cjs +1243 -0
- package/dist/vitepress.cjs.map +1 -0
- package/dist/vitepress.d.cts +81 -0
- package/dist/vitepress.d.ts +81 -0
- package/dist/vitepress.js +234 -0
- package/dist/vitepress.js.map +1 -0
- package/package.json +108 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vitepress.ts","../src/collect.ts","../src/frontmatter.ts","../src/feed.ts","../src/util.ts","../src/plugin.ts","../src/render.ts","../src/types.ts","../src/scan.ts","../src/widget.ts"],"sourcesContent":["import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport type { MarkdownIt } from 'markdown-it';\nimport type { Plugin } from 'vite';\nimport type { HeadConfig, PageData, SiteConfig, TransformContext, TransformPageContext, UserConfig } from 'vitepress';\nimport { findBlogsListing, isIndex, isPost, listBlogPosts, type IndexPage, type PostPage } from './collect.js';\nimport { mayHaveBlogFrontmatter } from './frontmatter.js';\nimport { renderFeed, resolvePostImage, toFeedPath, toPageUrl, type SiteLocation } from './feed.js';\nimport { blogMarkdown } from './plugin.js';\nimport { hasWidget, widgetPages } from './widget.js';\nimport { fingerprintBlogSource, loadBlogPages, type BlogPage } from './scan.js';\nimport type { BlogOptions } from './types.js';\nimport { joinUrl } from './util.js';\n\nconst DEFAULT_FEED_LIMIT = 20;\n\n/** Where the site is published, or `null` without a site URL: feeds need absolute links. */\nexport function resolveSiteLocation(siteConfig: SiteConfig, options: BlogOptions): SiteLocation | null {\n const siteUrl = options.siteUrl ?? siteConfig.userConfig?.sitemap?.hostname ?? siteConfig.sitemap?.hostname;\n if (!siteUrl) return null;\n return {\n siteUrl: siteUrl.replace(/\\/+$/, ''),\n base: siteConfig.site?.base ?? '/',\n cleanUrls: !!siteConfig.cleanUrls,\n publicDir: siteConfig.publicDir ?? path.join(siteConfig.srcDir, 'public'),\n imagesDir: (options.imagesDir ?? 'blog-images').replace(/^\\/+|\\/+$/g, ''),\n };\n}\n\nfunction loadPages(siteConfig: SiteConfig, options: BlogOptions): BlogPage[] {\n return loadBlogPages(siteConfig.srcDir, options, siteConfig);\n}\n\n/** The posts in a blog's feed: no drafts, newest first, up to the feed's limit. */\nfunction listFeedPosts(pages: BlogPage[], index: IndexPage, options: BlogOptions): PostPage[] {\n const limit = index.index.feed?.limit ?? options.feedLimit ?? DEFAULT_FEED_LIMIT;\n return listBlogPosts(pages, index, { drafts: false, stickyFirst: false, applyLimit: false }).slice(0, limit);\n}\n\nfunction renderIndexFeed(\n pages: BlogPage[],\n index: IndexPage,\n site: SiteLocation,\n siteConfig: SiteConfig,\n options: BlogOptions,\n) {\n return renderFeed({\n index,\n posts: listFeedPosts(pages, index, options),\n site,\n language: siteConfig.site?.lang,\n siteDescription: siteConfig.site?.description,\n });\n}\n\nfunction feedLink(index: IndexPage, base: string): HeadConfig {\n const href = joinUrl(base, toFeedPath(index));\n return [\n 'link',\n { rel: 'alternate', type: 'application/rss+xml', title: index.index.feed?.title ?? index.title, href },\n ];\n}\n\n/**\n * `<head>` tags for blog pages: a feed link on each blog page, and on each\n * post Open Graph and Twitter card tags (title, summary, image, dates) plus\n * links to the feeds that carry it. Use it from VitePress's `transformHead`:\n *\n * ```ts\n * transformHead: (context) => blogHead(context, options),\n * ```\n *\n * Social tags need absolute URLs, so `og:url` and `og:image` are left out\n * without a site URL (the `siteUrl` option or `sitemap.hostname`).\n */\nexport function blogHead(context: TransformContext, options: BlogOptions = {}): HeadConfig[] {\n const { pageData, siteConfig } = context;\n const frontmatter = pageData.frontmatter ?? {};\n if (!pageData.filePath || (!frontmatter.blog && !frontmatter['blog-index'])) return [];\n const pages = loadPages(siteConfig, options);\n const page = pages.find((candidate) => candidate.relativePath === pageData.filePath);\n if (!page) return [];\n const site = resolveSiteLocation(siteConfig, options);\n const base = siteConfig.site?.base ?? '/';\n const head: HeadConfig[] = [];\n\n if (isIndex(page)) {\n if (page.index.feed) head.push(feedLink(page, base));\n if (site) head.push(['meta', { property: 'og:url', content: toPageUrl(page.routePath, site) }]);\n return head;\n }\n if (!isPost(page)) return head;\n\n const { post } = page;\n head.push(\n ['meta', { property: 'og:type', content: 'article' }],\n ['meta', { property: 'og:title', content: post.title }],\n );\n if (post.summary) head.push(['meta', { property: 'og:description', content: post.summary }]);\n if (site) head.push(['meta', { property: 'og:url', content: toPageUrl(page.routePath, site) }]);\n\n const image = site ? resolvePostImage(page, site) : null;\n if (image) {\n head.push(['meta', { property: 'og:image', content: image.url }]);\n if (post.imageAlt) head.push(['meta', { property: 'og:image:alt', content: post.imageAlt }]);\n }\n head.push(['meta', { name: 'twitter:card', content: image ? 'summary_large_image' : 'summary' }]);\n if (post.date) head.push(['meta', { property: 'article:published_time', content: post.date.toISOString() }]);\n if (post.updated) head.push(['meta', { property: 'article:modified_time', content: post.updated.toISOString() }]);\n if (post.authors.length) {\n head.push(['meta', { property: 'article:author', content: post.authors.map((author) => author.name).join(', ') }]);\n }\n if (post.category) head.push(['meta', { property: 'article:section', content: post.category }]);\n if (!post.draft) {\n for (const blog of findBlogsListing(pages, page)) if (blog.index.feed) head.push(feedLink(blog, base));\n }\n return head;\n}\n\n/**\n * Gives a post without a `description` its summary (or the first paragraph,\n * when it has no summary) as one, so VitePress writes it into the page's\n * `<meta name=\"description\">`. Use it from `transformPageData`:\n *\n * ```ts\n * transformPageData: (pageData, context) => blogPageData(pageData, context, options),\n * ```\n */\nexport function blogPageData(pageData: PageData, context: TransformPageContext, options: BlogOptions = {}): void {\n if (!pageData.frontmatter?.blog || pageData.description || !pageData.filePath) return;\n const page = loadPages(context.siteConfig, options).find((candidate) => candidate.relativePath === pageData.filePath);\n if (page?.post?.summary) pageData.description = page.post.summary;\n}\n\n/**\n * Writes an RSS feed for every blog page whose feed isn't turned off, and\n * copies post images to `imagesDir` for the feeds and social cards to link.\n * Use it from VitePress's `buildEnd`.\n */\nexport function writeBlogFeeds(siteConfig: SiteConfig, options: BlogOptions = {}): void {\n const pages = loadPages(siteConfig, options);\n const indexes = pages.filter(isIndex).filter((index) => index.index.feed);\n const site = resolveSiteLocation(siteConfig, options);\n if (!site) {\n if (indexes.length) {\n siteConfig.logger?.warn(\n '[vitepress-blog] no feeds written: set the siteUrl option or sitemap.hostname so feeds can use absolute links.',\n );\n }\n return;\n }\n\n const written = new Map<string, string>();\n for (const index of indexes) {\n const feedPath = toFeedPath(index);\n if (written.has(feedPath)) {\n siteConfig.logger?.warn(\n `[vitepress-blog] ${index.relativePath} and ${written.get(feedPath)} both write ${feedPath}; set feed.path on one of them.`,\n );\n continue;\n }\n written.set(feedPath, index.relativePath);\n const file = path.join(siteConfig.outDir, feedPath);\n mkdirSync(path.dirname(file), { recursive: true });\n writeFileSync(file, renderIndexFeed(pages, index, site, siteConfig, options));\n }\n\n for (const post of pages.filter(isPost)) {\n if (post.post.draft) continue;\n const image = resolvePostImage(post, site);\n if (!image?.file || !image.outputPath) continue;\n const target = path.join(siteConfig.outDir, image.outputPath);\n mkdirSync(path.dirname(target), { recursive: true });\n copyFileSync(image.file, target);\n }\n}\n\n/**\n * The Vite side of the blog, for `vitepress dev`:\n *\n * - VitePress caches a page's rendered output by its own source, so a new or\n * edited post would leave blog lists and \"newer / older\" links stale. This\n * adds a fingerprint of every post to blog pages and posts (busting the\n * cache) and reloads them when any of them changes.\n * - Serves each blog's feed and the copied post images, which the build\n * writes in `buildEnd`, so feed links work in development too.\n */\nexport function blogVitePlugin(options: BlogOptions = {}): Plugin {\n let siteConfig: SiteConfig | undefined;\n const currentPages = () => (siteConfig ? loadPages(siteConfig, options) : []);\n // Every file whose output shows posts: blog pages, posts, and pages with a `::: blog-posts` block.\n const dependentFiles = () => [...currentPages().map((page) => page.absolutePath), ...widgetPages];\n\n return {\n name: 'vitepress-blog',\n enforce: 'pre',\n configResolved(config) {\n siteConfig = (config as { vitepress?: SiteConfig }).vitepress;\n },\n transform(code, id) {\n if (!siteConfig || !id.endsWith('.md') || !(mayHaveBlogFrontmatter(code) || hasWidget(code))) return null;\n return { code: `${code}\\n<!-- vitepress-blog:${fingerprintBlogSource(currentPages())} -->\\n`, map: null };\n },\n configureServer(server) {\n // Vite only calls handleHotUpdate for edits; a post added or deleted changes the lists too.\n const refresh = (file: string) => {\n if (!siteConfig || !file.endsWith('.md')) return;\n for (const dependent of dependentFiles()) {\n for (const module of server.moduleGraph.getModulesByFile(dependent) ?? []) {\n server.moduleGraph.invalidateModule(module);\n }\n }\n server.ws.send({ type: 'full-reload' });\n };\n server.watcher.on('add', refresh).on('unlink', refresh);\n\n server.middlewares.use((request, response, next) => {\n if (!siteConfig || !request.url) return next();\n const base = siteConfig.site?.base ?? '/';\n const pathname = decodeURIComponent(request.url.split('?')[0]);\n if (!pathname.startsWith(base)) return next();\n const relative = pathname.slice(base.length);\n const pages = currentPages();\n // Feeds link to the live site, or to the dev server without a site URL.\n const site =\n resolveSiteLocation(siteConfig, options) ??\n resolveSiteLocation(siteConfig, { ...options, siteUrl: `http://${request.headers.host ?? 'localhost'}` })!;\n\n const index = pages.filter(isIndex).find((page) => page.index.feed && toFeedPath(page) === relative);\n if (index) {\n response.setHeader('Content-Type', 'application/rss+xml; charset=utf-8');\n response.end(renderIndexFeed(pages, index, site, siteConfig, options));\n return;\n }\n for (const post of pages.filter(isPost)) {\n const image = resolvePostImage(post, site);\n if (image?.outputPath === relative && image.file) {\n if (image.type) response.setHeader('Content-Type', image.type);\n response.end(readFileSync(image.file));\n return;\n }\n }\n next();\n });\n },\n handleHotUpdate({ file, modules, server }) {\n if (!siteConfig || !file.endsWith('.md')) return;\n const pages = currentPages();\n if (!pages.some((page) => page.absolutePath === file)) return;\n const affected = dependentFiles()\n .filter((dependent) => dependent !== file)\n .flatMap((dependent) => [...(server.moduleGraph.getModulesByFile(dependent) ?? [])]);\n return affected.length ? [...modules, ...affected] : undefined;\n },\n };\n}\n\n/**\n * Wraps a VitePress config with the whole blog: the Markdown plugin, the dev\n * server plugin, `<head>` tags, descriptions from summaries, and feeds at the\n * end of the build.\n *\n * ```ts\n * // .vitepress/config.mts\n * import { defineConfig } from 'vitepress';\n * import { withBlog } from '@binarynoir/vitepress-blog/vitepress';\n *\n * export default withBlog(defineConfig({ /* ...your config... *\\/ }), { siteUrl: 'https://example.com' });\n * ```\n *\n * Hooks you already have (`markdown.config`, `transformHead`,\n * `transformPageData`, `buildEnd`) are not replaced: the blog's runs and\n * then yours, so it composes with other `withX()` wrappers in any order.\n */\nexport function withBlog(config: UserConfig, options: BlogOptions = {}): UserConfig {\n config.markdown ??= {};\n const existingMarkdownConfig = config.markdown.config ?? (() => {});\n config.markdown.config = (md) => {\n // VitePress 2 types this callback against `markdown-it-async`'s MarkdownIt,\n // a structural superset of plain markdown-it's; the cast bridges the two.\n blogMarkdown(md as unknown as MarkdownIt, options);\n existingMarkdownConfig(md);\n };\n\n config.vite ??= {};\n config.vite.plugins = [...(config.vite.plugins ?? []), blogVitePlugin(options)];\n\n const existingTransformHead = config.transformHead;\n config.transformHead = async (context) => [\n ...blogHead(context, options),\n ...((await existingTransformHead?.(context)) ?? []),\n ];\n\n const existingTransformPageData = config.transformPageData;\n config.transformPageData = (pageData, context) => {\n blogPageData(pageData, context, options);\n return existingTransformPageData?.(pageData, context);\n };\n\n const existingBuildEnd = config.buildEnd;\n config.buildEnd = async (siteConfig) => {\n writeBlogFeeds(siteConfig, options);\n await existingBuildEnd?.(siteConfig);\n };\n\n return config;\n}\n\nexport { blogMarkdown };\nexport type { BlogOptions };\n","import type { BlogPage } from './scan.js';\nimport type { BlogPostData } from './types.js';\n\nexport type PostPage = BlogPage & { post: BlogPostData };\nexport type IndexPage = BlogPage & { index: NonNullable<BlogPage['index']> };\n\nexport function isPost(page: BlogPage): page is PostPage {\n return page.post !== null;\n}\n\nexport function isIndex(page: BlogPage): page is IndexPage {\n return page.index !== null;\n}\n\nfunction dirname(relativePath: string): string {\n return relativePath.includes('/') ? relativePath.slice(0, relativePath.lastIndexOf('/')) : '';\n}\n\nfunction isInFolders(relativePath: string, folders: string[]): boolean {\n return folders.length === 0 || folders.some((folder) => !folder || relativePath.startsWith(`${folder}/`));\n}\n\n/** How deep a blog's folder is: `''` is 0, `news` is 1, `news/eng` is 2. A blog covering several folders ranks by its deepest. */\nfunction folderDepth(index: IndexPage): number {\n return Math.max(0, ...index.index.folders.map((folder) => (folder ? folder.split('/').length : 0)));\n}\n\nfunction hasAnyTag(post: PostPage, tags: string[]): boolean {\n if (tags.length === 0) return true;\n const wanted = new Set(tags.map((tag) => tag.toLowerCase()));\n return post.post.tags.some((tag) => wanted.has(tag.toLowerCase()));\n}\n\n/**\n * Whether a blog lists a post, before drafts, tags and limits:\n *\n * - A section blog lists every post in its folders, whatever the post's scope.\n * - The main blog (`scope: site`) lists every `scope: site` post, and every\n * post that no section blog covers, so a site with only a main blog needs\n * no scope settings at all.\n */\nexport function blogCoversPost(index: IndexPage, post: PostPage, sections: IndexPage[]): boolean {\n if (index.index.scope === 'section') return isInFolders(post.relativePath, index.index.folders);\n if (!isInFolders(post.relativePath, index.index.folders)) return false;\n if (post.post.scope === 'site') return true;\n return !sections.some((section) => isInFolders(post.relativePath, section.index.folders));\n}\n\nfunction compareDates(a: Date | null, b: Date | null): number {\n if (a && b) return b.getTime() - a.getTime();\n // Undated posts go last.\n return a ? -1 : b ? 1 : 0;\n}\n\n/** Newest first; undated posts last, then by title. */\nexport function compareByDate(a: PostPage, b: PostPage): number {\n return compareDates(a.post.date, b.post.date) || a.post.title.localeCompare(b.post.title);\n}\n\nexport interface ListOptions {\n /** Include `draft: true` posts. */\n drafts: boolean;\n /** Pin `sticky: true` posts to the top, up to the blog's `stickyLimit`. */\n stickyFirst: boolean;\n /** Apply the blog's `limit`. */\n applyLimit: boolean;\n}\n\n/**\n * Splits date-ordered posts into the sticky ones pinned to the top (the\n * newest `limit` of them, or all with `0`) and the rest, still in date order.\n */\nexport function pinStickyPosts(posts: PostPage[], limit: number): { pinned: PostPage[]; rest: PostPage[] } {\n const sticky = posts.filter((post) => post.post.sticky);\n const pinned = new Set(limit ? sticky.slice(0, limit) : sticky);\n return { pinned: [...pinned], rest: posts.filter((post) => !pinned.has(post)) };\n}\n\n/** The posts a blog page lists, in order. */\nexport function listBlogPosts(pages: BlogPage[], index: IndexPage, options: ListOptions): PostPage[] {\n const sections = pages.filter(isIndex).filter((page) => page.index.scope === 'section');\n let posts = pages\n .filter(isPost)\n .filter((post) => options.drafts || !post.post.draft)\n .filter((post) => blogCoversPost(index, post, sections) && hasAnyTag(post, index.index.tags))\n .sort(compareByDate);\n if (options.stickyFirst) {\n const { pinned, rest } = pinStickyPosts(posts, index.index.stickyLimit);\n posts = [...pinned, ...rest];\n }\n return options.applyLimit && index.index.limit ? posts.slice(0, index.index.limit) : posts;\n}\n\n/**\n * The blog a post belongs to, for its \"newer / older\" links: the deepest\n * section blog that lists it, otherwise the first main blog that does.\n */\nexport function findHomeBlog(pages: BlogPage[], post: PostPage, drafts: boolean): IndexPage | null {\n const listing = pages\n .filter(isIndex)\n .filter((index) => listBlogPosts(pages, index, { drafts, stickyFirst: false, applyLimit: false }).includes(post));\n const sections = listing.filter((index) => index.index.scope === 'section');\n if (sections.length) return deepestFirst(sections, post.relativePath)[0];\n return listing[0] ?? null;\n}\n\n/** Section blogs, deepest folder first; a tie goes to the blog whose own page sits in a folder above `relativePath`. */\nfunction deepestFirst(sections: IndexPage[], relativePath: string): IndexPage[] {\n const isAbove = (index: IndexPage) => Number(isInFolders(relativePath, [dirname(index.relativePath)]));\n return [...sections].sort((a, b) => folderDepth(b) - folderDepth(a) || isAbove(b) - isAbove(a));\n}\n\n/**\n * The blog any page belongs to, post or not: the deepest section blog whose\n * folders hold it, else a main blog whose folders hold it, else the first\n * main blog. So the home page belongs to the main blog, and a section's pages\n * to the section's blog.\n */\nexport function findBlogForPage(pages: BlogPage[], relativePath: string): IndexPage | null {\n const indexes = pages.filter(isIndex);\n const covering = indexes.filter((index) => isInFolders(relativePath, index.index.folders));\n const sections = covering.filter((index) => index.index.scope === 'section');\n if (sections.length) return deepestFirst(sections, relativePath)[0];\n const main = indexes.filter((index) => index.index.scope === 'site');\n return main.find((index) => covering.includes(index)) ?? main[0] ?? null;\n}\n\n/** Every blog page that lists the post, for the feed links in its `<head>`. */\nexport function findBlogsListing(pages: BlogPage[], post: PostPage): IndexPage[] {\n return pages\n .filter(isIndex)\n .filter((index) =>\n listBlogPosts(pages, index, { drafts: false, stickyFirst: false, applyLimit: false }).includes(post),\n );\n}\n","import path from 'node:path';\nimport { parse } from 'yaml';\nimport type {\n BlogAuthor,\n BlogFeedConfig,\n BlogIndexConfig,\n BlogLayout,\n BlogOptions,\n BlogPostData,\n BlogScope,\n} from './types.js';\n\nexport const POST_FRONTMATTER_KEY = 'blog';\nexport const INDEX_FRONTMATTER_KEY = 'blog-index';\n\n// An opening `---` line, an optional YAML body, and a closing `---` line.\nconst FRONTMATTER = /^---[ \\t]*\\r?\\n(?:([\\s\\S]*?)\\r?\\n)?---[ \\t]*(?:\\r?\\n|$)/;\nconst BLOG_KEY = /^blog(?:-index)?[ \\t]*:/m;\nconst FENCED_CODE = /^[ \\t]*(`{3,}|~{3,})[^\\n]*\\n[\\s\\S]*?(?:^[ \\t]*\\1[`~]*[ \\t]*$|(?![\\s\\S]))/gm;\nconst HEADING_1 = /^ {0,3}#[ \\t]+(.*?)(?:[ \\t]+#+)?[ \\t]*$/m;\n// `((tag|label))`, `((tag/label/background/foreground))` and their `((<tag…))`\n// form, as `@binarynoir/vitepress-markdown-tags` writes them.\nconst TAG_SYNTAX = /\\(\\(<?tag([|/])[^|/)\\r\\n]*(?:\\1[^|/)\\r\\n]*){0,2}\\)\\)/g;\nconst MAX_SUMMARY_LENGTH = 200;\n\n/**\n * Splits a Markdown file into its YAML frontmatter and the content after it.\n * Invalid YAML throws, so callers can report it.\n */\nexport function readFrontmatter(raw: string): { data: Record<string, unknown>; content: string } {\n const source = raw.replace(/^/, '');\n const match = FRONTMATTER.exec(source);\n if (!match) return { data: {}, content: source };\n const data: unknown = match[1] ? parse(match[1]) : null;\n return {\n data: data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : {},\n content: source.slice(match[0].length),\n };\n}\n\n/** A cheap test that skips the YAML parse for the many pages that aren't part of a blog. */\nexport function mayHaveBlogFrontmatter(raw: string): boolean {\n const frontmatter = FRONTMATTER.exec(raw.replace(/^/, ''))?.[1];\n return frontmatter !== undefined && BLOG_KEY.test(frontmatter);\n}\n\n/** `title` without markdown-tags syntax (`((tag|draft))`), its spacing tidied. */\nexport function stripTagSyntax(text: string): string {\n return text.replace(TAG_SYNTAX, ' ').replace(/\\s+/g, ' ').trim();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {};\n}\n\nfunction toText(value: unknown): string {\n return typeof value === 'string' ? value.trim() : typeof value === 'number' ? String(value) : '';\n}\n\nfunction toBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\n/** A list, or a single string split on commas. */\nexport function toStringList(value: unknown): string[] {\n const values = Array.isArray(value) ? value : typeof value === 'string' ? value.split(',') : [];\n return values.map(toText).filter(Boolean);\n}\n\n/**\n * YAML gives a bare `2026-10-01` as a string, while gray-matter (VitePress's\n * parser) gives a `Date`. Both are accepted, and a date without a time is UTC.\n */\nexport function toDate(value: unknown): Date | null {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value !== 'string' && typeof value !== 'number') return null;\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\nfunction toAuthor(value: unknown, options: BlogOptions): BlogAuthor | null {\n if (typeof value === 'string') {\n const name = value.trim();\n if (!name) return null;\n const known = options.authors && Object.hasOwn(options.authors, name) ? options.authors[name] : undefined;\n return known ? toAuthor(known, {}) : { name };\n }\n const record = toRecord(value);\n const name = toText(record.name);\n if (!name) return null;\n const author: BlogAuthor = { name };\n if (toText(record.url)) author.url = toText(record.url);\n if (toText(record.email)) author.email = toText(record.email);\n return author;\n}\n\nfunction toAuthors(value: unknown, options: BlogOptions): BlogAuthor[] {\n const values = Array.isArray(value) ? value : value === undefined ? [] : [value];\n const authors = values.map((item) => toAuthor(item, options)).filter((author) => author !== null);\n if (authors.length === 0 && options.defaultAuthor) {\n const fallback = toAuthor(options.defaultAuthor, options);\n if (fallback) authors.push(fallback);\n }\n return authors;\n}\n\n/**\n * A CSS `object-position` value, kept only if it is made of position words,\n * percentages and lengths (`top`, `left 20%`, `30% 20%`, `10px 0`), so\n * frontmatter can't write any other CSS into the page.\n */\nexport function toImagePosition(value: string): string {\n const parts = value.trim().toLowerCase().split(/\\s+/).filter(Boolean);\n const valid = /^(?:top|bottom|left|right|center|-?\\d+(?:\\.\\d+)?(?:%|px|rem|em)?)$/;\n return parts.length > 0 && parts.length <= 4 && parts.every((part) => valid.test(part)) ? parts.join(' ') : '';\n}\n\nfunction toScope(value: unknown): BlogScope {\n return value === 'site' || value === 'global' ? 'site' : 'section';\n}\n\n/** Blanks out fenced code, so headings and prose inside it are ignored. */\nfunction withoutCode(content: string): string {\n return content.replace(FENCED_CODE, '');\n}\n\n/** The text of the first level-1 heading, or `''`. */\nexport function readHeading(content: string): string {\n return (\n HEADING_1.exec(withoutCode(content))?.[1]\n ?.replace(/[ \\t]*\\{#[^}]*\\}$/, '')\n .trim() ?? ''\n );\n}\n\n/** Markdown reduced to plain text: links to their text, emphasis and code marks removed, HTML dropped. */\nexport function toPlainText(markdown: string): string {\n return markdown\n .replace(/!\\[[^\\]]*\\]\\([^)]*\\)/g, '')\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/<[^>]+>/g, '')\n .replace(/[*_`~]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** The first paragraph of prose, as plain text, for a post without a summary. */\nexport function readExcerpt(content: string): string {\n for (const block of withoutCode(content).split(/\\r?\\n[ \\t]*\\r?\\n/)) {\n const text = block.trim();\n // Skip headings, HTML, quotes, lists, tables, containers, includes and images.\n if (!text || /^(?:#|<|>|\\||:::|\\[\\[|!\\[|[-*+] |\\d+[.)] |\\{)/.test(text)) continue;\n const plain = toPlainText(text);\n if (!plain) continue;\n return plain.length > MAX_SUMMARY_LENGTH ? `${plain.slice(0, MAX_SUMMARY_LENGTH - 1).trimEnd()}…` : plain;\n }\n return '';\n}\n\n/** Minutes to read the page's prose, rounded up, at least 1. */\nexport function estimateReadingMinutes(content: string, wordsPerMinute: number): number {\n if (wordsPerMinute <= 0) return 0;\n const words = toPlainText(withoutCode(content)).split(' ').filter(Boolean).length;\n return Math.max(1, Math.ceil(words / wordsPerMinute));\n}\n\n/**\n * Reads a post from its frontmatter. `blog: true` is a post with every\n * default; `blog: { ... }` sets its details. Title, description, date,\n * author, image and tags may also be written at the top level, as other\n * blog tools expect; a value under `blog` wins.\n */\nexport function resolvePostData(\n data: Record<string, unknown>,\n content: string,\n fallbackTitle: string,\n options: BlogOptions = {},\n): BlogPostData | null {\n const value = data[POST_FRONTMATTER_KEY];\n if (!value) return null;\n const post = toRecord(value);\n const pick = (key: string) => post[key] ?? data[key];\n const stripTags = options.stripTags ?? true;\n const clean = (text: string) => (stripTags ? stripTagSyntax(text) : text);\n\n const image = toRecord(pick('image'));\n return {\n title: clean(toText(post.title) || toText(data.title) || readHeading(content)) || fallbackTitle,\n summary: clean(toText(post.summary) || toText(data.description) || readExcerpt(content)),\n date: toDate(pick('date')),\n updated: toDate(pick('updated')),\n image: toText(image.src) || toText(pick('image')),\n imageAlt: toText(image.alt) || toText(post.imageAlt) || toText(data.imageAlt),\n imageFit: (toText(image.fit) || toText(post.imageFit)) === 'contain' ? 'contain' : 'cover',\n imagePosition: toImagePosition(toText(image.position) || toText(post.imagePosition)),\n authors: toAuthors(post.author ?? post.authors ?? data.author ?? data.authors, options),\n tags: toStringList(pick('tags')),\n category: toText(pick('category')),\n scope: toScope(post.scope),\n sticky: post.sticky === true,\n draft: post.draft === true,\n byline: toBoolean(post.byline),\n cover: toBoolean(post.cover),\n postNav: toBoolean(post.postNav),\n readingMinutes: estimateReadingMinutes(content, options.wordsPerMinute ?? 200),\n };\n}\n\nfunction toLayout(value: unknown): BlogLayout {\n return value === 'grid' || value === 'compact' ? value : 'list';\n}\n\nfunction defaultFeedPath(relativePath: string): string {\n const name = path.posix.basename(relativePath, '.md');\n return name === 'index' ? 'feed.xml' : `${name}-feed.xml`;\n}\n\nfunction toFeed(value: unknown, relativePath: string): BlogFeedConfig | null {\n if (value === false) return null;\n const feed = toRecord(value);\n const config: BlogFeedConfig = { path: toText(feed.path) || defaultFeedPath(relativePath) };\n if (toText(feed.title)) config.title = toText(feed.title);\n if (toText(feed.description)) config.description = toText(feed.description);\n if (typeof feed.limit === 'number' && feed.limit > 0) config.limit = Math.floor(feed.limit);\n return config;\n}\n\n/**\n * Folder entries are relative to the source directory (`products`). One that\n * starts with `./` or `../` is relative to the folder of the page that lists it.\n */\nexport function resolveFolders(folders: string[], pageRelativePath: string): string[] {\n return folders.map((folder) => {\n const resolved = /^\\.\\.?(\\/|$)/.test(folder)\n ? path.posix.join(path.posix.dirname(pageRelativePath), folder)\n : path.posix.normalize(folder);\n return resolved.replace(/^\\/+|\\/+$/g, '').replace(/^\\.$/, '');\n });\n}\n\n/**\n * Reads the `blog-index` frontmatter value: `true` for the defaults, or an\n * object of settings. Returns `null` when the page is not a blog page.\n * A section blog covers its own folder unless `folders` says otherwise.\n * `pageSize`, `stickyLimit` and `rssLink` fall back to the plugin options.\n */\n/** A whole number of at least 0, or `fallback`. */\nfunction toCount(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;\n}\n\nexport function resolveIndexConfig(\n data: Record<string, unknown>,\n relativePath: string,\n defaults: Pick<BlogOptions, 'pageSize' | 'stickyLimit' | 'rssLink'> = {},\n): BlogIndexConfig | null {\n const value = data[INDEX_FRONTMATTER_KEY];\n if (!value) return null;\n const options = toRecord(value);\n const scope = toScope(options.scope);\n const folders = toStringList(options.folders);\n const ownFolder = path.posix.dirname(relativePath).replace(/^\\.$/, '');\n return {\n scope,\n folders: folders.length ? resolveFolders(folders, relativePath) : scope === 'section' ? [ownFolder] : [],\n tags: toStringList(options.tags),\n limit: typeof options.limit === 'number' && options.limit > 0 ? Math.floor(options.limit) : 0,\n layout: toLayout(options.layout),\n groupByYear: options.groupBy === 'year',\n showTags: options.showTags !== false,\n tagList: options.tagList !== false,\n pageSize: toCount(options.pageSize, toCount(defaults.pageSize, 10)),\n stickyLimit: toCount(options.stickyLimit, toCount(defaults.stickyLimit, 0)),\n rssLink: typeof options.rssLink === 'boolean' ? options.rssLink : (defaults.rssLink ?? true),\n feed: toFeed(options.feed, relativePath),\n };\n}\n","import { createHash } from 'node:crypto';\nimport { readFileSync, statSync } from 'node:fs';\nimport path from 'node:path';\nimport type { IndexPage, PostPage } from './collect.js';\nimport { isExternalUrl, joinUrl, toUrlPath } from './util.js';\n\n/** Where the site is published, for the absolute links feeds and social cards need. */\nexport interface SiteLocation {\n /** `https://example.com`, without the base. */\n siteUrl: string;\n /** VitePress's `base`: `/` or `/docs/`. */\n base: string;\n cleanUrls: boolean;\n /** Absolute path of VitePress's public folder, for `/x.png` images. */\n publicDir: string;\n /** Folder under the output that relative post images are copied to. */\n imagesDir: string;\n}\n\n/** An absolute URL for a path on the site: `/news/` becomes `https://example.com/docs/news/`. */\nexport function toAbsoluteUrl(urlPath: string, site: SiteLocation): string {\n return joinUrl(site.siteUrl, joinUrl(site.base, urlPath));\n}\n\nexport function toPageUrl(routePath: string, site: SiteLocation): string {\n return toAbsoluteUrl(toUrlPath(routePath, site.cleanUrls), site);\n}\n\n/** A blog page's feed file, relative to the output folder: `news/feed.xml`. */\nexport function toFeedPath(index: IndexPage): string {\n return path.posix.join(path.posix.dirname(index.routePath), index.index.feed!.path);\n}\n\nexport interface ResolvedImage {\n /** Absolute URL to publish. */\n url: string;\n /** For a file on disk: where it is, and where in the output it is copied to (relative). */\n file?: string;\n outputPath?: string;\n bytes?: number;\n type?: string;\n}\n\nconst IMAGE_TYPES: Record<string, string> = {\n '.avif': 'image/avif',\n '.gif': 'image/gif',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n};\n\nfunction toImageType(file: string): string | undefined {\n return IMAGE_TYPES[path.extname(file).toLowerCase()];\n}\n\n/**\n * A post's image as an absolute URL. Vite gives bundled images a hashed name\n * that isn't known until the build is done, so a relative image is instead\n * copied to `imagesDir` under a name made from its content: the URL is known\n * when the page's `<head>` is written, and changes when the image does.\n */\nexport function resolvePostImage(post: PostPage, site: SiteLocation): ResolvedImage | null {\n const { image } = post.post;\n if (!image) return null;\n if (isExternalUrl(image)) return { url: image.startsWith('//') ? `https:${image}` : image, type: toImageType(image) };\n if (/^[@~]/.test(image)) return null;\n\n const file = image.startsWith('/')\n ? path.join(site.publicDir, decodeURI(image))\n : path.resolve(path.dirname(post.absolutePath), decodeURI(image));\n let bytes: number;\n try {\n bytes = statSync(file).size;\n } catch {\n return null;\n }\n if (image.startsWith('/')) return { url: toAbsoluteUrl(image, site), file, bytes, type: toImageType(file) };\n\n const hash = createHash('sha256').update(readFileSync(file)).digest('hex').slice(0, 8);\n const extension = path.extname(file);\n const outputPath = path.posix.join(site.imagesDir, `${path.basename(file, extension)}-${hash}${extension}`);\n return { url: toAbsoluteUrl(outputPath, site), file, outputPath, bytes, type: toImageType(file) };\n}\n\nfunction escapeXml(value: string): string {\n return value.replace(/[&<>\"']/g, (character) => `&#${character.charCodeAt(0)};`);\n}\n\nfunction element(name: string, value: string | undefined, attributes = ''): string {\n return value ? `<${name}${attributes}>${escapeXml(value)}</${name}>` : '';\n}\n\nexport interface FeedInput {\n index: IndexPage;\n posts: PostPage[];\n site: SiteLocation;\n /** The site's language, `en-US`. */\n language?: string;\n /** Used when neither the feed nor the blog page has a description. */\n siteDescription?: string;\n}\n\n/** Renders an RSS 2.0 feed for a blog page, newest posts first. */\nexport function renderFeed({ index, posts, site, language, siteDescription }: FeedInput): string {\n const feed = index.index.feed!;\n const pageUrl = toPageUrl(index.routePath, site);\n const newest = posts.map((post) => post.post.updated ?? post.post.date).find((date) => date !== null);\n\n const items = posts.map((post) => {\n const url = toPageUrl(post.routePath, site);\n const image = resolvePostImage(post, site);\n const lines = [\n element('title', post.post.title),\n element('link', url),\n element('guid', url, ' isPermaLink=\"true\"'),\n post.post.date ? element('pubDate', post.post.date.toUTCString()) : '',\n element('description', post.post.summary),\n ...post.post.authors.map((author) =>\n element('dc:creator', author.email ? `${author.email} (${author.name})` : author.name),\n ),\n ...[post.post.category, ...post.post.tags].filter(Boolean).map((category) => element('category', category)),\n image\n ? `<media:content url=\"${escapeXml(image.url)}\" medium=\"image\"${image.type ? ` type=\"${image.type}\"` : ''} />`\n : '',\n image?.bytes !== undefined && image.type\n ? `<enclosure url=\"${escapeXml(image.url)}\" length=\"${image.bytes}\" type=\"${image.type}\" />`\n : '',\n ];\n return ` <item>\\n${lines\n .filter(Boolean)\n .map((line) => ` ${line}`)\n .join('\\n')}\\n </item>`;\n });\n\n const channel = [\n element('title', feed.title ?? index.title),\n element('link', pageUrl),\n element('description', feed.description ?? (index.description || siteDescription || index.title)),\n `<atom:link href=\"${escapeXml(toAbsoluteUrl(toFeedPath(index), site))}\" rel=\"self\" type=\"application/rss+xml\" />`,\n element('language', language),\n newest ? element('lastBuildDate', newest.toUTCString()) : '',\n element('generator', '@binarynoir/vitepress-blog'),\n ];\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:media=\"http://search.yahoo.com/mrss/\">',\n ' <channel>',\n ...channel.filter(Boolean).map((line) => ` ${line}`),\n ...items,\n ' </channel>',\n '</rss>',\n '',\n ].join('\\n');\n}\n","const HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/** Escapes the characters that are special in HTML text and attribute values. */\nexport function escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (character) => HTML_ESCAPES[character]);\n}\n\n/**\n * Escapes text so it stays literal in Markdown and in VitePress's Vue\n * templates. Text with braces goes in a `v-pre` span, since Vue would otherwise\n * evaluate `{{ }}` even when the Markdown escaped it.\n */\nexport function escapeMarkdown(text: string): string {\n const escaped = text\n .replace(/[\\\\`*_[\\]#|~]/g, '\\\\$&')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;\n}\n\n/** HTML text that Vue leaves alone: braces go in a `v-pre` span. */\nexport function escapeTemplateText(text: string): string {\n const escaped = escapeHtml(text);\n return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;\n}\n\n/** A URL with a scheme (`https:`, `mailto:`) or protocol-relative (`//cdn`). */\nexport function isExternalUrl(url: string): boolean {\n return /^(?:[a-z][a-z\\d+.-]*:|\\/\\/)/i.test(url);\n}\n\n/** Encodes a URL for a Markdown link destination: spaces and parentheses would end it early. */\nexport function toMarkdownUrl(url: string): string {\n return encodeURI(url).replace(/[()]/g, (character) => (character === '(' ? '%28' : '%29'));\n}\n\n/** A Markdown link to a page by its served path, which VitePress then checks and gives the site's base. */\nexport function toPageLink(text: string, routePath: string): string {\n return `[${escapeMarkdown(text)}](${toMarkdownUrl(`/${routePath}`)})`;\n}\n\n/** A page's URL path, without the site's base: `news/index.md` is `/news/`, `news/launch.md` is `/news/launch`. */\nexport function toUrlPath(routePath: string, cleanUrls: boolean): string {\n return `/${routePath.replace(/(^|\\/)index\\.md$/, '$1').replace(/\\.md$/, cleanUrls ? '' : '.html')}`;\n}\n\n/** `base` and `urlPath` joined with exactly one slash between them. */\nexport function joinUrl(base: string, urlPath: string): string {\n return `${base.replace(/\\/+$/, '')}/${urlPath.replace(/^\\/+/, '')}`;\n}\n\n/** `2026-10-01` for a `datetime` attribute. */\nexport function toIsoDate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\nexport function formatDate(date: Date, locale: string, format: Intl.DateTimeFormatOptions): string {\n return new Intl.DateTimeFormat(locale, { timeZone: 'UTC', ...format }).format(date);\n}\n","import type { MarkdownIt, StateCore } from 'markdown-it';\nimport path from 'node:path';\nimport { findHomeBlog, isIndex, isPost, listBlogPosts, pinStickyPosts } from './collect.js';\nimport {\n BLOG_POSTS_PLACEHOLDER,\n createRenderContext,\n renderPostHeader,\n renderPostList,\n renderPostNav,\n} from './render.js';\nimport { loadBlogPages, type BlogPage, type SourceInfo } from './scan.js';\nimport type { BlogOptions } from './types.js';\nimport { applyWidgets, hasWidget, widgetPages } from './widget.js';\n\nconst FENCE = /^[ \\t]*(`{3,}|~{3,})/;\nconst HEADING_1 = /^ {0,3}#[ \\t]+\\S/;\n\n/**\n * VitePress's resolved rewrites (`README.md` served as `index.md`, say) and\n * the pages it builds, so links point where pages are served and pages kept\n * out by `srcExclude` aren't listed. VitePress keeps its config on this\n * global while it runs; `createContentLoader` reads it the same way.\n */\nexport function readSourceInfo(): SourceInfo {\n return (globalThis as { VITEPRESS_CONFIG?: SourceInfo }).VITEPRESS_CONFIG ?? {};\n}\n\n/** Puts `insert` on its own after the first level-1 heading outside code, or at the top if there is none. */\nexport function insertAfterTitle(source: string, insert: string): string {\n const lines = source.split('\\n');\n let fence = '';\n for (const [index, line] of lines.entries()) {\n const marker = FENCE.exec(line)?.[1];\n if (marker && (!fence || marker[0] === fence[0])) {\n fence = fence ? '' : marker;\n continue;\n }\n if (!fence && HEADING_1.test(line)) {\n return [...lines.slice(0, index + 1), '', insert, '', ...lines.slice(index + 1)].join('\\n');\n }\n }\n return `${insert}\\n\\n${source}`;\n}\n\n/** Whether the build writes feeds: they need a site URL, from the options or VitePress's `sitemap.hostname`. */\nfunction hasFeeds(options: BlogOptions): boolean {\n return !!(options.siteUrl ?? readSourceInfo().userConfig?.sitemap?.hostname);\n}\n\nfunction withPostList(source: string, page: BlogPage, pages: BlogPage[], options: BlogOptions): string {\n if (!isIndex(page)) return source;\n const posts = listBlogPosts(pages, page, { drafts: options.drafts ?? false, stickyFirst: true, applyLimit: true });\n const { pinned, rest } = pinStickyPosts(posts, page.index.stickyLimit);\n const context = createRenderContext(options, path.dirname(page.absolutePath), hasFeeds(options));\n const generated = renderPostList(pinned, rest, page, context);\n return source.includes(BLOG_POSTS_PLACEHOLDER)\n ? source.replace(BLOG_POSTS_PLACEHOLDER, () => `\\n${generated}\\n`)\n : `${source.replace(/\\s*$/, '')}\\n\\n${generated}`;\n}\n\nfunction withPostDecorations(source: string, page: BlogPage, pages: BlogPage[], options: BlogOptions): string {\n if (!isPost(page)) return source;\n const context = createRenderContext(options, path.dirname(page.absolutePath));\n const drafts = options.drafts ?? false;\n // The post's own blog: its keywords link there, and its newer / older links stay inside it.\n const blog = findHomeBlog(pages, page, drafts);\n const header = renderPostHeader(\n page,\n context,\n { byline: page.post.byline ?? options.byline ?? true, cover: page.post.cover ?? options.cover ?? false },\n blog?.routePath ?? null,\n );\n let result = header ? insertAfterTitle(source, header) : source;\n\n if (page.post.postNav ?? options.postNav ?? true) {\n if (blog) {\n const posts = listBlogPosts(pages, blog, { drafts, stickyFirst: false, applyLimit: false });\n result = `${result.replace(/\\s*$/, '')}\\n\\n${renderPostNav(page, posts, blog, context)}\\n`;\n }\n }\n return result;\n}\n\nconst warned = new Set<string>();\n\nfunction applyBlog({ src, env }: StateCore, options: BlogOptions): string {\n const frontmatter = env?.frontmatter as Record<string, unknown> | undefined;\n const isBlogPage = !!(frontmatter?.blog || frontmatter?.['blog-index']);\n const widgets = hasWidget(src);\n if (!isBlogPage && !widgets) return src;\n if (typeof env.path !== 'string' || typeof env.relativePath !== 'string') return src;\n\n // `env.path` is the page's served path (after rewrites), absolute, and\n // `env.relativePath` that same path from the source directory.\n const sourceDirectory = path.resolve(env.path, ...env.relativePath.split('/').map(() => '..'));\n const realPath = typeof env.realPath === 'string' ? env.realPath : env.path;\n const relativePath = path.relative(sourceDirectory, realPath).split(path.sep).join('/');\n\n const pages = loadBlogPages(sourceDirectory, options, readSourceInfo());\n let result = src;\n if (widgets) {\n // Remembered so `vitepress dev` can refresh this page when a post changes.\n widgetPages.add(realPath);\n result = applyWidgets(result, {\n pages,\n pageRelativePath: relativePath,\n drafts: options.drafts ?? false,\n context: createRenderContext(options, path.dirname(realPath), hasFeeds(options)),\n warn: (message) => {\n const key = `${relativePath}: ${message}`;\n if (warned.has(key)) return;\n warned.add(key);\n console.warn(`[vitepress-blog] ${relativePath}: ${message}`);\n },\n });\n }\n\n const page = pages.find((candidate) => candidate.relativePath === relativePath);\n if (!page) return result;\n return page.index ? withPostList(result, page, pages, options) : withPostDecorations(result, page, pages, options);\n}\n\n/**\n * A markdown-it plugin that turns pages into a blog.\n *\n * ```ts\n * // .vitepress/config.mts\n * import { blogMarkdown } from '@binarynoir/vitepress-blog';\n *\n * export default defineConfig({\n * markdown: {\n * config(md) {\n * md.use(blogMarkdown, { locale: 'en-US' });\n * },\n * },\n * });\n * ```\n *\n * A page with `blog-index` in its frontmatter gets a list of posts; a page\n * with `blog` gets a byline and links to the posts around it; and any page\n * can show a blog's latest posts with a `::: blog-posts` block. Runs on the raw\n * source before markdown-it's `normalize` rule, so the Markdown it writes is\n * parsed like the rest of the page and its links are checked and given the\n * site's base by VitePress. It needs VitePress's `env.path`,\n * `env.relativePath` and `env.frontmatter`, so it does nothing under plain\n * markdown-it.\n */\nexport function blogMarkdown(md: MarkdownIt, options: BlogOptions = {}): void {\n md.core.ruler.before('normalize', 'vitepress_blog', (state: StateCore) => {\n state.src = applyBlog(state, options);\n });\n}\n","import path from 'node:path';\nimport type { IndexPage, PostPage } from './collect.js';\nimport { toFeedPath } from './feed.js';\nimport { DEFAULT_LABELS, type BlogAuthor, type BlogLabels, type BlogOptions } from './types.js';\nimport {\n escapeHtml,\n escapeMarkdown,\n escapeTemplateText,\n formatDate,\n isExternalUrl,\n toIsoDate,\n toMarkdownUrl,\n toPageLink,\n} from './util.js';\n\n/** Marker a blog page can use to say where its list of posts goes. Without it the list is appended. */\nexport const BLOG_POSTS_PLACEHOLDER = '<!-- blog-posts -->';\n\nexport interface RenderContext {\n locale: string;\n dateFormat: Intl.DateTimeFormatOptions;\n labels: BlogLabels;\n /** Absolute folder of the source file being rendered: relative image paths are written from here. */\n pageDirectory: string;\n /** Whether feeds are written (the site has a site URL), so RSS links have something to point at. */\n feeds: boolean;\n}\n\nexport function createRenderContext(\n options: BlogOptions,\n pageDirectory: string,\n feeds: boolean = !!options.siteUrl,\n): RenderContext {\n return {\n locale: options.locale ?? 'en-US',\n dateFormat: options.dateFormat ?? { dateStyle: 'long' },\n labels: { ...DEFAULT_LABELS, ...options.labels },\n pageDirectory,\n feeds,\n };\n}\n\nconst RSS_ICON =\n '<svg class=\"vp-blog-rss__icon\" viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" aria-hidden=\"true\">' +\n '<circle cx=\"6\" cy=\"18\" r=\"2.5\" fill=\"currentColor\"></circle>' +\n '<path fill=\"currentColor\" d=\"M3.5 10.5v3a7 7 0 0 1 7 7h3a10 10 0 0 0-10-10z\"></path>' +\n '<path fill=\"currentColor\" d=\"M3.5 3.5v3a14 14 0 0 1 14 14h3a17 17 0 0 0-17-17z\"></path>' +\n '</svg>';\n\n/**\n * A \"Subscribe\" link with the RSS icon to a blog's feed, or `''` when the\n * blog has no feed, hides the link, or the site writes no feeds. A Markdown\n * link, so VitePress gives it the site's base.\n */\nexport function renderRssLink(index: IndexPage, context: RenderContext, show: boolean = index.index.rssLink): string {\n if (!show || !context.feeds || !index.index.feed) return '';\n const title = fill(context.labels.rssFeed, { blog: index.title }).replace(/\"/g, '"');\n const url = toMarkdownUrl(`/${toFeedPath(index)}`);\n return `<span class=\"vp-blog-rss\">[${RSS_ICON} ${escapeMarkdown(context.labels.subscribe)}](${url} \"${title}\")</span>`;\n}\n\nfunction fill(template: string, values: Record<string, string>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) => values[key] ?? match);\n}\n\n/**\n * A post's image as the rendered page should write it. A path relative to the\n * post is rewritten relative to `pageDirectory`, so Vite still finds and\n * bundles it; public paths (`/x.png`), aliases and URLs are kept as written.\n */\nexport function toImageSource(post: PostPage, pageDirectory: string): string {\n const { image } = post.post;\n if (!image || isExternalUrl(image) || /^[/@~]/.test(image)) return image;\n const absolute = path.resolve(path.dirname(post.absolutePath), image);\n const relative = path.relative(pageDirectory, absolute).split(path.sep).join('/');\n return relative.startsWith('.') ? relative : `./${relative}`;\n}\n\nfunction renderTime(date: Date, context: RenderContext): string {\n return `<time datetime=\"${toIsoDate(date)}\">${escapeTemplateText(formatDate(date, context.locale, context.dateFormat))}</time>`;\n}\n\nfunction renderAuthor(author: BlogAuthor): string {\n return author.url ? `[${escapeMarkdown(author.name)}](${toMarkdownUrl(author.url)})` : escapeMarkdown(author.name);\n}\n\nfunction renderAuthors(authors: BlogAuthor[], context: RenderContext): string {\n if (authors.length === 0) return '';\n // ListFormat joins plain strings, so join placeholders and swap the rendered names in after.\n const placeholders = authors.map((_, index) => `\\u0000${index}\\u0000`);\n const joined = new Intl.ListFormat(context.locale, { type: 'conjunction' }).format(placeholders);\n const names = joined.replace(/\\u0000(\\d+)\\u0000/g, (_, index: string) => renderAuthor(authors[Number(index)]));\n return `${escapeMarkdown(context.labels.by)} ${names}`;\n}\n\nfunction renderBadge(kind: 'draft' | 'sticky', context: RenderContext): string {\n return `<span class=\"vp-blog-badge vp-blog-badge--${kind}\">${escapeTemplateText(context.labels[kind])}</span>`;\n}\n\n/**\n * The URL that filters a blog page by a keyword, which VitePress gives the\n * base: `/blog/?tag=release`, or `/news/archive.md?tag=release` for a page not\n * named `index.md`. An `index.md` page is linked by its folder, because\n * VitePress reads anything after `index.md` in a link as a heading anchor.\n */\nexport function toTagUrl(blogRoutePath: string, tag: string): string {\n const value = encodeURIComponent(tag.toLowerCase()).replace(/[()]/g, (c) => (c === '(' ? '%28' : '%29'));\n const page = blogRoutePath.replace(/(^|\\/)index\\.md$/, '$1');\n return `${toMarkdownUrl(`/${page}`)}?tag=${value}`;\n}\n\n/** Keyword chips: links that filter `blogRoutePath` by the keyword, or plain text without a blog. */\nfunction renderTags(tags: string[], blogRoutePath: string | null): string {\n return tags\n .map((tag) =>\n blogRoutePath\n ? `[${escapeMarkdown(tag)}](${toTagUrl(blogRoutePath, tag)})`\n : `<span class=\"vp-blog-tag\">${escapeTemplateText(tag)}</span>`,\n )\n .join(' ');\n}\n\n/** What the client reads to filter a post by keyword: its tags, lowercased, joined with `|`. */\nfunction toTagsAttribute(post: PostPage): string {\n return escapeHtml(post.post.tags.map((tag) => tag.toLowerCase()).join('|'));\n}\n\ninterface MetaParts {\n authors: boolean;\n updated: boolean;\n badges: boolean;\n}\n\n/** One line of post details, joined with middle dots: badges, date, updated, reading time, authors. */\nfunction renderMeta(post: PostPage, context: RenderContext, include: MetaParts): string {\n const { date, updated, readingMinutes, authors, draft, sticky } = post.post;\n const parts: string[] = [];\n if (include.badges) {\n const badges = [draft && renderBadge('draft', context), sticky && renderBadge('sticky', context)];\n const shown = badges.filter(Boolean).join(' ');\n if (shown) parts.push(shown);\n }\n if (date) parts.push(renderTime(date, context));\n if (include.updated && updated && (!date || updated.getTime() !== date.getTime())) {\n parts.push(`${escapeMarkdown(context.labels.updated)} ${renderTime(updated, context)}`);\n }\n if (readingMinutes) parts.push(escapeMarkdown(fill(context.labels.readingTime, { n: String(readingMinutes) })));\n if (include.authors) {\n const names = renderAuthors(authors, context);\n if (names) parts.push(names);\n }\n return parts.join(' · ');\n}\n\n/** A `<div>` holding Markdown: blank lines around the content let markdown-it parse it. */\nfunction block(className: string, content: string): string[] {\n return content ? [`<div class=\"${className}\">`, '', content, '', '</div>'] : [];\n}\n\n/** The image's `object-position`, as a CSS variable on the card, when the post sets one. */\nfunction imageStyle(post: PostPage): string {\n return post.post.imagePosition ? ` style=\"--vp-blog-image-position: ${post.post.imagePosition}\"` : '';\n}\n\nfunction renderCard(post: PostPage, context: RenderContext, index: IndexPage): string[] {\n const link = `/${post.routePath}`;\n const image = toImageSource(post, context.pageDirectory);\n const modifiers = [\n post.post.sticky && 'sticky',\n post.post.draft && 'draft',\n image && 'with-image',\n image && post.post.imageFit === 'contain' && 'contain',\n ]\n .filter(Boolean)\n .map((modifier) => ` vp-blog-card--${modifier}`)\n .join('');\n const alt = escapeMarkdown(post.post.imageAlt);\n return [\n `<article class=\"vp-blog-card${modifiers}\"${imageStyle(post)} data-vp-blog-item data-tags=\"${toTagsAttribute(post)}\">`,\n ...(image ? block('vp-blog-card__image', `[})](${toMarkdownUrl(link)})`) : []),\n '<div class=\"vp-blog-card__body\">',\n ...block('vp-blog-card__title', toPageLink(post.post.title, post.routePath)),\n ...block('vp-blog-card__meta', renderMeta(post, context, { authors: true, updated: false, badges: true })),\n ...block('vp-blog-card__summary', escapeMarkdown(post.post.summary)),\n ...(index.index.showTags ? block('vp-blog-card__tags', renderTags(post.post.tags, index.routePath)) : []),\n '</div>',\n '</article>',\n ];\n}\n\nfunction renderCompactItem(post: PostPage, context: RenderContext): string {\n const date = post.post.date ? `${renderTime(post.post.date, context)} ` : '';\n const badges = [post.post.draft && renderBadge('draft', context), post.post.sticky && renderBadge('sticky', context)]\n .filter(Boolean)\n .join(' ');\n // List items can't carry attributes in Markdown, so a hidden marker inside each one does.\n const marker = `<span hidden data-vp-blog-item data-tags=\"${toTagsAttribute(post)}\"></span>`;\n return `- ${marker}${date}${toPageLink(post.post.title, post.routePath)}${badges ? ` ${badges}` : ''}`;\n}\n\n/** Posts in the blog page's layout: cards, or a compact list. */\nexport function renderGroup(posts: PostPage[], index: IndexPage, context: RenderContext): string[] {\n const { layout } = index.index;\n if (layout === 'compact') {\n return [\n '<div class=\"vp-blog-list vp-blog-list--compact\">',\n '',\n ...posts.map((post) => renderCompactItem(post, context)),\n '',\n '</div>',\n ];\n }\n return [\n `<div class=\"vp-blog-list vp-blog-list--${layout}\">`,\n ...posts.flatMap((post) => renderCard(post, context, index)),\n '</div>',\n ];\n}\n\n/** The posts under year headings, each year in a section the client can hide when none of its posts show. */\nfunction renderYears(posts: PostPage[], index: IndexPage, context: RenderContext): string[] {\n const years = new Map<string, PostPage[]>();\n for (const post of posts) {\n const year = post.post.date ? String(post.post.date.getUTCFullYear()) : context.labels.undated;\n years.set(year, [...(years.get(year) ?? []), post]);\n }\n const lines: string[] = [];\n for (const [year, group] of years) {\n lines.push(\n '<section class=\"vp-blog-year\">',\n '',\n `## ${escapeMarkdown(year)}`,\n '',\n ...renderGroup(group, index, context),\n '',\n '</section>',\n );\n }\n return lines;\n}\n\n/** Every keyword the listed posts use, with how many use it, alphabetically. */\nexport function collectTags(posts: PostPage[]): { tag: string; count: number }[] {\n const tags = new Map<string, { tag: string; count: number }>();\n for (const post of posts) {\n for (const tag of new Set(post.post.tags)) {\n const entry = tags.get(tag.toLowerCase()) ?? { tag, count: 0 };\n entry.count++;\n tags.set(tag.toLowerCase(), entry);\n }\n }\n return [...tags.values()].sort((a, b) => a.tag.localeCompare(b.tag, undefined, { sensitivity: 'base' }));\n}\n\nfunction renderTagList(posts: PostPage[], index: IndexPage, context: RenderContext): string[] {\n const tags = collectTags(posts);\n if (!index.index.tagList || tags.length === 0) return [];\n const links = tags.map(\n ({ tag, count }) =>\n `[${escapeMarkdown(tag)} <span class=\"vp-blog-tags__count\">${count}</span>](${toTagUrl(index.routePath, tag)})`,\n );\n const label = `<span class=\"vp-blog-tags__label\">${escapeTemplateText(context.labels.tags)}</span>`;\n return [\n `<nav class=\"vp-blog-tags\" aria-label=\"${escapeHtml(context.labels.tags)}\">`,\n '',\n `${label} ${links.join(' ')}`,\n '',\n '</nav>',\n ];\n}\n\n/** Labels the client needs, as data attributes, so they are translated in one place. */\nfunction renderClientAttributes(index: IndexPage, context: RenderContext): string {\n const { labels } = context;\n const attributes: Record<string, string> = {\n 'data-page-size': String(index.index.pageSize),\n 'data-label-tagged': labels.tagged,\n 'data-label-show-all': labels.showAll,\n 'data-label-no-tagged': labels.noTagged,\n 'data-label-previous': labels.previous,\n 'data-label-next': labels.next,\n 'data-label-pages': labels.pages,\n };\n return Object.entries(attributes)\n .map(([name, value]) => `${name}=\"${escapeHtml(value)}\"`)\n .join(' ');\n}\n\n/**\n * Renders a blog page's posts as Markdown: the keyword list, the pinned\n * sticky posts, then the rest (under year headings if the page asks for it).\n * Every post is in the HTML; `useBlog()` in the theme then filters them by\n * keyword and splits them into pages, using the empty slots written here.\n */\nexport function renderPostList(pinned: PostPage[], rest: PostPage[], index: IndexPage, context: RenderContext): string {\n const all = [...pinned, ...rest];\n if (all.length === 0) return `<p class=\"vp-blog-empty\">${escapeTemplateText(context.labels.noPosts)}</p>\\n`;\n const lines = [\n `<div class=\"vp-blog\" data-vp-blog ${renderClientAttributes(index, context)}>`,\n ...block('vp-blog-subscribe', renderRssLink(index, context)),\n ...renderTagList(all, index, context),\n '<div class=\"vp-blog-filter\" data-vp-blog-filter hidden></div>',\n ];\n if (pinned.length)\n lines.push(\n '<section class=\"vp-blog-sticky\" data-vp-blog-sticky>',\n ...renderGroup(pinned, index, context),\n '</section>',\n );\n if (rest.length)\n lines.push(...(index.index.groupByYear ? renderYears(rest, index, context) : renderGroup(rest, index, context)));\n lines.push(`<nav class=\"vp-blog-pagination\" data-vp-blog-pagination hidden></nav>`, '</div>');\n return `${lines.join('\\n')}\\n`;\n}\n\nexport interface PostDecorations {\n byline: boolean;\n cover: boolean;\n}\n\n/**\n * The author, date and tags block under a post's title, and its cover image.\n * Its keywords link to `blogRoutePath` (the post's own blog) filtered by them.\n */\nexport function renderPostHeader(\n post: PostPage,\n context: RenderContext,\n show: PostDecorations,\n blogRoutePath: string | null = null,\n): string {\n const lines: string[] = [];\n if (show.byline) {\n const meta = renderMeta(post, context, { authors: true, updated: true, badges: true });\n const tags = post.post.tags.length\n ? `<span class=\"vp-blog-byline__tags\">${renderTags(post.post.tags, blogRoutePath)}</span>`\n : '';\n lines.push(...block('vp-blog-byline', [meta, tags].filter(Boolean).join('\\n\\n')));\n }\n if (show.cover && post.post.image) {\n // Written relative to the post itself, as the author wrote it.\n const image =\n /^[./@~]/.test(post.post.image) || isExternalUrl(post.post.image) ? post.post.image : `./${post.post.image}`;\n lines.push(...block('vp-blog-cover', `})`));\n }\n return lines.join('\\n');\n}\n\nfunction renderNavLink(kind: 'newer' | 'older', post: PostPage | undefined, context: RenderContext): string[] {\n if (!post) return [`<div class=\"vp-blog-post-nav__${kind}\"></div>`];\n const label = `<span class=\"vp-blog-post-nav__label\">${escapeTemplateText(context.labels[kind])}</span>`;\n return block(`vp-blog-post-nav__${kind}`, `${label}<br>\\n${toPageLink(post.post.title, post.routePath)}`);\n}\n\n/** Links to the newer and older posts in the post's blog, and back to the blog itself. */\nexport function renderPostNav(post: PostPage, posts: PostPage[], blog: IndexPage, context: RenderContext): string {\n const position = posts.indexOf(post);\n const newer = position > 0 ? posts[position - 1] : undefined;\n const older = position >= 0 ? posts[position + 1] : undefined;\n const lines = ['<nav class=\"vp-blog-post-nav\">'];\n if (newer || older) lines.push(...renderNavLink('newer', newer, context), ...renderNavLink('older', older, context));\n lines.push(\n ...block('vp-blog-post-nav__all', toPageLink(fill(context.labels.allPosts, { blog: blog.title }), blog.routePath)),\n );\n lines.push('</nav>');\n return lines.join('\\n');\n}\n","export interface BlogAuthor {\n name: string;\n /** Linked from the author's name in bylines and cards. */\n url?: string;\n /** Shown in the feed's `<dc:creator>` as `email (name)` when set. */\n email?: string;\n}\n\nexport interface BlogOptions {\n /**\n * Absolute URL of the published site, such as `https://example.com`. Feeds\n * and social-card tags need absolute links. Default: VitePress's\n * `sitemap.hostname`. With neither, no feeds or social tags are written.\n */\n siteUrl?: string;\n /** Locale for dates in bylines and cards. Default: `'en-US'`. */\n locale?: string;\n /** How dates are written. Default: `{ dateStyle: 'long' }`, in UTC. */\n dateFormat?: Intl.DateTimeFormatOptions;\n /** Reading speed for the \"5 min read\" estimate. `0` hides it. Default: `200`. */\n wordsPerMinute?: number;\n /** Add an author, date and tags line under each post's title. Default: `true`. */\n byline?: boolean;\n /** Show each post's `image` under its byline. Default: `false`. */\n cover?: boolean;\n /** Add newer and older post links to the end of each post. Default: `true`. */\n postNav?: boolean;\n /**\n * List `draft: true` posts, marked as drafts. They are never put in a feed.\n * Pass `process.env.NODE_ENV !== 'production'` to see them in `vitepress dev` only.\n * Default: `false`.\n */\n drafts?: boolean;\n /** Remove markdown-tags syntax (`((tag|draft))`) from titles and summaries. Default: `true`. */\n stripTags?: boolean;\n /** Authors a post can name by key: `author: jane`. */\n authors?: Record<string, BlogAuthor | string>;\n /** Author for posts that don't name one. */\n defaultAuthor?: BlogAuthor | string;\n /** Most posts in each feed, newest first. Default: `20`. */\n feedLimit?: number;\n /**\n * Folder in the build output that post images are copied to, so feeds and\n * social cards can link them at a stable URL. Default: `'blog-images'`.\n */\n imagesDir?: string;\n /**\n * Posts per page on blog pages, for those that don't set `pageSize`. `0`\n * shows every post on one page. Needs `useBlog()` in the theme. Default: `10`.\n */\n pageSize?: number;\n /**\n * Most sticky posts pinned to the top of a blog page, for those that don't\n * set `stickyLimit`. Extra sticky posts take their place by date. `0` pins\n * them all. Default: `0`.\n */\n stickyLimit?: number;\n /**\n * Show a \"Subscribe\" RSS link on blog pages and `::: blog-posts` blocks,\n * for those that don't set `rssLink`. It only shows for a blog with a feed,\n * on a site with a site URL (without one, no feeds are written). Default: `true`.\n */\n rssLink?: boolean;\n /** Words the plugin writes on pages, for translating. `{n}`, `{blog}` and `{tag}` are filled in. */\n labels?: Partial<BlogLabels>;\n}\n\nexport interface BlogLabels {\n /** Before the author names: \"By Jane Doe\". */\n by: string;\n /** Before a post's `updated` date. */\n updated: string;\n /** Reading time: \"{n} min read\". */\n readingTime: string;\n newer: string;\n older: string;\n /** The link back to a post's blog: \"All posts in {blog}\". */\n allPosts: string;\n draft: string;\n /** Badge on a sticky post. */\n sticky: string;\n noPosts: string;\n /** Year heading for posts without a date, when a blog groups by year. */\n undated: string;\n /** Label of a blog page's keyword list. */\n tags: string;\n /** Above a list filtered by keyword: \"Posts tagged {tag}\". */\n tagged: string;\n /** Link that clears the keyword filter. */\n showAll: string;\n /** When no post has the keyword: \"No posts tagged {tag}.\" */\n noTagged: string;\n previous: string;\n next: string;\n /** Accessible name of the page links. */\n pages: string;\n /** Text of the RSS link on blog pages and blocks. */\n subscribe: string;\n /** Tooltip of the RSS link: \"RSS feed for {blog}\". */\n rssFeed: string;\n}\n\nexport const DEFAULT_LABELS: BlogLabels = {\n by: 'By',\n updated: 'Updated',\n readingTime: '{n} min read',\n newer: 'Newer post',\n older: 'Older post',\n allPosts: 'All posts in {blog}',\n draft: 'Draft',\n sticky: 'Pinned',\n noPosts: 'No posts yet.',\n undated: 'Undated',\n tags: 'Keywords',\n tagged: 'Posts tagged {tag}',\n showAll: 'Show all posts',\n noTagged: 'No posts tagged {tag}.',\n previous: 'Previous',\n next: 'Next',\n pages: 'Pages',\n subscribe: 'Subscribe',\n rssFeed: 'RSS feed for {blog}',\n};\n\nexport type BlogScope = 'site' | 'section';\nexport type BlogLayout = 'list' | 'grid' | 'compact';\n\nexport interface BlogFeedConfig {\n title?: string;\n description?: string;\n limit?: number;\n /** Feed file, relative to the blog page's folder. Default: `feed.xml` (`<name>-feed.xml` for a page not named `index.md`). */\n path: string;\n}\n\n/** What a blog page's `blog-index` frontmatter says, with defaults filled in. */\nexport interface BlogIndexConfig {\n /** `site` for the main blog; `section` (the default) for a blog of one part of the site. */\n scope: BlogScope;\n /** Folders the blog covers, relative to the source directory. Empty means the whole site. */\n folders: string[];\n /** Only list posts with at least one of these tags. */\n tags: string[];\n /** Most posts to list. `0` lists them all. */\n limit: number;\n layout: BlogLayout;\n /** Put a year heading above each year's posts. */\n groupByYear: boolean;\n /** Show the post's tags on each card. */\n showTags: boolean;\n /** List every keyword above the posts, each one a filter. */\n tagList: boolean;\n /** Posts per page. `0` shows them all on one page. */\n pageSize: number;\n /** Most sticky posts pinned to the top. `0` pins them all. */\n stickyLimit: number;\n /** Show a \"Subscribe\" link to the blog's feed. */\n rssLink: boolean;\n feed: BlogFeedConfig | null;\n}\n\n/** A post's frontmatter, normalized. */\nexport interface BlogPostData {\n title: string;\n summary: string;\n date: Date | null;\n updated: Date | null;\n /** As written: a path relative to the post, a public path (`/x.png`) or a URL. */\n image: string;\n imageAlt: string;\n /** How a card fits the image in its frame: `cover` crops to fill it, `contain` shows all of it. */\n imageFit: 'cover' | 'contain';\n /** Which part of a cropped image stays in view, as CSS `object-position`: `top`, `30% 20%`. `''` is the centre. */\n imagePosition: string;\n authors: BlogAuthor[];\n tags: string[];\n category: string;\n scope: BlogScope;\n sticky: boolean;\n draft: boolean;\n /** Per-post overrides of the `byline`, `cover` and `postNav` options. */\n byline?: boolean;\n cover?: boolean;\n postNav?: boolean;\n readingMinutes: number;\n}\n","import { createHash } from 'node:crypto';\nimport { readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';\nimport path from 'node:path';\nimport {\n mayHaveBlogFrontmatter,\n readFrontmatter,\n readHeading,\n resolveIndexConfig,\n resolvePostData,\n stripTagSyntax,\n} from './frontmatter.js';\nimport type { BlogIndexConfig, BlogOptions, BlogPostData } from './types.js';\n\n/** A post or a blog page found under the source directory. */\nexport interface BlogPage {\n /** Path of the source file from the source directory, with forward slashes: `news/launch.md`. */\n relativePath: string;\n absolutePath: string;\n /** `relativePath` after VitePress rewrites: the path the page is served at. */\n routePath: string;\n title: string;\n description: string;\n post: BlogPostData | null;\n index: BlogIndexConfig | null;\n}\n\ntype ScannedPage = Omit<BlogPage, 'absolutePath' | 'routePath'>;\n\ninterface CacheEntry {\n mtimeMs: number;\n size: number;\n /** A stable key for the options the page was read with: author lookups and reading time depend on them. */\n optionsKey: string;\n page: ScannedPage | null;\n}\n\nconst scanCache = new Map<string, CacheEntry>();\n\n/**\n * Follows symlinked folders and files. `ancestors` holds the real paths of the\n * folders above this one, so a link that points back up the tree is skipped\n * instead of looping forever. Broken links are skipped.\n */\nfunction listMarkdownFiles(directory: string, ancestors: Set<string> = new Set()): string[] {\n let realDirectory: string;\n try {\n realDirectory = realpathSync(directory);\n } catch {\n return [];\n }\n if (ancestors.has(realDirectory)) return [];\n const chain = new Set(ancestors).add(realDirectory);\n\n const files: string[] = [];\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n const fullPath = path.join(directory, entry.name);\n let isDirectory = entry.isDirectory();\n let isFile = entry.isFile();\n if (entry.isSymbolicLink()) {\n try {\n const target = statSync(fullPath);\n isDirectory = target.isDirectory();\n isFile = target.isFile();\n } catch {\n continue;\n }\n }\n if (isDirectory) files.push(...listMarkdownFiles(fullPath, chain));\n else if (isFile && entry.name.endsWith('.md')) files.push(fullPath);\n }\n return files;\n}\n\nfunction toFallbackTitle(relativePath: string): string {\n const parts = relativePath.replace(/\\.md$/, '').split('/');\n const name = parts.at(-1) === 'index' && parts.length > 1 ? parts.at(-2)! : parts.at(-1)!;\n return name.replace(/[-_]+/g, ' ');\n}\n\nconst warned = new Set<string>();\n\n/** Reads one file. A page with both `blog` and `blog-index` is a blog page, not a post. */\nexport function scanPage(raw: string, relativePath: string, options: BlogOptions = {}): ScannedPage | null {\n if (!mayHaveBlogFrontmatter(raw)) return null;\n const { data, content } = readFrontmatter(raw);\n const fallbackTitle = toFallbackTitle(relativePath);\n const index = resolveIndexConfig(data, relativePath, options);\n const post = index ? null : resolvePostData(data, content, fallbackTitle, options);\n if (!index && !post) return null;\n\n const rawTitle = (typeof data.title === 'string' && data.title.trim()) || readHeading(content);\n const title = (options.stripTags ?? true) ? stripTagSyntax(rawTitle) : rawTitle;\n return {\n relativePath,\n title: post?.title ?? (title || fallbackTitle),\n description: typeof data.description === 'string' ? data.description.trim() : '',\n post,\n index,\n };\n}\n\nfunction toOptionsKey(options: BlogOptions): string {\n return JSON.stringify([\n options.authors,\n options.defaultAuthor,\n options.wordsPerMinute,\n options.stripTags,\n options.pageSize,\n options.stickyLimit,\n options.rssLink,\n ]);\n}\n\n/**\n * Every post and blog page under `sourceDirectory`, sorted by path. Results\n * are cached per file and reused until its modification time or size\n * changes. `rewrites` maps a source path to the path it is served at.\n */\nexport function indexBlogSource(\n sourceDirectory: string,\n options: BlogOptions = {},\n rewrites: Record<string, string | undefined> = {},\n): BlogPage[] {\n const pages: BlogPage[] = [];\n const present = new Set<string>();\n const optionsKey = toOptionsKey(options);\n\n for (const absolutePath of listMarkdownFiles(sourceDirectory).sort()) {\n present.add(absolutePath);\n const stats = statSync(absolutePath);\n const relativePath = path.relative(sourceDirectory, absolutePath).split(path.sep).join('/');\n let entry = scanCache.get(absolutePath);\n if (!entry || entry.mtimeMs !== stats.mtimeMs || entry.size !== stats.size || entry.optionsKey !== optionsKey) {\n let page: ScannedPage | null = null;\n try {\n page = scanPage(readFileSync(absolutePath, 'utf8'), relativePath, options);\n } catch (error) {\n // Invalid YAML: VitePress reports it when it builds the page, so warn once and move on.\n if (!warned.has(absolutePath)) {\n warned.add(absolutePath);\n console.warn(`[vitepress-blog] skipped ${relativePath}: ${(error as Error).message}`);\n }\n }\n entry = { mtimeMs: stats.mtimeMs, size: stats.size, optionsKey, page };\n scanCache.set(absolutePath, entry);\n }\n if (entry.page) pages.push({ ...entry.page, absolutePath, routePath: rewrites[relativePath] ?? relativePath });\n }\n\n for (const cached of scanCache.keys()) {\n if (cached.startsWith(sourceDirectory + path.sep) && !present.has(cached)) scanCache.delete(cached);\n }\n return pages;\n}\n\n/** What VitePress knows about the site's pages. Both fields are optional, for use outside VitePress. */\nexport interface SourceInfo {\n /** Source path to served path. */\n rewrites?: { map?: Record<string, string | undefined> };\n /** Source paths of the pages VitePress builds: pages kept out by `srcExclude` aren't in it. */\n pages?: string[];\n /** The site's own config, for the site URL feeds need. */\n userConfig?: { sitemap?: { hostname?: string } };\n}\n\n/** The posts and blog pages VitePress builds, with rewrites applied. */\nexport function loadBlogPages(sourceDirectory: string, options: BlogOptions = {}, info: SourceInfo = {}): BlogPage[] {\n const built = info.pages ? new Set(info.pages) : null;\n return indexBlogSource(sourceDirectory, options, info.rewrites?.map).filter(\n (page) => !built || built.has(page.relativePath),\n );\n}\n\n/** Changes whenever a post or blog page under the directory is added, removed or edited. */\nexport function fingerprintBlogSource(pages: BlogPage[]): string {\n const hash = createHash('sha1');\n for (const page of pages) {\n const { mtimeMs, size } = statSync(page.absolutePath);\n hash.update(`${page.relativePath}:${mtimeMs}:${size}\\n`);\n }\n return hash.digest('hex').slice(0, 12);\n}\n","import path from 'node:path';\nimport { parse } from 'yaml';\nimport { findBlogForPage, isIndex, listBlogPosts, pinStickyPosts, type IndexPage } from './collect.js';\nimport { toStringList } from './frontmatter.js';\nimport { renderGroup, renderRssLink, type RenderContext } from './render.js';\nimport type { BlogPage } from './scan.js';\nimport type { BlogLayout } from './types.js';\nimport { escapeTemplateText, toPageLink } from './util.js';\n\n/** Pages that hold a block, by absolute path, so `vitepress dev` can refresh them when a post changes. */\nexport const widgetPages = new Set<string>();\n\n/** Name of the block: `::: blog-posts` … `:::`. */\nexport const WIDGET_NAME = 'blog-posts';\n\n// `::: blog-posts` on its own line, a YAML body, and a closing `:::` line.\nconst WIDGET_OPEN = new RegExp(`^ {0,3}:{3,}[ \\\\t]*${WIDGET_NAME}[ \\\\t]*$`);\nconst WIDGET_CLOSE = /^ {0,3}:{3,}[ \\t]*$/;\nconst FENCE = /^[ \\t]*(`{3,}|~{3,})/;\nconst DEFAULT_LIMIT = 5;\n\n/** What a `::: blog-posts` block asks for, with defaults filled in from its blog page. */\nexport interface WidgetConfig {\n /** As written: a blog page's URL (`/blog/`) or file (`blog/index.md`), `./` and `../` page-relative. */\n blog: string;\n limit: number;\n stickyLimit: number;\n layout: BlogLayout;\n showTags: boolean;\n tags: string[];\n /** Text of the link to the blog page, or `null` for none. */\n more: string | null;\n /** Show a \"Subscribe\" link to the blog's feed. */\n rssLink: boolean;\n}\n\n/** Whether a page's source holds a block, cheaply, before it is parsed. */\nexport function hasWidget(source: string): boolean {\n return source.includes(`${WIDGET_NAME}`) && source.split('\\n').some((line) => WIDGET_OPEN.test(line));\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {};\n}\n\nfunction toCount(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined;\n}\n\n/**\n * Finds the blog page a block names. `blog` can be a URL path (`/blog/`,\n * `/news/archive`) or a file (`blog/index.md`), from the source directory or,\n * starting with `./` or `../`, from the page's folder. Without one, it is the\n * page's own blog (see `findBlogForPage`).\n */\nexport function findWidgetBlog(pages: BlogPage[], blog: string, pageRelativePath: string): IndexPage | null {\n const indexes = pages.filter(isIndex);\n if (!blog) return findBlogForPage(pages, pageRelativePath);\n let target = /^\\.\\.?(\\/|$)/.test(blog)\n ? path.posix.join(path.posix.dirname(pageRelativePath), blog)\n : blog.replace(/^\\/+/, '');\n target = target.replace(/\\.html$/, '');\n const candidates = target.endsWith('.md')\n ? [target]\n : target === '' || target.endsWith('/')\n ? [`${target}index.md`]\n : [`${target}.md`, `${target}/index.md`];\n return (\n indexes.find((index) => candidates.includes(index.routePath)) ??\n indexes.find((index) => candidates.includes(index.relativePath)) ??\n null\n );\n}\n\n/** A block's settings as written: the ones left out fall back to its blog page's. */\nexport type WidgetSettings = Omit<WidgetConfig, 'stickyLimit' | 'layout' | 'showTags' | 'rssLink'> &\n Partial<Pick<WidgetConfig, 'stickyLimit' | 'layout' | 'showTags' | 'rssLink'>>;\n\n/** Reads a block's YAML body. Values it can't use are left to the defaults. */\nexport function resolveWidgetConfig(body: string): { config: WidgetSettings; problem?: string } {\n let data: Record<string, unknown> = {};\n let problem: string | undefined;\n try {\n data = toRecord(body.trim() ? parse(body) : {});\n } catch (error) {\n problem = `its settings are not valid YAML: ${(error as Error).message.split('\\n')[0]}`;\n }\n const layout =\n data.layout === 'list' || data.layout === 'grid' || data.layout === 'compact' ? data.layout : undefined;\n return {\n config: {\n blog: typeof data.blog === 'string' ? data.blog.trim() : '',\n limit: toCount(data.limit) ?? DEFAULT_LIMIT,\n stickyLimit: toCount(data.stickyLimit),\n layout,\n showTags: typeof data.showTags === 'boolean' ? data.showTags : undefined,\n tags: toStringList(data.tags),\n more: data.more === false ? null : typeof data.more === 'string' && data.more.trim() ? data.more.trim() : '',\n rssLink: typeof data.rssLink === 'boolean' ? data.rssLink : undefined,\n },\n problem,\n };\n}\n\nexport interface WidgetEnvironment {\n pages: BlogPage[];\n /** The page holding the block, from the source directory. */\n pageRelativePath: string;\n drafts: boolean;\n context: RenderContext;\n /** Called with a message for each block that can't be shown. */\n warn: (message: string) => void;\n}\n\n/** Renders one block's Markdown: its posts, then a link to its blog page. */\nexport function renderWidget(body: string, environment: WidgetEnvironment): string {\n const { pages, pageRelativePath, drafts, context, warn } = environment;\n const { config, problem } = resolveWidgetConfig(body);\n if (problem) warn(problem);\n\n const blog = findWidgetBlog(pages, config.blog, pageRelativePath);\n if (!blog) {\n warn(config.blog ? `no blog page found at \"${config.blog}\"` : 'no blog covers this page: name one with \"blog:\"');\n return '';\n }\n\n const stickyLimit = config.stickyLimit ?? blog.index.stickyLimit;\n const wanted = new Set(config.tags.map((tag) => tag.toLowerCase()));\n const posts = listBlogPosts(pages, blog, { drafts, stickyFirst: false, applyLimit: false }).filter(\n (post) => wanted.size === 0 || post.post.tags.some((tag) => wanted.has(tag.toLowerCase())),\n );\n const { pinned, rest } = pinStickyPosts(posts, stickyLimit);\n const shown = [...pinned, ...rest].slice(0, config.limit || undefined);\n\n // The block's own settings over the blog page's; keyword chips still link to the blog page.\n const view: IndexPage = {\n ...blog,\n index: {\n ...blog.index,\n layout: config.layout ?? blog.index.layout,\n showTags: config.showTags ?? blog.index.showTags,\n },\n };\n\n const lines = [`<div class=\"vp-blog-widget\">`];\n if (shown.length) lines.push(...renderGroup(shown, view, context));\n else lines.push(`<p class=\"vp-blog-empty\">${escapeTemplateText(context.labels.noPosts)}</p>`);\n // The footer: a \"Subscribe\" link to the blog's feed, and a link to the blog page.\n const footer = [renderRssLink(blog, context, config.rssLink ?? blog.index.rssLink)];\n if (config.more !== null) {\n footer.push(toPageLink(config.more || context.labels.allPosts.replace('{blog}', blog.title), blog.routePath));\n }\n if (footer.some(Boolean)) {\n lines.push('<div class=\"vp-blog-widget__footer\">', '', footer.filter(Boolean).join(' '), '', '</div>');\n }\n lines.push('</div>');\n return lines.join('\\n');\n}\n\n/**\n * Replaces every `::: blog-posts` block outside fenced code with its posts.\n * Blocks inside code fences are left as written, so the syntax can be shown.\n */\nexport function applyWidgets(source: string, environment: WidgetEnvironment): string {\n const lines = source.split('\\n');\n const output: string[] = [];\n let fence = '';\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index];\n const marker = FENCE.exec(line)?.[1];\n if (marker && (!fence || (marker[0] === fence[0] && marker.length >= fence.length))) {\n fence = fence ? '' : marker;\n output.push(line);\n continue;\n }\n if (fence || !WIDGET_OPEN.test(line)) {\n output.push(line);\n continue;\n }\n const close = lines.findIndex((candidate, at) => at > index && WIDGET_CLOSE.test(candidate));\n if (close === -1) {\n environment.warn(`\"::: ${WIDGET_NAME}\" on line ${index + 1} has no closing \":::\"`);\n output.push(line);\n continue;\n }\n const lineWarn = (message: string) => environment.warn(`the block on line ${index + 1}: ${message}`);\n output.push('', renderWidget(lines.slice(index + 1, close).join('\\n'), { ...environment, warn: lineWarn }), '');\n index = close;\n }\n return output.join('\\n');\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","name","block","path","import_node_path","path","import_node_path","import_node_path","path","import_node_crypto","import_node_fs","import_node_path","path","import_node_path","import_yaml","toRecord","toCount","path","FENCE","HEADING_1","path","warned","path","module"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { TransformContext, HeadConfig, PageData, TransformPageContext, SiteConfig, UserConfig } from 'vitepress';
|
|
3
|
+
import { e as BlogOptions } from './plugin-BjbV6nDK.cjs';
|
|
4
|
+
export { h as blogMarkdown } from './plugin-BjbV6nDK.cjs';
|
|
5
|
+
import 'markdown-it';
|
|
6
|
+
|
|
7
|
+
/** Where the site is published, for the absolute links feeds and social cards need. */
|
|
8
|
+
interface SiteLocation {
|
|
9
|
+
/** `https://example.com`, without the base. */
|
|
10
|
+
siteUrl: string;
|
|
11
|
+
/** VitePress's `base`: `/` or `/docs/`. */
|
|
12
|
+
base: string;
|
|
13
|
+
cleanUrls: boolean;
|
|
14
|
+
/** Absolute path of VitePress's public folder, for `/x.png` images. */
|
|
15
|
+
publicDir: string;
|
|
16
|
+
/** Folder under the output that relative post images are copied to. */
|
|
17
|
+
imagesDir: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Where the site is published, or `null` without a site URL: feeds need absolute links. */
|
|
21
|
+
declare function resolveSiteLocation(siteConfig: SiteConfig, options: BlogOptions): SiteLocation | null;
|
|
22
|
+
/**
|
|
23
|
+
* `<head>` tags for blog pages: a feed link on each blog page, and on each
|
|
24
|
+
* post Open Graph and Twitter card tags (title, summary, image, dates) plus
|
|
25
|
+
* links to the feeds that carry it. Use it from VitePress's `transformHead`:
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* transformHead: (context) => blogHead(context, options),
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Social tags need absolute URLs, so `og:url` and `og:image` are left out
|
|
32
|
+
* without a site URL (the `siteUrl` option or `sitemap.hostname`).
|
|
33
|
+
*/
|
|
34
|
+
declare function blogHead(context: TransformContext, options?: BlogOptions): HeadConfig[];
|
|
35
|
+
/**
|
|
36
|
+
* Gives a post without a `description` its summary (or the first paragraph,
|
|
37
|
+
* when it has no summary) as one, so VitePress writes it into the page's
|
|
38
|
+
* `<meta name="description">`. Use it from `transformPageData`:
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* transformPageData: (pageData, context) => blogPageData(pageData, context, options),
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare function blogPageData(pageData: PageData, context: TransformPageContext, options?: BlogOptions): void;
|
|
45
|
+
/**
|
|
46
|
+
* Writes an RSS feed for every blog page whose feed isn't turned off, and
|
|
47
|
+
* copies post images to `imagesDir` for the feeds and social cards to link.
|
|
48
|
+
* Use it from VitePress's `buildEnd`.
|
|
49
|
+
*/
|
|
50
|
+
declare function writeBlogFeeds(siteConfig: SiteConfig, options?: BlogOptions): void;
|
|
51
|
+
/**
|
|
52
|
+
* The Vite side of the blog, for `vitepress dev`:
|
|
53
|
+
*
|
|
54
|
+
* - VitePress caches a page's rendered output by its own source, so a new or
|
|
55
|
+
* edited post would leave blog lists and "newer / older" links stale. This
|
|
56
|
+
* adds a fingerprint of every post to blog pages and posts (busting the
|
|
57
|
+
* cache) and reloads them when any of them changes.
|
|
58
|
+
* - Serves each blog's feed and the copied post images, which the build
|
|
59
|
+
* writes in `buildEnd`, so feed links work in development too.
|
|
60
|
+
*/
|
|
61
|
+
declare function blogVitePlugin(options?: BlogOptions): Plugin;
|
|
62
|
+
/**
|
|
63
|
+
* Wraps a VitePress config with the whole blog: the Markdown plugin, the dev
|
|
64
|
+
* server plugin, `<head>` tags, descriptions from summaries, and feeds at the
|
|
65
|
+
* end of the build.
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* // .vitepress/config.mts
|
|
69
|
+
* import { defineConfig } from 'vitepress';
|
|
70
|
+
* import { withBlog } from '@binarynoir/vitepress-blog/vitepress';
|
|
71
|
+
*
|
|
72
|
+
* export default withBlog(defineConfig({ /* ...your config... *\/ }), { siteUrl: 'https://example.com' });
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* Hooks you already have (`markdown.config`, `transformHead`,
|
|
76
|
+
* `transformPageData`, `buildEnd`) are not replaced: the blog's runs and
|
|
77
|
+
* then yours, so it composes with other `withX()` wrappers in any order.
|
|
78
|
+
*/
|
|
79
|
+
declare function withBlog(config: UserConfig, options?: BlogOptions): UserConfig;
|
|
80
|
+
|
|
81
|
+
export { BlogOptions, blogHead, blogPageData, blogVitePlugin, resolveSiteLocation, withBlog, writeBlogFeeds };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { TransformContext, HeadConfig, PageData, TransformPageContext, SiteConfig, UserConfig } from 'vitepress';
|
|
3
|
+
import { e as BlogOptions } from './plugin-BjbV6nDK.js';
|
|
4
|
+
export { h as blogMarkdown } from './plugin-BjbV6nDK.js';
|
|
5
|
+
import 'markdown-it';
|
|
6
|
+
|
|
7
|
+
/** Where the site is published, for the absolute links feeds and social cards need. */
|
|
8
|
+
interface SiteLocation {
|
|
9
|
+
/** `https://example.com`, without the base. */
|
|
10
|
+
siteUrl: string;
|
|
11
|
+
/** VitePress's `base`: `/` or `/docs/`. */
|
|
12
|
+
base: string;
|
|
13
|
+
cleanUrls: boolean;
|
|
14
|
+
/** Absolute path of VitePress's public folder, for `/x.png` images. */
|
|
15
|
+
publicDir: string;
|
|
16
|
+
/** Folder under the output that relative post images are copied to. */
|
|
17
|
+
imagesDir: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Where the site is published, or `null` without a site URL: feeds need absolute links. */
|
|
21
|
+
declare function resolveSiteLocation(siteConfig: SiteConfig, options: BlogOptions): SiteLocation | null;
|
|
22
|
+
/**
|
|
23
|
+
* `<head>` tags for blog pages: a feed link on each blog page, and on each
|
|
24
|
+
* post Open Graph and Twitter card tags (title, summary, image, dates) plus
|
|
25
|
+
* links to the feeds that carry it. Use it from VitePress's `transformHead`:
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* transformHead: (context) => blogHead(context, options),
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Social tags need absolute URLs, so `og:url` and `og:image` are left out
|
|
32
|
+
* without a site URL (the `siteUrl` option or `sitemap.hostname`).
|
|
33
|
+
*/
|
|
34
|
+
declare function blogHead(context: TransformContext, options?: BlogOptions): HeadConfig[];
|
|
35
|
+
/**
|
|
36
|
+
* Gives a post without a `description` its summary (or the first paragraph,
|
|
37
|
+
* when it has no summary) as one, so VitePress writes it into the page's
|
|
38
|
+
* `<meta name="description">`. Use it from `transformPageData`:
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* transformPageData: (pageData, context) => blogPageData(pageData, context, options),
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare function blogPageData(pageData: PageData, context: TransformPageContext, options?: BlogOptions): void;
|
|
45
|
+
/**
|
|
46
|
+
* Writes an RSS feed for every blog page whose feed isn't turned off, and
|
|
47
|
+
* copies post images to `imagesDir` for the feeds and social cards to link.
|
|
48
|
+
* Use it from VitePress's `buildEnd`.
|
|
49
|
+
*/
|
|
50
|
+
declare function writeBlogFeeds(siteConfig: SiteConfig, options?: BlogOptions): void;
|
|
51
|
+
/**
|
|
52
|
+
* The Vite side of the blog, for `vitepress dev`:
|
|
53
|
+
*
|
|
54
|
+
* - VitePress caches a page's rendered output by its own source, so a new or
|
|
55
|
+
* edited post would leave blog lists and "newer / older" links stale. This
|
|
56
|
+
* adds a fingerprint of every post to blog pages and posts (busting the
|
|
57
|
+
* cache) and reloads them when any of them changes.
|
|
58
|
+
* - Serves each blog's feed and the copied post images, which the build
|
|
59
|
+
* writes in `buildEnd`, so feed links work in development too.
|
|
60
|
+
*/
|
|
61
|
+
declare function blogVitePlugin(options?: BlogOptions): Plugin;
|
|
62
|
+
/**
|
|
63
|
+
* Wraps a VitePress config with the whole blog: the Markdown plugin, the dev
|
|
64
|
+
* server plugin, `<head>` tags, descriptions from summaries, and feeds at the
|
|
65
|
+
* end of the build.
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* // .vitepress/config.mts
|
|
69
|
+
* import { defineConfig } from 'vitepress';
|
|
70
|
+
* import { withBlog } from '@binarynoir/vitepress-blog/vitepress';
|
|
71
|
+
*
|
|
72
|
+
* export default withBlog(defineConfig({ /* ...your config... *\/ }), { siteUrl: 'https://example.com' });
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* Hooks you already have (`markdown.config`, `transformHead`,
|
|
76
|
+
* `transformPageData`, `buildEnd`) are not replaced: the blog's runs and
|
|
77
|
+
* then yours, so it composes with other `withX()` wrappers in any order.
|
|
78
|
+
*/
|
|
79
|
+
declare function withBlog(config: UserConfig, options?: BlogOptions): UserConfig;
|
|
80
|
+
|
|
81
|
+
export { BlogOptions, blogHead, blogPageData, blogVitePlugin, resolveSiteLocation, withBlog, writeBlogFeeds };
|