alchemy_cms 7.4.16 → 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.
Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -77
  3. data/Gemfile +13 -7
  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 +6 -3
  21. data/app/components/alchemy/ingredients/datetime_view.rb +2 -4
  22. data/app/components/alchemy/ingredients/number_view.rb +18 -0
  23. data/app/controllers/alchemy/admin/attachments_controller.rb +8 -15
  24. data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -6
  25. data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
  26. data/app/controllers/alchemy/admin/languages_controller.rb +1 -4
  27. data/app/controllers/alchemy/admin/pages_controller.rb +15 -17
  28. data/app/controllers/alchemy/admin/pictures_controller.rb +9 -5
  29. data/app/controllers/alchemy/admin/resources_controller.rb +16 -106
  30. data/app/controllers/alchemy/api/nodes_controller.rb +1 -1
  31. data/app/controllers/alchemy/api/pages_controller.rb +0 -2
  32. data/app/controllers/alchemy/attachments_controller.rb +43 -14
  33. data/app/controllers/alchemy/messages_controller.rb +1 -1
  34. data/app/controllers/alchemy/pages_controller.rb +7 -2
  35. data/app/controllers/concerns/alchemy/admin/resource_filter.rb +92 -0
  36. data/app/decorators/alchemy/element_editor.rb +5 -48
  37. data/app/decorators/alchemy/ingredient_editor.rb +4 -62
  38. data/app/helpers/alchemy/admin/base_helper.rb +14 -84
  39. data/app/helpers/alchemy/admin/elements_helper.rb +4 -4
  40. data/app/helpers/alchemy/admin/pages_helper.rb +1 -1
  41. data/app/helpers/alchemy/base_helper.rb +0 -30
  42. data/app/helpers/alchemy/elements_block_helper.rb +0 -14
  43. data/app/helpers/alchemy/pages_helper.rb +2 -2
  44. data/{lib → app/helpers}/alchemy/resources_helper.rb +6 -46
  45. data/app/javascript/alchemy_admin/components/action.js +2 -0
  46. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +3 -3
  47. data/app/javascript/alchemy_admin/components/datepicker.js +10 -2
  48. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +7 -7
  49. data/app/javascript/alchemy_admin/components/element_editor.js +1 -1
  50. data/app/javascript/alchemy_admin/components/index.js +1 -3
  51. data/app/javascript/alchemy_admin/components/remote_select.js +4 -1
  52. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +5 -1
  53. data/app/javascript/alchemy_admin/components/tinymce.js +4 -2
  54. data/app/javascript/alchemy_admin/components/update_check.js +42 -0
  55. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +15 -8
  56. data/app/javascript/alchemy_admin/components/uploader/progress.js +12 -6
  57. data/app/javascript/alchemy_admin/components/uploader.js +4 -2
  58. data/app/javascript/alchemy_admin/confirm_dialog.js +27 -57
  59. data/app/javascript/alchemy_admin/dirty.js +3 -2
  60. data/app/javascript/alchemy_admin/i18n.js +15 -16
  61. data/app/javascript/alchemy_admin/initializer.js +1 -49
  62. data/app/javascript/alchemy_admin/link_dialog.js +1 -6
  63. data/app/javascript/alchemy_admin/utils/ajax.js +51 -44
  64. data/app/javascript/alchemy_admin.js +3 -8
  65. data/app/models/alchemy/admin/filters/base.rb +38 -0
  66. data/app/models/alchemy/admin/filters/checkbox.rb +24 -0
  67. data/app/models/alchemy/admin/filters/datepicker.rb +53 -0
  68. data/app/models/alchemy/admin/filters/select.rb +70 -0
  69. data/app/models/alchemy/admin/resource_name.rb +27 -0
  70. data/app/models/alchemy/attachment.rb +49 -37
  71. data/app/models/alchemy/base_record.rb +2 -0
  72. data/app/models/alchemy/element/definitions.rb +1 -1
  73. data/app/models/alchemy/element/element_ingredients.rb +6 -6
  74. data/app/models/alchemy/element/presenters.rb +3 -12
  75. data/app/models/alchemy/element.rb +9 -27
  76. data/app/models/alchemy/element_definition.rb +160 -0
  77. data/app/models/alchemy/ingredient.rb +11 -44
  78. data/app/models/alchemy/ingredient_definition.rb +134 -0
  79. data/app/models/alchemy/ingredient_validator.rb +7 -3
  80. data/app/models/alchemy/ingredients/number.rb +19 -0
  81. data/app/models/alchemy/language/code.rb +0 -1
  82. data/app/models/alchemy/language.rb +0 -14
  83. data/app/models/alchemy/message.rb +3 -7
  84. data/app/models/alchemy/node.rb +1 -22
  85. data/app/models/alchemy/page/{page_layouts.rb → definitions.rb} +12 -19
  86. data/app/models/alchemy/page/fixed_attributes.rb +1 -1
  87. data/app/models/alchemy/page/page_elements.rb +13 -14
  88. data/app/models/alchemy/page/page_naming.rb +0 -1
  89. data/app/models/alchemy/page/page_natures.rb +7 -7
  90. data/app/models/alchemy/page/page_scopes.rb +1 -1
  91. data/app/models/alchemy/page.rb +11 -33
  92. data/app/models/alchemy/page_definition.rb +115 -0
  93. data/app/models/alchemy/picture.rb +71 -88
  94. data/app/models/alchemy/picture_variant.rb +115 -5
  95. data/{lib → app/models}/alchemy/resource.rb +4 -18
  96. data/{lib → app/models}/alchemy/searchable_resource.rb +15 -0
  97. data/app/models/alchemy/site/layout.rb +5 -6
  98. data/app/models/alchemy/site.rb +0 -15
  99. data/app/models/alchemy/storage_adapter/active_storage/attachment_url.rb +41 -0
  100. data/app/models/alchemy/storage_adapter/active_storage/picture_url.rb +55 -0
  101. data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +40 -0
  102. data/app/models/alchemy/storage_adapter/active_storage.rb +173 -0
  103. data/app/models/alchemy/{attachment/url.rb → storage_adapter/dragonfly/attachment_url.rb} +12 -12
  104. data/app/models/alchemy/{picture/url.rb → storage_adapter/dragonfly/picture_url.rb} +28 -12
  105. data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
  106. data/app/models/alchemy/storage_adapter/dragonfly.rb +183 -0
  107. data/app/models/alchemy/storage_adapter.rb +74 -0
  108. data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
  109. data/app/serializers/alchemy/element_serializer.rb +0 -1
  110. data/app/serializers/alchemy/page_tree_serializer.rb +1 -17
  111. data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -0
  112. data/app/stylesheets/alchemy/_defaults.scss +3 -0
  113. data/app/stylesheets/alchemy/_extends.scss +69 -0
  114. data/app/{assets/stylesheets → stylesheets}/alchemy/_mixins.scss +33 -49
  115. data/app/stylesheets/alchemy/_variables.scss +5 -0
  116. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +20 -37
  117. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +16 -14
  118. data/app/stylesheets/alchemy/admin/buttons.scss +160 -0
  119. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/clipboard.scss +2 -2
  120. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dashboard.scss +13 -16
  121. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dialogs.scss +23 -16
  122. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +150 -105
  123. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
  124. data/app/stylesheets/alchemy/admin/filters.scss +58 -0
  125. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +53 -60
  126. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +21 -7
  127. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +31 -19
  128. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -16
  129. data/app/stylesheets/alchemy/admin/hints.scss +5 -0
  130. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +10 -1
  131. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +10 -8
  132. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/images.scss +1 -1
  133. data/app/stylesheets/alchemy/admin/labels.scss +5 -0
  134. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/lists.scss +3 -3
  135. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/navigation.scss +61 -55
  136. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
  137. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +18 -18
  138. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +2 -2
  139. data/app/stylesheets/alchemy/admin/pagination.scss +144 -0
  140. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/preview_window.scss +8 -6
  141. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
  142. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/resource_info.scss +8 -5
  143. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +9 -6
  144. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +49 -37
  145. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +5 -6
  146. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +38 -33
  147. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +6 -4
  148. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tags.scss +6 -4
  149. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +12 -6
  150. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +2 -2
  151. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
  152. data/app/stylesheets/alchemy/admin.scss +44 -0
  153. data/app/stylesheets/alchemy/custom-properties.css +244 -0
  154. data/app/stylesheets/alchemy/welcome.scss +75 -0
  155. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/content/alchemy/content.scss +8 -9
  156. data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
  157. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +133 -136
  158. data/app/views/alchemy/admin/attachments/_files_list.html.erb +2 -2
  159. data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
  160. data/app/views/alchemy/admin/{elements/_clipboard_button.html.erb → clipboard/_button.html.erb} +3 -5
  161. data/app/views/alchemy/admin/clipboard/_update_nested_element_button.turbo_stream.erb +11 -0
  162. data/app/views/alchemy/admin/clipboard/clear.turbo_stream.erb +4 -0
  163. data/app/views/alchemy/admin/clipboard/index.html.erb +15 -13
  164. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +18 -0
  165. data/app/views/alchemy/admin/clipboard/remove.turbo_stream.erb +9 -0
  166. data/app/views/alchemy/admin/dashboard/info.html.erb +17 -31
  167. data/app/views/alchemy/admin/elements/_element.html.erb +4 -8
  168. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  169. data/app/views/alchemy/admin/elements/_header.html.erb +1 -0
  170. data/app/views/alchemy/admin/elements/_toolbar.html.erb +4 -6
  171. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +2 -1
  172. data/app/views/alchemy/admin/elements/index.html.erb +2 -2
  173. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +3 -16
  174. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +1 -10
  175. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  176. data/app/views/alchemy/admin/languages/_table.html.erb +1 -1
  177. data/app/views/alchemy/admin/languages/index.html.erb +5 -2
  178. data/app/views/alchemy/admin/layoutpages/index.html.erb +1 -12
  179. data/app/views/alchemy/admin/pages/_form.html.erb +2 -2
  180. data/app/views/alchemy/admin/pages/_page.html.erb +2 -3
  181. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -15
  182. data/app/views/alchemy/admin/pages/index.html.erb +1 -1
  183. data/app/views/alchemy/admin/pages/info.html.erb +1 -1
  184. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +9 -12
  185. data/app/views/alchemy/admin/partials/_search_form.html.erb +4 -10
  186. data/app/views/alchemy/admin/pictures/_archive.html.erb +4 -7
  187. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +2 -1
  188. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -1
  189. data/app/views/alchemy/admin/pictures/_form.html.erb +1 -1
  190. data/app/views/alchemy/admin/pictures/index.html.erb +2 -7
  191. data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
  192. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +11 -21
  193. data/app/views/alchemy/admin/resources/_pagination.html.erb +6 -0
  194. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +4 -2
  195. data/app/views/alchemy/admin/resources/_resource_table.html.erb +1 -1
  196. data/app/views/alchemy/admin/resources/_table_header.html.erb +1 -15
  197. data/app/views/alchemy/admin/sites/index.html.erb +5 -1
  198. data/app/views/alchemy/admin/styleguide/index.html.erb +8 -0
  199. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  200. data/app/views/alchemy/admin/tinymce/_setup.html.erb +7 -7
  201. data/app/{javascript/alchemy_admin/locales/en.js → views/alchemy/admin/translations/_en.js} +5 -2
  202. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
  203. data/app/views/alchemy/admin/uploader/_setup.html.erb +4 -4
  204. data/app/views/alchemy/ingredients/_number_editor.html.erb +24 -0
  205. data/app/views/alchemy/ingredients/_page_editor.html.erb +1 -0
  206. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +1 -0
  207. data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
  208. data/app/views/alchemy/no_index.html.erb +31 -0
  209. data/app/views/alchemy/welcome.html.erb +12 -10
  210. data/app/views/kaminari/alchemy/_first_page.html.erb +5 -3
  211. data/app/views/kaminari/alchemy/_last_page.html.erb +5 -3
  212. data/app/views/kaminari/alchemy/_next_page.html.erb +5 -3
  213. data/app/views/kaminari/alchemy/_paginator.html.erb +18 -13
  214. data/app/views/kaminari/alchemy/_prev_page.html.erb +5 -3
  215. data/app/views/layouts/alchemy/admin.html.erb +5 -9
  216. data/bun.lockb +0 -0
  217. data/bundles/remixicon.mjs +153 -0
  218. data/config/alchemy/config.yml +3 -2
  219. data/config/initializers/dragonfly.rb +0 -1
  220. data/config/initializers/mime_types.rb +1 -0
  221. data/config/locales/alchemy.en.yml +32 -14
  222. data/config/routes.rb +0 -2
  223. data/eslint.config.js +2 -1
  224. data/lib/alchemy/admin/preview_url.rb +4 -5
  225. data/lib/alchemy/cache_digests/template_tracker.rb +6 -9
  226. data/lib/alchemy/config_missing.rb +14 -0
  227. data/lib/alchemy/configuration/base_option.rb +24 -0
  228. data/lib/alchemy/configuration/boolean_option.rb +16 -0
  229. data/lib/alchemy/configuration/class_option.rb +15 -0
  230. data/lib/alchemy/configuration/class_set_option.rb +46 -0
  231. data/lib/alchemy/configuration/integer_list_option.rb +13 -0
  232. data/lib/alchemy/configuration/integer_option.rb +12 -0
  233. data/lib/alchemy/configuration/list_option.rb +22 -0
  234. data/lib/alchemy/configuration/regexp_option.rb +11 -0
  235. data/lib/alchemy/configuration/string_list_option.rb +13 -0
  236. data/lib/alchemy/configuration/string_option.rb +11 -0
  237. data/lib/alchemy/configuration.rb +115 -0
  238. data/lib/alchemy/configuration_methods.rb +3 -1
  239. data/lib/alchemy/configurations/default_language.rb +12 -0
  240. data/lib/alchemy/configurations/default_site.rb +10 -0
  241. data/lib/alchemy/configurations/format_matchers.rb +11 -0
  242. data/lib/alchemy/configurations/mailer.rb +16 -0
  243. data/lib/alchemy/configurations/main.rb +216 -0
  244. data/lib/alchemy/configurations/preview.rb +32 -0
  245. data/lib/alchemy/configurations/sitemap.rb +10 -0
  246. data/lib/alchemy/configurations/uploader.rb +34 -0
  247. data/lib/alchemy/controller_actions.rb +1 -2
  248. data/lib/alchemy/engine.rb +65 -17
  249. data/lib/alchemy/hints.rb +3 -7
  250. data/lib/alchemy/name_conversions.rb +0 -6
  251. data/lib/alchemy/on_page_layout.rb +2 -2
  252. data/lib/alchemy/permissions.rb +0 -10
  253. data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
  254. data/lib/alchemy/seeder.rb +6 -12
  255. data/lib/alchemy/tasks/usage.rb +4 -4
  256. data/lib/alchemy/test_support/config_stubbing.rb +1 -7
  257. data/lib/alchemy/test_support/factories/attachment_factory.rb +13 -2
  258. data/lib/alchemy/test_support/factories/language_factory.rb +5 -9
  259. data/lib/alchemy/test_support/factories/page_factory.rb +2 -4
  260. data/lib/alchemy/test_support/factories/picture_factory.rb +30 -2
  261. data/lib/alchemy/test_support/factories/site_factory.rb +2 -2
  262. data/lib/alchemy/test_support/having_crop_action_examples.rb +2 -2
  263. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +80 -26
  264. data/lib/alchemy/test_support/shared_ingredient_examples.rb +5 -5
  265. data/lib/alchemy/upgrader/.keep +0 -0
  266. data/lib/alchemy/upgrader/eight_zero.rb +14 -0
  267. data/lib/alchemy/upgrader.rb +33 -20
  268. data/lib/alchemy/version.rb +1 -1
  269. data/lib/alchemy.rb +192 -170
  270. data/lib/alchemy_cms.rb +1 -7
  271. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +0 -3
  272. data/lib/generators/alchemy/install/files/_article.html.erb +6 -4
  273. data/lib/generators/alchemy/install/files/alchemy.en.yml +22 -3
  274. data/lib/generators/alchemy/install/files/application.html.erb +5 -0
  275. data/lib/generators/alchemy/install/install_generator.rb +5 -14
  276. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +196 -0
  277. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -1
  278. data/lib/generators/alchemy/install/templates/elements.yml.tt +3 -1
  279. data/lib/generators/alchemy/install/templates/menus.yml.tt +1 -1
  280. data/lib/generators/alchemy/install/templates/page_layouts.yml.tt +2 -2
  281. data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -2
  282. data/lib/tasks/alchemy/assets.rake +14 -0
  283. data/lib/tasks/alchemy/upgrade.rake +12 -47
  284. data/lib/tasks/alchemy/usage.rake +0 -2
  285. data/vendor/javascript/tinymce.min.js +1 -1
  286. data/vitest.config.js +21 -0
  287. metadata +184 -183
  288. data/app/assets/builds/alchemy/admin/page-select.css.map +0 -1
  289. data/app/assets/builds/alchemy/admin/print.css.map +0 -1
  290. data/app/assets/builds/alchemy/admin.css.map +0 -1
  291. data/app/assets/builds/alchemy/custom-properties.css.map +0 -1
  292. data/app/assets/builds/alchemy/welcome.css.map +0 -1
  293. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +0 -1
  294. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +0 -1
  295. data/app/assets/javascripts/alchemy/admin.js +0 -10
  296. data/app/assets/stylesheets/alchemy/_defaults.scss +0 -3
  297. data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +0 -45
  298. data/app/assets/stylesheets/alchemy/_deprecation.scss +0 -17
  299. data/app/assets/stylesheets/alchemy/_extends.scss +0 -62
  300. data/app/assets/stylesheets/alchemy/_variables.scss +0 -201
  301. data/app/assets/stylesheets/alchemy/admin/buttons.scss +0 -123
  302. data/app/assets/stylesheets/alchemy/admin/hints.scss +0 -5
  303. data/app/assets/stylesheets/alchemy/admin/labels.scss +0 -3
  304. data/app/assets/stylesheets/alchemy/admin/pagination.scss +0 -92
  305. data/app/assets/stylesheets/alchemy/admin.scss +0 -42
  306. data/app/assets/stylesheets/alchemy/custom-properties.css +0 -98
  307. data/app/assets/stylesheets/alchemy/welcome.scss +0 -57
  308. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.css +0 -711
  309. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.css +0 -705
  310. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.min.css +0 -7
  311. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.min.css +0 -7
  312. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.css +0 -29
  313. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.min.css +0 -7
  314. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.css +0 -677
  315. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.min.css +0 -7
  316. data/app/controllers/alchemy/elements_controller.rb +0 -32
  317. data/app/models/alchemy/element/dom_id.rb +0 -31
  318. data/app/models/alchemy/picture/calculations.rb +0 -49
  319. data/app/models/alchemy/picture/transformations.rb +0 -115
  320. data/app/views/alchemy/admin/attachments/destroy.js.erb +0 -1
  321. data/app/views/alchemy/admin/clipboard/clear.js.erb +0 -3
  322. data/app/views/alchemy/admin/clipboard/insert.js.erb +0 -29
  323. data/app/views/alchemy/admin/clipboard/remove.js.erb +0 -10
  324. data/app/views/alchemy/admin/resources/_filter.html.erb +0 -12
  325. data/app/views/alchemy/admin/resources/_resource.html.erb +0 -34
  326. data/app/views/alchemy/admin/resources/_table.html.erb +0 -29
  327. data/app/views/alchemy/elements/show.html.erb +0 -1
  328. data/app/views/alchemy/elements/show.js.erb +0 -1
  329. data/app/views/alchemy/ingredients/_audio_view.html.erb +0 -1
  330. data/app/views/alchemy/ingredients/_boolean_view.html.erb +0 -1
  331. data/app/views/alchemy/ingredients/_datetime_view.html.erb +0 -3
  332. data/app/views/alchemy/ingredients/_file_view.html.erb +0 -4
  333. data/app/views/alchemy/ingredients/_headline_view.html.erb +0 -4
  334. data/app/views/alchemy/ingredients/_html_view.html.erb +0 -1
  335. data/app/views/alchemy/ingredients/_link_view.html.erb +0 -4
  336. data/app/views/alchemy/ingredients/_node_view.html.erb +0 -1
  337. data/app/views/alchemy/ingredients/_page_view.html.erb +0 -1
  338. data/app/views/alchemy/ingredients/_picture_view.html.erb +0 -4
  339. data/app/views/alchemy/ingredients/_richtext_view.html.erb +0 -3
  340. data/app/views/alchemy/ingredients/_select_view.html.erb +0 -1
  341. data/app/views/alchemy/ingredients/_text_view.html.erb +0 -4
  342. data/app/views/alchemy/ingredients/_video_view.html.erb +0 -3
  343. data/babel.config.js +0 -12
  344. data/config/initializers/assets.rb +0 -4
  345. data/lib/alchemy/config.rb +0 -114
  346. data/lib/alchemy/element_definition.rb +0 -73
  347. data/lib/alchemy/page_layout.rb +0 -73
  348. data/lib/alchemy/resource_filter.rb +0 -40
  349. data/lib/alchemy/upgrader/seven_point_four.rb +0 -26
  350. data/lib/alchemy/upgrader/seven_point_three.rb +0 -52
  351. data/lib/generators/alchemy/ingredient/templates/view.html.erb +0 -1
  352. data/lib/generators/alchemy/install/files/alchemy_admin.js +0 -1
  353. data/lib/generators/alchemy/install/files/all.js +0 -11
  354. data/lib/generators/alchemy/install/files/article.css +0 -25
  355. data/vendor/assets/images/remixicon.symbol.svg +0 -11
  356. /data/app/{assets/stylesheets → stylesheets}/alchemy/_fonts.scss +0 -0
  357. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachment-select.scss +0 -0
  358. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachments.scss +0 -0
  359. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flash.scss +0 -0
  360. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/list_filter.scss +0 -0
  361. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/node-select.scss +0 -0
  362. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/spinner.scss +0 -0
  363. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/skintool.json +0 -0
  364. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/fonts/tinymce-mobile.woff +0 -0
@@ -81,8 +81,8 @@ module Alchemy
81
81
  #
82
82
  def available_element_definitions(only_element_named = nil)
83
83
  @_available_element_definitions ||= if only_element_named
84
- definition = Element.definition_by_name(only_element_named)
85
- element_definitions_by_name(definition["nestable_elements"])
84
+ element_definition = Element.definition_by_name(only_element_named)
85
+ element_definitions_by_name(element_definition.nestable_elements)
86
86
  else
87
87
  element_definitions.dup
88
88
  end
@@ -100,7 +100,7 @@ module Alchemy
100
100
  # All names of elements that can actually be placed on current page.
101
101
  #
102
102
  def available_element_names
103
- @_available_element_names ||= available_element_definitions.map { |e| e["name"] }
103
+ @_available_element_names ||= available_element_definitions.map(&:name)
104
104
  end
105
105
 
106
106
  # Available element definitions excluding nested unique elements.
@@ -109,7 +109,7 @@ module Alchemy
109
109
  @_available_elements = if parent
110
110
  parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name)
111
111
  available_element_definitions(parent.name).reject do |e|
112
- parents_unique_nested_elements.include? e["name"]
112
+ parents_unique_nested_elements.include?(e.name)
113
113
  end
114
114
  else
115
115
  available_element_definitions
@@ -129,10 +129,9 @@ module Alchemy
129
129
  #
130
130
  def descendent_element_definitions
131
131
  definitions = element_definitions_by_name(element_definition_names)
132
- definitions.select { |d| d.key?("nestable_elements") }.each do |d|
133
- definitions += element_definitions_by_name(d["nestable_elements"])
134
- end
135
- definitions.uniq { |d| d["name"] }
132
+ definitions.select { _1.nestable_elements.any? }.flat_map do |d|
133
+ element_definitions_by_name(d.nestable_elements)
134
+ end.uniq(&:name)
136
135
  end
137
136
 
138
137
  # All names of elements that are defined in the page definition.
@@ -145,7 +144,7 @@ module Alchemy
145
144
  # elements: [headline, contactform]
146
145
  #
147
146
  def element_definition_names
148
- definition["elements"] || []
147
+ definition.elements || []
149
148
  end
150
149
 
151
150
  # Element definitions with given name(s)
@@ -161,7 +160,7 @@ module Alchemy
161
160
  if names.to_s == "all"
162
161
  Element.definitions
163
162
  else
164
- Element.definitions.select { |e| names.include? e["name"] }
163
+ Element.definitions.select { names.include?(_1.name) }
165
164
  end
166
165
  end
167
166
 
@@ -181,7 +180,7 @@ module Alchemy
181
180
  # And if so, it generates them.
182
181
  #
183
182
  def generate_elements
184
- definition.fetch("autogenerate", []).each do |element_name|
183
+ definition.autogenerate&.each do |element_name|
185
184
  Element.create(page: self, page_version: draft_version, name: element_name)
186
185
  end
187
186
  end
@@ -190,7 +189,7 @@ module Alchemy
190
189
  #
191
190
  def delete_unique_element_definitions!
192
191
  @_available_element_definitions.delete_if do |element|
193
- element["unique"] && @_existing_element_names.include?(element["name"])
192
+ element.unique && @_existing_element_names.include?(element.name)
194
193
  end
195
194
  end
196
195
 
@@ -198,8 +197,8 @@ module Alchemy
198
197
  #
199
198
  def delete_outnumbered_element_definitions!
200
199
  @_available_element_definitions.delete_if do |element|
201
- outnumbered = @_existing_element_names.select { |name| name == element["name"] }
202
- element["amount"] && outnumbered.count >= element["amount"].to_i
200
+ outnumbered = @_existing_element_names.select { |name| name == element.name }
201
+ element.amount && outnumbered.count >= element.amount
203
202
  end
204
203
  end
205
204
  end
@@ -5,7 +5,6 @@ module Alchemy
5
5
  module PageNaming
6
6
  extend ActiveSupport::Concern
7
7
  include NameConversions
8
-
9
8
  RESERVED_URLNAMES = %w[admin messages new]
10
9
 
11
10
  included do
@@ -38,13 +38,13 @@ module Alchemy
38
38
  # @returns Array
39
39
  #
40
40
  def has_limited_editors?
41
- definition["editable_by"].present?
41
+ definition.editable_by.present?
42
42
  end
43
43
 
44
44
  def editor_roles
45
45
  return unless has_limited_editors?
46
46
 
47
- definition["editable_by"]
47
+ definition.editable_by
48
48
  end
49
49
 
50
50
  # True if page locked_at timestamp and locked_by id are set
@@ -80,10 +80,10 @@ module Alchemy
80
80
 
81
81
  # Returns the self#page_layout definition from config/alchemy/page_layouts.yml file.
82
82
  def definition
83
- definition = PageLayout.get(page_layout)
83
+ definition = PageDefinition.get(page_layout)
84
84
  if definition.nil?
85
85
  log_warning "Page definition for `#{page_layout}` not found. Please check `page_layouts.yml` file."
86
- return {}
86
+ return PageDefinition.new
87
87
  end
88
88
  definition
89
89
  end
@@ -146,14 +146,14 @@ module Alchemy
146
146
  def cache_page?
147
147
  return false unless caching_enabled?
148
148
 
149
- page_layout = PageLayout.get(self.page_layout)
150
- page_layout["cache"] != false && page_layout["searchresults"] != true
149
+ page_layout = PageDefinition.get(self.page_layout)
150
+ page_layout.cache != false && page_layout.searchresults != true
151
151
  end
152
152
 
153
153
  private
154
154
 
155
155
  def caching_enabled?
156
- Alchemy::Config.get(:cache_pages) &&
156
+ Alchemy.config.cache_pages &&
157
157
  Rails.application.config.action_controller.perform_caching
158
158
  end
159
159
  end
@@ -124,7 +124,7 @@ module Alchemy
124
124
  end
125
125
 
126
126
  def ransackable_scopes(_auth_object)
127
- [:published, :contentpages, :from_current_site, :searchables, :layoutpages]
127
+ [:published, :not_public, :restricted, :contentpages, :from_current_site, :searchables, :layoutpages, :by_page_layout]
128
128
  end
129
129
  end
130
130
  end
@@ -36,7 +36,7 @@
36
36
  #
37
37
 
38
38
  require_dependency "alchemy/page/fixed_attributes"
39
- require_dependency "alchemy/page/page_layouts"
39
+ require_dependency "alchemy/page/definitions"
40
40
  require_dependency "alchemy/page/page_scopes"
41
41
  require_dependency "alchemy/page/page_natures"
42
42
  require_dependency "alchemy/page/page_naming"
@@ -44,7 +44,6 @@ require_dependency "alchemy/page/page_elements"
44
44
 
45
45
  module Alchemy
46
46
  class Page < BaseRecord
47
- include Alchemy::Hints
48
47
  include Alchemy::Logger
49
48
  include Alchemy::Taggable
50
49
 
@@ -90,7 +89,7 @@ module Alchemy
90
89
 
91
90
  acts_as_nested_set(dependent: :destroy, scope: [:layoutpage, :language_id])
92
91
 
93
- stampable stamper_class_name: Alchemy.user_class.name
92
+ stampable stamper_class_name: Alchemy.user_class_name
94
93
 
95
94
  belongs_to :language
96
95
 
@@ -156,7 +155,7 @@ module Alchemy
156
155
  after_update :touch_nodes
157
156
 
158
157
  # Concerns
159
- include PageLayouts
158
+ include Definitions
160
159
  include PageScopes
161
160
  include PageNatures
162
161
  include PageNaming
@@ -164,6 +163,7 @@ module Alchemy
164
163
 
165
164
  # site_name accessor
166
165
  delegate :name, to: :site, prefix: true, allow_nil: true
166
+ delegate :has_hint?, :hint, to: :definition
167
167
 
168
168
  # Class methods
169
169
  #
@@ -183,37 +183,10 @@ module Alchemy
183
183
  @_url_path_class = klass
184
184
  end
185
185
 
186
- def alchemy_resource_filters
187
- [
188
- {
189
- name: :by_page_layout,
190
- values: PageLayout.all.map { |p| [Alchemy.t(p["name"], scope: "page_layout_names"), p["name"]] }
191
- },
192
- {
193
- name: :status,
194
- values: %w[published not_public restricted]
195
- }
196
- ]
197
- end
198
-
199
186
  def searchable_alchemy_resource_attributes
200
187
  %w[name urlname title]
201
188
  end
202
189
 
203
- # Used to store the current page previewed in the edit page template.
204
- # @deprecated Use {Alchemy::Current#preview_page=} instead.
205
- def current_preview=(page)
206
- Current.preview_page = page
207
- end
208
- deprecate "current_preview=": :"Alchemy::Current.preview_page=", deprecator: Alchemy::Deprecation
209
-
210
- # Returns the current page previewed in the edit page template.
211
- # @deprecated Use {Alchemy::Current#preview_page} instead.
212
- def current_preview
213
- Current.preview_page
214
- end
215
- deprecate current_preview: :"Alchemy::Current.preview_page", deprecator: Alchemy::Deprecation
216
-
217
190
  # @return the language root page for given language id.
218
191
  # @param language_id [Fixnum]
219
192
  #
@@ -265,13 +238,13 @@ module Alchemy
265
238
 
266
239
  clipboard_pages = all_from_clipboard(clipboard)
267
240
  allowed_page_layouts = Alchemy::Page.selectable_layouts(language_id, layoutpages: layoutpages)
268
- allowed_page_layout_names = allowed_page_layouts.collect { |p| p["name"] }
241
+ allowed_page_layout_names = allowed_page_layouts.collect(&:name)
269
242
  clipboard_pages.select { |cp| allowed_page_layout_names.include?(cp.page_layout) }
270
243
  end
271
244
 
272
245
  def link_target_options
273
246
  options = [[Alchemy.t(:default, scope: "link_target_options"), ""]]
274
- link_target_options = Config.get(:link_target_options)
247
+ link_target_options = Alchemy.config.link_target_options
275
248
  link_target_options.each do |option|
276
249
  # add an underscore to the options to provide the default syntax
277
250
  # @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target
@@ -280,6 +253,11 @@ module Alchemy
280
253
  end
281
254
  options
282
255
  end
256
+
257
+ # Allow all string and text attributes to be searchable by Ransack.
258
+ def ransackable_attributes(_auth_object = nil)
259
+ searchable_alchemy_resource_attributes + ["updated_at"]
260
+ end
283
261
  end
284
262
 
285
263
  # Instance methods
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class PageDefinition
5
+ include ActiveModel::Model
6
+ include ActiveModel::Attributes
7
+ include Alchemy::Hints
8
+
9
+ extend ActiveModel::Translation
10
+
11
+ attribute :name, :string
12
+ attribute :elements, default: []
13
+ attribute :autogenerate, default: []
14
+ attribute :layoutpage, :boolean, default: false
15
+ attribute :unique, :boolean, default: false
16
+ attribute :cache, :boolean, default: true
17
+ attribute :insert_elements_at, :string, default: "bottom"
18
+ attribute :fixed_attributes, default: {}
19
+ attribute :searchable, :boolean, default: true
20
+ attribute :searchresults, :boolean, default: false
21
+ attribute :hide, :boolean, default: false
22
+ attribute :editable_by
23
+ attribute :hint
24
+
25
+ validates :name,
26
+ presence: true,
27
+ format: {
28
+ with: /\A[a-z_-]+\z/
29
+ }
30
+
31
+ delegate :[], to: :attributes
32
+ delegate :blank?, to: :name
33
+
34
+ class << self
35
+ # Returns all page layouts.
36
+ #
37
+ # They are defined in +config/alchemy/page_layout.yml+ file.
38
+ #
39
+ def all
40
+ @definitions ||= read_definitions_file.map { new(**_1) }
41
+ end
42
+
43
+ def map(...)
44
+ all.map(...)
45
+ end
46
+ alias_method :collect, :map
47
+
48
+ # Add additional page definitions to collection.
49
+ #
50
+ # Useful for extending the page layouts from an Alchemy module.
51
+ #
52
+ # === Usage Example
53
+ #
54
+ # Call +Alchemy::PageDefinition.add(your_definition)+ in your engine.rb file.
55
+ #
56
+ # @param [Array || Hash]
57
+ # You can pass a single layout definition as Hash, or a collection of page layouts as Array.
58
+ #
59
+ def add(definition)
60
+ all
61
+ @definitions += Array.wrap(definition).map { new(**_1) }
62
+ end
63
+
64
+ # Returns one page definition by given name.
65
+ #
66
+ def get(name)
67
+ return new if name.blank?
68
+
69
+ all.detect { _1.name.casecmp(name).zero? }
70
+ end
71
+
72
+ def reset!
73
+ @definitions = nil
74
+ end
75
+
76
+ # The absolute +page_layouts.yml+ file path
77
+ # @return [Pathname]
78
+ def layouts_file_path
79
+ Rails.root.join("config", "alchemy", "page_layouts.yml")
80
+ end
81
+
82
+ private
83
+
84
+ # Reads the layout definitions from +config/alchemy/page_layouts.yml+.
85
+ #
86
+ def read_definitions_file
87
+ if File.exist?(layouts_file_path)
88
+ Array.wrap(
89
+ YAML.safe_load(
90
+ ERB.new(File.read(layouts_file_path)).result,
91
+ permitted_classes: YAML_PERMITTED_CLASSES,
92
+ aliases: true
93
+ ) || []
94
+ )
95
+ else
96
+ raise LoadError, "Could not find page_layouts.yml file! Please run `rails generate alchemy:install`"
97
+ end
98
+ end
99
+ end
100
+
101
+ def human_name
102
+ Alchemy::Page.human_layout_name(name)
103
+ end
104
+
105
+ def attributes
106
+ super.with_indifferent_access
107
+ end
108
+
109
+ private
110
+
111
+ def hint_translation_scope
112
+ :page_hints
113
+ end
114
+ end
115
+ end
@@ -28,24 +28,10 @@ module Alchemy
28
28
  large: "240x180"
29
29
  }.with_indifferent_access.freeze
30
30
 
31
- CONVERTIBLE_FILE_FORMATS = %w[gif jpg jpeg png webp].freeze
32
-
33
- TRANSFORMATION_OPTIONS = [
34
- :crop,
35
- :crop_from,
36
- :crop_size,
37
- :flatten,
38
- :format,
39
- :quality,
40
- :size,
41
- :upsample
42
- ]
43
-
44
31
  include Alchemy::Logger
45
32
  include Alchemy::NameConversions
46
33
  include Alchemy::Taggable
47
34
  include Alchemy::TouchElements
48
- include Calculations
49
35
 
50
36
  has_many :picture_ingredients,
51
37
  class_name: "Alchemy::Ingredients::Picture",
@@ -54,7 +40,6 @@ module Alchemy
54
40
 
55
41
  has_many :elements, through: :picture_ingredients
56
42
  has_many :pages, through: :elements
57
- has_many :thumbs, class_name: "Alchemy::PictureThumb", dependent: :destroy
58
43
  has_many :descriptions, class_name: "Alchemy::PictureDescription", dependent: :destroy
59
44
 
60
45
  accepts_nested_attributes_for :descriptions, allow_destroy: true, reject_if: ->(attr) { attr[:text].blank? }
@@ -72,7 +57,7 @@ module Alchemy
72
57
 
73
58
  # Image preprocessing class
74
59
  def self.preprocessor_class
75
- @_preprocessor_class ||= Preprocessor
60
+ @_preprocessor_class ||= Alchemy.storage_adapter.preprocessor_class
76
61
  end
77
62
 
78
63
  # Set a image preprocessing class
@@ -84,56 +69,39 @@ module Alchemy
84
69
  @_preprocessor_class = klass
85
70
  end
86
71
 
87
- # Enables Dragonfly image processing
88
- dragonfly_accessor :image_file, app: :alchemy_pictures do
89
- # Preprocess after uploading the picture
90
- after_assign do |image|
91
- if has_convertible_format?
92
- self.class.preprocessor_class.new(image).call
93
- end
94
- end
95
- end
96
-
97
- before_save :sanitize_image_file_name, if: :image_file_name
98
- # Create important thumbnails upfront
99
- after_create -> { PictureThumb.generate_thumbs!(self) if has_convertible_format? }
72
+ include Alchemy.storage_adapter.picture_class_methods
100
73
 
101
74
  # We need to define this method here to have it available in the validations below.
102
75
  class << self
103
76
  def allowed_filetypes
104
- Config.get(:uploader).fetch("allowed_filetypes", {}).fetch("alchemy/pictures", [])
77
+ Alchemy.config.uploader.allowed_filetypes.alchemy_pictures
105
78
  end
106
79
  end
107
80
 
108
81
  validates_presence_of :image_file
109
- validates_size_of :image_file, maximum: Config.get(:uploader)["file_size_limit"].megabytes
110
- validates_property :format,
111
- of: :image_file,
112
- in: allowed_filetypes,
113
- case_sensitive: false,
114
- message: Alchemy.t("not a valid image")
82
+ validates_size_of :image_file, maximum: Alchemy.config.uploader.file_size_limit.megabytes
83
+ validate :image_file_type_allowed, if: -> { image_file.present? }
115
84
 
116
- stampable stamper_class_name: Alchemy.user_class.name
85
+ stampable stamper_class_name: Alchemy.user_class_name
117
86
 
118
87
  scope :named, ->(name) { where("#{table_name}.name LIKE ?", "%#{name}%") }
119
88
  scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
120
- scope :deletable, -> do
121
- where(
122
- "#{table_name}.id NOT IN (SELECT related_object_id FROM alchemy_ingredients WHERE related_object_id IS NOT NULL AND related_object_type = ?)",
123
- name
124
- )
125
- end
89
+ scope :deletable,
90
+ -> {
91
+ where("#{table_name}.id NOT IN (SELECT related_object_id FROM alchemy_ingredients WHERE related_object_type = 'Alchemy::Picture')")
92
+ }
126
93
  scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: {id: nil}) }
127
- scope :by_file_format, ->(format) { where(image_file_format: format) }
94
+ scope :by_file_format, ->(file_format) do
95
+ Alchemy.storage_adapter.by_file_format_scope(file_format)
96
+ end
128
97
 
129
98
  # Class methods
130
99
 
131
100
  class << self
132
101
  # The class used to generate URLs for pictures
133
102
  #
134
- # @see Alchemy::Picture::Url
135
103
  def url_class
136
- @_url_class ||= Alchemy::Picture::Url
104
+ @_url_class ||= Alchemy.storage_adapter.picture_url_class
137
105
  end
138
106
 
139
107
  # Set a different picture url class
@@ -143,22 +111,16 @@ module Alchemy
143
111
  @_url_class = klass
144
112
  end
145
113
 
146
- def alchemy_resource_filters
147
- @_file_formats ||= distinct.pluck(:image_file_format).compact.presence || []
148
- [
149
- {
150
- name: :by_file_format,
151
- values: @_file_formats
152
- },
153
- {
154
- name: :misc,
155
- values: %w[recent last_upload without_tag deletable]
156
- }
157
- ]
114
+ def searchable_alchemy_resource_attributes
115
+ Alchemy.storage_adapter.searchable_alchemy_resource_attributes(name)
116
+ end
117
+
118
+ def ransackable_attributes(_auth_object = nil)
119
+ Alchemy.storage_adapter.ransackable_attributes(name)
158
120
  end
159
121
 
160
- def searchable_alchemy_resource_attributes
161
- %w[name image_file_name]
122
+ def ransackable_associations(_auth_object = nil)
123
+ Alchemy.storage_adapter.ransackable_associations(name)
162
124
  end
163
125
 
164
126
  def last_upload
@@ -167,33 +129,30 @@ module Alchemy
167
129
 
168
130
  Picture.where(upload_hash: last_picture.upload_hash)
169
131
  end
132
+
133
+ def ransackable_scopes(_auth_object = nil)
134
+ [:by_file_format, :recent, :last_upload, :without_tag, :deletable]
135
+ end
136
+
137
+ def file_formats(scope = all)
138
+ Alchemy.storage_adapter.file_formats(name, scope:)
139
+ end
170
140
  end
171
141
 
172
142
  # Instance methods
173
143
 
174
144
  # Returns an url (or relative path) to a processed image for use inside an image_tag helper.
175
145
  #
176
- # Any additional options are passed to the url method, so you can add params to your url.
177
- #
178
146
  # Example:
179
147
  #
180
148
  # <%= image_tag picture.url(size: '320x200', format: 'png') %>
181
149
  #
182
- # @see Alchemy::PictureVariant#call for transformation options
183
- # @see Alchemy::Picture::Url#call for url options
184
150
  # @return [String|Nil]
185
151
  def url(options = {})
186
152
  return unless image_file
187
153
 
188
- variant = PictureVariant.new(self, options.slice(*TRANSFORMATION_OPTIONS))
189
- self.class.url_class.new(variant).call(
190
- options.except(*TRANSFORMATION_OPTIONS).merge(
191
- basename: name,
192
- ext: variant.render_format,
193
- name: name
194
- )
195
- )
196
- rescue ::Dragonfly::Job::Fetch::NotFound => e
154
+ self.class.url_class.new(self).call(options)
155
+ rescue Alchemy.storage_adapter.rescuable_errors => e
197
156
  log_warning(e.message)
198
157
  nil
199
158
  end
@@ -208,7 +167,7 @@ module Alchemy
208
167
 
209
168
  url(
210
169
  flatten: true,
211
- format: image_file_format || "jpg",
170
+ format: image_file_extension || "jpg",
212
171
  size: size
213
172
  )
214
173
  end
@@ -242,18 +201,12 @@ module Alchemy
242
201
  end
243
202
  end
244
203
 
245
- # Returns the suffix of the filename.
246
- #
247
- def suffix
248
- image_file.ext
249
- end
250
-
251
204
  # Returns a humanized, readable name from image filename.
252
205
  #
253
206
  def humanized_name
254
207
  return "" if image_file_name.blank?
255
208
 
256
- convert_to_humanized_name(image_file_name, suffix)
209
+ convert_to_humanized_name(image_file_name, image_file_extension)
257
210
  end
258
211
 
259
212
  # Returns the format the image should be rendered with
@@ -263,9 +216,9 @@ module Alchemy
263
216
  #
264
217
  def default_render_format
265
218
  if convertible?
266
- Config.get(:image_output_format)
219
+ Alchemy.config.image_output_format
267
220
  else
268
- image_file_format
221
+ image_file_extension
269
222
  end
270
223
  end
271
224
 
@@ -275,15 +228,15 @@ module Alchemy
275
228
  # image has not a convertible file format (i.e. SVG) this returns +false+
276
229
  #
277
230
  def convertible?
278
- Config.get(:image_output_format) &&
279
- Config.get(:image_output_format) != "original" &&
231
+ Alchemy.config.image_output_format &&
232
+ Alchemy.config.image_output_format != "original" &&
280
233
  has_convertible_format?
281
234
  end
282
235
 
283
236
  # Returns true if the image can be converted into other formats
284
237
  #
285
238
  def has_convertible_format?
286
- image_file_format.in?(CONVERTIBLE_FILE_FORMATS)
239
+ Alchemy.storage_adapter.has_convertible_format?(self)
287
240
  end
288
241
 
289
242
  # Checks if the picture is restricted.
@@ -304,6 +257,32 @@ module Alchemy
304
257
  picture_ingredients.empty?
305
258
  end
306
259
 
260
+ def image_file_name
261
+ Alchemy.storage_adapter.image_file_name(self)
262
+ end
263
+
264
+ def image_file_format
265
+ Alchemy.storage_adapter.image_file_format(self)
266
+ end
267
+
268
+ def image_file_size
269
+ Alchemy.storage_adapter.image_file_size(self)
270
+ end
271
+
272
+ def image_file_width
273
+ Alchemy.storage_adapter.image_file_width(self)
274
+ end
275
+
276
+ def image_file_height
277
+ Alchemy.storage_adapter.image_file_height(self)
278
+ end
279
+
280
+ def image_file_extension
281
+ Alchemy.storage_adapter.image_file_extension(self)
282
+ end
283
+ alias_method :suffix, :image_file_extension
284
+ deprecate suffix: :image_file_extension, deprecator: Alchemy::Deprecation
285
+
307
286
  # A size String from original image file values.
308
287
  #
309
288
  # == Example
@@ -314,8 +293,12 @@ module Alchemy
314
293
  "#{image_file_width}x#{image_file_height}"
315
294
  end
316
295
 
317
- def sanitize_image_file_name
318
- self.image_file_name = sanitized_filename(image_file_name)
296
+ private
297
+
298
+ def image_file_type_allowed
299
+ unless image_file_extension&.in?(self.class.allowed_filetypes)
300
+ errors.add(:image_file, Alchemy.t("not a valid image"))
301
+ end
319
302
  end
320
303
  end
321
304
  end