alchemy_cms 7.4.5 → 8.0.0.a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (358) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/Gemfile +13 -6
  4. data/README.md +13 -5
  5. data/alchemy_cms.gemspec +14 -5
  6. data/app/assets/builds/alchemy/admin/page-select.css +1 -1
  7. data/app/assets/builds/alchemy/admin/print.css +1 -1
  8. data/app/assets/builds/alchemy/admin.css +2 -2
  9. data/app/assets/builds/alchemy/custom-properties.css +1 -1
  10. data/app/assets/builds/alchemy/welcome.css +1 -1
  11. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  12. data/app/assets/builds/tinymce/skins/ui/alchemy/content.min.css +1 -0
  13. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
  14. data/app/assets/config/alchemy_manifest.js +0 -2
  15. data/app/assets/images/alchemy/icons-sprite.svg +1 -0
  16. data/app/components/alchemy/admin/resource/applied_filter.rb +29 -0
  17. data/app/components/alchemy/admin/resource/checkbox_filter.rb +36 -0
  18. data/app/components/alchemy/admin/resource/datepicker_filter.rb +42 -0
  19. data/app/components/alchemy/admin/resource/select_filter.rb +43 -0
  20. data/app/components/alchemy/admin/toolbar_button.rb +5 -2
  21. data/app/components/alchemy/ingredients/number_view.rb +18 -0
  22. data/app/controllers/alchemy/admin/attachments_controller.rb +8 -15
  23. data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -6
  24. data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
  25. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  26. data/app/controllers/alchemy/admin/pages_controller.rb +15 -15
  27. data/app/controllers/alchemy/admin/picture_descriptions_controller.rb +5 -1
  28. data/app/controllers/alchemy/admin/pictures_controller.rb +10 -5
  29. data/app/controllers/alchemy/admin/resources_controller.rb +16 -106
  30. data/app/controllers/alchemy/attachments_controller.rb +43 -14
  31. data/app/controllers/alchemy/messages_controller.rb +1 -1
  32. data/app/controllers/alchemy/pages_controller.rb +7 -2
  33. data/app/controllers/concerns/alchemy/admin/picture_descriptions_form_helper.rb +16 -0
  34. data/app/controllers/concerns/alchemy/admin/resource_filter.rb +92 -0
  35. data/app/decorators/alchemy/element_editor.rb +5 -48
  36. data/app/decorators/alchemy/ingredient_editor.rb +3 -53
  37. data/app/helpers/alchemy/admin/base_helper.rb +14 -84
  38. data/app/helpers/alchemy/admin/elements_helper.rb +4 -4
  39. data/app/helpers/alchemy/admin/pages_helper.rb +1 -1
  40. data/app/helpers/alchemy/base_helper.rb +0 -30
  41. data/app/helpers/alchemy/elements_block_helper.rb +0 -14
  42. data/app/helpers/alchemy/pages_helper.rb +1 -1
  43. data/{lib → app/helpers}/alchemy/resources_helper.rb +5 -45
  44. data/app/javascript/alchemy_admin/components/action.js +4 -0
  45. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +3 -3
  46. data/app/javascript/alchemy_admin/components/datepicker.js +10 -2
  47. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +7 -7
  48. data/app/javascript/alchemy_admin/components/element_editor.js +1 -1
  49. data/app/javascript/alchemy_admin/components/growl.js +1 -0
  50. data/app/javascript/alchemy_admin/components/index.js +1 -0
  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/utils/ajax.js +51 -44
  63. data/app/javascript/alchemy_admin.js +3 -8
  64. data/app/models/alchemy/admin/filters/base.rb +38 -0
  65. data/app/models/alchemy/admin/filters/checkbox.rb +24 -0
  66. data/app/models/alchemy/admin/filters/datepicker.rb +53 -0
  67. data/app/models/alchemy/admin/filters/select.rb +70 -0
  68. data/app/models/alchemy/admin/resource_name.rb +27 -0
  69. data/app/models/alchemy/attachment.rb +51 -34
  70. data/app/models/alchemy/base_record.rb +2 -0
  71. data/app/models/alchemy/element/definitions.rb +1 -1
  72. data/app/models/alchemy/element/element_ingredients.rb +6 -6
  73. data/app/models/alchemy/element/presenters.rb +3 -12
  74. data/app/models/alchemy/element.rb +9 -27
  75. data/app/models/alchemy/element_definition.rb +160 -0
  76. data/app/models/alchemy/ingredient.rb +10 -43
  77. data/app/models/alchemy/ingredient_definition.rb +134 -0
  78. data/app/models/alchemy/ingredient_validator.rb +7 -3
  79. data/app/models/alchemy/ingredients/number.rb +19 -0
  80. data/app/models/alchemy/language.rb +0 -14
  81. data/app/models/alchemy/message.rb +3 -7
  82. data/app/models/alchemy/node.rb +1 -1
  83. data/app/models/alchemy/page/{page_layouts.rb → definitions.rb} +12 -19
  84. data/app/models/alchemy/page/fixed_attributes.rb +1 -1
  85. data/app/models/alchemy/page/page_elements.rb +13 -14
  86. data/app/models/alchemy/page/page_natures.rb +7 -7
  87. data/app/models/alchemy/page/page_scopes.rb +1 -1
  88. data/app/models/alchemy/page.rb +11 -33
  89. data/app/models/alchemy/page_definition.rb +115 -0
  90. data/app/models/alchemy/picture.rb +69 -79
  91. data/app/models/alchemy/picture_variant.rb +115 -5
  92. data/{lib → app/models}/alchemy/resource.rb +4 -18
  93. data/{lib → app/models}/alchemy/searchable_resource.rb +15 -0
  94. data/app/models/alchemy/site/layout.rb +5 -5
  95. data/app/models/alchemy/site.rb +0 -15
  96. data/app/models/alchemy/storage_adapter/active_storage/attachment_url.rb +41 -0
  97. data/app/models/alchemy/storage_adapter/active_storage/picture_url.rb +55 -0
  98. data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +40 -0
  99. data/app/models/alchemy/storage_adapter/active_storage.rb +173 -0
  100. data/app/models/alchemy/{attachment/url.rb → storage_adapter/dragonfly/attachment_url.rb} +12 -12
  101. data/app/models/alchemy/{picture/url.rb → storage_adapter/dragonfly/picture_url.rb} +28 -12
  102. data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
  103. data/app/models/alchemy/storage_adapter/dragonfly.rb +183 -0
  104. data/app/models/alchemy/storage_adapter.rb +74 -0
  105. data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
  106. data/app/serializers/alchemy/element_serializer.rb +0 -1
  107. data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -0
  108. data/app/stylesheets/alchemy/_defaults.scss +3 -0
  109. data/app/stylesheets/alchemy/_extends.scss +69 -0
  110. data/app/{assets/stylesheets → stylesheets}/alchemy/_mixins.scss +36 -49
  111. data/app/stylesheets/alchemy/_variables.scss +5 -0
  112. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +20 -37
  113. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +16 -14
  114. data/app/stylesheets/alchemy/admin/buttons.scss +160 -0
  115. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/clipboard.scss +2 -2
  116. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dashboard.scss +13 -16
  117. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dialogs.scss +23 -16
  118. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +150 -105
  119. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
  120. data/app/stylesheets/alchemy/admin/filters.scss +58 -0
  121. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +53 -60
  122. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +21 -7
  123. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +31 -19
  124. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -16
  125. data/app/stylesheets/alchemy/admin/hints.scss +5 -0
  126. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +10 -1
  127. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +10 -8
  128. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/images.scss +1 -1
  129. data/app/stylesheets/alchemy/admin/labels.scss +5 -0
  130. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/lists.scss +3 -3
  131. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/navigation.scss +61 -55
  132. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
  133. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +18 -18
  134. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +2 -2
  135. data/app/stylesheets/alchemy/admin/pagination.scss +144 -0
  136. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/preview_window.scss +8 -6
  137. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
  138. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/resource_info.scss +8 -5
  139. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +9 -6
  140. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +49 -37
  141. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +5 -6
  142. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +38 -33
  143. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +6 -4
  144. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tags.scss +6 -4
  145. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +12 -6
  146. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +2 -2
  147. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
  148. data/app/stylesheets/alchemy/admin.scss +44 -0
  149. data/app/stylesheets/alchemy/custom-properties.css +244 -0
  150. data/app/stylesheets/alchemy/welcome.scss +75 -0
  151. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/content/alchemy/content.scss +8 -9
  152. data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
  153. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +133 -136
  154. data/app/views/alchemy/admin/attachments/_files_list.html.erb +2 -2
  155. data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
  156. data/app/views/alchemy/admin/{elements/_clipboard_button.html.erb → clipboard/_button.html.erb} +3 -5
  157. data/app/views/alchemy/admin/clipboard/_update_nested_element_button.turbo_stream.erb +11 -0
  158. data/app/views/alchemy/admin/clipboard/clear.turbo_stream.erb +4 -0
  159. data/app/views/alchemy/admin/clipboard/index.html.erb +15 -13
  160. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +18 -0
  161. data/app/views/alchemy/admin/clipboard/remove.turbo_stream.erb +9 -0
  162. data/app/views/alchemy/admin/dashboard/info.html.erb +17 -31
  163. data/app/views/alchemy/admin/elements/_element.html.erb +4 -8
  164. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  165. data/app/views/alchemy/admin/elements/_header.html.erb +1 -0
  166. data/app/views/alchemy/admin/elements/_toolbar.html.erb +4 -6
  167. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +2 -1
  168. data/app/views/alchemy/admin/elements/index.html.erb +2 -2
  169. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +3 -16
  170. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +0 -9
  171. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  172. data/app/views/alchemy/admin/languages/_table.html.erb +1 -1
  173. data/app/views/alchemy/admin/languages/index.html.erb +5 -2
  174. data/app/views/alchemy/admin/layoutpages/index.html.erb +1 -12
  175. data/app/views/alchemy/admin/pages/_form.html.erb +2 -2
  176. data/app/views/alchemy/admin/pages/_page.html.erb +2 -3
  177. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -15
  178. data/app/views/alchemy/admin/pages/index.html.erb +1 -1
  179. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +9 -12
  180. data/app/views/alchemy/admin/partials/_search_form.html.erb +4 -9
  181. data/app/views/alchemy/admin/picture_descriptions/_form.html.erb +5 -7
  182. data/app/views/alchemy/admin/picture_descriptions/edit.html.erb +2 -2
  183. data/app/views/alchemy/admin/pictures/_archive.html.erb +4 -7
  184. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +2 -1
  185. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -1
  186. data/app/views/alchemy/admin/pictures/_form.html.erb +2 -2
  187. data/app/views/alchemy/admin/pictures/_picture_description_field.html.erb +21 -13
  188. data/app/views/alchemy/admin/pictures/index.html.erb +2 -7
  189. data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
  190. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +11 -21
  191. data/app/views/alchemy/admin/resources/_pagination.html.erb +6 -0
  192. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +4 -2
  193. data/app/views/alchemy/admin/resources/_resource_table.html.erb +1 -1
  194. data/app/views/alchemy/admin/resources/_table_header.html.erb +1 -15
  195. data/app/views/alchemy/admin/sites/index.html.erb +5 -1
  196. data/app/views/alchemy/admin/styleguide/index.html.erb +8 -0
  197. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  198. data/app/views/alchemy/admin/tinymce/_setup.html.erb +7 -7
  199. data/app/{javascript/alchemy_admin/locales/en.js → views/alchemy/admin/translations/_en.js} +5 -2
  200. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
  201. data/app/views/alchemy/admin/uploader/_setup.html.erb +4 -4
  202. data/app/views/alchemy/base/500.turbo_stream.erb +3 -0
  203. data/app/views/alchemy/base/redirect.js.erb +1 -1
  204. data/app/views/alchemy/ingredients/_number_editor.html.erb +24 -0
  205. data/app/views/alchemy/no_index.html.erb +31 -0
  206. data/app/views/alchemy/welcome.html.erb +12 -10
  207. data/app/views/kaminari/alchemy/_first_page.html.erb +5 -3
  208. data/app/views/kaminari/alchemy/_last_page.html.erb +5 -3
  209. data/app/views/kaminari/alchemy/_next_page.html.erb +5 -3
  210. data/app/views/kaminari/alchemy/_paginator.html.erb +18 -13
  211. data/app/views/kaminari/alchemy/_prev_page.html.erb +5 -3
  212. data/app/views/layouts/alchemy/admin.html.erb +5 -9
  213. data/bun.lockb +0 -0
  214. data/bundles/remixicon.mjs +153 -0
  215. data/config/alchemy/config.yml +3 -2
  216. data/config/initializers/dragonfly.rb +0 -1
  217. data/config/initializers/mime_types.rb +1 -0
  218. data/config/locales/alchemy.en.yml +32 -14
  219. data/config/routes.rb +0 -2
  220. data/db/migrate/20250626160259_add_unique_index_to_picture_descriptions.rb +7 -0
  221. data/eslint.config.js +2 -1
  222. data/lib/alchemy/admin/preview_url.rb +4 -5
  223. data/lib/alchemy/cache_digests/template_tracker.rb +6 -9
  224. data/lib/alchemy/config_missing.rb +14 -0
  225. data/lib/alchemy/configuration/base_option.rb +24 -0
  226. data/lib/alchemy/configuration/boolean_option.rb +16 -0
  227. data/lib/alchemy/configuration/class_option.rb +15 -0
  228. data/lib/alchemy/configuration/class_set_option.rb +46 -0
  229. data/lib/alchemy/configuration/integer_list_option.rb +13 -0
  230. data/lib/alchemy/configuration/integer_option.rb +12 -0
  231. data/lib/alchemy/configuration/list_option.rb +22 -0
  232. data/lib/alchemy/configuration/regexp_option.rb +11 -0
  233. data/lib/alchemy/configuration/string_list_option.rb +13 -0
  234. data/lib/alchemy/configuration/string_option.rb +11 -0
  235. data/lib/alchemy/configuration.rb +115 -0
  236. data/lib/alchemy/configuration_methods.rb +3 -1
  237. data/lib/alchemy/configurations/default_language.rb +12 -0
  238. data/lib/alchemy/configurations/default_site.rb +10 -0
  239. data/lib/alchemy/configurations/format_matchers.rb +11 -0
  240. data/lib/alchemy/configurations/mailer.rb +16 -0
  241. data/lib/alchemy/configurations/main.rb +216 -0
  242. data/lib/alchemy/configurations/preview.rb +32 -0
  243. data/lib/alchemy/configurations/sitemap.rb +10 -0
  244. data/lib/alchemy/configurations/uploader.rb +34 -0
  245. data/lib/alchemy/engine.rb +65 -17
  246. data/lib/alchemy/hints.rb +3 -7
  247. data/lib/alchemy/on_page_layout.rb +2 -2
  248. data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
  249. data/lib/alchemy/seeder.rb +2 -2
  250. data/lib/alchemy/tasks/usage.rb +4 -4
  251. data/lib/alchemy/test_support/config_stubbing.rb +1 -7
  252. data/lib/alchemy/test_support/factories/attachment_factory.rb +13 -2
  253. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  254. data/lib/alchemy/test_support/factories/page_factory.rb +2 -3
  255. data/lib/alchemy/test_support/factories/picture_factory.rb +30 -2
  256. data/lib/alchemy/test_support/factories/site_factory.rb +2 -2
  257. data/lib/alchemy/test_support/having_crop_action_examples.rb +2 -2
  258. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +80 -26
  259. data/lib/alchemy/test_support/shared_ingredient_examples.rb +5 -5
  260. data/lib/alchemy/upgrader/.keep +0 -0
  261. data/lib/alchemy/upgrader/eight_zero.rb +14 -0
  262. data/lib/alchemy/upgrader.rb +33 -20
  263. data/lib/alchemy/version.rb +1 -1
  264. data/lib/alchemy.rb +192 -170
  265. data/lib/alchemy_cms.rb +1 -7
  266. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +0 -3
  267. data/lib/generators/alchemy/install/files/_article.html.erb +6 -4
  268. data/lib/generators/alchemy/install/files/alchemy.en.yml +22 -3
  269. data/lib/generators/alchemy/install/files/application.html.erb +5 -0
  270. data/lib/generators/alchemy/install/install_generator.rb +5 -14
  271. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +196 -0
  272. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -1
  273. data/lib/generators/alchemy/install/templates/elements.yml.tt +3 -1
  274. data/lib/generators/alchemy/install/templates/menus.yml.tt +1 -1
  275. data/lib/generators/alchemy/install/templates/page_layouts.yml.tt +2 -2
  276. data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -2
  277. data/lib/tasks/alchemy/assets.rake +14 -0
  278. data/lib/tasks/alchemy/upgrade.rake +12 -47
  279. data/vendor/javascript/tinymce.min.js +1 -1
  280. data/vitest.config.js +21 -0
  281. metadata +184 -180
  282. data/app/assets/builds/alchemy/admin/page-select.css.map +0 -1
  283. data/app/assets/builds/alchemy/admin/print.css.map +0 -1
  284. data/app/assets/builds/alchemy/admin.css.map +0 -1
  285. data/app/assets/builds/alchemy/custom-properties.css.map +0 -1
  286. data/app/assets/builds/alchemy/welcome.css.map +0 -1
  287. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +0 -1
  288. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +0 -1
  289. data/app/assets/javascripts/alchemy/admin.js +0 -10
  290. data/app/assets/stylesheets/alchemy/_defaults.scss +0 -3
  291. data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +0 -45
  292. data/app/assets/stylesheets/alchemy/_deprecation.scss +0 -17
  293. data/app/assets/stylesheets/alchemy/_extends.scss +0 -62
  294. data/app/assets/stylesheets/alchemy/_variables.scss +0 -201
  295. data/app/assets/stylesheets/alchemy/admin/buttons.scss +0 -124
  296. data/app/assets/stylesheets/alchemy/admin/hints.scss +0 -5
  297. data/app/assets/stylesheets/alchemy/admin/labels.scss +0 -3
  298. data/app/assets/stylesheets/alchemy/admin/pagination.scss +0 -92
  299. data/app/assets/stylesheets/alchemy/admin.scss +0 -42
  300. data/app/assets/stylesheets/alchemy/custom-properties.css +0 -98
  301. data/app/assets/stylesheets/alchemy/welcome.scss +0 -57
  302. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.css +0 -711
  303. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.css +0 -705
  304. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.min.css +0 -7
  305. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.min.css +0 -7
  306. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.css +0 -29
  307. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.min.css +0 -7
  308. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.css +0 -677
  309. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.min.css +0 -7
  310. data/app/controllers/alchemy/elements_controller.rb +0 -32
  311. data/app/models/alchemy/element/dom_id.rb +0 -31
  312. data/app/models/alchemy/picture/calculations.rb +0 -49
  313. data/app/models/alchemy/picture/transformations.rb +0 -115
  314. data/app/views/alchemy/admin/attachments/destroy.js.erb +0 -1
  315. data/app/views/alchemy/admin/clipboard/clear.js.erb +0 -3
  316. data/app/views/alchemy/admin/clipboard/insert.js.erb +0 -29
  317. data/app/views/alchemy/admin/clipboard/remove.js.erb +0 -10
  318. data/app/views/alchemy/admin/resources/_filter.html.erb +0 -12
  319. data/app/views/alchemy/admin/resources/_resource.html.erb +0 -34
  320. data/app/views/alchemy/admin/resources/_table.html.erb +0 -29
  321. data/app/views/alchemy/elements/show.html.erb +0 -1
  322. data/app/views/alchemy/elements/show.js.erb +0 -1
  323. data/app/views/alchemy/ingredients/_audio_view.html.erb +0 -1
  324. data/app/views/alchemy/ingredients/_boolean_view.html.erb +0 -1
  325. data/app/views/alchemy/ingredients/_datetime_view.html.erb +0 -3
  326. data/app/views/alchemy/ingredients/_file_view.html.erb +0 -4
  327. data/app/views/alchemy/ingredients/_headline_view.html.erb +0 -4
  328. data/app/views/alchemy/ingredients/_html_view.html.erb +0 -1
  329. data/app/views/alchemy/ingredients/_link_view.html.erb +0 -4
  330. data/app/views/alchemy/ingredients/_node_view.html.erb +0 -1
  331. data/app/views/alchemy/ingredients/_page_view.html.erb +0 -1
  332. data/app/views/alchemy/ingredients/_picture_view.html.erb +0 -4
  333. data/app/views/alchemy/ingredients/_richtext_view.html.erb +0 -3
  334. data/app/views/alchemy/ingredients/_select_view.html.erb +0 -1
  335. data/app/views/alchemy/ingredients/_text_view.html.erb +0 -4
  336. data/app/views/alchemy/ingredients/_video_view.html.erb +0 -3
  337. data/babel.config.js +0 -12
  338. data/config/initializers/assets.rb +0 -4
  339. data/lib/alchemy/config.rb +0 -114
  340. data/lib/alchemy/element_definition.rb +0 -73
  341. data/lib/alchemy/page_layout.rb +0 -73
  342. data/lib/alchemy/resource_filter.rb +0 -40
  343. data/lib/alchemy/upgrader/seven_point_four.rb +0 -26
  344. data/lib/alchemy/upgrader/seven_point_three.rb +0 -52
  345. data/lib/generators/alchemy/ingredient/templates/view.html.erb +0 -1
  346. data/lib/generators/alchemy/install/files/alchemy_admin.js +0 -1
  347. data/lib/generators/alchemy/install/files/all.js +0 -11
  348. data/lib/generators/alchemy/install/files/article.css +0 -25
  349. data/vendor/assets/images/remixicon.symbol.svg +0 -11
  350. /data/app/{assets/stylesheets → stylesheets}/alchemy/_fonts.scss +0 -0
  351. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachment-select.scss +0 -0
  352. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachments.scss +0 -0
  353. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flash.scss +0 -0
  354. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/list_filter.scss +0 -0
  355. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/node-select.scss +0 -0
  356. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/spinner.scss +0 -0
  357. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/skintool.json +0 -0
  358. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/fonts/tinymce-mobile.woff +0 -0
@@ -21,6 +21,21 @@ module Alchemy
21
21
  searchable_alchemy_resource_associations
22
22
  end
23
23
 
24
+ # Allow all scopes used in resource filters to be used with Ransack
25
+ def ransackable_scopes(_auth_object = nil)
26
+ if respond_to?(:alchemy_resource_filters)
27
+ alchemy_resource_filters.flat_map do |filter_set|
28
+ if respond_to?(filter_set[:name])
29
+ filter_set[:name].to_s
30
+ else
31
+ filter_set[:values].map(&:to_s)
32
+ end
33
+ end
34
+ else
35
+ []
36
+ end
37
+ end
38
+
24
39
  protected
25
40
 
26
41
  def searchable_alchemy_resource_attributes
@@ -41,8 +41,8 @@ module Alchemy
41
41
  #
42
42
  def page_layout_names(layoutpages: false)
43
43
  page_layout_definitions.select do |layout|
44
- !!layout["layoutpage"] && layoutpages || !layout["layoutpage"] && !layoutpages
45
- end.collect { |layout| layout["name"] }
44
+ !!layout.layoutpage && layoutpages || !layout.layoutpage && !layoutpages
45
+ end.tap { _1.collect!(&:name) }
46
46
  end
47
47
 
48
48
  # Returns sites page layout definitions
@@ -51,11 +51,11 @@ module Alchemy
51
51
  #
52
52
  def page_layout_definitions
53
53
  if definition["page_layouts"].presence
54
- Alchemy::PageLayout.all.select do |layout|
55
- layout["name"].in?(definition["page_layouts"])
54
+ Alchemy::PageDefinition.all.select do |layout|
55
+ layout.name.in?(definition["page_layouts"])
56
56
  end
57
57
  else
58
- Alchemy::PageLayout.all
58
+ Alchemy::PageDefinition.all
59
59
  end
60
60
  end
61
61
 
@@ -57,21 +57,6 @@ module Alchemy
57
57
  end
58
58
 
59
59
  class << self
60
- # @deprecated Use {Alchemy::Current#site=} instead.
61
- def current=(site)
62
- Current.site = site
63
- end
64
- deprecate "current=": :"Alchemy::Current.site=", deprecator: Alchemy::Deprecation
65
-
66
- # @deprecated Use {Alchemy::Current#site} instead.
67
- def current
68
- Current.site
69
- end
70
- deprecate current: :"Alchemy::Current.site", deprecator: Alchemy::Deprecation
71
-
72
- alias_method :default, :first
73
- deprecate default: :first, deprecator: Alchemy::Deprecation
74
-
75
60
  def find_for_host(host)
76
61
  # These are split up into two separate queries in order to run the
77
62
  # fastest query first (selecting the domain by its primary host name).
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class StorageAdapter
5
+ # The class representing an URL to an ActiveStorage attachment
6
+ #
7
+ class ActiveStorage::AttachmentUrl
8
+ attr_reader :attachment
9
+
10
+ def initialize(attachment)
11
+ @attachment = attachment
12
+ end
13
+
14
+ # The attachment url
15
+ #
16
+ # @param [Hash] options
17
+ # @option options [Symbol] :download return a URL for downloading the attachment
18
+ # @option options [Symbol] :name The filename
19
+ # @option options [Symbol] :format The file extension
20
+ #
21
+ # @return [String]
22
+ #
23
+ def call(options = {})
24
+ if attachment.file.attached?
25
+ options[:format] ||= attachment.suffix
26
+ if options.delete(:download)
27
+ routes.download_attachment_path(attachment, options)
28
+ else
29
+ routes.show_attachment_path(attachment, options)
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def routes
37
+ Alchemy::Engine.routes.url_helpers
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class StorageAdapter
5
+ # Returns the URL to a variant of a picture using ActiveStorage
6
+ class ActiveStorage::PictureUrl
7
+ attr_reader :picture, :image_file
8
+
9
+ # @param [Alchemy::Picture]
10
+ #
11
+ def initialize(picture)
12
+ @picture = picture
13
+ @image_file = picture.image_file
14
+ end
15
+
16
+ # The URL to a variant of a picture
17
+ #
18
+ # @return [String]
19
+ #
20
+ def call(options = {})
21
+ variant_options = DragonflyToImageProcessing.call(options)
22
+ variant_options[:format] = options[:format] || default_output_format
23
+ variant = image_file&.variant(variant_options)
24
+ return unless variant
25
+
26
+ Rails.application.routes.url_helpers.rails_blob_path(
27
+ variant,
28
+ {
29
+ filename: filename(options),
30
+ format: variant_options[:format],
31
+ only_path: true
32
+ }
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ def filename(options = {})
39
+ if picture.name.presence
40
+ picture.name.to_param
41
+ else
42
+ picture.image_file_name
43
+ end
44
+ end
45
+
46
+ def default_output_format
47
+ if Alchemy.config.image_output_format == "original"
48
+ picture.image_file_extension
49
+ else
50
+ Alchemy.config.image_output_format
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class StorageAdapter
5
+ class ActiveStorage::Preprocessor
6
+ def initialize(attachable)
7
+ @attachable = attachable
8
+ end
9
+
10
+ # Preprocess images after upload
11
+ #
12
+ # Define preprocessing options in the Alchemy.config
13
+ #
14
+ # preprocess_image_resize [String] - Downsizing example: '1000x1000>'
15
+ #
16
+ def call
17
+ max_image_size = Alchemy.config.get(:preprocess_image_resize)
18
+ if max_image_size.present?
19
+ self.class.process_thumb(attachable, size: max_image_size)
20
+ end
21
+ end
22
+
23
+ attr_reader :attachable
24
+
25
+ class << self
26
+ def generate_thumbs!(attachable)
27
+ Alchemy::Picture::THUMBNAIL_SIZES.values.each do |size|
28
+ process_thumb(attachable, size: size, flatten: true)
29
+ end
30
+ end
31
+
32
+ def process_thumb(attachable, options = {})
33
+ attachable.variant :thumb,
34
+ **Alchemy::DragonflyToImageProcessing.call(options),
35
+ preprocessed: true
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,173 @@
1
+ module Alchemy
2
+ class StorageAdapter
3
+ module ActiveStorage
4
+ module PictureClassMethods
5
+ def self.included(base)
6
+ base.has_one_attached :image_file do |attachable|
7
+ # Only works in Rails 7.1+
8
+ # https://github.com/rails/rails/pull/47473
9
+ Alchemy.storage_adapter.preprocessor_class.new(attachable).call
10
+ Alchemy.storage_adapter.preprocessor_class.generate_thumbs!(attachable)
11
+ end
12
+ end
13
+ end
14
+
15
+ module AttachmentClassMethods
16
+ def self.included(base)
17
+ base.has_one_attached :file
18
+ end
19
+ end
20
+
21
+ extend self
22
+
23
+ def attachment_url_class
24
+ AttachmentUrl
25
+ end
26
+
27
+ def preprocessor_class
28
+ Preprocessor
29
+ end
30
+
31
+ def picture_url_class
32
+ PictureUrl
33
+ end
34
+
35
+ def file_formats(class_name, scope:)
36
+ attachment_scope = case class_name
37
+ when "Alchemy::Attachment" then scope.with_attached_file
38
+ when "Alchemy::Picture" then scope.with_attached_image_file
39
+ end
40
+
41
+ attachment_scope.pluck("active_storage_blobs.content_type").uniq.tap(&:compact!).presence || []
42
+ end
43
+
44
+ # @param [String]
45
+ # @return [Array<String>]
46
+ def searchable_alchemy_resource_attributes(class_name)
47
+ case class_name
48
+ when "Alchemy::Attachment"
49
+ %w[name file_blob_filename]
50
+ when "Alchemy::Picture"
51
+ %w[name image_file_blob_filename]
52
+ end
53
+ end
54
+
55
+ # @param [String]
56
+ # @return [Array<String>]
57
+ def ransackable_attributes(_class_name)
58
+ %w[name]
59
+ end
60
+
61
+ # @param [String]
62
+ # @return [Array<String>]
63
+ def ransackable_associations(class_name)
64
+ case class_name
65
+ when "Alchemy::Attachment"
66
+ %w[file_blob]
67
+ when "Alchemy::Picture"
68
+ %w[image_file_blob]
69
+ end
70
+ end
71
+
72
+ def rescuable_errors
73
+ ::ActiveStorage::Error
74
+ end
75
+
76
+ # @param [String]
77
+ # @return [Alchemy::Picture::ActiveRecord_Relation]
78
+ def by_file_format_scope(file_format)
79
+ Picture.with_attached_image_file.joins(:image_file_blob).where(active_storage_blobs: {content_type: file_format})
80
+ end
81
+
82
+ # @param [String]
83
+ # @return [Alchemy::Atachment::ActiveRecord_Relation]
84
+ def by_file_type_scope(file_type)
85
+ Attachment.with_attached_file.joins(:file_blob).where(active_storage_blobs: {content_type: file_type})
86
+ end
87
+
88
+ # @param [Alchemy::Attachment]
89
+ # @return [String]
90
+ def file_name(attachment)
91
+ attachment.file&.filename&.to_s
92
+ end
93
+
94
+ # @param [Alchemy::Attachment]
95
+ # @return [Integer]
96
+ def file_size(attachment)
97
+ attachment.file&.byte_size
98
+ end
99
+
100
+ # @param [Alchemy::Attachment]
101
+ # @return [String]
102
+ def file_mime_type(attachment)
103
+ attachment.file&.content_type
104
+ end
105
+
106
+ # @param [Alchemy::Attachment]
107
+ # @return [String]
108
+ def file_extension(attachment)
109
+ attachment.file&.filename&.extension
110
+ end
111
+
112
+ # @param [Alchemy::Picture]
113
+ # @return [TrueClass, FalseClass]
114
+ def has_convertible_format?(picture)
115
+ picture.image_file&.variable?
116
+ end
117
+
118
+ # @param [Alchemy::Picture]
119
+ # @return [String]
120
+ def image_file_name(picture)
121
+ picture.image_file&.filename&.to_s
122
+ end
123
+
124
+ # @param [Alchemy::Picture]
125
+ # @return [String]
126
+ def image_file_format(picture)
127
+ picture.image_file&.content_type
128
+ end
129
+
130
+ # @param [Alchemy::Picture]
131
+ # @return [Integer]
132
+ def image_file_size(picture)
133
+ picture.image_file&.byte_size
134
+ end
135
+
136
+ # @param [Alchemy::Picture]
137
+ # @return [Integer]
138
+ def image_file_width(picture)
139
+ picture.image_file&.metadata&.fetch(:width, nil)
140
+ end
141
+
142
+ # @param [Alchemy::Picture]
143
+ # @return [Integer]
144
+ def image_file_height(picture)
145
+ picture.image_file&.metadata&.fetch(:height, nil)
146
+ end
147
+
148
+ # @param [Alchemy::Picture]
149
+ # @return [Integer]
150
+ def image_file_extension(picture)
151
+ picture.image_file&.filename&.extension&.downcase
152
+ end
153
+
154
+ # @param [Alchemy::Picture]
155
+ # @return [TrueClass, FalseClass]
156
+ def image_file_present?(picture)
157
+ picture.image_file.attached?
158
+ end
159
+
160
+ # @param Alchemy::Picture::ActiveRecord_Relation
161
+ # @return Alchemy::Picture::ActiveRecord_Relation
162
+ def preloaded_pictures(pictures)
163
+ pictures.with_attached_image_file
164
+ end
165
+
166
+ # @param [Alchemy::Attachment]
167
+ # @return [TrueClass, FalseClass]
168
+ def set_attachment_name?(attachment)
169
+ attachment.file.changed?
170
+ end
171
+ end
172
+ end
173
+ end
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- class Attachment < BaseRecord
5
- # The class representing an URL to an attachment
4
+ class StorageAdapter
5
+ # The class representing an URL to an Dragonfly attachment
6
6
  #
7
- # Set a different one
8
- #
9
- # Alchemy::Attachment.url_class = MyRemoteUrlClass
10
- #
11
- class Url
7
+ class Dragonfly::AttachmentUrl
8
+ attr_reader :attachment
9
+
12
10
  def initialize(attachment)
13
11
  @attachment = attachment
14
12
  end
@@ -23,11 +21,13 @@ module Alchemy
23
21
  # @return [String]
24
22
  #
25
23
  def call(options = {})
26
- options[:format] ||= @attachment.suffix
27
- if options.delete(:download)
28
- routes.download_attachment_path(@attachment, options)
29
- else
30
- routes.show_attachment_path(@attachment, options)
24
+ if attachment.file
25
+ options[:format] ||= attachment.suffix
26
+ if options.delete(:download)
27
+ routes.download_attachment_path(attachment, options)
28
+ else
29
+ routes.show_attachment_path(attachment, options)
30
+ end
31
31
  end
32
32
  end
33
33
 
@@ -1,23 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- class Picture < BaseRecord
5
- class Url
6
- attr_reader :variant, :thumb
4
+ class StorageAdapter
5
+ class Dragonfly::PictureUrl
6
+ TRANSFORMATION_OPTIONS = [
7
+ :crop,
8
+ :crop_from,
9
+ :crop_size,
10
+ :flatten,
11
+ :format,
12
+ :quality,
13
+ :size,
14
+ :upsample
15
+ ]
7
16
 
8
- # @param [Alchemy::PictureVariant]
9
- #
10
- def initialize(variant)
11
- raise ArgumentError, "Variant missing!" if variant.nil?
17
+ attr_reader :picture, :variant, :thumb
12
18
 
13
- @variant = variant
19
+ # @param [Alchemy::Picture]
20
+ #
21
+ def initialize(picture)
22
+ @picture = picture
14
23
  end
15
24
 
16
25
  # The URL to a variant of a picture
17
26
  #
18
27
  # @return [String]
19
28
  #
20
- def call(params = {})
29
+ def call(options = {})
30
+ @variant = PictureVariant.new(picture, options.slice(*TRANSFORMATION_OPTIONS))
31
+ params = {
32
+ basename: picture.name,
33
+ ext: variant.render_format,
34
+ name: picture.name
35
+ }
36
+
21
37
  return variant.image.url(params) unless processible_image?
22
38
 
23
39
  "/#{uid}"
@@ -43,10 +59,10 @@ module Alchemy
43
59
  end
44
60
 
45
61
  def find_thumb_by(signature)
46
- @thumb = if variant.picture.thumbs.loaded?
47
- variant.picture.thumbs.find { |t| t.signature == signature }
62
+ @thumb = if picture.thumbs.loaded?
63
+ picture.thumbs.find { |t| t.signature == signature }
48
64
  else
49
- variant.picture.thumbs.find_by(signature: signature)
65
+ picture.thumbs.find_by(signature: signature)
50
66
  end
51
67
  end
52
68
  end
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- class Picture < BaseRecord
5
- class Preprocessor
4
+ class StorageAdapter
5
+ class Dragonfly::Preprocessor
6
6
  def initialize(image_file)
7
7
  @image_file = image_file
8
8
  end
9
9
 
10
10
  # Preprocess images after upload
11
11
  #
12
- # Define preprocessing options in the Alchemy::Config
12
+ # Define preprocessing options in the Alchemy.config
13
13
  #
14
14
  # preprocess_image_resize [String] - Downsizing example: '1000x1000>'
15
15
  #
16
16
  def call
17
- max_image_size = Alchemy::Config.get(:preprocess_image_resize)
17
+ max_image_size = Alchemy.config.preprocess_image_resize
18
18
  image_file.thumb!(max_image_size) if max_image_size.present?
19
19
  # Auto orient the image so EXIF orientation data is taken into account
20
20
  image_file.auto_orient!
@@ -0,0 +1,183 @@
1
+ module Alchemy
2
+ class StorageAdapter
3
+ module Dragonfly
4
+ module PictureClassMethods
5
+ def self.included(base)
6
+ base.class_eval do
7
+ dragonfly_accessor :image_file, app: :alchemy_pictures do
8
+ # Preprocess after uploading the picture
9
+ after_assign do |image|
10
+ if has_convertible_format?
11
+ Alchemy.storage_adapter.preprocessor_class.new(image).call
12
+ end
13
+ end
14
+ end
15
+
16
+ has_many :thumbs, class_name: "Alchemy::PictureThumb", dependent: :destroy
17
+
18
+ # Create important thumbnails upfront
19
+ after_create -> { PictureThumb.generate_thumbs!(self) },
20
+ if: :has_convertible_format?
21
+ end
22
+ end
23
+ end
24
+
25
+ module AttachmentClassMethods
26
+ def self.included(base)
27
+ base.class_eval do
28
+ dragonfly_accessor :file, app: :alchemy_attachments do
29
+ after_assign { |file|
30
+ write_attribute(:file_mime_type, file.mime_type)
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ extend self
38
+
39
+ CONVERTIBLE_FILE_FORMATS = %w[gif jpg jpeg png webp].freeze
40
+
41
+ def attachment_url_class
42
+ AttachmentUrl
43
+ end
44
+
45
+ def preprocessor_class
46
+ Preprocessor
47
+ end
48
+
49
+ def picture_url_class
50
+ PictureUrl
51
+ end
52
+
53
+ def file_formats(class_name, scope:)
54
+ mime_type_column = case class_name
55
+ when "Alchemy::Attachment" then :file_mime_type
56
+ when "Alchemy::Picture" then :image_file_format
57
+ end
58
+
59
+ scope.reorder(mime_type_column).distinct.pluck(mime_type_column).compact.presence || []
60
+ end
61
+
62
+ # @param [String]
63
+ # @return [Array<String>]
64
+ def searchable_alchemy_resource_attributes(class_name)
65
+ case class_name
66
+ when "Alchemy::Attachment"
67
+ %w[name file_name]
68
+ when "Alchemy::Picture"
69
+ %w[name image_file_name]
70
+ end
71
+ end
72
+ alias_method :ransackable_attributes, :searchable_alchemy_resource_attributes
73
+
74
+ # @param [String]
75
+ # @return [Array<String>]
76
+ def ransackable_associations(_class_name)
77
+ %w[]
78
+ end
79
+
80
+ def rescuable_errors
81
+ ::Dragonfly::Job::Fetch::NotFound
82
+ end
83
+
84
+ # @param [String]
85
+ # @return [Alchemy::Picture::ActiveRecord_Relation]
86
+ def by_file_format_scope(file_format)
87
+ Picture.where(image_file_format: file_format)
88
+ end
89
+
90
+ # @param [String]
91
+ # @return [Alchemy::Attachment::ActiveRecord_Relation]
92
+ def by_file_type_scope(file_type)
93
+ Attachment.where(file_mime_type: file_type)
94
+ end
95
+
96
+ # @param [Alchemy::Attachment]
97
+ # @return [String]
98
+ def file_name(attachment)
99
+ attachment.read_attribute(:file_name)
100
+ end
101
+
102
+ # @param [Alchemy::Attachment]
103
+ # @return [Integer]
104
+ def file_size(attachment)
105
+ attachment.read_attribute(:file_size)
106
+ end
107
+
108
+ # @param [Alchemy::Attachment]
109
+ # @return [String]
110
+ def file_mime_type(attachment)
111
+ attachment.read_attribute(:file_mime_type)
112
+ end
113
+
114
+ # @param [Alchemy::Attachment]
115
+ # @return [String]
116
+ def file_extension(attachment)
117
+ content_type = file_mime_type(attachment)
118
+ Marcel::Magic.new(content_type).extensions.first if content_type
119
+ end
120
+
121
+ # @param [Alchemy::Picture]
122
+ # @return [TrueClass, FalseClass]
123
+ def has_convertible_format?(picture)
124
+ image_file_extension(picture).in?(CONVERTIBLE_FILE_FORMATS)
125
+ end
126
+
127
+ # @param [Alchemy::Picture]
128
+ # @return [String]
129
+ def image_file_name(picture)
130
+ picture.read_attribute(:image_file_name)
131
+ end
132
+
133
+ # @param [Alchemy::Picture]
134
+ # @return [String]
135
+ def image_file_format(picture)
136
+ ext = picture.read_attribute(:image_file_format)
137
+ Marcel::MimeType.for(extension: ext) if ext
138
+ end
139
+
140
+ # @param [Alchemy::Picture]
141
+ # @return [Integer]
142
+ def image_file_size(picture)
143
+ picture.read_attribute(:image_file_size)
144
+ end
145
+
146
+ # @param [Alchemy::Picture]
147
+ # @return [Integer]
148
+ def image_file_width(picture)
149
+ picture.read_attribute(:image_file_width)
150
+ end
151
+
152
+ # @param [Alchemy::Picture]
153
+ # @return [Integer]
154
+ def image_file_height(picture)
155
+ picture.read_attribute(:image_file_height)
156
+ end
157
+
158
+ # @param [Alchemy::Picture]
159
+ # @return [String]
160
+ def image_file_extension(picture)
161
+ picture.read_attribute(:image_file_format)
162
+ end
163
+
164
+ # @param [Alchemy::Picture]
165
+ # @return [TrueClass, FalseClass]
166
+ def image_file_present?(picture)
167
+ !!picture.image_file
168
+ end
169
+
170
+ # @param Alchemy::Picture::ActiveRecord_Relation
171
+ # @return Alchemy::Picture::ActiveRecord_Relation
172
+ def preloaded_pictures(pictures)
173
+ pictures.includes(:thumbs)
174
+ end
175
+
176
+ # @param [Alchemy::Attachment]
177
+ # @return [TrueClass, FalseClass]
178
+ def set_attachment_name?(attachment)
179
+ attachment.file_name_changed?
180
+ end
181
+ end
182
+ end
183
+ end