@actuate-media/cms-admin 0.17.0 → 0.19.0

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 (117) hide show
  1. package/dist/__tests__/lib/page-editor-service-api.test.d.ts +2 -0
  2. package/dist/__tests__/lib/page-editor-service-api.test.d.ts.map +1 -0
  3. package/dist/__tests__/lib/page-editor-service-api.test.js +155 -0
  4. package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -0
  5. package/dist/__tests__/lib/pages-service-fetch.test.d.ts +2 -0
  6. package/dist/__tests__/lib/pages-service-fetch.test.d.ts.map +1 -0
  7. package/dist/__tests__/lib/pages-service-fetch.test.js +125 -0
  8. package/dist/__tests__/lib/pages-service-fetch.test.js.map +1 -0
  9. package/dist/__tests__/lib/post-editor-service.test.js +75 -23
  10. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
  11. package/dist/__tests__/lib/posts-service.test.js +132 -142
  12. package/dist/__tests__/lib/posts-service.test.js.map +1 -1
  13. package/dist/__tests__/views/page-section-editor.test.d.ts +2 -0
  14. package/dist/__tests__/views/page-section-editor.test.d.ts.map +1 -0
  15. package/dist/__tests__/views/page-section-editor.test.js +56 -0
  16. package/dist/__tests__/views/page-section-editor.test.js.map +1 -0
  17. package/dist/__tests__/views/pages-list-view.test.d.ts +2 -0
  18. package/dist/__tests__/views/pages-list-view.test.d.ts.map +1 -0
  19. package/dist/__tests__/views/pages-list-view.test.js +102 -0
  20. package/dist/__tests__/views/pages-list-view.test.js.map +1 -0
  21. package/dist/__tests__/views/posts-list-view.render.test.d.ts +2 -0
  22. package/dist/__tests__/views/posts-list-view.render.test.d.ts.map +1 -0
  23. package/dist/__tests__/views/posts-list-view.render.test.js +73 -0
  24. package/dist/__tests__/views/posts-list-view.render.test.js.map +1 -0
  25. package/dist/actuate-admin.css +1 -1
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +0 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/lib/page-editor-service.d.ts +1 -0
  31. package/dist/lib/page-editor-service.d.ts.map +1 -1
  32. package/dist/lib/page-editor-service.js +55 -0
  33. package/dist/lib/page-editor-service.js.map +1 -1
  34. package/dist/lib/pages-service.d.ts +26 -0
  35. package/dist/lib/pages-service.d.ts.map +1 -1
  36. package/dist/lib/pages-service.js +65 -38
  37. package/dist/lib/pages-service.js.map +1 -1
  38. package/dist/lib/post-editor-service.d.ts +6 -4
  39. package/dist/lib/post-editor-service.d.ts.map +1 -1
  40. package/dist/lib/post-editor-service.js +32 -37
  41. package/dist/lib/post-editor-service.js.map +1 -1
  42. package/dist/lib/posts-service.d.ts.map +1 -1
  43. package/dist/lib/posts-service.js +91 -160
  44. package/dist/lib/posts-service.js.map +1 -1
  45. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  46. package/dist/views/Pages/PagesListView.js +71 -46
  47. package/dist/views/Pages/PagesListView.js.map +1 -1
  48. package/dist/views/Posts/NewPostPicker.d.ts +5 -4
  49. package/dist/views/Posts/NewPostPicker.d.ts.map +1 -1
  50. package/dist/views/Posts/NewPostPicker.js +5 -4
  51. package/dist/views/Posts/NewPostPicker.js.map +1 -1
  52. package/dist/views/Posts/PostTypesView.d.ts +5 -4
  53. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  54. package/dist/views/Posts/PostTypesView.js.map +1 -1
  55. package/dist/views/Posts/PostsListView.d.ts.map +1 -1
  56. package/dist/views/Posts/PostsListView.js +8 -0
  57. package/dist/views/Posts/PostsListView.js.map +1 -1
  58. package/dist/views/page-editor/PagePreview.d.ts.map +1 -1
  59. package/dist/views/page-editor/PagePreview.js +10 -1
  60. package/dist/views/page-editor/PagePreview.js.map +1 -1
  61. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  62. package/dist/views/page-editor/PageSectionEditor.js +22 -2
  63. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  64. package/dist/views/page-editor/sections/ArticleBodySection.d.ts +6 -2
  65. package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -1
  66. package/dist/views/page-editor/sections/ArticleBodySection.js +8 -8
  67. package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
  68. package/dist/views/post-editor/PostEditorCanvas.js +7 -1
  69. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  70. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
  71. package/dist/views/post-editor/PostFieldsModal.js +10 -4
  72. package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
  73. package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
  74. package/dist/views/post-editor/PostHeader.js +4 -3
  75. package/dist/views/post-editor/PostHeader.js.map +1 -1
  76. package/dist/views/post-editor/PostPreview.d.ts.map +1 -1
  77. package/dist/views/post-editor/PostPreview.js +11 -1
  78. package/dist/views/post-editor/PostPreview.js.map +1 -1
  79. package/dist/views/post-editor/PostSectionEditor.d.ts +22 -2
  80. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  81. package/dist/views/post-editor/PostSectionEditor.js +4 -1
  82. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  83. package/package.json +2 -2
  84. package/src/__tests__/lib/page-editor-service-api.test.ts +180 -0
  85. package/src/__tests__/lib/pages-service-fetch.test.ts +160 -0
  86. package/src/__tests__/lib/post-editor-service.test.ts +78 -23
  87. package/src/__tests__/lib/posts-service.test.ts +134 -163
  88. package/src/__tests__/views/page-section-editor.test.tsx +65 -0
  89. package/src/__tests__/views/pages-list-view.test.tsx +119 -0
  90. package/src/__tests__/views/posts-list-view.render.test.tsx +84 -0
  91. package/src/index.ts +0 -2
  92. package/src/lib/page-editor-service.ts +61 -0
  93. package/src/lib/pages-service.ts +88 -35
  94. package/src/lib/post-editor-service.ts +41 -40
  95. package/src/lib/posts-service.ts +109 -180
  96. package/src/views/Pages/PagesListView.tsx +113 -82
  97. package/src/views/Posts/NewPostPicker.tsx +5 -4
  98. package/src/views/Posts/PostTypesView.tsx +5 -4
  99. package/src/views/Posts/PostsListView.tsx +9 -0
  100. package/src/views/page-editor/PagePreview.tsx +10 -1
  101. package/src/views/page-editor/PageSectionEditor.tsx +25 -1
  102. package/src/views/page-editor/sections/ArticleBodySection.tsx +12 -16
  103. package/src/views/post-editor/PostEditorCanvas.tsx +7 -1
  104. package/src/views/post-editor/PostFieldsModal.tsx +36 -21
  105. package/src/views/post-editor/PostHeader.tsx +4 -4
  106. package/src/views/post-editor/PostPreview.tsx +12 -1
  107. package/src/views/post-editor/PostSectionEditor.tsx +22 -5
  108. package/dist/views/PostEditor.d.ts +0 -6
  109. package/dist/views/PostEditor.d.ts.map +0 -1
  110. package/dist/views/PostEditor.js +0 -79
  111. package/dist/views/PostEditor.js.map +0 -1
  112. package/dist/views/Posts.d.ts +0 -5
  113. package/dist/views/Posts.d.ts.map +0 -1
  114. package/dist/views/Posts.js +0 -73
  115. package/dist/views/Posts.js.map +0 -1
  116. package/src/views/PostEditor.tsx +0 -257
  117. package/src/views/Posts.tsx +0 -375
@@ -1,25 +1,16 @@
1
1
  /**
2
- * Posts service — thin client over the existing REST endpoints,
3
- * shaped around the data the Posts admin area needs.
2
+ * Posts service — thin client over the CMS REST endpoints, shaped around the
3
+ * data the Posts admin area needs.
4
4
  *
5
- * V1 strategy: aggregate across post-typed collections client-side
6
- * (one `/collections/:slug` call per type) and join authors via
7
- * `/users`. Each view component pulls from `fetchPosts()` so when
8
- * a server-side `/api/cms/posts` endpoint lands later, only this
9
- * file changes callers consume the same `Post[]` / `PostType[]`
10
- * shapes.
5
+ * `fetchPosts` calls the server-side aggregate endpoint `GET /posts`, which
6
+ * does the cross-collection filter / sort / paginate at the DB level (one
7
+ * query + one count) and returns the post rows alongside dataset-wide type
8
+ * counts and the contributor list. This keeps the Posts list O(page) instead
9
+ * of pulling every document per type into the browser, and lets it scale past
10
+ * the old ~500-row-per-type client cap.
11
11
  *
12
- * Why not a single `/collections/all` call?
13
- * - Each collection has its own access-control check, so listing
14
- * across collections must respect per-collection `read` rules.
15
- * The existing endpoint enforces this per-call; mixing them
16
- * server-side would need a new auth boundary.
17
- * - V1 traffic is small (the seed is 15 rows); a few extra
18
- * parallel requests are well under the rate-limit budget.
19
- *
20
- * Promote to a single endpoint when:
21
- * - Any post-type collection grows past ~5k rows, OR
22
- * - Page-load latency exceeds 300ms in the admin metrics.
12
+ * Access control still lives server-side: the endpoint only returns rows from
13
+ * collections the caller's scope can read.
23
14
  */
24
15
  import { cmsApi } from './api.js';
25
16
  function isPostsCollection(c) {
@@ -54,41 +45,17 @@ function collectionToPostType(c, count = 0) {
54
45
  active: true,
55
46
  };
56
47
  }
57
- function initialsFromName(name, email) {
58
- if (name && name.trim().length > 0) {
59
- const parts = name.trim().split(/\s+/);
60
- const first = parts[0]?.[0] ?? '';
61
- const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? '') : '';
62
- return (first + last).toUpperCase() || (email[0] ?? '?').toUpperCase();
63
- }
64
- return (email[0] ?? '?').toUpperCase();
65
- }
66
- async function fetchAuthorsMap() {
67
- const res = await cmsApi('/users?pageSize=200');
68
- const list = Array.isArray(res.data)
69
- ? res.data
70
- : (res.data?.users ?? []);
71
- const map = new Map();
72
- for (const u of list) {
73
- map.set(u.id, {
74
- id: u.id,
75
- name: u.name?.trim() || u.email,
76
- initials: initialsFromName(u.name, u.email),
77
- });
78
- }
79
- return map;
80
- }
81
- function extractSeoScore(data) {
82
- if (!data)
83
- return null;
84
- const candidates = ['seoScore', 'seo_score', 'metaScore'];
85
- for (const k of candidates) {
86
- const v = data[k];
87
- if (typeof v === 'number' && Number.isFinite(v))
88
- return v;
89
- }
90
- return null;
48
+ // ─── Authors ─────────────────────────────────────────────────────────
49
+ function initialsFromName(name) {
50
+ const trimmed = name?.trim();
51
+ if (!trimmed)
52
+ return '?';
53
+ const parts = trimmed.split(/\s+/);
54
+ const first = parts[0]?.[0] ?? '';
55
+ const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? '') : '';
56
+ return (first + last).toUpperCase() || '?';
91
57
  }
58
+ /** Display date matches the old client behaviour: scheduled/published date when set, else updated. */
92
59
  function pickDisplayDate(d) {
93
60
  if (d.status === 'SCHEDULED' && d.scheduledAt)
94
61
  return d.scheduledAt;
@@ -96,122 +63,86 @@ function pickDisplayDate(d) {
96
63
  return d.publishedAt;
97
64
  return d.updatedAt;
98
65
  }
66
+ function buildPostsQueryString(query) {
67
+ const params = new URLSearchParams();
68
+ if (query.search?.trim())
69
+ params.set('search', query.search.trim());
70
+ if (query.postType && query.postType !== 'all')
71
+ params.set('postType', query.postType);
72
+ if (query.status && query.status !== 'all')
73
+ params.set('status', query.status);
74
+ if (query.author && query.author !== 'all')
75
+ params.set('author', query.author);
76
+ if (query.sortBy)
77
+ params.set('sortBy', query.sortBy);
78
+ if (query.sortDirection)
79
+ params.set('sortDirection', query.sortDirection);
80
+ params.set('page', String(Math.max(1, query.page ?? 1)));
81
+ params.set('pageSize', String(Math.max(1, query.pageSize ?? 10)));
82
+ const qs = params.toString();
83
+ return qs ? `?${qs}` : '';
84
+ }
99
85
  export async function fetchPosts(query = {}) {
100
- const cols = await fetchPostCollections();
101
- const authorsMap = await fetchAuthorsMap();
102
- // Pull every doc for each post type (cap at 500 to avoid runaway
103
- // requests; pagination + filtering still happens client-side over
104
- // this set). When a type grows past 500 docs we'll need the
105
- // server-side aggregate endpoint described in this file's header.
106
- const perTypeRaw = await Promise.all(cols.map(async (c) => {
107
- const res = await cmsApi(`/collections/${encodeURIComponent(c.slug)}?pageSize=500&sort=updatedAt&order=desc`);
108
- const docs = (res.data?.docs ?? []);
109
- return { col: c, docs };
110
- }));
111
- // Map docs → Post objects.
112
- const allPosts = [];
113
- for (const { col, docs } of perTypeRaw) {
114
- for (const d of docs) {
115
- const author = (d.createdById && authorsMap.get(d.createdById)) ||
116
- (d.updatedById && authorsMap.get(d.updatedById)) || {
117
- id: '',
118
- name: 'Unknown',
119
- initials: '??',
120
- };
121
- allPosts.push({
122
- id: d.id,
123
- title: d.title ?? d.data?.title ?? '(untitled)',
124
- slug: d.slug ?? d.data?.slug ?? '',
125
- postTypeId: col.slug,
126
- postTypeName: col.labels.singular,
127
- postTypeAccentColor: col.color ?? null,
128
- authorId: author.id,
129
- authorName: author.name,
130
- authorInitials: author.initials,
131
- status: d.status,
132
- publishDate: d.publishedAt ?? d.scheduledAt ?? null,
133
- updatedDate: pickDisplayDate(d),
134
- seoScore: extractSeoScore(d.data),
135
- createdAt: d.createdAt,
136
- updatedAt: d.updatedAt,
137
- });
138
- }
139
- }
140
- // Compute per-type counts BEFORE filtering — the tabs at the top
141
- // of the page reflect the dataset, not the active query.
142
- const countsBySlug = new Map();
143
- for (const p of allPosts) {
144
- countsBySlug.set(p.postTypeId, (countsBySlug.get(p.postTypeId) ?? 0) + 1);
145
- }
146
- const types = cols.map((c) => collectionToPostType(c, countsBySlug.get(c.slug) ?? 0));
147
- // Apply filters / search.
148
- const search = (query.search ?? '').trim().toLowerCase();
149
- const filtered = allPosts.filter((p) => {
150
- if (query.postType && query.postType !== 'all' && p.postTypeId !== query.postType)
151
- return false;
152
- if (query.status && query.status !== 'all' && p.status !== query.status)
153
- return false;
154
- if (query.author && query.author !== 'all' && p.authorId !== query.author)
155
- return false;
156
- if (search) {
157
- const haystack = `${p.title} ${p.slug} ${p.authorName} ${p.postTypeName}`.toLowerCase();
158
- if (!haystack.includes(search))
159
- return false;
160
- }
161
- return true;
162
- });
163
- // Sort.
164
- const sortBy = query.sortBy ?? 'date';
165
- const dir = query.sortDirection ?? 'desc';
166
- filtered.sort((a, b) => {
167
- const cmp = comparePostsFor(sortBy, a, b);
168
- return dir === 'asc' ? cmp : -cmp;
169
- });
170
- // Paginate.
171
86
  const page = Math.max(1, query.page ?? 1);
172
87
  const pageSize = Math.max(1, query.pageSize ?? 10);
173
- const start = (page - 1) * pageSize;
174
- const paged = filtered.slice(start, start + pageSize);
175
- // Unique authors from the full set (so the dropdown lists every
176
- // contributor, not just the currently visible page).
177
- const authorIds = new Set();
178
- const authors = [];
179
- for (const p of allPosts) {
180
- if (p.authorId && !authorIds.has(p.authorId)) {
181
- authorIds.add(p.authorId);
182
- authors.push({ id: p.authorId, name: p.authorName, initials: p.authorInitials });
183
- }
88
+ const res = await cmsApi(`/posts${buildPostsQueryString(query)}`);
89
+ if (res.error || !res.data) {
90
+ throw new Error(res.error ?? 'Failed to load posts');
184
91
  }
185
- authors.sort((a, b) => a.name.localeCompare(b.name));
92
+ const r = res.data;
93
+ const typeBySlug = new Map();
94
+ for (const t of r.types)
95
+ typeBySlug.set(t.slug, t);
96
+ const types = r.types.map((t) => ({
97
+ id: t.slug,
98
+ slug: t.slug,
99
+ name: t.plural,
100
+ singularName: t.singular,
101
+ description: t.description ?? null,
102
+ urlBase: `/${t.urlPrefix || t.slug}`,
103
+ accentColor: t.color ?? null,
104
+ icon: t.icon ?? null,
105
+ postCount: t.count,
106
+ fieldCount: t.fieldCount,
107
+ active: true,
108
+ }));
109
+ const posts = r.posts.map((d) => {
110
+ const type = typeBySlug.get(d.collection);
111
+ const authorName = d.author?.name?.trim() || 'Unknown';
112
+ return {
113
+ id: d.id,
114
+ title: d.title ?? '(untitled)',
115
+ slug: d.slug ?? '',
116
+ postTypeId: d.collection,
117
+ postTypeName: type?.singular ?? d.collection,
118
+ postTypeAccentColor: type?.color ?? null,
119
+ authorId: d.author?.id ?? '',
120
+ authorName,
121
+ authorInitials: d.author ? initialsFromName(d.author.name) : '??',
122
+ status: d.status,
123
+ publishDate: d.publishedAt ?? d.scheduledAt ?? null,
124
+ updatedDate: pickDisplayDate(d),
125
+ seoScore: d.seoScore,
126
+ createdAt: d.createdAt,
127
+ updatedAt: d.updatedAt,
128
+ };
129
+ });
130
+ const authors = r.authors
131
+ .map((a) => ({
132
+ id: a.id,
133
+ name: a.name?.trim() || 'Unknown',
134
+ initials: initialsFromName(a.name),
135
+ }))
136
+ .sort((a, b) => a.name.localeCompare(b.name));
186
137
  return {
187
- posts: paged,
188
- total: filtered.length,
189
- page,
190
- pageSize,
138
+ posts,
139
+ total: r.total,
140
+ page: r.page ?? page,
141
+ pageSize: r.pageSize ?? pageSize,
191
142
  types,
192
143
  authors,
193
144
  };
194
145
  }
195
- function comparePostsFor(by, a, b) {
196
- switch (by) {
197
- case 'title':
198
- return a.title.localeCompare(b.title);
199
- case 'type':
200
- return a.postTypeName.localeCompare(b.postTypeName);
201
- case 'author':
202
- return a.authorName.localeCompare(b.authorName);
203
- case 'status':
204
- return a.status.localeCompare(b.status);
205
- case 'seo': {
206
- const av = a.seoScore ?? -1;
207
- const bv = b.seoScore ?? -1;
208
- return av - bv;
209
- }
210
- case 'date':
211
- default:
212
- return new Date(a.updatedDate).getTime() - new Date(b.updatedDate).getTime();
213
- }
214
- }
215
146
  // ─── Bulk mutations ──────────────────────────────────────────────────
216
147
  export async function bulkUpdatePosts(ids, payload) {
217
148
  let ok = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"posts-service.js","sourceRoot":"","sources":["../../src/lib/posts-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"posts-service.js","sourceRoot":"","sources":["../../src/lib/posts-service.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PagesListView.d.ts","sourceRoot":"","sources":["../../../src/views/Pages/PagesListView.tsx"],"names":[],"mappings":"AA+BA;;;;;;;;;;GAUG;AAEH,UAAU,kBAAkB;IAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAOD,wBAAgB,aAAa,CAAC,EAAE,UAAU,EAAE,EAAE,kBAAkB,2CA6b/D;AAobD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAU/E"}
1
+ {"version":3,"file":"PagesListView.d.ts","sourceRoot":"","sources":["../../../src/views/Pages/PagesListView.tsx"],"names":[],"mappings":"AAkCA;;;;;;;;;;GAUG;AAEH,UAAU,kBAAkB;IAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAOD,wBAAgB,aAAa,CAAC,EAAE,UAAU,EAAE,EAAE,kBAAkB,2CA2e/D;AAkaD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAU/E"}
@@ -3,7 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Plus, Search, Tag as TagIcon, Trash2, X, } from 'lucide-react';
5
5
  import { toast } from 'sonner';
6
- import { bulkChangePageTags, bulkDeletePages, bulkUpdatePages, fetchPages, groupPages, } from '../../lib/pages-service.js';
6
+ import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
7
+ import { applyPagesQuery, bulkChangePageTags, bulkDeletePages, bulkUpdatePages, fetchPagesDataset, groupPages, } from '../../lib/pages-service.js';
7
8
  import { PostStatusBadge, SeoScoreIndicator } from '../../components/posts/badges.js';
8
9
  import { PageTagChip, PageTagsCell } from '../../components/pages/tag-badges.js';
9
10
  import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
@@ -18,7 +19,7 @@ export function PagesListView({ onNavigate }) {
18
19
  const [dir, setDir] = useState('asc');
19
20
  const [page, setPage] = useState(1);
20
21
  const [selected, setSelected] = useState(new Set());
21
- const [data, setData] = useState(null);
22
+ const [dataset, setDataset] = useState(null);
22
23
  const [loading, setLoading] = useState(true);
23
24
  const [error, setError] = useState(null);
24
25
  const [refreshToken, setRefreshToken] = useState(0);
@@ -37,24 +38,18 @@ export function PagesListView({ onNavigate }) {
37
38
  };
38
39
  }, [search]);
39
40
  const tagIds = useMemo(() => [...selectedTags], [selectedTags]);
41
+ // Single network read. Search / sort / group / paging are applied in
42
+ // memory below, so changing any of those never re-fetches. The dataset
43
+ // refreshes only on mount and after a mutation (refreshToken bump).
40
44
  useEffect(() => {
41
45
  let cancelled = false;
42
46
  setLoading(true);
43
47
  setError(null);
44
- fetchPages({
45
- status,
46
- tagIds,
47
- search: debouncedSearch,
48
- groupBy,
49
- sortBy,
50
- sortDirection: dir,
51
- page,
52
- pageSize: PAGE_SIZE,
53
- })
48
+ fetchPagesDataset()
54
49
  .then((res) => {
55
50
  if (cancelled)
56
51
  return;
57
- setData(res);
52
+ setDataset(res);
58
53
  setLoading(false);
59
54
  })
60
55
  .catch((err) => {
@@ -66,18 +61,28 @@ export function PagesListView({ onNavigate }) {
66
61
  return () => {
67
62
  cancelled = true;
68
63
  };
69
- }, [status, tagIds, debouncedSearch, groupBy, sortBy, dir, page, refreshToken]);
70
- // Reset paging/selection when the result set changes shape.
64
+ }, [refreshToken]);
65
+ // Reset paging/selection when the active filters change shape.
71
66
  useEffect(() => {
72
67
  setPage(1);
73
68
  setSelected(new Set());
74
69
  }, [debouncedSearch, status, tagIds, groupBy]);
75
- const pages = data?.pages ?? [];
76
- const tags = data?.tags ?? [];
77
- const total = data?.total ?? 0;
78
- const totalAll = useMemo(() => tags.reduce((sum, t) => sum + t.pageCount, 0), [tags]);
79
- const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
70
+ const allPages = useMemo(() => dataset?.pages ?? [], [dataset]);
71
+ const tags = useMemo(() => dataset?.tags ?? [], [dataset]);
72
+ const grandTotal = allPages.length;
80
73
  const grouped = groupBy !== 'none';
74
+ const filtered = useMemo(() => applyPagesQuery(allPages, {
75
+ status,
76
+ tagIds,
77
+ search: debouncedSearch,
78
+ sortBy,
79
+ sortDirection: dir,
80
+ }), [allPages, status, tagIds, debouncedSearch, sortBy, dir]);
81
+ const total = filtered.length;
82
+ const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
83
+ // Pagination only applies to the flat view; grouped views render every
84
+ // match so the buckets stay complete.
85
+ const pages = useMemo(() => (grouped ? filtered : filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)), [filtered, grouped, page]);
81
86
  const tagBySlug = useMemo(() => {
82
87
  const m = new Map();
83
88
  for (const t of tags)
@@ -94,6 +99,9 @@ export function PagesListView({ onNavigate }) {
94
99
  setDir(field === 'modified' || field === 'seo' ? 'desc' : 'asc');
95
100
  }
96
101
  }, [sortBy]);
102
+ // `aria-sort` belongs on the column header cell (`<th>`), not the button
103
+ // inside it — assistive tech reads sort state from the header.
104
+ const ariaSortFor = (field) => sortBy === field ? (dir === 'asc' ? 'ascending' : 'descending') : 'none';
97
105
  const filtersAreActive = debouncedSearch !== '' || status !== 'all' || selectedTags.size > 0;
98
106
  const clearFilters = () => {
99
107
  setSearch('');
@@ -137,48 +145,81 @@ export function PagesListView({ onNavigate }) {
137
145
  setSelected(new Set());
138
146
  setRefreshToken((t) => t + 1);
139
147
  };
148
+ // Bulk service calls always resolve a { ok, failed } tally (they never
149
+ // reject), so we promote a *total* failure to a rejection — that's what
150
+ // makes `toast.promise`'s `error` branch reachable. The `await` is
151
+ // wrapped so a rejection can't escape as an unhandled promise, and
152
+ // `afterBulk` always runs to refresh the (possibly partially-changed) set.
153
+ const allFailed = (r) => r.ok === 0 && r.failed > 0;
140
154
  const runBulkStatus = async (target) => {
141
155
  const ids = selectedPages.map((p) => p.id);
142
156
  if (ids.length === 0)
143
157
  return;
144
158
  const verb = target === 'PUBLISHED' ? 'Publishing' : 'Updating';
145
- const promise = bulkUpdatePages(ids, { status: target });
159
+ const promise = bulkUpdatePages(ids, { status: target }).then((r) => {
160
+ if (allFailed(r))
161
+ throw new Error('Bulk update failed');
162
+ return r;
163
+ });
146
164
  toast.promise(promise, {
147
165
  loading: `${verb} ${ids.length} page${ids.length === 1 ? '' : 's'}…`,
148
166
  success: (r) => `${r.ok} updated${r.failed ? `, ${r.failed} failed` : ''}`,
149
167
  error: 'Bulk update failed',
150
168
  });
151
- await promise;
169
+ try {
170
+ await promise;
171
+ }
172
+ catch {
173
+ /* surfaced via toast.promise */
174
+ }
152
175
  afterBulk();
153
176
  };
154
177
  const runBulkTag = async (slug, mode) => {
155
178
  if (selectedPages.length === 0)
156
179
  return;
157
- const promise = bulkChangePageTags(selectedPages.map((p) => ({ id: p.id, tags: p.tags, primaryTagId: p.primaryTagId })), mode === 'add' ? { add: [slug] } : { remove: [slug] });
180
+ const promise = bulkChangePageTags(selectedPages.map((p) => ({ id: p.id, tags: p.tags, primaryTagId: p.primaryTagId })), mode === 'add' ? { add: [slug] } : { remove: [slug] }).then((r) => {
181
+ if (allFailed(r))
182
+ throw new Error('Bulk tag change failed');
183
+ return r;
184
+ });
158
185
  const tagName = tagBySlug.get(slug)?.name ?? slug;
159
186
  toast.promise(promise, {
160
187
  loading: `${mode === 'add' ? 'Adding' : 'Removing'} "${tagName}"…`,
161
188
  success: (r) => `${r.ok} page${r.ok === 1 ? '' : 's'} updated${r.failed ? `, ${r.failed} failed` : ''}`,
162
189
  error: 'Bulk tag change failed',
163
190
  });
164
- await promise;
191
+ try {
192
+ await promise;
193
+ }
194
+ catch {
195
+ /* surfaced via toast.promise */
196
+ }
165
197
  afterBulk();
166
198
  };
167
199
  const runBulkDelete = async () => {
168
200
  const ids = deletablePages.map((p) => p.id);
169
201
  if (ids.length === 0)
170
202
  return;
171
- const promise = bulkDeletePages(ids);
203
+ const promise = bulkDeletePages(ids).then((r) => {
204
+ if (allFailed(r))
205
+ throw new Error('Bulk delete failed');
206
+ return r;
207
+ });
172
208
  toast.promise(promise, {
173
209
  loading: `Deleting ${ids.length} page${ids.length === 1 ? '' : 's'}…`,
174
210
  success: (r) => `${r.ok} deleted${r.failed ? `, ${r.failed} failed` : ''}`,
175
211
  error: 'Bulk delete failed',
176
212
  });
177
- await promise;
213
+ try {
214
+ await promise;
215
+ }
216
+ catch {
217
+ /* surfaced via toast.promise */
218
+ }
178
219
  afterBulk();
179
220
  };
180
221
  // ─── Render ────────────────────────────────────────────────────────
181
- return (_jsxs("div", { className: "flex h-full flex-col gap-4 p-4 sm:p-6", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("h1", { className: "text-foreground text-xl font-medium tracking-tight sm:text-2xl", children: "Pages" }), _jsxs("p", { className: "text-muted-foreground text-sm", children: [totalAll, " page", totalAll === 1 ? '' : 's', " \u00B7 organized by tags"] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative max-w-sm flex-1", children: [_jsx(Search, { className: "text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2", "aria-hidden": true }), _jsx("input", { type: "search", value: search, onChange: (e) => setSearch(e.target.value), "aria-label": "Search pages", placeholder: "Search pages\u2026", className: "border-border bg-card text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-primary/30 w-full rounded-lg border py-2 pr-3 pl-9 text-sm shadow-sm focus:ring-2 focus:outline-none" })] }), _jsx(FilterSelect, { label: "Group by", value: groupBy, onChange: (v) => setGroupBy(v), options: [
222
+ return (_jsxs("div", { className: "flex h-full flex-col gap-4 p-4 sm:p-6", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("h1", { className: "text-foreground text-xl font-medium tracking-tight sm:text-2xl", children: "Pages" }), _jsxs("p", { className: "text-muted-foreground text-sm", children: [grandTotal, " page", grandTotal === 1 ? '' : 's', " \u00B7 organized by tags"] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative max-w-sm flex-1", children: [_jsx(Search, { className: "text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2", "aria-hidden": true }), _jsx("input", { type: "search", value: search, onChange: (e) => setSearch(e.target.value), "aria-label": "Search pages", placeholder: "Search pages\u2026", className: "border-border bg-card text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-primary/30 w-full rounded-lg border py-2 pr-3 pl-9 text-sm shadow-sm focus:ring-2 focus:outline-none" })] }), _jsx(FilterSelect, { label: "Group by", value: groupBy, onChange: (v) => setGroupBy(v), options: [
182
223
  { value: 'none', label: 'None' },
183
224
  { value: 'tag', label: 'Tag' },
184
225
  { value: 'status', label: 'Status' },
@@ -191,7 +232,7 @@ export function PagesListView({ onNavigate }) {
191
232
  { value: 'PUBLISHED', label: 'Published' },
192
233
  { value: 'DRAFT', label: 'Draft' },
193
234
  { value: 'SCHEDULED', label: 'Scheduled' },
194
- ] }), _jsxs("span", { className: "text-muted-foreground ml-auto text-sm tabular-nums", children: [total, " of ", totalAll] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground text-[10px] font-semibold tracking-wider uppercase", children: "Tags" }), tags.map((t) => (_jsx(PageTagChip, { name: t.name, color: t.color, count: t.pageCount, selected: selectedTags.has(t.slug), onToggle: () => toggleTag(t.slug) }, t.slug))), selectedTags.size > 0 && (_jsxs("button", { type: "button", onClick: () => setSelectedTags(new Set()), className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs", children: [_jsx(X, { className: "h-3 w-3", "aria-hidden": true }), "Clear tags"] })), _jsxs("button", { type: "button", onClick: () => onNavigate('/pages/tags'), className: "text-primary ml-auto inline-flex items-center gap-1 text-xs font-medium hover:underline", children: [_jsx(TagIcon, { className: "h-3 w-3", "aria-hidden": true }), "Manage tags"] })] }), selected.size > 0 && (_jsx(BulkActionBar, { count: selected.size, tags: tags, blockedHomepage: blockedHomepage, onClear: () => setSelected(new Set()), onPublish: () => runBulkStatus('PUBLISHED'), onDraft: () => runBulkStatus('DRAFT'), onAddTag: (slug) => runBulkTag(slug, 'add'), onRemoveTag: (slug) => runBulkTag(slug, 'remove'), onDelete: () => setPendingDelete(true), deletableCount: deletablePages.length })), _jsxs("div", { className: "bg-card border-border overflow-hidden rounded-xl border shadow-sm", children: [_jsxs("table", { className: "w-full table-fixed", role: "table", "aria-label": "Pages", children: [_jsxs("colgroup", { children: [_jsx("col", { style: { width: '40px' } }), _jsx("col", {}), _jsx("col", { style: { width: '200px' } }), _jsx("col", { style: { width: '170px' } }), _jsx("col", { style: { width: '120px' } }), _jsx("col", { style: { width: '130px' } }), _jsx("col", { style: { width: '80px' } })] }), _jsx("thead", { className: "border-border bg-muted/40 border-b", children: _jsxs("tr", { className: "text-muted-foreground text-xs", children: [_jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx("input", { type: "checkbox", "aria-label": "Select all pages on this page", checked: allOnPageSelected, onChange: (e) => toggleAllOnPage(e.target.checked), className: "border-border accent-primary rounded" }) }), _jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Title", field: "title", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx("span", { className: "text-xs font-medium tracking-wider uppercase", children: "Tags" }) }), _jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Author", field: "author", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Status", field: "status", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Last Modified", field: "modified", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "SEO", field: "seo", current: sortBy, dir: dir, onSort: handleSort }) })] }) }), _jsxs("tbody", { children: [loading && pages.length === 0 && _jsx(LoadingRows, {}), !loading && error && (_jsx("tr", { children: _jsxs("td", { colSpan: 7, className: "px-3 py-12 text-center", children: [_jsx("div", { className: "text-foreground mb-2 text-sm font-medium", children: error }), _jsx("button", { onClick: () => setRefreshToken((t) => t + 1), className: "text-primary text-sm hover:underline", children: "Try again" })] }) })), !loading && !error && pages.length === 0 && (_jsx(EmptyRow, { filtersActive: filtersAreActive, onClear: clearFilters, onNewPage: () => onNavigate('/pages/new') })), !loading &&
235
+ ] }), _jsxs("span", { className: "text-muted-foreground ml-auto text-sm tabular-nums", children: [total, " of ", grandTotal] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground text-[10px] font-semibold tracking-wider uppercase", children: "Tags" }), tags.map((t) => (_jsx(PageTagChip, { name: t.name, color: t.color, count: t.pageCount, selected: selectedTags.has(t.slug), onToggle: () => toggleTag(t.slug) }, t.slug))), selectedTags.size > 0 && (_jsxs("button", { type: "button", onClick: () => setSelectedTags(new Set()), className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs", children: [_jsx(X, { className: "h-3 w-3", "aria-hidden": true }), "Clear tags"] })), _jsxs("button", { type: "button", onClick: () => onNavigate('/pages/tags'), className: "text-primary ml-auto inline-flex items-center gap-1 text-xs font-medium hover:underline", children: [_jsx(TagIcon, { className: "h-3 w-3", "aria-hidden": true }), "Manage tags"] })] }), selected.size > 0 && (_jsx(BulkActionBar, { count: selected.size, tags: tags, blockedHomepage: blockedHomepage, onClear: () => setSelected(new Set()), onPublish: () => runBulkStatus('PUBLISHED'), onDraft: () => runBulkStatus('DRAFT'), onAddTag: (slug) => runBulkTag(slug, 'add'), onRemoveTag: (slug) => runBulkTag(slug, 'remove'), onDelete: () => setPendingDelete(true), deletableCount: deletablePages.length })), _jsxs("div", { className: "bg-card border-border overflow-hidden rounded-xl border shadow-sm", children: [_jsxs("table", { className: "w-full table-fixed", role: "table", "aria-label": "Pages", children: [_jsxs("colgroup", { children: [_jsx("col", { style: { width: '40px' } }), _jsx("col", {}), _jsx("col", { style: { width: '200px' } }), _jsx("col", { style: { width: '170px' } }), _jsx("col", { style: { width: '120px' } }), _jsx("col", { style: { width: '130px' } }), _jsx("col", { style: { width: '80px' } })] }), _jsx("thead", { className: "border-border bg-muted/40 border-b", children: _jsxs("tr", { className: "text-muted-foreground text-xs", children: [_jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx("input", { type: "checkbox", "aria-label": "Select all pages on this page", checked: allOnPageSelected, onChange: (e) => toggleAllOnPage(e.target.checked), className: "border-border accent-primary rounded" }) }), _jsx("th", { scope: "col", "aria-sort": ariaSortFor('title'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Title", field: "title", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", className: "px-3 py-2.5 text-left", children: _jsx("span", { className: "text-xs font-medium tracking-wider uppercase", children: "Tags" }) }), _jsx("th", { scope: "col", "aria-sort": ariaSortFor('author'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Author", field: "author", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", "aria-sort": ariaSortFor('status'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Status", field: "status", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", "aria-sort": ariaSortFor('modified'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "Last Modified", field: "modified", current: sortBy, dir: dir, onSort: handleSort }) }), _jsx("th", { scope: "col", "aria-sort": ariaSortFor('seo'), className: "px-3 py-2.5 text-left", children: _jsx(SortHeader, { label: "SEO", field: "seo", current: sortBy, dir: dir, onSort: handleSort }) })] }) }), _jsxs("tbody", { children: [loading && pages.length === 0 && _jsx(LoadingRows, {}), !loading && error && (_jsx("tr", { children: _jsxs("td", { colSpan: 7, className: "px-3 py-12 text-center", children: [_jsx("div", { className: "text-foreground mb-2 text-sm font-medium", children: error }), _jsx("button", { onClick: () => setRefreshToken((t) => t + 1), className: "text-primary text-sm hover:underline", children: "Try again" })] }) })), !loading && !error && pages.length === 0 && (_jsx(EmptyRow, { filtersActive: filtersAreActive, onClear: clearFilters, onNewPage: () => onNavigate('/pages/new') })), !loading &&
195
236
  !error &&
196
237
  displayGroups.map((group) => (_jsx(GroupBlock, { showHeader: grouped, label: group.label, color: group.color, count: group.pages.length, children: group.pages.map((p) => (_jsx(PageRow, { page: p, selected: selected.has(p.id), resolveTag: (slug) => tagBySlug.get(slug), onSelect: () => toggleOne(p.id), onOpen: () => onNavigate(`/pages/${p.id}/edit`) }, p.id))) }, group.key)))] })] }), !grouped && (_jsx(PaginationFooter, { page: page, totalPages: totalPages, total: total, pageSize: PAGE_SIZE, onPage: setPage }))] }), _jsx(ConfirmDialog, { open: pendingDelete, title: `Delete ${deletablePages.length} page${deletablePages.length === 1 ? '' : 's'}?`, description: blockedHomepage
197
238
  ? 'The homepage cannot be deleted and will be skipped. The remaining pages move to trash and can be restored for 30 days.'
@@ -202,8 +243,7 @@ export function PagesListView({ onNavigate }) {
202
243
  // ─── Sub-components ──────────────────────────────────────────────────
203
244
  function SortHeader({ label, field, current, dir, onSort, }) {
204
245
  const active = current === field;
205
- const ariaSort = active ? (dir === 'asc' ? 'ascending' : 'descending') : 'none';
206
- return (_jsxs("button", { type: "button", onClick: () => onSort(field), "aria-sort": ariaSort, className: `hover:text-foreground inline-flex items-center gap-1 text-xs font-medium tracking-wider uppercase transition-colors ${active ? 'text-foreground' : 'text-muted-foreground'}`, children: [_jsx("span", { children: label }), active &&
246
+ return (_jsxs("button", { type: "button", onClick: () => onSort(field), className: `hover:text-foreground inline-flex items-center gap-1 text-xs font-medium tracking-wider uppercase transition-colors ${active ? 'text-foreground' : 'text-muted-foreground'}`, children: [_jsx("span", { children: label }), active &&
207
247
  (dir === 'asc' ? (_jsx(ChevronUp, { className: "h-3 w-3", "aria-hidden": true })) : (_jsx(ChevronDown, { className: "h-3 w-3", "aria-hidden": true })))] }));
208
248
  }
209
249
  function FilterSelect({ label, value, onChange, options, }) {
@@ -227,22 +267,7 @@ function BulkActionBar({ count, tags, blockedHomepage, deletableCount, onClear,
227
267
  return (_jsxs("div", { className: "border-primary/30 bg-primary/5 flex flex-wrap items-center gap-2 rounded-lg border px-4 py-2", children: [_jsxs("span", { className: "text-foreground text-sm font-medium", children: [count, " selected"] }), _jsx("div", { className: "bg-border h-4 w-px", "aria-hidden": true }), _jsx("button", { type: "button", onClick: onPublish, className: "text-foreground hover:bg-accent rounded-md px-2 py-1 text-sm transition-colors", children: "Publish" }), _jsx("button", { type: "button", onClick: onDraft, className: "text-foreground hover:bg-accent rounded-md px-2 py-1 text-sm transition-colors", children: "Move to draft" }), _jsx(TagMenu, { label: "Add tag", tags: tags, onPick: onAddTag }), _jsx(TagMenu, { label: "Remove tag", tags: tags, onPick: onRemoveTag }), _jsxs("button", { type: "button", onClick: onDelete, disabled: deletableCount === 0, title: blockedHomepage ? 'The homepage is excluded from deletion' : undefined, className: "text-destructive hover:bg-destructive/10 inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40", children: [_jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": true }), "Delete"] }), _jsxs("button", { type: "button", onClick: onClear, className: "text-muted-foreground hover:text-foreground ml-auto inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm transition-colors", children: [_jsx(X, { className: "h-3.5 w-3.5", "aria-hidden": true }), "Clear"] })] }));
228
268
  }
229
269
  function TagMenu({ label, tags, onPick, }) {
230
- const [open, setOpen] = useState(false);
231
- const ref = useRef(null);
232
- useEffect(() => {
233
- if (!open)
234
- return;
235
- const onDoc = (e) => {
236
- if (ref.current && !ref.current.contains(e.target))
237
- setOpen(false);
238
- };
239
- document.addEventListener('mousedown', onDoc);
240
- return () => document.removeEventListener('mousedown', onDoc);
241
- }, [open]);
242
- return (_jsxs("div", { className: "relative", ref: ref, children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), "aria-haspopup": "menu", "aria-expanded": open, className: "text-foreground hover:bg-accent inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm transition-colors", children: [label, _jsx(ChevronDown, { className: "h-3 w-3", "aria-hidden": true })] }), open && (_jsxs("div", { role: "menu", className: "border-border bg-popover absolute z-20 mt-1 max-h-64 w-44 overflow-auto rounded-lg border p-1 shadow-lg", children: [tags.length === 0 && (_jsx("div", { className: "text-muted-foreground px-2 py-1.5 text-xs", children: "No tags yet" })), tags.map((t) => (_jsxs("button", { type: "button", role: "menuitem", onClick: () => {
243
- onPick(t.slug);
244
- setOpen(false);
245
- }, className: "text-foreground hover:bg-accent flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm", children: [_jsx("span", { "aria-hidden": true, className: `h-2 w-2 rounded-full ${dotFor(t.color ?? 'gray')}` }), t.name] }, t.slug)))] }))] }));
270
+ return (_jsxs(DropdownMenu.Root, { children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: "text-foreground hover:bg-accent inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm transition-colors", children: [label, _jsx(ChevronDown, { className: "h-3 w-3", "aria-hidden": true })] }) }), _jsx(DropdownMenu.Portal, { children: _jsx(DropdownMenu.Content, { align: "start", sideOffset: 4, className: "border-border bg-popover z-50 max-h-64 w-44 overflow-auto rounded-lg border p-1 shadow-md", children: tags.length === 0 ? (_jsx("div", { className: "text-muted-foreground px-2 py-1.5 text-xs", children: "No tags yet" })) : (tags.map((t) => (_jsxs(DropdownMenu.Item, { onSelect: () => onPick(t.slug), className: "text-foreground focus:bg-accent flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none", children: [_jsx("span", { "aria-hidden": true, className: `h-2 w-2 rounded-full ${dotFor(t.color ?? 'gray')}` }), t.name] }, t.slug)))) }) })] }));
246
271
  }
247
272
  function PageRow({ page, selected, resolveTag, onSelect, onOpen, }) {
248
273
  return (_jsxs("tr", { className: `border-border hover:bg-muted/30 group border-b transition-colors last:border-b-0 ${selected ? 'bg-primary/5' : ''}`, children: [_jsx("td", { className: "px-3 py-3", onClick: (e) => e.stopPropagation(), children: _jsx("input", { type: "checkbox", "aria-label": `Select "${page.title}"`, checked: selected, onChange: onSelect, className: "border-border accent-primary rounded" }) }), _jsx("td", { className: "px-3 py-3", children: _jsxs("button", { type: "button", onClick: onOpen, className: "flex w-full items-center gap-3 text-left", children: [_jsx("span", { "aria-hidden": true, className: "bg-muted block h-9 w-9 shrink-0 rounded-md" }), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-foreground group-hover:text-primary block truncate text-sm font-medium", children: page.title }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: page.path })] })] }) }), _jsx("td", { className: "px-3 py-3", children: _jsx(PageTagsCell, { tagSlugs: page.tags, primaryTagId: page.primaryTagId, resolve: resolveTag }) }), _jsx("td", { className: "px-3 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": true, className: "bg-muted text-muted-foreground inline-flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-semibold", children: page.authorInitials }), _jsx("span", { className: "text-foreground truncate text-sm", children: page.authorName })] }) }), _jsx("td", { className: "px-3 py-3", children: _jsx(PostStatusBadge, { status: page.status }) }), _jsx("td", { className: "text-muted-foreground px-3 py-3 text-sm tabular-nums", children: formatDateShort(page.lastModified) }), _jsx("td", { className: "px-3 py-3", children: _jsx(SeoScoreIndicator, { score: page.seoScore }) })] }));