alchemy_cms 7.4.10 → 8.0.0.b

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

Potentially problematic release.


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

Files changed (425) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -5
  3. data/app/assets/builds/alchemy/admin/page-select.css +1 -1
  4. data/app/assets/builds/alchemy/admin/print.css +1 -1
  5. data/app/assets/builds/alchemy/admin.css +2 -2
  6. data/app/assets/builds/alchemy/dark-theme.css +1 -0
  7. data/app/assets/builds/alchemy/light-theme.css +1 -0
  8. data/app/assets/builds/alchemy/theme.css +1 -0
  9. data/app/assets/builds/alchemy/welcome.css +1 -1
  10. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  11. data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -0
  12. data/app/assets/builds/tinymce/skins/ui/alchemy/content.min.css +1 -0
  13. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
  14. data/app/assets/builds/tinymce/skins/ui/alchemy-dark/content.min.css +1 -0
  15. data/app/assets/builds/tinymce/skins/ui/alchemy-dark/skin.min.css +1 -0
  16. data/app/assets/config/alchemy_manifest.js +0 -2
  17. data/app/assets/images/alchemy/element_icons/default.svg +1 -0
  18. data/app/assets/images/alchemy/icons-sprite.svg +1 -0
  19. data/app/components/alchemy/admin/element_select.rb +39 -0
  20. data/app/components/alchemy/admin/resource/applied_filter.rb +29 -0
  21. data/app/components/alchemy/admin/resource/checkbox_filter.rb +36 -0
  22. data/app/components/alchemy/admin/resource/datepicker_filter.rb +42 -0
  23. data/app/components/alchemy/admin/resource/select_filter.rb +43 -0
  24. data/app/components/alchemy/admin/toolbar_button.rb +5 -2
  25. data/app/components/alchemy/ingredients/number_view.rb +18 -0
  26. data/app/controllers/alchemy/admin/attachments_controller.rb +10 -15
  27. data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -6
  28. data/app/controllers/alchemy/admin/elements_controller.rb +3 -1
  29. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  30. data/app/controllers/alchemy/admin/pages_controller.rb +15 -15
  31. data/app/controllers/alchemy/admin/pictures_controller.rb +27 -37
  32. data/app/controllers/alchemy/admin/resources_controller.rb +16 -106
  33. data/app/controllers/alchemy/attachments_controller.rb +43 -14
  34. data/app/controllers/alchemy/messages_controller.rb +1 -1
  35. data/app/controllers/alchemy/pages_controller.rb +26 -4
  36. data/app/controllers/concerns/alchemy/admin/resource_filter.rb +93 -0
  37. data/app/decorators/alchemy/element_editor.rb +5 -48
  38. data/app/decorators/alchemy/ingredient_editor.rb +3 -53
  39. data/app/helpers/alchemy/admin/attachments_helper.rb +5 -5
  40. data/app/helpers/alchemy/admin/base_helper.rb +14 -84
  41. data/app/helpers/alchemy/admin/pages_helper.rb +1 -1
  42. data/app/helpers/alchemy/base_helper.rb +0 -30
  43. data/app/helpers/alchemy/elements_block_helper.rb +0 -14
  44. data/app/helpers/alchemy/pages_helper.rb +1 -1
  45. data/{lib → app/helpers}/alchemy/resources_helper.rb +5 -45
  46. data/app/javascript/alchemy_admin/components/action.js +2 -0
  47. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +3 -3
  48. data/app/javascript/alchemy_admin/components/auto_submit.js +20 -0
  49. data/app/javascript/alchemy_admin/components/datepicker.js +18 -7
  50. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +8 -7
  51. data/app/javascript/alchemy_admin/components/element_editor.js +25 -15
  52. data/app/javascript/alchemy_admin/components/element_select.js +43 -0
  53. data/app/javascript/alchemy_admin/components/index.js +3 -0
  54. data/app/javascript/alchemy_admin/components/link_buttons.js +6 -2
  55. data/app/javascript/alchemy_admin/components/remote_select.js +9 -2
  56. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +5 -1
  57. data/app/javascript/alchemy_admin/components/tinymce.js +93 -14
  58. data/app/javascript/alchemy_admin/components/update_check.js +42 -0
  59. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +15 -8
  60. data/app/javascript/alchemy_admin/components/uploader/progress.js +12 -6
  61. data/app/javascript/alchemy_admin/components/uploader.js +4 -2
  62. data/app/javascript/alchemy_admin/confirm_dialog.js +27 -57
  63. data/app/javascript/alchemy_admin/dialog.js +1 -1
  64. data/app/javascript/alchemy_admin/dirty.js +3 -2
  65. data/app/javascript/alchemy_admin/file_editors.js +1 -1
  66. data/app/javascript/alchemy_admin/i18n.js +15 -16
  67. data/app/javascript/alchemy_admin/image_loader.js +4 -2
  68. data/app/javascript/alchemy_admin/initializer.js +1 -49
  69. data/app/javascript/alchemy_admin/picture_editors.js +7 -4
  70. data/app/javascript/alchemy_admin/picture_selector.js +4 -4
  71. data/app/javascript/alchemy_admin/utils/ajax.js +51 -44
  72. data/app/javascript/alchemy_admin.js +3 -8
  73. data/app/jobs/alchemy/delete_picture_job.rb +12 -0
  74. data/app/models/alchemy/admin/filters/base.rb +38 -0
  75. data/app/models/alchemy/admin/filters/checkbox.rb +24 -0
  76. data/app/models/alchemy/admin/filters/datepicker.rb +53 -0
  77. data/app/models/alchemy/admin/filters/select.rb +70 -0
  78. data/app/models/alchemy/admin/resource_name.rb +27 -0
  79. data/app/models/alchemy/attachment.rb +49 -44
  80. data/app/models/alchemy/base_record.rb +2 -0
  81. data/app/models/alchemy/element/definitions.rb +1 -1
  82. data/app/models/alchemy/element/element_ingredients.rb +6 -6
  83. data/app/models/alchemy/element/presenters.rb +3 -12
  84. data/app/models/alchemy/element.rb +10 -27
  85. data/app/models/alchemy/element_definition.rb +190 -0
  86. data/app/models/alchemy/ingredient.rb +10 -43
  87. data/app/models/alchemy/ingredient_definition.rb +134 -0
  88. data/app/models/alchemy/ingredient_validator.rb +7 -3
  89. data/app/models/alchemy/ingredients/number.rb +19 -0
  90. data/app/models/alchemy/language.rb +2 -21
  91. data/app/models/alchemy/message.rb +3 -7
  92. data/app/models/alchemy/node.rb +1 -1
  93. data/app/models/alchemy/page/{page_layouts.rb → definitions.rb} +12 -19
  94. data/app/models/alchemy/page/fixed_attributes.rb +1 -1
  95. data/app/models/alchemy/page/page_elements.rb +13 -14
  96. data/app/models/alchemy/page/page_naming.rb +3 -11
  97. data/app/models/alchemy/page/page_natures.rb +20 -15
  98. data/app/models/alchemy/page/page_scopes.rb +1 -1
  99. data/app/models/alchemy/page.rb +12 -39
  100. data/app/models/alchemy/page_definition.rb +115 -0
  101. data/app/models/alchemy/picture.rb +71 -99
  102. data/app/models/alchemy/picture_variant.rb +115 -5
  103. data/{lib → app/models}/alchemy/resource.rb +4 -18
  104. data/{lib → app/models}/alchemy/searchable_resource.rb +15 -0
  105. data/app/models/alchemy/site/layout.rb +5 -5
  106. data/app/models/alchemy/site.rb +1 -21
  107. data/app/models/alchemy/storage_adapter/active_storage/attachment_url.rb +41 -0
  108. data/app/models/alchemy/storage_adapter/active_storage/picture_url.rb +55 -0
  109. data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +40 -0
  110. data/app/models/alchemy/storage_adapter/active_storage.rb +173 -0
  111. data/app/models/alchemy/{attachment/url.rb → storage_adapter/dragonfly/attachment_url.rb} +12 -12
  112. data/app/models/alchemy/storage_adapter/dragonfly/picture_url.rb +75 -0
  113. data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
  114. data/app/models/alchemy/storage_adapter/dragonfly.rb +205 -0
  115. data/app/models/alchemy/storage_adapter.rb +74 -0
  116. data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
  117. data/app/models/concerns/alchemy/relatable_resource.rb +28 -0
  118. data/app/serializers/alchemy/element_serializer.rb +0 -1
  119. data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -0
  120. data/app/stylesheets/alchemy/_custom-properties.scss +162 -0
  121. data/app/stylesheets/alchemy/_defaults.scss +3 -0
  122. data/app/stylesheets/alchemy/_extends.scss +69 -0
  123. data/app/{assets/stylesheets → stylesheets}/alchemy/_mixins.scss +40 -68
  124. data/app/stylesheets/alchemy/_themes.scss +540 -0
  125. data/app/stylesheets/alchemy/_variables.scss +5 -0
  126. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +45 -42
  127. data/app/stylesheets/alchemy/admin/attachments.scss +17 -0
  128. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +20 -15
  129. data/app/stylesheets/alchemy/admin/buttons.scss +135 -0
  130. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/clipboard.scss +2 -2
  131. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dashboard.scss +13 -16
  132. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dialogs.scss +33 -16
  133. data/app/stylesheets/alchemy/admin/element-select.scss +11 -0
  134. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +239 -133
  135. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
  136. data/app/stylesheets/alchemy/admin/filters.scss +57 -0
  137. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +54 -76
  138. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +10 -11
  139. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +28 -21
  140. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -18
  141. data/app/stylesheets/alchemy/admin/hints.scss +5 -0
  142. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +1 -1
  143. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +21 -61
  144. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/images.scss +1 -1
  145. data/app/stylesheets/alchemy/admin/labels.scss +5 -0
  146. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/lists.scss +3 -3
  147. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/navigation.scss +55 -59
  148. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/node-select.scss +1 -10
  149. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
  150. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +20 -19
  151. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +18 -2
  152. data/app/stylesheets/alchemy/admin/pagination.scss +137 -0
  153. data/app/stylesheets/alchemy/admin/preview_window.scss +46 -0
  154. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
  155. data/app/stylesheets/alchemy/admin/resource_info.scss +148 -0
  156. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +10 -7
  157. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +85 -46
  158. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +37 -68
  159. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +39 -34
  160. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +9 -7
  161. data/app/stylesheets/alchemy/admin/tags.scss +143 -0
  162. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +6 -6
  163. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +3 -6
  164. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
  165. data/app/stylesheets/alchemy/admin.scss +44 -0
  166. data/app/stylesheets/alchemy/dark-theme.scss +5 -0
  167. data/app/stylesheets/alchemy/light-theme.scss +6 -0
  168. data/app/stylesheets/alchemy/theme.scss +13 -0
  169. data/app/stylesheets/alchemy/welcome.scss +75 -0
  170. data/app/stylesheets/tinymce/skins/content/alchemy/content.scss +69 -0
  171. data/app/{assets/stylesheets/tinymce/skins/content/alchemy → stylesheets/tinymce/skins/content/alchemy-dark}/content.scss +12 -12
  172. data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
  173. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +158 -176
  174. data/app/stylesheets/tinymce/skins/ui/alchemy-dark/content.scss +1 -0
  175. data/app/stylesheets/tinymce/skins/ui/alchemy-dark/skin.scss +3784 -0
  176. data/app/views/alchemy/admin/attachments/_files_list.html.erb +22 -12
  177. data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
  178. data/app/views/alchemy/admin/attachments/assign.js.erb +4 -3
  179. data/app/views/alchemy/admin/attachments/show.html.erb +55 -43
  180. data/app/views/alchemy/admin/{elements/_clipboard_button.html.erb → clipboard/_button.html.erb} +3 -5
  181. data/app/views/alchemy/admin/clipboard/_update_nested_element_button.turbo_stream.erb +11 -0
  182. data/app/views/alchemy/admin/clipboard/clear.turbo_stream.erb +4 -0
  183. data/app/views/alchemy/admin/clipboard/index.html.erb +15 -13
  184. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +18 -0
  185. data/app/views/alchemy/admin/clipboard/remove.turbo_stream.erb +9 -0
  186. data/app/views/alchemy/admin/crop.html.erb +1 -1
  187. data/app/views/alchemy/admin/dashboard/info.html.erb +17 -31
  188. data/app/views/alchemy/admin/elements/_element.html.erb +4 -8
  189. data/app/views/alchemy/admin/elements/_form.html.erb +9 -9
  190. data/app/views/alchemy/admin/elements/_header.html.erb +5 -1
  191. data/app/views/alchemy/admin/elements/_toolbar.html.erb +4 -6
  192. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +2 -1
  193. data/app/views/alchemy/admin/elements/index.html.erb +2 -2
  194. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +3 -16
  195. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +0 -9
  196. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  197. data/app/views/alchemy/admin/languages/_table.html.erb +1 -1
  198. data/app/views/alchemy/admin/languages/index.html.erb +5 -2
  199. data/app/views/alchemy/admin/layoutpages/index.html.erb +1 -12
  200. data/app/views/alchemy/admin/pages/_form.html.erb +2 -2
  201. data/app/views/alchemy/admin/pages/_page.html.erb +2 -3
  202. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -15
  203. data/app/views/alchemy/admin/pages/index.html.erb +1 -1
  204. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +9 -12
  205. data/app/views/alchemy/admin/partials/_search_form.html.erb +4 -9
  206. data/app/views/alchemy/admin/pictures/_archive.html.erb +16 -29
  207. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +3 -7
  208. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -1
  209. data/app/views/alchemy/admin/pictures/_form.html.erb +1 -1
  210. data/app/views/alchemy/admin/pictures/_infos.html.erb +21 -52
  211. data/app/views/alchemy/admin/pictures/_library_sidebar.html.erb +7 -0
  212. data/app/views/alchemy/admin/pictures/_picture.html.erb +14 -20
  213. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +20 -16
  214. data/app/views/alchemy/admin/pictures/_sorting_select.html.erb +13 -0
  215. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -1
  216. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +1 -6
  217. data/app/views/alchemy/admin/pictures/index.html.erb +5 -19
  218. data/app/views/alchemy/admin/pictures/show.html.erb +10 -5
  219. data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
  220. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +5 -25
  221. data/app/views/alchemy/admin/resources/_pagination.html.erb +6 -0
  222. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +4 -2
  223. data/app/views/alchemy/admin/resources/_resource_table.html.erb +1 -1
  224. data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +36 -0
  225. data/app/views/alchemy/admin/resources/_table_header.html.erb +1 -15
  226. data/app/views/alchemy/admin/sites/index.html.erb +5 -1
  227. data/app/views/alchemy/admin/styleguide/index.html.erb +118 -58
  228. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  229. data/app/views/alchemy/admin/tinymce/_setup.html.erb +7 -7
  230. data/app/{javascript/alchemy_admin/locales/en.js → views/alchemy/admin/translations/_en.js} +5 -2
  231. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
  232. data/app/views/alchemy/admin/uploader/_setup.html.erb +4 -4
  233. data/app/views/alchemy/base/error_notice.html.erb +1 -1
  234. data/app/views/alchemy/ingredients/_number_editor.html.erb +24 -0
  235. data/app/views/alchemy/no_index.html.erb +31 -0
  236. data/app/views/alchemy/welcome.html.erb +12 -10
  237. data/app/views/kaminari/alchemy/_first_page.html.erb +5 -3
  238. data/app/views/kaminari/alchemy/_last_page.html.erb +5 -3
  239. data/app/views/kaminari/alchemy/_next_page.html.erb +5 -3
  240. data/app/views/kaminari/alchemy/_paginator.html.erb +18 -13
  241. data/app/views/kaminari/alchemy/_prev_page.html.erb +5 -3
  242. data/app/views/layouts/alchemy/admin.html.erb +25 -24
  243. data/config/alchemy/config.yml +3 -2
  244. data/config/initializers/dragonfly.rb +0 -1
  245. data/config/initializers/mime_types.rb +1 -0
  246. data/config/locales/alchemy.en.yml +57 -21
  247. data/config/routes.rb +0 -2
  248. data/db/migrate/20250905140323_add_created_at_index_to_pictures_and_attachments.rb +14 -0
  249. data/lib/alchemy/admin/preview_url.rb +4 -5
  250. data/lib/alchemy/cache_digests/template_tracker.rb +6 -9
  251. data/lib/alchemy/config_missing.rb +14 -0
  252. data/lib/alchemy/configuration/base_option.rb +24 -0
  253. data/lib/alchemy/configuration/boolean_option.rb +16 -0
  254. data/lib/alchemy/configuration/class_option.rb +15 -0
  255. data/lib/alchemy/configuration/class_set_option.rb +46 -0
  256. data/lib/alchemy/configuration/integer_list_option.rb +13 -0
  257. data/lib/alchemy/configuration/integer_option.rb +12 -0
  258. data/lib/alchemy/configuration/list_option.rb +22 -0
  259. data/lib/alchemy/configuration/regexp_option.rb +11 -0
  260. data/lib/alchemy/configuration/string_list_option.rb +13 -0
  261. data/lib/alchemy/configuration/string_option.rb +11 -0
  262. data/lib/alchemy/configuration.rb +115 -0
  263. data/lib/alchemy/configuration_methods.rb +3 -1
  264. data/lib/alchemy/configurations/default_language.rb +12 -0
  265. data/lib/alchemy/configurations/default_site.rb +10 -0
  266. data/lib/alchemy/configurations/format_matchers.rb +11 -0
  267. data/lib/alchemy/configurations/mailer.rb +16 -0
  268. data/lib/alchemy/configurations/main.rb +223 -0
  269. data/lib/alchemy/configurations/page_cache.rb +19 -0
  270. data/lib/alchemy/configurations/preview.rb +32 -0
  271. data/lib/alchemy/configurations/sitemap.rb +10 -0
  272. data/lib/alchemy/configurations/uploader.rb +34 -0
  273. data/lib/alchemy/engine.rb +81 -24
  274. data/lib/alchemy/hints.rb +3 -7
  275. data/lib/alchemy/install/tasks.rb +0 -12
  276. data/lib/alchemy/on_page_layout.rb +2 -2
  277. data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
  278. data/lib/alchemy/seeder.rb +2 -2
  279. data/lib/alchemy/tasks/tidy.rb +18 -0
  280. data/lib/alchemy/tasks/usage.rb +4 -4
  281. data/lib/alchemy/test_support/config_stubbing.rb +1 -7
  282. data/lib/alchemy/test_support/factories/attachment_factory.rb +13 -2
  283. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  284. data/lib/alchemy/test_support/factories/page_factory.rb +2 -3
  285. data/lib/alchemy/test_support/factories/picture_factory.rb +31 -2
  286. data/lib/alchemy/test_support/factories/site_factory.rb +2 -2
  287. data/lib/alchemy/test_support/having_crop_action_examples.rb +2 -2
  288. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +80 -26
  289. data/lib/alchemy/test_support/relatable_resource_examples.rb +58 -0
  290. data/lib/alchemy/test_support/shared_ingredient_examples.rb +5 -5
  291. data/lib/alchemy/tinymce.rb +0 -1
  292. data/lib/alchemy/upgrader/eight_zero.rb +14 -0
  293. data/lib/alchemy/upgrader.rb +33 -20
  294. data/lib/alchemy/version.rb +1 -1
  295. data/lib/alchemy.rb +185 -172
  296. data/lib/alchemy_cms.rb +1 -7
  297. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +0 -3
  298. data/lib/generators/alchemy/install/files/_article.html.erb +6 -4
  299. data/lib/generators/alchemy/install/files/alchemy.en.yml +22 -3
  300. data/lib/generators/alchemy/install/files/application.html.erb +5 -0
  301. data/lib/generators/alchemy/install/install_generator.rb +25 -23
  302. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +200 -0
  303. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -1
  304. data/lib/generators/alchemy/install/templates/elements.yml.tt +3 -1
  305. data/lib/generators/alchemy/install/templates/menus.yml.tt +1 -1
  306. data/lib/generators/alchemy/install/templates/page_layouts.yml.tt +2 -2
  307. data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -2
  308. data/lib/tasks/alchemy/assets.rake +14 -0
  309. data/lib/tasks/alchemy/tidy.rake +6 -0
  310. data/lib/tasks/alchemy/upgrade.rake +12 -47
  311. data/vendor/assets/stylesheets/tinymce/skins/content/dark/content.min.css +1 -0
  312. data/vendor/assets/stylesheets/tinymce/skins/content/default/content.min.css +1 -0
  313. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide/skin.min.css +1 -0
  314. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide-dark/content.min.css +1 -0
  315. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide-dark/skin.min.css +1 -0
  316. data/vendor/javascript/clipboard.min.js +1 -1
  317. data/vendor/javascript/cropperjs.min.js +1 -1
  318. data/vendor/javascript/handlebars.min.js +3 -3
  319. data/vendor/javascript/jquery.min.js +1 -1
  320. data/vendor/javascript/select2.min.js +3 -3
  321. data/vendor/javascript/shoelace.min.js +92 -76
  322. data/vendor/javascript/sortable.min.js +2 -2
  323. data/vendor/javascript/tinymce.min.js +1 -1
  324. data/vendor/javascript/ungap-custom-elements.min.js +2 -2
  325. metadata +223 -208
  326. data/CHANGELOG.md +0 -2041
  327. data/CODE_OF_CONDUCT.md +0 -13
  328. data/CONTRIBUTING.md +0 -73
  329. data/Gemfile +0 -71
  330. data/Rakefile +0 -102
  331. data/SECURITY.md +0 -13
  332. data/alchemy_cms.gemspec +0 -88
  333. data/app/assets/builds/alchemy/admin/page-select.css.map +0 -1
  334. data/app/assets/builds/alchemy/admin/print.css.map +0 -1
  335. data/app/assets/builds/alchemy/admin.css.map +0 -1
  336. data/app/assets/builds/alchemy/custom-properties.css +0 -1
  337. data/app/assets/builds/alchemy/custom-properties.css.map +0 -1
  338. data/app/assets/builds/alchemy/welcome.css.map +0 -1
  339. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +0 -1
  340. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +0 -1
  341. data/app/assets/javascripts/alchemy/admin.js +0 -10
  342. data/app/assets/stylesheets/alchemy/_defaults.scss +0 -3
  343. data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +0 -45
  344. data/app/assets/stylesheets/alchemy/_deprecation.scss +0 -17
  345. data/app/assets/stylesheets/alchemy/_extends.scss +0 -62
  346. data/app/assets/stylesheets/alchemy/_variables.scss +0 -201
  347. data/app/assets/stylesheets/alchemy/admin/attachments.scss +0 -40
  348. data/app/assets/stylesheets/alchemy/admin/buttons.scss +0 -123
  349. data/app/assets/stylesheets/alchemy/admin/hints.scss +0 -5
  350. data/app/assets/stylesheets/alchemy/admin/labels.scss +0 -3
  351. data/app/assets/stylesheets/alchemy/admin/pagination.scss +0 -92
  352. data/app/assets/stylesheets/alchemy/admin/preview_window.scss +0 -33
  353. data/app/assets/stylesheets/alchemy/admin/resource_info.scss +0 -42
  354. data/app/assets/stylesheets/alchemy/admin/tags.scss +0 -158
  355. data/app/assets/stylesheets/alchemy/admin.scss +0 -42
  356. data/app/assets/stylesheets/alchemy/custom-properties.css +0 -98
  357. data/app/assets/stylesheets/alchemy/welcome.scss +0 -57
  358. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.css +0 -711
  359. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.css +0 -705
  360. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.min.css +0 -7
  361. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.min.css +0 -7
  362. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.css +0 -29
  363. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.min.css +0 -7
  364. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.css +0 -677
  365. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.min.css +0 -7
  366. data/app/controllers/alchemy/elements_controller.rb +0 -32
  367. data/app/helpers/alchemy/admin/elements_helper.rb +0 -25
  368. data/app/models/alchemy/element/dom_id.rb +0 -31
  369. data/app/models/alchemy/picture/calculations.rb +0 -49
  370. data/app/models/alchemy/picture/transformations.rb +0 -115
  371. data/app/models/alchemy/picture/url.rb +0 -54
  372. data/app/views/alchemy/admin/attachments/destroy.js.erb +0 -1
  373. data/app/views/alchemy/admin/clipboard/clear.js.erb +0 -3
  374. data/app/views/alchemy/admin/clipboard/insert.js.erb +0 -29
  375. data/app/views/alchemy/admin/clipboard/remove.js.erb +0 -10
  376. data/app/views/alchemy/admin/resources/_filter.html.erb +0 -12
  377. data/app/views/alchemy/admin/resources/_resource.html.erb +0 -34
  378. data/app/views/alchemy/admin/resources/_table.html.erb +0 -29
  379. data/app/views/alchemy/elements/show.html.erb +0 -1
  380. data/app/views/alchemy/elements/show.js.erb +0 -1
  381. data/app/views/alchemy/ingredients/_audio_view.html.erb +0 -1
  382. data/app/views/alchemy/ingredients/_boolean_view.html.erb +0 -1
  383. data/app/views/alchemy/ingredients/_datetime_view.html.erb +0 -3
  384. data/app/views/alchemy/ingredients/_file_view.html.erb +0 -4
  385. data/app/views/alchemy/ingredients/_headline_view.html.erb +0 -4
  386. data/app/views/alchemy/ingredients/_html_view.html.erb +0 -1
  387. data/app/views/alchemy/ingredients/_link_view.html.erb +0 -4
  388. data/app/views/alchemy/ingredients/_node_view.html.erb +0 -1
  389. data/app/views/alchemy/ingredients/_page_view.html.erb +0 -1
  390. data/app/views/alchemy/ingredients/_picture_view.html.erb +0 -4
  391. data/app/views/alchemy/ingredients/_richtext_view.html.erb +0 -3
  392. data/app/views/alchemy/ingredients/_select_view.html.erb +0 -1
  393. data/app/views/alchemy/ingredients/_text_view.html.erb +0 -4
  394. data/app/views/alchemy/ingredients/_video_view.html.erb +0 -3
  395. data/babel.config.js +0 -12
  396. data/bin/importmap +0 -4
  397. data/bin/rails +0 -9
  398. data/bin/rspec +0 -3
  399. data/bin/setup +0 -30
  400. data/bin/start +0 -17
  401. data/bun.lockb +0 -0
  402. data/bundles/shoelace.js +0 -12
  403. data/bundles/tinymce.js +0 -22
  404. data/config/initializers/assets.rb +0 -4
  405. data/eslint.config.js +0 -17
  406. data/lib/alchemy/config.rb +0 -114
  407. data/lib/alchemy/element_definition.rb +0 -73
  408. data/lib/alchemy/page_layout.rb +0 -73
  409. data/lib/alchemy/resource_filter.rb +0 -40
  410. data/lib/alchemy/upgrader/seven_point_four.rb +0 -26
  411. data/lib/alchemy/upgrader/seven_point_three.rb +0 -52
  412. data/lib/alchemy/upgrader/tasks/.keep +0 -0
  413. data/lib/generators/alchemy/ingredient/templates/view.html.erb +0 -1
  414. data/lib/generators/alchemy/install/files/alchemy_admin.js +0 -1
  415. data/lib/generators/alchemy/install/files/all.js +0 -11
  416. data/lib/generators/alchemy/install/files/article.css +0 -25
  417. data/rollup.config.mjs +0 -108
  418. data/vendor/assets/images/remixicon.symbol.svg +0 -11
  419. /data/app/{assets/stylesheets → stylesheets}/alchemy/_fonts.scss +0 -0
  420. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachment-select.scss +0 -0
  421. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flash.scss +0 -0
  422. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/list_filter.scss +0 -0
  423. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/spinner.scss +0 -0
  424. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/skintool.json +0 -0
  425. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/fonts/tinymce-mobile.woff +0 -0
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration"
4
+ require "alchemy/configurations/default_language"
5
+ require "alchemy/configurations/default_site"
6
+ require "alchemy/configurations/format_matchers"
7
+ require "alchemy/configurations/mailer"
8
+ require "alchemy/configurations/page_cache"
9
+ require "alchemy/configurations/preview"
10
+ require "alchemy/configurations/sitemap"
11
+ require "alchemy/configurations/uploader"
12
+ require "alchemy/deprecation"
13
+
14
+ module Alchemy
15
+ module Configurations
16
+ class Main < Alchemy::Configuration
17
+ # == This is the global Alchemy configuration class
18
+
19
+ # === Auto Log Out Time
20
+ #
21
+ # The amount of time of inactivity in minutes after which the user is kicked out of his current session.
22
+ #
23
+ # NOTE: This is only active in production environments
24
+ #
25
+ option :auto_logout_time, :integer, default: 30
26
+
27
+ # === Page caching
28
+ #
29
+ # Enable/Disable page caching globally.
30
+ #
31
+ # NOTE: You can enable/disable page caching for single Alchemy::Definitions in the page_layout.yml file.
32
+ #
33
+ option :cache_pages, :boolean, default: true
34
+
35
+ # === Page caching max age
36
+ #
37
+ # max-age [Integer] # The duration in seconds for which the page is cached before revalidation.
38
+ # stale-while-revalidate [Boolean] # If true, enables the stale-while-revalidate caching strategy.
39
+ configuration :page_cache, PageCache
40
+
41
+ # === Sitemap
42
+ #
43
+ # Alchemy creates a XML, Google compatible, sitemap for you.
44
+ #
45
+ # The url is: http://your-domain.tld/sitemap.xml
46
+ #
47
+ # ==== Config Options:
48
+ #
49
+ # show_root [Boolean] # Show language root page in sitemap?
50
+ # show_flag [Boolean] # Enables the Checkbox in Page#update overlay. So your customer can set the visibility of pages in the sitemap.
51
+ configuration :sitemap, Sitemap
52
+
53
+ # === Default items per page in admin views
54
+ #
55
+ # In Alchemy's Admin, change how many items you would get shown per page by Kaminari
56
+ option :items_per_page, :integer, default: 15
57
+
58
+ # === Preview window URL configuration
59
+ #
60
+ # By default Alchemy uses its internal page preview renderer,
61
+ # but you can configure it to be any URL instead.
62
+ #
63
+ # Basic Auth is supported.
64
+ #
65
+ # preview:
66
+ # host: https://www.my-static-site.com
67
+ # auth:
68
+ # username: <%= ENV["BASIC_AUTH_USERNAME"] %>
69
+ # password: <%= ENV["BASIC_AUTH_PASSWORD"] %>
70
+ #
71
+ # Preview config per site is supported as well.
72
+ #
73
+ # preview:
74
+ # My site name:
75
+ # host: https://www.my-static-site.com
76
+ # auth:
77
+ # username: <%= ENV["BASIC_AUTH_USERNAME"] %>
78
+ # password: <%= ENV["BASIC_AUTH_PASSWORD"] %>
79
+ #
80
+ configuration :preview, Preview
81
+
82
+ # === Picture rendering settings
83
+ #
84
+ # Alchemy uses Dragonfly to render images. Settings for image rendering are specific to elements and are defined in elements.yml
85
+ #
86
+ # Example:
87
+ # - name: some_element
88
+ # ingredients:
89
+ # - role: some_picture
90
+ # type: Picture
91
+ # settings:
92
+ # hint: true
93
+ # crop: true # turns on image cropping
94
+ # size: '500x500' # image will be cropped to this size
95
+ #
96
+ # See http://markevans.github.com/dragonfly for further info.
97
+ #
98
+ # ==== Global Options:
99
+ #
100
+ # output_image_quality [Integer] # If image gets rendered as JPG or WebP this is the quality setting for it. (Default 85)
101
+ # preprocess_image_resize [String] # Use this option to resize images to the given size when they are uploaded to the image library. Downsizing example: '1000x1000>' (Default nil)
102
+ # image_output_format [String] # The global image output format setting. (Default +original+)
103
+ #
104
+ # NOTE: You can always override the output format in the settings of your ingredients in elements.yml, I.E. {format: 'gif'}
105
+ #
106
+ option :output_image_quality, :integer, default: 85
107
+ alias_method :output_image_jpg_quality, :output_image_quality
108
+ deprecate output_image_jpg_quality: :output_image_quality, deprecator: Alchemy::Deprecation
109
+ alias_method :output_image_jpg_quality=, :output_image_quality=
110
+ deprecate "output_image_jpg_quality=": :output_image_quality=, deprecator: Alchemy::Deprecation
111
+ option :preprocess_image_resize, :string
112
+ option :image_output_format, :string, default: "original"
113
+ option :sharpen_images, :boolean, default: false
114
+
115
+ # This is used by the seeder to create the default site.
116
+ configuration :default_site, DefaultSite
117
+
118
+ # This is the default language when seeding.
119
+ configuration :default_language, DefaultLanguage
120
+
121
+ # === Mailer Settings:
122
+ #
123
+ # To send emails via contact forms, you can create your form fields here and set which fields are to be validated.
124
+ #
125
+ # === Validating fields:
126
+ #
127
+ # Pass the field name as a symbol and a message_id (will be translated) to :validate_fields:
128
+ #
129
+ # ==== Options:
130
+ #
131
+ # page_layout_name: [String] # A +Alchemy::PageDefinition+ name. Used to render the contactform on a page with this layout.
132
+ # fields: [Array] # An Array of fieldnames.
133
+ # validate_fields: [Array] # An Array of fieldnames to be validated on presence.
134
+ #
135
+ # ==== Translating validation messages:
136
+ #
137
+ # The validation messages are passed through ::I18n.t so you can translate it in your language yml file.
138
+ #
139
+ # ==== Example:
140
+ #
141
+ # de:
142
+ # activemodel:
143
+ # attributes:
144
+ # alchemy/message:
145
+ # firstname: Vorname
146
+ #
147
+ configuration :mailer, Mailer
148
+
149
+ # === User roles
150
+ #
151
+ # You can add own user roles.
152
+ #
153
+ # Further documentation for the auth system used please visit:
154
+ #
155
+ # https://github.com/ryanb/cancan/wiki
156
+ #
157
+ # ==== Translating User roles
158
+ #
159
+ # Userroles can be translated inside your the language yml file under:
160
+ #
161
+ # alchemy:
162
+ # user_roles:
163
+ # rolename: Name of the role
164
+ #
165
+ option :user_roles, :string_list, default: %w[member author editor admin]
166
+
167
+ # === Uploader Settings
168
+ #
169
+ # upload_limit [Integer] # Set an amount of files upload limit of files which can be uploaded at once. Set 0 for unlimited.
170
+ # file_size_limit* [Integer] # Set a file size limit in mega bytes for a per file limit.
171
+ #
172
+ # *) Allow filetypes to upload. Pass * to allow all kind of files.
173
+ #
174
+ configuration :uploader, Uploader
175
+
176
+ # === Link Target Options
177
+ #
178
+ # Values for the link target selectbox inside the page link overlay.
179
+ # The value gets attached as a data-link-target attribute to the link.
180
+ #
181
+ # == Example:
182
+ #
183
+ # Open all links set to overlay inside an jQuery UI Dialog Window.
184
+ #
185
+ # jQuery(a[data-link-target="overlay"]).dialog();
186
+ #
187
+ option :link_target_options, :string_list, default: %w[blank]
188
+
189
+ # === Format matchers
190
+ #
191
+ # Named aliases for regular expressions that can be used in various places.
192
+ # The most common use case is the format validation of ingredients, or attribute validations of your individual models.
193
+ #
194
+ # == Example:
195
+ #
196
+ # validates_format_of :url, with: Alchemy.config.format_matchers.url
197
+ #
198
+ configuration :format_matchers, FormatMatchers
199
+
200
+ # The layout used for rendering the +alchemy/admin/pages#show+ action.
201
+ option :admin_page_preview_layout, :string, default: "application"
202
+
203
+ # The sizes for the preview size select in the page editor.
204
+ option :page_preview_sizes, :integer_list, default: [360, 640, 768, 1024, 1280, 1440]
205
+
206
+ # Enable full text search configuration
207
+ #
208
+ # It enables a searchable checkbox in the page form to toggle
209
+ # the searchable field. These information can used in a search
210
+ # plugin (e.g. https://github.com/AlchemyCMS/alchemy-pg_search).
211
+ #
212
+ # == Example
213
+ #
214
+ # # config/initializers/alchemy.rb
215
+ # Alchemy.config.page_searchable_checkbox = true
216
+ option :show_page_searchable_checkbox, :boolean, default: false
217
+
218
+ # The storage adapter for Pictures and Attachments
219
+ #
220
+ option :storage_adapter, :string, default: "dragonfly"
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class PageCache < Alchemy::Configuration
6
+ # === Page caching max age
7
+ #
8
+ # Control the max-age duration in seconds in the cache-control header.
9
+ #
10
+ option :max_age, :integer, default: 600
11
+
12
+ # === Page caching stale-while-revalidate
13
+ #
14
+ # Set stale-while-revalidate cache-control header.
15
+ #
16
+ option :stale_while_revalidate, :integer
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class Preview < Alchemy::Configuration
6
+ class PreviewAuth < Alchemy::Configuration
7
+ option :username, :string
8
+ option :password, :string
9
+ end
10
+
11
+ attr_reader :per_site_configs
12
+
13
+ def initialize(configuration = {})
14
+ @per_site_configs = []
15
+ configuration = configuration.with_indifferent_access
16
+ configuration.except(:host, :site_name, :auth).keys.each do |site_name|
17
+ @per_site_configs << Preview.new(configuration[site_name].merge(site_name: site_name))
18
+ end
19
+ super(configuration.slice(:host, :site_name, :auth))
20
+ end
21
+
22
+ option :site_name, :string, default: "*"
23
+ option :host, :string
24
+
25
+ configuration :auth, PreviewAuth
26
+
27
+ def for_site(site)
28
+ per_site_configs.detect { _1.site_name == site.name } || self
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class Sitemap < Alchemy::Configuration
6
+ option :show_root, :boolean, default: true
7
+ option :show_flag, :boolean, default: false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class Uploader < Alchemy::Configuration
6
+ class AllowedFileTypes < Alchemy::Configuration
7
+ option :alchemy_attachments, :string_list, default: ["*"]
8
+ option :alchemy_pictures, :string_list, default: %w[jpg jpeg gif png svg webp]
9
+
10
+ def set(configuration_hash)
11
+ super(configuration_hash.transform_keys { transform_key(_1) })
12
+ end
13
+
14
+ def get(key)
15
+ super(transform_key(key))
16
+ end
17
+ alias_method :[], :get
18
+
19
+ private
20
+
21
+ def transform_key(key)
22
+ key.to_s.tr("/", "_")
23
+ end
24
+ end
25
+
26
+ # Number of files that can be uploaded at once
27
+ option :upload_limit, :integer, default: 50
28
+ # Megabytes
29
+ option :file_size_limit, :integer, default: 100
30
+
31
+ configuration :allowed_filetypes, AllowedFileTypes
32
+ end
33
+ end
34
+ end
@@ -16,13 +16,29 @@ module Alchemy
16
16
  end
17
17
  end
18
18
 
19
- initializer "alchemy.non_digest_assets" do
20
- NonStupidDigestAssets.whitelist += [/^tinymce\//]
19
+ initializer "alchemy.assets" do |app|
20
+ if defined?(Sprockets)
21
+ require_relative "../non_stupid_digest_assets"
22
+ NonStupidDigestAssets.whitelist += [/^tinymce\//]
23
+ app.config.assets.precompile << "alchemy_manifest.js"
24
+ end
21
25
  end
22
26
 
23
27
  initializer "alchemy.admin_stylesheets" do |app|
24
- Alchemy.admin_stylesheets.each do |stylesheet|
25
- app.config.assets.precompile << stylesheet
28
+ if defined?(Sprockets)
29
+ Alchemy.admin_stylesheets.each do |stylesheet|
30
+ app.config.assets.precompile << stylesheet
31
+ end
32
+ end
33
+ end
34
+
35
+ initializer "alchemy.propshaft" do |app|
36
+ if defined?(Propshaft)
37
+ if app.config.assets.server
38
+ # Monkey-patch Propshaft::Asset to enable access
39
+ # of TinyMCE assets without a hash digest.
40
+ require_relative "propshaft/tinymce_asset"
41
+ end
26
42
  end
27
43
  end
28
44
 
@@ -46,27 +62,36 @@ module Alchemy
46
62
  end
47
63
  end
48
64
 
49
- initializer "alchemy.watch_definition_changes" do |app|
50
- elements_reloader = app.config.file_watcher.new([ElementDefinition.definitions_file_path]) do
51
- Rails.logger.info "[#{engine_name}] Reloading Element Definitions."
65
+ # All the initialization that needs to be re-triggered during reloads
66
+ config.to_prepare do
67
+ # Definition files
68
+ elements_reloader = Rails.application.config.file_watcher.new([ElementDefinition.definitions_file_path]) do
69
+ Rails.logger.info "[alchemy] Reloading Element Definitions."
52
70
  ElementDefinition.reset!
53
71
  end
54
- page_layouts_reloader = app.config.file_watcher.new([PageLayout.layouts_file_path]) do
55
- Rails.logger.info "[#{engine_name}] Reloading Page Layouts."
56
- PageLayout.reset!
72
+ page_layouts_reloader = Rails.application.config.file_watcher.new([PageDefinition.layouts_file_path]) do
73
+ Rails.logger.info "[alchemy] Reloading Page Layouts."
74
+ PageDefinition.reset!
57
75
  end
58
76
  [elements_reloader, page_layouts_reloader].each do |reloader|
59
- app.reloaders << reloader
60
- app.reloader.to_run do
77
+ Rails.application.reloaders << reloader
78
+ Rails.application.reloader.to_run do
61
79
  reloader.execute_if_updated
62
80
  end
63
81
  end
64
- end
65
82
 
66
- # Gutentag downcases all tags before save
67
- # and Gutentag validations are not case sensitive.
68
- # But we support having tags with uppercase characters.
69
- config.to_prepare do
83
+ # The storage adapter for Pictures and Attachments
84
+ #
85
+ # Chose between 'active_storage' (default) or 'dragonfly' (legacy)
86
+ #
87
+ # Can be set via 'ALCHEMY_STORAGE_ADAPTER' env var.
88
+ Alchemy.storage_adapter = Alchemy::StorageAdapter.new(
89
+ ENV.fetch("ALCHEMY_STORAGE_ADAPTER", Alchemy.config.storage_adapter)
90
+ )
91
+
92
+ # Gutentag downcases all tags before save
93
+ # and Gutentag validations are not case sensitive.
94
+ # But we support having tags with uppercase characters.
70
95
  Gutentag.normaliser = ->(value) { value.to_s }
71
96
  Gutentag.tag_validations = Alchemy::TagValidations
72
97
  end
@@ -79,13 +104,36 @@ module Alchemy
79
104
  down_arrow: '<alchemy-icon name="arrow-down" size="1x"></alchemy-icon>'
80
105
  }
81
106
  end
107
+
108
+ ActiveSupport.on_load(:active_storage_blob) do
109
+ ActiveStorage::Blob.define_singleton_method(:ransackable_attributes) do |_auth_object|
110
+ %w[filename]
111
+ end
112
+ end
113
+ end
114
+
115
+ # Load Alchemy configuration from YAML files
116
+ # in config/alchemy/config.yml and config/alchemy/#{Rails.env}.config.yml
117
+ # if they exist.
118
+ # This has to be done before any app initializers are loaded, so that
119
+ # the configuration is available in all initializers.
120
+ initializer "alchemy.config_yml", before: :load_config_initializers do |app|
121
+ config_directory = Rails.root.join("config", "alchemy")
122
+ main_config = config_directory.join("config.yml")
123
+ env_specific_config = config_directory.join("#{Rails.env}.config.yml")
124
+ if File.exist?(main_config)
125
+ Alchemy.config.set_from_yaml(main_config)
126
+ end
127
+ if File.exist?(env_specific_config)
128
+ Alchemy.config.set_from_yaml(env_specific_config)
129
+ end
82
130
  end
83
131
 
84
132
  config.after_initialize do
85
133
  if Alchemy.user_class
86
134
  ActiveSupport.on_load(:active_record) do
87
135
  Alchemy.user_class.model_stamper
88
- Alchemy.user_class.stampable(stamper_class_name: Alchemy.user_class.name)
136
+ Alchemy.user_class.stampable(stamper_class_name: Alchemy.user_class_name)
89
137
  end
90
138
  end
91
139
 
@@ -96,14 +144,23 @@ module Alchemy
96
144
  end
97
145
  end
98
146
 
99
- initializer "alchemy.webp-mime_type" do
100
- # Rails does not know anything about webp even in 2022
101
- unless Mime::Type.lookup_by_extension(:webp)
102
- Mime::Type.register("image/webp", :webp)
103
- end
147
+ initializer "alchemy.webp-mime_type" do |app|
148
+ webp = "image/webp"
149
+
104
150
  # Dragonfly uses Rack to read the mime type and guess what
151
+ # Rack 3.0 has this included, but Rails 8 still allows Rack 2.2
105
152
  unless Rack::Mime::MIME_TYPES[".webp"]
106
- Rack::Mime::MIME_TYPES[".webp"] = "image/webp"
153
+ Rack::Mime::MIME_TYPES[".webp"] = webp
154
+ end
155
+
156
+ # ActiveStorage 7.2 adds support for webp, but we still support Rails 7.1
157
+ if app.config.active_storage
158
+ unless app.config.active_storage.web_image_content_types.include? webp
159
+ app.config.active_storage.web_image_content_types += [webp]
160
+ end
161
+ unless app.config.active_storage.content_types_allowed_inline.include? webp
162
+ app.config.active_storage.content_types_allowed_inline += [webp]
163
+ end
107
164
  end
108
165
  end
109
166
  end
data/lib/alchemy/hints.rb CHANGED
@@ -35,7 +35,7 @@ module Alchemy
35
35
  # @return String
36
36
  #
37
37
  def hint
38
- hint = definition[:hint]
38
+ hint = attributes[:hint]
39
39
  if hint == true
40
40
  Alchemy.t(hint_translation_attribute, scope: hint_translation_scope)
41
41
  else
@@ -43,9 +43,9 @@ module Alchemy
43
43
  end
44
44
  end
45
45
 
46
- # Returns true if the element has a hint defined
46
+ # Returns true if the definition has a hint defined
47
47
  def has_hint?
48
- !!definition[:hint]
48
+ !!attributes[:hint]
49
49
  end
50
50
 
51
51
  private
@@ -53,9 +53,5 @@ module Alchemy
53
53
  def hint_translation_attribute
54
54
  name
55
55
  end
56
-
57
- def hint_translation_scope
58
- "#{self.class.model_name.to_s.demodulize.downcase}_hints"
59
- end
60
56
  end
61
57
  end
@@ -22,18 +22,6 @@ module Alchemy
22
22
  {after: SENTINEL, verbose: true}
23
23
  end
24
24
 
25
- def set_primary_language(code: "en", name: "English", auto_accept: false)
26
- unless auto_accept
27
- code = ask("- What is the language code of your site's primary language?", default: code)
28
- end
29
- unless auto_accept
30
- name = ask("- What is the name of your site's primary language?", default: name)
31
- end
32
- gsub_file "./config/alchemy/config.yml", /default_language:\n\s\scode:\sen\n\s\sname:\sEnglish/m do
33
- "default_language:\n code: #{code}\n name: #{name}"
34
- end
35
- end
36
-
37
25
  def inject_seeder
38
26
  seed_file = Rails.root.join("db", "seeds.rb")
39
27
  args = [seed_file, "Alchemy::Seeder.seed!\n"]
@@ -9,7 +9,7 @@ module Alchemy
9
9
  # Pass a block or method name in which you have the +@page+ object available and can do
10
10
  # everything as if you were in a normal controller action.
11
11
  #
12
- # You can pass a +Alchemy::PageLayout+ name, an array of names, or +:all+ to
12
+ # You can pass a +Alchemy::PageDefinition+ name, an array of names, or +:all+ to
13
13
  # evaluate the callback on either some specific or all the pages.
14
14
  #
15
15
  # == Example:
@@ -56,7 +56,7 @@ module Alchemy
56
56
  # Pass a block or method name in which you have the +@page+ object available and can do
57
57
  # everything as if you were in a normal controller action.
58
58
  #
59
- # Pass a +Alchemy::PageLayout+ name, an array of names, or +:all+ to
59
+ # Pass a +Alchemy::PageDefinition+ name, an array of names, or +:all+ to
60
60
  # evaluate the callback on either some specific or all the pages.
61
61
  #
62
62
  def on_page_layout(page_layouts, callback = nil, &block)
@@ -0,0 +1,15 @@
1
+ require "propshaft/asset"
2
+
3
+ module Alchemy
4
+ module Propshaft
5
+ module TinymceAsset
6
+ # Allow TinyMCE assets to be accessed (in development mode) without a digest
7
+ def fresh?(digest)
8
+ super ||
9
+ (digest.blank? && logical_path.to_s.starts_with?("tinymce/"))
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Propshaft::Asset.prepend Alchemy::Propshaft::TinymceAsset
@@ -116,7 +116,7 @@ module Alchemy
116
116
  # If no languages are present, create a default language based
117
117
  # on the host app's Alchemy configuration.
118
118
  def create_default_language!
119
- default_language = Alchemy::Config.get(:default_language)
119
+ default_language = Alchemy.config.default_language
120
120
  if default_language
121
121
  Alchemy::Language.create!(
122
122
  name: default_language["name"],
@@ -134,7 +134,7 @@ module Alchemy
134
134
  end
135
135
 
136
136
  def create_default_site!
137
- default_site = Alchemy::Config.get(:default_site)
137
+ default_site = Alchemy.config.default_site
138
138
  if default_site
139
139
  Alchemy::Site.create!(name: default_site["name"], host: default_site["host"])
140
140
  else
@@ -73,6 +73,24 @@ module Alchemy
73
73
  log "Deleted #{count} duplicate legacy URLs"
74
74
  end
75
75
 
76
+ def remove_legacy_essence_tables
77
+ puts "\n## Removing legacy essence tables"
78
+ matching_tables = ActiveRecord::Base.connection.tables.select do |table|
79
+ table.start_with?("alchemy_essence_")
80
+ end
81
+ if matching_tables.length.zero?
82
+ log "No legacy essence tables found", :skip
83
+ nil
84
+ else
85
+ matching_tables.each do |table|
86
+ ActiveRecord::Base.connection.drop_table(table, if_exists: true)
87
+ print "."
88
+ end
89
+ puts "\n"
90
+ log "Deleted #{matching_tables.length} legacy essence tables"
91
+ end
92
+ end
93
+
76
94
  private
77
95
 
78
96
  def destroy_orphaned_records(records, class_name)
@@ -11,8 +11,8 @@ module Alchemy
11
11
  .group(:name)
12
12
  .order("count DESC, name ASC")
13
13
  .map { |e| {"name" => e.name, "count" => e.count} }
14
- Alchemy::Element.definitions.reject { |definition| res.map { |e| e["name"] }.include? definition["name"] }.sort_by { |d| d["name"] }.each do |definition|
15
- res << {"name" => definition["name"], "count" => 0}
14
+ Alchemy::Element.definitions.reject { |definition| res.map { |e| e["name"] }.include?(definition.name) }.sort_by(&:name).each do |definition|
15
+ res << {"name" => definition.name, "count" => 0}
16
16
  end
17
17
  res
18
18
  end
@@ -23,8 +23,8 @@ module Alchemy
23
23
  .group(:page_layout)
24
24
  .order("count DESC, page_layout ASC")
25
25
  .map { |p| {"page_layout" => p.page_layout, "count" => p.count} }
26
- Alchemy::PageLayout.all.reject { |page_layout| res.map { |p| p["page_layout"] }.include? page_layout["name"] }.sort_by { |d| d["name"] }.each do |page_layout|
27
- res << {"page_layout" => page_layout["name"], "count" => 0}
26
+ Alchemy::PageDefinition.all.reject { |page_layout| res.map { |p| p["page_layout"] }.include?(page_layout.name) }.sort_by(&:name).each do |page_layout|
27
+ res << {"page_layout" => page_layout.name, "count" => 0}
28
28
  end
29
29
  res
30
30
  end
@@ -17,13 +17,7 @@ module Alchemy
17
17
  # @param value [Object] The value you want to return instead of the original one
18
18
  #
19
19
  def stub_alchemy_config(key, value)
20
- allow(Alchemy::Config).to receive(:get) do |arg|
21
- if arg == key
22
- value
23
- else
24
- Alchemy::Config.show[arg.to_s]
25
- end
26
- end
20
+ allow(Alchemy.config).to receive(key).and_return(value)
27
21
  end
28
22
  end
29
23
  end
@@ -2,9 +2,20 @@
2
2
 
3
3
  FactoryBot.define do
4
4
  factory :alchemy_attachment, class: "Alchemy::Attachment" do
5
- file do
6
- File.new(Alchemy::Engine.root.join("lib", "alchemy", "test_support", "fixtures", "image.png"))
5
+ transient do
6
+ file do
7
+ Rack::Test::UploadedFile.new(
8
+ Alchemy::Engine.root.join("lib", "alchemy", "test_support", "fixtures", "image.png")
9
+ )
10
+ end
7
11
  end
12
+
13
+ after(:build) do |attachment, acc|
14
+ if acc.file
15
+ attachment.file = acc.file
16
+ end
17
+ end
18
+
8
19
  name { "image" }
9
20
  file_name { "image.png" }
10
21
  end
@@ -6,7 +6,7 @@ FactoryBot.define do
6
6
  code { ::I18n.available_locales.first.to_s }
7
7
  default { true }
8
8
  frontpage_name { "Intro" }
9
- page_layout { Alchemy::Config.get(:default_language)["page_layout"] }
9
+ page_layout { Alchemy.config.default_language.page_layout }
10
10
 
11
11
  public { true }
12
12