@rigel-cms/rigel 0.1.0-beta.1 → 0.1.4-beta.1
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/README.md +8 -0
- package/package.json +7 -2
- package/src/config.ts +6 -0
- package/src/editor/App.svelte +113 -58
- package/src/editor/lib/BlockEditor.svelte +18 -18
- package/src/editor/lib/ImagePicker.svelte +7 -7
- package/src/editor/lib/MarkdownEditor.svelte +7 -7
- package/src/editor/lib/PreviewFrame.svelte +45 -0
- package/src/editor/lib/SchemaField.svelte +4 -4
- package/src/editor/lib/SchemaPanel.svelte +4 -4
- package/src/editor/lib/Select.svelte +3 -2
- package/src/editor/lib/SeoGuidance.svelte +1 -1
- package/src/editor/lib/Settings.svelte +27 -2
- package/src/editor/lib/SitePreview.svelte +9 -8
- package/src/editor/lib/TopBar.svelte +1 -1
- package/src/editor/lib/VisualEditor.svelte +10 -8
- package/src/editor/lib/github-auth.ts +4 -3
- package/src/editor/lib/github-backend.ts +3 -3
- package/src/editor/lib/preview-frame.ts +29 -0
- package/src/editor/lib/slugify.ts +7 -2
- package/src/editor/lib/storage.ts +30 -2
- package/src/editor/lib/tooltip.ts +120 -0
- package/src/editor/styles.css +11 -0
- package/src/manifest/collections.ts +27 -0
- package/src/manifest/types.ts +1 -0
- package/src/preview-urls.ts +9 -0
- package/src/preview.ts +12 -4
- package/src/routes/local-post.ts +11 -5
- package/src/vite/manifest-plugin.ts +2 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export function tooltipPosition(rect: Pick<DOMRect, "left" | "right" | "top" | "bottom">, width: number, height: number, viewportWidth: number, viewportHeight: number) {
|
|
2
|
+
return {
|
|
3
|
+
left: Math.max(8, Math.min((rect.left + rect.right - width) / 2, viewportWidth - width - 8)),
|
|
4
|
+
top: Math.max(8, Math.min(rect.top >= height + 14 ? rect.top - height - 6 : rect.bottom + 6, viewportHeight - height - 8)),
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let provider: ReturnType<typeof createTooltips> | undefined;
|
|
9
|
+
|
|
10
|
+
export function tooltipRoot(root: HTMLElement) {
|
|
11
|
+
provider ??= createTooltips();
|
|
12
|
+
const remove = provider.register(root);
|
|
13
|
+
return { destroy: remove };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createTooltips() {
|
|
17
|
+
let tip: HTMLDivElement | undefined;
|
|
18
|
+
let anchor: HTMLElement | null = null;
|
|
19
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
20
|
+
let opening: ReturnType<typeof setTimeout> | undefined;
|
|
21
|
+
let frame = 0;
|
|
22
|
+
let view: Window | null = null;
|
|
23
|
+
let pointerTime = 0;
|
|
24
|
+
let users = 0;
|
|
25
|
+
const id = "rigel-tooltip";
|
|
26
|
+
|
|
27
|
+
function hide() {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
clearTimeout(opening);
|
|
30
|
+
if (view) view.cancelAnimationFrame(frame);
|
|
31
|
+
if (anchor) {
|
|
32
|
+
const ids = (anchor.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(value => value && value !== id);
|
|
33
|
+
if (ids.length) anchor.setAttribute("aria-describedby", ids.join(" ")); else anchor.removeAttribute("aria-describedby");
|
|
34
|
+
}
|
|
35
|
+
anchor = null;
|
|
36
|
+
if (tip?.matches(":popover-open")) tip.hidePopover();
|
|
37
|
+
tip?.remove();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function update() {
|
|
41
|
+
if (!anchor || !tip || !view) return;
|
|
42
|
+
if (!anchor.isConnected || !anchor.checkVisibility() || !anchor.dataset.tooltip) { hide(); return; }
|
|
43
|
+
if (tip.textContent !== anchor.dataset.tooltip) tip.textContent = anchor.dataset.tooltip;
|
|
44
|
+
const position = tooltipPosition(anchor.getBoundingClientRect(), tip.offsetWidth, tip.offsetHeight, view.innerWidth, view.innerHeight);
|
|
45
|
+
tip.style.left = `${position.left}px`;
|
|
46
|
+
tip.style.top = `${position.top}px`;
|
|
47
|
+
frame = view.requestAnimationFrame(update);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function show(target: HTMLElement) {
|
|
51
|
+
hide();
|
|
52
|
+
if (!target.isConnected || !target.checkVisibility() || !target.dataset.tooltip) return;
|
|
53
|
+
const doc = target.ownerDocument;
|
|
54
|
+
tip ??= doc.createElement("div");
|
|
55
|
+
tip.id = id;
|
|
56
|
+
tip.className = "rigel-tooltip";
|
|
57
|
+
tip.setAttribute("role", "tooltip");
|
|
58
|
+
tip.popover = "manual";
|
|
59
|
+
anchor = target;
|
|
60
|
+
view = doc.defaultView;
|
|
61
|
+
const scope = target.closest("dialog, .rigel") ?? target.parentElement!;
|
|
62
|
+
scope.append(tip);
|
|
63
|
+
tip.textContent = target.dataset.tooltip;
|
|
64
|
+
target.setAttribute("aria-describedby", [...new Set([...(target.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean), id])].join(" "));
|
|
65
|
+
try { tip.showPopover(); update(); }
|
|
66
|
+
catch (error) {
|
|
67
|
+
if ((error as DOMException)?.name !== "InvalidStateError") throw error;
|
|
68
|
+
opening = setTimeout(() => { if (anchor !== target) return; if (!target.isConnected || !target.checkVisibility()) { hide(); return; } tip!.showPopover(); update(); }, 0);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function register(root: HTMLElement) {
|
|
73
|
+
users++;
|
|
74
|
+
const doc = root.ownerDocument;
|
|
75
|
+
const win = doc.defaultView!;
|
|
76
|
+
const target = (event: Event) => {
|
|
77
|
+
const element = (event.target as Element)?.closest?.<HTMLElement>("[data-tooltip]");
|
|
78
|
+
return element && root.contains(element) && element.closest(".rigel") ? element : null;
|
|
79
|
+
};
|
|
80
|
+
const over = (event: PointerEvent) => {
|
|
81
|
+
if (event.pointerType === "touch") return;
|
|
82
|
+
if (event.target === tip) { clearTimeout(timer); return; }
|
|
83
|
+
const element = target(event);
|
|
84
|
+
if (!element || element.contains(event.relatedTarget as Node | null) || element === anchor || element === doc.activeElement) return;
|
|
85
|
+
hide(); timer = setTimeout(() => show(element), 450);
|
|
86
|
+
};
|
|
87
|
+
const out = (event: PointerEvent) => {
|
|
88
|
+
const element = target(event);
|
|
89
|
+
if (element?.contains(event.relatedTarget as Node | null)) return;
|
|
90
|
+
if (event.relatedTarget === tip || anchor?.contains(event.relatedTarget as Node | null)) return;
|
|
91
|
+
if (anchor === doc.activeElement) return;
|
|
92
|
+
clearTimeout(timer); timer = setTimeout(hide, 150);
|
|
93
|
+
};
|
|
94
|
+
const focus = (event: FocusEvent) => {
|
|
95
|
+
const element = target(event);
|
|
96
|
+
if (element && Date.now() - pointerTime > 700) show(element);
|
|
97
|
+
};
|
|
98
|
+
const down = () => { pointerTime = Date.now(); hide(); };
|
|
99
|
+
const key = (event: KeyboardEvent) => {
|
|
100
|
+
pointerTime = 0;
|
|
101
|
+
if (event.key !== "Escape") return;
|
|
102
|
+
const visible = !!anchor;
|
|
103
|
+
hide();
|
|
104
|
+
if (visible) { event.preventDefault(); event.stopImmediatePropagation(); }
|
|
105
|
+
};
|
|
106
|
+
const listeners = [["pointerover", over], ["pointerout", out], ["focusin", focus], ["focusout", hide], ["pointerdown", down], ["click", hide], ["keydown", key]] as const;
|
|
107
|
+
for (const [name, listener] of listeners) root.addEventListener(name, listener as EventListener, true);
|
|
108
|
+
win.addEventListener("blur", hide);
|
|
109
|
+
win.addEventListener("resize", hide);
|
|
110
|
+
win.addEventListener("scroll", hide, true);
|
|
111
|
+
return () => {
|
|
112
|
+
hide();
|
|
113
|
+
for (const [name, listener] of listeners) root.removeEventListener(name, listener as EventListener, true);
|
|
114
|
+
win.removeEventListener("blur", hide); win.removeEventListener("resize", hide);
|
|
115
|
+
win.removeEventListener("scroll", hide, true);
|
|
116
|
+
if (!--users) { tip = undefined; provider = undefined; }
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { register };
|
|
120
|
+
}
|
package/src/editor/styles.css
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
.rigel, .rigel *, .rigel *::before, .rigel *::after { box-sizing: border-box; }
|
|
2
|
+
.rigel .rigel-tooltip { position: fixed; pointer-events: auto; inset: auto; margin: 0; padding: 0.375rem 0.625rem; max-width: min(20rem, calc(100vw - 16px)); max-height: calc(100dvh - 16px); overflow: auto; border: 1px solid var(--border-control); border-radius: var(--radius); background: var(--text); color: var(--panel); box-shadow: var(--shadow-md); font: 500 0.75rem/1.5 -apple-system, BlinkMacSystemFont, system-ui, sans-serif; overflow-wrap: anywhere; text-align: start; white-space: normal; }
|
|
3
|
+
.rigel .rigel-tooltip::backdrop { background: transparent; pointer-events: none; }
|
|
4
|
+
@media (forced-colors: active) { .rigel .rigel-tooltip { background: Canvas; color: CanvasText; border-color: CanvasText; } }
|
|
2
5
|
.rigel {
|
|
3
6
|
--rigel-50: #EFFBFF;
|
|
4
7
|
--rigel-100: #D9F4FF;
|
|
@@ -26,6 +29,9 @@
|
|
|
26
29
|
|
|
27
30
|
.rigel.connection-shell { align-items: center; justify-content: center; padding: 1.5rem; min-height: 100dvh; }
|
|
28
31
|
.rigel .connection-card { flex: none; width: 100%; max-width: 560px; margin: 0; padding: 2rem; background: var(--panel); border: 1px solid var(--border); overflow-wrap: anywhere; }
|
|
32
|
+
.rigel .connection-brand { display: flex; align-items: center; gap: 0.75rem; font-size: 1.5rem; font-weight: 650; letter-spacing: -0.04em; }
|
|
33
|
+
.rigel .brand-suffix { font-weight: 450; }
|
|
34
|
+
.rigel .connection-brand img { flex-shrink: 0; }
|
|
29
35
|
.rigel .connection-card h1 { margin: 1rem 0; font-size: 1.75rem; line-height: 1.2; }
|
|
30
36
|
.rigel .connection-card p { margin: 1rem 0; }
|
|
31
37
|
.rigel .connection-card :is(button, a.primary) { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 0.625rem 1rem; font: inherit; text-decoration: none; text-align: center; }
|
|
@@ -263,6 +269,7 @@
|
|
|
263
269
|
justify-content: center;
|
|
264
270
|
flex-shrink: 0;
|
|
265
271
|
}
|
|
272
|
+
.rigel .avatar img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
266
273
|
.rigel .uname { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
267
274
|
.rigel .user-details { display: flex; flex: 1; min-width: 0; flex-direction: column; gap: 0.125rem; }
|
|
268
275
|
.rigel .user-details small { color: var(--text-muted); font-size: 0.75rem; font-weight: 400; }
|
|
@@ -958,6 +965,10 @@
|
|
|
958
965
|
font-size: 0.875rem; font-weight: 600; text-transform: none;
|
|
959
966
|
letter-spacing: 0; color: var(--text); margin: 0 0 0.75rem;
|
|
960
967
|
}
|
|
968
|
+
.rigel .connection-details { margin: 0; }
|
|
969
|
+
.rigel .connection-details dt { margin-top: 1rem; font-size: 0.75rem; font-weight: 600; color: var(--text-2); }
|
|
970
|
+
.rigel .connection-details dd { margin: 0.375rem 0 0; font-size: 0.875rem; overflow-wrap: anywhere; }
|
|
971
|
+
.rigel .connection-details small { display: block; margin-top: 0.125rem; color: var(--text-muted); }
|
|
961
972
|
.rigel .settings-pane fieldset { border: none; padding: 0; margin: 0 0 1rem; }
|
|
962
973
|
.rigel .settings-pane fieldset.accessibility-options { margin-top: 1.5rem; }
|
|
963
974
|
.rigel .accessibility-options label.inline { margin-top: 0; }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CollectionField } from "./types";
|
|
2
|
+
import type { ContentDocument } from "../schema/types";
|
|
3
|
+
import { validateCollectionReferences } from "./references";
|
|
4
|
+
|
|
5
|
+
export function initialCollectionValues(fields: CollectionField[], title: string): Record<string, unknown> {
|
|
6
|
+
return Object.fromEntries(fields.map(field => [field.key,
|
|
7
|
+
field.type === "boolean" ? field.key === "draft" :
|
|
8
|
+
field.type === "tags" || (field.type === "reference" && field.multiple) ? [] :
|
|
9
|
+
field.type === "date" ? new Date().toISOString().slice(0, 10) :
|
|
10
|
+
field.key === "title" && field.type === "string" ? title : "",
|
|
11
|
+
]));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function validateCollectionValues(fields: CollectionField[], values: Record<string, unknown>, documents: ContentDocument[] = []): void {
|
|
15
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) throw new Error("Invalid article frontmatter");
|
|
16
|
+
for (const field of fields) {
|
|
17
|
+
const value = values[field.key];
|
|
18
|
+
if (field.required && (value === undefined || value === null || (typeof value === "string" && !value.trim()) || (Array.isArray(value) && !value.length))) throw new Error(`${field.label}: required`);
|
|
19
|
+
if (value === undefined || field.type === "reference") continue;
|
|
20
|
+
const valid = field.type === "boolean" ? typeof value === "boolean" :
|
|
21
|
+
field.type === "tags" ? Array.isArray(value) && value.every(tag => typeof tag === "string") :
|
|
22
|
+
field.type === "date" ? typeof value === "string" && (value === "" || (/^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value)) :
|
|
23
|
+
typeof value === "string";
|
|
24
|
+
if (!valid) throw new Error(`${field.label}: expected ${field.type}`);
|
|
25
|
+
}
|
|
26
|
+
validateCollectionReferences(fields, values, documents);
|
|
27
|
+
}
|
package/src/manifest/types.ts
CHANGED
package/src/preview-urls.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
export function safePreviewUrl(value: string, image = false): boolean {
|
|
2
2
|
return !!value && !/[ \t\r\n\f\v\u0000-\u001f\u007f\\]/.test(value) && !value.startsWith("//") && (!/^[a-z][a-z\d+.-]*:/i.test(value) || (image ? /^https?:/i : /^(https?:|mailto:)/i).test(value));
|
|
3
3
|
}
|
|
4
|
+
|
|
5
|
+
export function previewBaseUrl(value: unknown): string {
|
|
6
|
+
try {
|
|
7
|
+
if (typeof value !== "string" || !/^https?:\/\/[^/@]+(?:\/|$)/.test(value) || /[\s\u0000-\u001f\u007f\\%?#]/.test(value) || value.split("/").some(part => part === "." || part === "..")) throw new Error();
|
|
8
|
+
const url = new URL(value);
|
|
9
|
+
if (url.username || url.password || (url.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) throw new Error();
|
|
10
|
+
return `${url.origin}${url.pathname.replace(/\/$/, "")}/`;
|
|
11
|
+
} catch { throw new Error("rigel: preview.liveUrl and preview.draftUrl must be absolute HTTPS base URLs without credentials, query or fragment (HTTP allowed on loopback)"); }
|
|
12
|
+
}
|
package/src/preview.ts
CHANGED
|
@@ -2,7 +2,7 @@ import MarkdownIt from "markdown-it";
|
|
|
2
2
|
import DOMPurify from "dompurify";
|
|
3
3
|
import { pageRoute } from "./schema/paths";
|
|
4
4
|
import { renderArticle } from "./article-render";
|
|
5
|
-
import { safePreviewUrl } from "./preview-urls";
|
|
5
|
+
import { safePreviewUrl, previewBaseUrl } from "./preview-urls";
|
|
6
6
|
export { safePreviewUrl } from "./preview-urls";
|
|
7
7
|
|
|
8
8
|
export interface PreviewDocumentMapping {
|
|
@@ -11,6 +11,8 @@ export interface PreviewDocumentMapping {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface PreviewConfig {
|
|
14
|
+
liveUrl?: string;
|
|
15
|
+
draftUrl?: string;
|
|
14
16
|
pages?: Record<string, string>;
|
|
15
17
|
collections?: Record<string, { url: string; new: string }>;
|
|
16
18
|
documents?: Record<string, PreviewDocumentMapping>;
|
|
@@ -23,10 +25,16 @@ export interface PreviewTarget {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function previewPath(config: PreviewConfig, target: PreviewTarget, isNew = false): string | null {
|
|
26
|
-
if (target.kind === "document") return pageRoute(target.slug, config.documents?.[target.type ?? ""]);
|
|
27
28
|
const mapping = target.kind === "post" ? config.collections?.[target.collection ?? ""] : undefined;
|
|
28
|
-
const path = target.kind === "page" ? config.pages?.[target.slug] : mapping ? (isNew ? mapping.new : mapping.url.replaceAll("{slug}", encodeURIComponent(target.slug))) : undefined;
|
|
29
|
-
return typeof path === "string" && /^\/(?!\/)/.test(path) && !/[\\\s?#]/.test(path) && !path.split("/").
|
|
29
|
+
const path = target.kind === "document" ? pageRoute(target.slug, config.documents?.[target.type ?? ""]) : target.kind === "page" ? config.pages?.[target.slug] : mapping ? (isNew ? mapping.new : mapping.url.replaceAll("{slug}", encodeURIComponent(target.slug))) : undefined;
|
|
30
|
+
return typeof path === "string" && /^\/(?!\/)/.test(path) && !/[\\\s?#]/.test(path) && !/%(?:2e|2f|5c)/i.test(path) && !path.split("/").some(part => part === "." || part === "..") && !/^\/rigel(?:\/|$)/.test(path) ? path : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function previewUrl(config: PreviewConfig, target: PreviewTarget, mode: "live" | "draft" | "website" = "draft", isNew = false): string | null {
|
|
34
|
+
const path = previewPath(config, target, mode === "draft" && (target.kind === "post" || isNew));
|
|
35
|
+
const base = mode === "live" ? config.liveUrl : config.draftUrl;
|
|
36
|
+
if (!path || base === undefined) return path;
|
|
37
|
+
try { return new URL(path.slice(1), previewBaseUrl(base)).href; } catch { return null; }
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
export function anchorValue(values: unknown, path: string): { found: boolean; value: unknown } {
|
package/src/routes/local-post.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { APIRoute } from "astro";
|
|
2
|
-
import { writeFile, mkdir, rm, readFile, rename } from "node:fs/promises";
|
|
2
|
+
import { writeFile, mkdir, rm, readFile, rename, link } from "node:fs/promises";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
import { validateBody, type ArticleBody } from "../article-body";
|
|
7
7
|
import config from "virtual:rigel-config";
|
|
8
|
-
import {
|
|
8
|
+
import { validateCollectionValues } from "../manifest/collections";
|
|
9
9
|
import { readDocuments } from "../schema/store";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import type { CollectionConfig } from "../config";
|
|
@@ -46,16 +46,22 @@ export const POST: APIRoute = async ({ request, params }) => {
|
|
|
46
46
|
validateBody(body.body);
|
|
47
47
|
const fields = config.collections?.find((collection: CollectionConfig) => collection.name === params.collection)?.fields ?? [];
|
|
48
48
|
const documents = config.schema && config.root ? await readDocuments(config.schema, fileURLToPath(config.root)) : [];
|
|
49
|
-
|
|
49
|
+
validateCollectionValues(fields, body.frontmatter, documents);
|
|
50
50
|
} catch (error) {
|
|
51
|
-
return new Response(error instanceof Error ? error.message : "Invalid
|
|
51
|
+
return new Response(error instanceof Error ? error.message : "Invalid article", { status: 400 });
|
|
52
52
|
}
|
|
53
53
|
await mkdir(path.dirname(file), { recursive: true });
|
|
54
54
|
const fm = { ...body.frontmatter, slug };
|
|
55
55
|
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
56
56
|
try {
|
|
57
57
|
await writeFile(temporary, JSON.stringify({ frontmatter: fm, body: body.body }, null, 2) + "\n", { flag: "wx" });
|
|
58
|
-
|
|
58
|
+
if (request.headers.get("If-None-Match") === "*") {
|
|
59
|
+
try { await link(temporary, file); }
|
|
60
|
+
catch (error) {
|
|
61
|
+
if ((error as NodeJS.ErrnoException).code === "EEXIST") return new Response("A post with that slug already exists. Reload the collection and create the draft again.", { status: 409 });
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
} else await rename(temporary, file);
|
|
59
65
|
} finally { await rm(temporary, { force: true }); }
|
|
60
66
|
return new Response(null, { status: 204 });
|
|
61
67
|
};
|
|
@@ -50,6 +50,7 @@ const __rigelReservedSlugs = new Set(documents.filter((doc) => !documentTypes.fi
|
|
|
50
50
|
).map(([path, raw]) => {
|
|
51
51
|
const post = JSON.parse(raw);
|
|
52
52
|
__rigelValidateBody(post.body);
|
|
53
|
+
__rigelValidateCollectionValues(${JSON.stringify(c.fields)}, post.frontmatter, documents);
|
|
53
54
|
post.slug = path.split("/").pop().replace(/\\.json$/, "");
|
|
54
55
|
return post;
|
|
55
56
|
}),
|
|
@@ -165,6 +166,7 @@ export function derefDocument(value) { return deref(value, __rigelIndex); }
|
|
|
165
166
|
}
|
|
166
167
|
return `
|
|
167
168
|
import { validateBody as __rigelValidateBody } from ${JSON.stringify(fileURLToPath(new URL("../article-body.ts", import.meta.url)))};
|
|
169
|
+
import { validateCollectionValues as __rigelValidateCollectionValues } from ${JSON.stringify(fileURLToPath(new URL("../manifest/collections.ts", import.meta.url)))};
|
|
168
170
|
const modules = import.meta.glob(${JSON.stringify(pattern)}, { eager: true });
|
|
169
171
|
const loaded = Object.values(modules).map((m) => m.default);
|
|
170
172
|
${documentsSrc}
|