ruby_cms 0.2.1.1 → 1.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 (647) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -72
  3. data/README.md +293 -158
  4. data/Rakefile +0 -18
  5. data/SYNC_RULES.md +84 -0
  6. data/exe/ruby_cms +13 -18
  7. data/lib/generators/ruby_cms/admin_page_generator.rb +116 -60
  8. data/lib/generators/ruby_cms/templates/admin.html.erb +35 -17
  9. data/lib/generators/ruby_cms/templates/admin_page/_admin_table_content.html.erb.tt +25 -0
  10. data/lib/generators/ruby_cms/templates/admin_page/_form.html.erb.tt +36 -0
  11. data/lib/generators/ruby_cms/templates/admin_page/_row.html.erb.tt +20 -0
  12. data/lib/generators/ruby_cms/templates/admin_page/controller.rb.tt +74 -2
  13. data/lib/generators/ruby_cms/templates/admin_page/edit.html.erb.tt +14 -0
  14. data/lib/generators/ruby_cms/templates/admin_page/index.html.erb.tt +21 -10
  15. data/lib/generators/ruby_cms/templates/admin_page/new.html.erb.tt +14 -0
  16. data/lib/generators/ruby_cms/templates/assets/tailwind/application.css +225 -0
  17. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion.rb +17 -0
  18. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_content.rb +21 -0
  19. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_default_content.rb +17 -0
  20. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_default_trigger.rb +19 -0
  21. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_icon.rb +38 -0
  22. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_item.rb +28 -0
  23. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_trigger.rb +17 -0
  24. data/lib/generators/ruby_cms/templates/components/ruby_ui/admin_page/admin_page.rb +31 -0
  25. data/lib/generators/ruby_cms/templates/components/ruby_ui/admin_page/admin_page_header.rb +65 -0
  26. data/lib/generators/ruby_cms/templates/components/ruby_ui/admin_page/admin_resource_card.rb +28 -0
  27. data/lib/generators/ruby_cms/templates/components/ruby_ui/admin_page/admin_table_content.rb +20 -0
  28. data/lib/generators/ruby_cms/templates/components/ruby_ui/alert/alert.rb +36 -0
  29. data/lib/generators/ruby_cms/templates/components/ruby_ui/alert/alert_description.rb +17 -0
  30. data/lib/generators/ruby_cms/templates/components/ruby_ui/alert/alert_title.rb +17 -0
  31. data/lib/generators/ruby_cms/templates/components/ruby_ui/aspect_ratio/aspect_ratio.rb +33 -0
  32. data/lib/generators/ruby_cms/templates/components/ruby_ui/avatar/avatar.rb +31 -0
  33. data/lib/generators/ruby_cms/templates/components/ruby_ui/avatar/avatar_fallback.rb +17 -0
  34. data/lib/generators/ruby_cms/templates/components/ruby_ui/avatar/avatar_image.rb +26 -0
  35. data/lib/generators/ruby_cms/templates/components/ruby_ui/badge/badge.rb +60 -0
  36. data/lib/generators/ruby_cms/templates/components/ruby_ui/base.rb +29 -0
  37. data/lib/generators/ruby_cms/templates/components/ruby_ui/box/box.rb +15 -0
  38. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb.rb +17 -0
  39. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_ellipsis.rb +39 -0
  40. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_item.rb +17 -0
  41. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_link.rb +22 -0
  42. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_list.rb +17 -0
  43. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_page.rb +19 -0
  44. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_separator.rb +38 -0
  45. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_button.rb +82 -0
  46. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table.rb +133 -0
  47. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_actions.rb +104 -0
  48. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_body.rb +17 -0
  49. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_checkbox_cell.rb +40 -0
  50. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_checkbox_head.rb +34 -0
  51. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_delete_modal.rb +43 -0
  52. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_header.rb +42 -0
  53. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_pagination.rb +115 -0
  54. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_row.rb +77 -0
  55. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_actions.rb +86 -0
  56. data/lib/generators/ruby_cms/templates/components/ruby_ui/button/button.rb +113 -0
  57. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar.rb +39 -0
  58. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_body.rb +19 -0
  59. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_days.rb +104 -0
  60. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_header.rb +17 -0
  61. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_next.rb +43 -0
  62. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_prev.rb +43 -0
  63. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_title.rb +27 -0
  64. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_weekdays.rb +33 -0
  65. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card.rb +17 -0
  66. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_content.rb +17 -0
  67. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_description.rb +17 -0
  68. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_footer.rb +17 -0
  69. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_header.rb +17 -0
  70. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_title.rb +17 -0
  71. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel.rb +44 -0
  72. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel_content.rb +23 -0
  73. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel_item.rb +23 -0
  74. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel_next.rb +48 -0
  75. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel_previous.rb +49 -0
  76. data/lib/generators/ruby_cms/templates/components/ruby_ui/checkbox/checkbox.rb +29 -0
  77. data/lib/generators/ruby_cms/templates/components/ruby_ui/checkbox/checkbox_group.rb +20 -0
  78. data/lib/generators/ruby_cms/templates/components/ruby_ui/clipboard/clipboard.rb +42 -0
  79. data/lib/generators/ruby_cms/templates/components/ruby_ui/clipboard/clipboard_popover.rb +40 -0
  80. data/lib/generators/ruby_cms/templates/components/ruby_ui/clipboard/clipboard_source.rb +19 -0
  81. data/lib/generators/ruby_cms/templates/components/ruby_ui/clipboard/clipboard_trigger.rb +20 -0
  82. data/lib/generators/ruby_cms/templates/components/ruby_ui/codeblock/codeblock.rb +102 -0
  83. data/lib/generators/ruby_cms/templates/components/ruby_ui/collapsible/collapsible.rb +25 -0
  84. data/lib/generators/ruby_cms/templates/components/ruby_ui/collapsible/collapsible_content.rb +18 -0
  85. data/lib/generators/ruby_cms/templates/components/ruby_ui/collapsible/collapsible_trigger.rb +19 -0
  86. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox.rb +26 -0
  87. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_checkbox.rb +25 -0
  88. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_empty_state.rb +21 -0
  89. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_item.rb +25 -0
  90. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_list.rb +18 -0
  91. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_list_group.rb +20 -0
  92. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_popover.rb +30 -0
  93. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_radio.rb +26 -0
  94. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_search_input.rb +53 -0
  95. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_toggle_all_checkbox.rb +25 -0
  96. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_trigger.rb +57 -0
  97. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command.rb +9 -0
  98. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_dialog.rb +17 -0
  99. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_dialog_content.rb +48 -0
  100. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_dialog_trigger.rb +29 -0
  101. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_empty.rb +19 -0
  102. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_group.rb +40 -0
  103. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_input.rb +56 -0
  104. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_item.rb +32 -0
  105. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_list.rb +17 -0
  106. data/lib/generators/ruby_cms/templates/components/ruby_ui/container/container.rb +33 -0
  107. data/lib/generators/ruby_cms/templates/components/ruby_ui/container/container_section.rb +31 -0
  108. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu.rb +26 -0
  109. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_content.rb +25 -0
  110. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_item.rb +66 -0
  111. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_label.rb +24 -0
  112. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_separator.rb +19 -0
  113. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_trigger.rb +20 -0
  114. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table.rb +29 -0
  115. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_bulk_actions.rb +18 -0
  116. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_column_toggle.rb +62 -0
  117. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_expand_toggle.rb +53 -0
  118. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_form.rb +39 -0
  119. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_kaminari_adapter.rb +17 -0
  120. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_manual_adapter.rb +17 -0
  121. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_pagination.rb +100 -0
  122. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_pagination_bar.rb +15 -0
  123. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_pagy_adapter.rb +17 -0
  124. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_per_page_select.rb +35 -0
  125. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_row_checkbox.rb +30 -0
  126. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_search.rb +57 -0
  127. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_select_all_checkbox.rb +21 -0
  128. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_selection_summary.rb +25 -0
  129. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_sort_head.rb +112 -0
  130. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_toolbar.rb +15 -0
  131. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog.rb +25 -0
  132. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_content.rb +78 -0
  133. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_description.rb +17 -0
  134. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_footer.rb +17 -0
  135. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_header.rb +17 -0
  136. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_middle.rb +17 -0
  137. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_title.rb +17 -0
  138. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_trigger.rb +20 -0
  139. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu.rb +35 -0
  140. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_content.rb +44 -0
  141. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_item.rb +28 -0
  142. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_label.rb +17 -0
  143. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_separator.rb +19 -0
  144. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_trigger.rb +18 -0
  145. data/lib/generators/ruby_cms/templates/components/ruby_ui/empty_state/empty_state.rb +190 -0
  146. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form.rb +15 -0
  147. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form_field.rb +20 -0
  148. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form_field_error.rb +20 -0
  149. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form_field_hint.rb +15 -0
  150. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form_field_label.rb +15 -0
  151. data/lib/generators/ruby_cms/templates/components/ruby_ui/hover_card/hover_card.rb +27 -0
  152. data/lib/generators/ruby_cms/templates/components/ruby_ui/hover_card/hover_card_content.rb +22 -0
  153. data/lib/generators/ruby_cms/templates/components/ruby_ui/hover_card/hover_card_trigger.rb +20 -0
  154. data/lib/generators/ruby_cms/templates/components/ruby_ui/input/input.rb +34 -0
  155. data/lib/generators/ruby_cms/templates/components/ruby_ui/link/link.rb +106 -0
  156. data/lib/generators/ruby_cms/templates/components/ruby_ui/masked_input/masked_input.rb +15 -0
  157. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal.rb +26 -0
  158. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_action.rb +17 -0
  159. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_cancel.rb +20 -0
  160. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_content.rb +68 -0
  161. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_description.rb +17 -0
  162. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_footer.rb +17 -0
  163. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_header.rb +17 -0
  164. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_title.rb +17 -0
  165. data/lib/generators/ruby_cms/templates/components/ruby_ui/native_select/native_select.rb +39 -0
  166. data/lib/generators/ruby_cms/templates/components/ruby_ui/native_select/native_select_group.rb +15 -0
  167. data/lib/generators/ruby_cms/templates/components/ruby_ui/native_select/native_select_icon.rb +39 -0
  168. data/lib/generators/ruby_cms/templates/components/ruby_ui/native_select/native_select_option.rb +15 -0
  169. data/lib/generators/ruby_cms/templates/components/ruby_ui/pagination/pagination.rb +19 -0
  170. data/lib/generators/ruby_cms/templates/components/ruby_ui/pagination/pagination_content.rb +17 -0
  171. data/lib/generators/ruby_cms/templates/components/ruby_ui/pagination/pagination_ellipsis.rb +42 -0
  172. data/lib/generators/ruby_cms/templates/components/ruby_ui/pagination/pagination_item.rb +28 -0
  173. data/lib/generators/ruby_cms/templates/components/ruby_ui/popover/popover.rb +26 -0
  174. data/lib/generators/ruby_cms/templates/components/ruby_ui/popover/popover_content.rb +27 -0
  175. data/lib/generators/ruby_cms/templates/components/ruby_ui/popover/popover_trigger.rb +20 -0
  176. data/lib/generators/ruby_cms/templates/components/ruby_ui/progress/progress.rb +37 -0
  177. data/lib/generators/ruby_cms/templates/components/ruby_ui/radio_button/radio_button.rb +25 -0
  178. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select.rb +23 -0
  179. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_content.rb +32 -0
  180. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_group.rb +15 -0
  181. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_input.rb +22 -0
  182. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_item.rb +52 -0
  183. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_label.rb +17 -0
  184. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_trigger.rb +54 -0
  185. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_value.rb +27 -0
  186. data/lib/generators/ruby_cms/templates/components/ruby_ui/separator/separator.rb +38 -0
  187. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet.rb +17 -0
  188. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_content.rb +77 -0
  189. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_description.rb +17 -0
  190. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_footer.rb +17 -0
  191. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_header.rb +17 -0
  192. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_middle.rb +17 -0
  193. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_title.rb +17 -0
  194. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_trigger.rb +17 -0
  195. data/lib/generators/ruby_cms/templates/components/ruby_ui/shortcut_key/shortcut_key.rb +17 -0
  196. data/lib/generators/ruby_cms/templates/components/ruby_ui/skeleton/skeleton.rb +17 -0
  197. data/lib/generators/ruby_cms/templates/components/ruby_ui/switch/switch.rb +24 -0
  198. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table.rb +19 -0
  199. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_body.rb +17 -0
  200. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_caption.rb +17 -0
  201. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_cell.rb +17 -0
  202. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_footer.rb +17 -0
  203. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_head.rb +17 -0
  204. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_header.rb +17 -0
  205. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_row.rb +17 -0
  206. data/lib/generators/ruby_cms/templates/components/ruby_ui/tabs/tabs.rb +25 -0
  207. data/lib/generators/ruby_cms/templates/components/ruby_ui/tabs/tabs_content.rb +26 -0
  208. data/lib/generators/ruby_cms/templates/components/ruby_ui/tabs/tabs_list.rb +17 -0
  209. data/lib/generators/ruby_cms/templates/components/ruby_ui/tabs/tabs_trigger.rb +28 -0
  210. data/lib/generators/ruby_cms/templates/components/ruby_ui/textarea/textarea.rb +26 -0
  211. data/lib/generators/ruby_cms/templates/components/ruby_ui/theme_toggle/set_dark_mode.rb +16 -0
  212. data/lib/generators/ruby_cms/templates/components/ruby_ui/theme_toggle/set_light_mode.rb +16 -0
  213. data/lib/generators/ruby_cms/templates/components/ruby_ui/theme_toggle/theme_toggle.rb +9 -0
  214. data/lib/generators/ruby_cms/templates/components/ruby_ui/tooltip/tooltip.rb +26 -0
  215. data/lib/generators/ruby_cms/templates/components/ruby_ui/tooltip/tooltip_content.rb +26 -0
  216. data/lib/generators/ruby_cms/templates/components/ruby_ui/tooltip/tooltip_trigger.rb +19 -0
  217. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/heading.rb +60 -0
  218. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/inline_code.rb +17 -0
  219. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/inline_link.rb +22 -0
  220. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/text.rb +53 -0
  221. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/typography_blockquote.rb +17 -0
  222. data/lib/generators/ruby_cms/templates/{ruby_cms.rb → config/initializers/admin.rb} +15 -9
  223. data/lib/generators/ruby_cms/templates/config/initializers/admin_dashboard.rb +43 -0
  224. data/lib/generators/ruby_cms/templates/config/initializers/pagy.rb +7 -0
  225. data/lib/generators/ruby_cms/templates/config/initializers/ruby_cms_core.rb +25 -0
  226. data/lib/generators/ruby_cms/templates/config/initializers/ruby_cms_custom_settings.rb +35 -0
  227. data/lib/generators/ruby_cms/templates/config/initializers/webauthn.rb +28 -0
  228. data/lib/generators/ruby_cms/templates/config/locales/en.yml +277 -0
  229. data/lib/generators/ruby_cms/templates/config/locales/nl.yml +329 -0
  230. data/lib/generators/ruby_cms/templates/controllers/admin/analytics_controller.rb +211 -0
  231. data/lib/generators/ruby_cms/templates/controllers/admin/application_controller.rb +186 -0
  232. data/lib/generators/ruby_cms/templates/controllers/admin/audit_log_entries_controller.rb +69 -0
  233. data/lib/generators/ruby_cms/templates/controllers/admin/commands_controller.rb +146 -0
  234. data/lib/generators/ruby_cms/templates/controllers/admin/content_block_versions_controller.rb +96 -0
  235. data/lib/generators/ruby_cms/templates/controllers/admin/content_blocks_controller.rb +405 -0
  236. data/lib/generators/ruby_cms/templates/controllers/admin/dashboard_controller.rb +114 -0
  237. data/lib/generators/ruby_cms/templates/controllers/admin/invitations_controller.rb +64 -0
  238. data/lib/generators/ruby_cms/templates/controllers/admin/locale_controller.rb +19 -0
  239. data/lib/generators/ruby_cms/templates/controllers/admin/media_assets_controller.rb +186 -0
  240. data/lib/generators/ruby_cms/templates/controllers/admin/notifications_controller.rb +37 -0
  241. data/lib/generators/ruby_cms/templates/controllers/admin/passkey_credentials_controller.rb +17 -0
  242. data/lib/generators/ruby_cms/templates/controllers/admin/passkey_registrations_controller.rb +50 -0
  243. data/lib/generators/ruby_cms/templates/controllers/admin/permissions_controller.rb +132 -0
  244. data/lib/generators/ruby_cms/templates/controllers/admin/redirects_controller.rb +175 -0
  245. data/lib/generators/ruby_cms/templates/controllers/admin/security_controller.rb +146 -0
  246. data/lib/generators/ruby_cms/templates/controllers/admin/settings_controller.rb +298 -0
  247. data/lib/generators/ruby_cms/templates/controllers/admin/system_health_controller.rb +11 -0
  248. data/lib/generators/ruby_cms/templates/controllers/admin/trash_controller.rb +61 -0
  249. data/lib/generators/ruby_cms/templates/controllers/admin/user_permissions_controller.rb +91 -0
  250. data/lib/generators/ruby_cms/templates/controllers/admin/users_controller.rb +220 -0
  251. data/lib/generators/ruby_cms/templates/controllers/admin/visitor_errors_controller.rb +114 -0
  252. data/lib/generators/ruby_cms/templates/controllers/admin/visual_editor_controller.rb +384 -0
  253. data/lib/generators/ruby_cms/templates/controllers/concerns/.keep +0 -0
  254. data/lib/generators/ruby_cms/templates/controllers/concerns/admin_bulk_actions.rb +59 -0
  255. data/lib/generators/ruby_cms/templates/controllers/concerns/admin_pagination.rb +106 -0
  256. data/lib/generators/ruby_cms/templates/controllers/concerns/admin_turbo_table.rb +46 -0
  257. data/lib/generators/ruby_cms/templates/controllers/concerns/audit_loggable.rb +28 -0
  258. data/lib/generators/ruby_cms/templates/controllers/concerns/authentication.rb +63 -0
  259. data/lib/generators/ruby_cms/templates/controllers/concerns/page_tracking.rb +45 -0
  260. data/lib/generators/ruby_cms/templates/controllers/concerns/security_monitoring.rb +102 -0
  261. data/lib/generators/ruby_cms/templates/controllers/concerns/sudo_mode.rb +29 -0
  262. data/lib/generators/ruby_cms/templates/controllers/concerns/visitor_error_capture.rb +35 -0
  263. data/lib/generators/ruby_cms/templates/controllers/errors_controller.rb +36 -0
  264. data/lib/generators/ruby_cms/templates/controllers/passkey_sessions_controller.rb +69 -0
  265. data/lib/generators/ruby_cms/templates/db/migrate/20251121085414_create_email_blocklists.rb +12 -0
  266. data/lib/generators/ruby_cms/templates/db/migrate/20251121161828_create_ip_blocklists.rb +12 -0
  267. data/lib/generators/ruby_cms/templates/db/migrate/20251205120000_add_analytics_indices.rb +48 -0
  268. data/lib/generators/ruby_cms/templates/db/migrate/20251216064753_backfill_security_fields_in_ahoy_events.rb +49 -0
  269. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260125000001_create_ruby_cms_permissions.rb +2 -2
  270. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260125000002_create_ruby_cms_user_permissions.rb +2 -2
  271. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260125000003_create_ruby_cms_content_blocks.rb +3 -3
  272. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260125000010_add_indexes_to_ruby_cms_tables.rb +1 -1
  273. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260127000001_add_locale_to_ruby_cms_content_blocks.rb +8 -6
  274. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260129000001_create_ruby_cms_visitor_errors.rb +4 -4
  275. data/lib/generators/ruby_cms/templates/db/migrate/20260130000001_add_referer_and_query_to_ruby_cms_visitor_errors.rb +10 -0
  276. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260130000002_create_ruby_cms_preferences.rb +2 -2
  277. data/lib/generators/ruby_cms/templates/db/migrate/20260130000003_add_category_to_ruby_cms_preferences.rb +10 -0
  278. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260328000001_create_content_block_versions.rb +2 -2
  279. data/lib/generators/ruby_cms/templates/db/migrate/20260525120000_create_audit_log_entries.rb +24 -0
  280. data/lib/generators/ruby_cms/templates/db/migrate/20260525130000_create_redirects.rb +28 -0
  281. data/lib/generators/ruby_cms/templates/db/migrate/20260525140000_create_media_assets.rb +25 -0
  282. data/lib/generators/ruby_cms/templates/db/migrate/20260525150000_create_invitations.rb +22 -0
  283. data/lib/generators/ruby_cms/templates/db/migrate/20260525160000_create_command_runs.rb +22 -0
  284. data/lib/generators/ruby_cms/templates/db/migrate/20260525190000_add_state_to_visitor_errors.rb +20 -0
  285. data/lib/generators/ruby_cms/templates/db/migrate/20260527165017_add_discarded_at_to_visitor_errors.rb +10 -0
  286. data/lib/generators/ruby_cms/templates/db/migrate/20260527165728_add_discarded_at_to_cms_tables.rb +12 -0
  287. data/lib/generators/ruby_cms/templates/db/migrate/20260605151935_create_passkey_credentials.rb +15 -0
  288. data/lib/generators/ruby_cms/templates/db/migrate/20260605151936_add_webauthn_id_to_users.rb +22 -0
  289. data/lib/generators/ruby_cms/templates/db/migrate/20260606000001_add_authenticated_at_to_sessions.rb +14 -0
  290. data/lib/generators/ruby_cms/templates/helpers/admin/admin_page_helper.rb +9 -0
  291. data/lib/generators/ruby_cms/templates/helpers/admin/dashboard_helper.rb +16 -0
  292. data/lib/generators/ruby_cms/templates/helpers/admin/media_assets_helper.rb +38 -0
  293. data/lib/generators/ruby_cms/templates/helpers/admin/relative_time_helper.rb +37 -0
  294. data/lib/generators/ruby_cms/templates/helpers/admin/ui_helper.rb +48 -0
  295. data/lib/generators/ruby_cms/templates/helpers/cms_application_helpers.rb +70 -0
  296. data/lib/generators/ruby_cms/templates/helpers/content_blocks_helper.rb +382 -0
  297. data/lib/generators/ruby_cms/templates/helpers/settings_helper.rb +192 -0
  298. data/lib/generators/ruby_cms/templates/javascript/admin.js +6 -0
  299. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/admin_commands_controller.js +2 -2
  300. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/autosearch_controller.js +18 -0
  301. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/clickable_row_controller.js +11 -0
  302. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/clipboard_controller.js +34 -0
  303. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/command_palette_controller.js +189 -0
  304. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/commands_controller.js +300 -0
  305. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/content_block_history_controller.js +167 -0
  306. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/flash_messages_controller.js +1 -1
  307. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/inline_rich_editor_controller.js +157 -0
  308. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/media_selection_controller.js +76 -0
  309. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/media_uploader_controller.js +23 -0
  310. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/mobile_menu_controller.js +125 -0
  311. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/nav_order_sortable_controller.js +296 -0
  312. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/nav_shortcuts_controller.js +47 -0
  313. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/page_preview_controller.js +64 -3
  314. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/permission_matrix_controller.js +49 -0
  315. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/search_shortcut_controller.js +24 -0
  316. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/settings_rail_search_controller.js +17 -0
  317. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/shortcuts_overlay_controller.js +39 -0
  318. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/sidebar_collapse_controller.js +135 -0
  319. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/sidebar_nav_persist_controller.js +48 -0
  320. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/sidebar_toggle_trigger_controller.js +10 -0
  321. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/topbar_controller.js +88 -0
  322. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/visual_editor_controller.js +721 -0
  323. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/visual_editor_page_select_controller.js +174 -0
  324. data/lib/generators/ruby_cms/templates/javascript/controllers/application.js +10 -0
  325. data/lib/generators/ruby_cms/templates/javascript/controllers/index.js +2 -0
  326. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/accordion_controller.js +97 -0
  327. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui}/bulk_action_table_controller.js +86 -233
  328. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/calendar_controller.js +249 -0
  329. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/calendar_input_controller.js +8 -0
  330. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/carousel_controller.js +60 -0
  331. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/checkbox_group_controller.js +21 -0
  332. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/clipboard_controller.js +54 -0
  333. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/collapsible_controller.js +47 -0
  334. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/combobox_controller.js +190 -0
  335. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/command_controller.js +138 -0
  336. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/context_menu_controller.js +144 -0
  337. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/data_table_column_visibility_controller.js +14 -0
  338. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/data_table_controller.js +109 -0
  339. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/data_table_search_controller.js +62 -0
  340. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/dialog_controller.js +32 -0
  341. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/dropdown_menu_controller.js +149 -0
  342. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/form_field_controller.js +61 -0
  343. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/hover_card_controller.js +153 -0
  344. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/masked_input_controller.js +9 -0
  345. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/modal_controller.js +45 -0
  346. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/popover_controller.js +107 -0
  347. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/select_controller.js +192 -0
  348. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/select_item_controller.js +11 -0
  349. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/sheet_content_controller.js +7 -0
  350. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/sheet_controller.js +9 -0
  351. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/tabs_controller.js +67 -0
  352. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/theme_toggle_controller.js +30 -0
  353. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/tooltip_controller.js +38 -0
  354. data/lib/generators/ruby_cms/templates/javascript/controllers/webauthn_controller.js +153 -0
  355. data/lib/generators/ruby_cms/templates/lib/ruby_cms/cli.rb +170 -0
  356. data/lib/generators/ruby_cms/templates/lib/ruby_cms/commands_registry.rb +76 -0
  357. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/content_blocks_grouping.rb +2 -6
  358. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/content_blocks_sync.rb +6 -8
  359. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/dashboard_blocks.rb +5 -5
  360. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/icons.rb +41 -10
  361. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/settings.rb +60 -10
  362. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/settings_registry.rb +79 -3
  363. data/lib/generators/ruby_cms/templates/lib/ruby_cms.rb +342 -0
  364. data/lib/{tasks → generators/ruby_cms/templates/lib/tasks}/admin.rake +16 -16
  365. data/lib/generators/ruby_cms/templates/lib/tasks/ruby_cms.rake +158 -0
  366. data/lib/generators/ruby_cms/templates/models/audit_log_entry.rb +56 -0
  367. data/lib/generators/ruby_cms/templates/models/command_run.rb +29 -0
  368. data/lib/generators/ruby_cms/templates/models/concerns/content_block/searchable.rb +18 -0
  369. data/{app → lib/generators/ruby_cms/templates}/models/concerns/content_block/versionable.rb +1 -1
  370. data/lib/generators/ruby_cms/templates/models/concerns/ransackable.rb +32 -0
  371. data/{app → lib/generators/ruby_cms/templates}/models/content_block.rb +13 -5
  372. data/lib/generators/ruby_cms/templates/models/email_blocklist.rb +21 -0
  373. data/lib/generators/ruby_cms/templates/models/invitation.rb +86 -0
  374. data/lib/generators/ruby_cms/templates/models/ip_blocklist.rb +34 -0
  375. data/lib/generators/ruby_cms/templates/models/media_asset.rb +90 -0
  376. data/lib/generators/ruby_cms/templates/models/passkey_credential.rb +6 -0
  377. data/lib/generators/ruby_cms/templates/models/permission.rb +71 -0
  378. data/lib/generators/ruby_cms/templates/models/permittable.rb +69 -0
  379. data/lib/generators/ruby_cms/templates/models/preference.rb +109 -0
  380. data/lib/generators/ruby_cms/templates/models/redirect.rb +100 -0
  381. data/lib/generators/ruby_cms/templates/models/user_permission.rb +25 -0
  382. data/lib/generators/ruby_cms/templates/models/visitor_error.rb +157 -0
  383. data/lib/generators/ruby_cms/templates/nav/analytics.rb +3 -0
  384. data/lib/generators/ruby_cms/templates/nav/audit_log.rb +3 -0
  385. data/lib/generators/ruby_cms/templates/nav/commands.rb +3 -0
  386. data/lib/generators/ruby_cms/templates/nav/content_blocks.rb +3 -0
  387. data/lib/generators/ruby_cms/templates/nav/core.rb +13 -0
  388. data/lib/generators/ruby_cms/templates/nav/media.rb +3 -0
  389. data/lib/generators/ruby_cms/templates/nav/permissions.rb +3 -0
  390. data/lib/generators/ruby_cms/templates/nav/redirects.rb +3 -0
  391. data/lib/generators/ruby_cms/templates/nav/security.rb +4 -0
  392. data/lib/generators/ruby_cms/templates/nav/system_health.rb +3 -0
  393. data/lib/generators/ruby_cms/templates/nav/trash.rb +3 -0
  394. data/lib/generators/ruby_cms/templates/nav/users.rb +3 -0
  395. data/lib/generators/ruby_cms/templates/nav/visual_editor.rb +3 -0
  396. data/lib/generators/ruby_cms/templates/routes/analytics.rb +5 -0
  397. data/lib/generators/ruby_cms/templates/routes/audit_log.rb +3 -0
  398. data/lib/generators/ruby_cms/templates/routes/auth.public.rb +4 -0
  399. data/lib/generators/ruby_cms/templates/routes/auth.rb +6 -0
  400. data/lib/generators/ruby_cms/templates/routes/commands.rb +4 -0
  401. data/lib/generators/ruby_cms/templates/routes/content_blocks.rb +12 -0
  402. data/lib/generators/ruby_cms/templates/routes/core.public.rb +5 -0
  403. data/lib/generators/ruby_cms/templates/routes/core.rb +16 -0
  404. data/lib/generators/ruby_cms/templates/routes/media.rb +9 -0
  405. data/lib/generators/ruby_cms/templates/routes/passkeys.public.rb +4 -0
  406. data/lib/generators/ruby_cms/templates/routes/passkeys.rb +5 -0
  407. data/lib/generators/ruby_cms/templates/routes/permissions.rb +5 -0
  408. data/lib/generators/ruby_cms/templates/routes/redirects.rb +10 -0
  409. data/lib/generators/ruby_cms/templates/routes/security.rb +20 -0
  410. data/lib/generators/ruby_cms/templates/routes/system_health.rb +3 -0
  411. data/lib/generators/ruby_cms/templates/routes/trash.rb +5 -0
  412. data/lib/generators/ruby_cms/templates/routes/users.rb +10 -0
  413. data/lib/generators/ruby_cms/templates/routes/visual_editor.rb +5 -0
  414. data/lib/generators/ruby_cms/templates/services/admin/health_check.rb +362 -0
  415. data/lib/generators/ruby_cms/templates/services/analytics/report.rb +503 -0
  416. data/lib/generators/ruby_cms/templates/services/analytics_service.rb +305 -0
  417. data/lib/generators/ruby_cms/templates/services/audit_log.rb +60 -0
  418. data/lib/generators/ruby_cms/templates/services/command_runner.rb +79 -0
  419. data/lib/generators/ruby_cms/templates/services/security_service.rb +203 -0
  420. data/lib/generators/ruby_cms/templates/services/security_tracker.rb +90 -0
  421. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/index.html.erb +61 -50
  422. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/page_details.html.erb +15 -10
  423. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_browser_device.html.erb +2 -2
  424. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_conversions.html.erb +3 -3
  425. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_daily_activity_chart.html.erb +18 -17
  426. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_exit_pages.html.erb +4 -4
  427. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_hourly_activity_chart.html.erb +18 -17
  428. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_landing_pages.html.erb +3 -3
  429. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_os_stats.html.erb +2 -2
  430. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_popular_pages.html.erb +4 -4
  431. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_recent_activity.html.erb +4 -4
  432. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_top_referrers.html.erb +3 -3
  433. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_top_visitors.html.erb +4 -4
  434. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_utm_sources.html.erb +2 -2
  435. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/visitor_details.html.erb +15 -10
  436. data/lib/generators/ruby_cms/templates/views/admin/audit_log_entries/_admin_table_content.html.erb +55 -0
  437. data/lib/generators/ruby_cms/templates/views/admin/audit_log_entries/_row.html.erb +84 -0
  438. data/lib/generators/ruby_cms/templates/views/admin/audit_log_entries/index.html.erb +33 -0
  439. data/lib/generators/ruby_cms/templates/views/admin/audit_log_entries/show.html.erb +89 -0
  440. data/lib/generators/ruby_cms/templates/views/admin/commands/index.html.erb +393 -0
  441. data/lib/generators/ruby_cms/templates/views/admin/content_block_versions/index.html.erb +63 -0
  442. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/content_block_versions/show.html.erb +2 -2
  443. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/_audits.html.erb +49 -0
  444. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/_form.html.erb +79 -0
  445. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/_rich_field.html.erb +44 -0
  446. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/_row.html.erb +60 -0
  447. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/index.html.erb +84 -0
  448. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/new.html.erb +18 -0
  449. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/content_blocks/show.html.erb +67 -61
  450. data/lib/generators/ruby_cms/templates/views/admin/dashboard/_gmail_token_status.html.erb +69 -0
  451. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_analytics_overview.html.erb +11 -11
  452. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_content_blocks_stats.html.erb +3 -3
  453. data/lib/generators/ruby_cms/templates/views/admin/dashboard/blocks/_notifications.html.erb +50 -0
  454. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_permissions_stats.html.erb +3 -3
  455. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_quick_actions.html.erb +13 -13
  456. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_recent_errors.html.erb +8 -8
  457. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_users_stats.html.erb +3 -3
  458. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_visitor_errors_stats.html.erb +3 -3
  459. data/lib/generators/ruby_cms/templates/views/admin/dashboard/index.html.erb +34 -0
  460. data/lib/generators/ruby_cms/templates/views/admin/media_assets/_admin_table_content.html.erb +107 -0
  461. data/lib/generators/ruby_cms/templates/views/admin/media_assets/_form.html.erb +33 -0
  462. data/lib/generators/ruby_cms/templates/views/admin/media_assets/_grid.html.erb +58 -0
  463. data/lib/generators/ruby_cms/templates/views/admin/media_assets/_list.html.erb +43 -0
  464. data/lib/generators/ruby_cms/templates/views/admin/media_assets/edit.html.erb +13 -0
  465. data/lib/generators/ruby_cms/templates/views/admin/media_assets/index.html.erb +36 -0
  466. data/lib/generators/ruby_cms/templates/views/admin/media_assets/show.html.erb +97 -0
  467. data/lib/generators/ruby_cms/templates/views/admin/passkey_credentials/index.html.erb +30 -0
  468. data/lib/generators/ruby_cms/templates/views/admin/passkey_registrations/new.html.erb +23 -0
  469. data/lib/generators/ruby_cms/templates/views/admin/permissions/_admin_table_content.html.erb +45 -0
  470. data/lib/generators/ruby_cms/templates/views/admin/permissions/_matrix.html.erb +75 -0
  471. data/lib/generators/ruby_cms/templates/views/admin/permissions/_row.html.erb +62 -0
  472. data/lib/generators/ruby_cms/templates/views/admin/permissions/index.html.erb +65 -0
  473. data/lib/generators/ruby_cms/templates/views/admin/redirects/_admin_table_content.html.erb +77 -0
  474. data/lib/generators/ruby_cms/templates/views/admin/redirects/_form.html.erb +97 -0
  475. data/lib/generators/ruby_cms/templates/views/admin/redirects/_row.html.erb +82 -0
  476. data/lib/generators/ruby_cms/templates/views/admin/redirects/edit.html.erb +21 -0
  477. data/lib/generators/ruby_cms/templates/views/admin/redirects/index.html.erb +40 -0
  478. data/lib/generators/ruby_cms/templates/views/admin/redirects/new.html.erb +12 -0
  479. data/lib/generators/ruby_cms/templates/views/admin/security/index.html.erb +88 -0
  480. data/lib/generators/ruby_cms/templates/views/admin/security/ip_blocklist_content.html.erb +3 -0
  481. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_events_by_type.html.erb +24 -0
  482. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_ip_blocklist.html.erb +37 -0
  483. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_ip_blocklist_content.html.erb +41 -0
  484. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_ip_management.html.erb +11 -0
  485. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_recent_events.html.erb +59 -0
  486. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_top_threat_ips.html.erb +21 -0
  487. data/lib/generators/ruby_cms/templates/views/admin/settings/index.html.erb +509 -0
  488. data/lib/generators/ruby_cms/templates/views/admin/shared/_add_button.html.erb +15 -0
  489. data/lib/generators/ruby_cms/templates/views/admin/shared/_data_table.html.erb +255 -0
  490. data/lib/generators/ruby_cms/templates/views/admin/shared/_turbo_pagination.html.erb +53 -0
  491. data/lib/generators/ruby_cms/templates/views/admin/system_health/index.html.erb +248 -0
  492. data/lib/generators/ruby_cms/templates/views/admin/trash/index.html.erb +58 -0
  493. data/lib/generators/ruby_cms/templates/views/admin/user_permissions/_row.html.erb +29 -0
  494. data/lib/generators/ruby_cms/templates/views/admin/user_permissions/index.html.erb +78 -0
  495. data/lib/generators/ruby_cms/templates/views/admin/users/_admin_table_content.html.erb +50 -0
  496. data/lib/generators/ruby_cms/templates/views/admin/users/_invite_form.html.erb +43 -0
  497. data/lib/generators/ruby_cms/templates/views/admin/users/_invites_panel.html.erb +91 -0
  498. data/lib/generators/ruby_cms/templates/views/admin/users/_row.html.erb +109 -0
  499. data/lib/generators/ruby_cms/templates/views/admin/users/index.html.erb +43 -0
  500. data/lib/generators/ruby_cms/templates/views/admin/users/show.html.erb +343 -0
  501. data/lib/generators/ruby_cms/templates/views/admin/visitor_errors/_admin_table_content.html.erb +61 -0
  502. data/lib/generators/ruby_cms/templates/views/admin/visitor_errors/_row.html.erb +90 -0
  503. data/lib/generators/ruby_cms/templates/views/admin/visitor_errors/index.html.erb +18 -0
  504. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/visitor_errors/show.html.erb +4 -4
  505. data/lib/generators/ruby_cms/templates/views/admin/visual_editor/index.html.erb +383 -0
  506. data/{app/views/layouts/ruby_cms → lib/generators/ruby_cms/templates/views/layouts/admin}/_admin_flash_messages.html.erb +3 -3
  507. data/{app/views/layouts/ruby_cms → lib/generators/ruby_cms/templates/views/layouts/admin}/_admin_sidebar.html.erb +47 -48
  508. data/lib/generators/ruby_cms/templates/views/layouts/admin/_command_palette.html.erb +65 -0
  509. data/lib/generators/ruby_cms/templates/views/layouts/admin/_shortcuts_overlay.html.erb +81 -0
  510. data/lib/generators/ruby_cms/templates/views/layouts/admin/_topbar_widgets.html.erb +9 -0
  511. data/lib/generators/ruby_cms/templates/views/layouts/admin/minimal.html.erb +71 -0
  512. data/lib/generators/ruby_cms/templates/views/layouts/admin/topbar/_bell.html.erb +81 -0
  513. data/lib/generators/ruby_cms/templates/views/layouts/admin/topbar/_profile_menu.html.erb +86 -0
  514. data/lib/generators/ruby_cms/templates/views/layouts/admin/topbar/_search_trigger.html.erb +9 -0
  515. data/lib/generators/ruby_cms/templates/views/layouts/admin/topbar/_shortcuts_button.html.erb +8 -0
  516. data/lib/generators/ruby_cms/templates/views/shared/_maintenance_banner.html.erb +10 -0
  517. data/lib/ruby_cms/app_wiring.rb +56 -0
  518. data/lib/ruby_cms/auth_wiring.rb +64 -0
  519. data/lib/ruby_cms/cli.rb +181 -121
  520. data/lib/ruby_cms/excludes.rb +41 -0
  521. data/lib/ruby_cms/file_installer.rb +41 -0
  522. data/lib/ruby_cms/gem_setup.rb +113 -0
  523. data/lib/ruby_cms/helper_wiring.rb +32 -0
  524. data/lib/ruby_cms/importmap_wiring.rb +41 -0
  525. data/lib/ruby_cms/installer.rb +133 -0
  526. data/lib/ruby_cms/lockfile.rb +49 -0
  527. data/lib/ruby_cms/manifest.rb +88 -0
  528. data/lib/ruby_cms/manifest_data.rb +233 -0
  529. data/lib/ruby_cms/migration_installer.rb +64 -0
  530. data/lib/ruby_cms/nav_assembler.rb +43 -0
  531. data/lib/ruby_cms/passkey_wiring.rb +42 -0
  532. data/lib/ruby_cms/path_map.rb +26 -0
  533. data/lib/ruby_cms/routes_assembler.rb +103 -0
  534. data/lib/ruby_cms/syncer.rb +81 -0
  535. data/lib/ruby_cms/updater.rb +76 -0
  536. data/lib/ruby_cms/version.rb +1 -1
  537. data/lib/ruby_cms.rb +4 -328
  538. metadata +606 -170
  539. data/.cursor/dhh.mdc +0 -698
  540. data/app/components/ruby_cms/admin/admin_page/admin_table_content.rb +0 -32
  541. data/app/components/ruby_cms/admin/admin_page.rb +0 -345
  542. data/app/components/ruby_cms/admin/admin_page_header.rb +0 -79
  543. data/app/components/ruby_cms/admin/admin_resource_card.rb +0 -55
  544. data/app/components/ruby_cms/admin/base_component.rb +0 -78
  545. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb +0 -157
  546. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_actions.rb +0 -127
  547. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_body.rb +0 -15
  548. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_cell.rb +0 -43
  549. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_head.rb +0 -35
  550. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_delete_modal.rb +0 -174
  551. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header.rb +0 -59
  552. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header_bar.rb +0 -169
  553. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_pagination.rb +0 -192
  554. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_row.rb +0 -96
  555. data/app/components/ruby_cms/admin/bulk_action_table/bulk_actions.rb +0 -196
  556. data/app/controllers/concerns/ruby_cms/admin_pagination.rb +0 -120
  557. data/app/controllers/concerns/ruby_cms/admin_turbo_table.rb +0 -68
  558. data/app/controllers/concerns/ruby_cms/page_tracking.rb +0 -67
  559. data/app/controllers/concerns/ruby_cms/visitor_error_capture.rb +0 -39
  560. data/app/controllers/ruby_cms/admin/analytics_controller.rb +0 -213
  561. data/app/controllers/ruby_cms/admin/base_controller.rb +0 -132
  562. data/app/controllers/ruby_cms/admin/commands_controller.rb +0 -122
  563. data/app/controllers/ruby_cms/admin/content_block_versions_controller.rb +0 -62
  564. data/app/controllers/ruby_cms/admin/content_blocks_controller.rb +0 -391
  565. data/app/controllers/ruby_cms/admin/dashboard_controller.rb +0 -89
  566. data/app/controllers/ruby_cms/admin/locale_controller.rb +0 -21
  567. data/app/controllers/ruby_cms/admin/permissions_controller.rb +0 -66
  568. data/app/controllers/ruby_cms/admin/settings_controller.rb +0 -223
  569. data/app/controllers/ruby_cms/admin/user_permissions_controller.rb +0 -77
  570. data/app/controllers/ruby_cms/admin/users_controller.rb +0 -107
  571. data/app/controllers/ruby_cms/admin/visitor_errors_controller.rb +0 -89
  572. data/app/controllers/ruby_cms/admin/visual_editor_controller.rb +0 -345
  573. data/app/controllers/ruby_cms/errors_controller.rb +0 -35
  574. data/app/helpers/ruby_cms/admin/admin_page_helper.rb +0 -21
  575. data/app/helpers/ruby_cms/admin/bulk_action_table_helper.rb +0 -159
  576. data/app/helpers/ruby_cms/admin/dashboard_helper.rb +0 -20
  577. data/app/helpers/ruby_cms/application_helper.rb +0 -49
  578. data/app/helpers/ruby_cms/bulk_action_table_helper.rb +0 -151
  579. data/app/helpers/ruby_cms/content_blocks_helper.rb +0 -399
  580. data/app/helpers/ruby_cms/settings_helper.rb +0 -172
  581. data/app/javascript/controllers/ruby_cms/content_block_history_controller.js +0 -91
  582. data/app/javascript/controllers/ruby_cms/index.js +0 -117
  583. data/app/javascript/controllers/ruby_cms/mobile_menu_controller.js +0 -55
  584. data/app/javascript/controllers/ruby_cms/nav_order_sortable_controller.js +0 -192
  585. data/app/javascript/controllers/ruby_cms/visual_editor_controller.js +0 -325
  586. data/app/models/concerns/content_block/searchable.rb +0 -22
  587. data/app/models/ruby_cms/content_block.rb +0 -8
  588. data/app/models/ruby_cms/permission.rb +0 -57
  589. data/app/models/ruby_cms/permittable.rb +0 -37
  590. data/app/models/ruby_cms/preference.rb +0 -111
  591. data/app/models/ruby_cms/user_permission.rb +0 -12
  592. data/app/models/ruby_cms/visitor_error.rb +0 -109
  593. data/app/services/ruby_cms/analytics/report.rb +0 -512
  594. data/app/services/ruby_cms/command_runner.rb +0 -42
  595. data/app/services/ruby_cms/security_tracker.rb +0 -92
  596. data/app/views/admin/content_block_versions/index.html.erb +0 -52
  597. data/app/views/admin/content_block_versions/show.html.erb +0 -37
  598. data/app/views/layouts/ruby_cms/admin.html.erb +0 -77
  599. data/app/views/layouts/ruby_cms/minimal.html.erb +0 -181
  600. data/app/views/ruby_cms/_tailwind_safelist.html.erb +0 -2
  601. data/app/views/ruby_cms/admin/commands/index.html.erb +0 -104
  602. data/app/views/ruby_cms/admin/content_block_versions/index.html.erb +0 -52
  603. data/app/views/ruby_cms/admin/content_blocks/_form.html.erb +0 -92
  604. data/app/views/ruby_cms/admin/content_blocks/_row.html.erb +0 -25
  605. data/app/views/ruby_cms/admin/content_blocks/index.html.erb +0 -62
  606. data/app/views/ruby_cms/admin/content_blocks/new.html.erb +0 -10
  607. data/app/views/ruby_cms/admin/dashboard/index.html.erb +0 -31
  608. data/app/views/ruby_cms/admin/permissions/_row.html.erb +0 -11
  609. data/app/views/ruby_cms/admin/permissions/index.html.erb +0 -62
  610. data/app/views/ruby_cms/admin/settings/index.html.erb +0 -268
  611. data/app/views/ruby_cms/admin/shared/_bulk_action_table_index.html.erb +0 -56
  612. data/app/views/ruby_cms/admin/user_permissions/index.html.erb +0 -85
  613. data/app/views/ruby_cms/admin/users/_row.html.erb +0 -17
  614. data/app/views/ruby_cms/admin/users/index.html.erb +0 -70
  615. data/app/views/ruby_cms/admin/visitor_errors/_row.html.erb +0 -35
  616. data/app/views/ruby_cms/admin/visitor_errors/index.html.erb +0 -57
  617. data/app/views/ruby_cms/admin/visual_editor/index.html.erb +0 -157
  618. data/app/views/ruby_cms/errors/not_found.html.erb +0 -92
  619. data/config/database.yml +0 -6
  620. data/config/importmap.rb +0 -40
  621. data/config/locales/en.yml +0 -198
  622. data/config/locales/nl.yml +0 -156
  623. data/config/routes.rb +0 -76
  624. data/db/migrate/20260130000001_add_referer_and_query_to_ruby_cms_visitor_errors.rb +0 -8
  625. data/db/migrate/20260130000003_add_category_to_ruby_cms_preferences.rb +0 -8
  626. data/docs/assets/ruby_cms_logo.png +0 -0
  627. data/lib/generators/ruby_cms/install_generator.rb +0 -1159
  628. data/lib/ruby_cms/app_integration.rb +0 -82
  629. data/lib/ruby_cms/commands_registry.rb +0 -40
  630. data/lib/ruby_cms/css_compiler.rb +0 -35
  631. data/lib/ruby_cms/engine/admin_permissions.rb +0 -69
  632. data/lib/ruby_cms/engine/content_blocks_tasks.rb +0 -66
  633. data/lib/ruby_cms/engine/css.rb +0 -14
  634. data/lib/ruby_cms/engine/dashboard_registration.rb +0 -66
  635. data/lib/ruby_cms/engine/navigation_registration.rb +0 -90
  636. data/lib/ruby_cms/engine.rb +0 -273
  637. /data/{app → lib/generators/ruby_cms/templates}/assets/images/ruby_cms/logo.png +0 -0
  638. /data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260211000001_add_ruby_cms_analytics_fields_to_ahoy_events.rb +0 -0
  639. /data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260212000001_use_unprefixed_cms_tables.rb +0 -0
  640. /data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260409000001_add_analytics_performance_indexes.rb +0 -0
  641. /data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/auto_save_preference_controller.js +0 -0
  642. /data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/locale_tabs_controller.js +0 -0
  643. /data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/toggle_controller.js +0 -0
  644. /data/{app → lib/generators/ruby_cms/templates}/models/concerns/content_block/publishable.rb +0 -0
  645. /data/{app → lib/generators/ruby_cms/templates}/models/content_block_version.rb +0 -0
  646. /data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_back_button.html.erb +0 -0
  647. /data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_security_alert.html.erb +0 -0
@@ -0,0 +1,86 @@
1
+ <%# User avatar + dropdown menu (profile, settings, view site, logout). %>
2
+ <%
3
+ user = current_user_cms
4
+ email = user&.respond_to?(:email_address) ? user.email_address : nil
5
+ initials = email.to_s.split(/[@.\s_-]/).first(2).map { |s| s[0].to_s.upcase }.join.presence || "??"
6
+ role_label = user&.admin? ? "Admin" : "Editor"
7
+ %>
8
+ <div class="relative">
9
+ <button type="button"
10
+ data-action="click->admin--topbar#toggleMenu"
11
+ data-admin--topbar-target="menuButton"
12
+ aria-label="<%= email.presence || 'Account menu' %>"
13
+ title="<%= email %>"
14
+ class="inline-flex items-center justify-center w-8 h-8 rounded-md border border-transparent hover:bg-[#faf8f3] transition-colors data-[active=true]:bg-[#faf8f3]">
15
+ <span class="inline-flex items-center justify-center w-6 h-6 rounded-md bg-blue-600 text-white text-[10.5px] font-semibold tracking-wide"><%= initials %></span>
16
+ </button>
17
+ <div data-admin--topbar-target="menuPanel" hidden
18
+ class="absolute right-0 top-[calc(100%+6px)] w-[min(280px,calc(100vw-1.5rem))] max-w-[280px] rounded-lg border border-[#e9e4d4] bg-white shadow-lg z-50 py-1">
19
+ <div class="flex items-center gap-2.5 px-3 py-2.5 border-b border-[#e9e4d4]">
20
+ <div class="inline-flex items-center justify-center w-9 h-9 rounded-md bg-blue-600 text-white text-[12.5px] font-semibold"><%= initials %></div>
21
+ <div class="min-w-0 flex-1">
22
+ <div class="text-[13.5px] font-semibold text-[#1c1917] truncate"><%= email %></div>
23
+ <div class="text-[11.5px] text-[#78716c]"><%= role_label %></div>
24
+ </div>
25
+ <%= status_pill(:danger, role_label) %>
26
+ </div>
27
+
28
+ <% if user&.id %>
29
+ <%= link_to admin_user_path(user), class: "flex items-center gap-2.5 px-3 py-2 text-[13px] text-[#1c1917] hover:bg-[#faf8f3] no-underline" do %>
30
+ <svg class="w-3.5 h-3.5 text-[#78716c]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
31
+ <span class="flex-1"><%= t("ruby_cms.admin.topbar.profile", default: "Mijn profiel") %></span>
32
+ <% end %>
33
+ <% end %>
34
+
35
+ <%= link_to admin_settings_path, class: "flex items-center gap-2.5 px-3 py-2 text-[13px] text-[#1c1917] hover:bg-[#faf8f3] no-underline" do %>
36
+ <svg class="w-3.5 h-3.5 text-[#78716c]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><circle cx="12" cy="12" r="3" /></svg>
37
+ <span class="flex-1"><%= t("ruby_cms.admin.topbar.settings", default: "Settings") %></span>
38
+ <% end %>
39
+
40
+ <%# Language toggle — segmented, both options visible. Switches via fetch so the
41
+ menu stays open (new locale applies on the next navigation). %>
42
+ <% locales = cms_available_locales %>
43
+ <% if locales.size > 1 %>
44
+ <div class="flex items-center justify-between gap-2.5 px-3 py-2">
45
+ <span class="inline-flex items-center gap-2.5 text-[13px] text-[#1c1917]">
46
+ <svg class="w-3.5 h-3.5 text-[#78716c]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 21a9 9 0 100-18 9 9 0 000 18zM3.6 9h16.8M3.6 15h16.8M11 3c-2.2 2.2-3.5 5.2-3.5 9s1.3 6.8 3.5 9m2 0c2.2-2.2 3.5-5.2 3.5-9S15.2 5.2 13 3" /></svg>
47
+ <%= t("ruby_cms.admin.topbar.language", default: "Taal") %>
48
+ </span>
49
+ <div class="inline-flex rounded-md border border-[#e9e4d4] bg-[#faf8f3] p-0.5" data-locale-url="<%= admin_locale_path %>">
50
+ <% locales.each do |loc| %>
51
+ <button type="button"
52
+ data-locale="<%= loc %>"
53
+ data-active="<%= loc == I18n.locale %>"
54
+ data-action="click->admin--topbar#setLocale"
55
+ class="h-6 px-2 rounded text-[11px] font-semibold uppercase text-[#78716c] transition-colors cursor-pointer data-[active=true]:bg-white data-[active=true]:text-[#1c1917] data-[active=true]:shadow-sm">
56
+ <%= loc.to_s %>
57
+ </button>
58
+ <% end %>
59
+ </div>
60
+ </div>
61
+ <% end %>
62
+
63
+ <button type="button" data-action="click->admin--topbar#closeAll click->admin--shortcuts-overlay#open"
64
+ class="w-full flex items-center gap-2.5 px-3 py-2 text-[13px] text-[#1c1917] hover:bg-[#faf8f3] border-0 bg-transparent cursor-pointer">
65
+ <svg class="w-3.5 h-3.5 text-[#78716c]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><rect x="2" y="6" width="20" height="12" rx="2" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 18h8" /></svg>
66
+ <span class="flex-1 text-left"><%= t("ruby_cms.admin.topbar.shortcuts", default: "Toetsenbord shortcuts") %></span>
67
+ <kbd class="inline-flex items-center justify-center h-5 px-1.5 rounded border border-[#e9e4d4] bg-[#faf8f3] text-[10.5px] font-mono text-[#78716c]">?</kbd>
68
+ </button>
69
+
70
+ <div class="h-px bg-[#e9e4d4] my-1"></div>
71
+
72
+ <% if respond_to?(:main_app, true) && main_app.respond_to?(:root_path) %>
73
+ <%= link_to main_app.root_path, target: "_blank", class: "flex items-center gap-2.5 px-3 py-2 text-[13px] text-[#1c1917] hover:bg-[#faf8f3] no-underline" do %>
74
+ <svg class="w-3.5 h-3.5 text-[#78716c]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg>
75
+ <span class="flex-1"><%= t("ruby_cms.admin.topbar.view_site", default: "Bekijk site") %></span>
76
+ <% end %>
77
+ <% end %>
78
+
79
+ <% if respond_to?(:main_app, true) && main_app.respond_to?(:session_path) %>
80
+ <%= button_to main_app.session_path, method: :delete, data: { turbo_confirm: t("nav.logout_confirm", default: "Are you sure you want to logout?") }, class: "w-full flex items-center gap-2.5 px-3 py-2 text-[13px] text-red-600 hover:bg-red-50 border-0 bg-transparent cursor-pointer" do %>
81
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /></svg>
82
+ <span class="flex-1 text-left"><%= t("ruby_cms.admin.topbar.logout", default: "Uitloggen") %></span>
83
+ <% end %>
84
+ <% end %>
85
+ </div>
86
+ </div>
@@ -0,0 +1,9 @@
1
+ <%# Global ⌘K search trigger — icon only (opens the command palette popup). %>
2
+ <button type="button"
3
+ data-action="click->admin--command-palette#open"
4
+ title="<%= t('ruby_cms.admin.topbar.search', default: 'Zoek of spring naar…') %> (⌘K)"
5
+ aria-label="<%= t('ruby_cms.admin.topbar.search', default: 'Zoek of spring naar…') %>"
6
+ class="inline-flex items-center justify-center w-8 h-8 rounded-md border border-transparent text-[#78716c] hover:bg-[#faf8f3] hover:text-[#1c1917] transition-colors">
7
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-4.35-4.35M11 19a8 8 0 100-16 8 8 0 000 16z" /></svg>
8
+ <span class="sr-only" data-admin--topbar-target="modKey"></span>
9
+ </button>
@@ -0,0 +1,8 @@
1
+ <%# Opens keyboard shortcuts overlay. %>
2
+ <button type="button"
3
+ data-action="click->admin--shortcuts-overlay#open"
4
+ title="<%= t("ruby_cms.admin.topbar.shortcuts_title", default: "Toetsenbord shortcuts (?)") %>"
5
+ aria-label="Shortcuts"
6
+ class="inline-flex items-center justify-center w-8 h-8 rounded-md border border-transparent text-[#78716c] hover:bg-[#faf8f3] hover:text-[#1c1917] transition-colors">
7
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><rect x="2" y="6" width="20" height="12" rx="2" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M6 14h.01M10 14h.01M18 14h.01M14 14h.01M8 18h8" /></svg>
8
+ </button>
@@ -0,0 +1,10 @@
1
+ <%# Visitor-facing maintenance banner. Shown when Settings → General → maintenance_mode is on. %>
2
+ <% if cms_maintenance_active? && cms_maintenance_message.present? %>
3
+ <div role="status"
4
+ class="sticky top-0 z-[100] w-full bg-amber-500 text-amber-950 px-4 py-2.5 text-center text-sm font-medium shadow-md flex items-center justify-center gap-2">
5
+ <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
6
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M4.93 19h14.14c1.54 0 2.5-1.67 1.73-3L13.73 4a2 2 0 00-3.46 0L3.2 16c-.77 1.33.19 3 1.73 3z" />
7
+ </svg>
8
+ <span><%= cms_maintenance_message %></span>
9
+ </div>
10
+ <% end %>
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module RubyCms
6
+ # Wires the host app's config/application.rb so the copied runtime in lib/ruby_cms
7
+ # loads at boot:
8
+ # * `require_relative "../lib/ruby_cms"` right after Bundler.require (defines
9
+ # RubyCms.configure et al., which config/initializers/admin.rb depends on)
10
+ # * adds `ruby_cms` to `config.autoload_lib(ignore: ...)` so Zeitwerk leaves the
11
+ # manually-required tree alone.
12
+ # Idempotent: safe to run on every install/add.
13
+ class AppWiring
14
+ REQUIRE_LINE = 'require_relative "../lib/ruby_cms"'
15
+
16
+ def initialize(app_root:)
17
+ @path = Pathname.new(app_root).join("config/application.rb")
18
+ end
19
+
20
+ def apply! # rubocop:disable Naming/PredicateMethod
21
+ return false unless @path.exist?
22
+
23
+ content = @path.read
24
+ updated = add_autoload_ignore(add_require(content))
25
+ return false if updated == content
26
+
27
+ @path.write(updated)
28
+ true
29
+ end
30
+
31
+ private
32
+
33
+ def add_require(content)
34
+ return content if content.include?(REQUIRE_LINE)
35
+ return content unless content =~ /^([ \t]*)Bundler\.require.*\n/
36
+
37
+ indent = ::Regexp.last_match(1)
38
+ content.sub(/^([ \t]*Bundler\.require.*\n)/) { "#{::Regexp.last_match(1)}\n#{indent}#{REQUIRE_LINE}\n" }
39
+ end
40
+
41
+ def add_autoload_ignore(content)
42
+ if (m = content.match(/autoload_lib\(ignore:\s*%w[\[(]([^\])]*)[\])]\)/))
43
+ words = m[1].split
44
+ return content if words.include?("ruby_cms")
45
+
46
+ content.sub(m[0], "autoload_lib(ignore: %w[#{(words + ['ruby_cms']).join(' ')}])")
47
+ elsif content.include?("class Application < Rails::Application\n")
48
+ content.sub(/(class Application < Rails::Application\n)/) do
49
+ "#{::Regexp.last_match(1)} config.autoload_lib(ignore: %w[assets tasks ruby_cms])\n"
50
+ end
51
+ else
52
+ content
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module RubyCms
6
+ # Wires the host app's authentication into RubyCMS:
7
+ # * `include Permittable` in the User model (Permittable is copied by the core module)
8
+ # * exposes `current_user` from the generated Authentication concern
9
+ # The actual `rails g authentication` run is driven by the Installer (it needs a boot);
10
+ # this unit only checks for the model and patches the generated files. Idempotent.
11
+ class AuthWiring
12
+ NOTIFICATIONS_ASSOCIATION =
13
+ %( has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification")
14
+
15
+ def initialize(app_root:)
16
+ @root = Pathname.new(app_root)
17
+ end
18
+
19
+ def user_model_present?
20
+ @root.join("app/models/user.rb").exist?
21
+ end
22
+
23
+ # Patch the generated files. Returns the list of files changed.
24
+ def patch!
25
+ [patch_user, patch_authentication_concern].compact
26
+ end
27
+
28
+ private
29
+
30
+ def patch_user
31
+ path = @root.join("app/models/user.rb")
32
+ return nil unless path.exist?
33
+
34
+ content = path.read
35
+ additions = []
36
+ additions << " include Permittable" unless content.include?("include Permittable")
37
+ additions << NOTIFICATIONS_ASSOCIATION unless content.include?("Noticed::Notification")
38
+ return nil if additions.empty?
39
+ return nil unless content =~ /class User .*\n/
40
+
41
+ path.write(content.sub(/(class User .*\n)/) { "#{::Regexp.last_match(1)}#{additions.join("\n")}\n" })
42
+ "app/models/user.rb"
43
+ end
44
+
45
+ def patch_authentication_concern
46
+ path = @root.join("app/controllers/concerns/authentication.rb")
47
+ return nil unless path.exist?
48
+
49
+ content = path.read
50
+ return nil if content.include?("def current_user")
51
+
52
+ content = content.sub(
53
+ " helper_method :authenticated?\n",
54
+ " helper_method :authenticated?, :current_user\n"
55
+ )
56
+ content = content.sub(/^(\s*)private\n/) do
57
+ indent = ::Regexp.last_match(1)
58
+ "#{indent}private\n\n#{indent} def current_user\n#{indent} Current.user\n#{indent} end\n"
59
+ end
60
+ path.write(content)
61
+ "app/controllers/concerns/authentication.rb"
62
+ end
63
+ end
64
+ end
data/lib/ruby_cms/cli.rb CHANGED
@@ -1,168 +1,228 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
+ require "ruby_cms/manifest_data"
5
+ require "ruby_cms/installer"
6
+ require "ruby_cms/syncer"
7
+ require "ruby_cms/updater"
8
+ require "ruby_cms/version"
4
9
 
5
10
  module RubyCms
11
+ # Standalone `ruby_cms` CLI: install (interactive), sync (app->gem), update (gem->app).
6
12
  class CLI < Thor
7
- default_task :setup_admin
13
+ def self.parse_modules(str)
14
+ return [] if str.nil? || str.strip.empty?
8
15
 
9
- desc "setup_admin",
10
- "Interactively create or select the first admin user and grant full permissions"
11
- def setup_admin
12
- RunSetupAdmin.call(shell:)
16
+ str.split(",").map {|s| s.strip.to_sym }
13
17
  end
14
18
 
15
- private
16
-
17
- def shell
18
- @shell ||= Thor::Shell::Basic.new
19
- end
20
- end
19
+ def self.exit_on_failure? = true
21
20
 
22
- # Logic for the interactive first-admin setup. Uses Thor::Shell for prompts.
23
- class RunSetupAdmin
24
- ADMIN_PERMISSION_KEYS = %w[
25
- manage_admin manage_permissions manage_content_blocks manage_visitor_errors manage_analytics
26
- ].freeze
21
+ desc "install", "Install RubyCMS admin into the current Rails app (interactive module picker)"
22
+ method_option :modules, type: :string, desc: "Comma list of optional modules (non-interactive)"
23
+ method_option :add, type: :string, desc: "Add modules to an existing install"
24
+ def install
25
+ app_root = detect_app_root!
26
+ installer = Installer.new(
27
+ manifest: RubyCms.manifest, templates_root: templates_root,
28
+ app_root: app_root, shell: ShellRunner.new(self, app_root), version: RubyCms::VERSION
29
+ )
27
30
 
28
- class << self
29
- def call(shell: Thor::Shell::Basic.new)
30
- new(shell:).call
31
+ modules =
32
+ if options[:add]
33
+ installer.add(new_keys: self.class.parse_modules(options[:add]))
34
+ else
35
+ selected = options[:modules] ? self.class.parse_modules(options[:modules]) : pick_modules
36
+ installer.install(selected:)
37
+ end
38
+
39
+ say "\n#{set_color('✓ RubyCMS installed', :red, :bold)} — #{modules.size} modules"
40
+ say " #{set_color(modules.map(&:key).join(', '), :cyan)}"
41
+
42
+ if yes?("\nCreate your admin user now? (runs ruby_cms:setup_admin) [y/N]")
43
+ # Interactive (prompts for email/password) — run with an inherited TTY, not the
44
+ # capturing ShellRunner.
45
+ system("bin/rails ruby_cms:setup_admin", chdir: app_root)
46
+ say "\nNext: #{set_color('bin/rails server', :yellow)} → #{set_color('/admin', :yellow)}"
47
+ else
48
+ say "\nNext: #{set_color('bin/rails ruby_cms:setup_admin', :yellow)} → " \
49
+ "#{set_color('bin/rails server', :yellow)} → #{set_color('/admin', :yellow)}"
31
50
  end
51
+ rescue Installer::Error => e
52
+ raise Thor::Error, "✗ Install aborted: #{e.message}"
53
+ end
54
+
55
+ desc "sync", "Push gem-owned files from a host app back into the gem templates"
56
+ method_option :from, type: :string, default: "../portfolio", desc: "Source app path"
57
+ method_option :dry_run, type: :boolean, default: false
58
+ method_option :include_new, type: :boolean, default: false,
59
+ desc: "Also create files that don't exist in the gem yet (default: update existing only)"
60
+ def sync
61
+ result = Syncer.new(
62
+ manifest: RubyCms.manifest, source_root: File.expand_path(options[:from]),
63
+ templates_root: templates_root
64
+ ).sync(dry_run: options[:dry_run], include_new: options[:include_new])
65
+ say "#{'[dry-run] ' if options[:dry_run]}#{result.changed.size} changed, #{result.unchanged.size} unchanged.", :green
66
+ result.changed.each {|f| say " ~ #{f}", :cyan }
67
+ report_skipped_new(result.skipped_new)
68
+ report_excluded(result.excluded)
69
+ end
70
+
71
+ desc "update", "Pull new gem versions of installed modules into the current app"
72
+ method_option :only, type: :string, desc: "Comma list to restrict the update"
73
+ method_option :dry_run, type: :boolean, default: false
74
+ def update
75
+ result = Updater.new(
76
+ manifest: RubyCms.manifest, templates_root: templates_root, app_root: detect_app_root!
77
+ ).update(dry_run: options[:dry_run], only: options[:only] && self.class.parse_modules(options[:only]))
78
+ say "#{'[dry-run] ' if options[:dry_run]}#{result.changed.size} file(s) updated.", :green
79
+ result.changed.each {|f| say " ~ #{f}", :cyan }
80
+ report_excluded(result.excluded)
32
81
  end
33
82
 
34
- def initialize(shell:)
35
- @shell = shell
36
- end
37
-
38
- def call
39
- RubyCms::Permission.ensure_defaults!
40
- user_class = Rails.application.config.ruby_cms.user_class_name.constantize
41
- email_attr = user_class.column_names.include?("email_address") ? :email_address : :email
83
+ private
42
84
 
43
- user = choose_or_create_user(user_class, email_attr)
44
- return if user.nil?
85
+ def report_skipped_new(skipped)
86
+ return unless skipped&.any?
45
87
 
46
- grant_permissions(user)
47
- notify_success(user, email_attr)
88
+ say "\n#{skipped.size} new file(s) skipped (not yet owned by the gem). " \
89
+ "Re-run with --include-new to add them deliberately:", :yellow
90
+ skipped.each {|f| say " + #{f}", :yellow }
48
91
  end
49
92
 
50
- private
93
+ def report_excluded(excluded)
94
+ return unless excluded&.any?
51
95
 
52
- def notify_success(user, email_attr)
53
- @shell.say(
54
- "\nDone. #{user.public_send(email_attr)}
55
- now has: #{ADMIN_PERMISSION_KEYS.join(', ')}.", :green
56
- )
57
- @shell.say("Visit /admin to sign in.", :green)
96
+ say "\n#{excluded.size} file(s) excluded (gem default or .ruby_cms.yml `exclude:`):", :yellow
97
+ excluded.each {|f| say " − #{f}", :yellow }
58
98
  end
59
99
 
60
- def choose_or_create_user(user_class, email_attr)
61
- users = fetch_recent_users(user_class)
62
-
63
- if users.any?
64
- prompt_existing_users(users, email_attr)
65
- else
66
- @shell.say("\nNo users yet. Create the first admin user.")
67
- create_user(user_class, email_attr)
68
- end
100
+ def templates_root
101
+ File.expand_path("../generators/ruby_cms/templates", __dir__)
69
102
  end
70
103
 
71
- def fetch_recent_users(user_class)
72
- user_class.order(id: :asc).limit(20).to_a
73
- end
104
+ def detect_app_root!
105
+ dir = Dir.pwd
106
+ loop do
107
+ return dir if File.file?(File.join(dir, "config/application.rb"))
74
108
 
75
- def prompt_existing_users(users, email_attr)
76
- display_users(users, email_attr)
77
- choice = ask_for_user_choice(users.size)
109
+ parent = File.dirname(dir)
110
+ break if parent == dir
78
111
 
79
- case choice
80
- when "0"
81
- find_or_create_by_email(users.first.class, email_attr)
82
- else
83
- select_user_or_fallback(users, choice.to_i, email_attr)
112
+ dir = parent
84
113
  end
114
+ raise Thor::Error, "Not in a Rails app (config/application.rb not found)."
85
115
  end
86
116
 
87
- def display_users(users, email_attr)
88
- @shell.say("\nExisting users:", :bold)
89
- users.each_with_index do |u, i|
90
- @shell.say(" #{i + 1}. #{u.public_send(email_attr)}")
91
- end
92
- @shell.say(" 0. Enter another email (or create new user)")
93
- end
117
+ def pick_modules
118
+ require "tty-prompt"
119
+ prompt = TTY::Prompt.new(active_color: :red)
120
+ manifest = RubyCms.manifest
94
121
 
95
- def ask_for_user_choice(max)
96
- @shell.ask("Select (0–#{max}):", default: "1").to_s.strip
97
- end
122
+ say set_color("Base modules (always installed):", :cyan, :bold)
123
+ manifest.base_modules.each do |m|
124
+ say " #{set_color('✓', :red, :bold)} #{m.key}#{set_color(" — #{m.description}", :white)}"
125
+ end
98
126
 
99
- def select_user_or_fallback(users, index, email_attr)
100
- if index.between?(1, users.size)
101
- users[index - 1]
102
- else
103
- find_or_create_by_email(users.first.class, email_attr)
127
+ prompt.multi_select(
128
+ set_color("Select optional modules (Space to toggle, Enter to confirm):", :cyan, :bold),
129
+ per_page: 20, echo: false
130
+ ) do |menu|
131
+ manifest.optional_modules.each do |mod|
132
+ menu.choice "#{mod.key} — #{mod.description}", mod.key
133
+ end
104
134
  end
105
135
  end
106
136
 
107
- def find_or_create_by_email(user_class, email_attr)
108
- email = @shell.ask("Email:").to_s.strip
109
- return nil if email.blank?
137
+ # Runs shell commands in the host app root with a compact, fancy status line:
138
+ # a spinner while running, a red check on success (output hidden), or a red
139
+ # cross plus the captured output when a step fails.
140
+ class ShellRunner
141
+ FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
142
+
143
+ # Friendly labels per command (matched by substring); falls back to the command.
144
+ LABELS = {
145
+ "bundle install" => "Installing gems",
146
+ "generate authentication" => "Setting up authentication",
147
+ "ruby_ui:install" => "Installing UI components",
148
+ "tailwindcss:install" => "Installing Tailwind",
149
+ "ahoy:install" => "Installing analytics",
150
+ "action_text:install" => "Installing rich text",
151
+ "noticed:install:migrations" => "Installing notifications",
152
+ "db:migrate" => "Running migrations",
153
+ "seed_permissions" => "Seeding permissions",
154
+ "tailwindcss:build" => "Building styles"
155
+ }.freeze
156
+
157
+ # Noise lines suppressed even on failure (environment chatter, not real errors).
158
+ NOISE = %r{Unresolved or ambiguous specs|Clearing out unresolved|Available/installed versions|already initialized constant|report a bug if this causes|gem cleanup}
159
+
160
+ def initialize(thor, app_root)
161
+ @thor = thor
162
+ @app_root = app_root
163
+ end
110
164
 
111
- user = user_class.find_by(email_attr => email)
112
- if user
113
- user
114
- else
115
- @shell.say("User not found.")
116
- create = @shell.yes?("Create new user with this email?", default: true)
117
- create ? create_user_with_email(user_class, email_attr, email) : nil
165
+ # Returns true on success, false on failure (so the Installer can decide whether
166
+ # a failed step is fatal).
167
+ def run(cmd) # rubocop:disable Naming/PredicateMethod
168
+ require "open3"
169
+ label = label_for(cmd)
170
+ done = false
171
+ spinner = spin(label) { done }
172
+ output, status = Open3.capture2e(cmd, chdir: @app_root.to_s)
173
+ done = true
174
+ spinner&.join
175
+
176
+ if status.success?
177
+ @thor.say("#{clear} #{@thor.set_color('✓', :red, :bold)} #{label}")
178
+ true
179
+ else
180
+ @thor.say("#{clear} #{@thor.set_color('✗', :red, :bold)} #{label}")
181
+ @thor.say(@thor.set_color(filter_noise(output), :red))
182
+ false
183
+ end
118
184
  end
119
- end
120
185
 
121
- def create_user(user_class, email_attr)
122
- email = @shell.ask("Email:").to_s.strip
123
- return nil if email.blank?
186
+ def say(msg, color=nil) = @thor.say(msg, color)
124
187
 
125
- create_user_with_email(user_class, email_attr, email)
126
- end
188
+ # A bold cyan section header.
189
+ def heading(text) = @thor.say(@thor.set_color(text, :cyan, :bold))
127
190
 
128
- def create_user_with_email(user_class, email_attr, email)
129
- password, password_confirmation = ask_password_and_confirmation
130
- return nil if password.blank? || password_confirmation.blank?
131
-
132
- attrs = {
133
- email_attr => email, password: password,
134
- password_confirmation: password_confirmation
135
- }
136
- user_class.create!(attrs)
137
- rescue ActiveRecord::RecordInvalid => e
138
- @shell.say("Could not create user: #{e.record.errors.full_messages.to_sentence}", :red)
139
- nil
140
- end
191
+ # A red ✓ item: bright check, plain label, dimmed hint (e.g. a description).
192
+ def ok(label, hint: nil)
193
+ line = " #{@thor.set_color('✓', :red, :bold)} #{label}"
194
+ line += @thor.set_color(" — #{hint}", :white) if hint
195
+ @thor.say(line)
196
+ end
141
197
 
142
- def ask_password_and_confirmation
143
- password = ask_password("Password:")
144
- return [nil, nil] if password.blank?
198
+ private
145
199
 
146
- password_confirmation = ask_password("Password (again):")
147
- return [password, password_confirmation] unless password != password_confirmation
200
+ def label_for(cmd)
201
+ LABELS.find {|fragment, _| cmd.include?(fragment) }&.last || cmd
202
+ end
148
203
 
149
- @shell.say("Passwords did not match. Aborted.", :red)
150
- [nil, nil]
151
- end
204
+ def filter_noise(output)
205
+ output.each_line.grep_v(NOISE).map {|line| " #{line}" }.join
206
+ end
152
207
 
153
- def ask_password(prompt)
154
- if $stdin.respond_to?(:noecho) && $stdin.tty?
155
- @shell.say("#{prompt} (hidden)")
156
- $stdin.noecho { $stdin.gets&.chomp }.to_s
157
- else
158
- @shell.ask("#{prompt}:").to_s.strip
208
+ # Cursor reset codes only when attached to a TTY (avoids raw escapes in logs).
209
+ def clear
210
+ $stdout.tty? ? "\r\e[2K" : ""
159
211
  end
160
- end
161
212
 
162
- def grant_permissions(user)
163
- ADMIN_PERMISSION_KEYS.each do |key|
164
- perm = RubyCms::Permission.find_by!(key:)
165
- RubyCms::UserPermission.find_or_create_by!(user: user, permission: perm)
213
+ # Animate a spinner on the same line until the block returns true. No-op without a TTY.
214
+ def spin(label, &)
215
+ return nil unless $stdout.tty?
216
+
217
+ Thread.new do
218
+ i = 0
219
+ until yield
220
+ $stdout.print("\r\e[2K#{@thor.set_color(FRAMES[i % FRAMES.size], :cyan)} #{label}…")
221
+ $stdout.flush
222
+ sleep 0.08
223
+ i += 1
224
+ end
225
+ end
166
226
  end
167
227
  end
168
228
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module RubyCms
6
+ # App-relative paths that `ruby_cms update` / `ruby_cms sync` must never touch.
7
+ #
8
+ # Two layers:
9
+ # * DEFAULT — gem-owned files that hosts are expected to customize (config
10
+ # initializers, generated nav). Shipped once, then left alone.
11
+ # * Per-app — globs listed under `exclude:` in the host's .ruby_cms.yml.
12
+ #
13
+ # Both are matched against the APP-relative path with File.fnmatch (globs +
14
+ # extglob), e.g. "config/initializers/admin.rb" or "app/views/layouts/admin/*".
15
+ module Excludes
16
+ DEFAULT = [
17
+ "config/initializers/admin.rb", # RubyCms.configure block — host config
18
+ "config/initializers/admin_dashboard.rb", # dashboard blocks — host-arranged
19
+ "config/initializers/admin_nav.rb", # sidebar nav — host-arranged / generated
20
+ "config/initializers/admin_pages.rb", # app-registered admin pages
21
+ "config/initializers/ruby_cms_custom_settings.rb", # host-registered custom settings
22
+ "app/assets/builds/**" # compiled Tailwind/CSS — never overwrite
23
+ ].freeze
24
+
25
+ FNM_FLAGS = File::FNM_PATHNAME | File::FNM_EXTGLOB
26
+
27
+ module_function
28
+
29
+ # Combine gem defaults with the host's per-app excludes.
30
+ def all(extra=[])
31
+ (DEFAULT + Array(extra)).uniq
32
+ end
33
+
34
+ def excluded?(app_relative_path, extra=[])
35
+ # Normalize first ("./x", "a//b", "a/../b" → clean) so the safety net can't be
36
+ # bypassed by a non-canonical path.
37
+ path = Pathname.new(app_relative_path.to_s).cleanpath.to_s
38
+ all(extra).any? {|glob| File.fnmatch?(glob, path, FNM_FLAGS) }
39
+ end
40
+ end
41
+ end