alchemy_cms 7.0.16 → 7.1.0.pre.b1

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 (359) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/backport.yml +36 -0
  3. data/.github/workflows/brakeman-analysis.yml +5 -13
  4. data/.github/workflows/lint.yml +2 -9
  5. data/.github/workflows/stale.yml +2 -5
  6. data/.github/workflows/test.yml +7 -15
  7. data/.gitignore +0 -1
  8. data/.standard.yml +1 -1
  9. data/CHANGELOG.md +144 -51
  10. data/Gemfile +7 -18
  11. data/README.md +10 -8
  12. data/alchemy_cms.gemspec +4 -3
  13. data/app/assets/config/alchemy_manifest.js +0 -1
  14. data/app/assets/javascripts/alchemy/admin.js +1 -19
  15. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +2 -3
  16. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +19 -34
  17. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +38 -13
  18. data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +1 -1
  19. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +32 -25
  20. data/app/assets/javascripts/alchemy/alchemy.growler.js.coffee +1 -1
  21. data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +3 -5
  22. data/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee +0 -57
  23. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +22 -63
  24. data/app/assets/javascripts/alchemy/alchemy.list_filter.js.coffee +2 -2
  25. data/app/assets/javascripts/alchemy/alchemy.preview.js.coffee +5 -4
  26. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +5 -5
  27. data/app/assets/javascripts/alchemy/templates/index.js +0 -2
  28. data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
  29. data/app/assets/javascripts/alchemy/templates/page.hbs +1 -1
  30. data/app/assets/javascripts/alchemy/templates/page_folder.hbs +2 -2
  31. data/app/assets/stylesheets/alchemy/_custom-properties.scss +82 -0
  32. data/app/assets/stylesheets/alchemy/_mixins.scss +38 -30
  33. data/app/assets/stylesheets/alchemy/_variables.scss +12 -5
  34. data/app/assets/stylesheets/alchemy/admin.scss +3 -4
  35. data/app/assets/stylesheets/alchemy/archive.scss +107 -50
  36. data/app/assets/stylesheets/alchemy/attachments.scss +5 -4
  37. data/app/assets/stylesheets/alchemy/buttons.scss +38 -164
  38. data/app/assets/stylesheets/alchemy/dashboard.scss +31 -6
  39. data/app/assets/stylesheets/alchemy/dialogs.scss +12 -28
  40. data/app/assets/stylesheets/alchemy/elements.scss +273 -282
  41. data/app/assets/stylesheets/alchemy/flash.scss +20 -12
  42. data/app/assets/stylesheets/alchemy/forms.scss +21 -34
  43. data/app/assets/stylesheets/alchemy/frame.scss +11 -32
  44. data/app/assets/stylesheets/alchemy/hints.scss +4 -62
  45. data/app/assets/stylesheets/alchemy/image_library.scss +36 -33
  46. data/app/assets/stylesheets/alchemy/labels.scss +4 -1
  47. data/app/assets/stylesheets/alchemy/menubar.scss +7 -6
  48. data/app/assets/stylesheets/alchemy/navigation.scss +27 -15
  49. data/app/assets/stylesheets/alchemy/nodes.scss +11 -7
  50. data/app/assets/stylesheets/alchemy/notices.scss +16 -4
  51. data/app/assets/stylesheets/alchemy/page-select.scss +10 -2
  52. data/app/assets/stylesheets/alchemy/pagination.scss +22 -13
  53. data/app/assets/stylesheets/alchemy/preview_window.scss +4 -8
  54. data/app/assets/stylesheets/alchemy/resource_info.scss +7 -5
  55. data/app/assets/stylesheets/alchemy/selects.scss +49 -42
  56. data/app/assets/stylesheets/alchemy/shoelace.scss +345 -0
  57. data/app/assets/stylesheets/alchemy/sitemap.scss +24 -14
  58. data/app/assets/stylesheets/alchemy/spinner.scss +9 -19
  59. data/app/assets/stylesheets/alchemy/tables.scss +16 -24
  60. data/app/assets/stylesheets/alchemy/tags.scss +4 -0
  61. data/app/assets/stylesheets/alchemy/toolbar.scss +29 -25
  62. data/app/assets/stylesheets/alchemy/upload.scss +140 -89
  63. data/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +80 -108
  64. data/app/components/alchemy/admin/node_select.rb +39 -0
  65. data/app/components/alchemy/admin/page_select.rb +42 -0
  66. data/app/components/alchemy/ingredients/audio_view.rb +1 -1
  67. data/app/components/alchemy/ingredients/base_view.rb +1 -1
  68. data/app/components/alchemy/ingredients/boolean_view.rb +1 -1
  69. data/app/components/alchemy/ingredients/datetime_view.rb +3 -4
  70. data/app/components/alchemy/ingredients/file_view.rb +1 -1
  71. data/app/components/alchemy/ingredients/headline_view.rb +7 -16
  72. data/app/components/alchemy/ingredients/link_view.rb +1 -1
  73. data/app/components/alchemy/ingredients/page_view.rb +1 -1
  74. data/app/components/alchemy/ingredients/picture_view.rb +1 -1
  75. data/app/components/alchemy/ingredients/richtext_view.rb +1 -1
  76. data/app/components/alchemy/ingredients/text_view.rb +1 -1
  77. data/app/components/alchemy/ingredients/video_view.rb +1 -1
  78. data/app/controllers/alchemy/admin/base_controller.rb +7 -32
  79. data/app/controllers/alchemy/admin/elements_controller.rb +63 -35
  80. data/app/controllers/alchemy/admin/languages_controller.rb +2 -3
  81. data/app/controllers/alchemy/admin/layoutpages_controller.rb +0 -19
  82. data/app/controllers/alchemy/admin/pages_controller.rb +4 -5
  83. data/app/controllers/alchemy/admin/resources_controller.rb +1 -1
  84. data/app/controllers/alchemy/base_controller.rb +4 -2
  85. data/app/controllers/alchemy/messages_controller.rb +1 -1
  86. data/app/controllers/concerns/alchemy/admin/current_language.rb +1 -5
  87. data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +1 -1
  88. data/app/decorators/alchemy/element_editor.rb +0 -2
  89. data/app/helpers/alchemy/admin/attachments_helper.rb +6 -5
  90. data/app/helpers/alchemy/admin/base_helper.rb +17 -12
  91. data/app/helpers/alchemy/admin/ingredients_helper.rb +4 -1
  92. data/app/helpers/alchemy/admin/pages_helper.rb +5 -11
  93. data/app/helpers/alchemy/base_helper.rb +47 -13
  94. data/app/javascript/alchemy_admin/components/alchemy_html_element.js +129 -0
  95. data/app/javascript/alchemy_admin/components/button.js +59 -0
  96. data/app/javascript/alchemy_admin/components/char_counter.js +40 -0
  97. data/app/javascript/alchemy_admin/components/datepicker.js +39 -0
  98. data/app/javascript/alchemy_admin/components/dialog_link.js +45 -0
  99. data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +36 -0
  100. data/app/javascript/alchemy_admin/components/element_editor.js +553 -0
  101. data/app/javascript/alchemy_admin/components/ingredient_group.js +54 -0
  102. data/app/javascript/alchemy_admin/components/link_buttons/link_button.js +48 -0
  103. data/app/javascript/alchemy_admin/components/link_buttons/unlink_button.js +38 -0
  104. data/app/javascript/alchemy_admin/components/link_buttons.js +79 -0
  105. data/app/javascript/alchemy_admin/components/node_select.js +45 -0
  106. data/app/javascript/alchemy_admin/components/overlay.js +18 -0
  107. data/app/javascript/alchemy_admin/components/page_select.js +63 -0
  108. data/app/javascript/alchemy_admin/components/remote_select.js +134 -0
  109. data/app/javascript/alchemy_admin/components/select.js +12 -0
  110. data/app/javascript/alchemy_admin/components/spinner.js +31 -0
  111. data/app/javascript/alchemy_admin/components/tinymce.js +146 -0
  112. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +266 -0
  113. data/app/javascript/alchemy_admin/components/uploader/progress.js +258 -0
  114. data/app/javascript/alchemy_admin/components/uploader.js +132 -0
  115. data/app/javascript/alchemy_admin/dirty.js +49 -0
  116. data/app/javascript/alchemy_admin/file_editors.js +1 -1
  117. data/app/javascript/alchemy_admin/gui.js +14 -0
  118. data/app/javascript/alchemy_admin/i18n.js +12 -8
  119. data/app/javascript/alchemy_admin/image_cropper.js +6 -3
  120. data/app/javascript/alchemy_admin/image_loader.js +7 -15
  121. data/app/javascript/alchemy_admin/ingredient_anchor_link.js +2 -5
  122. data/app/javascript/alchemy_admin/initializer.js +65 -0
  123. data/app/javascript/alchemy_admin/locales/en.js +31 -0
  124. data/app/javascript/alchemy_admin/picture_editors.js +2 -2
  125. data/app/javascript/alchemy_admin/picture_selector.js +38 -0
  126. data/app/javascript/alchemy_admin/please_wait_overlay.js +8 -0
  127. data/app/javascript/alchemy_admin/sortable_elements.js +78 -0
  128. data/app/javascript/alchemy_admin/spinner.js +36 -0
  129. data/app/javascript/alchemy_admin/tags_autocomplete.js +46 -0
  130. data/app/javascript/alchemy_admin/utils/ajax.js +6 -5
  131. data/app/javascript/alchemy_admin/utils/dom_helpers.js +20 -0
  132. data/app/javascript/alchemy_admin/utils/format.js +11 -0
  133. data/app/javascript/alchemy_admin/utils/string_conversions.js +10 -0
  134. data/app/javascript/alchemy_admin.js +70 -13
  135. data/app/javascript/menubar.js +10 -0
  136. data/app/models/alchemy/attachment.rb +9 -11
  137. data/app/models/alchemy/element.rb +11 -0
  138. data/app/models/alchemy/ingredients/audio.rb +0 -11
  139. data/app/models/alchemy/ingredients/datetime.rb +1 -1
  140. data/app/models/alchemy/ingredients/richtext.rb +1 -10
  141. data/app/models/alchemy/ingredients/video.rb +0 -12
  142. data/app/models/alchemy/node.rb +4 -0
  143. data/app/models/alchemy/page/page_elements.rb +2 -11
  144. data/app/models/alchemy/page/page_natures.rb +10 -2
  145. data/app/models/alchemy/page.rb +12 -54
  146. data/app/models/alchemy/picture/url.rb +1 -9
  147. data/app/models/concerns/alchemy/picture_thumbnails.rb +5 -4
  148. data/app/serializers/alchemy/page_tree_serializer.rb +2 -1
  149. data/app/services/alchemy/copy_page.rb +98 -0
  150. data/app/views/alchemy/_menubar.html.erb +17 -13
  151. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +14 -10
  152. data/app/views/alchemy/admin/attachments/_attachment.html.erb +44 -36
  153. data/app/views/alchemy/admin/attachments/_replace_button.html.erb +15 -21
  154. data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -1
  155. data/app/views/alchemy/admin/attachments/assign.js.erb +1 -1
  156. data/app/views/alchemy/admin/attachments/index.html.erb +6 -4
  157. data/app/views/alchemy/admin/attachments/show.html.erb +8 -8
  158. data/app/views/alchemy/admin/clipboard/clear.js.erb +1 -1
  159. data/app/views/alchemy/admin/clipboard/index.html.erb +3 -7
  160. data/app/views/alchemy/admin/clipboard/insert.js.erb +1 -1
  161. data/app/views/alchemy/admin/crop.html.erb +1 -1
  162. data/app/views/alchemy/admin/dashboard/_locked_pages.html.erb +1 -1
  163. data/app/views/alchemy/admin/dashboard/index.html.erb +13 -11
  164. data/app/views/alchemy/admin/dashboard/info.html.erb +7 -7
  165. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +21 -23
  166. data/app/views/alchemy/admin/elements/_element.html.erb +52 -44
  167. data/app/views/alchemy/admin/elements/_footer.html.erb +1 -1
  168. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  169. data/app/views/alchemy/admin/elements/_header.html.erb +11 -12
  170. data/app/views/alchemy/admin/elements/_toolbar.html.erb +33 -45
  171. data/app/views/alchemy/admin/elements/create.js.erb +7 -15
  172. data/app/views/alchemy/admin/elements/destroy.js.erb +0 -2
  173. data/app/views/alchemy/admin/elements/index.html.erb +27 -24
  174. data/app/views/alchemy/admin/elements/new.html.erb +9 -11
  175. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +2 -2
  176. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +3 -3
  177. data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +1 -2
  178. data/app/views/alchemy/admin/languages/_form.html.erb +2 -3
  179. data/app/views/alchemy/admin/languages/_language.html.erb +15 -8
  180. data/app/views/alchemy/admin/languages/_table.html.erb +1 -0
  181. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +28 -16
  182. data/app/views/alchemy/admin/layoutpages/edit.html.erb +1 -1
  183. data/app/views/alchemy/admin/layoutpages/index.html.erb +2 -2
  184. data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +12 -8
  185. data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +1 -1
  186. data/app/views/alchemy/admin/nodes/_form.html.erb +20 -21
  187. data/app/views/alchemy/admin/nodes/_node.html.erb +39 -34
  188. data/app/views/alchemy/admin/nodes/index.html.erb +1 -1
  189. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +4 -4
  190. data/app/views/alchemy/admin/pages/_create_language_form.html.erb +2 -2
  191. data/app/views/alchemy/admin/pages/_current_page.html.erb +1 -1
  192. data/app/views/alchemy/admin/pages/_external_link.html.erb +4 -4
  193. data/app/views/alchemy/admin/pages/_file_link.html.erb +5 -5
  194. data/app/views/alchemy/admin/pages/_form.html.erb +10 -21
  195. data/app/views/alchemy/admin/pages/_internal_link.html.erb +4 -4
  196. data/app/views/alchemy/admin/pages/_locked_page.html.erb +2 -2
  197. data/app/views/alchemy/admin/pages/_new_page_form.html.erb +4 -17
  198. data/app/views/alchemy/admin/pages/_page.html.erb +76 -72
  199. data/app/views/alchemy/admin/pages/_page_infos.html.erb +23 -7
  200. data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +2 -1
  201. data/app/views/alchemy/admin/pages/_page_status.html.erb +11 -21
  202. data/app/views/alchemy/admin/pages/_publication_fields.html.erb +2 -5
  203. data/app/views/alchemy/admin/pages/_table.html.erb +1 -1
  204. data/app/views/alchemy/admin/pages/_table_row.html.erb +43 -39
  205. data/app/views/alchemy/admin/pages/_toolbar.html.erb +43 -38
  206. data/app/views/alchemy/admin/pages/configure.html.erb +12 -14
  207. data/app/views/alchemy/admin/pages/edit.html.erb +80 -103
  208. data/app/views/alchemy/admin/pages/info.html.erb +20 -11
  209. data/app/views/alchemy/admin/pages/link.html.erb +22 -16
  210. data/app/views/alchemy/admin/pages/new.html.erb +9 -11
  211. data/app/views/alchemy/admin/pages/unlock.js.erb +10 -3
  212. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +15 -13
  213. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +3 -5
  214. data/app/views/alchemy/admin/partials/_routes.html.erb +10 -2
  215. data/app/views/alchemy/admin/partials/_site_select.html.erb +6 -5
  216. data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +28 -23
  217. data/app/views/alchemy/admin/pictures/_archive.html.erb +5 -5
  218. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
  219. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +21 -23
  220. data/app/views/alchemy/admin/pictures/_infos.html.erb +2 -6
  221. data/app/views/alchemy/admin/pictures/_picture.html.erb +15 -17
  222. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +17 -16
  223. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -1
  224. data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +1 -1
  225. data/app/views/alchemy/admin/pictures/assign.js.erb +1 -1
  226. data/app/views/alchemy/admin/pictures/index.html.erb +34 -30
  227. data/app/views/alchemy/admin/pictures/show.html.erb +3 -3
  228. data/app/views/alchemy/admin/resources/_filter.html.erb +2 -2
  229. data/app/views/alchemy/admin/resources/_form.html.erb +2 -2
  230. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +1 -1
  231. data/app/views/alchemy/admin/resources/_resource.html.erb +16 -9
  232. data/app/views/alchemy/admin/resources/_table.html.erb +4 -1
  233. data/app/views/alchemy/admin/resources/index.html.erb +22 -19
  234. data/app/views/alchemy/admin/sites/index.html.erb +2 -1
  235. data/app/views/alchemy/admin/styleguide/index.html.erb +54 -28
  236. data/app/views/alchemy/admin/tags/_tag.html.erb +16 -18
  237. data/app/views/alchemy/admin/tags/index.html.erb +15 -12
  238. data/app/views/alchemy/admin/tinymce/_setup.html.erb +29 -0
  239. data/app/views/alchemy/admin/uploader/_button.html.erb +23 -29
  240. data/app/views/alchemy/admin/uploader/_setup.html.erb +3 -8
  241. data/app/views/alchemy/base/500.html.erb +1 -1
  242. data/app/views/alchemy/base/error_notice.js.erb +0 -1
  243. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +1 -1
  244. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +2 -3
  245. data/app/views/alchemy/ingredients/_file_editor.html.erb +5 -5
  246. data/app/views/alchemy/ingredients/_link_editor.html.erb +1 -1
  247. data/app/views/alchemy/ingredients/_node_editor.html.erb +6 -19
  248. data/app/views/alchemy/ingredients/_page_editor.html.erb +7 -19
  249. data/app/views/alchemy/ingredients/_picture_editor.html.erb +2 -2
  250. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +6 -15
  251. data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
  252. data/app/views/alchemy/ingredients/_text_editor.html.erb +1 -1
  253. data/app/views/alchemy/ingredients/shared/_anchor.html.erb +1 -1
  254. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +10 -20
  255. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +42 -49
  256. data/app/views/kaminari/alchemy/_first_page.html.erb +4 -2
  257. data/app/views/kaminari/alchemy/_gap.html.erb +1 -1
  258. data/app/views/kaminari/alchemy/_last_page.html.erb +4 -2
  259. data/app/views/kaminari/alchemy/_next_page.html.erb +4 -2
  260. data/app/views/kaminari/alchemy/_prev_page.html.erb +4 -2
  261. data/app/views/layouts/alchemy/admin.html.erb +10 -29
  262. data/config/alchemy/modules.yml +30 -30
  263. data/config/importmap.rb +10 -1
  264. data/config/initializers/rails_live_reload.rb +13 -0
  265. data/config/locales/alchemy.en.yml +23 -9
  266. data/config/routes.rb +3 -2
  267. data/lib/alchemy/auth_accessors.rb +6 -1
  268. data/lib/alchemy/controller_actions.rb +17 -4
  269. data/lib/alchemy/dev_support/live_reload_watcher.rb +5 -0
  270. data/lib/alchemy/engine.rb +8 -2
  271. data/lib/alchemy/forms/builder.rb +18 -12
  272. data/lib/alchemy/modules.rb +2 -2
  273. data/lib/alchemy/permissions.rb +1 -1
  274. data/lib/alchemy/resources_helper.rb +3 -3
  275. data/lib/alchemy/routing_constraints.rb +1 -1
  276. data/lib/alchemy/test_support/capybara_helpers.rb +8 -5
  277. data/lib/alchemy/test_support/rspec_matchers.rb +14 -0
  278. data/lib/alchemy/test_support/shared_uploader_examples.rb +1 -1
  279. data/lib/alchemy/tinymce.rb +8 -3
  280. data/lib/alchemy/version.rb +1 -1
  281. data/lib/tasks/alchemy/tidy.rake +1 -0
  282. data/package.json +14 -5
  283. data/vendor/assets/fonts/remixicon.eot +0 -0
  284. data/vendor/assets/fonts/remixicon.svg +7816 -0
  285. data/vendor/assets/fonts/remixicon.ttf +0 -0
  286. data/vendor/assets/fonts/remixicon.woff +0 -0
  287. data/vendor/assets/fonts/remixicon.woff2 +0 -0
  288. data/vendor/assets/stylesheets/remixicon.scss +10480 -0
  289. metadata +87 -97
  290. data/.gem_release.yml +0 -8
  291. data/app/assets/javascripts/alchemy/alchemy.autocomplete.js.coffee +0 -30
  292. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +0 -53
  293. data/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee +0 -45
  294. data/app/assets/javascripts/alchemy/alchemy.char_counter.js.coffee +0 -19
  295. data/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee +0 -59
  296. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +0 -79
  297. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +0 -267
  298. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +0 -27
  299. data/app/assets/javascripts/alchemy/alchemy.spinner.js +0 -32
  300. data/app/assets/javascripts/alchemy/alchemy.tooltips.coffee +0 -10
  301. data/app/assets/javascripts/alchemy/alchemy.uploader.js.coffee +0 -131
  302. data/app/assets/javascripts/alchemy/menubar.js.coffee +0 -8
  303. data/app/assets/javascripts/alchemy/node_select.js +0 -39
  304. data/app/assets/javascripts/alchemy/page_select.js +0 -46
  305. data/app/assets/javascripts/alchemy/templates/node.hbs +0 -16
  306. data/app/assets/javascripts/alchemy/templates/spinner.hbs +0 -7
  307. data/app/assets/stylesheets/alchemy/jquery-ui.scss +0 -435
  308. data/app/javascript/alchemy_admin/datepicker.js +0 -40
  309. data/app/javascript/alchemy_admin/tinymce.js +0 -146
  310. data/app/javascript/alchemy_admin/translations.js +0 -32
  311. data/app/views/alchemy/admin/elements/fold.js.erb +0 -33
  312. data/app/views/alchemy/admin/elements/order.js.erb +0 -11
  313. data/app/views/alchemy/admin/elements/publish.js.erb +0 -21
  314. data/app/views/alchemy/admin/elements/update.js.erb +0 -27
  315. data/vendor/assets/fonts/fa-regular-400.eot +0 -0
  316. data/vendor/assets/fonts/fa-regular-400.svg +0 -803
  317. data/vendor/assets/fonts/fa-regular-400.ttf +0 -0
  318. data/vendor/assets/fonts/fa-regular-400.woff +0 -0
  319. data/vendor/assets/fonts/fa-regular-400.woff2 +0 -0
  320. data/vendor/assets/fonts/fa-solid-900.eot +0 -0
  321. data/vendor/assets/fonts/fa-solid-900.svg +0 -4938
  322. data/vendor/assets/fonts/fa-solid-900.ttf +0 -0
  323. data/vendor/assets/fonts/fa-solid-900.woff +0 -0
  324. data/vendor/assets/fonts/fa-solid-900.woff2 +0 -0
  325. data/vendor/assets/javascripts/fileupload/jquery.fileupload-process.js +0 -178
  326. data/vendor/assets/javascripts/fileupload/jquery.fileupload-validate.js +0 -125
  327. data/vendor/assets/javascripts/fileupload/jquery.fileupload.js +0 -1502
  328. data/vendor/assets/javascripts/fileupload/jquery.iframe-transport.js +0 -224
  329. data/vendor/assets/javascripts/jquery-ui/data.js +0 -45
  330. data/vendor/assets/javascripts/jquery-ui/ie.js +0 -20
  331. data/vendor/assets/javascripts/jquery-ui/keycode.js +0 -51
  332. data/vendor/assets/javascripts/jquery-ui/plugin.js +0 -49
  333. data/vendor/assets/javascripts/jquery-ui/safe-active-element.js +0 -46
  334. data/vendor/assets/javascripts/jquery-ui/safe-blur.js +0 -27
  335. data/vendor/assets/javascripts/jquery-ui/scroll-parent.js +0 -50
  336. data/vendor/assets/javascripts/jquery-ui/unique-id.js +0 -54
  337. data/vendor/assets/javascripts/jquery-ui/version.js +0 -20
  338. data/vendor/assets/javascripts/jquery-ui/widget.js +0 -754
  339. data/vendor/assets/javascripts/jquery-ui/widgets/draggable.js +0 -1268
  340. data/vendor/assets/javascripts/jquery-ui/widgets/mouse.js +0 -241
  341. data/vendor/assets/javascripts/jquery-ui/widgets/sortable.js +0 -1623
  342. data/vendor/assets/javascripts/jquery-ui/widgets/tabs.js +0 -931
  343. data/vendor/assets/javascripts/jquery_plugins/jquery.scrollTo.min.js +0 -7
  344. data/vendor/assets/javascripts/jquery_plugins/jquery.ui.tabspaging.js +0 -296
  345. data/vendor/assets/stylesheets/fontawesome/_animated.scss +0 -20
  346. data/vendor/assets/stylesheets/fontawesome/_bordered-pulled.scss +0 -20
  347. data/vendor/assets/stylesheets/fontawesome/_core.scss +0 -21
  348. data/vendor/assets/stylesheets/fontawesome/_fixed-width.scss +0 -6
  349. data/vendor/assets/stylesheets/fontawesome/_icons.scss +0 -1441
  350. data/vendor/assets/stylesheets/fontawesome/_larger.scss +0 -23
  351. data/vendor/assets/stylesheets/fontawesome/_list.scss +0 -18
  352. data/vendor/assets/stylesheets/fontawesome/_mixins.scss +0 -56
  353. data/vendor/assets/stylesheets/fontawesome/_rotated-flipped.scss +0 -24
  354. data/vendor/assets/stylesheets/fontawesome/_screen-reader.scss +0 -5
  355. data/vendor/assets/stylesheets/fontawesome/_stacked.scss +0 -31
  356. data/vendor/assets/stylesheets/fontawesome/_variables.scss +0 -1458
  357. data/vendor/assets/stylesheets/fontawesome/fontawesome.scss +0 -16
  358. data/vendor/assets/stylesheets/fontawesome/regular.scss +0 -23
  359. data/vendor/assets/stylesheets/fontawesome/solid.scss +0 -24
@@ -0,0 +1,553 @@
1
+ import TagsAutocomplete from "alchemy_admin/tags_autocomplete"
2
+ import ImageLoader from "alchemy_admin/image_loader"
3
+ import fileEditors from "alchemy_admin/file_editors"
4
+ import pictureEditors from "alchemy_admin/picture_editors"
5
+ import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
6
+ import { post } from "alchemy_admin/utils/ajax"
7
+ import { createHtmlElement } from "../utils/dom_helpers"
8
+
9
+ import "./element_editor/publish_element_button"
10
+
11
+ export class ElementEditor extends HTMLElement {
12
+ constructor() {
13
+ super()
14
+
15
+ // Add event listeners
16
+ this.addEventListener("click", this)
17
+ // Triggered by child elements
18
+ this.addEventListener("alchemy:element-update-title", this)
19
+ // We use of @rails/ujs for Rails remote forms
20
+ this.addEventListener("ajax:success", this)
21
+ // Dirty observer
22
+ this.addEventListener("change", this)
23
+
24
+ this.header?.addEventListener("dblclick", () => {
25
+ this.toggle()
26
+ })
27
+ this.toggleButton?.addEventListener("click", (evt) => {
28
+ const elementEditor = evt.target.closest("alchemy-element-editor")
29
+ if (elementEditor === this) {
30
+ this.toggle()
31
+ }
32
+ })
33
+ }
34
+
35
+ connectedCallback() {
36
+ // The placeholder while be being dragged is empty.
37
+ if (this.classList.contains("ui-sortable-placeholder")) {
38
+ return
39
+ }
40
+
41
+ // Init GUI elements
42
+ ImageLoader.init(this)
43
+ fileEditors(
44
+ `#${this.id} .ingredient-editor.file, #${this.id} .ingredient-editor.audio, #${this.id} .ingredient-editor.video`
45
+ )
46
+ pictureEditors(`#${this.id} .ingredient-editor.picture`)
47
+ TagsAutocomplete(this)
48
+ }
49
+
50
+ handleEvent(event) {
51
+ switch (event.type) {
52
+ case "click":
53
+ const elementEditor = event.target.closest("alchemy-element-editor")
54
+ if (elementEditor === this) {
55
+ this.onClickElement()
56
+ }
57
+ break
58
+ case "ajax:success":
59
+ if (event.target === this.body) {
60
+ const responseJSON = event.detail[0]
61
+ event.stopPropagation()
62
+ this.onSaveElement(responseJSON)
63
+ }
64
+ break
65
+ case "alchemy:element-update-title":
66
+ if (!this.hasEditors && event.target == this.firstChild) {
67
+ this.setTitle(event.detail.title)
68
+ }
69
+ break
70
+ case "change":
71
+ // SortableJS fires a native change event :/
72
+ // and we do not want to set the element editor dirty
73
+ // when this happens
74
+ if (event.target.classList.contains("nested-elements")) {
75
+ return
76
+ }
77
+ event.stopPropagation()
78
+ event.target.classList.add("dirty")
79
+ this.setDirty()
80
+ break
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Scrolls to and highlights element
86
+ * Expands if collapsed
87
+ * Also chooses the right fixed elements tab, if necessary.
88
+ * Can be triggered through custom event 'FocusElementEditor.Alchemy'
89
+ * Used by the elements on click events in the preview frame.
90
+ */
91
+ async focusElement() {
92
+ // Select tab if necessary
93
+ if (document.querySelector("#fixed-elements")) {
94
+ await this.selectTabForElement()
95
+ }
96
+ // Expand if necessary
97
+ await this.expand()
98
+ this.selectElement(true)
99
+ }
100
+
101
+ focusElementPreview() {
102
+ Alchemy.PreviewWindow.postMessage({
103
+ message: "Alchemy.focusElement",
104
+ element_id: this.elementId
105
+ })
106
+ }
107
+
108
+ onClickElement() {
109
+ this.selectElement()
110
+ this.focusElementPreview()
111
+ }
112
+
113
+ /**
114
+ * Sets the element to saved state
115
+ * Updates title
116
+ * Shows error messages if ingredient validations fail
117
+ * @argument {JSON} data
118
+ */
119
+ onSaveElement(data) {
120
+ // JS event bubbling will also update the parents element quote.
121
+ this.setClean()
122
+ // Reset errors that might be visible from last save attempt
123
+ this.errorsDisplay.innerHTML = ""
124
+ this.body
125
+ .querySelectorAll(".ingredient-editor")
126
+ .forEach((el) => el.classList.remove("validation_failed"))
127
+ // If validation failed
128
+ if (data.errors) {
129
+ const warning = data.warning
130
+ // Create error messages
131
+ data.errors.forEach((message) => {
132
+ this.errorsDisplay.append(createHtmlElement(`<li>${message}</li>`))
133
+ })
134
+ // Mark ingredients as failed
135
+ data.ingredientsWithErrors.forEach((id) => {
136
+ this.querySelector(`[data-ingredient-id="${id}"]`)?.classList.add(
137
+ "validation_failed"
138
+ )
139
+ })
140
+ // Show message
141
+ Alchemy.growl(warning, "warn")
142
+ this.elementErrors.classList.remove("hidden")
143
+ } else {
144
+ Alchemy.growl(data.notice)
145
+ Alchemy.PreviewWindow.refresh(() => this.focusElementPreview())
146
+ this.updateTitle(data.previewText)
147
+ data.ingredientAnchors.forEach((anchor) => {
148
+ IngredientAnchorLink.updateIcon(anchor.ingredientId, anchor.active)
149
+ })
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Smoothly scrolls to element
155
+ */
156
+ scrollToElement() {
157
+ // The timeout gives the browser some time to calculate the position
158
+ // of nested elements correctly
159
+ setTimeout(() => {
160
+ this.scrollIntoView({
161
+ behavior: "smooth"
162
+ })
163
+ }, 50)
164
+ }
165
+
166
+ /**
167
+ * Highlight element and optionally scroll into view
168
+ * @param {boolean} scroll smoothly scroll element into view. Default (false)
169
+ */
170
+ selectElement(scroll = false) {
171
+ document
172
+ .querySelectorAll("alchemy-element-editor.selected")
173
+ .forEach((el) => {
174
+ el.classList.remove("selected")
175
+ })
176
+ window.requestAnimationFrame(() => {
177
+ this.classList.add("selected")
178
+ })
179
+ if (scroll) this.scrollToElement()
180
+ }
181
+
182
+ /**
183
+ * Selects tab for given element
184
+ * Resolves the promise if this is done.
185
+ * @returns {Promise}
186
+ */
187
+ selectTabForElement() {
188
+ return new Promise((resolve, reject) => {
189
+ const tabs = document.querySelector("#fixed-elements")
190
+ const panel = this.closest("sl-tab-panel")
191
+ if (tabs && panel) {
192
+ tabs.show(panel.getAttribute("name"))
193
+ resolve()
194
+ } else {
195
+ reject(new Error("No tabs present"))
196
+ }
197
+ })
198
+ }
199
+
200
+ /**
201
+ * Sets the element into clean (safed) state
202
+ */
203
+ setClean() {
204
+ this.dirty = false
205
+ window.onbeforeunload = null
206
+ if (this.hasEditors) {
207
+ this.body.querySelectorAll(".dirty").forEach((el) => {
208
+ el.classList.remove("dirty")
209
+ })
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Sets the element into dirty (unsafed) state
215
+ */
216
+ setDirty() {
217
+ if (this.hasEditors) {
218
+ this.dirty = true
219
+ window.onbeforeunload = () => Alchemy.t("page_dirty_notice")
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Sets the title quote
225
+ * @param {string} title
226
+ */
227
+ setTitle(title) {
228
+ const quote = this.querySelector(".element-header .preview_text_quote")
229
+ quote.textContent = title
230
+ }
231
+
232
+ /**
233
+ * Expands or collapses element editor
234
+ * If the element is dirty (has unsaved changes) it displays a confirm first.
235
+ */
236
+ async toggle() {
237
+ if (this.collapsed) {
238
+ await this.expand()
239
+ } else {
240
+ await this.collapse()
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Collapses the element editor and persists the state on the server
246
+ * @returns {Promise}
247
+ */
248
+ collapse() {
249
+ if (this.collapsed || this.compact || this.fixed) {
250
+ return Promise.resolve("Element is already collapsed.")
251
+ }
252
+
253
+ const spinner = new Alchemy.Spinner("small")
254
+ spinner.spin(this.toggleButton)
255
+ this.toggleIcon?.classList?.add("hidden")
256
+ return post(Alchemy.routes.collapse_admin_element_path(this.elementId))
257
+ .then((response) => {
258
+ const data = response.data
259
+
260
+ this.collapsed = true
261
+ this.toggleButton?.setAttribute("title", data.title)
262
+
263
+ // Collapse all nested elements if necessarry
264
+ if (data.nestedElementIds.length) {
265
+ const selector = data.nestedElementIds
266
+ .map((id) => `#element_${id}`)
267
+ .join(", ")
268
+ this.querySelectorAll(selector).forEach((nestedElement) => {
269
+ nestedElement.collapsed = true
270
+ nestedElement.toggleButton?.setAttribute("title", data.title)
271
+ })
272
+ }
273
+ })
274
+ .catch((error) => {
275
+ Alchemy.growl(error.message, "error")
276
+ console.error(error)
277
+ })
278
+ .finally(() => {
279
+ this.toggleIcon?.classList?.remove("hidden")
280
+ spinner.stop()
281
+ })
282
+ }
283
+
284
+ /**
285
+ * Collapses the element editor and persists the state on the server
286
+ * @* @returns {Promise}
287
+ */
288
+ expand() {
289
+ if (this.expanded && !this.compact) {
290
+ return Promise.resolve("Element is already expanded.")
291
+ }
292
+
293
+ if (this.compact && this.parentElementEditor) {
294
+ return this.parentElementEditor.expand()
295
+ } else {
296
+ const spinner = new Alchemy.Spinner("small")
297
+ spinner.spin(this.toggleButton)
298
+ this.toggleIcon?.classList.add("hidden")
299
+
300
+ return new Promise((resolve, reject) => {
301
+ post(Alchemy.routes.expand_admin_element_path(this.elementId))
302
+ .then((response) => {
303
+ const data = response.data
304
+
305
+ // First expand all parent elements if necessary
306
+ if (data.parentElementIds.length) {
307
+ const selector = data.parentElementIds
308
+ .map((id) => `#element_${id}`)
309
+ .join(", ")
310
+ document.querySelectorAll(selector).forEach((parentElement) => {
311
+ parentElement.collapsed = false
312
+ parentElement.toggleButton?.setAttribute("title", data.title)
313
+ })
314
+ }
315
+ // Finally expand ourselve
316
+ this.collapsed = false
317
+ this.toggleButton?.setAttribute("title", data.title)
318
+ // Resolve the promise that scrolls to the element very last
319
+ resolve()
320
+ })
321
+ .catch((error) => {
322
+ Alchemy.growl(error.message, "error")
323
+ console.error(error)
324
+ reject(error)
325
+ })
326
+ .finally(() => {
327
+ this.toggleIcon?.classList?.remove("hidden")
328
+ spinner.stop()
329
+ })
330
+ })
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Updates the quote in the element header and dispatches event
336
+ * to parent elements
337
+ * @param {string} title
338
+ */
339
+ updateTitle(title) {
340
+ this.setTitle(title)
341
+ this.dispatchEvent(
342
+ new CustomEvent("alchemy:element-update-title", {
343
+ bubbles: true,
344
+ detail: { title }
345
+ })
346
+ )
347
+ }
348
+
349
+ /**
350
+ * Sets element published or hidden
351
+ * @param {boolean}
352
+ */
353
+ set published(isPublished) {
354
+ if (isPublished) {
355
+ this.classList.remove("hidden")
356
+ } else {
357
+ this.classList.add("hidden")
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Is element published or hidden
363
+ * @returns {boolean}
364
+ */
365
+ get published() {
366
+ return !this.classList.contains("hidden")
367
+ }
368
+
369
+ /**
370
+ * @returns {boolean}
371
+ */
372
+ get compact() {
373
+ return this.getAttribute("compact") !== null
374
+ }
375
+
376
+ /**
377
+ * @returns {boolean}
378
+ */
379
+ get fixed() {
380
+ return this.getAttribute("fixed") !== null
381
+ }
382
+
383
+ /**
384
+ * @param {boolean} value
385
+ */
386
+ set collapsed(value) {
387
+ this.classList.toggle("folded", value)
388
+ this.classList.toggle("expanded", !value)
389
+ this.toggleIcon?.classList?.toggle("ri-arrow-down-s-line", !value)
390
+ this.toggleIcon?.classList?.toggle("ri-arrow-left-s-line", value)
391
+ }
392
+
393
+ /**
394
+ * @returns {boolean}
395
+ */
396
+ get collapsed() {
397
+ return this.classList.contains("folded")
398
+ }
399
+
400
+ /**
401
+ * @returns {boolean}
402
+ */
403
+ get expanded() {
404
+ return !this.collapsed
405
+ }
406
+
407
+ /**
408
+ * Toggles the dirty class
409
+ *
410
+ * @param {boolean} value
411
+ */
412
+ set dirty(value) {
413
+ this.classList.toggle("dirty", value)
414
+ }
415
+
416
+ /**
417
+ * Returns the dirty state of this element
418
+ *
419
+ * @returns {boolean}
420
+ */
421
+ get dirty() {
422
+ return this.classList.contains("dirty")
423
+ }
424
+
425
+ /**
426
+ * Returns the element header
427
+ *
428
+ * @returns {HTMLElement|undefined}
429
+ */
430
+ get header() {
431
+ return this.querySelector(`.element-header`)
432
+ }
433
+
434
+ /**
435
+ * Returns the immediate body container of this element if present
436
+ *
437
+ * Makes sure it does not return a nested elements body
438
+ * by scoping the selector to this elements id.
439
+ *
440
+ * @returns {HTMLElement|undefined}
441
+ */
442
+ get body() {
443
+ return this.querySelector(this.bodySelector)
444
+ }
445
+
446
+ get bodySelector() {
447
+ return `#${this.id} > .element-body`
448
+ }
449
+
450
+ /**
451
+ * Returns the immediate footer container of this element if present
452
+ *
453
+ * Makes sure it does not return a nested elements footer
454
+ * by scoping the selector to this elements id.
455
+ *
456
+ * @returns {HTMLElement|undefined}
457
+ */
458
+ get footer() {
459
+ return this.querySelector(`#${this.id} > .element-footer`)
460
+ }
461
+
462
+ /**
463
+ * The collapse/expand toggle button
464
+ *
465
+ * @returns {HTMLButtonElement|undefined}
466
+ */
467
+ get toggleButton() {
468
+ return this.querySelector(".element-toggle")
469
+ }
470
+
471
+ /**
472
+ * The collapse/expand toggle buttons icon
473
+ *
474
+ * @returns {HTMLElement|undefined}
475
+ */
476
+ get toggleIcon() {
477
+ return this.toggleButton?.querySelector(".icon")
478
+ }
479
+
480
+ /**
481
+ * The error messages container
482
+ *
483
+ * @returns {HTMLElement}
484
+ */
485
+ get errorsDisplay() {
486
+ return this.body.querySelector(".error-messages")
487
+ }
488
+
489
+ /**
490
+ * The validation messages list container
491
+ *
492
+ * @returns {HTMLElement}
493
+ */
494
+ get elementErrors() {
495
+ return this.body.querySelector(".element_errors")
496
+ }
497
+
498
+ /**
499
+ * The element database id
500
+ *
501
+ * @returns {string}
502
+ */
503
+ get elementId() {
504
+ return this.dataset.elementId
505
+ }
506
+
507
+ /**
508
+ * The element defintion name
509
+ *
510
+ * @returns {string}
511
+ */
512
+ get elementName() {
513
+ return this.dataset.elementName
514
+ }
515
+
516
+ /**
517
+ * Does this element have ingredient editor fields?
518
+ *
519
+ * @returns {boolean}
520
+ */
521
+ get hasEditors() {
522
+ return !!this.body?.querySelector(".element-ingredient-editors")
523
+ }
524
+
525
+ /**
526
+ * Does this element have nested elements?
527
+ *
528
+ * @returns {boolean}
529
+ */
530
+ get hasChildren() {
531
+ return !!this.querySelector(".nested-elements")
532
+ }
533
+
534
+ /**
535
+ * The first child element editor if present
536
+ *
537
+ * @returns {HTMLButtonElement|undefined}
538
+ */
539
+ get firstChild() {
540
+ return this.querySelector("alchemy-element-editor")
541
+ }
542
+
543
+ /**
544
+ * The parent element editor if present
545
+ *
546
+ * @returns {ElementEditor|undefined}
547
+ */
548
+ get parentElementEditor() {
549
+ return this.parentElement?.closest("alchemy-element-editor")
550
+ }
551
+ }
552
+
553
+ customElements.define("alchemy-element-editor", ElementEditor)
@@ -0,0 +1,54 @@
1
+ export class IngredientGroup extends HTMLDetailsElement {
2
+ #localStorageKey = "Alchemy.expanded_ingredient_groups"
3
+
4
+ constructor() {
5
+ super()
6
+
7
+ this.addEventListener("toggle", this)
8
+
9
+ if (this.isInLocalStorage) {
10
+ this.open = true
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Toggle visibility of the ingredient fields in this group
16
+ */
17
+ handleEvent() {
18
+ let expanded_ingredient_groups = this.localStorageItem
19
+
20
+ if (this.open) {
21
+ if (!this.isInLocalStorage) expanded_ingredient_groups.push(this.id)
22
+ } else {
23
+ expanded_ingredient_groups = expanded_ingredient_groups.filter(
24
+ (value) => value !== this.id
25
+ )
26
+ }
27
+
28
+ localStorage.setItem(
29
+ this.#localStorageKey,
30
+ JSON.stringify(expanded_ingredient_groups)
31
+ )
32
+ }
33
+
34
+ get isInLocalStorage() {
35
+ return this.localStorageItem.includes(this.id)
36
+ }
37
+
38
+ get localStorageItem() {
39
+ const item = localStorage.getItem(this.#localStorageKey)
40
+
41
+ if (!item) return []
42
+
43
+ try {
44
+ return JSON.parse(item)
45
+ } catch (error) {
46
+ console.error(error)
47
+ return []
48
+ }
49
+ }
50
+ }
51
+
52
+ customElements.define("alchemy-ingredient-group", IngredientGroup, {
53
+ extends: "details"
54
+ })
@@ -0,0 +1,48 @@
1
+ class LinkButton extends HTMLButtonElement {
2
+ constructor() {
3
+ super()
4
+ this.addEventListener("click", this)
5
+ this.classList.add("icon_button")
6
+ // Prevent accidental form submits if this component is wrapped inside a form
7
+ this.setAttribute("type", "button")
8
+ this.innerHTML = '<i class="icon ri-link-m ri-fw"></i>'
9
+ }
10
+
11
+ handleEvent(event) {
12
+ const dialog = new Alchemy.LinkDialog(this)
13
+ dialog.open()
14
+ event.preventDefault()
15
+ }
16
+
17
+ setLink(url, title, target, type) {
18
+ this.classList.add("linked")
19
+ this.dispatchEvent(
20
+ new CustomEvent("alchemy:link", {
21
+ bubbles: true,
22
+ detail: { url, title, target, type }
23
+ })
24
+ )
25
+ }
26
+
27
+ get linkUrl() {
28
+ return this.linkButtons.linkUrlField.value
29
+ }
30
+
31
+ get linkTitle() {
32
+ return this.linkButtons.linkTitleField.value
33
+ }
34
+
35
+ get linkTarget() {
36
+ return this.linkButtons.linkTargetField.value
37
+ }
38
+
39
+ get linkClass() {
40
+ return this.linkButtons.linkClassField.value
41
+ }
42
+
43
+ get linkButtons() {
44
+ return this.closest("alchemy-link-buttons")
45
+ }
46
+ }
47
+
48
+ customElements.define("alchemy-link-button", LinkButton, { extends: "button" })
@@ -0,0 +1,38 @@
1
+ class UnlinkButton extends HTMLButtonElement {
2
+ constructor() {
3
+ super()
4
+ this.addEventListener("click", this)
5
+ this.classList.add("icon_button")
6
+ // Prevent accidental form submits if this component is wrapped inside a form
7
+ this.setAttribute("type", "button")
8
+ this.linked = this.linked
9
+ this.innerHTML = '<i class="icon ri-link-unlink-m ri-fw"></i>'
10
+ }
11
+
12
+ handleEvent(event) {
13
+ if (this.linked) {
14
+ this.linked = false
15
+ this.blur()
16
+ this.dispatchEvent(new CustomEvent("alchemy:unlink", { bubbles: true }))
17
+ }
18
+ event.preventDefault()
19
+ }
20
+
21
+ set linked(isLinked) {
22
+ if (isLinked) {
23
+ this.classList.replace("disabled", "linked")
24
+ this.removeAttribute("tabindex")
25
+ } else {
26
+ this.classList.replace("linked", "disabled")
27
+ this.setAttribute("tabindex", "-1")
28
+ }
29
+ }
30
+
31
+ get linked() {
32
+ return this.classList.contains("linked")
33
+ }
34
+ }
35
+
36
+ customElements.define("alchemy-unlink-button", UnlinkButton, {
37
+ extends: "button"
38
+ })