@ampless/admin 0.2.0-alpha.10 → 0.2.0-alpha.12

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.
@@ -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,9 @@
1
+ "use client";
2
+ import {
3
+ AdminDashboard
4
+ } from "../chunk-4YEBIBFG.js";
5
+ import "../chunk-OFHKZNZS.js";
6
+ import "../chunk-OPQ3SAZJ.js";
7
+ export {
8
+ AdminDashboard
9
+ };
@@ -0,0 +1,9 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function EditPostPage({ params }: {
4
+ params: Promise<{
5
+ postId: string;
6
+ }>;
7
+ }): react_jsx_runtime.JSX.Element;
8
+
9
+ export { EditPostPage };
@@ -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
+ };
@@ -2,7 +2,6 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
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
- export { A as AdminDashboard, E as EditPostPage, L as LoginPage, M as MediaPage, N as NewPostPage, P as PostsList } from '../login-view-BKrSZLJu.js';
6
5
  import { ProcessOptions } from 'ampless/media';
7
6
  export { invalidateSiteSettingsCache } from '../lib/theme-actions.js';
8
7
 
@@ -1,53 +1,51 @@
1
1
  'use client';
2
2
  import {
3
- AdminDashboard,
4
3
  AdminProviders,
5
- EditPostPage,
6
- ImageUploadDialog,
7
- LoginPage,
8
- MediaPage,
9
- MediaPicker,
10
- MediaUploader,
11
- NewPostPage,
12
- PostForm,
13
- PostsList,
14
4
  Sidebar,
15
5
  SiteSelector,
16
6
  SiteSettingsForm,
17
- ThemeSettingsForm,
18
- readAdminSiteIdFromCookie,
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,
19
18
  sanitizeName,
20
- setAdminCmsConfig,
21
19
  uploadProcessedImage
22
- } from "../chunk-PAL62MXF.js";
20
+ } from "../chunk-7IJDOT2K.js";
21
+ import {
22
+ readAdminSiteIdFromCookie,
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";
23
31
  import {
24
- ADMIN_SITE_COOKIE,
25
32
  publicMediaUrl,
26
33
  setAdminMediaContext
27
- } from "../chunk-QMVFRT62.js";
34
+ } from "../chunk-2ITWLRYF.js";
28
35
  import {
29
36
  I18nProvider,
30
37
  useLocale,
31
38
  useT
32
39
  } from "../chunk-OFHKZNZS.js";
33
40
  import "../chunk-OPQ3SAZJ.js";
34
- import {
35
- invalidateSiteSettingsCache
36
- } from "../chunk-VXEVLHGL.js";
37
41
  export {
38
42
  ADMIN_SITE_COOKIE,
39
- AdminDashboard,
40
43
  AdminProviders,
41
- EditPostPage,
42
44
  I18nProvider,
43
45
  ImageUploadDialog,
44
- LoginPage,
45
- MediaPage,
46
46
  MediaPicker,
47
47
  MediaUploader,
48
- NewPostPage,
49
48
  PostForm,
50
- PostsList,
51
49
  Sidebar,
52
50
  SiteSelector,
53
51
  SiteSettingsForm,
@@ -0,0 +1,5 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function LoginPage(): react_jsx_runtime.JSX.Element;
4
+
5
+ export { LoginPage };
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import {
3
+ LoginPage
4
+ } from "../chunk-5OIPGVGG.js";
5
+ import "../chunk-OFHKZNZS.js";
6
+ import "../chunk-OPQ3SAZJ.js";
7
+ export {
8
+ LoginPage
9
+ };
@@ -0,0 +1,5 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function MediaPage(): react_jsx_runtime.JSX.Element;
4
+
5
+ export { MediaPage };
@@ -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,5 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function NewPostPage(): react_jsx_runtime.JSX.Element;
4
+
5
+ export { NewPostPage };
@@ -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
+ };
@@ -0,0 +1,5 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function PostsList(): react_jsx_runtime.JSX.Element;
4
+
5
+ export { PostsList };
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import {
3
+ PostsList
4
+ } from "../chunk-BFZODSUT.js";
5
+ import "../chunk-TZWSXAHD.js";
6
+ import "../chunk-7IR4F7GA.js";
7
+ import "../chunk-OFHKZNZS.js";
8
+ import "../chunk-OPQ3SAZJ.js";
9
+ export {
10
+ PostsList
11
+ };
package/dist/index.d.ts CHANGED
@@ -24,14 +24,19 @@ interface CreateAdminOpts {
24
24
  cmsConfig: Config;
25
25
  /**
26
26
  * Optional pre-built ampless runtime instance for cross-package
27
- * sharing. When omitted, admin doesn't build one server pages
28
- * (sites list / site edit / theme edit) still need
29
- * `loadSiteSettings` / `loadThemeConfig`, so passing an `ampless`
30
- * here lets admin reuse the same runtime your public pages already
31
- * use. If not provided, the admin's server pages won't be able to
32
- * load site settings.
27
+ * sharing. Accepts either the instance itself OR a thunk (sync or
28
+ * async). The thunk form is the recommended one for the template
29
+ * scaffold's `lib/admin.ts` it lets the consumer break the
30
+ * `lib/admin.ts lib/ampless.ts themes-registry lib/admin.ts`
31
+ * static-import cycle by lazily resolving `ampless` via `import()`
32
+ * inside the thunk body (the module isn't loaded until the first
33
+ * `loadSiteSettings` / `loadThemeConfig` call, by which time all
34
+ * other modules have finished initialising).
35
+ *
36
+ * When omitted, server pages that depend on settings / theme config
37
+ * throw at request time.
33
38
  */
34
- ampless?: Ampless;
39
+ ampless?: Ampless | (() => Ampless | Promise<Ampless>);
35
40
  /**
36
41
  * Locale for admin UI strings. Pass a string code ('en', 'ja') for a
37
42
  * built-in dictionary, or an object literal to override specific
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
- ADMIN_SITE_COOKIE,
2
+ ADMIN_SITE_COOKIE
3
+ } from "./chunk-7IR4F7GA.js";
4
+ import {
3
5
  createMedia
4
- } from "./chunk-QMVFRT62.js";
6
+ } from "./chunk-2ITWLRYF.js";
5
7
  import {
6
8
  getDictionary,
7
9
  resolveLocale,
@@ -82,15 +84,19 @@ function createAdmin(opts) {
82
84
  const amplifyServer = createAmplifyServer(outputs);
83
85
  const auth = createAuthServer(amplifyServer);
84
86
  const media = createMedia(outputs, cmsConfig);
85
- const ampless = amplessIn ?? null;
86
- function requireAmpless() {
87
- if (!ampless) {
87
+ let amplessCache = null;
88
+ async function resolveAmpless() {
89
+ if (amplessCache) return amplessCache;
90
+ if (amplessIn === void 0 || amplessIn === null) {
88
91
  throw new Error(
89
92
  "[@ampless/admin] createAdmin was called without an `ampless` runtime instance, but a method that needs one (loadSiteSettings / loadThemeConfig) was invoked. Pass `ampless` in the `createAdmin` options so admin can reuse your public-side runtime."
90
93
  );
91
94
  }
92
- return ampless;
95
+ const resolved = typeof amplessIn === "function" ? await amplessIn() : amplessIn;
96
+ amplessCache = resolved;
97
+ return resolved;
93
98
  }
99
+ const eagerAmpless = amplessIn !== void 0 && amplessIn !== null && typeof amplessIn !== "function" ? amplessIn : null;
94
100
  return {
95
101
  t: (key, vars) => translate(dict, key, vars),
96
102
  locale,
@@ -101,12 +107,12 @@ function createAdmin(opts) {
101
107
  amplifyServer,
102
108
  currentAdminSiteId: adminSite.currentAdminSiteId,
103
109
  adminSiteOptions: adminSite.adminSiteOptions,
104
- loadSiteSettings: (siteId) => requireAmpless().loadSiteSettings(siteId),
105
- loadThemeConfig: (siteId) => requireAmpless().loadThemeConfig(siteId),
110
+ loadSiteSettings: async (siteId) => (await resolveAmpless()).loadSiteSettings(siteId),
111
+ loadThemeConfig: async (siteId) => (await resolveAmpless()).loadThemeConfig(siteId),
106
112
  publicMediaUrl: media.publicMediaUrl,
107
113
  outputs,
108
114
  cmsConfig,
109
- ampless
115
+ ampless: eagerAmpless
110
116
  };
111
117
  }
112
118
  export {