@binarynoir/vitepress-blog 0.1.1 → 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 +158 -33
- package/dist/chunk-42Z6H7IN.js +52 -0
- package/dist/chunk-42Z6H7IN.js.map +1 -0
- package/dist/{chunk-DICBXOTS.js → chunk-LYTJBMVK.js} +182 -66
- 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 +157 -31
- 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-BjbV6nDK.d.cts → plugin-Bb4rRwO8.d.cts} +55 -2
- package/dist/{plugin-BjbV6nDK.d.ts → plugin-Bb4rRwO8.d.ts} +55 -2
- package/dist/style.css +113 -0
- package/dist/vitepress.cjs +294 -98
- 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-DICBXOTS.js.map +0 -1
package/README.md
CHANGED
|
@@ -127,8 +127,8 @@ with every default; an object sets its details.
|
|
|
127
127
|
---
|
|
128
128
|
title: We launched
|
|
129
129
|
blog:
|
|
130
|
-
date: 2026-10-01
|
|
131
|
-
updated: 2026-10-05
|
|
130
|
+
date: 2026-10-01 09:00
|
|
131
|
+
updated: 2026-10-05 16:30
|
|
132
132
|
summary: Team A's first release is out.
|
|
133
133
|
image: ./launch.png
|
|
134
134
|
imageAlt: The release dashboard
|
|
@@ -144,8 +144,8 @@ blog:
|
|
|
144
144
|
| --------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
145
|
| `title` | the page's `title`, then its `#` heading | Title in lists, feeds and social cards. |
|
|
146
146
|
| `summary` | the page's `description`, then first prose | One or two sentences for cards, feeds and `<meta name="description">`. |
|
|
147
|
-
| `date` | none |
|
|
148
|
-
| `updated` | none |
|
|
147
|
+
| `date` | none | When the post was published: a date, or a date and time. See [Dates and times](#dates-and-times). Newest first; undated posts go last. |
|
|
148
|
+
| `updated` | none | When the post was last changed, in the same forms. The byline shows both: "Published … · Updated …". It is the feed's last-updated time. |
|
|
149
149
|
| `image` | none | Card, social and feed image: a path relative to the post, a public path (`/cover.png`) or a URL. `{ src, alt, fit, position }` also works. |
|
|
150
150
|
| `imageAlt` | `''` | Alt text for the image. |
|
|
151
151
|
| `imageFit` | `cover` | How cards fit the image in their frame: `cover` crops it to fill the frame, `contain` shows all of it, letterboxed. Use `contain` for logos and diagrams. |
|
|
@@ -155,7 +155,8 @@ blog:
|
|
|
155
155
|
| `category` | none | Written to the feed and `article:section`. |
|
|
156
156
|
| `scope` | `section` | `site` (or `global`) also lists the post on the main blog. See [Scopes](#scopes). |
|
|
157
157
|
| `sticky` | `false` | Pinned to the top of its blog pages, above the paginated posts, with a Pinned badge. See [Sticky posts](#sticky-posts). |
|
|
158
|
-
| `
|
|
158
|
+
| `announcement` | `false` | `true`, or the banner's text, shows the post in the announcement banner above the nav bar. See [Announcements](#announcements). |
|
|
159
|
+
| `draft` | `false` | Left out of lists and feeds. The page itself is still built: to leave drafts out of the build, the sidebar and the navbar too, see [Drafts](#drafts). |
|
|
159
160
|
| `byline` | the `byline` option | `false` hides this post's byline. |
|
|
160
161
|
| `cover` | the `cover` option | `true` shows this post's image under its byline. |
|
|
161
162
|
| `postNav` | the `postNav` option | `false` hides this post's newer / older links. |
|
|
@@ -164,6 +165,67 @@ Title, description, date, author(s), image and tags may also be written at the
|
|
|
164
165
|
top level of the frontmatter, as other blog tools expect. A value under `blog`
|
|
165
166
|
wins.
|
|
166
167
|
|
|
168
|
+
### Dates and times
|
|
169
|
+
|
|
170
|
+
`date` and `updated` take a date, or a date and a time, in any of these forms:
|
|
171
|
+
|
|
172
|
+
| Written | Means |
|
|
173
|
+
| --------------------------- | ------------------------------------------- |
|
|
174
|
+
| `2026-10-20` | A day. Shows "October 20, 2026". |
|
|
175
|
+
| `2026-10-20 14:30` | A day and a time, in the `timeZone` option. |
|
|
176
|
+
| `2026-10-20T14:30:00` | The same, with a `T` and seconds. |
|
|
177
|
+
| `2026-10-20T14:30:00-04:00` | An exact moment, with its offset from UTC. |
|
|
178
|
+
| `2026-10-20T18:30:00Z` | An exact moment, in UTC. |
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
blog:
|
|
182
|
+
date: 2026-10-20 14:30
|
|
183
|
+
updated: 2026-10-21 09:05
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
- **A time shows only when you give one.** A date alone is shown as a date,
|
|
187
|
+
never as "12:00 AM".
|
|
188
|
+
- **Times without an offset are in the `timeZone` option**, `UTC` unless you
|
|
189
|
+
set one, so the same frontmatter means the same moment on every build
|
|
190
|
+
machine. Set `timeZone: 'America/New_York'` and write local times; daylight
|
|
191
|
+
saving is taken care of. Dates and times are also shown in that time zone.
|
|
192
|
+
- **The byline tells readers both:** "Published October 20, 2026 at 2:30 PM
|
|
193
|
+
EDT · Updated October 21, 2026 at 9:05 AM EDT". Cards show the published
|
|
194
|
+
date alone.
|
|
195
|
+
- **Posts on the same day sort by their times**, newest first, and a date
|
|
196
|
+
alone counts as the start of its day.
|
|
197
|
+
- Feeds and social tags use the exact moment: `pubDate`, `lastBuildDate`,
|
|
198
|
+
`article:published_time` and `article:modified_time`.
|
|
199
|
+
|
|
200
|
+
### Drafts
|
|
201
|
+
|
|
202
|
+
`draft: true` keeps a post out of blog lists, `::: blog-posts` blocks and
|
|
203
|
+
feeds, but the page is still built and can be opened by its URL. With the
|
|
204
|
+
`drafts` option on, drafts are listed too, with a Draft badge, which is handy in
|
|
205
|
+
`vitepress dev`.
|
|
206
|
+
|
|
207
|
+
To leave drafts out of the build entirely, use
|
|
208
|
+
[`@binarynoir/vitepress-exclude`](https://binarynoir.tech/plugins/exclude/)'s
|
|
209
|
+
`excludeFrontmatter` option, and the same option in Auto Sidebar and Auto
|
|
210
|
+
Navbar to hide them from the menus. The blog lists only what VitePress builds,
|
|
211
|
+
so nothing else changes:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
// Drafts stay out of the production build, and show (marked) in `vitepress dev`.
|
|
215
|
+
const isBuild = process.env.NODE_ENV === 'production';
|
|
216
|
+
const excludeFrontmatter = isBuild ? ['draft', 'blog.draft'] : [];
|
|
217
|
+
const blogOptions = { drafts: !isBuild };
|
|
218
|
+
|
|
219
|
+
export default defineConfig({
|
|
220
|
+
srcExclude: excludeSrc(docsRoot, { excludeFrontmatter }),
|
|
221
|
+
themeConfig: {
|
|
222
|
+
sidebar: generateSidebar(docsRoot, { excludeFrontmatter }),
|
|
223
|
+
nav: generateNav(docsRoot, { excludeFrontmatter }),
|
|
224
|
+
},
|
|
225
|
+
// ...the blog's pieces, with blogOptions
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
167
229
|
## Blog pages
|
|
168
230
|
|
|
169
231
|
A page with `blog-index` in its frontmatter lists posts. `blog-index: true` is a
|
|
@@ -238,6 +300,57 @@ a blog page, on each card, and in a post's byline, where they link to the
|
|
|
238
300
|
post's own blog, filtered. They are ordinary links, so VitePress follows them
|
|
239
301
|
without reloading the page, and each filtered view has its own URL to share.
|
|
240
302
|
|
|
303
|
+
## Announcements
|
|
304
|
+
|
|
305
|
+
`announcement: true` puts a post in a banner across the top of every page,
|
|
306
|
+
above the nav bar: one line, the full width of the window, linked to the post.
|
|
307
|
+
A string sets the banner's text instead of the post's title.
|
|
308
|
+
|
|
309
|
+
```md
|
|
310
|
+
---
|
|
311
|
+
blog:
|
|
312
|
+
date: 2026-10-20 09:00
|
|
313
|
+
announcement: Scheduled maintenance on Friday, 18:00 UTC
|
|
314
|
+
---
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Add the banner to your theme's `layout-top` slot:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
// .vitepress/theme/index.ts
|
|
321
|
+
import { h } from 'vue';
|
|
322
|
+
import DefaultTheme from 'vitepress/theme';
|
|
323
|
+
import { BlogAnnouncementBanner, useBlog } from '@binarynoir/vitepress-blog/client';
|
|
324
|
+
import '@binarynoir/vitepress-blog/style.css';
|
|
325
|
+
|
|
326
|
+
export default {
|
|
327
|
+
extends: DefaultTheme,
|
|
328
|
+
Layout: () => h(DefaultTheme.Layout, null, { 'layout-top': () => h(BlogAnnouncementBanner) }),
|
|
329
|
+
setup() {
|
|
330
|
+
useBlog();
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
- **The newest one wins.** With several, the banner shows the newest and a
|
|
336
|
+
badge with how many there are. The badge opens a blog page filtered to
|
|
337
|
+
announcements (`/blog/?announcements`): the main blog when it lists them all,
|
|
338
|
+
else another blog page that does, else the newest one's own blog.
|
|
339
|
+
- **Visitors can clear it.** The close button dismisses every current
|
|
340
|
+
announcement, remembered in the browser. A newer one brings the banner back,
|
|
341
|
+
and its badge counts only the ones not yet dismissed. Changing a post's
|
|
342
|
+
`date` announces it again.
|
|
343
|
+
- **No jump on load.** The banner is rendered on the server, and a small
|
|
344
|
+
`<head>` script hides it before the page shows if the visitor dismissed it.
|
|
345
|
+
While it shows, it sets VitePress's `--vp-layout-top-height`, so the nav bar,
|
|
346
|
+
sidebar and content move down to make room.
|
|
347
|
+
- Announcements are marked on blog pages with an Announcement badge. Drafts
|
|
348
|
+
are only announced with the `drafts` option.
|
|
349
|
+
|
|
350
|
+
The banner reads its posts from VitePress's site data, which `withBlog` (or
|
|
351
|
+
`blogVitePlugin`) fills in; `blogVitePlugin` also has Vite bundle this package
|
|
352
|
+
for the server-side build, so the banner renders there.
|
|
353
|
+
|
|
241
354
|
## Latest posts on any page
|
|
242
355
|
|
|
243
356
|
A `::: blog-posts` block shows a blog's latest posts anywhere: on the home
|
|
@@ -333,38 +446,47 @@ link that copy. Public paths (`/cover.png`) and URLs are linked as they are.
|
|
|
333
446
|
|
|
334
447
|
## Options
|
|
335
448
|
|
|
336
|
-
| Option | Default
|
|
337
|
-
| ---------------- |
|
|
338
|
-
| `siteUrl` | `sitemap.hostname`
|
|
339
|
-
| `authors` | `{}`
|
|
340
|
-
| `defaultAuthor` | none
|
|
341
|
-
| `locale` | `'en-US'`
|
|
342
|
-
| `dateFormat` | `{ dateStyle: 'long' }`
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
449
|
+
| Option | Default | What it does |
|
|
450
|
+
| ---------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
451
|
+
| `siteUrl` | `sitemap.hostname` | Absolute URL of the published site, for feeds and social tags. |
|
|
452
|
+
| `authors` | `{}` | Authors posts can name by key: `{ jane: { name, url, email } }` or `{ jane: 'Jane Doe' }`. |
|
|
453
|
+
| `defaultAuthor` | none | Author for posts that don't name one. |
|
|
454
|
+
| `locale` | `'en-US'` | Locale for dates and author lists ("Jane and Sam"). |
|
|
455
|
+
| `dateFormat` | `{ dateStyle: 'long' }` | `Intl.DateTimeFormat` options for dates. |
|
|
456
|
+
| `timeFormat` | `{ hour: 'numeric', minute: '2-digit', timeZoneName: 'short' }` | `Intl.DateTimeFormat` options for times, as in "2:30 PM EDT". Only dates given with a time show one. |
|
|
457
|
+
| `timeZone` | `'UTC'` | The IANA time zone (`'America/New_York'`) that times without an offset are in, and that dates and times are shown in. |
|
|
458
|
+
| `wordsPerMinute` | `200` | For "5 min read". `0` hides reading time. |
|
|
459
|
+
| `byline` | `true` | Author, date and tags under each post's title. |
|
|
460
|
+
| `cover` | `false` | Each post's image under its byline. |
|
|
461
|
+
| `postNav` | `true` | Newer and older post links at the end of each post. |
|
|
462
|
+
| `drafts` | `false` | List drafts, marked. Pass `process.env.NODE_ENV !== 'production'` for dev only. |
|
|
463
|
+
| `stripTags` | `true` | Remove [Markdown Tags](https://binarynoir.tech/plugins/markdown-tags/) syntax from titles and summaries. |
|
|
464
|
+
| `feedLimit` | `20` | Most posts in each feed. |
|
|
465
|
+
| `imagesDir` | `'blog-images'` | Output folder for copied post images. |
|
|
466
|
+
| `pageSize` | `10` | Posts per page on blog pages that don't set `pageSize`. `0` turns pages off. |
|
|
467
|
+
| `stickyLimit` | `0` (all) | Most pinned sticky posts on blog pages that don't set `stickyLimit`. |
|
|
468
|
+
| `rssLink` | `true` | A "Subscribe" RSS link on blog pages and blocks that don't set `rssLink`. |
|
|
469
|
+
| `labels` | English | Words the plugin writes, for translating. See below. |
|
|
355
470
|
|
|
356
471
|
```ts
|
|
357
472
|
withBlog(config, {
|
|
358
473
|
locale: 'de-DE',
|
|
359
474
|
labels: {
|
|
360
475
|
by: 'Von',
|
|
476
|
+
published: 'Veröffentlicht',
|
|
361
477
|
updated: 'Aktualisiert',
|
|
478
|
+
dateTime: '{date} um {time}',
|
|
362
479
|
readingTime: '{n} Min. Lesezeit',
|
|
363
480
|
newer: 'Neuerer Beitrag',
|
|
364
481
|
older: 'Älterer Beitrag',
|
|
365
482
|
allPosts: 'Alle Beiträge in {blog}',
|
|
366
483
|
draft: 'Entwurf',
|
|
367
484
|
sticky: 'Angeheftet',
|
|
485
|
+
announcement: 'Ankündigung',
|
|
486
|
+
announcements: 'Ankündigungen',
|
|
487
|
+
noAnnouncements: 'Keine Ankündigungen.',
|
|
488
|
+
announcementCount: '{n} Ankündigungen',
|
|
489
|
+
dismiss: 'Schließen',
|
|
368
490
|
noPosts: 'Noch keine Beiträge.',
|
|
369
491
|
undated: 'Ohne Datum',
|
|
370
492
|
tags: 'Schlagwörter',
|
|
@@ -385,15 +507,18 @@ withBlog(config, {
|
|
|
385
507
|
`style.css` uses VitePress's theme variables, so it follows light and dark
|
|
386
508
|
mode. Override these to restyle it without new rules:
|
|
387
509
|
|
|
388
|
-
| Variable
|
|
389
|
-
|
|
|
390
|
-
| `--vp-blog-card-bg`
|
|
391
|
-
| `--vp-blog-card-border`
|
|
392
|
-
| `--vp-blog-card-radius`
|
|
393
|
-
| `--vp-blog-meta-color`
|
|
394
|
-
| `--vp-blog-tag-bg`
|
|
395
|
-
| `--vp-blog-tag-color`
|
|
396
|
-
| `--vp-blog-accent`
|
|
510
|
+
| Variable | Default |
|
|
511
|
+
| ------------------------------- | ----------------------------- |
|
|
512
|
+
| `--vp-blog-card-bg` | `var(--vp-c-bg-soft)` |
|
|
513
|
+
| `--vp-blog-card-border` | `var(--vp-c-divider)` |
|
|
514
|
+
| `--vp-blog-card-radius` | `12px` |
|
|
515
|
+
| `--vp-blog-meta-color` | `var(--vp-c-text-2)` |
|
|
516
|
+
| `--vp-blog-tag-bg` | `var(--vp-c-default-soft)` |
|
|
517
|
+
| `--vp-blog-tag-color` | `var(--vp-c-text-2)` |
|
|
518
|
+
| `--vp-blog-accent` | `var(--vp-c-brand-1)` |
|
|
519
|
+
| `--vp-blog-announcement-height` | `40px` |
|
|
520
|
+
| `--vp-blog-announcement-bg` | `var(--vp-button-brand-bg)` |
|
|
521
|
+
| `--vp-blog-announcement-color` | `var(--vp-button-brand-text)` |
|
|
397
522
|
|
|
398
523
|
Every generated element has a `vp-blog-` class (`vp-blog-card`,
|
|
399
524
|
`vp-blog-byline`, `vp-blog-post-nav`, …) for anything more.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/util.ts
|
|
2
|
+
var HTML_ESCAPES = {
|
|
3
|
+
"&": "&",
|
|
4
|
+
"<": "<",
|
|
5
|
+
">": ">",
|
|
6
|
+
'"': """,
|
|
7
|
+
"'": "'"
|
|
8
|
+
};
|
|
9
|
+
function escapeHtml(value) {
|
|
10
|
+
return value.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
|
|
11
|
+
}
|
|
12
|
+
function escapeMarkdown(text) {
|
|
13
|
+
const escaped = text.replace(/[\\`*_[\]#|~]/g, "\\$&").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
14
|
+
return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;
|
|
15
|
+
}
|
|
16
|
+
function escapeTemplateText(text) {
|
|
17
|
+
const escaped = escapeHtml(text);
|
|
18
|
+
return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;
|
|
19
|
+
}
|
|
20
|
+
function isExternalUrl(url) {
|
|
21
|
+
return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(url);
|
|
22
|
+
}
|
|
23
|
+
function toMarkdownUrl(url) {
|
|
24
|
+
return encodeURI(url).replace(/[()]/g, (character) => character === "(" ? "%28" : "%29");
|
|
25
|
+
}
|
|
26
|
+
function toPageLink(text, routePath) {
|
|
27
|
+
return `[${escapeMarkdown(text)}](${toMarkdownUrl(`/${routePath}`)})`;
|
|
28
|
+
}
|
|
29
|
+
function toUrlPath(routePath, cleanUrls) {
|
|
30
|
+
return `/${routePath.replace(/(^|\/)index\.md$/, "$1").replace(/\.md$/, cleanUrls ? "" : ".html")}`;
|
|
31
|
+
}
|
|
32
|
+
function joinUrl(base, urlPath) {
|
|
33
|
+
return `${base.replace(/\/+$/, "")}/${urlPath.replace(/^\/+/, "")}`;
|
|
34
|
+
}
|
|
35
|
+
var ANNOUNCEMENTS_KEY = "blogAnnouncements";
|
|
36
|
+
var DISMISSED_STORAGE_KEY = "vp-blog-announcements-dismissed";
|
|
37
|
+
var DISMISSED_CLASS = "vp-blog-announcement-dismissed";
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
escapeHtml,
|
|
41
|
+
escapeMarkdown,
|
|
42
|
+
escapeTemplateText,
|
|
43
|
+
isExternalUrl,
|
|
44
|
+
toMarkdownUrl,
|
|
45
|
+
toPageLink,
|
|
46
|
+
toUrlPath,
|
|
47
|
+
joinUrl,
|
|
48
|
+
ANNOUNCEMENTS_KEY,
|
|
49
|
+
DISMISSED_STORAGE_KEY,
|
|
50
|
+
DISMISSED_CLASS
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=chunk-42Z6H7IN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/util.ts"],"sourcesContent":["const HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n/** Escapes the characters that are special in HTML text and attribute values. */\nexport function escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (character) => HTML_ESCAPES[character]);\n}\n\n/**\n * Escapes text so it stays literal in Markdown and in VitePress's Vue\n * templates. Text with braces goes in a `v-pre` span, since Vue would otherwise\n * evaluate `{{ }}` even when the Markdown escaped it.\n */\nexport function escapeMarkdown(text: string): string {\n const escaped = text\n .replace(/[\\\\`*_[\\]#|~]/g, '\\\\$&')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;\n}\n\n/** HTML text that Vue leaves alone: braces go in a `v-pre` span. */\nexport function escapeTemplateText(text: string): string {\n const escaped = escapeHtml(text);\n return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;\n}\n\n/** A URL with a scheme (`https:`, `mailto:`) or protocol-relative (`//cdn`). */\nexport function isExternalUrl(url: string): boolean {\n return /^(?:[a-z][a-z\\d+.-]*:|\\/\\/)/i.test(url);\n}\n\n/** Encodes a URL for a Markdown link destination: spaces and parentheses would end it early. */\nexport function toMarkdownUrl(url: string): string {\n return encodeURI(url).replace(/[()]/g, (character) => (character === '(' ? '%28' : '%29'));\n}\n\n/** A Markdown link to a page by its served path, which VitePress then checks and gives the site's base. */\nexport function toPageLink(text: string, routePath: string): string {\n return `[${escapeMarkdown(text)}](${toMarkdownUrl(`/${routePath}`)})`;\n}\n\n/** A page's URL path, without the site's base: `news/index.md` is `/news/`, `news/launch.md` is `/news/launch`. */\nexport function toUrlPath(routePath: string, cleanUrls: boolean): string {\n return `/${routePath.replace(/(^|\\/)index\\.md$/, '$1').replace(/\\.md$/, cleanUrls ? '' : '.html')}`;\n}\n\n/** `base` and `urlPath` joined with exactly one slash between them. */\nexport function joinUrl(base: string, urlPath: string): string {\n return `${base.replace(/\\/+$/, '')}/${urlPath.replace(/^\\/+/, '')}`;\n}\n\n/** Where the announcement banner's data sits on VitePress's site data: `useData().site.value.blogAnnouncements`. */\nexport const ANNOUNCEMENTS_KEY = 'blogAnnouncements';\n\n/** The `localStorage` key holding the ids of the announcements a visitor dismissed, as a JSON array. */\nexport const DISMISSED_STORAGE_KEY = 'vp-blog-announcements-dismissed';\n\n/** The class on `<html>` that hides a dismissed banner before the page hydrates. */\nexport const DISMISSED_CLASS = 'vp-blog-announcement-dismissed';\n"],"mappings":";AAAA,IAAM,eAAuC;AAAA,EAC3C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAGO,SAAS,WAAW,OAAuB;AAChD,SAAO,MAAM,QAAQ,YAAY,CAAC,cAAc,aAAa,SAAS,CAAC;AACzE;AAOO,SAAS,eAAe,MAAsB;AACnD,QAAM,UAAU,KACb,QAAQ,kBAAkB,MAAM,EAChC,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM;AACvB,SAAO,OAAO,KAAK,OAAO,IAAI,eAAe,OAAO,YAAY;AAClE;AAGO,SAAS,mBAAmB,MAAsB;AACvD,QAAM,UAAU,WAAW,IAAI;AAC/B,SAAO,OAAO,KAAK,OAAO,IAAI,eAAe,OAAO,YAAY;AAClE;AAGO,SAAS,cAAc,KAAsB;AAClD,SAAO,+BAA+B,KAAK,GAAG;AAChD;AAGO,SAAS,cAAc,KAAqB;AACjD,SAAO,UAAU,GAAG,EAAE,QAAQ,SAAS,CAAC,cAAe,cAAc,MAAM,QAAQ,KAAM;AAC3F;AAGO,SAAS,WAAW,MAAc,WAA2B;AAClE,SAAO,IAAI,eAAe,IAAI,CAAC,KAAK,cAAc,IAAI,SAAS,EAAE,CAAC;AACpE;AAGO,SAAS,UAAU,WAAmB,WAA4B;AACvE,SAAO,IAAI,UAAU,QAAQ,oBAAoB,IAAI,EAAE,QAAQ,SAAS,YAAY,KAAK,OAAO,CAAC;AACnG;AAGO,SAAS,QAAQ,MAAc,SAAyB;AAC7D,SAAO,GAAG,KAAK,QAAQ,QAAQ,EAAE,CAAC,IAAI,QAAQ,QAAQ,QAAQ,EAAE,CAAC;AACnE;AAGO,IAAM,oBAAoB;AAG1B,IAAM,wBAAwB;AAG9B,IAAM,kBAAkB;","names":[]}
|