@ampless/admin 0.2.0-alpha.6 → 0.2.0-alpha.8
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/api/index.d.ts +1 -1
- package/dist/{chunk-FI7CM4LH.js → chunk-KKM2MCM4.js} +74 -2
- package/dist/{chunk-7WFZULH7.js → chunk-QDPB5W35.js} +923 -380
- package/dist/components/index.d.ts +4 -2
- package/dist/components/index.js +2 -2
- package/dist/{i18n-ByHM_Bho.d.ts → i18n-DzXXcIQQ.d.ts} +110 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/pages/index.d.ts +9 -2
- package/dist/pages/index.js +184 -17
- package/package.json +10 -9
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
publicMediaUrl,
|
|
5
5
|
setAdminMediaContext,
|
|
6
6
|
translate
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KKM2MCM4.js";
|
|
8
8
|
import {
|
|
9
9
|
invalidateSiteSettingsCache
|
|
10
10
|
} from "./chunk-VXEVLHGL.js";
|
|
@@ -82,6 +82,18 @@ function decodeBody(value) {
|
|
|
82
82
|
return value;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
function decodeMetadata(value) {
|
|
86
|
+
if (value === null || value === void 0) return void 0;
|
|
87
|
+
const parsed = typeof value === "string" ? safeJsonParse(value) : value;
|
|
88
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
89
|
+
}
|
|
90
|
+
function safeJsonParse(value) {
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(value);
|
|
93
|
+
} catch {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
85
97
|
function toCorePost(p) {
|
|
86
98
|
return {
|
|
87
99
|
postId: p.postId,
|
|
@@ -93,7 +105,8 @@ function toCorePost(p) {
|
|
|
93
105
|
body: decodeBody(p.body),
|
|
94
106
|
status: p.status ?? "draft",
|
|
95
107
|
publishedAt: p.publishedAt ?? void 0,
|
|
96
|
-
tags: (p.tags ?? []).filter((t) => typeof t === "string")
|
|
108
|
+
tags: (p.tags ?? []).filter((t) => typeof t === "string"),
|
|
109
|
+
metadata: decodeMetadata(p.metadata)
|
|
97
110
|
};
|
|
98
111
|
}
|
|
99
112
|
function postTagEntries(post) {
|
|
@@ -190,6 +203,7 @@ function installAdminPostsProvider() {
|
|
|
190
203
|
status: input.status,
|
|
191
204
|
publishedAt: input.publishedAt,
|
|
192
205
|
tags: input.tags,
|
|
206
|
+
...input.metadata !== void 0 && { metadata: encodeBody(input.metadata) },
|
|
193
207
|
// Denormalized GSI keys. Must match every change to slug /
|
|
194
208
|
// status — see the update() branch below.
|
|
195
209
|
siteIdStatus: composeSiteIdStatus(input.siteId, input.status),
|
|
@@ -216,6 +230,7 @@ function installAdminPostsProvider() {
|
|
|
216
230
|
...patch.status !== void 0 && { status: patch.status },
|
|
217
231
|
...patch.publishedAt !== void 0 && { publishedAt: patch.publishedAt },
|
|
218
232
|
...patch.tags !== void 0 && { tags: patch.tags },
|
|
233
|
+
...patch.metadata !== void 0 && { metadata: encodeBody(patch.metadata) },
|
|
219
234
|
...patch.status !== void 0 && nextStatus && { siteIdStatus: composeSiteIdStatus(siteId, nextStatus) },
|
|
220
235
|
...patch.slug !== void 0 && nextSlug && { siteIdSlug: composeSiteIdSlug(siteId, nextSlug) }
|
|
221
236
|
});
|
|
@@ -351,12 +366,12 @@ function AdminDashboard() {
|
|
|
351
366
|
}, []);
|
|
352
367
|
const published = posts.filter((p) => p.status === "published").length;
|
|
353
368
|
const drafts = posts.filter((p) => p.status === "draft").length;
|
|
354
|
-
return /* @__PURE__ */ jsxs("div", { className: "p-8", children: [
|
|
355
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-
|
|
356
|
-
/* @__PURE__ */ jsx3("h1", { className: "text-
|
|
369
|
+
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
|
|
370
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3 md:mb-8", children: [
|
|
371
|
+
/* @__PURE__ */ jsx3("h1", { className: "text-2xl font-bold md:text-3xl", children: t("dashboard.title") }),
|
|
357
372
|
/* @__PURE__ */ jsx3(Button, { asChild: true, children: /* @__PURE__ */ jsx3(Link, { href: "/admin/posts/new", children: t("dashboard.newPost") }) })
|
|
358
373
|
] }),
|
|
359
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4
|
|
374
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: [
|
|
360
375
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
361
376
|
/* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.totalPosts") }) }),
|
|
362
377
|
/* @__PURE__ */ jsxs(CardContent, { children: [
|
|
@@ -398,15 +413,15 @@ function PostsList() {
|
|
|
398
413
|
const siteId = readAdminSiteIdFromCookie();
|
|
399
414
|
listPosts2({ status: "all", siteId }).then(setPosts).finally(() => setLoading(false));
|
|
400
415
|
}, []);
|
|
401
|
-
return /* @__PURE__ */ jsxs2("div", { className: "p-8", children: [
|
|
402
|
-
/* @__PURE__ */ jsxs2("div", { className: "mb-
|
|
403
|
-
/* @__PURE__ */ jsx4("h1", { className: "text-
|
|
416
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
|
|
417
|
+
/* @__PURE__ */ jsxs2("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3 md:mb-8", children: [
|
|
418
|
+
/* @__PURE__ */ jsx4("h1", { className: "text-2xl font-bold md:text-3xl", children: t("posts.list.title") }),
|
|
404
419
|
/* @__PURE__ */ jsx4(Button2, { asChild: true, children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.newButton") }) })
|
|
405
420
|
] }),
|
|
406
421
|
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
422
|
/* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("posts.list.empty") }),
|
|
408
423
|
/* @__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: [
|
|
424
|
+
] }) : /* @__PURE__ */ jsx4("div", { className: "overflow-x-auto rounded-md border", children: /* @__PURE__ */ jsxs2(Table, { children: [
|
|
410
425
|
/* @__PURE__ */ jsx4(TableHeader, { children: /* @__PURE__ */ jsxs2(TableRow, { children: [
|
|
411
426
|
/* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnTitle") }),
|
|
412
427
|
/* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnStatus") }),
|
|
@@ -916,7 +931,7 @@ function MediaPicker({ trigger, onSelect }) {
|
|
|
916
931
|
}
|
|
917
932
|
|
|
918
933
|
// src/components/post-form.tsx
|
|
919
|
-
import { useRef as useRef3, useState as
|
|
934
|
+
import { useRef as useRef3, useState as useState6 } from "react";
|
|
920
935
|
import { useRouter } from "next/navigation";
|
|
921
936
|
import { Image as ImageIcon3 } from "lucide-react";
|
|
922
937
|
import {
|
|
@@ -932,7 +947,7 @@ import {
|
|
|
932
947
|
markdownToHtml,
|
|
933
948
|
htmlToMarkdown
|
|
934
949
|
} from "@ampless/runtime";
|
|
935
|
-
import { Button as
|
|
950
|
+
import { Button as Button8, Input as Input2, Label as Label2, Textarea } from "@ampless/runtime/ui";
|
|
936
951
|
|
|
937
952
|
// src/editor/tiptap-editor.tsx
|
|
938
953
|
import { useEditor, EditorContent } from "@tiptap/react";
|
|
@@ -1029,7 +1044,7 @@ function ImageBubbleMenu({ editor }) {
|
|
|
1029
1044
|
if (alt === null) return;
|
|
1030
1045
|
editor.chain().focus().updateAttributes("image", { alt }).run();
|
|
1031
1046
|
};
|
|
1032
|
-
const
|
|
1047
|
+
const remove3 = () => {
|
|
1033
1048
|
editor.chain().focus().deleteSelection().run();
|
|
1034
1049
|
};
|
|
1035
1050
|
const setDisplay = (display) => {
|
|
@@ -1080,7 +1095,7 @@ function ImageBubbleMenu({ editor }) {
|
|
|
1080
1095
|
/* @__PURE__ */ jsx8(Pencil, { className: "mr-1 h-3 w-3" }),
|
|
1081
1096
|
t("editor.image.alt")
|
|
1082
1097
|
] }),
|
|
1083
|
-
/* @__PURE__ */ jsxs6(Button6, { type: "button", variant: "ghost", size: "sm", onClick:
|
|
1098
|
+
/* @__PURE__ */ jsxs6(Button6, { type: "button", variant: "ghost", size: "sm", onClick: remove3, children: [
|
|
1084
1099
|
/* @__PURE__ */ jsx8(Trash2, { className: "mr-1 h-3 w-3" }),
|
|
1085
1100
|
t("editor.image.delete")
|
|
1086
1101
|
] })
|
|
@@ -1134,8 +1149,397 @@ function TiptapEditor({ initialContent, onChange }) {
|
|
|
1134
1149
|
] });
|
|
1135
1150
|
}
|
|
1136
1151
|
|
|
1137
|
-
// src/components/
|
|
1152
|
+
// src/components/static-uploader.tsx
|
|
1153
|
+
import { useState as useState5 } from "react";
|
|
1154
|
+
import { FileText, AlertTriangle, FileArchive, X } from "lucide-react";
|
|
1155
|
+
import { Button as Button7 } from "@ampless/runtime/ui";
|
|
1156
|
+
|
|
1157
|
+
// src/lib/static-bundle.ts
|
|
1158
|
+
import JSZip from "jszip";
|
|
1159
|
+
import { uploadData as uploadData2, list as list2, remove } from "aws-amplify/storage";
|
|
1160
|
+
var DEFAULT_ENTRYPOINT = "index.html";
|
|
1161
|
+
var MAX_BUNDLE_BYTES = 50 * 1024 * 1024;
|
|
1162
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".css", ".svg"]);
|
|
1163
|
+
var MIME_TYPES = {
|
|
1164
|
+
".html": "text/html; charset=utf-8",
|
|
1165
|
+
".htm": "text/html; charset=utf-8",
|
|
1166
|
+
".css": "text/css; charset=utf-8",
|
|
1167
|
+
".js": "application/javascript; charset=utf-8",
|
|
1168
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
1169
|
+
".json": "application/json; charset=utf-8",
|
|
1170
|
+
".svg": "image/svg+xml",
|
|
1171
|
+
".png": "image/png",
|
|
1172
|
+
".jpg": "image/jpeg",
|
|
1173
|
+
".jpeg": "image/jpeg",
|
|
1174
|
+
".gif": "image/gif",
|
|
1175
|
+
".webp": "image/webp",
|
|
1176
|
+
".avif": "image/avif",
|
|
1177
|
+
".ico": "image/x-icon",
|
|
1178
|
+
".woff": "font/woff",
|
|
1179
|
+
".woff2": "font/woff2",
|
|
1180
|
+
".ttf": "font/ttf",
|
|
1181
|
+
".otf": "font/otf",
|
|
1182
|
+
".eot": "application/vnd.ms-fontobject",
|
|
1183
|
+
".txt": "text/plain; charset=utf-8",
|
|
1184
|
+
".xml": "application/xml; charset=utf-8",
|
|
1185
|
+
".map": "application/json; charset=utf-8",
|
|
1186
|
+
".pdf": "application/pdf"
|
|
1187
|
+
};
|
|
1188
|
+
function mimeTypeFor(path) {
|
|
1189
|
+
const lower = path.toLowerCase();
|
|
1190
|
+
const dot = lower.lastIndexOf(".");
|
|
1191
|
+
if (dot < 0) return "application/octet-stream";
|
|
1192
|
+
return MIME_TYPES[lower.slice(dot)] ?? "application/octet-stream";
|
|
1193
|
+
}
|
|
1194
|
+
function validateBundlePath(path) {
|
|
1195
|
+
if (path === "" || path.endsWith("/")) return "directory entry";
|
|
1196
|
+
if (path.includes("\0")) return "contains null byte";
|
|
1197
|
+
if (path.startsWith("/") || path.startsWith("\\")) return "absolute path";
|
|
1198
|
+
if (path.split(/[/\\]/).some((seg) => seg === "..")) return "parent-directory traversal";
|
|
1199
|
+
if (path.startsWith("__MACOSX/") || /(^|\/)\._/.test(path)) return "macOS resource fork";
|
|
1200
|
+
if (/(^|\/)\.DS_Store$/.test(path)) return ".DS_Store junk";
|
|
1201
|
+
if (/(^|\/)Thumbs\.db$/i.test(path)) return "Thumbs.db junk";
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
var HTML_URL_ATTR_RE = /\b(?:href|src|action|data|poster|cite|formaction|manifest|srcset)\s*=\s*["']([^"']+)["']/gi;
|
|
1205
|
+
var CSS_URL_RE = /url\(\s*["']?([^"')\s]+)["']?\s*\)|@import\s+["']([^"']+)["']/gi;
|
|
1206
|
+
function findAbsolutePathRefs(path, content) {
|
|
1207
|
+
const ext = path.toLowerCase().slice(path.lastIndexOf("."));
|
|
1208
|
+
if (!TEXT_EXTENSIONS.has(ext)) return [];
|
|
1209
|
+
const issues = [];
|
|
1210
|
+
function check(url, lineHint) {
|
|
1211
|
+
const trimmed = url.trim();
|
|
1212
|
+
if (!trimmed) return;
|
|
1213
|
+
if (trimmed.startsWith("#")) return;
|
|
1214
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) return;
|
|
1215
|
+
if (trimmed.startsWith("//")) {
|
|
1216
|
+
issues.push({ path, reason: `protocol-relative URL: ${lineHint}` });
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
if (trimmed.startsWith("/")) {
|
|
1220
|
+
issues.push({ path, reason: `absolute path: ${lineHint}` });
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
if (ext === ".html" || ext === ".htm" || ext === ".svg") {
|
|
1225
|
+
HTML_URL_ATTR_RE.lastIndex = 0;
|
|
1226
|
+
let m;
|
|
1227
|
+
while ((m = HTML_URL_ATTR_RE.exec(content)) !== null) {
|
|
1228
|
+
const val = m[1] ?? "";
|
|
1229
|
+
if (/\bsrcset\s*=/i.test(m[0])) {
|
|
1230
|
+
for (const candidate of val.split(",")) {
|
|
1231
|
+
const urlPart = candidate.trim().split(/\s+/)[0];
|
|
1232
|
+
if (urlPart) check(urlPart, candidate.trim());
|
|
1233
|
+
}
|
|
1234
|
+
} else {
|
|
1235
|
+
check(val, m[0]);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (ext === ".css" || ext === ".svg") {
|
|
1240
|
+
CSS_URL_RE.lastIndex = 0;
|
|
1241
|
+
let m;
|
|
1242
|
+
while ((m = CSS_URL_RE.exec(content)) !== null) {
|
|
1243
|
+
const val = (m[1] ?? m[2] ?? "").trim();
|
|
1244
|
+
check(val, m[0]);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return issues;
|
|
1248
|
+
}
|
|
1249
|
+
async function extractZip(file) {
|
|
1250
|
+
const zip = await JSZip.loadAsync(file);
|
|
1251
|
+
const files = [];
|
|
1252
|
+
const issues = [];
|
|
1253
|
+
let totalBytes = 0;
|
|
1254
|
+
for (const entry of Object.values(zip.files)) {
|
|
1255
|
+
if (entry.dir) continue;
|
|
1256
|
+
const reason = validateBundlePath(entry.name);
|
|
1257
|
+
if (reason) {
|
|
1258
|
+
const silent = reason === "macOS resource fork" || reason === ".DS_Store junk" || reason === "Thumbs.db junk";
|
|
1259
|
+
if (!silent) issues.push({ path: entry.name, reason });
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
const data = await entry.async("uint8array");
|
|
1263
|
+
totalBytes += data.byteLength;
|
|
1264
|
+
files.push({ path: entry.name, data });
|
|
1265
|
+
}
|
|
1266
|
+
return { files: stripCommonPrefix(files), issues, totalBytes };
|
|
1267
|
+
}
|
|
1268
|
+
function stripCommonPrefix(files) {
|
|
1269
|
+
if (files.length === 0) return files;
|
|
1270
|
+
const firstSlash = files[0].path.indexOf("/");
|
|
1271
|
+
if (firstSlash < 0) return files;
|
|
1272
|
+
const prefix = files[0].path.slice(0, firstSlash + 1);
|
|
1273
|
+
if (!files.every((f) => f.path.startsWith(prefix))) return files;
|
|
1274
|
+
return files.map((f) => ({ ...f, path: f.path.slice(prefix.length) }));
|
|
1275
|
+
}
|
|
1276
|
+
function validateBundle(files) {
|
|
1277
|
+
const issues = [];
|
|
1278
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
1279
|
+
for (const f of files) {
|
|
1280
|
+
const ext = f.path.toLowerCase().slice(f.path.lastIndexOf("."));
|
|
1281
|
+
if (!TEXT_EXTENSIONS.has(ext)) continue;
|
|
1282
|
+
const text = decoder.decode(f.data);
|
|
1283
|
+
issues.push(...findAbsolutePathRefs(f.path, text));
|
|
1284
|
+
}
|
|
1285
|
+
return issues;
|
|
1286
|
+
}
|
|
1287
|
+
function bundlePrefix(siteId, slug) {
|
|
1288
|
+
return `public/static/${siteId}/${slug}/`;
|
|
1289
|
+
}
|
|
1290
|
+
async function uploadBundle(opts) {
|
|
1291
|
+
if (opts.files.length === 0) {
|
|
1292
|
+
throw new Error("Bundle is empty.");
|
|
1293
|
+
}
|
|
1294
|
+
const totalBytes = opts.files.reduce((sum, f) => sum + f.data.byteLength, 0);
|
|
1295
|
+
if (totalBytes > MAX_BUNDLE_BYTES) {
|
|
1296
|
+
throw new Error(
|
|
1297
|
+
`Bundle too large: ${Math.round(totalBytes / 1024 / 1024)} MB exceeds the ${Math.round(MAX_BUNDLE_BYTES / 1024 / 1024)} MB ceiling for browser-side upload.`
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
const entrypoint = opts.entrypoint ?? pickDefaultEntrypoint(opts.files);
|
|
1301
|
+
if (!opts.files.some((f) => f.path === entrypoint)) {
|
|
1302
|
+
throw new Error(`Entrypoint "${entrypoint}" is not present in the bundle.`);
|
|
1303
|
+
}
|
|
1304
|
+
await deleteBundle(opts.siteId, opts.slug).catch(() => void 0);
|
|
1305
|
+
const prefix = bundlePrefix(opts.siteId, opts.slug);
|
|
1306
|
+
let uploaded = 0;
|
|
1307
|
+
for (const f of opts.files) {
|
|
1308
|
+
const task = uploadData2({
|
|
1309
|
+
path: `${prefix}${f.path}`,
|
|
1310
|
+
data: f.data,
|
|
1311
|
+
// Forcing Content-Type at upload means CloudFront / browsers see
|
|
1312
|
+
// it directly when serving the file via the public bucket URL.
|
|
1313
|
+
// (The runtime route handler overrides it for the proxied path,
|
|
1314
|
+
// but tooling that hits S3 directly benefits from a correct CT.)
|
|
1315
|
+
options: { contentType: mimeTypeFor(f.path) }
|
|
1316
|
+
});
|
|
1317
|
+
await task.result;
|
|
1318
|
+
uploaded += f.data.byteLength;
|
|
1319
|
+
opts.onProgress?.(uploaded, totalBytes);
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
entrypoint,
|
|
1323
|
+
files: opts.files.map((f) => f.path).sort(),
|
|
1324
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
async function deleteBundle(siteId, slug) {
|
|
1328
|
+
const prefix = bundlePrefix(siteId, slug);
|
|
1329
|
+
const result = await list2({ path: prefix });
|
|
1330
|
+
for (const item of result.items) {
|
|
1331
|
+
await remove({ path: item.path });
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
function pickDefaultEntrypoint(files) {
|
|
1335
|
+
const exact = files.find((f) => f.path === DEFAULT_ENTRYPOINT);
|
|
1336
|
+
if (exact) return exact.path;
|
|
1337
|
+
const altRoot = files.find((f) => f.path === "index.htm");
|
|
1338
|
+
if (altRoot) return altRoot.path;
|
|
1339
|
+
const htmlRoot = files.filter((f) => /^[^/]+\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path));
|
|
1340
|
+
if (htmlRoot.length > 0) return htmlRoot[0].path;
|
|
1341
|
+
const htmlAny = files.filter((f) => /\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path));
|
|
1342
|
+
if (htmlAny.length > 0) return htmlAny[0].path;
|
|
1343
|
+
return DEFAULT_ENTRYPOINT;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// src/components/static-uploader.tsx
|
|
1138
1347
|
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1348
|
+
function StaticUploader({ initial, onFilesReady, onClear }) {
|
|
1349
|
+
const t = useT();
|
|
1350
|
+
const [pending, setPending] = useState5(null);
|
|
1351
|
+
const [issues, setIssues] = useState5([]);
|
|
1352
|
+
const [busy, setBusy] = useState5(false);
|
|
1353
|
+
const [error, setError] = useState5(null);
|
|
1354
|
+
async function handleZip(file) {
|
|
1355
|
+
setBusy(true);
|
|
1356
|
+
setError(null);
|
|
1357
|
+
try {
|
|
1358
|
+
const { files, issues: structuralIssues } = await extractZip(file);
|
|
1359
|
+
if (files.length === 0) {
|
|
1360
|
+
setError(t("posts.form.static.emptyBundle"));
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const contentIssues = validateBundle(files);
|
|
1364
|
+
const all = [...structuralIssues, ...contentIssues];
|
|
1365
|
+
setPending(files);
|
|
1366
|
+
setIssues(all);
|
|
1367
|
+
if (all.length === 0) {
|
|
1368
|
+
onFilesReady(files, guessEntrypoint(files));
|
|
1369
|
+
}
|
|
1370
|
+
} catch (e) {
|
|
1371
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1372
|
+
} finally {
|
|
1373
|
+
setBusy(false);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
async function handleLooseFiles(files) {
|
|
1377
|
+
setBusy(true);
|
|
1378
|
+
setError(null);
|
|
1379
|
+
try {
|
|
1380
|
+
const extracted = [];
|
|
1381
|
+
const structural = [];
|
|
1382
|
+
for (const f of Array.from(files)) {
|
|
1383
|
+
const rel = f.webkitRelativePath ?? f.name;
|
|
1384
|
+
const stripped = rel.includes("/") ? rel.slice(rel.indexOf("/") + 1) : rel;
|
|
1385
|
+
const reason = validateBundlePath(stripped);
|
|
1386
|
+
if (reason) {
|
|
1387
|
+
structural.push({ path: stripped, reason });
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
const buf = new Uint8Array(await f.arrayBuffer());
|
|
1391
|
+
extracted.push({ path: stripped, data: buf });
|
|
1392
|
+
}
|
|
1393
|
+
if (extracted.length === 0) {
|
|
1394
|
+
setError(t("posts.form.static.emptyBundle"));
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
const content = validateBundle(extracted);
|
|
1398
|
+
const all = [...structural, ...content];
|
|
1399
|
+
setPending(extracted);
|
|
1400
|
+
setIssues(all);
|
|
1401
|
+
if (all.length === 0) {
|
|
1402
|
+
onFilesReady(extracted, guessEntrypoint(extracted));
|
|
1403
|
+
}
|
|
1404
|
+
} catch (e) {
|
|
1405
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1406
|
+
} finally {
|
|
1407
|
+
setBusy(false);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function onPickerChange(e) {
|
|
1411
|
+
const fl = e.target.files;
|
|
1412
|
+
if (!fl || fl.length === 0) return;
|
|
1413
|
+
e.target.value = "";
|
|
1414
|
+
if (fl.length === 1 && fl[0].name.toLowerCase().endsWith(".zip")) {
|
|
1415
|
+
void handleZip(fl[0]);
|
|
1416
|
+
} else {
|
|
1417
|
+
void handleLooseFiles(fl);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
function clearPending() {
|
|
1421
|
+
setPending(null);
|
|
1422
|
+
setIssues([]);
|
|
1423
|
+
setError(null);
|
|
1424
|
+
onClear();
|
|
1425
|
+
}
|
|
1426
|
+
const showCurrent = !pending && initial && initial.files.length > 0;
|
|
1427
|
+
return /* @__PURE__ */ jsxs8("div", { className: "space-y-4", children: [
|
|
1428
|
+
/* @__PURE__ */ jsxs8("div", { className: "rounded-md border border-dashed p-4", children: [
|
|
1429
|
+
/* @__PURE__ */ jsxs8("label", { className: "flex flex-col items-start gap-2 text-sm", children: [
|
|
1430
|
+
/* @__PURE__ */ jsx10("span", { className: "font-medium", children: t("posts.form.static.pick") }),
|
|
1431
|
+
/* @__PURE__ */ jsx10(
|
|
1432
|
+
"input",
|
|
1433
|
+
{
|
|
1434
|
+
type: "file",
|
|
1435
|
+
accept: ".zip,application/zip,*/*",
|
|
1436
|
+
multiple: true,
|
|
1437
|
+
onChange: onPickerChange,
|
|
1438
|
+
disabled: busy
|
|
1439
|
+
}
|
|
1440
|
+
),
|
|
1441
|
+
/* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", children: t("posts.form.static.pickHint") })
|
|
1442
|
+
] }),
|
|
1443
|
+
busy && /* @__PURE__ */ jsx10("p", { className: "mt-2 text-sm text-muted-foreground", children: t("common.loading") }),
|
|
1444
|
+
error && /* @__PURE__ */ jsx10("p", { className: "mt-2 text-sm text-destructive", children: error })
|
|
1445
|
+
] }),
|
|
1446
|
+
showCurrent && /* @__PURE__ */ jsx10(CurrentBundle, { body: initial }),
|
|
1447
|
+
pending && /* @__PURE__ */ jsx10(
|
|
1448
|
+
PendingBundle,
|
|
1449
|
+
{
|
|
1450
|
+
files: pending,
|
|
1451
|
+
issues,
|
|
1452
|
+
onClear: clearPending
|
|
1453
|
+
}
|
|
1454
|
+
)
|
|
1455
|
+
] });
|
|
1456
|
+
}
|
|
1457
|
+
function CurrentBundle({ body }) {
|
|
1458
|
+
const t = useT();
|
|
1459
|
+
return /* @__PURE__ */ jsxs8("div", { className: "rounded-md border bg-muted/30 p-3", children: [
|
|
1460
|
+
/* @__PURE__ */ jsxs8("div", { className: "mb-2 flex items-center gap-2 text-sm font-medium", children: [
|
|
1461
|
+
/* @__PURE__ */ jsx10(FileArchive, { className: "h-4 w-4" }),
|
|
1462
|
+
t("posts.form.static.currentBundle", {
|
|
1463
|
+
count: body.files.length,
|
|
1464
|
+
entrypoint: body.entrypoint
|
|
1465
|
+
})
|
|
1466
|
+
] }),
|
|
1467
|
+
/* @__PURE__ */ jsx10(FileList, { files: body.files })
|
|
1468
|
+
] });
|
|
1469
|
+
}
|
|
1470
|
+
function PendingBundle({
|
|
1471
|
+
files,
|
|
1472
|
+
issues,
|
|
1473
|
+
onClear
|
|
1474
|
+
}) {
|
|
1475
|
+
const t = useT();
|
|
1476
|
+
const totalBytes = files.reduce((sum, f) => sum + f.data.byteLength, 0);
|
|
1477
|
+
return /* @__PURE__ */ jsxs8("div", { className: "rounded-md border p-3", children: [
|
|
1478
|
+
/* @__PURE__ */ jsxs8("div", { className: "mb-2 flex items-center justify-between gap-2 text-sm font-medium", children: [
|
|
1479
|
+
/* @__PURE__ */ jsxs8("span", { className: "flex items-center gap-2", children: [
|
|
1480
|
+
/* @__PURE__ */ jsx10(FileArchive, { className: "h-4 w-4" }),
|
|
1481
|
+
t("posts.form.static.pendingBundle", {
|
|
1482
|
+
count: files.length,
|
|
1483
|
+
size: formatBytes2(totalBytes)
|
|
1484
|
+
})
|
|
1485
|
+
] }),
|
|
1486
|
+
/* @__PURE__ */ jsxs8(Button7, { type: "button", variant: "ghost", size: "sm", onClick: onClear, children: [
|
|
1487
|
+
/* @__PURE__ */ jsx10(X, { className: "mr-1 h-3 w-3" }),
|
|
1488
|
+
t("common.cancel")
|
|
1489
|
+
] })
|
|
1490
|
+
] }),
|
|
1491
|
+
issues.length > 0 && /* @__PURE__ */ jsxs8("div", { className: "mb-3 rounded-md border border-destructive/40 bg-destructive/5 p-2 text-sm", children: [
|
|
1492
|
+
/* @__PURE__ */ jsxs8("div", { className: "mb-1 flex items-center gap-2 font-medium text-destructive", children: [
|
|
1493
|
+
/* @__PURE__ */ jsx10(AlertTriangle, { className: "h-4 w-4" }),
|
|
1494
|
+
t("posts.form.static.issuesTitle", { count: issues.length })
|
|
1495
|
+
] }),
|
|
1496
|
+
/* @__PURE__ */ jsxs8("ul", { className: "space-y-0.5 text-xs", children: [
|
|
1497
|
+
issues.slice(0, 20).map((issue, idx) => /* @__PURE__ */ jsxs8("li", { className: "font-mono", children: [
|
|
1498
|
+
issue.path,
|
|
1499
|
+
": ",
|
|
1500
|
+
issue.reason
|
|
1501
|
+
] }, `${issue.path}-${idx}`)),
|
|
1502
|
+
issues.length > 20 && /* @__PURE__ */ jsxs8("li", { className: "font-mono text-muted-foreground", children: [
|
|
1503
|
+
"\u2026 ",
|
|
1504
|
+
issues.length - 20,
|
|
1505
|
+
" more"
|
|
1506
|
+
] })
|
|
1507
|
+
] }),
|
|
1508
|
+
/* @__PURE__ */ jsx10("p", { className: "mt-2 text-xs text-muted-foreground", children: t("posts.form.static.issuesHint") })
|
|
1509
|
+
] }),
|
|
1510
|
+
/* @__PURE__ */ jsx10(FileList, { files: files.map((f) => f.path) })
|
|
1511
|
+
] });
|
|
1512
|
+
}
|
|
1513
|
+
function FileList({ files }) {
|
|
1514
|
+
return /* @__PURE__ */ jsxs8("ul", { className: "space-y-0.5 text-xs", children: [
|
|
1515
|
+
files.slice(0, 40).map((path) => /* @__PURE__ */ jsxs8("li", { className: "flex items-center gap-1.5 font-mono text-muted-foreground", children: [
|
|
1516
|
+
/* @__PURE__ */ jsx10(FileText, { className: "h-3 w-3 shrink-0" }),
|
|
1517
|
+
path
|
|
1518
|
+
] }, path)),
|
|
1519
|
+
files.length > 40 && /* @__PURE__ */ jsxs8("li", { className: "font-mono text-xs text-muted-foreground", children: [
|
|
1520
|
+
"\u2026 ",
|
|
1521
|
+
files.length - 40,
|
|
1522
|
+
" more"
|
|
1523
|
+
] })
|
|
1524
|
+
] });
|
|
1525
|
+
}
|
|
1526
|
+
function formatBytes2(bytes) {
|
|
1527
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1528
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1529
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
1530
|
+
}
|
|
1531
|
+
function guessEntrypoint(files) {
|
|
1532
|
+
const exact = files.find((f) => f.path === "index.html");
|
|
1533
|
+
if (exact) return "index.html";
|
|
1534
|
+
const alt = files.find((f) => f.path === "index.htm");
|
|
1535
|
+
if (alt) return "index.htm";
|
|
1536
|
+
const rootHtml = files.filter((f) => /^[^/]+\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path))[0];
|
|
1537
|
+
if (rootHtml) return rootHtml.path;
|
|
1538
|
+
return files[0]?.path ?? "index.html";
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// src/components/post-form.tsx
|
|
1542
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1139
1543
|
var EMPTY_TIPTAP_DOC = { type: "doc", content: [{ type: "paragraph" }] };
|
|
1140
1544
|
var IMAGE_URL_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)(\?|$)/i;
|
|
1141
1545
|
var STYLESHEET_URL_RE = /\.css(\?|$)/i;
|
|
@@ -1155,23 +1559,36 @@ function slugify(s) {
|
|
|
1155
1559
|
}
|
|
1156
1560
|
function defaultBodyForFormat(format) {
|
|
1157
1561
|
if (format === "tiptap") return EMPTY_TIPTAP_DOC;
|
|
1562
|
+
if (format === "static") return null;
|
|
1158
1563
|
return "";
|
|
1159
1564
|
}
|
|
1565
|
+
function isStaticBody(value) {
|
|
1566
|
+
return !!value && typeof value === "object" && "entrypoint" in value && "files" in value && Array.isArray(value.files);
|
|
1567
|
+
}
|
|
1160
1568
|
function PostForm({ post }) {
|
|
1161
1569
|
const router = useRouter();
|
|
1162
1570
|
const t = useT();
|
|
1163
1571
|
const isEdit = !!post;
|
|
1164
1572
|
const bodyTextareaRef = useRef3(null);
|
|
1165
|
-
const [title, setTitle] =
|
|
1166
|
-
const [slug, setSlug] =
|
|
1167
|
-
const [excerpt, setExcerpt] =
|
|
1168
|
-
const [format, setFormat] =
|
|
1169
|
-
const [body, setBody] =
|
|
1170
|
-
const [status, setStatus] =
|
|
1171
|
-
const [tagsInput, setTagsInput] =
|
|
1172
|
-
const [
|
|
1173
|
-
const [
|
|
1174
|
-
const [
|
|
1573
|
+
const [title, setTitle] = useState6(post?.title ?? "");
|
|
1574
|
+
const [slug, setSlug] = useState6(post?.slug ?? "");
|
|
1575
|
+
const [excerpt, setExcerpt] = useState6(post?.excerpt ?? "");
|
|
1576
|
+
const [format, setFormat] = useState6(post?.format ?? "tiptap");
|
|
1577
|
+
const [body, setBody] = useState6(post?.body ?? EMPTY_TIPTAP_DOC);
|
|
1578
|
+
const [status, setStatus] = useState6(post?.status ?? "draft");
|
|
1579
|
+
const [tagsInput, setTagsInput] = useState6((post?.tags ?? []).join(", "));
|
|
1580
|
+
const [noLayout, setNoLayout] = useState6(post?.metadata?.no_layout === true);
|
|
1581
|
+
const [saving, setSaving] = useState6(false);
|
|
1582
|
+
const [error, setError] = useState6(null);
|
|
1583
|
+
const [view, setView] = useState6("edit");
|
|
1584
|
+
const [pendingBundle, setPendingBundle] = useState6(null);
|
|
1585
|
+
const initialStaticBody = isStaticBody(post?.body) ? post.body : null;
|
|
1586
|
+
function buildMetadata() {
|
|
1587
|
+
const next = { ...post?.metadata ?? {} };
|
|
1588
|
+
if (noLayout && format === "html") next.no_layout = true;
|
|
1589
|
+
else delete next.no_layout;
|
|
1590
|
+
return Object.keys(next).length > 0 ? next : void 0;
|
|
1591
|
+
}
|
|
1175
1592
|
function parseTags(raw) {
|
|
1176
1593
|
return Array.from(
|
|
1177
1594
|
new Set(
|
|
@@ -1203,31 +1620,37 @@ function PostForm({ post }) {
|
|
|
1203
1620
|
function changeFormat(next) {
|
|
1204
1621
|
if (next === format) return;
|
|
1205
1622
|
let nextBody = body;
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1623
|
+
if (next === "static" || format === "static") {
|
|
1624
|
+
nextBody = defaultBodyForFormat(next);
|
|
1625
|
+
} else {
|
|
1626
|
+
const k = `${format}\u2192${next}`;
|
|
1627
|
+
switch (k) {
|
|
1628
|
+
case "tiptap\u2192html":
|
|
1629
|
+
nextBody = tiptapToHtml(body);
|
|
1630
|
+
break;
|
|
1631
|
+
case "tiptap\u2192markdown":
|
|
1632
|
+
nextBody = tiptapToMarkdown(body);
|
|
1633
|
+
break;
|
|
1634
|
+
case "html\u2192tiptap":
|
|
1635
|
+
nextBody = String(body ?? "");
|
|
1636
|
+
break;
|
|
1637
|
+
case "markdown\u2192tiptap":
|
|
1638
|
+
nextBody = markdownToHtml(String(body ?? ""));
|
|
1639
|
+
break;
|
|
1640
|
+
case "html\u2192markdown":
|
|
1641
|
+
nextBody = htmlToMarkdown(String(body ?? ""));
|
|
1642
|
+
break;
|
|
1643
|
+
case "markdown\u2192html":
|
|
1644
|
+
nextBody = markdownToHtml(String(body ?? ""));
|
|
1645
|
+
break;
|
|
1646
|
+
default:
|
|
1647
|
+
nextBody = defaultBodyForFormat(next);
|
|
1648
|
+
}
|
|
1228
1649
|
}
|
|
1229
1650
|
setFormat(next);
|
|
1230
1651
|
setBody(nextBody);
|
|
1652
|
+
setPendingBundle(null);
|
|
1653
|
+
if (next !== "html") setNoLayout(false);
|
|
1231
1654
|
}
|
|
1232
1655
|
async function save(e) {
|
|
1233
1656
|
e.preventDefault();
|
|
@@ -1235,32 +1658,52 @@ function PostForm({ post }) {
|
|
|
1235
1658
|
setError(null);
|
|
1236
1659
|
try {
|
|
1237
1660
|
const tags = parseTags(tagsInput);
|
|
1661
|
+
const metadata = buildMetadata();
|
|
1662
|
+
const finalSlug = slug || slugify(title);
|
|
1663
|
+
const finalSiteId = post?.siteId ?? readAdminSiteIdFromCookie();
|
|
1664
|
+
let nextBody = body;
|
|
1665
|
+
if (format === "static") {
|
|
1666
|
+
if (pendingBundle) {
|
|
1667
|
+
nextBody = await uploadBundle({
|
|
1668
|
+
siteId: finalSiteId,
|
|
1669
|
+
slug: finalSlug,
|
|
1670
|
+
files: pendingBundle.files,
|
|
1671
|
+
entrypoint: pendingBundle.entrypoint
|
|
1672
|
+
});
|
|
1673
|
+
} else if (initialStaticBody) {
|
|
1674
|
+
nextBody = initialStaticBody;
|
|
1675
|
+
} else {
|
|
1676
|
+
throw new Error(t("posts.form.static.noBundle"));
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1238
1679
|
if (isEdit) {
|
|
1239
1680
|
await updatePost(
|
|
1240
1681
|
post.postId,
|
|
1241
1682
|
{
|
|
1242
1683
|
title,
|
|
1243
|
-
slug:
|
|
1684
|
+
slug: finalSlug,
|
|
1244
1685
|
excerpt: excerpt || void 0,
|
|
1245
1686
|
format,
|
|
1246
|
-
body,
|
|
1687
|
+
body: nextBody,
|
|
1247
1688
|
status,
|
|
1248
1689
|
publishedAt: status === "published" ? post?.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString() : void 0,
|
|
1249
|
-
tags
|
|
1690
|
+
tags,
|
|
1691
|
+
metadata
|
|
1250
1692
|
},
|
|
1251
1693
|
{ siteId: post.siteId }
|
|
1252
1694
|
);
|
|
1253
1695
|
} else {
|
|
1254
1696
|
await createPost({
|
|
1255
|
-
siteId:
|
|
1256
|
-
slug:
|
|
1697
|
+
siteId: finalSiteId,
|
|
1698
|
+
slug: finalSlug,
|
|
1257
1699
|
title,
|
|
1258
1700
|
excerpt: excerpt || void 0,
|
|
1259
1701
|
format,
|
|
1260
|
-
body,
|
|
1702
|
+
body: nextBody,
|
|
1261
1703
|
status,
|
|
1262
1704
|
publishedAt: status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
|
|
1263
|
-
tags
|
|
1705
|
+
tags,
|
|
1706
|
+
metadata
|
|
1264
1707
|
});
|
|
1265
1708
|
}
|
|
1266
1709
|
router.push("/admin/posts");
|
|
@@ -1276,6 +1719,9 @@ function PostForm({ post }) {
|
|
|
1276
1719
|
if (!confirm(t("posts.form.deleteConfirm", { title: post.title }))) return;
|
|
1277
1720
|
setSaving(true);
|
|
1278
1721
|
try {
|
|
1722
|
+
if (post.format === "static") {
|
|
1723
|
+
await deleteBundle(post.siteId, post.slug).catch(() => void 0);
|
|
1724
|
+
}
|
|
1279
1725
|
await deletePost(post.postId);
|
|
1280
1726
|
router.push("/admin/posts");
|
|
1281
1727
|
router.refresh();
|
|
@@ -1296,9 +1742,9 @@ function PostForm({ post }) {
|
|
|
1296
1742
|
publishedAt: status === "published" ? post?.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString() : void 0,
|
|
1297
1743
|
tags: parseTags(tagsInput)
|
|
1298
1744
|
};
|
|
1299
|
-
return /* @__PURE__ */
|
|
1300
|
-
/* @__PURE__ */
|
|
1301
|
-
/* @__PURE__ */
|
|
1745
|
+
return /* @__PURE__ */ jsxs9("form", { onSubmit: save, className: "space-y-6", children: [
|
|
1746
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex gap-1 border-b", children: [
|
|
1747
|
+
/* @__PURE__ */ jsx11(
|
|
1302
1748
|
"button",
|
|
1303
1749
|
{
|
|
1304
1750
|
type: "button",
|
|
@@ -1308,7 +1754,7 @@ function PostForm({ post }) {
|
|
|
1308
1754
|
children: t("posts.form.tabEdit")
|
|
1309
1755
|
}
|
|
1310
1756
|
),
|
|
1311
|
-
/* @__PURE__ */
|
|
1757
|
+
/* @__PURE__ */ jsx11(
|
|
1312
1758
|
"button",
|
|
1313
1759
|
{
|
|
1314
1760
|
type: "button",
|
|
@@ -1319,24 +1765,24 @@ function PostForm({ post }) {
|
|
|
1319
1765
|
}
|
|
1320
1766
|
)
|
|
1321
1767
|
] }),
|
|
1322
|
-
view === "preview" && /* @__PURE__ */
|
|
1323
|
-
/* @__PURE__ */
|
|
1324
|
-
/* @__PURE__ */
|
|
1325
|
-
/* @__PURE__ */
|
|
1326
|
-
previewPost.publishedAt ? /* @__PURE__ */
|
|
1327
|
-
/* @__PURE__ */
|
|
1328
|
-
/* @__PURE__ */
|
|
1768
|
+
view === "preview" && /* @__PURE__ */ jsxs9("article", { className: "space-y-4", children: [
|
|
1769
|
+
/* @__PURE__ */ jsxs9("header", { className: "border-b pb-4", children: [
|
|
1770
|
+
/* @__PURE__ */ jsx11("h1", { className: "text-3xl font-bold tracking-tight", children: title || /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground italic", children: t("posts.form.previewNoTitle") }) }),
|
|
1771
|
+
/* @__PURE__ */ jsxs9("p", { className: "mt-2 text-sm text-muted-foreground", children: [
|
|
1772
|
+
previewPost.publishedAt ? /* @__PURE__ */ jsx11("time", { dateTime: previewPost.publishedAt, children: formatDate(previewPost.publishedAt) }) : /* @__PURE__ */ jsx11("span", { children: t("common.draft") }),
|
|
1773
|
+
/* @__PURE__ */ jsx11("span", { className: "mx-2", children: "\xB7" }),
|
|
1774
|
+
/* @__PURE__ */ jsx11("span", { className: "font-mono text-xs uppercase", children: format })
|
|
1329
1775
|
] }),
|
|
1330
|
-
excerpt && /* @__PURE__ */
|
|
1776
|
+
excerpt && /* @__PURE__ */ jsx11("p", { className: "mt-3 text-base text-muted-foreground", children: excerpt })
|
|
1331
1777
|
] }),
|
|
1332
|
-
/* @__PURE__ */
|
|
1778
|
+
format === "static" ? /* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground", children: t("posts.form.static.previewHint") }) : /* @__PURE__ */ jsx11(
|
|
1333
1779
|
"div",
|
|
1334
1780
|
{
|
|
1335
1781
|
className: "prose prose-neutral dark:prose-invert max-w-none",
|
|
1336
1782
|
dangerouslySetInnerHTML: { __html: renderBody(previewPost) }
|
|
1337
1783
|
}
|
|
1338
1784
|
),
|
|
1339
|
-
previewPost.tags && previewPost.tags.length > 0 && /* @__PURE__ */
|
|
1785
|
+
previewPost.tags && previewPost.tags.length > 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-2 border-t pt-4 text-sm", children: previewPost.tags.map((tag) => /* @__PURE__ */ jsxs9(
|
|
1340
1786
|
"span",
|
|
1341
1787
|
{
|
|
1342
1788
|
className: "rounded-full border px-2 py-0.5 text-xs text-muted-foreground",
|
|
@@ -1347,12 +1793,12 @@ function PostForm({ post }) {
|
|
|
1347
1793
|
},
|
|
1348
1794
|
tag
|
|
1349
1795
|
)) }),
|
|
1350
|
-
/* @__PURE__ */
|
|
1796
|
+
/* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("posts.form.previewHint") })
|
|
1351
1797
|
] }),
|
|
1352
|
-
/* @__PURE__ */
|
|
1353
|
-
/* @__PURE__ */
|
|
1354
|
-
/* @__PURE__ */
|
|
1355
|
-
/* @__PURE__ */
|
|
1798
|
+
/* @__PURE__ */ jsxs9("div", { className: view === "edit" ? "space-y-6" : "hidden", children: [
|
|
1799
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
1800
|
+
/* @__PURE__ */ jsx11(Label2, { htmlFor: "title", children: t("posts.form.title") }),
|
|
1801
|
+
/* @__PURE__ */ jsx11(
|
|
1356
1802
|
Input2,
|
|
1357
1803
|
{
|
|
1358
1804
|
id: "title",
|
|
@@ -1365,9 +1811,9 @@ function PostForm({ post }) {
|
|
|
1365
1811
|
}
|
|
1366
1812
|
)
|
|
1367
1813
|
] }),
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1814
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
1815
|
+
/* @__PURE__ */ jsx11(Label2, { htmlFor: "slug", children: t("posts.form.slug") }),
|
|
1816
|
+
/* @__PURE__ */ jsx11(
|
|
1371
1817
|
Input2,
|
|
1372
1818
|
{
|
|
1373
1819
|
id: "slug",
|
|
@@ -1377,9 +1823,9 @@ function PostForm({ post }) {
|
|
|
1377
1823
|
}
|
|
1378
1824
|
)
|
|
1379
1825
|
] }),
|
|
1380
|
-
/* @__PURE__ */
|
|
1381
|
-
/* @__PURE__ */
|
|
1382
|
-
/* @__PURE__ */
|
|
1826
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
1827
|
+
/* @__PURE__ */ jsx11(Label2, { htmlFor: "excerpt", children: t("posts.form.excerpt") }),
|
|
1828
|
+
/* @__PURE__ */ jsx11(
|
|
1383
1829
|
Textarea,
|
|
1384
1830
|
{
|
|
1385
1831
|
id: "excerpt",
|
|
@@ -1389,9 +1835,9 @@ function PostForm({ post }) {
|
|
|
1389
1835
|
}
|
|
1390
1836
|
)
|
|
1391
1837
|
] }),
|
|
1392
|
-
/* @__PURE__ */
|
|
1393
|
-
/* @__PURE__ */
|
|
1394
|
-
/* @__PURE__ */
|
|
1838
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
1839
|
+
/* @__PURE__ */ jsx11(Label2, { htmlFor: "format", children: t("posts.form.format") }),
|
|
1840
|
+
/* @__PURE__ */ jsxs9(
|
|
1395
1841
|
"select",
|
|
1396
1842
|
{
|
|
1397
1843
|
id: "format",
|
|
@@ -1399,33 +1845,41 @@ function PostForm({ post }) {
|
|
|
1399
1845
|
onChange: (e) => changeFormat(e.target.value),
|
|
1400
1846
|
className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
|
|
1401
1847
|
children: [
|
|
1402
|
-
/* @__PURE__ */
|
|
1403
|
-
/* @__PURE__ */
|
|
1404
|
-
/* @__PURE__ */
|
|
1848
|
+
/* @__PURE__ */ jsx11("option", { value: "tiptap", children: "Tiptap (rich editor)" }),
|
|
1849
|
+
/* @__PURE__ */ jsx11("option", { value: "markdown", children: "Markdown" }),
|
|
1850
|
+
/* @__PURE__ */ jsx11("option", { value: "html", children: "HTML" }),
|
|
1851
|
+
/* @__PURE__ */ jsx11("option", { value: "static", children: t("posts.form.formatStaticLabel") })
|
|
1405
1852
|
]
|
|
1406
1853
|
}
|
|
1407
1854
|
),
|
|
1408
|
-
/* @__PURE__ */
|
|
1855
|
+
/* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("posts.form.formatHint") })
|
|
1409
1856
|
] }),
|
|
1410
|
-
/* @__PURE__ */
|
|
1411
|
-
/* @__PURE__ */
|
|
1412
|
-
/* @__PURE__ */
|
|
1413
|
-
format !== "tiptap" && // For textarea-based formats (markdown / html) there's no
|
|
1857
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
1858
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
|
|
1859
|
+
/* @__PURE__ */ jsx11(Label2, { children: t("posts.form.body") }),
|
|
1860
|
+
format !== "tiptap" && format !== "static" && // For textarea-based formats (markdown / html) there's no
|
|
1414
1861
|
// embedded toolbar, so we surface the MediaPicker as a
|
|
1415
1862
|
// standalone button. Selecting an asset inserts a
|
|
1416
1863
|
// format-aware snippet at the cursor.
|
|
1417
|
-
/* @__PURE__ */
|
|
1864
|
+
/* @__PURE__ */ jsx11(
|
|
1418
1865
|
MediaPicker,
|
|
1419
1866
|
{
|
|
1420
1867
|
onSelect: insertMediaSnippet,
|
|
1421
|
-
trigger: /* @__PURE__ */
|
|
1422
|
-
/* @__PURE__ */
|
|
1868
|
+
trigger: /* @__PURE__ */ jsxs9(Button8, { type: "button", variant: "outline", size: "sm", children: [
|
|
1869
|
+
/* @__PURE__ */ jsx11(ImageIcon3, { className: "mr-2 h-3 w-3" }),
|
|
1423
1870
|
t("posts.form.insertMedia")
|
|
1424
1871
|
] })
|
|
1425
1872
|
}
|
|
1426
1873
|
)
|
|
1427
1874
|
] }),
|
|
1428
|
-
format === "tiptap" ? /* @__PURE__ */
|
|
1875
|
+
format === "tiptap" ? /* @__PURE__ */ jsx11(TiptapEditor, { initialContent: body, onChange: setBody }) : format === "static" ? /* @__PURE__ */ jsx11(
|
|
1876
|
+
StaticUploader,
|
|
1877
|
+
{
|
|
1878
|
+
initial: initialStaticBody,
|
|
1879
|
+
onFilesReady: (files, entrypoint) => setPendingBundle({ files, entrypoint }),
|
|
1880
|
+
onClear: () => setPendingBundle(null)
|
|
1881
|
+
}
|
|
1882
|
+
) : /* @__PURE__ */ jsx11(
|
|
1429
1883
|
Textarea,
|
|
1430
1884
|
{
|
|
1431
1885
|
ref: bodyTextareaRef,
|
|
@@ -1436,9 +1890,9 @@ function PostForm({ post }) {
|
|
|
1436
1890
|
}
|
|
1437
1891
|
)
|
|
1438
1892
|
] }),
|
|
1439
|
-
/* @__PURE__ */
|
|
1440
|
-
/* @__PURE__ */
|
|
1441
|
-
/* @__PURE__ */
|
|
1893
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
1894
|
+
/* @__PURE__ */ jsx11(Label2, { htmlFor: "tags", children: t("posts.form.tags") }),
|
|
1895
|
+
/* @__PURE__ */ jsx11(
|
|
1442
1896
|
Input2,
|
|
1443
1897
|
{
|
|
1444
1898
|
id: "tags",
|
|
@@ -1447,11 +1901,11 @@ function PostForm({ post }) {
|
|
|
1447
1901
|
placeholder: t("posts.form.tagsPlaceholder")
|
|
1448
1902
|
}
|
|
1449
1903
|
),
|
|
1450
|
-
/* @__PURE__ */
|
|
1904
|
+
/* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("posts.form.tagsHint") })
|
|
1451
1905
|
] }),
|
|
1452
|
-
/* @__PURE__ */
|
|
1453
|
-
/* @__PURE__ */
|
|
1454
|
-
/* @__PURE__ */
|
|
1906
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
1907
|
+
/* @__PURE__ */ jsx11(Label2, { htmlFor: "status", children: t("posts.form.status") }),
|
|
1908
|
+
/* @__PURE__ */ jsxs9(
|
|
1455
1909
|
"select",
|
|
1456
1910
|
{
|
|
1457
1911
|
id: "status",
|
|
@@ -1459,42 +1913,57 @@ function PostForm({ post }) {
|
|
|
1459
1913
|
onChange: (e) => setStatus(e.target.value),
|
|
1460
1914
|
className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
|
|
1461
1915
|
children: [
|
|
1462
|
-
/* @__PURE__ */
|
|
1463
|
-
/* @__PURE__ */
|
|
1916
|
+
/* @__PURE__ */ jsx11("option", { value: "draft", children: t("common.draft") }),
|
|
1917
|
+
/* @__PURE__ */ jsx11("option", { value: "published", children: t("common.published") })
|
|
1464
1918
|
]
|
|
1465
1919
|
}
|
|
1466
1920
|
)
|
|
1467
1921
|
] }),
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1922
|
+
format === "html" && /* @__PURE__ */ jsx11("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs9("label", { className: "flex items-start gap-2 text-sm", children: [
|
|
1923
|
+
/* @__PURE__ */ jsx11(
|
|
1924
|
+
"input",
|
|
1925
|
+
{
|
|
1926
|
+
type: "checkbox",
|
|
1927
|
+
checked: noLayout,
|
|
1928
|
+
onChange: (e) => setNoLayout(e.target.checked),
|
|
1929
|
+
className: "mt-1"
|
|
1930
|
+
}
|
|
1931
|
+
),
|
|
1932
|
+
/* @__PURE__ */ jsxs9("span", { children: [
|
|
1933
|
+
/* @__PURE__ */ jsx11("span", { className: "font-medium", children: t("posts.form.noLayout") }),
|
|
1934
|
+
/* @__PURE__ */ jsx11("span", { className: "block text-xs text-muted-foreground", children: t("posts.form.noLayoutHint") })
|
|
1935
|
+
] })
|
|
1936
|
+
] }) }),
|
|
1937
|
+
error && /* @__PURE__ */ jsx11("p", { className: "text-sm text-destructive", children: error }),
|
|
1938
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
|
|
1939
|
+
/* @__PURE__ */ jsx11(Button8, { type: "submit", disabled: saving, children: saving ? t("common.saving") : isEdit ? t("posts.form.saveChanges") : t("posts.form.createPost") }),
|
|
1940
|
+
isEdit && /* @__PURE__ */ jsx11(Button8, { type: "button", variant: "destructive", onClick: handleDelete, disabled: saving, children: t("posts.form.delete") })
|
|
1472
1941
|
] })
|
|
1473
1942
|
] })
|
|
1474
1943
|
] });
|
|
1475
1944
|
}
|
|
1476
1945
|
|
|
1477
1946
|
// src/components/new-post-view.tsx
|
|
1478
|
-
import { jsx as
|
|
1947
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1479
1948
|
function NewPostPage() {
|
|
1480
1949
|
const t = useT();
|
|
1481
|
-
return /* @__PURE__ */
|
|
1482
|
-
/* @__PURE__ */
|
|
1483
|
-
/* @__PURE__ */
|
|
1950
|
+
return /* @__PURE__ */ jsxs10("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
|
|
1951
|
+
/* @__PURE__ */ jsx12("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.newTitle") }),
|
|
1952
|
+
/* @__PURE__ */ jsx12(PostForm, {})
|
|
1484
1953
|
] });
|
|
1485
1954
|
}
|
|
1486
1955
|
|
|
1487
1956
|
// src/components/edit-post-view.tsx
|
|
1488
|
-
import { useEffect as useEffect5, useState as
|
|
1957
|
+
import { useEffect as useEffect5, useState as useState7, use } from "react";
|
|
1489
1958
|
import { notFound } from "next/navigation";
|
|
1490
1959
|
import { getPostById } from "ampless";
|
|
1491
|
-
import { jsx as
|
|
1960
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1492
1961
|
function EditPostPage({ params }) {
|
|
1493
1962
|
const t = useT();
|
|
1494
1963
|
const { postId } = use(params);
|
|
1495
|
-
const [post, setPost] =
|
|
1496
|
-
const [loading, setLoading] =
|
|
1497
|
-
const [missing, setMissing] =
|
|
1964
|
+
const [post, setPost] = useState7(null);
|
|
1965
|
+
const [loading, setLoading] = useState7(true);
|
|
1966
|
+
const [missing, setMissing] = useState7(false);
|
|
1498
1967
|
useEffect5(() => {
|
|
1499
1968
|
const siteId = readAdminSiteIdFromCookie();
|
|
1500
1969
|
getPostById(postId, { siteId }).then((p) => {
|
|
@@ -1502,21 +1971,22 @@ function EditPostPage({ params }) {
|
|
|
1502
1971
|
else setPost(p);
|
|
1503
1972
|
}).finally(() => setLoading(false));
|
|
1504
1973
|
}, [postId]);
|
|
1505
|
-
if (loading)
|
|
1974
|
+
if (loading)
|
|
1975
|
+
return /* @__PURE__ */ jsx13("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: t("common.loading") });
|
|
1506
1976
|
if (missing) notFound();
|
|
1507
|
-
return /* @__PURE__ */
|
|
1508
|
-
/* @__PURE__ */
|
|
1509
|
-
post && /* @__PURE__ */
|
|
1977
|
+
return /* @__PURE__ */ jsxs11("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
|
|
1978
|
+
/* @__PURE__ */ jsx13("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.editTitle") }),
|
|
1979
|
+
post && /* @__PURE__ */ jsx13(PostForm, { post })
|
|
1510
1980
|
] });
|
|
1511
1981
|
}
|
|
1512
1982
|
|
|
1513
1983
|
// src/components/media-uploader.tsx
|
|
1514
|
-
import { useState as
|
|
1515
|
-
import { uploadData as
|
|
1984
|
+
import { useState as useState8, useEffect as useEffect6, useCallback, useRef as useRef4 } from "react";
|
|
1985
|
+
import { uploadData as uploadData3, list as list3, remove as remove2, isCancelError } from "aws-amplify/storage";
|
|
1516
1986
|
import { processImage as processImage2 } from "ampless/media";
|
|
1517
|
-
import { Button as
|
|
1518
|
-
import { Trash2 as Trash22, Copy, Check, FileText, Code2 } from "lucide-react";
|
|
1519
|
-
import { jsx as
|
|
1987
|
+
import { Button as Button9, Input as Input3 } from "@ampless/runtime/ui";
|
|
1988
|
+
import { Trash2 as Trash22, Copy, Check, FileText as FileText2, Code2 } from "lucide-react";
|
|
1989
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1520
1990
|
var IMAGE_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)$/i;
|
|
1521
1991
|
var STYLESHEET_EXT_RE = /\.css$/i;
|
|
1522
1992
|
var SCRIPT_EXT_RE = /\.m?js$/i;
|
|
@@ -1541,16 +2011,16 @@ function sanitizeName2(name) {
|
|
|
1541
2011
|
}
|
|
1542
2012
|
function MediaUploader() {
|
|
1543
2013
|
const t = useT();
|
|
1544
|
-
const [items, setItems] =
|
|
1545
|
-
const [queue, setQueue] =
|
|
1546
|
-
const [uploading, setUploading] =
|
|
1547
|
-
const [error, setError] =
|
|
1548
|
-
const [copiedPath, setCopiedPath] =
|
|
2014
|
+
const [items, setItems] = useState8([]);
|
|
2015
|
+
const [queue, setQueue] = useState8([]);
|
|
2016
|
+
const [uploading, setUploading] = useState8(false);
|
|
2017
|
+
const [error, setError] = useState8(null);
|
|
2018
|
+
const [copiedPath, setCopiedPath] = useState8(null);
|
|
1549
2019
|
const uploadTaskRef = useRef4(null);
|
|
1550
2020
|
const cancelTokenRef = useRef4({ cancelled: false });
|
|
1551
2021
|
const refresh = useCallback(async () => {
|
|
1552
2022
|
try {
|
|
1553
|
-
const result = await
|
|
2023
|
+
const result = await list3({ path: "public/media/" });
|
|
1554
2024
|
setItems(
|
|
1555
2025
|
result.items.map((item) => ({
|
|
1556
2026
|
path: item.path,
|
|
@@ -1588,7 +2058,7 @@ function MediaUploader() {
|
|
|
1588
2058
|
const yyyy = now.getFullYear();
|
|
1589
2059
|
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
1590
2060
|
const path = `public/media/${yyyy}/${mm}/${Date.now()}-${safeName}`;
|
|
1591
|
-
const task =
|
|
2061
|
+
const task = uploadData3({
|
|
1592
2062
|
path,
|
|
1593
2063
|
data: processed.blob,
|
|
1594
2064
|
options: { contentType: processed.mime }
|
|
@@ -1622,7 +2092,7 @@ function MediaUploader() {
|
|
|
1622
2092
|
async function handleDelete(path) {
|
|
1623
2093
|
if (!confirm(t("media.deleteConfirm"))) return;
|
|
1624
2094
|
try {
|
|
1625
|
-
await
|
|
2095
|
+
await remove2({ path });
|
|
1626
2096
|
await refresh();
|
|
1627
2097
|
} catch (err) {
|
|
1628
2098
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -1636,9 +2106,9 @@ function MediaUploader() {
|
|
|
1636
2106
|
setTimeout(() => setCopiedPath((p) => p === key ? null : p), 1500);
|
|
1637
2107
|
}
|
|
1638
2108
|
const currentFile = queue[0] ?? null;
|
|
1639
|
-
return /* @__PURE__ */
|
|
1640
|
-
/* @__PURE__ */
|
|
1641
|
-
/* @__PURE__ */
|
|
2109
|
+
return /* @__PURE__ */ jsxs12("div", { className: "space-y-6", children: [
|
|
2110
|
+
/* @__PURE__ */ jsxs12("div", { className: "rounded-md border p-4", children: [
|
|
2111
|
+
/* @__PURE__ */ jsx14(
|
|
1642
2112
|
Input3,
|
|
1643
2113
|
{
|
|
1644
2114
|
type: "file",
|
|
@@ -1647,11 +2117,11 @@ function MediaUploader() {
|
|
|
1647
2117
|
disabled: uploading
|
|
1648
2118
|
}
|
|
1649
2119
|
),
|
|
1650
|
-
uploading && /* @__PURE__ */
|
|
1651
|
-
!uploading && queue.length > 0 && /* @__PURE__ */
|
|
2120
|
+
uploading && /* @__PURE__ */ jsx14("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.uploading") }),
|
|
2121
|
+
!uploading && queue.length > 0 && /* @__PURE__ */ jsx14("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.queued", { count: queue.length }) })
|
|
1652
2122
|
] }),
|
|
1653
|
-
error && /* @__PURE__ */
|
|
1654
|
-
/* @__PURE__ */
|
|
2123
|
+
error && /* @__PURE__ */ jsx14("p", { className: "text-sm text-destructive", children: error }),
|
|
2124
|
+
/* @__PURE__ */ jsx14(
|
|
1655
2125
|
ImageUploadDialog,
|
|
1656
2126
|
{
|
|
1657
2127
|
file: currentFile,
|
|
@@ -1663,7 +2133,7 @@ function MediaUploader() {
|
|
|
1663
2133
|
onCancel: handleDialogCancel
|
|
1664
2134
|
}
|
|
1665
2135
|
),
|
|
1666
|
-
/* @__PURE__ */
|
|
2136
|
+
/* @__PURE__ */ jsx14("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4", children: items.map((item) => {
|
|
1667
2137
|
const isImage = IMAGE_EXT_RE.test(item.path);
|
|
1668
2138
|
const isStylesheet = STYLESHEET_EXT_RE.test(item.path);
|
|
1669
2139
|
const isScript = SCRIPT_EXT_RE.test(item.path);
|
|
@@ -1673,14 +2143,14 @@ function MediaUploader() {
|
|
|
1673
2143
|
const tagDiffersFromUrl = tagSnippet !== item.url;
|
|
1674
2144
|
const urlCopied = copiedPath === `${item.path}:url`;
|
|
1675
2145
|
const tagCopied = copiedPath === `${item.path}:tag`;
|
|
1676
|
-
return /* @__PURE__ */
|
|
2146
|
+
return /* @__PURE__ */ jsxs12(
|
|
1677
2147
|
"div",
|
|
1678
2148
|
{
|
|
1679
2149
|
className: "group relative overflow-hidden rounded-md border bg-[var(--card)]",
|
|
1680
2150
|
children: [
|
|
1681
2151
|
isImage ? (
|
|
1682
2152
|
// eslint-disable-next-line @next/next/no-img-element
|
|
1683
|
-
/* @__PURE__ */
|
|
2153
|
+
/* @__PURE__ */ jsx14(
|
|
1684
2154
|
"img",
|
|
1685
2155
|
{
|
|
1686
2156
|
src: item.url,
|
|
@@ -1688,18 +2158,18 @@ function MediaUploader() {
|
|
|
1688
2158
|
className: "aspect-square w-full object-cover"
|
|
1689
2159
|
}
|
|
1690
2160
|
)
|
|
1691
|
-
) : /* @__PURE__ */
|
|
1692
|
-
isStylesheet || isScript ? /* @__PURE__ */
|
|
1693
|
-
/* @__PURE__ */
|
|
2161
|
+
) : /* @__PURE__ */ jsxs12("div", { className: "flex aspect-square w-full flex-col items-center justify-center gap-2 bg-muted text-muted-foreground", children: [
|
|
2162
|
+
isStylesheet || isScript ? /* @__PURE__ */ jsx14(Code2, { className: "h-8 w-8" }) : /* @__PURE__ */ jsx14(FileText2, { className: "h-8 w-8" }),
|
|
2163
|
+
/* @__PURE__ */ jsxs12("span", { className: "font-mono text-xs font-semibold", children: [
|
|
1694
2164
|
".",
|
|
1695
2165
|
ext.toLowerCase()
|
|
1696
2166
|
] })
|
|
1697
2167
|
] }),
|
|
1698
|
-
/* @__PURE__ */
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
/* @__PURE__ */
|
|
1701
|
-
/* @__PURE__ */
|
|
1702
|
-
|
|
2168
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between border-t px-2 py-1 text-xs", children: [
|
|
2169
|
+
/* @__PURE__ */ jsx14("span", { className: "truncate", title: filename, children: filename }),
|
|
2170
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
2171
|
+
/* @__PURE__ */ jsx14(
|
|
2172
|
+
Button9,
|
|
1703
2173
|
{
|
|
1704
2174
|
type: "button",
|
|
1705
2175
|
variant: "ghost",
|
|
@@ -1707,11 +2177,11 @@ function MediaUploader() {
|
|
|
1707
2177
|
className: "h-6 w-6",
|
|
1708
2178
|
onClick: () => handleCopy(item, "url"),
|
|
1709
2179
|
title: t("media.copyUrl"),
|
|
1710
|
-
children: urlCopied ? /* @__PURE__ */
|
|
2180
|
+
children: urlCopied ? /* @__PURE__ */ jsx14(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx14(Copy, { className: "h-3 w-3" })
|
|
1711
2181
|
}
|
|
1712
2182
|
),
|
|
1713
|
-
tagDiffersFromUrl && /* @__PURE__ */
|
|
1714
|
-
|
|
2183
|
+
tagDiffersFromUrl && /* @__PURE__ */ jsx14(
|
|
2184
|
+
Button9,
|
|
1715
2185
|
{
|
|
1716
2186
|
type: "button",
|
|
1717
2187
|
variant: "ghost",
|
|
@@ -1719,11 +2189,11 @@ function MediaUploader() {
|
|
|
1719
2189
|
className: "h-6 w-6",
|
|
1720
2190
|
onClick: () => handleCopy(item, "tag"),
|
|
1721
2191
|
title: t("media.copyTag"),
|
|
1722
|
-
children: tagCopied ? /* @__PURE__ */
|
|
2192
|
+
children: tagCopied ? /* @__PURE__ */ jsx14(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx14(Code2, { className: "h-3 w-3" })
|
|
1723
2193
|
}
|
|
1724
2194
|
),
|
|
1725
|
-
/* @__PURE__ */
|
|
1726
|
-
|
|
2195
|
+
/* @__PURE__ */ jsx14(
|
|
2196
|
+
Button9,
|
|
1727
2197
|
{
|
|
1728
2198
|
type: "button",
|
|
1729
2199
|
variant: "ghost",
|
|
@@ -1731,7 +2201,7 @@ function MediaUploader() {
|
|
|
1731
2201
|
className: "h-6 w-6",
|
|
1732
2202
|
onClick: () => handleDelete(item.path),
|
|
1733
2203
|
title: t("media.delete"),
|
|
1734
|
-
children: /* @__PURE__ */
|
|
2204
|
+
children: /* @__PURE__ */ jsx14(Trash22, { className: "h-3 w-3" })
|
|
1735
2205
|
}
|
|
1736
2206
|
)
|
|
1737
2207
|
] })
|
|
@@ -1741,22 +2211,22 @@ function MediaUploader() {
|
|
|
1741
2211
|
item.path
|
|
1742
2212
|
);
|
|
1743
2213
|
}) }),
|
|
1744
|
-
items.length === 0 && /* @__PURE__ */
|
|
2214
|
+
items.length === 0 && /* @__PURE__ */ jsx14("p", { className: "text-center text-sm text-muted-foreground", children: t("media.empty") })
|
|
1745
2215
|
] });
|
|
1746
2216
|
}
|
|
1747
2217
|
|
|
1748
2218
|
// src/components/media-view.tsx
|
|
1749
|
-
import { jsx as
|
|
2219
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1750
2220
|
function MediaPage() {
|
|
1751
2221
|
const t = useT();
|
|
1752
|
-
return /* @__PURE__ */
|
|
1753
|
-
/* @__PURE__ */
|
|
1754
|
-
/* @__PURE__ */
|
|
2222
|
+
return /* @__PURE__ */ jsxs13("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
|
|
2223
|
+
/* @__PURE__ */ jsx15("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("media.title") }),
|
|
2224
|
+
/* @__PURE__ */ jsx15(MediaUploader, {})
|
|
1755
2225
|
] });
|
|
1756
2226
|
}
|
|
1757
2227
|
|
|
1758
2228
|
// src/components/login-view.tsx
|
|
1759
|
-
import { useState as
|
|
2229
|
+
import { useState as useState9 } from "react";
|
|
1760
2230
|
import { useRouter as useRouter2 } from "next/navigation";
|
|
1761
2231
|
import {
|
|
1762
2232
|
signIn,
|
|
@@ -1766,7 +2236,7 @@ import {
|
|
|
1766
2236
|
confirmResetPassword
|
|
1767
2237
|
} from "aws-amplify/auth";
|
|
1768
2238
|
import {
|
|
1769
|
-
Button as
|
|
2239
|
+
Button as Button10,
|
|
1770
2240
|
Input as Input4,
|
|
1771
2241
|
Label as Label3,
|
|
1772
2242
|
Card as Card2,
|
|
@@ -1775,17 +2245,17 @@ import {
|
|
|
1775
2245
|
CardTitle as CardTitle2,
|
|
1776
2246
|
CardDescription
|
|
1777
2247
|
} from "@ampless/runtime/ui";
|
|
1778
|
-
import { Fragment as Fragment4, jsx as
|
|
2248
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1779
2249
|
function LoginPage() {
|
|
1780
2250
|
const router = useRouter2();
|
|
1781
2251
|
const t = useT();
|
|
1782
|
-
const [mode, setMode] =
|
|
1783
|
-
const [email, setEmail] =
|
|
1784
|
-
const [password, setPassword] =
|
|
1785
|
-
const [code, setCode] =
|
|
1786
|
-
const [error, setError] =
|
|
1787
|
-
const [info, setInfo] =
|
|
1788
|
-
const [loading, setLoading] =
|
|
2252
|
+
const [mode, setMode] = useState9("signIn");
|
|
2253
|
+
const [email, setEmail] = useState9("");
|
|
2254
|
+
const [password, setPassword] = useState9("");
|
|
2255
|
+
const [code, setCode] = useState9("");
|
|
2256
|
+
const [error, setError] = useState9(null);
|
|
2257
|
+
const [info, setInfo] = useState9(null);
|
|
2258
|
+
const [loading, setLoading] = useState9(false);
|
|
1789
2259
|
function go(next) {
|
|
1790
2260
|
setMode(next);
|
|
1791
2261
|
setError(null);
|
|
@@ -1849,15 +2319,15 @@ function LoginPage() {
|
|
|
1849
2319
|
const showEmail = mode !== "confirm" && mode !== "reset";
|
|
1850
2320
|
const showPassword = mode === "signIn" || mode === "signUp" || mode === "reset";
|
|
1851
2321
|
const showCode = mode === "confirm" || mode === "reset";
|
|
1852
|
-
return /* @__PURE__ */
|
|
1853
|
-
/* @__PURE__ */
|
|
1854
|
-
/* @__PURE__ */
|
|
1855
|
-
/* @__PURE__ */
|
|
2322
|
+
return /* @__PURE__ */ jsx16("main", { className: "flex min-h-screen items-center justify-center bg-muted/30 p-4", children: /* @__PURE__ */ jsxs14(Card2, { className: "w-full max-w-md", children: [
|
|
2323
|
+
/* @__PURE__ */ jsxs14(CardHeader2, { children: [
|
|
2324
|
+
/* @__PURE__ */ jsx16(CardTitle2, { children: t(`auth.${mode}.title`) }),
|
|
2325
|
+
/* @__PURE__ */ jsx16(CardDescription, { children: t(`auth.${mode}.description`) })
|
|
1856
2326
|
] }),
|
|
1857
|
-
/* @__PURE__ */
|
|
1858
|
-
showEmail && /* @__PURE__ */
|
|
1859
|
-
/* @__PURE__ */
|
|
1860
|
-
/* @__PURE__ */
|
|
2327
|
+
/* @__PURE__ */ jsx16(CardContent2, { children: /* @__PURE__ */ jsxs14("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
2328
|
+
showEmail && /* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2329
|
+
/* @__PURE__ */ jsx16(Label3, { htmlFor: "email", children: t("auth.common.email") }),
|
|
2330
|
+
/* @__PURE__ */ jsx16(
|
|
1861
2331
|
Input4,
|
|
1862
2332
|
{
|
|
1863
2333
|
id: "email",
|
|
@@ -1869,9 +2339,9 @@ function LoginPage() {
|
|
|
1869
2339
|
}
|
|
1870
2340
|
)
|
|
1871
2341
|
] }),
|
|
1872
|
-
showCode && /* @__PURE__ */
|
|
1873
|
-
/* @__PURE__ */
|
|
1874
|
-
/* @__PURE__ */
|
|
2342
|
+
showCode && /* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2343
|
+
/* @__PURE__ */ jsx16(Label3, { htmlFor: "code", children: t("auth.common.code") }),
|
|
2344
|
+
/* @__PURE__ */ jsx16(
|
|
1875
2345
|
Input4,
|
|
1876
2346
|
{
|
|
1877
2347
|
id: "code",
|
|
@@ -1882,9 +2352,9 @@ function LoginPage() {
|
|
|
1882
2352
|
}
|
|
1883
2353
|
)
|
|
1884
2354
|
] }),
|
|
1885
|
-
showPassword && /* @__PURE__ */
|
|
1886
|
-
/* @__PURE__ */
|
|
1887
|
-
/* @__PURE__ */
|
|
2355
|
+
showPassword && /* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
2356
|
+
/* @__PURE__ */ jsx16(Label3, { htmlFor: "password", children: mode === "reset" ? t("auth.common.newPassword") : t("auth.common.password") }),
|
|
2357
|
+
/* @__PURE__ */ jsx16(
|
|
1888
2358
|
Input4,
|
|
1889
2359
|
{
|
|
1890
2360
|
id: "password",
|
|
@@ -1896,14 +2366,14 @@ function LoginPage() {
|
|
|
1896
2366
|
autoComplete: mode === "signIn" ? "current-password" : "new-password"
|
|
1897
2367
|
}
|
|
1898
2368
|
),
|
|
1899
|
-
(mode === "signUp" || mode === "reset") && /* @__PURE__ */
|
|
2369
|
+
(mode === "signUp" || mode === "reset") && /* @__PURE__ */ jsx16("p", { className: "text-xs text-muted-foreground", children: t("auth.common.passwordHint") })
|
|
1900
2370
|
] }),
|
|
1901
|
-
info && /* @__PURE__ */
|
|
1902
|
-
error && /* @__PURE__ */
|
|
1903
|
-
/* @__PURE__ */
|
|
1904
|
-
/* @__PURE__ */
|
|
1905
|
-
mode === "signIn" && /* @__PURE__ */
|
|
1906
|
-
/* @__PURE__ */
|
|
2371
|
+
info && /* @__PURE__ */ jsx16("p", { className: "text-sm text-muted-foreground", children: info }),
|
|
2372
|
+
error && /* @__PURE__ */ jsx16("p", { className: "text-sm text-destructive", children: error }),
|
|
2373
|
+
/* @__PURE__ */ jsx16(Button10, { type: "submit", className: "w-full", disabled: loading, children: loading ? t("auth.common.working") : t(`auth.${mode}.submit`) }),
|
|
2374
|
+
/* @__PURE__ */ jsxs14("div", { className: "space-y-1 text-center text-sm", children: [
|
|
2375
|
+
mode === "signIn" && /* @__PURE__ */ jsxs14(Fragment4, { children: [
|
|
2376
|
+
/* @__PURE__ */ jsx16("p", { children: /* @__PURE__ */ jsx16(
|
|
1907
2377
|
"button",
|
|
1908
2378
|
{
|
|
1909
2379
|
type: "button",
|
|
@@ -1912,7 +2382,7 @@ function LoginPage() {
|
|
|
1912
2382
|
children: t("auth.signIn.forgotPassword")
|
|
1913
2383
|
}
|
|
1914
2384
|
) }),
|
|
1915
|
-
/* @__PURE__ */
|
|
2385
|
+
/* @__PURE__ */ jsx16("p", { children: /* @__PURE__ */ jsx16(
|
|
1916
2386
|
"button",
|
|
1917
2387
|
{
|
|
1918
2388
|
type: "button",
|
|
@@ -1922,7 +2392,7 @@ function LoginPage() {
|
|
|
1922
2392
|
}
|
|
1923
2393
|
) })
|
|
1924
2394
|
] }),
|
|
1925
|
-
(mode === "signUp" || mode === "forgot" || mode === "reset") && /* @__PURE__ */
|
|
2395
|
+
(mode === "signUp" || mode === "forgot" || mode === "reset") && /* @__PURE__ */ jsx16("p", { children: /* @__PURE__ */ jsx16(
|
|
1926
2396
|
"button",
|
|
1927
2397
|
{
|
|
1928
2398
|
type: "button",
|
|
@@ -1931,7 +2401,7 @@ function LoginPage() {
|
|
|
1931
2401
|
children: t("auth.signUp.backToSignIn")
|
|
1932
2402
|
}
|
|
1933
2403
|
) }),
|
|
1934
|
-
mode === "reset" && /* @__PURE__ */
|
|
2404
|
+
mode === "reset" && /* @__PURE__ */ jsx16("p", { children: /* @__PURE__ */ jsx16(
|
|
1935
2405
|
"button",
|
|
1936
2406
|
{
|
|
1937
2407
|
type: "button",
|
|
@@ -1946,83 +2416,156 @@ function LoginPage() {
|
|
|
1946
2416
|
}
|
|
1947
2417
|
|
|
1948
2418
|
// src/components/sidebar.tsx
|
|
2419
|
+
import { useEffect as useEffect7, useState as useState10 } from "react";
|
|
1949
2420
|
import Link4 from "next/link";
|
|
1950
2421
|
import { usePathname } from "next/navigation";
|
|
1951
2422
|
import { signOut } from "aws-amplify/auth";
|
|
1952
|
-
import {
|
|
1953
|
-
|
|
1954
|
-
|
|
2423
|
+
import {
|
|
2424
|
+
LayoutDashboard,
|
|
2425
|
+
FileText as FileText3,
|
|
2426
|
+
Image as Image2,
|
|
2427
|
+
Globe,
|
|
2428
|
+
Users,
|
|
2429
|
+
LogOut,
|
|
2430
|
+
ExternalLink,
|
|
2431
|
+
Menu,
|
|
2432
|
+
X as X2
|
|
2433
|
+
} from "lucide-react";
|
|
2434
|
+
import { Button as Button11, cn as cn3 } from "@ampless/runtime/ui";
|
|
2435
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1955
2436
|
var navItems = [
|
|
1956
2437
|
{ href: "/admin", key: "sidebar.dashboard", icon: LayoutDashboard },
|
|
1957
|
-
{ href: "/admin/posts", key: "sidebar.posts", icon:
|
|
2438
|
+
{ href: "/admin/posts", key: "sidebar.posts", icon: FileText3 },
|
|
1958
2439
|
{ href: "/admin/media", key: "sidebar.media", icon: Image2 },
|
|
1959
|
-
{ href: "/admin/sites", key: "sidebar.sites", icon: Globe }
|
|
2440
|
+
{ href: "/admin/sites", key: "sidebar.sites", icon: Globe },
|
|
2441
|
+
{ href: "/admin/users", key: "sidebar.users", icon: Users, adminOnly: true }
|
|
1960
2442
|
];
|
|
1961
2443
|
function Sidebar({
|
|
1962
2444
|
email,
|
|
1963
|
-
siteSelector
|
|
2445
|
+
siteSelector,
|
|
2446
|
+
isAdmin
|
|
1964
2447
|
}) {
|
|
1965
2448
|
const pathname = usePathname();
|
|
1966
2449
|
const t = useT();
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
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,
|
|
2450
|
+
const [open, setOpen] = useState10(false);
|
|
2451
|
+
useEffect7(() => {
|
|
2452
|
+
setOpen(false);
|
|
2453
|
+
}, [pathname]);
|
|
2454
|
+
useEffect7(() => {
|
|
2455
|
+
if (!open) return;
|
|
2456
|
+
const prev = document.body.style.overflow;
|
|
2457
|
+
document.body.style.overflow = "hidden";
|
|
2458
|
+
return () => {
|
|
2459
|
+
document.body.style.overflow = prev;
|
|
2460
|
+
};
|
|
2461
|
+
}, [open]);
|
|
2462
|
+
return /* @__PURE__ */ jsxs15(Fragment5, { children: [
|
|
2463
|
+
/* @__PURE__ */ jsxs15("header", { className: "sticky top-0 z-30 flex h-14 items-center justify-between border-b bg-background px-4 md:hidden", children: [
|
|
2464
|
+
/* @__PURE__ */ jsx17(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }),
|
|
2465
|
+
/* @__PURE__ */ jsx17(
|
|
2466
|
+
Button11,
|
|
2005
2467
|
{
|
|
2006
2468
|
variant: "ghost",
|
|
2007
|
-
size: "
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
},
|
|
2013
|
-
children: [
|
|
2014
|
-
/* @__PURE__ */ jsx16(LogOut, { className: "h-4 w-4" }),
|
|
2015
|
-
t("sidebar.signOut")
|
|
2016
|
-
]
|
|
2469
|
+
size: "icon",
|
|
2470
|
+
"aria-label": t("sidebar.openMenu"),
|
|
2471
|
+
"aria-expanded": open,
|
|
2472
|
+
onClick: () => setOpen(true),
|
|
2473
|
+
children: /* @__PURE__ */ jsx17(Menu, { className: "h-5 w-5" })
|
|
2017
2474
|
}
|
|
2018
2475
|
)
|
|
2019
|
-
] })
|
|
2476
|
+
] }),
|
|
2477
|
+
open && /* @__PURE__ */ jsx17(
|
|
2478
|
+
"div",
|
|
2479
|
+
{
|
|
2480
|
+
className: "fixed inset-0 z-40 bg-black/40 md:hidden",
|
|
2481
|
+
"aria-hidden": "true",
|
|
2482
|
+
onClick: () => setOpen(false)
|
|
2483
|
+
}
|
|
2484
|
+
),
|
|
2485
|
+
/* @__PURE__ */ jsxs15(
|
|
2486
|
+
"aside",
|
|
2487
|
+
{
|
|
2488
|
+
className: cn3(
|
|
2489
|
+
"fixed inset-y-0 left-0 z-50 flex w-60 flex-col border-r bg-muted/30 transition-transform md:sticky md:top-0 md:h-screen md:translate-x-0",
|
|
2490
|
+
open ? "translate-x-0" : "-translate-x-full md:translate-x-0"
|
|
2491
|
+
),
|
|
2492
|
+
"aria-label": t("sidebar.brand"),
|
|
2493
|
+
children: [
|
|
2494
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between border-b p-4", children: [
|
|
2495
|
+
/* @__PURE__ */ jsx17(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }),
|
|
2496
|
+
/* @__PURE__ */ jsx17(
|
|
2497
|
+
Button11,
|
|
2498
|
+
{
|
|
2499
|
+
variant: "ghost",
|
|
2500
|
+
size: "icon",
|
|
2501
|
+
className: "md:hidden",
|
|
2502
|
+
"aria-label": t("sidebar.closeMenu"),
|
|
2503
|
+
onClick: () => setOpen(false),
|
|
2504
|
+
children: /* @__PURE__ */ jsx17(X2, { className: "h-5 w-5" })
|
|
2505
|
+
}
|
|
2506
|
+
)
|
|
2507
|
+
] }),
|
|
2508
|
+
siteSelector ? /* @__PURE__ */ jsx17("div", { className: "border-b", children: siteSelector }) : null,
|
|
2509
|
+
/* @__PURE__ */ jsx17("nav", { className: "flex-1 space-y-1 overflow-y-auto p-2", children: navItems.map((item) => {
|
|
2510
|
+
if (item.adminOnly && !isAdmin) return null;
|
|
2511
|
+
const Icon = item.icon;
|
|
2512
|
+
const isActive = pathname === item.href || item.href !== "/admin" && pathname?.startsWith(item.href);
|
|
2513
|
+
return /* @__PURE__ */ jsxs15(
|
|
2514
|
+
Link4,
|
|
2515
|
+
{
|
|
2516
|
+
href: item.href,
|
|
2517
|
+
className: cn3(
|
|
2518
|
+
"flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors",
|
|
2519
|
+
isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
2520
|
+
),
|
|
2521
|
+
children: [
|
|
2522
|
+
/* @__PURE__ */ jsx17(Icon, { className: "h-4 w-4" }),
|
|
2523
|
+
t(item.key)
|
|
2524
|
+
]
|
|
2525
|
+
},
|
|
2526
|
+
item.href
|
|
2527
|
+
);
|
|
2528
|
+
}) }),
|
|
2529
|
+
/* @__PURE__ */ jsxs15("div", { className: "border-t p-2 space-y-1", children: [
|
|
2530
|
+
/* @__PURE__ */ jsxs15(
|
|
2531
|
+
Link4,
|
|
2532
|
+
{
|
|
2533
|
+
href: "/",
|
|
2534
|
+
target: "_blank",
|
|
2535
|
+
className: "flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2536
|
+
children: [
|
|
2537
|
+
/* @__PURE__ */ jsx17(ExternalLink, { className: "h-4 w-4" }),
|
|
2538
|
+
t("sidebar.viewSite")
|
|
2539
|
+
]
|
|
2540
|
+
}
|
|
2541
|
+
),
|
|
2542
|
+
/* @__PURE__ */ jsx17("div", { className: "px-3 py-2 text-xs text-muted-foreground truncate", children: email }),
|
|
2543
|
+
/* @__PURE__ */ jsxs15(
|
|
2544
|
+
Button11,
|
|
2545
|
+
{
|
|
2546
|
+
variant: "ghost",
|
|
2547
|
+
size: "sm",
|
|
2548
|
+
className: "w-full justify-start gap-3",
|
|
2549
|
+
onClick: async () => {
|
|
2550
|
+
await signOut();
|
|
2551
|
+
window.location.href = "/login";
|
|
2552
|
+
},
|
|
2553
|
+
children: [
|
|
2554
|
+
/* @__PURE__ */ jsx17(LogOut, { className: "h-4 w-4" }),
|
|
2555
|
+
t("sidebar.signOut")
|
|
2556
|
+
]
|
|
2557
|
+
}
|
|
2558
|
+
)
|
|
2559
|
+
] })
|
|
2560
|
+
]
|
|
2561
|
+
}
|
|
2562
|
+
)
|
|
2020
2563
|
] });
|
|
2021
2564
|
}
|
|
2022
2565
|
|
|
2023
2566
|
// src/components/site-selector.tsx
|
|
2024
2567
|
import { useRouter as useRouter3 } from "next/navigation";
|
|
2025
|
-
import { jsx as
|
|
2568
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2026
2569
|
function SiteSelector({ current, sites }) {
|
|
2027
2570
|
const router = useRouter3();
|
|
2028
2571
|
const t = useT();
|
|
@@ -2031,26 +2574,26 @@ function SiteSelector({ current, sites }) {
|
|
|
2031
2574
|
document.cookie = `${ADMIN_SITE_COOKIE}=${encodeURIComponent(next)}; Path=/; Max-Age=${60 * 60 * 24 * 365}; SameSite=Lax`;
|
|
2032
2575
|
router.refresh();
|
|
2033
2576
|
}
|
|
2034
|
-
return /* @__PURE__ */
|
|
2035
|
-
/* @__PURE__ */
|
|
2036
|
-
/* @__PURE__ */
|
|
2577
|
+
return /* @__PURE__ */ jsxs16("div", { className: "px-3 py-2", children: [
|
|
2578
|
+
/* @__PURE__ */ jsx18("label", { className: "block text-xs uppercase tracking-wide text-muted-foreground mb-1", children: t("sites.selector.label") }),
|
|
2579
|
+
/* @__PURE__ */ jsx18(
|
|
2037
2580
|
"select",
|
|
2038
2581
|
{
|
|
2039
2582
|
value: current,
|
|
2040
2583
|
onChange,
|
|
2041
2584
|
className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
|
|
2042
|
-
children: sites.map((s) => /* @__PURE__ */
|
|
2585
|
+
children: sites.map((s) => /* @__PURE__ */ jsx18("option", { value: s.id, children: s.name }, s.id))
|
|
2043
2586
|
}
|
|
2044
2587
|
)
|
|
2045
2588
|
] });
|
|
2046
2589
|
}
|
|
2047
2590
|
|
|
2048
2591
|
// src/components/site-settings-form.tsx
|
|
2049
|
-
import { useState as
|
|
2592
|
+
import { useState as useState11 } from "react";
|
|
2050
2593
|
import { useRouter as useRouter4 } from "next/navigation";
|
|
2051
2594
|
import { setSiteSetting } from "ampless";
|
|
2052
|
-
import { Button as
|
|
2053
|
-
import { jsx as
|
|
2595
|
+
import { Button as Button12, Input as Input5, Label as Label4, Textarea as Textarea2 } from "@ampless/runtime/ui";
|
|
2596
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2054
2597
|
var KEYS = [
|
|
2055
2598
|
"site.name",
|
|
2056
2599
|
"site.url",
|
|
@@ -2063,10 +2606,10 @@ var KEYS = [
|
|
|
2063
2606
|
function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
2064
2607
|
const router = useRouter4();
|
|
2065
2608
|
const t = useT();
|
|
2066
|
-
const [values, setValues] =
|
|
2067
|
-
const [saving, setSaving] =
|
|
2068
|
-
const [error, setError] =
|
|
2069
|
-
const [info, setInfo] =
|
|
2609
|
+
const [values, setValues] = useState11(initial);
|
|
2610
|
+
const [saving, setSaving] = useState11(false);
|
|
2611
|
+
const [error, setError] = useState11(null);
|
|
2612
|
+
const [info, setInfo] = useState11(null);
|
|
2070
2613
|
function update(key, value) {
|
|
2071
2614
|
setValues((prev) => ({ ...prev, [key]: value }));
|
|
2072
2615
|
}
|
|
@@ -2091,12 +2634,12 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
|
2091
2634
|
setSaving(false);
|
|
2092
2635
|
}
|
|
2093
2636
|
}
|
|
2094
|
-
return /* @__PURE__ */
|
|
2095
|
-
/* @__PURE__ */
|
|
2096
|
-
/* @__PURE__ */
|
|
2097
|
-
/* @__PURE__ */
|
|
2098
|
-
/* @__PURE__ */
|
|
2099
|
-
/* @__PURE__ */
|
|
2637
|
+
return /* @__PURE__ */ jsxs17("form", { onSubmit: save, className: "space-y-6 max-w-xl", children: [
|
|
2638
|
+
/* @__PURE__ */ jsxs17("fieldset", { className: "space-y-4", children: [
|
|
2639
|
+
/* @__PURE__ */ jsx19("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.site") }),
|
|
2640
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
2641
|
+
/* @__PURE__ */ jsx19(Label4, { htmlFor: "name", children: t("common.name") }),
|
|
2642
|
+
/* @__PURE__ */ jsx19(
|
|
2100
2643
|
Input5,
|
|
2101
2644
|
{
|
|
2102
2645
|
id: "name",
|
|
@@ -2106,9 +2649,9 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
|
2106
2649
|
}
|
|
2107
2650
|
)
|
|
2108
2651
|
] }),
|
|
2109
|
-
/* @__PURE__ */
|
|
2110
|
-
/* @__PURE__ */
|
|
2111
|
-
/* @__PURE__ */
|
|
2652
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
2653
|
+
/* @__PURE__ */ jsx19(Label4, { htmlFor: "url", children: t("common.url") }),
|
|
2654
|
+
/* @__PURE__ */ jsx19(
|
|
2112
2655
|
Input5,
|
|
2113
2656
|
{
|
|
2114
2657
|
id: "url",
|
|
@@ -2118,9 +2661,9 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
|
2118
2661
|
}
|
|
2119
2662
|
)
|
|
2120
2663
|
] }),
|
|
2121
|
-
/* @__PURE__ */
|
|
2122
|
-
/* @__PURE__ */
|
|
2123
|
-
/* @__PURE__ */
|
|
2664
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
2665
|
+
/* @__PURE__ */ jsx19(Label4, { htmlFor: "description", children: t("common.description") }),
|
|
2666
|
+
/* @__PURE__ */ jsx19(
|
|
2124
2667
|
Textarea2,
|
|
2125
2668
|
{
|
|
2126
2669
|
id: "description",
|
|
@@ -2132,11 +2675,11 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
|
2132
2675
|
)
|
|
2133
2676
|
] })
|
|
2134
2677
|
] }),
|
|
2135
|
-
/* @__PURE__ */
|
|
2136
|
-
/* @__PURE__ */
|
|
2137
|
-
/* @__PURE__ */
|
|
2138
|
-
/* @__PURE__ */
|
|
2139
|
-
/* @__PURE__ */
|
|
2678
|
+
/* @__PURE__ */ jsxs17("fieldset", { className: "space-y-4", children: [
|
|
2679
|
+
/* @__PURE__ */ jsx19("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.media") }),
|
|
2680
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
2681
|
+
/* @__PURE__ */ jsx19(Label4, { htmlFor: "imageDisplay", children: t("sites.edit.imageDisplay") }),
|
|
2682
|
+
/* @__PURE__ */ jsxs17(
|
|
2140
2683
|
"select",
|
|
2141
2684
|
{
|
|
2142
2685
|
id: "imageDisplay",
|
|
@@ -2144,18 +2687,18 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
|
2144
2687
|
value: values["media.imageDisplay"] ?? "",
|
|
2145
2688
|
onChange: (e) => update("media.imageDisplay", e.target.value),
|
|
2146
2689
|
children: [
|
|
2147
|
-
/* @__PURE__ */
|
|
2690
|
+
/* @__PURE__ */ jsx19("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
|
|
2148
2691
|
value: fallback["media.imageDisplay"] ?? "inline"
|
|
2149
2692
|
}) }),
|
|
2150
|
-
/* @__PURE__ */
|
|
2151
|
-
/* @__PURE__ */
|
|
2693
|
+
/* @__PURE__ */ jsx19("option", { value: "inline", children: t("sites.edit.imageDisplayInline") }),
|
|
2694
|
+
/* @__PURE__ */ jsx19("option", { value: "lightbox", children: t("sites.edit.imageDisplayLightbox") })
|
|
2152
2695
|
]
|
|
2153
2696
|
}
|
|
2154
2697
|
)
|
|
2155
2698
|
] }),
|
|
2156
|
-
/* @__PURE__ */
|
|
2157
|
-
/* @__PURE__ */
|
|
2158
|
-
/* @__PURE__ */
|
|
2699
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
2700
|
+
/* @__PURE__ */ jsx19(Label4, { htmlFor: "imageMaxWidth", children: t("sites.edit.imageMaxWidth") }),
|
|
2701
|
+
/* @__PURE__ */ jsx19(
|
|
2159
2702
|
Input5,
|
|
2160
2703
|
{
|
|
2161
2704
|
id: "imageMaxWidth",
|
|
@@ -2166,11 +2709,11 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
|
2166
2709
|
)
|
|
2167
2710
|
] })
|
|
2168
2711
|
] }),
|
|
2169
|
-
/* @__PURE__ */
|
|
2170
|
-
/* @__PURE__ */
|
|
2171
|
-
/* @__PURE__ */
|
|
2172
|
-
/* @__PURE__ */
|
|
2173
|
-
/* @__PURE__ */
|
|
2712
|
+
/* @__PURE__ */ jsxs17("fieldset", { className: "space-y-4", children: [
|
|
2713
|
+
/* @__PURE__ */ jsx19("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.dateDisplay") }),
|
|
2714
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
2715
|
+
/* @__PURE__ */ jsx19(Label4, { htmlFor: "dateFormat", children: t("sites.edit.dateFormat") }),
|
|
2716
|
+
/* @__PURE__ */ jsxs17(
|
|
2174
2717
|
"select",
|
|
2175
2718
|
{
|
|
2176
2719
|
id: "dateFormat",
|
|
@@ -2178,19 +2721,19 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
|
2178
2721
|
value: values["dateFormat"] ?? "",
|
|
2179
2722
|
onChange: (e) => update("dateFormat", e.target.value),
|
|
2180
2723
|
children: [
|
|
2181
|
-
/* @__PURE__ */
|
|
2724
|
+
/* @__PURE__ */ jsx19("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
|
|
2182
2725
|
value: fallback["dateFormat"] ?? "iso"
|
|
2183
2726
|
}) }),
|
|
2184
|
-
/* @__PURE__ */
|
|
2185
|
-
/* @__PURE__ */
|
|
2186
|
-
/* @__PURE__ */
|
|
2727
|
+
/* @__PURE__ */ jsx19("option", { value: "iso", children: t("sites.edit.dateFormatIso") }),
|
|
2728
|
+
/* @__PURE__ */ jsx19("option", { value: "long", children: t("sites.edit.dateFormatLong") }),
|
|
2729
|
+
/* @__PURE__ */ jsx19("option", { value: "locale", children: t("sites.edit.dateFormatLocale") })
|
|
2187
2730
|
]
|
|
2188
2731
|
}
|
|
2189
2732
|
)
|
|
2190
2733
|
] }),
|
|
2191
|
-
/* @__PURE__ */
|
|
2192
|
-
/* @__PURE__ */
|
|
2193
|
-
/* @__PURE__ */
|
|
2734
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
2735
|
+
/* @__PURE__ */ jsx19(Label4, { htmlFor: "timezone", children: t("sites.edit.timezone") }),
|
|
2736
|
+
/* @__PURE__ */ jsx19(
|
|
2194
2737
|
Input5,
|
|
2195
2738
|
{
|
|
2196
2739
|
id: "timezone",
|
|
@@ -2201,14 +2744,14 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
|
|
|
2201
2744
|
)
|
|
2202
2745
|
] })
|
|
2203
2746
|
] }),
|
|
2204
|
-
info && /* @__PURE__ */
|
|
2205
|
-
error && /* @__PURE__ */
|
|
2206
|
-
/* @__PURE__ */
|
|
2747
|
+
info && /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: info }),
|
|
2748
|
+
error && /* @__PURE__ */ jsx19("p", { className: "text-sm text-destructive", children: error }),
|
|
2749
|
+
/* @__PURE__ */ jsx19(Button12, { type: "submit", disabled: saving, children: saving ? t("common.saving") : t("sites.edit.saveButton") })
|
|
2207
2750
|
] });
|
|
2208
2751
|
}
|
|
2209
2752
|
|
|
2210
2753
|
// src/components/theme-settings-form.tsx
|
|
2211
|
-
import { useState as
|
|
2754
|
+
import { useState as useState12 } from "react";
|
|
2212
2755
|
import { useRouter as useRouter5 } from "next/navigation";
|
|
2213
2756
|
import {
|
|
2214
2757
|
setSiteSetting as setSiteSetting2,
|
|
@@ -2219,8 +2762,8 @@ import {
|
|
|
2219
2762
|
parseLinkList,
|
|
2220
2763
|
stringifyLinkList
|
|
2221
2764
|
} from "ampless";
|
|
2222
|
-
import { Button as
|
|
2223
|
-
import { jsx as
|
|
2765
|
+
import { Button as Button13, Input as Input6, Label as Label5 } from "@ampless/runtime/ui";
|
|
2766
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2224
2767
|
var CACHE_REBUILD_DELAY_MS = 8e3;
|
|
2225
2768
|
function ThemeSettingsForm({
|
|
2226
2769
|
siteId,
|
|
@@ -2232,14 +2775,14 @@ function ThemeSettingsForm({
|
|
|
2232
2775
|
const router = useRouter5();
|
|
2233
2776
|
const t = useT();
|
|
2234
2777
|
const locale = useLocale();
|
|
2235
|
-
const [state, setState] =
|
|
2236
|
-
const [pendingTheme, setPendingTheme] =
|
|
2237
|
-
const [optimisticActive, setOptimisticActive] =
|
|
2238
|
-
const [saving, setSaving] =
|
|
2239
|
-
const [switching, setSwitching] =
|
|
2240
|
-
const [error, setError] =
|
|
2241
|
-
const [info, setInfo] =
|
|
2242
|
-
const [invalid, setInvalid] =
|
|
2778
|
+
const [state, setState] = useState12({ values: initial, touched: {} });
|
|
2779
|
+
const [pendingTheme, setPendingTheme] = useState12(activeTheme);
|
|
2780
|
+
const [optimisticActive, setOptimisticActive] = useState12(activeTheme);
|
|
2781
|
+
const [saving, setSaving] = useState12(false);
|
|
2782
|
+
const [switching, setSwitching] = useState12(false);
|
|
2783
|
+
const [error, setError] = useState12(null);
|
|
2784
|
+
const [info, setInfo] = useState12(null);
|
|
2785
|
+
const [invalid, setInvalid] = useState12({});
|
|
2243
2786
|
function update(key, value) {
|
|
2244
2787
|
setState((prev) => ({
|
|
2245
2788
|
values: { ...prev.values, [key]: value },
|
|
@@ -2325,23 +2868,23 @@ function ThemeSettingsForm({
|
|
|
2325
2868
|
}
|
|
2326
2869
|
}
|
|
2327
2870
|
const groups = groupFields(manifest.fields);
|
|
2328
|
-
return /* @__PURE__ */
|
|
2329
|
-
/* @__PURE__ */
|
|
2330
|
-
/* @__PURE__ */
|
|
2331
|
-
/* @__PURE__ */
|
|
2332
|
-
/* @__PURE__ */
|
|
2871
|
+
return /* @__PURE__ */ jsxs18("div", { className: "space-y-8", children: [
|
|
2872
|
+
/* @__PURE__ */ jsxs18("form", { onSubmit: switchTheme, className: "max-w-xl space-y-3 rounded-md border p-4", children: [
|
|
2873
|
+
/* @__PURE__ */ jsxs18("div", { className: "space-y-1", children: [
|
|
2874
|
+
/* @__PURE__ */ jsx20(Label5, { htmlFor: "active-theme", className: "text-sm font-medium", children: t("theme.activeLabel") }),
|
|
2875
|
+
/* @__PURE__ */ jsxs18("p", { className: "text-xs text-muted-foreground", children: [
|
|
2333
2876
|
t("theme.currentlyActive", { theme: optimisticActive }),
|
|
2334
2877
|
optimisticActive !== activeTheme && t("theme.propagating")
|
|
2335
2878
|
] })
|
|
2336
2879
|
] }),
|
|
2337
|
-
/* @__PURE__ */
|
|
2880
|
+
/* @__PURE__ */ jsx20(
|
|
2338
2881
|
"select",
|
|
2339
2882
|
{
|
|
2340
2883
|
id: "active-theme",
|
|
2341
2884
|
className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
|
|
2342
2885
|
value: pendingTheme,
|
|
2343
2886
|
onChange: (e) => setPendingTheme(e.target.value),
|
|
2344
|
-
children: themeOptions.map((opt) => /* @__PURE__ */
|
|
2887
|
+
children: themeOptions.map((opt) => /* @__PURE__ */ jsxs18("option", { value: opt.value, children: [
|
|
2345
2888
|
resolveLocalized(opt.label, locale),
|
|
2346
2889
|
" (",
|
|
2347
2890
|
opt.value,
|
|
@@ -2351,10 +2894,10 @@ function ThemeSettingsForm({
|
|
|
2351
2894
|
),
|
|
2352
2895
|
(() => {
|
|
2353
2896
|
const desc = themeOptions.find((o) => o.value === pendingTheme)?.description;
|
|
2354
|
-
return desc ? /* @__PURE__ */
|
|
2897
|
+
return desc ? /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(desc, locale) }) : null;
|
|
2355
2898
|
})(),
|
|
2356
|
-
/* @__PURE__ */
|
|
2357
|
-
|
|
2899
|
+
/* @__PURE__ */ jsx20(
|
|
2900
|
+
Button13,
|
|
2358
2901
|
{
|
|
2359
2902
|
type: "submit",
|
|
2360
2903
|
disabled: switching || pendingTheme === optimisticActive,
|
|
@@ -2363,9 +2906,9 @@ function ThemeSettingsForm({
|
|
|
2363
2906
|
}
|
|
2364
2907
|
)
|
|
2365
2908
|
] }),
|
|
2366
|
-
/* @__PURE__ */
|
|
2367
|
-
/* @__PURE__ */
|
|
2368
|
-
/* @__PURE__ */
|
|
2909
|
+
/* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2910
|
+
/* @__PURE__ */ jsx20(Label5, { className: "text-sm font-medium", children: t("theme.previewLabel") }),
|
|
2911
|
+
/* @__PURE__ */ jsx20(
|
|
2369
2912
|
"iframe",
|
|
2370
2913
|
{
|
|
2371
2914
|
src: `/?previewTheme=${encodeURIComponent(pendingTheme)}`,
|
|
@@ -2374,19 +2917,19 @@ function ThemeSettingsForm({
|
|
|
2374
2917
|
},
|
|
2375
2918
|
pendingTheme
|
|
2376
2919
|
),
|
|
2377
|
-
/* @__PURE__ */
|
|
2920
|
+
/* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: t("theme.previewHint") })
|
|
2378
2921
|
] }),
|
|
2379
|
-
/* @__PURE__ */
|
|
2380
|
-
/* @__PURE__ */
|
|
2381
|
-
/* @__PURE__ */
|
|
2922
|
+
/* @__PURE__ */ jsxs18("form", { onSubmit: save, className: "max-w-xl space-y-6", children: [
|
|
2923
|
+
/* @__PURE__ */ jsxs18("div", { children: [
|
|
2924
|
+
/* @__PURE__ */ jsx20("h2", { className: "text-lg font-semibold", children: t("theme.customizationHeading", {
|
|
2382
2925
|
theme: resolveLocalized(manifest.label, locale)
|
|
2383
2926
|
}) }),
|
|
2384
|
-
manifest.description && /* @__PURE__ */
|
|
2385
|
-
/* @__PURE__ */
|
|
2927
|
+
manifest.description && /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: resolveLocalized(manifest.description, locale) }),
|
|
2928
|
+
/* @__PURE__ */ jsx20("p", { className: "mt-1 text-xs text-muted-foreground", children: t("theme.customizationHint") })
|
|
2386
2929
|
] }),
|
|
2387
|
-
groups.map(({ key, name, fields }) => /* @__PURE__ */
|
|
2388
|
-
/* @__PURE__ */
|
|
2389
|
-
fields.map((field) => /* @__PURE__ */
|
|
2930
|
+
groups.map(({ key, name, fields }) => /* @__PURE__ */ jsxs18("fieldset", { className: "space-y-4", children: [
|
|
2931
|
+
/* @__PURE__ */ jsx20("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: resolveLocalized(name, locale) }),
|
|
2932
|
+
fields.map((field) => /* @__PURE__ */ jsx20(
|
|
2390
2933
|
FieldRow,
|
|
2391
2934
|
{
|
|
2392
2935
|
field,
|
|
@@ -2397,9 +2940,9 @@ function ThemeSettingsForm({
|
|
|
2397
2940
|
field.key
|
|
2398
2941
|
))
|
|
2399
2942
|
] }, key)),
|
|
2400
|
-
info && /* @__PURE__ */
|
|
2401
|
-
error && /* @__PURE__ */
|
|
2402
|
-
/* @__PURE__ */
|
|
2943
|
+
info && /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: info }),
|
|
2944
|
+
error && /* @__PURE__ */ jsx20("p", { className: "text-sm text-destructive", children: error }),
|
|
2945
|
+
/* @__PURE__ */ jsx20(Button13, { type: "submit", disabled: saving, children: saving ? t("theme.saving") : t("theme.saveButton") })
|
|
2403
2946
|
] })
|
|
2404
2947
|
] });
|
|
2405
2948
|
}
|
|
@@ -2421,16 +2964,16 @@ function FieldRow({ field, value, invalid, onChange }) {
|
|
|
2421
2964
|
const t = useT();
|
|
2422
2965
|
const locale = useLocale();
|
|
2423
2966
|
const id = `theme-${field.key}`;
|
|
2424
|
-
const labelEl = /* @__PURE__ */
|
|
2425
|
-
const description = field.description ? /* @__PURE__ */
|
|
2967
|
+
const labelEl = /* @__PURE__ */ jsx20(Label5, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: resolveLocalized(field.label, locale) });
|
|
2968
|
+
const description = field.description ? /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(field.description, locale) }) : null;
|
|
2426
2969
|
switch (field.type) {
|
|
2427
2970
|
case "color":
|
|
2428
|
-
return /* @__PURE__ */
|
|
2971
|
+
return /* @__PURE__ */ jsx20(ColorField, { field, id, labelEl, description, value, invalid, onChange });
|
|
2429
2972
|
case "length":
|
|
2430
|
-
return /* @__PURE__ */
|
|
2973
|
+
return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2431
2974
|
labelEl,
|
|
2432
2975
|
description,
|
|
2433
|
-
/* @__PURE__ */
|
|
2976
|
+
/* @__PURE__ */ jsx20(
|
|
2434
2977
|
Input6,
|
|
2435
2978
|
{
|
|
2436
2979
|
id,
|
|
@@ -2441,14 +2984,14 @@ function FieldRow({ field, value, invalid, onChange }) {
|
|
|
2441
2984
|
className: "font-mono text-xs"
|
|
2442
2985
|
}
|
|
2443
2986
|
),
|
|
2444
|
-
/* @__PURE__ */
|
|
2987
|
+
/* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: t("theme.lengthHelp") })
|
|
2445
2988
|
] });
|
|
2446
2989
|
case "select":
|
|
2447
2990
|
case "fontFamily":
|
|
2448
|
-
return /* @__PURE__ */
|
|
2991
|
+
return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2449
2992
|
labelEl,
|
|
2450
2993
|
description,
|
|
2451
|
-
/* @__PURE__ */
|
|
2994
|
+
/* @__PURE__ */ jsxs18(
|
|
2452
2995
|
"select",
|
|
2453
2996
|
{
|
|
2454
2997
|
id,
|
|
@@ -2457,17 +3000,17 @@ function FieldRow({ field, value, invalid, onChange }) {
|
|
|
2457
3000
|
onChange: (e) => onChange(e.target.value),
|
|
2458
3001
|
"aria-invalid": invalid,
|
|
2459
3002
|
children: [
|
|
2460
|
-
/* @__PURE__ */
|
|
2461
|
-
field.options.map((opt) => /* @__PURE__ */
|
|
3003
|
+
/* @__PURE__ */ jsx20("option", { value: "", children: t("common.default") }),
|
|
3004
|
+
field.options.map((opt) => /* @__PURE__ */ jsx20("option", { value: opt.value, children: resolveLocalized(opt.label, locale) }, opt.value))
|
|
2462
3005
|
]
|
|
2463
3006
|
}
|
|
2464
3007
|
)
|
|
2465
3008
|
] });
|
|
2466
3009
|
case "image":
|
|
2467
|
-
return /* @__PURE__ */
|
|
3010
|
+
return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2468
3011
|
labelEl,
|
|
2469
3012
|
description,
|
|
2470
|
-
/* @__PURE__ */
|
|
3013
|
+
/* @__PURE__ */ jsx20(
|
|
2471
3014
|
Input6,
|
|
2472
3015
|
{
|
|
2473
3016
|
id,
|
|
@@ -2479,10 +3022,10 @@ function FieldRow({ field, value, invalid, onChange }) {
|
|
|
2479
3022
|
)
|
|
2480
3023
|
] });
|
|
2481
3024
|
case "text":
|
|
2482
|
-
return /* @__PURE__ */
|
|
3025
|
+
return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2483
3026
|
labelEl,
|
|
2484
3027
|
description,
|
|
2485
|
-
/* @__PURE__ */
|
|
3028
|
+
/* @__PURE__ */ jsx20(
|
|
2486
3029
|
Input6,
|
|
2487
3030
|
{
|
|
2488
3031
|
id,
|
|
@@ -2495,7 +3038,7 @@ function FieldRow({ field, value, invalid, onChange }) {
|
|
|
2495
3038
|
)
|
|
2496
3039
|
] });
|
|
2497
3040
|
case "linkList":
|
|
2498
|
-
return /* @__PURE__ */
|
|
3041
|
+
return /* @__PURE__ */ jsx20(
|
|
2499
3042
|
LinkListField,
|
|
2500
3043
|
{
|
|
2501
3044
|
field,
|
|
@@ -2520,7 +3063,7 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
|
|
|
2520
3063
|
if (items.length >= max) return;
|
|
2521
3064
|
commit([...items, { label: "", url: "" }]);
|
|
2522
3065
|
}
|
|
2523
|
-
function
|
|
3066
|
+
function remove3(idx) {
|
|
2524
3067
|
commit(items.filter((_, i) => i !== idx));
|
|
2525
3068
|
}
|
|
2526
3069
|
function move(idx, delta) {
|
|
@@ -2531,16 +3074,16 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
|
|
|
2531
3074
|
next.splice(target, 0, moved);
|
|
2532
3075
|
commit(next);
|
|
2533
3076
|
}
|
|
2534
|
-
return /* @__PURE__ */
|
|
3077
|
+
return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2535
3078
|
labelEl,
|
|
2536
3079
|
description,
|
|
2537
|
-
/* @__PURE__ */
|
|
2538
|
-
items.length === 0 && /* @__PURE__ */
|
|
3080
|
+
/* @__PURE__ */ jsxs18("div", { className: "space-y-2 rounded-md border bg-muted/20 p-3", children: [
|
|
3081
|
+
items.length === 0 && /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: "No links yet." }),
|
|
2539
3082
|
items.map((item, idx) => {
|
|
2540
3083
|
const isTagRef = /^tag:/.test(item.url.trim());
|
|
2541
|
-
return /* @__PURE__ */
|
|
2542
|
-
/* @__PURE__ */
|
|
2543
|
-
/* @__PURE__ */
|
|
3084
|
+
return /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-start gap-2", children: [
|
|
3085
|
+
/* @__PURE__ */ jsxs18("div", { className: "grid flex-1 grid-cols-1 gap-2 sm:grid-cols-2", children: [
|
|
3086
|
+
/* @__PURE__ */ jsx20(
|
|
2544
3087
|
Input6,
|
|
2545
3088
|
{
|
|
2546
3089
|
value: item.label,
|
|
@@ -2548,7 +3091,7 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
|
|
|
2548
3091
|
onChange: (e) => update(idx, { label: e.target.value })
|
|
2549
3092
|
}
|
|
2550
3093
|
),
|
|
2551
|
-
/* @__PURE__ */
|
|
3094
|
+
/* @__PURE__ */ jsx20(
|
|
2552
3095
|
Input6,
|
|
2553
3096
|
{
|
|
2554
3097
|
value: item.url,
|
|
@@ -2558,9 +3101,9 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
|
|
|
2558
3101
|
}
|
|
2559
3102
|
)
|
|
2560
3103
|
] }),
|
|
2561
|
-
/* @__PURE__ */
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
|
|
3104
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
3105
|
+
/* @__PURE__ */ jsx20(
|
|
3106
|
+
Button13,
|
|
2564
3107
|
{
|
|
2565
3108
|
type: "button",
|
|
2566
3109
|
variant: "ghost",
|
|
@@ -2571,8 +3114,8 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
|
|
|
2571
3114
|
children: "\u2191"
|
|
2572
3115
|
}
|
|
2573
3116
|
),
|
|
2574
|
-
/* @__PURE__ */
|
|
2575
|
-
|
|
3117
|
+
/* @__PURE__ */ jsx20(
|
|
3118
|
+
Button13,
|
|
2576
3119
|
{
|
|
2577
3120
|
type: "button",
|
|
2578
3121
|
variant: "ghost",
|
|
@@ -2583,13 +3126,13 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
|
|
|
2583
3126
|
children: "\u2193"
|
|
2584
3127
|
}
|
|
2585
3128
|
),
|
|
2586
|
-
/* @__PURE__ */
|
|
2587
|
-
|
|
3129
|
+
/* @__PURE__ */ jsx20(
|
|
3130
|
+
Button13,
|
|
2588
3131
|
{
|
|
2589
3132
|
type: "button",
|
|
2590
3133
|
variant: "ghost",
|
|
2591
3134
|
size: "icon",
|
|
2592
|
-
onClick: () =>
|
|
3135
|
+
onClick: () => remove3(idx),
|
|
2593
3136
|
"aria-label": "Remove",
|
|
2594
3137
|
children: "\xD7"
|
|
2595
3138
|
}
|
|
@@ -2597,8 +3140,8 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
|
|
|
2597
3140
|
] })
|
|
2598
3141
|
] }, idx);
|
|
2599
3142
|
}),
|
|
2600
|
-
/* @__PURE__ */
|
|
2601
|
-
|
|
3143
|
+
/* @__PURE__ */ jsx20(
|
|
3144
|
+
Button13,
|
|
2602
3145
|
{
|
|
2603
3146
|
type: "button",
|
|
2604
3147
|
variant: "outline",
|
|
@@ -2608,9 +3151,9 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
|
|
|
2608
3151
|
children: "+ Add link"
|
|
2609
3152
|
}
|
|
2610
3153
|
),
|
|
2611
|
-
/* @__PURE__ */
|
|
3154
|
+
/* @__PURE__ */ jsxs18("p", { className: "text-xs text-muted-foreground", children: [
|
|
2612
3155
|
"Tip: use ",
|
|
2613
|
-
/* @__PURE__ */
|
|
3156
|
+
/* @__PURE__ */ jsx20("code", { children: "tag:<name>" }),
|
|
2614
3157
|
" as a URL to render a list of posts with that tag instead of a single link."
|
|
2615
3158
|
] })
|
|
2616
3159
|
] })
|
|
@@ -2627,11 +3170,11 @@ function ColorField({
|
|
|
2627
3170
|
}) {
|
|
2628
3171
|
const effective = value || field.default;
|
|
2629
3172
|
const hex = useColorAsHex(effective);
|
|
2630
|
-
return /* @__PURE__ */
|
|
3173
|
+
return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2631
3174
|
labelEl,
|
|
2632
3175
|
description,
|
|
2633
|
-
/* @__PURE__ */
|
|
2634
|
-
/* @__PURE__ */
|
|
3176
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", children: [
|
|
3177
|
+
/* @__PURE__ */ jsx20(
|
|
2635
3178
|
"input",
|
|
2636
3179
|
{
|
|
2637
3180
|
type: "color",
|
|
@@ -2641,7 +3184,7 @@ function ColorField({
|
|
|
2641
3184
|
"aria-label": `${field.label} swatch`
|
|
2642
3185
|
}
|
|
2643
3186
|
),
|
|
2644
|
-
/* @__PURE__ */
|
|
3187
|
+
/* @__PURE__ */ jsx20(
|
|
2645
3188
|
Input6,
|
|
2646
3189
|
{
|
|
2647
3190
|
id,
|
|
@@ -2653,8 +3196,8 @@ function ColorField({
|
|
|
2653
3196
|
}
|
|
2654
3197
|
)
|
|
2655
3198
|
] }),
|
|
2656
|
-
/* @__PURE__ */
|
|
2657
|
-
/* @__PURE__ */
|
|
3199
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", children: [
|
|
3200
|
+
/* @__PURE__ */ jsx20(
|
|
2658
3201
|
"span",
|
|
2659
3202
|
{
|
|
2660
3203
|
className: "inline-block h-4 w-4 rounded border",
|
|
@@ -2662,7 +3205,7 @@ function ColorField({
|
|
|
2662
3205
|
"aria-hidden": true
|
|
2663
3206
|
}
|
|
2664
3207
|
),
|
|
2665
|
-
/* @__PURE__ */
|
|
3208
|
+
/* @__PURE__ */ jsx20("code", { className: "text-xs text-muted-foreground", children: effective })
|
|
2666
3209
|
] })
|
|
2667
3210
|
] });
|
|
2668
3211
|
}
|