@blinkk/root-cms 2.2.1 → 2.2.3
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/dist/app.js +2 -2
- package/dist/{client-BKO9Ikqy.d.ts → client-Rka_a6jc.d.ts} +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/core.d.ts +189 -5
- package/dist/core.js +287 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +77 -2
- package/dist/project.d.ts +1 -1
- package/dist/{schema-FB6Ck9sN.d.ts → schema-KYFRsBf5.d.ts} +2 -2
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +101 -101
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -83,7 +83,7 @@ function convertOneOfTypes(collection) {
|
|
|
83
83
|
// package.json
|
|
84
84
|
var package_default = {
|
|
85
85
|
name: "@blinkk/root-cms",
|
|
86
|
-
version: "2.2.
|
|
86
|
+
version: "2.2.3",
|
|
87
87
|
author: "s@blinkk.com",
|
|
88
88
|
license: "MIT",
|
|
89
89
|
engines: {
|
|
@@ -232,7 +232,7 @@ var package_default = {
|
|
|
232
232
|
yjs: "13.6.27"
|
|
233
233
|
},
|
|
234
234
|
peerDependencies: {
|
|
235
|
-
"@blinkk/root": "2.2.
|
|
235
|
+
"@blinkk/root": "2.2.3",
|
|
236
236
|
"firebase-admin": ">=11",
|
|
237
237
|
"firebase-functions": ">=4",
|
|
238
238
|
preact: ">=10",
|
|
@@ -861,4 +861,4 @@ declare class BatchResponse {
|
|
|
861
861
|
private getTranslationsMap;
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
-
export { cmsPlugin as $, type Action as A, type BatchRequestOptions as B, BatchResponse as C, type
|
|
864
|
+
export { cmsPlugin as $, type Action as A, type BatchRequestOptions as B, BatchResponse as C, type DocMode as D, type Locale as E, type SourceString as F, type GetDocOptions as G, type HttpMethod as H, type TranslatedString as I, type TranslationsDocMode as J, type TranslationsLocaleDocHashMap as K, type LoadTranslationsOptions as L, type TranslationsLocaleDocEntry as M, type MultiLocaleTranslationsMap as N, type SingleLocaleTranslationsMap as O, TranslationsManager as P, buildTranslationsDbPath as Q, RootCMSClient as R, type SetDocOptions as S, type TranslationsMap as T, type UserRole as U, buildTranslationsLocaleDocDbPath as V, type CMSUser as W, type CMSAIConfig as X, type CMSSidebarTool as Y, type CMSPluginOptions as Z, type CMSPlugin as _, type LocaleTranslations as a, type Doc as b, type DataSource as c, type DataSourceData as d, type DataSourceMode as e, type SaveDraftOptions as f, type ListDocsOptions as g, type GetCountOptions as h, type Translation as i, type Release as j, type ListActionsOptions as k, isRichTextData as l, getCmsPlugin as m, marshalData as n, normalizeData as o, type ArrayObject as p, marshalArray as q, unmarshalArray as r, translationsForLocale as s, toArrayObject as t, unmarshalData as u, parseDocId as v, type BatchRequestQuery as w, type BatchRequestQueryOptions as x, type TranslationsDoc as y, BatchRequest as z };
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '@blinkk/root';
|
|
2
2
|
import 'firebase-admin/app';
|
|
3
3
|
import 'firebase-admin/firestore';
|
|
4
|
-
export { A as Action, p as ArrayObject, z as BatchRequest, B as BatchRequestOptions, w as BatchRequestQuery, x as BatchRequestQueryOptions, C as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode,
|
|
4
|
+
export { A as Action, p as ArrayObject, z as BatchRequest, B as BatchRequestOptions, w as BatchRequestQuery, x as BatchRequestQueryOptions, C as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, D as DocMode, h as GetCountOptions, G as GetDocOptions, H as HttpMethod, k as ListActionsOptions, g as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, j as Release, R as RootCMSClient, f as SaveDraftOptions, S as SetDocOptions, i as Translation, y as TranslationsDoc, T as TranslationsMap, U as UserRole, m as getCmsPlugin, l as isRichTextData, q as marshalArray, n as marshalData, o as normalizeData, v as parseDocId, t as toArrayObject, s as translationsForLocale, r as unmarshalArray, u as unmarshalData } from './client-Rka_a6jc.js';
|
package/dist/core.d.ts
CHANGED
|
@@ -1,10 +1,194 @@
|
|
|
1
|
-
import { L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-
|
|
2
|
-
export { A as Action, p as ArrayObject, z as BatchRequest, B as BatchRequestOptions, w as BatchRequestQuery, x as BatchRequestQueryOptions, C as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode,
|
|
3
|
-
import { RootConfig } from '@blinkk/root';
|
|
1
|
+
import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-Rka_a6jc.js';
|
|
2
|
+
export { A as Action, p as ArrayObject, z as BatchRequest, B as BatchRequestOptions, w as BatchRequestQuery, x as BatchRequestQueryOptions, C as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, h as GetCountOptions, G as GetDocOptions, H as HttpMethod, k as ListActionsOptions, g as ListDocsOptions, E as Locale, N as MultiLocaleTranslationsMap, j as Release, f as SaveDraftOptions, S as SetDocOptions, O as SingleLocaleTranslationsMap, F as SourceString, I as TranslatedString, i as Translation, y as TranslationsDoc, J as TranslationsDocMode, M as TranslationsLocaleDocEntry, K as TranslationsLocaleDocHashMap, P as TranslationsManager, U as UserRole, Q as buildTranslationsDbPath, V as buildTranslationsLocaleDocDbPath, m as getCmsPlugin, l as isRichTextData, q as marshalArray, n as marshalData, o as normalizeData, v as parseDocId, t as toArrayObject, s as translationsForLocale, r as unmarshalArray, u as unmarshalData } from './client-Rka_a6jc.js';
|
|
3
|
+
import { Request, RootConfig, Response, RouteParams, GetStaticProps, GetStaticPaths } from '@blinkk/root';
|
|
4
4
|
import { Query } from 'firebase-admin/firestore';
|
|
5
|
-
export { s as schema } from './schema-
|
|
5
|
+
export { s as schema } from './schema-KYFRsBf5.js';
|
|
6
6
|
import 'firebase-admin/app';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Utility for creating Root filesystem routes that are connected to a CMS doc.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* // routes/blog/[slug].tsx
|
|
15
|
+
* import {createRoute} from '@blinkk/root-cms';
|
|
16
|
+
*
|
|
17
|
+
* export default function Page(props) { ... }
|
|
18
|
+
*
|
|
19
|
+
* export const {handle} = createRoute({collection: 'BlogPosts'});
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* For SSG enabled sites, add `{ssg: true}`:
|
|
23
|
+
*
|
|
24
|
+
* ```
|
|
25
|
+
* // routes/blog/[slug].tsx
|
|
26
|
+
* import {createRoute} from '@blinkk/root-cms';
|
|
27
|
+
*
|
|
28
|
+
* export default function Page(props) { ... }
|
|
29
|
+
*
|
|
30
|
+
* export const {getStaticProps, getStaticPaths} = createRoute({collection: 'BlogPosts'});
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
interface RootCMSDoc<Fields = any> {
|
|
35
|
+
/** The id of the doc, e.g. "Pages/foo-bar". */
|
|
36
|
+
id: string;
|
|
37
|
+
/** The collection id of the doc, e.g. "Pages". */
|
|
38
|
+
collection: string;
|
|
39
|
+
/** The slug of the doc, e.g. "foo-bar". */
|
|
40
|
+
slug: string;
|
|
41
|
+
/** System-level metadata. */
|
|
42
|
+
sys: {
|
|
43
|
+
createdAt: number;
|
|
44
|
+
createdBy: string;
|
|
45
|
+
modifiedAt: number;
|
|
46
|
+
modifiedBy: string;
|
|
47
|
+
firstPublishedAt?: number;
|
|
48
|
+
firstPublishedBy?: string;
|
|
49
|
+
publishedAt?: number;
|
|
50
|
+
publishedBy?: string;
|
|
51
|
+
locales?: string[];
|
|
52
|
+
};
|
|
53
|
+
/** User-entered field values from the CMS. */
|
|
54
|
+
fields?: Fields;
|
|
55
|
+
}
|
|
56
|
+
type RouteRequest = Request & {
|
|
57
|
+
rootConfig: RootConfig;
|
|
58
|
+
cmsClient: RootCMSClient;
|
|
59
|
+
};
|
|
60
|
+
type RouteResponse = Response;
|
|
61
|
+
interface RouteContext {
|
|
62
|
+
/**
|
|
63
|
+
* HTTP request object. Only available in SSR mode.
|
|
64
|
+
*/
|
|
65
|
+
req?: RouteRequest;
|
|
66
|
+
/**
|
|
67
|
+
* The slug of the page being requested.
|
|
68
|
+
*/
|
|
69
|
+
slug: string;
|
|
70
|
+
/**
|
|
71
|
+
* Doc publishing mode.
|
|
72
|
+
*/
|
|
73
|
+
mode: DocMode;
|
|
74
|
+
/**
|
|
75
|
+
* Client for interacting with Root CMS data.
|
|
76
|
+
*/
|
|
77
|
+
cmsClient: RootCMSClient;
|
|
78
|
+
/**
|
|
79
|
+
* URL param map from filesystem routing.
|
|
80
|
+
*/
|
|
81
|
+
params: RouteParams;
|
|
82
|
+
}
|
|
83
|
+
interface Route {
|
|
84
|
+
/**
|
|
85
|
+
* SSR handler.
|
|
86
|
+
*/
|
|
87
|
+
handle: (req: RouteRequest, res: RouteResponse) => Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* SSG handler props handler, enabled with `{ssg: true}`.
|
|
90
|
+
*/
|
|
91
|
+
getStaticProps?: GetStaticProps;
|
|
92
|
+
/**
|
|
93
|
+
* SSG path params provider, enabled with `{ssg: true}`.
|
|
94
|
+
*/
|
|
95
|
+
getStaticPaths?: GetStaticPaths;
|
|
96
|
+
}
|
|
97
|
+
interface CreateRouteOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Collection mapped to the route.
|
|
100
|
+
*/
|
|
101
|
+
collection: string;
|
|
102
|
+
/**
|
|
103
|
+
* Route param name used for the slug. Used for dynamic routes, e.g.
|
|
104
|
+
* `[...slug].tsx`. Defaults to "slug".
|
|
105
|
+
*/
|
|
106
|
+
slugParam?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Format pattern for slugs that use multiple param values to form the slug,
|
|
109
|
+
* e.g. for experiments you might have something like:
|
|
110
|
+
*
|
|
111
|
+
* Route: routes/ex/[experimentId]/[...page].tsx
|
|
112
|
+
* Doc ID: ExperimentPages/1234--about--foo
|
|
113
|
+
* URL path: /ex/1234/about/foo/
|
|
114
|
+
*
|
|
115
|
+
* To grab the correct doc, use `{slugFormat: '[experimentId]/[page]'}`.
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
slugFormat?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Slug to use for the route. Used for non-dynamic, single-document routes.
|
|
121
|
+
*/
|
|
122
|
+
slug?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Callback function that returns a map of Promises that contain fetched data.
|
|
125
|
+
* Once the promise is resolved, the values are injected into page's props
|
|
126
|
+
* for rendering.
|
|
127
|
+
*/
|
|
128
|
+
fetchData?: (context: RouteContext) => Record<string, Promise<any>>;
|
|
129
|
+
/**
|
|
130
|
+
* Hook that's called when the doc is not found. If not provided, the default
|
|
131
|
+
* 404 handler will be called.
|
|
132
|
+
*/
|
|
133
|
+
notFoundHook?: (req: Request, res: Response) => void | Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Hook for amending any props values before being passed to the page
|
|
136
|
+
* component.
|
|
137
|
+
*/
|
|
138
|
+
preRenderHook?: (props: any, context: RouteContext) => any | Promise<any>;
|
|
139
|
+
/**
|
|
140
|
+
* Hook for setting any response headers.
|
|
141
|
+
*/
|
|
142
|
+
setResponseHeaders?: (req: Request, res: Response) => void;
|
|
143
|
+
/**
|
|
144
|
+
* Translations configuration.
|
|
145
|
+
*/
|
|
146
|
+
translations?: (context: RouteContext) => {
|
|
147
|
+
tags?: string[];
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Sets Cache-Control header to `private`.
|
|
151
|
+
*/
|
|
152
|
+
disableCacheControl?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Enables SSG mode for sites that serve on SCS or other static servers.
|
|
155
|
+
*/
|
|
156
|
+
ssg?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Overrides the "mode" (draft vs published) for SSG builds. Primarily
|
|
159
|
+
* intended for testing prior to launches.
|
|
160
|
+
*/
|
|
161
|
+
ssgMode?: DocMode;
|
|
162
|
+
/**
|
|
163
|
+
* Whether the route should only be available with ?preview=true.
|
|
164
|
+
*/
|
|
165
|
+
previewOnly?: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Overrides the default locale for the route.
|
|
168
|
+
*/
|
|
169
|
+
defaultLocale?: string;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Utility for creating Root filesystem routes that are connected to a CMS doc.
|
|
173
|
+
*/
|
|
174
|
+
declare function createRoute(options: CreateRouteOptions): Route;
|
|
175
|
+
declare function resolvePromisesMap(promisesMap: Record<string, Promise<any>>): Promise<Record<string, any>>;
|
|
176
|
+
/**
|
|
177
|
+
* Returns the first query param value in a given request.
|
|
178
|
+
*
|
|
179
|
+
* For example, for a URL like `/?foo=bar&foo=baz`, calling
|
|
180
|
+
* `getFirstQueryParam(req, 'foo')` would return `"bar"`.
|
|
181
|
+
*/
|
|
182
|
+
declare function getFirstQueryParam(req: Request, key: string): string | null;
|
|
183
|
+
/**
|
|
184
|
+
* Issues an HTTP redirect, preserving any query params from the original req.
|
|
185
|
+
*/
|
|
186
|
+
declare function redirectWithQuery(req: Request, res: Response, redirectCode: number, redirectPath: string): void;
|
|
187
|
+
/**
|
|
188
|
+
* Returns the CMS document mode associated with the request.
|
|
189
|
+
*/
|
|
190
|
+
declare function getMode(req: Request): Promise<DocMode>;
|
|
191
|
+
|
|
8
192
|
/** @deprecated Use client.ts instead. */
|
|
9
193
|
|
|
10
194
|
/**
|
|
@@ -50,4 +234,4 @@ declare function loadTranslations(rootConfig: RootConfig, options?: LoadTranslat
|
|
|
50
234
|
*/
|
|
51
235
|
declare function loadTranslationsForLocale(rootConfig: RootConfig, locale: string, options?: LoadTranslationsOptions): Promise<LocaleTranslations>;
|
|
52
236
|
|
|
53
|
-
export { LoadTranslationsOptions, LocaleTranslations, TranslationsMap, getDoc, listDocs, loadTranslations, loadTranslationsForLocale, numDocs, publishScheduledDocs };
|
|
237
|
+
export { type CreateRouteOptions, DocMode, LoadTranslationsOptions, LocaleTranslations, RootCMSClient, type RootCMSDoc, type Route, type RouteContext, type RouteRequest, type RouteResponse, TranslationsMap, createRoute, getDoc, getFirstQueryParam, getMode, listDocs, loadTranslations, loadTranslationsForLocale, numDocs, publishScheduledDocs, redirectWithQuery, resolvePromisesMap };
|
package/dist/core.js
CHANGED
|
@@ -1512,6 +1512,288 @@ var BatchResponse = class {
|
|
|
1512
1512
|
}
|
|
1513
1513
|
};
|
|
1514
1514
|
|
|
1515
|
+
// core/route.ts
|
|
1516
|
+
import {
|
|
1517
|
+
replaceParams
|
|
1518
|
+
} from "@blinkk/root";
|
|
1519
|
+
function createRoute(options) {
|
|
1520
|
+
let cmsClient;
|
|
1521
|
+
function getSlug(params) {
|
|
1522
|
+
if (options.slug) {
|
|
1523
|
+
return options.slug;
|
|
1524
|
+
}
|
|
1525
|
+
if (options.slugFormat) {
|
|
1526
|
+
return replaceParams(options.slugFormat, params);
|
|
1527
|
+
}
|
|
1528
|
+
const slugParam = options.slugParam || "slug";
|
|
1529
|
+
const slug = params[slugParam] || "index";
|
|
1530
|
+
return slug;
|
|
1531
|
+
}
|
|
1532
|
+
async function fetchData(fetchOptions) {
|
|
1533
|
+
if (!options.fetchData) {
|
|
1534
|
+
return {};
|
|
1535
|
+
}
|
|
1536
|
+
const promisesMap = options.fetchData(fetchOptions);
|
|
1537
|
+
return resolvePromisesMap(promisesMap);
|
|
1538
|
+
}
|
|
1539
|
+
async function generateProps(routeContext, locale) {
|
|
1540
|
+
const { slug, mode } = routeContext;
|
|
1541
|
+
const translationsTags = [
|
|
1542
|
+
"common",
|
|
1543
|
+
`${options.collection}/${slug.replaceAll("/", "--")}`
|
|
1544
|
+
];
|
|
1545
|
+
if (options.translations) {
|
|
1546
|
+
const tags = options.translations(routeContext)?.tags || [];
|
|
1547
|
+
translationsTags.push(...tags);
|
|
1548
|
+
}
|
|
1549
|
+
const [doc, translationsMap, data] = await Promise.all([
|
|
1550
|
+
cmsClient.getDoc(options.collection, slug, {
|
|
1551
|
+
mode
|
|
1552
|
+
}),
|
|
1553
|
+
cmsClient.loadTranslations({ tags: translationsTags }),
|
|
1554
|
+
fetchData(routeContext)
|
|
1555
|
+
]);
|
|
1556
|
+
if (!doc) {
|
|
1557
|
+
return { notFound: true };
|
|
1558
|
+
}
|
|
1559
|
+
const docLocales = doc.sys.locales || [];
|
|
1560
|
+
if (!docLocales.includes(locale)) {
|
|
1561
|
+
return { notFound: true };
|
|
1562
|
+
}
|
|
1563
|
+
const translations = translationsForLocale(translationsMap, locale);
|
|
1564
|
+
let props = { ...data, locale, mode, slug, doc };
|
|
1565
|
+
if (options.preRenderHook) {
|
|
1566
|
+
props = await options.preRenderHook(props, routeContext);
|
|
1567
|
+
}
|
|
1568
|
+
return { props, locale, translations };
|
|
1569
|
+
}
|
|
1570
|
+
const route = {
|
|
1571
|
+
// SSR handler.
|
|
1572
|
+
handle: async (req, res) => {
|
|
1573
|
+
if (!cmsClient) {
|
|
1574
|
+
cmsClient = new RootCMSClient(req.rootConfig);
|
|
1575
|
+
}
|
|
1576
|
+
req.cmsClient = cmsClient;
|
|
1577
|
+
const ctx = req.handlerContext;
|
|
1578
|
+
const slug = getSlug(ctx.params);
|
|
1579
|
+
if (slug.includes(".") || slug.includes("--")) {
|
|
1580
|
+
res.setHeader("cache-control", "private");
|
|
1581
|
+
return ctx.render404();
|
|
1582
|
+
}
|
|
1583
|
+
const mode = await getMode(req);
|
|
1584
|
+
const siteLocales = req.rootConfig?.i18n?.locales || ["en"];
|
|
1585
|
+
if (options.previewOnly && mode !== "draft") {
|
|
1586
|
+
return ctx.render404();
|
|
1587
|
+
}
|
|
1588
|
+
const routeContext = {
|
|
1589
|
+
req,
|
|
1590
|
+
slug,
|
|
1591
|
+
mode,
|
|
1592
|
+
cmsClient,
|
|
1593
|
+
params: ctx.params
|
|
1594
|
+
};
|
|
1595
|
+
const translationsTags = [
|
|
1596
|
+
"common",
|
|
1597
|
+
`${options.collection}/${slug.replaceAll("/", "--")}`
|
|
1598
|
+
];
|
|
1599
|
+
if (options.translations) {
|
|
1600
|
+
const tags = options.translations(routeContext)?.tags || [];
|
|
1601
|
+
translationsTags.push(...tags);
|
|
1602
|
+
}
|
|
1603
|
+
const [doc, translationsMap, data] = await Promise.all([
|
|
1604
|
+
cmsClient.getDoc(options.collection, slug, {
|
|
1605
|
+
mode
|
|
1606
|
+
}),
|
|
1607
|
+
// Only load translations for sites that have >1 locale configured.
|
|
1608
|
+
siteLocales.length > 1 ? cmsClient.loadTranslations({ tags: translationsTags }) : Promise.resolve({}),
|
|
1609
|
+
fetchData(routeContext)
|
|
1610
|
+
]);
|
|
1611
|
+
if (!doc) {
|
|
1612
|
+
if (options.notFoundHook) {
|
|
1613
|
+
await options.notFoundHook(req, res);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
res.setHeader("cache-control", "private");
|
|
1617
|
+
return ctx.render404();
|
|
1618
|
+
}
|
|
1619
|
+
const hl = getFirstQueryParam(req, "hl");
|
|
1620
|
+
let country = getFirstQueryParam(req, "gl") || req.get("x-country-code") || req.get("x-appengine-country") || "";
|
|
1621
|
+
if (country) {
|
|
1622
|
+
country = country.toUpperCase();
|
|
1623
|
+
}
|
|
1624
|
+
function getFallbackLocale(docLocales2) {
|
|
1625
|
+
const localesMap = {};
|
|
1626
|
+
docLocales2.forEach((docLocale) => {
|
|
1627
|
+
const lowerLocale = docLocale.toLowerCase();
|
|
1628
|
+
localesMap[lowerLocale] = docLocale;
|
|
1629
|
+
});
|
|
1630
|
+
if (hl === "fr") {
|
|
1631
|
+
if (country === "CA" && "fr-ca" in localesMap) {
|
|
1632
|
+
return localesMap["fr-ca"];
|
|
1633
|
+
}
|
|
1634
|
+
if (country === "FR" && "fr-fr" in localesMap) {
|
|
1635
|
+
return localesMap["fr-fr"];
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (hl === "pt") {
|
|
1639
|
+
if (country === "BR" && "pt-br" in localesMap) {
|
|
1640
|
+
return localesMap["pt-br"];
|
|
1641
|
+
}
|
|
1642
|
+
if (country === "PT" && "pt-pt" in localesMap) {
|
|
1643
|
+
return localesMap["pt-pt"];
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
const preferredLocale = ctx.getPreferredLocale(docLocales2);
|
|
1647
|
+
if (preferredLocale) {
|
|
1648
|
+
const normalizedLocale = localesMap[preferredLocale] || preferredLocale;
|
|
1649
|
+
if (preferredLocale === "en") {
|
|
1650
|
+
if (["AU", "CA", "IN", "MY"].includes(country)) {
|
|
1651
|
+
if (localesMap[`en-${country.toLowerCase()}`]) {
|
|
1652
|
+
return localesMap[`en-${country.toLowerCase()}`];
|
|
1653
|
+
}
|
|
1654
|
+
if (localesMap["en-gb"]) {
|
|
1655
|
+
return localesMap["en-gb"];
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return normalizedLocale;
|
|
1660
|
+
}
|
|
1661
|
+
const defaultLocale = options.defaultLocale || req.rootConfig?.i18n?.defaultLocale || "en";
|
|
1662
|
+
if (docLocales2.includes(defaultLocale)) {
|
|
1663
|
+
return defaultLocale;
|
|
1664
|
+
}
|
|
1665
|
+
return docLocales2[0];
|
|
1666
|
+
}
|
|
1667
|
+
const sys = doc.sys;
|
|
1668
|
+
const docLocales = sys.locales || ["en"];
|
|
1669
|
+
let locale = ctx.route.locale;
|
|
1670
|
+
if (ctx.route.isDefaultLocale) {
|
|
1671
|
+
locale = getFallbackLocale(docLocales);
|
|
1672
|
+
} else {
|
|
1673
|
+
if (!docLocales.includes(locale)) {
|
|
1674
|
+
if (options.notFoundHook) {
|
|
1675
|
+
await options.notFoundHook(req, res);
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
res.setHeader("cache-control", "private");
|
|
1679
|
+
return ctx.render404();
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
const translations = translationsForLocale(translationsMap, locale);
|
|
1683
|
+
let props = { ...data, req, locale, mode, slug, doc, country };
|
|
1684
|
+
if (options.preRenderHook) {
|
|
1685
|
+
props = await options.preRenderHook(props, routeContext);
|
|
1686
|
+
}
|
|
1687
|
+
if (props.$redirect) {
|
|
1688
|
+
const redirectCode = props.$redirectCode || 302;
|
|
1689
|
+
console.log(`redirecting to: ${props.$redirect} (${redirectCode})`);
|
|
1690
|
+
redirectWithQuery(req, res, redirectCode, props.$redirect);
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
if (options.disableCacheControl) {
|
|
1694
|
+
res.setHeader("cache-control", "private");
|
|
1695
|
+
} else if (mode === "published") {
|
|
1696
|
+
res.setHeader("cache-control", "public, max-age=15, s-maxage=30");
|
|
1697
|
+
if (ctx.route.isDefaultLocale) {
|
|
1698
|
+
res.setHeader(
|
|
1699
|
+
"vary",
|
|
1700
|
+
"accept-language,x-appengine-country,x-country-code"
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
if (options.setResponseHeaders) {
|
|
1705
|
+
options.setResponseHeaders(req, res);
|
|
1706
|
+
}
|
|
1707
|
+
return ctx.render(props, { locale, translations });
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
if (options.ssg) {
|
|
1711
|
+
route.getStaticPaths = async (ctx) => {
|
|
1712
|
+
if (!options.slugParam) {
|
|
1713
|
+
return { paths: [] };
|
|
1714
|
+
}
|
|
1715
|
+
if (!cmsClient) {
|
|
1716
|
+
cmsClient = new RootCMSClient(ctx.rootConfig);
|
|
1717
|
+
}
|
|
1718
|
+
const mode = options.ssgMode || "published";
|
|
1719
|
+
const res = await cmsClient.listDocs(options.collection, {
|
|
1720
|
+
mode
|
|
1721
|
+
});
|
|
1722
|
+
const ssgPaths = [];
|
|
1723
|
+
const slugParam = options.slugParam || "slug";
|
|
1724
|
+
res.docs.forEach((doc) => {
|
|
1725
|
+
const params = {};
|
|
1726
|
+
params[slugParam] = doc.slug.replaceAll("--", "/");
|
|
1727
|
+
ssgPaths.push({ params });
|
|
1728
|
+
});
|
|
1729
|
+
return { paths: ssgPaths };
|
|
1730
|
+
};
|
|
1731
|
+
route.getStaticProps = async (ctx) => {
|
|
1732
|
+
if (!cmsClient) {
|
|
1733
|
+
cmsClient = new RootCMSClient(ctx.rootConfig);
|
|
1734
|
+
}
|
|
1735
|
+
const slug = getSlug(ctx.params);
|
|
1736
|
+
const mode = options.ssgMode || "published";
|
|
1737
|
+
const routeContext = {
|
|
1738
|
+
req: void 0,
|
|
1739
|
+
slug,
|
|
1740
|
+
mode,
|
|
1741
|
+
cmsClient,
|
|
1742
|
+
params: ctx.params
|
|
1743
|
+
};
|
|
1744
|
+
return generateProps(routeContext, ctx.params.$locale);
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
return route;
|
|
1748
|
+
}
|
|
1749
|
+
async function resolvePromisesMap(promisesMap) {
|
|
1750
|
+
const keys = Object.keys(promisesMap);
|
|
1751
|
+
const promises = Object.values(promisesMap);
|
|
1752
|
+
const results = await Promise.all(promises);
|
|
1753
|
+
const resultMap = {};
|
|
1754
|
+
keys.forEach((key, index) => {
|
|
1755
|
+
resultMap[key] = results[index];
|
|
1756
|
+
});
|
|
1757
|
+
return resultMap;
|
|
1758
|
+
}
|
|
1759
|
+
function getFirstQueryParam(req, key) {
|
|
1760
|
+
const val = req.query[key];
|
|
1761
|
+
if (val === null || val === void 0) {
|
|
1762
|
+
return null;
|
|
1763
|
+
}
|
|
1764
|
+
if (Array.isArray(val)) {
|
|
1765
|
+
if (val.length === 0) {
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
return String(val[0]);
|
|
1769
|
+
}
|
|
1770
|
+
return String(val);
|
|
1771
|
+
}
|
|
1772
|
+
function redirectWithQuery(req, res, redirectCode, redirectPath) {
|
|
1773
|
+
if (!redirectPath.startsWith("/")) {
|
|
1774
|
+
res.redirect(redirectCode, redirectPath);
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
const queryStr = getQueryStr(req);
|
|
1778
|
+
const redirectUrl = queryStr ? `${redirectPath}?${queryStr}` : redirectPath;
|
|
1779
|
+
res.redirect(redirectCode, redirectUrl);
|
|
1780
|
+
}
|
|
1781
|
+
function getQueryStr(req) {
|
|
1782
|
+
const qIndex = req.originalUrl.indexOf("?");
|
|
1783
|
+
if (qIndex === -1) {
|
|
1784
|
+
return "";
|
|
1785
|
+
}
|
|
1786
|
+
return req.originalUrl.slice(qIndex + 1);
|
|
1787
|
+
}
|
|
1788
|
+
async function getMode(req) {
|
|
1789
|
+
const isPreview = String(req.query.preview) === "true";
|
|
1790
|
+
let mode = isPreview ? "draft" : "published";
|
|
1791
|
+
if (isPreview && req.query.mode === "published") {
|
|
1792
|
+
mode = "published";
|
|
1793
|
+
}
|
|
1794
|
+
return mode;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1515
1797
|
// core/runtime.ts
|
|
1516
1798
|
import { FieldValue as FieldValue3 } from "firebase-admin/firestore";
|
|
1517
1799
|
async function getDoc(rootConfig, collectionId, slug, options) {
|
|
@@ -1765,8 +2047,11 @@ export {
|
|
|
1765
2047
|
TranslationsManager,
|
|
1766
2048
|
buildTranslationsDbPath,
|
|
1767
2049
|
buildTranslationsLocaleDocDbPath,
|
|
2050
|
+
createRoute,
|
|
1768
2051
|
getCmsPlugin,
|
|
1769
2052
|
getDoc,
|
|
2053
|
+
getFirstQueryParam,
|
|
2054
|
+
getMode,
|
|
1770
2055
|
isRichTextData,
|
|
1771
2056
|
listDocs,
|
|
1772
2057
|
loadTranslations,
|
|
@@ -1777,6 +2062,8 @@ export {
|
|
|
1777
2062
|
numDocs,
|
|
1778
2063
|
parseDocId,
|
|
1779
2064
|
publishScheduledDocs,
|
|
2065
|
+
redirectWithQuery,
|
|
2066
|
+
resolvePromisesMap,
|
|
1780
2067
|
schema_exports as schema,
|
|
1781
2068
|
toArrayObject,
|
|
1782
2069
|
translationsForLocale,
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '@blinkk/root';
|
|
2
2
|
import 'firebase-admin/app';
|
|
3
3
|
import 'firebase-admin/firestore';
|
|
4
|
-
export { X as CMSAIConfig, _ as CMSPlugin, Z as CMSPluginOptions, Y as CMSSidebarTool, W as CMSUser, $ as cmsPlugin } from './client-
|
|
4
|
+
export { X as CMSAIConfig, _ as CMSPlugin, Z as CMSPluginOptions, Y as CMSSidebarTool, W as CMSUser, $ as cmsPlugin } from './client-Rka_a6jc.js';
|
package/dist/plugin.js
CHANGED
|
@@ -397,6 +397,51 @@ import { genkit } from "genkit";
|
|
|
397
397
|
import { logger } from "genkit/logging";
|
|
398
398
|
logger.setLogLevel("warn");
|
|
399
399
|
var DEFAULT_MODEL = "vertexai/gemini-2.5-flash";
|
|
400
|
+
async function summarizeDiff(cmsClient, options) {
|
|
401
|
+
const cmsPluginOptions = cmsClient.cmsPlugin.getConfig();
|
|
402
|
+
const firebaseConfig = cmsPluginOptions.firebaseConfig;
|
|
403
|
+
const model = (typeof cmsPluginOptions.experiments?.ai === "object" ? cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
|
|
404
|
+
const ai = genkit({
|
|
405
|
+
plugins: [
|
|
406
|
+
vertexAI({
|
|
407
|
+
projectId: firebaseConfig.projectId,
|
|
408
|
+
location: firebaseConfig.location || "us-central1"
|
|
409
|
+
})
|
|
410
|
+
]
|
|
411
|
+
});
|
|
412
|
+
const beforeJson = JSON.stringify(options.before ?? null, null, 2);
|
|
413
|
+
const afterJson = JSON.stringify(options.after ?? null, null, 2);
|
|
414
|
+
const systemPrompt = [
|
|
415
|
+
"You are an assistant that summarizes changes made to CMS documents stored as JSON.",
|
|
416
|
+
"Provide a concise description of the most important updates using short bullet points.",
|
|
417
|
+
'If there are no meaningful differences, respond with "No significant changes."',
|
|
418
|
+
`Focus on just the content changes, ignore insignificant changes to richtext blocks and structure, such as updates to the richtext block's "timestamp" and "version" fields.`
|
|
419
|
+
].join("\n");
|
|
420
|
+
const diffPrompt = [
|
|
421
|
+
"Previous version JSON:",
|
|
422
|
+
"```json",
|
|
423
|
+
beforeJson,
|
|
424
|
+
"```",
|
|
425
|
+
"",
|
|
426
|
+
"Updated version JSON:",
|
|
427
|
+
"```json",
|
|
428
|
+
afterJson,
|
|
429
|
+
"```",
|
|
430
|
+
"",
|
|
431
|
+
"Summarize the differences between the two payloads."
|
|
432
|
+
].join("\n");
|
|
433
|
+
const res = await ai.generate({
|
|
434
|
+
model,
|
|
435
|
+
messages: [
|
|
436
|
+
{
|
|
437
|
+
role: "system",
|
|
438
|
+
content: [{ text: systemPrompt }]
|
|
439
|
+
}
|
|
440
|
+
],
|
|
441
|
+
prompt: [{ text: diffPrompt }]
|
|
442
|
+
});
|
|
443
|
+
return res.text?.trim() || "";
|
|
444
|
+
}
|
|
400
445
|
var Chat = class {
|
|
401
446
|
constructor(chatClient, id, options) {
|
|
402
447
|
this.chatClient = chatClient;
|
|
@@ -2402,6 +2447,36 @@ function api(server, options) {
|
|
|
2402
2447
|
res.status(500).json({ success: false, error: "UNKNOWN" });
|
|
2403
2448
|
}
|
|
2404
2449
|
});
|
|
2450
|
+
server.use("/cms/api/ai.diff", async (req, res) => {
|
|
2451
|
+
if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
|
|
2452
|
+
res.status(400).json({ success: false, error: "BAD_REQUEST" });
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
if (!req.user?.email) {
|
|
2456
|
+
res.status(401).json({ success: false, error: "UNAUTHORIZED" });
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
const reqBody = req.body || {};
|
|
2460
|
+
if (!Object.prototype.hasOwnProperty.call(reqBody, "after")) {
|
|
2461
|
+
res.status(400).json({
|
|
2462
|
+
success: false,
|
|
2463
|
+
error: "MISSING_REQUIRED_FIELD",
|
|
2464
|
+
field: "after"
|
|
2465
|
+
});
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
try {
|
|
2469
|
+
const cmsClient = new RootCMSClient(req.rootConfig);
|
|
2470
|
+
const summary = await summarizeDiff(cmsClient, {
|
|
2471
|
+
before: reqBody.before ?? null,
|
|
2472
|
+
after: reqBody.after ?? null
|
|
2473
|
+
});
|
|
2474
|
+
res.status(200).json({ success: true, summary });
|
|
2475
|
+
} catch (err) {
|
|
2476
|
+
console.error(err.stack || err);
|
|
2477
|
+
res.status(500).json({ success: false, error: "UNKNOWN" });
|
|
2478
|
+
}
|
|
2479
|
+
});
|
|
2405
2480
|
server.use("/cms/api/ai.list_chats", async (req, res) => {
|
|
2406
2481
|
if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
|
|
2407
2482
|
res.status(400).json({ success: false, error: "BAD_REQUEST" });
|
|
@@ -2428,7 +2503,7 @@ function api(server, options) {
|
|
|
2428
2503
|
// package.json
|
|
2429
2504
|
var package_default = {
|
|
2430
2505
|
name: "@blinkk/root-cms",
|
|
2431
|
-
version: "2.2.
|
|
2506
|
+
version: "2.2.3",
|
|
2432
2507
|
author: "s@blinkk.com",
|
|
2433
2508
|
license: "MIT",
|
|
2434
2509
|
engines: {
|
|
@@ -2577,7 +2652,7 @@ var package_default = {
|
|
|
2577
2652
|
yjs: "13.6.27"
|
|
2578
2653
|
},
|
|
2579
2654
|
peerDependencies: {
|
|
2580
|
-
"@blinkk/root": "2.2.
|
|
2655
|
+
"@blinkk/root": "2.2.3",
|
|
2581
2656
|
"firebase-admin": ">=11",
|
|
2582
2657
|
"firebase-functions": ">=4",
|
|
2583
2658
|
preact: ">=10",
|
package/dist/project.d.ts
CHANGED
|
@@ -240,9 +240,9 @@ type Collection = SchemaWithTypes & {
|
|
|
240
240
|
*/
|
|
241
241
|
preview?: {
|
|
242
242
|
/** The field that provides the document title. */
|
|
243
|
-
title?: string;
|
|
243
|
+
title?: string | string[];
|
|
244
244
|
/** The field that provides the document image. */
|
|
245
|
-
image?: string;
|
|
245
|
+
image?: string | string[];
|
|
246
246
|
/** A fallback image to display when the document image field is empty. */
|
|
247
247
|
defaultImage?: {
|
|
248
248
|
src: string;
|