@bettercms-ai/react 0.2.0 → 0.3.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/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -73,6 +73,10 @@ function BcmsProvider({ env, draftPath = "/api/draft", client: given, children,
|
|
|
73
73
|
live = false;
|
|
74
74
|
};
|
|
75
75
|
}, [draftPath, apiUrl]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (document.querySelector('meta[name="generator"][content="BetterCMS"]')) return;
|
|
78
|
+
document.head.append(Object.assign(document.createElement("meta"), { name: "generator", content: "BetterCMS" }));
|
|
79
|
+
}, []);
|
|
76
80
|
const value = useMemo(
|
|
77
81
|
() => ({
|
|
78
82
|
client: given ?? createBcmsClient({ apiUrl, workspace, projectId, mediaUrl, studioUrl, previewToken: token }),
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.tsx","../src/hooks.ts","../src/index.ts"],"sourcesContent":["import { createContext, createElement, useContext, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { createClient, type BetterCMSReadClient } from \"@bettercms-ai/sdk/read\";\nimport { resolveBcmsConfig, type BcmsSiteOptions } from \"@bettercms-ai/sdk/config\";\nimport { previewTokenValid, safeRedirectPath } from \"@bettercms-ai/sdk/draft\";\n\n/** Where the tab keeps a checked preview token: per tab, gone when it closes. */\nexport const DRAFT_TOKEN_KEY = \"bcms-preview-token\";\n\nexport interface BcmsContext {\n client: BetterCMSReadClient;\n /** The tab is previewing drafts. */\n draft: boolean;\n /** Leave draft mode (forget the token). */\n exitDraft(): void;\n}\n\n/** A read client for these settings: drafts when a preview token is given. */\nexport function createBcmsClient(\n options: BcmsSiteOptions & { env?: Record<string, unknown>; previewToken?: string | null } = {},\n): BetterCMSReadClient {\n const { env, previewToken, ...site } = options;\n const { config, missing } = resolveBcmsConfig({ options: site, env: (env ?? {}) as Record<string, string | undefined>, prefixes: [\"VITE_\", \"\"] });\n if (missing.length > 0) {\n throw new Error(\n `[@bettercms-ai/react] Missing ${missing.join(\" and \")}: pass <BcmsProvider env={import.meta.env}> with VITE_BCMS_API_URL and VITE_BCMS_WORKSPACE set, or the values as props.`,\n );\n }\n return createClient({\n apiUrl: config.apiUrl,\n workspace: config.workspace,\n projectId: config.projectId,\n ...(previewToken ? { perspective: \"drafts\" as const, previewToken } : {}),\n });\n}\n\nconst Ctx = createContext<BcmsContext | null>(null);\n\nexport interface BcmsProviderProps extends BcmsSiteOptions {\n /** The app's env, usually `import.meta.env` (a library cannot read it for you). */\n env?: Record<string, unknown>;\n /** The path the editor's draft link opens. Default `/api/draft`, what the editor uses. */\n draftPath?: string;\n /** A ready client instead of one built from settings (tests, SSR). */\n client?: BetterCMSReadClient;\n children?: ReactNode;\n}\n\nfunction storedToken(): string | null {\n try {\n return sessionStorage.getItem(DRAFT_TOKEN_KEY);\n } catch {\n return null;\n }\n}\n\nexport function BcmsProvider({ env, draftPath = \"/api/draft\", client: given, children, ...site }: BcmsProviderProps) {\n const [token, setToken] = useState<string | null>(() => (typeof window === \"undefined\" ? null : storedToken()));\n // Keyed on the resolved strings, never on `env` itself: a Vite production build inlines\n // `import.meta.env` as a fresh object at each use, so an object key would build a new client\n // (and refetch every hook) on every render.\n const { config } = resolveBcmsConfig({ options: site, env: (env ?? {}) as Record<string, string | undefined>, prefixes: [\"VITE_\", \"\"] });\n const { apiUrl, workspace, projectId, mediaUrl, studioUrl } = config;\n\n const forget = () => {\n try {\n sessionStorage.removeItem(DRAFT_TOKEN_KEY);\n } catch {\n // Nothing kept.\n }\n setToken(null);\n };\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !apiUrl) return;\n let live = true;\n const here = window.location.pathname.replace(/\\/+$/, \"\") || \"/\";\n if (here === (draftPath.replace(/\\/+$/, \"\") || \"/\")) {\n // The editor's draft link: check the token with the backend (the signature authority), keep\n // it for this tab, and land on the page it names. A refused or unreachable check leaves the\n // app published, and the token never stays in the address bar either way.\n const url = new URL(window.location.href);\n const fresh = url.searchParams.get(\"token\");\n const to = safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\");\n const land = () => {\n window.history.replaceState(null, \"\", to);\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n };\n if (!fresh) {\n land();\n return;\n }\n void previewTokenValid(apiUrl, fresh)\n .catch(() => false)\n .then((ok) => {\n if (!live) return;\n if (ok) {\n try {\n sessionStorage.setItem(DRAFT_TOKEN_KEY, fresh);\n } catch {\n // Storage refused (a sandboxed frame): draft mode lasts this page only.\n }\n setToken(fresh);\n }\n land();\n });\n } else {\n // A token kept from earlier in the tab is checked again: an expired one would fail every read.\n const kept = storedToken();\n if (kept) void previewTokenValid(apiUrl, kept).catch(() => true).then((ok) => live && !ok && forget());\n }\n return () => {\n live = false;\n };\n }, [draftPath, apiUrl]);\n\n const value = useMemo<BcmsContext>(\n () => ({\n client: given ?? createBcmsClient({ apiUrl, workspace, projectId, mediaUrl, studioUrl, previewToken: token }),\n draft: Boolean(token),\n exitDraft: forget,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [given, token, apiUrl, workspace, projectId, mediaUrl, studioUrl],\n );\n return createElement(Ctx.Provider, { value }, children);\n}\n\n/** The client and draft state from the nearest <BcmsProvider>. */\nexport function useBcms(): BcmsContext {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error(\"[@bettercms-ai/react] useBcms() needs a <BcmsProvider> above it.\");\n return ctx;\n}\n","import { useEffect, useState } from \"react\";\nimport type { BetterCMSReadClient } from \"@bettercms-ai/sdk/read\";\nimport { useBcms } from \"./provider.js\";\n\nexport interface BcmsQuery<T> {\n data: T | undefined;\n error: Error | null;\n loading: boolean;\n}\n\n/**\n * Read with the provider's client; re-reads when `key` or the client (draft mode) changes. A\n * stale answer never overwrites a newer one.\n */\nexport function useBcmsQuery<T>(key: string, read: (client: BetterCMSReadClient) => Promise<T>): BcmsQuery<T> {\n const { client } = useBcms();\n const [state, setState] = useState<BcmsQuery<T>>({ data: undefined, error: null, loading: true });\n useEffect(() => {\n let current = true;\n // A new key shows nothing of the old one's answer while it loads.\n setState({ data: undefined, error: null, loading: true });\n read(client).then(\n (data) => current && setState({ data, error: null, loading: false }),\n (error: unknown) => current && setState({ data: undefined, error: error instanceof Error ? error : new Error(String(error)), loading: false }),\n );\n return () => {\n current = false;\n };\n // `read` is the caller's inline function: the key says when it means something else.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, key]);\n return state;\n}\n\ntype Opts<K extends keyof BetterCMSReadClient> = BetterCMSReadClient[K] extends (...a: infer A) => unknown ? A : never;\n\n/** A page by slug, with its `blocks` for <BcmsBlocks>. */\nexport const usePage = (slug: string) => useBcmsQuery(`page:${slug}`, (c) => c.getPage(slug));\n/** An entry by slug. */\nexport const useEntry = <T = Record<string, unknown>>(slug: string, opts?: Opts<\"getEntry\">[1]) =>\n useBcmsQuery(`entry:${slug}:${JSON.stringify(opts ?? {})}`, (c) => c.getEntry<T>(slug, opts));\n/** A list of entries. */\nexport const useEntries = <T = Record<string, unknown>>(opts?: Opts<\"listEntries\">[0]) =>\n useBcmsQuery(`entries:${JSON.stringify(opts ?? {})}`, (c) => c.listEntries<T>(opts));\n/** The project's Global Layout (navigation, footer). */\nexport const useLayout = () => useBcmsQuery(\"layout\", (c) => c.getLayout());\n/** Component definitions for <BcmsBlocks components={…}>. */\nexport const useComponents = () => useBcmsQuery(\"components\", (c) => c.listComponents());\n","/**\n * @bettercms-ai/react — BetterCMS for client-rendered React apps (Vite, CRA, any SPA).\n *\n * // main.tsx\n * import { BcmsProvider } from \"@bettercms-ai/react\";\n * <BcmsProvider env={import.meta.env}><App /></BcmsProvider>\n *\n * // a page\n * const { data: page } = usePage(\"home\");\n * return page ? <BcmsBlocks blocks={page.blocks} /> : null;\n *\n * Settings resolve the way every adapter resolves them (@bettercms-ai/sdk/config): props, then\n * `VITE_BCMS_*`, then `BCMS_*`. Draft mode needs no server: the editor's draft link lands on the\n * app's own draft path (`/api/draft?token=…&redirect=/x`, which an SPA host serves as the app),\n * the provider checks the token with the backend, keeps it for the tab and moves to `redirect`.\n * The components are @bettercms-ai/next's browser-safe ones, the same markup on both.\n */\nexport { BcmsProvider, useBcms, createBcmsClient, DRAFT_TOKEN_KEY, type BcmsProviderProps, type BcmsContext } from \"./provider.js\";\nexport { usePage, useEntry, useEntries, useLayout, useComponents, useBcmsQuery, type BcmsQuery } from \"./hooks.js\";\nexport { BcmsBlocks } from \"@bettercms-ai/next/blocks\";\nexport { BcmsSections, BcmsSection } from \"@bettercms-ai/next/sections\";\nexport { BcmsField, BcmsImage, BcmsButton } from \"@bettercms-ai/next/elements\";\nexport { BcmsForm } from \"@bettercms-ai/next/form\";\nexport { BcmsSearch } from \"@bettercms-ai/next/search\";\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/hooks.ts","../src/index.ts"],"sourcesContent":["import { createContext, createElement, useContext, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { createClient, type BetterCMSReadClient } from \"@bettercms-ai/sdk/read\";\nimport { resolveBcmsConfig, type BcmsSiteOptions } from \"@bettercms-ai/sdk/config\";\nimport { previewTokenValid, safeRedirectPath } from \"@bettercms-ai/sdk/draft\";\n\n/** Where the tab keeps a checked preview token: per tab, gone when it closes. */\nexport const DRAFT_TOKEN_KEY = \"bcms-preview-token\";\n\nexport interface BcmsContext {\n client: BetterCMSReadClient;\n /** The tab is previewing drafts. */\n draft: boolean;\n /** Leave draft mode (forget the token). */\n exitDraft(): void;\n}\n\n/** A read client for these settings: drafts when a preview token is given. */\nexport function createBcmsClient(\n options: BcmsSiteOptions & { env?: Record<string, unknown>; previewToken?: string | null } = {},\n): BetterCMSReadClient {\n const { env, previewToken, ...site } = options;\n const { config, missing } = resolveBcmsConfig({ options: site, env: (env ?? {}) as Record<string, string | undefined>, prefixes: [\"VITE_\", \"\"] });\n if (missing.length > 0) {\n throw new Error(\n `[@bettercms-ai/react] Missing ${missing.join(\" and \")}: pass <BcmsProvider env={import.meta.env}> with VITE_BCMS_API_URL and VITE_BCMS_WORKSPACE set, or the values as props.`,\n );\n }\n return createClient({\n apiUrl: config.apiUrl,\n workspace: config.workspace,\n projectId: config.projectId,\n ...(previewToken ? { perspective: \"drafts\" as const, previewToken } : {}),\n });\n}\n\nconst Ctx = createContext<BcmsContext | null>(null);\n\nexport interface BcmsProviderProps extends BcmsSiteOptions {\n /** The app's env, usually `import.meta.env` (a library cannot read it for you). */\n env?: Record<string, unknown>;\n /** The path the editor's draft link opens. Default `/api/draft`, what the editor uses. */\n draftPath?: string;\n /** A ready client instead of one built from settings (tests, SSR). */\n client?: BetterCMSReadClient;\n children?: ReactNode;\n}\n\nfunction storedToken(): string | null {\n try {\n return sessionStorage.getItem(DRAFT_TOKEN_KEY);\n } catch {\n return null;\n }\n}\n\nexport function BcmsProvider({ env, draftPath = \"/api/draft\", client: given, children, ...site }: BcmsProviderProps) {\n const [token, setToken] = useState<string | null>(() => (typeof window === \"undefined\" ? null : storedToken()));\n // Keyed on the resolved strings, never on `env` itself: a Vite production build inlines\n // `import.meta.env` as a fresh object at each use, so an object key would build a new client\n // (and refetch every hook) on every render.\n const { config } = resolveBcmsConfig({ options: site, env: (env ?? {}) as Record<string, string | undefined>, prefixes: [\"VITE_\", \"\"] });\n const { apiUrl, workspace, projectId, mediaUrl, studioUrl } = config;\n\n const forget = () => {\n try {\n sessionStorage.removeItem(DRAFT_TOKEN_KEY);\n } catch {\n // Nothing kept.\n }\n setToken(null);\n };\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !apiUrl) return;\n let live = true;\n const here = window.location.pathname.replace(/\\/+$/, \"\") || \"/\";\n if (here === (draftPath.replace(/\\/+$/, \"\") || \"/\")) {\n // The editor's draft link: check the token with the backend (the signature authority), keep\n // it for this tab, and land on the page it names. A refused or unreachable check leaves the\n // app published, and the token never stays in the address bar either way.\n const url = new URL(window.location.href);\n const fresh = url.searchParams.get(\"token\");\n const to = safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\");\n const land = () => {\n window.history.replaceState(null, \"\", to);\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n };\n if (!fresh) {\n land();\n return;\n }\n void previewTokenValid(apiUrl, fresh)\n .catch(() => false)\n .then((ok) => {\n if (!live) return;\n if (ok) {\n try {\n sessionStorage.setItem(DRAFT_TOKEN_KEY, fresh);\n } catch {\n // Storage refused (a sandboxed frame): draft mode lasts this page only.\n }\n setToken(fresh);\n }\n land();\n });\n } else {\n // A token kept from earlier in the tab is checked again: an expired one would fail every read.\n const kept = storedToken();\n if (kept) void previewTokenValid(apiUrl, kept).catch(() => true).then((ok) => live && !ok && forget());\n }\n return () => {\n live = false;\n };\n }, [draftPath, apiUrl]);\n\n // `<meta name=\"generator\">`, what Wappalyzer and BuiltWith read to name the CMS. A client-rendered\n // app has no server `<head>` to carry it, so the provider adds it once.\n useEffect(() => {\n if (document.querySelector('meta[name=\"generator\"][content=\"BetterCMS\"]')) return;\n document.head.append(Object.assign(document.createElement(\"meta\"), { name: \"generator\", content: \"BetterCMS\" }));\n }, []);\n\n const value = useMemo<BcmsContext>(\n () => ({\n client: given ?? createBcmsClient({ apiUrl, workspace, projectId, mediaUrl, studioUrl, previewToken: token }),\n draft: Boolean(token),\n exitDraft: forget,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [given, token, apiUrl, workspace, projectId, mediaUrl, studioUrl],\n );\n return createElement(Ctx.Provider, { value }, children);\n}\n\n/** The client and draft state from the nearest <BcmsProvider>. */\nexport function useBcms(): BcmsContext {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error(\"[@bettercms-ai/react] useBcms() needs a <BcmsProvider> above it.\");\n return ctx;\n}\n","import { useEffect, useState } from \"react\";\nimport type { BetterCMSReadClient } from \"@bettercms-ai/sdk/read\";\nimport { useBcms } from \"./provider.js\";\n\nexport interface BcmsQuery<T> {\n data: T | undefined;\n error: Error | null;\n loading: boolean;\n}\n\n/**\n * Read with the provider's client; re-reads when `key` or the client (draft mode) changes. A\n * stale answer never overwrites a newer one.\n */\nexport function useBcmsQuery<T>(key: string, read: (client: BetterCMSReadClient) => Promise<T>): BcmsQuery<T> {\n const { client } = useBcms();\n const [state, setState] = useState<BcmsQuery<T>>({ data: undefined, error: null, loading: true });\n useEffect(() => {\n let current = true;\n // A new key shows nothing of the old one's answer while it loads.\n setState({ data: undefined, error: null, loading: true });\n read(client).then(\n (data) => current && setState({ data, error: null, loading: false }),\n (error: unknown) => current && setState({ data: undefined, error: error instanceof Error ? error : new Error(String(error)), loading: false }),\n );\n return () => {\n current = false;\n };\n // `read` is the caller's inline function: the key says when it means something else.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, key]);\n return state;\n}\n\ntype Opts<K extends keyof BetterCMSReadClient> = BetterCMSReadClient[K] extends (...a: infer A) => unknown ? A : never;\n\n/** A page by slug, with its `blocks` for <BcmsBlocks>. */\nexport const usePage = (slug: string) => useBcmsQuery(`page:${slug}`, (c) => c.getPage(slug));\n/** An entry by slug. */\nexport const useEntry = <T = Record<string, unknown>>(slug: string, opts?: Opts<\"getEntry\">[1]) =>\n useBcmsQuery(`entry:${slug}:${JSON.stringify(opts ?? {})}`, (c) => c.getEntry<T>(slug, opts));\n/** A list of entries. */\nexport const useEntries = <T = Record<string, unknown>>(opts?: Opts<\"listEntries\">[0]) =>\n useBcmsQuery(`entries:${JSON.stringify(opts ?? {})}`, (c) => c.listEntries<T>(opts));\n/** The project's Global Layout (navigation, footer). */\nexport const useLayout = () => useBcmsQuery(\"layout\", (c) => c.getLayout());\n/** Component definitions for <BcmsBlocks components={…}>. */\nexport const useComponents = () => useBcmsQuery(\"components\", (c) => c.listComponents());\n","/**\n * @bettercms-ai/react — BetterCMS for client-rendered React apps (Vite, CRA, any SPA).\n *\n * // main.tsx\n * import { BcmsProvider } from \"@bettercms-ai/react\";\n * <BcmsProvider env={import.meta.env}><App /></BcmsProvider>\n *\n * // a page\n * const { data: page } = usePage(\"home\");\n * return page ? <BcmsBlocks blocks={page.blocks} /> : null;\n *\n * Settings resolve the way every adapter resolves them (@bettercms-ai/sdk/config): props, then\n * `VITE_BCMS_*`, then `BCMS_*`. Draft mode needs no server: the editor's draft link lands on the\n * app's own draft path (`/api/draft?token=…&redirect=/x`, which an SPA host serves as the app),\n * the provider checks the token with the backend, keeps it for the tab and moves to `redirect`.\n * The components are @bettercms-ai/next's browser-safe ones, the same markup on both.\n */\nexport { BcmsProvider, useBcms, createBcmsClient, DRAFT_TOKEN_KEY, type BcmsProviderProps, type BcmsContext } from \"./provider.js\";\nexport { usePage, useEntry, useEntries, useLayout, useComponents, useBcmsQuery, type BcmsQuery } from \"./hooks.js\";\nexport { BcmsBlocks } from \"@bettercms-ai/next/blocks\";\nexport { BcmsSections, BcmsSection } from \"@bettercms-ai/next/sections\";\nexport { BcmsField, BcmsImage, BcmsButton } from \"@bettercms-ai/next/elements\";\nexport { BcmsForm } from \"@bettercms-ai/next/form\";\nexport { BcmsSearch } from \"@bettercms-ai/next/search\";\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettercms-ai/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "BetterCMS for React apps (Vite and other client-rendered React): provider, hooks, draft mode by link, and the section and field components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@bettercms-ai/next": "^0.
|
|
22
|
+
"@bettercms-ai/next": "^0.30.0",
|
|
23
23
|
"@bettercms-ai/sdk": "^1.34.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|