@prenta/admin 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/__tests__/components/request-changes-dialog.render.test.d.ts +2 -0
- package/dist/__tests__/components/request-changes-dialog.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/request-changes-dialog.render.test.js +12 -0
- package/dist/__tests__/components/request-changes-dialog.render.test.js.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.js +10 -0
- package/dist/__tests__/lib/page-audit-input.test.js.map +1 -1
- package/dist/__tests__/lib/page-editor-service-api.test.js +28 -15
- package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
- package/dist/__tests__/lib/post-editor-service-api.test.d.ts +2 -0
- package/dist/__tests__/lib/post-editor-service-api.test.d.ts.map +1 -0
- package/dist/__tests__/lib/post-editor-service-api.test.js +68 -0
- package/dist/__tests__/lib/post-editor-service-api.test.js.map +1 -0
- package/dist/__tests__/lib/post-editor-service.test.js +27 -6
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/version-history.test.js +27 -0
- package/dist/__tests__/lib/version-history.test.js.map +1 -1
- package/dist/__tests__/lib/workflow-chrome.test.d.ts +2 -0
- package/dist/__tests__/lib/workflow-chrome.test.d.ts.map +1 -0
- package/dist/__tests__/lib/workflow-chrome.test.js +108 -0
- package/dist/__tests__/lib/workflow-chrome.test.js.map +1 -0
- package/dist/__tests__/views/editor-top-bar.render.test.js +40 -33
- package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
- package/dist/__tests__/views/page-section-editor.test.js +8 -0
- package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
- package/dist/__tests__/views/page-tab.render.test.js +2 -0
- package/dist/__tests__/views/page-tab.render.test.js.map +1 -1
- package/dist/__tests__/views/post-fields-modal.render.test.js +21 -0
- package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -1
- package/dist/__tests__/views/post-section-editor.test.js +3 -0
- package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
- package/dist/__tests__/views/post-tab.render.test.js +3 -0
- package/dist/__tests__/views/post-tab.render.test.js.map +1 -1
- package/dist/components/RequestChangesDialog.d.ts +6 -0
- package/dist/components/RequestChangesDialog.d.ts.map +1 -0
- package/dist/components/RequestChangesDialog.js +15 -0
- package/dist/components/RequestChangesDialog.js.map +1 -0
- package/dist/lib/page-audit-input.js +1 -1
- package/dist/lib/page-audit-input.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts +6 -2
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +16 -12
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/lib/post-editor-service.d.ts +24 -4
- package/dist/lib/post-editor-service.d.ts.map +1 -1
- package/dist/lib/post-editor-service.js +49 -24
- package/dist/lib/post-editor-service.js.map +1 -1
- package/dist/lib/version-history.d.ts.map +1 -1
- package/dist/lib/version-history.js +3 -9
- package/dist/lib/version-history.js.map +1 -1
- package/dist/lib/workflow-chrome.d.ts +22 -0
- package/dist/lib/workflow-chrome.d.ts.map +1 -0
- package/dist/lib/workflow-chrome.js +80 -0
- package/dist/lib/workflow-chrome.js.map +1 -0
- package/dist/prenta-admin.css +1 -1
- package/dist/views/page-editor/EditorTopBar.d.ts +10 -5
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +38 -28
- 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 +83 -62
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/post-editor/PostFieldsForm.d.ts +3 -0
- package/dist/views/post-editor/PostFieldsForm.d.ts.map +1 -1
- package/dist/views/post-editor/PostFieldsForm.js +7 -2
- package/dist/views/post-editor/PostFieldsForm.js.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.js +15 -2
- package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +81 -65
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/post-editor/PostTab.d.ts.map +1 -1
- package/dist/views/post-editor/PostTab.js +1 -0
- package/dist/views/post-editor/PostTab.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/components/request-changes-dialog.render.test.tsx +11 -0
- package/src/__tests__/lib/page-audit-input.test.ts +11 -0
- package/src/__tests__/lib/page-editor-service-api.test.ts +37 -18
- package/src/__tests__/lib/post-editor-service-api.test.ts +74 -0
- package/src/__tests__/lib/post-editor-service.test.ts +28 -9
- package/src/__tests__/lib/version-history.test.ts +29 -0
- package/src/__tests__/lib/workflow-chrome.test.ts +125 -0
- package/src/__tests__/views/editor-top-bar.render.test.tsx +42 -45
- package/src/__tests__/views/page-section-editor.test.tsx +18 -0
- package/src/__tests__/views/page-tab.render.test.tsx +2 -0
- package/src/__tests__/views/post-fields-modal.render.test.tsx +41 -0
- package/src/__tests__/views/post-section-editor.test.tsx +3 -0
- package/src/__tests__/views/post-tab.render.test.tsx +3 -0
- package/src/components/RequestChangesDialog.tsx +56 -0
- package/src/lib/page-audit-input.ts +1 -1
- package/src/lib/page-editor-service.ts +26 -11
- package/src/lib/post-editor-service.ts +71 -20
- package/src/lib/version-history.ts +3 -7
- package/src/lib/workflow-chrome.ts +105 -0
- package/src/views/page-editor/EditorTopBar.tsx +110 -48
- package/src/views/page-editor/PageSectionEditor.tsx +121 -92
- package/src/views/post-editor/PostFieldsForm.tsx +14 -5
- package/src/views/post-editor/PostFieldsModal.tsx +15 -2
- package/src/views/post-editor/PostSectionEditor.tsx +114 -91
- package/src/views/post-editor/PostTab.tsx +1 -0
|
@@ -25,9 +25,9 @@ import {
|
|
|
25
25
|
fetchPostForEditor,
|
|
26
26
|
fetchTemplateForEditor,
|
|
27
27
|
missingPostFields,
|
|
28
|
-
publishPost,
|
|
29
28
|
savePostDraft,
|
|
30
29
|
toggleSectionVisibility,
|
|
30
|
+
transitionPostWorkflow,
|
|
31
31
|
updateSectionContent,
|
|
32
32
|
updateSectionMeta,
|
|
33
33
|
validatePost,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type PostHeaderConfig,
|
|
36
36
|
type ValidationResult,
|
|
37
37
|
} from '../../lib/post-editor-service.js'
|
|
38
|
+
import { resolveWorkflowChrome, type WorkflowStage } from '../../lib/workflow-chrome.js'
|
|
38
39
|
import { SharePreviewLinkDialog } from '../../components/SharePreviewLinkDialog.js'
|
|
39
40
|
import { defaultPostHeader } from '@prenta/core/page-sections'
|
|
40
41
|
import { openPublicSiteTab } from '../../lib/open-public-site-tab.js'
|
|
@@ -160,7 +161,6 @@ export function PostSectionEditor({
|
|
|
160
161
|
* audit so the header chip and tab dot always have a number.
|
|
161
162
|
*/
|
|
162
163
|
const [liveAudit, setLiveAudit] = useState<PageAuditResult | null>(null)
|
|
163
|
-
const [confirmPublish, setConfirmPublish] = useState(false)
|
|
164
164
|
const [scheduleOpen, setScheduleOpen] = useState(false)
|
|
165
165
|
const [shareOpen, setShareOpen] = useState(false)
|
|
166
166
|
const [leaveTarget, setLeaveTarget] = useState<string | null>(null)
|
|
@@ -180,7 +180,8 @@ export function PostSectionEditor({
|
|
|
180
180
|
setBaseline(snapshotBaseline(p.sections, p.status))
|
|
181
181
|
}, [])
|
|
182
182
|
|
|
183
|
-
const { canEdit
|
|
183
|
+
const { canEdit } = useMemo(() => deriveCan(session), [session])
|
|
184
|
+
const sessionRole = String(session?.user?.role ?? '')
|
|
184
185
|
const customRenderers = useAdminSectionRenderers()
|
|
185
186
|
const {
|
|
186
187
|
siteUrl: publicSiteUrl,
|
|
@@ -225,7 +226,7 @@ export function PostSectionEditor({
|
|
|
225
226
|
post: {
|
|
226
227
|
title: post?.title,
|
|
227
228
|
bodyHtml: post?.body,
|
|
228
|
-
featuredImage: post?.
|
|
229
|
+
featuredImage: post?.featuredImageUrl,
|
|
229
230
|
},
|
|
230
231
|
})
|
|
231
232
|
const audit = liveAudit ?? savedAudit
|
|
@@ -337,7 +338,7 @@ export function PostSectionEditor({
|
|
|
337
338
|
title: post?.title,
|
|
338
339
|
excerpt: post?.excerpt,
|
|
339
340
|
body: post?.body,
|
|
340
|
-
featuredImageUrl: post?.
|
|
341
|
+
featuredImageUrl: post?.featuredImageUrl,
|
|
341
342
|
publishDate: post?.publishDate ?? post?.updatedAt ?? null,
|
|
342
343
|
category: post?.category,
|
|
343
344
|
author: { name: session?.user?.name || session?.user?.email },
|
|
@@ -391,61 +392,36 @@ export function PostSectionEditor({
|
|
|
391
392
|
}, [])
|
|
392
393
|
|
|
393
394
|
// ─── Save / publish ──────────────────────────────────────────────────
|
|
394
|
-
const handleSaveDraft = useCallback(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (result.errors.length > 0) {
|
|
425
|
-
toast.error('Resolve the listed issues before publishing')
|
|
426
|
-
return
|
|
427
|
-
}
|
|
428
|
-
setConfirmPublish(true)
|
|
429
|
-
}, [post])
|
|
430
|
-
|
|
431
|
-
const requestSchedule = useCallback(async () => {
|
|
432
|
-
if (!post) return
|
|
433
|
-
if (!post.id) {
|
|
434
|
-
toast.error('Save the post before scheduling')
|
|
435
|
-
return
|
|
436
|
-
}
|
|
437
|
-
const result = validatePost(post, true)
|
|
438
|
-
setValidation(result.errors.length || result.warnings.length ? result : null)
|
|
439
|
-
if (result.errors.length > 0) {
|
|
440
|
-
toast.error('Resolve the listed issues before scheduling')
|
|
441
|
-
return
|
|
442
|
-
}
|
|
443
|
-
if (dirty) {
|
|
444
|
-
const ok = await handleSaveDraft()
|
|
445
|
-
if (!ok) return
|
|
446
|
-
}
|
|
447
|
-
setScheduleOpen(true)
|
|
448
|
-
}, [post, dirty, handleSaveDraft])
|
|
395
|
+
const handleSaveDraft = useCallback(
|
|
396
|
+
async (options?: { navigate?: boolean }): Promise<EditorPost | false> => {
|
|
397
|
+
if (!post) return false
|
|
398
|
+
const result = validatePost(post, false)
|
|
399
|
+
if (result.errors.length > 0) {
|
|
400
|
+
setValidation(result)
|
|
401
|
+
toast.error(result.errors[0]!)
|
|
402
|
+
setFieldsOpen(true)
|
|
403
|
+
return false
|
|
404
|
+
}
|
|
405
|
+
setSaveState('saving')
|
|
406
|
+
try {
|
|
407
|
+
const saved = await savePostDraft(post)
|
|
408
|
+
acceptPost(saved)
|
|
409
|
+
setDirty(false)
|
|
410
|
+
setSaveState('saved')
|
|
411
|
+
setValidation(null)
|
|
412
|
+
toast.success('Draft saved')
|
|
413
|
+
if ((options?.navigate ?? true) && !documentId && saved.id) {
|
|
414
|
+
onNavigate(`/posts/${postType}/${saved.id}/edit`)
|
|
415
|
+
}
|
|
416
|
+
return saved
|
|
417
|
+
} catch (err) {
|
|
418
|
+
setSaveState('error')
|
|
419
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save draft')
|
|
420
|
+
return false
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
[post, documentId, postType, onNavigate, acceptPost],
|
|
424
|
+
)
|
|
449
425
|
|
|
450
426
|
const handleScheduled = useCallback(
|
|
451
427
|
(next: { status: string; scheduledAt: string | null; scheduledUnpublishAt: string | null }) => {
|
|
@@ -465,24 +441,6 @@ export function PostSectionEditor({
|
|
|
465
441
|
[],
|
|
466
442
|
)
|
|
467
443
|
|
|
468
|
-
const doPublish = useCallback(async () => {
|
|
469
|
-
if (!post) return
|
|
470
|
-
setPublishing(true)
|
|
471
|
-
try {
|
|
472
|
-
const published = await publishPost(post)
|
|
473
|
-
acceptPost(published)
|
|
474
|
-
setDirty(false)
|
|
475
|
-
setSaveState('saved')
|
|
476
|
-
setValidation(null)
|
|
477
|
-
toast.success('Post published')
|
|
478
|
-
if (!documentId && published.id) onNavigate(`/posts/${postType}/${published.id}/edit`)
|
|
479
|
-
} catch (err) {
|
|
480
|
-
toast.error(err instanceof Error ? err.message : 'Failed to publish post')
|
|
481
|
-
} finally {
|
|
482
|
-
setPublishing(false)
|
|
483
|
-
}
|
|
484
|
-
}, [post, documentId, postType, onNavigate])
|
|
485
|
-
|
|
486
444
|
// ─── Version history ─────────────────────────────────────────────────
|
|
487
445
|
// History is a MODE, not a dialog: it overlays the rail and drives the
|
|
488
446
|
// canvas, so the version being reviewed is editor state rather than
|
|
@@ -555,6 +513,76 @@ export function PostSectionEditor({
|
|
|
555
513
|
const effectiveCanEdit = canEdit && !reviewing
|
|
556
514
|
const canRestoreVersion = canEdit
|
|
557
515
|
|
|
516
|
+
const runWorkflow = useCallback(
|
|
517
|
+
async (stage: WorkflowStage, note?: string, toastOk?: string) => {
|
|
518
|
+
if (!post) return
|
|
519
|
+
let current = post
|
|
520
|
+
if (dirty || !current.id) {
|
|
521
|
+
const saved = await handleSaveDraft({ navigate: false })
|
|
522
|
+
if (!saved) return
|
|
523
|
+
current = saved
|
|
524
|
+
}
|
|
525
|
+
setPublishing(true)
|
|
526
|
+
try {
|
|
527
|
+
const next = await transitionPostWorkflow(current, stage, note)
|
|
528
|
+
acceptPost(next)
|
|
529
|
+
if (!documentId && next.id) onNavigate(`/posts/${postType}/${next.id}/edit`)
|
|
530
|
+
if (toastOk) toast.success(toastOk)
|
|
531
|
+
} catch (err) {
|
|
532
|
+
toast.error(err instanceof Error ? err.message : 'Transition failed')
|
|
533
|
+
} finally {
|
|
534
|
+
setPublishing(false)
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
[post, dirty, documentId, postType, handleSaveDraft, acceptPost, onNavigate],
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
const handleSubmitForReview = useCallback(
|
|
541
|
+
() => runWorkflow('IN_REVIEW', undefined, 'Submitted for review.'),
|
|
542
|
+
[runWorkflow],
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
const handleApprove = useCallback(() => {
|
|
546
|
+
if (!post) return
|
|
547
|
+
const result = validatePost(post, true)
|
|
548
|
+
if (result.errors.length > 0) {
|
|
549
|
+
toast.error('Resolve the listed issues before publishing')
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
void runWorkflow('PUBLISHED', undefined, 'Published.')
|
|
553
|
+
}, [post, runWorkflow])
|
|
554
|
+
|
|
555
|
+
const handleRequestChanges = useCallback(
|
|
556
|
+
(note: string) => runWorkflow('DRAFT', note || undefined, 'Sent back to draft.'),
|
|
557
|
+
[runWorkflow],
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
const requestSchedule = useCallback(async () => {
|
|
561
|
+
if (!post) return
|
|
562
|
+
if (!post.id) {
|
|
563
|
+
toast.error('Save the post before scheduling')
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
const result = validatePost(post, true)
|
|
567
|
+
setValidation(result.errors.length || result.warnings.length ? result : null)
|
|
568
|
+
if (result.errors.length > 0) {
|
|
569
|
+
toast.error('Resolve the listed issues before scheduling')
|
|
570
|
+
return
|
|
571
|
+
}
|
|
572
|
+
if (dirty) {
|
|
573
|
+
const ok = await handleSaveDraft()
|
|
574
|
+
if (!ok) return
|
|
575
|
+
}
|
|
576
|
+
const chrome = resolveWorkflowChrome({
|
|
577
|
+
stage: post.workflowStage,
|
|
578
|
+
status: post.status,
|
|
579
|
+
role: sessionRole,
|
|
580
|
+
canEdit: effectiveCanEdit,
|
|
581
|
+
})
|
|
582
|
+
if (!chrome.showSchedule) return
|
|
583
|
+
setScheduleOpen(true)
|
|
584
|
+
}, [post, dirty, handleSaveDraft, sessionRole, effectiveCanEdit])
|
|
585
|
+
|
|
558
586
|
const handleSeoSaved = useCallback(() => {
|
|
559
587
|
if (!post?.id) return
|
|
560
588
|
fetchPostForEditor(postType, post.id)
|
|
@@ -616,7 +644,7 @@ export function PostSectionEditor({
|
|
|
616
644
|
data: { path: (post as { path?: string }).path },
|
|
617
645
|
trailingSlash,
|
|
618
646
|
dirty,
|
|
619
|
-
saveDraft: handleSaveDraft,
|
|
647
|
+
saveDraft: async () => Boolean(await handleSaveDraft()),
|
|
620
648
|
}).catch((err) => {
|
|
621
649
|
toast.error(err instanceof Error ? err.message : 'Failed to open the site')
|
|
622
650
|
})
|
|
@@ -688,11 +716,13 @@ export function PostSectionEditor({
|
|
|
688
716
|
<EditorTopBar
|
|
689
717
|
title={post.title || `New ${typeLabel}`}
|
|
690
718
|
status={post.status}
|
|
719
|
+
workflowStage={post.workflowStage}
|
|
720
|
+
reviewNote={post.reviewNote}
|
|
721
|
+
role={sessionRole}
|
|
691
722
|
dirty={dirty}
|
|
692
723
|
saveState={saveState}
|
|
693
724
|
publishing={publishing}
|
|
694
725
|
canEdit={effectiveCanEdit}
|
|
695
|
-
canPublish={canPublish && effectiveCanEdit}
|
|
696
726
|
publishReady={publishReady}
|
|
697
727
|
publishDisabledReason={publishDisabledReason}
|
|
698
728
|
viewport={viewport}
|
|
@@ -716,7 +746,9 @@ export function PostSectionEditor({
|
|
|
716
746
|
typeof audit?.seo.score === 'number' ? scoreBand(audit.seo.score) : undefined
|
|
717
747
|
}
|
|
718
748
|
onSaveDraft={handleSaveDraft}
|
|
719
|
-
|
|
749
|
+
onSubmitForReview={handleSubmitForReview}
|
|
750
|
+
onApprove={handleApprove}
|
|
751
|
+
onRequestChanges={handleRequestChanges}
|
|
720
752
|
onSchedule={post.id ? requestSchedule : undefined}
|
|
721
753
|
scheduleReady={Boolean(post.id)}
|
|
722
754
|
scheduledAt={post.scheduledAt}
|
|
@@ -809,7 +841,7 @@ export function PostSectionEditor({
|
|
|
809
841
|
contentHtml={seoContentHtml}
|
|
810
842
|
title={post.title}
|
|
811
843
|
bodyHtml={post.body}
|
|
812
|
-
featuredImage={post.
|
|
844
|
+
featuredImage={post.featuredImageUrl}
|
|
813
845
|
draftMeta={{
|
|
814
846
|
seoTitle: post.seoTitle,
|
|
815
847
|
seoDescription: post.seoDescription,
|
|
@@ -889,15 +921,6 @@ export function PostSectionEditor({
|
|
|
889
921
|
onSave={handleFields}
|
|
890
922
|
/>
|
|
891
923
|
|
|
892
|
-
<ConfirmDialog
|
|
893
|
-
open={confirmPublish}
|
|
894
|
-
onClose={() => setConfirmPublish(false)}
|
|
895
|
-
onConfirm={doPublish}
|
|
896
|
-
title="Publish this post?"
|
|
897
|
-
description="The current content will go live and replace the published version."
|
|
898
|
-
confirmLabel="Publish"
|
|
899
|
-
/>
|
|
900
|
-
|
|
901
924
|
{post.id && (
|
|
902
925
|
<SchedulePublishDialog
|
|
903
926
|
collectionSlug={postType}
|