@actuate-media/cms-admin 0.74.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 (81) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/__tests__/views/post-fields-modal.render.test.js +3 -6
  3. package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -1
  4. package/dist/__tests__/views/seo-content-pane.render.test.js +2 -0
  5. package/dist/__tests__/views/seo-content-pane.render.test.js.map +1 -1
  6. package/dist/actuate-admin.css +1 -1
  7. package/dist/components/EditorSeoAside.d.ts +7 -4
  8. package/dist/components/EditorSeoAside.d.ts.map +1 -1
  9. package/dist/components/EditorSeoAside.js +7 -16
  10. package/dist/components/EditorSeoAside.js.map +1 -1
  11. package/dist/components/seo/EditorSeoScoreBadge.d.ts +7 -0
  12. package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -0
  13. package/dist/components/seo/EditorSeoScoreBadge.js +9 -0
  14. package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -0
  15. package/dist/components/seo/SeoAccordion.d.ts +16 -0
  16. package/dist/components/seo/SeoAccordion.d.ts.map +1 -0
  17. package/dist/components/seo/SeoAccordion.js +21 -0
  18. package/dist/components/seo/SeoAccordion.js.map +1 -0
  19. package/dist/components/seo/SeoAnalysisList.d.ts +7 -0
  20. package/dist/components/seo/SeoAnalysisList.d.ts.map +1 -0
  21. package/dist/components/seo/SeoAnalysisList.js +32 -0
  22. package/dist/components/seo/SeoAnalysisList.js.map +1 -0
  23. package/dist/components/seo/SeoEditorPane.d.ts +15 -4
  24. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
  25. package/dist/components/seo/SeoEditorPane.js +131 -31
  26. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  27. package/dist/components/seo/SeoInsightsMetrics.d.ts +6 -0
  28. package/dist/components/seo/SeoInsightsMetrics.d.ts.map +1 -0
  29. package/dist/components/seo/SeoInsightsMetrics.js +16 -0
  30. package/dist/components/seo/SeoInsightsMetrics.js.map +1 -0
  31. package/dist/components/seo/SeoLinkingSection.d.ts +9 -0
  32. package/dist/components/seo/SeoLinkingSection.d.ts.map +1 -0
  33. package/dist/components/seo/SeoLinkingSection.js +44 -0
  34. package/dist/components/seo/SeoLinkingSection.js.map +1 -0
  35. package/dist/lib/editor-seo-content.d.ts +16 -0
  36. package/dist/lib/editor-seo-content.d.ts.map +1 -0
  37. package/dist/lib/editor-seo-content.js +139 -0
  38. package/dist/lib/editor-seo-content.js.map +1 -0
  39. package/dist/lib/seo-service.d.ts +2 -0
  40. package/dist/lib/seo-service.d.ts.map +1 -1
  41. package/dist/lib/seo-service.js +5 -0
  42. package/dist/lib/seo-service.js.map +1 -1
  43. package/dist/views/page-editor/EditorTopBar.d.ts +3 -1
  44. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  45. package/dist/views/page-editor/EditorTopBar.js +5 -4
  46. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  47. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  48. package/dist/views/page-editor/PageSectionEditor.js +13 -1
  49. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  50. package/dist/views/page-editor/PageSettingsModal.d.ts +1 -1
  51. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -1
  52. package/dist/views/page-editor/PageSettingsModal.js +2 -13
  53. package/dist/views/page-editor/PageSettingsModal.js.map +1 -1
  54. package/dist/views/post-editor/PostFieldsModal.d.ts +1 -1
  55. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
  56. package/dist/views/post-editor/PostFieldsModal.js +17 -79
  57. package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
  58. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  59. package/dist/views/post-editor/PostSectionEditor.js +14 -1
  60. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  61. package/dist/views/seo/ContentTab.d.ts.map +1 -1
  62. package/dist/views/seo/ContentTab.js +7 -1
  63. package/dist/views/seo/ContentTab.js.map +1 -1
  64. package/package.json +2 -2
  65. package/src/__tests__/views/post-fields-modal.render.test.tsx +3 -9
  66. package/src/__tests__/views/seo-content-pane.render.test.tsx +2 -0
  67. package/src/components/EditorSeoAside.tsx +27 -108
  68. package/src/components/seo/EditorSeoScoreBadge.tsx +26 -0
  69. package/src/components/seo/SeoAccordion.tsx +76 -0
  70. package/src/components/seo/SeoAnalysisList.tsx +113 -0
  71. package/src/components/seo/SeoEditorPane.tsx +497 -244
  72. package/src/components/seo/SeoInsightsMetrics.tsx +109 -0
  73. package/src/components/seo/SeoLinkingSection.tsx +151 -0
  74. package/src/lib/editor-seo-content.ts +144 -0
  75. package/src/lib/seo-service.ts +7 -0
  76. package/src/views/page-editor/EditorTopBar.tsx +7 -4
  77. package/src/views/page-editor/PageSectionEditor.tsx +15 -0
  78. package/src/views/page-editor/PageSettingsModal.tsx +1 -45
  79. package/src/views/post-editor/PostFieldsModal.tsx +19 -138
  80. package/src/views/post-editor/PostSectionEditor.tsx +17 -0
  81. package/src/views/seo/ContentTab.tsx +11 -5
@@ -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
+ }