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
@@ -2,6 +2,7 @@
2
2
  import PageSorter from "alchemy_admin/page_sorter"
3
3
  import { on } from "alchemy_admin/utils/events"
4
4
  import { get, patch } from "alchemy_admin/utils/ajax"
5
+ import { growl } from "alchemy_admin/growler"
5
6
  import { createSortables, displayPageFolders } from "alchemy_admin/page_sorter"
6
7
 
7
8
  export default class Sitemap {
@@ -40,6 +41,11 @@ export default class Sitemap {
40
41
  .catch(this.errorHandler)
41
42
  }
42
43
 
44
+ // Reloads the sitemap
45
+ reload() {
46
+ this.load(this.options.page_root_id)
47
+ }
48
+
43
49
  // Watch page folder clicks and re-render the page branch
44
50
  handlePageFolders() {
45
51
  on(
@@ -103,7 +109,6 @@ export default class Sitemap {
103
109
  item.classList.remove("highlight")
104
110
  }
105
111
  })
106
- this.filter_field_clear.style.display = "inline-block"
107
112
  const { length } = results
108
113
 
109
114
  if (length === 1) {
@@ -123,7 +128,6 @@ export default class Sitemap {
123
128
  left: 0,
124
129
  behavior: "smooth"
125
130
  })
126
- this.filter_field_clear.style.display = "none"
127
131
  }
128
132
  }
129
133
 
@@ -131,6 +135,8 @@ export default class Sitemap {
131
135
  _observe() {
132
136
  this.search_field.addEventListener("keyup", (evt) => {
133
137
  const term = evt.target.value
138
+ this.filter_field_clear.style.visibility =
139
+ term == "" ? "hidden" : "visible"
134
140
  this.filter(term.toLowerCase())
135
141
  })
136
142
  this.search_field.addEventListener("focus", () => key.setScope("search"))
@@ -142,7 +148,7 @@ export default class Sitemap {
142
148
  }
143
149
 
144
150
  errorHandler(error) {
145
- Alchemy.growl(error.message || error, "error")
151
+ growl(error.message || error, "error")
146
152
  console.error(error)
147
153
  }
148
154
  }
@@ -1,5 +1,7 @@
1
1
  import Sortable from "sortablejs"
2
+ import { growl } from "alchemy_admin/growler"
2
3
  import { post } from "alchemy_admin/utils/ajax"
4
+ import { reloadPreview } from "alchemy_admin/components/preview_window"
3
5
 
4
6
  const SORTABLE_OPTIONS = {
5
7
  draggable: ".element-editor",
@@ -35,8 +37,8 @@ function onSort(event) {
35
37
  if (event.target === event.to) {
36
38
  post(Alchemy.routes.order_admin_elements_path, params).then((response) => {
37
39
  const data = response.data
38
- Alchemy.growl(data.message)
39
- Alchemy.PreviewWindow.refresh()
40
+ growl(data.message)
41
+ reloadPreview()
40
42
  item.updateTitle(data.preview_text)
41
43
  })
42
44
  }
@@ -76,8 +78,4 @@ export default function SortableElements(selector) {
76
78
  })
77
79
 
78
80
  sortable_areas.forEach((element) => createSortable(element))
79
-
80
- document.querySelectorAll(".nested-elements").forEach((nestedElement) => {
81
- createSortable(nestedElement)
82
- })
83
81
  }
@@ -1,62 +1,34 @@
1
1
  import "@ungap/custom-elements"
2
2
  import "@hotwired/turbo-rails"
3
- import "keymaster"
4
3
 
5
4
  import Rails from "@rails/ujs"
6
5
 
7
6
  import GUI from "alchemy_admin/gui"
8
7
  import { translate } from "alchemy_admin/i18n"
9
8
  import Dirty from "alchemy_admin/dirty"
9
+ import * as FixedElements from "alchemy_admin/fixed_elements"
10
+ import { growl } from "alchemy_admin/growler"
10
11
  import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
11
12
  import ImageLoader from "alchemy_admin/image_loader"
12
13
  import ImageCropper from "alchemy_admin/image_cropper"
13
14
  import Initializer from "alchemy_admin/initializer"
15
+ import { LinkDialog } from "alchemy_admin/link_dialog"
14
16
  import pictureSelector from "alchemy_admin/picture_selector"
15
17
  import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
16
18
  import Sitemap from "alchemy_admin/sitemap"
17
- import SortableElements from "alchemy_admin/sortable_elements"
18
19
  import Spinner from "alchemy_admin/spinner"
19
20
  import PagePublicationFields from "alchemy_admin/page_publication_fields"
21
+ import { reloadPreview } from "alchemy_admin/components/preview_window"
22
+ import {
23
+ openConfirmDialog,
24
+ confirmToDeleteDialog
25
+ } from "alchemy_admin/confirm_dialog"
20
26
 
21
27
  // Web Components
22
- import "alchemy_admin/components/button"
23
- import "alchemy_admin/components/char_counter"
24
- import "alchemy_admin/components/clipboard_button"
25
- import "alchemy_admin/components/datepicker"
26
- import "alchemy_admin/components/dialog_link"
27
- import "alchemy_admin/components/element_editor"
28
- import "alchemy_admin/components/ingredient_group"
29
- import "alchemy_admin/components/link_buttons"
30
- import "alchemy_admin/components/node_select"
31
- import "alchemy_admin/components/uploader"
32
- import "alchemy_admin/components/overlay"
33
- import "alchemy_admin/components/page_select"
34
- import "alchemy_admin/components/select"
35
- import "alchemy_admin/components/spinner"
36
- import "alchemy_admin/components/tinymce"
28
+ import "alchemy_admin/components"
37
29
 
38
- import { setDefaultAnimation } from "shoelace"
39
-
40
- // Change the default animation for all dialogs
41
- setDefaultAnimation("tooltip.show", {
42
- keyframes: [
43
- { transform: "translateY(10px)", opacity: "0" },
44
- { transform: "translateY(0)", opacity: "1" }
45
- ],
46
- options: {
47
- duration: 100
48
- }
49
- })
50
-
51
- setDefaultAnimation("tooltip.hide", {
52
- keyframes: [
53
- { transform: "translateY(0)", opacity: "1" },
54
- { transform: "translateY(10px)", opacity: "0" }
55
- ],
56
- options: {
57
- duration: 100
58
- }
59
- })
30
+ // Shoelace Setup
31
+ import "alchemy_admin/shoelace_theme"
60
32
 
61
33
  // Global Alchemy object
62
34
  if (typeof window.Alchemy === "undefined") {
@@ -68,16 +40,20 @@ Object.assign(Alchemy, {
68
40
  ...Dirty,
69
41
  GUI,
70
42
  t: translate, // Global utility method for translating a given string
43
+ FixedElements,
44
+ growl,
71
45
  ImageLoader: ImageLoader.init,
72
46
  ImageCropper,
73
- Initializer,
74
47
  IngredientAnchorLink,
48
+ LinkDialog,
75
49
  pictureSelector,
76
50
  pleaseWaitOverlay,
77
51
  Sitemap,
78
- SortableElements,
79
52
  Spinner,
80
- PagePublicationFields
53
+ PagePublicationFields,
54
+ reloadPreview,
55
+ openConfirmDialog,
56
+ confirmToDeleteDialog
81
57
  })
82
58
 
83
59
  Rails.start()
@@ -0,0 +1,26 @@
1
+ module Alchemy
2
+ class Current < ActiveSupport::CurrentAttributes
3
+ attribute :preview_page, :page, :language, :site
4
+
5
+ def language
6
+ super || Language.default
7
+ end
8
+
9
+ def site
10
+ super || Site.first
11
+ end
12
+
13
+ def preview_page=(page)
14
+ super
15
+
16
+ self.page = page
17
+ self.language = page&.language
18
+ self.site = page&.site
19
+ end
20
+
21
+ def preview_page?(page = Current.page)
22
+ return false if preview_page.nil?
23
+ preview_page == page
24
+ end
25
+ end
26
+ end
@@ -101,7 +101,7 @@ module Alchemy
101
101
  scope :excluded, ->(names) { where.not(name: names) }
102
102
  scope :fixed, -> { where(fixed: true) }
103
103
  scope :unfixed, -> { where(fixed: false) }
104
- scope :from_current_site, -> { where(Language.table_name => {site_id: Site.current || Site.default}).joins(page: "language") }
104
+ scope :from_current_site, -> { where(Language.table_name => {site_id: Current.site}).joins(page: "language") }
105
105
  scope :folded, -> { where(folded: true) }
106
106
  scope :expanded, -> { where(folded: false) }
107
107
  scope :not_nested, -> { where(parent_element_id: nil) }
@@ -29,7 +29,14 @@ module Alchemy
29
29
  end
30
30
 
31
31
  def size_options
32
- sizes.map { |size| [".h#{size}", size] }
32
+ sizes.map do |size|
33
+ case size
34
+ when Array
35
+ size
36
+ else
37
+ [".h#{size}", size]
38
+ end
39
+ end
33
40
  end
34
41
 
35
42
  private
@@ -38,6 +38,12 @@ module Alchemy
38
38
  upsample
39
39
  ]
40
40
 
41
+ def alt_text(language: Alchemy::Current.language)
42
+ alt_tag.presence ||
43
+ picture&.description_for(language) ||
44
+ picture&.name&.humanize
45
+ end
46
+
41
47
  # The first 30 characters of the pictures name
42
48
  #
43
49
  # Used by the Element#preview_text method.
@@ -68,24 +68,26 @@ module Alchemy
68
68
  end
69
69
 
70
70
  def on_current_site
71
- on_site(Site.current)
71
+ on_site(Current.site)
72
72
  end
73
73
 
74
74
  # Store the current language in the current thread.
75
+ # @deprecated Use {Alchemy::Current#language=} instead.
75
76
  def current=(language)
76
- RequestStore.store[:alchemy_current_language] = language
77
+ Current.language = language
77
78
  end
79
+ deprecate "current=": :"Alchemy::Current.language=", deprecator: Alchemy::Deprecation
78
80
 
79
81
  # Current language from current thread or default.
82
+ # @deprecated Use {Alchemy::Current#language} instead.
80
83
  def current
81
- RequestStore.store[:alchemy_current_language] || default
84
+ Current.language
82
85
  end
86
+ deprecate current: :"Alchemy::Current.language", deprecator: Alchemy::Deprecation
83
87
 
84
88
  # The root page of the current language.
85
89
  def current_root_page
86
- return unless current
87
-
88
- current.pages.language_roots.first
90
+ Current.language&.pages&.language_roots&.first
89
91
  end
90
92
 
91
93
  # Default language for current site
@@ -37,9 +37,9 @@ module Alchemy
37
37
  class << self
38
38
  # Returns all root nodes for current language
39
39
  def language_root_nodes
40
- raise "No language found" if Language.current.nil?
40
+ raise "No language found" if Current.language.nil?
41
41
 
42
- roots.where(language_id: Language.current.id)
42
+ roots.where(language_id: Current.language.id)
43
43
  end
44
44
 
45
45
  def available_menu_names
@@ -80,21 +80,21 @@ module Alchemy
80
80
  # type: Richtext
81
81
  #
82
82
  def available_element_definitions(only_element_named = nil)
83
- @_element_definitions ||= if only_element_named
83
+ @_available_element_definitions ||= if only_element_named
84
84
  definition = Element.definition_by_name(only_element_named)
85
85
  element_definitions_by_name(definition["nestable_elements"])
86
86
  else
87
- element_definitions
87
+ element_definitions.dup
88
88
  end
89
89
 
90
- return [] if @_element_definitions.blank?
90
+ return [] if @_available_element_definitions.blank?
91
91
 
92
92
  existing_elements = draft_version.elements.not_nested
93
93
  @_existing_element_names = existing_elements.pluck(:name)
94
94
  delete_unique_element_definitions!
95
95
  delete_outnumbered_element_definitions!
96
96
 
97
- @_element_definitions
97
+ @_available_element_definitions
98
98
  end
99
99
 
100
100
  # All names of elements that can actually be placed on current page.
@@ -186,18 +186,18 @@ module Alchemy
186
186
  end
187
187
  end
188
188
 
189
- # Deletes unique and already present definitions from @_element_definitions.
189
+ # Deletes unique and already present definitions from @_available_element_definitions.
190
190
  #
191
191
  def delete_unique_element_definitions!
192
- @_element_definitions.delete_if do |element|
192
+ @_available_element_definitions.delete_if do |element|
193
193
  element["unique"] && @_existing_element_names.include?(element["name"])
194
194
  end
195
195
  end
196
196
 
197
- # Deletes limited and outnumbered definitions from @_element_definitions.
197
+ # Deletes limited and outnumbered definitions from @_available_element_definitions.
198
198
  #
199
199
  def delete_outnumbered_element_definitions!
200
- @_element_definitions.delete_if do |element|
200
+ @_available_element_definitions.delete_if do |element|
201
201
  outnumbered = @_existing_element_names.select { |name| name == element["name"] }
202
202
  element["amount"] && outnumbered.count >= element["amount"].to_i
203
203
  end
@@ -103,10 +103,10 @@ module Alchemy
103
103
  # page_layouts: [default_intro]
104
104
  #
105
105
  def available_on_site?(layout)
106
- return false unless Alchemy::Site.current
106
+ return false unless Alchemy::Current.site
107
107
 
108
- Alchemy::Site.current.definition.blank? ||
109
- Alchemy::Site.current.definition.fetch("page_layouts", []).include?(layout["name"])
108
+ Alchemy::Current.site.definition.blank? ||
109
+ Alchemy::Current.site.definition.fetch("page_layouts", []).include?(layout["name"])
110
110
  end
111
111
  end
112
112
  end
@@ -101,18 +101,22 @@ module Alchemy
101
101
  page_layout.parameterize.underscore
102
102
  end
103
103
 
104
- # Returns the version that's taken for Rails' recycable cache key.
104
+ # Returns the version string that's taken for Rails' recycable cache key.
105
105
  #
106
- # Uses the +published_at+ value that's updated when the user publishes the page.
106
+ def cache_version
107
+ last_modified_at&.to_s
108
+ end
109
+
110
+ # Returns the timestamp that the page was last modified at, regardless of through
111
+ # publishing or editing page, or through a change of related objects through ingredients.
112
+ # Respects the public version not changing if editing a preview.
107
113
  #
108
- # If the page is the current preview it uses the +updated_at+ value as cache key.
114
+ # In preview mode, it will take the draft version's updated_at timestamp.
115
+ # In public mode, it will take the public version's updated_at timestamp.
109
116
  #
110
- def cache_version
111
- if Page.current_preview == id
112
- updated_at.to_s
113
- else
114
- published_at.to_s
115
- end
117
+ def last_modified_at
118
+ relevant_page_version = (Current.preview_page == self) ? draft_version : public_version
119
+ relevant_page_version&.updated_at
116
120
  end
117
121
 
118
122
  # Returns true if the page cache control headers should be set.
@@ -94,11 +94,11 @@ module Alchemy
94
94
  #
95
95
  scope :searchables, -> { not_restricted.published.contentpages }
96
96
 
97
- # All pages from +Alchemy::Site.current+
97
+ # All pages from +Alchemy::Current.site+
98
98
  #
99
99
  scope :from_current_site,
100
100
  -> {
101
- where(Language.table_name => {site_id: Site.current || Site.default}).joins(:language)
101
+ where(Language.table_name => {site_id: Current.site}).joins(:language)
102
102
  }
103
103
 
104
104
  # All pages for xml sitemap
@@ -35,6 +35,7 @@ module Alchemy
35
35
  end
36
36
  end
37
37
  end
38
+ version.update(updated_at: public_on)
38
39
  page.update(published_at: public_on)
39
40
  end
40
41
  end
@@ -200,17 +200,19 @@ module Alchemy
200
200
  %w[name urlname title]
201
201
  end
202
202
 
203
- # Used to store the current page id previewed in the edit page template.
204
- #
203
+ # Used to store the current page previewed in the edit page template.
204
+ # @deprecated Use {Alchemy::Current#preview_page=} instead.
205
205
  def current_preview=(page)
206
- RequestStore.store[:alchemy_current_preview] = page&.id
206
+ Current.preview_page = page
207
207
  end
208
+ deprecate "current_preview=": :"Alchemy::Current.preview_page=", deprecator: Alchemy::Deprecation
208
209
 
209
- # Returns the current page id previewed in the edit page template.
210
- #
210
+ # Returns the current page previewed in the edit page template.
211
+ # @deprecated Use {Alchemy::Current#preview_page} instead.
211
212
  def current_preview
212
- RequestStore.store[:alchemy_current_preview]
213
+ Current.preview_page
213
214
  end
215
+ deprecate current_preview: :"Alchemy::Current.preview_page", deprecator: Alchemy::Deprecation
214
216
 
215
217
  # @return the language root page for given language id.
216
218
  # @param language_id [Fixnum]
@@ -271,8 +273,10 @@ module Alchemy
271
273
  options = [[Alchemy.t(:default, scope: "link_target_options"), ""]]
272
274
  link_target_options = Config.get(:link_target_options)
273
275
  link_target_options.each do |option|
276
+ # add an underscore to the options to provide the default syntax
277
+ # @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target
274
278
  options << [Alchemy.t(option, scope: "link_target_options",
275
- default: option.to_s.humanize), option]
279
+ default: option.to_s.humanize), "_#{option}"]
276
280
  end
277
281
  options
278
282
  end
@@ -375,7 +379,7 @@ module Alchemy
375
379
  #
376
380
  def unlock!
377
381
  if update_columns(locked_at: nil, locked_by: nil)
378
- Page.current_preview = nil
382
+ Current.preview_page = nil
379
383
  end
380
384
  end
381
385
 
@@ -444,25 +448,6 @@ module Alchemy
444
448
 
445
449
  delegate :public_until=, to: :public_version, allow_nil: true
446
450
 
447
- # Updates an Alchemy::Page based on a new ordering to be applied to it
448
- #
449
- # Note: Page's urls should not be updated (and a legacy URL created) if nesting is OFF
450
- # or if the URL is the same
451
- #
452
- # @param [TreeNode]
453
- # A tree node with new lft, rgt, depth, url, parent_id and restricted indexes to be updated
454
- #
455
- def update_node!(node)
456
- hash = {lft: node.left, rgt: node.right, parent_id: node.parent, depth: node.depth, restricted: node.restricted}
457
-
458
- if urlname != node.url
459
- LegacyPageUrl.create(page_id: id, urlname: urlname)
460
- hash[:urlname] = node.url
461
- end
462
-
463
- update_columns(hash)
464
- end
465
-
466
451
  # Holds an instance of +FixedAttributes+
467
452
  def fixed_attributes
468
453
  @_fixed_attributes ||= FixedAttributes.new(self)
@@ -555,7 +540,7 @@ module Alchemy
555
540
  end
556
541
 
557
542
  def set_language
558
- self.language = parent&.language || Language.current
543
+ self.language = parent&.language || Current.language
559
544
  set_language_code
560
545
  end
561
546
 
@@ -55,6 +55,9 @@ module Alchemy
55
55
  has_many :elements, through: :picture_ingredients
56
56
  has_many :pages, through: :elements
57
57
  has_many :thumbs, class_name: "Alchemy::PictureThumb", dependent: :destroy
58
+ has_many :descriptions, class_name: "Alchemy::PictureDescription", dependent: :destroy
59
+
60
+ accepts_nested_attributes_for :descriptions, allow_destroy: true, reject_if: ->(attr) { attr[:text].blank? }
58
61
 
59
62
  # Raise error, if picture is in use (aka. assigned to an Picture ingredient)
60
63
  #
@@ -231,6 +234,11 @@ module Alchemy
231
234
  }
232
235
  end
233
236
 
237
+ # Returns the picture description for a given language.
238
+ def description_for(language)
239
+ descriptions.find_by(language: language)&.text
240
+ end
241
+
234
242
  # Returns an uri escaped name.
235
243
  #
236
244
  def urlname
@@ -0,0 +1,8 @@
1
+ module Alchemy
2
+ class PictureDescription < ActiveRecord::Base
3
+ belongs_to :picture, class_name: "Alchemy::Picture"
4
+ belongs_to :language, class_name: "Alchemy::Language"
5
+
6
+ validates_uniqueness_of :picture_id, scope: :language_id
7
+ end
8
+ end
@@ -43,7 +43,7 @@ module Alchemy
43
43
 
44
44
  @picture = picture
45
45
  @options = options
46
- @render_format = options[:format] || picture.default_render_format
46
+ @render_format = (options[:format] || picture.default_render_format).to_s
47
47
  end
48
48
 
49
49
  # Process a variant of picture
@@ -35,7 +35,7 @@ module Alchemy
35
35
 
36
36
  # Returns true if this site is the current site
37
37
  def current?
38
- self.class.current == self
38
+ Current.site == self
39
39
  end
40
40
 
41
41
  # Returns the path to site's view partial.
@@ -57,23 +57,26 @@ module Alchemy
57
57
  end
58
58
 
59
59
  class << self
60
+ # @deprecated Use {Alchemy::Current#site=} instead.
60
61
  def current=(site)
61
- RequestStore.store[:alchemy_current_site] = site
62
+ Current.site = site
62
63
  end
64
+ deprecate "current=": :"Alchemy::Current.site=", deprecator: Alchemy::Deprecation
63
65
 
66
+ # @deprecated Use {Alchemy::Current#site} instead.
64
67
  def current
65
- RequestStore.store[:alchemy_current_site] || default
68
+ Current.site
66
69
  end
70
+ deprecate current: :"Alchemy::Current.site", deprecator: Alchemy::Deprecation
67
71
 
68
- def default
69
- Site.first
70
- end
72
+ alias_method :default, :first
73
+ deprecate default: :first, deprecator: Alchemy::Deprecation
71
74
 
72
75
  def find_for_host(host)
73
76
  # These are split up into two separate queries in order to run the
74
77
  # fastest query first (selecting the domain by its primary host name).
75
78
  #
76
- find_by(host: host) || find_in_aliases(host) || default
79
+ find_by(host: host) || find_in_aliases(host) || first
77
80
  end
78
81
 
79
82
  def find_in_aliases(host)
@@ -7,8 +7,16 @@ module Alchemy
7
7
  :file_name,
8
8
  :file_mime_type,
9
9
  :file_size,
10
+ :icon_css_class,
10
11
  :tag_list,
11
12
  :created_at,
12
13
  :updated_at
14
+
15
+ attribute :url do
16
+ Alchemy::Engine.routes.url_helpers.download_attachment_path(
17
+ id: object.id,
18
+ name: object.file_name
19
+ )
20
+ end
13
21
  end
14
22
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class PageNodeSerializer < ActiveModel::Serializer
5
+ attributes :id,
6
+ :url_path,
7
+ :parent_id
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
- <% if !@preview_mode && @page && can?(:edit_content, @page) %>
1
+ <% if !Alchemy::Current.preview_page? && @page && can?(:edit_content, @page) %>
2
2
  <alchemy-menubar>
3
3
  <template>
4
4
  <%= stylesheet_link_tag("alchemy/menubar") %>
@@ -1,4 +1,4 @@
1
- <% if @preview_mode %>
1
+ <% if Alchemy::Current.preview_page? %>
2
2
  <script type="text/javascript">
3
3
  Alchemy = { locale: "<%= session[:alchemy_locale] %>" };
4
4
  </script>
@@ -1,12 +1,12 @@
1
1
  <% if Alchemy::Attachment.tag_counts.any? %>
2
2
  <h3><%= Alchemy.t("Filter by tag") %></h3>
3
- <%= js_filter_field '.tag-list li' %>
3
+ <%= render Alchemy::Admin::ListFilter.new(".tag-list li") %>
4
4
  <ul>
5
5
  <%= render_tag_list('Alchemy::Attachment') %>
6
6
  </ul>
7
7
  <% if search_filter_params[:tagged_with].present? %>
8
8
  <%= link_to(
9
- render_icon(:times, size: 'xs') + Alchemy.t('Remove tag filter'),
9
+ render_icon(:times, size: '1x') + Alchemy.t('Remove tag filter'),
10
10
  url_for(search_filter_params.except(:tagged_with)),
11
11
  remote: request.xhr?,
12
12
  class: 'secondary button small with_icon'
@@ -1,5 +1,4 @@
1
1
  (function() {
2
2
  var dialog = Alchemy.currentDialog();
3
3
  dialog.dialog_body.html('<%= j render("archive_overlay") %>');
4
- Alchemy.ListFilter(dialog.dialog_body);
5
4
  })();
@@ -2,9 +2,8 @@
2
2
  url: {action: :update, q: search_filter_params[:q], page: params[:page]}) do |f| -%>
3
3
  <%= f.input :name, input_html: {autofocus: true} %>
4
4
  <%= f.input :file_name, input_html: {autofocus: true}, hint: Alchemy.t(:attachment_filename_notice) %>
5
- <div class="input string autocomplete_tag_list">
6
- <%= f.label :tag_list %>
7
- <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %>
8
- </div>
5
+ <%= render Alchemy::Admin::TagsAutocomplete.new do %>
6
+ <%= f.input :tag_list, input_html: { value: f.object.tag_list.join(",") } %>
7
+ <% end %>
9
8
  <%= f.submit Alchemy.t(:save) %>
10
9
  <% end %>