@binarynoir/vitepress-blog 0.2.0 → 0.4.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.
- package/README.md +238 -88
- package/dist/chunk-42Z6H7IN.js +52 -0
- package/dist/chunk-42Z6H7IN.js.map +1 -0
- package/dist/{chunk-PWNWSYI4.js → chunk-ZM5FOUT4.js} +229 -106
- package/dist/chunk-ZM5FOUT4.js.map +1 -0
- package/dist/client.cjs +280 -64
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +56 -13
- package/dist/client.d.ts +56 -13
- package/dist/client.js +275 -62
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +205 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/{plugin-BlfpqyX4.d.cts → plugin-DUTUA_t4.d.cts} +73 -19
- package/dist/{plugin-BlfpqyX4.d.ts → plugin-DUTUA_t4.d.ts} +73 -19
- package/dist/style.css +270 -25
- package/dist/vitepress.cjs +341 -138
- package/dist/vitepress.cjs.map +1 -1
- package/dist/vitepress.d.cts +17 -7
- package/dist/vitepress.d.ts +17 -7
- package/dist/vitepress.js +76 -8
- package/dist/vitepress.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PWNWSYI4.js.map +0 -1
package/dist/vitepress.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
import { TransformContext, HeadConfig, PageData, TransformPageContext,
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
2
|
+
import { SiteConfig, TransformContext, HeadConfig, PageData, TransformPageContext, UserConfig } from 'vitepress';
|
|
3
|
+
import { h as BlogOptions } from './plugin-DUTUA_t4.cjs';
|
|
4
|
+
export { k as blogMarkdown } from './plugin-DUTUA_t4.cjs';
|
|
5
5
|
import 'markdown-it';
|
|
6
6
|
|
|
7
7
|
/** Where the site is published, for the absolute links feeds and social cards need. */
|
|
@@ -19,6 +19,13 @@ interface SiteLocation {
|
|
|
19
19
|
|
|
20
20
|
/** Where the site is published, or `null` without a site URL: feeds need absolute links. */
|
|
21
21
|
declare function resolveSiteLocation(siteConfig: SiteConfig, options: BlogOptions): SiteLocation | null;
|
|
22
|
+
/**
|
|
23
|
+
* Puts the announcement banner's data on VitePress's site data, where
|
|
24
|
+
* `useData().site` reads it on every page, and adds the `<head>` script that
|
|
25
|
+
* hides a dismissed banner before the page hydrates. Returns whether the
|
|
26
|
+
* data changed.
|
|
27
|
+
*/
|
|
28
|
+
declare function attachAnnouncements(siteConfig: SiteConfig, options?: BlogOptions): boolean;
|
|
22
29
|
/**
|
|
23
30
|
* `<head>` tags for blog pages: a feed link on each blog page, and on each
|
|
24
31
|
* post Open Graph and Twitter card tags (title, summary, image, dates) plus
|
|
@@ -57,12 +64,15 @@ declare function writeBlogFeeds(siteConfig: SiteConfig, options?: BlogOptions):
|
|
|
57
64
|
* cache) and reloads them when any of them changes.
|
|
58
65
|
* - Serves each blog's feed and the copied post images, which the build
|
|
59
66
|
* writes in `buildEnd`, so feed links work in development too.
|
|
67
|
+
* - Gives every page the announcement banner's data (see `attachAnnouncements`),
|
|
68
|
+
* kept current while posts change, and has Vite bundle this package for the
|
|
69
|
+
* server-side build, so the banner can read it there too.
|
|
60
70
|
*/
|
|
61
71
|
declare function blogVitePlugin(options?: BlogOptions): Plugin;
|
|
62
72
|
/**
|
|
63
|
-
* Wraps a VitePress config with the whole blog: the Markdown plugin, the
|
|
64
|
-
* server
|
|
65
|
-
* end of the build.
|
|
73
|
+
* Wraps a VitePress config with the whole blog: the Markdown plugin, the Vite
|
|
74
|
+
* plugin (dev server and announcements), `<head>` tags, descriptions from
|
|
75
|
+
* summaries, and feeds at the end of the build.
|
|
66
76
|
*
|
|
67
77
|
* ```ts
|
|
68
78
|
* // .vitepress/config.mts
|
|
@@ -78,4 +88,4 @@ declare function blogVitePlugin(options?: BlogOptions): Plugin;
|
|
|
78
88
|
*/
|
|
79
89
|
declare function withBlog(config: UserConfig, options?: BlogOptions): UserConfig;
|
|
80
90
|
|
|
81
|
-
export { BlogOptions, blogHead, blogPageData, blogVitePlugin, resolveSiteLocation, withBlog, writeBlogFeeds };
|
|
91
|
+
export { BlogOptions, attachAnnouncements, blogHead, blogPageData, blogVitePlugin, resolveSiteLocation, withBlog, writeBlogFeeds };
|
package/dist/vitepress.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
import { TransformContext, HeadConfig, PageData, TransformPageContext,
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
2
|
+
import { SiteConfig, TransformContext, HeadConfig, PageData, TransformPageContext, UserConfig } from 'vitepress';
|
|
3
|
+
import { h as BlogOptions } from './plugin-DUTUA_t4.js';
|
|
4
|
+
export { k as blogMarkdown } from './plugin-DUTUA_t4.js';
|
|
5
5
|
import 'markdown-it';
|
|
6
6
|
|
|
7
7
|
/** Where the site is published, for the absolute links feeds and social cards need. */
|
|
@@ -19,6 +19,13 @@ interface SiteLocation {
|
|
|
19
19
|
|
|
20
20
|
/** Where the site is published, or `null` without a site URL: feeds need absolute links. */
|
|
21
21
|
declare function resolveSiteLocation(siteConfig: SiteConfig, options: BlogOptions): SiteLocation | null;
|
|
22
|
+
/**
|
|
23
|
+
* Puts the announcement banner's data on VitePress's site data, where
|
|
24
|
+
* `useData().site` reads it on every page, and adds the `<head>` script that
|
|
25
|
+
* hides a dismissed banner before the page hydrates. Returns whether the
|
|
26
|
+
* data changed.
|
|
27
|
+
*/
|
|
28
|
+
declare function attachAnnouncements(siteConfig: SiteConfig, options?: BlogOptions): boolean;
|
|
22
29
|
/**
|
|
23
30
|
* `<head>` tags for blog pages: a feed link on each blog page, and on each
|
|
24
31
|
* post Open Graph and Twitter card tags (title, summary, image, dates) plus
|
|
@@ -57,12 +64,15 @@ declare function writeBlogFeeds(siteConfig: SiteConfig, options?: BlogOptions):
|
|
|
57
64
|
* cache) and reloads them when any of them changes.
|
|
58
65
|
* - Serves each blog's feed and the copied post images, which the build
|
|
59
66
|
* writes in `buildEnd`, so feed links work in development too.
|
|
67
|
+
* - Gives every page the announcement banner's data (see `attachAnnouncements`),
|
|
68
|
+
* kept current while posts change, and has Vite bundle this package for the
|
|
69
|
+
* server-side build, so the banner can read it there too.
|
|
60
70
|
*/
|
|
61
71
|
declare function blogVitePlugin(options?: BlogOptions): Plugin;
|
|
62
72
|
/**
|
|
63
|
-
* Wraps a VitePress config with the whole blog: the Markdown plugin, the
|
|
64
|
-
* server
|
|
65
|
-
* end of the build.
|
|
73
|
+
* Wraps a VitePress config with the whole blog: the Markdown plugin, the Vite
|
|
74
|
+
* plugin (dev server and announcements), `<head>` tags, descriptions from
|
|
75
|
+
* summaries, and feeds at the end of the build.
|
|
66
76
|
*
|
|
67
77
|
* ```ts
|
|
68
78
|
* // .vitepress/config.mts
|
|
@@ -78,4 +88,4 @@ declare function blogVitePlugin(options?: BlogOptions): Plugin;
|
|
|
78
88
|
*/
|
|
79
89
|
declare function withBlog(config: UserConfig, options?: BlogOptions): UserConfig;
|
|
80
90
|
|
|
81
|
-
export { BlogOptions, blogHead, blogPageData, blogVitePlugin, resolveSiteLocation, withBlog, writeBlogFeeds };
|
|
91
|
+
export { BlogOptions, attachAnnouncements, blogHead, blogPageData, blogVitePlugin, resolveSiteLocation, withBlog, writeBlogFeeds };
|
package/dist/vitepress.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_LABELS,
|
|
2
3
|
blogMarkdown,
|
|
4
|
+
findAnnouncementBlog,
|
|
3
5
|
findBlogsListing,
|
|
4
6
|
fingerprintBlogSource,
|
|
5
7
|
hasWidget,
|
|
6
8
|
isIndex,
|
|
7
9
|
isPost,
|
|
8
|
-
|
|
10
|
+
listAnnouncements,
|
|
9
11
|
listBlogPosts,
|
|
10
12
|
loadBlogPages,
|
|
11
13
|
mayHaveBlogFrontmatter,
|
|
@@ -14,12 +16,50 @@ import {
|
|
|
14
16
|
toFeedPath,
|
|
15
17
|
toPageUrl,
|
|
16
18
|
widgetPages
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ZM5FOUT4.js";
|
|
20
|
+
import {
|
|
21
|
+
ANNOUNCEMENTS_KEY,
|
|
22
|
+
DISMISSED_CLASS,
|
|
23
|
+
DISMISSED_STORAGE_KEY,
|
|
24
|
+
joinUrl,
|
|
25
|
+
toUrlPath
|
|
26
|
+
} from "./chunk-42Z6H7IN.js";
|
|
18
27
|
|
|
19
28
|
// src/vitepress.ts
|
|
20
29
|
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
21
30
|
import path from "path";
|
|
31
|
+
|
|
32
|
+
// src/announcements.ts
|
|
33
|
+
function resolveAnnouncements(pages, options, cleanUrls) {
|
|
34
|
+
const drafts = options.drafts ?? false;
|
|
35
|
+
const announcements = listAnnouncements(pages, drafts);
|
|
36
|
+
if (announcements.length === 0) return null;
|
|
37
|
+
const blog = findAnnouncementBlog(pages, announcements, drafts);
|
|
38
|
+
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
39
|
+
return {
|
|
40
|
+
items: announcements.map((post) => ({
|
|
41
|
+
id: `${post.routePath}@${post.post.date?.toISOString() ?? ""}`,
|
|
42
|
+
text: post.post.announcementText || post.post.title,
|
|
43
|
+
link: toUrlPath(post.routePath, cleanUrls)
|
|
44
|
+
})),
|
|
45
|
+
listLink: blog ? `${toUrlPath(blog.routePath, cleanUrls)}?announcements` : "",
|
|
46
|
+
labels: {
|
|
47
|
+
announcement: labels.announcement,
|
|
48
|
+
announcementCount: labels.announcementCount,
|
|
49
|
+
dismiss: labels.dismiss
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function renderDismissedScript(data) {
|
|
54
|
+
const id = JSON.stringify(data.items[0].id).replace(/</g, "\\u003c");
|
|
55
|
+
return `try{var d=JSON.parse(localStorage.getItem(${JSON.stringify(DISMISSED_STORAGE_KEY)})||'[]');if(Array.isArray(d)&&d.indexOf(${id})>-1)document.documentElement.classList.add(${JSON.stringify(DISMISSED_CLASS)})}catch(e){}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/vitepress.ts
|
|
22
59
|
var DEFAULT_FEED_LIMIT = 20;
|
|
60
|
+
var PACKAGE_NAME = "@binarynoir/vitepress-blog";
|
|
61
|
+
var SITE_DATA_MODULE = "/@siteData";
|
|
62
|
+
var SCRIPT_MARKER = "data-vp-blog-announcement";
|
|
23
63
|
function resolveSiteLocation(siteConfig, options) {
|
|
24
64
|
const siteUrl = options.siteUrl ?? siteConfig.userConfig?.sitemap?.hostname ?? siteConfig.sitemap?.hostname;
|
|
25
65
|
if (!siteUrl) return null;
|
|
@@ -34,9 +74,19 @@ function resolveSiteLocation(siteConfig, options) {
|
|
|
34
74
|
function loadPages(siteConfig, options) {
|
|
35
75
|
return loadBlogPages(siteConfig.srcDir, options, siteConfig);
|
|
36
76
|
}
|
|
77
|
+
function attachAnnouncements(siteConfig, options = {}) {
|
|
78
|
+
const site = siteConfig.site;
|
|
79
|
+
const data = resolveAnnouncements(loadPages(siteConfig, options), options, !!siteConfig.cleanUrls);
|
|
80
|
+
const changed = JSON.stringify(site[ANNOUNCEMENTS_KEY] ?? null) !== JSON.stringify(data);
|
|
81
|
+
if (data) site[ANNOUNCEMENTS_KEY] = data;
|
|
82
|
+
else delete site[ANNOUNCEMENTS_KEY];
|
|
83
|
+
site.head = (site.head ?? []).filter(([, attributes]) => !(SCRIPT_MARKER in (attributes ?? {})));
|
|
84
|
+
if (data) site.head.push(["script", { [SCRIPT_MARKER]: "" }, renderDismissedScript(data)]);
|
|
85
|
+
return changed;
|
|
86
|
+
}
|
|
37
87
|
function listFeedPosts(pages, index, options) {
|
|
38
88
|
const limit = index.index.feed?.limit ?? options.feedLimit ?? DEFAULT_FEED_LIMIT;
|
|
39
|
-
return listBlogPosts(pages, index, { drafts: false,
|
|
89
|
+
return listBlogPosts(pages, index, { drafts: false, pinnedFirst: false, applyLimit: false }).slice(0, limit);
|
|
40
90
|
}
|
|
41
91
|
function renderIndexFeed(pages, index, site, siteConfig, options) {
|
|
42
92
|
return renderFeed({
|
|
@@ -138,11 +188,26 @@ function blogVitePlugin(options = {}) {
|
|
|
138
188
|
let siteConfig;
|
|
139
189
|
const currentPages = () => siteConfig ? loadPages(siteConfig, options) : [];
|
|
140
190
|
const dependentFiles = () => [...currentPages().map((page) => page.absolutePath), ...widgetPages];
|
|
191
|
+
const invalidate = (server, files) => {
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
for (const module of server.moduleGraph.getModulesByFile(file) ?? []) server.moduleGraph.invalidateModule(module);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const refreshAnnouncements = (server) => {
|
|
197
|
+
if (!siteConfig || !attachAnnouncements(siteConfig, options)) return false;
|
|
198
|
+
const module = server.moduleGraph.getModuleById(SITE_DATA_MODULE);
|
|
199
|
+
if (module) server.moduleGraph.invalidateModule(module);
|
|
200
|
+
return true;
|
|
201
|
+
};
|
|
141
202
|
return {
|
|
142
203
|
name: "vitepress-blog",
|
|
143
204
|
enforce: "pre",
|
|
205
|
+
config() {
|
|
206
|
+
return { ssr: { noExternal: [PACKAGE_NAME] } };
|
|
207
|
+
},
|
|
144
208
|
configResolved(config) {
|
|
145
209
|
siteConfig = config.vitepress;
|
|
210
|
+
if (siteConfig) attachAnnouncements(siteConfig, options);
|
|
146
211
|
},
|
|
147
212
|
transform(code, id) {
|
|
148
213
|
if (!siteConfig || !id.endsWith(".md") || !(mayHaveBlogFrontmatter(code) || hasWidget(code))) return null;
|
|
@@ -153,11 +218,8 @@ function blogVitePlugin(options = {}) {
|
|
|
153
218
|
configureServer(server) {
|
|
154
219
|
const refresh = (file) => {
|
|
155
220
|
if (!siteConfig || !file.endsWith(".md")) return;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
server.moduleGraph.invalidateModule(module);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
221
|
+
invalidate(server, dependentFiles());
|
|
222
|
+
refreshAnnouncements(server);
|
|
161
223
|
server.ws.send({ type: "full-reload" });
|
|
162
224
|
};
|
|
163
225
|
server.watcher.on("add", refresh).on("unlink", refresh);
|
|
@@ -188,6 +250,11 @@ function blogVitePlugin(options = {}) {
|
|
|
188
250
|
},
|
|
189
251
|
handleHotUpdate({ file, modules, server }) {
|
|
190
252
|
if (!siteConfig || !file.endsWith(".md")) return;
|
|
253
|
+
if (refreshAnnouncements(server)) {
|
|
254
|
+
invalidate(server, dependentFiles());
|
|
255
|
+
server.ws.send({ type: "full-reload" });
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
191
258
|
const pages = currentPages();
|
|
192
259
|
if (!pages.some((page) => page.absolutePath === file)) return;
|
|
193
260
|
const affected = dependentFiles().filter((dependent) => dependent !== file).flatMap((dependent) => [...server.moduleGraph.getModulesByFile(dependent) ?? []]);
|
|
@@ -223,6 +290,7 @@ function withBlog(config, options = {}) {
|
|
|
223
290
|
return config;
|
|
224
291
|
}
|
|
225
292
|
export {
|
|
293
|
+
attachAnnouncements,
|
|
226
294
|
blogHead,
|
|
227
295
|
blogMarkdown,
|
|
228
296
|
blogPageData,
|
package/dist/vitepress.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vitepress.ts"],"sourcesContent":["import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport type { MarkdownIt } from 'markdown-it';\nimport type { Plugin } from 'vite';\nimport type { HeadConfig, PageData, SiteConfig, TransformContext, TransformPageContext, UserConfig } from 'vitepress';\nimport { findBlogsListing, isIndex, isPost, listBlogPosts, type IndexPage, type PostPage } from './collect.js';\nimport { mayHaveBlogFrontmatter } from './frontmatter.js';\nimport { renderFeed, resolvePostImage, toFeedPath, toPageUrl, type SiteLocation } from './feed.js';\nimport { blogMarkdown } from './plugin.js';\nimport { hasWidget, widgetPages } from './widget.js';\nimport { fingerprintBlogSource, loadBlogPages, type BlogPage } from './scan.js';\nimport type { BlogOptions } from './types.js';\nimport { joinUrl } from './util.js';\n\nconst DEFAULT_FEED_LIMIT = 20;\n\n/** Where the site is published, or `null` without a site URL: feeds need absolute links. */\nexport function resolveSiteLocation(siteConfig: SiteConfig, options: BlogOptions): SiteLocation | null {\n const siteUrl = options.siteUrl ?? siteConfig.userConfig?.sitemap?.hostname ?? siteConfig.sitemap?.hostname;\n if (!siteUrl) return null;\n return {\n siteUrl: siteUrl.replace(/\\/+$/, ''),\n base: siteConfig.site?.base ?? '/',\n cleanUrls: !!siteConfig.cleanUrls,\n publicDir: siteConfig.publicDir ?? path.join(siteConfig.srcDir, 'public'),\n imagesDir: (options.imagesDir ?? 'blog-images').replace(/^\\/+|\\/+$/g, ''),\n };\n}\n\nfunction loadPages(siteConfig: SiteConfig, options: BlogOptions): BlogPage[] {\n return loadBlogPages(siteConfig.srcDir, options, siteConfig);\n}\n\n/** The posts in a blog's feed: no drafts, newest first, up to the feed's limit. */\nfunction listFeedPosts(pages: BlogPage[], index: IndexPage, options: BlogOptions): PostPage[] {\n const limit = index.index.feed?.limit ?? options.feedLimit ?? DEFAULT_FEED_LIMIT;\n return listBlogPosts(pages, index, { drafts: false, stickyFirst: false, applyLimit: false }).slice(0, limit);\n}\n\nfunction renderIndexFeed(\n pages: BlogPage[],\n index: IndexPage,\n site: SiteLocation,\n siteConfig: SiteConfig,\n options: BlogOptions,\n) {\n return renderFeed({\n index,\n posts: listFeedPosts(pages, index, options),\n site,\n language: siteConfig.site?.lang,\n siteDescription: siteConfig.site?.description,\n });\n}\n\nfunction feedLink(index: IndexPage, base: string): HeadConfig {\n const href = joinUrl(base, toFeedPath(index));\n return [\n 'link',\n { rel: 'alternate', type: 'application/rss+xml', title: index.index.feed?.title ?? index.title, href },\n ];\n}\n\n/**\n * `<head>` tags for blog pages: a feed link on each blog page, and on each\n * post Open Graph and Twitter card tags (title, summary, image, dates) plus\n * links to the feeds that carry it. Use it from VitePress's `transformHead`:\n *\n * ```ts\n * transformHead: (context) => blogHead(context, options),\n * ```\n *\n * Social tags need absolute URLs, so `og:url` and `og:image` are left out\n * without a site URL (the `siteUrl` option or `sitemap.hostname`).\n */\nexport function blogHead(context: TransformContext, options: BlogOptions = {}): HeadConfig[] {\n const { pageData, siteConfig } = context;\n const frontmatter = pageData.frontmatter ?? {};\n if (!pageData.filePath || (!frontmatter.blog && !frontmatter['blog-index'])) return [];\n const pages = loadPages(siteConfig, options);\n const page = pages.find((candidate) => candidate.relativePath === pageData.filePath);\n if (!page) return [];\n const site = resolveSiteLocation(siteConfig, options);\n const base = siteConfig.site?.base ?? '/';\n const head: HeadConfig[] = [];\n\n if (isIndex(page)) {\n if (page.index.feed) head.push(feedLink(page, base));\n if (site) head.push(['meta', { property: 'og:url', content: toPageUrl(page.routePath, site) }]);\n return head;\n }\n if (!isPost(page)) return head;\n\n const { post } = page;\n head.push(\n ['meta', { property: 'og:type', content: 'article' }],\n ['meta', { property: 'og:title', content: post.title }],\n );\n if (post.summary) head.push(['meta', { property: 'og:description', content: post.summary }]);\n if (site) head.push(['meta', { property: 'og:url', content: toPageUrl(page.routePath, site) }]);\n\n const image = site ? resolvePostImage(page, site) : null;\n if (image) {\n head.push(['meta', { property: 'og:image', content: image.url }]);\n if (post.imageAlt) head.push(['meta', { property: 'og:image:alt', content: post.imageAlt }]);\n }\n head.push(['meta', { name: 'twitter:card', content: image ? 'summary_large_image' : 'summary' }]);\n if (post.date) head.push(['meta', { property: 'article:published_time', content: post.date.toISOString() }]);\n if (post.updated) head.push(['meta', { property: 'article:modified_time', content: post.updated.toISOString() }]);\n if (post.authors.length) {\n head.push(['meta', { property: 'article:author', content: post.authors.map((author) => author.name).join(', ') }]);\n }\n if (post.category) head.push(['meta', { property: 'article:section', content: post.category }]);\n if (!post.draft) {\n for (const blog of findBlogsListing(pages, page)) if (blog.index.feed) head.push(feedLink(blog, base));\n }\n return head;\n}\n\n/**\n * Gives a post without a `description` its summary (or the first paragraph,\n * when it has no summary) as one, so VitePress writes it into the page's\n * `<meta name=\"description\">`. Use it from `transformPageData`:\n *\n * ```ts\n * transformPageData: (pageData, context) => blogPageData(pageData, context, options),\n * ```\n */\nexport function blogPageData(pageData: PageData, context: TransformPageContext, options: BlogOptions = {}): void {\n if (!pageData.frontmatter?.blog || pageData.description || !pageData.filePath) return;\n const page = loadPages(context.siteConfig, options).find((candidate) => candidate.relativePath === pageData.filePath);\n if (page?.post?.summary) pageData.description = page.post.summary;\n}\n\n/**\n * Writes an RSS feed for every blog page whose feed isn't turned off, and\n * copies post images to `imagesDir` for the feeds and social cards to link.\n * Use it from VitePress's `buildEnd`.\n */\nexport function writeBlogFeeds(siteConfig: SiteConfig, options: BlogOptions = {}): void {\n const pages = loadPages(siteConfig, options);\n const indexes = pages.filter(isIndex).filter((index) => index.index.feed);\n const site = resolveSiteLocation(siteConfig, options);\n if (!site) {\n if (indexes.length) {\n siteConfig.logger?.warn(\n '[vitepress-blog] no feeds written: set the siteUrl option or sitemap.hostname so feeds can use absolute links.',\n );\n }\n return;\n }\n\n const written = new Map<string, string>();\n for (const index of indexes) {\n const feedPath = toFeedPath(index);\n if (written.has(feedPath)) {\n siteConfig.logger?.warn(\n `[vitepress-blog] ${index.relativePath} and ${written.get(feedPath)} both write ${feedPath}; set feed.path on one of them.`,\n );\n continue;\n }\n written.set(feedPath, index.relativePath);\n const file = path.join(siteConfig.outDir, feedPath);\n mkdirSync(path.dirname(file), { recursive: true });\n writeFileSync(file, renderIndexFeed(pages, index, site, siteConfig, options));\n }\n\n for (const post of pages.filter(isPost)) {\n if (post.post.draft) continue;\n const image = resolvePostImage(post, site);\n if (!image?.file || !image.outputPath) continue;\n const target = path.join(siteConfig.outDir, image.outputPath);\n mkdirSync(path.dirname(target), { recursive: true });\n copyFileSync(image.file, target);\n }\n}\n\n/**\n * The Vite side of the blog, for `vitepress dev`:\n *\n * - VitePress caches a page's rendered output by its own source, so a new or\n * edited post would leave blog lists and \"newer / older\" links stale. This\n * adds a fingerprint of every post to blog pages and posts (busting the\n * cache) and reloads them when any of them changes.\n * - Serves each blog's feed and the copied post images, which the build\n * writes in `buildEnd`, so feed links work in development too.\n */\nexport function blogVitePlugin(options: BlogOptions = {}): Plugin {\n let siteConfig: SiteConfig | undefined;\n const currentPages = () => (siteConfig ? loadPages(siteConfig, options) : []);\n // Every file whose output shows posts: blog pages, posts, and pages with a `::: blog-posts` block.\n const dependentFiles = () => [...currentPages().map((page) => page.absolutePath), ...widgetPages];\n\n return {\n name: 'vitepress-blog',\n enforce: 'pre',\n configResolved(config) {\n siteConfig = (config as { vitepress?: SiteConfig }).vitepress;\n },\n transform(code, id) {\n if (!siteConfig || !id.endsWith('.md') || !(mayHaveBlogFrontmatter(code) || hasWidget(code))) return null;\n return { code: `${code}\\n<!-- vitepress-blog:${fingerprintBlogSource(currentPages())} -->\\n`, map: null };\n },\n configureServer(server) {\n // Vite only calls handleHotUpdate for edits; a post added or deleted changes the lists too.\n const refresh = (file: string) => {\n if (!siteConfig || !file.endsWith('.md')) return;\n for (const dependent of dependentFiles()) {\n for (const module of server.moduleGraph.getModulesByFile(dependent) ?? []) {\n server.moduleGraph.invalidateModule(module);\n }\n }\n server.ws.send({ type: 'full-reload' });\n };\n server.watcher.on('add', refresh).on('unlink', refresh);\n\n server.middlewares.use((request, response, next) => {\n if (!siteConfig || !request.url) return next();\n const base = siteConfig.site?.base ?? '/';\n const pathname = decodeURIComponent(request.url.split('?')[0]);\n if (!pathname.startsWith(base)) return next();\n const relative = pathname.slice(base.length);\n const pages = currentPages();\n // Feeds link to the live site, or to the dev server without a site URL.\n const site =\n resolveSiteLocation(siteConfig, options) ??\n resolveSiteLocation(siteConfig, { ...options, siteUrl: `http://${request.headers.host ?? 'localhost'}` })!;\n\n const index = pages.filter(isIndex).find((page) => page.index.feed && toFeedPath(page) === relative);\n if (index) {\n response.setHeader('Content-Type', 'application/rss+xml; charset=utf-8');\n response.end(renderIndexFeed(pages, index, site, siteConfig, options));\n return;\n }\n for (const post of pages.filter(isPost)) {\n const image = resolvePostImage(post, site);\n if (image?.outputPath === relative && image.file) {\n if (image.type) response.setHeader('Content-Type', image.type);\n response.end(readFileSync(image.file));\n return;\n }\n }\n next();\n });\n },\n handleHotUpdate({ file, modules, server }) {\n if (!siteConfig || !file.endsWith('.md')) return;\n const pages = currentPages();\n if (!pages.some((page) => page.absolutePath === file)) return;\n const affected = dependentFiles()\n .filter((dependent) => dependent !== file)\n .flatMap((dependent) => [...(server.moduleGraph.getModulesByFile(dependent) ?? [])]);\n return affected.length ? [...modules, ...affected] : undefined;\n },\n };\n}\n\n/**\n * Wraps a VitePress config with the whole blog: the Markdown plugin, the dev\n * server plugin, `<head>` tags, descriptions from summaries, and feeds at the\n * end of the build.\n *\n * ```ts\n * // .vitepress/config.mts\n * import { defineConfig } from 'vitepress';\n * import { withBlog } from '@binarynoir/vitepress-blog/vitepress';\n *\n * export default withBlog(defineConfig({ /* ...your config... *\\/ }), { siteUrl: 'https://example.com' });\n * ```\n *\n * Hooks you already have (`markdown.config`, `transformHead`,\n * `transformPageData`, `buildEnd`) are not replaced: the blog's runs and\n * then yours, so it composes with other `withX()` wrappers in any order.\n */\nexport function withBlog(config: UserConfig, options: BlogOptions = {}): UserConfig {\n config.markdown ??= {};\n const existingMarkdownConfig = config.markdown.config ?? (() => {});\n config.markdown.config = (md) => {\n // VitePress 2 types this callback against `markdown-it-async`'s MarkdownIt,\n // a structural superset of plain markdown-it's; the cast bridges the two.\n blogMarkdown(md as unknown as MarkdownIt, options);\n existingMarkdownConfig(md);\n };\n\n config.vite ??= {};\n config.vite.plugins = [...(config.vite.plugins ?? []), blogVitePlugin(options)];\n\n const existingTransformHead = config.transformHead;\n config.transformHead = async (context) => [\n ...blogHead(context, options),\n ...((await existingTransformHead?.(context)) ?? []),\n ];\n\n const existingTransformPageData = config.transformPageData;\n config.transformPageData = (pageData, context) => {\n blogPageData(pageData, context, options);\n return existingTransformPageData?.(pageData, context);\n };\n\n const existingBuildEnd = config.buildEnd;\n config.buildEnd = async (siteConfig) => {\n writeBlogFeeds(siteConfig, options);\n await existingBuildEnd?.(siteConfig);\n };\n\n return config;\n}\n\nexport { blogMarkdown };\nexport type { BlogOptions };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/vitepress.ts","../src/announcements.ts"],"sourcesContent":["import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport type { MarkdownIt } from 'markdown-it';\nimport type { Plugin, ViteDevServer } from 'vite';\nimport type { HeadConfig, PageData, SiteConfig, TransformContext, TransformPageContext, UserConfig } from 'vitepress';\nimport { renderDismissedScript, resolveAnnouncements } from './announcements.js';\nimport { findBlogsListing, isIndex, isPost, listBlogPosts, type IndexPage, type PostPage } from './collect.js';\nimport { mayHaveBlogFrontmatter } from './frontmatter.js';\nimport { renderFeed, resolvePostImage, toFeedPath, toPageUrl, type SiteLocation } from './feed.js';\nimport { blogMarkdown } from './plugin.js';\nimport { hasWidget, widgetPages } from './widget.js';\nimport { fingerprintBlogSource, loadBlogPages, type BlogPage } from './scan.js';\nimport type { BlogOptions } from './types.js';\nimport { ANNOUNCEMENTS_KEY, joinUrl } from './util.js';\n\nconst DEFAULT_FEED_LIMIT = 20;\nconst PACKAGE_NAME = '@binarynoir/vitepress-blog';\n// VitePress's id for the module that serves the site data to the browser.\nconst SITE_DATA_MODULE = '/@siteData';\n// Marks the `<head>` script this adds, so it is replaced rather than added twice.\nconst SCRIPT_MARKER = 'data-vp-blog-announcement';\n\n/** Where the site is published, or `null` without a site URL: feeds need absolute links. */\nexport function resolveSiteLocation(siteConfig: SiteConfig, options: BlogOptions): SiteLocation | null {\n const siteUrl = options.siteUrl ?? siteConfig.userConfig?.sitemap?.hostname ?? siteConfig.sitemap?.hostname;\n if (!siteUrl) return null;\n return {\n siteUrl: siteUrl.replace(/\\/+$/, ''),\n base: siteConfig.site?.base ?? '/',\n cleanUrls: !!siteConfig.cleanUrls,\n publicDir: siteConfig.publicDir ?? path.join(siteConfig.srcDir, 'public'),\n imagesDir: (options.imagesDir ?? 'blog-images').replace(/^\\/+|\\/+$/g, ''),\n };\n}\n\nfunction loadPages(siteConfig: SiteConfig, options: BlogOptions): BlogPage[] {\n return loadBlogPages(siteConfig.srcDir, options, siteConfig);\n}\n\n/**\n * Puts the announcement banner's data on VitePress's site data, where\n * `useData().site` reads it on every page, and adds the `<head>` script that\n * hides a dismissed banner before the page hydrates. Returns whether the\n * data changed.\n */\nexport function attachAnnouncements(siteConfig: SiteConfig, options: BlogOptions = {}): boolean {\n const site = siteConfig.site as SiteConfig['site'] & Record<string, unknown>;\n const data = resolveAnnouncements(loadPages(siteConfig, options), options, !!siteConfig.cleanUrls);\n const changed = JSON.stringify(site[ANNOUNCEMENTS_KEY] ?? null) !== JSON.stringify(data);\n if (data) site[ANNOUNCEMENTS_KEY] = data;\n else delete site[ANNOUNCEMENTS_KEY];\n site.head = (site.head ?? []).filter(([, attributes]) => !(SCRIPT_MARKER in (attributes ?? {})));\n if (data) site.head.push(['script', { [SCRIPT_MARKER]: '' }, renderDismissedScript(data)]);\n return changed;\n}\n\n/** The posts in a blog's feed: no drafts, newest first, up to the feed's limit. */\nfunction listFeedPosts(pages: BlogPage[], index: IndexPage, options: BlogOptions): PostPage[] {\n const limit = index.index.feed?.limit ?? options.feedLimit ?? DEFAULT_FEED_LIMIT;\n return listBlogPosts(pages, index, { drafts: false, pinnedFirst: false, applyLimit: false }).slice(0, limit);\n}\n\nfunction renderIndexFeed(\n pages: BlogPage[],\n index: IndexPage,\n site: SiteLocation,\n siteConfig: SiteConfig,\n options: BlogOptions,\n) {\n return renderFeed({\n index,\n posts: listFeedPosts(pages, index, options),\n site,\n language: siteConfig.site?.lang,\n siteDescription: siteConfig.site?.description,\n });\n}\n\nfunction feedLink(index: IndexPage, base: string): HeadConfig {\n const href = joinUrl(base, toFeedPath(index));\n return [\n 'link',\n { rel: 'alternate', type: 'application/rss+xml', title: index.index.feed?.title ?? index.title, href },\n ];\n}\n\n/**\n * `<head>` tags for blog pages: a feed link on each blog page, and on each\n * post Open Graph and Twitter card tags (title, summary, image, dates) plus\n * links to the feeds that carry it. Use it from VitePress's `transformHead`:\n *\n * ```ts\n * transformHead: (context) => blogHead(context, options),\n * ```\n *\n * Social tags need absolute URLs, so `og:url` and `og:image` are left out\n * without a site URL (the `siteUrl` option or `sitemap.hostname`).\n */\nexport function blogHead(context: TransformContext, options: BlogOptions = {}): HeadConfig[] {\n const { pageData, siteConfig } = context;\n const frontmatter = pageData.frontmatter ?? {};\n if (!pageData.filePath || (!frontmatter.blog && !frontmatter['blog-index'])) return [];\n const pages = loadPages(siteConfig, options);\n const page = pages.find((candidate) => candidate.relativePath === pageData.filePath);\n if (!page) return [];\n const site = resolveSiteLocation(siteConfig, options);\n const base = siteConfig.site?.base ?? '/';\n const head: HeadConfig[] = [];\n\n if (isIndex(page)) {\n if (page.index.feed) head.push(feedLink(page, base));\n if (site) head.push(['meta', { property: 'og:url', content: toPageUrl(page.routePath, site) }]);\n return head;\n }\n if (!isPost(page)) return head;\n\n const { post } = page;\n head.push(\n ['meta', { property: 'og:type', content: 'article' }],\n ['meta', { property: 'og:title', content: post.title }],\n );\n if (post.summary) head.push(['meta', { property: 'og:description', content: post.summary }]);\n if (site) head.push(['meta', { property: 'og:url', content: toPageUrl(page.routePath, site) }]);\n\n const image = site ? resolvePostImage(page, site) : null;\n if (image) {\n head.push(['meta', { property: 'og:image', content: image.url }]);\n if (post.imageAlt) head.push(['meta', { property: 'og:image:alt', content: post.imageAlt }]);\n }\n head.push(['meta', { name: 'twitter:card', content: image ? 'summary_large_image' : 'summary' }]);\n if (post.date) head.push(['meta', { property: 'article:published_time', content: post.date.toISOString() }]);\n if (post.updated) head.push(['meta', { property: 'article:modified_time', content: post.updated.toISOString() }]);\n if (post.authors.length) {\n head.push(['meta', { property: 'article:author', content: post.authors.map((author) => author.name).join(', ') }]);\n }\n if (post.category) head.push(['meta', { property: 'article:section', content: post.category }]);\n if (!post.draft) {\n for (const blog of findBlogsListing(pages, page)) if (blog.index.feed) head.push(feedLink(blog, base));\n }\n return head;\n}\n\n/**\n * Gives a post without a `description` its summary (or the first paragraph,\n * when it has no summary) as one, so VitePress writes it into the page's\n * `<meta name=\"description\">`. Use it from `transformPageData`:\n *\n * ```ts\n * transformPageData: (pageData, context) => blogPageData(pageData, context, options),\n * ```\n */\nexport function blogPageData(pageData: PageData, context: TransformPageContext, options: BlogOptions = {}): void {\n if (!pageData.frontmatter?.blog || pageData.description || !pageData.filePath) return;\n const page = loadPages(context.siteConfig, options).find((candidate) => candidate.relativePath === pageData.filePath);\n if (page?.post?.summary) pageData.description = page.post.summary;\n}\n\n/**\n * Writes an RSS feed for every blog page whose feed isn't turned off, and\n * copies post images to `imagesDir` for the feeds and social cards to link.\n * Use it from VitePress's `buildEnd`.\n */\nexport function writeBlogFeeds(siteConfig: SiteConfig, options: BlogOptions = {}): void {\n const pages = loadPages(siteConfig, options);\n const indexes = pages.filter(isIndex).filter((index) => index.index.feed);\n const site = resolveSiteLocation(siteConfig, options);\n if (!site) {\n if (indexes.length) {\n siteConfig.logger?.warn(\n '[vitepress-blog] no feeds written: set the siteUrl option or sitemap.hostname so feeds can use absolute links.',\n );\n }\n return;\n }\n\n const written = new Map<string, string>();\n for (const index of indexes) {\n const feedPath = toFeedPath(index);\n if (written.has(feedPath)) {\n siteConfig.logger?.warn(\n `[vitepress-blog] ${index.relativePath} and ${written.get(feedPath)} both write ${feedPath}; set feed.path on one of them.`,\n );\n continue;\n }\n written.set(feedPath, index.relativePath);\n const file = path.join(siteConfig.outDir, feedPath);\n mkdirSync(path.dirname(file), { recursive: true });\n writeFileSync(file, renderIndexFeed(pages, index, site, siteConfig, options));\n }\n\n for (const post of pages.filter(isPost)) {\n if (post.post.draft) continue;\n const image = resolvePostImage(post, site);\n if (!image?.file || !image.outputPath) continue;\n const target = path.join(siteConfig.outDir, image.outputPath);\n mkdirSync(path.dirname(target), { recursive: true });\n copyFileSync(image.file, target);\n }\n}\n\n/**\n * The Vite side of the blog, for `vitepress dev`:\n *\n * - VitePress caches a page's rendered output by its own source, so a new or\n * edited post would leave blog lists and \"newer / older\" links stale. This\n * adds a fingerprint of every post to blog pages and posts (busting the\n * cache) and reloads them when any of them changes.\n * - Serves each blog's feed and the copied post images, which the build\n * writes in `buildEnd`, so feed links work in development too.\n * - Gives every page the announcement banner's data (see `attachAnnouncements`),\n * kept current while posts change, and has Vite bundle this package for the\n * server-side build, so the banner can read it there too.\n */\nexport function blogVitePlugin(options: BlogOptions = {}): Plugin {\n let siteConfig: SiteConfig | undefined;\n const currentPages = () => (siteConfig ? loadPages(siteConfig, options) : []);\n // Every file whose output shows posts: blog pages, posts, and pages with a `::: blog-posts` block.\n const dependentFiles = () => [...currentPages().map((page) => page.absolutePath), ...widgetPages];\n const invalidate = (server: ViteDevServer, files: string[]) => {\n for (const file of files) {\n for (const module of server.moduleGraph.getModulesByFile(file) ?? []) server.moduleGraph.invalidateModule(module);\n }\n };\n /** Re-reads the announcements; when they changed, the site data is served afresh. */\n const refreshAnnouncements = (server: ViteDevServer): boolean => {\n if (!siteConfig || !attachAnnouncements(siteConfig, options)) return false;\n const module = server.moduleGraph.getModuleById(SITE_DATA_MODULE);\n if (module) server.moduleGraph.invalidateModule(module);\n return true;\n };\n\n return {\n name: 'vitepress-blog',\n enforce: 'pre',\n config() {\n // Bundled rather than loaded by Node, the client entry gets VitePress's\n // client (`useData`) on the server too, so the banner renders there.\n return { ssr: { noExternal: [PACKAGE_NAME] } };\n },\n configResolved(config) {\n siteConfig = (config as { vitepress?: SiteConfig }).vitepress;\n if (siteConfig) attachAnnouncements(siteConfig, options);\n },\n transform(code, id) {\n if (!siteConfig || !id.endsWith('.md') || !(mayHaveBlogFrontmatter(code) || hasWidget(code))) return null;\n return { code: `${code}\\n<!-- vitepress-blog:${fingerprintBlogSource(currentPages())} -->\\n`, map: null };\n },\n configureServer(server) {\n // Vite only calls handleHotUpdate for edits; a post added or deleted changes the lists too.\n const refresh = (file: string) => {\n if (!siteConfig || !file.endsWith('.md')) return;\n invalidate(server, dependentFiles());\n refreshAnnouncements(server);\n server.ws.send({ type: 'full-reload' });\n };\n server.watcher.on('add', refresh).on('unlink', refresh);\n\n server.middlewares.use((request, response, next) => {\n if (!siteConfig || !request.url) return next();\n const base = siteConfig.site?.base ?? '/';\n const pathname = decodeURIComponent(request.url.split('?')[0]);\n if (!pathname.startsWith(base)) return next();\n const relative = pathname.slice(base.length);\n const pages = currentPages();\n // Feeds link to the live site, or to the dev server without a site URL.\n const site =\n resolveSiteLocation(siteConfig, options) ??\n resolveSiteLocation(siteConfig, { ...options, siteUrl: `http://${request.headers.host ?? 'localhost'}` })!;\n\n const index = pages.filter(isIndex).find((page) => page.index.feed && toFeedPath(page) === relative);\n if (index) {\n response.setHeader('Content-Type', 'application/rss+xml; charset=utf-8');\n response.end(renderIndexFeed(pages, index, site, siteConfig, options));\n return;\n }\n for (const post of pages.filter(isPost)) {\n const image = resolvePostImage(post, site);\n if (image?.outputPath === relative && image.file) {\n if (image.type) response.setHeader('Content-Type', image.type);\n response.end(readFileSync(image.file));\n return;\n }\n }\n next();\n });\n },\n handleHotUpdate({ file, modules, server }) {\n if (!siteConfig || !file.endsWith('.md')) return;\n // Every page shows the banner, so a change to it reloads them all. Checked\n // first: a page that stops being an announcement is no longer a post.\n if (refreshAnnouncements(server)) {\n invalidate(server, dependentFiles());\n server.ws.send({ type: 'full-reload' });\n return [];\n }\n const pages = currentPages();\n if (!pages.some((page) => page.absolutePath === file)) return;\n const affected = dependentFiles()\n .filter((dependent) => dependent !== file)\n .flatMap((dependent) => [...(server.moduleGraph.getModulesByFile(dependent) ?? [])]);\n return affected.length ? [...modules, ...affected] : undefined;\n },\n };\n}\n\n/**\n * Wraps a VitePress config with the whole blog: the Markdown plugin, the Vite\n * plugin (dev server and announcements), `<head>` tags, descriptions from\n * summaries, and feeds at the end of the build.\n *\n * ```ts\n * // .vitepress/config.mts\n * import { defineConfig } from 'vitepress';\n * import { withBlog } from '@binarynoir/vitepress-blog/vitepress';\n *\n * export default withBlog(defineConfig({ /* ...your config... *\\/ }), { siteUrl: 'https://example.com' });\n * ```\n *\n * Hooks you already have (`markdown.config`, `transformHead`,\n * `transformPageData`, `buildEnd`) are not replaced: the blog's runs and\n * then yours, so it composes with other `withX()` wrappers in any order.\n */\nexport function withBlog(config: UserConfig, options: BlogOptions = {}): UserConfig {\n config.markdown ??= {};\n const existingMarkdownConfig = config.markdown.config ?? (() => {});\n config.markdown.config = (md) => {\n // VitePress 2 types this callback against `markdown-it-async`'s MarkdownIt,\n // a structural superset of plain markdown-it's; the cast bridges the two.\n blogMarkdown(md as unknown as MarkdownIt, options);\n existingMarkdownConfig(md);\n };\n\n config.vite ??= {};\n config.vite.plugins = [...(config.vite.plugins ?? []), blogVitePlugin(options)];\n\n const existingTransformHead = config.transformHead;\n config.transformHead = async (context) => [\n ...blogHead(context, options),\n ...((await existingTransformHead?.(context)) ?? []),\n ];\n\n const existingTransformPageData = config.transformPageData;\n config.transformPageData = (pageData, context) => {\n blogPageData(pageData, context, options);\n return existingTransformPageData?.(pageData, context);\n };\n\n const existingBuildEnd = config.buildEnd;\n config.buildEnd = async (siteConfig) => {\n writeBlogFeeds(siteConfig, options);\n await existingBuildEnd?.(siteConfig);\n };\n\n return config;\n}\n\nexport { blogMarkdown };\nexport type { BlogOptions };\n","import { findAnnouncementBlog, listAnnouncements } from './collect.js';\nimport type { BlogPage } from './scan.js';\nimport { DEFAULT_LABELS, type BlogAnnouncementData, type BlogOptions } from './types.js';\nimport { DISMISSED_CLASS, DISMISSED_STORAGE_KEY, toUrlPath } from './util.js';\n\n/** The banner's data: every announcement, newest first, and where the list of them is. `null` with none. */\nexport function resolveAnnouncements(\n pages: BlogPage[],\n options: BlogOptions,\n cleanUrls: boolean,\n): BlogAnnouncementData | null {\n const drafts = options.drafts ?? false;\n const announcements = listAnnouncements(pages, drafts);\n if (announcements.length === 0) return null;\n const blog = findAnnouncementBlog(pages, announcements, drafts);\n const labels = { ...DEFAULT_LABELS, ...options.labels };\n return {\n items: announcements.map((post) => ({\n id: `${post.routePath}@${post.post.date?.toISOString() ?? ''}`,\n text: post.post.announcementText || post.post.title,\n link: toUrlPath(post.routePath, cleanUrls),\n })),\n listLink: blog ? `${toUrlPath(blog.routePath, cleanUrls)}?announcements` : '',\n labels: {\n announcement: labels.announcement,\n announcementCount: labels.announcementCount,\n dismiss: labels.dismiss,\n },\n };\n}\n\n/**\n * A `<head>` script that marks `<html>` with `DISMISSED_CLASS` when the visitor\n * dismissed the newest announcement, so the server-rendered banner never\n * flashes before the page hydrates.\n */\nexport function renderDismissedScript(data: BlogAnnouncementData): string {\n // `<` escaped so an id can't close the script element.\n const id = JSON.stringify(data.items[0].id).replace(/</g, '\\\\u003c');\n return (\n `try{var d=JSON.parse(localStorage.getItem(${JSON.stringify(DISMISSED_STORAGE_KEY)})||'[]');` +\n `if(Array.isArray(d)&&d.indexOf(${id})>-1)document.documentElement.classList.add(${JSON.stringify(DISMISSED_CLASS)})}catch(e){}`\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@binarynoir/vitepress-blog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Turn any VitePress 2 page into a blog post with frontmatter. A main blog, folder-scoped section blogs, sticky posts, pagination, keyword filters and an RSS feed for each.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vitepress",
|