alchemy_cms 8.3.11 → 8.4.0

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 (368) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +58 -10
  4. data/app/assets/builds/alchemy/admin/page-select.css +1 -1
  5. data/app/assets/builds/alchemy/admin/print.css +1 -1
  6. data/app/assets/builds/alchemy/admin/remote-select.css +1 -0
  7. data/app/assets/builds/alchemy/admin/tags-autocomplete.css +1 -0
  8. data/app/assets/builds/alchemy/admin.css +4 -4
  9. data/app/assets/builds/alchemy/alchemy_admin.min.js +1 -1
  10. data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -1
  11. data/app/assets/builds/alchemy/dark-theme.css +1 -1
  12. data/app/assets/builds/alchemy/light-theme.css +1 -1
  13. data/app/assets/builds/alchemy/preview.min.js +1 -1
  14. data/app/assets/builds/alchemy/theme.css +1 -1
  15. data/app/assets/builds/alchemy/welcome.css +1 -1
  16. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  17. data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -1
  18. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
  19. data/app/assets/builds/tinymce/skins/ui/alchemy-dark/skin.min.css +1 -1
  20. data/app/assets/images/alchemy/icons-sprite.svg +1 -1
  21. data/app/components/alchemy/admin/dashboard/widgets/greeting.html.erb +1 -1
  22. data/app/components/alchemy/admin/dashboard/widgets/news_reader.html.erb +38 -0
  23. data/app/components/alchemy/admin/dashboard/widgets/news_reader.rb +54 -0
  24. data/app/components/alchemy/admin/dashboard/widgets/online_users.html.erb +1 -1
  25. data/app/components/alchemy/admin/dashboard/widgets/recent_pages.html.erb +1 -1
  26. data/app/components/alchemy/admin/dashboard/widgets/system_info.html.erb +11 -3
  27. data/app/components/alchemy/admin/dashboard/widgets/system_info.rb +6 -1
  28. data/app/components/alchemy/admin/element_editor.html.erb +1 -1
  29. data/app/components/alchemy/admin/element_select.rb +30 -20
  30. data/app/components/alchemy/admin/element_status_indicators.html.erb +16 -0
  31. data/app/components/alchemy/admin/element_status_indicators.rb +31 -0
  32. data/app/components/alchemy/admin/link_dialog/external_tab.rb +2 -1
  33. data/app/components/alchemy/admin/main_navigation/entry.rb +61 -0
  34. data/app/components/alchemy/admin/main_navigation/menu.rb +21 -0
  35. data/app/components/alchemy/admin/main_navigation/sub_navigation.rb +31 -0
  36. data/app/components/alchemy/admin/page_node.html.erb +1 -1
  37. data/app/components/alchemy/admin/page_permitted_roles_select.html.erb +11 -0
  38. data/app/components/alchemy/admin/page_permitted_roles_select.rb +35 -0
  39. data/app/components/alchemy/admin/page_select.rb +18 -8
  40. data/app/components/alchemy/admin/preview_time_select.rb +1 -1
  41. data/app/components/alchemy/admin/toolbar_button.rb +43 -20
  42. data/app/components/alchemy/admin/uploader_button.rb +66 -0
  43. data/app/components/alchemy/ingredients/base_editor.rb +10 -1
  44. data/app/components/alchemy/ingredients/boolean_editor.rb +1 -0
  45. data/app/components/alchemy/ingredients/color_editor.rb +6 -3
  46. data/app/components/alchemy/ingredients/datetime_view.rb +1 -1
  47. data/app/components/alchemy/ingredients/picture_view.rb +2 -4
  48. data/app/components/alchemy/menubar.html.erb +157 -0
  49. data/app/components/alchemy/menubar.rb +15 -0
  50. data/app/controllers/alchemy/admin/attachments_controller.rb +12 -9
  51. data/app/controllers/alchemy/admin/base_controller.rb +34 -21
  52. data/app/controllers/alchemy/admin/dashboard_controller.rb +4 -0
  53. data/app/controllers/alchemy/admin/elements_controller.rb +4 -23
  54. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  55. data/app/controllers/alchemy/admin/nodes_controller.rb +1 -1
  56. data/app/controllers/alchemy/admin/pages_controller.rb +1 -1
  57. data/app/controllers/alchemy/admin/pictures_controller.rb +6 -3
  58. data/app/controllers/alchemy/admin/resources_controller.rb +5 -5
  59. data/app/controllers/alchemy/admin/sites_controller.rb +1 -1
  60. data/app/controllers/alchemy/base_controller.rb +24 -16
  61. data/app/controllers/alchemy/pages_controller.rb +29 -8
  62. data/app/controllers/alchemy/sitemap_controller.rb +58 -0
  63. data/app/controllers/concerns/alchemy/admin/csp_protection.rb +43 -0
  64. data/app/controllers/concerns/alchemy/admin/current_language.rb +1 -1
  65. data/app/controllers/concerns/alchemy/admin/resource_filter.rb +3 -1
  66. data/{lib → app/controllers/concerns}/alchemy/controller_actions.rb +13 -0
  67. data/app/controllers/concerns/alchemy/legacy_page_redirects.rb +4 -4
  68. data/app/helpers/alchemy/admin/base_helper.rb +4 -1
  69. data/app/helpers/alchemy/admin/form_helper.rb +0 -2
  70. data/app/helpers/alchemy/admin/navigation_helper.rb +10 -181
  71. data/app/helpers/alchemy/elements_block_helper.rb +14 -0
  72. data/app/helpers/alchemy/resources_helper.rb +15 -1
  73. data/app/javascript/alchemy_admin/components/attachment_select.js +11 -20
  74. data/app/javascript/alchemy_admin/components/auto_submit.js +2 -4
  75. data/app/javascript/alchemy_admin/components/button.js +0 -6
  76. data/app/javascript/alchemy_admin/components/clear_schedule_button.js +21 -0
  77. data/app/javascript/alchemy_admin/components/color_select.js +50 -45
  78. data/app/javascript/alchemy_admin/components/conditional_field.js +50 -0
  79. data/app/javascript/alchemy_admin/components/dirty_guard.js +77 -0
  80. data/app/javascript/alchemy_admin/components/dom_id_select.js +17 -10
  81. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +7 -2
  82. data/app/javascript/alchemy_admin/components/element_editor.js +29 -56
  83. data/app/javascript/alchemy_admin/components/element_select.js +26 -66
  84. data/app/javascript/alchemy_admin/components/icon.js +9 -4
  85. data/app/javascript/alchemy_admin/{image_cropper.js → components/image_cropper.js} +39 -53
  86. data/app/javascript/alchemy_admin/components/image_overlay_link.js +14 -0
  87. data/app/javascript/alchemy_admin/components/index.js +12 -1
  88. data/app/javascript/alchemy_admin/components/main_navi_entry.js +75 -0
  89. data/app/javascript/alchemy_admin/components/menu_node.js +73 -0
  90. data/app/javascript/alchemy_admin/components/news_reader.js +109 -0
  91. data/app/javascript/alchemy_admin/components/node_folder.js +36 -0
  92. data/app/javascript/alchemy_admin/components/node_form.js +34 -0
  93. data/app/javascript/alchemy_admin/components/node_select.js +25 -30
  94. data/app/javascript/alchemy_admin/components/node_tree.js +56 -0
  95. data/app/javascript/alchemy_admin/components/page_node.js +2 -1
  96. data/app/javascript/alchemy_admin/components/page_select.js +16 -34
  97. data/app/javascript/alchemy_admin/components/picture_archive.js +92 -0
  98. data/app/javascript/alchemy_admin/components/picture_editor.js +2 -1
  99. data/app/javascript/alchemy_admin/components/preview_window.js +11 -3
  100. data/app/javascript/alchemy_admin/components/remote_select.js +461 -81
  101. data/app/javascript/alchemy_admin/components/select.js +26 -88
  102. data/app/javascript/alchemy_admin/components/sitemap.js +2 -1
  103. data/app/javascript/alchemy_admin/components/sortable_elements.js +2 -1
  104. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +63 -48
  105. data/app/javascript/alchemy_admin/components/tinymce.js +2 -1
  106. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +3 -2
  107. data/app/javascript/alchemy_admin/components/uploader.js +19 -3
  108. data/app/javascript/alchemy_admin/confirm_dialog.js +7 -1
  109. data/app/javascript/alchemy_admin/dialog.js +182 -110
  110. data/app/javascript/alchemy_admin/hotkeys.js +0 -5
  111. data/app/javascript/alchemy_admin/i18n.js +3 -9
  112. data/app/javascript/alchemy_admin/image_overlay.js +58 -42
  113. data/app/javascript/alchemy_admin/initializer.js +2 -2
  114. data/app/javascript/alchemy_admin/link_dialog.js +15 -2
  115. data/app/javascript/alchemy_admin/routes.js +18 -0
  116. data/app/javascript/alchemy_admin/turbo_stream_actions.js +88 -0
  117. data/app/javascript/alchemy_admin/utils/ajax.js +14 -3
  118. data/app/javascript/alchemy_admin/utils/events.js +5 -0
  119. data/app/javascript/alchemy_admin/utils/json_script.js +12 -0
  120. data/app/javascript/alchemy_admin/utils/tom_select.js +171 -0
  121. data/app/javascript/alchemy_admin.js +2 -19
  122. data/app/javascript/preview.js +131 -113
  123. data/app/jobs/alchemy/invalidate_elements_cache_job.rb +1 -1
  124. data/app/models/alchemy/admin/content_security_policy.rb +143 -0
  125. data/app/models/alchemy/admin/filters/base.rb +7 -0
  126. data/app/models/alchemy/admin/filters/datepicker.rb +1 -1
  127. data/app/models/alchemy/admin/filters/select.rb +20 -7
  128. data/app/models/alchemy/attachment.rb +14 -7
  129. data/app/models/alchemy/cache_control.rb +104 -0
  130. data/app/models/alchemy/current.rb +1 -1
  131. data/app/models/alchemy/eager_loading.rb +0 -2
  132. data/app/models/alchemy/element.rb +23 -8
  133. data/app/models/alchemy/element_definition.rb +1 -0
  134. data/app/models/alchemy/elements_repository.rb +11 -0
  135. data/app/models/alchemy/folded_page.rb +5 -1
  136. data/app/models/alchemy/ingredient.rb +26 -0
  137. data/app/models/alchemy/ingredients/datetime.rb +1 -1
  138. data/app/models/alchemy/ingredients/link.rb +2 -0
  139. data/app/models/alchemy/ingredients/picture.rb +3 -0
  140. data/app/models/alchemy/ingredients/text.rb +2 -0
  141. data/app/models/alchemy/language.rb +19 -7
  142. data/app/models/alchemy/legacy_page_url.rb +6 -1
  143. data/app/models/alchemy/node.rb +51 -0
  144. data/app/models/alchemy/page/page_natures.rb +41 -8
  145. data/app/models/alchemy/page/page_scopes.rb +23 -0
  146. data/app/models/alchemy/page/sitemap_cache_key.rb +56 -0
  147. data/app/models/alchemy/page.rb +88 -32
  148. data/app/models/alchemy/page_definition.rb +1 -1
  149. data/app/models/alchemy/page_mutex.rb +16 -0
  150. data/app/models/alchemy/page_version.rb +24 -1
  151. data/app/models/alchemy/permissions.rb +6 -5
  152. data/app/models/alchemy/picture.rb +15 -7
  153. data/app/models/alchemy/picture_description.rb +22 -0
  154. data/app/models/alchemy/picture_thumb.rb +18 -0
  155. data/app/models/alchemy/publishable/timestamp_resolver.rb +70 -0
  156. data/app/models/alchemy/resource.rb +1 -1
  157. data/app/models/alchemy/safe_url_validator.rb +34 -0
  158. data/app/models/alchemy/site.rb +8 -3
  159. data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +7 -3
  160. data/app/models/alchemy/storage_adapter/active_storage.rb +4 -1
  161. data/app/models/alchemy/tag.rb +10 -4
  162. data/{lib → app/models/concerns}/alchemy/filetypes.rb +5 -0
  163. data/app/models/concerns/alchemy/picture_thumbnails.rb +61 -6
  164. data/app/models/concerns/alchemy/publishable.rb +14 -27
  165. data/app/models/concerns/alchemy/taggable.rb +92 -0
  166. data/app/models/concerns/alchemy/user_methods.rb +95 -0
  167. data/app/services/alchemy/duplicate_element.rb +5 -2
  168. data/app/services/alchemy/element_preloader.rb +1 -2
  169. data/app/stylesheets/alchemy/_custom-properties.scss +1 -0
  170. data/app/stylesheets/alchemy/_extends.scss +0 -8
  171. data/app/stylesheets/alchemy/_themes.scss +10 -9
  172. data/app/stylesheets/alchemy/admin/_empty-state.scss +29 -0
  173. data/app/stylesheets/alchemy/admin/_help.scss +10 -0
  174. data/app/stylesheets/alchemy/admin/_tom-select.scss +104 -15
  175. data/app/stylesheets/alchemy/admin/archive.scss +56 -11
  176. data/app/stylesheets/alchemy/admin/base.scss +4 -5
  177. data/app/stylesheets/alchemy/admin/buttons.scss +12 -1
  178. data/app/stylesheets/alchemy/admin/dashboard.scss +150 -20
  179. data/app/stylesheets/alchemy/admin/dialogs.scss +27 -36
  180. data/app/stylesheets/alchemy/admin/element-select.scss +1 -1
  181. data/app/stylesheets/alchemy/admin/elements.scss +8 -24
  182. data/app/stylesheets/alchemy/admin/flash.scss +2 -1
  183. data/app/stylesheets/alchemy/admin/forms.scss +60 -24
  184. data/app/stylesheets/alchemy/admin/frame.scss +40 -4
  185. data/app/stylesheets/alchemy/admin/image_library.scss +9 -12
  186. data/app/stylesheets/alchemy/admin/navigation.scss +17 -14
  187. data/app/stylesheets/alchemy/admin/node-select.scss +13 -37
  188. data/app/stylesheets/alchemy/admin/nodes.scss +1 -1
  189. data/app/stylesheets/alchemy/admin/page-select.scss +6 -97
  190. data/app/stylesheets/alchemy/admin/print.scss +0 -4
  191. data/app/stylesheets/alchemy/admin/remote-select.scss +170 -0
  192. data/app/stylesheets/alchemy/admin/search.scss +2 -3
  193. data/app/stylesheets/alchemy/admin/selects.scss +1 -294
  194. data/app/stylesheets/alchemy/admin/sitemap.scss +2 -1
  195. data/app/stylesheets/alchemy/admin/tables.scss +7 -1
  196. data/app/stylesheets/alchemy/admin/tags-autocomplete.scss +17 -0
  197. data/app/stylesheets/alchemy/admin/tags.scss +0 -13
  198. data/app/stylesheets/alchemy/admin/toolbar.scss +9 -6
  199. data/app/stylesheets/alchemy/admin/upload.scss +61 -1
  200. data/app/stylesheets/alchemy/admin.scss +4 -1
  201. data/app/types/alchemy/cache_control_type.rb +84 -0
  202. data/app/views/alchemy/_edit_mode.html.erb +1 -1
  203. data/app/views/alchemy/_menubar.html.erb +2 -154
  204. data/app/views/alchemy/_preview_mode_code.html.erb +0 -3
  205. data/app/views/alchemy/admin/_left_menu.html.erb +18 -5
  206. data/app/views/alchemy/admin/_top_menu.html.erb +1 -1
  207. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +5 -4
  208. data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +3 -3
  209. data/app/views/alchemy/admin/attachments/_files_list.html.erb +71 -83
  210. data/app/views/alchemy/admin/attachments/assign.turbo_stream.erb +8 -0
  211. data/app/views/alchemy/admin/attachments/index.html.erb +32 -7
  212. data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +1 -1
  213. data/app/views/alchemy/admin/crop.html.erb +20 -20
  214. data/app/views/alchemy/admin/dashboard/_footer.html.erb +0 -3
  215. data/app/views/alchemy/admin/dashboard/_top.html.erb +1 -0
  216. data/app/views/alchemy/admin/dashboard/help.html.erb +84 -57
  217. data/app/views/alchemy/admin/dashboard/license.html.erb +6 -0
  218. data/app/views/alchemy/admin/elements/_footer.html.erb +0 -1
  219. data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
  220. data/app/views/alchemy/admin/elements/_header.html.erb +2 -19
  221. data/app/views/alchemy/admin/elements/_preview_text_quote.html.erb +3 -0
  222. data/app/views/alchemy/admin/elements/_schedule.html.erb +7 -7
  223. data/app/views/alchemy/admin/elements/_toolbar.html.erb +1 -1
  224. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +1 -1
  225. data/app/views/alchemy/admin/elements/index.html.erb +1 -1
  226. data/app/views/alchemy/admin/elements/publish.turbo_stream.erb +2 -2
  227. data/app/views/alchemy/admin/elements/update.turbo_stream.erb +31 -0
  228. data/app/views/alchemy/admin/ingredients/update.turbo_stream.erb +6 -3
  229. data/app/views/alchemy/admin/languages/_table.html.erb +0 -2
  230. data/app/views/alchemy/admin/languages/index.html.erb +25 -29
  231. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +1 -3
  232. data/app/views/alchemy/admin/layoutpages/edit.html.erb +1 -1
  233. data/app/views/alchemy/admin/layoutpages/index.html.erb +26 -7
  234. data/app/views/alchemy/admin/nodes/_form.html.erb +3 -21
  235. data/app/views/alchemy/admin/nodes/_node.html.erb +93 -88
  236. data/app/views/alchemy/admin/nodes/index.html.erb +15 -23
  237. data/app/views/alchemy/admin/pages/_create_language_form.html.erb +2 -6
  238. data/app/views/alchemy/admin/pages/_form.html.erb +2 -1
  239. data/app/views/alchemy/admin/pages/_publication_fields.html.erb +1 -1
  240. data/app/views/alchemy/admin/pages/_table.html.erb +2 -4
  241. data/app/views/alchemy/admin/pages/_toolbar.html.erb +2 -1
  242. data/app/views/alchemy/admin/pages/edit.html.erb +10 -21
  243. data/app/views/alchemy/admin/pages/flush.turbo_stream.erb +1 -1
  244. data/app/views/alchemy/admin/pages/index.html.erb +26 -31
  245. data/app/views/alchemy/admin/pages/info.html.erb +2 -2
  246. data/app/views/alchemy/admin/pages/update.turbo_stream.erb +2 -2
  247. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +1 -1
  248. data/app/views/alchemy/admin/partials/_routes.html.erb +24 -48
  249. data/app/views/alchemy/admin/pictures/_archive.html.erb +3 -17
  250. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
  251. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +5 -3
  252. data/app/views/alchemy/admin/pictures/_form.html.erb +0 -1
  253. data/app/views/alchemy/admin/pictures/_infos.html.erb +5 -1
  254. data/app/views/alchemy/admin/pictures/_picture.html.erb +1 -1
  255. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +3 -4
  256. data/app/views/alchemy/admin/pictures/assign.turbo_stream.erb +7 -0
  257. data/app/views/alchemy/admin/pictures/index.html.erb +38 -30
  258. data/app/views/alchemy/admin/pictures/show.html.erb +41 -41
  259. data/app/views/alchemy/admin/resources/_empty_state.html.erb +27 -0
  260. data/app/views/alchemy/admin/resources/_pagination.html.erb +1 -1
  261. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +1 -1
  262. data/app/views/alchemy/admin/resources/_resource_table.html.erb +0 -2
  263. data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +3 -2
  264. data/app/views/alchemy/admin/resources/index.html.erb +32 -13
  265. data/app/views/alchemy/admin/sites/index.html.erb +24 -22
  266. data/app/views/alchemy/admin/styleguide/index.html.erb +35 -12
  267. data/app/views/alchemy/admin/tags/index.html.erb +27 -25
  268. data/app/views/alchemy/admin/tinymce/_setup.html.erb +6 -8
  269. data/app/views/alchemy/admin/translations/_en.js +3 -0
  270. data/app/views/alchemy/admin/uploader/_button.html.erb +11 -17
  271. data/app/views/alchemy/admin/uploader/_setup.html.erb +3 -3
  272. data/app/views/alchemy/pages/sitemap.xml.erb +2 -0
  273. data/app/views/kaminari/alchemy/_first_page.html.erb +1 -2
  274. data/app/views/kaminari/alchemy/_gap.html.erb +0 -1
  275. data/app/views/kaminari/alchemy/_last_page.html.erb +1 -2
  276. data/app/views/kaminari/alchemy/_next_page.html.erb +1 -2
  277. data/app/views/kaminari/alchemy/_page.html.erb +1 -2
  278. data/app/views/kaminari/alchemy/_paginator.html.erb +0 -1
  279. data/app/views/kaminari/alchemy/_prev_page.html.erb +1 -2
  280. data/app/views/layouts/alchemy/admin/dialog.html.erb +3 -0
  281. data/app/views/layouts/alchemy/admin.html.erb +5 -6
  282. data/config/brakeman.ignore +23 -1
  283. data/config/importmap.rb +10 -8
  284. data/config/initializers/dragonfly.rb +21 -17
  285. data/config/locales/alchemy.en.yml +52 -11
  286. data/config/routes.rb +3 -3
  287. data/db/migrate/20260727120000_add_permitted_roles_to_alchemy_pages.rb +23 -0
  288. data/db/migrate/20260820120000_add_cached_tag_list_to_taggables.rb +57 -0
  289. data/lib/alchemy/configuration/number_option.rb +13 -0
  290. data/lib/alchemy/configuration/proc_option.rb +13 -0
  291. data/lib/alchemy/configuration.rb +2 -0
  292. data/lib/alchemy/configurations/admin_components.rb +30 -0
  293. data/lib/alchemy/configurations/format_matchers.rb +7 -1
  294. data/lib/alchemy/configurations/main.rb +103 -4
  295. data/lib/alchemy/configurations/menu.rb +91 -0
  296. data/lib/alchemy/configurations/menu_item.rb +123 -0
  297. data/lib/alchemy/configurations/sitemap.rb +1 -0
  298. data/lib/alchemy/configurations/uploader.rb +15 -1
  299. data/lib/alchemy/engine.rb +0 -6
  300. data/lib/alchemy/i18n.rb +26 -0
  301. data/lib/alchemy/modules.rb +52 -50
  302. data/lib/alchemy/tasks/generate_thumbnails.rb +99 -0
  303. data/lib/alchemy/tasks/tidy.rb +1 -1
  304. data/lib/alchemy/test_support/capybara_helpers.rb +48 -37
  305. data/lib/alchemy/test_support/current_language_shared_examples.rb +1 -1
  306. data/lib/alchemy/test_support/factories/dummy_user_factory.rb +4 -1
  307. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +132 -0
  308. data/lib/alchemy/test_support/shared_ingredient_examples.rb +52 -0
  309. data/lib/alchemy/test_support/shared_publishable_examples.rb +29 -4
  310. data/lib/alchemy/upgrader/eight_four.rb +237 -0
  311. data/lib/alchemy/upgrader/register_module_rewriter.rb +177 -0
  312. data/lib/alchemy/upgrader.rb +2 -1
  313. data/lib/alchemy/version.rb +1 -1
  314. data/lib/alchemy.rb +0 -1
  315. data/lib/alchemy_cms.rb +0 -13
  316. data/lib/generators/alchemy/elements/templates/view.html.erb +1 -1
  317. data/lib/generators/alchemy/elements/templates/view.html.haml +1 -1
  318. data/lib/generators/alchemy/elements/templates/view.html.slim +1 -1
  319. data/lib/generators/alchemy/install/install_generator.rb +2 -13
  320. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +24 -9
  321. data/lib/generators/alchemy/module/module_generator.rb +1 -0
  322. data/lib/generators/alchemy/module/templates/ability.rb.tt +1 -1
  323. data/lib/generators/alchemy/module/templates/module_config.rb.tt +16 -18
  324. data/lib/generators/alchemy/user_columns_migration/templates/migration.rb.tt +19 -0
  325. data/lib/generators/alchemy/user_columns_migration/user_columns_migration_generator.rb +30 -0
  326. data/lib/tasks/alchemy/thumbnails.rake +16 -29
  327. data/lib/tasks/alchemy/upgrade.rake +45 -3
  328. data/vendor/javascript/cropperjs.min.js +3 -3
  329. data/vendor/javascript/floating-ui.min.js +1 -1
  330. data/vendor/javascript/tinymce.min.js +5 -5
  331. data/vendor/javascript/tom-select.min.js +1 -1
  332. metadata +85 -65
  333. data/app/javascript/alchemy_admin/components/action.js +0 -50
  334. data/app/javascript/alchemy_admin/dirty.js +0 -55
  335. data/app/javascript/alchemy_admin/node_tree.js +0 -82
  336. data/app/javascript/alchemy_admin/picture_selector.js +0 -67
  337. data/app/javascript/alchemy_admin/templates/compiled.js +0 -1
  338. data/app/javascript/alchemy_admin/templates/node_folder.hbs +0 -3
  339. data/app/stylesheets/alchemy/admin/attachment-select.scss +0 -19
  340. data/app/views/alchemy/admin/_main_navi.html.erb +0 -6
  341. data/app/views/alchemy/admin/attachments/assign.js.erb +0 -15
  342. data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +0 -30
  343. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +0 -20
  344. data/app/views/alchemy/admin/partials/_sub_navigation.html.erb +0 -11
  345. data/app/views/alchemy/admin/pictures/assign.js.erb +0 -13
  346. data/app/views/alchemy/base/error_notice.js.erb +0 -1
  347. data/app/views/alchemy/base/permission_denied.js.erb +0 -3
  348. data/app/views/alchemy/base/redirect.js.erb +0 -11
  349. data/config/alchemy/config.yml +0 -217
  350. data/config/alchemy/modules.yml +0 -82
  351. data/config/initializers/rails_live_reload.rb +0 -13
  352. data/lib/alchemy/dev_support/live_reload_watcher.rb +0 -5
  353. data/lib/alchemy/taggable.rb +0 -49
  354. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -34
  355. data/vendor/assets/stylesheets/alchemy_admin/select2.css +0 -1
  356. data/vendor/javascript/handlebars.min.js +0 -29
  357. data/vendor/javascript/jquery.min.js +0 -2
  358. data/vendor/javascript/rails-ujs.min.js +0 -1
  359. data/vendor/javascript/select2.min.js +0 -23
  360. /data/{lib → app/controllers/concerns}/alchemy/ability_helper.rb +0 -0
  361. /data/{lib → app/controllers/concerns}/alchemy/admin/locale.rb +0 -0
  362. /data/{lib → app/controllers/concerns}/alchemy/admin/preview_time.rb +0 -0
  363. /data/{lib → app/controllers/concerns}/alchemy/admin/timezone.rb +0 -0
  364. /data/{lib → app/controllers/concerns}/alchemy/configuration_methods.rb +0 -0
  365. /data/{lib → app/models}/alchemy/admin/preview_url.rb +0 -0
  366. /data/{lib → app/models/concerns}/alchemy/hints.rb +0 -0
  367. /data/{lib → app/models/concerns}/alchemy/name_conversions.rb +0 -0
  368. /data/{lib → app/services}/alchemy/elements_finder.rb +0 -0
@@ -0,0 +1,170 @@
1
+ /* Shared layout for the remote selects (page, node, attachment, and external
2
+ * consumers like solidus and blish). Two surfaces share one leading column and
3
+ * the same cell classes:
4
+ *
5
+ * - the dropdown option, a two row grid (name/aside over secondary/badge)
6
+ * - the selected item shown in the control, a single flex row
7
+ *
8
+ * The grid keeps the option's secondary text (a url) on its own row where the
9
+ * flexible name column lets it ellipsis. The control instead needs the url to
10
+ * give way *before* the name does, which a grid cannot express (its `1fr` name
11
+ * track collapses first), so the control is a flex row where the trailing text
12
+ * shrinks ten times faster than the name.
13
+ */
14
+ .remote-select--entry,
15
+ .remote-select--selection {
16
+ /* Redefine the spacing and colour tokens with Alchemy's defaults, so the
17
+ * layout also works when only this file is loaded (e.g. from a gem that links
18
+ * the standalone stylesheet).
19
+ */
20
+ --size-1: var(--spacing-0, 2px);
21
+ --size-2: var(--spacing-1, 4px);
22
+ --size-3: var(--spacing-2, 8px);
23
+ --small-font-size: var(--font-size_small, 10px);
24
+ --color: var(--text-color, hsla(224deg, 23%, 26%, 0.8));
25
+ --muted-color: var(--text-color-muted, hsla(224deg, 23%, 26%, 0.5));
26
+ --highlight-color: var(--color-grey_light, hsl(0deg, 0%, 94%));
27
+ --badge-bg-color: var(--color-grey_light, hsl(0deg, 0%, 94%));
28
+ --badge-highlight-bg-color: var(--color-white, hsl(0deg, 0%, 100%));
29
+ --border-radius: var(--border-radius_medium, 3px);
30
+
31
+ color: var(--color);
32
+
33
+ .ts-dropdown .option.active & {
34
+ color: var(--highlight-color);
35
+ }
36
+ }
37
+
38
+ .remote-select--entry {
39
+ display: grid;
40
+ grid-template-columns: auto minmax(0, 1fr) auto;
41
+ /* The name and its secondary text sit in different sizes; align them on a
42
+ * common baseline rather than centering both boxes.
43
+ */
44
+ align-items: baseline;
45
+ column-gap: var(--size-3);
46
+ row-gap: var(--size-1);
47
+ overflow: hidden;
48
+ }
49
+
50
+ .remote-select--selection {
51
+ display: flex;
52
+ align-items: baseline;
53
+ column-gap: var(--size-3);
54
+ overflow: hidden;
55
+ }
56
+
57
+ /* Leading column, two mutually exclusive modes. A select uses one or the other,
58
+ * never both.
59
+ */
60
+ .remote-select--icon {
61
+ grid-column: 1;
62
+ grid-row: 1;
63
+ /* An icon has no baseline of its own, it would hang off the bottom edge. */
64
+ align-self: center;
65
+
66
+ .icon {
67
+ fill: currentColor;
68
+ }
69
+ }
70
+
71
+ .remote-select--media {
72
+ grid-column: 1;
73
+ grid-row: 1 / -1;
74
+ align-self: center;
75
+ width: var(--remote-select-media-size, 20px);
76
+ height: var(--remote-select-media-size, 20px);
77
+ border-radius: var(--border-radius_medium);
78
+ object-fit: cover;
79
+ }
80
+
81
+ .remote-select--primary {
82
+ grid-column: 2;
83
+ grid-row: 1;
84
+ }
85
+
86
+ .remote-select--secondary {
87
+ grid-column: 2;
88
+ grid-row: 2;
89
+ color: var(--muted-color);
90
+ font-size: var(--small-font-size);
91
+
92
+ .ts-dropdown .option.active & {
93
+ color: var(--highlight-color);
94
+ }
95
+ }
96
+
97
+ .remote-select--primary,
98
+ .remote-select--secondary,
99
+ .remote-select--selection-name,
100
+ .remote-select--selection-aside {
101
+ min-width: 0;
102
+ overflow: hidden;
103
+ text-overflow: ellipsis;
104
+ white-space: nowrap;
105
+ }
106
+
107
+ .remote-select--aside {
108
+ grid-column: 3;
109
+ grid-row: 1;
110
+ }
111
+
112
+ .remote-select--secondary-aside {
113
+ grid-column: 3;
114
+ grid-row: 2;
115
+ }
116
+
117
+ .remote-select--aside,
118
+ .remote-select--secondary-aside {
119
+ justify-self: end;
120
+ white-space: nowrap;
121
+ font-size: var(--small-font-size);
122
+ }
123
+
124
+ /* The trailing text of the selected item (e.g. a url). It shrinks much faster
125
+ * than the name, so a long url gives way to an ellipsis before the name does.
126
+ */
127
+ .remote-select--selection-aside {
128
+ flex: 0 10 auto;
129
+ margin-left: auto;
130
+ color: var(--muted-color);
131
+ font-size: var(--small-font-size);
132
+
133
+ .ts-dropdown .option.active & {
134
+ color: var(--highlight-color);
135
+ }
136
+ }
137
+
138
+ /* Head truncation, for a url whose identifying part is its leaf segment. The
139
+ * helper wraps the text in <bdi>; without the wrapper the leading slash reorders
140
+ * to the wrong end.
141
+ */
142
+ .remote-select--truncate-head {
143
+ direction: rtl;
144
+ text-align: left;
145
+ }
146
+
147
+ .remote-select--badge {
148
+ display: inline-block;
149
+ background-color: var(--badge-bg-color);
150
+ border-radius: var(--border-radius);
151
+ padding: var(--size-1) var(--size-2);
152
+ text-transform: uppercase;
153
+ color: var(--color);
154
+
155
+ .ts-dropdown .option.active & {
156
+ color: var(--color);
157
+ background-color: var(--badge-highlight-bg-color);
158
+ }
159
+ }
160
+
161
+ @media (prefers-color-scheme: dark) {
162
+ .remote-select--entry,
163
+ .remote-select--selection {
164
+ --color: var(--select-text-color, hsl(200deg, 5%, 70%));
165
+ --muted-color: var(--a-lighter-grey, hsl(200deg, 5%, 70%));
166
+ --highlight-color: var(--select-hover-text-color, hsl(0deg, 0%, 100%));
167
+ --badge-bg-color: var(--a-dark-grey, hsl(200deg, 5%, 17.5%));
168
+ --badge-highlight-bg-color: var(--badge-bg-color);
169
+ }
170
+ }
@@ -42,8 +42,7 @@
42
42
  justify-content: center;
43
43
  }
44
44
 
45
- #toolbar,
46
- #overlay_toolbar {
45
+ .toolbar {
47
46
  .search_form {
48
47
  position: absolute;
49
48
  top: 0;
@@ -55,7 +54,7 @@
55
54
  height: inherit;
56
55
  }
57
56
 
58
- .search_input_field {
57
+ input.search_input_field {
59
58
  width: 0;
60
59
  height: 100%;
61
60
  border: none;
@@ -1,5 +1,4 @@
1
1
  @use "sass:color";
2
- @use "alchemy_admin/select2";
3
2
  @use "../extends";
4
3
  @use "../mixins";
5
4
 
@@ -44,288 +43,8 @@ select {
44
43
  }
45
44
  }
46
45
 
47
- .select2-container {
48
- border-radius: var(--border-radius_medium);
49
-
50
- &.alchemy_selectbox {
51
- .select2-choice {
52
- width: 100%;
53
-
54
- @include mixins.button-defaults(
55
- $background-color: var(--select-background-color),
56
- $height: var(--form-field-height),
57
- $hover-color: var(--select-background-color),
58
- $hover-border-color: var(--select-hover-border-color),
59
- $padding: var(--form-field-padding),
60
- $border: 1px solid var(--select-border-color),
61
- $box-shadow: none,
62
- $color: var(--select-text-color),
63
- $margin: 0
64
- );
65
-
66
- & {
67
- background-image: none;
68
- display: flex;
69
- gap: var(--spacing-2);
70
- align-items: center;
71
- font-weight: normal;
72
- text-align: left;
73
- }
74
-
75
- .select2-chosen {
76
- flex-grow: 1;
77
- margin-right: 0;
78
- }
79
-
80
- .select2-arrow {
81
- position: static;
82
- background: transparent;
83
- border-left: 0 none;
84
-
85
- b {
86
- display: flex;
87
- align-items: center;
88
- justify-content: center;
89
- background-image: none;
90
- color: var(--icon-color);
91
-
92
- &:before {
93
- display: inline-block;
94
- content: "";
95
- width: 1rem;
96
- height: 1rem;
97
- background: var(--select-background-image) no-repeat;
98
- }
99
- }
100
- }
101
- }
102
-
103
- &.medium {
104
- width: var(--select-medium-width);
105
- }
106
-
107
- &.large {
108
- width: var(--select-large-width);
109
- }
110
-
111
- &.select2-container-active {
112
- .select2-choice,
113
- .select2-choices {
114
- @include mixins.default-focus-style(
115
- $box-shadow: 0 0 0 1px var(--focus-color)
116
- );
117
- }
118
- }
119
-
120
- &.select2-container-disabled {
121
- .select2-choice,
122
- .select2-choice:hover {
123
- background-image: none;
124
- background-color: var(--select-disabled-background-color);
125
- box-shadow: none;
126
- border-color: var(--select-disabled-border-color);
127
- cursor: not-allowed;
128
- color: var(--select-disabled-text-color);
129
-
130
- .select2-arrow {
131
- border-color: var(--select-disabled-border-color);
132
-
133
- b {
134
- color: var(--text-color-muted);
135
- }
136
- }
137
- }
138
- }
139
- }
140
-
141
- &.select2-allowclear {
142
- .select2-search-choice-close {
143
- position: static;
144
- display: inline-flex;
145
- left: auto;
146
- right: var(--spacing-1);
147
- top: 5px;
148
- background: none;
149
- text-decoration: none;
150
- align-items: center;
151
- justify-content: center;
152
- }
153
- }
154
-
155
- &.select2-container-multi {
156
- margin-top: 0 !important;
157
-
158
- &.select2-container-active {
159
- .select2-choices {
160
- @include mixins.default-focus-style(
161
- $box-shadow: 0 0 0 1px var(--focus-color)
162
- );
163
- }
164
- }
165
-
166
- &.select2-container-disabled {
167
- .select2-choices {
168
- background-color: var(--select-disabled-background-color);
169
- border-color: var(--select-disabled-border-color);
170
- cursor: not-allowed;
171
- color: var(--select-disabled-text-color);
172
- }
173
- }
174
-
175
- .select2-search-choice-close {
176
- position: absolute;
177
- left: initial;
178
- right: var(--spacing-1);
179
- }
180
-
181
- .select2-choices {
182
- @extend %default-input-style;
183
- padding: 0 var(--spacing-0);
184
- width: 100%;
185
-
186
- .select2-search-choice {
187
- display: flex;
188
- align-items: center;
189
- gap: var(--spacing-0);
190
- margin: var(--spacing-1);
191
- padding: var(--spacing-1) var(--spacing-6) var(--spacing-1)
192
- var(--spacing-2);
193
- background-image: none;
194
- border: 0 none;
195
- box-shadow: none;
196
- color: var(--text-color);
197
- background-color: var(--tag-background-color);
198
- }
199
-
200
- .select2-search-field {
201
- height: 29px;
202
-
203
- input {
204
- color: var(--form-field-text-color);
205
- padding: 3px 6px;
206
- margin: 0;
207
- width: 0;
208
-
209
- &.select2-active {
210
- background: var(--form-field-background-color) !important;
211
- }
212
- }
213
- }
214
- }
215
- }
216
-
217
- .select2-default {
218
- color: var(--text-color-muted) !important;
219
- }
220
-
221
- .select2-search-choice-close {
222
- &:before {
223
- content: "";
224
- display: inline-flex;
225
- width: 12px;
226
- height: 12px;
227
- background: var(--select-close-icon) no-repeat;
228
- }
229
-
230
- &:hover {
231
- color: var(--icon-color);
232
- }
233
- }
234
- }
235
-
236
- .select2-drop {
237
- background: var(--select-dropdown-background-color);
238
- box-shadow: var(--select-dropdown-box-shadow);
239
- color: var(--select-dropdown-text-color, var(--text-color));
240
- margin-top: 1px;
241
-
242
- &.select2-drop-above {
243
- margin-top: -1px;
244
-
245
- &.select2-drop-active {
246
- border-top-color: var(--select-dropdown-border-color);
247
- }
248
- }
249
-
250
- .select2-search {
251
- margin: var(--spacing-1) 0;
252
-
253
- .select2-input {
254
- width: inherit;
255
-
256
- &.select2-active {
257
- background: var(--form-field-background-color) !important;
258
- }
259
- }
260
- }
261
-
262
- &.select2-with-searchbox .select2-results > .select2-result:first-child {
263
- @include mixins.border-top-radius(0);
264
- }
265
-
266
- .select2-results {
267
- padding: 0;
268
- margin: 0;
269
-
270
- > .select2-result:first-child {
271
- @include mixins.border-top-radius(var(--border-radius_medium));
272
- }
273
-
274
- > .select2-result:last-child {
275
- @include mixins.border-bottom-radius(var(--border-radius_medium));
276
- }
277
-
278
- .select2-result {
279
- margin: 0;
280
- padding: var(--spacing-1) 0;
281
-
282
- &.select2-highlighted {
283
- background: var(--select-hover-bg-color, var(--color-blue_dark));
284
- color: var(--select-hover-text-color, var(--color-white));
285
- }
286
-
287
- &.select2-result-with-children {
288
- padding: 0;
289
- }
290
- }
291
-
292
- .select2-no-results,
293
- .select2-searching {
294
- padding: var(--spacing-2);
295
- margin: 0;
296
- background: var(--select-dropdown-background-color);
297
- }
298
-
299
- .select2-result-label {
300
- padding: var(--spacing-1) var(--spacing-2);
301
-
302
- em {
303
- text-decoration: underline;
304
- }
305
- }
306
-
307
- ul.select2-result-sub > li .select2-result-label {
308
- padding-left: var(--spacing-4);
309
- }
310
- }
311
-
312
- &.select2-drop-active {
313
- border-radius: var(--border-radius_medium);
314
- border: 1px solid var(--select-dropdown-border-color);
315
- }
316
-
317
- .select2-more-results,
318
- .select2-ajax-error {
319
- padding: var(--spacing-2);
320
- margin-bottom: 0;
321
- background: var(--notice-error-background-color);
322
- color: var(--notice-error-text-color);
323
- }
324
- }
325
-
326
46
  #filter_bar {
327
- select,
328
- .select2-container {
47
+ select {
329
48
  width: 100%;
330
49
  }
331
50
  }
@@ -340,15 +59,3 @@ select {
340
59
  margin-right: var(--spacing-2);
341
60
  }
342
61
  }
343
-
344
- // overriding important of select2 default style for retina screens
345
- @media only screen and (-webkit-min-device-pixel-ratio: 1.5),
346
- only screen and (min-resolution: 2dppx) {
347
- #alchemy {
348
- .select2-search input,
349
- .select2-container .select2-choice abbr,
350
- .select2-container .select2-choice .select2-arrow b {
351
- background-image: none !important;
352
- }
353
- }
354
- }
@@ -71,7 +71,7 @@ alchemy-sitemap {
71
71
  }
72
72
 
73
73
  #sitemap {
74
- padding: 0 0 104px 0;
74
+ padding: 0 0 var(--spacing-4) 0;
75
75
 
76
76
  .page-icon {
77
77
  display: inline-flex;
@@ -102,6 +102,7 @@ alchemy-sitemap {
102
102
 
103
103
  li {
104
104
  padding-left: 0;
105
+ list-style: none;
105
106
 
106
107
  li {
107
108
  padding-left: var(--sitemap-line-height);
@@ -10,6 +10,12 @@ table {
10
10
  &.list .tools {
11
11
  background-color: transparent;
12
12
  padding: var(--spacing-2);
13
+
14
+ .button_to {
15
+ display: inline-block;
16
+ margin: 0;
17
+ padding: 0;
18
+ }
13
19
  }
14
20
  }
15
21
 
@@ -93,7 +99,7 @@ tr.odd td,
93
99
  }
94
100
 
95
101
  .list tr .tools {
96
- width: 40px;
102
+ width: 1%; // keep it small and let the content decide the width
97
103
 
98
104
  &.long {
99
105
  width: 60px;
@@ -0,0 +1,17 @@
1
+ /* Compiled standalone as well, so pages that do not load the admin stylesheet
2
+ * can link it for the <alchemy-tags-autocomplete> component.
3
+ */
4
+ @use "tom-select";
5
+
6
+ .autocomplete_tag_list {
7
+ // Prefix each Tom Select tag pill with the tag glyph. The base pill styling
8
+ // in `_tom-select.scss` lays the item out as an inline flex row, so the icon
9
+ // slots in before the label as `[icon] [label] [remove]`.
10
+ .ts-control > .item::before {
11
+ content: "";
12
+ flex: 0 0 1rem;
13
+ width: 1rem;
14
+ height: 1rem;
15
+ background: var(--tag-background-image) no-repeat center / contain;
16
+ }
17
+ }
@@ -121,16 +121,3 @@
121
121
  }
122
122
  }
123
123
  }
124
-
125
- .autocomplete_tag_list {
126
- .select2-container.select2-container-multi {
127
- .select2-search-choice {
128
- padding: var(--spacing-1) 22px;
129
- background-image: var(--tag-background-image);
130
- background-repeat: no-repeat;
131
- background-position: var(--spacing-1);
132
- background-color: var(--tag-background-color);
133
- background-size: 1rem;
134
- }
135
- }
136
- }
@@ -1,14 +1,21 @@
1
1
  @use "../extends";
2
2
  @use "../variables" as vars;
3
3
 
4
- #toolbar {
5
- @extend %gradiated-toolbar;
4
+ .toolbar {
5
+ display: flex;
6
6
  align-items: center;
7
7
  gap: var(--spacing-2);
8
8
  margin-right: 0px;
9
9
  position: relative;
10
+ background: var(--toolbar-bg-color);
11
+ padding: var(--toolbar-padding);
12
+ height: var(--toolbar-height);
13
+ border-bottom: var(--toolbar-border-bottom);
10
14
 
11
15
  .toolbar_button {
16
+ --button-height: var(--icon-button-height);
17
+ --button-margin: 0;
18
+
12
19
  form {
13
20
  margin: 0;
14
21
  display: inline-flex;
@@ -87,10 +94,6 @@
87
94
  }
88
95
  }
89
96
 
90
- #overlay_toolbar {
91
- @extend %gradiated-toolbar;
92
- }
93
-
94
97
  .button_group {
95
98
  position: relative;
96
99
  display: inline-block;
@@ -41,6 +41,16 @@
41
41
  }
42
42
  }
43
43
 
44
+ // #main_content scrolls, so the absolute drop indicator (pinned to the top of
45
+ // the scrollable content) scrolls out of view. Pin it to the visible viewport
46
+ // area instead, inset past the top and main menu.
47
+ #main_content.dragover::after {
48
+ position: fixed;
49
+ inset: var(--top-menu-height) 0 0 var(--main-menu-width);
50
+ width: auto;
51
+ height: auto;
52
+ }
53
+
44
54
  alchemy-upload-progress {
45
55
  --border-radius: var(--border-radius_medium);
46
56
  --pogress_value-width: calc(232px - var(--spacing-2));
@@ -55,9 +65,13 @@ alchemy-upload-progress {
55
65
  );
56
66
  height: auto;
57
67
  left: 0;
68
+ max-height: 100%;
58
69
  opacity: 0;
70
+ // Only scroll vertically; setting overflow-y alone forces overflow-x to auto,
71
+ // which shows a spurious horizontal scrollbar for the grid's sub-pixel overflow.
72
+ overflow: hidden auto;
59
73
  padding: var(--spacing-4);
60
- position: fixed;
74
+ position: absolute;
61
75
  transition: var(--transition-duration) ease-in-out;
62
76
  width: 100%;
63
77
  z-index: 150;
@@ -161,6 +175,52 @@ alchemy-upload-progress {
161
175
  }
162
176
  }
163
177
 
178
+ // Inside the scrolling, menu-padded #main_content (a z-index:0 stacking context
179
+ // below the fixed main menu) an absolutely positioned progress hides behind the
180
+ // menu and scrolls away. Pin it to the viewport, inset past the main menu.
181
+ #main_content > alchemy-upload-progress {
182
+ position: fixed;
183
+ left: var(--main-menu-width);
184
+ right: 0;
185
+ width: auto;
186
+ }
187
+
188
+ // Mimic the picture archive grid: each upload is a thumbnail-shaped card.
189
+ alchemy-upload-progress[layout="card"] {
190
+ .single-uploads {
191
+ grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
192
+ }
193
+
194
+ alchemy-file-upload {
195
+ flex-direction: column;
196
+ align-items: stretch;
197
+ text-align: center;
198
+
199
+ img {
200
+ height: 120px;
201
+ width: 100%;
202
+ }
203
+
204
+ .description {
205
+ align-self: stretch;
206
+ }
207
+
208
+ button {
209
+ position: absolute;
210
+ top: var(--spacing-1);
211
+ right: var(--spacing-1);
212
+ margin-left: 0;
213
+ }
214
+ }
215
+ }
216
+
217
+ // Default: each upload is a stacked row, fitting resource table views.
218
+ alchemy-upload-progress[layout="row"] {
219
+ .single-uploads {
220
+ grid-template-columns: 1fr;
221
+ }
222
+ }
223
+
164
224
  .successful {
165
225
  --file-upload_progress-indicator-color: var(
166
226
  --file-upload_progress-indicator-color-successful