alchemy_cms 7.1.7 → 7.2.0.b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +133 -0
  3. data/Gemfile +2 -4
  4. data/LICENSE +1 -1
  5. data/README.md +5 -6
  6. data/SECURITY.md +1 -1
  7. data/alchemy_cms.gemspec +3 -4
  8. data/app/assets/javascripts/alchemy/admin.js +0 -9
  9. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +5 -15
  10. data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +5 -4
  11. data/app/assets/javascripts/alchemy/templates/index.js +0 -1
  12. data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
  13. data/app/assets/javascripts/alchemy/templates/page_folder.hbs +1 -1
  14. data/app/assets/javascripts/tinymce/plugins/alchemy_link/plugin.min.js +20 -7
  15. data/app/assets/stylesheets/alchemy/_custom-properties.scss +12 -0
  16. data/app/assets/stylesheets/alchemy/_mixins.scss +10 -6
  17. data/app/assets/stylesheets/alchemy/_variables.scss +3 -0
  18. data/app/assets/stylesheets/alchemy/admin.scss +2 -2
  19. data/app/assets/stylesheets/alchemy/archive.scss +4 -3
  20. data/app/assets/stylesheets/alchemy/attachment-select.scss +19 -0
  21. data/app/assets/stylesheets/alchemy/base.scss +31 -18
  22. data/app/assets/stylesheets/alchemy/buttons.scss +3 -4
  23. data/app/assets/stylesheets/alchemy/dashboard.scss +1 -1
  24. data/app/assets/stylesheets/alchemy/dialogs.scss +2 -5
  25. data/app/assets/stylesheets/alchemy/elements.scss +76 -46
  26. data/app/assets/stylesheets/alchemy/flash.scss +20 -70
  27. data/app/assets/stylesheets/alchemy/forms.scss +41 -36
  28. data/app/assets/stylesheets/alchemy/frame.scss +12 -3
  29. data/app/assets/stylesheets/alchemy/icons.scss +34 -2
  30. data/app/assets/stylesheets/alchemy/image_library.scss +18 -9
  31. data/app/assets/stylesheets/alchemy/{filter_field.scss → list_filter.scss} +8 -7
  32. data/app/assets/stylesheets/alchemy/lists.scss +1 -1
  33. data/app/assets/stylesheets/alchemy/navigation.scss +9 -12
  34. data/app/assets/stylesheets/alchemy/node-select.scss +1 -1
  35. data/app/assets/stylesheets/alchemy/nodes.scss +15 -13
  36. data/app/assets/stylesheets/alchemy/notices.scss +56 -39
  37. data/app/assets/stylesheets/alchemy/page-select.scss +1 -4
  38. data/app/assets/stylesheets/alchemy/pagination.scss +11 -1
  39. data/app/assets/stylesheets/alchemy/preview_window.scss +7 -3
  40. data/app/assets/stylesheets/alchemy/search.scss +4 -4
  41. data/app/assets/stylesheets/alchemy/selects.scss +13 -7
  42. data/app/assets/stylesheets/alchemy/shoelace.scss +33 -2
  43. data/app/assets/stylesheets/alchemy/sitemap.scss +155 -159
  44. data/app/assets/stylesheets/alchemy/tables.scss +49 -12
  45. data/app/assets/stylesheets/alchemy/tags.scss +17 -11
  46. data/app/assets/stylesheets/alchemy/toolbar.scss +2 -2
  47. data/app/assets/stylesheets/alchemy/typography.scss +41 -22
  48. data/app/assets/stylesheets/alchemy/upload.scss +5 -4
  49. data/app/components/alchemy/admin/attachment_select.rb +39 -0
  50. data/app/components/alchemy/admin/icon.rb +72 -0
  51. data/app/components/alchemy/admin/link_dialog/anchor_tab.rb +41 -0
  52. data/app/components/alchemy/admin/link_dialog/base_tab.rb +75 -0
  53. data/app/components/alchemy/admin/link_dialog/external_tab.rb +42 -0
  54. data/app/components/alchemy/admin/link_dialog/file_tab.rb +45 -0
  55. data/app/components/alchemy/admin/link_dialog/internal_tab.rb +66 -0
  56. data/app/components/alchemy/admin/link_dialog/tabs.rb +33 -0
  57. data/app/components/alchemy/admin/list_filter.rb +42 -0
  58. data/app/components/alchemy/admin/message.rb +19 -0
  59. data/app/components/alchemy/admin/tags_autocomplete.rb +25 -0
  60. data/app/components/alchemy/admin/toolbar_button.rb +111 -0
  61. data/app/components/alchemy/ingredients/link_view.rb +1 -7
  62. data/app/components/alchemy/ingredients/picture_view.rb +2 -2
  63. data/app/components/alchemy/ingredients/text_view.rb +1 -2
  64. data/app/controllers/alchemy/admin/base_controller.rb +1 -1
  65. data/app/controllers/alchemy/admin/elements_controller.rb +4 -2
  66. data/app/controllers/alchemy/admin/ingredients_controller.rb +2 -0
  67. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  68. data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +12 -4
  69. data/app/controllers/alchemy/admin/nodes_controller.rb +26 -0
  70. data/app/controllers/alchemy/admin/pages_controller.rb +11 -78
  71. data/app/controllers/alchemy/admin/picture_descriptions_controller.rb +15 -0
  72. data/app/controllers/alchemy/admin/pictures_controller.rb +18 -1
  73. data/app/controllers/alchemy/admin/resources_controller.rb +15 -10
  74. data/app/controllers/alchemy/api/attachments_controller.rb +44 -0
  75. data/app/controllers/alchemy/api/pages_controller.rb +10 -6
  76. data/app/controllers/alchemy/base_controller.rb +2 -2
  77. data/app/controllers/alchemy/messages_controller.rb +2 -2
  78. data/app/controllers/alchemy/pages_controller.rb +8 -6
  79. data/app/controllers/concerns/alchemy/admin/current_language.rb +1 -1
  80. data/app/controllers/concerns/alchemy/legacy_page_redirects.rb +1 -1
  81. data/app/decorators/alchemy/element_editor.rb +2 -2
  82. data/app/helpers/alchemy/admin/base_helper.rb +8 -60
  83. data/app/helpers/alchemy/admin/elements_helper.rb +1 -1
  84. data/app/helpers/alchemy/admin/ingredients_helper.rb +1 -1
  85. data/app/helpers/alchemy/base_helper.rb +9 -91
  86. data/app/helpers/alchemy/elements_helper.rb +3 -3
  87. data/app/helpers/alchemy/pages_helper.rb +16 -9
  88. data/app/javascript/alchemy_admin/components/attachment_select.js +24 -0
  89. data/app/javascript/alchemy_admin/components/button.js +3 -0
  90. data/app/javascript/alchemy_admin/components/clipboard_button.js +3 -2
  91. data/app/javascript/alchemy_admin/components/dialog_link.js +10 -7
  92. data/app/javascript/alchemy_admin/components/dom_id_select.js +69 -0
  93. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +42 -0
  94. data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +4 -2
  95. data/app/javascript/alchemy_admin/components/element_editor.js +21 -13
  96. data/app/javascript/alchemy_admin/components/elements_window.js +87 -0
  97. data/app/javascript/alchemy_admin/components/growl.js +13 -0
  98. data/app/javascript/alchemy_admin/components/icon.js +51 -0
  99. data/app/javascript/alchemy_admin/components/index.js +24 -0
  100. data/app/javascript/alchemy_admin/components/ingredient_group.js +6 -0
  101. data/app/javascript/alchemy_admin/components/link_buttons/link_button.js +21 -11
  102. data/app/javascript/alchemy_admin/components/link_buttons/unlink_button.js +2 -1
  103. data/app/javascript/alchemy_admin/components/link_buttons.js +1 -0
  104. data/app/javascript/alchemy_admin/components/list_filter.js +68 -0
  105. data/app/javascript/alchemy_admin/components/message.js +69 -0
  106. data/app/javascript/alchemy_admin/components/node_select.js +1 -1
  107. data/app/javascript/alchemy_admin/components/overlay.js +6 -6
  108. data/app/javascript/alchemy_admin/components/page_select.js +3 -7
  109. data/app/javascript/alchemy_admin/components/preview_window.js +121 -0
  110. data/app/javascript/alchemy_admin/components/remote_select.js +4 -1
  111. data/app/javascript/alchemy_admin/components/select.js +37 -1
  112. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +57 -0
  113. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +4 -3
  114. data/app/javascript/alchemy_admin/components/uploader/progress.js +1 -1
  115. data/app/javascript/alchemy_admin/confirm_dialog.js +133 -0
  116. data/app/javascript/alchemy_admin/dirty.js +19 -14
  117. data/app/javascript/alchemy_admin/fixed_elements.js +24 -0
  118. data/app/javascript/alchemy_admin/growler.js +15 -0
  119. data/app/javascript/alchemy_admin/gui.js +2 -4
  120. data/app/javascript/alchemy_admin/hotkeys.js +60 -0
  121. data/app/javascript/alchemy_admin/image_loader.js +2 -2
  122. data/app/javascript/alchemy_admin/ingredient_anchor_link.js +2 -3
  123. data/app/javascript/alchemy_admin/initializer.js +1 -8
  124. data/app/javascript/alchemy_admin/link_dialog.js +131 -0
  125. data/app/javascript/alchemy_admin/locales/en.js +3 -0
  126. data/app/javascript/alchemy_admin/node_tree.js +4 -3
  127. data/app/javascript/alchemy_admin/page_sorter.js +23 -14
  128. data/app/javascript/alchemy_admin/picture_editors.js +3 -2
  129. data/app/javascript/alchemy_admin/shoelace_theme.js +60 -0
  130. data/app/javascript/alchemy_admin/sitemap.js +9 -3
  131. data/app/javascript/alchemy_admin/sortable_elements.js +4 -6
  132. data/app/javascript/alchemy_admin.js +18 -42
  133. data/app/models/alchemy/current.rb +26 -0
  134. data/app/models/alchemy/element.rb +1 -1
  135. data/app/models/alchemy/ingredients/headline.rb +8 -1
  136. data/app/models/alchemy/ingredients/picture.rb +6 -0
  137. data/app/models/alchemy/language.rb +8 -6
  138. data/app/models/alchemy/node.rb +2 -2
  139. data/app/models/alchemy/page/page_elements.rb +8 -8
  140. data/app/models/alchemy/page/page_layouts.rb +3 -3
  141. data/app/models/alchemy/page/page_natures.rb +13 -9
  142. data/app/models/alchemy/page/page_scopes.rb +2 -2
  143. data/app/models/alchemy/page/publisher.rb +1 -0
  144. data/app/models/alchemy/page.rb +13 -28
  145. data/app/models/alchemy/picture.rb +8 -0
  146. data/app/models/alchemy/picture_description.rb +8 -0
  147. data/app/models/alchemy/picture_variant.rb +1 -1
  148. data/app/models/alchemy/site.rb +10 -7
  149. data/app/serializers/alchemy/attachment_serializer.rb +8 -0
  150. data/app/serializers/alchemy/page_node_serializer.rb +9 -0
  151. data/app/views/alchemy/_menubar.html.erb +1 -1
  152. data/app/views/alchemy/_preview_mode_code.html.erb +1 -1
  153. data/app/views/alchemy/admin/attachments/_tag_list.html.erb +2 -2
  154. data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -1
  155. data/app/views/alchemy/admin/attachments/edit.html.erb +3 -4
  156. data/app/views/alchemy/admin/clipboard/clear.js.erb +1 -1
  157. data/app/views/alchemy/admin/clipboard/index.html.erb +1 -1
  158. data/app/views/alchemy/admin/clipboard/insert.js.erb +1 -1
  159. data/app/views/alchemy/admin/clipboard/remove.js.erb +1 -1
  160. data/app/views/alchemy/admin/dashboard/_locked_pages.html.erb +1 -1
  161. data/app/views/alchemy/admin/dashboard/_sites.html.erb +1 -1
  162. data/app/views/alchemy/admin/dashboard/help.html.erb +48 -12
  163. data/app/views/alchemy/admin/dashboard/index.html.erb +1 -1
  164. data/app/views/alchemy/admin/dashboard/info.html.erb +5 -8
  165. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +1 -1
  166. data/app/views/alchemy/admin/elements/_element.html.erb +5 -5
  167. data/app/views/alchemy/admin/elements/_footer.html.erb +1 -1
  168. data/app/views/alchemy/admin/elements/_header.html.erb +6 -2
  169. data/app/views/alchemy/admin/elements/_toolbar.html.erb +8 -6
  170. data/app/views/alchemy/admin/elements/create.js.erb +0 -5
  171. data/app/views/alchemy/admin/elements/index.html.erb +70 -34
  172. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +1 -2
  173. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +3 -5
  174. data/app/views/alchemy/admin/languages/_language.html.erb +1 -1
  175. data/app/views/alchemy/admin/languages/index.html.erb +2 -2
  176. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +18 -18
  177. data/app/views/alchemy/admin/layoutpages/edit.html.erb +3 -4
  178. data/app/views/alchemy/admin/layoutpages/index.html.erb +2 -2
  179. data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +10 -11
  180. data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +15 -17
  181. data/app/views/alchemy/admin/legacy_page_urls/_table.html.erb +16 -0
  182. data/app/views/alchemy/admin/legacy_page_urls/_update.turbo_stream.erb +12 -0
  183. data/app/views/alchemy/admin/legacy_page_urls/create.turbo_stream.erb +8 -0
  184. data/app/views/alchemy/admin/legacy_page_urls/destroy.turbo_stream.erb +1 -0
  185. data/app/views/alchemy/admin/legacy_page_urls/edit.html.erb +27 -0
  186. data/app/views/alchemy/admin/legacy_page_urls/show.html.erb +1 -0
  187. data/app/views/alchemy/admin/legacy_page_urls/update.turbo_stream.erb +1 -0
  188. data/app/views/alchemy/admin/nodes/_form.html.erb +12 -11
  189. data/app/views/alchemy/admin/nodes/_label.html.erb +1 -0
  190. data/app/views/alchemy/admin/nodes/_node.html.erb +19 -19
  191. data/app/views/alchemy/admin/nodes/_page_nodes.html.erb +48 -0
  192. data/app/views/alchemy/admin/nodes/_update.turbo_stream.erb +9 -0
  193. data/app/views/alchemy/admin/nodes/create.turbo_stream.erb +1 -0
  194. data/app/views/alchemy/admin/nodes/destroy.turbo_stream.erb +1 -0
  195. data/app/views/alchemy/admin/nodes/index.html.erb +3 -3
  196. data/app/views/alchemy/admin/pages/_form.html.erb +3 -4
  197. data/app/views/alchemy/admin/pages/_legacy_urls.html.erb +4 -15
  198. data/app/views/alchemy/admin/pages/_page.html.erb +39 -39
  199. data/app/views/alchemy/admin/pages/_table_row.html.erb +3 -3
  200. data/app/views/alchemy/admin/pages/_toolbar.html.erb +2 -2
  201. data/app/views/alchemy/admin/pages/configure.html.erb +6 -0
  202. data/app/views/alchemy/admin/pages/edit.html.erb +15 -62
  203. data/app/views/alchemy/admin/pages/unlock.js.erb +2 -2
  204. data/app/views/alchemy/admin/partials/_autocomplete_tag_list.html.erb +3 -1
  205. data/app/views/alchemy/admin/partials/_flash_notices.html.erb +4 -2
  206. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +1 -1
  207. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +5 -2
  208. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -2
  209. data/app/views/alchemy/admin/partials/_search_form.html.erb +2 -2
  210. data/app/views/alchemy/admin/partials/_site_select.html.erb +1 -1
  211. data/app/views/alchemy/admin/picture_descriptions/_form.html.erb +11 -0
  212. data/app/views/alchemy/admin/picture_descriptions/edit.html.erb +6 -0
  213. data/app/views/alchemy/admin/pictures/_form.html.erb +4 -3
  214. data/app/views/alchemy/admin/pictures/_infos.html.erb +1 -1
  215. data/app/views/alchemy/admin/pictures/_picture_description_field.html.erb +29 -0
  216. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +2 -2
  217. data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +0 -2
  218. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +3 -3
  219. data/app/views/alchemy/admin/pictures/show.html.erb +3 -3
  220. data/app/views/alchemy/admin/resources/_form.html.erb +3 -4
  221. data/app/views/alchemy/admin/resources/_tag_list.html.erb +2 -2
  222. data/app/views/alchemy/admin/resources/index.html.erb +2 -2
  223. data/app/views/alchemy/admin/sites/index.html.erb +1 -1
  224. data/app/views/alchemy/admin/styleguide/index.html.erb +29 -24
  225. data/app/views/alchemy/admin/tags/_tag.html.erb +1 -1
  226. data/app/views/alchemy/admin/tags/edit.html.erb +1 -1
  227. data/app/views/alchemy/admin/tags/index.html.erb +1 -1
  228. data/app/views/alchemy/base/500.html.erb +7 -18
  229. data/app/views/alchemy/base/error_notice.html.erb +3 -1
  230. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +1 -1
  231. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +1 -1
  232. data/app/views/alchemy/ingredients/_headline_editor.html.erb +13 -8
  233. data/app/views/alchemy/ingredients/_picture_editor.html.erb +1 -1
  234. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +1 -1
  235. data/app/views/alchemy/language_links/_language.html.erb +1 -1
  236. data/app/views/kaminari/alchemy/_first_page.html.erb +2 -2
  237. data/app/views/kaminari/alchemy/_gap.html.erb +1 -1
  238. data/app/views/kaminari/alchemy/_last_page.html.erb +2 -2
  239. data/app/views/kaminari/alchemy/_next_page.html.erb +2 -2
  240. data/app/views/kaminari/alchemy/_prev_page.html.erb +2 -2
  241. data/app/views/layouts/alchemy/admin.html.erb +2 -1
  242. data/bundles/shoelace.js +3 -1
  243. data/config/locales/alchemy.en.yml +16 -3
  244. data/config/routes.rb +3 -1
  245. data/db/migrate/20240314105244_create_alchemy_picture_descriptions.rb +11 -0
  246. data/lib/alchemy/configuration_methods.rb +1 -1
  247. data/lib/alchemy/controller_actions.rb +3 -3
  248. data/lib/alchemy/element_definition.rb +10 -6
  249. data/lib/alchemy/engine.rb +19 -2
  250. data/lib/alchemy/page_layout.rb +10 -6
  251. data/lib/alchemy/permissions.rb +3 -2
  252. data/lib/alchemy/seeder.rb +2 -2
  253. data/lib/alchemy/test_support/capybara_helpers.rb +4 -0
  254. data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
  255. data/lib/alchemy/test_support/shared_contexts.rb +8 -0
  256. data/lib/alchemy/tinymce.rb +2 -1
  257. data/lib/alchemy/version.rb +1 -1
  258. data/lib/alchemy.rb +36 -0
  259. data/lib/alchemy_cms.rb +0 -1
  260. data/lib/generators/alchemy/menus/templates/node.html.erb +2 -2
  261. data/lib/generators/alchemy/menus/templates/node.html.haml +2 -2
  262. data/lib/generators/alchemy/menus/templates/node.html.slim +2 -2
  263. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  264. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  265. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  266. data/lib/tasks/alchemy/sitemap.rake +97 -0
  267. data/package.json +8 -8
  268. data/vendor/assets/fonts/remixicon.symbol.svg +11 -0
  269. data/vendor/javascript/shoelace.min.js +333 -118
  270. data/vendor/javascript/sortable.min.js +1 -1
  271. data/vendor/javascript/tinymce.min.js +1 -1
  272. data/vendor/javascript/ungap-custom-elements.min.js +1 -1
  273. metadata +61 -54
  274. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +0 -85
  275. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +0 -107
  276. data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +0 -66
  277. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +0 -45
  278. data/app/assets/javascripts/alchemy/alchemy.growler.js.coffee +0 -24
  279. data/app/assets/javascripts/alchemy/alchemy.hotkeys.js.coffee +0 -49
  280. data/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee +0 -0
  281. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +0 -230
  282. data/app/assets/javascripts/alchemy/alchemy.list_filter.js.coffee +0 -49
  283. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +0 -82
  284. data/app/assets/javascripts/alchemy/alchemy.string_extension.js.coffee +0 -11
  285. data/app/assets/javascripts/alchemy/templates/page.hbs +0 -19
  286. data/app/javascript/alchemy_admin/tags_autocomplete.js +0 -46
  287. data/app/models/alchemy/tree_node.rb +0 -7
  288. data/app/views/alchemy/admin/elements/destroy.js.erb +0 -8
  289. data/app/views/alchemy/admin/legacy_page_urls/_form.html.erb +0 -5
  290. data/app/views/alchemy/admin/legacy_page_urls/create.js.erb +0 -9
  291. data/app/views/alchemy/admin/legacy_page_urls/destroy.js.erb +0 -6
  292. data/app/views/alchemy/admin/legacy_page_urls/update.js.erb +0 -2
  293. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +0 -22
  294. data/app/views/alchemy/admin/pages/_external_link.html.erb +0 -31
  295. data/app/views/alchemy/admin/pages/_file_link.html.erb +0 -31
  296. data/app/views/alchemy/admin/pages/_internal_link.html.erb +0 -35
  297. data/app/views/alchemy/admin/pages/link.html.erb +0 -26
  298. data/app/views/alchemy/admin/partials/_flash.html.erb +0 -4
  299. data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +0 -29
  300. data/vendor/assets/fonts/remixicon.eot +0 -0
  301. data/vendor/assets/fonts/remixicon.svg +0 -7816
  302. data/vendor/assets/fonts/remixicon.ttf +0 -0
  303. data/vendor/assets/fonts/remixicon.woff +0 -0
  304. data/vendor/assets/fonts/remixicon.woff2 +0 -0
  305. data/vendor/assets/stylesheets/remixicon.scss +0 -10480
@@ -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}\"")
@@ -99,7 +99,7 @@ module Alchemy
99
99
  end
100
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