betterstart-cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/README.md +5 -0
  2. package/dist/assets/adapters/next/integrations/mailchimp/actions/mailchimp.ts +38 -0
  3. package/dist/assets/adapters/next/integrations/mailchimp/integration.ts +33 -0
  4. package/dist/assets/adapters/next/integrations/r2/actions/r2.ts +77 -0
  5. package/dist/assets/adapters/next/integrations/r2/integration.ts +39 -0
  6. package/dist/assets/adapters/next/integrations/resend/actions/resend.ts +138 -0
  7. package/dist/assets/adapters/next/integrations/resend/integration.ts +36 -0
  8. package/dist/assets/adapters/next/plugins/blog/plugin.ts +17 -0
  9. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +169 -0
  10. package/dist/assets/adapters/next/templates/init/admin-globals.css +677 -0
  11. package/dist/assets/adapters/next/templates/init/api/auth-route.ts +3 -0
  12. package/dist/assets/adapters/next/templates/init/api/upload-route.ts +132 -0
  13. package/dist/assets/adapters/next/templates/init/components/layouts/admin-header.tsx +32 -0
  14. package/dist/assets/adapters/next/templates/init/components/layouts/admin-nav-link.tsx +23 -0
  15. package/dist/assets/adapters/next/templates/init/components/layouts/admin-providers.tsx +33 -0
  16. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-branding-rsc.tsx +8 -0
  17. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-branding-skeleton.tsx +10 -0
  18. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-nav-link-skeleton.tsx +11 -0
  19. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-nav-link.tsx +12 -0
  20. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-user-menu-skeleton.tsx +15 -0
  21. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-user-menu.tsx +90 -0
  22. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +78 -0
  23. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sign-out.tsx +44 -0
  24. package/dist/assets/adapters/next/templates/init/components/layouts/content-skeleton.tsx +44 -0
  25. package/dist/assets/adapters/next/templates/init/components/layouts/sidebar-branding.tsx +41 -0
  26. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table-pagination.tsx +139 -0
  27. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +236 -0
  28. package/dist/assets/adapters/next/templates/init/components/shared/delete-dialog.tsx +67 -0
  29. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/copyable-code-block.tsx +104 -0
  30. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-code-mirror.tsx +68 -0
  31. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +75 -0
  32. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/lifecycle-hooks-tab.tsx +111 -0
  33. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/plain-code-fallback.tsx +11 -0
  34. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +125 -0
  35. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +108 -0
  36. package/dist/assets/adapters/next/templates/init/components/shared/entity-filters-bar.tsx +184 -0
  37. package/dist/assets/adapters/next/templates/init/components/shared/entity-metadata.tsx +93 -0
  38. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-version-item.tsx +55 -0
  39. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-version-restore-dialog.tsx +80 -0
  40. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-button.tsx +74 -0
  41. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-current-row.tsx +48 -0
  42. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-drawer.tsx +79 -0
  43. package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +222 -0
  44. package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog.tsx +56 -0
  45. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +83 -0
  46. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +148 -0
  47. package/dist/assets/adapters/next/templates/init/components/shared/media/media-empty-state.tsx +45 -0
  48. package/dist/assets/adapters/next/templates/init/components/shared/media/media-filters-bar.tsx +129 -0
  49. package/dist/assets/adapters/next/templates/init/components/shared/media/media-gallery-dialog.tsx +182 -0
  50. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-item.tsx +56 -0
  51. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-pagination.tsx +114 -0
  52. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid.tsx +44 -0
  53. package/dist/assets/adapters/next/templates/init/components/shared/media/media-preview.tsx +69 -0
  54. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +139 -0
  55. package/dist/assets/adapters/next/templates/init/components/shared/page-header.tsx +46 -0
  56. package/dist/assets/adapters/next/templates/init/components/shared/search-input.tsx +88 -0
  57. package/dist/assets/adapters/next/templates/init/components/shared/sort-indicator.tsx +24 -0
  58. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-dialog.tsx +242 -0
  59. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-drag-overlay-item.tsx +15 -0
  60. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-item.tsx +32 -0
  61. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-types.ts +9 -0
  62. package/dist/assets/adapters/next/templates/init/data/navigation.ts +43 -0
  63. package/dist/assets/adapters/next/templates/init/drizzle.config.ts +36 -0
  64. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-blockquote.ts +251 -0
  65. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-code-block.ts +258 -0
  66. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-color-highlight.ts +347 -0
  67. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-media-insertion.ts +59 -0
  68. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-mobile-toolbar.ts +17 -0
  69. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-slash-menu.ts +116 -0
  70. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +638 -0
  71. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-table-add-controls.ts +174 -0
  72. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor.ts +288 -0
  73. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-heading-dropdown-menu.ts +127 -0
  74. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-heading.ts +269 -0
  75. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-is-breakpoint.ts +32 -0
  76. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-link-popover.ts +278 -0
  77. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-list-dropdown-menu.ts +199 -0
  78. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-list.ts +290 -0
  79. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-mark.ts +199 -0
  80. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-menu-navigation.ts +221 -0
  81. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-tiptap-editor.ts +46 -0
  82. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-undo-redo.ts +169 -0
  83. package/dist/assets/adapters/next/templates/init/hooks/use-admin-theme.tsx +74 -0
  84. package/dist/assets/adapters/next/templates/init/hooks/use-copy-to-clipboard.ts +48 -0
  85. package/dist/assets/adapters/next/templates/init/hooks/use-dev-mode-integration.ts +43 -0
  86. package/dist/assets/adapters/next/templates/init/hooks/use-entity-versions.ts +32 -0
  87. package/dist/assets/adapters/next/templates/init/hooks/use-failed-image-url.ts +21 -0
  88. package/dist/assets/adapters/next/templates/init/hooks/use-local-storage.ts +46 -0
  89. package/dist/assets/adapters/next/templates/init/hooks/use-media-filters-bar.ts +72 -0
  90. package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +102 -0
  91. package/dist/assets/adapters/next/templates/init/hooks/use-mobile.ts +19 -0
  92. package/dist/assets/adapters/next/templates/init/hooks/use-page-boundary-blur-visibility.ts +59 -0
  93. package/dist/assets/adapters/next/templates/init/hooks/use-page-scroll-threshold.ts +27 -0
  94. package/dist/assets/adapters/next/templates/init/hooks/use-table-utils.ts +315 -0
  95. package/dist/assets/adapters/next/templates/init/hooks/use-upload.ts +321 -0
  96. package/dist/assets/adapters/next/templates/init/hooks/use-users.ts +12 -0
  97. package/dist/assets/adapters/next/templates/init/lib/actions/auth/auth.ts +58 -0
  98. package/dist/assets/adapters/next/templates/init/lib/actions/auth/client.ts +12 -0
  99. package/dist/assets/adapters/next/templates/init/lib/actions/auth/middleware.ts +46 -0
  100. package/dist/assets/adapters/next/templates/init/lib/actions/email/form-delivery.ts +72 -0
  101. package/dist/assets/adapters/next/templates/init/lib/actions/email/index.ts +4 -0
  102. package/dist/assets/adapters/next/templates/init/lib/actions/email/is-email-delivery-configured.ts +5 -0
  103. package/dist/assets/adapters/next/templates/init/lib/actions/email/none.ts +24 -0
  104. package/dist/assets/adapters/next/templates/init/lib/actions/email/provider.ts +6 -0
  105. package/dist/assets/adapters/next/templates/init/lib/actions/email/send-email.ts +6 -0
  106. package/dist/assets/adapters/next/templates/init/lib/actions/email/send-password-reset-email.ts +10 -0
  107. package/dist/assets/adapters/next/templates/init/lib/actions/email/types.ts +25 -0
  108. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/get-entity-versions.ts +48 -0
  109. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/index.ts +10 -0
  110. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/internal-create-entity-version.ts +43 -0
  111. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/internal-delete-entity-versions.ts +19 -0
  112. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/types.ts +26 -0
  113. package/dist/assets/adapters/next/templates/init/lib/actions/forms/get-all-form-settings.ts +21 -0
  114. package/dist/assets/adapters/next/templates/init/lib/actions/forms/get-form-settings.ts +27 -0
  115. package/dist/assets/adapters/next/templates/init/lib/actions/forms/index.ts +9 -0
  116. package/dist/assets/adapters/next/templates/init/lib/actions/forms/test-form-webhook.ts +40 -0
  117. package/dist/assets/adapters/next/templates/init/lib/actions/forms/types.ts +26 -0
  118. package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +40 -0
  119. package/dist/assets/adapters/next/templates/init/lib/actions/media/create-media.ts +39 -0
  120. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media-bulk.ts +29 -0
  121. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media.ts +22 -0
  122. package/dist/assets/adapters/next/templates/init/lib/actions/media/get-media-by-id.ts +18 -0
  123. package/dist/assets/adapters/next/templates/init/lib/actions/media/get-media-by-ids.ts +25 -0
  124. package/dist/assets/adapters/next/templates/init/lib/actions/media/get-media.ts +71 -0
  125. package/dist/assets/adapters/next/templates/init/lib/actions/media/index.ts +14 -0
  126. package/dist/assets/adapters/next/templates/init/lib/actions/media/types.ts +31 -0
  127. package/dist/assets/adapters/next/templates/init/lib/actions/media/update-media.ts +35 -0
  128. package/dist/assets/adapters/next/templates/init/lib/actions/profile/index.ts +4 -0
  129. package/dist/assets/adapters/next/templates/init/lib/actions/profile/invalidate-users-cache.ts +14 -0
  130. package/dist/assets/adapters/next/templates/init/lib/actions/profile/is-email-configured.ts +7 -0
  131. package/dist/assets/adapters/next/templates/init/lib/actions/profile/types.ts +4 -0
  132. package/dist/assets/adapters/next/templates/init/lib/actions/profile/update-email.ts +57 -0
  133. package/dist/assets/adapters/next/templates/init/lib/actions/storage/index.ts +2 -0
  134. package/dist/assets/adapters/next/templates/init/lib/actions/storage/local.ts +31 -0
  135. package/dist/assets/adapters/next/templates/init/lib/actions/storage/provider.ts +6 -0
  136. package/dist/assets/adapters/next/templates/init/lib/actions/storage/save-upload.ts +11 -0
  137. package/dist/assets/adapters/next/templates/init/lib/actions/storage/types.ts +18 -0
  138. package/dist/assets/adapters/next/templates/init/lib/actions/upload/index.ts +9 -0
  139. package/dist/assets/adapters/next/templates/init/lib/actions/upload/types.ts +17 -0
  140. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-file.ts +11 -0
  141. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-files.ts +92 -0
  142. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-image-from-url.ts +22 -0
  143. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-media-from-url.ts +133 -0
  144. package/dist/assets/adapters/next/templates/init/lib/actions/users/create-user.ts +55 -0
  145. package/dist/assets/adapters/next/templates/init/lib/actions/users/delete-user.ts +24 -0
  146. package/dist/assets/adapters/next/templates/init/lib/actions/users/get-users.ts +49 -0
  147. package/dist/assets/adapters/next/templates/init/lib/actions/users/index.ts +12 -0
  148. package/dist/assets/adapters/next/templates/init/lib/actions/users/types.ts +43 -0
  149. package/dist/assets/adapters/next/templates/init/lib/actions/users/update-user-role.ts +28 -0
  150. package/dist/assets/adapters/next/templates/init/lib/db/client.ts +13 -0
  151. package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +160 -0
  152. package/dist/assets/adapters/next/templates/init/lib/db/schema.ts +1 -0
  153. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/index.ts +19 -0
  154. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.local.ts +2 -0
  155. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.ts +9 -0
  156. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/registry.ts +55 -0
  157. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +51 -0
  158. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +39 -0
  159. package/dist/assets/adapters/next/templates/init/pages/account-layout.tsx +11 -0
  160. package/dist/assets/adapters/next/templates/init/pages/account-shell-rsc.tsx +30 -0
  161. package/dist/assets/adapters/next/templates/init/pages/admin-layout.tsx +24 -0
  162. package/dist/assets/adapters/next/templates/init/pages/auth-gate-rsc.tsx +6 -0
  163. package/dist/assets/adapters/next/templates/init/pages/authenticated-layout.tsx +18 -0
  164. package/dist/assets/adapters/next/templates/init/pages/dashboard-page.tsx +121 -0
  165. package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +124 -0
  166. package/dist/assets/adapters/next/templates/init/pages/forgot-password-page-skeleton.tsx +24 -0
  167. package/dist/assets/adapters/next/templates/init/pages/forgot-password-page.tsx +21 -0
  168. package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +131 -0
  169. package/dist/assets/adapters/next/templates/init/pages/login-page-rsc.tsx +14 -0
  170. package/dist/assets/adapters/next/templates/init/pages/login-page-skeleton.tsx +26 -0
  171. package/dist/assets/adapters/next/templates/init/pages/login-page.tsx +21 -0
  172. package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +273 -0
  173. package/dist/assets/adapters/next/templates/init/pages/media/media-page-skeleton.tsx +7 -0
  174. package/dist/assets/adapters/next/templates/init/pages/media/media-page.tsx +11 -0
  175. package/dist/assets/adapters/next/templates/init/pages/minimal-account-shell.tsx +25 -0
  176. package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +281 -0
  177. package/dist/assets/adapters/next/templates/init/pages/profile/profile-page.tsx +31 -0
  178. package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +161 -0
  179. package/dist/assets/adapters/next/templates/init/pages/reset-password-page-skeleton.tsx +26 -0
  180. package/dist/assets/adapters/next/templates/init/pages/reset-password-page.tsx +21 -0
  181. package/dist/assets/adapters/next/templates/init/pages/users/columns.tsx +170 -0
  182. package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +221 -0
  183. package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +172 -0
  184. package/dist/assets/adapters/next/templates/init/pages/users/edit-role-dialog.tsx +91 -0
  185. package/dist/assets/adapters/next/templates/init/pages/users/users-page-content.tsx +25 -0
  186. package/dist/assets/adapters/next/templates/init/pages/users/users-page-skeleton.tsx +7 -0
  187. package/dist/assets/adapters/next/templates/init/pages/users/users-page.tsx +11 -0
  188. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +221 -0
  189. package/dist/assets/adapters/next/templates/init/types/auth.ts +71 -0
  190. package/dist/assets/adapters/next/templates/init/types/index.ts +108 -0
  191. package/dist/assets/adapters/next/templates/init/types/navigation.ts +11 -0
  192. package/dist/assets/adapters/next/templates/init/types/table-meta.ts +14 -0
  193. package/dist/assets/adapters/next/templates/init/utils/auth/roles.ts +17 -0
  194. package/dist/assets/adapters/next/templates/init/utils/date/date.ts +90 -0
  195. package/dist/assets/adapters/next/templates/init/utils/dev-mode/code-block-height.ts +9 -0
  196. package/dist/assets/adapters/next/templates/init/utils/editor/content-editor-rich-extensions.ts +824 -0
  197. package/dist/assets/adapters/next/templates/init/utils/editor/content-editor.ts +316 -0
  198. package/dist/assets/adapters/next/templates/init/utils/editor/editor-view.ts +19 -0
  199. package/dist/assets/adapters/next/templates/init/utils/editor/markdown.ts +542 -0
  200. package/dist/assets/adapters/next/templates/init/utils/editor/node-attrs.ts +25 -0
  201. package/dist/assets/adapters/next/templates/init/utils/editor/slash-commands.ts +148 -0
  202. package/dist/assets/adapters/next/templates/init/utils/editor/source-media.ts +11 -0
  203. package/dist/assets/adapters/next/templates/init/utils/editor/table-add-controls.ts +91 -0
  204. package/dist/assets/adapters/next/templates/init/utils/editor/table-bubble.ts +172 -0
  205. package/dist/assets/adapters/next/templates/init/utils/editor/table-input.ts +5 -0
  206. package/dist/assets/adapters/next/templates/init/utils/editor/task-item.ts +19 -0
  207. package/dist/assets/adapters/next/templates/init/utils/editor/tiptap.ts +991 -0
  208. package/dist/assets/adapters/next/templates/init/utils/email/form-delivery.ts +104 -0
  209. package/dist/assets/adapters/next/templates/init/utils/media/fallback.ts +37 -0
  210. package/dist/assets/adapters/next/templates/init/utils/media/media.ts +91 -0
  211. package/dist/assets/adapters/next/templates/init/utils/media/query.ts +96 -0
  212. package/dist/assets/adapters/next/templates/init/utils/navigation/order.ts +6 -0
  213. package/dist/assets/adapters/next/templates/init/utils/navigation/sidebar.ts +26 -0
  214. package/dist/assets/adapters/next/templates/init/utils/page/boundary.ts +32 -0
  215. package/dist/assets/adapters/next/templates/init/utils/seo/seo.ts +90 -0
  216. package/dist/assets/adapters/next/templates/init/utils/shared/cn.ts +6 -0
  217. package/dist/assets/adapters/next/templates/init/utils/storage/local.ts +9 -0
  218. package/dist/assets/adapters/next/templates/init/utils/table/table.ts +10 -0
  219. package/dist/assets/adapters/next/templates/init/utils/text/text.ts +4 -0
  220. package/dist/assets/adapters/next/templates/init/utils/theme/system.ts +6 -0
  221. package/dist/assets/adapters/next/templates/init/utils/upload/remote.ts +55 -0
  222. package/dist/assets/adapters/next/templates/init/utils/upload/upload.ts +26 -0
  223. package/dist/assets/adapters/next/templates/init/utils/user/user.ts +11 -0
  224. package/dist/assets/adapters/next/templates/init/utils/validation/validation.ts +114 -0
  225. package/dist/assets/adapters/next/templates/init/utils/webhook/webhook.ts +28 -0
  226. package/dist/assets/shared-assets/react-admin/custom/content-editor/editor-toolbar.tsx +25 -0
  227. package/dist/assets/shared-assets/react-admin/custom/content-editor/horizontal-rule-button.tsx +22 -0
  228. package/dist/assets/shared-assets/react-admin/custom/content-editor/index.tsx +142 -0
  229. package/dist/assets/shared-assets/react-admin/custom/content-editor/main-toolbar-content.tsx +118 -0
  230. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-bubble-menu.tsx +80 -0
  231. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-button.tsx +22 -0
  232. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-editor-controls.tsx +117 -0
  233. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-popover-button.tsx +59 -0
  234. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-popover.tsx +6 -0
  235. package/dist/assets/shared-assets/react-admin/custom/content-editor/media-gallery-block.tsx +31 -0
  236. package/dist/assets/shared-assets/react-admin/custom/content-editor/mobile-toolbar-content.tsx +56 -0
  237. package/dist/assets/shared-assets/react-admin/custom/content-editor/mode-toggle-button.tsx +29 -0
  238. package/dist/assets/shared-assets/react-admin/custom/content-editor/remove-table-part-icon.tsx +17 -0
  239. package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +105 -0
  240. package/dist/assets/shared-assets/react-admin/custom/content-editor/slash-command-menu.tsx +65 -0
  241. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode-dropdown-button.tsx +52 -0
  242. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +360 -0
  243. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-add-controls.tsx +46 -0
  244. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-bubble-menu.tsx +290 -0
  245. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-button.tsx +116 -0
  246. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-extension/node-background-extension.ts +138 -0
  247. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +10 -0
  248. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/media-gallery-placeholder-node/index.tsx +1 -0
  249. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/media-gallery-placeholder-node/media-gallery-placeholder-node-extension.ts +117 -0
  250. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/media-gallery-placeholder-node/media-gallery-placeholder-node.tsx +63 -0
  251. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/removable-image-node/index.tsx +1 -0
  252. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/removable-image-node/removable-image-node-extension.ts +11 -0
  253. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/removable-image-node/removable-image-node.tsx +168 -0
  254. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/task-item-node/task-item-node-extension.tsx +142 -0
  255. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/blockquote-button/blockquote-button.tsx +114 -0
  256. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/blockquote-button/index.tsx +1 -0
  257. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/code-block-button/code-block-button.tsx +112 -0
  258. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/code-block-button/index.tsx +1 -0
  259. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-button/color-highlight-button.tsx +185 -0
  260. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-button/index.tsx +1 -0
  261. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover-button.tsx +40 -0
  262. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover-content.tsx +130 -0
  263. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +98 -0
  264. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/highlight-color-button.tsx +75 -0
  265. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/highlight-colors.ts +24 -0
  266. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/index.tsx +1 -0
  267. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/source-color-highlight-popover.tsx +65 -0
  268. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/control-options.ts +27 -0
  269. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu-item.tsx +35 -0
  270. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +119 -0
  271. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/index.tsx +1 -0
  272. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/index.tsx +1 -0
  273. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-button.tsx +39 -0
  274. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-content.tsx +13 -0
  275. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-control-popover.tsx +90 -0
  276. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-main.tsx +96 -0
  277. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-popover.tsx +121 -0
  278. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/index.tsx +1 -0
  279. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu-item.tsx +44 -0
  280. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +115 -0
  281. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/mark-button/index.tsx +1 -0
  282. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/mark-button/mark-button.tsx +117 -0
  283. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/undo-redo-button/index.tsx +1 -0
  284. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/undo-redo-button/undo-redo-button.tsx +115 -0
  285. package/dist/assets/shared-assets/react-admin/custom/date-picker.tsx +70 -0
  286. package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +216 -0
  287. package/dist/assets/shared-assets/react-admin/custom/dynamic-list-field.tsx +111 -0
  288. package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +124 -0
  289. package/dist/assets/shared-assets/react-admin/custom/gallery-thumbnail.tsx +39 -0
  290. package/dist/assets/shared-assets/react-admin/custom/icon-picker.tsx +377 -0
  291. package/dist/assets/shared-assets/react-admin/custom/icons-column-skeleton.tsx +16 -0
  292. package/dist/assets/shared-assets/react-admin/custom/icons-data.ts +8 -0
  293. package/dist/assets/shared-assets/react-admin/custom/logo.tsx +115 -0
  294. package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +182 -0
  295. package/dist/assets/shared-assets/react-admin/custom/page-skeleton.tsx +11 -0
  296. package/dist/assets/shared-assets/react-admin/custom/placeholder-card.tsx +34 -0
  297. package/dist/assets/shared-assets/react-admin/custom/placeholder.tsx +25 -0
  298. package/dist/assets/shared-assets/react-admin/custom/progressive-blur.tsx +62 -0
  299. package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +68 -0
  300. package/dist/assets/shared-assets/react-admin/custom/upload-dropzone.tsx +107 -0
  301. package/dist/assets/shared-assets/react-admin/dependencies.ts +73 -0
  302. package/dist/assets/shared-assets/react-admin/schema.json +1670 -0
  303. package/dist/assets/shared-assets/react-admin/ui/accordion.tsx +86 -0
  304. package/dist/assets/shared-assets/react-admin/ui/alert-dialog.tsx +178 -0
  305. package/dist/assets/shared-assets/react-admin/ui/alert.tsx +72 -0
  306. package/dist/assets/shared-assets/react-admin/ui/aspect-ratio.tsx +9 -0
  307. package/dist/assets/shared-assets/react-admin/ui/avatar.tsx +95 -0
  308. package/dist/assets/shared-assets/react-admin/ui/badge.tsx +48 -0
  309. package/dist/assets/shared-assets/react-admin/ui/breadcrumb.tsx +99 -0
  310. package/dist/assets/shared-assets/react-admin/ui/button-group.tsx +76 -0
  311. package/dist/assets/shared-assets/react-admin/ui/button.tsx +66 -0
  312. package/dist/assets/shared-assets/react-admin/ui/calendar.tsx +184 -0
  313. package/dist/assets/shared-assets/react-admin/ui/card.tsx +94 -0
  314. package/dist/assets/shared-assets/react-admin/ui/carousel.tsx +239 -0
  315. package/dist/assets/shared-assets/react-admin/ui/chart.tsx +336 -0
  316. package/dist/assets/shared-assets/react-admin/ui/checkbox.tsx +28 -0
  317. package/dist/assets/shared-assets/react-admin/ui/collapsible.tsx +21 -0
  318. package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +272 -0
  319. package/dist/assets/shared-assets/react-admin/ui/command.tsx +180 -0
  320. package/dist/assets/shared-assets/react-admin/ui/context-menu.tsx +243 -0
  321. package/dist/assets/shared-assets/react-admin/ui/dialog.tsx +141 -0
  322. package/dist/assets/shared-assets/react-admin/ui/direction.tsx +20 -0
  323. package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +119 -0
  324. package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +253 -0
  325. package/dist/assets/shared-assets/react-admin/ui/empty.tsx +93 -0
  326. package/dist/assets/shared-assets/react-admin/ui/field.tsx +234 -0
  327. package/dist/assets/shared-assets/react-admin/ui/form.tsx +172 -0
  328. package/dist/assets/shared-assets/react-admin/ui/hover-card.tsx +37 -0
  329. package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +134 -0
  330. package/dist/assets/shared-assets/react-admin/ui/input-otp.tsx +85 -0
  331. package/dist/assets/shared-assets/react-admin/ui/input.tsx +18 -0
  332. package/dist/assets/shared-assets/react-admin/ui/item.tsx +180 -0
  333. package/dist/assets/shared-assets/react-admin/ui/kbd.tsx +26 -0
  334. package/dist/assets/shared-assets/react-admin/ui/label.tsx +20 -0
  335. package/dist/assets/shared-assets/react-admin/ui/menubar.tsx +259 -0
  336. package/dist/assets/shared-assets/react-admin/ui/native-select.tsx +54 -0
  337. package/dist/assets/shared-assets/react-admin/ui/navigation-menu.tsx +159 -0
  338. package/dist/assets/shared-assets/react-admin/ui/pagination.tsx +111 -0
  339. package/dist/assets/shared-assets/react-admin/ui/popover.tsx +75 -0
  340. package/dist/assets/shared-assets/react-admin/ui/progress.tsx +30 -0
  341. package/dist/assets/shared-assets/react-admin/ui/radio-group.tsx +43 -0
  342. package/dist/assets/shared-assets/react-admin/ui/resizable.tsx +41 -0
  343. package/dist/assets/shared-assets/react-admin/ui/scroll-area.tsx +54 -0
  344. package/dist/assets/shared-assets/react-admin/ui/select.tsx +183 -0
  345. package/dist/assets/shared-assets/react-admin/ui/separator.tsx +27 -0
  346. package/dist/assets/shared-assets/react-admin/ui/sheet.tsx +129 -0
  347. package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +688 -0
  348. package/dist/assets/shared-assets/react-admin/ui/skeleton.tsx +13 -0
  349. package/dist/assets/shared-assets/react-admin/ui/slider.tsx +53 -0
  350. package/dist/assets/shared-assets/react-admin/ui/sonner.tsx +45 -0
  351. package/dist/assets/shared-assets/react-admin/ui/spinner.tsx +15 -0
  352. package/dist/assets/shared-assets/react-admin/ui/switch.tsx +32 -0
  353. package/dist/assets/shared-assets/react-admin/ui/table.tsx +101 -0
  354. package/dist/assets/shared-assets/react-admin/ui/tabs.tsx +79 -0
  355. package/dist/assets/shared-assets/react-admin/ui/textarea.tsx +17 -0
  356. package/dist/assets/shared-assets/react-admin/ui/toggle-group.tsx +85 -0
  357. package/dist/assets/shared-assets/react-admin/ui/toggle.tsx +45 -0
  358. package/dist/assets/shared-assets/react-admin/ui/tooltip.tsx +51 -0
  359. package/dist/chunk-MUZQCVQA.js +306 -0
  360. package/dist/chunk-MUZQCVQA.js.map +1 -0
  361. package/dist/cli.d.ts +2 -0
  362. package/dist/cli.js +23437 -0
  363. package/dist/cli.js.map +1 -0
  364. package/dist/index.d.ts +90 -0
  365. package/dist/index.js +8 -0
  366. package/dist/index.js.map +1 -0
  367. package/dist/template-reader-YKWE2C7O.js +13 -0
  368. package/dist/template-reader-YKWE2C7O.js.map +1 -0
  369. package/package.json +74 -0
@@ -0,0 +1,32 @@
1
+ import { Button } from '@admin/components/ui/button'
2
+ import { cn } from '@admin/utils/shared/cn'
3
+ import { useSortable } from '@dnd-kit/sortable'
4
+ import { CSS } from '@dnd-kit/utilities'
5
+ import { GripVertical } from 'lucide-react'
6
+ import type { SortOrderItem as SortOrderItemData } from './sort-order-types'
7
+
8
+ export function SortableItem({ id, label }: SortOrderItemData) {
9
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
10
+ id
11
+ })
12
+
13
+ return (
14
+ <div ref={setNodeRef} style={{ transform: CSS.Transform.toString(transform), transition }}>
15
+ <Button
16
+ type="button"
17
+ variant="outline"
18
+ className={cn(
19
+ 'shrink-0 w-full justify-start gap-2 px-2 font-normal cursor-grab touch-none active:cursor-grabbing',
20
+ {
21
+ 'opacity-50 z-20': isDragging
22
+ }
23
+ )}
24
+ {...attributes}
25
+ {...listeners}
26
+ >
27
+ <GripVertical strokeWidth={1} />
28
+ <span className="truncate text-sm">{label}</span>
29
+ </Button>
30
+ </div>
31
+ )
32
+ }
@@ -0,0 +1,9 @@
1
+ export interface SortOrderItem {
2
+ id: string
3
+ label: string
4
+ }
5
+
6
+ export interface SortOrderPage {
7
+ items: SortOrderItem[]
8
+ total: number
9
+ }
@@ -0,0 +1,43 @@
1
+ import type { AdminNavigationItem } from '@admin/types/navigation'
2
+ import { House, ImagePlay } from 'lucide-react'
3
+ import { postsNav } from './navigation/posts'
4
+
5
+ export type { AdminNavigationItem } from '@admin/types/navigation'
6
+
7
+ const coreNav: AdminNavigationItem[] = [
8
+ {
9
+ label: 'Overview',
10
+ href: '/admin',
11
+ icon: House,
12
+ position: 0
13
+ },
14
+ {
15
+ label: 'Media',
16
+ href: '/admin/media',
17
+ icon: ImagePlay,
18
+ position: 1
19
+ }
20
+ ]
21
+
22
+ function comparePositions(a?: number, b?: number): number {
23
+ if (a != null && b != null) return a - b
24
+ if (a != null) return -1
25
+ if (b != null) return 1
26
+ return 0
27
+ }
28
+
29
+ const combinedNavigation = [...coreNav, ...postsNav]
30
+
31
+ const dashboard = combinedNavigation.find((item) => item.href === '/admin')
32
+ const others = combinedNavigation.filter((item) => item.href !== '/admin')
33
+
34
+ others.sort((a, b) => {
35
+ if (!a.group && b.group) return -1
36
+ if (a.group && !b.group) return 1
37
+ if (a.group && b.group && a.group !== b.group) {
38
+ return comparePositions(a.groupPosition, b.groupPosition) || a.group.localeCompare(b.group)
39
+ }
40
+ return comparePositions(a.position, b.position) || a.label.localeCompare(b.label)
41
+ })
42
+
43
+ export const adminNavigation = [...(dashboard ? [dashboard] : []), ...others]
@@ -0,0 +1,36 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import { defineConfig } from 'drizzle-kit'
4
+
5
+ // Load .env.local (avoids @next/env dependency for drizzle-kit)
6
+ for (const envFile of ['.env.local', '.env']) {
7
+ try {
8
+ const content = readFileSync(resolve(process.cwd(), envFile), 'utf-8')
9
+ for (const line of content.split('\n')) {
10
+ const trimmed = line.trim()
11
+ if (!trimmed || trimmed.startsWith('#')) continue
12
+ const eqIdx = trimmed.indexOf('=')
13
+ if (eqIdx === -1) continue
14
+ const key = trimmed.slice(0, eqIdx).trim()
15
+ const val = trimmed
16
+ .slice(eqIdx + 1)
17
+ .trim()
18
+ .replace(/^['"]|['"]$/g, '')
19
+ if (!process.env[key]) process.env[key] = val
20
+ }
21
+ } catch {}
22
+ }
23
+
24
+ const databaseUrl = process.env.DATABASE_URL
25
+ if (!databaseUrl) {
26
+ throw new Error('DATABASE_URL is required')
27
+ }
28
+
29
+ export default defineConfig({
30
+ out: './admin/lib/db/migrations',
31
+ schema: './admin/lib/db/schema.ts',
32
+ dialect: 'postgresql',
33
+ dbCredentials: {
34
+ url: databaseUrl
35
+ }
36
+ })
@@ -0,0 +1,251 @@
1
+ 'use client'
2
+
3
+ // --- Hooks ---
4
+ import { useTiptapEditor } from '@admin/hooks/content-editor/use-tiptap-editor'
5
+ // --- UI Utils ---
6
+ import {
7
+ findNodePosition,
8
+ getSelectedBlockNodes,
9
+ isNodeInSchema,
10
+ isNodeTypeSelected,
11
+ isValidPosition,
12
+ selectionWithinConvertibleTypes
13
+ } from '@admin/utils/editor/tiptap'
14
+ import { NodeSelection, TextSelection } from '@tiptap/pm/state'
15
+ import type { Editor } from '@tiptap/react'
16
+ // --- Icons ---
17
+ import { TextQuoteIcon as BlockquoteIcon } from 'lucide-react'
18
+ import { useCallback, useEffect, useState } from 'react'
19
+
20
+ export const BLOCKQUOTE_SHORTCUT_KEY = 'mod+shift+b'
21
+
22
+ /**
23
+ * Configuration for the blockquote functionality
24
+ */
25
+ export interface UseBlockquoteConfig {
26
+ /**
27
+ * The Tiptap editor instance.
28
+ */
29
+ editor?: Editor | null
30
+ /**
31
+ * Whether the button should hide when blockquote is not available.
32
+ * @default false
33
+ */
34
+ hideWhenUnavailable?: boolean
35
+ /**
36
+ * Callback function called after a successful toggle.
37
+ */
38
+ onToggled?: () => void
39
+ }
40
+
41
+ /**
42
+ * Checks if blockquote can be toggled in the current editor state
43
+ */
44
+ export function canToggleBlockquote(editor: Editor | null, turnInto: boolean = true): boolean {
45
+ if (!editor || !editor.isEditable) return false
46
+ if (!isNodeInSchema('blockquote', editor) || isNodeTypeSelected(editor, ['image'])) return false
47
+
48
+ if (!turnInto) {
49
+ return editor.can().toggleWrap('blockquote')
50
+ }
51
+
52
+ // Ensure selection is in nodes we're allowed to convert
53
+ if (
54
+ !selectionWithinConvertibleTypes(editor, [
55
+ 'paragraph',
56
+ 'heading',
57
+ 'bulletList',
58
+ 'orderedList',
59
+ 'taskList',
60
+ 'blockquote',
61
+ 'codeBlock'
62
+ ])
63
+ )
64
+ return false
65
+
66
+ // Either we can wrap in blockquote directly on the selection,
67
+ // or we can clear formatting/nodes to arrive at a blockquote.
68
+ return editor.can().toggleWrap('blockquote') || editor.can().clearNodes()
69
+ }
70
+
71
+ /**
72
+ * Toggles blockquote formatting for a specific node or the current selection
73
+ */
74
+ export function toggleBlockquote(editor: Editor | null): boolean {
75
+ if (!editor || !editor.isEditable) return false
76
+ if (!canToggleBlockquote(editor)) return false
77
+
78
+ try {
79
+ const view = editor.view
80
+ let state = view.state
81
+ let tr = state.tr
82
+
83
+ const blocks = getSelectedBlockNodes(editor)
84
+
85
+ // In case a selection contains multiple blocks, we only allow
86
+ // toggling to nide if there's exactly one block selected
87
+ // we also dont block the canToggle since it will fall back to the bottom logic
88
+ const isPossibleToTurnInto =
89
+ selectionWithinConvertibleTypes(editor, [
90
+ 'paragraph',
91
+ 'heading',
92
+ 'bulletList',
93
+ 'orderedList',
94
+ 'taskList',
95
+ 'blockquote',
96
+ 'codeBlock'
97
+ ]) && blocks.length === 1
98
+
99
+ // No selection, find the the cursor position
100
+ if (
101
+ (state.selection.empty || state.selection instanceof TextSelection) &&
102
+ isPossibleToTurnInto
103
+ ) {
104
+ const pos = findNodePosition({
105
+ editor,
106
+ node: state.selection.$anchor.node(1)
107
+ })?.pos
108
+ if (!isValidPosition(pos)) return false
109
+
110
+ tr = tr.setSelection(NodeSelection.create(state.doc, pos))
111
+ view.dispatch(tr)
112
+ state = view.state
113
+ }
114
+
115
+ const selection = state.selection
116
+
117
+ let chain = editor.chain().focus()
118
+
119
+ // Handle NodeSelection
120
+ if (selection instanceof NodeSelection) {
121
+ const firstChild = selection.node.firstChild?.firstChild
122
+ const lastChild = selection.node.lastChild?.lastChild
123
+
124
+ const from = firstChild ? selection.from + firstChild.nodeSize : selection.from + 1
125
+
126
+ const to = lastChild ? selection.to - lastChild.nodeSize : selection.to - 1
127
+
128
+ const resolvedFrom = state.doc.resolve(from)
129
+ const resolvedTo = state.doc.resolve(to)
130
+
131
+ chain = chain.setTextSelection(TextSelection.between(resolvedFrom, resolvedTo)).clearNodes()
132
+ }
133
+
134
+ const toggle = editor.isActive('blockquote')
135
+ ? chain.lift('blockquote')
136
+ : chain.wrapIn('blockquote')
137
+
138
+ toggle.run()
139
+
140
+ editor.chain().focus().selectTextblockEnd().run()
141
+
142
+ return true
143
+ } catch {
144
+ return false
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Determines if the blockquote button should be shown
150
+ */
151
+ export function shouldShowButton(props: {
152
+ editor: Editor | null
153
+ hideWhenUnavailable: boolean
154
+ }): boolean {
155
+ const { editor, hideWhenUnavailable } = props
156
+
157
+ if (!editor || !editor.isEditable) return false
158
+
159
+ if (!hideWhenUnavailable) {
160
+ return true
161
+ }
162
+
163
+ if (!isNodeInSchema('blockquote', editor)) return false
164
+
165
+ if (!editor.isActive('code')) {
166
+ return canToggleBlockquote(editor)
167
+ }
168
+
169
+ return true
170
+ }
171
+
172
+ /**
173
+ * Custom hook that provides blockquote functionality for Tiptap editor
174
+ *
175
+ * @example
176
+ * ```tsx
177
+ * // Simple usage - no params needed
178
+ * function MySimpleBlockquoteButton() {
179
+ * const { isVisible, handleToggle, isActive } = useBlockquote()
180
+ *
181
+ * if (!isVisible) return null
182
+ *
183
+ * return <button onClick={handleToggle}>Blockquote</button>
184
+ * }
185
+ *
186
+ * // Advanced usage with configuration
187
+ * function MyAdvancedBlockquoteButton() {
188
+ * const { isVisible, handleToggle, label, isActive } = useBlockquote({
189
+ * editor: myEditor,
190
+ * hideWhenUnavailable: true,
191
+ * onToggled: () => console.log('Blockquote toggled!')
192
+ * })
193
+ *
194
+ * if (!isVisible) return null
195
+ *
196
+ * return (
197
+ * <MyButton
198
+ * onClick={handleToggle}
199
+ * aria-label={label}
200
+ * aria-pressed={isActive}
201
+ * >
202
+ * Toggle Blockquote
203
+ * </MyButton>
204
+ * )
205
+ * }
206
+ * ```
207
+ */
208
+ export function useBlockquote(config?: UseBlockquoteConfig) {
209
+ const { editor: providedEditor, hideWhenUnavailable = false, onToggled } = config || {}
210
+
211
+ const { editor } = useTiptapEditor(providedEditor)
212
+ const [isVisible, setIsVisible] = useState<boolean>(true)
213
+ const canToggle = canToggleBlockquote(editor)
214
+ const isActive = editor?.isActive('blockquote') || false
215
+
216
+ useEffect(() => {
217
+ if (!editor) return
218
+
219
+ const handleSelectionUpdate = () => {
220
+ setIsVisible(shouldShowButton({ editor, hideWhenUnavailable }))
221
+ }
222
+
223
+ handleSelectionUpdate()
224
+
225
+ editor.on('selectionUpdate', handleSelectionUpdate)
226
+
227
+ return () => {
228
+ editor.off('selectionUpdate', handleSelectionUpdate)
229
+ }
230
+ }, [editor, hideWhenUnavailable])
231
+
232
+ const handleToggle = useCallback(() => {
233
+ if (!editor) return false
234
+
235
+ const success = toggleBlockquote(editor)
236
+ if (success) {
237
+ onToggled?.()
238
+ }
239
+ return success
240
+ }, [editor, onToggled])
241
+
242
+ return {
243
+ isVisible,
244
+ isActive,
245
+ handleToggle,
246
+ canToggle,
247
+ label: 'Blockquote',
248
+ shortcutKeys: BLOCKQUOTE_SHORTCUT_KEY,
249
+ Icon: BlockquoteIcon
250
+ }
251
+ }
@@ -0,0 +1,258 @@
1
+ 'use client'
2
+
3
+ // --- Hooks ---
4
+ import { useTiptapEditor } from '@admin/hooks/content-editor/use-tiptap-editor'
5
+ // --- Lib ---
6
+ import {
7
+ findNodePosition,
8
+ getSelectedBlockNodes,
9
+ isNodeInSchema,
10
+ isNodeTypeSelected,
11
+ isValidPosition,
12
+ selectionWithinConvertibleTypes
13
+ } from '@admin/utils/editor/tiptap'
14
+ import { NodeSelection, TextSelection } from '@tiptap/pm/state'
15
+ import type { Editor } from '@tiptap/react'
16
+ // --- Icons ---
17
+ import { SquareCodeIcon as CodeBlockIcon } from 'lucide-react'
18
+ import { useCallback, useEffect, useState } from 'react'
19
+
20
+ export const CODE_BLOCK_SHORTCUT_KEY = 'mod+alt+c'
21
+
22
+ /**
23
+ * Configuration for the code block functionality
24
+ */
25
+ export interface UseCodeBlockConfig {
26
+ /**
27
+ * The Tiptap editor instance.
28
+ */
29
+ editor?: Editor | null
30
+ /**
31
+ * Whether the button should hide when code block is not available.
32
+ * @default false
33
+ */
34
+ hideWhenUnavailable?: boolean
35
+ /**
36
+ * Callback function called after a successful code block toggle.
37
+ */
38
+ onToggled?: () => void
39
+ }
40
+
41
+ /**
42
+ * Checks if code block can be toggled in the current editor state
43
+ */
44
+ export function canToggle(editor: Editor | null, turnInto: boolean = true): boolean {
45
+ if (!editor || !editor.isEditable) return false
46
+ if (!isNodeInSchema('codeBlock', editor) || isNodeTypeSelected(editor, ['image'])) return false
47
+
48
+ if (!turnInto) {
49
+ return editor.can().toggleNode('codeBlock', 'paragraph')
50
+ }
51
+
52
+ // Ensure selection is in nodes we're allowed to convert
53
+ if (
54
+ !selectionWithinConvertibleTypes(editor, [
55
+ 'paragraph',
56
+ 'heading',
57
+ 'bulletList',
58
+ 'orderedList',
59
+ 'taskList',
60
+ 'blockquote',
61
+ 'codeBlock'
62
+ ])
63
+ )
64
+ return false
65
+
66
+ // Either we can toggle code block directly on the selection,
67
+ // or we can clear formatting/nodes to arrive at a code block.
68
+ return editor.can().toggleNode('codeBlock', 'paragraph') || editor.can().clearNodes()
69
+ }
70
+
71
+ /**
72
+ * Toggles code block in the editor
73
+ */
74
+ export function toggleCodeBlock(editor: Editor | null): boolean {
75
+ if (!editor || !editor.isEditable) return false
76
+ if (!canToggle(editor)) return false
77
+
78
+ try {
79
+ const view = editor.view
80
+ let state = view.state
81
+ let tr = state.tr
82
+
83
+ const blocks = getSelectedBlockNodes(editor)
84
+
85
+ // In case a selection contains multiple blocks, we only allow
86
+ // toggling to nide if there's exactly one block selected
87
+ // we also dont block the canToggle since it will fall back to the bottom logic
88
+ const isPossibleToTurnInto =
89
+ selectionWithinConvertibleTypes(editor, [
90
+ 'paragraph',
91
+ 'heading',
92
+ 'bulletList',
93
+ 'orderedList',
94
+ 'taskList',
95
+ 'blockquote',
96
+ 'codeBlock'
97
+ ]) && blocks.length === 1
98
+
99
+ // No selection, find the the cursor position
100
+ if (
101
+ (state.selection.empty || state.selection instanceof TextSelection) &&
102
+ isPossibleToTurnInto
103
+ ) {
104
+ const pos = findNodePosition({
105
+ editor,
106
+ node: state.selection.$anchor.node(1)
107
+ })?.pos
108
+ if (!isValidPosition(pos)) return false
109
+
110
+ tr = tr.setSelection(NodeSelection.create(state.doc, pos))
111
+ view.dispatch(tr)
112
+ state = view.state
113
+ }
114
+
115
+ const selection = state.selection
116
+
117
+ let chain = editor.chain().focus()
118
+
119
+ // Handle NodeSelection
120
+ if (selection instanceof NodeSelection) {
121
+ const firstChild = selection.node.firstChild?.firstChild
122
+ const lastChild = selection.node.lastChild?.lastChild
123
+
124
+ const from = firstChild ? selection.from + firstChild.nodeSize : selection.from + 1
125
+
126
+ const to = lastChild ? selection.to - lastChild.nodeSize : selection.to - 1
127
+
128
+ const resolvedFrom = state.doc.resolve(from)
129
+ const resolvedTo = state.doc.resolve(to)
130
+
131
+ chain = chain.setTextSelection(TextSelection.between(resolvedFrom, resolvedTo)).clearNodes()
132
+ }
133
+
134
+ const toggle = editor.isActive('codeBlock')
135
+ ? chain.setNode('paragraph')
136
+ : chain.toggleNode('codeBlock', 'paragraph')
137
+
138
+ toggle.run()
139
+
140
+ editor.chain().focus().selectTextblockEnd().run()
141
+
142
+ return true
143
+ } catch {
144
+ return false
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Determines if the code block button should be shown
150
+ */
151
+ export function shouldShowButton(props: {
152
+ editor: Editor | null
153
+ hideWhenUnavailable: boolean
154
+ }): boolean {
155
+ const { editor, hideWhenUnavailable } = props
156
+
157
+ if (!editor || !editor.isEditable) return false
158
+
159
+ if (!hideWhenUnavailable) {
160
+ return true
161
+ }
162
+
163
+ if (!isNodeInSchema('codeBlock', editor)) return false
164
+
165
+ if (!editor.isActive('code')) {
166
+ return canToggle(editor)
167
+ }
168
+
169
+ return true
170
+ }
171
+
172
+ /**
173
+ * Custom hook that provides code block functionality for Tiptap editor
174
+ *
175
+ * @example
176
+ * ```tsx
177
+ * // Simple usage - no params needed
178
+ * function MySimpleCodeBlockButton() {
179
+ * const { isVisible, isActive, handleToggle } = useCodeBlock()
180
+ *
181
+ * if (!isVisible) return null
182
+ *
183
+ * return (
184
+ * <button
185
+ * onClick={handleToggle}
186
+ * aria-pressed={isActive}
187
+ * >
188
+ * Code Block
189
+ * </button>
190
+ * )
191
+ * }
192
+ *
193
+ * // Advanced usage with configuration
194
+ * function MyAdvancedCodeBlockButton() {
195
+ * const { isVisible, isActive, handleToggle, label } = useCodeBlock({
196
+ * editor: myEditor,
197
+ * hideWhenUnavailable: true,
198
+ * onToggled: (isActive) => console.log('Code block toggled:', isActive)
199
+ * })
200
+ *
201
+ * if (!isVisible) return null
202
+ *
203
+ * return (
204
+ * <MyButton
205
+ * onClick={handleToggle}
206
+ * aria-label={label}
207
+ * aria-pressed={isActive}
208
+ * >
209
+ * Toggle Code Block
210
+ * </MyButton>
211
+ * )
212
+ * }
213
+ * ```
214
+ */
215
+ export function useCodeBlock(config?: UseCodeBlockConfig) {
216
+ const { editor: providedEditor, hideWhenUnavailable = false, onToggled } = config || {}
217
+
218
+ const { editor } = useTiptapEditor(providedEditor)
219
+ const [isVisible, setIsVisible] = useState<boolean>(true)
220
+ const canToggleState = canToggle(editor)
221
+ const isActive = editor?.isActive('codeBlock') || false
222
+
223
+ useEffect(() => {
224
+ if (!editor) return
225
+
226
+ const handleSelectionUpdate = () => {
227
+ setIsVisible(shouldShowButton({ editor, hideWhenUnavailable }))
228
+ }
229
+
230
+ handleSelectionUpdate()
231
+
232
+ editor.on('selectionUpdate', handleSelectionUpdate)
233
+
234
+ return () => {
235
+ editor.off('selectionUpdate', handleSelectionUpdate)
236
+ }
237
+ }, [editor, hideWhenUnavailable])
238
+
239
+ const handleToggle = useCallback(() => {
240
+ if (!editor) return false
241
+
242
+ const success = toggleCodeBlock(editor)
243
+ if (success) {
244
+ onToggled?.()
245
+ }
246
+ return success
247
+ }, [editor, onToggled])
248
+
249
+ return {
250
+ isVisible,
251
+ isActive,
252
+ handleToggle,
253
+ canToggle: canToggleState,
254
+ label: 'Code Block',
255
+ shortcutKeys: CODE_BLOCK_SHORTCUT_KEY,
256
+ Icon: CodeBlockIcon
257
+ }
258
+ }