alchemy_cms 7.4.10 → 8.0.0.b

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 (425) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -5
  3. data/app/assets/builds/alchemy/admin/page-select.css +1 -1
  4. data/app/assets/builds/alchemy/admin/print.css +1 -1
  5. data/app/assets/builds/alchemy/admin.css +2 -2
  6. data/app/assets/builds/alchemy/dark-theme.css +1 -0
  7. data/app/assets/builds/alchemy/light-theme.css +1 -0
  8. data/app/assets/builds/alchemy/theme.css +1 -0
  9. data/app/assets/builds/alchemy/welcome.css +1 -1
  10. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  11. data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -0
  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/builds/tinymce/skins/ui/alchemy-dark/content.min.css +1 -0
  15. data/app/assets/builds/tinymce/skins/ui/alchemy-dark/skin.min.css +1 -0
  16. data/app/assets/config/alchemy_manifest.js +0 -2
  17. data/app/assets/images/alchemy/element_icons/default.svg +1 -0
  18. data/app/assets/images/alchemy/icons-sprite.svg +1 -0
  19. data/app/components/alchemy/admin/element_select.rb +39 -0
  20. data/app/components/alchemy/admin/resource/applied_filter.rb +29 -0
  21. data/app/components/alchemy/admin/resource/checkbox_filter.rb +36 -0
  22. data/app/components/alchemy/admin/resource/datepicker_filter.rb +42 -0
  23. data/app/components/alchemy/admin/resource/select_filter.rb +43 -0
  24. data/app/components/alchemy/admin/toolbar_button.rb +5 -2
  25. data/app/components/alchemy/ingredients/number_view.rb +18 -0
  26. data/app/controllers/alchemy/admin/attachments_controller.rb +10 -15
  27. data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -6
  28. data/app/controllers/alchemy/admin/elements_controller.rb +3 -1
  29. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  30. data/app/controllers/alchemy/admin/pages_controller.rb +15 -15
  31. data/app/controllers/alchemy/admin/pictures_controller.rb +27 -37
  32. data/app/controllers/alchemy/admin/resources_controller.rb +16 -106
  33. data/app/controllers/alchemy/attachments_controller.rb +43 -14
  34. data/app/controllers/alchemy/messages_controller.rb +1 -1
  35. data/app/controllers/alchemy/pages_controller.rb +26 -4
  36. data/app/controllers/concerns/alchemy/admin/resource_filter.rb +93 -0
  37. data/app/decorators/alchemy/element_editor.rb +5 -48
  38. data/app/decorators/alchemy/ingredient_editor.rb +3 -53
  39. data/app/helpers/alchemy/admin/attachments_helper.rb +5 -5
  40. data/app/helpers/alchemy/admin/base_helper.rb +14 -84
  41. data/app/helpers/alchemy/admin/pages_helper.rb +1 -1
  42. data/app/helpers/alchemy/base_helper.rb +0 -30
  43. data/app/helpers/alchemy/elements_block_helper.rb +0 -14
  44. data/app/helpers/alchemy/pages_helper.rb +1 -1
  45. data/{lib → app/helpers}/alchemy/resources_helper.rb +5 -45
  46. data/app/javascript/alchemy_admin/components/action.js +2 -0
  47. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +3 -3
  48. data/app/javascript/alchemy_admin/components/auto_submit.js +20 -0
  49. data/app/javascript/alchemy_admin/components/datepicker.js +18 -7
  50. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +8 -7
  51. data/app/javascript/alchemy_admin/components/element_editor.js +25 -15
  52. data/app/javascript/alchemy_admin/components/element_select.js +43 -0
  53. data/app/javascript/alchemy_admin/components/index.js +3 -0
  54. data/app/javascript/alchemy_admin/components/link_buttons.js +6 -2
  55. data/app/javascript/alchemy_admin/components/remote_select.js +9 -2
  56. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +5 -1
  57. data/app/javascript/alchemy_admin/components/tinymce.js +93 -14
  58. data/app/javascript/alchemy_admin/components/update_check.js +42 -0
  59. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +15 -8
  60. data/app/javascript/alchemy_admin/components/uploader/progress.js +12 -6
  61. data/app/javascript/alchemy_admin/components/uploader.js +4 -2
  62. data/app/javascript/alchemy_admin/confirm_dialog.js +27 -57
  63. data/app/javascript/alchemy_admin/dialog.js +1 -1
  64. data/app/javascript/alchemy_admin/dirty.js +3 -2
  65. data/app/javascript/alchemy_admin/file_editors.js +1 -1
  66. data/app/javascript/alchemy_admin/i18n.js +15 -16
  67. data/app/javascript/alchemy_admin/image_loader.js +4 -2
  68. data/app/javascript/alchemy_admin/initializer.js +1 -49
  69. data/app/javascript/alchemy_admin/picture_editors.js +7 -4
  70. data/app/javascript/alchemy_admin/picture_selector.js +4 -4
  71. data/app/javascript/alchemy_admin/utils/ajax.js +51 -44
  72. data/app/javascript/alchemy_admin.js +3 -8
  73. data/app/jobs/alchemy/delete_picture_job.rb +12 -0
  74. data/app/models/alchemy/admin/filters/base.rb +38 -0
  75. data/app/models/alchemy/admin/filters/checkbox.rb +24 -0
  76. data/app/models/alchemy/admin/filters/datepicker.rb +53 -0
  77. data/app/models/alchemy/admin/filters/select.rb +70 -0
  78. data/app/models/alchemy/admin/resource_name.rb +27 -0
  79. data/app/models/alchemy/attachment.rb +49 -44
  80. data/app/models/alchemy/base_record.rb +2 -0
  81. data/app/models/alchemy/element/definitions.rb +1 -1
  82. data/app/models/alchemy/element/element_ingredients.rb +6 -6
  83. data/app/models/alchemy/element/presenters.rb +3 -12
  84. data/app/models/alchemy/element.rb +10 -27
  85. data/app/models/alchemy/element_definition.rb +190 -0
  86. data/app/models/alchemy/ingredient.rb +10 -43
  87. data/app/models/alchemy/ingredient_definition.rb +134 -0
  88. data/app/models/alchemy/ingredient_validator.rb +7 -3
  89. data/app/models/alchemy/ingredients/number.rb +19 -0
  90. data/app/models/alchemy/language.rb +2 -21
  91. data/app/models/alchemy/message.rb +3 -7
  92. data/app/models/alchemy/node.rb +1 -1
  93. data/app/models/alchemy/page/{page_layouts.rb → definitions.rb} +12 -19
  94. data/app/models/alchemy/page/fixed_attributes.rb +1 -1
  95. data/app/models/alchemy/page/page_elements.rb +13 -14
  96. data/app/models/alchemy/page/page_naming.rb +3 -11
  97. data/app/models/alchemy/page/page_natures.rb +20 -15
  98. data/app/models/alchemy/page/page_scopes.rb +1 -1
  99. data/app/models/alchemy/page.rb +12 -39
  100. data/app/models/alchemy/page_definition.rb +115 -0
  101. data/app/models/alchemy/picture.rb +71 -99
  102. data/app/models/alchemy/picture_variant.rb +115 -5
  103. data/{lib → app/models}/alchemy/resource.rb +4 -18
  104. data/{lib → app/models}/alchemy/searchable_resource.rb +15 -0
  105. data/app/models/alchemy/site/layout.rb +5 -5
  106. data/app/models/alchemy/site.rb +1 -21
  107. data/app/models/alchemy/storage_adapter/active_storage/attachment_url.rb +41 -0
  108. data/app/models/alchemy/storage_adapter/active_storage/picture_url.rb +55 -0
  109. data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +40 -0
  110. data/app/models/alchemy/storage_adapter/active_storage.rb +173 -0
  111. data/app/models/alchemy/{attachment/url.rb → storage_adapter/dragonfly/attachment_url.rb} +12 -12
  112. data/app/models/alchemy/storage_adapter/dragonfly/picture_url.rb +75 -0
  113. data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
  114. data/app/models/alchemy/storage_adapter/dragonfly.rb +205 -0
  115. data/app/models/alchemy/storage_adapter.rb +74 -0
  116. data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
  117. data/app/models/concerns/alchemy/relatable_resource.rb +28 -0
  118. data/app/serializers/alchemy/element_serializer.rb +0 -1
  119. data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -0
  120. data/app/stylesheets/alchemy/_custom-properties.scss +162 -0
  121. data/app/stylesheets/alchemy/_defaults.scss +3 -0
  122. data/app/stylesheets/alchemy/_extends.scss +69 -0
  123. data/app/{assets/stylesheets → stylesheets}/alchemy/_mixins.scss +40 -68
  124. data/app/stylesheets/alchemy/_themes.scss +540 -0
  125. data/app/stylesheets/alchemy/_variables.scss +5 -0
  126. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +45 -42
  127. data/app/stylesheets/alchemy/admin/attachments.scss +17 -0
  128. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +20 -15
  129. data/app/stylesheets/alchemy/admin/buttons.scss +135 -0
  130. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/clipboard.scss +2 -2
  131. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dashboard.scss +13 -16
  132. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dialogs.scss +33 -16
  133. data/app/stylesheets/alchemy/admin/element-select.scss +11 -0
  134. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +239 -133
  135. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
  136. data/app/stylesheets/alchemy/admin/filters.scss +57 -0
  137. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +54 -76
  138. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +10 -11
  139. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +28 -21
  140. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -18
  141. data/app/stylesheets/alchemy/admin/hints.scss +5 -0
  142. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +1 -1
  143. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +21 -61
  144. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/images.scss +1 -1
  145. data/app/stylesheets/alchemy/admin/labels.scss +5 -0
  146. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/lists.scss +3 -3
  147. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/navigation.scss +55 -59
  148. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/node-select.scss +1 -10
  149. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
  150. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +20 -19
  151. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +18 -2
  152. data/app/stylesheets/alchemy/admin/pagination.scss +137 -0
  153. data/app/stylesheets/alchemy/admin/preview_window.scss +46 -0
  154. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
  155. data/app/stylesheets/alchemy/admin/resource_info.scss +148 -0
  156. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +10 -7
  157. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +85 -46
  158. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +37 -68
  159. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +39 -34
  160. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +9 -7
  161. data/app/stylesheets/alchemy/admin/tags.scss +143 -0
  162. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +6 -6
  163. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +3 -6
  164. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
  165. data/app/stylesheets/alchemy/admin.scss +44 -0
  166. data/app/stylesheets/alchemy/dark-theme.scss +5 -0
  167. data/app/stylesheets/alchemy/light-theme.scss +6 -0
  168. data/app/stylesheets/alchemy/theme.scss +13 -0
  169. data/app/stylesheets/alchemy/welcome.scss +75 -0
  170. data/app/stylesheets/tinymce/skins/content/alchemy/content.scss +69 -0
  171. data/app/{assets/stylesheets/tinymce/skins/content/alchemy → stylesheets/tinymce/skins/content/alchemy-dark}/content.scss +12 -12
  172. data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
  173. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +158 -176
  174. data/app/stylesheets/tinymce/skins/ui/alchemy-dark/content.scss +1 -0
  175. data/app/stylesheets/tinymce/skins/ui/alchemy-dark/skin.scss +3784 -0
  176. data/app/views/alchemy/admin/attachments/_files_list.html.erb +22 -12
  177. data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
  178. data/app/views/alchemy/admin/attachments/assign.js.erb +4 -3
  179. data/app/views/alchemy/admin/attachments/show.html.erb +55 -43
  180. data/app/views/alchemy/admin/{elements/_clipboard_button.html.erb → clipboard/_button.html.erb} +3 -5
  181. data/app/views/alchemy/admin/clipboard/_update_nested_element_button.turbo_stream.erb +11 -0
  182. data/app/views/alchemy/admin/clipboard/clear.turbo_stream.erb +4 -0
  183. data/app/views/alchemy/admin/clipboard/index.html.erb +15 -13
  184. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +18 -0
  185. data/app/views/alchemy/admin/clipboard/remove.turbo_stream.erb +9 -0
  186. data/app/views/alchemy/admin/crop.html.erb +1 -1
  187. data/app/views/alchemy/admin/dashboard/info.html.erb +17 -31
  188. data/app/views/alchemy/admin/elements/_element.html.erb +4 -8
  189. data/app/views/alchemy/admin/elements/_form.html.erb +9 -9
  190. data/app/views/alchemy/admin/elements/_header.html.erb +5 -1
  191. data/app/views/alchemy/admin/elements/_toolbar.html.erb +4 -6
  192. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +2 -1
  193. data/app/views/alchemy/admin/elements/index.html.erb +2 -2
  194. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +3 -16
  195. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +0 -9
  196. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  197. data/app/views/alchemy/admin/languages/_table.html.erb +1 -1
  198. data/app/views/alchemy/admin/languages/index.html.erb +5 -2
  199. data/app/views/alchemy/admin/layoutpages/index.html.erb +1 -12
  200. data/app/views/alchemy/admin/pages/_form.html.erb +2 -2
  201. data/app/views/alchemy/admin/pages/_page.html.erb +2 -3
  202. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -15
  203. data/app/views/alchemy/admin/pages/index.html.erb +1 -1
  204. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +9 -12
  205. data/app/views/alchemy/admin/partials/_search_form.html.erb +4 -9
  206. data/app/views/alchemy/admin/pictures/_archive.html.erb +16 -29
  207. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +3 -7
  208. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -1
  209. data/app/views/alchemy/admin/pictures/_form.html.erb +1 -1
  210. data/app/views/alchemy/admin/pictures/_infos.html.erb +21 -52
  211. data/app/views/alchemy/admin/pictures/_library_sidebar.html.erb +7 -0
  212. data/app/views/alchemy/admin/pictures/_picture.html.erb +14 -20
  213. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +20 -16
  214. data/app/views/alchemy/admin/pictures/_sorting_select.html.erb +13 -0
  215. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -1
  216. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +1 -6
  217. data/app/views/alchemy/admin/pictures/index.html.erb +5 -19
  218. data/app/views/alchemy/admin/pictures/show.html.erb +10 -5
  219. data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
  220. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +5 -25
  221. data/app/views/alchemy/admin/resources/_pagination.html.erb +6 -0
  222. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +4 -2
  223. data/app/views/alchemy/admin/resources/_resource_table.html.erb +1 -1
  224. data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +36 -0
  225. data/app/views/alchemy/admin/resources/_table_header.html.erb +1 -15
  226. data/app/views/alchemy/admin/sites/index.html.erb +5 -1
  227. data/app/views/alchemy/admin/styleguide/index.html.erb +118 -58
  228. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  229. data/app/views/alchemy/admin/tinymce/_setup.html.erb +7 -7
  230. data/app/{javascript/alchemy_admin/locales/en.js → views/alchemy/admin/translations/_en.js} +5 -2
  231. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
  232. data/app/views/alchemy/admin/uploader/_setup.html.erb +4 -4
  233. data/app/views/alchemy/base/error_notice.html.erb +1 -1
  234. data/app/views/alchemy/ingredients/_number_editor.html.erb +24 -0
  235. data/app/views/alchemy/no_index.html.erb +31 -0
  236. data/app/views/alchemy/welcome.html.erb +12 -10
  237. data/app/views/kaminari/alchemy/_first_page.html.erb +5 -3
  238. data/app/views/kaminari/alchemy/_last_page.html.erb +5 -3
  239. data/app/views/kaminari/alchemy/_next_page.html.erb +5 -3
  240. data/app/views/kaminari/alchemy/_paginator.html.erb +18 -13
  241. data/app/views/kaminari/alchemy/_prev_page.html.erb +5 -3
  242. data/app/views/layouts/alchemy/admin.html.erb +25 -24
  243. data/config/alchemy/config.yml +3 -2
  244. data/config/initializers/dragonfly.rb +0 -1
  245. data/config/initializers/mime_types.rb +1 -0
  246. data/config/locales/alchemy.en.yml +57 -21
  247. data/config/routes.rb +0 -2
  248. data/db/migrate/20250905140323_add_created_at_index_to_pictures_and_attachments.rb +14 -0
  249. data/lib/alchemy/admin/preview_url.rb +4 -5
  250. data/lib/alchemy/cache_digests/template_tracker.rb +6 -9
  251. data/lib/alchemy/config_missing.rb +14 -0
  252. data/lib/alchemy/configuration/base_option.rb +24 -0
  253. data/lib/alchemy/configuration/boolean_option.rb +16 -0
  254. data/lib/alchemy/configuration/class_option.rb +15 -0
  255. data/lib/alchemy/configuration/class_set_option.rb +46 -0
  256. data/lib/alchemy/configuration/integer_list_option.rb +13 -0
  257. data/lib/alchemy/configuration/integer_option.rb +12 -0
  258. data/lib/alchemy/configuration/list_option.rb +22 -0
  259. data/lib/alchemy/configuration/regexp_option.rb +11 -0
  260. data/lib/alchemy/configuration/string_list_option.rb +13 -0
  261. data/lib/alchemy/configuration/string_option.rb +11 -0
  262. data/lib/alchemy/configuration.rb +115 -0
  263. data/lib/alchemy/configuration_methods.rb +3 -1
  264. data/lib/alchemy/configurations/default_language.rb +12 -0
  265. data/lib/alchemy/configurations/default_site.rb +10 -0
  266. data/lib/alchemy/configurations/format_matchers.rb +11 -0
  267. data/lib/alchemy/configurations/mailer.rb +16 -0
  268. data/lib/alchemy/configurations/main.rb +223 -0
  269. data/lib/alchemy/configurations/page_cache.rb +19 -0
  270. data/lib/alchemy/configurations/preview.rb +32 -0
  271. data/lib/alchemy/configurations/sitemap.rb +10 -0
  272. data/lib/alchemy/configurations/uploader.rb +34 -0
  273. data/lib/alchemy/engine.rb +81 -24
  274. data/lib/alchemy/hints.rb +3 -7
  275. data/lib/alchemy/install/tasks.rb +0 -12
  276. data/lib/alchemy/on_page_layout.rb +2 -2
  277. data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
  278. data/lib/alchemy/seeder.rb +2 -2
  279. data/lib/alchemy/tasks/tidy.rb +18 -0
  280. data/lib/alchemy/tasks/usage.rb +4 -4
  281. data/lib/alchemy/test_support/config_stubbing.rb +1 -7
  282. data/lib/alchemy/test_support/factories/attachment_factory.rb +13 -2
  283. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  284. data/lib/alchemy/test_support/factories/page_factory.rb +2 -3
  285. data/lib/alchemy/test_support/factories/picture_factory.rb +31 -2
  286. data/lib/alchemy/test_support/factories/site_factory.rb +2 -2
  287. data/lib/alchemy/test_support/having_crop_action_examples.rb +2 -2
  288. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +80 -26
  289. data/lib/alchemy/test_support/relatable_resource_examples.rb +58 -0
  290. data/lib/alchemy/test_support/shared_ingredient_examples.rb +5 -5
  291. data/lib/alchemy/tinymce.rb +0 -1
  292. data/lib/alchemy/upgrader/eight_zero.rb +14 -0
  293. data/lib/alchemy/upgrader.rb +33 -20
  294. data/lib/alchemy/version.rb +1 -1
  295. data/lib/alchemy.rb +185 -172
  296. data/lib/alchemy_cms.rb +1 -7
  297. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +0 -3
  298. data/lib/generators/alchemy/install/files/_article.html.erb +6 -4
  299. data/lib/generators/alchemy/install/files/alchemy.en.yml +22 -3
  300. data/lib/generators/alchemy/install/files/application.html.erb +5 -0
  301. data/lib/generators/alchemy/install/install_generator.rb +25 -23
  302. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +200 -0
  303. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -1
  304. data/lib/generators/alchemy/install/templates/elements.yml.tt +3 -1
  305. data/lib/generators/alchemy/install/templates/menus.yml.tt +1 -1
  306. data/lib/generators/alchemy/install/templates/page_layouts.yml.tt +2 -2
  307. data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -2
  308. data/lib/tasks/alchemy/assets.rake +14 -0
  309. data/lib/tasks/alchemy/tidy.rake +6 -0
  310. data/lib/tasks/alchemy/upgrade.rake +12 -47
  311. data/vendor/assets/stylesheets/tinymce/skins/content/dark/content.min.css +1 -0
  312. data/vendor/assets/stylesheets/tinymce/skins/content/default/content.min.css +1 -0
  313. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide/skin.min.css +1 -0
  314. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide-dark/content.min.css +1 -0
  315. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide-dark/skin.min.css +1 -0
  316. data/vendor/javascript/clipboard.min.js +1 -1
  317. data/vendor/javascript/cropperjs.min.js +1 -1
  318. data/vendor/javascript/handlebars.min.js +3 -3
  319. data/vendor/javascript/jquery.min.js +1 -1
  320. data/vendor/javascript/select2.min.js +3 -3
  321. data/vendor/javascript/shoelace.min.js +92 -76
  322. data/vendor/javascript/sortable.min.js +2 -2
  323. data/vendor/javascript/tinymce.min.js +1 -1
  324. data/vendor/javascript/ungap-custom-elements.min.js +2 -2
  325. metadata +223 -208
  326. data/CHANGELOG.md +0 -2041
  327. data/CODE_OF_CONDUCT.md +0 -13
  328. data/CONTRIBUTING.md +0 -73
  329. data/Gemfile +0 -71
  330. data/Rakefile +0 -102
  331. data/SECURITY.md +0 -13
  332. data/alchemy_cms.gemspec +0 -88
  333. data/app/assets/builds/alchemy/admin/page-select.css.map +0 -1
  334. data/app/assets/builds/alchemy/admin/print.css.map +0 -1
  335. data/app/assets/builds/alchemy/admin.css.map +0 -1
  336. data/app/assets/builds/alchemy/custom-properties.css +0 -1
  337. data/app/assets/builds/alchemy/custom-properties.css.map +0 -1
  338. data/app/assets/builds/alchemy/welcome.css.map +0 -1
  339. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +0 -1
  340. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +0 -1
  341. data/app/assets/javascripts/alchemy/admin.js +0 -10
  342. data/app/assets/stylesheets/alchemy/_defaults.scss +0 -3
  343. data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +0 -45
  344. data/app/assets/stylesheets/alchemy/_deprecation.scss +0 -17
  345. data/app/assets/stylesheets/alchemy/_extends.scss +0 -62
  346. data/app/assets/stylesheets/alchemy/_variables.scss +0 -201
  347. data/app/assets/stylesheets/alchemy/admin/attachments.scss +0 -40
  348. data/app/assets/stylesheets/alchemy/admin/buttons.scss +0 -123
  349. data/app/assets/stylesheets/alchemy/admin/hints.scss +0 -5
  350. data/app/assets/stylesheets/alchemy/admin/labels.scss +0 -3
  351. data/app/assets/stylesheets/alchemy/admin/pagination.scss +0 -92
  352. data/app/assets/stylesheets/alchemy/admin/preview_window.scss +0 -33
  353. data/app/assets/stylesheets/alchemy/admin/resource_info.scss +0 -42
  354. data/app/assets/stylesheets/alchemy/admin/tags.scss +0 -158
  355. data/app/assets/stylesheets/alchemy/admin.scss +0 -42
  356. data/app/assets/stylesheets/alchemy/custom-properties.css +0 -98
  357. data/app/assets/stylesheets/alchemy/welcome.scss +0 -57
  358. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.css +0 -711
  359. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.css +0 -705
  360. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.min.css +0 -7
  361. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.min.css +0 -7
  362. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.css +0 -29
  363. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.min.css +0 -7
  364. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.css +0 -677
  365. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.min.css +0 -7
  366. data/app/controllers/alchemy/elements_controller.rb +0 -32
  367. data/app/helpers/alchemy/admin/elements_helper.rb +0 -25
  368. data/app/models/alchemy/element/dom_id.rb +0 -31
  369. data/app/models/alchemy/picture/calculations.rb +0 -49
  370. data/app/models/alchemy/picture/transformations.rb +0 -115
  371. data/app/models/alchemy/picture/url.rb +0 -54
  372. data/app/views/alchemy/admin/attachments/destroy.js.erb +0 -1
  373. data/app/views/alchemy/admin/clipboard/clear.js.erb +0 -3
  374. data/app/views/alchemy/admin/clipboard/insert.js.erb +0 -29
  375. data/app/views/alchemy/admin/clipboard/remove.js.erb +0 -10
  376. data/app/views/alchemy/admin/resources/_filter.html.erb +0 -12
  377. data/app/views/alchemy/admin/resources/_resource.html.erb +0 -34
  378. data/app/views/alchemy/admin/resources/_table.html.erb +0 -29
  379. data/app/views/alchemy/elements/show.html.erb +0 -1
  380. data/app/views/alchemy/elements/show.js.erb +0 -1
  381. data/app/views/alchemy/ingredients/_audio_view.html.erb +0 -1
  382. data/app/views/alchemy/ingredients/_boolean_view.html.erb +0 -1
  383. data/app/views/alchemy/ingredients/_datetime_view.html.erb +0 -3
  384. data/app/views/alchemy/ingredients/_file_view.html.erb +0 -4
  385. data/app/views/alchemy/ingredients/_headline_view.html.erb +0 -4
  386. data/app/views/alchemy/ingredients/_html_view.html.erb +0 -1
  387. data/app/views/alchemy/ingredients/_link_view.html.erb +0 -4
  388. data/app/views/alchemy/ingredients/_node_view.html.erb +0 -1
  389. data/app/views/alchemy/ingredients/_page_view.html.erb +0 -1
  390. data/app/views/alchemy/ingredients/_picture_view.html.erb +0 -4
  391. data/app/views/alchemy/ingredients/_richtext_view.html.erb +0 -3
  392. data/app/views/alchemy/ingredients/_select_view.html.erb +0 -1
  393. data/app/views/alchemy/ingredients/_text_view.html.erb +0 -4
  394. data/app/views/alchemy/ingredients/_video_view.html.erb +0 -3
  395. data/babel.config.js +0 -12
  396. data/bin/importmap +0 -4
  397. data/bin/rails +0 -9
  398. data/bin/rspec +0 -3
  399. data/bin/setup +0 -30
  400. data/bin/start +0 -17
  401. data/bun.lockb +0 -0
  402. data/bundles/shoelace.js +0 -12
  403. data/bundles/tinymce.js +0 -22
  404. data/config/initializers/assets.rb +0 -4
  405. data/eslint.config.js +0 -17
  406. data/lib/alchemy/config.rb +0 -114
  407. data/lib/alchemy/element_definition.rb +0 -73
  408. data/lib/alchemy/page_layout.rb +0 -73
  409. data/lib/alchemy/resource_filter.rb +0 -40
  410. data/lib/alchemy/upgrader/seven_point_four.rb +0 -26
  411. data/lib/alchemy/upgrader/seven_point_three.rb +0 -52
  412. data/lib/alchemy/upgrader/tasks/.keep +0 -0
  413. data/lib/generators/alchemy/ingredient/templates/view.html.erb +0 -1
  414. data/lib/generators/alchemy/install/files/alchemy_admin.js +0 -1
  415. data/lib/generators/alchemy/install/files/all.js +0 -11
  416. data/lib/generators/alchemy/install/files/article.css +0 -25
  417. data/rollup.config.mjs +0 -108
  418. data/vendor/assets/images/remixicon.symbol.svg +0 -11
  419. /data/app/{assets/stylesheets → stylesheets}/alchemy/_fonts.scss +0 -0
  420. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachment-select.scss +0 -0
  421. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flash.scss +0 -0
  422. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/list_filter.scss +0 -0
  423. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/spinner.scss +0 -0
  424. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/skintool.json +0 -0
  425. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/fonts/tinymce-mobile.woff +0 -0
@@ -1,27 +1,37 @@
1
- function buildPromise(xhr) {
2
- return new Promise((resolve, reject) => {
3
- xhr.onload = () => {
4
- if (xhr.status >= 200 && xhr.status < 400) {
5
- try {
6
- resolve({
7
- data: JSON.parse(xhr.responseText),
8
- status: xhr.status
9
- })
10
- } catch (error) {
11
- reject(error)
12
- }
13
- } else {
14
- try {
15
- reject(JSON.parse(xhr.responseText))
16
- } catch (error) {
17
- reject(error)
18
- }
19
- }
20
- }
21
- xhr.onerror = () => {
22
- reject(new Error("An error occurred during the transaction"))
23
- }
24
- })
1
+ const JSON_CONTENT_TYPE = "application/json"
2
+
3
+ function isGetRequest(method) {
4
+ return method.toLowerCase() === "get"
5
+ }
6
+
7
+ function prepareURL(path, data, method) {
8
+ const url = new URL(window.location.origin + path)
9
+
10
+ if (data && isGetRequest(method)) {
11
+ url.search = new URLSearchParams(data).toString()
12
+ }
13
+
14
+ return url.toString()
15
+ }
16
+
17
+ function prepareHeaders(accept) {
18
+ return {
19
+ "Content-Type": "application/json; charset=utf-8",
20
+ Accept: accept,
21
+ "X-Requested-With": "XMLHttpRequest",
22
+ "X-CSRF-Token": getToken()
23
+ }
24
+ }
25
+
26
+ function prepareOptions(method, data, accept) {
27
+ const headers = prepareHeaders(accept)
28
+ const options = { method, headers }
29
+
30
+ if (data && !isGetRequest(method)) {
31
+ options.body = JSON.stringify(data)
32
+ }
33
+
34
+ return options
25
35
  }
26
36
 
27
37
  export function getToken() {
@@ -37,30 +47,27 @@ export function patch(url, data) {
37
47
  return ajax("PATCH", url, data)
38
48
  }
39
49
 
40
- export function post(url, data, accept = "application/json") {
50
+ export function post(url, data, accept = JSON_CONTENT_TYPE) {
41
51
  return ajax("POST", url, data, accept)
42
52
  }
43
53
 
44
- export default function ajax(method, path, data, accept = "application/json") {
45
- const xhr = new XMLHttpRequest()
46
- const promise = buildPromise(xhr)
47
- const url = new URL(window.location.origin + path)
54
+ export default async function ajax(
55
+ method,
56
+ path,
57
+ data,
58
+ accept = JSON_CONTENT_TYPE
59
+ ) {
60
+ const response = await fetch(
61
+ prepareURL(path, data, method),
62
+ prepareOptions(method, data, accept)
63
+ )
64
+ const contentType = response.headers.get("content-type")
65
+ const isJson = contentType?.includes(JSON_CONTENT_TYPE)
66
+ const responseData = isJson ? await response.json() : null
48
67
 
49
- if (data && method.toLowerCase() === "get") {
50
- url.search = new URLSearchParams(data).toString()
51
- }
52
-
53
- xhr.open(method, url.toString())
54
- xhr.setRequestHeader("Content-type", "application/json; charset=utf-8")
55
- xhr.setRequestHeader("Accept", accept)
56
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
57
- xhr.setRequestHeader("X-CSRF-Token", getToken())
58
-
59
- if (data && method.toLowerCase() !== "get") {
60
- xhr.send(JSON.stringify(data))
68
+ if (response.ok) {
69
+ return { data: responseData, status: response.status }
61
70
  } else {
62
- xhr.send()
71
+ throw responseData || new Error("An error occurred during the transaction")
63
72
  }
64
-
65
- return promise
66
73
  }
@@ -20,10 +20,7 @@ import Sitemap from "alchemy_admin/sitemap"
20
20
  import Spinner from "alchemy_admin/spinner"
21
21
  import PagePublicationFields from "alchemy_admin/page_publication_fields"
22
22
  import { reloadPreview } from "alchemy_admin/components/preview_window"
23
- import {
24
- openConfirmDialog,
25
- confirmToDeleteDialog
26
- } from "alchemy_admin/confirm_dialog"
23
+ import { openConfirmDialog } from "alchemy_admin/confirm_dialog"
27
24
 
28
25
  // Web Components
29
26
  import "alchemy_admin/components"
@@ -53,11 +50,9 @@ Object.assign(Alchemy, {
53
50
  Sitemap,
54
51
  Spinner,
55
52
  PagePublicationFields,
56
- reloadPreview,
57
- openConfirmDialog,
58
- confirmToDeleteDialog
53
+ reloadPreview
59
54
  })
60
55
 
61
56
  Rails.start()
62
-
57
+ Turbo.config.forms.confirm = openConfirmDialog
63
58
  $(document).on("turbo:load", Initializer)
@@ -0,0 +1,12 @@
1
+ module Alchemy
2
+ class DeletePictureJob < BaseJob
3
+ queue_as :default
4
+
5
+ def perform(picture_id)
6
+ picture = Alchemy::Picture.find_by(id: picture_id)
7
+ return if picture.nil? || !picture.deletable?
8
+
9
+ picture.destroy
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module Filters
6
+ class Base
7
+ attr_reader :name, :resource_name
8
+
9
+ def initialize(name:, resource_name:)
10
+ @name = name
11
+ @resource_name = resource_name
12
+ end
13
+
14
+ def applied_filter_component(search_filter_params:, resource_url_proxy:, query:)
15
+ Alchemy::Admin::Resource::AppliedFilter.new(
16
+ link: dismiss_filter_url(search_filter_params, resource_url_proxy),
17
+ applied_filter_label: translated_name,
18
+ applied_filter_value: translated_value(search_filter_params[:q][name], query)
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def translated_name
25
+ Alchemy.t(:name, scope: [:filters, resource_name, name], default: name.to_s.humanize)
26
+ end
27
+
28
+ def dismiss_filter_url(search_filter_params, resource_url_proxy)
29
+ tmp_params = search_filter_params.dup
30
+ tmp_params[:q] = tmp_params[:q].except(name)
31
+ resource_url_proxy.url_for(
32
+ {action: "index"}.merge(tmp_params.except(:page))
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module Filters
6
+ class Checkbox < Base
7
+ # Renders a checkbox filter input.
8
+ # @param [Hash] params The controller params.
9
+ # @param [Ransack::Search] _query The current search query.
10
+ # @return [Alchemy::Admin::FilterInputs::Checkbox] The checkbox filter input component.
11
+ def input_component(params, _query)
12
+ Alchemy::Admin::Resource::CheckboxFilter.new(name:, label: translated_name, params:)
13
+ end
14
+
15
+ def applied_filter_component(search_filter_params:, resource_url_proxy:, query:)
16
+ Alchemy::Admin::Resource::AppliedFilter.new(
17
+ link: dismiss_filter_url(search_filter_params, resource_url_proxy),
18
+ applied_filter_label: translated_name
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module Filters
6
+ class Datepicker < Base
7
+ attr_reader :input_type
8
+ # Creates a resource filter that displays as a datepicker.
9
+ # @param name [String] The name of the filter.
10
+ # @param resource_name [String] The name of the resource.
11
+ # @param input_type [Symbol] The input type of the datepicker. Can be :date, :datetime, or :time.
12
+ # @example
13
+ # Alchemy::Admin::Filters::Datepicker.new(
14
+ # name: :created_at_lt,
15
+ # resource_name: :events,
16
+ # mode: :single,
17
+ # default: "2023-01-01"
18
+ def initialize(name:, resource_name:, input_type: :datetime)
19
+ super(name:, resource_name:)
20
+ @input_type = input_type
21
+ end
22
+
23
+ # Returns a datepicker filter component.
24
+ # @param params [Hash] The search filter params.
25
+ # @param query [Ransack::Search] The current search query.
26
+ # @return [Alchemy::Admin::Resource::DatepickerFilter] The datepicker filter component.
27
+ def input_component(params, _query)
28
+ Alchemy::Admin::Resource::DatepickerFilter.new(
29
+ name:,
30
+ label: translated_name,
31
+ input_type: @input_type,
32
+ params:
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ def translated_value(value, query)
39
+ date = Time.zone.parse(value) if value.is_a?(String)
40
+ format = case input_type
41
+ when :date
42
+ ::I18n.t(:default, scope: [:date, :formats, :alchemy])
43
+ when :datetime
44
+ ::I18n.t(:default, scope: [:time, :formats, :alchemy])
45
+ when :time
46
+ ::I18n.t(:time, scope: [:time, :formats, :alchemy])
47
+ end
48
+ ::I18n.l(date, format: format)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module Filters
6
+ class Select < Base
7
+ attr_reader :options
8
+
9
+ # Creates a resource filter that displays as a select.
10
+ # @param name [String] The name of the filter.
11
+ # @param resource_name [String] The name of the resource.
12
+ # @param options [Proc, Array] A proc that returns the options for the select, or an array of options.
13
+ def initialize(name:, resource_name:, options:)
14
+ super(name:, resource_name:)
15
+ @options = options_to_proc(options)
16
+ end
17
+
18
+ # Returns a select filter component.
19
+ # @param params [Hash] The search filter params.
20
+ # @param query [Ransack::Search] The current search query.
21
+ # @return [ Alchemy::Admin::Resource::SelectFilter] The select filter component.
22
+ def input_component(params, query)
23
+ Alchemy::Admin::Resource::SelectFilter.new(
24
+ name:,
25
+ resource_name:,
26
+ label: translated_name,
27
+ include_blank:,
28
+ options: get_options_for_select(query),
29
+ params:
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def include_blank
36
+ Alchemy.t(:all, scope: [:filters, resource_name, name])
37
+ end
38
+
39
+ def options_to_proc(options)
40
+ if options.is_a? Proc
41
+ options
42
+ else
43
+ ->(_query) { options }
44
+ end
45
+ end
46
+
47
+ def get_options_for_select(query)
48
+ options_for_select = options.call(query)
49
+ # The result of the query is an Array of Arrays, where the first element is the translated name and the second element is the value.
50
+ # If the first element is an Array, we assume that the options are already translated.
51
+ if options_for_select.first.is_a? Array
52
+ options_for_select
53
+ # If the values are translatable, we translate them.
54
+ elsif Alchemy.t(:values, scope: [:filters, resource_name, name])
55
+ options_for_select.map do |value|
56
+ [Alchemy.t(value.to_sym, scope: [:filters, resource_name, name, :values]), value]
57
+ end
58
+ # Otherwise we return the options as they are.
59
+ else
60
+ options_for_select.map { |option| [option, option] }
61
+ end
62
+ end
63
+
64
+ def translated_value(value, query)
65
+ get_options_for_select(query).detect { |option| option[1].to_s == value }&.first
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module ResourceName
6
+ def resource_model_name
7
+ resource_array.join("/").classify
8
+ end
9
+
10
+ def resource_name
11
+ resources_name.singularize
12
+ end
13
+
14
+ def resource_array
15
+ controller_path_array.reject { |el| el == "admin" }
16
+ end
17
+
18
+ def resources_name
19
+ resource_array.last
20
+ end
21
+
22
+ def controller_path_array
23
+ controller_path.split("/")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -23,22 +23,16 @@ module Alchemy
23
23
  include Alchemy::NameConversions
24
24
  include Alchemy::Taggable
25
25
  include Alchemy::TouchElements
26
+ include Alchemy::RelatableResource
26
27
 
27
- dragonfly_accessor :file, app: :alchemy_attachments do
28
- after_assign { |f| write_attribute(:file_mime_type, f.mime_type) }
29
- end
30
-
31
- stampable stamper_class_name: Alchemy.user_class.name
28
+ include Alchemy.storage_adapter.attachment_class_methods
32
29
 
33
- has_many :file_ingredients,
34
- class_name: "Alchemy::Ingredients::File",
35
- foreign_key: "related_object_id",
36
- inverse_of: :related_object
30
+ stampable stamper_class_name: Alchemy.user_class_name
37
31
 
38
- has_many :elements, through: :file_ingredients
39
- has_many :pages, through: :elements
32
+ scope :by_file_type, ->(file_type) do
33
+ Alchemy.storage_adapter.by_file_type_scope(file_type)
34
+ end
40
35
 
41
- scope :by_file_type, ->(file_type) { where(file_mime_type: file_type) }
42
36
  scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
43
37
  scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: {id: nil}) }
44
38
 
@@ -46,9 +40,8 @@ module Alchemy
46
40
  class << self
47
41
  # The class used to generate URLs for attachments
48
42
  #
49
- # @see Alchemy::Attachment::Url
50
43
  def url_class
51
- @_url_class ||= Alchemy::Attachment::Url
44
+ @_url_class ||= Alchemy.storage_adapter.attachment_url_class
52
45
  end
53
46
 
54
47
  # Set a different attachment url class
@@ -58,19 +51,6 @@ module Alchemy
58
51
  @_url_class = klass
59
52
  end
60
53
 
61
- def alchemy_resource_filters
62
- [
63
- {
64
- name: :by_file_type,
65
- values: distinct.pluck(:file_mime_type).map { |type| [Alchemy.t(type, scope: "mime_types"), type] }.sort_by(&:first)
66
- },
67
- {
68
- name: :misc,
69
- values: %w[recent last_upload without_tag]
70
- }
71
- ]
72
- end
73
-
74
54
  def last_upload
75
55
  last_id = Attachment.maximum(:id)
76
56
  return Attachment.all unless last_id
@@ -79,34 +59,43 @@ module Alchemy
79
59
  end
80
60
 
81
61
  def searchable_alchemy_resource_attributes
82
- %w[name file_name]
62
+ Alchemy.storage_adapter.searchable_alchemy_resource_attributes(name)
63
+ end
64
+
65
+ def ransackable_attributes(_auth_object = nil)
66
+ Alchemy.storage_adapter.ransackable_attributes(name)
67
+ end
68
+
69
+ def ransackable_associations(_auth_object = nil)
70
+ Alchemy.storage_adapter.ransackable_associations(name)
71
+ end
72
+
73
+ def file_types(scope = all)
74
+ Alchemy.storage_adapter.file_formats(name, scope:)
83
75
  end
84
76
 
85
77
  def allowed_filetypes
86
- Config.get(:uploader).fetch("allowed_filetypes", {}).fetch("alchemy/attachments", [])
78
+ Alchemy.config.uploader.allowed_filetypes.alchemy_attachments
79
+ end
80
+
81
+ def ransackable_scopes(_auth_object = nil)
82
+ %i[by_file_type recent last_upload without_tag deletable]
87
83
  end
88
84
  end
89
85
 
90
86
  validates_presence_of :file
91
- validates_size_of :file, maximum: Config.get(:uploader)["file_size_limit"].megabytes
92
- validates_property :ext,
93
- of: :file,
94
- in: allowed_filetypes,
95
- case_sensitive: false,
96
- message: Alchemy.t("not a valid file"),
87
+ validates_size_of :file, maximum: Alchemy.config.uploader.file_size_limit.megabytes
88
+ validate :file_type_allowed,
97
89
  unless: -> { self.class.allowed_filetypes.include?("*") }
98
90
 
99
- before_save :sanitize_file_name
100
- before_save :set_name, if: :file_name_changed?
91
+ before_save :set_name, if: -> { Alchemy.storage_adapter.set_attachment_name?(self) }
101
92
 
102
93
  scope :with_file_type, ->(file_type) { where(file_mime_type: file_type) }
103
94
 
104
95
  # Instance methods
105
96
 
106
97
  def url(options = {})
107
- if file
108
- self.class.url_class.new(self).call(options)
109
- end
98
+ self.class.url_class.new(self).call(options)
110
99
  end
111
100
 
112
101
  # An url save filename without format suffix
@@ -119,9 +108,23 @@ module Alchemy
119
108
  pages.any? && pages.not_restricted.blank?
120
109
  end
121
110
 
111
+ # File name
112
+ def file_name
113
+ Alchemy.storage_adapter.file_name(self)
114
+ end
115
+
116
+ # File size
117
+ def file_size
118
+ Alchemy.storage_adapter.file_size(self)
119
+ end
120
+
121
+ def file_mime_type
122
+ Alchemy.storage_adapter.file_mime_type(self)
123
+ end
124
+
122
125
  # File format suffix
123
126
  def extension
124
- file_name.split(".").last
127
+ Alchemy.storage_adapter.file_extension(self)
125
128
  end
126
129
 
127
130
  alias_method :suffix, :extension
@@ -157,12 +160,14 @@ module Alchemy
157
160
 
158
161
  private
159
162
 
160
- def sanitize_file_name
161
- self.file_name = sanitized_filename(file_name)
163
+ def file_type_allowed
164
+ unless extension&.in?(self.class.allowed_filetypes)
165
+ errors.add(:file, Alchemy.t("not a valid file"))
166
+ end
162
167
  end
163
168
 
164
169
  def set_name
165
- self.name = convert_to_humanized_name(file_name, file.ext)
170
+ self.name ||= convert_to_humanized_name(file_name, extension)
166
171
  end
167
172
  end
168
173
  end
@@ -6,6 +6,8 @@ module Alchemy
6
6
  end
7
7
 
8
8
  class BaseRecord < ActiveRecord::Base
9
+ include ConfigMissing
10
+ extend ConfigMissing
9
11
  extend Alchemy::SearchableResource
10
12
 
11
13
  self.abstract_class = true
@@ -46,7 +46,7 @@ module Alchemy
46
46
  else
47
47
  log_warning "Could not find element definition for #{name}. " \
48
48
  "Please check your elements.yml file!"
49
- {}
49
+ ElementDefinition.new
50
50
  end
51
51
  end
52
52
  end
@@ -53,16 +53,16 @@ module Alchemy
53
53
 
54
54
  # Returns all element ingredient definitions from the +elements.yml+ file
55
55
  def ingredient_definitions
56
- definition.fetch(:ingredients, [])
56
+ definition.ingredients
57
57
  end
58
58
 
59
59
  # Returns the definition for given ingredient role
60
60
  def ingredient_definition_for(role)
61
61
  if ingredient_definitions.blank?
62
- log_warning "Element #{name} is missing the ingredient definition for #{role}"
63
62
  nil
64
63
  else
65
- ingredient_definitions.find { |d| d[:role] == role.to_s }
64
+ ingredient_definitions.find { _1.role == role.to_s } ||
65
+ log_warning("Element #{name} is missing the ingredient definition for #{role}")
66
66
  end
67
67
  end
68
68
 
@@ -99,10 +99,10 @@ module Alchemy
99
99
 
100
100
  # Builds ingredients for this element as described in the +elements.yml+
101
101
  def build_ingredients
102
- ingredient_definitions.each do |attributes|
102
+ ingredient_definitions.each do |definition|
103
103
  ingredients.build(
104
- role: attributes[:role],
105
- type: Alchemy::Ingredient.normalize_type(attributes[:type])
104
+ role: definition.role,
105
+ type: Alchemy::Ingredient.normalize_type(definition.type)
106
106
  )
107
107
  end
108
108
  end
@@ -33,7 +33,7 @@ module Alchemy
33
33
  # @see Alchemy::Element::Presenters#display_name_for
34
34
  #
35
35
  def display_name
36
- self.class.display_name_for(definition["name"] || name)
36
+ self.class.display_name_for(definition.name || name)
37
37
  end
38
38
 
39
39
  # Returns a preview text for element.
@@ -78,15 +78,6 @@ module Alchemy
78
78
  "#{display_name}: #{preview_text(maxlength)}"
79
79
  end
80
80
 
81
- # Returns a dom id used for elements html id tag.
82
- # @deprecated
83
- def dom_id
84
- if caller.none? { |l| l =~ Regexp.new("alchemy/elements_block_helper.rb:117:in `element_view_for'") }
85
- Alchemy::Deprecation.warn("dom_id is deprecated and will be removed from Alchemy 8.0. Please pass an id to the element_view_for helper instead.")
86
- end
87
- self.class.dom_id_class.new(self).call
88
- end
89
-
90
81
  # The ingredient that's used for element's preview text.
91
82
  #
92
83
  # It tries to find one of element's ingredients that is defined +as_element_title+.
@@ -113,8 +104,8 @@ module Alchemy
113
104
  def first_ingredient_by_definition
114
105
  return if ingredient_definitions.empty?
115
106
 
116
- role = ingredient_definitions.first["role"]
117
- ingredients.detect { |ingredient| ingredient.role == role }
107
+ role = ingredient_definitions.first.role
108
+ ingredients.detect { _1.role == role }
118
109
  end
119
110
  end
120
111
  end