@drawnagency/primitives 0.1.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 (517) hide show
  1. package/README.md +28 -0
  2. package/dist/auth/cookies.d.ts +8 -0
  3. package/dist/auth/cookies.d.ts.map +1 -0
  4. package/dist/auth/cookies.js +44 -0
  5. package/dist/auth/errors.d.ts +8 -0
  6. package/dist/auth/errors.d.ts.map +1 -0
  7. package/dist/auth/errors.js +10 -0
  8. package/dist/auth/index.d.ts +5 -0
  9. package/dist/auth/index.d.ts.map +1 -0
  10. package/dist/auth/index.js +3 -0
  11. package/dist/auth/security.d.ts +13 -0
  12. package/dist/auth/security.d.ts.map +1 -0
  13. package/dist/auth/security.js +48 -0
  14. package/dist/auth/types.d.ts +64 -0
  15. package/dist/auth/types.d.ts.map +1 -0
  16. package/dist/auth/types.js +1 -0
  17. package/dist/components/brandguide/ColorSwatchSettings.d.ts +8 -0
  18. package/dist/components/brandguide/ColorSwatchSettings.d.ts.map +1 -0
  19. package/dist/components/brandguide/ColorSwatchSettings.js +10 -0
  20. package/dist/components/brandguide/Colors.d.ts +15 -0
  21. package/dist/components/brandguide/Colors.d.ts.map +1 -0
  22. package/dist/components/brandguide/Colors.js +79 -0
  23. package/dist/components/brandguide/DoDontList.d.ts +16 -0
  24. package/dist/components/brandguide/DoDontList.d.ts.map +1 -0
  25. package/dist/components/brandguide/DoDontList.js +22 -0
  26. package/dist/components/brandguide/DoDontMediaGrid.d.ts +16 -0
  27. package/dist/components/brandguide/DoDontMediaGrid.d.ts.map +1 -0
  28. package/dist/components/brandguide/DoDontMediaGrid.js +5 -0
  29. package/dist/components/editor/AudiencePicker.d.ts +9 -0
  30. package/dist/components/editor/AudiencePicker.d.ts.map +1 -0
  31. package/dist/components/editor/AudiencePicker.js +24 -0
  32. package/dist/components/editor/DeleteButton.d.ts +6 -0
  33. package/dist/components/editor/DeleteButton.d.ts.map +1 -0
  34. package/dist/components/editor/DeleteButton.js +6 -0
  35. package/dist/components/editor/DragHandle.d.ts +2 -0
  36. package/dist/components/editor/DragHandle.d.ts.map +1 -0
  37. package/dist/components/editor/DragHandle.js +8 -0
  38. package/dist/components/editor/InsertButton.d.ts +7 -0
  39. package/dist/components/editor/InsertButton.d.ts.map +1 -0
  40. package/dist/components/editor/InsertButton.js +7 -0
  41. package/dist/components/editor/SectionWrapper.d.ts +3 -0
  42. package/dist/components/editor/SectionWrapper.d.ts.map +1 -0
  43. package/dist/components/editor/SectionWrapper.js +135 -0
  44. package/dist/components/editor/SettingsButton.d.ts +6 -0
  45. package/dist/components/editor/SettingsButton.d.ts.map +1 -0
  46. package/dist/components/editor/SettingsButton.js +6 -0
  47. package/dist/components/editor/SettingsForm.d.ts +13 -0
  48. package/dist/components/editor/SettingsForm.d.ts.map +1 -0
  49. package/dist/components/editor/SettingsForm.js +64 -0
  50. package/dist/components/editor/StatusBadge.d.ts +7 -0
  51. package/dist/components/editor/StatusBadge.d.ts.map +1 -0
  52. package/dist/components/editor/StatusBadge.js +10 -0
  53. package/dist/components/editor/StatusPicker.d.ts +9 -0
  54. package/dist/components/editor/StatusPicker.d.ts.map +1 -0
  55. package/dist/components/editor/StatusPicker.js +30 -0
  56. package/dist/components/editor/index.d.ts +8 -0
  57. package/dist/components/editor/index.d.ts.map +1 -0
  58. package/dist/components/editor/index.js +7 -0
  59. package/dist/components/primitives/CustomParagraph.d.ts +2 -0
  60. package/dist/components/primitives/CustomParagraph.d.ts.map +1 -0
  61. package/dist/components/primitives/CustomParagraph.js +24 -0
  62. package/dist/components/primitives/EditableGrid.d.ts +19 -0
  63. package/dist/components/primitives/EditableGrid.d.ts.map +1 -0
  64. package/dist/components/primitives/EditableGrid.js +90 -0
  65. package/dist/components/primitives/EditableList.d.ts +15 -0
  66. package/dist/components/primitives/EditableList.d.ts.map +1 -0
  67. package/dist/components/primitives/EditableList.js +54 -0
  68. package/dist/components/primitives/EditablePlainText.d.ts +12 -0
  69. package/dist/components/primitives/EditablePlainText.d.ts.map +1 -0
  70. package/dist/components/primitives/EditablePlainText.js +52 -0
  71. package/dist/components/primitives/EditableRichText.d.ts +11 -0
  72. package/dist/components/primitives/EditableRichText.d.ts.map +1 -0
  73. package/dist/components/primitives/EditableRichText.js +86 -0
  74. package/dist/components/primitives/HeadingSection.d.ts +10 -0
  75. package/dist/components/primitives/HeadingSection.d.ts.map +1 -0
  76. package/dist/components/primitives/HeadingSection.js +7 -0
  77. package/dist/components/primitives/IconPicker.d.ts +9 -0
  78. package/dist/components/primitives/IconPicker.d.ts.map +1 -0
  79. package/dist/components/primitives/IconPicker.js +21 -0
  80. package/dist/components/primitives/LinkPopover.d.ts +8 -0
  81. package/dist/components/primitives/LinkPopover.d.ts.map +1 -0
  82. package/dist/components/primitives/LinkPopover.js +48 -0
  83. package/dist/components/primitives/MediaSettingsForms.d.ts +19 -0
  84. package/dist/components/primitives/MediaSettingsForms.d.ts.map +1 -0
  85. package/dist/components/primitives/MediaSettingsForms.js +42 -0
  86. package/dist/components/primitives/ResolvedMedia.d.ts +8 -0
  87. package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -0
  88. package/dist/components/primitives/ResolvedMedia.js +9 -0
  89. package/dist/components/primitives/RichTextToolbar.d.ts +9 -0
  90. package/dist/components/primitives/RichTextToolbar.d.ts.map +1 -0
  91. package/dist/components/primitives/RichTextToolbar.js +26 -0
  92. package/dist/components/primitives/tiptap-presets.d.ts +4 -0
  93. package/dist/components/primitives/tiptap-presets.d.ts.map +1 -0
  94. package/dist/components/primitives/tiptap-presets.js +44 -0
  95. package/dist/components/primitives/useEditableCollection.d.ts +19 -0
  96. package/dist/components/primitives/useEditableCollection.d.ts.map +1 -0
  97. package/dist/components/primitives/useEditableCollection.js +61 -0
  98. package/dist/components/primitives/useEditablePlainText.d.ts +14 -0
  99. package/dist/components/primitives/useEditablePlainText.d.ts.map +1 -0
  100. package/dist/components/primitives/useEditablePlainText.js +27 -0
  101. package/dist/components/primitives/useEditableRichText.d.ts +16 -0
  102. package/dist/components/primitives/useEditableRichText.d.ts.map +1 -0
  103. package/dist/components/primitives/useEditableRichText.js +52 -0
  104. package/dist/components/sections/Button/CTAButton.d.ts +11 -0
  105. package/dist/components/sections/Button/CTAButton.d.ts.map +1 -0
  106. package/dist/components/sections/Button/CTAButton.js +18 -0
  107. package/dist/components/sections/Button/index.d.ts +11 -0
  108. package/dist/components/sections/Button/index.d.ts.map +1 -0
  109. package/dist/components/sections/Button/index.js +28 -0
  110. package/dist/components/sections/Colors/index.d.ts +22 -0
  111. package/dist/components/sections/Colors/index.d.ts.map +1 -0
  112. package/dist/components/sections/Colors/index.js +34 -0
  113. package/dist/components/sections/DoDontList/index.d.ts +21 -0
  114. package/dist/components/sections/DoDontList/index.d.ts.map +1 -0
  115. package/dist/components/sections/DoDontList/index.js +33 -0
  116. package/dist/components/sections/DoDontMediaGrid/index.d.ts +55 -0
  117. package/dist/components/sections/DoDontMediaGrid/index.d.ts.map +1 -0
  118. package/dist/components/sections/DoDontMediaGrid/index.js +41 -0
  119. package/dist/components/sections/IconList/IconList.d.ts +20 -0
  120. package/dist/components/sections/IconList/IconList.d.ts.map +1 -0
  121. package/dist/components/sections/IconList/IconList.js +131 -0
  122. package/dist/components/sections/IconList/IconListSettings.d.ts +11 -0
  123. package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -0
  124. package/dist/components/sections/IconList/IconListSettings.js +22 -0
  125. package/dist/components/sections/IconList/index.d.ts +17 -0
  126. package/dist/components/sections/IconList/index.d.ts.map +1 -0
  127. package/dist/components/sections/IconList/index.js +27 -0
  128. package/dist/components/sections/LinkHeading/index.d.ts +8 -0
  129. package/dist/components/sections/LinkHeading/index.d.ts.map +1 -0
  130. package/dist/components/sections/LinkHeading/index.js +15 -0
  131. package/dist/components/sections/MediaGrid/MediaGrid.d.ts +17 -0
  132. package/dist/components/sections/MediaGrid/MediaGrid.d.ts.map +1 -0
  133. package/dist/components/sections/MediaGrid/MediaGrid.js +62 -0
  134. package/dist/components/sections/MediaGrid/index.d.ts +55 -0
  135. package/dist/components/sections/MediaGrid/index.d.ts.map +1 -0
  136. package/dist/components/sections/MediaGrid/index.js +35 -0
  137. package/dist/components/sections/Prose/Prose.d.ts +8 -0
  138. package/dist/components/sections/Prose/Prose.d.ts.map +1 -0
  139. package/dist/components/sections/Prose/Prose.js +11 -0
  140. package/dist/components/sections/Prose/index.d.ts +8 -0
  141. package/dist/components/sections/Prose/index.d.ts.map +1 -0
  142. package/dist/components/sections/Prose/index.js +15 -0
  143. package/dist/components/sections/SectionLayout.d.ts +11 -0
  144. package/dist/components/sections/SectionLayout.d.ts.map +1 -0
  145. package/dist/components/sections/SectionLayout.js +15 -0
  146. package/dist/components/sections/SplitContent/SplitContent.d.ts +11 -0
  147. package/dist/components/sections/SplitContent/SplitContent.d.ts.map +1 -0
  148. package/dist/components/sections/SplitContent/SplitContent.js +31 -0
  149. package/dist/components/sections/SplitContent/SplitContentSettings.d.ts +9 -0
  150. package/dist/components/sections/SplitContent/SplitContentSettings.d.ts.map +1 -0
  151. package/dist/components/sections/SplitContent/SplitContentSettings.js +17 -0
  152. package/dist/components/sections/SplitContent/index.d.ts +13 -0
  153. package/dist/components/sections/SplitContent/index.d.ts.map +1 -0
  154. package/dist/components/sections/SplitContent/index.js +27 -0
  155. package/dist/components/sections/SubHeading/index.d.ts +9 -0
  156. package/dist/components/sections/SubHeading/index.d.ts.map +1 -0
  157. package/dist/components/sections/SubHeading/index.js +18 -0
  158. package/dist/components/sections/SubSubHeading/index.d.ts +9 -0
  159. package/dist/components/sections/SubSubHeading/index.d.ts.map +1 -0
  160. package/dist/components/sections/SubSubHeading/index.js +18 -0
  161. package/dist/components/sections/ViewRenderer.d.ts +8 -0
  162. package/dist/components/sections/ViewRenderer.d.ts.map +1 -0
  163. package/dist/components/sections/ViewRenderer.js +13 -0
  164. package/dist/components/sections/register-schemas.d.ts +2 -0
  165. package/dist/components/sections/register-schemas.d.ts.map +1 -0
  166. package/dist/components/sections/register-schemas.js +15 -0
  167. package/dist/components/sections/register.d.ts +2 -0
  168. package/dist/components/sections/register.d.ts.map +1 -0
  169. package/dist/components/sections/register.js +15 -0
  170. package/dist/components/shared/Button.d.ts +15 -0
  171. package/dist/components/shared/Button.d.ts.map +1 -0
  172. package/dist/components/shared/Button.js +27 -0
  173. package/dist/components/shared/Checkbox.d.ts +14 -0
  174. package/dist/components/shared/Checkbox.d.ts.map +1 -0
  175. package/dist/components/shared/Checkbox.js +10 -0
  176. package/dist/components/shared/ColorPicker.d.ts +9 -0
  177. package/dist/components/shared/ColorPicker.d.ts.map +1 -0
  178. package/dist/components/shared/ColorPicker.js +5 -0
  179. package/dist/components/shared/ErrorBoundary.d.ts +24 -0
  180. package/dist/components/shared/ErrorBoundary.d.ts.map +1 -0
  181. package/dist/components/shared/ErrorBoundary.js +30 -0
  182. package/dist/components/shared/FontPicker.d.ts +8 -0
  183. package/dist/components/shared/FontPicker.d.ts.map +1 -0
  184. package/dist/components/shared/FontPicker.js +190 -0
  185. package/dist/components/shared/FormLabel.d.ts +8 -0
  186. package/dist/components/shared/FormLabel.d.ts.map +1 -0
  187. package/dist/components/shared/FormLabel.js +5 -0
  188. package/dist/components/shared/IconButton.d.ts +12 -0
  189. package/dist/components/shared/IconButton.d.ts.map +1 -0
  190. package/dist/components/shared/IconButton.js +16 -0
  191. package/dist/components/shared/Input.d.ts +8 -0
  192. package/dist/components/shared/Input.d.ts.map +1 -0
  193. package/dist/components/shared/Input.js +8 -0
  194. package/dist/components/shared/Navigation.d.ts +9 -0
  195. package/dist/components/shared/Navigation.d.ts.map +1 -0
  196. package/dist/components/shared/Navigation.js +71 -0
  197. package/dist/components/shared/PasswordInput.d.ts +13 -0
  198. package/dist/components/shared/PasswordInput.d.ts.map +1 -0
  199. package/dist/components/shared/PasswordInput.js +11 -0
  200. package/dist/components/shared/Popover.d.ts +12 -0
  201. package/dist/components/shared/Popover.d.ts.map +1 -0
  202. package/dist/components/shared/Popover.js +33 -0
  203. package/dist/components/shared/PopoverItem.d.ts +7 -0
  204. package/dist/components/shared/PopoverItem.d.ts.map +1 -0
  205. package/dist/components/shared/PopoverItem.js +6 -0
  206. package/dist/components/shared/Select.d.ts +16 -0
  207. package/dist/components/shared/Select.d.ts.map +1 -0
  208. package/dist/components/shared/Select.js +9 -0
  209. package/dist/components/shared/Textarea.d.ts +8 -0
  210. package/dist/components/shared/Textarea.d.ts.map +1 -0
  211. package/dist/components/shared/Textarea.js +8 -0
  212. package/dist/components/shared/Toggle.d.ts +10 -0
  213. package/dist/components/shared/Toggle.d.ts.map +1 -0
  214. package/dist/components/shared/Toggle.js +5 -0
  215. package/dist/components/shared/Tooltip.d.ts +12 -0
  216. package/dist/components/shared/Tooltip.d.ts.map +1 -0
  217. package/dist/components/shared/Tooltip.js +8 -0
  218. package/dist/components/shared/icons.d.ts +17 -0
  219. package/dist/components/shared/icons.d.ts.map +1 -0
  220. package/dist/components/shared/icons.js +23 -0
  221. package/dist/components/shell/AudienceAddForm.d.ts +11 -0
  222. package/dist/components/shell/AudienceAddForm.d.ts.map +1 -0
  223. package/dist/components/shell/AudienceAddForm.js +43 -0
  224. package/dist/components/shell/AudienceRow.d.ts +15 -0
  225. package/dist/components/shell/AudienceRow.d.ts.map +1 -0
  226. package/dist/components/shell/AudienceRow.js +74 -0
  227. package/dist/components/shell/EditorContext.d.ts +14 -0
  228. package/dist/components/shell/EditorContext.d.ts.map +1 -0
  229. package/dist/components/shell/EditorContext.js +24 -0
  230. package/dist/components/shell/EditorLoginForm.d.ts +13 -0
  231. package/dist/components/shell/EditorLoginForm.d.ts.map +1 -0
  232. package/dist/components/shell/EditorLoginForm.js +46 -0
  233. package/dist/components/shell/EditorModal.d.ts +13 -0
  234. package/dist/components/shell/EditorModal.d.ts.map +1 -0
  235. package/dist/components/shell/EditorModal.js +43 -0
  236. package/dist/components/shell/EditorModalContext.d.ts +16 -0
  237. package/dist/components/shell/EditorModalContext.d.ts.map +1 -0
  238. package/dist/components/shell/EditorModalContext.js +20 -0
  239. package/dist/components/shell/EditorShell.d.ts +22 -0
  240. package/dist/components/shell/EditorShell.d.ts.map +1 -0
  241. package/dist/components/shell/EditorShell.js +483 -0
  242. package/dist/components/shell/MediaLibraryContext.d.ts +14 -0
  243. package/dist/components/shell/MediaLibraryContext.d.ts.map +1 -0
  244. package/dist/components/shell/MediaLibraryContext.js +5 -0
  245. package/dist/components/shell/MediaLibraryModal.d.ts +14 -0
  246. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -0
  247. package/dist/components/shell/MediaLibraryModal.js +145 -0
  248. package/dist/components/shell/ProcessingIndicator.d.ts +7 -0
  249. package/dist/components/shell/ProcessingIndicator.d.ts.map +1 -0
  250. package/dist/components/shell/ProcessingIndicator.js +15 -0
  251. package/dist/components/shell/SectionSkeleton.d.ts +9 -0
  252. package/dist/components/shell/SectionSkeleton.d.ts.map +1 -0
  253. package/dist/components/shell/SectionSkeleton.js +22 -0
  254. package/dist/components/shell/SectionTypePicker.d.ts +14 -0
  255. package/dist/components/shell/SectionTypePicker.d.ts.map +1 -0
  256. package/dist/components/shell/SectionTypePicker.js +15 -0
  257. package/dist/components/shell/SiteSettingsDisplay.d.ts +8 -0
  258. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -0
  259. package/dist/components/shell/SiteSettingsDisplay.js +28 -0
  260. package/dist/components/shell/SiteSettingsModal.d.ts +24 -0
  261. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -0
  262. package/dist/components/shell/SiteSettingsModal.js +40 -0
  263. package/dist/components/shell/SiteSettingsUsers.d.ts +9 -0
  264. package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -0
  265. package/dist/components/shell/SiteSettingsUsers.js +87 -0
  266. package/dist/components/shell/SiteSettingsViewerAccess.d.ts +9 -0
  267. package/dist/components/shell/SiteSettingsViewerAccess.d.ts.map +1 -0
  268. package/dist/components/shell/SiteSettingsViewerAccess.js +94 -0
  269. package/dist/components/shell/ViewerLoginForm.d.ts +10 -0
  270. package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -0
  271. package/dist/components/shell/ViewerLoginForm.js +40 -0
  272. package/dist/data/google-fonts.json +7718 -0
  273. package/dist/hooks/index.d.ts +7 -0
  274. package/dist/hooks/index.d.ts.map +1 -0
  275. package/dist/hooks/index.js +6 -0
  276. package/dist/hooks/useActiveHeadings.d.ts +7 -0
  277. package/dist/hooks/useActiveHeadings.d.ts.map +1 -0
  278. package/dist/hooks/useActiveHeadings.js +99 -0
  279. package/dist/hooks/useEditorPersistence.d.ts +13 -0
  280. package/dist/hooks/useEditorPersistence.d.ts.map +1 -0
  281. package/dist/hooks/useEditorPersistence.js +73 -0
  282. package/dist/hooks/useEditorPublish.d.ts +24 -0
  283. package/dist/hooks/useEditorPublish.d.ts.map +1 -0
  284. package/dist/hooks/useEditorPublish.js +145 -0
  285. package/dist/hooks/useFocusTrap.d.ts +3 -0
  286. package/dist/hooks/useFocusTrap.d.ts.map +1 -0
  287. package/dist/hooks/useFocusTrap.js +51 -0
  288. package/dist/hooks/useMediaPipeline.d.ts +65 -0
  289. package/dist/hooks/useMediaPipeline.d.ts.map +1 -0
  290. package/dist/hooks/useMediaPipeline.js +253 -0
  291. package/dist/hooks/useResolvedMedia.d.ts +9 -0
  292. package/dist/hooks/useResolvedMedia.d.ts.map +1 -0
  293. package/dist/hooks/useResolvedMedia.js +39 -0
  294. package/dist/index.d.ts +5 -0
  295. package/dist/index.d.ts.map +1 -0
  296. package/dist/index.js +4 -0
  297. package/dist/lib/cn.d.ts +3 -0
  298. package/dist/lib/cn.d.ts.map +1 -0
  299. package/dist/lib/cn.js +5 -0
  300. package/dist/lib/contrast.d.ts +2 -0
  301. package/dist/lib/contrast.d.ts.map +1 -0
  302. package/dist/lib/contrast.js +11 -0
  303. package/dist/lib/dexie.d.ts +41 -0
  304. package/dist/lib/dexie.d.ts.map +1 -0
  305. package/dist/lib/dexie.js +236 -0
  306. package/dist/lib/events.d.ts +12 -0
  307. package/dist/lib/events.d.ts.map +1 -0
  308. package/dist/lib/events.js +15 -0
  309. package/dist/lib/google-fonts.d.ts +2 -0
  310. package/dist/lib/google-fonts.d.ts.map +1 -0
  311. package/dist/lib/google-fonts.js +11 -0
  312. package/dist/lib/grid.d.ts +2 -0
  313. package/dist/lib/grid.d.ts.map +1 -0
  314. package/dist/lib/grid.js +7 -0
  315. package/dist/lib/icons.d.ts +9 -0
  316. package/dist/lib/icons.d.ts.map +1 -0
  317. package/dist/lib/icons.js +27 -0
  318. package/dist/lib/index.d.ts +13 -0
  319. package/dist/lib/index.d.ts.map +1 -0
  320. package/dist/lib/index.js +12 -0
  321. package/dist/lib/loader.d.ts +28 -0
  322. package/dist/lib/loader.d.ts.map +1 -0
  323. package/dist/lib/loader.js +57 -0
  324. package/dist/lib/nav.d.ts +15 -0
  325. package/dist/lib/nav.d.ts.map +1 -0
  326. package/dist/lib/nav.js +58 -0
  327. package/dist/lib/registry.d.ts +110 -0
  328. package/dist/lib/registry.d.ts.map +1 -0
  329. package/dist/lib/registry.js +64 -0
  330. package/dist/lib/safeRedirect.d.ts +7 -0
  331. package/dist/lib/safeRedirect.d.ts.map +1 -0
  332. package/dist/lib/safeRedirect.js +11 -0
  333. package/dist/lib/sanitize.d.ts +2 -0
  334. package/dist/lib/sanitize.d.ts.map +1 -0
  335. package/dist/lib/sanitize.js +6 -0
  336. package/dist/lib/timestamp.d.ts +2 -0
  337. package/dist/lib/timestamp.d.ts.map +1 -0
  338. package/dist/lib/timestamp.js +28 -0
  339. package/dist/media/github.d.ts +3 -0
  340. package/dist/media/github.d.ts.map +1 -0
  341. package/dist/media/github.js +60 -0
  342. package/dist/media/index.d.ts +8 -0
  343. package/dist/media/index.d.ts.map +1 -0
  344. package/dist/media/index.js +9 -0
  345. package/dist/media/queue.d.ts +74 -0
  346. package/dist/media/queue.d.ts.map +1 -0
  347. package/dist/media/queue.js +116 -0
  348. package/dist/media/resolve.d.ts +14 -0
  349. package/dist/media/resolve.d.ts.map +1 -0
  350. package/dist/media/resolve.js +50 -0
  351. package/dist/media/types.d.ts +42 -0
  352. package/dist/media/types.d.ts.map +1 -0
  353. package/dist/media/types.js +1 -0
  354. package/dist/media/utils.d.ts +7 -0
  355. package/dist/media/utils.d.ts.map +1 -0
  356. package/dist/media/utils.js +41 -0
  357. package/dist/media/videoPoster.d.ts +6 -0
  358. package/dist/media/videoPoster.d.ts.map +1 -0
  359. package/dist/media/videoPoster.js +44 -0
  360. package/dist/media/worker.d.ts +36 -0
  361. package/dist/media/worker.d.ts.map +1 -0
  362. package/dist/media/worker.js +73 -0
  363. package/dist/schemas/audience.d.ts +12 -0
  364. package/dist/schemas/audience.d.ts.map +1 -0
  365. package/dist/schemas/audience.js +19 -0
  366. package/dist/schemas/auth.d.ts +36 -0
  367. package/dist/schemas/auth.d.ts.map +1 -0
  368. package/dist/schemas/auth.js +22 -0
  369. package/dist/schemas/index.d.ts +8 -0
  370. package/dist/schemas/index.d.ts.map +1 -0
  371. package/dist/schemas/index.js +7 -0
  372. package/dist/schemas/media-grid-options.d.ts +8 -0
  373. package/dist/schemas/media-grid-options.d.ts.map +1 -0
  374. package/dist/schemas/media-grid-options.js +7 -0
  375. package/dist/schemas/media.d.ts +63 -0
  376. package/dist/schemas/media.d.ts.map +1 -0
  377. package/dist/schemas/media.js +28 -0
  378. package/dist/schemas/sections.d.ts +12 -0
  379. package/dist/schemas/sections.d.ts.map +1 -0
  380. package/dist/schemas/sections.js +12 -0
  381. package/dist/schemas/shared.d.ts +98 -0
  382. package/dist/schemas/shared.d.ts.map +1 -0
  383. package/dist/schemas/shared.js +71 -0
  384. package/dist/schemas/site-config.d.ts +48 -0
  385. package/dist/schemas/site-config.d.ts.map +1 -0
  386. package/dist/schemas/site-config.js +26 -0
  387. package/package.json +57 -0
  388. package/src/auth/cookies.ts +51 -0
  389. package/src/auth/errors.ts +10 -0
  390. package/src/auth/index.ts +15 -0
  391. package/src/auth/security.ts +44 -0
  392. package/src/auth/types.ts +61 -0
  393. package/src/components/brandguide/ColorSwatchSettings.tsx +34 -0
  394. package/src/components/brandguide/Colors.tsx +277 -0
  395. package/src/components/brandguide/DoDontList.tsx +67 -0
  396. package/src/components/brandguide/DoDontMediaGrid.tsx +19 -0
  397. package/src/components/editor/AudiencePicker.tsx +103 -0
  398. package/src/components/editor/DeleteButton.tsx +18 -0
  399. package/src/components/editor/DragHandle.tsx +18 -0
  400. package/src/components/editor/InsertButton.tsx +22 -0
  401. package/src/components/editor/SectionWrapper.tsx +279 -0
  402. package/src/components/editor/SettingsButton.tsx +17 -0
  403. package/src/components/editor/SettingsForm.tsx +159 -0
  404. package/src/components/editor/StatusBadge.tsx +30 -0
  405. package/src/components/editor/StatusPicker.tsx +86 -0
  406. package/src/components/editor/index.ts +7 -0
  407. package/src/components/primitives/CustomParagraph.ts +25 -0
  408. package/src/components/primitives/EditableGrid.tsx +260 -0
  409. package/src/components/primitives/EditableList.tsx +160 -0
  410. package/src/components/primitives/EditablePlainText.tsx +99 -0
  411. package/src/components/primitives/EditableRichText.tsx +132 -0
  412. package/src/components/primitives/HeadingSection.tsx +25 -0
  413. package/src/components/primitives/IconPicker.tsx +60 -0
  414. package/src/components/primitives/LinkPopover.tsx +109 -0
  415. package/src/components/primitives/MediaSettingsForms.tsx +109 -0
  416. package/src/components/primitives/ResolvedMedia.tsx +36 -0
  417. package/src/components/primitives/RichTextToolbar.tsx +149 -0
  418. package/src/components/primitives/tiptap-presets.ts +50 -0
  419. package/src/components/primitives/useEditableCollection.ts +104 -0
  420. package/src/components/primitives/useEditablePlainText.ts +49 -0
  421. package/src/components/primitives/useEditableRichText.ts +74 -0
  422. package/src/components/sections/Button/CTAButton.tsx +50 -0
  423. package/src/components/sections/Button/index.tsx +37 -0
  424. package/src/components/sections/Colors/index.tsx +45 -0
  425. package/src/components/sections/DoDontList/index.tsx +43 -0
  426. package/src/components/sections/DoDontMediaGrid/index.tsx +53 -0
  427. package/src/components/sections/IconList/IconList.tsx +462 -0
  428. package/src/components/sections/IconList/IconListSettings.tsx +81 -0
  429. package/src/components/sections/IconList/index.tsx +36 -0
  430. package/src/components/sections/LinkHeading/index.tsx +24 -0
  431. package/src/components/sections/MediaGrid/MediaGrid.tsx +219 -0
  432. package/src/components/sections/MediaGrid/index.tsx +47 -0
  433. package/src/components/sections/Prose/Prose.tsx +34 -0
  434. package/src/components/sections/Prose/index.tsx +18 -0
  435. package/src/components/sections/SectionLayout.tsx +31 -0
  436. package/src/components/sections/SplitContent/SplitContent.tsx +77 -0
  437. package/src/components/sections/SplitContent/SplitContentSettings.tsx +42 -0
  438. package/src/components/sections/SplitContent/index.tsx +36 -0
  439. package/src/components/sections/SubHeading/index.tsx +27 -0
  440. package/src/components/sections/SubSubHeading/index.tsx +27 -0
  441. package/src/components/sections/ViewRenderer.tsx +29 -0
  442. package/src/components/sections/register-schemas.ts +16 -0
  443. package/src/components/sections/register.ts +16 -0
  444. package/src/components/shared/Button.tsx +77 -0
  445. package/src/components/shared/Checkbox.tsx +73 -0
  446. package/src/components/shared/ColorPicker.tsx +28 -0
  447. package/src/components/shared/ErrorBoundary.tsx +92 -0
  448. package/src/components/shared/FontPicker.tsx +300 -0
  449. package/src/components/shared/FormLabel.tsx +18 -0
  450. package/src/components/shared/IconButton.tsx +45 -0
  451. package/src/components/shared/Input.tsx +34 -0
  452. package/src/components/shared/Navigation.tsx +196 -0
  453. package/src/components/shared/PasswordInput.tsx +64 -0
  454. package/src/components/shared/Popover.tsx +61 -0
  455. package/src/components/shared/PopoverItem.tsx +24 -0
  456. package/src/components/shared/Select.tsx +60 -0
  457. package/src/components/shared/Textarea.tsx +34 -0
  458. package/src/components/shared/Toggle.tsx +35 -0
  459. package/src/components/shared/Tooltip.tsx +28 -0
  460. package/src/components/shared/icons.tsx +28 -0
  461. package/src/components/shell/AudienceAddForm.tsx +94 -0
  462. package/src/components/shell/AudienceRow.tsx +171 -0
  463. package/src/components/shell/EditorContext.tsx +44 -0
  464. package/src/components/shell/EditorLoginForm.tsx +130 -0
  465. package/src/components/shell/EditorModal.tsx +93 -0
  466. package/src/components/shell/EditorModalContext.tsx +40 -0
  467. package/src/components/shell/EditorShell.tsx +910 -0
  468. package/src/components/shell/MediaLibraryContext.tsx +19 -0
  469. package/src/components/shell/MediaLibraryModal.tsx +371 -0
  470. package/src/components/shell/ProcessingIndicator.tsx +84 -0
  471. package/src/components/shell/SectionSkeleton.tsx +54 -0
  472. package/src/components/shell/SectionTypePicker.tsx +47 -0
  473. package/src/components/shell/SiteSettingsDisplay.tsx +78 -0
  474. package/src/components/shell/SiteSettingsModal.tsx +129 -0
  475. package/src/components/shell/SiteSettingsUsers.tsx +190 -0
  476. package/src/components/shell/SiteSettingsViewerAccess.tsx +171 -0
  477. package/src/components/shell/ViewerLoginForm.tsx +95 -0
  478. package/src/data/google-fonts.json +7718 -0
  479. package/src/env.d.ts +3 -0
  480. package/src/hooks/index.ts +6 -0
  481. package/src/hooks/useActiveHeadings.ts +120 -0
  482. package/src/hooks/useEditorPersistence.ts +103 -0
  483. package/src/hooks/useEditorPublish.ts +187 -0
  484. package/src/hooks/useFocusTrap.ts +57 -0
  485. package/src/hooks/useMediaPipeline.ts +309 -0
  486. package/src/hooks/useResolvedMedia.ts +54 -0
  487. package/src/index.ts +4 -0
  488. package/src/lib/cn.ts +6 -0
  489. package/src/lib/contrast.ts +11 -0
  490. package/src/lib/dexie.ts +347 -0
  491. package/src/lib/events.ts +23 -0
  492. package/src/lib/google-fonts.ts +10 -0
  493. package/src/lib/grid.ts +7 -0
  494. package/src/lib/icons.ts +58 -0
  495. package/src/lib/index.ts +35 -0
  496. package/src/lib/loader.ts +81 -0
  497. package/src/lib/nav.ts +64 -0
  498. package/src/lib/registry.ts +202 -0
  499. package/src/lib/safeRedirect.ts +11 -0
  500. package/src/lib/sanitize.ts +6 -0
  501. package/src/lib/timestamp.ts +31 -0
  502. package/src/media/github.ts +72 -0
  503. package/src/media/index.ts +12 -0
  504. package/src/media/queue.ts +166 -0
  505. package/src/media/resolve.ts +63 -0
  506. package/src/media/types.ts +58 -0
  507. package/src/media/utils.ts +48 -0
  508. package/src/media/videoPoster.ts +53 -0
  509. package/src/media/worker.ts +122 -0
  510. package/src/schemas/audience.ts +28 -0
  511. package/src/schemas/auth.ts +31 -0
  512. package/src/schemas/index.ts +7 -0
  513. package/src/schemas/media-grid-options.ts +8 -0
  514. package/src/schemas/media.ts +40 -0
  515. package/src/schemas/sections.ts +24 -0
  516. package/src/schemas/shared.ts +96 -0
  517. package/src/schemas/site-config.ts +38 -0
@@ -0,0 +1,910 @@
1
+ import { Fragment, useState, useCallback, useEffect, useRef } from "react";
2
+
3
+ import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
4
+ import type { LoadedSection } from "../../lib/loader";
5
+ import type { SectionContent } from "../../schemas/sections";
6
+ import type { SiteIndex, SiteConfig } from "../../schemas/site-config";
7
+ import type { Audience } from "../../auth/types";
8
+ import type { MediaManifest } from "../../media/types";
9
+ import type { QueueItem } from "../../media/queue";
10
+ import { SiteConfigSchema } from "../../schemas/site-config";
11
+ import { EditorProvider, useEditorContext } from "./EditorContext";
12
+ import { EditorModalProvider, useEditorModal } from "./EditorModalContext";
13
+ import { EditorModal } from "./EditorModal";
14
+ import { SiteSettingsModal } from "./SiteSettingsModal";
15
+ import { MediaLibraryModal } from "./MediaLibraryModal";
16
+ import { MediaLibraryContext } from "./MediaLibraryContext";
17
+ import { ProcessingIndicator } from "./ProcessingIndicator";
18
+ import { SectionSkeleton } from "./SectionSkeleton";
19
+ import "../sections/register";
20
+ import { getSection, getAllSections } from "../../lib/registry";
21
+ import { SectionWrapper } from "../editor/SectionWrapper";
22
+ import { SectionLayout } from "../sections/SectionLayout";
23
+ import {
24
+ initEditorStore,
25
+ checkForLocalChanges,
26
+ restoreLocalChanges,
27
+ discardLocalChanges,
28
+ getCachedContent,
29
+ cacheContent,
30
+ persistMediaManifest,
31
+ getMediaManifest,
32
+ getPendingMediaItems,
33
+ getPendingMediaLocalUrls,
34
+ getPendingMediaDeletions,
35
+ } from "../../lib/dexie";
36
+ import { useEditorPersistence } from "../../hooks/useEditorPersistence";
37
+ import { useEditorPublish } from "../../hooks/useEditorPublish";
38
+ import { useMediaPipeline } from "../../hooks/useMediaPipeline";
39
+ import { formatTimestamp } from "../../lib/timestamp";
40
+ import { generateNavLinks } from "../../lib/nav";
41
+ import { navChangeEvent, darkModeEvent } from "../../lib/events";
42
+ import { cn } from "../../lib/cn";
43
+ import { Button } from "../shared/Button";
44
+ import { IconButton } from "../shared/IconButton";
45
+ import { SettingsIcon } from "../shared/icons";
46
+ import { ImageIcon } from "lucide-react";
47
+ import { ErrorBoundary } from "../shared/ErrorBoundary";
48
+
49
+ export { useMediaLibrary } from "./MediaLibraryContext";
50
+
51
+ const typeOptions = getAllSections().map((def) => ({
52
+ type: def.type,
53
+ label: def.label,
54
+ icon: () => null,
55
+ }));
56
+
57
+ type ShellState =
58
+ | { phase: "loading-content" }
59
+ | { phase: "error"; message: string }
60
+ | { phase: "recovery"; latestTimestamp: string }
61
+ | { phase: "ready" };
62
+
63
+ interface Props {
64
+ headSha: string;
65
+ siteId: string;
66
+ audiences: Audience[];
67
+ capabilities: {
68
+ oauth: boolean;
69
+ emailPassword: boolean;
70
+ passwordOnly: boolean;
71
+ userManagement: boolean;
72
+ audienceManagement: boolean;
73
+ passwordToggle: boolean;
74
+ };
75
+ currentUser: { email: string; role: "owner" | "editor" } | null;
76
+ }
77
+
78
+ export default function EditorShell({
79
+ headSha,
80
+ siteId,
81
+ audiences: initialAudiences,
82
+ capabilities,
83
+ currentUser,
84
+ }: Props) {
85
+ const [shellState, setShellState] = useState<ShellState>({ phase: "loading-content" });
86
+ const [sections, setSections] = useState<LoadedSection[]>([]);
87
+ const [siteIndex, setSiteIndex] = useState<SiteIndex>({ siteId, order: [], sections: {} });
88
+ const [audiences, setAudiences] = useState<Audience[]>(initialAudiences);
89
+ const [localChangesExist, setLocalChangesExist] = useState(false);
90
+ const [showDiscardConfirm, setShowDiscardConfirm] = useState(false);
91
+ const [showSiteSettings, setShowSiteSettings] = useState(false);
92
+ const [dirtySectionIds, setDirtySectionIds] = useState<Set<string>>(new Set());
93
+ const [siteConfig, setSiteConfig] = useState<SiteConfig | null>(null);
94
+ const [mediaManifest, setMediaManifest] = useState<MediaManifest>({ images: {} });
95
+ const [showMediaLibrary, setShowMediaLibrary] = useState(false);
96
+ const [mediaModalMode, setMediaModalMode] = useState<"select" | "manage">("manage");
97
+ const [mediaSelectCallback, setMediaSelectCallback] = useState<((id: string) => void) | null>(null);
98
+
99
+ const siteIndexRef = useRef<SiteIndex>({ siteId, order: [], sections: {} });
100
+ const fontLinkRef = useRef<HTMLLinkElement | null>(null);
101
+ useEffect(() => { siteIndexRef.current = siteIndex; }, [siteIndex]);
102
+
103
+ const persistence = useEditorPersistence(siteIndexRef);
104
+
105
+ const mediaPipeline = useMediaPipeline({
106
+ siteConfig,
107
+ mediaManifest,
108
+ setMediaManifest,
109
+ sections,
110
+ setSections,
111
+ setLocalChangesExist,
112
+ setDirtySectionIds,
113
+ markSectionDirty: persistence.markSectionDirty,
114
+ });
115
+
116
+ const { isPublishing, publishFeedback, handlePublish } = useEditorPublish({
117
+ flushNow: persistence.flushNow,
118
+ cancelPendingFlush: persistence.cancelPendingFlush,
119
+ isConfigDirty: persistence.isConfigDirty,
120
+ clearConfigDirty: persistence.clearConfigDirty,
121
+ siteIndexRef,
122
+ siteConfig,
123
+ sections,
124
+ onSuccess: () => {
125
+ setDirtySectionIds(new Set());
126
+ setLocalChangesExist(false);
127
+ },
128
+ mediaManifest,
129
+ pendingMediaItems: mediaPipeline.pendingMediaItems,
130
+ pendingMediaDeletions: mediaPipeline.pendingDeletions,
131
+ onMediaPublished: (publishedItems, publishedDeletions) => {
132
+ mediaPipeline.setPendingMediaItems([]);
133
+ mediaPipeline.setPendingLocalUrls({});
134
+ mediaPipeline.setPendingDeletions([]);
135
+ setMediaManifest((prev) => {
136
+ const images = { ...prev.images };
137
+ for (const item of publishedItems) {
138
+ images[item.id] = item;
139
+ }
140
+ for (const id of publishedDeletions) {
141
+ delete images[id];
142
+ }
143
+ return { images };
144
+ });
145
+ },
146
+ });
147
+
148
+ useEffect(() => {
149
+ if (sections.length === 0) return;
150
+ const navLinks = generateNavLinks(sections);
151
+ navChangeEvent.dispatch(navLinks);
152
+ }, [sections]);
153
+
154
+ const applySiteConfigPreview = useCallback((config: SiteConfig) => {
155
+ const root = document.documentElement;
156
+ root.style.setProperty("--color-primary", config.primaryColor);
157
+ root.style.setProperty("--color-primary-contrast", config.primaryContrast);
158
+ root.style.setProperty("--font-heading", `${config.headingFont}, system-ui, sans-serif`);
159
+ root.style.setProperty("--font-body", `${config.bodyFont}, system-ui, sans-serif`);
160
+
161
+ if (config.googleFontsUrl) {
162
+ if (fontLinkRef.current?.href !== config.googleFontsUrl) {
163
+ const link = document.createElement("link");
164
+ link.rel = "stylesheet";
165
+ link.href = config.googleFontsUrl;
166
+ document.head.appendChild(link);
167
+ fontLinkRef.current?.remove();
168
+ fontLinkRef.current = link;
169
+ }
170
+ } else {
171
+ fontLinkRef.current?.remove();
172
+ fontLinkRef.current = null;
173
+ }
174
+
175
+ if (config.darkMode === "dark") {
176
+ root.classList.add("dark");
177
+ } else if (config.darkMode === "light") {
178
+ root.classList.remove("dark");
179
+ } else {
180
+ const stored = localStorage.getItem("theme");
181
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
182
+ root.classList.toggle("dark", stored === "dark" || (!stored && prefersDark));
183
+ }
184
+
185
+ localStorage.setItem("portal-dark-mode", config.darkMode);
186
+ localStorage.setItem("portal-primary-color", config.primaryColor);
187
+ localStorage.setItem("portal-primary-contrast", config.primaryContrast);
188
+ darkModeEvent.dispatch(config.darkMode);
189
+ }, []);
190
+
191
+ // --- Content loading ---
192
+
193
+ useEffect(() => {
194
+ let cancelled = false;
195
+
196
+ async function loadContent() {
197
+ initEditorStore(siteId);
198
+ let loadedSections: LoadedSection[];
199
+ let loadedIndex: SiteIndex;
200
+ let loadedConfig: SiteConfig;
201
+ let loadedManifest: MediaManifest = { images: {} };
202
+
203
+ const cached = await getCachedContent();
204
+ if (cached && cached.sha === headSha) {
205
+ loadedSections = cached.sections;
206
+ loadedIndex = cached.index;
207
+ loadedConfig = SiteConfigSchema.parse(cached.siteConfig);
208
+ const savedManifest = await getMediaManifest();
209
+ if (savedManifest) loadedManifest = savedManifest;
210
+ } else {
211
+ const response = await fetch("/api/content");
212
+ if (!response.ok) throw new Error(`Failed to load content: ${response.status}`);
213
+ const data = await response.json();
214
+ loadedSections = data.sections;
215
+ loadedIndex = data.index;
216
+ loadedConfig = SiteConfigSchema.parse(data.siteConfig);
217
+ await cacheContent(data.sha, data.sections, data.index, data.siteConfig);
218
+ if (data.mediaManifest) {
219
+ loadedManifest = data.mediaManifest as MediaManifest;
220
+ await persistMediaManifest(loadedManifest);
221
+ }
222
+ }
223
+
224
+ if (cancelled) return;
225
+ setSections(loadedSections);
226
+ setSiteIndex(loadedIndex);
227
+ setSiteConfig(loadedConfig);
228
+ setMediaManifest(loadedManifest);
229
+ siteIndexRef.current = loadedIndex;
230
+ applySiteConfigPreview(loadedConfig);
231
+
232
+ // Load pending media from Dexie
233
+ const savedPendingItems = await getPendingMediaItems();
234
+ if (!cancelled && savedPendingItems.length > 0) {
235
+ mediaPipeline.setPendingMediaItems(savedPendingItems);
236
+ const urlMap: Record<string, string> = {};
237
+ for (const pi of savedPendingItems) {
238
+ const urls = await getPendingMediaLocalUrls(pi.id);
239
+ if (urls) {
240
+ const smallestKey = Object.keys(urls).find((k) => k !== "primary") ?? "primary";
241
+ if (urls[smallestKey]) urlMap[pi.id] = urls[smallestKey];
242
+ }
243
+ }
244
+ if (!cancelled) mediaPipeline.setPendingLocalUrls(urlMap);
245
+ }
246
+ const savedDeletions = await getPendingMediaDeletions();
247
+ if (!cancelled && savedDeletions.length > 0) mediaPipeline.setPendingDeletions(savedDeletions);
248
+
249
+ const result = await checkForLocalChanges();
250
+ if (cancelled) return;
251
+ if (result) {
252
+ setShellState({ phase: "recovery", latestTimestamp: result.latestTimestamp });
253
+ } else {
254
+ setShellState({ phase: "ready" });
255
+ }
256
+ }
257
+
258
+ loadContent().catch((err) => {
259
+ console.error("Failed to load editor content:", err);
260
+ setShellState({ phase: "error", message: err instanceof Error ? err.message : "Failed to load content" });
261
+ });
262
+ return () => { cancelled = true; };
263
+ }, [headSha, siteId, applySiteConfigPreview]);
264
+
265
+ // --- Recovery handlers ---
266
+
267
+ const handleRestore = useCallback(async () => {
268
+ const restored = await restoreLocalChanges();
269
+
270
+ if (restored.siteIndex) {
271
+ const restoredIndex = restored.siteIndex;
272
+ setSections(() =>
273
+ restoredIndex.order.map((id) => {
274
+ const restoredContent = restored.sections[id];
275
+ const existing = sections.find((s) => s.section.id === id);
276
+ if (restoredContent) {
277
+ return {
278
+ section: { id, ...restoredContent },
279
+ meta: restoredIndex.sections[id] ?? existing?.meta ?? { type: restoredContent.type, status: "draft", access: ["internal"] },
280
+ };
281
+ }
282
+ if (existing) {
283
+ return { ...existing, meta: restoredIndex.sections[id] ?? existing.meta };
284
+ }
285
+ return null;
286
+ }).filter((s): s is LoadedSection => s !== null),
287
+ );
288
+ setSiteIndex(restoredIndex);
289
+ } else {
290
+ setSections((prev) =>
291
+ prev.map((loaded) => {
292
+ const restoredContent = restored.sections[loaded.section.id];
293
+ if (restoredContent) {
294
+ return { ...loaded, section: { id: loaded.section.id, ...restoredContent } };
295
+ }
296
+ return loaded;
297
+ }),
298
+ );
299
+ }
300
+
301
+ if (restored.siteConfig) {
302
+ const parsed = SiteConfigSchema.safeParse(restored.siteConfig);
303
+ if (parsed.success) {
304
+ setSiteConfig(parsed.data);
305
+ persistence.persistConfig(parsed.data as unknown as Record<string, unknown>);
306
+ applySiteConfigPreview(parsed.data);
307
+ }
308
+ }
309
+
310
+ setLocalChangesExist(true);
311
+ setDirtySectionIds(new Set(Object.keys(restored.sections)));
312
+ setShellState({ phase: "ready" });
313
+ }, [sections, applySiteConfigPreview, persistence]);
314
+
315
+ const handleDiscard = useCallback(async () => {
316
+ await discardLocalChanges();
317
+ setLocalChangesExist(false);
318
+ setDirtySectionIds(new Set());
319
+ mediaPipeline.setPendingMediaItems([]);
320
+ mediaPipeline.setPendingLocalUrls({});
321
+ mediaPipeline.setPendingDeletions([]);
322
+ const cached = await getCachedContent();
323
+ if (cached) {
324
+ setSections(cached.sections);
325
+ setSiteIndex(cached.index);
326
+ siteIndexRef.current = cached.index;
327
+ const parsed = SiteConfigSchema.safeParse(cached.siteConfig);
328
+ if (parsed.success) {
329
+ setSiteConfig(parsed.data);
330
+ applySiteConfigPreview(parsed.data);
331
+ }
332
+ }
333
+ const savedManifest = await getMediaManifest();
334
+ setMediaManifest(savedManifest ?? { images: {} });
335
+ persistence.clearConfigDirty();
336
+ setShellState({ phase: "ready" });
337
+ }, [applySiteConfigPreview, persistence]);
338
+
339
+ const handleToolbarDiscard = useCallback(async () => {
340
+ setShowDiscardConfirm(false);
341
+ await handleDiscard();
342
+ }, [handleDiscard]);
343
+
344
+ // --- Section CRUD ---
345
+
346
+ const onSectionChange = useCallback(
347
+ (sectionId: string, newContent: SectionContent) => {
348
+ setSections((prev) =>
349
+ prev.map((loaded) =>
350
+ loaded.section.id === sectionId
351
+ ? { ...loaded, section: { id: loaded.section.id, ...newContent } }
352
+ : loaded,
353
+ ),
354
+ );
355
+ persistence.markSectionDirty(sectionId, newContent);
356
+ setLocalChangesExist(true);
357
+ setDirtySectionIds((prev) => new Set(prev).add(sectionId));
358
+ },
359
+ [persistence],
360
+ );
361
+
362
+ const onAddSection = useCallback(
363
+ (insertIndex: number, type: string) => {
364
+ const definition = getSection(type);
365
+ if (!definition) return;
366
+
367
+ const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
368
+ ? crypto.randomUUID()
369
+ : `_id_${Date.now()}_${Math.random().toString(36).slice(2)}`;
370
+ const content = definition.defaults() as object;
371
+ const newSection: LoadedSection = {
372
+ section: { id, ...content } as LoadedSection["section"],
373
+ meta: { type, status: "draft", access: ["internal"] },
374
+ };
375
+
376
+ setSections((prev) => {
377
+ const next = [...prev];
378
+ next.splice(insertIndex, 0, newSection);
379
+ return next;
380
+ });
381
+
382
+ setSiteIndex((prev) => {
383
+ const newOrder = [...prev.order];
384
+ newOrder.splice(insertIndex, 0, id);
385
+ return {
386
+ ...prev,
387
+ order: newOrder,
388
+ sections: { ...prev.sections, [id]: { type, status: "draft", access: ["internal"] } },
389
+ };
390
+ });
391
+
392
+ persistence.markSectionDirty(id, content as SectionContent);
393
+ persistence.markIndexDirty();
394
+ setLocalChangesExist(true);
395
+ setDirtySectionIds((prev) => new Set(prev).add(id));
396
+ },
397
+ [persistence],
398
+ );
399
+
400
+ const onDeleteSection = useCallback(
401
+ (sectionId: string) => {
402
+ setSections((prev) => prev.filter((s) => s.section.id !== sectionId));
403
+ setSiteIndex((prev) => {
404
+ const { [sectionId]: _, ...remainingSections } = prev.sections;
405
+ return {
406
+ ...prev,
407
+ order: prev.order.filter((id) => id !== sectionId),
408
+ sections: remainingSections,
409
+ };
410
+ });
411
+
412
+ persistence.markSectionDeleted(sectionId);
413
+ setLocalChangesExist(true);
414
+ setDirtySectionIds((prev) => {
415
+ const next = new Set(prev);
416
+ next.delete(sectionId);
417
+ return next;
418
+ });
419
+ },
420
+ [persistence],
421
+ );
422
+
423
+ const onReorderSections = useCallback(
424
+ (fromIndex: number, toIndex: number) => {
425
+ setSections((prev) => {
426
+ const next = [...prev];
427
+ const [moved] = next.splice(fromIndex, 1);
428
+ next.splice(toIndex, 0, moved);
429
+ return next;
430
+ });
431
+ setSiteIndex((prev) => {
432
+ const newOrder = [...prev.order];
433
+ const [movedId] = newOrder.splice(fromIndex, 1);
434
+ newOrder.splice(toIndex, 0, movedId);
435
+ return { ...prev, order: newOrder };
436
+ });
437
+
438
+ persistence.markIndexDirty();
439
+ setLocalChangesExist(true);
440
+ },
441
+ [persistence],
442
+ );
443
+
444
+ const onAccessChange = useCallback(
445
+ (sectionId: string, access: string[]) => {
446
+ setSections((prev) =>
447
+ prev.map((loaded) =>
448
+ loaded.section.id === sectionId
449
+ ? { ...loaded, meta: { ...loaded.meta, access } }
450
+ : loaded,
451
+ ),
452
+ );
453
+ setSiteIndex((prev) => ({
454
+ ...prev,
455
+ sections: { ...prev.sections, [sectionId]: { ...prev.sections[sectionId], access } },
456
+ }));
457
+ persistence.markIndexDirty();
458
+ setLocalChangesExist(true);
459
+ },
460
+ [persistence],
461
+ );
462
+
463
+ const onStatusChange = useCallback(
464
+ (sectionId: string, status: "draft" | "published" | "archived") => {
465
+ setSections((prev) =>
466
+ prev.map((loaded) =>
467
+ loaded.section.id === sectionId
468
+ ? { ...loaded, meta: { ...loaded.meta, status } }
469
+ : loaded,
470
+ ),
471
+ );
472
+ setSiteIndex((prev) => ({
473
+ ...prev,
474
+ sections: { ...prev.sections, [sectionId]: { ...prev.sections[sectionId], status } },
475
+ }));
476
+ persistence.markIndexDirty();
477
+ setLocalChangesExist(true);
478
+ },
479
+ [persistence],
480
+ );
481
+
482
+ const handleSiteConfigChange = useCallback((config: SiteConfig) => {
483
+ setSiteConfig(config);
484
+ applySiteConfigPreview(config);
485
+ persistence.persistConfig(config as unknown as Record<string, unknown>);
486
+ setLocalChangesExist(true);
487
+ }, [applySiteConfigPreview, persistence]);
488
+
489
+ // --- Render ---
490
+
491
+ if (shellState.phase === "loading-content") {
492
+ return <div className="min-h-screen" aria-busy="true" aria-label="Loading content" />;
493
+ }
494
+
495
+ if (shellState.phase === "error") {
496
+ return (
497
+ <div className="flex min-h-[50vh] items-center justify-center">
498
+ <div className="text-center">
499
+ <p className="text-sm text-red-600">{shellState.message}</p>
500
+ <Button variant="secondary" size="md" className="mt-3" onClick={() => window.location.reload()}>
501
+ Retry
502
+ </Button>
503
+ </div>
504
+ </div>
505
+ );
506
+ }
507
+
508
+ if (shellState.phase === "recovery") {
509
+ return (
510
+ <EditorModal isOpen={true} onClose={() => {}} title="Unsaved Changes" blocking>
511
+ <p className="text-sm text-base-contrast-light">
512
+ You have unsaved changes from <strong>{formatTimestamp(shellState.latestTimestamp)}</strong>.
513
+ Would you like to restore them or start fresh?
514
+ </p>
515
+ <div className="mt-4 flex justify-end gap-3">
516
+ <Button type="button" variant="secondary" size="md" onClick={handleDiscard}>
517
+ Discard
518
+ </Button>
519
+ <Button type="button" variant="primary" size="md" onClick={handleRestore}>
520
+ Restore
521
+ </Button>
522
+ </div>
523
+ </EditorModal>
524
+ );
525
+ }
526
+
527
+ return (
528
+ <EditorProvider>
529
+ <EditorModalProvider>
530
+ <MediaLibraryContext.Provider value={{
531
+ ...mediaPipeline.contextValue,
532
+ openSelectModal: (onSelect) => {
533
+ setMediaModalMode("select");
534
+ setMediaSelectCallback(() => onSelect);
535
+ setShowMediaLibrary(true);
536
+ },
537
+ }}>
538
+ <div className="editor-shell relative">
539
+ <EditorToolbar
540
+ localChangesExist={localChangesExist}
541
+ isPublishing={isPublishing}
542
+ publishFeedback={publishFeedback}
543
+ onPublish={handlePublish}
544
+ onDiscardClick={() => setShowDiscardConfirm(true)}
545
+ onSettingsClick={() => setShowSiteSettings(true)}
546
+ onMediaClick={() => {
547
+ setMediaModalMode("manage");
548
+ setShowMediaLibrary(true);
549
+ }}
550
+ processingItems={mediaPipeline.processingItems}
551
+ />
552
+
553
+ <EditorContent
554
+ sections={sections}
555
+ audiences={audiences}
556
+ dirtySectionIds={dirtySectionIds}
557
+ isPublishing={isPublishing}
558
+ onSectionChange={onSectionChange}
559
+ onAddSection={onAddSection}
560
+ onDeleteSection={onDeleteSection}
561
+ onReorderSections={onReorderSections}
562
+ onAccessChange={onAccessChange}
563
+ onStatusChange={onStatusChange}
564
+ />
565
+ <GlobalModal />
566
+ <SiteSettingsModal
567
+ isOpen={showSiteSettings}
568
+ onClose={() => setShowSiteSettings(false)}
569
+ siteConfig={siteConfig!}
570
+ onSiteConfigChange={handleSiteConfigChange}
571
+ onAudiencesChange={setAudiences}
572
+ capabilities={capabilities}
573
+ currentUser={currentUser}
574
+ />
575
+ <EditorModal
576
+ isOpen={showDiscardConfirm}
577
+ onClose={() => setShowDiscardConfirm(false)}
578
+ title="Discard Changes"
579
+ >
580
+ <p className="mb-4 text-sm text-base-contrast-light">
581
+ Discard all unsaved changes? This will reload with the last published content.
582
+ </p>
583
+ <div className="flex justify-end gap-3">
584
+ <Button variant="secondary" size="md" onClick={() => setShowDiscardConfirm(false)}>
585
+ Cancel
586
+ </Button>
587
+ <Button variant="destructive" size="md" onClick={handleToolbarDiscard}>
588
+ Discard
589
+ </Button>
590
+ </div>
591
+ </EditorModal>
592
+ <EditorModal
593
+ isOpen={showMediaLibrary}
594
+ onClose={() => {
595
+ setShowMediaLibrary(false);
596
+ setMediaSelectCallback(null);
597
+ }}
598
+ title="Media Library"
599
+ size="large"
600
+ >
601
+ <MediaLibraryModal
602
+ mode={mediaModalMode}
603
+ items={[
604
+ ...Object.values(mediaManifest.images),
605
+ ...mediaPipeline.pendingMediaItems,
606
+ ].filter((item) => !mediaPipeline.pendingDeletions.includes(item.id))}
607
+ onSelect={(id) => {
608
+ if (mediaSelectCallback) {
609
+ mediaSelectCallback(id);
610
+ }
611
+ setShowMediaLibrary(false);
612
+ setMediaSelectCallback(null);
613
+ }}
614
+ onUpload={mediaPipeline.handleMediaUpload}
615
+ onDelete={mediaPipeline.handleMediaDelete}
616
+ onAltChange={mediaPipeline.handleMediaAltChange}
617
+ referenceCountMap={mediaPipeline.referenceCountMap}
618
+ localUrlMap={mediaPipeline.pendingLocalUrls}
619
+ maxFileSize={siteConfig?.media.maxFileSize}
620
+ />
621
+ </EditorModal>
622
+ </div>
623
+ </MediaLibraryContext.Provider>
624
+ </EditorModalProvider>
625
+ </EditorProvider>
626
+ );
627
+ }
628
+
629
+ function EditorContent({
630
+ sections,
631
+ audiences,
632
+ dirtySectionIds,
633
+ isPublishing,
634
+ onSectionChange,
635
+ onAddSection,
636
+ onDeleteSection,
637
+ onReorderSections,
638
+ onAccessChange,
639
+ onStatusChange,
640
+ }: {
641
+ sections: LoadedSection[];
642
+ audiences: Audience[];
643
+ dirtySectionIds: Set<string>;
644
+ isPublishing: boolean;
645
+ onSectionChange: (sectionId: string, content: SectionContent) => void;
646
+ onAddSection: (insertIndex: number, type: string) => void;
647
+ onDeleteSection: (sectionId: string) => void;
648
+ onReorderSections: (fromIndex: number, toIndex: number) => void;
649
+ onAccessChange: (sectionId: string, access: string[]) => void;
650
+ onStatusChange: (sectionId: string, status: "draft" | "published" | "archived") => void;
651
+ }) {
652
+ const { isEditMode } = useEditorContext();
653
+ const { openModal, closeModal } = useEditorModal();
654
+ const [pendingInsertIndex, setPendingInsertIndex] = useState<number | null>(null);
655
+ const dismissPendingInsert = useCallback(() => setPendingInsertIndex(null), []);
656
+
657
+ const editingEnabled = isEditMode && !isPublishing;
658
+
659
+ useEffect(() => {
660
+ return monitorForElements({
661
+ onDragStart: ({ source }) => {
662
+ if (source.data.dragType === "section") {
663
+ setPendingInsertIndex(null);
664
+ }
665
+ },
666
+ });
667
+ }, []);
668
+
669
+ return (
670
+ <div>
671
+ {sections.map(({ section, meta }, index) => {
672
+ const definition = getSection(section.type);
673
+ if (!definition) {
674
+ return (
675
+ <div
676
+ key={section.id}
677
+ className="border border-amber-400 bg-amber-50 px-4 py-3 text-sm text-amber-800 dark:border-amber-600 dark:bg-amber-900/30 dark:text-amber-300"
678
+ role="alert"
679
+ >
680
+ Unknown section type: <code className="font-mono font-semibold">{section.type}</code>
681
+ </div>
682
+ );
683
+ }
684
+ const Component = definition.component;
685
+
686
+ return (
687
+ <Fragment key={section.id}>
688
+ {editingEnabled && pendingInsertIndex === index && (
689
+ <SectionSkeleton
690
+ types={typeOptions}
691
+ onSelect={(type) => {
692
+ onAddSection(index, type);
693
+ setPendingInsertIndex(null);
694
+ }}
695
+ onDismiss={dismissPendingInsert}
696
+ />
697
+ )}
698
+
699
+ <ErrorBoundary label={`${definition.label} (${section.type})`}>
700
+ <SectionLayout type={section.type} status={meta.status} dimNonPublished={!isEditMode}>
701
+ <SectionWrapper
702
+ sectionId={section.id}
703
+ sectionType={section.type}
704
+ status={meta.status}
705
+ dirty={dirtySectionIds.has(section.id)}
706
+ index={index}
707
+ isLast={index === sections.length - 1}
708
+ definition={definition}
709
+ options={{
710
+ ...(section.content as Record<string, unknown>),
711
+ ...("options" in section ? (section.options as Record<string, unknown>) : {}),
712
+ }}
713
+ audiences={audiences}
714
+ access={meta.access}
715
+ onAccessChange={editingEnabled ? (a) => onAccessChange(section.id, a) : undefined}
716
+ onStatusChange={editingEnabled ? (s) => onStatusChange(section.id, s) : undefined}
717
+ onSectionChange={editingEnabled ? (settingsResult) => {
718
+ const result = settingsResult as Record<string, unknown>;
719
+ if (result && typeof result === "object" && "content" in result && "options" in result) {
720
+ const contentPatch = result.content as Record<string, unknown>;
721
+ const optionsPatch = result.options as Record<string, unknown>;
722
+ onSectionChange(section.id, {
723
+ ...section,
724
+ content: { ...section.content, ...contentPatch },
725
+ options: { ...("options" in section ? section.options : {}), ...optionsPatch },
726
+ } as SectionContent);
727
+ } else {
728
+ onSectionChange(section.id, {
729
+ ...section,
730
+ ...result,
731
+ } as SectionContent);
732
+ }
733
+ } : undefined}
734
+ onReorder={editingEnabled ? onReorderSections : undefined}
735
+ onRequestInsert={editingEnabled ? (i) => setPendingInsertIndex(i) : undefined}
736
+ onDelete={editingEnabled ? () => {
737
+ openModal("Delete Section", (
738
+ <div>
739
+ <p className="mb-4 text-sm text-base-contrast-light">
740
+ Delete this <strong>{definition.label}</strong> section? This cannot be undone.
741
+ </p>
742
+ <div className="flex justify-end gap-3">
743
+ <Button variant="secondary" size="md" onClick={() => closeModal()}>
744
+ Cancel
745
+ </Button>
746
+ <Button variant="destructive" size="md" onClick={() => {
747
+ onDeleteSection(section.id);
748
+ closeModal();
749
+ }}>
750
+ Confirm
751
+ </Button>
752
+ </div>
753
+ </div>
754
+ ));
755
+ } : undefined}
756
+ >
757
+ <Component
758
+ content={section}
759
+ options={"options" in section ? (section.options as Record<string, unknown>) : undefined}
760
+ onChange={editingEnabled ? (newContent) => onSectionChange(section.id, newContent as SectionContent) : undefined}
761
+ isEditMode={isEditMode}
762
+ openModal={editingEnabled ? openModal : undefined}
763
+ />
764
+ </SectionWrapper>
765
+ </SectionLayout>
766
+ </ErrorBoundary>
767
+ </Fragment>
768
+ );
769
+ })}
770
+
771
+ {editingEnabled && pendingInsertIndex === sections.length && (
772
+ <SectionSkeleton
773
+ types={typeOptions}
774
+ onSelect={(type) => {
775
+ onAddSection(sections.length, type);
776
+ setPendingInsertIndex(null);
777
+ }}
778
+ onDismiss={dismissPendingInsert}
779
+ />
780
+ )}
781
+
782
+ </div>
783
+ );
784
+ }
785
+
786
+ function GlobalModal() {
787
+ const { modalState, closeModal } = useEditorModal();
788
+
789
+ return (
790
+ <EditorModal
791
+ isOpen={modalState !== null}
792
+ onClose={closeModal}
793
+ title={modalState?.title ?? ""}
794
+ >
795
+ {modalState?.content}
796
+ </EditorModal>
797
+ );
798
+ }
799
+
800
+ function EditorToolbar({
801
+ localChangesExist,
802
+ isPublishing,
803
+ publishFeedback,
804
+ onPublish,
805
+ onDiscardClick,
806
+ onSettingsClick,
807
+ onMediaClick,
808
+ processingItems,
809
+ }: {
810
+ localChangesExist: boolean;
811
+ isPublishing: boolean;
812
+ publishFeedback: string | null;
813
+ onPublish: () => void;
814
+ onDiscardClick: () => void;
815
+ onSettingsClick: () => void;
816
+ onMediaClick: () => void;
817
+ processingItems: QueueItem[];
818
+ }) {
819
+ const { isEditMode, toggleEditMode } = useEditorContext();
820
+
821
+ return (
822
+ <>
823
+ {isEditMode && (
824
+ <div className="fixed top-0 right-0 left-0 z-50 flex items-center justify-between border-b border-base-200 bg-base px-4 py-2">
825
+ <div className="flex items-center gap-2">
826
+ {publishFeedback && (
827
+ <span className={cn(
828
+ "text-xs font-medium",
829
+ publishFeedback === "Published" ? "text-green-600" : "text-red-600",
830
+ )}>
831
+ {publishFeedback}
832
+ </span>
833
+ )}
834
+ {localChangesExist && (
835
+ <Button
836
+ type="button"
837
+ variant="primary"
838
+ onClick={onPublish}
839
+ isLoading={isPublishing}
840
+ loadingLabel="Publishing..."
841
+ >
842
+ Publish
843
+ </Button>
844
+ )}
845
+ {localChangesExist && (
846
+ <Button
847
+ type="button"
848
+ variant="destructive"
849
+ onClick={onDiscardClick}
850
+ disabled={isPublishing}
851
+ >
852
+ Discard Changes
853
+ </Button>
854
+ )}
855
+ </div>
856
+ <div className="flex items-center gap-2">
857
+ <ProcessingIndicator items={processingItems} />
858
+ <IconButton
859
+ icon={<ImageIcon size={16} />}
860
+ label="Media library"
861
+ size="md"
862
+ onClick={onMediaClick}
863
+ className="border border-base-200 bg-base-accent"
864
+ />
865
+ <IconButton
866
+ icon={<SettingsIcon size={16} />}
867
+ label="Site settings"
868
+ size="md"
869
+ onClick={onSettingsClick}
870
+ className="border border-base-200 bg-base-accent"
871
+ />
872
+ <ShowAllChromeToggle />
873
+ </div>
874
+ </div>
875
+ )}
876
+ <button
877
+ onClick={toggleEditMode}
878
+ className="cursor-pointer fixed bottom-4 right-4 z-50 flex h-10 w-10 items-center justify-center rounded-full bg-primary text-primary-contrast shadow-lg hover:opacity-90 transition-opacity"
879
+ aria-label={isEditMode ? "Switch to view mode" : "Switch to edit mode"}
880
+ >
881
+ {isEditMode ? (
882
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
883
+ <path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
884
+ <path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" />
885
+ </svg>
886
+ ) : (
887
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
888
+ <path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
889
+ </svg>
890
+ )}
891
+ </button>
892
+ </>
893
+ );
894
+ }
895
+
896
+ function ShowAllChromeToggle() {
897
+ const { showAllChrome, toggleShowAllChrome } = useEditorContext();
898
+
899
+ return (
900
+ <Button
901
+ variant="secondary"
902
+ size="sm"
903
+ onClick={toggleShowAllChrome}
904
+ className="bg-base-accent"
905
+ aria-label="Toggle editor chrome visibility"
906
+ >
907
+ {showAllChrome ? "Hide Controls" : "Show Controls"}
908
+ </Button>
909
+ );
910
+ }