@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.
Files changed (44) hide show
  1. package/dist/api/index.d.ts +1 -1
  2. package/dist/chunk-2ITWLRYF.js +38 -0
  3. package/dist/chunk-4YEBIBFG.js +48 -0
  4. package/dist/chunk-5OIPGVGG.js +198 -0
  5. package/dist/chunk-7IJDOT2K.js +1197 -0
  6. package/dist/chunk-7IR4F7GA.js +6 -0
  7. package/dist/chunk-BFZODSUT.js +71 -0
  8. package/dist/chunk-BYLCQYEQ.js +21 -0
  9. package/dist/chunk-GDQC5X46.js +250 -0
  10. package/dist/chunk-OFHKZNZS.js +33 -0
  11. package/dist/{chunk-TJR3ALRJ.js → chunk-OPQ3SAZJ.js} +75 -66
  12. package/dist/chunk-QVUTNQZH.js +21 -0
  13. package/dist/chunk-TZWSXAHD.js +32 -0
  14. package/dist/chunk-UOU7KQLR.js +149 -0
  15. package/dist/chunk-VXEVLHGL.js +10 -0
  16. package/dist/chunk-XHWECTED.js +1125 -0
  17. package/dist/chunk-XXJDT6FF.js +335 -0
  18. package/dist/chunk-XZQRPXKN.js +41 -0
  19. package/dist/components/admin-dashboard.d.ts +10 -0
  20. package/dist/components/admin-dashboard.js +9 -0
  21. package/dist/components/edit-post-view.d.ts +9 -0
  22. package/dist/components/edit-post-view.js +14 -0
  23. package/dist/components/index.d.ts +32 -19
  24. package/dist/components/index.js +33 -15
  25. package/dist/components/login-view.d.ts +5 -0
  26. package/dist/components/login-view.js +9 -0
  27. package/dist/components/media-view.d.ts +5 -0
  28. package/dist/components/media-view.js +12 -0
  29. package/dist/components/new-post-view.d.ts +5 -0
  30. package/dist/components/new-post-view.js +14 -0
  31. package/dist/components/posts-list-view.d.ts +5 -0
  32. package/dist/components/posts-list-view.js +11 -0
  33. package/dist/components/users-list-view.d.ts +7 -0
  34. package/dist/components/users-list-view.js +9 -0
  35. package/dist/{i18n-ByHM_Bho.d.ts → i18n-DzXXcIQQ.d.ts} +110 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.js +7 -3
  38. package/dist/lib/theme-actions.d.ts +17 -0
  39. package/dist/lib/theme-actions.js +7 -0
  40. package/dist/metafile-esm.json +1 -0
  41. package/dist/pages/index.d.ts +49 -15
  42. package/dist/pages/index.js +114 -658
  43. package/package.json +10 -9
  44. package/dist/chunk-T2RSMFOI.js +0 -2074
@@ -0,0 +1,6 @@
1
+ // src/lib/admin-site-cookie.ts
2
+ var ADMIN_SITE_COOKIE = "admin-site-id";
3
+
4
+ export {
5
+ ADMIN_SITE_COOKIE
6
+ };
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+ import {
3
+ readAdminSiteIdFromCookie
4
+ } from "./chunk-TZWSXAHD.js";
5
+ import {
6
+ useT
7
+ } from "./chunk-OFHKZNZS.js";
8
+
9
+ // src/components/posts-list-view.tsx
10
+ import { useEffect, useState } from "react";
11
+ import Link from "next/link";
12
+ import { listPosts } from "ampless";
13
+ import {
14
+ Button,
15
+ Table,
16
+ TableBody,
17
+ TableCell,
18
+ TableHead,
19
+ TableHeader,
20
+ TableRow
21
+ } from "@ampless/runtime/ui";
22
+ import { jsx, jsxs } from "react/jsx-runtime";
23
+ function PostsList() {
24
+ const t = useT();
25
+ const [posts, setPosts] = useState([]);
26
+ const [loading, setLoading] = useState(true);
27
+ useEffect(() => {
28
+ const siteId = readAdminSiteIdFromCookie();
29
+ listPosts({ status: "all", siteId }).then(setPosts).finally(() => setLoading(false));
30
+ }, []);
31
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
32
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3 md:mb-8", children: [
33
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold md:text-3xl", children: t("posts.list.title") }),
34
+ /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/admin/posts/new", children: t("posts.list.newButton") }) })
35
+ ] }),
36
+ loading ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("common.loading") }) : posts.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-md border p-12 text-center", children: [
37
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("posts.list.empty") }),
38
+ /* @__PURE__ */ jsx(Button, { asChild: true, className: "mt-4", children: /* @__PURE__ */ jsx(Link, { href: "/admin/posts/new", children: t("posts.list.createFirst") }) })
39
+ ] }) : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-md border", children: /* @__PURE__ */ jsxs(Table, { children: [
40
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
41
+ /* @__PURE__ */ jsx(TableHead, { children: t("posts.list.columnTitle") }),
42
+ /* @__PURE__ */ jsx(TableHead, { children: t("posts.list.columnStatus") }),
43
+ /* @__PURE__ */ jsx(TableHead, { children: t("posts.list.columnSlug") }),
44
+ /* @__PURE__ */ jsx(TableHead, { children: t("posts.list.columnUpdated") })
45
+ ] }) }),
46
+ /* @__PURE__ */ jsx(TableBody, { children: posts.map((post) => /* @__PURE__ */ jsxs(TableRow, { children: [
47
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
48
+ Link,
49
+ {
50
+ href: `/admin/posts/${post.postId}`,
51
+ className: "font-medium hover:underline",
52
+ children: post.title
53
+ }
54
+ ) }),
55
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
56
+ "span",
57
+ {
58
+ className: post.status === "published" ? "inline-block rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-700" : "inline-block rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-700",
59
+ children: t(`common.${post.status}`)
60
+ }
61
+ ) }),
62
+ /* @__PURE__ */ jsx(TableCell, { className: "font-mono text-xs text-muted-foreground", children: post.slug }),
63
+ /* @__PURE__ */ jsx(TableCell, { className: "text-sm text-muted-foreground", children: post.publishedAt ? new Date(post.publishedAt).toLocaleDateString() : "\u2014" })
64
+ ] }, post.postId)) })
65
+ ] }) })
66
+ ] });
67
+ }
68
+
69
+ export {
70
+ PostsList
71
+ };
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import {
3
+ MediaUploader
4
+ } from "./chunk-GDQC5X46.js";
5
+ import {
6
+ useT
7
+ } from "./chunk-OFHKZNZS.js";
8
+
9
+ // src/components/media-view.tsx
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ function MediaPage() {
12
+ const t = useT();
13
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
14
+ /* @__PURE__ */ jsx("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("media.title") }),
15
+ /* @__PURE__ */ jsx(MediaUploader, {})
16
+ ] });
17
+ }
18
+
19
+ export {
20
+ MediaPage
21
+ };
@@ -0,0 +1,250 @@
1
+ 'use client';
2
+ import {
3
+ ImageUploadDialog,
4
+ getMediaProcessingDefaults
5
+ } from "./chunk-XXJDT6FF.js";
6
+ import {
7
+ publicMediaUrl
8
+ } from "./chunk-2ITWLRYF.js";
9
+ import {
10
+ useT
11
+ } from "./chunk-OFHKZNZS.js";
12
+
13
+ // src/components/media-uploader.tsx
14
+ import { useState, useEffect, useCallback, useRef } from "react";
15
+ import { uploadData, list, remove, isCancelError } from "aws-amplify/storage";
16
+ import { processImage } from "ampless/media";
17
+ import { Button, Input } from "@ampless/runtime/ui";
18
+ import { Trash2, Copy, Check, FileText, Code2 } from "lucide-react";
19
+ import { jsx, jsxs } from "react/jsx-runtime";
20
+ var IMAGE_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)$/i;
21
+ var STYLESHEET_EXT_RE = /\.css$/i;
22
+ var SCRIPT_EXT_RE = /\.m?js$/i;
23
+ function getExtension(path) {
24
+ const dot = path.lastIndexOf(".");
25
+ return dot >= 0 ? path.slice(dot + 1).toUpperCase() : "FILE";
26
+ }
27
+ function snippetFor(url, path) {
28
+ if (IMAGE_EXT_RE.test(path)) {
29
+ return `<img src="${url}" alt="" />`;
30
+ }
31
+ if (STYLESHEET_EXT_RE.test(path)) {
32
+ return `<link rel="stylesheet" href="${url}" />`;
33
+ }
34
+ if (SCRIPT_EXT_RE.test(path)) {
35
+ return `<script src="${url}"></script>`;
36
+ }
37
+ return url;
38
+ }
39
+ function sanitizeName(name) {
40
+ return name.replace(/[ -]/g, "").replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, "_").replace(/^\.+/, "_").slice(0, 200) || "upload";
41
+ }
42
+ function MediaUploader() {
43
+ const t = useT();
44
+ const [items, setItems] = useState([]);
45
+ const [queue, setQueue] = useState([]);
46
+ const [uploading, setUploading] = useState(false);
47
+ const [error, setError] = useState(null);
48
+ const [copiedPath, setCopiedPath] = useState(null);
49
+ const uploadTaskRef = useRef(null);
50
+ const cancelTokenRef = useRef({ cancelled: false });
51
+ const refresh = useCallback(async () => {
52
+ try {
53
+ const result = await list({ path: "public/media/" });
54
+ setItems(
55
+ result.items.map((item) => ({
56
+ path: item.path,
57
+ url: publicMediaUrl(item.path)
58
+ }))
59
+ );
60
+ } catch (err) {
61
+ setError(err instanceof Error ? err.message : String(err));
62
+ }
63
+ }, []);
64
+ useEffect(() => {
65
+ refresh();
66
+ }, [refresh]);
67
+ function handleFiles(e) {
68
+ const files = Array.from(e.target.files ?? []);
69
+ e.target.value = "";
70
+ if (files.length === 0) return;
71
+ setError(null);
72
+ setQueue((prev) => [...prev, ...files]);
73
+ }
74
+ async function handleDialogConfirm(file, options) {
75
+ const token = { cancelled: false };
76
+ cancelTokenRef.current = token;
77
+ setUploading(true);
78
+ setError(null);
79
+ let advance = true;
80
+ try {
81
+ const processed = await processImage(file, options);
82
+ if (token.cancelled) {
83
+ advance = false;
84
+ return;
85
+ }
86
+ const safeName = sanitizeName(processed.suggestedName);
87
+ const now = /* @__PURE__ */ new Date();
88
+ const yyyy = now.getFullYear();
89
+ const mm = String(now.getMonth() + 1).padStart(2, "0");
90
+ const path = `public/media/${yyyy}/${mm}/${Date.now()}-${safeName}`;
91
+ const task = uploadData({
92
+ path,
93
+ data: processed.blob,
94
+ options: { contentType: processed.mime }
95
+ });
96
+ uploadTaskRef.current = task;
97
+ await task.result;
98
+ await refresh();
99
+ } catch (err) {
100
+ if (isCancelError(err) || token.cancelled) {
101
+ advance = false;
102
+ } else {
103
+ setError(err instanceof Error ? err.message : String(err));
104
+ }
105
+ } finally {
106
+ uploadTaskRef.current = null;
107
+ setUploading(false);
108
+ if (advance) {
109
+ setQueue((prev) => prev.slice(1));
110
+ }
111
+ }
112
+ }
113
+ function handleDialogSkip() {
114
+ if (uploading) return;
115
+ setQueue((prev) => prev.slice(1));
116
+ }
117
+ function handleDialogCancel() {
118
+ cancelTokenRef.current.cancelled = true;
119
+ uploadTaskRef.current?.cancel();
120
+ setQueue([]);
121
+ }
122
+ async function handleDelete(path) {
123
+ if (!confirm(t("media.deleteConfirm"))) return;
124
+ try {
125
+ await remove({ path });
126
+ await refresh();
127
+ } catch (err) {
128
+ setError(err instanceof Error ? err.message : String(err));
129
+ }
130
+ }
131
+ async function handleCopy(item, mode) {
132
+ const text = mode === "url" ? item.url : snippetFor(item.url, item.path);
133
+ await navigator.clipboard.writeText(text);
134
+ const key = `${item.path}:${mode}`;
135
+ setCopiedPath(key);
136
+ setTimeout(() => setCopiedPath((p) => p === key ? null : p), 1500);
137
+ }
138
+ const currentFile = queue[0] ?? null;
139
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
140
+ /* @__PURE__ */ jsxs("div", { className: "rounded-md border p-4", children: [
141
+ /* @__PURE__ */ jsx(
142
+ Input,
143
+ {
144
+ type: "file",
145
+ multiple: true,
146
+ onChange: handleFiles,
147
+ disabled: uploading
148
+ }
149
+ ),
150
+ uploading && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.uploading") }),
151
+ !uploading && queue.length > 0 && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.queued", { count: queue.length }) })
152
+ ] }),
153
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
154
+ /* @__PURE__ */ jsx(
155
+ ImageUploadDialog,
156
+ {
157
+ file: currentFile,
158
+ remaining: queue.length,
159
+ busy: uploading,
160
+ defaults: getMediaProcessingDefaults(),
161
+ onConfirm: handleDialogConfirm,
162
+ onSkip: handleDialogSkip,
163
+ onCancel: handleDialogCancel
164
+ }
165
+ ),
166
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4", children: items.map((item) => {
167
+ const isImage = IMAGE_EXT_RE.test(item.path);
168
+ const isStylesheet = STYLESHEET_EXT_RE.test(item.path);
169
+ const isScript = SCRIPT_EXT_RE.test(item.path);
170
+ const filename = item.path.split("/").pop() ?? "";
171
+ const ext = getExtension(item.path);
172
+ const tagSnippet = snippetFor(item.url, item.path);
173
+ const tagDiffersFromUrl = tagSnippet !== item.url;
174
+ const urlCopied = copiedPath === `${item.path}:url`;
175
+ const tagCopied = copiedPath === `${item.path}:tag`;
176
+ return /* @__PURE__ */ jsxs(
177
+ "div",
178
+ {
179
+ className: "group relative overflow-hidden rounded-md border bg-[var(--card)]",
180
+ children: [
181
+ isImage ? (
182
+ // eslint-disable-next-line @next/next/no-img-element
183
+ /* @__PURE__ */ jsx(
184
+ "img",
185
+ {
186
+ src: item.url,
187
+ alt: item.path,
188
+ className: "aspect-square w-full object-cover"
189
+ }
190
+ )
191
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex aspect-square w-full flex-col items-center justify-center gap-2 bg-muted text-muted-foreground", children: [
192
+ isStylesheet || isScript ? /* @__PURE__ */ jsx(Code2, { className: "h-8 w-8" }) : /* @__PURE__ */ jsx(FileText, { className: "h-8 w-8" }),
193
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs font-semibold", children: [
194
+ ".",
195
+ ext.toLowerCase()
196
+ ] })
197
+ ] }),
198
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-2 py-1 text-xs", children: [
199
+ /* @__PURE__ */ jsx("span", { className: "truncate", title: filename, children: filename }),
200
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
201
+ /* @__PURE__ */ jsx(
202
+ Button,
203
+ {
204
+ type: "button",
205
+ variant: "ghost",
206
+ size: "icon",
207
+ className: "h-6 w-6",
208
+ onClick: () => handleCopy(item, "url"),
209
+ title: t("media.copyUrl"),
210
+ children: urlCopied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
211
+ }
212
+ ),
213
+ tagDiffersFromUrl && /* @__PURE__ */ jsx(
214
+ Button,
215
+ {
216
+ type: "button",
217
+ variant: "ghost",
218
+ size: "icon",
219
+ className: "h-6 w-6",
220
+ onClick: () => handleCopy(item, "tag"),
221
+ title: t("media.copyTag"),
222
+ children: tagCopied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Code2, { className: "h-3 w-3" })
223
+ }
224
+ ),
225
+ /* @__PURE__ */ jsx(
226
+ Button,
227
+ {
228
+ type: "button",
229
+ variant: "ghost",
230
+ size: "icon",
231
+ className: "h-6 w-6",
232
+ onClick: () => handleDelete(item.path),
233
+ title: t("media.delete"),
234
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
235
+ }
236
+ )
237
+ ] })
238
+ ] })
239
+ ]
240
+ },
241
+ item.path
242
+ );
243
+ }) }),
244
+ items.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-center text-sm text-muted-foreground", children: t("media.empty") })
245
+ ] });
246
+ }
247
+
248
+ export {
249
+ MediaUploader
250
+ };
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+ import {
3
+ translate
4
+ } from "./chunk-OPQ3SAZJ.js";
5
+
6
+ // src/components/i18n-provider.tsx
7
+ import { createContext, useContext, useMemo } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ var I18nContext = createContext(null);
10
+ function I18nProvider({ locale, dict, children }) {
11
+ const value = useMemo(() => ({ locale, dict }), [locale, dict]);
12
+ return /* @__PURE__ */ jsx(I18nContext.Provider, { value, children });
13
+ }
14
+ function useT() {
15
+ const ctx = useContext(I18nContext);
16
+ if (!ctx) {
17
+ throw new Error(
18
+ "useT() called outside <I18nProvider>. Wrap the admin layout (or root layout) with <I18nProvider locale={...} dict={...}>."
19
+ );
20
+ }
21
+ return (key, vars) => translate(ctx.dict, key, vars);
22
+ }
23
+ function useLocale() {
24
+ const ctx = useContext(I18nContext);
25
+ if (!ctx) throw new Error("useLocale() called outside <I18nProvider>.");
26
+ return ctx.locale;
27
+ }
28
+
29
+ export {
30
+ I18nProvider,
31
+ useT,
32
+ useLocale
33
+ };
@@ -34,9 +34,12 @@ var en_default = {
34
34
  posts: "Posts",
35
35
  media: "Media",
36
36
  sites: "Sites",
37
+ users: "Users",
37
38
  viewSite: "View site",
38
39
  signOut: "Sign out",
39
- site: "Site"
40
+ site: "Site",
41
+ openMenu: "Open menu",
42
+ closeMenu: "Close menu"
40
43
  },
41
44
  dashboard: {
42
45
  title: "Dashboard",
@@ -76,6 +79,20 @@ var en_default = {
76
79
  tagsPlaceholder: "comma, separated, tags",
77
80
  tagsHint: "Used to group posts on tag pages (e.g. /tag/tech).",
78
81
  status: "Status",
82
+ noLayout: "No layout",
83
+ noLayoutHint: "Serve this post as bare HTML \u2014 no theme header, footer, or root layout. The body is returned as the entire response. Best paired with format: HTML when the body contains a full <!DOCTYPE html>\u2026</html> document.",
84
+ formatStaticLabel: "Static (zip / file bundle)",
85
+ static: {
86
+ pick: "Pick a .zip or one or more files",
87
+ pickHint: "Bundle assets must reference each other with relative paths only (no leading /). The zip's contents are extracted into a directory under your post slug and served verbatim \u2014 no theme chrome.",
88
+ currentBundle: "Current bundle: {count} files (entry: {entrypoint})",
89
+ pendingBundle: "Pending upload: {count} files, {size}",
90
+ issuesTitle: "{count} validation issue(s)",
91
+ issuesHint: "Fix the listed paths and re-upload. Absolute (/foo) and protocol-relative (//cdn.example/foo) references break under the bundle's URL prefix.",
92
+ emptyBundle: "No usable files found in the selection.",
93
+ noBundle: "Pick a bundle before saving.",
94
+ previewHint: "Static bundles render as the served HTML at /<slug>/. Preview by saving and visiting the public URL."
95
+ },
79
96
  saveChanges: "Save changes",
80
97
  createPost: "Create post",
81
98
  delete: "Delete",
@@ -147,6 +164,25 @@ var en_default = {
147
164
  label: "Site"
148
165
  }
149
166
  },
167
+ users: {
168
+ list: {
169
+ title: "Users",
170
+ description: "Promote signed-up users to admin or editor. Sign-ups always start with no role and must be granted access here.",
171
+ columnEmail: "Email",
172
+ columnRole: "Role",
173
+ columnActions: "Actions",
174
+ save: "Save",
175
+ saving: "Saving...",
176
+ saved: "Saved.",
177
+ cannotEditSelf: "You cannot change your own role.",
178
+ empty: "No users yet.",
179
+ loading: "Loading users...",
180
+ error: "Failed to load users",
181
+ roleAdmin: "Admin",
182
+ roleEditor: "Editor",
183
+ roleNone: "None"
184
+ }
185
+ },
150
186
  theme: {
151
187
  title: "Theme",
152
188
  activeLabel: "Active theme",
@@ -269,9 +305,12 @@ var ja_default = {
269
305
  posts: "\u8A18\u4E8B",
270
306
  media: "\u30E1\u30C7\u30A3\u30A2",
271
307
  sites: "\u30B5\u30A4\u30C8",
308
+ users: "\u30E6\u30FC\u30B6\u30FC",
272
309
  viewSite: "\u30B5\u30A4\u30C8\u3092\u8868\u793A",
273
310
  signOut: "\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8",
274
- site: "\u30B5\u30A4\u30C8"
311
+ site: "\u30B5\u30A4\u30C8",
312
+ openMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u958B\u304F",
313
+ closeMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B"
275
314
  },
276
315
  dashboard: {
277
316
  title: "\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9",
@@ -311,6 +350,20 @@ var ja_default = {
311
350
  tagsPlaceholder: "\u30AB\u30F3\u30DE, \u533A\u5207\u308A, \u30BF\u30B0",
312
351
  tagsHint: "\u30BF\u30B0\u30DA\u30FC\u30B8\u3067\u8A18\u4E8B\u3092\u30B0\u30EB\u30FC\u30D4\u30F3\u30B0 (\u4F8B: /tag/tech)\u3002",
313
352
  status: "\u30B9\u30C6\u30FC\u30BF\u30B9",
353
+ noLayout: "\u30EC\u30A4\u30A2\u30A6\u30C8\u3092\u4F7F\u308F\u306A\u3044",
354
+ noLayoutHint: "\u30C6\u30FC\u30DE\u306E\u30D8\u30C3\u30C0\u30FC / \u30D5\u30C3\u30BF\u30FC / root layout \u3092\u4ECB\u3055\u305A\u3001\u672C\u6587\u3092 HTTP \u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u4F53\u3068\u3057\u3066\u305D\u306E\u307E\u307E\u8FD4\u3057\u307E\u3059\u3002\u672C\u6587\u306B <!DOCTYPE html>\u2026</html> \u304C\u542B\u307E\u308C\u308B\u5834\u5408\u306F\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8: HTML \u3068\u306E\u7D44\u307F\u5408\u308F\u305B\u304C\u6700\u9069\u3067\u3059\u3002",
355
+ formatStaticLabel: "Static (zip / \u30D5\u30A1\u30A4\u30EB\u4E00\u62EC)",
356
+ static: {
357
+ pick: ".zip \u307E\u305F\u306F\u8907\u6570\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u629E",
358
+ pickHint: "\u30D0\u30F3\u30C9\u30EB\u5185\u306E\u30D5\u30A1\u30A4\u30EB\u9593\u53C2\u7167\u306F\u76F8\u5BFE\u30D1\u30B9\u306E\u307F (\u5148\u982D / \u306A\u3057)\u3002zip \u306E\u4E2D\u8EAB\u306F\u8A18\u4E8B slug \u306E\u914D\u4E0B\u306B\u5C55\u958B\u3055\u308C\u3001\u30C6\u30FC\u30DE\u88C5\u98FE\u3092\u4ECB\u3055\u305A\u305D\u306E\u307E\u307E\u914D\u4FE1\u3055\u308C\u307E\u3059\u3002",
359
+ currentBundle: "\u73FE\u5728\u306E\u30D0\u30F3\u30C9\u30EB: {count} \u30D5\u30A1\u30A4\u30EB (entry: {entrypoint})",
360
+ pendingBundle: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u5F85\u3061: {count} \u30D5\u30A1\u30A4\u30EB, {size}",
361
+ issuesTitle: "{count} \u4EF6\u306E\u691C\u8A3C\u30A8\u30E9\u30FC",
362
+ issuesHint: "\u8A72\u5F53\u30D1\u30B9\u3092\u4FEE\u6B63\u3057\u3066\u518D\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u7D76\u5BFE\u30D1\u30B9 (/foo) \u3084\u30D7\u30ED\u30C8\u30B3\u30EB\u7701\u7565 (//cdn.example/foo) \u306F\u30D0\u30F3\u30C9\u30EB\u306E URL prefix \u4E0B\u3067\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002",
363
+ emptyBundle: "\u9078\u629E\u30D5\u30A1\u30A4\u30EB\u304B\u3089\u53D6\u308A\u51FA\u305B\u308B\u4E2D\u8EAB\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
364
+ noBundle: "\u4FDD\u5B58\u524D\u306B\u30D0\u30F3\u30C9\u30EB\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
365
+ previewHint: "Static \u30D0\u30F3\u30C9\u30EB\u306F /<slug>/ \u304B\u3089\u5B9F HTML \u3068\u3057\u3066\u914D\u4FE1\u3055\u308C\u307E\u3059\u3002\u4FDD\u5B58\u5F8C\u306B\u516C\u958B URL \u3092\u958B\u3044\u3066\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
366
+ },
314
367
  saveChanges: "\u5909\u66F4\u3092\u4FDD\u5B58",
315
368
  createPost: "\u8A18\u4E8B\u3092\u4F5C\u6210",
316
369
  delete: "\u524A\u9664",
@@ -382,6 +435,25 @@ var ja_default = {
382
435
  label: "\u30B5\u30A4\u30C8"
383
436
  }
384
437
  },
438
+ users: {
439
+ list: {
440
+ title: "\u30E6\u30FC\u30B6\u30FC",
441
+ description: "\u30B5\u30A4\u30F3\u30A2\u30C3\u30D7\u6E08\u307F\u30E6\u30FC\u30B6\u30FC\u3092\u7BA1\u7406\u8005\u307E\u305F\u306F\u7DE8\u96C6\u8005\u306B\u6607\u683C\u3057\u307E\u3059\u3002\u30B5\u30A4\u30F3\u30A2\u30C3\u30D7\u76F4\u5F8C\u306F\u30ED\u30FC\u30EB\u7121\u3057\u306E\u305F\u3081\u3001\u3053\u3053\u3067\u30A2\u30AF\u30BB\u30B9\u3092\u4ED8\u4E0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
442
+ columnEmail: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9",
443
+ columnRole: "\u30ED\u30FC\u30EB",
444
+ columnActions: "\u64CD\u4F5C",
445
+ save: "\u4FDD\u5B58",
446
+ saving: "\u4FDD\u5B58\u4E2D...",
447
+ saved: "\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002",
448
+ cannotEditSelf: "\u81EA\u5206\u81EA\u8EAB\u306E\u30ED\u30FC\u30EB\u306F\u5909\u66F4\u3067\u304D\u307E\u305B\u3093\u3002",
449
+ empty: "\u30E6\u30FC\u30B6\u30FC\u304C\u3044\u307E\u305B\u3093\u3002",
450
+ loading: "\u30E6\u30FC\u30B6\u30FC\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D...",
451
+ error: "\u30E6\u30FC\u30B6\u30FC\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
452
+ roleAdmin: "\u7BA1\u7406\u8005",
453
+ roleEditor: "\u7DE8\u96C6\u8005",
454
+ roleNone: "\u306A\u3057"
455
+ }
456
+ },
385
457
  theme: {
386
458
  title: "\u30C6\u30FC\u30DE",
387
459
  activeLabel: "\u30A2\u30AF\u30C6\u30A3\u30D6\u30C6\u30FC\u30DE",
@@ -505,71 +577,8 @@ function translate(dict, key, vars) {
505
577
  });
506
578
  }
507
579
 
508
- // src/lib/media.ts
509
- var state = { outputs: null, cmsConfig: null };
510
- function setAdminMediaContext(outputs, cmsConfig2) {
511
- state.outputs = outputs;
512
- state.cmsConfig = cmsConfig2;
513
- }
514
- function publicMediaUrl(input) {
515
- if (/^https?:\/\//.test(input)) return input;
516
- let path = input.replace(/^\/+/, "");
517
- if (path.startsWith("public/")) path = path.slice("public/".length);
518
- const { outputs, cmsConfig: cmsConfig2 } = state;
519
- const delivery = cmsConfig2?.media?.delivery ?? "nextjs";
520
- if (delivery !== "s3-direct") return `/api/media/${path}`;
521
- const storage = outputs?.storage;
522
- if (!storage) return `/api/media/${path}`;
523
- return `https://${storage.bucket_name}.s3.${storage.aws_region}.amazonaws.com/public/${path}`;
524
- }
525
- function createMedia(outputs, cmsConfig2) {
526
- const storage = outputs.storage;
527
- function s3DirectUrl(path) {
528
- if (!storage) return `/api/media/${path}`;
529
- return `https://${storage.bucket_name}.s3.${storage.aws_region}.amazonaws.com/public/${path}`;
530
- }
531
- function urlFor(input) {
532
- if (/^https?:\/\//.test(input)) return input;
533
- let path = input.replace(/^\/+/, "");
534
- if (path.startsWith("public/")) path = path.slice("public/".length);
535
- const delivery = cmsConfig2.media?.delivery ?? "nextjs";
536
- return delivery === "s3-direct" ? s3DirectUrl(path) : `/api/media/${path}`;
537
- }
538
- return { publicMediaUrl: urlFor };
539
- }
540
-
541
- // src/lib/admin-site-client.ts
542
- import { DEFAULT_SITE_ID, isMultiSite } from "ampless";
543
- var ADMIN_SITE_COOKIE = "admin-site-id";
544
- var cmsConfig = null;
545
- function setAdminCmsConfig(config) {
546
- cmsConfig = config;
547
- }
548
- function readAdminSiteIdFromCookie() {
549
- if (!cmsConfig) return DEFAULT_SITE_ID;
550
- if (!isMultiSite(cmsConfig)) return DEFAULT_SITE_ID;
551
- const sites = cmsConfig.sites ?? {};
552
- if (typeof document !== "undefined") {
553
- const match = document.cookie.match(
554
- new RegExp(`(?:^|;\\s*)${ADMIN_SITE_COOKIE}=([^;]+)`)
555
- );
556
- if (match) {
557
- const v = decodeURIComponent(match[1]);
558
- if (sites[v]) return v;
559
- }
560
- }
561
- const first = Object.keys(sites)[0];
562
- return first ?? DEFAULT_SITE_ID;
563
- }
564
-
565
580
  export {
566
581
  resolveLocale,
567
582
  getDictionary,
568
- translate,
569
- setAdminMediaContext,
570
- publicMediaUrl,
571
- createMedia,
572
- ADMIN_SITE_COOKIE,
573
- setAdminCmsConfig,
574
- readAdminSiteIdFromCookie
583
+ translate
575
584
  };
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import {
3
+ PostForm
4
+ } from "./chunk-7IJDOT2K.js";
5
+ import {
6
+ useT
7
+ } from "./chunk-OFHKZNZS.js";
8
+
9
+ // src/components/new-post-view.tsx
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ function NewPostPage() {
12
+ const t = useT();
13
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
14
+ /* @__PURE__ */ jsx("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.newTitle") }),
15
+ /* @__PURE__ */ jsx(PostForm, {})
16
+ ] });
17
+ }
18
+
19
+ export {
20
+ NewPostPage
21
+ };
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+ import {
3
+ ADMIN_SITE_COOKIE
4
+ } from "./chunk-7IR4F7GA.js";
5
+
6
+ // src/lib/admin-site-client.ts
7
+ import { DEFAULT_SITE_ID, isMultiSite } from "ampless";
8
+ var cmsConfig = null;
9
+ function setAdminCmsConfig(config) {
10
+ cmsConfig = config;
11
+ }
12
+ function readAdminSiteIdFromCookie() {
13
+ if (!cmsConfig) return DEFAULT_SITE_ID;
14
+ if (!isMultiSite(cmsConfig)) return DEFAULT_SITE_ID;
15
+ const sites = cmsConfig.sites ?? {};
16
+ if (typeof document !== "undefined") {
17
+ const match = document.cookie.match(
18
+ new RegExp(`(?:^|;\\s*)${ADMIN_SITE_COOKIE}=([^;]+)`)
19
+ );
20
+ if (match) {
21
+ const v = decodeURIComponent(match[1]);
22
+ if (sites[v]) return v;
23
+ }
24
+ }
25
+ const first = Object.keys(sites)[0];
26
+ return first ?? DEFAULT_SITE_ID;
27
+ }
28
+
29
+ export {
30
+ setAdminCmsConfig,
31
+ readAdminSiteIdFromCookie
32
+ };