@binarynoir/vitepress-blog 0.1.1 → 0.2.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 +59 -23
- package/dist/{chunk-DICBXOTS.js → chunk-PWNWSYI4.js} +133 -26
- package/dist/chunk-PWNWSYI4.js.map +1 -0
- package/dist/index.cjs +130 -25
- 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 +1 -1
- package/dist/{plugin-BjbV6nDK.d.cts → plugin-BlfpqyX4.d.cts} +24 -1
- package/dist/{plugin-BjbV6nDK.d.ts → plugin-BlfpqyX4.d.ts} +24 -1
- package/dist/vitepress.cjs +132 -25
- package/dist/vitepress.cjs.map +1 -1
- package/dist/vitepress.d.cts +2 -2
- package/dist/vitepress.d.ts +2 -2
- package/dist/vitepress.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-DICBXOTS.js.map +0 -1
|
@@ -16,8 +16,21 @@ interface BlogOptions {
|
|
|
16
16
|
siteUrl?: string;
|
|
17
17
|
/** Locale for dates in bylines and cards. Default: `'en-US'`. */
|
|
18
18
|
locale?: string;
|
|
19
|
-
/** How dates are written. Default: `{ dateStyle: 'long' }
|
|
19
|
+
/** How dates are written. Default: `{ dateStyle: 'long' }`. */
|
|
20
20
|
dateFormat?: Intl.DateTimeFormatOptions;
|
|
21
|
+
/**
|
|
22
|
+
* How times are written, for dates whose frontmatter gives one
|
|
23
|
+
* (`2026-10-20 14:30`). Default: `{ hour: 'numeric', minute: '2-digit', timeZoneName: 'short' }`,
|
|
24
|
+
* as in "2:30 PM EDT".
|
|
25
|
+
*/
|
|
26
|
+
timeFormat?: Intl.DateTimeFormatOptions;
|
|
27
|
+
/**
|
|
28
|
+
* The time zone frontmatter times without an offset are in, and that every
|
|
29
|
+
* date and time is shown in: an IANA name such as `'America/New_York'`.
|
|
30
|
+
* A value with an offset (`2026-10-20T14:30:00-04:00`, `…Z`) is read as
|
|
31
|
+
* written. Default: `'UTC'`.
|
|
32
|
+
*/
|
|
33
|
+
timeZone?: string;
|
|
21
34
|
/** Reading speed for the "5 min read" estimate. `0` hides it. Default: `200`. */
|
|
22
35
|
wordsPerMinute?: number;
|
|
23
36
|
/** Add an author, date and tags line under each post's title. Default: `true`. */
|
|
@@ -68,8 +81,12 @@ interface BlogOptions {
|
|
|
68
81
|
interface BlogLabels {
|
|
69
82
|
/** Before the author names: "By Jane Doe". */
|
|
70
83
|
by: string;
|
|
84
|
+
/** Before a post's `date` in its byline: "Published October 20, 2026". */
|
|
85
|
+
published: string;
|
|
71
86
|
/** Before a post's `updated` date. */
|
|
72
87
|
updated: string;
|
|
88
|
+
/** A date and its time: "{date} at {time}". */
|
|
89
|
+
dateTime: string;
|
|
73
90
|
/** Reading time: "{n} min read". */
|
|
74
91
|
readingTime: string;
|
|
75
92
|
newer: string;
|
|
@@ -138,8 +155,14 @@ interface BlogIndexConfig {
|
|
|
138
155
|
interface BlogPostData {
|
|
139
156
|
title: string;
|
|
140
157
|
summary: string;
|
|
158
|
+
/** When the post was published, from `date`. */
|
|
141
159
|
date: Date | null;
|
|
160
|
+
/** Whether `date` gave a time, not just a day. */
|
|
161
|
+
dateHasTime: boolean;
|
|
162
|
+
/** When the post was last changed, from `updated`. */
|
|
142
163
|
updated: Date | null;
|
|
164
|
+
/** Whether `updated` gave a time, not just a day. */
|
|
165
|
+
updatedHasTime: boolean;
|
|
143
166
|
/** As written: a path relative to the post, a public path (`/x.png`) or a URL. */
|
|
144
167
|
image: string;
|
|
145
168
|
imageAlt: string;
|
|
@@ -16,8 +16,21 @@ interface BlogOptions {
|
|
|
16
16
|
siteUrl?: string;
|
|
17
17
|
/** Locale for dates in bylines and cards. Default: `'en-US'`. */
|
|
18
18
|
locale?: string;
|
|
19
|
-
/** How dates are written. Default: `{ dateStyle: 'long' }
|
|
19
|
+
/** How dates are written. Default: `{ dateStyle: 'long' }`. */
|
|
20
20
|
dateFormat?: Intl.DateTimeFormatOptions;
|
|
21
|
+
/**
|
|
22
|
+
* How times are written, for dates whose frontmatter gives one
|
|
23
|
+
* (`2026-10-20 14:30`). Default: `{ hour: 'numeric', minute: '2-digit', timeZoneName: 'short' }`,
|
|
24
|
+
* as in "2:30 PM EDT".
|
|
25
|
+
*/
|
|
26
|
+
timeFormat?: Intl.DateTimeFormatOptions;
|
|
27
|
+
/**
|
|
28
|
+
* The time zone frontmatter times without an offset are in, and that every
|
|
29
|
+
* date and time is shown in: an IANA name such as `'America/New_York'`.
|
|
30
|
+
* A value with an offset (`2026-10-20T14:30:00-04:00`, `…Z`) is read as
|
|
31
|
+
* written. Default: `'UTC'`.
|
|
32
|
+
*/
|
|
33
|
+
timeZone?: string;
|
|
21
34
|
/** Reading speed for the "5 min read" estimate. `0` hides it. Default: `200`. */
|
|
22
35
|
wordsPerMinute?: number;
|
|
23
36
|
/** Add an author, date and tags line under each post's title. Default: `true`. */
|
|
@@ -68,8 +81,12 @@ interface BlogOptions {
|
|
|
68
81
|
interface BlogLabels {
|
|
69
82
|
/** Before the author names: "By Jane Doe". */
|
|
70
83
|
by: string;
|
|
84
|
+
/** Before a post's `date` in its byline: "Published October 20, 2026". */
|
|
85
|
+
published: string;
|
|
71
86
|
/** Before a post's `updated` date. */
|
|
72
87
|
updated: string;
|
|
88
|
+
/** A date and its time: "{date} at {time}". */
|
|
89
|
+
dateTime: string;
|
|
73
90
|
/** Reading time: "{n} min read". */
|
|
74
91
|
readingTime: string;
|
|
75
92
|
newer: string;
|
|
@@ -138,8 +155,14 @@ interface BlogIndexConfig {
|
|
|
138
155
|
interface BlogPostData {
|
|
139
156
|
title: string;
|
|
140
157
|
summary: string;
|
|
158
|
+
/** When the post was published, from `date`. */
|
|
141
159
|
date: Date | null;
|
|
160
|
+
/** Whether `date` gave a time, not just a day. */
|
|
161
|
+
dateHasTime: boolean;
|
|
162
|
+
/** When the post was last changed, from `updated`. */
|
|
142
163
|
updated: Date | null;
|
|
164
|
+
/** Whether `updated` gave a time, not just a day. */
|
|
165
|
+
updatedHasTime: boolean;
|
|
143
166
|
/** As written: a path relative to the post, a public path (`/x.png`) or a URL. */
|
|
144
167
|
image: string;
|
|
145
168
|
imageAlt: string;
|
package/dist/vitepress.cjs
CHANGED
|
@@ -117,6 +117,104 @@ function findBlogsListing(pages, post) {
|
|
|
117
117
|
// src/frontmatter.ts
|
|
118
118
|
var import_node_path = __toESM(require("path"), 1);
|
|
119
119
|
var import_yaml = require("yaml");
|
|
120
|
+
|
|
121
|
+
// src/dates.ts
|
|
122
|
+
var DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3})\d*)?)?)?\s*(Z|[+-]\d{2}:?\d{2})?$/i;
|
|
123
|
+
function isTimeZone(timeZone) {
|
|
124
|
+
try {
|
|
125
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
126
|
+
return true;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
var warnedZones = /* @__PURE__ */ new Set();
|
|
132
|
+
function resolveTimeZone(timeZone) {
|
|
133
|
+
if (!timeZone) return "UTC";
|
|
134
|
+
if (isTimeZone(timeZone)) return timeZone;
|
|
135
|
+
if (!warnedZones.has(timeZone)) {
|
|
136
|
+
warnedZones.add(timeZone);
|
|
137
|
+
console.warn(
|
|
138
|
+
`[vitepress-blog] "${timeZone}" is not a time zone; using UTC. Use an IANA name such as "America/New_York".`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return "UTC";
|
|
142
|
+
}
|
|
143
|
+
var offsetFormats = /* @__PURE__ */ new Map();
|
|
144
|
+
function zoneOffset(instant, timeZone) {
|
|
145
|
+
let format = offsetFormats.get(timeZone);
|
|
146
|
+
if (!format) {
|
|
147
|
+
format = new Intl.DateTimeFormat("en-US", {
|
|
148
|
+
timeZone,
|
|
149
|
+
hourCycle: "h23",
|
|
150
|
+
year: "numeric",
|
|
151
|
+
month: "2-digit",
|
|
152
|
+
day: "2-digit",
|
|
153
|
+
hour: "2-digit",
|
|
154
|
+
minute: "2-digit",
|
|
155
|
+
second: "2-digit"
|
|
156
|
+
});
|
|
157
|
+
offsetFormats.set(timeZone, format);
|
|
158
|
+
}
|
|
159
|
+
const parts = Object.fromEntries(format.formatToParts(instant).map(({ type, value }) => [type, Number(value)]));
|
|
160
|
+
const wall = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
|
|
161
|
+
return wall - Math.floor(instant / 1e3) * 1e3;
|
|
162
|
+
}
|
|
163
|
+
function fromZonedTime(wall, timeZone) {
|
|
164
|
+
const first = wall - zoneOffset(wall, timeZone);
|
|
165
|
+
const second = wall - zoneOffset(first, timeZone);
|
|
166
|
+
return second;
|
|
167
|
+
}
|
|
168
|
+
function parsePostDate(value, timeZone = "UTC") {
|
|
169
|
+
if (value instanceof Date) {
|
|
170
|
+
if (Number.isNaN(value.getTime())) return null;
|
|
171
|
+
const midnight = value.getUTCHours() === 0 && value.getUTCMinutes() === 0 && value.getUTCSeconds() === 0;
|
|
172
|
+
return { date: value, hasTime: !midnight };
|
|
173
|
+
}
|
|
174
|
+
if (typeof value !== "string") return null;
|
|
175
|
+
const match = DATE_TIME.exec(value.trim());
|
|
176
|
+
if (!match) return null;
|
|
177
|
+
const [, year, month, day, hour, minute, second = "0", millisecond = "0", offset] = match;
|
|
178
|
+
const hasTime = hour !== void 0;
|
|
179
|
+
const wall = Date.UTC(
|
|
180
|
+
Number(year),
|
|
181
|
+
Number(month) - 1,
|
|
182
|
+
Number(day),
|
|
183
|
+
hasTime ? Number(hour) : 0,
|
|
184
|
+
hasTime ? Number(minute) : 0,
|
|
185
|
+
Number(second),
|
|
186
|
+
Number(millisecond.padEnd(3, "0"))
|
|
187
|
+
);
|
|
188
|
+
const check = new Date(wall);
|
|
189
|
+
if (check.getUTCMonth() !== Number(month) - 1 || check.getUTCDate() !== Number(day)) return null;
|
|
190
|
+
if (!offset) return { date: new Date(fromZonedTime(wall, timeZone)), hasTime };
|
|
191
|
+
if (offset.toUpperCase() === "Z") return { date: new Date(wall), hasTime };
|
|
192
|
+
const sign = offset.startsWith("-") ? -1 : 1;
|
|
193
|
+
const digits = offset.slice(1).replace(":", "");
|
|
194
|
+
const minutes = Number(digits.slice(0, 2)) * 60 + Number(digits.slice(2));
|
|
195
|
+
return { date: new Date(wall - sign * minutes * 6e4), hasTime };
|
|
196
|
+
}
|
|
197
|
+
function formatPostDate({ date, hasTime }, display) {
|
|
198
|
+
const { locale, timeZone } = display;
|
|
199
|
+
const day = new Intl.DateTimeFormat(locale, { timeZone, ...display.dateFormat }).format(date);
|
|
200
|
+
if (!hasTime) return day;
|
|
201
|
+
const time = new Intl.DateTimeFormat(locale, { timeZone, ...display.timeFormat }).format(date);
|
|
202
|
+
return display.dateTime.replace("{date}", day).replace("{time}", time);
|
|
203
|
+
}
|
|
204
|
+
function toZonedIsoDate(date, timeZone) {
|
|
205
|
+
const parts = Object.fromEntries(
|
|
206
|
+
new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(date).map(({ type, value }) => [type, value])
|
|
207
|
+
);
|
|
208
|
+
return `${parts.year}-${parts.month}-${parts.day}`;
|
|
209
|
+
}
|
|
210
|
+
function toDateTimeAttribute({ date, hasTime }, timeZone) {
|
|
211
|
+
return hasTime ? date.toISOString() : toZonedIsoDate(date, timeZone);
|
|
212
|
+
}
|
|
213
|
+
function yearIn(date, timeZone) {
|
|
214
|
+
return Number(toZonedIsoDate(date, timeZone).slice(0, 4));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/frontmatter.ts
|
|
120
218
|
var POST_FRONTMATTER_KEY = "blog";
|
|
121
219
|
var INDEX_FRONTMATTER_KEY = "blog-index";
|
|
122
220
|
var FRONTMATTER = /^---[ \t]*\r?\n(?:([\s\S]*?)\r?\n)?---[ \t]*(?:\r?\n|$)/;
|
|
@@ -155,12 +253,6 @@ function toStringList(value) {
|
|
|
155
253
|
const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
156
254
|
return values.map(toText).filter(Boolean);
|
|
157
255
|
}
|
|
158
|
-
function toDate(value) {
|
|
159
|
-
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
160
|
-
if (typeof value !== "string" && typeof value !== "number") return null;
|
|
161
|
-
const date = new Date(value);
|
|
162
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
163
|
-
}
|
|
164
256
|
function toAuthor(value, options) {
|
|
165
257
|
if (typeof value === "string") {
|
|
166
258
|
const name2 = value.trim();
|
|
@@ -225,11 +317,16 @@ function resolvePostData(data, content, fallbackTitle, options = {}) {
|
|
|
225
317
|
const stripTags = options.stripTags ?? true;
|
|
226
318
|
const clean = (text) => stripTags ? stripTagSyntax(text) : text;
|
|
227
319
|
const image = toRecord(pick("image"));
|
|
320
|
+
const timeZone = resolveTimeZone(options.timeZone);
|
|
321
|
+
const date = parsePostDate(pick("date"), timeZone);
|
|
322
|
+
const updated = parsePostDate(pick("updated"), timeZone);
|
|
228
323
|
return {
|
|
229
324
|
title: clean(toText(post.title) || toText(data.title) || readHeading(content)) || fallbackTitle,
|
|
230
325
|
summary: clean(toText(post.summary) || toText(data.description) || readExcerpt(content)),
|
|
231
|
-
date:
|
|
232
|
-
|
|
326
|
+
date: date?.date ?? null,
|
|
327
|
+
dateHasTime: date?.hasTime ?? false,
|
|
328
|
+
updated: updated?.date ?? null,
|
|
329
|
+
updatedHasTime: updated?.hasTime ?? false,
|
|
233
330
|
image: toText(image.src) || toText(pick("image")),
|
|
234
331
|
imageAlt: toText(image.alt) || toText(post.imageAlt) || toText(data.imageAlt),
|
|
235
332
|
imageFit: (toText(image.fit) || toText(post.imageFit)) === "contain" ? "contain" : "cover",
|
|
@@ -333,12 +430,6 @@ function toUrlPath(routePath, cleanUrls) {
|
|
|
333
430
|
function joinUrl(base, urlPath) {
|
|
334
431
|
return `${base.replace(/\/+$/, "")}/${urlPath.replace(/^\/+/, "")}`;
|
|
335
432
|
}
|
|
336
|
-
function toIsoDate(date) {
|
|
337
|
-
return date.toISOString().slice(0, 10);
|
|
338
|
-
}
|
|
339
|
-
function formatDate(date, locale, format) {
|
|
340
|
-
return new Intl.DateTimeFormat(locale, { timeZone: "UTC", ...format }).format(date);
|
|
341
|
-
}
|
|
342
433
|
|
|
343
434
|
// src/feed.ts
|
|
344
435
|
function toAbsoluteUrl(urlPath, site) {
|
|
@@ -440,7 +531,9 @@ var import_node_path3 = __toESM(require("path"), 1);
|
|
|
440
531
|
// src/types.ts
|
|
441
532
|
var DEFAULT_LABELS = {
|
|
442
533
|
by: "By",
|
|
534
|
+
published: "Published",
|
|
443
535
|
updated: "Updated",
|
|
536
|
+
dateTime: "{date} at {time}",
|
|
444
537
|
readingTime: "{n} min read",
|
|
445
538
|
newer: "Newer post",
|
|
446
539
|
older: "Older post",
|
|
@@ -463,10 +556,18 @@ var DEFAULT_LABELS = {
|
|
|
463
556
|
// src/render.ts
|
|
464
557
|
var BLOG_POSTS_PLACEHOLDER = "<!-- blog-posts -->";
|
|
465
558
|
function createRenderContext(options, pageDirectory, feeds = !!options.siteUrl) {
|
|
559
|
+
const locale = options.locale ?? "en-US";
|
|
560
|
+
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
466
561
|
return {
|
|
467
|
-
locale
|
|
468
|
-
|
|
469
|
-
|
|
562
|
+
locale,
|
|
563
|
+
dates: {
|
|
564
|
+
locale,
|
|
565
|
+
timeZone: resolveTimeZone(options.timeZone),
|
|
566
|
+
dateFormat: options.dateFormat ?? { dateStyle: "long" },
|
|
567
|
+
timeFormat: options.timeFormat ?? { hour: "numeric", minute: "2-digit", timeZoneName: "short" },
|
|
568
|
+
dateTime: labels.dateTime
|
|
569
|
+
},
|
|
570
|
+
labels,
|
|
470
571
|
pageDirectory,
|
|
471
572
|
feeds
|
|
472
573
|
};
|
|
@@ -488,8 +589,10 @@ function toImageSource(post, pageDirectory) {
|
|
|
488
589
|
const relative = import_node_path3.default.relative(pageDirectory, absolute).split(import_node_path3.default.sep).join("/");
|
|
489
590
|
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
490
591
|
}
|
|
491
|
-
function renderTime(date, context) {
|
|
492
|
-
|
|
592
|
+
function renderTime(date, hasTime, context) {
|
|
593
|
+
const value = { date, hasTime };
|
|
594
|
+
const attribute = toDateTimeAttribute(value, context.dates.timeZone);
|
|
595
|
+
return `<time datetime="${attribute}">${escapeTemplateText(formatPostDate(value, context.dates))}</time>`;
|
|
493
596
|
}
|
|
494
597
|
function renderAuthor(author) {
|
|
495
598
|
return author.url ? `[${escapeMarkdown(author.name)}](${toMarkdownUrl(author.url)})` : escapeMarkdown(author.name);
|
|
@@ -518,16 +621,19 @@ function toTagsAttribute(post) {
|
|
|
518
621
|
return escapeHtml(post.post.tags.map((tag) => tag.toLowerCase()).join("|"));
|
|
519
622
|
}
|
|
520
623
|
function renderMeta(post, context, include) {
|
|
521
|
-
const { date, updated, readingMinutes, authors, draft, sticky } = post.post;
|
|
624
|
+
const { date, dateHasTime, updated, updatedHasTime, readingMinutes, authors, draft, sticky } = post.post;
|
|
522
625
|
const parts = [];
|
|
523
626
|
if (include.badges) {
|
|
524
627
|
const badges = [draft && renderBadge("draft", context), sticky && renderBadge("sticky", context)];
|
|
525
628
|
const shown = badges.filter(Boolean).join(" ");
|
|
526
629
|
if (shown) parts.push(shown);
|
|
527
630
|
}
|
|
528
|
-
if (date)
|
|
631
|
+
if (date) {
|
|
632
|
+
const time = renderTime(date, dateHasTime, context);
|
|
633
|
+
parts.push(include.updated ? `${escapeMarkdown(context.labels.published)} ${time}` : time);
|
|
634
|
+
}
|
|
529
635
|
if (include.updated && updated && (!date || updated.getTime() !== date.getTime())) {
|
|
530
|
-
parts.push(`${escapeMarkdown(context.labels.updated)} ${renderTime(updated, context)}`);
|
|
636
|
+
parts.push(`${escapeMarkdown(context.labels.updated)} ${renderTime(updated, updatedHasTime, context)}`);
|
|
531
637
|
}
|
|
532
638
|
if (readingMinutes) parts.push(escapeMarkdown(fill(context.labels.readingTime, { n: String(readingMinutes) })));
|
|
533
639
|
if (include.authors) {
|
|
@@ -565,7 +671,7 @@ function renderCard(post, context, index) {
|
|
|
565
671
|
];
|
|
566
672
|
}
|
|
567
673
|
function renderCompactItem(post, context) {
|
|
568
|
-
const date = post.post.date ? `${renderTime(post.post.date, context)} ` : "";
|
|
674
|
+
const date = post.post.date ? `${renderTime(post.post.date, post.post.dateHasTime, context)} ` : "";
|
|
569
675
|
const badges = [post.post.draft && renderBadge("draft", context), post.post.sticky && renderBadge("sticky", context)].filter(Boolean).join(" ");
|
|
570
676
|
const marker = `<span hidden data-vp-blog-item data-tags="${toTagsAttribute(post)}"></span>`;
|
|
571
677
|
return `- ${marker}${date}${toPageLink(post.post.title, post.routePath)}${badges ? ` ${badges}` : ""}`;
|
|
@@ -590,7 +696,7 @@ function renderGroup(posts, index, context) {
|
|
|
590
696
|
function renderYears(posts, index, context) {
|
|
591
697
|
const years = /* @__PURE__ */ new Map();
|
|
592
698
|
for (const post of posts) {
|
|
593
|
-
const year = post.post.date ? String(post.post.date.
|
|
699
|
+
const year = post.post.date ? String(yearIn(post.post.date, context.dates.timeZone)) : context.labels.undated;
|
|
594
700
|
years.set(year, [...years.get(year) ?? [], post]);
|
|
595
701
|
}
|
|
596
702
|
const lines = [];
|
|
@@ -765,7 +871,8 @@ function toOptionsKey(options) {
|
|
|
765
871
|
options.stripTags,
|
|
766
872
|
options.pageSize,
|
|
767
873
|
options.stickyLimit,
|
|
768
|
-
options.rssLink
|
|
874
|
+
options.rssLink,
|
|
875
|
+
options.timeZone
|
|
769
876
|
]);
|
|
770
877
|
}
|
|
771
878
|
function indexBlogSource(sourceDirectory, options = {}, rewrites = {}) {
|