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