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.
Files changed (40) hide show
  1. package/README.md +746 -7
  2. package/dist/admin/CmsAdminLanding.d.ts +89 -0
  3. package/dist/admin/CmsAdminLanding.d.ts.map +1 -0
  4. package/dist/admin/CmsAdminLayout.d.ts +26 -3
  5. package/dist/admin/CmsAdminLayout.d.ts.map +1 -1
  6. package/dist/admin/CmsAdminPage.d.ts.map +1 -1
  7. package/dist/admin/CmsBlogAdmin.d.ts +37 -0
  8. package/dist/admin/CmsBlogAdmin.d.ts.map +1 -0
  9. package/dist/admin/config.d.ts +32 -0
  10. package/dist/admin/config.d.ts.map +1 -1
  11. package/dist/admin/config.js +16 -0
  12. package/dist/admin/exports.d.ts +5 -2
  13. package/dist/admin/exports.d.ts.map +1 -1
  14. package/dist/admin/exports.js +1467 -30
  15. package/dist/admin/language.d.ts +53 -0
  16. package/dist/admin/language.d.ts.map +1 -0
  17. package/dist/components/CmsAutoForm.d.ts.map +1 -1
  18. package/dist/components/CmsForm.d.ts.map +1 -1
  19. package/dist/components/CmsImageField.d.ts +2 -1
  20. package/dist/components/CmsImageField.d.ts.map +1 -1
  21. package/dist/components/CmsImagePickerModal.d.ts +21 -0
  22. package/dist/components/CmsImagePickerModal.d.ts.map +1 -0
  23. package/dist/components/CmsSimpleForm.d.ts.map +1 -1
  24. package/dist/components/index.d.ts +1 -0
  25. package/dist/components/index.d.ts.map +1 -1
  26. package/dist/components/index.js +51 -190
  27. package/dist/index-c9btr14k.js +4422 -0
  28. package/dist/index-szreq4v9.js +12 -0
  29. package/dist/index-wmd953zf.js +11423 -0
  30. package/dist/index.js +6 -2
  31. package/dist/schema/index.js +2 -0
  32. package/dist/server/index.d.ts +301 -0
  33. package/dist/server/index.d.ts.map +1 -1
  34. package/dist/server/index.js +2585 -1
  35. package/dist/storage/index.js +2 -0
  36. package/package.json +13 -3
  37. package/dist/domain/index.d.ts +0 -1
  38. package/dist/domain/index.d.ts.map +0 -1
  39. package/dist/stores/index.d.ts +0 -1
  40. package/dist/stores/index.d.ts.map +0 -1
@@ -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-pmb5m3ek.js";
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 CmsAdminLayout({
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
- pages,
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: groupedPages.map((group) => /* @__PURE__ */ jsxDEV2(NavSection, {
85
- group,
86
- basePath,
87
- currentSlug
88
- }, group.name ?? "__default", false, undefined, this))
89
- }, undefined, false, undefined, this),
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: "View site"
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: "Sign out"
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: pages.map((page) => /* @__PURE__ */ jsxDEV2("a", {
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 React from "react";
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 = "Save",
160
- submitPendingText = "Saving...",
161
- successMessage = "Changes saved",
247
+ submitText,
248
+ submitPendingText,
249
+ successMessage,
162
250
  storage
163
251
  }) {
164
- const [state, setState] = React.useState({
252
+ const [state, setState] = React2.useState({
165
253
  success: false
166
254
  });
167
- const [isPending, setIsPending] = React.useState(false);
168
- const [showSuccess, setShowSuccess] = React.useState(false);
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 = React.useMemo(() => {
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
- React.useEffect(() => {
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: successMessage
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: "Reset"
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 ? submitPendingText : submitText,
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: successMessage ?? "Changes saved successfully.",
434
- submitText: submitText ?? "Save",
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 = `![${altText}](${url})`;
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
  };