@actuate-media/cms-admin 0.75.0 → 0.76.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 (76) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/__tests__/layout/sidebar-plugin-nav.test.d.ts +2 -0
  3. package/dist/__tests__/layout/sidebar-plugin-nav.test.d.ts.map +1 -0
  4. package/dist/__tests__/layout/sidebar-plugin-nav.test.js +46 -0
  5. package/dist/__tests__/layout/sidebar-plugin-nav.test.js.map +1 -0
  6. package/dist/__tests__/lib/post-editor-service.test.js +2 -0
  7. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
  8. package/dist/__tests__/router/navigation-blocker.test.d.ts +2 -0
  9. package/dist/__tests__/router/navigation-blocker.test.d.ts.map +1 -0
  10. package/dist/__tests__/router/navigation-blocker.test.js +69 -0
  11. package/dist/__tests__/router/navigation-blocker.test.js.map +1 -0
  12. package/dist/__tests__/views/article-body-section.render.test.d.ts +2 -0
  13. package/dist/__tests__/views/article-body-section.render.test.d.ts.map +1 -0
  14. package/dist/__tests__/views/article-body-section.render.test.js +24 -0
  15. package/dist/__tests__/views/article-body-section.render.test.js.map +1 -0
  16. package/dist/__tests__/views/editor-top-bar.render.test.js +12 -0
  17. package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
  18. package/dist/__tests__/views/post-fields-modal.render.test.js +2 -0
  19. package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -1
  20. package/dist/actuate-admin.css +1 -1
  21. package/dist/layout/Sidebar.d.ts.map +1 -1
  22. package/dist/layout/Sidebar.js +17 -0
  23. package/dist/layout/Sidebar.js.map +1 -1
  24. package/dist/lib/page-editor-service.d.ts +2 -0
  25. package/dist/lib/page-editor-service.d.ts.map +1 -1
  26. package/dist/lib/page-editor-service.js +4 -0
  27. package/dist/lib/page-editor-service.js.map +1 -1
  28. package/dist/lib/post-editor-service.d.ts +3 -0
  29. package/dist/lib/post-editor-service.d.ts.map +1 -1
  30. package/dist/lib/post-editor-service.js +4 -0
  31. package/dist/lib/post-editor-service.js.map +1 -1
  32. package/dist/router/index.d.ts +13 -0
  33. package/dist/router/index.d.ts.map +1 -1
  34. package/dist/router/index.js +49 -0
  35. package/dist/router/index.js.map +1 -1
  36. package/dist/views/DocumentEdit.d.ts.map +1 -1
  37. package/dist/views/DocumentEdit.js +4 -9
  38. package/dist/views/DocumentEdit.js.map +1 -1
  39. package/dist/views/page-builder/PageBuilder.d.ts.map +1 -1
  40. package/dist/views/page-builder/PageBuilder.js +3 -0
  41. package/dist/views/page-builder/PageBuilder.js.map +1 -1
  42. package/dist/views/page-editor/EditorTopBar.d.ts +7 -1
  43. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  44. package/dist/views/page-editor/EditorTopBar.js +35 -4
  45. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  46. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  47. package/dist/views/page-editor/PageSectionEditor.js +38 -13
  48. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  49. package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -1
  50. package/dist/views/page-editor/sections/ArticleBodySection.js +1 -1
  51. package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
  52. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  53. package/dist/views/post-editor/PostSectionEditor.js +38 -12
  54. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  55. package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -1
  56. package/dist/views/post-editor/PostTemplateEditor.js +3 -10
  57. package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
  58. package/package.json +4 -3
  59. package/src/__tests__/layout/sidebar-plugin-nav.test.ts +50 -0
  60. package/src/__tests__/lib/post-editor-service.test.ts +2 -0
  61. package/src/__tests__/router/navigation-blocker.test.ts +80 -0
  62. package/src/__tests__/views/article-body-section.render.test.tsx +32 -0
  63. package/src/__tests__/views/editor-top-bar.render.test.tsx +24 -0
  64. package/src/__tests__/views/post-fields-modal.render.test.tsx +2 -0
  65. package/src/layout/Sidebar.tsx +19 -0
  66. package/src/lib/page-editor-service.ts +8 -0
  67. package/src/lib/post-editor-service.ts +8 -0
  68. package/src/router/index.ts +61 -0
  69. package/src/styles/theme.css +91 -0
  70. package/src/views/DocumentEdit.tsx +4 -8
  71. package/src/views/page-builder/PageBuilder.tsx +4 -0
  72. package/src/views/page-editor/EditorTopBar.tsx +69 -0
  73. package/src/views/page-editor/PageSectionEditor.tsx +58 -10
  74. package/src/views/page-editor/sections/ArticleBodySection.tsx +1 -4
  75. package/src/views/post-editor/PostSectionEditor.tsx +58 -10
  76. package/src/views/post-editor/PostTemplateEditor.tsx +3 -9
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
4
4
  import { AlertTriangle, ArrowLeft, Layout, Loader2, Save } from 'lucide-react';
5
5
  import { toast } from 'sonner';
6
6
  import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
7
+ import { useNavigationBlocker } from '../../router/index.js';
7
8
  import { ErrorBoundary } from '../../components/ErrorBoundary.js';
8
9
  import { addSection, createSection, duplicateSection, fetchTemplateForEditor, reorderSections, removeSection, saveTemplate, toggleSectionVisibility, updateSectionContent, updateSectionMeta, updateSectionSettings, validateTemplate, } from '../../lib/post-editor-service.js';
9
10
  import { DEFAULT_VIEWPORT } from '../page-editor/viewports.js';
@@ -64,16 +65,8 @@ export function PostTemplateEditor({ postType, config, session, onNavigate, }) {
64
65
  cancelled = true;
65
66
  };
66
67
  }, [postType]);
67
- useEffect(() => {
68
- if (!dirty)
69
- return;
70
- function onBeforeUnload(e) {
71
- e.preventDefault();
72
- e.returnValue = '';
73
- }
74
- window.addEventListener('beforeunload', onBeforeUnload);
75
- return () => window.removeEventListener('beforeunload', onBeforeUnload);
76
- }, [dirty]);
68
+ // Unsaved-changes guard (client-side navigation + browser unload).
69
+ useNavigationBlocker(dirty);
77
70
  const mutateSections = useCallback((fn) => {
78
71
  setTemplate((prev) => (prev ? { ...prev, sections: fn(prev.sections) } : prev));
79
72
  setDirty(true);
@@ -1 +1 @@
1
- {"version":3,"file":"PostTemplateEditor.js","sourceRoot":"","sources":["../../../src/views/post-editor/PostTemplateEditor.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PostTemplateEditor.js","sourceRoot":"","sources":["../../../src/views/post-editor/PostTemplateEditor.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAA;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAA;AACjE,OAAO,EACL,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,GAGjB,MAAM,kCAAkC,CAAA;AAGzC,OAAO,EAAE,gBAAgB,EAAmB,MAAM,6BAA6B,CAAA;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAStD,SAAS,aAAa,CAAC,OAAY;IACjC,MAAM,IAAI,GAAuB,OAAO,EAAE,IAAI,EAAE,IAAI,CAAA;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,cAAc,GAAsB;IACxC,KAAK,EAAE,mBAAmB;IAC1B,OAAO,EAAE,iEAAiE;IAC1E,IAAI,EAAE,2IAA2I;IACjJ,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;IACrC,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,6CAA6C,EAAE;CACpF,CAAA;AAED,MAAM,UAAU,kBAAkB,CAAC,EACjC,QAAQ,EACR,MAAM,EACN,OAAO,EACP,UAAU,GACc;IACxB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAA4B,IAAI,CAAC,CAAA;IACzE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAE/D,mEAAmE;IACnE,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAEtC,IAAI,CAAC,CAAA;IACP,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAa,gBAAgB,CAAC,CAAA;IACtE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE3C,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACzE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAEnE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAChE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE;QAC7B,MAAM,GAAG,GAAG,MAAM,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAA;QAC3C,OAAO,GAAG,EAAE,MAAM,EAAE,QAAQ,IAAI,QAAQ,CAAA;IAC1C,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IAEtB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,YAAY,CAAC,IAAI,CAAC,CAAA;QAClB,sBAAsB,CAAC,QAAQ,CAAC;aAC7B,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;YACV,IAAI,CAAC,SAAS;gBAAE,WAAW,CAAC,CAAC,CAAC,CAAA;QAChC,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,IAAI,CAAC,SAAS;gBAAE,YAAY,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAA;QAC9F,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,SAAS;gBAAE,UAAU,CAAC,KAAK,CAAC,CAAA;QACnC,CAAC,CAAC,CAAA;QACJ,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAA;QAClB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEd,mEAAmE;IACnE,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAE3B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,EAAgF,EAAE,EAAE;QACnF,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QAC/E,QAAQ,CAAC,IAAI,CAAC,CAAA;IAChB,CAAC,EACD,EAAE,CACH,CAAA;IAED,MAAM,kBAAkB,GAAG,WAAW,CAAC,CAAC,KAAgC,EAAE,EAAE;QAC1E,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QACxF,QAAQ,CAAC,IAAI,CAAC,CAAA;IAChB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,IAAmB,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;QACnC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;QAC7C,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAA;QAChD,UAAU,CAAC,KAAK,CAAC,CAAA;IACnB,CAAC,EACD,CAAC,cAAc,CAAC,CACjB,CAAA;IAED,MAAM,iBAAiB,GAAG,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAE3E,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACxC,IAAI,CAAC,QAAQ;YAAE,OAAM;QACrB,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;QACzC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAA;YAC9B,OAAM;QACR,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAA;YAC1C,WAAW,CAAC,KAAK,CAAC,CAAA;YAClB,QAAQ,CAAC,KAAK,CAAC,CAAA;YACf,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,KAAK,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAA;QAC7E,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,KAAK,CAAC,CAAA;QAClB,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEd,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,IAAY,EAAE,EAAE;QACf,IAAI,KAAK;YAAE,cAAc,CAAC,IAAI,CAAC,CAAA;;YAC1B,UAAU,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC,EACD,CAAC,KAAK,EAAE,UAAU,CAAC,CACpB,CAAA;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CACL,eAAK,SAAS,EAAC,uDAAuD,aACpE,KAAC,OAAO,IAAC,SAAS,EAAC,4CAA4C,wBAAe,EAC9E,eAAM,SAAS,EAAC,SAAS,8CAAgC,IACrD,CACP,CAAA;IACH,CAAC;IAED,IAAI,SAAS,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC3B,OAAO,CACL,cAAK,SAAS,EAAC,2DAA2D,YACxE,eAAK,SAAS,EAAC,+FAA+F,aAC5G,KAAC,aAAa,IAAC,SAAS,EAAC,2BAA2B,EAAC,IAAI,EAAE,EAAE,wBAAgB,EAC7E,eAAK,SAAS,EAAC,SAAS,aACtB,YAAG,SAAS,EAAC,qCAAqC,wCAA4B,EAC9E,YAAG,SAAS,EAAC,oCAAoC,YAAE,SAAS,IAAI,YAAY,GAAK,EACjF,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EACzC,SAAS,EAAC,uDAAuD,mCAG1D,IACL,IACF,GACF,CACP,CAAA;IACH,CAAC;IAED,MAAM,eAAe,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,iBAAiB,CAAC,IAAI,IAAI,CAAA;IAEzF,OAAO,CACL,MAAC,aAAa,eACZ,eAAK,SAAS,EAAC,oDAAoD,aAEjE,kBAAQ,SAAS,EAAC,2FAA2F,aAC3G,6BAAgB,YAAY,EAAC,SAAS,EAAC,2CAA2C,aAChF,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,cAAc,CAAC,EAC9C,SAAS,EAAC,uFAAuF,aAEjG,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,wBAAe,kBAEtC,EACT,eAAM,SAAS,EAAC,uBAAuB,uCAEhC,EACP,gBAAM,SAAS,EAAC,sCAAsC,aAAE,SAAS,iBAAiB,EACjF,KAAK,IAAI,CACR,eAAM,SAAS,EAAC,oCAAoC,eAAW,QAAQ,gCAEhE,CACR,IACG,EAEN,eAAK,SAAS,EAAC,yBAAyB,aACtC,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAC9C,SAAS,EAAE,wGACT,QAAQ,EAAE,IAAI,KAAK,QAAQ;4CACzB,CAAC,CAAC,0CAA0C;4CAC5C,CAAC,CAAC,+CACN,EAAE,aAEF,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,wBAAe,cAEnC,EACT,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,CAAC,OAAO,IAAI,MAAM,IAAI,CAAC,KAAK,EACtC,SAAS,EAAC,wMAAwM,aAEjN,MAAM,CAAC,CAAC,CAAC,CACR,KAAC,OAAO,IAAC,SAAS,EAAC,0BAA0B,wBAAe,CAC7D,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,SAAS,EAAC,aAAa,wBAAe,CAC7C,YAEM,IACL,IACC,EAET,eAAK,SAAS,EAAC,sCAAsC,aACnD,KAAC,aAAa,IACZ,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAC3B,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EACtD,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAClE,kBAAkB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EACjF,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EACnE,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC,EAC9C,KAAK,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAC7B,EAEF,cAAK,SAAS,EAAC,wBAAwB,YACrC,KAAC,gBAAgB,IACf,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM,EACvB,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EACtD,YAAY,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GACpC,GACE,EAEL,QAAQ,EAAE,IAAI,KAAK,QAAQ,IAAI,CAC9B,KAAC,eAAe,IACd,MAAM,EAAE,QAAQ,CAAC,MAAM,EACvB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAChC,QAAQ,EAAE,kBAAkB,GAC5B,CACH,EAEA,eAAe,IAAI,CAClB,KAAC,gBAAgB,IACf,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAChC,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,CACzB,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,CAAC,CAAC,EAAE,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,EAE3E,gBAAgB,EAAE,CAAC,KAA+B,EAAE,EAAE,CACpD,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,CAAC,EAAE,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,EAE5E,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CACtB,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,EAExE,kBAAkB,EAAE,GAAG,EAAE,CACvB,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,CAAC,EAAE,eAAe,CAAC,EAAE,CAAC,CAAC,GAEvE,CACH,IACG,IACF,EAEN,KAAC,eAAe,IACd,IAAI,EAAE,OAAO,EACb,KAAK,EAAC,MAAM,EACZ,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAChC,KAAK,EAAE,SAAS,GAChB,EAEF,KAAC,aAAa,IACZ,IAAI,EAAE,cAAc,KAAK,IAAI,EAC7B,OAAO,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,EACtC,SAAS,EAAE,GAAG,EAAE;oBACd,IAAI,cAAc,EAAE,CAAC;wBACnB,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAA;wBACvD,IAAI,iBAAiB,KAAK,cAAc;4BAAE,WAAW,CAAC,IAAI,CAAC,CAAA;oBAC7D,CAAC;oBACD,iBAAiB,CAAC,IAAI,CAAC,CAAA;gBACzB,CAAC,EACD,KAAK,EAAC,iBAAiB,EACvB,WAAW,EAAC,qFAAqF,EACjG,YAAY,EAAC,QAAQ,EACrB,WAAW,SACX,EAEF,KAAC,aAAa,IACZ,IAAI,EAAE,WAAW,KAAK,IAAI,EAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EACnC,SAAS,EAAE,GAAG,EAAE;oBACd,MAAM,MAAM,GAAG,WAAW,CAAA;oBAC1B,cAAc,CAAC,IAAI,CAAC,CAAA;oBACpB,QAAQ,CAAC,KAAK,CAAC,CAAA;oBACf,IAAI,MAAM;wBAAE,UAAU,CAAC,MAAM,CAAC,CAAA;gBAChC,CAAC,EACD,KAAK,EAAC,0BAA0B,EAChC,WAAW,EAAC,8DAA8D,EAC1E,YAAY,EAAC,OAAO,EACpB,WAAW,EAAC,MAAM,EAClB,WAAW,SACX,IACY,CACjB,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@actuate-media/cms-admin",
3
- "version": "0.75.0",
3
+ "version": "0.76.1",
4
+ "license": "MIT",
4
5
  "repository": {
5
6
  "type": "git",
6
7
  "url": "https://github.com/actuate-media/actuatecms.git",
@@ -95,8 +96,8 @@
95
96
  "tailwindcss": "^4.0.0",
96
97
  "typescript": "^5.7.0",
97
98
  "vitest": "^4.1.0",
98
- "@actuate-media/cms-core": "0.90.1",
99
- "@actuate-media/component-blocks": "0.2.1"
99
+ "@actuate-media/cms-core": "0.91.0",
100
+ "@actuate-media/component-blocks": "0.2.2"
100
101
  },
101
102
  "scripts": {
102
103
  "build": "tsc --project tsconfig.json && npx @tailwindcss/cli -i src/styles/build-input.css -o dist/actuate-admin.css --minify",
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { buildNavItems } from '../../layout/Sidebar.js'
3
+
4
+ // Plugin adminNavItems (collected onto `_pluginNavItems` by cms-core's
5
+ // applyPlugins) must surface in the admin sidebar, without doubling a built-in
6
+ // destination or one of the plugin's own collections.
7
+ const baseCollections = {
8
+ collections: {
9
+ pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, type: 'page' },
10
+ },
11
+ }
12
+
13
+ describe('buildNavItems — plugin nav items', () => {
14
+ it('adds a plugin-contributed custom nav link', () => {
15
+ const items = buildNavItems({
16
+ ...baseCollections,
17
+ _pluginNavItems: [{ label: 'Redirects', icon: 'arrow-right-left', path: '/redirects' }],
18
+ })
19
+ const redirects = items.find((i) => i.path === '/redirects')
20
+ expect(redirects).toBeDefined()
21
+ expect(redirects?.label).toBe('Redirects')
22
+ expect(redirects?.group).toBe('Content')
23
+ })
24
+
25
+ it('does not double a built-in destination path', () => {
26
+ const items = buildNavItems({
27
+ ...baseCollections,
28
+ _pluginNavItems: [{ label: 'Media Library', icon: 'image', path: '/media' }],
29
+ })
30
+ expect(items.filter((i) => i.path === '/media')).toHaveLength(1)
31
+ })
32
+
33
+ it('falls back to a default icon for an unknown icon string and still renders', () => {
34
+ const items = buildNavItems({
35
+ ...baseCollections,
36
+ _pluginNavItems: [{ label: 'Custom', icon: 'no-such-icon', path: '/custom' }],
37
+ })
38
+ const custom = items.find((i) => i.path === '/custom')
39
+ expect(custom).toBeDefined()
40
+ expect(custom?.icon).toBeTruthy()
41
+ })
42
+
43
+ it('ignores malformed plugin nav entries', () => {
44
+ const items = buildNavItems({
45
+ ...baseCollections,
46
+ _pluginNavItems: [{ label: 'No path' }, null, { path: '/ok', label: 'Ok', icon: 'menu' }],
47
+ })
48
+ expect(items.find((i) => i.path === '/ok')).toBeDefined()
49
+ })
50
+ })
@@ -32,6 +32,8 @@ function basePost(over: Partial<EditorPostT> = {}): EditorPostT {
32
32
  tags: [],
33
33
  status: 'DRAFT',
34
34
  publishDate: null,
35
+ scheduledAt: null,
36
+ scheduledUnpublishAt: null,
35
37
  sections: [],
36
38
  seoTitle: '',
37
39
  seoDescription: '',
@@ -0,0 +1,80 @@
1
+ // @vitest-environment happy-dom
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import { act, renderHook } from '@testing-library/react'
4
+
5
+ import {
6
+ hasBlockingNavigation,
7
+ NAVIGATION_CONFIRM_MESSAGE,
8
+ registerNavigationBlocker,
9
+ useAdminRouter,
10
+ useNavigationBlocker,
11
+ } from '../../router/index.js'
12
+
13
+ // Guards against silent data loss: an in-app route change (Cancel, sidebar /
14
+ // breadcrumb links, command palette) must confirm before discarding an editor's
15
+ // unsaved changes — beforeunload only covers hard reloads, never SPA navigation.
16
+ describe('navigation blocker', () => {
17
+ let confirmMock: ReturnType<typeof vi.fn>
18
+
19
+ beforeEach(() => {
20
+ // Reset the URL so each test's router mounts at '/' (pushState from a prior
21
+ // test would otherwise leak into window.location and skew currentPath).
22
+ window.history.replaceState(null, '', '/admin')
23
+ confirmMock = vi.fn(() => true)
24
+ ;(window as unknown as { confirm: typeof window.confirm }).confirm =
25
+ confirmMock as unknown as typeof window.confirm
26
+ })
27
+
28
+ afterEach(() => {
29
+ // Ensure no blocker leaks into the next test.
30
+ expect(hasBlockingNavigation()).toBe(false)
31
+ })
32
+
33
+ it('navigates freely and never prompts when nothing is dirty', () => {
34
+ const { result } = renderHook(() => useAdminRouter('/admin', '/'))
35
+ act(() => result.current.navigate('/collections/posts'))
36
+ expect(result.current.currentPath).toBe('/collections/posts')
37
+ expect(confirmMock).not.toHaveBeenCalled()
38
+ })
39
+
40
+ it('aborts the navigation when a dirty editor is registered and the user cancels', () => {
41
+ confirmMock.mockReturnValue(false)
42
+ const unregister = registerNavigationBlocker(() => true)
43
+ try {
44
+ const { result } = renderHook(() => useAdminRouter('/admin', '/'))
45
+ act(() => result.current.navigate('/collections/posts'))
46
+ expect(confirmMock).toHaveBeenCalledWith(NAVIGATION_CONFIRM_MESSAGE)
47
+ expect(result.current.currentPath).toBe('/')
48
+ } finally {
49
+ unregister()
50
+ }
51
+ })
52
+
53
+ it('proceeds with the navigation when the user confirms leaving', () => {
54
+ confirmMock.mockReturnValue(true)
55
+ const unregister = registerNavigationBlocker(() => true)
56
+ try {
57
+ const { result } = renderHook(() => useAdminRouter('/admin', '/'))
58
+ act(() => result.current.navigate('/settings'))
59
+ expect(confirmMock).toHaveBeenCalledOnce()
60
+ expect(result.current.currentPath).toBe('/settings')
61
+ } finally {
62
+ unregister()
63
+ }
64
+ })
65
+
66
+ it('useNavigationBlocker only blocks while dirty and cleans up on unmount', () => {
67
+ const { rerender, unmount } = renderHook(({ dirty }) => useNavigationBlocker(dirty), {
68
+ initialProps: { dirty: false },
69
+ })
70
+ expect(hasBlockingNavigation()).toBe(false)
71
+ rerender({ dirty: true })
72
+ expect(hasBlockingNavigation()).toBe(true)
73
+ rerender({ dirty: false })
74
+ expect(hasBlockingNavigation()).toBe(false)
75
+ rerender({ dirty: true })
76
+ expect(hasBlockingNavigation()).toBe(true)
77
+ unmount()
78
+ expect(hasBlockingNavigation()).toBe(false)
79
+ })
80
+ })
@@ -0,0 +1,32 @@
1
+ // @vitest-environment happy-dom
2
+ import { render, screen } from '@testing-library/react'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import { ArticleBodySection } from '../../views/page-editor/sections/ArticleBodySection.js'
6
+ import type { PageSection } from '../../lib/page-editor-service.js'
7
+
8
+ function section(content: Record<string, unknown> = {}): PageSection {
9
+ return {
10
+ id: 's1',
11
+ sectionType: 'article-body',
12
+ name: 'Article Body',
13
+ visible: true,
14
+ content,
15
+ settings: {},
16
+ }
17
+ }
18
+
19
+ describe('ArticleBodySection', () => {
20
+ it('renders post body HTML with rich-text preview class', () => {
21
+ const { container } = render(
22
+ <ArticleBodySection
23
+ section={section({ source: 'field' })}
24
+ context={{ body: '<h2>What is Lorem Ipsum?</h2><p>Body copy.</p>' }}
25
+ />,
26
+ )
27
+
28
+ expect(screen.getByRole('heading', { level: 2, name: 'What is Lorem Ipsum?' })).toBeTruthy()
29
+ expect(container.querySelector('.actuate-rich-text')).toBeTruthy()
30
+ expect(container.querySelector('.prose')).toBeNull()
31
+ })
32
+ })
@@ -62,4 +62,28 @@ describe('EditorTopBar publish gating', () => {
62
62
  fireEvent.click(screen.getByRole('button', { name: 'Publish' }))
63
63
  expect(onPublish).toHaveBeenCalledOnce()
64
64
  })
65
+
66
+ it('shows Schedule when onSchedule is provided and calls it when ready', () => {
67
+ const onSchedule = vi.fn()
68
+ render(
69
+ <EditorTopBar
70
+ {...baseProps}
71
+ publishReady
72
+ scheduleReady
73
+ onSchedule={onSchedule}
74
+ scheduledAt="2026-07-11T16:00:00.000Z"
75
+ />,
76
+ )
77
+ const btn = screen.getByRole('button', { name: 'Reschedule publish' })
78
+ expect((btn as HTMLButtonElement).disabled).toBe(false)
79
+ fireEvent.click(btn)
80
+ expect(onSchedule).toHaveBeenCalledOnce()
81
+ })
82
+
83
+ it('disables Schedule when scheduleReady is false', () => {
84
+ render(<EditorTopBar {...baseProps} publishReady scheduleReady={false} onSchedule={vi.fn()} />)
85
+ expect(
86
+ (screen.getByRole('button', { name: 'Schedule publish' }) as HTMLButtonElement).disabled,
87
+ ).toBe(true)
88
+ })
65
89
  })
@@ -44,6 +44,8 @@ function basePost(over: Partial<ModalProps['post']> = {}): ModalProps['post'] {
44
44
  tags: [],
45
45
  status: 'DRAFT',
46
46
  publishDate: null,
47
+ scheduledAt: null,
48
+ scheduledUnpublishAt: null,
47
49
  sections: [],
48
50
  seoTitle: '',
49
51
  seoDescription: '',
@@ -876,6 +876,25 @@ export function buildNavItems(config: any): NavItem[] {
876
876
  }
877
877
  for (const bucket of groupBuckets.values()) items.push(...bucket)
878
878
 
879
+ // Plugin-contributed custom nav links (PluginDefinition.adminNavItems,
880
+ // collected onto `_pluginNavItems` by cms-core's applyPlugins). Deduped by
881
+ // path against everything already present so a plugin can't double a built-in
882
+ // destination or one of its own collections.
883
+ const pluginNav: Array<{ label?: string; icon?: string; path?: string }> = Array.isArray(
884
+ config?._pluginNavItems,
885
+ )
886
+ ? config._pluginNavItems
887
+ : []
888
+ for (const item of pluginNav) {
889
+ if (!item?.path || items.some((n) => n.path === item.path)) continue
890
+ items.push({
891
+ label: item.label ?? item.path,
892
+ path: item.path,
893
+ icon: (item.icon && ICON_MAP[item.icon]) || FileText,
894
+ group: 'Content',
895
+ })
896
+ }
897
+
879
898
  // Settings destinations — always last. Users + API Keys now live inside
880
899
  // the Settings view as tabs (see Settings.tsx), so only Settings is anchored
881
900
  // here.
@@ -75,6 +75,8 @@ export interface EditorPage {
75
75
  seoDescription: string
76
76
  publishedAt: string | null
77
77
  updatedAt: string | null
78
+ scheduledAt: string | null
79
+ scheduledUnpublishAt: string | null
78
80
  /**
79
81
  * True when the document was authored in the visual Page Builder (it carries
80
82
  * a `_layout` tree). Such a page's `sections` are *compiled* from the layout
@@ -176,6 +178,8 @@ interface DocumentApiRow {
176
178
  slug: string | null
177
179
  status: PageStatus
178
180
  publishedAt: string | null
181
+ scheduledAt?: string | null
182
+ scheduledUnpublishAt?: string | null
179
183
  updatedAt: string | null
180
184
  data: Record<string, unknown> | null
181
185
  /** The page-builder tree, lifted out of `data` onto the wire envelope by the API. */
@@ -207,6 +211,8 @@ function rowToEditorPage(doc: DocumentApiRow): EditorPage {
207
211
  seoDescription: typeof data.metaDescription === 'string' ? data.metaDescription : '',
208
212
  publishedAt: doc.publishedAt ?? null,
209
213
  updatedAt: doc.updatedAt ?? null,
214
+ scheduledAt: doc.scheduledAt ?? null,
215
+ scheduledUnpublishAt: doc.scheduledUnpublishAt ?? null,
210
216
  builderOwned,
211
217
  }
212
218
  }
@@ -302,6 +308,8 @@ export function emptyPage(parentPageId: string | null = null): EditorPage {
302
308
  seoDescription: '',
303
309
  publishedAt: null,
304
310
  updatedAt: null,
311
+ scheduledAt: null,
312
+ scheduledUnpublishAt: null,
305
313
  builderOwned: false,
306
314
  }
307
315
  }
@@ -78,6 +78,9 @@ export interface EditorPost {
78
78
  tags: string[]
79
79
  status: PostStatus
80
80
  publishDate: string | null
81
+ /** When set and status is SCHEDULED, the cron publishes at this time. */
82
+ scheduledAt: string | null
83
+ scheduledUnpublishAt: string | null
81
84
  sections: PageSection[]
82
85
  seoTitle: string
83
86
  seoDescription: string
@@ -189,6 +192,7 @@ interface DocumentApiRow {
189
192
  status: PostStatus
190
193
  publishedAt: string | null
191
194
  scheduledAt?: string | null
195
+ scheduledUnpublishAt?: string | null
192
196
  updatedAt: string | null
193
197
  data: Record<string, unknown> | null
194
198
  }
@@ -235,6 +239,8 @@ function rowToEditorPost(postType: string, doc: DocumentApiRow): EditorPost {
235
239
  tags: parseTags(data.tags),
236
240
  status: doc.status ?? 'DRAFT',
237
241
  publishDate: (data.publishDate as string) ?? doc.scheduledAt ?? null,
242
+ scheduledAt: doc.scheduledAt ?? null,
243
+ scheduledUnpublishAt: doc.scheduledUnpublishAt ?? null,
238
244
  // Preserve unknown (externally-managed) sections so the editor can show
239
245
  // them read-only and round-trip them on save instead of dropping them.
240
246
  sections: coerceSections(data.sections, { onUnknown: 'preserve' }),
@@ -417,6 +423,8 @@ export async function emptyPostFromTemplate(postType: string): Promise<EditorPos
417
423
  tags: [],
418
424
  status: 'DRAFT',
419
425
  publishDate: null,
426
+ scheduledAt: null,
427
+ scheduledUnpublishAt: null,
420
428
  sections,
421
429
  seoTitle: '',
422
430
  seoDescription: '',
@@ -23,6 +23,61 @@ export function parseRouteQuery(path: string): URLSearchParams {
23
23
  return new URLSearchParams(path.split('#')[0]!.split('?')[1] ?? '')
24
24
  }
25
25
 
26
+ /** Confirmation shown when navigating away from an editor with unsaved changes. */
27
+ export const NAVIGATION_CONFIRM_MESSAGE =
28
+ 'You have unsaved changes that will be lost. Leave this page anyway?'
29
+
30
+ // Module-level registry of "is there unsaved work?" predicates. Editors register
31
+ // via useNavigationBlocker; the SPA router's navigate() consults them before an
32
+ // in-app route change so Cancel / sidebar / breadcrumb / command-palette links
33
+ // can't silently discard an editor's unsaved changes (beforeunload only covers
34
+ // hard reloads, never client-side navigation).
35
+ type BlockerPredicate = () => boolean
36
+ const navigationBlockers = new Set<BlockerPredicate>()
37
+
38
+ export function registerNavigationBlocker(predicate: BlockerPredicate): () => void {
39
+ navigationBlockers.add(predicate)
40
+ return () => {
41
+ navigationBlockers.delete(predicate)
42
+ }
43
+ }
44
+
45
+ /** True when any registered editor currently has unsaved changes. */
46
+ export function hasBlockingNavigation(): boolean {
47
+ for (const predicate of navigationBlockers) {
48
+ try {
49
+ if (predicate()) return true
50
+ } catch {
51
+ // A misbehaving predicate must not wedge navigation.
52
+ }
53
+ }
54
+ return false
55
+ }
56
+
57
+ /**
58
+ * Register an unsaved-changes guard for an editor. While `isDirty` is true, an
59
+ * in-app navigation prompts for confirmation (via navigate()) and a browser
60
+ * unload/refresh shows the native "leave site?" prompt. The registration and
61
+ * the beforeunload listener are torn down on unmount.
62
+ */
63
+ export function useNavigationBlocker(isDirty: boolean): void {
64
+ const dirtyRef = useRef(isDirty)
65
+ dirtyRef.current = isDirty
66
+
67
+ useEffect(() => registerNavigationBlocker(() => dirtyRef.current), [])
68
+
69
+ useEffect(() => {
70
+ if (!isDirty || typeof window === 'undefined') return
71
+ const onBeforeUnload = (e: BeforeUnloadEvent) => {
72
+ e.preventDefault()
73
+ // Required by Chrome/Safari to actually trigger the native prompt.
74
+ e.returnValue = ''
75
+ }
76
+ window.addEventListener('beforeunload', onBeforeUnload)
77
+ return () => window.removeEventListener('beforeunload', onBeforeUnload)
78
+ }, [isDirty])
79
+ }
80
+
26
81
  export function useAdminRouter(basePath = '/admin', serverPath = '/') {
27
82
  const [currentPath, setCurrentPath] = useState(serverPath)
28
83
  const baseRef = useRef(basePath)
@@ -52,6 +107,12 @@ export function useAdminRouter(basePath = '/admin', serverPath = '/') {
52
107
 
53
108
  const navigate = useCallback((path: string) => {
54
109
  const normalizedPath = path.startsWith('/') ? path : `/${path}`
110
+
111
+ // Guard against discarding unsaved editor work on client-side navigation.
112
+ if (hasBlockingNavigation() && typeof window !== 'undefined') {
113
+ if (!window.confirm(NAVIGATION_CONFIRM_MESSAGE)) return
114
+ }
115
+
55
116
  setCurrentPath(normalizedPath)
56
117
 
57
118
  const fullUrl = normalizedPath === '/' ? baseRef.current : `${baseRef.current}${normalizedPath}`
@@ -224,6 +224,97 @@
224
224
  letter-spacing: revert-layer;
225
225
  line-height: revert-layer;
226
226
  }
227
+
228
+ /*
229
+ * Rich-text HTML in the canvas preview (article body, etc.). The TipTap
230
+ * editor styles headings in `.ProseMirror`; the canvas cannot rely on
231
+ * `prose` because @tailwindcss/typography is not bundled, and the revert
232
+ * above strips admin heading scales without substituting site typography.
233
+ */
234
+ .actuate-admin .actuate-canvas .actuate-rich-text {
235
+ color: var(--foreground);
236
+ font-size: 1rem;
237
+ line-height: 1.625;
238
+ }
239
+
240
+ .actuate-admin .actuate-canvas .actuate-rich-text :is(h1, h2, h3, h4, h5, h6) {
241
+ color: var(--foreground);
242
+ font-weight: 600;
243
+ line-height: 1.5;
244
+ margin-top: 1.25em;
245
+ margin-bottom: 0.5em;
246
+ }
247
+
248
+ .actuate-admin .actuate-canvas .actuate-rich-text h1 {
249
+ font-size: 2em;
250
+ }
251
+
252
+ .actuate-admin .actuate-canvas .actuate-rich-text h2 {
253
+ font-size: 1.5em;
254
+ }
255
+
256
+ .actuate-admin .actuate-canvas .actuate-rich-text h3 {
257
+ font-size: 1.25em;
258
+ }
259
+
260
+ .actuate-admin .actuate-canvas .actuate-rich-text h4 {
261
+ font-size: 1.125em;
262
+ }
263
+
264
+ .actuate-admin .actuate-canvas .actuate-rich-text h5 {
265
+ font-size: 1em;
266
+ }
267
+
268
+ .actuate-admin .actuate-canvas .actuate-rich-text h6 {
269
+ font-size: 0.875em;
270
+ }
271
+
272
+ .actuate-admin .actuate-canvas .actuate-rich-text p {
273
+ margin-top: 0.75em;
274
+ margin-bottom: 0.75em;
275
+ }
276
+
277
+ .actuate-admin .actuate-canvas .actuate-rich-text :is(ul, ol) {
278
+ margin-top: 0.75em;
279
+ margin-bottom: 0.75em;
280
+ padding-left: 1.5rem;
281
+ }
282
+
283
+ .actuate-admin .actuate-canvas .actuate-rich-text ul {
284
+ list-style-type: disc;
285
+ }
286
+
287
+ .actuate-admin .actuate-canvas .actuate-rich-text ol {
288
+ list-style-type: decimal;
289
+ }
290
+
291
+ .actuate-admin .actuate-canvas .actuate-rich-text li {
292
+ margin-top: 0.25em;
293
+ margin-bottom: 0.25em;
294
+ }
295
+
296
+ .actuate-admin .actuate-canvas .actuate-rich-text a {
297
+ color: var(--primary);
298
+ text-decoration: underline;
299
+ }
300
+
301
+ .actuate-admin .actuate-canvas .actuate-rich-text :is(strong, b) {
302
+ font-weight: 600;
303
+ }
304
+
305
+ .actuate-admin .actuate-canvas .actuate-rich-text blockquote {
306
+ border-left: 3px solid var(--border);
307
+ margin: 1em 0;
308
+ padding-left: 1em;
309
+ color: var(--muted-foreground);
310
+ }
311
+
312
+ .actuate-admin .actuate-canvas .actuate-rich-text img {
313
+ max-width: 100%;
314
+ height: auto;
315
+ border-radius: var(--radius-md);
316
+ margin: 1em 0;
317
+ }
227
318
  }
228
319
 
229
320
  .actuate-admin.reduce-motion,
@@ -14,6 +14,7 @@ import {
14
14
  XCircle,
15
15
  } from 'lucide-react'
16
16
  import { toast } from 'sonner'
17
+ import { useNavigationBlocker } from '../router/index.js'
17
18
  import { FieldRenderer } from '../fields/FieldRenderer.js'
18
19
  import { RelationshipField } from '../fields/RelationshipField.js'
19
20
  import { Button } from '../components/ui/Button.js'
@@ -101,14 +102,9 @@ export function DocumentEdit({
101
102
  }
102
103
  }, [documentId, isNew])
103
104
 
104
- useEffect(() => {
105
- if (!isDirty) return
106
- const handler = (e: BeforeUnloadEvent) => {
107
- e.preventDefault()
108
- }
109
- window.addEventListener('beforeunload', handler)
110
- return () => window.removeEventListener('beforeunload', handler)
111
- }, [isDirty])
105
+ // Warn before discarding unsaved changes on client-side navigation (Cancel,
106
+ // sidebar/breadcrumb links) and on browser unload/refresh.
107
+ useNavigationBlocker(isDirty)
112
108
 
113
109
  const SEO_FIELDS: (keyof SEOData)[] = [
114
110
  'metaTitle',
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'
4
4
  import { Loader2, AlertTriangle } from 'lucide-react'
5
5
  import { toast } from 'sonner'
6
6
  import { cmsApi } from '../../lib/api.js'
7
+ import { useNavigationBlocker } from '../../router/index.js'
7
8
  import { ErrorBoundary } from '../../components/ErrorBoundary.js'
8
9
  import {
9
10
  useBuilderState,
@@ -169,6 +170,9 @@ export function PageBuilder({
169
170
  // eslint-disable-next-line react-hooks/exhaustive-deps -- replaceTree/setPageSettings are stable builder actions
170
171
  }, [documentId, templateId, collectionSlug])
171
172
 
173
+ // Warn before discarding unsaved builder changes on navigation / unload.
174
+ useNavigationBlocker(dirty)
175
+
172
176
  const handleSave = useCallback(async () => {
173
177
  setSaving(true)
174
178
  const body = JSON.stringify({