alchemy_cms 7.4.6 → 8.0.0.a

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.

Potentially problematic release.


This version of alchemy_cms might be problematic. Click here for more details.

Files changed (349) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -0
  3. data/Gemfile +13 -6
  4. data/README.md +13 -5
  5. data/alchemy_cms.gemspec +14 -5
  6. data/app/assets/builds/alchemy/admin/page-select.css +1 -1
  7. data/app/assets/builds/alchemy/admin/print.css +1 -1
  8. data/app/assets/builds/alchemy/admin.css +2 -2
  9. data/app/assets/builds/alchemy/custom-properties.css +1 -1
  10. data/app/assets/builds/alchemy/welcome.css +1 -1
  11. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  12. data/app/assets/builds/tinymce/skins/ui/alchemy/content.min.css +1 -0
  13. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
  14. data/app/assets/config/alchemy_manifest.js +0 -2
  15. data/app/assets/images/alchemy/icons-sprite.svg +1 -0
  16. data/app/components/alchemy/admin/resource/applied_filter.rb +29 -0
  17. data/app/components/alchemy/admin/resource/checkbox_filter.rb +36 -0
  18. data/app/components/alchemy/admin/resource/datepicker_filter.rb +42 -0
  19. data/app/components/alchemy/admin/resource/select_filter.rb +43 -0
  20. data/app/components/alchemy/admin/toolbar_button.rb +5 -2
  21. data/app/components/alchemy/ingredients/number_view.rb +18 -0
  22. data/app/controllers/alchemy/admin/attachments_controller.rb +8 -15
  23. data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -6
  24. data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
  25. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  26. data/app/controllers/alchemy/admin/pages_controller.rb +15 -15
  27. data/app/controllers/alchemy/admin/pictures_controller.rb +9 -5
  28. data/app/controllers/alchemy/admin/resources_controller.rb +16 -106
  29. data/app/controllers/alchemy/attachments_controller.rb +43 -14
  30. data/app/controllers/alchemy/messages_controller.rb +1 -1
  31. data/app/controllers/alchemy/pages_controller.rb +7 -2
  32. data/app/controllers/concerns/alchemy/admin/resource_filter.rb +92 -0
  33. data/app/decorators/alchemy/element_editor.rb +5 -48
  34. data/app/decorators/alchemy/ingredient_editor.rb +3 -53
  35. data/app/helpers/alchemy/admin/base_helper.rb +14 -84
  36. data/app/helpers/alchemy/admin/elements_helper.rb +4 -4
  37. data/app/helpers/alchemy/admin/pages_helper.rb +1 -1
  38. data/app/helpers/alchemy/base_helper.rb +0 -30
  39. data/app/helpers/alchemy/elements_block_helper.rb +0 -14
  40. data/app/helpers/alchemy/pages_helper.rb +1 -1
  41. data/{lib → app/helpers}/alchemy/resources_helper.rb +5 -45
  42. data/app/javascript/alchemy_admin/components/action.js +2 -0
  43. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +3 -3
  44. data/app/javascript/alchemy_admin/components/datepicker.js +10 -2
  45. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +7 -7
  46. data/app/javascript/alchemy_admin/components/element_editor.js +1 -1
  47. data/app/javascript/alchemy_admin/components/index.js +1 -0
  48. data/app/javascript/alchemy_admin/components/remote_select.js +4 -1
  49. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +5 -1
  50. data/app/javascript/alchemy_admin/components/tinymce.js +4 -2
  51. data/app/javascript/alchemy_admin/components/update_check.js +42 -0
  52. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +15 -8
  53. data/app/javascript/alchemy_admin/components/uploader/progress.js +12 -6
  54. data/app/javascript/alchemy_admin/components/uploader.js +4 -2
  55. data/app/javascript/alchemy_admin/confirm_dialog.js +27 -57
  56. data/app/javascript/alchemy_admin/dirty.js +3 -2
  57. data/app/javascript/alchemy_admin/i18n.js +15 -16
  58. data/app/javascript/alchemy_admin/initializer.js +1 -49
  59. data/app/javascript/alchemy_admin/utils/ajax.js +51 -44
  60. data/app/javascript/alchemy_admin.js +3 -8
  61. data/app/models/alchemy/admin/filters/base.rb +38 -0
  62. data/app/models/alchemy/admin/filters/checkbox.rb +24 -0
  63. data/app/models/alchemy/admin/filters/datepicker.rb +53 -0
  64. data/app/models/alchemy/admin/filters/select.rb +70 -0
  65. data/app/models/alchemy/admin/resource_name.rb +27 -0
  66. data/app/models/alchemy/attachment.rb +51 -34
  67. data/app/models/alchemy/base_record.rb +2 -0
  68. data/app/models/alchemy/element/definitions.rb +1 -1
  69. data/app/models/alchemy/element/element_ingredients.rb +6 -6
  70. data/app/models/alchemy/element/presenters.rb +3 -12
  71. data/app/models/alchemy/element.rb +9 -27
  72. data/app/models/alchemy/element_definition.rb +160 -0
  73. data/app/models/alchemy/ingredient.rb +10 -43
  74. data/app/models/alchemy/ingredient_definition.rb +134 -0
  75. data/app/models/alchemy/ingredient_validator.rb +7 -3
  76. data/app/models/alchemy/ingredients/number.rb +19 -0
  77. data/app/models/alchemy/language.rb +0 -14
  78. data/app/models/alchemy/message.rb +3 -7
  79. data/app/models/alchemy/node.rb +1 -1
  80. data/app/models/alchemy/page/{page_layouts.rb → definitions.rb} +12 -19
  81. data/app/models/alchemy/page/fixed_attributes.rb +1 -1
  82. data/app/models/alchemy/page/page_elements.rb +13 -14
  83. data/app/models/alchemy/page/page_natures.rb +7 -7
  84. data/app/models/alchemy/page/page_scopes.rb +1 -1
  85. data/app/models/alchemy/page.rb +11 -33
  86. data/app/models/alchemy/page_definition.rb +115 -0
  87. data/app/models/alchemy/picture.rb +69 -79
  88. data/app/models/alchemy/picture_variant.rb +115 -5
  89. data/{lib → app/models}/alchemy/resource.rb +4 -18
  90. data/{lib → app/models}/alchemy/searchable_resource.rb +15 -0
  91. data/app/models/alchemy/site/layout.rb +5 -5
  92. data/app/models/alchemy/site.rb +0 -15
  93. data/app/models/alchemy/storage_adapter/active_storage/attachment_url.rb +41 -0
  94. data/app/models/alchemy/storage_adapter/active_storage/picture_url.rb +55 -0
  95. data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +40 -0
  96. data/app/models/alchemy/storage_adapter/active_storage.rb +173 -0
  97. data/app/models/alchemy/{attachment/url.rb → storage_adapter/dragonfly/attachment_url.rb} +12 -12
  98. data/app/models/alchemy/{picture/url.rb → storage_adapter/dragonfly/picture_url.rb} +28 -12
  99. data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
  100. data/app/models/alchemy/storage_adapter/dragonfly.rb +183 -0
  101. data/app/models/alchemy/storage_adapter.rb +74 -0
  102. data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
  103. data/app/serializers/alchemy/element_serializer.rb +0 -1
  104. data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -0
  105. data/app/stylesheets/alchemy/_defaults.scss +3 -0
  106. data/app/stylesheets/alchemy/_extends.scss +69 -0
  107. data/app/{assets/stylesheets → stylesheets}/alchemy/_mixins.scss +36 -49
  108. data/app/stylesheets/alchemy/_variables.scss +5 -0
  109. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +20 -37
  110. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +16 -14
  111. data/app/stylesheets/alchemy/admin/buttons.scss +160 -0
  112. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/clipboard.scss +2 -2
  113. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dashboard.scss +13 -16
  114. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dialogs.scss +23 -16
  115. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +150 -105
  116. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
  117. data/app/stylesheets/alchemy/admin/filters.scss +58 -0
  118. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +53 -60
  119. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +21 -7
  120. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +31 -19
  121. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -16
  122. data/app/stylesheets/alchemy/admin/hints.scss +5 -0
  123. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +10 -1
  124. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +10 -8
  125. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/images.scss +1 -1
  126. data/app/stylesheets/alchemy/admin/labels.scss +5 -0
  127. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/lists.scss +3 -3
  128. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/navigation.scss +61 -55
  129. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
  130. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +18 -18
  131. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +2 -2
  132. data/app/stylesheets/alchemy/admin/pagination.scss +144 -0
  133. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/preview_window.scss +8 -6
  134. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
  135. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/resource_info.scss +8 -5
  136. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +9 -6
  137. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +49 -37
  138. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +5 -6
  139. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +38 -33
  140. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +6 -4
  141. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tags.scss +6 -4
  142. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +12 -6
  143. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +2 -2
  144. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
  145. data/app/stylesheets/alchemy/admin.scss +44 -0
  146. data/app/stylesheets/alchemy/custom-properties.css +244 -0
  147. data/app/stylesheets/alchemy/welcome.scss +75 -0
  148. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/content/alchemy/content.scss +8 -9
  149. data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
  150. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +133 -136
  151. data/app/views/alchemy/admin/attachments/_files_list.html.erb +2 -2
  152. data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
  153. data/app/views/alchemy/admin/{elements/_clipboard_button.html.erb → clipboard/_button.html.erb} +3 -5
  154. data/app/views/alchemy/admin/clipboard/_update_nested_element_button.turbo_stream.erb +11 -0
  155. data/app/views/alchemy/admin/clipboard/clear.turbo_stream.erb +4 -0
  156. data/app/views/alchemy/admin/clipboard/index.html.erb +15 -13
  157. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +18 -0
  158. data/app/views/alchemy/admin/clipboard/remove.turbo_stream.erb +9 -0
  159. data/app/views/alchemy/admin/dashboard/info.html.erb +17 -31
  160. data/app/views/alchemy/admin/elements/_element.html.erb +4 -8
  161. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  162. data/app/views/alchemy/admin/elements/_header.html.erb +1 -0
  163. data/app/views/alchemy/admin/elements/_toolbar.html.erb +4 -6
  164. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +2 -1
  165. data/app/views/alchemy/admin/elements/index.html.erb +2 -2
  166. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +3 -16
  167. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +0 -9
  168. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  169. data/app/views/alchemy/admin/languages/_table.html.erb +1 -1
  170. data/app/views/alchemy/admin/languages/index.html.erb +5 -2
  171. data/app/views/alchemy/admin/layoutpages/index.html.erb +1 -12
  172. data/app/views/alchemy/admin/pages/_form.html.erb +2 -2
  173. data/app/views/alchemy/admin/pages/_page.html.erb +2 -3
  174. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -15
  175. data/app/views/alchemy/admin/pages/index.html.erb +1 -1
  176. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +9 -12
  177. data/app/views/alchemy/admin/partials/_search_form.html.erb +4 -9
  178. data/app/views/alchemy/admin/pictures/_archive.html.erb +4 -7
  179. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +2 -1
  180. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -1
  181. data/app/views/alchemy/admin/pictures/index.html.erb +2 -7
  182. data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
  183. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +11 -21
  184. data/app/views/alchemy/admin/resources/_pagination.html.erb +6 -0
  185. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +4 -2
  186. data/app/views/alchemy/admin/resources/_resource_table.html.erb +1 -1
  187. data/app/views/alchemy/admin/resources/_table_header.html.erb +1 -15
  188. data/app/views/alchemy/admin/sites/index.html.erb +5 -1
  189. data/app/views/alchemy/admin/styleguide/index.html.erb +8 -0
  190. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  191. data/app/views/alchemy/admin/tinymce/_setup.html.erb +7 -7
  192. data/app/{javascript/alchemy_admin/locales/en.js → views/alchemy/admin/translations/_en.js} +5 -2
  193. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
  194. data/app/views/alchemy/admin/uploader/_setup.html.erb +4 -4
  195. data/app/views/alchemy/base/redirect.js.erb +1 -1
  196. data/app/views/alchemy/ingredients/_number_editor.html.erb +24 -0
  197. data/app/views/alchemy/no_index.html.erb +31 -0
  198. data/app/views/alchemy/welcome.html.erb +12 -10
  199. data/app/views/kaminari/alchemy/_first_page.html.erb +5 -3
  200. data/app/views/kaminari/alchemy/_last_page.html.erb +5 -3
  201. data/app/views/kaminari/alchemy/_next_page.html.erb +5 -3
  202. data/app/views/kaminari/alchemy/_paginator.html.erb +18 -13
  203. data/app/views/kaminari/alchemy/_prev_page.html.erb +5 -3
  204. data/app/views/layouts/alchemy/admin.html.erb +5 -9
  205. data/bun.lockb +0 -0
  206. data/bundles/remixicon.mjs +153 -0
  207. data/config/alchemy/config.yml +3 -2
  208. data/config/initializers/dragonfly.rb +0 -1
  209. data/config/initializers/mime_types.rb +1 -0
  210. data/config/locales/alchemy.en.yml +32 -14
  211. data/config/routes.rb +0 -2
  212. data/eslint.config.js +2 -1
  213. data/lib/alchemy/admin/preview_url.rb +4 -5
  214. data/lib/alchemy/cache_digests/template_tracker.rb +6 -9
  215. data/lib/alchemy/config_missing.rb +14 -0
  216. data/lib/alchemy/configuration/base_option.rb +24 -0
  217. data/lib/alchemy/configuration/boolean_option.rb +16 -0
  218. data/lib/alchemy/configuration/class_option.rb +15 -0
  219. data/lib/alchemy/configuration/class_set_option.rb +46 -0
  220. data/lib/alchemy/configuration/integer_list_option.rb +13 -0
  221. data/lib/alchemy/configuration/integer_option.rb +12 -0
  222. data/lib/alchemy/configuration/list_option.rb +22 -0
  223. data/lib/alchemy/configuration/regexp_option.rb +11 -0
  224. data/lib/alchemy/configuration/string_list_option.rb +13 -0
  225. data/lib/alchemy/configuration/string_option.rb +11 -0
  226. data/lib/alchemy/configuration.rb +115 -0
  227. data/lib/alchemy/configuration_methods.rb +3 -1
  228. data/lib/alchemy/configurations/default_language.rb +12 -0
  229. data/lib/alchemy/configurations/default_site.rb +10 -0
  230. data/lib/alchemy/configurations/format_matchers.rb +11 -0
  231. data/lib/alchemy/configurations/mailer.rb +16 -0
  232. data/lib/alchemy/configurations/main.rb +216 -0
  233. data/lib/alchemy/configurations/preview.rb +32 -0
  234. data/lib/alchemy/configurations/sitemap.rb +10 -0
  235. data/lib/alchemy/configurations/uploader.rb +34 -0
  236. data/lib/alchemy/engine.rb +65 -17
  237. data/lib/alchemy/hints.rb +3 -7
  238. data/lib/alchemy/on_page_layout.rb +2 -2
  239. data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
  240. data/lib/alchemy/seeder.rb +2 -2
  241. data/lib/alchemy/tasks/usage.rb +4 -4
  242. data/lib/alchemy/test_support/config_stubbing.rb +1 -7
  243. data/lib/alchemy/test_support/factories/attachment_factory.rb +13 -2
  244. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  245. data/lib/alchemy/test_support/factories/page_factory.rb +2 -3
  246. data/lib/alchemy/test_support/factories/picture_factory.rb +30 -2
  247. data/lib/alchemy/test_support/factories/site_factory.rb +2 -2
  248. data/lib/alchemy/test_support/having_crop_action_examples.rb +2 -2
  249. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +80 -26
  250. data/lib/alchemy/test_support/shared_ingredient_examples.rb +5 -5
  251. data/lib/alchemy/upgrader/.keep +0 -0
  252. data/lib/alchemy/upgrader/eight_zero.rb +14 -0
  253. data/lib/alchemy/upgrader.rb +33 -20
  254. data/lib/alchemy/version.rb +1 -1
  255. data/lib/alchemy.rb +192 -170
  256. data/lib/alchemy_cms.rb +1 -7
  257. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +0 -3
  258. data/lib/generators/alchemy/install/files/_article.html.erb +6 -4
  259. data/lib/generators/alchemy/install/files/alchemy.en.yml +22 -3
  260. data/lib/generators/alchemy/install/files/application.html.erb +5 -0
  261. data/lib/generators/alchemy/install/install_generator.rb +5 -14
  262. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +196 -0
  263. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -1
  264. data/lib/generators/alchemy/install/templates/elements.yml.tt +3 -1
  265. data/lib/generators/alchemy/install/templates/menus.yml.tt +1 -1
  266. data/lib/generators/alchemy/install/templates/page_layouts.yml.tt +2 -2
  267. data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -2
  268. data/lib/tasks/alchemy/assets.rake +14 -0
  269. data/lib/tasks/alchemy/upgrade.rake +12 -47
  270. data/vendor/javascript/tinymce.min.js +1 -1
  271. data/vitest.config.js +21 -0
  272. metadata +181 -180
  273. data/app/assets/builds/alchemy/admin/page-select.css.map +0 -1
  274. data/app/assets/builds/alchemy/admin/print.css.map +0 -1
  275. data/app/assets/builds/alchemy/admin.css.map +0 -1
  276. data/app/assets/builds/alchemy/custom-properties.css.map +0 -1
  277. data/app/assets/builds/alchemy/welcome.css.map +0 -1
  278. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +0 -1
  279. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +0 -1
  280. data/app/assets/javascripts/alchemy/admin.js +0 -10
  281. data/app/assets/stylesheets/alchemy/_defaults.scss +0 -3
  282. data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +0 -45
  283. data/app/assets/stylesheets/alchemy/_deprecation.scss +0 -17
  284. data/app/assets/stylesheets/alchemy/_extends.scss +0 -62
  285. data/app/assets/stylesheets/alchemy/_variables.scss +0 -201
  286. data/app/assets/stylesheets/alchemy/admin/buttons.scss +0 -124
  287. data/app/assets/stylesheets/alchemy/admin/hints.scss +0 -5
  288. data/app/assets/stylesheets/alchemy/admin/labels.scss +0 -3
  289. data/app/assets/stylesheets/alchemy/admin/pagination.scss +0 -92
  290. data/app/assets/stylesheets/alchemy/admin.scss +0 -42
  291. data/app/assets/stylesheets/alchemy/custom-properties.css +0 -98
  292. data/app/assets/stylesheets/alchemy/welcome.scss +0 -57
  293. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.css +0 -711
  294. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.css +0 -705
  295. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.min.css +0 -7
  296. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.min.css +0 -7
  297. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.css +0 -29
  298. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.min.css +0 -7
  299. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.css +0 -677
  300. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.min.css +0 -7
  301. data/app/controllers/alchemy/elements_controller.rb +0 -32
  302. data/app/models/alchemy/element/dom_id.rb +0 -31
  303. data/app/models/alchemy/picture/calculations.rb +0 -49
  304. data/app/models/alchemy/picture/transformations.rb +0 -115
  305. data/app/views/alchemy/admin/attachments/destroy.js.erb +0 -1
  306. data/app/views/alchemy/admin/clipboard/clear.js.erb +0 -3
  307. data/app/views/alchemy/admin/clipboard/insert.js.erb +0 -29
  308. data/app/views/alchemy/admin/clipboard/remove.js.erb +0 -10
  309. data/app/views/alchemy/admin/resources/_filter.html.erb +0 -12
  310. data/app/views/alchemy/admin/resources/_resource.html.erb +0 -34
  311. data/app/views/alchemy/admin/resources/_table.html.erb +0 -29
  312. data/app/views/alchemy/elements/show.html.erb +0 -1
  313. data/app/views/alchemy/elements/show.js.erb +0 -1
  314. data/app/views/alchemy/ingredients/_audio_view.html.erb +0 -1
  315. data/app/views/alchemy/ingredients/_boolean_view.html.erb +0 -1
  316. data/app/views/alchemy/ingredients/_datetime_view.html.erb +0 -3
  317. data/app/views/alchemy/ingredients/_file_view.html.erb +0 -4
  318. data/app/views/alchemy/ingredients/_headline_view.html.erb +0 -4
  319. data/app/views/alchemy/ingredients/_html_view.html.erb +0 -1
  320. data/app/views/alchemy/ingredients/_link_view.html.erb +0 -4
  321. data/app/views/alchemy/ingredients/_node_view.html.erb +0 -1
  322. data/app/views/alchemy/ingredients/_page_view.html.erb +0 -1
  323. data/app/views/alchemy/ingredients/_picture_view.html.erb +0 -4
  324. data/app/views/alchemy/ingredients/_richtext_view.html.erb +0 -3
  325. data/app/views/alchemy/ingredients/_select_view.html.erb +0 -1
  326. data/app/views/alchemy/ingredients/_text_view.html.erb +0 -4
  327. data/app/views/alchemy/ingredients/_video_view.html.erb +0 -3
  328. data/babel.config.js +0 -12
  329. data/config/initializers/assets.rb +0 -4
  330. data/lib/alchemy/config.rb +0 -114
  331. data/lib/alchemy/element_definition.rb +0 -73
  332. data/lib/alchemy/page_layout.rb +0 -73
  333. data/lib/alchemy/resource_filter.rb +0 -40
  334. data/lib/alchemy/upgrader/seven_point_four.rb +0 -26
  335. data/lib/alchemy/upgrader/seven_point_three.rb +0 -52
  336. data/lib/generators/alchemy/ingredient/templates/view.html.erb +0 -1
  337. data/lib/generators/alchemy/install/files/alchemy_admin.js +0 -1
  338. data/lib/generators/alchemy/install/files/all.js +0 -11
  339. data/lib/generators/alchemy/install/files/article.css +0 -25
  340. data/vendor/assets/images/remixicon.symbol.svg +0 -11
  341. /data/app/{assets/stylesheets → stylesheets}/alchemy/_fonts.scss +0 -0
  342. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachment-select.scss +0 -0
  343. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachments.scss +0 -0
  344. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flash.scss +0 -0
  345. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/list_filter.scss +0 -0
  346. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/node-select.scss +0 -0
  347. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/spinner.scss +0 -0
  348. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/skintool.json +0 -0
  349. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/fonts/tinymce-mobile.woff +0 -0
@@ -33,7 +33,7 @@ module Alchemy
33
33
  #
34
34
  class ToolbarButton < ViewComponent::Base
35
35
  erb_template <<-ERB
36
- <div class="toolbar_button">
36
+ <div class="toolbar_button" id="<%= id %>">
37
37
  <sl-tooltip content="<%= label %>" placement="<%= tooltip_placement %>">
38
38
  <%= link_to(render_icon(icon, style: icon_style), url, {
39
39
  class: css_classes,
@@ -57,6 +57,7 @@ module Alchemy
57
57
  :if_permitted_to,
58
58
  :active,
59
59
  :link_options,
60
+ :id,
60
61
  :icon_style,
61
62
  :tooltip_placement
62
63
 
@@ -72,6 +73,7 @@ module Alchemy
72
73
  if_permitted_to: [],
73
74
  active: false,
74
75
  link_options: {},
76
+ id: nil,
75
77
  icon_style: "line",
76
78
  tooltip_placement: "top-start"
77
79
  )
@@ -85,6 +87,7 @@ module Alchemy
85
87
  @if_permitted_to = if_permitted_to
86
88
  @active = active
87
89
  @link_options = link_options
90
+ @id = id
88
91
  @icon_style = icon_style
89
92
  @tooltip_placement = tooltip_placement
90
93
  end
@@ -100,7 +103,7 @@ module Alchemy
100
103
  def permission_options = if_permitted_to.presence || permissions_from_url
101
104
 
102
105
  def permissions_from_url
103
- action_controller = url.gsub(/\A\//, "").split("/")
106
+ action_controller = url.delete_prefix("/").split("/")
104
107
  [
105
108
  action_controller.last.to_sym,
106
109
  action_controller[0..action_controller.length - 2].join("_").to_sym
@@ -0,0 +1,18 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ class NumberView < BaseView
4
+ def initialize(ingredient, options = {})
5
+ super(ingredient)
6
+ @options = {
7
+ units: {
8
+ unit: settings_value(:unit, value: options[:unit])
9
+ }.merge(options[:units] || {})
10
+ }
11
+ end
12
+
13
+ def call
14
+ number_to_human(value, @options)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -6,6 +6,12 @@ module Alchemy
6
6
  include UploaderResponses
7
7
  include ArchiveOverlay
8
8
 
9
+ add_alchemy_filter :by_file_type, type: :select,
10
+ options: -> { Alchemy::Attachment.file_types(_1.result) }
11
+ add_alchemy_filter :recent, type: :checkbox
12
+ add_alchemy_filter :last_upload, type: :checkbox
13
+ add_alchemy_filter :without_tag, type: :checkbox
14
+
9
15
  helper "alchemy/admin/tags"
10
16
 
11
17
  before_action(only: :assign) do
@@ -21,10 +27,6 @@ module Alchemy
21
27
  @attachments = @attachments.tagged_with(search_filter_params[:tagged_with])
22
28
  end
23
29
 
24
- if search_filter_params[:filter].present?
25
- @attachments = apply_filters(@attachments)
26
- end
27
-
28
30
  @attachments = @attachments
29
31
  .page(params[:page] || 1)
30
32
  .per(items_per_page)
@@ -58,18 +60,9 @@ module Alchemy
58
60
  end
59
61
 
60
62
  def destroy
61
- name = @attachment.name
62
63
  @attachment.destroy
63
- @url = admin_attachments_url(search_filter_params)
64
- flash[:notice] = Alchemy.t("File deleted successfully", name: name)
65
- end
66
-
67
- def download
68
- @attachment = Attachment.find(params[:id])
69
- send_file @attachment.file.path, {
70
- filename: @attachment.file_name,
71
- type: @attachment.file_mime_type
72
- }
64
+ flash[:notice] = Alchemy.t("File deleted successfully", name: @attachment.name)
65
+ redirect_to alchemy.admin_attachments_path(**search_filter_params)
73
66
  end
74
67
 
75
68
  private
@@ -8,6 +8,8 @@ module Alchemy
8
8
  authorize_resource class: :alchemy_admin_clipboard
9
9
  before_action :set_clipboard
10
10
 
11
+ helper_method :remarkable_type
12
+
11
13
  def index
12
14
  @clipboard_items = model_class.all_from_clipboard(@clipboard)
13
15
  respond_to do |format|
@@ -23,17 +25,11 @@ module Alchemy
23
25
  "action" => params[:remove] ? "cut" : "copy"
24
26
  }
25
27
  end
26
- respond_to do |format|
27
- format.js
28
- end
29
28
  end
30
29
 
31
30
  def remove
32
31
  @item = model_class.find(remarkable_params[:remarkable_id])
33
32
  @clipboard.delete_if { |item| item["id"] == remarkable_params[:remarkable_id] }
34
- respond_to do |format|
35
- format.js
36
- end
37
33
  end
38
34
 
39
35
  def clear
@@ -35,7 +35,7 @@ module Alchemy
35
35
  else
36
36
  Element.new(create_element_params)
37
37
  end
38
- if @page.definition["insert_elements_at"] == "top"
38
+ if @page.definition.insert_elements_at == "top"
39
39
  @insert_at_top = true
40
40
  @element.position = 1
41
41
  end
@@ -14,7 +14,7 @@ module Alchemy
14
14
  def new
15
15
  @language = Language.new(
16
16
  site: @current_site,
17
- page_layout: Config.get(:default_language)["page_layout"]
17
+ page_layout: Alchemy.config.default_language.page_layout
18
18
  )
19
19
  end
20
20
 
@@ -37,6 +37,16 @@ module Alchemy
37
37
  if: :run_on_page_layout_callbacks?,
38
38
  only: [:show]
39
39
 
40
+ add_alchemy_filter :by_page_layout, type: :select, options: ->(_q) do
41
+ PageDefinition.all.map { |p| [Alchemy.t(p[:name], scope: "page_layout_names"), p[:name]] }
42
+ end
43
+
44
+ add_alchemy_filter :updated_at_gteq, type: :datepicker
45
+ add_alchemy_filter :updated_at_lteq, type: :datepicker
46
+ add_alchemy_filter :published, type: :checkbox
47
+ add_alchemy_filter :not_public, type: :checkbox
48
+ add_alchemy_filter :restricted, type: :checkbox
49
+
40
50
  def index
41
51
  @query = @current_language.pages.contentpages.ransack(search_filter_params[:q])
42
52
 
@@ -48,10 +58,6 @@ module Alchemy
48
58
  items = items.tagged_with(search_filter_params[:tagged_with])
49
59
  end
50
60
 
51
- if search_filter_params[:filter].present?
52
- items = apply_filters(items)
53
- end
54
-
55
61
  items = items.page(params[:page] || 1).per(items_per_page)
56
62
  @pages = items
57
63
  end
@@ -69,7 +75,7 @@ module Alchemy
69
75
  Current.preview_page = @page
70
76
  # Setting the locale to pages language, so the page content has it's correct translations.
71
77
  ::I18n.locale = @page.language.locale
72
- render(layout: Alchemy::Config.get(:admin_page_preview_layout) || "application")
78
+ render(layout: Alchemy.config.admin_page_preview_layout || "application")
73
79
  end
74
80
 
75
81
  def info
@@ -155,16 +161,10 @@ module Alchemy
155
161
  flash[:warning] = @page.errors.full_messages.to_sentence
156
162
  end
157
163
 
158
- respond_to do |format|
159
- format.js do
160
- @redirect_url = if @page.layoutpage?
161
- alchemy.admin_layoutpages_path
162
- else
163
- alchemy.admin_pages_path
164
- end
165
-
166
- render :redirect
167
- end
164
+ if @page.layoutpage?
165
+ redirect_to alchemy.admin_layoutpages_path
166
+ else
167
+ redirect_to alchemy.admin_pages_path
168
168
  end
169
169
  end
170
170
 
@@ -21,9 +21,17 @@ module Alchemy
21
21
  @picture = Picture.find(params[:id])
22
22
  end
23
23
 
24
+ add_alchemy_filter :by_file_format, type: :select, options: ->(query) do
25
+ Alchemy::Picture.file_formats(query.result)
26
+ end
27
+ add_alchemy_filter :recent, type: :checkbox
28
+ add_alchemy_filter :last_upload, type: :checkbox
29
+ add_alchemy_filter :without_tag, type: :checkbox
30
+ add_alchemy_filter :deletable, type: :checkbox
31
+
24
32
  def index
25
33
  @query = Picture.ransack(search_filter_params[:q])
26
- @pictures = filtered_pictures.includes(:thumbs)
34
+ @pictures = Alchemy.storage_adapter.preloaded_pictures(filtered_pictures)
27
35
 
28
36
  if in_overlay?
29
37
  archive_overlay
@@ -139,10 +147,6 @@ module Alchemy
139
147
  pictures = pictures.tagged_with(params[:tagged_with])
140
148
  end
141
149
 
142
- if search_filter_params[:filter].present?
143
- pictures = apply_filters(pictures)
144
- end
145
-
146
150
  pictures = pictures.page(params[:page] || 1).per(items_per_page)
147
151
 
148
152
  pictures.order(:name)
@@ -1,21 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "csv"
4
- require "alchemy/resource"
5
- require "alchemy/resources_helper"
6
- require "alchemy/resource_filter"
7
4
 
8
5
  module Alchemy
9
6
  module Admin
10
7
  class ResourcesController < Alchemy::Admin::BaseController
11
- COMMON_SEARCH_FILTER_EXCLUDES = %i[id utf8 _method _ format].freeze
12
-
13
8
  include Alchemy::ResourcesHelper
9
+ extend Alchemy::Admin::ResourceName
10
+ include Alchemy::Admin::ResourceFilter
14
11
 
15
12
  helper Alchemy::ResourcesHelper, TagsHelper
16
- helper_method :resource_handler, :search_filter_params,
17
- :items_per_page, :items_per_page_options, :resource_has_filters,
18
- :resource_filters_for_select
13
+ helper_method :resource_handler, :items_per_page, :items_per_page_options
19
14
 
20
15
  before_action :load_resource,
21
16
  only: %i[show edit update destroy]
@@ -29,21 +24,20 @@ module Alchemy
29
24
 
30
25
  items = items.includes(*resource_relations_names) if contains_relations?
31
26
  items = items.tagged_with(search_filter_params[:tagged_with]) if search_filter_params[:tagged_with].present?
32
- items = apply_filters(items) if search_filter_params[:filter].present?
33
27
 
34
28
  respond_to do |format|
35
29
  format.html do
36
30
  items = items.page(params[:page] || 1).per(items_per_page)
37
- instance_variable_set(:"@#{resource_handler.resources_name}", items)
31
+ instance_variable_set(:"@#{resources_name}", items)
38
32
  end
39
33
  format.csv do
40
- instance_variable_set(:"@#{resource_handler.resources_name}", items)
34
+ instance_variable_set(:"@#{resources_name}", items)
41
35
  end
42
36
  end
43
37
  end
44
38
 
45
39
  def new
46
- instance_variable_set(:"@#{resource_handler.resource_name}", resource_handler.model.new)
40
+ instance_variable_set(:"@#{resource_name}", resource_handler.model.new)
47
41
  end
48
42
 
49
43
  def show
@@ -54,7 +48,7 @@ module Alchemy
54
48
  end
55
49
 
56
50
  def create
57
- instance_variable_set(:"@#{resource_handler.resource_name}", resource_handler.model.new(resource_params))
51
+ instance_variable_set(:"@#{resource_name}", resource_handler.model.new(resource_params))
58
52
  resource_instance_variable.save
59
53
  render_errors_or_redirect(
60
54
  resource_instance_variable,
@@ -85,62 +79,8 @@ module Alchemy
85
79
  @_resource_handler ||= Alchemy::Resource.new(controller_path, alchemy_module)
86
80
  end
87
81
 
88
- def resource_has_filters
89
- resource_model.respond_to?(:alchemy_resource_filters)
90
- end
91
-
92
- def resource_has_deprecated_filters
93
- resource_model.alchemy_resource_filters.any? { |f| !f.is_a?(Hash) }
94
- end
95
-
96
- def resource_filters
97
- return unless resource_has_filters
98
-
99
- @_resource_filters ||= resource_model.alchemy_resource_filters
100
- end
101
-
102
- def resource_filters_for_select
103
- resource_filters.map do |filter|
104
- ResourceFilter.new(filter, resource_handler.resource_name)
105
- end
106
- end
107
-
108
82
  protected
109
83
 
110
- def apply_filters(items)
111
- sanitize_filter_params!
112
-
113
- search_filter_params[:filter].each do |filter|
114
- if argument_scope_filter?(filter)
115
- items = items.public_send(filter[0], filter[1])
116
- elsif simple_scope_filter?(filter)
117
- items = items.public_send(filter[1])
118
- else
119
- raise "Can't apply filter #{filter[0]}. Either the name or the values must be defined as class methods / scopes on the model."
120
- end
121
- end
122
-
123
- items
124
- end
125
-
126
- def simple_scope_filter?(filter)
127
- resource_model.respond_to?(filter[1])
128
- end
129
-
130
- def argument_scope_filter?(filter)
131
- resource_model.respond_to?(filter[0])
132
- end
133
-
134
- def sanitize_filter_params!
135
- search_filter_params[:filter].reject! do |_, v|
136
- eligible_resource_filter_values.exclude?(v)
137
- end
138
- end
139
-
140
- def eligible_resource_filter_values
141
- resource_filters.map(&:values).flatten!.map!(&:to_s)
142
- end
143
-
144
84
  # Returns a translated +flash[:notice]+ for current controller action.
145
85
  def flash_notice_for_resource_action(action = action_name)
146
86
  return if resource_instance_variable.errors.any?
@@ -174,61 +114,31 @@ module Alchemy
174
114
  end
175
115
 
176
116
  def load_resource
177
- instance_variable_set(:"@#{resource_handler.resource_name}", resource_handler.model.find(params[:id]))
117
+ instance_variable_set(:"@#{resource_name}", resource_handler.model.find(params[:id]))
178
118
  end
179
119
 
180
120
  def authorize_resource
181
121
  authorize!(action_name.to_sym, resource_instance_variable || resource_handler.model)
182
122
  end
183
123
 
184
- # Permits all parameters as default!
185
- #
186
- # THIS IS INSECURE! Although only signed in admin users can send requests anyway, but we should change this.
124
+ # Permits all editable resource attributes as default.
187
125
  #
188
- # Please define this method in your inheriting controller and set the parameters you want to permit.
189
- #
190
- # TODO: Hook this into authorization provider.
126
+ # Define this method in your inheriting controller if you want to permit additional attributes.
191
127
  #
128
+ # @see Alchemy::Resource#editable_attributes
192
129
  def resource_params
193
- params.require(resource_handler.namespaced_resource_name).permit!
194
- end
195
-
196
- def search_filter_params
197
- @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES).permit(*common_search_filter_includes).to_h
198
- end
199
-
200
- def common_search_filter_includes
201
- search_filters = [
202
- {
203
- q: [:s] + permitted_ransack_search_fields
204
- },
205
- :tagged_with,
206
- :page,
207
- :per_page
208
- ]
209
-
210
- if resource_has_filters
211
- search_filters << {
212
- filter: resource_filters.map { |f| f[:name] }
213
- }
214
- end
215
-
216
- search_filters
217
- end
218
-
219
- def permitted_ransack_search_fields
220
- [
221
- resource_handler.search_field_name
222
- ]
130
+ params.require(resource_handler.namespaced_resource_name).permit(
131
+ resource_handler.editable_attributes.map { _1[:name] }
132
+ )
223
133
  end
224
134
 
225
135
  def items_per_page
226
136
  cookies[:alchemy_items_per_page] =
227
- params[:per_page] || cookies[:alchemy_items_per_page] || Alchemy::Config.get(:items_per_page)
137
+ params[:per_page] || cookies[:alchemy_items_per_page] || Alchemy.config.items_per_page
228
138
  end
229
139
 
230
140
  def items_per_page_options
231
- per_page = Alchemy::Config.get(:items_per_page)
141
+ per_page = Alchemy.config.items_per_page
232
142
  [per_page, per_page * 2, per_page * 4]
233
143
  end
234
144
 
@@ -2,37 +2,66 @@
2
2
 
3
3
  module Alchemy
4
4
  class AttachmentsController < BaseController
5
+ if Alchemy.storage_adapter.active_storage?
6
+ include ActiveStorage::Streaming
7
+ end
8
+
5
9
  before_action :load_attachment
6
10
  authorize_resource class: Alchemy::Attachment
7
11
 
12
+ self.etag_with_template_digest = false
13
+
8
14
  # sends file inline. i.e. for viewing pdfs/movies in browser
9
15
  def show
10
- response.headers["Content-Length"] = @attachment.file.size.to_s
11
- send_file(
12
- @attachment.file.path,
13
- {
14
- filename: @attachment.file_name,
15
- type: @attachment.file_mime_type,
16
- disposition: "inline"
17
- }
18
- )
16
+ if Alchemy.storage_adapter.dragonfly?
17
+ send_attachment_file(disposition: :inline)
18
+ else
19
+ send_attachment_blob(disposition: :inline)
20
+ end
19
21
  end
20
22
 
21
23
  # sends file as attachment. aka download
22
24
  def download
25
+ if Alchemy.storage_adapter.dragonfly?
26
+ send_attachment_file(disposition: :attachment)
27
+ else
28
+ send_attachment_blob(disposition: :attachment)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def load_attachment
35
+ @attachment = Attachment.find(params[:id])
36
+ end
37
+
38
+ def send_attachment_file(disposition: :inline)
23
39
  response.headers["Content-Length"] = @attachment.file.size.to_s
40
+
24
41
  send_file(
25
- @attachment.file.path, {
42
+ @attachment.file.path,
43
+ {
26
44
  filename: @attachment.file_name,
27
- type: @attachment.file_mime_type
45
+ type: @attachment.file_mime_type,
46
+ disposition: disposition
28
47
  }
29
48
  )
30
49
  end
31
50
 
32
- private
51
+ def send_attachment_blob(disposition: :inline)
52
+ @blob = @attachment.file.blob
33
53
 
34
- def load_attachment
35
- @attachment = Attachment.find(params[:id])
54
+ if request.headers["Range"].present?
55
+ send_blob_byte_range_data @blob, request.headers["Range"], disposition: disposition
56
+ else
57
+ http_cache_forever public: true do
58
+ response.headers["Accept-Ranges"] = "bytes"
59
+ send_blob_stream @blob, disposition: disposition
60
+ # Rails ActionController::Live removes the Content-Length header,
61
+ # but browsers need that to be able to show a progress bar during download.
62
+ response.headers["Content-Length"] = @blob.byte_size.to_s
63
+ end
64
+ end
36
65
  end
37
66
  end
38
67
  end
@@ -74,7 +74,7 @@ module Alchemy
74
74
  private
75
75
 
76
76
  def mailer_config
77
- Alchemy::Config.get(:mailer)
77
+ Alchemy.config.mailer
78
78
  end
79
79
 
80
80
  def mail_to
@@ -106,7 +106,12 @@ module Alchemy
106
106
  def load_index_page
107
107
  @page ||= Language.current_root_page
108
108
  Current.page = @page
109
- render template: "alchemy/welcome", layout: false if signup_required?
109
+
110
+ if signup_required? && @page.nil?
111
+ render template: "alchemy/welcome", layout: false
112
+ elsif !@page&.public?
113
+ render template: "alchemy/no_index", layout: false
114
+ end
110
115
  end
111
116
 
112
117
  # == Loads page by urlname
@@ -192,7 +197,7 @@ module Alchemy
192
197
 
193
198
  def signup_required?
194
199
  if Alchemy.user_class.respond_to?(:admins)
195
- Alchemy.user_class.admins.empty? && @page.nil?
200
+ Alchemy.user_class.admins.empty?
196
201
  end
197
202
  end
198
203
 
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module ResourceFilter
6
+ extend ActiveSupport::Concern
7
+ COMMON_SEARCH_FILTER_EXCLUDES = %i[id utf8 _method _ format].freeze
8
+
9
+ included do
10
+ prepend_before_action :initialize_alchemy_filters
11
+ helper_method :alchemy_filters, :search_form_name, :applied_filters, :search_filter_params, :resource_has_filters
12
+ end
13
+
14
+ class_methods do
15
+ # Adds a filter to the resource.
16
+ # @param name [String, Symbol] The name of the filter.
17
+ # @param type [Symbol] The type of the filter. Can currently be `:select` or `:checkbox`.
18
+ # @param args [Hash] Additional arguments for the filter.
19
+ # @example
20
+ # add_alchemy_filter :by_location, type: :select, options: ->(query) { Location.pluck(:name, :id) }
21
+ # add_alchemy_filter :future, type: :checkbox
22
+ # add_alchemy_filter :by_timeframe, type: :select, options: ["today", "tomorrow"]
23
+ def add_alchemy_filter(name, type:, **args)
24
+ alchemy_filters << "Alchemy::Admin::Filters::#{type.to_s.camelize}".constantize.new(
25
+ name:,
26
+ resource_name:,
27
+ **args
28
+ )
29
+ end
30
+
31
+ def alchemy_filters
32
+ @alchemy_filters ||= []
33
+ end
34
+ end
35
+ delegate :alchemy_filters, to: :class
36
+
37
+ private
38
+
39
+ def initialize_alchemy_filters
40
+ return if alchemy_filters.any?
41
+ return unless resource_model.respond_to?(:alchemy_resource_filters)
42
+ Alchemy::Deprecation.warn(
43
+ "The `alchemy_resource_filters` method is deprecated and will be removed in Alchemy 8.1. " \
44
+ "Please use `add_alchemy_filter` in your controller instead. Make sure to safelist the scopes " \
45
+ "you want to use in the `ransackable_scopes` method of your model."
46
+ )
47
+ resource_model.alchemy_resource_filters.each do |filter_config|
48
+ if resource_model.respond_to?(filter_config[:name])
49
+ self.class.add_alchemy_filter filter_config[:name], type: :select, options: filter_config[:values]
50
+ else
51
+ filter_config[:values].each do |scope|
52
+ self.class.add_alchemy_filter scope, type: :checkbox
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ def search_filter_params
59
+ @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES).permit(*common_search_filter_includes).to_h
60
+ end
61
+
62
+ def common_search_filter_includes
63
+ [
64
+ {
65
+ q: [:s] + permitted_ransack_search_fields
66
+ },
67
+ :tagged_with,
68
+ :page,
69
+ :per_page
70
+ ]
71
+ end
72
+
73
+ def permitted_ransack_search_fields
74
+ [
75
+ resource_handler.search_field_name
76
+ ] + alchemy_filters.map(&:name)
77
+ end
78
+
79
+ def resource_has_filters
80
+ alchemy_filters.any?
81
+ end
82
+
83
+ def applied_filters
84
+ return [] unless search_filter_params[:q]
85
+
86
+ alchemy_filters.select do |alchemy_filter|
87
+ search_filter_params[:q][alchemy_filter.name].present?
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end