@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/README.md
CHANGED
|
@@ -33,9 +33,9 @@ blog:
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
- **Blog pages** (`blog-index` in the frontmatter) get a list of posts: cards
|
|
36
|
-
with image, title, date, reading time, authors, summary and tags.
|
|
37
|
-
posts
|
|
38
|
-
|
|
36
|
+
with image, title, date, reading time, authors, summary and tags. Pinned
|
|
37
|
+
posts stay at the top, the rest are split into pages, and a browse
|
|
38
|
+
panel above them filters the posts by category, keyword, year and month.
|
|
39
39
|
- **Posts** (`blog` in the frontmatter) get a byline under their title, links to
|
|
40
40
|
the newer and older posts, and Open Graph and Twitter card tags.
|
|
41
41
|
- **Every blog page gets an RSS feed**, written beside it at build time and
|
|
@@ -43,8 +43,8 @@ blog:
|
|
|
43
43
|
- Lists are plain Markdown generated at build time, with every post in the
|
|
44
44
|
HTML, so they show up in search and work without JavaScript. Links go
|
|
45
45
|
through VitePress, so they get your `base` and `cleanUrls` and are checked
|
|
46
|
-
for dead links. A small composable, `useBlog()`, adds pages and
|
|
47
|
-
|
|
46
|
+
for dead links. A small composable, `useBlog()`, adds pages and filtering
|
|
47
|
+
in the browser.
|
|
48
48
|
|
|
49
49
|
## Install
|
|
50
50
|
|
|
@@ -83,7 +83,7 @@ import '@binarynoir/vitepress-blog/style.css';
|
|
|
83
83
|
export default {
|
|
84
84
|
extends: DefaultTheme,
|
|
85
85
|
setup() {
|
|
86
|
-
// Pages (?page=2) and
|
|
86
|
+
// Pages (?page=2) and filters (?category=, ?tag=, ?year=, ?month=) on blog pages.
|
|
87
87
|
useBlog();
|
|
88
88
|
},
|
|
89
89
|
};
|
|
@@ -133,37 +133,57 @@ blog:
|
|
|
133
133
|
image: ./launch.png
|
|
134
134
|
imageAlt: The release dashboard
|
|
135
135
|
author: [jane, Sam Lee]
|
|
136
|
-
tags: [
|
|
137
|
-
category:
|
|
136
|
+
tags: [team-a, dashboard]
|
|
137
|
+
category: Releases
|
|
138
138
|
scope: site
|
|
139
|
-
|
|
139
|
+
pinned: true
|
|
140
140
|
---
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
| Key | Default | What it does
|
|
144
|
-
| --------------- | ------------------------------------------ |
|
|
145
|
-
| `title` | the page's `title`, then its `#` heading | Title in lists, feeds and social cards.
|
|
146
|
-
| `summary` | the page's `description`, then first prose | One or two sentences for cards, feeds and `<meta name="description">`.
|
|
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
|
-
| `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
|
-
| `imageAlt` | `''` | Alt text for the image.
|
|
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.
|
|
152
|
-
| `imagePosition` | the centre | Which part of a cropped image stays in view, as CSS `object-position`: `top`, `left`, `30% 20%`.
|
|
153
|
-
| `author` | the `defaultAuthor` option | A name, a key from the `authors` option, `{ name, url, email }`, or a list of these. `authors` works too.
|
|
154
|
-
| `tags` | none |
|
|
155
|
-
| `category` | none | Written to the feed and `article:section`.
|
|
156
|
-
| `scope` | `section` | `site` (or `global`) also lists the post on the main blog. See [Scopes](#scopes).
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
143
|
+
| Key | Default | What it does |
|
|
144
|
+
| --------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| `title` | the page's `title`, then its `#` heading | Title in lists, feeds and social cards. |
|
|
146
|
+
| `summary` | the page's `description`, then first prose | One or two sentences for cards, feeds and `<meta name="description">`. |
|
|
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
|
+
| `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
|
+
| `imageAlt` | `''` | Alt text for the image. |
|
|
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. |
|
|
152
|
+
| `imagePosition` | the centre | Which part of a cropped image stays in view, as CSS `object-position`: `top`, `left`, `30% 20%`. |
|
|
153
|
+
| `author` | the `defaultAuthor` option | A name, a key from the `authors` option, `{ name, url, email }`, or a list of these. `authors` works too. |
|
|
154
|
+
| `tags` | none | Any number of topics: a list or a comma-separated string. Shown as chips, each a filter, and written to the feed as categories. See [Category or tags?](#category-or-tags). |
|
|
155
|
+
| `category` | none | The one kind of post this is, such as `Releases`. Shown above its title on cards and in its byline, each a filter. Written to the feed and `article:section`. See [Category or tags?](#category-or-tags). |
|
|
156
|
+
| `scope` | `section` | `site` (or `global`) also lists the post on the main blog. See [Scopes](#scopes). |
|
|
157
|
+
| `pinned` | `false` | Kept at the top of its blog pages, above the paginated posts, with a Pinned badge. See [Pinned posts](#pinned-posts). |
|
|
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). |
|
|
160
|
+
| `byline` | the `byline` option | `false` hides this post's byline. |
|
|
161
|
+
| `cover` | the `cover` option | `true` shows this post's image under its byline. |
|
|
162
|
+
| `postNav` | the `postNav` option | `false` hides this post's newer / older links. |
|
|
162
163
|
|
|
163
164
|
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
|
+
### Category or tags?
|
|
169
|
+
|
|
170
|
+
Both sort posts, in different ways:
|
|
171
|
+
|
|
172
|
+
- **`category` is the one way you slice the blog.** Each post has exactly one,
|
|
173
|
+
from a short list that rarely changes. It is the label above the title on
|
|
174
|
+
cards, and the first row of the browse panel. Pick the axis your readers
|
|
175
|
+
browse by most: the kind of post (Releases, Guides, News), the team, or the
|
|
176
|
+
product line.
|
|
177
|
+
- **`tags` are everything else a post is about.** A post can have several, and
|
|
178
|
+
new ones can appear at any time: the product it changes, the feature, the
|
|
179
|
+
topic.
|
|
180
|
+
- **Don't say the same thing twice.** If a post's category is Releases, a
|
|
181
|
+
`release` tag adds nothing.
|
|
182
|
+
- **`categories:` on a blog page is a filter, not a label.**
|
|
183
|
+
`blog-index: { categories: [Releases] }` makes a page that lists only
|
|
184
|
+
releases. `tags:` works the same way there, and both work in
|
|
185
|
+
`::: blog-posts` blocks.
|
|
186
|
+
|
|
167
187
|
### Dates and times
|
|
168
188
|
|
|
169
189
|
`date` and `updated` take a date, or a date and a time, in any of these forms:
|
|
@@ -196,6 +216,35 @@ blog:
|
|
|
196
216
|
- Feeds and social tags use the exact moment: `pubDate`, `lastBuildDate`,
|
|
197
217
|
`article:published_time` and `article:modified_time`.
|
|
198
218
|
|
|
219
|
+
### Drafts
|
|
220
|
+
|
|
221
|
+
`draft: true` keeps a post out of blog lists, `::: blog-posts` blocks and
|
|
222
|
+
feeds, but the page is still built and can be opened by its URL. With the
|
|
223
|
+
`drafts` option on, drafts are listed too, with a Draft badge, which is handy in
|
|
224
|
+
`vitepress dev`.
|
|
225
|
+
|
|
226
|
+
To leave drafts out of the build entirely, use
|
|
227
|
+
[`@binarynoir/vitepress-exclude`](https://binarynoir.tech/plugins/exclude/)'s
|
|
228
|
+
`excludeFrontmatter` option, and the same option in Auto Sidebar and Auto
|
|
229
|
+
Navbar to hide them from the menus. The blog lists only what VitePress builds,
|
|
230
|
+
so nothing else changes:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// Drafts stay out of the production build, and show (marked) in `vitepress dev`.
|
|
234
|
+
const isBuild = process.env.NODE_ENV === 'production';
|
|
235
|
+
const excludeFrontmatter = isBuild ? ['draft', 'blog.draft'] : [];
|
|
236
|
+
const blogOptions = { drafts: !isBuild };
|
|
237
|
+
|
|
238
|
+
export default defineConfig({
|
|
239
|
+
srcExclude: excludeSrc(docsRoot, { excludeFrontmatter }),
|
|
240
|
+
themeConfig: {
|
|
241
|
+
sidebar: generateSidebar(docsRoot, { excludeFrontmatter }),
|
|
242
|
+
nav: generateNav(docsRoot, { excludeFrontmatter }),
|
|
243
|
+
},
|
|
244
|
+
// ...the blog's pieces, with blogOptions
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
199
248
|
## Blog pages
|
|
200
249
|
|
|
201
250
|
A page with `blog-index` in its frontmatter lists posts. `blog-index: true` is a
|
|
@@ -218,57 +267,140 @@ Notes from Team A.
|
|
|
218
267
|
|
|
219
268
|
The list goes where `<!-- blog-posts -->` is, or after the page's content.
|
|
220
269
|
|
|
221
|
-
| Key
|
|
222
|
-
|
|
|
223
|
-
| `scope`
|
|
224
|
-
| `folders`
|
|
225
|
-
| `tags`
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
`
|
|
270
|
+
| Key | Default | What it does |
|
|
271
|
+
| -------------- | ----------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
272
|
+
| `scope` | `section` | `site` makes this the main blog. See [Scopes](#scopes). |
|
|
273
|
+
| `folders` | the page's folder | Folders to list posts from, relative to the source directory; `./` and `../` are page-relative. |
|
|
274
|
+
| `tags` | all | Only list posts with at least one of these tags. |
|
|
275
|
+
| `categories` | all | Only list posts in one of these categories. |
|
|
276
|
+
| `limit` | all | Most posts to list. The feed has its own limit. |
|
|
277
|
+
| `layout` | `list` | `list` (image beside text), `grid` (cards) or `compact` (dates and titles). |
|
|
278
|
+
| `groupBy` | none | `year` or `month` puts a heading ("2026", "October 2026") above each year's or month's posts. |
|
|
279
|
+
| `showTags` | `true` | `false` hides tag chips on cards. |
|
|
280
|
+
| `showCategory` | `true` | `false` hides the category above each card's title. |
|
|
281
|
+
| `categoryList` | `true` | A row of categories in the browse panel, each with its count. `false` leaves it out. |
|
|
282
|
+
| `tagList` | `true` | A row of keywords in the browse panel, each with its count. `false` leaves it out. |
|
|
283
|
+
| `archive` | `true` | Rows of years, and each year's months, in the browse panel. `false` leaves them out. |
|
|
284
|
+
| `pageSize` | `10` (the `pageSize` option) | Posts per page. `0` shows them all on one page. |
|
|
285
|
+
| `pinLimit` | all (the `pinLimit` option) | Most pinned posts kept at the top. Extra ones take their place by date. |
|
|
286
|
+
| `rssLink` | `true` (the `rssLink` option) | A "Subscribe" link with the RSS icon to this blog's feed. `false` leaves it out. |
|
|
287
|
+
| `feed` | on | `false` turns the feed off, or `{ title, description, limit, path }` to set it up. |
|
|
288
|
+
|
|
289
|
+
### Pinned posts
|
|
290
|
+
|
|
291
|
+
`pinned: true` keeps a post at the top of every blog page that lists it, in its
|
|
239
292
|
own section above the other posts, with a Pinned badge. Pinned posts show on
|
|
240
293
|
the first page only and don't count toward `pageSize`. When more posts are
|
|
241
|
-
|
|
242
|
-
take their place by date. Feeds ignore
|
|
294
|
+
pinned than a page's `pinLimit`, the newest ones stay on top and the others
|
|
295
|
+
take their place by date. Feeds ignore pinning and stay in date order.
|
|
296
|
+
|
|
297
|
+
`sticky` and `stickyLimit`, the old names of `pinned` and `pinLimit`, are
|
|
298
|
+
still read, so existing posts and pages keep working.
|
|
243
299
|
|
|
244
|
-
To keep
|
|
245
|
-
sidebar too, sort the blog folder by
|
|
300
|
+
To keep pinned posts on top of an [Auto Sidebar](https://binarynoir.tech/plugins/auto-sidebar/)
|
|
301
|
+
sidebar too, sort the blog folder by pinning, then date:
|
|
246
302
|
|
|
247
303
|
```txt
|
|
248
304
|
# docs/blog/.sidebar
|
|
249
305
|
index.md:All posts
|
|
250
|
-
.sort: blog.
|
|
306
|
+
.sort: blog.pinned desc, date desc
|
|
251
307
|
```
|
|
252
308
|
|
|
253
|
-
The sidebar pins every
|
|
309
|
+
The sidebar pins every pinned post; `pinLimit` applies to blog pages.
|
|
254
310
|
|
|
255
|
-
###
|
|
311
|
+
### Browsing: pages, categories, keywords and dates
|
|
256
312
|
|
|
257
313
|
Every post is written into the blog page's HTML, so search engines, local
|
|
258
314
|
search and readers without JavaScript see them all. With `useBlog()` in your
|
|
259
315
|
theme (see [Usage](#usage)), the browser then shows one page at a time and
|
|
260
|
-
filters
|
|
316
|
+
filters the posts, all from the URL:
|
|
317
|
+
|
|
318
|
+
| URL | Shows |
|
|
319
|
+
| ------------------- | -------------------------------------------------------- |
|
|
320
|
+
| `?page=2` | The second page. Page links are added below the posts. |
|
|
321
|
+
| `?category=product` | Posts in the `Product` category, in any case. |
|
|
322
|
+
| `?tag=release` | Posts tagged `release`, in any case. |
|
|
323
|
+
| `?year=2026` | Posts published in 2026, in the `timeZone` option. |
|
|
324
|
+
| `?month=2026-10` | Posts published in October 2026. It wins over `?year=`. |
|
|
325
|
+
| `?announcements` | Announcements only. See [Announcements](#announcements). |
|
|
326
|
+
|
|
327
|
+
Filters combine: `?category=engineering&tag=api&year=2026` shows the
|
|
328
|
+
engineering posts tagged `api` from 2026. Pages count the matching posts only.
|
|
329
|
+
|
|
330
|
+
**The browse panel** above the posts has a row each for categories, keywords
|
|
331
|
+
and the archive, each chip with its post count:
|
|
332
|
+
|
|
333
|
+
- **Chips combine.** A chip adds its filter to the ones in view; another chip
|
|
334
|
+
in the same row replaces it, and clicking the active chip takes it off again.
|
|
335
|
+
- **Counts follow the view.** Each count says how many posts the chip would
|
|
336
|
+
show with the other filters kept, so readers can see where a click leads.
|
|
337
|
+
Chips that would show nothing are dimmed.
|
|
338
|
+
- **Years open into months.** Choosing a year shows a row of its months below
|
|
339
|
+
the years (with only one year, its months always show). A month keeps its
|
|
340
|
+
year active, and clicking it again steps back out to the whole year.
|
|
341
|
+
- Rows only appear when they have something to choose: no categories means no
|
|
342
|
+
category row, and the archive needs posts from more than one month.
|
|
343
|
+
|
|
344
|
+
**The active filters** show above the posts while any filter is on: how many
|
|
345
|
+
posts match ("4 posts", or "No posts match these filters."), a chip for each
|
|
346
|
+
filter that removes just that one, and "Show all posts" to clear them all. It
|
|
347
|
+
is a status region, so screen readers announce the new count.
|
|
348
|
+
|
|
349
|
+
Categories and keywords are links wherever they appear: in the browse panel,
|
|
350
|
+
on each card, and in a post's byline, where they link to the post's own blog,
|
|
351
|
+
filtered. They are ordinary links, so VitePress follows them without
|
|
352
|
+
reloading the page, and each filtered view has its own URL to share.
|
|
353
|
+
|
|
354
|
+
## Announcements
|
|
355
|
+
|
|
356
|
+
`announcement: true` puts a post in a banner across the top of every page,
|
|
357
|
+
above the nav bar: one line, the full width of the window, linked to the post.
|
|
358
|
+
A string sets the banner's text instead of the post's title.
|
|
261
359
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
360
|
+
```md
|
|
361
|
+
---
|
|
362
|
+
blog:
|
|
363
|
+
date: 2026-10-20 09:00
|
|
364
|
+
announcement: Scheduled maintenance on Friday, 18:00 UTC
|
|
365
|
+
---
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Add the banner to your theme's `layout-top` slot:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
// .vitepress/theme/index.ts
|
|
372
|
+
import { h } from 'vue';
|
|
373
|
+
import DefaultTheme from 'vitepress/theme';
|
|
374
|
+
import { BlogAnnouncementBanner, useBlog } from '@binarynoir/vitepress-blog/client';
|
|
375
|
+
import '@binarynoir/vitepress-blog/style.css';
|
|
376
|
+
|
|
377
|
+
export default {
|
|
378
|
+
extends: DefaultTheme,
|
|
379
|
+
Layout: () => h(DefaultTheme.Layout, null, { 'layout-top': () => h(BlogAnnouncementBanner) }),
|
|
380
|
+
setup() {
|
|
381
|
+
useBlog();
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
```
|
|
267
385
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
386
|
+
- **The newest one wins.** With several, the banner shows the newest and a
|
|
387
|
+
badge with how many there are. The badge opens a blog page filtered to
|
|
388
|
+
announcements (`/blog/?announcements`): the main blog when it lists them all,
|
|
389
|
+
else another blog page that does, else the newest one's own blog.
|
|
390
|
+
- **Visitors can clear it.** The close button dismisses every current
|
|
391
|
+
announcement, remembered in the browser. A newer one brings the banner back,
|
|
392
|
+
and its badge counts only the ones not yet dismissed. Changing a post's
|
|
393
|
+
`date` announces it again.
|
|
394
|
+
- **No jump on load.** The banner is rendered on the server, and a small
|
|
395
|
+
`<head>` script hides it before the page shows if the visitor dismissed it.
|
|
396
|
+
While it shows, it sets VitePress's `--vp-layout-top-height`, so the nav bar,
|
|
397
|
+
sidebar and content move down to make room.
|
|
398
|
+
- Announcements are marked on blog pages with an Announcement badge. Drafts
|
|
399
|
+
are only announced with the `drafts` option.
|
|
400
|
+
|
|
401
|
+
The banner reads its posts from VitePress's site data, which `withBlog` (or
|
|
402
|
+
`blogVitePlugin`) fills in; `blogVitePlugin` also has Vite bundle this package
|
|
403
|
+
for the server-side build, so the banner renders there.
|
|
272
404
|
|
|
273
405
|
## Latest posts on any page
|
|
274
406
|
|
|
@@ -282,28 +414,30 @@ own settings:
|
|
|
282
414
|
::: blog-posts
|
|
283
415
|
blog: /blog/
|
|
284
416
|
limit: 3
|
|
285
|
-
|
|
417
|
+
pinLimit: 1
|
|
286
418
|
layout: grid
|
|
287
419
|
more: All posts →
|
|
288
420
|
:::
|
|
289
421
|
```
|
|
290
422
|
|
|
291
|
-
| Key
|
|
292
|
-
|
|
|
293
|
-
| `blog`
|
|
294
|
-
| `limit`
|
|
295
|
-
| `
|
|
296
|
-
| `layout`
|
|
297
|
-
| `showTags`
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
423
|
+
| Key | Default | What it does |
|
|
424
|
+
| -------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
425
|
+
| `blog` | the page's own blog | The blog to show: its URL (`/blog/`) or file (`blog/index.md`); `./` and `../` are page-relative. |
|
|
426
|
+
| `limit` | `5` | Most posts shown, pinned ones included. `0` shows them all. |
|
|
427
|
+
| `pinLimit` | the blog page's | Most pinned posts kept at the top. `0` pins them all. |
|
|
428
|
+
| `layout` | the blog page's | `list`, `grid` or `compact`. |
|
|
429
|
+
| `showTags` | the blog page's | `false` hides keyword chips. |
|
|
430
|
+
| `showCategory` | the blog page's | `false` hides the category above each title. |
|
|
431
|
+
| `tags` | all | Only show posts with at least one of these keywords. |
|
|
432
|
+
| `categories` | all | Only show posts in one of these categories. |
|
|
433
|
+
| `more` | `All posts in {blog}` | Text of the link to the blog page below the posts. `false` leaves the link out. |
|
|
434
|
+
| `rssLink` | the blog page's | A "Subscribe" link with the RSS icon to the blog's feed, beside the link to the blog page. `false` leaves it out. |
|
|
301
435
|
|
|
302
436
|
- The page's own blog is the deepest section blog covering its folder, else
|
|
303
437
|
the main blog, so a block on the home page shows the main blog and one on
|
|
304
438
|
a section's page shows that section's blog.
|
|
305
439
|
- It shows the posts its blog lists, newest first, with no drafts unless the
|
|
306
|
-
`drafts` option is on.
|
|
440
|
+
`drafts` option is on. Category and keyword links go to the blog page, filtered.
|
|
307
441
|
- It is written into the page at build time, like blog pages, and refreshes in
|
|
308
442
|
`vitepress dev` when a post changes. A page can hold several blocks. A
|
|
309
443
|
block inside a code fence is left as written.
|
|
@@ -383,7 +517,7 @@ link that copy. Public paths (`/cover.png`) and URLs are linked as they are.
|
|
|
383
517
|
| `feedLimit` | `20` | Most posts in each feed. |
|
|
384
518
|
| `imagesDir` | `'blog-images'` | Output folder for copied post images. |
|
|
385
519
|
| `pageSize` | `10` | Posts per page on blog pages that don't set `pageSize`. `0` turns pages off. |
|
|
386
|
-
| `
|
|
520
|
+
| `pinLimit` | `0` (all) | Most pinned posts on blog pages that don't set `pinLimit`. |
|
|
387
521
|
| `rssLink` | `true` | A "Subscribe" RSS link on blog pages and blocks that don't set `rssLink`. |
|
|
388
522
|
| `labels` | English | Words the plugin writes, for translating. See below. |
|
|
389
523
|
|
|
@@ -400,13 +534,24 @@ withBlog(config, {
|
|
|
400
534
|
older: 'Älterer Beitrag',
|
|
401
535
|
allPosts: 'Alle Beiträge in {blog}',
|
|
402
536
|
draft: 'Entwurf',
|
|
403
|
-
|
|
537
|
+
pinned: 'Angeheftet',
|
|
538
|
+
announcement: 'Ankündigung',
|
|
539
|
+
announcements: 'Ankündigungen',
|
|
540
|
+
announcementCount: '{n} Ankündigungen',
|
|
541
|
+
dismiss: 'Schließen',
|
|
404
542
|
noPosts: 'Noch keine Beiträge.',
|
|
405
543
|
undated: 'Ohne Datum',
|
|
544
|
+
browse: 'Beiträge durchsuchen',
|
|
545
|
+
categories: 'Kategorien',
|
|
406
546
|
tags: 'Schlagwörter',
|
|
407
|
-
|
|
547
|
+
archive: 'Archiv',
|
|
548
|
+
category: 'Kategorie',
|
|
549
|
+
tag: 'Schlagwort',
|
|
550
|
+
postCount: '{n} Beiträge',
|
|
551
|
+
postCountOne: '{n} Beitrag',
|
|
552
|
+
noMatches: 'Keine Beiträge für diese Filter.',
|
|
553
|
+
removeFilter: 'Filter entfernen: {filter}',
|
|
408
554
|
showAll: 'Alle Beiträge zeigen',
|
|
409
|
-
noTagged: 'Keine Beiträge mit {tag}.',
|
|
410
555
|
previous: 'Zurück',
|
|
411
556
|
next: 'Weiter',
|
|
412
557
|
pages: 'Seiten',
|
|
@@ -421,15 +566,20 @@ withBlog(config, {
|
|
|
421
566
|
`style.css` uses VitePress's theme variables, so it follows light and dark
|
|
422
567
|
mode. Override these to restyle it without new rules:
|
|
423
568
|
|
|
424
|
-
| Variable
|
|
425
|
-
|
|
|
426
|
-
| `--vp-blog-card-bg`
|
|
427
|
-
| `--vp-blog-card-border`
|
|
428
|
-
| `--vp-blog-card-radius`
|
|
429
|
-
| `--vp-blog-meta-color`
|
|
430
|
-
| `--vp-blog-tag-bg`
|
|
431
|
-
| `--vp-blog-tag-color`
|
|
432
|
-
| `--vp-blog-accent`
|
|
569
|
+
| Variable | Default |
|
|
570
|
+
| ------------------------------- | ----------------------------- |
|
|
571
|
+
| `--vp-blog-card-bg` | `var(--vp-c-bg-soft)` |
|
|
572
|
+
| `--vp-blog-card-border` | `var(--vp-c-divider)` |
|
|
573
|
+
| `--vp-blog-card-radius` | `12px` |
|
|
574
|
+
| `--vp-blog-meta-color` | `var(--vp-c-text-2)` |
|
|
575
|
+
| `--vp-blog-tag-bg` | `var(--vp-c-default-soft)` |
|
|
576
|
+
| `--vp-blog-tag-color` | `var(--vp-c-text-2)` |
|
|
577
|
+
| `--vp-blog-accent` | `var(--vp-c-brand-1)` |
|
|
578
|
+
| `--vp-blog-browse-bg` | `var(--vp-c-bg-soft)` |
|
|
579
|
+
| `--vp-blog-facet-label-width` | `6.5rem` |
|
|
580
|
+
| `--vp-blog-announcement-height` | `40px` |
|
|
581
|
+
| `--vp-blog-announcement-bg` | `var(--vp-button-brand-bg)` |
|
|
582
|
+
| `--vp-blog-announcement-color` | `var(--vp-button-brand-text)` |
|
|
433
583
|
|
|
434
584
|
Every generated element has a `vp-blog-` class (`vp-blog-card`,
|
|
435
585
|
`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":[]}
|