simple-module-pagebuilder 0.0.5__tar.gz → 0.0.7__tar.gz

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 (332) hide show
  1. simple_module_pagebuilder-0.0.7/PKG-INFO +315 -0
  2. simple_module_pagebuilder-0.0.7/README.md +292 -0
  3. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/package.json +1 -1
  4. simple_module_pagebuilder-0.0.7/pagebuilder/board.py +148 -0
  5. simple_module_pagebuilder-0.0.7/pagebuilder/boot.py +140 -0
  6. simple_module_pagebuilder-0.0.7/pagebuilder/components/ConfirmDialog.tsx +138 -0
  7. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/DiffSummary.tsx +14 -13
  8. simple_module_pagebuilder-0.0.7/pagebuilder/components/NewPageDialog.tsx +273 -0
  9. simple_module_pagebuilder-0.0.7/pagebuilder/components/NoteDialog.tsx +116 -0
  10. simple_module_pagebuilder-0.0.7/pagebuilder/components/PageBoard.tsx +127 -0
  11. simple_module_pagebuilder-0.0.7/pagebuilder/components/PageBoardCard.tsx +158 -0
  12. simple_module_pagebuilder-0.0.7/pagebuilder/components/PageListFilters.tsx +140 -0
  13. simple_module_pagebuilder-0.0.7/pagebuilder/components/PageListRow.tsx +121 -0
  14. simple_module_pagebuilder-0.0.7/pagebuilder/components/TranslatePageDialog.tsx +187 -0
  15. simple_module_pagebuilder-0.0.7/pagebuilder/components/blocks/Image.test.tsx +171 -0
  16. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blocks/Image.tsx +74 -9
  17. simple_module_pagebuilder-0.0.7/pagebuilder/components/editor/BlockOutline.tsx +76 -0
  18. simple_module_pagebuilder-0.0.7/pagebuilder/components/editor/NarrowCanvas.tsx +59 -0
  19. simple_module_pagebuilder-0.0.7/pagebuilder/components/editor/PageActions.tsx +103 -0
  20. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/editor/PageEditorToolbar.tsx +61 -17
  21. simple_module_pagebuilder-0.0.7/pagebuilder/components/editor/PageInspectorDrawer.tsx +170 -0
  22. simple_module_pagebuilder-0.0.7/pagebuilder/components/editor/PageSettingsPanel.tsx +147 -0
  23. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/editor/RevisionHistoryPanel.tsx +36 -19
  24. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/editor/SchedulePanel.tsx +38 -21
  25. simple_module_pagebuilder-0.0.7/pagebuilder/components/editor/SeoPreview.tsx +93 -0
  26. simple_module_pagebuilder-0.0.7/pagebuilder/components/editor/SeoSettingsPanel.tsx +188 -0
  27. simple_module_pagebuilder-0.0.7/pagebuilder/components/editor/TranslationsPanel.tsx +139 -0
  28. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/media/MediaFilters.tsx +4 -4
  29. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/media/MediaFolderSidebar.tsx +4 -4
  30. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/media/MediaGrid.tsx +22 -9
  31. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/media/MediaUploadQueue.tsx +10 -7
  32. simple_module_pagebuilder-0.0.7/pagebuilder/components/media/MediaUsageList.tsx +58 -0
  33. simple_module_pagebuilder-0.0.7/pagebuilder/components/snapshots/ImportPlanSummary.tsx +139 -0
  34. simple_module_pagebuilder-0.0.7/pagebuilder/components/snapshots/SnapshotList.tsx +112 -0
  35. simple_module_pagebuilder-0.0.7/pagebuilder/components/snapshots/UploadBundleDialog.tsx +88 -0
  36. simple_module_pagebuilder-0.0.7/pagebuilder/components/snapshots/planSummary.test.ts +133 -0
  37. simple_module_pagebuilder-0.0.7/pagebuilder/components/snapshots/planSummary.ts +115 -0
  38. simple_module_pagebuilder-0.0.7/pagebuilder/components/widgets/divider-widget.test.tsx +48 -0
  39. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/divider-widget.tsx +9 -2
  40. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/contracts/__init__.py +6 -0
  41. simple_module_pagebuilder-0.0.7/pagebuilder/contracts/schemas/__init__.py +94 -0
  42. simple_module_pagebuilder-0.0.7/pagebuilder/contracts/schemas/_layout.py +52 -0
  43. simple_module_pagebuilder-0.0.7/pagebuilder/contracts/schemas/_media.py +95 -0
  44. simple_module_pagebuilder-0.0.7/pagebuilder/contracts/schemas/_pages.py +279 -0
  45. simple_module_pagebuilder-0.0.7/pagebuilder/contracts/schemas/_snapshots.py +59 -0
  46. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/deps.py +8 -0
  47. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/diff.py +15 -10
  48. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/endpoints/api/__init__.py +12 -1
  49. simple_module_pagebuilder-0.0.7/pagebuilder/endpoints/api/media_detail.py +102 -0
  50. simple_module_pagebuilder-0.0.7/pagebuilder/endpoints/api/pages.py +193 -0
  51. simple_module_pagebuilder-0.0.7/pagebuilder/endpoints/api/snapshots.py +198 -0
  52. simple_module_pagebuilder-0.0.7/pagebuilder/endpoints/content_views.py +69 -0
  53. simple_module_pagebuilder-0.0.7/pagebuilder/endpoints/public_views.py +269 -0
  54. simple_module_pagebuilder-0.0.7/pagebuilder/endpoints/seo.py +163 -0
  55. simple_module_pagebuilder-0.0.7/pagebuilder/endpoints/views.py +291 -0
  56. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/hooks/useAutosave.ts +14 -1
  57. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/hooks/useEditorForm.ts +57 -1
  58. simple_module_pagebuilder-0.0.7/pagebuilder/hooks/useIsNarrow.ts +45 -0
  59. simple_module_pagebuilder-0.0.7/pagebuilder/hooks/useMediaDetail.ts +78 -0
  60. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/hooks/usePageRevisions.ts +2 -3
  61. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/hooks/usePageWorkflow.ts +19 -23
  62. simple_module_pagebuilder-0.0.7/pagebuilder/hooks/usePendingDialog.ts +43 -0
  63. simple_module_pagebuilder-0.0.7/pagebuilder/hooks/useSnapshots.ts +85 -0
  64. simple_module_pagebuilder-0.0.7/pagebuilder/locales.py +101 -0
  65. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/media_files.py +1 -1
  66. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/media_service.py +3 -0
  67. simple_module_pagebuilder-0.0.7/pagebuilder/media_usage.py +101 -0
  68. simple_module_pagebuilder-0.0.7/pagebuilder/models/__init__.py +47 -0
  69. simple_module_pagebuilder-0.0.7/pagebuilder/models/_base.py +14 -0
  70. simple_module_pagebuilder-0.0.7/pagebuilder/models/_layout.py +45 -0
  71. simple_module_pagebuilder-0.0.7/pagebuilder/models/_media.py +58 -0
  72. simple_module_pagebuilder-0.0.7/pagebuilder/models/_page.py +264 -0
  73. simple_module_pagebuilder-0.0.7/pagebuilder/models/_redirect.py +48 -0
  74. simple_module_pagebuilder-0.0.7/pagebuilder/models/_snapshot.py +131 -0
  75. simple_module_pagebuilder-0.0.7/pagebuilder/module.py +239 -0
  76. simple_module_pagebuilder-0.0.7/pagebuilder/pages/ContentImportReview.tsx +107 -0
  77. simple_module_pagebuilder-0.0.7/pagebuilder/pages/ContentSnapshots.tsx +79 -0
  78. simple_module_pagebuilder-0.0.7/pagebuilder/pages/LayoutEditor.tsx +225 -0
  79. simple_module_pagebuilder-0.0.7/pagebuilder/pages/MediaDetail.tsx +185 -0
  80. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/pages/MediaLibrary.tsx +17 -13
  81. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/pages/PageEditor.tsx +72 -53
  82. simple_module_pagebuilder-0.0.7/pagebuilder/pages/PageList.tsx +255 -0
  83. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/pages/PendingReview.tsx +52 -44
  84. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/pages/PublicPage.tsx +25 -0
  85. simple_module_pagebuilder-0.0.7/pagebuilder/pages/Trash.tsx +150 -0
  86. simple_module_pagebuilder-0.0.7/pagebuilder/public_claims.py +81 -0
  87. simple_module_pagebuilder-0.0.7/pagebuilder/redirects.py +82 -0
  88. simple_module_pagebuilder-0.0.7/pagebuilder/scheduler.py +92 -0
  89. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/security.py +52 -28
  90. simple_module_pagebuilder-0.0.7/pagebuilder/service/__init__.py +285 -0
  91. simple_module_pagebuilder-0.0.7/pagebuilder/service/_translations.py +215 -0
  92. simple_module_pagebuilder-0.0.7/pagebuilder/service/_trash.py +166 -0
  93. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/service/_workflow.py +5 -1
  94. simple_module_pagebuilder-0.0.7/pagebuilder/settings.py +279 -0
  95. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/__init__.py +13 -0
  96. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/apply.py +233 -0
  97. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/archive.py +202 -0
  98. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/assets.py +89 -0
  99. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/blobs.py +115 -0
  100. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/capture.py +280 -0
  101. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/format.py +56 -0
  102. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/media_match.py +116 -0
  103. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/pages.py +150 -0
  104. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/plan.py +260 -0
  105. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/service.py +300 -0
  106. simple_module_pagebuilder-0.0.7/pagebuilder/snapshots/validate.py +194 -0
  107. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/api.ts +1 -0
  108. simple_module_pagebuilder-0.0.7/pagebuilder/utils/blockOutline.test.ts +139 -0
  109. simple_module_pagebuilder-0.0.7/pagebuilder/utils/blockOutline.ts +130 -0
  110. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/editorSnapshot.ts +13 -0
  111. simple_module_pagebuilder-0.0.7/pagebuilder/utils/locale.ts +41 -0
  112. simple_module_pagebuilder-0.0.7/pagebuilder/utils/measure.ts +54 -0
  113. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/mediaApi.ts +17 -0
  114. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/pagesApi.ts +46 -22
  115. simple_module_pagebuilder-0.0.7/pagebuilder/utils/request.test.ts +106 -0
  116. simple_module_pagebuilder-0.0.7/pagebuilder/utils/request.ts +120 -0
  117. simple_module_pagebuilder-0.0.7/pagebuilder/utils/snapshotsApi.ts +125 -0
  118. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/types.ts +77 -0
  119. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pyproject.toml +12 -1
  120. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/conftest.py +10 -22
  121. simple_module_pagebuilder-0.0.7/tests/db_fixture.py +37 -0
  122. simple_module_pagebuilder-0.0.7/tests/locale_fixture.py +48 -0
  123. simple_module_pagebuilder-0.0.7/tests/page_helpers.py +33 -0
  124. simple_module_pagebuilder-0.0.7/tests/snapshot_fixture.py +90 -0
  125. simple_module_pagebuilder-0.0.7/tests/test_admin_list_view.py +113 -0
  126. simple_module_pagebuilder-0.0.7/tests/test_draft_preview.py +92 -0
  127. simple_module_pagebuilder-0.0.7/tests/test_hreflang_and_sitemap.py +163 -0
  128. simple_module_pagebuilder-0.0.7/tests/test_media_detail.py +175 -0
  129. simple_module_pagebuilder-0.0.7/tests/test_media_usage_escaping.py +58 -0
  130. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_module.py +2 -2
  131. simple_module_pagebuilder-0.0.7/tests/test_multilingual.py +186 -0
  132. simple_module_pagebuilder-0.0.7/tests/test_page_templates.py +164 -0
  133. simple_module_pagebuilder-0.0.7/tests/test_page_translations.py +222 -0
  134. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_pages_api.py +1 -1
  135. simple_module_pagebuilder-0.0.7/tests/test_pages_search.py +98 -0
  136. simple_module_pagebuilder-0.0.7/tests/test_public_claims.py +216 -0
  137. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_public_routes.py +21 -4
  138. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_public_view_headers.py +1 -1
  139. simple_module_pagebuilder-0.0.7/tests/test_redirects.py +144 -0
  140. simple_module_pagebuilder-0.0.7/tests/test_settings_source.py +126 -0
  141. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_site_layout.py +2 -2
  142. simple_module_pagebuilder-0.0.7/tests/test_snapshot_apply.py +246 -0
  143. simple_module_pagebuilder-0.0.7/tests/test_snapshot_apply_media.py +107 -0
  144. simple_module_pagebuilder-0.0.7/tests/test_snapshot_archive.py +299 -0
  145. simple_module_pagebuilder-0.0.7/tests/test_snapshot_assets.py +70 -0
  146. simple_module_pagebuilder-0.0.7/tests/test_snapshot_blobs.py +64 -0
  147. simple_module_pagebuilder-0.0.7/tests/test_snapshot_capture.py +252 -0
  148. simple_module_pagebuilder-0.0.7/tests/test_snapshot_media_match.py +96 -0
  149. simple_module_pagebuilder-0.0.7/tests/test_snapshot_models.py +30 -0
  150. simple_module_pagebuilder-0.0.7/tests/test_snapshot_multilingual.py +238 -0
  151. simple_module_pagebuilder-0.0.7/tests/test_snapshot_page_payload.py +65 -0
  152. simple_module_pagebuilder-0.0.7/tests/test_snapshot_plan.py +288 -0
  153. simple_module_pagebuilder-0.0.7/tests/test_snapshot_validate.py +138 -0
  154. simple_module_pagebuilder-0.0.7/tests/test_snapshot_views.py +73 -0
  155. simple_module_pagebuilder-0.0.7/tests/test_snapshots_api.py +184 -0
  156. simple_module_pagebuilder-0.0.7/tests/test_trash.py +177 -0
  157. simple_module_pagebuilder-0.0.7/tests/test_trash_preconditions.py +93 -0
  158. simple_module_pagebuilder-0.0.5/PKG-INFO +0 -184
  159. simple_module_pagebuilder-0.0.5/README.md +0 -162
  160. simple_module_pagebuilder-0.0.5/pagebuilder/components/blocks/Image.test.tsx +0 -94
  161. simple_module_pagebuilder-0.0.5/pagebuilder/components/editor/SeoSettingsPanel.tsx +0 -119
  162. simple_module_pagebuilder-0.0.5/pagebuilder/contracts/schemas.py +0 -239
  163. simple_module_pagebuilder-0.0.5/pagebuilder/endpoints/api/pages.py +0 -85
  164. simple_module_pagebuilder-0.0.5/pagebuilder/endpoints/seo.py +0 -98
  165. simple_module_pagebuilder-0.0.5/pagebuilder/endpoints/views.py +0 -228
  166. simple_module_pagebuilder-0.0.5/pagebuilder/models.py +0 -209
  167. simple_module_pagebuilder-0.0.5/pagebuilder/module.py +0 -284
  168. simple_module_pagebuilder-0.0.5/pagebuilder/pages/LayoutEditor.tsx +0 -183
  169. simple_module_pagebuilder-0.0.5/pagebuilder/pages/PageList.tsx +0 -129
  170. simple_module_pagebuilder-0.0.5/pagebuilder/service/__init__.py +0 -171
  171. simple_module_pagebuilder-0.0.5/pagebuilder/settings.py +0 -164
  172. simple_module_pagebuilder-0.0.5/pagebuilder/utils/request.ts +0 -52
  173. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/.gitignore +0 -0
  174. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/__init__.py +0 -0
  175. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/ScheduledBadge.tsx +0 -0
  176. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/StatusBadge.tsx +0 -0
  177. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blockRegistry.test.ts +0 -0
  178. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blockRegistry.ts +0 -0
  179. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blocks/Button.tsx +0 -0
  180. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blocks/Columns.tsx +0 -0
  181. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blocks/Heading.tsx +0 -0
  182. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blocks/MediaPicker.tsx +0 -0
  183. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blocks/Spacer.tsx +0 -0
  184. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/blocks/Text.tsx +0 -0
  185. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/layoutPuckConfig.tsx +0 -0
  186. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/lazyBlock.tsx +0 -0
  187. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/media/MediaHeader.tsx +0 -0
  188. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/media/types.ts +0 -0
  189. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/migrateContent.test.ts +0 -0
  190. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/migrateContent.ts +0 -0
  191. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/puckConfig.test.tsx +0 -0
  192. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/puckConfig.tsx +0 -0
  193. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgetCatalog.test.tsx +0 -0
  194. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgetCatalog.ts +0 -0
  195. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_internal/accent-text.tsx +0 -0
  196. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_internal/disclosure-chevron.tsx +0 -0
  197. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_internal/eyebrow-split-section.tsx +0 -0
  198. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_internal/link-arrow.tsx +0 -0
  199. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_internal/rich-text.tsx +0 -0
  200. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_internal/solid-icons.tsx +0 -0
  201. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/background-media.tsx +0 -0
  202. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/content-media.tsx +0 -0
  203. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/cta-button.tsx +0 -0
  204. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/empty-placeholder.tsx +0 -0
  205. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/eyebrow-text.tsx +0 -0
  206. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/heading.tsx +0 -0
  207. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/image-srcset.ts +0 -0
  208. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/index.ts +0 -0
  209. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/mask.ts +0 -0
  210. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/section.tsx +0 -0
  211. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/social-icon.tsx +0 -0
  212. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/_shared/text-link.tsx +0 -0
  213. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/accordion-widget.tsx +0 -0
  214. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/alert-widget.tsx +0 -0
  215. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/app-store-badges-widget.tsx +0 -0
  216. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/article-cards-render.tsx +0 -0
  217. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/article-cards-widget.tsx +0 -0
  218. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/call-to-action-render.tsx +0 -0
  219. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/call-to-action-styles.ts +0 -0
  220. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/call-to-action-widget.tsx +0 -0
  221. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/carousel-widget.tsx +0 -0
  222. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/centered-hero-widget.tsx +0 -0
  223. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/colored-action-list-widget.tsx +0 -0
  224. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/column-widget.tsx +0 -0
  225. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/contact-cards-widget.tsx +0 -0
  226. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/contact-form-render.tsx +0 -0
  227. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/contact-form-widget.tsx +0 -0
  228. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/container-widget.tsx +0 -0
  229. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/definition-list-widget.tsx +0 -0
  230. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/eyebrow-section-widget.tsx +0 -0
  231. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/faq-render.tsx +0 -0
  232. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/faq-widget.tsx +0 -0
  233. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/feature-card-widget.tsx +0 -0
  234. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/feature-cards-render.tsx +0 -0
  235. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/feature-cards-styles.ts +0 -0
  236. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/feature-cards-widget.tsx +0 -0
  237. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/feature-grid-widget.tsx +0 -0
  238. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/feature-showcase-widget.tsx +0 -0
  239. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/gallery-widget.tsx +0 -0
  240. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/grid-widget.tsx +0 -0
  241. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/hero-widget-props.ts +0 -0
  242. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/hero-widget.tsx +0 -0
  243. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/html-widget.tsx +0 -0
  244. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/iframe-widget.tsx +0 -0
  245. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/leaderboard-widget.tsx +0 -0
  246. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/list-widget.tsx +0 -0
  247. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/logo-cloud-widget.tsx +0 -0
  248. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/markdown-widget.tsx +0 -0
  249. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/media-object-layout.ts +0 -0
  250. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/media-object-render.tsx +0 -0
  251. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/media-object-text.tsx +0 -0
  252. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/media-object-widget.tsx +0 -0
  253. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/newsletter-widget.tsx +0 -0
  254. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/page-header-widget.tsx +0 -0
  255. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/prose-section-widget.tsx +0 -0
  256. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/quote-widget.tsx +0 -0
  257. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/row-widget.tsx +0 -0
  258. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/section-intro-widget.tsx +0 -0
  259. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/signup-banner-widget.tsx +0 -0
  260. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/simple-action-list-widget.tsx +0 -0
  261. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/site-footer-widget.tsx +0 -0
  262. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/site-header-nav.tsx +0 -0
  263. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/site-header-widget.tsx +0 -0
  264. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/social-banner-widget.tsx +0 -0
  265. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/stats-widget.tsx +0 -0
  266. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/steps-widget.tsx +0 -0
  267. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/story-cards-widget.tsx +0 -0
  268. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/table-widget.tsx +0 -0
  269. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/tabs-widget.tsx +0 -0
  270. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/tags-widget.tsx +0 -0
  271. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/testimonial-widget.tsx +0 -0
  272. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/timeline-layout.ts +0 -0
  273. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/timeline-render.tsx +0 -0
  274. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/timeline-widget.tsx +0 -0
  275. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/under-construction-widget.tsx +0 -0
  276. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/video-widget.tsx +0 -0
  277. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/components/widgets/welcome-widget.tsx +0 -0
  278. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/contracts/events.py +0 -0
  279. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/conversion/blocks-to-html.test.ts +0 -0
  280. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/conversion/blocks-to-html.ts +0 -0
  281. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/conversion/index.ts +0 -0
  282. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/conversion/types.ts +0 -0
  283. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/endpoints/__init__.py +0 -0
  284. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/endpoints/api/_deps.py +0 -0
  285. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/endpoints/api/layout.py +0 -0
  286. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/endpoints/api/revisions.py +0 -0
  287. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/endpoints/api/uploads.py +0 -0
  288. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/endpoints/api/workflow.py +0 -0
  289. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/fields/checkbox-field.tsx +0 -0
  290. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/fields/file-picker/file-gallery.tsx +0 -0
  291. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/fields/file-picker/image-picker-field.tsx +0 -0
  292. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/fields/file-picker/index.ts +0 -0
  293. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/fields/file-picker/types.ts +0 -0
  294. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/fields/index.ts +0 -0
  295. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/fields/media-adapter.ts +0 -0
  296. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/hooks/useMediaUploads.ts +0 -0
  297. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/hooks/usePageSchedule.ts +0 -0
  298. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/layout_service.py +0 -0
  299. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/media_images.py +0 -0
  300. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/media_queries.py +0 -0
  301. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/permissions.py +0 -0
  302. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/py.typed +0 -0
  303. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/service/_common.py +0 -0
  304. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/service/_revisions.py +0 -0
  305. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/services.py +0 -0
  306. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/static/widgets-base.css +0 -0
  307. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/translation/apply.ts +0 -0
  308. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/translation/extract.ts +0 -0
  309. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/translation/index.ts +0 -0
  310. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/translation/translatable-fields.ts +0 -0
  311. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/translation/translation.test.ts +0 -0
  312. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/datetime.ts +0 -0
  313. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/decorative-image.ts +0 -0
  314. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/markdown.ts +0 -0
  315. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/mediaFormat.ts +0 -0
  316. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/parse-list.ts +0 -0
  317. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/slugify.ts +0 -0
  318. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/pagebuilder/utils/widgetUtils.ts +0 -0
  319. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_approval_workflow.py +0 -0
  320. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_auth_gating.py +0 -0
  321. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_csrf.py +0 -0
  322. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_image_processing.py +0 -0
  323. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_media_files.py +0 -0
  324. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_media_security.py +0 -0
  325. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_public_viewer.py +0 -0
  326. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_revision_notes_and_diff.py +0 -0
  327. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_revisions_api.py +0 -0
  328. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_scheduled_publish.py +0 -0
  329. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_seo_meta.py +0 -0
  330. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_sitemap_robots.py +0 -0
  331. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tests/test_uploads_api.py +0 -0
  332. {simple_module_pagebuilder-0.0.5 → simple_module_pagebuilder-0.0.7}/tsconfig.json +0 -0
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.5
2
+ Name: simple_module_pagebuilder
3
+ Version: 0.0.7
4
+ Summary: Drag-and-drop visual page builder for simple_module apps
5
+ Project-URL: Repository, https://github.com/antosubash/simple_module_python_modules
6
+ Author-email: Anto Subash <antosubash@live.com>
7
+ License-Expression: MIT
8
+ Keywords: cms,pagebuilder,puck,simple-module
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: pillow>=10.0
11
+ Requires-Dist: pydantic-settings>=2.0
12
+ Requires-Dist: simple-module-core<0.1,>=0.0.25
13
+ Requires-Dist: simple-module-db<0.1,>=0.0.25
14
+ Requires-Dist: simple-module-hosting<0.1,>=0.0.25
15
+ Requires-Dist: simple-module-settings<0.1,>=0.0.25
16
+ Requires-Dist: sqlalchemy>=2.0
17
+ Requires-Dist: sqlmodel>=0.0.21
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
20
+ Requires-Dist: pytest>=8.0; extra == 'dev'
21
+ Requires-Dist: simple-module-test<0.1,>=0.0.25; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # simple_module_pagebuilder
25
+
26
+ Drag-and-drop visual page builder for [simple_module](https://github.com/antosubash/simple_module_python)
27
+ apps. Pages are composed from a reusable block library using
28
+ [Puck](https://puckeditor.com) and stored as JSON; published pages are served
29
+ at `/p/{slug}`.
30
+
31
+ ## Features
32
+
33
+ - **Visual editor** — Puck-based drag-and-drop with a block library (heading,
34
+ text, image, button, columns, spacer) and a media picker.
35
+ - **Site layout** — a shared header/footer layout, edited the same way, wrapped
36
+ around every public page.
37
+ - **Media library** — uploads with magic-byte content sniffing, folder
38
+ organisation, and server-generated WebP thumbnails.
39
+ - **Revisions** — every status transition snapshots the page, with a diff view
40
+ and restore-as-draft.
41
+ - **Approval workflow** — edit → submit for review → approve / reject, gated by
42
+ three separate permissions.
43
+ - **Scheduled publishing** — set `publish_at` / `unpublish_at`; a background
44
+ poller flips status at the due time.
45
+ - **SEO** — per-page meta description, canonical URL, OG image, JSON-LD, plus
46
+ `/sitemap.xml` and `/robots.txt`.
47
+ - **Multilingual content** — a page can exist in several languages, each with
48
+ its own slug, draft and approval state. See below.
49
+ - **Content snapshots** — capture the whole site, download or upload it as a
50
+ `.zip`, and restore behind an approval gate. See below.
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ uv add simple_module_pagebuilder
56
+ ```
57
+
58
+ Then add it to your host's `pyproject.toml` dependencies. The module is
59
+ discovered at boot through the `simple_module` entry point — no registration
60
+ code required.
61
+
62
+ Its frontend ships inside the wheel. Pull its JS dependencies into your host's
63
+ client app and regenerate the page manifest with:
64
+
65
+ ```bash
66
+ smpy host sync-js-deps --host-client-app=host/client_app
67
+ smpy host gen-pages --host-dir=host/client_app
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ Once installed and migrated, sign in and open `/pagebuilder`:
73
+
74
+ 1. **Create a page** — `/pagebuilder/new`. Give it a title; the slug is derived
75
+ from it and stays editable.
76
+ 2. **Compose it** — drag blocks from the Puck sidebar. Images come from the
77
+ media library, which uploads and thumbnails them for you.
78
+ 3. **Publish it** — publish directly with `pagebuilder.publish`, or submit for
79
+ review if your role only carries `pagebuilder.edit`. Reviewers work through
80
+ `/pagebuilder/pending`.
81
+ 4. **Schedule it** — set `publish_at` / `unpublish_at` instead of publishing
82
+ now, and the poller flips the status at the due time.
83
+ 5. **View it** — the page is live at `/p/{slug}`, wrapped in the site layout
84
+ from `/pagebuilder/layout`.
85
+
86
+ Every transition writes a revision, so `/pagebuilder/{id}/edit` can compare any
87
+ two revisions and restore either one as a draft.
88
+
89
+ ## Contributing blocks from another module
90
+
91
+ A module ships Puck blocks by calling `registerPuckBlocks` from its
92
+ `puck-blocks.ts`; the host imports every module's registration eagerly at app
93
+ start, so the registry is populated before the first render.
94
+
95
+ Because registration is eager, a block that statically imports a heavy render
96
+ component puts that component's whole dependency graph in the entry chunk —
97
+ on every page of the site, for every visitor. Measured on a consuming site, a
98
+ map block's static import put maplibre-gl (1 MB minified, 62% of the bundle)
99
+ in front of visitors who never opened the map. Register heavy blocks with
100
+ `lazyBlock` instead, which keeps fields and defaults eager but loads the
101
+ component the first time a page actually renders the block:
102
+
103
+ ```tsx
104
+ import { lazyBlock } from '@simple-module-py/pagebuilder/pagebuilder/components/lazyBlock';
105
+ import { registerPuckBlocks } from '@simple-module-py/pagebuilder/pagebuilder/components/blockRegistry';
106
+
107
+ registerPuckBlocks({
108
+ blocks: {
109
+ AtlasMap: lazyBlock(
110
+ () => import('./components/AtlasMap').then((m) => m.AtlasMapEmbed),
111
+ { label: 'Atlas map', fields: { /* … */ }, defaultProps: { /* … */ } },
112
+ (props) => <div className={heightClass[props.height]} aria-busy="true" />,
113
+ ),
114
+ },
115
+ });
116
+ ```
117
+
118
+ Light blocks (text, cards, lists) can keep their static imports — the split
119
+ only pays for itself when the component drags in something big.
120
+
121
+ ## Migrations
122
+
123
+ This module ships **no** migrations — that is the framework convention. Its
124
+ SQLModel tables are picked up by your host's `build_module_metadata()`, so
125
+ after installing it run:
126
+
127
+ ```bash
128
+ make migration msg="add pagebuilder"
129
+ make migrate
130
+ ```
131
+
132
+ Add `branch_labels = ("pagebuilder",)` to that first generated revision so the
133
+ module can later be removed on its own with
134
+ `alembic downgrade pagebuilder@base`.
135
+
136
+ ## Permissions
137
+
138
+ | Permission | Grants |
139
+ |---|---|
140
+ | `pagebuilder.edit` | Draft, save, submit for review, restore a revision |
141
+ | `pagebuilder.publish` | Publish and unpublish directly |
142
+ | `pagebuilder.approve` | Approve or reject a submitted page |
143
+
144
+ `DEFAULT_ROLE_MAP` in `pagebuilder.permissions` suggests three roles —
145
+ `pagebuilder_editor`, `pagebuilder_publisher`, `pagebuilder_approver` —
146
+ cumulatively mapped to those permissions.
147
+
148
+ ## Settings
149
+
150
+ Stored in the database and edited under **Settings → PageBuilder**. There is
151
+ no `SM_PAGEBUILDER_*` environment variable: the settings class drops every env
152
+ source, so a value can only come from the store or from the default below.
153
+ Headless deployments write them with `scripts/set_setting.py`.
154
+
155
+ Fields marked ● are read once while the app boots — they decide which routes
156
+ are mounted and what the auth layer exempts — so changing one needs a restart.
157
+ The Settings screen says so next to the input.
158
+
159
+ | Setting | Default | Purpose |
160
+ |---|---|---|
161
+ | `public_route_prefix` ● | `/p` | Where published pages are served |
162
+ | `content_locales` ● | `["en"]` | Languages a page may be authored in |
163
+ | `default_content_locale` ● | `en` | The language served at the unprefixed URL |
164
+ | `requires_auth` | `true` | Gate the admin surface behind authentication |
165
+ | `csrf_protect` | `true` | Require a CSRF token on mutating admin requests |
166
+ | `media_root` ● | `var/pagebuilder/media` | Upload storage directory |
167
+ | `media_url_prefix` ● | `/media/pagebuilder` | Public URL prefix for uploads |
168
+ | `media_max_bytes` | `10485760` | Per-upload size ceiling |
169
+ | `media_thumbnail_widths` | `320,640,1280,1920` | Widths generated as WebP |
170
+ | `media_webp_quality` | `82` | WebP encoder quality |
171
+ | `snapshot_root` | `var/pagebuilder/snapshots` | Snapshot + blob storage directory |
172
+ | `snapshot_max_upload_bytes` | `209715200` | Ceiling on an uploaded bundle (200 MB) |
173
+ | `snapshot_max_extracted_bytes` | `1073741824` | Ceiling on what a bundle may expand to (1 GB) |
174
+ | `public_csp` | see `settings.py` | CSP header on public pages |
175
+ | `public_cache_max_age` | `60` | `max-age` on public pages |
176
+ | `public_base_url` | `None` | Absolute base for canonical URLs and the sitemap |
177
+ | `site_name` | `None` | OpenGraph site name |
178
+ | `sitemap_enabled` ● | `true` | Serve `/sitemap.xml` |
179
+ | `robots_enabled` ● | `true` | Serve `/robots.txt` |
180
+ | `scheduler_enabled` ● | `true` | Run the scheduled publish/unpublish poller |
181
+ | `scheduler_interval_seconds` ● | `30` | Poll interval |
182
+
183
+ ## Routes
184
+
185
+ **Admin views** (under `/pagebuilder`): `/`, `/new`, `/{page_id}/edit`,
186
+ `/pending`, `/layout`, `/media`, `/trash`, `/content`, `/content/review`.
187
+
188
+ **Admin API** (under `/api/pagebuilder`): pages CRUD, workflow transitions,
189
+ revisions and diffs, layout and its revisions, uploads, snapshots and imports.
190
+
191
+ **Public**: `/p/{slug}`, `/{locale}/p/{slug}` (one mount per non-default
192
+ content locale), `/sitemap.xml`, `/robots.txt`.
193
+
194
+ ## Multilingual content
195
+
196
+ Off by default: with one content locale nothing changes, no locale-prefixed
197
+ route is mounted, and every existing URL is exactly what it was.
198
+
199
+ ```bash
200
+ python scripts/set_setting.py pagebuilder \
201
+ content_locales '["en","de","fr"]' default_content_locale en
202
+ ```
203
+
204
+ Or on the Settings screen, which is the same store. Either way it takes effect
205
+ at the next boot: the languages decide how many public routers are mounted.
206
+
207
+ These are *content* languages, deliberately separate from the host's
208
+ `SM_I18N_SUPPORTED_LOCALES`, which decides what language the admin console
209
+ speaks. A site can translate its content without translating its console, or
210
+ the reverse.
211
+
212
+ **A translation is an ordinary page.** It has its own row, slug, draft,
213
+ revisions, schedule and approval state; what makes two pages translations of
214
+ each other is a shared `translation_group`. Publishing one never publishes
215
+ another, and there is no "master" — deleting the English page leaves the German
216
+ one intact.
217
+
218
+ **Addresses.** The default locale keeps the bare prefix (`/p/about`) so adding
219
+ a language strands no link that already exists; every other locale is prefixed
220
+ (`/de/p/about`). `/{default}/p/{slug}` permanently redirects to the bare form,
221
+ so one document never answers at two URLs. Slugs are unique *per language*, so
222
+ `/p/about` and `/de/p/about` can both be "about".
223
+
224
+ **Discovery.** A published page emits `hreflang` links for every published
225
+ counterpart plus `x-default`, and the sitemap lists each language at its own
226
+ address with `xhtml:link` alternates — so a crawler finds a translation nothing
227
+ links to yet.
228
+
229
+ **Authoring.** Two ways in, because "we need this in German" is a thought an
230
+ author has while scanning the page list as often as while editing the page:
231
+
232
+ * the page list's **Translate** row action duplicates the page into a language
233
+ it does not have yet — it asks the server which languages are still free
234
+ (the list is paged, so a counterpart may be on a page the browser has not
235
+ loaded), offers a *Copy this page's content* choice, and opens the editor on
236
+ the result;
237
+ * the editor's **Languages** tab lists the site's locales, shows which ones the
238
+ page exists in and where each serves, and starts the ones it does not.
239
+
240
+ Both call `POST /api/pagebuilder/pages/{id}/translations`. A new translation inherits
241
+ the layout, nav membership and the source's title (untranslated, so what still
242
+ needs doing is obvious), starts as a draft, and never inherits `canonical_url`.
243
+ Its breadcrumb parent is the parent's own counterpart, so a trail never crosses
244
+ languages. A page's own language is fixed for its lifetime — moving one would
245
+ strand its slug and orphan the redirect pointing at it.
246
+
247
+ ## Content snapshots
248
+
249
+ **Content › Import / Export** captures the site as a restore point. Every entry
250
+ in that list is a snapshot; they differ only in where they came from —
251
+ `manual` (you took it), `upload` (a bundle from another host), or `pre_restore`
252
+ (taken automatically before a restore).
253
+
254
+ **What a snapshot holds:** every non-trashed page including templates, page
255
+ redirects, the site layout, and the media library's files.
256
+
257
+ **What it deliberately does not hold:**
258
+
259
+ - *Branding.* `pagebuilder` has no Python dependency on the branding module,
260
+ and capture runs in-process. Bundling three fields would force every
261
+ pagebuilder host to install branding for a feature unrelated to it. Re-pick
262
+ the colour and design pack in Settings → Branding after restoring onto a
263
+ fresh host.
264
+ - *Another module's content*, news articles included, for the same reason.
265
+ - *Trashed pages, revision history and audit trails.* A snapshot is the site as
266
+ it stands, not its past; restoring history from another host would fabricate
267
+ an audit trail that never happened here.
268
+
269
+ A restore also does **not** write a `PageRevision` per page, unlike every other
270
+ mutation path. The `pre_restore` snapshot already holds the previous state of
271
+ every page, once, with media deduplicated by digest — a revision row per page
272
+ would duplicate that whole before-state and recover nothing extra. The restore
273
+ itself stays audited: who approved it, when, from which snapshot, and the plan
274
+ they were shown.
275
+
276
+ **Restoring is staged, never immediate.** Choosing Restore computes a plan —
277
+ what is created, what is overwritten, what is unchanged — and parks it for
278
+ approval. Taking a snapshot, uploading and staging need `pagebuilder.publish`;
279
+ only `pagebuilder.approve` can apply one. One import is pending at a time,
280
+ because a plan computed against content that has since changed no longer
281
+ describes what applying would do.
282
+
283
+ **Restoring is reversible and never deletes.** Applying takes a `pre_restore`
284
+ snapshot first, so the previous state is one restore away. A page live on the
285
+ site but absent from the bundle is left alone and reported in the plan.
286
+
287
+ **Portability.** Media URLs embed a host-local filename, so bundled content
288
+ stores `asset://<name>` sentinels instead and they resolve to whatever URL each
289
+ host assigns. `Page.parent_id` and `PageRedirect.page_id` travel as slugs and
290
+ are resolved in a second pass, since a parent may sort after its child and a
291
+ redirect may target a page the same restore creates.
292
+
293
+ **Languages.** A bundle identifies a page by `(locale, slug)`, not by slug
294
+ alone — the same pair the database is unique on — so a site publishing `about`
295
+ in two languages captures and restores as two pages rather than one silently
296
+ overwriting the other. Each document carries its `locale` and its
297
+ `translation_group`, so counterparts stay linked as one document after a
298
+ restore. A parent and a redirect both resolve within their own language, so a
299
+ breadcrumb or a retired address never crosses into another one.
300
+
301
+ Pages in a language the target host does not publish are restored and kept, not
302
+ discarded: nothing routes `/fr/p/…` until `fr` is added to `content_locales`,
303
+ at which point they simply start serving. Bundles written before languages
304
+ existed (`format_version` 1) still restore — every page in one is read as the
305
+ default content locale, which is what the schema guaranteed at the time.
306
+
307
+ ## Contracts
308
+
309
+ DTOs other modules may depend on are exported from `pagebuilder.contracts`.
310
+ Everything else in the package is internal and may change without a major
311
+ version bump.
312
+
313
+ ## Licence
314
+
315
+ MIT
@@ -0,0 +1,292 @@
1
+ # simple_module_pagebuilder
2
+
3
+ Drag-and-drop visual page builder for [simple_module](https://github.com/antosubash/simple_module_python)
4
+ apps. Pages are composed from a reusable block library using
5
+ [Puck](https://puckeditor.com) and stored as JSON; published pages are served
6
+ at `/p/{slug}`.
7
+
8
+ ## Features
9
+
10
+ - **Visual editor** — Puck-based drag-and-drop with a block library (heading,
11
+ text, image, button, columns, spacer) and a media picker.
12
+ - **Site layout** — a shared header/footer layout, edited the same way, wrapped
13
+ around every public page.
14
+ - **Media library** — uploads with magic-byte content sniffing, folder
15
+ organisation, and server-generated WebP thumbnails.
16
+ - **Revisions** — every status transition snapshots the page, with a diff view
17
+ and restore-as-draft.
18
+ - **Approval workflow** — edit → submit for review → approve / reject, gated by
19
+ three separate permissions.
20
+ - **Scheduled publishing** — set `publish_at` / `unpublish_at`; a background
21
+ poller flips status at the due time.
22
+ - **SEO** — per-page meta description, canonical URL, OG image, JSON-LD, plus
23
+ `/sitemap.xml` and `/robots.txt`.
24
+ - **Multilingual content** — a page can exist in several languages, each with
25
+ its own slug, draft and approval state. See below.
26
+ - **Content snapshots** — capture the whole site, download or upload it as a
27
+ `.zip`, and restore behind an approval gate. See below.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ uv add simple_module_pagebuilder
33
+ ```
34
+
35
+ Then add it to your host's `pyproject.toml` dependencies. The module is
36
+ discovered at boot through the `simple_module` entry point — no registration
37
+ code required.
38
+
39
+ Its frontend ships inside the wheel. Pull its JS dependencies into your host's
40
+ client app and regenerate the page manifest with:
41
+
42
+ ```bash
43
+ smpy host sync-js-deps --host-client-app=host/client_app
44
+ smpy host gen-pages --host-dir=host/client_app
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ Once installed and migrated, sign in and open `/pagebuilder`:
50
+
51
+ 1. **Create a page** — `/pagebuilder/new`. Give it a title; the slug is derived
52
+ from it and stays editable.
53
+ 2. **Compose it** — drag blocks from the Puck sidebar. Images come from the
54
+ media library, which uploads and thumbnails them for you.
55
+ 3. **Publish it** — publish directly with `pagebuilder.publish`, or submit for
56
+ review if your role only carries `pagebuilder.edit`. Reviewers work through
57
+ `/pagebuilder/pending`.
58
+ 4. **Schedule it** — set `publish_at` / `unpublish_at` instead of publishing
59
+ now, and the poller flips the status at the due time.
60
+ 5. **View it** — the page is live at `/p/{slug}`, wrapped in the site layout
61
+ from `/pagebuilder/layout`.
62
+
63
+ Every transition writes a revision, so `/pagebuilder/{id}/edit` can compare any
64
+ two revisions and restore either one as a draft.
65
+
66
+ ## Contributing blocks from another module
67
+
68
+ A module ships Puck blocks by calling `registerPuckBlocks` from its
69
+ `puck-blocks.ts`; the host imports every module's registration eagerly at app
70
+ start, so the registry is populated before the first render.
71
+
72
+ Because registration is eager, a block that statically imports a heavy render
73
+ component puts that component's whole dependency graph in the entry chunk —
74
+ on every page of the site, for every visitor. Measured on a consuming site, a
75
+ map block's static import put maplibre-gl (1 MB minified, 62% of the bundle)
76
+ in front of visitors who never opened the map. Register heavy blocks with
77
+ `lazyBlock` instead, which keeps fields and defaults eager but loads the
78
+ component the first time a page actually renders the block:
79
+
80
+ ```tsx
81
+ import { lazyBlock } from '@simple-module-py/pagebuilder/pagebuilder/components/lazyBlock';
82
+ import { registerPuckBlocks } from '@simple-module-py/pagebuilder/pagebuilder/components/blockRegistry';
83
+
84
+ registerPuckBlocks({
85
+ blocks: {
86
+ AtlasMap: lazyBlock(
87
+ () => import('./components/AtlasMap').then((m) => m.AtlasMapEmbed),
88
+ { label: 'Atlas map', fields: { /* … */ }, defaultProps: { /* … */ } },
89
+ (props) => <div className={heightClass[props.height]} aria-busy="true" />,
90
+ ),
91
+ },
92
+ });
93
+ ```
94
+
95
+ Light blocks (text, cards, lists) can keep their static imports — the split
96
+ only pays for itself when the component drags in something big.
97
+
98
+ ## Migrations
99
+
100
+ This module ships **no** migrations — that is the framework convention. Its
101
+ SQLModel tables are picked up by your host's `build_module_metadata()`, so
102
+ after installing it run:
103
+
104
+ ```bash
105
+ make migration msg="add pagebuilder"
106
+ make migrate
107
+ ```
108
+
109
+ Add `branch_labels = ("pagebuilder",)` to that first generated revision so the
110
+ module can later be removed on its own with
111
+ `alembic downgrade pagebuilder@base`.
112
+
113
+ ## Permissions
114
+
115
+ | Permission | Grants |
116
+ |---|---|
117
+ | `pagebuilder.edit` | Draft, save, submit for review, restore a revision |
118
+ | `pagebuilder.publish` | Publish and unpublish directly |
119
+ | `pagebuilder.approve` | Approve or reject a submitted page |
120
+
121
+ `DEFAULT_ROLE_MAP` in `pagebuilder.permissions` suggests three roles —
122
+ `pagebuilder_editor`, `pagebuilder_publisher`, `pagebuilder_approver` —
123
+ cumulatively mapped to those permissions.
124
+
125
+ ## Settings
126
+
127
+ Stored in the database and edited under **Settings → PageBuilder**. There is
128
+ no `SM_PAGEBUILDER_*` environment variable: the settings class drops every env
129
+ source, so a value can only come from the store or from the default below.
130
+ Headless deployments write them with `scripts/set_setting.py`.
131
+
132
+ Fields marked ● are read once while the app boots — they decide which routes
133
+ are mounted and what the auth layer exempts — so changing one needs a restart.
134
+ The Settings screen says so next to the input.
135
+
136
+ | Setting | Default | Purpose |
137
+ |---|---|---|
138
+ | `public_route_prefix` ● | `/p` | Where published pages are served |
139
+ | `content_locales` ● | `["en"]` | Languages a page may be authored in |
140
+ | `default_content_locale` ● | `en` | The language served at the unprefixed URL |
141
+ | `requires_auth` | `true` | Gate the admin surface behind authentication |
142
+ | `csrf_protect` | `true` | Require a CSRF token on mutating admin requests |
143
+ | `media_root` ● | `var/pagebuilder/media` | Upload storage directory |
144
+ | `media_url_prefix` ● | `/media/pagebuilder` | Public URL prefix for uploads |
145
+ | `media_max_bytes` | `10485760` | Per-upload size ceiling |
146
+ | `media_thumbnail_widths` | `320,640,1280,1920` | Widths generated as WebP |
147
+ | `media_webp_quality` | `82` | WebP encoder quality |
148
+ | `snapshot_root` | `var/pagebuilder/snapshots` | Snapshot + blob storage directory |
149
+ | `snapshot_max_upload_bytes` | `209715200` | Ceiling on an uploaded bundle (200 MB) |
150
+ | `snapshot_max_extracted_bytes` | `1073741824` | Ceiling on what a bundle may expand to (1 GB) |
151
+ | `public_csp` | see `settings.py` | CSP header on public pages |
152
+ | `public_cache_max_age` | `60` | `max-age` on public pages |
153
+ | `public_base_url` | `None` | Absolute base for canonical URLs and the sitemap |
154
+ | `site_name` | `None` | OpenGraph site name |
155
+ | `sitemap_enabled` ● | `true` | Serve `/sitemap.xml` |
156
+ | `robots_enabled` ● | `true` | Serve `/robots.txt` |
157
+ | `scheduler_enabled` ● | `true` | Run the scheduled publish/unpublish poller |
158
+ | `scheduler_interval_seconds` ● | `30` | Poll interval |
159
+
160
+ ## Routes
161
+
162
+ **Admin views** (under `/pagebuilder`): `/`, `/new`, `/{page_id}/edit`,
163
+ `/pending`, `/layout`, `/media`, `/trash`, `/content`, `/content/review`.
164
+
165
+ **Admin API** (under `/api/pagebuilder`): pages CRUD, workflow transitions,
166
+ revisions and diffs, layout and its revisions, uploads, snapshots and imports.
167
+
168
+ **Public**: `/p/{slug}`, `/{locale}/p/{slug}` (one mount per non-default
169
+ content locale), `/sitemap.xml`, `/robots.txt`.
170
+
171
+ ## Multilingual content
172
+
173
+ Off by default: with one content locale nothing changes, no locale-prefixed
174
+ route is mounted, and every existing URL is exactly what it was.
175
+
176
+ ```bash
177
+ python scripts/set_setting.py pagebuilder \
178
+ content_locales '["en","de","fr"]' default_content_locale en
179
+ ```
180
+
181
+ Or on the Settings screen, which is the same store. Either way it takes effect
182
+ at the next boot: the languages decide how many public routers are mounted.
183
+
184
+ These are *content* languages, deliberately separate from the host's
185
+ `SM_I18N_SUPPORTED_LOCALES`, which decides what language the admin console
186
+ speaks. A site can translate its content without translating its console, or
187
+ the reverse.
188
+
189
+ **A translation is an ordinary page.** It has its own row, slug, draft,
190
+ revisions, schedule and approval state; what makes two pages translations of
191
+ each other is a shared `translation_group`. Publishing one never publishes
192
+ another, and there is no "master" — deleting the English page leaves the German
193
+ one intact.
194
+
195
+ **Addresses.** The default locale keeps the bare prefix (`/p/about`) so adding
196
+ a language strands no link that already exists; every other locale is prefixed
197
+ (`/de/p/about`). `/{default}/p/{slug}` permanently redirects to the bare form,
198
+ so one document never answers at two URLs. Slugs are unique *per language*, so
199
+ `/p/about` and `/de/p/about` can both be "about".
200
+
201
+ **Discovery.** A published page emits `hreflang` links for every published
202
+ counterpart plus `x-default`, and the sitemap lists each language at its own
203
+ address with `xhtml:link` alternates — so a crawler finds a translation nothing
204
+ links to yet.
205
+
206
+ **Authoring.** Two ways in, because "we need this in German" is a thought an
207
+ author has while scanning the page list as often as while editing the page:
208
+
209
+ * the page list's **Translate** row action duplicates the page into a language
210
+ it does not have yet — it asks the server which languages are still free
211
+ (the list is paged, so a counterpart may be on a page the browser has not
212
+ loaded), offers a *Copy this page's content* choice, and opens the editor on
213
+ the result;
214
+ * the editor's **Languages** tab lists the site's locales, shows which ones the
215
+ page exists in and where each serves, and starts the ones it does not.
216
+
217
+ Both call `POST /api/pagebuilder/pages/{id}/translations`. A new translation inherits
218
+ the layout, nav membership and the source's title (untranslated, so what still
219
+ needs doing is obvious), starts as a draft, and never inherits `canonical_url`.
220
+ Its breadcrumb parent is the parent's own counterpart, so a trail never crosses
221
+ languages. A page's own language is fixed for its lifetime — moving one would
222
+ strand its slug and orphan the redirect pointing at it.
223
+
224
+ ## Content snapshots
225
+
226
+ **Content › Import / Export** captures the site as a restore point. Every entry
227
+ in that list is a snapshot; they differ only in where they came from —
228
+ `manual` (you took it), `upload` (a bundle from another host), or `pre_restore`
229
+ (taken automatically before a restore).
230
+
231
+ **What a snapshot holds:** every non-trashed page including templates, page
232
+ redirects, the site layout, and the media library's files.
233
+
234
+ **What it deliberately does not hold:**
235
+
236
+ - *Branding.* `pagebuilder` has no Python dependency on the branding module,
237
+ and capture runs in-process. Bundling three fields would force every
238
+ pagebuilder host to install branding for a feature unrelated to it. Re-pick
239
+ the colour and design pack in Settings → Branding after restoring onto a
240
+ fresh host.
241
+ - *Another module's content*, news articles included, for the same reason.
242
+ - *Trashed pages, revision history and audit trails.* A snapshot is the site as
243
+ it stands, not its past; restoring history from another host would fabricate
244
+ an audit trail that never happened here.
245
+
246
+ A restore also does **not** write a `PageRevision` per page, unlike every other
247
+ mutation path. The `pre_restore` snapshot already holds the previous state of
248
+ every page, once, with media deduplicated by digest — a revision row per page
249
+ would duplicate that whole before-state and recover nothing extra. The restore
250
+ itself stays audited: who approved it, when, from which snapshot, and the plan
251
+ they were shown.
252
+
253
+ **Restoring is staged, never immediate.** Choosing Restore computes a plan —
254
+ what is created, what is overwritten, what is unchanged — and parks it for
255
+ approval. Taking a snapshot, uploading and staging need `pagebuilder.publish`;
256
+ only `pagebuilder.approve` can apply one. One import is pending at a time,
257
+ because a plan computed against content that has since changed no longer
258
+ describes what applying would do.
259
+
260
+ **Restoring is reversible and never deletes.** Applying takes a `pre_restore`
261
+ snapshot first, so the previous state is one restore away. A page live on the
262
+ site but absent from the bundle is left alone and reported in the plan.
263
+
264
+ **Portability.** Media URLs embed a host-local filename, so bundled content
265
+ stores `asset://<name>` sentinels instead and they resolve to whatever URL each
266
+ host assigns. `Page.parent_id` and `PageRedirect.page_id` travel as slugs and
267
+ are resolved in a second pass, since a parent may sort after its child and a
268
+ redirect may target a page the same restore creates.
269
+
270
+ **Languages.** A bundle identifies a page by `(locale, slug)`, not by slug
271
+ alone — the same pair the database is unique on — so a site publishing `about`
272
+ in two languages captures and restores as two pages rather than one silently
273
+ overwriting the other. Each document carries its `locale` and its
274
+ `translation_group`, so counterparts stay linked as one document after a
275
+ restore. A parent and a redirect both resolve within their own language, so a
276
+ breadcrumb or a retired address never crosses into another one.
277
+
278
+ Pages in a language the target host does not publish are restored and kept, not
279
+ discarded: nothing routes `/fr/p/…` until `fr` is added to `content_locales`,
280
+ at which point they simply start serving. Bundles written before languages
281
+ existed (`format_version` 1) still restore — every page in one is read as the
282
+ default content locale, which is what the schema guaranteed at the time.
283
+
284
+ ## Contracts
285
+
286
+ DTOs other modules may depend on are exported from `pagebuilder.contracts`.
287
+ Everything else in the package is internal and may change without a major
288
+ version bump.
289
+
290
+ ## Licence
291
+
292
+ MIT
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-module-py/pagebuilder",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "private": true,
5
5
  "description": "Frontend assets for the PageBuilder module",
6
6
  "peerDependencies": {