@actuate-media/cms-admin 0.44.0 → 0.46.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 +37 -0
- package/dist/__tests__/components/tag-input.render.test.d.ts +2 -0
- package/dist/__tests__/components/tag-input.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/tag-input.render.test.js +86 -0
- package/dist/__tests__/components/tag-input.render.test.js.map +1 -0
- package/dist/__tests__/lib/post-editor-service.test.js +26 -0
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
- package/dist/__tests__/views/post-fields-modal.render.test.d.ts +2 -0
- package/dist/__tests__/views/post-fields-modal.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/post-fields-modal.render.test.js +86 -0
- package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -0
- package/dist/__tests__/views/section-inspector-repeater.render.test.d.ts +2 -0
- package/dist/__tests__/views/section-inspector-repeater.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/section-inspector-repeater.render.test.js +68 -0
- package/dist/__tests__/views/section-inspector-repeater.render.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/TagInput.d.ts +16 -0
- package/dist/components/TagInput.d.ts.map +1 -0
- package/dist/components/TagInput.js +105 -0
- package/dist/components/TagInput.js.map +1 -0
- package/dist/lib/post-editor-service.d.ts +8 -0
- package/dist/lib/post-editor-service.d.ts.map +1 -1
- package/dist/lib/post-editor-service.js +25 -0
- package/dist/lib/post-editor-service.js.map +1 -1
- package/dist/views/page-editor/SectionInspector.d.ts +9 -0
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +38 -1
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.d.ts +17 -2
- package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.js +13 -4
- package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts +2 -0
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +1 -1
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/components/tag-input.render.test.tsx +103 -0
- package/src/__tests__/lib/post-editor-service.test.ts +28 -0
- package/src/__tests__/views/post-fields-modal.render.test.tsx +126 -0
- package/src/__tests__/views/section-inspector-repeater.render.test.tsx +90 -0
- package/src/components/TagInput.tsx +196 -0
- package/src/lib/post-editor-service.ts +28 -0
- package/src/views/page-editor/SectionInspector.tsx +40 -2
- package/src/views/post-editor/PostFieldsModal.tsx +79 -10
- package/src/views/post-editor/PostSectionEditor.tsx +11 -2
|
@@ -4,13 +4,27 @@ import { useEffect, useState } from 'react'
|
|
|
4
4
|
import { Modal } from '../../components/ui/Modal.js'
|
|
5
5
|
import { Button } from '../../components/ui/Button.js'
|
|
6
6
|
import { MediaPickerModal } from '../../components/MediaPickerModal.js'
|
|
7
|
+
import { TagInput } from '../../components/TagInput.js'
|
|
7
8
|
import { RichTextField } from '../../fields/RichTextField.js'
|
|
8
9
|
import type { EditorPost } from '../../lib/post-editor-service.js'
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The slice of a collection's field config the modal reads to pick controls:
|
|
13
|
+
* a `select` category renders a dropdown (structured vocabulary), an `array`
|
|
14
|
+
* tags field renders the chip input with autocomplete.
|
|
15
|
+
*/
|
|
16
|
+
export interface PostFieldConfig {
|
|
17
|
+
type?: string
|
|
18
|
+
options?: Array<{ label: string; value: string }>
|
|
19
|
+
maxRows?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
interface PostFieldsModalProps {
|
|
11
23
|
open: boolean
|
|
12
24
|
post: EditorPost
|
|
13
25
|
canEdit: boolean
|
|
26
|
+
/** Field definitions from the post type's collection config. */
|
|
27
|
+
fields?: Record<string, PostFieldConfig | undefined>
|
|
14
28
|
onClose: () => void
|
|
15
29
|
onSave: (patch: Partial<EditorPost>) => void
|
|
16
30
|
}
|
|
@@ -27,19 +41,35 @@ function slugify(value: string): string {
|
|
|
27
41
|
.replace(/^-+|-+$/g, '')
|
|
28
42
|
}
|
|
29
43
|
|
|
30
|
-
/** Edits post-level fields: title, slug, excerpt, featured image, category, body, SEO. */
|
|
31
|
-
export function PostFieldsModal({
|
|
44
|
+
/** Edits post-level fields: title, slug, excerpt, featured image, category, tags, body, SEO. */
|
|
45
|
+
export function PostFieldsModal({
|
|
46
|
+
open,
|
|
47
|
+
post,
|
|
48
|
+
canEdit,
|
|
49
|
+
fields,
|
|
50
|
+
onClose,
|
|
51
|
+
onSave,
|
|
52
|
+
}: PostFieldsModalProps) {
|
|
32
53
|
const [title, setTitle] = useState(post.title)
|
|
33
54
|
const [slug, setSlug] = useState(post.slug)
|
|
34
55
|
const [excerpt, setExcerpt] = useState(post.excerpt)
|
|
35
56
|
const [featuredImage, setFeaturedImage] = useState(post.featuredImage)
|
|
36
57
|
const [category, setCategory] = useState(post.category)
|
|
58
|
+
const [tags, setTags] = useState<string[]>(post.tags)
|
|
37
59
|
const [body, setBody] = useState(post.body)
|
|
38
60
|
const [seoTitle, setSeoTitle] = useState(post.seoTitle)
|
|
39
61
|
const [seoDescription, setSeoDescription] = useState(post.seoDescription)
|
|
40
62
|
const [slugTouched, setSlugTouched] = useState(Boolean(post.slug))
|
|
41
63
|
const [mediaOpen, setMediaOpen] = useState(false)
|
|
42
64
|
|
|
65
|
+
const categoryField = fields?.category
|
|
66
|
+
const categoryOptions =
|
|
67
|
+
categoryField?.type === 'select' && Array.isArray(categoryField.options)
|
|
68
|
+
? categoryField.options
|
|
69
|
+
: null
|
|
70
|
+
const tagsField = fields?.tags
|
|
71
|
+
const hasTags = tagsField?.type === 'array'
|
|
72
|
+
|
|
43
73
|
useEffect(() => {
|
|
44
74
|
if (!open) return
|
|
45
75
|
setTitle(post.title)
|
|
@@ -47,6 +77,7 @@ export function PostFieldsModal({ open, post, canEdit, onClose, onSave }: PostFi
|
|
|
47
77
|
setExcerpt(post.excerpt)
|
|
48
78
|
setFeaturedImage(post.featuredImage)
|
|
49
79
|
setCategory(post.category)
|
|
80
|
+
setTags(post.tags)
|
|
50
81
|
setBody(post.body)
|
|
51
82
|
setSeoTitle(post.seoTitle)
|
|
52
83
|
setSeoDescription(post.seoDescription)
|
|
@@ -59,7 +90,7 @@ export function PostFieldsModal({ open, post, canEdit, onClose, onSave }: PostFi
|
|
|
59
90
|
}
|
|
60
91
|
|
|
61
92
|
function handleSave() {
|
|
62
|
-
onSave({ title, slug, excerpt, featuredImage, category, body, seoTitle, seoDescription })
|
|
93
|
+
onSave({ title, slug, excerpt, featuredImage, category, tags, body, seoTitle, seoDescription })
|
|
63
94
|
onClose()
|
|
64
95
|
}
|
|
65
96
|
|
|
@@ -112,16 +143,54 @@ export function PostFieldsModal({ open, post, canEdit, onClose, onSave }: PostFi
|
|
|
112
143
|
<label className={LABEL} htmlFor="pf-category">
|
|
113
144
|
Category
|
|
114
145
|
</label>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
146
|
+
{categoryOptions ? (
|
|
147
|
+
<select
|
|
148
|
+
id="pf-category"
|
|
149
|
+
className={INPUT}
|
|
150
|
+
value={category}
|
|
151
|
+
disabled={!canEdit}
|
|
152
|
+
onChange={(e) => setCategory(e.target.value)}
|
|
153
|
+
>
|
|
154
|
+
<option value="">No category</option>
|
|
155
|
+
{categoryOptions.map((o) => (
|
|
156
|
+
<option key={o.value} value={o.value}>
|
|
157
|
+
{o.label}
|
|
158
|
+
</option>
|
|
159
|
+
))}
|
|
160
|
+
{/* Keep a legacy free-text value selectable so opening the
|
|
161
|
+
modal never silently discards it. */}
|
|
162
|
+
{category && !categoryOptions.some((o) => o.value === category) && (
|
|
163
|
+
<option value={category}>{category}</option>
|
|
164
|
+
)}
|
|
165
|
+
</select>
|
|
166
|
+
) : (
|
|
167
|
+
<input
|
|
168
|
+
id="pf-category"
|
|
169
|
+
className={INPUT}
|
|
170
|
+
value={category}
|
|
171
|
+
disabled={!canEdit}
|
|
172
|
+
placeholder="e.g. Product"
|
|
173
|
+
onChange={(e) => setCategory(e.target.value)}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
{hasTags && (
|
|
179
|
+
<div>
|
|
180
|
+
<label className={LABEL} htmlFor="pf-tags">
|
|
181
|
+
Tags
|
|
182
|
+
</label>
|
|
183
|
+
<TagInput
|
|
184
|
+
id="pf-tags"
|
|
185
|
+
value={tags}
|
|
186
|
+
onChange={setTags}
|
|
187
|
+
collection={post.postType}
|
|
188
|
+
field="tags"
|
|
119
189
|
disabled={!canEdit}
|
|
120
|
-
|
|
121
|
-
onChange={(e) => setCategory(e.target.value)}
|
|
190
|
+
maxTags={tagsField?.maxRows}
|
|
122
191
|
/>
|
|
123
192
|
</div>
|
|
124
|
-
|
|
193
|
+
)}
|
|
125
194
|
<div>
|
|
126
195
|
<label className={LABEL} htmlFor="pf-excerpt">
|
|
127
196
|
Excerpt
|
|
@@ -47,11 +47,19 @@ import { SectionInspector } from '../page-editor/SectionInspector.js'
|
|
|
47
47
|
import { AddSectionModal } from '../page-editor/AddSectionModal.js'
|
|
48
48
|
import { ValidationSummary } from '../page-editor/ValidationSummary.js'
|
|
49
49
|
import { PostEditorCanvas } from './PostEditorCanvas.js'
|
|
50
|
-
import { PostFieldsModal } from './PostFieldsModal.js'
|
|
50
|
+
import { PostFieldsModal, type PostFieldConfig } from './PostFieldsModal.js'
|
|
51
51
|
|
|
52
52
|
/** The slice of the resolved CMS config this editor reads. */
|
|
53
53
|
interface EditorConfig {
|
|
54
|
-
collections?: Record<
|
|
54
|
+
collections?: Record<
|
|
55
|
+
string,
|
|
56
|
+
| {
|
|
57
|
+
labels?: { singular?: string }
|
|
58
|
+
urlPrefix?: string
|
|
59
|
+
fields?: Record<string, PostFieldConfig | undefined>
|
|
60
|
+
}
|
|
61
|
+
| undefined
|
|
62
|
+
>
|
|
55
63
|
seo?: { siteUrl?: string }
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -576,6 +584,7 @@ export function PostSectionEditor({
|
|
|
576
584
|
open={fieldsOpen}
|
|
577
585
|
post={post}
|
|
578
586
|
canEdit={canEdit}
|
|
587
|
+
fields={config?.collections?.[postType]?.fields}
|
|
579
588
|
onClose={() => setFieldsOpen(false)}
|
|
580
589
|
onSave={handleFields}
|
|
581
590
|
/>
|