@botlr/next 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +72 -20
- package/dist/index.cjs +570 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +326 -3
- package/dist/index.d.ts +326 -3
- package/dist/index.js +551 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -8,7 +8,11 @@ var BOTLR_ENV = {
|
|
|
8
8
|
apiUrl: "BOTLR_API_URL",
|
|
9
9
|
apiKey: "BOTLR_API_KEY",
|
|
10
10
|
appId: "BOTLR_APP_ID",
|
|
11
|
-
pat: "BOTLR_PAT"
|
|
11
|
+
pat: "BOTLR_PAT",
|
|
12
|
+
orgSlug: "BOTLR_ORG_SLUG",
|
|
13
|
+
projectSlug: "BOTLR_PROJECT_SLUG",
|
|
14
|
+
appInstanceSlug: "BOTLR_APP_INSTANCE_SLUG",
|
|
15
|
+
environment: "BOTLR_ENVIRONMENT"
|
|
12
16
|
};
|
|
13
17
|
var DEFAULT_API_URL = "http://localhost:8888";
|
|
14
18
|
function env(name) {
|
|
@@ -28,6 +32,18 @@ function configFromEnv(overrides = {}) {
|
|
|
28
32
|
personalToken: overrides.personalToken ?? env(BOTLR_ENV.pat)
|
|
29
33
|
};
|
|
30
34
|
}
|
|
35
|
+
function environmentFromEnv(fallback = "production") {
|
|
36
|
+
return env(BOTLR_ENV.environment) ?? env("ENVIRONMENT") ?? env("NODE_ENV") ?? fallback;
|
|
37
|
+
}
|
|
38
|
+
function scopeFromEnv(overrides) {
|
|
39
|
+
return {
|
|
40
|
+
orgSlug: env(BOTLR_ENV.orgSlug) ?? "",
|
|
41
|
+
projectSlug: env(BOTLR_ENV.projectSlug) ?? "",
|
|
42
|
+
appInstanceSlug: env(BOTLR_ENV.appInstanceSlug) ?? "",
|
|
43
|
+
appInstanceId: env(BOTLR_ENV.appId) ?? "",
|
|
44
|
+
...overrides
|
|
45
|
+
};
|
|
46
|
+
}
|
|
31
47
|
|
|
32
48
|
// src/locale.ts
|
|
33
49
|
function localeFromAcceptLanguage(header, fallback = "en") {
|
|
@@ -57,19 +73,551 @@ function createBotlrFromEnv(overrides = {}) {
|
|
|
57
73
|
return createBotlrClient(configFromEnv(overrides));
|
|
58
74
|
}
|
|
59
75
|
|
|
76
|
+
// src/resolve-page.ts
|
|
77
|
+
import { cache as cache2 } from "react";
|
|
78
|
+
var DEFAULT_CONTENT_SECTIONS = { article: "article" };
|
|
79
|
+
async function resolvePage(client, slug, locale, options = {}) {
|
|
80
|
+
try {
|
|
81
|
+
const result = await client.resolveUrl(slug, locale);
|
|
82
|
+
if (result.kind === "404") return { kind: "404" };
|
|
83
|
+
if (result.kind === "redirect" && result.redirect) {
|
|
84
|
+
return { kind: "redirect", destination: result.redirect.destination };
|
|
85
|
+
}
|
|
86
|
+
if (!result.data) return { kind: "404" };
|
|
87
|
+
const data = result.data;
|
|
88
|
+
const layout = data.layout ?? {};
|
|
89
|
+
const content = data.content ?? {};
|
|
90
|
+
const contentType = content["type"] ?? data.type ?? null;
|
|
91
|
+
if (contentType && contentType !== "page") {
|
|
92
|
+
const sectionType = (options.contentSections ?? DEFAULT_CONTENT_SECTIONS)[contentType];
|
|
93
|
+
if (sectionType) {
|
|
94
|
+
const section = {
|
|
95
|
+
_key: `${sectionType}-${content["id"] ?? data.id}`,
|
|
96
|
+
mode: "inline",
|
|
97
|
+
sectionType,
|
|
98
|
+
props: content,
|
|
99
|
+
status: "published"
|
|
100
|
+
};
|
|
101
|
+
layout["main"] = [section, ...layout["main"] ?? []];
|
|
102
|
+
} else {
|
|
103
|
+
console.warn("Unknown content type:", contentType);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
kind: "page",
|
|
108
|
+
pageData: {
|
|
109
|
+
id: data.id,
|
|
110
|
+
name: data.name,
|
|
111
|
+
slug: data.slug || "",
|
|
112
|
+
full_slug: data.full_slug || "",
|
|
113
|
+
layout,
|
|
114
|
+
status: data.status,
|
|
115
|
+
theme: data.theme,
|
|
116
|
+
page_template: data.page_template,
|
|
117
|
+
seo_meta: data.seo_meta,
|
|
118
|
+
alternate_translations: result.alternate_translations ?? [],
|
|
119
|
+
breadcrumbs: result.breadcrumbs ?? []
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
kind: "error",
|
|
125
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function createPageResolver(client, options = {}) {
|
|
130
|
+
return cache2((slug, locale) => resolvePage(client, slug, locale, options));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/page-metadata.ts
|
|
134
|
+
function buildPageMetadata(pageData, options = {}) {
|
|
135
|
+
const seo = pageData.seo_meta || {};
|
|
136
|
+
const title = seo.title || pageData.name;
|
|
137
|
+
const alternates = pageData.alternate_translations || [];
|
|
138
|
+
const siteUrl = options.siteUrl?.replace(/\/+$/, "");
|
|
139
|
+
const folder = options.folder ? `/${options.folder.replace(/^\/+|\/+$/g, "")}` : "";
|
|
140
|
+
const metadata = { title };
|
|
141
|
+
if (seo.description) metadata.description = seo.description;
|
|
142
|
+
if (seo.keywords) metadata.keywords = seo.keywords;
|
|
143
|
+
const ogImage = seo.ogImage?.url || seo.ogImage?.filename;
|
|
144
|
+
const description = seo.ogDescription || seo.description;
|
|
145
|
+
metadata.openGraph = {
|
|
146
|
+
title: seo.ogTitle || title,
|
|
147
|
+
...description ? { description } : {},
|
|
148
|
+
...seo.ogType ? { type: seo.ogType } : {},
|
|
149
|
+
...ogImage ? { images: [ogImage] } : {}
|
|
150
|
+
};
|
|
151
|
+
if (seo.twitterCard) {
|
|
152
|
+
metadata.twitter = {
|
|
153
|
+
card: seo.twitterCard,
|
|
154
|
+
title: seo.ogTitle || title,
|
|
155
|
+
...description ? { description } : {},
|
|
156
|
+
...ogImage ? { images: [ogImage] } : {}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (seo.noindex !== void 0 || seo.nofollow !== void 0 || seo.robots) {
|
|
160
|
+
metadata.robots = seo.robots ?? { index: !seo.noindex, follow: !seo.nofollow };
|
|
161
|
+
}
|
|
162
|
+
if (seo.canonical) {
|
|
163
|
+
metadata.alternates = { canonical: seo.canonical };
|
|
164
|
+
} else if (siteUrl) {
|
|
165
|
+
const canonical = pageData.full_slug ? `${siteUrl}${folder}/${pageData.full_slug}` : `${siteUrl}${folder}`;
|
|
166
|
+
metadata.alternates = { canonical };
|
|
167
|
+
}
|
|
168
|
+
if (siteUrl && alternates.length > 0) {
|
|
169
|
+
const languages = {};
|
|
170
|
+
for (const alt of alternates) {
|
|
171
|
+
languages[alt.locale] = `${siteUrl}${alt.full_slug}`;
|
|
172
|
+
}
|
|
173
|
+
metadata.alternates = { ...metadata.alternates, languages };
|
|
174
|
+
}
|
|
175
|
+
return metadata;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/render-cms-page.tsx
|
|
179
|
+
import parse from "html-react-parser";
|
|
180
|
+
import {
|
|
181
|
+
SectionRenderer,
|
|
182
|
+
filterVisibleNodesRecursive
|
|
183
|
+
} from "@botlr/react";
|
|
184
|
+
|
|
185
|
+
// src/theme-css.ts
|
|
186
|
+
var COLOR_KEYS = [
|
|
187
|
+
"primary",
|
|
188
|
+
"primaryForeground",
|
|
189
|
+
"secondary",
|
|
190
|
+
"secondaryForeground",
|
|
191
|
+
"accent",
|
|
192
|
+
"accentForeground",
|
|
193
|
+
"background",
|
|
194
|
+
"foreground",
|
|
195
|
+
"muted",
|
|
196
|
+
"mutedForeground",
|
|
197
|
+
"border",
|
|
198
|
+
"input",
|
|
199
|
+
"ring",
|
|
200
|
+
"destructive",
|
|
201
|
+
"destructiveForeground",
|
|
202
|
+
"success",
|
|
203
|
+
"successForeground",
|
|
204
|
+
"warning",
|
|
205
|
+
"warningForeground",
|
|
206
|
+
"info",
|
|
207
|
+
"infoForeground"
|
|
208
|
+
];
|
|
209
|
+
var COMMON_GROUPS = [
|
|
210
|
+
[["typography", "fontFamily"], "--font", ["heading", "body", "ui", "code", "sans", "serif", "mono"]],
|
|
211
|
+
[["typography", "fontSize"], "--font-size", ["h1", "h2", "h3", "h4", "h5", "h6", "xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl"]],
|
|
212
|
+
[["typography", "fontWeight"], "--font-weight", ["normal", "medium", "semibold", "bold"]],
|
|
213
|
+
[["typography", "lineHeight"], "--line-height", ["tight", "normal", "relaxed"]],
|
|
214
|
+
[["spacing"], "--spacing", ["xs", "sm", "md", "lg", "xl", "2xl"]],
|
|
215
|
+
[["borderRadius"], "--radius", ["none", "sm", "md", "lg", "xl", "full"]],
|
|
216
|
+
[["shadows"], "--shadow", ["sm", "md", "lg", "xl"]],
|
|
217
|
+
[["layout"], "-", ["containerMaxWidth", "sidebarWidth", "headerHeight", "footerHeight"]]
|
|
218
|
+
];
|
|
219
|
+
var kebab = (key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
220
|
+
function colorVars(colors) {
|
|
221
|
+
if (!colors) return [];
|
|
222
|
+
const vars = [];
|
|
223
|
+
for (const key of COLOR_KEYS) {
|
|
224
|
+
const value = colors[key];
|
|
225
|
+
if (value) vars.push(`--color-${kebab(key)}: ${value};`);
|
|
226
|
+
}
|
|
227
|
+
return vars;
|
|
228
|
+
}
|
|
229
|
+
function pick(settings, path) {
|
|
230
|
+
let current = settings;
|
|
231
|
+
for (const segment of path) {
|
|
232
|
+
current = current?.[segment];
|
|
233
|
+
if (!current) return void 0;
|
|
234
|
+
}
|
|
235
|
+
return current;
|
|
236
|
+
}
|
|
237
|
+
function themeSettingsToCss(settings) {
|
|
238
|
+
if (!settings) return "";
|
|
239
|
+
const s = settings;
|
|
240
|
+
const lightVars = colorVars(s.colors?.light);
|
|
241
|
+
const darkVars = colorVars(s.colors?.dark);
|
|
242
|
+
const commonVars = [];
|
|
243
|
+
for (const [path, prefix, keys] of COMMON_GROUPS) {
|
|
244
|
+
const group = pick(settings, path);
|
|
245
|
+
if (!group) continue;
|
|
246
|
+
for (const key of keys) {
|
|
247
|
+
const value = group[key];
|
|
248
|
+
if (value) commonVars.push(`${prefix}-${kebab(key)}: ${value};`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const cssBlocks = [];
|
|
252
|
+
if (commonVars.length > 0) {
|
|
253
|
+
cssBlocks.push(`:root {
|
|
254
|
+
${commonVars.join("\n ")}
|
|
255
|
+
}`);
|
|
256
|
+
}
|
|
257
|
+
if (lightVars.length > 0) {
|
|
258
|
+
cssBlocks.push(`:root {
|
|
259
|
+
${lightVars.join("\n ")}
|
|
260
|
+
}`);
|
|
261
|
+
}
|
|
262
|
+
if (darkVars.length > 0) {
|
|
263
|
+
cssBlocks.push(`@media (prefers-color-scheme: dark) {
|
|
264
|
+
:root {
|
|
265
|
+
${darkVars.join("\n ")}
|
|
266
|
+
}
|
|
267
|
+
}`);
|
|
268
|
+
cssBlocks.push(`.dark {
|
|
269
|
+
${darkVars.join("\n ")}
|
|
270
|
+
}`);
|
|
271
|
+
}
|
|
272
|
+
return cssBlocks.join("\n\n");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/render-cms-page.tsx
|
|
276
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
277
|
+
var DEFAULT_TEMPLATE = `<!DOCTYPE html>
|
|
278
|
+
<html lang="en">
|
|
279
|
+
<head>
|
|
280
|
+
<meta charset="UTF-8">
|
|
281
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
282
|
+
<title>__PAGE_TITLE__</title>
|
|
283
|
+
<style>
|
|
284
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
285
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
286
|
+
</style>
|
|
287
|
+
__THEME_CSS__
|
|
288
|
+
</head>
|
|
289
|
+
<body>
|
|
290
|
+
<main>
|
|
291
|
+
__REGION_MAIN__
|
|
292
|
+
</main>
|
|
293
|
+
</body>
|
|
294
|
+
</html>`;
|
|
295
|
+
function extractBodyContent(html) {
|
|
296
|
+
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
297
|
+
return bodyMatch?.[1] ? bodyMatch[1].trim() : html;
|
|
298
|
+
}
|
|
299
|
+
function collectSharedSectionIds(nodes) {
|
|
300
|
+
const ids = [];
|
|
301
|
+
for (const node of nodes) {
|
|
302
|
+
if ("mode" in node && node.mode === "shared" && node.sharedSectionId) {
|
|
303
|
+
ids.push(node.sharedSectionId);
|
|
304
|
+
}
|
|
305
|
+
if ("type" in node && node.type === "grid-container" && Array.isArray(node.columns)) {
|
|
306
|
+
for (const column of node.columns) {
|
|
307
|
+
if (Array.isArray(column.sections)) ids.push(...collectSharedSectionIds(column.sections));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return ids;
|
|
312
|
+
}
|
|
313
|
+
function prepareTemplate(templateHtml, pageData, templateCss, locale) {
|
|
314
|
+
let prepared = templateHtml.replace(/\{\{page_title\}\}/g, pageData.name).replace(/\{\{page_lang\}\}/g, locale).replace(/\{\{page_description\}\}/g, "");
|
|
315
|
+
prepared = prepared.replace(/\{\{\{theme_css\}\}\}/g, templateCss ? `<style>${templateCss}</style>` : "");
|
|
316
|
+
prepared = prepared.replace(/\{\{\{([A-Z_]+)\}\}\}/g, "__REGION_$1__");
|
|
317
|
+
prepared = prepared.replace(/\{\{#if\s+([A-Z_]+)\}\}/g, "");
|
|
318
|
+
prepared = prepared.replace(/\{\{\/if\}\}/g, "");
|
|
319
|
+
return extractBodyContent(prepared);
|
|
320
|
+
}
|
|
321
|
+
function mergeTemplateLayout(pageData) {
|
|
322
|
+
const merged = {};
|
|
323
|
+
const regions = /* @__PURE__ */ new Set([...Object.keys(pageData.layout), ...Object.keys(pageData.page_template?.layout ?? {})]);
|
|
324
|
+
for (const region of regions) {
|
|
325
|
+
merged[region] = [...pageData.page_template?.layout?.[region] ?? [], ...pageData.layout[region] ?? []];
|
|
326
|
+
}
|
|
327
|
+
return merged;
|
|
328
|
+
}
|
|
329
|
+
var errorBox = (heading, message) => /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh" }, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
|
|
330
|
+
/* @__PURE__ */ jsx("h1", { style: { fontSize: "1.5rem", fontWeight: 700, marginBottom: "0.5rem" }, children: heading }),
|
|
331
|
+
/* @__PURE__ */ jsx("p", { style: { opacity: 0.7 }, children: message })
|
|
332
|
+
] }) });
|
|
333
|
+
async function renderCmsPage({
|
|
334
|
+
pageData,
|
|
335
|
+
scope,
|
|
336
|
+
componentRegistry,
|
|
337
|
+
mode = "published",
|
|
338
|
+
locale = scope.locale,
|
|
339
|
+
environment = environmentFromEnv(),
|
|
340
|
+
client,
|
|
341
|
+
fetchSharedSection,
|
|
342
|
+
showModeBadge = false,
|
|
343
|
+
renderError
|
|
344
|
+
}) {
|
|
345
|
+
let templateHtml;
|
|
346
|
+
let templateCss = "";
|
|
347
|
+
if (pageData.page_template?.template_html) {
|
|
348
|
+
templateHtml = pageData.page_template.template_html;
|
|
349
|
+
templateCss = pageData.page_template.template_css || "";
|
|
350
|
+
} else if (pageData.theme?.default_template_html) {
|
|
351
|
+
templateHtml = pageData.theme.default_template_html;
|
|
352
|
+
templateCss = pageData.theme.default_template_css || "";
|
|
353
|
+
} else {
|
|
354
|
+
templateHtml = DEFAULT_TEMPLATE;
|
|
355
|
+
}
|
|
356
|
+
const preparedTemplate = prepareTemplate(templateHtml, pageData, templateCss, locale);
|
|
357
|
+
const pageScope = {
|
|
358
|
+
id: pageData.id,
|
|
359
|
+
kind: "page",
|
|
360
|
+
name: pageData.name,
|
|
361
|
+
slug: pageData.slug || "",
|
|
362
|
+
breadcrumbs: pageData.breadcrumbs || []
|
|
363
|
+
};
|
|
364
|
+
const visibilityContext = {
|
|
365
|
+
pageId: pageData.id,
|
|
366
|
+
currentPath: "/" + (pageData.slug || ""),
|
|
367
|
+
locale,
|
|
368
|
+
environment
|
|
369
|
+
};
|
|
370
|
+
const filteredLayout = {};
|
|
371
|
+
for (const [region, sections] of Object.entries(mergeTemplateLayout(pageData))) {
|
|
372
|
+
filteredLayout[region] = filterVisibleNodesRecursive(sections, visibilityContext);
|
|
373
|
+
}
|
|
374
|
+
const fetchShared = fetchSharedSection ?? (client ? (id) => client.getPageBySlug(id, locale) : void 0);
|
|
375
|
+
const sharedSectionIds = Object.values(filteredLayout).flatMap(collectSharedSectionIds);
|
|
376
|
+
const sharedSectionsMap = /* @__PURE__ */ new Map();
|
|
377
|
+
if (sharedSectionIds.length > 0) {
|
|
378
|
+
if (!fetchShared) {
|
|
379
|
+
throw new Error("@botlr/next: renderCmsPage needs `client` or `fetchSharedSection` to render shared sections");
|
|
380
|
+
}
|
|
381
|
+
const results = await Promise.all(
|
|
382
|
+
[...new Set(sharedSectionIds)].map(async (id) => {
|
|
383
|
+
try {
|
|
384
|
+
const data = await fetchShared(id);
|
|
385
|
+
const main = data?.layout?.["main"];
|
|
386
|
+
if (Array.isArray(main) && main.length > 0) return { id, sections: main };
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.error(`Error fetching shared section ${id}:`, err);
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
391
|
+
})
|
|
392
|
+
);
|
|
393
|
+
for (const result of results) {
|
|
394
|
+
if (result) sharedSectionsMap.set(result.id, result.sections);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const parseOptions = {
|
|
398
|
+
replace: (domNode) => {
|
|
399
|
+
if (!("data" in domNode) || typeof domNode.data !== "string") return;
|
|
400
|
+
const regionMatch = domNode.data.match(/__REGION_([A-Z_]+)__/);
|
|
401
|
+
if (!regionMatch?.[1]) return;
|
|
402
|
+
const regionKey = regionMatch[1].toLowerCase();
|
|
403
|
+
const regionSections = filteredLayout[regionKey] ?? [];
|
|
404
|
+
if (regionSections.length === 0) return /* @__PURE__ */ jsx(Fragment, {});
|
|
405
|
+
const templateSectionKeys = new Set(
|
|
406
|
+
(pageData.page_template?.layout?.[regionKey] ?? []).map((section) => section._key)
|
|
407
|
+
);
|
|
408
|
+
return /* @__PURE__ */ jsx(Fragment, { children: regionSections.map((section) => /* @__PURE__ */ jsx(
|
|
409
|
+
SectionRenderer,
|
|
410
|
+
{
|
|
411
|
+
section,
|
|
412
|
+
componentRegistry,
|
|
413
|
+
mode,
|
|
414
|
+
sharedSectionsData: sharedSectionsMap,
|
|
415
|
+
scope,
|
|
416
|
+
pageScope,
|
|
417
|
+
fromTemplate: templateSectionKeys.has(section._key)
|
|
418
|
+
},
|
|
419
|
+
section._key
|
|
420
|
+
)) });
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
const themeSettingsCss = themeSettingsToCss(pageData.theme?.settings);
|
|
424
|
+
try {
|
|
425
|
+
const renderedContent = parse(preparedTemplate, parseOptions);
|
|
426
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
427
|
+
themeSettingsCss && /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: themeSettingsCss } }),
|
|
428
|
+
templateCss && /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: templateCss } }),
|
|
429
|
+
renderedContent,
|
|
430
|
+
showModeBadge && /* @__PURE__ */ jsx(
|
|
431
|
+
"div",
|
|
432
|
+
{
|
|
433
|
+
style: {
|
|
434
|
+
position: "fixed",
|
|
435
|
+
bottom: "1rem",
|
|
436
|
+
right: "1rem",
|
|
437
|
+
padding: "0.5rem 1rem",
|
|
438
|
+
borderRadius: "0.5rem",
|
|
439
|
+
background: "#111",
|
|
440
|
+
color: "#fff",
|
|
441
|
+
fontSize: "0.875rem",
|
|
442
|
+
fontWeight: 500,
|
|
443
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
|
|
444
|
+
zIndex: 50
|
|
445
|
+
},
|
|
446
|
+
children: mode === "draft" ? "Draft preview" : "Published preview"
|
|
447
|
+
}
|
|
448
|
+
)
|
|
449
|
+
] });
|
|
450
|
+
} catch (error) {
|
|
451
|
+
console.error("Error rendering template:", error);
|
|
452
|
+
if (renderError) return /* @__PURE__ */ jsx(Fragment, { children: renderError(error) });
|
|
453
|
+
return errorBox("Template Error", error instanceof Error ? error.message : "Failed to render template");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/static-text-file.ts
|
|
458
|
+
import { filterVisibleNodesRecursive as filterVisibleNodesRecursive2 } from "@botlr/react";
|
|
459
|
+
function collectSections(nodes, sectionType) {
|
|
460
|
+
const result = [];
|
|
461
|
+
for (const node of nodes) {
|
|
462
|
+
if ("mode" in node && node.mode === "inline" && node.sectionType === sectionType) {
|
|
463
|
+
result.push(node);
|
|
464
|
+
} else if ("type" in node && node.type === "grid-container" && Array.isArray(node.columns)) {
|
|
465
|
+
for (const column of node.columns) {
|
|
466
|
+
result.push(...collectSections(column.sections ?? [], sectionType));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return result;
|
|
471
|
+
}
|
|
472
|
+
var TEXT_PLAIN = { "Content-Type": "text/plain; charset=utf-8" };
|
|
473
|
+
async function serveStaticTextFile(client, slug, defaultContent, options = {}) {
|
|
474
|
+
const locale = options.locale ?? client.config.locale ?? "en";
|
|
475
|
+
const environment = options.environment ?? environmentFromEnv();
|
|
476
|
+
const sectionType = options.sectionType ?? "text";
|
|
477
|
+
const propKeys = options.propKeys ?? ["content", "body", "text"];
|
|
478
|
+
try {
|
|
479
|
+
const page = await client.getPageBySlug(slug, locale);
|
|
480
|
+
if (page?.layout) {
|
|
481
|
+
const visibilityContext = { locale, environment };
|
|
482
|
+
const sections = [];
|
|
483
|
+
for (const region of Object.values(page.layout)) {
|
|
484
|
+
if (Array.isArray(region)) {
|
|
485
|
+
sections.push(...collectSections(filterVisibleNodesRecursive2(region, visibilityContext), sectionType));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const content = sections.map((section) => {
|
|
489
|
+
const props = "props" in section ? section.props ?? {} : {};
|
|
490
|
+
const value = propKeys.map((key) => props[key]).find((v) => typeof v === "string" && v);
|
|
491
|
+
return typeof value === "string" ? value : "";
|
|
492
|
+
}).filter(Boolean).join("\n\n");
|
|
493
|
+
if (content) return new Response(content, { headers: TEXT_PLAIN });
|
|
494
|
+
}
|
|
495
|
+
} catch {
|
|
496
|
+
}
|
|
497
|
+
return new Response(defaultContent, { headers: TEXT_PLAIN });
|
|
498
|
+
}
|
|
499
|
+
function defaultRobotsContent(environment = environmentFromEnv()) {
|
|
500
|
+
return environment === "production" ? "User-agent: *\nAllow: /\n" : "User-agent: *\nDisallow: /\n";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/locale-page.tsx
|
|
504
|
+
import { notFound, permanentRedirect, redirect } from "next/navigation";
|
|
505
|
+
import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
|
|
506
|
+
async function pageSlugFromParams(params) {
|
|
507
|
+
const slug = (await params).slug ?? [];
|
|
508
|
+
return slug.length > 0 ? slug.join("/") : "index";
|
|
509
|
+
}
|
|
510
|
+
function createLocalePage(config) {
|
|
511
|
+
const { locale, client, componentRegistry } = config;
|
|
512
|
+
const folder = config.folder ? `/${config.folder.replace(/^\/+|\/+$/g, "")}` : "";
|
|
513
|
+
const scope = scopeFromEnv({ locale, localePrefix: folder.replace(/^\//, ""), ...config.scope });
|
|
514
|
+
const resolvePage2 = createPageResolver(client, config.resolveOptions);
|
|
515
|
+
const sendRedirect = config.redirect === "temporary" ? redirect : permanentRedirect;
|
|
516
|
+
async function generateStaticParams() {
|
|
517
|
+
const response = await client.getPageSlugList({ locale, limit: config.staticParamsLimit ?? 1e3 });
|
|
518
|
+
return (response.data ?? []).map((path) => ({ slug: path ? path.split("/") : [] }));
|
|
519
|
+
}
|
|
520
|
+
async function generateMetadata({ params }) {
|
|
521
|
+
const resolved = await resolvePage2(await pageSlugFromParams(params), locale);
|
|
522
|
+
if (resolved.kind !== "page") return {};
|
|
523
|
+
return buildPageMetadata(resolved.pageData, { siteUrl: config.siteUrl, folder });
|
|
524
|
+
}
|
|
525
|
+
async function Page({ params }) {
|
|
526
|
+
const resolved = await resolvePage2(await pageSlugFromParams(params), locale);
|
|
527
|
+
if (resolved.kind === "error") {
|
|
528
|
+
if (config.renderError) return /* @__PURE__ */ jsx2(Fragment2, { children: config.renderError(resolved.message) });
|
|
529
|
+
return errorBox("Error Loading Page", resolved.message);
|
|
530
|
+
}
|
|
531
|
+
if (resolved.kind === "404") notFound();
|
|
532
|
+
if (resolved.kind === "redirect") sendRedirect(`${folder}${resolved.destination}`);
|
|
533
|
+
const { pageData } = resolved;
|
|
534
|
+
if (pageData.status !== "published") notFound();
|
|
535
|
+
return renderCmsPage({
|
|
536
|
+
pageData,
|
|
537
|
+
scope,
|
|
538
|
+
componentRegistry,
|
|
539
|
+
mode: "published",
|
|
540
|
+
locale,
|
|
541
|
+
environment: config.environment ?? environmentFromEnv(),
|
|
542
|
+
client
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return { generateStaticParams, generateMetadata, Page, resolvePage: resolvePage2, scope };
|
|
546
|
+
}
|
|
547
|
+
function createLocaleAliasPage(targetFolder) {
|
|
548
|
+
return async function AliasPage({ params }) {
|
|
549
|
+
const slug = (await params).slug ?? [];
|
|
550
|
+
permanentRedirect(`${targetFolder}/${slug.join("/")}`);
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/rate-limit.ts
|
|
555
|
+
var store = /* @__PURE__ */ new Map();
|
|
556
|
+
function rateLimit(key, config) {
|
|
557
|
+
const now = Date.now();
|
|
558
|
+
const windowMs = config.window * 1e3;
|
|
559
|
+
let entry = store.get(key);
|
|
560
|
+
if (!entry || now >= entry.resetAt) {
|
|
561
|
+
entry = { count: 1, resetAt: now + windowMs };
|
|
562
|
+
store.set(key, entry);
|
|
563
|
+
return { allowed: true, remaining: config.limit - 1, resetAt: entry.resetAt };
|
|
564
|
+
}
|
|
565
|
+
entry.count++;
|
|
566
|
+
const allowed = entry.count <= config.limit;
|
|
567
|
+
return {
|
|
568
|
+
allowed,
|
|
569
|
+
remaining: Math.max(0, config.limit - entry.count),
|
|
570
|
+
resetAt: entry.resetAt
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function getClientIp(request) {
|
|
574
|
+
return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || "unknown";
|
|
575
|
+
}
|
|
576
|
+
|
|
60
577
|
// src/index.ts
|
|
61
578
|
export * from "@botlr/react";
|
|
62
|
-
import {
|
|
579
|
+
import {
|
|
580
|
+
BotlrError,
|
|
581
|
+
BotlrConflictError,
|
|
582
|
+
createBotlrClient as createBotlrClient2,
|
|
583
|
+
getAssetUrl,
|
|
584
|
+
createAssetUrl,
|
|
585
|
+
getAssetId,
|
|
586
|
+
getAssetFilename,
|
|
587
|
+
isAssetData
|
|
588
|
+
} from "@botlr/sdk";
|
|
63
589
|
export {
|
|
64
590
|
BOTLR_ENV,
|
|
65
591
|
BotlrConflictError,
|
|
66
592
|
BotlrError,
|
|
67
593
|
DEFAULT_API_URL,
|
|
594
|
+
buildPageMetadata,
|
|
595
|
+
collectSharedSectionIds,
|
|
68
596
|
configFromEnv,
|
|
597
|
+
createAssetUrl,
|
|
69
598
|
createBotlrClient2 as createBotlrClient,
|
|
70
599
|
createBotlrFromEnv,
|
|
600
|
+
createLocaleAliasPage,
|
|
601
|
+
createLocalePage,
|
|
602
|
+
createPageResolver,
|
|
603
|
+
defaultRobotsContent,
|
|
604
|
+
environmentFromEnv,
|
|
605
|
+
getAssetFilename,
|
|
606
|
+
getAssetId,
|
|
607
|
+
getAssetUrl,
|
|
71
608
|
getBotlrForRoute,
|
|
72
609
|
getCachedBotlr,
|
|
73
|
-
|
|
610
|
+
getClientIp,
|
|
611
|
+
isAssetData,
|
|
612
|
+
localeFromAcceptLanguage,
|
|
613
|
+
mergeTemplateLayout,
|
|
614
|
+
pageSlugFromParams,
|
|
615
|
+
prepareTemplate,
|
|
616
|
+
rateLimit,
|
|
617
|
+
renderCmsPage,
|
|
618
|
+
resolvePage,
|
|
619
|
+
scopeFromEnv,
|
|
620
|
+
serveStaticTextFile,
|
|
621
|
+
themeSettingsToCss
|
|
74
622
|
};
|
|
75
623
|
//# sourceMappingURL=index.js.map
|