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
@@ -2,6 +2,9 @@ import "tinymce"
2
2
  import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element"
3
3
  import { currentLocale } from "alchemy_admin/i18n"
4
4
 
5
+ const DARK_THEME = "alchemy-dark"
6
+ const LIGHT_THEME = "alchemy"
7
+
5
8
  class Tinymce extends AlchemyHTMLElement {
6
9
  #min_height = null
7
10
 
@@ -32,6 +35,9 @@ class Tinymce extends AlchemyHTMLElement {
32
35
  options
33
36
  )
34
37
  this.tinymceIntersectionObserver.observe(this)
38
+
39
+ // Set up theme change listener
40
+ this._setupThemeChangeListener()
35
41
  }
36
42
 
37
43
  /**
@@ -42,6 +48,9 @@ class Tinymce extends AlchemyHTMLElement {
42
48
  this.tinymceIntersectionObserver.disconnect()
43
49
  }
44
50
 
51
+ // Remove theme change listener
52
+ this._removeThemeChangeListener()
53
+
45
54
  tinymce.get(this.editorId)?.remove(this.editorId)
46
55
  }
47
56
 
@@ -66,22 +75,84 @@ class Tinymce extends AlchemyHTMLElement {
66
75
  */
67
76
  _initTinymceEditor() {
68
77
  tinymce.init(this.configuration).then((editors) => {
69
- editors.forEach((editor) => {
70
- // mark the editor container as visible
71
- // without these correction the editor remains hidden
72
- // after a drag and drop action
73
- editor.show()
74
-
75
- // remove the spinner after the Tinymce initialized
76
- this.getElementsByTagName("alchemy-spinner")[0].remove()
77
-
78
- // event listener to mark the editor as dirty
79
- editor.on("dirty", () => this.elementEditor.setDirty())
80
- editor.on("click", () => this.elementEditor.onClickElement(false))
81
- })
78
+ editors.forEach((editor) => this._setupEditor(editor))
82
79
  })
83
80
  }
84
81
 
82
+ /**
83
+ * Setup editor after initialization
84
+ * @param {Object} editor - The TinyMCE editor instance
85
+ * @private
86
+ */
87
+ _setupEditor(editor) {
88
+ // mark the editor container as visible
89
+ // without these correction the editor remains hidden
90
+ // after a drag and drop action
91
+ editor.show()
92
+
93
+ // remove the spinner after the Tinymce initialized (only on first init)
94
+ const spinner = this.getElementsByTagName("alchemy-spinner")[0]
95
+ if (spinner) {
96
+ spinner.remove()
97
+ }
98
+
99
+ // event listener to mark the editor as dirty
100
+ if (this.elementEditor) {
101
+ editor.on("dirty", (evt) => {
102
+ this.elementEditor.setDirty(evt.target.editorContainer)
103
+ })
104
+ editor.on("click", () => this.elementEditor.onClickElement(false))
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Set up listener for OS theme changes
110
+ * @private
111
+ */
112
+ _setupThemeChangeListener() {
113
+ this.darkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
114
+ this.themeChangeHandler = (event) => this._handleThemeChange(event)
115
+ this.darkModeMediaQuery.addEventListener("change", this.themeChangeHandler)
116
+ }
117
+
118
+ /**
119
+ * Remove theme change listener
120
+ * @private
121
+ */
122
+ _removeThemeChangeListener() {
123
+ if (this.darkModeMediaQuery && this.themeChangeHandler) {
124
+ this.darkModeMediaQuery.removeEventListener(
125
+ "change",
126
+ this.themeChangeHandler
127
+ )
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Handle OS theme change and update TinyMCE skin
133
+ * @param {MediaQueryListEvent} event - The media query change event
134
+ * @private
135
+ */
136
+ _handleThemeChange(event) {
137
+ const editor = tinymce.get(this.editorId)
138
+ if (editor) {
139
+ const skin = event.matches ? DARK_THEME : LIGHT_THEME
140
+ const content_css = event.matches ? DARK_THEME : LIGHT_THEME
141
+
142
+ // Update the skin by reinitializing the editor with new configuration
143
+ editor.remove()
144
+ tinymce
145
+ .init({
146
+ content_css,
147
+ ...this.configuration,
148
+ skin
149
+ })
150
+ .then((editors) => {
151
+ editors.forEach((editor) => this._setupEditor(editor))
152
+ })
153
+ }
154
+ }
155
+
85
156
  get configuration() {
86
157
  const customConfig = {}
87
158
 
@@ -101,10 +172,12 @@ class Tinymce extends AlchemyHTMLElement {
101
172
  })
102
173
 
103
174
  const config = {
175
+ content_css: this.preferredTheme,
104
176
  ...Alchemy.TinymceDefaults,
105
177
  ...customConfig,
106
178
  language: currentLocale(),
107
- selector: `#${this.editorId}`
179
+ selector: `#${this.editorId}`,
180
+ skin: this.preferredTheme
108
181
  }
109
182
 
110
183
  // Tinymce has a height of 400px by default
@@ -115,6 +188,12 @@ class Tinymce extends AlchemyHTMLElement {
115
188
  return config
116
189
  }
117
190
 
191
+ get preferredTheme() {
192
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
193
+ ? DARK_THEME
194
+ : LIGHT_THEME
195
+ }
196
+
118
197
  get editorId() {
119
198
  return this.editor.id
120
199
  }
@@ -0,0 +1,42 @@
1
+ import Spinner from "alchemy_admin/spinner"
2
+
3
+ class UpdateCheck extends HTMLElement {
4
+ async connectedCallback() {
5
+ const spinner = new Spinner("small")
6
+ spinner.spin(this)
7
+
8
+ try {
9
+ const response = await fetch(this.url)
10
+ const responseText = await response.text()
11
+
12
+ if (response.ok) {
13
+ this.showStatus(responseText)
14
+ } else {
15
+ this.showError(response)
16
+ }
17
+ } catch (error) {
18
+ this.showError(error)
19
+ } finally {
20
+ spinner.stop()
21
+ }
22
+ }
23
+
24
+ get url() {
25
+ return this.getAttribute("url")
26
+ }
27
+
28
+ showStatus(responseText) {
29
+ if (responseText == "true") {
30
+ this.querySelector(".update_available").classList.remove("hidden")
31
+ } else {
32
+ this.querySelector(".up_to_date").classList.remove("hidden")
33
+ }
34
+ }
35
+
36
+ showError(error) {
37
+ this.querySelector(".error").classList.remove("hidden")
38
+ console.error("[alchemy] Error fetching update status", error)
39
+ }
40
+ }
41
+
42
+ customElements.define("alchemy-update-check", UpdateCheck)
@@ -4,21 +4,28 @@ import { translate } from "alchemy_admin/i18n"
4
4
  import { growl } from "alchemy_admin/growler"
5
5
 
6
6
  export class FileUpload extends AlchemyHTMLElement {
7
+ constructor() {
8
+ super()
9
+
10
+ this.file = null
11
+ this.request = null
12
+
13
+ this.progressEventLoaded = 0
14
+ this.progressEventTotal = 0
15
+ this.className = "in-progress"
16
+ this.valid = true
17
+ this.value = 0
18
+ }
19
+
7
20
  /**
21
+ * Initialize the component with file and request
8
22
  * @param {File} file
9
23
  * @param {XMLHttpRequest} request
10
24
  */
11
- constructor(file, request) {
12
- super({})
13
-
25
+ initialize(file, request) {
14
26
  this.file = file
15
27
  this.request = request
16
-
17
- this.progressEventLoaded = 0
18
28
  this.progressEventTotal = file ? file.size : 0
19
- this.className = "in-progress"
20
- this.valid = true
21
- this.value = 0
22
29
 
23
30
  this._validateFile()
24
31
  this._addRequestEventListener()
@@ -6,19 +6,25 @@ import { translate } from "alchemy_admin/i18n"
6
6
  export class Progress extends AlchemyHTMLElement {
7
7
  #visible = false
8
8
 
9
- /**
10
- * @param {FileUpload[]} fileUploads
11
- */
12
- constructor(fileUploads = []) {
9
+ constructor() {
13
10
  super()
14
11
  this.buttonLabel = translate("Cancel all uploads")
15
- this.fileUploads = fileUploads
16
- this.fileCount = fileUploads.length
12
+ this.fileUploads = []
13
+ this.fileCount = 0
17
14
  this.className = "in-progress"
18
15
  this.visible = true
19
16
  this.handleFileChange = () => this._updateView()
20
17
  }
21
18
 
19
+ /**
20
+ * Initialize the component with file uploads
21
+ * @param {FileUpload[]} fileUploads
22
+ */
23
+ initialize(fileUploads = []) {
24
+ this.fileUploads = fileUploads
25
+ this.fileCount = fileUploads.length
26
+ }
27
+
22
28
  /**
23
29
  * append file progress - components for each file
24
30
  */
@@ -68,7 +68,8 @@ export class Uploader extends AlchemyHTMLElement {
68
68
 
69
69
  const fileUploads = files.map((file) => {
70
70
  const request = new XMLHttpRequest()
71
- const fileUpload = new FileUpload(file, request)
71
+ const fileUpload = new FileUpload()
72
+ fileUpload.initialize(file, request)
72
73
 
73
74
  if (Alchemy.uploader_defaults.upload_limit - 1 < fileUploadCount) {
74
75
  fileUpload.valid = false
@@ -110,7 +111,8 @@ export class Uploader extends AlchemyHTMLElement {
110
111
  this.uploadProgress.cancel()
111
112
  document.body.removeChild(this.uploadProgress)
112
113
  }
113
- this.uploadProgress = new Progress(fileUploads)
114
+ this.uploadProgress = new Progress()
115
+ this.uploadProgress.initialize(fileUploads)
114
116
  this.uploadProgress.onComplete = (status) => {
115
117
  this.dispatchCustomEvent(`upload.${status}`)
116
118
  }
@@ -1,20 +1,19 @@
1
- import { growl } from "alchemy_admin/growler"
2
- import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
3
1
  import { createHtmlElement } from "alchemy_admin/utils/dom_helpers"
4
2
  import { translate } from "alchemy_admin/i18n"
5
3
 
6
- const DEFAULTS = {
4
+ const getDefaults = () => ({
5
+ // The default size of the dialog
7
6
  size: "300x100",
8
7
  title: translate("Please confirm"),
9
8
  ok_label: translate("Yes"),
10
9
  cancel_label: translate("No"),
11
10
  on_ok() {}
12
- }
11
+ })
13
12
 
14
13
  class ConfirmDialog {
15
14
  constructor(message, options = {}) {
16
15
  this.message = message
17
- this.options = { ...DEFAULTS, ...options }
16
+ this.options = { ...getDefaults(), ...options }
18
17
  this.#build()
19
18
  this.#bindEvents()
20
19
  }
@@ -44,6 +43,7 @@ class ConfirmDialog {
44
43
  #bindEvents() {
45
44
  this.cancelButton.addEventListener("click", (evt) => {
46
45
  evt.preventDefault()
46
+ this.options.on_cancel()
47
47
  this.dialog.hide()
48
48
  })
49
49
  this.okButton.addEventListener("click", (evt) => {
@@ -54,6 +54,7 @@ class ConfirmDialog {
54
54
  // Prevent the dialog from closing when the user clicks on the overlay
55
55
  this.dialog.addEventListener("sl-request-close", (event) => {
56
56
  if (event.detail.source === "overlay") {
57
+ this.options.on_cancel()
57
58
  event.preventDefault()
58
59
  }
59
60
  })
@@ -72,60 +73,29 @@ class ConfirmDialog {
72
73
  }
73
74
  }
74
75
 
75
- // Opens a confirm dialog
76
- //
77
- // Arguments:
78
- //
79
- // message - The message that will be displayed to the user (String)
80
- //
81
- // Options:
82
- //
83
- // title: '' - The title of the overlay window (String)
84
- // cancel_label: '' - The label of the cancel button (String)
85
- // ok_label: '' - The label of the ok button (String)
86
- // on_ok: null - The function to invoke on confirmation (Function)
87
- //
76
+ /* Opens a confirm dialog
77
+ *
78
+ * @param {string} message - The message that will be displayed to the user
79
+ * @param {Object} [options={}] - Configuration options for the dialog
80
+ * @param {string} [options.title="Please confirm"] - The title of the overlay window
81
+ * @param {string} [options.cancel_label="No"] - The label of the cancel button
82
+ * @param {string} [options.ok_label="Yes"] - The label of the ok button
83
+ *
84
+ * @returns {Promise<void>} A promise that resolves to true when the OK button is clicked and
85
+ * resolves to false when the cancel button is clicked. Works as confirm dialog replacement
86
+ * for Turbo.confirm.
87
+ */
88
88
  export function openConfirmDialog(message, options = {}) {
89
- const dialog = new ConfirmDialog(message, options)
90
- dialog.open()
91
- return dialog
92
- }
93
-
94
- // Opens a confirm to delete dialog
95
- //
96
- // Arguments:
97
- //
98
- // url - The url to the server delete action. Uses DELETE as HTTP method. (String)
99
- // opts - An options object (Object)
100
- //
101
- // Options:
102
- //
103
- // title: '' - The title of the confirmation window (String)
104
- // message: '' - The message that will be displayed to the user (String)
105
- // ok_label: '' - The label for the ok button (String)
106
- // cancel_label: '' - The label for the cancel button (String)
107
- //
108
- export function confirmToDeleteDialog(url, opts = {}) {
109
- return new Promise((resolve, reject) => {
110
- const options = {
89
+ return new Promise((resolve) => {
90
+ const dialog = new ConfirmDialog(message, {
91
+ ...options,
111
92
  on_ok() {
112
- pleaseWaitOverlay()
113
- $.ajax({
114
- url,
115
- type: "DELETE",
116
- error(xhr, _status, error) {
117
- const type = xhr.status === 403 ? "warning" : "error"
118
- growl(xhr.responseText || error, type)
119
- reject(error)
120
- },
121
- complete(response) {
122
- pleaseWaitOverlay(false)
123
- resolve(response)
124
- }
125
- })
93
+ resolve(true)
94
+ },
95
+ on_cancel() {
96
+ resolve(false)
126
97
  }
127
- }
128
-
129
- openConfirmDialog(opts.message, { ...options, ...opts })
98
+ })
99
+ dialog.open()
130
100
  })
131
101
  }
@@ -233,7 +233,7 @@ export class Dialog {
233
233
  this.dialog_header = $('<div class="alchemy-dialog-header" />')
234
234
  this.dialog_title = $('<div class="alchemy-dialog-title" />')
235
235
  this.close_button = $(
236
- '<a class="alchemy-dialog-close"><alchemy-icon name="close"></alchemy-icon></a>'
236
+ '<button class="alchemy-dialog-close"><alchemy-icon name="close"></alchemy-icon></button>'
237
237
  )
238
238
  this.dialog_title.text(this.options.title)
239
239
  this.dialog_header.append(this.dialog_title)
@@ -27,8 +27,9 @@ function checkPageDirtyness(element) {
27
27
  openConfirmDialog(translate("page_dirty_notice"), {
28
28
  title: translate("warning"),
29
29
  ok_label: translate("ok"),
30
- cancel_label: translate("cancel"),
31
- on_ok: function () {
30
+ cancel_label: translate("cancel")
31
+ }).then((proceed) => {
32
+ if (proceed) {
32
33
  window.onbeforeunload = void 0
33
34
  callback()
34
35
  }
@@ -16,7 +16,7 @@ class FileEditor {
16
16
  this.fileIcon.innerHTML = ""
17
17
  this.fileName.innerHTML = ""
18
18
  this.deleteLink.classList.add("hidden")
19
- this.container.closest("alchemy-element-editor").setDirty()
19
+ this.container.closest("alchemy-element-editor").setDirty(this.formField)
20
20
  return false
21
21
  }
22
22
  }
@@ -1,20 +1,5 @@
1
- import { en } from "alchemy_admin/locales/en"
2
-
3
- Alchemy.translations = Object.assign(Alchemy.translations || {}, { en })
4
-
5
1
  const KEY_SEPARATOR = /\./
6
2
 
7
- function getTranslations() {
8
- const locale = currentLocale()
9
- const translations = Alchemy.translations && Alchemy.translations[locale]
10
-
11
- if (translations) {
12
- return translations
13
- }
14
- console.warn(`Translations for locale ${locale} not found!`)
15
- return {}
16
- }
17
-
18
3
  function nestedTranslation(translations, key) {
19
4
  const keys = key.split(KEY_SEPARATOR)
20
5
  const group = translations[keys[0]]
@@ -25,7 +10,13 @@ function nestedTranslation(translations, key) {
25
10
  }
26
11
 
27
12
  function getTranslation(key) {
28
- const translations = getTranslations()
13
+ const locale = currentLocale()
14
+ const translations = Alchemy.translations
15
+
16
+ if (!translations) {
17
+ console.warn(`Translations for locale ${locale} not found!`)
18
+ return key
19
+ }
29
20
 
30
21
  if (KEY_SEPARATOR.test(key)) {
31
22
  return nestedTranslation(translations, key)
@@ -48,3 +39,11 @@ export function translate(key, replacement = undefined) {
48
39
  }
49
40
  return translation
50
41
  }
42
+
43
+ export async function setupSelectLocale() {
44
+ const locale = currentLocale()
45
+ if (locale === "en") return
46
+
47
+ await import(`select2/${locale}.js`)
48
+ $.extend($.fn.select2.defaults, $.fn.select2.locales[locale])
49
+ }
@@ -1,6 +1,8 @@
1
1
  // Shows spinner while loading images and
2
2
  // fades the image after its been loaded
3
3
 
4
+ import Spinner from "alchemy_admin/spinner"
5
+
4
6
  export default class ImageLoader {
5
7
  static init(scope = document) {
6
8
  if (typeof scope === "string") {
@@ -15,7 +17,7 @@ export default class ImageLoader {
15
17
  constructor(image) {
16
18
  this.image = image
17
19
  this.parent = image.parentNode
18
- this.spinner = new Alchemy.Spinner("small")
20
+ this.spinner = new Spinner("small")
19
21
  this.bind()
20
22
  }
21
23
 
@@ -28,7 +30,7 @@ export default class ImageLoader {
28
30
  if (!force && this.image.complete) return
29
31
 
30
32
  this.image.classList.add("loading")
31
- this.spinner.spin(this.image.parentElement)
33
+ this.spinner.spin(this.parent)
32
34
  }
33
35
 
34
36
  onLoaded() {
@@ -1,8 +1,3 @@
1
- import {
2
- confirmToDeleteDialog,
3
- openConfirmDialog
4
- } from "alchemy_admin/confirm_dialog"
5
-
6
1
  import Hotkeys from "alchemy_admin/hotkeys"
7
2
  import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
8
3
 
@@ -26,44 +21,6 @@ function selectHandler(selectId, parameterName, forcedReload = false) {
26
21
  })
27
22
  }
28
23
 
29
- // Watches elements for Alchemy Dialogs
30
- //
31
- // Links having a data-alchemy-confirm-delete
32
- // and input/buttons having a data-alchemy-confirm attribute get watched.
33
- //
34
- // You can pass a scope so that only elements inside this scope are queried.
35
- //
36
- // The href attribute of the link is the url for the overlay window.
37
- //
38
- // See Dialog for further options you can add to the data attribute.
39
- //
40
- function watchForConfirmDialogs(scope) {
41
- if (scope == null) {
42
- scope = "#alchemy"
43
- }
44
- $(scope).on("click", "[data-alchemy-confirm-delete]", function (event) {
45
- const $this = $(this)
46
- const options = $this.data("alchemy-confirm-delete")
47
- confirmToDeleteDialog($this.attr("href"), options)
48
- event.preventDefault()
49
- })
50
- $(scope).on("click", "[data-alchemy-confirm]", function (event) {
51
- const options = $(this).data("alchemy-confirm")
52
- openConfirmDialog(
53
- options.message,
54
- $.extend(options, {
55
- ok_label: options.ok_label,
56
- cancel_label: options.cancel_label,
57
- on_ok: () => {
58
- pleaseWaitOverlay()
59
- this.form.submit()
60
- }
61
- })
62
- )
63
- event.preventDefault()
64
- })
65
- }
66
-
67
24
  export default function Initializer() {
68
25
  // We obviously have javascript enabled.
69
26
  $("html").removeClass("no-js")
@@ -71,13 +28,8 @@ export default function Initializer() {
71
28
  // Initialize hotkeys.
72
29
  Hotkeys()
73
30
 
74
- // Watch for click on confirm dialog links.
75
- watchForConfirmDialogs()
76
-
77
31
  // Add observer for please wait overlay.
78
- $(".please_wait")
79
- .not("*[data-alchemy-confirm]")
80
- .on("click", Alchemy.pleaseWaitOverlay)
32
+ $(".please_wait").on("click", pleaseWaitOverlay)
81
33
 
82
34
  // Hack for enabling tab focus for <a>'s styled as button.
83
35
  $("a.button").attr({ tabindex: 0 })
@@ -5,7 +5,7 @@ import { growl } from "alchemy_admin/growler"
5
5
  import ImageLoader from "alchemy_admin/image_loader"
6
6
 
7
7
  const UPDATE_DELAY = 125
8
- const IMAGE_PLACEHOLDER = '<alchemy-icon name="image"></alchemy-icon>'
8
+ const IMAGE_PLACEHOLDER = '<alchemy-icon name="image" size="xl"></alchemy-icon>'
9
9
  const THUMBNAIL_SIZE = "160x120"
10
10
 
11
11
  export class PictureEditor {
@@ -75,6 +75,7 @@ export class PictureEditor {
75
75
  this.image.src = data.url
76
76
  this.image.alt = data.alt
77
77
  this.image.title = data.title
78
+ this.setElementDirty()
78
79
  })
79
80
  .catch((error) => {
80
81
  console.error(error.message || error)
@@ -83,8 +84,6 @@ export class PictureEditor {
83
84
  }
84
85
 
85
86
  ensureImage() {
86
- if (this.image) return
87
-
88
87
  const img = new Image()
89
88
  this.thumbnailBackground.replaceChildren(img)
90
89
  this.image = img
@@ -96,7 +95,11 @@ export class PictureEditor {
96
95
  this.pictureIdField.value = ""
97
96
  this.image = null
98
97
  this.cropLink.classList.add("disabled")
99
- this.container.closest(".element-editor").setDirty()
98
+ this.setElementDirty()
99
+ }
100
+
101
+ setElementDirty() {
102
+ this.container.closest(".element-editor").setDirty(this.container)
100
103
  }
101
104
 
102
105
  updateCropLink() {
@@ -15,13 +15,13 @@ function checkedInputs() {
15
15
  }
16
16
 
17
17
  function editMultiplePicturesUrl(href) {
18
- const searchParameters = new URLSearchParams()
18
+ const url = new URL(href)
19
+
19
20
  checkedInputs().forEach((entry) =>
20
- searchParameters.append(entry.name, entry.value)
21
+ url.searchParams.append(entry.name, entry.value)
21
22
  )
22
- const url = href + "?" + searchParameters.toString()
23
23
 
24
- return url
24
+ return url.toString()
25
25
  }
26
26
 
27
27
  /**