@docubook/flame 1.0.0-beta.60 → 1.0.0-beta.80

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.
Files changed (50) hide show
  1. package/.docu/components/Anchor.tsx +1 -1
  2. package/.docu/components/Context.tsx +2 -2
  3. package/.docu/components/DocsLayout.tsx +1 -1
  4. package/.docu/components/EditWith.tsx +1 -1
  5. package/.docu/components/Menu.tsx +2 -2
  6. package/.docu/components/Navbar.tsx +1 -1
  7. package/.docu/components/Pagination.tsx +1 -1
  8. package/.docu/components/ScrollTo.tsx +1 -1
  9. package/.docu/components/Search.tsx +6 -6
  10. package/.docu/components/Sidebar.tsx +3 -3
  11. package/.docu/components/Social.tsx +1 -1
  12. package/.docu/components/Sublink.tsx +2 -2
  13. package/.docu/components/Toc.tsx +2 -2
  14. package/.docu/components/base/collapse.tsx +1 -1
  15. package/.docu/components/base/drawer.tsx +1 -1
  16. package/.docu/components/base/dropdown.tsx +1 -1
  17. package/.docu/components/base/input.tsx +1 -1
  18. package/.docu/components/base/kbd.tsx +1 -1
  19. package/.docu/components/base/modal.tsx +1 -1
  20. package/.docu/components/base/pagination/index.ts +21 -0
  21. package/.docu/components/base/pagination/pagination-docs.tsx +39 -0
  22. package/.docu/components/base/{pagination.tsx → pagination/pagination-numbers.tsx} +11 -180
  23. package/.docu/components/base/pagination/types.ts +129 -0
  24. package/.docu/components/base/theme-controller.tsx +1 -1
  25. package/.docu/components/base/toggle.tsx +1 -1
  26. package/.docu/{lib → node}/build.ts +31 -44
  27. package/.docu/node/mdx.ts +61 -0
  28. package/.docu/{lib → node}/paths.ts +5 -3
  29. package/.docu/{lib → node}/search-indexer.ts +2 -20
  30. package/.docu/{lib → node}/server.ts +8 -35
  31. package/.docu/{lib → node}/utils.ts +29 -0
  32. package/.docu/pages/docs/[[...slug]].tsx +2 -2
  33. package/.docu/pages/index.tsx +1 -1
  34. package/bin/cli.js +3 -1
  35. package/package.json +6 -6
  36. /package/.docu/{lib → node}/clean.ts +0 -0
  37. /package/.docu/{lib → node}/client-routes.ts +0 -0
  38. /package/.docu/{lib → node}/client.ts +0 -0
  39. /package/.docu/{lib → node}/deploy.ts +0 -0
  40. /package/.docu/{lib → node}/fs-scanner.ts +0 -0
  41. /package/.docu/{lib → node}/helpers.ts +0 -0
  42. /package/.docu/{lib → node}/html.ts +0 -0
  43. /package/.docu/{lib → node}/hydrate.ts +0 -0
  44. /package/.docu/{lib → node}/logger.ts +0 -0
  45. /package/.docu/{lib → node}/preview.ts +0 -0
  46. /package/.docu/{lib → node}/route.ts +0 -0
  47. /package/.docu/{lib → node}/search.ts +0 -0
  48. /package/.docu/{lib → node}/security.ts +0 -0
  49. /package/.docu/{lib → node}/sentry.ts +0 -0
  50. /package/.docu/{lib → node}/types.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  import { AnchorHTMLAttributes, ReactNode } from "react";
2
2
  import { ArrowUpRight } from "lucide-react";
3
- import { cn, isExternalUrl } from "../lib/utils";
3
+ import { cn, isExternalUrl } from "../node/utils";
4
4
 
5
5
  export interface AnchorProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
6
6
  href?: string;
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import { useState } from "react";
4
- import { cn } from "../lib/utils";
4
+ import { cn } from "../node/utils";
5
5
  import { Dropdown, DropdownItem } from "./base/dropdown";
6
- import { routes } from "../lib/client-routes";
6
+ import { routes } from "../node/client-routes";
7
7
  import { ChevronsUpDown, Check, type LucideIcon } from "lucide-react";
8
8
  import * as LucideIcons from "lucide-react";
9
9
 
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { loadDocuConfig } from "../lib/paths";
2
+ import { loadDocuConfig } from "../node/paths";
3
3
  import Menu from "./Menu";
4
4
 
5
5
  const docuConfig = loadDocuConfig();
@@ -1,5 +1,5 @@
1
1
  import { SquarePen } from "lucide-react";
2
- import { isEditEnabled, getEditLink, getRepoUrl } from "../lib/helpers";
2
+ import { isEditEnabled, getEditLink, getRepoUrl } from "../node/helpers";
3
3
 
4
4
  export interface EditWithProps {
5
5
  filePath: string;
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { useState } from "react";
4
4
  import Sublink from "./Sublink";
5
- import type { DocuRoute } from "../lib/types";
6
- import { cn } from "../lib/utils";
5
+ import type { DocuRoute } from "../node/types";
6
+ import { cn } from "../node/utils";
7
7
 
8
8
  interface MenuProps {
9
9
  onNavigate?: () => void;
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { useState } from "react";
3
3
  import Anchor from "./Anchor";
4
- import { cn } from "../lib/utils";
4
+ import { cn } from "../node/utils";
5
5
  import {
6
6
  Navbar as BaseNavbar,
7
7
  Logo as BaseLogo,
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { getPreviousNext } from "../lib/route";
3
+ import { getPreviousNext } from "../node/route";
4
4
  import { PaginationDocs } from "./base/pagination";
5
5
 
6
6
  interface PaginationProps {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { ArrowUpIcon } from "lucide-react";
4
4
  import { useEffect, useState, useCallback } from "react";
5
- import { cn } from "../lib/utils";
5
+ import { cn } from "../node/utils";
6
6
 
7
7
  interface ScrollToProps {
8
8
  className?: string;
@@ -4,9 +4,9 @@ import { useState, useEffect, useRef, useCallback } from "react";
4
4
  import { Search as SearchIcon, FileText, CornerDownLeft, Hash, AlignLeft } from "lucide-react";
5
5
  import { Modal, useModal } from "./base/modal";
6
6
  import { Kbd, FnKey } from "./base/kbd";
7
- import { cn } from "../lib/utils";
8
- import { search, type SearchResult } from "../lib/search";
9
- import type { SearchRecord } from "../lib/search-indexer";
7
+ import { cn } from "../node/utils";
8
+ import { search, type SearchResult } from "../node/search";
9
+ import type { SearchRecord } from "../node/search-indexer";
10
10
 
11
11
  interface SearchProps {
12
12
  className?: string;
@@ -123,7 +123,7 @@ export default function Search({ className }: SearchProps) {
123
123
  boxClassName="w-[calc(100%-2rem)] max-w-[640px] p-0 mx-auto relative z-10"
124
124
  >
125
125
  <div onKeyDown={handleKeyDown}>
126
- <div className="px-4 pt-4 pb-2">
126
+ <div className="px-4 pb-2 pt-4">
127
127
  <div className="border-base-300 flex items-center gap-3 rounded-lg border px-4 py-2.5">
128
128
  <SearchIcon className="text-primary h-5 w-5 shrink-0" />
129
129
  <input
@@ -132,7 +132,7 @@ export default function Search({ className }: SearchProps) {
132
132
  onChange={handleQueryChange}
133
133
  placeholder="Search documentation..."
134
134
  autoFocus
135
- className="placeholder:text-base-content/40 h-8 w-full border-none bg-transparent text-base ring-0 outline-none focus:ring-0 focus:outline-none"
135
+ className="placeholder:text-base-content/40 h-8 w-full border-none bg-transparent text-base outline-none ring-0 focus:outline-none focus:ring-0"
136
136
  aria-label="Search documentation"
137
137
  />
138
138
  {query && (
@@ -264,7 +264,7 @@ function GroupedResults({
264
264
  <div className="flex flex-col gap-1">
265
265
  {groups.map((group) => (
266
266
  <div key={group.section}>
267
- <div className="text-base-content/50 px-2 pt-3 pb-1 text-xs font-semibold">
267
+ <div className="text-base-content/50 px-2 pb-1 pt-3 text-xs font-semibold">
268
268
  {group.section}
269
269
  </div>
270
270
  {group.items.map(({ result, globalIndex }) => {
@@ -10,14 +10,14 @@ import {
10
10
  FileText,
11
11
  } from "lucide-react";
12
12
  import { Dropdown, DropdownLink } from "./base/dropdown";
13
- import { cn } from "../lib/utils";
13
+ import { cn } from "../node/utils";
14
14
  import { Context } from "./Context";
15
15
  import Menu from "./Menu";
16
16
  import { ThemeToggle } from "./Theme";
17
17
  import { GitHubLink } from "./Navbar";
18
18
  import Search from "./Search";
19
- import type { TocItem } from "../lib/types";
20
- import { config as docuConfig, routes } from "../lib/client-routes";
19
+ import type { TocItem } from "../node/types";
20
+ import { config as docuConfig, routes } from "../node/client-routes";
21
21
 
22
22
  interface SidebarProps {
23
23
  tocs?: TocItem[];
@@ -1,4 +1,4 @@
1
- import { getSocialLinks } from "../lib/helpers";
1
+ import { getSocialLinks } from "../node/helpers";
2
2
 
3
3
  export interface SocialProps {
4
4
  className?: string;
@@ -3,8 +3,8 @@
3
3
  import { useState } from "react";
4
4
  import { ChevronDown } from "lucide-react";
5
5
  import Anchor from "./Anchor";
6
- import type { DocuRoute } from "../lib/types";
7
- import { cn } from "../lib/utils";
6
+ import type { DocuRoute } from "../node/types";
7
+ import { cn } from "../node/utils";
8
8
 
9
9
  interface SublinkProps extends DocuRoute {
10
10
  level: number;
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { useState, useCallback, useEffect, useRef } from "react";
4
4
  import { ListIcon } from "lucide-react";
5
- import { cn } from "../lib/utils";
5
+ import { cn } from "../node/utils";
6
6
  import { ScrollTo } from "./ScrollTo";
7
- import { TocItem } from "../lib/types";
7
+ import { TocItem } from "../node/types";
8
8
 
9
9
  interface TocProps {
10
10
  tocs: TocItem[];
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { cn } from "../../lib/utils";
3
+ import { cn } from "../../node/utils";
4
4
  import { ChevronDown, ChevronRight, Plus, Minus } from "lucide-react";
5
5
  import { useState, type ReactNode } from "react";
6
6
 
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { cn } from "../../lib/utils";
3
+ import { cn } from "../../node/utils";
4
4
  import { X } from "lucide-react";
5
5
  import { useState, type ReactNode } from "react";
6
6
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { type HTMLAttributes, type ReactNode, forwardRef, useRef, useEffect } from "react";
4
- import { cn } from "../../lib/utils";
4
+ import { cn } from "../../node/utils";
5
5
 
6
6
  export interface DropdownProps extends HTMLAttributes<HTMLDetailsElement> {
7
7
  align?: "start" | "end";
@@ -1,5 +1,5 @@
1
1
  import { forwardRef } from "react";
2
- import { cn } from "../../lib/utils";
2
+ import { cn } from "../../node/utils";
3
3
 
4
4
  type InputColor =
5
5
  | "neutral"
@@ -13,7 +13,7 @@ import {
13
13
  Delete,
14
14
  ArrowRightToLine,
15
15
  } from "lucide-react";
16
- import { cn } from "../../lib/utils";
16
+ import { cn } from "../../node/utils";
17
17
 
18
18
  type KbdSize = "xs" | "sm" | "md" | "lg" | "xl";
19
19
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { forwardRef, useRef, useCallback } from "react";
4
- import { cn } from "../../lib/utils";
4
+ import { cn } from "../../node/utils";
5
5
 
6
6
  interface ModalProps extends React.DialogHTMLAttributes<HTMLDialogElement> {
7
7
  children: React.ReactNode;
@@ -0,0 +1,21 @@
1
+ export { PaginationDocs } from "./pagination-docs";
2
+ export {
3
+ Pagination,
4
+ PaginationItem,
5
+ PaginationButtons,
6
+ PaginationRange,
7
+ PaginationInfo,
8
+ PaginationFull,
9
+ } from "./pagination-numbers";
10
+ export { getPaginationRange } from "./types";
11
+ export type {
12
+ PaginationRootProps as PaginationProps,
13
+ PaginationItemProps,
14
+ PaginationRangeProps,
15
+ PaginationButtonsProps,
16
+ PaginationInfoProps,
17
+ PaginationFullProps,
18
+ PaginationDocsProps,
19
+ PaginationSize,
20
+ PaginationVariant,
21
+ } from "./types";
@@ -0,0 +1,39 @@
1
+ import { cn } from "../../../node/utils";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
3
+ import type { PaginationDocsProps } from "./types";
4
+
5
+ export function PaginationDocs({ prev, next, className }: PaginationDocsProps) {
6
+ return (
7
+ <div className={cn("grid grow grid-cols-1 gap-4 py-8 sm:grid-cols-2", className)}>
8
+ <div>
9
+ {prev && (
10
+ <a
11
+ href={prev.href}
12
+ className="btn btn-outline border-base-300 items-start! py-2! h-auto w-full flex-col pl-4 no-underline"
13
+ >
14
+ <span className="text-muted-foreground flex items-center text-xs">
15
+ <ChevronLeft className="mr-1 h-4 w-4" />
16
+ Previous
17
+ </span>
18
+ <span className="text-base-content mt-1 text-sm font-medium">{prev.title}</span>
19
+ </a>
20
+ )}
21
+ </div>
22
+
23
+ <div>
24
+ {next && (
25
+ <a
26
+ href={next.href}
27
+ className="btn btn-outline border-base-300 items-end! py-2! h-auto w-full flex-col pr-4 no-underline"
28
+ >
29
+ <span className="text-muted-foreground flex items-center text-xs">
30
+ Next
31
+ <ChevronRight className="ml-1 h-4 w-4" />
32
+ </span>
33
+ <span className="text-base-content mt-1 text-sm font-medium">{next.title}</span>
34
+ </a>
35
+ )}
36
+ </div>
37
+ </div>
38
+ );
39
+ }
@@ -1,136 +1,17 @@
1
1
  "use client";
2
2
 
3
- import { cn } from "../../lib/utils";
3
+ import { cn } from "../../../node/utils";
4
4
  import { ChevronLeft, ChevronRight } from "lucide-react";
5
- import { useMemo, type ReactNode } from "react";
6
-
7
- type PaginationSize = "lg" | "md" | "sm" | "xs";
8
- type PaginationVariant = "default" | "square" | "rounded";
9
-
10
- interface PaginationRootProps {
11
- children?: ReactNode;
12
- className?: string;
13
- size?: PaginationSize;
14
- variant?: PaginationVariant;
15
- }
16
-
17
- interface PaginationItemProps {
18
- children: ReactNode;
19
- active?: boolean;
20
- disabled?: boolean;
21
- onClick?: () => void;
22
- className?: string;
23
- size?: PaginationSize;
24
- variant?: PaginationVariant;
25
- "aria-label"?: string;
26
- }
27
-
28
- interface PaginationRangeProps {
29
- start: number;
30
- end: number;
31
- total: number;
32
- current: number;
33
- onPageChange: (page: number) => void;
34
- siblingCount?: number;
35
- className?: string;
36
- size?: PaginationSize;
37
- variant?: PaginationVariant;
38
- }
39
-
40
- interface PaginationButtonsProps {
41
- page: number;
42
- totalPages: number;
43
- onPageChange: (page: number) => void;
44
- className?: string;
45
- size?: PaginationSize;
46
- showFirstLast?: boolean;
47
- showPrevNext?: boolean;
48
- prevLabel?: string;
49
- nextLabel?: string;
50
- firstLabel?: string;
51
- lastLabel?: string;
52
- disabled?: boolean;
53
- }
54
-
55
- interface PaginationInfoProps {
56
- current: number;
57
- total: number;
58
- pageSize?: number;
59
- className?: string;
60
- label?: string;
61
- showTotal?: boolean;
62
- }
63
-
64
- interface PaginationFullProps {
65
- current: number;
66
- total: number;
67
- pageSize?: number;
68
- onPageChange: (page: number) => void;
69
- siblingCount?: number;
70
- className?: string;
71
- size?: PaginationSize;
72
- variant?: PaginationVariant;
73
- showFirstLast?: boolean;
74
- showPrevNext?: boolean;
75
- infoClassName?: string;
76
- }
77
-
78
- interface PaginationDocsProps {
79
- prev?: { href: string; title: string };
80
- next?: { href: string; title: string };
81
- className?: string;
82
- }
83
-
84
- function getPaginationRange({
85
- totalCount,
86
- pageSize,
87
- siblingCount = 1,
88
- currentPage,
89
- }: {
90
- totalCount: number;
91
- pageSize: number;
92
- siblingCount?: number;
93
- currentPage: number;
94
- }): (number | "ellipsis")[] {
95
- const totalPages = Math.ceil(totalCount / pageSize);
96
- const DOTS = "ellipsis" as const;
97
-
98
- if (totalPages === 1) {
99
- return [1];
100
- }
101
-
102
- const totalPageNumbers = siblingCount + 5;
103
-
104
- if (totalPages < totalPageNumbers) {
105
- return Array.from({ length: totalPages }, (_, i) => i + 1);
106
- }
107
-
108
- const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
109
- const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages);
110
-
111
- const showLeftDots = leftSiblingIndex > 2;
112
- const showRightDots = rightSiblingIndex < totalPages - 1;
113
-
114
- if (!showLeftDots && showRightDots) {
115
- const leftRange = Array.from({ length: 3 }, (_, i) => i + 1);
116
- return [...leftRange, DOTS, totalPages - 1, totalPages];
117
- }
118
-
119
- if (showLeftDots && !showRightDots) {
120
- const rightRange = Array.from({ length: 3 }, (_, i) => totalPages - 2 + i);
121
- return [1, 2, DOTS, ...rightRange];
122
- }
123
-
124
- if (showLeftDots && showRightDots) {
125
- const middleRange = Array.from(
126
- { length: rightSiblingIndex - leftSiblingIndex + 1 },
127
- (_, i) => leftSiblingIndex + i
128
- );
129
- return [1, 2, DOTS, ...middleRange, DOTS, totalPages - 1, totalPages];
130
- }
131
-
132
- return [1];
133
- }
5
+ import { useMemo } from "react";
6
+ import {
7
+ getPaginationRange,
8
+ type PaginationRootProps,
9
+ type PaginationItemProps,
10
+ type PaginationButtonsProps,
11
+ type PaginationRangeProps,
12
+ type PaginationInfoProps,
13
+ type PaginationFullProps,
14
+ } from "./types";
134
15
 
135
16
  export function Pagination({
136
17
  children,
@@ -496,53 +377,3 @@ export function PaginationFull({
496
377
  </div>
497
378
  );
498
379
  }
499
-
500
- export function PaginationDocs({ prev, next, className }: PaginationDocsProps) {
501
- return (
502
- <div className={cn("grid grow grid-cols-1 gap-4 py-8 sm:grid-cols-2", className)}>
503
- <div>
504
- {prev && (
505
- <a
506
- href={prev.href}
507
- className="btn btn-outline border-base-300 h-auto w-full flex-col items-start! py-2! pl-4 no-underline"
508
- >
509
- <span className="text-muted-foreground flex items-center text-xs">
510
- <ChevronLeft className="mr-1 h-4 w-4" />
511
- Previous
512
- </span>
513
- <span className="text-base-content mt-1 text-sm font-medium">{prev.title}</span>
514
- </a>
515
- )}
516
- </div>
517
-
518
- <div>
519
- {next && (
520
- <a
521
- href={next.href}
522
- className="btn btn-outline border-base-300 h-auto w-full flex-col items-end! py-2! pr-4 no-underline"
523
- >
524
- <span className="text-muted-foreground flex items-center text-xs">
525
- Next
526
- <ChevronRight className="ml-1 h-4 w-4" />
527
- </span>
528
- <span className="text-base-content mt-1 text-sm font-medium">{next.title}</span>
529
- </a>
530
- )}
531
- </div>
532
- </div>
533
- );
534
- }
535
-
536
- export type {
537
- PaginationRootProps as PaginationProps,
538
- PaginationItemProps,
539
- PaginationRangeProps,
540
- PaginationButtonsProps,
541
- PaginationInfoProps,
542
- PaginationFullProps,
543
- PaginationDocsProps,
544
- PaginationSize,
545
- PaginationVariant,
546
- };
547
-
548
- export { getPaginationRange };
@@ -0,0 +1,129 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export type PaginationSize = "lg" | "md" | "sm" | "xs";
4
+ export type PaginationVariant = "default" | "square" | "rounded";
5
+
6
+ export interface PaginationRootProps {
7
+ children?: ReactNode;
8
+ className?: string;
9
+ size?: PaginationSize;
10
+ variant?: PaginationVariant;
11
+ }
12
+
13
+ export interface PaginationItemProps {
14
+ children: ReactNode;
15
+ active?: boolean;
16
+ disabled?: boolean;
17
+ onClick?: () => void;
18
+ className?: string;
19
+ size?: PaginationSize;
20
+ variant?: PaginationVariant;
21
+ "aria-label"?: string;
22
+ }
23
+
24
+ export interface PaginationRangeProps {
25
+ start: number;
26
+ end: number;
27
+ total: number;
28
+ current: number;
29
+ onPageChange: (page: number) => void;
30
+ siblingCount?: number;
31
+ className?: string;
32
+ size?: PaginationSize;
33
+ variant?: PaginationVariant;
34
+ }
35
+
36
+ export interface PaginationButtonsProps {
37
+ page: number;
38
+ totalPages: number;
39
+ onPageChange: (page: number) => void;
40
+ className?: string;
41
+ size?: PaginationSize;
42
+ showFirstLast?: boolean;
43
+ showPrevNext?: boolean;
44
+ prevLabel?: string;
45
+ nextLabel?: string;
46
+ firstLabel?: string;
47
+ lastLabel?: string;
48
+ disabled?: boolean;
49
+ }
50
+
51
+ export interface PaginationInfoProps {
52
+ current: number;
53
+ total: number;
54
+ pageSize?: number;
55
+ className?: string;
56
+ label?: string;
57
+ showTotal?: boolean;
58
+ }
59
+
60
+ export interface PaginationFullProps {
61
+ current: number;
62
+ total: number;
63
+ pageSize?: number;
64
+ onPageChange: (page: number) => void;
65
+ siblingCount?: number;
66
+ className?: string;
67
+ size?: PaginationSize;
68
+ variant?: PaginationVariant;
69
+ showFirstLast?: boolean;
70
+ showPrevNext?: boolean;
71
+ infoClassName?: string;
72
+ }
73
+
74
+ export interface PaginationDocsProps {
75
+ prev?: { href: string; title: string };
76
+ next?: { href: string; title: string };
77
+ className?: string;
78
+ }
79
+
80
+ export function getPaginationRange({
81
+ totalCount,
82
+ pageSize,
83
+ siblingCount = 1,
84
+ currentPage,
85
+ }: {
86
+ totalCount: number;
87
+ pageSize: number;
88
+ siblingCount?: number;
89
+ currentPage: number;
90
+ }): (number | "ellipsis")[] {
91
+ const totalPages = Math.ceil(totalCount / pageSize);
92
+ const DOTS = "ellipsis" as const;
93
+
94
+ if (totalPages === 1) {
95
+ return [1];
96
+ }
97
+
98
+ const totalPageNumbers = siblingCount + 5;
99
+
100
+ if (totalPages < totalPageNumbers) {
101
+ return Array.from({ length: totalPages }, (_, i) => i + 1);
102
+ }
103
+
104
+ const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
105
+ const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages);
106
+
107
+ const showLeftDots = leftSiblingIndex > 2;
108
+ const showRightDots = rightSiblingIndex < totalPages - 1;
109
+
110
+ if (!showLeftDots && showRightDots) {
111
+ const leftRange = Array.from({ length: 3 }, (_, i) => i + 1);
112
+ return [...leftRange, DOTS, totalPages - 1, totalPages];
113
+ }
114
+
115
+ if (showLeftDots && !showRightDots) {
116
+ const rightRange = Array.from({ length: 3 }, (_, i) => totalPages - 2 + i);
117
+ return [1, 2, DOTS, ...rightRange];
118
+ }
119
+
120
+ if (showLeftDots && showRightDots) {
121
+ const middleRange = Array.from(
122
+ { length: rightSiblingIndex - leftSiblingIndex + 1 },
123
+ (_, i) => leftSiblingIndex + i
124
+ );
125
+ return [1, 2, DOTS, ...middleRange, DOTS, totalPages - 1, totalPages];
126
+ }
127
+
128
+ return [1];
129
+ }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useRef, useState, type ReactNode } from "react";
4
- import { cn } from "../../lib/utils";
4
+ import { cn } from "../../node/utils";
5
5
 
6
6
  export type ThemeName =
7
7
  | "light"
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { cn } from "../../lib/utils";
3
+ import { cn } from "../../node/utils";
4
4
  import type { InputHTMLAttributes, ReactNode } from "react";
5
5
  import { forwardRef, useState, useEffect } from "react";
6
6
 
@@ -4,16 +4,7 @@ import { createHash } from "node:crypto";
4
4
  import { join, dirname } from "node:path";
5
5
  import React from "react";
6
6
  import { renderToString } from "react-dom/server";
7
- import {
8
- serialize,
9
- extractTocsFromRawMdx,
10
- extractFrontmatterWithContent,
11
- createDefaultRehypePlugins,
12
- createDefaultRemarkPlugins,
13
- MDXRemote,
14
- } from "@docubook/core";
15
- import { createMdxComponents } from "@docubook/mdx-content";
16
- import { getGitLastModified } from "./utils";
7
+ import { compileMdx, getGitLastModifiedBatch } from "./mdx";
17
8
  import {
18
9
  DOCS_DIR,
19
10
  DIST_DIR,
@@ -112,39 +103,22 @@ function htmlShell(title: string, description: string, body: string): string {
112
103
  });
113
104
  }
114
105
 
115
- async function renderDocsPage(slug: string, rawMdx: string, filePath: string): Promise<string> {
116
- const tocs = extractTocsFromRawMdx(rawMdx);
117
- const { frontmatter, strippedContent } = extractFrontmatterWithContent<{
118
- title?: string;
119
- description?: string;
120
- date?: string;
121
- }>(rawMdx);
122
-
123
- const components = createMdxComponents();
124
- let compiledSource: string;
106
+ async function renderDocsPage(
107
+ slug: string,
108
+ rawMdx: string,
109
+ filePath: string,
110
+ gitDates?: Map<string, string>
111
+ ): Promise<string> {
112
+ let result;
125
113
  try {
126
- const serialized = await serialize(strippedContent, {
127
- mdxOptions: {
128
- rehypePlugins: createDefaultRehypePlugins(),
129
- remarkPlugins: createDefaultRemarkPlugins(),
130
- },
131
- });
132
- compiledSource = serialized.compiledSource;
114
+ result = await compileMdx(rawMdx, filePath, gitDates);
133
115
  } catch (err) {
134
116
  const msg = err instanceof Error ? err.message : "Unknown MDX error";
135
117
  throw new Error(`MDX Error in: docs/${slug}.mdx\n${msg}`, { cause: err });
136
118
  }
137
119
 
138
- const content = React.createElement(MDXRemote, {
139
- compiledSource,
140
- scope: {},
141
- frontmatter: {},
142
- components,
143
- });
144
-
145
- const title = frontmatter.title || slug || "Docs";
146
- const description = frontmatter.description || "";
147
- const date = frontmatter.date || (await getGitLastModified(filePath));
120
+ const title = result.frontmatter.title || slug || "Docs";
121
+ const description = result.frontmatter.description || "";
148
122
  const slugParts = slug ? slug.split("/") : [];
149
123
 
150
124
  const page = React.createElement(
@@ -154,12 +128,12 @@ async function renderDocsPage(slug: string, rawMdx: string, filePath: string): P
154
128
  slug: slugParts,
155
129
  title,
156
130
  description,
157
- date: date || undefined,
158
- content,
159
- tocs,
131
+ date: result.frontmatter.date || undefined,
132
+ content: result.content,
133
+ tocs: result.tocs,
160
134
  filePath,
161
135
  repoUrl: docuConfig.repo?.url,
162
- compiledSource,
136
+ compiledSource: result.compiledSource,
163
137
  })
164
138
  );
165
139
 
@@ -225,7 +199,20 @@ async function build() {
225
199
  logger.spinner.start("Building pages...");
226
200
  t = performance.now();
227
201
 
228
- const CONCURRENCY = parseInt(process.env.BUILD_CONCURRENCY || "10", 10);
202
+ const allRelPaths = mdxFiles
203
+ .map((f) => {
204
+ const mdxPath1 = join(DOCS_DIR, f.path, "index.mdx");
205
+ const mdxPath2 = join(DOCS_DIR, `${f.path}.mdx`);
206
+ const mdxPath3 = join(DOCS_DIR, `${f.path}.md`);
207
+ for (const p of [mdxPath1, mdxPath2, mdxPath3]) {
208
+ if (existsSync(p)) return p.replace(PROJECT_ROOT + "/", "");
209
+ }
210
+ return null;
211
+ })
212
+ .filter((p): p is string => p !== null);
213
+ const gitDates = await getGitLastModifiedBatch(allRelPaths);
214
+
215
+ const CONCURRENCY = Math.max(1, parseInt(process.env.BUILD_CONCURRENCY || "10", 10) || 10);
229
216
  const buildTasks = [];
230
217
  const errors: string[] = [];
231
218
 
@@ -263,7 +250,7 @@ async function build() {
263
250
 
264
251
  buildTasks.push(async () => {
265
252
  try {
266
- const html = await renderDocsPage(capturedFile.path, capturedRawMdx, relPath);
253
+ const html = await renderDocsPage(capturedFile.path, capturedRawMdx, relPath, gitDates);
267
254
  const outputPath = join(DIST_DIR, "docs", `${capturedFile.path}.html`);
268
255
  await mkdir(dirname(outputPath), { recursive: true });
269
256
  await writeFile(outputPath, html);
@@ -289,7 +276,7 @@ async function build() {
289
276
  const indexMdxPath = join(DOCS_DIR, "index.mdx");
290
277
  const indexRaw = await readFile(indexMdxPath, "utf-8");
291
278
  const indexRelPath = indexMdxPath.replace(PROJECT_ROOT + "/", "");
292
- const indexHtml = await renderDocsPage("", indexRaw, indexRelPath);
279
+ const indexHtml = await renderDocsPage("", indexRaw, indexRelPath, gitDates);
293
280
  await mkdir(join(DIST_DIR, "docs"), { recursive: true });
294
281
  await writeFile(join(DIST_DIR, "docs", "index.html"), indexHtml);
295
282
  } catch (err) {
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+ import {
3
+ serialize,
4
+ extractTocsFromRawMdx,
5
+ extractFrontmatterWithContent,
6
+ createDefaultRehypePlugins,
7
+ createDefaultRemarkPlugins,
8
+ MDXRemote,
9
+ } from "@docubook/core";
10
+ import { createMdxComponents } from "@docubook/mdx-content";
11
+ import { getGitLastModified, getGitLastModifiedBatch } from "./utils";
12
+
13
+ export { getGitLastModifiedBatch };
14
+
15
+ export interface MdxResult {
16
+ content: React.ReactElement;
17
+ compiledSource: string;
18
+ frontmatter: { title?: string; description?: string; date?: string };
19
+ tocs: ReturnType<typeof extractTocsFromRawMdx>;
20
+ }
21
+
22
+ export async function compileMdx(
23
+ rawMdx: string,
24
+ filePath: string,
25
+ gitDates?: Map<string, string>
26
+ ): Promise<MdxResult> {
27
+ const tocs = extractTocsFromRawMdx(rawMdx);
28
+ const { frontmatter, strippedContent } = extractFrontmatterWithContent<{
29
+ title?: string;
30
+ description?: string;
31
+ date?: string;
32
+ }>(rawMdx);
33
+
34
+ const serialized = await serialize(strippedContent, {
35
+ mdxOptions: {
36
+ rehypePlugins: createDefaultRehypePlugins(),
37
+ remarkPlugins: createDefaultRemarkPlugins(),
38
+ },
39
+ });
40
+
41
+ const components = createMdxComponents();
42
+ const content = React.createElement(MDXRemote, {
43
+ compiledSource: serialized.compiledSource,
44
+ scope: {},
45
+ frontmatter: {},
46
+ components,
47
+ });
48
+
49
+ const date =
50
+ frontmatter.date ||
51
+ gitDates?.get(filePath) ||
52
+ (await getGitLastModified(filePath)) ||
53
+ undefined;
54
+
55
+ return {
56
+ content,
57
+ compiledSource: serialized.compiledSource,
58
+ frontmatter: { ...frontmatter, date },
59
+ tocs,
60
+ };
61
+ }
@@ -3,18 +3,20 @@ import { existsSync, readFileSync } from "node:fs";
3
3
  import type { DocuConfig } from "./types";
4
4
 
5
5
  /**
6
- * FRAMEWORK_ROOT: Where the package code lives (.docu/components, .docu/pages, .docu/styles, .docu/lib)
6
+ * FRAMEWORK_ROOT: Where the package code lives (.docu/components, .docu/pages, .docu/styles, .docu/node)
7
7
  * PROJECT_ROOT: Where the user's project lives (docs/, docu.json)
8
8
  */
9
9
 
10
- // .docu/lib/paths.ts → package root is 2 levels up
10
+ // .docu/node/paths.ts → package root is 2 levels up
11
11
  export const FRAMEWORK_ROOT = resolve(import.meta.dirname, "../..");
12
12
  export const PROJECT_ROOT = process.cwd();
13
13
 
14
14
  // Framework paths (internal)
15
15
  export const PAGES_DIR = join(FRAMEWORK_ROOT, ".docu/pages");
16
16
  export const STYLES_DIR = join(FRAMEWORK_ROOT, ".docu/styles");
17
- export const LIB_DIR = join(FRAMEWORK_ROOT, ".docu/lib");
17
+ const nodeDir = join(FRAMEWORK_ROOT, ".docu/node");
18
+ const libDir = join(FRAMEWORK_ROOT, ".docu/lib");
19
+ export const LIB_DIR = existsSync(nodeDir) ? nodeDir : libDir;
18
20
 
19
21
  // Build output (user project)
20
22
  export const DIST_DIR = join(PROJECT_ROOT, ".docu/dist");
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { readFile, writeFile, readdir, mkdir } from "node:fs/promises";
15
15
  import { resolve, join } from "node:path";
16
+ import { extractFrontmatterWithContent } from "@docubook/core";
16
17
  import { DOCS_DIR, ASSETS_DIR, loadDocuConfig } from "./paths";
17
18
 
18
19
  const docuConfig = loadDocuConfig();
@@ -37,25 +38,6 @@ interface Frontmatter {
37
38
  description?: string;
38
39
  }
39
40
 
40
- function parseFrontmatter(raw: string): { frontmatter: Frontmatter; content: string } {
41
- if (!raw.startsWith("---")) return { frontmatter: {}, content: raw };
42
- const end = raw.indexOf("---", 3);
43
- if (end < 0) return { frontmatter: {}, content: raw };
44
-
45
- const fm: Frontmatter = {};
46
- const lines = raw.slice(3, end).trim().split("\n");
47
- for (const line of lines) {
48
- const match = line.match(/^(\w+):\s*(.+)$/);
49
- if (match) {
50
- const [, key, value] = match;
51
- if (key === "title" || key === "description") {
52
- fm[key] = value.trim();
53
- }
54
- }
55
- }
56
- return { frontmatter: fm, content: raw.slice(end + 3) };
57
- }
58
-
59
41
  function getSectionTitle(filePath: string): string {
60
42
  const parts = filePath.split("/");
61
43
  if (parts.length > 1) {
@@ -75,7 +57,7 @@ function slugify(text: string): string {
75
57
  }
76
58
 
77
59
  function extractRecords(filePath: string, raw: string): SearchRecord[] {
78
- const { frontmatter, content } = parseFrontmatter(raw);
60
+ const { frontmatter, strippedContent: content } = extractFrontmatterWithContent<Frontmatter>(raw);
79
61
  const records: SearchRecord[] = [];
80
62
  const url = `/docs/${filePath}`;
81
63
  const lvl0 = getSectionTitle(filePath);
@@ -4,16 +4,8 @@ import { resolve, join } from "node:path";
4
4
  import { watch } from "node:fs";
5
5
  import React from "react";
6
6
  import { renderToString } from "react-dom/server";
7
- import {
8
- serialize,
9
- extractTocsFromRawMdx,
10
- extractFrontmatterWithContent,
11
- createDefaultRehypePlugins,
12
- createDefaultRemarkPlugins,
13
- MDXRemote,
14
- } from "@docubook/core";
15
- import { createMdxComponents } from "@docubook/mdx-content";
16
- import { getGitLastModified, getContentType } from "./utils";
7
+ import { getContentType } from "./utils";
8
+ import { compileMdx } from "./mdx";
17
9
  import { DOCS_DIR, DIST_DIR, PAGES_DIR, PROJECT_ROOT, loadDocuConfig } from "./paths";
18
10
  import DocsPage from "../pages/docs/[[...slug]]";
19
11
  import NotFoundPage from "../pages/404";
@@ -139,34 +131,15 @@ async function getDocsForSlug(slug: string) {
139
131
  }
140
132
  }
141
133
  if (!filePath || !raw) return null;
142
- const tocs = extractTocsFromRawMdx(raw);
143
- const { frontmatter, strippedContent } = extractFrontmatterWithContent<{
144
- title?: string;
145
- description?: string;
146
- date?: string;
147
- }>(raw);
148
-
149
- const components = createMdxComponents();
150
- const serialized = await serialize(strippedContent, {
151
- mdxOptions: {
152
- rehypePlugins: createDefaultRehypePlugins(),
153
- remarkPlugins: createDefaultRemarkPlugins(),
154
- },
155
- });
156
- const content = React.createElement(MDXRemote, {
157
- compiledSource: serialized.compiledSource,
158
- scope: {},
159
- frontmatter: {},
160
- components,
161
- });
162
134
 
163
135
  const relPath = filePath.replace(PROJECT_ROOT + "/", "");
164
- const date = frontmatter.date || (await getGitLastModified(relPath)) || undefined;
136
+ const result = await compileMdx(raw, relPath);
137
+
165
138
  return {
166
- content,
167
- compiledSource: serialized.compiledSource,
168
- frontmatter: { ...frontmatter, date },
169
- tocs,
139
+ content: result.content,
140
+ compiledSource: result.compiledSource,
141
+ frontmatter: result.frontmatter,
142
+ tocs: result.tocs,
170
143
  filePath: relPath,
171
144
  };
172
145
  }
@@ -33,6 +33,35 @@ export async function getGitLastModified(filePath: string): Promise<string | nul
33
33
  }
34
34
  }
35
35
 
36
+ /** Batch git last modified dates for multiple files in a single spawn */
37
+ export async function getGitLastModifiedBatch(filePaths: string[]): Promise<Map<string, string>> {
38
+ const result = new Map<string, string>();
39
+ if (filePaths.length === 0) return result;
40
+
41
+ try {
42
+ const proc = Bun.spawn(
43
+ ["git", "log", "--format=%cI", "--name-only", "--diff-filter=ACMR", ...filePaths],
44
+ { stderr: "ignore" }
45
+ );
46
+ const text = await new Response(proc.stdout).text();
47
+ let currentDate = "";
48
+
49
+ for (const line of text.split("\n")) {
50
+ const trimmed = line.trim();
51
+ if (!trimmed) continue;
52
+ if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
53
+ currentDate = trimmed;
54
+ } else if (currentDate && !result.has(trimmed)) {
55
+ result.set(trimmed, currentDate);
56
+ }
57
+ }
58
+ } catch {
59
+ // fallback: return empty map, callers use frontmatter date
60
+ }
61
+
62
+ return result;
63
+ }
64
+
36
65
  const MIME_TYPES: Record<string, string> = {
37
66
  html: "text/html",
38
67
  css: "text/css",
@@ -3,8 +3,8 @@ import DocsBreadcrumb from "../../components/Breadcrumb";
3
3
  import Pagination from "../../components/Pagination";
4
4
  import { Typography } from "../../components/Typography";
5
5
  import EditWith from "../../components/EditWith";
6
- import { formatDate2 } from "../../lib/utils";
7
- import type { TocItem } from "../../lib/types";
6
+ import { formatDate2 } from "../../node/utils";
7
+ import type { TocItem } from "../../node/types";
8
8
  import { Footer } from "../../components/Footer";
9
9
  import Toc from "../../components/Toc";
10
10
 
@@ -1,5 +1,5 @@
1
1
  import * as icons from "lucide-react";
2
- import { loadDocuConfig } from "../lib/paths";
2
+ import { loadDocuConfig } from "../node/paths";
3
3
 
4
4
  const docuConfig = loadDocuConfig();
5
5
 
package/bin/cli.js CHANGED
@@ -78,5 +78,7 @@ if (!(command in COMMAND_MAP)) {
78
78
  process.exit(1);
79
79
  }
80
80
 
81
- const scriptPath = resolve(__dirname, "../.docu/lib", COMMAND_MAP[command]);
81
+ const nodePath = resolve(__dirname, "../.docu/node", COMMAND_MAP[command]);
82
+ const libPath = resolve(__dirname, "../.docu/lib", COMMAND_MAP[command]);
83
+ const scriptPath = existsSync(nodePath) ? nodePath : libPath;
82
84
  await import(scriptPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/flame",
3
- "version": "1.0.0-beta.60",
3
+ "version": "1.0.0-beta.80",
4
4
  "description": "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -76,11 +76,11 @@
76
76
  "typescript-eslint": "^8.59.2"
77
77
  },
78
78
  "scripts": {
79
- "dev": "bun .docu/lib/server.ts",
80
- "build": "NODE_ENV=production bun .docu/lib/build.ts",
81
- "clean": "bun .docu/lib/clean.ts",
82
- "preview": "bun .docu/lib/preview.ts",
83
- "deploy": "bun .docu/lib/deploy.ts",
79
+ "dev": "bun .docu/node/server.ts",
80
+ "build": "NODE_ENV=production bun .docu/node/build.ts",
81
+ "clean": "bun .docu/node/clean.ts",
82
+ "preview": "bun .docu/node/preview.ts",
83
+ "deploy": "bun .docu/node/deploy.ts",
84
84
  "test": "vitest run",
85
85
  "lint": "eslint ."
86
86
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes