alchemy_cms 7.1.10 → 7.2.0.b

Sign up to get free protection for your applications and to get access to all the features.
Files changed (308) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -16
  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 +73 -41
  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/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 +3 -3
  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/audio.rb +0 -11
  136. data/app/models/alchemy/ingredients/headline.rb +8 -1
  137. data/app/models/alchemy/ingredients/picture.rb +6 -0
  138. data/app/models/alchemy/ingredients/video.rb +0 -12
  139. data/app/models/alchemy/language.rb +8 -6
  140. data/app/models/alchemy/node.rb +2 -2
  141. data/app/models/alchemy/page/page_elements.rb +8 -8
  142. data/app/models/alchemy/page/page_layouts.rb +3 -3
  143. data/app/models/alchemy/page/page_natures.rb +13 -9
  144. data/app/models/alchemy/page/page_scopes.rb +2 -2
  145. data/app/models/alchemy/page/publisher.rb +1 -0
  146. data/app/models/alchemy/page.rb +13 -28
  147. data/app/models/alchemy/picture.rb +8 -0
  148. data/app/models/alchemy/picture_description.rb +8 -0
  149. data/app/models/alchemy/picture_variant.rb +1 -1
  150. data/app/models/alchemy/site.rb +10 -7
  151. data/app/serializers/alchemy/attachment_serializer.rb +8 -0
  152. data/app/serializers/alchemy/page_node_serializer.rb +9 -0
  153. data/app/views/alchemy/_menubar.html.erb +1 -1
  154. data/app/views/alchemy/_preview_mode_code.html.erb +1 -1
  155. data/app/views/alchemy/admin/attachments/_tag_list.html.erb +2 -2
  156. data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -1
  157. data/app/views/alchemy/admin/attachments/edit.html.erb +3 -4
  158. data/app/views/alchemy/admin/clipboard/clear.js.erb +1 -1
  159. data/app/views/alchemy/admin/clipboard/index.html.erb +1 -1
  160. data/app/views/alchemy/admin/clipboard/insert.js.erb +1 -1
  161. data/app/views/alchemy/admin/clipboard/remove.js.erb +1 -1
  162. data/app/views/alchemy/admin/dashboard/_locked_pages.html.erb +1 -1
  163. data/app/views/alchemy/admin/dashboard/_sites.html.erb +1 -1
  164. data/app/views/alchemy/admin/dashboard/help.html.erb +48 -12
  165. data/app/views/alchemy/admin/dashboard/index.html.erb +1 -1
  166. data/app/views/alchemy/admin/dashboard/info.html.erb +5 -8
  167. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +1 -1
  168. data/app/views/alchemy/admin/elements/_element.html.erb +5 -5
  169. data/app/views/alchemy/admin/elements/_footer.html.erb +1 -1
  170. data/app/views/alchemy/admin/elements/_header.html.erb +6 -2
  171. data/app/views/alchemy/admin/elements/_toolbar.html.erb +8 -6
  172. data/app/views/alchemy/admin/elements/create.js.erb +0 -5
  173. data/app/views/alchemy/admin/elements/index.html.erb +70 -34
  174. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +1 -2
  175. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +3 -5
  176. data/app/views/alchemy/admin/languages/_language.html.erb +1 -1
  177. data/app/views/alchemy/admin/languages/index.html.erb +2 -2
  178. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +18 -18
  179. data/app/views/alchemy/admin/layoutpages/edit.html.erb +3 -4
  180. data/app/views/alchemy/admin/layoutpages/index.html.erb +2 -2
  181. data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +10 -11
  182. data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +15 -17
  183. data/app/views/alchemy/admin/legacy_page_urls/_table.html.erb +16 -0
  184. data/app/views/alchemy/admin/legacy_page_urls/_update.turbo_stream.erb +12 -0
  185. data/app/views/alchemy/admin/legacy_page_urls/create.turbo_stream.erb +8 -0
  186. data/app/views/alchemy/admin/legacy_page_urls/destroy.turbo_stream.erb +1 -0
  187. data/app/views/alchemy/admin/legacy_page_urls/edit.html.erb +27 -0
  188. data/app/views/alchemy/admin/legacy_page_urls/show.html.erb +1 -0
  189. data/app/views/alchemy/admin/legacy_page_urls/update.turbo_stream.erb +1 -0
  190. data/app/views/alchemy/admin/nodes/_form.html.erb +12 -11
  191. data/app/views/alchemy/admin/nodes/_label.html.erb +1 -0
  192. data/app/views/alchemy/admin/nodes/_node.html.erb +19 -19
  193. data/app/views/alchemy/admin/nodes/_page_nodes.html.erb +48 -0
  194. data/app/views/alchemy/admin/nodes/_update.turbo_stream.erb +9 -0
  195. data/app/views/alchemy/admin/nodes/create.turbo_stream.erb +1 -0
  196. data/app/views/alchemy/admin/nodes/destroy.turbo_stream.erb +1 -0
  197. data/app/views/alchemy/admin/nodes/index.html.erb +3 -3
  198. data/app/views/alchemy/admin/pages/_form.html.erb +3 -4
  199. data/app/views/alchemy/admin/pages/_legacy_urls.html.erb +4 -15
  200. data/app/views/alchemy/admin/pages/_page.html.erb +39 -39
  201. data/app/views/alchemy/admin/pages/_table_row.html.erb +3 -3
  202. data/app/views/alchemy/admin/pages/_toolbar.html.erb +2 -2
  203. data/app/views/alchemy/admin/pages/configure.html.erb +6 -0
  204. data/app/views/alchemy/admin/pages/edit.html.erb +15 -62
  205. data/app/views/alchemy/admin/pages/unlock.js.erb +3 -3
  206. data/app/views/alchemy/admin/partials/_autocomplete_tag_list.html.erb +3 -1
  207. data/app/views/alchemy/admin/partials/_flash_notices.html.erb +4 -2
  208. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +1 -1
  209. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +5 -2
  210. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -2
  211. data/app/views/alchemy/admin/partials/_search_form.html.erb +2 -2
  212. data/app/views/alchemy/admin/partials/_site_select.html.erb +1 -1
  213. data/app/views/alchemy/admin/picture_descriptions/_form.html.erb +11 -0
  214. data/app/views/alchemy/admin/picture_descriptions/edit.html.erb +6 -0
  215. data/app/views/alchemy/admin/pictures/_form.html.erb +4 -3
  216. data/app/views/alchemy/admin/pictures/_infos.html.erb +1 -1
  217. data/app/views/alchemy/admin/pictures/_picture_description_field.html.erb +29 -0
  218. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +2 -2
  219. data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +0 -2
  220. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +3 -3
  221. data/app/views/alchemy/admin/pictures/show.html.erb +3 -3
  222. data/app/views/alchemy/admin/resources/_form.html.erb +3 -4
  223. data/app/views/alchemy/admin/resources/_tag_list.html.erb +2 -2
  224. data/app/views/alchemy/admin/resources/index.html.erb +2 -2
  225. data/app/views/alchemy/admin/sites/index.html.erb +1 -1
  226. data/app/views/alchemy/admin/styleguide/index.html.erb +29 -24
  227. data/app/views/alchemy/admin/tags/_tag.html.erb +1 -1
  228. data/app/views/alchemy/admin/tags/edit.html.erb +1 -1
  229. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  230. data/app/views/alchemy/base/500.html.erb +7 -18
  231. data/app/views/alchemy/base/error_notice.html.erb +3 -1
  232. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +1 -1
  233. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +1 -1
  234. data/app/views/alchemy/ingredients/_headline_editor.html.erb +13 -8
  235. data/app/views/alchemy/ingredients/_picture_editor.html.erb +1 -1
  236. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +1 -1
  237. data/app/views/alchemy/language_links/_language.html.erb +1 -1
  238. data/app/views/kaminari/alchemy/_first_page.html.erb +2 -2
  239. data/app/views/kaminari/alchemy/_gap.html.erb +1 -1
  240. data/app/views/kaminari/alchemy/_last_page.html.erb +2 -2
  241. data/app/views/kaminari/alchemy/_next_page.html.erb +2 -2
  242. data/app/views/kaminari/alchemy/_prev_page.html.erb +2 -2
  243. data/app/views/layouts/alchemy/admin.html.erb +2 -1
  244. data/bundles/shoelace.js +3 -1
  245. data/config/locales/alchemy.en.yml +16 -3
  246. data/config/routes.rb +3 -1
  247. data/db/migrate/20240314105244_create_alchemy_picture_descriptions.rb +11 -0
  248. data/lib/alchemy/configuration_methods.rb +1 -1
  249. data/lib/alchemy/controller_actions.rb +3 -3
  250. data/lib/alchemy/element_definition.rb +10 -6
  251. data/lib/alchemy/engine.rb +19 -2
  252. data/lib/alchemy/page_layout.rb +10 -6
  253. data/lib/alchemy/permissions.rb +3 -2
  254. data/lib/alchemy/routing_constraints.rb +1 -1
  255. data/lib/alchemy/seeder.rb +2 -2
  256. data/lib/alchemy/test_support/capybara_helpers.rb +4 -0
  257. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  258. data/lib/alchemy/test_support/shared_contexts.rb +8 -0
  259. data/lib/alchemy/tinymce.rb +2 -1
  260. data/lib/alchemy/version.rb +1 -1
  261. data/lib/alchemy.rb +36 -0
  262. data/lib/alchemy_cms.rb +0 -1
  263. data/lib/generators/alchemy/menus/templates/node.html.erb +2 -2
  264. data/lib/generators/alchemy/menus/templates/node.html.haml +2 -2
  265. data/lib/generators/alchemy/menus/templates/node.html.slim +2 -2
  266. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  267. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  268. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  269. data/lib/tasks/alchemy/sitemap.rake +97 -0
  270. data/package.json +8 -8
  271. data/vendor/assets/fonts/remixicon.symbol.svg +11 -0
  272. data/vendor/javascript/shoelace.min.js +333 -118
  273. data/vendor/javascript/sortable.min.js +1 -1
  274. data/vendor/javascript/tinymce.min.js +1 -1
  275. data/vendor/javascript/ungap-custom-elements.min.js +1 -1
  276. metadata +61 -54
  277. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +0 -85
  278. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +0 -107
  279. data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +0 -66
  280. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +0 -45
  281. data/app/assets/javascripts/alchemy/alchemy.growler.js.coffee +0 -24
  282. data/app/assets/javascripts/alchemy/alchemy.hotkeys.js.coffee +0 -49
  283. data/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee +0 -0
  284. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +0 -230
  285. data/app/assets/javascripts/alchemy/alchemy.list_filter.js.coffee +0 -49
  286. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +0 -82
  287. data/app/assets/javascripts/alchemy/alchemy.string_extension.js.coffee +0 -11
  288. data/app/assets/javascripts/alchemy/templates/page.hbs +0 -19
  289. data/app/javascript/alchemy_admin/tags_autocomplete.js +0 -46
  290. data/app/models/alchemy/tree_node.rb +0 -7
  291. data/app/views/alchemy/admin/elements/destroy.js.erb +0 -8
  292. data/app/views/alchemy/admin/legacy_page_urls/_form.html.erb +0 -5
  293. data/app/views/alchemy/admin/legacy_page_urls/create.js.erb +0 -9
  294. data/app/views/alchemy/admin/legacy_page_urls/destroy.js.erb +0 -6
  295. data/app/views/alchemy/admin/legacy_page_urls/update.js.erb +0 -2
  296. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +0 -22
  297. data/app/views/alchemy/admin/pages/_external_link.html.erb +0 -31
  298. data/app/views/alchemy/admin/pages/_file_link.html.erb +0 -31
  299. data/app/views/alchemy/admin/pages/_internal_link.html.erb +0 -35
  300. data/app/views/alchemy/admin/pages/link.html.erb +0 -26
  301. data/app/views/alchemy/admin/partials/_flash.html.erb +0 -4
  302. data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +0 -29
  303. data/vendor/assets/fonts/remixicon.eot +0 -0
  304. data/vendor/assets/fonts/remixicon.svg +0 -7816
  305. data/vendor/assets/fonts/remixicon.ttf +0 -0
  306. data/vendor/assets/fonts/remixicon.woff +0 -0
  307. data/vendor/assets/fonts/remixicon.woff2 +0 -0
  308. data/vendor/assets/stylesheets/remixicon.scss +0 -10480
@@ -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
@@ -46,7 +46,7 @@ module Alchemy
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
@@ -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
@@ -212,23 +212,6 @@ module Alchemy
212
212
  redirect_to admin_pages_path
213
213
  end
214
214
 
215
- # Receives a JSON object representing a language tree to be ordered
216
- # and updates all pages in that language structure to their correct indexes
217
- def order
218
- neworder = JSON.parse(params[:set])
219
- tree = create_tree(neworder, @page_root)
220
-
221
- Alchemy::Page.transaction do
222
- tree.each do |key, node|
223
- dbitem = Page.find(key)
224
- dbitem.update_node!(node)
225
- end
226
- end
227
-
228
- flash[:notice] = Alchemy.t("Pages order saved")
229
- do_redirect_to admin_pages_path
230
- end
231
-
232
215
  def flush
233
216
  @current_language.pages.flushables.update_all(published_at: Time.current)
234
217
  # We need to ensure, that also all layoutpages get the +published_at+ timestamp set,
@@ -261,60 +244,6 @@ module Alchemy
261
244
  Page.language_root_for(params[:languages][:old_lang_id])
262
245
  end
263
246
 
264
- # Returns the current left index and the aggregated hash of tree nodes indexed by page id visited so far
265
- #
266
- # Visits a batch of children nodes, assigns them the correct ordering indexes and spuns recursively the same
267
- # procedure on their children, if any
268
- #
269
- # @param [Array]
270
- # An array of children nodes to be visited
271
- # @param [Integer]
272
- # The lft attribute that should be given to the first node in the array
273
- # @param [Integer]
274
- # The page id of the parent of this batch of children nodes
275
- # @param [Integer]
276
- # The depth at which these children reside
277
- # @param [Hash]
278
- # A Hash of TreeNode's indexed by their page ids
279
- # @param [String]
280
- # The url for the parent node of these children
281
- # @param [Boolean]
282
- # Whether these children reside in a restricted branch according to their ancestors
283
- #
284
- def visit_nodes(nodes, my_left, parent, depth, tree, url, restricted)
285
- nodes.each do |item|
286
- my_right = my_left + 1
287
- my_restricted = item["restricted"] || restricted
288
- urls = process_url(url, item)
289
-
290
- if item["children"]
291
- my_right, tree = visit_nodes(item["children"], my_left + 1, item["id"], depth + 1, tree, urls[:children_path], my_restricted)
292
- end
293
-
294
- tree[item["id"]] = TreeNode.new(my_left, my_right, parent, depth, urls[:my_urlname], my_restricted)
295
- my_left = my_right + 1
296
- end
297
-
298
- [my_left, tree]
299
- end
300
-
301
- # Returns a Hash of TreeNode's indexed by their page ids
302
- #
303
- # Grabs the array representing a tree structure of pages passed as a parameter,
304
- # visits it and creates a map of TreeNodes indexed by page id featuring Nested Set
305
- # ordering information consisting of the left, right, depth and parent_id indexes as
306
- # well as a node's url and restricted status
307
- #
308
- # @param [Array]
309
- # An Array representing a tree of Alchemy::Page's
310
- # @param [Alchemy::Page]
311
- # The root page for the language being ordered
312
- #
313
- def create_tree(items, rootpage)
314
- _, tree = visit_nodes(items, rootpage.lft + 1, rootpage.id, rootpage.depth + 1, {}, "", rootpage.restricted)
315
- tree
316
- end
317
-
318
247
  # Returns a pair, the path that a given tree node should take, and the path its children should take
319
248
  #
320
249
  # This function will add a node's own slug into their ancestor's path
@@ -355,6 +284,10 @@ module Alchemy
355
284
  params.require(:page).permit(*secure_attributes)
356
285
  end
357
286
 
287
+ def link_dialog_params
288
+ params.permit([:url, :selected_tab, :link_title, :link_target])
289
+ end
290
+
358
291
  def secure_attributes
359
292
  if can?(:create, Alchemy::Page)
360
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
@@ -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
  )
@@ -15,7 +15,7 @@ module Alchemy
15
15
  @current_language = if session[:alchemy_language_id].present?
16
16
  set_alchemy_language(session[:alchemy_language_id])
17
17
  else
18
- Alchemy::Language.current
18
+ Current.language
19
19
  end
20
20
  if @current_language.nil?
21
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.
@@ -2,12 +2,10 @@
2
2
 
3
3
  module Alchemy
4
4
  module Admin
5
- # This module contains helper methods for rendering dialogs, toolbar buttons and confirmation windows.
5
+ # This module contains helper methods for rendering dialogs and confirmation windows.
6
6
  #
7
7
  # The most important helpers for module developers are:
8
8
  #
9
- # * {#toolbar}
10
- # * {#toolbar_button}
11
9
  # * {#link_to_dialog}
12
10
  # * {#link_to_confirm_dialog}
13
11
  #
@@ -106,18 +104,11 @@ module Alchemy
106
104
  # The css class of the <input> tag
107
105
  # @option options [String or Hash] :data ({'alchemy-list-filter' => items})
108
106
  # A HTML data attribute that holds the jQuery selector that represents the list to be filtered
109
- #
110
- def js_filter_field(items, options = {})
111
- options = {
112
- class: "js_filter_field",
113
- data: {"alchemy-list-filter" => items}
114
- }.merge(options)
115
- content_tag(:div, class: "js_filter_field_box") do
116
- concat text_field_tag(nil, nil, options)
117
- concat render_icon(:search)
118
- concat link_to(render_icon(:times, size: "xs"), "", class: "js_filter_field_clear", title: Alchemy.t(:click_to_show_all))
119
- end
107
+ # @deprecated render Alchemy::Admin::ListFilter.new(items) instead
108
+ def js_filter_field(items, _options = {})
109
+ render Alchemy::Admin::ListFilter.new(items)
120
110
  end
111
+ deprecate js_filter_field: "render Alchemy::Admin::ListFilter.new(items) instead", deprecator: Alchemy::Deprecation
121
112
 
122
113
  # Returns a link that opens a modal confirmation to delete window.
123
114
  #
@@ -266,36 +257,11 @@ module Alchemy
266
257
  # Skip the permission check. NOT RECOMMENDED!
267
258
  # @option options [Boolean] :loading_indicator (true)
268
259
  # Shows the please wait dialog while loading. Only for buttons not opening an dialog.
269
- #
260
+ # @deprecated render Alchemy::Admin::ToolbarButton.new instead
270
261
  def toolbar_button(options = {})
271
- options = {
272
- dialog: true,
273
- skip_permission_check: false,
274
- active: false,
275
- link_options: {},
276
- dialog_options: {},
277
- loading_indicator: false
278
- }.merge(options.symbolize_keys)
279
- button = render(
280
- "alchemy/admin/partials/toolbar_button",
281
- options: options
282
- )
283
- if options[:skip_permission_check] || can?(*permission_from_options(options))
284
- button
285
- else
286
- ""
287
- end
288
- end
289
-
290
- # (internal) Used by upload form
291
- def new_asset_path_with_session_information(asset_type)
292
- session_key = Rails.application.config.session_options[:key]
293
- if asset_type == "picture"
294
- alchemy.admin_pictures_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token, :format => :js)
295
- elsif asset_type == "attachment"
296
- alchemy.admin_attachments_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token, :format => :js)
297
- end
262
+ render Alchemy::Admin::ToolbarButton.new(**options)
298
263
  end
264
+ deprecate toolbar_button: "render Alchemy::Admin::ToolbarButton.new instead", deprecator: Alchemy::Deprecation
299
265
 
300
266
  # Renders a textfield ready to display a datepicker
301
267
  #
@@ -406,24 +372,6 @@ module Alchemy
406
372
  Alchemy.t(:page_definition_missing)
407
373
  )
408
374
  end
409
-
410
- private
411
-
412
- def permission_from_options(options)
413
- if options[:if_permitted_to].blank?
414
- options[:if_permitted_to] = permission_array_from_url(options)
415
- else
416
- options[:if_permitted_to]
417
- end
418
- end
419
-
420
- def permission_array_from_url(options)
421
- action_controller = options[:url].gsub(/\A\//, "").split("/")
422
- [
423
- action_controller.last.to_sym,
424
- action_controller[0..action_controller.length - 2].join("_").to_sym
425
- ]
426
- end
427
375
  end
428
376
  end
429
377
  end
@@ -18,7 +18,7 @@ module Alchemy
18
18
  Element.display_name_for(e["name"]),
19
19
  e["name"]
20
20
  ]
21
- end
21
+ end.sort
22
22
  end
23
23
  end
24
24
  end
@@ -36,7 +36,7 @@ module Alchemy
36
36
  label_tag ingredient.form_field_id(column), html_options do
37
37
  [
38
38
  render_ingredient_role(ingredient),
39
- render_hint_for(ingredient, size: "lg", fixed_width: false)
39
+ render_hint_for(ingredient, size: "1x", fixed_width: false)
40
40
  ].compact.join("&nbsp;").html_safe
41
41
  end
42
42
  end