zreader 0.0.0-stage → 0.1.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 +15 -0
- package/LICENSE +21 -0
- package/README.md +234 -2
- 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 +272 -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 +262 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/server/index.d.ts +58 -0
- package/dist/next/server/index.js +44 -0
- package/dist/next/server/index.js.map +1 -0
- package/dist/react/index.d.ts +376 -0
- package/dist/react/index.js +2793 -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 +41 -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 +225 -4
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { rehypeBlocks } from '../chunk-3WQBK6DE.js';
|
|
2
|
+
export { rehypeBlocks } from '../chunk-3WQBK6DE.js';
|
|
3
|
+
export { hashString } from '../chunk-ISI35TCM.js';
|
|
4
|
+
import { mergeConfig } from '../chunk-RNSAFDZ5.js';
|
|
5
|
+
export { defineConfig, glob, z } from '../chunk-RNSAFDZ5.js';
|
|
6
|
+
import '../chunk-DYQNGV46.js';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { readFile, stat, mkdir, writeFile } from 'fs/promises';
|
|
10
|
+
import { resolve, extname, join, basename } from 'path';
|
|
11
|
+
import matter from 'gray-matter';
|
|
12
|
+
import { glob } from 'tinyglobby';
|
|
13
|
+
import { compile, run } from '@mdx-js/mdx';
|
|
14
|
+
import * as jsxRuntime from 'react/jsx-runtime';
|
|
15
|
+
import rehypeParse from 'rehype-parse';
|
|
16
|
+
import rehypeRaw from 'rehype-raw';
|
|
17
|
+
import rehypeStringify from 'rehype-stringify';
|
|
18
|
+
import remarkGfm from 'remark-gfm';
|
|
19
|
+
import remarkParse from 'remark-parse';
|
|
20
|
+
import remarkRehype from 'remark-rehype';
|
|
21
|
+
import { unified } from 'unified';
|
|
22
|
+
import DOMPurify from 'isomorphic-dompurify';
|
|
23
|
+
|
|
24
|
+
var FORBID_TAGS = ["style", "form", "button", "textarea", "select", "option", "object", "embed", "base", "link", "meta"];
|
|
25
|
+
var FORBID_ATTR = ["style", "srcset"];
|
|
26
|
+
function hostAllowed(src, allow) {
|
|
27
|
+
try {
|
|
28
|
+
const u = new URL(src, "https://invalid.local");
|
|
29
|
+
if (u.protocol !== "https:") return false;
|
|
30
|
+
return allow.some((h) => u.hostname === h || u.hostname.endsWith(`.${h}`));
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function sanitizeHtml(html, options = {}) {
|
|
36
|
+
const allowIframes = options.allowIframes ?? [];
|
|
37
|
+
const addTags = [...options.allowTags ?? [], ...allowIframes.length ? ["iframe"] : []];
|
|
38
|
+
const addAttr = ["target", ...options.allowAttributes ?? [], ...allowIframes.length ? ["allowfullscreen", "loading"] : []];
|
|
39
|
+
const purify = DOMPurify;
|
|
40
|
+
purify.addHook("uponSanitizeElement", (node, data) => {
|
|
41
|
+
if (data.tagName === "input" && node.getAttribute?.("type") !== "checkbox") {
|
|
42
|
+
node.parentNode?.removeChild(node);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (data.tagName === "iframe") {
|
|
46
|
+
const el = node;
|
|
47
|
+
const src = el.getAttribute?.("src") ?? "";
|
|
48
|
+
if (!hostAllowed(src, allowIframes)) el.parentNode?.removeChild(el);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
purify.addHook("afterSanitizeAttributes", (node) => {
|
|
52
|
+
const el = node;
|
|
53
|
+
if (el.hasAttribute?.("target")) {
|
|
54
|
+
if (el.tagName === "A" && el.getAttribute("target") === "_blank") el.setAttribute("rel", "noopener noreferrer");
|
|
55
|
+
else el.removeAttribute("target");
|
|
56
|
+
}
|
|
57
|
+
if (el.tagName === "IFRAME") {
|
|
58
|
+
el.setAttribute("sandbox", "allow-scripts allow-same-origin allow-presentation");
|
|
59
|
+
el.setAttribute("loading", "lazy");
|
|
60
|
+
}
|
|
61
|
+
if (el.tagName === "INPUT") {
|
|
62
|
+
for (const a of Array.from(el.attributes)) if (!["type", "checked", "disabled"].includes(a.name)) el.removeAttribute(a.name);
|
|
63
|
+
el.setAttribute("disabled", "");
|
|
64
|
+
}
|
|
65
|
+
el.removeAttribute?.("data-block-id");
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
return purify.sanitize(html, {
|
|
69
|
+
USE_PROFILES: { html: true },
|
|
70
|
+
ADD_TAGS: addTags,
|
|
71
|
+
ADD_ATTR: addAttr,
|
|
72
|
+
FORBID_TAGS: FORBID_TAGS.filter((t) => !addTags.includes(t)),
|
|
73
|
+
FORBID_ATTR: FORBID_ATTR.filter((a) => !addAttr.includes(a)),
|
|
74
|
+
ALLOW_DATA_ATTR: false
|
|
75
|
+
});
|
|
76
|
+
} finally {
|
|
77
|
+
purify.removeHook("uponSanitizeElement");
|
|
78
|
+
purify.removeHook("afterSanitizeAttributes");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/content/pipeline.ts
|
|
83
|
+
async function annotateHtml(html) {
|
|
84
|
+
const file = await unified().use(rehypeParse, { fragment: true }).use(rehypeBlocks).use(rehypeStringify).process(html);
|
|
85
|
+
return { kind: "html", html: String(file), ...file.data.reader };
|
|
86
|
+
}
|
|
87
|
+
async function renderHtml(html, opts = {}) {
|
|
88
|
+
return annotateHtml(sanitizeHtml(html, opts.sanitize));
|
|
89
|
+
}
|
|
90
|
+
async function renderMarkdown(markdown, opts = {}) {
|
|
91
|
+
const file = await unified().use(remarkParse).use(remarkGfm).use(opts.remarkPlugins ?? []).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(opts.rehypePlugins ?? []).use(rehypeStringify).process(markdown);
|
|
92
|
+
return renderHtml(String(file), opts);
|
|
93
|
+
}
|
|
94
|
+
async function compileMdx(source, opts = {}) {
|
|
95
|
+
const file = await compile(source, {
|
|
96
|
+
outputFormat: "function-body",
|
|
97
|
+
development: false,
|
|
98
|
+
remarkPlugins: [remarkGfm, ...opts.remarkPlugins ?? []],
|
|
99
|
+
rehypePlugins: [...opts.rehypePlugins ?? [], rehypeBlocks]
|
|
100
|
+
});
|
|
101
|
+
return { kind: "mdx", code: String(file), ...file.data.reader };
|
|
102
|
+
}
|
|
103
|
+
var mdxModuleCache = /* @__PURE__ */ new Map();
|
|
104
|
+
function runMdx(code) {
|
|
105
|
+
let p = mdxModuleCache.get(code);
|
|
106
|
+
if (!p) {
|
|
107
|
+
p = run(code, { ...jsxRuntime, baseUrl: import.meta.url }).then((m) => m.default);
|
|
108
|
+
mdxModuleCache.set(code, p);
|
|
109
|
+
if (mdxModuleCache.size > 200) mdxModuleCache.delete(mdxModuleCache.keys().next().value);
|
|
110
|
+
}
|
|
111
|
+
return p;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/content/loader.ts
|
|
115
|
+
var ContentError = class extends Error {
|
|
116
|
+
constructor(message, file) {
|
|
117
|
+
super(file ? `${message}
|
|
118
|
+
at ${file}` : message);
|
|
119
|
+
this.file = file;
|
|
120
|
+
this.name = "ContentError";
|
|
121
|
+
}
|
|
122
|
+
file;
|
|
123
|
+
};
|
|
124
|
+
var FORMATS = { ".md": "md", ".markdown": "md", ".mdx": "mdx", ".html": "html", ".htm": "html" };
|
|
125
|
+
var ORDER_PREFIX = /^(\d+)[-_. ]+/;
|
|
126
|
+
function titleCase(slug) {
|
|
127
|
+
return slug.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
128
|
+
}
|
|
129
|
+
function validate(schema, value, file, what) {
|
|
130
|
+
const r = schema.safeParse(value);
|
|
131
|
+
if (!r.success) {
|
|
132
|
+
const issues = r.error?.issues ?? [];
|
|
133
|
+
const msg = issues.map((i) => ` - ${i.path.map(String).join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
134
|
+
throw new ContentError(`Invalid ${what}:
|
|
135
|
+
${msg || String(r.error)}`, file);
|
|
136
|
+
}
|
|
137
|
+
return r.data;
|
|
138
|
+
}
|
|
139
|
+
async function loadBooks(config) {
|
|
140
|
+
const { loader, schemas, includeDrafts } = config.content;
|
|
141
|
+
const base = resolve(config.root, loader.base);
|
|
142
|
+
if (!existsSync(base)) return [];
|
|
143
|
+
const files = await glob(loader.pattern, { cwd: base, ignore: loader.ignore, onlyFiles: true });
|
|
144
|
+
files.sort();
|
|
145
|
+
const byBook = /* @__PURE__ */ new Map();
|
|
146
|
+
for (const rel of files) {
|
|
147
|
+
const parts = rel.split(/[\\/]/);
|
|
148
|
+
if (parts.length < 2) continue;
|
|
149
|
+
const format = FORMATS[extname(rel).toLowerCase()];
|
|
150
|
+
if (!format) continue;
|
|
151
|
+
const bookId = parts[0];
|
|
152
|
+
const file = join(base, rel);
|
|
153
|
+
const [raw, st] = await Promise.all([readFile(file, "utf8"), stat(file)]);
|
|
154
|
+
const fm = matter(raw);
|
|
155
|
+
const data = validate(schemas.chapter, fm.data, file, "chapter frontmatter");
|
|
156
|
+
const stem = basename(rel, extname(rel));
|
|
157
|
+
const prefix = ORDER_PREFIX.exec(stem);
|
|
158
|
+
const id = stem.replace(ORDER_PREFIX, "");
|
|
159
|
+
const draft = data.draft === true;
|
|
160
|
+
if (draft && !includeDrafts) continue;
|
|
161
|
+
const list = byBook.get(bookId) ?? [];
|
|
162
|
+
if (list.some((c) => c.id === id)) throw new ContentError(`Duplicate chapter id "${id}" in book "${bookId}"`, file);
|
|
163
|
+
list.push({
|
|
164
|
+
id,
|
|
165
|
+
bookId,
|
|
166
|
+
title: String(data.title ?? titleCase(id)),
|
|
167
|
+
order: typeof data.order === "number" ? data.order : prefix ? Number(prefix[1]) : Number.MAX_SAFE_INTEGER,
|
|
168
|
+
description: typeof data.description === "string" ? data.description : void 0,
|
|
169
|
+
part: typeof data.part === "string" ? data.part : void 0,
|
|
170
|
+
label: typeof data.label === "string" ? data.label : void 0,
|
|
171
|
+
number: data.number != null ? String(data.number) : void 0,
|
|
172
|
+
file,
|
|
173
|
+
format,
|
|
174
|
+
data,
|
|
175
|
+
body: fm.content,
|
|
176
|
+
draft,
|
|
177
|
+
mtimeMs: st.mtimeMs
|
|
178
|
+
});
|
|
179
|
+
byBook.set(bookId, list);
|
|
180
|
+
}
|
|
181
|
+
const books = [];
|
|
182
|
+
for (const [bookId, chapters] of byBook) {
|
|
183
|
+
const dir = join(base, bookId);
|
|
184
|
+
const metaFile = join(dir, "book.json");
|
|
185
|
+
let rawMeta = { title: titleCase(bookId) };
|
|
186
|
+
if (existsSync(metaFile)) {
|
|
187
|
+
try {
|
|
188
|
+
rawMeta = JSON.parse(await readFile(metaFile, "utf8"));
|
|
189
|
+
} catch (e) {
|
|
190
|
+
throw new ContentError(`book.json is not valid JSON: ${e.message}`, metaFile);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const data = validate(schemas.book, rawMeta, metaFile, "book.json");
|
|
194
|
+
const explicit = Array.isArray(data.chapters) ? data.chapters : null;
|
|
195
|
+
chapters.sort((a, b) => {
|
|
196
|
+
if (explicit) {
|
|
197
|
+
const ia = explicit.indexOf(a.id);
|
|
198
|
+
const ib = explicit.indexOf(b.id);
|
|
199
|
+
if (ia !== ib) return (ia < 0 ? Infinity : ia) - (ib < 0 ? Infinity : ib);
|
|
200
|
+
}
|
|
201
|
+
return a.order - b.order || a.file.localeCompare(b.file);
|
|
202
|
+
});
|
|
203
|
+
books.push({
|
|
204
|
+
id: bookId,
|
|
205
|
+
title: String(data.title ?? titleCase(bookId)),
|
|
206
|
+
authors: Array.isArray(data.authors) ? data.authors : [],
|
|
207
|
+
description: typeof data.description === "string" ? data.description : void 0,
|
|
208
|
+
cover: typeof data.cover === "string" ? data.cover : void 0,
|
|
209
|
+
subtitle: typeof data.subtitle === "string" ? data.subtitle : void 0,
|
|
210
|
+
chapters: chapters.map(({ id, title, order, description, part, label, number }) => ({
|
|
211
|
+
id,
|
|
212
|
+
title,
|
|
213
|
+
order,
|
|
214
|
+
...description ? { description } : {},
|
|
215
|
+
...part ? { part } : {},
|
|
216
|
+
...label ? { label } : {},
|
|
217
|
+
...number ? { number } : {}
|
|
218
|
+
})),
|
|
219
|
+
data,
|
|
220
|
+
dir,
|
|
221
|
+
chapterEntries: chapters
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
books.sort((a, b) => a.title.localeCompare(b.title));
|
|
225
|
+
return books;
|
|
226
|
+
}
|
|
227
|
+
var PIPELINE_VERSION = "2";
|
|
228
|
+
var memory = /* @__PURE__ */ new Map();
|
|
229
|
+
function pluginSignature(list) {
|
|
230
|
+
return list.map((p) => Array.isArray(p) ? `${p[0]?.name}:${JSON.stringify(p.slice(1))}` : p?.name).join(",");
|
|
231
|
+
}
|
|
232
|
+
function cacheKey(entry, config) {
|
|
233
|
+
const c = config.content;
|
|
234
|
+
return createHash("sha256").update(
|
|
235
|
+
[
|
|
236
|
+
PIPELINE_VERSION,
|
|
237
|
+
entry.file,
|
|
238
|
+
entry.mtimeMs,
|
|
239
|
+
entry.body.length,
|
|
240
|
+
createHash("sha1").update(entry.body).digest("hex"),
|
|
241
|
+
JSON.stringify(c.sanitize),
|
|
242
|
+
pluginSignature(c.remarkPlugins),
|
|
243
|
+
pluginSignature(c.rehypePlugins)
|
|
244
|
+
].join("|")
|
|
245
|
+
).digest("hex").slice(0, 32);
|
|
246
|
+
}
|
|
247
|
+
async function compileChapter(entry, config) {
|
|
248
|
+
const key = cacheKey(entry, config);
|
|
249
|
+
const hit = memory.get(key);
|
|
250
|
+
if (hit) return hit;
|
|
251
|
+
const p = (async () => {
|
|
252
|
+
const dir = resolve(config.root, config.content.cacheDir);
|
|
253
|
+
const diskFile = join(dir, `${key}.json`);
|
|
254
|
+
try {
|
|
255
|
+
return JSON.parse(await readFile(diskFile, "utf8"));
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
const opts = {
|
|
259
|
+
sanitize: config.content.sanitize,
|
|
260
|
+
remarkPlugins: config.content.remarkPlugins,
|
|
261
|
+
rehypePlugins: config.content.rehypePlugins
|
|
262
|
+
};
|
|
263
|
+
let out;
|
|
264
|
+
try {
|
|
265
|
+
out = entry.format === "mdx" ? await compileMdx(entry.body, opts) : entry.format === "md" ? await renderMarkdown(entry.body, opts) : await renderHtml(entry.body, opts);
|
|
266
|
+
} catch (e) {
|
|
267
|
+
throw new ContentError(`Failed to compile chapter: ${e.message}`, entry.file);
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
await mkdir(dir, { recursive: true });
|
|
271
|
+
await writeFile(diskFile, JSON.stringify(out));
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
})();
|
|
276
|
+
memory.set(key, p);
|
|
277
|
+
p.catch(() => memory.delete(key));
|
|
278
|
+
return p;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/content/index.ts
|
|
282
|
+
async function globalConfig() {
|
|
283
|
+
const mod = await import('zreader/virtual-config');
|
|
284
|
+
return mod.default;
|
|
285
|
+
}
|
|
286
|
+
var bookCache = /* @__PURE__ */ new WeakMap();
|
|
287
|
+
function cachedBooks(config) {
|
|
288
|
+
if (process.env.NODE_ENV === "development") return loadBooks(config);
|
|
289
|
+
let p = bookCache.get(config);
|
|
290
|
+
if (!p) {
|
|
291
|
+
p = loadBooks(config);
|
|
292
|
+
bookCache.set(config, p);
|
|
293
|
+
p.catch(() => bookCache.delete(config));
|
|
294
|
+
}
|
|
295
|
+
return p;
|
|
296
|
+
}
|
|
297
|
+
function createApi(getConfig) {
|
|
298
|
+
const api = {
|
|
299
|
+
config: getConfig,
|
|
300
|
+
async getBooks() {
|
|
301
|
+
return cachedBooks(await getConfig());
|
|
302
|
+
},
|
|
303
|
+
/** Plain, serializable book metadata (safe to pass to client components). */
|
|
304
|
+
async getBookMetas() {
|
|
305
|
+
return (await api.getBooks()).map(toBookMeta);
|
|
306
|
+
},
|
|
307
|
+
async getBook(bookId) {
|
|
308
|
+
return (await api.getBooks()).find((b) => b.id === bookId) ?? null;
|
|
309
|
+
},
|
|
310
|
+
async getChapter(bookId, chapterId) {
|
|
311
|
+
const book = await api.getBook(bookId);
|
|
312
|
+
if (!book) return null;
|
|
313
|
+
const index = book.chapterEntries.findIndex((c) => c.id === chapterId);
|
|
314
|
+
if (index < 0) return null;
|
|
315
|
+
return {
|
|
316
|
+
book,
|
|
317
|
+
chapter: book.chapterEntries[index],
|
|
318
|
+
index,
|
|
319
|
+
prev: book.chapters[index - 1] ?? null,
|
|
320
|
+
next: book.chapters[index + 1] ?? null
|
|
321
|
+
};
|
|
322
|
+
},
|
|
323
|
+
async renderChapter(entry) {
|
|
324
|
+
const compiled = await compileChapter(entry, await getConfig());
|
|
325
|
+
if (compiled.kind === "mdx") {
|
|
326
|
+
const { code, ...rest } = compiled;
|
|
327
|
+
return { ...rest, kind: "mdx", Content: await runMdx(code) };
|
|
328
|
+
}
|
|
329
|
+
return compiled;
|
|
330
|
+
},
|
|
331
|
+
/** For `generateStaticParams`. Param names are configurable. */
|
|
332
|
+
async getStaticParams(names = {}) {
|
|
333
|
+
const b = names.book ?? "book";
|
|
334
|
+
const c = names.chapter ?? "chapter";
|
|
335
|
+
const out = [];
|
|
336
|
+
for (const book of await api.getBooks()) {
|
|
337
|
+
for (const ch of book.chapterEntries) out.push({ [b]: book.id, [c]: ch.id });
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
return api;
|
|
343
|
+
}
|
|
344
|
+
function toBookMeta(book) {
|
|
345
|
+
return {
|
|
346
|
+
id: book.id,
|
|
347
|
+
title: book.title,
|
|
348
|
+
...book.subtitle ? { subtitle: book.subtitle } : {},
|
|
349
|
+
authors: book.authors,
|
|
350
|
+
...book.description ? { description: book.description } : {},
|
|
351
|
+
...book.cover ? { cover: book.cover } : {},
|
|
352
|
+
chapters: book.chapters
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
var defaultApi = createApi(globalConfig);
|
|
356
|
+
var getBooks = defaultApi.getBooks;
|
|
357
|
+
var getBookMetas = defaultApi.getBookMetas;
|
|
358
|
+
var getBook = defaultApi.getBook;
|
|
359
|
+
var getChapter = defaultApi.getChapter;
|
|
360
|
+
var renderChapter = defaultApi.renderChapter;
|
|
361
|
+
var getStaticParams = defaultApi.getStaticParams;
|
|
362
|
+
var getReaderConfig = globalConfig;
|
|
363
|
+
function defineBooks(inline = {}) {
|
|
364
|
+
if (inline && "root" in inline && "reader" in inline) {
|
|
365
|
+
const resolved = inline;
|
|
366
|
+
return createApi(async () => resolved);
|
|
367
|
+
}
|
|
368
|
+
let memo;
|
|
369
|
+
return createApi(
|
|
370
|
+
() => memo ??= globalConfig().then((g) => ({
|
|
371
|
+
...g,
|
|
372
|
+
content: mergeConfig(g.content, inline)
|
|
373
|
+
}))
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export { ContentError, annotateHtml, compileChapter, compileMdx, defineBooks, getBook, getBookMetas, getBooks, getChapter, getReaderConfig, getStaticParams, loadBooks, renderChapter, renderHtml, renderMarkdown, runMdx, sanitizeHtml, toBookMeta };
|
|
378
|
+
//# sourceMappingURL=index.js.map
|
|
379
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/content/sanitize.ts","../../src/content/pipeline.ts","../../src/content/loader.ts","../../src/content/index.ts"],"names":["globFiles"],"mappings":"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","file":"index.js","sourcesContent":["import DOMPurify from \"isomorphic-dompurify\";\nimport type { SanitizeOptions } from \"../config/index\";\n\nconst FORBID_TAGS = [\"style\", \"form\", \"button\", \"textarea\", \"select\", \"option\", \"object\", \"embed\", \"base\", \"link\", \"meta\"];\nconst FORBID_ATTR = [\"style\", \"srcset\"];\n\nfunction hostAllowed(src: string, allow: string[]): boolean {\n try {\n const u = new URL(src, \"https://invalid.local\");\n if (u.protocol !== \"https:\") return false;\n return allow.some((h) => u.hostname === h || u.hostname.endsWith(`.${h}`));\n } catch {\n return false;\n }\n}\n\n/**\n * Sanitize untrusted HTML with DOMPurify (html profile, no scripts, no event\n * handlers, no javascript: URLs, no inline styles, no forms). Iframes are\n * stripped unless their https host is in `allowIframes`. External links opened\n * in a new tab get `rel=\"noopener noreferrer\"`.\n */\nexport function sanitizeHtml(html: string, options: SanitizeOptions = {}): string {\n const allowIframes = options.allowIframes ?? [];\n const addTags = [...(options.allowTags ?? []), ...(allowIframes.length ? [\"iframe\"] : [])];\n const addAttr = [\"target\", ...(options.allowAttributes ?? []), ...(allowIframes.length ? [\"allowfullscreen\", \"loading\"] : [])];\n\n const purify = DOMPurify;\n purify.addHook(\"uponSanitizeElement\", (node, data) => {\n // GFM task lists render checkboxes; any other input is dropped.\n if (data.tagName === \"input\" && (node as Element).getAttribute?.(\"type\") !== \"checkbox\") {\n (node as Element).parentNode?.removeChild(node as Element);\n return;\n }\n if (data.tagName === \"iframe\") {\n const el = node as Element;\n const src = el.getAttribute?.(\"src\") ?? \"\";\n if (!hostAllowed(src, allowIframes)) el.parentNode?.removeChild(el);\n }\n });\n purify.addHook(\"afterSanitizeAttributes\", (node) => {\n const el = node as Element;\n if (el.hasAttribute?.(\"target\")) {\n if (el.tagName === \"A\" && el.getAttribute(\"target\") === \"_blank\") el.setAttribute(\"rel\", \"noopener noreferrer\");\n else el.removeAttribute(\"target\");\n }\n if (el.tagName === \"IFRAME\") {\n el.setAttribute(\"sandbox\", \"allow-scripts allow-same-origin allow-presentation\");\n el.setAttribute(\"loading\", \"lazy\");\n }\n if (el.tagName === \"INPUT\") {\n for (const a of Array.from(el.attributes)) if (![\"type\", \"checked\", \"disabled\"].includes(a.name)) el.removeAttribute(a.name);\n el.setAttribute(\"disabled\", \"\");\n }\n // Block ids are ours to assign.\n el.removeAttribute?.(\"data-block-id\");\n });\n try {\n return purify.sanitize(html, {\n USE_PROFILES: { html: true },\n ADD_TAGS: addTags,\n ADD_ATTR: addAttr,\n FORBID_TAGS: FORBID_TAGS.filter((t) => !addTags.includes(t)),\n FORBID_ATTR: FORBID_ATTR.filter((a) => !addAttr.includes(a)),\n ALLOW_DATA_ATTR: false,\n }) as unknown as string;\n } finally {\n purify.removeHook(\"uponSanitizeElement\");\n purify.removeHook(\"afterSanitizeAttributes\");\n }\n}\n","import { compile, run } from \"@mdx-js/mdx\";\nimport * as jsxRuntime from \"react/jsx-runtime\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeRaw from \"rehype-raw\";\nimport rehypeStringify from \"rehype-stringify\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified, type PluggableList } from \"unified\";\nimport type { ComponentType } from \"react\";\nimport type { SanitizeOptions } from \"../config/index\";\nimport { rehypeBlocks, type BlocksData } from \"../rehype/index\";\nimport { sanitizeHtml } from \"./sanitize\";\n\nexport type ChapterFormat = \"md\" | \"mdx\" | \"html\";\n\nexport interface PipelineOptions {\n sanitize?: SanitizeOptions;\n remarkPlugins?: PluggableList;\n rehypePlugins?: PluggableList;\n}\n\nexport interface HtmlResult extends BlocksData {\n kind: \"html\";\n html: string;\n}\n\nexport interface MdxCompiled extends BlocksData {\n kind: \"mdx\";\n /** Compiled function body; run with `runMdx`. Serializable (cacheable). */\n code: string;\n}\n\n/** Add block ids / heading ids / TOC to already-sanitized HTML. */\nexport async function annotateHtml(html: string): Promise<HtmlResult> {\n const file = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeBlocks)\n .use(rehypeStringify)\n .process(html);\n return { kind: \"html\", html: String(file), ...(file.data as { reader: BlocksData }).reader };\n}\n\n/** Untrusted HTML string → sanitized, annotated HTML. */\nexport async function renderHtml(html: string, opts: PipelineOptions = {}): Promise<HtmlResult> {\n return annotateHtml(sanitizeHtml(html, opts.sanitize));\n}\n\n/**\n * Markdown (GFM, inline HTML allowed) → sanitized, annotated HTML.\n * Inline HTML is parsed, then the whole output goes through DOMPurify.\n */\nexport async function renderMarkdown(markdown: string, opts: PipelineOptions = {}): Promise<HtmlResult> {\n const file = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(opts.remarkPlugins ?? [])\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeRaw)\n .use(opts.rehypePlugins ?? [])\n .use(rehypeStringify)\n .process(markdown);\n return renderHtml(String(file), opts);\n}\n\n/**\n * Trusted MDX → compiled function body. MDX is code: only compile files you\n * author (e.g. from your repo), never user-supplied input.\n */\nexport async function compileMdx(source: string, opts: PipelineOptions = {}): Promise<MdxCompiled> {\n const file = await compile(source, {\n outputFormat: \"function-body\",\n development: false,\n remarkPlugins: [remarkGfm, ...(opts.remarkPlugins ?? [])],\n rehypePlugins: [...(opts.rehypePlugins ?? []), rehypeBlocks],\n });\n return { kind: \"mdx\", code: String(file), ...(file.data as { reader: BlocksData }).reader };\n}\n\nexport type MdxComponents = Record<string, ComponentType<Record<string, unknown>>>;\nexport type MdxContent = ComponentType<{ components?: MdxComponents }>;\n\nconst mdxModuleCache = new Map<string, Promise<MdxContent>>();\n\nexport function runMdx(code: string): Promise<MdxContent> {\n let p = mdxModuleCache.get(code);\n if (!p) {\n p = run(code, { ...(jsxRuntime as unknown as Record<string, unknown>), baseUrl: import.meta.url } as never).then((m) => m.default as MdxContent);\n mdxModuleCache.set(code, p);\n if (mdxModuleCache.size > 200) mdxModuleCache.delete(mdxModuleCache.keys().next().value!);\n }\n return p;\n}\n","import { createHash } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { basename, extname, join, relative, resolve, sep } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { glob as globFiles } from \"tinyglobby\";\nimport type { ResolvedReaderConfig } from \"../config/index\";\nimport type { BookMeta, ChapterMeta } from \"../core/types\";\nimport { compileMdx, renderHtml, renderMarkdown, type ChapterFormat, type HtmlResult, type MdxCompiled } from \"./pipeline\";\n\nexport interface ChapterEntry extends ChapterMeta {\n bookId: string;\n file: string;\n format: ChapterFormat;\n /** Validated frontmatter. */\n data: Record<string, unknown>;\n body: string;\n draft: boolean;\n mtimeMs: number;\n}\n\nexport interface BookEntry extends BookMeta {\n /** Validated book.json. */\n data: Record<string, unknown>;\n dir: string;\n chapterEntries: ChapterEntry[];\n}\n\nexport class ContentError extends Error {\n constructor(\n message: string,\n public readonly file?: string,\n ) {\n super(file ? `${message}\\n at ${file}` : message);\n this.name = \"ContentError\";\n }\n}\n\nconst FORMATS: Record<string, ChapterFormat> = { \".md\": \"md\", \".markdown\": \"md\", \".mdx\": \"mdx\", \".html\": \"html\", \".htm\": \"html\" };\nconst ORDER_PREFIX = /^(\\d+)[-_. ]+/;\n\nfunction titleCase(slug: string) {\n return slug.replace(/[-_]+/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction validate(schema: { safeParse(v: unknown): { success: boolean; data?: unknown; error?: unknown } }, value: unknown, file: string, what: string) {\n const r = schema.safeParse(value);\n if (!r.success) {\n const issues = (r.error as { issues?: { path: PropertyKey[]; message: string }[] })?.issues ?? [];\n const msg = issues.map((i) => ` - ${i.path.map(String).join(\".\") || \"(root)\"}: ${i.message}`).join(\"\\n\");\n throw new ContentError(`Invalid ${what}:\\n${msg || String(r.error)}`, file);\n }\n return r.data as Record<string, unknown>;\n}\n\n/** Read every book + chapter frontmatter (bodies are compiled lazily). */\nexport async function loadBooks(config: ResolvedReaderConfig): Promise<BookEntry[]> {\n const { loader, schemas, includeDrafts } = config.content;\n const base = resolve(config.root, loader.base);\n if (!existsSync(base)) return [];\n const files = await globFiles(loader.pattern, { cwd: base, ignore: loader.ignore, onlyFiles: true });\n files.sort();\n\n const byBook = new Map<string, ChapterEntry[]>();\n for (const rel of files) {\n const parts = rel.split(/[\\\\/]/);\n if (parts.length < 2) continue; // files must live inside a book folder\n const format = FORMATS[extname(rel).toLowerCase()];\n if (!format) continue;\n const bookId = parts[0]!;\n const file = join(base, rel);\n const [raw, st] = await Promise.all([readFile(file, \"utf8\"), stat(file)]);\n const fm = matter(raw);\n const data = validate(schemas.chapter, fm.data, file, \"chapter frontmatter\");\n const stem = basename(rel, extname(rel));\n const prefix = ORDER_PREFIX.exec(stem);\n const id = stem.replace(ORDER_PREFIX, \"\");\n const draft = data.draft === true;\n if (draft && !includeDrafts) continue;\n const list = byBook.get(bookId) ?? [];\n if (list.some((c) => c.id === id)) throw new ContentError(`Duplicate chapter id \"${id}\" in book \"${bookId}\"`, file);\n list.push({\n id,\n bookId,\n title: String(data.title ?? titleCase(id)),\n order: typeof data.order === \"number\" ? data.order : prefix ? Number(prefix[1]) : Number.MAX_SAFE_INTEGER,\n description: typeof data.description === \"string\" ? data.description : undefined,\n part: typeof data.part === \"string\" ? data.part : undefined,\n label: typeof data.label === \"string\" ? data.label : undefined,\n number: data.number != null ? String(data.number) : undefined,\n file,\n format,\n data,\n body: fm.content,\n draft,\n mtimeMs: st.mtimeMs,\n });\n byBook.set(bookId, list);\n }\n\n const books: BookEntry[] = [];\n for (const [bookId, chapters] of byBook) {\n const dir = join(base, bookId);\n const metaFile = join(dir, \"book.json\");\n let rawMeta: unknown = { title: titleCase(bookId) };\n if (existsSync(metaFile)) {\n try {\n rawMeta = JSON.parse(await readFile(metaFile, \"utf8\"));\n } catch (e) {\n throw new ContentError(`book.json is not valid JSON: ${(e as Error).message}`, metaFile);\n }\n }\n const data = validate(schemas.book, rawMeta, metaFile, \"book.json\");\n const explicit = Array.isArray(data.chapters) ? (data.chapters as string[]) : null;\n chapters.sort((a, b) => {\n if (explicit) {\n const ia = explicit.indexOf(a.id);\n const ib = explicit.indexOf(b.id);\n if (ia !== ib) return (ia < 0 ? Infinity : ia) - (ib < 0 ? Infinity : ib);\n }\n return a.order - b.order || a.file.localeCompare(b.file);\n });\n books.push({\n id: bookId,\n title: String(data.title ?? titleCase(bookId)),\n authors: Array.isArray(data.authors) ? (data.authors as string[]) : [],\n description: typeof data.description === \"string\" ? data.description : undefined,\n cover: typeof data.cover === \"string\" ? data.cover : undefined,\n subtitle: typeof data.subtitle === \"string\" ? data.subtitle : undefined,\n chapters: chapters.map(({ id, title, order, description, part, label, number }) => ({\n id,\n title,\n order,\n ...(description ? { description } : {}),\n ...(part ? { part } : {}),\n ...(label ? { label } : {}),\n ...(number ? { number } : {}),\n })),\n data,\n dir,\n chapterEntries: chapters,\n });\n }\n books.sort((a, b) => a.title.localeCompare(b.title));\n return books;\n}\n\n// ------------------------------------------------------------- compile cache\n\nconst PIPELINE_VERSION = \"2\"; // bump when compiled output changes (e.g. block-id scheme)\nconst memory = new Map<string, Promise<HtmlResult | MdxCompiled>>();\n\nfunction pluginSignature(list: unknown[]): string {\n return list\n .map((p) => (Array.isArray(p) ? `${(p[0] as { name?: string })?.name}:${JSON.stringify(p.slice(1))}` : (p as { name?: string })?.name))\n .join(\",\");\n}\n\nfunction cacheKey(entry: ChapterEntry, config: ResolvedReaderConfig) {\n const c = config.content;\n return createHash(\"sha256\")\n .update(\n [\n PIPELINE_VERSION,\n entry.file,\n entry.mtimeMs,\n entry.body.length,\n createHash(\"sha1\").update(entry.body).digest(\"hex\"),\n JSON.stringify(c.sanitize),\n pluginSignature(c.remarkPlugins),\n pluginSignature(c.rehypePlugins),\n ].join(\"|\"),\n )\n .digest(\"hex\")\n .slice(0, 32);\n}\n\nexport async function compileChapter(entry: ChapterEntry, config: ResolvedReaderConfig): Promise<HtmlResult | MdxCompiled> {\n const key = cacheKey(entry, config);\n const hit = memory.get(key);\n if (hit) return hit;\n const p = (async () => {\n const dir = resolve(config.root, config.content.cacheDir);\n const diskFile = join(dir, `${key}.json`);\n try {\n return JSON.parse(await readFile(diskFile, \"utf8\")) as HtmlResult | MdxCompiled;\n } catch {\n /* miss */\n }\n const opts = {\n sanitize: config.content.sanitize,\n remarkPlugins: config.content.remarkPlugins as never,\n rehypePlugins: config.content.rehypePlugins as never,\n };\n let out: HtmlResult | MdxCompiled;\n try {\n out =\n entry.format === \"mdx\"\n ? await compileMdx(entry.body, opts)\n : entry.format === \"md\"\n ? await renderMarkdown(entry.body, opts)\n : await renderHtml(entry.body, opts);\n } catch (e) {\n throw new ContentError(`Failed to compile chapter: ${(e as Error).message}`, entry.file);\n }\n try {\n await mkdir(dir, { recursive: true });\n await writeFile(diskFile, JSON.stringify(out));\n } catch {\n /* cache is best-effort (read-only FS on some hosts) */\n }\n return out;\n })();\n memory.set(key, p);\n p.catch(() => memory.delete(key));\n return p;\n}\n\nexport function relativeToRoot(config: ResolvedReaderConfig, file: string) {\n return relative(config.root, file).split(sep).join(\"/\");\n}\n","/**\n * `zreader/content` — Astro-style content layer (server/build only).\n *\n * const books = await getBooks();\n * const { chapter } = await getChapter(\"my-book\", \"intro\");\n * const rendered = await renderChapter(chapter);\n */\nimport { mergeConfig, type ContentConfig, type ReaderUserConfig, type ResolvedReaderConfig } from \"../config/index\";\nimport type { BookMeta, ChapterMeta, TocEntry } from \"../core/types\";\nimport { compileChapter, loadBooks, type BookEntry, type ChapterEntry } from \"./loader\";\nimport { runMdx, type MdxContent } from \"./pipeline\";\nimport type { BlockInfo } from \"../rehype/index\";\n\nexport type RenderedChapter =\n | { kind: \"html\"; html: string; toc: TocEntry[]; blocks: BlockInfo[]; contentHash: string }\n | { kind: \"mdx\"; Content: MdxContent; toc: TocEntry[]; blocks: BlockInfo[]; contentHash: string };\n\nexport interface ChapterLookup {\n book: BookEntry;\n chapter: ChapterEntry;\n index: number;\n prev: ChapterMeta | null;\n next: ChapterMeta | null;\n}\n\nasync function globalConfig(): Promise<ResolvedReaderConfig> {\n const mod = await import(\"zreader/virtual-config\");\n return mod.default;\n}\n\nconst bookCache = new WeakMap<ResolvedReaderConfig, Promise<BookEntry[]>>();\n\nfunction cachedBooks(config: ResolvedReaderConfig): Promise<BookEntry[]> {\n if (process.env.NODE_ENV === \"development\") return loadBooks(config); // always fresh in dev\n let p = bookCache.get(config);\n if (!p) {\n p = loadBooks(config);\n bookCache.set(config, p);\n p.catch(() => bookCache.delete(config));\n }\n return p;\n}\n\nfunction createApi(getConfig: () => Promise<ResolvedReaderConfig>) {\n const api = {\n config: getConfig,\n\n async getBooks(): Promise<BookEntry[]> {\n return cachedBooks(await getConfig());\n },\n\n /** Plain, serializable book metadata (safe to pass to client components). */\n async getBookMetas(): Promise<BookMeta[]> {\n return (await api.getBooks()).map(toBookMeta);\n },\n\n async getBook(bookId: string): Promise<BookEntry | null> {\n return (await api.getBooks()).find((b) => b.id === bookId) ?? null;\n },\n\n async getChapter(bookId: string, chapterId: string): Promise<ChapterLookup | null> {\n const book = await api.getBook(bookId);\n if (!book) return null;\n const index = book.chapterEntries.findIndex((c) => c.id === chapterId);\n if (index < 0) return null;\n return {\n book,\n chapter: book.chapterEntries[index]!,\n index,\n prev: book.chapters[index - 1] ?? null,\n next: book.chapters[index + 1] ?? null,\n };\n },\n\n async renderChapter(entry: ChapterEntry): Promise<RenderedChapter> {\n const compiled = await compileChapter(entry, await getConfig());\n if (compiled.kind === \"mdx\") {\n const { code, ...rest } = compiled;\n return { ...rest, kind: \"mdx\", Content: await runMdx(code) };\n }\n return compiled;\n },\n\n /** For `generateStaticParams`. Param names are configurable. */\n async getStaticParams<B extends string = \"book\", C extends string = \"chapter\">(\n names: { book?: B; chapter?: C } = {},\n ): Promise<Array<Record<B | C, string>>> {\n const b = (names.book ?? \"book\") as B;\n const c = (names.chapter ?? \"chapter\") as C;\n const out: Array<Record<B | C, string>> = [];\n for (const book of await api.getBooks()) {\n for (const ch of book.chapterEntries) out.push({ [b]: book.id, [c]: ch.id } as Record<B | C, string>);\n }\n return out;\n },\n };\n return api;\n}\n\nexport function toBookMeta(book: BookEntry): BookMeta {\n return {\n id: book.id,\n title: book.title,\n ...(book.subtitle ? { subtitle: book.subtitle } : {}),\n authors: book.authors,\n ...(book.description ? { description: book.description } : {}),\n ...(book.cover ? { cover: book.cover } : {}),\n chapters: book.chapters,\n };\n}\n\nconst defaultApi = createApi(globalConfig);\n\nexport const getBooks = defaultApi.getBooks;\nexport const getBookMetas = defaultApi.getBookMetas;\nexport const getBook = defaultApi.getBook;\nexport const getChapter = defaultApi.getChapter;\nexport const renderChapter = defaultApi.renderChapter;\nexport const getStaticParams = defaultApi.getStaticParams;\nexport const getReaderConfig = globalConfig;\n\ntype InlineContent = ReaderUserConfig[\"content\"];\n\n/**\n * Inline form: merge content options on top of the global reader.config.ts.\n *\n * export const handbook = defineBooks({ loader: glob({ base: \"./content/handbook\" }) });\n * await handbook.getBooks();\n */\nexport function defineBooks(inline: InlineContent | ResolvedReaderConfig = {}) {\n if (inline && \"root\" in inline && \"reader\" in inline) {\n const resolved = inline as ResolvedReaderConfig;\n return createApi(async () => resolved);\n }\n let memo: Promise<ResolvedReaderConfig> | undefined;\n return createApi(\n () =>\n (memo ??= globalConfig().then((g) => ({\n ...g,\n content: mergeConfig<ContentConfig>(g.content, inline),\n }))),\n );\n}\n\nexport { loadBooks, compileChapter, ContentError, type BookEntry, type ChapterEntry } from \"./loader\";\nexport {\n renderMarkdown,\n renderHtml,\n annotateHtml,\n compileMdx,\n runMdx,\n type HtmlResult,\n type MdxCompiled,\n type MdxContent,\n type MdxComponents,\n type ChapterFormat,\n type PipelineOptions,\n} from \"./pipeline\";\nexport { sanitizeHtml } from \"./sanitize\";\nexport { rehypeBlocks, type BlockInfo, type BlocksData } from \"../rehype/index\";\nexport { hashString } from \"../core/blocks\";\nexport { glob, defineConfig, z } from \"../config/index\";\nexport type { ReaderUserConfig, ResolvedReaderConfig, SanitizeOptions, ContentConfig, GlobLoader } from \"../config/index\";\n"]}
|