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
@@ -31,30 +31,9 @@ module Alchemy
31
31
 
32
32
  private
33
33
 
34
- def safe_redirect_path(path = params[:redirect_to], fallback: admin_path)
35
- if is_safe_redirect_path?(path)
36
- path
37
- elsif is_safe_redirect_path?(fallback)
38
- fallback
39
- else
40
- admin_path
41
- end
42
- end
43
-
44
- def is_safe_redirect_path?(path)
45
- mount_path = alchemy.root_path
46
- path.to_s.match? %r{^#{mount_path}admin/}
47
- end
48
-
49
- def relative_referer_path(referer = request.referer)
50
- return unless referer
51
-
52
- URI(referer).path
53
- end
54
-
55
34
  # Disable layout rendering for xhr requests.
56
35
  def set_layout
57
- request.xhr? ? false : "alchemy/admin"
36
+ (request.xhr? || turbo_frame_request?) ? false : "alchemy/admin"
58
37
  end
59
38
 
60
39
  # Handles exceptions
@@ -127,16 +106,13 @@ module Alchemy
127
106
 
128
107
  # Does redirects for html and js requests
129
108
  #
130
- # Makes sure that the redirect path is safe.
131
- #
132
109
  def do_redirect_to(url_or_path)
133
- redirect_path = safe_redirect_path(url_or_path)
134
110
  respond_to do |format|
135
111
  format.js {
136
- @redirect_url = redirect_path
112
+ @redirect_url = url_or_path
137
113
  render :redirect
138
114
  }
139
- format.html { redirect_to redirect_path }
115
+ format.html { redirect_to url_or_path }
140
116
  end
141
117
  end
142
118
 
@@ -76,9 +76,11 @@ module Alchemy
76
76
  end
77
77
 
78
78
  def destroy
79
- @richtext_ids = @element.richtext_ingredients_ids
80
79
  @element.destroy
81
- @notice = Alchemy.t("Successfully deleted element") % {element: @element.display_name}
80
+
81
+ render json: {
82
+ message: Alchemy.t("Successfully deleted element") % {element: @element.display_name}
83
+ }
82
84
  end
83
85
 
84
86
  def publish
@@ -10,6 +10,8 @@ module Alchemy
10
10
  helper "Alchemy::Admin::Ingredients"
11
11
 
12
12
  def edit
13
+ @language = Alchemy::Language.find_by(id: params[:language_id]) ||
14
+ Alchemy::Current.language
13
15
  end
14
16
 
15
17
  def update
@@ -40,13 +40,13 @@ module Alchemy
40
40
  def switch
41
41
  @language = set_alchemy_language(params[:language_id])
42
42
  session[:alchemy_language_id] = @language.id
43
- do_redirect_to relative_referer_path || alchemy.admin_dashboard_path
43
+ do_redirect_to request.referer || alchemy.admin_dashboard_path
44
44
  end
45
45
 
46
46
  private
47
47
 
48
48
  def load_current_site
49
- @current_site = Alchemy::Site.current
49
+ @current_site = Alchemy::Current.site
50
50
  if @current_site.nil?
51
51
  flash[:warning] = Alchemy.t("Please create a site first.")
52
52
  do_redirect_to admin_sites_path
@@ -17,25 +17,6 @@ module Alchemy
17
17
  def edit
18
18
  @page = Page.find(params[:id])
19
19
  end
20
-
21
- def update
22
- @page = Page.find(params[:id])
23
- if @page.update(page_params)
24
- @notice = Alchemy.t("Page saved", name: @page.name)
25
- render "alchemy/admin/pages/update"
26
- else
27
- render :edit, status: :unprocessable_entity
28
- end
29
- end
30
-
31
- private
32
-
33
- def page_params
34
- params.require(:page).permit(
35
- :name,
36
- :tag_list
37
- )
38
- end
39
20
  end
40
21
  end
41
22
  end
@@ -9,13 +9,17 @@ module Alchemy
9
9
  end
10
10
 
11
11
  def create
12
- @legacy_page_url = @page.legacy_urls.build(legacy_page_url_params)
13
- @legacy_page_url.save
12
+ @legacy_page_url = @page.legacy_urls.create(legacy_page_url_params)
13
+ @message = message_for_resource_action
14
+ end
15
+
16
+ def show
14
17
  end
15
18
 
16
19
  def update
17
20
  @legacy_page_url = LegacyPageUrl.find(params[:id])
18
21
  if @legacy_page_url.update(legacy_page_url_params)
22
+ @message = message_for_resource_action
19
23
  render :update
20
24
  else
21
25
  render :edit
@@ -23,8 +27,8 @@ module Alchemy
23
27
  end
24
28
 
25
29
  def destroy
26
- @legacy_page_url = LegacyPageUrl.find(params[:id])
27
- @legacy_page_url.destroy
30
+ @page.legacy_urls.destroy(@legacy_page_url)
31
+ @message = message_for_resource_action
28
32
  end
29
33
 
30
34
  private
@@ -33,6 +37,10 @@ module Alchemy
33
37
  @page = Page.find(params[:page_id])
34
38
  end
35
39
 
40
+ def load_resource
41
+ @legacy_page_url = LegacyPageUrl.find(params[:id])
42
+ end
43
+
36
44
  def legacy_page_url_params
37
45
  params.require(:legacy_page_url).permit(:urlname)
38
46
  end
@@ -16,6 +16,32 @@ module Alchemy
16
16
  )
17
17
  end
18
18
 
19
+ def create
20
+ if turbo_frame_request?
21
+ @page = Alchemy::Page.find(resource_params[:page_id])
22
+ @node = @page.nodes.build(resource_params)
23
+ if @node.valid?
24
+ @node.save
25
+ flash_notice_for_resource_action(:create)
26
+ else
27
+ flash[:error] = @node.errors.full_messages.join(", ")
28
+ end
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ if turbo_frame_request?
36
+ @node = Alchemy::Node.find(params[:id])
37
+ @page = @node.page
38
+ @page.nodes.destroy(@node)
39
+ flash_notice_for_resource_action(:destroy)
40
+ else
41
+ super
42
+ end
43
+ end
44
+
19
45
  private
20
46
 
21
47
  def resource_params
@@ -7,11 +7,11 @@ module Alchemy
7
7
 
8
8
  helper "alchemy/pages"
9
9
 
10
- before_action :load_resource, except: [:index, :flush, :new, :order, :create, :copy_language_tree, :link]
10
+ before_action :load_resource, except: [:index, :flush, :new, :create, :copy_language_tree, :link]
11
11
 
12
12
  authorize_resource class: Alchemy::Page, except: [:index, :tree]
13
13
 
14
- before_action only: [:index, :tree, :flush, :new, :order, :create, :copy_language_tree] do
14
+ before_action only: [:index, :tree, :flush, :new, :create, :copy_language_tree] do
15
15
  authorize! :index, :alchemy_admin_pages
16
16
  end
17
17
 
@@ -21,7 +21,7 @@ module Alchemy
21
21
  except: [:show]
22
22
 
23
23
  before_action :set_root_page,
24
- only: [:index, :show, :order]
24
+ only: [:index, :show]
25
25
 
26
26
  before_action :set_preview_mode, only: [:show]
27
27
 
@@ -66,7 +66,7 @@ module Alchemy
66
66
  # Used by page preview iframe in Page#edit view.
67
67
  #
68
68
  def show
69
- Page.current_preview = @page
69
+ Current.preview_page = @page
70
70
  # Setting the locale to pages language, so the page content has it's correct translations.
71
71
  ::I18n.locale = @page.language.locale
72
72
  render(layout: Alchemy::Config.get(:admin_page_preview_layout) || "application")
@@ -112,7 +112,9 @@ module Alchemy
112
112
  klass.new(routes: Alchemy::Engine.routes).url_for(@page)
113
113
  ]
114
114
  end
115
+ @preview_url = @preview_urls.first.last
115
116
  @layoutpage = @page.layoutpage?
117
+ Alchemy::Current.language = @page.language
116
118
  end
117
119
 
118
120
  # Set page configuration like page names, meta tags and states.
@@ -163,9 +165,7 @@ module Alchemy
163
165
  end
164
166
 
165
167
  def link
166
- @attachments = Attachment.all.collect { |f|
167
- [f.name, download_attachment_path(id: f.id, name: f.slug)]
168
- }
168
+ render LinkDialog::Tabs.new(**link_dialog_params)
169
169
  end
170
170
 
171
171
  def fold
@@ -189,7 +189,11 @@ module Alchemy
189
189
  end
190
190
 
191
191
  def unlock_redirect_path
192
- safe_redirect_path(fallback: admin_pages_path)
192
+ if params[:redirect_to].to_s.match?(/\A\/admin\/(layout_)?pages/)
193
+ params[:redirect_to]
194
+ else
195
+ admin_pages_path
196
+ end
193
197
  end
194
198
 
195
199
  # Sets the page public and updates the published_at attribute that is used as cache_key
@@ -208,23 +212,6 @@ module Alchemy
208
212
  redirect_to admin_pages_path
209
213
  end
210
214
 
211
- # Receives a JSON object representing a language tree to be ordered
212
- # and updates all pages in that language structure to their correct indexes
213
- def order
214
- neworder = JSON.parse(params[:set])
215
- tree = create_tree(neworder, @page_root)
216
-
217
- Alchemy::Page.transaction do
218
- tree.each do |key, node|
219
- dbitem = Page.find(key)
220
- dbitem.update_node!(node)
221
- end
222
- end
223
-
224
- flash[:notice] = Alchemy.t("Pages order saved")
225
- do_redirect_to admin_pages_path
226
- end
227
-
228
215
  def flush
229
216
  @current_language.pages.flushables.update_all(published_at: Time.current)
230
217
  # We need to ensure, that also all layoutpages get the +published_at+ timestamp set,
@@ -257,60 +244,6 @@ module Alchemy
257
244
  Page.language_root_for(params[:languages][:old_lang_id])
258
245
  end
259
246
 
260
- # Returns the current left index and the aggregated hash of tree nodes indexed by page id visited so far
261
- #
262
- # Visits a batch of children nodes, assigns them the correct ordering indexes and spuns recursively the same
263
- # procedure on their children, if any
264
- #
265
- # @param [Array]
266
- # An array of children nodes to be visited
267
- # @param [Integer]
268
- # The lft attribute that should be given to the first node in the array
269
- # @param [Integer]
270
- # The page id of the parent of this batch of children nodes
271
- # @param [Integer]
272
- # The depth at which these children reside
273
- # @param [Hash]
274
- # A Hash of TreeNode's indexed by their page ids
275
- # @param [String]
276
- # The url for the parent node of these children
277
- # @param [Boolean]
278
- # Whether these children reside in a restricted branch according to their ancestors
279
- #
280
- def visit_nodes(nodes, my_left, parent, depth, tree, url, restricted)
281
- nodes.each do |item|
282
- my_right = my_left + 1
283
- my_restricted = item["restricted"] || restricted
284
- urls = process_url(url, item)
285
-
286
- if item["children"]
287
- my_right, tree = visit_nodes(item["children"], my_left + 1, item["id"], depth + 1, tree, urls[:children_path], my_restricted)
288
- end
289
-
290
- tree[item["id"]] = TreeNode.new(my_left, my_right, parent, depth, urls[:my_urlname], my_restricted)
291
- my_left = my_right + 1
292
- end
293
-
294
- [my_left, tree]
295
- end
296
-
297
- # Returns a Hash of TreeNode's indexed by their page ids
298
- #
299
- # Grabs the array representing a tree structure of pages passed as a parameter,
300
- # visits it and creates a map of TreeNodes indexed by page id featuring Nested Set
301
- # ordering information consisting of the left, right, depth and parent_id indexes as
302
- # well as a node's url and restricted status
303
- #
304
- # @param [Array]
305
- # An Array representing a tree of Alchemy::Page's
306
- # @param [Alchemy::Page]
307
- # The root page for the language being ordered
308
- #
309
- def create_tree(items, rootpage)
310
- _, tree = visit_nodes(items, rootpage.lft + 1, rootpage.id, rootpage.depth + 1, {}, "", rootpage.restricted)
311
- tree
312
- end
313
-
314
247
  # Returns a pair, the path that a given tree node should take, and the path its children should take
315
248
  #
316
249
  # This function will add a node's own slug into their ancestor's path
@@ -351,6 +284,10 @@ module Alchemy
351
284
  params.require(:page).permit(*secure_attributes)
352
285
  end
353
286
 
287
+ def link_dialog_params
288
+ params.permit([:url, :selected_tab, :link_title, :link_target])
289
+ end
290
+
354
291
  def secure_attributes
355
292
  if can?(:create, Alchemy::Page)
356
293
  Page::PERMITTED_ATTRIBUTES + [:language_root, :parent_id, :language_id, :language_code]
@@ -0,0 +1,15 @@
1
+ module Alchemy
2
+ module Admin
3
+ class PictureDescriptionsController < Alchemy::Admin::ResourcesController
4
+ def edit
5
+ @picture_description = @picture.descriptions.find_or_initialize_by(language_id: params[:language_id])
6
+ end
7
+
8
+ private
9
+
10
+ def load_resource
11
+ @picture = Alchemy::Picture.find(params[:picture_id])
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,6 +5,7 @@ module Alchemy
5
5
  class PicturesController < Alchemy::Admin::ResourcesController
6
6
  include UploaderResponses
7
7
  include ArchiveOverlay
8
+ include CurrentLanguage
8
9
 
9
10
  helper "alchemy/admin/tags"
10
11
 
@@ -33,6 +34,9 @@ module Alchemy
33
34
  @previous = filtered_pictures.where("name < ?", @picture.name).last
34
35
  @next = filtered_pictures.where("name > ?", @picture.name).first
35
36
  @assignments = @picture.picture_ingredients.joins(element: :page)
37
+ @picture_description = @picture.descriptions.find_or_initialize_by(
38
+ language_id: Alchemy::Current.language.id
39
+ )
36
40
 
37
41
  render action: "show"
38
42
  end
@@ -193,7 +197,20 @@ module Alchemy
193
197
  end
194
198
 
195
199
  def picture_params
196
- params.require(:picture).permit(:image_file, :upload_hash, :name, :tag_list)
200
+ params.require(:picture).permit(
201
+ :image_file,
202
+ :upload_hash,
203
+ :name,
204
+ {
205
+ descriptions_attributes: [
206
+ :id,
207
+ :text,
208
+ :language_id,
209
+ :picture_id
210
+ ]
211
+ },
212
+ :tag_list
213
+ )
197
214
  end
198
215
 
199
216
  def picture_url_params
@@ -78,7 +78,7 @@ module Alchemy
78
78
  flash[:error] = resource_instance_variable.errors.full_messages.join(", ")
79
79
  end
80
80
  flash_notice_for_resource_action
81
- do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index", only_path: true))
81
+ do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index"))
82
82
  end
83
83
 
84
84
  def resource_handler
@@ -141,23 +141,28 @@ module Alchemy
141
141
  resource_filters.map(&:values).flatten
142
142
  end
143
143
 
144
- # Returns a translated +flash[:notice]+.
145
- # The key should look like "Modelname successfully created|updated|destroyed."
146
- def flash_notice_for_resource_action(action = params[:action])
144
+ # Returns a translated +flash[:notice]+ for current controller action.
145
+ def flash_notice_for_resource_action(action = action_name)
147
146
  return if resource_instance_variable.errors.any?
148
147
 
148
+ flash[:notice] = message_for_resource_action(action)
149
+ end
150
+
151
+ # Returns a translated message for a +flash[:notice]+.
152
+ # The key should look like "Modelname successfully created|updated|destroyed."
153
+ def message_for_resource_action(action = action_name)
149
154
  case action.to_sym
150
155
  when :create
151
- verb = "created"
156
+ verb = Alchemy.t("created", scope: "resources.actions")
152
157
  when :update
153
- verb = "updated"
158
+ verb = Alchemy.t("updated", scope: "resources.actions")
154
159
  when :destroy
155
- verb = "removed"
160
+ verb = Alchemy.t("removed", scope: "resources.actions")
156
161
  end
157
- flash[:notice] = Alchemy.t(
158
- "#{resource_handler.resource_name.classify} successfully #{verb}",
159
- default: Alchemy.t("Successfully #{verb}")
160
- )
162
+ Alchemy.t("%{resource_name} successfully %{action}",
163
+ resource_name: resource_handler.model.model_name.human,
164
+ action: verb,
165
+ default: Alchemy.t("Successfully #{verb}"))
161
166
  end
162
167
 
163
168
  def is_alchemy_module?
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class Api::AttachmentsController < Api::BaseController
5
+ def index
6
+ authorize! :index, Attachment
7
+
8
+ @attachments = Attachment.all
9
+ @attachments = @attachments.ransack(params[:q]).result
10
+
11
+ if params[:page]
12
+ @attachments = @attachments.page(params[:page]).per(params[:per_page])
13
+ end
14
+
15
+ render json: @attachments, adapter: :json, root: "data", meta: meta_data
16
+ end
17
+
18
+ private
19
+
20
+ def meta_data
21
+ {
22
+ total_count: total_count_value,
23
+ per_page: per_page_value,
24
+ page: page_value
25
+ }
26
+ end
27
+
28
+ def total_count_value
29
+ params[:page] ? @attachments.total_count : @attachments.size
30
+ end
31
+
32
+ def per_page_value
33
+ if params[:page]
34
+ (params[:per_page] || Kaminari.config.default_per_page).to_i
35
+ else
36
+ @attachments.size
37
+ end
38
+ end
39
+
40
+ def page_value
41
+ params[:page] ? params[:page].to_i : 1
42
+ end
43
+ end
44
+ end
@@ -49,9 +49,13 @@ module Alchemy
49
49
  def move
50
50
  @page = Page.find(params[:id])
51
51
  authorize! :update, @page
52
- target_parent_page = Page.find(params[:target_parent_id])
53
- @page.move_to_child_with_index(target_parent_page, params[:new_position])
54
- render json: @page, serializer: PageSerializer
52
+ begin
53
+ target_parent_page = Page.find(params[:target_parent_id])
54
+ @page.move_to_child_with_index(target_parent_page, params[:new_position])
55
+ render json: @page, serializer: PageNodeSerializer
56
+ rescue => err
57
+ render json: {message: err.message}, status: 422
58
+ end
55
59
  end
56
60
 
57
61
  private
@@ -66,11 +70,11 @@ module Alchemy
66
70
  end
67
71
 
68
72
  def load_page_by_urlname
69
- return unless Language.current
73
+ return unless Current.language
70
74
 
71
- Language.current.pages.where(
75
+ Current.language.pages.where(
72
76
  urlname: params[:urlname],
73
- language_code: params[:locale] || Language.current.code
77
+ language_code: params[:locale] || Current.language.code
74
78
  ).includes(page_includes).first
75
79
  end
76
80
 
@@ -25,9 +25,9 @@ module Alchemy
25
25
  # Sets +I18n.locale+ to current Alchemy language.
26
26
  #
27
27
  def set_locale
28
- return unless Language.current
28
+ return unless Current.language
29
29
 
30
- ::I18n.locale = Language.current&.locale
30
+ ::I18n.locale = Current.language&.locale
31
31
  end
32
32
 
33
33
  def not_found_error!(msg = "Not found \"#{request.fullpath}\"")
@@ -97,9 +97,9 @@ module Alchemy
97
97
  else
98
98
  Language.current_root_page.urlname
99
99
  end
100
- redirect_to alchemy.show_page_path(
100
+ redirect_to show_page_path(
101
101
  urlname: urlname,
102
- locale: prefix_locale? ? Language.current.code : nil
102
+ locale: prefix_locale? ? Current.language.code : nil
103
103
  )
104
104
  end
105
105
 
@@ -117,7 +117,7 @@ module Alchemy
117
117
  end
118
118
 
119
119
  def get_page
120
- @page = Language.current.pages.find_by(page_layout: mailer_config["page_layout_name"])
120
+ @page = Current.language.pages.find_by(page_layout: mailer_config["page_layout_name"])
121
121
  if @page.blank?
122
122
  raise "Page for page_layout #{mailer_config["page_layout_name"]} not found"
123
123
  end
@@ -105,6 +105,7 @@ module Alchemy
105
105
  #
106
106
  def load_index_page
107
107
  @page ||= Language.current_root_page
108
+ Current.page = @page
108
109
  render template: "alchemy/welcome", layout: false if signup_required?
109
110
  end
110
111
 
@@ -118,16 +119,17 @@ module Alchemy
118
119
  # @return NilClass
119
120
  #
120
121
  def load_page
121
- page_not_found! unless Language.current
122
+ page_not_found! unless Current.language
122
123
 
123
- @page ||= Language.current.pages.contentpages.find_by(
124
+ @page ||= Current.language.pages.contentpages.find_by(
124
125
  urlname: params[:urlname],
125
- language_code: params[:locale] || Language.current.code
126
+ language_code: params[:locale] || Current.language.code
126
127
  )
128
+ Current.page = @page
127
129
  end
128
130
 
129
131
  def enforce_locale
130
- redirect_permanently_to page_locale_redirect_url(locale: Language.current.code)
132
+ redirect_permanently_to page_locale_redirect_url(locale: Current.language.code)
131
133
  end
132
134
 
133
135
  def locale_prefix_missing?
@@ -135,7 +137,7 @@ module Alchemy
135
137
  end
136
138
 
137
139
  def default_locale?
138
- Language.current.code.to_sym == ::I18n.default_locale.to_sym
140
+ Current.language.code.to_sym == ::I18n.default_locale.to_sym
139
141
  end
140
142
 
141
143
  # Page url with or without locale while keeping all additional params
@@ -214,7 +216,7 @@ module Alchemy
214
216
  def render_fresh_page?
215
217
  must_not_cache? || stale?(
216
218
  etag: page_etag,
217
- last_modified: @page.published_at,
219
+ last_modified: @page.last_modified_at,
218
220
  public: !@page.restricted,
219
221
  template: "pages/show"
220
222
  )
@@ -6,26 +6,16 @@ module Alchemy
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- # This needs to happen before BaseController#current_alchemy_site sets the session value.
10
- prepend_before_action :clear_current_language_from_session, if: :switching_site?, only: :index
11
9
  before_action :load_current_language
12
10
  end
13
11
 
14
12
  private
15
13
 
16
- def switching_site?
17
- params[:site_id].present? && (params[:site_id] != session[:alchemy_site_id]&.to_s)
18
- end
19
-
20
- def clear_current_language_from_session
21
- session.delete(:alchemy_language_id)
22
- end
23
-
24
14
  def load_current_language
25
15
  @current_language = if session[:alchemy_language_id].present?
26
16
  set_alchemy_language(session[:alchemy_language_id])
27
17
  else
28
- Alchemy::Language.current
18
+ Current.language
29
19
  end
30
20
  if @current_language.nil?
31
21
  flash[:warning] = Alchemy.t("Please create a language first.")
@@ -46,7 +46,7 @@ module Alchemy
46
46
  LegacyPageUrl.joins(:page).where(
47
47
  :urlname => urlname,
48
48
  Page.table_name => {
49
- language_id: Language.current.id
49
+ language_id: Current.language.id
50
50
  }
51
51
  )
52
52
  end
@@ -59,8 +59,8 @@ module Alchemy
59
59
  compact? ? "compact" : nil,
60
60
  deprecated? ? "deprecated" : nil,
61
61
  fixed? ? "is-fixed" : "not-fixed",
62
- public? ? "visible" : "hidden"
63
- ].join(" ")
62
+ public? ? nil : "element-hidden"
63
+ ]
64
64
  end
65
65
 
66
66
  # Tells us, if we should show the element footer and form inputs.