@prenta/admin 0.95.1 → 0.97.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 +76 -0
- package/dist/__tests__/lib/page-editor-service.test.js +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/post-editor-service.test.js +27 -0
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
- package/dist/__tests__/styles/responsive-layer.test.js +2 -2
- package/dist/__tests__/styles/responsive-layer.test.js.map +1 -1
- package/dist/__tests__/views/canvas-surface-shared.test.d.ts +2 -0
- package/dist/__tests__/views/canvas-surface-shared.test.d.ts.map +1 -0
- package/dist/__tests__/views/canvas-surface-shared.test.js +53 -0
- package/dist/__tests__/views/canvas-surface-shared.test.js.map +1 -0
- package/dist/__tests__/views/post-section-editor.test.js +42 -0
- package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
- package/dist/__tests__/views/version-history-panel.render.test.js +23 -2
- package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -1
- package/dist/lib/post-editor-service.d.ts.map +1 -1
- package/dist/lib/post-editor-service.js +7 -0
- package/dist/lib/post-editor-service.js.map +1 -1
- package/dist/prenta-admin.css +1 -1
- package/dist/views/Posts/PostsListView.d.ts.map +1 -1
- package/dist/views/Posts/PostsListView.js +11 -1
- package/dist/views/Posts/PostsListView.js.map +1 -1
- package/dist/views/page-editor/CanvasSurface.d.ts +55 -0
- package/dist/views/page-editor/CanvasSurface.d.ts.map +1 -0
- package/dist/views/page-editor/CanvasSurface.js +94 -0
- package/dist/views/page-editor/CanvasSurface.js.map +1 -0
- package/dist/views/page-editor/EditorCanvas.d.ts +5 -28
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +17 -79
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/SectionEditForm.d.ts.map +1 -1
- package/dist/views/page-editor/SectionEditForm.js +6 -1
- package/dist/views/page-editor/SectionEditForm.js.map +1 -1
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +5 -1
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/sections/DividerSection.d.ts +14 -0
- package/dist/views/page-editor/sections/DividerSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/DividerSection.js +19 -0
- package/dist/views/page-editor/sections/DividerSection.js.map +1 -0
- package/dist/views/page-editor/sections/EmbedSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/EmbedSection.js +11 -3
- package/dist/views/page-editor/sections/EmbedSection.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +4 -0
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/page-editor/sections/WidgetEmbedSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/WidgetEmbedSection.js +1 -1
- package/dist/views/page-editor/sections/WidgetEmbedSection.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts +16 -5
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +16 -47
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +82 -22
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/lib/page-editor-service.test.ts +1 -0
- package/src/__tests__/lib/post-editor-service.test.ts +30 -0
- package/src/__tests__/styles/responsive-layer.test.ts +2 -2
- package/src/__tests__/views/canvas-surface-shared.test.ts +61 -0
- package/src/__tests__/views/post-section-editor.test.tsx +63 -0
- package/src/__tests__/views/version-history-panel.render.test.tsx +26 -2
- package/src/lib/post-editor-service.ts +7 -0
- package/src/views/Posts/PostsListView.tsx +17 -1
- package/src/views/page-editor/CanvasSurface.tsx +171 -0
- package/src/views/page-editor/EditorCanvas.tsx +46 -144
- package/src/views/page-editor/SectionEditForm.tsx +9 -1
- package/src/views/page-editor/SectionInspector.tsx +7 -2
- package/src/views/page-editor/sections/DividerSection.tsx +31 -0
- package/src/views/page-editor/sections/EmbedSection.tsx +11 -5
- package/src/views/page-editor/sections/SectionRenderer.tsx +4 -0
- package/src/views/page-editor/sections/WidgetEmbedSection.tsx +4 -1
- package/src/views/post-editor/PostEditorCanvas.tsx +54 -74
- package/src/views/post-editor/PostSectionEditor.tsx +180 -81
- package/dist/components/EditorSeoAside.d.ts +0 -23
- package/dist/components/EditorSeoAside.d.ts.map +0 -1
- package/dist/components/EditorSeoAside.js +0 -21
- package/dist/components/EditorSeoAside.js.map +0 -1
- package/src/components/EditorSeoAside.tsx +0 -68
|
@@ -6,7 +6,9 @@ import { toast } from 'sonner'
|
|
|
6
6
|
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js'
|
|
7
7
|
import { useNavigationBlocker } from '../../router/index.js'
|
|
8
8
|
import { SchedulePublishDialog } from '../../components/SchedulePublishDialog.js'
|
|
9
|
-
import {
|
|
9
|
+
import { EditorRail, RailHeader } from '../../components/EditorRail.js'
|
|
10
|
+
import { EditorRailTabs, type RailTab, type RailTabId } from '../../components/EditorRailTabs.js'
|
|
11
|
+
import { SeoEditorPane } from '../../components/seo/SeoEditorPane.js'
|
|
10
12
|
import { ErrorBoundary } from '../../components/ErrorBoundary.js'
|
|
11
13
|
import { VersionHistory } from '../../components/VersionHistory.js'
|
|
12
14
|
import {
|
|
@@ -42,11 +44,14 @@ import { DEFAULT_VIEWPORT, type ViewportId } from '../page-editor/viewports.js'
|
|
|
42
44
|
import { EditorTopBar, type SaveState } from '../page-editor/EditorTopBar.js'
|
|
43
45
|
import { SitePreviewFrame } from '../page-editor/SitePreviewFrame.js'
|
|
44
46
|
import { useSitePreview } from '../page-editor/useSitePreview.js'
|
|
45
|
-
import {
|
|
47
|
+
import { SectionsList } from '../page-editor/SectionsPanel.js'
|
|
46
48
|
import { SectionEditForm } from '../page-editor/SectionEditForm.js'
|
|
47
49
|
import { ValidationSummary } from '../page-editor/ValidationSummary.js'
|
|
48
50
|
import { resolveDefaultSitePreview } from '../../lib/editor-preview.js'
|
|
49
|
-
import { computeLiveSeoScore } from '../../lib/editor-seo-content.js'
|
|
51
|
+
import { buildEditorContentHtml, computeLiveSeoScore } from '../../lib/editor-seo-content.js'
|
|
52
|
+
import { snapshotBaseline, type ReflowBaseline } from '../../lib/reflow-baseline.js'
|
|
53
|
+
import { ReflowContext } from '../page-editor/reflow-context.js'
|
|
54
|
+
import type { Zoom } from '../../lib/breakpoints.js'
|
|
50
55
|
import { companionDimensionPaths, inspectorIdFromFieldPath } from '../../lib/preview-bridge.js'
|
|
51
56
|
import { PostEditorCanvas } from './PostEditorCanvas.js'
|
|
52
57
|
import { PostFieldsModal, type PostFieldConfig } from './PostFieldsModal.js'
|
|
@@ -124,18 +129,45 @@ export function PostSectionEditor({
|
|
|
124
129
|
fieldPath: string
|
|
125
130
|
} | null>(null)
|
|
126
131
|
const [viewport, setViewport] = useState<ViewportId>(DEFAULT_VIEWPORT)
|
|
132
|
+
const [zoom, setZoom] = useState<Zoom>('fit')
|
|
133
|
+
/** Scale the canvas measured — reported upward so the zoom chip shows a real %. */
|
|
134
|
+
const [canvasScale, setCanvasScale] = useState(1)
|
|
135
|
+
/** The live canvas surface, for the reflow guard to clone. */
|
|
136
|
+
const [canvasEl, setCanvasEl] = useState<HTMLElement | null>(null)
|
|
137
|
+
/** Section content as it is on the live site, for the reflow guard's baseline. */
|
|
138
|
+
const [baseline, setBaseline] = useState<ReflowBaseline | null>(null)
|
|
127
139
|
const [dirty, setDirty] = useState(false)
|
|
128
140
|
const [saveState, setSaveState] = useState<SaveState>('idle')
|
|
129
141
|
const [publishing, setPublishing] = useState(false)
|
|
130
142
|
|
|
131
143
|
const [fieldsOpen, setFieldsOpen] = useState(false)
|
|
132
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Active rail tab. Replaces the boolean `seoOpen` that drove a right-hand
|
|
146
|
+
* aside — the same aside the page editor retired so the editor has one
|
|
147
|
+
* settings surface rather than two competing ones.
|
|
148
|
+
*/
|
|
149
|
+
const [railTab, setRailTab] = useState<RailTabId>('sections')
|
|
150
|
+
const seoOpen = railTab === 'seo'
|
|
133
151
|
const [historyOpen, setHistoryOpen] = useState(false)
|
|
134
152
|
const [confirmPublish, setConfirmPublish] = useState(false)
|
|
135
153
|
const [scheduleOpen, setScheduleOpen] = useState(false)
|
|
136
154
|
const [leaveTarget, setLeaveTarget] = useState<string | null>(null)
|
|
137
155
|
const [validation, setValidation] = useState<ValidationResult | null>(null)
|
|
138
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Take delivery of server state.
|
|
159
|
+
*
|
|
160
|
+
* Every path that receives a fresh post from the API goes through here so
|
|
161
|
+
* the reflow baseline is re-captured with it. Saving a PUBLISHED post writes
|
|
162
|
+
* straight through to the live content, so a save moves the baseline exactly
|
|
163
|
+
* as a publish does; missing one would leave the guard comparing against
|
|
164
|
+
* content no longer on the site.
|
|
165
|
+
*/
|
|
166
|
+
const acceptPost = useCallback((p: EditorPost) => {
|
|
167
|
+
setPost(p)
|
|
168
|
+
setBaseline(snapshotBaseline(p.sections, p.status))
|
|
169
|
+
}, [])
|
|
170
|
+
|
|
139
171
|
const { canEdit, canPublish } = useMemo(() => deriveCan(session), [session])
|
|
140
172
|
const customRenderers = useAdminSectionRenderers()
|
|
141
173
|
const { siteUrl: publicSiteUrl } = usePublicSiteUrl(config?.seo?.siteUrl)
|
|
@@ -166,6 +198,45 @@ export function PostSectionEditor({
|
|
|
166
198
|
)
|
|
167
199
|
const publishReady = Boolean(post) && publishCheck.errors.length === 0
|
|
168
200
|
|
|
201
|
+
const railTabs = useMemo<RailTab[]>(
|
|
202
|
+
() => [
|
|
203
|
+
{
|
|
204
|
+
id: 'sections',
|
|
205
|
+
label: 'Sections',
|
|
206
|
+
title: 'Post content',
|
|
207
|
+
count: post?.sections.length ?? 0,
|
|
208
|
+
},
|
|
209
|
+
// No score dot yet. The page editor's comes from the page audit, which
|
|
210
|
+
// is page-scoped; wiring a number here from `computeLiveSeoScore` would
|
|
211
|
+
// put a different model's score on a tab, which is the disagreement the
|
|
212
|
+
// page editor was rewired to remove.
|
|
213
|
+
{ id: 'seo', label: 'SEO & GEO', title: 'Search & AI readiness' },
|
|
214
|
+
],
|
|
215
|
+
[post?.sections.length],
|
|
216
|
+
)
|
|
217
|
+
const activeRailTab = railTabs.find((tab) => tab.id === railTab) ?? railTabs[0]!
|
|
218
|
+
|
|
219
|
+
// Content the SEO pane analyses — sections plus the post body.
|
|
220
|
+
const seoContentHtml = useMemo(
|
|
221
|
+
() => buildEditorContentHtml(post?.sections ?? [], post?.body ?? ''),
|
|
222
|
+
[post?.sections, post?.body],
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
// The reflow guard's inputs, gathered in one place. Memoised because a new
|
|
226
|
+
// object identity on every render would restart the debounce in every guarded
|
|
227
|
+
// field's effect — the measurement would then never settle while typing,
|
|
228
|
+
// which is precisely when it is wanted.
|
|
229
|
+
const reflowContext = useMemo(
|
|
230
|
+
() => ({
|
|
231
|
+
canvas: canvasEl,
|
|
232
|
+
sectionId: selectedId,
|
|
233
|
+
baseline,
|
|
234
|
+
device: viewport,
|
|
235
|
+
onProof: setViewport,
|
|
236
|
+
}),
|
|
237
|
+
[canvasEl, selectedId, baseline, viewport],
|
|
238
|
+
)
|
|
239
|
+
|
|
169
240
|
const liveSeoScore = useMemo(() => {
|
|
170
241
|
if (!post) return 0
|
|
171
242
|
return computeLiveSeoScore({
|
|
@@ -210,7 +281,7 @@ export function PostSectionEditor({
|
|
|
210
281
|
load
|
|
211
282
|
.then((p) => {
|
|
212
283
|
if (cancelled) return
|
|
213
|
-
|
|
284
|
+
acceptPost(p)
|
|
214
285
|
setSelectedId(null)
|
|
215
286
|
if (!documentId) {
|
|
216
287
|
setDirty(true)
|
|
@@ -305,7 +376,7 @@ export function PostSectionEditor({
|
|
|
305
376
|
setSaveState('saving')
|
|
306
377
|
try {
|
|
307
378
|
const saved = await savePostDraft(post)
|
|
308
|
-
|
|
379
|
+
acceptPost(saved)
|
|
309
380
|
setDirty(false)
|
|
310
381
|
setSaveState('saved')
|
|
311
382
|
setValidation(null)
|
|
@@ -387,7 +458,7 @@ export function PostSectionEditor({
|
|
|
387
458
|
setPublishing(true)
|
|
388
459
|
try {
|
|
389
460
|
const published = await publishPost(post)
|
|
390
|
-
|
|
461
|
+
acceptPost(published)
|
|
391
462
|
setDirty(false)
|
|
392
463
|
setSaveState('saved')
|
|
393
464
|
setValidation(null)
|
|
@@ -406,9 +477,9 @@ export function PostSectionEditor({
|
|
|
406
477
|
if (!post?.id) return
|
|
407
478
|
fetchPostForEditor(postType, post.id)
|
|
408
479
|
.then((p) => {
|
|
409
|
-
|
|
480
|
+
acceptPost(p)
|
|
410
481
|
setSelectedId(null)
|
|
411
|
-
|
|
482
|
+
setRailTab('sections')
|
|
412
483
|
setDirty(false)
|
|
413
484
|
setSaveState('idle')
|
|
414
485
|
setValidation(null)
|
|
@@ -421,21 +492,22 @@ export function PostSectionEditor({
|
|
|
421
492
|
const handleSeoSaved = useCallback(() => {
|
|
422
493
|
if (!post?.id) return
|
|
423
494
|
fetchPostForEditor(postType, post.id)
|
|
424
|
-
.then((p) =>
|
|
495
|
+
.then((p) => acceptPost(p))
|
|
425
496
|
.catch(() => {
|
|
426
497
|
/* SEO API saved; editor meta will sync on next full load */
|
|
427
498
|
})
|
|
428
499
|
}, [postType, post?.id])
|
|
429
500
|
|
|
430
501
|
const handleToggleSeo = useCallback(() => {
|
|
431
|
-
|
|
432
|
-
if (
|
|
433
|
-
|
|
502
|
+
setRailTab((tab) => {
|
|
503
|
+
if (tab === 'seo') return 'sections'
|
|
504
|
+
setSelectedId(null)
|
|
505
|
+
return 'seo'
|
|
434
506
|
})
|
|
435
507
|
}, [])
|
|
436
508
|
|
|
437
509
|
const handleSelectSection = useCallback((id: string | null, fieldPath?: string) => {
|
|
438
|
-
|
|
510
|
+
setRailTab('sections')
|
|
439
511
|
setPendingFocusPath(fieldPath ?? null)
|
|
440
512
|
// Toggles, because selection is now disclosure — see the note on the page
|
|
441
513
|
// editor's copy. A `fieldPath` means the site preview is pointing at a
|
|
@@ -621,6 +693,11 @@ export function PostSectionEditor({
|
|
|
621
693
|
viewport={viewport}
|
|
622
694
|
backLabel="Posts"
|
|
623
695
|
onViewport={setViewport}
|
|
696
|
+
{...(sitePreview.active
|
|
697
|
+
? // The site preview is an iframe of the real front end, not a
|
|
698
|
+
// scaled canvas — a zoom percentage there would be a lie.
|
|
699
|
+
{}
|
|
700
|
+
: { zoom, canvasScale, onZoom: setZoom })}
|
|
624
701
|
onBack={() => guardedNavigate('/posts')}
|
|
625
702
|
onPreview={handlePreview}
|
|
626
703
|
onViewOnSite={post.id ? handleViewOnSite : undefined}
|
|
@@ -665,74 +742,96 @@ export function PostSectionEditor({
|
|
|
665
742
|
/>
|
|
666
743
|
)}
|
|
667
744
|
|
|
668
|
-
<
|
|
669
|
-
<
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
745
|
+
<ReflowContext.Provider value={reflowContext}>
|
|
746
|
+
<div className="prenta-editor-body relative flex flex-1 overflow-hidden">
|
|
747
|
+
<EditorRail collapsedLabel={activeRailTab.label}>
|
|
748
|
+
{({ width, dragging, collapse }) => (
|
|
749
|
+
<>
|
|
750
|
+
<EditorRailTabs tabs={railTabs} active={railTab} onChange={setRailTab} />
|
|
751
|
+
<RailHeader
|
|
752
|
+
title={activeRailTab.title}
|
|
753
|
+
width={width}
|
|
754
|
+
dragging={dragging}
|
|
755
|
+
onCollapse={collapse}
|
|
756
|
+
/>
|
|
757
|
+
|
|
758
|
+
<div
|
|
759
|
+
role="tabpanel"
|
|
760
|
+
id={`rail-panel-${railTab}`}
|
|
761
|
+
aria-labelledby={`rail-tab-${railTab}`}
|
|
762
|
+
className="flex min-h-0 flex-1 flex-col"
|
|
763
|
+
>
|
|
764
|
+
{railTab === 'sections' ? (
|
|
765
|
+
<SectionsList
|
|
766
|
+
sections={post.sections}
|
|
767
|
+
selectedId={selectedId}
|
|
768
|
+
canEdit={canEdit}
|
|
769
|
+
onSelect={handleSelectSection}
|
|
770
|
+
onToggleVisibility={handleToggleVisibility}
|
|
771
|
+
renderEditor={(section) => (
|
|
772
|
+
<SectionEditForm
|
|
773
|
+
section={section}
|
|
774
|
+
canEdit={canEdit}
|
|
775
|
+
onContentChange={handleContentChange}
|
|
776
|
+
onMetaChange={handleMetaChange}
|
|
777
|
+
postBody={post.body}
|
|
778
|
+
onPostBodyChange={handlePostBodyChange}
|
|
779
|
+
/>
|
|
780
|
+
)}
|
|
781
|
+
/>
|
|
782
|
+
) : (
|
|
783
|
+
<SeoEditorPane
|
|
784
|
+
entityType="post"
|
|
785
|
+
entityId={post.id}
|
|
786
|
+
slug={post.slug}
|
|
787
|
+
auditSections={post.sections}
|
|
788
|
+
contentHtml={seoContentHtml}
|
|
789
|
+
draftMeta={{
|
|
790
|
+
seoTitle: post.seoTitle,
|
|
791
|
+
seoDescription: post.seoDescription,
|
|
792
|
+
}}
|
|
793
|
+
onDraftChange={handleFields}
|
|
794
|
+
onClose={() => setRailTab('sections')}
|
|
795
|
+
onSaved={handleSeoSaved}
|
|
796
|
+
/>
|
|
797
|
+
)}
|
|
798
|
+
</div>
|
|
799
|
+
</>
|
|
800
|
+
)}
|
|
801
|
+
</EditorRail>
|
|
802
|
+
|
|
803
|
+
<div className="flex-1 overflow-hidden">
|
|
804
|
+
{sitePreview.active ? (
|
|
805
|
+
<SitePreviewFrame
|
|
806
|
+
src={sitePreview.url}
|
|
807
|
+
viewport={viewport}
|
|
808
|
+
reloadKey={sitePreview.reloadKey}
|
|
809
|
+
title={`Site preview: ${post.title || `New ${typeLabel}`}`}
|
|
810
|
+
selectedId={selectedId}
|
|
811
|
+
sections={post.sections}
|
|
812
|
+
suppressSectionsEcho={suppressSectionsEcho}
|
|
813
|
+
onSelectSection={handleSelectSection}
|
|
814
|
+
onCanvasEdit={handleCanvasEdit}
|
|
815
|
+
onCanvasRequestMedia={handleCanvasRequestMedia}
|
|
816
|
+
/>
|
|
817
|
+
) : (
|
|
818
|
+
<PostEditorCanvas
|
|
819
|
+
sections={post.sections}
|
|
820
|
+
header={header}
|
|
821
|
+
context={context}
|
|
822
|
+
selectedId={selectedId}
|
|
823
|
+
viewport={viewport}
|
|
824
|
+
zoom={zoom}
|
|
825
|
+
onScaleChange={setCanvasScale}
|
|
826
|
+
onCanvasElement={setCanvasEl}
|
|
827
|
+
onSelect={handleSelectSection}
|
|
828
|
+
structuralTypes={structuralTypes}
|
|
829
|
+
sitePreviewAvailable={sitePreviewAvailable && !sitePreview.active}
|
|
830
|
+
/>
|
|
831
|
+
)}
|
|
832
|
+
</div>
|
|
717
833
|
</div>
|
|
718
|
-
|
|
719
|
-
{seoOpen && (
|
|
720
|
-
<EditorSeoAside
|
|
721
|
-
entityType="post"
|
|
722
|
-
entityId={post.id}
|
|
723
|
-
title={post.title}
|
|
724
|
-
slug={post.slug}
|
|
725
|
-
seoTitle={post.seoTitle}
|
|
726
|
-
seoDescription={post.seoDescription}
|
|
727
|
-
sections={post.sections}
|
|
728
|
-
bodyHtml={post.body}
|
|
729
|
-
canEdit={canEdit}
|
|
730
|
-
onClose={() => setSeoOpen(false)}
|
|
731
|
-
onDraftChange={handleFields}
|
|
732
|
-
onSaved={handleSeoSaved}
|
|
733
|
-
/>
|
|
734
|
-
)}
|
|
735
|
-
</div>
|
|
834
|
+
</ReflowContext.Provider>
|
|
736
835
|
</div>
|
|
737
836
|
|
|
738
837
|
{post.id && (
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { PageSection } from '@prenta/core/page-sections';
|
|
2
|
-
export interface EditorSeoAsideProps {
|
|
3
|
-
entityType: 'page' | 'post';
|
|
4
|
-
/** Saved document id — when absent, draft mode with live analysis. */
|
|
5
|
-
entityId: string | null;
|
|
6
|
-
title: string;
|
|
7
|
-
slug: string;
|
|
8
|
-
seoTitle: string;
|
|
9
|
-
seoDescription: string;
|
|
10
|
-
/** Post body HTML and/or section content for live SEO analysis. */
|
|
11
|
-
sections?: PageSection[];
|
|
12
|
-
bodyHtml?: string;
|
|
13
|
-
canEdit: boolean;
|
|
14
|
-
onClose: () => void;
|
|
15
|
-
onDraftChange?: (patch: {
|
|
16
|
-
seoTitle?: string;
|
|
17
|
-
seoDescription?: string;
|
|
18
|
-
}) => void;
|
|
19
|
-
onSaved?: () => void;
|
|
20
|
-
}
|
|
21
|
-
/** Right-side SEO panel for the Page / Post section editors. */
|
|
22
|
-
export declare function EditorSeoAside({ entityType, entityId, slug, seoTitle, seoDescription, sections, bodyHtml, onClose, onDraftChange, onSaved, }: EditorSeoAsideProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
-
//# sourceMappingURL=EditorSeoAside.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EditorSeoAside.d.ts","sourceRoot":"","sources":["../../src/components/EditorSeoAside.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAI7D,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;IAC/E,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,EAC7B,UAAU,EACV,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,QAAa,EACb,QAAa,EACb,OAAO,EACP,aAAa,EACb,OAAO,GACR,EAAE,mBAAmB,2CA4BrB"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useMemo } from 'react';
|
|
4
|
-
import { SeoEditorPane } from './seo/SeoEditorPane.js';
|
|
5
|
-
import { ResizableAside } from './ui/ResizableAside.js';
|
|
6
|
-
import { buildEditorContentHtml } from '../lib/editor-seo-content.js';
|
|
7
|
-
const INSPECTOR_SHELL = 'bg-card border-border flex h-full flex-col border-l';
|
|
8
|
-
/** Right-side SEO panel for the Page / Post section editors. */
|
|
9
|
-
export function EditorSeoAside({ entityType, entityId, slug, seoTitle, seoDescription, sections = [], bodyHtml = '', onClose, onDraftChange, onSaved, }) {
|
|
10
|
-
const contentHtml = useMemo(() => buildEditorContentHtml(sections, bodyHtml), [sections, bodyHtml]);
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
function onKey(e) {
|
|
13
|
-
if (e.key === 'Escape')
|
|
14
|
-
onClose();
|
|
15
|
-
}
|
|
16
|
-
document.addEventListener('keydown', onKey);
|
|
17
|
-
return () => document.removeEventListener('keydown', onKey);
|
|
18
|
-
}, [onClose]);
|
|
19
|
-
return (_jsx(ResizableAside, { role: "dialog", "aria-label": "SEO settings", className: INSPECTOR_SHELL, children: _jsx(SeoEditorPane, { entityType: entityType, entityId: entityId, slug: slug, contentHtml: contentHtml, draftMeta: { seoTitle, seoDescription }, onDraftChange: onDraftChange, onClose: onClose, onSaved: onSaved }) }));
|
|
20
|
-
}
|
|
21
|
-
//# sourceMappingURL=EditorSeoAside.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EditorSeoAside.js","sourceRoot":"","sources":["../../src/components/EditorSeoAside.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAGrE,MAAM,eAAe,GAAG,qDAAqD,CAAA;AAmB7E,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,EAC7B,UAAU,EACV,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,QAAQ,GAAG,EAAE,EACb,QAAQ,GAAG,EAAE,EACb,OAAO,EACP,aAAa,EACb,OAAO,GACa;IACpB,MAAM,WAAW,GAAG,OAAO,CACzB,GAAG,EAAE,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAChD,CAAC,QAAQ,EAAE,QAAQ,CAAC,CACrB,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,KAAK,CAAC,CAAgB;YAC7B,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;gBAAE,OAAO,EAAE,CAAA;QACnC,CAAC;QACD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QAC3C,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAC7D,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEb,OAAO,CACL,KAAC,cAAc,IAAC,IAAI,EAAC,QAAQ,gBAAY,cAAc,EAAC,SAAS,EAAE,eAAe,YAChF,KAAC,aAAa,IACZ,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,EACvC,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,GAChB,GACa,CAClB,CAAA;AACH,CAAC"}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useMemo } from 'react'
|
|
4
|
-
import { SeoEditorPane } from './seo/SeoEditorPane.js'
|
|
5
|
-
import { ResizableAside } from './ui/ResizableAside.js'
|
|
6
|
-
import { buildEditorContentHtml } from '../lib/editor-seo-content.js'
|
|
7
|
-
import type { PageSection } from '@prenta/core/page-sections'
|
|
8
|
-
|
|
9
|
-
const INSPECTOR_SHELL = 'bg-card border-border flex h-full flex-col border-l'
|
|
10
|
-
|
|
11
|
-
export interface EditorSeoAsideProps {
|
|
12
|
-
entityType: 'page' | 'post'
|
|
13
|
-
/** Saved document id — when absent, draft mode with live analysis. */
|
|
14
|
-
entityId: string | null
|
|
15
|
-
title: string
|
|
16
|
-
slug: string
|
|
17
|
-
seoTitle: string
|
|
18
|
-
seoDescription: string
|
|
19
|
-
/** Post body HTML and/or section content for live SEO analysis. */
|
|
20
|
-
sections?: PageSection[]
|
|
21
|
-
bodyHtml?: string
|
|
22
|
-
canEdit: boolean
|
|
23
|
-
onClose: () => void
|
|
24
|
-
onDraftChange?: (patch: { seoTitle?: string; seoDescription?: string }) => void
|
|
25
|
-
onSaved?: () => void
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Right-side SEO panel for the Page / Post section editors. */
|
|
29
|
-
export function EditorSeoAside({
|
|
30
|
-
entityType,
|
|
31
|
-
entityId,
|
|
32
|
-
slug,
|
|
33
|
-
seoTitle,
|
|
34
|
-
seoDescription,
|
|
35
|
-
sections = [],
|
|
36
|
-
bodyHtml = '',
|
|
37
|
-
onClose,
|
|
38
|
-
onDraftChange,
|
|
39
|
-
onSaved,
|
|
40
|
-
}: EditorSeoAsideProps) {
|
|
41
|
-
const contentHtml = useMemo(
|
|
42
|
-
() => buildEditorContentHtml(sections, bodyHtml),
|
|
43
|
-
[sections, bodyHtml],
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
function onKey(e: KeyboardEvent) {
|
|
48
|
-
if (e.key === 'Escape') onClose()
|
|
49
|
-
}
|
|
50
|
-
document.addEventListener('keydown', onKey)
|
|
51
|
-
return () => document.removeEventListener('keydown', onKey)
|
|
52
|
-
}, [onClose])
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<ResizableAside role="dialog" aria-label="SEO settings" className={INSPECTOR_SHELL}>
|
|
56
|
-
<SeoEditorPane
|
|
57
|
-
entityType={entityType}
|
|
58
|
-
entityId={entityId}
|
|
59
|
-
slug={slug}
|
|
60
|
-
contentHtml={contentHtml}
|
|
61
|
-
draftMeta={{ seoTitle, seoDescription }}
|
|
62
|
-
onDraftChange={onDraftChange}
|
|
63
|
-
onClose={onClose}
|
|
64
|
-
onSaved={onSaved}
|
|
65
|
-
/>
|
|
66
|
-
</ResizableAside>
|
|
67
|
-
)
|
|
68
|
-
}
|