@binarynoir/vitepress-blog 0.2.0 → 0.3.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 +99 -10
- package/dist/chunk-42Z6H7IN.js +52 -0
- package/dist/chunk-42Z6H7IN.js.map +1 -0
- package/dist/{chunk-PWNWSYI4.js → chunk-LYTJBMVK.js} +51 -42
- package/dist/chunk-LYTJBMVK.js.map +1 -0
- package/dist/client.cjs +140 -28
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +35 -4
- package/dist/client.d.ts +35 -4
- package/dist/client.js +136 -26
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +28 -7
- 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-Bb4rRwO8.d.cts} +31 -1
- package/dist/{plugin-BlfpqyX4.d.ts → plugin-Bb4rRwO8.d.ts} +31 -1
- package/dist/style.css +113 -0
- package/dist/vitepress.cjs +165 -76
- 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 +75 -7
- package/dist/vitepress.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PWNWSYI4.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -96,6 +96,16 @@ interface BlogLabels {
|
|
|
96
96
|
draft: string;
|
|
97
97
|
/** Badge on a sticky post. */
|
|
98
98
|
sticky: string;
|
|
99
|
+
/** Badge on an announcement, and the accessible name of the announcement banner. */
|
|
100
|
+
announcement: string;
|
|
101
|
+
/** Above a blog page filtered to announcements (`?announcements`). */
|
|
102
|
+
announcements: string;
|
|
103
|
+
/** When the blog page has no announcements to show. */
|
|
104
|
+
noAnnouncements: string;
|
|
105
|
+
/** Tooltip of the banner's count badge: "{n} announcements". */
|
|
106
|
+
announcementCount: string;
|
|
107
|
+
/** Accessible name of the banner's close button. */
|
|
108
|
+
dismiss: string;
|
|
99
109
|
noPosts: string;
|
|
100
110
|
/** Year heading for posts without a date, when a blog groups by year. */
|
|
101
111
|
undated: string;
|
|
@@ -176,12 +186,32 @@ interface BlogPostData {
|
|
|
176
186
|
scope: BlogScope;
|
|
177
187
|
sticky: boolean;
|
|
178
188
|
draft: boolean;
|
|
189
|
+
/** Shown in the announcement banner above the nav bar. */
|
|
190
|
+
announcement: boolean;
|
|
191
|
+
/** The banner's text, from `announcement: "…"`. `''` uses the title. */
|
|
192
|
+
announcementText: string;
|
|
179
193
|
/** Per-post overrides of the `byline`, `cover` and `postNav` options. */
|
|
180
194
|
byline?: boolean;
|
|
181
195
|
cover?: boolean;
|
|
182
196
|
postNav?: boolean;
|
|
183
197
|
readingMinutes: number;
|
|
184
198
|
}
|
|
199
|
+
/** One announcement, as the banner shows it. */
|
|
200
|
+
interface BlogAnnouncementItem {
|
|
201
|
+
/** The post's path and date: what a dismissal remembers, so re-dating a post announces it again. */
|
|
202
|
+
id: string;
|
|
203
|
+
text: string;
|
|
204
|
+
/** The post's URL path, without the site's base. */
|
|
205
|
+
link: string;
|
|
206
|
+
}
|
|
207
|
+
/** What the announcement banner reads from VitePress's site data. */
|
|
208
|
+
interface BlogAnnouncementData {
|
|
209
|
+
/** Newest first: the banner shows the first one that hasn't been dismissed. */
|
|
210
|
+
items: BlogAnnouncementItem[];
|
|
211
|
+
/** The blog page filtered to announcements (`/blog/?announcements`), without the base, or `''` when no blog lists them. */
|
|
212
|
+
listLink: string;
|
|
213
|
+
labels: Pick<BlogLabels, 'announcement' | 'announcementCount' | 'dismiss'>;
|
|
214
|
+
}
|
|
185
215
|
|
|
186
216
|
/**
|
|
187
217
|
* A markdown-it plugin that turns pages into a blog.
|
|
@@ -210,4 +240,4 @@ interface BlogPostData {
|
|
|
210
240
|
*/
|
|
211
241
|
declare function blogMarkdown(md: MarkdownIt, options?: BlogOptions): void;
|
|
212
242
|
|
|
213
|
-
export { type
|
|
243
|
+
export { type BlogAnnouncementData as B, DEFAULT_LABELS as D, type BlogAnnouncementItem as a, type BlogAuthor as b, type BlogFeedConfig as c, type BlogIndexConfig as d, type BlogLabels as e, type BlogLayout as f, type BlogOptions as g, type BlogPostData as h, type BlogScope as i, blogMarkdown as j };
|
|
@@ -96,6 +96,16 @@ interface BlogLabels {
|
|
|
96
96
|
draft: string;
|
|
97
97
|
/** Badge on a sticky post. */
|
|
98
98
|
sticky: string;
|
|
99
|
+
/** Badge on an announcement, and the accessible name of the announcement banner. */
|
|
100
|
+
announcement: string;
|
|
101
|
+
/** Above a blog page filtered to announcements (`?announcements`). */
|
|
102
|
+
announcements: string;
|
|
103
|
+
/** When the blog page has no announcements to show. */
|
|
104
|
+
noAnnouncements: string;
|
|
105
|
+
/** Tooltip of the banner's count badge: "{n} announcements". */
|
|
106
|
+
announcementCount: string;
|
|
107
|
+
/** Accessible name of the banner's close button. */
|
|
108
|
+
dismiss: string;
|
|
99
109
|
noPosts: string;
|
|
100
110
|
/** Year heading for posts without a date, when a blog groups by year. */
|
|
101
111
|
undated: string;
|
|
@@ -176,12 +186,32 @@ interface BlogPostData {
|
|
|
176
186
|
scope: BlogScope;
|
|
177
187
|
sticky: boolean;
|
|
178
188
|
draft: boolean;
|
|
189
|
+
/** Shown in the announcement banner above the nav bar. */
|
|
190
|
+
announcement: boolean;
|
|
191
|
+
/** The banner's text, from `announcement: "…"`. `''` uses the title. */
|
|
192
|
+
announcementText: string;
|
|
179
193
|
/** Per-post overrides of the `byline`, `cover` and `postNav` options. */
|
|
180
194
|
byline?: boolean;
|
|
181
195
|
cover?: boolean;
|
|
182
196
|
postNav?: boolean;
|
|
183
197
|
readingMinutes: number;
|
|
184
198
|
}
|
|
199
|
+
/** One announcement, as the banner shows it. */
|
|
200
|
+
interface BlogAnnouncementItem {
|
|
201
|
+
/** The post's path and date: what a dismissal remembers, so re-dating a post announces it again. */
|
|
202
|
+
id: string;
|
|
203
|
+
text: string;
|
|
204
|
+
/** The post's URL path, without the site's base. */
|
|
205
|
+
link: string;
|
|
206
|
+
}
|
|
207
|
+
/** What the announcement banner reads from VitePress's site data. */
|
|
208
|
+
interface BlogAnnouncementData {
|
|
209
|
+
/** Newest first: the banner shows the first one that hasn't been dismissed. */
|
|
210
|
+
items: BlogAnnouncementItem[];
|
|
211
|
+
/** The blog page filtered to announcements (`/blog/?announcements`), without the base, or `''` when no blog lists them. */
|
|
212
|
+
listLink: string;
|
|
213
|
+
labels: Pick<BlogLabels, 'announcement' | 'announcementCount' | 'dismiss'>;
|
|
214
|
+
}
|
|
185
215
|
|
|
186
216
|
/**
|
|
187
217
|
* A markdown-it plugin that turns pages into a blog.
|
|
@@ -210,4 +240,4 @@ interface BlogPostData {
|
|
|
210
240
|
*/
|
|
211
241
|
declare function blogMarkdown(md: MarkdownIt, options?: BlogOptions): void;
|
|
212
242
|
|
|
213
|
-
export { type
|
|
243
|
+
export { type BlogAnnouncementData as B, DEFAULT_LABELS as D, type BlogAnnouncementItem as a, type BlogAuthor as b, type BlogFeedConfig as c, type BlogIndexConfig as d, type BlogLabels as e, type BlogLayout as f, type BlogOptions as g, type BlogPostData as h, type BlogScope as i, blogMarkdown as j };
|
package/dist/style.css
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
--vp-blog-tag-bg: var(--vp-c-default-soft);
|
|
11
11
|
--vp-blog-tag-color: var(--vp-c-text-2);
|
|
12
12
|
--vp-blog-accent: var(--vp-c-brand-1);
|
|
13
|
+
--vp-blog-announcement-height: 40px;
|
|
14
|
+
--vp-blog-announcement-bg: var(--vp-button-brand-bg);
|
|
15
|
+
--vp-blog-announcement-color: var(--vp-button-brand-text);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/* Markdown inside the generated blocks is wrapped in <p>; its margins are handled here instead. */
|
|
@@ -201,6 +204,11 @@
|
|
|
201
204
|
color: var(--vp-c-brand-1);
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
.vp-blog-badge--announcement {
|
|
208
|
+
background: var(--vp-c-tip-soft);
|
|
209
|
+
color: var(--vp-c-tip-1);
|
|
210
|
+
}
|
|
211
|
+
|
|
204
212
|
.vp-blog-badge--draft {
|
|
205
213
|
background: var(--vp-c-warning-soft);
|
|
206
214
|
color: var(--vp-c-warning-1);
|
|
@@ -416,3 +424,108 @@
|
|
|
416
424
|
/* The RSS orange, as feed readers expect. */
|
|
417
425
|
color: #f26522;
|
|
418
426
|
}
|
|
427
|
+
|
|
428
|
+
/*
|
|
429
|
+
* The announcement banner, in the theme's `layout-top` slot: fixed across the
|
|
430
|
+
* top of the window, one line high. While it shows, VitePress's
|
|
431
|
+
* --vp-layout-top-height moves the nav bar, sidebar and content down by its
|
|
432
|
+
* height. The class on <html> hides it before hydration once dismissed.
|
|
433
|
+
*/
|
|
434
|
+
:root:not(.vp-blog-announcement-dismissed):has(.vp-blog-announcement) {
|
|
435
|
+
--vp-layout-top-height: var(--vp-blog-announcement-height);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.vp-blog-announcement-dismissed .vp-blog-announcement {
|
|
439
|
+
display: none;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.vp-blog-announcement {
|
|
443
|
+
position: fixed;
|
|
444
|
+
top: 0;
|
|
445
|
+
right: 0;
|
|
446
|
+
left: 0;
|
|
447
|
+
z-index: var(--vp-z-index-layout-top);
|
|
448
|
+
display: flex;
|
|
449
|
+
gap: 8px;
|
|
450
|
+
align-items: center;
|
|
451
|
+
justify-content: center;
|
|
452
|
+
box-sizing: border-box;
|
|
453
|
+
height: var(--vp-blog-announcement-height);
|
|
454
|
+
padding: 0 48px;
|
|
455
|
+
background: var(--vp-blog-announcement-bg);
|
|
456
|
+
color: var(--vp-blog-announcement-color);
|
|
457
|
+
font-size: 14px;
|
|
458
|
+
font-weight: 500;
|
|
459
|
+
line-height: var(--vp-blog-announcement-height);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.vp-blog-announcement__content {
|
|
463
|
+
display: flex;
|
|
464
|
+
gap: 10px;
|
|
465
|
+
align-items: center;
|
|
466
|
+
min-width: 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.vp-blog-announcement__text {
|
|
470
|
+
overflow: hidden;
|
|
471
|
+
color: inherit;
|
|
472
|
+
text-decoration: none;
|
|
473
|
+
text-overflow: ellipsis;
|
|
474
|
+
white-space: nowrap;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.vp-blog-announcement__text:hover {
|
|
478
|
+
text-decoration: underline;
|
|
479
|
+
text-underline-offset: 3px;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* How many announcements there are: a counter pill that opens the list of them. */
|
|
483
|
+
.vp-blog-announcement__count {
|
|
484
|
+
flex: none;
|
|
485
|
+
min-width: 22px;
|
|
486
|
+
padding: 0 7px;
|
|
487
|
+
border-radius: 999px;
|
|
488
|
+
background: var(--vp-blog-announcement-color);
|
|
489
|
+
color: var(--vp-blog-announcement-bg);
|
|
490
|
+
font-size: 12px;
|
|
491
|
+
font-weight: 700;
|
|
492
|
+
font-variant-numeric: tabular-nums;
|
|
493
|
+
line-height: 20px;
|
|
494
|
+
text-align: center;
|
|
495
|
+
text-decoration: none;
|
|
496
|
+
transition: opacity 0.25s;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
a.vp-blog-announcement__count:hover {
|
|
500
|
+
opacity: 0.85;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.vp-blog-announcement__dismiss {
|
|
504
|
+
position: absolute;
|
|
505
|
+
top: 50%;
|
|
506
|
+
right: 12px;
|
|
507
|
+
display: flex;
|
|
508
|
+
align-items: center;
|
|
509
|
+
justify-content: center;
|
|
510
|
+
width: 28px;
|
|
511
|
+
height: 28px;
|
|
512
|
+
border-radius: 6px;
|
|
513
|
+
color: inherit;
|
|
514
|
+
opacity: 0.8;
|
|
515
|
+
transform: translateY(-50%);
|
|
516
|
+
transition:
|
|
517
|
+
background-color 0.25s,
|
|
518
|
+
opacity 0.25s;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.vp-blog-announcement__dismiss:hover {
|
|
522
|
+
background: rgba(255, 255, 255, 0.16);
|
|
523
|
+
opacity: 1;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
@media (max-width: 639px) {
|
|
527
|
+
.vp-blog-announcement {
|
|
528
|
+
justify-content: flex-start;
|
|
529
|
+
padding: 0 48px 0 16px;
|
|
530
|
+
}
|
|
531
|
+
}
|
package/dist/vitepress.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/vitepress.ts
|
|
31
31
|
var vitepress_exports = {};
|
|
32
32
|
__export(vitepress_exports, {
|
|
33
|
+
attachAnnouncements: () => attachAnnouncements,
|
|
33
34
|
blogHead: () => blogHead,
|
|
34
35
|
blogMarkdown: () => blogMarkdown,
|
|
35
36
|
blogPageData: () => blogPageData,
|
|
@@ -113,6 +114,111 @@ function findBlogsListing(pages, post) {
|
|
|
113
114
|
(index) => listBlogPosts(pages, index, { drafts: false, stickyFirst: false, applyLimit: false }).includes(post)
|
|
114
115
|
);
|
|
115
116
|
}
|
|
117
|
+
function listAnnouncements(pages, drafts) {
|
|
118
|
+
return pages.filter(isPost).filter((post) => post.post.announcement && (drafts || !post.post.draft)).sort(compareByDate);
|
|
119
|
+
}
|
|
120
|
+
function findAnnouncementBlog(pages, announcements, drafts) {
|
|
121
|
+
if (announcements.length === 0) return null;
|
|
122
|
+
const listingAll = pages.filter(isIndex).filter((index) => {
|
|
123
|
+
const listed = listBlogPosts(pages, index, { drafts, stickyFirst: false, applyLimit: false });
|
|
124
|
+
return announcements.every((post) => listed.includes(post));
|
|
125
|
+
});
|
|
126
|
+
return listingAll.find((index) => index.index.scope === "site") ?? listingAll[0] ?? findHomeBlog(pages, announcements[0], drafts);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/types.ts
|
|
130
|
+
var DEFAULT_LABELS = {
|
|
131
|
+
by: "By",
|
|
132
|
+
published: "Published",
|
|
133
|
+
updated: "Updated",
|
|
134
|
+
dateTime: "{date} at {time}",
|
|
135
|
+
readingTime: "{n} min read",
|
|
136
|
+
newer: "Newer post",
|
|
137
|
+
older: "Older post",
|
|
138
|
+
allPosts: "All posts in {blog}",
|
|
139
|
+
draft: "Draft",
|
|
140
|
+
sticky: "Pinned",
|
|
141
|
+
announcement: "Announcement",
|
|
142
|
+
announcements: "Announcements",
|
|
143
|
+
noAnnouncements: "No announcements.",
|
|
144
|
+
announcementCount: "{n} announcements",
|
|
145
|
+
dismiss: "Dismiss",
|
|
146
|
+
noPosts: "No posts yet.",
|
|
147
|
+
undated: "Undated",
|
|
148
|
+
tags: "Keywords",
|
|
149
|
+
tagged: "Posts tagged {tag}",
|
|
150
|
+
showAll: "Show all posts",
|
|
151
|
+
noTagged: "No posts tagged {tag}.",
|
|
152
|
+
previous: "Previous",
|
|
153
|
+
next: "Next",
|
|
154
|
+
pages: "Pages",
|
|
155
|
+
subscribe: "Subscribe",
|
|
156
|
+
rssFeed: "RSS feed for {blog}"
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// src/util.ts
|
|
160
|
+
var HTML_ESCAPES = {
|
|
161
|
+
"&": "&",
|
|
162
|
+
"<": "<",
|
|
163
|
+
">": ">",
|
|
164
|
+
'"': """,
|
|
165
|
+
"'": "'"
|
|
166
|
+
};
|
|
167
|
+
function escapeHtml(value) {
|
|
168
|
+
return value.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
|
|
169
|
+
}
|
|
170
|
+
function escapeMarkdown(text) {
|
|
171
|
+
const escaped = text.replace(/[\\`*_[\]#|~]/g, "\\$&").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
172
|
+
return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;
|
|
173
|
+
}
|
|
174
|
+
function escapeTemplateText(text) {
|
|
175
|
+
const escaped = escapeHtml(text);
|
|
176
|
+
return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;
|
|
177
|
+
}
|
|
178
|
+
function isExternalUrl(url) {
|
|
179
|
+
return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(url);
|
|
180
|
+
}
|
|
181
|
+
function toMarkdownUrl(url) {
|
|
182
|
+
return encodeURI(url).replace(/[()]/g, (character) => character === "(" ? "%28" : "%29");
|
|
183
|
+
}
|
|
184
|
+
function toPageLink(text, routePath) {
|
|
185
|
+
return `[${escapeMarkdown(text)}](${toMarkdownUrl(`/${routePath}`)})`;
|
|
186
|
+
}
|
|
187
|
+
function toUrlPath(routePath, cleanUrls) {
|
|
188
|
+
return `/${routePath.replace(/(^|\/)index\.md$/, "$1").replace(/\.md$/, cleanUrls ? "" : ".html")}`;
|
|
189
|
+
}
|
|
190
|
+
function joinUrl(base, urlPath) {
|
|
191
|
+
return `${base.replace(/\/+$/, "")}/${urlPath.replace(/^\/+/, "")}`;
|
|
192
|
+
}
|
|
193
|
+
var ANNOUNCEMENTS_KEY = "blogAnnouncements";
|
|
194
|
+
var DISMISSED_STORAGE_KEY = "vp-blog-announcements-dismissed";
|
|
195
|
+
var DISMISSED_CLASS = "vp-blog-announcement-dismissed";
|
|
196
|
+
|
|
197
|
+
// src/announcements.ts
|
|
198
|
+
function resolveAnnouncements(pages, options, cleanUrls) {
|
|
199
|
+
const drafts = options.drafts ?? false;
|
|
200
|
+
const announcements = listAnnouncements(pages, drafts);
|
|
201
|
+
if (announcements.length === 0) return null;
|
|
202
|
+
const blog = findAnnouncementBlog(pages, announcements, drafts);
|
|
203
|
+
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
204
|
+
return {
|
|
205
|
+
items: announcements.map((post) => ({
|
|
206
|
+
id: `${post.routePath}@${post.post.date?.toISOString() ?? ""}`,
|
|
207
|
+
text: post.post.announcementText || post.post.title,
|
|
208
|
+
link: toUrlPath(post.routePath, cleanUrls)
|
|
209
|
+
})),
|
|
210
|
+
listLink: blog ? `${toUrlPath(blog.routePath, cleanUrls)}?announcements` : "",
|
|
211
|
+
labels: {
|
|
212
|
+
announcement: labels.announcement,
|
|
213
|
+
announcementCount: labels.announcementCount,
|
|
214
|
+
dismiss: labels.dismiss
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function renderDismissedScript(data) {
|
|
219
|
+
const id = JSON.stringify(data.items[0].id).replace(/</g, "\\u003c");
|
|
220
|
+
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){}`;
|
|
221
|
+
}
|
|
116
222
|
|
|
117
223
|
// src/frontmatter.ts
|
|
118
224
|
var import_node_path = __toESM(require("path"), 1);
|
|
@@ -320,6 +426,7 @@ function resolvePostData(data, content, fallbackTitle, options = {}) {
|
|
|
320
426
|
const timeZone = resolveTimeZone(options.timeZone);
|
|
321
427
|
const date = parsePostDate(pick("date"), timeZone);
|
|
322
428
|
const updated = parsePostDate(pick("updated"), timeZone);
|
|
429
|
+
const announcementText = typeof post.announcement === "string" ? clean(post.announcement.trim()) : "";
|
|
323
430
|
return {
|
|
324
431
|
title: clean(toText(post.title) || toText(data.title) || readHeading(content)) || fallbackTitle,
|
|
325
432
|
summary: clean(toText(post.summary) || toText(data.description) || readExcerpt(content)),
|
|
@@ -337,6 +444,8 @@ function resolvePostData(data, content, fallbackTitle, options = {}) {
|
|
|
337
444
|
scope: toScope(post.scope),
|
|
338
445
|
sticky: post.sticky === true,
|
|
339
446
|
draft: post.draft === true,
|
|
447
|
+
announcement: post.announcement === true || !!announcementText,
|
|
448
|
+
announcementText,
|
|
340
449
|
byline: toBoolean(post.byline),
|
|
341
450
|
cover: toBoolean(post.cover),
|
|
342
451
|
postNav: toBoolean(post.postNav),
|
|
@@ -395,43 +504,6 @@ function resolveIndexConfig(data, relativePath, defaults = {}) {
|
|
|
395
504
|
var import_node_crypto = require("crypto");
|
|
396
505
|
var import_node_fs = require("fs");
|
|
397
506
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
398
|
-
|
|
399
|
-
// src/util.ts
|
|
400
|
-
var HTML_ESCAPES = {
|
|
401
|
-
"&": "&",
|
|
402
|
-
"<": "<",
|
|
403
|
-
">": ">",
|
|
404
|
-
'"': """,
|
|
405
|
-
"'": "'"
|
|
406
|
-
};
|
|
407
|
-
function escapeHtml(value) {
|
|
408
|
-
return value.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
|
|
409
|
-
}
|
|
410
|
-
function escapeMarkdown(text) {
|
|
411
|
-
const escaped = text.replace(/[\\`*_[\]#|~]/g, "\\$&").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
412
|
-
return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;
|
|
413
|
-
}
|
|
414
|
-
function escapeTemplateText(text) {
|
|
415
|
-
const escaped = escapeHtml(text);
|
|
416
|
-
return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;
|
|
417
|
-
}
|
|
418
|
-
function isExternalUrl(url) {
|
|
419
|
-
return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(url);
|
|
420
|
-
}
|
|
421
|
-
function toMarkdownUrl(url) {
|
|
422
|
-
return encodeURI(url).replace(/[()]/g, (character) => character === "(" ? "%28" : "%29");
|
|
423
|
-
}
|
|
424
|
-
function toPageLink(text, routePath) {
|
|
425
|
-
return `[${escapeMarkdown(text)}](${toMarkdownUrl(`/${routePath}`)})`;
|
|
426
|
-
}
|
|
427
|
-
function toUrlPath(routePath, cleanUrls) {
|
|
428
|
-
return `/${routePath.replace(/(^|\/)index\.md$/, "$1").replace(/\.md$/, cleanUrls ? "" : ".html")}`;
|
|
429
|
-
}
|
|
430
|
-
function joinUrl(base, urlPath) {
|
|
431
|
-
return `${base.replace(/\/+$/, "")}/${urlPath.replace(/^\/+/, "")}`;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// src/feed.ts
|
|
435
507
|
function toAbsoluteUrl(urlPath, site) {
|
|
436
508
|
return joinUrl(site.siteUrl, joinUrl(site.base, urlPath));
|
|
437
509
|
}
|
|
@@ -527,33 +599,6 @@ var import_node_path6 = __toESM(require("path"), 1);
|
|
|
527
599
|
|
|
528
600
|
// src/render.ts
|
|
529
601
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
530
|
-
|
|
531
|
-
// src/types.ts
|
|
532
|
-
var DEFAULT_LABELS = {
|
|
533
|
-
by: "By",
|
|
534
|
-
published: "Published",
|
|
535
|
-
updated: "Updated",
|
|
536
|
-
dateTime: "{date} at {time}",
|
|
537
|
-
readingTime: "{n} min read",
|
|
538
|
-
newer: "Newer post",
|
|
539
|
-
older: "Older post",
|
|
540
|
-
allPosts: "All posts in {blog}",
|
|
541
|
-
draft: "Draft",
|
|
542
|
-
sticky: "Pinned",
|
|
543
|
-
noPosts: "No posts yet.",
|
|
544
|
-
undated: "Undated",
|
|
545
|
-
tags: "Keywords",
|
|
546
|
-
tagged: "Posts tagged {tag}",
|
|
547
|
-
showAll: "Show all posts",
|
|
548
|
-
noTagged: "No posts tagged {tag}.",
|
|
549
|
-
previous: "Previous",
|
|
550
|
-
next: "Next",
|
|
551
|
-
pages: "Pages",
|
|
552
|
-
subscribe: "Subscribe",
|
|
553
|
-
rssFeed: "RSS feed for {blog}"
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
// src/render.ts
|
|
557
602
|
var BLOG_POSTS_PLACEHOLDER = "<!-- blog-posts -->";
|
|
558
603
|
function createRenderContext(options, pageDirectory, feeds = !!options.siteUrl) {
|
|
559
604
|
const locale = options.locale ?? "en-US";
|
|
@@ -620,12 +665,22 @@ function renderTags(tags, blogRoutePath) {
|
|
|
620
665
|
function toTagsAttribute(post) {
|
|
621
666
|
return escapeHtml(post.post.tags.map((tag) => tag.toLowerCase()).join("|"));
|
|
622
667
|
}
|
|
668
|
+
function toItemAttributes(post) {
|
|
669
|
+
return `data-vp-blog-item data-tags="${toTagsAttribute(post)}"${post.post.announcement ? " data-announcement" : ""}`;
|
|
670
|
+
}
|
|
671
|
+
function renderBadges(post, context) {
|
|
672
|
+
const { draft, sticky, announcement } = post.post;
|
|
673
|
+
return [
|
|
674
|
+
draft && renderBadge("draft", context),
|
|
675
|
+
sticky && renderBadge("sticky", context),
|
|
676
|
+
announcement && renderBadge("announcement", context)
|
|
677
|
+
].filter(Boolean).join(" ");
|
|
678
|
+
}
|
|
623
679
|
function renderMeta(post, context, include) {
|
|
624
|
-
const { date, dateHasTime, updated, updatedHasTime, readingMinutes, authors
|
|
680
|
+
const { date, dateHasTime, updated, updatedHasTime, readingMinutes, authors } = post.post;
|
|
625
681
|
const parts = [];
|
|
626
682
|
if (include.badges) {
|
|
627
|
-
const
|
|
628
|
-
const shown = badges.filter(Boolean).join(" ");
|
|
683
|
+
const shown = renderBadges(post, context);
|
|
629
684
|
if (shown) parts.push(shown);
|
|
630
685
|
}
|
|
631
686
|
if (date) {
|
|
@@ -654,12 +709,13 @@ function renderCard(post, context, index) {
|
|
|
654
709
|
const modifiers = [
|
|
655
710
|
post.post.sticky && "sticky",
|
|
656
711
|
post.post.draft && "draft",
|
|
712
|
+
post.post.announcement && "announcement",
|
|
657
713
|
image && "with-image",
|
|
658
714
|
image && post.post.imageFit === "contain" && "contain"
|
|
659
715
|
].filter(Boolean).map((modifier) => ` vp-blog-card--${modifier}`).join("");
|
|
660
716
|
const alt = escapeMarkdown(post.post.imageAlt);
|
|
661
717
|
return [
|
|
662
|
-
`<article class="vp-blog-card${modifiers}"${imageStyle(post)}
|
|
718
|
+
`<article class="vp-blog-card${modifiers}"${imageStyle(post)} ${toItemAttributes(post)}>`,
|
|
663
719
|
...image ? block("vp-blog-card__image", `[})](${toMarkdownUrl(link)})`) : [],
|
|
664
720
|
'<div class="vp-blog-card__body">',
|
|
665
721
|
...block("vp-blog-card__title", toPageLink(post.post.title, post.routePath)),
|
|
@@ -672,8 +728,8 @@ function renderCard(post, context, index) {
|
|
|
672
728
|
}
|
|
673
729
|
function renderCompactItem(post, context) {
|
|
674
730
|
const date = post.post.date ? `${renderTime(post.post.date, post.post.dateHasTime, context)} ` : "";
|
|
675
|
-
const badges =
|
|
676
|
-
const marker = `<span hidden
|
|
731
|
+
const badges = renderBadges(post, context);
|
|
732
|
+
const marker = `<span hidden ${toItemAttributes(post)}></span>`;
|
|
677
733
|
return `- ${marker}${date}${toPageLink(post.post.title, post.routePath)}${badges ? ` ${badges}` : ""}`;
|
|
678
734
|
}
|
|
679
735
|
function renderGroup(posts, index, context) {
|
|
@@ -748,7 +804,9 @@ function renderClientAttributes(index, context) {
|
|
|
748
804
|
"data-label-no-tagged": labels.noTagged,
|
|
749
805
|
"data-label-previous": labels.previous,
|
|
750
806
|
"data-label-next": labels.next,
|
|
751
|
-
"data-label-pages": labels.pages
|
|
807
|
+
"data-label-pages": labels.pages,
|
|
808
|
+
"data-label-announcements": labels.announcements,
|
|
809
|
+
"data-label-no-announcements": labels.noAnnouncements
|
|
752
810
|
};
|
|
753
811
|
return Object.entries(attributes).map(([name, value]) => `${name}="${escapeHtml(value)}"`).join(" ");
|
|
754
812
|
}
|
|
@@ -1135,6 +1193,9 @@ function blogMarkdown(md, options = {}) {
|
|
|
1135
1193
|
|
|
1136
1194
|
// src/vitepress.ts
|
|
1137
1195
|
var DEFAULT_FEED_LIMIT = 20;
|
|
1196
|
+
var PACKAGE_NAME = "@binarynoir/vitepress-blog";
|
|
1197
|
+
var SITE_DATA_MODULE = "/@siteData";
|
|
1198
|
+
var SCRIPT_MARKER = "data-vp-blog-announcement";
|
|
1138
1199
|
function resolveSiteLocation(siteConfig, options) {
|
|
1139
1200
|
const siteUrl = options.siteUrl ?? siteConfig.userConfig?.sitemap?.hostname ?? siteConfig.sitemap?.hostname;
|
|
1140
1201
|
if (!siteUrl) return null;
|
|
@@ -1149,6 +1210,16 @@ function resolveSiteLocation(siteConfig, options) {
|
|
|
1149
1210
|
function loadPages(siteConfig, options) {
|
|
1150
1211
|
return loadBlogPages(siteConfig.srcDir, options, siteConfig);
|
|
1151
1212
|
}
|
|
1213
|
+
function attachAnnouncements(siteConfig, options = {}) {
|
|
1214
|
+
const site = siteConfig.site;
|
|
1215
|
+
const data = resolveAnnouncements(loadPages(siteConfig, options), options, !!siteConfig.cleanUrls);
|
|
1216
|
+
const changed = JSON.stringify(site[ANNOUNCEMENTS_KEY] ?? null) !== JSON.stringify(data);
|
|
1217
|
+
if (data) site[ANNOUNCEMENTS_KEY] = data;
|
|
1218
|
+
else delete site[ANNOUNCEMENTS_KEY];
|
|
1219
|
+
site.head = (site.head ?? []).filter(([, attributes]) => !(SCRIPT_MARKER in (attributes ?? {})));
|
|
1220
|
+
if (data) site.head.push(["script", { [SCRIPT_MARKER]: "" }, renderDismissedScript(data)]);
|
|
1221
|
+
return changed;
|
|
1222
|
+
}
|
|
1152
1223
|
function listFeedPosts(pages, index, options) {
|
|
1153
1224
|
const limit = index.index.feed?.limit ?? options.feedLimit ?? DEFAULT_FEED_LIMIT;
|
|
1154
1225
|
return listBlogPosts(pages, index, { drafts: false, stickyFirst: false, applyLimit: false }).slice(0, limit);
|
|
@@ -1253,11 +1324,26 @@ function blogVitePlugin(options = {}) {
|
|
|
1253
1324
|
let siteConfig;
|
|
1254
1325
|
const currentPages = () => siteConfig ? loadPages(siteConfig, options) : [];
|
|
1255
1326
|
const dependentFiles = () => [...currentPages().map((page) => page.absolutePath), ...widgetPages];
|
|
1327
|
+
const invalidate = (server, files) => {
|
|
1328
|
+
for (const file of files) {
|
|
1329
|
+
for (const module2 of server.moduleGraph.getModulesByFile(file) ?? []) server.moduleGraph.invalidateModule(module2);
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
const refreshAnnouncements = (server) => {
|
|
1333
|
+
if (!siteConfig || !attachAnnouncements(siteConfig, options)) return false;
|
|
1334
|
+
const module2 = server.moduleGraph.getModuleById(SITE_DATA_MODULE);
|
|
1335
|
+
if (module2) server.moduleGraph.invalidateModule(module2);
|
|
1336
|
+
return true;
|
|
1337
|
+
};
|
|
1256
1338
|
return {
|
|
1257
1339
|
name: "vitepress-blog",
|
|
1258
1340
|
enforce: "pre",
|
|
1341
|
+
config() {
|
|
1342
|
+
return { ssr: { noExternal: [PACKAGE_NAME] } };
|
|
1343
|
+
},
|
|
1259
1344
|
configResolved(config) {
|
|
1260
1345
|
siteConfig = config.vitepress;
|
|
1346
|
+
if (siteConfig) attachAnnouncements(siteConfig, options);
|
|
1261
1347
|
},
|
|
1262
1348
|
transform(code, id) {
|
|
1263
1349
|
if (!siteConfig || !id.endsWith(".md") || !(mayHaveBlogFrontmatter(code) || hasWidget(code))) return null;
|
|
@@ -1268,11 +1354,8 @@ function blogVitePlugin(options = {}) {
|
|
|
1268
1354
|
configureServer(server) {
|
|
1269
1355
|
const refresh = (file) => {
|
|
1270
1356
|
if (!siteConfig || !file.endsWith(".md")) return;
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
server.moduleGraph.invalidateModule(module2);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1357
|
+
invalidate(server, dependentFiles());
|
|
1358
|
+
refreshAnnouncements(server);
|
|
1276
1359
|
server.ws.send({ type: "full-reload" });
|
|
1277
1360
|
};
|
|
1278
1361
|
server.watcher.on("add", refresh).on("unlink", refresh);
|
|
@@ -1303,6 +1386,11 @@ function blogVitePlugin(options = {}) {
|
|
|
1303
1386
|
},
|
|
1304
1387
|
handleHotUpdate({ file, modules, server }) {
|
|
1305
1388
|
if (!siteConfig || !file.endsWith(".md")) return;
|
|
1389
|
+
if (refreshAnnouncements(server)) {
|
|
1390
|
+
invalidate(server, dependentFiles());
|
|
1391
|
+
server.ws.send({ type: "full-reload" });
|
|
1392
|
+
return [];
|
|
1393
|
+
}
|
|
1306
1394
|
const pages = currentPages();
|
|
1307
1395
|
if (!pages.some((page) => page.absolutePath === file)) return;
|
|
1308
1396
|
const affected = dependentFiles().filter((dependent) => dependent !== file).flatMap((dependent) => [...server.moduleGraph.getModulesByFile(dependent) ?? []]);
|
|
@@ -1339,6 +1427,7 @@ function withBlog(config, options = {}) {
|
|
|
1339
1427
|
}
|
|
1340
1428
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1341
1429
|
0 && (module.exports = {
|
|
1430
|
+
attachAnnouncements,
|
|
1342
1431
|
blogHead,
|
|
1343
1432
|
blogMarkdown,
|
|
1344
1433
|
blogPageData,
|