@actuate-media/cms-admin 0.18.0 → 0.20.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/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +9 -5
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/lib/post-editor-service.test.js +75 -23
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/posts-service.test.js +132 -142
- package/dist/__tests__/lib/posts-service.test.js.map +1 -1
- package/dist/__tests__/views/posts-list-view.render.test.d.ts +2 -0
- package/dist/__tests__/views/posts-list-view.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/posts-list-view.render.test.js +73 -0
- package/dist/__tests__/views/posts-list-view.render.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/seo/Drawer.d.ts +11 -0
- package/dist/components/seo/Drawer.d.ts.map +1 -0
- package/dist/components/seo/Drawer.js +13 -0
- package/dist/components/seo/Drawer.js.map +1 -0
- package/dist/components/seo/RedirectEditorDrawer.d.ts +9 -0
- package/dist/components/seo/RedirectEditorDrawer.d.ts.map +1 -0
- package/dist/components/seo/RedirectEditorDrawer.js +94 -0
- package/dist/components/seo/RedirectEditorDrawer.js.map +1 -0
- package/dist/components/seo/SeoCopilotDrawer.d.ts +10 -0
- package/dist/components/seo/SeoCopilotDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoCopilotDrawer.js +34 -0
- package/dist/components/seo/SeoCopilotDrawer.js.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts +8 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.js +111 -0
- package/dist/components/seo/SeoEditorDrawer.js.map +1 -0
- package/dist/components/seo/SeoIssueFixDrawer.d.ts +8 -0
- package/dist/components/seo/SeoIssueFixDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoIssueFixDrawer.js +111 -0
- package/dist/components/seo/SeoIssueFixDrawer.js.map +1 -0
- package/dist/components/seo/primitives.d.ts +53 -0
- package/dist/components/seo/primitives.d.ts.map +1 -0
- package/dist/components/seo/primitives.js +100 -0
- package/dist/components/seo/primitives.js.map +1 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +3 -0
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/post-editor-service.d.ts +6 -4
- package/dist/lib/post-editor-service.d.ts.map +1 -1
- package/dist/lib/post-editor-service.js +32 -37
- package/dist/lib/post-editor-service.js.map +1 -1
- package/dist/lib/posts-service.d.ts.map +1 -1
- package/dist/lib/posts-service.js +91 -160
- package/dist/lib/posts-service.js.map +1 -1
- package/dist/lib/seo-service.d.ts +322 -0
- package/dist/lib/seo-service.d.ts.map +1 -0
- package/dist/lib/seo-service.js +295 -0
- package/dist/lib/seo-service.js.map +1 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +4 -1
- package/dist/router/index.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +36 -25
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/Posts/NewPostPicker.d.ts +5 -4
- package/dist/views/Posts/NewPostPicker.d.ts.map +1 -1
- package/dist/views/Posts/NewPostPicker.js +5 -4
- package/dist/views/Posts/NewPostPicker.js.map +1 -1
- package/dist/views/Posts/NewPostTypeInstructions.js +2 -2
- package/dist/views/Posts/PostTypesView.d.ts +5 -4
- package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
- package/dist/views/Posts/PostTypesView.js.map +1 -1
- package/dist/views/Posts/PostsListView.d.ts.map +1 -1
- package/dist/views/Posts/PostsListView.js +8 -0
- package/dist/views/Posts/PostsListView.js.map +1 -1
- package/dist/views/Posts/post-type-presets.d.ts +5 -0
- package/dist/views/Posts/post-type-presets.d.ts.map +1 -1
- package/dist/views/Posts/post-type-presets.js +10 -64
- package/dist/views/Posts/post-type-presets.js.map +1 -1
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +89 -132
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts +6 -2
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.js +8 -8
- package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +7 -1
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.js +10 -4
- 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 +4 -3
- package/dist/views/post-editor/PostHeader.js.map +1 -1
- package/dist/views/post-editor/PostPreview.d.ts.map +1 -1
- package/dist/views/post-editor/PostPreview.js +11 -1
- package/dist/views/post-editor/PostPreview.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts +22 -2
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +4 -1
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/seo/AuditTab.d.ts +6 -0
- package/dist/views/seo/AuditTab.d.ts.map +1 -0
- package/dist/views/seo/AuditTab.js +57 -0
- package/dist/views/seo/AuditTab.js.map +1 -0
- package/dist/views/seo/ContentTab.d.ts +5 -0
- package/dist/views/seo/ContentTab.d.ts.map +1 -0
- package/dist/views/seo/ContentTab.js +27 -0
- package/dist/views/seo/ContentTab.js.map +1 -0
- package/dist/views/seo/OverviewTab.d.ts +8 -0
- package/dist/views/seo/OverviewTab.d.ts.map +1 -0
- package/dist/views/seo/OverviewTab.js +35 -0
- package/dist/views/seo/OverviewTab.js.map +1 -0
- package/dist/views/seo/RedirectsTab.d.ts +2 -0
- package/dist/views/seo/RedirectsTab.d.ts.map +1 -0
- package/dist/views/seo/RedirectsTab.js +124 -0
- package/dist/views/seo/RedirectsTab.js.map +1 -0
- package/dist/views/seo/TechnicalTab.d.ts +2 -0
- package/dist/views/seo/TechnicalTab.d.ts.map +1 -0
- package/dist/views/seo/TechnicalTab.js +102 -0
- package/dist/views/seo/TechnicalTab.js.map +1 -0
- package/dist/views/seo/useSeoResource.d.ts +8 -0
- package/dist/views/seo/useSeoResource.d.ts.map +1 -0
- package/dist/views/seo/useSeoResource.js +42 -0
- package/dist/views/seo/useSeoResource.js.map +1 -0
- package/package.json +2 -2
- package/src/AdminRoot.tsx +9 -5
- package/src/__tests__/lib/post-editor-service.test.ts +78 -23
- package/src/__tests__/lib/posts-service.test.ts +134 -163
- package/src/__tests__/views/posts-list-view.render.test.tsx +84 -0
- package/src/components/seo/Drawer.tsx +61 -0
- package/src/components/seo/RedirectEditorDrawer.tsx +235 -0
- package/src/components/seo/SeoCopilotDrawer.tsx +76 -0
- package/src/components/seo/SeoEditorDrawer.tsx +347 -0
- package/src/components/seo/SeoIssueFixDrawer.tsx +244 -0
- package/src/components/seo/primitives.tsx +237 -0
- package/src/index.ts +0 -2
- package/src/layout/Sidebar.tsx +3 -0
- package/src/lib/post-editor-service.ts +41 -40
- package/src/lib/posts-service.ts +109 -180
- package/src/lib/seo-service.ts +623 -0
- package/src/router/index.ts +4 -1
- package/src/views/MediaBrowser.tsx +89 -67
- package/src/views/Posts/NewPostPicker.tsx +5 -4
- package/src/views/Posts/NewPostTypeInstructions.tsx +2 -2
- package/src/views/Posts/PostTypesView.tsx +5 -4
- package/src/views/Posts/PostsListView.tsx +9 -0
- package/src/views/Posts/post-type-presets.ts +15 -64
- package/src/views/SEO.tsx +186 -863
- package/src/views/page-editor/sections/ArticleBodySection.tsx +12 -16
- package/src/views/post-editor/PostEditorCanvas.tsx +7 -1
- package/src/views/post-editor/PostFieldsModal.tsx +36 -21
- package/src/views/post-editor/PostHeader.tsx +4 -4
- package/src/views/post-editor/PostPreview.tsx +12 -1
- package/src/views/post-editor/PostSectionEditor.tsx +22 -5
- package/src/views/seo/AuditTab.tsx +229 -0
- package/src/views/seo/ContentTab.tsx +164 -0
- package/src/views/seo/OverviewTab.tsx +257 -0
- package/src/views/seo/RedirectsTab.tsx +392 -0
- package/src/views/seo/TechnicalTab.tsx +303 -0
- package/src/views/seo/useSeoResource.ts +52 -0
- package/dist/views/PostEditor.d.ts +0 -6
- package/dist/views/PostEditor.d.ts.map +0 -1
- package/dist/views/PostEditor.js +0 -79
- package/dist/views/PostEditor.js.map +0 -1
- package/dist/views/Posts.d.ts +0 -5
- package/dist/views/Posts.d.ts.map +0 -1
- package/dist/views/Posts.js +0 -73
- package/dist/views/Posts.js.map +0 -1
- package/src/views/PostEditor.tsx +0 -257
- package/src/views/Posts.tsx +0 -375
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
import { Plus, Pencil, Trash2, Upload, Download, Sparkles, ArrowRight, GitBranch, Check, X, Link2, } from 'lucide-react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { fetchRedirectOverview, fetchRedirectRules, fetchRedirectChains, fetchRedirectSuggestions, deleteRedirectRule, flattenRedirectChain, generate404RecoverySuggestions, acceptRedirectSuggestion, dismissRedirectSuggestion, exportRedirectsCsv, importRedirectsCsv, } from '../../lib/seo-service.js';
|
|
7
|
+
import { SectionCard, SeoLoading, SeoEmptyState, SeoErrorState, SeoStatusBadge, btnPrimary, btnSecondary, btnGhostIcon, } from '../../components/seo/primitives.js';
|
|
8
|
+
import { RedirectEditorDrawer } from '../../components/seo/RedirectEditorDrawer.js';
|
|
9
|
+
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
|
|
10
|
+
import { useSeoResource } from './useSeoResource.js';
|
|
11
|
+
function OverviewCards({ onRefetchKey }) {
|
|
12
|
+
const { data, loading, error, refetch } = useSeoResource(fetchRedirectOverview, [onRefetchKey]);
|
|
13
|
+
if (loading)
|
|
14
|
+
return _jsx(SeoLoading, { label: "Loading redirect overview\u2026" });
|
|
15
|
+
if (error)
|
|
16
|
+
return _jsx(SeoErrorState, { message: error, onRetry: refetch });
|
|
17
|
+
if (!data)
|
|
18
|
+
return null;
|
|
19
|
+
const cards = [
|
|
20
|
+
{ label: 'Active redirects', value: data.activeRedirects },
|
|
21
|
+
{ label: 'Stale redirects', value: data.staleRedirects },
|
|
22
|
+
{ label: '404s tracked', value: data.notFoundCount },
|
|
23
|
+
{ label: 'Chains & loops', value: data.chainsAndLoops },
|
|
24
|
+
];
|
|
25
|
+
return (_jsx("div", { className: "grid grid-cols-2 gap-4 lg:grid-cols-4", children: cards.map((c) => (_jsxs("div", { className: "border-border bg-card rounded-lg border p-4", children: [_jsx("p", { className: "text-muted-foreground text-sm", children: c.label }), _jsx("p", { className: "text-foreground mt-1 text-2xl font-medium tabular-nums", children: c.value })] }, c.label))) }));
|
|
26
|
+
}
|
|
27
|
+
function Ai404RecoveryCard({ onApplied }) {
|
|
28
|
+
const { data, loading, error, refetch } = useSeoResource(fetchRedirectSuggestions, []);
|
|
29
|
+
const [generating, setGenerating] = useState(false);
|
|
30
|
+
async function handleGenerate() {
|
|
31
|
+
setGenerating(true);
|
|
32
|
+
const res = await generate404RecoverySuggestions();
|
|
33
|
+
setGenerating(false);
|
|
34
|
+
if (res.error) {
|
|
35
|
+
toast.error(res.error);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
toast.success('Generated 404 recovery suggestions.');
|
|
39
|
+
refetch();
|
|
40
|
+
}
|
|
41
|
+
async function handleAccept(id) {
|
|
42
|
+
const res = await acceptRedirectSuggestion(id);
|
|
43
|
+
if (res.error)
|
|
44
|
+
return toast.error(res.error);
|
|
45
|
+
toast.success('Redirect created from suggestion.');
|
|
46
|
+
refetch();
|
|
47
|
+
onApplied();
|
|
48
|
+
}
|
|
49
|
+
async function handleDismiss(id) {
|
|
50
|
+
const res = await dismissRedirectSuggestion(id);
|
|
51
|
+
if (res.error)
|
|
52
|
+
return toast.error(res.error);
|
|
53
|
+
refetch();
|
|
54
|
+
}
|
|
55
|
+
return (_jsx(SectionCard, { title: "AI 404 Recovery", description: "Suggested redirects for paths returning 404s", action: _jsxs("button", { className: btnSecondary, onClick: handleGenerate, disabled: generating, children: [_jsx(Sparkles, { className: `h-4 w-4 ${generating ? 'animate-pulse' : ''}`, "aria-hidden": true }), generating ? 'Analyzing…' : 'Generate suggestions'] }), children: loading ? (_jsx(SeoLoading, {})) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : (data ?? []).length === 0 ? (_jsx(SeoEmptyState, { icon: _jsx(Sparkles, { size: 24 }), title: "No suggestions", description: "Generate suggestions to recover traffic from broken URLs." })) : (_jsx("ul", { className: "divide-border divide-y", children: data.map((s) => (_jsxs("li", { className: "flex items-center justify-between gap-3 py-3 first:pt-0", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "text-foreground flex items-center gap-2 text-sm", children: [_jsx("span", { className: "truncate font-medium", children: s.fromPath }), _jsx(ArrowRight, { className: "text-muted-foreground h-3.5 w-3.5 shrink-0", "aria-hidden": true }), _jsx("span", { className: "text-muted-foreground truncate", children: s.toPath })] }), s.reason && _jsx("p", { className: "text-muted-foreground mt-0.5 text-sm", children: s.reason })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(SeoStatusBadge, { label: `${Math.round(s.confidence * 100)}%`, tone: s.confidence >= 0.7 ? 'good' : s.confidence >= 0.4 ? 'fair' : 'poor' }), _jsx("button", { onClick: () => handleAccept(s.id), className: "text-success hover:bg-accent focus-visible:ring-ring rounded-md p-1.5 focus-visible:ring-2 focus-visible:outline-none", "aria-label": `Accept redirect from ${s.fromPath}`, children: _jsx(Check, { className: "h-4 w-4", "aria-hidden": true }) }), _jsx("button", { onClick: () => handleDismiss(s.id), className: "text-muted-foreground hover:bg-accent focus-visible:ring-ring rounded-md p-1.5 focus-visible:ring-2 focus-visible:outline-none", "aria-label": `Dismiss suggestion for ${s.fromPath}`, children: _jsx(X, { className: "h-4 w-4", "aria-hidden": true }) })] })] }, s.id))) })) }));
|
|
56
|
+
}
|
|
57
|
+
function ChainMapCard({ onFlattened }) {
|
|
58
|
+
const { data, loading, error, refetch } = useSeoResource(fetchRedirectChains, []);
|
|
59
|
+
async function handleFlatten(id) {
|
|
60
|
+
const res = await flattenRedirectChain(id);
|
|
61
|
+
if (res.error)
|
|
62
|
+
return toast.error(res.error);
|
|
63
|
+
toast.success('Chain flattened.');
|
|
64
|
+
refetch();
|
|
65
|
+
onFlattened();
|
|
66
|
+
}
|
|
67
|
+
if (loading)
|
|
68
|
+
return null;
|
|
69
|
+
if (error)
|
|
70
|
+
return null;
|
|
71
|
+
if (!data || data.length === 0)
|
|
72
|
+
return null;
|
|
73
|
+
return (_jsx(SectionCard, { title: "Redirect Chains & Loops", description: "Multi-hop redirects slow crawlers and lose link equity", children: _jsx("ul", { className: "space-y-3", children: data.map((chain) => (_jsxs("li", { className: "border-border flex items-center justify-between gap-3 rounded-md border p-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx(GitBranch, { className: "text-muted-foreground h-4 w-4", "aria-hidden": true }), _jsx(SeoStatusBadge, { label: chain.kind === 'loop' ? 'Loop' : 'Chain', tone: chain.kind === 'loop' ? 'critical' : 'fair' })] }), _jsx("p", { className: "text-foreground flex flex-wrap items-center gap-1 text-sm", children: chain.hops.map((hop, i) => (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx("span", { className: "truncate", children: hop }), i < chain.hops.length - 1 && (_jsx(ArrowRight, { className: "text-muted-foreground h-3 w-3", "aria-hidden": true }))] }, i))) })] }), chain.kind === 'chain' && (_jsx("button", { className: btnSecondary, onClick: () => handleFlatten(chain.id), children: "Flatten" }))] }, chain.id))) }) }));
|
|
74
|
+
}
|
|
75
|
+
export function RedirectsTab() {
|
|
76
|
+
const [refetchKey, setRefetchKey] = useState(0);
|
|
77
|
+
const { data, loading, error, refetch } = useSeoResource(fetchRedirectRules, [refetchKey]);
|
|
78
|
+
const [editor, setEditor] = useState(null);
|
|
79
|
+
const [confirmDelete, setConfirmDelete] = useState(null);
|
|
80
|
+
const fileRef = useRef(null);
|
|
81
|
+
const bump = () => setRefetchKey((k) => k + 1);
|
|
82
|
+
const refetchAll = () => {
|
|
83
|
+
refetch();
|
|
84
|
+
bump();
|
|
85
|
+
};
|
|
86
|
+
async function handleDelete(rule) {
|
|
87
|
+
const res = await deleteRedirectRule(rule.id);
|
|
88
|
+
if (res.error)
|
|
89
|
+
return toast.error(res.error);
|
|
90
|
+
toast.success('Redirect deleted.');
|
|
91
|
+
refetchAll();
|
|
92
|
+
}
|
|
93
|
+
async function handleExport() {
|
|
94
|
+
const res = await exportRedirectsCsv();
|
|
95
|
+
if (res.error || !res.csv)
|
|
96
|
+
return toast.error(res.error ?? 'Export failed.');
|
|
97
|
+
const blob = new Blob([res.csv], { type: 'text/csv' });
|
|
98
|
+
const link = document.createElement('a');
|
|
99
|
+
link.href = URL.createObjectURL(blob);
|
|
100
|
+
link.download = 'redirects.csv';
|
|
101
|
+
link.click();
|
|
102
|
+
URL.revokeObjectURL(link.href);
|
|
103
|
+
}
|
|
104
|
+
async function handleImportFile(file) {
|
|
105
|
+
const csv = await file.text();
|
|
106
|
+
const res = await importRedirectsCsv(csv);
|
|
107
|
+
if (res.error)
|
|
108
|
+
return toast.error(res.error);
|
|
109
|
+
if (res.errors && res.errors.length > 0) {
|
|
110
|
+
toast.error(`Imported ${res.imported ?? 0}, ${res.errors.length} row(s) skipped.`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
toast.success(`Imported ${res.imported ?? 0} redirect(s).`);
|
|
114
|
+
}
|
|
115
|
+
refetchAll();
|
|
116
|
+
}
|
|
117
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(OverviewCards, { onRefetchKey: refetchKey }), _jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2", children: [_jsx(Ai404RecoveryCard, { onApplied: refetchAll }), _jsx(ChainMapCard, { onFlattened: refetchAll })] }), _jsx(SectionCard, { title: "Redirect Rules", action: _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx("input", { ref: fileRef, type: "file", accept: ".csv,text/csv", className: "sr-only", "aria-hidden": true, tabIndex: -1, onChange: (e) => {
|
|
118
|
+
const f = e.target.files?.[0];
|
|
119
|
+
if (f)
|
|
120
|
+
handleImportFile(f);
|
|
121
|
+
e.target.value = '';
|
|
122
|
+
} }), _jsxs("button", { className: btnSecondary, onClick: () => fileRef.current?.click(), children: [_jsx(Upload, { className: "h-4 w-4", "aria-hidden": true }), " Import"] }), _jsxs("button", { className: btnSecondary, onClick: handleExport, children: [_jsx(Download, { className: "h-4 w-4", "aria-hidden": true }), " Export"] }), _jsxs("button", { className: btnPrimary, onClick: () => setEditor({ rule: null }), children: [_jsx(Plus, { className: "h-4 w-4", "aria-hidden": true }), " New redirect"] })] }), children: loading ? (_jsx(SeoLoading, { label: "Loading redirects\u2026" })) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : (data ?? []).length === 0 ? (_jsx(SeoEmptyState, { icon: _jsx(Link2, { size: 24 }), title: "No redirects yet", description: "Create a redirect or import a CSV to get started.", action: _jsxs("button", { className: btnPrimary, onClick: () => setEditor({ rule: null }), children: [_jsx(Plus, { className: "h-4 w-4", "aria-hidden": true }), " New redirect"] }) })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", "aria-label": "Redirect rules", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-muted-foreground border-border border-b text-left", children: [_jsx("th", { className: "py-2 pr-4 font-medium", children: "Source" }), _jsx("th", { className: "py-2 pr-4 font-medium", children: "Destination" }), _jsx("th", { className: "py-2 pr-4 font-medium", children: "Type" }), _jsx("th", { className: "py-2 pr-4 text-right font-medium", children: "Hits" }), _jsx("th", { className: "py-2 pr-4 font-medium", children: "Status" }), _jsx("th", { className: "py-2 text-right font-medium", children: _jsx("span", { className: "sr-only", children: "Actions" }) })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: data.map((r) => (_jsxs("tr", { className: "hover:bg-accent/40", children: [_jsx("td", { className: "text-foreground py-2.5 pr-4 font-mono", children: r.fromPath }), _jsx("td", { className: "text-muted-foreground py-2.5 pr-4 font-mono", children: r.type === 410 ? _jsx("span", { className: "italic", children: "\u2014 (gone)" }) : r.toPath }), _jsx("td", { className: "text-foreground py-2.5 pr-4 tabular-nums", children: r.type }), _jsx("td", { className: "text-foreground py-2.5 pr-4 text-right tabular-nums", children: r.hits }), _jsx("td", { className: "py-2.5 pr-4", children: _jsxs("span", { className: "flex flex-wrap gap-1.5", children: [_jsx(SeoStatusBadge, { label: r.enabled ? 'Enabled' : 'Disabled', tone: r.enabled ? 'good' : 'neutral' }), r.stale && _jsx(SeoStatusBadge, { label: "Stale", tone: "fair" })] }) }), _jsx("td", { className: "py-2.5 text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx("button", { className: btnGhostIcon, onClick: () => setEditor({ rule: r }), "aria-label": `Edit redirect ${r.fromPath}`, children: _jsx(Pencil, { className: "h-4 w-4", "aria-hidden": true }) }), _jsx("button", { className: btnGhostIcon, onClick: () => setConfirmDelete(r), "aria-label": `Delete redirect ${r.fromPath}`, children: _jsx(Trash2, { className: "h-4 w-4", "aria-hidden": true }) })] }) })] }, r.id))) })] }) })) }), _jsx(RedirectEditorDrawer, { open: editor !== null, onOpenChange: (o) => !o && setEditor(null), rule: editor?.rule ?? null, onSaved: refetchAll }), _jsx(ConfirmDialog, { open: confirmDelete !== null, onClose: () => setConfirmDelete(null), onConfirm: () => confirmDelete && handleDelete(confirmDelete), title: "Delete redirect", description: `Delete the redirect from "${confirmDelete?.fromPath}"? This cannot be undone.`, confirmLabel: "Delete", destructive: true })] }));
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=RedirectsTab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedirectsTab.js","sourceRoot":"","sources":["../../../src/views/seo/RedirectsTab.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACxC,OAAO,EACL,IAAI,EACJ,MAAM,EACN,MAAM,EACN,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,KAAK,EACL,CAAC,EACD,KAAK,GACN,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,WAAW,EACX,UAAU,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,UAAU,EACV,YAAY,EACZ,YAAY,GACb,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAA;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD,SAAS,aAAa,CAAC,EAAE,YAAY,EAA4B;IAC/D,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,qBAAqB,EAAE,CAAC,YAAY,CAAC,CAAC,CAAA;IAC/F,IAAI,OAAO;QAAE,OAAO,KAAC,UAAU,IAAC,KAAK,EAAC,iCAA4B,GAAG,CAAA;IACrE,IAAI,KAAK;QAAE,OAAO,KAAC,aAAa,IAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAI,CAAA;IACrE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,MAAM,KAAK,GAAG;QACZ,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE;QAC1D,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE;QACxD,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE;QACpD,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE;KACxD,CAAA;IACD,OAAO,CACL,cAAK,SAAS,EAAC,uCAAuC,YACnD,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAChB,eAAmB,SAAS,EAAC,6CAA6C,aACxE,YAAG,SAAS,EAAC,+BAA+B,YAAE,CAAC,CAAC,KAAK,GAAK,EAC1D,YAAG,SAAS,EAAC,wDAAwD,YAAE,CAAC,CAAC,KAAK,GAAK,KAF3E,CAAC,CAAC,KAAK,CAGX,CACP,CAAC,GACE,CACP,CAAA;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAE,SAAS,EAA6B;IACjE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAA;IACtF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEnD,KAAK,UAAU,cAAc;QAC3B,aAAa,CAAC,IAAI,CAAC,CAAA;QACnB,MAAM,GAAG,GAAG,MAAM,8BAA8B,EAAE,CAAA;QAClD,aAAa,CAAC,KAAK,CAAC,CAAA;QACpB,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACd,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACtB,OAAM;QACR,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAA;QACpD,OAAO,EAAE,CAAA;IACX,CAAC;IAED,KAAK,UAAU,YAAY,CAAC,EAAU;QACpC,MAAM,GAAG,GAAG,MAAM,wBAAwB,CAAC,EAAE,CAAC,CAAA;QAC9C,IAAI,GAAG,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC5C,KAAK,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAA;QAClD,OAAO,EAAE,CAAA;QACT,SAAS,EAAE,CAAA;IACb,CAAC;IAED,KAAK,UAAU,aAAa,CAAC,EAAU;QACrC,MAAM,GAAG,GAAG,MAAM,yBAAyB,CAAC,EAAE,CAAC,CAAA;QAC/C,IAAI,GAAG,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC5C,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO,CACL,KAAC,WAAW,IACV,KAAK,EAAC,iBAAiB,EACvB,WAAW,EAAC,8CAA8C,EAC1D,MAAM,EACJ,kBAAQ,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,aAC5E,KAAC,QAAQ,IAAC,SAAS,EAAE,WAAW,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,wBAAgB,EAClF,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,sBAAsB,IAC5C,YAGV,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,UAAU,KAAG,CACf,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACV,KAAC,aAAa,IAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAI,CACpD,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAC9B,KAAC,aAAa,IACZ,IAAI,EAAE,KAAC,QAAQ,IAAC,IAAI,EAAE,EAAE,GAAI,EAC5B,KAAK,EAAC,gBAAgB,EACtB,WAAW,EAAC,2DAA2D,GACvE,CACH,CAAC,CAAC,CAAC,CACF,aAAI,SAAS,EAAC,wBAAwB,YACnC,IAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAChB,cAAe,SAAS,EAAC,yDAAyD,aAChF,eAAK,SAAS,EAAC,SAAS,aACtB,aAAG,SAAS,EAAC,iDAAiD,aAC5D,eAAM,SAAS,EAAC,sBAAsB,YAAE,CAAC,CAAC,QAAQ,GAAQ,EAC1D,KAAC,UAAU,IAAC,SAAS,EAAC,4CAA4C,wBAAe,EACjF,eAAM,SAAS,EAAC,gCAAgC,YAAE,CAAC,CAAC,MAAM,GAAQ,IAChE,EACH,CAAC,CAAC,MAAM,IAAI,YAAG,SAAS,EAAC,sCAAsC,YAAE,CAAC,CAAC,MAAM,GAAK,IAC3E,EACN,eAAK,SAAS,EAAC,kCAAkC,aAC/C,KAAC,cAAc,IACb,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,EAC3C,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GAC1E,EACF,iBACE,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,EACjC,SAAS,EAAC,uHAAuH,gBACrH,wBAAwB,CAAC,CAAC,QAAQ,EAAE,YAEhD,KAAC,KAAK,IAAC,SAAS,EAAC,SAAS,wBAAe,GAClC,EACT,iBACE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,EAClC,SAAS,EAAC,gIAAgI,gBAC9H,0BAA0B,CAAC,CAAC,QAAQ,EAAE,YAElD,KAAC,CAAC,IAAC,SAAS,EAAC,SAAS,wBAAe,GAC9B,IACL,KA5BC,CAAC,CAAC,EAAE,CA6BR,CACN,CAAC,GACC,CACN,GACW,CACf,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,EAAE,WAAW,EAA+B;IAChE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAA;IAEjF,KAAK,UAAU,aAAa,CAAC,EAAU;QACrC,MAAM,GAAG,GAAG,MAAM,oBAAoB,CAAC,EAAE,CAAC,CAAA;QAC1C,IAAI,GAAG,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC5C,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;QACjC,OAAO,EAAE,CAAA;QACT,WAAW,EAAE,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QAAE,OAAO,IAAI,CAAA;IACxB,IAAI,KAAK;QAAE,OAAO,IAAI,CAAA;IACtB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAE3C,OAAO,CACL,KAAC,WAAW,IACV,KAAK,EAAC,yBAAyB,EAC/B,WAAW,EAAC,wDAAwD,YAEpE,aAAI,SAAS,EAAC,WAAW,YACtB,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACnB,cAEE,SAAS,EAAC,6EAA6E,aAEvF,eAAK,SAAS,EAAC,SAAS,aACtB,eAAK,SAAS,EAAC,8BAA8B,aAC3C,KAAC,SAAS,IAAC,SAAS,EAAC,+BAA+B,wBAAe,EACnE,KAAC,cAAc,IACb,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAC/C,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,GACjD,IACE,EACN,YAAG,SAAS,EAAC,2DAA2D,YACrE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAC1B,gBAAc,SAAS,EAAC,yBAAyB,aAC/C,eAAM,SAAS,EAAC,UAAU,YAAE,GAAG,GAAQ,EACtC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAC5B,KAAC,UAAU,IAAC,SAAS,EAAC,+BAA+B,wBAAe,CACrE,KAJQ,CAAC,CAKL,CACR,CAAC,GACA,IACA,EACL,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,CACzB,iBAAQ,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,wBAE9D,CACV,KA1BI,KAAK,CAAC,EAAE,CA2BV,CACN,CAAC,GACC,GACO,CACf,CAAA;AACH,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC/C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,kBAAkB,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAC1F,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAuC,IAAI,CAAC,CAAA;IAChF,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAsB,IAAI,CAAC,CAAA;IAC7E,MAAM,OAAO,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAA;IAE9C,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9C,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,OAAO,EAAE,CAAA;QACT,IAAI,EAAE,CAAA;IACR,CAAC,CAAA;IAED,KAAK,UAAU,YAAY,CAAC,IAAkB;QAC5C,MAAM,GAAG,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC7C,IAAI,GAAG,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC5C,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;QAClC,UAAU,EAAE,CAAA;IACd,CAAC;IAED,KAAK,UAAU,YAAY;QACzB,MAAM,GAAG,GAAG,MAAM,kBAAkB,EAAE,CAAA;QACtC,IAAI,GAAG,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,gBAAgB,CAAC,CAAA;QAC5E,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAA;QACtD,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;QACxC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;QACrC,IAAI,CAAC,QAAQ,GAAG,eAAe,CAAA;QAC/B,IAAI,CAAC,KAAK,EAAE,CAAA;QACZ,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAChC,CAAC;IAED,KAAK,UAAU,gBAAgB,CAAC,IAAU;QACxC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;QAC7B,MAAM,GAAG,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAA;QACzC,IAAI,GAAG,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC5C,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,KAAK,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,QAAQ,IAAI,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,MAAM,kBAAkB,CAAC,CAAA;QACpF,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,OAAO,CAAC,YAAY,GAAG,CAAC,QAAQ,IAAI,CAAC,eAAe,CAAC,CAAA;QAC7D,CAAC;QACD,UAAU,EAAE,CAAA;IACd,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,WAAW,aACxB,KAAC,aAAa,IAAC,YAAY,EAAE,UAAU,GAAI,EAE3C,eAAK,SAAS,EAAC,uCAAuC,aACpD,KAAC,iBAAiB,IAAC,SAAS,EAAE,UAAU,GAAI,EAC5C,KAAC,YAAY,IAAC,WAAW,EAAE,UAAU,GAAI,IACrC,EAEN,KAAC,WAAW,IACV,KAAK,EAAC,gBAAgB,EACtB,MAAM,EACJ,eAAK,SAAS,EAAC,sBAAsB,aACnC,gBACE,GAAG,EAAE,OAAO,EACZ,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,eAAe,EACtB,SAAS,EAAC,SAAS,uBAEnB,QAAQ,EAAE,CAAC,CAAC,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;gCACd,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;gCAC7B,IAAI,CAAC;oCAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;gCAC1B,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAA;4BACrB,CAAC,GACD,EACF,kBAAQ,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,aACtE,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,wBAAe,eACnC,EACT,kBAAQ,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,aACpD,KAAC,QAAQ,IAAC,SAAS,EAAC,SAAS,wBAAe,eACrC,EACT,kBAAQ,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,aACrE,KAAC,IAAI,IAAC,SAAS,EAAC,SAAS,wBAAe,qBACjC,IACL,YAGP,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,UAAU,IAAC,KAAK,EAAC,yBAAoB,GAAG,CAC1C,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACV,KAAC,aAAa,IAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAI,CACpD,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAC9B,KAAC,aAAa,IACZ,IAAI,EAAE,KAAC,KAAK,IAAC,IAAI,EAAE,EAAE,GAAI,EACzB,KAAK,EAAC,kBAAkB,EACxB,WAAW,EAAC,mDAAmD,EAC/D,MAAM,EACJ,kBAAQ,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,aACrE,KAAC,IAAI,IAAC,SAAS,EAAC,SAAS,wBAAe,qBACjC,GAEX,CACH,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,iBAAiB,YAC9B,iBAAO,SAAS,EAAC,gBAAgB,gBAAY,gBAAgB,aAC3D,0BACE,cAAI,SAAS,EAAC,wDAAwD,aACpE,aAAI,SAAS,EAAC,uBAAuB,uBAAY,EACjD,aAAI,SAAS,EAAC,uBAAuB,4BAAiB,EACtD,aAAI,SAAS,EAAC,uBAAuB,qBAAU,EAC/C,aAAI,SAAS,EAAC,kCAAkC,qBAAU,EAC1D,aAAI,SAAS,EAAC,uBAAuB,uBAAY,EACjD,aAAI,SAAS,EAAC,6BAA6B,YACzC,eAAM,SAAS,EAAC,SAAS,wBAAe,GACrC,IACF,GACC,EACR,gBAAO,SAAS,EAAC,wBAAwB,YACtC,IAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAChB,cAAe,SAAS,EAAC,oBAAoB,aAC3C,aAAI,SAAS,EAAC,uCAAuC,YAAE,CAAC,CAAC,QAAQ,GAAM,EACvE,aAAI,SAAS,EAAC,6CAA6C,YACxD,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,QAAQ,8BAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAClE,EACL,aAAI,SAAS,EAAC,0CAA0C,YAAE,CAAC,CAAC,IAAI,GAAM,EACtE,aAAI,SAAS,EAAC,qDAAqD,YAChE,CAAC,CAAC,IAAI,GACJ,EACL,aAAI,SAAS,EAAC,aAAa,YACzB,gBAAM,SAAS,EAAC,wBAAwB,aACtC,KAAC,cAAc,IACb,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EACzC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,GACpC,EACD,CAAC,CAAC,KAAK,IAAI,KAAC,cAAc,IAAC,KAAK,EAAC,OAAO,EAAC,IAAI,EAAC,MAAM,GAAG,IACnD,GACJ,EACL,aAAI,SAAS,EAAC,mBAAmB,YAC/B,eAAK,SAAS,EAAC,wBAAwB,aACrC,iBACE,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,gBACzB,iBAAiB,CAAC,CAAC,QAAQ,EAAE,YAEzC,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,wBAAe,GACnC,EACT,iBACE,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,gBACtB,mBAAmB,CAAC,CAAC,QAAQ,EAAE,YAE3C,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,wBAAe,GACnC,IACL,GACH,KAnCE,CAAC,CAAC,EAAE,CAoCR,CACN,CAAC,GACI,IACF,GACJ,CACP,GACW,EAEd,KAAC,oBAAoB,IACnB,IAAI,EAAE,MAAM,KAAK,IAAI,EACrB,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,EAC1C,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,IAAI,EAC1B,OAAO,EAAE,UAAU,GACnB,EAEF,KAAC,aAAa,IACZ,IAAI,EAAE,aAAa,KAAK,IAAI,EAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EACrC,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,IAAI,YAAY,CAAC,aAAa,CAAC,EAC7D,KAAK,EAAC,iBAAiB,EACvB,WAAW,EAAE,6BAA6B,aAAa,EAAE,QAAQ,2BAA2B,EAC5F,YAAY,EAAC,QAAQ,EACrB,WAAW,SACX,IACE,CACP,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TechnicalTab.d.ts","sourceRoot":"","sources":["../../../src/views/seo/TechnicalTab.tsx"],"names":[],"mappings":"AAoSA,wBAAgB,YAAY,4CAU3B"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { RefreshCw, Map, AlertTriangle, CheckCircle2, ExternalLink } from 'lucide-react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { fetchSitemapStatus, regenerateSitemap, fetchCrawlSettings, updateCrawlSettings, fetchRobotsTxt, updateRobotsTxt, validateRobotsTxt, } from '../../lib/seo-service.js';
|
|
7
|
+
import { SectionCard, SeoLoading, SeoErrorState, btnPrimary, btnSecondary, } from '../../components/seo/primitives.js';
|
|
8
|
+
import { useSeoResource } from './useSeoResource.js';
|
|
9
|
+
const CRAWL_TOGGLES = [
|
|
10
|
+
{
|
|
11
|
+
key: 'indexEntireSite',
|
|
12
|
+
label: 'Index entire site',
|
|
13
|
+
help: 'Allow search engines to index all public pages.',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: 'includeImagesInSitemap',
|
|
17
|
+
label: 'Include images in sitemap',
|
|
18
|
+
help: 'Add image entries to the XML sitemap.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: 'includePostsInSitemap',
|
|
22
|
+
label: 'Include posts in sitemap',
|
|
23
|
+
help: 'Add published posts to the sitemap.',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: 'noindexPaginatedArchives',
|
|
27
|
+
label: 'No-index paginated archives',
|
|
28
|
+
help: 'Avoid duplicate-content penalties on page 2+.',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: 'noindexTagPages',
|
|
32
|
+
label: 'No-index tag pages',
|
|
33
|
+
help: 'Keep thin tag archives out of the index.',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
function Toggle({ checked, onChange, label, help, }) {
|
|
37
|
+
return (_jsxs("label", { className: "flex items-center justify-between gap-3 py-2.5", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-foreground block text-sm font-medium", children: label }), _jsx("span", { className: "text-muted-foreground block text-sm", children: help })] }), _jsx("input", { type: "checkbox", role: "switch", "aria-checked": checked, checked: checked, onChange: (e) => onChange(e.target.checked), className: "accent-primary h-4 w-4 shrink-0" })] }));
|
|
38
|
+
}
|
|
39
|
+
function SitemapCard() {
|
|
40
|
+
const { data, loading, error, refetch } = useSeoResource(fetchSitemapStatus, []);
|
|
41
|
+
const [busy, setBusy] = useState(false);
|
|
42
|
+
async function handleRegenerate() {
|
|
43
|
+
setBusy(true);
|
|
44
|
+
const res = await regenerateSitemap();
|
|
45
|
+
setBusy(false);
|
|
46
|
+
if (res.error) {
|
|
47
|
+
toast.error(res.error);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
toast.success('Sitemap regenerated.');
|
|
51
|
+
refetch();
|
|
52
|
+
}
|
|
53
|
+
return (_jsx(SectionCard, { title: "Sitemap", action: _jsxs("button", { className: btnSecondary, onClick: handleRegenerate, disabled: busy || loading, children: [_jsx(RefreshCw, { className: `h-4 w-4 ${busy ? 'animate-spin' : ''}`, "aria-hidden": true }), "Regenerate"] }), children: loading ? (_jsx(SeoLoading, { label: "Loading sitemap status\u2026" })) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : !data ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Sitemap status unavailable." })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("a", { href: data.sitemapUrl, target: "_blank", rel: "noopener noreferrer", className: "text-primary inline-flex items-center gap-1 text-sm hover:underline", children: [_jsx(Map, { className: "h-4 w-4", "aria-hidden": true }), data.sitemapUrl, _jsx(ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true })] }), _jsxs("dl", { className: "grid grid-cols-2 gap-3 text-sm", children: [_jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "Pages included" }), _jsx("dd", { className: "text-foreground font-medium tabular-nums", children: data.pagesIncluded })] }), _jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "Pages excluded" }), _jsx("dd", { className: "text-foreground font-medium tabular-nums", children: data.pagesExcluded })] }), _jsxs("div", { className: "col-span-2", children: [_jsx("dt", { className: "text-muted-foreground", children: "Last generated" }), _jsx("dd", { className: "text-foreground", children: data.lastGeneratedAt ? new Date(data.lastGeneratedAt).toLocaleString() : 'Never' })] })] }), data.warnings.length > 0 && (_jsx("ul", { className: "space-y-1", children: data.warnings.map((w, i) => (_jsxs("li", { className: "text-warning flex items-center gap-1.5 text-sm", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5", "aria-hidden": true }), " ", w] }, i))) }))] })) }));
|
|
54
|
+
}
|
|
55
|
+
function CrawlCard() {
|
|
56
|
+
const { data, loading, error, refetch } = useSeoResource(fetchCrawlSettings, []);
|
|
57
|
+
const [settings, setSettings] = useState(null);
|
|
58
|
+
const [saving, setSaving] = useState(false);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (data)
|
|
61
|
+
setSettings(data);
|
|
62
|
+
}, [data]);
|
|
63
|
+
async function save(next, changed) {
|
|
64
|
+
setSettings(next);
|
|
65
|
+
setSaving(true);
|
|
66
|
+
const res = await updateCrawlSettings({ [changed]: next[changed] });
|
|
67
|
+
setSaving(false);
|
|
68
|
+
if (res.error) {
|
|
69
|
+
toast.error(res.error);
|
|
70
|
+
refetch();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return (_jsx(SectionCard, { title: "Crawl Settings", children: loading ? (_jsx(SeoLoading, { label: "Loading crawl settings\u2026" })) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : !settings ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Crawl settings unavailable." })) : (_jsx("div", { className: "divide-border divide-y", "aria-busy": saving, children: CRAWL_TOGGLES.map((t) => (_jsx(Toggle, { label: t.label, help: t.help, checked: settings[t.key], onChange: (v) => save({ ...settings, [t.key]: v }, t.key) }, t.key))) })) }));
|
|
74
|
+
}
|
|
75
|
+
function RobotsCard() {
|
|
76
|
+
const { data, loading, error, refetch } = useSeoResource(fetchRobotsTxt, []);
|
|
77
|
+
const [body, setBody] = useState('');
|
|
78
|
+
const [saving, setSaving] = useState(false);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (data)
|
|
81
|
+
setBody(data.body);
|
|
82
|
+
}, [data]);
|
|
83
|
+
const validation = validateRobotsTxt(body);
|
|
84
|
+
async function handleSave() {
|
|
85
|
+
setSaving(true);
|
|
86
|
+
const res = await updateRobotsTxt(body);
|
|
87
|
+
setSaving(false);
|
|
88
|
+
if (res.error) {
|
|
89
|
+
toast.error(res.error);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
toast.success('robots.txt saved.');
|
|
93
|
+
refetch();
|
|
94
|
+
}
|
|
95
|
+
return (_jsx(SectionCard, { title: "robots.txt", description: "Controls which crawlers can access your site.", action: _jsx("button", { className: btnPrimary, onClick: handleSave, disabled: saving || loading || !validation.valid, children: saving ? 'Saving…' : 'Save' }), children: loading ? (_jsx(SeoLoading, { label: "Loading robots.txt\u2026" })) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : (_jsxs("div", { className: "space-y-3", children: [validation.blocksEntireSite && (_jsxs("div", { className: "border-destructive/30 bg-destructive/10 flex items-start gap-2 rounded-md border p-3", role: "alert", children: [_jsx(AlertTriangle, { className: "text-destructive mt-0.5 h-4 w-4 shrink-0", "aria-hidden": true }), _jsx("p", { className: "text-destructive text-sm", children: "This configuration blocks your entire site from all search engines. Pages will be removed from search results." })] })), _jsx("label", { className: "sr-only", htmlFor: "robots-editor", children: "robots.txt contents" }), _jsx("textarea", { id: "robots-editor", value: body, onChange: (e) => setBody(e.target.value), spellCheck: false, className: "border-border bg-input-background text-foreground focus-visible:ring-ring min-h-48 w-full rounded-md border p-3 font-mono text-sm focus-visible:ring-2 focus-visible:outline-none" }), validation.errors.length > 0 ? (_jsx("ul", { className: "space-y-1", children: validation.errors.map((e, i) => (_jsxs("li", { className: "text-destructive flex items-center gap-1.5 text-sm", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5", "aria-hidden": true }), " ", e] }, i))) })) : (_jsxs("p", { className: "text-success flex items-center gap-1.5 text-sm", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5", "aria-hidden": true }), " Valid robots.txt"] })), validation.warnings
|
|
96
|
+
.filter((w) => !validation.blocksEntireSite || !w.includes('blocks the entire site'))
|
|
97
|
+
.map((w, i) => (_jsxs("p", { className: "text-warning flex items-center gap-1.5 text-sm", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5", "aria-hidden": true }), " ", w] }, i)))] })) }));
|
|
98
|
+
}
|
|
99
|
+
export function TechnicalTab() {
|
|
100
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-2", children: [_jsx(SitemapCard, {}), _jsx(CrawlCard, {})] }), _jsx(RobotsCard, {})] }));
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=TechnicalTab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TechnicalTab.js","sourceRoot":"","sources":["../../../src/views/seo/TechnicalTab.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SeoResource<T> {
|
|
2
|
+
data: T | null;
|
|
3
|
+
loading: boolean;
|
|
4
|
+
error: string | null;
|
|
5
|
+
refetch: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function useSeoResource<T>(fetcher: () => Promise<T>, deps?: ReadonlyArray<unknown>): SeoResource<T>;
|
|
8
|
+
//# sourceMappingURL=useSeoResource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSeoResource.d.ts","sourceRoot":"","sources":["../../../src/views/seo/useSeoResource.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,IAAI,GAAE,aAAa,CAAC,OAAO,CAAM,GAChC,WAAW,CAAC,CAAC,CAAC,CAgChB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* Small async-resource hook for the SEO tabs: wraps a service fetcher with
|
|
4
|
+
* loading / error / data / refetch and abort-on-unmount safety. Keeps each
|
|
5
|
+
* tab declarative without re-implementing fetch plumbing.
|
|
6
|
+
*/
|
|
7
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
8
|
+
export function useSeoResource(fetcher, deps = []) {
|
|
9
|
+
const [data, setData] = useState(null);
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const aliveRef = useRef(true);
|
|
13
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14
|
+
const stableFetcher = useCallback(fetcher, deps);
|
|
15
|
+
const load = useCallback(async () => {
|
|
16
|
+
setLoading(true);
|
|
17
|
+
setError(null);
|
|
18
|
+
try {
|
|
19
|
+
const result = await stableFetcher();
|
|
20
|
+
if (aliveRef.current)
|
|
21
|
+
setData(result);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (aliveRef.current) {
|
|
25
|
+
setError(err instanceof Error ? err.message : 'Failed to load data');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (aliveRef.current)
|
|
30
|
+
setLoading(false);
|
|
31
|
+
}
|
|
32
|
+
}, [stableFetcher]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
aliveRef.current = true;
|
|
35
|
+
load();
|
|
36
|
+
return () => {
|
|
37
|
+
aliveRef.current = false;
|
|
38
|
+
};
|
|
39
|
+
}, [load]);
|
|
40
|
+
return { data, loading, error, refetch: load };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=useSeoResource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSeoResource.js","sourceRoot":"","sources":["../../../src/views/seo/useSeoResource.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAShE,MAAM,UAAU,cAAc,CAC5B,OAAyB,EACzB,OAA+B,EAAE;IAEjC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAW,IAAI,CAAC,CAAA;IAChD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IAC7B,uDAAuD;IACvD,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAEhD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAClC,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAA;YACpC,IAAI,QAAQ,CAAC,OAAO;gBAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAA;YACtE,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,QAAQ,CAAC,OAAO;gBAAE,UAAU,CAAC,KAAK,CAAC,CAAA;QACzC,CAAC;IACH,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAA;IAEnB,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAA;QACvB,IAAI,EAAE,CAAA;QACN,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAA;QAC1B,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAChD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuate-media/cms-admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/actuate-media/actuatecms.git",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"tailwindcss": "^4.0.0",
|
|
88
88
|
"typescript": "^5.7.0",
|
|
89
89
|
"vitest": "^3.0.0",
|
|
90
|
-
"@actuate-media/cms-core": "0.
|
|
90
|
+
"@actuate-media/cms-core": "0.29.0",
|
|
91
91
|
"@actuate-media/component-blocks": "0.2.0"
|
|
92
92
|
},
|
|
93
93
|
"scripts": {
|
package/src/AdminRoot.tsx
CHANGED
|
@@ -379,17 +379,21 @@ function AdminShell({
|
|
|
379
379
|
return <Forms onNavigate={navigate} />
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
// Legacy deep paths map onto the new five-tab IA.
|
|
382
383
|
if (matchRoute('/seo/redirects')) {
|
|
383
384
|
return <SEO onNavigate={navigate} initialTab="redirects" />
|
|
384
385
|
}
|
|
385
|
-
if (matchRoute('/seo/canonicals')) {
|
|
386
|
-
return <SEO onNavigate={navigate} initialTab="
|
|
386
|
+
if (matchRoute('/seo/content') || matchRoute('/seo/canonicals')) {
|
|
387
|
+
return <SEO onNavigate={navigate} initialTab="content" />
|
|
387
388
|
}
|
|
388
|
-
if (matchRoute('/seo/links')) {
|
|
389
|
-
return <SEO onNavigate={navigate} initialTab="
|
|
389
|
+
if (matchRoute('/seo/technical') || matchRoute('/seo/links')) {
|
|
390
|
+
return <SEO onNavigate={navigate} initialTab="technical" />
|
|
391
|
+
}
|
|
392
|
+
if (matchRoute('/seo/audit')) {
|
|
393
|
+
return <SEO onNavigate={navigate} initialTab="audit" />
|
|
390
394
|
}
|
|
391
395
|
if (matchRoute('/seo')) {
|
|
392
|
-
return <SEO onNavigate={navigate} initialTab="
|
|
396
|
+
return <SEO onNavigate={navigate} initialTab="overview" />
|
|
393
397
|
}
|
|
394
398
|
|
|
395
399
|
if (matchRoute('/script-tags/new')) {
|
|
@@ -135,26 +135,21 @@ describe('validateTemplate', () => {
|
|
|
135
135
|
|
|
136
136
|
describe('emptyPostFromTemplate', () => {
|
|
137
137
|
it('seeds sections from the type template with fresh ids', async () => {
|
|
138
|
-
// fetchTemplateForEditor
|
|
138
|
+
// fetchTemplateForEditor reads the dedicated /post-templates/:type endpoint,
|
|
139
|
+
// whose `{ data }` envelope is unwrapped by cmsApi to { postType, docId, header, sections }.
|
|
139
140
|
cmsApiMock.mockResolvedValueOnce({
|
|
140
141
|
data: {
|
|
141
|
-
|
|
142
|
+
postType: 'blog',
|
|
143
|
+
docId: 'tmpl-1',
|
|
144
|
+
header: { layout: 'left' },
|
|
145
|
+
sections: [
|
|
142
146
|
{
|
|
143
|
-
id: '
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
id: 'orig',
|
|
150
|
-
sectionType: 'article-body',
|
|
151
|
-
name: 'Body',
|
|
152
|
-
visible: true,
|
|
153
|
-
content: {},
|
|
154
|
-
settings: {},
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
},
|
|
147
|
+
id: 'orig',
|
|
148
|
+
sectionType: 'article-body',
|
|
149
|
+
name: 'Body',
|
|
150
|
+
visible: true,
|
|
151
|
+
content: {},
|
|
152
|
+
settings: {},
|
|
158
153
|
},
|
|
159
154
|
],
|
|
160
155
|
},
|
|
@@ -166,22 +161,82 @@ describe('emptyPostFromTemplate', () => {
|
|
|
166
161
|
expect(post.sections[0]!.sectionType).toBe('article-body')
|
|
167
162
|
// cloned: a fresh id, not the template's section id
|
|
168
163
|
expect(post.sections[0]!.id).not.toBe('orig')
|
|
164
|
+
// the endpoint was addressed by post type, not the raw collection API
|
|
165
|
+
expect(cmsApiMock.mock.calls[0]![0]).toBe('/post-templates/blog')
|
|
169
166
|
})
|
|
170
167
|
|
|
171
|
-
it('
|
|
172
|
-
|
|
168
|
+
it('seeds the default starting template when the type has no saved template', async () => {
|
|
169
|
+
// Unsaved type → endpoint returns docId null + empty sections; the service
|
|
170
|
+
// backfills the default (article-body + related-posts) so the post is never blank.
|
|
171
|
+
cmsApiMock.mockResolvedValueOnce({ data: { postType: 'news', docId: null, sections: [] } })
|
|
173
172
|
const post = await emptyPostFromTemplate('news')
|
|
174
|
-
expect(post.sections).toEqual([])
|
|
173
|
+
expect(post.sections.map((s) => s.sectionType)).toEqual(['article-body', 'related-posts'])
|
|
174
|
+
// cloned with fresh, distinct ids
|
|
175
|
+
expect(post.sections[0]!.id).not.toBe(post.sections[1]!.id)
|
|
175
176
|
})
|
|
176
177
|
})
|
|
177
178
|
|
|
178
179
|
describe('fetchTemplateForEditor', () => {
|
|
179
|
-
it('
|
|
180
|
-
cmsApiMock.mockResolvedValueOnce({
|
|
180
|
+
it('maps a saved template from the dedicated endpoint', async () => {
|
|
181
|
+
cmsApiMock.mockResolvedValueOnce({
|
|
182
|
+
data: {
|
|
183
|
+
postType: 'blog',
|
|
184
|
+
docId: 'tmpl-9',
|
|
185
|
+
header: { layout: 'overlay' },
|
|
186
|
+
sections: [
|
|
187
|
+
{ id: 's1', sectionType: 'quote', name: 'Q', visible: true, content: {}, settings: {} },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
const t = await fetchTemplateForEditor('blog')
|
|
192
|
+
expect(t.docId).toBe('tmpl-9')
|
|
193
|
+
expect(t.header.layout).toBe('overlay')
|
|
194
|
+
expect(t.sections).toHaveLength(1)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('seeds the default starting template when the type has no saved template', async () => {
|
|
198
|
+
cmsApiMock.mockResolvedValueOnce({ data: { postType: 'blog', docId: null, sections: [] } })
|
|
181
199
|
const t = await fetchTemplateForEditor('blog')
|
|
182
200
|
expect(t.docId).toBeNull()
|
|
183
201
|
expect(t.header.layout).toBe('centered')
|
|
184
|
-
expect(t.sections).toEqual([])
|
|
202
|
+
expect(t.sections.map((s) => s.sectionType)).toEqual(['article-body', 'related-posts'])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('falls back to the default template when the request fails', async () => {
|
|
206
|
+
cmsApiMock.mockResolvedValueOnce({ error: 'network down' })
|
|
207
|
+
const t = await fetchTemplateForEditor('blog')
|
|
208
|
+
expect(t.docId).toBeNull()
|
|
209
|
+
expect(t.sections.map((s) => s.sectionType)).toEqual(['article-body', 'related-posts'])
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
describe('saveTemplate', () => {
|
|
214
|
+
it('PUTs header + sections to the dedicated endpoint', async () => {
|
|
215
|
+
cmsApiMock.mockResolvedValueOnce({
|
|
216
|
+
data: { postType: 'blog', docId: 'tmpl-1', header: { layout: 'left' }, sections: [] },
|
|
217
|
+
})
|
|
218
|
+
const { saveTemplate } = await import('../../lib/post-editor-service.js')
|
|
219
|
+
const out = await saveTemplate({
|
|
220
|
+
docId: null,
|
|
221
|
+
postType: 'blog',
|
|
222
|
+
header: {
|
|
223
|
+
layout: 'left',
|
|
224
|
+
showFeaturedImage: true,
|
|
225
|
+
showExcerpt: true,
|
|
226
|
+
showAuthor: true,
|
|
227
|
+
showDate: true,
|
|
228
|
+
showCategory: true,
|
|
229
|
+
showReadingTime: false,
|
|
230
|
+
},
|
|
231
|
+
sections: [],
|
|
232
|
+
})
|
|
233
|
+
const [endpoint, init] = cmsApiMock.mock.calls[0]!
|
|
234
|
+
expect(endpoint).toBe('/post-templates/blog')
|
|
235
|
+
expect(init.method).toBe('PUT')
|
|
236
|
+
const body = JSON.parse(init.body as string)
|
|
237
|
+
expect(body.header.layout).toBe('left')
|
|
238
|
+
expect(Array.isArray(body.sections)).toBe(true)
|
|
239
|
+
expect(out.docId).toBe('tmpl-1')
|
|
185
240
|
})
|
|
186
241
|
})
|
|
187
242
|
|