@actuate-media/cms-admin 0.72.3 → 0.74.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/CHANGELOG.md +22 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +3 -2
- package/dist/AdminRoot.js.map +1 -1
- package/dist/LoginRoot.d.ts.map +1 -1
- package/dist/LoginRoot.js +2 -1
- package/dist/LoginRoot.js.map +1 -1
- package/dist/__tests__/components/resizable-aside.test.d.ts +2 -0
- package/dist/__tests__/components/resizable-aside.test.d.ts.map +1 -0
- package/dist/__tests__/components/resizable-aside.test.js +77 -0
- package/dist/__tests__/components/resizable-aside.test.js.map +1 -0
- package/dist/__tests__/components/theme-provider-accent.test.d.ts +2 -0
- package/dist/__tests__/components/theme-provider-accent.test.d.ts.map +1 -0
- package/dist/__tests__/components/theme-provider-accent.test.js +64 -0
- package/dist/__tests__/components/theme-provider-accent.test.js.map +1 -0
- package/dist/__tests__/views/portal-container.test.js +13 -0
- package/dist/__tests__/views/portal-container.test.js.map +1 -1
- package/dist/__tests__/views/redirects-pane.render.test.js +6 -0
- package/dist/__tests__/views/redirects-pane.render.test.js.map +1 -1
- package/dist/__tests__/views/seo-content-pane.render.test.js +13 -4
- package/dist/__tests__/views/seo-content-pane.render.test.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/CoauthorResultPopover.js +1 -1
- package/dist/components/EditorSeoAside.d.ts +20 -0
- package/dist/components/EditorSeoAside.d.ts.map +1 -0
- package/dist/components/EditorSeoAside.js +30 -0
- package/dist/components/EditorSeoAside.js.map +1 -0
- package/dist/components/MediaPickerModal.js +1 -1
- package/dist/components/SEOConfigPanel.js +1 -1
- package/dist/components/SchedulePublishDialog.js +1 -1
- package/dist/components/SharePreviewLinkDialog.js +1 -1
- package/dist/components/ThemeProvider.d.ts.map +1 -1
- package/dist/components/ThemeProvider.js +2 -1
- package/dist/components/ThemeProvider.js.map +1 -1
- package/dist/components/VersionHistory.js +1 -1
- package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
- package/dist/components/seo/SeoEditorPane.js +28 -8
- package/dist/components/seo/SeoEditorPane.js.map +1 -1
- package/dist/components/seo/SeoIssueFixPanel.d.ts.map +1 -1
- package/dist/components/seo/SeoIssueFixPanel.js +13 -3
- package/dist/components/seo/SeoIssueFixPanel.js.map +1 -1
- package/dist/components/seo/primitives.d.ts.map +1 -1
- package/dist/components/seo/primitives.js +3 -3
- package/dist/components/seo/primitives.js.map +1 -1
- package/dist/components/ui/Card.js +5 -5
- package/dist/components/ui/Card.js.map +1 -1
- package/dist/components/ui/InspectorPane.js +3 -3
- package/dist/components/ui/InspectorPane.js.map +1 -1
- package/dist/components/ui/PanelSection.d.ts +16 -0
- package/dist/components/ui/PanelSection.d.ts.map +1 -0
- package/dist/components/ui/PanelSection.js +15 -0
- package/dist/components/ui/PanelSection.js.map +1 -0
- package/dist/components/ui/ResizableAside.d.ts +38 -0
- package/dist/components/ui/ResizableAside.d.ts.map +1 -0
- package/dist/components/ui/ResizableAside.js +209 -0
- package/dist/components/ui/ResizableAside.js.map +1 -0
- package/dist/lib/admin-root-element.d.ts +9 -0
- package/dist/lib/admin-root-element.d.ts.map +1 -0
- package/dist/lib/admin-root-element.js +14 -0
- package/dist/lib/admin-root-element.js.map +1 -0
- package/dist/lib/portal-container.d.ts.map +1 -1
- package/dist/lib/portal-container.js +2 -3
- package/dist/lib/portal-container.js.map +1 -1
- package/dist/lib/seo-service.d.ts +89 -0
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js +50 -2
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/views/ApiKeys.js +2 -2
- package/dist/views/Dashboard.d.ts.map +1 -1
- package/dist/views/Dashboard.js +12 -14
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +35 -6
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/PageEditor.js +1 -1
- package/dist/views/Posts/PostTypeEditor.js +3 -3
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +4 -2
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/ScriptTags.js +1 -1
- package/dist/views/ScriptTags.js.map +1 -1
- package/dist/views/Users.js +1 -1
- package/dist/views/page-editor/EditorTopBar.d.ts +4 -1
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +5 -3
- package/dist/views/page-editor/EditorTopBar.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +24 -1
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +7 -2
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SectionsPanel.js +1 -1
- package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -1
- package/dist/views/post-editor/PostHeaderPanel.js +2 -1
- package/dist/views/post-editor/PostHeaderPanel.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +24 -1
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.js +1 -1
- package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
- package/dist/views/seo/LinksTab.d.ts +4 -0
- package/dist/views/seo/LinksTab.d.ts.map +1 -0
- package/dist/views/seo/LinksTab.js +111 -0
- package/dist/views/seo/LinksTab.js.map +1 -0
- package/dist/views/seo/OverviewTab.js +1 -1
- package/dist/views/seo/OverviewTab.js.map +1 -1
- package/dist/views/seo/RedirectsTab.d.ts.map +1 -1
- package/dist/views/seo/RedirectsTab.js +39 -6
- package/dist/views/seo/RedirectsTab.js.map +1 -1
- package/dist/views/seo/TechnicalTab.js +2 -2
- package/dist/views/settings/AISettingsTab.js +1 -1
- package/dist/views/settings/AiFeaturesCard.js +1 -1
- package/dist/views/settings/AiFeaturesCard.js.map +1 -1
- package/dist/views/settings/AppearanceTab.js +1 -1
- package/dist/views/settings/BrandVoiceCard.js +1 -1
- package/dist/views/settings/EmailSettingsTab.js +2 -2
- package/dist/views/settings/GeneralSettingsTab.js +1 -1
- package/dist/views/settings/SecurityStatusCard.js +1 -1
- package/dist/views/settings/SeoSettingsTab.js +4 -4
- package/dist/views/settings/UpdatesTab.js +2 -2
- package/dist/views/settings/components.js +3 -3
- package/dist/views/settings/components.js.map +1 -1
- package/package.json +2 -2
- package/src/AdminRoot.tsx +3 -2
- package/src/LoginRoot.tsx +2 -1
- package/src/__tests__/components/resizable-aside.test.tsx +130 -0
- package/src/__tests__/components/theme-provider-accent.test.tsx +84 -0
- package/src/__tests__/views/portal-container.test.tsx +14 -0
- package/src/__tests__/views/redirects-pane.render.test.tsx +6 -0
- package/src/__tests__/views/seo-content-pane.render.test.tsx +14 -4
- package/src/components/CoauthorResultPopover.tsx +1 -1
- package/src/components/EditorSeoAside.tsx +149 -0
- package/src/components/MediaPickerModal.tsx +1 -1
- package/src/components/SEOConfigPanel.tsx +2 -2
- package/src/components/SchedulePublishDialog.tsx +1 -1
- package/src/components/SharePreviewLinkDialog.tsx +1 -1
- package/src/components/ThemeProvider.tsx +3 -1
- package/src/components/VersionHistory.tsx +1 -1
- package/src/components/seo/SeoEditorPane.tsx +41 -12
- package/src/components/seo/SeoIssueFixPanel.tsx +16 -4
- package/src/components/seo/primitives.tsx +7 -3
- package/src/components/ui/Card.tsx +5 -5
- package/src/components/ui/InspectorPane.tsx +3 -3
- package/src/components/ui/PanelSection.tsx +53 -0
- package/src/components/ui/ResizableAside.tsx +317 -0
- package/src/lib/admin-root-element.ts +15 -0
- package/src/lib/portal-container.ts +3 -2
- package/src/lib/seo-service.ts +143 -2
- package/src/views/ApiKeys.tsx +2 -2
- package/src/views/Dashboard.tsx +40 -53
- package/src/views/MediaBrowser.tsx +62 -12
- package/src/views/PageEditor.tsx +1 -1
- package/src/views/Posts/PostTypeEditor.tsx +3 -3
- package/src/views/SEO.tsx +6 -0
- package/src/views/ScriptTags.tsx +2 -2
- package/src/views/Users.tsx +1 -1
- package/src/views/page-editor/EditorTopBar.tsx +24 -0
- package/src/views/page-editor/PageSectionEditor.tsx +44 -4
- package/src/views/page-editor/SectionInspector.tsx +33 -27
- package/src/views/page-editor/SectionsPanel.tsx +1 -1
- package/src/views/post-editor/PostHeaderPanel.tsx +16 -12
- package/src/views/post-editor/PostSectionEditor.tsx +44 -4
- package/src/views/post-editor/PostTemplateEditor.tsx +1 -1
- package/src/views/seo/LinksTab.tsx +462 -0
- package/src/views/seo/OverviewTab.tsx +1 -1
- package/src/views/seo/RedirectsTab.tsx +97 -4
- package/src/views/seo/TechnicalTab.tsx +2 -2
- package/src/views/settings/AISettingsTab.tsx +3 -3
- package/src/views/settings/AiFeaturesCard.tsx +1 -1
- package/src/views/settings/AppearanceTab.tsx +1 -1
- package/src/views/settings/BrandVoiceCard.tsx +2 -2
- package/src/views/settings/EmailSettingsTab.tsx +2 -2
- package/src/views/settings/GeneralSettingsTab.tsx +3 -3
- package/src/views/settings/SecurityStatusCard.tsx +1 -1
- package/src/views/settings/SeoSettingsTab.tsx +6 -6
- package/src/views/settings/UpdatesTab.tsx +3 -3
- package/src/views/settings/components.tsx +3 -3
package/src/views/Users.tsx
CHANGED
|
@@ -982,7 +982,7 @@ export function Users({ embedded = false, currentPath }: UsersProps = {}) {
|
|
|
982
982
|
|
|
983
983
|
{/* Role filter pills */}
|
|
984
984
|
<div
|
|
985
|
-
className="border-border flex flex-wrap gap-2 border-b px-
|
|
985
|
+
className="border-border flex flex-wrap gap-2 border-b px-6 py-4"
|
|
986
986
|
role="group"
|
|
987
987
|
aria-label="Filter by role"
|
|
988
988
|
>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Loader2,
|
|
11
11
|
type LucideIcon,
|
|
12
12
|
Monitor,
|
|
13
|
+
Search,
|
|
13
14
|
Settings2,
|
|
14
15
|
Smartphone,
|
|
15
16
|
Tablet,
|
|
@@ -57,6 +58,9 @@ interface EditorTopBarProps {
|
|
|
57
58
|
*/
|
|
58
59
|
onToggleSitePreview?: () => void
|
|
59
60
|
onOpenSettings: () => void
|
|
61
|
+
/** Open the document SEO panel (meta, social, schema, analysis). */
|
|
62
|
+
onOpenSeo?: () => void
|
|
63
|
+
seoOpen?: boolean
|
|
60
64
|
onSaveDraft: () => void
|
|
61
65
|
onPublish: () => void
|
|
62
66
|
/**
|
|
@@ -102,6 +106,8 @@ export function EditorTopBar({
|
|
|
102
106
|
sitePreview = false,
|
|
103
107
|
onToggleSitePreview,
|
|
104
108
|
onOpenSettings,
|
|
109
|
+
onOpenSeo,
|
|
110
|
+
seoOpen = false,
|
|
105
111
|
onSaveDraft,
|
|
106
112
|
onPublish,
|
|
107
113
|
structuralTypes = [],
|
|
@@ -192,6 +198,24 @@ export function EditorTopBar({
|
|
|
192
198
|
<Settings2 className="h-4 w-4" aria-hidden />
|
|
193
199
|
</button>
|
|
194
200
|
|
|
201
|
+
{onOpenSeo && (
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
onClick={onOpenSeo}
|
|
205
|
+
aria-pressed={seoOpen}
|
|
206
|
+
aria-label="SEO"
|
|
207
|
+
title="SEO — meta tags, social preview, schema, and analysis"
|
|
208
|
+
className={`inline-flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-sm font-medium transition-colors ${
|
|
209
|
+
seoOpen
|
|
210
|
+
? 'bg-primary text-primary-foreground'
|
|
211
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-accent'
|
|
212
|
+
}`}
|
|
213
|
+
>
|
|
214
|
+
<Search className="h-4 w-4 shrink-0" aria-hidden />
|
|
215
|
+
<span className="hidden sm:inline">SEO</span>
|
|
216
|
+
</button>
|
|
217
|
+
)}
|
|
218
|
+
|
|
195
219
|
{onHistory && (
|
|
196
220
|
<button
|
|
197
221
|
type="button"
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
|
4
4
|
import { AlertTriangle, Blocks, Loader2, Lock } from 'lucide-react'
|
|
5
5
|
import { toast } from 'sonner'
|
|
6
6
|
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js'
|
|
7
|
+
import { EditorSeoAside } from '../../components/EditorSeoAside.js'
|
|
7
8
|
import { ErrorBoundary } from '../../components/ErrorBoundary.js'
|
|
8
9
|
import { VersionHistory } from '../../components/VersionHistory.js'
|
|
9
10
|
import {
|
|
@@ -97,6 +98,7 @@ export function PageSectionEditor({
|
|
|
97
98
|
|
|
98
99
|
const [addOpen, setAddOpen] = useState(false)
|
|
99
100
|
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
101
|
+
const [seoOpen, setSeoOpen] = useState(false)
|
|
100
102
|
const [historyOpen, setHistoryOpen] = useState(false)
|
|
101
103
|
const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null)
|
|
102
104
|
const [confirmPublish, setConfirmPublish] = useState(false)
|
|
@@ -336,6 +338,7 @@ export function PageSectionEditor({
|
|
|
336
338
|
.then((p) => {
|
|
337
339
|
setPage(p)
|
|
338
340
|
setSelectedId(null)
|
|
341
|
+
setSeoOpen(false)
|
|
339
342
|
setDirty(false)
|
|
340
343
|
setSaveState('idle')
|
|
341
344
|
setValidation(null)
|
|
@@ -345,6 +348,27 @@ export function PageSectionEditor({
|
|
|
345
348
|
})
|
|
346
349
|
}, [page?.id])
|
|
347
350
|
|
|
351
|
+
const handleSeoSaved = useCallback(() => {
|
|
352
|
+
if (!page?.id) return
|
|
353
|
+
fetchPageForEditor(page.id)
|
|
354
|
+
.then((p) => setPage(p))
|
|
355
|
+
.catch(() => {
|
|
356
|
+
/* SEO API saved; editor meta will sync on next full load */
|
|
357
|
+
})
|
|
358
|
+
}, [page?.id])
|
|
359
|
+
|
|
360
|
+
const handleToggleSeo = useCallback(() => {
|
|
361
|
+
setSeoOpen((open) => {
|
|
362
|
+
if (!open) setSelectedId(null)
|
|
363
|
+
return !open
|
|
364
|
+
})
|
|
365
|
+
}, [])
|
|
366
|
+
|
|
367
|
+
const handleSelectSection = useCallback((id: string | null) => {
|
|
368
|
+
setSeoOpen(false)
|
|
369
|
+
setSelectedId(id)
|
|
370
|
+
}, [])
|
|
371
|
+
|
|
348
372
|
// ─── Navigation with unsaved guard ───────────────────────────────────
|
|
349
373
|
const guardedNavigate = useCallback(
|
|
350
374
|
(path: string) => {
|
|
@@ -492,6 +516,8 @@ export function PageSectionEditor({
|
|
|
492
516
|
onToggleSitePreview={page.id && page.slug ? sitePreview.toggle : undefined}
|
|
493
517
|
onHistory={page.id ? () => setHistoryOpen(true) : undefined}
|
|
494
518
|
onOpenSettings={() => setSettingsOpen(true)}
|
|
519
|
+
onOpenSeo={handleToggleSeo}
|
|
520
|
+
seoOpen={seoOpen}
|
|
495
521
|
onSaveDraft={handleSaveDraft}
|
|
496
522
|
onPublish={requestPublish}
|
|
497
523
|
structuralTypes={structuralTypes}
|
|
@@ -545,12 +571,12 @@ export function PageSectionEditor({
|
|
|
545
571
|
/>
|
|
546
572
|
)}
|
|
547
573
|
|
|
548
|
-
<div className="flex flex-1 overflow-hidden">
|
|
574
|
+
<div className="relative flex flex-1 overflow-hidden">
|
|
549
575
|
<SectionsPanel
|
|
550
576
|
sections={page.sections}
|
|
551
577
|
selectedId={selectedId}
|
|
552
578
|
canEdit={effectiveCanEdit}
|
|
553
|
-
onSelect={
|
|
579
|
+
onSelect={handleSelectSection}
|
|
554
580
|
onReorder={handleReorder}
|
|
555
581
|
onToggleVisibility={handleToggleVisibility}
|
|
556
582
|
onDuplicate={handleDuplicate}
|
|
@@ -571,7 +597,7 @@ export function PageSectionEditor({
|
|
|
571
597
|
sections={page.sections}
|
|
572
598
|
selectedId={selectedId}
|
|
573
599
|
viewport={viewport}
|
|
574
|
-
onSelect={
|
|
600
|
+
onSelect={handleSelectSection}
|
|
575
601
|
onAddSection={() => setAddOpen(true)}
|
|
576
602
|
structuralTypes={structuralTypes}
|
|
577
603
|
sitePreviewAvailable={sitePreviewAvailable && !sitePreview.active}
|
|
@@ -579,7 +605,7 @@ export function PageSectionEditor({
|
|
|
579
605
|
)}
|
|
580
606
|
</div>
|
|
581
607
|
|
|
582
|
-
{selected && (
|
|
608
|
+
{selected && !seoOpen && (
|
|
583
609
|
<SectionInspector
|
|
584
610
|
section={selected}
|
|
585
611
|
canEdit={effectiveCanEdit}
|
|
@@ -590,6 +616,20 @@ export function PageSectionEditor({
|
|
|
590
616
|
onToggleVisibility={() => handleToggleVisibility(selected.id)}
|
|
591
617
|
/>
|
|
592
618
|
)}
|
|
619
|
+
|
|
620
|
+
{seoOpen && (
|
|
621
|
+
<EditorSeoAside
|
|
622
|
+
entityType="page"
|
|
623
|
+
entityId={page.id}
|
|
624
|
+
title={page.title}
|
|
625
|
+
seoTitle={page.seoTitle}
|
|
626
|
+
seoDescription={page.seoDescription}
|
|
627
|
+
canEdit={effectiveCanEdit}
|
|
628
|
+
onClose={() => setSeoOpen(false)}
|
|
629
|
+
onDraftChange={handlePageSettings}
|
|
630
|
+
onSaved={handleSeoSaved}
|
|
631
|
+
/>
|
|
632
|
+
)}
|
|
593
633
|
</div>
|
|
594
634
|
</div>
|
|
595
635
|
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from '@dnd-kit/sortable'
|
|
20
20
|
import { CSS } from '@dnd-kit/utilities'
|
|
21
21
|
import { MediaPickerModal } from '../../components/MediaPickerModal.js'
|
|
22
|
+
import { InspectorExpandButton, ResizableAside } from '../../components/ui/ResizableAside.js'
|
|
22
23
|
import { Toggle } from '../../components/ui/Toggle.js'
|
|
23
24
|
import { RichTextField } from '../../fields/RichTextField.js'
|
|
24
25
|
import type { PageSection } from '../../lib/page-editor-service.js'
|
|
@@ -46,6 +47,29 @@ interface SectionInspectorProps {
|
|
|
46
47
|
const LABEL = 'text-foreground mb-1 block text-xs font-medium'
|
|
47
48
|
const INPUT =
|
|
48
49
|
'border-input bg-input-background text-foreground focus-visible:ring-ring w-full rounded-md border px-2.5 py-1.5 text-sm focus-visible:ring-2 focus-visible:outline-none'
|
|
50
|
+
const INSPECTOR_SHELL = 'bg-card border-border flex h-full flex-col border-l'
|
|
51
|
+
|
|
52
|
+
function InspectorHeaderActions({
|
|
53
|
+
onClose,
|
|
54
|
+
closeLabel = 'Close inspector',
|
|
55
|
+
}: {
|
|
56
|
+
onClose: () => void
|
|
57
|
+
closeLabel?: string
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<div className="flex shrink-0 items-center gap-0.5">
|
|
61
|
+
<InspectorExpandButton />
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
onClick={onClose}
|
|
65
|
+
aria-label={closeLabel}
|
|
66
|
+
className="text-muted-foreground hover:text-foreground hover:bg-accent flex h-9 w-9 items-center justify-center rounded-md md:h-7 md:w-7"
|
|
67
|
+
>
|
|
68
|
+
<X className="h-4 w-4" aria-hidden />
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
49
73
|
|
|
50
74
|
export function SectionInspector({
|
|
51
75
|
section,
|
|
@@ -80,24 +104,17 @@ export function SectionInspector({
|
|
|
80
104
|
// editable controls, so the externally-managed content is never altered here.
|
|
81
105
|
if (section.unmanaged) {
|
|
82
106
|
return (
|
|
83
|
-
<
|
|
107
|
+
<ResizableAside
|
|
84
108
|
role="dialog"
|
|
85
109
|
aria-label={`${section.name} (read-only)`}
|
|
86
|
-
className=
|
|
110
|
+
className={INSPECTOR_SHELL}
|
|
87
111
|
>
|
|
88
|
-
<div className="border-border flex items-center justify-between border-b px-
|
|
112
|
+
<div className="border-border flex items-center justify-between border-b px-6 py-4">
|
|
89
113
|
<div className="min-w-0">
|
|
90
114
|
<h2 className="text-foreground truncate text-sm font-medium">{section.name}</h2>
|
|
91
115
|
<p className="text-muted-foreground text-xs">Managed outside the editor</p>
|
|
92
116
|
</div>
|
|
93
|
-
<
|
|
94
|
-
type="button"
|
|
95
|
-
onClick={onClose}
|
|
96
|
-
aria-label="Close inspector"
|
|
97
|
-
className="text-muted-foreground hover:text-foreground hover:bg-accent rounded p-1"
|
|
98
|
-
>
|
|
99
|
-
<X className="h-4 w-4" aria-hidden />
|
|
100
|
-
</button>
|
|
117
|
+
<InspectorHeaderActions onClose={onClose} />
|
|
101
118
|
</div>
|
|
102
119
|
<div className="flex-1 space-y-3 overflow-y-auto p-4">
|
|
103
120
|
<div className="border-border bg-muted text-muted-foreground space-y-2 rounded-md border p-3 text-sm">
|
|
@@ -119,29 +136,18 @@ export function SectionInspector({
|
|
|
119
136
|
</p>
|
|
120
137
|
</div>
|
|
121
138
|
</div>
|
|
122
|
-
</
|
|
139
|
+
</ResizableAside>
|
|
123
140
|
)
|
|
124
141
|
}
|
|
125
142
|
|
|
126
143
|
return (
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
aria-label={`Edit ${section.name}`}
|
|
130
|
-
className="bg-card border-border flex h-full w-80 shrink-0 flex-col border-l"
|
|
131
|
-
>
|
|
132
|
-
<div className="border-border flex items-center justify-between border-b px-4 py-3">
|
|
144
|
+
<ResizableAside role="dialog" aria-label={`Edit ${section.name}`} className={INSPECTOR_SHELL}>
|
|
145
|
+
<div className="border-border flex items-center justify-between border-b px-6 py-4">
|
|
133
146
|
<div className="min-w-0">
|
|
134
147
|
<h2 className="text-foreground truncate text-sm font-medium">{section.name}</h2>
|
|
135
148
|
<p className="text-muted-foreground text-xs">{def?.name ?? 'Section'}</p>
|
|
136
149
|
</div>
|
|
137
|
-
<
|
|
138
|
-
type="button"
|
|
139
|
-
onClick={onClose}
|
|
140
|
-
aria-label="Close inspector"
|
|
141
|
-
className="text-muted-foreground hover:text-foreground hover:bg-accent rounded p-1"
|
|
142
|
-
>
|
|
143
|
-
<X className="h-4 w-4" aria-hidden />
|
|
144
|
-
</button>
|
|
150
|
+
<InspectorHeaderActions onClose={onClose} />
|
|
145
151
|
</div>
|
|
146
152
|
|
|
147
153
|
<div className="flex-1 space-y-6 overflow-y-auto p-4">
|
|
@@ -316,7 +322,7 @@ export function SectionInspector({
|
|
|
316
322
|
</div>
|
|
317
323
|
</fieldset>
|
|
318
324
|
</div>
|
|
319
|
-
</
|
|
325
|
+
</ResizableAside>
|
|
320
326
|
)
|
|
321
327
|
}
|
|
322
328
|
|
|
@@ -78,7 +78,7 @@ export function SectionsPanel({
|
|
|
78
78
|
className="bg-card border-border flex h-full w-72 shrink-0 flex-col border-r"
|
|
79
79
|
aria-label="Page sections"
|
|
80
80
|
>
|
|
81
|
-
<div className="border-border flex items-center justify-between border-b px-
|
|
81
|
+
<div className="border-border flex items-center justify-between border-b px-6 py-4">
|
|
82
82
|
<div className="flex items-center gap-2">
|
|
83
83
|
<h2 className="text-foreground text-sm font-medium">Sections</h2>
|
|
84
84
|
<span className="bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs font-medium tabular-nums">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { X } from 'lucide-react'
|
|
4
|
+
import { InspectorExpandButton, ResizableAside } from '../../components/ui/ResizableAside.js'
|
|
4
5
|
import { Toggle } from '../../components/ui/Toggle.js'
|
|
5
6
|
import type { PostHeaderConfig } from '../../lib/post-editor-service.js'
|
|
6
7
|
|
|
@@ -27,24 +28,27 @@ const TOGGLES: Array<{ key: keyof PostHeaderConfig; label: string }> = [
|
|
|
27
28
|
/** Inspector panel for the post header layout (template-level design config). */
|
|
28
29
|
export function PostHeaderPanel({ header, canEdit, onClose, onChange }: PostHeaderPanelProps) {
|
|
29
30
|
return (
|
|
30
|
-
<
|
|
31
|
+
<ResizableAside
|
|
31
32
|
role="dialog"
|
|
32
33
|
aria-label="Edit post header"
|
|
33
|
-
className="bg-card border-border flex h-full
|
|
34
|
+
className="bg-card border-border flex h-full flex-col border-l"
|
|
34
35
|
>
|
|
35
|
-
<div className="border-border flex items-center justify-between border-b px-
|
|
36
|
+
<div className="border-border flex items-center justify-between border-b px-6 py-4">
|
|
36
37
|
<div className="min-w-0">
|
|
37
38
|
<h2 className="text-foreground truncate text-sm font-medium">Post header</h2>
|
|
38
39
|
<p className="text-muted-foreground text-xs">Layout for every post of this type</p>
|
|
39
40
|
</div>
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
<div className="flex shrink-0 items-center gap-0.5">
|
|
42
|
+
<InspectorExpandButton />
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={onClose}
|
|
46
|
+
aria-label="Close header panel"
|
|
47
|
+
className="text-muted-foreground hover:text-foreground hover:bg-accent flex h-9 w-9 items-center justify-center rounded-md md:h-7 md:w-7"
|
|
48
|
+
>
|
|
49
|
+
<X className="h-4 w-4" aria-hidden />
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
48
52
|
</div>
|
|
49
53
|
|
|
50
54
|
<div className="flex-1 space-y-6 overflow-y-auto p-4">
|
|
@@ -92,6 +96,6 @@ export function PostHeaderPanel({ header, canEdit, onClose, onChange }: PostHead
|
|
|
92
96
|
})}
|
|
93
97
|
</fieldset>
|
|
94
98
|
</div>
|
|
95
|
-
</
|
|
99
|
+
</ResizableAside>
|
|
96
100
|
)
|
|
97
101
|
}
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
|
4
4
|
import { AlertTriangle, Loader2, Lock } from 'lucide-react'
|
|
5
5
|
import { toast } from 'sonner'
|
|
6
6
|
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js'
|
|
7
|
+
import { EditorSeoAside } from '../../components/EditorSeoAside.js'
|
|
7
8
|
import { ErrorBoundary } from '../../components/ErrorBoundary.js'
|
|
8
9
|
import { VersionHistory } from '../../components/VersionHistory.js'
|
|
9
10
|
import {
|
|
@@ -122,6 +123,7 @@ export function PostSectionEditor({
|
|
|
122
123
|
|
|
123
124
|
const [addOpen, setAddOpen] = useState(false)
|
|
124
125
|
const [fieldsOpen, setFieldsOpen] = useState(false)
|
|
126
|
+
const [seoOpen, setSeoOpen] = useState(false)
|
|
125
127
|
const [historyOpen, setHistoryOpen] = useState(false)
|
|
126
128
|
const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null)
|
|
127
129
|
const [confirmPublish, setConfirmPublish] = useState(false)
|
|
@@ -386,6 +388,7 @@ export function PostSectionEditor({
|
|
|
386
388
|
.then((p) => {
|
|
387
389
|
setPost(p)
|
|
388
390
|
setSelectedId(null)
|
|
391
|
+
setSeoOpen(false)
|
|
389
392
|
setDirty(false)
|
|
390
393
|
setSaveState('idle')
|
|
391
394
|
setValidation(null)
|
|
@@ -395,6 +398,27 @@ export function PostSectionEditor({
|
|
|
395
398
|
})
|
|
396
399
|
}, [postType, post?.id])
|
|
397
400
|
|
|
401
|
+
const handleSeoSaved = useCallback(() => {
|
|
402
|
+
if (!post?.id) return
|
|
403
|
+
fetchPostForEditor(postType, post.id)
|
|
404
|
+
.then((p) => setPost(p))
|
|
405
|
+
.catch(() => {
|
|
406
|
+
/* SEO API saved; editor meta will sync on next full load */
|
|
407
|
+
})
|
|
408
|
+
}, [postType, post?.id])
|
|
409
|
+
|
|
410
|
+
const handleToggleSeo = useCallback(() => {
|
|
411
|
+
setSeoOpen((open) => {
|
|
412
|
+
if (!open) setSelectedId(null)
|
|
413
|
+
return !open
|
|
414
|
+
})
|
|
415
|
+
}, [])
|
|
416
|
+
|
|
417
|
+
const handleSelectSection = useCallback((id: string | null) => {
|
|
418
|
+
setSeoOpen(false)
|
|
419
|
+
setSelectedId(id)
|
|
420
|
+
}, [])
|
|
421
|
+
|
|
398
422
|
const guardedNavigate = useCallback(
|
|
399
423
|
(path: string) => {
|
|
400
424
|
if (dirty) setLeaveTarget(path)
|
|
@@ -534,6 +558,8 @@ export function PostSectionEditor({
|
|
|
534
558
|
onToggleSitePreview={post.id && post.slug ? sitePreview.toggle : undefined}
|
|
535
559
|
onHistory={post.id ? () => setHistoryOpen(true) : undefined}
|
|
536
560
|
onOpenSettings={() => setFieldsOpen(true)}
|
|
561
|
+
onOpenSeo={handleToggleSeo}
|
|
562
|
+
seoOpen={seoOpen}
|
|
537
563
|
onSaveDraft={handleSaveDraft}
|
|
538
564
|
onPublish={requestPublish}
|
|
539
565
|
structuralTypes={structuralTypes}
|
|
@@ -564,12 +590,12 @@ export function PostSectionEditor({
|
|
|
564
590
|
/>
|
|
565
591
|
)}
|
|
566
592
|
|
|
567
|
-
<div className="flex flex-1 overflow-hidden">
|
|
593
|
+
<div className="relative flex flex-1 overflow-hidden">
|
|
568
594
|
<SectionsPanel
|
|
569
595
|
sections={post.sections}
|
|
570
596
|
selectedId={selectedId}
|
|
571
597
|
canEdit={canEdit}
|
|
572
|
-
onSelect={
|
|
598
|
+
onSelect={handleSelectSection}
|
|
573
599
|
onReorder={handleReorder}
|
|
574
600
|
onToggleVisibility={handleToggleVisibility}
|
|
575
601
|
onDuplicate={handleDuplicate}
|
|
@@ -592,7 +618,7 @@ export function PostSectionEditor({
|
|
|
592
618
|
context={context}
|
|
593
619
|
selectedId={selectedId}
|
|
594
620
|
viewport={viewport}
|
|
595
|
-
onSelect={
|
|
621
|
+
onSelect={handleSelectSection}
|
|
596
622
|
onAddSection={() => setAddOpen(true)}
|
|
597
623
|
structuralTypes={structuralTypes}
|
|
598
624
|
sitePreviewAvailable={sitePreviewAvailable && !sitePreview.active}
|
|
@@ -600,7 +626,7 @@ export function PostSectionEditor({
|
|
|
600
626
|
)}
|
|
601
627
|
</div>
|
|
602
628
|
|
|
603
|
-
{selected && (
|
|
629
|
+
{selected && !seoOpen && (
|
|
604
630
|
<SectionInspector
|
|
605
631
|
section={selected}
|
|
606
632
|
canEdit={canEdit}
|
|
@@ -613,6 +639,20 @@ export function PostSectionEditor({
|
|
|
613
639
|
onPostBodyChange={handlePostBodyChange}
|
|
614
640
|
/>
|
|
615
641
|
)}
|
|
642
|
+
|
|
643
|
+
{seoOpen && (
|
|
644
|
+
<EditorSeoAside
|
|
645
|
+
entityType="post"
|
|
646
|
+
entityId={post.id}
|
|
647
|
+
title={post.title}
|
|
648
|
+
seoTitle={post.seoTitle}
|
|
649
|
+
seoDescription={post.seoDescription}
|
|
650
|
+
canEdit={canEdit}
|
|
651
|
+
onClose={() => setSeoOpen(false)}
|
|
652
|
+
onDraftChange={handleFields}
|
|
653
|
+
onSaved={handleSeoSaved}
|
|
654
|
+
/>
|
|
655
|
+
)}
|
|
616
656
|
</div>
|
|
617
657
|
</div>
|
|
618
658
|
|
|
@@ -248,7 +248,7 @@ export function PostTemplateEditor({
|
|
|
248
248
|
</div>
|
|
249
249
|
</header>
|
|
250
250
|
|
|
251
|
-
<div className="flex flex-1 overflow-hidden">
|
|
251
|
+
<div className="relative flex flex-1 overflow-hidden">
|
|
252
252
|
<SectionsPanel
|
|
253
253
|
sections={template.sections}
|
|
254
254
|
selectedId={selectedSectionId}
|