alchemy_cms 7.0.14 → 7.1.0.pre.b1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (348) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/backport.yml +36 -0
  3. data/.github/workflows/test.yml +3 -2
  4. data/.gitignore +0 -1
  5. data/.standard.yml +1 -1
  6. data/CHANGELOG.md +144 -33
  7. data/Gemfile +7 -11
  8. data/README.md +10 -8
  9. data/alchemy_cms.gemspec +4 -3
  10. data/app/assets/config/alchemy_manifest.js +0 -1
  11. data/app/assets/javascripts/alchemy/admin.js +1 -19
  12. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +2 -3
  13. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +19 -34
  14. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +38 -13
  15. data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +1 -1
  16. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +32 -25
  17. data/app/assets/javascripts/alchemy/alchemy.growler.js.coffee +1 -1
  18. data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +3 -5
  19. data/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee +0 -57
  20. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +22 -63
  21. data/app/assets/javascripts/alchemy/alchemy.list_filter.js.coffee +2 -2
  22. data/app/assets/javascripts/alchemy/alchemy.preview.js.coffee +5 -4
  23. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +5 -5
  24. data/app/assets/javascripts/alchemy/templates/index.js +0 -2
  25. data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
  26. data/app/assets/javascripts/alchemy/templates/page.hbs +1 -1
  27. data/app/assets/javascripts/alchemy/templates/page_folder.hbs +2 -2
  28. data/app/assets/stylesheets/alchemy/_custom-properties.scss +82 -0
  29. data/app/assets/stylesheets/alchemy/_mixins.scss +38 -30
  30. data/app/assets/stylesheets/alchemy/_variables.scss +12 -5
  31. data/app/assets/stylesheets/alchemy/admin.scss +3 -4
  32. data/app/assets/stylesheets/alchemy/archive.scss +107 -50
  33. data/app/assets/stylesheets/alchemy/attachments.scss +5 -4
  34. data/app/assets/stylesheets/alchemy/buttons.scss +38 -164
  35. data/app/assets/stylesheets/alchemy/dashboard.scss +31 -6
  36. data/app/assets/stylesheets/alchemy/dialogs.scss +12 -28
  37. data/app/assets/stylesheets/alchemy/elements.scss +273 -282
  38. data/app/assets/stylesheets/alchemy/flash.scss +20 -12
  39. data/app/assets/stylesheets/alchemy/forms.scss +21 -34
  40. data/app/assets/stylesheets/alchemy/frame.scss +11 -32
  41. data/app/assets/stylesheets/alchemy/hints.scss +4 -62
  42. data/app/assets/stylesheets/alchemy/image_library.scss +36 -33
  43. data/app/assets/stylesheets/alchemy/labels.scss +4 -1
  44. data/app/assets/stylesheets/alchemy/menubar.scss +7 -6
  45. data/app/assets/stylesheets/alchemy/navigation.scss +27 -15
  46. data/app/assets/stylesheets/alchemy/nodes.scss +11 -7
  47. data/app/assets/stylesheets/alchemy/notices.scss +16 -4
  48. data/app/assets/stylesheets/alchemy/page-select.scss +10 -2
  49. data/app/assets/stylesheets/alchemy/pagination.scss +22 -13
  50. data/app/assets/stylesheets/alchemy/preview_window.scss +4 -8
  51. data/app/assets/stylesheets/alchemy/resource_info.scss +7 -5
  52. data/app/assets/stylesheets/alchemy/selects.scss +49 -42
  53. data/app/assets/stylesheets/alchemy/shoelace.scss +345 -0
  54. data/app/assets/stylesheets/alchemy/sitemap.scss +24 -14
  55. data/app/assets/stylesheets/alchemy/spinner.scss +9 -19
  56. data/app/assets/stylesheets/alchemy/tables.scss +16 -24
  57. data/app/assets/stylesheets/alchemy/tags.scss +4 -0
  58. data/app/assets/stylesheets/alchemy/toolbar.scss +29 -25
  59. data/app/assets/stylesheets/alchemy/upload.scss +140 -89
  60. data/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +80 -108
  61. data/app/components/alchemy/admin/node_select.rb +39 -0
  62. data/app/components/alchemy/admin/page_select.rb +42 -0
  63. data/app/components/alchemy/ingredients/audio_view.rb +1 -1
  64. data/app/components/alchemy/ingredients/base_view.rb +1 -1
  65. data/app/components/alchemy/ingredients/boolean_view.rb +1 -1
  66. data/app/components/alchemy/ingredients/datetime_view.rb +1 -1
  67. data/app/components/alchemy/ingredients/file_view.rb +1 -1
  68. data/app/components/alchemy/ingredients/headline_view.rb +7 -16
  69. data/app/components/alchemy/ingredients/link_view.rb +1 -1
  70. data/app/components/alchemy/ingredients/page_view.rb +1 -1
  71. data/app/components/alchemy/ingredients/picture_view.rb +1 -1
  72. data/app/components/alchemy/ingredients/richtext_view.rb +1 -1
  73. data/app/components/alchemy/ingredients/text_view.rb +1 -1
  74. data/app/components/alchemy/ingredients/video_view.rb +1 -1
  75. data/app/controllers/alchemy/admin/base_controller.rb +5 -6
  76. data/app/controllers/alchemy/admin/elements_controller.rb +63 -35
  77. data/app/controllers/alchemy/admin/languages_controller.rb +1 -2
  78. data/app/controllers/alchemy/base_controller.rb +4 -2
  79. data/app/controllers/concerns/alchemy/admin/current_language.rb +1 -5
  80. data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +1 -1
  81. data/app/controllers/concerns/alchemy/site_redirects.rb +1 -1
  82. data/app/decorators/alchemy/element_editor.rb +0 -2
  83. data/app/helpers/alchemy/admin/attachments_helper.rb +6 -5
  84. data/app/helpers/alchemy/admin/base_helper.rb +17 -12
  85. data/app/helpers/alchemy/admin/ingredients_helper.rb +4 -1
  86. data/app/helpers/alchemy/admin/pages_helper.rb +5 -11
  87. data/app/helpers/alchemy/base_helper.rb +47 -13
  88. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +129 -0
  89. data/app/javascript/alchemy_admin/components/button.js +59 -0
  90. data/app/javascript/alchemy_admin/components/char_counter.js +40 -0
  91. data/app/javascript/alchemy_admin/components/datepicker.js +39 -0
  92. data/app/javascript/alchemy_admin/components/dialog_link.js +45 -0
  93. data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +36 -0
  94. data/app/javascript/alchemy_admin/components/element_editor.js +553 -0
  95. data/app/javascript/alchemy_admin/components/ingredient_group.js +54 -0
  96. data/app/javascript/alchemy_admin/components/link_buttons/link_button.js +48 -0
  97. data/app/javascript/alchemy_admin/components/link_buttons/unlink_button.js +38 -0
  98. data/app/javascript/alchemy_admin/components/link_buttons.js +79 -0
  99. data/app/javascript/alchemy_admin/components/node_select.js +45 -0
  100. data/app/javascript/alchemy_admin/components/overlay.js +18 -0
  101. data/app/javascript/alchemy_admin/components/page_select.js +63 -0
  102. data/app/javascript/alchemy_admin/components/remote_select.js +134 -0
  103. data/app/javascript/alchemy_admin/components/select.js +12 -0
  104. data/app/javascript/alchemy_admin/components/spinner.js +31 -0
  105. data/app/javascript/alchemy_admin/components/tinymce.js +146 -0
  106. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +266 -0
  107. data/app/javascript/alchemy_admin/components/uploader/progress.js +258 -0
  108. data/app/javascript/alchemy_admin/components/uploader.js +132 -0
  109. data/app/javascript/alchemy_admin/dirty.js +49 -0
  110. data/app/javascript/alchemy_admin/file_editors.js +1 -1
  111. data/app/javascript/alchemy_admin/gui.js +14 -0
  112. data/app/javascript/alchemy_admin/i18n.js +12 -8
  113. data/app/javascript/alchemy_admin/image_cropper.js +6 -3
  114. data/app/javascript/alchemy_admin/image_loader.js +7 -15
  115. data/app/javascript/alchemy_admin/ingredient_anchor_link.js +2 -5
  116. data/app/javascript/alchemy_admin/initializer.js +65 -0
  117. data/app/javascript/alchemy_admin/locales/en.js +31 -0
  118. data/app/javascript/alchemy_admin/picture_editors.js +2 -2
  119. data/app/javascript/alchemy_admin/picture_selector.js +38 -0
  120. data/app/javascript/alchemy_admin/please_wait_overlay.js +8 -0
  121. data/app/javascript/alchemy_admin/sortable_elements.js +78 -0
  122. data/app/javascript/alchemy_admin/spinner.js +36 -0
  123. data/app/javascript/alchemy_admin/tags_autocomplete.js +46 -0
  124. data/app/javascript/alchemy_admin/utils/ajax.js +6 -5
  125. data/app/javascript/alchemy_admin/utils/dom_helpers.js +20 -0
  126. data/app/javascript/alchemy_admin/utils/format.js +11 -0
  127. data/app/javascript/alchemy_admin/utils/string_conversions.js +10 -0
  128. data/app/javascript/alchemy_admin.js +70 -13
  129. data/app/javascript/menubar.js +10 -0
  130. data/app/models/alchemy/attachment.rb +9 -11
  131. data/app/models/alchemy/element.rb +11 -0
  132. data/app/models/alchemy/ingredients/audio.rb +0 -11
  133. data/app/models/alchemy/ingredients/richtext.rb +1 -10
  134. data/app/models/alchemy/ingredients/video.rb +0 -12
  135. data/app/models/alchemy/node.rb +4 -0
  136. data/app/models/alchemy/page/page_elements.rb +2 -11
  137. data/app/models/alchemy/page/page_natures.rb +10 -2
  138. data/app/models/alchemy/page.rb +9 -51
  139. data/app/models/alchemy/picture/url.rb +1 -9
  140. data/app/serializers/alchemy/page_tree_serializer.rb +2 -1
  141. data/app/services/alchemy/copy_page.rb +98 -0
  142. data/app/views/alchemy/_menubar.html.erb +17 -13
  143. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +14 -10
  144. data/app/views/alchemy/admin/attachments/_attachment.html.erb +44 -36
  145. data/app/views/alchemy/admin/attachments/_replace_button.html.erb +15 -21
  146. data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -1
  147. data/app/views/alchemy/admin/attachments/assign.js.erb +1 -1
  148. data/app/views/alchemy/admin/attachments/index.html.erb +6 -4
  149. data/app/views/alchemy/admin/attachments/show.html.erb +8 -8
  150. data/app/views/alchemy/admin/clipboard/clear.js.erb +1 -1
  151. data/app/views/alchemy/admin/clipboard/index.html.erb +3 -7
  152. data/app/views/alchemy/admin/clipboard/insert.js.erb +1 -1
  153. data/app/views/alchemy/admin/crop.html.erb +1 -1
  154. data/app/views/alchemy/admin/dashboard/_locked_pages.html.erb +1 -1
  155. data/app/views/alchemy/admin/dashboard/index.html.erb +13 -11
  156. data/app/views/alchemy/admin/dashboard/info.html.erb +7 -7
  157. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +21 -23
  158. data/app/views/alchemy/admin/elements/_element.html.erb +52 -44
  159. data/app/views/alchemy/admin/elements/_footer.html.erb +1 -1
  160. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  161. data/app/views/alchemy/admin/elements/_header.html.erb +11 -12
  162. data/app/views/alchemy/admin/elements/_toolbar.html.erb +33 -45
  163. data/app/views/alchemy/admin/elements/create.js.erb +7 -15
  164. data/app/views/alchemy/admin/elements/destroy.js.erb +0 -2
  165. data/app/views/alchemy/admin/elements/index.html.erb +27 -24
  166. data/app/views/alchemy/admin/elements/new.html.erb +9 -11
  167. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +2 -2
  168. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +3 -3
  169. data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +1 -2
  170. data/app/views/alchemy/admin/languages/_form.html.erb +2 -3
  171. data/app/views/alchemy/admin/languages/_language.html.erb +15 -8
  172. data/app/views/alchemy/admin/languages/_table.html.erb +1 -0
  173. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +28 -16
  174. data/app/views/alchemy/admin/layoutpages/index.html.erb +2 -2
  175. data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +12 -8
  176. data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +1 -1
  177. data/app/views/alchemy/admin/nodes/_form.html.erb +20 -21
  178. data/app/views/alchemy/admin/nodes/_node.html.erb +39 -34
  179. data/app/views/alchemy/admin/nodes/index.html.erb +1 -1
  180. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +4 -4
  181. data/app/views/alchemy/admin/pages/_create_language_form.html.erb +2 -2
  182. data/app/views/alchemy/admin/pages/_current_page.html.erb +1 -1
  183. data/app/views/alchemy/admin/pages/_external_link.html.erb +4 -4
  184. data/app/views/alchemy/admin/pages/_file_link.html.erb +5 -5
  185. data/app/views/alchemy/admin/pages/_form.html.erb +10 -21
  186. data/app/views/alchemy/admin/pages/_internal_link.html.erb +4 -4
  187. data/app/views/alchemy/admin/pages/_locked_page.html.erb +2 -2
  188. data/app/views/alchemy/admin/pages/_new_page_form.html.erb +4 -17
  189. data/app/views/alchemy/admin/pages/_page.html.erb +76 -72
  190. data/app/views/alchemy/admin/pages/_page_infos.html.erb +23 -7
  191. data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +2 -1
  192. data/app/views/alchemy/admin/pages/_page_status.html.erb +11 -21
  193. data/app/views/alchemy/admin/pages/_publication_fields.html.erb +2 -5
  194. data/app/views/alchemy/admin/pages/_table.html.erb +1 -1
  195. data/app/views/alchemy/admin/pages/_table_row.html.erb +43 -39
  196. data/app/views/alchemy/admin/pages/_toolbar.html.erb +43 -38
  197. data/app/views/alchemy/admin/pages/configure.html.erb +12 -14
  198. data/app/views/alchemy/admin/pages/edit.html.erb +80 -103
  199. data/app/views/alchemy/admin/pages/info.html.erb +20 -11
  200. data/app/views/alchemy/admin/pages/link.html.erb +22 -16
  201. data/app/views/alchemy/admin/pages/new.html.erb +9 -11
  202. data/app/views/alchemy/admin/pages/unlock.js.erb +10 -3
  203. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +15 -13
  204. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +3 -5
  205. data/app/views/alchemy/admin/partials/_routes.html.erb +10 -2
  206. data/app/views/alchemy/admin/partials/_site_select.html.erb +6 -5
  207. data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +28 -23
  208. data/app/views/alchemy/admin/pictures/_archive.html.erb +5 -5
  209. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
  210. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +21 -23
  211. data/app/views/alchemy/admin/pictures/_infos.html.erb +2 -6
  212. data/app/views/alchemy/admin/pictures/_picture.html.erb +15 -17
  213. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +17 -16
  214. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -1
  215. data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +1 -1
  216. data/app/views/alchemy/admin/pictures/assign.js.erb +1 -1
  217. data/app/views/alchemy/admin/pictures/index.html.erb +34 -30
  218. data/app/views/alchemy/admin/pictures/show.html.erb +3 -3
  219. data/app/views/alchemy/admin/resources/_filter.html.erb +2 -2
  220. data/app/views/alchemy/admin/resources/_form.html.erb +2 -2
  221. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +1 -1
  222. data/app/views/alchemy/admin/resources/_resource.html.erb +16 -9
  223. data/app/views/alchemy/admin/resources/_table.html.erb +4 -1
  224. data/app/views/alchemy/admin/resources/index.html.erb +22 -19
  225. data/app/views/alchemy/admin/sites/index.html.erb +2 -1
  226. data/app/views/alchemy/admin/styleguide/index.html.erb +54 -28
  227. data/app/views/alchemy/admin/tags/_tag.html.erb +16 -18
  228. data/app/views/alchemy/admin/tags/index.html.erb +15 -12
  229. data/app/views/alchemy/admin/tinymce/_setup.html.erb +29 -0
  230. data/app/views/alchemy/admin/uploader/_button.html.erb +23 -29
  231. data/app/views/alchemy/admin/uploader/_setup.html.erb +3 -8
  232. data/app/views/alchemy/base/500.html.erb +1 -1
  233. data/app/views/alchemy/base/error_notice.js.erb +0 -1
  234. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +1 -1
  235. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +1 -1
  236. data/app/views/alchemy/ingredients/_file_editor.html.erb +5 -5
  237. data/app/views/alchemy/ingredients/_link_editor.html.erb +1 -1
  238. data/app/views/alchemy/ingredients/_node_editor.html.erb +6 -19
  239. data/app/views/alchemy/ingredients/_page_editor.html.erb +7 -19
  240. data/app/views/alchemy/ingredients/_picture_editor.html.erb +2 -2
  241. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +6 -15
  242. data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
  243. data/app/views/alchemy/ingredients/_text_editor.html.erb +1 -1
  244. data/app/views/alchemy/ingredients/shared/_anchor.html.erb +1 -1
  245. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +10 -20
  246. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +42 -49
  247. data/app/views/kaminari/alchemy/_first_page.html.erb +4 -2
  248. data/app/views/kaminari/alchemy/_gap.html.erb +1 -1
  249. data/app/views/kaminari/alchemy/_last_page.html.erb +4 -2
  250. data/app/views/kaminari/alchemy/_next_page.html.erb +4 -2
  251. data/app/views/kaminari/alchemy/_prev_page.html.erb +4 -2
  252. data/app/views/layouts/alchemy/admin.html.erb +10 -29
  253. data/config/alchemy/modules.yml +30 -30
  254. data/config/importmap.rb +10 -1
  255. data/config/initializers/rails_live_reload.rb +13 -0
  256. data/config/locales/alchemy.en.yml +23 -9
  257. data/config/routes.rb +2 -1
  258. data/lib/alchemy/auth_accessors.rb +6 -1
  259. data/lib/alchemy/controller_actions.rb +17 -4
  260. data/lib/alchemy/dev_support/live_reload_watcher.rb +5 -0
  261. data/lib/alchemy/engine.rb +8 -2
  262. data/lib/alchemy/forms/builder.rb +18 -12
  263. data/lib/alchemy/modules.rb +2 -2
  264. data/lib/alchemy/resources_helper.rb +3 -3
  265. data/lib/alchemy/routing_constraints.rb +1 -1
  266. data/lib/alchemy/test_support/capybara_helpers.rb +8 -5
  267. data/lib/alchemy/test_support/rspec_matchers.rb +14 -0
  268. data/lib/alchemy/test_support/shared_uploader_examples.rb +1 -1
  269. data/lib/alchemy/tinymce.rb +8 -3
  270. data/lib/alchemy/version.rb +1 -1
  271. data/package.json +14 -5
  272. data/vendor/assets/fonts/remixicon.eot +0 -0
  273. data/vendor/assets/fonts/remixicon.svg +7816 -0
  274. data/vendor/assets/fonts/remixicon.ttf +0 -0
  275. data/vendor/assets/fonts/remixicon.woff +0 -0
  276. data/vendor/assets/fonts/remixicon.woff2 +0 -0
  277. data/vendor/assets/stylesheets/remixicon.scss +10480 -0
  278. metadata +85 -97
  279. data/.gem_release.yml +0 -8
  280. data/app/assets/javascripts/alchemy/alchemy.autocomplete.js.coffee +0 -30
  281. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +0 -53
  282. data/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee +0 -45
  283. data/app/assets/javascripts/alchemy/alchemy.char_counter.js.coffee +0 -19
  284. data/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee +0 -59
  285. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +0 -79
  286. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +0 -267
  287. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +0 -27
  288. data/app/assets/javascripts/alchemy/alchemy.spinner.js +0 -32
  289. data/app/assets/javascripts/alchemy/alchemy.tooltips.coffee +0 -10
  290. data/app/assets/javascripts/alchemy/alchemy.uploader.js.coffee +0 -131
  291. data/app/assets/javascripts/alchemy/menubar.js.coffee +0 -8
  292. data/app/assets/javascripts/alchemy/node_select.js +0 -39
  293. data/app/assets/javascripts/alchemy/page_select.js +0 -46
  294. data/app/assets/javascripts/alchemy/templates/node.hbs +0 -16
  295. data/app/assets/javascripts/alchemy/templates/spinner.hbs +0 -7
  296. data/app/assets/stylesheets/alchemy/jquery-ui.scss +0 -435
  297. data/app/javascript/alchemy_admin/datepicker.js +0 -40
  298. data/app/javascript/alchemy_admin/tinymce.js +0 -146
  299. data/app/javascript/alchemy_admin/translations.js +0 -32
  300. data/app/views/alchemy/admin/elements/fold.js.erb +0 -33
  301. data/app/views/alchemy/admin/elements/order.js.erb +0 -11
  302. data/app/views/alchemy/admin/elements/publish.js.erb +0 -21
  303. data/app/views/alchemy/admin/elements/update.js.erb +0 -27
  304. data/vendor/assets/fonts/fa-regular-400.eot +0 -0
  305. data/vendor/assets/fonts/fa-regular-400.svg +0 -803
  306. data/vendor/assets/fonts/fa-regular-400.ttf +0 -0
  307. data/vendor/assets/fonts/fa-regular-400.woff +0 -0
  308. data/vendor/assets/fonts/fa-regular-400.woff2 +0 -0
  309. data/vendor/assets/fonts/fa-solid-900.eot +0 -0
  310. data/vendor/assets/fonts/fa-solid-900.svg +0 -4938
  311. data/vendor/assets/fonts/fa-solid-900.ttf +0 -0
  312. data/vendor/assets/fonts/fa-solid-900.woff +0 -0
  313. data/vendor/assets/fonts/fa-solid-900.woff2 +0 -0
  314. data/vendor/assets/javascripts/fileupload/jquery.fileupload-process.js +0 -178
  315. data/vendor/assets/javascripts/fileupload/jquery.fileupload-validate.js +0 -125
  316. data/vendor/assets/javascripts/fileupload/jquery.fileupload.js +0 -1502
  317. data/vendor/assets/javascripts/fileupload/jquery.iframe-transport.js +0 -224
  318. data/vendor/assets/javascripts/jquery-ui/data.js +0 -45
  319. data/vendor/assets/javascripts/jquery-ui/ie.js +0 -20
  320. data/vendor/assets/javascripts/jquery-ui/keycode.js +0 -51
  321. data/vendor/assets/javascripts/jquery-ui/plugin.js +0 -49
  322. data/vendor/assets/javascripts/jquery-ui/safe-active-element.js +0 -46
  323. data/vendor/assets/javascripts/jquery-ui/safe-blur.js +0 -27
  324. data/vendor/assets/javascripts/jquery-ui/scroll-parent.js +0 -50
  325. data/vendor/assets/javascripts/jquery-ui/unique-id.js +0 -54
  326. data/vendor/assets/javascripts/jquery-ui/version.js +0 -20
  327. data/vendor/assets/javascripts/jquery-ui/widget.js +0 -754
  328. data/vendor/assets/javascripts/jquery-ui/widgets/draggable.js +0 -1268
  329. data/vendor/assets/javascripts/jquery-ui/widgets/mouse.js +0 -241
  330. data/vendor/assets/javascripts/jquery-ui/widgets/sortable.js +0 -1623
  331. data/vendor/assets/javascripts/jquery-ui/widgets/tabs.js +0 -931
  332. data/vendor/assets/javascripts/jquery_plugins/jquery.scrollTo.min.js +0 -7
  333. data/vendor/assets/javascripts/jquery_plugins/jquery.ui.tabspaging.js +0 -296
  334. data/vendor/assets/stylesheets/fontawesome/_animated.scss +0 -20
  335. data/vendor/assets/stylesheets/fontawesome/_bordered-pulled.scss +0 -20
  336. data/vendor/assets/stylesheets/fontawesome/_core.scss +0 -21
  337. data/vendor/assets/stylesheets/fontawesome/_fixed-width.scss +0 -6
  338. data/vendor/assets/stylesheets/fontawesome/_icons.scss +0 -1441
  339. data/vendor/assets/stylesheets/fontawesome/_larger.scss +0 -23
  340. data/vendor/assets/stylesheets/fontawesome/_list.scss +0 -18
  341. data/vendor/assets/stylesheets/fontawesome/_mixins.scss +0 -56
  342. data/vendor/assets/stylesheets/fontawesome/_rotated-flipped.scss +0 -24
  343. data/vendor/assets/stylesheets/fontawesome/_screen-reader.scss +0 -5
  344. data/vendor/assets/stylesheets/fontawesome/_stacked.scss +0 -31
  345. data/vendor/assets/stylesheets/fontawesome/_variables.scss +0 -1458
  346. data/vendor/assets/stylesheets/fontawesome/fontawesome.scss +0 -16
  347. data/vendor/assets/stylesheets/fontawesome/regular.scss +0 -23
  348. data/vendor/assets/stylesheets/fontawesome/solid.scss +0 -24
@@ -15,7 +15,7 @@ module Alchemy
15
15
  ingredient.stripped_body
16
16
  else
17
17
  value.to_s.html_safe
18
- end.html_safe
18
+ end
19
19
  end
20
20
  end
21
21
  end
@@ -24,7 +24,7 @@ module Alchemy
24
24
  target: ((link_target == "blank") ? "_blank" : nil),
25
25
  data: {link_target: link_target}
26
26
  }.merge(html_options))
27
- end.html_safe
27
+ end
28
28
  end
29
29
 
30
30
  private
@@ -6,7 +6,7 @@ module Alchemy
6
6
  def call
7
7
  content_tag(:video, html_options) do
8
8
  tag(:source, src: src, type: attachment.file_mime_type)
9
- end.html_safe
9
+ end
10
10
  end
11
11
 
12
12
  def render?
@@ -48,14 +48,13 @@ module Alchemy
48
48
  @error = error
49
49
  # truncate the message, because very long error messages (i.e from mysql2) causes cookie overflow errors
50
50
  @notice = error.message[0..255]
51
- @trace = error.backtrace[0..50]
52
- if request.xhr?
51
+ if request.format.json?
52
+ render json: {message: @notice}, status: 500
53
+ elsif request.xhr?
53
54
  render action: "error_notice"
54
55
  else
55
- respond_to do |format|
56
- format.html { render "500", status: 500 }
57
- format.json { render json: {message: @notice}, status: 500 }
58
- end
56
+ @trace = error.backtrace[0..50]
57
+ render "500", status: 500
59
58
  end
60
59
  end
61
60
 
@@ -3,7 +3,7 @@
3
3
  module Alchemy
4
4
  module Admin
5
5
  class ElementsController < Alchemy::Admin::BaseController
6
- before_action :load_element, only: [:update, :destroy, :fold, :publish]
6
+ before_action :load_element, only: [:update, :destroy, :collapse, :expand, :publish]
7
7
  authorize_resource class: Alchemy::Element
8
8
 
9
9
  def index
@@ -53,13 +53,25 @@ module Alchemy
53
53
  # Updates the element and all ingredients in the element.
54
54
  #
55
55
  def update
56
- @page = @element.page
57
-
58
56
  if @element.update(element_params)
59
- @element_validated = true
57
+ render json: {
58
+ notice: Alchemy.t(:element_saved),
59
+ previewText: Rails::Html::SafeListSanitizer.new.sanitize(@element.preview_text),
60
+ ingredientAnchors: @element.ingredients.select { |i| i.settings[:anchor] }.map do |ingredient|
61
+ {
62
+ ingredientId: ingredient.id,
63
+ active: ingredient.dom_id.present?
64
+ }
65
+ end
66
+ }
60
67
  else
61
- element_update_error
62
- @error_messages = @element.ingredient_error_messages
68
+ @warning = Alchemy.t("Validation failed")
69
+ render json: {
70
+ warning: @warning,
71
+ errorMessage: Alchemy.t(:ingredient_validations_headline),
72
+ ingredientsWithErrors: @element.ingredients_with_errors.map(&:id),
73
+ errors: @element.ingredient_error_messages
74
+ }
63
75
  end
64
76
  end
65
77
 
@@ -70,37 +82,59 @@ module Alchemy
70
82
  end
71
83
 
72
84
  def publish
73
- @element.update(public: !@element.public?)
85
+ @element.public = !@element.public?
86
+ @element.save(validate: false)
87
+ render json: {
88
+ public: @element.public?,
89
+ label: @element.public? ? Alchemy.t(:hide_element) : Alchemy.t(:show_element)
90
+ }
74
91
  end
75
92
 
76
93
  def order
77
- @parent_element = Element.find_by(id: params[:parent_element_id])
78
- Element.transaction do
79
- params.fetch(:element_ids, []).each.with_index(1) do |element_id, position|
80
- # We need to set the parent_element_id, because we might have dragged the
81
- # element over from another nestable element
82
- Element.find_by(id: element_id).update_columns(
83
- parent_element_id: params[:parent_element_id],
84
- position: position
85
- )
86
- end
87
- # Need to manually touch the parent because Rails does not do it
88
- # with the update_columns above
89
- @parent_element&.touch
94
+ @element = Element.find(params[:element_id])
95
+ @element.update(
96
+ parent_element_id: params[:parent_element_id],
97
+ position: params[:position]
98
+ )
99
+ if params[:parent_element_id].present?
100
+ @parent_element = Element.find_by(id: params[:parent_element_id])
90
101
  end
102
+
103
+ render json: {
104
+ message: Alchemy.t(:successfully_saved_element_position),
105
+ preview_text: @element.preview_text
106
+ }
91
107
  end
92
108
 
93
- # Toggle folds the element and persists the state in the db
109
+ # Collapses the element, all nested elements and persists the state in the db
94
110
  #
95
- def fold
96
- @page = @element.page
111
+ def collapse
97
112
  # We do not want to trigger the touch callback or any validations
98
- @element.update_columns(folded: !@element.folded)
99
- # Fold all nested elements if folded
100
- if @element.folded?
101
- ids = collapse_nested_elements_ids(@element)
102
- Alchemy::Element.where(id: ids).update_all(folded: true)
103
- end
113
+ @element.update_columns(folded: true)
114
+ # Collapse all nested elements
115
+ nested_elements_ids = collapse_nested_elements_ids(@element)
116
+ Alchemy::Element.where(id: nested_elements_ids).update_all(folded: true)
117
+
118
+ render json: {
119
+ nestedElementIds: nested_elements_ids,
120
+ title: Alchemy.t(@element.folded? ? :show_element_content : :hide_element_content)
121
+ }
122
+ end
123
+
124
+ # Expands the element, all parents and persists the state in the db
125
+ #
126
+ def expand
127
+ # We do not want to trigger the touch callback or any validations
128
+ @element.update_columns(folded: false)
129
+ # We want to expand the upper most parent first in order to prevent
130
+ # re-painting issues in the browser
131
+ parent_element_ids = @element.parent_element_ids.reverse
132
+ Alchemy::Element.where(id: parent_element_ids).update_all(folded: false)
133
+
134
+ render json: {
135
+ parentElementIds: parent_element_ids,
136
+ title: Alchemy.t(@element.folded? ? :show_element_content : :hide_element_content)
137
+ }
104
138
  end
105
139
 
106
140
  private
@@ -171,12 +205,6 @@ module Alchemy
171
205
  def create_element_params
172
206
  params.require(:element).permit(:name, :page_version_id, :parent_element_id)
173
207
  end
174
-
175
- def element_update_error
176
- @element_validated = false
177
- @notice = Alchemy.t("Validation failed")
178
- @error_message = "<h2>#{@notice}</h2><p>#{Alchemy.t(:ingredient_validations_headline)}</p>".html_safe
179
- end
180
208
  end
181
209
  end
182
210
  end
@@ -38,8 +38,7 @@ module Alchemy
38
38
  end
39
39
 
40
40
  def switch
41
- @language = set_alchemy_language(params[:language_id])
42
- session[:alchemy_language_id] = @language.id
41
+ set_alchemy_language(params[:language_id])
43
42
  do_redirect_to request.referer || alchemy.admin_dashboard_path
44
43
  end
45
44
 
@@ -53,7 +53,9 @@ module Alchemy
53
53
  #{current_alchemy_user.inspect}
54
54
  WARN
55
55
  end
56
- if current_alchemy_user
56
+ if request.format.json?
57
+ render json: {message: Alchemy.t("You are not authorized")}, status: :unauthorized
58
+ elsif current_alchemy_user
57
59
  handle_redirect_for_user
58
60
  else
59
61
  handle_redirect_for_guest
@@ -65,7 +67,7 @@ module Alchemy
65
67
  if can?(:index, :alchemy_admin_dashboard)
66
68
  redirect_or_render_notice
67
69
  else
68
- redirect_to("/")
70
+ redirect_to Alchemy.unauthorized_path
69
71
  end
70
72
  end
71
73
 
@@ -12,11 +12,7 @@ module Alchemy
12
12
  private
13
13
 
14
14
  def load_current_language
15
- @current_language = if session[:alchemy_language_id].present?
16
- set_alchemy_language(session[:alchemy_language_id])
17
- else
18
- Alchemy::Language.current
19
- end
15
+ @current_language = Alchemy::Language.current
20
16
  if @current_language.nil?
21
17
  flash[:warning] = Alchemy.t("Please create a language first.")
22
18
  redirect_to admin_languages_path
@@ -33,7 +33,7 @@ module Alchemy
33
33
  def uploader_response(file:, message:)
34
34
  {
35
35
  files: [file.to_jq_upload],
36
- growl_message: message
36
+ message: message
37
37
  }
38
38
  end
39
39
  end
@@ -12,7 +12,7 @@ module Alchemy
12
12
  private
13
13
 
14
14
  def enforce_primary_host_for_site
15
- redirect_to url_for(host: current_alchemy_site.host), status: :moved_permanently
15
+ redirect_to url_for(host: current_alchemy_site.host), status: :moved_permanently, allow_other_host: true
16
16
  end
17
17
 
18
18
  def needs_redirect_to_primary_host?
@@ -65,8 +65,6 @@ module Alchemy
65
65
 
66
66
  # Tells us, if we should show the element footer and form inputs.
67
67
  def editable?
68
- return false if folded?
69
-
70
68
  ingredient_definitions.any? || taggable?
71
69
  end
72
70
 
@@ -4,17 +4,18 @@ module Alchemy
4
4
  module Admin
5
5
  module AttachmentsHelper
6
6
  include Alchemy::Admin::BaseHelper
7
+ include Alchemy::Filetypes
7
8
 
8
9
  def mime_to_human(mime)
9
10
  Alchemy.t(mime, scope: "mime_types", default: Alchemy.t(:document))
10
11
  end
11
12
 
12
13
  def attachment_preview_size(attachment)
13
- case attachment.icon_css_class
14
- when "image" then "600x475"
15
- when "audio" then "600x190"
16
- when "video" then "600x485"
17
- when "pdf" then "600x500"
14
+ case attachment.file_mime_type
15
+ when *IMAGE_FILE_TYPES then "600x475"
16
+ when *AUDIO_FILE_TYPES then "600x190"
17
+ when *VIDEO_FILE_TYPES then "600x485"
18
+ when "application/pdf" then "600x600"
18
19
  else
19
20
  "600x145"
20
21
  end
@@ -55,7 +55,10 @@ module Alchemy
55
55
  default_options = {modal: true}
56
56
  options = default_options.merge(options)
57
57
  link_to content, url,
58
- html_options.merge("data-alchemy-dialog" => options.to_json)
58
+ html_options.merge(
59
+ "data-dialog-options" => options.to_json,
60
+ :is => "alchemy-dialog-link"
61
+ )
59
62
  end
60
63
 
61
64
  # Used for translations selector in Alchemy cockpit user settings.
@@ -189,7 +192,7 @@ module Alchemy
189
192
  options = {
190
193
  title: Alchemy.t("Delete"),
191
194
  message: Alchemy.t("Are you sure?"),
192
- icon: :minus
195
+ icon: "delete-bin-2"
193
196
  }.merge(options)
194
197
  button_with_confirm(
195
198
  render_icon(options[:icon]),
@@ -322,18 +325,20 @@ module Alchemy
322
325
  date = Time.zone.parse(date) if date.is_a?(String)
323
326
  value = date&.iso8601
324
327
 
325
- text_field object.class.name.demodulize.underscore.to_sym,
326
- method.to_sym, {:type => "text", :class => type, "data-datepicker-type" => type, :value => value}.merge(html_options)
328
+ input_field = text_field object.class.name.demodulize.underscore.to_sym,
329
+ method.to_sym, {type: "text", class: type, value: value}.merge(html_options)
330
+
331
+ content_tag("alchemy-datepicker", input_field, "input-type" => type)
327
332
  end
328
333
 
329
334
  # Render a hint icon with tooltip for given object.
330
335
  # The model class needs to include the hints module
331
- def render_hint_for(element)
336
+ def render_hint_for(element, icon_options = {})
332
337
  return unless element.has_hint?
333
338
 
334
- content_tag :span, class: "hint-with-icon" do
335
- render_icon("question-circle") +
336
- content_tag(:span, element.hint.html_safe, class: "hint-bubble")
339
+ content_tag "sl-tooltip", class: "like-hint-tooltip", placement: "bottom-start" do
340
+ render_icon("question", icon_options) +
341
+ content_tag(:span, element.hint.html_safe, slot: "content")
337
342
  end
338
343
  end
339
344
 
@@ -372,12 +377,12 @@ module Alchemy
372
377
  # <%= hint_with_tooltip('Page layout is missing', icon: 'info') %>
373
378
  #
374
379
  # @param text [String] - The text displayed in the tooltip
375
- # @param icon: 'exclamation-triangle' [String] - Icon name
380
+ # @param icon: 'alert' [String] - Icon name
376
381
  #
377
382
  # @return [String]
378
- def hint_with_tooltip(text, icon: "exclamation-triangle")
379
- content_tag :span, class: "hint-with-icon" do
380
- render_icon(icon) + content_tag(:span, text, class: "hint-bubble")
383
+ def hint_with_tooltip(text, icon: "alert")
384
+ content_tag :"sl-tooltip", class: "like-hint-tooltip", content: text, placement: "bottom" do
385
+ render_icon(icon)
381
386
  end
382
387
  end
383
388
 
@@ -34,7 +34,10 @@ module Alchemy
34
34
  # Renders the label and hint for a ingredient.
35
35
  def ingredient_label(ingredient, column = :value, html_options = {})
36
36
  label_tag ingredient.form_field_id(column), html_options do
37
- [render_ingredient_role(ingredient), render_hint_for(ingredient)].compact.join("&nbsp;").html_safe
37
+ [
38
+ render_ingredient_role(ingredient),
39
+ render_hint_for(ingredient, size: "lg", fixed_width: false)
40
+ ].compact.join("&nbsp;").html_safe
38
41
  end
39
42
  end
40
43
  end
@@ -36,21 +36,15 @@ module Alchemy
36
36
 
37
37
  def page_status_checkbox(page, attribute)
38
38
  label = page.class.human_attribute_name(attribute)
39
-
40
- if page.attribute_fixed?(attribute)
41
- checkbox = check_box(:page, attribute, disabled: true)
42
- hint = content_tag(:span, class: "hint-bubble") do
43
- Alchemy.t(:attribute_fixed, attribute: attribute)
44
- end
45
- content = content_tag(:span, class: "with-hint") do
46
- "#{checkbox}\n#{label}\n#{hint}".html_safe
39
+ checkbox = if page.attribute_fixed?(attribute)
40
+ content_tag("sl-tooltip", class: "like-hint-tooltip", content: Alchemy.t(:attribute_fixed, attribute: attribute), placement: "bottom-start") do
41
+ check_box_tag("page[#{attribute}]", "1", page.send(attribute), disabled: true)
47
42
  end
48
43
  else
49
- checkbox = check_box(:page, attribute)
50
- content = "#{checkbox}\n#{label}".html_safe
44
+ check_box(:page, attribute)
51
45
  end
52
46
 
53
- content_tag(:label, class: "checkbox") { content }
47
+ content_tag(:label, class: "checkbox") { "#{checkbox}\n#{label}".html_safe }
54
48
  end
55
49
  end
56
50
  end
@@ -19,21 +19,21 @@ module Alchemy
19
19
  end
20
20
  end
21
21
 
22
- # Render a Fontawesome icon
22
+ # Render a Remix icon
23
23
  #
24
- # @param icon_class [String] Fontawesome icon name
25
- # @param size: nil [String] Fontawesome icon size
26
- # @param transform: nil [String] Fontawesome transform style
24
+ # @param icon_name [String] icon name
25
+ # @option options - style: nil [String] icon style. line or fill
26
+ # @option options - size: nil [String] icon size
27
27
  #
28
28
  # @return [String]
29
- def render_icon(icon_class, options = {})
30
- options = {style: "solid"}.merge(options)
29
+ def render_icon(icon_name, options = {})
30
+ options = {style: "line", fixed_width: true}.merge(options)
31
+ style = options[:style] && "-#{options[:style]}"
31
32
  classes = [
32
- "icon fa-fw",
33
- "fa-#{icon_class}",
34
- "fa#{options[:style].first}",
35
- options[:size] ? "fa-#{options[:size]}" : nil,
36
- options[:transform] ? "fa-#{options[:transform]}" : nil,
33
+ "icon",
34
+ "ri-#{ri_icon(icon_name)}#{style}",
35
+ options[:size] ? "ri-#{options[:size]}" : nil,
36
+ options[:fixed_width] ? "ri-fw" : nil,
37
37
  options[:class]
38
38
  ].compact
39
39
  content_tag("i", nil, class: classes)
@@ -87,18 +87,52 @@ module Alchemy
87
87
  end
88
88
  end
89
89
 
90
- # Returns the FontAwesome icon name for given message type
90
+ # Returns the icon name for given message type
91
91
  #
92
92
  # @param message_type [String] The message type. One of +warning+, +info+, +notice+, +error+
93
- # @return [String] The FontAwesome icon name
93
+ # @return [String] The icon name
94
94
  def message_icon_class(message_type)
95
95
  case message_type.to_s
96
96
  when "warning", "warn", "alert" then "exclamation"
97
97
  when "notice" then "check"
98
98
  when "error" then "bug"
99
+ when "hint" then "info"
99
100
  else
100
101
  message_type
101
102
  end
102
103
  end
104
+
105
+ private
106
+
107
+ # Returns the Remix icon name for given icon name
108
+ #
109
+ # @param icon_name [String] The icon name.
110
+ # @return [String] The Remix icon class
111
+ def ri_icon(icon_name)
112
+ case icon_name.to_s
113
+ when "minus", "remove", "delete"
114
+ "delete-bin-2"
115
+ when "plus"
116
+ "add"
117
+ when "copy"
118
+ "file-copy"
119
+ when "download"
120
+ "download-2"
121
+ when "upload"
122
+ "upload-2"
123
+ when "exclamation"
124
+ "alert"
125
+ when "info-circle", "info"
126
+ "information"
127
+ when "times"
128
+ "close"
129
+ when "tag"
130
+ "price-tag-3"
131
+ when "cog"
132
+ "settings-3"
133
+ else
134
+ icon_name
135
+ end
136
+ end
103
137
  end
104
138
  end
@@ -0,0 +1,129 @@
1
+ import { toCamelCase } from "alchemy_admin/utils/string_conversions"
2
+
3
+ export class AlchemyHTMLElement extends HTMLElement {
4
+ static properties = {}
5
+
6
+ /**
7
+ * create the list of observed attributes
8
+ * this function is a requirement for the `attributeChangedCallback` - method
9
+ * @returns {string[]}
10
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference
11
+ */
12
+ static get observedAttributes() {
13
+ return Object.keys(this.properties)
14
+ }
15
+
16
+ constructor(options = {}) {
17
+ super()
18
+
19
+ this.options = options
20
+ this.changeComponent = true
21
+ this.initialContent = this.innerHTML // store the inner content of the component
22
+ }
23
+
24
+ /**
25
+ * run when the component will be initialized by the Browser
26
+ * this is a default function
27
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference
28
+ */
29
+ connectedCallback() {
30
+ // parse the properties object and register property with the default values
31
+ Object.keys(this.constructor.properties).forEach((name) => {
32
+ // if the options was given via the constructor, they should be prefer (e.g. new <WebComponentName>({title: "Foo"}))
33
+ this[name] =
34
+ this.options[name] ?? this.constructor.properties[name].default
35
+ })
36
+
37
+ // then process the attributes
38
+ this.getAttributeNames().forEach((name) => this._updateFromAttribute(name))
39
+
40
+ // render the component
41
+ this._updateComponent()
42
+ this.connected()
43
+ }
44
+
45
+ /**
46
+ * disconnected callback if the component is removed from the DOM
47
+ * this is currently only a Proxy to the disconnected - callback to use the same callback structure
48
+ * as for the connected - callback
49
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference
50
+ */
51
+ disconnectedCallback() {
52
+ this.disconnected()
53
+ }
54
+
55
+ /**
56
+ * triggered by the browser, if one of the observed attributes is changing
57
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Components#reference
58
+ */
59
+ attributeChangedCallback(name) {
60
+ this._updateFromAttribute(name)
61
+ this._updateComponent()
62
+ }
63
+
64
+ /**
65
+ * a connected method to make it easier to overwrite the connection callback
66
+ */
67
+ connected() {}
68
+
69
+ /**
70
+ * a disconnected method to make it easier to overwrite the disconnection callback
71
+ */
72
+ disconnected() {}
73
+
74
+ /**
75
+ * empty method container to allow the child component to put the rendered string into this method
76
+ * @returns {String}
77
+ */
78
+ render() {
79
+ return this.initialContent
80
+ }
81
+
82
+ /**
83
+ * after render callback
84
+ * the function will be triggered after the DOM was updated
85
+ */
86
+ afterRender() {}
87
+
88
+ /**
89
+ * Dispatches a custom event with given name
90
+ * @param {string} name The name of the custom event
91
+ * @param {object} detail Optional event details
92
+ */
93
+ dispatchCustomEvent(name, detail = {}) {
94
+ const event = new CustomEvent(`Alchemy.${name}`, { bubbles: true, detail })
95
+ this.dispatchEvent(event)
96
+ }
97
+
98
+ /**
99
+ * (re)render the component content inside the component container
100
+ * @private
101
+ */
102
+ _updateComponent() {
103
+ if (this.changeComponent) {
104
+ this.innerHTML = this.render()
105
+ this.changeComponent = false
106
+ this.afterRender()
107
+ }
108
+ }
109
+
110
+ /**
111
+ * update the value from the given attribute
112
+ *
113
+ * @param {string} name
114
+ * @private
115
+ */
116
+ _updateFromAttribute(name) {
117
+ const attributeValue = this.getAttribute(name)
118
+ const propertyName = toCamelCase(name)
119
+ const isBooleanValue =
120
+ attributeValue.length === 0 || attributeValue === "true"
121
+
122
+ const value = isBooleanValue ? true : attributeValue
123
+
124
+ if (this[propertyName] !== value) {
125
+ this[propertyName] = value
126
+ this.changeComponent = true
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,59 @@
1
+ import Spinner from "../spinner"
2
+
3
+ class Button extends HTMLButtonElement {
4
+ connectedCallback() {
5
+ if (this.form) {
6
+ this.form.addEventListener("submit", this)
7
+
8
+ if (this.form.dataset.remote == "true") {
9
+ this.form.addEventListener("ajax:complete", this)
10
+ }
11
+ } else {
12
+ console.warn("No form for button found!", this)
13
+ }
14
+ }
15
+
16
+ handleEvent(event) {
17
+ switch (event.type) {
18
+ case "submit":
19
+ const isDisabled = this.getAttribute("disabled") === "disabled"
20
+
21
+ if (isDisabled) {
22
+ event.preventDefault()
23
+ event.stopPropagation()
24
+ } else {
25
+ this.disable()
26
+ }
27
+ break
28
+ case "ajax:complete":
29
+ this.enable()
30
+ break
31
+ }
32
+ }
33
+
34
+ disable() {
35
+ const spinner = new Spinner("small")
36
+ const rect = this.getBoundingClientRect()
37
+
38
+ this.dataset.initialButtonText = this.innerHTML
39
+ this.setAttribute("disabled", "disabled")
40
+ this.setAttribute("tabindex", "-1")
41
+ this.classList.add("disabled")
42
+ this.style.width = `${rect.width}px`
43
+ this.style.height = `${rect.height}px`
44
+ this.innerHTML = "&nbsp;"
45
+
46
+ spinner.spin(this)
47
+ }
48
+
49
+ enable() {
50
+ this.classList.remove("disabled")
51
+ this.removeAttribute("disabled")
52
+ this.removeAttribute("tabindex")
53
+ this.style.width = null
54
+ this.style.height = null
55
+ this.innerHTML = this.dataset.initialButtonText
56
+ }
57
+ }
58
+
59
+ customElements.define("alchemy-button", Button, { extends: "button" })