@prenta/admin 1.17.12 → 1.17.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/__tests__/lib/post-canvas-context.test.d.ts +2 -0
  3. package/dist/__tests__/lib/post-canvas-context.test.d.ts.map +1 -0
  4. package/dist/__tests__/lib/post-canvas-context.test.js +40 -0
  5. package/dist/__tests__/lib/post-canvas-context.test.js.map +1 -0
  6. package/dist/__tests__/lib/post-editor-service-api.test.js +2 -0
  7. package/dist/__tests__/lib/post-editor-service-api.test.js.map +1 -1
  8. package/dist/__tests__/lib/post-editor-service.test.js +21 -0
  9. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
  10. package/dist/__tests__/views/post-fields-modal.render.test.js +2 -0
  11. package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -1
  12. package/dist/__tests__/views/post-section-editor.test.js +2 -0
  13. package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
  14. package/dist/__tests__/views/post-tab.render.test.js +2 -0
  15. package/dist/__tests__/views/post-tab.render.test.js.map +1 -1
  16. package/dist/lib/post-canvas-context.d.ts +38 -0
  17. package/dist/lib/post-canvas-context.d.ts.map +1 -0
  18. package/dist/lib/post-canvas-context.js +65 -0
  19. package/dist/lib/post-canvas-context.js.map +1 -0
  20. package/dist/lib/post-editor-service.d.ts +8 -0
  21. package/dist/lib/post-editor-service.d.ts.map +1 -1
  22. package/dist/lib/post-editor-service.js +5 -0
  23. package/dist/lib/post-editor-service.js.map +1 -1
  24. package/dist/views/post-editor/PostEditorCanvas.js +1 -1
  25. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  26. package/dist/views/post-editor/PostHeader.d.ts +1 -1
  27. package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
  28. package/dist/views/post-editor/PostHeader.js +2 -0
  29. package/dist/views/post-editor/PostHeader.js.map +1 -1
  30. package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -1
  31. package/dist/views/post-editor/PostHeaderPanel.js +2 -2
  32. package/dist/views/post-editor/PostHeaderPanel.js.map +1 -1
  33. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  34. package/dist/views/post-editor/PostSectionEditor.js +31 -2
  35. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  36. package/package.json +6 -6
  37. package/src/__tests__/lib/post-canvas-context.test.ts +56 -0
  38. package/src/__tests__/lib/post-editor-service-api.test.ts +2 -0
  39. package/src/__tests__/lib/post-editor-service.test.ts +22 -0
  40. package/src/__tests__/views/post-fields-modal.render.test.tsx +2 -0
  41. package/src/__tests__/views/post-section-editor.test.tsx +2 -0
  42. package/src/__tests__/views/post-tab.render.test.tsx +2 -0
  43. package/src/lib/post-canvas-context.ts +80 -0
  44. package/src/lib/post-editor-service.ts +12 -0
  45. package/src/views/post-editor/PostEditorCanvas.tsx +1 -1
  46. package/src/views/post-editor/PostHeader.tsx +1 -0
  47. package/src/views/post-editor/PostHeaderPanel.tsx +30 -22
  48. package/src/views/post-editor/PostSectionEditor.tsx +46 -2
@@ -20,6 +20,7 @@
20
20
  * so the field-access layer doesn't strip them on write.
21
21
  */
22
22
  import { cmsApi } from './api.js'
23
+ import { relatedPostsFromField, type CanvasRelatedPost } from './post-canvas-context.js'
23
24
  import { missingCollectionFields } from './collection-schema.js'
24
25
  import { SUBMIT_BEFORE_PUBLISH_ERROR, type WorkflowStage } from './workflow-chrome.js'
25
26
  import {
@@ -75,6 +76,13 @@ export interface EditorPost {
75
76
  featuredImageUrl: string
76
77
  body: string
77
78
  category: string
79
+ /** Display byline. Empty when the document has no `authorName`. */
80
+ authorName: string
81
+ /**
82
+ * Hand-picked related posts ready for the canvas. Null when the field is
83
+ * absent or only ids, so the editor loads recent published siblings instead.
84
+ */
85
+ canvasRelatedPosts: CanvasRelatedPost[] | null
78
86
  /** Editorial tags as plain strings (stored as `[{ tag }]` rows, see {@link parseTags}). */
79
87
  tags: string[]
80
88
  status: PostStatus
@@ -297,6 +305,8 @@ function rowToEditorPost(postType: string, doc: DocumentApiRow): EditorPost {
297
305
  featuredImageUrl: featured.url,
298
306
  body: dataStr(data, 'body'),
299
307
  category: dataStr(data, 'category'),
308
+ authorName: dataStr(data, 'authorName'),
309
+ canvasRelatedPosts: relatedPostsFromField(data.relatedPosts),
300
310
  tags: parseTags(data.tags),
301
311
  status: doc.status ?? 'DRAFT',
302
312
  workflowStage: (doc.workflowStage as WorkflowStage | undefined) ?? 'DRAFT',
@@ -491,6 +501,8 @@ export async function emptyPostFromTemplate(postType: string): Promise<EditorPos
491
501
  featuredImageUrl: '',
492
502
  body: '',
493
503
  category: '',
504
+ authorName: '',
505
+ canvasRelatedPosts: null,
494
506
  tags: [],
495
507
  status: 'DRAFT',
496
508
  workflowStage: 'DRAFT',
@@ -125,7 +125,7 @@ function EmptyBody({ onAddSection }: { onAddSection?: () => void }) {
125
125
  </span>
126
126
  <p className="text-muted-foreground text-sm">
127
127
  {onAddSection
128
- ? 'The header is set by the template. Add body sections to build out this post. Hover sections for a light outline, click to open its editor in the section list.'
128
+ ? 'Add body sections to build out this post. Hover sections for a light outline, click to open its editor in the section list.'
129
129
  : 'This post’s template renders no body sections. Layout is design-owned — sections come from the template in code. Pick a different template in Post settings, or edit the template itself.'}
130
130
  </p>
131
131
  {onAddSection && (
@@ -21,6 +21,7 @@ function formatDate(value?: string | null): string {
21
21
  * public preview match.
22
22
  */
23
23
  export function PostHeader({ config, context }: PostHeaderProps) {
24
+ if (config.layout === 'none') return null
24
25
  const title = context.title || 'Untitled post'
25
26
  const date = config.showDate ? formatDate(context.publishDate) : ''
26
27
  const overlay =
@@ -69,32 +69,40 @@ export function PostHeaderPanel({ header, canEdit, onClose, onChange }: PostHead
69
69
  <option value="centered">Centered</option>
70
70
  <option value="left">Left-aligned</option>
71
71
  <option value="overlay">Image overlay</option>
72
+ <option value="none">None</option>
72
73
  </select>
74
+ {header.layout === 'none' && (
75
+ <p className="text-muted-foreground mt-1 text-xs">
76
+ The built-in header is hidden. Put the title in a section instead.
77
+ </p>
78
+ )}
73
79
  </div>
74
80
  </fieldset>
75
81
 
76
- <fieldset disabled={!canEdit} className="space-y-2">
77
- <legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
78
- Show in header
79
- </legend>
80
- {TOGGLES.map((t) => {
81
- const value = header[t.key] === true
82
- return (
83
- <div
84
- key={t.key}
85
- className="border-border text-foreground flex w-full items-center justify-between rounded-md border px-2.5 py-1.5 text-sm"
86
- >
87
- <span>{t.label}</span>
88
- <Toggle
89
- checked={value}
90
- disabled={!canEdit}
91
- onCheckedChange={(v) => onChange({ [t.key]: v } as Partial<PostHeaderConfig>)}
92
- aria-label={t.label}
93
- />
94
- </div>
95
- )
96
- })}
97
- </fieldset>
82
+ {header.layout !== 'none' && (
83
+ <fieldset disabled={!canEdit} className="space-y-2">
84
+ <legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
85
+ Show in header
86
+ </legend>
87
+ {TOGGLES.map((t) => {
88
+ const value = header[t.key] === true
89
+ return (
90
+ <div
91
+ key={t.key}
92
+ className="border-border text-foreground flex w-full items-center justify-between rounded-md border px-2.5 py-1.5 text-sm"
93
+ >
94
+ <span>{t.label}</span>
95
+ <Toggle
96
+ checked={value}
97
+ disabled={!canEdit}
98
+ onCheckedChange={(v) => onChange({ [t.key]: v } as Partial<PostHeaderConfig>)}
99
+ aria-label={t.label}
100
+ />
101
+ </div>
102
+ )
103
+ })}
104
+ </fieldset>
105
+ )}
98
106
  </div>
99
107
  </ResizableAside>
100
108
  )
@@ -55,6 +55,12 @@ import { buildEditorContentHtml } from '../../lib/editor-seo-content.js'
55
55
  import { snapshotBaseline, type ReflowBaseline } from '../../lib/reflow-baseline.js'
56
56
  import { ReflowContext } from '../page-editor/reflow-context.js'
57
57
  import type { Zoom } from '../../lib/breakpoints.js'
58
+ import { cmsApi } from '../../lib/api.js'
59
+ import {
60
+ canvasAuthorName,
61
+ relatedPostsFromSiblings,
62
+ type CanvasRelatedPost,
63
+ } from '../../lib/post-canvas-context.js'
58
64
  import { inspectorIdFromFieldPath } from '../../lib/preview-bridge.js'
59
65
  import { PostEditorCanvas } from './PostEditorCanvas.js'
60
66
  import { PostFieldsModal, type PostFieldConfig } from './PostFieldsModal.js'
@@ -333,6 +339,43 @@ export function PostSectionEditor({
333
339
  // ─── Unsaved-changes guard (client-side navigation + browser unload) ──
334
340
  useNavigationBlocker(dirty)
335
341
 
342
+ const [siblingPosts, setSiblingPosts] = useState<CanvasRelatedPost[]>([])
343
+
344
+ useEffect(() => {
345
+ if (!post?.id || post.canvasRelatedPosts !== null) {
346
+ setSiblingPosts([])
347
+ return
348
+ }
349
+ let cancelled = false
350
+ const urlPrefix = config?.collections?.[postType]?.urlPrefix
351
+ cmsApi<{
352
+ docs?: Array<{
353
+ id: string
354
+ title?: string | null
355
+ slug?: string | null
356
+ data?: Record<string, unknown> | null
357
+ }>
358
+ }>(
359
+ `/collections/${encodeURIComponent(postType)}?status=PUBLISHED&pageSize=4&sort=publishedAt&order=desc`,
360
+ )
361
+ .then((res) => {
362
+ if (cancelled) return
363
+ setSiblingPosts(
364
+ relatedPostsFromSiblings(res.data?.docs ?? [], {
365
+ excludeId: post.id,
366
+ urlPrefix,
367
+ collection: postType,
368
+ }),
369
+ )
370
+ })
371
+ .catch(() => {
372
+ if (!cancelled) setSiblingPosts([])
373
+ })
374
+ return () => {
375
+ cancelled = true
376
+ }
377
+ }, [post?.id, post?.canvasRelatedPosts, postType, config])
378
+
336
379
  const context: PostRenderContext = useMemo(
337
380
  () => ({
338
381
  title: post?.title,
@@ -341,9 +384,10 @@ export function PostSectionEditor({
341
384
  featuredImageUrl: post?.featuredImageUrl,
342
385
  publishDate: post?.publishDate ?? post?.updatedAt ?? null,
343
386
  category: post?.category,
344
- author: { name: session?.user?.name || session?.user?.email },
387
+ author: { name: canvasAuthorName(post?.authorName, session?.user) },
388
+ relatedPosts: post?.canvasRelatedPosts ?? siblingPosts,
345
389
  }),
346
- [post, session],
390
+ [post, session, siblingPosts],
347
391
  )
348
392
 
349
393
  // ─── Section mutations ───────────────────────────────────────────────