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