@canvas-digital/blog-sdk 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,20 +43,23 @@ apiKey: bp_public_... # or admin key
43
43
  domain: example.com # optional
44
44
  ```
45
45
 
46
- ## Host app / package `.env` (API base URL)
46
+ ## Host app `.env` (API base URL)
47
47
 
48
- This package includes root `.env.example`. On a new machine:
48
+ Use **one key** in every host app (Vite React, Next.js, or anything else):
49
49
 
50
50
  ```bash
51
- cp .env.example .env
51
+ BLOG_API_URL=https://blog-platform-backend-omega.vercel.app/api/v1
52
52
  ```
53
53
 
54
- ```bash
55
- # Platform API v1 (not the customer website)
56
- VITE_BLOG_API_URL=http://localhost:8000/api/v1
57
- ```
54
+ | Host | How `BLOG_API_URL` is exposed |
55
+ |------|-------------------------------|
56
+ | **Vite** | Set `envPrefix: ["VITE_", "BLOG_"]` in `vite.config` (see examples) |
57
+ | **Next.js** | Map it in `next.config` `env: { BLOG_API_URL: process.env.BLOG_API_URL }` |
58
+ | **Prop** | `<BlogPlatform apiUrl={...} />` overrides env |
59
+
60
+ Restart the dev server after changing `.env`. If unset, the SDK defaults to the production URL above. Legacy `VITE_BLOG_API_URL` / `NEXT_PUBLIC_BLOG_API_URL` still work.
58
61
 
59
- `email`, `apiKey`, and optional `domain` are **props** on `BlogPlatform`, not env vars in this package.
62
+ `email`, `apiKey`, and optional `domain` are **props** on `BlogPlatform`, not env vars.
60
63
 
61
64
  ## Usage in your React app
62
65
 
@@ -95,7 +98,7 @@ import { BlogPlatform } from "@canvas-digital/blog-sdk";
95
98
  | `theme` | user UI layout/colors |
96
99
  | `adminTheme` | admin UI theme |
97
100
  | `timeoutMs` | request timeout |
98
- | `apiUrl` | override env API base URL (usually omit) |
101
+ | `apiUrl` | override `BLOG_API_URL` (usually omit) |
99
102
  | `className` | wrapper class |
100
103
 
101
104
  ## Develop this package
@@ -108,3 +111,15 @@ npm test
108
111
  npm run build
109
112
  npm run dev # playground reads root .env
110
113
  ```
114
+
115
+ ## Publish to npm
116
+
117
+ Create a granular access token on [npmjs.com](https://www.npmjs.com) (publish permission for `@canvas-digital/blog-sdk`). Do not commit the token.
118
+
119
+ ```bash
120
+ npm config set //registry.npmjs.org/:_authToken=YOUR_NEW_TOKEN
121
+ npm whoami
122
+ npm publish --access public
123
+ ```
124
+
125
+ Bump `"version"` in `package.json` before each new release (npm rejects republishing the same version).
@@ -20,8 +20,7 @@ export type BlogPlatformProps = {
20
20
  email: string;
21
21
  /**
22
22
  * Optional override for the platform API base URL.
23
- * Prefer setting this in the host app `.env` instead:
24
- * `VITE_BLOG_API_URL` or `NEXT_PUBLIC_BLOG_API_URL`
23
+ * Prefer `BLOG_API_URL` in the host app `.env` (works for Vite, Next, etc.).
25
24
  */
26
25
  apiUrl?: string;
27
26
  timeoutMs?: number;
@@ -34,7 +33,7 @@ export type BlogPlatformProps = {
34
33
  * Drop into any React / Next.js app after `npm install @canvas-digital/blog-sdk`.
35
34
  *
36
35
  * - `email` + `apiKey` (+ optional `domain`) are sent as request headers
37
- * - API base URL comes from your app `.env` (`VITE_BLOG_API_URL` / `NEXT_PUBLIC_BLOG_API_URL`)
36
+ * - API base URL from host `.env` key `BLOG_API_URL` (or prop `apiUrl`)
38
37
  */
39
38
  export declare function BlogPlatform({ mode, domain, apiKey: apiKeyProp, email: emailProp, apiUrl: apiUrlOverride, timeoutMs, theme, branding, adminTheme, className, }: BlogPlatformProps): import("react").JSX.Element;
40
39
  //# sourceMappingURL=BlogPlatform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BlogPlatform.d.ts","sourceRoot":"","sources":["../app/BlogPlatform.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,eAAe,CAAC;AAE3E,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,IAAI,EAAE,gBAAgB,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACpC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,MAAM,EACN,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,cAAc,EACtB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE,iBAAiB,+BAwDnB"}
1
+ {"version":3,"file":"BlogPlatform.d.ts","sourceRoot":"","sources":["../app/BlogPlatform.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,eAAe,CAAC;AAE3E,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,IAAI,EAAE,gBAAgB,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACpC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,MAAM,EACN,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,cAAc,EACtB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE,iBAAiB,+BAuDnB"}
@@ -6,7 +6,7 @@ import { normalizeDomain, resolveBlogApiUrl } from "./utils/env";
6
6
  * Drop into any React / Next.js app after `npm install @canvas-digital/blog-sdk`.
7
7
  *
8
8
  * - `email` + `apiKey` (+ optional `domain`) are sent as request headers
9
- * - API base URL comes from your app `.env` (`VITE_BLOG_API_URL` / `NEXT_PUBLIC_BLOG_API_URL`)
9
+ * - API base URL from host `.env` key `BLOG_API_URL` (or prop `apiUrl`)
10
10
  */
11
11
  export function BlogPlatform({ mode, domain, apiKey: apiKeyProp, email: emailProp, apiUrl: apiUrlOverride, timeoutMs, theme, branding, adminTheme, className, }) {
12
12
  const clientDomain = normalizeDomain(domain);
@@ -17,7 +17,7 @@ export function BlogPlatform({ mode, domain, apiKey: apiKeyProp, email: emailPro
17
17
  return (_jsxs("div", { className: className, role: "alert", children: ["Provide ", _jsx("code", { children: "mode" }), ", ", _jsx("code", { children: "email" }), ", and ", _jsx("code", { children: "apiKey" }), " ", "(sent as request headers). ", _jsx("code", { children: "domain" }), " is optional."] }));
18
18
  }
19
19
  if (!apiUrl) {
20
- return (_jsxs("div", { className: className, role: "alert", children: ["Set the Blog API base URL in your app ", _jsx("code", { children: ".env" }), " as", " ", _jsx("code", { children: "VITE_BLOG_API_URL" }), " or ", _jsx("code", { children: "NEXT_PUBLIC_BLOG_API_URL" }), " ", "(e.g. ", _jsx("code", { children: "https://api.yourplatform.com/api/v1" }), ")."] }));
20
+ return (_jsxs("div", { className: className, role: "alert", children: ["Set ", _jsx("code", { children: "BLOG_API_URL" }), " in your app ", _jsx("code", { children: ".env" }), " ", "(e.g. ", _jsx("code", { children: "https://blog-platform-backend-omega.vercel.app/api/v1" }), ")."] }));
21
21
  }
22
22
  if (mode === "admin") {
23
23
  return (_jsx(BlogAdmin, { className: className, config: {
@@ -1 +1 @@
1
- {"version":3,"file":"AdminPostDetail.d.ts","sourceRoot":"","sources":["../../../app/components/admin/AdminPostDetail.tsx"],"names":[],"mappings":"AAcA,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,MAAM,EACN,MAAM,EAAE,OAAO,EACf,aAAa,GACd,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,+BAqNA"}
1
+ {"version":3,"file":"AdminPostDetail.d.ts","sourceRoot":"","sources":["../../../app/components/admin/AdminPostDetail.tsx"],"names":[],"mappings":"AAcA,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,MAAM,EACN,MAAM,EAAE,OAAO,EACf,aAAa,GACd,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,+BAuOA"}
@@ -14,10 +14,29 @@ export function AdminPostDetail({ postId, onBack, onEdit: _onEdit, onViewRelated
14
14
  const media = useMemo(() => {
15
15
  if (!post?.featuredImageId)
16
16
  return null;
17
+ const ref = post.featuredImageId;
17
18
  const hit = store
18
19
  .listMedia(post.brandId, "", 1, 200)
19
- .items.find((m) => m.id === post.featuredImageId);
20
- return hit ?? null;
20
+ .items.find((m) => m.id === ref || m.url === ref);
21
+ if (hit)
22
+ return hit;
23
+ if (/^(https?:|data:|blob:|\/)/i.test(ref)) {
24
+ return {
25
+ id: ref,
26
+ url: ref,
27
+ thumbUrl: ref,
28
+ alt: post.title,
29
+ filename: "",
30
+ width: null,
31
+ height: null,
32
+ filesize: null,
33
+ createdAt: post.updatedAt,
34
+ brandId: post.brandId,
35
+ caption: "",
36
+ credit: "",
37
+ };
38
+ }
39
+ return null;
21
40
  }, [post, store]);
22
41
  const authors = useMemo(() => {
23
42
  if (!post)
@@ -1 +1 @@
1
- {"version":3,"file":"MediaLibrary.d.ts","sourceRoot":"","sources":["../../../app/components/admin/MediaLibrary.tsx"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,gCAmR3B;AAED,uEAAuE;AACvE,wBAAgB,YAAY,gCAE3B"}
1
+ {"version":3,"file":"MediaLibrary.d.ts","sourceRoot":"","sources":["../../../app/components/admin/MediaLibrary.tsx"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,gCAiT3B;AAED,uEAAuE;AACvE,wBAAgB,YAAY,gCAE3B"}
@@ -4,8 +4,8 @@ import { useAdminMock } from "./mock/store";
4
4
  const kb = (n) => (n ? `${Math.round(n / 1024)} KB` : "—");
5
5
  export function MediaLibrary() {
6
6
  const store = useAdminMock();
7
- const tenant = store.activeBrandId;
8
- const tenantName = store.activeBrand?.name ?? "all brands";
7
+ const tenant = store.activeBrandId ?? store.brands[0]?.id ?? null;
8
+ const tenantName = store.brands.find((b) => b.id === tenant)?.name ?? store.activeBrand?.name ?? "all brands";
9
9
  const [items, setItems] = useState([]);
10
10
  const [count, setCount] = useState(0);
11
11
  const [query, setQuery] = useState("");
@@ -15,6 +15,7 @@ export function MediaLibrary() {
15
15
  const [uploading, setUploading] = useState(null);
16
16
  const [dragging, setDragging] = useState(false);
17
17
  const [error, setError] = useState(null);
18
+ const [flash, setFlash] = useState(null);
18
19
  const [open, setOpen] = useState(null);
19
20
  const [meta, setMeta] = useState({ alt: "", caption: "", credit: "" });
20
21
  const [saving, startSaving] = useTransition();
@@ -39,13 +40,29 @@ export function MediaLibrary() {
39
40
  return () => clearTimeout(id);
40
41
  }, [query, load]);
41
42
  const upload = async (files) => {
43
+ if (!tenant) {
44
+ setError("Select a brand before uploading images.");
45
+ return;
46
+ }
42
47
  const total = Array.from(files).length;
43
48
  setError(null);
49
+ setFlash(null);
44
50
  setUploading({ done: 0, total });
45
51
  const res = await store.uploadMedia(files, tenant);
46
52
  setUploading(null);
47
- if (!res.ok)
48
- setError(res.error ?? "Upload failed.");
53
+ if (!res.ok) {
54
+ setError(res.error ?? "Upload failed. Please try again.");
55
+ return;
56
+ }
57
+ setFlash(res.images.length === 1
58
+ ? "Image uploaded successfully."
59
+ : `${res.images.length} images uploaded successfully.`);
60
+ // Show uploaded items immediately (absolute URLs), then refresh list
61
+ setItems((prev) => {
62
+ const ids = new Set(res.images.map((i) => i.id));
63
+ return [...res.images, ...prev.filter((p) => !ids.has(p.id))];
64
+ });
65
+ setCount((c) => c + res.images.length);
49
66
  setPage(1);
50
67
  load(1, query, false);
51
68
  const first = res.images[0];
@@ -96,7 +113,7 @@ export function MediaLibrary() {
96
113
  if (e.target.files?.length)
97
114
  void upload(e.target.files);
98
115
  e.target.value = "";
99
- } }), error ? (_jsx("p", { className: "form-error", role: "alert", children: error })) : null, _jsxs("p", { className: "explain", children: ["Mock uploads create local object URLs via ", _jsx("b", { children: "FileReader" }), " \u2014 no server re-encoding in this package demo."] }), _jsxs("section", { className: `panel dropzone${dragging ? " is-dragging" : ""}`, onDragOver: (e) => {
116
+ } }), error ? (_jsx("p", { className: "form-error", role: "alert", children: error })) : null, flash ? (_jsx("p", { className: "form-ok", role: "status", children: flash })) : null, _jsx("p", { className: "explain", children: "Uploaded images are stored on the Blog Platform API and shown here for every brand." }), _jsxs("section", { className: `panel dropzone${dragging ? " is-dragging" : ""}`, onDragOver: (e) => {
100
117
  e.preventDefault();
101
118
  setDragging(true);
102
119
  }, onDragLeave: () => setDragging(false), onDrop: (e) => {
@@ -104,7 +121,7 @@ export function MediaLibrary() {
104
121
  setDragging(false);
105
122
  if (e.dataTransfer.files?.length)
106
123
  void upload(e.dataTransfer.files);
107
- }, children: [items.length === 0 && !loading ? (_jsx("p", { className: "d-empty", children: "Nothing here yet \u2014 drop images anywhere on this panel to upload." })) : (_jsx("div", { className: "media-grid", children: items.map((item) => (_jsxs("button", { type: "button", className: "media-card", onClick: () => openDetail(item), children: [item.thumbUrl ? _jsx("img", { src: item.thumbUrl, alt: "", loading: "lazy" }) : _jsx("span", { className: "pick-blank" }), _jsxs("span", { className: "media-meta", children: [_jsx("span", { className: "media-alt", children: item.alt || item.filename || "Image" }), _jsxs("span", { className: "media-sub", children: [item.width && item.height ? `${item.width}×${item.height}` : "—", " \u00B7 ", kb(item.filesize)] })] })] }, item.id))) })), hasMore ? (_jsx("div", { className: "modal-more", children: _jsx("button", { type: "button", className: "btn", disabled: loading, onClick: () => {
124
+ }, children: [items.length === 0 && !loading ? (_jsx("p", { className: "d-empty", children: "Nothing here yet \u2014 drop images anywhere on this panel to upload." })) : (_jsx("div", { className: "media-grid", children: items.map((item) => (_jsxs("button", { type: "button", className: "media-card", onClick: () => openDetail(item), children: [item.thumbUrl || item.url ? (_jsx("img", { src: item.thumbUrl || item.url, alt: "", loading: "lazy" })) : (_jsx("span", { className: "pick-blank" })), _jsxs("span", { className: "media-meta", children: [_jsx("span", { className: "media-alt", children: item.alt || item.filename || "Image" }), _jsxs("span", { className: "media-sub", children: [item.width && item.height ? `${item.width}×${item.height}` : "—", " \u00B7 ", kb(item.filesize)] })] })] }, item.id))) })), hasMore ? (_jsx("div", { className: "modal-more", children: _jsx("button", { type: "button", className: "btn", disabled: loading, onClick: () => {
108
125
  const next = page + 1;
109
126
  setPage(next);
110
127
  load(next, query, true);
@@ -1 +1 @@
1
- {"version":3,"file":"MediaPicker.d.ts","sourceRoot":"","sources":["../../../app/components/admin/MediaPicker.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAIF,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,OAAO,EACP,OAAO,EACP,QAAQ,EACR,KAAyB,GAC1B,EAAE,gBAAgB,sCAyMlB"}
1
+ {"version":3,"file":"MediaPicker.d.ts","sourceRoot":"","sources":["../../../app/components/admin/MediaPicker.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAIF,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,OAAO,EACP,OAAO,EACP,QAAQ,EACR,KAAyB,GAC1B,EAAE,gBAAgB,sCAgNlB"}
@@ -56,8 +56,14 @@ export function MediaPicker({ open, brandId, onClose, onSelect, title = "Choose
56
56
  setUploading(true);
57
57
  const res = await store.uploadMedia(files, brandId);
58
58
  setUploading(false);
59
- if (!res.ok)
60
- setError(res.error ?? "Upload failed");
59
+ if (!res.ok) {
60
+ setError(res.error ?? "Upload failed. Please try again.");
61
+ return;
62
+ }
63
+ setItems((prev) => {
64
+ const ids = new Set(res.images.map((i) => i.id));
65
+ return [...res.images, ...prev.filter((p) => !ids.has(p.id))];
66
+ });
61
67
  load(1, query, false);
62
68
  setPage(1);
63
69
  const last = res.images[res.images.length - 1];
@@ -78,7 +84,7 @@ export function MediaPicker({ open, brandId, onClose, onSelect, title = "Choose
78
84
  setDragging(false);
79
85
  if (e.dataTransfer.files?.length)
80
86
  void upload(e.dataTransfer.files);
81
- }, children: [items.length === 0 && !loading ? (_jsx("p", { className: "d-empty", children: "Nothing in the library yet. Drop an image here, or press Upload." })) : (_jsx("div", { className: "pick-grid", children: items.map((item) => (_jsxs("button", { type: "button", className: `pick${selected?.id === item.id ? " is-picked" : ""}`, onClick: () => setSelected(item), onDoubleClick: () => onSelect(item), children: [item.thumbUrl ? (_jsx("img", { src: item.thumbUrl, alt: "", loading: "lazy" })) : (_jsx("span", { className: "pick-blank" })), _jsxs("span", { className: "pick-meta", children: [_jsx("span", { className: `pick-alt${item.alt ? "" : " is-missing"}`, children: item.alt || "Image" }), _jsxs("span", { className: "pick-sub", children: [item.width && item.height ? `${item.width}×${item.height}` : "—", " \u00B7", " ", kb(item.filesize)] })] })] }, item.id))) })), hasMore ? (_jsx("div", { className: "modal-more", children: _jsx("button", { type: "button", className: "btn", disabled: loading, onClick: () => {
87
+ }, children: [items.length === 0 && !loading ? (_jsx("p", { className: "d-empty", children: "Nothing in the library yet. Drop an image here, or press Upload." })) : (_jsx("div", { className: "pick-grid", children: items.map((item) => (_jsxs("button", { type: "button", className: `pick${selected?.id === item.id ? " is-picked" : ""}`, onClick: () => setSelected(item), onDoubleClick: () => onSelect(item), children: [item.thumbUrl || item.url ? (_jsx("img", { src: item.thumbUrl || item.url, alt: "", loading: "lazy" })) : (_jsx("span", { className: "pick-blank" })), _jsxs("span", { className: "pick-meta", children: [_jsx("span", { className: `pick-alt${item.alt ? "" : " is-missing"}`, children: item.alt || "Image" }), _jsxs("span", { className: "pick-sub", children: [item.width && item.height ? `${item.width}×${item.height}` : "—", " \u00B7", " ", kb(item.filesize)] })] })] }, item.id))) })), hasMore ? (_jsx("div", { className: "modal-more", children: _jsx("button", { type: "button", className: "btn", disabled: loading, onClick: () => {
82
88
  const next = page + 1;
83
89
  setPage(next);
84
90
  load(next, query, true);
@@ -1,11 +1,13 @@
1
1
  import type { AdminSdkAuthor, AdminSdkBrand, AdminSdkCategory, AdminSdkComment, AdminSdkMe, AdminSdkMedia, AdminSdkPost, AdminSdkTag } from "../../../services/admin-sdk-client";
2
2
  import type { BrandForm, CommentRow, MediaHit, MockProfile, PostRow, TermRow } from "../mock/fixtures";
3
+ /** Turn API-relative `/media/...` paths into absolute URLs for `<img src>`. */
4
+ export declare function resolveMediaUrl(url: string | null | undefined, apiBaseUrl?: string): string;
3
5
  export declare function mapAdminMeToProfile(me: AdminSdkMe): MockProfile;
4
6
  export declare function mapBrandToForm(brand: AdminSdkBrand): BrandForm;
5
7
  export declare function mapCategoryToTerm(cat: AdminSdkCategory): TermRow;
6
8
  export declare function mapTagToTerm(tag: AdminSdkTag): TermRow;
7
9
  export declare function mapAuthorToTerm(author: AdminSdkAuthor): TermRow;
8
- export declare function mapMediaToHit(media: AdminSdkMedia): MediaHit;
10
+ export declare function mapMediaToHit(media: AdminSdkMedia, apiBaseUrl?: string): MediaHit;
9
11
  export declare function mapPostToRow(post: AdminSdkPost, brands: BrandForm[], categories: TermRow[], tags?: TermRow[], authors?: TermRow[]): PostRow;
10
12
  export declare function mapCommentToRow(comment: AdminSdkComment, posts: PostRow[], brands: BrandForm[]): CommentRow;
11
13
  export declare function postRowToWrite(input: Partial<PostRow> & {
@@ -1 +1 @@
1
- {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../../app/components/admin/api/mappers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAKvG,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,UAAU,GAAG,WAAW,CAO/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAgB9D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAWhE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAWtD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAW/D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,CAe5D;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,SAAS,EAAE,EACnB,UAAU,EAAE,OAAO,EAAE,EACrB,IAAI,GAAE,OAAO,EAAO,EACpB,OAAO,GAAE,OAAO,EAAO,GACtB,OAAO,CAsDT;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,OAAO,EAAE,EAChB,MAAM,EAAE,SAAS,EAAE,GAClB,UAAU,CAgBZ;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC5D,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;EAyB7E"}
1
+ {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../../app/components/admin/api/mappers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAKvG,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAO3F;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,UAAU,GAAG,WAAW,CAO/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAgB9D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAWhE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAWtD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAW/D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CAiBjF;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,SAAS,EAAE,EACnB,UAAU,EAAE,OAAO,EAAE,EACrB,IAAI,GAAE,OAAO,EAAO,EACpB,OAAO,GAAE,OAAO,EAAO,GACtB,OAAO,CAsDT;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,OAAO,EAAE,EAChB,MAAM,EAAE,SAAS,EAAE,GAClB,UAAU,CAgBZ;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC5D,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;EAyB7E"}
@@ -1,4 +1,17 @@
1
1
  const asIso = (value) => (value ? String(value) : null);
2
+ /** Turn API-relative `/media/...` paths into absolute URLs for `<img src>`. */
3
+ export function resolveMediaUrl(url, apiBaseUrl) {
4
+ if (!url)
5
+ return "";
6
+ if (/^(https?:|data:|blob:)/i.test(url))
7
+ return url;
8
+ if (!apiBaseUrl)
9
+ return url;
10
+ const origin = apiBaseUrl.replace(/\/api\/v\d+\/?$/i, "").replace(/\/+$/, "");
11
+ if (!origin)
12
+ return url;
13
+ return url.startsWith("/") ? `${origin}${url}` : `${origin}/${url}`;
14
+ }
2
15
  export function mapAdminMeToProfile(me) {
3
16
  return {
4
17
  id: me.id,
@@ -60,11 +73,13 @@ export function mapAuthorToTerm(author) {
60
73
  brandId: author.brandId ?? "",
61
74
  };
62
75
  }
63
- export function mapMediaToHit(media) {
76
+ export function mapMediaToHit(media, apiBaseUrl) {
77
+ const url = resolveMediaUrl(media.url, apiBaseUrl);
78
+ const thumbUrl = resolveMediaUrl(media.thumbUrl || media.url, apiBaseUrl);
64
79
  return {
65
80
  id: media.id,
66
- url: media.url,
67
- thumbUrl: media.thumbUrl || media.url,
81
+ url,
82
+ thumbUrl: thumbUrl || url,
68
83
  alt: media.alt ?? media.originalFilename ?? media.filename ?? "",
69
84
  filename: media.originalFilename || media.filename,
70
85
  width: media.width ?? null,
@@ -1 +1 @@
1
- {"version":3,"file":"PostEditor.d.ts","sourceRoot":"","sources":["../../../../app/components/admin/editor/PostEditor.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,MAAM,GACP,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,+BA6iCA"}
1
+ {"version":3,"file":"PostEditor.d.ts","sourceRoot":"","sources":["../../../../app/components/admin/editor/PostEditor.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,MAAM,GACP,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,+BAkkCA"}
@@ -43,10 +43,16 @@ export function PostEditor({ postId, onDone, }) {
43
43
  const [publishedAt, setPublishedAt] = useState(existing?.publishedAt ?? null);
44
44
  const [scheduleWhen, setScheduleWhen] = useState("");
45
45
  const [featuredImage, setFeaturedImage] = useState(() => {
46
- if (!existing?.featuredImageId)
46
+ const ref = existing?.featuredImageId;
47
+ if (!ref)
47
48
  return null;
48
- const hit = store.listMedia(brandId || null, "", 1, 200).items.find((m) => m.id === existing.featuredImageId);
49
- return hit ? { id: hit.id, url: hit.url, alt: hit.alt } : null;
49
+ const hit = store.listMedia(brandId || null, "", 1, 200).items.find((m) => m.id === ref || m.url === ref);
50
+ if (hit)
51
+ return { id: hit.id, url: hit.url, alt: hit.alt };
52
+ if (/^(https?:|data:|blob:|\/)/i.test(ref)) {
53
+ return { id: ref, url: ref, alt: "" };
54
+ }
55
+ return null;
50
56
  });
51
57
  const [seo, setSeo] = useState({
52
58
  metaTitle: existing?.seo.metaTitle ?? "",
@@ -186,60 +192,76 @@ export function PostEditor({ postId, onDone, }) {
186
192
  }
187
193
  start(() => {
188
194
  void (async () => {
189
- const createdTagIds = [];
190
- for (const name of newTags) {
191
- const term = await Promise.resolve(store.saveTerm({ collection: "tags", brandId, name }));
192
- if (term.ok && term.id)
193
- createdTagIds.push(term.id);
194
- }
195
- const mergedTagIds = [...tagIds, ...createdTagIds];
196
- const res = await Promise.resolve(store.savePost({
197
- id: existing?.id,
198
- title,
199
- slug: slug || slugify(title),
200
- brandId,
201
- excerpt,
202
- contentHtml,
203
- status: nextStatus,
204
- reviewState: nextReview,
205
- categoryIds,
206
- tagIds: mergedTagIds,
207
- authorIds,
208
- scheduledFor: nextScheduled,
209
- publishedAt: nextPublished,
210
- featuredImageId: featuredImage?.id ?? null,
211
- allowComments,
212
- visibility,
213
- template,
214
- authorBox,
215
- showInTableOfContents: showToc,
216
- postFormat,
217
- seo: {
218
- metaTitle: seo.metaTitle,
219
- metaDescription: seo.metaDescription,
220
- focusKeyphrase: seo.focusKeyphrase,
221
- noindex: seo.noindex,
222
- },
223
- }));
224
- if (!res.ok) {
225
- setError(res.error ?? "Save failed");
226
- return;
195
+ try {
196
+ const createdTagIds = [];
197
+ for (const name of newTags) {
198
+ const term = await Promise.resolve(store.saveTerm({ collection: "tags", brandId, name }));
199
+ if (!term.ok) {
200
+ setError(term.error ?? "Could not create tag.");
201
+ return;
202
+ }
203
+ if (term.id)
204
+ createdTagIds.push(term.id);
205
+ }
206
+ const mergedTagIds = [...tagIds, ...createdTagIds];
207
+ const res = await Promise.resolve(store.savePost({
208
+ id: existing?.id,
209
+ title,
210
+ slug: slug || slugify(title),
211
+ brandId,
212
+ excerpt,
213
+ contentHtml,
214
+ status: nextStatus,
215
+ reviewState: nextReview,
216
+ categoryIds,
217
+ tagIds: mergedTagIds,
218
+ authorIds,
219
+ scheduledFor: nextScheduled,
220
+ publishedAt: nextPublished,
221
+ // Backend stores a public image URL in featured_image
222
+ featuredImageId: featuredImage?.url ?? featuredImage?.id ?? null,
223
+ allowComments,
224
+ visibility,
225
+ template,
226
+ authorBox,
227
+ showInTableOfContents: showToc,
228
+ postFormat,
229
+ seo: {
230
+ metaTitle: seo.metaTitle,
231
+ metaDescription: seo.metaDescription,
232
+ focusKeyphrase: seo.focusKeyphrase,
233
+ noindex: seo.noindex,
234
+ },
235
+ }));
236
+ if (!res.ok) {
237
+ setError(res.error ?? "Failed to save post. Please try again.");
238
+ return;
239
+ }
240
+ if (createdTagIds.length) {
241
+ setTagIds(mergedTagIds);
242
+ setNewTags([]);
243
+ }
244
+ setStatus(nextStatus);
245
+ setReviewState(nextReview);
246
+ setScheduledFor(nextScheduled);
247
+ setPublishedAt(nextPublished);
248
+ const successMsg = intent === "publish"
249
+ ? existing?.id
250
+ ? "Post published successfully."
251
+ : "Post created and published successfully."
252
+ : intent === "schedule"
253
+ ? "Post scheduled successfully."
254
+ : intent === "unpublish"
255
+ ? "Post unpublished."
256
+ : existing?.id
257
+ ? "Post updated successfully."
258
+ : "Post created successfully.";
259
+ setFlash(successMsg);
260
+ window.setTimeout(() => onDone(), 900);
227
261
  }
228
- if (createdTagIds.length) {
229
- setTagIds(mergedTagIds);
230
- setNewTags([]);
262
+ catch (err) {
263
+ setError(err instanceof Error ? err.message : "Failed to save post. Please try again.");
231
264
  }
232
- setStatus(nextStatus);
233
- setReviewState(nextReview);
234
- setScheduledFor(nextScheduled);
235
- setPublishedAt(nextPublished);
236
- setFlash(intent === "publish"
237
- ? "Published."
238
- : intent === "schedule"
239
- ? "Scheduled."
240
- : intent === "unpublish"
241
- ? "Unpublished."
242
- : "Saved.");
243
265
  })();
244
266
  });
245
267
  };
@@ -275,14 +297,16 @@ export function PostEditor({ postId, onDone, }) {
275
297
  const uploadFeatured = async (files) => {
276
298
  setFeaturedBusy(true);
277
299
  setError(null);
300
+ setFlash(null);
278
301
  const res = await store.uploadMedia(files, brandId || null);
279
302
  setFeaturedBusy(false);
280
303
  if (!res.ok || !res.images[0]) {
281
- setError(res.error ?? "Upload failed");
304
+ setError(res.error ?? "Image upload failed. Please try again.");
282
305
  return;
283
306
  }
284
307
  const img = res.images[0];
285
308
  setFeaturedImage({ id: img.id, url: img.url, alt: img.alt });
309
+ setFlash("Image uploaded successfully.");
286
310
  };
287
311
  const choosePicked = (item) => {
288
312
  if (pickerFor === "og") {
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../app/components/admin/mock/store.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../app/components/admin/mock/store.tsx"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
3
3
  import { AdminSdkClient } from "../../../services/admin-sdk-client";
4
4
  import { getErrorMessage } from "../../../services/errors";
5
- import { mapAdminMeToProfile, mapAuthorToTerm, mapBrandToForm, mapCategoryToTerm, mapCommentToRow, mapMediaToHit, mapPostToRow, mapTagToTerm, postRowToWrite, } from "../api/mappers";
5
+ import { mapAdminMeToProfile, mapAuthorToTerm, mapBrandToForm, mapCategoryToTerm, mapCommentToRow, mapMediaToHit, mapPostToRow, mapTagToTerm, postRowToWrite, resolveMediaUrl, } from "../api/mappers";
6
6
  import { createInitialFixtures, nextId, slugify, } from "./fixtures";
7
7
  const syncTermCounts = (state) => {
8
8
  const countFor = (collection, termId) => state.posts.filter((p) => {
@@ -106,8 +106,14 @@ export function AdminMockProvider({ children, config, }) {
106
106
  const categories = catsPage.items.map(mapCategoryToTerm);
107
107
  const tags = tagsPage.items.map(mapTagToTerm);
108
108
  const authors = authorsPage.items.map(mapAuthorToTerm);
109
- const media = mediaPage.items.map(mapMediaToHit);
110
- const posts = postsPage.items.map((p) => mapPostToRow(p, brands, categories, tags, authors));
109
+ const media = mediaPage.items.map((m) => mapMediaToHit(m, config.apiUrl));
110
+ const posts = postsPage.items.map((p) => {
111
+ const row = mapPostToRow(p, brands, categories, tags, authors);
112
+ if (row.featuredImageId) {
113
+ row.featuredImageId = resolveMediaUrl(row.featuredImageId, config.apiUrl);
114
+ }
115
+ return row;
116
+ });
111
117
  const comments = commentsPage.items.map((c) => mapCommentToRow(c, posts, brands));
112
118
  const profile = mapAdminMeToProfile(me);
113
119
  const domainHint = config.domain?.trim().toLowerCase().replace(/^www\./, "") ?? "";
@@ -630,9 +636,10 @@ export function AdminMockProvider({ children, config, }) {
630
636
  const images = [];
631
637
  for (const file of list) {
632
638
  const saved = await api.uploadMedia(file, brand);
633
- images.push(mapMediaToHit(saved));
639
+ images.push(mapMediaToHit(saved, config?.apiUrl));
634
640
  }
635
641
  await refreshFromApi();
642
+ // Prefer freshly mapped upload results (already absolute URLs)
636
643
  return { ok: true, images };
637
644
  }
638
645
  catch (error) {
@@ -666,7 +673,7 @@ export function AdminMockProvider({ children, config, }) {
666
673
  }
667
674
  update((prev) => ({ ...prev, media: [...images, ...prev.media] }));
668
675
  return { ok: true, images };
669
- }, [activeBrandId, data.brands, update, refreshFromApi]);
676
+ }, [activeBrandId, data.brands, update, refreshFromApi, config?.apiUrl]);
670
677
  const updateMedia = useCallback((id, meta) => {
671
678
  if (!meta.alt.trim())
672
679
  return { ok: false, error: "Alt text is required." };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./services";
2
2
  export { BlogList, BlogDetail, BlogSearch, BlogCategories, BlogTags, FeaturedBlogs, RelatedBlogs, } from "./components/user";
3
- export { sanitizeHtml, normalizeDomain, resolveBlogApiUrl, } from "./utils";
3
+ export { sanitizeHtml, normalizeDomain, resolveBlogApiUrl, DEFAULT_BLOG_API_URL, } from "./utils";
4
4
  export { DEFAULT_THEME, formatDate, cardStyle, } from "./styles/userTheme";
5
5
  export type { BlogTheme, ThemeColors, ThemeFonts, BlogComponentProps, Branding, } from "./types/user";
6
6
  export { BlogAdmin } from "./pages/admin";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../app/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,OAAO,EACL,QAAQ,EACR,UAAU,EACV,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,YAAY,EACZ,eAAe,EACf,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,aAAa,EACb,UAAU,EACV,SAAS,GACV,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,QAAQ,GACT,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EACL,QAAQ,EACR,UAAU,EACV,eAAe,EACf,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,YAAY,EAAE,UAAU,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,eAAe,CAAC;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../app/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,OAAO,EACL,QAAQ,EACR,UAAU,EACV,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,aAAa,EACb,UAAU,EACV,SAAS,GACV,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,QAAQ,GACT,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EACL,QAAQ,EACR,UAAU,EACV,eAAe,EACf,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,YAAY,EAAE,UAAU,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,eAAe,CAAC;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,cAAc,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./services";
2
2
  export { BlogList, BlogDetail, BlogSearch, BlogCategories, BlogTags, FeaturedBlogs, RelatedBlogs, } from "./components/user";
3
- export { sanitizeHtml, normalizeDomain, resolveBlogApiUrl, } from "./utils";
3
+ export { sanitizeHtml, normalizeDomain, resolveBlogApiUrl, DEFAULT_BLOG_API_URL, } from "./utils";
4
4
  export { DEFAULT_THEME, formatDate, cardStyle, } from "./styles/userTheme";
5
5
  export { BlogAdmin } from "./pages/admin";
6
6
  export { PostList, PostEditor, CategoryManager, TagManager, MediaManager, } from "./components/admin";
@@ -3,14 +3,18 @@
3
3
  * `https://example.com/blog` → `example.com`
4
4
  */
5
5
  export declare function normalizeDomain(input?: string | null): string;
6
+ /** Deployed platform API — used when `BLOG_API_URL` is unset. */
7
+ export declare const DEFAULT_BLOG_API_URL = "https://blog-platform-backend-omega.vercel.app/api/v1";
6
8
  /**
7
- * Blog platform API base URL from the host app `.env`.
8
- * Supported names (first match wins):
9
- * - BLOG_API_URL
10
- * - VITE_BLOG_API_URL
11
- * - NEXT_PUBLIC_BLOG_API_URL
12
- * - VITE_API_URL
13
- * - NEXT_PUBLIC_API_URL
9
+ * Resolve the Blog Platform API base URL.
10
+ *
11
+ * One key for every host (Vite, Next, CRA, Node):
12
+ * ```bash
13
+ * BLOG_API_URL=https://blog-platform-backend-omega.vercel.app/api/v1
14
+ * ```
15
+ *
16
+ * Legacy aliases still work: `VITE_BLOG_API_URL`, `NEXT_PUBLIC_BLOG_API_URL`.
17
+ * Or pass `apiUrl` on `<BlogPlatform />`.
14
18
  */
15
19
  export declare function resolveBlogApiUrl(override?: string): string;
16
20
  //# sourceMappingURL=env.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../app/utils/env.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAY7D;AAuBD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAc3D"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../app/utils/env.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAY7D;AAuBD,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,0DACwB,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAY3D"}
package/dist/utils/env.js CHANGED
@@ -34,14 +34,18 @@ function readEnv(name) {
34
34
  }
35
35
  return undefined;
36
36
  }
37
+ /** Deployed platform API — used when `BLOG_API_URL` is unset. */
38
+ export const DEFAULT_BLOG_API_URL = "https://blog-platform-backend-omega.vercel.app/api/v1";
37
39
  /**
38
- * Blog platform API base URL from the host app `.env`.
39
- * Supported names (first match wins):
40
- * - BLOG_API_URL
41
- * - VITE_BLOG_API_URL
42
- * - NEXT_PUBLIC_BLOG_API_URL
43
- * - VITE_API_URL
44
- * - NEXT_PUBLIC_API_URL
40
+ * Resolve the Blog Platform API base URL.
41
+ *
42
+ * One key for every host (Vite, Next, CRA, Node):
43
+ * ```bash
44
+ * BLOG_API_URL=https://blog-platform-backend-omega.vercel.app/api/v1
45
+ * ```
46
+ *
47
+ * Legacy aliases still work: `VITE_BLOG_API_URL`, `NEXT_PUBLIC_BLOG_API_URL`.
48
+ * Or pass `apiUrl` on `<BlogPlatform />`.
45
49
  */
46
50
  export function resolveBlogApiUrl(override) {
47
51
  const explicit = override?.trim();
@@ -50,8 +54,6 @@ export function resolveBlogApiUrl(override) {
50
54
  }
51
55
  const fromEnv = readEnv("BLOG_API_URL") ??
52
56
  readEnv("VITE_BLOG_API_URL") ??
53
- readEnv("NEXT_PUBLIC_BLOG_API_URL") ??
54
- readEnv("VITE_API_URL") ??
55
- readEnv("NEXT_PUBLIC_API_URL");
56
- return (fromEnv ?? "").replace(/\/+$/, "");
57
+ readEnv("NEXT_PUBLIC_BLOG_API_URL");
58
+ return (fromEnv ?? DEFAULT_BLOG_API_URL).replace(/\/+$/, "");
57
59
  }
@@ -1,3 +1,3 @@
1
1
  export { sanitizeHtml } from "./sanitize";
2
- export { normalizeDomain, resolveBlogApiUrl } from "./env";
2
+ export { normalizeDomain, resolveBlogApiUrl, DEFAULT_BLOG_API_URL } from "./env";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../app/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../app/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC"}
@@ -1,2 +1,2 @@
1
1
  export { sanitizeHtml } from "./sanitize";
2
- export { normalizeDomain, resolveBlogApiUrl } from "./env";
2
+ export { normalizeDomain, resolveBlogApiUrl, DEFAULT_BLOG_API_URL } from "./env";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canvas-digital/blog-sdk",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Unified Blog SDK — user (published blogs) and admin (CRUD) in one package.",
5
5
  "license": "MIT",
6
6
  "type": "module",