@ampless/admin 0.2.0-alpha.1 → 0.2.0-alpha.3

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.
@@ -1,9 +1,13 @@
1
+ 'use client';
1
2
  import {
2
3
  ADMIN_SITE_COOKIE,
3
4
  publicMediaUrl,
4
- readAdminSiteIdFromCookie,
5
+ setAdminMediaContext,
5
6
  translate
6
- } from "./chunk-TJR3ALRJ.js";
7
+ } from "./chunk-FI7CM4LH.js";
8
+ import {
9
+ invalidateSiteSettingsCache
10
+ } from "./chunk-VXEVLHGL.js";
7
11
 
8
12
  // src/components/i18n-provider.tsx
9
13
  import { createContext, useContext, useMemo } from "react";
@@ -28,6 +32,409 @@ function useLocale() {
28
32
  return ctx.locale;
29
33
  }
30
34
 
35
+ // src/lib/admin-site-client.ts
36
+ import { DEFAULT_SITE_ID, isMultiSite } from "ampless";
37
+ var cmsConfig = null;
38
+ function setAdminCmsConfig(config) {
39
+ cmsConfig = config;
40
+ }
41
+ function readAdminSiteIdFromCookie() {
42
+ if (!cmsConfig) return DEFAULT_SITE_ID;
43
+ if (!isMultiSite(cmsConfig)) return DEFAULT_SITE_ID;
44
+ const sites = cmsConfig.sites ?? {};
45
+ if (typeof document !== "undefined") {
46
+ const match = document.cookie.match(
47
+ new RegExp(`(?:^|;\\s*)${ADMIN_SITE_COOKIE}=([^;]+)`)
48
+ );
49
+ if (match) {
50
+ const v = decodeURIComponent(match[1]);
51
+ if (sites[v]) return v;
52
+ }
53
+ }
54
+ const first = Object.keys(sites)[0];
55
+ return first ?? DEFAULT_SITE_ID;
56
+ }
57
+
58
+ // src/components/admin-providers.tsx
59
+ import { useEffect } from "react";
60
+
61
+ // src/lib/amplify-client.ts
62
+ import { Amplify } from "aws-amplify";
63
+ var configured = false;
64
+ function configureAmplify(outputs) {
65
+ if (configured) return;
66
+ Amplify.configure(outputs, { ssr: true });
67
+ configured = true;
68
+ }
69
+
70
+ // src/lib/posts-provider.ts
71
+ import { generateClient } from "aws-amplify/api";
72
+ import {
73
+ setPostsProvider,
74
+ composeSiteIdStatus,
75
+ composeSiteIdSlug
76
+ } from "ampless";
77
+ function encodeBody(value) {
78
+ if (typeof value === "string") return value;
79
+ return JSON.stringify(value ?? null);
80
+ }
81
+ function decodeBody(value) {
82
+ if (typeof value !== "string") return value;
83
+ try {
84
+ return JSON.parse(value);
85
+ } catch {
86
+ return value;
87
+ }
88
+ }
89
+ function toCorePost(p) {
90
+ return {
91
+ postId: p.postId,
92
+ siteId: p.siteId,
93
+ slug: p.slug,
94
+ title: p.title,
95
+ excerpt: p.excerpt ?? void 0,
96
+ format: p.format ?? "markdown",
97
+ body: decodeBody(p.body),
98
+ status: p.status ?? "draft",
99
+ publishedAt: p.publishedAt ?? void 0,
100
+ tags: (p.tags ?? []).filter((t) => typeof t === "string")
101
+ };
102
+ }
103
+ function postTagEntries(post) {
104
+ if (post.status !== "published" || !post.publishedAt || !post.tags?.length) return [];
105
+ return post.tags.map((tag) => ({
106
+ siteIdTag: `${post.siteId}#${tag}`,
107
+ publishedAtPostId: `${post.publishedAt}#${post.postId}`
108
+ }));
109
+ }
110
+ function entryKey(e) {
111
+ return `${e.siteIdTag}|${e.publishedAtPostId}`;
112
+ }
113
+ var installed = false;
114
+ function installAdminPostsProvider() {
115
+ if (installed) return;
116
+ installed = true;
117
+ const client = generateClient();
118
+ async function syncPostTags(post, oldPost) {
119
+ const oldEntries = oldPost ? postTagEntries(oldPost) : [];
120
+ const newEntries = postTagEntries(post);
121
+ const oldKeys = new Set(oldEntries.map(entryKey));
122
+ const newKeys = new Set(newEntries.map(entryKey));
123
+ await Promise.all(
124
+ oldEntries.filter((e) => !newKeys.has(entryKey(e))).map((e) => client.models.PostTag.delete(e))
125
+ );
126
+ await Promise.all(
127
+ newEntries.filter((e) => !oldKeys.has(entryKey(e))).map(
128
+ (e) => client.models.PostTag.create({
129
+ siteIdTag: e.siteIdTag,
130
+ publishedAtPostId: e.publishedAtPostId,
131
+ siteId: post.siteId,
132
+ tag: e.siteIdTag.slice(post.siteId.length + 1),
133
+ postId: post.postId,
134
+ publishedAt: post.publishedAt,
135
+ slug: post.slug,
136
+ title: post.title,
137
+ excerpt: post.excerpt,
138
+ tags: post.tags ?? []
139
+ })
140
+ )
141
+ );
142
+ await Promise.all(
143
+ newEntries.filter((e) => oldKeys.has(entryKey(e))).map(
144
+ (e) => client.models.PostTag.update({
145
+ siteIdTag: e.siteIdTag,
146
+ publishedAtPostId: e.publishedAtPostId,
147
+ slug: post.slug,
148
+ title: post.title,
149
+ excerpt: post.excerpt,
150
+ tags: post.tags ?? []
151
+ })
152
+ )
153
+ );
154
+ }
155
+ const provider = {
156
+ async list(opts = {}) {
157
+ const siteId = opts.siteId ?? "default";
158
+ const status = opts.status ?? "published";
159
+ const filter = { siteId: { eq: siteId } };
160
+ if (status !== "all") filter.status = { eq: status };
161
+ const { data } = await client.models.Post.list({ filter, limit: opts.limit ?? 100 });
162
+ return data.map(toCorePost);
163
+ },
164
+ async get(slug, opts = {}) {
165
+ const siteId = opts.siteId ?? "default";
166
+ const { data } = await client.models.Post.list({
167
+ filter: { siteId: { eq: siteId }, slug: { eq: slug } },
168
+ limit: 1
169
+ });
170
+ return data[0] ? toCorePost(data[0]) : null;
171
+ },
172
+ async getById(postId, opts = {}) {
173
+ const siteId = opts.siteId ?? "default";
174
+ const { data } = await client.models.Post.get({ siteId, postId });
175
+ return data ? toCorePost(data) : null;
176
+ },
177
+ async create(input) {
178
+ const postId = input.postId ?? `post-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
179
+ const { data, errors } = await client.models.Post.create({
180
+ siteId: input.siteId,
181
+ postId,
182
+ slug: input.slug,
183
+ title: input.title,
184
+ excerpt: input.excerpt,
185
+ format: input.format,
186
+ body: encodeBody(input.body),
187
+ status: input.status,
188
+ publishedAt: input.publishedAt,
189
+ tags: input.tags,
190
+ // Denormalized GSI keys. Must match every change to slug /
191
+ // status — see the update() branch below.
192
+ siteIdStatus: composeSiteIdStatus(input.siteId, input.status),
193
+ siteIdSlug: composeSiteIdSlug(input.siteId, input.slug)
194
+ });
195
+ if (errors || !data) throw new Error(errors?.[0]?.message ?? "Failed to create post");
196
+ const created = toCorePost(data);
197
+ await syncPostTags(created, null);
198
+ return created;
199
+ },
200
+ async update(postId, patch, opts = {}) {
201
+ const siteId = opts.siteId ?? "default";
202
+ const oldPost = await this.getById(postId, { siteId });
203
+ const nextStatus = patch.status ?? oldPost?.status;
204
+ const nextSlug = patch.slug ?? oldPost?.slug;
205
+ const { data, errors } = await client.models.Post.update({
206
+ siteId,
207
+ postId,
208
+ ...patch.slug !== void 0 && { slug: patch.slug },
209
+ ...patch.title !== void 0 && { title: patch.title },
210
+ ...patch.excerpt !== void 0 && { excerpt: patch.excerpt },
211
+ ...patch.format !== void 0 && { format: patch.format },
212
+ ...patch.body !== void 0 && { body: encodeBody(patch.body) },
213
+ ...patch.status !== void 0 && { status: patch.status },
214
+ ...patch.publishedAt !== void 0 && { publishedAt: patch.publishedAt },
215
+ ...patch.tags !== void 0 && { tags: patch.tags },
216
+ ...patch.status !== void 0 && nextStatus && { siteIdStatus: composeSiteIdStatus(siteId, nextStatus) },
217
+ ...patch.slug !== void 0 && nextSlug && { siteIdSlug: composeSiteIdSlug(siteId, nextSlug) }
218
+ });
219
+ if (errors || !data) throw new Error(errors?.[0]?.message ?? "Failed to update post");
220
+ const updated = toCorePost(data);
221
+ await syncPostTags(updated, oldPost);
222
+ return updated;
223
+ },
224
+ async remove(postId, opts = {}) {
225
+ const siteId = opts.siteId ?? "default";
226
+ const oldPost = await this.getById(postId, { siteId });
227
+ if (oldPost) {
228
+ await syncPostTags({ ...oldPost, status: "draft" }, oldPost);
229
+ }
230
+ const { errors } = await client.models.Post.delete({ siteId, postId });
231
+ if (errors) throw new Error(errors[0]?.message ?? "Failed to delete post");
232
+ }
233
+ };
234
+ setPostsProvider(provider);
235
+ }
236
+
237
+ // src/lib/kv-provider.ts
238
+ import { generateClient as generateClient2 } from "aws-amplify/api";
239
+ import { setKvStore } from "ampless";
240
+ var installed2 = false;
241
+ function installAdminKvProvider() {
242
+ if (installed2) return;
243
+ installed2 = true;
244
+ const client = generateClient2();
245
+ function requireModel() {
246
+ const m = client.models.KvStore;
247
+ if (!m) {
248
+ throw new Error(
249
+ "KvStore model is not available on the AppSync client. Did you redeploy the sandbox? Run `npx ampx sandbox` and wait for it to finish, then reload this page."
250
+ );
251
+ }
252
+ return m;
253
+ }
254
+ function encodeValue(value) {
255
+ return JSON.stringify(value ?? null);
256
+ }
257
+ function decodeValue(raw) {
258
+ if (typeof raw !== "string") return raw;
259
+ try {
260
+ return JSON.parse(raw);
261
+ } catch {
262
+ return raw;
263
+ }
264
+ }
265
+ const store = {
266
+ async get(pk, sk) {
267
+ const model = requireModel();
268
+ const { data } = await model.get({ pk, sk });
269
+ return data ? decodeValue(data.value) : null;
270
+ },
271
+ async query(pk) {
272
+ const model = requireModel();
273
+ const { data } = await model.list({
274
+ filter: { pk: { eq: pk } },
275
+ limit: 1e3
276
+ });
277
+ return (data ?? []).map((row) => ({
278
+ pk: row.pk,
279
+ sk: row.sk,
280
+ value: decodeValue(row.value),
281
+ ttl: row.ttl ?? void 0
282
+ }));
283
+ },
284
+ async put(pk, sk, value, opts) {
285
+ const model = requireModel();
286
+ const ttl = opts?.ttlSeconds ? Math.floor(Date.now() / 1e3) + opts.ttlSeconds : void 0;
287
+ const existing = await model.get({ pk, sk });
288
+ if (existing.data) {
289
+ const { errors } = await model.update({
290
+ pk,
291
+ sk,
292
+ value: encodeValue(value),
293
+ ttl: ttl ?? null
294
+ });
295
+ if (errors) throw new Error(errors[0]?.message ?? "KvStore.update failed");
296
+ } else {
297
+ const { errors } = await model.create({
298
+ pk,
299
+ sk,
300
+ value: encodeValue(value),
301
+ ttl
302
+ });
303
+ if (errors) throw new Error(errors[0]?.message ?? "KvStore.create failed");
304
+ }
305
+ },
306
+ async remove(pk, sk) {
307
+ const model = requireModel();
308
+ const { errors } = await model.delete({ pk, sk });
309
+ if (errors) throw new Error(errors[0]?.message ?? "KvStore.delete failed");
310
+ }
311
+ };
312
+ setKvStore(store);
313
+ }
314
+
315
+ // src/lib/admin-config-client.ts
316
+ var cmsConfig2 = null;
317
+ function setAdminCmsConfigClient(config) {
318
+ cmsConfig2 = config;
319
+ }
320
+ function getMediaProcessingDefaults() {
321
+ return cmsConfig2?.media?.processing;
322
+ }
323
+
324
+ // src/components/admin-providers.tsx
325
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
326
+ function AdminProviders({ outputs, cmsConfig: cmsConfig3, children }) {
327
+ configureAmplify(outputs);
328
+ setAdminCmsConfig(cmsConfig3);
329
+ setAdminCmsConfigClient(cmsConfig3);
330
+ setAdminMediaContext(outputs, cmsConfig3);
331
+ useEffect(() => {
332
+ installAdminPostsProvider();
333
+ installAdminKvProvider();
334
+ }, []);
335
+ return /* @__PURE__ */ jsx2(Fragment, { children });
336
+ }
337
+
338
+ // src/components/admin-dashboard.tsx
339
+ import { useEffect as useEffect2, useState } from "react";
340
+ import Link from "next/link";
341
+ import { listPosts } from "ampless";
342
+ import { Button, Card, CardContent, CardHeader, CardTitle } from "@ampless/runtime/ui";
343
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
344
+ function AdminDashboard() {
345
+ const t = useT();
346
+ const [posts, setPosts] = useState([]);
347
+ const [loading, setLoading] = useState(true);
348
+ useEffect2(() => {
349
+ listPosts({ status: "all" }).then(setPosts).finally(() => setLoading(false));
350
+ }, []);
351
+ const published = posts.filter((p) => p.status === "published").length;
352
+ const drafts = posts.filter((p) => p.status === "draft").length;
353
+ return /* @__PURE__ */ jsxs("div", { className: "p-8", children: [
354
+ /* @__PURE__ */ jsxs("div", { className: "mb-8 flex items-center justify-between", children: [
355
+ /* @__PURE__ */ jsx3("h1", { className: "text-3xl font-bold", children: t("dashboard.title") }),
356
+ /* @__PURE__ */ jsx3(Button, { asChild: true, children: /* @__PURE__ */ jsx3(Link, { href: "/admin/posts/new", children: t("dashboard.newPost") }) })
357
+ ] }),
358
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-3", children: [
359
+ /* @__PURE__ */ jsxs(Card, { children: [
360
+ /* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.totalPosts") }) }),
361
+ /* @__PURE__ */ jsxs(CardContent, { children: [
362
+ /* @__PURE__ */ jsx3("p", { className: "text-3xl font-bold", children: loading ? "\u2014" : posts.length }),
363
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: t("dashboard.totalLabel") })
364
+ ] })
365
+ ] }),
366
+ /* @__PURE__ */ jsxs(Card, { children: [
367
+ /* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.published") }) }),
368
+ /* @__PURE__ */ jsx3(CardContent, { children: /* @__PURE__ */ jsx3("p", { className: "text-3xl font-bold", children: loading ? "\u2014" : published }) })
369
+ ] }),
370
+ /* @__PURE__ */ jsxs(Card, { children: [
371
+ /* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.drafts") }) }),
372
+ /* @__PURE__ */ jsx3(CardContent, { children: /* @__PURE__ */ jsx3("p", { className: "text-3xl font-bold", children: loading ? "\u2014" : drafts }) })
373
+ ] })
374
+ ] })
375
+ ] });
376
+ }
377
+
378
+ // src/components/posts-list-view.tsx
379
+ import { useEffect as useEffect3, useState as useState2 } from "react";
380
+ import Link2 from "next/link";
381
+ import { listPosts as listPosts2 } from "ampless";
382
+ import {
383
+ Button as Button2,
384
+ Table,
385
+ TableBody,
386
+ TableCell,
387
+ TableHead,
388
+ TableHeader,
389
+ TableRow
390
+ } from "@ampless/runtime/ui";
391
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
392
+ function PostsList() {
393
+ const t = useT();
394
+ const [posts, setPosts] = useState2([]);
395
+ const [loading, setLoading] = useState2(true);
396
+ useEffect3(() => {
397
+ const siteId = readAdminSiteIdFromCookie();
398
+ listPosts2({ status: "all", siteId }).then(setPosts).finally(() => setLoading(false));
399
+ }, []);
400
+ return /* @__PURE__ */ jsxs2("div", { className: "p-8", children: [
401
+ /* @__PURE__ */ jsxs2("div", { className: "mb-8 flex items-center justify-between", children: [
402
+ /* @__PURE__ */ jsx4("h1", { className: "text-3xl font-bold", children: t("posts.list.title") }),
403
+ /* @__PURE__ */ jsx4(Button2, { asChild: true, children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.newButton") }) })
404
+ ] }),
405
+ loading ? /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("common.loading") }) : posts.length === 0 ? /* @__PURE__ */ jsxs2("div", { className: "rounded-md border p-12 text-center", children: [
406
+ /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("posts.list.empty") }),
407
+ /* @__PURE__ */ jsx4(Button2, { asChild: true, className: "mt-4", children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.createFirst") }) })
408
+ ] }) : /* @__PURE__ */ jsx4("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs2(Table, { children: [
409
+ /* @__PURE__ */ jsx4(TableHeader, { children: /* @__PURE__ */ jsxs2(TableRow, { children: [
410
+ /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnTitle") }),
411
+ /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnStatus") }),
412
+ /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnSlug") }),
413
+ /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnUpdated") })
414
+ ] }) }),
415
+ /* @__PURE__ */ jsx4(TableBody, { children: posts.map((post) => /* @__PURE__ */ jsxs2(TableRow, { children: [
416
+ /* @__PURE__ */ jsx4(TableCell, { children: /* @__PURE__ */ jsx4(
417
+ Link2,
418
+ {
419
+ href: `/admin/posts/${post.postId}`,
420
+ className: "font-medium hover:underline",
421
+ children: post.title
422
+ }
423
+ ) }),
424
+ /* @__PURE__ */ jsx4(TableCell, { children: /* @__PURE__ */ jsx4(
425
+ "span",
426
+ {
427
+ className: post.status === "published" ? "inline-block rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-700" : "inline-block rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-700",
428
+ children: t(`common.${post.status}`)
429
+ }
430
+ ) }),
431
+ /* @__PURE__ */ jsx4(TableCell, { className: "font-mono text-xs text-muted-foreground", children: post.slug }),
432
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-sm text-muted-foreground", children: post.publishedAt ? new Date(post.publishedAt).toLocaleDateString() : "\u2014" })
433
+ ] }, post.postId)) })
434
+ ] }) })
435
+ ] });
436
+ }
437
+
31
438
  // src/lib/upload.ts
32
439
  import { uploadData } from "aws-amplify/storage";
33
440
  import { processImage } from "ampless/media";
@@ -49,114 +456,8 @@ async function uploadProcessedImage(file, options) {
49
456
  return { path, url: publicMediaUrl(path) };
50
457
  }
51
458
 
52
- // src/lib/theme-actions.ts
53
- import { updateTag } from "next/cache";
54
- async function invalidateSiteSettingsCache(siteId) {
55
- updateTag(`site-settings:${siteId}`);
56
- }
57
-
58
- // src/components/sidebar.tsx
59
- import Link from "next/link";
60
- import { usePathname } from "next/navigation";
61
- import { signOut } from "aws-amplify/auth";
62
- import { LayoutDashboard, FileText, Image, Globe, LogOut, ExternalLink } from "lucide-react";
63
- import { Button, cn } from "@ampless/runtime/ui";
64
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
65
- var navItems = [
66
- { href: "/admin", key: "sidebar.dashboard", icon: LayoutDashboard },
67
- { href: "/admin/posts", key: "sidebar.posts", icon: FileText },
68
- { href: "/admin/media", key: "sidebar.media", icon: Image },
69
- { href: "/admin/sites", key: "sidebar.sites", icon: Globe }
70
- ];
71
- function Sidebar({
72
- email,
73
- siteSelector
74
- }) {
75
- const pathname = usePathname();
76
- const t = useT();
77
- return /* @__PURE__ */ jsxs("aside", { className: "flex h-screen w-60 flex-col border-r bg-muted/30", children: [
78
- /* @__PURE__ */ jsx2("div", { className: "border-b p-4", children: /* @__PURE__ */ jsx2(Link, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }) }),
79
- siteSelector ? /* @__PURE__ */ jsx2("div", { className: "border-b", children: siteSelector }) : null,
80
- /* @__PURE__ */ jsx2("nav", { className: "flex-1 space-y-1 p-2", children: navItems.map((item) => {
81
- const Icon = item.icon;
82
- const isActive = pathname === item.href || item.href !== "/admin" && pathname?.startsWith(item.href);
83
- return /* @__PURE__ */ jsxs(
84
- Link,
85
- {
86
- href: item.href,
87
- className: cn(
88
- "flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors",
89
- isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
90
- ),
91
- children: [
92
- /* @__PURE__ */ jsx2(Icon, { className: "h-4 w-4" }),
93
- t(item.key)
94
- ]
95
- },
96
- item.href
97
- );
98
- }) }),
99
- /* @__PURE__ */ jsxs("div", { className: "border-t p-2 space-y-1", children: [
100
- /* @__PURE__ */ jsxs(
101
- Link,
102
- {
103
- href: "/",
104
- target: "_blank",
105
- className: "flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground",
106
- children: [
107
- /* @__PURE__ */ jsx2(ExternalLink, { className: "h-4 w-4" }),
108
- t("sidebar.viewSite")
109
- ]
110
- }
111
- ),
112
- /* @__PURE__ */ jsx2("div", { className: "px-3 py-2 text-xs text-muted-foreground truncate", children: email }),
113
- /* @__PURE__ */ jsxs(
114
- Button,
115
- {
116
- variant: "ghost",
117
- size: "sm",
118
- className: "w-full justify-start gap-3",
119
- onClick: async () => {
120
- await signOut();
121
- window.location.href = "/login";
122
- },
123
- children: [
124
- /* @__PURE__ */ jsx2(LogOut, { className: "h-4 w-4" }),
125
- t("sidebar.signOut")
126
- ]
127
- }
128
- )
129
- ] })
130
- ] });
131
- }
132
-
133
- // src/components/site-selector.tsx
134
- import { useRouter } from "next/navigation";
135
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
136
- function SiteSelector({ current, sites }) {
137
- const router = useRouter();
138
- const t = useT();
139
- function onChange(e) {
140
- const next = e.target.value;
141
- document.cookie = `${ADMIN_SITE_COOKIE}=${encodeURIComponent(next)}; Path=/; Max-Age=${60 * 60 * 24 * 365}; SameSite=Lax`;
142
- router.refresh();
143
- }
144
- return /* @__PURE__ */ jsxs2("div", { className: "px-3 py-2", children: [
145
- /* @__PURE__ */ jsx3("label", { className: "block text-xs uppercase tracking-wide text-muted-foreground mb-1", children: t("sites.selector.label") }),
146
- /* @__PURE__ */ jsx3(
147
- "select",
148
- {
149
- value: current,
150
- onChange,
151
- className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
152
- children: sites.map((s) => /* @__PURE__ */ jsx3("option", { value: s.id, children: s.name }, s.id))
153
- }
154
- )
155
- ] });
156
- }
157
-
158
459
  // src/components/image-upload-dialog.tsx
159
- import { useEffect, useRef, useState } from "react";
460
+ import { useEffect as useEffect4, useRef, useState as useState3 } from "react";
160
461
  import ReactCrop, { centerCrop, makeAspectCrop } from "react-image-crop";
161
462
  import "react-image-crop/dist/ReactCrop.css";
162
463
  import { shouldSkipProcessing } from "ampless/media";
@@ -166,11 +467,11 @@ import {
166
467
  DialogDescription,
167
468
  DialogHeader,
168
469
  DialogTitle,
169
- Button as Button2,
470
+ Button as Button3,
170
471
  Input,
171
472
  Label
172
473
  } from "@ampless/runtime/ui";
173
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
474
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
174
475
  var ASPECTS = {
175
476
  free: void 0,
176
477
  "1:1": 1,
@@ -223,18 +524,18 @@ function ImageUploadDialog({
223
524
  const defaultMaxDimension = defaults?.maxDimension ?? 2400;
224
525
  const defaultQuality = defaults?.quality ?? 0.85;
225
526
  const losslessForPng = defaults?.losslessForPng ?? true;
226
- const [original, setOriginal] = useState(false);
227
- const [aspect, setAspect] = useState("free");
228
- const [crop, setCrop] = useState(void 0);
229
- const [percentCrop, setPercentCrop] = useState(null);
230
- const [naturalSize, setNaturalSize] = useState(null);
231
- const [formatChoice, setFormatChoice] = useState("auto");
232
- const [losslessOverride, setLosslessOverride] = useState(null);
233
- const [quality, setQuality] = useState(defaultQuality);
234
- const [maxDimension, setMaxDimension] = useState(defaultMaxDimension);
235
- const [previewUrl, setPreviewUrl] = useState(null);
527
+ const [original, setOriginal] = useState3(false);
528
+ const [aspect, setAspect] = useState3("free");
529
+ const [crop, setCrop] = useState3(void 0);
530
+ const [percentCrop, setPercentCrop] = useState3(null);
531
+ const [naturalSize, setNaturalSize] = useState3(null);
532
+ const [formatChoice, setFormatChoice] = useState3("auto");
533
+ const [losslessOverride, setLosslessOverride] = useState3(null);
534
+ const [quality, setQuality] = useState3(defaultQuality);
535
+ const [maxDimension, setMaxDimension] = useState3(defaultMaxDimension);
536
+ const [previewUrl, setPreviewUrl] = useState3(null);
236
537
  const imgRef = useRef(null);
237
- useEffect(() => {
538
+ useEffect4(() => {
238
539
  setOriginal(false);
239
540
  setAspect("free");
240
541
  setCrop(void 0);
@@ -245,13 +546,13 @@ function ImageUploadDialog({
245
546
  setQuality(defaultQuality);
246
547
  setMaxDimension(defaultMaxDimension);
247
548
  }, [file, defaultQuality, defaultMaxDimension]);
248
- useEffect(() => {
549
+ useEffect4(() => {
249
550
  if (!naturalSize) return;
250
551
  const next = buildInitialCrop(naturalSize.width, naturalSize.height, ASPECTS[aspect]);
251
552
  setCrop(next);
252
553
  setPercentCrop(next);
253
554
  }, [aspect, naturalSize]);
254
- useEffect(() => {
555
+ useEffect4(() => {
255
556
  if (!file) {
256
557
  setPreviewUrl(null);
257
558
  return;
@@ -299,7 +600,7 @@ function ImageUploadDialog({
299
600
  lossless: format === "webp" ? lossless : false
300
601
  });
301
602
  }
302
- return /* @__PURE__ */ jsx4(
603
+ return /* @__PURE__ */ jsx5(
303
604
  Dialog,
304
605
  {
305
606
  open: true,
@@ -309,13 +610,13 @@ function ImageUploadDialog({
309
610
  },
310
611
  children: /* @__PURE__ */ jsxs3(DialogContent, { className: "max-h-[90vh] max-w-4xl overflow-y-auto", children: [
311
612
  /* @__PURE__ */ jsxs3(DialogHeader, { children: [
312
- /* @__PURE__ */ jsx4(DialogTitle, { className: "truncate", children: file.name }),
613
+ /* @__PURE__ */ jsx5(DialogTitle, { className: "truncate", children: file.name }),
313
614
  /* @__PURE__ */ jsxs3(DialogDescription, { children: [
314
615
  remaining > 1 ? t("media.dialog.remaining", { count: remaining }) : `${formatBytes(file.size)} \xB7 ${file.type || "unknown"}`,
315
616
  busy && t("media.dialog.uploading")
316
617
  ] })
317
618
  ] }),
318
- previewUrl && showCropper && /* @__PURE__ */ jsx4("div", { className: "flex items-center justify-center rounded-md bg-black/90 p-2", children: /* @__PURE__ */ jsx4(
619
+ previewUrl && showCropper && /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center rounded-md bg-black/90 p-2", children: /* @__PURE__ */ jsx5(
319
620
  ReactCrop,
320
621
  {
321
622
  crop,
@@ -326,7 +627,7 @@ function ImageUploadDialog({
326
627
  setCrop(percent);
327
628
  setPercentCrop(percent);
328
629
  },
329
- children: /* @__PURE__ */ jsx4(
630
+ children: /* @__PURE__ */ jsx5(
330
631
  "img",
331
632
  {
332
633
  ref: imgRef,
@@ -338,12 +639,12 @@ function ImageUploadDialog({
338
639
  )
339
640
  }
340
641
  ) }),
341
- previewUrl && !showCropper && isImage && /* @__PURE__ */ jsx4("div", { className: "flex h-48 items-center justify-center rounded-md bg-muted", children: /* @__PURE__ */ jsx4("img", { src: previewUrl, alt: "preview", className: "max-h-full max-w-full object-contain" }) }),
642
+ previewUrl && !showCropper && isImage && /* @__PURE__ */ jsx5("div", { className: "flex h-48 items-center justify-center rounded-md bg-muted", children: /* @__PURE__ */ jsx5("img", { src: previewUrl, alt: "preview", className: "max-h-full max-w-full object-contain" }) }),
342
643
  !isImage && // Non-image upload: skip the broken-img preview. Show the
343
644
  // file's name / size / mime so the admin can confirm before
344
645
  // committing the bytes to S3.
345
646
  /* @__PURE__ */ jsxs3("div", { className: "flex h-32 flex-col items-center justify-center gap-1 rounded-md bg-muted text-sm text-muted-foreground", children: [
346
- /* @__PURE__ */ jsx4("span", { className: "font-medium", children: file.name }),
647
+ /* @__PURE__ */ jsx5("span", { className: "font-medium", children: file.name }),
347
648
  /* @__PURE__ */ jsxs3("span", { className: "font-mono text-xs", children: [
348
649
  formatBytes(file.size),
349
650
  " \xB7 ",
@@ -352,7 +653,7 @@ function ImageUploadDialog({
352
653
  ] }),
353
654
  /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [
354
655
  /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-2 text-sm", children: [
355
- /* @__PURE__ */ jsx4(
656
+ /* @__PURE__ */ jsx5(
356
657
  "input",
357
658
  {
358
659
  type: "checkbox",
@@ -361,14 +662,14 @@ function ImageUploadDialog({
361
662
  onChange: (e) => setOriginal(e.target.checked)
362
663
  }
363
664
  ),
364
- /* @__PURE__ */ jsx4("span", { children: t("media.dialog.useOriginal") }),
365
- passthrough && /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: t("media.dialog.passthroughNote") })
665
+ /* @__PURE__ */ jsx5("span", { children: t("media.dialog.useOriginal") }),
666
+ passthrough && /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: t("media.dialog.passthroughNote") })
366
667
  ] }),
367
- !original && !passthrough && /* @__PURE__ */ jsxs3(Fragment, { children: [
668
+ !original && !passthrough && /* @__PURE__ */ jsxs3(Fragment2, { children: [
368
669
  /* @__PURE__ */ jsxs3("div", { children: [
369
- /* @__PURE__ */ jsx4(Label, { children: t("media.dialog.aspectRatio") }),
370
- /* @__PURE__ */ jsx4("div", { className: "mt-2 flex flex-wrap gap-2", children: ASPECT_CHOICES.map((choice) => /* @__PURE__ */ jsx4(
371
- Button2,
670
+ /* @__PURE__ */ jsx5(Label, { children: t("media.dialog.aspectRatio") }),
671
+ /* @__PURE__ */ jsx5("div", { className: "mt-2 flex flex-wrap gap-2", children: ASPECT_CHOICES.map((choice) => /* @__PURE__ */ jsx5(
672
+ Button3,
372
673
  {
373
674
  type: "button",
374
675
  variant: aspect === choice ? "default" : "outline",
@@ -381,9 +682,9 @@ function ImageUploadDialog({
381
682
  )) })
382
683
  ] }),
383
684
  /* @__PURE__ */ jsxs3("div", { children: [
384
- /* @__PURE__ */ jsx4(Label, { children: t("media.dialog.outputFormat") }),
385
- /* @__PURE__ */ jsx4("div", { className: "mt-2 flex flex-wrap gap-2", children: FORMAT_CHOICES.map((choice) => /* @__PURE__ */ jsx4(
386
- Button2,
685
+ /* @__PURE__ */ jsx5(Label, { children: t("media.dialog.outputFormat") }),
686
+ /* @__PURE__ */ jsx5("div", { className: "mt-2 flex flex-wrap gap-2", children: FORMAT_CHOICES.map((choice) => /* @__PURE__ */ jsx5(
687
+ Button3,
387
688
  {
388
689
  type: "button",
389
690
  variant: formatChoice === choice ? "default" : "outline",
@@ -399,7 +700,7 @@ function ImageUploadDialog({
399
700
  )) })
400
701
  ] }),
401
702
  showLosslessToggle && /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-2 text-sm", children: [
402
- /* @__PURE__ */ jsx4(
703
+ /* @__PURE__ */ jsx5(
403
704
  "input",
404
705
  {
405
706
  type: "checkbox",
@@ -408,11 +709,11 @@ function ImageUploadDialog({
408
709
  onChange: (e) => setLosslessOverride(e.target.checked)
409
710
  }
410
711
  ),
411
- /* @__PURE__ */ jsx4("span", { children: t("media.dialog.losslessWebp") })
712
+ /* @__PURE__ */ jsx5("span", { children: t("media.dialog.losslessWebp") })
412
713
  ] }),
413
714
  showQualitySlider && /* @__PURE__ */ jsxs3("div", { children: [
414
- /* @__PURE__ */ jsx4(Label, { children: t("media.dialog.quality", { value: Math.round(quality * 100) }) }),
415
- /* @__PURE__ */ jsx4(
715
+ /* @__PURE__ */ jsx5(Label, { children: t("media.dialog.quality", { value: Math.round(quality * 100) }) }),
716
+ /* @__PURE__ */ jsx5(
416
717
  "input",
417
718
  {
418
719
  type: "range",
@@ -427,9 +728,9 @@ function ImageUploadDialog({
427
728
  )
428
729
  ] }),
429
730
  /* @__PURE__ */ jsxs3("div", { className: "max-w-xs", children: [
430
- /* @__PURE__ */ jsx4(Label, { htmlFor: "maxDimension", children: t("media.dialog.maxDimension") }),
431
- /* @__PURE__ */ jsx4("div", { className: "mt-2 flex flex-wrap gap-2", children: MAX_DIMENSION_PRESETS.map((preset) => /* @__PURE__ */ jsx4(
432
- Button2,
731
+ /* @__PURE__ */ jsx5(Label, { htmlFor: "maxDimension", children: t("media.dialog.maxDimension") }),
732
+ /* @__PURE__ */ jsx5("div", { className: "mt-2 flex flex-wrap gap-2", children: MAX_DIMENSION_PRESETS.map((preset) => /* @__PURE__ */ jsx5(
733
+ Button3,
433
734
  {
434
735
  type: "button",
435
736
  variant: maxDimension === preset ? "default" : "outline",
@@ -440,7 +741,7 @@ function ImageUploadDialog({
440
741
  },
441
742
  preset
442
743
  )) }),
443
- /* @__PURE__ */ jsx4(
744
+ /* @__PURE__ */ jsx5(
444
745
  Input,
445
746
  {
446
747
  id: "maxDimension",
@@ -457,9 +758,9 @@ function ImageUploadDialog({
457
758
  ] })
458
759
  ] }),
459
760
  /* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2", children: [
460
- /* @__PURE__ */ jsx4(Button2, { variant: "ghost", type: "button", onClick: onCancel, children: t("media.dialog.cancelAll") }),
461
- /* @__PURE__ */ jsx4(Button2, { variant: "outline", type: "button", disabled: busy, onClick: onSkip, children: t("media.dialog.skip") }),
462
- /* @__PURE__ */ jsx4(Button2, { type: "button", disabled: busy, onClick: handleConfirm, children: busy ? t("media.dialog.uploadingButton") : t("media.dialog.upload") })
761
+ /* @__PURE__ */ jsx5(Button3, { variant: "ghost", type: "button", onClick: onCancel, children: t("media.dialog.cancelAll") }),
762
+ /* @__PURE__ */ jsx5(Button3, { variant: "outline", type: "button", disabled: busy, onClick: onSkip, children: t("media.dialog.skip") }),
763
+ /* @__PURE__ */ jsx5(Button3, { type: "button", disabled: busy, onClick: handleConfirm, children: busy ? t("media.dialog.uploadingButton") : t("media.dialog.upload") })
463
764
  ] })
464
765
  ] })
465
766
  }
@@ -472,7 +773,7 @@ function formatBytes(bytes) {
472
773
  }
473
774
 
474
775
  // src/components/media-picker.tsx
475
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
776
+ import { useEffect as useEffect5, useRef as useRef2, useState as useState4 } from "react";
476
777
  import { list } from "aws-amplify/storage";
477
778
  import { Upload } from "lucide-react";
478
779
  import {
@@ -482,30 +783,19 @@ import {
482
783
  DialogHeader as DialogHeader2,
483
784
  DialogTitle as DialogTitle2,
484
785
  DialogTrigger,
485
- Button as Button3
786
+ Button as Button4
486
787
  } from "@ampless/runtime/ui";
487
-
488
- // src/lib/admin-config-client.ts
489
- var cmsConfig = null;
490
- function setAdminCmsConfigClient(config) {
491
- cmsConfig = config;
492
- }
493
- function getMediaProcessingDefaults() {
494
- return cmsConfig?.media?.processing;
495
- }
496
-
497
- // src/components/media-picker.tsx
498
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
788
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
499
789
  function MediaPicker({ trigger, onSelect }) {
500
790
  const t = useT();
501
- const [open, setOpen] = useState2(false);
502
- const [items, setItems] = useState2([]);
503
- const [loading, setLoading] = useState2(false);
504
- const [error, setError] = useState2(null);
505
- const [pendingUpload, setPendingUpload] = useState2(null);
506
- const [uploading, setUploading] = useState2(false);
791
+ const [open, setOpen] = useState4(false);
792
+ const [items, setItems] = useState4([]);
793
+ const [loading, setLoading] = useState4(false);
794
+ const [error, setError] = useState4(null);
795
+ const [pendingUpload, setPendingUpload] = useState4(null);
796
+ const [uploading, setUploading] = useState4(false);
507
797
  const fileInputRef = useRef2(null);
508
- useEffect2(() => {
798
+ useEffect5(() => {
509
799
  if (!open) return;
510
800
  let cancelled = false;
511
801
  setLoading(true);
@@ -547,30 +837,30 @@ function MediaPicker({ trigger, onSelect }) {
547
837
  }
548
838
  }
549
839
  const pickerOpen = open && !pendingUpload;
550
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
840
+ return /* @__PURE__ */ jsxs4(Fragment3, { children: [
551
841
  /* @__PURE__ */ jsxs4(Dialog2, { open: pickerOpen, onOpenChange: (next) => setOpen(next), children: [
552
- /* @__PURE__ */ jsx5(DialogTrigger, { asChild: true, onClick: () => setOpen(true), children: trigger }),
842
+ /* @__PURE__ */ jsx6(DialogTrigger, { asChild: true, onClick: () => setOpen(true), children: trigger }),
553
843
  /* @__PURE__ */ jsxs4(DialogContent2, { children: [
554
- /* @__PURE__ */ jsx5(DialogHeader2, { children: /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-4", children: [
844
+ /* @__PURE__ */ jsx6(DialogHeader2, { children: /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-4", children: [
555
845
  /* @__PURE__ */ jsxs4("div", { children: [
556
- /* @__PURE__ */ jsx5(DialogTitle2, { children: t("mediaPicker.title") }),
557
- /* @__PURE__ */ jsx5(DialogDescription2, { children: t("mediaPicker.description") })
846
+ /* @__PURE__ */ jsx6(DialogTitle2, { children: t("mediaPicker.title") }),
847
+ /* @__PURE__ */ jsx6(DialogDescription2, { children: t("mediaPicker.description") })
558
848
  ] }),
559
849
  /* @__PURE__ */ jsxs4(
560
- Button3,
850
+ Button4,
561
851
  {
562
852
  type: "button",
563
853
  variant: "outline",
564
854
  size: "sm",
565
855
  onClick: () => fileInputRef.current?.click(),
566
856
  children: [
567
- /* @__PURE__ */ jsx5(Upload, { className: "mr-2 h-3 w-3" }),
857
+ /* @__PURE__ */ jsx6(Upload, { className: "mr-2 h-3 w-3" }),
568
858
  t("mediaPicker.uploadNew")
569
859
  ]
570
860
  }
571
861
  )
572
862
  ] }) }),
573
- /* @__PURE__ */ jsx5(
863
+ /* @__PURE__ */ jsx6(
574
864
  "input",
575
865
  {
576
866
  ref: fileInputRef,
@@ -580,21 +870,21 @@ function MediaPicker({ trigger, onSelect }) {
580
870
  onChange: handleFileSelected
581
871
  }
582
872
  ),
583
- loading && /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground", children: t("common.loading") }),
584
- error && /* @__PURE__ */ jsx5("p", { className: "text-sm text-destructive", children: error }),
873
+ loading && /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: t("common.loading") }),
874
+ error && /* @__PURE__ */ jsx6("p", { className: "text-sm text-destructive", children: error }),
585
875
  !loading && items.length === 0 && /* @__PURE__ */ jsxs4("p", { className: "text-sm text-muted-foreground", children: [
586
876
  t("mediaPicker.empty"),
587
877
  " ",
588
878
  t("mediaPicker.emptyHint", { action: t("mediaPicker.emptyAction") })
589
879
  ] }),
590
- /* @__PURE__ */ jsx5("div", { className: "grid max-h-[60vh] grid-cols-3 gap-3 overflow-auto sm:grid-cols-4", children: items.map((path) => /* @__PURE__ */ jsxs4(
880
+ /* @__PURE__ */ jsx6("div", { className: "grid max-h-[60vh] grid-cols-3 gap-3 overflow-auto sm:grid-cols-4", children: items.map((path) => /* @__PURE__ */ jsxs4(
591
881
  "button",
592
882
  {
593
883
  type: "button",
594
884
  onClick: () => handlePick(path),
595
885
  className: "group overflow-hidden rounded-md border transition hover:border-primary",
596
886
  children: [
597
- /* @__PURE__ */ jsx5(
887
+ /* @__PURE__ */ jsx6(
598
888
  "img",
599
889
  {
600
890
  src: publicMediaUrl(path),
@@ -602,14 +892,14 @@ function MediaPicker({ trigger, onSelect }) {
602
892
  className: "aspect-square w-full object-cover"
603
893
  }
604
894
  ),
605
- /* @__PURE__ */ jsx5("div", { className: "truncate p-1 text-xs text-muted-foreground", children: path.split("/").pop() })
895
+ /* @__PURE__ */ jsx6("div", { className: "truncate p-1 text-xs text-muted-foreground", children: path.split("/").pop() })
606
896
  ]
607
897
  },
608
898
  path
609
899
  )) })
610
900
  ] })
611
901
  ] }),
612
- /* @__PURE__ */ jsx5(
902
+ /* @__PURE__ */ jsx6(
613
903
  ImageUploadDialog,
614
904
  {
615
905
  file: pendingUpload,
@@ -625,8 +915,8 @@ function MediaPicker({ trigger, onSelect }) {
625
915
  }
626
916
 
627
917
  // src/components/post-form.tsx
628
- import { useRef as useRef3, useState as useState3 } from "react";
629
- import { useRouter as useRouter2 } from "next/navigation";
918
+ import { useRef as useRef3, useState as useState5 } from "react";
919
+ import { useRouter } from "next/navigation";
630
920
  import { Image as ImageIcon3 } from "lucide-react";
631
921
  import {
632
922
  createPost,
@@ -641,13 +931,13 @@ import {
641
931
  markdownToHtml,
642
932
  htmlToMarkdown
643
933
  } from "@ampless/runtime";
644
- import { Button as Button6, Input as Input2, Label as Label2, Textarea } from "@ampless/runtime/ui";
934
+ import { Button as Button7, Input as Input2, Label as Label2, Textarea } from "@ampless/runtime/ui";
645
935
 
646
936
  // src/editor/tiptap-editor.tsx
647
937
  import { useEditor, EditorContent } from "@tiptap/react";
648
938
  import StarterKit from "@tiptap/starter-kit";
649
- import Link2 from "@tiptap/extension-link";
650
- import Image2 from "@tiptap/extension-image";
939
+ import Link3 from "@tiptap/extension-link";
940
+ import Image from "@tiptap/extension-image";
651
941
 
652
942
  // src/editor/toolbar.tsx
653
943
  import {
@@ -661,8 +951,8 @@ import {
661
951
  Link as LinkIcon,
662
952
  Image as ImageIcon
663
953
  } from "lucide-react";
664
- import { Button as Button4, cn as cn2 } from "@ampless/runtime/ui";
665
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
954
+ import { Button as Button5, cn } from "@ampless/runtime/ui";
955
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
666
956
  function Toolbar({ editor }) {
667
957
  const t = useT();
668
958
  if (!editor) return null;
@@ -691,35 +981,35 @@ function Toolbar({ editor }) {
691
981
  return /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap gap-1 border-b p-2", children: [
692
982
  tools.map((tool) => {
693
983
  const Icon = tool.icon;
694
- return /* @__PURE__ */ jsx6(
695
- Button4,
984
+ return /* @__PURE__ */ jsx7(
985
+ Button5,
696
986
  {
697
987
  type: "button",
698
988
  variant: "ghost",
699
989
  size: "icon",
700
990
  onClick: tool.action,
701
- className: cn2(tool.isActive() && "bg-accent text-accent-foreground"),
702
- children: /* @__PURE__ */ jsx6(Icon, { className: "h-4 w-4" })
991
+ className: cn(tool.isActive() && "bg-accent text-accent-foreground"),
992
+ children: /* @__PURE__ */ jsx7(Icon, { className: "h-4 w-4" })
703
993
  },
704
994
  tool.name
705
995
  );
706
996
  }),
707
- /* @__PURE__ */ jsx6(
708
- Button4,
997
+ /* @__PURE__ */ jsx7(
998
+ Button5,
709
999
  {
710
1000
  type: "button",
711
1001
  variant: "ghost",
712
1002
  size: "icon",
713
1003
  onClick: setLink,
714
- className: cn2(editor.isActive("link") && "bg-accent text-accent-foreground"),
715
- children: /* @__PURE__ */ jsx6(LinkIcon, { className: "h-4 w-4" })
1004
+ className: cn(editor.isActive("link") && "bg-accent text-accent-foreground"),
1005
+ children: /* @__PURE__ */ jsx7(LinkIcon, { className: "h-4 w-4" })
716
1006
  }
717
1007
  ),
718
- /* @__PURE__ */ jsx6(
1008
+ /* @__PURE__ */ jsx7(
719
1009
  MediaPicker,
720
1010
  {
721
1011
  onSelect: insertImage,
722
- trigger: /* @__PURE__ */ jsx6(Button4, { type: "button", variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx6(ImageIcon, { className: "h-4 w-4" }) })
1012
+ trigger: /* @__PURE__ */ jsx7(Button5, { type: "button", variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx7(ImageIcon, { className: "h-4 w-4" }) })
723
1013
  }
724
1014
  )
725
1015
  ] });
@@ -728,8 +1018,8 @@ function Toolbar({ editor }) {
728
1018
  // src/editor/image-bubble-menu.tsx
729
1019
  import { BubbleMenu } from "@tiptap/react/menus";
730
1020
  import { Trash2, Pencil, ImageIcon as ImageIcon2, Maximize2 } from "lucide-react";
731
- import { Button as Button5, cn as cn3 } from "@ampless/runtime/ui";
732
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1021
+ import { Button as Button6, cn as cn2 } from "@ampless/runtime/ui";
1022
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
733
1023
  function ImageBubbleMenu({ editor }) {
734
1024
  const t = useT();
735
1025
  const editAlt = () => {
@@ -747,7 +1037,7 @@ function ImageBubbleMenu({ editor }) {
747
1037
  editor.chain().focus().updateAttributes("image", { display: next }).run();
748
1038
  };
749
1039
  const currentDisplay = editor.getAttributes("image").display ?? null;
750
- return /* @__PURE__ */ jsx7(
1040
+ return /* @__PURE__ */ jsx8(
751
1041
  BubbleMenu,
752
1042
  {
753
1043
  editor,
@@ -755,42 +1045,42 @@ function ImageBubbleMenu({ editor }) {
755
1045
  options: { placement: "top" },
756
1046
  children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1 rounded-md border bg-popover p-1 shadow", children: [
757
1047
  /* @__PURE__ */ jsxs6(
758
- Button5,
1048
+ Button6,
759
1049
  {
760
1050
  type: "button",
761
1051
  variant: "ghost",
762
1052
  size: "sm",
763
1053
  onClick: () => setDisplay("inline"),
764
- className: cn3(currentDisplay === "inline" && "bg-accent text-accent-foreground"),
1054
+ className: cn2(currentDisplay === "inline" && "bg-accent text-accent-foreground"),
765
1055
  title: t("editor.image.inlineTitle"),
766
1056
  children: [
767
- /* @__PURE__ */ jsx7(ImageIcon2, { className: "mr-1 h-3 w-3" }),
1057
+ /* @__PURE__ */ jsx8(ImageIcon2, { className: "mr-1 h-3 w-3" }),
768
1058
  t("editor.image.inline")
769
1059
  ]
770
1060
  }
771
1061
  ),
772
1062
  /* @__PURE__ */ jsxs6(
773
- Button5,
1063
+ Button6,
774
1064
  {
775
1065
  type: "button",
776
1066
  variant: "ghost",
777
1067
  size: "sm",
778
1068
  onClick: () => setDisplay("lightbox"),
779
- className: cn3(currentDisplay === "lightbox" && "bg-accent text-accent-foreground"),
1069
+ className: cn2(currentDisplay === "lightbox" && "bg-accent text-accent-foreground"),
780
1070
  title: t("editor.image.lightboxTitle"),
781
1071
  children: [
782
- /* @__PURE__ */ jsx7(Maximize2, { className: "mr-1 h-3 w-3" }),
1072
+ /* @__PURE__ */ jsx8(Maximize2, { className: "mr-1 h-3 w-3" }),
783
1073
  t("editor.image.lightbox")
784
1074
  ]
785
1075
  }
786
1076
  ),
787
- /* @__PURE__ */ jsx7("span", { className: "mx-1 h-4 w-px bg-border" }),
788
- /* @__PURE__ */ jsxs6(Button5, { type: "button", variant: "ghost", size: "sm", onClick: editAlt, children: [
789
- /* @__PURE__ */ jsx7(Pencil, { className: "mr-1 h-3 w-3" }),
1077
+ /* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border" }),
1078
+ /* @__PURE__ */ jsxs6(Button6, { type: "button", variant: "ghost", size: "sm", onClick: editAlt, children: [
1079
+ /* @__PURE__ */ jsx8(Pencil, { className: "mr-1 h-3 w-3" }),
790
1080
  t("editor.image.alt")
791
1081
  ] }),
792
- /* @__PURE__ */ jsxs6(Button5, { type: "button", variant: "ghost", size: "sm", onClick: remove2, children: [
793
- /* @__PURE__ */ jsx7(Trash2, { className: "mr-1 h-3 w-3" }),
1082
+ /* @__PURE__ */ jsxs6(Button6, { type: "button", variant: "ghost", size: "sm", onClick: remove2, children: [
1083
+ /* @__PURE__ */ jsx8(Trash2, { className: "mr-1 h-3 w-3" }),
794
1084
  t("editor.image.delete")
795
1085
  ] })
796
1086
  ] })
@@ -799,8 +1089,8 @@ function ImageBubbleMenu({ editor }) {
799
1089
  }
800
1090
 
801
1091
  // src/editor/tiptap-editor.tsx
802
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
803
- var AmplessImage = Image2.extend({
1092
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1093
+ var AmplessImage = Image.extend({
804
1094
  addAttributes() {
805
1095
  return {
806
1096
  ...this.parent?.(),
@@ -819,7 +1109,7 @@ function TiptapEditor({ initialContent, onChange }) {
819
1109
  const editor = useEditor({
820
1110
  extensions: [
821
1111
  StarterKit,
822
- Link2.configure({ openOnClick: false }),
1112
+ Link3.configure({ openOnClick: false }),
823
1113
  AmplessImage.configure({ inline: false, allowBase64: false })
824
1114
  ],
825
1115
  content: initialContent ?? { type: "doc", content: [{ type: "paragraph" }] },
@@ -837,14 +1127,14 @@ function TiptapEditor({ initialContent, onChange }) {
837
1127
  }
838
1128
  });
839
1129
  return /* @__PURE__ */ jsxs7("div", { className: "rounded-md border", children: [
840
- /* @__PURE__ */ jsx8(Toolbar, { editor }),
841
- editor && /* @__PURE__ */ jsx8(ImageBubbleMenu, { editor }),
842
- /* @__PURE__ */ jsx8(EditorContent, { editor })
1130
+ /* @__PURE__ */ jsx9(Toolbar, { editor }),
1131
+ editor && /* @__PURE__ */ jsx9(ImageBubbleMenu, { editor }),
1132
+ /* @__PURE__ */ jsx9(EditorContent, { editor })
843
1133
  ] });
844
1134
  }
845
1135
 
846
1136
  // src/components/post-form.tsx
847
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1137
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
848
1138
  var EMPTY_TIPTAP_DOC = { type: "doc", content: [{ type: "paragraph" }] };
849
1139
  var IMAGE_URL_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)(\?|$)/i;
850
1140
  var STYLESHEET_URL_RE = /\.css(\?|$)/i;
@@ -867,20 +1157,20 @@ function defaultBodyForFormat(format) {
867
1157
  return "";
868
1158
  }
869
1159
  function PostForm({ post }) {
870
- const router = useRouter2();
1160
+ const router = useRouter();
871
1161
  const t = useT();
872
1162
  const isEdit = !!post;
873
1163
  const bodyTextareaRef = useRef3(null);
874
- const [title, setTitle] = useState3(post?.title ?? "");
875
- const [slug, setSlug] = useState3(post?.slug ?? "");
876
- const [excerpt, setExcerpt] = useState3(post?.excerpt ?? "");
877
- const [format, setFormat] = useState3(post?.format ?? "tiptap");
878
- const [body, setBody] = useState3(post?.body ?? EMPTY_TIPTAP_DOC);
879
- const [status, setStatus] = useState3(post?.status ?? "draft");
880
- const [tagsInput, setTagsInput] = useState3((post?.tags ?? []).join(", "));
881
- const [saving, setSaving] = useState3(false);
882
- const [error, setError] = useState3(null);
883
- const [view, setView] = useState3("edit");
1164
+ const [title, setTitle] = useState5(post?.title ?? "");
1165
+ const [slug, setSlug] = useState5(post?.slug ?? "");
1166
+ const [excerpt, setExcerpt] = useState5(post?.excerpt ?? "");
1167
+ const [format, setFormat] = useState5(post?.format ?? "tiptap");
1168
+ const [body, setBody] = useState5(post?.body ?? EMPTY_TIPTAP_DOC);
1169
+ const [status, setStatus] = useState5(post?.status ?? "draft");
1170
+ const [tagsInput, setTagsInput] = useState5((post?.tags ?? []).join(", "));
1171
+ const [saving, setSaving] = useState5(false);
1172
+ const [error, setError] = useState5(null);
1173
+ const [view, setView] = useState5("edit");
884
1174
  function parseTags(raw) {
885
1175
  return Array.from(
886
1176
  new Set(
@@ -1007,7 +1297,7 @@ function PostForm({ post }) {
1007
1297
  };
1008
1298
  return /* @__PURE__ */ jsxs8("form", { onSubmit: save, className: "space-y-6", children: [
1009
1299
  /* @__PURE__ */ jsxs8("div", { className: "flex gap-1 border-b", children: [
1010
- /* @__PURE__ */ jsx9(
1300
+ /* @__PURE__ */ jsx10(
1011
1301
  "button",
1012
1302
  {
1013
1303
  type: "button",
@@ -1017,7 +1307,7 @@ function PostForm({ post }) {
1017
1307
  children: t("posts.form.tabEdit")
1018
1308
  }
1019
1309
  ),
1020
- /* @__PURE__ */ jsx9(
1310
+ /* @__PURE__ */ jsx10(
1021
1311
  "button",
1022
1312
  {
1023
1313
  type: "button",
@@ -1030,22 +1320,22 @@ function PostForm({ post }) {
1030
1320
  ] }),
1031
1321
  view === "preview" && /* @__PURE__ */ jsxs8("article", { className: "space-y-4", children: [
1032
1322
  /* @__PURE__ */ jsxs8("header", { className: "border-b pb-4", children: [
1033
- /* @__PURE__ */ jsx9("h1", { className: "text-3xl font-bold tracking-tight", children: title || /* @__PURE__ */ jsx9("span", { className: "text-muted-foreground italic", children: t("posts.form.previewNoTitle") }) }),
1323
+ /* @__PURE__ */ jsx10("h1", { className: "text-3xl font-bold tracking-tight", children: title || /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground italic", children: t("posts.form.previewNoTitle") }) }),
1034
1324
  /* @__PURE__ */ jsxs8("p", { className: "mt-2 text-sm text-muted-foreground", children: [
1035
- previewPost.publishedAt ? /* @__PURE__ */ jsx9("time", { dateTime: previewPost.publishedAt, children: formatDate(previewPost.publishedAt) }) : /* @__PURE__ */ jsx9("span", { children: t("common.draft") }),
1036
- /* @__PURE__ */ jsx9("span", { className: "mx-2", children: "\xB7" }),
1037
- /* @__PURE__ */ jsx9("span", { className: "font-mono text-xs uppercase", children: format })
1325
+ previewPost.publishedAt ? /* @__PURE__ */ jsx10("time", { dateTime: previewPost.publishedAt, children: formatDate(previewPost.publishedAt) }) : /* @__PURE__ */ jsx10("span", { children: t("common.draft") }),
1326
+ /* @__PURE__ */ jsx10("span", { className: "mx-2", children: "\xB7" }),
1327
+ /* @__PURE__ */ jsx10("span", { className: "font-mono text-xs uppercase", children: format })
1038
1328
  ] }),
1039
- excerpt && /* @__PURE__ */ jsx9("p", { className: "mt-3 text-base text-muted-foreground", children: excerpt })
1329
+ excerpt && /* @__PURE__ */ jsx10("p", { className: "mt-3 text-base text-muted-foreground", children: excerpt })
1040
1330
  ] }),
1041
- /* @__PURE__ */ jsx9(
1331
+ /* @__PURE__ */ jsx10(
1042
1332
  "div",
1043
1333
  {
1044
1334
  className: "prose prose-neutral dark:prose-invert max-w-none",
1045
1335
  dangerouslySetInnerHTML: { __html: renderBody(previewPost) }
1046
1336
  }
1047
1337
  ),
1048
- previewPost.tags && previewPost.tags.length > 0 && /* @__PURE__ */ jsx9("div", { className: "flex flex-wrap gap-2 border-t pt-4 text-sm", children: previewPost.tags.map((tag) => /* @__PURE__ */ jsxs8(
1338
+ previewPost.tags && previewPost.tags.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-2 border-t pt-4 text-sm", children: previewPost.tags.map((tag) => /* @__PURE__ */ jsxs8(
1049
1339
  "span",
1050
1340
  {
1051
1341
  className: "rounded-full border px-2 py-0.5 text-xs text-muted-foreground",
@@ -1056,12 +1346,12 @@ function PostForm({ post }) {
1056
1346
  },
1057
1347
  tag
1058
1348
  )) }),
1059
- /* @__PURE__ */ jsx9("p", { className: "text-xs text-muted-foreground", children: t("posts.form.previewHint") })
1349
+ /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground", children: t("posts.form.previewHint") })
1060
1350
  ] }),
1061
1351
  /* @__PURE__ */ jsxs8("div", { className: view === "edit" ? "space-y-6" : "hidden", children: [
1062
1352
  /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1063
- /* @__PURE__ */ jsx9(Label2, { htmlFor: "title", children: t("posts.form.title") }),
1064
- /* @__PURE__ */ jsx9(
1353
+ /* @__PURE__ */ jsx10(Label2, { htmlFor: "title", children: t("posts.form.title") }),
1354
+ /* @__PURE__ */ jsx10(
1065
1355
  Input2,
1066
1356
  {
1067
1357
  id: "title",
@@ -1075,8 +1365,8 @@ function PostForm({ post }) {
1075
1365
  )
1076
1366
  ] }),
1077
1367
  /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1078
- /* @__PURE__ */ jsx9(Label2, { htmlFor: "slug", children: t("posts.form.slug") }),
1079
- /* @__PURE__ */ jsx9(
1368
+ /* @__PURE__ */ jsx10(Label2, { htmlFor: "slug", children: t("posts.form.slug") }),
1369
+ /* @__PURE__ */ jsx10(
1080
1370
  Input2,
1081
1371
  {
1082
1372
  id: "slug",
@@ -1087,8 +1377,8 @@ function PostForm({ post }) {
1087
1377
  )
1088
1378
  ] }),
1089
1379
  /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1090
- /* @__PURE__ */ jsx9(Label2, { htmlFor: "excerpt", children: t("posts.form.excerpt") }),
1091
- /* @__PURE__ */ jsx9(
1380
+ /* @__PURE__ */ jsx10(Label2, { htmlFor: "excerpt", children: t("posts.form.excerpt") }),
1381
+ /* @__PURE__ */ jsx10(
1092
1382
  Textarea,
1093
1383
  {
1094
1384
  id: "excerpt",
@@ -1099,7 +1389,7 @@ function PostForm({ post }) {
1099
1389
  )
1100
1390
  ] }),
1101
1391
  /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1102
- /* @__PURE__ */ jsx9(Label2, { htmlFor: "format", children: t("posts.form.format") }),
1392
+ /* @__PURE__ */ jsx10(Label2, { htmlFor: "format", children: t("posts.form.format") }),
1103
1393
  /* @__PURE__ */ jsxs8(
1104
1394
  "select",
1105
1395
  {
@@ -1108,33 +1398,33 @@ function PostForm({ post }) {
1108
1398
  onChange: (e) => changeFormat(e.target.value),
1109
1399
  className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
1110
1400
  children: [
1111
- /* @__PURE__ */ jsx9("option", { value: "tiptap", children: "Tiptap (rich editor)" }),
1112
- /* @__PURE__ */ jsx9("option", { value: "markdown", children: "Markdown" }),
1113
- /* @__PURE__ */ jsx9("option", { value: "html", children: "HTML" })
1401
+ /* @__PURE__ */ jsx10("option", { value: "tiptap", children: "Tiptap (rich editor)" }),
1402
+ /* @__PURE__ */ jsx10("option", { value: "markdown", children: "Markdown" }),
1403
+ /* @__PURE__ */ jsx10("option", { value: "html", children: "HTML" })
1114
1404
  ]
1115
1405
  }
1116
1406
  ),
1117
- /* @__PURE__ */ jsx9("p", { className: "text-xs text-muted-foreground", children: t("posts.form.formatHint") })
1407
+ /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground", children: t("posts.form.formatHint") })
1118
1408
  ] }),
1119
1409
  /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1120
1410
  /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between", children: [
1121
- /* @__PURE__ */ jsx9(Label2, { children: t("posts.form.body") }),
1411
+ /* @__PURE__ */ jsx10(Label2, { children: t("posts.form.body") }),
1122
1412
  format !== "tiptap" && // For textarea-based formats (markdown / html) there's no
1123
1413
  // embedded toolbar, so we surface the MediaPicker as a
1124
1414
  // standalone button. Selecting an asset inserts a
1125
1415
  // format-aware snippet at the cursor.
1126
- /* @__PURE__ */ jsx9(
1416
+ /* @__PURE__ */ jsx10(
1127
1417
  MediaPicker,
1128
1418
  {
1129
1419
  onSelect: insertMediaSnippet,
1130
- trigger: /* @__PURE__ */ jsxs8(Button6, { type: "button", variant: "outline", size: "sm", children: [
1131
- /* @__PURE__ */ jsx9(ImageIcon3, { className: "mr-2 h-3 w-3" }),
1420
+ trigger: /* @__PURE__ */ jsxs8(Button7, { type: "button", variant: "outline", size: "sm", children: [
1421
+ /* @__PURE__ */ jsx10(ImageIcon3, { className: "mr-2 h-3 w-3" }),
1132
1422
  t("posts.form.insertMedia")
1133
1423
  ] })
1134
1424
  }
1135
1425
  )
1136
1426
  ] }),
1137
- format === "tiptap" ? /* @__PURE__ */ jsx9(TiptapEditor, { initialContent: body, onChange: setBody }) : /* @__PURE__ */ jsx9(
1427
+ format === "tiptap" ? /* @__PURE__ */ jsx10(TiptapEditor, { initialContent: body, onChange: setBody }) : /* @__PURE__ */ jsx10(
1138
1428
  Textarea,
1139
1429
  {
1140
1430
  ref: bodyTextareaRef,
@@ -1146,8 +1436,8 @@ function PostForm({ post }) {
1146
1436
  )
1147
1437
  ] }),
1148
1438
  /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1149
- /* @__PURE__ */ jsx9(Label2, { htmlFor: "tags", children: t("posts.form.tags") }),
1150
- /* @__PURE__ */ jsx9(
1439
+ /* @__PURE__ */ jsx10(Label2, { htmlFor: "tags", children: t("posts.form.tags") }),
1440
+ /* @__PURE__ */ jsx10(
1151
1441
  Input2,
1152
1442
  {
1153
1443
  id: "tags",
@@ -1156,10 +1446,10 @@ function PostForm({ post }) {
1156
1446
  placeholder: t("posts.form.tagsPlaceholder")
1157
1447
  }
1158
1448
  ),
1159
- /* @__PURE__ */ jsx9("p", { className: "text-xs text-muted-foreground", children: t("posts.form.tagsHint") })
1449
+ /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground", children: t("posts.form.tagsHint") })
1160
1450
  ] }),
1161
1451
  /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1162
- /* @__PURE__ */ jsx9(Label2, { htmlFor: "status", children: t("posts.form.status") }),
1452
+ /* @__PURE__ */ jsx10(Label2, { htmlFor: "status", children: t("posts.form.status") }),
1163
1453
  /* @__PURE__ */ jsxs8(
1164
1454
  "select",
1165
1455
  {
@@ -1168,53 +1458,624 @@ function PostForm({ post }) {
1168
1458
  onChange: (e) => setStatus(e.target.value),
1169
1459
  className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
1170
1460
  children: [
1171
- /* @__PURE__ */ jsx9("option", { value: "draft", children: t("common.draft") }),
1172
- /* @__PURE__ */ jsx9("option", { value: "published", children: t("common.published") })
1461
+ /* @__PURE__ */ jsx10("option", { value: "draft", children: t("common.draft") }),
1462
+ /* @__PURE__ */ jsx10("option", { value: "published", children: t("common.published") })
1173
1463
  ]
1174
1464
  }
1175
1465
  )
1176
1466
  ] }),
1177
- error && /* @__PURE__ */ jsx9("p", { className: "text-sm text-destructive", children: error }),
1467
+ error && /* @__PURE__ */ jsx10("p", { className: "text-sm text-destructive", children: error }),
1178
1468
  /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1179
- /* @__PURE__ */ jsx9(Button6, { type: "submit", disabled: saving, children: saving ? t("common.saving") : isEdit ? t("posts.form.saveChanges") : t("posts.form.createPost") }),
1180
- isEdit && /* @__PURE__ */ jsx9(Button6, { type: "button", variant: "destructive", onClick: handleDelete, disabled: saving, children: t("posts.form.delete") })
1469
+ /* @__PURE__ */ jsx10(Button7, { type: "submit", disabled: saving, children: saving ? t("common.saving") : isEdit ? t("posts.form.saveChanges") : t("posts.form.createPost") }),
1470
+ isEdit && /* @__PURE__ */ jsx10(Button7, { type: "button", variant: "destructive", onClick: handleDelete, disabled: saving, children: t("posts.form.delete") })
1181
1471
  ] })
1182
1472
  ] })
1183
1473
  ] });
1184
1474
  }
1185
1475
 
1186
- // src/components/site-settings-form.tsx
1187
- import { useState as useState4 } from "react";
1188
- import { useRouter as useRouter3 } from "next/navigation";
1189
- import { setSiteSetting } from "ampless";
1190
- import { Button as Button7, Input as Input3, Label as Label3, Textarea as Textarea2 } from "@ampless/runtime/ui";
1191
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1192
- var KEYS = [
1193
- "site.name",
1194
- "site.url",
1195
- "site.description",
1196
- "media.imageDisplay",
1197
- "media.imageMaxWidth",
1198
- "dateFormat",
1199
- "timezone"
1200
- ];
1201
- function SiteSettingsForm({ siteId, initial, fallback }) {
1202
- const router = useRouter3();
1476
+ // src/components/new-post-view.tsx
1477
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1478
+ function NewPostPage() {
1203
1479
  const t = useT();
1204
- const [values, setValues] = useState4(initial);
1205
- const [saving, setSaving] = useState4(false);
1206
- const [error, setError] = useState4(null);
1207
- const [info, setInfo] = useState4(null);
1208
- function update(key, value) {
1209
- setValues((prev) => ({ ...prev, [key]: value }));
1480
+ return /* @__PURE__ */ jsxs9("div", { className: "p-8", children: [
1481
+ /* @__PURE__ */ jsx11("h1", { className: "mb-8 text-3xl font-bold", children: t("posts.form.newTitle") }),
1482
+ /* @__PURE__ */ jsx11(PostForm, {})
1483
+ ] });
1484
+ }
1485
+
1486
+ // src/components/edit-post-view.tsx
1487
+ import { useEffect as useEffect6, useState as useState6, use } from "react";
1488
+ import { notFound } from "next/navigation";
1489
+ import { getPostById } from "ampless";
1490
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1491
+ function EditPostPage({ params }) {
1492
+ const t = useT();
1493
+ const { postId } = use(params);
1494
+ const [post, setPost] = useState6(null);
1495
+ const [loading, setLoading] = useState6(true);
1496
+ const [missing, setMissing] = useState6(false);
1497
+ useEffect6(() => {
1498
+ const siteId = readAdminSiteIdFromCookie();
1499
+ getPostById(postId, { siteId }).then((p) => {
1500
+ if (!p) setMissing(true);
1501
+ else setPost(p);
1502
+ }).finally(() => setLoading(false));
1503
+ }, [postId]);
1504
+ if (loading) return /* @__PURE__ */ jsx12("div", { className: "p-8", children: t("common.loading") });
1505
+ if (missing) notFound();
1506
+ return /* @__PURE__ */ jsxs10("div", { className: "p-8", children: [
1507
+ /* @__PURE__ */ jsx12("h1", { className: "mb-8 text-3xl font-bold", children: t("posts.form.editTitle") }),
1508
+ post && /* @__PURE__ */ jsx12(PostForm, { post })
1509
+ ] });
1510
+ }
1511
+
1512
+ // src/components/media-uploader.tsx
1513
+ import { useState as useState7, useEffect as useEffect7, useCallback, useRef as useRef4 } from "react";
1514
+ import { uploadData as uploadData2, list as list2, remove, isCancelError } from "aws-amplify/storage";
1515
+ import { processImage as processImage2 } from "ampless/media";
1516
+ import { Button as Button8, Input as Input3 } from "@ampless/runtime/ui";
1517
+ import { Trash2 as Trash22, Copy, Check, FileText, Code2 } from "lucide-react";
1518
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1519
+ var IMAGE_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)$/i;
1520
+ var STYLESHEET_EXT_RE = /\.css$/i;
1521
+ var SCRIPT_EXT_RE = /\.m?js$/i;
1522
+ function getExtension(path) {
1523
+ const dot = path.lastIndexOf(".");
1524
+ return dot >= 0 ? path.slice(dot + 1).toUpperCase() : "FILE";
1525
+ }
1526
+ function snippetFor2(url, path) {
1527
+ if (IMAGE_EXT_RE.test(path)) {
1528
+ return `<img src="${url}" alt="" />`;
1210
1529
  }
1211
- async function save(e) {
1212
- e.preventDefault();
1213
- setSaving(true);
1214
- setError(null);
1215
- setInfo(null);
1216
- try {
1217
- await Promise.all(
1530
+ if (STYLESHEET_EXT_RE.test(path)) {
1531
+ return `<link rel="stylesheet" href="${url}" />`;
1532
+ }
1533
+ if (SCRIPT_EXT_RE.test(path)) {
1534
+ return `<script src="${url}"></script>`;
1535
+ }
1536
+ return url;
1537
+ }
1538
+ function sanitizeName2(name) {
1539
+ return name.replace(/[ -]/g, "").replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, "_").replace(/^\.+/, "_").slice(0, 200) || "upload";
1540
+ }
1541
+ function MediaUploader() {
1542
+ const t = useT();
1543
+ const [items, setItems] = useState7([]);
1544
+ const [queue, setQueue] = useState7([]);
1545
+ const [uploading, setUploading] = useState7(false);
1546
+ const [error, setError] = useState7(null);
1547
+ const [copiedPath, setCopiedPath] = useState7(null);
1548
+ const uploadTaskRef = useRef4(null);
1549
+ const cancelTokenRef = useRef4({ cancelled: false });
1550
+ const refresh = useCallback(async () => {
1551
+ try {
1552
+ const result = await list2({ path: "public/media/" });
1553
+ setItems(
1554
+ result.items.map((item) => ({
1555
+ path: item.path,
1556
+ url: publicMediaUrl(item.path)
1557
+ }))
1558
+ );
1559
+ } catch (err) {
1560
+ setError(err instanceof Error ? err.message : String(err));
1561
+ }
1562
+ }, []);
1563
+ useEffect7(() => {
1564
+ refresh();
1565
+ }, [refresh]);
1566
+ function handleFiles(e) {
1567
+ const files = Array.from(e.target.files ?? []);
1568
+ e.target.value = "";
1569
+ if (files.length === 0) return;
1570
+ setError(null);
1571
+ setQueue((prev) => [...prev, ...files]);
1572
+ }
1573
+ async function handleDialogConfirm(file, options) {
1574
+ const token = { cancelled: false };
1575
+ cancelTokenRef.current = token;
1576
+ setUploading(true);
1577
+ setError(null);
1578
+ let advance = true;
1579
+ try {
1580
+ const processed = await processImage2(file, options);
1581
+ if (token.cancelled) {
1582
+ advance = false;
1583
+ return;
1584
+ }
1585
+ const safeName = sanitizeName2(processed.suggestedName);
1586
+ const now = /* @__PURE__ */ new Date();
1587
+ const yyyy = now.getFullYear();
1588
+ const mm = String(now.getMonth() + 1).padStart(2, "0");
1589
+ const path = `public/media/${yyyy}/${mm}/${Date.now()}-${safeName}`;
1590
+ const task = uploadData2({
1591
+ path,
1592
+ data: processed.blob,
1593
+ options: { contentType: processed.mime }
1594
+ });
1595
+ uploadTaskRef.current = task;
1596
+ await task.result;
1597
+ await refresh();
1598
+ } catch (err) {
1599
+ if (isCancelError(err) || token.cancelled) {
1600
+ advance = false;
1601
+ } else {
1602
+ setError(err instanceof Error ? err.message : String(err));
1603
+ }
1604
+ } finally {
1605
+ uploadTaskRef.current = null;
1606
+ setUploading(false);
1607
+ if (advance) {
1608
+ setQueue((prev) => prev.slice(1));
1609
+ }
1610
+ }
1611
+ }
1612
+ function handleDialogSkip() {
1613
+ if (uploading) return;
1614
+ setQueue((prev) => prev.slice(1));
1615
+ }
1616
+ function handleDialogCancel() {
1617
+ cancelTokenRef.current.cancelled = true;
1618
+ uploadTaskRef.current?.cancel();
1619
+ setQueue([]);
1620
+ }
1621
+ async function handleDelete(path) {
1622
+ if (!confirm(t("media.deleteConfirm"))) return;
1623
+ try {
1624
+ await remove({ path });
1625
+ await refresh();
1626
+ } catch (err) {
1627
+ setError(err instanceof Error ? err.message : String(err));
1628
+ }
1629
+ }
1630
+ async function handleCopy(item, mode) {
1631
+ const text = mode === "url" ? item.url : snippetFor2(item.url, item.path);
1632
+ await navigator.clipboard.writeText(text);
1633
+ const key = `${item.path}:${mode}`;
1634
+ setCopiedPath(key);
1635
+ setTimeout(() => setCopiedPath((p) => p === key ? null : p), 1500);
1636
+ }
1637
+ const currentFile = queue[0] ?? null;
1638
+ return /* @__PURE__ */ jsxs11("div", { className: "space-y-6", children: [
1639
+ /* @__PURE__ */ jsxs11("div", { className: "rounded-md border p-4", children: [
1640
+ /* @__PURE__ */ jsx13(
1641
+ Input3,
1642
+ {
1643
+ type: "file",
1644
+ multiple: true,
1645
+ onChange: handleFiles,
1646
+ disabled: uploading
1647
+ }
1648
+ ),
1649
+ uploading && /* @__PURE__ */ jsx13("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.uploading") }),
1650
+ !uploading && queue.length > 0 && /* @__PURE__ */ jsx13("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.queued", { count: queue.length }) })
1651
+ ] }),
1652
+ error && /* @__PURE__ */ jsx13("p", { className: "text-sm text-destructive", children: error }),
1653
+ /* @__PURE__ */ jsx13(
1654
+ ImageUploadDialog,
1655
+ {
1656
+ file: currentFile,
1657
+ remaining: queue.length,
1658
+ busy: uploading,
1659
+ defaults: getMediaProcessingDefaults(),
1660
+ onConfirm: handleDialogConfirm,
1661
+ onSkip: handleDialogSkip,
1662
+ onCancel: handleDialogCancel
1663
+ }
1664
+ ),
1665
+ /* @__PURE__ */ jsx13("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4", children: items.map((item) => {
1666
+ const isImage = IMAGE_EXT_RE.test(item.path);
1667
+ const isStylesheet = STYLESHEET_EXT_RE.test(item.path);
1668
+ const isScript = SCRIPT_EXT_RE.test(item.path);
1669
+ const filename = item.path.split("/").pop() ?? "";
1670
+ const ext = getExtension(item.path);
1671
+ const tagSnippet = snippetFor2(item.url, item.path);
1672
+ const tagDiffersFromUrl = tagSnippet !== item.url;
1673
+ const urlCopied = copiedPath === `${item.path}:url`;
1674
+ const tagCopied = copiedPath === `${item.path}:tag`;
1675
+ return /* @__PURE__ */ jsxs11(
1676
+ "div",
1677
+ {
1678
+ className: "group relative overflow-hidden rounded-md border bg-[var(--card)]",
1679
+ children: [
1680
+ isImage ? (
1681
+ // eslint-disable-next-line @next/next/no-img-element
1682
+ /* @__PURE__ */ jsx13(
1683
+ "img",
1684
+ {
1685
+ src: item.url,
1686
+ alt: item.path,
1687
+ className: "aspect-square w-full object-cover"
1688
+ }
1689
+ )
1690
+ ) : /* @__PURE__ */ jsxs11("div", { className: "flex aspect-square w-full flex-col items-center justify-center gap-2 bg-muted text-muted-foreground", children: [
1691
+ isStylesheet || isScript ? /* @__PURE__ */ jsx13(Code2, { className: "h-8 w-8" }) : /* @__PURE__ */ jsx13(FileText, { className: "h-8 w-8" }),
1692
+ /* @__PURE__ */ jsxs11("span", { className: "font-mono text-xs font-semibold", children: [
1693
+ ".",
1694
+ ext.toLowerCase()
1695
+ ] })
1696
+ ] }),
1697
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between border-t px-2 py-1 text-xs", children: [
1698
+ /* @__PURE__ */ jsx13("span", { className: "truncate", title: filename, children: filename }),
1699
+ /* @__PURE__ */ jsxs11("div", { className: "flex shrink-0 items-center gap-0.5", children: [
1700
+ /* @__PURE__ */ jsx13(
1701
+ Button8,
1702
+ {
1703
+ type: "button",
1704
+ variant: "ghost",
1705
+ size: "icon",
1706
+ className: "h-6 w-6",
1707
+ onClick: () => handleCopy(item, "url"),
1708
+ title: t("media.copyUrl"),
1709
+ children: urlCopied ? /* @__PURE__ */ jsx13(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx13(Copy, { className: "h-3 w-3" })
1710
+ }
1711
+ ),
1712
+ tagDiffersFromUrl && /* @__PURE__ */ jsx13(
1713
+ Button8,
1714
+ {
1715
+ type: "button",
1716
+ variant: "ghost",
1717
+ size: "icon",
1718
+ className: "h-6 w-6",
1719
+ onClick: () => handleCopy(item, "tag"),
1720
+ title: t("media.copyTag"),
1721
+ children: tagCopied ? /* @__PURE__ */ jsx13(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx13(Code2, { className: "h-3 w-3" })
1722
+ }
1723
+ ),
1724
+ /* @__PURE__ */ jsx13(
1725
+ Button8,
1726
+ {
1727
+ type: "button",
1728
+ variant: "ghost",
1729
+ size: "icon",
1730
+ className: "h-6 w-6",
1731
+ onClick: () => handleDelete(item.path),
1732
+ title: t("media.delete"),
1733
+ children: /* @__PURE__ */ jsx13(Trash22, { className: "h-3 w-3" })
1734
+ }
1735
+ )
1736
+ ] })
1737
+ ] })
1738
+ ]
1739
+ },
1740
+ item.path
1741
+ );
1742
+ }) }),
1743
+ items.length === 0 && /* @__PURE__ */ jsx13("p", { className: "text-center text-sm text-muted-foreground", children: t("media.empty") })
1744
+ ] });
1745
+ }
1746
+
1747
+ // src/components/media-view.tsx
1748
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1749
+ function MediaPage() {
1750
+ const t = useT();
1751
+ return /* @__PURE__ */ jsxs12("div", { className: "p-8", children: [
1752
+ /* @__PURE__ */ jsx14("h1", { className: "mb-8 text-3xl font-bold", children: t("media.title") }),
1753
+ /* @__PURE__ */ jsx14(MediaUploader, {})
1754
+ ] });
1755
+ }
1756
+
1757
+ // src/components/login-view.tsx
1758
+ import { useState as useState8 } from "react";
1759
+ import { useRouter as useRouter2 } from "next/navigation";
1760
+ import {
1761
+ signIn,
1762
+ signUp,
1763
+ confirmSignUp,
1764
+ resetPassword,
1765
+ confirmResetPassword
1766
+ } from "aws-amplify/auth";
1767
+ import {
1768
+ Button as Button9,
1769
+ Input as Input4,
1770
+ Label as Label3,
1771
+ Card as Card2,
1772
+ CardContent as CardContent2,
1773
+ CardHeader as CardHeader2,
1774
+ CardTitle as CardTitle2,
1775
+ CardDescription
1776
+ } from "@ampless/runtime/ui";
1777
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1778
+ function LoginPage() {
1779
+ const router = useRouter2();
1780
+ const t = useT();
1781
+ const [mode, setMode] = useState8("signIn");
1782
+ const [email, setEmail] = useState8("");
1783
+ const [password, setPassword] = useState8("");
1784
+ const [code, setCode] = useState8("");
1785
+ const [error, setError] = useState8(null);
1786
+ const [info, setInfo] = useState8(null);
1787
+ const [loading, setLoading] = useState8(false);
1788
+ function go(next) {
1789
+ setMode(next);
1790
+ setError(null);
1791
+ setInfo(null);
1792
+ setCode("");
1793
+ if (next === "signIn" || next === "signUp" || next === "forgot") setPassword("");
1794
+ }
1795
+ async function handleSubmit(e) {
1796
+ e.preventDefault();
1797
+ setError(null);
1798
+ setInfo(null);
1799
+ setLoading(true);
1800
+ try {
1801
+ if (mode === "signIn") {
1802
+ const result = await signIn({ username: email, password });
1803
+ if (result.isSignedIn) {
1804
+ router.push("/admin");
1805
+ router.refresh();
1806
+ } else {
1807
+ setError(t("auth.additionalStep", { step: result.nextStep.signInStep }));
1808
+ }
1809
+ } else if (mode === "signUp") {
1810
+ await signUp({
1811
+ username: email,
1812
+ password,
1813
+ options: { userAttributes: { email } }
1814
+ });
1815
+ go("confirm");
1816
+ } else if (mode === "confirm") {
1817
+ await confirmSignUp({ username: email, confirmationCode: code });
1818
+ const result = await signIn({ username: email, password });
1819
+ if (result.isSignedIn) {
1820
+ router.push("/admin");
1821
+ router.refresh();
1822
+ }
1823
+ } else if (mode === "forgot") {
1824
+ await resetPassword({ username: email });
1825
+ setMode("reset");
1826
+ setInfo(t("auth.forgot.codeSent"));
1827
+ } else if (mode === "reset") {
1828
+ await confirmResetPassword({
1829
+ username: email,
1830
+ confirmationCode: code,
1831
+ newPassword: password
1832
+ });
1833
+ const result = await signIn({ username: email, password });
1834
+ if (result.isSignedIn) {
1835
+ router.push("/admin");
1836
+ router.refresh();
1837
+ } else {
1838
+ setMode("signIn");
1839
+ setInfo(t("auth.reset.passwordUpdated"));
1840
+ }
1841
+ }
1842
+ } catch (err) {
1843
+ setError(err instanceof Error ? err.message : String(err));
1844
+ } finally {
1845
+ setLoading(false);
1846
+ }
1847
+ }
1848
+ const showEmail = mode !== "confirm" && mode !== "reset";
1849
+ const showPassword = mode === "signIn" || mode === "signUp" || mode === "reset";
1850
+ const showCode = mode === "confirm" || mode === "reset";
1851
+ return /* @__PURE__ */ jsx15("main", { className: "flex min-h-screen items-center justify-center bg-muted/30 p-4", children: /* @__PURE__ */ jsxs13(Card2, { className: "w-full max-w-md", children: [
1852
+ /* @__PURE__ */ jsxs13(CardHeader2, { children: [
1853
+ /* @__PURE__ */ jsx15(CardTitle2, { children: t(`auth.${mode}.title`) }),
1854
+ /* @__PURE__ */ jsx15(CardDescription, { children: t(`auth.${mode}.description`) })
1855
+ ] }),
1856
+ /* @__PURE__ */ jsx15(CardContent2, { children: /* @__PURE__ */ jsxs13("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
1857
+ showEmail && /* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
1858
+ /* @__PURE__ */ jsx15(Label3, { htmlFor: "email", children: t("auth.common.email") }),
1859
+ /* @__PURE__ */ jsx15(
1860
+ Input4,
1861
+ {
1862
+ id: "email",
1863
+ type: "email",
1864
+ required: true,
1865
+ value: email,
1866
+ onChange: (e) => setEmail(e.target.value),
1867
+ autoComplete: "email"
1868
+ }
1869
+ )
1870
+ ] }),
1871
+ showCode && /* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
1872
+ /* @__PURE__ */ jsx15(Label3, { htmlFor: "code", children: t("auth.common.code") }),
1873
+ /* @__PURE__ */ jsx15(
1874
+ Input4,
1875
+ {
1876
+ id: "code",
1877
+ required: true,
1878
+ value: code,
1879
+ onChange: (e) => setCode(e.target.value),
1880
+ autoComplete: "one-time-code"
1881
+ }
1882
+ )
1883
+ ] }),
1884
+ showPassword && /* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
1885
+ /* @__PURE__ */ jsx15(Label3, { htmlFor: "password", children: mode === "reset" ? t("auth.common.newPassword") : t("auth.common.password") }),
1886
+ /* @__PURE__ */ jsx15(
1887
+ Input4,
1888
+ {
1889
+ id: "password",
1890
+ type: "password",
1891
+ required: true,
1892
+ minLength: 8,
1893
+ value: password,
1894
+ onChange: (e) => setPassword(e.target.value),
1895
+ autoComplete: mode === "signIn" ? "current-password" : "new-password"
1896
+ }
1897
+ ),
1898
+ (mode === "signUp" || mode === "reset") && /* @__PURE__ */ jsx15("p", { className: "text-xs text-muted-foreground", children: t("auth.common.passwordHint") })
1899
+ ] }),
1900
+ info && /* @__PURE__ */ jsx15("p", { className: "text-sm text-muted-foreground", children: info }),
1901
+ error && /* @__PURE__ */ jsx15("p", { className: "text-sm text-destructive", children: error }),
1902
+ /* @__PURE__ */ jsx15(Button9, { type: "submit", className: "w-full", disabled: loading, children: loading ? t("auth.common.working") : t(`auth.${mode}.submit`) }),
1903
+ /* @__PURE__ */ jsxs13("div", { className: "space-y-1 text-center text-sm", children: [
1904
+ mode === "signIn" && /* @__PURE__ */ jsxs13(Fragment4, { children: [
1905
+ /* @__PURE__ */ jsx15("p", { children: /* @__PURE__ */ jsx15(
1906
+ "button",
1907
+ {
1908
+ type: "button",
1909
+ className: "text-primary hover:underline",
1910
+ onClick: () => go("forgot"),
1911
+ children: t("auth.signIn.forgotPassword")
1912
+ }
1913
+ ) }),
1914
+ /* @__PURE__ */ jsx15("p", { children: /* @__PURE__ */ jsx15(
1915
+ "button",
1916
+ {
1917
+ type: "button",
1918
+ className: "text-primary hover:underline",
1919
+ onClick: () => go("signUp"),
1920
+ children: t("auth.signIn.createAccount")
1921
+ }
1922
+ ) })
1923
+ ] }),
1924
+ (mode === "signUp" || mode === "forgot" || mode === "reset") && /* @__PURE__ */ jsx15("p", { children: /* @__PURE__ */ jsx15(
1925
+ "button",
1926
+ {
1927
+ type: "button",
1928
+ className: "text-primary hover:underline",
1929
+ onClick: () => go("signIn"),
1930
+ children: t("auth.signUp.backToSignIn")
1931
+ }
1932
+ ) }),
1933
+ mode === "reset" && /* @__PURE__ */ jsx15("p", { children: /* @__PURE__ */ jsx15(
1934
+ "button",
1935
+ {
1936
+ type: "button",
1937
+ className: "text-primary hover:underline",
1938
+ onClick: () => go("forgot"),
1939
+ children: t("auth.reset.resendCode")
1940
+ }
1941
+ ) })
1942
+ ] })
1943
+ ] }) })
1944
+ ] }) });
1945
+ }
1946
+
1947
+ // src/components/sidebar.tsx
1948
+ import Link4 from "next/link";
1949
+ import { usePathname } from "next/navigation";
1950
+ import { signOut } from "aws-amplify/auth";
1951
+ import { LayoutDashboard, FileText as FileText2, Image as Image2, Globe, LogOut, ExternalLink } from "lucide-react";
1952
+ import { Button as Button10, cn as cn3 } from "@ampless/runtime/ui";
1953
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1954
+ var navItems = [
1955
+ { href: "/admin", key: "sidebar.dashboard", icon: LayoutDashboard },
1956
+ { href: "/admin/posts", key: "sidebar.posts", icon: FileText2 },
1957
+ { href: "/admin/media", key: "sidebar.media", icon: Image2 },
1958
+ { href: "/admin/sites", key: "sidebar.sites", icon: Globe }
1959
+ ];
1960
+ function Sidebar({
1961
+ email,
1962
+ siteSelector
1963
+ }) {
1964
+ const pathname = usePathname();
1965
+ const t = useT();
1966
+ return /* @__PURE__ */ jsxs14("aside", { className: "flex h-screen w-60 flex-col border-r bg-muted/30", children: [
1967
+ /* @__PURE__ */ jsx16("div", { className: "border-b p-4", children: /* @__PURE__ */ jsx16(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }) }),
1968
+ siteSelector ? /* @__PURE__ */ jsx16("div", { className: "border-b", children: siteSelector }) : null,
1969
+ /* @__PURE__ */ jsx16("nav", { className: "flex-1 space-y-1 p-2", children: navItems.map((item) => {
1970
+ const Icon = item.icon;
1971
+ const isActive = pathname === item.href || item.href !== "/admin" && pathname?.startsWith(item.href);
1972
+ return /* @__PURE__ */ jsxs14(
1973
+ Link4,
1974
+ {
1975
+ href: item.href,
1976
+ className: cn3(
1977
+ "flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors",
1978
+ isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1979
+ ),
1980
+ children: [
1981
+ /* @__PURE__ */ jsx16(Icon, { className: "h-4 w-4" }),
1982
+ t(item.key)
1983
+ ]
1984
+ },
1985
+ item.href
1986
+ );
1987
+ }) }),
1988
+ /* @__PURE__ */ jsxs14("div", { className: "border-t p-2 space-y-1", children: [
1989
+ /* @__PURE__ */ jsxs14(
1990
+ Link4,
1991
+ {
1992
+ href: "/",
1993
+ target: "_blank",
1994
+ className: "flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground",
1995
+ children: [
1996
+ /* @__PURE__ */ jsx16(ExternalLink, { className: "h-4 w-4" }),
1997
+ t("sidebar.viewSite")
1998
+ ]
1999
+ }
2000
+ ),
2001
+ /* @__PURE__ */ jsx16("div", { className: "px-3 py-2 text-xs text-muted-foreground truncate", children: email }),
2002
+ /* @__PURE__ */ jsxs14(
2003
+ Button10,
2004
+ {
2005
+ variant: "ghost",
2006
+ size: "sm",
2007
+ className: "w-full justify-start gap-3",
2008
+ onClick: async () => {
2009
+ await signOut();
2010
+ window.location.href = "/login";
2011
+ },
2012
+ children: [
2013
+ /* @__PURE__ */ jsx16(LogOut, { className: "h-4 w-4" }),
2014
+ t("sidebar.signOut")
2015
+ ]
2016
+ }
2017
+ )
2018
+ ] })
2019
+ ] });
2020
+ }
2021
+
2022
+ // src/components/site-selector.tsx
2023
+ import { useRouter as useRouter3 } from "next/navigation";
2024
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2025
+ function SiteSelector({ current, sites }) {
2026
+ const router = useRouter3();
2027
+ const t = useT();
2028
+ function onChange(e) {
2029
+ const next = e.target.value;
2030
+ document.cookie = `${ADMIN_SITE_COOKIE}=${encodeURIComponent(next)}; Path=/; Max-Age=${60 * 60 * 24 * 365}; SameSite=Lax`;
2031
+ router.refresh();
2032
+ }
2033
+ return /* @__PURE__ */ jsxs15("div", { className: "px-3 py-2", children: [
2034
+ /* @__PURE__ */ jsx17("label", { className: "block text-xs uppercase tracking-wide text-muted-foreground mb-1", children: t("sites.selector.label") }),
2035
+ /* @__PURE__ */ jsx17(
2036
+ "select",
2037
+ {
2038
+ value: current,
2039
+ onChange,
2040
+ className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
2041
+ children: sites.map((s) => /* @__PURE__ */ jsx17("option", { value: s.id, children: s.name }, s.id))
2042
+ }
2043
+ )
2044
+ ] });
2045
+ }
2046
+
2047
+ // src/components/site-settings-form.tsx
2048
+ import { useState as useState9 } from "react";
2049
+ import { useRouter as useRouter4 } from "next/navigation";
2050
+ import { setSiteSetting } from "ampless";
2051
+ import { Button as Button11, Input as Input5, Label as Label4, Textarea as Textarea2 } from "@ampless/runtime/ui";
2052
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2053
+ var KEYS = [
2054
+ "site.name",
2055
+ "site.url",
2056
+ "site.description",
2057
+ "media.imageDisplay",
2058
+ "media.imageMaxWidth",
2059
+ "dateFormat",
2060
+ "timezone"
2061
+ ];
2062
+ function SiteSettingsForm({ siteId, initial, fallback }) {
2063
+ const router = useRouter4();
2064
+ const t = useT();
2065
+ const [values, setValues] = useState9(initial);
2066
+ const [saving, setSaving] = useState9(false);
2067
+ const [error, setError] = useState9(null);
2068
+ const [info, setInfo] = useState9(null);
2069
+ function update(key, value) {
2070
+ setValues((prev) => ({ ...prev, [key]: value }));
2071
+ }
2072
+ async function save(e) {
2073
+ e.preventDefault();
2074
+ setSaving(true);
2075
+ setError(null);
2076
+ setInfo(null);
2077
+ try {
2078
+ await Promise.all(
1218
2079
  KEYS.map((key) => {
1219
2080
  const value = values[key];
1220
2081
  if (value === void 0 || value === "") return Promise.resolve();
@@ -1229,13 +2090,13 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
1229
2090
  setSaving(false);
1230
2091
  }
1231
2092
  }
1232
- return /* @__PURE__ */ jsxs9("form", { onSubmit: save, className: "space-y-6 max-w-xl", children: [
1233
- /* @__PURE__ */ jsxs9("fieldset", { className: "space-y-4", children: [
1234
- /* @__PURE__ */ jsx10("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.site") }),
1235
- /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1236
- /* @__PURE__ */ jsx10(Label3, { htmlFor: "name", children: t("common.name") }),
1237
- /* @__PURE__ */ jsx10(
1238
- Input3,
2093
+ return /* @__PURE__ */ jsxs16("form", { onSubmit: save, className: "space-y-6 max-w-xl", children: [
2094
+ /* @__PURE__ */ jsxs16("fieldset", { className: "space-y-4", children: [
2095
+ /* @__PURE__ */ jsx18("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.site") }),
2096
+ /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2097
+ /* @__PURE__ */ jsx18(Label4, { htmlFor: "name", children: t("common.name") }),
2098
+ /* @__PURE__ */ jsx18(
2099
+ Input5,
1239
2100
  {
1240
2101
  id: "name",
1241
2102
  value: values["site.name"] ?? "",
@@ -1244,10 +2105,10 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
1244
2105
  }
1245
2106
  )
1246
2107
  ] }),
1247
- /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1248
- /* @__PURE__ */ jsx10(Label3, { htmlFor: "url", children: t("common.url") }),
1249
- /* @__PURE__ */ jsx10(
1250
- Input3,
2108
+ /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2109
+ /* @__PURE__ */ jsx18(Label4, { htmlFor: "url", children: t("common.url") }),
2110
+ /* @__PURE__ */ jsx18(
2111
+ Input5,
1251
2112
  {
1252
2113
  id: "url",
1253
2114
  value: values["site.url"] ?? "",
@@ -1256,9 +2117,9 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
1256
2117
  }
1257
2118
  )
1258
2119
  ] }),
1259
- /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1260
- /* @__PURE__ */ jsx10(Label3, { htmlFor: "description", children: t("common.description") }),
1261
- /* @__PURE__ */ jsx10(
2120
+ /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2121
+ /* @__PURE__ */ jsx18(Label4, { htmlFor: "description", children: t("common.description") }),
2122
+ /* @__PURE__ */ jsx18(
1262
2123
  Textarea2,
1263
2124
  {
1264
2125
  id: "description",
@@ -1270,11 +2131,11 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
1270
2131
  )
1271
2132
  ] })
1272
2133
  ] }),
1273
- /* @__PURE__ */ jsxs9("fieldset", { className: "space-y-4", children: [
1274
- /* @__PURE__ */ jsx10("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.media") }),
1275
- /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1276
- /* @__PURE__ */ jsx10(Label3, { htmlFor: "imageDisplay", children: t("sites.edit.imageDisplay") }),
1277
- /* @__PURE__ */ jsxs9(
2134
+ /* @__PURE__ */ jsxs16("fieldset", { className: "space-y-4", children: [
2135
+ /* @__PURE__ */ jsx18("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.media") }),
2136
+ /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2137
+ /* @__PURE__ */ jsx18(Label4, { htmlFor: "imageDisplay", children: t("sites.edit.imageDisplay") }),
2138
+ /* @__PURE__ */ jsxs16(
1278
2139
  "select",
1279
2140
  {
1280
2141
  id: "imageDisplay",
@@ -1282,19 +2143,19 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
1282
2143
  value: values["media.imageDisplay"] ?? "",
1283
2144
  onChange: (e) => update("media.imageDisplay", e.target.value),
1284
2145
  children: [
1285
- /* @__PURE__ */ jsx10("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
2146
+ /* @__PURE__ */ jsx18("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
1286
2147
  value: fallback["media.imageDisplay"] ?? "inline"
1287
2148
  }) }),
1288
- /* @__PURE__ */ jsx10("option", { value: "inline", children: t("sites.edit.imageDisplayInline") }),
1289
- /* @__PURE__ */ jsx10("option", { value: "lightbox", children: t("sites.edit.imageDisplayLightbox") })
2149
+ /* @__PURE__ */ jsx18("option", { value: "inline", children: t("sites.edit.imageDisplayInline") }),
2150
+ /* @__PURE__ */ jsx18("option", { value: "lightbox", children: t("sites.edit.imageDisplayLightbox") })
1290
2151
  ]
1291
2152
  }
1292
2153
  )
1293
2154
  ] }),
1294
- /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1295
- /* @__PURE__ */ jsx10(Label3, { htmlFor: "imageMaxWidth", children: t("sites.edit.imageMaxWidth") }),
1296
- /* @__PURE__ */ jsx10(
1297
- Input3,
2155
+ /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2156
+ /* @__PURE__ */ jsx18(Label4, { htmlFor: "imageMaxWidth", children: t("sites.edit.imageMaxWidth") }),
2157
+ /* @__PURE__ */ jsx18(
2158
+ Input5,
1298
2159
  {
1299
2160
  id: "imageMaxWidth",
1300
2161
  value: values["media.imageMaxWidth"] ?? "",
@@ -1304,11 +2165,11 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
1304
2165
  )
1305
2166
  ] })
1306
2167
  ] }),
1307
- /* @__PURE__ */ jsxs9("fieldset", { className: "space-y-4", children: [
1308
- /* @__PURE__ */ jsx10("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.dateDisplay") }),
1309
- /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1310
- /* @__PURE__ */ jsx10(Label3, { htmlFor: "dateFormat", children: t("sites.edit.dateFormat") }),
1311
- /* @__PURE__ */ jsxs9(
2168
+ /* @__PURE__ */ jsxs16("fieldset", { className: "space-y-4", children: [
2169
+ /* @__PURE__ */ jsx18("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.dateDisplay") }),
2170
+ /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2171
+ /* @__PURE__ */ jsx18(Label4, { htmlFor: "dateFormat", children: t("sites.edit.dateFormat") }),
2172
+ /* @__PURE__ */ jsxs16(
1312
2173
  "select",
1313
2174
  {
1314
2175
  id: "dateFormat",
@@ -1316,20 +2177,20 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
1316
2177
  value: values["dateFormat"] ?? "",
1317
2178
  onChange: (e) => update("dateFormat", e.target.value),
1318
2179
  children: [
1319
- /* @__PURE__ */ jsx10("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
2180
+ /* @__PURE__ */ jsx18("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
1320
2181
  value: fallback["dateFormat"] ?? "iso"
1321
2182
  }) }),
1322
- /* @__PURE__ */ jsx10("option", { value: "iso", children: t("sites.edit.dateFormatIso") }),
1323
- /* @__PURE__ */ jsx10("option", { value: "long", children: t("sites.edit.dateFormatLong") }),
1324
- /* @__PURE__ */ jsx10("option", { value: "locale", children: t("sites.edit.dateFormatLocale") })
2183
+ /* @__PURE__ */ jsx18("option", { value: "iso", children: t("sites.edit.dateFormatIso") }),
2184
+ /* @__PURE__ */ jsx18("option", { value: "long", children: t("sites.edit.dateFormatLong") }),
2185
+ /* @__PURE__ */ jsx18("option", { value: "locale", children: t("sites.edit.dateFormatLocale") })
1325
2186
  ]
1326
2187
  }
1327
2188
  )
1328
2189
  ] }),
1329
- /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1330
- /* @__PURE__ */ jsx10(Label3, { htmlFor: "timezone", children: t("sites.edit.timezone") }),
1331
- /* @__PURE__ */ jsx10(
1332
- Input3,
2190
+ /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2191
+ /* @__PURE__ */ jsx18(Label4, { htmlFor: "timezone", children: t("sites.edit.timezone") }),
2192
+ /* @__PURE__ */ jsx18(
2193
+ Input5,
1333
2194
  {
1334
2195
  id: "timezone",
1335
2196
  value: values["timezone"] ?? "",
@@ -1339,15 +2200,15 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
1339
2200
  )
1340
2201
  ] })
1341
2202
  ] }),
1342
- info && /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground", children: info }),
1343
- error && /* @__PURE__ */ jsx10("p", { className: "text-sm text-destructive", children: error }),
1344
- /* @__PURE__ */ jsx10(Button7, { type: "submit", disabled: saving, children: saving ? t("common.saving") : t("sites.edit.saveButton") })
2203
+ info && /* @__PURE__ */ jsx18("p", { className: "text-sm text-muted-foreground", children: info }),
2204
+ error && /* @__PURE__ */ jsx18("p", { className: "text-sm text-destructive", children: error }),
2205
+ /* @__PURE__ */ jsx18(Button11, { type: "submit", disabled: saving, children: saving ? t("common.saving") : t("sites.edit.saveButton") })
1345
2206
  ] });
1346
2207
  }
1347
2208
 
1348
2209
  // src/components/theme-settings-form.tsx
1349
- import { useState as useState5 } from "react";
1350
- import { useRouter as useRouter4 } from "next/navigation";
2210
+ import { useState as useState10 } from "react";
2211
+ import { useRouter as useRouter5 } from "next/navigation";
1351
2212
  import {
1352
2213
  setSiteSetting as setSiteSetting2,
1353
2214
  deleteSiteSetting,
@@ -1357,8 +2218,8 @@ import {
1357
2218
  parseLinkList,
1358
2219
  stringifyLinkList
1359
2220
  } from "ampless";
1360
- import { Button as Button8, Input as Input4, Label as Label4 } from "@ampless/runtime/ui";
1361
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2221
+ import { Button as Button12, Input as Input6, Label as Label5 } from "@ampless/runtime/ui";
2222
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
1362
2223
  var CACHE_REBUILD_DELAY_MS = 8e3;
1363
2224
  function ThemeSettingsForm({
1364
2225
  siteId,
@@ -1367,17 +2228,17 @@ function ThemeSettingsForm({
1367
2228
  themeOptions,
1368
2229
  initial
1369
2230
  }) {
1370
- const router = useRouter4();
2231
+ const router = useRouter5();
1371
2232
  const t = useT();
1372
2233
  const locale = useLocale();
1373
- const [state, setState] = useState5({ values: initial, touched: {} });
1374
- const [pendingTheme, setPendingTheme] = useState5(activeTheme);
1375
- const [optimisticActive, setOptimisticActive] = useState5(activeTheme);
1376
- const [saving, setSaving] = useState5(false);
1377
- const [switching, setSwitching] = useState5(false);
1378
- const [error, setError] = useState5(null);
1379
- const [info, setInfo] = useState5(null);
1380
- const [invalid, setInvalid] = useState5({});
2234
+ const [state, setState] = useState10({ values: initial, touched: {} });
2235
+ const [pendingTheme, setPendingTheme] = useState10(activeTheme);
2236
+ const [optimisticActive, setOptimisticActive] = useState10(activeTheme);
2237
+ const [saving, setSaving] = useState10(false);
2238
+ const [switching, setSwitching] = useState10(false);
2239
+ const [error, setError] = useState10(null);
2240
+ const [info, setInfo] = useState10(null);
2241
+ const [invalid, setInvalid] = useState10({});
1381
2242
  function update(key, value) {
1382
2243
  setState((prev) => ({
1383
2244
  values: { ...prev.values, [key]: value },
@@ -1463,23 +2324,23 @@ function ThemeSettingsForm({
1463
2324
  }
1464
2325
  }
1465
2326
  const groups = groupFields(manifest.fields);
1466
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-8", children: [
1467
- /* @__PURE__ */ jsxs10("form", { onSubmit: switchTheme, className: "max-w-xl space-y-3 rounded-md border p-4", children: [
1468
- /* @__PURE__ */ jsxs10("div", { className: "space-y-1", children: [
1469
- /* @__PURE__ */ jsx11(Label4, { htmlFor: "active-theme", className: "text-sm font-medium", children: t("theme.activeLabel") }),
1470
- /* @__PURE__ */ jsxs10("p", { className: "text-xs text-muted-foreground", children: [
2327
+ return /* @__PURE__ */ jsxs17("div", { className: "space-y-8", children: [
2328
+ /* @__PURE__ */ jsxs17("form", { onSubmit: switchTheme, className: "max-w-xl space-y-3 rounded-md border p-4", children: [
2329
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-1", children: [
2330
+ /* @__PURE__ */ jsx19(Label5, { htmlFor: "active-theme", className: "text-sm font-medium", children: t("theme.activeLabel") }),
2331
+ /* @__PURE__ */ jsxs17("p", { className: "text-xs text-muted-foreground", children: [
1471
2332
  t("theme.currentlyActive", { theme: optimisticActive }),
1472
2333
  optimisticActive !== activeTheme && t("theme.propagating")
1473
2334
  ] })
1474
2335
  ] }),
1475
- /* @__PURE__ */ jsx11(
2336
+ /* @__PURE__ */ jsx19(
1476
2337
  "select",
1477
2338
  {
1478
2339
  id: "active-theme",
1479
2340
  className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
1480
2341
  value: pendingTheme,
1481
2342
  onChange: (e) => setPendingTheme(e.target.value),
1482
- children: themeOptions.map((opt) => /* @__PURE__ */ jsxs10("option", { value: opt.value, children: [
2343
+ children: themeOptions.map((opt) => /* @__PURE__ */ jsxs17("option", { value: opt.value, children: [
1483
2344
  resolveLocalized(opt.label, locale),
1484
2345
  " (",
1485
2346
  opt.value,
@@ -1489,10 +2350,10 @@ function ThemeSettingsForm({
1489
2350
  ),
1490
2351
  (() => {
1491
2352
  const desc = themeOptions.find((o) => o.value === pendingTheme)?.description;
1492
- return desc ? /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(desc, locale) }) : null;
2353
+ return desc ? /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(desc, locale) }) : null;
1493
2354
  })(),
1494
- /* @__PURE__ */ jsx11(
1495
- Button8,
2355
+ /* @__PURE__ */ jsx19(
2356
+ Button12,
1496
2357
  {
1497
2358
  type: "submit",
1498
2359
  disabled: switching || pendingTheme === optimisticActive,
@@ -1501,9 +2362,9 @@ function ThemeSettingsForm({
1501
2362
  }
1502
2363
  )
1503
2364
  ] }),
1504
- /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
1505
- /* @__PURE__ */ jsx11(Label4, { className: "text-sm font-medium", children: t("theme.previewLabel") }),
1506
- /* @__PURE__ */ jsx11(
2365
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2366
+ /* @__PURE__ */ jsx19(Label5, { className: "text-sm font-medium", children: t("theme.previewLabel") }),
2367
+ /* @__PURE__ */ jsx19(
1507
2368
  "iframe",
1508
2369
  {
1509
2370
  src: `/?previewTheme=${encodeURIComponent(pendingTheme)}`,
@@ -1512,19 +2373,19 @@ function ThemeSettingsForm({
1512
2373
  },
1513
2374
  pendingTheme
1514
2375
  ),
1515
- /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("theme.previewHint") })
2376
+ /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: t("theme.previewHint") })
1516
2377
  ] }),
1517
- /* @__PURE__ */ jsxs10("form", { onSubmit: save, className: "max-w-xl space-y-6", children: [
1518
- /* @__PURE__ */ jsxs10("div", { children: [
1519
- /* @__PURE__ */ jsx11("h2", { className: "text-lg font-semibold", children: t("theme.customizationHeading", {
2378
+ /* @__PURE__ */ jsxs17("form", { onSubmit: save, className: "max-w-xl space-y-6", children: [
2379
+ /* @__PURE__ */ jsxs17("div", { children: [
2380
+ /* @__PURE__ */ jsx19("h2", { className: "text-lg font-semibold", children: t("theme.customizationHeading", {
1520
2381
  theme: resolveLocalized(manifest.label, locale)
1521
2382
  }) }),
1522
- manifest.description && /* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground", children: resolveLocalized(manifest.description, locale) }),
1523
- /* @__PURE__ */ jsx11("p", { className: "mt-1 text-xs text-muted-foreground", children: t("theme.customizationHint") })
2383
+ manifest.description && /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: resolveLocalized(manifest.description, locale) }),
2384
+ /* @__PURE__ */ jsx19("p", { className: "mt-1 text-xs text-muted-foreground", children: t("theme.customizationHint") })
1524
2385
  ] }),
1525
- groups.map(({ key, name, fields }) => /* @__PURE__ */ jsxs10("fieldset", { className: "space-y-4", children: [
1526
- /* @__PURE__ */ jsx11("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: resolveLocalized(name, locale) }),
1527
- fields.map((field) => /* @__PURE__ */ jsx11(
2386
+ groups.map(({ key, name, fields }) => /* @__PURE__ */ jsxs17("fieldset", { className: "space-y-4", children: [
2387
+ /* @__PURE__ */ jsx19("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: resolveLocalized(name, locale) }),
2388
+ fields.map((field) => /* @__PURE__ */ jsx19(
1528
2389
  FieldRow,
1529
2390
  {
1530
2391
  field,
@@ -1535,9 +2396,9 @@ function ThemeSettingsForm({
1535
2396
  field.key
1536
2397
  ))
1537
2398
  ] }, key)),
1538
- info && /* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground", children: info }),
1539
- error && /* @__PURE__ */ jsx11("p", { className: "text-sm text-destructive", children: error }),
1540
- /* @__PURE__ */ jsx11(Button8, { type: "submit", disabled: saving, children: saving ? t("theme.saving") : t("theme.saveButton") })
2399
+ info && /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: info }),
2400
+ error && /* @__PURE__ */ jsx19("p", { className: "text-sm text-destructive", children: error }),
2401
+ /* @__PURE__ */ jsx19(Button12, { type: "submit", disabled: saving, children: saving ? t("theme.saving") : t("theme.saveButton") })
1541
2402
  ] })
1542
2403
  ] });
1543
2404
  }
@@ -1559,17 +2420,17 @@ function FieldRow({ field, value, invalid, onChange }) {
1559
2420
  const t = useT();
1560
2421
  const locale = useLocale();
1561
2422
  const id = `theme-${field.key}`;
1562
- const labelEl = /* @__PURE__ */ jsx11(Label4, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: resolveLocalized(field.label, locale) });
1563
- const description = field.description ? /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(field.description, locale) }) : null;
2423
+ const labelEl = /* @__PURE__ */ jsx19(Label5, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: resolveLocalized(field.label, locale) });
2424
+ const description = field.description ? /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(field.description, locale) }) : null;
1564
2425
  switch (field.type) {
1565
2426
  case "color":
1566
- return /* @__PURE__ */ jsx11(ColorField, { field, id, labelEl, description, value, invalid, onChange });
2427
+ return /* @__PURE__ */ jsx19(ColorField, { field, id, labelEl, description, value, invalid, onChange });
1567
2428
  case "length":
1568
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
2429
+ return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
1569
2430
  labelEl,
1570
2431
  description,
1571
- /* @__PURE__ */ jsx11(
1572
- Input4,
2432
+ /* @__PURE__ */ jsx19(
2433
+ Input6,
1573
2434
  {
1574
2435
  id,
1575
2436
  value,
@@ -1579,14 +2440,14 @@ function FieldRow({ field, value, invalid, onChange }) {
1579
2440
  className: "font-mono text-xs"
1580
2441
  }
1581
2442
  ),
1582
- /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("theme.lengthHelp") })
2443
+ /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: t("theme.lengthHelp") })
1583
2444
  ] });
1584
2445
  case "select":
1585
2446
  case "fontFamily":
1586
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
2447
+ return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
1587
2448
  labelEl,
1588
2449
  description,
1589
- /* @__PURE__ */ jsxs10(
2450
+ /* @__PURE__ */ jsxs17(
1590
2451
  "select",
1591
2452
  {
1592
2453
  id,
@@ -1595,18 +2456,18 @@ function FieldRow({ field, value, invalid, onChange }) {
1595
2456
  onChange: (e) => onChange(e.target.value),
1596
2457
  "aria-invalid": invalid,
1597
2458
  children: [
1598
- /* @__PURE__ */ jsx11("option", { value: "", children: t("common.default") }),
1599
- field.options.map((opt) => /* @__PURE__ */ jsx11("option", { value: opt.value, children: resolveLocalized(opt.label, locale) }, opt.value))
2459
+ /* @__PURE__ */ jsx19("option", { value: "", children: t("common.default") }),
2460
+ field.options.map((opt) => /* @__PURE__ */ jsx19("option", { value: opt.value, children: resolveLocalized(opt.label, locale) }, opt.value))
1600
2461
  ]
1601
2462
  }
1602
2463
  )
1603
2464
  ] });
1604
2465
  case "image":
1605
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
2466
+ return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
1606
2467
  labelEl,
1607
2468
  description,
1608
- /* @__PURE__ */ jsx11(
1609
- Input4,
2469
+ /* @__PURE__ */ jsx19(
2470
+ Input6,
1610
2471
  {
1611
2472
  id,
1612
2473
  value,
@@ -1617,11 +2478,11 @@ function FieldRow({ field, value, invalid, onChange }) {
1617
2478
  )
1618
2479
  ] });
1619
2480
  case "text":
1620
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
2481
+ return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
1621
2482
  labelEl,
1622
2483
  description,
1623
- /* @__PURE__ */ jsx11(
1624
- Input4,
2484
+ /* @__PURE__ */ jsx19(
2485
+ Input6,
1625
2486
  {
1626
2487
  id,
1627
2488
  value,
@@ -1633,7 +2494,7 @@ function FieldRow({ field, value, invalid, onChange }) {
1633
2494
  )
1634
2495
  ] });
1635
2496
  case "linkList":
1636
- return /* @__PURE__ */ jsx11(
2497
+ return /* @__PURE__ */ jsx19(
1637
2498
  LinkListField,
1638
2499
  {
1639
2500
  field,
@@ -1669,25 +2530,25 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
1669
2530
  next.splice(target, 0, moved);
1670
2531
  commit(next);
1671
2532
  }
1672
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
2533
+ return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
1673
2534
  labelEl,
1674
2535
  description,
1675
- /* @__PURE__ */ jsxs10("div", { className: "space-y-2 rounded-md border bg-muted/20 p-3", children: [
1676
- items.length === 0 && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: "No links yet." }),
2536
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2 rounded-md border bg-muted/20 p-3", children: [
2537
+ items.length === 0 && /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: "No links yet." }),
1677
2538
  items.map((item, idx) => {
1678
2539
  const isTagRef = /^tag:/.test(item.url.trim());
1679
- return /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-start gap-2", children: [
1680
- /* @__PURE__ */ jsxs10("div", { className: "grid flex-1 grid-cols-1 gap-2 sm:grid-cols-2", children: [
1681
- /* @__PURE__ */ jsx11(
1682
- Input4,
2540
+ return /* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap items-start gap-2", children: [
2541
+ /* @__PURE__ */ jsxs17("div", { className: "grid flex-1 grid-cols-1 gap-2 sm:grid-cols-2", children: [
2542
+ /* @__PURE__ */ jsx19(
2543
+ Input6,
1683
2544
  {
1684
2545
  value: item.label,
1685
2546
  placeholder: "Label",
1686
2547
  onChange: (e) => update(idx, { label: e.target.value })
1687
2548
  }
1688
2549
  ),
1689
- /* @__PURE__ */ jsx11(
1690
- Input4,
2550
+ /* @__PURE__ */ jsx19(
2551
+ Input6,
1691
2552
  {
1692
2553
  value: item.url,
1693
2554
  placeholder: "/path or https://\u2026 or tag:name",
@@ -1696,9 +2557,9 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
1696
2557
  }
1697
2558
  )
1698
2559
  ] }),
1699
- /* @__PURE__ */ jsxs10("div", { className: "flex shrink-0 items-center gap-1", children: [
1700
- /* @__PURE__ */ jsx11(
1701
- Button8,
2560
+ /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-1", children: [
2561
+ /* @__PURE__ */ jsx19(
2562
+ Button12,
1702
2563
  {
1703
2564
  type: "button",
1704
2565
  variant: "ghost",
@@ -1709,8 +2570,8 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
1709
2570
  children: "\u2191"
1710
2571
  }
1711
2572
  ),
1712
- /* @__PURE__ */ jsx11(
1713
- Button8,
2573
+ /* @__PURE__ */ jsx19(
2574
+ Button12,
1714
2575
  {
1715
2576
  type: "button",
1716
2577
  variant: "ghost",
@@ -1721,8 +2582,8 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
1721
2582
  children: "\u2193"
1722
2583
  }
1723
2584
  ),
1724
- /* @__PURE__ */ jsx11(
1725
- Button8,
2585
+ /* @__PURE__ */ jsx19(
2586
+ Button12,
1726
2587
  {
1727
2588
  type: "button",
1728
2589
  variant: "ghost",
@@ -1735,8 +2596,8 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
1735
2596
  ] })
1736
2597
  ] }, idx);
1737
2598
  }),
1738
- /* @__PURE__ */ jsx11(
1739
- Button8,
2599
+ /* @__PURE__ */ jsx19(
2600
+ Button12,
1740
2601
  {
1741
2602
  type: "button",
1742
2603
  variant: "outline",
@@ -1746,9 +2607,9 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
1746
2607
  children: "+ Add link"
1747
2608
  }
1748
2609
  ),
1749
- /* @__PURE__ */ jsxs10("p", { className: "text-xs text-muted-foreground", children: [
2610
+ /* @__PURE__ */ jsxs17("p", { className: "text-xs text-muted-foreground", children: [
1750
2611
  "Tip: use ",
1751
- /* @__PURE__ */ jsx11("code", { children: "tag:<name>" }),
2612
+ /* @__PURE__ */ jsx19("code", { children: "tag:<name>" }),
1752
2613
  " as a URL to render a list of posts with that tag instead of a single link."
1753
2614
  ] })
1754
2615
  ] })
@@ -1765,11 +2626,11 @@ function ColorField({
1765
2626
  }) {
1766
2627
  const effective = value || field.default;
1767
2628
  const hex = useColorAsHex(effective);
1768
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
2629
+ return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
1769
2630
  labelEl,
1770
2631
  description,
1771
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
1772
- /* @__PURE__ */ jsx11(
2632
+ /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
2633
+ /* @__PURE__ */ jsx19(
1773
2634
  "input",
1774
2635
  {
1775
2636
  type: "color",
@@ -1779,8 +2640,8 @@ function ColorField({
1779
2640
  "aria-label": `${field.label} swatch`
1780
2641
  }
1781
2642
  ),
1782
- /* @__PURE__ */ jsx11(
1783
- Input4,
2643
+ /* @__PURE__ */ jsx19(
2644
+ Input6,
1784
2645
  {
1785
2646
  id,
1786
2647
  value,
@@ -1791,8 +2652,8 @@ function ColorField({
1791
2652
  }
1792
2653
  )
1793
2654
  ] }),
1794
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
1795
- /* @__PURE__ */ jsx11(
2655
+ /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
2656
+ /* @__PURE__ */ jsx19(
1796
2657
  "span",
1797
2658
  {
1798
2659
  className: "inline-block h-4 w-4 rounded border",
@@ -1800,7 +2661,7 @@ function ColorField({
1800
2661
  "aria-hidden": true
1801
2662
  }
1802
2663
  ),
1803
- /* @__PURE__ */ jsx11("code", { className: "text-xs text-muted-foreground", children: effective })
2664
+ /* @__PURE__ */ jsx19("code", { className: "text-xs text-muted-foreground", children: effective })
1804
2665
  ] })
1805
2666
  ] });
1806
2667
  }
@@ -1820,255 +2681,27 @@ function useColorAsHex(value) {
1820
2681
  }
1821
2682
  }
1822
2683
 
1823
- // src/components/media-uploader.tsx
1824
- import { useState as useState6, useEffect as useEffect3, useCallback, useRef as useRef4 } from "react";
1825
- import { uploadData as uploadData2, list as list2, remove, isCancelError } from "aws-amplify/storage";
1826
- import { processImage as processImage2 } from "ampless/media";
1827
- import { Button as Button9, Input as Input5 } from "@ampless/runtime/ui";
1828
- import { Trash2 as Trash22, Copy, Check, FileText as FileText2, Code2 } from "lucide-react";
1829
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1830
- var IMAGE_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)$/i;
1831
- var STYLESHEET_EXT_RE = /\.css$/i;
1832
- var SCRIPT_EXT_RE = /\.m?js$/i;
1833
- function getExtension(path) {
1834
- const dot = path.lastIndexOf(".");
1835
- return dot >= 0 ? path.slice(dot + 1).toUpperCase() : "FILE";
1836
- }
1837
- function snippetFor2(url, path) {
1838
- if (IMAGE_EXT_RE.test(path)) {
1839
- return `<img src="${url}" alt="" />`;
1840
- }
1841
- if (STYLESHEET_EXT_RE.test(path)) {
1842
- return `<link rel="stylesheet" href="${url}" />`;
1843
- }
1844
- if (SCRIPT_EXT_RE.test(path)) {
1845
- return `<script src="${url}"></script>`;
1846
- }
1847
- return url;
1848
- }
1849
- function sanitizeName2(name) {
1850
- return name.replace(/[ -]/g, "").replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, "_").replace(/^\.+/, "_").slice(0, 200) || "upload";
1851
- }
1852
- function MediaUploader() {
1853
- const t = useT();
1854
- const [items, setItems] = useState6([]);
1855
- const [queue, setQueue] = useState6([]);
1856
- const [uploading, setUploading] = useState6(false);
1857
- const [error, setError] = useState6(null);
1858
- const [copiedPath, setCopiedPath] = useState6(null);
1859
- const uploadTaskRef = useRef4(null);
1860
- const cancelTokenRef = useRef4({ cancelled: false });
1861
- const refresh = useCallback(async () => {
1862
- try {
1863
- const result = await list2({ path: "public/media/" });
1864
- setItems(
1865
- result.items.map((item) => ({
1866
- path: item.path,
1867
- url: publicMediaUrl(item.path)
1868
- }))
1869
- );
1870
- } catch (err) {
1871
- setError(err instanceof Error ? err.message : String(err));
1872
- }
1873
- }, []);
1874
- useEffect3(() => {
1875
- refresh();
1876
- }, [refresh]);
1877
- function handleFiles(e) {
1878
- const files = Array.from(e.target.files ?? []);
1879
- e.target.value = "";
1880
- if (files.length === 0) return;
1881
- setError(null);
1882
- setQueue((prev) => [...prev, ...files]);
1883
- }
1884
- async function handleDialogConfirm(file, options) {
1885
- const token = { cancelled: false };
1886
- cancelTokenRef.current = token;
1887
- setUploading(true);
1888
- setError(null);
1889
- let advance = true;
1890
- try {
1891
- const processed = await processImage2(file, options);
1892
- if (token.cancelled) {
1893
- advance = false;
1894
- return;
1895
- }
1896
- const safeName = sanitizeName2(processed.suggestedName);
1897
- const now = /* @__PURE__ */ new Date();
1898
- const yyyy = now.getFullYear();
1899
- const mm = String(now.getMonth() + 1).padStart(2, "0");
1900
- const path = `public/media/${yyyy}/${mm}/${Date.now()}-${safeName}`;
1901
- const task = uploadData2({
1902
- path,
1903
- data: processed.blob,
1904
- options: { contentType: processed.mime }
1905
- });
1906
- uploadTaskRef.current = task;
1907
- await task.result;
1908
- await refresh();
1909
- } catch (err) {
1910
- if (isCancelError(err) || token.cancelled) {
1911
- advance = false;
1912
- } else {
1913
- setError(err instanceof Error ? err.message : String(err));
1914
- }
1915
- } finally {
1916
- uploadTaskRef.current = null;
1917
- setUploading(false);
1918
- if (advance) {
1919
- setQueue((prev) => prev.slice(1));
1920
- }
1921
- }
1922
- }
1923
- function handleDialogSkip() {
1924
- if (uploading) return;
1925
- setQueue((prev) => prev.slice(1));
1926
- }
1927
- function handleDialogCancel() {
1928
- cancelTokenRef.current.cancelled = true;
1929
- uploadTaskRef.current?.cancel();
1930
- setQueue([]);
1931
- }
1932
- async function handleDelete(path) {
1933
- if (!confirm(t("media.deleteConfirm"))) return;
1934
- try {
1935
- await remove({ path });
1936
- await refresh();
1937
- } catch (err) {
1938
- setError(err instanceof Error ? err.message : String(err));
1939
- }
1940
- }
1941
- async function handleCopy(item, mode) {
1942
- const text = mode === "url" ? item.url : snippetFor2(item.url, item.path);
1943
- await navigator.clipboard.writeText(text);
1944
- const key = `${item.path}:${mode}`;
1945
- setCopiedPath(key);
1946
- setTimeout(() => setCopiedPath((p) => p === key ? null : p), 1500);
1947
- }
1948
- const currentFile = queue[0] ?? null;
1949
- return /* @__PURE__ */ jsxs11("div", { className: "space-y-6", children: [
1950
- /* @__PURE__ */ jsxs11("div", { className: "rounded-md border p-4", children: [
1951
- /* @__PURE__ */ jsx12(
1952
- Input5,
1953
- {
1954
- type: "file",
1955
- multiple: true,
1956
- onChange: handleFiles,
1957
- disabled: uploading
1958
- }
1959
- ),
1960
- uploading && /* @__PURE__ */ jsx12("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.uploading") }),
1961
- !uploading && queue.length > 0 && /* @__PURE__ */ jsx12("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.queued", { count: queue.length }) })
1962
- ] }),
1963
- error && /* @__PURE__ */ jsx12("p", { className: "text-sm text-destructive", children: error }),
1964
- /* @__PURE__ */ jsx12(
1965
- ImageUploadDialog,
1966
- {
1967
- file: currentFile,
1968
- remaining: queue.length,
1969
- busy: uploading,
1970
- defaults: getMediaProcessingDefaults(),
1971
- onConfirm: handleDialogConfirm,
1972
- onSkip: handleDialogSkip,
1973
- onCancel: handleDialogCancel
1974
- }
1975
- ),
1976
- /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4", children: items.map((item) => {
1977
- const isImage = IMAGE_EXT_RE.test(item.path);
1978
- const isStylesheet = STYLESHEET_EXT_RE.test(item.path);
1979
- const isScript = SCRIPT_EXT_RE.test(item.path);
1980
- const filename = item.path.split("/").pop() ?? "";
1981
- const ext = getExtension(item.path);
1982
- const tagSnippet = snippetFor2(item.url, item.path);
1983
- const tagDiffersFromUrl = tagSnippet !== item.url;
1984
- const urlCopied = copiedPath === `${item.path}:url`;
1985
- const tagCopied = copiedPath === `${item.path}:tag`;
1986
- return /* @__PURE__ */ jsxs11(
1987
- "div",
1988
- {
1989
- className: "group relative overflow-hidden rounded-md border bg-[var(--card)]",
1990
- children: [
1991
- isImage ? (
1992
- // eslint-disable-next-line @next/next/no-img-element
1993
- /* @__PURE__ */ jsx12(
1994
- "img",
1995
- {
1996
- src: item.url,
1997
- alt: item.path,
1998
- className: "aspect-square w-full object-cover"
1999
- }
2000
- )
2001
- ) : /* @__PURE__ */ jsxs11("div", { className: "flex aspect-square w-full flex-col items-center justify-center gap-2 bg-muted text-muted-foreground", children: [
2002
- isStylesheet || isScript ? /* @__PURE__ */ jsx12(Code2, { className: "h-8 w-8" }) : /* @__PURE__ */ jsx12(FileText2, { className: "h-8 w-8" }),
2003
- /* @__PURE__ */ jsxs11("span", { className: "font-mono text-xs font-semibold", children: [
2004
- ".",
2005
- ext.toLowerCase()
2006
- ] })
2007
- ] }),
2008
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between border-t px-2 py-1 text-xs", children: [
2009
- /* @__PURE__ */ jsx12("span", { className: "truncate", title: filename, children: filename }),
2010
- /* @__PURE__ */ jsxs11("div", { className: "flex shrink-0 items-center gap-0.5", children: [
2011
- /* @__PURE__ */ jsx12(
2012
- Button9,
2013
- {
2014
- type: "button",
2015
- variant: "ghost",
2016
- size: "icon",
2017
- className: "h-6 w-6",
2018
- onClick: () => handleCopy(item, "url"),
2019
- title: t("media.copyUrl"),
2020
- children: urlCopied ? /* @__PURE__ */ jsx12(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx12(Copy, { className: "h-3 w-3" })
2021
- }
2022
- ),
2023
- tagDiffersFromUrl && /* @__PURE__ */ jsx12(
2024
- Button9,
2025
- {
2026
- type: "button",
2027
- variant: "ghost",
2028
- size: "icon",
2029
- className: "h-6 w-6",
2030
- onClick: () => handleCopy(item, "tag"),
2031
- title: t("media.copyTag"),
2032
- children: tagCopied ? /* @__PURE__ */ jsx12(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx12(Code2, { className: "h-3 w-3" })
2033
- }
2034
- ),
2035
- /* @__PURE__ */ jsx12(
2036
- Button9,
2037
- {
2038
- type: "button",
2039
- variant: "ghost",
2040
- size: "icon",
2041
- className: "h-6 w-6",
2042
- onClick: () => handleDelete(item.path),
2043
- title: t("media.delete"),
2044
- children: /* @__PURE__ */ jsx12(Trash22, { className: "h-3 w-3" })
2045
- }
2046
- )
2047
- ] })
2048
- ] })
2049
- ]
2050
- },
2051
- item.path
2052
- );
2053
- }) }),
2054
- items.length === 0 && /* @__PURE__ */ jsx12("p", { className: "text-center text-sm text-muted-foreground", children: t("media.empty") })
2055
- ] });
2056
- }
2057
-
2058
2684
  export {
2059
2685
  I18nProvider,
2060
2686
  useT,
2061
2687
  useLocale,
2688
+ setAdminCmsConfig,
2689
+ readAdminSiteIdFromCookie,
2690
+ AdminProviders,
2691
+ AdminDashboard,
2692
+ PostsList,
2062
2693
  sanitizeName,
2063
2694
  uploadProcessedImage,
2064
- invalidateSiteSettingsCache,
2065
- Sidebar,
2066
- SiteSelector,
2067
2695
  ImageUploadDialog,
2068
- setAdminCmsConfigClient,
2069
2696
  MediaPicker,
2070
2697
  PostForm,
2698
+ NewPostPage,
2699
+ EditPostPage,
2700
+ MediaUploader,
2701
+ MediaPage,
2702
+ LoginPage,
2703
+ Sidebar,
2704
+ SiteSelector,
2071
2705
  SiteSettingsForm,
2072
- ThemeSettingsForm,
2073
- MediaUploader
2706
+ ThemeSettingsForm
2074
2707
  };