@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.
Files changed (308) hide show
  1. package/dist/AdminRoot.d.ts.map +1 -1
  2. package/dist/AdminRoot.js +148 -2
  3. package/dist/AdminRoot.js.map +1 -1
  4. package/dist/__tests__/components/posts-badges.test.d.ts +2 -0
  5. package/dist/__tests__/components/posts-badges.test.d.ts.map +1 -0
  6. package/dist/__tests__/components/posts-badges.test.js +68 -0
  7. package/dist/__tests__/components/posts-badges.test.js.map +1 -0
  8. package/dist/__tests__/layout/sidebar-posts.test.d.ts +2 -0
  9. package/dist/__tests__/layout/sidebar-posts.test.d.ts.map +1 -0
  10. package/dist/__tests__/layout/sidebar-posts.test.js +162 -0
  11. package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -0
  12. package/dist/__tests__/lib/page-editor-service.test.d.ts +2 -0
  13. package/dist/__tests__/lib/page-editor-service.test.d.ts.map +1 -0
  14. package/dist/__tests__/lib/page-editor-service.test.js +137 -0
  15. package/dist/__tests__/lib/page-editor-service.test.js.map +1 -0
  16. package/dist/__tests__/lib/pages-service.test.d.ts +2 -0
  17. package/dist/__tests__/lib/pages-service.test.d.ts.map +1 -0
  18. package/dist/__tests__/lib/pages-service.test.js +140 -0
  19. package/dist/__tests__/lib/pages-service.test.js.map +1 -0
  20. package/dist/__tests__/lib/post-editor-service.test.d.ts +2 -0
  21. package/dist/__tests__/lib/post-editor-service.test.d.ts.map +1 -0
  22. package/dist/__tests__/lib/post-editor-service.test.js +185 -0
  23. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -0
  24. package/dist/__tests__/lib/posts-service.test.d.ts +2 -0
  25. package/dist/__tests__/lib/posts-service.test.d.ts.map +1 -0
  26. package/dist/__tests__/lib/posts-service.test.js +244 -0
  27. package/dist/__tests__/lib/posts-service.test.js.map +1 -0
  28. package/dist/__tests__/lib/useApiData.test.d.ts +2 -0
  29. package/dist/__tests__/lib/useApiData.test.d.ts.map +1 -0
  30. package/dist/__tests__/lib/useApiData.test.js +84 -0
  31. package/dist/__tests__/lib/useApiData.test.js.map +1 -0
  32. package/dist/__tests__/views/dashboard.test.d.ts +2 -0
  33. package/dist/__tests__/views/dashboard.test.d.ts.map +1 -0
  34. package/dist/__tests__/views/dashboard.test.js +138 -0
  35. package/dist/__tests__/views/dashboard.test.js.map +1 -0
  36. package/dist/__tests__/views/posts-list-view.test.d.ts +2 -0
  37. package/dist/__tests__/views/posts-list-view.test.d.ts.map +1 -0
  38. package/dist/__tests__/views/posts-list-view.test.js +98 -0
  39. package/dist/__tests__/views/posts-list-view.test.js.map +1 -0
  40. package/dist/actuate-admin.css +1 -1
  41. package/dist/components/pages/tag-badges.d.ts +21 -0
  42. package/dist/components/pages/tag-badges.d.ts.map +1 -0
  43. package/dist/components/pages/tag-badges.js +56 -0
  44. package/dist/components/pages/tag-badges.js.map +1 -0
  45. package/dist/components/posts/badges.d.ts +25 -0
  46. package/dist/components/posts/badges.d.ts.map +1 -0
  47. package/dist/components/posts/badges.js +123 -0
  48. package/dist/components/posts/badges.js.map +1 -0
  49. package/dist/components/posts/icons.d.ts +12 -0
  50. package/dist/components/posts/icons.d.ts.map +1 -0
  51. package/dist/components/posts/icons.js +26 -0
  52. package/dist/components/posts/icons.js.map +1 -0
  53. package/dist/layout/Layout.js +36 -8
  54. package/dist/layout/Layout.js.map +1 -1
  55. package/dist/layout/Sidebar.d.ts +9 -0
  56. package/dist/layout/Sidebar.d.ts.map +1 -1
  57. package/dist/layout/Sidebar.js +100 -48
  58. package/dist/layout/Sidebar.js.map +1 -1
  59. package/dist/lib/page-editor-service.d.ts +45 -0
  60. package/dist/lib/page-editor-service.d.ts.map +1 -0
  61. package/dist/lib/page-editor-service.js +153 -0
  62. package/dist/lib/page-editor-service.js.map +1 -0
  63. package/dist/lib/pages-service.d.ts +143 -0
  64. package/dist/lib/pages-service.d.ts.map +1 -0
  65. package/dist/lib/pages-service.js +444 -0
  66. package/dist/lib/pages-service.js.map +1 -0
  67. package/dist/lib/post-editor-service.d.ts +57 -0
  68. package/dist/lib/post-editor-service.d.ts.map +1 -0
  69. package/dist/lib/post-editor-service.js +234 -0
  70. package/dist/lib/post-editor-service.js.map +1 -0
  71. package/dist/lib/posts-service.d.ts +73 -0
  72. package/dist/lib/posts-service.d.ts.map +1 -0
  73. package/dist/lib/posts-service.js +245 -0
  74. package/dist/lib/posts-service.js.map +1 -0
  75. package/dist/lib/preview-link.d.ts +32 -0
  76. package/dist/lib/preview-link.d.ts.map +1 -0
  77. package/dist/lib/preview-link.js +40 -0
  78. package/dist/lib/preview-link.js.map +1 -0
  79. package/dist/router/index.d.ts +1 -0
  80. package/dist/router/index.d.ts.map +1 -1
  81. package/dist/router/index.js +9 -1
  82. package/dist/router/index.js.map +1 -1
  83. package/dist/views/Dashboard.d.ts.map +1 -1
  84. package/dist/views/Dashboard.js +64 -20
  85. package/dist/views/Dashboard.js.map +1 -1
  86. package/dist/views/Pages/PageTagManager.d.ts +14 -0
  87. package/dist/views/Pages/PageTagManager.d.ts.map +1 -0
  88. package/dist/views/Pages/PageTagManager.js +145 -0
  89. package/dist/views/Pages/PageTagManager.js.map +1 -0
  90. package/dist/views/Pages/PagesListView.d.ts +18 -0
  91. package/dist/views/Pages/PagesListView.d.ts.map +1 -0
  92. package/dist/views/Pages/PagesListView.js +287 -0
  93. package/dist/views/Pages/PagesListView.js.map +1 -0
  94. package/dist/views/Posts/NewPostPicker.d.ts +16 -0
  95. package/dist/views/Posts/NewPostPicker.d.ts.map +1 -0
  96. package/dist/views/Posts/NewPostPicker.js +59 -0
  97. package/dist/views/Posts/NewPostPicker.js.map +1 -0
  98. package/dist/views/Posts/NewPostTypeInstructions.d.ts +18 -0
  99. package/dist/views/Posts/NewPostTypeInstructions.d.ts.map +1 -0
  100. package/dist/views/Posts/NewPostTypeInstructions.js +147 -0
  101. package/dist/views/Posts/NewPostTypeInstructions.js.map +1 -0
  102. package/dist/views/Posts/PostTypesView.d.ts +26 -0
  103. package/dist/views/Posts/PostTypesView.d.ts.map +1 -0
  104. package/dist/views/Posts/PostTypesView.js +52 -0
  105. package/dist/views/Posts/PostTypesView.js.map +1 -0
  106. package/dist/views/Posts/PostsListView.d.ts +25 -0
  107. package/dist/views/Posts/PostsListView.d.ts.map +1 -0
  108. package/dist/views/Posts/PostsListView.js +239 -0
  109. package/dist/views/Posts/PostsListView.js.map +1 -0
  110. package/dist/views/Posts/post-type-presets.d.ts +32 -0
  111. package/dist/views/Posts/post-type-presets.d.ts.map +1 -0
  112. package/dist/views/Posts/post-type-presets.js +72 -0
  113. package/dist/views/Posts/post-type-presets.js.map +1 -0
  114. package/dist/views/page-builder/Widgets.d.ts +13 -0
  115. package/dist/views/page-builder/Widgets.d.ts.map +1 -0
  116. package/dist/views/page-builder/Widgets.js +15 -0
  117. package/dist/views/page-builder/Widgets.js.map +1 -0
  118. package/dist/views/page-editor/AddSectionModal.d.ts +20 -0
  119. package/dist/views/page-editor/AddSectionModal.d.ts.map +1 -0
  120. package/dist/views/page-editor/AddSectionModal.js +22 -0
  121. package/dist/views/page-editor/AddSectionModal.js.map +1 -0
  122. package/dist/views/page-editor/EditorCanvas.d.ts +18 -0
  123. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -0
  124. package/dist/views/page-editor/EditorCanvas.js +64 -0
  125. package/dist/views/page-editor/EditorCanvas.js.map +1 -0
  126. package/dist/views/page-editor/EditorTopBar.d.ts +30 -0
  127. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -0
  128. package/dist/views/page-editor/EditorTopBar.js +42 -0
  129. package/dist/views/page-editor/EditorTopBar.js.map +1 -0
  130. package/dist/views/page-editor/PagePreview.d.ts +13 -0
  131. package/dist/views/page-editor/PagePreview.d.ts.map +1 -0
  132. package/dist/views/page-editor/PagePreview.js +37 -0
  133. package/dist/views/page-editor/PagePreview.js.map +1 -0
  134. package/dist/views/page-editor/PageSectionEditor.d.ts +16 -0
  135. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -0
  136. package/dist/views/page-editor/PageSectionEditor.js +272 -0
  137. package/dist/views/page-editor/PageSectionEditor.js.map +1 -0
  138. package/dist/views/page-editor/PageSettingsModal.d.ts +12 -0
  139. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -0
  140. package/dist/views/page-editor/PageSettingsModal.js +52 -0
  141. package/dist/views/page-editor/PageSettingsModal.js.map +1 -0
  142. package/dist/views/page-editor/SectionInspector.d.ts +17 -0
  143. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -0
  144. package/dist/views/page-editor/SectionInspector.js +98 -0
  145. package/dist/views/page-editor/SectionInspector.js.map +1 -0
  146. package/dist/views/page-editor/SectionsPanel.d.ts +15 -0
  147. package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -0
  148. package/dist/views/page-editor/SectionsPanel.js +43 -0
  149. package/dist/views/page-editor/SectionsPanel.js.map +1 -0
  150. package/dist/views/page-editor/ValidationSummary.d.ts +9 -0
  151. package/dist/views/page-editor/ValidationSummary.d.ts.map +1 -0
  152. package/dist/views/page-editor/ValidationSummary.js +10 -0
  153. package/dist/views/page-editor/ValidationSummary.js.map +1 -0
  154. package/dist/views/page-editor/section-icons.d.ts +4 -0
  155. package/dist/views/page-editor/section-icons.d.ts.map +1 -0
  156. package/dist/views/page-editor/section-icons.js +20 -0
  157. package/dist/views/page-editor/section-icons.js.map +1 -0
  158. package/dist/views/page-editor/section-types.d.ts +11 -0
  159. package/dist/views/page-editor/section-types.d.ts.map +1 -0
  160. package/dist/views/page-editor/section-types.js +10 -0
  161. package/dist/views/page-editor/section-types.js.map +1 -0
  162. package/dist/views/page-editor/sections/ArticleBodySection.d.ts +13 -0
  163. package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -0
  164. package/dist/views/page-editor/sections/ArticleBodySection.js +23 -0
  165. package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -0
  166. package/dist/views/page-editor/sections/AuthorBioSection.d.ts +8 -0
  167. package/dist/views/page-editor/sections/AuthorBioSection.d.ts.map +1 -0
  168. package/dist/views/page-editor/sections/AuthorBioSection.js +21 -0
  169. package/dist/views/page-editor/sections/AuthorBioSection.js.map +1 -0
  170. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts +5 -0
  171. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -0
  172. package/dist/views/page-editor/sections/ByTheNumbersSection.js +10 -0
  173. package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -0
  174. package/dist/views/page-editor/sections/CtaBannerSection.d.ts +5 -0
  175. package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -0
  176. package/dist/views/page-editor/sections/CtaBannerSection.js +10 -0
  177. package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -0
  178. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts +5 -0
  179. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -0
  180. package/dist/views/page-editor/sections/FeatureHighlightSection.js +18 -0
  181. package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -0
  182. package/dist/views/page-editor/sections/GallerySection.d.ts +6 -0
  183. package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -0
  184. package/dist/views/page-editor/sections/GallerySection.js +17 -0
  185. package/dist/views/page-editor/sections/GallerySection.js.map +1 -0
  186. package/dist/views/page-editor/sections/HeroBannerSection.d.ts +5 -0
  187. package/dist/views/page-editor/sections/HeroBannerSection.d.ts.map +1 -0
  188. package/dist/views/page-editor/sections/HeroBannerSection.js +11 -0
  189. package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -0
  190. package/dist/views/page-editor/sections/MissionSection.d.ts +6 -0
  191. package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -0
  192. package/dist/views/page-editor/sections/MissionSection.js +12 -0
  193. package/dist/views/page-editor/sections/MissionSection.js.map +1 -0
  194. package/dist/views/page-editor/sections/PullQuoteSection.d.ts +6 -0
  195. package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -0
  196. package/dist/views/page-editor/sections/PullQuoteSection.js +13 -0
  197. package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -0
  198. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts +12 -0
  199. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -0
  200. package/dist/views/page-editor/sections/RelatedPostsSection.js +23 -0
  201. package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -0
  202. package/dist/views/page-editor/sections/SectionRenderer.d.ts +22 -0
  203. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -0
  204. package/dist/views/page-editor/sections/SectionRenderer.js +52 -0
  205. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -0
  206. package/dist/views/page-editor/sections/index.d.ts +14 -0
  207. package/dist/views/page-editor/sections/index.d.ts.map +1 -0
  208. package/dist/views/page-editor/sections/index.js +12 -0
  209. package/dist/views/page-editor/sections/index.js.map +1 -0
  210. package/dist/views/page-editor/sections/parts.d.ts +55 -0
  211. package/dist/views/page-editor/sections/parts.d.ts.map +1 -0
  212. package/dist/views/page-editor/sections/parts.js +60 -0
  213. package/dist/views/page-editor/sections/parts.js.map +1 -0
  214. package/dist/views/page-editor/viewports.d.ts +14 -0
  215. package/dist/views/page-editor/viewports.d.ts.map +1 -0
  216. package/dist/views/page-editor/viewports.js +12 -0
  217. package/dist/views/page-editor/viewports.js.map +1 -0
  218. package/dist/views/post-editor/PostEditorCanvas.d.ts +21 -0
  219. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -0
  220. package/dist/views/post-editor/PostEditorCanvas.js +58 -0
  221. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -0
  222. package/dist/views/post-editor/PostFieldsModal.d.ts +12 -0
  223. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -0
  224. package/dist/views/post-editor/PostFieldsModal.js +53 -0
  225. package/dist/views/post-editor/PostFieldsModal.js.map +1 -0
  226. package/dist/views/post-editor/PostHeader.d.ts +15 -0
  227. package/dist/views/post-editor/PostHeader.d.ts.map +1 -0
  228. package/dist/views/post-editor/PostHeader.js +27 -0
  229. package/dist/views/post-editor/PostHeader.js.map +1 -0
  230. package/dist/views/post-editor/PostHeaderPanel.d.ts +11 -0
  231. package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -0
  232. package/dist/views/post-editor/PostHeaderPanel.js +21 -0
  233. package/dist/views/post-editor/PostHeaderPanel.js.map +1 -0
  234. package/dist/views/post-editor/PostPreview.d.ts +14 -0
  235. package/dist/views/post-editor/PostPreview.d.ts.map +1 -0
  236. package/dist/views/post-editor/PostPreview.js +53 -0
  237. package/dist/views/post-editor/PostPreview.js.map +1 -0
  238. package/dist/views/post-editor/PostSectionEditor.d.ts +17 -0
  239. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -0
  240. package/dist/views/post-editor/PostSectionEditor.js +290 -0
  241. package/dist/views/post-editor/PostSectionEditor.js.map +1 -0
  242. package/dist/views/post-editor/PostTemplateEditor.d.ts +9 -0
  243. package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -0
  244. package/dist/views/post-editor/PostTemplateEditor.js +144 -0
  245. package/dist/views/post-editor/PostTemplateEditor.js.map +1 -0
  246. package/package.json +2 -2
  247. package/src/AdminRoot.tsx +214 -2
  248. package/src/__tests__/components/posts-badges.test.tsx +77 -0
  249. package/src/__tests__/layout/sidebar-posts.test.ts +179 -0
  250. package/src/__tests__/lib/page-editor-service.test.ts +173 -0
  251. package/src/__tests__/lib/pages-service.test.ts +157 -0
  252. package/src/__tests__/lib/post-editor-service.test.ts +215 -0
  253. package/src/__tests__/lib/posts-service.test.ts +279 -0
  254. package/src/__tests__/lib/useApiData.test.ts +105 -0
  255. package/src/__tests__/views/dashboard.test.tsx +180 -0
  256. package/src/__tests__/views/posts-list-view.test.ts +110 -0
  257. package/src/components/pages/tag-badges.tsx +104 -0
  258. package/src/components/posts/badges.tsx +170 -0
  259. package/src/components/posts/icons.ts +40 -0
  260. package/src/layout/Layout.tsx +66 -10
  261. package/src/layout/Sidebar.tsx +116 -50
  262. package/src/lib/page-editor-service.ts +233 -0
  263. package/src/lib/pages-service.ts +599 -0
  264. package/src/lib/post-editor-service.ts +342 -0
  265. package/src/lib/posts-service.ts +366 -0
  266. package/src/lib/preview-link.ts +63 -0
  267. package/src/router/index.ts +10 -1
  268. package/src/views/Dashboard.tsx +142 -89
  269. package/src/views/Pages/PageTagManager.tsx +436 -0
  270. package/src/views/Pages/PagesListView.tsx +944 -0
  271. package/src/views/Posts/NewPostPicker.tsx +147 -0
  272. package/src/views/Posts/NewPostTypeInstructions.tsx +344 -0
  273. package/src/views/Posts/PostTypesView.tsx +194 -0
  274. package/src/views/Posts/PostsListView.tsx +869 -0
  275. package/src/views/Posts/post-type-presets.ts +105 -0
  276. package/src/views/page-builder/Widgets.tsx +44 -0
  277. package/src/views/page-editor/AddSectionModal.tsx +56 -0
  278. package/src/views/page-editor/EditorCanvas.tsx +127 -0
  279. package/src/views/page-editor/EditorTopBar.tsx +233 -0
  280. package/src/views/page-editor/PagePreview.tsx +96 -0
  281. package/src/views/page-editor/PageSectionEditor.tsx +476 -0
  282. package/src/views/page-editor/PageSettingsModal.tsx +154 -0
  283. package/src/views/page-editor/SectionInspector.tsx +599 -0
  284. package/src/views/page-editor/SectionsPanel.tsx +270 -0
  285. package/src/views/page-editor/ValidationSummary.tsx +53 -0
  286. package/src/views/page-editor/section-icons.ts +33 -0
  287. package/src/views/page-editor/section-types.ts +27 -0
  288. package/src/views/page-editor/sections/ArticleBodySection.tsx +57 -0
  289. package/src/views/page-editor/sections/AuthorBioSection.tsx +61 -0
  290. package/src/views/page-editor/sections/ByTheNumbersSection.tsx +41 -0
  291. package/src/views/page-editor/sections/CtaBannerSection.tsx +29 -0
  292. package/src/views/page-editor/sections/FeatureHighlightSection.tsx +63 -0
  293. package/src/views/page-editor/sections/GallerySection.tsx +54 -0
  294. package/src/views/page-editor/sections/HeroBannerSection.tsx +44 -0
  295. package/src/views/page-editor/sections/MissionSection.tsx +22 -0
  296. package/src/views/page-editor/sections/PullQuoteSection.tsx +27 -0
  297. package/src/views/page-editor/sections/RelatedPostsSection.tsx +55 -0
  298. package/src/views/page-editor/sections/SectionRenderer.tsx +102 -0
  299. package/src/views/page-editor/sections/index.ts +13 -0
  300. package/src/views/page-editor/sections/parts.tsx +113 -0
  301. package/src/views/page-editor/viewports.ts +25 -0
  302. package/src/views/post-editor/PostEditorCanvas.tsx +123 -0
  303. package/src/views/post-editor/PostFieldsModal.tsx +196 -0
  304. package/src/views/post-editor/PostHeader.tsx +87 -0
  305. package/src/views/post-editor/PostHeaderPanel.tsx +107 -0
  306. package/src/views/post-editor/PostPreview.tsx +114 -0
  307. package/src/views/post-editor/PostSectionEditor.tsx +506 -0
  308. package/src/views/post-editor/PostTemplateEditor.tsx +347 -0
@@ -0,0 +1,347 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from 'react'
4
+ import { AlertTriangle, ArrowLeft, Layout, Loader2, Save } from 'lucide-react'
5
+ import { toast } from 'sonner'
6
+ import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js'
7
+ import { ErrorBoundary } from '../../components/ErrorBoundary.js'
8
+ import {
9
+ addSection,
10
+ createSection,
11
+ duplicateSection,
12
+ fetchTemplateForEditor,
13
+ reorderSections,
14
+ removeSection,
15
+ saveTemplate,
16
+ toggleSectionVisibility,
17
+ updateSectionContent,
18
+ updateSectionMeta,
19
+ updateSectionSettings,
20
+ validateTemplate,
21
+ type EditorPostTemplate,
22
+ type PostHeaderConfig,
23
+ } from '../../lib/post-editor-service.js'
24
+ import type { SectionSettings, SectionTypeId } from '../page-editor/section-types.js'
25
+ import type { PostRenderContext } from '../page-editor/sections/index.js'
26
+ import { DEFAULT_VIEWPORT, type ViewportId } from '../page-editor/viewports.js'
27
+ import { SectionsPanel } from '../page-editor/SectionsPanel.js'
28
+ import { SectionInspector } from '../page-editor/SectionInspector.js'
29
+ import { AddSectionModal } from '../page-editor/AddSectionModal.js'
30
+ import { PostEditorCanvas } from './PostEditorCanvas.js'
31
+ import { PostHeaderPanel } from './PostHeaderPanel.js'
32
+
33
+ interface PostTemplateEditorProps {
34
+ postType: string
35
+ config?: any
36
+ session?: any
37
+ onNavigate: (path: string) => void
38
+ }
39
+
40
+ function deriveCanEdit(session: any): boolean {
41
+ const role: string | undefined = session?.user?.role
42
+ if (!role) return true
43
+ return ['ADMIN', 'EDITOR'].includes(role)
44
+ }
45
+
46
+ const SAMPLE_CONTEXT: PostRenderContext = {
47
+ title: 'Sample Post Title',
48
+ excerpt: 'A short summary that previews how the header excerpt will look.',
49
+ body: 'This is sample body copy.\n\nIt shows how the Article Body section renders the post body once an author writes a post with this template.',
50
+ publishDate: new Date().toISOString(),
51
+ category: 'Category',
52
+ author: { name: 'Author Name', bio: 'A short example author bio for the preview.' },
53
+ }
54
+
55
+ export function PostTemplateEditor({
56
+ postType,
57
+ config,
58
+ session,
59
+ onNavigate,
60
+ }: PostTemplateEditorProps) {
61
+ const [template, setTemplate] = useState<EditorPostTemplate | null>(null)
62
+ const [loading, setLoading] = useState(true)
63
+ const [loadError, setLoadError] = useState<string | null>(null)
64
+
65
+ // selection: a section id, the special 'header' panel, or nothing.
66
+ const [selected, setSelected] = useState<
67
+ { kind: 'header' } | { kind: 'section'; id: string } | null
68
+ >(null)
69
+ const [viewport, setViewport] = useState<ViewportId>(DEFAULT_VIEWPORT)
70
+ const [dirty, setDirty] = useState(false)
71
+ const [saving, setSaving] = useState(false)
72
+
73
+ const [addOpen, setAddOpen] = useState(false)
74
+ const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null)
75
+ const [leaveTarget, setLeaveTarget] = useState<string | null>(null)
76
+
77
+ const canEdit = useMemo(() => deriveCanEdit(session), [session])
78
+ const typeLabel = useMemo(() => {
79
+ const col = config?.collections?.[postType]
80
+ return col?.labels?.singular ?? postType
81
+ }, [config, postType])
82
+
83
+ useEffect(() => {
84
+ let cancelled = false
85
+ setLoading(true)
86
+ setLoadError(null)
87
+ fetchTemplateForEditor(postType)
88
+ .then((t) => {
89
+ if (!cancelled) setTemplate(t)
90
+ })
91
+ .catch((err) => {
92
+ if (!cancelled) setLoadError(err instanceof Error ? err.message : 'Failed to load template')
93
+ })
94
+ .finally(() => {
95
+ if (!cancelled) setLoading(false)
96
+ })
97
+ return () => {
98
+ cancelled = true
99
+ }
100
+ }, [postType])
101
+
102
+ useEffect(() => {
103
+ if (!dirty) return
104
+ function onBeforeUnload(e: BeforeUnloadEvent) {
105
+ e.preventDefault()
106
+ e.returnValue = ''
107
+ }
108
+ window.addEventListener('beforeunload', onBeforeUnload)
109
+ return () => window.removeEventListener('beforeunload', onBeforeUnload)
110
+ }, [dirty])
111
+
112
+ const mutateSections = useCallback(
113
+ (fn: (sections: EditorPostTemplate['sections']) => EditorPostTemplate['sections']) => {
114
+ setTemplate((prev) => (prev ? { ...prev, sections: fn(prev.sections) } : prev))
115
+ setDirty(true)
116
+ },
117
+ [],
118
+ )
119
+
120
+ const handleHeaderChange = useCallback((patch: Partial<PostHeaderConfig>) => {
121
+ setTemplate((prev) => (prev ? { ...prev, header: { ...prev.header, ...patch } } : prev))
122
+ setDirty(true)
123
+ }, [])
124
+
125
+ const handleAdd = useCallback(
126
+ (type: SectionTypeId) => {
127
+ const section = createSection(type)
128
+ mutateSections((s) => addSection(s, section))
129
+ setSelected({ kind: 'section', id: section.id })
130
+ setAddOpen(false)
131
+ },
132
+ [mutateSections],
133
+ )
134
+
135
+ const selectedSectionId = selected?.kind === 'section' ? selected.id : null
136
+
137
+ const handleSave = useCallback(async () => {
138
+ if (!template) return
139
+ const result = validateTemplate(template)
140
+ if (result.errors.length > 0) {
141
+ toast.error(result.errors[0]!)
142
+ return
143
+ }
144
+ setSaving(true)
145
+ try {
146
+ const saved = await saveTemplate(template)
147
+ setTemplate(saved)
148
+ setDirty(false)
149
+ toast.success('Template saved')
150
+ } catch (err) {
151
+ toast.error(err instanceof Error ? err.message : 'Failed to save template')
152
+ } finally {
153
+ setSaving(false)
154
+ }
155
+ }, [template])
156
+
157
+ const guardedNavigate = useCallback(
158
+ (path: string) => {
159
+ if (dirty) setLeaveTarget(path)
160
+ else onNavigate(path)
161
+ },
162
+ [dirty, onNavigate],
163
+ )
164
+
165
+ if (loading) {
166
+ return (
167
+ <div className="bg-background flex h-full items-center justify-center">
168
+ <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" aria-hidden />
169
+ <span className="sr-only">Loading template editor…</span>
170
+ </div>
171
+ )
172
+ }
173
+
174
+ if (loadError || !template) {
175
+ return (
176
+ <div className="bg-background flex h-full items-center justify-center p-6">
177
+ <div className="border-destructive/30 bg-destructive/5 flex max-w-md items-center gap-3 rounded-lg border p-4">
178
+ <AlertTriangle className="text-destructive shrink-0" size={20} aria-hidden />
179
+ <div className="min-w-0">
180
+ <p className="text-foreground text-sm font-medium">Failed to load template</p>
181
+ <p className="text-muted-foreground mt-1 text-sm">{loadError ?? 'Not found.'}</p>
182
+ <button
183
+ type="button"
184
+ onClick={() => onNavigate('/posts/types')}
185
+ className="text-primary mt-2 text-sm font-medium hover:underline"
186
+ >
187
+ Back to Post Types
188
+ </button>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ )
193
+ }
194
+
195
+ const selectedSection = template.sections.find((s) => s.id === selectedSectionId) ?? null
196
+
197
+ return (
198
+ <ErrorBoundary>
199
+ <div className="bg-background flex h-full flex-col overflow-hidden">
200
+ {/* Slim top bar (template has no publish lifecycle — just Save). */}
201
+ <header className="bg-card border-border flex h-14 shrink-0 items-center justify-between gap-4 border-b px-4">
202
+ <nav aria-label="Breadcrumb" className="flex min-w-0 items-center gap-1.5 text-sm">
203
+ <button
204
+ type="button"
205
+ onClick={() => guardedNavigate('/posts/types')}
206
+ className="text-muted-foreground hover:text-foreground inline-flex shrink-0 items-center gap-1.5"
207
+ >
208
+ <ArrowLeft className="h-4 w-4" aria-hidden />
209
+ Post Types
210
+ </button>
211
+ <span className="text-muted-foreground" aria-hidden>
212
+ /
213
+ </span>
214
+ <span className="text-foreground truncate font-medium">{typeLabel} Template</span>
215
+ {dirty && (
216
+ <span className="text-muted-foreground ml-2 text-xs" aria-live="polite">
217
+ Unsaved changes
218
+ </span>
219
+ )}
220
+ </nav>
221
+
222
+ <div className="flex items-center gap-2">
223
+ <button
224
+ type="button"
225
+ onClick={() => setSelected({ kind: 'header' })}
226
+ className={`inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-medium transition-colors ${
227
+ selected?.kind === 'header'
228
+ ? 'border-primary bg-accent text-foreground'
229
+ : 'border-border text-foreground hover:bg-accent'
230
+ }`}
231
+ >
232
+ <Layout className="h-4 w-4" aria-hidden />
233
+ Header
234
+ </button>
235
+ <button
236
+ type="button"
237
+ onClick={handleSave}
238
+ disabled={!canEdit || saving || !dirty}
239
+ className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
240
+ >
241
+ {saving ? (
242
+ <Loader2 className="h-3.5 w-3.5 animate-spin" aria-hidden />
243
+ ) : (
244
+ <Save className="h-3.5 w-3.5" aria-hidden />
245
+ )}
246
+ Save
247
+ </button>
248
+ </div>
249
+ </header>
250
+
251
+ <div className="flex flex-1 overflow-hidden">
252
+ <SectionsPanel
253
+ sections={template.sections}
254
+ selectedId={selectedSectionId}
255
+ canEdit={canEdit}
256
+ onSelect={(id) => setSelected({ kind: 'section', id })}
257
+ onReorder={(ids) => mutateSections((s) => reorderSections(s, ids))}
258
+ onToggleVisibility={(id) => mutateSections((s) => toggleSectionVisibility(s, id))}
259
+ onDuplicate={(id) => mutateSections((s) => duplicateSection(s, id))}
260
+ onRequestDelete={(id) => setDeleteTargetId(id)}
261
+ onAdd={() => setAddOpen(true)}
262
+ />
263
+
264
+ <div className="flex-1 overflow-hidden">
265
+ <PostEditorCanvas
266
+ sections={template.sections}
267
+ header={template.header}
268
+ context={SAMPLE_CONTEXT}
269
+ selectedId={selectedSectionId}
270
+ viewport={viewport}
271
+ onSelect={(id) => setSelected({ kind: 'section', id })}
272
+ onAddSection={() => setAddOpen(true)}
273
+ />
274
+ </div>
275
+
276
+ {selected?.kind === 'header' && (
277
+ <PostHeaderPanel
278
+ header={template.header}
279
+ canEdit={canEdit}
280
+ onClose={() => setSelected(null)}
281
+ onChange={handleHeaderChange}
282
+ />
283
+ )}
284
+
285
+ {selectedSection && (
286
+ <SectionInspector
287
+ section={selectedSection}
288
+ canEdit={canEdit}
289
+ onClose={() => setSelected(null)}
290
+ onContentChange={(patch) =>
291
+ mutateSections((s) => updateSectionContent(s, selectedSection.id, patch))
292
+ }
293
+ onSettingsChange={(patch: Partial<SectionSettings>) =>
294
+ mutateSections((s) => updateSectionSettings(s, selectedSection.id, patch))
295
+ }
296
+ onMetaChange={(patch) =>
297
+ mutateSections((s) => updateSectionMeta(s, selectedSection.id, patch))
298
+ }
299
+ onToggleVisibility={() =>
300
+ mutateSections((s) => toggleSectionVisibility(s, selectedSection.id))
301
+ }
302
+ />
303
+ )}
304
+ </div>
305
+ </div>
306
+
307
+ <AddSectionModal
308
+ open={addOpen}
309
+ scope="post"
310
+ onClose={() => setAddOpen(false)}
311
+ onAdd={handleAdd}
312
+ />
313
+
314
+ <ConfirmDialog
315
+ open={deleteTargetId !== null}
316
+ onClose={() => setDeleteTargetId(null)}
317
+ onConfirm={() => {
318
+ if (deleteTargetId) {
319
+ mutateSections((s) => removeSection(s, deleteTargetId))
320
+ if (selectedSectionId === deleteTargetId) setSelected(null)
321
+ }
322
+ setDeleteTargetId(null)
323
+ }}
324
+ title="Delete section?"
325
+ description="This removes the section from the template. You can undo by leaving without saving."
326
+ confirmLabel="Delete"
327
+ destructive
328
+ />
329
+
330
+ <ConfirmDialog
331
+ open={leaveTarget !== null}
332
+ onClose={() => setLeaveTarget(null)}
333
+ onConfirm={() => {
334
+ const target = leaveTarget
335
+ setLeaveTarget(null)
336
+ setDirty(false)
337
+ if (target) onNavigate(target)
338
+ }}
339
+ title="Discard unsaved changes?"
340
+ description="You have unsaved changes that will be lost if you leave now."
341
+ confirmLabel="Leave"
342
+ cancelLabel="Stay"
343
+ destructive
344
+ />
345
+ </ErrorBoundary>
346
+ )
347
+ }