zreader 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +467 -2
- package/dist/agent/index.d.ts +90 -0
- package/dist/agent/index.js +293 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/react/index.d.ts +65 -0
- package/dist/agent/react/index.js +286 -0
- package/dist/agent/react/index.js.map +1 -0
- package/dist/blocks-DwR7l7pz.d.ts +20 -0
- package/dist/chunk-3WQBK6DE.js +47 -0
- package/dist/chunk-3WQBK6DE.js.map +1 -0
- package/dist/chunk-DYQNGV46.js +31 -0
- package/dist/chunk-DYQNGV46.js.map +1 -0
- package/dist/chunk-ISI35TCM.js +62 -0
- package/dist/chunk-ISI35TCM.js.map +1 -0
- package/dist/chunk-RNSAFDZ5.js +105 -0
- package/dist/chunk-RNSAFDZ5.js.map +1 -0
- package/dist/config/index.cjs +196 -0
- package/dist/config/index.cjs.map +1 -0
- package/dist/config/index.d.cts +159 -0
- package/dist/config/index.d.ts +159 -0
- package/dist/config/index.js +182 -0
- package/dist/config/index.js.map +1 -0
- package/dist/content/index.d.ts +143 -0
- package/dist/content/index.js +379 -0
- package/dist/content/index.js.map +1 -0
- package/dist/core/index.d.ts +262 -0
- package/dist/core/index.js +935 -0
- package/dist/core/index.js.map +1 -0
- package/dist/editor/index.d.ts +19 -0
- package/dist/editor/index.js +277 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/index-Bp5oIiZd.d.ts +61 -0
- package/dist/next/client/index.d.ts +8 -0
- package/dist/next/client/index.js +13 -0
- package/dist/next/client/index.js.map +1 -0
- package/dist/next/index.cjs +275 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +47 -0
- package/dist/next/index.d.ts +47 -0
- package/dist/next/index.js +265 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/server/index.d.ts +81 -0
- package/dist/next/server/index.js +51 -0
- package/dist/next/server/index.js.map +1 -0
- package/dist/react/index.d.ts +392 -0
- package/dist/react/index.js +2798 -0
- package/dist/react/index.js.map +1 -0
- package/dist/reader.css +116 -0
- package/dist/rehype/index.d.ts +27 -0
- package/dist/rehype/index.js +4 -0
- package/dist/rehype/index.js.map +1 -0
- package/dist/stubs/empty-config.d.ts +9 -0
- package/dist/stubs/empty-config.js +7 -0
- package/dist/stubs/empty-config.js.map +1 -0
- package/dist/stubs/server-only.d.ts +2 -0
- package/dist/stubs/server-only.js +6 -0
- package/dist/stubs/server-only.js.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/tailwind.css +42 -0
- package/dist/types-xkdpkPxA.d.ts +147 -0
- package/dist/user-config/index.d.ts +17 -0
- package/dist/user-config/index.js +35 -0
- package/dist/user-config/index.js.map +1 -0
- package/dist/virtual-config/index.d.ts +6 -0
- package/dist/virtual-config/index.js +10 -0
- package/dist/virtual-config/index.js.map +1 -0
- package/package.json +237 -4
|
@@ -0,0 +1,2798 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React13 from 'react';
|
|
3
|
+
import { forwardRef, createContext, lazy, useMemo, useState, useEffect, useContext, useSyncExternalStore, useRef, Suspense, useId, useCallback, Component } from 'react';
|
|
4
|
+
import { ThemeProvider, useTheme } from 'next-themes';
|
|
5
|
+
import { DEFAULT_CLIENT_OPTIONS, createReaderStore, createLocalStorageAdapter, TEXT_SIZES, LEADINGS, MEASURES, FONT_FAMILIES, exportBackup, highlightsToMarkdown, requestPersistentStorage, buildTextIndex, resolveAnchor, spanToRange, describeSpan, paintHighlights, expandSpanToSentences, findBlock, BLOCK_ATTR, supportsHighlightApi, highlightToMarkdown, quoteToMarkdown, importBackup, ensureBlockIds, collectToc, IGNORE_ATTR, offsetFromPoint, highlightAtOffset, sentenceSpanAt, selectionSpan } from 'zreader';
|
|
6
|
+
import { cn } from 'cn';
|
|
7
|
+
import { Tooltip as Tooltip$1, Dialog as Dialog$1, Tabs as Tabs$1, Slot, ScrollArea as ScrollArea$1, ToggleGroup as ToggleGroup$1, Label as Label$1, DropdownMenu as DropdownMenu$1, Separator, Popover as Popover$1, Switch as Switch$1, Progress as Progress$1, Toggle as Toggle$1 } from 'radix-ui';
|
|
8
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
import { CheckCircle2Icon, SearchIcon, Trash2Icon, CopyIcon, QuoteIcon, EllipsisIcon, DownloadIcon, XIcon, TextSelectIcon, MessageSquarePlusIcon, RotateCcwIcon, HardDriveIcon, UploadIcon, ChevronLeftIcon, ListIcon, BookmarkIcon, HighlighterIcon, TypeIcon, NotebookPenIcon, CheckIcon, ArrowLeftIcon, ArrowRightIcon, BookOpenIcon, CircleIcon, LightbulbIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, InfoIcon, CircleCheckIcon, GripVerticalIcon } from 'lucide-react';
|
|
10
|
+
import { toast, Toaster as Toaster$1 } from 'sonner';
|
|
11
|
+
import * as ResizablePrimitive from 'react-resizable-panels';
|
|
12
|
+
import { usePanelRef, useDefaultLayout } from 'react-resizable-panels';
|
|
13
|
+
import { cva } from 'class-variance-authority';
|
|
14
|
+
import { Drawer as Drawer$1 } from 'vaul';
|
|
15
|
+
|
|
16
|
+
// src/react/context.tsx
|
|
17
|
+
function TooltipProvider({
|
|
18
|
+
delayDuration = 0,
|
|
19
|
+
...props
|
|
20
|
+
}) {
|
|
21
|
+
return /* @__PURE__ */ jsx(
|
|
22
|
+
Tooltip$1.Provider,
|
|
23
|
+
{
|
|
24
|
+
"data-slot": "tooltip-provider",
|
|
25
|
+
delayDuration,
|
|
26
|
+
...props
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
function Tooltip({
|
|
31
|
+
...props
|
|
32
|
+
}) {
|
|
33
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
|
|
34
|
+
}
|
|
35
|
+
function TooltipTrigger({
|
|
36
|
+
...props
|
|
37
|
+
}) {
|
|
38
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
39
|
+
}
|
|
40
|
+
function TooltipContent({
|
|
41
|
+
className,
|
|
42
|
+
sideOffset = 0,
|
|
43
|
+
children,
|
|
44
|
+
...props
|
|
45
|
+
}) {
|
|
46
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
|
|
47
|
+
Tooltip$1.Content,
|
|
48
|
+
{
|
|
49
|
+
"data-slot": "tooltip-content",
|
|
50
|
+
sideOffset,
|
|
51
|
+
className: cn(
|
|
52
|
+
"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",
|
|
53
|
+
className
|
|
54
|
+
),
|
|
55
|
+
...props,
|
|
56
|
+
children: [
|
|
57
|
+
children,
|
|
58
|
+
/* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
) });
|
|
62
|
+
}
|
|
63
|
+
var DefaultLink = forwardRef(
|
|
64
|
+
function DefaultLink2(props, ref) {
|
|
65
|
+
return /* @__PURE__ */ jsx("a", { ref, ...props });
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
function defaultHref(basePath, bookId, chapterId, hash) {
|
|
69
|
+
const base = basePath.replace(/\/+$/, "");
|
|
70
|
+
const path = chapterId ? `${base}/${encodeURIComponent(bookId)}/${encodeURIComponent(chapterId)}` : `${base}/${encodeURIComponent(bookId)}`;
|
|
71
|
+
return hash ? `${path}#${encodeURIComponent(hash)}` : path;
|
|
72
|
+
}
|
|
73
|
+
var Toaster = ({ ...props }) => {
|
|
74
|
+
const { theme = "system" } = useTheme();
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
Toaster$1,
|
|
77
|
+
{
|
|
78
|
+
theme,
|
|
79
|
+
className: "toaster group",
|
|
80
|
+
icons: {
|
|
81
|
+
success: /* @__PURE__ */ jsx(CircleCheckIcon, { className: "size-4" }),
|
|
82
|
+
info: /* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }),
|
|
83
|
+
warning: /* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4" }),
|
|
84
|
+
error: /* @__PURE__ */ jsx(OctagonXIcon, { className: "size-4" }),
|
|
85
|
+
loading: /* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" })
|
|
86
|
+
},
|
|
87
|
+
style: {
|
|
88
|
+
"--normal-bg": "var(--popover)",
|
|
89
|
+
"--normal-text": "var(--popover-foreground)",
|
|
90
|
+
"--normal-border": "var(--border)",
|
|
91
|
+
"--border-radius": "var(--radius)"
|
|
92
|
+
},
|
|
93
|
+
...props
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
function isPlainObject(x) {
|
|
98
|
+
if (typeof x !== "object" || x === null || Array.isArray(x)) return false;
|
|
99
|
+
const proto = Object.getPrototypeOf(x);
|
|
100
|
+
return proto === Object.prototype || proto === null;
|
|
101
|
+
}
|
|
102
|
+
function mergeOptions(base, ...overrides) {
|
|
103
|
+
const merge = (a, b) => {
|
|
104
|
+
if (b === void 0 || b === null) return a;
|
|
105
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
106
|
+
const out = { ...a };
|
|
107
|
+
for (const [k, v] of Object.entries(b)) out[k] = merge(a[k], v);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
return b;
|
|
111
|
+
};
|
|
112
|
+
return overrides.reduce(merge, base);
|
|
113
|
+
}
|
|
114
|
+
var ReaderContext = createContext(null);
|
|
115
|
+
function ReaderProvider({
|
|
116
|
+
options,
|
|
117
|
+
overrides,
|
|
118
|
+
adapter,
|
|
119
|
+
store: external,
|
|
120
|
+
onStorageError,
|
|
121
|
+
manageTheme = true,
|
|
122
|
+
toaster = true,
|
|
123
|
+
books,
|
|
124
|
+
Link,
|
|
125
|
+
hrefFor,
|
|
126
|
+
headerSlot,
|
|
127
|
+
readerActions,
|
|
128
|
+
children
|
|
129
|
+
}) {
|
|
130
|
+
const key = JSON.stringify([options ?? null, overrides ?? null]);
|
|
131
|
+
const resolved = useMemo(
|
|
132
|
+
() => mergeOptions(DEFAULT_CLIENT_OPTIONS, options, overrides),
|
|
133
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
134
|
+
[key]
|
|
135
|
+
);
|
|
136
|
+
const [store] = useState(
|
|
137
|
+
() => external ?? createReaderStore({ adapter: adapter ?? createLocalStorageAdapter(), options: resolved, onError: onStorageError })
|
|
138
|
+
);
|
|
139
|
+
useEffect(() => store.connect(), [store]);
|
|
140
|
+
const library = useMemo(() => {
|
|
141
|
+
const list = books ?? [];
|
|
142
|
+
const byId = new Map(list.map((b) => [b.id, b]));
|
|
143
|
+
return {
|
|
144
|
+
books: list,
|
|
145
|
+
getBook: (id) => byId.get(id),
|
|
146
|
+
Link: Link ?? DefaultLink,
|
|
147
|
+
href: hrefFor ?? ((b, c, h) => defaultHref(resolved.basePath, b, c, h)),
|
|
148
|
+
headerSlot,
|
|
149
|
+
readerActions
|
|
150
|
+
};
|
|
151
|
+
}, [books, Link, hrefFor, headerSlot, readerActions, resolved.basePath]);
|
|
152
|
+
const value = useMemo(() => ({ store, options: resolved, library }), [store, resolved, library]);
|
|
153
|
+
const tree = /* @__PURE__ */ jsx(ReaderContext.Provider, { value, children: /* @__PURE__ */ jsxs(TooltipProvider, { delayDuration: 400, children: [
|
|
154
|
+
children,
|
|
155
|
+
toaster && /* @__PURE__ */ jsx(Toaster, { position: "top-center" })
|
|
156
|
+
] }) });
|
|
157
|
+
if (!manageTheme) return tree;
|
|
158
|
+
return /* @__PURE__ */ jsx(
|
|
159
|
+
ThemeProvider,
|
|
160
|
+
{
|
|
161
|
+
attribute: "class",
|
|
162
|
+
themes: resolved.themes.filter((t) => t !== "system"),
|
|
163
|
+
enableSystem: resolved.themes.includes("system"),
|
|
164
|
+
defaultTheme: resolved.defaultTheme,
|
|
165
|
+
storageKey: `${resolved.storagePrefix}:v1:theme`,
|
|
166
|
+
disableTransitionOnChange: true,
|
|
167
|
+
children: tree
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
function useReader() {
|
|
172
|
+
const ctx = useContext(ReaderContext);
|
|
173
|
+
if (!ctx) throw new Error("zreader: wrap your tree in <ReaderProvider> (or <ReaderRoot>).");
|
|
174
|
+
return ctx;
|
|
175
|
+
}
|
|
176
|
+
function useLibrary() {
|
|
177
|
+
return useReader().library;
|
|
178
|
+
}
|
|
179
|
+
function useBook(book) {
|
|
180
|
+
const { getBook, books } = useLibrary();
|
|
181
|
+
if (typeof book !== "string") return book;
|
|
182
|
+
const found = getBook(book);
|
|
183
|
+
if (!found) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
books.length ? `zreader: no book with id "${book}" in <ReaderProvider books>. Known: ${books.map((b) => b.id).join(", ")}.` : `zreader: <\u2026 book="${book}"> needs the catalogue. Pass books to <ReaderProvider books={\u2026}> (or use <ReaderRoot>, which loads them).`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return found;
|
|
189
|
+
}
|
|
190
|
+
function useReaderStore() {
|
|
191
|
+
return useReader().store;
|
|
192
|
+
}
|
|
193
|
+
function useCell(cell, serverValue) {
|
|
194
|
+
return useSyncExternalStore(cell.subscribe, cell.get, () => serverValue);
|
|
195
|
+
}
|
|
196
|
+
var noopSubscribe = () => () => {
|
|
197
|
+
};
|
|
198
|
+
function useHydrated() {
|
|
199
|
+
return useSyncExternalStore(
|
|
200
|
+
noopSubscribe,
|
|
201
|
+
() => true,
|
|
202
|
+
() => false
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
function useSettings() {
|
|
206
|
+
const store = useReaderStore();
|
|
207
|
+
const settings = useCell(store.settings(), store.server.settings);
|
|
208
|
+
return [settings, store.updateSettings];
|
|
209
|
+
}
|
|
210
|
+
function useHighlights(bookId, chapterId) {
|
|
211
|
+
const store = useReaderStore();
|
|
212
|
+
const all = useCell(store.highlights(bookId), store.server.list);
|
|
213
|
+
return useMemo(() => chapterId ? all.filter((h) => h.chapterId === chapterId) : all, [all, chapterId]);
|
|
214
|
+
}
|
|
215
|
+
function useBookmarks(bookId) {
|
|
216
|
+
const store = useReaderStore();
|
|
217
|
+
return useCell(store.bookmarks(bookId), store.server.list);
|
|
218
|
+
}
|
|
219
|
+
function useProgress(bookId) {
|
|
220
|
+
const store = useReaderStore();
|
|
221
|
+
return useCell(store.progress(bookId), store.server.progress(bookId));
|
|
222
|
+
}
|
|
223
|
+
function usePad(scope) {
|
|
224
|
+
const store = useReaderStore();
|
|
225
|
+
return useCell(store.pad(scope), store.server.pad);
|
|
226
|
+
}
|
|
227
|
+
function useMediaQuery(query, serverValue = false) {
|
|
228
|
+
return useSyncExternalStore(
|
|
229
|
+
(cb) => {
|
|
230
|
+
if (typeof window === "undefined" || !window.matchMedia) return () => {
|
|
231
|
+
};
|
|
232
|
+
const mql = window.matchMedia(query);
|
|
233
|
+
mql.addEventListener("change", cb);
|
|
234
|
+
return () => mql.removeEventListener("change", cb);
|
|
235
|
+
},
|
|
236
|
+
() => typeof window !== "undefined" && window.matchMedia ? window.matchMedia(query).matches : serverValue,
|
|
237
|
+
() => serverValue
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
function useIsDesktop() {
|
|
241
|
+
return useMediaQuery("(min-width: 1024px)", false);
|
|
242
|
+
}
|
|
243
|
+
var buttonVariants = cva(
|
|
244
|
+
"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",
|
|
245
|
+
{
|
|
246
|
+
variants: {
|
|
247
|
+
variant: {
|
|
248
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
249
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
250
|
+
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",
|
|
251
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
252
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
253
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
254
|
+
},
|
|
255
|
+
size: {
|
|
256
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
257
|
+
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
258
|
+
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
|
259
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
260
|
+
icon: "size-9",
|
|
261
|
+
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
262
|
+
"icon-sm": "size-8",
|
|
263
|
+
"icon-lg": "size-10"
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
defaultVariants: {
|
|
267
|
+
variant: "default",
|
|
268
|
+
size: "default"
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
function Button({
|
|
273
|
+
className,
|
|
274
|
+
variant = "default",
|
|
275
|
+
size = "default",
|
|
276
|
+
asChild = false,
|
|
277
|
+
...props
|
|
278
|
+
}) {
|
|
279
|
+
const Comp = asChild ? Slot.Root : "button";
|
|
280
|
+
return /* @__PURE__ */ jsx(
|
|
281
|
+
Comp,
|
|
282
|
+
{
|
|
283
|
+
"data-slot": "button",
|
|
284
|
+
"data-variant": variant,
|
|
285
|
+
"data-size": size,
|
|
286
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
287
|
+
...props
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
function Drawer({
|
|
292
|
+
...props
|
|
293
|
+
}) {
|
|
294
|
+
return /* @__PURE__ */ jsx(Drawer$1.Root, { "data-slot": "drawer", ...props });
|
|
295
|
+
}
|
|
296
|
+
function DrawerPortal({
|
|
297
|
+
...props
|
|
298
|
+
}) {
|
|
299
|
+
return /* @__PURE__ */ jsx(Drawer$1.Portal, { "data-slot": "drawer-portal", ...props });
|
|
300
|
+
}
|
|
301
|
+
function DrawerOverlay({
|
|
302
|
+
className,
|
|
303
|
+
...props
|
|
304
|
+
}) {
|
|
305
|
+
return /* @__PURE__ */ jsx(
|
|
306
|
+
Drawer$1.Overlay,
|
|
307
|
+
{
|
|
308
|
+
"data-slot": "drawer-overlay",
|
|
309
|
+
className: cn(
|
|
310
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
311
|
+
className
|
|
312
|
+
),
|
|
313
|
+
...props
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
function DrawerContent({
|
|
318
|
+
className,
|
|
319
|
+
children,
|
|
320
|
+
...props
|
|
321
|
+
}) {
|
|
322
|
+
return /* @__PURE__ */ jsxs(DrawerPortal, { "data-slot": "drawer-portal", children: [
|
|
323
|
+
/* @__PURE__ */ jsx(DrawerOverlay, {}),
|
|
324
|
+
/* @__PURE__ */ jsxs(
|
|
325
|
+
Drawer$1.Content,
|
|
326
|
+
{
|
|
327
|
+
"data-slot": "drawer-content",
|
|
328
|
+
className: cn(
|
|
329
|
+
"group/drawer-content fixed z-50 flex h-auto flex-col bg-background",
|
|
330
|
+
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
|
331
|
+
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
|
332
|
+
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
|
333
|
+
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
|
334
|
+
className
|
|
335
|
+
),
|
|
336
|
+
...props,
|
|
337
|
+
children: [
|
|
338
|
+
/* @__PURE__ */ jsx("div", { className: "mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" }),
|
|
339
|
+
children
|
|
340
|
+
]
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
] });
|
|
344
|
+
}
|
|
345
|
+
function DrawerHeader({ className, ...props }) {
|
|
346
|
+
return /* @__PURE__ */ jsx(
|
|
347
|
+
"div",
|
|
348
|
+
{
|
|
349
|
+
"data-slot": "drawer-header",
|
|
350
|
+
className: cn(
|
|
351
|
+
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
|
|
352
|
+
className
|
|
353
|
+
),
|
|
354
|
+
...props
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
function DrawerFooter({ className, ...props }) {
|
|
359
|
+
return /* @__PURE__ */ jsx(
|
|
360
|
+
"div",
|
|
361
|
+
{
|
|
362
|
+
"data-slot": "drawer-footer",
|
|
363
|
+
className: cn("mt-auto flex flex-col gap-2 p-4", className),
|
|
364
|
+
...props
|
|
365
|
+
}
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
function DrawerTitle({
|
|
369
|
+
className,
|
|
370
|
+
...props
|
|
371
|
+
}) {
|
|
372
|
+
return /* @__PURE__ */ jsx(
|
|
373
|
+
Drawer$1.Title,
|
|
374
|
+
{
|
|
375
|
+
"data-slot": "drawer-title",
|
|
376
|
+
className: cn("font-semibold text-foreground", className),
|
|
377
|
+
...props
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
function DrawerDescription({
|
|
382
|
+
className,
|
|
383
|
+
...props
|
|
384
|
+
}) {
|
|
385
|
+
return /* @__PURE__ */ jsx(
|
|
386
|
+
Drawer$1.Description,
|
|
387
|
+
{
|
|
388
|
+
"data-slot": "drawer-description",
|
|
389
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
390
|
+
...props
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
function Progress({
|
|
395
|
+
className,
|
|
396
|
+
value,
|
|
397
|
+
...props
|
|
398
|
+
}) {
|
|
399
|
+
return /* @__PURE__ */ jsx(
|
|
400
|
+
Progress$1.Root,
|
|
401
|
+
{
|
|
402
|
+
"data-slot": "progress",
|
|
403
|
+
className: cn(
|
|
404
|
+
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
|
405
|
+
className
|
|
406
|
+
),
|
|
407
|
+
...props,
|
|
408
|
+
children: /* @__PURE__ */ jsx(
|
|
409
|
+
Progress$1.Indicator,
|
|
410
|
+
{
|
|
411
|
+
"data-slot": "progress-indicator",
|
|
412
|
+
className: "h-full w-full flex-1 bg-primary transition-all",
|
|
413
|
+
style: { transform: `translateX(-${100 - (value || 0)}%)` }
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
function ResizablePanelGroup({
|
|
420
|
+
className,
|
|
421
|
+
...props
|
|
422
|
+
}) {
|
|
423
|
+
return /* @__PURE__ */ jsx(
|
|
424
|
+
ResizablePrimitive.Group,
|
|
425
|
+
{
|
|
426
|
+
"data-slot": "resizable-panel-group",
|
|
427
|
+
className: cn(
|
|
428
|
+
"flex h-full w-full aria-[orientation=vertical]:flex-col",
|
|
429
|
+
className
|
|
430
|
+
),
|
|
431
|
+
...props
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
function ResizablePanel({ ...props }) {
|
|
436
|
+
return /* @__PURE__ */ jsx(ResizablePrimitive.Panel, { "data-slot": "resizable-panel", ...props });
|
|
437
|
+
}
|
|
438
|
+
function ResizableHandle({
|
|
439
|
+
withHandle,
|
|
440
|
+
className,
|
|
441
|
+
...props
|
|
442
|
+
}) {
|
|
443
|
+
return /* @__PURE__ */ jsx(
|
|
444
|
+
ResizablePrimitive.Separator,
|
|
445
|
+
{
|
|
446
|
+
"data-slot": "resizable-handle",
|
|
447
|
+
className: cn(
|
|
448
|
+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
|
|
449
|
+
className
|
|
450
|
+
),
|
|
451
|
+
...props,
|
|
452
|
+
children: withHandle && /* @__PURE__ */ jsx("div", { className: "z-10 flex h-4 w-3 items-center justify-center rounded-xs border bg-border", children: /* @__PURE__ */ jsx(GripVerticalIcon, { className: "size-2.5" }) })
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
var toggleVariants = cva(
|
|
457
|
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground 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 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
458
|
+
{
|
|
459
|
+
variants: {
|
|
460
|
+
variant: {
|
|
461
|
+
default: "bg-transparent",
|
|
462
|
+
outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
|
|
463
|
+
},
|
|
464
|
+
size: {
|
|
465
|
+
default: "h-9 min-w-9 px-2",
|
|
466
|
+
sm: "h-8 min-w-8 px-1.5",
|
|
467
|
+
lg: "h-10 min-w-10 px-2.5"
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
defaultVariants: {
|
|
471
|
+
variant: "default",
|
|
472
|
+
size: "default"
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
function Toggle({
|
|
477
|
+
className,
|
|
478
|
+
variant,
|
|
479
|
+
size,
|
|
480
|
+
...props
|
|
481
|
+
}) {
|
|
482
|
+
return /* @__PURE__ */ jsx(
|
|
483
|
+
Toggle$1.Root,
|
|
484
|
+
{
|
|
485
|
+
"data-slot": "toggle",
|
|
486
|
+
className: cn(toggleVariants({ variant, size, className })),
|
|
487
|
+
...props
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
var badgeVariants = cva(
|
|
492
|
+
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
|
|
493
|
+
{
|
|
494
|
+
variants: {
|
|
495
|
+
variant: {
|
|
496
|
+
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
497
|
+
secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
498
|
+
destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
|
|
499
|
+
outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
500
|
+
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
501
|
+
link: "text-primary underline-offset-4 [a&]:hover:underline"
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
defaultVariants: {
|
|
505
|
+
variant: "default"
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
);
|
|
509
|
+
function Badge({
|
|
510
|
+
className,
|
|
511
|
+
variant = "default",
|
|
512
|
+
asChild = false,
|
|
513
|
+
...props
|
|
514
|
+
}) {
|
|
515
|
+
const Comp = asChild ? Slot.Root : "span";
|
|
516
|
+
return /* @__PURE__ */ jsx(
|
|
517
|
+
Comp,
|
|
518
|
+
{
|
|
519
|
+
"data-slot": "badge",
|
|
520
|
+
"data-variant": variant,
|
|
521
|
+
className: cn(badgeVariants({ variant }), className),
|
|
522
|
+
...props
|
|
523
|
+
}
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
function Input({ className, type, ...props }) {
|
|
527
|
+
return /* @__PURE__ */ jsx(
|
|
528
|
+
"input",
|
|
529
|
+
{
|
|
530
|
+
type,
|
|
531
|
+
"data-slot": "input",
|
|
532
|
+
className: cn(
|
|
533
|
+
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
|
534
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
535
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
536
|
+
className
|
|
537
|
+
),
|
|
538
|
+
...props
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
function ScrollArea({
|
|
543
|
+
className,
|
|
544
|
+
children,
|
|
545
|
+
...props
|
|
546
|
+
}) {
|
|
547
|
+
return /* @__PURE__ */ jsxs(
|
|
548
|
+
ScrollArea$1.Root,
|
|
549
|
+
{
|
|
550
|
+
"data-slot": "scroll-area",
|
|
551
|
+
className: cn("relative", className),
|
|
552
|
+
...props,
|
|
553
|
+
children: [
|
|
554
|
+
/* @__PURE__ */ jsx(
|
|
555
|
+
ScrollArea$1.Viewport,
|
|
556
|
+
{
|
|
557
|
+
"data-slot": "scroll-area-viewport",
|
|
558
|
+
className: "size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
|
|
559
|
+
children
|
|
560
|
+
}
|
|
561
|
+
),
|
|
562
|
+
/* @__PURE__ */ jsx(ScrollBar, {}),
|
|
563
|
+
/* @__PURE__ */ jsx(ScrollArea$1.Corner, {})
|
|
564
|
+
]
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
function ScrollBar({
|
|
569
|
+
className,
|
|
570
|
+
orientation = "vertical",
|
|
571
|
+
...props
|
|
572
|
+
}) {
|
|
573
|
+
return /* @__PURE__ */ jsx(
|
|
574
|
+
ScrollArea$1.ScrollAreaScrollbar,
|
|
575
|
+
{
|
|
576
|
+
"data-slot": "scroll-area-scrollbar",
|
|
577
|
+
orientation,
|
|
578
|
+
className: cn(
|
|
579
|
+
"flex touch-none p-px transition-colors select-none",
|
|
580
|
+
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
|
|
581
|
+
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
|
|
582
|
+
className
|
|
583
|
+
),
|
|
584
|
+
...props,
|
|
585
|
+
children: /* @__PURE__ */ jsx(
|
|
586
|
+
ScrollArea$1.ScrollAreaThumb,
|
|
587
|
+
{
|
|
588
|
+
"data-slot": "scroll-area-thumb",
|
|
589
|
+
className: "relative flex-1 rounded-full bg-border"
|
|
590
|
+
}
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
function Sheet({ ...props }) {
|
|
596
|
+
return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "sheet", ...props });
|
|
597
|
+
}
|
|
598
|
+
function SheetPortal({
|
|
599
|
+
...props
|
|
600
|
+
}) {
|
|
601
|
+
return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "sheet-portal", ...props });
|
|
602
|
+
}
|
|
603
|
+
function SheetOverlay({
|
|
604
|
+
className,
|
|
605
|
+
...props
|
|
606
|
+
}) {
|
|
607
|
+
return /* @__PURE__ */ jsx(
|
|
608
|
+
Dialog$1.Overlay,
|
|
609
|
+
{
|
|
610
|
+
"data-slot": "sheet-overlay",
|
|
611
|
+
className: cn(
|
|
612
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
613
|
+
className
|
|
614
|
+
),
|
|
615
|
+
...props
|
|
616
|
+
}
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
function SheetContent({
|
|
620
|
+
className,
|
|
621
|
+
children,
|
|
622
|
+
side = "right",
|
|
623
|
+
showCloseButton = true,
|
|
624
|
+
...props
|
|
625
|
+
}) {
|
|
626
|
+
return /* @__PURE__ */ jsxs(SheetPortal, { children: [
|
|
627
|
+
/* @__PURE__ */ jsx(SheetOverlay, {}),
|
|
628
|
+
/* @__PURE__ */ jsxs(
|
|
629
|
+
Dialog$1.Content,
|
|
630
|
+
{
|
|
631
|
+
"data-slot": "sheet-content",
|
|
632
|
+
className: cn(
|
|
633
|
+
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
634
|
+
side === "right" && "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
635
|
+
side === "left" && "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
|
636
|
+
side === "top" && "inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
637
|
+
side === "bottom" && "inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
638
|
+
className
|
|
639
|
+
),
|
|
640
|
+
...props,
|
|
641
|
+
children: [
|
|
642
|
+
children,
|
|
643
|
+
showCloseButton && /* @__PURE__ */ jsxs(Dialog$1.Close, { className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary", children: [
|
|
644
|
+
/* @__PURE__ */ jsx(XIcon, { className: "size-4" }),
|
|
645
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
646
|
+
] })
|
|
647
|
+
]
|
|
648
|
+
}
|
|
649
|
+
)
|
|
650
|
+
] });
|
|
651
|
+
}
|
|
652
|
+
function SheetHeader({ className, ...props }) {
|
|
653
|
+
return /* @__PURE__ */ jsx(
|
|
654
|
+
"div",
|
|
655
|
+
{
|
|
656
|
+
"data-slot": "sheet-header",
|
|
657
|
+
className: cn("flex flex-col gap-1.5 p-4", className),
|
|
658
|
+
...props
|
|
659
|
+
}
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
function SheetFooter({ className, ...props }) {
|
|
663
|
+
return /* @__PURE__ */ jsx(
|
|
664
|
+
"div",
|
|
665
|
+
{
|
|
666
|
+
"data-slot": "sheet-footer",
|
|
667
|
+
className: cn("mt-auto flex flex-col gap-2 p-4", className),
|
|
668
|
+
...props
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
function SheetTitle({
|
|
673
|
+
className,
|
|
674
|
+
...props
|
|
675
|
+
}) {
|
|
676
|
+
return /* @__PURE__ */ jsx(
|
|
677
|
+
Dialog$1.Title,
|
|
678
|
+
{
|
|
679
|
+
"data-slot": "sheet-title",
|
|
680
|
+
className: cn("font-semibold text-foreground", className),
|
|
681
|
+
...props
|
|
682
|
+
}
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
function SheetDescription({
|
|
686
|
+
className,
|
|
687
|
+
...props
|
|
688
|
+
}) {
|
|
689
|
+
return /* @__PURE__ */ jsx(
|
|
690
|
+
Dialog$1.Description,
|
|
691
|
+
{
|
|
692
|
+
"data-slot": "sheet-description",
|
|
693
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
694
|
+
...props
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
function Tabs({
|
|
699
|
+
className,
|
|
700
|
+
orientation = "horizontal",
|
|
701
|
+
...props
|
|
702
|
+
}) {
|
|
703
|
+
return /* @__PURE__ */ jsx(
|
|
704
|
+
Tabs$1.Root,
|
|
705
|
+
{
|
|
706
|
+
"data-slot": "tabs",
|
|
707
|
+
"data-orientation": orientation,
|
|
708
|
+
orientation,
|
|
709
|
+
className: cn(
|
|
710
|
+
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
|
711
|
+
className
|
|
712
|
+
),
|
|
713
|
+
...props
|
|
714
|
+
}
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
var tabsListVariants = cva(
|
|
718
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none",
|
|
719
|
+
{
|
|
720
|
+
variants: {
|
|
721
|
+
variant: {
|
|
722
|
+
default: "bg-muted",
|
|
723
|
+
line: "gap-1 bg-transparent"
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
defaultVariants: {
|
|
727
|
+
variant: "default"
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
function TabsList({
|
|
732
|
+
className,
|
|
733
|
+
variant = "default",
|
|
734
|
+
...props
|
|
735
|
+
}) {
|
|
736
|
+
return /* @__PURE__ */ jsx(
|
|
737
|
+
Tabs$1.List,
|
|
738
|
+
{
|
|
739
|
+
"data-slot": "tabs-list",
|
|
740
|
+
"data-variant": variant,
|
|
741
|
+
className: cn(tabsListVariants({ variant }), className),
|
|
742
|
+
...props
|
|
743
|
+
}
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
function TabsTrigger({
|
|
747
|
+
className,
|
|
748
|
+
...props
|
|
749
|
+
}) {
|
|
750
|
+
return /* @__PURE__ */ jsx(
|
|
751
|
+
Tabs$1.Trigger,
|
|
752
|
+
{
|
|
753
|
+
"data-slot": "tabs-trigger",
|
|
754
|
+
className: cn(
|
|
755
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
756
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
757
|
+
"data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground",
|
|
758
|
+
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
759
|
+
className
|
|
760
|
+
),
|
|
761
|
+
...props
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
function TabsContent({
|
|
766
|
+
className,
|
|
767
|
+
...props
|
|
768
|
+
}) {
|
|
769
|
+
return /* @__PURE__ */ jsx(
|
|
770
|
+
Tabs$1.Content,
|
|
771
|
+
{
|
|
772
|
+
"data-slot": "tabs-content",
|
|
773
|
+
className: cn("flex-1 outline-none", className),
|
|
774
|
+
...props
|
|
775
|
+
}
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/react/parts.ts
|
|
780
|
+
function groupByPart(chapters) {
|
|
781
|
+
const groups = [];
|
|
782
|
+
chapters.forEach((chapter, index) => {
|
|
783
|
+
const part = chapter.part ?? null;
|
|
784
|
+
const last = groups[groups.length - 1];
|
|
785
|
+
if (last && last.part === part) last.items.push({ chapter, index });
|
|
786
|
+
else groups.push({ part, items: [{ chapter, index }] });
|
|
787
|
+
});
|
|
788
|
+
return groups;
|
|
789
|
+
}
|
|
790
|
+
function chapterMarker(chapter, index) {
|
|
791
|
+
return chapter.number ?? (chapter.part || chapter.label ? "" : String(index + 1));
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/react/tokens.ts
|
|
795
|
+
var TEXT_SIZE_CLASS = {
|
|
796
|
+
sm: "prose-sm",
|
|
797
|
+
base: "prose-base",
|
|
798
|
+
lg: "prose-lg",
|
|
799
|
+
xl: "prose-xl",
|
|
800
|
+
"2xl": "prose-2xl"
|
|
801
|
+
};
|
|
802
|
+
var LEADING_CLASS = {
|
|
803
|
+
tight: "prose-p:leading-snug prose-li:leading-snug",
|
|
804
|
+
normal: "prose-p:leading-normal prose-li:leading-normal",
|
|
805
|
+
relaxed: "prose-p:leading-relaxed prose-li:leading-relaxed"
|
|
806
|
+
};
|
|
807
|
+
var MEASURE_CLASS = {
|
|
808
|
+
narrow: "max-w-[58ch]",
|
|
809
|
+
normal: "max-w-[68ch]",
|
|
810
|
+
wide: "max-w-[82ch]"
|
|
811
|
+
};
|
|
812
|
+
var FONT_CLASS = {
|
|
813
|
+
serif: "font-serif",
|
|
814
|
+
sans: "font-sans",
|
|
815
|
+
mono: "font-mono"
|
|
816
|
+
};
|
|
817
|
+
var SWATCH_CLASS = {
|
|
818
|
+
yellow: "bg-yellow-300 dark:bg-yellow-400/70",
|
|
819
|
+
green: "bg-green-300 dark:bg-green-400/60",
|
|
820
|
+
blue: "bg-sky-300 dark:bg-sky-400/60",
|
|
821
|
+
pink: "bg-pink-300 dark:bg-pink-400/60",
|
|
822
|
+
purple: "bg-violet-300 dark:bg-violet-400/60",
|
|
823
|
+
orange: "bg-orange-300 dark:bg-orange-400/60"
|
|
824
|
+
};
|
|
825
|
+
var QUOTE_BORDER_CLASS = {
|
|
826
|
+
yellow: "border-yellow-400",
|
|
827
|
+
green: "border-green-400",
|
|
828
|
+
blue: "border-sky-400",
|
|
829
|
+
pink: "border-pink-400",
|
|
830
|
+
purple: "border-violet-400",
|
|
831
|
+
orange: "border-orange-400"
|
|
832
|
+
};
|
|
833
|
+
function swatchClass(color) {
|
|
834
|
+
return SWATCH_CLASS[color] ?? SWATCH_CLASS.yellow;
|
|
835
|
+
}
|
|
836
|
+
function quoteBorderClass(color) {
|
|
837
|
+
return QUOTE_BORDER_CLASS[color] ?? QUOTE_BORDER_CLASS.yellow;
|
|
838
|
+
}
|
|
839
|
+
var PROSE_CLASS = "prose max-w-none text-foreground prose-headings:text-foreground prose-headings:scroll-mt-20 prose-p:text-foreground prose-li:text-foreground prose-strong:text-foreground prose-a:text-primary prose-a:underline-offset-4 prose-blockquote:border-l-border prose-blockquote:text-muted-foreground prose-code:text-foreground prose-pre:bg-muted prose-pre:text-foreground prose-hr:border-border prose-th:text-foreground prose-td:text-foreground prose-figcaption:text-muted-foreground prose-img:rounded-lg prose-table:block prose-table:max-w-full prose-table:overflow-x-auto prose-th:align-bottom prose-td:align-top";
|
|
840
|
+
var dateFmt = (iso) => new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
841
|
+
function ContentsSheet(p) {
|
|
842
|
+
const { book, chapterId, Link } = p;
|
|
843
|
+
const store = useReaderStore();
|
|
844
|
+
const highlights = useHighlights(book.id);
|
|
845
|
+
const bookmarks = useBookmarks(book.id);
|
|
846
|
+
const progress = useProgress(book.id);
|
|
847
|
+
const [query, setQuery] = useState("");
|
|
848
|
+
const titles = useMemo(() => new Map(book.chapters.map((c) => [c.id, c.title])), [book.chapters]);
|
|
849
|
+
const order = useMemo(() => new Map(book.chapters.map((c, i) => [c.id, i])), [book.chapters]);
|
|
850
|
+
const grouped = useMemo(() => {
|
|
851
|
+
const q = query.trim().toLowerCase();
|
|
852
|
+
const groups = /* @__PURE__ */ new Map();
|
|
853
|
+
highlights.filter((h) => !q || h.anchor.exact.toLowerCase().includes(q) || h.note?.toLowerCase().includes(q)).sort((a, b) => (order.get(a.chapterId) ?? 0) - (order.get(b.chapterId) ?? 0) || a.anchor.start - b.anchor.start).forEach((h) => groups.set(h.chapterId, [...groups.get(h.chapterId) ?? [], h]));
|
|
854
|
+
return groups;
|
|
855
|
+
}, [highlights, query, order]);
|
|
856
|
+
const sortedBookmarks = useMemo(
|
|
857
|
+
() => [...bookmarks].sort((a, b) => (order.get(a.chapterId) ?? 0) - (order.get(b.chapterId) ?? 0)),
|
|
858
|
+
[bookmarks, order]
|
|
859
|
+
);
|
|
860
|
+
const close = () => p.onOpenChange(false);
|
|
861
|
+
const jumpOr = (targetChapter, inPage) => (e) => {
|
|
862
|
+
if (targetChapter !== chapterId) return close();
|
|
863
|
+
e.preventDefault();
|
|
864
|
+
close();
|
|
865
|
+
inPage();
|
|
866
|
+
};
|
|
867
|
+
return /* @__PURE__ */ jsx(Sheet, { open: p.open, onOpenChange: p.onOpenChange, children: /* @__PURE__ */ jsxs(SheetContent, { side: "left", className: "w-[88vw] gap-0 p-0 sm:max-w-sm", children: [
|
|
868
|
+
/* @__PURE__ */ jsxs(SheetHeader, { className: "border-b", children: [
|
|
869
|
+
/* @__PURE__ */ jsx(SheetTitle, { className: "line-clamp-2", children: book.title }),
|
|
870
|
+
book.authors.length > 0 && /* @__PURE__ */ jsx(SheetDescription, { children: book.authors.join(", ") })
|
|
871
|
+
] }),
|
|
872
|
+
/* @__PURE__ */ jsxs(Tabs, { value: p.tab, onValueChange: (v) => p.onTabChange(v), className: "min-h-0 flex-1 gap-0", children: [
|
|
873
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 pt-3", children: /* @__PURE__ */ jsxs(TabsList, { className: "w-full", children: [
|
|
874
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "contents", children: "Contents" }),
|
|
875
|
+
/* @__PURE__ */ jsxs(TabsTrigger, { value: "highlights", children: [
|
|
876
|
+
"Highlights",
|
|
877
|
+
highlights.length > 0 && /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: highlights.length })
|
|
878
|
+
] }),
|
|
879
|
+
/* @__PURE__ */ jsxs(TabsTrigger, { value: "bookmarks", children: [
|
|
880
|
+
"Bookmarks",
|
|
881
|
+
bookmarks.length > 0 && /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: bookmarks.length })
|
|
882
|
+
] })
|
|
883
|
+
] }) }),
|
|
884
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "contents", className: "min-h-0", children: /* @__PURE__ */ jsx(ScrollArea, { className: "h-full", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3 p-2", children: groupByPart(book.chapters).map((group) => /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-0.5", children: [
|
|
885
|
+
group.part && /* @__PURE__ */ jsx("h3", { className: "px-3 pt-2 pb-1 text-xs font-medium tracking-wide text-muted-foreground uppercase", children: group.part }),
|
|
886
|
+
/* @__PURE__ */ jsx("ol", { className: "flex flex-col gap-0.5", children: group.items.map(({ chapter: c, index: i }) => {
|
|
887
|
+
const pr = progress.chapters[c.id];
|
|
888
|
+
const current = c.id === chapterId;
|
|
889
|
+
return /* @__PURE__ */ jsxs("li", { children: [
|
|
890
|
+
/* @__PURE__ */ jsx(Button, { asChild: true, variant: current ? "secondary" : "ghost", className: "h-auto w-full justify-start gap-3 py-2 text-left whitespace-normal", children: /* @__PURE__ */ jsxs(Link, { href: p.hrefFor(c.id), "aria-current": current ? "page" : void 0, onClick: close, children: [
|
|
891
|
+
/* @__PURE__ */ jsx("span", { className: "w-5 shrink-0 text-xs text-muted-foreground tabular-nums", children: chapterMarker(c, i) }),
|
|
892
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", children: c.title }),
|
|
893
|
+
pr?.completed ? /* @__PURE__ */ jsx(CheckCircle2Icon, { className: "text-primary", "aria-label": "Completed" }) : pr ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground tabular-nums", children: [
|
|
894
|
+
Math.round(pr.percent * 100),
|
|
895
|
+
"%"
|
|
896
|
+
] }) : null
|
|
897
|
+
] }) }),
|
|
898
|
+
current && p.toc.length > 0 && /* @__PURE__ */ jsx("ol", { className: "my-1 ml-8 flex flex-col border-l", children: p.toc.map((t) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
899
|
+
Button,
|
|
900
|
+
{
|
|
901
|
+
asChild: true,
|
|
902
|
+
variant: "link",
|
|
903
|
+
size: "sm",
|
|
904
|
+
className: cn("h-auto w-full justify-start py-1.5 text-left whitespace-normal text-muted-foreground", t.depth > 2 && "pl-6"),
|
|
905
|
+
children: /* @__PURE__ */ jsx("a", { href: `#${t.id}`, onClick: jumpOr(chapterId, () => p.onJumpHeading(t.id)), children: t.text })
|
|
906
|
+
}
|
|
907
|
+
) }, t.id)) })
|
|
908
|
+
] }, c.id);
|
|
909
|
+
}) })
|
|
910
|
+
] }, group.part ?? "_")) }) }) }),
|
|
911
|
+
/* @__PURE__ */ jsxs(TabsContent, { value: "highlights", className: "flex min-h-0 flex-col", children: [
|
|
912
|
+
highlights.length > 3 && /* @__PURE__ */ jsx("div", { className: "px-4 pt-3", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
913
|
+
/* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" }),
|
|
914
|
+
/* @__PURE__ */ jsx(Input, { type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search highlights and notes", "aria-label": "Search highlights", className: "pl-8" })
|
|
915
|
+
] }) }),
|
|
916
|
+
/* @__PURE__ */ jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-4", children: [
|
|
917
|
+
grouped.size === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: highlights.length ? "No matches." : "Select text \u2014 or turn on highlight mode and tap a sentence \u2014 to highlight it." }),
|
|
918
|
+
[...grouped].map(([cid, list]) => /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-2", children: [
|
|
919
|
+
/* @__PURE__ */ jsx("h3", { className: "text-xs font-medium tracking-wide text-muted-foreground uppercase", children: titles.get(cid) ?? cid }),
|
|
920
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-2", children: list.map((h) => /* @__PURE__ */ jsxs("li", { className: "group flex items-start gap-1", children: [
|
|
921
|
+
/* @__PURE__ */ jsxs(
|
|
922
|
+
Link,
|
|
923
|
+
{
|
|
924
|
+
href: p.hrefFor(cid, `hl-${h.id}`),
|
|
925
|
+
onClick: jumpOr(cid, () => p.onJumpHighlight(h.id)),
|
|
926
|
+
className: cn("flex-1 rounded-md border-l-4 bg-muted/40 px-3 py-2 text-sm transition-colors hover:bg-muted", quoteBorderClass(h.color)),
|
|
927
|
+
children: [
|
|
928
|
+
/* @__PURE__ */ jsx("span", { className: "line-clamp-3 font-serif", children: h.anchor.exact }),
|
|
929
|
+
h.note && /* @__PURE__ */ jsx("span", { className: "mt-1 line-clamp-2 block text-muted-foreground", children: h.note }),
|
|
930
|
+
/* @__PURE__ */ jsxs("span", { className: "mt-1 flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
931
|
+
dateFmt(h.updatedAt),
|
|
932
|
+
cid === chapterId && p.orphanedIds.has(h.id) && /* @__PURE__ */ jsx(Badge, { variant: "outline", title: "The passage changed and this highlight could not be placed", children: "Unplaced" })
|
|
933
|
+
] })
|
|
934
|
+
]
|
|
935
|
+
}
|
|
936
|
+
),
|
|
937
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Delete highlight", onClick: () => store.removeHighlight(book.id, h.id), children: /* @__PURE__ */ jsx(Trash2Icon, {}) })
|
|
938
|
+
] }, h.id)) })
|
|
939
|
+
] }, cid))
|
|
940
|
+
] }) })
|
|
941
|
+
] }),
|
|
942
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "bookmarks", className: "min-h-0", children: /* @__PURE__ */ jsx(ScrollArea, { className: "h-full", children: /* @__PURE__ */ jsxs("ul", { className: "flex flex-col gap-2 p-4", children: [
|
|
943
|
+
sortedBookmarks.length === 0 && /* @__PURE__ */ jsx("li", { className: "text-sm text-muted-foreground", children: "Use the bookmark button to save your place." }),
|
|
944
|
+
sortedBookmarks.map((b) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-1", children: [
|
|
945
|
+
/* @__PURE__ */ jsxs(
|
|
946
|
+
Link,
|
|
947
|
+
{
|
|
948
|
+
href: p.hrefFor(b.chapterId, b.blockId ? `b-${b.blockId}` : void 0),
|
|
949
|
+
onClick: b.blockId ? jumpOr(b.chapterId, () => p.onJumpBlock(b.blockId)) : close,
|
|
950
|
+
className: "flex-1 rounded-md bg-muted/40 px-3 py-2 text-sm transition-colors hover:bg-muted",
|
|
951
|
+
children: [
|
|
952
|
+
/* @__PURE__ */ jsx("span", { className: "block font-medium", children: b.label ?? titles.get(b.chapterId) ?? b.chapterId }),
|
|
953
|
+
b.excerpt && /* @__PURE__ */ jsx("span", { className: "mt-0.5 line-clamp-2 block text-muted-foreground", children: b.excerpt }),
|
|
954
|
+
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-xs text-muted-foreground", children: dateFmt(b.createdAt) })
|
|
955
|
+
]
|
|
956
|
+
}
|
|
957
|
+
),
|
|
958
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Remove bookmark", onClick: () => store.removeBookmark(book.id, b.id), children: /* @__PURE__ */ jsx(Trash2Icon, {}) })
|
|
959
|
+
] }, b.id))
|
|
960
|
+
] }) }) })
|
|
961
|
+
] })
|
|
962
|
+
] }) });
|
|
963
|
+
}
|
|
964
|
+
function Label({
|
|
965
|
+
className,
|
|
966
|
+
...props
|
|
967
|
+
}) {
|
|
968
|
+
return /* @__PURE__ */ jsx(
|
|
969
|
+
Label$1.Root,
|
|
970
|
+
{
|
|
971
|
+
"data-slot": "label",
|
|
972
|
+
className: cn(
|
|
973
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
974
|
+
className
|
|
975
|
+
),
|
|
976
|
+
...props
|
|
977
|
+
}
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
function Textarea({ className, ...props }) {
|
|
981
|
+
return /* @__PURE__ */ jsx(
|
|
982
|
+
"textarea",
|
|
983
|
+
{
|
|
984
|
+
"data-slot": "textarea",
|
|
985
|
+
className: cn(
|
|
986
|
+
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
|
|
987
|
+
className
|
|
988
|
+
),
|
|
989
|
+
...props
|
|
990
|
+
}
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
var ToggleGroupContext = React13.createContext({
|
|
994
|
+
size: "default",
|
|
995
|
+
variant: "default",
|
|
996
|
+
spacing: 0
|
|
997
|
+
});
|
|
998
|
+
function ToggleGroup({
|
|
999
|
+
className,
|
|
1000
|
+
variant,
|
|
1001
|
+
size,
|
|
1002
|
+
spacing = 0,
|
|
1003
|
+
children,
|
|
1004
|
+
...props
|
|
1005
|
+
}) {
|
|
1006
|
+
return /* @__PURE__ */ jsx(
|
|
1007
|
+
ToggleGroup$1.Root,
|
|
1008
|
+
{
|
|
1009
|
+
"data-slot": "toggle-group",
|
|
1010
|
+
"data-variant": variant,
|
|
1011
|
+
"data-size": size,
|
|
1012
|
+
"data-spacing": spacing,
|
|
1013
|
+
style: { "--gap": spacing },
|
|
1014
|
+
className: cn(
|
|
1015
|
+
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
|
|
1016
|
+
className
|
|
1017
|
+
),
|
|
1018
|
+
...props,
|
|
1019
|
+
children: /* @__PURE__ */ jsx(ToggleGroupContext.Provider, { value: { variant, size, spacing }, children })
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
function ToggleGroupItem({
|
|
1024
|
+
className,
|
|
1025
|
+
children,
|
|
1026
|
+
variant,
|
|
1027
|
+
size,
|
|
1028
|
+
...props
|
|
1029
|
+
}) {
|
|
1030
|
+
const context = React13.useContext(ToggleGroupContext);
|
|
1031
|
+
return /* @__PURE__ */ jsx(
|
|
1032
|
+
ToggleGroup$1.Item,
|
|
1033
|
+
{
|
|
1034
|
+
"data-slot": "toggle-group-item",
|
|
1035
|
+
"data-variant": context.variant || variant,
|
|
1036
|
+
"data-size": context.size || size,
|
|
1037
|
+
"data-spacing": context.spacing,
|
|
1038
|
+
className: cn(
|
|
1039
|
+
toggleVariants({
|
|
1040
|
+
variant: context.variant || variant,
|
|
1041
|
+
size: context.size || size
|
|
1042
|
+
}),
|
|
1043
|
+
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
|
1044
|
+
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
|
1045
|
+
className
|
|
1046
|
+
),
|
|
1047
|
+
...props,
|
|
1048
|
+
children
|
|
1049
|
+
}
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
function Dialog({
|
|
1053
|
+
...props
|
|
1054
|
+
}) {
|
|
1055
|
+
return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
|
|
1056
|
+
}
|
|
1057
|
+
function DialogPortal({
|
|
1058
|
+
...props
|
|
1059
|
+
}) {
|
|
1060
|
+
return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "dialog-portal", ...props });
|
|
1061
|
+
}
|
|
1062
|
+
function DialogOverlay({
|
|
1063
|
+
className,
|
|
1064
|
+
...props
|
|
1065
|
+
}) {
|
|
1066
|
+
return /* @__PURE__ */ jsx(
|
|
1067
|
+
Dialog$1.Overlay,
|
|
1068
|
+
{
|
|
1069
|
+
"data-slot": "dialog-overlay",
|
|
1070
|
+
className: cn(
|
|
1071
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
1072
|
+
className
|
|
1073
|
+
),
|
|
1074
|
+
...props
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
function DialogContent({
|
|
1079
|
+
className,
|
|
1080
|
+
children,
|
|
1081
|
+
showCloseButton = true,
|
|
1082
|
+
...props
|
|
1083
|
+
}) {
|
|
1084
|
+
return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
1085
|
+
/* @__PURE__ */ jsx(DialogOverlay, {}),
|
|
1086
|
+
/* @__PURE__ */ jsxs(
|
|
1087
|
+
Dialog$1.Content,
|
|
1088
|
+
{
|
|
1089
|
+
"data-slot": "dialog-content",
|
|
1090
|
+
className: cn(
|
|
1091
|
+
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none 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 sm:max-w-lg",
|
|
1092
|
+
className
|
|
1093
|
+
),
|
|
1094
|
+
...props,
|
|
1095
|
+
children: [
|
|
1096
|
+
children,
|
|
1097
|
+
showCloseButton && /* @__PURE__ */ jsxs(
|
|
1098
|
+
Dialog$1.Close,
|
|
1099
|
+
{
|
|
1100
|
+
"data-slot": "dialog-close",
|
|
1101
|
+
className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1102
|
+
children: [
|
|
1103
|
+
/* @__PURE__ */ jsx(XIcon, {}),
|
|
1104
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
1105
|
+
]
|
|
1106
|
+
}
|
|
1107
|
+
)
|
|
1108
|
+
]
|
|
1109
|
+
}
|
|
1110
|
+
)
|
|
1111
|
+
] });
|
|
1112
|
+
}
|
|
1113
|
+
function DialogHeader({ className, ...props }) {
|
|
1114
|
+
return /* @__PURE__ */ jsx(
|
|
1115
|
+
"div",
|
|
1116
|
+
{
|
|
1117
|
+
"data-slot": "dialog-header",
|
|
1118
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
1119
|
+
...props
|
|
1120
|
+
}
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
function DialogFooter({
|
|
1124
|
+
className,
|
|
1125
|
+
showCloseButton = false,
|
|
1126
|
+
children,
|
|
1127
|
+
...props
|
|
1128
|
+
}) {
|
|
1129
|
+
return /* @__PURE__ */ jsxs(
|
|
1130
|
+
"div",
|
|
1131
|
+
{
|
|
1132
|
+
"data-slot": "dialog-footer",
|
|
1133
|
+
className: cn(
|
|
1134
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
1135
|
+
className
|
|
1136
|
+
),
|
|
1137
|
+
...props,
|
|
1138
|
+
children: [
|
|
1139
|
+
children,
|
|
1140
|
+
showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Close" }) })
|
|
1141
|
+
]
|
|
1142
|
+
}
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
function DialogTitle({
|
|
1146
|
+
className,
|
|
1147
|
+
...props
|
|
1148
|
+
}) {
|
|
1149
|
+
return /* @__PURE__ */ jsx(
|
|
1150
|
+
Dialog$1.Title,
|
|
1151
|
+
{
|
|
1152
|
+
"data-slot": "dialog-title",
|
|
1153
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
1154
|
+
...props
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
function DialogDescription({
|
|
1159
|
+
className,
|
|
1160
|
+
...props
|
|
1161
|
+
}) {
|
|
1162
|
+
return /* @__PURE__ */ jsx(
|
|
1163
|
+
Dialog$1.Description,
|
|
1164
|
+
{
|
|
1165
|
+
"data-slot": "dialog-description",
|
|
1166
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
1167
|
+
...props
|
|
1168
|
+
}
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
function ResponsivePanel({
|
|
1172
|
+
open,
|
|
1173
|
+
onOpenChange,
|
|
1174
|
+
title,
|
|
1175
|
+
description,
|
|
1176
|
+
desktop = "dialog",
|
|
1177
|
+
hideHeader,
|
|
1178
|
+
footer,
|
|
1179
|
+
className,
|
|
1180
|
+
children
|
|
1181
|
+
}) {
|
|
1182
|
+
const isDesktop = useIsDesktop();
|
|
1183
|
+
const headerClass = hideHeader ? "sr-only" : void 0;
|
|
1184
|
+
if (isDesktop && desktop === "dialog") {
|
|
1185
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: cn("sm:max-w-lg", className), children: [
|
|
1186
|
+
/* @__PURE__ */ jsxs(DialogHeader, { className: headerClass, children: [
|
|
1187
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: title }),
|
|
1188
|
+
description ? /* @__PURE__ */ jsx(DialogDescription, { children: description }) : null
|
|
1189
|
+
] }),
|
|
1190
|
+
children,
|
|
1191
|
+
footer ? /* @__PURE__ */ jsx(DialogFooter, { children: footer }) : null
|
|
1192
|
+
] }) });
|
|
1193
|
+
}
|
|
1194
|
+
if (isDesktop) {
|
|
1195
|
+
return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(SheetContent, { side: desktop === "sheet-left" ? "left" : "right", className: cn("w-full gap-0 sm:max-w-md", className), children: [
|
|
1196
|
+
/* @__PURE__ */ jsxs(SheetHeader, { className: headerClass, children: [
|
|
1197
|
+
/* @__PURE__ */ jsx(SheetTitle, { children: title }),
|
|
1198
|
+
description ? /* @__PURE__ */ jsx(SheetDescription, { children: description }) : null
|
|
1199
|
+
] }),
|
|
1200
|
+
/* @__PURE__ */ jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children }) }),
|
|
1201
|
+
footer ? /* @__PURE__ */ jsx(SheetFooter, { children: footer }) : null
|
|
1202
|
+
] }) });
|
|
1203
|
+
}
|
|
1204
|
+
return /* @__PURE__ */ jsx(Drawer, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DrawerContent, { className: cn("max-h-[92svh]", className), children: [
|
|
1205
|
+
/* @__PURE__ */ jsxs(DrawerHeader, { className: cn("text-left", headerClass), children: [
|
|
1206
|
+
/* @__PURE__ */ jsx(DrawerTitle, { children: title }),
|
|
1207
|
+
description ? /* @__PURE__ */ jsx(DrawerDescription, { children: description }) : null
|
|
1208
|
+
] }),
|
|
1209
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pb-4", children }),
|
|
1210
|
+
footer ? /* @__PURE__ */ jsx(DrawerFooter, { className: "pb-[max(1rem,env(safe-area-inset-bottom))]", children: footer }) : null
|
|
1211
|
+
] }) });
|
|
1212
|
+
}
|
|
1213
|
+
function HighlightPanel({ highlight, colors, focusNote, onColor, onNote, onDelete, onQuote, onCopy, onClose }) {
|
|
1214
|
+
const [note, setNote] = useState(highlight?.note ?? "");
|
|
1215
|
+
const saved = useRef(highlight?.note ?? "");
|
|
1216
|
+
const textRef = useRef(null);
|
|
1217
|
+
const id = highlight?.id;
|
|
1218
|
+
useEffect(() => {
|
|
1219
|
+
setNote(highlight?.note ?? "");
|
|
1220
|
+
saved.current = highlight?.note ?? "";
|
|
1221
|
+
}, [id]);
|
|
1222
|
+
useEffect(() => {
|
|
1223
|
+
if (note === saved.current) return;
|
|
1224
|
+
const t = setTimeout(() => {
|
|
1225
|
+
saved.current = note;
|
|
1226
|
+
onNote(note);
|
|
1227
|
+
}, 400);
|
|
1228
|
+
return () => clearTimeout(t);
|
|
1229
|
+
}, [note, onNote]);
|
|
1230
|
+
useEffect(() => {
|
|
1231
|
+
if (!focusNote || !id) return;
|
|
1232
|
+
const t = setTimeout(() => textRef.current?.focus(), 250);
|
|
1233
|
+
return () => clearTimeout(t);
|
|
1234
|
+
}, [focusNote, id]);
|
|
1235
|
+
const flush = () => {
|
|
1236
|
+
if (note !== saved.current) {
|
|
1237
|
+
saved.current = note;
|
|
1238
|
+
onNote(note);
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
const exact = highlight?.anchor.exact ?? "";
|
|
1242
|
+
return /* @__PURE__ */ jsx(
|
|
1243
|
+
ResponsivePanel,
|
|
1244
|
+
{
|
|
1245
|
+
open: !!highlight,
|
|
1246
|
+
onOpenChange: (open) => {
|
|
1247
|
+
if (!open) {
|
|
1248
|
+
flush();
|
|
1249
|
+
onClose();
|
|
1250
|
+
}
|
|
1251
|
+
},
|
|
1252
|
+
title: "Highlight",
|
|
1253
|
+
description: "Change the colour, add a note, or remove it.",
|
|
1254
|
+
footer: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
1255
|
+
onCopy && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", onClick: onCopy, children: [
|
|
1256
|
+
/* @__PURE__ */ jsx(CopyIcon, {}),
|
|
1257
|
+
" Copy"
|
|
1258
|
+
] }),
|
|
1259
|
+
onQuote && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", onClick: onQuote, children: [
|
|
1260
|
+
/* @__PURE__ */ jsx(QuoteIcon, {}),
|
|
1261
|
+
" To scratchpad"
|
|
1262
|
+
] }),
|
|
1263
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "destructive", onClick: onDelete, children: [
|
|
1264
|
+
/* @__PURE__ */ jsx(Trash2Icon, {}),
|
|
1265
|
+
" Delete"
|
|
1266
|
+
] })
|
|
1267
|
+
] }),
|
|
1268
|
+
children: highlight && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
1269
|
+
/* @__PURE__ */ jsx("blockquote", { className: cn("line-clamp-6 border-l-4 pl-3 font-serif text-sm text-muted-foreground", quoteBorderClass(highlight.color)), children: exact }),
|
|
1270
|
+
/* @__PURE__ */ jsx(
|
|
1271
|
+
ToggleGroup,
|
|
1272
|
+
{
|
|
1273
|
+
type: "single",
|
|
1274
|
+
variant: "outline",
|
|
1275
|
+
value: highlight.color,
|
|
1276
|
+
onValueChange: (v) => v && onColor(v),
|
|
1277
|
+
"aria-label": "Highlight colour",
|
|
1278
|
+
className: "justify-start",
|
|
1279
|
+
children: colors.map((c) => /* @__PURE__ */ jsx(ToggleGroupItem, { value: c, "aria-label": c, children: /* @__PURE__ */ jsx("span", { className: cn("size-4 rounded-full ring-1 ring-foreground/15", swatchClass(c)) }) }, c))
|
|
1280
|
+
}
|
|
1281
|
+
),
|
|
1282
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
1283
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `zr-note-${highlight.id}`, children: "Note" }),
|
|
1284
|
+
/* @__PURE__ */ jsx(
|
|
1285
|
+
Textarea,
|
|
1286
|
+
{
|
|
1287
|
+
id: `zr-note-${highlight.id}`,
|
|
1288
|
+
ref: textRef,
|
|
1289
|
+
rows: 4,
|
|
1290
|
+
value: note,
|
|
1291
|
+
placeholder: "Add a note\u2026",
|
|
1292
|
+
onChange: (e) => setNote(e.target.value),
|
|
1293
|
+
onBlur: flush
|
|
1294
|
+
}
|
|
1295
|
+
)
|
|
1296
|
+
] })
|
|
1297
|
+
] })
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
function DropdownMenu({
|
|
1302
|
+
...props
|
|
1303
|
+
}) {
|
|
1304
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
|
|
1305
|
+
}
|
|
1306
|
+
function DropdownMenuTrigger({
|
|
1307
|
+
...props
|
|
1308
|
+
}) {
|
|
1309
|
+
return /* @__PURE__ */ jsx(
|
|
1310
|
+
DropdownMenu$1.Trigger,
|
|
1311
|
+
{
|
|
1312
|
+
"data-slot": "dropdown-menu-trigger",
|
|
1313
|
+
...props
|
|
1314
|
+
}
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
function DropdownMenuContent({
|
|
1318
|
+
className,
|
|
1319
|
+
sideOffset = 4,
|
|
1320
|
+
...props
|
|
1321
|
+
}) {
|
|
1322
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { children: /* @__PURE__ */ jsx(
|
|
1323
|
+
DropdownMenu$1.Content,
|
|
1324
|
+
{
|
|
1325
|
+
"data-slot": "dropdown-menu-content",
|
|
1326
|
+
sideOffset,
|
|
1327
|
+
className: cn(
|
|
1328
|
+
"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",
|
|
1329
|
+
className
|
|
1330
|
+
),
|
|
1331
|
+
...props
|
|
1332
|
+
}
|
|
1333
|
+
) });
|
|
1334
|
+
}
|
|
1335
|
+
function DropdownMenuItem({
|
|
1336
|
+
className,
|
|
1337
|
+
inset,
|
|
1338
|
+
variant = "default",
|
|
1339
|
+
...props
|
|
1340
|
+
}) {
|
|
1341
|
+
return /* @__PURE__ */ jsx(
|
|
1342
|
+
DropdownMenu$1.Item,
|
|
1343
|
+
{
|
|
1344
|
+
"data-slot": "dropdown-menu-item",
|
|
1345
|
+
"data-inset": inset,
|
|
1346
|
+
"data-variant": variant,
|
|
1347
|
+
className: cn(
|
|
1348
|
+
"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!",
|
|
1349
|
+
className
|
|
1350
|
+
),
|
|
1351
|
+
...props
|
|
1352
|
+
}
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
function DropdownMenuSeparator({
|
|
1356
|
+
className,
|
|
1357
|
+
...props
|
|
1358
|
+
}) {
|
|
1359
|
+
return /* @__PURE__ */ jsx(
|
|
1360
|
+
DropdownMenu$1.Separator,
|
|
1361
|
+
{
|
|
1362
|
+
"data-slot": "dropdown-menu-separator",
|
|
1363
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
1364
|
+
...props
|
|
1365
|
+
}
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
function Skeleton({ className, ...props }) {
|
|
1369
|
+
return /* @__PURE__ */ jsx(
|
|
1370
|
+
"div",
|
|
1371
|
+
{
|
|
1372
|
+
"data-slot": "skeleton",
|
|
1373
|
+
className: cn("animate-pulse rounded-md bg-accent", className),
|
|
1374
|
+
...props
|
|
1375
|
+
}
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// src/react/download.ts
|
|
1380
|
+
function downloadFile(filename, content, type = "application/json") {
|
|
1381
|
+
const url = URL.createObjectURL(new Blob([content], { type }));
|
|
1382
|
+
const a = document.createElement("a");
|
|
1383
|
+
a.href = url;
|
|
1384
|
+
a.download = filename;
|
|
1385
|
+
document.body.appendChild(a);
|
|
1386
|
+
a.click();
|
|
1387
|
+
a.remove();
|
|
1388
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
1389
|
+
}
|
|
1390
|
+
async function copyText(text) {
|
|
1391
|
+
try {
|
|
1392
|
+
await navigator.clipboard.writeText(text);
|
|
1393
|
+
return true;
|
|
1394
|
+
} catch {
|
|
1395
|
+
return false;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
var ScratchpadEditor = lazy(() => import('zreader/editor').then((m) => ({ default: m.ScratchpadEditor })));
|
|
1399
|
+
var SCOPE_LABEL = { chapter: "Chapter", book: "Book", global: "All books" };
|
|
1400
|
+
function scopeFor(kind, bookId, chapterId) {
|
|
1401
|
+
if (kind === "global") return { kind };
|
|
1402
|
+
if (kind === "book") return { kind, bookId };
|
|
1403
|
+
return { kind, bookId, chapterId };
|
|
1404
|
+
}
|
|
1405
|
+
function scopeKey(scope) {
|
|
1406
|
+
return scope.kind === "global" ? "global" : scope.kind === "book" ? `book:${scope.bookId}` : `chapter:${scope.bookId}:${scope.chapterId}`;
|
|
1407
|
+
}
|
|
1408
|
+
var EditorBoundary = class extends Component {
|
|
1409
|
+
state = { failed: false };
|
|
1410
|
+
static getDerivedStateFromError() {
|
|
1411
|
+
return { failed: true };
|
|
1412
|
+
}
|
|
1413
|
+
render() {
|
|
1414
|
+
return this.state.failed ? this.props.fallback : this.props.children;
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
function PlainEditor({ scope, onFocus }) {
|
|
1418
|
+
const { store } = useReader();
|
|
1419
|
+
const pad = usePad(scope);
|
|
1420
|
+
return /* @__PURE__ */ jsx(
|
|
1421
|
+
Textarea,
|
|
1422
|
+
{
|
|
1423
|
+
value: pad.markdown,
|
|
1424
|
+
onChange: (e) => store.savePad(scope, e.target.value),
|
|
1425
|
+
onFocus,
|
|
1426
|
+
placeholder: "Write in markdown. - [ ] makes a checklist item.",
|
|
1427
|
+
className: "h-full min-h-48 flex-1 resize-none font-mono text-sm",
|
|
1428
|
+
"aria-label": "Scratchpad (markdown)"
|
|
1429
|
+
}
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
function ScratchpadPanel({ bookId, chapterId, kind, onKindChange, onClose, onEditorFocus, title }) {
|
|
1433
|
+
const { store, options } = useReader();
|
|
1434
|
+
const scope = useMemo(() => scopeFor(kind, bookId, chapterId), [kind, bookId, chapterId]);
|
|
1435
|
+
const pad = usePad(scope);
|
|
1436
|
+
const scopes = options.scratchpad.scopes;
|
|
1437
|
+
const stem = `${title ?? bookId}-${kind}-notes`.replace(/[^a-z0-9-]+/gi, "-").toLowerCase();
|
|
1438
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
1439
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-2", children: [
|
|
1440
|
+
scopes.length > 1 ? /* @__PURE__ */ jsx(Tabs, { value: kind, onValueChange: (v) => onKindChange(v), className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(TabsList, { className: "w-full", children: scopes.map((s) => /* @__PURE__ */ jsx(TabsTrigger, { value: s, children: SCOPE_LABEL[s] }, s)) }) }) : /* @__PURE__ */ jsx("h2", { className: "flex-1 text-sm font-medium", children: "Scratchpad" }),
|
|
1441
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
1442
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Scratchpad options", children: /* @__PURE__ */ jsx(EllipsisIcon, {}) }) }),
|
|
1443
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
1444
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => downloadFile(`${stem}.md`, pad.markdown, "text/markdown"), disabled: !pad.markdown.trim(), children: [
|
|
1445
|
+
/* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
1446
|
+
" Download .md"
|
|
1447
|
+
] }),
|
|
1448
|
+
/* @__PURE__ */ jsxs(
|
|
1449
|
+
DropdownMenuItem,
|
|
1450
|
+
{
|
|
1451
|
+
onSelect: async () => toast(await copyText(pad.markdown) ? "Markdown copied" : "Could not copy"),
|
|
1452
|
+
disabled: !pad.markdown.trim(),
|
|
1453
|
+
children: [
|
|
1454
|
+
/* @__PURE__ */ jsx(CopyIcon, {}),
|
|
1455
|
+
" Copy markdown"
|
|
1456
|
+
]
|
|
1457
|
+
}
|
|
1458
|
+
),
|
|
1459
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1460
|
+
/* @__PURE__ */ jsxs(
|
|
1461
|
+
DropdownMenuItem,
|
|
1462
|
+
{
|
|
1463
|
+
variant: "destructive",
|
|
1464
|
+
disabled: !pad.markdown.trim(),
|
|
1465
|
+
onSelect: () => {
|
|
1466
|
+
const previous = pad;
|
|
1467
|
+
store.clearPad(scope);
|
|
1468
|
+
toast("Scratchpad cleared", {
|
|
1469
|
+
action: { label: "Undo", onClick: () => store.savePad(scope, previous.markdown, previous.json) }
|
|
1470
|
+
});
|
|
1471
|
+
},
|
|
1472
|
+
children: [
|
|
1473
|
+
/* @__PURE__ */ jsx(Trash2Icon, {}),
|
|
1474
|
+
" Clear"
|
|
1475
|
+
]
|
|
1476
|
+
}
|
|
1477
|
+
)
|
|
1478
|
+
] })
|
|
1479
|
+
] }),
|
|
1480
|
+
onClose && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Close scratchpad", onClick: onClose, children: /* @__PURE__ */ jsx(XIcon, {}) })
|
|
1481
|
+
] }),
|
|
1482
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx(EditorBoundary, { fallback: /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col p-3", children: /* @__PURE__ */ jsx(PlainEditor, { scope, onFocus: onEditorFocus }) }), children: /* @__PURE__ */ jsx(
|
|
1483
|
+
Suspense,
|
|
1484
|
+
{
|
|
1485
|
+
fallback: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 p-4", children: [
|
|
1486
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-full" }),
|
|
1487
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-3/4" }),
|
|
1488
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/2" })
|
|
1489
|
+
] }),
|
|
1490
|
+
children: /* @__PURE__ */ jsx(ScratchpadEditor, { scope, onFocus: onEditorFocus }, scopeKey(scope))
|
|
1491
|
+
}
|
|
1492
|
+
) }, scopeKey(scope)) })
|
|
1493
|
+
] });
|
|
1494
|
+
}
|
|
1495
|
+
function Popover({
|
|
1496
|
+
...props
|
|
1497
|
+
}) {
|
|
1498
|
+
return /* @__PURE__ */ jsx(Popover$1.Root, { "data-slot": "popover", ...props });
|
|
1499
|
+
}
|
|
1500
|
+
function PopoverContent({
|
|
1501
|
+
className,
|
|
1502
|
+
align = "center",
|
|
1503
|
+
sideOffset = 4,
|
|
1504
|
+
...props
|
|
1505
|
+
}) {
|
|
1506
|
+
return /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(
|
|
1507
|
+
Popover$1.Content,
|
|
1508
|
+
{
|
|
1509
|
+
"data-slot": "popover-content",
|
|
1510
|
+
align,
|
|
1511
|
+
sideOffset,
|
|
1512
|
+
className: cn(
|
|
1513
|
+
"z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden 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",
|
|
1514
|
+
className
|
|
1515
|
+
),
|
|
1516
|
+
...props
|
|
1517
|
+
}
|
|
1518
|
+
) });
|
|
1519
|
+
}
|
|
1520
|
+
function PopoverAnchor({
|
|
1521
|
+
...props
|
|
1522
|
+
}) {
|
|
1523
|
+
return /* @__PURE__ */ jsx(Popover$1.Anchor, { "data-slot": "popover-anchor", ...props });
|
|
1524
|
+
}
|
|
1525
|
+
function Separator2({
|
|
1526
|
+
className,
|
|
1527
|
+
orientation = "horizontal",
|
|
1528
|
+
decorative = true,
|
|
1529
|
+
...props
|
|
1530
|
+
}) {
|
|
1531
|
+
return /* @__PURE__ */ jsx(
|
|
1532
|
+
Separator.Root,
|
|
1533
|
+
{
|
|
1534
|
+
"data-slot": "separator",
|
|
1535
|
+
decorative,
|
|
1536
|
+
orientation,
|
|
1537
|
+
className: cn(
|
|
1538
|
+
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
1539
|
+
className
|
|
1540
|
+
),
|
|
1541
|
+
...props
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
function Action({ label, onSelect, children }) {
|
|
1546
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1547
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": label, onClick: onSelect, children }) }),
|
|
1548
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: label })
|
|
1549
|
+
] });
|
|
1550
|
+
}
|
|
1551
|
+
function SelectionToolbar({ selection, colors, onHighlight, onNote, onSentence, onQuote, onCopy, onDismiss }) {
|
|
1552
|
+
const isDesktop = useIsDesktop();
|
|
1553
|
+
const last = useRef(null);
|
|
1554
|
+
if (selection) last.current = selection;
|
|
1555
|
+
const current = () => last.current;
|
|
1556
|
+
const anchorRef = useMemo(
|
|
1557
|
+
() => ({
|
|
1558
|
+
current: {
|
|
1559
|
+
getBoundingClientRect: () => {
|
|
1560
|
+
const r = last.current?.rect;
|
|
1561
|
+
return DOMRect.fromRect(r ? { x: r.left, y: r.top, width: r.width, height: r.height } : void 0);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}),
|
|
1565
|
+
[]
|
|
1566
|
+
);
|
|
1567
|
+
const keepSelection = (e) => e.preventDefault();
|
|
1568
|
+
const actions = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", role: "toolbar", "aria-label": "Selection actions", onPointerDown: keepSelection, onMouseDown: keepSelection, children: [
|
|
1569
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-1", children: colors.map((c) => /* @__PURE__ */ jsx(
|
|
1570
|
+
Button,
|
|
1571
|
+
{
|
|
1572
|
+
type: "button",
|
|
1573
|
+
variant: "ghost",
|
|
1574
|
+
size: "icon-sm",
|
|
1575
|
+
"aria-label": `Highlight ${c}`,
|
|
1576
|
+
onClick: () => current() && onHighlight(current().span, c),
|
|
1577
|
+
children: /* @__PURE__ */ jsx("span", { className: cn("size-5 rounded-full ring-1 ring-foreground/15", swatchClass(c)) })
|
|
1578
|
+
},
|
|
1579
|
+
c
|
|
1580
|
+
)) }),
|
|
1581
|
+
/* @__PURE__ */ jsx(Separator2, { orientation: "vertical", className: "mx-1 h-6" }),
|
|
1582
|
+
/* @__PURE__ */ jsx(Action, { label: "Whole sentence", onSelect: () => current() && onSentence(current().span), children: /* @__PURE__ */ jsx(TextSelectIcon, {}) }),
|
|
1583
|
+
/* @__PURE__ */ jsx(Action, { label: "Add note", onSelect: () => current() && onNote(current().span), children: /* @__PURE__ */ jsx(MessageSquarePlusIcon, {}) }),
|
|
1584
|
+
onQuote && /* @__PURE__ */ jsx(Action, { label: "Quote to scratchpad", onSelect: () => current() && onQuote(current().span, current().text), children: /* @__PURE__ */ jsx(QuoteIcon, {}) }),
|
|
1585
|
+
onCopy && /* @__PURE__ */ jsx(Action, { label: "Copy", onSelect: () => current() && onCopy(current().text), children: /* @__PURE__ */ jsx(CopyIcon, {}) }),
|
|
1586
|
+
!isDesktop && /* @__PURE__ */ jsx(Action, { label: "Dismiss", onSelect: onDismiss, children: /* @__PURE__ */ jsx(XIcon, {}) })
|
|
1587
|
+
] });
|
|
1588
|
+
if (isDesktop) {
|
|
1589
|
+
return /* @__PURE__ */ jsxs(Popover, { open: !!selection, modal: false, children: [
|
|
1590
|
+
/* @__PURE__ */ jsx(PopoverAnchor, { virtualRef: anchorRef }),
|
|
1591
|
+
/* @__PURE__ */ jsx(
|
|
1592
|
+
PopoverContent,
|
|
1593
|
+
{
|
|
1594
|
+
side: "top",
|
|
1595
|
+
align: "center",
|
|
1596
|
+
sideOffset: 10,
|
|
1597
|
+
className: "w-auto p-1",
|
|
1598
|
+
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
1599
|
+
onCloseAutoFocus: (e) => e.preventDefault(),
|
|
1600
|
+
onEscapeKeyDown: onDismiss,
|
|
1601
|
+
children: actions
|
|
1602
|
+
}
|
|
1603
|
+
)
|
|
1604
|
+
] });
|
|
1605
|
+
}
|
|
1606
|
+
if (!selection) return null;
|
|
1607
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed inset-x-2 bottom-[max(0.75rem,env(safe-area-inset-bottom))] z-40 flex justify-center animate-in fade-in-0 slide-in-from-bottom-2", children: /* @__PURE__ */ jsx("div", { className: "max-w-full overflow-x-auto rounded-xl border bg-popover p-1 text-popover-foreground shadow-lg", children: actions }) });
|
|
1608
|
+
}
|
|
1609
|
+
var alertVariants = cva(
|
|
1610
|
+
"relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
|
1611
|
+
{
|
|
1612
|
+
variants: {
|
|
1613
|
+
variant: {
|
|
1614
|
+
default: "bg-card text-card-foreground",
|
|
1615
|
+
destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current"
|
|
1616
|
+
}
|
|
1617
|
+
},
|
|
1618
|
+
defaultVariants: {
|
|
1619
|
+
variant: "default"
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
);
|
|
1623
|
+
function Alert({
|
|
1624
|
+
className,
|
|
1625
|
+
variant,
|
|
1626
|
+
...props
|
|
1627
|
+
}) {
|
|
1628
|
+
return /* @__PURE__ */ jsx(
|
|
1629
|
+
"div",
|
|
1630
|
+
{
|
|
1631
|
+
"data-slot": "alert",
|
|
1632
|
+
role: "alert",
|
|
1633
|
+
className: cn(alertVariants({ variant }), className),
|
|
1634
|
+
...props
|
|
1635
|
+
}
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
function AlertTitle({ className, ...props }) {
|
|
1639
|
+
return /* @__PURE__ */ jsx(
|
|
1640
|
+
"div",
|
|
1641
|
+
{
|
|
1642
|
+
"data-slot": "alert-title",
|
|
1643
|
+
className: cn(
|
|
1644
|
+
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
|
1645
|
+
className
|
|
1646
|
+
),
|
|
1647
|
+
...props
|
|
1648
|
+
}
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
function AlertDescription({
|
|
1652
|
+
className,
|
|
1653
|
+
...props
|
|
1654
|
+
}) {
|
|
1655
|
+
return /* @__PURE__ */ jsx(
|
|
1656
|
+
"div",
|
|
1657
|
+
{
|
|
1658
|
+
"data-slot": "alert-description",
|
|
1659
|
+
className: cn(
|
|
1660
|
+
"col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
|
|
1661
|
+
className
|
|
1662
|
+
),
|
|
1663
|
+
...props
|
|
1664
|
+
}
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
function Switch({
|
|
1668
|
+
className,
|
|
1669
|
+
size = "default",
|
|
1670
|
+
...props
|
|
1671
|
+
}) {
|
|
1672
|
+
return /* @__PURE__ */ jsx(
|
|
1673
|
+
Switch$1.Root,
|
|
1674
|
+
{
|
|
1675
|
+
"data-slot": "switch",
|
|
1676
|
+
"data-size": size,
|
|
1677
|
+
className: cn(
|
|
1678
|
+
"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
|
|
1679
|
+
className
|
|
1680
|
+
),
|
|
1681
|
+
...props,
|
|
1682
|
+
children: /* @__PURE__ */ jsx(
|
|
1683
|
+
Switch$1.Thumb,
|
|
1684
|
+
{
|
|
1685
|
+
"data-slot": "switch-thumb",
|
|
1686
|
+
className: cn(
|
|
1687
|
+
"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
|
|
1688
|
+
)
|
|
1689
|
+
}
|
|
1690
|
+
)
|
|
1691
|
+
}
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
var THEME_LABEL = { system: "Auto", light: "Light", sepia: "Sepia", dark: "Dark" };
|
|
1695
|
+
var SIZE_LABEL = { sm: "S", base: "M", lg: "L", xl: "XL", "2xl": "2XL" };
|
|
1696
|
+
var LEADING_LABEL = { tight: "Tight", normal: "Normal", relaxed: "Relaxed" };
|
|
1697
|
+
var MEASURE_LABEL = { narrow: "Narrow", normal: "Normal", wide: "Wide" };
|
|
1698
|
+
var FONT_LABEL = { serif: "Serif", sans: "Sans", mono: "Mono" };
|
|
1699
|
+
function Choice({
|
|
1700
|
+
label,
|
|
1701
|
+
value,
|
|
1702
|
+
values,
|
|
1703
|
+
labels,
|
|
1704
|
+
onChange
|
|
1705
|
+
}) {
|
|
1706
|
+
const id = useId();
|
|
1707
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
1708
|
+
/* @__PURE__ */ jsx(Label, { id, children: label }),
|
|
1709
|
+
/* @__PURE__ */ jsx(
|
|
1710
|
+
ToggleGroup,
|
|
1711
|
+
{
|
|
1712
|
+
type: "single",
|
|
1713
|
+
variant: "outline",
|
|
1714
|
+
value,
|
|
1715
|
+
onValueChange: (v) => v && onChange(v),
|
|
1716
|
+
"aria-labelledby": id,
|
|
1717
|
+
className: "w-full",
|
|
1718
|
+
children: values.map((v) => /* @__PURE__ */ jsx(ToggleGroupItem, { value: v, className: "flex-1", children: labels[v] }, v))
|
|
1719
|
+
}
|
|
1720
|
+
)
|
|
1721
|
+
] });
|
|
1722
|
+
}
|
|
1723
|
+
function SettingsPanel({ open, onOpenChange, book }) {
|
|
1724
|
+
const { store, options } = useReader();
|
|
1725
|
+
const [settings, update] = useSettings();
|
|
1726
|
+
const { theme, setTheme } = useTheme();
|
|
1727
|
+
const hydrated = useHydrated();
|
|
1728
|
+
const highlights = useHighlights(book?.id ?? "");
|
|
1729
|
+
const fileRef = useRef(null);
|
|
1730
|
+
const [replace, setReplace] = useState(false);
|
|
1731
|
+
const modeId = useId();
|
|
1732
|
+
const hlModeId = useId();
|
|
1733
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1734
|
+
const onImport = async (file) => {
|
|
1735
|
+
try {
|
|
1736
|
+
const res = importBackup(store, await file.text(), { mode: replace ? "replace" : "merge" });
|
|
1737
|
+
toast.success(`Imported ${res.imported} record${res.imported === 1 ? "" : "s"}`, {
|
|
1738
|
+
description: res.skipped ? `${res.skipped} skipped` : void 0
|
|
1739
|
+
});
|
|
1740
|
+
} catch (e) {
|
|
1741
|
+
toast.error("Import failed", { description: e.message });
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
return /* @__PURE__ */ jsx(ResponsivePanel, { open, onOpenChange, title: "Reading settings", desktop: "sheet-right", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5", children: [
|
|
1745
|
+
hydrated && /* @__PURE__ */ jsx(
|
|
1746
|
+
Choice,
|
|
1747
|
+
{
|
|
1748
|
+
label: "Theme",
|
|
1749
|
+
value: theme ?? options.defaultTheme,
|
|
1750
|
+
values: options.themes,
|
|
1751
|
+
labels: THEME_LABEL,
|
|
1752
|
+
onChange: setTheme
|
|
1753
|
+
}
|
|
1754
|
+
),
|
|
1755
|
+
/* @__PURE__ */ jsx(Choice, { label: "Text size", value: settings.textSize, values: TEXT_SIZES, labels: SIZE_LABEL, onChange: (textSize) => update({ textSize }) }),
|
|
1756
|
+
/* @__PURE__ */ jsx(Choice, { label: "Line spacing", value: settings.leading, values: LEADINGS, labels: LEADING_LABEL, onChange: (leading) => update({ leading }) }),
|
|
1757
|
+
/* @__PURE__ */ jsx(Choice, { label: "Line width", value: settings.measure, values: MEASURES, labels: MEASURE_LABEL, onChange: (measure) => update({ measure }) }),
|
|
1758
|
+
/* @__PURE__ */ jsx(Choice, { label: "Font", value: settings.fontFamily, values: FONT_FAMILIES, labels: FONT_LABEL, onChange: (fontFamily) => update({ fontFamily }) }),
|
|
1759
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 rounded-lg border p-3", children: [
|
|
1760
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
|
|
1761
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: hlModeId, children: "Highlight mode" }),
|
|
1762
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Tap any sentence to highlight it." })
|
|
1763
|
+
] }),
|
|
1764
|
+
/* @__PURE__ */ jsx(Switch, { id: hlModeId, checked: settings.highlightMode, onCheckedChange: (highlightMode) => update({ highlightMode }) })
|
|
1765
|
+
] }),
|
|
1766
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: "self-start", onClick: () => store.resetSettings(), children: [
|
|
1767
|
+
/* @__PURE__ */ jsx(RotateCcwIcon, {}),
|
|
1768
|
+
" Reset reading settings"
|
|
1769
|
+
] }),
|
|
1770
|
+
/* @__PURE__ */ jsx(Separator2, {}),
|
|
1771
|
+
/* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-3", "aria-labelledby": `${modeId}-title`, children: [
|
|
1772
|
+
/* @__PURE__ */ jsx("h3", { id: `${modeId}-title`, className: "text-sm font-medium", children: "Your data" }),
|
|
1773
|
+
options.backupReminder && /* @__PURE__ */ jsxs(Alert, { children: [
|
|
1774
|
+
/* @__PURE__ */ jsx(HardDriveIcon, {}),
|
|
1775
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Stored only in this browser" }),
|
|
1776
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: "Highlights, notes, bookmarks and scratchpads are removed if site data is cleared. Export a backup now and then." })
|
|
1777
|
+
] }),
|
|
1778
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
1779
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", onClick: () => downloadFile(`reader-backup-${stamp}.json`, JSON.stringify(exportBackup(store), null, 2)), children: [
|
|
1780
|
+
/* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
1781
|
+
" Export backup"
|
|
1782
|
+
] }),
|
|
1783
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", onClick: () => fileRef.current?.click(), children: [
|
|
1784
|
+
/* @__PURE__ */ jsx(UploadIcon, {}),
|
|
1785
|
+
" Import backup"
|
|
1786
|
+
] }),
|
|
1787
|
+
book && highlights.length > 0 && /* @__PURE__ */ jsxs(
|
|
1788
|
+
Button,
|
|
1789
|
+
{
|
|
1790
|
+
type: "button",
|
|
1791
|
+
variant: "outline",
|
|
1792
|
+
onClick: () => {
|
|
1793
|
+
const titles = new Map(book.chapters.map((c) => [c.id, c.title]));
|
|
1794
|
+
downloadFile(`${book.id}-highlights.md`, highlightsToMarkdown(book.title, highlights, (id) => titles.get(id) ?? id), "text/markdown");
|
|
1795
|
+
},
|
|
1796
|
+
children: [
|
|
1797
|
+
/* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
1798
|
+
" Highlights (.md)"
|
|
1799
|
+
]
|
|
1800
|
+
}
|
|
1801
|
+
)
|
|
1802
|
+
] }),
|
|
1803
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1804
|
+
/* @__PURE__ */ jsx(Switch, { id: modeId, checked: replace, onCheckedChange: setReplace }),
|
|
1805
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: modeId, className: "font-normal", children: "Replace existing data on import" })
|
|
1806
|
+
] }),
|
|
1807
|
+
/* @__PURE__ */ jsx(
|
|
1808
|
+
"input",
|
|
1809
|
+
{
|
|
1810
|
+
ref: fileRef,
|
|
1811
|
+
type: "file",
|
|
1812
|
+
accept: "application/json,.json",
|
|
1813
|
+
className: "hidden",
|
|
1814
|
+
onChange: (e) => {
|
|
1815
|
+
const f = e.target.files?.[0];
|
|
1816
|
+
if (f) void onImport(f);
|
|
1817
|
+
e.target.value = "";
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
),
|
|
1821
|
+
/* @__PURE__ */ jsx(
|
|
1822
|
+
Button,
|
|
1823
|
+
{
|
|
1824
|
+
type: "button",
|
|
1825
|
+
variant: "link",
|
|
1826
|
+
className: "self-start px-0",
|
|
1827
|
+
onClick: async () => await requestPersistentStorage() ? toast.success("This browser will keep your reader data.") : toast.info("The browser did not grant persistent storage."),
|
|
1828
|
+
children: "Ask the browser to keep this data"
|
|
1829
|
+
}
|
|
1830
|
+
)
|
|
1831
|
+
] })
|
|
1832
|
+
] }) });
|
|
1833
|
+
}
|
|
1834
|
+
var INTERACTIVE = `a,button,input,textarea,select,summary,label,video,audio,iframe,[contenteditable],[${IGNORE_ATTR}]`;
|
|
1835
|
+
function prefersReducedMotion() {
|
|
1836
|
+
return typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1837
|
+
}
|
|
1838
|
+
function createValueStore(initial) {
|
|
1839
|
+
let value = initial;
|
|
1840
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1841
|
+
return {
|
|
1842
|
+
get: () => value,
|
|
1843
|
+
set(v) {
|
|
1844
|
+
if (Math.abs(v - value) < 1e-3) return;
|
|
1845
|
+
value = v;
|
|
1846
|
+
listeners.forEach((l) => l());
|
|
1847
|
+
},
|
|
1848
|
+
subscribe(l) {
|
|
1849
|
+
listeners.add(l);
|
|
1850
|
+
return () => listeners.delete(l);
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
function useValueStore(store) {
|
|
1855
|
+
return useSyncExternalStore(store.subscribe, store.get, () => 0);
|
|
1856
|
+
}
|
|
1857
|
+
function useChapterEngine(opts) {
|
|
1858
|
+
const { bookId, chapterId, contentHash, root: rootEl, scroller: scrollEl = null, topOffset = 64 } = opts;
|
|
1859
|
+
const rootRef = useRef(rootEl);
|
|
1860
|
+
rootRef.current = rootEl;
|
|
1861
|
+
const { store, options } = useReader();
|
|
1862
|
+
const [settings] = useSettings();
|
|
1863
|
+
const highlights = useHighlights(bookId, chapterId);
|
|
1864
|
+
const indexRef = useRef(null);
|
|
1865
|
+
const [version, setVersion] = useState(0);
|
|
1866
|
+
const [selection, setSelection] = useState(null);
|
|
1867
|
+
const [activeId, setActiveId] = useState(null);
|
|
1868
|
+
const [resume, setResume] = useState(null);
|
|
1869
|
+
const [percentStore] = useState(() => createValueStore(0));
|
|
1870
|
+
const [domToc, setDomToc] = useState([]);
|
|
1871
|
+
const onTapRef = useRef(opts.onHighlightTap);
|
|
1872
|
+
onTapRef.current = opts.onHighlightTap;
|
|
1873
|
+
const scroller = useCallback(() => scrollEl, [scrollEl]);
|
|
1874
|
+
const viewportTop = useCallback(() => scroller()?.getBoundingClientRect().top ?? 0, [scroller]);
|
|
1875
|
+
const viewportHeight = useCallback(() => scroller()?.clientHeight ?? window.innerHeight, [scroller]);
|
|
1876
|
+
const scrollTop = useCallback(() => scroller()?.scrollTop ?? window.scrollY, [scroller]);
|
|
1877
|
+
const scrollToY = useCallback(
|
|
1878
|
+
(y, smooth = true) => {
|
|
1879
|
+
const opts2 = { top: Math.max(0, y), behavior: smooth && !prefersReducedMotion() ? "smooth" : "auto" };
|
|
1880
|
+
const el = scroller();
|
|
1881
|
+
if (el) el.scrollTo(opts2);
|
|
1882
|
+
else window.scrollTo(opts2);
|
|
1883
|
+
},
|
|
1884
|
+
[scroller]
|
|
1885
|
+
);
|
|
1886
|
+
const targetFor = useCallback(
|
|
1887
|
+
(rectTop, extra = 8) => scrollTop() + rectTop - viewportTop() - topOffset - extra,
|
|
1888
|
+
[scrollTop, viewportTop, topOffset]
|
|
1889
|
+
);
|
|
1890
|
+
useEffect(() => {
|
|
1891
|
+
const root = rootEl;
|
|
1892
|
+
if (!root) return;
|
|
1893
|
+
const rebuild = () => {
|
|
1894
|
+
ensureBlockIds(root);
|
|
1895
|
+
setDomToc((prev) => {
|
|
1896
|
+
const next = collectToc(root);
|
|
1897
|
+
return JSON.stringify(prev) === JSON.stringify(next) ? prev : next;
|
|
1898
|
+
});
|
|
1899
|
+
indexRef.current = buildTextIndex(root);
|
|
1900
|
+
setVersion((v) => v + 1);
|
|
1901
|
+
};
|
|
1902
|
+
rebuild();
|
|
1903
|
+
let t;
|
|
1904
|
+
const mo = new MutationObserver(() => {
|
|
1905
|
+
clearTimeout(t);
|
|
1906
|
+
t = setTimeout(rebuild, 60);
|
|
1907
|
+
});
|
|
1908
|
+
mo.observe(root, { childList: true, subtree: true, characterData: true });
|
|
1909
|
+
return () => {
|
|
1910
|
+
mo.disconnect();
|
|
1911
|
+
clearTimeout(t);
|
|
1912
|
+
};
|
|
1913
|
+
}, [rootEl, chapterId]);
|
|
1914
|
+
const getIndex = useCallback(() => {
|
|
1915
|
+
const root = rootRef.current;
|
|
1916
|
+
if (root && (!indexRef.current || indexRef.current.root !== root)) indexRef.current = buildTextIndex(root);
|
|
1917
|
+
return indexRef.current;
|
|
1918
|
+
}, []);
|
|
1919
|
+
const { resolved, orphaned } = useMemo(() => {
|
|
1920
|
+
const index = indexRef.current;
|
|
1921
|
+
const ok = [];
|
|
1922
|
+
const lost = [];
|
|
1923
|
+
if (index) {
|
|
1924
|
+
for (const h of highlights) {
|
|
1925
|
+
const span = resolveAnchor(index, h.anchor);
|
|
1926
|
+
const range = span ? spanToRange(index, span) : null;
|
|
1927
|
+
if (span && range) ok.push({ highlight: h, span, range });
|
|
1928
|
+
else lost.push(h);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
return { resolved: ok, orphaned: lost };
|
|
1932
|
+
}, [highlights, version]);
|
|
1933
|
+
const resolvedRef = useRef(resolved);
|
|
1934
|
+
resolvedRef.current = resolved;
|
|
1935
|
+
useEffect(() => {
|
|
1936
|
+
const index = indexRef.current;
|
|
1937
|
+
if (!index) return;
|
|
1938
|
+
for (const r of resolved) {
|
|
1939
|
+
const a = r.highlight.anchor;
|
|
1940
|
+
if (a.start === r.span.start && a.end === r.span.end) continue;
|
|
1941
|
+
const fresh = describeSpan(index, r.span, contentHash);
|
|
1942
|
+
if (fresh) store.updateHighlight(bookId, r.highlight.id, { anchor: fresh });
|
|
1943
|
+
}
|
|
1944
|
+
}, [resolved, bookId, contentHash, store]);
|
|
1945
|
+
useEffect(() => paintHighlights(resolved, { activeId }), [resolved, activeId]);
|
|
1946
|
+
useEffect(() => {
|
|
1947
|
+
let t;
|
|
1948
|
+
const update = () => {
|
|
1949
|
+
const index = getIndex();
|
|
1950
|
+
const sel = document.getSelection();
|
|
1951
|
+
const span = index ? selectionSpan(index, sel) : null;
|
|
1952
|
+
if (!index || !span || !sel || sel.rangeCount === 0) {
|
|
1953
|
+
setSelection(null);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
const r = sel.getRangeAt(0).getBoundingClientRect();
|
|
1957
|
+
setSelection({
|
|
1958
|
+
span,
|
|
1959
|
+
text: index.text.slice(span.start, span.end),
|
|
1960
|
+
rect: { top: r.top, left: r.left, bottom: r.bottom, right: r.right, width: r.width, height: r.height }
|
|
1961
|
+
});
|
|
1962
|
+
};
|
|
1963
|
+
const onChange = () => {
|
|
1964
|
+
clearTimeout(t);
|
|
1965
|
+
t = setTimeout(update, 120);
|
|
1966
|
+
};
|
|
1967
|
+
document.addEventListener("selectionchange", onChange);
|
|
1968
|
+
return () => {
|
|
1969
|
+
document.removeEventListener("selectionchange", onChange);
|
|
1970
|
+
clearTimeout(t);
|
|
1971
|
+
};
|
|
1972
|
+
}, [getIndex]);
|
|
1973
|
+
const clearSelection = useCallback(() => {
|
|
1974
|
+
document.getSelection()?.removeAllRanges();
|
|
1975
|
+
setSelection(null);
|
|
1976
|
+
}, []);
|
|
1977
|
+
const selectSpan = useCallback(
|
|
1978
|
+
(span) => {
|
|
1979
|
+
const index = getIndex();
|
|
1980
|
+
const range = index && spanToRange(index, span);
|
|
1981
|
+
const sel = document.getSelection();
|
|
1982
|
+
if (!range || !sel) return;
|
|
1983
|
+
sel.removeAllRanges();
|
|
1984
|
+
sel.addRange(range);
|
|
1985
|
+
},
|
|
1986
|
+
[getIndex]
|
|
1987
|
+
);
|
|
1988
|
+
const expandSelectionToSentences = useCallback(
|
|
1989
|
+
(span) => {
|
|
1990
|
+
const index = getIndex();
|
|
1991
|
+
const base = span ?? selection?.span;
|
|
1992
|
+
if (!index || !base) return;
|
|
1993
|
+
selectSpan(expandSpanToSentences(index, base, document.documentElement.lang || void 0));
|
|
1994
|
+
},
|
|
1995
|
+
[getIndex, selection, selectSpan]
|
|
1996
|
+
);
|
|
1997
|
+
const createHighlight = useCallback(
|
|
1998
|
+
(span, color, note) => {
|
|
1999
|
+
const index = getIndex();
|
|
2000
|
+
if (!index) return null;
|
|
2001
|
+
const anchor = describeSpan(index, span, contentHash);
|
|
2002
|
+
if (!anchor) return null;
|
|
2003
|
+
const dup = highlights.find((h) => h.anchor.start === anchor.start && h.anchor.end === anchor.end);
|
|
2004
|
+
if (dup) {
|
|
2005
|
+
if (color && dup.color !== color) store.updateHighlight(bookId, dup.id, { color });
|
|
2006
|
+
return dup;
|
|
2007
|
+
}
|
|
2008
|
+
return store.addHighlight({ bookId, chapterId, anchor, color, note });
|
|
2009
|
+
},
|
|
2010
|
+
[getIndex, contentHash, highlights, store, bookId, chapterId]
|
|
2011
|
+
);
|
|
2012
|
+
const highlightModeRef = useRef(settings.highlightMode);
|
|
2013
|
+
highlightModeRef.current = settings.highlightMode;
|
|
2014
|
+
const createRef = useRef(createHighlight);
|
|
2015
|
+
createRef.current = createHighlight;
|
|
2016
|
+
useEffect(() => {
|
|
2017
|
+
const root = rootEl;
|
|
2018
|
+
if (!root) return;
|
|
2019
|
+
const onClick = (e) => {
|
|
2020
|
+
const sel = document.getSelection();
|
|
2021
|
+
if (sel && !sel.isCollapsed) return;
|
|
2022
|
+
if (e.target.closest?.(INTERACTIVE)) return;
|
|
2023
|
+
const index = getIndex();
|
|
2024
|
+
if (!index) return;
|
|
2025
|
+
const offset = offsetFromPoint(index, e.clientX, e.clientY);
|
|
2026
|
+
if (offset == null) return;
|
|
2027
|
+
const hit = highlightAtOffset(resolvedRef.current, offset);
|
|
2028
|
+
if (hit) {
|
|
2029
|
+
setActiveId(hit.highlight.id);
|
|
2030
|
+
onTapRef.current?.(hit.highlight.id);
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
if (highlightModeRef.current) {
|
|
2034
|
+
const span = sentenceSpanAt(index, offset, document.documentElement.lang || void 0);
|
|
2035
|
+
if (span && createRef.current(span)) navigator.vibrate?.(8);
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
root.addEventListener("click", onClick);
|
|
2039
|
+
return () => root.removeEventListener("click", onClick);
|
|
2040
|
+
}, [rootEl, getIndex]);
|
|
2041
|
+
const scrollToSpan = useCallback(
|
|
2042
|
+
(span, smooth = true) => {
|
|
2043
|
+
const index = getIndex();
|
|
2044
|
+
const range = index && spanToRange(index, span);
|
|
2045
|
+
if (!range) return false;
|
|
2046
|
+
scrollToY(targetFor(range.getBoundingClientRect().top, viewportHeight() * 0.15), smooth);
|
|
2047
|
+
return true;
|
|
2048
|
+
},
|
|
2049
|
+
[getIndex, scrollToY, targetFor, viewportHeight]
|
|
2050
|
+
);
|
|
2051
|
+
const scrollToHighlight = useCallback(
|
|
2052
|
+
(id, smooth = true) => {
|
|
2053
|
+
const r = resolvedRef.current.find((x) => x.highlight.id === id);
|
|
2054
|
+
if (!r) return false;
|
|
2055
|
+
setActiveId(id);
|
|
2056
|
+
return scrollToSpan(r.span, smooth);
|
|
2057
|
+
},
|
|
2058
|
+
[scrollToSpan]
|
|
2059
|
+
);
|
|
2060
|
+
const scrollToElement = useCallback(
|
|
2061
|
+
(el, smooth = true) => {
|
|
2062
|
+
if (!el) return false;
|
|
2063
|
+
scrollToY(targetFor(el.getBoundingClientRect().top), smooth);
|
|
2064
|
+
return true;
|
|
2065
|
+
},
|
|
2066
|
+
[scrollToY, targetFor]
|
|
2067
|
+
);
|
|
2068
|
+
const scrollToBlock = useCallback(
|
|
2069
|
+
(blockId, smooth = true) => {
|
|
2070
|
+
const root = rootRef.current;
|
|
2071
|
+
return scrollToElement(root && findBlock(root, blockId), smooth);
|
|
2072
|
+
},
|
|
2073
|
+
[scrollToElement]
|
|
2074
|
+
);
|
|
2075
|
+
const scrollToHeading = useCallback(
|
|
2076
|
+
(id, smooth = true) => scrollToElement(rootRef.current?.ownerDocument.getElementById(id) ?? null, smooth),
|
|
2077
|
+
[scrollToElement]
|
|
2078
|
+
);
|
|
2079
|
+
const scrollToTop = useCallback(() => scrollToY(0), [scrollToY]);
|
|
2080
|
+
const getTopBlock = useCallback(() => {
|
|
2081
|
+
const root = rootRef.current;
|
|
2082
|
+
if (!root) return null;
|
|
2083
|
+
const line = viewportTop() + topOffset + 8;
|
|
2084
|
+
for (const el of Array.from(root.querySelectorAll(`[${BLOCK_ATTR}]`))) {
|
|
2085
|
+
if (el.getBoundingClientRect().bottom > line) return el;
|
|
2086
|
+
}
|
|
2087
|
+
return null;
|
|
2088
|
+
}, [viewportTop, topOffset]);
|
|
2089
|
+
useEffect(() => {
|
|
2090
|
+
const root = rootEl;
|
|
2091
|
+
if (!root) return;
|
|
2092
|
+
const target = scroller() ?? window;
|
|
2093
|
+
let raf = 0;
|
|
2094
|
+
let lastSave = 0;
|
|
2095
|
+
let pending;
|
|
2096
|
+
const compute = () => {
|
|
2097
|
+
const box = root.getBoundingClientRect();
|
|
2098
|
+
const top = box.top - viewportTop();
|
|
2099
|
+
const total = box.height - viewportHeight() + topOffset;
|
|
2100
|
+
const percent = total <= 0 ? 1 : Math.max(0, Math.min(1, (topOffset - top) / total));
|
|
2101
|
+
percentStore.set(percent);
|
|
2102
|
+
return percent;
|
|
2103
|
+
};
|
|
2104
|
+
const save = () => {
|
|
2105
|
+
const percent = compute();
|
|
2106
|
+
const block = getTopBlock();
|
|
2107
|
+
store.recordProgress(bookId, chapterId, { blockId: block?.getAttribute(BLOCK_ATTR) ?? void 0, percent });
|
|
2108
|
+
lastSave = Date.now();
|
|
2109
|
+
};
|
|
2110
|
+
const onScroll = () => {
|
|
2111
|
+
cancelAnimationFrame(raf);
|
|
2112
|
+
raf = requestAnimationFrame(() => {
|
|
2113
|
+
compute();
|
|
2114
|
+
clearTimeout(pending);
|
|
2115
|
+
pending = setTimeout(save, Math.max(0, options.progress.saveIntervalMs - (Date.now() - lastSave)));
|
|
2116
|
+
});
|
|
2117
|
+
};
|
|
2118
|
+
compute();
|
|
2119
|
+
target.addEventListener("scroll", onScroll, { passive: true });
|
|
2120
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
2121
|
+
window.addEventListener("pagehide", save);
|
|
2122
|
+
return () => {
|
|
2123
|
+
target.removeEventListener("scroll", onScroll);
|
|
2124
|
+
window.removeEventListener("resize", onScroll);
|
|
2125
|
+
window.removeEventListener("pagehide", save);
|
|
2126
|
+
cancelAnimationFrame(raf);
|
|
2127
|
+
clearTimeout(pending);
|
|
2128
|
+
};
|
|
2129
|
+
}, [rootEl, scroller, viewportTop, viewportHeight, bookId, chapterId, store, options.progress.saveIntervalMs, getTopBlock, topOffset, percentStore]);
|
|
2130
|
+
const latest = useRef({ scrollToHighlight, scrollToBlock, scrollToHeading, scrollTop });
|
|
2131
|
+
latest.current = { scrollToHighlight, scrollToBlock, scrollToHeading, scrollTop };
|
|
2132
|
+
const restoredFor = useRef(null);
|
|
2133
|
+
useEffect(() => {
|
|
2134
|
+
if (!rootEl || version === 0 || restoredFor.current === chapterId) return;
|
|
2135
|
+
restoredFor.current = chapterId;
|
|
2136
|
+
const hash = decodeURIComponent(window.location.hash.slice(1));
|
|
2137
|
+
const run = () => {
|
|
2138
|
+
const l = latest.current;
|
|
2139
|
+
if (hash.startsWith("hl-")) {
|
|
2140
|
+
if (l.scrollToHighlight(hash.slice(3), false)) return;
|
|
2141
|
+
} else if (hash.startsWith("b-")) {
|
|
2142
|
+
if (l.scrollToBlock(hash.slice(2), false)) return;
|
|
2143
|
+
} else if (hash) {
|
|
2144
|
+
l.scrollToHeading(hash, false);
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
const saved = store.progress(bookId).get().chapters[chapterId];
|
|
2148
|
+
if (saved?.blockId && saved.percent > 0.03 && saved.percent < 0.98 && l.scrollTop() < 40) {
|
|
2149
|
+
if (l.scrollToBlock(saved.blockId, false)) setResume({ blockId: saved.blockId, percent: saved.percent });
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
const fonts = document.fonts;
|
|
2153
|
+
const go = () => requestAnimationFrame(() => requestAnimationFrame(run));
|
|
2154
|
+
if (fonts?.ready) void fonts.ready.then(go);
|
|
2155
|
+
else go();
|
|
2156
|
+
}, [version, chapterId, bookId, store, rootEl]);
|
|
2157
|
+
const dismissResume = useCallback(() => setResume(null), []);
|
|
2158
|
+
return {
|
|
2159
|
+
highlights,
|
|
2160
|
+
resolved,
|
|
2161
|
+
orphaned,
|
|
2162
|
+
selection,
|
|
2163
|
+
activeId,
|
|
2164
|
+
setActiveId,
|
|
2165
|
+
resume,
|
|
2166
|
+
dismissResume,
|
|
2167
|
+
percentStore,
|
|
2168
|
+
domToc,
|
|
2169
|
+
clearSelection,
|
|
2170
|
+
selectSpan,
|
|
2171
|
+
expandSelectionToSentences,
|
|
2172
|
+
createHighlight,
|
|
2173
|
+
scrollToHighlight,
|
|
2174
|
+
scrollToBlock,
|
|
2175
|
+
scrollToHeading,
|
|
2176
|
+
scrollToTop,
|
|
2177
|
+
getTopBlock,
|
|
2178
|
+
getIndex
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
var ReaderChapterContext = createContext(null);
|
|
2182
|
+
function useReaderChapter() {
|
|
2183
|
+
const ctx = useContext(ReaderChapterContext);
|
|
2184
|
+
if (!ctx) throw new Error("zreader: useReaderChapter() must be used inside <Reader>.");
|
|
2185
|
+
return ctx;
|
|
2186
|
+
}
|
|
2187
|
+
function ReaderTitle({ className }) {
|
|
2188
|
+
const { book, chapter } = useReaderChapter();
|
|
2189
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("min-w-0", className), children: [
|
|
2190
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: book.title }),
|
|
2191
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: chapter.title })
|
|
2192
|
+
] });
|
|
2193
|
+
}
|
|
2194
|
+
function HeaderContent({ slot, book, chapter }) {
|
|
2195
|
+
const content = typeof slot === "function" ? slot({ book, chapter }) : slot;
|
|
2196
|
+
if (content === void 0) return /* @__PURE__ */ jsx(ReaderTitle, {});
|
|
2197
|
+
if (typeof content === "string" || typeof content === "number") {
|
|
2198
|
+
return /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: content });
|
|
2199
|
+
}
|
|
2200
|
+
return /* @__PURE__ */ jsx(Fragment, { children: content });
|
|
2201
|
+
}
|
|
2202
|
+
function ReadingProgress({ store, className }) {
|
|
2203
|
+
const value = useValueStore(store);
|
|
2204
|
+
return /* @__PURE__ */ jsx(Progress, { value: Math.round(value * 100), "aria-label": "Chapter progress", className: cn("h-0.5 rounded-none bg-transparent", className) });
|
|
2205
|
+
}
|
|
2206
|
+
function IconAction({ label, children, ...props }) {
|
|
2207
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2208
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": label, ...props, children }) }),
|
|
2209
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: label })
|
|
2210
|
+
] });
|
|
2211
|
+
}
|
|
2212
|
+
function IconToggle({ label, children, ...props }) {
|
|
2213
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2214
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Toggle, { "aria-label": label, ...props, children }) }),
|
|
2215
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: label })
|
|
2216
|
+
] });
|
|
2217
|
+
}
|
|
2218
|
+
function useAutoHide(enabled) {
|
|
2219
|
+
const [hidden, setHidden] = useState(false);
|
|
2220
|
+
useEffect(() => {
|
|
2221
|
+
if (!enabled) {
|
|
2222
|
+
setHidden(false);
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
let last = window.scrollY;
|
|
2226
|
+
let raf = 0;
|
|
2227
|
+
const onScroll = () => {
|
|
2228
|
+
cancelAnimationFrame(raf);
|
|
2229
|
+
raf = requestAnimationFrame(() => {
|
|
2230
|
+
const y = window.scrollY;
|
|
2231
|
+
if (Math.abs(y - last) < 8) return;
|
|
2232
|
+
setHidden(y > last && y > 80);
|
|
2233
|
+
last = y;
|
|
2234
|
+
});
|
|
2235
|
+
};
|
|
2236
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
2237
|
+
return () => {
|
|
2238
|
+
window.removeEventListener("scroll", onScroll);
|
|
2239
|
+
cancelAnimationFrame(raf);
|
|
2240
|
+
};
|
|
2241
|
+
}, [enabled]);
|
|
2242
|
+
return hidden;
|
|
2243
|
+
}
|
|
2244
|
+
var isEditable = (el) => !!el?.closest("input,textarea,select,[contenteditable='true']");
|
|
2245
|
+
function Reader({
|
|
2246
|
+
book: bookArg,
|
|
2247
|
+
chapter: chapterArg,
|
|
2248
|
+
prev: prevArg,
|
|
2249
|
+
next: nextArg,
|
|
2250
|
+
toc: tocProp,
|
|
2251
|
+
contentHash,
|
|
2252
|
+
html,
|
|
2253
|
+
children,
|
|
2254
|
+
Link: LinkProp,
|
|
2255
|
+
hrefFor,
|
|
2256
|
+
header,
|
|
2257
|
+
actions,
|
|
2258
|
+
backHref,
|
|
2259
|
+
className
|
|
2260
|
+
}) {
|
|
2261
|
+
const { store, options } = useReader();
|
|
2262
|
+
const library = useLibrary();
|
|
2263
|
+
const book = useBook(bookArg);
|
|
2264
|
+
const chapterIndex = book.chapters.findIndex((c) => c.id === (typeof chapterArg === "string" ? chapterArg : chapterArg.id));
|
|
2265
|
+
const chapter = typeof chapterArg === "string" ? book.chapters[chapterIndex] : chapterArg;
|
|
2266
|
+
if (!chapter) {
|
|
2267
|
+
throw new Error(`zreader: book "${book.id}" has no chapter "${String(chapterArg)}".`);
|
|
2268
|
+
}
|
|
2269
|
+
const prev = prevArg !== void 0 ? prevArg : book.chapters[chapterIndex - 1] ?? null;
|
|
2270
|
+
const next = nextArg !== void 0 ? nextArg : chapterIndex >= 0 ? book.chapters[chapterIndex + 1] ?? null : null;
|
|
2271
|
+
const Link = LinkProp ?? library.Link;
|
|
2272
|
+
const slot = header !== void 0 ? header : library.headerSlot;
|
|
2273
|
+
const topActions = actions !== void 0 ? actions : library.readerActions;
|
|
2274
|
+
const [settings, updateSettings] = useSettings();
|
|
2275
|
+
const isDesktop = useIsDesktop();
|
|
2276
|
+
const bookmarks = useBookmarks(book.id);
|
|
2277
|
+
const progress = useProgress(book.id);
|
|
2278
|
+
const build = hrefFor ?? library.href;
|
|
2279
|
+
const href = useCallback((chapterId, hash) => build(book.id, chapterId, hash), [build, book.id]);
|
|
2280
|
+
const chapterCtx = useMemo(() => ({ book, chapter, href }), [book, chapter, href]);
|
|
2281
|
+
const [rootEl, setRootEl] = useState(null);
|
|
2282
|
+
const [scrollEl, setScrollEl] = useState(null);
|
|
2283
|
+
const [contentsOpen, setContentsOpen] = useState(false);
|
|
2284
|
+
const [contentsTab, setContentsTab] = useState("contents");
|
|
2285
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
2286
|
+
const [padOpen, setPadOpen] = useState(false);
|
|
2287
|
+
const [padKind, setPadKind] = useState(options.scratchpad.scopes[0] ?? "chapter");
|
|
2288
|
+
const [padSnap, setPadSnap] = useState(0.55);
|
|
2289
|
+
const [openHighlightId, setOpenHighlightId] = useState(null);
|
|
2290
|
+
const [focusNote, setFocusNote] = useState(false);
|
|
2291
|
+
const engine = useChapterEngine({
|
|
2292
|
+
bookId: book.id,
|
|
2293
|
+
chapterId: chapter.id,
|
|
2294
|
+
contentHash,
|
|
2295
|
+
root: rootEl,
|
|
2296
|
+
scroller: isDesktop ? scrollEl : null,
|
|
2297
|
+
topOffset: isDesktop ? 16 : 64,
|
|
2298
|
+
onHighlightTap: (id) => {
|
|
2299
|
+
setFocusNote(false);
|
|
2300
|
+
setOpenHighlightId(id);
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
const toc = tocProp && tocProp.length > 0 ? tocProp : engine.domToc;
|
|
2304
|
+
const headerHidden = useAutoHide(!isDesktop && !engine.selection);
|
|
2305
|
+
const chapterBookmarks = useMemo(() => bookmarks.filter((b) => b.chapterId === chapter.id), [bookmarks, chapter.id]);
|
|
2306
|
+
const completed = progress.chapters[chapter.id]?.completed ?? false;
|
|
2307
|
+
const openHighlight = engine.highlights.find((h) => h.id === openHighlightId) ?? null;
|
|
2308
|
+
const orphanedIds = useMemo(() => new Set(engine.orphaned.map((h) => h.id)), [engine.orphaned]);
|
|
2309
|
+
const padEnabled = options.scratchpad.enabled;
|
|
2310
|
+
useEffect(() => {
|
|
2311
|
+
if (!supportsHighlightApi()) {
|
|
2312
|
+
toast.info("Highlights can't be drawn in this browser", { description: "They are still saved and listed under Contents \u2192 Highlights." });
|
|
2313
|
+
}
|
|
2314
|
+
}, []);
|
|
2315
|
+
useEffect(() => {
|
|
2316
|
+
if (!engine.resume) return;
|
|
2317
|
+
toast(`Resumed at ${Math.round(engine.resume.percent * 100)}%`, {
|
|
2318
|
+
id: `zr-resume-${chapter.id}`,
|
|
2319
|
+
action: { label: "Start from top", onClick: () => engine.scrollToTop() }
|
|
2320
|
+
});
|
|
2321
|
+
engine.dismissResume();
|
|
2322
|
+
}, [engine.resume, engine, chapter.id]);
|
|
2323
|
+
const openPad = useCallback(() => {
|
|
2324
|
+
setPadOpen(true);
|
|
2325
|
+
setPadSnap(0.55);
|
|
2326
|
+
}, []);
|
|
2327
|
+
const quoteToPad = useCallback(
|
|
2328
|
+
(markdown) => {
|
|
2329
|
+
store.appendToPad(scopeFor(padKind, book.id, chapter.id), markdown);
|
|
2330
|
+
openPad();
|
|
2331
|
+
toast.success("Added to scratchpad");
|
|
2332
|
+
},
|
|
2333
|
+
[store, padKind, book.id, chapter.id, openPad]
|
|
2334
|
+
);
|
|
2335
|
+
const quoteSpan = (span, text) => {
|
|
2336
|
+
const h = engine.createHighlight(span);
|
|
2337
|
+
engine.clearSelection();
|
|
2338
|
+
quoteToPad(h ? highlightToMarkdown(h, href(chapter.id, `hl-${h.id}`)) : quoteToMarkdown(text));
|
|
2339
|
+
};
|
|
2340
|
+
const copy = async (text) => {
|
|
2341
|
+
toast(await copyText(text) ? "Copied" : "Could not copy");
|
|
2342
|
+
};
|
|
2343
|
+
const toggleBookmark = () => {
|
|
2344
|
+
if (chapterBookmarks.length) {
|
|
2345
|
+
const removed = chapterBookmarks;
|
|
2346
|
+
removed.forEach((b) => store.removeBookmark(book.id, b.id));
|
|
2347
|
+
toast("Bookmark removed", {
|
|
2348
|
+
action: {
|
|
2349
|
+
label: "Undo",
|
|
2350
|
+
onClick: () => removed.forEach(
|
|
2351
|
+
(b) => store.addBookmark({ bookId: b.bookId, chapterId: b.chapterId, blockId: b.blockId, label: b.label, excerpt: b.excerpt })
|
|
2352
|
+
)
|
|
2353
|
+
}
|
|
2354
|
+
});
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
const block = engine.getTopBlock();
|
|
2358
|
+
store.addBookmark({
|
|
2359
|
+
bookId: book.id,
|
|
2360
|
+
chapterId: chapter.id,
|
|
2361
|
+
...block ? { blockId: block.getAttribute(BLOCK_ATTR) ?? void 0, excerpt: block.textContent?.trim().slice(0, 120) } : {},
|
|
2362
|
+
label: chapter.title
|
|
2363
|
+
});
|
|
2364
|
+
toast.success("Bookmarked", { description: "Find it under Contents \u2192 Bookmarks." });
|
|
2365
|
+
};
|
|
2366
|
+
useEffect(() => {
|
|
2367
|
+
const onKey = (e) => {
|
|
2368
|
+
if (!engine.selection || e.metaKey || e.ctrlKey || e.altKey || isEditable(document.activeElement)) return;
|
|
2369
|
+
if (e.key === "h") {
|
|
2370
|
+
e.preventDefault();
|
|
2371
|
+
engine.createHighlight(engine.selection.span);
|
|
2372
|
+
engine.clearSelection();
|
|
2373
|
+
} else if (e.key === "n") {
|
|
2374
|
+
e.preventDefault();
|
|
2375
|
+
const h = engine.createHighlight(engine.selection.span);
|
|
2376
|
+
engine.clearSelection();
|
|
2377
|
+
if (h) {
|
|
2378
|
+
setFocusNote(true);
|
|
2379
|
+
setOpenHighlightId(h.id);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
document.addEventListener("keydown", onKey);
|
|
2384
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
2385
|
+
}, [engine]);
|
|
2386
|
+
const topBar = /* @__PURE__ */ jsxs(
|
|
2387
|
+
"header",
|
|
2388
|
+
{
|
|
2389
|
+
className: cn(
|
|
2390
|
+
"sticky top-0 z-30 border-b bg-background/85 backdrop-blur-md transition-transform duration-200 supports-[backdrop-filter]:bg-background/70",
|
|
2391
|
+
headerHidden && "-translate-y-full"
|
|
2392
|
+
),
|
|
2393
|
+
children: [
|
|
2394
|
+
/* @__PURE__ */ jsxs("div", { className: "flex h-14 items-center gap-1 px-2 pt-[env(safe-area-inset-top)] sm:px-3", children: [
|
|
2395
|
+
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2396
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "ghost", size: "icon", "aria-label": "Back to book", children: /* @__PURE__ */ jsx(Link, { href: backHref ?? href(), children: /* @__PURE__ */ jsx(ChevronLeftIcon, {}) }) }) }),
|
|
2397
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: "Back to book" })
|
|
2398
|
+
] }),
|
|
2399
|
+
/* @__PURE__ */ jsx(IconAction, { label: "Contents, highlights and bookmarks", onClick: () => setContentsOpen(true), children: /* @__PURE__ */ jsx(ListIcon, {}) }),
|
|
2400
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2 px-1", children: /* @__PURE__ */ jsx(HeaderContent, { slot, book, chapter }) }),
|
|
2401
|
+
topActions,
|
|
2402
|
+
/* @__PURE__ */ jsx(IconToggle, { label: chapterBookmarks.length ? "Remove bookmark" : "Bookmark this place", pressed: chapterBookmarks.length > 0, onPressedChange: toggleBookmark, className: "data-[state=on]:[&_svg]:fill-current", children: /* @__PURE__ */ jsx(BookmarkIcon, {}) }),
|
|
2403
|
+
/* @__PURE__ */ jsx(IconToggle, { label: "Highlight mode: tap a sentence", pressed: settings.highlightMode, onPressedChange: (highlightMode) => updateSettings({ highlightMode }), children: /* @__PURE__ */ jsx(HighlighterIcon, {}) }),
|
|
2404
|
+
/* @__PURE__ */ jsx(IconAction, { label: "Reading settings", onClick: () => setSettingsOpen(true), children: /* @__PURE__ */ jsx(TypeIcon, {}) }),
|
|
2405
|
+
padEnabled && /* @__PURE__ */ jsx(IconToggle, { label: "Scratchpad", pressed: padOpen, onPressedChange: (open) => open ? openPad() : setPadOpen(false), children: /* @__PURE__ */ jsx(NotebookPenIcon, {}) })
|
|
2406
|
+
] }),
|
|
2407
|
+
/* @__PURE__ */ jsx(ReadingProgress, { store: engine.percentStore, className: "absolute inset-x-0 bottom-0" })
|
|
2408
|
+
]
|
|
2409
|
+
}
|
|
2410
|
+
);
|
|
2411
|
+
const body = /* @__PURE__ */ jsxs("div", { className: cn("mx-auto w-full px-5 pt-8 pb-[calc(7rem+env(safe-area-inset-bottom))] sm:px-8 lg:pt-12", MEASURE_CLASS[settings.measure]), children: [
|
|
2412
|
+
/* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-x-2 text-sm text-muted-foreground", children: [
|
|
2413
|
+
chapter.part && /* @__PURE__ */ jsx("span", { children: chapter.part }),
|
|
2414
|
+
chapter.part && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
2415
|
+
/* @__PURE__ */ jsx("span", { children: chapter.label ?? `Chapter ${chapterIndex + 1} of ${book.chapters.length}` })
|
|
2416
|
+
] }),
|
|
2417
|
+
/* @__PURE__ */ jsx("h1", { className: cn("mt-1 text-3xl font-semibold tracking-tight text-balance sm:text-4xl", FONT_CLASS[settings.fontFamily]), children: chapter.title }),
|
|
2418
|
+
/* @__PURE__ */ jsx(
|
|
2419
|
+
"article",
|
|
2420
|
+
{
|
|
2421
|
+
ref: setRootEl,
|
|
2422
|
+
className: cn("mt-8", PROSE_CLASS, TEXT_SIZE_CLASS[settings.textSize], LEADING_CLASS[settings.leading], FONT_CLASS[settings.fontFamily], settings.highlightMode && "cursor-pointer"),
|
|
2423
|
+
"aria-label": chapter.title,
|
|
2424
|
+
children: html !== void 0 ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }) : children
|
|
2425
|
+
}
|
|
2426
|
+
),
|
|
2427
|
+
/* @__PURE__ */ jsxs("footer", { className: "mt-16 flex flex-col gap-4 border-t pt-8", children: [
|
|
2428
|
+
!completed && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", className: "self-center", onClick: () => store.markChapterComplete(book.id, chapter.id), children: [
|
|
2429
|
+
/* @__PURE__ */ jsx(CheckIcon, {}),
|
|
2430
|
+
" Mark chapter as read"
|
|
2431
|
+
] }),
|
|
2432
|
+
/* @__PURE__ */ jsxs("nav", { "aria-label": "Chapters", className: "grid grid-cols-2 gap-3", children: [
|
|
2433
|
+
prev ? /* @__PURE__ */ jsx(Button, { asChild: true, variant: "ghost", className: "h-auto flex-col items-start gap-1 py-3 text-left whitespace-normal", children: /* @__PURE__ */ jsxs(Link, { href: href(prev.id), children: [
|
|
2434
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
2435
|
+
/* @__PURE__ */ jsx(ArrowLeftIcon, {}),
|
|
2436
|
+
" Previous"
|
|
2437
|
+
] }),
|
|
2438
|
+
/* @__PURE__ */ jsx("span", { className: "line-clamp-2 font-medium", children: prev.title })
|
|
2439
|
+
] }) }) : /* @__PURE__ */ jsx("span", {}),
|
|
2440
|
+
next ? /* @__PURE__ */ jsx(Button, { asChild: true, variant: "secondary", className: "col-start-2 h-auto flex-col items-end gap-1 py-3 text-right whitespace-normal", children: /* @__PURE__ */ jsxs(Link, { href: href(next.id), children: [
|
|
2441
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
2442
|
+
"Next ",
|
|
2443
|
+
/* @__PURE__ */ jsx(ArrowRightIcon, {})
|
|
2444
|
+
] }),
|
|
2445
|
+
/* @__PURE__ */ jsx("span", { className: "line-clamp-2 font-medium", children: next.title })
|
|
2446
|
+
] }) }) : null
|
|
2447
|
+
] })
|
|
2448
|
+
] })
|
|
2449
|
+
] });
|
|
2450
|
+
const pad = (onClose) => /* @__PURE__ */ jsx(
|
|
2451
|
+
ScratchpadPanel,
|
|
2452
|
+
{
|
|
2453
|
+
bookId: book.id,
|
|
2454
|
+
chapterId: chapter.id,
|
|
2455
|
+
kind: padKind,
|
|
2456
|
+
onKindChange: setPadKind,
|
|
2457
|
+
onClose,
|
|
2458
|
+
onEditorFocus: () => setPadSnap(0.95),
|
|
2459
|
+
title: book.id
|
|
2460
|
+
}
|
|
2461
|
+
);
|
|
2462
|
+
const padPanelRef = usePanelRef();
|
|
2463
|
+
const layoutStorage = useMemo(
|
|
2464
|
+
() => ({ getItem: (k) => store.adapter.get(k), setItem: (k, v) => store.adapter.set(k, v) }),
|
|
2465
|
+
[store]
|
|
2466
|
+
);
|
|
2467
|
+
const layout = useDefaultLayout({ id: `${options.storagePrefix}:v1:layout`, panelIds: ["content", "pad"], storage: layoutStorage });
|
|
2468
|
+
useEffect(() => {
|
|
2469
|
+
const p = padPanelRef.current;
|
|
2470
|
+
if (!isDesktop || !p) return;
|
|
2471
|
+
if (padOpen && p.isCollapsed()) p.expand();
|
|
2472
|
+
if (!padOpen && !p.isCollapsed()) p.collapse();
|
|
2473
|
+
}, [padOpen, isDesktop, padPanelRef]);
|
|
2474
|
+
return /* @__PURE__ */ jsx(ReaderChapterContext.Provider, { value: chapterCtx, children: /* @__PURE__ */ jsxs("div", { "data-zr-reader": "", className: cn("min-h-svh bg-background text-foreground", isDesktop && "flex h-svh flex-col overflow-hidden", className), children: [
|
|
2475
|
+
topBar,
|
|
2476
|
+
isDesktop ? /* @__PURE__ */ jsxs(ResizablePanelGroup, { orientation: "horizontal", className: "min-h-0 flex-1", defaultLayout: layout.defaultLayout, onLayoutChanged: layout.onLayoutChanged, children: [
|
|
2477
|
+
/* @__PURE__ */ jsx(ResizablePanel, { id: "content", minSize: "40%", children: /* @__PURE__ */ jsx("div", { ref: setScrollEl, className: "h-full overflow-y-auto overscroll-contain", children: body }) }),
|
|
2478
|
+
padEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2479
|
+
/* @__PURE__ */ jsx(ResizableHandle, { withHandle: true, className: cn(!padOpen && "hidden") }),
|
|
2480
|
+
/* @__PURE__ */ jsx(
|
|
2481
|
+
ResizablePanel,
|
|
2482
|
+
{
|
|
2483
|
+
id: "pad",
|
|
2484
|
+
panelRef: padPanelRef,
|
|
2485
|
+
collapsible: true,
|
|
2486
|
+
collapsedSize: "0%",
|
|
2487
|
+
defaultSize: padOpen ? "32%" : "0%",
|
|
2488
|
+
minSize: "22%",
|
|
2489
|
+
maxSize: "55%",
|
|
2490
|
+
onResize: (size) => setPadOpen(size.asPercentage > 0),
|
|
2491
|
+
className: "bg-background",
|
|
2492
|
+
children: padOpen && pad(() => setPadOpen(false))
|
|
2493
|
+
}
|
|
2494
|
+
)
|
|
2495
|
+
] })
|
|
2496
|
+
] }) : /* @__PURE__ */ jsx("main", { children: body }),
|
|
2497
|
+
!isDesktop && padEnabled && /* @__PURE__ */ jsx(Drawer, { open: padOpen, onOpenChange: setPadOpen, snapPoints: [0.55, 0.95], activeSnapPoint: padSnap, setActiveSnapPoint: setPadSnap, repositionInputs: false, children: /* @__PURE__ */ jsxs(DrawerContent, { className: "h-[95svh] max-h-[95svh]", children: [
|
|
2498
|
+
/* @__PURE__ */ jsx(DrawerTitle, { className: "sr-only", children: "Scratchpad" }),
|
|
2499
|
+
/* @__PURE__ */ jsx(DrawerDescription, { className: "sr-only", children: "Notes and checklists saved in this browser." }),
|
|
2500
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col pb-[env(safe-area-inset-bottom)]", children: pad(() => setPadOpen(false)) })
|
|
2501
|
+
] }) }),
|
|
2502
|
+
/* @__PURE__ */ jsx(
|
|
2503
|
+
SelectionToolbar,
|
|
2504
|
+
{
|
|
2505
|
+
selection: openHighlightId || settingsOpen || contentsOpen ? null : engine.selection,
|
|
2506
|
+
colors: options.highlightColors,
|
|
2507
|
+
onHighlight: (span, color) => {
|
|
2508
|
+
engine.createHighlight(span, color);
|
|
2509
|
+
engine.clearSelection();
|
|
2510
|
+
},
|
|
2511
|
+
onSentence: (span) => engine.expandSelectionToSentences(span),
|
|
2512
|
+
onNote: (span) => {
|
|
2513
|
+
const h = engine.createHighlight(span);
|
|
2514
|
+
engine.clearSelection();
|
|
2515
|
+
if (h) {
|
|
2516
|
+
setFocusNote(true);
|
|
2517
|
+
setOpenHighlightId(h.id);
|
|
2518
|
+
}
|
|
2519
|
+
},
|
|
2520
|
+
onQuote: padEnabled ? quoteSpan : void 0,
|
|
2521
|
+
onCopy: (text) => {
|
|
2522
|
+
void copy(text);
|
|
2523
|
+
engine.clearSelection();
|
|
2524
|
+
},
|
|
2525
|
+
onDismiss: engine.clearSelection
|
|
2526
|
+
}
|
|
2527
|
+
),
|
|
2528
|
+
/* @__PURE__ */ jsx(
|
|
2529
|
+
HighlightPanel,
|
|
2530
|
+
{
|
|
2531
|
+
highlight: openHighlight,
|
|
2532
|
+
colors: options.highlightColors,
|
|
2533
|
+
focusNote,
|
|
2534
|
+
onColor: (color) => openHighlight && store.updateHighlight(book.id, openHighlight.id, { color }),
|
|
2535
|
+
onNote: (note) => openHighlight && store.updateHighlight(book.id, openHighlight.id, { note }),
|
|
2536
|
+
onDelete: () => {
|
|
2537
|
+
if (!openHighlight) return;
|
|
2538
|
+
const removed = openHighlight;
|
|
2539
|
+
store.removeHighlight(book.id, removed.id);
|
|
2540
|
+
setOpenHighlightId(null);
|
|
2541
|
+
engine.setActiveId(null);
|
|
2542
|
+
toast("Highlight deleted", {
|
|
2543
|
+
action: {
|
|
2544
|
+
label: "Undo",
|
|
2545
|
+
onClick: () => store.addHighlight({ bookId: removed.bookId, chapterId: removed.chapterId, anchor: removed.anchor, color: removed.color, note: removed.note })
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
},
|
|
2549
|
+
onQuote: padEnabled && openHighlight ? () => quoteToPad(highlightToMarkdown(openHighlight, href(chapter.id, `hl-${openHighlight.id}`))) : void 0,
|
|
2550
|
+
onCopy: openHighlight ? () => void copy(openHighlight.anchor.exact) : void 0,
|
|
2551
|
+
onClose: () => {
|
|
2552
|
+
setOpenHighlightId(null);
|
|
2553
|
+
setFocusNote(false);
|
|
2554
|
+
engine.setActiveId(null);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
),
|
|
2558
|
+
/* @__PURE__ */ jsx(
|
|
2559
|
+
ContentsSheet,
|
|
2560
|
+
{
|
|
2561
|
+
open: contentsOpen,
|
|
2562
|
+
onOpenChange: setContentsOpen,
|
|
2563
|
+
tab: contentsTab,
|
|
2564
|
+
onTabChange: setContentsTab,
|
|
2565
|
+
book,
|
|
2566
|
+
chapterId: chapter.id,
|
|
2567
|
+
toc,
|
|
2568
|
+
hrefFor: (c, h) => href(c, h),
|
|
2569
|
+
Link,
|
|
2570
|
+
orphanedIds,
|
|
2571
|
+
onJumpHighlight: (id) => engine.scrollToHighlight(id),
|
|
2572
|
+
onJumpBlock: (id) => engine.scrollToBlock(id),
|
|
2573
|
+
onJumpHeading: (id) => engine.scrollToHeading(id)
|
|
2574
|
+
}
|
|
2575
|
+
),
|
|
2576
|
+
/* @__PURE__ */ jsx(SettingsPanel, { open: settingsOpen, onOpenChange: setSettingsOpen, book })
|
|
2577
|
+
] }) });
|
|
2578
|
+
}
|
|
2579
|
+
function Card({ className, ...props }) {
|
|
2580
|
+
return /* @__PURE__ */ jsx(
|
|
2581
|
+
"div",
|
|
2582
|
+
{
|
|
2583
|
+
"data-slot": "card",
|
|
2584
|
+
className: cn(
|
|
2585
|
+
"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
|
|
2586
|
+
className
|
|
2587
|
+
),
|
|
2588
|
+
...props
|
|
2589
|
+
}
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2592
|
+
function CardHeader({ className, ...props }) {
|
|
2593
|
+
return /* @__PURE__ */ jsx(
|
|
2594
|
+
"div",
|
|
2595
|
+
{
|
|
2596
|
+
"data-slot": "card-header",
|
|
2597
|
+
className: cn(
|
|
2598
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
2599
|
+
className
|
|
2600
|
+
),
|
|
2601
|
+
...props
|
|
2602
|
+
}
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2605
|
+
function CardTitle({ className, ...props }) {
|
|
2606
|
+
return /* @__PURE__ */ jsx(
|
|
2607
|
+
"div",
|
|
2608
|
+
{
|
|
2609
|
+
"data-slot": "card-title",
|
|
2610
|
+
className: cn("leading-none font-semibold", className),
|
|
2611
|
+
...props
|
|
2612
|
+
}
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
function CardDescription({ className, ...props }) {
|
|
2616
|
+
return /* @__PURE__ */ jsx(
|
|
2617
|
+
"div",
|
|
2618
|
+
{
|
|
2619
|
+
"data-slot": "card-description",
|
|
2620
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
2621
|
+
...props
|
|
2622
|
+
}
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
function CardContent({ className, ...props }) {
|
|
2626
|
+
return /* @__PURE__ */ jsx(
|
|
2627
|
+
"div",
|
|
2628
|
+
{
|
|
2629
|
+
"data-slot": "card-content",
|
|
2630
|
+
className: cn("px-6", className),
|
|
2631
|
+
...props
|
|
2632
|
+
}
|
|
2633
|
+
);
|
|
2634
|
+
}
|
|
2635
|
+
function CardFooter({ className, ...props }) {
|
|
2636
|
+
return /* @__PURE__ */ jsx(
|
|
2637
|
+
"div",
|
|
2638
|
+
{
|
|
2639
|
+
"data-slot": "card-footer",
|
|
2640
|
+
className: cn("flex items-center px-6 [.border-t]:pt-6", className),
|
|
2641
|
+
...props
|
|
2642
|
+
}
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
function useNav(bookArg, LinkProp, hrefFor) {
|
|
2646
|
+
const library = useLibrary();
|
|
2647
|
+
const book = useBook(bookArg);
|
|
2648
|
+
const build = hrefFor ?? library.href;
|
|
2649
|
+
return {
|
|
2650
|
+
book,
|
|
2651
|
+
Link: LinkProp ?? library.Link,
|
|
2652
|
+
href: (chapterId, hash) => build(book.id, chapterId, hash)
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
function ContinueReading({ book: bookArg, Link: LinkProp, hrefFor, className }) {
|
|
2656
|
+
const { book, Link, href } = useNav(bookArg, LinkProp, hrefFor);
|
|
2657
|
+
const hydrated = useHydrated();
|
|
2658
|
+
const progress = useProgress(book.id);
|
|
2659
|
+
const highlights = useHighlights(book.id);
|
|
2660
|
+
const bookmarks = useBookmarks(book.id);
|
|
2661
|
+
const first = book.chapters[0];
|
|
2662
|
+
const lastId = progress.last?.chapterId;
|
|
2663
|
+
const last = book.chapters.find((c) => c.id === lastId);
|
|
2664
|
+
const done = book.chapters.filter((c) => progress.chapters[c.id]?.completed).length;
|
|
2665
|
+
const overall = book.chapters.length ? done / book.chapters.length : 0;
|
|
2666
|
+
const lastPct = last ? Math.round((progress.chapters[last.id]?.percent ?? 0) * 100) : 0;
|
|
2667
|
+
if (!first) return null;
|
|
2668
|
+
return /* @__PURE__ */ jsxs(Card, { className: cn("gap-4", className), children: [
|
|
2669
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2670
|
+
/* @__PURE__ */ jsx(CardTitle, { children: last ? "Continue reading" : "Start reading" }),
|
|
2671
|
+
/* @__PURE__ */ jsx(CardDescription, { children: !hydrated ? /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-40" }) : last ? `${last.title} \xB7 ${lastPct}%` : `${book.chapters.length} chapters` })
|
|
2672
|
+
] }),
|
|
2673
|
+
hydrated && (done > 0 || highlights.length > 0 || bookmarks.length > 0) && /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-3", children: [
|
|
2674
|
+
/* @__PURE__ */ jsx(Progress, { value: Math.round(overall * 100), "aria-label": "Book progress" }),
|
|
2675
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 text-sm text-muted-foreground", children: [
|
|
2676
|
+
/* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
|
|
2677
|
+
done,
|
|
2678
|
+
"/",
|
|
2679
|
+
book.chapters.length,
|
|
2680
|
+
" chapters read"
|
|
2681
|
+
] }),
|
|
2682
|
+
highlights.length > 0 && /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
|
|
2683
|
+
highlights.length,
|
|
2684
|
+
" highlights"
|
|
2685
|
+
] }),
|
|
2686
|
+
bookmarks.length > 0 && /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
|
|
2687
|
+
bookmarks.length,
|
|
2688
|
+
" bookmarks"
|
|
2689
|
+
] })
|
|
2690
|
+
] })
|
|
2691
|
+
] }),
|
|
2692
|
+
/* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx(Button, { asChild: true, className: "w-full sm:w-auto", children: /* @__PURE__ */ jsxs(Link, { href: last ? href(last.id, progress.last?.blockId ? `b-${progress.last.blockId}` : void 0) : href(first.id), children: [
|
|
2693
|
+
/* @__PURE__ */ jsx(BookOpenIcon, {}),
|
|
2694
|
+
" ",
|
|
2695
|
+
last ? "Continue" : "Start"
|
|
2696
|
+
] }) }) })
|
|
2697
|
+
] });
|
|
2698
|
+
}
|
|
2699
|
+
function ChapterList({ book: bookArg, Link: LinkProp, hrefFor, className }) {
|
|
2700
|
+
const { book, Link, href } = useNav(bookArg, LinkProp, hrefFor);
|
|
2701
|
+
const hydrated = useHydrated();
|
|
2702
|
+
const progress = useProgress(book.id);
|
|
2703
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-6", className), children: groupByPart(book.chapters).map((group) => /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-2", children: [
|
|
2704
|
+
group.part && /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-muted-foreground", children: group.part }),
|
|
2705
|
+
/* @__PURE__ */ jsx("ol", { className: "flex flex-col divide-y overflow-hidden rounded-xl border", children: group.items.map(({ chapter: c, index: i }) => {
|
|
2706
|
+
const pr = hydrated ? progress.chapters[c.id] : void 0;
|
|
2707
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "ghost", className: "h-auto w-full justify-start gap-4 rounded-none px-4 py-3 text-left whitespace-normal", children: /* @__PURE__ */ jsxs(Link, { href: href(c.id), children: [
|
|
2708
|
+
/* @__PURE__ */ jsx("span", { className: "w-6 shrink-0 text-sm text-muted-foreground tabular-nums", children: chapterMarker(c, i) }),
|
|
2709
|
+
/* @__PURE__ */ jsxs("span", { className: "flex flex-1 flex-col gap-0.5", children: [
|
|
2710
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: c.title }),
|
|
2711
|
+
c.description && /* @__PURE__ */ jsx("span", { className: "text-sm font-normal text-muted-foreground", children: c.description })
|
|
2712
|
+
] }),
|
|
2713
|
+
pr?.completed ? /* @__PURE__ */ jsx(CheckCircle2Icon, { className: "text-primary", "aria-label": "Read" }) : pr ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground tabular-nums", children: [
|
|
2714
|
+
Math.round(pr.percent * 100),
|
|
2715
|
+
"%"
|
|
2716
|
+
] }) : /* @__PURE__ */ jsx(CircleIcon, { className: "text-muted-foreground/40", "aria-hidden": true })
|
|
2717
|
+
] }) }) }, c.id);
|
|
2718
|
+
}) })
|
|
2719
|
+
] }, group.part ?? "_")) });
|
|
2720
|
+
}
|
|
2721
|
+
function BookShelf({ books: booksProp, Link: LinkProp, hrefFor, title = "Books", description, embedded, className }) {
|
|
2722
|
+
const library = useLibrary();
|
|
2723
|
+
const books = booksProp ?? library.books;
|
|
2724
|
+
const Link = LinkProp ?? library.Link;
|
|
2725
|
+
const build = hrefFor ?? library.href;
|
|
2726
|
+
const hydrated = useHydrated();
|
|
2727
|
+
const grid = books.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No books published yet." }) : /* @__PURE__ */ jsx("ul", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: books.map((book) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(BookCard, { book, Link, href: build(book.id), hydrated }) }, book.id)) });
|
|
2728
|
+
if (embedded) {
|
|
2729
|
+
return /* @__PURE__ */ jsx("div", { "data-zr-reader": "", className: cn("text-foreground", className), children: grid });
|
|
2730
|
+
}
|
|
2731
|
+
return /* @__PURE__ */ jsx("div", { "data-zr-reader": "", className: cn("min-h-svh bg-background text-foreground", className), children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-5xl flex-col gap-8 px-5 py-10 sm:px-8 sm:py-16", children: [
|
|
2732
|
+
/* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-2", children: [
|
|
2733
|
+
/* @__PURE__ */ jsx("h1", { className: "font-serif text-3xl font-semibold tracking-tight sm:text-4xl", children: title }),
|
|
2734
|
+
description && /* @__PURE__ */ jsx("p", { className: "max-w-prose text-muted-foreground", children: description })
|
|
2735
|
+
] }),
|
|
2736
|
+
grid
|
|
2737
|
+
] }) });
|
|
2738
|
+
}
|
|
2739
|
+
function BookCard({ book, Link, href, hydrated }) {
|
|
2740
|
+
const progress = useProgress(book.id);
|
|
2741
|
+
const done = hydrated ? book.chapters.filter((c) => progress.chapters[c.id]?.completed).length : 0;
|
|
2742
|
+
return /* @__PURE__ */ jsxs(Card, { className: "relative h-full gap-4 transition-shadow hover:shadow-md", children: [
|
|
2743
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2744
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "font-serif text-xl", children: /* @__PURE__ */ jsx(Link, { href, className: "after:absolute after:inset-0 focus-visible:outline-none", children: book.title }) }),
|
|
2745
|
+
book.authors.length > 0 && /* @__PURE__ */ jsx(CardDescription, { children: book.authors.join(", ") })
|
|
2746
|
+
] }),
|
|
2747
|
+
book.description && /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "line-clamp-3 text-sm text-muted-foreground", children: book.description }) }),
|
|
2748
|
+
/* @__PURE__ */ jsxs(CardFooter, { className: "mt-auto flex items-center justify-between gap-2 text-sm text-muted-foreground", children: [
|
|
2749
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2750
|
+
book.chapters.length,
|
|
2751
|
+
" chapters"
|
|
2752
|
+
] }),
|
|
2753
|
+
done > 0 && /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: done === book.chapters.length ? "Finished" : `${done} read` })
|
|
2754
|
+
] })
|
|
2755
|
+
] });
|
|
2756
|
+
}
|
|
2757
|
+
function BookHome({ book: bookArg, Link: LinkProp, hrefFor, backHref, backLabel = "All books", embedded, className }) {
|
|
2758
|
+
const { book, Link } = useNav(bookArg, LinkProp, hrefFor);
|
|
2759
|
+
if (embedded) {
|
|
2760
|
+
return /* @__PURE__ */ jsxs("div", { "data-zr-reader": "", className: cn("flex flex-col gap-8 text-foreground", className), children: [
|
|
2761
|
+
/* @__PURE__ */ jsx(ContinueReading, { book, Link, hrefFor }),
|
|
2762
|
+
/* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-3", "aria-labelledby": "zr-chapters", children: [
|
|
2763
|
+
/* @__PURE__ */ jsx("h2", { id: "zr-chapters", className: "text-sm font-medium tracking-wide text-muted-foreground uppercase", children: "Chapters" }),
|
|
2764
|
+
/* @__PURE__ */ jsx(ChapterList, { book, Link, hrefFor })
|
|
2765
|
+
] })
|
|
2766
|
+
] });
|
|
2767
|
+
}
|
|
2768
|
+
return /* @__PURE__ */ jsx("div", { "data-zr-reader": "", className: cn("min-h-svh bg-background text-foreground", className), children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-3xl flex-col gap-8 px-5 py-8 sm:px-8 sm:py-14", children: [
|
|
2769
|
+
backHref && /* @__PURE__ */ jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "self-start", children: /* @__PURE__ */ jsxs(Link, { href: backHref, children: [
|
|
2770
|
+
/* @__PURE__ */ jsx(ChevronLeftIcon, {}),
|
|
2771
|
+
" ",
|
|
2772
|
+
backLabel
|
|
2773
|
+
] }) }),
|
|
2774
|
+
/* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-3", children: [
|
|
2775
|
+
/* @__PURE__ */ jsx("h1", { className: "font-serif text-3xl font-semibold tracking-tight text-balance sm:text-5xl", children: book.title }),
|
|
2776
|
+
book.subtitle && /* @__PURE__ */ jsx("p", { className: "font-serif text-xl text-balance text-foreground/80 sm:text-2xl", children: book.subtitle }),
|
|
2777
|
+
book.authors.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: book.authors.join(", ") }),
|
|
2778
|
+
book.description && /* @__PURE__ */ jsx("p", { className: "max-w-prose text-lg text-foreground/80", children: book.description })
|
|
2779
|
+
] }),
|
|
2780
|
+
/* @__PURE__ */ jsx(ContinueReading, { book, Link, hrefFor }),
|
|
2781
|
+
/* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-3", "aria-labelledby": "zr-chapters", children: [
|
|
2782
|
+
/* @__PURE__ */ jsx("h2", { id: "zr-chapters", className: "text-sm font-medium tracking-wide text-muted-foreground uppercase", children: "Chapters" }),
|
|
2783
|
+
/* @__PURE__ */ jsx(ChapterList, { book, Link, hrefFor })
|
|
2784
|
+
] })
|
|
2785
|
+
] }) });
|
|
2786
|
+
}
|
|
2787
|
+
var BookContents = BookHome;
|
|
2788
|
+
function Callout({ title, children }) {
|
|
2789
|
+
return /* @__PURE__ */ jsxs(Alert, { className: "not-prose my-6", "data-zr-ignore": "", children: [
|
|
2790
|
+
/* @__PURE__ */ jsx(LightbulbIcon, {}),
|
|
2791
|
+
title && /* @__PURE__ */ jsx(AlertTitle, { children: title }),
|
|
2792
|
+
/* @__PURE__ */ jsx(AlertDescription, { children })
|
|
2793
|
+
] });
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
export { BookContents, BookHome, BookShelf, Callout, ChapterList, ContentsSheet, ContinueReading, DefaultLink, FONT_CLASS, HighlightPanel, LEADING_CLASS, MEASURE_CLASS, PROSE_CLASS, QUOTE_BORDER_CLASS, Reader, ReaderProvider, ReaderTitle, ResponsivePanel, SWATCH_CLASS, ScratchpadPanel, SelectionToolbar, SettingsPanel, TEXT_SIZE_CLASS, chapterMarker, copyText, defaultHref, downloadFile, groupByPart, mergeOptions, quoteBorderClass, scopeFor, scopeKey, swatchClass, useBook, useBookmarks, useChapterEngine, useHighlights, useHydrated, useIsDesktop, useLibrary, useMediaQuery, usePad, useProgress, useReader, useReaderChapter, useReaderStore, useSettings, useValueStore };
|
|
2797
|
+
//# sourceMappingURL=index.js.map
|
|
2798
|
+
//# sourceMappingURL=index.js.map
|