alchemy_cms 7.4.16 → 8.0.0.a

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.
Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -77
  3. data/Gemfile +13 -7
  4. data/README.md +13 -5
  5. data/alchemy_cms.gemspec +14 -5
  6. data/app/assets/builds/alchemy/admin/page-select.css +1 -1
  7. data/app/assets/builds/alchemy/admin/print.css +1 -1
  8. data/app/assets/builds/alchemy/admin.css +2 -2
  9. data/app/assets/builds/alchemy/custom-properties.css +1 -1
  10. data/app/assets/builds/alchemy/welcome.css +1 -1
  11. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  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/config/alchemy_manifest.js +0 -2
  15. data/app/assets/images/alchemy/icons-sprite.svg +1 -0
  16. data/app/components/alchemy/admin/resource/applied_filter.rb +29 -0
  17. data/app/components/alchemy/admin/resource/checkbox_filter.rb +36 -0
  18. data/app/components/alchemy/admin/resource/datepicker_filter.rb +42 -0
  19. data/app/components/alchemy/admin/resource/select_filter.rb +43 -0
  20. data/app/components/alchemy/admin/toolbar_button.rb +6 -3
  21. data/app/components/alchemy/ingredients/datetime_view.rb +2 -4
  22. data/app/components/alchemy/ingredients/number_view.rb +18 -0
  23. data/app/controllers/alchemy/admin/attachments_controller.rb +8 -15
  24. data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -6
  25. data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
  26. data/app/controllers/alchemy/admin/languages_controller.rb +1 -4
  27. data/app/controllers/alchemy/admin/pages_controller.rb +15 -17
  28. data/app/controllers/alchemy/admin/pictures_controller.rb +9 -5
  29. data/app/controllers/alchemy/admin/resources_controller.rb +16 -106
  30. data/app/controllers/alchemy/api/nodes_controller.rb +1 -1
  31. data/app/controllers/alchemy/api/pages_controller.rb +0 -2
  32. data/app/controllers/alchemy/attachments_controller.rb +43 -14
  33. data/app/controllers/alchemy/messages_controller.rb +1 -1
  34. data/app/controllers/alchemy/pages_controller.rb +7 -2
  35. data/app/controllers/concerns/alchemy/admin/resource_filter.rb +92 -0
  36. data/app/decorators/alchemy/element_editor.rb +5 -48
  37. data/app/decorators/alchemy/ingredient_editor.rb +4 -62
  38. data/app/helpers/alchemy/admin/base_helper.rb +14 -84
  39. data/app/helpers/alchemy/admin/elements_helper.rb +4 -4
  40. data/app/helpers/alchemy/admin/pages_helper.rb +1 -1
  41. data/app/helpers/alchemy/base_helper.rb +0 -30
  42. data/app/helpers/alchemy/elements_block_helper.rb +0 -14
  43. data/app/helpers/alchemy/pages_helper.rb +2 -2
  44. data/{lib → app/helpers}/alchemy/resources_helper.rb +6 -46
  45. data/app/javascript/alchemy_admin/components/action.js +2 -0
  46. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +3 -3
  47. data/app/javascript/alchemy_admin/components/datepicker.js +10 -2
  48. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +7 -7
  49. data/app/javascript/alchemy_admin/components/element_editor.js +1 -1
  50. data/app/javascript/alchemy_admin/components/index.js +1 -3
  51. data/app/javascript/alchemy_admin/components/remote_select.js +4 -1
  52. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +5 -1
  53. data/app/javascript/alchemy_admin/components/tinymce.js +4 -2
  54. data/app/javascript/alchemy_admin/components/update_check.js +42 -0
  55. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +15 -8
  56. data/app/javascript/alchemy_admin/components/uploader/progress.js +12 -6
  57. data/app/javascript/alchemy_admin/components/uploader.js +4 -2
  58. data/app/javascript/alchemy_admin/confirm_dialog.js +27 -57
  59. data/app/javascript/alchemy_admin/dirty.js +3 -2
  60. data/app/javascript/alchemy_admin/i18n.js +15 -16
  61. data/app/javascript/alchemy_admin/initializer.js +1 -49
  62. data/app/javascript/alchemy_admin/link_dialog.js +1 -6
  63. data/app/javascript/alchemy_admin/utils/ajax.js +51 -44
  64. data/app/javascript/alchemy_admin.js +3 -8
  65. data/app/models/alchemy/admin/filters/base.rb +38 -0
  66. data/app/models/alchemy/admin/filters/checkbox.rb +24 -0
  67. data/app/models/alchemy/admin/filters/datepicker.rb +53 -0
  68. data/app/models/alchemy/admin/filters/select.rb +70 -0
  69. data/app/models/alchemy/admin/resource_name.rb +27 -0
  70. data/app/models/alchemy/attachment.rb +49 -37
  71. data/app/models/alchemy/base_record.rb +2 -0
  72. data/app/models/alchemy/element/definitions.rb +1 -1
  73. data/app/models/alchemy/element/element_ingredients.rb +6 -6
  74. data/app/models/alchemy/element/presenters.rb +3 -12
  75. data/app/models/alchemy/element.rb +9 -27
  76. data/app/models/alchemy/element_definition.rb +160 -0
  77. data/app/models/alchemy/ingredient.rb +11 -44
  78. data/app/models/alchemy/ingredient_definition.rb +134 -0
  79. data/app/models/alchemy/ingredient_validator.rb +7 -3
  80. data/app/models/alchemy/ingredients/number.rb +19 -0
  81. data/app/models/alchemy/language/code.rb +0 -1
  82. data/app/models/alchemy/language.rb +0 -14
  83. data/app/models/alchemy/message.rb +3 -7
  84. data/app/models/alchemy/node.rb +1 -22
  85. data/app/models/alchemy/page/{page_layouts.rb → definitions.rb} +12 -19
  86. data/app/models/alchemy/page/fixed_attributes.rb +1 -1
  87. data/app/models/alchemy/page/page_elements.rb +13 -14
  88. data/app/models/alchemy/page/page_naming.rb +0 -1
  89. data/app/models/alchemy/page/page_natures.rb +7 -7
  90. data/app/models/alchemy/page/page_scopes.rb +1 -1
  91. data/app/models/alchemy/page.rb +11 -33
  92. data/app/models/alchemy/page_definition.rb +115 -0
  93. data/app/models/alchemy/picture.rb +71 -88
  94. data/app/models/alchemy/picture_variant.rb +115 -5
  95. data/{lib → app/models}/alchemy/resource.rb +4 -18
  96. data/{lib → app/models}/alchemy/searchable_resource.rb +15 -0
  97. data/app/models/alchemy/site/layout.rb +5 -6
  98. data/app/models/alchemy/site.rb +0 -15
  99. data/app/models/alchemy/storage_adapter/active_storage/attachment_url.rb +41 -0
  100. data/app/models/alchemy/storage_adapter/active_storage/picture_url.rb +55 -0
  101. data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +40 -0
  102. data/app/models/alchemy/storage_adapter/active_storage.rb +173 -0
  103. data/app/models/alchemy/{attachment/url.rb → storage_adapter/dragonfly/attachment_url.rb} +12 -12
  104. data/app/models/alchemy/{picture/url.rb → storage_adapter/dragonfly/picture_url.rb} +28 -12
  105. data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
  106. data/app/models/alchemy/storage_adapter/dragonfly.rb +183 -0
  107. data/app/models/alchemy/storage_adapter.rb +74 -0
  108. data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
  109. data/app/serializers/alchemy/element_serializer.rb +0 -1
  110. data/app/serializers/alchemy/page_tree_serializer.rb +1 -17
  111. data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -0
  112. data/app/stylesheets/alchemy/_defaults.scss +3 -0
  113. data/app/stylesheets/alchemy/_extends.scss +69 -0
  114. data/app/{assets/stylesheets → stylesheets}/alchemy/_mixins.scss +33 -49
  115. data/app/stylesheets/alchemy/_variables.scss +5 -0
  116. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +20 -37
  117. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +16 -14
  118. data/app/stylesheets/alchemy/admin/buttons.scss +160 -0
  119. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/clipboard.scss +2 -2
  120. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dashboard.scss +13 -16
  121. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dialogs.scss +23 -16
  122. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +150 -105
  123. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
  124. data/app/stylesheets/alchemy/admin/filters.scss +58 -0
  125. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +53 -60
  126. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +21 -7
  127. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +31 -19
  128. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -16
  129. data/app/stylesheets/alchemy/admin/hints.scss +5 -0
  130. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +10 -1
  131. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +10 -8
  132. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/images.scss +1 -1
  133. data/app/stylesheets/alchemy/admin/labels.scss +5 -0
  134. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/lists.scss +3 -3
  135. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/navigation.scss +61 -55
  136. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
  137. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +18 -18
  138. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +2 -2
  139. data/app/stylesheets/alchemy/admin/pagination.scss +144 -0
  140. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/preview_window.scss +8 -6
  141. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
  142. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/resource_info.scss +8 -5
  143. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +9 -6
  144. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +49 -37
  145. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +5 -6
  146. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +38 -33
  147. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +6 -4
  148. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tags.scss +6 -4
  149. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +12 -6
  150. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +2 -2
  151. data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
  152. data/app/stylesheets/alchemy/admin.scss +44 -0
  153. data/app/stylesheets/alchemy/custom-properties.css +244 -0
  154. data/app/stylesheets/alchemy/welcome.scss +75 -0
  155. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/content/alchemy/content.scss +8 -9
  156. data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
  157. data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +133 -136
  158. data/app/views/alchemy/admin/attachments/_files_list.html.erb +2 -2
  159. data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
  160. data/app/views/alchemy/admin/{elements/_clipboard_button.html.erb → clipboard/_button.html.erb} +3 -5
  161. data/app/views/alchemy/admin/clipboard/_update_nested_element_button.turbo_stream.erb +11 -0
  162. data/app/views/alchemy/admin/clipboard/clear.turbo_stream.erb +4 -0
  163. data/app/views/alchemy/admin/clipboard/index.html.erb +15 -13
  164. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +18 -0
  165. data/app/views/alchemy/admin/clipboard/remove.turbo_stream.erb +9 -0
  166. data/app/views/alchemy/admin/dashboard/info.html.erb +17 -31
  167. data/app/views/alchemy/admin/elements/_element.html.erb +4 -8
  168. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  169. data/app/views/alchemy/admin/elements/_header.html.erb +1 -0
  170. data/app/views/alchemy/admin/elements/_toolbar.html.erb +4 -6
  171. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +2 -1
  172. data/app/views/alchemy/admin/elements/index.html.erb +2 -2
  173. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +3 -16
  174. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +1 -10
  175. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  176. data/app/views/alchemy/admin/languages/_table.html.erb +1 -1
  177. data/app/views/alchemy/admin/languages/index.html.erb +5 -2
  178. data/app/views/alchemy/admin/layoutpages/index.html.erb +1 -12
  179. data/app/views/alchemy/admin/pages/_form.html.erb +2 -2
  180. data/app/views/alchemy/admin/pages/_page.html.erb +2 -3
  181. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -15
  182. data/app/views/alchemy/admin/pages/index.html.erb +1 -1
  183. data/app/views/alchemy/admin/pages/info.html.erb +1 -1
  184. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +9 -12
  185. data/app/views/alchemy/admin/partials/_search_form.html.erb +4 -10
  186. data/app/views/alchemy/admin/pictures/_archive.html.erb +4 -7
  187. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +2 -1
  188. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -1
  189. data/app/views/alchemy/admin/pictures/_form.html.erb +1 -1
  190. data/app/views/alchemy/admin/pictures/index.html.erb +2 -7
  191. data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
  192. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +11 -21
  193. data/app/views/alchemy/admin/resources/_pagination.html.erb +6 -0
  194. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +4 -2
  195. data/app/views/alchemy/admin/resources/_resource_table.html.erb +1 -1
  196. data/app/views/alchemy/admin/resources/_table_header.html.erb +1 -15
  197. data/app/views/alchemy/admin/sites/index.html.erb +5 -1
  198. data/app/views/alchemy/admin/styleguide/index.html.erb +8 -0
  199. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  200. data/app/views/alchemy/admin/tinymce/_setup.html.erb +7 -7
  201. data/app/{javascript/alchemy_admin/locales/en.js → views/alchemy/admin/translations/_en.js} +5 -2
  202. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
  203. data/app/views/alchemy/admin/uploader/_setup.html.erb +4 -4
  204. data/app/views/alchemy/ingredients/_number_editor.html.erb +24 -0
  205. data/app/views/alchemy/ingredients/_page_editor.html.erb +1 -0
  206. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +1 -0
  207. data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
  208. data/app/views/alchemy/no_index.html.erb +31 -0
  209. data/app/views/alchemy/welcome.html.erb +12 -10
  210. data/app/views/kaminari/alchemy/_first_page.html.erb +5 -3
  211. data/app/views/kaminari/alchemy/_last_page.html.erb +5 -3
  212. data/app/views/kaminari/alchemy/_next_page.html.erb +5 -3
  213. data/app/views/kaminari/alchemy/_paginator.html.erb +18 -13
  214. data/app/views/kaminari/alchemy/_prev_page.html.erb +5 -3
  215. data/app/views/layouts/alchemy/admin.html.erb +5 -9
  216. data/bun.lockb +0 -0
  217. data/bundles/remixicon.mjs +153 -0
  218. data/config/alchemy/config.yml +3 -2
  219. data/config/initializers/dragonfly.rb +0 -1
  220. data/config/initializers/mime_types.rb +1 -0
  221. data/config/locales/alchemy.en.yml +32 -14
  222. data/config/routes.rb +0 -2
  223. data/eslint.config.js +2 -1
  224. data/lib/alchemy/admin/preview_url.rb +4 -5
  225. data/lib/alchemy/cache_digests/template_tracker.rb +6 -9
  226. data/lib/alchemy/config_missing.rb +14 -0
  227. data/lib/alchemy/configuration/base_option.rb +24 -0
  228. data/lib/alchemy/configuration/boolean_option.rb +16 -0
  229. data/lib/alchemy/configuration/class_option.rb +15 -0
  230. data/lib/alchemy/configuration/class_set_option.rb +46 -0
  231. data/lib/alchemy/configuration/integer_list_option.rb +13 -0
  232. data/lib/alchemy/configuration/integer_option.rb +12 -0
  233. data/lib/alchemy/configuration/list_option.rb +22 -0
  234. data/lib/alchemy/configuration/regexp_option.rb +11 -0
  235. data/lib/alchemy/configuration/string_list_option.rb +13 -0
  236. data/lib/alchemy/configuration/string_option.rb +11 -0
  237. data/lib/alchemy/configuration.rb +115 -0
  238. data/lib/alchemy/configuration_methods.rb +3 -1
  239. data/lib/alchemy/configurations/default_language.rb +12 -0
  240. data/lib/alchemy/configurations/default_site.rb +10 -0
  241. data/lib/alchemy/configurations/format_matchers.rb +11 -0
  242. data/lib/alchemy/configurations/mailer.rb +16 -0
  243. data/lib/alchemy/configurations/main.rb +216 -0
  244. data/lib/alchemy/configurations/preview.rb +32 -0
  245. data/lib/alchemy/configurations/sitemap.rb +10 -0
  246. data/lib/alchemy/configurations/uploader.rb +34 -0
  247. data/lib/alchemy/controller_actions.rb +1 -2
  248. data/lib/alchemy/engine.rb +65 -17
  249. data/lib/alchemy/hints.rb +3 -7
  250. data/lib/alchemy/name_conversions.rb +0 -6
  251. data/lib/alchemy/on_page_layout.rb +2 -2
  252. data/lib/alchemy/permissions.rb +0 -10
  253. data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
  254. data/lib/alchemy/seeder.rb +6 -12
  255. data/lib/alchemy/tasks/usage.rb +4 -4
  256. data/lib/alchemy/test_support/config_stubbing.rb +1 -7
  257. data/lib/alchemy/test_support/factories/attachment_factory.rb +13 -2
  258. data/lib/alchemy/test_support/factories/language_factory.rb +5 -9
  259. data/lib/alchemy/test_support/factories/page_factory.rb +2 -4
  260. data/lib/alchemy/test_support/factories/picture_factory.rb +30 -2
  261. data/lib/alchemy/test_support/factories/site_factory.rb +2 -2
  262. data/lib/alchemy/test_support/having_crop_action_examples.rb +2 -2
  263. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +80 -26
  264. data/lib/alchemy/test_support/shared_ingredient_examples.rb +5 -5
  265. data/lib/alchemy/upgrader/.keep +0 -0
  266. data/lib/alchemy/upgrader/eight_zero.rb +14 -0
  267. data/lib/alchemy/upgrader.rb +33 -20
  268. data/lib/alchemy/version.rb +1 -1
  269. data/lib/alchemy.rb +192 -170
  270. data/lib/alchemy_cms.rb +1 -7
  271. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +0 -3
  272. data/lib/generators/alchemy/install/files/_article.html.erb +6 -4
  273. data/lib/generators/alchemy/install/files/alchemy.en.yml +22 -3
  274. data/lib/generators/alchemy/install/files/application.html.erb +5 -0
  275. data/lib/generators/alchemy/install/install_generator.rb +5 -14
  276. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +196 -0
  277. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -1
  278. data/lib/generators/alchemy/install/templates/elements.yml.tt +3 -1
  279. data/lib/generators/alchemy/install/templates/menus.yml.tt +1 -1
  280. data/lib/generators/alchemy/install/templates/page_layouts.yml.tt +2 -2
  281. data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -2
  282. data/lib/tasks/alchemy/assets.rake +14 -0
  283. data/lib/tasks/alchemy/upgrade.rake +12 -47
  284. data/lib/tasks/alchemy/usage.rake +0 -2
  285. data/vendor/javascript/tinymce.min.js +1 -1
  286. data/vitest.config.js +21 -0
  287. metadata +184 -183
  288. data/app/assets/builds/alchemy/admin/page-select.css.map +0 -1
  289. data/app/assets/builds/alchemy/admin/print.css.map +0 -1
  290. data/app/assets/builds/alchemy/admin.css.map +0 -1
  291. data/app/assets/builds/alchemy/custom-properties.css.map +0 -1
  292. data/app/assets/builds/alchemy/welcome.css.map +0 -1
  293. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +0 -1
  294. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +0 -1
  295. data/app/assets/javascripts/alchemy/admin.js +0 -10
  296. data/app/assets/stylesheets/alchemy/_defaults.scss +0 -3
  297. data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +0 -45
  298. data/app/assets/stylesheets/alchemy/_deprecation.scss +0 -17
  299. data/app/assets/stylesheets/alchemy/_extends.scss +0 -62
  300. data/app/assets/stylesheets/alchemy/_variables.scss +0 -201
  301. data/app/assets/stylesheets/alchemy/admin/buttons.scss +0 -123
  302. data/app/assets/stylesheets/alchemy/admin/hints.scss +0 -5
  303. data/app/assets/stylesheets/alchemy/admin/labels.scss +0 -3
  304. data/app/assets/stylesheets/alchemy/admin/pagination.scss +0 -92
  305. data/app/assets/stylesheets/alchemy/admin.scss +0 -42
  306. data/app/assets/stylesheets/alchemy/custom-properties.css +0 -98
  307. data/app/assets/stylesheets/alchemy/welcome.scss +0 -57
  308. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.css +0 -711
  309. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.css +0 -705
  310. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.min.css +0 -7
  311. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.min.css +0 -7
  312. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.css +0 -29
  313. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.min.css +0 -7
  314. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.css +0 -677
  315. data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.min.css +0 -7
  316. data/app/controllers/alchemy/elements_controller.rb +0 -32
  317. data/app/models/alchemy/element/dom_id.rb +0 -31
  318. data/app/models/alchemy/picture/calculations.rb +0 -49
  319. data/app/models/alchemy/picture/transformations.rb +0 -115
  320. data/app/views/alchemy/admin/attachments/destroy.js.erb +0 -1
  321. data/app/views/alchemy/admin/clipboard/clear.js.erb +0 -3
  322. data/app/views/alchemy/admin/clipboard/insert.js.erb +0 -29
  323. data/app/views/alchemy/admin/clipboard/remove.js.erb +0 -10
  324. data/app/views/alchemy/admin/resources/_filter.html.erb +0 -12
  325. data/app/views/alchemy/admin/resources/_resource.html.erb +0 -34
  326. data/app/views/alchemy/admin/resources/_table.html.erb +0 -29
  327. data/app/views/alchemy/elements/show.html.erb +0 -1
  328. data/app/views/alchemy/elements/show.js.erb +0 -1
  329. data/app/views/alchemy/ingredients/_audio_view.html.erb +0 -1
  330. data/app/views/alchemy/ingredients/_boolean_view.html.erb +0 -1
  331. data/app/views/alchemy/ingredients/_datetime_view.html.erb +0 -3
  332. data/app/views/alchemy/ingredients/_file_view.html.erb +0 -4
  333. data/app/views/alchemy/ingredients/_headline_view.html.erb +0 -4
  334. data/app/views/alchemy/ingredients/_html_view.html.erb +0 -1
  335. data/app/views/alchemy/ingredients/_link_view.html.erb +0 -4
  336. data/app/views/alchemy/ingredients/_node_view.html.erb +0 -1
  337. data/app/views/alchemy/ingredients/_page_view.html.erb +0 -1
  338. data/app/views/alchemy/ingredients/_picture_view.html.erb +0 -4
  339. data/app/views/alchemy/ingredients/_richtext_view.html.erb +0 -3
  340. data/app/views/alchemy/ingredients/_select_view.html.erb +0 -1
  341. data/app/views/alchemy/ingredients/_text_view.html.erb +0 -4
  342. data/app/views/alchemy/ingredients/_video_view.html.erb +0 -3
  343. data/babel.config.js +0 -12
  344. data/config/initializers/assets.rb +0 -4
  345. data/lib/alchemy/config.rb +0 -114
  346. data/lib/alchemy/element_definition.rb +0 -73
  347. data/lib/alchemy/page_layout.rb +0 -73
  348. data/lib/alchemy/resource_filter.rb +0 -40
  349. data/lib/alchemy/upgrader/seven_point_four.rb +0 -26
  350. data/lib/alchemy/upgrader/seven_point_three.rb +0 -52
  351. data/lib/generators/alchemy/ingredient/templates/view.html.erb +0 -1
  352. data/lib/generators/alchemy/install/files/alchemy_admin.js +0 -1
  353. data/lib/generators/alchemy/install/files/all.js +0 -11
  354. data/lib/generators/alchemy/install/files/article.css +0 -25
  355. data/vendor/assets/images/remixicon.symbol.svg +0 -11
  356. /data/app/{assets/stylesheets → stylesheets}/alchemy/_fonts.scss +0 -0
  357. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachment-select.scss +0 -0
  358. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachments.scss +0 -0
  359. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flash.scss +0 -0
  360. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/list_filter.scss +0 -0
  361. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/node-select.scss +0 -0
  362. /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/spinner.scss +0 -0
  363. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/skintool.json +0 -0
  364. /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/fonts/tinymce-mobile.woff +0 -0
@@ -1,7 +1,7 @@
1
1
  import { removeTab } from "alchemy_admin/fixed_elements"
2
2
  import { growl } from "alchemy_admin/growler"
3
3
  import { reloadPreview } from "alchemy_admin/components/preview_window"
4
- import { confirmToDeleteDialog } from "alchemy_admin/confirm_dialog"
4
+ import { openConfirmDialog } from "alchemy_admin/confirm_dialog"
5
5
 
6
6
  export class DeleteElementButton extends HTMLElement {
7
7
  constructor() {
@@ -9,12 +9,12 @@ export class DeleteElementButton extends HTMLElement {
9
9
  this.addEventListener("click", this)
10
10
  }
11
11
 
12
- handleEvent() {
13
- confirmToDeleteDialog(this.url, { message: this.message }).then(
14
- (response) => {
15
- this.#removeElement(response)
16
- }
17
- )
12
+ async handleEvent() {
13
+ const confirmed = await openConfirmDialog(this.message)
14
+ if (confirmed) {
15
+ const response = await fetch(this.url, { method: "DELETE" })
16
+ this.#removeElement(await response.json())
17
+ }
18
18
  }
19
19
 
20
20
  #removeElement(response) {
@@ -231,7 +231,7 @@ export class ElementEditor extends HTMLElement {
231
231
  setDirty() {
232
232
  if (this.hasEditors) {
233
233
  this.dirty = true
234
- window.onbeforeunload = () => Alchemy.t("page_dirty_notice")
234
+ window.onbeforeunload = (event) => event.preventDefault()
235
235
  }
236
236
  }
237
237
 
@@ -1,6 +1,3 @@
1
- "use strict"
2
- // ^ add support for top-level await in Terser
3
-
4
1
  import "alchemy_admin/components/action"
5
2
  import "alchemy_admin/components/attachment_select"
6
3
  import "alchemy_admin/components/button"
@@ -27,6 +24,7 @@ import "alchemy_admin/components/select"
27
24
  import "alchemy_admin/components/spinner"
28
25
  import "alchemy_admin/components/tags_autocomplete"
29
26
  import "alchemy_admin/components/tinymce"
27
+ import "alchemy_admin/components/update_check"
30
28
 
31
29
  await Promise.race([
32
30
  // Load all global custom elements
@@ -1,4 +1,5 @@
1
1
  import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element"
2
+ import { setupSelectLocale } from "alchemy_admin/i18n"
2
3
 
3
4
  export class RemoteSelect extends AlchemyHTMLElement {
4
5
  static properties = {
@@ -9,7 +10,9 @@ export class RemoteSelect extends AlchemyHTMLElement {
9
10
  url: { default: "" }
10
11
  }
11
12
 
12
- connected() {
13
+ async connected() {
14
+ await setupSelectLocale()
15
+
13
16
  this.input.classList.add("alchemy_selectbox")
14
17
 
15
18
  $(this.input)
@@ -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
  }
@@ -76,8 +76,10 @@ class Tinymce extends AlchemyHTMLElement {
76
76
  this.getElementsByTagName("alchemy-spinner")[0].remove()
77
77
 
78
78
  // event listener to mark the editor as dirty
79
- editor.on("dirty", () => this.elementEditor.setDirty())
80
- editor.on("click", () => this.elementEditor.onClickElement(false))
79
+ if (this.elementEditor) {
80
+ editor.on("dirty", () => this.elementEditor.setDirty())
81
+ editor.on("click", () => this.elementEditor.onClickElement(false))
82
+ }
81
83
  })
82
84
  })
83
85
  }
@@ -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
  }
@@ -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
  }
@@ -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,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 })
@@ -1,11 +1,6 @@
1
1
  import { translate } from "alchemy_admin/i18n"
2
2
  import { Dialog } from "alchemy_admin/dialog"
3
3
 
4
- // Matches a URL fragment (#anchor) at the end of a string.
5
- // Covers RFC 3986 unreserved characters (ALPHA, DIGIT, "-", ".", "_", "~")
6
- // which are the characters valid in URL fragments and common in DOM element IDs.
7
- const ANCHOR_REGEX = /#[\w.~-]+$/
8
-
9
4
  // Represents the link Dialog that appears, if a user clicks the link buttons
10
5
  // in TinyMCE or on an Ingredient that has links enabled (e.g. Picture)
11
6
  //
@@ -108,7 +103,7 @@ export class LinkDialog extends Dialog {
108
103
 
109
104
  if (linkType === "internal" && elementAnchor.value !== "") {
110
105
  // remove possible fragments on the url and attach the fragment (which contains the #)
111
- url = url.replace(ANCHOR_REGEX, "") + elementAnchor.value
106
+ url = url.replace(/#\w+$/, "") + elementAnchor.value
112
107
  } else if (linkType === "external" && !url.match(Alchemy.link_url_regexp)) {
113
108
  // show validation error and prevent link creation
114
109
  this.#showValidationError()
@@ -1,27 +1,37 @@
1
- function buildPromise(xhr) {
2
- return new Promise((resolve, reject) => {
3
- xhr.onload = () => {
4
- if (xhr.status >= 200 && xhr.status < 400) {
5
- try {
6
- resolve({
7
- data: JSON.parse(xhr.responseText),
8
- status: xhr.status
9
- })
10
- } catch (error) {
11
- reject(error)
12
- }
13
- } else {
14
- try {
15
- reject(JSON.parse(xhr.responseText))
16
- } catch (error) {
17
- reject(error)
18
- }
19
- }
20
- }
21
- xhr.onerror = () => {
22
- reject(new Error("An error occurred during the transaction"))
23
- }
24
- })
1
+ const JSON_CONTENT_TYPE = "application/json"
2
+
3
+ function isGetRequest(method) {
4
+ return method.toLowerCase() === "get"
5
+ }
6
+
7
+ function prepareURL(path, data, method) {
8
+ const url = new URL(window.location.origin + path)
9
+
10
+ if (data && isGetRequest(method)) {
11
+ url.search = new URLSearchParams(data).toString()
12
+ }
13
+
14
+ return url.toString()
15
+ }
16
+
17
+ function prepareHeaders(accept) {
18
+ return {
19
+ "Content-Type": "application/json; charset=utf-8",
20
+ Accept: accept,
21
+ "X-Requested-With": "XMLHttpRequest",
22
+ "X-CSRF-Token": getToken()
23
+ }
24
+ }
25
+
26
+ function prepareOptions(method, data, accept) {
27
+ const headers = prepareHeaders(accept)
28
+ const options = { method, headers }
29
+
30
+ if (data && !isGetRequest(method)) {
31
+ options.body = JSON.stringify(data)
32
+ }
33
+
34
+ return options
25
35
  }
26
36
 
27
37
  export function getToken() {
@@ -37,30 +47,27 @@ export function patch(url, data) {
37
47
  return ajax("PATCH", url, data)
38
48
  }
39
49
 
40
- export function post(url, data, accept = "application/json") {
50
+ export function post(url, data, accept = JSON_CONTENT_TYPE) {
41
51
  return ajax("POST", url, data, accept)
42
52
  }
43
53
 
44
- export default function ajax(method, path, data, accept = "application/json") {
45
- const xhr = new XMLHttpRequest()
46
- const promise = buildPromise(xhr)
47
- const url = new URL(window.location.origin + path)
54
+ export default async function ajax(
55
+ method,
56
+ path,
57
+ data,
58
+ accept = JSON_CONTENT_TYPE
59
+ ) {
60
+ const response = await fetch(
61
+ prepareURL(path, data, method),
62
+ prepareOptions(method, data, accept)
63
+ )
64
+ const contentType = response.headers.get("content-type")
65
+ const isJson = contentType?.includes(JSON_CONTENT_TYPE)
66
+ const responseData = isJson ? await response.json() : null
48
67
 
49
- if (data && method.toLowerCase() === "get") {
50
- url.search = new URLSearchParams(data).toString()
51
- }
52
-
53
- xhr.open(method, url.toString())
54
- xhr.setRequestHeader("Content-type", "application/json; charset=utf-8")
55
- xhr.setRequestHeader("Accept", accept)
56
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
57
- xhr.setRequestHeader("X-CSRF-Token", getToken())
58
-
59
- if (data && method.toLowerCase() !== "get") {
60
- xhr.send(JSON.stringify(data))
68
+ if (response.ok) {
69
+ return { data: responseData, status: response.status }
61
70
  } else {
62
- xhr.send()
71
+ throw responseData || new Error("An error occurred during the transaction")
63
72
  }
64
-
65
- return promise
66
73
  }
@@ -20,10 +20,7 @@ import Sitemap from "alchemy_admin/sitemap"
20
20
  import Spinner from "alchemy_admin/spinner"
21
21
  import PagePublicationFields from "alchemy_admin/page_publication_fields"
22
22
  import { reloadPreview } from "alchemy_admin/components/preview_window"
23
- import {
24
- openConfirmDialog,
25
- confirmToDeleteDialog
26
- } from "alchemy_admin/confirm_dialog"
23
+ import { openConfirmDialog } from "alchemy_admin/confirm_dialog"
27
24
 
28
25
  // Web Components
29
26
  import "alchemy_admin/components"
@@ -53,11 +50,9 @@ Object.assign(Alchemy, {
53
50
  Sitemap,
54
51
  Spinner,
55
52
  PagePublicationFields,
56
- reloadPreview,
57
- openConfirmDialog,
58
- confirmToDeleteDialog
53
+ reloadPreview
59
54
  })
60
55
 
61
56
  Rails.start()
62
-
57
+ Turbo.config.forms.confirm = openConfirmDialog
63
58
  $(document).on("turbo:load", Initializer)