vestrycms 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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +383 -2
  3. package/dist/config/fields.d.ts +30 -0
  4. package/dist/config/fields.js +75 -0
  5. package/dist/config/index.d.ts +9 -0
  6. package/dist/config/index.js +18 -0
  7. package/dist/config/types.d.ts +412 -0
  8. package/dist/config/types.js +10 -0
  9. package/dist/core/api.d.ts +67 -0
  10. package/dist/core/api.js +543 -0
  11. package/dist/core/auth.d.ts +1597 -0
  12. package/dist/core/auth.js +264 -0
  13. package/dist/core/cache.d.ts +27 -0
  14. package/dist/core/cache.js +52 -0
  15. package/dist/core/entries.d.ts +19 -0
  16. package/dist/core/entries.js +127 -0
  17. package/dist/core/index-types.d.ts +20 -0
  18. package/dist/core/index-types.js +1 -0
  19. package/dist/core/index.d.ts +13 -0
  20. package/dist/core/index.js +13 -0
  21. package/dist/core/mail.d.ts +28 -0
  22. package/dist/core/mail.js +62 -0
  23. package/dist/core/paths.d.ts +35 -0
  24. package/dist/core/paths.js +148 -0
  25. package/dist/core/roles.d.ts +24 -0
  26. package/dist/core/roles.js +15 -0
  27. package/dist/core/service.d.ts +102 -0
  28. package/dist/core/service.js +478 -0
  29. package/dist/core/source.d.ts +82 -0
  30. package/dist/core/source.js +75 -0
  31. package/dist/core/sources/github-app.d.ts +26 -0
  32. package/dist/core/sources/github-app.js +161 -0
  33. package/dist/core/sources/github.d.ts +40 -0
  34. package/dist/core/sources/github.js +278 -0
  35. package/dist/core/sources/local.d.ts +16 -0
  36. package/dist/core/sources/local.js +98 -0
  37. package/dist/core/values.d.ts +22 -0
  38. package/dist/core/values.js +123 -0
  39. package/dist/env.d.ts +19 -0
  40. package/dist/index.d.ts +18 -0
  41. package/dist/index.js +167 -0
  42. package/dist/integration/build-index.d.ts +8 -0
  43. package/dist/integration/build-index.js +67 -0
  44. package/dist/integration/client-deps.d.ts +10 -0
  45. package/dist/integration/client-deps.js +43 -0
  46. package/dist/integration/dev-middleware.d.ts +6 -0
  47. package/dist/integration/dev-middleware.js +48 -0
  48. package/dist/integration/formatter.d.ts +7 -0
  49. package/dist/integration/formatter.js +56 -0
  50. package/dist/mdx/expressions.d.ts +14 -0
  51. package/dist/mdx/expressions.js +141 -0
  52. package/dist/mdx/index.d.ts +4 -0
  53. package/dist/mdx/index.js +4 -0
  54. package/dist/mdx/parse.d.ts +6 -0
  55. package/dist/mdx/parse.js +401 -0
  56. package/dist/mdx/serialize.d.ts +9 -0
  57. package/dist/mdx/serialize.js +485 -0
  58. package/dist/mdx/types.d.ts +42 -0
  59. package/dist/mdx/types.js +6 -0
  60. package/dist/pages/admin.astro +27 -0
  61. package/dist/pages/api.d.ts +3 -0
  62. package/dist/pages/api.js +31 -0
  63. package/dist/ui/App.d.ts +4 -0
  64. package/dist/ui/App.js +70 -0
  65. package/dist/ui/api.d.ts +83 -0
  66. package/dist/ui/api.js +97 -0
  67. package/dist/ui/auth-client.d.ts +27 -0
  68. package/dist/ui/auth-client.js +29 -0
  69. package/dist/ui/components/AssetPicker.d.ts +27 -0
  70. package/dist/ui/components/AssetPicker.js +97 -0
  71. package/dist/ui/components/Icon.d.ts +11 -0
  72. package/dist/ui/components/Icon.js +46 -0
  73. package/dist/ui/components/Layout.d.ts +15 -0
  74. package/dist/ui/components/Layout.js +87 -0
  75. package/dist/ui/components/editor/EditorContext.d.ts +15 -0
  76. package/dist/ui/components/editor/EditorContext.js +9 -0
  77. package/dist/ui/components/editor/NodeViews.d.ts +4 -0
  78. package/dist/ui/components/editor/NodeViews.js +127 -0
  79. package/dist/ui/components/editor/RichTextEditor.d.ts +24 -0
  80. package/dist/ui/components/editor/RichTextEditor.js +230 -0
  81. package/dist/ui/components/editor/extensions.d.ts +30 -0
  82. package/dist/ui/components/editor/extensions.js +248 -0
  83. package/dist/ui/components/editor/schema.d.ts +12 -0
  84. package/dist/ui/components/editor/schema.js +37 -0
  85. package/dist/ui/components/fields/CodeInput.d.ts +10 -0
  86. package/dist/ui/components/fields/CodeInput.js +8 -0
  87. package/dist/ui/components/fields/CodeMirrorInput.d.ts +2 -0
  88. package/dist/ui/components/fields/CodeMirrorInput.js +44 -0
  89. package/dist/ui/components/fields/FieldRenderer.d.ts +41 -0
  90. package/dist/ui/components/fields/FieldRenderer.js +299 -0
  91. package/dist/ui/components/fields/ImageTools.d.ts +31 -0
  92. package/dist/ui/components/fields/ImageTools.js +296 -0
  93. package/dist/ui/components/fields/MarkdownField.d.ts +7 -0
  94. package/dist/ui/components/fields/MarkdownField.js +16 -0
  95. package/dist/ui/components/fields/TreeInput.d.ts +13 -0
  96. package/dist/ui/components/fields/TreeInput.js +221 -0
  97. package/dist/ui/components/ui.d.ts +63 -0
  98. package/dist/ui/components/ui.js +113 -0
  99. package/dist/ui/context.d.ts +17 -0
  100. package/dist/ui/context.js +13 -0
  101. package/dist/ui/lib/format.d.ts +5 -0
  102. package/dist/ui/lib/format.js +11 -0
  103. package/dist/ui/lib/utils.d.ts +24 -0
  104. package/dist/ui/lib/utils.js +255 -0
  105. package/dist/ui/main.d.ts +1 -0
  106. package/dist/ui/main.js +11 -0
  107. package/dist/ui/pages/Assets.d.ts +1 -0
  108. package/dist/ui/pages/Assets.js +69 -0
  109. package/dist/ui/pages/CollectionList.d.ts +6 -0
  110. package/dist/ui/pages/CollectionList.js +115 -0
  111. package/dist/ui/pages/Dashboard.d.ts +1 -0
  112. package/dist/ui/pages/Dashboard.js +57 -0
  113. package/dist/ui/pages/EntryEditor.d.ts +3 -0
  114. package/dist/ui/pages/EntryEditor.js +313 -0
  115. package/dist/ui/pages/Login.d.ts +6 -0
  116. package/dist/ui/pages/Login.js +88 -0
  117. package/dist/ui/pages/Users.d.ts +1 -0
  118. package/dist/ui/pages/Users.js +102 -0
  119. package/dist/ui/styles.css +2554 -0
  120. package/package.json +123 -3
@@ -0,0 +1,485 @@
1
+ import { gfmToMarkdown } from "mdast-util-gfm";
2
+ import { mdxToMarkdown } from "mdast-util-mdx";
3
+ import { defaultHandlers, toMarkdown, } from "mdast-util-to-markdown";
4
+ import { isPlainString, valueToSource } from "./expressions.js";
5
+ /**
6
+ * Note every identifier-like word in a piece of JS source. Words inside
7
+ * strings or comments count too, which at worst keeps an unused import.
8
+ */
9
+ function scan(source, used) {
10
+ for (const m of source.matchAll(IDENTIFIERS))
11
+ used.identifiers.add(m[0]);
12
+ }
13
+ const IDENTIFIER = "[\\p{ID_Start}$_][\\p{ID_Continue}$\\u200c\\u200d]*";
14
+ const IDENTIFIERS = new RegExp(IDENTIFIER, "gu");
15
+ const IS_IDENTIFIER = new RegExp(`^${IDENTIFIER}$`, "u");
16
+ export function serializeMdx(doc, options = {}) {
17
+ const isMdx = options.format !== "md";
18
+ const used = { components: new Set(), identifiers: new Set() };
19
+ const root = {
20
+ type: "root",
21
+ children: blocks(doc.content ?? [], used),
22
+ };
23
+ const imports = isMdx ? importStatements(used, options) : [];
24
+ if (imports.length) {
25
+ root.children.unshift({
26
+ type: "mdxjsEsm",
27
+ value: imports.join("\n"),
28
+ });
29
+ }
30
+ const mdx = mdxToMarkdown({ quote: '"' });
31
+ const libFlow = findHandler(mdx, "mdxJsxFlowElement");
32
+ let out = toMarkdown(root, {
33
+ extensions: isMdx ? [mdx, gfmToMarkdown()] : [gfmToMarkdown()],
34
+ bullet: "-",
35
+ bulletOther: "*",
36
+ emphasis: "_",
37
+ strong: "*",
38
+ fences: true,
39
+ rule: "-",
40
+ listItemIndent: "one",
41
+ resourceLink: true,
42
+ handlers: {
43
+ rawHtml: (node) => node.value,
44
+ rawInlineHtml: (node) => node.value,
45
+ ...(libFlow ? { mdxJsxFlowElement: flowElementHandler(libFlow) } : {}),
46
+ list: listHandler(defaultHandlers.list),
47
+ },
48
+ });
49
+ // Place a blank line after the import block like Prettier/MDX conventions.
50
+ out = out.replace(/\s+$/, "");
51
+ return out ? `${out}\n` : "";
52
+ }
53
+ function findHandler(ext, name) {
54
+ const own = ext.handlers?.[name];
55
+ if (typeof own === "function")
56
+ return own;
57
+ for (const sub of ext.extensions ?? []) {
58
+ const found = findHandler(sub, name);
59
+ if (found)
60
+ return found;
61
+ }
62
+ return undefined;
63
+ }
64
+ /**
65
+ * Block components with children are written with blank lines around the
66
+ * children and no indentation:
67
+ *
68
+ * <Article>
69
+ *
70
+ * ## Heading
71
+ *
72
+ * </Article>
73
+ *
74
+ * The library default indents children, which Prettier's MDX parser then
75
+ * reflows as JSX text.
76
+ */
77
+ function flowElementHandler(lib) {
78
+ return (node, parent, state, info) => {
79
+ const el = node;
80
+ if (!el.children?.length)
81
+ return lib(node, parent, state, info);
82
+ const open = lib({ ...el, children: [] }, parent, state, info).replace(/\s*\/>$/, ">");
83
+ const children = state.containerFlow(el, info);
84
+ return `${open}\n\n${children}\n\n</${el.name ?? ""}>`;
85
+ };
86
+ }
87
+ /**
88
+ * A tight list is written without blank lines, but some neighbouring blocks
89
+ * need one to stay apart (two paragraphs would merge, kind 7 HTML would join
90
+ * the paragraph before it, ...), and a blank line anywhere in a list makes it
91
+ * loose when read back. Ask the serializer's own join rules whether any
92
+ * neighbours in the list or its items need a blank line, and if so write the
93
+ * whole list loose.
94
+ */
95
+ function listHandler(lib) {
96
+ return (node, parent, state, info) => {
97
+ const list = node;
98
+ if (!list.spread &&
99
+ [list, ...list.children].some((p) => needsBlankLine(p, state))) {
100
+ list.spread = true;
101
+ for (const item of list.children)
102
+ item.spread = true;
103
+ }
104
+ return lib(node, parent, state, info);
105
+ };
106
+ }
107
+ /** Mirrors mdast-util-to-markdown's `between`: tight only if a join says 0. */
108
+ function needsBlankLine(parent, state) {
109
+ const children = parent.children;
110
+ for (let i = 1; i < children.length; i++) {
111
+ let index = state.join.length;
112
+ let lines;
113
+ while (index--) {
114
+ const result = state.join[index](children[i - 1], children[i], parent, state);
115
+ if (result === true || result === 1)
116
+ break;
117
+ if (typeof result === "number" || result === false) {
118
+ lines = result === false ? 1 : result;
119
+ break;
120
+ }
121
+ }
122
+ if (lines !== 0)
123
+ return true;
124
+ }
125
+ return false;
126
+ }
127
+ function importStatements(used, options) {
128
+ const lines = new Map();
129
+ const add = (name, spec) => {
130
+ if (spec)
131
+ lines.set(name, importLine(spec));
132
+ };
133
+ // Components get the registry's import, falling back to the source's.
134
+ for (const name of used.components) {
135
+ const reg = options.components?.[name];
136
+ add(name, reg?.import
137
+ ? { name, from: reg.import, named: reg.namedImport }
138
+ : options.existingImports?.[name]);
139
+ }
140
+ // Anything else the source imported stays while it is still referenced.
141
+ for (const [name, spec] of Object.entries(options.existingImports ?? {}))
142
+ if (!lines.has(name) && used.identifiers.has(name))
143
+ add(name, spec);
144
+ const originalOrder = Object.keys(options.existingImports ?? {});
145
+ return [...lines.entries()]
146
+ .sort(([a], [b]) => {
147
+ const ia = originalOrder.indexOf(a);
148
+ const ib = originalOrder.indexOf(b);
149
+ if (ia !== -1 && ib !== -1)
150
+ return ia - ib;
151
+ if (ia !== -1)
152
+ return -1;
153
+ if (ib !== -1)
154
+ return 1;
155
+ return a.localeCompare(b);
156
+ })
157
+ .map(([, l]) => l);
158
+ }
159
+ function importLine(spec) {
160
+ const from = JSON.stringify(spec.from);
161
+ if (!spec.named)
162
+ return `import ${spec.name} from ${from};`;
163
+ const imported = spec.imported === undefined || spec.imported === spec.name
164
+ ? ""
165
+ : IS_IDENTIFIER.test(spec.imported)
166
+ ? `${spec.imported} as `
167
+ : `${JSON.stringify(spec.imported)} as `;
168
+ return `import { ${imported}${spec.name} } from ${from};`;
169
+ }
170
+ function blocks(nodes, used) {
171
+ const out = [];
172
+ for (const node of nodes) {
173
+ const converted = block(node, used);
174
+ if (converted)
175
+ out.push(converted);
176
+ }
177
+ return out;
178
+ }
179
+ function block(node, used) {
180
+ switch (node.type) {
181
+ case "paragraph": {
182
+ const children = inlines(node.content ?? [], used);
183
+ if (!children.length)
184
+ return null;
185
+ return { type: "paragraph", children: children };
186
+ }
187
+ case "heading":
188
+ return {
189
+ type: "heading",
190
+ depth: clampDepth(Number(node.attrs?.level ?? 1)),
191
+ children: inlines(node.content ?? [], used),
192
+ };
193
+ case "horizontalRule":
194
+ return { type: "thematicBreak" };
195
+ case "blockquote":
196
+ return {
197
+ type: "blockquote",
198
+ children: blocks(node.content ?? [], used),
199
+ };
200
+ case "bulletList":
201
+ case "orderedList": {
202
+ const ordered = node.type === "orderedList";
203
+ // Lists that must be loose to keep their content apart are loosened
204
+ // while serializing; see listHandler.
205
+ const spread = node.attrs?.spread === true;
206
+ const items = (node.content ?? []).map((item) => {
207
+ const checked = item.attrs?.checked;
208
+ return {
209
+ type: "listItem",
210
+ spread,
211
+ checked: typeof checked === "boolean" ? checked : null,
212
+ children: blocks(item.content ?? [], used),
213
+ };
214
+ });
215
+ return {
216
+ type: "list",
217
+ ordered,
218
+ start: ordered ? Number(node.attrs?.start ?? 1) : null,
219
+ spread,
220
+ children: items,
221
+ };
222
+ }
223
+ case "codeBlock":
224
+ return {
225
+ type: "code",
226
+ lang: node.attrs?.language ?? null,
227
+ meta: node.attrs?.meta ?? null,
228
+ value: (node.content ?? []).map((t) => t.text ?? "").join(""),
229
+ };
230
+ case "rawHtml":
231
+ return { type: "html", value: String(node.attrs?.value ?? "") };
232
+ case "table":
233
+ return table(node, used);
234
+ case "mdxComponent":
235
+ return jsxElement(node, used, false);
236
+ case "footnoteDefinition":
237
+ return {
238
+ type: "footnoteDefinition",
239
+ ...footnoteLabel(node),
240
+ children: blocks(node.content ?? [], used),
241
+ };
242
+ case "mdxExpression": {
243
+ const value = String(node.attrs?.value ?? "");
244
+ scan(value, used);
245
+ return { type: "mdxFlowExpression", value };
246
+ }
247
+ case "mdxEsm": {
248
+ const value = String(node.attrs?.value ?? "");
249
+ scan(value, used);
250
+ return { type: "mdxjsEsm", value };
251
+ }
252
+ case "image":
253
+ return {
254
+ type: "paragraph",
255
+ children: inlines([node], used),
256
+ };
257
+ default: {
258
+ const children = inlines([node], used);
259
+ return children.length
260
+ ? { type: "paragraph", children: children }
261
+ : null;
262
+ }
263
+ }
264
+ }
265
+ function footnoteLabel(node) {
266
+ const label = String(node.attrs?.label ?? node.attrs?.identifier ?? "");
267
+ const identifier = String(node.attrs?.identifier ?? label.toLowerCase());
268
+ return { identifier, label };
269
+ }
270
+ function clampDepth(n) {
271
+ return Math.min(6, Math.max(1, Math.round(n)));
272
+ }
273
+ function table(node, used) {
274
+ const rows = node.content ?? [];
275
+ const align = [];
276
+ const children = rows.map((row, rowIdx) => ({
277
+ type: "tableRow",
278
+ children: (row.content ?? []).map((cell, colIdx) => {
279
+ if (rowIdx === 0)
280
+ align[colIdx] = cell.attrs?.align ?? null;
281
+ const paragraphs = cell.content ?? [];
282
+ const inline = [];
283
+ paragraphs.forEach((p, i) => {
284
+ if (i > 0)
285
+ inline.push({ type: "hardBreak" });
286
+ inline.push(...(p.type === "paragraph" ? (p.content ?? []) : [p]));
287
+ });
288
+ return {
289
+ type: "tableCell",
290
+ children: inlines(inline, used),
291
+ };
292
+ }),
293
+ }));
294
+ return { type: "table", align, children };
295
+ }
296
+ function propsToAttributes(node, used, inline = false) {
297
+ const props = (node.attrs?.props ?? {});
298
+ const spreads = (node.attrs?.spreads ?? []);
299
+ const attrs = [];
300
+ for (const [name, value] of Object.entries(props)) {
301
+ if (value === undefined)
302
+ continue;
303
+ if (value === true) {
304
+ attrs.push({ type: "mdxJsxAttribute", name, value: null });
305
+ }
306
+ else if (isPlainString(value)) {
307
+ attrs.push({ type: "mdxJsxAttribute", name, value });
308
+ }
309
+ else {
310
+ // Block components print each attribute on its own line, indented
311
+ // two spaces, so nested lines are indented relative to that.
312
+ const source = valueToSource(value, inline ? "" : " ");
313
+ scan(source, used);
314
+ attrs.push({
315
+ type: "mdxJsxAttribute",
316
+ name,
317
+ value: { type: "mdxJsxAttributeValueExpression", value: source },
318
+ });
319
+ }
320
+ }
321
+ for (const s of spreads) {
322
+ scan(s, used);
323
+ attrs.push({ type: "mdxJsxExpressionAttribute", value: s });
324
+ }
325
+ return attrs;
326
+ }
327
+ function jsxElement(node, used, inline) {
328
+ const name = String(node.attrs?.name ?? "");
329
+ if (name && /^[A-Z]/.test(name))
330
+ used.components.add(name);
331
+ // `<ui.Card />` needs the import of `ui`.
332
+ scan(name, used);
333
+ const attributes = propsToAttributes(node, used, inline);
334
+ if (inline) {
335
+ return {
336
+ type: "mdxJsxTextElement",
337
+ name: name || null,
338
+ attributes,
339
+ children: inlines(node.content ?? [], used),
340
+ };
341
+ }
342
+ return {
343
+ type: "mdxJsxFlowElement",
344
+ name: name || null,
345
+ attributes,
346
+ children: blocks(node.content ?? [], used),
347
+ };
348
+ }
349
+ const MARK_ORDER = [
350
+ "link",
351
+ "superscript",
352
+ "subscript",
353
+ "bold",
354
+ "italic",
355
+ "strike",
356
+ "code",
357
+ ];
358
+ /**
359
+ * Convert a flat run of inline PM nodes (text with marks) into nested mdast
360
+ * phrasing content. At each step the mark covering the longest run of
361
+ * following nodes becomes the outer wrapper, so `**a <sup>6</sup> b**`
362
+ * keeps bold outermost.
363
+ */
364
+ function inlines(nodes, used) {
365
+ return group(nodes, used);
366
+ }
367
+ function markKey(m) {
368
+ return JSON.stringify([m.type, m.attrs ?? {}]);
369
+ }
370
+ function hasMark(node, key) {
371
+ return (node.marks ?? []).some((m) => markKey(m) === key);
372
+ }
373
+ function stripMark(node, key) {
374
+ return {
375
+ ...node,
376
+ marks: (node.marks ?? []).filter((m) => markKey(m) !== key),
377
+ };
378
+ }
379
+ function group(nodes, used) {
380
+ const out = [];
381
+ let i = 0;
382
+ while (i < nodes.length) {
383
+ const node = nodes[i];
384
+ const marks = node.marks ?? [];
385
+ if (!marks.length) {
386
+ out.push(...leaf(node, used));
387
+ i++;
388
+ continue;
389
+ }
390
+ // Pick the mark with the longest run from here.
391
+ let best = null;
392
+ let bestLen = 0;
393
+ for (const m of marks) {
394
+ const key = markKey(m);
395
+ let j = i;
396
+ while (j < nodes.length && hasMark(nodes[j], key))
397
+ j++;
398
+ const len = j - i;
399
+ const better = len > bestLen ||
400
+ (len === bestLen &&
401
+ best !== null &&
402
+ MARK_ORDER.indexOf(m.type) <
403
+ MARK_ORDER.indexOf(best.type));
404
+ if (better) {
405
+ best = m;
406
+ bestLen = len;
407
+ }
408
+ }
409
+ const key = markKey(best);
410
+ const run = nodes.slice(i, i + bestLen).map((n) => stripMark(n, key));
411
+ const children = group(run, used);
412
+ out.push(wrap(best, children));
413
+ i += bestLen;
414
+ }
415
+ return out;
416
+ }
417
+ function wrap(mark, children) {
418
+ switch (mark.type) {
419
+ case "bold":
420
+ return { type: "strong", children };
421
+ case "italic":
422
+ return { type: "emphasis", children };
423
+ case "strike":
424
+ return { type: "delete", children };
425
+ case "link":
426
+ return {
427
+ type: "link",
428
+ url: String(mark.attrs?.href ?? ""),
429
+ title: mark.attrs?.title ?? null,
430
+ children,
431
+ };
432
+ case "superscript":
433
+ return {
434
+ type: "mdxJsxTextElement",
435
+ name: "sup",
436
+ attributes: [],
437
+ children,
438
+ };
439
+ case "subscript":
440
+ return {
441
+ type: "mdxJsxTextElement",
442
+ name: "sub",
443
+ attributes: [],
444
+ children,
445
+ };
446
+ case "code": {
447
+ const text = children.map((c) => ("value" in c ? c.value : "")).join("");
448
+ return { type: "inlineCode", value: text };
449
+ }
450
+ default:
451
+ return { type: "paragraph", children };
452
+ }
453
+ }
454
+ function leaf(node, used) {
455
+ switch (node.type) {
456
+ case "text":
457
+ return node.text ? [{ type: "text", value: node.text }] : [];
458
+ case "hardBreak":
459
+ return [{ type: "break" }];
460
+ case "image":
461
+ return [
462
+ {
463
+ type: "image",
464
+ url: String(node.attrs?.src ?? ""),
465
+ alt: node.attrs?.alt ?? null,
466
+ title: node.attrs?.title ?? null,
467
+ },
468
+ ];
469
+ case "rawInlineHtml":
470
+ return [{ type: "html", value: String(node.attrs?.value ?? "") }];
471
+ case "mdxInlineComponent":
472
+ return [jsxElement(node, used, true)];
473
+ case "mdxInlineExpression": {
474
+ const value = String(node.attrs?.value ?? "");
475
+ scan(value, used);
476
+ return [{ type: "mdxTextExpression", value }];
477
+ }
478
+ case "footnoteReference":
479
+ return [{ type: "footnoteReference", ...footnoteLabel(node) }];
480
+ case "mdxComponent":
481
+ return [jsxElement(node, used, true)];
482
+ default:
483
+ return node.content ? inlines(node.content, used) : [];
484
+ }
485
+ }
@@ -0,0 +1,42 @@
1
+ /** Minimal ProseMirror JSON shapes, shared by the converter and the editor. */
2
+ export interface PMMark {
3
+ type: string;
4
+ attrs?: Record<string, unknown>;
5
+ }
6
+ export interface PMNode {
7
+ type: string;
8
+ attrs?: Record<string, unknown>;
9
+ content?: PMNode[];
10
+ marks?: PMMark[];
11
+ text?: string;
12
+ }
13
+ /** A non-literal JSX attribute value, kept verbatim as source. */
14
+ export interface RawExpression {
15
+ $raw: string;
16
+ }
17
+ export declare function isRawExpression(v: unknown): v is RawExpression;
18
+ export type PropValue = unknown;
19
+ export interface ImportSpec {
20
+ /** Local identifier, e.g. `Highlights`. */
21
+ name: string;
22
+ /** Module specifier. */
23
+ from: string;
24
+ /** Default or named import. */
25
+ named?: boolean;
26
+ /** Exported name of a named import when it differs from `name` (`import { imported as name }`). */
27
+ imported?: string;
28
+ }
29
+ export interface ParsedDocument {
30
+ doc: PMNode;
31
+ /** Imports found in the source, keyed by local name. */
32
+ imports: Record<string, ImportSpec>;
33
+ }
34
+ export interface ComponentRegistry {
35
+ /** Component name → import spec to emit when it is used. */
36
+ [name: string]: {
37
+ import?: string;
38
+ namedImport?: boolean;
39
+ inline?: boolean;
40
+ children?: boolean | "text";
41
+ };
42
+ }
@@ -0,0 +1,6 @@
1
+ export function isRawExpression(v) {
2
+ return (typeof v === "object" &&
3
+ v !== null &&
4
+ "$raw" in v &&
5
+ typeof v.$raw === "string");
6
+ }
@@ -0,0 +1,27 @@
1
+ ---
2
+ /// <reference path="../env.d.ts" />
3
+ import { basePath, name } from "virtual:vestry/runtime";
4
+
5
+ export const prerender = false;
6
+ ---
7
+
8
+ <!doctype html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8" />
12
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
13
+ <meta name="robots" content="noindex, nofollow" />
14
+ <meta name="color-scheme" content="light" />
15
+ <title>{name}</title>
16
+ <link
17
+ rel="icon"
18
+ href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%231e293b'/%3E%3Cpath d='M9 9l7 14 7-14' stroke='%23f8fafc' stroke-width='3.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"
19
+ />
20
+ </head>
21
+ <body>
22
+ <div id="vestry-root" data-base={basePath} data-name={name}></div>
23
+ <script>
24
+ import "../ui/main.js";
25
+ </script>
26
+ </body>
27
+ </html>
@@ -0,0 +1,3 @@
1
+ import type { APIRoute } from "astro";
2
+ export declare const prerender = false;
3
+ export declare const ALL: APIRoute;
@@ -0,0 +1,31 @@
1
+ import config from "virtual:vestry/config";
2
+ import index from "virtual:vestry/index";
3
+ import { basePath } from "virtual:vestry/runtime";
4
+ import { createApi } from "../core/api.js";
5
+ export const prerender = false;
6
+ function readEnv(key) {
7
+ // Cloudflare (nodejs_compat populates process.env), Node adapters, and
8
+ // anything else that exposes a process.env object.
9
+ const fromProcess = globalThis.process?.env?.[key];
10
+ if (fromProcess !== undefined)
11
+ return fromProcess;
12
+ const fromMeta = import.meta.env?.[key];
13
+ return fromMeta;
14
+ }
15
+ const api = createApi({ config, index, basePath, env: readEnv });
16
+ /**
17
+ * Cloudflare bindings (the D1 database) come from `cloudflare:workers`; the
18
+ * adapter's `locals.runtime.env` was removed in Astro 6 and now throws.
19
+ * Imported lazily and by variable so other runtimes just get no bindings.
20
+ */
21
+ async function bindings() {
22
+ try {
23
+ const specifier = "cloudflare:workers";
24
+ const mod = (await import(/* @vite-ignore */ specifier));
25
+ return mod.env;
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
31
+ export const ALL = async ({ request }) => api.handle(request, { bindings: await bindings() });
@@ -0,0 +1,4 @@
1
+ export declare function App({ basePath, name }: {
2
+ basePath: string;
3
+ name: string;
4
+ }): import("react").JSX.Element;
package/dist/ui/App.js ADDED
@@ -0,0 +1,70 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { QueryClient, QueryClientProvider, useQuery, } from "@tanstack/react-query";
3
+ import { useEffect, useMemo } from "react";
4
+ import { BrowserRouter, Navigate, Route, Routes, useLocation, } from "react-router";
5
+ import { createClient, UNAUTHORIZED_EVENT } from "./api.js";
6
+ import { createVestryAuthClient } from "./auth-client.js";
7
+ import { Shell } from "./components/Layout.js";
8
+ import { Alert, Loading, ToastProvider } from "./components/ui.js";
9
+ import { VestryContext } from "./context.js";
10
+ import { AssetsPage } from "./pages/Assets.js";
11
+ import { CollectionListPage } from "./pages/CollectionList.js";
12
+ import { DashboardPage } from "./pages/Dashboard.js";
13
+ import { CollectionEntryPage, NewEntryPage, SingletonPage, } from "./pages/EntryEditor.js";
14
+ import { LoginPage } from "./pages/Login.js";
15
+ import { UsersPage } from "./pages/Users.js";
16
+ export function App({ basePath, name }) {
17
+ const queryClient = useMemo(() => new QueryClient({
18
+ defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: false } },
19
+ }), []);
20
+ return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ToastProvider, { children: _jsx(BrowserRouter, { basename: basePath, children: _jsx(Root, { basePath: basePath, name: name }) }) }) }));
21
+ }
22
+ function Root({ basePath, name }) {
23
+ const api = useMemo(() => createClient(basePath), [basePath]);
24
+ const authClient = useMemo(() => createVestryAuthClient(basePath), [basePath]);
25
+ const location = useLocation();
26
+ const configQuery = useQuery({
27
+ queryKey: ["config"],
28
+ queryFn: () => api.config(),
29
+ staleTime: Infinity,
30
+ });
31
+ // When a session expires mid-use the API answers 401; reloading the config
32
+ // drops the user and the router falls back to the sign-in page.
33
+ const refetch = configQuery.refetch;
34
+ useEffect(() => {
35
+ const onUnauthorized = () => void refetch();
36
+ window.addEventListener(UNAUTHORIZED_EVENT, onUnauthorized);
37
+ return () => window.removeEventListener(UNAUTHORIZED_EVENT, onUnauthorized);
38
+ }, [refetch]);
39
+ if (configQuery.isLoading)
40
+ return _jsx(Loading, { label: "Loading Vestry\u2026" });
41
+ if (configQuery.error || !configQuery.data) {
42
+ return (_jsx("div", { className: "v-page", children: _jsxs(Alert, { kind: "error", children: ["Could not load the Vestry configuration:", " ", configQuery.error?.message] }) }));
43
+ }
44
+ const client = configQuery.data;
45
+ const value = {
46
+ basePath,
47
+ name: client.config.name ?? name,
48
+ api,
49
+ authClient,
50
+ client,
51
+ config: client.config,
52
+ refresh: async () => {
53
+ await configQuery.refetch();
54
+ },
55
+ };
56
+ if (!client.user) {
57
+ return (_jsx(VestryContext.Provider, { value: value, children: _jsxs(Routes, { children: [_jsx(Route, { path: "/login", element: _jsx(LoginPage, {}) }), _jsx(Route, { path: "*", element: _jsx(Navigate, { to: `/login?next=${encodeURIComponent(basePath + location.pathname + location.search)}`, replace: true }) })] }) }));
58
+ }
59
+ return (_jsx(VestryContext.Provider, { value: value, children: _jsxs(Routes, { children: [_jsx(Route, { path: "/login", element: _jsx(Navigate, { to: afterLogin(location.search, basePath), replace: true }) }), _jsx(Route, { path: "*", element: _jsx(Shell, { children: _jsxs(Routes, { children: [_jsx(Route, { index: true, element: _jsx(DashboardPage, {}) }), _jsx(Route, { path: "collections/:collection", element: _jsx(CollectionListPage, {}) }), _jsx(Route, { path: "collections/:collection/new", element: _jsx(NewEntryPage, {}) }), _jsx(Route, { path: "collections/:collection/*", element: _jsx(CollectionEntryPage, {}) }), _jsx(Route, { path: "singletons/:singleton", element: _jsx(SingletonPage, {}) }), _jsx(Route, { path: "assets/:container", element: _jsx(AssetsPage, {}) }), _jsx(Route, { path: "users", element: _jsx(UsersPage, {}) }), _jsx(Route, { path: "*", element: _jsx(Navigate, { to: "/", replace: true }) })] }) }) })] }) }));
60
+ }
61
+ /** Where to go after sign-in: the `next` the login page was sent with, made router-relative. */
62
+ function afterLogin(search, basePath) {
63
+ const next = new URLSearchParams(search).get("next");
64
+ if (!next || !next.startsWith("/") || next.startsWith("//"))
65
+ return "/";
66
+ const base = basePath.replace(/\/$/, "");
67
+ const underBase = next === base || next.startsWith(`${base}/`) || next.startsWith(`${base}?`);
68
+ const rest = underBase ? next.slice(base.length) : next;
69
+ return rest.startsWith("/") ? rest : `/${rest}`;
70
+ }