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
@@ -73,6 +73,13 @@ module Alchemy
73
73
  end
74
74
  end
75
75
 
76
+ def alchemy_admin_js_translations(locale = ::I18n.locale)
77
+ render partial: "alchemy/admin/translations/#{locale}", formats: [:js]
78
+ rescue ActionView::MissingTemplate
79
+ # Fallback to default translations
80
+ render partial: "alchemy/admin/translations/en", formats: [:js]
81
+ end
82
+
76
83
  # Used for site selector in Alchemy cockpit.
77
84
  def sites_for_select
78
85
  Alchemy::Site.all.map do |site|
@@ -80,36 +87,6 @@ module Alchemy
80
87
  end
81
88
  end
82
89
 
83
- # Returns a javascript driven live filter for lists.
84
- #
85
- # The items must have a html +name+ attribute that holds the filterable value.
86
- #
87
- # == Example
88
- #
89
- # Given a list of items:
90
- #
91
- # <%= js_filter_field('#products .product') %>
92
- #
93
- # <ul id="products">
94
- # <li class="product" name="kat litter">Kat Litter</li>
95
- # <li class="product" name="milk">Milk</li>
96
- # </ul>
97
- #
98
- # @param [String] items
99
- # A jquery compatible selector string that represents the items to filter
100
- # @param [Hash] options
101
- # HTML options passed to the input field
102
- #
103
- # @option options [String] :class ('js_filter_field')
104
- # The css class of the <input> tag
105
- # @option options [String or Hash] :data ({'alchemy-list-filter' => items})
106
- # A HTML data attribute that holds the jQuery selector that represents the list to be filtered
107
- # @deprecated render Alchemy::Admin::ListFilter.new(items) instead
108
- def js_filter_field(items, _options = {})
109
- render Alchemy::Admin::ListFilter.new(items)
110
- end
111
- deprecate js_filter_field: "render Alchemy::Admin::ListFilter.new(items) instead", deprecator: Alchemy::Deprecation
112
-
113
90
  # Returns a link that opens a modal confirmation to delete window.
114
91
  #
115
92
  # === Example:
@@ -137,12 +114,10 @@ module Alchemy
137
114
  def link_to_confirm_dialog(link_string = "", message = "", url = "", html_options = {})
138
115
  link_to(link_string, url,
139
116
  html_options.merge(
140
- "data-alchemy-confirm-delete" => {
141
- title: Alchemy.t(:please_confirm),
142
- message: message,
143
- ok_label: Alchemy.t("Yes"),
144
- cancel_label: Alchemy.t("No")
145
- }.to_json
117
+ data: {
118
+ "turbo-method": :delete,
119
+ "turbo-confirm": message
120
+ }
146
121
  ))
147
122
  end
148
123
 
@@ -168,12 +143,10 @@ module Alchemy
168
143
  def button_with_confirm(value = "", url = "", options = {}, html_options = {})
169
144
  options = {
170
145
  message: Alchemy.t(:confirm_to_proceed),
171
- ok_label: Alchemy.t("Yes"),
172
- title: Alchemy.t(:please_confirm),
173
- cancel_label: Alchemy.t("No")
146
+ title: Alchemy.t(:please_confirm)
174
147
  }.merge(options)
175
148
  form_tag url, {method: html_options.delete(:method), class: "button-with-confirm"} do
176
- button_tag value, html_options.merge("data-alchemy-confirm" => options.to_json)
149
+ button_tag value, html_options.merge("data-turbo-confirm" => options[:message])
177
150
  end
178
151
  end
179
152
 
@@ -220,49 +193,6 @@ module Alchemy
220
193
  "Alchemy CMS - #{title}"
221
194
  end
222
195
 
223
- # Renders a toolbar button for the Alchemy toolbar
224
- #
225
- # == Example
226
- #
227
- # <%= toolbar_button(
228
- # icon: :plus,
229
- # label: 'Create',
230
- # url: new_resource_path,
231
- # title: 'Create Resource',
232
- # hotkey: 'alt+n',
233
- # dialog_options: {
234
- # title: 'Create Resource',
235
- # size: "430x400"
236
- # },
237
- # if_permitted_to: [:create, resource_model]
238
- # ) %>
239
- #
240
- # @option options [String] :icon
241
- # Icon class. See +app/assets/stylesheets/alchemy/icons.css.sccs+ for available icons, or make your own.
242
- # @option options [String] :label
243
- # Text for button label.
244
- # @option options [String] :url
245
- # Url for link.
246
- # @option options [String] :title
247
- # Text for title tag.
248
- # @option options [String] :hotkey
249
- # Keyboard shortcut for this button. I.E +alt-n+
250
- # @option options [Boolean] :dialog (true)
251
- # Open the link in a modal dialog.
252
- # @option options [Hash] :dialog_options
253
- # Overlay options. See link_to_dialog helper.
254
- # @option options [Array] :if_permitted_to ([:action, :controller])
255
- # Check permission for button. Exactly how you defined the permission in your +authorization_rules.rb+. Defaults to controller and action from button url.
256
- # @option options [Boolean] :skip_permission_check (false)
257
- # Skip the permission check. NOT RECOMMENDED!
258
- # @option options [Boolean] :loading_indicator (true)
259
- # Shows the please wait dialog while loading. Only for buttons not opening an dialog.
260
- # @deprecated render Alchemy::Admin::ToolbarButton.new instead
261
- def toolbar_button(options = {})
262
- render Alchemy::Admin::ToolbarButton.new(**options)
263
- end
264
- deprecate toolbar_button: "render Alchemy::Admin::ToolbarButton.new instead", deprecator: Alchemy::Deprecation
265
-
266
196
  # Renders a textfield ready to display a datepicker
267
197
  #
268
198
  # A Javascript observer converts this into a fancy Datepicker.
@@ -346,7 +276,7 @@ module Alchemy
346
276
 
347
277
  # Returns the regular expression used for external url validation in link dialog.
348
278
  def link_url_regexp
349
- Alchemy::Config.get(:format_matchers)["link_url"] || /^(mailto:|\/|[a-z]+:\/\/)/
279
+ Alchemy.config.format_matchers.link_url || /^(mailto:|\/|[a-z]+:\/\/)/
350
280
  end
351
281
 
352
282
  # Renders a hint with tooltip
@@ -10,7 +10,7 @@ module Alchemy
10
10
  # You can configure the screen sizes in your +config/alchemy/config.yml+.
11
11
  #
12
12
  def preview_sizes_for_select
13
- Alchemy::Config.get(:page_preview_sizes).map do |size|
13
+ Alchemy.config.page_preview_sizes.map do |size|
14
14
  [Alchemy.t(size, scope: "preview_sizes"), size]
15
15
  end
16
16
  end
@@ -2,14 +2,6 @@
2
2
 
3
3
  module Alchemy
4
4
  module BaseHelper
5
- # An alias for truncate.
6
- # Left here for downwards compatibilty.
7
- # @deprecated
8
- def shorten(text, length)
9
- text.truncate(length: length)
10
- end
11
- deprecate :shorten, deprecator: Alchemy::Deprecation
12
-
13
5
  # Logs a message in the Rails logger (warn level)
14
6
  # and optionally displays an error message to the user.
15
7
  def warning(message, text = nil)
@@ -45,27 +37,5 @@ module Alchemy
45
37
  def render_message(type = :info, msg = nil, &)
46
38
  render Alchemy::Admin::Message.new(msg || capture(&), type: type)
47
39
  end
48
-
49
- # Checks if the given argument is a String or a Page object.
50
- # If a String is given, it tries to find the page via page_layout
51
- # Logs a warning if no page is given.
52
- # @deprecated
53
- def page_or_find(page)
54
- unless Current.language
55
- warning("No default language set up")
56
- return nil
57
- end
58
-
59
- if page.is_a?(String)
60
- page = Current.language.pages.find_by(page_layout: page)
61
- end
62
- if page.blank?
63
- warning("No Page found for #{page.inspect}")
64
- nil
65
- else
66
- page
67
- end
68
- end
69
- deprecate :page_or_find, deprecator: Alchemy::Deprecation
70
40
  end
71
41
  end
@@ -100,22 +100,8 @@ module Alchemy
100
100
  # A lambda used for formatting the element's tags (see Alchemy::ElementsHelper::element_tags_attributes). Set to +false+ to not include tags in the wrapper element.
101
101
  #
102
102
  def element_view_for(element, options = {})
103
- if options[:id].nil?
104
- Alchemy::Deprecation.warn <<~WARN
105
- Relying on an implicit DOM id in `element_view_for` is deprecated. Please provide an explicit `id` if you actually want to render an `id` attribute on the #{element.name} element wrapper tag.
106
- WARN
107
- end
108
-
109
- if options[:class].nil?
110
- Alchemy::Deprecation.warn <<~WARN
111
- Relying on an implicit CSS class in `element_view_for` is deprecated. Please provide an explicit `class` for the #{element.name} element wrapper tag.
112
- WARN
113
- end
114
-
115
103
  options = {
116
104
  tag: :div,
117
- id: (!!options[:id]) ? options[:id] : element.dom_id,
118
- class: element.name,
119
105
  tags_formatter: ->(tags) { tags.join(" ") }
120
106
  }.merge(options)
121
107
 
@@ -46,7 +46,7 @@ module Alchemy
46
46
  def render_page_layout
47
47
  render @page, page: @page
48
48
  rescue ActionView::MissingTemplate
49
- warning("PageLayout: '#{@page.page_layout}' not found. Rendering standard page_layout.")
49
+ warning("[alchemy]: '#{@page.page_layout}' page layout not found. Rendering 'standard' page layout.")
50
50
  render "alchemy/page_layouts/standard", page: @page
51
51
  end
52
52
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Alchemy
4
4
  module ResourcesHelper
5
+ include Alchemy::Admin::ResourceName
6
+
5
7
  # = Alchemy::ResourceHelper
6
8
  #
7
9
  # Used to DRY up resource like structures in Alchemy's admin backend in combination with Alchemy::Resource
@@ -14,11 +16,11 @@ module Alchemy
14
16
  end
15
17
 
16
18
  def resource_instance_variable
17
- instance_variable_get(:"@#{resource_handler.resource_name}")
19
+ instance_variable_get(:"@#{resource_name}")
18
20
  end
19
21
 
20
22
  def resources_instance_variable
21
- instance_variable_get(:"@#{resource_handler.resources_name}")
23
+ instance_variable_get(:"@#{resources_name}")
22
24
  end
23
25
 
24
26
  def resource_url_proxy
@@ -46,14 +48,10 @@ module Alchemy
46
48
  end
47
49
 
48
50
  def edit_resource_path(resource = nil, options = {})
49
- path_segments = resource_scope + [resource] || resource_handler.resource_array
51
+ path_segments = resource_scope + [resource] || resource_array
50
52
  edit_polymorphic_path path_segments, options
51
53
  end
52
54
 
53
- def resource_name
54
- resource_handler.resource_name
55
- end
56
-
57
55
  def resource_model
58
56
  resource_handler.model
59
57
  end
@@ -127,44 +125,6 @@ module Alchemy
127
125
  resource_handler.resource_relations.collect { |_k, v| v[:name].to_sym }
128
126
  end
129
127
 
130
- # Returns the attribute's column for sorting
131
- #
132
- # If the attribute contains a resource_relation, then the table and column for related model will be returned.
133
- # @deprecated
134
- def sortable_resource_header_column(attribute)
135
- if (relation = attribute[:relation])
136
- "#{relation[:model_association].name}_#{relation[:attr_method]}"
137
- else
138
- attribute[:name]
139
- end
140
- end
141
- deprecate sortable_resource_header_column: "Please `render Alchemy::Admin::Resource::Table instead`", deprecator: Alchemy::Deprecation
142
-
143
- # Renders the row for a resource record in the resources table.
144
- #
145
- # This helper has a nice fallback. If you create a partial for your record then this partial will be rendered.
146
- #
147
- # Otherwise the default +app/views/alchemy/admin/resources/_resource.html.erb+ partial gets rendered.
148
- #
149
- # == Example
150
- #
151
- # For a resource named +Comment+ you can create a partial named +_comment.html.erb+
152
- #
153
- # # app/views/admin/comments/_comment.html.erb
154
- # <tr>
155
- # <td><%= comment.title %></td>
156
- # <td><%= comment.body %></td>
157
- # </tr>
158
- #
159
- # NOTE: Alchemy gives you a local variable named like your resource
160
- # @deprecated
161
- def render_resources(icon: nil)
162
- render partial: resource_name, collection: resources_instance_variable, locals: {icon: icon}
163
- rescue ActionView::MissingTemplate
164
- render partial: "resource", collection: resources_instance_variable, locals: {icon: icon}
165
- end
166
- deprecate render_resources: "Please `render Alchemy::Admin::Resource::Table instead`", deprecator: Alchemy::Deprecation
167
-
168
128
  def resource_has_tags
169
129
  resource_model.respond_to?(:tag_counts) && resource_model.tag_counts.any?
170
130
  end
@@ -1,4 +1,5 @@
1
1
  import { reloadPreview } from "alchemy_admin/components/preview_window"
2
+ import { removeTab } from "alchemy_admin/fixed_elements"
2
3
  import { closeCurrentDialog } from "alchemy_admin/dialog"
3
4
  import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
4
5
 
@@ -13,6 +14,7 @@ class Action extends HTMLElement {
13
14
  // we don't have to implicitly close the dialog
14
15
  closeCurrentDialog,
15
16
  reloadPreview,
17
+ removeFixedElement: removeTab,
16
18
  updateAnchorIcon: IngredientAnchorLink.updateIcon
17
19
  }
18
20
  }
@@ -26,7 +26,7 @@ export class AlchemyHTMLElement extends HTMLElement {
26
26
  * this is a default function
27
27
  * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference
28
28
  */
29
- connectedCallback() {
29
+ async connectedCallback() {
30
30
  // parse the properties object and register property with the default values
31
31
  Object.keys(this.constructor.properties).forEach((name) => {
32
32
  // if the options was given via the constructor, they should be prefer (e.g. new <WebComponentName>({title: "Foo"}))
@@ -39,7 +39,7 @@ export class AlchemyHTMLElement extends HTMLElement {
39
39
 
40
40
  // render the component
41
41
  this._updateComponent()
42
- this.connected()
42
+ await this.connected()
43
43
  }
44
44
 
45
45
  /**
@@ -64,7 +64,7 @@ export class AlchemyHTMLElement extends HTMLElement {
64
64
  /**
65
65
  * a connected method to make it easier to overwrite the connection callback
66
66
  */
67
- connected() {}
67
+ async connected() {}
68
68
 
69
69
  /**
70
70
  * a disconnected method to make it easier to overwrite the disconnection callback
@@ -0,0 +1,20 @@
1
+ // Dispatch a submit event on change of input or select elements
2
+ // contained in a form, so that Turbo can submit the form.
3
+ class AutoSubmit extends HTMLElement {
4
+ connectedCallback() {
5
+ // Still using jQuery here, because select2 does not emit
6
+ // the event from the original select element.
7
+ $(this).on("change", function (event) {
8
+ // We need to dispatch a submit event, so that Turbo that listens
9
+ // to it submits the search form us.
10
+ const submitEvent = new Event("submit", {
11
+ bubbles: true,
12
+ cancelable: true
13
+ })
14
+ event.target.form.dispatchEvent(submitEvent)
15
+ return false
16
+ })
17
+ }
18
+ }
19
+
20
+ customElements.define("alchemy-auto-submit", AutoSubmit)
@@ -2,6 +2,8 @@ import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_elemen
2
2
  import { translate, currentLocale } from "alchemy_admin/i18n"
3
3
  import flatpickr from "flatpickr"
4
4
 
5
+ const locale = currentLocale()
6
+
5
7
  class Datepicker extends AlchemyHTMLElement {
6
8
  static properties = {
7
9
  inputType: { default: "date" }
@@ -12,11 +14,14 @@ class Datepicker extends AlchemyHTMLElement {
12
14
  this.flatpickr = undefined
13
15
  }
14
16
 
15
- afterRender() {
16
- this.flatpickr = flatpickr(
17
- this.getElementsByTagName("input")[0],
18
- this.flatpickrOptions
19
- )
17
+ // Load the locales for flatpickr before setting it up.
18
+ async connected() {
19
+ // English is the default locale for flatpickr, so we don't need to load it
20
+ if (locale !== "en") {
21
+ await import(`flatpickr/${locale}.js`)
22
+ }
23
+
24
+ this.flatpickr = flatpickr(this.inputField, this.flatpickrOptions)
20
25
  }
21
26
 
22
27
  disconnected() {
@@ -27,7 +32,7 @@ class Datepicker extends AlchemyHTMLElement {
27
32
  const enableTime = /time/.test(this.inputType)
28
33
  const options = {
29
34
  // alchemy_i18n supports `zh_CN` etc., but flatpickr only has two-letter codes (`zh`)
30
- locale: currentLocale().slice(0, 2),
35
+ locale: locale.slice(0, 2),
31
36
  altInput: true,
32
37
  altFormat: translate(`formats.${this.inputType}`),
33
38
  altInputClass: "flatpickr-input",
@@ -35,7 +40,9 @@ class Datepicker extends AlchemyHTMLElement {
35
40
  noCalendar: this.inputType === "time",
36
41
  time_24hr: translate("formats.time_24hr"),
37
42
  onValueUpdate(_selectedDates, _dateStr, instance) {
38
- instance.element.closest("alchemy-element-editor")?.setDirty()
43
+ instance.element
44
+ .closest("alchemy-element-editor")
45
+ ?.setDirty(this.inputField)
39
46
  }
40
47
  }
41
48
 
@@ -45,6 +52,10 @@ class Datepicker extends AlchemyHTMLElement {
45
52
 
46
53
  return options
47
54
  }
55
+
56
+ get inputField() {
57
+ return this.querySelector("input")
58
+ }
48
59
  }
49
60
 
50
61
  customElements.define("alchemy-datepicker", Datepicker)
@@ -1,7 +1,8 @@
1
+ import ajax from "alchemy_admin/utils/ajax"
1
2
  import { removeTab } from "alchemy_admin/fixed_elements"
2
3
  import { growl } from "alchemy_admin/growler"
3
4
  import { reloadPreview } from "alchemy_admin/components/preview_window"
4
- import { confirmToDeleteDialog } from "alchemy_admin/confirm_dialog"
5
+ import { openConfirmDialog } from "alchemy_admin/confirm_dialog"
5
6
 
6
7
  export class DeleteElementButton extends HTMLElement {
7
8
  constructor() {
@@ -9,12 +10,12 @@ export class DeleteElementButton extends HTMLElement {
9
10
  this.addEventListener("click", this)
10
11
  }
11
12
 
12
- handleEvent() {
13
- confirmToDeleteDialog(this.url, { message: this.message }).then(
14
- (response) => {
15
- this.#removeElement(response)
16
- }
17
- )
13
+ async handleEvent() {
14
+ const confirmed = await openConfirmDialog(this.message)
15
+ if (confirmed) {
16
+ const response = await ajax("DELETE", this.url)
17
+ this.#removeElement(response.data)
18
+ }
18
19
  }
19
20
 
20
21
  #removeElement(response) {
@@ -20,8 +20,10 @@ export class ElementEditor extends HTMLElement {
20
20
  this.addEventListener("alchemy:element-update-title", this)
21
21
  // We use of @rails/ujs for Rails remote forms
22
22
  this.addEventListener("ajax:complete", this)
23
- // Dirty observer
24
- this.addEventListener("change", this)
23
+
24
+ // Dirty observer still needs to be jQuery
25
+ // in order to support select2.
26
+ $(this).on("change", this.onChange)
25
27
 
26
28
  this.header?.addEventListener("dblclick", () => {
27
29
  this.toggle()
@@ -78,20 +80,22 @@ export class ElementEditor extends HTMLElement {
78
80
  this.setTitle(event.detail.title)
79
81
  }
80
82
  break
81
- case "change":
82
- // SortableJS fires a native change event :/
83
- // and we do not want to set the element editor dirty
84
- // when this happens
85
- if (event.target.classList.contains("nested-elements")) {
86
- return
87
- }
88
- event.stopPropagation()
89
- event.target.classList.add("dirty")
90
- this.setDirty()
91
- break
92
83
  }
93
84
  }
94
85
 
86
+ onChange(event) {
87
+ const target = event.target
88
+ // SortableJS fires a native change event :/
89
+ // and we do not want to set the element editor dirty
90
+ // when this happens
91
+ if (target.classList.contains("nested-elements")) {
92
+ return
93
+ }
94
+ this.setDirty(target)
95
+ event.stopPropagation()
96
+ return false
97
+ }
98
+
95
99
  /**
96
100
  * Scrolls to and highlights element
97
101
  * Expands if collapsed
@@ -227,11 +231,17 @@ export class ElementEditor extends HTMLElement {
227
231
 
228
232
  /**
229
233
  * Sets the element into dirty (unsafed) state
234
+ * @param {HTMLElement} editor
230
235
  */
231
- setDirty() {
236
+ setDirty(editor) {
232
237
  if (this.hasEditors) {
233
238
  this.dirty = true
234
- window.onbeforeunload = () => Alchemy.t("page_dirty_notice")
239
+
240
+ if (!window.onbeforeunload) {
241
+ window.onbeforeunload = (event) => event.preventDefault()
242
+ }
243
+
244
+ editor?.closest(".ingredient-editor")?.classList.add("dirty")
235
245
  }
236
246
  }
237
247
 
@@ -0,0 +1,43 @@
1
+ import { hightlightTerm } from "alchemy_admin/components/remote_select"
2
+
3
+ const formatItem = (icon, text) => {
4
+ return `<div class="element-select-item">${icon} ${text}</div>`
5
+ }
6
+
7
+ class ElementSelect extends HTMLInputElement {
8
+ constructor() {
9
+ super()
10
+ this.classList.add("alchemy_selectbox")
11
+ }
12
+
13
+ connectedCallback() {
14
+ const el = this
15
+ const options = {
16
+ minimumResultsForSearch: 3,
17
+ dropdownAutoWidth: true,
18
+ data() {
19
+ return { results: JSON.parse(el.dataset.options) }
20
+ },
21
+ formatResult: (option, _el, search) => {
22
+ let text
23
+
24
+ if (option.id === "") return option.text
25
+ if (search.term !== "") {
26
+ text = hightlightTerm(option.text, search.term)
27
+ } else {
28
+ text = option.text
29
+ }
30
+
31
+ return formatItem(option.icon, text)
32
+ },
33
+ formatSelection: (option) => {
34
+ return formatItem(option.icon, option.text)
35
+ }
36
+ }
37
+ $(this).select2(options)
38
+ }
39
+ }
40
+
41
+ customElements.define("alchemy-element-select", ElementSelect, {
42
+ extends: "input"
43
+ })
@@ -3,6 +3,7 @@
3
3
 
4
4
  import "alchemy_admin/components/action"
5
5
  import "alchemy_admin/components/attachment_select"
6
+ import "alchemy_admin/components/auto_submit"
6
7
  import "alchemy_admin/components/button"
7
8
  import "alchemy_admin/components/char_counter"
8
9
  import "alchemy_admin/components/clipboard_button"
@@ -10,6 +11,7 @@ import "alchemy_admin/components/datepicker"
10
11
  import "alchemy_admin/components/dialog_link"
11
12
  import "alchemy_admin/components/dom_id_select"
12
13
  import "alchemy_admin/components/element_editor"
14
+ import "alchemy_admin/components/element_select"
13
15
  import "alchemy_admin/components/elements_window"
14
16
  import "alchemy_admin/components/elements_window_handle"
15
17
  import "alchemy_admin/components/list_filter"
@@ -27,6 +29,7 @@ import "alchemy_admin/components/select"
27
29
  import "alchemy_admin/components/spinner"
28
30
  import "alchemy_admin/components/tags_autocomplete"
29
31
  import "alchemy_admin/components/tinymce"
32
+ import "alchemy_admin/components/update_check"
30
33
 
31
34
  await Promise.race([
32
35
  // Load all global custom elements
@@ -27,7 +27,7 @@ class LinkButtons extends HTMLElement {
27
27
  this.linkTargetField.value = data.target
28
28
 
29
29
  this.unlinkButton.linked = true
30
- this.elementEditor.setDirty()
30
+ this.setElementDirty()
31
31
  }
32
32
 
33
33
  removeLink() {
@@ -40,7 +40,11 @@ class LinkButtons extends HTMLElement {
40
40
  this.linkButton.classList.remove("linked")
41
41
  this.unlinkButton.linked = false
42
42
 
43
- this.elementEditor.setDirty()
43
+ this.setElementDirty()
44
+ }
45
+
46
+ setElementDirty() {
47
+ this.elementEditor.setDirty(this)
44
48
  }
45
49
 
46
50
  get linkButton() {
@@ -1,4 +1,9 @@
1
1
  import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element"
2
+ import { setupSelectLocale } from "alchemy_admin/i18n"
3
+
4
+ export function hightlightTerm(name, term) {
5
+ return name.replace(new RegExp(term, "gi"), (match) => `<em>${match}</em>`)
6
+ }
2
7
 
3
8
  export class RemoteSelect extends AlchemyHTMLElement {
4
9
  static properties = {
@@ -9,7 +14,9 @@ export class RemoteSelect extends AlchemyHTMLElement {
9
14
  url: { default: "" }
10
15
  }
11
16
 
12
- connected() {
17
+ async connected() {
18
+ await setupSelectLocale()
19
+
13
20
  this.input.classList.add("alchemy_selectbox")
14
21
 
15
22
  $(this.input)
@@ -145,6 +152,6 @@ export class RemoteSelect extends AlchemyHTMLElement {
145
152
  * @private
146
153
  */
147
154
  _hightlightTerm(name, term) {
148
- return name.replace(new RegExp(term, "gi"), (match) => `<em>${match}</em>`)
155
+ return hightlightTerm(name, term)
149
156
  }
150
157
  }
@@ -1,5 +1,9 @@
1
+ import { setupSelectLocale } from "alchemy_admin/i18n"
2
+
1
3
  class TagsAutocomplete extends HTMLElement {
2
- connectedCallback() {
4
+ async connectedCallback() {
5
+ await setupSelectLocale()
6
+
3
7
  this.classList.add("autocomplete_tag_list")
4
8
  $(this.input).select2(this.select2Config)
5
9
  }