@acms-subir/core 0.2.0 → 0.3.1

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 (254) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/cli/common.mjs +35 -0
  3. package/cli/legacy.mjs +317 -0
  4. package/cli/main.mjs +66 -0
  5. package/cli/palette.mjs +4 -0
  6. package/cli/preview.mjs +50 -0
  7. package/cli/remote.mjs +55 -0
  8. package/cli/themes.mjs +127 -0
  9. package/cli.mjs +2 -369
  10. package/deploy/Caddyfile +8 -0
  11. package/deploy/Dockerfile +34 -0
  12. package/deploy/compose.https.yml +21 -0
  13. package/deploy/compose.yml +46 -0
  14. package/docs/live-video.md +91 -0
  15. package/docs/theme-authoring.md +103 -0
  16. package/email-runner.mjs +32 -0
  17. package/integration.mjs +189 -2
  18. package/ops-check.mjs +27 -0
  19. package/package.json +27 -5
  20. package/src/actions/index.ts +578 -49
  21. package/src/components/AiCleaner.tsx +291 -0
  22. package/src/components/AiSettings.tsx +575 -0
  23. package/src/components/CommandPalette.tsx +200 -0
  24. package/src/components/ContentList.astro +246 -0
  25. package/src/components/DesignAccess.tsx +182 -0
  26. package/src/components/DesignManager.tsx +418 -46
  27. package/src/components/EmailSettings.tsx +521 -0
  28. package/src/components/EventPage.astro +178 -0
  29. package/src/components/LegalPage.astro +27 -0
  30. package/src/components/LinksManager.tsx +119 -58
  31. package/src/components/LiveCard.astro +174 -0
  32. package/src/components/LiveManager.tsx +910 -0
  33. package/src/components/NavManager.tsx +42 -43
  34. package/src/components/ThemeScript.astro +50 -0
  35. package/src/components/ThemeSections.astro +58 -0
  36. package/src/components/ThemeSectionsEditor.tsx +513 -0
  37. package/src/components/WidgetRenderer.astro +3 -0
  38. package/src/components/booking/BookingFlow.astro +150 -0
  39. package/src/components/booking/HoursEditor.astro +63 -0
  40. package/src/components/builder/PageBuilder.tsx +10 -2
  41. package/src/components/editor/EventPanel.tsx +401 -0
  42. package/src/components/editor/FormatDock.tsx +81 -0
  43. package/src/components/editor/LivePane.tsx +122 -0
  44. package/src/components/editor/MobileToolbar.tsx +93 -0
  45. package/src/components/editor/PostEditor.tsx +605 -223
  46. package/src/components/editor/PreviewOverlay.tsx +242 -43
  47. package/src/components/editor/extensions/calendar.tsx +51 -7
  48. package/src/components/editor/extensions/events.tsx +153 -0
  49. package/src/components/editor/extensions/slash.tsx +7 -1
  50. package/src/components/site/Pager.astro +24 -0
  51. package/src/components/site/PostCard.astro +64 -0
  52. package/src/components/site/TagLabel.astro +11 -0
  53. package/src/layouts/Admin.astro +289 -49
  54. package/src/layouts/Site.astro +352 -59
  55. package/src/lib/agent-api.ts +13 -0
  56. package/src/lib/agent-discovery.ts +40 -0
  57. package/src/lib/agent.ts +114 -0
  58. package/src/lib/ai-config.ts +58 -0
  59. package/src/lib/ai-schema.ts +22 -0
  60. package/src/lib/ai.ts +90 -0
  61. package/src/lib/api-keys.ts +65 -0
  62. package/src/lib/api.ts +56 -0
  63. package/src/lib/auth.ts +25 -5
  64. package/src/lib/backup/content.ts +36 -0
  65. package/src/lib/backup/unzip.ts +12 -1
  66. package/src/lib/backup/zip.ts +1 -1
  67. package/src/lib/booking/agent.ts +108 -0
  68. package/src/lib/booking/availability.ts +221 -0
  69. package/src/lib/booking/emails.ts +115 -0
  70. package/src/lib/booking/forms.ts +77 -0
  71. package/src/lib/booking/service.ts +277 -0
  72. package/src/lib/booking/view.ts +54 -0
  73. package/src/lib/calendar/agent-schema.ts +48 -0
  74. package/src/lib/calendar/agent.ts +136 -0
  75. package/src/lib/calendar/api.ts +81 -0
  76. package/src/lib/calendar/cards.ts +102 -0
  77. package/src/lib/calendar/config.ts +52 -0
  78. package/src/lib/calendar/data.ts +130 -0
  79. package/src/lib/calendar/events.ts +261 -0
  80. package/src/lib/calendar/feeds.ts +43 -0
  81. package/src/lib/calendar/hooks.ts +38 -0
  82. package/src/lib/calendar/http.ts +30 -0
  83. package/src/lib/calendar/ics-write.ts +120 -0
  84. package/src/lib/calendar/ics.ts +15 -5
  85. package/src/lib/calendar/import.ts +203 -0
  86. package/src/lib/calendar/render.ts +120 -37
  87. package/src/lib/calendar/rrule.ts +149 -0
  88. package/src/lib/calendar/seo.ts +48 -0
  89. package/src/lib/design-service.ts +58 -0
  90. package/src/lib/designDiff.ts +145 -0
  91. package/src/lib/designPreview.ts +110 -12
  92. package/src/lib/documents/cleanup.ts +31 -0
  93. package/src/lib/documents/extract.ts +154 -0
  94. package/src/lib/documents/vendor.d.ts +4 -0
  95. package/src/lib/drafts.ts +60 -6
  96. package/src/lib/dynamicBlocks.ts +40 -8
  97. package/src/lib/email/accounts.ts +55 -0
  98. package/src/lib/email/config.ts +86 -0
  99. package/src/lib/email/migration.ts +24 -0
  100. package/src/lib/email/providers.ts +89 -0
  101. package/src/lib/email/service.ts +78 -0
  102. package/src/lib/email/store.ts +167 -0
  103. package/src/lib/email/templates.ts +18 -0
  104. package/src/lib/email/types.ts +32 -0
  105. package/src/lib/email/webhooks.ts +87 -0
  106. package/src/lib/email.ts +15 -0
  107. package/src/lib/env.ts +1 -1
  108. package/src/lib/legal/index.ts +25 -0
  109. package/src/lib/live/client.ts +88 -0
  110. package/src/lib/live/crypto.ts +28 -0
  111. package/src/lib/live/migration.ts +15 -0
  112. package/src/lib/live/providers.ts +152 -0
  113. package/src/lib/live/service.ts +170 -0
  114. package/src/lib/live/signals.ts +89 -0
  115. package/src/lib/live/store.ts +49 -0
  116. package/src/lib/live/types.ts +52 -0
  117. package/src/lib/live/view.ts +78 -0
  118. package/src/lib/mobile/bookings.ts +73 -0
  119. package/src/lib/mobile/calendar.ts +85 -0
  120. package/src/lib/mobile/content.ts +85 -0
  121. package/src/lib/mobile/http.ts +63 -0
  122. package/src/lib/mobile/route.ts +20 -0
  123. package/src/lib/ops.ts +8 -0
  124. package/src/lib/preview.ts +21 -1
  125. package/src/lib/repo/migrations.ts +221 -0
  126. package/src/lib/repo/sql.ts +907 -68
  127. package/src/lib/repo/types.ts +125 -5
  128. package/src/lib/scheduler.ts +27 -0
  129. package/src/lib/schema.ts +316 -1
  130. package/src/lib/search-i18n.ts +3 -0
  131. package/src/lib/socialIcons.ts +32 -0
  132. package/src/lib/tagColor.ts +11 -0
  133. package/src/lib/theme.ts +59 -40
  134. package/src/lib/themeTokens.ts +183 -0
  135. package/src/lib/umami.ts +108 -0
  136. package/src/lib/webhook-sql.ts +68 -0
  137. package/src/lib/webhooks.ts +147 -0
  138. package/src/lib/widgets.ts +1 -0
  139. package/src/middleware.ts +45 -3
  140. package/src/pages/404.astro +23 -0
  141. package/src/pages/[...slug].astro +14 -5
  142. package/src/pages/account/[action].astro +53 -0
  143. package/src/pages/admin/ai.astro +12 -0
  144. package/src/pages/admin/c/[collection]/[id].astro +4 -1
  145. package/src/pages/admin/c/[collection]/index.astro +127 -92
  146. package/src/pages/admin/calendar/bookings.astro +400 -0
  147. package/src/pages/admin/calendar/calendars.astro +265 -0
  148. package/src/pages/admin/calendar/events/[id].astro +56 -0
  149. package/src/pages/admin/calendar/events/index.astro +408 -0
  150. package/src/pages/admin/calendar/index.ts +4 -0
  151. package/src/pages/admin/calendar/integrations.astro +4 -0
  152. package/src/pages/admin/calendar/resources/[id].astro +180 -0
  153. package/src/pages/admin/calendar/resources/index.astro +106 -0
  154. package/src/pages/admin/calendar/resources/link.astro +196 -0
  155. package/src/pages/admin/design.astro +10 -3
  156. package/src/pages/admin/email.astro +13 -0
  157. package/src/pages/admin/index.astro +232 -85
  158. package/src/pages/admin/integrations.astro +237 -0
  159. package/src/pages/admin/links.astro +3 -3
  160. package/src/pages/admin/live.astro +12 -0
  161. package/src/pages/admin/login.astro +14 -10
  162. package/src/pages/admin/navigation.astro +8 -2
  163. package/src/pages/admin/pages/[id].astro +4 -1
  164. package/src/pages/admin/pages/index.astro +41 -48
  165. package/src/pages/admin/settings.astro +418 -367
  166. package/src/pages/admin/tags.astro +4 -4
  167. package/src/pages/admin/users.astro +38 -28
  168. package/src/pages/api/agent/docs.ts +33 -0
  169. package/src/pages/api/agent/index.ts +21 -0
  170. package/src/pages/api/ai/clean.ts +13 -0
  171. package/src/pages/api/ai/draft.ts +14 -0
  172. package/src/pages/api/ai/settings.ts +53 -0
  173. package/src/pages/api/ai/test.ts +12 -0
  174. package/src/pages/api/backup.ts +8 -21
  175. package/src/pages/api/calendar/availability.ts +35 -0
  176. package/src/pages/api/calendar/bookings/[id].ts +49 -0
  177. package/src/pages/api/calendar/bookings/index.ts +73 -0
  178. package/src/pages/api/calendar/calendars.ts +35 -0
  179. package/src/pages/api/calendar/docs.ts +66 -0
  180. package/src/pages/api/calendar/events/[id].ts +63 -0
  181. package/src/pages/api/calendar/events/index.ts +112 -0
  182. package/src/pages/api/calendar/resources.ts +34 -0
  183. package/src/pages/api/design/capabilities.ts +10 -0
  184. package/src/pages/api/design/index.ts +58 -0
  185. package/src/pages/api/design/keys.ts +24 -0
  186. package/src/pages/api/design/media.ts +32 -0
  187. package/src/pages/api/email/run.ts +11 -0
  188. package/src/pages/api/email/settings.ts +61 -0
  189. package/src/pages/api/email/webhooks/[provider].ts +4 -0
  190. package/src/pages/api/live/oauth/youtube/callback.ts +19 -0
  191. package/src/pages/api/live/settings.ts +45 -0
  192. package/src/pages/api/live/status.ts +7 -0
  193. package/src/pages/api/live/thumb.ts +48 -0
  194. package/src/pages/api/mcp.ts +59 -0
  195. package/src/pages/api/media/upload.ts +2 -2
  196. package/src/pages/api/ops/status.ts +39 -0
  197. package/src/pages/api/restore.ts +27 -37
  198. package/src/pages/api/search.ts +18 -4
  199. package/src/pages/api/v1/availability.ts +7 -0
  200. package/src/pages/api/v1/bookings.ts +17 -0
  201. package/src/pages/api/v1/calendars.ts +7 -0
  202. package/src/pages/api/v1/content/[id].ts +7 -0
  203. package/src/pages/api/v1/content/index.ts +7 -0
  204. package/src/pages/api/v1/events.ts +7 -0
  205. package/src/pages/api/v1/resources.ts +7 -0
  206. package/src/pages/api/webhooks/docs.ts +61 -0
  207. package/src/pages/author/[slug].astro +43 -35
  208. package/src/pages/blog/[slug].astro +137 -66
  209. package/src/pages/blog/index.astro +95 -30
  210. package/src/pages/blog/tag/[slug].astro +45 -40
  211. package/src/pages/book/[resource]/[link].astro +70 -0
  212. package/src/pages/book/[resource]/index.astro +63 -0
  213. package/src/pages/book/[resource].ics.ts +38 -0
  214. package/src/pages/book/index.astro +47 -0
  215. package/src/pages/book/manage/[token].astro +108 -0
  216. package/src/pages/calendar/[slug].ics.ts +27 -0
  217. package/src/pages/calendar/feed.ics.ts +15 -0
  218. package/src/pages/calendar/index.astro +191 -0
  219. package/src/pages/embed/book/[resource]/[link].astro +33 -0
  220. package/src/pages/embed/calendar/[slug].astro +43 -0
  221. package/src/pages/events/[slug].astro +74 -0
  222. package/src/pages/events/[slug].ics.ts +25 -0
  223. package/src/pages/index.astro +12 -3
  224. package/src/pages/legal/privacy.astro +4 -0
  225. package/src/pages/legal/terms.astro +4 -0
  226. package/src/pages/links.astro +29 -3
  227. package/src/pages/llms.txt.ts +4 -0
  228. package/src/pages/preview/[type]/[id].astro +27 -6
  229. package/src/pages/search.astro +40 -20
  230. package/src/pages/sitemap.xml.ts +16 -1
  231. package/src/styles/admin.css +924 -105
  232. package/src/styles/cards.css +92 -74
  233. package/src/styles/editor.css +492 -114
  234. package/src/styles/email-settings.css +117 -0
  235. package/src/styles/live-admin.css +126 -0
  236. package/src/styles/live.css +199 -0
  237. package/src/styles/sections.css +12 -0
  238. package/src/styles/site.css +938 -281
  239. package/src/styles/theme-editor.css +135 -0
  240. package/src/theme-sdk/contract.d.mts +619 -0
  241. package/src/theme-sdk/contract.mjs +81 -0
  242. package/src/theme-sdk/index.ts +15 -0
  243. package/src/theme-sdk/tokens.d.mts +148 -0
  244. package/src/theme-sdk/tokens.mjs +55 -0
  245. package/src/themes/editorial/templates/home.astro +4 -4
  246. package/src/themes/editorial/templates/post-minimal.astro +2 -2
  247. package/src/themes/editorial/theme.css +27 -37
  248. package/src/themes/editorial/theme.json +25 -3
  249. package/src/themes/field/theme.json +27 -9
  250. package/src/themes/ink/theme.json +30 -12
  251. package/src/themes/night/theme.css +4 -7
  252. package/src/themes/night/theme.json +28 -10
  253. package/src/virtual.d.ts +14 -0
  254. package/src/worker.ts +5 -1
@@ -1,10 +1,11 @@
1
1
  ---
2
- import { checkPassword, createSessionToken, verifyPassword, verifySessionToken, SESSION_COOKIE } from '../../lib/auth';
2
+ import { checkPassword, createSessionToken, verifyPassword, validateSession, SESSION_COOKIE } from '../../lib/auth';
3
3
  import { getRepo } from '../../lib/repo';
4
4
  import { BRAND } from '../../lib/brand';
5
+ import ThemeScript from '../../components/ThemeScript.astro';
5
6
  import '../../styles/admin.css';
6
7
 
7
- if (verifySessionToken(Astro.cookies.get(SESSION_COOKIE)?.value)) {
8
+ if ((await validateSession(Astro.cookies.get(SESSION_COOKIE)?.value, Astro.locals))) {
8
9
  return Astro.redirect('/admin');
9
10
  }
10
11
 
@@ -17,12 +18,13 @@ if (Astro.request.method === 'POST') {
17
18
  // Email empty → the env master password (bootstrap owner). Email present →
18
19
  // a user account created in /admin/users.
19
20
  let sessionUserId: string | null = null;
21
+ let sessionVersion = 0;
20
22
  if (!email && checkPassword(password)) {
21
23
  sessionUserId = null; // default token = env owner
22
24
  } else if (email) {
23
25
  const repo = await getRepo(Astro.locals);
24
26
  const auth = await repo.getUserAuth(email);
25
- if (auth && (await verifyPassword(password, auth.passHash))) sessionUserId = auth.user.id;
27
+ if (auth && (await verifyPassword(password, auth.passHash))) { sessionUserId = auth.user.id; sessionVersion = auth.sessionVersion; }
26
28
  else {
27
29
  error = 'Wrong email or password.';
28
30
  }
@@ -31,7 +33,7 @@ if (Astro.request.method === 'POST') {
31
33
  }
32
34
 
33
35
  if (!error) {
34
- Astro.cookies.set(SESSION_COOKIE, sessionUserId ? createSessionToken(sessionUserId) : createSessionToken(), {
36
+ Astro.cookies.set(SESSION_COOKIE, sessionUserId ? createSessionToken(sessionUserId, sessionVersion) : createSessionToken(), {
35
37
  httpOnly: true,
36
38
  sameSite: 'lax',
37
39
  path: '/',
@@ -47,7 +49,8 @@ if (Astro.request.method === 'POST') {
47
49
  <html lang="en">
48
50
  <head>
49
51
  <meta charset="utf-8" />
50
- <meta name="viewport" content="width=device-width, initial-scale=1" />
52
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
53
+ <ThemeScript />
51
54
  <meta name="robots" content="noindex" />
52
55
  <title>Sign in · {BRAND.name} admin</title>
53
56
  <link rel="preconnect" href="https://fonts.googleapis.com" />
@@ -58,9 +61,9 @@ if (Astro.request.method === 'POST') {
58
61
  <div class="card card-pad login-card">
59
62
  <div class="brand">
60
63
  <svg width="26" height="26" viewBox="0 0 24 24" role="img" aria-label={BRAND.name}>
61
- <rect x="1" y="3" width="22" height="4.4" rx="1.9" fill="#2a2d36" />
62
- <rect x="1" y="10" width="12.5" height="4.4" rx="1.9" fill="#ee5d20" />
63
- <rect x="1" y="17" width="17.5" height="4.4" rx="1.9" fill="#2a2d36" opacity="0.42" />
64
+ <rect x="1" y="3" width="22" height="4.4" rx="1.9" fill="currentColor" opacity="0.85" />
65
+ <rect x="1" y="10" width="12.5" height="4.4" rx="1.9" fill="var(--accent)" />
66
+ <rect x="1" y="17" width="17.5" height="4.4" rx="1.9" fill="currentColor" opacity="0.35" />
64
67
  </svg>
65
68
  <div class="brand-name">{BRAND.name}</div>
66
69
  </div>
@@ -74,9 +77,10 @@ if (Astro.request.method === 'POST') {
74
77
  <span>Password</span>
75
78
  <input type="password" name="password" autofocus autocomplete="current-password" />
76
79
  </label>
77
- <button class="btn primary" style="width:100%;justify-content:center" type="submit">Sign in</button>
80
+ <button class="btn primary block" type="submit">Sign in</button>
78
81
  </form>
79
- <p class="hint" style="margin-top:14px">No account? The owner password from <code>ADMIN_PASSWORD</code> in <code>.env</code> always works with email left empty.</p>
82
+ <p class="hint"><a href="/account/forgot">Forgot your password?</a></p>
83
+ <p class="hint login-note">No account? The owner password from <code>ADMIN_PASSWORD</code> in <code>.env</code> always works with email left empty.</p>
80
84
  </div>
81
85
  </div>
82
86
  </body>
@@ -2,9 +2,14 @@
2
2
  import Admin from '../../layouts/Admin.astro';
3
3
  import { getRepo } from '../../lib/repo';
4
4
  import NavManager from '../../components/NavManager';
5
+ import { draftNavItems, toNavItems } from '../../lib/drafts';
5
6
 
7
+ // Menus stage with the design: the editor shows the staged list when one
8
+ // exists, the live list otherwise.
6
9
  const repo = await getRepo(Astro.locals);
7
- const items = await repo.listNavItems();
10
+ const [live, settings] = await Promise.all([repo.listNavItems(), repo.getSettings()]);
11
+ const stagedList = draftNavItems(settings.draftDesign);
12
+ const items = stagedList ? toNavItems(stagedList) : live;
8
13
  ---
9
14
 
10
15
  <Admin title="Navigation" section="navigation">
@@ -12,6 +17,7 @@ const items = await repo.listNavItems();
12
17
  <h1>Navigation</h1>
13
18
  <a class="btn sm" href="/" target="_blank" rel="noopener">View site ↗</a>
14
19
  </div>
20
+ <p class="hint nm-intro">Menu changes are staged and go live with <b>Publish</b>, together with any staged design changes. Use Preview site to see them first.</p>
15
21
 
16
- <NavManager client:only="react" items={items} />
22
+ <NavManager client:only="react" items={items} staged={Boolean(stagedList)} />
17
23
  </Admin>
@@ -7,6 +7,7 @@ import { env } from '../../../lib/env';
7
7
  import { BRAND } from '../../../lib/brand';
8
8
  import { mergePageDraft } from '../../../lib/drafts';
9
9
  import { localeHomeUrl } from '../../../lib/i18n';
10
+ import ThemeScript from '../../../components/ThemeScript.astro';
10
11
  import PostEditor from '../../../components/editor/PostEditor';
11
12
  import '../../../styles/admin.css';
12
13
  import '../../../styles/editor.css';
@@ -35,7 +36,8 @@ const templates = Object.keys(resolveDesign(settings).theme.pageTemplates);
35
36
  <html lang="en">
36
37
  <head>
37
38
  <meta charset="utf-8" />
38
- <meta name="viewport" content="width=device-width, initial-scale=1" />
39
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-visual" />
40
+ <ThemeScript />
39
41
  <meta name="robots" content="noindex" />
40
42
  <title>{page.title || 'Untitled'} · {BRAND.name} editor</title>
41
43
  <link rel="preconnect" href="https://fonts.googleapis.com" />
@@ -52,6 +54,7 @@ const templates = Object.keys(resolveDesign(settings).theme.pageTemplates);
52
54
  sharePath={sharePath}
53
55
  livePath={livePath}
54
56
  templates={templates}
57
+ site={{ title: settings.siteTitle, url: settings.siteUrl }}
55
58
  />
56
59
  </body>
57
60
  </html>
@@ -1,8 +1,9 @@
1
1
  ---
2
2
  import Admin from '../../../layouts/Admin.astro';
3
3
  import Modal from '../../../components/Modal.astro';
4
+ import ContentList, { type ContentItem } from '../../../components/ContentList.astro';
4
5
  import { getRepo } from '../../../lib/repo';
5
- import { slugify } from '../../../lib/schema';
6
+ import { slugify, type Status } from '../../../lib/schema';
6
7
  import { I18N, localeHomeUrl } from '../../../lib/i18n';
7
8
 
8
9
  const repo = await getRepo(Astro.locals);
@@ -19,21 +20,44 @@ if (Astro.request.method === 'POST') {
19
20
  const page = await repo.createPage({ title, slug });
20
21
  return Astro.redirect(`/admin/pages/${page.id}`);
21
22
  }
22
- if (op === 'delete') {
23
- await repo.deletePage(String(form.get('id')));
23
+ if (op === 'bulk') {
24
+ const to = String(form.get('to'));
25
+ for (const id of form.getAll('ids').map(String).filter(Boolean)) {
26
+ if (to === 'delete') await repo.deletePage(id);
27
+ else if (to === 'published' || to === 'draft' || to === 'archived') await repo.updatePage(id, { status: to as Status });
28
+ }
24
29
  return Astro.redirect('/admin/pages');
25
30
  }
26
31
  }
27
32
 
28
- const pages = await repo.listPages();
33
+ const [pages, settings] = await Promise.all([repo.listPages(), repo.getSettings()]);
29
34
  const showNew = Astro.url.searchParams.has('new');
30
- const fmt = (ts: number) => new Date(ts).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
35
+ const items: ContentItem[] = [...pages]
36
+ .sort((a, b) => Number(b.isHome) - Number(a.isHome) || b.updatedAt - a.updatedAt)
37
+ .map((p) => {
38
+ const path = p.isHome ? localeHomeUrl(p.locale) : `/${p.slug}`;
39
+ return {
40
+ id: p.id,
41
+ href: `/admin/pages/${p.id}`,
42
+ title: p.title,
43
+ sub: path,
44
+ meta: p.isHome ? 'Home page' : '',
45
+ at: p.updatedAt,
46
+ status: p.status,
47
+ edited: Boolean(p.draft),
48
+ viewHref: p.status === 'published' ? path : undefined,
49
+ badges: I18N.enabled ? [(p.locale || I18N.defaultLocale).toUpperCase()] : [],
50
+ };
51
+ });
31
52
  ---
32
53
 
33
54
  <Admin title="Pages" section="pages">
34
55
  <div class="page-head">
35
- <h1>Pages</h1>
36
- <button class="btn primary" type="button" data-modal="new-page">New page</button>
56
+ <div>
57
+ <h1>Pages</h1>
58
+ <p class="sub">{pages.length} {pages.length === 1 ? 'page' : 'pages'} · static pages and your home page</p>
59
+ </div>
60
+ <div class="page-actions"><a class="btn" href="/admin/ai?kind=pages">Create from document</a><button class="btn primary" type="button" data-modal="new-page">New page</button></div>
37
61
  </div>
38
62
 
39
63
  <Modal id="new-page" title="New page" open={showNew}>
@@ -54,48 +78,17 @@ const fmt = (ts: number) => new Date(ts).toLocaleDateString('en-US', { month: 's
54
78
  <button class="btn ghost" type="button" data-close>Cancel</button>
55
79
  <button class="btn primary" type="submit">Create page</button>
56
80
  </div>
57
- <p class="hint" style="margin:12px 0 0">Creates a draft and opens the editor.</p>
81
+ <p class="hint modal-note">Creates a draft and opens the editor.</p>
58
82
  </form>
59
83
  </Modal>
60
84
 
61
- <div class="card">
62
- <table class="list">
63
- <thead><tr><th>Title</th><th>Status</th><th>Updated</th><th></th></tr></thead>
64
- <tbody>
65
- {pages.map((p) => (
66
- <tr>
67
- <td>
68
- <a class="t-title" href={`/admin/pages/${p.id}`}>{p.title}</a>
69
- <div class="t-sub">{p.isHome ? localeHomeUrl(p.locale) : `/${p.slug}`}</div>
70
- </td>
71
- <td>
72
- <span class={`pill ${p.status}`}>{p.status}</span>
73
- {p.draft && <span class="pill edited" title="Has pending edits — press Update in the editor to push them live">edited</span>}
74
- {p.isHome && <span class="pill home">home</span>}
75
- {I18N.enabled && <span class="pill lang">{(p.locale || I18N.defaultLocale).toUpperCase()}</span>}
76
- </td>
77
- <td class="t-sub">{fmt(p.updatedAt)}</td>
78
- <td style="text-align:right">
79
- <form class="inline-form" method="post" onsubmit="return confirm('Delete this page?')">
80
- <input type="hidden" name="op" value="delete" />
81
- <input type="hidden" name="id" value={p.id} />
82
- <button class="btn sm ghost danger" type="submit">Delete</button>
83
- </form>
84
- </td>
85
- </tr>
86
- ))}
87
- {pages.length === 0 && (
88
- <tr>
89
- <td colspan="4">
90
- <div class="empty-state">
91
- <b>No pages yet</b>
92
- <p>Your site's static pages — about, contact, landing pages — live here.</p>
93
- <button class="btn primary" type="button" data-modal="new-page">New page</button>
94
- </div>
95
- </td>
96
- </tr>
97
- )}
98
- </tbody>
99
- </table>
100
- </div>
85
+ {pages.length === 0 ? (
86
+ <div class="empty-state">
87
+ <b>No pages yet</b>
88
+ <p>Your site's static pages — about, contact, landing pages — live here.</p>
89
+ <button class="btn primary" type="button" data-modal="new-page">New page</button>
90
+ </div>
91
+ ) : (
92
+ <ContentList items={items} noun="page" timeZone={settings.timezone || undefined} groupByMonth={false} />
93
+ )}
101
94
  </Admin>