@actuate-media/cms-admin 0.73.0 → 0.75.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.
Files changed (209) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +3 -2
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/LoginRoot.d.ts.map +1 -1
  6. package/dist/LoginRoot.js +2 -1
  7. package/dist/LoginRoot.js.map +1 -1
  8. package/dist/__tests__/components/theme-provider-accent.test.d.ts +2 -0
  9. package/dist/__tests__/components/theme-provider-accent.test.d.ts.map +1 -0
  10. package/dist/__tests__/components/theme-provider-accent.test.js +64 -0
  11. package/dist/__tests__/components/theme-provider-accent.test.js.map +1 -0
  12. package/dist/__tests__/views/portal-container.test.js +13 -0
  13. package/dist/__tests__/views/portal-container.test.js.map +1 -1
  14. package/dist/__tests__/views/post-fields-modal.render.test.js +3 -6
  15. package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -1
  16. package/dist/__tests__/views/redirects-pane.render.test.js +6 -0
  17. package/dist/__tests__/views/redirects-pane.render.test.js.map +1 -1
  18. package/dist/__tests__/views/seo-content-pane.render.test.js +15 -4
  19. package/dist/__tests__/views/seo-content-pane.render.test.js.map +1 -1
  20. package/dist/actuate-admin.css +1 -1
  21. package/dist/components/CoauthorResultPopover.js +1 -1
  22. package/dist/components/EditorSeoAside.d.ts +23 -0
  23. package/dist/components/EditorSeoAside.d.ts.map +1 -0
  24. package/dist/components/EditorSeoAside.js +21 -0
  25. package/dist/components/EditorSeoAside.js.map +1 -0
  26. package/dist/components/MediaPickerModal.js +1 -1
  27. package/dist/components/SEOConfigPanel.js +1 -1
  28. package/dist/components/SchedulePublishDialog.js +1 -1
  29. package/dist/components/SharePreviewLinkDialog.js +1 -1
  30. package/dist/components/ThemeProvider.d.ts.map +1 -1
  31. package/dist/components/ThemeProvider.js +2 -1
  32. package/dist/components/ThemeProvider.js.map +1 -1
  33. package/dist/components/VersionHistory.js +1 -1
  34. package/dist/components/seo/EditorSeoScoreBadge.d.ts +7 -0
  35. package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -0
  36. package/dist/components/seo/EditorSeoScoreBadge.js +9 -0
  37. package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -0
  38. package/dist/components/seo/SeoAccordion.d.ts +16 -0
  39. package/dist/components/seo/SeoAccordion.d.ts.map +1 -0
  40. package/dist/components/seo/SeoAccordion.js +21 -0
  41. package/dist/components/seo/SeoAccordion.js.map +1 -0
  42. package/dist/components/seo/SeoAnalysisList.d.ts +7 -0
  43. package/dist/components/seo/SeoAnalysisList.d.ts.map +1 -0
  44. package/dist/components/seo/SeoAnalysisList.js +32 -0
  45. package/dist/components/seo/SeoAnalysisList.js.map +1 -0
  46. package/dist/components/seo/SeoEditorPane.d.ts +15 -4
  47. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
  48. package/dist/components/seo/SeoEditorPane.js +155 -35
  49. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  50. package/dist/components/seo/SeoInsightsMetrics.d.ts +6 -0
  51. package/dist/components/seo/SeoInsightsMetrics.d.ts.map +1 -0
  52. package/dist/components/seo/SeoInsightsMetrics.js +16 -0
  53. package/dist/components/seo/SeoInsightsMetrics.js.map +1 -0
  54. package/dist/components/seo/SeoIssueFixPanel.d.ts.map +1 -1
  55. package/dist/components/seo/SeoIssueFixPanel.js +13 -3
  56. package/dist/components/seo/SeoIssueFixPanel.js.map +1 -1
  57. package/dist/components/seo/SeoLinkingSection.d.ts +9 -0
  58. package/dist/components/seo/SeoLinkingSection.d.ts.map +1 -0
  59. package/dist/components/seo/SeoLinkingSection.js +44 -0
  60. package/dist/components/seo/SeoLinkingSection.js.map +1 -0
  61. package/dist/components/seo/primitives.d.ts.map +1 -1
  62. package/dist/components/seo/primitives.js +3 -3
  63. package/dist/components/seo/primitives.js.map +1 -1
  64. package/dist/components/ui/Card.js +5 -5
  65. package/dist/components/ui/Card.js.map +1 -1
  66. package/dist/components/ui/InspectorPane.js +3 -3
  67. package/dist/components/ui/InspectorPane.js.map +1 -1
  68. package/dist/components/ui/PanelSection.d.ts +16 -0
  69. package/dist/components/ui/PanelSection.d.ts.map +1 -0
  70. package/dist/components/ui/PanelSection.js +15 -0
  71. package/dist/components/ui/PanelSection.js.map +1 -0
  72. package/dist/lib/admin-root-element.d.ts +9 -0
  73. package/dist/lib/admin-root-element.d.ts.map +1 -0
  74. package/dist/lib/admin-root-element.js +14 -0
  75. package/dist/lib/admin-root-element.js.map +1 -0
  76. package/dist/lib/editor-seo-content.d.ts +16 -0
  77. package/dist/lib/editor-seo-content.d.ts.map +1 -0
  78. package/dist/lib/editor-seo-content.js +139 -0
  79. package/dist/lib/editor-seo-content.js.map +1 -0
  80. package/dist/lib/portal-container.d.ts.map +1 -1
  81. package/dist/lib/portal-container.js +2 -3
  82. package/dist/lib/portal-container.js.map +1 -1
  83. package/dist/lib/seo-service.d.ts +91 -0
  84. package/dist/lib/seo-service.d.ts.map +1 -1
  85. package/dist/lib/seo-service.js +55 -2
  86. package/dist/lib/seo-service.js.map +1 -1
  87. package/dist/views/ApiKeys.js +2 -2
  88. package/dist/views/Dashboard.d.ts.map +1 -1
  89. package/dist/views/Dashboard.js +12 -14
  90. package/dist/views/Dashboard.js.map +1 -1
  91. package/dist/views/MediaBrowser.d.ts.map +1 -1
  92. package/dist/views/MediaBrowser.js +35 -6
  93. package/dist/views/MediaBrowser.js.map +1 -1
  94. package/dist/views/PageEditor.js +1 -1
  95. package/dist/views/Posts/PostTypeEditor.js +3 -3
  96. package/dist/views/SEO.d.ts.map +1 -1
  97. package/dist/views/SEO.js +4 -2
  98. package/dist/views/SEO.js.map +1 -1
  99. package/dist/views/ScriptTags.js +1 -1
  100. package/dist/views/ScriptTags.js.map +1 -1
  101. package/dist/views/Users.js +1 -1
  102. package/dist/views/page-editor/EditorTopBar.d.ts +6 -1
  103. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  104. package/dist/views/page-editor/EditorTopBar.js +5 -2
  105. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  106. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  107. package/dist/views/page-editor/PageSectionEditor.js +36 -1
  108. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  109. package/dist/views/page-editor/PageSettingsModal.d.ts +1 -1
  110. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -1
  111. package/dist/views/page-editor/PageSettingsModal.js +2 -13
  112. package/dist/views/page-editor/PageSettingsModal.js.map +1 -1
  113. package/dist/views/page-editor/SectionInspector.js +2 -2
  114. package/dist/views/page-editor/SectionsPanel.js +1 -1
  115. package/dist/views/post-editor/PostFieldsModal.d.ts +1 -1
  116. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
  117. package/dist/views/post-editor/PostFieldsModal.js +17 -79
  118. package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
  119. package/dist/views/post-editor/PostHeaderPanel.js +1 -1
  120. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  121. package/dist/views/post-editor/PostSectionEditor.js +37 -1
  122. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  123. package/dist/views/seo/ContentTab.d.ts.map +1 -1
  124. package/dist/views/seo/ContentTab.js +7 -1
  125. package/dist/views/seo/ContentTab.js.map +1 -1
  126. package/dist/views/seo/LinksTab.d.ts +4 -0
  127. package/dist/views/seo/LinksTab.d.ts.map +1 -0
  128. package/dist/views/seo/LinksTab.js +111 -0
  129. package/dist/views/seo/LinksTab.js.map +1 -0
  130. package/dist/views/seo/OverviewTab.js +1 -1
  131. package/dist/views/seo/OverviewTab.js.map +1 -1
  132. package/dist/views/seo/RedirectsTab.d.ts.map +1 -1
  133. package/dist/views/seo/RedirectsTab.js +39 -6
  134. package/dist/views/seo/RedirectsTab.js.map +1 -1
  135. package/dist/views/seo/TechnicalTab.js +2 -2
  136. package/dist/views/settings/AISettingsTab.js +1 -1
  137. package/dist/views/settings/AiFeaturesCard.js +1 -1
  138. package/dist/views/settings/AiFeaturesCard.js.map +1 -1
  139. package/dist/views/settings/AppearanceTab.js +1 -1
  140. package/dist/views/settings/BrandVoiceCard.js +1 -1
  141. package/dist/views/settings/EmailSettingsTab.js +2 -2
  142. package/dist/views/settings/GeneralSettingsTab.js +1 -1
  143. package/dist/views/settings/SecurityStatusCard.js +1 -1
  144. package/dist/views/settings/SeoSettingsTab.js +4 -4
  145. package/dist/views/settings/UpdatesTab.js +2 -2
  146. package/dist/views/settings/components.js +3 -3
  147. package/dist/views/settings/components.js.map +1 -1
  148. package/package.json +2 -2
  149. package/src/AdminRoot.tsx +3 -2
  150. package/src/LoginRoot.tsx +2 -1
  151. package/src/__tests__/components/theme-provider-accent.test.tsx +84 -0
  152. package/src/__tests__/views/portal-container.test.tsx +14 -0
  153. package/src/__tests__/views/post-fields-modal.render.test.tsx +3 -9
  154. package/src/__tests__/views/redirects-pane.render.test.tsx +6 -0
  155. package/src/__tests__/views/seo-content-pane.render.test.tsx +16 -4
  156. package/src/components/CoauthorResultPopover.tsx +1 -1
  157. package/src/components/EditorSeoAside.tsx +68 -0
  158. package/src/components/MediaPickerModal.tsx +1 -1
  159. package/src/components/SEOConfigPanel.tsx +2 -2
  160. package/src/components/SchedulePublishDialog.tsx +1 -1
  161. package/src/components/SharePreviewLinkDialog.tsx +1 -1
  162. package/src/components/ThemeProvider.tsx +3 -1
  163. package/src/components/VersionHistory.tsx +1 -1
  164. package/src/components/seo/EditorSeoScoreBadge.tsx +26 -0
  165. package/src/components/seo/SeoAccordion.tsx +76 -0
  166. package/src/components/seo/SeoAnalysisList.tsx +113 -0
  167. package/src/components/seo/SeoEditorPane.tsx +532 -250
  168. package/src/components/seo/SeoInsightsMetrics.tsx +109 -0
  169. package/src/components/seo/SeoIssueFixPanel.tsx +16 -4
  170. package/src/components/seo/SeoLinkingSection.tsx +151 -0
  171. package/src/components/seo/primitives.tsx +7 -3
  172. package/src/components/ui/Card.tsx +5 -5
  173. package/src/components/ui/InspectorPane.tsx +3 -3
  174. package/src/components/ui/PanelSection.tsx +53 -0
  175. package/src/lib/admin-root-element.ts +15 -0
  176. package/src/lib/editor-seo-content.ts +144 -0
  177. package/src/lib/portal-container.ts +3 -2
  178. package/src/lib/seo-service.ts +150 -2
  179. package/src/views/ApiKeys.tsx +2 -2
  180. package/src/views/Dashboard.tsx +40 -53
  181. package/src/views/MediaBrowser.tsx +62 -12
  182. package/src/views/PageEditor.tsx +1 -1
  183. package/src/views/Posts/PostTypeEditor.tsx +3 -3
  184. package/src/views/SEO.tsx +6 -0
  185. package/src/views/ScriptTags.tsx +2 -2
  186. package/src/views/Users.tsx +1 -1
  187. package/src/views/page-editor/EditorTopBar.tsx +27 -0
  188. package/src/views/page-editor/PageSectionEditor.tsx +58 -3
  189. package/src/views/page-editor/PageSettingsModal.tsx +1 -45
  190. package/src/views/page-editor/SectionInspector.tsx +2 -2
  191. package/src/views/page-editor/SectionsPanel.tsx +1 -1
  192. package/src/views/post-editor/PostFieldsModal.tsx +19 -138
  193. package/src/views/post-editor/PostHeaderPanel.tsx +1 -1
  194. package/src/views/post-editor/PostSectionEditor.tsx +60 -3
  195. package/src/views/seo/ContentTab.tsx +11 -5
  196. package/src/views/seo/LinksTab.tsx +462 -0
  197. package/src/views/seo/OverviewTab.tsx +1 -1
  198. package/src/views/seo/RedirectsTab.tsx +97 -4
  199. package/src/views/seo/TechnicalTab.tsx +2 -2
  200. package/src/views/settings/AISettingsTab.tsx +3 -3
  201. package/src/views/settings/AiFeaturesCard.tsx +1 -1
  202. package/src/views/settings/AppearanceTab.tsx +1 -1
  203. package/src/views/settings/BrandVoiceCard.tsx +2 -2
  204. package/src/views/settings/EmailSettingsTab.tsx +2 -2
  205. package/src/views/settings/GeneralSettingsTab.tsx +3 -3
  206. package/src/views/settings/SecurityStatusCard.tsx +1 -1
  207. package/src/views/settings/SeoSettingsTab.tsx +6 -6
  208. package/src/views/settings/UpdatesTab.tsx +3 -3
  209. package/src/views/settings/components.tsx +3 -3
@@ -98,7 +98,7 @@ export function VersionHistory({
98
98
  aria-describedby={undefined}
99
99
  className="bg-card text-card-foreground border-border animate-in slide-in-from-right fixed inset-y-0 right-0 z-50 flex w-full max-w-md flex-col border-l shadow-xl focus:outline-none"
100
100
  >
101
- <div className="border-border flex items-center justify-between border-b px-4 py-3">
101
+ <div className="border-border flex items-center justify-between border-b px-6 py-4">
102
102
  <div className="flex items-center gap-2">
103
103
  <Clock className="text-muted-foreground h-5 w-5" aria-hidden />
104
104
  <Dialog.Title className="text-foreground text-lg font-medium">
@@ -0,0 +1,26 @@
1
+ 'use client'
2
+
3
+ import { gradeFromScore, gradeMeta } from './primitives.js'
4
+
5
+ /** Compact color-coded score for the page/post editor toolbar (`77/100` left of "SEO"). */
6
+ export function EditorSeoScoreBadge({
7
+ score,
8
+ inverted = false,
9
+ }: {
10
+ score: number
11
+ /** When the SEO toggle uses `bg-primary`, render score in primary-foreground. */
12
+ inverted?: boolean
13
+ }) {
14
+ const meta = gradeMeta(gradeFromScore(score))
15
+ return (
16
+ <span
17
+ className={`inline-flex items-baseline gap-0.5 text-xs font-medium tabular-nums ${
18
+ inverted ? 'text-primary-foreground' : meta.text
19
+ }`}
20
+ aria-hidden
21
+ >
22
+ {score}
23
+ <span className={inverted ? 'text-primary-foreground/80' : 'opacity-70'}>/100</span>
24
+ </span>
25
+ )
26
+ }
@@ -0,0 +1,76 @@
1
+ 'use client'
2
+
3
+ import type { ReactNode } from 'react'
4
+ import * as Accordion from '@radix-ui/react-accordion'
5
+ import { ChevronDown } from 'lucide-react'
6
+
7
+ export function SeoAccordion({
8
+ children,
9
+ defaultOpen = ['focus-keyword', 'search-appearance'],
10
+ }: {
11
+ children: ReactNode
12
+ defaultOpen?: string[]
13
+ }) {
14
+ return (
15
+ <Accordion.Root
16
+ type="multiple"
17
+ defaultValue={defaultOpen}
18
+ className="divide-y divide-[var(--bdr)]"
19
+ >
20
+ {children}
21
+ </Accordion.Root>
22
+ )
23
+ }
24
+
25
+ export function SeoAccordionItem({
26
+ value,
27
+ title,
28
+ badge,
29
+ children,
30
+ }: {
31
+ value: string
32
+ title: string
33
+ badge?: ReactNode
34
+ children: ReactNode
35
+ }) {
36
+ return (
37
+ <Accordion.Item value={value} className="border-[var(--bdr)] bg-[var(--sb)]">
38
+ <Accordion.Header>
39
+ <Accordion.Trigger className="group flex w-full items-center justify-between gap-2 px-5 py-3.5 text-left transition-colors duration-[var(--motion-fast)] hover:bg-[var(--bg)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none">
40
+ <span className="flex min-w-0 items-center gap-2">
41
+ <span className="text-[13px] font-medium text-[var(--txt)]">{title}</span>
42
+ {badge}
43
+ </span>
44
+ <ChevronDown
45
+ size={16}
46
+ aria-hidden
47
+ className="shrink-0 text-[var(--muted)] group-data-[state=open]:rotate-180 motion-safe:transition-transform motion-safe:duration-200"
48
+ />
49
+ </Accordion.Trigger>
50
+ </Accordion.Header>
51
+ <Accordion.Content className="overflow-hidden data-[state=closed]:hidden">
52
+ <div className="space-y-3 px-5 pt-1 pb-4">{children}</div>
53
+ </Accordion.Content>
54
+ </Accordion.Item>
55
+ )
56
+ }
57
+
58
+ export function SeoAccordionBadge({
59
+ tone,
60
+ children,
61
+ }: {
62
+ tone: 'neutral' | 'good' | 'warn' | 'bad'
63
+ children: ReactNode
64
+ }) {
65
+ const cls =
66
+ tone === 'good'
67
+ ? 'bg-[var(--suc-l)] text-[var(--suc)]'
68
+ : tone === 'warn'
69
+ ? 'bg-[var(--warn-l)] text-[var(--warn)]'
70
+ : tone === 'bad'
71
+ ? 'bg-[var(--err-l)] text-[var(--err)]'
72
+ : 'bg-[var(--bg)] text-[var(--sub)]'
73
+ return (
74
+ <span className={`rounded-full px-2 py-0.5 text-[10.5px] font-medium ${cls}`}>{children}</span>
75
+ )
76
+ }
@@ -0,0 +1,113 @@
1
+ 'use client'
2
+
3
+ import { AlertCircle, CheckCircle2, Sparkles, Wand2 } from 'lucide-react'
4
+ import type { SEOCheck } from '@actuate-media/cms-core/seo/analysis'
5
+
6
+ export function SeoAnalysisList({
7
+ checks,
8
+ onAiFixField,
9
+ aiFixField,
10
+ }: {
11
+ checks: SEOCheck[]
12
+ onAiFixField?: (field: 'metaTitle' | 'metaDescription' | 'focusKeyword') => void
13
+ aiFixField?: string | null
14
+ }) {
15
+ const problems = checks.filter((c) => c.status === 'problem' || c.status === 'improvement')
16
+ const good = checks.filter((c) => c.status === 'good')
17
+
18
+ if (checks.length === 0) {
19
+ return (
20
+ <p className="text-[12px] text-[var(--muted)]">
21
+ Add content and a focus keyphrase to run SEO checks.
22
+ </p>
23
+ )
24
+ }
25
+
26
+ return (
27
+ <div className="space-y-4">
28
+ {problems.length > 0 && (
29
+ <div>
30
+ <h4 className="mb-2 text-[11px] font-semibold tracking-wide text-[var(--sub)] uppercase">
31
+ Problems ({problems.length})
32
+ </h4>
33
+ <ul className="space-y-2">
34
+ {problems.map((check) => (
35
+ <AnalysisRow
36
+ key={check.id}
37
+ check={check}
38
+ onAiFix={onAiFixField}
39
+ aiBusy={aiFixField}
40
+ />
41
+ ))}
42
+ </ul>
43
+ </div>
44
+ )}
45
+ {good.length > 0 && (
46
+ <div>
47
+ <h4 className="mb-2 text-[11px] font-semibold tracking-wide text-[var(--sub)] uppercase">
48
+ Good results ({good.length})
49
+ </h4>
50
+ <ul className="space-y-2">
51
+ {good.map((check) => (
52
+ <AnalysisRow key={check.id} check={check} />
53
+ ))}
54
+ </ul>
55
+ </div>
56
+ )}
57
+ </div>
58
+ )
59
+ }
60
+
61
+ function AnalysisRow({
62
+ check,
63
+ onAiFix,
64
+ aiBusy,
65
+ }: {
66
+ check: SEOCheck
67
+ onAiFix?: (field: 'metaTitle' | 'metaDescription' | 'focusKeyword') => void
68
+ aiBusy?: string | null
69
+ }) {
70
+ const tone =
71
+ check.status === 'good'
72
+ ? 'text-[var(--suc)]'
73
+ : check.status === 'improvement'
74
+ ? 'text-[var(--warn)]'
75
+ : 'text-[var(--err)]'
76
+ const Icon = check.status === 'good' ? CheckCircle2 : AlertCircle
77
+ const aiField = aiFieldForCheck(check.id)
78
+
79
+ return (
80
+ <li className="flex items-start gap-2 rounded-[7px] border border-[var(--bdr)] bg-[var(--card)] px-2.5 py-2">
81
+ <Icon className={`mt-0.5 h-3.5 w-3.5 shrink-0 ${tone}`} aria-hidden />
82
+ <div className="min-w-0 flex-1">
83
+ <div className="text-[12.5px] font-medium text-[var(--txt)]">{check.title}</div>
84
+ <p className="text-[11.5px] text-[var(--sub)]">{check.description}</p>
85
+ </div>
86
+ {onAiFix && aiField && check.status !== 'good' && (
87
+ <button
88
+ type="button"
89
+ aria-label={`AI help for ${check.title}`}
90
+ disabled={Boolean(aiBusy)}
91
+ onClick={() => onAiFix(aiField)}
92
+ className="inline-flex shrink-0 items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] font-medium text-[var(--acc)] hover:bg-[var(--acc-l)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none disabled:opacity-60"
93
+ >
94
+ {aiBusy === aiField ? (
95
+ <Sparkles className="h-3 w-3 animate-pulse" aria-hidden />
96
+ ) : (
97
+ <Wand2 className="h-3 w-3" aria-hidden />
98
+ )}
99
+ AI
100
+ </button>
101
+ )}
102
+ </li>
103
+ )
104
+ }
105
+
106
+ function aiFieldForCheck(checkId: string): 'metaTitle' | 'metaDescription' | 'focusKeyword' | null {
107
+ if (checkId.includes('meta-title') || checkId.includes('keyphrase-in-title')) return 'metaTitle'
108
+ if (checkId.includes('meta-description') || checkId.includes('keyphrase-in-meta-description')) {
109
+ return 'metaDescription'
110
+ }
111
+ if (checkId.includes('keyphrase')) return 'focusKeyword'
112
+ return null
113
+ }