@actuate-media/cms-admin 0.15.0 → 0.16.1
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 +148 -2
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/posts-badges.test.d.ts +2 -0
- package/dist/__tests__/components/posts-badges.test.d.ts.map +1 -0
- package/dist/__tests__/components/posts-badges.test.js +68 -0
- package/dist/__tests__/components/posts-badges.test.js.map +1 -0
- package/dist/__tests__/layout/sidebar-posts.test.d.ts +2 -0
- package/dist/__tests__/layout/sidebar-posts.test.d.ts.map +1 -0
- package/dist/__tests__/layout/sidebar-posts.test.js +162 -0
- package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.d.ts +2 -0
- package/dist/__tests__/lib/page-editor-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js +137 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -0
- package/dist/__tests__/lib/pages-service.test.d.ts +2 -0
- package/dist/__tests__/lib/pages-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/pages-service.test.js +140 -0
- package/dist/__tests__/lib/pages-service.test.js.map +1 -0
- package/dist/__tests__/lib/post-editor-service.test.d.ts +2 -0
- package/dist/__tests__/lib/post-editor-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/post-editor-service.test.js +185 -0
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -0
- package/dist/__tests__/lib/posts-service.test.d.ts +2 -0
- package/dist/__tests__/lib/posts-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/posts-service.test.js +244 -0
- package/dist/__tests__/lib/posts-service.test.js.map +1 -0
- package/dist/__tests__/lib/useApiData.test.d.ts +2 -0
- package/dist/__tests__/lib/useApiData.test.d.ts.map +1 -0
- package/dist/__tests__/lib/useApiData.test.js +84 -0
- package/dist/__tests__/lib/useApiData.test.js.map +1 -0
- package/dist/__tests__/views/dashboard.test.d.ts +2 -0
- package/dist/__tests__/views/dashboard.test.d.ts.map +1 -0
- package/dist/__tests__/views/dashboard.test.js +138 -0
- package/dist/__tests__/views/dashboard.test.js.map +1 -0
- package/dist/__tests__/views/posts-list-view.test.d.ts +2 -0
- package/dist/__tests__/views/posts-list-view.test.d.ts.map +1 -0
- package/dist/__tests__/views/posts-list-view.test.js +98 -0
- package/dist/__tests__/views/posts-list-view.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/pages/tag-badges.d.ts +21 -0
- package/dist/components/pages/tag-badges.d.ts.map +1 -0
- package/dist/components/pages/tag-badges.js +56 -0
- package/dist/components/pages/tag-badges.js.map +1 -0
- package/dist/components/posts/badges.d.ts +25 -0
- package/dist/components/posts/badges.d.ts.map +1 -0
- package/dist/components/posts/badges.js +123 -0
- package/dist/components/posts/badges.js.map +1 -0
- package/dist/components/posts/icons.d.ts +12 -0
- package/dist/components/posts/icons.d.ts.map +1 -0
- package/dist/components/posts/icons.js +26 -0
- package/dist/components/posts/icons.js.map +1 -0
- package/dist/layout/Layout.js +36 -8
- package/dist/layout/Layout.js.map +1 -1
- package/dist/layout/Sidebar.d.ts +9 -0
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +100 -48
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts +45 -0
- package/dist/lib/page-editor-service.d.ts.map +1 -0
- package/dist/lib/page-editor-service.js +153 -0
- package/dist/lib/page-editor-service.js.map +1 -0
- package/dist/lib/pages-service.d.ts +143 -0
- package/dist/lib/pages-service.d.ts.map +1 -0
- package/dist/lib/pages-service.js +444 -0
- package/dist/lib/pages-service.js.map +1 -0
- package/dist/lib/post-editor-service.d.ts +57 -0
- package/dist/lib/post-editor-service.d.ts.map +1 -0
- package/dist/lib/post-editor-service.js +234 -0
- package/dist/lib/post-editor-service.js.map +1 -0
- package/dist/lib/posts-service.d.ts +73 -0
- package/dist/lib/posts-service.d.ts.map +1 -0
- package/dist/lib/posts-service.js +245 -0
- package/dist/lib/posts-service.js.map +1 -0
- package/dist/lib/preview-link.d.ts +32 -0
- package/dist/lib/preview-link.d.ts.map +1 -0
- package/dist/lib/preview-link.js +40 -0
- package/dist/lib/preview-link.js.map +1 -0
- package/dist/router/index.d.ts +1 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +9 -1
- package/dist/router/index.js.map +1 -1
- package/dist/views/Dashboard.d.ts.map +1 -1
- package/dist/views/Dashboard.js +64 -20
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/Pages/PageTagManager.d.ts +14 -0
- package/dist/views/Pages/PageTagManager.d.ts.map +1 -0
- package/dist/views/Pages/PageTagManager.js +145 -0
- package/dist/views/Pages/PageTagManager.js.map +1 -0
- package/dist/views/Pages/PagesListView.d.ts +18 -0
- package/dist/views/Pages/PagesListView.d.ts.map +1 -0
- package/dist/views/Pages/PagesListView.js +287 -0
- package/dist/views/Pages/PagesListView.js.map +1 -0
- package/dist/views/Posts/NewPostPicker.d.ts +16 -0
- package/dist/views/Posts/NewPostPicker.d.ts.map +1 -0
- package/dist/views/Posts/NewPostPicker.js +59 -0
- package/dist/views/Posts/NewPostPicker.js.map +1 -0
- package/dist/views/Posts/NewPostTypeInstructions.d.ts +18 -0
- package/dist/views/Posts/NewPostTypeInstructions.d.ts.map +1 -0
- package/dist/views/Posts/NewPostTypeInstructions.js +147 -0
- package/dist/views/Posts/NewPostTypeInstructions.js.map +1 -0
- package/dist/views/Posts/PostTypesView.d.ts +26 -0
- package/dist/views/Posts/PostTypesView.d.ts.map +1 -0
- package/dist/views/Posts/PostTypesView.js +52 -0
- package/dist/views/Posts/PostTypesView.js.map +1 -0
- package/dist/views/Posts/PostsListView.d.ts +25 -0
- package/dist/views/Posts/PostsListView.d.ts.map +1 -0
- package/dist/views/Posts/PostsListView.js +239 -0
- package/dist/views/Posts/PostsListView.js.map +1 -0
- package/dist/views/Posts/post-type-presets.d.ts +32 -0
- package/dist/views/Posts/post-type-presets.d.ts.map +1 -0
- package/dist/views/Posts/post-type-presets.js +72 -0
- package/dist/views/Posts/post-type-presets.js.map +1 -0
- package/dist/views/page-builder/Widgets.d.ts +13 -0
- package/dist/views/page-builder/Widgets.d.ts.map +1 -0
- package/dist/views/page-builder/Widgets.js +15 -0
- package/dist/views/page-builder/Widgets.js.map +1 -0
- package/dist/views/page-editor/AddSectionModal.d.ts +20 -0
- package/dist/views/page-editor/AddSectionModal.d.ts.map +1 -0
- package/dist/views/page-editor/AddSectionModal.js +22 -0
- package/dist/views/page-editor/AddSectionModal.js.map +1 -0
- package/dist/views/page-editor/EditorCanvas.d.ts +18 -0
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -0
- package/dist/views/page-editor/EditorCanvas.js +64 -0
- package/dist/views/page-editor/EditorCanvas.js.map +1 -0
- package/dist/views/page-editor/EditorTopBar.d.ts +30 -0
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -0
- package/dist/views/page-editor/EditorTopBar.js +42 -0
- package/dist/views/page-editor/EditorTopBar.js.map +1 -0
- package/dist/views/page-editor/PagePreview.d.ts +13 -0
- package/dist/views/page-editor/PagePreview.d.ts.map +1 -0
- package/dist/views/page-editor/PagePreview.js +37 -0
- package/dist/views/page-editor/PagePreview.js.map +1 -0
- package/dist/views/page-editor/PageSectionEditor.d.ts +16 -0
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -0
- package/dist/views/page-editor/PageSectionEditor.js +272 -0
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -0
- package/dist/views/page-editor/PageSettingsModal.d.ts +12 -0
- package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -0
- package/dist/views/page-editor/PageSettingsModal.js +52 -0
- package/dist/views/page-editor/PageSettingsModal.js.map +1 -0
- package/dist/views/page-editor/SectionInspector.d.ts +17 -0
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -0
- package/dist/views/page-editor/SectionInspector.js +98 -0
- package/dist/views/page-editor/SectionInspector.js.map +1 -0
- package/dist/views/page-editor/SectionsPanel.d.ts +15 -0
- package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -0
- package/dist/views/page-editor/SectionsPanel.js +43 -0
- package/dist/views/page-editor/SectionsPanel.js.map +1 -0
- package/dist/views/page-editor/ValidationSummary.d.ts +9 -0
- package/dist/views/page-editor/ValidationSummary.d.ts.map +1 -0
- package/dist/views/page-editor/ValidationSummary.js +10 -0
- package/dist/views/page-editor/ValidationSummary.js.map +1 -0
- package/dist/views/page-editor/section-icons.d.ts +4 -0
- package/dist/views/page-editor/section-icons.d.ts.map +1 -0
- package/dist/views/page-editor/section-icons.js +20 -0
- package/dist/views/page-editor/section-icons.js.map +1 -0
- package/dist/views/page-editor/section-types.d.ts +11 -0
- package/dist/views/page-editor/section-types.d.ts.map +1 -0
- package/dist/views/page-editor/section-types.js +10 -0
- package/dist/views/page-editor/section-types.js.map +1 -0
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts +13 -0
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/ArticleBodySection.js +23 -0
- package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -0
- package/dist/views/page-editor/sections/AuthorBioSection.d.ts +8 -0
- package/dist/views/page-editor/sections/AuthorBioSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/AuthorBioSection.js +21 -0
- package/dist/views/page-editor/sections/AuthorBioSection.js.map +1 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts +5 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.js +10 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -0
- package/dist/views/page-editor/sections/CtaBannerSection.d.ts +5 -0
- package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/CtaBannerSection.js +10 -0
- package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts +5 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.js +18 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -0
- package/dist/views/page-editor/sections/GallerySection.d.ts +6 -0
- package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/GallerySection.js +17 -0
- package/dist/views/page-editor/sections/GallerySection.js.map +1 -0
- package/dist/views/page-editor/sections/HeroBannerSection.d.ts +5 -0
- package/dist/views/page-editor/sections/HeroBannerSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/HeroBannerSection.js +11 -0
- package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -0
- package/dist/views/page-editor/sections/MissionSection.d.ts +6 -0
- package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/MissionSection.js +12 -0
- package/dist/views/page-editor/sections/MissionSection.js.map +1 -0
- package/dist/views/page-editor/sections/PullQuoteSection.d.ts +6 -0
- package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/PullQuoteSection.js +13 -0
- package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -0
- package/dist/views/page-editor/sections/RelatedPostsSection.d.ts +12 -0
- package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/RelatedPostsSection.js +23 -0
- package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -0
- package/dist/views/page-editor/sections/SectionRenderer.d.ts +22 -0
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -0
- package/dist/views/page-editor/sections/SectionRenderer.js +52 -0
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -0
- package/dist/views/page-editor/sections/index.d.ts +14 -0
- package/dist/views/page-editor/sections/index.d.ts.map +1 -0
- package/dist/views/page-editor/sections/index.js +12 -0
- package/dist/views/page-editor/sections/index.js.map +1 -0
- package/dist/views/page-editor/sections/parts.d.ts +55 -0
- package/dist/views/page-editor/sections/parts.d.ts.map +1 -0
- package/dist/views/page-editor/sections/parts.js +60 -0
- package/dist/views/page-editor/sections/parts.js.map +1 -0
- package/dist/views/page-editor/viewports.d.ts +14 -0
- package/dist/views/page-editor/viewports.d.ts.map +1 -0
- package/dist/views/page-editor/viewports.js +12 -0
- package/dist/views/page-editor/viewports.js.map +1 -0
- package/dist/views/post-editor/PostEditorCanvas.d.ts +21 -0
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -0
- package/dist/views/post-editor/PostEditorCanvas.js +58 -0
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -0
- package/dist/views/post-editor/PostFieldsModal.d.ts +12 -0
- package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -0
- package/dist/views/post-editor/PostFieldsModal.js +53 -0
- package/dist/views/post-editor/PostFieldsModal.js.map +1 -0
- package/dist/views/post-editor/PostHeader.d.ts +15 -0
- package/dist/views/post-editor/PostHeader.d.ts.map +1 -0
- package/dist/views/post-editor/PostHeader.js +27 -0
- package/dist/views/post-editor/PostHeader.js.map +1 -0
- package/dist/views/post-editor/PostHeaderPanel.d.ts +11 -0
- package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -0
- package/dist/views/post-editor/PostHeaderPanel.js +21 -0
- package/dist/views/post-editor/PostHeaderPanel.js.map +1 -0
- package/dist/views/post-editor/PostPreview.d.ts +14 -0
- package/dist/views/post-editor/PostPreview.d.ts.map +1 -0
- package/dist/views/post-editor/PostPreview.js +53 -0
- package/dist/views/post-editor/PostPreview.js.map +1 -0
- package/dist/views/post-editor/PostSectionEditor.d.ts +17 -0
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -0
- package/dist/views/post-editor/PostSectionEditor.js +290 -0
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -0
- package/dist/views/post-editor/PostTemplateEditor.d.ts +9 -0
- package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -0
- package/dist/views/post-editor/PostTemplateEditor.js +144 -0
- package/dist/views/post-editor/PostTemplateEditor.js.map +1 -0
- package/package.json +2 -2
- package/src/AdminRoot.tsx +214 -2
- package/src/__tests__/components/posts-badges.test.tsx +77 -0
- package/src/__tests__/layout/sidebar-posts.test.ts +179 -0
- package/src/__tests__/lib/page-editor-service.test.ts +173 -0
- package/src/__tests__/lib/pages-service.test.ts +157 -0
- package/src/__tests__/lib/post-editor-service.test.ts +215 -0
- package/src/__tests__/lib/posts-service.test.ts +279 -0
- package/src/__tests__/lib/useApiData.test.ts +105 -0
- package/src/__tests__/views/dashboard.test.tsx +180 -0
- package/src/__tests__/views/posts-list-view.test.ts +110 -0
- package/src/components/pages/tag-badges.tsx +104 -0
- package/src/components/posts/badges.tsx +170 -0
- package/src/components/posts/icons.ts +40 -0
- package/src/layout/Layout.tsx +66 -10
- package/src/layout/Sidebar.tsx +116 -50
- package/src/lib/page-editor-service.ts +233 -0
- package/src/lib/pages-service.ts +599 -0
- package/src/lib/post-editor-service.ts +342 -0
- package/src/lib/posts-service.ts +366 -0
- package/src/lib/preview-link.ts +63 -0
- package/src/router/index.ts +10 -1
- package/src/views/Dashboard.tsx +142 -89
- package/src/views/Pages/PageTagManager.tsx +436 -0
- package/src/views/Pages/PagesListView.tsx +944 -0
- package/src/views/Posts/NewPostPicker.tsx +147 -0
- package/src/views/Posts/NewPostTypeInstructions.tsx +344 -0
- package/src/views/Posts/PostTypesView.tsx +194 -0
- package/src/views/Posts/PostsListView.tsx +869 -0
- package/src/views/Posts/post-type-presets.ts +105 -0
- package/src/views/page-builder/Widgets.tsx +44 -0
- package/src/views/page-editor/AddSectionModal.tsx +56 -0
- package/src/views/page-editor/EditorCanvas.tsx +127 -0
- package/src/views/page-editor/EditorTopBar.tsx +233 -0
- package/src/views/page-editor/PagePreview.tsx +96 -0
- package/src/views/page-editor/PageSectionEditor.tsx +476 -0
- package/src/views/page-editor/PageSettingsModal.tsx +154 -0
- package/src/views/page-editor/SectionInspector.tsx +599 -0
- package/src/views/page-editor/SectionsPanel.tsx +270 -0
- package/src/views/page-editor/ValidationSummary.tsx +53 -0
- package/src/views/page-editor/section-icons.ts +33 -0
- package/src/views/page-editor/section-types.ts +27 -0
- package/src/views/page-editor/sections/ArticleBodySection.tsx +57 -0
- package/src/views/page-editor/sections/AuthorBioSection.tsx +61 -0
- package/src/views/page-editor/sections/ByTheNumbersSection.tsx +41 -0
- package/src/views/page-editor/sections/CtaBannerSection.tsx +29 -0
- package/src/views/page-editor/sections/FeatureHighlightSection.tsx +63 -0
- package/src/views/page-editor/sections/GallerySection.tsx +54 -0
- package/src/views/page-editor/sections/HeroBannerSection.tsx +44 -0
- package/src/views/page-editor/sections/MissionSection.tsx +22 -0
- package/src/views/page-editor/sections/PullQuoteSection.tsx +27 -0
- package/src/views/page-editor/sections/RelatedPostsSection.tsx +55 -0
- package/src/views/page-editor/sections/SectionRenderer.tsx +102 -0
- package/src/views/page-editor/sections/index.ts +13 -0
- package/src/views/page-editor/sections/parts.tsx +113 -0
- package/src/views/page-editor/viewports.ts +25 -0
- package/src/views/post-editor/PostEditorCanvas.tsx +123 -0
- package/src/views/post-editor/PostFieldsModal.tsx +196 -0
- package/src/views/post-editor/PostHeader.tsx +87 -0
- package/src/views/post-editor/PostHeaderPanel.tsx +107 -0
- package/src/views/post-editor/PostPreview.tsx +114 -0
- package/src/views/post-editor/PostSectionEditor.tsx +506 -0
- package/src/views/post-editor/PostTemplateEditor.tsx +347 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { cmsApi } from './api.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mint a signed, time-boxed preview token for a single document. The token is
|
|
5
|
+
* bound to the (collection, documentId) pair server-side, so it can only
|
|
6
|
+
* unlock the exact draft it was issued for. Used to build public preview URLs
|
|
7
|
+
* (`/<prefix>/<slug>?preview=<token>`) that render a draft through the
|
|
8
|
+
* consumer's real front-end — see {@link buildPublicPreviewUrl}.
|
|
9
|
+
*/
|
|
10
|
+
export async function createPreviewToken(
|
|
11
|
+
collection: string,
|
|
12
|
+
documentId: string,
|
|
13
|
+
ttlSeconds?: number,
|
|
14
|
+
): Promise<{ token: string; expiresAt: string; slug?: string | null }> {
|
|
15
|
+
const res = await cmsApi<{ token: string; expiresAt: string; slug?: string | null }>(
|
|
16
|
+
'/preview/token',
|
|
17
|
+
{
|
|
18
|
+
method: 'POST',
|
|
19
|
+
body: JSON.stringify({ collection, documentId, ttlSeconds }),
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
if (res.error || !res.data) {
|
|
23
|
+
throw new Error(res.error ?? 'Failed to create preview token')
|
|
24
|
+
}
|
|
25
|
+
return res.data
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PublicPreviewUrlParts {
|
|
29
|
+
/** Absolute site origin, e.g. `https://example.com`. Trailing slash optional. */
|
|
30
|
+
siteUrl: string
|
|
31
|
+
/** Collection URL prefix (e.g. `blog`). Empty for top-level page collections. */
|
|
32
|
+
urlPrefix?: string | null
|
|
33
|
+
/** Document slug used in the public path. */
|
|
34
|
+
slug: string
|
|
35
|
+
/** Signed preview token from {@link createPreviewToken}. */
|
|
36
|
+
token: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build the public URL a draft preview opens at — the SAME path the
|
|
41
|
+
* `/resolve` endpoint matches: `<urlPrefix>/<slug>` (or `/<slug>` for
|
|
42
|
+
* top-level pages), with the preview token on the query string so the
|
|
43
|
+
* public route can drop the `PUBLISHED` filter for this one document.
|
|
44
|
+
*/
|
|
45
|
+
export function buildPublicPreviewUrl({
|
|
46
|
+
siteUrl,
|
|
47
|
+
urlPrefix,
|
|
48
|
+
slug,
|
|
49
|
+
token,
|
|
50
|
+
}: PublicPreviewUrlParts): string {
|
|
51
|
+
const base = (siteUrl ?? '').replace(/\/+$/, '')
|
|
52
|
+
const prefix = (urlPrefix ?? '').replace(/^\/+|\/+$/g, '')
|
|
53
|
+
const cleanSlug = slug.replace(/^\/+|\/+$/g, '')
|
|
54
|
+
const path = prefix ? `/${prefix}/${cleanSlug}` : `/${cleanSlug}`
|
|
55
|
+
return `${base}${path}?preview=${encodeURIComponent(token)}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Resolve the public site origin: explicit config first, else the current tab's origin. */
|
|
59
|
+
export function resolveSiteUrl(configSiteUrl?: string | null): string {
|
|
60
|
+
if (configSiteUrl && configSiteUrl.trim()) return configSiteUrl.trim()
|
|
61
|
+
if (typeof window !== 'undefined') return window.location.origin
|
|
62
|
+
return ''
|
|
63
|
+
}
|
package/src/router/index.ts
CHANGED
|
@@ -44,6 +44,15 @@ export function useAdminRouter(basePath = '/admin', serverPath = '/') {
|
|
|
44
44
|
window.history.pushState({ adminPath: normalizedPath }, '', fullUrl)
|
|
45
45
|
}, [])
|
|
46
46
|
|
|
47
|
+
// Build an absolute (basePath-prefixed) admin URL without navigating. Used
|
|
48
|
+
// for links that must load a real document — e.g. opening a preview in a
|
|
49
|
+
// new browser tab, where `pushState` (which only mutates the current tab)
|
|
50
|
+
// is not an option.
|
|
51
|
+
const buildHref = useCallback((path: string) => {
|
|
52
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`
|
|
53
|
+
return normalizedPath === '/' ? baseRef.current : `${baseRef.current}${normalizedPath}`
|
|
54
|
+
}, [])
|
|
55
|
+
|
|
47
56
|
const matchRoute = useCallback(
|
|
48
57
|
(pattern: string): RouteParams | null => {
|
|
49
58
|
const patternParts = pattern.split('/').filter(Boolean)
|
|
@@ -75,5 +84,5 @@ export function useAdminRouter(basePath = '/admin', serverPath = '/') {
|
|
|
75
84
|
[currentPath],
|
|
76
85
|
)
|
|
77
86
|
|
|
78
|
-
return { currentPath, navigate, matchRoute }
|
|
87
|
+
return { currentPath, navigate, matchRoute, buildHref }
|
|
79
88
|
}
|
package/src/views/Dashboard.tsx
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* >= 1024 (desktop): stat cards 5-col, main grid is `1fr 320px`
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { useMemo, useState } from 'react'
|
|
18
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
19
19
|
import {
|
|
20
20
|
FileText,
|
|
21
21
|
File as FileIcon,
|
|
@@ -119,6 +119,24 @@ function collectionLabel(col: CollectionMeta, plural = true): string {
|
|
|
119
119
|
return col.labels?.singular ?? fallback
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Resolve the correct editor route for a recent/queued document.
|
|
124
|
+
*
|
|
125
|
+
* Post-type documents MUST open in the section-based post editor
|
|
126
|
+
* (`/posts/:type/:id/edit`). Routing them through the generic
|
|
127
|
+
* `/:collection/:id` handler lands them in the legacy `DocumentEdit`
|
|
128
|
+
* view, which has no concept of the section model — opening + saving
|
|
129
|
+
* there silently strips a post's `sections` data. The canonical Pages
|
|
130
|
+
* collection opens in the full page editor (which wires up preview).
|
|
131
|
+
* Everything else falls through to the existing generic collection
|
|
132
|
+
* route, which already resolves correctly.
|
|
133
|
+
*/
|
|
134
|
+
function editPathForDoc(col: CollectionMeta | undefined, collection: string, id: string): string {
|
|
135
|
+
if (col?.type === 'post') return `/posts/${collection}/${id}/edit`
|
|
136
|
+
if (collection === 'pages') return `/pages/${id}/edit`
|
|
137
|
+
return `/${collection}/${id}`
|
|
138
|
+
}
|
|
139
|
+
|
|
122
140
|
function relativeTime(dateStr: string): string {
|
|
123
141
|
const then = new Date(dateStr).getTime()
|
|
124
142
|
const diff = Date.now() - then
|
|
@@ -252,8 +270,18 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
252
270
|
const { data: contentHealthData, loading: contentHealthLoading } =
|
|
253
271
|
useApiData<ContentHealthData>('/stats/content-health')
|
|
254
272
|
|
|
255
|
-
|
|
256
|
-
|
|
273
|
+
// A slow ticker (60s) so the greeting, calendar date, and "x minutes
|
|
274
|
+
// ago" relative times stay correct on a dashboard left open for hours
|
|
275
|
+
// — without it, a tab opened at 11:58am would read "Good morning" all
|
|
276
|
+
// afternoon and show stale activity timestamps.
|
|
277
|
+
const [now, setNow] = useState(() => Date.now())
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
const id = setInterval(() => setNow(Date.now()), 60_000)
|
|
280
|
+
return () => clearInterval(id)
|
|
281
|
+
}, [])
|
|
282
|
+
|
|
283
|
+
const greeting = useMemo(() => timeOfDayGreeting(), [now])
|
|
284
|
+
const dateStr = useMemo(() => todayDateString(), [now])
|
|
257
285
|
const userName = useMemo(() => firstNameForGreeting(session), [session])
|
|
258
286
|
|
|
259
287
|
const collections = useMemo(() => resolveCollections(config), [config])
|
|
@@ -358,12 +386,24 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
358
386
|
// dashboard's content-creation toolbar. Order mirrors the natural
|
|
359
387
|
// authoring flow (post → page → media → form → integration).
|
|
360
388
|
const quickActions = useMemo(() => {
|
|
361
|
-
const
|
|
362
|
-
const postsSlug = posts?.slug ?? 'posts'
|
|
389
|
+
const postTypes = collections.filter((c) => c.type === 'post')
|
|
363
390
|
const pages = collections.find((c) => c.slug === 'pages' || c.type === 'page')
|
|
364
391
|
const pagesSlug = pages?.slug ?? 'pages'
|
|
392
|
+
// "New Post": one Post Type → straight to its editor; several →
|
|
393
|
+
// the picker; none → the Post Type setup instructions (avoids a
|
|
394
|
+
// broken slug-based new URL).
|
|
395
|
+
const newPostPath =
|
|
396
|
+
postTypes.length === 1
|
|
397
|
+
? `/${postTypes[0]!.slug}/new`
|
|
398
|
+
: postTypes.length > 1
|
|
399
|
+
? '/posts/new'
|
|
400
|
+
: '/posts/types/new'
|
|
365
401
|
const items: { label: string; icon: LucideIcon; onClick: () => void }[] = [
|
|
366
|
-
{
|
|
402
|
+
{
|
|
403
|
+
label: 'New Post',
|
|
404
|
+
icon: Plus,
|
|
405
|
+
onClick: () => nav(newPostPath),
|
|
406
|
+
},
|
|
367
407
|
{ label: 'New Page', icon: Plus, onClick: () => nav(`/${pagesSlug}/new`) },
|
|
368
408
|
{ label: 'Upload Media', icon: Upload, onClick: () => nav('/media') },
|
|
369
409
|
{ label: 'New Form', icon: Plus, onClick: () => nav('/forms') },
|
|
@@ -384,9 +424,12 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
384
424
|
relTime: relativeTime(d.updatedAt),
|
|
385
425
|
avatar: authorAvatar(d.author),
|
|
386
426
|
statusInfo: statusBadge(d.status),
|
|
427
|
+
editPath: editPathForDoc(col, d.collection, d.id),
|
|
387
428
|
}
|
|
388
429
|
})
|
|
389
|
-
|
|
430
|
+
// `now` keeps the relative timestamps fresh on a long-open tab.
|
|
431
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
432
|
+
}, [stats, collections, activityLimit, now])
|
|
390
433
|
|
|
391
434
|
// ── Publishing queue (scheduled docs only) ──────────────────────────────
|
|
392
435
|
const publishQueue = useMemo(() => {
|
|
@@ -403,9 +446,11 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
403
446
|
type: col ? collectionLabel(col, false) : d.collection,
|
|
404
447
|
date: relativeTime(d.updatedAt),
|
|
405
448
|
author: d.author,
|
|
449
|
+
editPath: editPathForDoc(col, d.collection, d.id),
|
|
406
450
|
}
|
|
407
451
|
})
|
|
408
|
-
|
|
452
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
453
|
+
}, [stats, collections, now])
|
|
409
454
|
|
|
410
455
|
// ── Content health (sourced from /stats/content-health) ─────────────────
|
|
411
456
|
// Persisted by the nightly content-health cron. The four issue rows
|
|
@@ -606,12 +651,14 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
606
651
|
<h2 className="text-foreground text-sm font-semibold">Recent Activity</h2>
|
|
607
652
|
<p className="text-muted-foreground mt-0.5 text-xs">Last 7 days across all content</p>
|
|
608
653
|
</div>
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
654
|
+
{(stats?.recentDocuments?.length ?? 0) > 8 && (
|
|
655
|
+
<button
|
|
656
|
+
className="text-xs font-medium text-violet-600 hover:underline dark:text-violet-400"
|
|
657
|
+
onClick={() => setActivityLimit((n) => (n >= 20 ? 8 : 20))}
|
|
658
|
+
>
|
|
659
|
+
{activityLimit >= 20 ? 'Show less' : 'Show more'}
|
|
660
|
+
</button>
|
|
661
|
+
)}
|
|
615
662
|
</header>
|
|
616
663
|
{activity.length === 0 ? (
|
|
617
664
|
<EmptyState
|
|
@@ -622,38 +669,40 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
622
669
|
) : (
|
|
623
670
|
<ul role="list" className="divide-border divide-y">
|
|
624
671
|
{activity.map((it) => (
|
|
625
|
-
<li
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
<div
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
<
|
|
640
|
-
<
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
<
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
672
|
+
<li key={it.id}>
|
|
673
|
+
<button
|
|
674
|
+
type="button"
|
|
675
|
+
onClick={() => nav(it.editPath)}
|
|
676
|
+
className="hover:bg-accent/50 focus-visible:bg-accent/50 w-full cursor-pointer px-4 py-3 text-left transition-colors"
|
|
677
|
+
>
|
|
678
|
+
<div className="flex min-w-0 items-start gap-3">
|
|
679
|
+
<div
|
|
680
|
+
className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[11px] font-bold text-white"
|
|
681
|
+
style={{ background: it.avatar.color }}
|
|
682
|
+
aria-hidden
|
|
683
|
+
>
|
|
684
|
+
{it.avatar.initials}
|
|
685
|
+
</div>
|
|
686
|
+
<div className="min-w-0 flex-1">
|
|
687
|
+
<p className="text-foreground truncate text-sm leading-snug">
|
|
688
|
+
<span className="font-semibold">
|
|
689
|
+
“{it.title || 'Untitled'}”
|
|
690
|
+
</span>{' '}
|
|
691
|
+
<span className="text-muted-foreground">— {it.typeLabel}</span>
|
|
692
|
+
</p>
|
|
693
|
+
<div className="text-muted-foreground mt-1 flex flex-wrap items-center gap-2 text-[11px]">
|
|
694
|
+
<span
|
|
695
|
+
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium ${it.statusInfo.cls}`}
|
|
696
|
+
>
|
|
697
|
+
{it.statusInfo.label}
|
|
698
|
+
</span>
|
|
699
|
+
<span className="max-w-[120px] truncate">{it.author}</span>
|
|
700
|
+
<span aria-hidden>·</span>
|
|
701
|
+
<span>{it.relTime}</span>
|
|
702
|
+
</div>
|
|
654
703
|
</div>
|
|
655
704
|
</div>
|
|
656
|
-
</
|
|
705
|
+
</button>
|
|
657
706
|
</li>
|
|
658
707
|
))}
|
|
659
708
|
</ul>
|
|
@@ -679,7 +728,7 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
679
728
|
// link to the first scheduled item — that's where authors
|
|
680
729
|
// typically need to land to reschedule or cancel.
|
|
681
730
|
const first = publishQueue[0]
|
|
682
|
-
if (first) nav(
|
|
731
|
+
if (first) nav(first.editPath)
|
|
683
732
|
}}
|
|
684
733
|
>
|
|
685
734
|
Manage
|
|
@@ -695,21 +744,23 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
695
744
|
) : (
|
|
696
745
|
<ul role="list" className="divide-border divide-y">
|
|
697
746
|
{publishQueue.map((q) => (
|
|
698
|
-
<li
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
<
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
747
|
+
<li key={q.id}>
|
|
748
|
+
<button
|
|
749
|
+
type="button"
|
|
750
|
+
onClick={() => nav(q.editPath)}
|
|
751
|
+
className="hover:bg-accent/50 focus-visible:bg-accent/50 flex w-full min-w-0 cursor-pointer items-center gap-2.5 px-4 py-2.5 text-left transition-colors"
|
|
752
|
+
>
|
|
753
|
+
<span className="h-2 w-2 shrink-0 rounded-full bg-amber-500" aria-hidden />
|
|
754
|
+
<div className="min-w-0 flex-1">
|
|
755
|
+
<p className="text-foreground truncate text-sm font-medium">{q.title}</p>
|
|
756
|
+
<p className="text-muted-foreground truncate text-[11px]">
|
|
757
|
+
{q.date} · {q.author}
|
|
758
|
+
</p>
|
|
759
|
+
</div>
|
|
760
|
+
<span className="border-border bg-background text-muted-foreground shrink-0 rounded border px-1.5 py-0.5 text-[10px] capitalize">
|
|
761
|
+
{q.type}
|
|
762
|
+
</span>
|
|
763
|
+
</button>
|
|
713
764
|
</li>
|
|
714
765
|
))}
|
|
715
766
|
</ul>
|
|
@@ -774,36 +825,38 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
|
|
|
774
825
|
) : (
|
|
775
826
|
<ul role="list" className="divide-border divide-y">
|
|
776
827
|
{contentHealth.issues.map((iss, i) => (
|
|
777
|
-
<li
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
<span
|
|
783
|
-
className={`h-1.5 w-1.5 shrink-0 rounded-full ${
|
|
784
|
-
iss.tone === 'err'
|
|
785
|
-
? 'bg-red-500'
|
|
786
|
-
: iss.tone === 'warn'
|
|
787
|
-
? 'bg-amber-500'
|
|
788
|
-
: 'bg-muted-foreground'
|
|
789
|
-
}`}
|
|
790
|
-
aria-hidden
|
|
791
|
-
/>
|
|
792
|
-
<span className="text-muted-foreground flex-1 truncate text-xs">
|
|
793
|
-
{iss.label}
|
|
794
|
-
</span>
|
|
795
|
-
<span
|
|
796
|
-
className={`shrink-0 text-sm font-semibold ${
|
|
797
|
-
iss.tone === 'err'
|
|
798
|
-
? 'text-red-600 dark:text-red-400'
|
|
799
|
-
: iss.tone === 'warn'
|
|
800
|
-
? 'text-amber-600 dark:text-amber-400'
|
|
801
|
-
: 'text-muted-foreground'
|
|
802
|
-
}`}
|
|
828
|
+
<li key={i}>
|
|
829
|
+
<button
|
|
830
|
+
type="button"
|
|
831
|
+
onClick={() => nav('/seo')}
|
|
832
|
+
className="hover:bg-accent/50 focus-visible:bg-accent/50 flex w-full items-center gap-2.5 px-4 py-2 text-left transition-colors"
|
|
803
833
|
>
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
834
|
+
<span
|
|
835
|
+
className={`h-1.5 w-1.5 shrink-0 rounded-full ${
|
|
836
|
+
iss.tone === 'err'
|
|
837
|
+
? 'bg-red-500'
|
|
838
|
+
: iss.tone === 'warn'
|
|
839
|
+
? 'bg-amber-500'
|
|
840
|
+
: 'bg-muted-foreground'
|
|
841
|
+
}`}
|
|
842
|
+
aria-hidden
|
|
843
|
+
/>
|
|
844
|
+
<span className="text-muted-foreground flex-1 truncate text-xs">
|
|
845
|
+
{iss.label}
|
|
846
|
+
</span>
|
|
847
|
+
<span
|
|
848
|
+
className={`shrink-0 text-sm font-semibold ${
|
|
849
|
+
iss.tone === 'err'
|
|
850
|
+
? 'text-red-600 dark:text-red-400'
|
|
851
|
+
: iss.tone === 'warn'
|
|
852
|
+
? 'text-amber-600 dark:text-amber-400'
|
|
853
|
+
: 'text-muted-foreground'
|
|
854
|
+
}`}
|
|
855
|
+
>
|
|
856
|
+
{iss.count}
|
|
857
|
+
</span>
|
|
858
|
+
<ChevronRight className="text-muted-foreground/60 h-3.5 w-3.5 shrink-0" />
|
|
859
|
+
</button>
|
|
807
860
|
</li>
|
|
808
861
|
))}
|
|
809
862
|
</ul>
|