@actuate-media/cms-admin 0.49.3 → 0.50.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 +61 -0
- package/dist/AdminRoot.js +2 -2
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/seo-char-count.test.d.ts +2 -0
- package/dist/__tests__/components/seo-char-count.test.d.ts.map +1 -0
- package/dist/__tests__/components/seo-char-count.test.js +25 -0
- package/dist/__tests__/components/seo-char-count.test.js.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js +12 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/post-editor-service.test.js +4 -0
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
- package/dist/__tests__/views/seo-settings.render.test.js +1 -1
- package/dist/__tests__/views/seo-settings.render.test.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/CoauthorResultPopover.js +1 -1
- package/dist/components/CoauthorResultPopover.js.map +1 -1
- package/dist/components/CommentSidePanel.js +1 -1
- package/dist/components/CommentSidePanel.js.map +1 -1
- package/dist/components/ContentOverviewChart.js +1 -1
- package/dist/components/ContentOverviewChart.js.map +1 -1
- package/dist/components/MediaPickerModal.d.ts.map +1 -1
- package/dist/components/MediaPickerModal.js +3 -1
- package/dist/components/MediaPickerModal.js.map +1 -1
- package/dist/components/NotificationBell.js +1 -1
- package/dist/components/NotificationBell.js.map +1 -1
- package/dist/components/PresenceChips.js +1 -1
- package/dist/components/PresenceChips.js.map +1 -1
- package/dist/components/SEOConfigPanel.js +2 -2
- package/dist/components/SEOConfigPanel.js.map +1 -1
- package/dist/components/SEOPanel.d.ts +1 -0
- package/dist/components/SEOPanel.d.ts.map +1 -1
- package/dist/components/SEOPanel.js +3 -3
- package/dist/components/SEOPanel.js.map +1 -1
- package/dist/components/SEOPerformance.js +2 -2
- package/dist/components/SEOPerformance.js.map +1 -1
- package/dist/components/forms/EntriesTable.js +1 -1
- package/dist/components/forms/EntriesTable.js.map +1 -1
- package/dist/components/seo/SeoCharCount.d.ts +15 -0
- package/dist/components/seo/SeoCharCount.d.ts.map +1 -0
- package/dist/components/seo/SeoCharCount.js +25 -0
- package/dist/components/seo/SeoCharCount.js.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts.map +1 -1
- package/dist/components/seo/SeoEditorDrawer.js +3 -1
- package/dist/components/seo/SeoEditorDrawer.js.map +1 -1
- package/dist/components/ui/Card.js +1 -1
- package/dist/components/ui/Card.js.map +1 -1
- package/dist/components/ui/EmptyState.js +1 -1
- package/dist/components/ui/EmptyState.js.map +1 -1
- package/dist/fields/SlugField.d.ts.map +1 -1
- package/dist/fields/SlugField.js +6 -2
- package/dist/fields/SlugField.js.map +1 -1
- package/dist/layout/Sidebar.js +1 -1
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/layout/primitives/PageContainer.js +1 -1
- package/dist/layout/primitives/PageContainer.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +6 -0
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/lib/post-editor-service.d.ts.map +1 -1
- package/dist/lib/post-editor-service.js +6 -0
- package/dist/lib/post-editor-service.js.map +1 -1
- package/dist/lib/seo-service.d.ts +3 -1
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/views/CollectionList.js +1 -1
- package/dist/views/CollectionList.js.map +1 -1
- package/dist/views/Dashboard.js +8 -8
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/DocumentEdit.d.ts.map +1 -1
- package/dist/views/DocumentEdit.js +2 -1
- package/dist/views/DocumentEdit.js.map +1 -1
- package/dist/views/ForgotPassword.js +1 -1
- package/dist/views/ForgotPassword.js.map +1 -1
- package/dist/views/FormEditor.d.ts.map +1 -1
- package/dist/views/FormEditor.js +1 -1
- package/dist/views/FormEditor.js.map +1 -1
- package/dist/views/Login.js +1 -1
- package/dist/views/Login.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +3 -3
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/PageEditor.js +1 -1
- package/dist/views/PageEditor.js.map +1 -1
- package/dist/views/Pages/PagesListView.js +3 -3
- package/dist/views/Pages/PagesListView.js.map +1 -1
- package/dist/views/Pages.js +1 -1
- package/dist/views/Pages.js.map +1 -1
- package/dist/views/Posts/PostsListView.js +1 -1
- package/dist/views/Posts/PostsListView.js.map +1 -1
- package/dist/views/Redirects.js +2 -2
- package/dist/views/Redirects.js.map +1 -1
- package/dist/views/ResetPassword.js +2 -2
- package/dist/views/ResetPassword.js.map +1 -1
- package/dist/views/ScriptTagEditor.js +1 -1
- package/dist/views/ScriptTagEditor.js.map +1 -1
- package/dist/views/ScriptTags.js +1 -1
- package/dist/views/ScriptTags.js.map +1 -1
- package/dist/views/SetupWizard.js +2 -2
- package/dist/views/SetupWizard.js.map +1 -1
- package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -1
- package/dist/views/page-editor/PageSettingsModal.js +12 -9
- package/dist/views/page-editor/PageSettingsModal.js.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +2 -2
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.js +12 -9
- package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
- package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
- package/dist/views/post-editor/PostHeader.js +2 -2
- package/dist/views/post-editor/PostHeader.js.map +1 -1
- package/dist/views/post-editor/PostHeaderPanel.js +1 -1
- package/dist/views/post-editor/PostHeaderPanel.js.map +1 -1
- package/package.json +2 -2
- package/src/AdminRoot.tsx +2 -2
- package/src/__tests__/components/seo-char-count.test.tsx +27 -0
- package/src/__tests__/lib/page-editor-service.test.ts +14 -0
- package/src/__tests__/lib/post-editor-service.test.ts +9 -0
- package/src/__tests__/views/seo-settings.render.test.tsx +1 -1
- package/src/components/CoauthorResultPopover.tsx +1 -1
- package/src/components/CommentSidePanel.tsx +1 -1
- package/src/components/ContentOverviewChart.tsx +1 -1
- package/src/components/MediaPickerModal.tsx +5 -1
- package/src/components/NotificationBell.tsx +2 -2
- package/src/components/PresenceChips.tsx +1 -1
- package/src/components/SEOConfigPanel.tsx +3 -3
- package/src/components/SEOPanel.tsx +17 -6
- package/src/components/SEOPerformance.tsx +5 -5
- package/src/components/forms/EntriesTable.tsx +1 -1
- package/src/components/seo/SeoCharCount.tsx +44 -0
- package/src/components/seo/SeoEditorDrawer.tsx +27 -1
- package/src/components/ui/Card.tsx +1 -1
- package/src/components/ui/EmptyState.tsx +1 -1
- package/src/fields/SlugField.tsx +6 -2
- package/src/layout/Sidebar.tsx +1 -1
- package/src/layout/primitives/PageContainer.tsx +1 -1
- package/src/lib/page-editor-service.ts +6 -0
- package/src/lib/post-editor-service.ts +6 -0
- package/src/lib/seo-service.ts +9 -1
- package/src/views/CollectionList.tsx +1 -1
- package/src/views/Dashboard.tsx +12 -12
- package/src/views/DocumentEdit.tsx +5 -4
- package/src/views/ForgotPassword.tsx +1 -1
- package/src/views/FormEditor.tsx +1 -3
- package/src/views/Login.tsx +2 -2
- package/src/views/MediaBrowser.tsx +7 -9
- package/src/views/PageEditor.tsx +2 -2
- package/src/views/Pages/PagesListView.tsx +3 -3
- package/src/views/Pages.tsx +1 -1
- package/src/views/Posts/PostsListView.tsx +1 -1
- package/src/views/Redirects.tsx +2 -2
- package/src/views/ResetPassword.tsx +2 -2
- package/src/views/ScriptTagEditor.tsx +2 -2
- package/src/views/ScriptTags.tsx +2 -2
- package/src/views/SetupWizard.tsx +2 -2
- package/src/views/page-editor/PageSettingsModal.tsx +33 -14
- package/src/views/page-editor/SectionInspector.tsx +4 -4
- package/src/views/post-editor/PostFieldsModal.tsx +33 -14
- package/src/views/post-editor/PostHeader.tsx +2 -4
- package/src/views/post-editor/PostHeaderPanel.tsx +2 -2
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from 'react'
|
|
4
|
+
import { normalizeSlug, validateSlug } from '@actuate-media/cms-core/client'
|
|
4
5
|
import { Modal } from '../../components/ui/Modal.js'
|
|
5
6
|
import { Button } from '../../components/ui/Button.js'
|
|
6
7
|
import { MediaPickerModal } from '../../components/MediaPickerModal.js'
|
|
7
8
|
import { TagInput } from '../../components/TagInput.js'
|
|
8
9
|
import { RichTextField } from '../../fields/RichTextField.js'
|
|
10
|
+
import { SeoCharCount } from '../../components/seo/SeoCharCount.js'
|
|
9
11
|
import type { EditorPost } from '../../lib/post-editor-service.js'
|
|
10
12
|
|
|
13
|
+
const SEO_TITLE_MIN = 30
|
|
14
|
+
const SEO_TITLE_MAX = 60
|
|
15
|
+
const SEO_DESC_MIN = 120
|
|
16
|
+
const SEO_DESC_MAX = 160
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* The slice of a collection's field config the modal reads to pick controls:
|
|
13
20
|
* a `select` category renders a dropdown (structured vocabulary), an `array`
|
|
@@ -33,13 +40,7 @@ const LABEL = 'text-foreground mb-1 block text-xs font-medium'
|
|
|
33
40
|
const INPUT =
|
|
34
41
|
'border-input bg-input-background text-foreground focus-visible:ring-ring w-full rounded-md border px-2.5 py-1.5 text-sm focus-visible:ring-2 focus-visible:outline-none'
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
return value
|
|
38
|
-
.toLowerCase()
|
|
39
|
-
.trim()
|
|
40
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
41
|
-
.replace(/^-+|-+$/g, '')
|
|
42
|
-
}
|
|
43
|
+
const slugify = normalizeSlug
|
|
43
44
|
|
|
44
45
|
/** Edits post-level fields: title, slug, excerpt, featured image, category, tags, body, SEO. */
|
|
45
46
|
export function PostFieldsModal({
|
|
@@ -89,7 +90,10 @@ export function PostFieldsModal({
|
|
|
89
90
|
if (!slugTouched) setSlug(slugify(next))
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
const slugError = validateSlug(slug)
|
|
94
|
+
|
|
92
95
|
function handleSave() {
|
|
96
|
+
if (slugError) return
|
|
93
97
|
onSave({ title, slug, excerpt, featuredImage, category, tags, body, seoTitle, seoDescription })
|
|
94
98
|
onClose()
|
|
95
99
|
}
|
|
@@ -104,7 +108,7 @@ export function PostFieldsModal({
|
|
|
104
108
|
<Button variant="ghost" onClick={onClose}>
|
|
105
109
|
Cancel
|
|
106
110
|
</Button>
|
|
107
|
-
<Button variant="primary" onClick={handleSave} disabled={!canEdit}>
|
|
111
|
+
<Button variant="primary" onClick={handleSave} disabled={!canEdit || Boolean(slugError)}>
|
|
108
112
|
Done
|
|
109
113
|
</Button>
|
|
110
114
|
</>
|
|
@@ -133,11 +137,18 @@ export function PostFieldsModal({
|
|
|
133
137
|
className={INPUT}
|
|
134
138
|
value={slug}
|
|
135
139
|
disabled={!canEdit}
|
|
140
|
+
aria-invalid={Boolean(slugError)}
|
|
141
|
+
aria-describedby={slugError ? 'pf-slug-error' : undefined}
|
|
136
142
|
onChange={(e) => {
|
|
137
143
|
setSlugTouched(true)
|
|
138
144
|
setSlug(slugify(e.target.value))
|
|
139
145
|
}}
|
|
140
146
|
/>
|
|
147
|
+
{slugError && (
|
|
148
|
+
<p id="pf-slug-error" className="text-destructive mt-1 text-xs">
|
|
149
|
+
{slugError}
|
|
150
|
+
</p>
|
|
151
|
+
)}
|
|
141
152
|
</div>
|
|
142
153
|
<div>
|
|
143
154
|
<label className={LABEL} htmlFor="pf-category">
|
|
@@ -241,26 +252,34 @@ export function PostFieldsModal({
|
|
|
241
252
|
/>
|
|
242
253
|
</div>
|
|
243
254
|
<div>
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
<div className="mb-1 flex items-center justify-between">
|
|
256
|
+
<label className={`${LABEL} mb-0`} htmlFor="pf-seo-title">
|
|
257
|
+
SEO title
|
|
258
|
+
</label>
|
|
259
|
+
<SeoCharCount value={seoTitle} min={SEO_TITLE_MIN} max={SEO_TITLE_MAX} />
|
|
260
|
+
</div>
|
|
247
261
|
<input
|
|
248
262
|
id="pf-seo-title"
|
|
249
263
|
className={INPUT}
|
|
250
264
|
value={seoTitle}
|
|
251
265
|
disabled={!canEdit}
|
|
266
|
+
placeholder={title || 'Defaults to the post title'}
|
|
252
267
|
onChange={(e) => setSeoTitle(e.target.value)}
|
|
253
268
|
/>
|
|
254
269
|
</div>
|
|
255
270
|
<div>
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
271
|
+
<div className="mb-1 flex items-center justify-between">
|
|
272
|
+
<label className={`${LABEL} mb-0`} htmlFor="pf-seo-desc">
|
|
273
|
+
SEO description
|
|
274
|
+
</label>
|
|
275
|
+
<SeoCharCount value={seoDescription} min={SEO_DESC_MIN} max={SEO_DESC_MAX} />
|
|
276
|
+
</div>
|
|
259
277
|
<textarea
|
|
260
278
|
id="pf-seo-desc"
|
|
261
279
|
className={`${INPUT} min-h-[60px] resize-y`}
|
|
262
280
|
value={seoDescription}
|
|
263
281
|
disabled={!canEdit}
|
|
282
|
+
placeholder={excerpt || 'Summarize the post for search results (120–160 characters)'}
|
|
264
283
|
onChange={(e) => setSeoDescription(e.target.value)}
|
|
265
284
|
/>
|
|
266
285
|
</div>
|
|
@@ -49,9 +49,7 @@ export function PostHeader({ config, context }: PostHeaderProps) {
|
|
|
49
49
|
<div className="absolute inset-0 bg-linear-to-t from-black/70 to-black/10" aria-hidden />
|
|
50
50
|
<div className="absolute inset-x-0 bottom-0 px-6 pb-8">
|
|
51
51
|
<div className={`mx-auto flex max-w-3xl flex-col ${align}`}>
|
|
52
|
-
<h1 className="text-3xl font-
|
|
53
|
-
{title}
|
|
54
|
-
</h1>
|
|
52
|
+
<h1 className="text-3xl font-medium tracking-tight text-white sm:text-4xl">{title}</h1>
|
|
55
53
|
{config.showExcerpt && context.excerpt && (
|
|
56
54
|
<p className="mt-3 max-w-2xl text-lg text-white/90">{context.excerpt}</p>
|
|
57
55
|
)}
|
|
@@ -67,7 +65,7 @@ export function PostHeader({ config, context }: PostHeaderProps) {
|
|
|
67
65
|
<div
|
|
68
66
|
className={`flex max-w-3xl flex-col ${align} ${config.layout === 'centered' ? 'mx-auto' : ''}`}
|
|
69
67
|
>
|
|
70
|
-
<h1 className="text-3xl font-
|
|
68
|
+
<h1 className="text-3xl font-medium tracking-tight text-zinc-900 sm:text-4xl">{title}</h1>
|
|
71
69
|
{config.showExcerpt && context.excerpt && (
|
|
72
70
|
<p className="mt-3 max-w-2xl text-lg text-zinc-600">{context.excerpt}</p>
|
|
73
71
|
)}
|
|
@@ -48,7 +48,7 @@ export function PostHeaderPanel({ header, canEdit, onClose, onChange }: PostHead
|
|
|
48
48
|
|
|
49
49
|
<div className="flex-1 space-y-6 overflow-y-auto p-4">
|
|
50
50
|
<fieldset disabled={!canEdit} className="space-y-3">
|
|
51
|
-
<legend className="text-muted-foreground mb-2 text-[10px] font-
|
|
51
|
+
<legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
|
|
52
52
|
Layout
|
|
53
53
|
</legend>
|
|
54
54
|
<div>
|
|
@@ -69,7 +69,7 @@ export function PostHeaderPanel({ header, canEdit, onClose, onChange }: PostHead
|
|
|
69
69
|
</fieldset>
|
|
70
70
|
|
|
71
71
|
<fieldset disabled={!canEdit} className="space-y-2">
|
|
72
|
-
<legend className="text-muted-foreground mb-2 text-[10px] font-
|
|
72
|
+
<legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
|
|
73
73
|
Show in header
|
|
74
74
|
</legend>
|
|
75
75
|
{TOGGLES.map((t) => {
|