@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 +24 -9
- package/dist/BlogPlatform.d.ts +2 -3
- package/dist/BlogPlatform.d.ts.map +1 -1
- package/dist/BlogPlatform.js +2 -2
- package/dist/components/admin/AdminPostDetail.d.ts.map +1 -1
- package/dist/components/admin/AdminPostDetail.js +21 -2
- package/dist/components/admin/MediaLibrary.d.ts.map +1 -1
- package/dist/components/admin/MediaLibrary.js +23 -6
- package/dist/components/admin/MediaPicker.d.ts.map +1 -1
- package/dist/components/admin/MediaPicker.js +9 -3
- package/dist/components/admin/api/mappers.d.ts +3 -1
- package/dist/components/admin/api/mappers.d.ts.map +1 -1
- package/dist/components/admin/api/mappers.js +18 -3
- package/dist/components/admin/editor/PostEditor.d.ts.map +1 -1
- package/dist/components/admin/editor/PostEditor.js +80 -56
- package/dist/components/admin/mock/store.d.ts.map +1 -1
- package/dist/components/admin/mock/store.js +12 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/env.d.ts +11 -7
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/env.js +13 -11
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
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
|
|
46
|
+
## Host app `.env` (API base URL)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Use **one key** in every host app (Vite React, Next.js, or anything else):
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
|
|
51
|
+
BLOG_API_URL=https://blog-platform-backend-omega.vercel.app/api/v1
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
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
|
|
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).
|
package/dist/BlogPlatform.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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"}
|
package/dist/BlogPlatform.js
CHANGED
|
@@ -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
|
|
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
|
|
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,+
|
|
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 ===
|
|
20
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
67
|
-
thumbUrl:
|
|
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,+
|
|
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
|
-
|
|
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 ===
|
|
49
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
229
|
-
|
|
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 ?? "
|
|
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":"AAAA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;
|
|
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) =>
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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";
|
package/dist/utils/env.d.ts
CHANGED
|
@@ -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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
package/dist/utils/env.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* -
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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
|
-
|
|
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
|
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -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"}
|
package/dist/utils/index.js
CHANGED
|
@@ -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";
|