@actuate-media/cms-admin 0.54.0 → 0.56.2
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 +30 -0
- package/dist/__tests__/components/posts-badges.test.js +3 -3
- package/dist/__tests__/components/posts-badges.test.js.map +1 -1
- package/dist/__tests__/lib/page-editor-service.test.js +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/AICoauthorBubbleMenu.js +2 -2
- package/dist/components/AICoauthorBubbleMenu.js.map +1 -1
- package/dist/components/Breadcrumbs.js +2 -2
- package/dist/components/Breadcrumbs.js.map +1 -1
- package/dist/components/CoauthorResultPopover.d.ts.map +1 -1
- package/dist/components/CoauthorResultPopover.js +1 -1
- package/dist/components/CoauthorResultPopover.js.map +1 -1
- package/dist/components/CommandPalette.d.ts.map +1 -1
- package/dist/components/CommandPalette.js +11 -11
- package/dist/components/CommandPalette.js.map +1 -1
- package/dist/components/CommentSidePanel.js +11 -11
- package/dist/components/CommentSidePanel.js.map +1 -1
- package/dist/components/ContentOverviewChart.js +2 -2
- package/dist/components/ContentOverviewChart.js.map +1 -1
- package/dist/components/FocalPointPicker.js +1 -1
- package/dist/components/FocalPointPicker.js.map +1 -1
- package/dist/components/LivePreview.d.ts.map +1 -1
- package/dist/components/LivePreview.js +5 -5
- package/dist/components/LivePreview.js.map +1 -1
- package/dist/components/LogoPlaceholder.js +1 -1
- package/dist/components/LogoPlaceholder.js.map +1 -1
- package/dist/components/MentionableTextarea.js +3 -3
- package/dist/components/MentionableTextarea.js.map +1 -1
- package/dist/components/NotificationBell.js +1 -1
- package/dist/components/NotificationBell.js.map +1 -1
- package/dist/components/OfflineStatus.js +8 -8
- package/dist/components/OfflineStatus.js.map +1 -1
- package/dist/components/PresenceChips.d.ts.map +1 -1
- package/dist/components/PresenceChips.js +10 -6
- package/dist/components/PresenceChips.js.map +1 -1
- package/dist/components/SEOConfigPanel.d.ts.map +1 -1
- package/dist/components/SEOConfigPanel.js +10 -10
- package/dist/components/SEOConfigPanel.js.map +1 -1
- package/dist/components/SEOPanel.js +12 -12
- package/dist/components/SEOPanel.js.map +1 -1
- package/dist/components/SEOPerformance.d.ts.map +1 -1
- package/dist/components/SEOPerformance.js +3 -3
- package/dist/components/SEOPerformance.js.map +1 -1
- package/dist/components/TipTapEditor.js +6 -6
- package/dist/components/TipTapEditor.js.map +1 -1
- package/dist/components/pages/tag-badges.js +6 -6
- package/dist/components/pages/tag-badges.js.map +1 -1
- package/dist/components/posts/badges.d.ts +0 -4
- package/dist/components/posts/badges.d.ts.map +1 -1
- package/dist/components/posts/badges.js +28 -42
- package/dist/components/posts/badges.js.map +1 -1
- package/dist/components/ui/Badge.js +5 -5
- package/dist/components/ui/Badge.js.map +1 -1
- package/dist/components/ui/Toast.d.ts.map +1 -1
- package/dist/components/ui/Toast.js +4 -4
- package/dist/components/ui/Toast.js.map +1 -1
- package/dist/fields/ComponentBlockField.js +2 -2
- package/dist/fields/ComponentBlockField.js.map +1 -1
- package/dist/fields/FieldRenderer.js +1 -1
- package/dist/fields/FieldRenderer.js.map +1 -1
- package/dist/fields/PropInput.js +1 -1
- package/dist/fields/PropInput.js.map +1 -1
- package/dist/fields/RelationshipField.js +5 -5
- package/dist/fields/RelationshipField.js.map +1 -1
- package/dist/fields/ToggleField.js +1 -1
- package/dist/fields/ToggleField.js.map +1 -1
- package/dist/lib/seo-service.d.ts +0 -2
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/views/Dashboard.js +5 -5
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/DocumentEdit.d.ts.map +1 -1
- package/dist/views/DocumentEdit.js +5 -5
- package/dist/views/DocumentEdit.js.map +1 -1
- package/dist/views/ForgotPassword.d.ts.map +1 -1
- package/dist/views/ForgotPassword.js +3 -2
- package/dist/views/ForgotPassword.js.map +1 -1
- package/dist/views/FormEditor.d.ts.map +1 -1
- package/dist/views/FormEditor.js +5 -5
- package/dist/views/FormEditor.js.map +1 -1
- package/dist/views/Login.d.ts.map +1 -1
- package/dist/views/Login.js +17 -17
- package/dist/views/Login.js.map +1 -1
- package/dist/views/PageEditor.d.ts.map +1 -1
- package/dist/views/PageEditor.js +2 -2
- package/dist/views/PageEditor.js.map +1 -1
- package/dist/views/Pages/PagesListView.js +6 -6
- package/dist/views/Pages/PagesListView.js.map +1 -1
- package/dist/views/Pages.d.ts.map +1 -1
- package/dist/views/Pages.js +16 -19
- package/dist/views/Pages.js.map +1 -1
- package/dist/views/ResetPassword.d.ts.map +1 -1
- package/dist/views/ResetPassword.js +3 -2
- package/dist/views/ResetPassword.js.map +1 -1
- package/dist/views/Settings.js +2 -2
- package/dist/views/Settings.js.map +1 -1
- package/dist/views/SetupWizard.d.ts.map +1 -1
- package/dist/views/SetupWizard.js +6 -5
- package/dist/views/SetupWizard.js.map +1 -1
- package/dist/views/page-builder/DesignScore.js +12 -12
- package/dist/views/page-builder/DesignScore.js.map +1 -1
- package/dist/views/page-builder/SEOPanel.js +9 -9
- package/dist/views/page-builder/SEOPanel.js.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +1 -1
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +2 -2
- package/dist/views/page-editor/EditorTopBar.js.map +1 -1
- package/dist/views/page-editor/PagePreview.js +1 -1
- package/dist/views/page-editor/PagePreview.js.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +1 -1
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.js +1 -1
- package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.js +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
- package/dist/views/page-editor/sections/AuthorBioSection.js +2 -2
- package/dist/views/page-editor/sections/AuthorBioSection.js.map +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/EmbedSection.d.ts +10 -0
- package/dist/views/page-editor/sections/EmbedSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/EmbedSection.js +24 -0
- package/dist/views/page-editor/sections/EmbedSection.js.map +1 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
- package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.js +1 -1
- package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.js +1 -1
- package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +5 -1
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/page-editor/sections/parts.js +6 -6
- package/dist/views/page-editor/sections/parts.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +2 -2
- package/dist/views/post-editor/PostEditorCanvas.js.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/dist/views/post-editor/PostPreview.js +1 -1
- package/dist/views/post-editor/PostPreview.js.map +1 -1
- package/dist/views/profile/AccountSecurity.js +1 -1
- package/dist/views/profile/AccountSecurity.js.map +1 -1
- package/dist/views/seo/TechnicalTab.d.ts.map +1 -1
- package/dist/views/seo/TechnicalTab.js +0 -10
- package/dist/views/seo/TechnicalTab.js.map +1 -1
- package/dist/views/settings/components.js +1 -1
- package/dist/views/settings/components.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/components/posts-badges.test.tsx +3 -3
- package/src/__tests__/lib/page-editor-service.test.ts +1 -0
- package/src/components/AICoauthorBubbleMenu.tsx +4 -4
- package/src/components/Breadcrumbs.tsx +5 -5
- package/src/components/CoauthorResultPopover.tsx +26 -23
- package/src/components/CommandPalette.tsx +41 -39
- package/src/components/CommentSidePanel.tsx +20 -20
- package/src/components/ContentOverviewChart.tsx +4 -4
- package/src/components/FocalPointPicker.tsx +2 -2
- package/src/components/LivePreview.tsx +20 -17
- package/src/components/LogoPlaceholder.tsx +1 -1
- package/src/components/MentionableTextarea.tsx +5 -5
- package/src/components/NotificationBell.tsx +10 -10
- package/src/components/OfflineStatus.tsx +8 -8
- package/src/components/PresenceChips.tsx +12 -8
- package/src/components/SEOConfigPanel.tsx +40 -38
- package/src/components/SEOPanel.tsx +15 -15
- package/src/components/SEOPerformance.tsx +25 -23
- package/src/components/TipTapEditor.tsx +7 -7
- package/src/components/pages/tag-badges.tsx +6 -6
- package/src/components/posts/badges.tsx +28 -46
- package/src/components/ui/Badge.tsx +5 -5
- package/src/components/ui/Toast.tsx +4 -5
- package/src/fields/ComponentBlockField.tsx +2 -2
- package/src/fields/FieldRenderer.tsx +1 -1
- package/src/fields/PropInput.tsx +1 -1
- package/src/fields/RelationshipField.tsx +5 -5
- package/src/fields/ToggleField.tsx +1 -1
- package/src/lib/seo-service.ts +0 -2
- package/src/views/Dashboard.tsx +5 -5
- package/src/views/DocumentEdit.tsx +13 -11
- package/src/views/ForgotPassword.tsx +21 -18
- package/src/views/FormEditor.tsx +74 -69
- package/src/views/Login.tsx +45 -39
- package/src/views/PageEditor.tsx +26 -22
- package/src/views/Pages/PagesListView.tsx +6 -6
- package/src/views/Pages.tsx +64 -65
- package/src/views/ResetPassword.tsx +32 -29
- package/src/views/Settings.tsx +2 -2
- package/src/views/SetupWizard.tsx +42 -33
- package/src/views/page-builder/DesignScore.tsx +12 -12
- package/src/views/page-builder/SEOPanel.tsx +12 -12
- package/src/views/page-editor/EditorCanvas.tsx +1 -1
- package/src/views/page-editor/EditorTopBar.tsx +2 -2
- package/src/views/page-editor/PagePreview.tsx +1 -1
- package/src/views/page-editor/SectionInspector.tsx +1 -1
- package/src/views/page-editor/SitePreviewFrame.tsx +1 -1
- package/src/views/page-editor/sections/ArticleBodySection.tsx +5 -5
- package/src/views/page-editor/sections/AuthorBioSection.tsx +6 -6
- package/src/views/page-editor/sections/ByTheNumbersSection.tsx +5 -5
- package/src/views/page-editor/sections/CtaBannerSection.tsx +1 -1
- package/src/views/page-editor/sections/EmbedSection.tsx +43 -0
- package/src/views/page-editor/sections/FeatureHighlightSection.tsx +7 -5
- package/src/views/page-editor/sections/GallerySection.tsx +6 -4
- package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
- package/src/views/page-editor/sections/MissionSection.tsx +4 -2
- package/src/views/page-editor/sections/PullQuoteSection.tsx +4 -4
- package/src/views/page-editor/sections/RelatedPostsSection.tsx +5 -5
- package/src/views/page-editor/sections/SectionRenderer.tsx +6 -2
- package/src/views/page-editor/sections/parts.tsx +6 -6
- package/src/views/post-editor/PostEditorCanvas.tsx +4 -4
- package/src/views/post-editor/PostHeader.tsx +5 -5
- package/src/views/post-editor/PostHeaderPanel.tsx +1 -1
- package/src/views/post-editor/PostPreview.tsx +1 -1
- package/src/views/profile/AccountSecurity.tsx +1 -1
- package/src/views/seo/TechnicalTab.tsx +0 -10
- package/src/views/settings/components.tsx +1 -1
|
@@ -192,7 +192,7 @@ export function SEOConfigPanel() {
|
|
|
192
192
|
|
|
193
193
|
if (loading) {
|
|
194
194
|
return (
|
|
195
|
-
<div className="flex items-center gap-2 py-8 text-sm
|
|
195
|
+
<div className="text-muted-foreground flex items-center gap-2 py-8 text-sm">
|
|
196
196
|
<Loader2 className="h-4 w-4 animate-spin" /> Loading SEO defaults…
|
|
197
197
|
</div>
|
|
198
198
|
)
|
|
@@ -200,12 +200,12 @@ export function SEOConfigPanel() {
|
|
|
200
200
|
|
|
201
201
|
if (error) {
|
|
202
202
|
return (
|
|
203
|
-
<div className="
|
|
203
|
+
<div className="border-destructive/30 bg-destructive/10 text-destructive rounded-lg border p-4 text-sm">
|
|
204
204
|
<div className="mb-2 font-medium">Failed to load SEO config</div>
|
|
205
205
|
<div className="font-mono text-xs">{error}</div>
|
|
206
206
|
<button
|
|
207
207
|
onClick={() => void load()}
|
|
208
|
-
className="mt-3 inline-flex items-center gap-1.5 rounded-md border
|
|
208
|
+
className="border-destructive bg-card text-destructive hover:bg-destructive/10 mt-3 inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-xs font-medium"
|
|
209
209
|
>
|
|
210
210
|
<RefreshCw className="h-3.5 w-3.5" /> Retry
|
|
211
211
|
</button>
|
|
@@ -217,21 +217,23 @@ export function SEOConfigPanel() {
|
|
|
217
217
|
<div className="space-y-6">
|
|
218
218
|
<div className="flex items-start justify-between gap-4">
|
|
219
219
|
<div>
|
|
220
|
-
<p className="text-
|
|
220
|
+
<p className="text-muted-foreground text-sm">
|
|
221
221
|
Edit the SEO defaults that flow into{' '}
|
|
222
|
-
<code className="
|
|
223
|
-
<code className="
|
|
224
|
-
<code className="
|
|
225
|
-
<code className="
|
|
222
|
+
<code className="bg-muted rounded px-1 py-0.5 text-[11px]">/sitemap.xml</code>,{' '}
|
|
223
|
+
<code className="bg-muted rounded px-1 py-0.5 text-[11px]">/robots.txt</code>,{' '}
|
|
224
|
+
<code className="bg-muted rounded px-1 py-0.5 text-[11px]">/og.png</code>, and the{' '}
|
|
225
|
+
<code className="bg-muted rounded px-1 py-0.5 text-[11px]">/resolve</code> meta +
|
|
226
226
|
JSON-LD response. Empty fields fall back to{' '}
|
|
227
|
-
<code className="
|
|
227
|
+
<code className="bg-muted rounded px-1 py-0.5 text-[11px]">actuate.config.ts</code>.
|
|
228
228
|
</p>
|
|
229
|
-
{updatedAt &&
|
|
229
|
+
{updatedAt && (
|
|
230
|
+
<p className="text-muted-foreground mt-1 text-xs">Last saved: {updatedAt}</p>
|
|
231
|
+
)}
|
|
230
232
|
</div>
|
|
231
233
|
<button
|
|
232
234
|
onClick={handleSave}
|
|
233
235
|
disabled={saving}
|
|
234
|
-
className="inline-flex shrink-0 items-center gap-1.5 rounded-md
|
|
236
|
+
className="bg-primary hover:bg-primary/90 inline-flex shrink-0 items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-white shadow-sm disabled:opacity-50"
|
|
235
237
|
>
|
|
236
238
|
{saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
|
|
237
239
|
Save SEO defaults
|
|
@@ -239,10 +241,10 @@ export function SEOConfigPanel() {
|
|
|
239
241
|
</div>
|
|
240
242
|
|
|
241
243
|
{/* Site-wide section */}
|
|
242
|
-
<section className="
|
|
243
|
-
<header className="flex items-center gap-2 border-b
|
|
244
|
-
<Globe className="h-4 w-4
|
|
245
|
-
<h3 className="text-sm font-medium
|
|
244
|
+
<section className="border-border bg-card rounded-lg border">
|
|
245
|
+
<header className="border-border flex items-center gap-2 border-b px-4 py-3">
|
|
246
|
+
<Globe className="text-primary h-4 w-4" />
|
|
247
|
+
<h3 className="text-foreground text-sm font-medium">Site-wide defaults</h3>
|
|
246
248
|
</header>
|
|
247
249
|
<div className="grid grid-cols-1 gap-4 p-4 md:grid-cols-2">
|
|
248
250
|
<Field
|
|
@@ -330,11 +332,11 @@ export function SEOConfigPanel() {
|
|
|
330
332
|
</section>
|
|
331
333
|
|
|
332
334
|
{/* Per-collection section */}
|
|
333
|
-
<section className="
|
|
334
|
-
<header className="flex items-center gap-2 border-b
|
|
335
|
-
<FileText className="h-4 w-4
|
|
336
|
-
<h3 className="text-sm font-medium
|
|
337
|
-
<span className="text-
|
|
335
|
+
<section className="border-border bg-card rounded-lg border">
|
|
336
|
+
<header className="border-border flex items-center gap-2 border-b px-4 py-3">
|
|
337
|
+
<FileText className="text-primary h-4 w-4" />
|
|
338
|
+
<h3 className="text-foreground text-sm font-medium">Per-collection defaults</h3>
|
|
339
|
+
<span className="text-muted-foreground text-xs">
|
|
338
340
|
— shown placeholders are the static defaults
|
|
339
341
|
</span>
|
|
340
342
|
</header>
|
|
@@ -346,14 +348,14 @@ export function SEOConfigPanel() {
|
|
|
346
348
|
<div key={col.slug} className="p-4">
|
|
347
349
|
<div className="mb-3 flex items-baseline justify-between gap-4">
|
|
348
350
|
<div>
|
|
349
|
-
<h4 className="text-sm font-medium
|
|
350
|
-
<p className="text-
|
|
351
|
-
<code className="
|
|
351
|
+
<h4 className="text-foreground text-sm font-medium">{col.label}</h4>
|
|
352
|
+
<p className="text-muted-foreground text-xs">
|
|
353
|
+
<code className="bg-muted rounded px-1 py-0.5">{col.slug}</code>
|
|
352
354
|
{col.urlPrefix !== undefined && (
|
|
353
355
|
<>
|
|
354
356
|
{' '}
|
|
355
357
|
prefix:{' '}
|
|
356
|
-
<code className="
|
|
358
|
+
<code className="bg-muted rounded px-1 py-0.5">/{col.urlPrefix}</code>
|
|
357
359
|
</>
|
|
358
360
|
)}{' '}
|
|
359
361
|
— {col.type}
|
|
@@ -414,7 +416,7 @@ export function SEOConfigPanel() {
|
|
|
414
416
|
)
|
|
415
417
|
})}
|
|
416
418
|
{data?.collections.length === 0 && (
|
|
417
|
-
<div className="p-4 text-sm
|
|
419
|
+
<div className="text-muted-foreground p-4 text-sm">
|
|
418
420
|
No collections configured in <code>actuate.config.ts</code>.
|
|
419
421
|
</div>
|
|
420
422
|
)}
|
|
@@ -425,7 +427,7 @@ export function SEOConfigPanel() {
|
|
|
425
427
|
<button
|
|
426
428
|
onClick={handleSave}
|
|
427
429
|
disabled={saving}
|
|
428
|
-
className="inline-flex items-center gap-1.5 rounded-md
|
|
430
|
+
className="bg-primary hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-sm font-medium text-white shadow-sm disabled:opacity-50"
|
|
429
431
|
>
|
|
430
432
|
{saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
|
|
431
433
|
Save SEO defaults
|
|
@@ -465,15 +467,15 @@ function Field({
|
|
|
465
467
|
}) {
|
|
466
468
|
return (
|
|
467
469
|
<div>
|
|
468
|
-
<label className="mb-1 block text-xs font-medium
|
|
470
|
+
<label className="text-foreground mb-1 block text-xs font-medium">{label}</label>
|
|
469
471
|
<input
|
|
470
472
|
type="text"
|
|
471
473
|
value={value}
|
|
472
474
|
placeholder={placeholder}
|
|
473
475
|
onChange={(e) => onChange(e.target.value)}
|
|
474
|
-
className="w-full rounded-md border
|
|
476
|
+
className="border-border text-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-md border px-2.5 py-1.5 text-sm placeholder-gray-400 focus:ring-1 focus:outline-none"
|
|
475
477
|
/>
|
|
476
|
-
{hint && <p className="mt-1 text-[11px]
|
|
478
|
+
{hint && <p className="text-muted-foreground mt-1 text-[11px]">{hint}</p>}
|
|
477
479
|
</div>
|
|
478
480
|
)
|
|
479
481
|
}
|
|
@@ -499,7 +501,7 @@ function NumberField({
|
|
|
499
501
|
}) {
|
|
500
502
|
return (
|
|
501
503
|
<div>
|
|
502
|
-
<label className="mb-1 block text-xs font-medium
|
|
504
|
+
<label className="text-foreground mb-1 block text-xs font-medium">{label}</label>
|
|
503
505
|
<input
|
|
504
506
|
type="number"
|
|
505
507
|
value={value ?? ''}
|
|
@@ -513,9 +515,9 @@ function NumberField({
|
|
|
513
515
|
const n = Number(raw)
|
|
514
516
|
if (Number.isFinite(n)) onChange(n)
|
|
515
517
|
}}
|
|
516
|
-
className="w-full rounded-md border
|
|
518
|
+
className="border-border text-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-md border px-2.5 py-1.5 text-sm placeholder-gray-400 focus:ring-1 focus:outline-none"
|
|
517
519
|
/>
|
|
518
|
-
{hint && <p className="mt-1 text-[11px]
|
|
520
|
+
{hint && <p className="text-muted-foreground mt-1 text-[11px]">{hint}</p>}
|
|
519
521
|
</div>
|
|
520
522
|
)
|
|
521
523
|
}
|
|
@@ -537,11 +539,11 @@ function Select({
|
|
|
537
539
|
}) {
|
|
538
540
|
return (
|
|
539
541
|
<div>
|
|
540
|
-
<label className="mb-1 block text-xs font-medium
|
|
542
|
+
<label className="text-foreground mb-1 block text-xs font-medium">{label}</label>
|
|
541
543
|
<select
|
|
542
544
|
value={value}
|
|
543
545
|
onChange={(e) => onChange(e.target.value)}
|
|
544
|
-
className="
|
|
546
|
+
className="border-border bg-card text-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-md border px-2.5 py-1.5 text-sm focus:ring-1 focus:outline-none"
|
|
545
547
|
>
|
|
546
548
|
{options.map((opt) => (
|
|
547
549
|
<option key={opt} value={opt}>
|
|
@@ -549,7 +551,7 @@ function Select({
|
|
|
549
551
|
</option>
|
|
550
552
|
))}
|
|
551
553
|
</select>
|
|
552
|
-
{hint && <p className="mt-1 text-[11px]
|
|
554
|
+
{hint && <p className="text-muted-foreground mt-1 text-[11px]">{hint}</p>}
|
|
553
555
|
</div>
|
|
554
556
|
)
|
|
555
557
|
}
|
|
@@ -566,16 +568,16 @@ function Toggle({
|
|
|
566
568
|
hint?: string
|
|
567
569
|
}) {
|
|
568
570
|
return (
|
|
569
|
-
<label className="flex cursor-pointer items-center gap-2 text-sm
|
|
571
|
+
<label className="text-foreground flex cursor-pointer items-center gap-2 text-sm">
|
|
570
572
|
<input
|
|
571
573
|
type="checkbox"
|
|
572
574
|
checked={checked}
|
|
573
575
|
onChange={(e) => onChange(e.target.checked)}
|
|
574
|
-
className="
|
|
576
|
+
className="border-border text-primary focus-visible:ring-ring/50 h-4 w-4 rounded"
|
|
575
577
|
/>
|
|
576
578
|
<span>
|
|
577
579
|
{label}
|
|
578
|
-
{hint && <span className="ml-1 text-[11px]
|
|
580
|
+
{hint && <span className="text-muted-foreground ml-1 text-[11px]">— {hint}</span>}
|
|
579
581
|
</span>
|
|
580
582
|
</label>
|
|
581
583
|
)
|
|
@@ -387,9 +387,9 @@ function computeOverallScore(checks: SEOCheck[]): number {
|
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
function StatusDot({ status }: { status: 'good' | 'ok' | 'bad' }) {
|
|
390
|
-
if (status === 'good') return <CheckCircle2 className="h-4 w-4 shrink-0
|
|
391
|
-
if (status === 'ok') return <AlertCircle className="h-4 w-4 shrink-0
|
|
392
|
-
return <XCircle className="h-4 w-4 shrink-0
|
|
390
|
+
if (status === 'good') return <CheckCircle2 className="text-success h-4 w-4 shrink-0" />
|
|
391
|
+
if (status === 'ok') return <AlertCircle className="text-warning h-4 w-4 shrink-0" />
|
|
392
|
+
return <XCircle className="text-destructive h-4 w-4 shrink-0" />
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
function ScoreRing({ score }: { score: number }) {
|
|
@@ -486,7 +486,7 @@ function ToggleSwitch({
|
|
|
486
486
|
className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full transition-colors ${checked ? 'bg-[var(--primary)]' : 'bg-[var(--muted)]'}`}
|
|
487
487
|
>
|
|
488
488
|
<span
|
|
489
|
-
className={`pointer-events-none mt-0.5 block h-5 w-5 rounded-full
|
|
489
|
+
className={`bg-card pointer-events-none mt-0.5 block h-5 w-5 rounded-full shadow-sm transition-transform ${checked ? 'translate-x-[22px]' : 'translate-x-0.5'}`}
|
|
490
490
|
/>
|
|
491
491
|
</button>
|
|
492
492
|
</div>
|
|
@@ -616,10 +616,10 @@ export function SEOPanel({
|
|
|
616
616
|
: 'Very difficult'
|
|
617
617
|
const fleschColor =
|
|
618
618
|
readability.fleschScore >= 60
|
|
619
|
-
? 'text-
|
|
619
|
+
? 'text-success'
|
|
620
620
|
: readability.fleschScore >= 30
|
|
621
|
-
? 'text-
|
|
622
|
-
: 'text-
|
|
621
|
+
? 'text-warning'
|
|
622
|
+
: 'text-destructive'
|
|
623
623
|
|
|
624
624
|
return (
|
|
625
625
|
<div className="rounded-lg border border-[var(--border)] bg-[var(--card)]">
|
|
@@ -643,13 +643,13 @@ export function SEOPanel({
|
|
|
643
643
|
<ScoreRing score={score} />
|
|
644
644
|
<div className="space-y-0.5 text-xs text-[var(--muted-foreground)]">
|
|
645
645
|
<div className="flex items-center gap-1.5">
|
|
646
|
-
<CheckCircle2 className="h-3 w-3
|
|
646
|
+
<CheckCircle2 className="text-success h-3 w-3" /> {goodCount} passed
|
|
647
647
|
</div>
|
|
648
648
|
<div className="flex items-center gap-1.5">
|
|
649
|
-
<AlertCircle className="h-3 w-3
|
|
649
|
+
<AlertCircle className="text-warning h-3 w-3" /> {okCount} improvements
|
|
650
650
|
</div>
|
|
651
651
|
<div className="flex items-center gap-1.5">
|
|
652
|
-
<XCircle className="h-3 w-3
|
|
652
|
+
<XCircle className="text-destructive h-3 w-3" /> {badCount} issues
|
|
653
653
|
</div>
|
|
654
654
|
</div>
|
|
655
655
|
</div>
|
|
@@ -723,8 +723,8 @@ export function SEOPanel({
|
|
|
723
723
|
<span
|
|
724
724
|
className={
|
|
725
725
|
readability.passiveEstimate > 15
|
|
726
|
-
? 'font-medium
|
|
727
|
-
: 'font-medium
|
|
726
|
+
? 'text-warning font-medium'
|
|
727
|
+
: 'text-success font-medium'
|
|
728
728
|
}
|
|
729
729
|
>
|
|
730
730
|
{readability.passiveEstimate}%
|
|
@@ -849,10 +849,10 @@ export function SEOPanel({
|
|
|
849
849
|
onToggle={toggleSection}
|
|
850
850
|
>
|
|
851
851
|
<div className="rounded-lg border border-[var(--border)] bg-[var(--background)] p-3">
|
|
852
|
-
<div className="line-clamp-1 cursor-pointer text-sm
|
|
852
|
+
<div className="text-primary line-clamp-1 cursor-pointer text-sm hover:underline">
|
|
853
853
|
{metaTitle || title || 'Page Title'}
|
|
854
854
|
</div>
|
|
855
|
-
<div className="mt-1 truncate text-xs
|
|
855
|
+
<div className="text-success mt-1 truncate text-xs">
|
|
856
856
|
{siteUrl}/{slug}
|
|
857
857
|
</div>
|
|
858
858
|
<div className="mt-1 line-clamp-2 text-sm text-[var(--muted-foreground)]">
|
|
@@ -971,7 +971,7 @@ export function SEOPanel({
|
|
|
971
971
|
onChange={(v) => update({ isCornerstone: v })}
|
|
972
972
|
/>
|
|
973
973
|
{seoData.isCornerstone && (
|
|
974
|
-
<div className="flex items-center gap-1.5 rounded-md
|
|
974
|
+
<div className="text-warning bg-warning/10 flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs">
|
|
975
975
|
<Star className="h-3.5 w-3.5" />
|
|
976
976
|
Cornerstone content is held to stricter SEO standards
|
|
977
977
|
</div>
|
|
@@ -23,8 +23,8 @@ interface SEOSummary {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function ScoreBadge({ score }: { score: number }) {
|
|
26
|
-
const color = score >= 80 ? 'text-
|
|
27
|
-
const bg = score >= 80 ? 'bg-
|
|
26
|
+
const color = score >= 80 ? 'text-success' : score >= 60 ? 'text-warning' : 'text-destructive'
|
|
27
|
+
const bg = score >= 80 ? 'bg-success/10' : score >= 60 ? 'bg-warning/10' : 'bg-destructive/10'
|
|
28
28
|
return (
|
|
29
29
|
<span
|
|
30
30
|
className={`inline-flex h-9 w-9 items-center justify-center rounded-full text-sm font-medium ${color} ${bg}`}
|
|
@@ -53,38 +53,40 @@ export function SEOPerformance({ onNavigate }: SEOPerformanceProps) {
|
|
|
53
53
|
const visible = topContent.slice(page * perPage, (page + 1) * perPage)
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
|
-
<div className="
|
|
57
|
-
<div className="flex items-center justify-between border-b
|
|
56
|
+
<div className="border-border bg-card rounded-lg border">
|
|
57
|
+
<div className="border-border flex items-center justify-between border-b p-4">
|
|
58
58
|
<div className="flex items-center gap-2">
|
|
59
|
-
<Search className="h-4 w-4
|
|
60
|
-
<h2 className="text-sm font-medium
|
|
59
|
+
<Search className="text-muted-foreground h-4 w-4" />
|
|
60
|
+
<h2 className="text-foreground text-sm font-medium">SEO Performance</h2>
|
|
61
61
|
</div>
|
|
62
62
|
{totalIssues > 0 && (
|
|
63
|
-
<span className="text-
|
|
63
|
+
<span className="text-muted-foreground text-xs">
|
|
64
64
|
{totalIssues} issue{totalIssues !== 1 ? 's' : ''} found
|
|
65
65
|
</span>
|
|
66
66
|
)}
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
|
-
<div className="grid grid-cols-1 divide-y
|
|
69
|
+
<div className="divide-border grid grid-cols-1 divide-y lg:grid-cols-12 lg:divide-x lg:divide-y-0">
|
|
70
70
|
<div className="p-4 lg:col-span-7">
|
|
71
|
-
<h3 className="mb-3 text-sm font-medium
|
|
71
|
+
<h3 className="text-foreground mb-3 text-sm font-medium">Top Performing Content</h3>
|
|
72
72
|
<div className="space-y-2">
|
|
73
73
|
{visible.map((item) => (
|
|
74
74
|
<div key={item.id} className="flex items-center justify-between py-2">
|
|
75
75
|
<div className="mr-3 min-w-0 flex-1">
|
|
76
|
-
<p className="truncate text-sm font-medium
|
|
77
|
-
<p className="text-
|
|
76
|
+
<p className="text-foreground truncate text-sm font-medium">{item.title}</p>
|
|
77
|
+
<p className="text-muted-foreground text-xs capitalize">{item.collection}</p>
|
|
78
78
|
</div>
|
|
79
79
|
<ScoreBadge score={item.score} />
|
|
80
80
|
</div>
|
|
81
81
|
))}
|
|
82
82
|
{visible.length === 0 && (
|
|
83
|
-
<p className="py-4 text-center text-sm
|
|
83
|
+
<p className="text-muted-foreground py-4 text-center text-sm">
|
|
84
|
+
No published content yet
|
|
85
|
+
</p>
|
|
84
86
|
)}
|
|
85
87
|
</div>
|
|
86
88
|
{topContent.length > perPage && (
|
|
87
|
-
<div className="mt-3 flex items-center justify-between border-t border-gray-100 pt-3 text-xs
|
|
89
|
+
<div className="text-muted-foreground mt-3 flex items-center justify-between border-t border-gray-100 pt-3 text-xs">
|
|
88
90
|
<span>
|
|
89
91
|
Showing {page * perPage + 1}-{Math.min((page + 1) * perPage, topContent.length)} of{' '}
|
|
90
92
|
{topContent.length}
|
|
@@ -93,7 +95,7 @@ export function SEOPerformance({ onNavigate }: SEOPerformanceProps) {
|
|
|
93
95
|
<button
|
|
94
96
|
onClick={() => setPage(Math.max(0, page - 1))}
|
|
95
97
|
disabled={page === 0}
|
|
96
|
-
className="rounded p-1
|
|
98
|
+
className="hover:bg-accent rounded p-1 disabled:opacity-30"
|
|
97
99
|
>
|
|
98
100
|
<ChevronLeft className="h-3.5 w-3.5" />
|
|
99
101
|
</button>
|
|
@@ -103,7 +105,7 @@ export function SEOPerformance({ onNavigate }: SEOPerformanceProps) {
|
|
|
103
105
|
<button
|
|
104
106
|
onClick={() => setPage(Math.min(totalPages - 1, page + 1))}
|
|
105
107
|
disabled={page >= totalPages - 1}
|
|
106
|
-
className="rounded p-1
|
|
108
|
+
className="hover:bg-accent rounded p-1 disabled:opacity-30"
|
|
107
109
|
>
|
|
108
110
|
<ChevronRight className="h-3.5 w-3.5" />
|
|
109
111
|
</button>
|
|
@@ -113,38 +115,38 @@ export function SEOPerformance({ onNavigate }: SEOPerformanceProps) {
|
|
|
113
115
|
</div>
|
|
114
116
|
|
|
115
117
|
<div className="p-4 lg:col-span-5">
|
|
116
|
-
<h3 className="mb-3 text-sm font-medium
|
|
118
|
+
<h3 className="text-foreground mb-3 text-sm font-medium">SEO Issues</h3>
|
|
117
119
|
<div className="space-y-3">
|
|
118
120
|
<div className="flex items-center justify-between">
|
|
119
121
|
<div className="flex items-center gap-2">
|
|
120
122
|
<FileWarning className="h-4 w-4 text-red-400" />
|
|
121
|
-
<span className="text-
|
|
123
|
+
<span className="text-foreground text-sm">Missing meta descriptions</span>
|
|
122
124
|
</div>
|
|
123
|
-
<span className="text-sm font-medium
|
|
125
|
+
<span className="text-foreground text-sm font-medium">
|
|
124
126
|
{issues.missingMetaDescriptions}
|
|
125
127
|
</span>
|
|
126
128
|
</div>
|
|
127
129
|
<div className="flex items-center justify-between">
|
|
128
130
|
<div className="flex items-center gap-2">
|
|
129
131
|
<LinkIcon className="h-4 w-4 text-red-400" />
|
|
130
|
-
<span className="text-
|
|
132
|
+
<span className="text-foreground text-sm">Broken internal links</span>
|
|
131
133
|
</div>
|
|
132
|
-
<span className="text-sm font-medium
|
|
134
|
+
<span className="text-foreground text-sm font-medium">
|
|
133
135
|
{issues.brokenInternalLinks}
|
|
134
136
|
</span>
|
|
135
137
|
</div>
|
|
136
138
|
<div className="flex items-center justify-between">
|
|
137
139
|
<div className="flex items-center gap-2">
|
|
138
140
|
<ImageOff className="h-4 w-4 text-red-400" />
|
|
139
|
-
<span className="text-
|
|
141
|
+
<span className="text-foreground text-sm">Missing alt text</span>
|
|
140
142
|
</div>
|
|
141
|
-
<span className="text-sm font-medium
|
|
143
|
+
<span className="text-foreground text-sm font-medium">{issues.missingAltText}</span>
|
|
142
144
|
</div>
|
|
143
145
|
</div>
|
|
144
146
|
{totalIssues > 0 && (
|
|
145
147
|
<button
|
|
146
148
|
onClick={() => onNavigate?.('/seo')}
|
|
147
|
-
className="mt-4 text-sm font-medium
|
|
149
|
+
className="text-primary hover:text-primary/80 mt-4 text-sm font-medium"
|
|
148
150
|
>
|
|
149
151
|
View All Issues
|
|
150
152
|
</button>
|
|
@@ -233,8 +233,8 @@ function ToolbarButton({
|
|
|
233
233
|
title={title}
|
|
234
234
|
className={`rounded p-1.5 transition-colors ${
|
|
235
235
|
isActive
|
|
236
|
-
? 'bg-
|
|
237
|
-
: 'text-
|
|
236
|
+
? 'bg-info/10 text-info'
|
|
237
|
+
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
|
238
238
|
} ${disabled ? 'cursor-not-allowed opacity-40' : ''}`}
|
|
239
239
|
>
|
|
240
240
|
{children}
|
|
@@ -243,7 +243,7 @@ function ToolbarButton({
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
function Divider() {
|
|
246
|
-
return <div className="mx-1 h-6 w-px
|
|
246
|
+
return <div className="bg-muted mx-1 h-6 w-px" />
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
function Toolbar({ editor, onOpenMediaPicker }: { editor: Editor; onOpenMediaPicker: () => void }) {
|
|
@@ -265,7 +265,7 @@ function Toolbar({ editor, onOpenMediaPicker }: { editor: Editor; onOpenMediaPic
|
|
|
265
265
|
const isInTable = editor.isActive('table')
|
|
266
266
|
|
|
267
267
|
return (
|
|
268
|
-
<div className="sticky top-0 z-10 flex flex-wrap items-center gap-0.5 border-b
|
|
268
|
+
<div className="border-border bg-muted sticky top-0 z-10 flex flex-wrap items-center gap-0.5 border-b p-2">
|
|
269
269
|
{/* Group 1: Text formatting */}
|
|
270
270
|
<ToolbarButton
|
|
271
271
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
@@ -544,7 +544,7 @@ export function TipTapEditor({
|
|
|
544
544
|
UnderlineExt,
|
|
545
545
|
Link.configure({
|
|
546
546
|
openOnClick: false,
|
|
547
|
-
HTMLAttributes: { class: 'text-
|
|
547
|
+
HTMLAttributes: { class: 'text-primary underline' },
|
|
548
548
|
}),
|
|
549
549
|
Image.configure({
|
|
550
550
|
inline: true,
|
|
@@ -656,10 +656,10 @@ export function TipTapEditor({
|
|
|
656
656
|
|
|
657
657
|
const editorPane = (
|
|
658
658
|
<div
|
|
659
|
-
className={`flex-1 overflow-hidden rounded-lg border
|
|
659
|
+
className={`border-border bg-card flex-1 overflow-hidden rounded-lg border ${className ?? ''}`}
|
|
660
660
|
>
|
|
661
661
|
{collaboration && awareness && (
|
|
662
|
-
<div className="flex items-center justify-between gap-2 border-b border-gray-100
|
|
662
|
+
<div className="bg-card flex items-center justify-between gap-2 border-b border-gray-100 px-2 py-1.5">
|
|
663
663
|
<PresenceChips awareness={awareness} status={collabStatus} />
|
|
664
664
|
<OfflineStatus connection={collabStatus} offline={offlineStatus} />
|
|
665
665
|
</div>
|
|
@@ -14,12 +14,12 @@ import { paletteFor } from '../posts/badges.js'
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const DOT_COLOR: Record<string, string> = {
|
|
17
|
-
purple: 'bg-
|
|
18
|
-
green: 'bg-
|
|
19
|
-
orange: 'bg-
|
|
20
|
-
red: 'bg-
|
|
21
|
-
blue: 'bg-
|
|
22
|
-
pink: 'bg-
|
|
17
|
+
purple: 'bg-brand',
|
|
18
|
+
green: 'bg-success',
|
|
19
|
+
orange: 'bg-warning',
|
|
20
|
+
red: 'bg-destructive',
|
|
21
|
+
blue: 'bg-info',
|
|
22
|
+
pink: 'bg-chart-4',
|
|
23
23
|
gray: 'bg-muted-foreground',
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -5,53 +5,45 @@ import type { PostStatus } from '../../lib/posts-service.js'
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Shared status / type / SEO indicators for the Posts admin area.
|
|
8
|
-
*
|
|
9
|
-
* Colors are restricted to the theme tokens already used elsewhere in
|
|
10
|
-
* the admin (`success`, `warning`, `destructive`, `primary`, `muted`)
|
|
11
|
-
* plus a small named-color palette (`purple`, `green`, `orange`, ...)
|
|
12
|
-
* that maps to fixed Tailwind utilities. This keeps the badges working
|
|
13
|
-
* in both light and dark mode and avoids leaking `bg-[#...]` arbitrary
|
|
14
|
-
* values into components.
|
|
8
|
+
* Colors use semantic theme tokens only.
|
|
15
9
|
*/
|
|
16
10
|
|
|
17
|
-
// ─── Named color palette ─────────────────────────────────────────────
|
|
18
|
-
|
|
19
11
|
const PALETTE: Record<string, { bg: string; text: string; ring: string; border: string }> = {
|
|
20
12
|
purple: {
|
|
21
|
-
bg: 'bg-
|
|
22
|
-
text: 'text-
|
|
23
|
-
ring: 'ring-
|
|
24
|
-
border: 'border-
|
|
13
|
+
bg: 'bg-brand/10',
|
|
14
|
+
text: 'text-brand',
|
|
15
|
+
ring: 'ring-brand/20',
|
|
16
|
+
border: 'border-brand',
|
|
25
17
|
},
|
|
26
18
|
green: {
|
|
27
|
-
bg: 'bg-
|
|
28
|
-
text: 'text-
|
|
29
|
-
ring: 'ring-
|
|
30
|
-
border: 'border-
|
|
19
|
+
bg: 'bg-success/10',
|
|
20
|
+
text: 'text-success',
|
|
21
|
+
ring: 'ring-success/20',
|
|
22
|
+
border: 'border-success',
|
|
31
23
|
},
|
|
32
24
|
orange: {
|
|
33
|
-
bg: 'bg-
|
|
34
|
-
text: 'text-
|
|
35
|
-
ring: 'ring-
|
|
36
|
-
border: 'border-
|
|
25
|
+
bg: 'bg-warning/10',
|
|
26
|
+
text: 'text-warning',
|
|
27
|
+
ring: 'ring-warning/20',
|
|
28
|
+
border: 'border-warning',
|
|
37
29
|
},
|
|
38
30
|
red: {
|
|
39
|
-
bg: 'bg-
|
|
40
|
-
text: 'text-
|
|
41
|
-
ring: 'ring-
|
|
42
|
-
border: 'border-
|
|
31
|
+
bg: 'bg-destructive/10',
|
|
32
|
+
text: 'text-destructive',
|
|
33
|
+
ring: 'ring-destructive/20',
|
|
34
|
+
border: 'border-destructive',
|
|
43
35
|
},
|
|
44
36
|
blue: {
|
|
45
|
-
bg: 'bg-
|
|
46
|
-
text: 'text-
|
|
47
|
-
ring: 'ring-
|
|
48
|
-
border: 'border-
|
|
37
|
+
bg: 'bg-info/10',
|
|
38
|
+
text: 'text-info',
|
|
39
|
+
ring: 'ring-info/20',
|
|
40
|
+
border: 'border-info',
|
|
49
41
|
},
|
|
50
42
|
pink: {
|
|
51
|
-
bg: 'bg-
|
|
52
|
-
text: 'text-
|
|
53
|
-
ring: 'ring-
|
|
54
|
-
border: 'border-
|
|
43
|
+
bg: 'bg-chart-4/15',
|
|
44
|
+
text: 'text-chart-4',
|
|
45
|
+
ring: 'ring-chart-4/20',
|
|
46
|
+
border: 'border-chart-4',
|
|
55
47
|
},
|
|
56
48
|
gray: {
|
|
57
49
|
bg: 'bg-muted',
|
|
@@ -66,8 +58,6 @@ export function paletteFor(color: string | null | undefined) {
|
|
|
66
58
|
return PALETTE[color] ?? PALETTE.gray!
|
|
67
59
|
}
|
|
68
60
|
|
|
69
|
-
// ─── PostStatusBadge ─────────────────────────────────────────────────
|
|
70
|
-
|
|
71
61
|
const STATUS_LABEL: Record<PostStatus, string> = {
|
|
72
62
|
PUBLISHED: 'Published',
|
|
73
63
|
DRAFT: 'Draft',
|
|
@@ -104,8 +94,6 @@ export function PostStatusBadge({ status }: { status: PostStatus }) {
|
|
|
104
94
|
)
|
|
105
95
|
}
|
|
106
96
|
|
|
107
|
-
// ─── PostTypeBadge ───────────────────────────────────────────────────
|
|
108
|
-
|
|
109
97
|
export function PostTypeBadge({
|
|
110
98
|
label,
|
|
111
99
|
color,
|
|
@@ -127,12 +115,6 @@ export function PostTypeBadge({
|
|
|
127
115
|
return <span className={className}>{label}</span>
|
|
128
116
|
}
|
|
129
117
|
|
|
130
|
-
// ─── SeoScoreIndicator ───────────────────────────────────────────────
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Returns the palette key for an SEO score so callers can match the
|
|
134
|
-
* colored dot. 80+ green, 60–79 orange, 1–59 red, missing = gray.
|
|
135
|
-
*/
|
|
136
118
|
export function seoScorePalette(score: number | null): string {
|
|
137
119
|
if (score === null || Number.isNaN(score)) return 'gray'
|
|
138
120
|
if (score >= 80) return 'green'
|
|
@@ -158,10 +140,10 @@ export function SeoScoreIndicator({ score }: { score: number | null }) {
|
|
|
158
140
|
aria-hidden
|
|
159
141
|
className={`h-2 w-2 rounded-full ${
|
|
160
142
|
colorKey === 'green'
|
|
161
|
-
? 'bg-
|
|
143
|
+
? 'bg-success'
|
|
162
144
|
: colorKey === 'orange'
|
|
163
|
-
? 'bg-
|
|
164
|
-
: 'bg-
|
|
145
|
+
? 'bg-warning'
|
|
146
|
+
: 'bg-destructive'
|
|
165
147
|
}`}
|
|
166
148
|
/>
|
|
167
149
|
<span className="tabular-nums">{score}</span>
|
|
@@ -4,11 +4,11 @@ import { cv, type VariantProps } from '../../lib/cv.js'
|
|
|
4
4
|
const badge = cv('inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium', {
|
|
5
5
|
variants: {
|
|
6
6
|
tone: {
|
|
7
|
-
neutral: 'bg-
|
|
8
|
-
success: 'bg-
|
|
9
|
-
warning: 'bg-
|
|
10
|
-
danger: 'bg-
|
|
11
|
-
info: 'bg-
|
|
7
|
+
neutral: 'bg-muted text-muted-foreground',
|
|
8
|
+
success: 'bg-success/10 text-success',
|
|
9
|
+
warning: 'bg-warning/10 text-warning',
|
|
10
|
+
danger: 'bg-destructive/10 text-destructive',
|
|
11
|
+
info: 'bg-info/10 text-info',
|
|
12
12
|
primary: 'bg-[var(--primary)]/15 text-[var(--primary)] dark:bg-[var(--primary)]/25',
|
|
13
13
|
},
|
|
14
14
|
size: {
|