zreader 0.1.0 → 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.
@@ -0,0 +1,286 @@
1
+ "use client";
2
+ import { useCallback } from 'react';
3
+ import { cn } from 'cn';
4
+ import { toast } from 'sonner';
5
+ import { LinkIcon, ChevronDownIcon, CopyIcon, FileTextIcon, ExternalLinkIcon } from 'lucide-react';
6
+ import { useReaderChapter, copyText } from 'zreader/react';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { cva } from 'class-variance-authority';
9
+ import { Tooltip as Tooltip$1, Slot, DropdownMenu as DropdownMenu$1 } from 'radix-ui';
10
+
11
+ // src/agent/react/copy-reference.tsx
12
+ function useOptionalReaderChapter() {
13
+ try {
14
+ return useReaderChapter();
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ function markdownHref(href) {
20
+ const path = href.split("#")[0].split("?")[0].replace(/\/+$/, "");
21
+ if (!path) return "/index.md";
22
+ return path.endsWith(".md") ? path : `${path}.md`;
23
+ }
24
+ function MarkdownAlternate() {
25
+ const ctx = useOptionalReaderChapter();
26
+ if (!ctx) return null;
27
+ return /* @__PURE__ */ jsx("link", { rel: "alternate", type: "text/markdown", href: markdownHref(ctx.href(ctx.chapter.id)), title: ctx.chapter.title });
28
+ }
29
+ var buttonVariants = cva(
30
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
31
+ {
32
+ variants: {
33
+ variant: {
34
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
35
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
36
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
37
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
38
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
39
+ link: "text-primary underline-offset-4 hover:underline"
40
+ },
41
+ size: {
42
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
43
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
44
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
45
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
46
+ icon: "size-9",
47
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
48
+ "icon-sm": "size-8",
49
+ "icon-lg": "size-10"
50
+ }
51
+ },
52
+ defaultVariants: {
53
+ variant: "default",
54
+ size: "default"
55
+ }
56
+ }
57
+ );
58
+ function Button({
59
+ className,
60
+ variant = "default",
61
+ size = "default",
62
+ asChild = false,
63
+ ...props
64
+ }) {
65
+ const Comp = asChild ? Slot.Root : "button";
66
+ return /* @__PURE__ */ jsx(
67
+ Comp,
68
+ {
69
+ "data-slot": "button",
70
+ "data-variant": variant,
71
+ "data-size": size,
72
+ className: cn(buttonVariants({ variant, size, className })),
73
+ ...props
74
+ }
75
+ );
76
+ }
77
+ function DropdownMenu({
78
+ ...props
79
+ }) {
80
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
81
+ }
82
+ function DropdownMenuTrigger({
83
+ ...props
84
+ }) {
85
+ return /* @__PURE__ */ jsx(
86
+ DropdownMenu$1.Trigger,
87
+ {
88
+ "data-slot": "dropdown-menu-trigger",
89
+ ...props
90
+ }
91
+ );
92
+ }
93
+ function DropdownMenuContent({
94
+ className,
95
+ sideOffset = 4,
96
+ ...props
97
+ }) {
98
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { children: /* @__PURE__ */ jsx(
99
+ DropdownMenu$1.Content,
100
+ {
101
+ "data-slot": "dropdown-menu-content",
102
+ sideOffset,
103
+ className: cn(
104
+ "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
105
+ className
106
+ ),
107
+ ...props
108
+ }
109
+ ) });
110
+ }
111
+ function DropdownMenuItem({
112
+ className,
113
+ inset,
114
+ variant = "default",
115
+ ...props
116
+ }) {
117
+ return /* @__PURE__ */ jsx(
118
+ DropdownMenu$1.Item,
119
+ {
120
+ "data-slot": "dropdown-menu-item",
121
+ "data-inset": inset,
122
+ "data-variant": variant,
123
+ className: cn(
124
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
125
+ className
126
+ ),
127
+ ...props
128
+ }
129
+ );
130
+ }
131
+ function DropdownMenuLabel({
132
+ className,
133
+ inset,
134
+ ...props
135
+ }) {
136
+ return /* @__PURE__ */ jsx(
137
+ DropdownMenu$1.Label,
138
+ {
139
+ "data-slot": "dropdown-menu-label",
140
+ "data-inset": inset,
141
+ className: cn(
142
+ "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
143
+ className
144
+ ),
145
+ ...props
146
+ }
147
+ );
148
+ }
149
+ function DropdownMenuSeparator({
150
+ className,
151
+ ...props
152
+ }) {
153
+ return /* @__PURE__ */ jsx(
154
+ DropdownMenu$1.Separator,
155
+ {
156
+ "data-slot": "dropdown-menu-separator",
157
+ className: cn("-mx-1 my-1 h-px bg-border", className),
158
+ ...props
159
+ }
160
+ );
161
+ }
162
+ function Tooltip({
163
+ ...props
164
+ }) {
165
+ return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
166
+ }
167
+ function TooltipTrigger({
168
+ ...props
169
+ }) {
170
+ return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
171
+ }
172
+ function TooltipContent({
173
+ className,
174
+ sideOffset = 0,
175
+ children,
176
+ ...props
177
+ }) {
178
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
179
+ Tooltip$1.Content,
180
+ {
181
+ "data-slot": "tooltip-content",
182
+ sideOffset,
183
+ className: cn(
184
+ "z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
185
+ className
186
+ ),
187
+ ...props,
188
+ children: [
189
+ children,
190
+ /* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
191
+ ]
192
+ }
193
+ ) });
194
+ }
195
+
196
+ // src/agent/react/reference.ts
197
+ var DEFAULT_PROMPT = "Refer the content at {url}";
198
+ var DEFAULT_ASK_PROMPT = "Read from {url} so I can ask questions about it.";
199
+ var PROVIDERS = {
200
+ chatgpt: { id: "chatgpt", label: "ChatGPT", href: (q) => `https://chatgpt.com/?hints=search&q=${encodeURIComponent(q)}` },
201
+ claude: { id: "claude", label: "Claude", href: (q) => `https://claude.ai/new?q=${encodeURIComponent(q)}` },
202
+ grok: { id: "grok", label: "Grok", href: (q) => `https://grok.com/?q=${encodeURIComponent(q)}` }
203
+ };
204
+ var DEFAULT_PROVIDERS = ["chatgpt", "claude", "grok"];
205
+ function resolveProvider(p) {
206
+ return typeof p === "string" ? PROVIDERS[p] : p;
207
+ }
208
+ function markdownUrl(pageUrl) {
209
+ const url = new URL(pageUrl);
210
+ url.search = "";
211
+ url.hash = "";
212
+ let path = url.pathname.replace(/\/+$/, "");
213
+ if (!path) path = "/index";
214
+ if (!path.endsWith(".md")) path += ".md";
215
+ url.pathname = path;
216
+ return url.href;
217
+ }
218
+ function formatPrompt(template, url) {
219
+ return template.includes("{url}") ? template.replaceAll("{url}", url) : `${template} ${url}`.trim();
220
+ }
221
+ function providerUrl(provider, prompt) {
222
+ return resolveProvider(provider).href(prompt);
223
+ }
224
+ function CopyReference({
225
+ markdown = false,
226
+ prompt = DEFAULT_PROMPT,
227
+ askPrompt = DEFAULT_ASK_PROMPT,
228
+ providers = DEFAULT_PROVIDERS,
229
+ label = "Copy reference",
230
+ className
231
+ }) {
232
+ const ctx = useOptionalReaderChapter();
233
+ const referenceUrl = useCallback(() => {
234
+ const page = ctx ? new URL(ctx.href(ctx.chapter.id), window.location.href).href : window.location.href;
235
+ if (!markdown) return page.split("#")[0];
236
+ return typeof markdown === "function" ? markdown(page) : markdownUrl(page);
237
+ }, [ctx, markdown]);
238
+ const copy = async (text, what) => {
239
+ if (await copyText(text)) toast.success(`${what} copied`, { description: text });
240
+ else toast.error("Could not copy");
241
+ };
242
+ const copyPrompt = () => void copy(formatPrompt(prompt, referenceUrl()), "Reference");
243
+ const copyUrl = () => void copy(referenceUrl(), markdown ? "Markdown URL" : "Link");
244
+ const open = (p) => {
245
+ window.open(p.href(formatPrompt(askPrompt, referenceUrl())), "_blank", "noopener,noreferrer");
246
+ };
247
+ const specs = providers.map(resolveProvider);
248
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center", className), "data-zr-copy-reference": "", children: [
249
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
250
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": label, onClick: copyPrompt, className: "hidden rounded-r-none sm:inline-flex lg:w-auto lg:px-3", children: [
251
+ /* @__PURE__ */ jsx(LinkIcon, {}),
252
+ /* @__PURE__ */ jsx("span", { className: "hidden lg:inline", children: label })
253
+ ] }) }),
254
+ /* @__PURE__ */ jsx(TooltipContent, { children: "Copy a prompt that points an AI agent at this page" })
255
+ ] }),
256
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
257
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": "Reference options", className: "sm:w-6 sm:rounded-l-none", children: [
258
+ /* @__PURE__ */ jsx(LinkIcon, { className: "sm:hidden" }),
259
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "hidden sm:block" })
260
+ ] }) }),
261
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-60", children: [
262
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: copyPrompt, children: [
263
+ /* @__PURE__ */ jsx(CopyIcon, {}),
264
+ label
265
+ ] }),
266
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: copyUrl, children: [
267
+ /* @__PURE__ */ jsx(FileTextIcon, {}),
268
+ markdown ? "Copy Markdown URL" : "Copy link"
269
+ ] }),
270
+ specs.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
271
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
272
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs font-normal text-muted-foreground", children: "Ask about this page" }),
273
+ specs.map((p) => /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => open(p), children: [
274
+ /* @__PURE__ */ jsx(ExternalLinkIcon, {}),
275
+ "Open in ",
276
+ p.label
277
+ ] }, p.id))
278
+ ] })
279
+ ] })
280
+ ] })
281
+ ] });
282
+ }
283
+
284
+ export { CopyReference, DEFAULT_ASK_PROMPT, DEFAULT_PROMPT, DEFAULT_PROVIDERS, MarkdownAlternate, PROVIDERS, formatPrompt, markdownHref, markdownUrl, providerUrl, resolveProvider };
285
+ //# sourceMappingURL=index.js.map
286
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/agent/react/markdown-alternate.tsx","../../../src/react/components/ui/button.tsx","../../../src/react/components/ui/dropdown-menu.tsx","../../../src/react/components/ui/tooltip.tsx","../../../src/agent/react/reference.ts","../../../src/agent/react/copy-reference.tsx"],"names":["jsx","DropdownMenuPrimitive","cn","TooltipPrimitive","jsxs"],"mappings":"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","file":"index.js","sourcesContent":["import { useReaderChapter, type ReaderChapterValue } from \"zreader/react\";\n\nfunction useOptionalReaderChapter(): ReaderChapterValue | null {\n // useReaderChapter() reads a context and throws outside <Reader>; the hook\n // itself is always called, so this keeps hook order stable.\n try {\n return useReaderChapter();\n } catch {\n return null;\n }\n}\n\nexport { useOptionalReaderChapter };\n\n/** `.md` twin of a reader href (relative or absolute): drops the hash/query, appends `.md`. */\nexport function markdownHref(href: string): string {\n const path = href.split(\"#\")[0]!.split(\"?\")[0]!.replace(/\\/+$/, \"\");\n if (!path) return \"/index.md\";\n return path.endsWith(\".md\") ? path : `${path}.md`;\n}\n\n/**\n * `<link rel=\"alternate\" type=\"text/markdown\">` for the current chapter, so\n * agents and crawlers can find its Markdown twin. React 19 hoists it into\n * <head>. Renders nothing outside <Reader>.\n */\nexport function MarkdownAlternate() {\n const ctx = useOptionalReaderChapter();\n if (!ctx) return null;\n return <link rel=\"alternate\" type=\"text/markdown\" href={markdownHref(ctx.href(ctx.chapter.id))} title={ctx.chapter.title} />;\n}\n","import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"cn\"\nimport { Slot } from \"radix-ui\"\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9\",\n \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n \"icon-sm\": \"size-8\",\n \"icon-lg\": \"size-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nfunction Button({\n className,\n variant = \"default\",\n size = \"default\",\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean\n }) {\n const Comp = asChild ? Slot.Root : \"button\"\n\n return (\n <Comp\n data-slot=\"button\"\n data-variant={variant}\n data-size={size}\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n )\n}\n\nexport { Button, buttonVariants }\n","\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"cn\"\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from \"lucide-react\"\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\"\n\nfunction DropdownMenu({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n\nfunction DropdownMenuPortal({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {\n return (\n <DropdownMenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\n )\n}\n\nfunction DropdownMenuTrigger({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n return (\n <DropdownMenuPrimitive.Trigger\n data-slot=\"dropdown-menu-trigger\"\n {...props}\n />\n )\n}\n\nfunction DropdownMenuContent({\n className,\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n data-slot=\"dropdown-menu-content\"\n sideOffset={sideOffset}\n className={cn(\n \"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n className\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n )\n}\n\nfunction DropdownMenuGroup({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n return (\n <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n )\n}\n\nfunction DropdownMenuItem({\n className,\n inset,\n variant = \"default\",\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n variant?: \"default\" | \"destructive\"\n}) {\n return (\n <DropdownMenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset}\n data-variant={variant}\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n return (\n <DropdownMenuPrimitive.CheckboxItem\n data-slot=\"dropdown-menu-checkbox-item\"\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n checked={checked}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon className=\"size-4\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n )\n}\n\nfunction DropdownMenuRadioGroup({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n return (\n <DropdownMenuPrimitive.RadioGroup\n data-slot=\"dropdown-menu-radio-group\"\n {...props}\n />\n )\n}\n\nfunction DropdownMenuRadioItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n return (\n <DropdownMenuPrimitive.RadioItem\n data-slot=\"dropdown-menu-radio-item\"\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <CircleIcon className=\"size-2 fill-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n )\n}\n\nfunction DropdownMenuLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n}) {\n return (\n <DropdownMenuPrimitive.Label\n data-slot=\"dropdown-menu-label\"\n data-inset={inset}\n className={cn(\n \"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuShortcut({\n className,\n ...props\n}: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"dropdown-menu-shortcut\"\n className={cn(\n \"ml-auto text-xs tracking-widest text-muted-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuSub({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />\n}\n\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean\n}) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset}\n className={cn(\n \"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className\n )}\n {...props}\n >\n {children}\n <ChevronRightIcon className=\"ml-auto size-4\" />\n </DropdownMenuPrimitive.SubTrigger>\n )\n}\n\nfunction DropdownMenuSubContent({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n return (\n <DropdownMenuPrimitive.SubContent\n data-slot=\"dropdown-menu-sub-content\"\n className={cn(\n \"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n DropdownMenu,\n DropdownMenuPortal,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuLabel,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubTrigger,\n DropdownMenuSubContent,\n}\n","import * as React from \"react\"\nimport { cn } from \"cn\"\nimport { Tooltip as TooltipPrimitive } from \"radix-ui\"\n\nfunction TooltipProvider({\n delayDuration = 0,\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n return (\n <TooltipPrimitive.Provider\n data-slot=\"tooltip-provider\"\n delayDuration={delayDuration}\n {...props}\n />\n )\n}\n\nfunction Tooltip({\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n}\n\nfunction TooltipTrigger({\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\n}\n\nfunction TooltipContent({\n className,\n sideOffset = 0,\n children,\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n data-slot=\"tooltip-content\"\n sideOffset={sideOffset}\n className={cn(\n \"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n className\n )}\n {...props}\n >\n {children}\n <TooltipPrimitive.Arrow className=\"z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground\" />\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n )\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\n","/**\n * Pure helpers for building agent references (no DOM, no React).\n */\n\nexport type BuiltInProvider = \"chatgpt\" | \"claude\" | \"grok\";\n\nexport interface ProviderSpec {\n id: string;\n label: string;\n /** URL that opens the assistant with `prompt` pre-filled. */\n href(prompt: string): string;\n}\n\nexport const DEFAULT_PROMPT = \"Refer the content at {url}\";\nexport const DEFAULT_ASK_PROMPT = \"Read from {url} so I can ask questions about it.\";\n\nexport const PROVIDERS: Record<BuiltInProvider, ProviderSpec> = {\n chatgpt: { id: \"chatgpt\", label: \"ChatGPT\", href: (q) => `https://chatgpt.com/?hints=search&q=${encodeURIComponent(q)}` },\n claude: { id: \"claude\", label: \"Claude\", href: (q) => `https://claude.ai/new?q=${encodeURIComponent(q)}` },\n grok: { id: \"grok\", label: \"Grok\", href: (q) => `https://grok.com/?q=${encodeURIComponent(q)}` },\n};\n\nexport const DEFAULT_PROVIDERS: BuiltInProvider[] = [\"chatgpt\", \"claude\", \"grok\"];\n\nexport function resolveProvider(p: BuiltInProvider | ProviderSpec): ProviderSpec {\n return typeof p === \"string\" ? PROVIDERS[p] : p;\n}\n\n/**\n * The `.md` twin of a page URL: drops the query and hash and any trailing\n * slash, then appends `.md` (`/` becomes `/index.md`).\n */\nexport function markdownUrl(pageUrl: string): string {\n const url = new URL(pageUrl);\n url.search = \"\";\n url.hash = \"\";\n let path = url.pathname.replace(/\\/+$/, \"\");\n if (!path) path = \"/index\";\n if (!path.endsWith(\".md\")) path += \".md\";\n url.pathname = path;\n return url.href;\n}\n\n/** Fill `{url}` in a prompt template (appends the URL when there is no placeholder). */\nexport function formatPrompt(template: string, url: string): string {\n return template.includes(\"{url}\") ? template.replaceAll(\"{url}\", url) : `${template} ${url}`.trim();\n}\n\n/** URL that opens an assistant with the prompt pre-filled. */\nexport function providerUrl(provider: BuiltInProvider | ProviderSpec, prompt: string): string {\n return resolveProvider(provider).href(prompt);\n}\n","import { useCallback } from \"react\";\nimport { cn } from \"cn\";\nimport { toast } from \"sonner\";\nimport { ChevronDownIcon, CopyIcon, ExternalLinkIcon, FileTextIcon, LinkIcon } from \"lucide-react\";\nimport { copyText } from \"zreader/react\";\nimport { useOptionalReaderChapter } from \"./markdown-alternate\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport {\n DEFAULT_ASK_PROMPT,\n DEFAULT_PROMPT,\n DEFAULT_PROVIDERS,\n formatPrompt,\n markdownUrl,\n resolveProvider,\n type BuiltInProvider,\n type ProviderSpec,\n} from \"./reference\";\n\nexport interface CopyReferenceProps {\n /**\n * Reference the page's `.md` twin (served by `createAgentHandler()` from\n * `zreader/agent`). `false` (default) references the page URL itself, so the\n * button never points at a route your app doesn't serve. Pass a function to\n * map page URLs to Markdown URLs yourself (e.g. for hash routing).\n */\n markdown?: boolean | ((pageUrl: string) => string);\n /** Copied text. `{url}` is replaced with the reference URL. */\n prompt?: string;\n /** Prompt sent to the assistants in the menu. */\n askPrompt?: string;\n /** Assistants offered in the menu, in order. `[]` hides them. */\n providers?: Array<BuiltInProvider | ProviderSpec>;\n /** Button text from the `lg` breakpoint up (icon-only below). */\n label?: string;\n className?: string;\n}\n\n/**\n * \"Copy reference\" split button for the reader's top bar: copies a prompt that\n * points an AI agent at this chapter, with a menu to open it in ChatGPT,\n * Claude or Grok.\n *\n * <Reader … actions={<CopyReference markdown />} />\n */\nexport function CopyReference({\n markdown = false,\n prompt = DEFAULT_PROMPT,\n askPrompt = DEFAULT_ASK_PROMPT,\n providers = DEFAULT_PROVIDERS,\n label = \"Copy reference\",\n className,\n}: CopyReferenceProps) {\n const ctx = useOptionalReaderChapter();\n\n // Built on click (not render), so SSR and hydration never see `window`.\n const referenceUrl = useCallback(() => {\n const page = ctx ? new URL(ctx.href(ctx.chapter.id), window.location.href).href : window.location.href;\n if (!markdown) return page.split(\"#\")[0]!;\n return typeof markdown === \"function\" ? markdown(page) : markdownUrl(page);\n }, [ctx, markdown]);\n\n const copy = async (text: string, what: string) => {\n if (await copyText(text)) toast.success(`${what} copied`, { description: text });\n else toast.error(\"Could not copy\");\n };\n const copyPrompt = () => void copy(formatPrompt(prompt, referenceUrl()), \"Reference\");\n const copyUrl = () => void copy(referenceUrl(), markdown ? \"Markdown URL\" : \"Link\");\n const open = (p: ProviderSpec) => {\n window.open(p.href(formatPrompt(askPrompt, referenceUrl())), \"_blank\", \"noopener,noreferrer\");\n };\n const specs = providers.map(resolveProvider);\n\n return (\n <div className={cn(\"flex items-center\", className)} data-zr-copy-reference=\"\">\n <Tooltip>\n <TooltipTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" aria-label={label} onClick={copyPrompt} className=\"hidden rounded-r-none sm:inline-flex lg:w-auto lg:px-3\">\n <LinkIcon />\n <span className=\"hidden lg:inline\">{label}</span>\n </Button>\n </TooltipTrigger>\n <TooltipContent>Copy a prompt that points an AI agent at this page</TooltipContent>\n </Tooltip>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" aria-label=\"Reference options\" className=\"sm:w-6 sm:rounded-l-none\">\n <LinkIcon className=\"sm:hidden\" />\n <ChevronDownIcon className=\"hidden sm:block\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-60\">\n <DropdownMenuItem onSelect={copyPrompt}>\n <CopyIcon />\n {label}\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={copyUrl}>\n <FileTextIcon />\n {markdown ? \"Copy Markdown URL\" : \"Copy link\"}\n </DropdownMenuItem>\n {specs.length > 0 && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuLabel className=\"text-xs font-normal text-muted-foreground\">Ask about this page</DropdownMenuLabel>\n {specs.map((p) => (\n <DropdownMenuItem key={p.id} onSelect={() => open(p)}>\n <ExternalLinkIcon />\n Open in {p.label}\n </DropdownMenuItem>\n ))}\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n}\n"]}
@@ -221,6 +221,9 @@ function applyReaderConfig(base, setup, root) {
221
221
  const tracing = { ...base.outputFileTracingIncludes ?? {} };
222
222
  const allRoutes = "/**";
223
223
  tracing[allRoutes] = Array.from(/* @__PURE__ */ new Set([...tracing[allRoutes] ?? [], contentGlob]));
224
+ for (const entry of ["middleware", "proxy"]) {
225
+ tracing[entry] = Array.from(/* @__PURE__ */ new Set([...tracing[entry] ?? [], contentGlob]));
226
+ }
224
227
  const externals = Array.from(/* @__PURE__ */ new Set([...base.serverExternalPackages ?? [], ...SERVER_EXTERNALS]));
225
228
  return {
226
229
  ...base,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/defaults.ts","../../src/core/types.ts","../../src/config/index.ts","../../src/config/load.ts","../../src/next/index.ts"],"names":["z","resolve","join","existsSync","dirname","sep","relative"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ReaderClientOptions } from \"./types\";\n\nexport const DEFAULT_CLIENT_OPTIONS: ReaderClientOptions = {\n storagePrefix: \"zr\",\n highlightColors: [\"yellow\", \"green\", \"blue\", \"pink\"],\n defaultHighlightColor: \"yellow\",\n scratchpad: { enabled: true, scopes: [\"chapter\", \"book\", \"global\"] },\n themes: [\"system\", \"light\", \"sepia\", \"dark\"],\n defaultTheme: \"system\",\n defaultSettings: {\n textSize: \"lg\",\n leading: \"relaxed\",\n measure: \"normal\",\n fontFamily: \"serif\",\n highlightMode: false,\n },\n progress: { completeAt: 0.9, saveIntervalMs: 1000 },\n backupReminder: true,\n basePath: \"/books\",\n};\n","/** Shared, framework-agnostic data model for zreader. */\n\nexport type ISODate = string;\n\n/** W3C Web Annotation-style anchor. Offsets are measured over the chapter root's text. */\nexport interface Anchor {\n /** `data-block-id` of the block containing the start of the selection. */\n blockId: string;\n /** Offsets relative to the start block's text (fast path). */\n blockStart: number;\n /** TextPositionSelector over the whole chapter text. */\n start: number;\n end: number;\n /** TextQuoteSelector. */\n exact: string;\n prefix: string;\n suffix: string;\n /** Content hash of the chapter when the anchor was created. */\n contentHash?: string;\n}\n\nexport interface Highlight {\n id: string;\n bookId: string;\n chapterId: string;\n anchor: Anchor;\n color: string;\n note?: string;\n createdAt: ISODate;\n updatedAt: ISODate;\n}\n\nexport interface Bookmark {\n id: string;\n bookId: string;\n chapterId: string;\n /** Absent for whole-chapter bookmarks. */\n blockId?: string;\n label?: string;\n excerpt?: string;\n createdAt: ISODate;\n updatedAt: ISODate;\n}\n\nexport interface ChapterProgress {\n chapterId: string;\n blockId?: string;\n /** 0..1 */\n percent: number;\n completed: boolean;\n updatedAt: ISODate;\n}\n\nexport interface BookProgress {\n bookId: string;\n chapters: Record<string, ChapterProgress>;\n last?: { chapterId: string; blockId?: string; updatedAt: ISODate };\n}\n\nexport type PadScope =\n | { kind: \"global\" }\n | { kind: \"book\"; bookId: string }\n | { kind: \"chapter\"; bookId: string; chapterId: string };\n\nexport type PadScopeKind = PadScope[\"kind\"];\n\nexport interface Pad {\n /** Markdown is the interchange/source format. */\n markdown: string;\n /** Lossless editor JSON cache. Cleared whenever `markdown` is changed outside the editor. */\n json?: unknown;\n updatedAt: ISODate;\n}\n\nexport type ThemeName = \"system\" | \"light\" | \"sepia\" | \"dark\";\nexport type Measure = \"narrow\" | \"normal\" | \"wide\";\nexport type FontFamily = \"serif\" | \"sans\" | \"mono\";\nexport type TextSize = \"sm\" | \"base\" | \"lg\" | \"xl\" | \"2xl\";\nexport type Leading = \"tight\" | \"normal\" | \"relaxed\";\n\nexport const THEMES = [\"system\", \"light\", \"sepia\", \"dark\"] as const satisfies readonly ThemeName[];\nexport const TEXT_SIZES = [\"sm\", \"base\", \"lg\", \"xl\", \"2xl\"] as const satisfies readonly TextSize[];\nexport const LEADINGS = [\"tight\", \"normal\", \"relaxed\"] as const satisfies readonly Leading[];\nexport const MEASURES = [\"narrow\", \"normal\", \"wide\"] as const satisfies readonly Measure[];\nexport const FONT_FAMILIES = [\"serif\", \"sans\", \"mono\"] as const satisfies readonly FontFamily[];\n/** The palette the UI ships styles for (`::highlight(zr-<color>)`). */\nexport const HIGHLIGHT_COLORS = [\"yellow\", \"green\", \"blue\", \"pink\", \"purple\", \"orange\"] as const;\nexport type HighlightColor = (typeof HIGHLIGHT_COLORS)[number];\n\nexport interface ReaderSettings {\n textSize: TextSize;\n leading: Leading;\n measure: Measure;\n fontFamily: FontFamily;\n /** Tap a sentence to highlight it with the default colour. */\n highlightMode: boolean;\n updatedAt: ISODate;\n}\n\n/** Serializable client options (the `reader` half of reader.config.ts). */\nexport interface ReaderClientOptions {\n storagePrefix: string;\n highlightColors: HighlightColor[];\n defaultHighlightColor: HighlightColor;\n scratchpad: { enabled: boolean; scopes: PadScopeKind[] };\n /** Themes offered in the UI. Applied as a class on <html> via next-themes. */\n themes: ThemeName[];\n defaultTheme: ThemeName;\n defaultSettings: Omit<ReaderSettings, \"updatedAt\">;\n progress: { completeAt: number; saveIntervalMs: number };\n /** Show a \"your notes only live in this browser\" reminder. */\n backupReminder: boolean;\n /** Base path used to build links back into the reader (e.g. quote-to-pad). */\n basePath: string;\n}\n\nexport interface TocEntry {\n id: string;\n depth: number;\n text: string;\n}\n\nexport interface ChapterMeta {\n id: string;\n title: string;\n order: number;\n description?: string;\n /** Section the chapter belongs to, e.g. \"Part I · For the Builders\". Groups the contents list. */\n part?: string;\n /** Eyebrow shown above the title, e.g. \"Chapter 5\" or \"Appendix A\". */\n label?: string;\n /** Short marker for lists, e.g. \"5\" or \"A\". */\n number?: string;\n}\n\nexport interface BookMeta {\n id: string;\n title: string;\n subtitle?: string;\n authors: string[];\n description?: string;\n cover?: string;\n chapters: ChapterMeta[];\n}\n","import { z } from \"zod/v4\";\nimport { DEFAULT_CLIENT_OPTIONS } from \"../core/defaults\";\nimport {\n FONT_FAMILIES,\n HIGHLIGHT_COLORS,\n LEADINGS,\n MEASURES,\n TEXT_SIZES,\n THEMES,\n type ReaderClientOptions,\n} from \"../core/types\";\n\nexport { z };\n\n// ---------------------------------------------------------------- types\n\nexport interface GlobLoader {\n kind: \"glob\";\n /** Directory containing one folder per book. Relative to the project root. */\n base: string;\n /** Chapter files, relative to `base`. */\n pattern: string | string[];\n ignore?: string[];\n}\n\n/** Astro-style glob loader: `content/books/<book>/{book.json, 01-intro.md, ...}` */\nexport function glob(opts: { base: string; pattern?: string | string[]; ignore?: string[] }): GlobLoader {\n return { kind: \"glob\", base: opts.base, pattern: opts.pattern ?? \"**/*.{md,mdx,html}\", ignore: opts.ignore };\n}\n\n// unified's PluggableList, kept loose to avoid a type dependency in this entry.\nexport type PluginList = unknown[];\n\nexport interface SanitizeOptions {\n /** Hostnames allowed for <iframe src>. Iframes are stripped unless listed here. */\n allowIframes?: string[];\n /** Extra tags to allow on top of DOMPurify's safe HTML profile. */\n allowTags?: string[];\n /** Extra attributes to allow. */\n allowAttributes?: string[];\n}\n\nexport interface ContentConfig {\n loader: GlobLoader;\n schemas: { book: z.ZodType; chapter: z.ZodType };\n /** Applied to `.md` and `.mdx`. */\n remarkPlugins: PluginList;\n rehypePlugins: PluginList;\n sanitize: SanitizeOptions;\n /** Include chapters with `draft: true`. Defaults to `true` in development. */\n includeDrafts: boolean;\n /** Where compiled chapters are cached. */\n cacheDir: string;\n}\n\ntype DeepPartial<T> = T extends unknown[] ? T : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;\n\nexport interface ReaderUserConfig {\n content?: DeepPartial<Omit<ContentConfig, \"schemas\" | \"loader\">> & {\n loader?: GlobLoader;\n schemas?: Partial<ContentConfig[\"schemas\"]>;\n };\n reader?: DeepPartial<ReaderClientOptions>;\n}\n\nexport interface ResolvedReaderConfig {\n root: string;\n content: ContentConfig;\n reader: ReaderClientOptions;\n}\n\n// ------------------------------------------------------------- defaults\n\nexport const defaultBookSchema = z.looseObject({\n title: z.string(),\n subtitle: z.string().optional(),\n authors: z.array(z.string()).default([]),\n description: z.string().optional(),\n cover: z.string().optional(),\n /** Optional explicit chapter order (chapter slugs). */\n chapters: z.array(z.string()).optional(),\n});\n\nexport const defaultChapterSchema = z.looseObject({\n title: z.string(),\n order: z.number().optional(),\n description: z.string().optional(),\n part: z.string().optional(),\n label: z.string().optional(),\n number: z.union([z.string(), z.number()]).transform(String).optional(),\n draft: z.boolean().default(false),\n});\n\nfunction isDev() {\n return typeof process !== \"undefined\" && process.env?.NODE_ENV === \"development\";\n}\n\nexport function defaultContentConfig(): ContentConfig {\n return {\n loader: glob({ base: \"./content/books\" }),\n schemas: { book: defaultBookSchema, chapter: defaultChapterSchema },\n remarkPlugins: [],\n rehypePlugins: [],\n sanitize: {},\n includeDrafts: isDev(),\n cacheDir:\n (typeof process !== \"undefined\" && process.env?.ZREADER_CACHE_DIR) || \"node_modules/.cache/zreader\",\n };\n}\n\n// ------------------------------------------------------------ define/merge\n\n/** Identity helper that gives `reader.config.ts` full type-checking. */\nexport function defineConfig(config: ReaderUserConfig): ReaderUserConfig {\n return config;\n}\n\nfunction isPlainObject(x: unknown): x is Record<string, unknown> {\n if (typeof x !== \"object\" || x === null || Array.isArray(x)) return false;\n const proto = Object.getPrototypeOf(x);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Deep merge: plain objects merge key-by-key, arrays and class instances\n * (e.g. zod schemas) replace, `undefined` never overrides.\n */\nexport function mergeConfig<T>(base: T, ...overrides: unknown[]): T {\n let out: unknown = base;\n for (const o of overrides) out = mergeTwo(out, o);\n return out as T;\n}\n\nfunction mergeTwo(a: unknown, b: unknown): unknown {\n if (b === undefined) return a;\n if (isPlainObject(a) && isPlainObject(b)) {\n const out: Record<string, unknown> = { ...a };\n for (const [k, v] of Object.entries(b)) out[k] = mergeTwo(a[k], v);\n return out;\n }\n return b;\n}\n\n// --------------------------------------------------------------- validate\n\nconst themeName = z.enum(THEMES);\nconst highlightColor = z.enum(HIGHLIGHT_COLORS);\nconst clientSchema = z\n .object({\n storagePrefix: z.string().regex(/^[a-z0-9_-]+$/i, \"use letters, digits, '-' or '_'\"),\n highlightColors: z.array(highlightColor).min(1, \"must be non-empty\"),\n defaultHighlightColor: highlightColor,\n scratchpad: z.object({\n enabled: z.boolean(),\n scopes: z.array(z.enum([\"global\", \"book\", \"chapter\"])).min(1, \"must be non-empty\"),\n }),\n themes: z.array(themeName).min(1),\n defaultTheme: themeName,\n defaultSettings: z.object({\n textSize: z.enum(TEXT_SIZES),\n leading: z.enum(LEADINGS),\n measure: z.enum(MEASURES),\n fontFamily: z.enum(FONT_FAMILIES),\n highlightMode: z.boolean(),\n }),\n progress: z.object({ completeAt: z.number().gt(0).max(1), saveIntervalMs: z.number().min(100) }),\n backupReminder: z.boolean(),\n basePath: z.string().startsWith(\"/\"),\n })\n .refine((c) => c.highlightColors.includes(c.defaultHighlightColor), {\n message: \"defaultHighlightColor must be one of highlightColors\",\n path: [\"defaultHighlightColor\"],\n })\n .refine((c) => c.themes.includes(c.defaultTheme), {\n message: \"defaultTheme must be one of themes\",\n path: [\"defaultTheme\"],\n });\n\nfunction isSchemaLike(v: unknown): boolean {\n return typeof v === \"object\" && v !== null && typeof (v as { safeParse?: unknown }).safeParse === \"function\";\n}\n\nconst contentSchema = z.object({\n loader: z.object({\n kind: z.literal(\"glob\"),\n base: z.string().min(1),\n pattern: z.union([z.string(), z.array(z.string()).min(1)]),\n ignore: z.array(z.string()).optional(),\n }),\n schemas: z.object({\n book: z.custom<z.ZodType>(isSchemaLike, \"must be a zod (or safeParse-compatible) schema\"),\n chapter: z.custom<z.ZodType>(isSchemaLike, \"must be a zod (or safeParse-compatible) schema\"),\n }),\n remarkPlugins: z.array(z.any()),\n rehypePlugins: z.array(z.any()),\n sanitize: z.object({\n allowIframes: z.array(z.string()).optional(),\n allowTags: z.array(z.string()).optional(),\n allowAttributes: z.array(z.string()).optional(),\n }),\n includeDrafts: z.boolean(),\n cacheDir: z.string(),\n});\n\nexport class ReaderConfigError extends Error {\n constructor(\n message: string,\n public readonly issues: { path: string; message: string }[] = [],\n ) {\n super(message);\n this.name = \"ReaderConfigError\";\n }\n}\n\nfunction formatIssues(source: string, section: string, error: z.ZodError) {\n const issues = error.issues.map((i) => ({ path: [section, ...i.path.map(String)].join(\".\"), message: i.message }));\n return new ReaderConfigError(\n `Invalid reader config${source ? ` (${source})` : \"\"}:\\n` + issues.map((i) => ` - ${i.path}: ${i.message}`).join(\"\\n\"),\n issues,\n );\n}\n\n/**\n * Resolve the final config: package defaults ← reader.config.ts ← inline overrides.\n * Throws `ReaderConfigError` with readable messages.\n */\nexport function resolveConfig(\n user?: ReaderUserConfig | null,\n inline?: ReaderUserConfig | null,\n opts: { root?: string; source?: string } = {},\n): ResolvedReaderConfig {\n const root = opts.root ?? (typeof process !== \"undefined\" && process.cwd ? process.cwd() : \"/\");\n const reader = mergeConfig<ReaderClientOptions>(\n structuredClone(DEFAULT_CLIENT_OPTIONS),\n user?.reader,\n inline?.reader,\n );\n const content = mergeConfig<ContentConfig>(defaultContentConfig(), user?.content, inline?.content);\n\n const r = clientSchema.safeParse(reader);\n if (!r.success) throw formatIssues(opts.source ?? \"\", \"reader\", r.error);\n const c = contentSchema.safeParse(content);\n if (!c.success) throw formatIssues(opts.source ?? \"\", \"content\", c.error);\n\n return { root, content, reader };\n}\n\n/** Only the serializable, client-safe half. */\nexport function clientOptions(config: ResolvedReaderConfig): ReaderClientOptions {\n return config.reader;\n}\n\nexport const CONFIG_FILE_NAMES = [\"reader.config.ts\", \"reader.config.mts\", \"reader.config.mjs\", \"reader.config.js\"];\n","/** Node-only helpers for locating and loading reader.config.* (via jiti). */\nimport { existsSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { CONFIG_FILE_NAMES, type ReaderUserConfig } from \"./index\";\n\n/** Search `from` and its parents (up to the filesystem root or a `.git` dir) for reader.config.*. */\nexport function findConfigFile(from: string = process.cwd()): string | null {\n let dir = resolve(from);\n for (;;) {\n for (const name of CONFIG_FILE_NAMES) {\n const p = join(dir, name);\n if (existsSync(p)) return p;\n }\n if (existsSync(join(dir, \"package.json\")) || existsSync(join(dir, \".git\"))) return null;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport async function loadConfigFile(file: string): Promise<ReaderUserConfig> {\n const { createJiti } = await import(\"jiti\");\n const jiti = createJiti(file, { interopDefault: true, moduleCache: false });\n const mod = (await jiti.import(file)) as { default?: ReaderUserConfig } | ReaderUserConfig;\n const cfg = (mod && typeof mod === \"object\" && \"default\" in mod ? mod.default : mod) as ReaderUserConfig | undefined;\n return cfg ?? {};\n}\n","/**\n * Next.js integration.\n *\n * // next.config.ts\n * import { withReader } from \"zreader/next\";\n * export default withReader({ reactStrictMode: true });\n */\nimport { join, relative, resolve, sep } from \"node:path\";\nimport type { NextConfig } from \"next\";\nimport { resolveConfig, type ResolvedReaderConfig, type ReaderUserConfig } from \"../config/index\";\nimport { findConfigFile, loadConfigFile } from \"../config/load\";\n\nexport const USER_CONFIG_SPECIFIER = \"zreader/user-config\";\nexport const EMPTY_CONFIG_SPECIFIER = \"zreader/empty-config\";\n\n/** Packages that must stay external on the server (dynamic requires / native-ish). */\nexport const SERVER_EXTERNALS = [\"jsdom\", \"isomorphic-dompurify\"];\n\nexport interface WithReaderOptions {\n /** Path to the config file. Defaults to discovering reader.config.{ts,mts,mjs,js}. */\n configPath?: string;\n /** Project root. Defaults to process.cwd() (the directory containing next.config). */\n root?: string;\n /** Inline overrides merged on top of reader.config.ts. */\n config?: ReaderUserConfig;\n /** Log the resolved config file path at startup. */\n verbose?: boolean;\n}\n\ntype NextConfigFn = (phase: string, ctx: { defaultConfig: NextConfig }) => NextConfig | Promise<NextConfig>;\nexport type NextConfigInput = NextConfig | NextConfigFn;\n\ntype WebpackFn = NonNullable<NextConfig[\"webpack\"]>;\n\nfunction toPosix(p: string) {\n return p.split(sep).join(\"/\");\n}\n\n/** Alias target relative to the project root, as Turbopack expects (`./reader.config.ts`). */\nfunction relativeImport(root: string, file: string) {\n const rel = toPosix(relative(root, file));\n return rel.startsWith(\".\") ? rel : `./${rel}`;\n}\n\nexport interface ReaderNextSetup {\n configFile: string | null;\n resolved: ResolvedReaderConfig;\n}\n\n/** Pure transform (exported for testing): apply reader wiring to a resolved Next config. */\nexport function applyReaderConfig(base: NextConfig, setup: ReaderNextSetup, root: string): NextConfig {\n const { configFile, resolved } = setup;\n const turboTarget = configFile ? relativeImport(root, configFile) : EMPTY_CONFIG_SPECIFIER;\n const webpackTarget = configFile ?? EMPTY_CONFIG_SPECIFIER;\n\n const prevWebpack = base.webpack as WebpackFn | undefined;\n const webpack: WebpackFn = (config, ctx) => {\n const cfg = (prevWebpack ? prevWebpack(config, ctx) : config) as { resolve?: { alias?: Record<string, unknown> } };\n cfg.resolve ??= {};\n const alias = (cfg.resolve.alias ??= {}) as Record<string, unknown>;\n alias[`${USER_CONFIG_SPECIFIER}$`] = webpackTarget;\n return cfg;\n };\n\n const contentGlob = toPosix(join(resolved.content.loader.base, \"**/*\"));\n const tracing = { ...(base.outputFileTracingIncludes ?? {}) };\n const allRoutes = \"/**\";\n tracing[allRoutes] = Array.from(new Set([...(tracing[allRoutes] ?? []), contentGlob]));\n\n const externals = Array.from(new Set([...(base.serverExternalPackages ?? []), ...SERVER_EXTERNALS]));\n\n return {\n ...base,\n serverExternalPackages: externals,\n outputFileTracingIncludes: tracing,\n turbopack: {\n ...base.turbopack,\n resolveAlias: {\n ...base.turbopack?.resolveAlias,\n [USER_CONFIG_SPECIFIER]: turboTarget,\n },\n },\n webpack,\n };\n}\n\nexport async function setupReader(options: WithReaderOptions = {}): Promise<ReaderNextSetup> {\n const root = resolve(options.root ?? process.cwd());\n const configFile = options.configPath ? resolve(root, options.configPath) : findConfigFile(root);\n const user = configFile ? await loadConfigFile(configFile) : {};\n // Validate eagerly so `next dev` / `next build` fail fast with a readable error.\n const resolved = resolveConfig(user, options.config, {\n root,\n source: configFile ? relative(root, configFile) : \"defaults\",\n });\n return { configFile, resolved };\n}\n\n/**\n * Wire zreader into Next.js:\n * - validates reader.config.* at startup;\n * - aliases `zreader/user-config` to it (Turbopack + webpack), so\n * `zreader/virtual-config` is the resolved config everywhere on the server;\n * - keeps jsdom/DOMPurify external on the server;\n * - traces the content directory into the server output (Vercel);\n * - points the compiled-chapter cache at `.next/cache/zreader`.\n */\nexport function withReader(nextConfig: NextConfigInput = {}, options: WithReaderOptions = {}): NextConfigFn {\n return async (phase, ctx) => {\n const root = resolve(options.root ?? process.cwd());\n process.env.ZREADER_CACHE_DIR ??= join(root, \".next\", \"cache\", \"zreader\");\n const base = typeof nextConfig === \"function\" ? await nextConfig(phase, ctx) : nextConfig;\n const setup = await setupReader({ ...options, root });\n if (options.verbose) {\n console.log(`[zreader] config: ${setup.configFile ?? \"(defaults)\"}`);\n }\n return applyReaderConfig(base, setup, root);\n };\n}\n\nexport { resolveConfig, findConfigFile, loadConfigFile };\nexport type { ResolvedReaderConfig, ReaderUserConfig };\n"]}
1
+ {"version":3,"sources":["../../src/core/defaults.ts","../../src/core/types.ts","../../src/config/index.ts","../../src/config/load.ts","../../src/next/index.ts"],"names":["z","resolve","join","existsSync","dirname","sep","relative"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ReaderClientOptions } from \"./types\";\n\nexport const DEFAULT_CLIENT_OPTIONS: ReaderClientOptions = {\n storagePrefix: \"zr\",\n highlightColors: [\"yellow\", \"green\", \"blue\", \"pink\"],\n defaultHighlightColor: \"yellow\",\n scratchpad: { enabled: true, scopes: [\"chapter\", \"book\", \"global\"] },\n themes: [\"system\", \"light\", \"sepia\", \"dark\"],\n defaultTheme: \"system\",\n defaultSettings: {\n textSize: \"lg\",\n leading: \"relaxed\",\n measure: \"normal\",\n fontFamily: \"serif\",\n highlightMode: false,\n },\n progress: { completeAt: 0.9, saveIntervalMs: 1000 },\n backupReminder: true,\n basePath: \"/books\",\n};\n","/** Shared, framework-agnostic data model for zreader. */\n\nexport type ISODate = string;\n\n/** W3C Web Annotation-style anchor. Offsets are measured over the chapter root's text. */\nexport interface Anchor {\n /** `data-block-id` of the block containing the start of the selection. */\n blockId: string;\n /** Offsets relative to the start block's text (fast path). */\n blockStart: number;\n /** TextPositionSelector over the whole chapter text. */\n start: number;\n end: number;\n /** TextQuoteSelector. */\n exact: string;\n prefix: string;\n suffix: string;\n /** Content hash of the chapter when the anchor was created. */\n contentHash?: string;\n}\n\nexport interface Highlight {\n id: string;\n bookId: string;\n chapterId: string;\n anchor: Anchor;\n color: string;\n note?: string;\n createdAt: ISODate;\n updatedAt: ISODate;\n}\n\nexport interface Bookmark {\n id: string;\n bookId: string;\n chapterId: string;\n /** Absent for whole-chapter bookmarks. */\n blockId?: string;\n label?: string;\n excerpt?: string;\n createdAt: ISODate;\n updatedAt: ISODate;\n}\n\nexport interface ChapterProgress {\n chapterId: string;\n blockId?: string;\n /** 0..1 */\n percent: number;\n completed: boolean;\n updatedAt: ISODate;\n}\n\nexport interface BookProgress {\n bookId: string;\n chapters: Record<string, ChapterProgress>;\n last?: { chapterId: string; blockId?: string; updatedAt: ISODate };\n}\n\nexport type PadScope =\n | { kind: \"global\" }\n | { kind: \"book\"; bookId: string }\n | { kind: \"chapter\"; bookId: string; chapterId: string };\n\nexport type PadScopeKind = PadScope[\"kind\"];\n\nexport interface Pad {\n /** Markdown is the interchange/source format. */\n markdown: string;\n /** Lossless editor JSON cache. Cleared whenever `markdown` is changed outside the editor. */\n json?: unknown;\n updatedAt: ISODate;\n}\n\nexport type ThemeName = \"system\" | \"light\" | \"sepia\" | \"dark\";\nexport type Measure = \"narrow\" | \"normal\" | \"wide\";\nexport type FontFamily = \"serif\" | \"sans\" | \"mono\";\nexport type TextSize = \"sm\" | \"base\" | \"lg\" | \"xl\" | \"2xl\";\nexport type Leading = \"tight\" | \"normal\" | \"relaxed\";\n\nexport const THEMES = [\"system\", \"light\", \"sepia\", \"dark\"] as const satisfies readonly ThemeName[];\nexport const TEXT_SIZES = [\"sm\", \"base\", \"lg\", \"xl\", \"2xl\"] as const satisfies readonly TextSize[];\nexport const LEADINGS = [\"tight\", \"normal\", \"relaxed\"] as const satisfies readonly Leading[];\nexport const MEASURES = [\"narrow\", \"normal\", \"wide\"] as const satisfies readonly Measure[];\nexport const FONT_FAMILIES = [\"serif\", \"sans\", \"mono\"] as const satisfies readonly FontFamily[];\n/** The palette the UI ships styles for (`::highlight(zr-<color>)`). */\nexport const HIGHLIGHT_COLORS = [\"yellow\", \"green\", \"blue\", \"pink\", \"purple\", \"orange\"] as const;\nexport type HighlightColor = (typeof HIGHLIGHT_COLORS)[number];\n\nexport interface ReaderSettings {\n textSize: TextSize;\n leading: Leading;\n measure: Measure;\n fontFamily: FontFamily;\n /** Tap a sentence to highlight it with the default colour. */\n highlightMode: boolean;\n updatedAt: ISODate;\n}\n\n/** Serializable client options (the `reader` half of reader.config.ts). */\nexport interface ReaderClientOptions {\n storagePrefix: string;\n highlightColors: HighlightColor[];\n defaultHighlightColor: HighlightColor;\n scratchpad: { enabled: boolean; scopes: PadScopeKind[] };\n /** Themes offered in the UI. Applied as a class on <html> via next-themes. */\n themes: ThemeName[];\n defaultTheme: ThemeName;\n defaultSettings: Omit<ReaderSettings, \"updatedAt\">;\n progress: { completeAt: number; saveIntervalMs: number };\n /** Show a \"your notes only live in this browser\" reminder. */\n backupReminder: boolean;\n /** Base path used to build links back into the reader (e.g. quote-to-pad). */\n basePath: string;\n}\n\nexport interface TocEntry {\n id: string;\n depth: number;\n text: string;\n}\n\nexport interface ChapterMeta {\n id: string;\n title: string;\n order: number;\n description?: string;\n /** Section the chapter belongs to, e.g. \"Part I · For the Builders\". Groups the contents list. */\n part?: string;\n /** Eyebrow shown above the title, e.g. \"Chapter 5\" or \"Appendix A\". */\n label?: string;\n /** Short marker for lists, e.g. \"5\" or \"A\". */\n number?: string;\n}\n\nexport interface BookMeta {\n id: string;\n title: string;\n subtitle?: string;\n authors: string[];\n description?: string;\n cover?: string;\n chapters: ChapterMeta[];\n}\n","import { z } from \"zod/v4\";\nimport { DEFAULT_CLIENT_OPTIONS } from \"../core/defaults\";\nimport {\n FONT_FAMILIES,\n HIGHLIGHT_COLORS,\n LEADINGS,\n MEASURES,\n TEXT_SIZES,\n THEMES,\n type ReaderClientOptions,\n} from \"../core/types\";\n\nexport { z };\n\n// ---------------------------------------------------------------- types\n\nexport interface GlobLoader {\n kind: \"glob\";\n /** Directory containing one folder per book. Relative to the project root. */\n base: string;\n /** Chapter files, relative to `base`. */\n pattern: string | string[];\n ignore?: string[];\n}\n\n/** Astro-style glob loader: `content/books/<book>/{book.json, 01-intro.md, ...}` */\nexport function glob(opts: { base: string; pattern?: string | string[]; ignore?: string[] }): GlobLoader {\n return { kind: \"glob\", base: opts.base, pattern: opts.pattern ?? \"**/*.{md,mdx,html}\", ignore: opts.ignore };\n}\n\n// unified's PluggableList, kept loose to avoid a type dependency in this entry.\nexport type PluginList = unknown[];\n\nexport interface SanitizeOptions {\n /** Hostnames allowed for <iframe src>. Iframes are stripped unless listed here. */\n allowIframes?: string[];\n /** Extra tags to allow on top of DOMPurify's safe HTML profile. */\n allowTags?: string[];\n /** Extra attributes to allow. */\n allowAttributes?: string[];\n}\n\nexport interface ContentConfig {\n loader: GlobLoader;\n schemas: { book: z.ZodType; chapter: z.ZodType };\n /** Applied to `.md` and `.mdx`. */\n remarkPlugins: PluginList;\n rehypePlugins: PluginList;\n sanitize: SanitizeOptions;\n /** Include chapters with `draft: true`. Defaults to `true` in development. */\n includeDrafts: boolean;\n /** Where compiled chapters are cached. */\n cacheDir: string;\n}\n\ntype DeepPartial<T> = T extends unknown[] ? T : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;\n\nexport interface ReaderUserConfig {\n content?: DeepPartial<Omit<ContentConfig, \"schemas\" | \"loader\">> & {\n loader?: GlobLoader;\n schemas?: Partial<ContentConfig[\"schemas\"]>;\n };\n reader?: DeepPartial<ReaderClientOptions>;\n}\n\nexport interface ResolvedReaderConfig {\n root: string;\n content: ContentConfig;\n reader: ReaderClientOptions;\n}\n\n// ------------------------------------------------------------- defaults\n\nexport const defaultBookSchema = z.looseObject({\n title: z.string(),\n subtitle: z.string().optional(),\n authors: z.array(z.string()).default([]),\n description: z.string().optional(),\n cover: z.string().optional(),\n /** Optional explicit chapter order (chapter slugs). */\n chapters: z.array(z.string()).optional(),\n});\n\nexport const defaultChapterSchema = z.looseObject({\n title: z.string(),\n order: z.number().optional(),\n description: z.string().optional(),\n part: z.string().optional(),\n label: z.string().optional(),\n number: z.union([z.string(), z.number()]).transform(String).optional(),\n draft: z.boolean().default(false),\n});\n\nfunction isDev() {\n return typeof process !== \"undefined\" && process.env?.NODE_ENV === \"development\";\n}\n\nexport function defaultContentConfig(): ContentConfig {\n return {\n loader: glob({ base: \"./content/books\" }),\n schemas: { book: defaultBookSchema, chapter: defaultChapterSchema },\n remarkPlugins: [],\n rehypePlugins: [],\n sanitize: {},\n includeDrafts: isDev(),\n cacheDir:\n (typeof process !== \"undefined\" && process.env?.ZREADER_CACHE_DIR) || \"node_modules/.cache/zreader\",\n };\n}\n\n// ------------------------------------------------------------ define/merge\n\n/** Identity helper that gives `reader.config.ts` full type-checking. */\nexport function defineConfig(config: ReaderUserConfig): ReaderUserConfig {\n return config;\n}\n\nfunction isPlainObject(x: unknown): x is Record<string, unknown> {\n if (typeof x !== \"object\" || x === null || Array.isArray(x)) return false;\n const proto = Object.getPrototypeOf(x);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Deep merge: plain objects merge key-by-key, arrays and class instances\n * (e.g. zod schemas) replace, `undefined` never overrides.\n */\nexport function mergeConfig<T>(base: T, ...overrides: unknown[]): T {\n let out: unknown = base;\n for (const o of overrides) out = mergeTwo(out, o);\n return out as T;\n}\n\nfunction mergeTwo(a: unknown, b: unknown): unknown {\n if (b === undefined) return a;\n if (isPlainObject(a) && isPlainObject(b)) {\n const out: Record<string, unknown> = { ...a };\n for (const [k, v] of Object.entries(b)) out[k] = mergeTwo(a[k], v);\n return out;\n }\n return b;\n}\n\n// --------------------------------------------------------------- validate\n\nconst themeName = z.enum(THEMES);\nconst highlightColor = z.enum(HIGHLIGHT_COLORS);\nconst clientSchema = z\n .object({\n storagePrefix: z.string().regex(/^[a-z0-9_-]+$/i, \"use letters, digits, '-' or '_'\"),\n highlightColors: z.array(highlightColor).min(1, \"must be non-empty\"),\n defaultHighlightColor: highlightColor,\n scratchpad: z.object({\n enabled: z.boolean(),\n scopes: z.array(z.enum([\"global\", \"book\", \"chapter\"])).min(1, \"must be non-empty\"),\n }),\n themes: z.array(themeName).min(1),\n defaultTheme: themeName,\n defaultSettings: z.object({\n textSize: z.enum(TEXT_SIZES),\n leading: z.enum(LEADINGS),\n measure: z.enum(MEASURES),\n fontFamily: z.enum(FONT_FAMILIES),\n highlightMode: z.boolean(),\n }),\n progress: z.object({ completeAt: z.number().gt(0).max(1), saveIntervalMs: z.number().min(100) }),\n backupReminder: z.boolean(),\n basePath: z.string().startsWith(\"/\"),\n })\n .refine((c) => c.highlightColors.includes(c.defaultHighlightColor), {\n message: \"defaultHighlightColor must be one of highlightColors\",\n path: [\"defaultHighlightColor\"],\n })\n .refine((c) => c.themes.includes(c.defaultTheme), {\n message: \"defaultTheme must be one of themes\",\n path: [\"defaultTheme\"],\n });\n\nfunction isSchemaLike(v: unknown): boolean {\n return typeof v === \"object\" && v !== null && typeof (v as { safeParse?: unknown }).safeParse === \"function\";\n}\n\nconst contentSchema = z.object({\n loader: z.object({\n kind: z.literal(\"glob\"),\n base: z.string().min(1),\n pattern: z.union([z.string(), z.array(z.string()).min(1)]),\n ignore: z.array(z.string()).optional(),\n }),\n schemas: z.object({\n book: z.custom<z.ZodType>(isSchemaLike, \"must be a zod (or safeParse-compatible) schema\"),\n chapter: z.custom<z.ZodType>(isSchemaLike, \"must be a zod (or safeParse-compatible) schema\"),\n }),\n remarkPlugins: z.array(z.any()),\n rehypePlugins: z.array(z.any()),\n sanitize: z.object({\n allowIframes: z.array(z.string()).optional(),\n allowTags: z.array(z.string()).optional(),\n allowAttributes: z.array(z.string()).optional(),\n }),\n includeDrafts: z.boolean(),\n cacheDir: z.string(),\n});\n\nexport class ReaderConfigError extends Error {\n constructor(\n message: string,\n public readonly issues: { path: string; message: string }[] = [],\n ) {\n super(message);\n this.name = \"ReaderConfigError\";\n }\n}\n\nfunction formatIssues(source: string, section: string, error: z.ZodError) {\n const issues = error.issues.map((i) => ({ path: [section, ...i.path.map(String)].join(\".\"), message: i.message }));\n return new ReaderConfigError(\n `Invalid reader config${source ? ` (${source})` : \"\"}:\\n` + issues.map((i) => ` - ${i.path}: ${i.message}`).join(\"\\n\"),\n issues,\n );\n}\n\n/**\n * Resolve the final config: package defaults ← reader.config.ts ← inline overrides.\n * Throws `ReaderConfigError` with readable messages.\n */\nexport function resolveConfig(\n user?: ReaderUserConfig | null,\n inline?: ReaderUserConfig | null,\n opts: { root?: string; source?: string } = {},\n): ResolvedReaderConfig {\n const root = opts.root ?? (typeof process !== \"undefined\" && process.cwd ? process.cwd() : \"/\");\n const reader = mergeConfig<ReaderClientOptions>(\n structuredClone(DEFAULT_CLIENT_OPTIONS),\n user?.reader,\n inline?.reader,\n );\n const content = mergeConfig<ContentConfig>(defaultContentConfig(), user?.content, inline?.content);\n\n const r = clientSchema.safeParse(reader);\n if (!r.success) throw formatIssues(opts.source ?? \"\", \"reader\", r.error);\n const c = contentSchema.safeParse(content);\n if (!c.success) throw formatIssues(opts.source ?? \"\", \"content\", c.error);\n\n return { root, content, reader };\n}\n\n/** Only the serializable, client-safe half. */\nexport function clientOptions(config: ResolvedReaderConfig): ReaderClientOptions {\n return config.reader;\n}\n\nexport const CONFIG_FILE_NAMES = [\"reader.config.ts\", \"reader.config.mts\", \"reader.config.mjs\", \"reader.config.js\"];\n","/** Node-only helpers for locating and loading reader.config.* (via jiti). */\nimport { existsSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { CONFIG_FILE_NAMES, type ReaderUserConfig } from \"./index\";\n\n/** Search `from` and its parents (up to the filesystem root or a `.git` dir) for reader.config.*. */\nexport function findConfigFile(from: string = process.cwd()): string | null {\n let dir = resolve(from);\n for (;;) {\n for (const name of CONFIG_FILE_NAMES) {\n const p = join(dir, name);\n if (existsSync(p)) return p;\n }\n if (existsSync(join(dir, \"package.json\")) || existsSync(join(dir, \".git\"))) return null;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport async function loadConfigFile(file: string): Promise<ReaderUserConfig> {\n const { createJiti } = await import(\"jiti\");\n const jiti = createJiti(file, { interopDefault: true, moduleCache: false });\n const mod = (await jiti.import(file)) as { default?: ReaderUserConfig } | ReaderUserConfig;\n const cfg = (mod && typeof mod === \"object\" && \"default\" in mod ? mod.default : mod) as ReaderUserConfig | undefined;\n return cfg ?? {};\n}\n","/**\n * Next.js integration.\n *\n * // next.config.ts\n * import { withReader } from \"zreader/next\";\n * export default withReader({ reactStrictMode: true });\n */\nimport { join, relative, resolve, sep } from \"node:path\";\nimport type { NextConfig } from \"next\";\nimport { resolveConfig, type ResolvedReaderConfig, type ReaderUserConfig } from \"../config/index\";\nimport { findConfigFile, loadConfigFile } from \"../config/load\";\n\nexport const USER_CONFIG_SPECIFIER = \"zreader/user-config\";\nexport const EMPTY_CONFIG_SPECIFIER = \"zreader/empty-config\";\n\n/** Packages that must stay external on the server (dynamic requires / native-ish). */\nexport const SERVER_EXTERNALS = [\"jsdom\", \"isomorphic-dompurify\"];\n\nexport interface WithReaderOptions {\n /** Path to the config file. Defaults to discovering reader.config.{ts,mts,mjs,js}. */\n configPath?: string;\n /** Project root. Defaults to process.cwd() (the directory containing next.config). */\n root?: string;\n /** Inline overrides merged on top of reader.config.ts. */\n config?: ReaderUserConfig;\n /** Log the resolved config file path at startup. */\n verbose?: boolean;\n}\n\ntype NextConfigFn = (phase: string, ctx: { defaultConfig: NextConfig }) => NextConfig | Promise<NextConfig>;\nexport type NextConfigInput = NextConfig | NextConfigFn;\n\ntype WebpackFn = NonNullable<NextConfig[\"webpack\"]>;\n\nfunction toPosix(p: string) {\n return p.split(sep).join(\"/\");\n}\n\n/** Alias target relative to the project root, as Turbopack expects (`./reader.config.ts`). */\nfunction relativeImport(root: string, file: string) {\n const rel = toPosix(relative(root, file));\n return rel.startsWith(\".\") ? rel : `./${rel}`;\n}\n\nexport interface ReaderNextSetup {\n configFile: string | null;\n resolved: ResolvedReaderConfig;\n}\n\n/** Pure transform (exported for testing): apply reader wiring to a resolved Next config. */\nexport function applyReaderConfig(base: NextConfig, setup: ReaderNextSetup, root: string): NextConfig {\n const { configFile, resolved } = setup;\n const turboTarget = configFile ? relativeImport(root, configFile) : EMPTY_CONFIG_SPECIFIER;\n const webpackTarget = configFile ?? EMPTY_CONFIG_SPECIFIER;\n\n const prevWebpack = base.webpack as WebpackFn | undefined;\n const webpack: WebpackFn = (config, ctx) => {\n const cfg = (prevWebpack ? prevWebpack(config, ctx) : config) as { resolve?: { alias?: Record<string, unknown> } };\n cfg.resolve ??= {};\n const alias = (cfg.resolve.alias ??= {}) as Record<string, unknown>;\n alias[`${USER_CONFIG_SPECIFIER}$`] = webpackTarget;\n return cfg;\n };\n\n const contentGlob = toPosix(join(resolved.content.loader.base, \"**/*\"));\n const tracing = { ...(base.outputFileTracingIncludes ?? {}) };\n const allRoutes = \"/**\";\n tracing[allRoutes] = Array.from(new Set([...(tracing[allRoutes] ?? []), contentGlob]));\n // The proxy/middleware entry is named \"proxy\" or \"middleware\" (no leading\n // slash, so \"/**\" misses it); zreader/agent reads chapters from the proxy.\n for (const entry of [\"middleware\", \"proxy\"]) {\n tracing[entry] = Array.from(new Set([...(tracing[entry] ?? []), contentGlob]));\n }\n\n const externals = Array.from(new Set([...(base.serverExternalPackages ?? []), ...SERVER_EXTERNALS]));\n\n return {\n ...base,\n serverExternalPackages: externals,\n outputFileTracingIncludes: tracing,\n turbopack: {\n ...base.turbopack,\n resolveAlias: {\n ...base.turbopack?.resolveAlias,\n [USER_CONFIG_SPECIFIER]: turboTarget,\n },\n },\n webpack,\n };\n}\n\nexport async function setupReader(options: WithReaderOptions = {}): Promise<ReaderNextSetup> {\n const root = resolve(options.root ?? process.cwd());\n const configFile = options.configPath ? resolve(root, options.configPath) : findConfigFile(root);\n const user = configFile ? await loadConfigFile(configFile) : {};\n // Validate eagerly so `next dev` / `next build` fail fast with a readable error.\n const resolved = resolveConfig(user, options.config, {\n root,\n source: configFile ? relative(root, configFile) : \"defaults\",\n });\n return { configFile, resolved };\n}\n\n/**\n * Wire zreader into Next.js:\n * - validates reader.config.* at startup;\n * - aliases `zreader/user-config` to it (Turbopack + webpack), so\n * `zreader/virtual-config` is the resolved config everywhere on the server;\n * - keeps jsdom/DOMPurify external on the server;\n * - traces the content directory into the server output (Vercel);\n * - points the compiled-chapter cache at `.next/cache/zreader`.\n */\nexport function withReader(nextConfig: NextConfigInput = {}, options: WithReaderOptions = {}): NextConfigFn {\n return async (phase, ctx) => {\n const root = resolve(options.root ?? process.cwd());\n process.env.ZREADER_CACHE_DIR ??= join(root, \".next\", \"cache\", \"zreader\");\n const base = typeof nextConfig === \"function\" ? await nextConfig(phase, ctx) : nextConfig;\n const setup = await setupReader({ ...options, root });\n if (options.verbose) {\n console.log(`[zreader] config: ${setup.configFile ?? \"(defaults)\"}`);\n }\n return applyReaderConfig(base, setup, root);\n };\n}\n\nexport { resolveConfig, findConfigFile, loadConfigFile };\nexport type { ResolvedReaderConfig, ReaderUserConfig };\n"]}
@@ -219,6 +219,9 @@ function applyReaderConfig(base, setup, root) {
219
219
  const tracing = { ...base.outputFileTracingIncludes ?? {} };
220
220
  const allRoutes = "/**";
221
221
  tracing[allRoutes] = Array.from(/* @__PURE__ */ new Set([...tracing[allRoutes] ?? [], contentGlob]));
222
+ for (const entry of ["middleware", "proxy"]) {
223
+ tracing[entry] = Array.from(/* @__PURE__ */ new Set([...tracing[entry] ?? [], contentGlob]));
224
+ }
222
225
  const externals = Array.from(/* @__PURE__ */ new Set([...base.serverExternalPackages ?? [], ...SERVER_EXTERNALS]));
223
226
  return {
224
227
  ...base,