@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
package/src/AdminRoot.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState, useMemo, useEffect, useRef } from 'react'
|
|
3
|
+
import { useState, useMemo, useEffect, useRef, useCallback } from 'react'
|
|
4
|
+
import { toast } from 'sonner'
|
|
4
5
|
import { Layout } from './layout/Layout.js'
|
|
5
6
|
import { useAdminRouter } from './router/index.js'
|
|
6
7
|
import { ensureCsrfToken } from './lib/api.js'
|
|
@@ -26,8 +27,20 @@ import { ThemeProvider } from './components/ThemeProvider.js'
|
|
|
26
27
|
import { LocaleProvider } from './components/LocaleProvider.js'
|
|
27
28
|
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts.js'
|
|
28
29
|
import { PageBuilder } from './views/page-builder/PageBuilder.js'
|
|
30
|
+
import { PageSectionEditor } from './views/page-editor/PageSectionEditor.js'
|
|
31
|
+
import { PagePreview } from './views/page-editor/PagePreview.js'
|
|
29
32
|
import { SavedSections } from './views/page-builder/SavedSections.js'
|
|
30
33
|
import { PageTemplates } from './views/page-builder/PageTemplates.js'
|
|
34
|
+
import { Widgets } from './views/page-builder/Widgets.js'
|
|
35
|
+
import { PagesListView } from './views/Pages/PagesListView.js'
|
|
36
|
+
import { PageTagManager } from './views/Pages/PageTagManager.js'
|
|
37
|
+
import { PostsListView } from './views/Posts/PostsListView.js'
|
|
38
|
+
import { PostTypesView } from './views/Posts/PostTypesView.js'
|
|
39
|
+
import { NewPostPicker } from './views/Posts/NewPostPicker.js'
|
|
40
|
+
import { NewPostTypeInstructions } from './views/Posts/NewPostTypeInstructions.js'
|
|
41
|
+
import { PostSectionEditor } from './views/post-editor/PostSectionEditor.js'
|
|
42
|
+
import { PostTemplateEditor } from './views/post-editor/PostTemplateEditor.js'
|
|
43
|
+
import { PostPreview } from './views/post-editor/PostPreview.js'
|
|
31
44
|
|
|
32
45
|
export interface AdminRootProps {
|
|
33
46
|
config: any
|
|
@@ -58,9 +71,45 @@ function AdminShell({
|
|
|
58
71
|
onLogin,
|
|
59
72
|
captchaConfig,
|
|
60
73
|
}: AdminRootProps) {
|
|
61
|
-
const { currentPath, navigate, matchRoute } = useAdminRouter(basePath, initialPath)
|
|
74
|
+
const { currentPath, navigate, matchRoute, buildHref } = useAdminRouter(basePath, initialPath)
|
|
62
75
|
const [shortcutHelpOpen, setShortcutHelpOpen] = useState(false)
|
|
63
76
|
|
|
77
|
+
// Open a preview route in a new browser tab so it behaves like a normal,
|
|
78
|
+
// shareable page (its own URL, no admin chrome — see the standalone branch
|
|
79
|
+
// below). The blank tab is opened synchronously inside the click gesture so
|
|
80
|
+
// popup blockers permit it; `prepare` (e.g. saving an unsaved draft) runs
|
|
81
|
+
// before we point the tab at the URL, guaranteeing the preview reflects the
|
|
82
|
+
// latest saved content. If `prepare` rejects, the tab is closed.
|
|
83
|
+
const openPreview = useCallback(
|
|
84
|
+
(path: string, prepare?: () => Promise<boolean | void> | boolean | void) => {
|
|
85
|
+
if (typeof window === 'undefined') return
|
|
86
|
+
const tab = window.open('about:blank', '_blank')
|
|
87
|
+
if (!tab) {
|
|
88
|
+
toast.error('Enable pop-ups for this site to open the preview in a new tab.')
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
const go = (ok: boolean | void) => {
|
|
92
|
+
if (ok === false) {
|
|
93
|
+
tab?.close()
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
if (tab && !tab.closed) {
|
|
97
|
+
// Sever the opener link for safety, then load the real URL.
|
|
98
|
+
try {
|
|
99
|
+
tab.opener = null
|
|
100
|
+
} catch {
|
|
101
|
+
/* cross-origin guard — never throws for same-origin admin */
|
|
102
|
+
}
|
|
103
|
+
tab.location.replace(buildHref(path))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
Promise.resolve(prepare?.())
|
|
107
|
+
.then(go)
|
|
108
|
+
.catch(() => tab?.close())
|
|
109
|
+
},
|
|
110
|
+
[buildHref],
|
|
111
|
+
)
|
|
112
|
+
|
|
64
113
|
useBranding(config)
|
|
65
114
|
useAdminPageTitle(config, currentPath)
|
|
66
115
|
|
|
@@ -178,6 +227,143 @@ function AdminShell({
|
|
|
178
227
|
return <MediaBrowser onNavigate={navigate} />
|
|
179
228
|
}
|
|
180
229
|
|
|
230
|
+
// ─── Posts area ────────────────────────────────────────────────
|
|
231
|
+
// These take priority over the per-collection `/<slug>` matcher
|
|
232
|
+
// below so a consumer-defined `posts` collection can't accidentally
|
|
233
|
+
// shadow the aggregate Posts view.
|
|
234
|
+
if (matchRoute('/posts/types/new')) {
|
|
235
|
+
return <NewPostTypeInstructions onNavigate={navigate} />
|
|
236
|
+
}
|
|
237
|
+
// Per-type template editor — must match before `/posts/types/:slug/edit`
|
|
238
|
+
// is irrelevant (distinct last segment) but stays grouped with the type
|
|
239
|
+
// routes so it can't be shadowed by the generic `/posts/:type/*` block.
|
|
240
|
+
const postTypeTemplate = matchRoute('/posts/types/:slug/template')
|
|
241
|
+
if (postTypeTemplate?.slug) {
|
|
242
|
+
return (
|
|
243
|
+
<PostTemplateEditor
|
|
244
|
+
postType={postTypeTemplate.slug}
|
|
245
|
+
config={config}
|
|
246
|
+
session={session}
|
|
247
|
+
onNavigate={navigate}
|
|
248
|
+
/>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
const postTypeEdit = matchRoute('/posts/types/:slug/edit')
|
|
252
|
+
if (postTypeEdit?.slug) {
|
|
253
|
+
// V1 falls through to the per-collection list view; future
|
|
254
|
+
// PR introduces a dedicated PostTypeEditor with field-level
|
|
255
|
+
// metadata. The list view is the natural per-type management
|
|
256
|
+
// surface today (it already shows posts in that type).
|
|
257
|
+
return (
|
|
258
|
+
<CollectionList collectionSlug={postTypeEdit.slug} config={config} onNavigate={navigate} />
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
if (matchRoute('/posts/types')) {
|
|
262
|
+
return <PostTypesView onNavigate={navigate} />
|
|
263
|
+
}
|
|
264
|
+
if (matchRoute('/posts/new')) {
|
|
265
|
+
return <NewPostPicker onNavigate={navigate} />
|
|
266
|
+
}
|
|
267
|
+
if (matchRoute('/posts')) {
|
|
268
|
+
return <PostsListView onNavigate={navigate} />
|
|
269
|
+
}
|
|
270
|
+
// Section-based Post editor. These `/posts/:type/*` patterns are tested
|
|
271
|
+
// AFTER every `/posts/types/*` route above so `:type` cannot swallow the
|
|
272
|
+
// reserved "types" segment.
|
|
273
|
+
const postNew = matchRoute('/posts/:type/new')
|
|
274
|
+
if (postNew?.type) {
|
|
275
|
+
return (
|
|
276
|
+
<PostSectionEditor
|
|
277
|
+
postType={postNew.type}
|
|
278
|
+
config={config}
|
|
279
|
+
session={session}
|
|
280
|
+
onNavigate={navigate}
|
|
281
|
+
onOpenPreview={openPreview}
|
|
282
|
+
/>
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
const postPreview = matchRoute('/posts/:type/:id/preview')
|
|
286
|
+
if (postPreview?.type && postPreview?.id) {
|
|
287
|
+
return (
|
|
288
|
+
<PostPreview
|
|
289
|
+
postType={postPreview.type}
|
|
290
|
+
documentId={postPreview.id}
|
|
291
|
+
session={session}
|
|
292
|
+
onNavigate={navigate}
|
|
293
|
+
/>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
const postEdit = matchRoute('/posts/:type/:id/edit')
|
|
297
|
+
if (postEdit?.type && postEdit?.id) {
|
|
298
|
+
return (
|
|
299
|
+
<PostSectionEditor
|
|
300
|
+
postType={postEdit.type}
|
|
301
|
+
documentId={postEdit.id}
|
|
302
|
+
config={config}
|
|
303
|
+
session={session}
|
|
304
|
+
onNavigate={navigate}
|
|
305
|
+
onOpenPreview={openPreview}
|
|
306
|
+
/>
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ─── Pages area ────────────────────────────────────────────────
|
|
311
|
+
// Ordering matters: tag routes and /pages/new must be tested before
|
|
312
|
+
// the `/pages/:id` matcher, otherwise `:id` swallows "tags"/"new".
|
|
313
|
+
if (matchRoute('/pages/tags/new')) {
|
|
314
|
+
return <PageTagManager onNavigate={navigate} startCreating />
|
|
315
|
+
}
|
|
316
|
+
const pageTagEdit = matchRoute('/pages/tags/:id/edit')
|
|
317
|
+
if (pageTagEdit?.id) {
|
|
318
|
+
// The manager is a single CRUD surface; deep-linking to a tag just
|
|
319
|
+
// opens the manager (inline edit lives per-row).
|
|
320
|
+
return <PageTagManager onNavigate={navigate} />
|
|
321
|
+
}
|
|
322
|
+
if (matchRoute('/pages/tags')) {
|
|
323
|
+
return <PageTagManager onNavigate={navigate} />
|
|
324
|
+
}
|
|
325
|
+
if (matchRoute('/pages/new')) {
|
|
326
|
+
return (
|
|
327
|
+
<PageSectionEditor
|
|
328
|
+
config={config}
|
|
329
|
+
session={session}
|
|
330
|
+
onNavigate={navigate}
|
|
331
|
+
onOpenPreview={openPreview}
|
|
332
|
+
/>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
const pagePreview = matchRoute('/pages/:id/preview')
|
|
336
|
+
if (pagePreview?.id) {
|
|
337
|
+
return <PagePreview documentId={pagePreview.id} onNavigate={navigate} />
|
|
338
|
+
}
|
|
339
|
+
const pageEdit = matchRoute('/pages/:id/edit')
|
|
340
|
+
if (pageEdit?.id) {
|
|
341
|
+
return (
|
|
342
|
+
<PageSectionEditor
|
|
343
|
+
documentId={pageEdit.id}
|
|
344
|
+
config={config}
|
|
345
|
+
session={session}
|
|
346
|
+
onNavigate={navigate}
|
|
347
|
+
onOpenPreview={openPreview}
|
|
348
|
+
/>
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
const pageEditBare = matchRoute('/pages/:id')
|
|
352
|
+
if (pageEditBare?.id) {
|
|
353
|
+
return (
|
|
354
|
+
<PageSectionEditor
|
|
355
|
+
documentId={pageEditBare.id}
|
|
356
|
+
config={config}
|
|
357
|
+
session={session}
|
|
358
|
+
onNavigate={navigate}
|
|
359
|
+
onOpenPreview={openPreview}
|
|
360
|
+
/>
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
if (matchRoute('/pages')) {
|
|
364
|
+
return <PagesListView onNavigate={navigate} />
|
|
365
|
+
}
|
|
366
|
+
|
|
181
367
|
if (matchRoute('/forms/new')) {
|
|
182
368
|
return <FormEditor onNavigate={navigate} />
|
|
183
369
|
}
|
|
@@ -225,6 +411,10 @@ function AdminShell({
|
|
|
225
411
|
return <PageTemplates onNavigate={navigate} />
|
|
226
412
|
}
|
|
227
413
|
|
|
414
|
+
if (matchRoute('/widgets')) {
|
|
415
|
+
return <Widgets onNavigate={navigate} />
|
|
416
|
+
}
|
|
417
|
+
|
|
228
418
|
if (matchRoute('/users')) {
|
|
229
419
|
return <Users onNavigate={navigate} />
|
|
230
420
|
}
|
|
@@ -264,10 +454,21 @@ function AdminShell({
|
|
|
264
454
|
'script-tags',
|
|
265
455
|
'saved-sections',
|
|
266
456
|
'page-templates',
|
|
457
|
+
'widgets',
|
|
267
458
|
'users',
|
|
268
459
|
'settings',
|
|
269
460
|
'page-builder',
|
|
270
461
|
'collections',
|
|
462
|
+
// `pages` is reserved so the Pages list/editor/tag routes above
|
|
463
|
+
// own the `/pages/*` namespace and can't be shadowed by a
|
|
464
|
+
// consumer-defined `pages` collection. The per-collection list is
|
|
465
|
+
// still reachable via `/collections/pages`.
|
|
466
|
+
'pages',
|
|
467
|
+
// `posts` is reserved so the aggregate Posts area is reachable
|
|
468
|
+
// even if a consumer defines a collection literally called
|
|
469
|
+
// `posts`. The per-type collection list is still available via
|
|
470
|
+
// `/collections/posts`.
|
|
471
|
+
'posts',
|
|
271
472
|
])
|
|
272
473
|
|
|
273
474
|
for (const [slug, def] of collectionMap) {
|
|
@@ -354,6 +555,17 @@ function AdminShell({
|
|
|
354
555
|
)
|
|
355
556
|
}
|
|
356
557
|
|
|
558
|
+
// Preview routes render as standalone pages (no sidebar/header) so a
|
|
559
|
+
// new-tab preview looks like the real published page rather than an
|
|
560
|
+
// embedded admin view. Everything else gets the full admin chrome.
|
|
561
|
+
if (currentPath.endsWith('/preview')) {
|
|
562
|
+
return (
|
|
563
|
+
<div className="bg-background h-full min-h-screen">
|
|
564
|
+
<ErrorBoundary>{renderView()}</ErrorBoundary>
|
|
565
|
+
</div>
|
|
566
|
+
)
|
|
567
|
+
}
|
|
568
|
+
|
|
357
569
|
return (
|
|
358
570
|
<Layout config={config} session={session} currentPath={currentPath} onNavigate={navigate}>
|
|
359
571
|
<ErrorBoundary>{renderView()}</ErrorBoundary>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { render, screen } from '@testing-library/react'
|
|
4
|
+
import {
|
|
5
|
+
PostStatusBadge,
|
|
6
|
+
PostTypeBadge,
|
|
7
|
+
SeoScoreIndicator,
|
|
8
|
+
seoScorePalette,
|
|
9
|
+
paletteFor,
|
|
10
|
+
} from '../../components/posts/badges.js'
|
|
11
|
+
|
|
12
|
+
describe('seoScorePalette', () => {
|
|
13
|
+
it('returns gray for null', () => {
|
|
14
|
+
expect(seoScorePalette(null)).toBe('gray')
|
|
15
|
+
})
|
|
16
|
+
it.each([
|
|
17
|
+
[80, 'green'],
|
|
18
|
+
[100, 'green'],
|
|
19
|
+
[79, 'orange'],
|
|
20
|
+
[60, 'orange'],
|
|
21
|
+
[59, 'red'],
|
|
22
|
+
[1, 'red'],
|
|
23
|
+
[0, 'gray'],
|
|
24
|
+
])('score %i → %s', (input, expected) => {
|
|
25
|
+
expect(seoScorePalette(input)).toBe(expected)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('paletteFor', () => {
|
|
30
|
+
it('falls back to gray for unknown colors', () => {
|
|
31
|
+
const p = paletteFor('chartreuse')
|
|
32
|
+
expect(p.bg).toContain('muted')
|
|
33
|
+
})
|
|
34
|
+
it('returns gray for null', () => {
|
|
35
|
+
const p = paletteFor(null)
|
|
36
|
+
expect(p.bg).toContain('muted')
|
|
37
|
+
})
|
|
38
|
+
it('returns purple tokens for "purple"', () => {
|
|
39
|
+
const p = paletteFor('purple')
|
|
40
|
+
expect(p.bg).toMatch(/violet/)
|
|
41
|
+
expect(p.border).toMatch(/violet/)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('SeoScoreIndicator', () => {
|
|
46
|
+
it('renders a dash for missing score', () => {
|
|
47
|
+
render(<SeoScoreIndicator score={null} />)
|
|
48
|
+
expect(screen.getByText('—')).toBeTruthy()
|
|
49
|
+
})
|
|
50
|
+
it('renders the numeric score when present', () => {
|
|
51
|
+
render(<SeoScoreIndicator score={88} />)
|
|
52
|
+
expect(screen.getByText('88')).toBeTruthy()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('PostStatusBadge', () => {
|
|
57
|
+
it('renders the human label for each status', () => {
|
|
58
|
+
render(<PostStatusBadge status="PUBLISHED" />)
|
|
59
|
+
expect(screen.getByText('Published')).toBeTruthy()
|
|
60
|
+
})
|
|
61
|
+
it('uses an aria-label so screen readers get a clean Status: foo announcement', () => {
|
|
62
|
+
const { container } = render(<PostStatusBadge status="SCHEDULED" />)
|
|
63
|
+
expect(container.querySelector('[aria-label="Status: Scheduled"]')).toBeTruthy()
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('PostTypeBadge', () => {
|
|
68
|
+
it('renders as a static span when no onClick is provided', () => {
|
|
69
|
+
const { container } = render(<PostTypeBadge label="Blog" color="purple" />)
|
|
70
|
+
expect(container.querySelector('span')?.textContent).toBe('Blog')
|
|
71
|
+
expect(container.querySelector('button')).toBeNull()
|
|
72
|
+
})
|
|
73
|
+
it('renders as a button when onClick is provided', () => {
|
|
74
|
+
render(<PostTypeBadge label="Blog" color="purple" onClick={() => {}} />)
|
|
75
|
+
expect(screen.getByRole('button', { name: 'Blog' })).toBeTruthy()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { buildNavItems } from '../../layout/Sidebar.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Regression coverage for the Posts area sidebar grouping.
|
|
6
|
+
*
|
|
7
|
+
* The reference design shows a single "Posts" parent with two fixed
|
|
8
|
+
* children — All Posts and Post Types — even when the config defines
|
|
9
|
+
* multiple post-type collections (Blog, Case Studies, News, ...). That
|
|
10
|
+
* collapsing is fragile (it's tempting to enumerate each collection
|
|
11
|
+
* as its own sidebar entry) so we lock the contract with explicit
|
|
12
|
+
* tests.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
describe('buildNavItems', () => {
|
|
16
|
+
it('returns the default tree when no collections are provided', () => {
|
|
17
|
+
const items = buildNavItems({})
|
|
18
|
+
const posts = items.find((i) => i.path === '/posts')
|
|
19
|
+
expect(posts).toBeDefined()
|
|
20
|
+
expect(posts?.children?.map((c) => c.path)).toEqual(['/posts', '/posts/types'])
|
|
21
|
+
// Pages no longer carries children — Saved Sections / Templates moved
|
|
22
|
+
// under the dedicated Templates parent.
|
|
23
|
+
const pages = items.find((i) => i.path === '/pages')
|
|
24
|
+
expect(pages?.children ?? []).toEqual([])
|
|
25
|
+
// Templates is its own item with Saved Sections + Widgets.
|
|
26
|
+
const templates = items.find((i) => i.path === '/page-templates')
|
|
27
|
+
expect(templates?.children?.map((c) => c.path)).toEqual(['/saved-sections', '/widgets'])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('exposes Templates as a top-level Content item with Saved Sections + Widgets', () => {
|
|
31
|
+
const items = buildNavItems({
|
|
32
|
+
collections: {
|
|
33
|
+
pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, type: 'page' },
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
const templates = items.find((i) => i.path === '/page-templates')
|
|
37
|
+
expect(templates).toBeDefined()
|
|
38
|
+
expect(templates?.group).toBe('Content')
|
|
39
|
+
expect(templates?.children?.map((c) => c.label)).toEqual(['Saved Sections', 'Widgets'])
|
|
40
|
+
// Templates is NOT nested under Pages.
|
|
41
|
+
const pages = items.find((i) => i.path === '/pages')
|
|
42
|
+
expect(pages?.children ?? []).toEqual([])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('collapses every type:post collection under a single Posts parent', () => {
|
|
46
|
+
const items = buildNavItems({
|
|
47
|
+
collections: {
|
|
48
|
+
blog: { slug: 'blog', labels: { singular: 'Blog Post', plural: 'Blog' }, type: 'post' },
|
|
49
|
+
'case-studies': {
|
|
50
|
+
slug: 'case-studies',
|
|
51
|
+
labels: { singular: 'Case Study', plural: 'Case Studies' },
|
|
52
|
+
type: 'post',
|
|
53
|
+
},
|
|
54
|
+
news: { slug: 'news', labels: { singular: 'News Item', plural: 'News' }, type: 'post' },
|
|
55
|
+
pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, type: 'page' },
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Exactly one /posts entry, regardless of how many post types exist.
|
|
60
|
+
const postEntries = items.filter((i) => i.path === '/posts')
|
|
61
|
+
expect(postEntries).toHaveLength(1)
|
|
62
|
+
|
|
63
|
+
// Children are still the fixed pair — the per-type collections
|
|
64
|
+
// are NOT enumerated as sidebar siblings.
|
|
65
|
+
const children = postEntries[0]?.children?.map((c) => c.path)
|
|
66
|
+
expect(children).toEqual(['/posts', '/posts/types'])
|
|
67
|
+
|
|
68
|
+
// No per-type slug ended up as a top-level sidebar item.
|
|
69
|
+
const slugPaths = items.map((i) => i.path)
|
|
70
|
+
for (const slug of ['blog', 'case-studies', 'news']) {
|
|
71
|
+
expect(slugPaths).not.toContain(`/${slug}`)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('treats admin.group=Posts (case-insensitive) as a synonym for type=post', () => {
|
|
76
|
+
const items = buildNavItems({
|
|
77
|
+
collections: {
|
|
78
|
+
reviews: {
|
|
79
|
+
slug: 'reviews',
|
|
80
|
+
labels: { singular: 'Review', plural: 'Reviews' },
|
|
81
|
+
// Intentionally NOT type:'post' — relying on the group marker.
|
|
82
|
+
admin: { group: 'posts' },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
expect(items.some((i) => i.path === '/posts')).toBe(true)
|
|
87
|
+
expect(items.some((i) => i.path === '/reviews')).toBe(false)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('omits the Posts parent entirely when no post-type collections exist', () => {
|
|
91
|
+
const items = buildNavItems({
|
|
92
|
+
collections: {
|
|
93
|
+
pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, type: 'page' },
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
expect(items.some((i) => i.path === '/posts')).toBe(false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('places Pages above Posts, both under the CONTENT group', () => {
|
|
100
|
+
const items = buildNavItems({
|
|
101
|
+
collections: {
|
|
102
|
+
blog: { slug: 'blog', labels: { singular: 'Blog Post', plural: 'Blog' }, type: 'post' },
|
|
103
|
+
pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, type: 'page' },
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const pagesIdx = items.findIndex((i) => i.path === '/pages')
|
|
108
|
+
const postsIdx = items.findIndex((i) => i.path === '/posts')
|
|
109
|
+
const mediaIdx = items.findIndex((i) => i.path === '/media')
|
|
110
|
+
|
|
111
|
+
// Pages comes before Posts, and both come before the static
|
|
112
|
+
// content destinations.
|
|
113
|
+
expect(pagesIdx).toBeGreaterThanOrEqual(0)
|
|
114
|
+
expect(pagesIdx).toBeLessThan(postsIdx)
|
|
115
|
+
expect(postsIdx).toBeLessThan(mediaIdx)
|
|
116
|
+
|
|
117
|
+
// Both are tagged Content so the single CONTENT header sits above
|
|
118
|
+
// Pages (the first Content item in the tree).
|
|
119
|
+
expect(items[pagesIdx]?.group).toBe('Content')
|
|
120
|
+
expect(items[postsIdx]?.group).toBe('Content')
|
|
121
|
+
|
|
122
|
+
// The first item carrying group 'Content' is Pages — i.e. the
|
|
123
|
+
// CONTENT header renders directly above it.
|
|
124
|
+
const firstContent = items.find((i) => i.group === 'Content')
|
|
125
|
+
expect(firstContent?.path).toBe('/pages')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('defaults: Pages is the first CONTENT item, above Posts', () => {
|
|
129
|
+
const items = buildNavItems({})
|
|
130
|
+
const pagesIdx = items.findIndex((i) => i.path === '/pages')
|
|
131
|
+
const postsIdx = items.findIndex((i) => i.path === '/posts')
|
|
132
|
+
expect(items[pagesIdx]?.group).toBe('Content')
|
|
133
|
+
expect(items[postsIdx]?.group).toBe('Content')
|
|
134
|
+
expect(pagesIdx).toBeLessThan(postsIdx)
|
|
135
|
+
expect(items.find((i) => i.group === 'Content')?.path).toBe('/pages')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('renders an admin.group=Content collection (Menus) as its own Content item', () => {
|
|
139
|
+
const items = buildNavItems({
|
|
140
|
+
collections: {
|
|
141
|
+
pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, type: 'page' },
|
|
142
|
+
navigations: {
|
|
143
|
+
slug: 'navigations',
|
|
144
|
+
labels: { singular: 'Menu', plural: 'Menus' },
|
|
145
|
+
admin: { group: 'Content', icon: 'navigation' },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const menus = items.find((i) => i.path === '/navigations')
|
|
151
|
+
expect(menus).toBeDefined()
|
|
152
|
+
expect(menus?.label).toBe('Menus')
|
|
153
|
+
expect(menus?.group).toBe('Content')
|
|
154
|
+
// It is a flat Content entry, not a child of Posts/Templates/Pages.
|
|
155
|
+
expect(menus?.children).toBeUndefined()
|
|
156
|
+
// Lands after the static Content destinations, before Settings.
|
|
157
|
+
const menusIdx = items.findIndex((i) => i.path === '/navigations')
|
|
158
|
+
const scriptIdx = items.findIndex((i) => i.path === '/script-tags')
|
|
159
|
+
const usersIdx = items.findIndex((i) => i.path === '/users')
|
|
160
|
+
expect(menusIdx).toBeGreaterThan(scriptIdx)
|
|
161
|
+
expect(menusIdx).toBeLessThan(usersIdx)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('keeps standard Content/Settings entries after collections', () => {
|
|
165
|
+
const items = buildNavItems({
|
|
166
|
+
collections: {
|
|
167
|
+
blog: { slug: 'blog', labels: { singular: 'Blog Post', plural: 'Blog' }, type: 'post' },
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
const paths = items.map((i) => i.path)
|
|
171
|
+
expect(paths).toContain('/media')
|
|
172
|
+
expect(paths).toContain('/forms')
|
|
173
|
+
expect(paths).toContain('/seo')
|
|
174
|
+
expect(paths).toContain('/script-tags')
|
|
175
|
+
expect(paths).toContain('/users')
|
|
176
|
+
expect(paths).toContain('/api-keys')
|
|
177
|
+
expect(paths).toContain('/settings')
|
|
178
|
+
})
|
|
179
|
+
})
|