litecms 0.1.1 → 0.2.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/README.md +746 -7
- package/dist/admin/CmsAdminLanding.d.ts +89 -0
- package/dist/admin/CmsAdminLanding.d.ts.map +1 -0
- package/dist/admin/CmsAdminLayout.d.ts +26 -3
- package/dist/admin/CmsAdminLayout.d.ts.map +1 -1
- package/dist/admin/CmsAdminPage.d.ts.map +1 -1
- package/dist/admin/CmsBlogAdmin.d.ts +37 -0
- package/dist/admin/CmsBlogAdmin.d.ts.map +1 -0
- package/dist/admin/config.d.ts +32 -0
- package/dist/admin/config.d.ts.map +1 -1
- package/dist/admin/config.js +16 -0
- package/dist/admin/exports.d.ts +5 -2
- package/dist/admin/exports.d.ts.map +1 -1
- package/dist/admin/exports.js +1467 -30
- package/dist/admin/language.d.ts +53 -0
- package/dist/admin/language.d.ts.map +1 -0
- package/dist/components/CmsAutoForm.d.ts.map +1 -1
- package/dist/components/CmsForm.d.ts.map +1 -1
- package/dist/components/CmsImageField.d.ts +2 -1
- package/dist/components/CmsImageField.d.ts.map +1 -1
- package/dist/components/CmsImagePickerModal.d.ts +21 -0
- package/dist/components/CmsImagePickerModal.d.ts.map +1 -0
- package/dist/components/CmsSimpleForm.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +51 -190
- package/dist/index-c9btr14k.js +4422 -0
- package/dist/index-szreq4v9.js +12 -0
- package/dist/index-wmd953zf.js +11423 -0
- package/dist/index.js +6 -2
- package/dist/schema/index.js +2 -0
- package/dist/server/index.d.ts +301 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2585 -1
- package/dist/storage/index.js +2 -0
- package/package.json +13 -3
- package/dist/domain/index.d.ts +0 -1
- package/dist/domain/index.d.ts.map +0 -1
- package/dist/stores/index.d.ts +0 -1
- package/dist/stores/index.d.ts.map +0 -1
package/dist/admin/exports.js
CHANGED
|
@@ -6,12 +6,20 @@ import {
|
|
|
6
6
|
CmsCheckbox,
|
|
7
7
|
CmsField,
|
|
8
8
|
CmsImageField,
|
|
9
|
+
CmsImagePickerModal,
|
|
10
|
+
CmsLanguageProvider,
|
|
11
|
+
CmsLanguageSelector,
|
|
9
12
|
FormProvider,
|
|
13
|
+
detectBrowserLanguage,
|
|
10
14
|
flattenValue,
|
|
11
15
|
groupFields,
|
|
12
16
|
groupPages,
|
|
17
|
+
translate,
|
|
18
|
+
useCmsLanguage,
|
|
19
|
+
useCmsLanguageOptional,
|
|
13
20
|
useForm
|
|
14
|
-
} from "../index-
|
|
21
|
+
} from "../index-c9btr14k.js";
|
|
22
|
+
import"../index-wmd953zf.js";
|
|
15
23
|
import {
|
|
16
24
|
createCmsConfig,
|
|
17
25
|
definePage,
|
|
@@ -19,8 +27,10 @@ import {
|
|
|
19
27
|
getNavPages,
|
|
20
28
|
getPageBySlug
|
|
21
29
|
} from "../index-8zcd33mx.js";
|
|
30
|
+
import"../index-szreq4v9.js";
|
|
22
31
|
|
|
23
32
|
// src/admin/CmsAdminLayout.tsx
|
|
33
|
+
import * as React from "react";
|
|
24
34
|
import { usePathname } from "next/navigation";
|
|
25
35
|
|
|
26
36
|
// src/components/CmsNavSection.tsx
|
|
@@ -50,13 +60,14 @@ function NavSection({
|
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
// src/admin/CmsAdminLayout.tsx
|
|
53
|
-
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
54
|
-
function
|
|
63
|
+
import { jsxDEV as jsxDEV2, Fragment } from "react/jsx-dev-runtime";
|
|
64
|
+
function CmsAdminLayoutInner({
|
|
55
65
|
adminTitle = "Content",
|
|
56
66
|
siteName,
|
|
57
67
|
basePath = "/admin",
|
|
58
68
|
publicSiteUrl,
|
|
59
|
-
|
|
69
|
+
modules,
|
|
70
|
+
pages = [],
|
|
60
71
|
currentSlug: currentSlugProp,
|
|
61
72
|
onLogout,
|
|
62
73
|
children
|
|
@@ -65,6 +76,38 @@ function CmsAdminLayout({
|
|
|
65
76
|
const detectedSlug = pathname?.replace(new RegExp(`^${basePath}/?`), "").split("/")[0];
|
|
66
77
|
const currentSlug = currentSlugProp ?? (detectedSlug || undefined);
|
|
67
78
|
const groupedPages = groupPages(pages);
|
|
79
|
+
const currentModuleId = React.useMemo(() => {
|
|
80
|
+
if (!modules?.length)
|
|
81
|
+
return;
|
|
82
|
+
for (const m of modules) {
|
|
83
|
+
const modulePath = m.href.replace(basePath, "").replace(/^\//, "");
|
|
84
|
+
if (modulePath && modulePath === currentSlug) {
|
|
85
|
+
return m.id;
|
|
86
|
+
}
|
|
87
|
+
if (modulePath && currentSlug?.startsWith(modulePath + "/")) {
|
|
88
|
+
return m.id;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const contentModule = modules.find((m) => m.id === "content");
|
|
92
|
+
if (contentModule) {
|
|
93
|
+
const contentPath = contentModule.href.replace(basePath, "").replace(/^\//, "");
|
|
94
|
+
if (!currentSlug || contentPath === "" && !modules.some((m) => {
|
|
95
|
+
const mp = m.href.replace(basePath, "").replace(/^\//, "");
|
|
96
|
+
return mp && (mp === currentSlug || currentSlug?.startsWith(mp + "/"));
|
|
97
|
+
})) {
|
|
98
|
+
return "content";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}, [modules, basePath, currentSlug]);
|
|
103
|
+
const langContext = useCmsLanguageOptional();
|
|
104
|
+
const t = langContext?.t ?? ((key) => {
|
|
105
|
+
const fallback = {
|
|
106
|
+
viewSite: "View site",
|
|
107
|
+
signOut: "Sign out"
|
|
108
|
+
};
|
|
109
|
+
return fallback[key] ?? key;
|
|
110
|
+
});
|
|
68
111
|
return /* @__PURE__ */ jsxDEV2("div", {
|
|
69
112
|
className: "min-h-screen font-sans antialiased bg-neutral-50 text-neutral-900 selection:bg-neutral-200 selection:text-neutral-900 [--cms-accent:#171717] [--cms-accent-subtle:rgba(23,23,23,0.08)]",
|
|
70
113
|
children: [
|
|
@@ -81,15 +124,45 @@ function CmsAdminLayout({
|
|
|
81
124
|
}, undefined, false, undefined, this),
|
|
82
125
|
/* @__PURE__ */ jsxDEV2("nav", {
|
|
83
126
|
className: "flex-1 overflow-y-auto px-6 py-8 space-y-8",
|
|
84
|
-
children:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
127
|
+
children: [
|
|
128
|
+
modules && modules.length > 0 && /* @__PURE__ */ jsxDEV2("div", {
|
|
129
|
+
className: "space-y-2",
|
|
130
|
+
children: modules.map((module) => {
|
|
131
|
+
const isActive = currentModuleId === module.id;
|
|
132
|
+
return /* @__PURE__ */ jsxDEV2("a", {
|
|
133
|
+
href: module.href,
|
|
134
|
+
className: `flex items-center gap-3 py-2 text-sm transition-colors ${isActive ? "text-neutral-900 font-medium" : "text-neutral-500 hover:text-neutral-900"}`,
|
|
135
|
+
children: [
|
|
136
|
+
module.icon && /* @__PURE__ */ jsxDEV2("span", {
|
|
137
|
+
className: isActive ? "text-neutral-900" : "text-neutral-400",
|
|
138
|
+
children: module.icon
|
|
139
|
+
}, undefined, false, undefined, this),
|
|
140
|
+
module.title
|
|
141
|
+
]
|
|
142
|
+
}, module.id, true, undefined, this);
|
|
143
|
+
})
|
|
144
|
+
}, undefined, false, undefined, this),
|
|
145
|
+
(!modules || modules.length === 0) && groupedPages.length > 0 && /* @__PURE__ */ jsxDEV2(Fragment, {
|
|
146
|
+
children: groupedPages.map((group) => /* @__PURE__ */ jsxDEV2(NavSection, {
|
|
147
|
+
group,
|
|
148
|
+
basePath,
|
|
149
|
+
currentSlug
|
|
150
|
+
}, group.name ?? "__default", false, undefined, this))
|
|
151
|
+
}, undefined, false, undefined, this),
|
|
152
|
+
modules && modules.length > 0 && currentModuleId === "content" && groupedPages.length > 0 && /* @__PURE__ */ jsxDEV2("div", {
|
|
153
|
+
className: "pt-4 border-t border-neutral-100",
|
|
154
|
+
children: groupedPages.map((group) => /* @__PURE__ */ jsxDEV2(NavSection, {
|
|
155
|
+
group,
|
|
156
|
+
basePath,
|
|
157
|
+
currentSlug
|
|
158
|
+
}, group.name ?? "__default", false, undefined, this))
|
|
159
|
+
}, undefined, false, undefined, this)
|
|
160
|
+
]
|
|
161
|
+
}, undefined, true, undefined, this),
|
|
90
162
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
91
163
|
className: "border-t border-neutral-200 px-6 py-4 space-y-3",
|
|
92
164
|
children: [
|
|
165
|
+
langContext && /* @__PURE__ */ jsxDEV2(CmsLanguageSelector, {}, undefined, false, undefined, this),
|
|
93
166
|
publicSiteUrl && /* @__PURE__ */ jsxDEV2("a", {
|
|
94
167
|
href: publicSiteUrl,
|
|
95
168
|
className: "flex items-center gap-2 text-xs tracking-wide text-neutral-400 hover:text-neutral-900 transition-colors",
|
|
@@ -97,7 +170,7 @@ function CmsAdminLayout({
|
|
|
97
170
|
rel: "noopener noreferrer",
|
|
98
171
|
children: [
|
|
99
172
|
/* @__PURE__ */ jsxDEV2("span", {
|
|
100
|
-
children: "
|
|
173
|
+
children: t("viewSite")
|
|
101
174
|
}, undefined, false, undefined, this),
|
|
102
175
|
/* @__PURE__ */ jsxDEV2("svg", {
|
|
103
176
|
className: "h-3 w-3",
|
|
@@ -116,7 +189,7 @@ function CmsAdminLayout({
|
|
|
116
189
|
onLogout && /* @__PURE__ */ jsxDEV2("button", {
|
|
117
190
|
onClick: onLogout,
|
|
118
191
|
className: "text-xs tracking-wide text-neutral-400 hover:text-neutral-900 transition-colors cursor-pointer",
|
|
119
|
-
children: "
|
|
192
|
+
children: t("signOut")
|
|
120
193
|
}, undefined, false, undefined, this)
|
|
121
194
|
]
|
|
122
195
|
}, undefined, true, undefined, this)
|
|
@@ -132,7 +205,11 @@ function CmsAdminLayout({
|
|
|
132
205
|
}, undefined, false, undefined, this),
|
|
133
206
|
/* @__PURE__ */ jsxDEV2("nav", {
|
|
134
207
|
className: "flex gap-4 overflow-x-auto px-4 py-3 bg-white border-b border-neutral-200 md:hidden",
|
|
135
|
-
children:
|
|
208
|
+
children: modules && modules.length > 0 ? modules.map((module) => /* @__PURE__ */ jsxDEV2("a", {
|
|
209
|
+
href: module.href,
|
|
210
|
+
className: `shrink-0 text-sm transition-colors ${currentModuleId === module.id ? "text-neutral-900 border-b border-neutral-900 pb-1" : "text-neutral-400 hover:text-neutral-900"}`,
|
|
211
|
+
children: module.title
|
|
212
|
+
}, module.id, false, undefined, this)) : pages.map((page) => /* @__PURE__ */ jsxDEV2("a", {
|
|
136
213
|
href: `${basePath}/${page.slug}`,
|
|
137
214
|
className: `shrink-0 text-sm transition-colors ${currentSlug === page.slug ? "text-neutral-900 border-b border-neutral-900 pb-1" : "text-neutral-400 hover:text-neutral-900"}`,
|
|
138
215
|
children: page.title
|
|
@@ -141,35 +218,59 @@ function CmsAdminLayout({
|
|
|
141
218
|
/* @__PURE__ */ jsxDEV2("main", {
|
|
142
219
|
className: "md:ml-56 min-h-screen bg-white",
|
|
143
220
|
children: /* @__PURE__ */ jsxDEV2("div", {
|
|
144
|
-
className: "mx-auto",
|
|
221
|
+
className: "mx-auto h-full",
|
|
145
222
|
children
|
|
146
223
|
}, undefined, false, undefined, this)
|
|
147
224
|
}, undefined, false, undefined, this)
|
|
148
225
|
]
|
|
149
226
|
}, undefined, true, undefined, this);
|
|
150
227
|
}
|
|
228
|
+
function CmsAdminLayout({
|
|
229
|
+
languageEndpoint,
|
|
230
|
+
...props
|
|
231
|
+
}) {
|
|
232
|
+
return /* @__PURE__ */ jsxDEV2(CmsLanguageProvider, {
|
|
233
|
+
languageEndpoint,
|
|
234
|
+
children: /* @__PURE__ */ jsxDEV2(CmsAdminLayoutInner, {
|
|
235
|
+
...props
|
|
236
|
+
}, undefined, false, undefined, this)
|
|
237
|
+
}, undefined, false, undefined, this);
|
|
238
|
+
}
|
|
151
239
|
// src/components/CmsSimpleForm.tsx
|
|
152
|
-
import * as
|
|
240
|
+
import * as React2 from "react";
|
|
153
241
|
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
154
242
|
function CmsSimpleForm({
|
|
155
243
|
fields,
|
|
156
244
|
action,
|
|
157
245
|
values = {},
|
|
158
246
|
styles = {},
|
|
159
|
-
submitText
|
|
160
|
-
submitPendingText
|
|
161
|
-
successMessage
|
|
247
|
+
submitText,
|
|
248
|
+
submitPendingText,
|
|
249
|
+
successMessage,
|
|
162
250
|
storage
|
|
163
251
|
}) {
|
|
164
|
-
const [state, setState] =
|
|
252
|
+
const [state, setState] = React2.useState({
|
|
165
253
|
success: false
|
|
166
254
|
});
|
|
167
|
-
const [isPending, setIsPending] =
|
|
168
|
-
const [showSuccess, setShowSuccess] =
|
|
255
|
+
const [isPending, setIsPending] = React2.useState(false);
|
|
256
|
+
const [showSuccess, setShowSuccess] = React2.useState(false);
|
|
257
|
+
const langContext = useCmsLanguageOptional();
|
|
258
|
+
const t = langContext?.t ?? ((key) => {
|
|
259
|
+
const fallback = {
|
|
260
|
+
save: "Save",
|
|
261
|
+
saving: "Saving...",
|
|
262
|
+
reset: "Reset",
|
|
263
|
+
changesSaved: "Changes saved"
|
|
264
|
+
};
|
|
265
|
+
return fallback[key] ?? key;
|
|
266
|
+
});
|
|
267
|
+
const resolvedSubmitText = submitText ?? t("save");
|
|
268
|
+
const resolvedSubmitPendingText = submitPendingText ?? t("saving");
|
|
269
|
+
const resolvedSuccessMessage = successMessage ?? t("changesSaved");
|
|
169
270
|
const form = useForm({
|
|
170
271
|
defaultValues: values
|
|
171
272
|
});
|
|
172
|
-
const hiddenFieldEntries =
|
|
273
|
+
const hiddenFieldEntries = React2.useMemo(() => {
|
|
173
274
|
const editableFieldNames = new Set(fields.map((f) => f.name));
|
|
174
275
|
const entries = [];
|
|
175
276
|
for (const [key, value] of Object.entries(values)) {
|
|
@@ -179,7 +280,7 @@ function CmsSimpleForm({
|
|
|
179
280
|
}
|
|
180
281
|
return entries;
|
|
181
282
|
}, [fields, values]);
|
|
182
|
-
|
|
283
|
+
React2.useEffect(() => {
|
|
183
284
|
if (state.success) {
|
|
184
285
|
setShowSuccess(true);
|
|
185
286
|
const timer = setTimeout(() => setShowSuccess(false), 4000);
|
|
@@ -260,7 +361,7 @@ function CmsSimpleForm({
|
|
|
260
361
|
}, undefined, false, undefined, this),
|
|
261
362
|
showSuccess && /* @__PURE__ */ jsxDEV3("div", {
|
|
262
363
|
className: "mt-12 py-4 px-5 border border-green-200 bg-green-50 text-green-900 text-sm",
|
|
263
|
-
children:
|
|
364
|
+
children: resolvedSuccessMessage
|
|
264
365
|
}, undefined, false, undefined, this),
|
|
265
366
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
266
367
|
className: "mt-16 pt-8 border-t border-neutral-200 flex items-center justify-between",
|
|
@@ -270,7 +371,7 @@ function CmsSimpleForm({
|
|
|
270
371
|
className: "text-sm text-neutral-400 hover:text-neutral-900 transition-colors cursor-pointer",
|
|
271
372
|
onClick: () => form.reset(),
|
|
272
373
|
disabled: isPending,
|
|
273
|
-
children: "
|
|
374
|
+
children: t("reset")
|
|
274
375
|
}, undefined, false, undefined, this),
|
|
275
376
|
/* @__PURE__ */ jsxDEV3("button", {
|
|
276
377
|
type: "submit",
|
|
@@ -287,7 +388,7 @@ function CmsSimpleForm({
|
|
|
287
388
|
d: "M21 12a9 9 0 1 1-6.219-8.56"
|
|
288
389
|
}, undefined, false, undefined, this)
|
|
289
390
|
}, undefined, false, undefined, this),
|
|
290
|
-
isPending ?
|
|
391
|
+
isPending ? resolvedSubmitPendingText : resolvedSubmitText,
|
|
291
392
|
!isPending && /* @__PURE__ */ jsxDEV3("span", {
|
|
292
393
|
children: "→"
|
|
293
394
|
}, undefined, false, undefined, this)
|
|
@@ -430,23 +531,1359 @@ function CmsAdminPage({
|
|
|
430
531
|
action,
|
|
431
532
|
values,
|
|
432
533
|
styles,
|
|
433
|
-
successMessage
|
|
434
|
-
submitText
|
|
435
|
-
submitPendingText: "Saving...",
|
|
534
|
+
successMessage,
|
|
535
|
+
submitText,
|
|
436
536
|
storage
|
|
437
537
|
}, undefined, false, undefined, this)
|
|
438
538
|
}, undefined, false, undefined, this)
|
|
439
539
|
]
|
|
440
540
|
}, undefined, true, undefined, this);
|
|
441
541
|
}
|
|
542
|
+
// src/admin/CmsAdminLanding.tsx
|
|
543
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
544
|
+
function EmptyState({ t }) {
|
|
545
|
+
return /* @__PURE__ */ jsxDEV5("div", {
|
|
546
|
+
className: "py-16 px-8 text-center flex flex-col items-center",
|
|
547
|
+
children: [
|
|
548
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
549
|
+
className: "w-12 h-12 rounded-lg flex items-center justify-center mb-4 bg-neutral-100",
|
|
550
|
+
children: /* @__PURE__ */ jsxDEV5("svg", {
|
|
551
|
+
className: "w-6 h-6 text-neutral-400",
|
|
552
|
+
viewBox: "0 0 24 24",
|
|
553
|
+
fill: "none",
|
|
554
|
+
stroke: "currentColor",
|
|
555
|
+
strokeWidth: "1.5",
|
|
556
|
+
children: /* @__PURE__ */ jsxDEV5("path", {
|
|
557
|
+
strokeLinecap: "round",
|
|
558
|
+
strokeLinejoin: "round",
|
|
559
|
+
d: "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
|
560
|
+
}, undefined, false, undefined, this)
|
|
561
|
+
}, undefined, false, undefined, this)
|
|
562
|
+
}, undefined, false, undefined, this),
|
|
563
|
+
/* @__PURE__ */ jsxDEV5("h2", {
|
|
564
|
+
className: "text-base font-medium mb-1 text-neutral-900",
|
|
565
|
+
children: t("cmsNotConfigured")
|
|
566
|
+
}, undefined, false, undefined, this),
|
|
567
|
+
/* @__PURE__ */ jsxDEV5("p", {
|
|
568
|
+
className: "text-sm max-w-sm leading-relaxed text-neutral-500",
|
|
569
|
+
children: t("cmsNotConfiguredDesc")
|
|
570
|
+
}, undefined, false, undefined, this)
|
|
571
|
+
]
|
|
572
|
+
}, undefined, true, undefined, this);
|
|
573
|
+
}
|
|
574
|
+
var CmsModuleIcons = {
|
|
575
|
+
pages: /* @__PURE__ */ jsxDEV5("svg", {
|
|
576
|
+
className: "w-5 h-5",
|
|
577
|
+
viewBox: "0 0 24 24",
|
|
578
|
+
fill: "none",
|
|
579
|
+
stroke: "currentColor",
|
|
580
|
+
strokeWidth: "1.5",
|
|
581
|
+
children: /* @__PURE__ */ jsxDEV5("path", {
|
|
582
|
+
strokeLinecap: "round",
|
|
583
|
+
strokeLinejoin: "round",
|
|
584
|
+
d: "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
|
585
|
+
}, undefined, false, undefined, this)
|
|
586
|
+
}, undefined, false, undefined, this),
|
|
587
|
+
blog: /* @__PURE__ */ jsxDEV5("svg", {
|
|
588
|
+
className: "w-5 h-5",
|
|
589
|
+
viewBox: "0 0 24 24",
|
|
590
|
+
fill: "none",
|
|
591
|
+
stroke: "currentColor",
|
|
592
|
+
strokeWidth: "1.5",
|
|
593
|
+
children: /* @__PURE__ */ jsxDEV5("path", {
|
|
594
|
+
strokeLinecap: "round",
|
|
595
|
+
strokeLinejoin: "round",
|
|
596
|
+
d: "M12 7.5h1.5m-1.5 3h1.5m-7.5 3h7.5m-7.5 3h7.5m3-9h3.375c.621 0 1.125.504 1.125 1.125V18a2.25 2.25 0 01-2.25 2.25M16.5 7.5V18a2.25 2.25 0 002.25 2.25M16.5 7.5V4.875c0-.621-.504-1.125-1.125-1.125H4.125C3.504 3.75 3 4.254 3 4.875V18a2.25 2.25 0 002.25 2.25h13.5M6 7.5h3v3H6v-3z"
|
|
597
|
+
}, undefined, false, undefined, this)
|
|
598
|
+
}, undefined, false, undefined, this),
|
|
599
|
+
email: /* @__PURE__ */ jsxDEV5("svg", {
|
|
600
|
+
className: "w-5 h-5",
|
|
601
|
+
viewBox: "0 0 24 24",
|
|
602
|
+
fill: "none",
|
|
603
|
+
stroke: "currentColor",
|
|
604
|
+
strokeWidth: "1.5",
|
|
605
|
+
children: /* @__PURE__ */ jsxDEV5("path", {
|
|
606
|
+
strokeLinecap: "round",
|
|
607
|
+
strokeLinejoin: "round",
|
|
608
|
+
d: "M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"
|
|
609
|
+
}, undefined, false, undefined, this)
|
|
610
|
+
}, undefined, false, undefined, this),
|
|
611
|
+
analytics: /* @__PURE__ */ jsxDEV5("svg", {
|
|
612
|
+
className: "w-5 h-5",
|
|
613
|
+
viewBox: "0 0 24 24",
|
|
614
|
+
fill: "none",
|
|
615
|
+
stroke: "currentColor",
|
|
616
|
+
strokeWidth: "1.5",
|
|
617
|
+
children: /* @__PURE__ */ jsxDEV5("path", {
|
|
618
|
+
strokeLinecap: "round",
|
|
619
|
+
strokeLinejoin: "round",
|
|
620
|
+
d: "M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
|
|
621
|
+
}, undefined, false, undefined, this)
|
|
622
|
+
}, undefined, false, undefined, this)
|
|
623
|
+
};
|
|
624
|
+
function ModuleCard({
|
|
625
|
+
title,
|
|
626
|
+
description,
|
|
627
|
+
icon,
|
|
628
|
+
href,
|
|
629
|
+
disabled = false,
|
|
630
|
+
badge
|
|
631
|
+
}) {
|
|
632
|
+
const content = /* @__PURE__ */ jsxDEV5("div", {
|
|
633
|
+
className: "flex items-start gap-4",
|
|
634
|
+
children: [
|
|
635
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
636
|
+
className: `w-10 h-10 flex items-center justify-center shrink-0 transition-colors ${disabled ? "bg-neutral-50 text-neutral-300" : "bg-neutral-100 text-neutral-500 group-hover:bg-neutral-200"}`,
|
|
637
|
+
children: icon
|
|
638
|
+
}, undefined, false, undefined, this),
|
|
639
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
640
|
+
className: "flex-1 min-w-0",
|
|
641
|
+
children: [
|
|
642
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
643
|
+
className: "flex items-center gap-2",
|
|
644
|
+
children: [
|
|
645
|
+
/* @__PURE__ */ jsxDEV5("h3", {
|
|
646
|
+
className: `text-base font-medium transition-colors ${disabled ? "text-neutral-400" : "text-neutral-900 group-hover:text-neutral-700"}`,
|
|
647
|
+
children: title
|
|
648
|
+
}, undefined, false, undefined, this),
|
|
649
|
+
badge && /* @__PURE__ */ jsxDEV5("span", {
|
|
650
|
+
className: "px-1.5 py-0.5 text-xs rounded bg-neutral-100 text-neutral-400",
|
|
651
|
+
children: badge
|
|
652
|
+
}, undefined, false, undefined, this)
|
|
653
|
+
]
|
|
654
|
+
}, undefined, true, undefined, this),
|
|
655
|
+
/* @__PURE__ */ jsxDEV5("p", {
|
|
656
|
+
className: `mt-0.5 text-sm ${disabled ? "text-neutral-300" : "text-neutral-500"}`,
|
|
657
|
+
children: description
|
|
658
|
+
}, undefined, false, undefined, this)
|
|
659
|
+
]
|
|
660
|
+
}, undefined, true, undefined, this),
|
|
661
|
+
!disabled && /* @__PURE__ */ jsxDEV5("svg", {
|
|
662
|
+
className: "w-5 h-5 text-neutral-300 group-hover:text-neutral-500 group-hover:translate-x-0.5 transition-all shrink-0",
|
|
663
|
+
viewBox: "0 0 24 24",
|
|
664
|
+
fill: "none",
|
|
665
|
+
stroke: "currentColor",
|
|
666
|
+
strokeWidth: "1.5",
|
|
667
|
+
children: /* @__PURE__ */ jsxDEV5("path", {
|
|
668
|
+
strokeLinecap: "round",
|
|
669
|
+
strokeLinejoin: "round",
|
|
670
|
+
d: "M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
671
|
+
}, undefined, false, undefined, this)
|
|
672
|
+
}, undefined, false, undefined, this)
|
|
673
|
+
]
|
|
674
|
+
}, undefined, true, undefined, this);
|
|
675
|
+
if (disabled) {
|
|
676
|
+
return /* @__PURE__ */ jsxDEV5("div", {
|
|
677
|
+
className: "block p-5 bg-neutral-50 border border-neutral-100 cursor-not-allowed",
|
|
678
|
+
children: content
|
|
679
|
+
}, undefined, false, undefined, this);
|
|
680
|
+
}
|
|
681
|
+
return /* @__PURE__ */ jsxDEV5("a", {
|
|
682
|
+
href,
|
|
683
|
+
className: "group block p-5 bg-white border border-neutral-200 hover:border-neutral-300 transition-colors",
|
|
684
|
+
children: content
|
|
685
|
+
}, undefined, false, undefined, this);
|
|
686
|
+
}
|
|
687
|
+
function CmsAdminLanding({
|
|
688
|
+
basePath = "/admin",
|
|
689
|
+
pages,
|
|
690
|
+
modules = []
|
|
691
|
+
}) {
|
|
692
|
+
const langContext = useCmsLanguageOptional();
|
|
693
|
+
const t = langContext?.t ?? ((key) => {
|
|
694
|
+
const fallback = {
|
|
695
|
+
dashboard: "Dashboard",
|
|
696
|
+
cmsNotConfigured: "CMS not configured",
|
|
697
|
+
cmsNotConfiguredDesc: "Please configure the CMS to get started editing content.",
|
|
698
|
+
modulePages: "Website Content",
|
|
699
|
+
modulePagesDesc: "Edit pages, text, and images on your website"
|
|
700
|
+
};
|
|
701
|
+
return fallback[key] ?? key;
|
|
702
|
+
});
|
|
703
|
+
if (pages.length === 0 && modules.length === 0) {
|
|
704
|
+
return /* @__PURE__ */ jsxDEV5(EmptyState, {
|
|
705
|
+
t
|
|
706
|
+
}, undefined, false, undefined, this);
|
|
707
|
+
}
|
|
708
|
+
const firstPageSlug = pages[0]?.slug;
|
|
709
|
+
return /* @__PURE__ */ jsxDEV5("div", {
|
|
710
|
+
className: "space-y-8",
|
|
711
|
+
children: [
|
|
712
|
+
/* @__PURE__ */ jsxDEV5("header", {
|
|
713
|
+
className: "border-b border-neutral-200 h-16 px-8 flex items-center",
|
|
714
|
+
children: /* @__PURE__ */ jsxDEV5("h1", {
|
|
715
|
+
className: "text-2xl font-light tracking-tight text-neutral-900",
|
|
716
|
+
children: t("dashboard")
|
|
717
|
+
}, undefined, false, undefined, this)
|
|
718
|
+
}, undefined, false, undefined, this),
|
|
719
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
720
|
+
className: "px-8 pb-12",
|
|
721
|
+
children: /* @__PURE__ */ jsxDEV5("div", {
|
|
722
|
+
className: "grid gap-4 xl:grid-cols-2",
|
|
723
|
+
children: [
|
|
724
|
+
pages.length > 0 && /* @__PURE__ */ jsxDEV5(ModuleCard, {
|
|
725
|
+
title: t("modulePages"),
|
|
726
|
+
description: t("modulePagesDesc"),
|
|
727
|
+
icon: CmsModuleIcons.pages,
|
|
728
|
+
href: firstPageSlug ? `${basePath}/${firstPageSlug}` : undefined,
|
|
729
|
+
disabled: !firstPageSlug
|
|
730
|
+
}, undefined, false, undefined, this),
|
|
731
|
+
modules.map((module) => /* @__PURE__ */ jsxDEV5(ModuleCard, {
|
|
732
|
+
title: module.title,
|
|
733
|
+
description: module.description,
|
|
734
|
+
icon: module.icon,
|
|
735
|
+
href: module.href,
|
|
736
|
+
disabled: module.disabled,
|
|
737
|
+
badge: module.badge
|
|
738
|
+
}, module.id, false, undefined, this))
|
|
739
|
+
]
|
|
740
|
+
}, undefined, true, undefined, this)
|
|
741
|
+
}, undefined, false, undefined, this)
|
|
742
|
+
]
|
|
743
|
+
}, undefined, true, undefined, this);
|
|
744
|
+
}
|
|
745
|
+
// src/admin/CmsBlogAdmin.tsx
|
|
746
|
+
import * as React3 from "react";
|
|
747
|
+
import ReactMarkdown from "react-markdown";
|
|
748
|
+
import remarkGfm from "remark-gfm";
|
|
749
|
+
import { jsxDEV as jsxDEV6, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
750
|
+
function generateId() {
|
|
751
|
+
return crypto.randomUUID();
|
|
752
|
+
}
|
|
753
|
+
function generateSlug(title) {
|
|
754
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 100);
|
|
755
|
+
}
|
|
756
|
+
function formatDate(dateString) {
|
|
757
|
+
return new Date(dateString).toLocaleDateString(undefined, {
|
|
758
|
+
year: "numeric",
|
|
759
|
+
month: "short",
|
|
760
|
+
day: "numeric"
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
var Icons = {
|
|
764
|
+
back: /* @__PURE__ */ jsxDEV6("svg", {
|
|
765
|
+
className: "w-4 h-4",
|
|
766
|
+
viewBox: "0 0 24 24",
|
|
767
|
+
fill: "none",
|
|
768
|
+
stroke: "currentColor",
|
|
769
|
+
strokeWidth: "2",
|
|
770
|
+
children: /* @__PURE__ */ jsxDEV6("path", {
|
|
771
|
+
strokeLinecap: "round",
|
|
772
|
+
strokeLinejoin: "round",
|
|
773
|
+
d: "M15.75 19.5L8.25 12l7.5-7.5"
|
|
774
|
+
}, undefined, false, undefined, this)
|
|
775
|
+
}, undefined, false, undefined, this),
|
|
776
|
+
plus: /* @__PURE__ */ jsxDEV6("svg", {
|
|
777
|
+
className: "w-4 h-4",
|
|
778
|
+
viewBox: "0 0 24 24",
|
|
779
|
+
fill: "none",
|
|
780
|
+
stroke: "currentColor",
|
|
781
|
+
strokeWidth: "2",
|
|
782
|
+
children: [
|
|
783
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
784
|
+
x1: "12",
|
|
785
|
+
y1: "5",
|
|
786
|
+
x2: "12",
|
|
787
|
+
y2: "19"
|
|
788
|
+
}, undefined, false, undefined, this),
|
|
789
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
790
|
+
x1: "5",
|
|
791
|
+
y1: "12",
|
|
792
|
+
x2: "19",
|
|
793
|
+
y2: "12"
|
|
794
|
+
}, undefined, false, undefined, this)
|
|
795
|
+
]
|
|
796
|
+
}, undefined, true, undefined, this),
|
|
797
|
+
image: /* @__PURE__ */ jsxDEV6("svg", {
|
|
798
|
+
className: "w-4 h-4",
|
|
799
|
+
viewBox: "0 0 24 24",
|
|
800
|
+
fill: "none",
|
|
801
|
+
stroke: "currentColor",
|
|
802
|
+
strokeWidth: "2",
|
|
803
|
+
children: [
|
|
804
|
+
/* @__PURE__ */ jsxDEV6("rect", {
|
|
805
|
+
x: "3",
|
|
806
|
+
y: "3",
|
|
807
|
+
width: "18",
|
|
808
|
+
height: "18",
|
|
809
|
+
rx: "2",
|
|
810
|
+
ry: "2"
|
|
811
|
+
}, undefined, false, undefined, this),
|
|
812
|
+
/* @__PURE__ */ jsxDEV6("circle", {
|
|
813
|
+
cx: "8.5",
|
|
814
|
+
cy: "8.5",
|
|
815
|
+
r: "1.5"
|
|
816
|
+
}, undefined, false, undefined, this),
|
|
817
|
+
/* @__PURE__ */ jsxDEV6("polyline", {
|
|
818
|
+
points: "21,15 16,10 5,21"
|
|
819
|
+
}, undefined, false, undefined, this)
|
|
820
|
+
]
|
|
821
|
+
}, undefined, true, undefined, this),
|
|
822
|
+
bold: /* @__PURE__ */ jsxDEV6("svg", {
|
|
823
|
+
className: "w-4 h-4",
|
|
824
|
+
viewBox: "0 0 24 24",
|
|
825
|
+
fill: "none",
|
|
826
|
+
stroke: "currentColor",
|
|
827
|
+
strokeWidth: "2",
|
|
828
|
+
children: [
|
|
829
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
830
|
+
d: "M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"
|
|
831
|
+
}, undefined, false, undefined, this),
|
|
832
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
833
|
+
d: "M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"
|
|
834
|
+
}, undefined, false, undefined, this)
|
|
835
|
+
]
|
|
836
|
+
}, undefined, true, undefined, this),
|
|
837
|
+
italic: /* @__PURE__ */ jsxDEV6("svg", {
|
|
838
|
+
className: "w-4 h-4",
|
|
839
|
+
viewBox: "0 0 24 24",
|
|
840
|
+
fill: "none",
|
|
841
|
+
stroke: "currentColor",
|
|
842
|
+
strokeWidth: "2",
|
|
843
|
+
children: [
|
|
844
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
845
|
+
x1: "19",
|
|
846
|
+
y1: "4",
|
|
847
|
+
x2: "10",
|
|
848
|
+
y2: "4"
|
|
849
|
+
}, undefined, false, undefined, this),
|
|
850
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
851
|
+
x1: "14",
|
|
852
|
+
y1: "20",
|
|
853
|
+
x2: "5",
|
|
854
|
+
y2: "20"
|
|
855
|
+
}, undefined, false, undefined, this),
|
|
856
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
857
|
+
x1: "15",
|
|
858
|
+
y1: "4",
|
|
859
|
+
x2: "9",
|
|
860
|
+
y2: "20"
|
|
861
|
+
}, undefined, false, undefined, this)
|
|
862
|
+
]
|
|
863
|
+
}, undefined, true, undefined, this),
|
|
864
|
+
heading: /* @__PURE__ */ jsxDEV6("svg", {
|
|
865
|
+
className: "w-4 h-4",
|
|
866
|
+
viewBox: "0 0 24 24",
|
|
867
|
+
fill: "none",
|
|
868
|
+
stroke: "currentColor",
|
|
869
|
+
strokeWidth: "2",
|
|
870
|
+
children: [
|
|
871
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
872
|
+
d: "M4 12h8"
|
|
873
|
+
}, undefined, false, undefined, this),
|
|
874
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
875
|
+
d: "M4 18V6"
|
|
876
|
+
}, undefined, false, undefined, this),
|
|
877
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
878
|
+
d: "M12 18V6"
|
|
879
|
+
}, undefined, false, undefined, this),
|
|
880
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
881
|
+
d: "M17 10v4h4"
|
|
882
|
+
}, undefined, false, undefined, this),
|
|
883
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
884
|
+
d: "M21 10v8"
|
|
885
|
+
}, undefined, false, undefined, this)
|
|
886
|
+
]
|
|
887
|
+
}, undefined, true, undefined, this),
|
|
888
|
+
list: /* @__PURE__ */ jsxDEV6("svg", {
|
|
889
|
+
className: "w-4 h-4",
|
|
890
|
+
viewBox: "0 0 24 24",
|
|
891
|
+
fill: "none",
|
|
892
|
+
stroke: "currentColor",
|
|
893
|
+
strokeWidth: "2",
|
|
894
|
+
children: [
|
|
895
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
896
|
+
x1: "8",
|
|
897
|
+
y1: "6",
|
|
898
|
+
x2: "21",
|
|
899
|
+
y2: "6"
|
|
900
|
+
}, undefined, false, undefined, this),
|
|
901
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
902
|
+
x1: "8",
|
|
903
|
+
y1: "12",
|
|
904
|
+
x2: "21",
|
|
905
|
+
y2: "12"
|
|
906
|
+
}, undefined, false, undefined, this),
|
|
907
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
908
|
+
x1: "8",
|
|
909
|
+
y1: "18",
|
|
910
|
+
x2: "21",
|
|
911
|
+
y2: "18"
|
|
912
|
+
}, undefined, false, undefined, this),
|
|
913
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
914
|
+
x1: "3",
|
|
915
|
+
y1: "6",
|
|
916
|
+
x2: "3.01",
|
|
917
|
+
y2: "6"
|
|
918
|
+
}, undefined, false, undefined, this),
|
|
919
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
920
|
+
x1: "3",
|
|
921
|
+
y1: "12",
|
|
922
|
+
x2: "3.01",
|
|
923
|
+
y2: "12"
|
|
924
|
+
}, undefined, false, undefined, this),
|
|
925
|
+
/* @__PURE__ */ jsxDEV6("line", {
|
|
926
|
+
x1: "3",
|
|
927
|
+
y1: "18",
|
|
928
|
+
x2: "3.01",
|
|
929
|
+
y2: "18"
|
|
930
|
+
}, undefined, false, undefined, this)
|
|
931
|
+
]
|
|
932
|
+
}, undefined, true, undefined, this),
|
|
933
|
+
link: /* @__PURE__ */ jsxDEV6("svg", {
|
|
934
|
+
className: "w-4 h-4",
|
|
935
|
+
viewBox: "0 0 24 24",
|
|
936
|
+
fill: "none",
|
|
937
|
+
stroke: "currentColor",
|
|
938
|
+
strokeWidth: "2",
|
|
939
|
+
children: [
|
|
940
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
941
|
+
d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
|
|
942
|
+
}, undefined, false, undefined, this),
|
|
943
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
944
|
+
d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
|
|
945
|
+
}, undefined, false, undefined, this)
|
|
946
|
+
]
|
|
947
|
+
}, undefined, true, undefined, this),
|
|
948
|
+
code: /* @__PURE__ */ jsxDEV6("svg", {
|
|
949
|
+
className: "w-4 h-4",
|
|
950
|
+
viewBox: "0 0 24 24",
|
|
951
|
+
fill: "none",
|
|
952
|
+
stroke: "currentColor",
|
|
953
|
+
strokeWidth: "2",
|
|
954
|
+
children: [
|
|
955
|
+
/* @__PURE__ */ jsxDEV6("polyline", {
|
|
956
|
+
points: "16,18 22,12 16,6"
|
|
957
|
+
}, undefined, false, undefined, this),
|
|
958
|
+
/* @__PURE__ */ jsxDEV6("polyline", {
|
|
959
|
+
points: "8,6 2,12 8,18"
|
|
960
|
+
}, undefined, false, undefined, this)
|
|
961
|
+
]
|
|
962
|
+
}, undefined, true, undefined, this),
|
|
963
|
+
quote: /* @__PURE__ */ jsxDEV6("svg", {
|
|
964
|
+
className: "w-4 h-4",
|
|
965
|
+
viewBox: "0 0 24 24",
|
|
966
|
+
fill: "none",
|
|
967
|
+
stroke: "currentColor",
|
|
968
|
+
strokeWidth: "2",
|
|
969
|
+
children: [
|
|
970
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
971
|
+
d: "M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"
|
|
972
|
+
}, undefined, false, undefined, this),
|
|
973
|
+
/* @__PURE__ */ jsxDEV6("path", {
|
|
974
|
+
d: "M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"
|
|
975
|
+
}, undefined, false, undefined, this)
|
|
976
|
+
]
|
|
977
|
+
}, undefined, true, undefined, this),
|
|
978
|
+
document: /* @__PURE__ */ jsxDEV6("svg", {
|
|
979
|
+
className: "w-5 h-5",
|
|
980
|
+
viewBox: "0 0 24 24",
|
|
981
|
+
fill: "none",
|
|
982
|
+
stroke: "currentColor",
|
|
983
|
+
strokeWidth: "1.5",
|
|
984
|
+
children: /* @__PURE__ */ jsxDEV6("path", {
|
|
985
|
+
strokeLinecap: "round",
|
|
986
|
+
strokeLinejoin: "round",
|
|
987
|
+
d: "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
|
988
|
+
}, undefined, false, undefined, this)
|
|
989
|
+
}, undefined, false, undefined, this)
|
|
990
|
+
};
|
|
991
|
+
function PostCard({
|
|
992
|
+
post,
|
|
993
|
+
onClick,
|
|
994
|
+
t
|
|
995
|
+
}) {
|
|
996
|
+
return /* @__PURE__ */ jsxDEV6("button", {
|
|
997
|
+
onClick,
|
|
998
|
+
className: "w-full text-left p-4 bg-white border border-neutral-200 hover:border-neutral-300 transition-colors group",
|
|
999
|
+
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
1000
|
+
className: "flex items-start gap-4",
|
|
1001
|
+
children: [
|
|
1002
|
+
post.coverImage ? /* @__PURE__ */ jsxDEV6("img", {
|
|
1003
|
+
src: post.coverImage,
|
|
1004
|
+
alt: "",
|
|
1005
|
+
className: "w-20 h-20 object-cover shrink-0"
|
|
1006
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6("div", {
|
|
1007
|
+
className: "w-20 h-20 bg-neutral-100 flex items-center justify-center shrink-0",
|
|
1008
|
+
children: Icons.document
|
|
1009
|
+
}, undefined, false, undefined, this),
|
|
1010
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1011
|
+
className: "flex-1 min-w-0",
|
|
1012
|
+
children: [
|
|
1013
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1014
|
+
className: "flex items-center gap-2 mb-1",
|
|
1015
|
+
children: [
|
|
1016
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
1017
|
+
className: "text-sm font-medium text-neutral-900 truncate group-hover:text-neutral-600 transition-colors",
|
|
1018
|
+
children: post.title || t("blogUntitled")
|
|
1019
|
+
}, undefined, false, undefined, this),
|
|
1020
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1021
|
+
className: `shrink-0 px-1.5 py-0.5 text-xs rounded ${post.status === "published" ? "bg-green-100 text-green-700" : "bg-neutral-100 text-neutral-500"}`,
|
|
1022
|
+
children: post.status === "published" ? t("blogPublished") : t("blogDraft")
|
|
1023
|
+
}, undefined, false, undefined, this)
|
|
1024
|
+
]
|
|
1025
|
+
}, undefined, true, undefined, this),
|
|
1026
|
+
/* @__PURE__ */ jsxDEV6("p", {
|
|
1027
|
+
className: "text-xs text-neutral-500 mb-2",
|
|
1028
|
+
children: [
|
|
1029
|
+
post.authorName,
|
|
1030
|
+
" · ",
|
|
1031
|
+
formatDate(post.updatedAt)
|
|
1032
|
+
]
|
|
1033
|
+
}, undefined, true, undefined, this),
|
|
1034
|
+
post.excerpt && /* @__PURE__ */ jsxDEV6("p", {
|
|
1035
|
+
className: "text-sm text-neutral-600 line-clamp-2",
|
|
1036
|
+
children: post.excerpt
|
|
1037
|
+
}, undefined, false, undefined, this)
|
|
1038
|
+
]
|
|
1039
|
+
}, undefined, true, undefined, this)
|
|
1040
|
+
]
|
|
1041
|
+
}, undefined, true, undefined, this)
|
|
1042
|
+
}, undefined, false, undefined, this);
|
|
1043
|
+
}
|
|
1044
|
+
function EmptyState2({
|
|
1045
|
+
t,
|
|
1046
|
+
onCreate
|
|
1047
|
+
}) {
|
|
1048
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1049
|
+
className: "flex flex-col items-center justify-center py-24 text-center",
|
|
1050
|
+
children: [
|
|
1051
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1052
|
+
className: "w-16 h-16 rounded-lg flex items-center justify-center mb-4 bg-neutral-100",
|
|
1053
|
+
children: Icons.document
|
|
1054
|
+
}, undefined, false, undefined, this),
|
|
1055
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
1056
|
+
className: "text-lg font-medium text-neutral-900 mb-2",
|
|
1057
|
+
children: t("blogNoPostsYet")
|
|
1058
|
+
}, undefined, false, undefined, this),
|
|
1059
|
+
/* @__PURE__ */ jsxDEV6("p", {
|
|
1060
|
+
className: "text-sm text-neutral-500 mb-6 max-w-sm",
|
|
1061
|
+
children: t("blogCreateFirstPost")
|
|
1062
|
+
}, undefined, false, undefined, this),
|
|
1063
|
+
/* @__PURE__ */ jsxDEV6("button", {
|
|
1064
|
+
onClick: onCreate,
|
|
1065
|
+
className: "inline-flex items-center gap-2 px-4 py-2 text-sm bg-neutral-900 text-white rounded hover:bg-neutral-800 transition-colors",
|
|
1066
|
+
children: [
|
|
1067
|
+
Icons.plus,
|
|
1068
|
+
t("blogNewPost")
|
|
1069
|
+
]
|
|
1070
|
+
}, undefined, true, undefined, this)
|
|
1071
|
+
]
|
|
1072
|
+
}, undefined, true, undefined, this);
|
|
1073
|
+
}
|
|
1074
|
+
function MarkdownPreview({
|
|
1075
|
+
content,
|
|
1076
|
+
t
|
|
1077
|
+
}) {
|
|
1078
|
+
if (!content.trim()) {
|
|
1079
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1080
|
+
className: "h-full flex items-center justify-center text-neutral-400 text-sm",
|
|
1081
|
+
children: t("blogPreviewPlaceholder")
|
|
1082
|
+
}, undefined, false, undefined, this);
|
|
1083
|
+
}
|
|
1084
|
+
const markdownComponents = {
|
|
1085
|
+
h1: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("h1", {
|
|
1086
|
+
...props,
|
|
1087
|
+
className: "text-2xl font-semibold text-neutral-900 mt-6 mb-3",
|
|
1088
|
+
style: {
|
|
1089
|
+
fontSize: "1.5rem",
|
|
1090
|
+
fontWeight: 600,
|
|
1091
|
+
lineHeight: "2rem"
|
|
1092
|
+
},
|
|
1093
|
+
children
|
|
1094
|
+
}, undefined, false, undefined, this),
|
|
1095
|
+
h2: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("h2", {
|
|
1096
|
+
...props,
|
|
1097
|
+
className: "text-xl font-semibold text-neutral-900 mt-5 mb-3",
|
|
1098
|
+
style: {
|
|
1099
|
+
fontSize: "1.25rem",
|
|
1100
|
+
fontWeight: 600,
|
|
1101
|
+
lineHeight: "1.75rem"
|
|
1102
|
+
},
|
|
1103
|
+
children
|
|
1104
|
+
}, undefined, false, undefined, this),
|
|
1105
|
+
h3: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("h3", {
|
|
1106
|
+
...props,
|
|
1107
|
+
className: "text-lg font-semibold text-neutral-900 mt-4 mb-2",
|
|
1108
|
+
style: {
|
|
1109
|
+
fontSize: "1.125rem",
|
|
1110
|
+
fontWeight: 600,
|
|
1111
|
+
lineHeight: "1.5rem"
|
|
1112
|
+
},
|
|
1113
|
+
children
|
|
1114
|
+
}, undefined, false, undefined, this),
|
|
1115
|
+
h4: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("h4", {
|
|
1116
|
+
...props,
|
|
1117
|
+
className: "text-base font-semibold text-neutral-900 mt-4 mb-2",
|
|
1118
|
+
style: {
|
|
1119
|
+
fontSize: "1rem",
|
|
1120
|
+
fontWeight: 600,
|
|
1121
|
+
lineHeight: "1.5rem"
|
|
1122
|
+
},
|
|
1123
|
+
children
|
|
1124
|
+
}, undefined, false, undefined, this),
|
|
1125
|
+
p: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("p", {
|
|
1126
|
+
...props,
|
|
1127
|
+
className: "text-sm text-neutral-700 leading-6 mb-3",
|
|
1128
|
+
children
|
|
1129
|
+
}, undefined, false, undefined, this),
|
|
1130
|
+
a: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("a", {
|
|
1131
|
+
...props,
|
|
1132
|
+
className: "text-blue-600 hover:text-blue-700 underline underline-offset-2",
|
|
1133
|
+
children
|
|
1134
|
+
}, undefined, false, undefined, this),
|
|
1135
|
+
ul: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("ul", {
|
|
1136
|
+
...props,
|
|
1137
|
+
className: "list-disc pl-5 text-sm text-neutral-700 mb-3",
|
|
1138
|
+
children
|
|
1139
|
+
}, undefined, false, undefined, this),
|
|
1140
|
+
ol: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("ol", {
|
|
1141
|
+
...props,
|
|
1142
|
+
className: "list-decimal pl-5 text-sm text-neutral-700 mb-3",
|
|
1143
|
+
children
|
|
1144
|
+
}, undefined, false, undefined, this),
|
|
1145
|
+
li: ({ children, ...props }) => /* @__PURE__ */ jsxDEV6("li", {
|
|
1146
|
+
...props,
|
|
1147
|
+
className: "mb-1",
|
|
1148
|
+
children
|
|
1149
|
+
}, undefined, false, undefined, this),
|
|
1150
|
+
blockquote: ({
|
|
1151
|
+
children,
|
|
1152
|
+
...props
|
|
1153
|
+
}) => /* @__PURE__ */ jsxDEV6("blockquote", {
|
|
1154
|
+
...props,
|
|
1155
|
+
className: "border-l-4 border-neutral-200 pl-4 italic text-neutral-600 my-4",
|
|
1156
|
+
children
|
|
1157
|
+
}, undefined, false, undefined, this),
|
|
1158
|
+
code: (props) => {
|
|
1159
|
+
const { children, inline, ...rest } = props;
|
|
1160
|
+
return inline ? /* @__PURE__ */ jsxDEV6("code", {
|
|
1161
|
+
...rest,
|
|
1162
|
+
className: "px-1 py-0.5 rounded bg-neutral-100 text-neutral-800 font-mono text-xs",
|
|
1163
|
+
children
|
|
1164
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6("code", {
|
|
1165
|
+
...rest,
|
|
1166
|
+
className: "block w-full overflow-x-auto rounded bg-neutral-900 text-neutral-100 p-3 text-xs font-mono",
|
|
1167
|
+
children
|
|
1168
|
+
}, undefined, false, undefined, this);
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1172
|
+
className: "max-w-none p-6",
|
|
1173
|
+
children: /* @__PURE__ */ jsxDEV6(ReactMarkdown, {
|
|
1174
|
+
remarkPlugins: [remarkGfm],
|
|
1175
|
+
components: markdownComponents,
|
|
1176
|
+
children: content
|
|
1177
|
+
}, undefined, false, undefined, this)
|
|
1178
|
+
}, undefined, false, undefined, this);
|
|
1179
|
+
}
|
|
1180
|
+
function MarkdownToolbar({
|
|
1181
|
+
textareaRef,
|
|
1182
|
+
content,
|
|
1183
|
+
setContent,
|
|
1184
|
+
onImageClick,
|
|
1185
|
+
t
|
|
1186
|
+
}) {
|
|
1187
|
+
function insertAtCursor(before, after = "", placeholder = "") {
|
|
1188
|
+
const textarea = textareaRef.current;
|
|
1189
|
+
if (!textarea)
|
|
1190
|
+
return;
|
|
1191
|
+
const start = textarea.selectionStart;
|
|
1192
|
+
const end = textarea.selectionEnd;
|
|
1193
|
+
const selectedText = content.substring(start, end);
|
|
1194
|
+
const textToInsert = selectedText || placeholder;
|
|
1195
|
+
const newContent = content.substring(0, start) + before + textToInsert + after + content.substring(end);
|
|
1196
|
+
setContent(newContent);
|
|
1197
|
+
setTimeout(() => {
|
|
1198
|
+
textarea.focus();
|
|
1199
|
+
const newCursorPos = start + before.length + textToInsert.length;
|
|
1200
|
+
textarea.setSelectionRange(start + before.length, newCursorPos);
|
|
1201
|
+
}, 0);
|
|
1202
|
+
}
|
|
1203
|
+
const buttons = [
|
|
1204
|
+
{
|
|
1205
|
+
icon: Icons.bold,
|
|
1206
|
+
action: () => insertAtCursor("**", "**", "bold"),
|
|
1207
|
+
title: "Bold"
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
icon: Icons.italic,
|
|
1211
|
+
action: () => insertAtCursor("_", "_", "italic"),
|
|
1212
|
+
title: "Italic"
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
icon: Icons.heading,
|
|
1216
|
+
action: () => insertAtCursor("## ", "", "Heading"),
|
|
1217
|
+
title: "Heading"
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
icon: Icons.list,
|
|
1221
|
+
action: () => insertAtCursor("- ", "", "List item"),
|
|
1222
|
+
title: "List"
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
icon: Icons.quote,
|
|
1226
|
+
action: () => insertAtCursor("> ", "", "Quote"),
|
|
1227
|
+
title: "Quote"
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
icon: Icons.code,
|
|
1231
|
+
action: () => insertAtCursor("`", "`", "code"),
|
|
1232
|
+
title: "Code"
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
icon: Icons.link,
|
|
1236
|
+
action: () => insertAtCursor("[", "](url)", "link text"),
|
|
1237
|
+
title: "Link"
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
icon: Icons.image,
|
|
1241
|
+
action: onImageClick,
|
|
1242
|
+
title: t("blogInsertImage")
|
|
1243
|
+
}
|
|
1244
|
+
];
|
|
1245
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1246
|
+
className: "flex items-center gap-1 px-2 py-1.5 border-b border-neutral-200 bg-neutral-50",
|
|
1247
|
+
children: buttons.map((btn, i) => /* @__PURE__ */ jsxDEV6("button", {
|
|
1248
|
+
type: "button",
|
|
1249
|
+
onClick: btn.action,
|
|
1250
|
+
title: btn.title,
|
|
1251
|
+
className: "p-1.5 text-neutral-500 hover:text-neutral-900 hover:bg-neutral-200 rounded transition-colors",
|
|
1252
|
+
children: btn.icon
|
|
1253
|
+
}, i, false, undefined, this))
|
|
1254
|
+
}, undefined, false, undefined, this);
|
|
1255
|
+
}
|
|
1256
|
+
function BlogListView({
|
|
1257
|
+
posts,
|
|
1258
|
+
isLoading,
|
|
1259
|
+
onSelectPost,
|
|
1260
|
+
onNewPost,
|
|
1261
|
+
t
|
|
1262
|
+
}) {
|
|
1263
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1264
|
+
className: "h-full flex flex-col",
|
|
1265
|
+
children: [
|
|
1266
|
+
/* @__PURE__ */ jsxDEV6("header", {
|
|
1267
|
+
className: "border-b border-neutral-200 h-16 px-8 flex items-center justify-between shrink-0",
|
|
1268
|
+
children: [
|
|
1269
|
+
/* @__PURE__ */ jsxDEV6("h1", {
|
|
1270
|
+
className: "text-2xl font-light tracking-tight text-neutral-900",
|
|
1271
|
+
children: t("blogPosts")
|
|
1272
|
+
}, undefined, false, undefined, this),
|
|
1273
|
+
/* @__PURE__ */ jsxDEV6("button", {
|
|
1274
|
+
onClick: onNewPost,
|
|
1275
|
+
className: "inline-flex items-center gap-2 px-3 py-1.5 text-sm bg-neutral-900 text-white rounded hover:bg-neutral-800 transition-colors",
|
|
1276
|
+
children: [
|
|
1277
|
+
Icons.plus,
|
|
1278
|
+
t("blogNewPost")
|
|
1279
|
+
]
|
|
1280
|
+
}, undefined, true, undefined, this)
|
|
1281
|
+
]
|
|
1282
|
+
}, undefined, true, undefined, this),
|
|
1283
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1284
|
+
className: "flex-1 overflow-y-auto p-8",
|
|
1285
|
+
children: isLoading ? /* @__PURE__ */ jsxDEV6("div", {
|
|
1286
|
+
className: "flex items-center justify-center py-24",
|
|
1287
|
+
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
1288
|
+
className: "text-neutral-500 text-sm",
|
|
1289
|
+
children: t("loading")
|
|
1290
|
+
}, undefined, false, undefined, this)
|
|
1291
|
+
}, undefined, false, undefined, this) : posts.length === 0 ? /* @__PURE__ */ jsxDEV6(EmptyState2, {
|
|
1292
|
+
t,
|
|
1293
|
+
onCreate: onNewPost
|
|
1294
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6("div", {
|
|
1295
|
+
className: "grid gap-4 max-w-4xl",
|
|
1296
|
+
children: posts.map((post) => /* @__PURE__ */ jsxDEV6(PostCard, {
|
|
1297
|
+
post,
|
|
1298
|
+
onClick: () => onSelectPost(post),
|
|
1299
|
+
t
|
|
1300
|
+
}, post.id, false, undefined, this))
|
|
1301
|
+
}, undefined, false, undefined, this)
|
|
1302
|
+
}, undefined, false, undefined, this)
|
|
1303
|
+
]
|
|
1304
|
+
}, undefined, true, undefined, this);
|
|
1305
|
+
}
|
|
1306
|
+
function BlogEditorView({
|
|
1307
|
+
post,
|
|
1308
|
+
onBack,
|
|
1309
|
+
onSave,
|
|
1310
|
+
onDelete,
|
|
1311
|
+
storage,
|
|
1312
|
+
defaultAuthorName,
|
|
1313
|
+
t
|
|
1314
|
+
}) {
|
|
1315
|
+
const contentTextareaRef = React3.useRef(null);
|
|
1316
|
+
const [title, setTitle] = React3.useState(post?.title ?? "");
|
|
1317
|
+
const [slug, setSlug] = React3.useState(post?.slug ?? "");
|
|
1318
|
+
const [excerpt, setExcerpt] = React3.useState(post?.excerpt ?? "");
|
|
1319
|
+
const [coverImage, setCoverImage] = React3.useState(post?.coverImage ?? "");
|
|
1320
|
+
const [content, setContent] = React3.useState(post?.content ?? "");
|
|
1321
|
+
const [tagsInput, setTagsInput] = React3.useState(post?.tags?.join(", ") ?? "");
|
|
1322
|
+
const [authorName, setAuthorName] = React3.useState(post?.authorName ?? defaultAuthorName);
|
|
1323
|
+
const [status, setStatus] = React3.useState(post?.status ?? "draft");
|
|
1324
|
+
const [isSaving, setIsSaving] = React3.useState(false);
|
|
1325
|
+
const [isDeleting, setIsDeleting] = React3.useState(false);
|
|
1326
|
+
const [error, setError] = React3.useState(null);
|
|
1327
|
+
const [showImagePicker, setShowImagePicker] = React3.useState(false);
|
|
1328
|
+
const [imagePickerTarget, setImagePickerTarget] = React3.useState("content");
|
|
1329
|
+
const [isDragging, setIsDragging] = React3.useState(false);
|
|
1330
|
+
React3.useEffect(() => {
|
|
1331
|
+
if (!post && title && !slug) {
|
|
1332
|
+
setSlug(generateSlug(title));
|
|
1333
|
+
}
|
|
1334
|
+
}, [title, post, slug]);
|
|
1335
|
+
React3.useEffect(() => {
|
|
1336
|
+
if (post) {
|
|
1337
|
+
setTitle(post.title);
|
|
1338
|
+
setSlug(post.slug);
|
|
1339
|
+
setExcerpt(post.excerpt ?? "");
|
|
1340
|
+
setCoverImage(post.coverImage ?? "");
|
|
1341
|
+
setContent(post.content);
|
|
1342
|
+
setTagsInput(post.tags?.join(", ") ?? "");
|
|
1343
|
+
setAuthorName(post.authorName);
|
|
1344
|
+
setStatus(post.status);
|
|
1345
|
+
}
|
|
1346
|
+
}, [post]);
|
|
1347
|
+
async function handleSave(publish = false) {
|
|
1348
|
+
if (!title.trim() || !slug.trim()) {
|
|
1349
|
+
setError("Title and slug are required");
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
setIsSaving(true);
|
|
1353
|
+
setError(null);
|
|
1354
|
+
const tags = tagsInput.split(",").map((t2) => t2.trim()).filter(Boolean);
|
|
1355
|
+
const result = await onSave({
|
|
1356
|
+
id: post?.id,
|
|
1357
|
+
title,
|
|
1358
|
+
slug,
|
|
1359
|
+
excerpt: excerpt || undefined,
|
|
1360
|
+
coverImage: coverImage || undefined,
|
|
1361
|
+
content,
|
|
1362
|
+
tags,
|
|
1363
|
+
authorName
|
|
1364
|
+
}, publish);
|
|
1365
|
+
setIsSaving(false);
|
|
1366
|
+
if (result) {
|
|
1367
|
+
setStatus(result.status);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
async function handleDelete() {
|
|
1371
|
+
if (!post)
|
|
1372
|
+
return;
|
|
1373
|
+
if (!confirm(t("blogConfirmDelete")))
|
|
1374
|
+
return;
|
|
1375
|
+
setIsDeleting(true);
|
|
1376
|
+
const success = await onDelete(post.id);
|
|
1377
|
+
setIsDeleting(false);
|
|
1378
|
+
if (success) {
|
|
1379
|
+
onBack();
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
function handleImageSelect(url) {
|
|
1383
|
+
if (imagePickerTarget === "cover") {
|
|
1384
|
+
setCoverImage(url);
|
|
1385
|
+
} else {
|
|
1386
|
+
insertImageAtCursor(url);
|
|
1387
|
+
}
|
|
1388
|
+
setShowImagePicker(false);
|
|
1389
|
+
}
|
|
1390
|
+
function insertImageAtCursor(url, altText = "image") {
|
|
1391
|
+
const textarea = contentTextareaRef.current;
|
|
1392
|
+
const imageMarkdown = ``;
|
|
1393
|
+
if (!textarea) {
|
|
1394
|
+
setContent((prev) => prev + `
|
|
1395
|
+
` + imageMarkdown + `
|
|
1396
|
+
`);
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const start = textarea.selectionStart;
|
|
1400
|
+
const newContent = content.substring(0, start) + imageMarkdown + content.substring(start);
|
|
1401
|
+
setContent(newContent);
|
|
1402
|
+
setTimeout(() => {
|
|
1403
|
+
textarea.focus();
|
|
1404
|
+
const newCursorPos = start + imageMarkdown.length;
|
|
1405
|
+
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
|
1406
|
+
}, 0);
|
|
1407
|
+
}
|
|
1408
|
+
function openImagePickerForContent() {
|
|
1409
|
+
setImagePickerTarget("content");
|
|
1410
|
+
setShowImagePicker(true);
|
|
1411
|
+
}
|
|
1412
|
+
function openImagePickerForCover() {
|
|
1413
|
+
setImagePickerTarget("cover");
|
|
1414
|
+
setShowImagePicker(true);
|
|
1415
|
+
}
|
|
1416
|
+
function handleDragOver(e) {
|
|
1417
|
+
e.preventDefault();
|
|
1418
|
+
if (e.dataTransfer.types.includes("Files")) {
|
|
1419
|
+
setIsDragging(true);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
function handleDragLeave(e) {
|
|
1423
|
+
e.preventDefault();
|
|
1424
|
+
setIsDragging(false);
|
|
1425
|
+
}
|
|
1426
|
+
async function handleDrop(e) {
|
|
1427
|
+
e.preventDefault();
|
|
1428
|
+
setIsDragging(false);
|
|
1429
|
+
const files = Array.from(e.dataTransfer.files);
|
|
1430
|
+
const imageFile = files.find((f) => f.type.startsWith("image/"));
|
|
1431
|
+
if (!imageFile || !storage?.uploadEndpoint)
|
|
1432
|
+
return;
|
|
1433
|
+
const formData = new FormData;
|
|
1434
|
+
formData.append("file", imageFile);
|
|
1435
|
+
try {
|
|
1436
|
+
const res = await fetch(storage.uploadEndpoint, {
|
|
1437
|
+
method: "POST",
|
|
1438
|
+
body: formData
|
|
1439
|
+
});
|
|
1440
|
+
if (res.ok) {
|
|
1441
|
+
const data = await res.json();
|
|
1442
|
+
insertImageAtCursor(data.url, imageFile.name.replace(/\.[^.]+$/, ""));
|
|
1443
|
+
}
|
|
1444
|
+
} catch (err) {
|
|
1445
|
+
console.error("[litecms] Drop upload failed:", err);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1449
|
+
className: "h-full flex flex-col",
|
|
1450
|
+
children: [
|
|
1451
|
+
/* @__PURE__ */ jsxDEV6("header", {
|
|
1452
|
+
className: "border-b border-neutral-200 h-16 px-8 flex items-center justify-between shrink-0",
|
|
1453
|
+
children: [
|
|
1454
|
+
/* @__PURE__ */ jsxDEV6("button", {
|
|
1455
|
+
onClick: onBack,
|
|
1456
|
+
className: "inline-flex items-center gap-2 text-sm text-neutral-500 hover:text-neutral-900 transition-colors",
|
|
1457
|
+
children: [
|
|
1458
|
+
Icons.back,
|
|
1459
|
+
t("blogBackToList")
|
|
1460
|
+
]
|
|
1461
|
+
}, undefined, true, undefined, this),
|
|
1462
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1463
|
+
className: "flex items-center gap-2",
|
|
1464
|
+
children: [
|
|
1465
|
+
/* @__PURE__ */ jsxDEV6("button", {
|
|
1466
|
+
onClick: () => handleSave(false),
|
|
1467
|
+
disabled: isSaving || isDeleting,
|
|
1468
|
+
className: "px-3 py-1.5 text-sm border border-neutral-200 rounded hover:bg-neutral-50 transition-colors disabled:opacity-50",
|
|
1469
|
+
children: isSaving ? t("blogSaving") : t("blogSaveDraft")
|
|
1470
|
+
}, undefined, false, undefined, this),
|
|
1471
|
+
status === "published" ? /* @__PURE__ */ jsxDEV6("button", {
|
|
1472
|
+
onClick: () => handleSave(false),
|
|
1473
|
+
disabled: isSaving || isDeleting,
|
|
1474
|
+
className: "px-3 py-1.5 text-sm bg-neutral-900 text-white rounded hover:bg-neutral-800 transition-colors disabled:opacity-50",
|
|
1475
|
+
children: t("blogUpdate")
|
|
1476
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6("button", {
|
|
1477
|
+
onClick: () => handleSave(true),
|
|
1478
|
+
disabled: isSaving || isDeleting,
|
|
1479
|
+
className: "px-3 py-1.5 text-sm bg-neutral-900 text-white rounded hover:bg-neutral-800 transition-colors disabled:opacity-50",
|
|
1480
|
+
children: t("blogPublish")
|
|
1481
|
+
}, undefined, false, undefined, this)
|
|
1482
|
+
]
|
|
1483
|
+
}, undefined, true, undefined, this)
|
|
1484
|
+
]
|
|
1485
|
+
}, undefined, true, undefined, this),
|
|
1486
|
+
error && /* @__PURE__ */ jsxDEV6("div", {
|
|
1487
|
+
className: "px-8 py-2 bg-red-50 text-red-600 text-sm",
|
|
1488
|
+
children: error
|
|
1489
|
+
}, undefined, false, undefined, this),
|
|
1490
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1491
|
+
className: "flex-1 flex min-h-0",
|
|
1492
|
+
children: [
|
|
1493
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1494
|
+
className: "w-1/2 flex flex-col border-r border-neutral-200 overflow-hidden",
|
|
1495
|
+
children: [
|
|
1496
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1497
|
+
className: "px-4 py-2 border-b border-neutral-100 text-xs font-medium text-neutral-500 uppercase tracking-wider",
|
|
1498
|
+
children: t("blogEditor")
|
|
1499
|
+
}, undefined, false, undefined, this),
|
|
1500
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1501
|
+
className: "flex-1 overflow-y-auto p-4 space-y-4",
|
|
1502
|
+
children: [
|
|
1503
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1504
|
+
children: [
|
|
1505
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
1506
|
+
className: "block text-sm font-medium text-neutral-700 mb-1",
|
|
1507
|
+
children: t("blogTitle")
|
|
1508
|
+
}, undefined, false, undefined, this),
|
|
1509
|
+
/* @__PURE__ */ jsxDEV6("input", {
|
|
1510
|
+
type: "text",
|
|
1511
|
+
value: title,
|
|
1512
|
+
onChange: (e) => setTitle(e.target.value),
|
|
1513
|
+
className: "w-full px-3 py-2 border border-neutral-200 rounded text-sm focus:outline-none focus:border-neutral-400",
|
|
1514
|
+
placeholder: "Post title..."
|
|
1515
|
+
}, undefined, false, undefined, this)
|
|
1516
|
+
]
|
|
1517
|
+
}, undefined, true, undefined, this),
|
|
1518
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1519
|
+
children: [
|
|
1520
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
1521
|
+
className: "block text-sm font-medium text-neutral-700 mb-1",
|
|
1522
|
+
children: t("blogSlug")
|
|
1523
|
+
}, undefined, false, undefined, this),
|
|
1524
|
+
/* @__PURE__ */ jsxDEV6("input", {
|
|
1525
|
+
type: "text",
|
|
1526
|
+
value: slug,
|
|
1527
|
+
onChange: (e) => setSlug(e.target.value),
|
|
1528
|
+
className: "w-full px-3 py-2 border border-neutral-200 rounded text-sm focus:outline-none focus:border-neutral-400",
|
|
1529
|
+
placeholder: "post-slug"
|
|
1530
|
+
}, undefined, false, undefined, this)
|
|
1531
|
+
]
|
|
1532
|
+
}, undefined, true, undefined, this),
|
|
1533
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1534
|
+
children: [
|
|
1535
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
1536
|
+
className: "block text-sm font-medium text-neutral-700 mb-1",
|
|
1537
|
+
children: t("blogAuthor")
|
|
1538
|
+
}, undefined, false, undefined, this),
|
|
1539
|
+
/* @__PURE__ */ jsxDEV6("input", {
|
|
1540
|
+
type: "text",
|
|
1541
|
+
value: authorName,
|
|
1542
|
+
onChange: (e) => setAuthorName(e.target.value),
|
|
1543
|
+
className: "w-full px-3 py-2 border border-neutral-200 rounded text-sm focus:outline-none focus:border-neutral-400"
|
|
1544
|
+
}, undefined, false, undefined, this)
|
|
1545
|
+
]
|
|
1546
|
+
}, undefined, true, undefined, this),
|
|
1547
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1548
|
+
children: [
|
|
1549
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
1550
|
+
className: "block text-sm font-medium text-neutral-700 mb-1",
|
|
1551
|
+
children: t("blogExcerpt")
|
|
1552
|
+
}, undefined, false, undefined, this),
|
|
1553
|
+
/* @__PURE__ */ jsxDEV6("textarea", {
|
|
1554
|
+
value: excerpt,
|
|
1555
|
+
onChange: (e) => setExcerpt(e.target.value),
|
|
1556
|
+
rows: 2,
|
|
1557
|
+
className: "w-full px-3 py-2 border border-neutral-200 rounded text-sm focus:outline-none focus:border-neutral-400 resize-none",
|
|
1558
|
+
placeholder: "Brief summary..."
|
|
1559
|
+
}, undefined, false, undefined, this)
|
|
1560
|
+
]
|
|
1561
|
+
}, undefined, true, undefined, this),
|
|
1562
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1563
|
+
children: [
|
|
1564
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
1565
|
+
className: "block text-sm font-medium text-neutral-700 mb-1",
|
|
1566
|
+
children: t("blogTags")
|
|
1567
|
+
}, undefined, false, undefined, this),
|
|
1568
|
+
/* @__PURE__ */ jsxDEV6("input", {
|
|
1569
|
+
type: "text",
|
|
1570
|
+
value: tagsInput,
|
|
1571
|
+
onChange: (e) => setTagsInput(e.target.value),
|
|
1572
|
+
className: "w-full px-3 py-2 border border-neutral-200 rounded text-sm focus:outline-none focus:border-neutral-400",
|
|
1573
|
+
placeholder: "tag1, tag2, tag3"
|
|
1574
|
+
}, undefined, false, undefined, this),
|
|
1575
|
+
/* @__PURE__ */ jsxDEV6("p", {
|
|
1576
|
+
className: "text-xs text-neutral-400 mt-1",
|
|
1577
|
+
children: t("blogTagsHelp")
|
|
1578
|
+
}, undefined, false, undefined, this)
|
|
1579
|
+
]
|
|
1580
|
+
}, undefined, true, undefined, this),
|
|
1581
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1582
|
+
children: [
|
|
1583
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
1584
|
+
className: "block text-sm font-medium text-neutral-700 mb-1",
|
|
1585
|
+
children: t("blogCoverImage")
|
|
1586
|
+
}, undefined, false, undefined, this),
|
|
1587
|
+
coverImage ? /* @__PURE__ */ jsxDEV6("div", {
|
|
1588
|
+
className: "relative group",
|
|
1589
|
+
children: [
|
|
1590
|
+
/* @__PURE__ */ jsxDEV6("img", {
|
|
1591
|
+
src: coverImage,
|
|
1592
|
+
alt: "Cover",
|
|
1593
|
+
className: "w-full h-32 object-cover border border-neutral-200"
|
|
1594
|
+
}, undefined, false, undefined, this),
|
|
1595
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1596
|
+
className: "absolute inset-0 flex items-center justify-center gap-4 opacity-0 group-hover:opacity-100 transition-opacity bg-black/50",
|
|
1597
|
+
children: [
|
|
1598
|
+
/* @__PURE__ */ jsxDEV6("button", {
|
|
1599
|
+
type: "button",
|
|
1600
|
+
onClick: openImagePickerForCover,
|
|
1601
|
+
className: "text-xs text-white border-b border-white",
|
|
1602
|
+
children: t("replace")
|
|
1603
|
+
}, undefined, false, undefined, this),
|
|
1604
|
+
/* @__PURE__ */ jsxDEV6("button", {
|
|
1605
|
+
type: "button",
|
|
1606
|
+
onClick: () => setCoverImage(""),
|
|
1607
|
+
className: "text-xs text-white/70 hover:text-white",
|
|
1608
|
+
children: t("remove")
|
|
1609
|
+
}, undefined, false, undefined, this)
|
|
1610
|
+
]
|
|
1611
|
+
}, undefined, true, undefined, this)
|
|
1612
|
+
]
|
|
1613
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV6("button", {
|
|
1614
|
+
type: "button",
|
|
1615
|
+
onClick: openImagePickerForCover,
|
|
1616
|
+
className: "w-full h-24 border border-dashed border-neutral-200 flex items-center justify-center text-neutral-400 text-sm hover:bg-neutral-50 transition-colors",
|
|
1617
|
+
children: t("clickToSelectImage")
|
|
1618
|
+
}, undefined, false, undefined, this)
|
|
1619
|
+
]
|
|
1620
|
+
}, undefined, true, undefined, this),
|
|
1621
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1622
|
+
className: "flex-1",
|
|
1623
|
+
children: [
|
|
1624
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
1625
|
+
className: "block text-sm font-medium text-neutral-700 mb-1",
|
|
1626
|
+
children: t("blogContent")
|
|
1627
|
+
}, undefined, false, undefined, this),
|
|
1628
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1629
|
+
className: `border rounded overflow-hidden transition-colors ${isDragging ? "border-blue-400 bg-blue-50" : "border-neutral-200"}`,
|
|
1630
|
+
onDragOver: handleDragOver,
|
|
1631
|
+
onDragLeave: handleDragLeave,
|
|
1632
|
+
onDrop: handleDrop,
|
|
1633
|
+
children: [
|
|
1634
|
+
/* @__PURE__ */ jsxDEV6(MarkdownToolbar, {
|
|
1635
|
+
textareaRef: contentTextareaRef,
|
|
1636
|
+
content,
|
|
1637
|
+
setContent,
|
|
1638
|
+
onImageClick: openImagePickerForContent,
|
|
1639
|
+
t
|
|
1640
|
+
}, undefined, false, undefined, this),
|
|
1641
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1642
|
+
className: "relative",
|
|
1643
|
+
children: [
|
|
1644
|
+
/* @__PURE__ */ jsxDEV6("textarea", {
|
|
1645
|
+
ref: contentTextareaRef,
|
|
1646
|
+
value: content,
|
|
1647
|
+
onChange: (e) => setContent(e.target.value),
|
|
1648
|
+
rows: 12,
|
|
1649
|
+
className: "w-full px-3 py-2 text-sm font-mono focus:outline-none resize-none border-0",
|
|
1650
|
+
placeholder: "Write your post in Markdown..."
|
|
1651
|
+
}, undefined, false, undefined, this),
|
|
1652
|
+
isDragging && /* @__PURE__ */ jsxDEV6("div", {
|
|
1653
|
+
className: "absolute inset-0 flex items-center justify-center bg-blue-50/90 pointer-events-none",
|
|
1654
|
+
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
1655
|
+
className: "text-blue-600 text-sm font-medium",
|
|
1656
|
+
children: t("blogDropImageHere")
|
|
1657
|
+
}, undefined, false, undefined, this)
|
|
1658
|
+
}, undefined, false, undefined, this)
|
|
1659
|
+
]
|
|
1660
|
+
}, undefined, true, undefined, this)
|
|
1661
|
+
]
|
|
1662
|
+
}, undefined, true, undefined, this)
|
|
1663
|
+
]
|
|
1664
|
+
}, undefined, true, undefined, this),
|
|
1665
|
+
post && /* @__PURE__ */ jsxDEV6("div", {
|
|
1666
|
+
className: "pt-4 border-t border-neutral-200",
|
|
1667
|
+
children: /* @__PURE__ */ jsxDEV6("button", {
|
|
1668
|
+
onClick: handleDelete,
|
|
1669
|
+
disabled: isSaving || isDeleting,
|
|
1670
|
+
className: "text-sm text-red-600 hover:text-red-700 disabled:opacity-50",
|
|
1671
|
+
children: isDeleting ? t("blogDeleting") : t("blogDelete")
|
|
1672
|
+
}, undefined, false, undefined, this)
|
|
1673
|
+
}, undefined, false, undefined, this)
|
|
1674
|
+
]
|
|
1675
|
+
}, undefined, true, undefined, this)
|
|
1676
|
+
]
|
|
1677
|
+
}, undefined, true, undefined, this),
|
|
1678
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1679
|
+
className: "w-1/2 flex flex-col bg-neutral-50 overflow-hidden",
|
|
1680
|
+
children: [
|
|
1681
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1682
|
+
className: "px-4 py-2 border-b border-neutral-100 text-xs font-medium text-neutral-500 uppercase tracking-wider",
|
|
1683
|
+
children: t("blogPreview")
|
|
1684
|
+
}, undefined, false, undefined, this),
|
|
1685
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1686
|
+
className: "flex-1 overflow-y-auto",
|
|
1687
|
+
children: /* @__PURE__ */ jsxDEV6(MarkdownPreview, {
|
|
1688
|
+
content,
|
|
1689
|
+
t
|
|
1690
|
+
}, undefined, false, undefined, this)
|
|
1691
|
+
}, undefined, false, undefined, this)
|
|
1692
|
+
]
|
|
1693
|
+
}, undefined, true, undefined, this)
|
|
1694
|
+
]
|
|
1695
|
+
}, undefined, true, undefined, this),
|
|
1696
|
+
showImagePicker && /* @__PURE__ */ jsxDEV6(CmsImagePickerModal, {
|
|
1697
|
+
storage,
|
|
1698
|
+
onSelect: handleImageSelect,
|
|
1699
|
+
onClose: () => setShowImagePicker(false),
|
|
1700
|
+
currentValue: imagePickerTarget === "cover" ? coverImage : undefined
|
|
1701
|
+
}, undefined, false, undefined, this)
|
|
1702
|
+
]
|
|
1703
|
+
}, undefined, true, undefined, this);
|
|
1704
|
+
}
|
|
1705
|
+
function CmsBlogAdmin({
|
|
1706
|
+
postsEndpoint = "/api/admin/blog/posts",
|
|
1707
|
+
defaultAuthorName = "Admin",
|
|
1708
|
+
storage
|
|
1709
|
+
}) {
|
|
1710
|
+
const langContext = useCmsLanguageOptional();
|
|
1711
|
+
const t = langContext?.t ?? ((key) => {
|
|
1712
|
+
const fallback = {
|
|
1713
|
+
blogPosts: "Blog Posts",
|
|
1714
|
+
blogNewPost: "New Post",
|
|
1715
|
+
blogNoPostsYet: "No posts yet",
|
|
1716
|
+
blogCreateFirstPost: "Create your first blog post to get started.",
|
|
1717
|
+
blogUntitled: "Untitled",
|
|
1718
|
+
blogDraft: "Draft",
|
|
1719
|
+
blogPublished: "Published",
|
|
1720
|
+
blogTitle: "Title",
|
|
1721
|
+
blogSlug: "Slug",
|
|
1722
|
+
blogExcerpt: "Excerpt",
|
|
1723
|
+
blogCoverImage: "Cover Image",
|
|
1724
|
+
blogContent: "Content",
|
|
1725
|
+
blogTags: "Tags",
|
|
1726
|
+
blogAuthor: "Author",
|
|
1727
|
+
blogSaveDraft: "Save Draft",
|
|
1728
|
+
blogPublish: "Publish",
|
|
1729
|
+
blogUnpublish: "Unpublish",
|
|
1730
|
+
blogUpdate: "Update",
|
|
1731
|
+
blogDelete: "Delete",
|
|
1732
|
+
blogPreview: "Preview",
|
|
1733
|
+
blogEditor: "Editor",
|
|
1734
|
+
blogPreviewPlaceholder: "Start typing to see preview...",
|
|
1735
|
+
blogSaving: "Saving...",
|
|
1736
|
+
blogDeleting: "Deleting...",
|
|
1737
|
+
blogConfirmDelete: "Are you sure you want to delete this post?",
|
|
1738
|
+
blogTagsHelp: "Comma-separated tags",
|
|
1739
|
+
blogBackToList: "Back to posts",
|
|
1740
|
+
blogInsertImage: "Insert image",
|
|
1741
|
+
blogDropImageHere: "Drop image here...",
|
|
1742
|
+
clickToSelectImage: "Click to select image",
|
|
1743
|
+
replace: "Replace",
|
|
1744
|
+
remove: "Remove",
|
|
1745
|
+
loading: "Loading..."
|
|
1746
|
+
};
|
|
1747
|
+
return fallback[key] ?? key;
|
|
1748
|
+
});
|
|
1749
|
+
const [posts, setPosts] = React3.useState([]);
|
|
1750
|
+
const [selectedPost, setSelectedPost] = React3.useState(null);
|
|
1751
|
+
const [isCreating, setIsCreating] = React3.useState(false);
|
|
1752
|
+
const [isLoading, setIsLoading] = React3.useState(true);
|
|
1753
|
+
const [error, setError] = React3.useState(null);
|
|
1754
|
+
const isEditorOpen = selectedPost !== null || isCreating;
|
|
1755
|
+
React3.useEffect(() => {
|
|
1756
|
+
loadPosts();
|
|
1757
|
+
}, [postsEndpoint]);
|
|
1758
|
+
async function loadPosts() {
|
|
1759
|
+
setIsLoading(true);
|
|
1760
|
+
setError(null);
|
|
1761
|
+
try {
|
|
1762
|
+
const res = await fetch(postsEndpoint);
|
|
1763
|
+
if (!res.ok)
|
|
1764
|
+
throw new Error("Failed to load posts");
|
|
1765
|
+
const data = await res.json();
|
|
1766
|
+
setPosts(data.posts ?? []);
|
|
1767
|
+
} catch (err) {
|
|
1768
|
+
setError(err instanceof Error ? err.message : "Failed to load posts");
|
|
1769
|
+
} finally {
|
|
1770
|
+
setIsLoading(false);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
function handleNewPost() {
|
|
1774
|
+
setSelectedPost(null);
|
|
1775
|
+
setIsCreating(true);
|
|
1776
|
+
}
|
|
1777
|
+
function handleSelectPost(post) {
|
|
1778
|
+
setSelectedPost(post);
|
|
1779
|
+
setIsCreating(false);
|
|
1780
|
+
}
|
|
1781
|
+
function handleBack() {
|
|
1782
|
+
setSelectedPost(null);
|
|
1783
|
+
setIsCreating(false);
|
|
1784
|
+
}
|
|
1785
|
+
async function handleSave(data, publish) {
|
|
1786
|
+
const postData = {
|
|
1787
|
+
...data,
|
|
1788
|
+
status: publish ? "published" : selectedPost?.status === "published" ? "published" : "draft"
|
|
1789
|
+
};
|
|
1790
|
+
try {
|
|
1791
|
+
if (selectedPost) {
|
|
1792
|
+
const res = await fetch(`${postsEndpoint}/${selectedPost.id}`, {
|
|
1793
|
+
method: "PATCH",
|
|
1794
|
+
headers: { "Content-Type": "application/json" },
|
|
1795
|
+
body: JSON.stringify(postData)
|
|
1796
|
+
});
|
|
1797
|
+
if (!res.ok) {
|
|
1798
|
+
const errorData = await res.json();
|
|
1799
|
+
throw new Error(errorData.error ?? "Failed to update post");
|
|
1800
|
+
}
|
|
1801
|
+
const result = await res.json();
|
|
1802
|
+
setSelectedPost(result.post);
|
|
1803
|
+
setPosts((prev) => prev.map((p) => p.id === result.post.id ? result.post : p));
|
|
1804
|
+
return result.post;
|
|
1805
|
+
} else {
|
|
1806
|
+
const res = await fetch(postsEndpoint, {
|
|
1807
|
+
method: "POST",
|
|
1808
|
+
headers: { "Content-Type": "application/json" },
|
|
1809
|
+
body: JSON.stringify({
|
|
1810
|
+
id: generateId(),
|
|
1811
|
+
...postData
|
|
1812
|
+
})
|
|
1813
|
+
});
|
|
1814
|
+
if (!res.ok) {
|
|
1815
|
+
const errorData = await res.json();
|
|
1816
|
+
throw new Error(errorData.error ?? "Failed to create post");
|
|
1817
|
+
}
|
|
1818
|
+
const result = await res.json();
|
|
1819
|
+
setSelectedPost(result.post);
|
|
1820
|
+
setIsCreating(false);
|
|
1821
|
+
setPosts((prev) => [result.post, ...prev]);
|
|
1822
|
+
return result.post;
|
|
1823
|
+
}
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
setError(err instanceof Error ? err.message : "Failed to save post");
|
|
1826
|
+
return null;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
async function handleDelete(id) {
|
|
1830
|
+
try {
|
|
1831
|
+
const res = await fetch(`${postsEndpoint}/${id}`, {
|
|
1832
|
+
method: "DELETE"
|
|
1833
|
+
});
|
|
1834
|
+
if (!res.ok)
|
|
1835
|
+
throw new Error("Failed to delete post");
|
|
1836
|
+
setPosts((prev) => prev.filter((p) => p.id !== id));
|
|
1837
|
+
return true;
|
|
1838
|
+
} catch (err) {
|
|
1839
|
+
setError(err instanceof Error ? err.message : "Failed to delete post");
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (isEditorOpen) {
|
|
1844
|
+
return /* @__PURE__ */ jsxDEV6(BlogEditorView, {
|
|
1845
|
+
post: selectedPost,
|
|
1846
|
+
onBack: handleBack,
|
|
1847
|
+
onSave: handleSave,
|
|
1848
|
+
onDelete: handleDelete,
|
|
1849
|
+
storage,
|
|
1850
|
+
defaultAuthorName,
|
|
1851
|
+
t
|
|
1852
|
+
}, undefined, false, undefined, this);
|
|
1853
|
+
}
|
|
1854
|
+
return /* @__PURE__ */ jsxDEV6(Fragment2, {
|
|
1855
|
+
children: [
|
|
1856
|
+
error && /* @__PURE__ */ jsxDEV6("div", {
|
|
1857
|
+
className: "px-8 py-2 bg-red-50 text-red-600 text-sm",
|
|
1858
|
+
children: error
|
|
1859
|
+
}, undefined, false, undefined, this),
|
|
1860
|
+
/* @__PURE__ */ jsxDEV6(BlogListView, {
|
|
1861
|
+
posts,
|
|
1862
|
+
isLoading,
|
|
1863
|
+
onSelectPost: handleSelectPost,
|
|
1864
|
+
onNewPost: handleNewPost,
|
|
1865
|
+
t
|
|
1866
|
+
}, undefined, false, undefined, this)
|
|
1867
|
+
]
|
|
1868
|
+
}, undefined, true, undefined, this);
|
|
1869
|
+
}
|
|
442
1870
|
|
|
443
1871
|
// src/admin/exports.ts
|
|
444
1872
|
export {
|
|
1873
|
+
useCmsLanguageOptional,
|
|
1874
|
+
useCmsLanguage,
|
|
1875
|
+
translate,
|
|
445
1876
|
getPageBySlug,
|
|
446
1877
|
getNavPages,
|
|
447
1878
|
getCmsConfig,
|
|
1879
|
+
detectBrowserLanguage,
|
|
448
1880
|
definePage,
|
|
449
1881
|
createCmsConfig,
|
|
1882
|
+
CmsModuleIcons,
|
|
1883
|
+
CmsLanguageSelector,
|
|
1884
|
+
CmsLanguageProvider,
|
|
1885
|
+
CmsBlogAdmin,
|
|
450
1886
|
CmsAdminPage,
|
|
451
|
-
CmsAdminLayout
|
|
1887
|
+
CmsAdminLayout,
|
|
1888
|
+
CmsAdminLanding
|
|
452
1889
|
};
|