@binarynoir/vitepress-blog 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -10
- package/dist/chunk-42Z6H7IN.js +52 -0
- package/dist/chunk-42Z6H7IN.js.map +1 -0
- package/dist/{chunk-PWNWSYI4.js → chunk-LYTJBMVK.js} +51 -42
- package/dist/chunk-LYTJBMVK.js.map +1 -0
- package/dist/client.cjs +140 -28
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +35 -4
- package/dist/client.d.ts +35 -4
- package/dist/client.js +136 -26
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +28 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/{plugin-BlfpqyX4.d.cts → plugin-Bb4rRwO8.d.cts} +31 -1
- package/dist/{plugin-BlfpqyX4.d.ts → plugin-Bb4rRwO8.d.ts} +31 -1
- package/dist/style.css +113 -0
- package/dist/vitepress.cjs +165 -76
- package/dist/vitepress.cjs.map +1 -1
- package/dist/vitepress.d.cts +17 -7
- package/dist/vitepress.d.ts +17 -7
- package/dist/vitepress.js +75 -7
- package/dist/vitepress.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PWNWSYI4.js.map +0 -1
package/README.md
CHANGED
|
@@ -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. |
|
|
@@ -196,6 +197,35 @@ blog:
|
|
|
196
197
|
- Feeds and social tags use the exact moment: `pubDate`, `lastBuildDate`,
|
|
197
198
|
`article:published_time` and `article:modified_time`.
|
|
198
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
|
+
|
|
199
229
|
## Blog pages
|
|
200
230
|
|
|
201
231
|
A page with `blog-index` in its frontmatter lists posts. `blog-index: true` is a
|
|
@@ -270,6 +300,57 @@ a blog page, on each card, and in a post's byline, where they link to the
|
|
|
270
300
|
post's own blog, filtered. They are ordinary links, so VitePress follows them
|
|
271
301
|
without reloading the page, and each filtered view has its own URL to share.
|
|
272
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
|
+
|
|
273
354
|
## Latest posts on any page
|
|
274
355
|
|
|
275
356
|
A `::: blog-posts` block shows a blog's latest posts anywhere: on the home
|
|
@@ -401,6 +482,11 @@ withBlog(config, {
|
|
|
401
482
|
allPosts: 'Alle Beiträge in {blog}',
|
|
402
483
|
draft: 'Entwurf',
|
|
403
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',
|
|
404
490
|
noPosts: 'Noch keine Beiträge.',
|
|
405
491
|
undated: 'Ohne Datum',
|
|
406
492
|
tags: 'Schlagwörter',
|
|
@@ -421,15 +507,18 @@ withBlog(config, {
|
|
|
421
507
|
`style.css` uses VitePress's theme variables, so it follows light and dark
|
|
422
508
|
mode. Override these to restyle it without new rules:
|
|
423
509
|
|
|
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`
|
|
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)` |
|
|
433
522
|
|
|
434
523
|
Every generated element has a `vp-blog-` class (`vp-blog-card`,
|
|
435
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":[]}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeHtml,
|
|
3
|
+
escapeMarkdown,
|
|
4
|
+
escapeTemplateText,
|
|
5
|
+
isExternalUrl,
|
|
6
|
+
joinUrl,
|
|
7
|
+
toMarkdownUrl,
|
|
8
|
+
toPageLink,
|
|
9
|
+
toUrlPath
|
|
10
|
+
} from "./chunk-42Z6H7IN.js";
|
|
11
|
+
|
|
1
12
|
// src/collect.ts
|
|
2
13
|
function isPost(page) {
|
|
3
14
|
return page.post !== null;
|
|
@@ -69,40 +80,16 @@ function findBlogsListing(pages, post) {
|
|
|
69
80
|
(index) => listBlogPosts(pages, index, { drafts: false, stickyFirst: false, applyLimit: false }).includes(post)
|
|
70
81
|
);
|
|
71
82
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
var HTML_ESCAPES = {
|
|
75
|
-
"&": "&",
|
|
76
|
-
"<": "<",
|
|
77
|
-
">": ">",
|
|
78
|
-
'"': """,
|
|
79
|
-
"'": "'"
|
|
80
|
-
};
|
|
81
|
-
function escapeHtml(value) {
|
|
82
|
-
return value.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
|
|
83
|
-
}
|
|
84
|
-
function escapeMarkdown(text) {
|
|
85
|
-
const escaped = text.replace(/[\\`*_[\]#|~]/g, "\\$&").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
86
|
-
return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;
|
|
87
|
-
}
|
|
88
|
-
function escapeTemplateText(text) {
|
|
89
|
-
const escaped = escapeHtml(text);
|
|
90
|
-
return /[{}]/.test(escaped) ? `<span v-pre>${escaped}</span>` : escaped;
|
|
91
|
-
}
|
|
92
|
-
function isExternalUrl(url) {
|
|
93
|
-
return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(url);
|
|
94
|
-
}
|
|
95
|
-
function toMarkdownUrl(url) {
|
|
96
|
-
return encodeURI(url).replace(/[()]/g, (character) => character === "(" ? "%28" : "%29");
|
|
83
|
+
function listAnnouncements(pages, drafts) {
|
|
84
|
+
return pages.filter(isPost).filter((post) => post.post.announcement && (drafts || !post.post.draft)).sort(compareByDate);
|
|
97
85
|
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return `${base.replace(/\/+$/, "")}/${urlPath.replace(/^\/+/, "")}`;
|
|
86
|
+
function findAnnouncementBlog(pages, announcements, drafts) {
|
|
87
|
+
if (announcements.length === 0) return null;
|
|
88
|
+
const listingAll = pages.filter(isIndex).filter((index) => {
|
|
89
|
+
const listed = listBlogPosts(pages, index, { drafts, stickyFirst: false, applyLimit: false });
|
|
90
|
+
return announcements.every((post) => listed.includes(post));
|
|
91
|
+
});
|
|
92
|
+
return listingAll.find((index) => index.index.scope === "site") ?? listingAll[0] ?? findHomeBlog(pages, announcements[0], drafts);
|
|
106
93
|
}
|
|
107
94
|
|
|
108
95
|
// src/feed.ts
|
|
@@ -211,6 +198,11 @@ var DEFAULT_LABELS = {
|
|
|
211
198
|
allPosts: "All posts in {blog}",
|
|
212
199
|
draft: "Draft",
|
|
213
200
|
sticky: "Pinned",
|
|
201
|
+
announcement: "Announcement",
|
|
202
|
+
announcements: "Announcements",
|
|
203
|
+
noAnnouncements: "No announcements.",
|
|
204
|
+
announcementCount: "{n} announcements",
|
|
205
|
+
dismiss: "Dismiss",
|
|
214
206
|
noPosts: "No posts yet.",
|
|
215
207
|
undated: "Undated",
|
|
216
208
|
tags: "Keywords",
|
|
@@ -390,12 +382,22 @@ function renderTags(tags, blogRoutePath) {
|
|
|
390
382
|
function toTagsAttribute(post) {
|
|
391
383
|
return escapeHtml(post.post.tags.map((tag) => tag.toLowerCase()).join("|"));
|
|
392
384
|
}
|
|
385
|
+
function toItemAttributes(post) {
|
|
386
|
+
return `data-vp-blog-item data-tags="${toTagsAttribute(post)}"${post.post.announcement ? " data-announcement" : ""}`;
|
|
387
|
+
}
|
|
388
|
+
function renderBadges(post, context) {
|
|
389
|
+
const { draft, sticky, announcement } = post.post;
|
|
390
|
+
return [
|
|
391
|
+
draft && renderBadge("draft", context),
|
|
392
|
+
sticky && renderBadge("sticky", context),
|
|
393
|
+
announcement && renderBadge("announcement", context)
|
|
394
|
+
].filter(Boolean).join(" ");
|
|
395
|
+
}
|
|
393
396
|
function renderMeta(post, context, include) {
|
|
394
|
-
const { date, dateHasTime, updated, updatedHasTime, readingMinutes, authors
|
|
397
|
+
const { date, dateHasTime, updated, updatedHasTime, readingMinutes, authors } = post.post;
|
|
395
398
|
const parts = [];
|
|
396
399
|
if (include.badges) {
|
|
397
|
-
const
|
|
398
|
-
const shown = badges.filter(Boolean).join(" ");
|
|
400
|
+
const shown = renderBadges(post, context);
|
|
399
401
|
if (shown) parts.push(shown);
|
|
400
402
|
}
|
|
401
403
|
if (date) {
|
|
@@ -424,12 +426,13 @@ function renderCard(post, context, index) {
|
|
|
424
426
|
const modifiers = [
|
|
425
427
|
post.post.sticky && "sticky",
|
|
426
428
|
post.post.draft && "draft",
|
|
429
|
+
post.post.announcement && "announcement",
|
|
427
430
|
image && "with-image",
|
|
428
431
|
image && post.post.imageFit === "contain" && "contain"
|
|
429
432
|
].filter(Boolean).map((modifier) => ` vp-blog-card--${modifier}`).join("");
|
|
430
433
|
const alt = escapeMarkdown(post.post.imageAlt);
|
|
431
434
|
return [
|
|
432
|
-
`<article class="vp-blog-card${modifiers}"${imageStyle(post)}
|
|
435
|
+
`<article class="vp-blog-card${modifiers}"${imageStyle(post)} ${toItemAttributes(post)}>`,
|
|
433
436
|
...image ? block("vp-blog-card__image", `[})](${toMarkdownUrl(link)})`) : [],
|
|
434
437
|
'<div class="vp-blog-card__body">',
|
|
435
438
|
...block("vp-blog-card__title", toPageLink(post.post.title, post.routePath)),
|
|
@@ -442,8 +445,8 @@ function renderCard(post, context, index) {
|
|
|
442
445
|
}
|
|
443
446
|
function renderCompactItem(post, context) {
|
|
444
447
|
const date = post.post.date ? `${renderTime(post.post.date, post.post.dateHasTime, context)} ` : "";
|
|
445
|
-
const badges =
|
|
446
|
-
const marker = `<span hidden
|
|
448
|
+
const badges = renderBadges(post, context);
|
|
449
|
+
const marker = `<span hidden ${toItemAttributes(post)}></span>`;
|
|
447
450
|
return `- ${marker}${date}${toPageLink(post.post.title, post.routePath)}${badges ? ` ${badges}` : ""}`;
|
|
448
451
|
}
|
|
449
452
|
function renderGroup(posts, index, context) {
|
|
@@ -518,7 +521,9 @@ function renderClientAttributes(index, context) {
|
|
|
518
521
|
"data-label-no-tagged": labels.noTagged,
|
|
519
522
|
"data-label-previous": labels.previous,
|
|
520
523
|
"data-label-next": labels.next,
|
|
521
|
-
"data-label-pages": labels.pages
|
|
524
|
+
"data-label-pages": labels.pages,
|
|
525
|
+
"data-label-announcements": labels.announcements,
|
|
526
|
+
"data-label-no-announcements": labels.noAnnouncements
|
|
522
527
|
};
|
|
523
528
|
return Object.entries(attributes).map(([name, value]) => `${name}="${escapeHtml(value)}"`).join(" ");
|
|
524
529
|
}
|
|
@@ -684,6 +689,7 @@ function resolvePostData(data, content, fallbackTitle, options = {}) {
|
|
|
684
689
|
const timeZone = resolveTimeZone(options.timeZone);
|
|
685
690
|
const date = parsePostDate(pick("date"), timeZone);
|
|
686
691
|
const updated = parsePostDate(pick("updated"), timeZone);
|
|
692
|
+
const announcementText = typeof post.announcement === "string" ? clean(post.announcement.trim()) : "";
|
|
687
693
|
return {
|
|
688
694
|
title: clean(toText(post.title) || toText(data.title) || readHeading(content)) || fallbackTitle,
|
|
689
695
|
summary: clean(toText(post.summary) || toText(data.description) || readExcerpt(content)),
|
|
@@ -701,6 +707,8 @@ function resolvePostData(data, content, fallbackTitle, options = {}) {
|
|
|
701
707
|
scope: toScope(post.scope),
|
|
702
708
|
sticky: post.sticky === true,
|
|
703
709
|
draft: post.draft === true,
|
|
710
|
+
announcement: post.announcement === true || !!announcementText,
|
|
711
|
+
announcementText,
|
|
704
712
|
byline: toBoolean(post.byline),
|
|
705
713
|
cover: toBoolean(post.cover),
|
|
706
714
|
postNav: toBoolean(post.postNav),
|
|
@@ -1088,7 +1096,8 @@ export {
|
|
|
1088
1096
|
isIndex,
|
|
1089
1097
|
listBlogPosts,
|
|
1090
1098
|
findBlogsListing,
|
|
1091
|
-
|
|
1099
|
+
listAnnouncements,
|
|
1100
|
+
findAnnouncementBlog,
|
|
1092
1101
|
toPageUrl,
|
|
1093
1102
|
toFeedPath,
|
|
1094
1103
|
resolvePostImage,
|
|
@@ -1105,4 +1114,4 @@ export {
|
|
|
1105
1114
|
hasWidget,
|
|
1106
1115
|
blogMarkdown
|
|
1107
1116
|
};
|
|
1108
|
-
//# sourceMappingURL=chunk-
|
|
1117
|
+
//# sourceMappingURL=chunk-LYTJBMVK.js.map
|