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