@riebeckite/plugin-daily-notes 0.0.23 → 0.0.25

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.
@@ -1,6 +1,6 @@
1
1
  import type { DailyNote } from "../src/daily-notes.js";
2
2
  type Props = {
3
- notes: DailyNote[];
3
+ notes?: DailyNote[];
4
4
  limit?: number;
5
5
  };
6
6
  export default function DailyNotes(props: Props): import("hono/jsx/jsx-dev-runtime").JSX.Element;
@@ -1,9 +1,346 @@
1
1
  // components/daily-notes.tsx
2
- import { jsx, jsxs } from "hono/jsx/jsx-runtime";
2
+ import { useSiteContext } from "@riebeckite/ui";
3
+
4
+ // src/daily-notes.server.ts
5
+ import { resolvePlugins } from "@riebeckite/core";
6
+
7
+ // src/daily-notes.ts
8
+ var DAILY_NOTES_PLUGIN_NAME = "daily-notes";
9
+ var DEFAULT_DIRECTORY = "Daily";
10
+ var DEFAULT_FRONTMATTER_KEY = "daily-summary";
11
+ var DEFAULT_SECTION = "今日のひとこと";
12
+ var DEFAULT_CODE_BLOCK = "daily-snippet";
3
13
  var DEFAULT_LIMIT = 5;
14
+ var DEFAULT_DAILY_NOTES_DATE_FORMAT = "iso";
15
+ var DEFAULT_DAILY_NOTES_LOCALE = "en";
16
+ var DEFAULT_SLUG_DATE_FORMAT = "YYYY-MM-DD";
17
+ function resolveExtractOptions(options) {
18
+ const extract = options?.extract;
19
+ return {
20
+ frontmatter: extract?.frontmatter === void 0 ? DEFAULT_FRONTMATTER_KEY : extract.frontmatter,
21
+ section: extract?.section === void 0 ? DEFAULT_SECTION : extract.section,
22
+ codeBlock: extract?.codeBlock === void 0 ? DEFAULT_CODE_BLOCK : extract.codeBlock
23
+ };
24
+ }
25
+ function resolveDisplayOptions(options) {
26
+ const locale = options?.locale;
27
+ return {
28
+ dateFormat: options?.dateFormat ?? DEFAULT_DAILY_NOTES_DATE_FORMAT,
29
+ locale: typeof locale === "string" && locale.trim() !== "" ? locale.trim() : DEFAULT_DAILY_NOTES_LOCALE
30
+ };
31
+ }
32
+ function formatDailyNoteDate(dateIso, display) {
33
+ if (display.dateFormat === "iso") return dateIso;
34
+ const date = /* @__PURE__ */ new Date(`${dateIso}T00:00:00Z`);
35
+ if (Number.isNaN(date.getTime())) return dateIso;
36
+ return new Intl.DateTimeFormat(display.locale, {
37
+ dateStyle: display.dateFormat === "long" ? "long" : "medium",
38
+ timeZone: "UTC"
39
+ }).format(date);
40
+ }
41
+ function isDailyNoteSlug(slug, directory, pathPattern) {
42
+ if (directory.length > 0) {
43
+ const prefix = `${directory}/`;
44
+ if (slug !== directory && !slug.startsWith(prefix)) return false;
45
+ }
46
+ if (pathPattern === void 0) return true;
47
+ return matchesSlugPattern(slug, pathPattern);
48
+ }
49
+ function matchesSlugPattern(slug, pattern) {
50
+ return new RegExp(`^${toSlugPatternSource(pattern)}$`).test(slug);
51
+ }
52
+ function toSlugPatternSource(pattern) {
53
+ let source = "";
54
+ for (let index = 0; index < pattern.length; index++) {
55
+ const character = pattern[index];
56
+ if (character === "{") {
57
+ const end = pattern.indexOf("}", index + 1);
58
+ if (end !== -1) {
59
+ source += placeholderSource(pattern.slice(index + 1, end));
60
+ index = end;
61
+ continue;
62
+ }
63
+ }
64
+ if (character === "*") {
65
+ source += "[^/]*";
66
+ continue;
67
+ }
68
+ source += escapeRegExp(character);
69
+ }
70
+ return source;
71
+ }
72
+ function placeholderSource(token) {
73
+ if (token === "YYYY") return "\\d{4}";
74
+ if (token === "MM" || token === "DD") return "\\d{2}";
75
+ return "[^/]+";
76
+ }
77
+ function escapeRegExp(value) {
78
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
79
+ }
80
+ function extractDailyNoteSnippet(entry, extract) {
81
+ if (extract.frontmatter !== false) {
82
+ const value = entry.frontmatter[extract.frontmatter];
83
+ const text = typeof value === "string" ? value.trim() : "";
84
+ if (text.length > 0) return text;
85
+ }
86
+ if (extract.section !== false) {
87
+ const text = extractSectionSnippet(entry.html, extract.section);
88
+ if (text !== null) return text;
89
+ }
90
+ if (extract.codeBlock !== false) {
91
+ const text = extractCodeBlockSnippet(entry.html, extract.codeBlock);
92
+ if (text !== null) return text;
93
+ }
94
+ return null;
95
+ }
96
+ var HEADING_PATTERN = /<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/g;
97
+ function extractSectionSnippet(html, section) {
98
+ const headings = [...html.matchAll(HEADING_PATTERN)].map((match) => {
99
+ const start = match.index ?? 0;
100
+ return {
101
+ level: Number(match[1]),
102
+ title: decodeHtmlEntities(stripHtml(match[2])).trim(),
103
+ start,
104
+ end: start + match[0].length
105
+ };
106
+ });
107
+ const target = headings.find((heading) => heading.title === section);
108
+ if (!target) return null;
109
+ const boundary = headings.find(
110
+ (heading) => heading.start >= target.end && heading.level <= target.level
111
+ );
112
+ const body = html.slice(target.end, boundary?.start ?? html.length);
113
+ const text = collapseWhitespace(decodeHtmlEntities(stripHtml(body)));
114
+ return text.length > 0 ? text : null;
115
+ }
116
+ var CODE_BLOCK_PATTERN = /<code\b([^>]*)>([\s\S]*?)<\/code>/g;
117
+ function extractCodeBlockSnippet(html, language) {
118
+ const wanted = `language-${language}`;
119
+ for (const match of html.matchAll(CODE_BLOCK_PATTERN)) {
120
+ const classAttribute = match[1].match(/\bclass="([^"]*)"/)?.[1] ?? "";
121
+ if (!classAttribute.split(/\s+/).includes(wanted)) continue;
122
+ const text = decodeHtmlEntities(stripHtml(match[2])).trim();
123
+ if (text.length > 0) return text;
124
+ }
125
+ return null;
126
+ }
127
+ function resolveDailyNoteDate(entry, slugDateFormat = DEFAULT_SLUG_DATE_FORMAT) {
128
+ const fromFrontmatter = normalizeDateValue(entry.frontmatter.date) ?? normalizeDateValue(entry.frontmatter.created);
129
+ if (fromFrontmatter !== null) return fromFrontmatter;
130
+ return matchSlugDate(entry.slug, slugDateFormat) ?? "";
131
+ }
132
+ var SLUG_DATE_TOKEN_SOURCES = {
133
+ YYYY: "\\d{4}",
134
+ YY: "\\d{2}",
135
+ MM: "\\d{2}",
136
+ M: "\\d{1,2}",
137
+ DD: "\\d{2}",
138
+ D: "\\d{1,2}"
139
+ };
140
+ var SLUG_DATE_TOKEN_FIELDS = {
141
+ YYYY: "year",
142
+ YY: "year",
143
+ MM: "month",
144
+ M: "month",
145
+ DD: "day",
146
+ D: "day"
147
+ };
148
+ function matchSlugDate(slug, format) {
149
+ const matcher = compileSlugDateFormat(format);
150
+ if (matcher === null) return null;
151
+ const match = matcher.exec(slug);
152
+ if (match?.groups === void 0) return null;
153
+ const year = resolveSlugDateYear(match.groups);
154
+ const month = Number(match.groups.month);
155
+ const day = Number(match.groups.day);
156
+ if (year === null) return null;
157
+ return formatDate(year, month, day);
158
+ }
159
+ function resolveSlugDateYear(groups) {
160
+ if (groups.year !== void 0) {
161
+ const year = Number(groups.year);
162
+ return Number.isInteger(year) ? year : null;
163
+ }
164
+ if (groups.yearShort !== void 0) {
165
+ const value = Number(groups.yearShort);
166
+ if (!Number.isInteger(value)) return null;
167
+ return value < 69 ? 2e3 + value : 1900 + value;
168
+ }
169
+ return null;
170
+ }
171
+ function compileSlugDateFormat(format) {
172
+ let source = "";
173
+ const captured = /* @__PURE__ */ new Set();
174
+ let hasYear = false;
175
+ let hasMonth = false;
176
+ let hasDay = false;
177
+ let index = 0;
178
+ while (index < format.length) {
179
+ const character = format[index];
180
+ if (character === "[") {
181
+ const end = format.indexOf("]", index + 1);
182
+ if (end === -1) return null;
183
+ source += escapeRegExp(format.slice(index + 1, end));
184
+ index = end + 1;
185
+ continue;
186
+ }
187
+ if (/[A-Za-z]/.test(character)) {
188
+ const token = readSlugDateToken(format, index);
189
+ if (token === null) return null;
190
+ const field = SLUG_DATE_TOKEN_FIELDS[token.value];
191
+ const group = field === "year" && token.value === "YY" ? "yearShort" : field;
192
+ if (captured.has(field)) {
193
+ source += `(?:${SLUG_DATE_TOKEN_SOURCES[token.value]})`;
194
+ } else {
195
+ captured.add(field);
196
+ source += `(?<${group}>${SLUG_DATE_TOKEN_SOURCES[token.value]})`;
197
+ }
198
+ if (field === "year") hasYear = true;
199
+ if (field === "month") hasMonth = true;
200
+ if (field === "day") hasDay = true;
201
+ index = token.end;
202
+ continue;
203
+ }
204
+ source += escapeRegExp(character);
205
+ index += 1;
206
+ }
207
+ if (!hasYear || !hasMonth || !hasDay) return null;
208
+ return new RegExp(source);
209
+ }
210
+ function readSlugDateToken(format, start) {
211
+ const character = format[start];
212
+ let end = start;
213
+ while (end < format.length && format[end] === character) end += 1;
214
+ const run = format.slice(start, end);
215
+ if (character === "Y" && (run === "YYYY" || run === "YY")) {
216
+ return { value: run, end };
217
+ }
218
+ if ((character === "M" || character === "D") && (run === "M" || run === "MM" || run === "D" || run === "DD")) {
219
+ return { value: run, end };
220
+ }
221
+ return null;
222
+ }
223
+ var DATE_PATTERN = /(\d{4})-(\d{1,2})-(\d{1,2})/;
224
+ function normalizeDateValue(value) {
225
+ if (value instanceof Date) {
226
+ if (Number.isNaN(value.getTime())) return null;
227
+ return formatDate(
228
+ value.getUTCFullYear(),
229
+ value.getUTCMonth() + 1,
230
+ value.getUTCDate()
231
+ );
232
+ }
233
+ if (typeof value === "string") return matchDate(value);
234
+ return null;
235
+ }
236
+ function matchDate(value) {
237
+ const match = DATE_PATTERN.exec(value);
238
+ if (!match) return null;
239
+ return formatDate(Number(match[1]), Number(match[2]), Number(match[3]));
240
+ }
241
+ function formatDate(year, month, day) {
242
+ if (month < 1 || month > 12 || day < 1 || day > 31) return null;
243
+ const paddedYear = year.toString().padStart(4, "0");
244
+ const paddedMonth = month.toString().padStart(2, "0");
245
+ const paddedDay = day.toString().padStart(2, "0");
246
+ return `${paddedYear}-${paddedMonth}-${paddedDay}`;
247
+ }
248
+ function compareDailyNotes(a, b) {
249
+ if (a.date !== b.date) {
250
+ if (a.date.length === 0) return 1;
251
+ if (b.date.length === 0) return -1;
252
+ return a.date < b.date ? 1 : -1;
253
+ }
254
+ if (a.slug === b.slug) return 0;
255
+ return a.slug < b.slug ? -1 : 1;
256
+ }
257
+ function stripHtml(html) {
258
+ return html.replace(/<[^>]*>/g, "");
259
+ }
260
+ function collapseWhitespace(text) {
261
+ return text.replace(/\s+/g, " ").trim();
262
+ }
263
+ function decodeHtmlEntities(text) {
264
+ return text.replace(
265
+ /&(#[xX]?[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g,
266
+ (match, entity) => {
267
+ switch (entity) {
268
+ case "amp":
269
+ return "&";
270
+ case "lt":
271
+ return "<";
272
+ case "gt":
273
+ return ">";
274
+ case "quot":
275
+ return '"';
276
+ case "apos":
277
+ return "'";
278
+ case "nbsp":
279
+ return " ";
280
+ default:
281
+ return decodeNumericEntity(match, entity);
282
+ }
283
+ }
284
+ );
285
+ }
286
+ function decodeNumericEntity(match, entity) {
287
+ if (entity.startsWith("#x") || entity.startsWith("#X")) {
288
+ return fromCodePoint(match, Number.parseInt(entity.slice(2), 16));
289
+ }
290
+ if (entity.startsWith("#")) {
291
+ return fromCodePoint(match, Number.parseInt(entity.slice(1), 10));
292
+ }
293
+ return match;
294
+ }
295
+ function fromCodePoint(match, code) {
296
+ if (!Number.isInteger(code) || code < 0 || code > 1114111) return match;
297
+ return String.fromCodePoint(code);
298
+ }
299
+
300
+ // src/daily-notes.server.ts
301
+ function resolveDailyNotesOptionsFromConfig(config) {
302
+ const plugin = resolvePlugins(config.plugins).find(
303
+ (candidate) => candidate.name === DAILY_NOTES_PLUGIN_NAME
304
+ );
305
+ return plugin?.options ?? {};
306
+ }
307
+ function getDailyNotes(args) {
308
+ const options = args.options ?? resolveDailyNotesOptionsFromConfig(args.config);
309
+ const extract = resolveExtractOptions(options);
310
+ const display = resolveDisplayOptions(options);
311
+ const directory = options?.source?.directory ?? DEFAULT_DIRECTORY;
312
+ const pathPattern = options?.source?.pathPattern;
313
+ const slugDateFormat = options?.source?.dateFormat ?? DEFAULT_SLUG_DATE_FORMAT;
314
+ const limit = Math.max(0, options?.widget?.limit ?? DEFAULT_LIMIT);
315
+ const notes = [];
316
+ const discoverableSlugs = new Set(
317
+ args.manifest.discoverableEntries.map((entry) => entry.slug)
318
+ );
319
+ for (const entry of args.manifest.entries) {
320
+ if (!isDailyNoteSlug(entry.slug, directory, pathPattern)) continue;
321
+ const snippet = extractDailyNoteSnippet(entry, extract);
322
+ if (snippet === null) continue;
323
+ const discoverable = discoverableSlugs.has(entry.slug);
324
+ const date = resolveDailyNoteDate(entry, slugDateFormat);
325
+ notes.push({
326
+ date,
327
+ dateDisplay: formatDailyNoteDate(date, display),
328
+ snippet,
329
+ slug: entry.slug,
330
+ sourceUrl: discoverable ? entry.permalink : null,
331
+ sourceTitle: discoverable ? entry.title : null
332
+ });
333
+ }
334
+ return notes.toSorted(compareDailyNotes).slice(0, limit);
335
+ }
336
+
337
+ // components/daily-notes.tsx
338
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
339
+ var DEFAULT_LIMIT2 = 5;
4
340
  function DailyNotes(props) {
5
- const limit = props.limit ?? DEFAULT_LIMIT;
6
- const notes = props.notes.slice(0, Math.max(0, limit));
341
+ const site = useSiteContext();
342
+ const limit = props.limit ?? DEFAULT_LIMIT2;
343
+ const notes = (props.notes ?? (site ? getDailyNotes({ manifest: site.manifest, config: site.config }) : [])).slice(0, Math.max(0, limit));
7
344
  if (notes.length === 0) return null;
8
345
  return /* @__PURE__ */ jsxs("section", { class: "rr-daily-notes", "aria-labelledby": "daily-notes-title", children: [
9
346
  /* @__PURE__ */ jsxs("div", { class: "rr-daily-notes__header", children: [
package/dist/index.js CHANGED
@@ -298,32 +298,7 @@ function fromCodePoint(match, code) {
298
298
  }
299
299
 
300
300
  // components/daily-notes.tsx
301
- import { jsx, jsxs } from "hono/jsx/jsx-runtime";
302
- var DEFAULT_LIMIT2 = 5;
303
- function DailyNotes(props) {
304
- const limit = props.limit ?? DEFAULT_LIMIT2;
305
- const notes = props.notes.slice(0, Math.max(0, limit));
306
- if (notes.length === 0) return null;
307
- return /* @__PURE__ */ jsxs("section", { class: "rr-daily-notes", "aria-labelledby": "daily-notes-title", children: [
308
- /* @__PURE__ */ jsxs("div", { class: "rr-daily-notes__header", children: [
309
- /* @__PURE__ */ jsx("p", { class: "rr-daily-notes__eyebrow", children: "DAILY NOTES" }),
310
- /* @__PURE__ */ jsx("h2", { class: "rr-daily-notes__title", id: "daily-notes-title", children: "Recent Daily Notes" })
311
- ] }),
312
- /* @__PURE__ */ jsx("ul", { class: "rr-daily-notes__list", children: notes.map((note) => /* @__PURE__ */ jsxs("li", { class: "rr-daily-notes__item", children: [
313
- /* @__PURE__ */ jsxs("div", { class: "rr-daily-notes__meta", children: [
314
- note.date.length > 0 ? /* @__PURE__ */ jsx("time", { class: "rr-daily-notes__date", dateTime: note.date, children: note.dateDisplay }) : null,
315
- note.sourceUrl !== null ? /* @__PURE__ */ jsx("a", { class: "rr-daily-notes__source", href: note.sourceUrl, children: resolveSourceLabel(note) }) : null
316
- ] }),
317
- /* @__PURE__ */ jsx("p", { class: "rr-daily-notes__snippet", children: note.snippet })
318
- ] }, note.slug)) })
319
- ] });
320
- }
321
- function resolveSourceLabel(note) {
322
- if (note.sourceTitle !== null && note.sourceTitle.length > 0) {
323
- return note.sourceTitle;
324
- }
325
- return note.sourceUrl ?? "";
326
- }
301
+ import { useSiteContext } from "@riebeckite/ui";
327
302
 
328
303
  // src/daily-notes.server.ts
329
304
  import { resolvePlugins } from "@riebeckite/core";
@@ -363,6 +338,35 @@ function getDailyNotes(args) {
363
338
  return notes.toSorted(compareDailyNotes).slice(0, limit);
364
339
  }
365
340
 
341
+ // components/daily-notes.tsx
342
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
343
+ var DEFAULT_LIMIT2 = 5;
344
+ function DailyNotes(props) {
345
+ const site = useSiteContext();
346
+ const limit = props.limit ?? DEFAULT_LIMIT2;
347
+ const notes = (props.notes ?? (site ? getDailyNotes({ manifest: site.manifest, config: site.config }) : [])).slice(0, Math.max(0, limit));
348
+ if (notes.length === 0) return null;
349
+ return /* @__PURE__ */ jsxs("section", { class: "rr-daily-notes", "aria-labelledby": "daily-notes-title", children: [
350
+ /* @__PURE__ */ jsxs("div", { class: "rr-daily-notes__header", children: [
351
+ /* @__PURE__ */ jsx("p", { class: "rr-daily-notes__eyebrow", children: "DAILY NOTES" }),
352
+ /* @__PURE__ */ jsx("h2", { class: "rr-daily-notes__title", id: "daily-notes-title", children: "Recent Daily Notes" })
353
+ ] }),
354
+ /* @__PURE__ */ jsx("ul", { class: "rr-daily-notes__list", children: notes.map((note) => /* @__PURE__ */ jsxs("li", { class: "rr-daily-notes__item", children: [
355
+ /* @__PURE__ */ jsxs("div", { class: "rr-daily-notes__meta", children: [
356
+ note.date.length > 0 ? /* @__PURE__ */ jsx("time", { class: "rr-daily-notes__date", dateTime: note.date, children: note.dateDisplay }) : null,
357
+ note.sourceUrl !== null ? /* @__PURE__ */ jsx("a", { class: "rr-daily-notes__source", href: note.sourceUrl, children: resolveSourceLabel(note) }) : null
358
+ ] }),
359
+ /* @__PURE__ */ jsx("p", { class: "rr-daily-notes__snippet", children: note.snippet })
360
+ ] }, note.slug)) })
361
+ ] });
362
+ }
363
+ function resolveSourceLabel(note) {
364
+ if (note.sourceTitle !== null && note.sourceTitle.length > 0) {
365
+ return note.sourceTitle;
366
+ }
367
+ return note.sourceUrl ?? "";
368
+ }
369
+
366
370
  // index.ts
367
371
  function dailyNotesPlugin(options) {
368
372
  return definePlugin({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-daily-notes",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,8 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "hono": "^4.12.25",
25
- "@riebeckite/core": "0.0.23"
25
+ "@riebeckite/core": "0.0.25",
26
+ "@riebeckite/ui": "0.0.25"
26
27
  },
27
28
  "devDependencies": {
28
29
  "tsx": "^4.22.4"