@binarynoir/vitepress-blog 0.3.0 → 0.5.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/collect.ts","../src/feed.ts","../src/types.ts","../src/render.ts","../src/dates.ts","../src/frontmatter.ts","../src/scan.ts","../src/widget.ts","../src/plugin.ts"],"sourcesContent":["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\n/** Whether the post has one of `tags`, in any case. No tags means any post. */\nexport function 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/** Whether the post is in one of `categories`, in any case. No categories means any post. */\nexport function inAnyCategory(post: PostPage, categories: string[]): boolean {\n if (categories.length === 0) return true;\n const category = post.post.category.toLowerCase();\n return !!category && categories.some((wanted) => wanted.toLowerCase() === category);\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 /** Put `pinned: true` posts at the top, up to the blog's `pinLimit`. */\n pinnedFirst: boolean;\n /** Apply the blog's `limit`. */\n applyLimit: boolean;\n}\n\n/**\n * Splits date-ordered posts into the pinned ones kept at the top (the newest\n * `limit` of them, or all with `0`) and the rest, still in date order.\n */\nexport function pinPosts(posts: PostPage[], limit: number): { pinned: PostPage[]; rest: PostPage[] } {\n const candidates = posts.filter((post) => post.post.pinned);\n const pinned = new Set(limit ? candidates.slice(0, limit) : candidates);\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 .filter((post) => inAnyCategory(post, index.index.categories))\n .sort(compareByDate);\n if (options.pinnedFirst) {\n const { pinned, rest } = pinPosts(posts, index.index.pinLimit);\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, pinnedFirst: 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, pinnedFirst: false, applyLimit: false }).includes(post),\n );\n}\n\n/** Every announcement, newest first. Drafts only with `drafts`. */\nexport function listAnnouncements(pages: BlogPage[], drafts: boolean): PostPage[] {\n return pages\n .filter(isPost)\n .filter((post) => post.post.announcement && (drafts || !post.post.draft))\n .sort(compareByDate);\n}\n\n/**\n * The blog page the banner's count badge opens, filtered to announcements: a\n * blog page that lists all of them (the main blog first), else the newest\n * one's own blog.\n */\nexport function findAnnouncementBlog(pages: BlogPage[], announcements: PostPage[], drafts: boolean): IndexPage | null {\n if (announcements.length === 0) return null;\n const listingAll = pages.filter(isIndex).filter((index) => {\n const listed = listBlogPosts(pages, index, { drafts, pinnedFirst: false, applyLimit: false });\n return announcements.every((post) => listed.includes(post));\n });\n return (\n listingAll.find((index) => index.index.scope === 'site') ??\n listingAll[0] ??\n findHomeBlog(pages, announcements[0], drafts)\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","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' }`. */\n dateFormat?: Intl.DateTimeFormatOptions;\n /**\n * How times are written, for dates whose frontmatter gives one\n * (`2026-10-20 14:30`). Default: `{ hour: 'numeric', minute: '2-digit', timeZoneName: 'short' }`,\n * as in \"2:30 PM EDT\".\n */\n timeFormat?: Intl.DateTimeFormatOptions;\n /**\n * The time zone frontmatter times without an offset are in, and that every\n * date and time is shown in: an IANA name such as `'America/New_York'`.\n * A value with an offset (`2026-10-20T14:30:00-04:00`, `…Z`) is read as\n * written. Default: `'UTC'`.\n */\n timeZone?: string;\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 `pinned: true` posts kept at the top of a blog page, for those that\n * don't set `pinLimit`. Extra pinned posts take their place by date. `0`\n * pins them all. Default: `0`.\n */\n pinLimit?: number;\n /** @deprecated Renamed `pinLimit`; still read when `pinLimit` isn't set. */\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}`, `{filter}` and `{row}` 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 `date` in its byline: \"Published October 20, 2026\". */\n published: string;\n /** Before a post's `updated` date. */\n updated: string;\n /** A date and its time: \"{date} at {time}\". */\n dateTime: 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 pinned post. */\n pinned: string;\n /** Badge on an announcement, and the accessible name of the announcement banner. */\n announcement: string;\n /** A blog page's announcements filter (`?announcements`), in the active filters. */\n announcements: string;\n /** Tooltip of the banner's count badge: \"{n} announcements\". */\n announcementCount: string;\n /** Accessible name of the banner's close button. */\n dismiss: string;\n noPosts: string;\n /** Group heading for posts without a date, when a blog groups by year or month. */\n undated: string;\n /** Accessible name of the browse panel above a blog page's posts. */\n browse: string;\n /** Label of the browse panel's category row. */\n categories: string;\n /** Label of the browse panel's keyword row. */\n tags: string;\n /** Label of the browse panel's year row. */\n archive: string;\n /** Names a category filter among the active filters: \"Category\". */\n category: string;\n /** Names a keyword filter among the active filters: \"Keyword\". */\n tag: string;\n /** How many posts the active filters match: \"{n} posts\". */\n postCount: string;\n /** The same, for one post: \"{n} post\". */\n postCountOne: string;\n /** When no post matches the active filters. */\n noMatches: string;\n /** Accessible name of an active filter's remove link: \"Remove filter: {filter}\". */\n removeFilter: string;\n /** Link that clears every filter. */\n showAll: string;\n /** Button that shows the rest of a browse row: \"+{n} more\". */\n more: string;\n /** Button that shows a browse row's first values again. */\n less: string;\n /** Accessible name of the field that narrows a long browse row: \"Filter {row}\". */\n filter: string;\n /** When nothing in a browse row matches the filter field. */\n noFilterMatches: 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 published: 'Published',\n updated: 'Updated',\n dateTime: '{date} at {time}',\n readingTime: '{n} min read',\n newer: 'Newer post',\n older: 'Older post',\n allPosts: 'All posts in {blog}',\n draft: 'Draft',\n pinned: 'Pinned',\n announcement: 'Announcement',\n announcements: 'Announcements',\n announcementCount: '{n} announcements',\n dismiss: 'Dismiss',\n noPosts: 'No posts yet.',\n undated: 'Undated',\n browse: 'Browse posts',\n categories: 'Categories',\n tags: 'Keywords',\n archive: 'Archive',\n category: 'Category',\n tag: 'Keyword',\n postCount: '{n} posts',\n postCountOne: '{n} post',\n noMatches: 'No posts match these filters.',\n removeFilter: 'Remove filter: {filter}',\n showAll: 'Show all posts',\n more: '+{n} more',\n less: 'Show fewer',\n filter: 'Filter {row}',\n noFilterMatches: 'Nothing matches.',\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/** How a blog page groups its posts under headings: by year, by month, or not at all (`''`). */\nexport type BlogGroupBy = 'year' | 'month' | '';\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 /** Only list posts in one of these categories. */\n categories: string[];\n /** Most posts to list. `0` lists them all. */\n limit: number;\n layout: BlogLayout;\n /** Put a heading above each year's or month's posts. */\n groupBy: BlogGroupBy;\n /** Show the post's tags on each card. */\n showTags: boolean;\n /** Show the post's category above its title on each card. */\n showCategory: boolean;\n /** List every category above the posts, each one a filter. */\n categoryList: boolean;\n /** Categories shown before \"+N more\", most used first. `0` shows them all. */\n categoryLimit: number;\n /** List every keyword above the posts, each one a filter. */\n tagList: boolean;\n /** Keywords shown before \"+N more\", most used first. `0` shows them all. */\n tagLimit: number;\n /** Leave keywords used by fewer posts than this out of the browse panel. They still show on cards. */\n tagMinCount: number;\n /** List the years and months that have posts above the posts, each one a filter. */\n archive: boolean;\n /** Posts per page. `0` shows them all on one page. */\n pageSize: number;\n /** Most pinned posts kept at the top. `0` pins them all. */\n pinLimit: 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 /** When the post was published, from `date`. */\n date: Date | null;\n /** Whether `date` gave a time, not just a day. */\n dateHasTime: boolean;\n /** When the post was last changed, from `updated`. */\n updated: Date | null;\n /** Whether `updated` gave a time, not just a day. */\n updatedHasTime: boolean;\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 pinned: boolean;\n draft: boolean;\n /** Shown in the announcement banner above the nav bar. */\n announcement: boolean;\n /** The banner's text, from `announcement: \"…\"`. `''` uses the title. */\n announcementText: string;\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\n/** One announcement, as the banner shows it. */\nexport interface BlogAnnouncementItem {\n /** The post's path and date: what a dismissal remembers, so re-dating a post announces it again. */\n id: string;\n text: string;\n /** The post's URL path, without the site's base. */\n link: string;\n}\n\n/** What the announcement banner reads from VitePress's site data. */\nexport interface BlogAnnouncementData {\n /** Newest first: the banner shows the first one that hasn't been dismissed. */\n items: BlogAnnouncementItem[];\n /** The blog page filtered to announcements (`/blog/?announcements`), without the base, or `''` when no blog lists them. */\n listLink: string;\n labels: Pick<BlogLabels, 'announcement' | 'announcementCount' | 'dismiss'>;\n}\n","import path from 'node:path';\nimport type { IndexPage, PostPage } from './collect.js';\nimport {\n formatMonth,\n formatPostDate,\n monthIn,\n resolveTimeZone,\n toDateTimeAttribute,\n yearIn,\n type DateDisplay,\n} from './dates.js';\nimport { toFeedPath } from './feed.js';\nimport { DEFAULT_LABELS, type BlogAuthor, type BlogLabels, type BlogOptions } from './types.js';\nimport { escapeHtml, escapeMarkdown, escapeTemplateText, isExternalUrl, toMarkdownUrl, toPageLink } 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 /** How dates and times are shown: format, time zone, and how a date and a time are joined. */\n dates: DateDisplay;\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 const locale = options.locale ?? 'en-US';\n const labels = { ...DEFAULT_LABELS, ...options.labels };\n return {\n locale,\n dates: {\n locale,\n timeZone: resolveTimeZone(options.timeZone),\n dateFormat: options.dateFormat ?? { dateStyle: 'long' },\n timeFormat: options.timeFormat ?? { hour: 'numeric', minute: '2-digit', timeZoneName: 'short' },\n dateTime: labels.dateTime,\n },\n 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, '&quot;');\n const url = toMarkdownUrl(`/${toFeedPath(index)}`);\n const text = `<span class=\"vp-blog-rss__text\">${escapeMarkdown(context.labels.subscribe)}</span>`;\n return `<span class=\"vp-blog-rss\">[${RSS_ICON} ${text}](${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\n/** A `<time>` element: the date, plus the time when the frontmatter gave one, in the blog's time zone. */\nfunction renderTime(date: Date, hasTime: boolean, context: RenderContext): string {\n const value = { date, hasTime };\n const attribute = toDateTimeAttribute(value, context.dates.timeZone);\n return `<time datetime=\"${attribute}\">${escapeTemplateText(formatPostDate(value, context.dates))}</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' | 'pinned' | 'announcement', context: RenderContext): string {\n return `<span class=\"vp-blog-badge vp-blog-badge--${kind}\">${escapeTemplateText(context.labels[kind])}</span>`;\n}\n\n/** The query parameters a blog page filters by: `?category=`, `?tag=`, `?year=2026` and `?month=2026-10`. */\nexport type FilterParam = 'category' | 'tag' | 'year' | 'month';\n\n/**\n * The URL that filters a blog page, which VitePress gives the base:\n * `/blog/?tag=release`, or `/news/archive.md?category=engineering` for a page\n * not 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 toFilterUrl(blogRoutePath: string, param: FilterParam, value: string): string {\n const encoded = encodeURIComponent(value.toLowerCase()).replace(/[()]/g, (c) => (c === '(' ? '%28' : '%29'));\n const page = blogRoutePath.replace(/(^|\\/)index\\.md$/, '$1');\n return `${toMarkdownUrl(`/${page}`)}?${param}=${encoded}`;\n}\n\n/** The URL that filters a blog page by a keyword: `/blog/?tag=release`. See `toFilterUrl`. */\nexport function toTagUrl(blogRoutePath: string, tag: string): string {\n return toFilterUrl(blogRoutePath, 'tag', tag);\n}\n\n/** A chip: a link that filters `blogRoutePath` by `param`, or plain text without a blog. */\nfunction renderFilterLink(text: string, param: FilterParam, blogRoutePath: string | null, className: string): string {\n return blogRoutePath\n ? `[${escapeMarkdown(text)}](${toFilterUrl(blogRoutePath, param, text)})`\n : `<span class=\"${className}\">${escapeTemplateText(text)}</span>`;\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.map((tag) => renderFilterLink(tag, 'tag', blogRoutePath, 'vp-blog-tag')).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\n/**\n * The attributes the client filters a post by: its keywords, its category\n * (lowercased), the month it was published in (`YYYY-MM`, in the blog's time\n * zone), and whether it is an announcement.\n */\nfunction toItemAttributes(post: PostPage, context: RenderContext): string {\n const { category, date, announcement } = post.post;\n const attributes = [`data-vp-blog-item data-tags=\"${toTagsAttribute(post)}\"`];\n if (category) attributes.push(`data-category=\"${escapeHtml(category.toLowerCase())}\"`);\n if (date) attributes.push(`data-month=\"${monthIn(date, context.dates.timeZone)}\"`);\n if (announcement) attributes.push('data-announcement');\n return attributes.join(' ');\n}\n\n/** Badges for a draft, a pinned post and an announcement, joined with spaces. */\nfunction renderBadges(post: PostPage, context: RenderContext): string {\n const { draft, pinned, announcement } = post.post;\n return [\n draft && renderBadge('draft', context),\n pinned && renderBadge('pinned', context),\n announcement && renderBadge('announcement', context),\n ]\n .filter(Boolean)\n .join(' ');\n}\n\ninterface MetaParts {\n authors: boolean;\n updated: boolean;\n badges: boolean;\n}\n\n/**\n * One line of post details, joined with middle dots: badges, date, updated,\n * reading time, authors. In a byline (`include.updated`) the dates are\n * labelled \"Published\" and \"Updated\"; on cards the date stands alone.\n */\nfunction renderMeta(post: PostPage, context: RenderContext, include: MetaParts): string {\n const { date, dateHasTime, updated, updatedHasTime, readingMinutes, authors } = post.post;\n const parts: string[] = [];\n if (include.badges) {\n const shown = renderBadges(post, context);\n if (shown) parts.push(shown);\n }\n if (date) {\n const time = renderTime(date, dateHasTime, context);\n parts.push(include.updated ? `${escapeMarkdown(context.labels.published)} ${time}` : time);\n }\n if (include.updated && updated && (!date || updated.getTime() !== date.getTime())) {\n parts.push(`${escapeMarkdown(context.labels.updated)} ${renderTime(updated, updatedHasTime, 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.pinned && 'pinned',\n post.post.draft && 'draft',\n post.post.announcement && 'announcement',\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)} ${toItemAttributes(post, context)}>`,\n ...(image ? block('vp-blog-card__image', `[![${alt}](${toMarkdownUrl(image)})](${toMarkdownUrl(link)})`) : []),\n '<div class=\"vp-blog-card__body\">',\n ...(index.index.showCategory && post.post.category\n ? block('vp-blog-card__category', renderFilterLink(post.post.category, 'category', index.routePath, ''))\n : []),\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, post.post.dateHasTime, context)} ` : '';\n const badges = renderBadges(post, context);\n // List items can't carry attributes in Markdown, so a hidden marker inside each one does.\n const marker = `<span hidden ${toItemAttributes(post, context)}></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/**\n * The posts under year or month headings (\"2026\", \"October 2026\"), each group\n * in a section the client can hide when none of its posts show.\n */\nfunction renderGroups(posts: PostPage[], index: IndexPage, context: RenderContext): string[] {\n const { groupBy } = index.index;\n if (!groupBy) return renderGroup(posts, index, context);\n const { timeZone, locale } = context.dates;\n const groups = new Map<string, PostPage[]>();\n for (const post of posts) {\n const { date } = post.post;\n const key = !date ? '' : groupBy === 'year' ? String(yearIn(date, timeZone)) : monthIn(date, timeZone);\n groups.set(key, [...(groups.get(key) ?? []), post]);\n }\n const lines: string[] = [];\n for (const [key, group] of groups) {\n const heading = !key\n ? context.labels.undated\n : groupBy === 'year'\n ? key\n : formatMonth(key, locale, { month: 'long', year: 'numeric' });\n lines.push(\n `<section class=\"vp-blog-group vp-blog-${groupBy}\">`,\n '',\n `## ${escapeMarkdown(heading)}`,\n '',\n ...renderGroup(group, index, context),\n '',\n '</section>',\n );\n }\n return lines;\n}\n\nexport interface FacetCount {\n value: string;\n count: number;\n}\n\n/** Every distinct value `pick` gives the posts, any case counted once in its first spelling, with how many posts have it, alphabetically. */\nfunction collectValues(posts: PostPage[], pick: (post: PostPage) => string[]): FacetCount[] {\n const values = new Map<string, FacetCount>();\n for (const post of posts) {\n for (const value of new Set(pick(post))) {\n const entry = values.get(value.toLowerCase()) ?? { value, count: 0 };\n entry.count++;\n values.set(value.toLowerCase(), entry);\n }\n }\n return [...values.values()].sort((a, b) => a.value.localeCompare(b.value, undefined, { sensitivity: 'base' }));\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 return collectValues(posts, (post) => post.post.tags).map(({ value, count }) => ({ tag: value, count }));\n}\n\n/** Every category the listed posts are in, with how many are in it, alphabetically. */\nexport function collectCategories(posts: PostPage[]): FacetCount[] {\n return collectValues(posts, (post) => (post.post.category ? [post.post.category] : []));\n}\n\nexport interface ArchiveYear extends FacetCount {\n /** The year's months that have posts, as `YYYY-MM`, newest first. */\n months: FacetCount[];\n}\n\n/** The years and months the dated posts were published in, in `timeZone`, with their post counts, newest first. */\nexport function collectArchive(posts: PostPage[], timeZone: string): ArchiveYear[] {\n const years = new Map<string, ArchiveYear>();\n for (const post of posts) {\n if (!post.post.date) continue;\n const month = monthIn(post.post.date, timeZone);\n const year = years.get(month.slice(0, 4)) ?? { value: month.slice(0, 4), count: 0, months: [] };\n year.count++;\n const entry = year.months.find((candidate) => candidate.value === month);\n if (entry) entry.count++;\n else year.months.push({ value: month, count: 1 });\n years.set(year.value, year);\n }\n const newestFirst = (a: FacetCount, b: FacetCount) => b.value.localeCompare(a.value);\n return [...years.values()].sort(newestFirst).map((year) => ({ ...year, months: [...year.months].sort(newestFirst) }));\n}\n\n/** A browse chip: a filter link holding its name and post count, with an optional tooltip. */\nfunction renderChip(name: string, count: number, url: string, title = ''): string {\n const tooltip = title ? ` \"${title.replace(/\"/g, '&quot;')}\"` : '';\n return (\n `[<span class=\"vp-blog-facet__name\">${escapeMarkdown(name)}</span> ` +\n `<span class=\"vp-blog-facet__count\">${count}</span>](${url}${tooltip})`\n );\n}\n\ninterface FacetRow {\n facet: FilterParam;\n label: string;\n chips: string[];\n /** The row's name for screen readers, when it isn't `label`. */\n ariaLabel?: string;\n /** How many chips show before \"+N more\". `0` shows them all. */\n limit?: number;\n /** Added to the row's opening tag. */\n attributes?: string;\n /** Markdown beside the chips, at the end of the row: the first row holds the \"Subscribe\" link. */\n aside?: string;\n}\n\n/**\n * One row of the browse panel: a label, its chips, and anything beside them.\n * The client shows the first `limit` chips and a \"+N more\" button for the\n * rest; without it, every chip shows.\n */\nfunction renderFacet(row: FacetRow): string[] {\n const { facet, label, chips, ariaLabel = label, limit = 0, attributes = '', aside = '' } = row;\n const limited = limit && chips.length > limit ? ` data-limit=\"${limit}\"` : '';\n return [\n `<div class=\"vp-blog-facet vp-blog-facet--${facet}\" role=\"group\" aria-label=\"${escapeHtml(ariaLabel)}\" data-vp-blog-facet=\"${facet}\"${limited}${attributes}>`,\n `<span class=\"vp-blog-facet__label\" aria-hidden=\"true\">${escapeTemplateText(label)}</span>`,\n '<div class=\"vp-blog-facet__chips\">',\n '',\n chips.join(' '),\n '',\n '</div>',\n ...block('vp-blog-facet__aside', aside),\n '</div>',\n ];\n}\n\n/** Whether choosing one of `values` would narrow `total` posts: a single value on every post would not. */\nfunction canNarrow(values: FacetCount[], total: number): boolean {\n return values.length > 1 || (values.length === 1 && values[0].count < total);\n}\n\n/** Most used first, then alphabetically: the order a row's first chips are chosen in. */\nfunction byUse(a: FacetCount, b: FacetCount): number {\n return b.count - a.count || a.value.localeCompare(b.value, undefined, { sensitivity: 'base' });\n}\n\n/**\n * The browse panel above a blog page's posts: a row of categories, a row of\n * keywords, and an archive of years, each chip a filter with its post count.\n * Below the years, one row of months per year, which the client shows for the\n * year being viewed. A row is left out when it can't narrow the posts, such\n * as one category that every post is in; with only one year, the archive is\n * just that year's months. Categories and keywords come most used first, so\n * the first `categoryLimit` and `tagLimit` of them are the ones that show\n * before \"+N more\". The \"Subscribe\" link sits at the end of the first row.\n */\nfunction renderBrowse(posts: PostPage[], index: IndexPage, context: RenderContext): string[] {\n const { labels } = context;\n const { locale, timeZone } = context.dates;\n const { categoryList, categoryLimit, tagList, tagLimit, tagMinCount, archive: showArchive } = index.index;\n const blog = index.routePath;\n const rows: FacetRow[] = [];\n\n const categories = categoryList ? collectCategories(posts).sort(byUse) : [];\n if (canNarrow(categories, posts.length)) {\n const chips = categories.map(({ value, count }) => renderChip(value, count, toFilterUrl(blog, 'category', value)));\n rows.push({ facet: 'category', label: labels.categories, chips, limit: categoryLimit });\n }\n\n const tags = tagList\n ? collectTags(posts)\n .map(({ tag, count }) => ({ value: tag, count }))\n .filter(({ count }) => count >= tagMinCount)\n .sort(byUse)\n : [];\n if (canNarrow(tags, posts.length)) {\n const chips = tags.map(({ value, count }) => renderChip(value, count, toFilterUrl(blog, 'tag', value)));\n rows.push({ facet: 'tag', label: labels.tags, chips, limit: tagLimit });\n }\n\n const archive = showArchive ? collectArchive(posts, timeZone) : [];\n const monthChips = (months: FacetCount[]) =>\n months.map(({ value, count }) =>\n renderChip(\n formatMonth(value, locale, { month: 'short' }),\n count,\n toFilterUrl(blog, 'month', value),\n formatMonth(value, locale, { month: 'long', year: 'numeric' }),\n ),\n );\n if (archive.length === 1 && canNarrow(archive[0].months, posts.length)) {\n // One year: choosing it would narrow nothing, so the archive is its months.\n const [year] = archive;\n const ariaLabel = `${labels.archive} ${year.value}`;\n const attributes = ` data-year=\"${year.value}\"`;\n rows.push({ facet: 'month', label: labels.archive, chips: monthChips(year.months), ariaLabel, attributes });\n } else if (archive.length > 1) {\n const years = archive.map(({ value, count }) => renderChip(value, count, toFilterUrl(blog, 'year', value)));\n rows.push({ facet: 'year', label: labels.archive, chips: years });\n for (const year of archive) {\n const ariaLabel = `${labels.archive} ${year.value}`;\n const attributes = ` data-year=\"${year.value}\" hidden`;\n rows.push({ facet: 'month', label: year.value, chips: monthChips(year.months), ariaLabel, attributes });\n }\n }\n\n if (rows.length === 0) return [];\n rows[0].aside = renderRssLink(index, context);\n return [\n `<nav class=\"vp-blog-browse\" aria-label=\"${escapeHtml(labels.browse)}\">`,\n ...rows.flatMap(renderFacet),\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 // For writing a `?month=` filter's name: \"October 2026\".\n 'data-locale': context.locale,\n 'data-label-show-all': labels.showAll,\n 'data-label-previous': labels.previous,\n 'data-label-next': labels.next,\n 'data-label-pages': labels.pages,\n 'data-label-announcements': labels.announcements,\n 'data-label-category': labels.category,\n 'data-label-tag': labels.tag,\n 'data-label-post-count': labels.postCount,\n 'data-label-post-count-one': labels.postCountOne,\n 'data-label-no-matches': labels.noMatches,\n 'data-label-remove-filter': labels.removeFilter,\n 'data-label-more': labels.more,\n 'data-label-less': labels.less,\n 'data-label-filter': labels.filter,\n 'data-label-no-filter-matches': labels.noFilterMatches,\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 browse panel, the pinned\n * posts, then the rest (under year or month headings if the page asks\n * for it). Every post is in the HTML; `useBlog()` in the theme then filters\n * them by category, keyword and date and splits them into pages, using the\n * 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 // The \"Subscribe\" link goes in the browse panel's first row, or on its own line when there is no panel.\n const browse = renderBrowse(all, index, context);\n const lines = [\n `<div class=\"vp-blog\" data-vp-blog ${renderClientAttributes(index, context)}>`,\n ...(browse.length ? browse : block('vp-blog-subscribe', renderRssLink(index, context))),\n '<div class=\"vp-blog-filter\" data-vp-blog-filter role=\"status\" hidden></div>',\n ];\n if (pinned.length)\n lines.push(\n '<section class=\"vp-blog-pinned\" data-vp-blog-pinned>',\n ...renderGroup(pinned, index, context),\n '</section>',\n );\n if (rest.length) lines.push(...renderGroups(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, category and tags block under a post's title, and its\n * cover image. Its category and keywords link to `blogRoutePath` (the post's\n * 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 { category, tags } = post.post;\n const chips = [\n category &&\n `<span class=\"vp-blog-byline__category\">${renderFilterLink(category, 'category', blogRoutePath, 'vp-blog-category')}</span>`,\n tags.length && `<span class=\"vp-blog-byline__tags\">${renderTags(tags, blogRoutePath)}</span>`,\n ]\n .filter(Boolean)\n .join(' ');\n lines.push(...block('vp-blog-byline', [meta, chips].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', `![${escapeMarkdown(post.post.imageAlt)}](${toMarkdownUrl(image)})`));\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","/**\n * Dates and times from frontmatter. A post can give a date (`2026-10-20`), a\n * date and time (`2026-10-20 14:30` or `2026-10-20T14:30:00`), or either with\n * an offset (`2026-10-20T14:30:00-04:00`, `…Z`). A value without an offset is\n * read in the blog's time zone, so the same frontmatter means the same moment\n * on every build machine, and is shown in that time zone too.\n */\n\n/** A post's date and whether its frontmatter gave a time, so a date alone is never shown with \"12:00 AM\". */\nexport interface PostDate {\n date: Date;\n hasTime: boolean;\n}\n\n// `2026-10-20`, then an optional `T` or space and `HH:mm[:ss[.sss]]`, then an optional `Z` or `±HH[:]mm`.\nconst DATE_TIME =\n /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{1,3})\\d*)?)?)?\\s*(Z|[+-]\\d{2}:?\\d{2})?$/i;\n\n/** Whether `timeZone` is a time zone this runtime knows, such as `UTC` or `America/New_York`. */\nexport function isTimeZone(timeZone: string): boolean {\n try {\n new Intl.DateTimeFormat('en-US', { timeZone });\n return true;\n } catch {\n return false;\n }\n}\n\nconst warnedZones = new Set<string>();\n\n/** The `timeZone` option, or `UTC` (with a warning, once) when it isn't a time zone this runtime knows. */\nexport function resolveTimeZone(timeZone: string | undefined): string {\n if (!timeZone) return 'UTC';\n if (isTimeZone(timeZone)) return timeZone;\n if (!warnedZones.has(timeZone)) {\n warnedZones.add(timeZone);\n console.warn(\n `[vitepress-blog] \"${timeZone}\" is not a time zone; using UTC. Use an IANA name such as \"America/New_York\".`,\n );\n }\n return 'UTC';\n}\n\nconst offsetFormats = new Map<string, Intl.DateTimeFormat>();\n\n/** How far `timeZone`'s wall clock is ahead of UTC at `instant`, in milliseconds (negative west of UTC). */\nexport function zoneOffset(instant: number, timeZone: string): number {\n let format = offsetFormats.get(timeZone);\n if (!format) {\n format = new Intl.DateTimeFormat('en-US', {\n timeZone,\n hourCycle: 'h23',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n });\n offsetFormats.set(timeZone, format);\n }\n const parts = Object.fromEntries(format.formatToParts(instant).map(({ type, value }) => [type, Number(value)]));\n const wall = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);\n return wall - Math.floor(instant / 1000) * 1000;\n}\n\n/** The instant a wall-clock time in `timeZone` names, using the offset in force then (daylight saving included). */\nfunction fromZonedTime(wall: number, timeZone: string): number {\n const first = wall - zoneOffset(wall, timeZone);\n const second = wall - zoneOffset(first, timeZone);\n return second;\n}\n\n/**\n * Reads a frontmatter date. Strings in the forms above are read exactly; a\n * `Date` (as other YAML parsers produce) counts as having a time unless it\n * is midnight UTC. Anything else is `null`.\n */\nexport function parsePostDate(value: unknown, timeZone = 'UTC'): PostDate | null {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null;\n const midnight = value.getUTCHours() === 0 && value.getUTCMinutes() === 0 && value.getUTCSeconds() === 0;\n return { date: value, hasTime: !midnight };\n }\n if (typeof value !== 'string') return null;\n const match = DATE_TIME.exec(value.trim());\n if (!match) return null;\n const [, year, month, day, hour, minute, second = '0', millisecond = '0', offset] = match;\n const hasTime = hour !== undefined;\n const wall = Date.UTC(\n Number(year),\n Number(month) - 1,\n Number(day),\n hasTime ? Number(hour) : 0,\n hasTime ? Number(minute) : 0,\n Number(second),\n Number(millisecond.padEnd(3, '0')),\n );\n // Date.UTC rolls over impossible dates (2026-02-30), so check the date survived intact.\n const check = new Date(wall);\n if (check.getUTCMonth() !== Number(month) - 1 || check.getUTCDate() !== Number(day)) return null;\n\n if (!offset) return { date: new Date(fromZonedTime(wall, timeZone)), hasTime };\n if (offset.toUpperCase() === 'Z') return { date: new Date(wall), hasTime };\n const sign = offset.startsWith('-') ? -1 : 1;\n const digits = offset.slice(1).replace(':', '');\n const minutes = Number(digits.slice(0, 2)) * 60 + Number(digits.slice(2));\n return { date: new Date(wall - sign * minutes * 60_000), hasTime };\n}\n\nexport interface DateDisplay {\n locale: string;\n timeZone: string;\n dateFormat: Intl.DateTimeFormatOptions;\n timeFormat: Intl.DateTimeFormatOptions;\n /** How a date and a time are joined: `{date} at {time}`. */\n dateTime: string;\n}\n\n/** A post date for readers: the date, plus the time when the frontmatter gave one, in the blog's time zone. */\nexport function formatPostDate({ date, hasTime }: PostDate, display: DateDisplay): string {\n const { locale, timeZone } = display;\n const day = new Intl.DateTimeFormat(locale, { timeZone, ...display.dateFormat }).format(date);\n if (!hasTime) return day;\n const time = new Intl.DateTimeFormat(locale, { timeZone, ...display.timeFormat }).format(date);\n return display.dateTime.replace('{date}', day).replace('{time}', time);\n}\n\n/** The calendar date in `timeZone`, as `YYYY-MM-DD`. */\nexport function toZonedIsoDate(date: Date, timeZone: string): string {\n const parts = Object.fromEntries(\n new Intl.DateTimeFormat('en-US', { timeZone, year: 'numeric', month: '2-digit', day: '2-digit' })\n .formatToParts(date)\n .map(({ type, value }) => [type, value]),\n );\n return `${parts.year}-${parts.month}-${parts.day}`;\n}\n\n/** A `<time datetime>` value: the date alone, or the exact moment when there is a time. */\nexport function toDateTimeAttribute({ date, hasTime }: PostDate, timeZone: string): string {\n return hasTime ? date.toISOString() : toZonedIsoDate(date, timeZone);\n}\n\n/** The year a date falls in, in `timeZone`, for year headings. */\nexport function yearIn(date: Date, timeZone: string): number {\n return Number(toZonedIsoDate(date, timeZone).slice(0, 4));\n}\n\n/** The month a date falls in, in `timeZone`, as `YYYY-MM`: what month headings and `?month=` use. */\nexport function monthIn(date: Date, timeZone: string): string {\n return toZonedIsoDate(date, timeZone).slice(0, 7);\n}\n\n/** A `YYYY-MM` month for readers, in `locale`: \"October 2026\", or with `{ month: 'short' }`, \"Oct\". */\nexport function formatMonth(month: string, locale: string, format: Intl.DateTimeFormatOptions): string {\n const [year, number] = month.split('-').map(Number);\n // The first of the month at noon UTC, shown in UTC, is that month in every locale.\n return new Intl.DateTimeFormat(locale, { timeZone: 'UTC', ...format }).format(Date.UTC(year, number - 1, 1, 12));\n}\n","import path from 'node:path';\nimport { parse } from 'yaml';\nimport { parsePostDate, resolveTimeZone } from './dates.js';\nimport type {\n BlogAuthor,\n BlogFeedConfig,\n BlogGroupBy,\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/** How many categories a browse row shows before \"+N more\": fewer than keywords, as their names run longer. */\nconst DEFAULT_CATEGORY_LIMIT = 8;\n/** How many keywords a browse row shows before \"+N more\". */\nconst DEFAULT_TAG_LIMIT = 12;\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 * Reads a frontmatter date: `2026-10-20`, `2026-10-20 14:30`, or either with\n * an offset (`…T14:30:00-04:00`, `…Z`). A value without an offset is read in\n * `timeZone`. See `parsePostDate`.\n */\nexport function toDate(value: unknown, timeZone = 'UTC'): Date | null {\n return parsePostDate(value, timeZone)?.date ?? null;\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 const timeZone = resolveTimeZone(options.timeZone);\n const date = parsePostDate(pick('date'), timeZone);\n const updated = parsePostDate(pick('updated'), timeZone);\n const announcementText = typeof post.announcement === 'string' ? clean(post.announcement.trim()) : '';\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: date?.date ?? null,\n dateHasTime: date?.hasTime ?? false,\n updated: updated?.date ?? null,\n updatedHasTime: updated?.hasTime ?? false,\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` is this setting's old name.\n pinned: (post.pinned ?? post.sticky) === true,\n draft: post.draft === true,\n announcement: post.announcement === true || !!announcementText,\n announcementText,\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 toGroupBy(value: unknown): BlogGroupBy {\n return value === 'year' || value === 'month' ? value : '';\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`, `pinLimit` 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' | 'pinLimit' | '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 categories: toStringList(options.categories),\n limit: typeof options.limit === 'number' && options.limit > 0 ? Math.floor(options.limit) : 0,\n layout: toLayout(options.layout),\n groupBy: toGroupBy(options.groupBy),\n showTags: options.showTags !== false,\n showCategory: options.showCategory !== false,\n categoryList: options.categoryList !== false,\n categoryLimit: toCount(options.categoryLimit, DEFAULT_CATEGORY_LIMIT),\n tagList: options.tagList !== false,\n tagLimit: toCount(options.tagLimit, DEFAULT_TAG_LIMIT),\n tagMinCount: toCount(options.tagMinCount, 1),\n archive: options.archive !== false,\n pageSize: toCount(options.pageSize, toCount(defaults.pageSize, 10)),\n pinLimit: toCount(options.pinLimit ?? options.stickyLimit, toCount(defaults.pinLimit ?? 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 { 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.pinLimit ?? options.stickyLimit,\n options.rssLink,\n options.timeZone,\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 {\n findBlogForPage,\n hasAnyTag,\n inAnyCategory,\n isIndex,\n listBlogPosts,\n pinPosts,\n type IndexPage,\n} 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 pinLimit: number;\n layout: BlogLayout;\n showTags: boolean;\n showCategory: boolean;\n tags: string[];\n categories: 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. */\ntype InheritedSetting = 'pinLimit' | 'layout' | 'showTags' | 'showCategory' | 'rssLink';\nexport type WidgetSettings = Omit<WidgetConfig, InheritedSetting> & Partial<Pick<WidgetConfig, InheritedSetting>>;\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` is this setting's old name.\n pinLimit: toCount(data.pinLimit ?? data.stickyLimit),\n layout,\n showTags: typeof data.showTags === 'boolean' ? data.showTags : undefined,\n showCategory: typeof data.showCategory === 'boolean' ? data.showCategory : undefined,\n tags: toStringList(data.tags),\n categories: toStringList(data.categories),\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 pinLimit = config.pinLimit ?? blog.index.pinLimit;\n const posts = listBlogPosts(pages, blog, { drafts, pinnedFirst: false, applyLimit: false }).filter(\n (post) => hasAnyTag(post, config.tags) && inAnyCategory(post, config.categories),\n );\n const { pinned, rest } = pinPosts(posts, pinLimit);\n const shown = [...pinned, ...rest].slice(0, config.limit || undefined);\n\n // The block's own settings over the blog page's; category and keyword links still go 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 showCategory: config.showCategory ?? blog.index.showCategory,\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","import type { MarkdownIt, StateCore } from 'markdown-it';\nimport path from 'node:path';\nimport { findHomeBlog, isIndex, isPost, listBlogPosts, pinPosts } 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, pinnedFirst: true, applyLimit: true });\n const { pinned, rest } = pinPosts(posts, page.index.pinLimit);\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, pinnedFirst: 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"],"mappings":"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","names":["path","path","path","name","block","path","createHash","readFileSync","statSync","path","path","statSync","readFileSync","createHash","path","parse","toRecord","toCount","path","parse","path","FENCE","HEADING_1","path","warned"]}