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
@@ -0,0 +1,39 @@
1
+ module Alchemy
2
+ module Admin
3
+ class AttachmentSelect < ViewComponent::Base
4
+ delegate :alchemy, to: :helpers
5
+
6
+ def initialize(attachment = nil, url: nil, placeholder: Alchemy.t("Please choose"), query_params: nil)
7
+ @attachment = attachment
8
+ @url = url
9
+ @placeholder = placeholder
10
+ @query_params = query_params
11
+ end
12
+
13
+ def call
14
+ content_tag("alchemy-attachment-select", content, attributes)
15
+ end
16
+
17
+ private
18
+
19
+ def attributes
20
+ options = {
21
+ "allow-clear": true,
22
+ placeholder: @placeholder,
23
+ url: @url || alchemy.api_attachments_path
24
+ }
25
+
26
+ if @query_params
27
+ options[:"query-params"] = @query_params.to_json
28
+ end
29
+
30
+ if @attachment
31
+ selection = ActiveModelSerializers::SerializableResource.new(@attachment)
32
+ options[:selection] = selection.to_json
33
+ end
34
+
35
+ options
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,72 @@
1
+ module Alchemy
2
+ module Admin
3
+ # Render a Remix icon
4
+ #
5
+ class Icon < ViewComponent::Base
6
+ attr_reader :icon_name, :style, :size, :css_class
7
+
8
+ # @param icon_name [String] icon name
9
+ # @option options - style: fill [String] icon style. line or fill. Pass false for no style.
10
+ # @option options - size: nil [String] icon size
11
+ # @option options - class: nil [String] css class
12
+ def initialize(icon_name, options = {})
13
+ @icon_name = icon_name
14
+ @style = options[:style].nil? ? "line" : options[:style]
15
+ @size = options[:size]
16
+ @css_class = options[:class]
17
+ end
18
+
19
+ def call
20
+ content_tag("alchemy-icon", nil, name: ri_icon, size: size, "icon-style": ri_style, class: css_class)
21
+ end
22
+
23
+ private
24
+
25
+ # Returns the Remix icon name for given icon name
26
+ #
27
+ # @return [String] The Remix icon class
28
+ def ri_icon
29
+ case icon_name.to_s
30
+ when "minus", "remove", "delete"
31
+ "delete-bin-2"
32
+ when "plus"
33
+ "add"
34
+ when "copy"
35
+ "file-copy"
36
+ when "download"
37
+ "download-2"
38
+ when "upload"
39
+ "upload-2"
40
+ when "exclamation"
41
+ "alert"
42
+ when "info-circle", "info"
43
+ "information"
44
+ when "times"
45
+ "close"
46
+ when "tag"
47
+ "price-tag-3"
48
+ when "cog"
49
+ "settings-3"
50
+ else
51
+ icon_name
52
+ end
53
+ end
54
+
55
+ # Returns the Remix icon style for given style
56
+ #
57
+ # @return [String] The RemixIcon style
58
+ def ri_style
59
+ return "none" if style == false
60
+
61
+ case style.to_s
62
+ when "solid", "fill"
63
+ "fill"
64
+ when "line", "regular"
65
+ "line"
66
+ else
67
+ style
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module LinkDialog
6
+ class AnchorTab < BaseTab
7
+ def title
8
+ Alchemy.t("link_overlay_tab_label.anchor")
9
+ end
10
+
11
+ def self.panel_name
12
+ :anchor
13
+ end
14
+
15
+ def fields
16
+ [
17
+ dom_id_select,
18
+ title_input
19
+ ]
20
+ end
21
+
22
+ def message
23
+ render_message(:info, content_tag("p", Alchemy.t(:anchor_link_headline)))
24
+ end
25
+
26
+ private
27
+
28
+ def dom_id_select
29
+ label = label_tag("anchor_link", Alchemy.t(:anchor), class: "control-label")
30
+ options = [[Alchemy.t("None"), ""]]
31
+ options += [[@url, @url]] if is_selected? && @url
32
+
33
+ select = select_tag(:anchor_link, options_for_select(options, @url), is: "alchemy-select")
34
+ select_component = content_tag("alchemy-dom-id-preview-select", select)
35
+
36
+ content_tag("div", label + select_component, class: "input select")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module LinkDialog
6
+ class BaseTab < ViewComponent::Base
7
+ include BaseHelper
8
+
9
+ attr_reader :url, :link_title, :link_target
10
+
11
+ erb_template <<~ERB
12
+ <sl-tab slot="nav" panel="overlay_tab_<%= panel_name %>_link"<%= is_selected? ? ' active' : '' %>><%= title %></sl-tab>
13
+ <sl-tab-panel name="overlay_tab_<%= panel_name %>_link">
14
+ <form data-link-form-type="<%= panel_name %>">
15
+ <%= message %>
16
+ <% fields.each do |field| %>
17
+ <%= field %>
18
+ <% end %>
19
+ <div class="submit">
20
+ <%= button_tag(Alchemy.t(:apply)) %>
21
+ </div>
22
+ </form>
23
+ </sl-tab-panel>
24
+ ERB
25
+
26
+ def initialize(url, is_selected: false, link_title: "", link_target: nil)
27
+ @url = url
28
+ @is_selected = is_selected
29
+ @link_title = link_title
30
+ @link_target = link_target
31
+ end
32
+
33
+ def is_selected?
34
+ @is_selected
35
+ end
36
+
37
+ def title
38
+ raise ArgumentError, "The tab needs to have a title"
39
+ end
40
+
41
+ def self.panel_name
42
+ raise ArgumentError, "The tab needs to have a panel name"
43
+ end
44
+
45
+ def fields
46
+ []
47
+ end
48
+
49
+ def message
50
+ ""
51
+ end
52
+
53
+ private
54
+
55
+ def panel_name
56
+ self.class.panel_name
57
+ end
58
+
59
+ def title_input
60
+ input_name = "#{panel_name}_link_title"
61
+ label = label_tag(input_name, Alchemy.t(:link_title), class: "control-label")
62
+ input = text_field_tag input_name, @link_title, class: "link_title"
63
+ content_tag("div", label + input, class: "input text")
64
+ end
65
+
66
+ def target_select
67
+ select_name = "#{panel_name}_link_target"
68
+ label = label_tag(select_name, Alchemy.t("Open Link in"), class: "control-label")
69
+ select = select_tag(select_name, options_for_select(Alchemy::Page.link_target_options, @link_target), class: "link_target")
70
+ content_tag("div", label + select, class: "input select")
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module LinkDialog
6
+ class ExternalTab < BaseTab
7
+ def title
8
+ Alchemy.t("link_overlay_tab_label.external")
9
+ end
10
+
11
+ def self.panel_name
12
+ :external
13
+ end
14
+
15
+ def fields
16
+ [
17
+ url_input,
18
+ title_input,
19
+ target_select
20
+ ]
21
+ end
22
+
23
+ def message
24
+ main_message = content_tag("h3", Alchemy.t(:enter_external_link)) +
25
+ content_tag("p", Alchemy.t(:external_link_notice_1)) +
26
+ content_tag("p", Alchemy.t(:external_link_notice_2))
27
+
28
+ render_message(:info, main_message) +
29
+ content_tag("div", content_tag("ul"), id: "errors", class: "errors")
30
+ end
31
+
32
+ private
33
+
34
+ def url_input
35
+ label = label_tag("external_link", "URL", class: "control-label")
36
+ input = text_field_tag "external_link", is_selected? ? @url : ""
37
+ content_tag("div", label + input, class: "input text")
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module LinkDialog
6
+ class FileTab < BaseTab
7
+ delegate :alchemy, to: :helpers
8
+
9
+ def title
10
+ Alchemy.t("link_overlay_tab_label.file")
11
+ end
12
+
13
+ def self.panel_name
14
+ :file
15
+ end
16
+
17
+ def fields
18
+ [
19
+ attachment_select,
20
+ title_input,
21
+ target_select
22
+ ]
23
+ end
24
+
25
+ def message
26
+ render_message(:info, content_tag("h3", Alchemy.t(:choose_file_to_link)))
27
+ end
28
+
29
+ private
30
+
31
+ def attachment
32
+ id = url&.match(/attachment\/(?<id>\d+)\/download/)&.captures
33
+ @_attachment ||= Alchemy::Attachment.find_by(id: id)
34
+ end
35
+
36
+ def attachment_select
37
+ label = label_tag("file_link", Alchemy.t(:file), class: "control-label")
38
+ input = text_field_tag("file_link", url, id: "file_link")
39
+ select = render Alchemy::Admin::AttachmentSelect.new(attachment).with_content(input)
40
+ content_tag("div", label + select, class: "input select")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module LinkDialog
6
+ class InternalTab < BaseTab
7
+ def title
8
+ Alchemy.t("link_overlay_tab_label.internal")
9
+ end
10
+
11
+ def self.panel_name
12
+ :internal
13
+ end
14
+
15
+ def fields
16
+ [
17
+ page_select,
18
+ dom_id_select,
19
+ title_input,
20
+ target_select
21
+ ]
22
+ end
23
+
24
+ def message
25
+ main_message = content_tag("h3", Alchemy.t(:internal_link_headline)) +
26
+ content_tag("p", Alchemy.t(:internal_link_page_elements_explanation))
27
+ render_message(:info, main_message)
28
+ end
29
+
30
+ private
31
+
32
+ ##
33
+ # transform url into a URI object
34
+ #
35
+ # encode the uri and parse it again to prevent exceptions if the Url is in the wrong format
36
+ # @return [URI,nil]
37
+ def uri
38
+ @_uri ||= @url ? URI(@url.strip) : nil
39
+ end
40
+
41
+ def page
42
+ @_page ||= uri ? Alchemy::Page.find_by(urlname: uri.path[1..]) : nil
43
+ end
44
+
45
+ def page_select
46
+ label = label_tag("internal_link", Alchemy.t(:page), class: "control-label")
47
+ input = text_field_tag("internal_link", is_selected? ? uri : "", id: "internal_link")
48
+ page_select = render Alchemy::Admin::PageSelect.new(page, allow_clear: true).with_content(input)
49
+ content_tag("div", label + page_select, class: "input select")
50
+ end
51
+
52
+ def dom_id_select
53
+ fragment = "##{uri.fragment}" if uri&.fragment
54
+ label = label_tag("element_anchor", Alchemy.t(:anchor), class: "control-label")
55
+ options = [[page.nil? ? Alchemy.t("Select a page first") : Alchemy.t("None"), ""]]
56
+ options += [[fragment, fragment]] if is_selected? && fragment
57
+
58
+ select = select_tag("element_anchor", options_for_select(options, fragment), is: "alchemy-select", disabled: page.nil?)
59
+ select_component = content_tag("alchemy-dom-id-api-select", select, {page: page&.id})
60
+
61
+ content_tag("div", label + select_component, class: "input select")
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Admin
5
+ module LinkDialog
6
+ class Tabs < ViewComponent::Base
7
+ erb_template <<~ERB
8
+ <sl-tab-group id="overlay_tabs">
9
+ <% tabs.each do |tab| %>
10
+ <%= render tab.new(@url, is_selected: is_selected?(tab), link_title: @link_title, link_target: @link_target) %>
11
+ <% end %>
12
+ </sl-tab-group>
13
+ ERB
14
+
15
+ def initialize(**options)
16
+ options.symbolize_keys!
17
+ @url = options[:url]
18
+ @selected_tab = options[:selected_tab]
19
+ @link_title = options[:link_title]
20
+ @link_target = options[:link_target]
21
+ end
22
+
23
+ def is_selected?(tab)
24
+ @selected_tab&.to_sym == tab.panel_name
25
+ end
26
+
27
+ def tabs
28
+ Alchemy.link_dialog_tabs
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ module Alchemy
2
+ module Admin
3
+ # Creates a live filter for lists of DOM items.
4
+ #
5
+ # The items must have a html +name+ attribute that holds the filterable value.
6
+ #
7
+ # == Example
8
+ #
9
+ # Given a list of items:
10
+ #
11
+ # <%= render Alchemy::Admin::ListFilter.new('#products .product') %>
12
+ #
13
+ # <ul id="products">
14
+ # <li class="product" name="kat litter">Kat Litter</li>
15
+ # <li class="product" name="milk">Milk</li>
16
+ # </ul>
17
+ #
18
+ # @param [String] items_selector - A CSS selector string that represents the items to filter
19
+ # @param [String] name_attribute - A name that represents the attribute on the items to get filtered by
20
+ #
21
+ class ListFilter < ViewComponent::Base
22
+ erb_template <<~ERB
23
+ <alchemy-list-filter items-selector="<%= items_selector %>" name-attribute="<%= name_attribute %>">
24
+ <input type="text" class="js_filter_field" />
25
+ <alchemy-icon name="search"></alchemy-icon>
26
+ <button type="button" class="js_filter_field_clear icon_button">
27
+ <alchemy-icon name="close" size="1x"></alchemy-icon>
28
+ </button>
29
+ </alchemy-list-filter>
30
+ ERB
31
+
32
+ def initialize(items_selector, name_attribute: "name")
33
+ @items_selector = items_selector
34
+ @name_attribute = name_attribute
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :items_selector, :name_attribute
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ module Alchemy
2
+ module Admin
3
+ class Message < ViewComponent::Base
4
+ attr_reader :message, :type, :dismissable
5
+
6
+ erb_template <<~ERB
7
+ <alchemy-message type="<%= type %>"<%= dismissable ? ' dismissable' : '' %>>
8
+ <%= message || content %>
9
+ </alchemy-message>
10
+ ERB
11
+
12
+ def initialize(message = nil, type: :info, dismissable: false)
13
+ @message = message
14
+ @dismissable = dismissable
15
+ @type = type
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module Alchemy
2
+ module Admin
3
+ class TagsAutocomplete < ViewComponent::Base
4
+ delegate :alchemy, to: :helpers
5
+
6
+ def initialize(additional_class: nil)
7
+ @additional_class = additional_class
8
+ end
9
+
10
+ def call
11
+ content_tag("alchemy-tags-autocomplete", content, attributes)
12
+ end
13
+
14
+ private
15
+
16
+ def attributes
17
+ {
18
+ placeholder: Alchemy.t(:search_tag),
19
+ url: alchemy.autocomplete_admin_tags_path,
20
+ class: @additional_class
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,111 @@
1
+ module Alchemy
2
+ module Admin
3
+ # Renders a toolbar button for the Alchemy toolbar
4
+ #
5
+ # == Example
6
+ #
7
+ # <%= render Alchemy::Admin::ToolbarButton.new(
8
+ # url: new_resource_path,
9
+ # icon: :plus,
10
+ # label: 'Create Resource',
11
+ # hotkey: 'alt+n',
12
+ # dialog_options: {
13
+ # title: 'Create Resource',
14
+ # size: "430x400"
15
+ # },
16
+ # if_permitted_to: [:create, resource_model]
17
+ # ) %>
18
+ #
19
+ # @param [String] :url
20
+ # Url for link.
21
+ # @param [String] :icon
22
+ # Icon name. See https://remixicon.com for available icons.
23
+ # @param [String] :label
24
+ # Text for button tooltip.
25
+ # @param [String] :hotkey
26
+ # Keyboard shortcut for this button. I.E +alt-n+
27
+ # @param [Hash] :dialog_options
28
+ # Overlay options. See link_to_dialog helper.
29
+ # @param [Array] :if_permitted_to ([:action, :controller])
30
+ # Check permission for button. Exactly how you defined the permission in your +authorization_rules.rb+. Defaults to controller and action from button url.
31
+ # @param [Boolean] :skip_permission_check (false)
32
+ # Skip the permission check. NOT RECOMMENDED!
33
+ #
34
+ class ToolbarButton < ViewComponent::Base
35
+ erb_template <<-ERB
36
+ <div class="toolbar_button">
37
+ <sl-tooltip content="<%= label %>" placement="<%= tooltip_placement %>">
38
+ <%= link_to(render_icon(icon, style: icon_style), url, {
39
+ class: css_classes,
40
+ "data-dialog-options" => dialog ? dialog_options.to_json : nil,
41
+ "data-alchemy-hotkey" => hotkey,
42
+ :is => dialog ? "alchemy-dialog-link" : nil
43
+ }.merge(link_options)) %>
44
+ </sl-tooltip>
45
+ </div>
46
+ ERB
47
+
48
+ delegate :can?, :link_to, :link_to_dialog, :render_icon, to: :helpers
49
+
50
+ attr_reader :url,
51
+ :icon,
52
+ :label,
53
+ :hotkey,
54
+ :dialog,
55
+ :dialog_options,
56
+ :skip_permission_check,
57
+ :if_permitted_to,
58
+ :active,
59
+ :link_options,
60
+ :icon_style,
61
+ :tooltip_placement
62
+
63
+ def initialize(
64
+ url:,
65
+ icon:,
66
+ label:,
67
+ hotkey: nil,
68
+ title: nil,
69
+ dialog: true,
70
+ dialog_options: {},
71
+ skip_permission_check: false,
72
+ if_permitted_to: [],
73
+ active: false,
74
+ link_options: {},
75
+ icon_style: "line",
76
+ tooltip_placement: "top-start"
77
+ )
78
+ @url = url
79
+ @icon = icon
80
+ @label = label
81
+ @hotkey = hotkey
82
+ @dialog = dialog
83
+ @dialog_options = dialog_options
84
+ @skip_permission_check = skip_permission_check
85
+ @if_permitted_to = if_permitted_to
86
+ @active = active
87
+ @link_options = link_options
88
+ @icon_style = icon_style
89
+ @tooltip_placement = tooltip_placement
90
+ end
91
+
92
+ def render?
93
+ skip_permission_check || can?(*permission_options)
94
+ end
95
+
96
+ private
97
+
98
+ def css_classes = ["icon_button", active && "active"].compact
99
+
100
+ def permission_options = if_permitted_to.presence || permissions_from_url
101
+
102
+ def permissions_from_url
103
+ action_controller = url.gsub(/\A\//, "").split("/")
104
+ [
105
+ action_controller.last.to_sym,
106
+ action_controller[0..action_controller.length - 2].join("_").to_sym
107
+ ]
108
+ end
109
+ end
110
+ end
111
+ end
@@ -11,11 +11,10 @@ module Alchemy
11
11
  end
12
12
 
13
13
  def call
14
- datetime = ingredient.value.in_time_zone(Rails.application.config.time_zone)
15
14
  if date_format == "rfc822"
16
- datetime.to_fs(:rfc822)
15
+ ingredient.value.to_s(:rfc822)
17
16
  else
18
- ::I18n.l(datetime, format: date_format)
17
+ ::I18n.l(ingredient.value, format: date_format)
19
18
  end.html_safe
20
19
  end
21
20
  end
@@ -12,13 +12,7 @@ module Alchemy
12
12
  end
13
13
 
14
14
  def call
15
- link_to(link_text, value, {target: link_target}.merge(html_options)).html_safe
16
- end
17
-
18
- private
19
-
20
- def link_target
21
- (ingredient.link_target == "blank") ? "_blank" : nil
15
+ link_to(link_text, value, {target: ingredient.link_target.presence}.merge(html_options)).html_safe
22
16
  end
23
17
  end
24
18
  end
@@ -65,7 +65,7 @@ module Alchemy
65
65
  def caption
66
66
  return unless show_caption?
67
67
 
68
- @_caption ||= content_tag(:figcaption, ingredient.caption)
68
+ @_caption ||= content_tag(:figcaption, ingredient.caption.html_safe)
69
69
  end
70
70
 
71
71
  def src
@@ -101,7 +101,7 @@ module Alchemy
101
101
  end
102
102
 
103
103
  def alt_text
104
- ingredient.alt_tag.presence || html_options.delete(:alt) || ingredient.picture.name&.humanize
104
+ html_options.delete(:alt) || ingredient.alt_text
105
105
  end
106
106
  end
107
107
  end
@@ -21,8 +21,7 @@ module Alchemy
21
21
  link_to(value, url_for(link), {
22
22
  id: dom_id.presence,
23
23
  title: link_title,
24
- target: ((link_target == "blank") ? "_blank" : nil),
25
- data: {link_target: link_target}
24
+ target: link_target
26
25
  }.merge(html_options))
27
26
  end.html_safe
28
27
  end