@ampless/admin 0.2.0-alpha.1 → 0.2.0-alpha.11
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-2ITWLRYF.js +38 -0
- package/dist/chunk-4YEBIBFG.js +48 -0
- package/dist/chunk-5OIPGVGG.js +198 -0
- package/dist/chunk-7IJDOT2K.js +1197 -0
- package/dist/chunk-7IR4F7GA.js +6 -0
- package/dist/chunk-BFZODSUT.js +71 -0
- package/dist/chunk-BYLCQYEQ.js +21 -0
- package/dist/chunk-GDQC5X46.js +250 -0
- package/dist/chunk-OFHKZNZS.js +33 -0
- package/dist/{chunk-TJR3ALRJ.js → chunk-OPQ3SAZJ.js} +75 -66
- package/dist/chunk-QVUTNQZH.js +21 -0
- package/dist/chunk-TZWSXAHD.js +32 -0
- package/dist/chunk-UOU7KQLR.js +149 -0
- package/dist/chunk-VXEVLHGL.js +10 -0
- package/dist/chunk-XHWECTED.js +1125 -0
- package/dist/chunk-XXJDT6FF.js +335 -0
- package/dist/chunk-XZQRPXKN.js +41 -0
- package/dist/components/admin-dashboard.d.ts +10 -0
- package/dist/components/admin-dashboard.js +9 -0
- package/dist/components/edit-post-view.d.ts +9 -0
- package/dist/components/edit-post-view.js +14 -0
- package/dist/components/index.d.ts +32 -19
- package/dist/components/index.js +33 -15
- package/dist/components/login-view.d.ts +5 -0
- package/dist/components/login-view.js +9 -0
- package/dist/components/media-view.d.ts +5 -0
- package/dist/components/media-view.js +12 -0
- package/dist/components/new-post-view.d.ts +5 -0
- package/dist/components/new-post-view.js +14 -0
- package/dist/components/posts-list-view.d.ts +5 -0
- package/dist/components/posts-list-view.js +11 -0
- package/dist/components/users-list-view.d.ts +7 -0
- package/dist/components/users-list-view.js +9 -0
- package/dist/{i18n-ByHM_Bho.d.ts → i18n-DzXXcIQQ.d.ts} +110 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -3
- package/dist/lib/theme-actions.d.ts +17 -0
- package/dist/lib/theme-actions.js +7 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/pages/index.d.ts +49 -15
- package/dist/pages/index.js +114 -658
- package/package.json +10 -9
- package/dist/chunk-T2RSMFOI.js +0 -2074
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
useT
|
|
4
|
+
} from "./chunk-OFHKZNZS.js";
|
|
5
|
+
|
|
6
|
+
// src/components/image-upload-dialog.tsx
|
|
7
|
+
import { useEffect, useRef, useState } from "react";
|
|
8
|
+
import ReactCrop, { centerCrop, makeAspectCrop } from "react-image-crop";
|
|
9
|
+
import "react-image-crop/dist/ReactCrop.css";
|
|
10
|
+
import { shouldSkipProcessing } from "ampless/media";
|
|
11
|
+
import {
|
|
12
|
+
Dialog,
|
|
13
|
+
DialogContent,
|
|
14
|
+
DialogDescription,
|
|
15
|
+
DialogHeader,
|
|
16
|
+
DialogTitle,
|
|
17
|
+
Button,
|
|
18
|
+
Input,
|
|
19
|
+
Label
|
|
20
|
+
} from "@ampless/runtime/ui";
|
|
21
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
22
|
+
var ASPECTS = {
|
|
23
|
+
free: void 0,
|
|
24
|
+
"1:1": 1,
|
|
25
|
+
"4:3": 4 / 3,
|
|
26
|
+
"16:9": 16 / 9,
|
|
27
|
+
"3:2": 3 / 2
|
|
28
|
+
};
|
|
29
|
+
var ASPECT_CHOICES = ["free", "1:1", "4:3", "16:9", "3:2"];
|
|
30
|
+
var FORMAT_CHOICES = ["auto", "webp", "jpeg"];
|
|
31
|
+
var MAX_DIMENSION_PRESETS = [640, 1024, 1600, 2400, 4e3];
|
|
32
|
+
var MIN_DIMENSION = 100;
|
|
33
|
+
var MAX_DIMENSION_CEILING = 8e3;
|
|
34
|
+
function clampMaxDimension(value, fallback) {
|
|
35
|
+
if (!Number.isFinite(value) || value <= 0) return fallback;
|
|
36
|
+
return Math.min(MAX_DIMENSION_CEILING, Math.max(MIN_DIMENSION, Math.round(value)));
|
|
37
|
+
}
|
|
38
|
+
function clampQuality(value) {
|
|
39
|
+
if (!Number.isFinite(value)) return 0.85;
|
|
40
|
+
return Math.min(1, Math.max(0, value));
|
|
41
|
+
}
|
|
42
|
+
function resolveFormat(choice, inputMime, losslessForPng) {
|
|
43
|
+
if (choice === "auto") {
|
|
44
|
+
return {
|
|
45
|
+
format: "webp",
|
|
46
|
+
lossless: losslessForPng && inputMime === "image/png"
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return { format: choice, lossless: false };
|
|
50
|
+
}
|
|
51
|
+
function buildInitialCrop(naturalWidth, naturalHeight, aspect) {
|
|
52
|
+
if (aspect) {
|
|
53
|
+
return centerCrop(
|
|
54
|
+
makeAspectCrop({ unit: "%", width: 100 }, aspect, naturalWidth, naturalHeight),
|
|
55
|
+
naturalWidth,
|
|
56
|
+
naturalHeight
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return { unit: "%", x: 0, y: 0, width: 100, height: 100 };
|
|
60
|
+
}
|
|
61
|
+
function ImageUploadDialog({
|
|
62
|
+
file,
|
|
63
|
+
remaining,
|
|
64
|
+
busy = false,
|
|
65
|
+
defaults,
|
|
66
|
+
onConfirm,
|
|
67
|
+
onSkip,
|
|
68
|
+
onCancel
|
|
69
|
+
}) {
|
|
70
|
+
const t = useT();
|
|
71
|
+
const defaultMaxDimension = defaults?.maxDimension ?? 2400;
|
|
72
|
+
const defaultQuality = defaults?.quality ?? 0.85;
|
|
73
|
+
const losslessForPng = defaults?.losslessForPng ?? true;
|
|
74
|
+
const [original, setOriginal] = useState(false);
|
|
75
|
+
const [aspect, setAspect] = useState("free");
|
|
76
|
+
const [crop, setCrop] = useState(void 0);
|
|
77
|
+
const [percentCrop, setPercentCrop] = useState(null);
|
|
78
|
+
const [naturalSize, setNaturalSize] = useState(null);
|
|
79
|
+
const [formatChoice, setFormatChoice] = useState("auto");
|
|
80
|
+
const [losslessOverride, setLosslessOverride] = useState(null);
|
|
81
|
+
const [quality, setQuality] = useState(defaultQuality);
|
|
82
|
+
const [maxDimension, setMaxDimension] = useState(defaultMaxDimension);
|
|
83
|
+
const [previewUrl, setPreviewUrl] = useState(null);
|
|
84
|
+
const imgRef = useRef(null);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
setOriginal(false);
|
|
87
|
+
setAspect("free");
|
|
88
|
+
setCrop(void 0);
|
|
89
|
+
setPercentCrop(null);
|
|
90
|
+
setNaturalSize(null);
|
|
91
|
+
setFormatChoice("auto");
|
|
92
|
+
setLosslessOverride(null);
|
|
93
|
+
setQuality(defaultQuality);
|
|
94
|
+
setMaxDimension(defaultMaxDimension);
|
|
95
|
+
}, [file, defaultQuality, defaultMaxDimension]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!naturalSize) return;
|
|
98
|
+
const next = buildInitialCrop(naturalSize.width, naturalSize.height, ASPECTS[aspect]);
|
|
99
|
+
setCrop(next);
|
|
100
|
+
setPercentCrop(next);
|
|
101
|
+
}, [aspect, naturalSize]);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!file) {
|
|
104
|
+
setPreviewUrl(null);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const url = URL.createObjectURL(file);
|
|
108
|
+
setPreviewUrl(url);
|
|
109
|
+
return () => URL.revokeObjectURL(url);
|
|
110
|
+
}, [file]);
|
|
111
|
+
if (!file) return null;
|
|
112
|
+
const isImage = file.type.startsWith("image/");
|
|
113
|
+
const passthrough = !isImage || shouldSkipProcessing(file.type);
|
|
114
|
+
const showCropper = !passthrough && !original;
|
|
115
|
+
const { format, lossless: autoLossless } = resolveFormat(formatChoice, file.type, losslessForPng);
|
|
116
|
+
const lossless = losslessOverride ?? autoLossless;
|
|
117
|
+
const showLosslessToggle = !original && !passthrough && format === "webp";
|
|
118
|
+
const showQualitySlider = !original && !passthrough && (format === "jpeg" || format === "webp" && !lossless);
|
|
119
|
+
function handleImageLoad(e) {
|
|
120
|
+
const { naturalWidth, naturalHeight } = e.currentTarget;
|
|
121
|
+
setNaturalSize({ width: naturalWidth, height: naturalHeight });
|
|
122
|
+
const initial = buildInitialCrop(naturalWidth, naturalHeight, ASPECTS[aspect]);
|
|
123
|
+
setCrop(initial);
|
|
124
|
+
setPercentCrop(initial);
|
|
125
|
+
}
|
|
126
|
+
function handleConfirm() {
|
|
127
|
+
if (!file || busy) return;
|
|
128
|
+
if (original || passthrough) {
|
|
129
|
+
onConfirm(file, { original: true });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
let cropArea = void 0;
|
|
133
|
+
if (percentCrop && naturalSize) {
|
|
134
|
+
const x = Math.round(percentCrop.x / 100 * naturalSize.width);
|
|
135
|
+
const y = Math.round(percentCrop.y / 100 * naturalSize.height);
|
|
136
|
+
const width = Math.round(percentCrop.width / 100 * naturalSize.width);
|
|
137
|
+
const height = Math.round(percentCrop.height / 100 * naturalSize.height);
|
|
138
|
+
if (width > 0 && height > 0 && (x !== 0 || y !== 0 || width !== naturalSize.width || height !== naturalSize.height)) {
|
|
139
|
+
cropArea = { x, y, width, height };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
onConfirm(file, {
|
|
143
|
+
crop: cropArea,
|
|
144
|
+
maxDimension: clampMaxDimension(maxDimension, defaultMaxDimension),
|
|
145
|
+
format,
|
|
146
|
+
quality: clampQuality(quality),
|
|
147
|
+
lossless: format === "webp" ? lossless : false
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return /* @__PURE__ */ jsx(
|
|
151
|
+
Dialog,
|
|
152
|
+
{
|
|
153
|
+
open: true,
|
|
154
|
+
onOpenChange: (open) => {
|
|
155
|
+
if (open) return;
|
|
156
|
+
onCancel();
|
|
157
|
+
},
|
|
158
|
+
children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90vh] max-w-4xl overflow-y-auto", children: [
|
|
159
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
160
|
+
/* @__PURE__ */ jsx(DialogTitle, { className: "truncate", children: file.name }),
|
|
161
|
+
/* @__PURE__ */ jsxs(DialogDescription, { children: [
|
|
162
|
+
remaining > 1 ? t("media.dialog.remaining", { count: remaining }) : `${formatBytes(file.size)} \xB7 ${file.type || "unknown"}`,
|
|
163
|
+
busy && t("media.dialog.uploading")
|
|
164
|
+
] })
|
|
165
|
+
] }),
|
|
166
|
+
previewUrl && showCropper && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center rounded-md bg-black/90 p-2", children: /* @__PURE__ */ jsx(
|
|
167
|
+
ReactCrop,
|
|
168
|
+
{
|
|
169
|
+
crop,
|
|
170
|
+
aspect: ASPECTS[aspect],
|
|
171
|
+
minWidth: 20,
|
|
172
|
+
minHeight: 20,
|
|
173
|
+
onChange: (_pixel, percent) => {
|
|
174
|
+
setCrop(percent);
|
|
175
|
+
setPercentCrop(percent);
|
|
176
|
+
},
|
|
177
|
+
children: /* @__PURE__ */ jsx(
|
|
178
|
+
"img",
|
|
179
|
+
{
|
|
180
|
+
ref: imgRef,
|
|
181
|
+
src: previewUrl,
|
|
182
|
+
alt: "preview",
|
|
183
|
+
className: "block max-h-[60vh] max-w-full",
|
|
184
|
+
onLoad: handleImageLoad
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
) }),
|
|
189
|
+
previewUrl && !showCropper && isImage && /* @__PURE__ */ jsx("div", { className: "flex h-48 items-center justify-center rounded-md bg-muted", children: /* @__PURE__ */ jsx("img", { src: previewUrl, alt: "preview", className: "max-h-full max-w-full object-contain" }) }),
|
|
190
|
+
!isImage && // Non-image upload: skip the broken-img preview. Show the
|
|
191
|
+
// file's name / size / mime so the admin can confirm before
|
|
192
|
+
// committing the bytes to S3.
|
|
193
|
+
/* @__PURE__ */ jsxs("div", { className: "flex h-32 flex-col items-center justify-center gap-1 rounded-md bg-muted text-sm text-muted-foreground", children: [
|
|
194
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: file.name }),
|
|
195
|
+
/* @__PURE__ */ jsxs("span", { className: "font-mono text-xs", children: [
|
|
196
|
+
formatBytes(file.size),
|
|
197
|
+
" \xB7 ",
|
|
198
|
+
file.type || "unknown"
|
|
199
|
+
] })
|
|
200
|
+
] }),
|
|
201
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
202
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
203
|
+
/* @__PURE__ */ jsx(
|
|
204
|
+
"input",
|
|
205
|
+
{
|
|
206
|
+
type: "checkbox",
|
|
207
|
+
checked: original,
|
|
208
|
+
disabled: busy,
|
|
209
|
+
onChange: (e) => setOriginal(e.target.checked)
|
|
210
|
+
}
|
|
211
|
+
),
|
|
212
|
+
/* @__PURE__ */ jsx("span", { children: t("media.dialog.useOriginal") }),
|
|
213
|
+
passthrough && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("media.dialog.passthroughNote") })
|
|
214
|
+
] }),
|
|
215
|
+
!original && !passthrough && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
216
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
217
|
+
/* @__PURE__ */ jsx(Label, { children: t("media.dialog.aspectRatio") }),
|
|
218
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: ASPECT_CHOICES.map((choice) => /* @__PURE__ */ jsx(
|
|
219
|
+
Button,
|
|
220
|
+
{
|
|
221
|
+
type: "button",
|
|
222
|
+
variant: aspect === choice ? "default" : "outline",
|
|
223
|
+
size: "sm",
|
|
224
|
+
disabled: busy,
|
|
225
|
+
onClick: () => setAspect(choice),
|
|
226
|
+
children: choice
|
|
227
|
+
},
|
|
228
|
+
choice
|
|
229
|
+
)) })
|
|
230
|
+
] }),
|
|
231
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
232
|
+
/* @__PURE__ */ jsx(Label, { children: t("media.dialog.outputFormat") }),
|
|
233
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: FORMAT_CHOICES.map((choice) => /* @__PURE__ */ jsx(
|
|
234
|
+
Button,
|
|
235
|
+
{
|
|
236
|
+
type: "button",
|
|
237
|
+
variant: formatChoice === choice ? "default" : "outline",
|
|
238
|
+
size: "sm",
|
|
239
|
+
disabled: busy,
|
|
240
|
+
onClick: () => {
|
|
241
|
+
setFormatChoice(choice);
|
|
242
|
+
setLosslessOverride(null);
|
|
243
|
+
},
|
|
244
|
+
children: choice
|
|
245
|
+
},
|
|
246
|
+
choice
|
|
247
|
+
)) })
|
|
248
|
+
] }),
|
|
249
|
+
showLosslessToggle && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
250
|
+
/* @__PURE__ */ jsx(
|
|
251
|
+
"input",
|
|
252
|
+
{
|
|
253
|
+
type: "checkbox",
|
|
254
|
+
checked: lossless,
|
|
255
|
+
disabled: busy,
|
|
256
|
+
onChange: (e) => setLosslessOverride(e.target.checked)
|
|
257
|
+
}
|
|
258
|
+
),
|
|
259
|
+
/* @__PURE__ */ jsx("span", { children: t("media.dialog.losslessWebp") })
|
|
260
|
+
] }),
|
|
261
|
+
showQualitySlider && /* @__PURE__ */ jsxs("div", { children: [
|
|
262
|
+
/* @__PURE__ */ jsx(Label, { children: t("media.dialog.quality", { value: Math.round(quality * 100) }) }),
|
|
263
|
+
/* @__PURE__ */ jsx(
|
|
264
|
+
"input",
|
|
265
|
+
{
|
|
266
|
+
type: "range",
|
|
267
|
+
min: 50,
|
|
268
|
+
max: 100,
|
|
269
|
+
step: 1,
|
|
270
|
+
disabled: busy,
|
|
271
|
+
value: Math.round(quality * 100),
|
|
272
|
+
onChange: (e) => setQuality(Number(e.target.value) / 100),
|
|
273
|
+
className: "mt-2 w-full"
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
] }),
|
|
277
|
+
/* @__PURE__ */ jsxs("div", { className: "max-w-xs", children: [
|
|
278
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "maxDimension", children: t("media.dialog.maxDimension") }),
|
|
279
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: MAX_DIMENSION_PRESETS.map((preset) => /* @__PURE__ */ jsx(
|
|
280
|
+
Button,
|
|
281
|
+
{
|
|
282
|
+
type: "button",
|
|
283
|
+
variant: maxDimension === preset ? "default" : "outline",
|
|
284
|
+
size: "sm",
|
|
285
|
+
disabled: busy,
|
|
286
|
+
onClick: () => setMaxDimension(preset),
|
|
287
|
+
children: preset
|
|
288
|
+
},
|
|
289
|
+
preset
|
|
290
|
+
)) }),
|
|
291
|
+
/* @__PURE__ */ jsx(
|
|
292
|
+
Input,
|
|
293
|
+
{
|
|
294
|
+
id: "maxDimension",
|
|
295
|
+
type: "number",
|
|
296
|
+
className: "mt-2",
|
|
297
|
+
min: MIN_DIMENSION,
|
|
298
|
+
max: MAX_DIMENSION_CEILING,
|
|
299
|
+
disabled: busy,
|
|
300
|
+
value: maxDimension,
|
|
301
|
+
onChange: (e) => setMaxDimension(Number(e.target.value) || defaultMaxDimension)
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
] })
|
|
305
|
+
] })
|
|
306
|
+
] }),
|
|
307
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
308
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", type: "button", onClick: onCancel, children: t("media.dialog.cancelAll") }),
|
|
309
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", type: "button", disabled: busy, onClick: onSkip, children: t("media.dialog.skip") }),
|
|
310
|
+
/* @__PURE__ */ jsx(Button, { type: "button", disabled: busy, onClick: handleConfirm, children: busy ? t("media.dialog.uploadingButton") : t("media.dialog.upload") })
|
|
311
|
+
] })
|
|
312
|
+
] })
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
function formatBytes(bytes) {
|
|
317
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
318
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
319
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/lib/admin-config-client.ts
|
|
323
|
+
var cmsConfig = null;
|
|
324
|
+
function setAdminCmsConfigClient(config) {
|
|
325
|
+
cmsConfig = config;
|
|
326
|
+
}
|
|
327
|
+
function getMediaProcessingDefaults() {
|
|
328
|
+
return cmsConfig?.media?.processing;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export {
|
|
332
|
+
ImageUploadDialog,
|
|
333
|
+
setAdminCmsConfigClient,
|
|
334
|
+
getMediaProcessingDefaults
|
|
335
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
PostForm
|
|
4
|
+
} from "./chunk-7IJDOT2K.js";
|
|
5
|
+
import {
|
|
6
|
+
readAdminSiteIdFromCookie
|
|
7
|
+
} from "./chunk-TZWSXAHD.js";
|
|
8
|
+
import {
|
|
9
|
+
useT
|
|
10
|
+
} from "./chunk-OFHKZNZS.js";
|
|
11
|
+
|
|
12
|
+
// src/components/edit-post-view.tsx
|
|
13
|
+
import { useEffect, useState, use } from "react";
|
|
14
|
+
import { notFound } from "next/navigation";
|
|
15
|
+
import { getPostById } from "ampless";
|
|
16
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
+
function EditPostPage({ params }) {
|
|
18
|
+
const t = useT();
|
|
19
|
+
const { postId } = use(params);
|
|
20
|
+
const [post, setPost] = useState(null);
|
|
21
|
+
const [loading, setLoading] = useState(true);
|
|
22
|
+
const [missing, setMissing] = useState(false);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const siteId = readAdminSiteIdFromCookie();
|
|
25
|
+
getPostById(postId, { siteId }).then((p) => {
|
|
26
|
+
if (!p) setMissing(true);
|
|
27
|
+
else setPost(p);
|
|
28
|
+
}).finally(() => setLoading(false));
|
|
29
|
+
}, [postId]);
|
|
30
|
+
if (loading)
|
|
31
|
+
return /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: t("common.loading") });
|
|
32
|
+
if (missing) notFound();
|
|
33
|
+
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.editTitle") }),
|
|
35
|
+
post && /* @__PURE__ */ jsx(PostForm, { post })
|
|
36
|
+
] });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
EditPostPage
|
|
41
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin home / dashboard. Lists post counts. Marked client-side because
|
|
5
|
+
* it reads from the AppSync client directly (no server-rendered query
|
|
6
|
+
* yet — listed posts come from Amplify SDK at mount time).
|
|
7
|
+
*/
|
|
8
|
+
declare function AdminDashboard(): react_jsx_runtime.JSX.Element;
|
|
9
|
+
|
|
10
|
+
export { AdminDashboard };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
EditPostPage
|
|
4
|
+
} from "../chunk-XZQRPXKN.js";
|
|
5
|
+
import "../chunk-7IJDOT2K.js";
|
|
6
|
+
import "../chunk-TZWSXAHD.js";
|
|
7
|
+
import "../chunk-7IR4F7GA.js";
|
|
8
|
+
import "../chunk-XXJDT6FF.js";
|
|
9
|
+
import "../chunk-2ITWLRYF.js";
|
|
10
|
+
import "../chunk-OFHKZNZS.js";
|
|
11
|
+
import "../chunk-OPQ3SAZJ.js";
|
|
12
|
+
export {
|
|
13
|
+
EditPostPage
|
|
14
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { L as Locale, D as Dictionary } from '../i18n-
|
|
2
|
+
import { L as Locale, D as Dictionary } from '../i18n-DzXXcIQQ.js';
|
|
3
3
|
import { Config, Post, ThemeManifest, LocalizedString, MediaProcessingDefaults } from 'ampless';
|
|
4
4
|
import { AmplessOutputs } from '@ampless/runtime';
|
|
5
5
|
import { ProcessOptions } from 'ampless/media';
|
|
6
|
+
export { invalidateSiteSettingsCache } from '../lib/theme-actions.js';
|
|
6
7
|
|
|
7
8
|
interface ProviderProps {
|
|
8
9
|
locale: Locale;
|
|
@@ -23,7 +24,33 @@ declare function useT(): (key: string, vars?: Record<string, string | number>) =
|
|
|
23
24
|
/** Read the active locale from context (e.g. for `<html lang>` parity). */
|
|
24
25
|
declare function useLocale(): Locale;
|
|
25
26
|
|
|
27
|
+
interface Props$3 {
|
|
28
|
+
outputs: AmplessOutputs;
|
|
29
|
+
cmsConfig: Config;
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Client-side admin bootstrap. Runs on first render of any admin route
|
|
34
|
+
* — configures the Amplify SDK, registers the cms.config / outputs in
|
|
35
|
+
* the client-side state modules, and installs the admin's posts / kv
|
|
36
|
+
* providers so 'ampless'-imported functions hit the AppSync client.
|
|
37
|
+
*
|
|
38
|
+
* All bootstrap calls happen synchronously during render, NOT in
|
|
39
|
+
* `useEffect`. React runs child useEffects before parent useEffects,
|
|
40
|
+
* so if `installAdminPostsProvider` were inside an effect, a child
|
|
41
|
+
* page's first `listPosts()` call (also in a useEffect) would race
|
|
42
|
+
* the install and fall back to ampless's dummy data. Synchronous
|
|
43
|
+
* registration guarantees the provider is in place before any child
|
|
44
|
+
* component mounts.
|
|
45
|
+
*
|
|
46
|
+
* All side effects are idempotent (the install functions guard with an
|
|
47
|
+
* `installed` flag) — mounting this multiple times (e.g. during HMR or
|
|
48
|
+
* remount) is safe.
|
|
49
|
+
*/
|
|
50
|
+
declare function AdminProviders({ outputs, cmsConfig, children }: Props$3): react_jsx_runtime.JSX.Element;
|
|
51
|
+
|
|
26
52
|
declare const ADMIN_SITE_COOKIE = "admin-site-id";
|
|
53
|
+
|
|
27
54
|
/**
|
|
28
55
|
* Register the cms.config for client-side multi-site lookups. Called
|
|
29
56
|
* once from the admin layout factory.
|
|
@@ -80,26 +107,12 @@ declare function uploadProcessedImage(file: File, options: ProcessOptions): Prom
|
|
|
80
107
|
url: string;
|
|
81
108
|
}>;
|
|
82
109
|
|
|
83
|
-
|
|
84
|
-
* Force-invalidate the Next.js fetch cache for a site's settings JSON.
|
|
85
|
-
* Used by the admin theme switcher: writes to KvStore propagate to S3
|
|
86
|
-
* via the trusted processor (~5-10s), and without this call the
|
|
87
|
-
* `revalidate: 60` on the public-side fetch would keep serving the old
|
|
88
|
-
* cached response for up to a minute after the rebuild.
|
|
89
|
-
*
|
|
90
|
-
* The cache tag matches the one used in `theme-active.ts` and
|
|
91
|
-
* `theme-config.ts` (in `@ampless/runtime`): `site-settings:{siteId}`.
|
|
92
|
-
*
|
|
93
|
-
* Uses `updateTag` (Next 16+) — the read-your-own-writes variant of
|
|
94
|
-
* the old `revalidateTag`, which is the right semantics inside a
|
|
95
|
-
* server action (this entire module is `'use server'`).
|
|
96
|
-
*/
|
|
97
|
-
declare function invalidateSiteSettingsCache(siteId: string): Promise<void>;
|
|
98
|
-
|
|
99
|
-
declare function Sidebar({ email, siteSelector, }: {
|
|
110
|
+
declare function Sidebar({ email, siteSelector, isAdmin, }: {
|
|
100
111
|
email: string;
|
|
101
112
|
/** Rendered above the main nav in multi-site mode. */
|
|
102
113
|
siteSelector?: React.ReactNode;
|
|
114
|
+
/** Gates `adminOnly` nav entries (user management). */
|
|
115
|
+
isAdmin: boolean;
|
|
103
116
|
}): react_jsx_runtime.JSX.Element;
|
|
104
117
|
|
|
105
118
|
interface SiteOption {
|
|
@@ -170,4 +183,4 @@ interface ImageUploadDialogProps {
|
|
|
170
183
|
}
|
|
171
184
|
declare function ImageUploadDialog({ file, remaining, busy, defaults, onConfirm, onSkip, onCancel, }: ImageUploadDialogProps): react_jsx_runtime.JSX.Element | null;
|
|
172
185
|
|
|
173
|
-
export { ADMIN_SITE_COOKIE, I18nProvider, ImageUploadDialog, type ImageUploadDialogProps, MediaPicker, MediaUploader, PostForm, Sidebar, SiteSelector, SiteSettingsForm, type SiteSettingsFormValues, ThemeSettingsForm,
|
|
186
|
+
export { ADMIN_SITE_COOKIE, AdminProviders, I18nProvider, ImageUploadDialog, type ImageUploadDialogProps, MediaPicker, MediaUploader, PostForm, Sidebar, SiteSelector, SiteSettingsForm, type SiteSettingsFormValues, ThemeSettingsForm, publicMediaUrl, readAdminSiteIdFromCookie, sanitizeName, setAdminCmsConfig, setAdminMediaContext, uploadProcessedImage, useLocale, useT };
|
package/dist/components/index.js
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
ImageUploadDialog,
|
|
4
|
-
MediaPicker,
|
|
5
|
-
MediaUploader,
|
|
6
|
-
PostForm,
|
|
3
|
+
AdminProviders,
|
|
7
4
|
Sidebar,
|
|
8
5
|
SiteSelector,
|
|
9
6
|
SiteSettingsForm,
|
|
10
|
-
ThemeSettingsForm
|
|
11
|
-
|
|
7
|
+
ThemeSettingsForm
|
|
8
|
+
} from "../chunk-XHWECTED.js";
|
|
9
|
+
import {
|
|
10
|
+
invalidateSiteSettingsCache
|
|
11
|
+
} from "../chunk-VXEVLHGL.js";
|
|
12
|
+
import {
|
|
13
|
+
MediaUploader
|
|
14
|
+
} from "../chunk-GDQC5X46.js";
|
|
15
|
+
import {
|
|
16
|
+
MediaPicker,
|
|
17
|
+
PostForm,
|
|
12
18
|
sanitizeName,
|
|
13
|
-
uploadProcessedImage
|
|
14
|
-
|
|
15
|
-
useT
|
|
16
|
-
} from "../chunk-T2RSMFOI.js";
|
|
19
|
+
uploadProcessedImage
|
|
20
|
+
} from "../chunk-7IJDOT2K.js";
|
|
17
21
|
import {
|
|
18
|
-
ADMIN_SITE_COOKIE,
|
|
19
|
-
publicMediaUrl,
|
|
20
22
|
readAdminSiteIdFromCookie,
|
|
21
|
-
setAdminCmsConfig
|
|
23
|
+
setAdminCmsConfig
|
|
24
|
+
} from "../chunk-TZWSXAHD.js";
|
|
25
|
+
import {
|
|
26
|
+
ADMIN_SITE_COOKIE
|
|
27
|
+
} from "../chunk-7IR4F7GA.js";
|
|
28
|
+
import {
|
|
29
|
+
ImageUploadDialog
|
|
30
|
+
} from "../chunk-XXJDT6FF.js";
|
|
31
|
+
import {
|
|
32
|
+
publicMediaUrl,
|
|
22
33
|
setAdminMediaContext
|
|
23
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-2ITWLRYF.js";
|
|
35
|
+
import {
|
|
36
|
+
I18nProvider,
|
|
37
|
+
useLocale,
|
|
38
|
+
useT
|
|
39
|
+
} from "../chunk-OFHKZNZS.js";
|
|
40
|
+
import "../chunk-OPQ3SAZJ.js";
|
|
24
41
|
export {
|
|
25
42
|
ADMIN_SITE_COOKIE,
|
|
43
|
+
AdminProviders,
|
|
26
44
|
I18nProvider,
|
|
27
45
|
ImageUploadDialog,
|
|
28
46
|
MediaPicker,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
MediaPage
|
|
4
|
+
} from "../chunk-BYLCQYEQ.js";
|
|
5
|
+
import "../chunk-GDQC5X46.js";
|
|
6
|
+
import "../chunk-XXJDT6FF.js";
|
|
7
|
+
import "../chunk-2ITWLRYF.js";
|
|
8
|
+
import "../chunk-OFHKZNZS.js";
|
|
9
|
+
import "../chunk-OPQ3SAZJ.js";
|
|
10
|
+
export {
|
|
11
|
+
MediaPage
|
|
12
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
NewPostPage
|
|
4
|
+
} from "../chunk-QVUTNQZH.js";
|
|
5
|
+
import "../chunk-7IJDOT2K.js";
|
|
6
|
+
import "../chunk-TZWSXAHD.js";
|
|
7
|
+
import "../chunk-7IR4F7GA.js";
|
|
8
|
+
import "../chunk-XXJDT6FF.js";
|
|
9
|
+
import "../chunk-2ITWLRYF.js";
|
|
10
|
+
import "../chunk-OFHKZNZS.js";
|
|
11
|
+
import "../chunk-OPQ3SAZJ.js";
|
|
12
|
+
export {
|
|
13
|
+
NewPostPage
|
|
14
|
+
};
|