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
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Mime::Type.register "image/svg+xml", :svg
4
+ Mime::Type.register "image/webp", :webp
@@ -89,6 +89,7 @@ en:
89
89
  image/tiff: TIFF Image
90
90
  image/x-psd: Photoshop File
91
91
  image/svg+xml: SVG Image
92
+ image/webp: WebP Image
92
93
  text/plain: Plain Text Document
93
94
  text/x-vcard: vCard
94
95
  video/mp4: MPEG-4 Video
@@ -104,41 +105,59 @@ en:
104
105
  filters:
105
106
  page:
106
107
  by_page_layout:
108
+ all: All page types
107
109
  name: Page Type
108
110
  status:
111
+ all: Do not filter
109
112
  name: Status
110
113
  values:
111
114
  not_public: Unpublished
112
115
  published: Published
113
116
  restricted: Restricted
117
+ not_public:
118
+ name: Unpublished
119
+ published:
120
+ name: Published
121
+ restricted:
122
+ name: Restricted
123
+ updated_at_lteq:
124
+ name: Updated before
125
+ updated_at_gteq:
126
+ name: Updated after
114
127
  picture:
115
128
  by_file_format:
129
+ all: Show all file types
116
130
  name: File Type
117
131
  values:
132
+ <<: *mime_types
118
133
  gif: GIF Image
119
134
  jpeg: JPG Image
120
135
  png: PNG Image
121
136
  svg: SVG Image
122
137
  tiff: TIFF Image
123
138
  webp: WebP Image
124
- misc:
125
- name: Miscellaneous
126
- values:
127
- last_upload: Last upload only
128
- recent: Recently uploaded only
129
- without_tag: Without tag
130
- deletable: Not used
139
+ last_upload:
140
+ name: Last upload only
141
+ recent:
142
+ name: Recently uploaded only
143
+ without_tag:
144
+ name: Without tag
145
+ deletable:
146
+ name: Not used
131
147
  attachment:
132
148
  by_file_type:
133
149
  name: File Type
134
150
  values:
135
151
  <<: *mime_types
136
- misc:
137
- name: Miscellaneous
138
- values:
139
- last_upload: Last upload only
140
- recent: Recently uploaded only
141
- without_tag: Without tag
152
+ all: Show all file types
153
+ last_upload:
154
+ name: Last upload only
155
+ recent:
156
+ name: Recently uploaded only
157
+ without_tag:
158
+ name: Without tag
159
+ deletable:
160
+ name: Not used
142
161
 
143
162
  default_ingredient_texts:
144
163
  lorem: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
@@ -207,6 +226,11 @@ en:
207
226
  elements:
208
227
  toolbar:
209
228
  hide: Hide
229
+ pictures:
230
+ sorting_select:
231
+ label: "Sorting"
232
+ by_latest: "Latest"
233
+ alphabetical: "A-Z"
210
234
  add_nested_element: "Add %{name}"
211
235
  anchor: "Anchor"
212
236
  anchor_link_headline: You can link to an anchor from the current page.
@@ -316,7 +340,6 @@ en:
316
340
  "Size": "Size"
317
341
  "Successfully deleted element": "Successfully deleted %{element}"
318
342
  "Tags": "Tags"
319
- "These pictures could not be deleted, because they were in use": "These pictures could not be deleted, because they were in use: %{names}"
320
343
  "This page is locked": "This page is currently locked by %{name}"
321
344
  "Title": "Title"
322
345
  "Trash": "Trash"
@@ -544,6 +567,7 @@ en:
544
567
  password: "Password"
545
568
  paste: "paste"
546
569
  pictures_in_page: "%{page} in %{pictures}"
570
+ pictures_will_be_deleted_now: Pictures will be deleted now
547
571
  place_link: "Add link"
548
572
  player_version: "Flash Player version"
549
573
  "please enter subject and mail address": "Please enter recipient and subject."
@@ -604,6 +628,12 @@ en:
604
628
  tag_list: Tags
605
629
  tags_get_created_if_used_the_first_time: "Tags get created if used the first time."
606
630
  this_picture_is_used_on_these_pages: "This picture is used on following pages"
631
+ used_on_these_pages:
632
+ attachment: "This file is used on following pages"
633
+ picture: "This picture is used on following pages"
634
+ not_in_use_yet:
635
+ attachment: "This file is not in use yet."
636
+ picture: "This picture is not in use yet."
607
637
  title: "Title"
608
638
  to_alchemy: "To Alchemy"
609
639
  unknown: "unknown"
@@ -681,7 +711,7 @@ en:
681
711
  time:
682
712
  formats:
683
713
  alchemy:
684
- default: "%m.%d.%Y %H:%M"
714
+ default: "%Y-%m-%d %H:%M"
685
715
  ingredient_date: "%Y-%m-%d"
686
716
  page_status: "%m.%d.%Y %H:%M"
687
717
  short_datetime: "%d %b %H:%M"
@@ -719,6 +749,7 @@ en:
719
749
  attributes:
720
750
  image_file:
721
751
  blank: "Please attach a picture."
752
+ too_big: must not be larger than %{maximum}MB
722
753
  alchemy/user:
723
754
  attributes:
724
755
  email:
@@ -755,16 +786,19 @@ en:
755
786
  node_ingredients_present: "This menu item is in use inside an Alchemy element on the following pages: %{page_names}."
756
787
  alchemy/site:
757
788
  attributes:
758
- languages:
759
- still_present: "are still attached to this site. Please remove them first."
789
+ base:
790
+ restrict_dependent_destroy:
791
+ has_many: "There are still %{record} attached to this site. Please remove them first."
760
792
  alchemy/language:
761
793
  attributes:
762
- pages:
763
- still_present: "are still attached to this language. Please remove them first."
794
+ base:
795
+ restrict_dependent_destroy:
796
+ has_many: "There are still %{record} attached to this language. Please remove them first."
764
797
  alchemy/page:
765
798
  attributes:
766
- nodes:
767
- still_present: "are still attached to this page. Please remove them first."
799
+ base:
800
+ restrict_dependent_destroy:
801
+ has_many: "There are still %{record} attached to this page. Please remove them first."
768
802
  models:
769
803
  gutentag/tag:
770
804
  one: Tag
@@ -851,6 +885,7 @@ en:
851
885
  public: "Public"
852
886
  locale: Localization
853
887
  code: ISO Code
888
+ nodes: Menu nodes
854
889
  alchemy/legacy_page_url:
855
890
  urlname: "URL-Path"
856
891
  alchemy/node:
@@ -869,6 +904,7 @@ en:
869
904
  meta_description: "Description"
870
905
  meta_keywords: "Keywords"
871
906
  name: "Name"
907
+ nodes: "Menu nodes"
872
908
  page_layout: "Page type"
873
909
  public: "public"
874
910
  restricted: "restricted"
data/config/routes.rb CHANGED
@@ -68,7 +68,6 @@ Alchemy::Engine.routes.draw do
68
68
 
69
69
  resources :attachments, except: [:new] do
70
70
  member do
71
- get :download
72
71
  put :assign
73
72
  end
74
73
  end
@@ -114,7 +113,6 @@ Alchemy::Engine.routes.draw do
114
113
  as: :show_attachment
115
114
 
116
115
  resources :messages, only: [:index, :new, :create]
117
- resources :elements, only: :show
118
116
 
119
117
  namespace :api, defaults: {format: "json"} do
120
118
  resources :attachments, only: [:index]
@@ -0,0 +1,14 @@
1
+ class AddCreatedAtIndexToPicturesAndAttachments < ActiveRecord::Migration[7.1]
2
+ disable_ddl_transaction! if connection.adapter_name.match?(/postgres/i)
3
+
4
+ def change
5
+ add_index :alchemy_pictures, :created_at, if_not_exists: true, algorithm: algorithm
6
+ add_index :alchemy_attachments, :created_at, if_not_exists: true, algorithm: algorithm
7
+ end
8
+
9
+ private
10
+
11
+ def algorithm
12
+ connection.adapter_name.match?(/postgres/i) ? :concurrently : nil
13
+ end
14
+ end
@@ -41,7 +41,6 @@ module Alchemy
41
41
 
42
42
  def url_for(page)
43
43
  @preview_config = preview_config_for(page)
44
-
45
44
  if @preview_config && uri
46
45
  uri_class.build(
47
46
  host: uri.host,
@@ -60,10 +59,10 @@ module Alchemy
60
59
  attr_reader :routes
61
60
 
62
61
  def preview_config_for(page)
63
- preview_config = Alchemy::Config.get(:preview)
62
+ preview_config = Alchemy.config.preview
64
63
  return unless preview_config
65
64
 
66
- preview_config[page.site.name] || preview_config
65
+ preview_config.for_site(page.site) || preview_config
67
66
  end
68
67
 
69
68
  def uri
@@ -81,8 +80,8 @@ module Alchemy
81
80
  end
82
81
 
83
82
  def userinfo
84
- auth = @preview_config["auth"]
85
- auth ? "#{auth["username"]}:#{auth["password"]}" : nil
83
+ auth = @preview_config.auth
84
+ auth.username ? "#{auth["username"]}:#{auth["password"]}" : nil
86
85
  end
87
86
  end
88
87
  end
@@ -14,15 +14,12 @@ module Alchemy
14
14
  def dependencies
15
15
  case @name.to_s
16
16
  when /^alchemy\/pages\/show/
17
- PageLayout.all.map { |p| "alchemy/page_layouts/_#{p["name"]}" }
17
+ PageDefinition.all.map { "alchemy/page_layouts/_#{_1.name}" }
18
18
  when /^alchemy\/page_layouts\/_(\w+)/
19
- page_layout = PageLayout.get($1)
20
- layout_elements = page_layout.fetch("elements", [])
21
- layout_elements.map { |name| "alchemy/elements/_#{name}" }
19
+ page_definition = PageDefinition.get($1)
20
+ page_definition.elements.map { "alchemy/elements/_#{_1}" }
22
21
  when /^alchemy\/elements\/_(\w+)/
23
- ingredient_types($1).map { |type|
24
- "alchemy/ingredients/_#{type.underscore}_view"
25
- }.uniq
22
+ ingredient_types($1).map { "alchemy/ingredients/_#{_1.underscore}_view" }.tap(&:uniq!)
26
23
  else
27
24
  ActionView::DependencyTracker::ERBTracker.call(@name, @template)
28
25
  end
@@ -31,10 +28,10 @@ module Alchemy
31
28
  private
32
29
 
33
30
  def ingredient_types(name)
34
- element = Element.definitions.detect { |e| e["name"] == name }
31
+ element = Element.definitions.detect { _1.name == name }
35
32
  return [] unless element
36
33
 
37
- element.fetch("ingredients", []).collect { |c| c["type"] }
34
+ element.ingredients.map(&:type)
38
35
  end
39
36
  end
40
37
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module ConfigMissing
5
+ def const_missing(missing_const_name)
6
+ if missing_const_name == :Config
7
+ Alchemy::Deprecation.warn("Alchemy::Config is deprecated. Use Alchemy.config instead.")
8
+ Alchemy.config
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class Configuration
5
+ class BaseOption
6
+ def self.value_class
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def initialize(value:, name:, **args)
11
+ @name = name
12
+ @value = validate(value) unless value.nil?
13
+ end
14
+ attr_reader :name, :value
15
+
16
+ private
17
+
18
+ def validate(value)
19
+ raise TypeError, "#{name} must be set as a #{self.class.value_class.name}, given #{value.inspect}" unless value.is_a?(self.class.value_class)
20
+ value
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/base_option"
4
+
5
+ module Alchemy
6
+ class Configuration
7
+ class BooleanOption < BaseOption
8
+ private
9
+
10
+ def validate(value)
11
+ raise TypeError, "#{name} must be a Boolean, given #{value.inspect}" unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
12
+ value
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/base_option"
4
+
5
+ module Alchemy
6
+ class Configuration
7
+ class ClassOption < BaseOption
8
+ def self.value_class
9
+ String
10
+ end
11
+
12
+ def value = @value.constantize
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/base_option"
4
+
5
+ module Alchemy
6
+ class Configuration
7
+ class ClassSetOption < BaseOption
8
+ include Enumerable
9
+
10
+ def value = self
11
+
12
+ def <<(klass)
13
+ @value << klass.to_s
14
+ end
15
+
16
+ def concat(klasses)
17
+ klasses.each do |klass|
18
+ self << klass
19
+ end
20
+
21
+ self
22
+ end
23
+
24
+ delegate :clear, :empty?, to: :@value
25
+
26
+ def delete(object)
27
+ @value.delete(object.to_s)
28
+ end
29
+
30
+ def each
31
+ @value.each do |klass|
32
+ yield klass.constantize
33
+ end
34
+ end
35
+
36
+ def as_json = @value
37
+
38
+ private
39
+
40
+ def validate(value)
41
+ raise TypeError, "each #{name} must be set as a String" unless value.all? { _1.is_a?(String) }
42
+ value
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/list_option"
4
+
5
+ module Alchemy
6
+ class Configuration
7
+ class IntegerListOption < ListOption
8
+ def self.item_class
9
+ Integer
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/base_option"
4
+ module Alchemy
5
+ class Configuration
6
+ class IntegerOption < BaseOption
7
+ def self.value_class
8
+ Integer
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/base_option"
4
+
5
+ module Alchemy
6
+ class Configuration
7
+ class ListOption < BaseOption
8
+ def self.item_class
9
+ raise NotImplementedError
10
+ end
11
+
12
+ private
13
+
14
+ def validate(value)
15
+ unless value.is_a?(Array) && value.all? { _1.is_a?(self.class.item_class) }
16
+ raise TypeError, "#{@name} must be an Array of #{self.class.item_class.name.downcase.pluralize}, given #{value.inspect}"
17
+ end
18
+ value
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class Configuration
5
+ class RegexpOption < BaseOption
6
+ def self.value_class
7
+ Regexp
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/list_option"
4
+
5
+ module Alchemy
6
+ class Configuration
7
+ class StringListOption < ListOption
8
+ def self.item_class
9
+ String
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class Configuration
5
+ class StringOption < BaseOption
6
+ def self.value_class
7
+ String
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext/string"
5
+
6
+ require "alchemy/configuration/boolean_option"
7
+ require "alchemy/configuration/class_option"
8
+ require "alchemy/configuration/class_set_option"
9
+ require "alchemy/configuration/integer_option"
10
+ require "alchemy/configuration/integer_list_option"
11
+ require "alchemy/configuration/regexp_option"
12
+ require "alchemy/configuration/string_list_option"
13
+ require "alchemy/configuration/string_option"
14
+
15
+ module Alchemy
16
+ class Configuration
17
+ def initialize(configuration_hash = {})
18
+ set(configuration_hash)
19
+ end
20
+
21
+ def set(configuration_hash)
22
+ configuration_hash.each do |key, value|
23
+ send(:"#{key}=", value)
24
+ end
25
+ end
26
+
27
+ alias_method :get, :send
28
+ alias_method :[], :get
29
+
30
+ def show = self
31
+
32
+ def fetch(key, default = nil)
33
+ get(key) || default
34
+ end
35
+
36
+ def set_from_yaml(file)
37
+ set(
38
+ YAML.safe_load(
39
+ ERB.new(File.read(file)).result,
40
+ permitted_classes: YAML_PERMITTED_CLASSES,
41
+ aliases: true
42
+ ) || {}
43
+ )
44
+ end
45
+
46
+ def to_h
47
+ self.class.defined_options.map do |option|
48
+ [option, send(option)]
49
+ end.concat(
50
+ self.class.defined_configurations.map do |configuration|
51
+ [configuration, send(configuration).to_h]
52
+ end
53
+ ).to_h
54
+ end
55
+
56
+ class << self
57
+ def defined_configurations = []
58
+
59
+ def defined_options = []
60
+
61
+ def configuration(name, configuration_class)
62
+ # The defined configurations on a class are all those defined directly on
63
+ # that class as well as those defined on ancestors.
64
+ # We store these as a class instance variable on each class which has a
65
+ # configuration. super() collects configurations defined on ancestors.
66
+ singleton_configurations = (@defined_singleton_configurations ||= [])
67
+ singleton_configurations << name.to_sym
68
+
69
+ define_singleton_method :defined_configurations do
70
+ super() + singleton_configurations
71
+ end
72
+
73
+ define_method(name) do
74
+ unless instance_variable_get(:"@#{name}")
75
+ send(:"#{name}=", configuration_class.new)
76
+ end
77
+ instance_variable_get(:"@#{name}")
78
+ end
79
+
80
+ define_method(:"#{name}=") do |value|
81
+ if value.is_a?(configuration_class)
82
+ instance_variable_set(:"@#{name}", value)
83
+ else
84
+ send(name).set(value)
85
+ end
86
+ end
87
+ end
88
+
89
+ def option(name, type, default: nil, **args)
90
+ klass = "Alchemy::Configuration::#{type.to_s.camelize}Option".constantize
91
+ # The defined options on a class are all those defined directly on
92
+ # that class as well as those defined on ancestors.
93
+ # We store these as a class instance variable on each class which has a
94
+ # option. super() collects options defined on ancestors.
95
+ singleton_options = (@defined_singleton_options ||= [])
96
+ singleton_options << name.to_sym
97
+
98
+ define_singleton_method :defined_options do
99
+ super() + singleton_options
100
+ end
101
+
102
+ define_method(name) do
103
+ unless instance_variable_defined?(:"@#{name}")
104
+ send(:"#{name}=", default)
105
+ end
106
+ instance_variable_get(:"@#{name}").value
107
+ end
108
+
109
+ define_method(:"#{name}=") do |value|
110
+ instance_variable_set(:"@#{name}", klass.new(value:, name:, **args))
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -12,9 +12,11 @@ module Alchemy
12
12
  #
13
13
  # Config file is in +config/alchemy/config.yml+
14
14
  #
15
+ # @deprecated - Use Alchemy.config.get instead
15
16
  def configuration(name)
16
- Config.get(name)
17
+ Alchemy.config.get(name)
17
18
  end
19
+ deprecate configuration: "Alchemy.config.get", deprecator: Alchemy::Deprecation
18
20
 
19
21
  # Returns true if more than one language is published on current site.
20
22
  #
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class DefaultLanguage < Alchemy::Configuration
6
+ option :name, :string, default: "English"
7
+ option :code, :string, default: "en"
8
+ option :page_layout, :string, default: "index"
9
+ option :frontpage_name, :string, default: "Index"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class DefaultSite < Alchemy::Configuration
6
+ option :name, :string, default: "Default Site"
7
+ option :host, :string, default: "*"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class FormatMatchers < Alchemy::Configuration
6
+ option :email, :regexp, default: /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/
7
+ option :url, :regexp, default: /\A[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix
8
+ option :link_url, :regexp, default: /^(tel:|mailto:|\/|[a-z]+:\/\/)/
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class Mailer < Alchemy::Configuration
6
+ option :page_layout_name, :string, default: "contact"
7
+ option :forward_to_page, :boolean, default: false
8
+ option :mail_success_page, :string, default: "thanks"
9
+ option :mail_from, :string, default: "your.mail@your-domain.com"
10
+ option :mail_to, :string, default: "your.mail@your-domain.com"
11
+ option :subject, :string, default: "A new contact form message"
12
+ option :fields, :string_list, default: %w[salutation firstname lastname address zip city phone email message]
13
+ option :validate_fields, :string_list, default: %w[lastname email]
14
+ end
15
+ end
16
+ end