alchemy_cms 8.0.12 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -10
  3. data/app/assets/builds/alchemy/admin.css +1 -1
  4. data/app/assets/builds/alchemy/dark-theme.css +1 -1
  5. data/app/assets/builds/alchemy/light-theme.css +1 -1
  6. data/app/assets/builds/alchemy/preview.min.js +1 -1
  7. data/app/assets/builds/alchemy/theme.css +1 -1
  8. data/app/{views/alchemy/admin/elements/_element.html.erb → components/alchemy/admin/element_editor.html.erb} +34 -29
  9. data/app/components/alchemy/admin/element_editor.rb +115 -0
  10. data/app/components/alchemy/admin/element_select.rb +12 -9
  11. data/app/components/alchemy/admin/ingredient_editor.rb +54 -0
  12. data/app/components/alchemy/admin/list_filter.rb +16 -5
  13. data/app/components/alchemy/admin/page_node.html.erb +214 -0
  14. data/app/components/alchemy/admin/page_node.rb +70 -0
  15. data/app/components/alchemy/admin/picture_thumbnail.rb +36 -0
  16. data/app/components/alchemy/admin/publish_page_button.html.erb +15 -0
  17. data/app/components/alchemy/admin/publish_page_button.rb +54 -0
  18. data/app/{helpers/alchemy/admin/tags_helper.rb → components/alchemy/admin/tags_list.rb} +19 -11
  19. data/app/components/alchemy/admin/toolbar_button.rb +17 -13
  20. data/app/components/alchemy/ingredients/audio_editor.rb +8 -0
  21. data/app/components/alchemy/ingredients/base_editor.rb +222 -0
  22. data/app/components/alchemy/ingredients/boolean_editor.rb +21 -0
  23. data/app/components/alchemy/ingredients/color_editor.rb +80 -0
  24. data/app/components/alchemy/ingredients/color_view.rb +13 -0
  25. data/app/components/alchemy/ingredients/datetime_editor.rb +28 -0
  26. data/app/components/alchemy/ingredients/file_editor.rb +69 -0
  27. data/app/components/alchemy/ingredients/headline_editor.rb +88 -0
  28. data/app/components/alchemy/ingredients/html_editor.rb +11 -0
  29. data/app/components/alchemy/ingredients/link_editor.rb +29 -0
  30. data/app/components/alchemy/ingredients/node_editor.rb +23 -0
  31. data/app/components/alchemy/ingredients/number_editor.rb +28 -0
  32. data/app/components/alchemy/ingredients/page_editor.rb +19 -0
  33. data/app/components/alchemy/ingredients/picture_editor.rb +81 -0
  34. data/app/components/alchemy/ingredients/richtext_editor.rb +31 -0
  35. data/app/components/alchemy/ingredients/select_editor.rb +37 -0
  36. data/app/components/alchemy/ingredients/select_view.rb +7 -0
  37. data/app/components/alchemy/ingredients/text_editor.rb +41 -0
  38. data/app/components/alchemy/ingredients/video_editor.rb +8 -0
  39. data/app/controllers/alchemy/admin/attachments_controller.rb +8 -15
  40. data/app/controllers/alchemy/admin/base_controller.rb +7 -18
  41. data/app/controllers/alchemy/admin/clipboard_controller.rb +15 -11
  42. data/app/controllers/alchemy/admin/dashboard_controller.rb +2 -2
  43. data/app/controllers/alchemy/admin/elements_controller.rb +34 -32
  44. data/app/controllers/alchemy/admin/ingredients_controller.rb +1 -0
  45. data/app/controllers/alchemy/admin/languages_controller.rb +0 -3
  46. data/app/controllers/alchemy/admin/layoutpages_controller.rb +2 -1
  47. data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +1 -1
  48. data/app/controllers/alchemy/admin/nodes_controller.rb +24 -1
  49. data/app/controllers/alchemy/admin/pages_controller.rb +36 -42
  50. data/app/controllers/alchemy/admin/pictures_controller.rb +4 -28
  51. data/app/controllers/alchemy/admin/resources_controller.rb +1 -1
  52. data/app/controllers/alchemy/api/ingredients_controller.rb +1 -1
  53. data/app/controllers/alchemy/api/pages_controller.rb +5 -3
  54. data/app/controllers/alchemy/base_controller.rb +6 -6
  55. data/app/controllers/alchemy/pages_controller.rb +12 -6
  56. data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +0 -1
  57. data/app/controllers/concerns/alchemy/admin/clipboard.rb +57 -0
  58. data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +2 -2
  59. data/app/controllers/concerns/alchemy/site_redirects.rb +1 -1
  60. data/app/decorators/alchemy/ingredient_editor.rb +37 -4
  61. data/app/helpers/alchemy/admin/base_helper.rb +10 -6
  62. data/app/helpers/alchemy/admin/ingredients_helper.rb +6 -3
  63. data/app/helpers/alchemy/base_helper.rb +1 -1
  64. data/app/helpers/alchemy/pages_helper.rb +1 -1
  65. data/app/javascript/alchemy_admin/components/action.js +5 -1
  66. data/app/javascript/alchemy_admin/components/color_select.js +73 -0
  67. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +11 -3
  68. data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +7 -2
  69. data/app/javascript/alchemy_admin/components/element_editor.js +11 -12
  70. data/app/javascript/alchemy_admin/components/element_select.js +39 -17
  71. data/app/javascript/alchemy_admin/components/elements_window.js +0 -2
  72. data/app/javascript/alchemy_admin/components/file_editor.js +26 -0
  73. data/app/javascript/alchemy_admin/components/index.js +9 -0
  74. data/app/javascript/alchemy_admin/components/list_filter.js +57 -8
  75. data/app/javascript/alchemy_admin/components/message.js +9 -3
  76. data/app/javascript/alchemy_admin/components/page_node.js +119 -0
  77. data/app/javascript/alchemy_admin/{page_publication_fields.js → components/page_publication_fields.js} +9 -8
  78. data/app/javascript/alchemy_admin/{picture_editors.js → components/picture_editor.js} +30 -45
  79. data/app/javascript/alchemy_admin/components/picture_thumbnail.js +107 -0
  80. data/app/javascript/alchemy_admin/components/publish_page_button.js +41 -0
  81. data/app/javascript/alchemy_admin/components/select.js +3 -1
  82. data/app/javascript/alchemy_admin/components/sitemap.js +210 -0
  83. data/app/javascript/alchemy_admin/{sortable_elements.js → components/sortable_elements.js} +22 -25
  84. data/app/javascript/alchemy_admin/components/tinymce.js +10 -5
  85. data/app/javascript/alchemy_admin/components/uploader.js +30 -0
  86. data/app/javascript/alchemy_admin/image_overlay.js +0 -2
  87. data/app/javascript/alchemy_admin/initializer.js +0 -3
  88. data/app/javascript/alchemy_admin/link_dialog.js +1 -6
  89. data/app/javascript/alchemy_admin/templates/compiled.js +1 -1
  90. data/app/javascript/alchemy_admin/utils/ajax.js +15 -3
  91. data/app/javascript/alchemy_admin.js +0 -6
  92. data/app/models/alchemy/attachment.rb +4 -44
  93. data/app/models/alchemy/element/definitions.rb +1 -2
  94. data/app/models/alchemy/element/element_ingredients.rb +6 -2
  95. data/app/models/alchemy/element.rb +54 -13
  96. data/app/models/alchemy/element_definition.rb +4 -1
  97. data/app/models/alchemy/elements_repository.rb +6 -0
  98. data/app/models/alchemy/folded_page.rb +2 -2
  99. data/app/models/alchemy/ingredient.rb +38 -1
  100. data/app/models/alchemy/ingredient_definition.rb +4 -1
  101. data/app/models/alchemy/ingredient_validator.rb +6 -2
  102. data/app/models/alchemy/ingredients/color.rb +10 -0
  103. data/app/models/alchemy/ingredients/headline.rb +2 -17
  104. data/app/models/alchemy/ingredients/picture.rb +4 -4
  105. data/app/models/alchemy/ingredients/select.rb +19 -0
  106. data/app/models/alchemy/language/code.rb +0 -1
  107. data/app/models/alchemy/node.rb +28 -1
  108. data/app/models/alchemy/page/page_naming.rb +0 -7
  109. data/app/models/alchemy/page/page_natures.rb +7 -3
  110. data/app/models/alchemy/page/page_scopes.rb +13 -1
  111. data/app/models/alchemy/page/publisher.rb +14 -2
  112. data/app/models/alchemy/page.rb +102 -23
  113. data/app/models/alchemy/page_definition.rb +4 -1
  114. data/app/models/alchemy/page_version.rb +22 -6
  115. data/app/models/alchemy/picture.rb +10 -11
  116. data/app/models/alchemy/picture_variant.rb +1 -3
  117. data/app/models/alchemy/resource.rb +1 -1
  118. data/app/models/alchemy/storage_adapter/active_storage.rb +14 -2
  119. data/app/models/alchemy/storage_adapter/dragonfly.rb +12 -0
  120. data/app/models/alchemy/storage_adapter.rb +2 -0
  121. data/app/models/concerns/alchemy/picture_thumbnails.rb +4 -4
  122. data/app/models/concerns/alchemy/publishable.rb +54 -0
  123. data/app/models/concerns/alchemy/relatable_resource.rb +4 -14
  124. data/app/serializers/alchemy/page_tree_serializer.rb +11 -31
  125. data/app/services/alchemy/copy_page.rb +17 -0
  126. data/app/services/alchemy/duplicate_element.rb +1 -1
  127. data/app/services/alchemy/page_tree_preloader.rb +105 -0
  128. data/app/stylesheets/alchemy/_extends.scss +3 -9
  129. data/app/stylesheets/alchemy/_mixins.scss +3 -1
  130. data/app/stylesheets/alchemy/_themes.scss +19 -10
  131. data/app/stylesheets/alchemy/admin/archive.scss +1 -0
  132. data/app/stylesheets/alchemy/admin/base.scss +5 -2
  133. data/app/stylesheets/alchemy/admin/buttons.scss +3 -3
  134. data/app/stylesheets/alchemy/admin/element-select.scss +18 -0
  135. data/app/stylesheets/alchemy/admin/elements.scss +123 -23
  136. data/app/stylesheets/alchemy/admin/errors.scss +1 -1
  137. data/app/stylesheets/alchemy/admin/flash.scss +6 -4
  138. data/app/stylesheets/alchemy/admin/images.scss +9 -5
  139. data/app/stylesheets/alchemy/admin/list_filter.scss +4 -4
  140. data/app/stylesheets/alchemy/admin/navigation.scss +1 -1
  141. data/app/stylesheets/alchemy/admin/notices.scss +1 -2
  142. data/app/stylesheets/alchemy/admin/selects.scss +36 -21
  143. data/app/stylesheets/alchemy/admin/shoelace.scss +14 -1
  144. data/app/stylesheets/alchemy/admin/sitemap.scss +11 -3
  145. data/app/stylesheets/alchemy/admin/tags.scss +3 -1
  146. data/app/stylesheets/alchemy/admin/toolbar.scss +1 -1
  147. data/app/views/alchemy/_edit_mode.html.erb +1 -1
  148. data/app/views/alchemy/_menubar.html.erb +1 -1
  149. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +35 -31
  150. data/app/views/alchemy/admin/attachments/_files_list.html.erb +1 -1
  151. data/app/views/alchemy/admin/attachments/_library_sidebar.html.erb +6 -0
  152. data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
  153. data/app/views/alchemy/admin/attachments/_replace_button.html.erb +1 -8
  154. data/app/views/alchemy/admin/attachments/_sorting_select.html.erb +13 -0
  155. data/app/views/alchemy/admin/attachments/_tag_list.html.erb +2 -3
  156. data/app/views/alchemy/admin/attachments/index.html.erb +5 -11
  157. data/app/views/alchemy/admin/attachments/show.html.erb +1 -1
  158. data/app/views/alchemy/admin/clipboard/_button.html.erb +1 -0
  159. data/app/views/alchemy/admin/clipboard/index.html.erb +4 -5
  160. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +1 -1
  161. data/app/views/alchemy/admin/crop.html.erb +5 -7
  162. data/app/views/alchemy/admin/dashboard/widgets/_locked_pages.html.erb +1 -1
  163. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +6 -6
  164. data/app/views/alchemy/admin/elements/_fixed_element.html.erb +1 -1
  165. data/app/views/alchemy/admin/elements/_footer.html.erb +7 -1
  166. data/app/views/alchemy/admin/elements/_header.html.erb +5 -5
  167. data/app/views/alchemy/admin/elements/_toolbar.html.erb +33 -8
  168. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +10 -10
  169. data/app/views/alchemy/admin/elements/index.html.erb +29 -16
  170. data/app/views/alchemy/admin/elements/new.html.erb +2 -2
  171. data/app/views/alchemy/admin/ingredients/update.turbo_stream.erb +3 -5
  172. data/app/views/alchemy/admin/leave.html.erb +1 -1
  173. data/app/views/alchemy/admin/nodes/_node.html.erb +19 -0
  174. data/app/views/alchemy/admin/nodes/edit.html.erb +1 -1
  175. data/app/views/alchemy/admin/nodes/index.html.erb +3 -1
  176. data/app/views/alchemy/admin/nodes/new.html.erb +14 -1
  177. data/app/views/alchemy/admin/pages/_current_page.html.erb +3 -1
  178. data/app/views/alchemy/admin/pages/_form.html.erb +21 -9
  179. data/app/views/alchemy/admin/pages/_page_status.html.erb +1 -1
  180. data/app/views/alchemy/admin/pages/_publication_fields.html.erb +28 -26
  181. data/app/views/alchemy/admin/pages/_table.html.erb +0 -7
  182. data/app/views/alchemy/admin/pages/_toolbar.html.erb +3 -6
  183. data/app/views/alchemy/admin/pages/edit.html.erb +5 -11
  184. data/app/views/alchemy/admin/pages/flush.turbo_stream.erb +2 -0
  185. data/app/views/alchemy/admin/pages/fold.turbo_stream.erb +5 -0
  186. data/app/views/alchemy/admin/pages/index.html.erb +5 -3
  187. data/app/views/alchemy/admin/pages/new.html.erb +2 -12
  188. data/app/views/alchemy/admin/pages/publish.turbo_stream.erb +12 -0
  189. data/app/views/alchemy/admin/pages/tree.html.erb +13 -0
  190. data/app/views/alchemy/admin/pages/update.turbo_stream.erb +5 -16
  191. data/app/views/alchemy/admin/partials/_flash_notices.html.erb +1 -1
  192. data/app/views/alchemy/admin/partials/{_remote_search_form.html.erb → _overlay_search_form.html.erb} +1 -2
  193. data/app/views/alchemy/admin/partials/_paste_from_clipboard_form.html.erb +12 -0
  194. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +24 -21
  195. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +18 -26
  196. data/app/views/alchemy/admin/pictures/_picture.html.erb +12 -16
  197. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +3 -6
  198. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +2 -3
  199. data/app/views/alchemy/admin/pictures/index.html.erb +0 -1
  200. data/app/views/alchemy/admin/pictures/update.turbo_stream.erb +1 -1
  201. data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +1 -1
  202. data/app/views/alchemy/admin/resources/_tag_list.html.erb +2 -3
  203. data/app/views/alchemy/admin/styleguide/index.html.erb +25 -20
  204. data/app/views/alchemy/admin/tags/edit.html.erb +1 -1
  205. data/app/views/alchemy/admin/tinymce/_setup.html.erb +2 -2
  206. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -15
  207. data/app/views/alchemy/attachments/show.html.erb +1 -1
  208. data/app/views/alchemy/base/permission_denied.js.erb +1 -1
  209. data/app/views/alchemy/ingredients/shared/_anchor.html.erb +9 -7
  210. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +12 -5
  211. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +10 -11
  212. data/app/views/alchemy/language_links/_spacer.html.erb +1 -1
  213. data/app/views/alchemy/messages_mailer/new.html.erb +1 -1
  214. data/app/views/alchemy/welcome.html.erb +1 -1
  215. data/config/locales/alchemy.en.yml +12 -3
  216. data/config/routes.rb +2 -2
  217. data/db/migrate/20230123112425_add_searchable_to_alchemy_pages.rb +1 -1
  218. data/db/migrate/20230505132743_add_indexes_to_alchemy_pictures.rb +1 -1
  219. data/db/migrate/20231113104432_create_page_mutexes.rb +1 -1
  220. data/db/migrate/20240314105244_create_alchemy_picture_descriptions.rb +1 -1
  221. data/db/migrate/20250626160259_add_unique_index_to_picture_descriptions.rb +1 -1
  222. data/db/migrate/20250905140323_add_created_at_index_to_pictures_and_attachments.rb +1 -1
  223. data/db/migrate/20251106150010_convert_select_value_for_multiple.rb +11 -0
  224. data/db/migrate/20260102121232_add_metadata_to_page_versions.rb +9 -0
  225. data/db/migrate/20260115164704_add_publication_timestamps_to_alchemy_elements.rb +30 -0
  226. data/db/migrate/20260115164705_add_index_to_element_publication_timestamps.rb +13 -0
  227. data/lib/alchemy/ability_helper.rb +1 -3
  228. data/lib/alchemy/auth_accessors.rb +51 -117
  229. data/lib/alchemy/configuration.rb +1 -2
  230. data/lib/alchemy/configurations/main.rb +63 -0
  231. data/lib/alchemy/controller_actions.rb +2 -3
  232. data/lib/alchemy/engine.rb +9 -12
  233. data/lib/alchemy/error_tracking/error_logger.rb +1 -1
  234. data/lib/alchemy/errors.rb +1 -1
  235. data/lib/alchemy/logger.rb +34 -4
  236. data/lib/alchemy/name_conversions.rb +0 -6
  237. data/lib/alchemy/seeder.rb +2 -2
  238. data/lib/alchemy/tasks/usage.rb +4 -4
  239. data/lib/alchemy/test_support/factories/page_version_factory.rb +3 -0
  240. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +30 -0
  241. data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +26 -6
  242. data/lib/alchemy/test_support/shared_publishable_examples.rb +114 -0
  243. data/lib/alchemy/upgrader/eight_one.rb +56 -0
  244. data/lib/alchemy/upgrader.rb +9 -1
  245. data/lib/alchemy/version.rb +1 -1
  246. data/lib/alchemy.rb +1 -4
  247. data/lib/alchemy_cms.rb +0 -1
  248. data/lib/generators/alchemy/elements/templates/view.html.erb +3 -3
  249. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +6 -8
  250. data/lib/generators/alchemy/ingredient/templates/editor_component.rb.tt +22 -0
  251. data/lib/generators/alchemy/page_layouts/templates/layout.html.erb +1 -1
  252. data/lib/generators/alchemy/site_layouts/templates/layout.html.erb +1 -1
  253. data/lib/tasks/alchemy/upgrade.rake +21 -7
  254. data/vendor/javascript/shoelace.min.js +713 -31
  255. data/vendor/javascript/tinymce.min.js +1 -1
  256. metadata +104 -84
  257. data/app/decorators/alchemy/element_editor.rb +0 -90
  258. data/app/helpers/alchemy/admin/pictures_helper.rb +0 -14
  259. data/app/javascript/alchemy_admin/file_editors.js +0 -28
  260. data/app/javascript/alchemy_admin/image_loader.js +0 -54
  261. data/app/javascript/alchemy_admin/page_sorter.js +0 -71
  262. data/app/javascript/alchemy_admin/sitemap.js +0 -154
  263. data/app/javascript/alchemy_admin/templates/page_folder.hbs +0 -3
  264. data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -4
  265. data/app/views/alchemy/admin/pages/_page.html.erb +0 -163
  266. data/app/views/alchemy/admin/pages/_sitemap.html.erb +0 -30
  267. data/app/views/alchemy/admin/pages/flush.js.erb +0 -2
  268. data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +0 -5
  269. data/app/views/alchemy/admin/pictures/index.js.erb +0 -2
  270. data/app/views/alchemy/ingredients/_audio_editor.html.erb +0 -5
  271. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +0 -11
  272. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +0 -20
  273. data/app/views/alchemy/ingredients/_file_editor.html.erb +0 -52
  274. data/app/views/alchemy/ingredients/_headline_editor.html.erb +0 -44
  275. data/app/views/alchemy/ingredients/_html_editor.html.erb +0 -8
  276. data/app/views/alchemy/ingredients/_link_editor.html.erb +0 -30
  277. data/app/views/alchemy/ingredients/_node_editor.html.erb +0 -13
  278. data/app/views/alchemy/ingredients/_number_editor.html.erb +0 -24
  279. data/app/views/alchemy/ingredients/_page_editor.html.erb +0 -13
  280. data/app/views/alchemy/ingredients/_picture_editor.html.erb +0 -59
  281. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +0 -15
  282. data/app/views/alchemy/ingredients/_select_editor.html.erb +0 -31
  283. data/app/views/alchemy/ingredients/_text_editor.html.erb +0 -29
  284. data/app/views/alchemy/ingredients/_video_editor.html.erb +0 -5
  285. data/lib/generators/alchemy/ingredient/templates/editor.html.erb +0 -14
  286. /data/{lib → app/models}/alchemy/permissions.rb +0 -0
@@ -1,134 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Provides authentication accessors.
4
- #
5
- # Alchemy has some defaults for user model name and login logout path names:
6
- #
7
- # +Alchemy.user_class_name+ defaults to +'User'+
8
- # +Alchemy.user_class_primary_key+ defaults to +:id+
9
- # +Alchemy.current_user_method defaults to +'current_user'+
10
- # +Alchemy.signup_path defaults to +'/signup'+
11
- # +Alchemy.login_path defaults to +'/login'+
12
- # +Alchemy.logout_path defaults to +'/logout'+
13
- # +Alchemy.logout_method defaults to +'delete'+
14
- # +Alchemy.unauthorized_path defaults to +'/'+
15
- #
16
- # Anyway, you can tell Alchemy about your authentication model configuration:
17
- #
18
- # 1. Your user class name - @see: Alchemy.user_class
19
- # 2. Your users table primary key - @see: Alchemy.user_class_primary_key
20
- # 3. A method on your ApplicationController to get current user -
21
- # @see: Alchemy.current_user_method
22
- # 4. The path to the signup form - @see: Alchemy.signup_path
23
- # 5. The path to the login form - @see: Alchemy.login_path
24
- # 6. The path to the logout method - @see: Alchemy.logout_path
25
- # 7. The http verb for the logout method - @see: Alchemy.logout_method
26
- # 8. The path to the page showing the user she's unauthorized - @see: Alchemy.unauthorized_path
27
- #
28
- # == Example
29
- #
30
- # # config/initializers/alchemy.rb
31
- # Alchemy.user_class_name = 'Admin'
32
- # Alchemy.user_class_primary_key = :user_id
33
- # Alchemy.current_user_method = 'current_admin'
34
- # Alchemy.signup_path = '/auth/signup'
35
- # Alchemy.login_path = '/auth/login'
36
- # Alchemy.logout_path = '/auth/logout'
37
- # Alchemy.logout_method = 'get'
38
- # Alchemy.unauthorized_path = '/home'
39
- #
40
- # If you don't have your own user model or don't want to provide one,
41
- # add the `alchemy-devise` gem into your App's Gemfile.
42
- #
43
- # == Adding your own CanCan abilities
44
- #
45
- # If your app or your engine has own CanCan abilities you must register them:
46
- #
47
- # Alchemy.register_ability MyCustom::Ability
48
- #
49
3
  module Alchemy
50
- mattr_accessor :user_class_primary_key,
51
- :current_user_method,
52
- :signup_path,
53
- :login_path,
54
- :logout_path,
55
- :logout_method,
56
- :unauthorized_path
57
-
58
- # Defaults
59
- #
60
- @@user_class_name = "User"
61
- @@user_class_primary_key = :id
62
- @@current_user_method = "current_user"
63
- @@signup_path = "/signup"
64
- @@login_path = "/login"
65
- @@logout_path = "/logout"
66
- @@logout_method = "delete"
67
- @@unauthorized_path = "/"
68
-
69
- # Returns the user class
70
- #
71
- # Set your App's user class to Alchemy.user_class_name in an initializer.
72
- #
73
- # Defaults to +User+
74
- #
75
- # == Example
76
- #
77
- # # config/initializers/alchemy.rb
78
- # Alchemy.user_class_name = 'Admin'
79
- #
80
-
81
- # Prefix with :: when getting to avoid constant name conflicts
82
- def self.user_class_name
83
- if !@@user_class_name.is_a?(String)
84
- raise TypeError, "Alchemy.user_class_name must be a String, not a Class."
85
- end
4
+ class << self
5
+ delegate :user_class_primary_key, to: :config
6
+ deprecate user_class_primary_key: "Use `Alchemy.config.user_class_primary_key instead", deprecator: Alchemy::Deprecation
86
7
 
87
- "::#{@@user_class_name}"
88
- end
8
+ delegate :user_class_primary_key=, to: :config
9
+ deprecate :user_class_primary_key= => "Use `Alchemy.config.user_class_primary_key instead", :deprecator => Alchemy::Deprecation
89
10
 
90
- def self.user_class_name=(user_class_name)
91
- @@user_class_name = user_class_name
92
- end
11
+ delegate :current_user_method, to: :config
12
+ deprecate current_user_method: "Use `Alchemy.config.current_user_method instead", deprecator: Alchemy::Deprecation
13
+
14
+ delegate :current_user_method=, to: :config
15
+ deprecate :current_user_method= => "Use `Alchemy.config.current_user_method instead", :deprecator => Alchemy::Deprecation
16
+
17
+ delegate :signup_path, to: :config
18
+ deprecate signup_path: "Use `Alchemy.config.signup_path instead", deprecator: Alchemy::Deprecation
19
+
20
+ delegate :signup_path=, to: :config
21
+ deprecate :signup_path= => "Use `Alchemy.config.signup_path instead", :deprecator => Alchemy::Deprecation
22
+
23
+ delegate :login_path, to: :config
24
+ deprecate login_path: "Use `Alchemy.config.login_path instead", deprecator: Alchemy::Deprecation
93
25
 
94
- def self.user_class
95
- @@user_class ||= begin
96
- @@user_class_name.constantize
97
- rescue NameError => e
98
- if /#{Regexp.escape(@@user_class_name)}/.match?(e.message)
99
- Rails.logger.warn <<~MSG
100
- #{e.message}
101
- #{e.backtrace.join("\n")}
26
+ delegate :login_path=, to: :config
27
+ deprecate :login_path= => "Use `Alchemy.config.login_path instead", :deprecator => Alchemy::Deprecation
102
28
 
103
- AlchemyCMS cannot find any user class!
29
+ delegate :logout_path, to: :config
30
+ deprecate logout_path: "Use `Alchemy.config.logout_path instead", deprecator: Alchemy::Deprecation
104
31
 
105
- Please add a user class and tell Alchemy about it:
32
+ delegate :logout_path=, to: :config
33
+ deprecate :logout_path= => "Use `Alchemy.config.logout_path instead", :deprecator => Alchemy::Deprecation
106
34
 
107
- # config/initializers/alchemy.rb
108
- Alchemy.user_class_name = 'MyUser'
35
+ delegate :logout_method, to: :config
36
+ deprecate logout_method: "Use `Alchemy.config.logout_method instead", deprecator: Alchemy::Deprecation
109
37
 
110
- Or add the `alchemy-devise` gem to your Gemfile:
38
+ delegate :logout_method=, to: :config
39
+ deprecate :logout_method= => "Use `Alchemy.config.logout_method instead", :deprecator => Alchemy::Deprecation
111
40
 
112
- bundle add alchemy-devise
41
+ delegate :unauthorized_path, to: :config
42
+ deprecate unauthorized_path: "Use `Alchemy.config.unauthorized_path instead", deprecator: Alchemy::Deprecation
113
43
 
114
- MSG
115
- nil
116
- else
117
- raise e
118
- end
44
+ delegate :unauthorized_path=, to: :config
45
+ deprecate :unauthorized_path= => "Use `Alchemy.config.unauthorized_path instead", :deprecator => Alchemy::Deprecation
46
+
47
+ delegate :user_class, to: :config
48
+ deprecate user_class: "Use `Alchemy.config.user_class instead", deprecator: Alchemy::Deprecation
49
+
50
+ delegate :user_class_name, to: :config
51
+ deprecate user_class_name: "Use `Alchemy.config.user_class_name instead", deprecator: Alchemy::Deprecation
52
+
53
+ def user_class_name=(klass_name)
54
+ config.user_class = klass_name
119
55
  end
120
- end
56
+ deprecate :user_class_name= => "Use `Alchemy.config.user_class instead", :deprecator => Alchemy::Deprecation
121
57
 
122
- # Register a CanCan Ability class
123
- #
124
- def self.register_ability(klass)
125
- @abilities ||= []
126
- @abilities << klass
127
- end
58
+ def register_ability(klass)
59
+ config.abilities.add(klass.name)
60
+ end
61
+ deprecate register_ability: 'Use `Alchemy.config.abilities.add("MyClass")` instead', deprecator: Alchemy::Deprecation
128
62
 
129
- # All CanCan Ability classes registered to Alchemy
130
- #
131
- def self.registered_abilities
132
- @abilities ||= []
63
+ def registered_abilities
64
+ config.abilities
65
+ end
66
+ deprecate registered_abilities: "Use `Alchemy.config.abilities` instead", deprecator: Alchemy::Deprecation
133
67
  end
134
68
  end
@@ -39,6 +39,7 @@ module Alchemy
39
39
 
40
40
  alias_method :get, :send
41
41
  alias_method :[], :get
42
+ alias_method :configure, :tap
42
43
 
43
44
  def show = self
44
45
 
@@ -67,8 +68,6 @@ module Alchemy
67
68
  ).to_h
68
69
  end
69
70
 
70
- delegate :to_json, to: :to_h
71
-
72
71
  class << self
73
72
  def defined_configurations = []
74
73
 
@@ -378,6 +378,69 @@ module Alchemy
378
378
  # Default is 1 hour.
379
379
  #
380
380
  option :update_check_cache_duration, :integer, default: 1
381
+
382
+ # === The user class to be used for Alchemy
383
+ #
384
+ # If you don't have your own user model or don't want to provide one,
385
+ # add the `alchemy-devise` gem into your App's Gemfile.
386
+ #
387
+ # == Example
388
+ # Alchemy.config.user_class = "MyUser"
389
+ option :user_class, :class
390
+
391
+ # Returns the user class name
392
+ #
393
+ # Prefixed with :: when getting to avoid constant name conflicts
394
+ def user_class_name = "::#{raw_user_class}"
395
+
396
+ # === User class primary key
397
+ #
398
+ # Your users table primary key. Defaults to +id+
399
+ #
400
+ # == Example
401
+ # Alchemy.config.user_class_primary_key = :uuid
402
+ option :user_class_primary_key, :symbol, default: :id
403
+
404
+ # === Current user method
405
+ #
406
+ # A method on your ApplicationController to get current user. Defaults to +:id+
407
+ #
408
+ # == Example
409
+ # Alchemy.config.current_user_method = :my_current_user
410
+ option :current_user_method, :symbol, default: :current_user
411
+
412
+ # === Signup Path
413
+ #
414
+ # The path to the signup form. Defaults to "/signup"
415
+ option :signup_path, :string, default: "/signup"
416
+
417
+ # === Login Path
418
+ #
419
+ # The path to the login form. Defaults to "/login"
420
+ option :login_path, :string, default: "/login"
421
+
422
+ # === Logout Path
423
+ #
424
+ # The path to the logout method.
425
+ option :logout_path, :string, default: "/logout"
426
+
427
+ # === Logout Method
428
+ #
429
+ # The http verb for the logout method. Defaults to "delete"
430
+ option :logout_method, :string, default: "delete"
431
+
432
+ # === Unauthorized Path
433
+ #
434
+ # The path to the page showing the user they're unauthorized
435
+ option :unauthorized_path, :string, default: "/"
436
+
437
+ # === CanCan abilities
438
+ #
439
+ # If your app or your engine has own CanCan abilities you must register them.
440
+ #
441
+ # == Example
442
+ # Alchemy.config.abilities.add("MyCustom::Ability")
443
+ option :abilities, :collection, item_type: :class
381
444
  end
382
445
  end
383
446
  end
@@ -34,7 +34,7 @@ module Alchemy
34
34
  # you can install the `alchemy-devise` gem that provides everything you need.
35
35
  #
36
36
  def current_alchemy_user
37
- current_user_method = Alchemy.current_user_method
37
+ current_user_method = Alchemy.config.current_user_method
38
38
  raise NoCurrentUserFoundError if !respond_to?(current_user_method, true)
39
39
 
40
40
  send current_user_method
@@ -80,8 +80,7 @@ module Alchemy
80
80
 
81
81
  def load_alchemy_language_from_id_or_code(id_or_code)
82
82
  Language.find_by(id: id_or_code) ||
83
- Language.find_by_code(id_or_code) ||
84
- Language.default
83
+ Language.find_by_code(id_or_code)
85
84
  end
86
85
 
87
86
  # Stores language in +Current.language+
@@ -79,11 +79,11 @@ module Alchemy
79
79
  config.to_prepare do
80
80
  # Definition files
81
81
  elements_reloader = Rails.application.config.file_watcher.new([ElementDefinition.definitions_file_path]) do
82
- Rails.logger.info "[alchemy] Reloading Element Definitions."
82
+ Logger.info "Reloading Element Definitions."
83
83
  ElementDefinition.reset!
84
84
  end
85
85
  page_layouts_reloader = Rails.application.config.file_watcher.new([PageDefinition.layouts_file_path]) do
86
- Rails.logger.info "[alchemy] Reloading Page Layouts."
86
+ Logger.info "Reloading Page Layouts."
87
87
  PageDefinition.reset!
88
88
  end
89
89
  [elements_reloader, page_layouts_reloader].each do |reloader|
@@ -122,6 +122,10 @@ module Alchemy
122
122
  ActiveStorage::Blob.define_singleton_method(:ransackable_attributes) do |_auth_object|
123
123
  %w[filename]
124
124
  end
125
+
126
+ ActiveStorage::Blob.define_singleton_method(:ransackable_associations) do |_auth_object|
127
+ %w[]
128
+ end
125
129
  end
126
130
  end
127
131
 
@@ -143,10 +147,10 @@ module Alchemy
143
147
  end
144
148
 
145
149
  config.after_initialize do
146
- if Alchemy.user_class
150
+ if Alchemy.config.user_class
147
151
  ActiveSupport.on_load(:active_record) do
148
- Alchemy.user_class.model_stamper
149
- Alchemy.user_class.stampable(stamper_class_name: Alchemy.user_class_name)
152
+ Alchemy.config.user_class.model_stamper
153
+ Alchemy.config.user_class.stampable(stamper_class_name: Alchemy.config.user_class_name)
150
154
  end
151
155
  end
152
156
 
@@ -167,14 +171,7 @@ module Alchemy
167
171
  Rack::Mime::MIME_TYPES[".webp"] = webp
168
172
  end
169
173
 
170
- # ActiveStorage 7.2 adds support for webp, but we still support Rails 7.1
171
174
  if app.config.active_storage
172
- unless app.config.active_storage.web_image_content_types.include? webp
173
- app.config.active_storage.web_image_content_types += [webp]
174
- end
175
- unless app.config.active_storage.content_types_allowed_inline.include? webp
176
- app.config.active_storage.content_types_allowed_inline += [webp]
177
- end
178
175
  # Rails sends svg images as attachment instead of inline.
179
176
  # We want to display svgs and not download them.
180
177
  unless app.config.active_storage.content_types_allowed_inline.include? svg
@@ -4,7 +4,7 @@ module Alchemy
4
4
  module ErrorTracking
5
5
  class ErrorLogger < BaseHandler
6
6
  def self.call(exception)
7
- ::Rails.logger.tagged("alchemy_cms") do
7
+ ::Rails.logger.tagged("alchemy") do
8
8
  ::Rails.logger.error("#{exception.class.name}: #{exception.message} in #{exception.backtrace.first}")
9
9
  end
10
10
  end
@@ -26,7 +26,7 @@ module Alchemy
26
26
  end
27
27
 
28
28
  def message
29
- "Element definition for #{@name} not found. Please check your elements.yml"
29
+ "Element definition for '#{@name}' not found! Please check your element definitions."
30
30
  end
31
31
  end
32
32
 
@@ -2,14 +2,44 @@
2
2
 
3
3
  module Alchemy
4
4
  module Logger
5
- # Logs a debug message to the Rails standard logger and adds some nicer formatting
6
- def self.warn(message, caller_string)
7
- Rails.logger.debug %(\n++++ WARNING: #{message}\nCalled from: #{caller_string}\n)
5
+ # Logs a debug message to the Rails standard logger
6
+ def self.debug(message)
7
+ Rails.logger.tagged("alchemy") do
8
+ Rails.logger.debug(message)
9
+ end
10
+ nil
11
+ end
12
+
13
+ # Logs a error message to the Rails standard logger
14
+ def self.error(message)
15
+ Rails.logger.tagged("alchemy") do
16
+ Rails.logger.error("ERROR: #{message}")
17
+ end
18
+ nil
19
+ end
20
+
21
+ # Logs a info message to the Rails standard logger
22
+ def self.info(message)
23
+ Rails.logger.tagged("alchemy") do
24
+ Rails.logger.info(message)
25
+ end
26
+ nil
27
+ end
28
+
29
+ # Logs a warning message to the Rails standard logger
30
+ def self.warn(message, caller_string = nil)
31
+ if caller_string
32
+ Alchemy::Deprecation.warn("Alchemy::Logger.warn second argument is deprecated and will be removed in Alchemy 9.0")
33
+ end
34
+ Rails.logger.tagged("alchemy") do
35
+ Rails.logger.warn("WARNING: #{message}")
36
+ end
8
37
  nil
9
38
  end
10
39
 
11
40
  def log_warning(message)
12
- Alchemy::Logger.warn(message, caller(1..1))
41
+ Alchemy::Logger.warn(message)
13
42
  end
43
+ deprecate log_warning: "Alchemy::Logger.warn", deprecator: Alchemy::Deprecation
14
44
  end
15
45
  end
@@ -17,12 +17,6 @@ module Alchemy
17
17
  .parameterize
18
18
  end
19
19
 
20
- # Converts a filename and suffix into a human readable name.
21
- #
22
- def convert_to_humanized_name(name, suffix)
23
- name.gsub(/\.#{::Regexp.quote(suffix)}$/i, "").tr("_", " ").strip
24
- end
25
-
26
20
  # Sanitizes a given filename by removing directory traversal attempts and HTML entities.
27
21
  def sanitized_filename(file_name)
28
22
  file_name = File.basename(file_name)
@@ -63,14 +63,14 @@ module Alchemy
63
63
  def seed_users
64
64
  desc "Seeding Alchemy users from #{user_seeds_file}"
65
65
 
66
- if Alchemy.user_class.exists?
66
+ if Alchemy.config.user_class.exists?
67
67
  log "There are already users present in your database. " \
68
68
  "Please use `rake db:reset' if you want to rebuild your database.", :skip
69
69
  false
70
70
  else
71
71
  users = load_yaml_file(user_seeds_file)
72
72
  users.each do |draft|
73
- user = Alchemy.user_class.create!(draft)
73
+ user = Alchemy.config.user_class.create!(draft)
74
74
  log "Created user: #{user.try(:email) || user.try(:login) || user.id}"
75
75
  end
76
76
  end
@@ -18,11 +18,11 @@ module Alchemy
18
18
  end
19
19
 
20
20
  def pages_count_by_type
21
- res = Alchemy::Page.all
22
- .select("page_layout, COUNT(*) AS count")
21
+ res = Alchemy::Page
23
22
  .group(:page_layout)
24
- .order("count DESC, page_layout ASC")
25
- .map { |p| {"page_layout" => p.page_layout, "count" => p.count} }
23
+ .order("count_all DESC, page_layout ASC")
24
+ .count
25
+ .map { |layout, count| {"page_layout" => layout, "count" => count} }
26
26
  Alchemy::PageDefinition.all.reject { |page_layout| res.map { |p| p["page_layout"] }.include?(page_layout.name) }.sort_by(&:name).each do |page_layout|
27
27
  res << {"page_layout" => page_layout.name, "count" => 0}
28
28
  end
@@ -3,6 +3,9 @@
3
3
  FactoryBot.define do
4
4
  factory :alchemy_page_version, class: "Alchemy::PageVersion" do
5
5
  association :page, factory: :alchemy_page
6
+ title { nil }
7
+ meta_description { nil }
8
+ meta_keywords { nil }
6
9
 
7
10
  trait :published do
8
11
  public_on { Time.current }
@@ -212,6 +212,22 @@ RSpec.shared_examples_for "having picture thumbnails" do
212
212
  thumbnail_url
213
213
  end
214
214
 
215
+ context "with size given" do
216
+ subject(:thumbnail_url) { record.thumbnail_url(size: "10x10") }
217
+
218
+ it "passes it to the thumbnail url options." do
219
+ expect(picture).to receive(:url).with(hash_including(size: "10x10"))
220
+ thumbnail_url
221
+ end
222
+ end
223
+
224
+ context "with no size given" do
225
+ it "passes default size to the thumbnail url options." do
226
+ expect(picture).to receive(:url).with(hash_including(size: "160x120"))
227
+ thumbnail_url
228
+ end
229
+ end
230
+
215
231
  context "when crop is enabled in the settings" do
216
232
  let(:settings) do
217
233
  {crop: true}
@@ -300,6 +316,20 @@ RSpec.shared_examples_for "having picture thumbnails" do
300
316
  allow(record).to receive(:settings) { settings }
301
317
  end
302
318
 
319
+ context "with size given" do
320
+ subject(:thumbnail_url_options) { record.thumbnail_url_options(size: "10x10") }
321
+
322
+ it "passes it to the thumbnail url options." do
323
+ expect(thumbnail_url_options[:size]).to eq("10x10")
324
+ end
325
+ end
326
+
327
+ context "with no size given" do
328
+ it "passes default size to the thumbnail url options." do
329
+ expect(thumbnail_url_options[:size]).to eq("160x120")
330
+ end
331
+ end
332
+
303
333
  context "with picture assigned" do
304
334
  let(:picture) do
305
335
  create(:alchemy_picture)
@@ -1,21 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.shared_examples_for "an alchemy ingredient editor" do
4
- let(:ingredient_editor) { Alchemy::IngredientEditor.new(ingredient) }
4
+ let(:ingredient_editor) { described_class.new(ingredient) }
5
5
 
6
6
  before do
7
- view.class.send :include, Alchemy::Admin::BaseHelper
8
- view.class.send :include, Alchemy::Admin::IngredientsHelper
9
- allow(element_editor).to receive(:ingredients) { [ingredient_editor] }
7
+ vc_test_view_context.class.include Alchemy::Admin::BaseHelper
8
+ allow(vc_test_view_context).to receive(:can?).and_return(true)
10
9
  end
11
10
 
12
11
  subject do
13
- render element_editor
14
- rendered
12
+ render_inline(ingredient_editor)
13
+ page
15
14
  end
16
15
 
17
16
  it "renders a ingredient editor", :aggregate_failures do
18
17
  is_expected.to have_css(".ingredient-editor.#{ingredient_editor.partial_name}")
19
18
  is_expected.to have_css("[data-ingredient-role]")
19
+ is_expected.to have_css("##{ingredient.class.model_name.param_key}_#{ingredient.id}")
20
+ end
21
+
22
+ it "provides a label" do
23
+ is_expected.to have_css("label[for]", text: Alchemy.t(
24
+ ingredient_editor.role,
25
+ scope: "ingredient_roles.#{element.name}",
26
+ default: Alchemy.t("ingredient_roles.#{ingredient_editor.role}", default: ingredient_editor.role.humanize)
27
+ ))
28
+ end
29
+
30
+ it "provides a ingredient input field" do
31
+ is_expected.to have_css("input[name]")
32
+ end
33
+
34
+ it "provides an ingredient id field for nested attributes" do
35
+ counter = ingredient_editor.send(:form_field_counter)
36
+ is_expected.to have_css(
37
+ "input[type='hidden'][name='element[ingredients_attributes][#{counter}][id]'][value='#{ingredient.id}']",
38
+ visible: :hidden
39
+ )
20
40
  end
21
41
  end