@prenta/admin 0.92.0 → 0.94.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/design-system/tokens.css +173 -71
  3. package/design-system/tokens.json +244 -63
  4. package/design-system/tokens.scss +203 -98
  5. package/design-system/tokens.tailwind.cjs +93 -38
  6. package/dist/AdminRoot.d.ts +27 -6
  7. package/dist/AdminRoot.d.ts.map +1 -1
  8. package/dist/AdminRoot.js.map +1 -1
  9. package/dist/__tests__/components/breakpoint-controls.render.test.d.ts +2 -0
  10. package/dist/__tests__/components/breakpoint-controls.render.test.d.ts.map +1 -0
  11. package/dist/__tests__/components/breakpoint-controls.render.test.js +51 -0
  12. package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -0
  13. package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts +2 -0
  14. package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts.map +1 -0
  15. package/dist/__tests__/components/canvas-edit-tip.render.test.js +37 -0
  16. package/dist/__tests__/components/canvas-edit-tip.render.test.js.map +1 -0
  17. package/dist/__tests__/components/editor-rail.render.test.d.ts +2 -0
  18. package/dist/__tests__/components/editor-rail.render.test.d.ts.map +1 -0
  19. package/dist/__tests__/components/editor-rail.render.test.js +138 -0
  20. package/dist/__tests__/components/editor-rail.render.test.js.map +1 -0
  21. package/dist/__tests__/components/media-block.render.test.d.ts +2 -0
  22. package/dist/__tests__/components/media-block.render.test.d.ts.map +1 -0
  23. package/dist/__tests__/components/media-block.render.test.js +108 -0
  24. package/dist/__tests__/components/media-block.render.test.js.map +1 -0
  25. package/dist/__tests__/components/page-audit-panel.render.test.d.ts +2 -0
  26. package/dist/__tests__/components/page-audit-panel.render.test.d.ts.map +1 -0
  27. package/dist/__tests__/components/page-audit-panel.render.test.js +59 -0
  28. package/dist/__tests__/components/page-audit-panel.render.test.js.map +1 -0
  29. package/dist/__tests__/components/reflow-meter.render.test.d.ts +2 -0
  30. package/dist/__tests__/components/reflow-meter.render.test.d.ts.map +1 -0
  31. package/dist/__tests__/components/reflow-meter.render.test.js +94 -0
  32. package/dist/__tests__/components/reflow-meter.render.test.js.map +1 -0
  33. package/dist/__tests__/components/seo-char-count.test.js +24 -6
  34. package/dist/__tests__/components/seo-char-count.test.js.map +1 -1
  35. package/dist/__tests__/components/serp-preview.render.test.d.ts +2 -0
  36. package/dist/__tests__/components/serp-preview.render.test.d.ts.map +1 -0
  37. package/dist/__tests__/components/serp-preview.render.test.js +25 -0
  38. package/dist/__tests__/components/serp-preview.render.test.js.map +1 -0
  39. package/dist/__tests__/lib/breakpoints.test.d.ts +2 -0
  40. package/dist/__tests__/lib/breakpoints.test.d.ts.map +1 -0
  41. package/dist/__tests__/lib/breakpoints.test.js +67 -0
  42. package/dist/__tests__/lib/breakpoints.test.js.map +1 -0
  43. package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts +2 -0
  44. package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts.map +1 -0
  45. package/dist/__tests__/lib/canvas-edit-chrome.test.js +44 -0
  46. package/dist/__tests__/lib/canvas-edit-chrome.test.js.map +1 -0
  47. package/dist/__tests__/lib/media-format.test.d.ts +2 -0
  48. package/dist/__tests__/lib/media-format.test.d.ts.map +1 -0
  49. package/dist/__tests__/lib/media-format.test.js +70 -0
  50. package/dist/__tests__/lib/media-format.test.js.map +1 -0
  51. package/dist/__tests__/lib/page-audit-input.test.d.ts +2 -0
  52. package/dist/__tests__/lib/page-audit-input.test.d.ts.map +1 -0
  53. package/dist/__tests__/lib/page-audit-input.test.js +122 -0
  54. package/dist/__tests__/lib/page-audit-input.test.js.map +1 -0
  55. package/dist/__tests__/lib/page-editor-service.test.js +2 -0
  56. package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
  57. package/dist/__tests__/lib/page-redirect.test.d.ts +2 -0
  58. package/dist/__tests__/lib/page-redirect.test.d.ts.map +1 -0
  59. package/dist/__tests__/lib/page-redirect.test.js +26 -0
  60. package/dist/__tests__/lib/page-redirect.test.js.map +1 -0
  61. package/dist/__tests__/lib/rail-width.test.d.ts +2 -0
  62. package/dist/__tests__/lib/rail-width.test.d.ts.map +1 -0
  63. package/dist/__tests__/lib/rail-width.test.js +69 -0
  64. package/dist/__tests__/lib/rail-width.test.js.map +1 -0
  65. package/dist/__tests__/lib/reflow-baseline.test.d.ts +2 -0
  66. package/dist/__tests__/lib/reflow-baseline.test.d.ts.map +1 -0
  67. package/dist/__tests__/lib/reflow-baseline.test.js +56 -0
  68. package/dist/__tests__/lib/reflow-baseline.test.js.map +1 -0
  69. package/dist/__tests__/lib/reflow-probe.test.d.ts +2 -0
  70. package/dist/__tests__/lib/reflow-probe.test.d.ts.map +1 -0
  71. package/dist/__tests__/lib/reflow-probe.test.js +73 -0
  72. package/dist/__tests__/lib/reflow-probe.test.js.map +1 -0
  73. package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
  74. package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
  75. package/dist/__tests__/lib/version-diff.test.js +230 -0
  76. package/dist/__tests__/lib/version-diff.test.js.map +1 -0
  77. package/dist/__tests__/lib/version-history.test.d.ts +2 -0
  78. package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
  79. package/dist/__tests__/lib/version-history.test.js +125 -0
  80. package/dist/__tests__/lib/version-history.test.js.map +1 -0
  81. package/dist/__tests__/styles/canvas-container-queries.test.d.ts +2 -0
  82. package/dist/__tests__/styles/canvas-container-queries.test.d.ts.map +1 -0
  83. package/dist/__tests__/styles/canvas-container-queries.test.js +87 -0
  84. package/dist/__tests__/styles/canvas-container-queries.test.js.map +1 -0
  85. package/dist/__tests__/styles/packaging.test.d.ts +2 -0
  86. package/dist/__tests__/styles/packaging.test.d.ts.map +1 -0
  87. package/dist/__tests__/styles/packaging.test.js +31 -0
  88. package/dist/__tests__/styles/packaging.test.js.map +1 -0
  89. package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
  90. package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
  91. package/dist/__tests__/styles/responsive-layer.test.js +127 -0
  92. package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
  93. package/dist/__tests__/styles/tokens-drift.test.d.ts +2 -0
  94. package/dist/__tests__/styles/tokens-drift.test.d.ts.map +1 -0
  95. package/dist/__tests__/styles/tokens-drift.test.js +92 -0
  96. package/dist/__tests__/styles/tokens-drift.test.js.map +1 -0
  97. package/dist/__tests__/views/admin-renderer-compat.test.d.ts +2 -0
  98. package/dist/__tests__/views/admin-renderer-compat.test.d.ts.map +1 -0
  99. package/dist/__tests__/views/admin-renderer-compat.test.js +43 -0
  100. package/dist/__tests__/views/admin-renderer-compat.test.js.map +1 -0
  101. package/dist/__tests__/views/admin-section-renderers.test.js +52 -4
  102. package/dist/__tests__/views/admin-section-renderers.test.js.map +1 -1
  103. package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
  104. package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
  105. package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
  106. package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
  107. package/dist/__tests__/views/editor-top-bar.render.test.js +30 -1
  108. package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
  109. package/dist/__tests__/views/page-tab.render.test.d.ts +2 -0
  110. package/dist/__tests__/views/page-tab.render.test.d.ts.map +1 -0
  111. package/dist/__tests__/views/page-tab.render.test.js +151 -0
  112. package/dist/__tests__/views/page-tab.render.test.js.map +1 -0
  113. package/dist/__tests__/views/section-class-parity.test.d.ts +2 -0
  114. package/dist/__tests__/views/section-class-parity.test.d.ts.map +1 -0
  115. package/dist/__tests__/views/section-class-parity.test.js +81 -0
  116. package/dist/__tests__/views/section-class-parity.test.js.map +1 -0
  117. package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts +2 -0
  118. package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts.map +1 -0
  119. package/dist/__tests__/views/sections-panel-design-owned.render.test.js +86 -0
  120. package/dist/__tests__/views/sections-panel-design-owned.render.test.js.map +1 -0
  121. package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
  122. package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
  123. package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
  124. package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
  125. package/dist/components/BreakpointPicker.d.ts +18 -0
  126. package/dist/components/BreakpointPicker.d.ts.map +1 -0
  127. package/dist/components/BreakpointPicker.js +31 -0
  128. package/dist/components/BreakpointPicker.js.map +1 -0
  129. package/dist/components/CanvasEditTip.d.ts +6 -0
  130. package/dist/components/CanvasEditTip.d.ts.map +1 -0
  131. package/dist/components/CanvasEditTip.js +22 -0
  132. package/dist/components/CanvasEditTip.js.map +1 -0
  133. package/dist/components/EditorRail.d.ts +36 -0
  134. package/dist/components/EditorRail.d.ts.map +1 -0
  135. package/dist/components/EditorRail.js +104 -0
  136. package/dist/components/EditorRail.js.map +1 -0
  137. package/dist/components/EditorRailTabs.d.ts +32 -0
  138. package/dist/components/EditorRailTabs.d.ts.map +1 -0
  139. package/dist/components/EditorRailTabs.js +45 -0
  140. package/dist/components/EditorRailTabs.js.map +1 -0
  141. package/dist/components/MediaBlock.d.ts +49 -0
  142. package/dist/components/MediaBlock.d.ts.map +1 -0
  143. package/dist/components/MediaBlock.js +50 -0
  144. package/dist/components/MediaBlock.js.map +1 -0
  145. package/dist/components/ReflowMeter.d.ts +28 -0
  146. package/dist/components/ReflowMeter.d.ts.map +1 -0
  147. package/dist/components/ReflowMeter.js +42 -0
  148. package/dist/components/ReflowMeter.js.map +1 -0
  149. package/dist/components/ZoomChip.d.ts +17 -0
  150. package/dist/components/ZoomChip.d.ts.map +1 -0
  151. package/dist/components/ZoomChip.js +17 -0
  152. package/dist/components/ZoomChip.js.map +1 -0
  153. package/dist/components/breakpoint-icons.d.ts +3 -0
  154. package/dist/components/breakpoint-icons.d.ts.map +1 -0
  155. package/dist/components/breakpoint-icons.js +16 -0
  156. package/dist/components/breakpoint-icons.js.map +1 -0
  157. package/dist/components/page-tab/AuthorPicker.d.ts +31 -0
  158. package/dist/components/page-tab/AuthorPicker.d.ts.map +1 -0
  159. package/dist/components/page-tab/AuthorPicker.js +95 -0
  160. package/dist/components/page-tab/AuthorPicker.js.map +1 -0
  161. package/dist/components/page-tab/DirectiveSwitch.d.ts +38 -0
  162. package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -0
  163. package/dist/components/page-tab/DirectiveSwitch.js +33 -0
  164. package/dist/components/page-tab/DirectiveSwitch.js.map +1 -0
  165. package/dist/components/seo/EditorSeoScoreBadge.d.ts +8 -1
  166. package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -1
  167. package/dist/components/seo/EditorSeoScoreBadge.js +2 -2
  168. package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -1
  169. package/dist/components/seo/PageAuditPanel.d.ts +14 -0
  170. package/dist/components/seo/PageAuditPanel.d.ts.map +1 -0
  171. package/dist/components/seo/PageAuditPanel.js +59 -0
  172. package/dist/components/seo/PageAuditPanel.js.map +1 -0
  173. package/dist/components/seo/SeoCharCount.d.ts +19 -7
  174. package/dist/components/seo/SeoCharCount.d.ts.map +1 -1
  175. package/dist/components/seo/SeoCharCount.js +30 -16
  176. package/dist/components/seo/SeoCharCount.js.map +1 -1
  177. package/dist/components/seo/SeoEditorPane.d.ts +15 -1
  178. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
  179. package/dist/components/seo/SeoEditorPane.js +118 -21
  180. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  181. package/dist/components/seo/SerpPreview.d.ts +20 -0
  182. package/dist/components/seo/SerpPreview.d.ts.map +1 -0
  183. package/dist/components/seo/SerpPreview.js +6 -0
  184. package/dist/components/seo/SerpPreview.js.map +1 -0
  185. package/dist/hooks/usePageAudit.d.ts +41 -0
  186. package/dist/hooks/usePageAudit.d.ts.map +1 -0
  187. package/dist/hooks/usePageAudit.js +68 -0
  188. package/dist/hooks/usePageAudit.js.map +1 -0
  189. package/dist/hooks/useReflowGuard.d.ts +47 -0
  190. package/dist/hooks/useReflowGuard.d.ts.map +1 -0
  191. package/dist/hooks/useReflowGuard.js +52 -0
  192. package/dist/hooks/useReflowGuard.js.map +1 -0
  193. package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
  194. package/dist/layout/primitives/AdminShell.js +2 -2
  195. package/dist/layout/primitives/AdminShell.js.map +1 -1
  196. package/dist/lib/breakpoints.d.ts +51 -0
  197. package/dist/lib/breakpoints.d.ts.map +1 -0
  198. package/dist/lib/breakpoints.js +84 -0
  199. package/dist/lib/breakpoints.js.map +1 -0
  200. package/dist/lib/canvas-edit-chrome.d.ts +22 -0
  201. package/dist/lib/canvas-edit-chrome.d.ts.map +1 -0
  202. package/dist/lib/canvas-edit-chrome.js +81 -0
  203. package/dist/lib/canvas-edit-chrome.js.map +1 -0
  204. package/dist/lib/media-format.d.ts +55 -0
  205. package/dist/lib/media-format.d.ts.map +1 -0
  206. package/dist/lib/media-format.js +93 -0
  207. package/dist/lib/media-format.js.map +1 -0
  208. package/dist/lib/page-audit-input.d.ts +45 -0
  209. package/dist/lib/page-audit-input.d.ts.map +1 -0
  210. package/dist/lib/page-audit-input.js +53 -0
  211. package/dist/lib/page-audit-input.js.map +1 -0
  212. package/dist/lib/page-editor-service.d.ts +12 -0
  213. package/dist/lib/page-editor-service.d.ts.map +1 -1
  214. package/dist/lib/page-editor-service.js +6 -0
  215. package/dist/lib/page-editor-service.js.map +1 -1
  216. package/dist/lib/page-redirect.d.ts +39 -0
  217. package/dist/lib/page-redirect.d.ts.map +1 -0
  218. package/dist/lib/page-redirect.js +38 -0
  219. package/dist/lib/page-redirect.js.map +1 -0
  220. package/dist/lib/rail-width.d.ts +23 -0
  221. package/dist/lib/rail-width.d.ts.map +1 -0
  222. package/dist/lib/rail-width.js +59 -0
  223. package/dist/lib/rail-width.js.map +1 -0
  224. package/dist/lib/reflow-baseline.d.ts +45 -0
  225. package/dist/lib/reflow-baseline.d.ts.map +1 -0
  226. package/dist/lib/reflow-baseline.js +38 -0
  227. package/dist/lib/reflow-baseline.js.map +1 -0
  228. package/dist/lib/reflow-probe.d.ts +159 -0
  229. package/dist/lib/reflow-probe.d.ts.map +1 -0
  230. package/dist/lib/reflow-probe.js +228 -0
  231. package/dist/lib/reflow-probe.js.map +1 -0
  232. package/dist/lib/seo-service.d.ts +10 -0
  233. package/dist/lib/seo-service.d.ts.map +1 -1
  234. package/dist/lib/seo-service.js.map +1 -1
  235. package/dist/lib/version-diff.d.ts +50 -0
  236. package/dist/lib/version-diff.d.ts.map +1 -0
  237. package/dist/lib/version-diff.js +294 -0
  238. package/dist/lib/version-diff.js.map +1 -0
  239. package/dist/lib/version-history.d.ts +73 -0
  240. package/dist/lib/version-history.d.ts.map +1 -0
  241. package/dist/lib/version-history.js +126 -0
  242. package/dist/lib/version-history.js.map +1 -0
  243. package/dist/prenta-admin.css +1 -1
  244. package/dist/views/page-editor/CanvasMetaLine.d.ts +19 -0
  245. package/dist/views/page-editor/CanvasMetaLine.d.ts.map +1 -0
  246. package/dist/views/page-editor/CanvasMetaLine.js +23 -0
  247. package/dist/views/page-editor/CanvasMetaLine.js.map +1 -0
  248. package/dist/views/page-editor/EditorCanvas.d.ts +47 -8
  249. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
  250. package/dist/views/page-editor/EditorCanvas.js +68 -34
  251. package/dist/views/page-editor/EditorCanvas.js.map +1 -1
  252. package/dist/views/page-editor/EditorTopBar.d.ts +21 -2
  253. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  254. package/dist/views/page-editor/EditorTopBar.js +13 -20
  255. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  256. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  257. package/dist/views/page-editor/PageSectionEditor.js +383 -78
  258. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  259. package/dist/views/page-editor/PageTab.d.ts +36 -0
  260. package/dist/views/page-editor/PageTab.d.ts.map +1 -0
  261. package/dist/views/page-editor/PageTab.js +65 -0
  262. package/dist/views/page-editor/PageTab.js.map +1 -0
  263. package/dist/views/page-editor/ReviewBar.d.ts +21 -0
  264. package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
  265. package/dist/views/page-editor/ReviewBar.js +22 -0
  266. package/dist/views/page-editor/ReviewBar.js.map +1 -0
  267. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
  268. package/dist/views/page-editor/SectionInspector.js +64 -20
  269. package/dist/views/page-editor/SectionInspector.js.map +1 -1
  270. package/dist/views/page-editor/SectionsPanel.d.ts +30 -5
  271. package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
  272. package/dist/views/page-editor/SectionsPanel.js +38 -7
  273. package/dist/views/page-editor/SectionsPanel.js.map +1 -1
  274. package/dist/views/page-editor/SitePreviewFrame.d.ts.map +1 -1
  275. package/dist/views/page-editor/SitePreviewFrame.js +3 -0
  276. package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
  277. package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
  278. package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
  279. package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
  280. package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
  281. package/dist/views/page-editor/reflow-context.d.ts +28 -0
  282. package/dist/views/page-editor/reflow-context.d.ts.map +1 -0
  283. package/dist/views/page-editor/reflow-context.js +14 -0
  284. package/dist/views/page-editor/reflow-context.js.map +1 -0
  285. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -1
  286. package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
  287. package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
  288. package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -1
  289. package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
  290. package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
  291. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
  292. package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
  293. package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
  294. package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
  295. package/dist/views/page-editor/sections/GallerySection.js +7 -3
  296. package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
  297. package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
  298. package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
  299. package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
  300. package/dist/views/page-editor/sections/MissionSection.js +1 -1
  301. package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
  302. package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -1
  303. package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
  304. package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
  305. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -1
  306. package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
  307. package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
  308. package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
  309. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
  310. package/dist/views/page-editor/sections/SectionRenderer.js +66 -26
  311. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
  312. package/dist/views/page-editor/sections/admin-renderers.d.ts +47 -15
  313. package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
  314. package/dist/views/page-editor/sections/admin-renderers.js +27 -15
  315. package/dist/views/page-editor/sections/admin-renderers.js.map +1 -1
  316. package/dist/views/page-editor/sections/parts.d.ts +0 -1
  317. package/dist/views/page-editor/sections/parts.d.ts.map +1 -1
  318. package/dist/views/page-editor/sections/parts.js +41 -16
  319. package/dist/views/page-editor/sections/parts.js.map +1 -1
  320. package/dist/views/page-editor/viewports.d.ts +7 -14
  321. package/dist/views/page-editor/viewports.d.ts.map +1 -1
  322. package/dist/views/page-editor/viewports.js +7 -12
  323. package/dist/views/page-editor/viewports.js.map +1 -1
  324. package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -1
  325. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
  326. package/dist/views/post-editor/PostEditorCanvas.js +5 -2
  327. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  328. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  329. package/dist/views/post-editor/PostSectionEditor.js +2 -20
  330. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  331. package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -1
  332. package/dist/views/post-editor/PostTemplateEditor.js +4 -1
  333. package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
  334. package/package.json +5 -3
  335. package/src/AdminRoot.tsx +27 -6
  336. package/src/__tests__/components/breakpoint-controls.render.test.tsx +57 -0
  337. package/src/__tests__/components/canvas-edit-tip.render.test.tsx +41 -0
  338. package/src/__tests__/components/editor-rail.render.test.tsx +192 -0
  339. package/src/__tests__/components/media-block.render.test.tsx +126 -0
  340. package/src/__tests__/components/page-audit-panel.render.test.tsx +64 -0
  341. package/src/__tests__/components/reflow-meter.render.test.tsx +144 -0
  342. package/src/__tests__/components/seo-char-count.test.tsx +26 -6
  343. package/src/__tests__/components/serp-preview.render.test.tsx +32 -0
  344. package/src/__tests__/lib/breakpoints.test.ts +84 -0
  345. package/src/__tests__/lib/canvas-edit-chrome.test.ts +56 -0
  346. package/src/__tests__/lib/media-format.test.ts +89 -0
  347. package/src/__tests__/lib/page-audit-input.test.ts +144 -0
  348. package/src/__tests__/lib/page-editor-service.test.ts +2 -0
  349. package/src/__tests__/lib/page-redirect.test.ts +29 -0
  350. package/src/__tests__/lib/rail-width.test.ts +86 -0
  351. package/src/__tests__/lib/reflow-baseline.test.ts +64 -0
  352. package/src/__tests__/lib/reflow-probe.test.ts +95 -0
  353. package/src/__tests__/lib/version-diff.test.ts +278 -0
  354. package/src/__tests__/lib/version-history.test.ts +166 -0
  355. package/src/__tests__/styles/canvas-container-queries.test.ts +103 -0
  356. package/src/__tests__/styles/packaging.test.ts +38 -0
  357. package/src/__tests__/styles/responsive-layer.test.ts +152 -0
  358. package/src/__tests__/styles/tokens-drift.test.ts +110 -0
  359. package/src/__tests__/views/admin-renderer-compat.test.ts +48 -0
  360. package/src/__tests__/views/admin-section-renderers.test.tsx +70 -4
  361. package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
  362. package/src/__tests__/views/editor-top-bar.render.test.tsx +43 -1
  363. package/src/__tests__/views/page-tab.render.test.tsx +226 -0
  364. package/src/__tests__/views/section-class-parity.test.ts +98 -0
  365. package/src/__tests__/views/sections-panel-design-owned.render.test.tsx +104 -0
  366. package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
  367. package/src/components/BreakpointPicker.tsx +114 -0
  368. package/src/components/CanvasEditTip.tsx +46 -0
  369. package/src/components/EditorRail.tsx +219 -0
  370. package/src/components/EditorRailTabs.tsx +117 -0
  371. package/src/components/MediaBlock.tsx +223 -0
  372. package/src/components/ReflowMeter.tsx +125 -0
  373. package/src/components/ZoomChip.tsx +39 -0
  374. package/src/components/breakpoint-icons.ts +17 -0
  375. package/src/components/page-tab/AuthorPicker.tsx +240 -0
  376. package/src/components/page-tab/DirectiveSwitch.tsx +89 -0
  377. package/src/components/seo/EditorSeoScoreBadge.tsx +9 -1
  378. package/src/components/seo/PageAuditPanel.tsx +192 -0
  379. package/src/components/seo/SeoCharCount.tsx +55 -24
  380. package/src/components/seo/SeoEditorPane.tsx +213 -31
  381. package/src/components/seo/SerpPreview.tsx +41 -0
  382. package/src/hooks/usePageAudit.ts +98 -0
  383. package/src/hooks/useReflowGuard.ts +96 -0
  384. package/src/layout/primitives/AdminShell.tsx +13 -1
  385. package/src/lib/breakpoints.ts +107 -0
  386. package/src/lib/canvas-edit-chrome.ts +82 -0
  387. package/src/lib/media-format.ts +99 -0
  388. package/src/lib/page-audit-input.ts +104 -0
  389. package/src/lib/page-editor-service.ts +18 -0
  390. package/src/lib/page-redirect.ts +60 -0
  391. package/src/lib/rail-width.ts +58 -0
  392. package/src/lib/reflow-baseline.ts +70 -0
  393. package/src/lib/reflow-probe.ts +299 -0
  394. package/src/lib/seo-service.ts +10 -0
  395. package/src/lib/version-diff.ts +376 -0
  396. package/src/lib/version-history.ts +192 -0
  397. package/src/styles/prenta-tokens.css +120 -42
  398. package/src/styles/responsive.css +281 -0
  399. package/src/styles/theme.css +138 -0
  400. package/src/views/page-editor/CanvasMetaLine.tsx +50 -0
  401. package/src/views/page-editor/EditorCanvas.tsx +140 -54
  402. package/src/views/page-editor/EditorTopBar.tsx +55 -51
  403. package/src/views/page-editor/PageSectionEditor.tsx +575 -185
  404. package/src/views/page-editor/PageTab.tsx +433 -0
  405. package/src/views/page-editor/ReviewBar.tsx +76 -0
  406. package/src/views/page-editor/SectionInspector.tsx +84 -42
  407. package/src/views/page-editor/SectionsPanel.tsx +282 -141
  408. package/src/views/page-editor/SitePreviewFrame.tsx +3 -0
  409. package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
  410. package/src/views/page-editor/reflow-context.ts +43 -0
  411. package/src/views/page-editor/sections/ByTheNumbersSection.tsx +3 -2
  412. package/src/views/page-editor/sections/CtaBannerSection.tsx +3 -1
  413. package/src/views/page-editor/sections/FeatureHighlightSection.tsx +3 -2
  414. package/src/views/page-editor/sections/GallerySection.tsx +7 -3
  415. package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
  416. package/src/views/page-editor/sections/MissionSection.tsx +4 -2
  417. package/src/views/page-editor/sections/PullQuoteSection.tsx +3 -1
  418. package/src/views/page-editor/sections/RelatedPostsSection.tsx +2 -1
  419. package/src/views/page-editor/sections/SectionRenderer.tsx +115 -34
  420. package/src/views/page-editor/sections/admin-renderers.tsx +54 -15
  421. package/src/views/page-editor/sections/parts.tsx +46 -18
  422. package/src/views/page-editor/viewports.ts +13 -26
  423. package/src/views/post-editor/PostEditorCanvas.tsx +18 -11
  424. package/src/views/post-editor/PostSectionEditor.tsx +1 -56
  425. package/src/views/post-editor/PostTemplateEditor.tsx +3 -0
@@ -0,0 +1,433 @@
1
+ 'use client'
2
+
3
+ import { useMemo, useState } from 'react'
4
+ import { AlertTriangle, Check, Copy, ExternalLink, Layers, Lock, Trash2 } from 'lucide-react'
5
+ import { toast } from 'sonner'
6
+ import {
7
+ restrictedCount,
8
+ robotsMetaLine,
9
+ type MaxImagePreview,
10
+ type RobotsPermissions,
11
+ } from '@prenta/core/seo/robots-directives'
12
+ import {
13
+ SeoAccordion,
14
+ SeoAccordionBadge,
15
+ SeoAccordionItem,
16
+ } from '../../components/seo/SeoAccordion.js'
17
+ import { AuthorPicker } from '../../components/page-tab/AuthorPicker.js'
18
+ import { DirectiveSwitch } from '../../components/page-tab/DirectiveSwitch.js'
19
+ import { pendingSlugChange } from '../../lib/page-redirect.js'
20
+ import type { EditorPage } from '../../lib/page-editor-service.js'
21
+
22
+ const INPUT =
23
+ 'border-border bg-input-background text-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-lg border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none'
24
+ const LABEL = 'text-foreground mb-1 block text-xs font-medium'
25
+
26
+ export interface PageTabProps {
27
+ page: EditorPage
28
+ canEdit: boolean
29
+ /** Path as it is on the live site, or null when never published. */
30
+ publishedPath: string | null
31
+ /** Site origin, for resolving the full URL. */
32
+ siteUrl: string
33
+ directives: RobotsPermissions
34
+ onDirectivesChange: (next: RobotsPermissions) => void
35
+ /** Whether a 301 should be created when the moved page is published. */
36
+ redirectOnPublish: boolean
37
+ onRedirectOnPublishChange: (next: boolean) => void
38
+ /**
39
+ * Whether this user can create redirects. `POST /redirects` is limited to
40
+ * publishers who may also publish the page; when false the URL-changed
41
+ * warning still shows, but the checkbox that would promise a 301 does not.
42
+ */
43
+ canCreateRedirect?: boolean
44
+ onPageChange: (patch: Partial<EditorPage>) => void
45
+ onDuplicate: () => void
46
+ onTrash: () => void
47
+ /** Template/schema binding, shown read-only. */
48
+ templateName?: string
49
+ schemaType?: string
50
+ /** Offered as the brand byline in the author picker. */
51
+ brandName?: string
52
+ }
53
+
54
+ /**
55
+ * Rail tab 3 — everything about the page that is not its content or its search
56
+ * copy: where it lives, when it goes live, what crawlers may do with it, what
57
+ * owns its layout, and how to get rid of it.
58
+ */
59
+ export function PageTab({
60
+ page,
61
+ canEdit,
62
+ publishedPath,
63
+ siteUrl,
64
+ directives,
65
+ onDirectivesChange,
66
+ redirectOnPublish,
67
+ onRedirectOnPublishChange,
68
+ canCreateRedirect = true,
69
+ onPageChange,
70
+ onDuplicate,
71
+ onTrash,
72
+ templateName,
73
+ schemaType,
74
+ brandName,
75
+ }: PageTabProps) {
76
+ const [copied, setCopied] = useState(false)
77
+ const [confirmTrash, setConfirmTrash] = useState(false)
78
+
79
+ const fullUrl = useMemo(() => {
80
+ const origin = siteUrl.replace(/\/+$/, '')
81
+ const path = page.path.startsWith('/') ? page.path : `/${page.path}`
82
+ return `${origin}${path}`
83
+ }, [siteUrl, page.path])
84
+
85
+ const move = pendingSlugChange(publishedPath, page.path)
86
+ const restricted = restrictedCount(directives)
87
+ const set = (patch: Partial<RobotsPermissions>) => onDirectivesChange({ ...directives, ...patch })
88
+
89
+ async function copyUrl() {
90
+ try {
91
+ await navigator.clipboard.writeText(fullUrl)
92
+ setCopied(true)
93
+ setTimeout(() => setCopied(false), 1600)
94
+ } catch {
95
+ toast.error('Could not copy — your browser blocked clipboard access.')
96
+ }
97
+ }
98
+
99
+ return (
100
+ <SeoAccordion defaultOpen={['address', 'publishing']}>
101
+ {/* ── Address ────────────────────────────────────────────────────── */}
102
+ <SeoAccordionItem
103
+ value="address"
104
+ title="Address"
105
+ badge={move ? <SeoAccordionBadge tone="warn">URL CHANGED</SeoAccordionBadge> : undefined}
106
+ >
107
+ <div>
108
+ <label className={LABEL} htmlFor="pt-slug">
109
+ URL slug
110
+ </label>
111
+ <input
112
+ id="pt-slug"
113
+ className={INPUT}
114
+ value={page.slug}
115
+ disabled={!canEdit}
116
+ onChange={(e) => {
117
+ // Path is the public URL the redirect detector and the displayed
118
+ // full link both read. Keep it in lockstep when it was derived
119
+ // from the previous slug — the same rule as PageSettingsModal.
120
+ // A custom path (e.g. `/team/about` while slug is `about`) is
121
+ // left alone; the editor must change that elsewhere on purpose.
122
+ const slug = e.target.value
123
+ const patch: Partial<EditorPage> = { slug }
124
+ if (!page.path || page.path === `/${page.slug}`) {
125
+ patch.path = slug ? `/${slug}` : '/'
126
+ }
127
+ onPageChange(patch)
128
+ }}
129
+ />
130
+ <div className="mt-1.5 flex items-center gap-1.5">
131
+ {/* `data-volatile` so visual baselines mask it: the origin comes
132
+ from `NEXT_PUBLIC_SITE_URL`, which is set in some environments
133
+ and not others, and a screenshot that bakes one in fails in the
134
+ other. */}
135
+ <code
136
+ data-volatile
137
+ className="text-muted-foreground min-w-0 flex-1 truncate font-mono text-[11px]"
138
+ >
139
+ {fullUrl}
140
+ </code>
141
+ <button
142
+ type="button"
143
+ onClick={copyUrl}
144
+ className="text-muted-foreground hover:text-foreground shrink-0 rounded p-1"
145
+ aria-label={copied ? 'Full URL copied' : 'Copy full URL'}
146
+ >
147
+ {copied ? (
148
+ <Check size={13} className="text-success" aria-hidden />
149
+ ) : (
150
+ <Copy size={13} aria-hidden />
151
+ )}
152
+ </button>
153
+ </div>
154
+ </div>
155
+
156
+ {/* Only for a page that is actually live. A slug change on a draft
157
+ breaks nothing, and warning about it would train editors to dismiss
158
+ the warning that matters. */}
159
+ {move && (
160
+ <div className="bg-warning/10 border-warning/30 space-y-2 rounded-lg border p-2.5">
161
+ <div className="flex gap-2">
162
+ <AlertTriangle size={14} className="text-warning mt-px shrink-0" aria-hidden />
163
+ <p className="text-[11.5px] leading-normal">
164
+ This page is live at <b className="font-medium">{move.from}</b>. Publishing moves
165
+ it, and every existing link to the old address will 404.
166
+ </p>
167
+ </div>
168
+ {canCreateRedirect ? (
169
+ <label className="flex cursor-pointer items-start gap-2 text-[11.5px]">
170
+ <input
171
+ type="checkbox"
172
+ checked={redirectOnPublish}
173
+ disabled={!canEdit}
174
+ onChange={(e) => onRedirectOnPublishChange(e.target.checked)}
175
+ className="accent-primary mt-0.5"
176
+ />
177
+ <span>
178
+ Create a 301 redirect on publish
179
+ <span className="text-muted-foreground mt-0.5 block font-mono text-[10.5px]">
180
+ {move.from} → {move.to}
181
+ </span>
182
+ </span>
183
+ </label>
184
+ ) : (
185
+ <p className="text-muted-foreground text-[11.5px] leading-normal">
186
+ Ask an admin to create a 301 from{' '}
187
+ <span className="font-mono text-[10.5px]">
188
+ {move.from} → {move.to}
189
+ </span>{' '}
190
+ so existing links keep working.
191
+ </p>
192
+ )}
193
+ </div>
194
+ )}
195
+
196
+ {page.status === 'PUBLISHED' && (
197
+ <a
198
+ href={fullUrl}
199
+ target="_blank"
200
+ rel="noreferrer"
201
+ className="border-border hover:bg-accent inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-medium"
202
+ >
203
+ <ExternalLink size={12} aria-hidden />
204
+ Open live page
205
+ </a>
206
+ )}
207
+ </SeoAccordionItem>
208
+
209
+ {/* ── Publishing ─────────────────────────────────────────────────── */}
210
+ <SeoAccordionItem
211
+ value="publishing"
212
+ title="Publishing"
213
+ badge={
214
+ <SeoAccordionBadge tone={page.status === 'PUBLISHED' ? 'good' : 'neutral'}>
215
+ {page.status}
216
+ </SeoAccordionBadge>
217
+ }
218
+ >
219
+ <dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5 text-[11.5px]">
220
+ <dt className="text-muted-foreground">Last edited</dt>
221
+ <dd data-volatile>{formatStamp(page.updatedAt) ?? '—'}</dd>
222
+ <dt className="text-muted-foreground">Published</dt>
223
+ <dd data-volatile>{formatStamp(page.publishedAt) ?? 'Not yet published'}</dd>
224
+ {page.scheduledAt && (
225
+ <>
226
+ <dt className="text-muted-foreground">Goes live</dt>
227
+ <dd data-volatile>{formatStamp(page.scheduledAt)}</dd>
228
+ </>
229
+ )}
230
+ {page.scheduledUnpublishAt && (
231
+ <>
232
+ <dt className="text-muted-foreground">Comes down</dt>
233
+ <dd data-volatile>{formatStamp(page.scheduledUnpublishAt)}</dd>
234
+ </>
235
+ )}
236
+ </dl>
237
+ <div>
238
+ <p className={LABEL}>Byline</p>
239
+ <AuthorPicker
240
+ value={{ name: page.authorName, id: page.authorId }}
241
+ onChange={(next) => onPageChange({ authorName: next.name, authorId: next.id })}
242
+ brandName={brandName}
243
+ disabled={!canEdit}
244
+ />
245
+ <p className="text-muted-foreground mt-1 text-[11px] leading-snug">
246
+ Shown on the page and in its structured data. Separate from who created the record,
247
+ which is not reassigned.
248
+ </p>
249
+ </div>
250
+
251
+ <p className="text-muted-foreground text-[11px] leading-snug">
252
+ Status is changed with Publish and Schedule in the toolbar, so there is one way to do it
253
+ rather than two that can disagree.
254
+ </p>
255
+ </SeoAccordionItem>
256
+
257
+ {/* ── Search directives ──────────────────────────────────────────── */}
258
+ <SeoAccordionItem
259
+ value="directives"
260
+ title="Search directives"
261
+ badge={
262
+ restricted === 0 ? (
263
+ <SeoAccordionBadge tone="good">OPEN</SeoAccordionBadge>
264
+ ) : (
265
+ <SeoAccordionBadge tone="warn">{restricted} RESTRICTED</SeoAccordionBadge>
266
+ )
267
+ }
268
+ >
269
+ <div className="divide-border divide-y">
270
+ <DirectiveSwitch
271
+ id="pt-index"
272
+ label="Show in search results"
273
+ hint="Off removes the page from Google and every other index."
274
+ checked={directives.index}
275
+ onChange={(v) => set({ index: v })}
276
+ permissiveToken="index"
277
+ restrictiveToken="noindex"
278
+ disabled={!canEdit}
279
+ />
280
+ <DirectiveSwitch
281
+ id="pt-follow"
282
+ label="Follow links on this page"
283
+ hint="Off stops crawlers passing authority to anything this page links to."
284
+ checked={directives.follow}
285
+ onChange={(v) => set({ follow: v })}
286
+ permissiveToken="follow"
287
+ restrictiveToken="nofollow"
288
+ disabled={!canEdit}
289
+ />
290
+ <DirectiveSwitch
291
+ id="pt-snippet"
292
+ label="Allow text snippets"
293
+ hint="Off blocks AI Overviews and chat citations entirely."
294
+ checked={directives.snippet}
295
+ onChange={(v) => set({ snippet: v })}
296
+ permissiveToken="snippet"
297
+ restrictiveToken="nosnippet"
298
+ disabled={!canEdit || !directives.index}
299
+ />
300
+ <DirectiveSwitch
301
+ id="pt-archive"
302
+ label="Allow cached copies"
303
+ checked={directives.archive}
304
+ onChange={(v) => set({ archive: v })}
305
+ permissiveToken="archive"
306
+ restrictiveToken="noarchive"
307
+ disabled={!canEdit || !directives.index}
308
+ />
309
+ </div>
310
+
311
+ <div>
312
+ <label className={LABEL} htmlFor="pt-img-preview">
313
+ Image preview size
314
+ </label>
315
+ <select
316
+ id="pt-img-preview"
317
+ className={INPUT}
318
+ value={directives.maxImagePreview}
319
+ disabled={!canEdit || !directives.index || !directives.snippet}
320
+ onChange={(e) => set({ maxImagePreview: e.target.value as MaxImagePreview })}
321
+ >
322
+ <option value="large">Large — full-width image in results</option>
323
+ <option value="standard">Standard — thumbnail</option>
324
+ <option value="none">None</option>
325
+ </select>
326
+ </div>
327
+
328
+ <div>
329
+ <p className={LABEL}>What ships in the page head</p>
330
+ {/* The literal line, composed by the same function the renderer uses.
331
+ An editor should never have to trust that this UI and the output
332
+ agree — they are the same code. */}
333
+ <code className="bg-muted text-muted-foreground block overflow-x-auto rounded-lg px-2.5 py-2 font-mono text-[10.5px] whitespace-pre">
334
+ {robotsMetaLine(directives)}
335
+ </code>
336
+ </div>
337
+
338
+ {!directives.index && (
339
+ <div className="bg-warning/10 border-warning/30 flex gap-2 rounded-lg border p-2.5">
340
+ <AlertTriangle size={14} className="text-warning mt-px shrink-0" aria-hidden />
341
+ <p className="text-[11.5px] leading-normal">
342
+ <b className="font-semibold">This page is hidden from search.</b> Publishing will not
343
+ make it findable.
344
+ </p>
345
+ </div>
346
+ )}
347
+ </SeoAccordionItem>
348
+
349
+ {/* ── Layout ─────────────────────────────────────────────────────── */}
350
+ <SeoAccordionItem
351
+ value="layout"
352
+ title="Layout"
353
+ badge={<SeoAccordionBadge tone="neutral">LOCKED</SeoAccordionBadge>}
354
+ >
355
+ <dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5 text-[11.5px]">
356
+ <dt className="text-muted-foreground">Template</dt>
357
+ <dd className="flex items-center gap-1.5">
358
+ <Layers size={12} className="text-muted-foreground" aria-hidden />
359
+ {templateName ?? 'Default'}
360
+ </dd>
361
+ <dt className="text-muted-foreground">Schema</dt>
362
+ <dd>{schemaType ?? 'WebPage'}</dd>
363
+ </dl>
364
+ <div className="text-muted-foreground flex gap-2 text-[11px] leading-snug">
365
+ <Lock size={13} className="mt-px shrink-0" aria-hidden />
366
+ <p>
367
+ Layout and styling are design-owned — built in code, not configured here. This tab edits
368
+ where the page lives and how it is indexed.
369
+ </p>
370
+ </div>
371
+ </SeoAccordionItem>
372
+
373
+ {/* ── Danger zone ────────────────────────────────────────────────── */}
374
+ <SeoAccordionItem value="danger" title="Danger zone">
375
+ <button
376
+ type="button"
377
+ onClick={onDuplicate}
378
+ disabled={!canEdit || !page.id}
379
+ className="border-border hover:border-destructive hover:bg-destructive/10 hover:text-destructive flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-xs font-medium transition-colors disabled:opacity-50"
380
+ >
381
+ <Copy size={13} aria-hidden />
382
+ Duplicate page
383
+ </button>
384
+
385
+ {confirmTrash ? (
386
+ <div className="border-destructive/40 bg-destructive/10 space-y-2 rounded-lg border p-2.5">
387
+ <p className="text-[11.5px] leading-normal">
388
+ Move <b className="font-semibold">{page.title || 'this page'}</b> to trash?
389
+ {page.status === 'PUBLISHED' && ' It is live, and will stop being served.'}
390
+ </p>
391
+ <div className="flex gap-2">
392
+ <button
393
+ type="button"
394
+ onClick={onTrash}
395
+ className="bg-destructive text-destructive-foreground rounded-md px-2.5 py-1 text-[11px] font-semibold"
396
+ >
397
+ Move to trash
398
+ </button>
399
+ <button
400
+ type="button"
401
+ onClick={() => setConfirmTrash(false)}
402
+ className="border-border rounded-md border px-2.5 py-1 text-[11px] font-medium"
403
+ >
404
+ Cancel
405
+ </button>
406
+ </div>
407
+ </div>
408
+ ) : (
409
+ <button
410
+ type="button"
411
+ onClick={() => setConfirmTrash(true)}
412
+ disabled={!canEdit || !page.id}
413
+ className="border-border hover:border-destructive hover:bg-destructive/10 hover:text-destructive flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-xs font-medium transition-colors disabled:opacity-50"
414
+ >
415
+ <Trash2 size={13} aria-hidden />
416
+ Move to trash
417
+ </button>
418
+ )}
419
+ </SeoAccordionItem>
420
+ </SeoAccordion>
421
+ )
422
+ }
423
+
424
+ /** `null` rather than a placeholder string, so callers choose their own dash. */
425
+ function formatStamp(value: string | null): string | null {
426
+ if (!value) return null
427
+ const d = new Date(value)
428
+ if (Number.isNaN(d.getTime())) return null
429
+ return d.toLocaleString(undefined, {
430
+ dateStyle: 'medium',
431
+ timeStyle: 'short',
432
+ })
433
+ }
@@ -0,0 +1,76 @@
1
+ 'use client'
2
+
3
+ import { History, Loader2 } from 'lucide-react'
4
+ import { dayLabel, timeLabel, type PageVersion } from '../../lib/version-history.js'
5
+
6
+ export interface ReviewBarProps {
7
+ version: PageVersion
8
+ changedCount: number
9
+ onRestore: () => void
10
+ onBackToCurrent: () => void
11
+ restoring: boolean
12
+ canRestore: boolean
13
+ }
14
+
15
+ /**
16
+ * The strip above the canvas while a past version is on it.
17
+ *
18
+ * Occupies the same slot as the editing tip and the structure notice, and
19
+ * replaces both rather than stacking with them: everything that slot normally
20
+ * says is about editing the current page, which is precisely what you cannot
21
+ * do here. It also has to be unmissable — the canvas below looks like a
22
+ * perfectly ordinary editable page, and the only thing separating "reviewing
23
+ * history" from "editing" is this bar.
24
+ */
25
+ export function ReviewBar({
26
+ version,
27
+ changedCount,
28
+ onRestore,
29
+ onBackToCurrent,
30
+ restoring,
31
+ canRestore,
32
+ }: ReviewBarProps) {
33
+ return (
34
+ <div
35
+ // `status`, not a bare div: entering review mode is a change of what the
36
+ // whole canvas means, and a screen-reader user gets no equivalent of the
37
+ // amber colour that announces it to everyone else.
38
+ role="status"
39
+ className="border-warning/35 bg-warning/10 text-foreground mx-auto mb-3 flex w-fit max-w-full flex-wrap items-center gap-2.5 rounded-full border py-1.5 pr-1.5 pl-3.5 text-[11.5px]"
40
+ >
41
+ <History className="text-warning h-3.5 w-3.5 shrink-0" aria-hidden />
42
+ <span className="min-w-0">
43
+ Viewing{' '}
44
+ <strong className="font-semibold">
45
+ {dayLabel(version.createdAt)} {timeLabel(version.createdAt)}
46
+ </strong>{' '}
47
+ by <strong className="font-semibold">{version.author.name}</strong>
48
+ {changedCount > 0 && (
49
+ <>
50
+ {' · '}
51
+ {changedCount} section{changedCount === 1 ? '' : 's'} changed
52
+ </>
53
+ )}
54
+ </span>
55
+
56
+ {canRestore && (
57
+ <button
58
+ type="button"
59
+ onClick={onRestore}
60
+ disabled={restoring}
61
+ className="border-border bg-card text-foreground hover:bg-accent inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
62
+ >
63
+ {restoring && <Loader2 className="h-3 w-3 animate-spin" aria-hidden />}
64
+ Restore
65
+ </button>
66
+ )}
67
+ <button
68
+ type="button"
69
+ onClick={onBackToCurrent}
70
+ className="border-border bg-card text-foreground hover:bg-accent shrink-0 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors"
71
+ >
72
+ Back to current
73
+ </button>
74
+ </div>
75
+ )
76
+ }