alchemy_cms 7.4.10 → 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 (356) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -28
  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/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 -1
  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/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/resource_filter.rb +92 -0
  34. data/app/decorators/alchemy/element_editor.rb +5 -48
  35. data/app/decorators/alchemy/ingredient_editor.rb +3 -53
  36. data/app/helpers/alchemy/admin/base_helper.rb +14 -84
  37. data/app/helpers/alchemy/admin/elements_helper.rb +4 -4
  38. data/app/helpers/alchemy/admin/pages_helper.rb +1 -1
  39. data/app/helpers/alchemy/base_helper.rb +0 -30
  40. data/app/helpers/alchemy/elements_block_helper.rb +0 -14
  41. data/app/helpers/alchemy/pages_helper.rb +2 -2
  42. data/{lib → app/helpers}/alchemy/resources_helper.rb +5 -45
  43. data/app/javascript/alchemy_admin/components/action.js +2 -0
  44. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +3 -3
  45. data/app/javascript/alchemy_admin/components/datepicker.js +10 -2
  46. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +7 -7
  47. data/app/javascript/alchemy_admin/components/element_editor.js +1 -1
  48. data/app/javascript/alchemy_admin/components/index.js +1 -3
  49. data/app/javascript/alchemy_admin/components/remote_select.js +4 -1
  50. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +5 -1
  51. data/app/javascript/alchemy_admin/components/tinymce.js +4 -2
  52. data/app/javascript/alchemy_admin/components/update_check.js +42 -0
  53. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +15 -8
  54. data/app/javascript/alchemy_admin/components/uploader/progress.js +12 -6
  55. data/app/javascript/alchemy_admin/components/uploader.js +4 -2
  56. data/app/javascript/alchemy_admin/confirm_dialog.js +27 -57
  57. data/app/javascript/alchemy_admin/dirty.js +3 -2
  58. data/app/javascript/alchemy_admin/i18n.js +15 -16
  59. data/app/javascript/alchemy_admin/initializer.js +1 -49
  60. data/app/javascript/alchemy_admin/utils/ajax.js +51 -44
  61. data/app/javascript/alchemy_admin.js +3 -8
  62. data/app/models/alchemy/admin/filters/base.rb +38 -0
  63. data/app/models/alchemy/admin/filters/checkbox.rb +24 -0
  64. data/app/models/alchemy/admin/filters/datepicker.rb +53 -0
  65. data/app/models/alchemy/admin/filters/select.rb +70 -0
  66. data/app/models/alchemy/admin/resource_name.rb +27 -0
  67. data/app/models/alchemy/attachment.rb +49 -37
  68. data/app/models/alchemy/base_record.rb +2 -0
  69. data/app/models/alchemy/element/definitions.rb +1 -1
  70. data/app/models/alchemy/element/element_ingredients.rb +6 -6
  71. data/app/models/alchemy/element/presenters.rb +3 -12
  72. data/app/models/alchemy/element.rb +9 -27
  73. data/app/models/alchemy/element_definition.rb +160 -0
  74. data/app/models/alchemy/ingredient.rb +11 -44
  75. data/app/models/alchemy/ingredient_definition.rb +134 -0
  76. data/app/models/alchemy/ingredient_validator.rb +7 -3
  77. data/app/models/alchemy/ingredients/number.rb +19 -0
  78. data/app/models/alchemy/language.rb +0 -14
  79. data/app/models/alchemy/message.rb +3 -7
  80. data/app/models/alchemy/node.rb +1 -1
  81. data/app/models/alchemy/page/{page_layouts.rb → definitions.rb} +12 -19
  82. data/app/models/alchemy/page/fixed_attributes.rb +1 -1
  83. data/app/models/alchemy/page/page_elements.rb +13 -14
  84. data/app/models/alchemy/page/page_naming.rb +0 -1
  85. data/app/models/alchemy/page/page_natures.rb +7 -7
  86. data/app/models/alchemy/page/page_scopes.rb +1 -1
  87. data/app/models/alchemy/page.rb +11 -33
  88. data/app/models/alchemy/page_definition.rb +115 -0
  89. data/app/models/alchemy/picture.rb +71 -88
  90. data/app/models/alchemy/picture_variant.rb +115 -5
  91. data/{lib → app/models}/alchemy/resource.rb +4 -18
  92. data/{lib → app/models}/alchemy/searchable_resource.rb +15 -0
  93. data/app/models/alchemy/site/layout.rb +5 -6
  94. data/app/models/alchemy/site.rb +0 -15
  95. data/app/models/alchemy/storage_adapter/active_storage/attachment_url.rb +41 -0
  96. data/app/models/alchemy/storage_adapter/active_storage/picture_url.rb +55 -0
  97. data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +40 -0
  98. data/app/models/alchemy/storage_adapter/active_storage.rb +173 -0
  99. data/app/models/alchemy/{attachment/url.rb → storage_adapter/dragonfly/attachment_url.rb} +12 -12
  100. data/app/models/alchemy/{picture/url.rb → storage_adapter/dragonfly/picture_url.rb} +28 -12
  101. data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
  102. data/app/models/alchemy/storage_adapter/dragonfly.rb +183 -0
  103. data/app/models/alchemy/storage_adapter.rb +74 -0
  104. data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
  105. data/app/serializers/alchemy/element_serializer.rb +0 -1
  106. data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -0
  107. data/app/stylesheets/alchemy/_defaults.scss +3 -0
  108. data/app/stylesheets/alchemy/_extends.scss +69 -0
  109. data/app/{assets/stylesheets → stylesheets}/alchemy/_mixins.scss +33 -49
  110. data/app/stylesheets/alchemy/_variables.scss +5 -0
  111. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +20 -37
  112. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +16 -14
  113. data/app/stylesheets/alchemy/admin/buttons.scss +160 -0
  114. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/clipboard.scss +2 -2
  115. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dashboard.scss +13 -16
  116. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dialogs.scss +23 -16
  117. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +150 -105
  118. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
  119. data/app/stylesheets/alchemy/admin/filters.scss +58 -0
  120. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +53 -60
  121. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +21 -7
  122. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +31 -19
  123. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -16
  124. data/app/stylesheets/alchemy/admin/hints.scss +5 -0
  125. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +10 -1
  126. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +10 -8
  127. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/images.scss +1 -1
  128. data/app/stylesheets/alchemy/admin/labels.scss +5 -0
  129. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/lists.scss +3 -3
  130. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/navigation.scss +61 -55
  131. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
  132. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +18 -18
  133. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +2 -2
  134. data/app/stylesheets/alchemy/admin/pagination.scss +144 -0
  135. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/preview_window.scss +8 -6
  136. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
  137. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/resource_info.scss +8 -5
  138. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +9 -6
  139. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +49 -37
  140. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +5 -6
  141. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +38 -33
  142. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +6 -4
  143. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tags.scss +6 -4
  144. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +12 -6
  145. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +2 -2
  146. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
  147. data/app/stylesheets/alchemy/admin.scss +44 -0
  148. data/app/stylesheets/alchemy/custom-properties.css +244 -0
  149. data/app/stylesheets/alchemy/welcome.scss +75 -0
  150. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/content/alchemy/content.scss +8 -9
  151. data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
  152. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +133 -136
  153. data/app/views/alchemy/admin/attachments/_files_list.html.erb +2 -2
  154. data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
  155. data/app/views/alchemy/admin/{elements/_clipboard_button.html.erb → clipboard/_button.html.erb} +3 -5
  156. data/app/views/alchemy/admin/clipboard/_update_nested_element_button.turbo_stream.erb +11 -0
  157. data/app/views/alchemy/admin/clipboard/clear.turbo_stream.erb +4 -0
  158. data/app/views/alchemy/admin/clipboard/index.html.erb +15 -13
  159. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +18 -0
  160. data/app/views/alchemy/admin/clipboard/remove.turbo_stream.erb +9 -0
  161. data/app/views/alchemy/admin/dashboard/info.html.erb +17 -31
  162. data/app/views/alchemy/admin/elements/_element.html.erb +4 -8
  163. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  164. data/app/views/alchemy/admin/elements/_header.html.erb +1 -0
  165. data/app/views/alchemy/admin/elements/_toolbar.html.erb +4 -6
  166. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +2 -1
  167. data/app/views/alchemy/admin/elements/index.html.erb +2 -2
  168. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +3 -16
  169. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +1 -10
  170. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  171. data/app/views/alchemy/admin/languages/_table.html.erb +1 -1
  172. data/app/views/alchemy/admin/languages/index.html.erb +5 -2
  173. data/app/views/alchemy/admin/layoutpages/index.html.erb +1 -12
  174. data/app/views/alchemy/admin/pages/_form.html.erb +2 -2
  175. data/app/views/alchemy/admin/pages/_page.html.erb +2 -3
  176. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -15
  177. data/app/views/alchemy/admin/pages/index.html.erb +1 -1
  178. data/app/views/alchemy/admin/pages/info.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 -10
  181. data/app/views/alchemy/admin/pictures/_archive.html.erb +4 -7
  182. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +2 -1
  183. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -1
  184. data/app/views/alchemy/admin/pictures/index.html.erb +2 -7
  185. data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
  186. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +11 -21
  187. data/app/views/alchemy/admin/resources/_pagination.html.erb +6 -0
  188. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +4 -2
  189. data/app/views/alchemy/admin/resources/_resource_table.html.erb +1 -1
  190. data/app/views/alchemy/admin/resources/_table_header.html.erb +1 -15
  191. data/app/views/alchemy/admin/sites/index.html.erb +5 -1
  192. data/app/views/alchemy/admin/styleguide/index.html.erb +8 -0
  193. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  194. data/app/views/alchemy/admin/tinymce/_setup.html.erb +7 -7
  195. data/app/{javascript/alchemy_admin/locales/en.js → views/alchemy/admin/translations/_en.js} +5 -2
  196. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
  197. data/app/views/alchemy/admin/uploader/_setup.html.erb +4 -4
  198. data/app/views/alchemy/ingredients/_number_editor.html.erb +24 -0
  199. data/app/views/alchemy/ingredients/_page_editor.html.erb +1 -0
  200. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +1 -0
  201. data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
  202. data/app/views/alchemy/no_index.html.erb +31 -0
  203. data/app/views/alchemy/welcome.html.erb +12 -10
  204. data/app/views/kaminari/alchemy/_first_page.html.erb +5 -3
  205. data/app/views/kaminari/alchemy/_last_page.html.erb +5 -3
  206. data/app/views/kaminari/alchemy/_next_page.html.erb +5 -3
  207. data/app/views/kaminari/alchemy/_paginator.html.erb +18 -13
  208. data/app/views/kaminari/alchemy/_prev_page.html.erb +5 -3
  209. data/app/views/layouts/alchemy/admin.html.erb +5 -9
  210. data/bun.lockb +0 -0
  211. data/bundles/remixicon.mjs +153 -0
  212. data/config/alchemy/config.yml +3 -2
  213. data/config/initializers/dragonfly.rb +0 -1
  214. data/config/initializers/mime_types.rb +1 -0
  215. data/config/locales/alchemy.en.yml +32 -14
  216. data/config/routes.rb +0 -2
  217. data/eslint.config.js +2 -1
  218. data/lib/alchemy/admin/preview_url.rb +4 -5
  219. data/lib/alchemy/cache_digests/template_tracker.rb +6 -9
  220. data/lib/alchemy/config_missing.rb +14 -0
  221. data/lib/alchemy/configuration/base_option.rb +24 -0
  222. data/lib/alchemy/configuration/boolean_option.rb +16 -0
  223. data/lib/alchemy/configuration/class_option.rb +15 -0
  224. data/lib/alchemy/configuration/class_set_option.rb +46 -0
  225. data/lib/alchemy/configuration/integer_list_option.rb +13 -0
  226. data/lib/alchemy/configuration/integer_option.rb +12 -0
  227. data/lib/alchemy/configuration/list_option.rb +22 -0
  228. data/lib/alchemy/configuration/regexp_option.rb +11 -0
  229. data/lib/alchemy/configuration/string_list_option.rb +13 -0
  230. data/lib/alchemy/configuration/string_option.rb +11 -0
  231. data/lib/alchemy/configuration.rb +115 -0
  232. data/lib/alchemy/configuration_methods.rb +3 -1
  233. data/lib/alchemy/configurations/default_language.rb +12 -0
  234. data/lib/alchemy/configurations/default_site.rb +10 -0
  235. data/lib/alchemy/configurations/format_matchers.rb +11 -0
  236. data/lib/alchemy/configurations/mailer.rb +16 -0
  237. data/lib/alchemy/configurations/main.rb +216 -0
  238. data/lib/alchemy/configurations/preview.rb +32 -0
  239. data/lib/alchemy/configurations/sitemap.rb +10 -0
  240. data/lib/alchemy/configurations/uploader.rb +34 -0
  241. data/lib/alchemy/engine.rb +65 -17
  242. data/lib/alchemy/hints.rb +3 -7
  243. data/lib/alchemy/name_conversions.rb +0 -6
  244. data/lib/alchemy/on_page_layout.rb +2 -2
  245. data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
  246. data/lib/alchemy/seeder.rb +2 -2
  247. data/lib/alchemy/tasks/usage.rb +4 -4
  248. data/lib/alchemy/test_support/config_stubbing.rb +1 -7
  249. data/lib/alchemy/test_support/factories/attachment_factory.rb +13 -2
  250. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  251. data/lib/alchemy/test_support/factories/page_factory.rb +2 -3
  252. data/lib/alchemy/test_support/factories/picture_factory.rb +30 -2
  253. data/lib/alchemy/test_support/factories/site_factory.rb +2 -2
  254. data/lib/alchemy/test_support/having_crop_action_examples.rb +2 -2
  255. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +80 -26
  256. data/lib/alchemy/test_support/shared_ingredient_examples.rb +5 -5
  257. data/lib/alchemy/upgrader/.keep +0 -0
  258. data/lib/alchemy/upgrader/eight_zero.rb +14 -0
  259. data/lib/alchemy/upgrader.rb +33 -20
  260. data/lib/alchemy/version.rb +1 -1
  261. data/lib/alchemy.rb +192 -170
  262. data/lib/alchemy_cms.rb +1 -7
  263. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +0 -3
  264. data/lib/generators/alchemy/install/files/_article.html.erb +6 -4
  265. data/lib/generators/alchemy/install/files/alchemy.en.yml +22 -3
  266. data/lib/generators/alchemy/install/files/application.html.erb +5 -0
  267. data/lib/generators/alchemy/install/install_generator.rb +5 -14
  268. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +196 -0
  269. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -1
  270. data/lib/generators/alchemy/install/templates/elements.yml.tt +3 -1
  271. data/lib/generators/alchemy/install/templates/menus.yml.tt +1 -1
  272. data/lib/generators/alchemy/install/templates/page_layouts.yml.tt +2 -2
  273. data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -2
  274. data/lib/tasks/alchemy/assets.rake +14 -0
  275. data/lib/tasks/alchemy/upgrade.rake +12 -47
  276. data/lib/tasks/alchemy/usage.rake +0 -2
  277. data/vendor/javascript/tinymce.min.js +1 -1
  278. data/vitest.config.js +21 -0
  279. metadata +184 -183
  280. data/app/assets/builds/alchemy/admin/page-select.css.map +0 -1
  281. data/app/assets/builds/alchemy/admin/print.css.map +0 -1
  282. data/app/assets/builds/alchemy/admin.css.map +0 -1
  283. data/app/assets/builds/alchemy/custom-properties.css.map +0 -1
  284. data/app/assets/builds/alchemy/welcome.css.map +0 -1
  285. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +0 -1
  286. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +0 -1
  287. data/app/assets/javascripts/alchemy/admin.js +0 -10
  288. data/app/assets/stylesheets/alchemy/_defaults.scss +0 -3
  289. data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +0 -45
  290. data/app/assets/stylesheets/alchemy/_deprecation.scss +0 -17
  291. data/app/assets/stylesheets/alchemy/_extends.scss +0 -62
  292. data/app/assets/stylesheets/alchemy/_variables.scss +0 -201
  293. data/app/assets/stylesheets/alchemy/admin/buttons.scss +0 -123
  294. data/app/assets/stylesheets/alchemy/admin/hints.scss +0 -5
  295. data/app/assets/stylesheets/alchemy/admin/labels.scss +0 -3
  296. data/app/assets/stylesheets/alchemy/admin/pagination.scss +0 -92
  297. data/app/assets/stylesheets/alchemy/admin.scss +0 -42
  298. data/app/assets/stylesheets/alchemy/custom-properties.css +0 -98
  299. data/app/assets/stylesheets/alchemy/welcome.scss +0 -57
  300. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.css +0 -711
  301. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.css +0 -705
  302. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.min.css +0 -7
  303. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.min.css +0 -7
  304. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.css +0 -29
  305. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.min.css +0 -7
  306. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.css +0 -677
  307. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.min.css +0 -7
  308. data/app/controllers/alchemy/elements_controller.rb +0 -32
  309. data/app/models/alchemy/element/dom_id.rb +0 -31
  310. data/app/models/alchemy/picture/calculations.rb +0 -49
  311. data/app/models/alchemy/picture/transformations.rb +0 -115
  312. data/app/views/alchemy/admin/attachments/destroy.js.erb +0 -1
  313. data/app/views/alchemy/admin/clipboard/clear.js.erb +0 -3
  314. data/app/views/alchemy/admin/clipboard/insert.js.erb +0 -29
  315. data/app/views/alchemy/admin/clipboard/remove.js.erb +0 -10
  316. data/app/views/alchemy/admin/resources/_filter.html.erb +0 -12
  317. data/app/views/alchemy/admin/resources/_resource.html.erb +0 -34
  318. data/app/views/alchemy/admin/resources/_table.html.erb +0 -29
  319. data/app/views/alchemy/elements/show.html.erb +0 -1
  320. data/app/views/alchemy/elements/show.js.erb +0 -1
  321. data/app/views/alchemy/ingredients/_audio_view.html.erb +0 -1
  322. data/app/views/alchemy/ingredients/_boolean_view.html.erb +0 -1
  323. data/app/views/alchemy/ingredients/_datetime_view.html.erb +0 -3
  324. data/app/views/alchemy/ingredients/_file_view.html.erb +0 -4
  325. data/app/views/alchemy/ingredients/_headline_view.html.erb +0 -4
  326. data/app/views/alchemy/ingredients/_html_view.html.erb +0 -1
  327. data/app/views/alchemy/ingredients/_link_view.html.erb +0 -4
  328. data/app/views/alchemy/ingredients/_node_view.html.erb +0 -1
  329. data/app/views/alchemy/ingredients/_page_view.html.erb +0 -1
  330. data/app/views/alchemy/ingredients/_picture_view.html.erb +0 -4
  331. data/app/views/alchemy/ingredients/_richtext_view.html.erb +0 -3
  332. data/app/views/alchemy/ingredients/_select_view.html.erb +0 -1
  333. data/app/views/alchemy/ingredients/_text_view.html.erb +0 -4
  334. data/app/views/alchemy/ingredients/_video_view.html.erb +0 -3
  335. data/babel.config.js +0 -12
  336. data/config/initializers/assets.rb +0 -4
  337. data/lib/alchemy/config.rb +0 -114
  338. data/lib/alchemy/element_definition.rb +0 -73
  339. data/lib/alchemy/page_layout.rb +0 -73
  340. data/lib/alchemy/resource_filter.rb +0 -40
  341. data/lib/alchemy/upgrader/seven_point_four.rb +0 -26
  342. data/lib/alchemy/upgrader/seven_point_three.rb +0 -52
  343. data/lib/generators/alchemy/ingredient/templates/view.html.erb +0 -1
  344. data/lib/generators/alchemy/install/files/alchemy_admin.js +0 -1
  345. data/lib/generators/alchemy/install/files/all.js +0 -11
  346. data/lib/generators/alchemy/install/files/article.css +0 -25
  347. data/vendor/assets/images/remixicon.symbol.svg +0 -11
  348. /data/app/{assets/stylesheets → stylesheets}/alchemy/_fonts.scss +0 -0
  349. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachment-select.scss +0 -0
  350. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachments.scss +0 -0
  351. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flash.scss +0 -0
  352. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/list_filter.scss +0 -0
  353. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/node-select.scss +0 -0
  354. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/spinner.scss +0 -0
  355. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/skintool.json +0 -0
  356. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/fonts/tinymce-mobile.woff +0 -0
@@ -98,8 +98,10 @@ module Alchemy
98
98
  # resource = Resource.new('/admin/tags', {"engine_name"=>"alchemy"}, Gutentag::Tag)
99
99
  #
100
100
  class Resource
101
+ include Alchemy::Admin::ResourceName
102
+
101
103
  attr_accessor :resource_relations, :model_associations
102
- attr_reader :model
104
+ attr_reader :model, :controller_path
103
105
 
104
106
  DEFAULT_SKIPPED_ATTRIBUTES = %w[id created_at creator_id]
105
107
  DEFAULT_SKIPPED_ASSOCIATIONS = %w[creator]
@@ -119,18 +121,6 @@ module Alchemy
119
121
  end
120
122
  end
121
123
 
122
- def resource_array
123
- @_resource_array ||= controller_path_array.reject { |el| el == "admin" }
124
- end
125
-
126
- def resources_name
127
- @_resources_name ||= resource_array.last
128
- end
129
-
130
- def resource_name
131
- @_resource_name ||= resources_name.singularize
132
- end
133
-
134
124
  def namespaced_resource_name
135
125
  @_namespaced_resource_name ||= begin
136
126
  namespaced_resources_name.to_s.singularize
@@ -296,11 +286,7 @@ module Alchemy
296
286
  end
297
287
 
298
288
  def guess_model_from_controller_path
299
- resource_array.join("/").classify.constantize
300
- end
301
-
302
- def controller_path_array
303
- @controller_path.split("/")
289
+ resource_model_name.classify.constantize
304
290
  end
305
291
 
306
292
  def namespace_diff
@@ -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
@@ -3,7 +3,6 @@
3
3
  module Alchemy
4
4
  module Site::Layout
5
5
  extend ActiveSupport::Concern
6
-
7
6
  SITE_DEFINITIONS_FILE = Rails.root.join("config/alchemy/site_layouts.yml")
8
7
 
9
8
  module ClassMethods
@@ -42,8 +41,8 @@ module Alchemy
42
41
  #
43
42
  def page_layout_names(layoutpages: false)
44
43
  page_layout_definitions.select do |layout|
45
- !!layout["layoutpage"] && layoutpages || !layout["layoutpage"] && !layoutpages
46
- end.collect { |layout| layout["name"] }
44
+ !!layout.layoutpage && layoutpages || !layout.layoutpage && !layoutpages
45
+ end.tap { _1.collect!(&:name) }
47
46
  end
48
47
 
49
48
  # Returns sites page layout definitions
@@ -52,11 +51,11 @@ module Alchemy
52
51
  #
53
52
  def page_layout_definitions
54
53
  if definition["page_layouts"].presence
55
- Alchemy::PageLayout.all.select do |layout|
56
- layout["name"].in?(definition["page_layouts"])
54
+ Alchemy::PageDefinition.all.select do |layout|
55
+ layout.name.in?(definition["page_layouts"])
57
56
  end
58
57
  else
59
- Alchemy::PageLayout.all
58
+ Alchemy::PageDefinition.all
60
59
  end
61
60
  end
62
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!