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
@@ -30,7 +30,6 @@ module Alchemy
30
30
 
31
31
  include Alchemy::Logger
32
32
  include Alchemy::Taggable
33
- include Alchemy::Hints
34
33
 
35
34
  FORBIDDEN_DEFINITION_ATTRIBUTES = [
36
35
  "amount",
@@ -38,6 +37,7 @@ module Alchemy
38
37
  "compact",
39
38
  "deprecated",
40
39
  "hint",
40
+ "icon",
41
41
  "ingredients",
42
42
  "message",
43
43
  "nestable_elements",
@@ -55,7 +55,7 @@ module Alchemy
55
55
  #
56
56
  acts_as_list scope: [:page_version_id, :fixed, :parent_element_id]
57
57
 
58
- stampable stamper_class_name: Alchemy.user_class.name
58
+ stampable stamper_class_name: Alchemy.user_class_name
59
59
 
60
60
  before_destroy :delete_all_nested_elements
61
61
 
@@ -107,6 +107,7 @@ module Alchemy
107
107
  scope :not_nested, -> { where(parent_element_id: nil) }
108
108
 
109
109
  delegate :restricted?, to: :page, allow_nil: true
110
+ delegate :deprecation_notice, :has_hint?, :hint, to: :definition
110
111
 
111
112
  # Concerns
112
113
  include Definitions
@@ -131,27 +132,9 @@ module Alchemy
131
132
  raise(ElementDefinitionError, attributes)
132
133
  end
133
134
 
134
- super(element_definition.merge(element_attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES))
135
+ super(element_definition.attributes.merge(element_attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES))
135
136
  end
136
137
 
137
- # The class responsible for the +dom_id+ of elements.
138
- # Defaults to +Alchemy::Element::DomId+.
139
- # @deprecated
140
- def dom_id_class
141
- if caller.none? { |l| l =~ Regexp.new("alchemy/element/presenters.rb:87:in `dom_id'") }
142
- Alchemy::Deprecation.warn("dom_id_class is deprecated and will be removed from Alchemy 8.0. Please pass an id to the element_view_for helper instead.")
143
- end
144
- @_dom_id_class || DomId
145
- end
146
-
147
- # Register a custom +DomId+ class responsible for the +dom_id+ of elements.
148
- # Defaults to +Alchemy::Element::DomId+.
149
- # @deprecated
150
- def dom_id_class=(klass)
151
- @_dom_id_class = klass
152
- end
153
- deprecate :dom_id_class=, deprecator: Alchemy::Deprecation
154
-
155
138
  # This methods does a copy of source and all its ingredients.
156
139
  #
157
140
  # == Options
@@ -185,7 +168,7 @@ module Alchemy
185
168
  def all_from_clipboard_for_parent_element(clipboard, parent_element)
186
169
  return none if clipboard.nil? || parent_element.nil?
187
170
 
188
- all_from_clipboard(clipboard).where(name: parent_element.definition["nestable_elements"])
171
+ all_from_clipboard(clipboard).where(name: parent_element.definition.nestable_elements)
189
172
  end
190
173
  end
191
174
 
@@ -229,7 +212,7 @@ module Alchemy
229
212
 
230
213
  # Returns true if the definition of this element has a taggable true value.
231
214
  def taggable?
232
- definition["taggable"] == true
215
+ definition.taggable == true
233
216
  end
234
217
 
235
218
  # The opposite of folded?
@@ -239,7 +222,7 @@ module Alchemy
239
222
 
240
223
  # Defined as compact element?
241
224
  def compact?
242
- definition["compact"] == true
225
+ definition.compact
243
226
  end
244
227
 
245
228
  # Defined as deprecated element?
@@ -271,7 +254,7 @@ module Alchemy
271
254
  #
272
255
  # @return Boolean
273
256
  def deprecated?
274
- !!definition["deprecated"]
257
+ !!definition.deprecated
275
258
  end
276
259
 
277
260
  # The element's view partial is dependent from its name
@@ -294,13 +277,13 @@ module Alchemy
294
277
 
295
278
  # A collection of element names that can be nested inside this element.
296
279
  def nestable_elements
297
- definition.fetch("nestable_elements", [])
280
+ definition.nestable_elements
298
281
  end
299
282
 
300
283
  private
301
284
 
302
285
  def generate_nested_elements
303
- definition.fetch("autogenerate", []).each do |nestable_element|
286
+ definition.autogenerate.each do |nestable_element|
304
287
  if nestable_elements.include?(nestable_element)
305
288
  Element.create(page_version: page_version, parent_element_id: id, name: nestable_element)
306
289
  else
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class ElementDefinition
5
+ include ActiveModel::Model
6
+ include ActiveModel::Attributes
7
+ include Alchemy::Hints
8
+
9
+ extend ActiveModel::Translation
10
+
11
+ attribute :name, :string
12
+ attribute :unique, :boolean, default: false
13
+ attribute :amount, :integer, default: Float::INFINITY
14
+ attribute :taggable, :boolean, default: false
15
+ attribute :compact, :boolean, default: false
16
+ attribute :fixed, :boolean, default: false
17
+ attribute :ingredients, default: []
18
+ attribute :nestable_elements, default: []
19
+ attribute :autogenerate, default: []
20
+ attribute :deprecated
21
+ attribute :message
22
+ attribute :warning
23
+ attribute :hint
24
+ attribute :icon
25
+
26
+ validates :name,
27
+ presence: true,
28
+ format: {
29
+ with: /\A[a-z_-]+\z/
30
+ }
31
+
32
+ validates :icon,
33
+ format: {with: /\A[\w-]+\z/i},
34
+ if: -> { icon.is_a?(String) }
35
+
36
+ delegate :blank?, to: :name
37
+
38
+ class << self
39
+ # Returns the definitions from elements.yml file.
40
+ #
41
+ # Place a +elements.yml+ file inside your apps +config/alchemy+ folder to define
42
+ # your own set of elements
43
+ #
44
+ def all
45
+ @definitions ||= read_definitions_file.map { new(**_1) }
46
+ end
47
+
48
+ # Add additional page definitions to collection.
49
+ #
50
+ # Useful for extending the elements from an Alchemy module.
51
+ #
52
+ # === Usage Example
53
+ #
54
+ # Call +Alchemy::ElementDefinition.add(your_definition)+ in your engine.rb file.
55
+ #
56
+ # @param [Array || Hash]
57
+ # You can pass a single element definition as Hash, or a collection of elements as Array.
58
+ #
59
+ def add(definition)
60
+ all
61
+ @definitions += Array.wrap(definition).map { new(**_1) }
62
+ end
63
+
64
+ # Returns one element definition by given name.
65
+ #
66
+ def get(name)
67
+ return new if name.blank?
68
+
69
+ all.detect { _1.name.casecmp(name).zero? }
70
+ end
71
+
72
+ def reset!
73
+ @definitions = nil
74
+ end
75
+
76
+ # The absolute +elements.yml+ file path
77
+ # @return [Pathname]
78
+ def definitions_file_path
79
+ Rails.root.join("config", "alchemy", "elements.yml")
80
+ end
81
+
82
+ private
83
+
84
+ def definitions_file
85
+ File.read(definitions_file_path)
86
+ end
87
+
88
+ def definitions_file_exist?
89
+ File.exist?(definitions_file_path)
90
+ end
91
+
92
+ # Reads the element definitions from +config/alchemy/elements.yml+.
93
+ #
94
+ def read_definitions_file
95
+ if definitions_file_exist?
96
+ YAML.safe_load(
97
+ ERB.new(definitions_file).result,
98
+ permitted_classes: YAML_PERMITTED_CLASSES,
99
+ aliases: true
100
+ ) || []
101
+ else
102
+ raise LoadError,
103
+ "Could not find elements.yml file! Please run `rails generate alchemy:install`"
104
+ end
105
+ end
106
+ end
107
+
108
+ def attributes
109
+ super.with_indifferent_access
110
+ end
111
+
112
+ def ingredients
113
+ super.map { IngredientDefinition.new(**_1) }
114
+ end
115
+
116
+ # Returns a deprecation notice for elements marked deprecated
117
+ #
118
+ # You can either use localizations or pass a String as notice
119
+ # in the element definition.
120
+ #
121
+ # == Custom deprecation notices
122
+ #
123
+ # Use general element deprecation notice
124
+ #
125
+ # - name: old_element
126
+ # deprecated: true
127
+ #
128
+ # Add a translation to your locale file for a per element notice.
129
+ #
130
+ # en:
131
+ # alchemy:
132
+ # element_deprecation_notices:
133
+ # old_element: Foo baz widget is deprecated
134
+ #
135
+ # or use the global translation that apply to all deprecated elements.
136
+ #
137
+ # en:
138
+ # alchemy:
139
+ # element_deprecation_notice: Foo baz widget is deprecated
140
+ #
141
+ # or pass string as deprecation notice.
142
+ #
143
+ # - name: old_element
144
+ # deprecated: This element will be removed soon.
145
+ #
146
+ def deprecation_notice
147
+ case deprecated
148
+ when String
149
+ deprecated
150
+ when TrueClass
151
+ Alchemy.t(
152
+ name,
153
+ scope: :element_deprecation_notices,
154
+ default: Alchemy.t(:element_deprecated)
155
+ )
156
+ end
157
+ end
158
+
159
+ def icon_file
160
+ @_icon_file ||= File.read(icon_file_path).html_safe
161
+ end
162
+
163
+ def icon_file_name
164
+ "#{icon_name}.svg"
165
+ end
166
+
167
+ def icon_name
168
+ case icon
169
+ when TrueClass then name
170
+ when String then icon
171
+ else
172
+ "default"
173
+ end
174
+ end
175
+
176
+ private
177
+
178
+ def icon_file_path
179
+ icons_root_path.join("app/assets/images/alchemy/element_icons", icon_file_name)
180
+ end
181
+
182
+ def icons_root_path
183
+ icon.nil? ? Alchemy::Engine.root : Rails.root
184
+ end
185
+
186
+ def hint_translation_scope
187
+ :element_hints
188
+ end
189
+ end
190
+ end
@@ -4,8 +4,6 @@ module Alchemy
4
4
  class Ingredient < BaseRecord
5
5
  class DefinitionError < StandardError; end
6
6
 
7
- include Hints
8
-
9
7
  self.table_name = "alchemy_ingredients"
10
8
 
11
9
  attribute :data, :json
@@ -16,7 +14,7 @@ module Alchemy
16
14
  has_one :page, through: :element, class_name: "Alchemy::Page"
17
15
 
18
16
  after_initialize :set_default_value,
19
- if: -> { definition.key?(:default) && value.nil? }
17
+ if: -> { definition.default && value.nil? }
20
18
 
21
19
  validates :type, presence: true
22
20
  validates :role, presence: true, uniqueness: {scope: :element_id, case_sensitive: false}
@@ -38,6 +36,8 @@ module Alchemy
38
36
  scope :texts, -> { where(type: "Alchemy::Ingredients::Text") }
39
37
  scope :videos, -> { where(type: "Alchemy::Ingredients::Video") }
40
38
 
39
+ delegate :has_hint?, :hint, to: :definition
40
+
41
41
  class << self
42
42
  # Defines getter and setter method aliases for related object
43
43
  #
@@ -97,15 +97,15 @@ module Alchemy
97
97
 
98
98
  # Settings for this ingredient from the +elements.yml+ definition.
99
99
  def settings
100
- definition[:settings] || {}
100
+ definition.settings
101
101
  end
102
102
 
103
103
  # Definition hash for this ingredient from +elements.yml+ file.
104
104
  #
105
105
  def definition
106
- return {} unless element
106
+ return IngredientDefinition.new unless element
107
107
 
108
- element.ingredient_definition_for(role) || {}
108
+ element.ingredient_definition_for(role) || IngredientDefinition.new
109
109
  end
110
110
 
111
111
  # The first 30 characters of the value
@@ -118,12 +118,6 @@ module Alchemy
118
118
  value.to_s[0..maxlength - 1]
119
119
  end
120
120
 
121
- # The path to the view partial of the ingredient
122
- # @return [String]
123
- def to_partial_path
124
- "alchemy/ingredients/#{partial_name}_view"
125
- end
126
-
127
121
  # The demodulized underscored class name of the ingredient
128
122
  # @return [String]
129
123
  def partial_name
@@ -132,17 +126,12 @@ module Alchemy
132
126
 
133
127
  # @return [Boolean]
134
128
  def has_validations?
135
- !!definition[:validate]
136
- end
137
-
138
- # @return [Boolean]
139
- def has_hint?
140
- !!definition[:hint]
129
+ definition.validate.any?
141
130
  end
142
131
 
143
132
  # @return [Boolean]
144
133
  def deprecated?
145
- !!definition[:deprecated]
134
+ !!definition.deprecated
146
135
  end
147
136
 
148
137
  # @return [Boolean]
@@ -152,7 +141,7 @@ module Alchemy
152
141
 
153
142
  # @return [Boolean]
154
143
  def preview_ingredient?
155
- !!definition[:as_element_title]
144
+ !!definition.as_element_title
156
145
  end
157
146
 
158
147
  # The view component of the ingredient with mapped options.
@@ -169,30 +158,8 @@ module Alchemy
169
158
  @_view_component_class ||= "#{self.class.name}View".constantize
170
159
  end
171
160
 
172
- def hint_translation_attribute
173
- role
174
- end
175
-
176
- def hint_translation_scope
177
- "ingredient_hints"
178
- end
179
-
180
161
  def set_default_value
181
- self.value = default_value
182
- end
183
-
184
- # Returns the default value from ingredient definition
185
- #
186
- # If the value is a symbol it gets passed through i18n
187
- # inside the +alchemy.default_ingredient_texts+ scope
188
- def default_value
189
- default = definition[:default]
190
- case default
191
- when Symbol
192
- Alchemy.t(default, scope: :default_ingredient_texts)
193
- else
194
- default
195
- end
162
+ self.value = definition.default_value
196
163
  end
197
164
  end
198
165
  end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class IngredientDefinition
5
+ include ActiveModel::Model
6
+ include ActiveModel::Attributes
7
+ include Alchemy::Hints
8
+
9
+ extend ActiveModel::Translation
10
+
11
+ attribute :role, :string
12
+ attribute :type, :string
13
+ attribute :as_element_title, :boolean, default: false
14
+ attribute :settings, default: {}
15
+ attribute :validate, default: []
16
+ attribute :group, :string
17
+ attribute :default
18
+ attribute :deprecated
19
+ attribute :hint
20
+
21
+ validates :role,
22
+ presence: true,
23
+ format: {
24
+ with: /\A[a-z_-]+\z/
25
+ }
26
+
27
+ validates :type,
28
+ presence: true,
29
+ format: {
30
+ with: /\A[A-Z][a-zA-Z]+\z/
31
+ }
32
+
33
+ delegate :blank?, to: :role
34
+
35
+ def attributes
36
+ super.with_indifferent_access
37
+ end
38
+
39
+ def settings
40
+ super.with_indifferent_access
41
+ end
42
+
43
+ def validate
44
+ super.map do |validation|
45
+ case validation
46
+ when Hash
47
+ validation.with_indifferent_access
48
+ else
49
+ validation
50
+ end
51
+ end
52
+ end
53
+
54
+ # Returns the default value from ingredient definition
55
+ #
56
+ # If the value is a symbol it gets passed through i18n
57
+ # inside the +alchemy.default_ingredient_texts+ scope
58
+ def default_value
59
+ case default
60
+ when Symbol
61
+ Alchemy.t(default, scope: :default_ingredient_texts)
62
+ when String
63
+ default
64
+ end
65
+ end
66
+
67
+ # Returns a deprecation notice for ingredients marked deprecated
68
+ #
69
+ # You can either use localizations or pass a String as notice
70
+ # in the ingredient definition.
71
+ #
72
+ # == Custom deprecation notices
73
+ #
74
+ # Use general ingredient deprecation notice
75
+ #
76
+ # - name: element_name
77
+ # ingredients:
78
+ # - role: old_ingredient
79
+ # type: Text
80
+ # deprecated: true
81
+ #
82
+ # Add a translation to your locale file for a per ingredient notice.
83
+ #
84
+ # en:
85
+ # alchemy:
86
+ # ingredient_deprecation_notices:
87
+ # old_ingredient: Foo baz widget is deprecated
88
+ #
89
+ # You can scope the translation per element as well.
90
+ #
91
+ # en:
92
+ # alchemy:
93
+ # ingredient_deprecation_notices:
94
+ # element_name:
95
+ # old_ingredient: Elements foo baz widget is deprecated
96
+ #
97
+ # or use the global translation that apply to all deprecated ingredients.
98
+ #
99
+ # en:
100
+ # alchemy:
101
+ # ingredient_deprecation_notice: Foo baz widget is deprecated
102
+ #
103
+ # or pass string as deprecation notice.
104
+ #
105
+ # - name: element_name
106
+ # ingredients:
107
+ # - role: old_ingredient
108
+ # type: Text
109
+ # deprecated: This ingredient will be removed soon.
110
+ #
111
+ def deprecation_notice(element_name: nil)
112
+ case deprecated
113
+ when String
114
+ deprecated
115
+ when TrueClass
116
+ Alchemy.t(
117
+ role,
118
+ scope: [:ingredient_deprecation_notices, element_name].compact,
119
+ default: Alchemy.t(:ingredient_deprecated)
120
+ )
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def hint_translation_scope
127
+ :ingredient_hints
128
+ end
129
+
130
+ def hint_translation_attribute
131
+ role
132
+ end
133
+ end
134
+ end
@@ -64,7 +64,7 @@ module Alchemy
64
64
  attr_reader :ingredient
65
65
 
66
66
  def validations
67
- ingredient.definition.fetch(:validate, [])
67
+ ingredient.definition.validate
68
68
  end
69
69
 
70
70
  def validate_presence(*)
@@ -80,8 +80,12 @@ module Alchemy
80
80
  end
81
81
 
82
82
  def validate_format(format)
83
- matcher = Alchemy::Config.get("format_matchers")[format] || format
84
- if !ingredient.value.to_s.match?(Regexp.new(matcher))
83
+ matcher = if format.is_a?(String) || format.is_a?(Symbol)
84
+ Alchemy.config.format_matchers.get(format)
85
+ else
86
+ format
87
+ end
88
+ if !ingredient.value.to_s.match?(matcher)
85
89
  ingredient.errors.add(:value, :invalid)
86
90
  end
87
91
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Ingredients
5
+ # A number
6
+ #
7
+ # Either a decimal or integer number
8
+ #
9
+ class Number < Alchemy::Ingredient
10
+ allow_settings %i[
11
+ input_type
12
+ step
13
+ min
14
+ max
15
+ unit
16
+ ]
17
+ end
18
+ end
19
+ end
@@ -25,8 +25,8 @@ require_dependency "alchemy/site"
25
25
  module Alchemy
26
26
  class Language < BaseRecord
27
27
  belongs_to :site
28
- has_many :pages, inverse_of: :language
29
- has_many :nodes, inverse_of: :language
28
+ has_many :pages, inverse_of: :language, dependent: :restrict_with_error
29
+ has_many :nodes, inverse_of: :language, dependent: :restrict_with_error
30
30
 
31
31
  before_validation :set_locale, if: -> { locale.blank? }
32
32
 
@@ -54,11 +54,6 @@ module Alchemy
54
54
  after_update :set_pages_language,
55
55
  if: :should_set_pages_language?
56
56
 
57
- before_destroy if: -> { pages.any? } do
58
- errors.add(:pages, :still_present)
59
- throw(:abort)
60
- end
61
-
62
57
  scope :published, -> { where(public: true) }
63
58
  scope :with_root_page, -> { joins(:pages).where(Page.table_name => {language_root: true}) }
64
59
 
@@ -71,20 +66,6 @@ module Alchemy
71
66
  on_site(Current.site)
72
67
  end
73
68
 
74
- # Store the current language in the current thread.
75
- # @deprecated Use {Alchemy::Current#language=} instead.
76
- def current=(language)
77
- Current.language = language
78
- end
79
- deprecate "current=": :"Alchemy::Current.language=", deprecator: Alchemy::Deprecation
80
-
81
- # Current language from current thread or default.
82
- # @deprecated Use {Alchemy::Current#language} instead.
83
- def current
84
- Current.language
85
- end
86
- deprecate current: :"Alchemy::Current.language", deprecator: Alchemy::Deprecation
87
-
88
69
  # The root page of the current language.
89
70
  def current_root_page
90
71
  Current.language&.pages&.language_roots&.first
@@ -16,23 +16,19 @@ module Alchemy
16
16
  class Message
17
17
  include ActiveModel::Model
18
18
 
19
- def self.config
20
- Alchemy::Config.get(:mailer)
21
- end
22
-
23
19
  attr_accessor :contact_form_id, :ip
24
20
 
25
- config["fields"].each do |field|
21
+ Alchemy.config.mailer.fields.each do |field|
26
22
  attr_accessor field.to_sym
27
23
  end
28
24
 
29
- config["validate_fields"].each do |field|
25
+ Alchemy.config.mailer.validate_fields.each do |field|
30
26
  validates_presence_of field
31
27
 
32
28
  case field.to_sym
33
29
  when /email/
34
30
  validates_format_of field,
35
- with: Alchemy::Config.get("format_matchers")["email"],
31
+ with: Alchemy.config.format_matchers.email,
36
32
  if: -> { send(field).present? }
37
33
  when :email_confirmation
38
34
  validates_confirmation_of :email
@@ -7,7 +7,7 @@ module Alchemy
7
7
  before_destroy :check_if_related_node_ingredients_present
8
8
 
9
9
  acts_as_nested_set scope: "language_id", touch: true
10
- stampable stamper_class_name: Alchemy.user_class.name
10
+ stampable stamper_class_name: Alchemy.user_class_name
11
11
 
12
12
  belongs_to :language, class_name: "Alchemy::Language"
13
13
  belongs_to :page, class_name: "Alchemy::Page", optional: true, inverse_of: :nodes