@farming-labs/next 0.1.13 → 0.1.17

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.
@@ -0,0 +1,447 @@
1
+ import { ChangelogDirectory, ChangelogTOC } from "./changelog-rail-search.mjs";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import Link from "next/link";
5
+ import { notFound } from "next/navigation";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { createPageMetadata } from "@farming-labs/theme";
8
+ import { resolveChangelogConfig } from "@farming-labs/docs";
9
+ import { createElement, isValidElement } from "react";
10
+
11
+ //#region src/changelog.tsx
12
+ function formatDisplayDate(value) {
13
+ const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
14
+ if (Number.isNaN(parsed.valueOf())) return value;
15
+ return new Intl.DateTimeFormat("en-US", {
16
+ month: "long",
17
+ day: "numeric",
18
+ year: "numeric",
19
+ timeZone: "UTC"
20
+ }).format(parsed).toUpperCase();
21
+ }
22
+ function normalizeAuthors(value) {
23
+ if (!value) return [];
24
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string" && item.trim().length > 0);
25
+ return value.trim() ? [value] : [];
26
+ }
27
+ function normalizeWhitespace(value) {
28
+ return value.replace(/\s+/g, " ").trim();
29
+ }
30
+ function stripInlineMarkdown(value) {
31
+ return normalizeWhitespace(value.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "$1").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/_([^_]+)_/g, "$1").replace(/<[^>]+>/g, ""));
32
+ }
33
+ function slugifyHeading(text) {
34
+ return text.trim().toLowerCase().replace(/[`'"‘’“”]/g, "").replace(/&/g, " and ").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
35
+ }
36
+ function extractHeadingId(value) {
37
+ const match = value.match(/\s+(?:\[#([A-Za-z0-9._:-]+)\]|\{#([A-Za-z0-9._:-]+)\})\s*$/);
38
+ if (!match) return { title: value };
39
+ return {
40
+ title: value.slice(0, match.index).trimEnd(),
41
+ id: match[1] ?? match[2]
42
+ };
43
+ }
44
+ function extractChangelogToc(sourcePath) {
45
+ try {
46
+ const lines = readFileSync(join(process.cwd(), sourcePath), "utf-8").split(/\r?\n/);
47
+ const items = [];
48
+ const seenSlugs = /* @__PURE__ */ new Map();
49
+ let inFrontmatter = false;
50
+ let frontmatterDone = false;
51
+ let inFence = false;
52
+ for (const line of lines) {
53
+ const trimmed = line.trim();
54
+ if (!frontmatterDone && trimmed === "---") {
55
+ inFrontmatter = !inFrontmatter;
56
+ if (!inFrontmatter) frontmatterDone = true;
57
+ continue;
58
+ }
59
+ if (inFrontmatter) continue;
60
+ if (/^(```|~~~)/.test(trimmed)) {
61
+ inFence = !inFence;
62
+ continue;
63
+ }
64
+ if (inFence) continue;
65
+ const headingMatch = line.match(/^(#{2,4})\s+(.+?)\s*#*\s*$/);
66
+ if (!headingMatch) continue;
67
+ const depth = headingMatch[1].length;
68
+ const resolvedHeading = extractHeadingId(headingMatch[2]);
69
+ const title = stripInlineMarkdown(resolvedHeading.title);
70
+ if (!title) continue;
71
+ const baseSlug = resolvedHeading.id || slugifyHeading(title) || `section-${items.length + 1}`;
72
+ const seen = seenSlugs.get(baseSlug) ?? 0;
73
+ seenSlugs.set(baseSlug, seen + 1);
74
+ const slug = resolvedHeading.id ? baseSlug : seen === 0 ? baseSlug : `${baseSlug}-${seen}`;
75
+ items.push({
76
+ title,
77
+ url: `#${slug}`,
78
+ depth
79
+ });
80
+ }
81
+ return items;
82
+ } catch {
83
+ return [];
84
+ }
85
+ }
86
+ function resolveEntries(entries) {
87
+ return entries.filter((entry) => entry.metadata?.draft !== true).map((entry) => ({
88
+ slug: entry.slug,
89
+ date: entry.date,
90
+ url: entry.url,
91
+ sourcePath: entry.sourcePath,
92
+ title: entry.metadata?.title?.trim() || entry.slug.replace(/-/g, " "),
93
+ description: entry.metadata?.description?.trim() || void 0,
94
+ authors: normalizeAuthors(entry.metadata?.authors),
95
+ version: typeof entry.metadata?.version === "string" ? entry.metadata.version : void 0,
96
+ tags: Array.isArray(entry.metadata?.tags) ? entry.metadata.tags.filter((item) => typeof item === "string" && item.trim().length > 0) : [],
97
+ pinned: entry.metadata?.pinned === true,
98
+ image: typeof entry.metadata?.image === "string" && entry.metadata.image.trim().length > 0 ? entry.metadata.image : void 0,
99
+ Component: entry.Component
100
+ })).sort((left, right) => {
101
+ if (left.pinned !== right.pinned) return left.pinned ? -1 : 1;
102
+ return right.date.localeCompare(left.date);
103
+ });
104
+ }
105
+ function findEntry(entries, slug) {
106
+ return entries.find((entry) => entry.slug === slug);
107
+ }
108
+ function toDirectoryEntries(entries) {
109
+ return entries.map((entry) => ({
110
+ slug: entry.slug,
111
+ title: entry.title,
112
+ description: entry.description,
113
+ date: entry.date,
114
+ url: entry.url,
115
+ version: entry.version,
116
+ tags: entry.tags,
117
+ authors: entry.authors,
118
+ image: entry.image,
119
+ content: createElement(entry.Component)
120
+ }));
121
+ }
122
+ function renderActionsComponent(value) {
123
+ if (!value) return null;
124
+ if (isValidElement(value)) return value;
125
+ if (typeof value === "function") return createElement(value);
126
+ return value;
127
+ }
128
+ function getListingUrl(config) {
129
+ const changelog = resolveChangelogConfig(config.changelog);
130
+ return `/${(config.entry ?? "docs").replace(/^\/+|\/+$/g, "")}/${changelog.path}`;
131
+ }
132
+ function formatChangelogTagLabel(tag) {
133
+ return tag.split(/[-_\s]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
134
+ }
135
+ function ChangelogActions({ children }) {
136
+ if (!children) return null;
137
+ return /* @__PURE__ */ jsx("div", {
138
+ className: "fd-changelog-actions",
139
+ children
140
+ });
141
+ }
142
+ function MetaRow({ entry, includeVersion = true }) {
143
+ return /* @__PURE__ */ jsx("div", {
144
+ className: "flex flex-wrap gap-2",
145
+ children: [includeVersion && entry.version ? {
146
+ label: entry.version,
147
+ kind: "version"
148
+ } : null, ...entry.tags.map((tag) => ({
149
+ label: tag,
150
+ kind: "tag"
151
+ }))].filter(Boolean).map((item) => /* @__PURE__ */ jsx("span", {
152
+ className: "fd-changelog-tag",
153
+ "data-testid": item.kind === "tag" ? "changelog-tag" : void 0,
154
+ children: item.kind === "tag" ? formatChangelogTagLabel(item.label) : item.label
155
+ }, `${item.kind}-${item.label}`))
156
+ });
157
+ }
158
+ function EmptyState() {
159
+ return /* @__PURE__ */ jsxs("div", {
160
+ className: "not-prose rounded-[1.75rem] bg-black/[0.02] px-8 py-12 shadow-[inset_0_0_0_1px_rgba(15,23,42,0.06)] dark:bg-white/[0.03] dark:shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)]",
161
+ children: [/* @__PURE__ */ jsx("h2", {
162
+ className: "text-2xl font-semibold tracking-tight text-neutral-950 dark:text-neutral-50",
163
+ children: "No changelog entries yet"
164
+ }), /* @__PURE__ */ jsxs("p", {
165
+ className: "mt-3 max-w-2xl text-base leading-7 text-neutral-600 dark:text-neutral-400",
166
+ children: [
167
+ "Add a dated folder like",
168
+ " ",
169
+ /* @__PURE__ */ jsx("code", {
170
+ className: "rounded bg-black/[0.05] px-1.5 py-0.5 dark:bg-white/[0.08]",
171
+ children: "changelog/2026-03-04/page.mdx"
172
+ }),
173
+ " ",
174
+ "and it will appear here automatically."
175
+ ]
176
+ })]
177
+ });
178
+ }
179
+ function ChangelogEntryPagination(props) {
180
+ if (!props.previous && !props.next) return null;
181
+ const both = Boolean(props.previous && props.next);
182
+ return /* @__PURE__ */ jsxs("nav", {
183
+ className: "not-prose fd-page-nav mt-16",
184
+ "aria-label": "Changelog pagination",
185
+ style: both ? void 0 : { gridTemplateColumns: "1fr" },
186
+ children: [props.previous ? /* @__PURE__ */ jsxs(Link, {
187
+ href: props.previous.url,
188
+ prefetch: true,
189
+ className: "fd-page-nav-card fd-page-nav-prev",
190
+ children: [
191
+ /* @__PURE__ */ jsxs("span", {
192
+ className: "fd-page-nav-label",
193
+ children: [/* @__PURE__ */ jsx("span", {
194
+ "aria-hidden": true,
195
+ children: "←"
196
+ }), "Previous"]
197
+ }),
198
+ /* @__PURE__ */ jsx("span", {
199
+ className: "fd-page-nav-title",
200
+ children: props.previous.title
201
+ }),
202
+ props.previous.description ? /* @__PURE__ */ jsx("span", {
203
+ className: "line-clamp-2 text-sm text-fd-muted-foreground",
204
+ children: props.previous.description
205
+ }) : null
206
+ ]
207
+ }) : both ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true" }) : null, props.next ? /* @__PURE__ */ jsxs(Link, {
208
+ href: props.next.url,
209
+ prefetch: true,
210
+ className: "fd-page-nav-card fd-page-nav-next",
211
+ children: [
212
+ /* @__PURE__ */ jsxs("span", {
213
+ className: "fd-page-nav-label",
214
+ children: ["Next", /* @__PURE__ */ jsx("span", {
215
+ "aria-hidden": true,
216
+ children: "→"
217
+ })]
218
+ }),
219
+ /* @__PURE__ */ jsx("span", {
220
+ className: "fd-page-nav-title",
221
+ children: props.next.title
222
+ }),
223
+ props.next.description ? /* @__PURE__ */ jsx("span", {
224
+ className: "line-clamp-2 text-sm text-fd-muted-foreground",
225
+ children: props.next.description
226
+ }) : null
227
+ ]
228
+ }) : both ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true" }) : null]
229
+ });
230
+ }
231
+ function ChangelogEntryView(props) {
232
+ const Content = props.entry.Component;
233
+ const contentClassName = "fd-changelog-prose prose dark:prose-invert max-w-none min-w-0 prose-headings:scroll-mt-8 prose-headings:font-semibold prose-a:no-underline prose-headings:tracking-tight prose-headings:text-balance prose-p:tracking-tight prose-p:text-balance";
234
+ const tocItems = props.toc.map((item) => ({
235
+ href: item.url,
236
+ title: item.title
237
+ }));
238
+ return /* @__PURE__ */ jsx("div", {
239
+ className: "fd-changelog-frame not-prose relative w-full pb-16",
240
+ children: /* @__PURE__ */ jsxs("div", {
241
+ className: "fd-changelog-shell",
242
+ children: [/* @__PURE__ */ jsxs("div", {
243
+ className: "fd-changelog-main",
244
+ children: [
245
+ /* @__PURE__ */ jsx("div", {
246
+ className: "border-b border-fd-border/50 pb-5",
247
+ children: /* @__PURE__ */ jsxs(Link, {
248
+ href: props.listingUrl,
249
+ prefetch: true,
250
+ style: { marginBottom: "30px" },
251
+ className: "inline-flex items-center gap-2 text-sm font-medium text-fd-muted-foreground no-underline transition-colors hover:text-fd-foreground",
252
+ children: [/* @__PURE__ */ jsx("span", {
253
+ "aria-hidden": true,
254
+ children: "←"
255
+ }), "Back to changelog"]
256
+ })
257
+ }),
258
+ /* @__PURE__ */ jsxs("div", {
259
+ style: { paddingTop: "4.5rem" },
260
+ children: [
261
+ /* @__PURE__ */ jsxs("div", {
262
+ className: "fd-changelog-mobile-summary",
263
+ children: [props.entry.version ? /* @__PURE__ */ jsx("div", {
264
+ className: "fd-changelog-version-box",
265
+ children: props.entry.version
266
+ }) : /* @__PURE__ */ jsx("div", {}), /* @__PURE__ */ jsx("time", {
267
+ className: "fd-changelog-date",
268
+ dateTime: props.entry.date,
269
+ children: formatDisplayDate(props.entry.date)
270
+ })]
271
+ }),
272
+ /* @__PURE__ */ jsxs("div", {
273
+ className: "fd-changelog-mobile-content",
274
+ children: [/* @__PURE__ */ jsxs("header", {
275
+ className: "flex flex-col gap-2",
276
+ children: [
277
+ /* @__PURE__ */ jsx("h1", {
278
+ className: "m-0 text-balance text-2xl font-semibold leading-snug tracking-tight text-fd-foreground md:text-3xl",
279
+ children: props.entry.title
280
+ }),
281
+ /* @__PURE__ */ jsx(MetaRow, {
282
+ entry: props.entry,
283
+ includeVersion: false
284
+ }),
285
+ props.entry.description ? /* @__PURE__ */ jsx("p", {
286
+ className: "m-0 max-w-2xl text-sm leading-relaxed text-fd-muted-foreground",
287
+ children: props.entry.description
288
+ }) : null
289
+ ]
290
+ }), /* @__PURE__ */ jsxs("article", {
291
+ className: `${contentClassName} mt-6`,
292
+ children: [props.entry.image ? /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("img", {
293
+ src: props.entry.image,
294
+ alt: `${props.entry.title} preview`,
295
+ loading: "lazy",
296
+ decoding: "async"
297
+ }) }) : null, /* @__PURE__ */ jsx(Content, {})]
298
+ })]
299
+ }),
300
+ /* @__PURE__ */ jsxs("div", {
301
+ className: "fd-changelog-desktop-grid",
302
+ children: [
303
+ /* @__PURE__ */ jsx("div", {
304
+ className: "flex items-start justify-end pt-0.5",
305
+ children: props.entry.version ? /* @__PURE__ */ jsx("div", {
306
+ className: "fd-changelog-version-box",
307
+ children: props.entry.version
308
+ }) : /* @__PURE__ */ jsx("div", {
309
+ className: "h-9",
310
+ "aria-hidden": "true"
311
+ })
312
+ }),
313
+ /* @__PURE__ */ jsxs("div", {
314
+ className: "fd-changelog-rail",
315
+ "data-testid": "changelog-rail",
316
+ children: [/* @__PURE__ */ jsx("div", {
317
+ className: "fd-changelog-timeline-line",
318
+ "data-testid": "changelog-timeline-line",
319
+ "aria-hidden": true,
320
+ style: {
321
+ top: "1rem",
322
+ bottom: "0"
323
+ }
324
+ }), /* @__PURE__ */ jsx("div", {
325
+ className: "fd-changelog-timeline-dot mt-1",
326
+ "data-testid": "changelog-timeline-dot",
327
+ "aria-hidden": true
328
+ })]
329
+ }),
330
+ /* @__PURE__ */ jsxs("div", {
331
+ className: "min-w-0 space-y-6",
332
+ children: [
333
+ /* @__PURE__ */ jsx("time", {
334
+ className: "fd-changelog-date block pt-0.5",
335
+ dateTime: props.entry.date,
336
+ children: formatDisplayDate(props.entry.date)
337
+ }),
338
+ /* @__PURE__ */ jsxs("header", {
339
+ className: "flex flex-col gap-2",
340
+ children: [
341
+ /* @__PURE__ */ jsx("h1", {
342
+ className: "m-0 text-balance text-2xl font-semibold leading-snug tracking-tight text-fd-foreground md:text-3xl",
343
+ children: props.entry.title
344
+ }),
345
+ /* @__PURE__ */ jsx(MetaRow, {
346
+ entry: props.entry,
347
+ includeVersion: false
348
+ }),
349
+ props.entry.description ? /* @__PURE__ */ jsx("p", {
350
+ className: "m-0 max-w-2xl text-sm leading-relaxed text-fd-muted-foreground",
351
+ children: props.entry.description
352
+ }) : null
353
+ ]
354
+ }),
355
+ /* @__PURE__ */ jsxs("article", {
356
+ className: contentClassName,
357
+ children: [props.entry.image ? /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("img", {
358
+ src: props.entry.image,
359
+ alt: `${props.entry.title} preview`,
360
+ loading: "lazy",
361
+ decoding: "async"
362
+ }) }) : null, /* @__PURE__ */ jsx(Content, {})]
363
+ })
364
+ ]
365
+ })
366
+ ]
367
+ })
368
+ ]
369
+ }),
370
+ /* @__PURE__ */ jsx(ChangelogEntryPagination, {
371
+ previous: props.previous,
372
+ next: props.next
373
+ })
374
+ ]
375
+ }), /* @__PURE__ */ jsx(ChangelogTOC, {
376
+ title: "On this page",
377
+ items: tocItems,
378
+ variant: "content"
379
+ })]
380
+ })
381
+ });
382
+ }
383
+ function createNextChangelogIndexPage(config, entries) {
384
+ return function NextChangelogIndexPage() {
385
+ const changelog = resolveChangelogConfig(config.changelog);
386
+ const resolvedEntries = resolveEntries(entries);
387
+ const actions = renderActionsComponent(changelog.actionsComponent);
388
+ return resolvedEntries.length > 0 ? /* @__PURE__ */ jsx(ChangelogDirectory, {
389
+ title: changelog.title,
390
+ description: changelog.description,
391
+ entries: toDirectoryEntries(resolvedEntries),
392
+ searchEnabled: changelog.search,
393
+ actions: actions ? /* @__PURE__ */ jsx(ChangelogActions, { children: actions }) : null
394
+ }) : /* @__PURE__ */ jsx(EmptyState, {});
395
+ };
396
+ }
397
+ function createNextChangelogEntryPage(config, entries) {
398
+ return async function NextChangelogEntryPage(props) {
399
+ const resolvedEntries = resolveEntries(entries);
400
+ const slug = (props.params ? await props.params : void 0)?.slug;
401
+ if (!slug) notFound();
402
+ const entry = findEntry(resolvedEntries, slug);
403
+ if (!entry) notFound();
404
+ const currentIndex = resolvedEntries.findIndex((candidate) => candidate.slug === slug);
405
+ const previous = currentIndex < resolvedEntries.length - 1 ? resolvedEntries[currentIndex + 1] : void 0;
406
+ const next = currentIndex > 0 ? resolvedEntries[currentIndex - 1] : void 0;
407
+ return /* @__PURE__ */ jsx(ChangelogEntryView, {
408
+ entry,
409
+ toc: extractChangelogToc(entry.sourcePath),
410
+ previous,
411
+ next,
412
+ listingUrl: getListingUrl(config)
413
+ });
414
+ };
415
+ }
416
+ function createNextChangelogStaticParams(entries) {
417
+ return function generateStaticParams() {
418
+ return resolveEntries(entries).map((entry) => ({ slug: entry.slug }));
419
+ };
420
+ }
421
+ function createNextChangelogIndexMetadata(config) {
422
+ const changelog = resolveChangelogConfig(config.changelog);
423
+ return createPageMetadata(config, {
424
+ title: changelog.title,
425
+ description: changelog.description
426
+ });
427
+ }
428
+ function createNextChangelogEntryMetadata(config, entries) {
429
+ return async function generateMetadata(props) {
430
+ const slug = (props.params ? await props.params : void 0)?.slug;
431
+ const entry = slug ? findEntry(resolveEntries(entries), slug) : void 0;
432
+ if (!entry) {
433
+ const changelog = resolveChangelogConfig(config.changelog);
434
+ return createPageMetadata(config, {
435
+ title: changelog.title,
436
+ description: changelog.description
437
+ });
438
+ }
439
+ return createPageMetadata(config, {
440
+ title: entry.title,
441
+ description: entry.description
442
+ });
443
+ };
444
+ }
445
+
446
+ //#endregion
447
+ export { createNextChangelogEntryMetadata, createNextChangelogEntryPage, createNextChangelogIndexMetadata, createNextChangelogIndexPage, createNextChangelogStaticParams };