@actuate-media/cms-admin 0.41.0 → 0.42.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 +10 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +5 -1
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/views/admin-section-renderers.test.d.ts +2 -0
- package/dist/__tests__/views/admin-section-renderers.test.d.ts.map +1 -0
- package/dist/__tests__/views/admin-section-renderers.test.js +71 -0
- package/dist/__tests__/views/admin-section-renderers.test.js.map +1 -0
- package/dist/__tests__/views/page-section-editor.test.js +14 -0
- package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +9 -5
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +25 -4
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.d.ts +87 -0
- package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -0
- package/dist/views/page-editor/sections/admin-renderers.js +46 -0
- package/dist/views/page-editor/sections/admin-renderers.js.map +1 -0
- package/dist/views/page-editor/sections/index.d.ts +2 -0
- package/dist/views/page-editor/sections/index.d.ts.map +1 -1
- package/dist/views/page-editor/sections/index.js +1 -0
- package/dist/views/page-editor/sections/index.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +9 -5
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/package.json +1 -1
- package/src/AdminRoot.tsx +20 -1
- package/src/__tests__/views/admin-section-renderers.test.tsx +101 -0
- package/src/__tests__/views/page-section-editor.test.tsx +29 -0
- package/src/index.ts +11 -0
- package/src/views/page-editor/PageSectionEditor.tsx +9 -5
- package/src/views/page-editor/sections/SectionRenderer.tsx +30 -4
- package/src/views/page-editor/sections/admin-renderers.tsx +105 -0
- package/src/views/page-editor/sections/index.ts +12 -0
- package/src/views/post-editor/PostSectionEditor.tsx +13 -5
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pluggable canvas renderers for custom section types.
|
|
5
|
+
*
|
|
6
|
+
* By default the editor canvas renders custom (config-declared) and unmanaged
|
|
7
|
+
* section types as structural placeholders. Projects that want a real design
|
|
8
|
+
* preview inside the admin can pass renderers to `<AdminRoot
|
|
9
|
+
* sectionRenderers={...}>`; the canvas and the eye preview then use them in
|
|
10
|
+
* place of the placeholder.
|
|
11
|
+
*
|
|
12
|
+
* The renderer signature is a structural mirror of `SectionRenderer` from
|
|
13
|
+
* `@actuate-media/sections-react` (cms-admin depends on cms-core only, so the
|
|
14
|
+
* type is duplicated rather than imported). Because the shapes match, a
|
|
15
|
+
* consumer can hand `AdminRoot` the exact same components it registers for
|
|
16
|
+
* the public site:
|
|
17
|
+
*
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import { OPAL_RENDERERS } from './sections' // SectionRendererMap
|
|
20
|
+
* <AdminRoot sectionRenderers={OPAL_RENDERERS} ... />
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Renderers run inside the admin bundle, so they must be client-safe (no
|
|
24
|
+
* server-only imports). Each one is wrapped in an error boundary — a throwing
|
|
25
|
+
* renderer falls back to the structural placeholder instead of taking down
|
|
26
|
+
* the editor.
|
|
27
|
+
*/
|
|
28
|
+
import { createContext, useContext, type ReactNode } from 'react'
|
|
29
|
+
|
|
30
|
+
/** Structural mirror of `RenderImageProps` from `@actuate-media/sections-react`. */
|
|
31
|
+
export interface AdminRenderImageProps {
|
|
32
|
+
src: string
|
|
33
|
+
alt: string
|
|
34
|
+
width?: number | null
|
|
35
|
+
height?: number | null
|
|
36
|
+
className?: string
|
|
37
|
+
priority?: boolean
|
|
38
|
+
sizes?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Structural mirror of `PostContext` from `@actuate-media/sections-react`. */
|
|
42
|
+
export interface AdminSectionPostContext {
|
|
43
|
+
title?: string
|
|
44
|
+
excerpt?: string
|
|
45
|
+
body?: string
|
|
46
|
+
featuredImageUrl?: string
|
|
47
|
+
publishDate?: string | null
|
|
48
|
+
category?: string
|
|
49
|
+
author?: { name?: string; bio?: string; avatarUrl?: string }
|
|
50
|
+
relatedPosts?: Array<{ title: string; url?: string; excerpt?: string }>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Props passed to an admin section renderer (mirrors `SectionRendererProps`). */
|
|
54
|
+
export interface AdminSectionRendererProps {
|
|
55
|
+
section: {
|
|
56
|
+
id: string
|
|
57
|
+
sectionType: string
|
|
58
|
+
name?: string
|
|
59
|
+
visible?: boolean
|
|
60
|
+
content?: Record<string, unknown>
|
|
61
|
+
settings?: Record<string, unknown>
|
|
62
|
+
}
|
|
63
|
+
ctx?: AdminSectionPostContext
|
|
64
|
+
options: {
|
|
65
|
+
sanitizeHtml: (html: string) => string
|
|
66
|
+
baseUrl: string
|
|
67
|
+
renderImage: (props: AdminRenderImageProps) => ReactNode
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A canvas renderer for one custom section type. */
|
|
72
|
+
export type AdminSectionRenderer = (props: AdminSectionRendererProps) => ReactNode
|
|
73
|
+
|
|
74
|
+
/** Section-type id → canvas renderer. */
|
|
75
|
+
export type AdminSectionRendererMap = Record<string, AdminSectionRenderer>
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Render options passed to every admin renderer. Rich text is sanitized on
|
|
79
|
+
* write by cms-core, so the canvas (same trust domain as the public default)
|
|
80
|
+
* uses the identity sanitizer; images render as plain lazy `<img>` since the
|
|
81
|
+
* editor canvas has no next/image pipeline.
|
|
82
|
+
*/
|
|
83
|
+
export const ADMIN_RENDER_OPTIONS: AdminSectionRendererProps['options'] = {
|
|
84
|
+
sanitizeHtml: (html) => html,
|
|
85
|
+
baseUrl: '',
|
|
86
|
+
renderImage: ({ src, alt, className, priority, width, height }) => (
|
|
87
|
+
<img
|
|
88
|
+
src={src}
|
|
89
|
+
alt={alt}
|
|
90
|
+
className={className}
|
|
91
|
+
width={width ?? undefined}
|
|
92
|
+
height={height ?? undefined}
|
|
93
|
+
loading={priority ? undefined : 'lazy'}
|
|
94
|
+
/>
|
|
95
|
+
),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const AdminSectionRenderersContext = createContext<AdminSectionRendererMap>({})
|
|
99
|
+
|
|
100
|
+
export const AdminSectionRenderersProvider = AdminSectionRenderersContext.Provider
|
|
101
|
+
|
|
102
|
+
/** The renderer map passed to `<AdminRoot sectionRenderers>` (empty by default). */
|
|
103
|
+
export function useAdminSectionRenderers(): AdminSectionRendererMap {
|
|
104
|
+
return useContext(AdminSectionRenderersContext)
|
|
105
|
+
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
export { SectionRenderer, hasCanvasRenderer } from './SectionRenderer.js'
|
|
2
2
|
export type { SectionRendererProps } from './SectionRenderer.js'
|
|
3
|
+
export {
|
|
4
|
+
AdminSectionRenderersProvider,
|
|
5
|
+
useAdminSectionRenderers,
|
|
6
|
+
ADMIN_RENDER_OPTIONS,
|
|
7
|
+
} from './admin-renderers.js'
|
|
8
|
+
export type {
|
|
9
|
+
AdminSectionRenderer,
|
|
10
|
+
AdminSectionRendererMap,
|
|
11
|
+
AdminSectionRendererProps,
|
|
12
|
+
AdminSectionPostContext,
|
|
13
|
+
AdminRenderImageProps,
|
|
14
|
+
} from './admin-renderers.js'
|
|
3
15
|
export type { PostRenderContext } from './parts.js'
|
|
4
16
|
export { HeroBannerSection } from './HeroBannerSection.js'
|
|
5
17
|
export { MissionSection } from './MissionSection.js'
|
|
@@ -34,7 +34,11 @@ import {
|
|
|
34
34
|
resolveSiteUrl,
|
|
35
35
|
} from '../../lib/preview-link.js'
|
|
36
36
|
import type { SectionSettings, SectionTypeId } from '../page-editor/section-types.js'
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
hasCanvasRenderer,
|
|
39
|
+
useAdminSectionRenderers,
|
|
40
|
+
type PostRenderContext,
|
|
41
|
+
} from '../page-editor/sections/index.js'
|
|
38
42
|
import { DEFAULT_VIEWPORT, type ViewportId } from '../page-editor/viewports.js'
|
|
39
43
|
import { EditorTopBar, type SaveState } from '../page-editor/EditorTopBar.js'
|
|
40
44
|
import { SectionsPanel } from '../page-editor/SectionsPanel.js'
|
|
@@ -112,6 +116,7 @@ export function PostSectionEditor({
|
|
|
112
116
|
const [validation, setValidation] = useState<ValidationResult | null>(null)
|
|
113
117
|
|
|
114
118
|
const { canEdit, canPublish } = useMemo(() => deriveCan(session), [session])
|
|
119
|
+
const customRenderers = useAdminSectionRenderers()
|
|
115
120
|
|
|
116
121
|
const typeLabel = useMemo(() => {
|
|
117
122
|
const col = config?.collections?.[postType]
|
|
@@ -419,12 +424,15 @@ export function PostSectionEditor({
|
|
|
419
424
|
|
|
420
425
|
const selected = post.sections.find((s) => s.id === selectedId) ?? null
|
|
421
426
|
|
|
422
|
-
// Section types the canvas renders as placeholders
|
|
423
|
-
//
|
|
424
|
-
//
|
|
427
|
+
// Section types the canvas renders as placeholders — no built-in body
|
|
428
|
+
// component and no consumer-registered admin renderer. When present the
|
|
429
|
+
// editor surfaces "structure view" messaging and promotes "View on site"
|
|
430
|
+
// as the primary design preview.
|
|
425
431
|
const structuralTypes = Array.from(
|
|
426
432
|
new Set(
|
|
427
|
-
post.sections
|
|
433
|
+
post.sections
|
|
434
|
+
.filter((s) => !hasCanvasRenderer(s.sectionType) && !customRenderers[s.sectionType])
|
|
435
|
+
.map((s) => s.sectionType),
|
|
428
436
|
),
|
|
429
437
|
)
|
|
430
438
|
|