@actuate-media/cms-admin 0.18.0 → 0.20.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.
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +9 -5
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/lib/post-editor-service.test.js +75 -23
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/posts-service.test.js +132 -142
- package/dist/__tests__/lib/posts-service.test.js.map +1 -1
- package/dist/__tests__/views/posts-list-view.render.test.d.ts +2 -0
- package/dist/__tests__/views/posts-list-view.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/posts-list-view.render.test.js +73 -0
- package/dist/__tests__/views/posts-list-view.render.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/seo/Drawer.d.ts +11 -0
- package/dist/components/seo/Drawer.d.ts.map +1 -0
- package/dist/components/seo/Drawer.js +13 -0
- package/dist/components/seo/Drawer.js.map +1 -0
- package/dist/components/seo/RedirectEditorDrawer.d.ts +9 -0
- package/dist/components/seo/RedirectEditorDrawer.d.ts.map +1 -0
- package/dist/components/seo/RedirectEditorDrawer.js +94 -0
- package/dist/components/seo/RedirectEditorDrawer.js.map +1 -0
- package/dist/components/seo/SeoCopilotDrawer.d.ts +10 -0
- package/dist/components/seo/SeoCopilotDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoCopilotDrawer.js +34 -0
- package/dist/components/seo/SeoCopilotDrawer.js.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts +8 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.js +111 -0
- package/dist/components/seo/SeoEditorDrawer.js.map +1 -0
- package/dist/components/seo/SeoIssueFixDrawer.d.ts +8 -0
- package/dist/components/seo/SeoIssueFixDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoIssueFixDrawer.js +111 -0
- package/dist/components/seo/SeoIssueFixDrawer.js.map +1 -0
- package/dist/components/seo/primitives.d.ts +53 -0
- package/dist/components/seo/primitives.d.ts.map +1 -0
- package/dist/components/seo/primitives.js +100 -0
- package/dist/components/seo/primitives.js.map +1 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +3 -0
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/post-editor-service.d.ts +6 -4
- package/dist/lib/post-editor-service.d.ts.map +1 -1
- package/dist/lib/post-editor-service.js +32 -37
- package/dist/lib/post-editor-service.js.map +1 -1
- package/dist/lib/posts-service.d.ts.map +1 -1
- package/dist/lib/posts-service.js +91 -160
- package/dist/lib/posts-service.js.map +1 -1
- package/dist/lib/seo-service.d.ts +322 -0
- package/dist/lib/seo-service.d.ts.map +1 -0
- package/dist/lib/seo-service.js +295 -0
- package/dist/lib/seo-service.js.map +1 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +4 -1
- package/dist/router/index.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +36 -25
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/Posts/NewPostPicker.d.ts +5 -4
- package/dist/views/Posts/NewPostPicker.d.ts.map +1 -1
- package/dist/views/Posts/NewPostPicker.js +5 -4
- package/dist/views/Posts/NewPostPicker.js.map +1 -1
- package/dist/views/Posts/NewPostTypeInstructions.js +2 -2
- package/dist/views/Posts/PostTypesView.d.ts +5 -4
- package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
- package/dist/views/Posts/PostTypesView.js.map +1 -1
- package/dist/views/Posts/PostsListView.d.ts.map +1 -1
- package/dist/views/Posts/PostsListView.js +8 -0
- package/dist/views/Posts/PostsListView.js.map +1 -1
- package/dist/views/Posts/post-type-presets.d.ts +5 -0
- package/dist/views/Posts/post-type-presets.d.ts.map +1 -1
- package/dist/views/Posts/post-type-presets.js +10 -64
- package/dist/views/Posts/post-type-presets.js.map +1 -1
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +89 -132
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts +6 -2
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.js +8 -8
- package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +7 -1
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.js +10 -4
- package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
- package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
- package/dist/views/post-editor/PostHeader.js +4 -3
- package/dist/views/post-editor/PostHeader.js.map +1 -1
- package/dist/views/post-editor/PostPreview.d.ts.map +1 -1
- package/dist/views/post-editor/PostPreview.js +11 -1
- package/dist/views/post-editor/PostPreview.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts +22 -2
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +4 -1
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/seo/AuditTab.d.ts +6 -0
- package/dist/views/seo/AuditTab.d.ts.map +1 -0
- package/dist/views/seo/AuditTab.js +57 -0
- package/dist/views/seo/AuditTab.js.map +1 -0
- package/dist/views/seo/ContentTab.d.ts +5 -0
- package/dist/views/seo/ContentTab.d.ts.map +1 -0
- package/dist/views/seo/ContentTab.js +27 -0
- package/dist/views/seo/ContentTab.js.map +1 -0
- package/dist/views/seo/OverviewTab.d.ts +8 -0
- package/dist/views/seo/OverviewTab.d.ts.map +1 -0
- package/dist/views/seo/OverviewTab.js +35 -0
- package/dist/views/seo/OverviewTab.js.map +1 -0
- package/dist/views/seo/RedirectsTab.d.ts +2 -0
- package/dist/views/seo/RedirectsTab.d.ts.map +1 -0
- package/dist/views/seo/RedirectsTab.js +124 -0
- package/dist/views/seo/RedirectsTab.js.map +1 -0
- package/dist/views/seo/TechnicalTab.d.ts +2 -0
- package/dist/views/seo/TechnicalTab.d.ts.map +1 -0
- package/dist/views/seo/TechnicalTab.js +102 -0
- package/dist/views/seo/TechnicalTab.js.map +1 -0
- package/dist/views/seo/useSeoResource.d.ts +8 -0
- package/dist/views/seo/useSeoResource.d.ts.map +1 -0
- package/dist/views/seo/useSeoResource.js +42 -0
- package/dist/views/seo/useSeoResource.js.map +1 -0
- package/package.json +2 -2
- package/src/AdminRoot.tsx +9 -5
- package/src/__tests__/lib/post-editor-service.test.ts +78 -23
- package/src/__tests__/lib/posts-service.test.ts +134 -163
- package/src/__tests__/views/posts-list-view.render.test.tsx +84 -0
- package/src/components/seo/Drawer.tsx +61 -0
- package/src/components/seo/RedirectEditorDrawer.tsx +235 -0
- package/src/components/seo/SeoCopilotDrawer.tsx +76 -0
- package/src/components/seo/SeoEditorDrawer.tsx +347 -0
- package/src/components/seo/SeoIssueFixDrawer.tsx +244 -0
- package/src/components/seo/primitives.tsx +237 -0
- package/src/index.ts +0 -2
- package/src/layout/Sidebar.tsx +3 -0
- package/src/lib/post-editor-service.ts +41 -40
- package/src/lib/posts-service.ts +109 -180
- package/src/lib/seo-service.ts +623 -0
- package/src/router/index.ts +4 -1
- package/src/views/MediaBrowser.tsx +89 -67
- package/src/views/Posts/NewPostPicker.tsx +5 -4
- package/src/views/Posts/NewPostTypeInstructions.tsx +2 -2
- package/src/views/Posts/PostTypesView.tsx +5 -4
- package/src/views/Posts/PostsListView.tsx +9 -0
- package/src/views/Posts/post-type-presets.ts +15 -64
- package/src/views/SEO.tsx +186 -863
- package/src/views/page-editor/sections/ArticleBodySection.tsx +12 -16
- package/src/views/post-editor/PostEditorCanvas.tsx +7 -1
- package/src/views/post-editor/PostFieldsModal.tsx +36 -21
- package/src/views/post-editor/PostHeader.tsx +4 -4
- package/src/views/post-editor/PostPreview.tsx +12 -1
- package/src/views/post-editor/PostSectionEditor.tsx +22 -5
- package/src/views/seo/AuditTab.tsx +229 -0
- package/src/views/seo/ContentTab.tsx +164 -0
- package/src/views/seo/OverviewTab.tsx +257 -0
- package/src/views/seo/RedirectsTab.tsx +392 -0
- package/src/views/seo/TechnicalTab.tsx +303 -0
- package/src/views/seo/useSeoResource.ts +52 -0
- package/dist/views/PostEditor.d.ts +0 -6
- package/dist/views/PostEditor.d.ts.map +0 -1
- package/dist/views/PostEditor.js +0 -79
- package/dist/views/PostEditor.js.map +0 -1
- package/dist/views/Posts.d.ts +0 -5
- package/dist/views/Posts.d.ts.map +0 -1
- package/dist/views/Posts.js +0 -73
- package/dist/views/Posts.js.map +0 -1
- package/src/views/PostEditor.tsx +0 -257
- package/src/views/Posts.tsx +0 -375
|
@@ -30,7 +30,6 @@ import { sortByRelevance, type SortConfig, toggleSort } from '../lib/search.js'
|
|
|
30
30
|
import { useApiData } from '../lib/useApiData.js'
|
|
31
31
|
import { cmsApi } from '../lib/api.js'
|
|
32
32
|
import { FolderTree, type FolderSelection } from '../components/FolderTree.js'
|
|
33
|
-
import { FocalPointPicker } from '../components/FocalPointPicker.js'
|
|
34
33
|
|
|
35
34
|
interface MediaItem {
|
|
36
35
|
id: number | string
|
|
@@ -120,8 +119,6 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
120
119
|
const [editAlt, setEditAlt] = useState('')
|
|
121
120
|
const [editTitle, setEditTitle] = useState('')
|
|
122
121
|
const [editFilename, setEditFilename] = useState('')
|
|
123
|
-
const [focalX, setFocalX] = useState(0.5)
|
|
124
|
-
const [focalY, setFocalY] = useState(0.5)
|
|
125
122
|
const [saving, setSaving] = useState(false)
|
|
126
123
|
const [aiGenerating, setAiGenerating] = useState<string | null>(null)
|
|
127
124
|
const [uploading, setUploading] = useState(false)
|
|
@@ -157,8 +154,6 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
157
154
|
setEditAlt(item.altTag ?? '')
|
|
158
155
|
setEditTitle(item.title ?? '')
|
|
159
156
|
setEditFilename(item.name)
|
|
160
|
-
setFocalX((item as any).focalPointX ?? 0.5)
|
|
161
|
-
setFocalY((item as any).focalPointY ?? 0.5)
|
|
162
157
|
}
|
|
163
158
|
|
|
164
159
|
const closeDetail = () => {
|
|
@@ -187,8 +182,6 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
187
182
|
alt: editAlt,
|
|
188
183
|
title: editTitle,
|
|
189
184
|
filename: editFilename,
|
|
190
|
-
focalX,
|
|
191
|
-
focalY,
|
|
192
185
|
}),
|
|
193
186
|
})
|
|
194
187
|
setSaving(false)
|
|
@@ -244,18 +237,38 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
244
237
|
return
|
|
245
238
|
}
|
|
246
239
|
|
|
247
|
-
|
|
240
|
+
if (!activeItem) {
|
|
241
|
+
setAiGenerating(null)
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const res = await cmsApi<{ alt?: string; title?: string; alternatives?: string[] }>(
|
|
246
|
+
'/ai/media-metadata',
|
|
247
|
+
{ method: 'POST', body: JSON.stringify({ mediaId: activeItem.id }) },
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if (res.error) {
|
|
251
|
+
// AI plugin/provider not configured (e.g. local dev): fall back to a
|
|
252
|
+
// basic filename-derived suggestion so the action still helps.
|
|
253
|
+
const base = (activeItem.name ?? '')
|
|
254
|
+
.replace(/\.\w+$/, '')
|
|
255
|
+
.replace(/[-_]/g, ' ')
|
|
256
|
+
.trim()
|
|
257
|
+
if (field === 'alt') {
|
|
258
|
+
setEditAlt(base ? `Image of ${base}` : '')
|
|
259
|
+
} else {
|
|
260
|
+
setEditTitle(base.replace(/\b\w/g, (c) => c.toUpperCase()))
|
|
261
|
+
}
|
|
262
|
+
toast.info('AI unavailable — generated a basic suggestion from the filename')
|
|
263
|
+
setAiGenerating(null)
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
248
267
|
if (field === 'alt') {
|
|
249
|
-
|
|
250
|
-
setEditAlt(generated)
|
|
268
|
+
setEditAlt(res.data?.alt ?? '')
|
|
251
269
|
toast.success('Alt tag generated by AI')
|
|
252
|
-
} else
|
|
253
|
-
|
|
254
|
-
activeItem?.name
|
|
255
|
-
.replace(/[-_]/g, ' ')
|
|
256
|
-
.replace(/\.\w+$/, '')
|
|
257
|
-
.replace(/\b\w/g, (c) => c.toUpperCase()) ?? ''
|
|
258
|
-
setEditTitle(generated)
|
|
270
|
+
} else {
|
|
271
|
+
setEditTitle(res.data?.title ?? '')
|
|
259
272
|
toast.success('Title generated by AI')
|
|
260
273
|
}
|
|
261
274
|
setAiGenerating(null)
|
|
@@ -533,7 +546,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
533
546
|
>
|
|
534
547
|
{viewMode === 'grid' ? (
|
|
535
548
|
<div
|
|
536
|
-
className={`grid h-full gap-
|
|
549
|
+
className={`grid h-full auto-rows-min content-start gap-3 overflow-y-auto p-2 sm:gap-4 sm:p-3 ${
|
|
537
550
|
panelOpen
|
|
538
551
|
? 'grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'
|
|
539
552
|
: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-8'
|
|
@@ -542,10 +555,17 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
542
555
|
{filteredAndSorted.map((item) => {
|
|
543
556
|
const isActive = activeItem?.id === item.id
|
|
544
557
|
const hasIssues = !item.altTag || !item.title || item.usedOn?.length === 0
|
|
558
|
+
const typeLabel = (
|
|
559
|
+
item.name.includes('.')
|
|
560
|
+
? item.name.split('.').pop()!
|
|
561
|
+
: item.format || item.type || ''
|
|
562
|
+
)
|
|
563
|
+
.replace(/^.*\//, '')
|
|
564
|
+
.toUpperCase()
|
|
545
565
|
return (
|
|
546
566
|
<div
|
|
547
567
|
key={item.id}
|
|
548
|
-
className={`group relative
|
|
568
|
+
className={`group relative flex cursor-pointer flex-col overflow-hidden rounded-lg border-2 transition-all ${
|
|
549
569
|
isActive
|
|
550
570
|
? 'border-blue-500 ring-2 ring-blue-200'
|
|
551
571
|
: selectedMedia.includes(item.id)
|
|
@@ -556,45 +576,61 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
556
576
|
draggable
|
|
557
577
|
onDragStart={(e) => handleDragStart(e, item.id)}
|
|
558
578
|
>
|
|
559
|
-
<div className="
|
|
560
|
-
<
|
|
561
|
-
|
|
562
|
-
<div className="absolute inset-0 bg-linear-to-t from-black/60 to-transparent opacity-0 transition-opacity group-hover:opacity-100">
|
|
563
|
-
<div className="absolute right-0 bottom-0 left-0 p-2">
|
|
564
|
-
<p className="truncate text-xs font-medium text-white">{item.name}</p>
|
|
565
|
-
<p className="text-xs text-white/80">{item.size}</p>
|
|
579
|
+
<div className="relative aspect-square w-full overflow-hidden">
|
|
580
|
+
<div className="flex h-full w-full items-center justify-center bg-gray-100">
|
|
581
|
+
<MediaPreview item={item} />
|
|
566
582
|
</div>
|
|
567
|
-
|
|
568
|
-
|
|
583
|
+
{hasIssues && (
|
|
584
|
+
<div
|
|
585
|
+
className="absolute top-1.5 left-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-yellow-500"
|
|
586
|
+
title="Needs attention"
|
|
587
|
+
>
|
|
588
|
+
<AlertTriangle className="h-3 w-3 text-white" />
|
|
589
|
+
</div>
|
|
590
|
+
)}
|
|
569
591
|
<div
|
|
570
|
-
className="absolute top-1.5
|
|
571
|
-
|
|
592
|
+
className="absolute top-1.5 right-1.5 opacity-0 transition-opacity group-hover:opacity-100"
|
|
593
|
+
onClick={(e) => handleCheckbox(e, item.id)}
|
|
572
594
|
>
|
|
573
|
-
<
|
|
595
|
+
<div
|
|
596
|
+
className={`flex h-5 w-5 items-center justify-center rounded border-2 transition-colors ${
|
|
597
|
+
selectedMedia.includes(item.id)
|
|
598
|
+
? 'border-blue-600 bg-blue-600'
|
|
599
|
+
: 'border-gray-400 bg-white/80'
|
|
600
|
+
}`}
|
|
601
|
+
>
|
|
602
|
+
{selectedMedia.includes(item.id) && (
|
|
603
|
+
<svg
|
|
604
|
+
className="h-3 w-3 text-white"
|
|
605
|
+
fill="none"
|
|
606
|
+
viewBox="0 0 12 12"
|
|
607
|
+
>
|
|
608
|
+
<path
|
|
609
|
+
d="M10 3L4.5 8.5L2 6"
|
|
610
|
+
stroke="currentColor"
|
|
611
|
+
strokeWidth="2"
|
|
612
|
+
strokeLinecap="round"
|
|
613
|
+
strokeLinejoin="round"
|
|
614
|
+
/>
|
|
615
|
+
</svg>
|
|
616
|
+
)}
|
|
617
|
+
</div>
|
|
574
618
|
</div>
|
|
575
|
-
|
|
576
|
-
<div
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
<div
|
|
581
|
-
className={`flex h-5 w-5 items-center justify-center rounded border-2 transition-colors ${
|
|
582
|
-
selectedMedia.includes(item.id)
|
|
583
|
-
? 'border-blue-600 bg-blue-600'
|
|
584
|
-
: 'border-gray-400 bg-white/80'
|
|
619
|
+
</div>
|
|
620
|
+
<div className="flex items-center gap-2 border-t border-gray-200 bg-white px-2 py-1.5">
|
|
621
|
+
<span
|
|
622
|
+
className={`mt-0.5 h-2 w-2 shrink-0 self-start rounded-full ${
|
|
623
|
+
hasIssues ? 'bg-yellow-500' : 'bg-green-500'
|
|
585
624
|
}`}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
/>
|
|
596
|
-
</svg>
|
|
597
|
-
)}
|
|
625
|
+
title={hasIssues ? 'Needs attention' : 'SEO complete'}
|
|
626
|
+
/>
|
|
627
|
+
<div className="min-w-0 flex-1">
|
|
628
|
+
<p className="truncate text-xs font-medium text-gray-900">
|
|
629
|
+
{item.name}
|
|
630
|
+
</p>
|
|
631
|
+
<p className="truncate text-xs text-gray-500">
|
|
632
|
+
{[typeLabel, item.size].filter(Boolean).join(' · ')}
|
|
633
|
+
</p>
|
|
598
634
|
</div>
|
|
599
635
|
</div>
|
|
600
636
|
</div>
|
|
@@ -872,20 +908,6 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
872
908
|
</div>
|
|
873
909
|
</div>
|
|
874
910
|
|
|
875
|
-
{isImageMedia(activeItem) && activeItem.url && (
|
|
876
|
-
<div className="border-b border-gray-200 p-4">
|
|
877
|
-
<FocalPointPicker
|
|
878
|
-
imageUrl={activeItem.url}
|
|
879
|
-
focalX={focalX}
|
|
880
|
-
focalY={focalY}
|
|
881
|
-
onChange={(x, y) => {
|
|
882
|
-
setFocalX(x)
|
|
883
|
-
setFocalY(y)
|
|
884
|
-
}}
|
|
885
|
-
/>
|
|
886
|
-
</div>
|
|
887
|
-
)}
|
|
888
|
-
|
|
889
911
|
<div className="border-b border-gray-200 p-4">
|
|
890
912
|
<h4 className="mb-3 text-xs font-semibold tracking-wide text-gray-500 uppercase">
|
|
891
913
|
Used On {activeItem.usedOn && `(${activeItem.usedOn.length})`}
|
|
@@ -9,10 +9,11 @@ import { resolvePostTypeIcon } from '../../components/posts/icons.js'
|
|
|
9
9
|
/**
|
|
10
10
|
* Type-picker landing page for "+ New Post".
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* a chooser
|
|
15
|
-
*
|
|
12
|
+
* Behavior by number of configured post types:
|
|
13
|
+
* - exactly one → skip the picker and forward straight to that type's
|
|
14
|
+
* new-post editor (a one-card chooser adds nothing but a flash)
|
|
15
|
+
* - two or more → show the chooser cards
|
|
16
|
+
* - zero → show a "no post types configured" setup hint
|
|
16
17
|
*
|
|
17
18
|
* Picking a card navigates to the section-based Post editor's new-post
|
|
18
19
|
* route (`/posts/<slug>/new`), which seeds the post from that type's
|
|
@@ -11,8 +11,8 @@ import { POST_TYPE_PRESETS, type PresetColor, type QuickPreset } from './post-ty
|
|
|
11
11
|
* /posts/types/new — Quick Post Type gallery + snippet generator.
|
|
12
12
|
*
|
|
13
13
|
* Two ways to start:
|
|
14
|
-
* 1. Pick a Quick Post Type preset (
|
|
15
|
-
*
|
|
14
|
+
* 1. Pick a Quick Post Type preset (Blog) — pre-fills the form and
|
|
15
|
+
* tailored extra fields.
|
|
16
16
|
* 2. Fill the form from scratch.
|
|
17
17
|
*
|
|
18
18
|
* Either way the page emits a paste-ready `actuate.config.ts` snippet.
|
|
@@ -14,10 +14,11 @@ import { resolvePostTypeIcon } from '../../components/posts/icons.js'
|
|
|
14
14
|
* - title + description
|
|
15
15
|
* - footer row: post count · field count · URL base
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* adding a new collection
|
|
17
|
+
* Clicking a card opens that type's settings (`/posts/types/:slug/edit`);
|
|
18
|
+
* each card also has an "Edit Template" action (`/posts/types/:slug/template`)
|
|
19
|
+
* for the type's default layout. The dashed "+ New Post Type" card routes to
|
|
20
|
+
* `/posts/types/new`, where users get instructions for adding a new collection
|
|
21
|
+
* to the config file.
|
|
21
22
|
*
|
|
22
23
|
* Why no in-app create/edit flow yet:
|
|
23
24
|
* Collections are file-defined (actuate.config.ts). Writing to that
|
|
@@ -118,6 +118,15 @@ export function PostsListView({ onNavigate }: PostsListViewProps) {
|
|
|
118
118
|
setSelected(new Set())
|
|
119
119
|
}, [activeType, debouncedSearch, status, author])
|
|
120
120
|
|
|
121
|
+
// Selection is scoped to the rows visible on the current page: bulk
|
|
122
|
+
// actions only ever touch `selectedPosts` (the current page's selected
|
|
123
|
+
// rows), so clear the selection whenever the page changes. Without this
|
|
124
|
+
// the bulk bar count keeps counting rows from earlier pages while the
|
|
125
|
+
// publish/delete handlers silently operate on nothing.
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
setSelected(new Set())
|
|
128
|
+
}, [page])
|
|
129
|
+
|
|
121
130
|
const total = data?.total ?? 0
|
|
122
131
|
const totalAll = useMemo(
|
|
123
132
|
() => (data?.types ?? []).reduce((sum, t) => sum + t.postCount, 0),
|
|
@@ -10,6 +10,11 @@ import type { PostTypeIconName } from '../../components/posts/icons.js'
|
|
|
10
10
|
* Presets are NOT a runtime data model — they only seed the snippet.
|
|
11
11
|
* Collections stay code-defined; this just saves the developer from
|
|
12
12
|
* hand-authoring common field sets.
|
|
13
|
+
*
|
|
14
|
+
* Only the canonical **Blog** post type ships as a built-in preset. Other
|
|
15
|
+
* content types (case studies, portfolio, team, …) are available as opt-in
|
|
16
|
+
* collection factories in `@actuate-media/cms-core` (`collections/presets.ts`)
|
|
17
|
+
* rather than shipped here as defaults.
|
|
13
18
|
*/
|
|
14
19
|
|
|
15
20
|
export type PresetColor = 'purple' | 'green' | 'orange' | 'blue' | 'red' | 'pink'
|
|
@@ -34,72 +39,18 @@ export interface QuickPreset {
|
|
|
34
39
|
|
|
35
40
|
export const POST_TYPE_PRESETS: QuickPreset[] = [
|
|
36
41
|
{
|
|
37
|
-
key: '
|
|
38
|
-
label: '
|
|
39
|
-
singular: '
|
|
40
|
-
plural: '
|
|
41
|
-
slug: '
|
|
42
|
-
description: '
|
|
43
|
-
color: 'green',
|
|
44
|
-
icon: 'briefcase',
|
|
45
|
-
extraFields: [
|
|
46
|
-
"client: { type: 'text', label: 'Client Name' },",
|
|
47
|
-
"industry: { type: 'text', label: 'Industry' },",
|
|
48
|
-
"results: { type: 'richText', label: 'Results & Outcomes' },",
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
key: 'news',
|
|
53
|
-
label: 'News',
|
|
54
|
-
singular: 'News Item',
|
|
55
|
-
plural: 'News',
|
|
56
|
-
slug: 'news',
|
|
57
|
-
description: 'Company announcements, press releases, and industry news.',
|
|
58
|
-
color: 'orange',
|
|
59
|
-
icon: 'newspaper',
|
|
60
|
-
extraFields: ["source: { type: 'text', label: 'Source' },"],
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
key: 'portfolio',
|
|
64
|
-
label: 'Portfolio',
|
|
65
|
-
singular: 'Portfolio Item',
|
|
66
|
-
plural: 'Portfolio',
|
|
67
|
-
slug: 'portfolio',
|
|
68
|
-
description: 'Showcase projects, work samples, and creative pieces.',
|
|
69
|
-
color: 'blue',
|
|
70
|
-
icon: 'image',
|
|
71
|
-
extraFields: [
|
|
72
|
-
"projectUrl: { type: 'url', label: 'Project URL' },",
|
|
73
|
-
"gallery: { type: 'media', label: 'Gallery' },",
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
key: 'events',
|
|
78
|
-
label: 'Events',
|
|
79
|
-
singular: 'Event',
|
|
80
|
-
plural: 'Events',
|
|
81
|
-
slug: 'events',
|
|
82
|
-
description: 'Webinars, conferences, and scheduled happenings.',
|
|
83
|
-
color: 'pink',
|
|
84
|
-
icon: 'calendar',
|
|
85
|
-
extraFields: [
|
|
86
|
-
"startDate: { type: 'date', label: 'Start Date', includeTime: true },",
|
|
87
|
-
"endDate: { type: 'date', label: 'End Date', includeTime: true },",
|
|
88
|
-
"location: { type: 'text', label: 'Location' },",
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
key: 'team',
|
|
93
|
-
label: 'Team Members',
|
|
94
|
-
singular: 'Team Member',
|
|
95
|
-
plural: 'Team',
|
|
96
|
-
slug: 'team',
|
|
97
|
-
description: 'Staff bios, headshots, and role information.',
|
|
42
|
+
key: 'blog',
|
|
43
|
+
label: 'Blog',
|
|
44
|
+
singular: 'Post',
|
|
45
|
+
plural: 'Posts',
|
|
46
|
+
slug: 'blog',
|
|
47
|
+
description: 'Long-form articles and blog posts with author, category, and tags.',
|
|
98
48
|
color: 'purple',
|
|
99
|
-
icon: '
|
|
49
|
+
icon: 'newspaper',
|
|
100
50
|
extraFields: [
|
|
101
|
-
"
|
|
102
|
-
"
|
|
51
|
+
"author: { type: 'text', label: 'Author' },",
|
|
52
|
+
"category: { type: 'text', label: 'Category' },",
|
|
53
|
+
"tags: { type: 'array', label: 'Tags', fields: { value: { type: 'text', label: 'Tag' } } },",
|
|
103
54
|
],
|
|
104
55
|
},
|
|
105
56
|
]
|