@endora-commerce/mod-blog 0.100.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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/admin/api/blog-client.d.ts +70 -0
- package/dist/admin/api/blog-client.d.ts.map +1 -0
- package/dist/admin/api/blog-client.js +137 -0
- package/dist/admin/api/blog-client.js.map +1 -0
- package/dist/admin/components/CategoryTreeNode.d.ts +25 -0
- package/dist/admin/components/CategoryTreeNode.d.ts.map +1 -0
- package/dist/admin/components/CategoryTreeNode.js +14 -0
- package/dist/admin/components/CategoryTreeNode.js.map +1 -0
- package/dist/admin/components/PostStatusBadge.d.ts +7 -0
- package/dist/admin/components/PostStatusBadge.d.ts.map +1 -0
- package/dist/admin/components/PostStatusBadge.js +16 -0
- package/dist/admin/components/PostStatusBadge.js.map +1 -0
- package/dist/admin/components/RelatedPostsPicker.d.ts +11 -0
- package/dist/admin/components/RelatedPostsPicker.d.ts.map +1 -0
- package/dist/admin/components/RelatedPostsPicker.js +77 -0
- package/dist/admin/components/RelatedPostsPicker.js.map +1 -0
- package/dist/admin/components/RelatedProductsPicker.d.ts +9 -0
- package/dist/admin/components/RelatedProductsPicker.d.ts.map +1 -0
- package/dist/admin/components/RelatedProductsPicker.js +66 -0
- package/dist/admin/components/RelatedProductsPicker.js.map +1 -0
- package/dist/admin/components/TagPicker.d.ts +9 -0
- package/dist/admin/components/TagPicker.d.ts.map +1 -0
- package/dist/admin/components/TagPicker.js +66 -0
- package/dist/admin/components/TagPicker.js.map +1 -0
- package/dist/admin/index.d.ts +33 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +114 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/BlogCategoryEditor.d.ts +9 -0
- package/dist/admin/pages/BlogCategoryEditor.d.ts.map +1 -0
- package/dist/admin/pages/BlogCategoryEditor.js +196 -0
- package/dist/admin/pages/BlogCategoryEditor.js.map +1 -0
- package/dist/admin/pages/BlogCategoryTreePage.d.ts +9 -0
- package/dist/admin/pages/BlogCategoryTreePage.d.ts.map +1 -0
- package/dist/admin/pages/BlogCategoryTreePage.js +122 -0
- package/dist/admin/pages/BlogCategoryTreePage.js.map +1 -0
- package/dist/admin/pages/BlogPostEditor.d.ts +9 -0
- package/dist/admin/pages/BlogPostEditor.d.ts.map +1 -0
- package/dist/admin/pages/BlogPostEditor.js +314 -0
- package/dist/admin/pages/BlogPostEditor.js.map +1 -0
- package/dist/admin/pages/BlogPostListPage.d.ts +9 -0
- package/dist/admin/pages/BlogPostListPage.d.ts.map +1 -0
- package/dist/admin/pages/BlogPostListPage.js +75 -0
- package/dist/admin/pages/BlogPostListPage.js.map +1 -0
- package/dist/admin/pages/BlogTagListPage.d.ts +9 -0
- package/dist/admin/pages/BlogTagListPage.d.ts.map +1 -0
- package/dist/admin/pages/BlogTagListPage.js +145 -0
- package/dist/admin/pages/BlogTagListPage.js.map +1 -0
- package/dist/backend/entities/blog-category-language.entity.d.ts +11 -0
- package/dist/backend/entities/blog-category-language.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-category-language.entity.js +35 -0
- package/dist/backend/entities/blog-category-language.entity.js.map +1 -0
- package/dist/backend/entities/blog-category-sales-channel.entity.d.ts +17 -0
- package/dist/backend/entities/blog-category-sales-channel.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-category-sales-channel.entity.js +48 -0
- package/dist/backend/entities/blog-category-sales-channel.entity.js.map +1 -0
- package/dist/backend/entities/blog-category.entity.d.ts +32 -0
- package/dist/backend/entities/blog-category.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-category.entity.js +113 -0
- package/dist/backend/entities/blog-category.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-category.entity.d.ts +10 -0
- package/dist/backend/entities/blog-post-category.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-category.entity.js +34 -0
- package/dist/backend/entities/blog-post-category.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-language.entity.d.ts +9 -0
- package/dist/backend/entities/blog-post-language.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-language.entity.js +33 -0
- package/dist/backend/entities/blog-post-language.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-related-post.entity.d.ts +15 -0
- package/dist/backend/entities/blog-post-related-post.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-related-post.entity.js +42 -0
- package/dist/backend/entities/blog-post-related-post.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-related-product.entity.d.ts +14 -0
- package/dist/backend/entities/blog-post-related-product.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-related-product.entity.js +41 -0
- package/dist/backend/entities/blog-post-related-product.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-sales-channel.entity.d.ts +16 -0
- package/dist/backend/entities/blog-post-sales-channel.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-sales-channel.entity.js +47 -0
- package/dist/backend/entities/blog-post-sales-channel.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-tag.entity.d.ts +14 -0
- package/dist/backend/entities/blog-post-tag.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-tag.entity.js +41 -0
- package/dist/backend/entities/blog-post-tag.entity.js.map +1 -0
- package/dist/backend/entities/blog-post.entity.d.ts +31 -0
- package/dist/backend/entities/blog-post.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post.entity.js +106 -0
- package/dist/backend/entities/blog-post.entity.js.map +1 -0
- package/dist/backend/entities/blog-tag.entity.d.ts +18 -0
- package/dist/backend/entities/blog-tag.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-tag.entity.js +66 -0
- package/dist/backend/entities/blog-tag.entity.js.map +1 -0
- package/dist/backend/index.d.ts +100 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +182 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +12 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +148 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +6 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +72 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/blog-asset-references.d.ts +4 -0
- package/dist/backend/services/blog-asset-references.d.ts.map +1 -0
- package/dist/backend/services/blog-asset-references.js +79 -0
- package/dist/backend/services/blog-asset-references.js.map +1 -0
- package/dist/backend/services/blog-cache.d.ts +55 -0
- package/dist/backend/services/blog-cache.d.ts.map +1 -0
- package/dist/backend/services/blog-cache.js +111 -0
- package/dist/backend/services/blog-cache.js.map +1 -0
- package/dist/backend/services/blog-category-service.d.ts +53 -0
- package/dist/backend/services/blog-category-service.d.ts.map +1 -0
- package/dist/backend/services/blog-category-service.js +446 -0
- package/dist/backend/services/blog-category-service.js.map +1 -0
- package/dist/backend/services/blog-language-reference.d.ts +18 -0
- package/dist/backend/services/blog-language-reference.d.ts.map +1 -0
- package/dist/backend/services/blog-language-reference.js +43 -0
- package/dist/backend/services/blog-language-reference.js.map +1 -0
- package/dist/backend/services/blog-post-service.d.ts +79 -0
- package/dist/backend/services/blog-post-service.d.ts.map +1 -0
- package/dist/backend/services/blog-post-service.js +539 -0
- package/dist/backend/services/blog-post-service.js.map +1 -0
- package/dist/backend/services/blog-settings-resolver.d.ts +36 -0
- package/dist/backend/services/blog-settings-resolver.d.ts.map +1 -0
- package/dist/backend/services/blog-settings-resolver.js +68 -0
- package/dist/backend/services/blog-settings-resolver.js.map +1 -0
- package/dist/backend/services/blog-slug-collision.d.ts +35 -0
- package/dist/backend/services/blog-slug-collision.d.ts.map +1 -0
- package/dist/backend/services/blog-slug-collision.js +84 -0
- package/dist/backend/services/blog-slug-collision.js.map +1 -0
- package/dist/backend/services/blog-storefront-resolver.d.ts +107 -0
- package/dist/backend/services/blog-storefront-resolver.d.ts.map +1 -0
- package/dist/backend/services/blog-storefront-resolver.js +492 -0
- package/dist/backend/services/blog-storefront-resolver.js.map +1 -0
- package/dist/backend/services/blog-tag-service.d.ts +45 -0
- package/dist/backend/services/blog-tag-service.d.ts.map +1 -0
- package/dist/backend/services/blog-tag-service.js +198 -0
- package/dist/backend/services/blog-tag-service.js.map +1 -0
- package/dist/backend/services/seed-default-category.d.ts +25 -0
- package/dist/backend/services/seed-default-category.d.ts.map +1 -0
- package/dist/backend/services/seed-default-category.js +48 -0
- package/dist/backend/services/seed-default-category.js.map +1 -0
- package/dist/backend/services/seed-roles.d.ts +41 -0
- package/dist/backend/services/seed-roles.d.ts.map +1 -0
- package/dist/backend/services/seed-roles.js +98 -0
- package/dist/backend/services/seed-roles.js.map +1 -0
- package/dist/manifest.d.ts +226 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +236 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260506T081055_blog_init.d.ts +33 -0
- package/dist/migrations/20260506T081055_blog_init.d.ts.map +1 -0
- package/dist/migrations/20260506T081055_blog_init.js +260 -0
- package/dist/migrations/20260506T081055_blog_init.js.map +1 -0
- package/dist/migrations/20260903T101744_blog_namespace_block_names.d.ts +6 -0
- package/dist/migrations/20260903T101744_blog_namespace_block_names.d.ts.map +1 -0
- package/dist/migrations/20260903T101744_blog_namespace_block_names.js +58 -0
- package/dist/migrations/20260903T101744_blog_namespace_block_names.js.map +1 -0
- package/dist/migrations/index.d.ts +34 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +37 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/blog/index.md +199 -0
- package/i18n/en.json +166 -0
- package/i18n/pl.json +166 -0
- package/package.json +108 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TagPicker.d.ts","sourceRoot":"","sources":["../../../src/admin/components/TagPicker.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAYzE,UAAU,cAAc;IACtB,kDAAkD;IAClD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACpC;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,cAAc,GAAG,SAAS,CA2JxE"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { Alert, AlertDescription, Badge, Button, Input, Label } from '@endora-commerce/admin-kit/ui';
|
|
4
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
5
|
+
import { blogClient } from '../api/blog-client.js';
|
|
6
|
+
import { normalize } from '@endora-commerce/admin-kit/lib';
|
|
7
|
+
function pickName(name, fallback) {
|
|
8
|
+
if (!name)
|
|
9
|
+
return fallback;
|
|
10
|
+
return name['en-US'] ?? Object.values(name)[0] ?? fallback;
|
|
11
|
+
}
|
|
12
|
+
export function TagPicker({ value, onChange }) {
|
|
13
|
+
const t = useTranslation('blog');
|
|
14
|
+
const [allTags, setAllTags] = useState([]);
|
|
15
|
+
const [q, setQ] = useState('');
|
|
16
|
+
const [loading, setLoading] = useState(true);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
let live = true;
|
|
20
|
+
blogClient
|
|
21
|
+
.listTags({ perPage: 100 })
|
|
22
|
+
.then((res) => {
|
|
23
|
+
if (!live)
|
|
24
|
+
return;
|
|
25
|
+
setAllTags(res.data);
|
|
26
|
+
setLoading(false);
|
|
27
|
+
})
|
|
28
|
+
.catch((err) => {
|
|
29
|
+
if (!live)
|
|
30
|
+
return;
|
|
31
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
32
|
+
setLoading(false);
|
|
33
|
+
});
|
|
34
|
+
return () => {
|
|
35
|
+
live = false;
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
const tagsById = new Map(allTags.map((t) => [t.id, t]));
|
|
39
|
+
const needle = normalize(q);
|
|
40
|
+
const candidates = allTags.filter((tag) => {
|
|
41
|
+
if (value.includes(tag.id))
|
|
42
|
+
return false;
|
|
43
|
+
if (needle === '')
|
|
44
|
+
return true;
|
|
45
|
+
const haystack = `${normalize(pickName(tag.name, tag.code))} ${normalize(tag.code)}`;
|
|
46
|
+
return haystack.includes(needle);
|
|
47
|
+
});
|
|
48
|
+
const attach = useCallback((id) => onChange([...value, id]), [onChange, value]);
|
|
49
|
+
const detach = useCallback((id) => onChange(value.filter((existing) => existing !== id)), [onChange, value]);
|
|
50
|
+
const move = useCallback((id, dir) => {
|
|
51
|
+
const idx = value.indexOf(id);
|
|
52
|
+
if (idx < 0)
|
|
53
|
+
return;
|
|
54
|
+
const next = dir === 'up' ? idx - 1 : idx + 1;
|
|
55
|
+
if (next < 0 || next >= value.length)
|
|
56
|
+
return;
|
|
57
|
+
const out = [...value];
|
|
58
|
+
[out[idx], out[next]] = [out[next], out[idx]];
|
|
59
|
+
onChange(out);
|
|
60
|
+
}, [onChange, value]);
|
|
61
|
+
return (_jsxs("div", { className: "space-y-3", children: [error ? (_jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: error }) })) : null, _jsxs("div", { children: [_jsx(Label, { children: t('tagPicker.title', { count: value.length }) }), value.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('tagPicker.empty') })) : (_jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: value.map((id, idx) => {
|
|
62
|
+
const tag = tagsById.get(id);
|
|
63
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded border px-2 py-1 text-xs", children: [_jsx(Badge, { variant: "outline", className: "font-mono text-[10px]", children: tag?.code ?? id }), _jsx("span", { children: tag ? pickName(tag.name, tag.code) : t('common.unknown') }), _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "ml-1 h-6 w-6 p-0", disabled: idx === 0, onClick: () => move(id, 'up'), "aria-label": t('tagPicker.moveUp'), children: "\u2191" }), _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-6 w-6 p-0", disabled: idx === value.length - 1, onClick: () => move(id, 'down'), "aria-label": t('tagPicker.moveDown'), children: "\u2193" }), _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-6 px-2", onClick: () => detach(id), children: "\u00D7" })] }, id));
|
|
64
|
+
}) }))] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "tag-picker-search", children: t('tagPicker.add') }), _jsx(Input, { id: "tag-picker-search", value: q, onChange: (event) => setQ(event.target.value), placeholder: t('tagPicker.searchPlaceholder') }), loading ? (_jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: t('tagPicker.loading') })) : candidates.length === 0 ? (_jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: q ? t('tagPicker.noMatches') : t('tagPicker.noCandidates') })) : (_jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: candidates.slice(0, 25).map((tag) => (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => attach(tag.id), children: [_jsx("span", { className: "mr-2 font-mono text-[10px]", children: tag.code }), pickName(tag.name, tag.code)] }, tag.id))) }))] })] }));
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=TagPicker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TagPicker.js","sourceRoot":"","sources":["../../../src/admin/components/TagPicker.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `blog`'s admin surface — seven routes and three sidebar entries (feature 091,
|
|
3
|
+
* Phase 4 batch 16).
|
|
4
|
+
*
|
|
5
|
+
* **This is one of the two modules that close the drain.** `blog` and `cms` are
|
|
6
|
+
* the last owners in `backend/scripts/ledgers/admin-registrations.ts`, so the
|
|
7
|
+
* eighteen routes and seven nav entries they take out of `admin/src/App.tsx`
|
|
8
|
+
* and `admin/src/components/AppShell.tsx` are the last module-owned
|
|
9
|
+
* registrations those two files hold — SC-007.
|
|
10
|
+
*
|
|
11
|
+
* **What it drains: two keys, re-keyed rather than retired, and the difference
|
|
12
|
+
* is the batch's one judgement.** `BlogPostEditor` and `BlogCategoryEditor`
|
|
13
|
+
* each render `cms`' `PageBuilderEditor`; those are the two entries in
|
|
14
|
+
* `backend/scripts/ledgers/cross-module-imports/blog.ts`, and both endpoints of
|
|
15
|
+
* the reach are in this batch. `admin-component-contribution.md` Z1 decides the
|
|
16
|
+
* exit from the **signature** rather than from the schedule, and this batch
|
|
17
|
+
* re-read the file rather than inheriting the shard's answer: the props are
|
|
18
|
+
* `data` in and `onChange` back, so the **consumer** decides that the canvas
|
|
19
|
+
* appears — Z1 question 1, a published component, not a zone contribution. The
|
|
20
|
+
* publication is D-191's `./admin-ui` subpath on `cms`' own package, and Z11
|
|
21
|
+
* keeps the reach counted: a subpath is contract surface iff its emitted module
|
|
22
|
+
* exports no runtime binding, and `dist/admin-ui/index.js` exports a React
|
|
23
|
+
* component. So the coupling has a supported spelling and is not thereby
|
|
24
|
+
* uncounted, and the shard's own retiring condition — D-192's page-builder
|
|
25
|
+
* family growing a home for the whole builder — is unchanged and is not this
|
|
26
|
+
* merge request's.
|
|
27
|
+
*
|
|
28
|
+
* **This entry exports data and nothing else** (R2); every component is a
|
|
29
|
+
* dynamic-import factory (R6).
|
|
30
|
+
*/
|
|
31
|
+
import type { AdminContributions } from '@endora-commerce/admin-kit/contributions';
|
|
32
|
+
export declare const contributions: AdminContributions;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAoCnF,eAAO,MAAM,aAAa,EAAE,kBAiF3B,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code that opens every screen below.
|
|
3
|
+
*
|
|
4
|
+
* `packages/modules/blog/src/backend/routes.admin.ts` gates every `GET` with
|
|
5
|
+
* `blog.read` and every mutation with `blog.write`, and it is the read code
|
|
6
|
+
* that opens a screen — issue #232's rule, and the code all three hand-written
|
|
7
|
+
* sidebar rows carried.
|
|
8
|
+
*
|
|
9
|
+
* **The two `/new` routes take the write code instead**, which is batch 14's
|
|
10
|
+
* `/sales-channels/new` shape and is forced rather than chosen. This module's
|
|
11
|
+
* `new-post` palette action advertises `/blog/posts/new` under `blog.write`;
|
|
12
|
+
* declaring that route on the read code would make the palette advertise a
|
|
13
|
+
* screen the advertised code cannot open — Principle XVI item 2's exact
|
|
14
|
+
* prohibition, and a **regression** rather than a status quo, because the route
|
|
15
|
+
* is ungated in `App.tsx` today and a `blog.write` operator reaches it. A
|
|
16
|
+
* create form's only purpose is a write, so the write code is also the honest
|
|
17
|
+
* answer for what opens it.
|
|
18
|
+
*
|
|
19
|
+
* It costs the other half batch 14 paid: a link whose destination the
|
|
20
|
+
* operator's codes cannot open answers the admin's not-found page, which says
|
|
21
|
+
* nothing about permissions, so the create affordances are gated on the same
|
|
22
|
+
* code — the *New post* and *New category* buttons and the tree's *+ Child*,
|
|
23
|
+
* beside `cms`' three. Neither module gated anything client-side before this;
|
|
24
|
+
* `useAuth().hasPermission` is the kit's, and it is the predicate every other
|
|
25
|
+
* converted module already uses.
|
|
26
|
+
*/
|
|
27
|
+
const READ_PERMISSION = 'blog.read';
|
|
28
|
+
/** The code the two create forms open on, and the one their saves enforce. */
|
|
29
|
+
const WRITE_PERMISSION = 'blog.write';
|
|
30
|
+
/** The module's landing route: the post list. */
|
|
31
|
+
const ROUTE_PATH = '/blog/posts';
|
|
32
|
+
export const contributions = {
|
|
33
|
+
routes: [
|
|
34
|
+
{
|
|
35
|
+
path: ROUTE_PATH,
|
|
36
|
+
component: () => import('./pages/BlogPostListPage.js'),
|
|
37
|
+
requiredPermission: READ_PERMISSION,
|
|
38
|
+
index: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
// Declared before `/blog/posts/:id`, as `App.tsx` declared it. `<Routes>`
|
|
42
|
+
// ranks by specificity rather than by declaration order, so the static
|
|
43
|
+
// segment wins whatever the order; the ordering is kept anyway, so a
|
|
44
|
+
// reader does not have to know that to see why the editor route is safe
|
|
45
|
+
// here.
|
|
46
|
+
path: `${ROUTE_PATH}/new`,
|
|
47
|
+
component: () => import('./pages/BlogPostEditor.js'),
|
|
48
|
+
requiredPermission: WRITE_PERMISSION,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
path: `${ROUTE_PATH}/:id`,
|
|
52
|
+
component: () => import('./pages/BlogPostEditor.js'),
|
|
53
|
+
requiredPermission: READ_PERMISSION,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
path: '/blog/categories',
|
|
57
|
+
component: () => import('./pages/BlogCategoryTreePage.js'),
|
|
58
|
+
requiredPermission: READ_PERMISSION,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
path: '/blog/categories/new',
|
|
62
|
+
component: () => import('./pages/BlogCategoryEditor.js'),
|
|
63
|
+
requiredPermission: WRITE_PERMISSION,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
path: '/blog/categories/:id',
|
|
67
|
+
component: () => import('./pages/BlogCategoryEditor.js'),
|
|
68
|
+
requiredPermission: READ_PERMISSION,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
path: '/blog/tags',
|
|
72
|
+
component: () => import('./pages/BlogTagListPage.js'),
|
|
73
|
+
requiredPermission: READ_PERMISSION,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
nav: [
|
|
77
|
+
{
|
|
78
|
+
to: ROUTE_PATH,
|
|
79
|
+
// Module-relative (R8), resolved in this module's own namespace out of
|
|
80
|
+
// `packages/modules/blog/i18n/`. It was `appShell.nav.blogPosts` in the
|
|
81
|
+
// shared `_i18n` bundle, one of the four shared files a module author had
|
|
82
|
+
// to edit.
|
|
83
|
+
labelKey: 'nav.blogPosts.label',
|
|
84
|
+
// The glyph `AppShell.tsx` rendered by hand, and already on
|
|
85
|
+
// `KnownIconNameSchema`.
|
|
86
|
+
icon: 'Newspaper',
|
|
87
|
+
section: 'content',
|
|
88
|
+
// Sixth, seventh and eighth of eight in the hand-written *Content* table,
|
|
89
|
+
// after the four `/cms/…` rows (100–400) and `megamenu`'s row, which has
|
|
90
|
+
// declared 500 since batch 8 with a comment saying it sits *"after the
|
|
91
|
+
// four `/cms/…` rows and before the three `/blog/…` ones"*. These three
|
|
92
|
+
// weights are what makes that comment true again.
|
|
93
|
+
weight: 600,
|
|
94
|
+
requiredPermission: READ_PERMISSION,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
to: '/blog/categories',
|
|
98
|
+
labelKey: 'nav.blogCategories.label',
|
|
99
|
+
icon: 'Newspaper',
|
|
100
|
+
section: 'content',
|
|
101
|
+
weight: 700,
|
|
102
|
+
requiredPermission: READ_PERMISSION,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
to: '/blog/tags',
|
|
106
|
+
labelKey: 'nav.blogTags.label',
|
|
107
|
+
icon: 'Newspaper',
|
|
108
|
+
section: 'content',
|
|
109
|
+
weight: 800,
|
|
110
|
+
requiredPermission: READ_PERMISSION,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,eAAe,GAAG,WAAW,CAAC;AAEpC,8EAA8E;AAC9E,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAEtC,iDAAiD;AACjD,MAAM,UAAU,GAAG,aAAa,CAAC;AAEjC,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,MAAM,EAAE;QACN;YACE,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,6BAA6B,CAAC;YACtD,kBAAkB,EAAE,eAAe;YACnC,KAAK,EAAE,IAAI;SACZ;QACD;YACE,0EAA0E;YAC1E,uEAAuE;YACvE,qEAAqE;YACrE,wEAAwE;YACxE,QAAQ;YACR,IAAI,EAAE,GAAG,UAAU,MAAM;YACzB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,2BAA2B,CAAC;YACpD,kBAAkB,EAAE,gBAAgB;SACrC;QACD;YACE,IAAI,EAAE,GAAG,UAAU,MAAM;YACzB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,2BAA2B,CAAC;YACpD,kBAAkB,EAAE,eAAe;SACpC;QACD;YACE,IAAI,EAAE,kBAAkB;YACxB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iCAAiC,CAAC;YAC1D,kBAAkB,EAAE,eAAe;SACpC;QACD;YACE,IAAI,EAAE,sBAAsB;YAC5B,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+BAA+B,CAAC;YACxD,kBAAkB,EAAE,gBAAgB;SACrC;QACD;YACE,IAAI,EAAE,sBAAsB;YAC5B,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+BAA+B,CAAC;YACxD,kBAAkB,EAAE,eAAe;SACpC;QACD;YACE,IAAI,EAAE,YAAY;YAClB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,4BAA4B,CAAC;YACrD,kBAAkB,EAAE,eAAe;SACpC;KACF;IACD,GAAG,EAAE;QACH;YACE,EAAE,EAAE,UAAU;YACd,uEAAuE;YACvE,wEAAwE;YACxE,0EAA0E;YAC1E,WAAW;YACX,QAAQ,EAAE,qBAAqB;YAC/B,4DAA4D;YAC5D,yBAAyB;YACzB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,SAAS;YAClB,0EAA0E;YAC1E,yEAAyE;YACzE,uEAAuE;YACvE,wEAAwE;YACxE,kDAAkD;YAClD,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,eAAe;SACpC;QACD;YACE,EAAE,EAAE,kBAAkB;YACtB,QAAQ,EAAE,0BAA0B;YACpC,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,eAAe;SACpC;QACD;YACE,EAAE,EAAE,YAAY;YAChB,QAAQ,EAAE,oBAAoB;YAC9B,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,eAAe;SACpC;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare function BlogCategoryEditor(): ReactNode;
|
|
3
|
+
/**
|
|
4
|
+
* The registry loads a route component through a dynamic-import factory and
|
|
5
|
+
* reads its default export (feature 091, FR-013). The named export stays: it is
|
|
6
|
+
* the spelling this module's own code and its tests use.
|
|
7
|
+
*/
|
|
8
|
+
export default BlogCategoryEditor;
|
|
9
|
+
//# sourceMappingURL=BlogCategoryEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlogCategoryEditor.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/BlogCategoryEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AA6ClF,wBAAgB,kBAAkB,IAAI,SAAS,CAkU9C;AAED;;;;GAIG;AACH,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|
4
|
+
import { Alert, AlertDescription, Button, Card, CardContent, CardHeader, CardTitle, Input, Label, PageHeader } from '@endora-commerce/admin-kit/ui';
|
|
5
|
+
import { AssetFieldPicker, ContentLanguageTabs, ScopePicker } from '@endora-commerce/admin-kit/components';
|
|
6
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
7
|
+
import { PageBuilderEditor } from '@endora-commerce/mod-cms/admin-ui';
|
|
8
|
+
import { blogClient } from '../api/blog-client.js';
|
|
9
|
+
const blankForm = {
|
|
10
|
+
name: '',
|
|
11
|
+
slug: '',
|
|
12
|
+
enabled: true,
|
|
13
|
+
metaTitle: '',
|
|
14
|
+
metaDescription: '',
|
|
15
|
+
metaKeywords: '',
|
|
16
|
+
mainImageAssetId: '',
|
|
17
|
+
};
|
|
18
|
+
function descriptionFor(category, language) {
|
|
19
|
+
if (!category || !language)
|
|
20
|
+
return null;
|
|
21
|
+
const tree = category.description?.languages?.[language];
|
|
22
|
+
if (tree && typeof tree === 'object')
|
|
23
|
+
return tree;
|
|
24
|
+
return { root: { props: {} }, content: [] };
|
|
25
|
+
}
|
|
26
|
+
function pickValue(map, lang) {
|
|
27
|
+
if (!map)
|
|
28
|
+
return '';
|
|
29
|
+
return map[lang] ?? '';
|
|
30
|
+
}
|
|
31
|
+
export function BlogCategoryEditor() {
|
|
32
|
+
const t = useTranslation('blog');
|
|
33
|
+
const { id } = useParams();
|
|
34
|
+
const isNew = !id || id === 'new';
|
|
35
|
+
const [searchParams] = useSearchParams();
|
|
36
|
+
const initialParentId = searchParams.get('parentId');
|
|
37
|
+
const navigate = useNavigate();
|
|
38
|
+
const [category, setCategory] = useState(null);
|
|
39
|
+
const [parentId, setParentId] = useState(initialParentId);
|
|
40
|
+
const [form, setForm] = useState(blankForm);
|
|
41
|
+
const [scope, setScope] = useState({
|
|
42
|
+
salesChannelIds: [],
|
|
43
|
+
languages: ['en-US'],
|
|
44
|
+
});
|
|
45
|
+
const [activeLanguage, setActiveLanguage] = useState('en-US');
|
|
46
|
+
const [draftDescription, setDraftDescription] = useState(null);
|
|
47
|
+
const [error, setError] = useState(null);
|
|
48
|
+
const [info, setInfo] = useState(null);
|
|
49
|
+
const [saving, setSaving] = useState(false);
|
|
50
|
+
const reloadFormFromCategory = useCallback((loaded, language) => {
|
|
51
|
+
const lang = language ?? loaded.languages[0] ?? 'en-US';
|
|
52
|
+
setForm({
|
|
53
|
+
name: pickValue(loaded.name, lang) || pickValue(loaded.name, 'en-US'),
|
|
54
|
+
slug: loaded.slug,
|
|
55
|
+
enabled: loaded.enabled,
|
|
56
|
+
metaTitle: pickValue(loaded.metaTitle, lang),
|
|
57
|
+
metaDescription: pickValue(loaded.metaDescription, lang),
|
|
58
|
+
metaKeywords: pickValue(loaded.metaKeywords, lang),
|
|
59
|
+
mainImageAssetId: loaded.mainImageAssetId ?? '',
|
|
60
|
+
});
|
|
61
|
+
setParentId(loaded.parentId);
|
|
62
|
+
}, []);
|
|
63
|
+
const load = useCallback(async () => {
|
|
64
|
+
if (isNew || !id)
|
|
65
|
+
return;
|
|
66
|
+
setError(null);
|
|
67
|
+
const loaded = await blogClient.getCategory(id);
|
|
68
|
+
setCategory(loaded);
|
|
69
|
+
setScope({
|
|
70
|
+
salesChannelIds: loaded.salesChannelIds,
|
|
71
|
+
languages: loaded.languages,
|
|
72
|
+
});
|
|
73
|
+
const lang = loaded.languages[0] ?? 'en-US';
|
|
74
|
+
setActiveLanguage(lang);
|
|
75
|
+
reloadFormFromCategory(loaded, lang);
|
|
76
|
+
setDraftDescription(descriptionFor(loaded, lang));
|
|
77
|
+
}, [id, isNew, reloadFormFromCategory]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
void load().catch((err) => setError(err instanceof Error ? err.message : String(err)));
|
|
80
|
+
}, [load]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (category && activeLanguage) {
|
|
83
|
+
reloadFormFromCategory(category, activeLanguage);
|
|
84
|
+
setDraftDescription(descriptionFor(category, activeLanguage));
|
|
85
|
+
}
|
|
86
|
+
}, [category, activeLanguage, reloadFormFromCategory]);
|
|
87
|
+
const canSave = useMemo(() => {
|
|
88
|
+
return (form.name.trim().length > 0 &&
|
|
89
|
+
/^(?!tag$)[a-z0-9](?:[a-z0-9-]{0,158}[a-z0-9])?$/.test(form.slug) &&
|
|
90
|
+
scope.salesChannelIds.length > 0 &&
|
|
91
|
+
scope.languages.length > 0);
|
|
92
|
+
}, [form, scope]);
|
|
93
|
+
const onCreate = useCallback(async () => {
|
|
94
|
+
if (!canSave)
|
|
95
|
+
return;
|
|
96
|
+
setSaving(true);
|
|
97
|
+
setError(null);
|
|
98
|
+
setInfo(null);
|
|
99
|
+
const lang = activeLanguage ?? 'en-US';
|
|
100
|
+
try {
|
|
101
|
+
const created = await blogClient.createCategory({
|
|
102
|
+
parentId: parentId ?? null,
|
|
103
|
+
name: { [lang]: form.name },
|
|
104
|
+
slug: form.slug,
|
|
105
|
+
enabled: form.enabled,
|
|
106
|
+
salesChannelIds: scope.salesChannelIds,
|
|
107
|
+
languages: scope.languages,
|
|
108
|
+
mainImageAssetId: form.mainImageAssetId || null,
|
|
109
|
+
...(form.metaTitle ? { metaTitle: { [lang]: form.metaTitle } } : {}),
|
|
110
|
+
...(form.metaDescription ? { metaDescription: { [lang]: form.metaDescription } } : {}),
|
|
111
|
+
...(form.metaKeywords ? { metaKeywords: { [lang]: form.metaKeywords } } : {}),
|
|
112
|
+
});
|
|
113
|
+
navigate(`/blog/categories/${created.id}`);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
setSaving(false);
|
|
120
|
+
}
|
|
121
|
+
}, [activeLanguage, canSave, form, navigate, parentId, scope]);
|
|
122
|
+
const onSaveMetadata = useCallback(async () => {
|
|
123
|
+
if (!category || !canSave)
|
|
124
|
+
return;
|
|
125
|
+
setSaving(true);
|
|
126
|
+
setError(null);
|
|
127
|
+
setInfo(null);
|
|
128
|
+
const lang = activeLanguage ?? 'en-US';
|
|
129
|
+
try {
|
|
130
|
+
const updated = await blogClient.patchCategory(category.id, {
|
|
131
|
+
name: { ...category.name, [lang]: form.name },
|
|
132
|
+
slug: form.slug,
|
|
133
|
+
enabled: form.enabled,
|
|
134
|
+
salesChannelIds: scope.salesChannelIds,
|
|
135
|
+
languages: scope.languages,
|
|
136
|
+
mainImageAssetId: form.mainImageAssetId || null,
|
|
137
|
+
metaTitle: { ...(category.metaTitle ?? {}), [lang]: form.metaTitle },
|
|
138
|
+
metaDescription: {
|
|
139
|
+
...(category.metaDescription ?? {}),
|
|
140
|
+
[lang]: form.metaDescription,
|
|
141
|
+
},
|
|
142
|
+
metaKeywords: {
|
|
143
|
+
...(category.metaKeywords ?? {}),
|
|
144
|
+
[lang]: form.metaKeywords,
|
|
145
|
+
},
|
|
146
|
+
version: category.version,
|
|
147
|
+
});
|
|
148
|
+
setCategory(updated);
|
|
149
|
+
setInfo(t('messages.saved'));
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
setSaving(false);
|
|
156
|
+
}
|
|
157
|
+
}, [activeLanguage, canSave, category, form, scope]);
|
|
158
|
+
const onSaveDescription = useCallback(async () => {
|
|
159
|
+
if (!category || !activeLanguage || !draftDescription)
|
|
160
|
+
return;
|
|
161
|
+
setSaving(true);
|
|
162
|
+
setError(null);
|
|
163
|
+
setInfo(null);
|
|
164
|
+
try {
|
|
165
|
+
const nextLanguages = {
|
|
166
|
+
...(category.description?.languages ?? {}),
|
|
167
|
+
[activeLanguage]: draftDescription,
|
|
168
|
+
};
|
|
169
|
+
const updated = await blogClient.putCategoryDescription(category.id, {
|
|
170
|
+
description: {
|
|
171
|
+
schema_version: category.description?.schema_version ?? 1,
|
|
172
|
+
languages: nextLanguages,
|
|
173
|
+
},
|
|
174
|
+
version: category.version,
|
|
175
|
+
});
|
|
176
|
+
setCategory(updated);
|
|
177
|
+
setInfo(t('messages.descriptionSaved'));
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
setSaving(false);
|
|
184
|
+
}
|
|
185
|
+
}, [activeLanguage, category, draftDescription]);
|
|
186
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(PageHeader, { title: isNew ? t('categoryEditor.title.new') : t('categoryEditor.title.edit'), description: category?.isSystem
|
|
187
|
+
? t('categoryEditor.description.system')
|
|
188
|
+
: t('categoryEditor.description.default'), actions: _jsx(Button, { asChild: true, variant: "outline", children: _jsx(Link, { to: "/blog/categories", children: t('categoryEditor.backToTree') }) }) }), error ? (_jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: error }) })) : null, info ? (_jsx(Alert, { children: _jsx(AlertDescription, { children: info }) })) : null, _jsx(ScopePicker, { value: scope, onChange: setScope }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: t('sections.metadata') }) }), _jsxs(CardContent, { className: "grid gap-4", children: [_jsx(ContentLanguageTabs, { languages: scope.languages, activeLanguage: activeLanguage, onChange: setActiveLanguage }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { htmlFor: "cat-name", children: t('fields.namePerLanguage') }), _jsx(Input, { id: "cat-name", value: form.name, onChange: (event) => setForm((f) => ({ ...f, name: event.target.value })), placeholder: t('categoryEditor.namePlaceholder') })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "cat-slug", children: t('fields.slug') }), _jsx(Input, { id: "cat-slug", value: form.slug, onChange: (event) => setForm((f) => ({ ...f, slug: event.target.value.toLowerCase() })), className: "font-mono", placeholder: t('categoryEditor.slugPlaceholder') })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { htmlFor: "cat-meta-title", children: t('fields.metaTitlePerLanguage') }), _jsx(Input, { id: "cat-meta-title", value: form.metaTitle, onChange: (event) => setForm((f) => ({ ...f, metaTitle: event.target.value })) })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "cat-meta-description", children: t('fields.metaDescriptionPerLanguage') }), _jsx(Input, { id: "cat-meta-description", value: form.metaDescription, onChange: (event) => setForm((f) => ({ ...f, metaDescription: event.target.value })) })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { htmlFor: "cat-meta-keywords", children: t('fields.metaKeywordsPerLanguage') }), _jsx(Input, { id: "cat-meta-keywords", value: form.metaKeywords, onChange: (event) => setForm((f) => ({ ...f, metaKeywords: event.target.value })) })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "cat-image", children: t('fields.mainImageAssetId') }), _jsx(AssetFieldPicker, { id: "cat-image", value: form.mainImageAssetId, onChange: (id) => setForm((f) => ({ ...f, mainImageAssetId: id })), acceptMimePrefix: "image/", allowUpload: true })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { id: "cat-enabled", type: "checkbox", checked: form.enabled, onChange: (event) => setForm((f) => ({ ...f, enabled: event.target.checked })) }), _jsx(Label, { htmlFor: "cat-enabled", children: t('fields.enabledStorefrontVisible') })] }), _jsx("div", { className: "flex justify-end gap-2", children: isNew ? (_jsx(Button, { type: "button", disabled: !canSave || saving, onClick: () => void onCreate(), children: saving ? t('common.saving') : t('common.create') })) : (_jsx(Button, { type: "button", disabled: !canSave || saving, onClick: () => void onSaveMetadata(), children: saving ? t('common.saving') : t('common.saveMetadata') })) })] })] }), !isNew && category && activeLanguage && draftDescription !== null ? (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between gap-2", children: [_jsx(CardTitle, { className: "text-base", children: t('categoryEditor.descriptionTitle', { language: activeLanguage }) }), _jsx(Button, { type: "button", size: "sm", disabled: saving, onClick: () => void onSaveDescription(), children: saving ? t('common.saving') : t('common.saveDescription') })] }), _jsx(CardContent, { children: _jsx(PageBuilderEditor, { data: draftDescription, onChange: setDraftDescription }) })] })) : null] }));
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The registry loads a route component through a dynamic-import factory and
|
|
192
|
+
* reads its default export (feature 091, FR-013). The named export stays: it is
|
|
193
|
+
* the spelling this module's own code and its tests use.
|
|
194
|
+
*/
|
|
195
|
+
export default BlogCategoryEditor;
|
|
196
|
+
//# sourceMappingURL=BlogCategoryEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlogCategoryEditor.js","sourceRoot":"","sources":["../../../src/admin/pages/BlogCategoryEditor.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare function BlogCategoryTreePage(): ReactNode;
|
|
3
|
+
/**
|
|
4
|
+
* The registry loads a route component through a dynamic-import factory and
|
|
5
|
+
* reads its default export (feature 091, FR-013). The named export stays: it is
|
|
6
|
+
* the spelling this module's own code and its tests use.
|
|
7
|
+
*/
|
|
8
|
+
export default BlogCategoryTreePage;
|
|
9
|
+
//# sourceMappingURL=BlogCategoryTreePage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlogCategoryTreePage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/BlogCategoryTreePage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AA8BzE,wBAAgB,oBAAoB,IAAI,SAAS,CA0JhD;AAED;;;;GAIG;AACH,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { useNavigate } from 'react-router-dom';
|
|
4
|
+
import { Alert, AlertDescription, Button, Card, CardContent, CardHeader, CardTitle, PageHeader } from '@endora-commerce/admin-kit/ui';
|
|
5
|
+
import { useAuth } from '@endora-commerce/admin-kit/lib';
|
|
6
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
7
|
+
import { blogClient } from '../api/blog-client.js';
|
|
8
|
+
import { CategoryTreeNode } from '../components/CategoryTreeNode.js';
|
|
9
|
+
function flatten(tree) {
|
|
10
|
+
const out = [];
|
|
11
|
+
const walk = (nodes, parentId, depth) => {
|
|
12
|
+
const total = nodes.length;
|
|
13
|
+
nodes.forEach((node, position) => {
|
|
14
|
+
out.push({ node, parentId, position, depth, siblingsTotal: total });
|
|
15
|
+
if (node.children.length > 0)
|
|
16
|
+
walk(node.children, node.id, depth + 1);
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
walk(tree, null, 0);
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
export function BlogCategoryTreePage() {
|
|
23
|
+
const t = useTranslation('blog');
|
|
24
|
+
const { hasPermission } = useAuth();
|
|
25
|
+
// The create route this screen links to takes the **write** code (feature
|
|
26
|
+
// 091, batch 16), so the button is gated on the same one: a link whose
|
|
27
|
+
// destination the operator's codes cannot open answers the admin's not-found
|
|
28
|
+
// page, which says nothing about permissions at all. That is batch 14's
|
|
29
|
+
// `/sales-channels/new` shape, applied to the five create entry points these
|
|
30
|
+
// two modules have.
|
|
31
|
+
const canWrite = hasPermission('blog.write');
|
|
32
|
+
const navigate = useNavigate();
|
|
33
|
+
const [tree, setTree] = useState([]);
|
|
34
|
+
const [loading, setLoading] = useState(true);
|
|
35
|
+
const [error, setError] = useState(null);
|
|
36
|
+
const [working, setWorking] = useState(false);
|
|
37
|
+
const load = useCallback(async () => {
|
|
38
|
+
setLoading(true);
|
|
39
|
+
setError(null);
|
|
40
|
+
try {
|
|
41
|
+
const out = await blogClient.getCategoryTree();
|
|
42
|
+
setTree(out.tree);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
setLoading(false);
|
|
49
|
+
}
|
|
50
|
+
}, []);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
void load();
|
|
53
|
+
}, [load]);
|
|
54
|
+
const onMove = useCallback(async (target, direction, siblings) => {
|
|
55
|
+
const targetIdx = siblings.findIndex((s) => s.node.id === target.id);
|
|
56
|
+
if (targetIdx < 0)
|
|
57
|
+
return;
|
|
58
|
+
const swapIdx = direction === 'up' ? targetIdx - 1 : targetIdx + 1;
|
|
59
|
+
if (swapIdx < 0 || swapIdx >= siblings.length)
|
|
60
|
+
return;
|
|
61
|
+
// Two-move atomic swap inside the parent slot.
|
|
62
|
+
const moves = [
|
|
63
|
+
{
|
|
64
|
+
id: target.id,
|
|
65
|
+
parentId: siblings[targetIdx].parentId,
|
|
66
|
+
position: siblings[swapIdx].position,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: siblings[swapIdx].node.id,
|
|
70
|
+
parentId: siblings[swapIdx].parentId,
|
|
71
|
+
position: siblings[targetIdx].position,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
setWorking(true);
|
|
75
|
+
setError(null);
|
|
76
|
+
try {
|
|
77
|
+
const out = await blogClient.applyCategoryTreeMoves(moves);
|
|
78
|
+
setTree(out.tree);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
setWorking(false);
|
|
85
|
+
}
|
|
86
|
+
}, []);
|
|
87
|
+
const onDelete = useCallback(async (node) => {
|
|
88
|
+
if (!window.confirm(t('categoryTree.deleteConfirm', { slug: node.slug })))
|
|
89
|
+
return;
|
|
90
|
+
setWorking(true);
|
|
91
|
+
setError(null);
|
|
92
|
+
try {
|
|
93
|
+
await blogClient.deleteCategory(node.id, node.version);
|
|
94
|
+
await load();
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
setWorking(false);
|
|
101
|
+
}
|
|
102
|
+
}, [load]);
|
|
103
|
+
const onAddChild = useCallback((parentId) => {
|
|
104
|
+
navigate(`/blog/categories/new?parentId=${encodeURIComponent(parentId)}`);
|
|
105
|
+
}, [navigate]);
|
|
106
|
+
const flat = flatten(tree);
|
|
107
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(PageHeader, { title: t('categoryTree.title'), description: t('categoryTree.description'), actions: canWrite ? (_jsx(Button, { asChild: true, children: _jsx("a", { href: "/blog/categories/new", children: t('categoryTree.newCategory') }) })) : null }), error ? (_jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: error }) })) : null, _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: t('categoryTree.cardTitle') }) }), _jsx(CardContent, { children: loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('common.loading') })) : flat.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('categoryTree.empty') })) : (_jsx("div", { className: "rounded border", children: flat.map((entry) => {
|
|
108
|
+
// Build the same-parent siblings list once per render — needed
|
|
109
|
+
// by the up/down handlers to compute the swap target.
|
|
110
|
+
const siblings = flat.filter((s) => (s.parentId ?? null) === (entry.parentId ?? null) &&
|
|
111
|
+
s.depth === entry.depth);
|
|
112
|
+
const idx = siblings.findIndex((s) => s.node.id === entry.node.id);
|
|
113
|
+
return (_jsx(CategoryTreeNode, { node: entry.node, depth: entry.depth, canMoveUp: idx > 0 && !working, canMoveDown: idx < siblings.length - 1 && !working, onMoveUp: () => void onMove(entry.node, 'up', siblings), onMoveDown: () => void onMove(entry.node, 'down', siblings), onAddChild: onAddChild, canCreate: canWrite, onDelete: () => void onDelete(entry.node) }, entry.node.id));
|
|
114
|
+
}) })) })] })] }));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The registry loads a route component through a dynamic-import factory and
|
|
118
|
+
* reads its default export (feature 091, FR-013). The named export stays: it is
|
|
119
|
+
* the spelling this module's own code and its tests use.
|
|
120
|
+
*/
|
|
121
|
+
export default BlogCategoryTreePage;
|
|
122
|
+
//# sourceMappingURL=BlogCategoryTreePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlogCategoryTreePage.js","sourceRoot":"","sources":["../../../src/admin/pages/BlogCategoryTreePage.tsx"],"names":[],"mappings":"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"}
|