@binarynoir/vitepress-blog 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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. Sticky
37
- posts are pinned to the top, the rest are split into pages, and a keyword
38
- list above them filters the posts by tag.
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 keyword
47
- filtering in the browser.
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 keyword filtering (?tag=release) on blog pages.
86
+ // Pages (?page=2) and filters (?category=, ?tag=, ?year=, ?month=) on blog pages.
87
87
  useBlog();
88
88
  },
89
89
  };
@@ -133,38 +133,57 @@ blog:
133
133
  image: ./launch.png
134
134
  imageAlt: The release dashboard
135
135
  author: [jane, Sam Lee]
136
- tags: [release, team-a]
137
- category: Engineering
136
+ tags: [team-a, dashboard]
137
+ category: Releases
138
138
  scope: site
139
- sticky: true
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 | A list or a comma-separated string. Shown as chips and written to the feed as categories. |
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
- | `sticky` | `false` | Pinned to the top of its blog pages, above the paginated posts, with a Pinned badge. See [Sticky posts](#sticky-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. |
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. |
163
163
 
164
164
  Title, description, date, author(s), image and tags may also be written at the
165
165
  top level of the frontmatter, as other blog tools expect. A value under `blog`
166
166
  wins.
167
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
+
168
187
  ### Dates and times
169
188
 
170
189
  `date` and `updated` take a date, or a date and a time, in any of these forms:
@@ -248,57 +267,103 @@ Notes from Team A.
248
267
 
249
268
  The list goes where `<!-- blog-posts -->` is, or after the page's content.
250
269
 
251
- | Key | Default | What it does |
252
- | ------------- | ------------------------------ | ----------------------------------------------------------------------------------------------- |
253
- | `scope` | `section` | `site` makes this the main blog. See [Scopes](#scopes). |
254
- | `folders` | the page's folder | Folders to list posts from, relative to the source directory; `./` and `../` are page-relative. |
255
- | `tags` | all | Only list posts with at least one of these tags. |
256
- | `limit` | all | Most posts to list. The feed has its own limit. |
257
- | `layout` | `list` | `list` (image beside text), `grid` (cards) or `compact` (dates and titles). |
258
- | `groupBy` | none | `year` puts a heading above each year's posts. |
259
- | `showTags` | `true` | `false` hides tag chips on cards. |
260
- | `tagList` | `true` | A keyword list above the posts: every tag with its count, each a filter. `false` leaves it out. |
261
- | `pageSize` | `10` (the `pageSize` option) | Posts per page. `0` shows them all on one page. |
262
- | `stickyLimit` | all (the `stickyLimit` option) | Most sticky posts pinned to the top. Extra ones take their place by date. |
263
- | `rssLink` | `true` (the `rssLink` option) | A "Subscribe" link with the RSS icon to this blog's feed. `false` leaves it out. |
264
- | `feed` | on | `false` turns the feed off, or `{ title, description, limit, path }` to set it up. |
265
-
266
- ### Sticky posts
267
-
268
- `sticky: true` pins a post to the top of every blog page that lists it, in its
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
+ | `categoryLimit` | `8` | Categories shown before "+N more", most used first. `0` shows them all. |
283
+ | `tagList` | `true` | A row of keywords in the browse panel, each with its count. `false` leaves it out. |
284
+ | `tagLimit` | `12` | Keywords shown before "+N more", most used first. `0` shows them all. |
285
+ | `tagMinCount` | `1` | Leave keywords used by fewer posts out of the browse panel. They still show on cards. |
286
+ | `archive` | `true` | Rows of years, and each year's months, in the browse panel. `false` leaves them out. |
287
+ | `pageSize` | `10` (the `pageSize` option) | Posts per page. `0` shows them all on one page. |
288
+ | `pinLimit` | all (the `pinLimit` option) | Most pinned posts kept at the top. Extra ones take their place by date. |
289
+ | `rssLink` | `true` (the `rssLink` option) | A "Subscribe" link with the RSS icon to this blog's feed. `false` leaves it out. |
290
+ | `feed` | on | `false` turns the feed off, or `{ title, description, limit, path }` to set it up. |
291
+
292
+ ### Pinned posts
293
+
294
+ `pinned: true` keeps a post at the top of every blog page that lists it, in its
269
295
  own section above the other posts, with a Pinned badge. Pinned posts show on
270
296
  the first page only and don't count toward `pageSize`. When more posts are
271
- sticky than a page's `stickyLimit`, the newest ones are pinned and the others
272
- take their place by date. Feeds ignore stickiness and stay in date order.
297
+ pinned than a page's `pinLimit`, the newest ones stay on top and the others
298
+ take their place by date. Feeds ignore pinning and stay in date order.
299
+
300
+ `sticky` and `stickyLimit`, the old names of `pinned` and `pinLimit`, are
301
+ still read, so existing posts and pages keep working.
273
302
 
274
- To keep sticky posts on top of an [Auto Sidebar](https://binarynoir.tech/plugins/auto-sidebar/)
275
- sidebar too, sort the blog folder by stickiness, then date:
303
+ To keep pinned posts on top of an [Auto Sidebar](https://binarynoir.tech/plugins/auto-sidebar/)
304
+ sidebar too, sort the blog folder by pinning, then date:
276
305
 
277
306
  ```txt
278
307
  # docs/blog/.sidebar
279
308
  index.md:All posts
280
- .sort: blog.sticky desc, date desc
309
+ .sort: blog.pinned desc, date desc
281
310
  ```
282
311
 
283
- The sidebar pins every sticky post; `stickyLimit` applies to blog pages.
312
+ The sidebar pins every pinned post; `pinLimit` applies to blog pages.
284
313
 
285
- ### Pages and keywords
314
+ ### Browsing: pages, categories, keywords and dates
286
315
 
287
316
  Every post is written into the blog page's HTML, so search engines, local
288
317
  search and readers without JavaScript see them all. With `useBlog()` in your
289
318
  theme (see [Usage](#usage)), the browser then shows one page at a time and
290
- filters by keyword, both from the URL:
291
-
292
- - `?page=2` shows the second page. Previous, page number and Next links are
293
- added below the posts when there is more than one page.
294
- - `?tag=release` shows only posts tagged `release` (in any case), with a
295
- "Posts tagged release · Show all posts" line above them. Pages then count
296
- the matching posts only.
297
-
298
- Keywords are links everywhere they appear: in the keyword list at the top of
299
- a blog page, on each card, and in a post's byline, where they link to the
300
- post's own blog, filtered. They are ordinary links, so VitePress follows them
301
- without reloading the page, and each filtered view has its own URL to share.
319
+ filters the posts, all from the URL:
320
+
321
+ | URL | Shows |
322
+ | ------------------- | -------------------------------------------------------- |
323
+ | `?page=2` | The second page. Page links are added below the posts. |
324
+ | `?category=product` | Posts in the `Product` category, in any case. |
325
+ | `?tag=release` | Posts tagged `release`, in any case. |
326
+ | `?year=2026` | Posts published in 2026, in the `timeZone` option. |
327
+ | `?month=2026-10` | Posts published in October 2026. It wins over `?year=`. |
328
+ | `?announcements` | Announcements only. See [Announcements](#announcements). |
329
+
330
+ Filters combine: `?category=engineering&tag=api&year=2026` shows the
331
+ engineering posts tagged `api` from 2026. Pages count the matching posts only.
332
+
333
+ **The browse panel** above the posts has a row each for categories, keywords
334
+ and the archive, each chip with its post count. It is a flat toolbar between
335
+ two rules, with the blog's "Subscribe" link at the end of its first row.
336
+
337
+ - **Chips combine.** A chip adds its filter to the ones in view; another chip
338
+ in the same row replaces it, and clicking the active chip takes it off again.
339
+ - **Counts follow the view.** Each count says how many posts the chip would
340
+ show with the other filters kept, so readers can see where a click leads.
341
+ Chips that would show nothing are dimmed.
342
+ - **Years open into months.** Choosing a year shows a row of its months below
343
+ the years (with only one year, its months always show). A month keeps its
344
+ year active, and clicking it again steps back out to the whole year.
345
+ - **It stays short with many values.** Categories and keywords are ranked by
346
+ how many posts use them, and a row shows its first `categoryLimit` (8) or
347
+ `tagLimit` (12), then "+N more". That opens the full list alphabetically,
348
+ and a row of more than 30 values also gets a field to narrow it. An active
349
+ chip always shows. `tagMinCount: 2` leaves out keywords used only once.
350
+ - **One line per row on phones.** On screens narrower than 640px, each row is
351
+ a single line that scrolls sideways, its end fading while there is more.
352
+ Chips are 32px high on touch screens.
353
+ - **Rows only appear when they can narrow the posts.** No categories, or one
354
+ category that every post is in, means no category row. The archive needs
355
+ posts from more than one month, and with only one year, it is that year's
356
+ months alone.
357
+
358
+ **The active filters** show above the posts while any filter is on: how many
359
+ posts match ("4 posts", or "No posts match these filters."), a chip for each
360
+ filter that removes just that one, and "Show all posts" to clear them all. It
361
+ is a status region, so screen readers announce the new count.
362
+
363
+ Categories and keywords are links wherever they appear: in the browse panel,
364
+ on each card, and in a post's byline, where they link to the post's own blog,
365
+ filtered. They are ordinary links, so VitePress follows them without
366
+ reloading the page, and each filtered view has its own URL to share.
302
367
 
303
368
  ## Announcements
304
369
 
@@ -363,28 +428,30 @@ own settings:
363
428
  ::: blog-posts
364
429
  blog: /blog/
365
430
  limit: 3
366
- stickyLimit: 1
431
+ pinLimit: 1
367
432
  layout: grid
368
433
  more: All posts →
369
434
  :::
370
435
  ```
371
436
 
372
- | Key | Default | What it does |
373
- | ------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------- |
374
- | `blog` | the page's own blog | The blog to show: its URL (`/blog/`) or file (`blog/index.md`); `./` and `../` are page-relative. |
375
- | `limit` | `5` | Most posts shown, pinned ones included. `0` shows them all. |
376
- | `stickyLimit` | the blog page's | Most sticky posts pinned to the top. `0` pins them all. |
377
- | `layout` | the blog page's | `list`, `grid` or `compact`. |
378
- | `showTags` | the blog page's | `false` hides keyword chips. |
379
- | `tags` | all | Only show posts with at least one of these keywords. |
380
- | `more` | `All posts in {blog}` | Text of the link to the blog page below the posts. `false` leaves the link out. |
381
- | `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. |
437
+ | Key | Default | What it does |
438
+ | -------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------- |
439
+ | `blog` | the page's own blog | The blog to show: its URL (`/blog/`) or file (`blog/index.md`); `./` and `../` are page-relative. |
440
+ | `limit` | `5` | Most posts shown, pinned ones included. `0` shows them all. |
441
+ | `pinLimit` | the blog page's | Most pinned posts kept at the top. `0` pins them all. |
442
+ | `layout` | the blog page's | `list`, `grid` or `compact`. |
443
+ | `showTags` | the blog page's | `false` hides keyword chips. |
444
+ | `showCategory` | the blog page's | `false` hides the category above each title. |
445
+ | `tags` | all | Only show posts with at least one of these keywords. |
446
+ | `categories` | all | Only show posts in one of these categories. |
447
+ | `more` | `All posts in {blog}` | Text of the link to the blog page below the posts. `false` leaves the link out. |
448
+ | `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. |
382
449
 
383
450
  - The page's own blog is the deepest section blog covering its folder, else
384
451
  the main blog, so a block on the home page shows the main blog and one on
385
452
  a section's page shows that section's blog.
386
453
  - It shows the posts its blog lists, newest first, with no drafts unless the
387
- `drafts` option is on. Keyword chips link to the blog page, filtered.
454
+ `drafts` option is on. Category and keyword links go to the blog page, filtered.
388
455
  - It is written into the page at build time, like blog pages, and refreshes in
389
456
  `vitepress dev` when a post changes. A page can hold several blocks. A
390
457
  block inside a code fence is left as written.
@@ -464,7 +531,7 @@ link that copy. Public paths (`/cover.png`) and URLs are linked as they are.
464
531
  | `feedLimit` | `20` | Most posts in each feed. |
465
532
  | `imagesDir` | `'blog-images'` | Output folder for copied post images. |
466
533
  | `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`. |
534
+ | `pinLimit` | `0` (all) | Most pinned posts on blog pages that don't set `pinLimit`. |
468
535
  | `rssLink` | `true` | A "Subscribe" RSS link on blog pages and blocks that don't set `rssLink`. |
469
536
  | `labels` | English | Words the plugin writes, for translating. See below. |
470
537
 
@@ -481,18 +548,28 @@ withBlog(config, {
481
548
  older: 'Älterer Beitrag',
482
549
  allPosts: 'Alle Beiträge in {blog}',
483
550
  draft: 'Entwurf',
484
- sticky: 'Angeheftet',
551
+ pinned: 'Angeheftet',
485
552
  announcement: 'Ankündigung',
486
553
  announcements: 'Ankündigungen',
487
- noAnnouncements: 'Keine Ankündigungen.',
488
554
  announcementCount: '{n} Ankündigungen',
489
555
  dismiss: 'Schließen',
490
556
  noPosts: 'Noch keine Beiträge.',
491
557
  undated: 'Ohne Datum',
558
+ browse: 'Beiträge durchsuchen',
559
+ categories: 'Kategorien',
492
560
  tags: 'Schlagwörter',
493
- tagged: 'Beiträge mit {tag}',
561
+ archive: 'Archiv',
562
+ category: 'Kategorie',
563
+ tag: 'Schlagwort',
564
+ postCount: '{n} Beiträge',
565
+ postCountOne: '{n} Beitrag',
566
+ noMatches: 'Keine Beiträge für diese Filter.',
567
+ removeFilter: 'Filter entfernen: {filter}',
494
568
  showAll: 'Alle Beiträge zeigen',
495
- noTagged: 'Keine Beiträge mit {tag}.',
569
+ more: '+{n} weitere',
570
+ less: 'Weniger zeigen',
571
+ filter: '{row} filtern',
572
+ noFilterMatches: 'Keine Treffer.',
496
573
  previous: 'Zurück',
497
574
  next: 'Weiter',
498
575
  pages: 'Seiten',
@@ -516,6 +593,8 @@ mode. Override these to restyle it without new rules:
516
593
  | `--vp-blog-tag-bg` | `var(--vp-c-default-soft)` |
517
594
  | `--vp-blog-tag-color` | `var(--vp-c-text-2)` |
518
595
  | `--vp-blog-accent` | `var(--vp-c-brand-1)` |
596
+ | `--vp-blog-browse-bg` | `transparent` |
597
+ | `--vp-blog-facet-label-width` | `6.5rem` |
519
598
  | `--vp-blog-announcement-height` | `40px` |
520
599
  | `--vp-blog-announcement-bg` | `var(--vp-button-brand-bg)` |
521
600
  | `--vp-blog-announcement-color` | `var(--vp-button-brand-text)` |