alchemy_cms 7.1.13 → 7.2.0.b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -39
  3. data/Gemfile +2 -11
  4. data/LICENSE +1 -1
  5. data/README.md +5 -6
  6. data/SECURITY.md +1 -1
  7. data/alchemy_cms.gemspec +3 -4
  8. data/app/assets/javascripts/alchemy/admin.js +0 -9
  9. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +5 -15
  10. data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +5 -4
  11. data/app/assets/javascripts/alchemy/templates/index.js +0 -1
  12. data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
  13. data/app/assets/javascripts/alchemy/templates/page_folder.hbs +1 -1
  14. data/app/assets/javascripts/tinymce/plugins/alchemy_link/plugin.min.js +20 -7
  15. data/app/assets/stylesheets/alchemy/_custom-properties.scss +12 -0
  16. data/app/assets/stylesheets/alchemy/_mixins.scss +10 -6
  17. data/app/assets/stylesheets/alchemy/_variables.scss +3 -0
  18. data/app/assets/stylesheets/alchemy/admin.scss +2 -2
  19. data/app/assets/stylesheets/alchemy/archive.scss +4 -3
  20. data/app/assets/stylesheets/alchemy/attachment-select.scss +19 -0
  21. data/app/assets/stylesheets/alchemy/base.scss +31 -18
  22. data/app/assets/stylesheets/alchemy/buttons.scss +3 -4
  23. data/app/assets/stylesheets/alchemy/dashboard.scss +1 -1
  24. data/app/assets/stylesheets/alchemy/dialogs.scss +2 -5
  25. data/app/assets/stylesheets/alchemy/elements.scss +74 -42
  26. data/app/assets/stylesheets/alchemy/flash.scss +20 -70
  27. data/app/assets/stylesheets/alchemy/forms.scss +41 -36
  28. data/app/assets/stylesheets/alchemy/frame.scss +12 -3
  29. data/app/assets/stylesheets/alchemy/icons.scss +34 -2
  30. data/app/assets/stylesheets/alchemy/image_library.scss +18 -9
  31. data/app/assets/stylesheets/alchemy/{filter_field.scss → list_filter.scss} +8 -7
  32. data/app/assets/stylesheets/alchemy/lists.scss +1 -1
  33. data/app/assets/stylesheets/alchemy/navigation.scss +9 -12
  34. data/app/assets/stylesheets/alchemy/node-select.scss +1 -1
  35. data/app/assets/stylesheets/alchemy/nodes.scss +15 -13
  36. data/app/assets/stylesheets/alchemy/notices.scss +56 -39
  37. data/app/assets/stylesheets/alchemy/page-select.scss +1 -4
  38. data/app/assets/stylesheets/alchemy/pagination.scss +11 -1
  39. data/app/assets/stylesheets/alchemy/preview_window.scss +3 -3
  40. data/app/assets/stylesheets/alchemy/search.scss +4 -4
  41. data/app/assets/stylesheets/alchemy/selects.scss +13 -7
  42. data/app/assets/stylesheets/alchemy/shoelace.scss +33 -2
  43. data/app/assets/stylesheets/alchemy/sitemap.scss +155 -159
  44. data/app/assets/stylesheets/alchemy/tables.scss +49 -12
  45. data/app/assets/stylesheets/alchemy/tags.scss +17 -11
  46. data/app/assets/stylesheets/alchemy/toolbar.scss +2 -2
  47. data/app/assets/stylesheets/alchemy/typography.scss +41 -22
  48. data/app/assets/stylesheets/alchemy/upload.scss +5 -4
  49. data/app/components/alchemy/admin/attachment_select.rb +39 -0
  50. data/app/components/alchemy/admin/icon.rb +72 -0
  51. data/app/components/alchemy/admin/link_dialog/anchor_tab.rb +41 -0
  52. data/app/components/alchemy/admin/link_dialog/base_tab.rb +75 -0
  53. data/app/components/alchemy/admin/link_dialog/external_tab.rb +42 -0
  54. data/app/components/alchemy/admin/link_dialog/file_tab.rb +45 -0
  55. data/app/components/alchemy/admin/link_dialog/internal_tab.rb +66 -0
  56. data/app/components/alchemy/admin/link_dialog/tabs.rb +33 -0
  57. data/app/components/alchemy/admin/list_filter.rb +42 -0
  58. data/app/components/alchemy/admin/message.rb +19 -0
  59. data/app/components/alchemy/admin/tags_autocomplete.rb +25 -0
  60. data/app/components/alchemy/admin/toolbar_button.rb +111 -0
  61. data/app/components/alchemy/ingredients/datetime_view.rb +2 -3
  62. data/app/components/alchemy/ingredients/link_view.rb +1 -7
  63. data/app/components/alchemy/ingredients/picture_view.rb +2 -2
  64. data/app/components/alchemy/ingredients/text_view.rb +1 -2
  65. data/app/controllers/alchemy/admin/base_controller.rb +3 -27
  66. data/app/controllers/alchemy/admin/elements_controller.rb +4 -2
  67. data/app/controllers/alchemy/admin/ingredients_controller.rb +2 -0
  68. data/app/controllers/alchemy/admin/languages_controller.rb +2 -2
  69. data/app/controllers/alchemy/admin/layoutpages_controller.rb +0 -19
  70. data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +12 -4
  71. data/app/controllers/alchemy/admin/nodes_controller.rb +26 -0
  72. data/app/controllers/alchemy/admin/pages_controller.rb +16 -79
  73. data/app/controllers/alchemy/admin/picture_descriptions_controller.rb +15 -0
  74. data/app/controllers/alchemy/admin/pictures_controller.rb +18 -1
  75. data/app/controllers/alchemy/admin/resources_controller.rb +16 -11
  76. data/app/controllers/alchemy/api/attachments_controller.rb +44 -0
  77. data/app/controllers/alchemy/api/pages_controller.rb +10 -6
  78. data/app/controllers/alchemy/base_controller.rb +2 -2
  79. data/app/controllers/alchemy/messages_controller.rb +3 -3
  80. data/app/controllers/alchemy/pages_controller.rb +8 -6
  81. data/app/controllers/concerns/alchemy/admin/current_language.rb +1 -11
  82. data/app/controllers/concerns/alchemy/legacy_page_redirects.rb +1 -1
  83. data/app/decorators/alchemy/element_editor.rb +2 -2
  84. data/app/helpers/alchemy/admin/base_helper.rb +8 -60
  85. data/app/helpers/alchemy/admin/elements_helper.rb +1 -1
  86. data/app/helpers/alchemy/admin/ingredients_helper.rb +1 -1
  87. data/app/helpers/alchemy/base_helper.rb +9 -91
  88. data/app/helpers/alchemy/elements_helper.rb +3 -3
  89. data/app/helpers/alchemy/pages_helper.rb +16 -9
  90. data/app/javascript/alchemy_admin/components/attachment_select.js +24 -0
  91. data/app/javascript/alchemy_admin/components/button.js +3 -0
  92. data/app/javascript/alchemy_admin/components/clipboard_button.js +3 -2
  93. data/app/javascript/alchemy_admin/components/dialog_link.js +10 -7
  94. data/app/javascript/alchemy_admin/components/dom_id_select.js +69 -0
  95. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +42 -0
  96. data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +4 -2
  97. data/app/javascript/alchemy_admin/components/element_editor.js +21 -13
  98. data/app/javascript/alchemy_admin/components/elements_window.js +87 -0
  99. data/app/javascript/alchemy_admin/components/growl.js +13 -0
  100. data/app/javascript/alchemy_admin/components/icon.js +51 -0
  101. data/app/javascript/alchemy_admin/components/index.js +24 -0
  102. data/app/javascript/alchemy_admin/components/ingredient_group.js +6 -0
  103. data/app/javascript/alchemy_admin/components/link_buttons/link_button.js +21 -11
  104. data/app/javascript/alchemy_admin/components/link_buttons/unlink_button.js +2 -1
  105. data/app/javascript/alchemy_admin/components/link_buttons.js +1 -0
  106. data/app/javascript/alchemy_admin/components/list_filter.js +68 -0
  107. data/app/javascript/alchemy_admin/components/message.js +69 -0
  108. data/app/javascript/alchemy_admin/components/node_select.js +1 -1
  109. data/app/javascript/alchemy_admin/components/overlay.js +6 -6
  110. data/app/javascript/alchemy_admin/components/page_select.js +3 -7
  111. data/app/javascript/alchemy_admin/components/preview_window.js +121 -0
  112. data/app/javascript/alchemy_admin/components/remote_select.js +4 -1
  113. data/app/javascript/alchemy_admin/components/select.js +37 -1
  114. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +57 -0
  115. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +4 -3
  116. data/app/javascript/alchemy_admin/components/uploader/progress.js +1 -1
  117. data/app/javascript/alchemy_admin/confirm_dialog.js +133 -0
  118. data/app/javascript/alchemy_admin/dirty.js +19 -14
  119. data/app/javascript/alchemy_admin/fixed_elements.js +24 -0
  120. data/app/javascript/alchemy_admin/growler.js +15 -0
  121. data/app/javascript/alchemy_admin/gui.js +2 -4
  122. data/app/javascript/alchemy_admin/hotkeys.js +60 -0
  123. data/app/javascript/alchemy_admin/image_loader.js +2 -2
  124. data/app/javascript/alchemy_admin/ingredient_anchor_link.js +2 -3
  125. data/app/javascript/alchemy_admin/initializer.js +1 -8
  126. data/app/javascript/alchemy_admin/link_dialog.js +131 -0
  127. data/app/javascript/alchemy_admin/locales/en.js +3 -0
  128. data/app/javascript/alchemy_admin/node_tree.js +4 -3
  129. data/app/javascript/alchemy_admin/page_sorter.js +23 -14
  130. data/app/javascript/alchemy_admin/picture_editors.js +6 -5
  131. data/app/javascript/alchemy_admin/shoelace_theme.js +60 -0
  132. data/app/javascript/alchemy_admin/sitemap.js +9 -3
  133. data/app/javascript/alchemy_admin/sortable_elements.js +4 -6
  134. data/app/javascript/alchemy_admin.js +18 -42
  135. data/app/models/alchemy/current.rb +26 -0
  136. data/app/models/alchemy/element.rb +1 -1
  137. data/app/models/alchemy/ingredients/audio.rb +0 -11
  138. data/app/models/alchemy/ingredients/datetime.rb +1 -1
  139. data/app/models/alchemy/ingredients/headline.rb +8 -1
  140. data/app/models/alchemy/ingredients/picture.rb +6 -0
  141. data/app/models/alchemy/ingredients/video.rb +0 -12
  142. data/app/models/alchemy/language.rb +8 -6
  143. data/app/models/alchemy/node.rb +2 -2
  144. data/app/models/alchemy/page/page_elements.rb +8 -8
  145. data/app/models/alchemy/page/page_layouts.rb +3 -3
  146. data/app/models/alchemy/page/page_natures.rb +13 -9
  147. data/app/models/alchemy/page/page_scopes.rb +2 -2
  148. data/app/models/alchemy/page/publisher.rb +1 -0
  149. data/app/models/alchemy/page.rb +16 -31
  150. data/app/models/alchemy/picture.rb +8 -0
  151. data/app/models/alchemy/picture_description.rb +8 -0
  152. data/app/models/alchemy/picture_variant.rb +1 -1
  153. data/app/models/alchemy/site.rb +10 -7
  154. data/app/models/concerns/alchemy/picture_thumbnails.rb +5 -4
  155. data/app/serializers/alchemy/attachment_serializer.rb +8 -0
  156. data/app/serializers/alchemy/page_node_serializer.rb +9 -0
  157. data/app/views/alchemy/_menubar.html.erb +1 -1
  158. data/app/views/alchemy/_preview_mode_code.html.erb +1 -1
  159. data/app/views/alchemy/admin/attachments/_tag_list.html.erb +2 -2
  160. data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -1
  161. data/app/views/alchemy/admin/attachments/edit.html.erb +3 -4
  162. data/app/views/alchemy/admin/clipboard/clear.js.erb +1 -1
  163. data/app/views/alchemy/admin/clipboard/index.html.erb +1 -1
  164. data/app/views/alchemy/admin/clipboard/insert.js.erb +1 -1
  165. data/app/views/alchemy/admin/clipboard/remove.js.erb +1 -1
  166. data/app/views/alchemy/admin/dashboard/_locked_pages.html.erb +1 -1
  167. data/app/views/alchemy/admin/dashboard/_sites.html.erb +1 -1
  168. data/app/views/alchemy/admin/dashboard/help.html.erb +48 -12
  169. data/app/views/alchemy/admin/dashboard/index.html.erb +1 -1
  170. data/app/views/alchemy/admin/dashboard/info.html.erb +5 -8
  171. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +1 -1
  172. data/app/views/alchemy/admin/elements/_element.html.erb +5 -5
  173. data/app/views/alchemy/admin/elements/_footer.html.erb +1 -1
  174. data/app/views/alchemy/admin/elements/_header.html.erb +6 -2
  175. data/app/views/alchemy/admin/elements/_toolbar.html.erb +8 -6
  176. data/app/views/alchemy/admin/elements/create.js.erb +0 -5
  177. data/app/views/alchemy/admin/elements/index.html.erb +70 -34
  178. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +1 -2
  179. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +3 -5
  180. data/app/views/alchemy/admin/languages/_language.html.erb +1 -1
  181. data/app/views/alchemy/admin/languages/index.html.erb +2 -2
  182. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +18 -18
  183. data/app/views/alchemy/admin/layoutpages/edit.html.erb +4 -5
  184. data/app/views/alchemy/admin/layoutpages/index.html.erb +2 -2
  185. data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +10 -11
  186. data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +15 -17
  187. data/app/views/alchemy/admin/legacy_page_urls/_table.html.erb +16 -0
  188. data/app/views/alchemy/admin/legacy_page_urls/_update.turbo_stream.erb +12 -0
  189. data/app/views/alchemy/admin/legacy_page_urls/create.turbo_stream.erb +8 -0
  190. data/app/views/alchemy/admin/legacy_page_urls/destroy.turbo_stream.erb +1 -0
  191. data/app/views/alchemy/admin/legacy_page_urls/edit.html.erb +27 -0
  192. data/app/views/alchemy/admin/legacy_page_urls/show.html.erb +1 -0
  193. data/app/views/alchemy/admin/legacy_page_urls/update.turbo_stream.erb +1 -0
  194. data/app/views/alchemy/admin/nodes/_form.html.erb +12 -11
  195. data/app/views/alchemy/admin/nodes/_label.html.erb +1 -0
  196. data/app/views/alchemy/admin/nodes/_node.html.erb +19 -19
  197. data/app/views/alchemy/admin/nodes/_page_nodes.html.erb +48 -0
  198. data/app/views/alchemy/admin/nodes/_update.turbo_stream.erb +9 -0
  199. data/app/views/alchemy/admin/nodes/create.turbo_stream.erb +1 -0
  200. data/app/views/alchemy/admin/nodes/destroy.turbo_stream.erb +1 -0
  201. data/app/views/alchemy/admin/nodes/index.html.erb +3 -3
  202. data/app/views/alchemy/admin/pages/_form.html.erb +3 -4
  203. data/app/views/alchemy/admin/pages/_legacy_urls.html.erb +4 -15
  204. data/app/views/alchemy/admin/pages/_page.html.erb +39 -39
  205. data/app/views/alchemy/admin/pages/_table_row.html.erb +3 -3
  206. data/app/views/alchemy/admin/pages/_toolbar.html.erb +2 -2
  207. data/app/views/alchemy/admin/pages/configure.html.erb +6 -0
  208. data/app/views/alchemy/admin/pages/edit.html.erb +15 -62
  209. data/app/views/alchemy/admin/pages/unlock.js.erb +3 -3
  210. data/app/views/alchemy/admin/partials/_autocomplete_tag_list.html.erb +3 -1
  211. data/app/views/alchemy/admin/partials/_flash_notices.html.erb +4 -2
  212. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +1 -1
  213. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +5 -2
  214. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -2
  215. data/app/views/alchemy/admin/partials/_search_form.html.erb +2 -2
  216. data/app/views/alchemy/admin/partials/_site_select.html.erb +1 -1
  217. data/app/views/alchemy/admin/picture_descriptions/_form.html.erb +11 -0
  218. data/app/views/alchemy/admin/picture_descriptions/edit.html.erb +6 -0
  219. data/app/views/alchemy/admin/pictures/_form.html.erb +4 -3
  220. data/app/views/alchemy/admin/pictures/_infos.html.erb +1 -1
  221. data/app/views/alchemy/admin/pictures/_picture_description_field.html.erb +29 -0
  222. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +2 -2
  223. data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +0 -2
  224. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +3 -3
  225. data/app/views/alchemy/admin/pictures/show.html.erb +3 -3
  226. data/app/views/alchemy/admin/resources/_form.html.erb +3 -4
  227. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +1 -1
  228. data/app/views/alchemy/admin/resources/_tag_list.html.erb +2 -2
  229. data/app/views/alchemy/admin/resources/index.html.erb +2 -2
  230. data/app/views/alchemy/admin/sites/index.html.erb +1 -1
  231. data/app/views/alchemy/admin/styleguide/index.html.erb +29 -24
  232. data/app/views/alchemy/admin/tags/_tag.html.erb +1 -1
  233. data/app/views/alchemy/admin/tags/edit.html.erb +1 -1
  234. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  235. data/app/views/alchemy/base/500.html.erb +7 -18
  236. data/app/views/alchemy/base/error_notice.html.erb +3 -1
  237. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +1 -1
  238. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +2 -3
  239. data/app/views/alchemy/ingredients/_headline_editor.html.erb +13 -8
  240. data/app/views/alchemy/ingredients/_picture_editor.html.erb +1 -1
  241. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +1 -1
  242. data/app/views/alchemy/language_links/_language.html.erb +1 -1
  243. data/app/views/kaminari/alchemy/_first_page.html.erb +2 -2
  244. data/app/views/kaminari/alchemy/_gap.html.erb +1 -1
  245. data/app/views/kaminari/alchemy/_last_page.html.erb +2 -2
  246. data/app/views/kaminari/alchemy/_next_page.html.erb +2 -2
  247. data/app/views/kaminari/alchemy/_prev_page.html.erb +2 -2
  248. data/app/views/layouts/alchemy/admin.html.erb +2 -1
  249. data/bundles/shoelace.js +3 -1
  250. data/config/locales/alchemy.en.yml +16 -3
  251. data/config/routes.rb +4 -2
  252. data/db/migrate/20240314105244_create_alchemy_picture_descriptions.rb +11 -0
  253. data/lib/alchemy/configuration_methods.rb +1 -1
  254. data/lib/alchemy/controller_actions.rb +3 -3
  255. data/lib/alchemy/element_definition.rb +10 -6
  256. data/lib/alchemy/engine.rb +19 -2
  257. data/lib/alchemy/page_layout.rb +10 -6
  258. data/lib/alchemy/permissions.rb +4 -3
  259. data/lib/alchemy/resource.rb +4 -14
  260. data/lib/alchemy/routing_constraints.rb +1 -1
  261. data/lib/alchemy/seeder.rb +2 -2
  262. data/lib/alchemy/test_support/capybara_helpers.rb +4 -0
  263. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  264. data/lib/alchemy/test_support/shared_contexts.rb +8 -0
  265. data/lib/alchemy/tinymce.rb +2 -1
  266. data/lib/alchemy/version.rb +1 -1
  267. data/lib/alchemy.rb +36 -0
  268. data/lib/alchemy_cms.rb +0 -1
  269. data/lib/generators/alchemy/menus/templates/node.html.erb +2 -2
  270. data/lib/generators/alchemy/menus/templates/node.html.haml +2 -2
  271. data/lib/generators/alchemy/menus/templates/node.html.slim +2 -2
  272. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  273. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  274. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  275. data/lib/tasks/alchemy/sitemap.rake +97 -0
  276. data/lib/tasks/alchemy/tidy.rake +1 -0
  277. data/package.json +8 -8
  278. data/vendor/assets/fonts/remixicon.symbol.svg +11 -0
  279. data/vendor/javascript/shoelace.min.js +333 -118
  280. data/vendor/javascript/sortable.min.js +1 -1
  281. data/vendor/javascript/tinymce.min.js +1 -1
  282. data/vendor/javascript/ungap-custom-elements.min.js +1 -1
  283. metadata +63 -55
  284. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +0 -85
  285. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +0 -107
  286. data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +0 -66
  287. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +0 -45
  288. data/app/assets/javascripts/alchemy/alchemy.growler.js.coffee +0 -24
  289. data/app/assets/javascripts/alchemy/alchemy.hotkeys.js.coffee +0 -49
  290. data/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee +0 -0
  291. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +0 -230
  292. data/app/assets/javascripts/alchemy/alchemy.list_filter.js.coffee +0 -49
  293. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +0 -82
  294. data/app/assets/javascripts/alchemy/alchemy.string_extension.js.coffee +0 -11
  295. data/app/assets/javascripts/alchemy/templates/page.hbs +0 -19
  296. data/app/javascript/alchemy_admin/tags_autocomplete.js +0 -46
  297. data/app/models/alchemy/tree_node.rb +0 -7
  298. data/app/views/alchemy/admin/elements/destroy.js.erb +0 -8
  299. data/app/views/alchemy/admin/legacy_page_urls/_form.html.erb +0 -5
  300. data/app/views/alchemy/admin/legacy_page_urls/create.js.erb +0 -9
  301. data/app/views/alchemy/admin/legacy_page_urls/destroy.js.erb +0 -6
  302. data/app/views/alchemy/admin/legacy_page_urls/update.js.erb +0 -2
  303. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +0 -22
  304. data/app/views/alchemy/admin/pages/_external_link.html.erb +0 -31
  305. data/app/views/alchemy/admin/pages/_file_link.html.erb +0 -31
  306. data/app/views/alchemy/admin/pages/_internal_link.html.erb +0 -35
  307. data/app/views/alchemy/admin/pages/link.html.erb +0 -26
  308. data/app/views/alchemy/admin/partials/_flash.html.erb +0 -4
  309. data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +0 -29
  310. data/lib/alchemy/test_support/current_language_shared_examples.rb +0 -33
  311. data/vendor/assets/fonts/remixicon.eot +0 -0
  312. data/vendor/assets/fonts/remixicon.svg +0 -7816
  313. data/vendor/assets/fonts/remixicon.ttf +0 -0
  314. data/vendor/assets/fonts/remixicon.woff +0 -0
  315. data/vendor/assets/fonts/remixicon.woff2 +0 -0
  316. data/vendor/assets/stylesheets/remixicon.scss +0 -10480
@@ -2,12 +2,10 @@
2
2
 
3
3
  module Alchemy
4
4
  module Admin
5
- # This module contains helper methods for rendering dialogs, toolbar buttons and confirmation windows.
5
+ # This module contains helper methods for rendering dialogs and confirmation windows.
6
6
  #
7
7
  # The most important helpers for module developers are:
8
8
  #
9
- # * {#toolbar}
10
- # * {#toolbar_button}
11
9
  # * {#link_to_dialog}
12
10
  # * {#link_to_confirm_dialog}
13
11
  #
@@ -106,18 +104,11 @@ module Alchemy
106
104
  # The css class of the <input> tag
107
105
  # @option options [String or Hash] :data ({'alchemy-list-filter' => items})
108
106
  # A HTML data attribute that holds the jQuery selector that represents the list to be filtered
109
- #
110
- def js_filter_field(items, options = {})
111
- options = {
112
- class: "js_filter_field",
113
- data: {"alchemy-list-filter" => items}
114
- }.merge(options)
115
- content_tag(:div, class: "js_filter_field_box") do
116
- concat text_field_tag(nil, nil, options)
117
- concat render_icon(:search)
118
- concat link_to(render_icon(:times, size: "xs"), "", class: "js_filter_field_clear", title: Alchemy.t(:click_to_show_all))
119
- end
107
+ # @deprecated render Alchemy::Admin::ListFilter.new(items) instead
108
+ def js_filter_field(items, _options = {})
109
+ render Alchemy::Admin::ListFilter.new(items)
120
110
  end
111
+ deprecate js_filter_field: "render Alchemy::Admin::ListFilter.new(items) instead", deprecator: Alchemy::Deprecation
121
112
 
122
113
  # Returns a link that opens a modal confirmation to delete window.
123
114
  #
@@ -266,36 +257,11 @@ module Alchemy
266
257
  # Skip the permission check. NOT RECOMMENDED!
267
258
  # @option options [Boolean] :loading_indicator (true)
268
259
  # Shows the please wait dialog while loading. Only for buttons not opening an dialog.
269
- #
260
+ # @deprecated render Alchemy::Admin::ToolbarButton.new instead
270
261
  def toolbar_button(options = {})
271
- options = {
272
- dialog: true,
273
- skip_permission_check: false,
274
- active: false,
275
- link_options: {},
276
- dialog_options: {},
277
- loading_indicator: false
278
- }.merge(options.symbolize_keys)
279
- button = render(
280
- "alchemy/admin/partials/toolbar_button",
281
- options: options
282
- )
283
- if options[:skip_permission_check] || can?(*permission_from_options(options))
284
- button
285
- else
286
- ""
287
- end
288
- end
289
-
290
- # (internal) Used by upload form
291
- def new_asset_path_with_session_information(asset_type)
292
- session_key = Rails.application.config.session_options[:key]
293
- if asset_type == "picture"
294
- alchemy.admin_pictures_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token, :format => :js)
295
- elsif asset_type == "attachment"
296
- alchemy.admin_attachments_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token, :format => :js)
297
- end
262
+ render Alchemy::Admin::ToolbarButton.new(**options)
298
263
  end
264
+ deprecate toolbar_button: "render Alchemy::Admin::ToolbarButton.new instead", deprecator: Alchemy::Deprecation
299
265
 
300
266
  # Renders a textfield ready to display a datepicker
301
267
  #
@@ -406,24 +372,6 @@ module Alchemy
406
372
  Alchemy.t(:page_definition_missing)
407
373
  )
408
374
  end
409
-
410
- private
411
-
412
- def permission_from_options(options)
413
- if options[:if_permitted_to].blank?
414
- options[:if_permitted_to] = permission_array_from_url(options)
415
- else
416
- options[:if_permitted_to]
417
- end
418
- end
419
-
420
- def permission_array_from_url(options)
421
- action_controller = options[:url].gsub(/\A\//, "").split("/")
422
- [
423
- action_controller.last.to_sym,
424
- action_controller[0..action_controller.length - 2].join("_").to_sym
425
- ]
426
- end
427
375
  end
428
376
  end
429
377
  end
@@ -18,7 +18,7 @@ module Alchemy
18
18
  Element.display_name_for(e["name"]),
19
19
  e["name"]
20
20
  ]
21
- end
21
+ end.sort
22
22
  end
23
23
  end
24
24
  end
@@ -36,7 +36,7 @@ module Alchemy
36
36
  label_tag ingredient.form_field_id(column), html_options do
37
37
  [
38
38
  render_ingredient_role(ingredient),
39
- render_hint_for(ingredient, size: "lg", fixed_width: false)
39
+ render_hint_for(ingredient, size: "1x", fixed_width: false)
40
40
  ].compact.join("&nbsp;").html_safe
41
41
  end
42
42
  end
@@ -4,9 +4,11 @@ module Alchemy
4
4
  module BaseHelper
5
5
  # An alias for truncate.
6
6
  # Left here for downwards compatibilty.
7
+ # @deprecated
7
8
  def shorten(text, length)
8
9
  text.truncate(length: length)
9
10
  end
11
+ deprecate :shorten, deprecator: Alchemy::Deprecation
10
12
 
11
13
  # Logs a message in the Rails logger (warn level)
12
14
  # and optionally displays an error message to the user.
@@ -22,21 +24,12 @@ module Alchemy
22
24
  # Render a Remix icon
23
25
  #
24
26
  # @param icon_name [String] icon name
25
- # @option options - style: nil [String] icon style. line or fill
27
+ # @option options - style: fill [String] icon style. line or fill. Pass false for no style.
26
28
  # @option options - size: nil [String] icon size
27
29
  #
28
30
  # @return [String]
29
31
  def render_icon(icon_name, options = {})
30
- options = {style: "line", fixed_width: true}.merge(options)
31
- style = options[:style] && "-#{ri_style(options[:style])}"
32
- classes = [
33
- "icon",
34
- "ri-#{ri_icon(icon_name)}#{style}",
35
- options[:size] ? "ri-#{options[:size]}" : nil,
36
- options[:fixed_width] ? "ri-fw" : nil,
37
- options[:class]
38
- ].compact
39
- content_tag("i", nil, class: classes)
32
+ render Alchemy::Admin::Icon.new(icon_name, options)
40
33
  end
41
34
 
42
35
  # Returns a div with an icon and the passed content
@@ -50,34 +43,21 @@ module Alchemy
50
43
  # <% end %>
51
44
  #
52
45
  def render_message(type = :info, msg = nil, &blk)
53
- icon_class = message_icon_class(type)
54
- if blk
55
- content_tag :div, render_icon(icon_class) + capture(&blk), class: "#{type} message"
56
- else
57
- content_tag :div, render_icon(icon_class) + msg, class: "#{type} message"
58
- end
59
- end
60
-
61
- # Renders the flash partial (+alchemy/admin/partials/flash+)
62
- #
63
- # @param [String] notice The notice you want to display
64
- # @param [Symbol] style The style of this flash. Valid values are +:notice+ (default), +:warn+ and +:error+
65
- #
66
- def render_flash_notice(notice, style = :notice)
67
- render("alchemy/admin/partials/flash", flash_type: style, message: notice)
46
+ render Alchemy::Admin::Message.new(msg || capture(&blk), type: type)
68
47
  end
69
48
 
70
49
  # Checks if the given argument is a String or a Page object.
71
50
  # If a String is given, it tries to find the page via page_layout
72
51
  # Logs a warning if no page is given.
52
+ # @deprecated
73
53
  def page_or_find(page)
74
- unless Language.current
54
+ unless Current.language
75
55
  warning("No default language set up")
76
56
  return nil
77
57
  end
78
58
 
79
59
  if page.is_a?(String)
80
- page = Language.current.pages.find_by(page_layout: page)
60
+ page = Current.language.pages.find_by(page_layout: page)
81
61
  end
82
62
  if page.blank?
83
63
  warning("No Page found for #{page.inspect}")
@@ -86,68 +66,6 @@ module Alchemy
86
66
  page
87
67
  end
88
68
  end
89
-
90
- # Returns the icon name for given message type
91
- #
92
- # @param message_type [String] The message type. One of +warning+, +info+, +notice+, +error+
93
- # @return [String] The icon name
94
- def message_icon_class(message_type)
95
- case message_type.to_s
96
- when "warning", "warn", "alert" then "exclamation"
97
- when "notice" then "check"
98
- when "error" then "bug"
99
- when "hint" then "info"
100
- else
101
- message_type
102
- end
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
137
-
138
- # Returns the Remix icon style for given style
139
- #
140
- # @param style [String] The style name
141
- # @return [String] The RemixIcon style
142
- def ri_style(style)
143
- case style.to_s
144
- when "solid", "fill"
145
- "fill"
146
- when "line", "regular"
147
- "line"
148
- else
149
- style
150
- end
151
- end
69
+ deprecate :page_or_find, deprecator: Alchemy::Deprecation
152
70
  end
153
71
  end
@@ -72,13 +72,13 @@ module Alchemy
72
72
  #
73
73
  def render_elements(options = {}, &blk)
74
74
  options = {
75
- from_page: @page,
75
+ from_page: Current.page,
76
76
  render_format: "html"
77
77
  }.update(options)
78
78
 
79
79
  finder = options[:finder] || Alchemy::ElementsFinder.new(options)
80
80
 
81
- page_version = if @preview_mode
81
+ page_version = if Current.preview_page?
82
82
  options[:from_page]&.draft_version
83
83
  else
84
84
  options[:from_page]&.public_version
@@ -173,7 +173,7 @@ module Alchemy
173
173
 
174
174
  # Returns a hash containing the HTML tag attributes required for preview mode.
175
175
  def element_preview_code_attributes(element)
176
- return {} unless element.present? && @preview_mode && element.page == @page
176
+ return {} unless element.present? && Current.preview_page?(element.page)
177
177
 
178
178
  {"data-alchemy-element" => element.id}
179
179
  end
@@ -64,9 +64,8 @@ module Alchemy
64
64
  #
65
65
  def render_site_layout(&block)
66
66
  render current_alchemy_site, &block
67
- rescue ActionView::MissingTemplate
68
- warning("Site layout for #{current_alchemy_site.try(:name)} not found. Please run `rails g alchemy:site_layouts`")
69
- ""
67
+ rescue ActionView::MissingTemplate => error
68
+ error_or_warning(error, "Site layout for #{current_alchemy_site.try(:name)} not found. Please run `rails g alchemy:site_layouts`")
70
69
  end
71
70
 
72
71
  # Renders a menu partial
@@ -79,7 +78,7 @@ module Alchemy
79
78
  def render_menu(menu_type, options = {})
80
79
  root_node = Alchemy::Node.roots.find_by(
81
80
  menu_type: menu_type,
82
- language: Alchemy::Language.current
81
+ language: Alchemy::Current.language
83
82
  )
84
83
  if root_node.nil?
85
84
  warning("Menu with type #{menu_type} not found!")
@@ -87,11 +86,8 @@ module Alchemy
87
86
  end
88
87
 
89
88
  render("alchemy/menus/#{menu_type}/wrapper", menu: root_node, options: options)
90
- rescue ActionView::MissingTemplate => e
91
- warning <<~WARN
92
- Menu partial not found for #{menu_type}.
93
- #{e}
94
- WARN
89
+ rescue ActionView::MissingTemplate => error
90
+ error_or_warning(error, "Menu partial for #{menu_type} not found. Please run `rails g alchemy:menus`")
95
91
  end
96
92
 
97
93
  # Returns page links in a breadcrumb beginning from root to current page.
@@ -169,5 +165,16 @@ module Alchemy
169
165
  def meta_robots
170
166
  "#{@page.robot_index? ? "" : "no"}index, #{@page.robot_follow? ? "" : "no"}follow"
171
167
  end
168
+
169
+ private
170
+
171
+ def error_or_warning(error, message)
172
+ if Rails.application.config.consider_all_requests_local?
173
+ raise error, message
174
+ else
175
+ Rails.logger.error message
176
+ ""
177
+ end
178
+ end
172
179
  end
173
180
  end
@@ -0,0 +1,24 @@
1
+ import { RemoteSelect } from "alchemy_admin/components/remote_select"
2
+
3
+ class AttachmentSelect extends RemoteSelect {
4
+ _renderResult(item) {
5
+ return this._renderListEntry(item)
6
+ }
7
+
8
+ /**
9
+ * html template for each list entry
10
+ * @param {object} page
11
+ * @returns {string}
12
+ * @private
13
+ */
14
+ _renderListEntry(attachment) {
15
+ return `
16
+ <div class="attachment-select--attachment">
17
+ <alchemy-icon name="${attachment.icon_css_class}"></alchemy-icon>
18
+ <span class="attachment-select--attachment-name">${attachment.name}</span>
19
+ </div>
20
+ `
21
+ }
22
+ }
23
+
24
+ customElements.define("alchemy-attachment-select", AttachmentSelect)
@@ -8,6 +8,8 @@ class Button extends HTMLButtonElement {
8
8
  if (this.form.dataset.remote == "true") {
9
9
  this.form.addEventListener("ajax:complete", this)
10
10
  }
11
+
12
+ this.form.addEventListener("turbo:submit-end", this)
11
13
  } else {
12
14
  console.warn("No form for button found!", this)
13
15
  }
@@ -26,6 +28,7 @@ class Button extends HTMLButtonElement {
26
28
  }
27
29
  break
28
30
  case "ajax:complete":
31
+ case "turbo:submit-end":
29
32
  this.enable()
30
33
  break
31
34
  }
@@ -1,11 +1,12 @@
1
1
  import "clipboard"
2
+ import { growl } from "alchemy_admin/growler"
2
3
 
3
4
  class ClipboardButton extends HTMLElement {
4
5
  constructor() {
5
6
  super()
6
7
 
7
8
  this.innerHTML = `
8
- <i class="icon ri-clipboard-line ri-fw"></i>
9
+ <alchemy-icon name="clipboard"></alchemy-icon>
9
10
  `
10
11
 
11
12
  this.clipboard = new ClipboardJS(this, {
@@ -15,7 +16,7 @@ class ClipboardButton extends HTMLElement {
15
16
  })
16
17
 
17
18
  this.clipboard.on("success", () => {
18
- Alchemy.growl(this.getAttribute("success-text"))
19
+ growl(this.getAttribute("success-text"))
19
20
  })
20
21
  }
21
22
 
@@ -10,13 +10,16 @@ export const DEFAULTS = {
10
10
  }
11
11
 
12
12
  export class DialogLink extends HTMLAnchorElement {
13
- connectedCallback() {
14
- this.addEventListener("click", (evt) => {
15
- if (!this.disabled) {
16
- this.openDialog()
17
- }
18
- evt.preventDefault()
19
- })
13
+ constructor() {
14
+ super()
15
+ this.addEventListener("click", this)
16
+ }
17
+
18
+ handleEvent(evt) {
19
+ if (!this.disabled) {
20
+ this.openDialog()
21
+ }
22
+ evt.preventDefault()
20
23
  }
21
24
 
22
25
  openDialog() {
@@ -0,0 +1,69 @@
1
+ import { get } from "alchemy_admin/utils/ajax"
2
+ import { translate } from "alchemy_admin/i18n"
3
+
4
+ class DomIdSelect extends HTMLElement {
5
+ dataItem(hash) {
6
+ return {
7
+ id: `#${hash}`,
8
+ text: `#${hash}`
9
+ }
10
+ }
11
+
12
+ get selectElement() {
13
+ return this.querySelector('select[is="alchemy-select"]')
14
+ }
15
+ }
16
+
17
+ class DomIdApiSelect extends DomIdSelect {
18
+ #pageId = undefined
19
+
20
+ connectedCallback() {
21
+ this.page = this.getAttribute("page")
22
+ }
23
+
24
+ async #fetchDomIds() {
25
+ const result = await get(Alchemy.routes.api_ingredients_path, {
26
+ page_id: this.#pageId
27
+ })
28
+ const options = result.data.ingredients
29
+ .filter((ingredient) => ingredient.data?.dom_id)
30
+ .map((ingredient) => this.dataItem(ingredient.data.dom_id))
31
+ const prompt =
32
+ options.length > 0 ? translate("None") : translate("No anchors found")
33
+
34
+ this.selectElement.setOptions(options, prompt)
35
+ this.selectElement.enable()
36
+ }
37
+
38
+ #reset() {
39
+ // wait a tick to initialize the alchemy-select
40
+ requestAnimationFrame(() => {
41
+ this.selectElement.disable()
42
+ this.selectElement.setOptions([], translate("Select a page first"))
43
+ })
44
+ }
45
+
46
+ set page(pageId) {
47
+ this.#pageId = pageId
48
+ pageId ? this.#fetchDomIds() : this.#reset()
49
+ }
50
+ }
51
+
52
+ class DomIdPreviewSelect extends DomIdSelect {
53
+ connectedCallback() {
54
+ // wait a tick to let the browser initialize the inner select component
55
+ requestAnimationFrame(() => {
56
+ const frame = document.getElementById("alchemy_preview_window")
57
+ const elements = frame.contentDocument?.querySelectorAll("[id]") || []
58
+ if (elements.length > 0) {
59
+ const options = Array.from(elements).map((element) => {
60
+ return this.dataItem(element.id)
61
+ })
62
+ this.selectElement.setOptions(options, translate("None"))
63
+ }
64
+ })
65
+ }
66
+ }
67
+
68
+ customElements.define("alchemy-dom-id-api-select", DomIdApiSelect)
69
+ customElements.define("alchemy-dom-id-preview-select", DomIdPreviewSelect)
@@ -0,0 +1,42 @@
1
+ import { removeTab } from "alchemy_admin/fixed_elements"
2
+ import { growl } from "alchemy_admin/growler"
3
+ import { reloadPreview } from "alchemy_admin/components/preview_window"
4
+ import { confirmToDeleteDialog } from "alchemy_admin/confirm_dialog"
5
+
6
+ export class DeleteElementButton extends HTMLElement {
7
+ constructor() {
8
+ super()
9
+ this.addEventListener("click", this)
10
+ }
11
+
12
+ handleEvent() {
13
+ confirmToDeleteDialog(this.url, { message: this.message }).then(
14
+ (response) => {
15
+ this.#removeElement(response)
16
+ }
17
+ )
18
+ }
19
+
20
+ #removeElement(response) {
21
+ const elementEditor = this.closest("alchemy-element-editor")
22
+ elementEditor.addEventListener("transitionend", () => {
23
+ if (elementEditor.fixed) {
24
+ removeTab(elementEditor.elementId)
25
+ }
26
+ elementEditor.remove()
27
+ })
28
+ elementEditor.classList.add("dismiss")
29
+ growl(response.message)
30
+ reloadPreview()
31
+ }
32
+
33
+ get url() {
34
+ return this.getAttribute("href")
35
+ }
36
+
37
+ get message() {
38
+ return this.getAttribute("message")
39
+ }
40
+ }
41
+
42
+ customElements.define("alchemy-delete-element-button", DeleteElementButton)
@@ -1,4 +1,6 @@
1
1
  import { patch } from "alchemy_admin/utils/ajax"
2
+ import { reloadPreview } from "alchemy_admin/components/preview_window"
3
+ import { growl } from "alchemy_admin/growler"
2
4
 
3
5
  export class PublishElementButton extends HTMLElement {
4
6
  constructor() {
@@ -14,9 +16,9 @@ export class PublishElementButton extends HTMLElement {
14
16
  .then((response) => {
15
17
  this.elementEditor.published = response.data.public
16
18
  this.tooltip.setAttribute("content", response.data.label)
17
- Alchemy.reloadPreview()
19
+ reloadPreview()
18
20
  })
19
- .catch((error) => Alchemy.growl(error.message, "error"))
21
+ .catch((error) => growl(error.message, "error"))
20
22
  }
21
23
  }
22
24
 
@@ -1,12 +1,14 @@
1
- import TagsAutocomplete from "alchemy_admin/tags_autocomplete"
2
1
  import ImageLoader from "alchemy_admin/image_loader"
3
2
  import fileEditors from "alchemy_admin/file_editors"
4
3
  import pictureEditors from "alchemy_admin/picture_editors"
4
+ import SortableElements from "alchemy_admin/sortable_elements"
5
5
  import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
6
6
  import { post } from "alchemy_admin/utils/ajax"
7
7
  import { createHtmlElement } from "alchemy_admin/utils/dom_helpers"
8
+ import { growl } from "alchemy_admin/growler"
8
9
 
9
10
  import "alchemy_admin/components/element_editor/publish_element_button"
11
+ import "alchemy_admin/components/element_editor/delete_element_button"
10
12
 
11
13
  export class ElementEditor extends HTMLElement {
12
14
  constructor() {
@@ -44,7 +46,7 @@ export class ElementEditor extends HTMLElement {
44
46
  `#${this.id} .ingredient-editor.file, #${this.id} .ingredient-editor.audio, #${this.id} .ingredient-editor.video`
45
47
  )
46
48
  pictureEditors(`#${this.id} .ingredient-editor.picture`)
47
- TagsAutocomplete(this)
49
+ SortableElements(`#${this.id} .nested-elements`)
48
50
  }
49
51
 
50
52
  handleEvent(event) {
@@ -99,7 +101,7 @@ export class ElementEditor extends HTMLElement {
99
101
  }
100
102
 
101
103
  focusElementPreview() {
102
- Alchemy.PreviewWindow.postMessage({
104
+ this.previewWindow?.postMessage({
103
105
  message: "Alchemy.focusElement",
104
106
  element_id: this.elementId
105
107
  })
@@ -139,11 +141,13 @@ export class ElementEditor extends HTMLElement {
139
141
  )
140
142
  })
141
143
  // Show message
142
- Alchemy.growl(warning, "warn")
144
+ growl(warning, "warn")
143
145
  this.elementErrors.classList.remove("hidden")
144
146
  } else {
145
- Alchemy.growl(data.notice)
146
- Alchemy.PreviewWindow.refresh(() => this.focusElementPreview())
147
+ growl(data.notice)
148
+ this.previewWindow?.refresh().then(() => {
149
+ this.focusElementPreview()
150
+ })
147
151
  this.updateTitle(data.previewText)
148
152
  data.ingredientAnchors.forEach((anchor) => {
149
153
  IngredientAnchorLink.updateIcon(anchor.ingredientId, anchor.active)
@@ -273,7 +277,7 @@ export class ElementEditor extends HTMLElement {
273
277
  }
274
278
  })
275
279
  .catch((error) => {
276
- Alchemy.growl(error.message, "error")
280
+ growl(error.message, "error")
277
281
  console.error(error)
278
282
  })
279
283
  .finally(() => {
@@ -320,7 +324,7 @@ export class ElementEditor extends HTMLElement {
320
324
  resolve()
321
325
  })
322
326
  .catch((error) => {
323
- Alchemy.growl(error.message, "error")
327
+ growl(error.message, "error")
324
328
  console.error(error)
325
329
  reject(error)
326
330
  })
@@ -353,9 +357,9 @@ export class ElementEditor extends HTMLElement {
353
357
  */
354
358
  set published(isPublished) {
355
359
  if (isPublished) {
356
- this.classList.remove("hidden")
360
+ this.classList.remove("element-hidden")
357
361
  } else {
358
- this.classList.add("hidden")
362
+ this.classList.add("element-hidden")
359
363
  }
360
364
  }
361
365
 
@@ -387,8 +391,8 @@ export class ElementEditor extends HTMLElement {
387
391
  set collapsed(value) {
388
392
  this.classList.toggle("folded", value)
389
393
  this.classList.toggle("expanded", !value)
390
- this.toggleIcon?.classList?.toggle("ri-arrow-down-s-line", !value)
391
- this.toggleIcon?.classList?.toggle("ri-arrow-left-s-line", value)
394
+ this.toggleIcon &&
395
+ (this.toggleIcon.name = value ? "arrow-left-s" : "arrow-down-s")
392
396
  }
393
397
 
394
398
  /**
@@ -475,7 +479,7 @@ export class ElementEditor extends HTMLElement {
475
479
  * @returns {HTMLElement|undefined}
476
480
  */
477
481
  get toggleIcon() {
478
- return this.toggleButton?.querySelector(".icon")
482
+ return this.toggleButton?.querySelector("alchemy-icon")
479
483
  }
480
484
 
481
485
  /**
@@ -549,6 +553,10 @@ export class ElementEditor extends HTMLElement {
549
553
  get parentElementEditor() {
550
554
  return this.parentElement?.closest("alchemy-element-editor")
551
555
  }
556
+
557
+ get previewWindow() {
558
+ return document.getElementById("alchemy_preview_window")
559
+ }
552
560
  }
553
561
 
554
562
  customElements.define("alchemy-element-editor", ElementEditor)