alchemy_cms 7.1.7 → 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 (305) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +133 -0
  3. data/Gemfile +2 -4
  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 +76 -46
  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 +7 -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/link_view.rb +1 -7
  62. data/app/components/alchemy/ingredients/picture_view.rb +2 -2
  63. data/app/components/alchemy/ingredients/text_view.rb +1 -2
  64. data/app/controllers/alchemy/admin/base_controller.rb +1 -1
  65. data/app/controllers/alchemy/admin/elements_controller.rb +4 -2
  66. data/app/controllers/alchemy/admin/ingredients_controller.rb +2 -0
  67. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  68. data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +12 -4
  69. data/app/controllers/alchemy/admin/nodes_controller.rb +26 -0
  70. data/app/controllers/alchemy/admin/pages_controller.rb +11 -78
  71. data/app/controllers/alchemy/admin/picture_descriptions_controller.rb +15 -0
  72. data/app/controllers/alchemy/admin/pictures_controller.rb +18 -1
  73. data/app/controllers/alchemy/admin/resources_controller.rb +15 -10
  74. data/app/controllers/alchemy/api/attachments_controller.rb +44 -0
  75. data/app/controllers/alchemy/api/pages_controller.rb +10 -6
  76. data/app/controllers/alchemy/base_controller.rb +2 -2
  77. data/app/controllers/alchemy/messages_controller.rb +2 -2
  78. data/app/controllers/alchemy/pages_controller.rb +8 -6
  79. data/app/controllers/concerns/alchemy/admin/current_language.rb +1 -1
  80. data/app/controllers/concerns/alchemy/legacy_page_redirects.rb +1 -1
  81. data/app/decorators/alchemy/element_editor.rb +2 -2
  82. data/app/helpers/alchemy/admin/base_helper.rb +8 -60
  83. data/app/helpers/alchemy/admin/elements_helper.rb +1 -1
  84. data/app/helpers/alchemy/admin/ingredients_helper.rb +1 -1
  85. data/app/helpers/alchemy/base_helper.rb +9 -91
  86. data/app/helpers/alchemy/elements_helper.rb +3 -3
  87. data/app/helpers/alchemy/pages_helper.rb +16 -9
  88. data/app/javascript/alchemy_admin/components/attachment_select.js +24 -0
  89. data/app/javascript/alchemy_admin/components/button.js +3 -0
  90. data/app/javascript/alchemy_admin/components/clipboard_button.js +3 -2
  91. data/app/javascript/alchemy_admin/components/dialog_link.js +10 -7
  92. data/app/javascript/alchemy_admin/components/dom_id_select.js +69 -0
  93. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +42 -0
  94. data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +4 -2
  95. data/app/javascript/alchemy_admin/components/element_editor.js +21 -13
  96. data/app/javascript/alchemy_admin/components/elements_window.js +87 -0
  97. data/app/javascript/alchemy_admin/components/growl.js +13 -0
  98. data/app/javascript/alchemy_admin/components/icon.js +51 -0
  99. data/app/javascript/alchemy_admin/components/index.js +24 -0
  100. data/app/javascript/alchemy_admin/components/ingredient_group.js +6 -0
  101. data/app/javascript/alchemy_admin/components/link_buttons/link_button.js +21 -11
  102. data/app/javascript/alchemy_admin/components/link_buttons/unlink_button.js +2 -1
  103. data/app/javascript/alchemy_admin/components/link_buttons.js +1 -0
  104. data/app/javascript/alchemy_admin/components/list_filter.js +68 -0
  105. data/app/javascript/alchemy_admin/components/message.js +69 -0
  106. data/app/javascript/alchemy_admin/components/node_select.js +1 -1
  107. data/app/javascript/alchemy_admin/components/overlay.js +6 -6
  108. data/app/javascript/alchemy_admin/components/page_select.js +3 -7
  109. data/app/javascript/alchemy_admin/components/preview_window.js +121 -0
  110. data/app/javascript/alchemy_admin/components/remote_select.js +4 -1
  111. data/app/javascript/alchemy_admin/components/select.js +37 -1
  112. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +57 -0
  113. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +4 -3
  114. data/app/javascript/alchemy_admin/components/uploader/progress.js +1 -1
  115. data/app/javascript/alchemy_admin/confirm_dialog.js +133 -0
  116. data/app/javascript/alchemy_admin/dirty.js +19 -14
  117. data/app/javascript/alchemy_admin/fixed_elements.js +24 -0
  118. data/app/javascript/alchemy_admin/growler.js +15 -0
  119. data/app/javascript/alchemy_admin/gui.js +2 -4
  120. data/app/javascript/alchemy_admin/hotkeys.js +60 -0
  121. data/app/javascript/alchemy_admin/image_loader.js +2 -2
  122. data/app/javascript/alchemy_admin/ingredient_anchor_link.js +2 -3
  123. data/app/javascript/alchemy_admin/initializer.js +1 -8
  124. data/app/javascript/alchemy_admin/link_dialog.js +131 -0
  125. data/app/javascript/alchemy_admin/locales/en.js +3 -0
  126. data/app/javascript/alchemy_admin/node_tree.js +4 -3
  127. data/app/javascript/alchemy_admin/page_sorter.js +23 -14
  128. data/app/javascript/alchemy_admin/picture_editors.js +3 -2
  129. data/app/javascript/alchemy_admin/shoelace_theme.js +60 -0
  130. data/app/javascript/alchemy_admin/sitemap.js +9 -3
  131. data/app/javascript/alchemy_admin/sortable_elements.js +4 -6
  132. data/app/javascript/alchemy_admin.js +18 -42
  133. data/app/models/alchemy/current.rb +26 -0
  134. data/app/models/alchemy/element.rb +1 -1
  135. data/app/models/alchemy/ingredients/headline.rb +8 -1
  136. data/app/models/alchemy/ingredients/picture.rb +6 -0
  137. data/app/models/alchemy/language.rb +8 -6
  138. data/app/models/alchemy/node.rb +2 -2
  139. data/app/models/alchemy/page/page_elements.rb +8 -8
  140. data/app/models/alchemy/page/page_layouts.rb +3 -3
  141. data/app/models/alchemy/page/page_natures.rb +13 -9
  142. data/app/models/alchemy/page/page_scopes.rb +2 -2
  143. data/app/models/alchemy/page/publisher.rb +1 -0
  144. data/app/models/alchemy/page.rb +13 -28
  145. data/app/models/alchemy/picture.rb +8 -0
  146. data/app/models/alchemy/picture_description.rb +8 -0
  147. data/app/models/alchemy/picture_variant.rb +1 -1
  148. data/app/models/alchemy/site.rb +10 -7
  149. data/app/serializers/alchemy/attachment_serializer.rb +8 -0
  150. data/app/serializers/alchemy/page_node_serializer.rb +9 -0
  151. data/app/views/alchemy/_menubar.html.erb +1 -1
  152. data/app/views/alchemy/_preview_mode_code.html.erb +1 -1
  153. data/app/views/alchemy/admin/attachments/_tag_list.html.erb +2 -2
  154. data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -1
  155. data/app/views/alchemy/admin/attachments/edit.html.erb +3 -4
  156. data/app/views/alchemy/admin/clipboard/clear.js.erb +1 -1
  157. data/app/views/alchemy/admin/clipboard/index.html.erb +1 -1
  158. data/app/views/alchemy/admin/clipboard/insert.js.erb +1 -1
  159. data/app/views/alchemy/admin/clipboard/remove.js.erb +1 -1
  160. data/app/views/alchemy/admin/dashboard/_locked_pages.html.erb +1 -1
  161. data/app/views/alchemy/admin/dashboard/_sites.html.erb +1 -1
  162. data/app/views/alchemy/admin/dashboard/help.html.erb +48 -12
  163. data/app/views/alchemy/admin/dashboard/index.html.erb +1 -1
  164. data/app/views/alchemy/admin/dashboard/info.html.erb +5 -8
  165. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +1 -1
  166. data/app/views/alchemy/admin/elements/_element.html.erb +5 -5
  167. data/app/views/alchemy/admin/elements/_footer.html.erb +1 -1
  168. data/app/views/alchemy/admin/elements/_header.html.erb +6 -2
  169. data/app/views/alchemy/admin/elements/_toolbar.html.erb +8 -6
  170. data/app/views/alchemy/admin/elements/create.js.erb +0 -5
  171. data/app/views/alchemy/admin/elements/index.html.erb +70 -34
  172. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +1 -2
  173. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +3 -5
  174. data/app/views/alchemy/admin/languages/_language.html.erb +1 -1
  175. data/app/views/alchemy/admin/languages/index.html.erb +2 -2
  176. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +18 -18
  177. data/app/views/alchemy/admin/layoutpages/edit.html.erb +3 -4
  178. data/app/views/alchemy/admin/layoutpages/index.html.erb +2 -2
  179. data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +10 -11
  180. data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +15 -17
  181. data/app/views/alchemy/admin/legacy_page_urls/_table.html.erb +16 -0
  182. data/app/views/alchemy/admin/legacy_page_urls/_update.turbo_stream.erb +12 -0
  183. data/app/views/alchemy/admin/legacy_page_urls/create.turbo_stream.erb +8 -0
  184. data/app/views/alchemy/admin/legacy_page_urls/destroy.turbo_stream.erb +1 -0
  185. data/app/views/alchemy/admin/legacy_page_urls/edit.html.erb +27 -0
  186. data/app/views/alchemy/admin/legacy_page_urls/show.html.erb +1 -0
  187. data/app/views/alchemy/admin/legacy_page_urls/update.turbo_stream.erb +1 -0
  188. data/app/views/alchemy/admin/nodes/_form.html.erb +12 -11
  189. data/app/views/alchemy/admin/nodes/_label.html.erb +1 -0
  190. data/app/views/alchemy/admin/nodes/_node.html.erb +19 -19
  191. data/app/views/alchemy/admin/nodes/_page_nodes.html.erb +48 -0
  192. data/app/views/alchemy/admin/nodes/_update.turbo_stream.erb +9 -0
  193. data/app/views/alchemy/admin/nodes/create.turbo_stream.erb +1 -0
  194. data/app/views/alchemy/admin/nodes/destroy.turbo_stream.erb +1 -0
  195. data/app/views/alchemy/admin/nodes/index.html.erb +3 -3
  196. data/app/views/alchemy/admin/pages/_form.html.erb +3 -4
  197. data/app/views/alchemy/admin/pages/_legacy_urls.html.erb +4 -15
  198. data/app/views/alchemy/admin/pages/_page.html.erb +39 -39
  199. data/app/views/alchemy/admin/pages/_table_row.html.erb +3 -3
  200. data/app/views/alchemy/admin/pages/_toolbar.html.erb +2 -2
  201. data/app/views/alchemy/admin/pages/configure.html.erb +6 -0
  202. data/app/views/alchemy/admin/pages/edit.html.erb +15 -62
  203. data/app/views/alchemy/admin/pages/unlock.js.erb +2 -2
  204. data/app/views/alchemy/admin/partials/_autocomplete_tag_list.html.erb +3 -1
  205. data/app/views/alchemy/admin/partials/_flash_notices.html.erb +4 -2
  206. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +1 -1
  207. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +5 -2
  208. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -2
  209. data/app/views/alchemy/admin/partials/_search_form.html.erb +2 -2
  210. data/app/views/alchemy/admin/partials/_site_select.html.erb +1 -1
  211. data/app/views/alchemy/admin/picture_descriptions/_form.html.erb +11 -0
  212. data/app/views/alchemy/admin/picture_descriptions/edit.html.erb +6 -0
  213. data/app/views/alchemy/admin/pictures/_form.html.erb +4 -3
  214. data/app/views/alchemy/admin/pictures/_infos.html.erb +1 -1
  215. data/app/views/alchemy/admin/pictures/_picture_description_field.html.erb +29 -0
  216. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +2 -2
  217. data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +0 -2
  218. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +3 -3
  219. data/app/views/alchemy/admin/pictures/show.html.erb +3 -3
  220. data/app/views/alchemy/admin/resources/_form.html.erb +3 -4
  221. data/app/views/alchemy/admin/resources/_tag_list.html.erb +2 -2
  222. data/app/views/alchemy/admin/resources/index.html.erb +2 -2
  223. data/app/views/alchemy/admin/sites/index.html.erb +1 -1
  224. data/app/views/alchemy/admin/styleguide/index.html.erb +29 -24
  225. data/app/views/alchemy/admin/tags/_tag.html.erb +1 -1
  226. data/app/views/alchemy/admin/tags/edit.html.erb +1 -1
  227. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  228. data/app/views/alchemy/base/500.html.erb +7 -18
  229. data/app/views/alchemy/base/error_notice.html.erb +3 -1
  230. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +1 -1
  231. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +1 -1
  232. data/app/views/alchemy/ingredients/_headline_editor.html.erb +13 -8
  233. data/app/views/alchemy/ingredients/_picture_editor.html.erb +1 -1
  234. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +1 -1
  235. data/app/views/alchemy/language_links/_language.html.erb +1 -1
  236. data/app/views/kaminari/alchemy/_first_page.html.erb +2 -2
  237. data/app/views/kaminari/alchemy/_gap.html.erb +1 -1
  238. data/app/views/kaminari/alchemy/_last_page.html.erb +2 -2
  239. data/app/views/kaminari/alchemy/_next_page.html.erb +2 -2
  240. data/app/views/kaminari/alchemy/_prev_page.html.erb +2 -2
  241. data/app/views/layouts/alchemy/admin.html.erb +2 -1
  242. data/bundles/shoelace.js +3 -1
  243. data/config/locales/alchemy.en.yml +16 -3
  244. data/config/routes.rb +3 -1
  245. data/db/migrate/20240314105244_create_alchemy_picture_descriptions.rb +11 -0
  246. data/lib/alchemy/configuration_methods.rb +1 -1
  247. data/lib/alchemy/controller_actions.rb +3 -3
  248. data/lib/alchemy/element_definition.rb +10 -6
  249. data/lib/alchemy/engine.rb +19 -2
  250. data/lib/alchemy/page_layout.rb +10 -6
  251. data/lib/alchemy/permissions.rb +3 -2
  252. data/lib/alchemy/seeder.rb +2 -2
  253. data/lib/alchemy/test_support/capybara_helpers.rb +4 -0
  254. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  255. data/lib/alchemy/test_support/shared_contexts.rb +8 -0
  256. data/lib/alchemy/tinymce.rb +2 -1
  257. data/lib/alchemy/version.rb +1 -1
  258. data/lib/alchemy.rb +36 -0
  259. data/lib/alchemy_cms.rb +0 -1
  260. data/lib/generators/alchemy/menus/templates/node.html.erb +2 -2
  261. data/lib/generators/alchemy/menus/templates/node.html.haml +2 -2
  262. data/lib/generators/alchemy/menus/templates/node.html.slim +2 -2
  263. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  264. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  265. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  266. data/lib/tasks/alchemy/sitemap.rake +97 -0
  267. data/package.json +8 -8
  268. data/vendor/assets/fonts/remixicon.symbol.svg +11 -0
  269. data/vendor/javascript/shoelace.min.js +333 -118
  270. data/vendor/javascript/sortable.min.js +1 -1
  271. data/vendor/javascript/tinymce.min.js +1 -1
  272. data/vendor/javascript/ungap-custom-elements.min.js +1 -1
  273. metadata +61 -54
  274. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +0 -85
  275. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +0 -107
  276. data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +0 -66
  277. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +0 -45
  278. data/app/assets/javascripts/alchemy/alchemy.growler.js.coffee +0 -24
  279. data/app/assets/javascripts/alchemy/alchemy.hotkeys.js.coffee +0 -49
  280. data/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee +0 -0
  281. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +0 -230
  282. data/app/assets/javascripts/alchemy/alchemy.list_filter.js.coffee +0 -49
  283. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +0 -82
  284. data/app/assets/javascripts/alchemy/alchemy.string_extension.js.coffee +0 -11
  285. data/app/assets/javascripts/alchemy/templates/page.hbs +0 -19
  286. data/app/javascript/alchemy_admin/tags_autocomplete.js +0 -46
  287. data/app/models/alchemy/tree_node.rb +0 -7
  288. data/app/views/alchemy/admin/elements/destroy.js.erb +0 -8
  289. data/app/views/alchemy/admin/legacy_page_urls/_form.html.erb +0 -5
  290. data/app/views/alchemy/admin/legacy_page_urls/create.js.erb +0 -9
  291. data/app/views/alchemy/admin/legacy_page_urls/destroy.js.erb +0 -6
  292. data/app/views/alchemy/admin/legacy_page_urls/update.js.erb +0 -2
  293. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +0 -22
  294. data/app/views/alchemy/admin/pages/_external_link.html.erb +0 -31
  295. data/app/views/alchemy/admin/pages/_file_link.html.erb +0 -31
  296. data/app/views/alchemy/admin/pages/_internal_link.html.erb +0 -35
  297. data/app/views/alchemy/admin/pages/link.html.erb +0 -26
  298. data/app/views/alchemy/admin/partials/_flash.html.erb +0 -4
  299. data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +0 -29
  300. data/vendor/assets/fonts/remixicon.eot +0 -0
  301. data/vendor/assets/fonts/remixicon.svg +0 -7816
  302. data/vendor/assets/fonts/remixicon.ttf +0 -0
  303. data/vendor/assets/fonts/remixicon.woff +0 -0
  304. data/vendor/assets/fonts/remixicon.woff2 +0 -0
  305. data/vendor/assets/stylesheets/remixicon.scss +0 -10480
@@ -0,0 +1,133 @@
1
+ import { growl } from "alchemy_admin/growler"
2
+ import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
3
+ import { createHtmlElement } from "alchemy_admin/utils/dom_helpers"
4
+ import { translate } from "alchemy_admin/i18n"
5
+
6
+ class ConfirmDialog {
7
+ constructor(message, options = {}) {
8
+ const DEFAULTS = {
9
+ size: "300x100",
10
+ title: translate("Please confirm"),
11
+ ok_label: translate("Yes"),
12
+ cancel_label: translate("No"),
13
+ on_ok() {}
14
+ }
15
+
16
+ options = { ...DEFAULTS, ...options }
17
+
18
+ this.message = message
19
+ this.options = options
20
+ this.#build()
21
+ this.#bindEvents()
22
+ }
23
+
24
+ open() {
25
+ requestAnimationFrame(() => {
26
+ this.dialog.show()
27
+ })
28
+ }
29
+
30
+ #build() {
31
+ const width = this.options.size.split("x")[0]
32
+ this.dialog = createHtmlElement(`
33
+ <sl-dialog label="${this.options.title}" style="--width: ${width}px">
34
+ ${this.message}
35
+ <button slot="footer" type="reset" class="secondary mx-1 my-0" autofocus>
36
+ ${this.options.cancel_label}
37
+ </button>
38
+ <button slot="footer" type="submit" class="mx-1 my-0">
39
+ ${this.options.ok_label}
40
+ </button>
41
+ </sl-dialog>
42
+ `)
43
+ document.body.append(this.dialog)
44
+ }
45
+
46
+ #bindEvents() {
47
+ this.cancelButton.addEventListener("click", (evt) => {
48
+ evt.preventDefault()
49
+ this.dialog.hide()
50
+ })
51
+ this.okButton.addEventListener("click", (evt) => {
52
+ evt.preventDefault()
53
+ this.options.on_ok()
54
+ this.dialog.hide()
55
+ })
56
+ // Prevent the dialog from closing when the user clicks on the overlay
57
+ this.dialog.addEventListener("sl-request-close", (event) => {
58
+ if (event.detail.source === "overlay") {
59
+ event.preventDefault()
60
+ }
61
+ })
62
+ // Remove the dialog from the DOM after it has been hidden
63
+ this.dialog.addEventListener("sl-after-hide", () => {
64
+ this.dialog.remove()
65
+ })
66
+ }
67
+
68
+ get cancelButton() {
69
+ return this.dialog.querySelector("button[type=reset]")
70
+ }
71
+
72
+ get okButton() {
73
+ return this.dialog.querySelector("button[type=submit]")
74
+ }
75
+ }
76
+
77
+ // Opens a confirm dialog
78
+ //
79
+ // Arguments:
80
+ //
81
+ // message - The message that will be displayed to the user (String)
82
+ //
83
+ // Options:
84
+ //
85
+ // title: '' - The title of the overlay window (String)
86
+ // cancel_label: '' - The label of the cancel button (String)
87
+ // ok_label: '' - The label of the ok button (String)
88
+ // on_ok: null - The function to invoke on confirmation (Function)
89
+ //
90
+ export function openConfirmDialog(message, options = {}) {
91
+ const dialog = new ConfirmDialog(message, options)
92
+ dialog.open()
93
+ return dialog
94
+ }
95
+
96
+ // Opens a confirm to delete dialog
97
+ //
98
+ // Arguments:
99
+ //
100
+ // url - The url to the server delete action. Uses DELETE as HTTP method. (String)
101
+ // opts - An options object (Object)
102
+ //
103
+ // Options:
104
+ //
105
+ // title: '' - The title of the confirmation window (String)
106
+ // message: '' - The message that will be displayed to the user (String)
107
+ // ok_label: '' - The label for the ok button (String)
108
+ // cancel_label: '' - The label for the cancel button (String)
109
+ //
110
+ export function confirmToDeleteDialog(url, opts = {}) {
111
+ return new Promise((resolve, reject) => {
112
+ const options = {
113
+ on_ok() {
114
+ pleaseWaitOverlay()
115
+ $.ajax({
116
+ url,
117
+ type: "DELETE",
118
+ error(xhr, _status, error) {
119
+ const type = xhr.status === 403 ? "warning" : "error"
120
+ growl(xhr.responseText || error, type)
121
+ reject(error)
122
+ },
123
+ complete(response) {
124
+ pleaseWaitOverlay(false)
125
+ resolve(response)
126
+ }
127
+ })
128
+ }
129
+ }
130
+
131
+ openConfirmDialog(opts.message, { ...options, ...opts })
132
+ })
133
+ }
@@ -1,6 +1,6 @@
1
- function isPageDirty() {
2
- return $("#element_area").find("alchemy-element-editor.dirty").length > 0
3
- }
1
+ import { openConfirmDialog } from "alchemy_admin/confirm_dialog"
2
+ import { translate } from "alchemy_admin/i18n"
3
+ import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
4
4
 
5
5
  function checkPageDirtyness(element) {
6
6
  let callback = () => {}
@@ -13,18 +13,21 @@ function checkPageDirtyness(element) {
13
13
  $form.append($(element).find("input"))
14
14
  $form.appendTo("body")
15
15
 
16
- Alchemy.pleaseWaitOverlay()
17
- $form.submit()
16
+ pleaseWaitOverlay()
17
+ $form.trigger("submit")
18
18
  }
19
19
  } else if ($(element).is("a")) {
20
20
  callback = () => Turbo.visit(element.pathname)
21
21
  }
22
22
 
23
- if (isPageDirty()) {
24
- Alchemy.openConfirmDialog(Alchemy.t("page_dirty_notice"), {
25
- title: Alchemy.t("warning"),
26
- ok_label: Alchemy.t("ok"),
27
- cancel_label: Alchemy.t("cancel"),
23
+ const isPageDirty =
24
+ document.querySelectorAll("alchemy-element-editor.dirty").length > 0
25
+
26
+ if (isPageDirty) {
27
+ openConfirmDialog(translate("page_dirty_notice"), {
28
+ title: translate("warning"),
29
+ ok_label: translate("ok"),
30
+ cancel_label: translate("cancel"),
28
31
  on_ok: function () {
29
32
  window.onbeforeunload = void 0
30
33
  callback()
@@ -36,10 +39,12 @@ function checkPageDirtyness(element) {
36
39
  }
37
40
 
38
41
  function PageLeaveObserver() {
39
- $("#main_navi a").on("click", function (event) {
40
- if (!checkPageDirtyness(event.currentTarget)) {
41
- event.preventDefault()
42
- }
42
+ document.querySelectorAll("#main_navi a").forEach((element) => {
43
+ element.addEventListener("click", (event) => {
44
+ if (!checkPageDirtyness(event.currentTarget)) {
45
+ event.preventDefault()
46
+ }
47
+ })
43
48
  })
44
49
  }
45
50
 
@@ -0,0 +1,24 @@
1
+ // Creates a fixed element tab.
2
+ export function createTab(element_id, label) {
3
+ const fixed_elements = document.getElementById("fixed-elements")
4
+ const panel_name = `fixed-element-${element_id}`
5
+
6
+ const tab = `<sl-tab slot="nav" panel="${panel_name}">${label}</sl-tab>`
7
+ const panel = `<sl-tab-panel name="${panel_name}" style="--padding: 0" />`
8
+
9
+ fixed_elements.innerHTML += tab + panel
10
+
11
+ window.requestAnimationFrame(function () {
12
+ fixed_elements.show(panel_name)
13
+ })
14
+ }
15
+
16
+ export function removeTab(element_id) {
17
+ const fixed_elements = document.getElementById("fixed-elements")
18
+ const panel_name = `fixed-element-${element_id}`
19
+
20
+ fixed_elements.querySelector(`sl-tab[panel="${panel_name}"]`).remove()
21
+ fixed_elements.querySelector(`sl-tab-panel[name="${panel_name}"]`).remove()
22
+
23
+ fixed_elements.show("main-content-elements")
24
+ }
@@ -0,0 +1,15 @@
1
+ import { createHtmlElement } from "alchemy_admin/utils/dom_helpers"
2
+
3
+ function build(message, flashType) {
4
+ const flashNotices = document.getElementById("flash_notices")
5
+ const flashMessage = createHtmlElement(`
6
+ <alchemy-message type="${flashType}" dismissable>
7
+ ${message}
8
+ </alchemy-message>
9
+ `)
10
+ flashNotices.append(flashMessage)
11
+ }
12
+
13
+ export function growl(message, style = "notice") {
14
+ build(message, style)
15
+ }
@@ -1,12 +1,10 @@
1
- import TagsAutocomplete from "alchemy_admin/tags_autocomplete"
1
+ import Hotkeys from "alchemy_admin/hotkeys"
2
2
 
3
3
  function init(scope) {
4
4
  if (!scope) {
5
5
  Alchemy.watchForDialogs()
6
6
  }
7
- Alchemy.Hotkeys(scope)
8
- Alchemy.ListFilter(scope)
9
- TagsAutocomplete(scope)
7
+ Hotkeys(scope)
10
8
  }
11
9
 
12
10
  export default {
@@ -0,0 +1,60 @@
1
+ import "keymaster"
2
+
3
+ const bindedHotkeys = []
4
+
5
+ function showHelp(evt) {
6
+ if (
7
+ !$(evt.target).is("input, textarea") &&
8
+ String.fromCharCode(evt.which) === "?"
9
+ ) {
10
+ Alchemy.openDialog("/admin/help", {
11
+ title: Alchemy.t("help"),
12
+ size: "400x492"
13
+ })
14
+ return false
15
+ } else {
16
+ return true
17
+ }
18
+ }
19
+
20
+ export default function (scope = document) {
21
+ // The scope can be a jQuery object because we still use jQuery in Alchemy.Dialog.
22
+ if (scope instanceof jQuery) {
23
+ scope = scope[0]
24
+ }
25
+
26
+ // Unbind all previously registered hotkeys if we are not inside a dialog.
27
+ if (scope === document) {
28
+ document.removeEventListener("keypress", showHelp)
29
+ document.addEventListener("keypress", showHelp)
30
+ bindedHotkeys.forEach((hotkey) => key.unbind(hotkey))
31
+ }
32
+
33
+ // Binds keyboard shortcuts to search fields.
34
+ const search_fields = scope.querySelectorAll(".search_input_field")
35
+ const search_fields_clear = scope.querySelectorAll(
36
+ ".search_field_clear, .js_filter_field_clear"
37
+ )
38
+ key("alt+f", function () {
39
+ key.setScope("search")
40
+ search_fields.forEach((el) => el.focus({ focusVisible: true }))
41
+ return false
42
+ })
43
+ bindedHotkeys.push("alt+f")
44
+ key("esc", "search", function () {
45
+ search_fields_clear.forEach((el) => el.click())
46
+ search_fields.forEach((el) => el.blur())
47
+ })
48
+ bindedHotkeys.push("esc")
49
+
50
+ // Binds click events to buttons with hotkeys.
51
+ //
52
+ // Simply add a data-alchemy-hotkey attribute to your link.
53
+ // If a hotkey is triggered by user, the click event of the element gets triggerd.
54
+ //
55
+ scope.querySelectorAll("[data-alchemy-hotkey]").forEach(function (el) {
56
+ const hotkey = el.dataset.alchemyHotkey
57
+ key(hotkey, () => el.click())
58
+ bindedHotkeys.push(hotkey)
59
+ })
60
+ }
@@ -38,9 +38,9 @@ export default class ImageLoader {
38
38
  }
39
39
 
40
40
  onError(evt) {
41
- const message = `Could not load "${this.image.src}"`
41
+ const message = `Could not load ${this.image.src}`
42
42
  this.spinner.stop()
43
- this.parent.innerHTML = `<span class="icon error ri-alert-line" title="${message}" />`
43
+ this.parent.innerHTML = `<alchemy-icon name="alert" class="error" title="${message}" />`
44
44
  console.error(message, evt)
45
45
  this.unbind()
46
46
  }
@@ -5,10 +5,9 @@ export default class IngredientAnchorLink {
5
5
  )
6
6
  if (ingredientEditor) {
7
7
  const icon = ingredientEditor.querySelector(
8
- ".edit-ingredient-anchor-link > a > .icon"
8
+ ".edit-ingredient-anchor-link alchemy-icon"
9
9
  )
10
- icon?.classList.toggle("ri-bookmark-fill", active)
11
- icon?.classList.toggle("ri-bookmark-line", !active)
10
+ icon.setAttribute("icon-style", active ? "fill" : "line")
12
11
  }
13
12
  }
14
13
  }
@@ -18,18 +18,13 @@ function selectHandler(selectId, parameterName, forcedReload = false) {
18
18
  })
19
19
  }
20
20
 
21
- function Initialize() {
21
+ export default function Initializer() {
22
22
  // We obviously have javascript enabled.
23
23
  $("html").removeClass("no-js")
24
24
 
25
25
  // Initialize the GUI.
26
26
  Alchemy.GUI.init()
27
27
 
28
- // Fade all growl notifications.
29
- if ($("#flash_notices").length > 0) {
30
- Alchemy.Growler.fade()
31
- }
32
-
33
28
  // Add observer for please wait overlay.
34
29
  $(".please_wait")
35
30
  .not("*[data-alchemy-confirm]")
@@ -61,5 +56,3 @@ function Initialize() {
61
56
  )
62
57
  }
63
58
  }
64
-
65
- export default Initialize
@@ -0,0 +1,131 @@
1
+ import { translate } from "alchemy_admin/i18n"
2
+
3
+ // Represents the link Dialog that appears, if a user clicks the link buttons
4
+ // in TinyMCE or on an Ingredient that has links enabled (e.g. Picture)
5
+ //
6
+ export class LinkDialog extends Alchemy.Dialog {
7
+ #onCreateLink
8
+
9
+ constructor(link) {
10
+ const url = new URL(Alchemy.routes.link_admin_pages_path, window.location)
11
+ const parameterMapping = {
12
+ url: link.url,
13
+ selected_tab: link.type,
14
+ link_title: link.title,
15
+ link_target: link.target
16
+ }
17
+
18
+ // searchParams.set would also add undefined values
19
+ Object.keys(parameterMapping).forEach((key) => {
20
+ if (parameterMapping[key]) {
21
+ url.searchParams.set(key, parameterMapping[key])
22
+ }
23
+ })
24
+
25
+ super(url.href, {
26
+ size: "600x320",
27
+ title: translate("Link")
28
+ })
29
+ }
30
+
31
+ /**
32
+ * Called from Dialog class after the url was loaded
33
+ */
34
+ replace(data) {
35
+ // let Dialog class handle the content replacement
36
+ super.replace(data)
37
+ this.#attachEvents()
38
+ }
39
+
40
+ /**
41
+ * make the open method a promise
42
+ * maybe in a future version the whole Dialog will respond with a promise result if the dialog is closing
43
+ * @returns {Promise<unknown>}
44
+ */
45
+ open() {
46
+ super.open()
47
+ return new Promise((resolve) => (this.#onCreateLink = resolve))
48
+ }
49
+
50
+ /**
51
+ * Attaches click events to forms in the link dialog.
52
+ */
53
+ #attachEvents() {
54
+ // enable the dom selection in internal link tab
55
+ const internalForm = document.querySelector(
56
+ '[data-link-form-type="internal"]'
57
+ )
58
+ const attachmentSelect = document.querySelector(
59
+ '[data-link-form-type="file"] alchemy-attachment-select'
60
+ )
61
+
62
+ internalForm.addEventListener("Alchemy.RemoteSelect.Change", (e) => {
63
+ this.#updatePage(e.detail.added)
64
+ })
65
+
66
+ attachmentSelect.addEventListener("Alchemy.RemoteSelect.Change", (e) => {
67
+ const attachment = e.detail.added
68
+ document.getElementById("file_link").value = attachment
69
+ ? attachment.url
70
+ : ""
71
+ })
72
+
73
+ document.querySelectorAll("[data-link-form-type]").forEach((form) => {
74
+ form.addEventListener("submit", (e) => {
75
+ e.preventDefault()
76
+ this.#submitForm(e.target.dataset.linkFormType)
77
+ })
78
+ })
79
+ }
80
+
81
+ /**
82
+ * update page select and set anchor select
83
+ * @param page
84
+ */
85
+ #updatePage(page = null) {
86
+ const internalLink = document.getElementById("internal_link")
87
+ const domIdSelect = document.querySelector(
88
+ '[data-link-form-type="internal"] alchemy-dom-id-api-select'
89
+ )
90
+
91
+ internalLink.value = page ? page.url_path : ""
92
+ domIdSelect.page = page ? page.id : undefined
93
+ }
94
+
95
+ /**
96
+ * submit the form itself
97
+ * @param linkType
98
+ */
99
+ #submitForm(linkType) {
100
+ const elementAnchor = document.getElementById("element_anchor")
101
+ let url = document.getElementById(`${linkType}_link`).value
102
+
103
+ if (linkType === "internal" && elementAnchor.value !== "") {
104
+ // remove possible fragments on the url and attach the fragment (which contains the #)
105
+ url = url.replace(/#\w+$/, "") + elementAnchor.value
106
+ } else if (linkType === "external" && !url.match(Alchemy.link_url_regexp)) {
107
+ // show validation error and prevent link creation
108
+ this.#showValidationError()
109
+ return
110
+ }
111
+
112
+ // Create the link
113
+ this.#onCreateLink({
114
+ url: url.trim(),
115
+ title: document.getElementById(`${linkType}_link_title`).value,
116
+ target: document.getElementById(`${linkType}_link_target`)?.value,
117
+ type: linkType
118
+ })
119
+ this.close()
120
+ }
121
+
122
+ /**
123
+ * Shows validation errors
124
+ */
125
+ #showValidationError() {
126
+ const errors = document.getElementById("errors")
127
+ errors.querySelector("ul").innerHTML =
128
+ `<li>${Alchemy.t("url_validation_failed")}</li>`
129
+ errors.style.display = "block"
130
+ }
131
+ }
@@ -21,6 +21,9 @@ export const en = {
21
21
  "Uploaded bytes exceed file size": "Uploaded bytes exceed file size",
22
22
  "Abort upload": "Abort upload",
23
23
  "Cancel all uploads": "Cancel all uploads",
24
+ None: "None",
25
+ "No anchors found": "No anchors found",
26
+ "Select a page first": "Select a page first",
24
27
  Close: "Close",
25
28
  formats: {
26
29
  datetime: "Y-m-d H:i",
@@ -1,6 +1,7 @@
1
1
  import Sortable from "sortablejs"
2
2
  import { patch } from "alchemy_admin/utils/ajax"
3
3
  import { on } from "alchemy_admin/utils/events"
4
+ import { growl } from "alchemy_admin/growler"
4
5
 
5
6
  function displayNodeFolders() {
6
7
  document.querySelectorAll("li.menu-item").forEach((el) => {
@@ -32,11 +33,11 @@ function onFinishDragging(evt) {
32
33
  patch(url, data)
33
34
  .then(() => {
34
35
  const message = Alchemy.t("Successfully moved menu item")
35
- Alchemy.growl(message)
36
+ growl(message)
36
37
  displayNodeFolders()
37
38
  })
38
39
  .catch((error) => {
39
- Alchemy.growl(error.message || error, "error")
40
+ growl(error.message || error, "error")
40
41
  })
41
42
  }
42
43
 
@@ -56,7 +57,7 @@ function handleNodeFolders() {
56
57
  displayNodeFolders()
57
58
  })
58
59
  .catch((error) => {
59
- Alchemy.growl(error.message || error)
60
+ growl(error.message || error)
60
61
  })
61
62
  })
62
63
  }
@@ -1,7 +1,9 @@
1
1
  import Sortable from "sortablejs"
2
+ import { growl } from "alchemy_admin/growler"
2
3
  import { patch } from "alchemy_admin/utils/ajax"
4
+ import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
3
5
 
4
- function onFinishDragging(evt) {
6
+ function onSort(evt) {
5
7
  const pageId = evt.item.dataset.pageId
6
8
  const url = Alchemy.routes.move_admin_page_path(pageId)
7
9
  const data = {
@@ -9,19 +11,26 @@ function onFinishDragging(evt) {
9
11
  new_position: evt.newIndex
10
12
  }
11
13
 
12
- patch(url, data)
13
- .then(async (response) => {
14
- const pageData = await response.data
15
- const pageEl = document.getElementById(`page_${pageId}`)
16
- const urlPathEl = pageEl.querySelector(".sitemap_url")
14
+ if (evt.target === evt.to) {
15
+ pleaseWaitOverlay(true)
16
+ patch(url, data)
17
+ .then(async (response) => {
18
+ const pageData = await response.data
19
+ const pageEl = document.getElementById(`page_${pageId}`)
20
+ const urlPathEl = pageEl.querySelector(".sitemap_url")
17
21
 
18
- Alchemy.growl(Alchemy.t("Successfully moved page"))
19
- urlPathEl.textContent = pageData.url_path
20
- displayPageFolders()
21
- })
22
- .catch((error) => {
23
- Alchemy.growl(error.message || error, "error")
24
- })
22
+ growl(Alchemy.t("Successfully moved page"))
23
+ urlPathEl.textContent = pageData.url_path
24
+ displayPageFolders()
25
+ })
26
+ .catch((error) => {
27
+ growl(error.message || error, "error")
28
+ Alchemy.currentSitemap.reload()
29
+ })
30
+ .finally(() => {
31
+ pleaseWaitOverlay(false)
32
+ })
33
+ }
25
34
  }
26
35
 
27
36
  export function displayPageFolders() {
@@ -50,7 +59,7 @@ export function createSortables(sortables) {
50
59
  fallbackOnBody: true,
51
60
  swapThreshold: 0.65,
52
61
  handle: ".handle",
53
- onEnd: onFinishDragging
62
+ onSort
54
63
  })
55
64
  })
56
65
  }
@@ -1,10 +1,11 @@
1
1
  import debounce from "alchemy_admin/utils/debounce"
2
2
  import max from "alchemy_admin/utils/max"
3
3
  import { get } from "alchemy_admin/utils/ajax"
4
+ import { growl } from "alchemy_admin/growler"
4
5
  import ImageLoader from "alchemy_admin/image_loader"
5
6
 
6
7
  const UPDATE_DELAY = 125
7
- const IMAGE_PLACEHOLDER = '<i class="icon ri-image-line ri-fw"></i>'
8
+ const IMAGE_PLACEHOLDER = '<alchemy-icon name="image"></alchemy-icon>'
8
9
  const THUMBNAIL_SIZE = "160x120"
9
10
 
10
11
  class PictureEditor {
@@ -77,7 +78,7 @@ class PictureEditor {
77
78
  })
78
79
  .catch((error) => {
79
80
  console.error(error.message || error)
80
- Alchemy.growl(error.message || error, "error")
81
+ growl(error.message || error, "error")
81
82
  })
82
83
  }
83
84
 
@@ -0,0 +1,60 @@
1
+ import { registerIconLibrary, setDefaultAnimation } from "shoelace"
2
+
3
+ // Change the default animation for all tooltips
4
+ setDefaultAnimation("tooltip.show", {
5
+ keyframes: [
6
+ { transform: "translateY(10px)", opacity: "0" },
7
+ { transform: "translateY(0)", opacity: "1" }
8
+ ],
9
+ options: {
10
+ duration: 100
11
+ }
12
+ })
13
+
14
+ setDefaultAnimation("tooltip.hide", {
15
+ keyframes: [
16
+ { transform: "translateY(0)", opacity: "1" },
17
+ { transform: "translateY(10px)", opacity: "0" }
18
+ ],
19
+ options: {
20
+ duration: 100
21
+ }
22
+ })
23
+
24
+ // Change the default animation for all dialogs
25
+ setDefaultAnimation("dialog.show", {
26
+ keyframes: [
27
+ { transform: "scale(0.98)", opacity: "0" },
28
+ { transform: "scale(1)", opacity: "1" }
29
+ ],
30
+ options: {
31
+ duration: 150
32
+ }
33
+ })
34
+
35
+ setDefaultAnimation("dialog.hide", {
36
+ keyframes: [
37
+ { transform: "scale(1)", opacity: "1" },
38
+ { transform: "scale(0.98)", opacity: "0" }
39
+ ],
40
+ options: {
41
+ duration: 150
42
+ }
43
+ })
44
+
45
+ const spriteUrl = document
46
+ .querySelector('meta[name="alchemy-icon-sprite"]')
47
+ .getAttribute("content")
48
+
49
+ const iconMap = {
50
+ "x-lg": "close"
51
+ }
52
+
53
+ const options = {
54
+ resolver: (name) => `${spriteUrl}#ri-${iconMap[name] || name}-line`,
55
+ mutator: (svg) => svg.setAttribute("fill", "currentColor"),
56
+ spriteSheet: true
57
+ }
58
+
59
+ registerIconLibrary("default", options)
60
+ registerIconLibrary("system", options)