@ampless/admin 0.2.0-alpha.2 → 0.2.0-alpha.5

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