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,991 @@
1
+ import type { JSONContent } from '@tiptap/core'
2
+ import type { Node as PMNode } from '@tiptap/pm/model'
3
+ import type { Transaction } from '@tiptap/pm/state'
4
+ import { AllSelection, NodeSelection, Selection, TextSelection } from '@tiptap/pm/state'
5
+ import {
6
+ addColumn,
7
+ addRow,
8
+ CellSelection,
9
+ cellAround,
10
+ selectedRect,
11
+ TableMap,
12
+ type TableRect
13
+ } from '@tiptap/pm/tables'
14
+ import { type Editor, findParentNodeClosestToPos, type NodeWithPos } from '@tiptap/react'
15
+ import type { CSSProperties } from 'react'
16
+ import {
17
+ type ContentEditorInlineMathDelimiter,
18
+ type ContentEditorMathKind,
19
+ parseContentEditorMathInput
20
+ } from './markdown'
21
+
22
+ export { cn } from '@admin/utils/shared/cn'
23
+
24
+ export const MAC_SYMBOLS: Record<string, string> = {
25
+ mod: '⌘',
26
+ command: '⌘',
27
+ meta: '⌘',
28
+ ctrl: '⌃',
29
+ control: '⌃',
30
+ alt: '⌥',
31
+ option: '⌥',
32
+ shift: '⇧',
33
+ backspace: 'Del',
34
+ delete: '⌦',
35
+ enter: '⏎',
36
+ escape: '⎋',
37
+ capslock: '⇪'
38
+ } as const
39
+
40
+ export const SR_ONLY = {
41
+ position: 'absolute',
42
+ width: '1px',
43
+ height: '1px',
44
+ padding: 0,
45
+ margin: '-1px',
46
+ overflow: 'hidden',
47
+ clip: 'rect(0, 0, 0, 0)',
48
+ whiteSpace: 'nowrap',
49
+ borderWidth: 0
50
+ } as const
51
+
52
+ export interface SlashMenuState {
53
+ from: number
54
+ to: number
55
+ query: string
56
+ rect: DOMRect
57
+ }
58
+
59
+ export interface RunnableSlashCommand {
60
+ run: (editor: Editor) => boolean
61
+ }
62
+
63
+ export type ContentEditorTableAlignment = 'left' | 'center' | 'right'
64
+
65
+ export interface ContentEditorSelectedMath {
66
+ delimiter?: ContentEditorInlineMathDelimiter
67
+ kind: ContentEditorMathKind
68
+ node: PMNode
69
+ pos: number
70
+ raw: string
71
+ value: string
72
+ }
73
+
74
+ const SLASH_QUERY_LIMIT = 48
75
+ const DEFAULT_CONTENT_EDITOR_TABLE_ROWS = 3
76
+ const DEFAULT_CONTENT_EDITOR_TABLE_COLS = 3
77
+ const CONTENT_EDITOR_MEDIA_PLACEHOLDER_NODE = 'mediaGalleryPlaceholder'
78
+
79
+ export const CONTENT_EDITOR_MEDIA_SHORTCUT_KEY = 'mod+shift+i'
80
+
81
+ export function getSlashMenuState(editor: Editor): SlashMenuState | null {
82
+ const { selection } = editor.state
83
+
84
+ if (!selection.empty || !editor.isEditable) return null
85
+
86
+ const { $from } = selection
87
+ const parent = $from.parent
88
+
89
+ if (!parent.isTextblock || parent.type.name === 'codeBlock') return null
90
+
91
+ const textBeforeCursor = parent.textBetween(0, $from.parentOffset, '\n', '\ufffc')
92
+ const slashIndex = textBeforeCursor.lastIndexOf('/')
93
+
94
+ if (slashIndex === -1) return null
95
+ if (slashIndex > 0 && !/\s/.test(textBeforeCursor[slashIndex - 1])) {
96
+ return null
97
+ }
98
+
99
+ const query = textBeforeCursor.slice(slashIndex + 1)
100
+
101
+ if (query.includes('/') || query.length > SLASH_QUERY_LIMIT) return null
102
+
103
+ const from = $from.start() + slashIndex
104
+ const to = selection.from
105
+
106
+ try {
107
+ const coords = editor.view.coordsAtPos(from)
108
+ return {
109
+ from,
110
+ to,
111
+ query,
112
+ rect: new DOMRect(
113
+ coords.left,
114
+ coords.top,
115
+ Math.max(1, coords.right - coords.left),
116
+ Math.max(1, coords.bottom - coords.top)
117
+ )
118
+ }
119
+ } catch {
120
+ return null
121
+ }
122
+ }
123
+
124
+ export function isSameSlashMenuState(
125
+ current: SlashMenuState | null,
126
+ next: SlashMenuState | null
127
+ ): boolean {
128
+ if (current === next) return true
129
+ if (!current || !next) return false
130
+
131
+ return (
132
+ current.from === next.from &&
133
+ current.to === next.to &&
134
+ current.query === next.query &&
135
+ current.rect.x === next.rect.x &&
136
+ current.rect.y === next.rect.y
137
+ )
138
+ }
139
+
140
+ export function runSlashCommand(
141
+ editor: Editor,
142
+ command: RunnableSlashCommand,
143
+ slashMenu: SlashMenuState
144
+ ): boolean {
145
+ editor.chain().focus().deleteRange({ from: slashMenu.from, to: slashMenu.to }).run()
146
+ return command.run(editor)
147
+ }
148
+
149
+ export function getSlashMenuStyle(rect: DOMRect): CSSProperties {
150
+ const margin = 8
151
+ const menuWidth = 288
152
+ const menuMaxHeight = 320
153
+
154
+ if (typeof window === 'undefined') {
155
+ return {
156
+ left: rect.left,
157
+ position: 'fixed',
158
+ top: rect.bottom + margin
159
+ }
160
+ }
161
+
162
+ const maxLeft = Math.max(margin, window.innerWidth - menuWidth - margin)
163
+ const maxTop = Math.max(margin, window.innerHeight - menuMaxHeight - margin)
164
+
165
+ return {
166
+ left: Math.min(Math.max(margin, rect.left), maxLeft),
167
+ position: 'fixed',
168
+ top: Math.min(Math.max(margin, rect.bottom + margin), maxTop)
169
+ }
170
+ }
171
+
172
+ function isContentEditorTableAlignment(value: unknown): value is ContentEditorTableAlignment {
173
+ return value === 'left' || value === 'center' || value === 'right'
174
+ }
175
+
176
+ function getTableRectAtPosition(editor: Editor, tablePos: number): TableRect | null {
177
+ const table = editor.state.doc.nodeAt(tablePos)
178
+ if (!table || table.type.name !== 'table') return null
179
+
180
+ const map = TableMap.get(table)
181
+
182
+ return {
183
+ bottom: map.height,
184
+ left: 0,
185
+ map,
186
+ right: map.width,
187
+ table,
188
+ tableStart: tablePos + 1,
189
+ top: 0
190
+ }
191
+ }
192
+
193
+ export function createContentEditorTableNode(
194
+ rows: number = DEFAULT_CONTENT_EDITOR_TABLE_ROWS,
195
+ cols: number = DEFAULT_CONTENT_EDITOR_TABLE_COLS
196
+ ): JSONContent {
197
+ const rowCount = Math.max(2, Math.floor(rows))
198
+ const colCount = Math.max(1, Math.floor(cols))
199
+
200
+ return {
201
+ type: 'table',
202
+ content: Array.from({ length: rowCount }, (_, rowIndex) => ({
203
+ type: 'tableRow',
204
+ content: Array.from({ length: colCount }, (_, colIndex) => ({
205
+ type: rowIndex === 0 ? 'tableHeader' : 'tableCell',
206
+ content: [
207
+ {
208
+ type: 'paragraph',
209
+ content: rowIndex === 0 ? [{ type: 'text', text: `Header ${colIndex + 1}` }] : []
210
+ }
211
+ ]
212
+ }))
213
+ }))
214
+ }
215
+ }
216
+
217
+ export function insertContentEditorTable(
218
+ editor: Editor,
219
+ rows: number = DEFAULT_CONTENT_EDITOR_TABLE_ROWS,
220
+ cols: number = DEFAULT_CONTENT_EDITOR_TABLE_COLS
221
+ ): boolean {
222
+ return editor.chain().focus().insertContent(createContentEditorTableNode(rows, cols)).run()
223
+ }
224
+
225
+ export function canInsertContentEditorMediaPlaceholder(editor: Editor | null): boolean {
226
+ if (!editor?.isEditable) return false
227
+ if (!isExtensionAvailable(editor, CONTENT_EDITOR_MEDIA_PLACEHOLDER_NODE)) return false
228
+
229
+ return editor.can().insertContent({ type: CONTENT_EDITOR_MEDIA_PLACEHOLDER_NODE })
230
+ }
231
+
232
+ export function isContentEditorMediaPlaceholderActive(editor: Editor | null): boolean {
233
+ if (!editor?.isEditable) return false
234
+
235
+ return editor.isActive(CONTENT_EDITOR_MEDIA_PLACEHOLDER_NODE)
236
+ }
237
+
238
+ export function insertContentEditorMediaPlaceholder(editor: Editor | null): boolean {
239
+ if (!editor) return false
240
+ if (!canInsertContentEditorMediaPlaceholder(editor)) return false
241
+
242
+ try {
243
+ return editor
244
+ .chain()
245
+ .focus()
246
+ .insertContent({
247
+ type: CONTENT_EDITOR_MEDIA_PLACEHOLDER_NODE
248
+ })
249
+ .run()
250
+ } catch {
251
+ return false
252
+ }
253
+ }
254
+
255
+ export function getSelectedContentEditorMath(
256
+ editor: Editor | null
257
+ ): ContentEditorSelectedMath | null {
258
+ if (!editor) return null
259
+
260
+ const { selection } = editor.state
261
+ if (!(selection instanceof NodeSelection)) return null
262
+
263
+ const node = selection.node
264
+ const pos = selection.from
265
+
266
+ if (node.type.name === 'inlineMath') {
267
+ const raw = typeof node.attrs.raw === 'string' ? node.attrs.raw : ''
268
+ const delimiter = node.attrs.delimiter === '\\(' ? '\\(' : '$'
269
+
270
+ return {
271
+ delimiter,
272
+ kind: 'inline',
273
+ node,
274
+ pos,
275
+ raw,
276
+ value: raw
277
+ }
278
+ }
279
+
280
+ if (node.type.name === 'rawContentBlock' && node.attrs.kind === 'math') {
281
+ const raw = typeof node.attrs.raw === 'string' ? node.attrs.raw : ''
282
+
283
+ return {
284
+ kind: 'display',
285
+ node,
286
+ pos,
287
+ raw,
288
+ value: raw
289
+ }
290
+ }
291
+
292
+ return null
293
+ }
294
+
295
+ export function insertContentEditorMath(editor: Editor | null, input: string): boolean {
296
+ if (!editor?.isEditable) return false
297
+
298
+ const math = parseContentEditorMathInput(input)
299
+ if (!math) return false
300
+
301
+ if (math.kind === 'display') {
302
+ return editor
303
+ .chain()
304
+ .focus()
305
+ .insertContent({
306
+ type: 'rawContentBlock',
307
+ attrs: {
308
+ kind: 'math',
309
+ raw: math.markdown
310
+ }
311
+ })
312
+ .run()
313
+ }
314
+
315
+ return editor
316
+ .chain()
317
+ .focus()
318
+ .insertContent({
319
+ type: 'inlineMath',
320
+ attrs: {
321
+ delimiter: math.delimiter ?? '$',
322
+ raw: math.raw
323
+ }
324
+ })
325
+ .run()
326
+ }
327
+
328
+ export function updateSelectedContentEditorMath(
329
+ editor: Editor | null,
330
+ input: string,
331
+ selectedMathOverride?: ContentEditorSelectedMath | null
332
+ ): boolean {
333
+ const selectedMath = selectedMathOverride ?? getSelectedContentEditorMath(editor)
334
+ if (!editor?.isEditable || !selectedMath) return false
335
+
336
+ const math = parseContentEditorMathInput(input, selectedMath.kind)
337
+ if (!math) return false
338
+
339
+ const attrs =
340
+ selectedMath.kind === 'display'
341
+ ? {
342
+ ...selectedMath.node.attrs,
343
+ kind: 'math',
344
+ raw: math.markdown
345
+ }
346
+ : {
347
+ ...selectedMath.node.attrs,
348
+ delimiter: math.delimiter ?? selectedMath.delimiter ?? '$',
349
+ raw: math.raw
350
+ }
351
+
352
+ const tr = editor.state.tr.setNodeMarkup(selectedMath.pos, undefined, attrs)
353
+ tr.setSelection(NodeSelection.create(tr.doc, selectedMath.pos))
354
+ editor.view.dispatch(tr.scrollIntoView())
355
+ editor.commands.focus()
356
+ return true
357
+ }
358
+
359
+ export function removeSelectedContentEditorMath(
360
+ editor: Editor | null,
361
+ selectedMathOverride?: ContentEditorSelectedMath | null
362
+ ): boolean {
363
+ const selectedMath = selectedMathOverride ?? getSelectedContentEditorMath(editor)
364
+ if (!editor?.isEditable || !selectedMath) return false
365
+
366
+ return editor
367
+ .chain()
368
+ .focus()
369
+ .deleteRange({
370
+ from: selectedMath.pos,
371
+ to: selectedMath.pos + selectedMath.node.nodeSize
372
+ })
373
+ .run()
374
+ }
375
+
376
+ export function appendColumnToTable(editor: Editor, tablePos: number): boolean {
377
+ const rect = getTableRectAtPosition(editor, tablePos)
378
+ if (!rect) return false
379
+
380
+ const tr = addColumn(editor.state.tr, rect, rect.map.width).scrollIntoView()
381
+ editor.view.dispatch(tr)
382
+ editor.commands.focus()
383
+ return true
384
+ }
385
+
386
+ export function appendRowToTable(editor: Editor, tablePos: number): boolean {
387
+ const rect = getTableRectAtPosition(editor, tablePos)
388
+ if (!rect) return false
389
+
390
+ const tr = addRow(editor.state.tr, rect, rect.map.height).scrollIntoView()
391
+ editor.view.dispatch(tr)
392
+ editor.commands.focus()
393
+ return true
394
+ }
395
+
396
+ function getSelectedTableColumnCellPositions(editor: Editor): number[] {
397
+ if (!editor.isActive('table')) return []
398
+
399
+ try {
400
+ const rect = selectedRect(editor.state)
401
+ const columnIndex = rect.left
402
+ const cellPositions = rect.map.cellsInRect({
403
+ bottom: rect.map.height,
404
+ left: columnIndex,
405
+ right: columnIndex + 1,
406
+ top: 0
407
+ })
408
+
409
+ return [...new Set(cellPositions)].map((pos) => rect.tableStart + pos)
410
+ } catch {
411
+ return []
412
+ }
413
+ }
414
+
415
+ export function getActiveTableColumnAlignment(
416
+ editor: Editor | null
417
+ ): ContentEditorTableAlignment | null {
418
+ if (!editor) return null
419
+
420
+ const positions = getSelectedTableColumnCellPositions(editor)
421
+ if (!positions.length) return null
422
+
423
+ let alignment: ContentEditorTableAlignment | null = null
424
+
425
+ for (const position of positions) {
426
+ const node = editor.state.doc.nodeAt(position)
427
+ const nodeAlignment = isContentEditorTableAlignment(node?.attrs.align)
428
+ ? node.attrs.align
429
+ : 'left'
430
+
431
+ if (alignment === null) {
432
+ alignment = nodeAlignment
433
+ continue
434
+ }
435
+
436
+ if (alignment !== nodeAlignment) return null
437
+ }
438
+
439
+ return alignment
440
+ }
441
+
442
+ export function setActiveTableColumnAlignment(
443
+ editor: Editor,
444
+ alignment: ContentEditorTableAlignment
445
+ ): boolean {
446
+ const positions = getSelectedTableColumnCellPositions(editor)
447
+ if (!positions.length) return false
448
+
449
+ const tr = editor.state.tr
450
+ let changed = false
451
+
452
+ for (const position of positions) {
453
+ const node = tr.doc.nodeAt(position)
454
+ if (!node) continue
455
+ if (node.type.name !== 'tableCell' && node.type.name !== 'tableHeader') {
456
+ continue
457
+ }
458
+
459
+ if (node.attrs.align === alignment) continue
460
+
461
+ tr.setNodeMarkup(position, undefined, {
462
+ ...node.attrs,
463
+ align: alignment
464
+ })
465
+ changed = true
466
+ }
467
+
468
+ if (!changed) {
469
+ editor.commands.focus()
470
+ return true
471
+ }
472
+
473
+ editor.view.dispatch(tr.scrollIntoView())
474
+ editor.commands.focus()
475
+ return true
476
+ }
477
+
478
+ /**
479
+ * Determines if the current platform is macOS
480
+ * @returns boolean indicating if the current platform is Mac
481
+ */
482
+ export function isMac(): boolean {
483
+ return typeof navigator !== 'undefined' && navigator.platform.toLowerCase().includes('mac')
484
+ }
485
+
486
+ /**
487
+ * Formats a shortcut key based on the platform (Mac or non-Mac)
488
+ * @param key - The key to format (e.g., "ctrl", "alt", "shift")
489
+ * @param isMac - Boolean indicating if the platform is Mac
490
+ * @param capitalize - Whether to capitalize the key (default: true)
491
+ * @returns Formatted shortcut key symbol
492
+ */
493
+ export const formatShortcutKey = (key: string, isMac: boolean, capitalize: boolean = true) => {
494
+ if (isMac) {
495
+ const lowerKey = key.toLowerCase()
496
+ return MAC_SYMBOLS[lowerKey] || (capitalize ? key.toUpperCase() : key)
497
+ }
498
+
499
+ return capitalize ? key.charAt(0).toUpperCase() + key.slice(1) : key
500
+ }
501
+
502
+ /**
503
+ * Parses a shortcut key string into an array of formatted key symbols
504
+ * @param shortcutKeys - The string of shortcut keys (e.g., "ctrl-alt-shift")
505
+ * @param delimiter - The delimiter used to split the keys (default: "-")
506
+ * @param capitalize - Whether to capitalize the keys (default: true)
507
+ * @returns Array of formatted shortcut key symbols
508
+ */
509
+ export const parseShortcutKeys = (props: {
510
+ shortcutKeys: string | undefined
511
+ delimiter?: string
512
+ capitalize?: boolean
513
+ }) => {
514
+ const { shortcutKeys, delimiter = '+', capitalize = true } = props
515
+
516
+ if (!shortcutKeys) return []
517
+
518
+ return shortcutKeys
519
+ .split(delimiter)
520
+ .map((key) => key.trim())
521
+ .map((key) => formatShortcutKey(key, isMac(), capitalize))
522
+ }
523
+
524
+ /**
525
+ * Checks if a mark exists in the editor schema
526
+ * @param markName - The name of the mark to check
527
+ * @param editor - The editor instance
528
+ * @returns boolean indicating if the mark exists in the schema
529
+ */
530
+ export const isMarkInSchema = (markName: string, editor: Editor | null): boolean => {
531
+ if (!editor?.schema) return false
532
+ return editor.schema.spec.marks.get(markName) !== undefined
533
+ }
534
+
535
+ /**
536
+ * Checks if a node exists in the editor schema
537
+ * @param nodeName - The name of the node to check
538
+ * @param editor - The editor instance
539
+ * @returns boolean indicating if the node exists in the schema
540
+ */
541
+ export const isNodeInSchema = (nodeName: string, editor: Editor | null): boolean => {
542
+ if (!editor?.schema) return false
543
+ return editor.schema.spec.nodes.get(nodeName) !== undefined
544
+ }
545
+
546
+ /**
547
+ * Moves the focus to the next node in the editor
548
+ * @param editor - The editor instance
549
+ * @returns boolean indicating if the focus was moved
550
+ */
551
+ export function focusNextNode(editor: Editor) {
552
+ const { state, view } = editor
553
+ const { doc, selection } = state
554
+
555
+ const nextSel = Selection.findFrom(selection.$to, 1, true)
556
+ if (nextSel) {
557
+ view.dispatch(state.tr.setSelection(nextSel).scrollIntoView())
558
+ return true
559
+ }
560
+
561
+ const paragraphType = state.schema.nodes.paragraph
562
+ if (!paragraphType) {
563
+ console.warn('No paragraph node type found in schema.')
564
+ return false
565
+ }
566
+
567
+ const end = doc.content.size
568
+ const para = paragraphType.create()
569
+ let tr = state.tr.insert(end, para)
570
+
571
+ // Place the selection inside the new paragraph
572
+ const $inside = tr.doc.resolve(end + 1)
573
+ tr = tr.setSelection(TextSelection.near($inside)).scrollIntoView()
574
+ view.dispatch(tr)
575
+ return true
576
+ }
577
+
578
+ /**
579
+ * Checks if a value is a valid number (not null, undefined, or NaN)
580
+ * @param value - The value to check
581
+ * @returns boolean indicating if the value is a valid number
582
+ */
583
+ export function isValidPosition(pos: number | null | undefined): pos is number {
584
+ return typeof pos === 'number' && pos >= 0
585
+ }
586
+
587
+ /**
588
+ * Checks if one or more extensions are registered in the Tiptap editor.
589
+ * @param editor - The Tiptap editor instance
590
+ * @param extensionNames - A single extension name or an array of names to check
591
+ * @returns True if at least one of the extensions is available, false otherwise
592
+ */
593
+ export function isExtensionAvailable(
594
+ editor: Editor | null,
595
+ extensionNames: string | string[]
596
+ ): boolean {
597
+ if (!editor) return false
598
+
599
+ const names = Array.isArray(extensionNames) ? extensionNames : [extensionNames]
600
+
601
+ const found = names.some((name) =>
602
+ editor.extensionManager.extensions.some((ext) => ext.name === name)
603
+ )
604
+
605
+ if (!found) {
606
+ console.warn(
607
+ `None of the extensions [${names.join(', ')}] were found in the editor schema. Ensure they are included in the editor configuration.`
608
+ )
609
+ }
610
+
611
+ return found
612
+ }
613
+
614
+ /**
615
+ * Finds a node at the specified position with error handling
616
+ * @param editor The Tiptap editor instance
617
+ * @param position The position in the document to find the node
618
+ * @returns The node at the specified position, or null if not found
619
+ */
620
+ export function findNodeAtPosition(editor: Editor, position: number) {
621
+ try {
622
+ const node = editor.state.doc.nodeAt(position)
623
+ if (!node) {
624
+ console.warn(`No node found at position ${position}`)
625
+ return null
626
+ }
627
+ return node
628
+ } catch (error) {
629
+ console.error(`Error getting node at position ${position}:`, error)
630
+ return null
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Finds the position and instance of a node in the document
636
+ * @param props Object containing editor, node (optional), and nodePos (optional)
637
+ * @param props.editor The Tiptap editor instance
638
+ * @param props.node The node to find (optional if nodePos is provided)
639
+ * @param props.nodePos The position of the node to find (optional if node is provided)
640
+ * @returns An object with the position and node, or null if not found
641
+ */
642
+ export function findNodePosition(props: {
643
+ editor: Editor | null
644
+ node?: PMNode | null
645
+ nodePos?: number | null
646
+ }): { pos: number; node: PMNode } | null {
647
+ const { editor, node, nodePos } = props
648
+
649
+ if (!editor || !editor.state?.doc) return null
650
+
651
+ // Zero is valid position
652
+ const hasValidNode = node !== undefined && node !== null
653
+ const hasValidPos = isValidPosition(nodePos)
654
+
655
+ if (!hasValidNode && !hasValidPos) {
656
+ return null
657
+ }
658
+
659
+ // First search for the node in the document if we have a node
660
+ if (hasValidNode) {
661
+ let foundPos = -1
662
+ let foundNode: PMNode | null = null
663
+
664
+ editor.state.doc.descendants((currentNode, pos) => {
665
+ // TODO: Needed?
666
+ // if (currentNode.type && currentNode.type.name === node!.type.name) {
667
+ if (currentNode === node) {
668
+ foundPos = pos
669
+ foundNode = currentNode
670
+ return false
671
+ }
672
+ return true
673
+ })
674
+
675
+ if (foundPos !== -1 && foundNode !== null) {
676
+ return { pos: foundPos, node: foundNode }
677
+ }
678
+ }
679
+
680
+ // If we have a valid position, use findNodeAtPosition
681
+ if (isValidPosition(nodePos)) {
682
+ const nodeAtPos = findNodeAtPosition(editor, nodePos)
683
+ if (nodeAtPos) {
684
+ return { pos: nodePos, node: nodeAtPos }
685
+ }
686
+ }
687
+
688
+ return null
689
+ }
690
+
691
+ /**
692
+ * Determines whether the current selection contains a node whose type matches
693
+ * any of the provided node type names.
694
+ * @param editor Tiptap editor instance
695
+ * @param nodeTypeNames List of node type names to match against
696
+ * @param checkAncestorNodes Whether to check ancestor node types up the depth chain
697
+ */
698
+ export function isNodeTypeSelected(
699
+ editor: Editor | null,
700
+ nodeTypeNames: string[] = [],
701
+ checkAncestorNodes: boolean = false
702
+ ): boolean {
703
+ if (!editor || !editor.state.selection) return false
704
+
705
+ const { selection } = editor.state
706
+ if (selection.empty) return false
707
+
708
+ // Direct node selection check
709
+ if (selection instanceof NodeSelection) {
710
+ const selectedNode = selection.node
711
+ return selectedNode ? nodeTypeNames.includes(selectedNode.type.name) : false
712
+ }
713
+
714
+ // Depth-based ancestor node check
715
+ if (checkAncestorNodes) {
716
+ const { $from } = selection
717
+ for (let depth = $from.depth; depth > 0; depth--) {
718
+ const ancestorNode = $from.node(depth)
719
+ if (nodeTypeNames.includes(ancestorNode.type.name)) {
720
+ return true
721
+ }
722
+ }
723
+ }
724
+
725
+ return false
726
+ }
727
+
728
+ /**
729
+ * Check whether the current selection is fully within nodes
730
+ * whose type names are in the provided `types` list.
731
+ *
732
+ * - NodeSelection → checks the selected node.
733
+ * - Text/AllSelection → ensures all textblocks within [from, to) are allowed.
734
+ */
735
+ export function selectionWithinConvertibleTypes(editor: Editor, types: string[] = []): boolean {
736
+ if (!editor || types.length === 0) return false
737
+
738
+ const { state } = editor
739
+ const { selection } = state
740
+ const allowed = new Set(types)
741
+
742
+ if (selection instanceof NodeSelection) {
743
+ const nodeType = selection.node?.type?.name
744
+ return !!nodeType && allowed.has(nodeType)
745
+ }
746
+
747
+ if (selection instanceof TextSelection || selection instanceof AllSelection) {
748
+ let valid = true
749
+ state.doc.nodesBetween(selection.from, selection.to, (node) => {
750
+ if (node.isTextblock && !allowed.has(node.type.name)) {
751
+ valid = false
752
+ return false // stop early
753
+ }
754
+ return valid
755
+ })
756
+ return valid
757
+ }
758
+
759
+ return false
760
+ }
761
+
762
+ type ProtocolOptions = {
763
+ /**
764
+ * The protocol scheme to be registered.
765
+ * @default '''
766
+ * @example 'ftp'
767
+ * @example 'git'
768
+ */
769
+ scheme: string
770
+
771
+ /**
772
+ * If enabled, it allows optional slashes after the protocol.
773
+ * @default false
774
+ * @example true
775
+ */
776
+ optionalSlashes?: boolean
777
+ }
778
+
779
+ type ProtocolConfig = Array<ProtocolOptions | string>
780
+
781
+ const ATTR_WHITESPACE_PATTERN =
782
+ '[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]'
783
+ const ATTR_WHITESPACE = new RegExp(ATTR_WHITESPACE_PATTERN, 'g')
784
+
785
+ export function isAllowedUri(uri: string | undefined, protocols?: ProtocolConfig) {
786
+ const allowedProtocols: string[] = [
787
+ 'http',
788
+ 'https',
789
+ 'ftp',
790
+ 'ftps',
791
+ 'mailto',
792
+ 'tel',
793
+ 'callto',
794
+ 'sms',
795
+ 'cid',
796
+ 'xmpp'
797
+ ]
798
+
799
+ if (protocols) {
800
+ protocols.forEach((protocol) => {
801
+ const nextProtocol = typeof protocol === 'string' ? protocol : protocol.scheme
802
+
803
+ if (nextProtocol) {
804
+ allowedProtocols.push(nextProtocol)
805
+ }
806
+ })
807
+ }
808
+
809
+ return (
810
+ !uri ||
811
+ uri.replace(ATTR_WHITESPACE, '').match(
812
+ new RegExp(
813
+ // eslint-disable-next-line no-useless-escape
814
+ `^(?:(?:${allowedProtocols.join('|')}):|[^a-z]|[a-z0-9+.-]+(?:[^a-z+.-:]|$))`,
815
+ 'i'
816
+ )
817
+ )
818
+ )
819
+ }
820
+
821
+ export function sanitizeUrl(inputUrl: string, baseUrl: string, protocols?: ProtocolConfig): string {
822
+ try {
823
+ const url = new URL(inputUrl, baseUrl)
824
+
825
+ if (isAllowedUri(url.href, protocols)) {
826
+ return url.href
827
+ }
828
+ } catch {
829
+ // If URL creation fails, it's considered invalid
830
+ }
831
+ return '#'
832
+ }
833
+
834
+ /**
835
+ * Update a single attribute on multiple nodes.
836
+ *
837
+ * @param tr - The transaction to mutate
838
+ * @param targets - Array of { node, pos }
839
+ * @param attrName - Attribute key to update
840
+ * @param next - New value OR updater function receiving previous value
841
+ * Pass `undefined` to remove the attribute.
842
+ * @returns true if at least one node was updated, false otherwise
843
+ */
844
+ export function updateNodesAttr<A extends string = string, V = unknown>(
845
+ tr: Transaction,
846
+ targets: readonly NodeWithPos[],
847
+ attrName: A,
848
+ next: V | ((prev: V | undefined) => V | undefined)
849
+ ): boolean {
850
+ if (!targets.length) return false
851
+
852
+ let changed = false
853
+
854
+ for (const { pos } of targets) {
855
+ // Always re-read from the transaction's current doc
856
+ const currentNode = tr.doc.nodeAt(pos)
857
+ if (!currentNode) continue
858
+
859
+ const prevValue = (currentNode.attrs as Record<string, unknown>)[attrName] as V | undefined
860
+ const resolvedNext =
861
+ typeof next === 'function' ? (next as (p: V | undefined) => V | undefined)(prevValue) : next
862
+
863
+ if (prevValue === resolvedNext) continue
864
+
865
+ const nextAttrs: Record<string, unknown> = { ...currentNode.attrs }
866
+ if (resolvedNext === undefined) {
867
+ // Remove the key entirely instead of setting null
868
+ delete nextAttrs[attrName]
869
+ } else {
870
+ nextAttrs[attrName] = resolvedNext
871
+ }
872
+
873
+ tr.setNodeMarkup(pos, undefined, nextAttrs)
874
+ changed = true
875
+ }
876
+
877
+ return changed
878
+ }
879
+
880
+ /**
881
+ * Selects the entire content of the current block node if the selection is empty.
882
+ * If the selection is not empty, it does nothing.
883
+ * @param editor The Tiptap editor instance
884
+ */
885
+ export function selectCurrentBlockContent(editor: Editor) {
886
+ const { selection, doc } = editor.state
887
+
888
+ if (!selection.empty) return
889
+
890
+ const $pos = selection.$from
891
+ let blockNode = null
892
+ let blockPos = -1
893
+
894
+ for (let depth = $pos.depth; depth >= 0; depth--) {
895
+ const node = $pos.node(depth)
896
+ const pos = $pos.start(depth)
897
+
898
+ if (node.isBlock && node.textContent.trim()) {
899
+ blockNode = node
900
+ blockPos = pos
901
+ break
902
+ }
903
+ }
904
+
905
+ if (blockNode && blockPos >= 0) {
906
+ const from = blockPos
907
+ const to = blockPos + blockNode.nodeSize - 2 // -2 to exclude the closing tag
908
+
909
+ if (from < to) {
910
+ const $from = doc.resolve(from)
911
+ const $to = doc.resolve(to)
912
+ const newSelection = TextSelection.between($from, $to, 1)
913
+
914
+ if (newSelection && !selection.eq(newSelection)) {
915
+ editor.view.dispatch(editor.state.tr.setSelection(newSelection))
916
+ }
917
+ }
918
+ }
919
+ }
920
+
921
+ /**
922
+ * Retrieves all nodes of specified types from the current selection.
923
+ * @param selection The current editor selection
924
+ * @param allowedNodeTypes An array of node type names to look for (e.g., ["image", "table"])
925
+ * @returns An array of objects containing the node and its position
926
+ */
927
+ export function getSelectedNodesOfType(
928
+ selection: Selection,
929
+ allowedNodeTypes: string[]
930
+ ): NodeWithPos[] {
931
+ const results: NodeWithPos[] = []
932
+ const allowed = new Set(allowedNodeTypes)
933
+
934
+ if (selection instanceof CellSelection) {
935
+ selection.forEachCell((node: PMNode, pos: number) => {
936
+ if (allowed.has(node.type.name)) {
937
+ results.push({ node, pos })
938
+ }
939
+ })
940
+ return results
941
+ }
942
+
943
+ if (selection instanceof NodeSelection) {
944
+ const { node, from: pos } = selection
945
+ if (node && allowed.has(node.type.name)) {
946
+ results.push({ node, pos })
947
+ }
948
+ return results
949
+ }
950
+
951
+ const { $anchor } = selection
952
+ const cell = cellAround($anchor)
953
+
954
+ if (cell) {
955
+ const cellNode = selection.$anchor.doc.nodeAt(cell.pos)
956
+ if (cellNode && allowed.has(cellNode.type.name)) {
957
+ results.push({ node: cellNode, pos: cell.pos })
958
+ return results
959
+ }
960
+ }
961
+
962
+ // Fallback: find parent nodes of allowed types
963
+ const parentNode = findParentNodeClosestToPos($anchor, (node) => allowed.has(node.type.name))
964
+
965
+ if (parentNode) {
966
+ results.push({ node: parentNode.node, pos: parentNode.pos })
967
+ }
968
+
969
+ return results
970
+ }
971
+
972
+ export function getSelectedBlockNodes(editor: Editor): PMNode[] {
973
+ const { doc } = editor.state
974
+ const { from, to } = editor.state.selection
975
+
976
+ const blocks: PMNode[] = []
977
+ const seen = new Set<number>()
978
+
979
+ doc.nodesBetween(from, to, (node, pos) => {
980
+ if (!node.isBlock) return
981
+
982
+ if (!seen.has(pos)) {
983
+ seen.add(pos)
984
+ blocks.push(node)
985
+ }
986
+
987
+ return false
988
+ })
989
+
990
+ return blocks
991
+ }