@binarynoir/vitepress-blog 0.3.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 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,89 @@ 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
+ | `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
269
292
  own section above the other posts, with a Pinned badge. Pinned posts show on
270
293
  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.
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.
273
299
 
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:
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:
276
302
 
277
303
  ```txt
278
304
  # docs/blog/.sidebar
279
305
  index.md:All posts
280
- .sort: blog.sticky desc, date desc
306
+ .sort: blog.pinned desc, date desc
281
307
  ```
282
308
 
283
- The sidebar pins every sticky post; `stickyLimit` applies to blog pages.
309
+ The sidebar pins every pinned post; `pinLimit` applies to blog pages.
284
310
 
285
- ### Pages and keywords
311
+ ### Browsing: pages, categories, keywords and dates
286
312
 
287
313
  Every post is written into the blog page's HTML, so search engines, local
288
314
  search and readers without JavaScript see them all. With `useBlog()` in your
289
315
  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.
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.
302
353
 
303
354
  ## Announcements
304
355
 
@@ -363,28 +414,30 @@ own settings:
363
414
  ::: blog-posts
364
415
  blog: /blog/
365
416
  limit: 3
366
- stickyLimit: 1
417
+ pinLimit: 1
367
418
  layout: grid
368
419
  more: All posts →
369
420
  :::
370
421
  ```
371
422
 
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. |
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. |
382
435
 
383
436
  - The page's own blog is the deepest section blog covering its folder, else
384
437
  the main blog, so a block on the home page shows the main blog and one on
385
438
  a section's page shows that section's blog.
386
439
  - 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.
440
+ `drafts` option is on. Category and keyword links go to the blog page, filtered.
388
441
  - It is written into the page at build time, like blog pages, and refreshes in
389
442
  `vitepress dev` when a post changes. A page can hold several blocks. A
390
443
  block inside a code fence is left as written.
@@ -464,7 +517,7 @@ link that copy. Public paths (`/cover.png`) and URLs are linked as they are.
464
517
  | `feedLimit` | `20` | Most posts in each feed. |
465
518
  | `imagesDir` | `'blog-images'` | Output folder for copied post images. |
466
519
  | `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`. |
520
+ | `pinLimit` | `0` (all) | Most pinned posts on blog pages that don't set `pinLimit`. |
468
521
  | `rssLink` | `true` | A "Subscribe" RSS link on blog pages and blocks that don't set `rssLink`. |
469
522
  | `labels` | English | Words the plugin writes, for translating. See below. |
470
523
 
@@ -481,18 +534,24 @@ withBlog(config, {
481
534
  older: 'Älterer Beitrag',
482
535
  allPosts: 'Alle Beiträge in {blog}',
483
536
  draft: 'Entwurf',
484
- sticky: 'Angeheftet',
537
+ pinned: 'Angeheftet',
485
538
  announcement: 'Ankündigung',
486
539
  announcements: 'Ankündigungen',
487
- noAnnouncements: 'Keine Ankündigungen.',
488
540
  announcementCount: '{n} Ankündigungen',
489
541
  dismiss: 'Schließen',
490
542
  noPosts: 'Noch keine Beiträge.',
491
543
  undated: 'Ohne Datum',
544
+ browse: 'Beiträge durchsuchen',
545
+ categories: 'Kategorien',
492
546
  tags: 'Schlagwörter',
493
- tagged: 'Beiträge mit {tag}',
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}',
494
554
  showAll: 'Alle Beiträge zeigen',
495
- noTagged: 'Keine Beiträge mit {tag}.',
496
555
  previous: 'Zurück',
497
556
  next: 'Weiter',
498
557
  pages: 'Seiten',
@@ -516,6 +575,8 @@ mode. Override these to restyle it without new rules:
516
575
  | `--vp-blog-tag-bg` | `var(--vp-c-default-soft)` |
517
576
  | `--vp-blog-tag-color` | `var(--vp-c-text-2)` |
518
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` |
519
580
  | `--vp-blog-announcement-height` | `40px` |
520
581
  | `--vp-blog-announcement-bg` | `var(--vp-button-brand-bg)` |
521
582
  | `--vp-blog-announcement-color` | `var(--vp-button-brand-text)` |