alchemy_cms 8.3.10 → 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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +58 -10
- data/app/assets/builds/alchemy/admin/page-select.css +1 -1
- data/app/assets/builds/alchemy/admin/print.css +1 -1
- data/app/assets/builds/alchemy/admin/remote-select.css +1 -0
- data/app/assets/builds/alchemy/admin/tags-autocomplete.css +1 -0
- data/app/assets/builds/alchemy/admin.css +4 -4
- data/app/assets/builds/alchemy/alchemy_admin.min.js +1 -1
- data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -1
- data/app/assets/builds/alchemy/dark-theme.css +1 -1
- data/app/assets/builds/alchemy/light-theme.css +1 -1
- data/app/assets/builds/alchemy/preview.min.js +1 -1
- data/app/assets/builds/alchemy/theme.css +1 -1
- data/app/assets/builds/alchemy/welcome.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -1
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
- data/app/assets/builds/tinymce/skins/ui/alchemy-dark/skin.min.css +1 -1
- data/app/assets/images/alchemy/icons-sprite.svg +1 -1
- data/app/components/alchemy/admin/dashboard/widgets/greeting.html.erb +1 -1
- data/app/components/alchemy/admin/dashboard/widgets/news_reader.html.erb +38 -0
- data/app/components/alchemy/admin/dashboard/widgets/news_reader.rb +54 -0
- data/app/components/alchemy/admin/dashboard/widgets/online_users.html.erb +1 -1
- data/app/components/alchemy/admin/dashboard/widgets/recent_pages.html.erb +1 -1
- data/app/components/alchemy/admin/dashboard/widgets/system_info.html.erb +11 -3
- data/app/components/alchemy/admin/dashboard/widgets/system_info.rb +6 -1
- data/app/components/alchemy/admin/element_editor.html.erb +1 -1
- data/app/components/alchemy/admin/element_select.rb +30 -20
- data/app/components/alchemy/admin/element_status_indicators.html.erb +16 -0
- data/app/components/alchemy/admin/element_status_indicators.rb +31 -0
- data/app/components/alchemy/admin/link_dialog/external_tab.rb +2 -1
- data/app/components/alchemy/admin/main_navigation/entry.rb +61 -0
- data/app/components/alchemy/admin/main_navigation/menu.rb +21 -0
- data/app/components/alchemy/admin/main_navigation/sub_navigation.rb +31 -0
- data/app/components/alchemy/admin/page_node.html.erb +1 -1
- data/app/components/alchemy/admin/page_permitted_roles_select.html.erb +11 -0
- data/app/components/alchemy/admin/page_permitted_roles_select.rb +35 -0
- data/app/components/alchemy/admin/page_select.rb +18 -8
- data/app/components/alchemy/admin/preview_time_select.rb +1 -1
- data/app/components/alchemy/admin/toolbar_button.rb +43 -20
- data/app/components/alchemy/admin/uploader_button.rb +66 -0
- data/app/components/alchemy/ingredients/base_editor.rb +10 -1
- data/app/components/alchemy/ingredients/boolean_editor.rb +1 -0
- data/app/components/alchemy/ingredients/color_editor.rb +6 -3
- data/app/components/alchemy/ingredients/datetime_view.rb +1 -1
- data/app/components/alchemy/ingredients/picture_view.rb +2 -4
- data/app/components/alchemy/menubar.html.erb +157 -0
- data/app/components/alchemy/menubar.rb +15 -0
- data/app/controllers/alchemy/admin/attachments_controller.rb +12 -9
- data/app/controllers/alchemy/admin/base_controller.rb +34 -21
- data/app/controllers/alchemy/admin/dashboard_controller.rb +4 -0
- data/app/controllers/alchemy/admin/elements_controller.rb +4 -23
- data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
- data/app/controllers/alchemy/admin/nodes_controller.rb +1 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +1 -1
- data/app/controllers/alchemy/admin/pictures_controller.rb +6 -3
- data/app/controllers/alchemy/admin/resources_controller.rb +5 -5
- data/app/controllers/alchemy/admin/sites_controller.rb +1 -1
- data/app/controllers/alchemy/base_controller.rb +24 -16
- data/app/controllers/alchemy/pages_controller.rb +29 -8
- data/app/controllers/alchemy/sitemap_controller.rb +58 -0
- data/app/controllers/concerns/alchemy/admin/csp_protection.rb +43 -0
- data/app/controllers/concerns/alchemy/admin/current_language.rb +1 -1
- data/app/controllers/concerns/alchemy/admin/resource_filter.rb +3 -1
- data/{lib → app/controllers/concerns}/alchemy/controller_actions.rb +13 -0
- data/app/controllers/concerns/alchemy/legacy_page_redirects.rb +4 -4
- data/app/helpers/alchemy/admin/base_helper.rb +4 -1
- data/app/helpers/alchemy/admin/form_helper.rb +0 -2
- data/app/helpers/alchemy/admin/navigation_helper.rb +10 -181
- data/app/helpers/alchemy/elements_block_helper.rb +14 -0
- data/app/helpers/alchemy/resources_helper.rb +15 -1
- data/app/javascript/alchemy_admin/components/attachment_select.js +11 -20
- data/app/javascript/alchemy_admin/components/auto_submit.js +2 -4
- data/app/javascript/alchemy_admin/components/button.js +0 -6
- data/app/javascript/alchemy_admin/components/clear_schedule_button.js +21 -0
- data/app/javascript/alchemy_admin/components/color_select.js +50 -45
- data/app/javascript/alchemy_admin/components/conditional_field.js +50 -0
- data/app/javascript/alchemy_admin/components/dirty_guard.js +77 -0
- data/app/javascript/alchemy_admin/components/dom_id_select.js +17 -10
- data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +7 -2
- data/app/javascript/alchemy_admin/components/element_editor.js +29 -56
- data/app/javascript/alchemy_admin/components/element_select.js +26 -66
- data/app/javascript/alchemy_admin/components/icon.js +9 -4
- data/app/javascript/alchemy_admin/{image_cropper.js → components/image_cropper.js} +39 -53
- data/app/javascript/alchemy_admin/components/image_overlay_link.js +14 -0
- data/app/javascript/alchemy_admin/components/index.js +12 -1
- data/app/javascript/alchemy_admin/components/main_navi_entry.js +75 -0
- data/app/javascript/alchemy_admin/components/menu_node.js +73 -0
- data/app/javascript/alchemy_admin/components/news_reader.js +109 -0
- data/app/javascript/alchemy_admin/components/node_folder.js +36 -0
- data/app/javascript/alchemy_admin/components/node_form.js +34 -0
- data/app/javascript/alchemy_admin/components/node_select.js +25 -30
- data/app/javascript/alchemy_admin/components/node_tree.js +56 -0
- data/app/javascript/alchemy_admin/components/page_node.js +2 -1
- data/app/javascript/alchemy_admin/components/page_select.js +16 -34
- data/app/javascript/alchemy_admin/components/picture_archive.js +92 -0
- data/app/javascript/alchemy_admin/components/picture_editor.js +2 -1
- data/app/javascript/alchemy_admin/components/preview_window.js +11 -3
- data/app/javascript/alchemy_admin/components/remote_select.js +461 -81
- data/app/javascript/alchemy_admin/components/select.js +26 -88
- data/app/javascript/alchemy_admin/components/sitemap.js +2 -1
- data/app/javascript/alchemy_admin/components/sortable_elements.js +2 -1
- data/app/javascript/alchemy_admin/components/tags_autocomplete.js +63 -48
- data/app/javascript/alchemy_admin/components/tinymce.js +2 -1
- data/app/javascript/alchemy_admin/components/uploader/file_upload.js +3 -2
- data/app/javascript/alchemy_admin/components/uploader.js +19 -3
- data/app/javascript/alchemy_admin/confirm_dialog.js +7 -1
- data/app/javascript/alchemy_admin/dialog.js +182 -110
- data/app/javascript/alchemy_admin/hotkeys.js +0 -5
- data/app/javascript/alchemy_admin/i18n.js +3 -9
- data/app/javascript/alchemy_admin/image_overlay.js +58 -42
- data/app/javascript/alchemy_admin/initializer.js +2 -2
- data/app/javascript/alchemy_admin/link_dialog.js +15 -2
- data/app/javascript/alchemy_admin/routes.js +18 -0
- data/app/javascript/alchemy_admin/turbo_stream_actions.js +88 -0
- data/app/javascript/alchemy_admin/utils/ajax.js +14 -3
- data/app/javascript/alchemy_admin/utils/events.js +5 -0
- data/app/javascript/alchemy_admin/utils/json_script.js +12 -0
- data/app/javascript/alchemy_admin/utils/tom_select.js +171 -0
- data/app/javascript/alchemy_admin.js +2 -19
- data/app/javascript/preview.js +131 -113
- data/app/jobs/alchemy/invalidate_elements_cache_job.rb +1 -1
- data/app/models/alchemy/admin/content_security_policy.rb +143 -0
- data/app/models/alchemy/admin/filters/base.rb +7 -0
- data/app/models/alchemy/admin/filters/datepicker.rb +1 -1
- data/app/models/alchemy/admin/filters/select.rb +20 -7
- data/app/models/alchemy/attachment.rb +14 -7
- data/app/models/alchemy/cache_control.rb +104 -0
- data/app/models/alchemy/current.rb +1 -1
- data/app/models/alchemy/eager_loading.rb +0 -2
- data/app/models/alchemy/element.rb +23 -8
- data/app/models/alchemy/element_definition.rb +1 -0
- data/app/models/alchemy/elements_repository.rb +11 -0
- data/app/models/alchemy/folded_page.rb +5 -1
- data/app/models/alchemy/ingredient.rb +26 -0
- data/app/models/alchemy/ingredients/datetime.rb +1 -1
- data/app/models/alchemy/ingredients/link.rb +2 -0
- data/app/models/alchemy/ingredients/picture.rb +3 -0
- data/app/models/alchemy/ingredients/text.rb +2 -0
- data/app/models/alchemy/language.rb +19 -7
- data/app/models/alchemy/legacy_page_url.rb +6 -1
- data/app/models/alchemy/node.rb +51 -0
- data/app/models/alchemy/page/page_natures.rb +41 -8
- data/app/models/alchemy/page/page_scopes.rb +23 -0
- data/app/models/alchemy/page/sitemap_cache_key.rb +56 -0
- data/app/models/alchemy/page.rb +88 -32
- data/app/models/alchemy/page_definition.rb +1 -1
- data/app/models/alchemy/page_mutex.rb +16 -0
- data/app/models/alchemy/page_version.rb +24 -1
- data/app/models/alchemy/permissions.rb +6 -5
- data/app/models/alchemy/picture.rb +15 -7
- data/app/models/alchemy/picture_description.rb +22 -0
- data/app/models/alchemy/picture_thumb.rb +18 -0
- data/app/models/alchemy/publishable/timestamp_resolver.rb +70 -0
- data/app/models/alchemy/resource.rb +1 -1
- data/app/models/alchemy/safe_url_validator.rb +34 -0
- data/app/models/alchemy/site.rb +8 -3
- data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +7 -3
- data/app/models/alchemy/storage_adapter/active_storage.rb +4 -1
- data/app/models/alchemy/tag.rb +10 -4
- data/{lib → app/models/concerns}/alchemy/filetypes.rb +5 -0
- data/app/models/concerns/alchemy/picture_thumbnails.rb +61 -6
- data/app/models/concerns/alchemy/publishable.rb +14 -29
- data/app/models/concerns/alchemy/taggable.rb +92 -0
- data/app/models/concerns/alchemy/user_methods.rb +95 -0
- data/app/services/alchemy/duplicate_element.rb +5 -2
- data/app/services/alchemy/element_preloader.rb +1 -2
- data/app/stylesheets/alchemy/_custom-properties.scss +1 -0
- data/app/stylesheets/alchemy/_extends.scss +0 -8
- data/app/stylesheets/alchemy/_themes.scss +10 -9
- data/app/stylesheets/alchemy/admin/_empty-state.scss +29 -0
- data/app/stylesheets/alchemy/admin/_help.scss +10 -0
- data/app/stylesheets/alchemy/admin/_tom-select.scss +104 -15
- data/app/stylesheets/alchemy/admin/archive.scss +56 -11
- data/app/stylesheets/alchemy/admin/base.scss +4 -5
- data/app/stylesheets/alchemy/admin/buttons.scss +12 -1
- data/app/stylesheets/alchemy/admin/dashboard.scss +150 -20
- data/app/stylesheets/alchemy/admin/dialogs.scss +27 -36
- data/app/stylesheets/alchemy/admin/element-select.scss +1 -1
- data/app/stylesheets/alchemy/admin/elements.scss +8 -24
- data/app/stylesheets/alchemy/admin/flash.scss +2 -1
- data/app/stylesheets/alchemy/admin/forms.scss +60 -24
- data/app/stylesheets/alchemy/admin/frame.scss +40 -4
- data/app/stylesheets/alchemy/admin/image_library.scss +9 -12
- data/app/stylesheets/alchemy/admin/navigation.scss +17 -14
- data/app/stylesheets/alchemy/admin/node-select.scss +13 -37
- data/app/stylesheets/alchemy/admin/nodes.scss +1 -1
- data/app/stylesheets/alchemy/admin/page-select.scss +6 -97
- data/app/stylesheets/alchemy/admin/print.scss +0 -4
- data/app/stylesheets/alchemy/admin/remote-select.scss +170 -0
- data/app/stylesheets/alchemy/admin/search.scss +2 -3
- data/app/stylesheets/alchemy/admin/selects.scss +1 -294
- data/app/stylesheets/alchemy/admin/sitemap.scss +2 -1
- data/app/stylesheets/alchemy/admin/tables.scss +7 -1
- data/app/stylesheets/alchemy/admin/tags-autocomplete.scss +17 -0
- data/app/stylesheets/alchemy/admin/tags.scss +0 -13
- data/app/stylesheets/alchemy/admin/toolbar.scss +9 -6
- data/app/stylesheets/alchemy/admin/upload.scss +61 -1
- data/app/stylesheets/alchemy/admin.scss +4 -1
- data/app/stylesheets/tinymce/skins/ui/alchemy/skin.scss +19 -0
- data/app/stylesheets/tinymce/skins/ui/alchemy-dark/skin.scss +19 -0
- data/app/types/alchemy/cache_control_type.rb +84 -0
- data/app/views/alchemy/_edit_mode.html.erb +1 -1
- data/app/views/alchemy/_menubar.html.erb +2 -154
- data/app/views/alchemy/_preview_mode_code.html.erb +0 -3
- data/app/views/alchemy/admin/_left_menu.html.erb +18 -5
- data/app/views/alchemy/admin/_top_menu.html.erb +1 -1
- data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +5 -4
- data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +3 -3
- data/app/views/alchemy/admin/attachments/_files_list.html.erb +71 -83
- data/app/views/alchemy/admin/attachments/assign.turbo_stream.erb +8 -0
- data/app/views/alchemy/admin/attachments/index.html.erb +32 -7
- data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +1 -1
- data/app/views/alchemy/admin/crop.html.erb +20 -20
- data/app/views/alchemy/admin/dashboard/_footer.html.erb +0 -3
- data/app/views/alchemy/admin/dashboard/_top.html.erb +1 -0
- data/app/views/alchemy/admin/dashboard/help.html.erb +84 -57
- data/app/views/alchemy/admin/dashboard/license.html.erb +6 -0
- data/app/views/alchemy/admin/elements/_footer.html.erb +0 -1
- data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_header.html.erb +2 -19
- data/app/views/alchemy/admin/elements/_preview_text_quote.html.erb +3 -0
- data/app/views/alchemy/admin/elements/_schedule.html.erb +7 -7
- data/app/views/alchemy/admin/elements/_toolbar.html.erb +1 -1
- data/app/views/alchemy/admin/elements/create.turbo_stream.erb +1 -1
- data/app/views/alchemy/admin/elements/index.html.erb +1 -1
- data/app/views/alchemy/admin/elements/publish.turbo_stream.erb +2 -2
- data/app/views/alchemy/admin/elements/update.turbo_stream.erb +31 -0
- data/app/views/alchemy/admin/ingredients/update.turbo_stream.erb +6 -3
- data/app/views/alchemy/admin/languages/_table.html.erb +0 -2
- data/app/views/alchemy/admin/languages/index.html.erb +25 -29
- data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +1 -3
- data/app/views/alchemy/admin/layoutpages/edit.html.erb +1 -1
- data/app/views/alchemy/admin/layoutpages/index.html.erb +26 -7
- data/app/views/alchemy/admin/nodes/_form.html.erb +3 -21
- data/app/views/alchemy/admin/nodes/_node.html.erb +93 -88
- data/app/views/alchemy/admin/nodes/index.html.erb +15 -23
- data/app/views/alchemy/admin/pages/_create_language_form.html.erb +2 -6
- data/app/views/alchemy/admin/pages/_form.html.erb +2 -1
- data/app/views/alchemy/admin/pages/_publication_fields.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_table.html.erb +2 -4
- data/app/views/alchemy/admin/pages/_toolbar.html.erb +2 -1
- data/app/views/alchemy/admin/pages/edit.html.erb +10 -21
- data/app/views/alchemy/admin/pages/flush.turbo_stream.erb +1 -1
- data/app/views/alchemy/admin/pages/index.html.erb +26 -31
- data/app/views/alchemy/admin/pages/info.html.erb +2 -2
- data/app/views/alchemy/admin/pages/update.turbo_stream.erb +2 -2
- data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +1 -1
- data/app/views/alchemy/admin/partials/_routes.html.erb +24 -48
- data/app/views/alchemy/admin/pictures/_archive.html.erb +3 -17
- data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +5 -3
- data/app/views/alchemy/admin/pictures/_form.html.erb +0 -1
- data/app/views/alchemy/admin/pictures/_infos.html.erb +5 -1
- data/app/views/alchemy/admin/pictures/_picture.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +3 -4
- data/app/views/alchemy/admin/pictures/assign.turbo_stream.erb +7 -0
- data/app/views/alchemy/admin/pictures/index.html.erb +38 -30
- data/app/views/alchemy/admin/pictures/show.html.erb +41 -41
- data/app/views/alchemy/admin/resources/_empty_state.html.erb +27 -0
- data/app/views/alchemy/admin/resources/_pagination.html.erb +1 -1
- data/app/views/alchemy/admin/resources/_per_page_select.html.erb +1 -1
- data/app/views/alchemy/admin/resources/_resource_table.html.erb +0 -2
- data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +3 -2
- data/app/views/alchemy/admin/resources/index.html.erb +32 -13
- data/app/views/alchemy/admin/sites/index.html.erb +24 -22
- data/app/views/alchemy/admin/styleguide/index.html.erb +35 -12
- data/app/views/alchemy/admin/tags/index.html.erb +27 -25
- data/app/views/alchemy/admin/tinymce/_setup.html.erb +6 -8
- data/app/views/alchemy/admin/translations/_en.js +3 -0
- data/app/views/alchemy/admin/uploader/_button.html.erb +11 -17
- data/app/views/alchemy/admin/uploader/_setup.html.erb +3 -3
- data/app/views/alchemy/pages/sitemap.xml.erb +2 -0
- data/app/views/kaminari/alchemy/_first_page.html.erb +1 -2
- data/app/views/kaminari/alchemy/_gap.html.erb +0 -1
- data/app/views/kaminari/alchemy/_last_page.html.erb +1 -2
- data/app/views/kaminari/alchemy/_next_page.html.erb +1 -2
- data/app/views/kaminari/alchemy/_page.html.erb +1 -2
- data/app/views/kaminari/alchemy/_paginator.html.erb +0 -1
- data/app/views/kaminari/alchemy/_prev_page.html.erb +1 -2
- data/app/views/layouts/alchemy/admin/dialog.html.erb +3 -0
- data/app/views/layouts/alchemy/admin.html.erb +5 -6
- data/config/brakeman.ignore +23 -1
- data/config/importmap.rb +10 -8
- data/config/initializers/dragonfly.rb +21 -17
- data/config/locales/alchemy.en.yml +53 -12
- data/config/routes.rb +3 -3
- data/db/migrate/20260727120000_add_permitted_roles_to_alchemy_pages.rb +23 -0
- data/db/migrate/20260820120000_add_cached_tag_list_to_taggables.rb +57 -0
- data/lib/alchemy/configuration/number_option.rb +13 -0
- data/lib/alchemy/configuration/proc_option.rb +13 -0
- data/lib/alchemy/configuration.rb +2 -0
- data/lib/alchemy/configurations/admin_components.rb +30 -0
- data/lib/alchemy/configurations/format_matchers.rb +7 -1
- data/lib/alchemy/configurations/main.rb +103 -4
- data/lib/alchemy/configurations/menu.rb +91 -0
- data/lib/alchemy/configurations/menu_item.rb +123 -0
- data/lib/alchemy/configurations/sitemap.rb +1 -0
- data/lib/alchemy/configurations/uploader.rb +15 -1
- data/lib/alchemy/engine.rb +0 -6
- data/lib/alchemy/i18n.rb +26 -0
- data/lib/alchemy/modules.rb +52 -50
- data/lib/alchemy/tasks/generate_thumbnails.rb +99 -0
- data/lib/alchemy/tasks/tidy.rb +1 -1
- data/lib/alchemy/test_support/capybara_helpers.rb +48 -37
- data/lib/alchemy/test_support/current_language_shared_examples.rb +1 -1
- data/lib/alchemy/test_support/factories/dummy_user_factory.rb +4 -1
- data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +132 -0
- data/lib/alchemy/test_support/shared_ingredient_examples.rb +52 -0
- data/lib/alchemy/test_support/shared_publishable_examples.rb +39 -4
- data/lib/alchemy/upgrader/eight_four.rb +237 -0
- data/lib/alchemy/upgrader/register_module_rewriter.rb +177 -0
- data/lib/alchemy/upgrader.rb +2 -1
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +0 -1
- data/lib/alchemy_cms.rb +0 -13
- data/lib/generators/alchemy/elements/templates/view.html.erb +1 -1
- data/lib/generators/alchemy/elements/templates/view.html.haml +1 -1
- data/lib/generators/alchemy/elements/templates/view.html.slim +1 -1
- data/lib/generators/alchemy/install/install_generator.rb +2 -13
- data/lib/generators/alchemy/install/templates/alchemy.rb.tt +24 -9
- data/lib/generators/alchemy/module/module_generator.rb +1 -0
- data/lib/generators/alchemy/module/templates/ability.rb.tt +1 -1
- data/lib/generators/alchemy/module/templates/module_config.rb.tt +16 -18
- data/lib/generators/alchemy/user_columns_migration/templates/migration.rb.tt +19 -0
- data/lib/generators/alchemy/user_columns_migration/user_columns_migration_generator.rb +30 -0
- data/lib/tasks/alchemy/thumbnails.rake +16 -29
- data/lib/tasks/alchemy/upgrade.rake +45 -3
- data/vendor/javascript/cropperjs.min.js +3 -3
- data/vendor/javascript/floating-ui.min.js +1 -1
- data/vendor/javascript/tinymce.min.js +5 -5
- data/vendor/javascript/tom-select.min.js +1 -1
- metadata +85 -65
- data/app/javascript/alchemy_admin/components/action.js +0 -50
- data/app/javascript/alchemy_admin/dirty.js +0 -55
- data/app/javascript/alchemy_admin/node_tree.js +0 -82
- data/app/javascript/alchemy_admin/picture_selector.js +0 -67
- data/app/javascript/alchemy_admin/templates/compiled.js +0 -1
- data/app/javascript/alchemy_admin/templates/node_folder.hbs +0 -3
- data/app/stylesheets/alchemy/admin/attachment-select.scss +0 -19
- data/app/views/alchemy/admin/_main_navi.html.erb +0 -6
- data/app/views/alchemy/admin/attachments/assign.js.erb +0 -15
- data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +0 -30
- data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +0 -20
- data/app/views/alchemy/admin/partials/_sub_navigation.html.erb +0 -11
- data/app/views/alchemy/admin/pictures/assign.js.erb +0 -13
- data/app/views/alchemy/base/error_notice.js.erb +0 -1
- data/app/views/alchemy/base/permission_denied.js.erb +0 -3
- data/app/views/alchemy/base/redirect.js.erb +0 -11
- data/config/alchemy/config.yml +0 -217
- data/config/alchemy/modules.yml +0 -82
- data/config/initializers/rails_live_reload.rb +0 -13
- data/lib/alchemy/dev_support/live_reload_watcher.rb +0 -5
- data/lib/alchemy/taggable.rb +0 -49
- data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -34
- data/vendor/assets/stylesheets/alchemy_admin/select2.css +0 -1
- data/vendor/javascript/handlebars.min.js +0 -29
- data/vendor/javascript/jquery.min.js +0 -2
- data/vendor/javascript/rails-ujs.min.js +0 -1
- data/vendor/javascript/select2.min.js +0 -23
- /data/{lib → app/controllers/concerns}/alchemy/ability_helper.rb +0 -0
- /data/{lib → app/controllers/concerns}/alchemy/admin/locale.rb +0 -0
- /data/{lib → app/controllers/concerns}/alchemy/admin/preview_time.rb +0 -0
- /data/{lib → app/controllers/concerns}/alchemy/admin/timezone.rb +0 -0
- /data/{lib → app/controllers/concerns}/alchemy/configuration_methods.rb +0 -0
- /data/{lib → app/models}/alchemy/admin/preview_url.rb +0 -0
- /data/{lib → app/models/concerns}/alchemy/hints.rb +0 -0
- /data/{lib → app/models/concerns}/alchemy/name_conversions.rb +0 -0
- /data/{lib → app/services}/alchemy/elements_finder.rb +0 -0
|
@@ -4,18 +4,21 @@ module Alchemy
|
|
|
4
4
|
module Ingredients
|
|
5
5
|
class ColorEditor < BaseEditor
|
|
6
6
|
def input_field
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
if colors.none?
|
|
8
|
+
content_tag("alchemy-color-input", class: "color-select") do
|
|
9
9
|
concat text_field_tag(
|
|
10
10
|
form_field_name,
|
|
11
11
|
value,
|
|
12
12
|
disabled: !editable?
|
|
13
13
|
)
|
|
14
14
|
concat color_field_tag(nil, value, disabled: disabled?)
|
|
15
|
-
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
content_tag("div", class: "color-select") do
|
|
16
18
|
concat select_tag(
|
|
17
19
|
form_field_name,
|
|
18
20
|
color_options.join.html_safe,
|
|
21
|
+
is: "alchemy-color-select",
|
|
19
22
|
disabled: !editable?
|
|
20
23
|
)
|
|
21
24
|
concat color_field_tag(form_field_name, value, disabled: disabled?)
|
|
@@ -96,10 +96,8 @@ module Alchemy
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def srcset_options
|
|
99
|
-
srcset.map do |
|
|
100
|
-
url
|
|
101
|
-
width, height = size.split("x")
|
|
102
|
-
width.present? ? "#{url} #{width}w" : "#{url} #{height}h"
|
|
99
|
+
ingredient.picture_srcset_options(srcset).map do |source|
|
|
100
|
+
"#{source[:url]} #{source[:desc]}"
|
|
103
101
|
end
|
|
104
102
|
end
|
|
105
103
|
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<alchemy-menubar>
|
|
2
|
+
<template>
|
|
3
|
+
<style>
|
|
4
|
+
.menubar {
|
|
5
|
+
--icon-size: 24px;
|
|
6
|
+
--panel-width: 525px;
|
|
7
|
+
--border-radius: 3px;
|
|
8
|
+
--left-offset: 0px;
|
|
9
|
+
display: flex;
|
|
10
|
+
position: fixed;
|
|
11
|
+
top: 0;
|
|
12
|
+
left: calc(-1 * (var(--panel-width) - var(--left-offset)));
|
|
13
|
+
z-index: 10000;
|
|
14
|
+
background-color: #214166cc;
|
|
15
|
+
backdrop-filter: blur(3px);
|
|
16
|
+
transition: left 0.25s cubic-bezier(0.23, 1, 0.32, 1), background-color .2s linear , backdrop-filter .2s linear;
|
|
17
|
+
box-shadow: 0 0 0 1px #ffffff80;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
border-bottom-right-radius: var(--border-radius);
|
|
20
|
+
padding: 12px 16px 12px 12px;
|
|
21
|
+
gap: 12px;
|
|
22
|
+
justify-content: space-between;
|
|
23
|
+
align-items: center;
|
|
24
|
+
flex-wrap: nowrap;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
font-family: "Open Sans", "Lucida Grande", "Lucida Sans Unicode",
|
|
27
|
+
"Lucida Sans", Verdana, Tahoma, sans-serif;
|
|
28
|
+
font-size: 13px;
|
|
29
|
+
line-height: normal;
|
|
30
|
+
|
|
31
|
+
&:hover {
|
|
32
|
+
background-color: #214166;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.menubar * {
|
|
37
|
+
box-sizing: border-box;
|
|
38
|
+
margin: 0;
|
|
39
|
+
padding: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.menubar:hover,
|
|
43
|
+
.menubar:focus-within,
|
|
44
|
+
.menubar.is-open {
|
|
45
|
+
left: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.menubar > svg {
|
|
49
|
+
width: var(--icon-size);
|
|
50
|
+
height: var(--icon-size);
|
|
51
|
+
flex-shrink: 0;
|
|
52
|
+
margin-left: 4px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.menubar .button {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: center;
|
|
59
|
+
gap: 8px;
|
|
60
|
+
font-size: inherit;
|
|
61
|
+
font-weight: 700;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
border-radius: var(--border-radius);
|
|
64
|
+
background-color: transparent;
|
|
65
|
+
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
66
|
+
color: #fff;
|
|
67
|
+
margin: 0;
|
|
68
|
+
padding: 0.75em 1.5em;
|
|
69
|
+
appearance: none;
|
|
70
|
+
transition: all 250ms;
|
|
71
|
+
-webkit-font-smoothing: antialiased;
|
|
72
|
+
-moz-osx-font-smoothing: grayscale;
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.menubar .button:hover {
|
|
78
|
+
text-decoration: none;
|
|
79
|
+
background-color: rgba(0, 0, 0, 0.25);
|
|
80
|
+
border-color: rgba(255, 255, 255, 0.75)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.menubar .button:active {
|
|
84
|
+
box-shadow: inset 0px 1px 1px -1px #333;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.menubar .button:focus {
|
|
88
|
+
outline: solid 2px #eca96e;
|
|
89
|
+
outline-offset: 2px;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
92
|
+
|
|
93
|
+
<div class="menubar" data-turbo="false">
|
|
94
|
+
<%= link_to alchemy.admin_dashboard_url, class: "button" do %>
|
|
95
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
96
|
+
<path d="M7.82843 10.9999H20V12.9999H7.82843L13.1924 18.3638L11.7782 19.778L4 11.9999L11.7782 4.22168L13.1924 5.63589L7.82843 10.9999Z"></path>
|
|
97
|
+
</svg>
|
|
98
|
+
<%= Alchemy.t(:to_alchemy) %>
|
|
99
|
+
<% end %>
|
|
100
|
+
<%= link_to alchemy.edit_admin_page_url(page), class: "button" do %>
|
|
101
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
102
|
+
<path d="M6.41421 15.89L16.5563 5.74785L15.1421 4.33363L5 14.4758V15.89H6.41421ZM7.24264 17.89H3V13.6473L14.435 2.21231C14.8256 1.82179 15.4587 1.82179 15.8492 2.21231L18.6777 5.04074C19.0682 5.43126 19.0682 6.06443 18.6777 6.45495L7.24264 17.89ZM3 19.89H21V21.89H3V19.89Z"></path>
|
|
103
|
+
</svg>
|
|
104
|
+
<%= Alchemy.t(:edit_page) %>
|
|
105
|
+
<% end %>
|
|
106
|
+
<%= form_tag Alchemy.config.logout_path, method: Alchemy.config.logout_method do %>
|
|
107
|
+
<%= button_tag class: "button" do %>
|
|
108
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
109
|
+
<path d="M5 22C4.44772 22 4 21.5523 4 21V3C4 2.44772 4.44772 2 5 2H19C19.5523 2 20 2.44772 20 3V6H18V4H6V20H18V18H20V21C20 21.5523 19.5523 22 19 22H5ZM18 16V13H11V11H18V8L23 12L18 16Z"></path>
|
|
110
|
+
</svg>
|
|
111
|
+
<%= Alchemy.t(:logout) %>
|
|
112
|
+
<% end %>
|
|
113
|
+
<% end %>
|
|
114
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
|
115
|
+
<path fill="#fff" d="M15.7 7.9L9.9 2.2 2.1 4.3 0 12.1l5.7 5.8 7.8-2.1 2.2-7.9zm-5.3 10.3l-1.2 4.4 3.2 3.2 4.4-1.2 1.2-4.4-3.2-3.2-4.4 1.2zM23.5 7.3L17.2 9l-1.7 6.2 4.5 4.6 6.2-1.7 1.7-6.2-4.4-4.6z" />
|
|
116
|
+
</svg>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<%= tag.script(type: "module", data: {turbo_eval: false}, nonce: helpers.content_security_policy_nonce) do %>
|
|
121
|
+
class Menubar extends HTMLElement {
|
|
122
|
+
connectedCallback() {
|
|
123
|
+
const template = this.querySelector("template")
|
|
124
|
+
const attachedShadowRoot = this.attachShadow({ mode: "open" })
|
|
125
|
+
attachedShadowRoot.appendChild(template.content.cloneNode(true))
|
|
126
|
+
const width = this.bar.offsetWidth
|
|
127
|
+
this.bar.style = `--panel-width: ${width}px; --left-offset: calc(var(--icon-size) + 32px);`
|
|
128
|
+
this.bar.addEventListener("touchstart", this, { passive: false })
|
|
129
|
+
document.addEventListener("touchstart", this, { passive: true })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
handleEvent(event) {
|
|
133
|
+
// click on menubar
|
|
134
|
+
if (event.currentTarget === this.bar) {
|
|
135
|
+
if (!this.bar.classList.contains("is-open")) {
|
|
136
|
+
event.preventDefault()
|
|
137
|
+
this.bar.classList.add("is-open")
|
|
138
|
+
}
|
|
139
|
+
// click outside of menubar
|
|
140
|
+
} else if (!event.composedPath().includes(this.bar)) {
|
|
141
|
+
this.bar.classList.remove("is-open")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
disconnectedCallback() {
|
|
146
|
+
this.bar.removeEventListener("touchstart", this)
|
|
147
|
+
document.removeEventListener("touchstart", this)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get bar() {
|
|
151
|
+
return this.shadowRoot.querySelector(".menubar")
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
customElements.define("alchemy-menubar", Menubar)
|
|
156
|
+
<% end %>
|
|
157
|
+
</alchemy-menubar>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Alchemy
|
|
2
|
+
class Menubar < ViewComponent::Base
|
|
3
|
+
attr_reader :page
|
|
4
|
+
|
|
5
|
+
delegate :alchemy, :can?, to: :helpers
|
|
6
|
+
|
|
7
|
+
def initialize(page: Alchemy::Current.page)
|
|
8
|
+
@page = page
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def render?
|
|
12
|
+
page && !Alchemy::Current.preview_page? && can?(:edit_content, page)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -6,16 +6,19 @@ module Alchemy
|
|
|
6
6
|
include UploaderResponses
|
|
7
7
|
include ArchiveOverlay
|
|
8
8
|
|
|
9
|
-
add_alchemy_filter :by_file_type, type: :select,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
add_alchemy_filter :by_file_type, type: :select,
|
|
10
|
+
multiple: ->(params) { params[:only].presence&.many? || params[:except].present? },
|
|
11
|
+
include_blank: ->(params) { params[:only].blank? && params[:except].blank? },
|
|
12
|
+
options: ->(_query, params) do
|
|
13
|
+
case params&.to_h
|
|
14
|
+
in {except:}
|
|
15
|
+
Attachment.file_types - Attachment.file_types(from_extensions: except)
|
|
16
|
+
in {only:}
|
|
17
|
+
Attachment.file_types(from_extensions: only)
|
|
18
|
+
else
|
|
19
|
+
Attachment.file_types
|
|
20
|
+
end
|
|
17
21
|
end
|
|
18
|
-
end
|
|
19
22
|
|
|
20
23
|
add_alchemy_filter :recent, type: :checkbox
|
|
21
24
|
add_alchemy_filter :last_upload, type: :checkbox
|
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Alchemy
|
|
4
4
|
module Admin
|
|
5
|
+
# Turbo Frame id wrapping admin dialog content. A form inside submits into
|
|
6
|
+
# this frame, so its request carries the id and the server can tell a dialog
|
|
7
|
+
# submission apart from the same form rendered on its own page.
|
|
8
|
+
DIALOG_FRAME_ID = "alchemy_dialog_frame"
|
|
9
|
+
|
|
5
10
|
class BaseController < Alchemy::BaseController
|
|
6
11
|
include Userstamp
|
|
7
12
|
include Locale
|
|
8
13
|
include Timezone
|
|
14
|
+
include CspProtection
|
|
9
15
|
|
|
10
16
|
before_action :load_locked_pages
|
|
11
17
|
|
|
12
|
-
helper_method :is_admin?
|
|
13
|
-
|
|
14
18
|
check_authorization
|
|
15
19
|
|
|
16
20
|
rescue_from Exception do |exception|
|
|
@@ -27,11 +31,15 @@ module Alchemy
|
|
|
27
31
|
|
|
28
32
|
def leave
|
|
29
33
|
authorize! :leave, :alchemy_admin
|
|
30
|
-
render template: "/alchemy/admin/leave", layout: !
|
|
34
|
+
render template: "/alchemy/admin/leave", layout: !turbo_frame_request?
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
private
|
|
34
38
|
|
|
39
|
+
# Controllers of the host application can inherit from this class, and
|
|
40
|
+
# their views would not be ours to make assumptions about.
|
|
41
|
+
def apply_content_security_policy? = controller_path.start_with?("alchemy/")
|
|
42
|
+
|
|
35
43
|
def safe_redirect_path(path = params[:redirect_to], fallback: admin_path)
|
|
36
44
|
if is_safe_redirect_path?(path)
|
|
37
45
|
path
|
|
@@ -53,9 +61,19 @@ module Alchemy
|
|
|
53
61
|
URI(referer).path
|
|
54
62
|
end
|
|
55
63
|
|
|
56
|
-
# Disable layout rendering for
|
|
64
|
+
# Disable layout rendering for Turbo Frame requests.
|
|
65
|
+
#
|
|
66
|
+
# Content requested by the dialog's Turbo Frame is wrapped in that frame,
|
|
67
|
+
# so Turbo can render it. Only the dialog's own request carries this
|
|
68
|
+
# frame id, so the same action rendered as a full page stays unwrapped.
|
|
57
69
|
def set_layout
|
|
58
|
-
|
|
70
|
+
if turbo_frame_request_id == DIALOG_FRAME_ID
|
|
71
|
+
"alchemy/admin/dialog"
|
|
72
|
+
elsif turbo_frame_request?
|
|
73
|
+
false
|
|
74
|
+
else
|
|
75
|
+
"alchemy/admin"
|
|
76
|
+
end
|
|
59
77
|
end
|
|
60
78
|
|
|
61
79
|
# Handles exceptions
|
|
@@ -72,8 +90,6 @@ module Alchemy
|
|
|
72
90
|
@notice = error.message[0..255]
|
|
73
91
|
if request.format.json?
|
|
74
92
|
render json: {message: @notice}, status: 500
|
|
75
|
-
elsif request.xhr?
|
|
76
|
-
render action: "error_notice"
|
|
77
93
|
else
|
|
78
94
|
@trace = error.backtrace[0..50]
|
|
79
95
|
render "500", status: 500
|
|
@@ -92,13 +108,6 @@ module Alchemy
|
|
|
92
108
|
end
|
|
93
109
|
end
|
|
94
110
|
|
|
95
|
-
# Returns true if the current_alchemy_user (The logged-in Alchemy User) has the admin role.
|
|
96
|
-
def is_admin?
|
|
97
|
-
return false if !current_alchemy_user
|
|
98
|
-
|
|
99
|
-
current_alchemy_user.admin?
|
|
100
|
-
end
|
|
101
|
-
|
|
102
111
|
# Displays errors in a #errors div if any errors are present on the object.
|
|
103
112
|
# Or redirects to the given redirect url.
|
|
104
113
|
#
|
|
@@ -122,16 +131,20 @@ module Alchemy
|
|
|
122
131
|
#
|
|
123
132
|
def do_redirect_to(url_or_path)
|
|
124
133
|
redirect_path = safe_redirect_path(url_or_path)
|
|
134
|
+
# A dialog form submits into the dialog frame, so a redirect would be
|
|
135
|
+
# followed inside it and render the target page within the dialog. Let
|
|
136
|
+
# the dialog close itself and visit the destination instead.
|
|
137
|
+
if turbo_frame_request_id == DIALOG_FRAME_ID
|
|
138
|
+
return render(turbo_stream: helpers.turbo_stream_action_tag(
|
|
139
|
+
:dialog_visit, url: redirect_path
|
|
140
|
+
))
|
|
141
|
+
end
|
|
125
142
|
respond_to do |format|
|
|
126
|
-
format.js do
|
|
127
|
-
@redirect_url = redirect_path
|
|
128
|
-
render :redirect
|
|
129
|
-
end
|
|
130
143
|
format.turbo_stream do
|
|
131
|
-
redirect_to(redirect_path, allow_other_host: false)
|
|
144
|
+
redirect_to(redirect_path, allow_other_host: false, status: :see_other)
|
|
132
145
|
end
|
|
133
146
|
format.html do
|
|
134
|
-
redirect_to(redirect_path, allow_other_host: false)
|
|
147
|
+
redirect_to(redirect_path, allow_other_host: false, status: :see_other)
|
|
135
148
|
end
|
|
136
149
|
end
|
|
137
150
|
end
|
|
@@ -150,7 +163,7 @@ module Alchemy
|
|
|
150
163
|
end
|
|
151
164
|
|
|
152
165
|
def load_locked_pages
|
|
153
|
-
@locked_pages
|
|
166
|
+
@locked_pages ||= current_alchemy_user&.locked_pages
|
|
154
167
|
end
|
|
155
168
|
|
|
156
169
|
# Returns the current site for admin controllers.
|
|
@@ -59,31 +59,12 @@ module Alchemy
|
|
|
59
59
|
# Updates the element and all ingredients in the element.
|
|
60
60
|
#
|
|
61
61
|
def update
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
previewText: Rails::Html::SafeListSanitizer.new.sanitize(@element.preview_text),
|
|
66
|
-
ingredientAnchors: @element.ingredients.filter_map do |ingredient|
|
|
67
|
-
if ingredient.settings[:anchor]
|
|
68
|
-
{
|
|
69
|
-
ingredientId: ingredient.id,
|
|
70
|
-
active: ingredient.dom_id.present?
|
|
71
|
-
}
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
}.merge(pagePublicationData(@element.page))
|
|
62
|
+
@element_validated = @element.update(element_params)
|
|
63
|
+
if @element_validated
|
|
64
|
+
render :update
|
|
75
65
|
else
|
|
76
66
|
@warning = Alchemy.t("Validation failed")
|
|
77
|
-
render
|
|
78
|
-
warning: @warning,
|
|
79
|
-
errorMessage: Alchemy.t(:ingredient_validations_headline),
|
|
80
|
-
ingredientsWithErrors: @element.ingredients_with_errors.map do |ingredient|
|
|
81
|
-
{
|
|
82
|
-
id: ingredient.id,
|
|
83
|
-
errorMessage: ingredient.errors.messages[:value].to_sentence
|
|
84
|
-
}
|
|
85
|
-
end
|
|
86
|
-
}, status: 422
|
|
67
|
+
render :update, status: 422
|
|
87
68
|
end
|
|
88
69
|
end
|
|
89
70
|
|
|
@@ -51,7 +51,7 @@ module Alchemy
|
|
|
51
51
|
def load_current_site
|
|
52
52
|
@current_site = Alchemy::Current.site
|
|
53
53
|
if @current_site.nil?
|
|
54
|
-
flash[:
|
|
54
|
+
flash[:info] = Alchemy.t("Please create a site first.")
|
|
55
55
|
do_redirect_to admin_sites_path
|
|
56
56
|
end
|
|
57
57
|
end
|
|
@@ -27,9 +27,12 @@ module Alchemy
|
|
|
27
27
|
# that would otherwise fire for every picture the view renders.
|
|
28
28
|
before_action :load_deletable_picture_ids, only: [:index, :update]
|
|
29
29
|
|
|
30
|
-
add_alchemy_filter :by_file_format, type: :select,
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
add_alchemy_filter :by_file_format, type: :select,
|
|
31
|
+
multiple: ->(params) { params[:only].presence&.many? || params[:except].present? },
|
|
32
|
+
include_blank: ->(params) { params[:only].blank? && params[:except].blank? },
|
|
33
|
+
options: ->(query) do
|
|
34
|
+
Alchemy::Picture.file_formats(query.result)
|
|
35
|
+
end
|
|
33
36
|
add_alchemy_filter :recent, type: :checkbox
|
|
34
37
|
add_alchemy_filter :last_upload, type: :checkbox
|
|
35
38
|
add_alchemy_filter :without_tag, type: :checkbox
|
|
@@ -100,12 +100,12 @@ module Alchemy
|
|
|
100
100
|
default: Alchemy.t("Successfully #{verb}"))
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
def is_alchemy_module?
|
|
104
|
-
!alchemy_module.nil? && !alchemy_module["engine_name"].nil?
|
|
105
|
-
end
|
|
106
|
-
|
|
107
103
|
def alchemy_module
|
|
108
|
-
@alchemy_module ||=
|
|
104
|
+
@alchemy_module ||= {
|
|
105
|
+
"engine_name" => Rails::Engine.subclasses.detect { |engine|
|
|
106
|
+
engine.routes.equal?(self.class._routes)
|
|
107
|
+
}&.engine_name
|
|
108
|
+
}
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def load_resource
|
|
@@ -6,7 +6,7 @@ module Alchemy
|
|
|
6
6
|
def create
|
|
7
7
|
@site = Alchemy::Site.new(resource_params)
|
|
8
8
|
if @site.save
|
|
9
|
-
flash[:
|
|
9
|
+
flash[:info] = Alchemy.t("Please create a default language for this site.")
|
|
10
10
|
do_redirect_to alchemy.admin_languages_path(site_id: @site)
|
|
11
11
|
else
|
|
12
12
|
render :new
|
|
@@ -53,9 +53,7 @@ module Alchemy
|
|
|
53
53
|
#{current_alchemy_user.inspect}
|
|
54
54
|
WARN
|
|
55
55
|
end
|
|
56
|
-
if
|
|
57
|
-
render json: {message: Alchemy.t("You are not authorized")}, status: 401
|
|
58
|
-
elsif current_alchemy_user
|
|
56
|
+
if current_alchemy_user
|
|
59
57
|
handle_redirect_for_user
|
|
60
58
|
else
|
|
61
59
|
handle_redirect_for_guest
|
|
@@ -63,6 +61,10 @@ module Alchemy
|
|
|
63
61
|
end
|
|
64
62
|
|
|
65
63
|
def handle_redirect_for_user
|
|
64
|
+
if api_request?
|
|
65
|
+
return render json: {message: Alchemy.t("You are not authorized")}, status: :forbidden
|
|
66
|
+
end
|
|
67
|
+
|
|
66
68
|
flash[:warning] = Alchemy.t("You are not authorized")
|
|
67
69
|
if can?(:index, :alchemy_admin_dashboard)
|
|
68
70
|
redirect_or_render_notice
|
|
@@ -72,17 +74,7 @@ module Alchemy
|
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
def redirect_or_render_notice
|
|
75
|
-
if
|
|
76
|
-
respond_to do |format|
|
|
77
|
-
format.js do
|
|
78
|
-
render plain: flash.discard(:warning), status: 403
|
|
79
|
-
end
|
|
80
|
-
format.html do
|
|
81
|
-
render partial: "alchemy/admin/partials/flash",
|
|
82
|
-
locals: {message: flash[:warning], flash_type: "warning"}
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
elsif turbo_frame_request?
|
|
77
|
+
if turbo_frame_request?
|
|
86
78
|
render "403", status: 403
|
|
87
79
|
else
|
|
88
80
|
redirect_to(alchemy.admin_dashboard_path)
|
|
@@ -91,14 +83,30 @@ module Alchemy
|
|
|
91
83
|
|
|
92
84
|
def handle_redirect_for_guest
|
|
93
85
|
flash[:info] = Alchemy.t("Please log in")
|
|
94
|
-
if
|
|
95
|
-
render :
|
|
86
|
+
if api_request?
|
|
87
|
+
render json: {
|
|
88
|
+
message: Alchemy.t("Please log in"),
|
|
89
|
+
redirect_url: Alchemy.config.login_path
|
|
90
|
+
}, status: :unauthorized
|
|
91
|
+
elsif turbo_frame_request?
|
|
92
|
+
# Redirecting would be followed inside the frame and render the login
|
|
93
|
+
# page in the dialog. Turbo runs a stream action from any response,
|
|
94
|
+
# whatever status or Accept header, so it can break out of the frame.
|
|
95
|
+
render turbo_stream: helpers.turbo_stream_action_tag(
|
|
96
|
+
:dialog_visit, url: Alchemy.config.login_path
|
|
97
|
+
), status: :unauthorized
|
|
96
98
|
else
|
|
97
99
|
store_location
|
|
98
100
|
redirect_to Alchemy.config.login_path
|
|
99
101
|
end
|
|
100
102
|
end
|
|
101
103
|
|
|
104
|
+
# Requests from our own admin JavaScript, that expect a machine readable
|
|
105
|
+
# response instead of a page to render.
|
|
106
|
+
def api_request?
|
|
107
|
+
request.format.json? || request.xhr?
|
|
108
|
+
end
|
|
109
|
+
|
|
102
110
|
# Logs the current exception to the error log.
|
|
103
111
|
def exception_logger(error)
|
|
104
112
|
Logger.error("\n#{error.class} #{error.message} in #{error.backtrace.first}")
|
|
@@ -73,11 +73,16 @@ module Alchemy
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
# Renders a search engine compatible xml sitemap.
|
|
76
|
+
#
|
|
77
|
+
# @deprecated Point the +/sitemap.xml+ route at {Alchemy::SitemapController} instead.
|
|
78
|
+
#
|
|
76
79
|
def sitemap
|
|
80
|
+
Alchemy::Deprecation.warn(
|
|
81
|
+
"Alchemy::PagesController#sitemap is deprecated and will be removed in Alchemy 9.0. " \
|
|
82
|
+
"Route /sitemap.xml to Alchemy::SitemapController#show instead."
|
|
83
|
+
)
|
|
77
84
|
@pages = Page.sitemap
|
|
78
|
-
|
|
79
|
-
format.xml { render layout: "alchemy/sitemap" }
|
|
80
|
-
end
|
|
85
|
+
render layout: "alchemy/sitemap"
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
private
|
|
@@ -190,9 +195,14 @@ module Alchemy
|
|
|
190
195
|
|
|
191
196
|
def set_expiration_headers
|
|
192
197
|
if must_not_cache?
|
|
198
|
+
# no-store, not no-cache: no-cache still lets caches store the response and
|
|
199
|
+
# reuse it after a 304, which serves stale content (e.g. flash messages) and
|
|
200
|
+
# skips re-rendering elements that opted out of page caching.
|
|
201
|
+
no_store
|
|
202
|
+
elsif page_cache_control.no_cache?
|
|
193
203
|
expires_now
|
|
194
204
|
else
|
|
195
|
-
expires_in @page.expiration_time, {public:
|
|
205
|
+
expires_in @page.expiration_time, {public: page_cache_control.public?}.merge(caching_options)
|
|
196
206
|
end
|
|
197
207
|
end
|
|
198
208
|
|
|
@@ -209,7 +219,13 @@ module Alchemy
|
|
|
209
219
|
end
|
|
210
220
|
|
|
211
221
|
def signup_required?
|
|
212
|
-
if Alchemy.config.user_class.respond_to?(:
|
|
222
|
+
if Alchemy.config.user_class.respond_to?(:alchemy_admins)
|
|
223
|
+
Alchemy.config.user_class.alchemy_admins.empty?
|
|
224
|
+
elsif Alchemy.config.user_class.respond_to?(:admins)
|
|
225
|
+
Alchemy::Deprecation.warn(
|
|
226
|
+
"Using `#{Alchemy.config.user_class}.admins` is deprecated. " \
|
|
227
|
+
"Include `Alchemy::UserMethods` into your user class to provide `alchemy_admins` instead."
|
|
228
|
+
)
|
|
213
229
|
Alchemy.config.user_class.admins.empty?
|
|
214
230
|
end
|
|
215
231
|
end
|
|
@@ -240,14 +256,19 @@ module Alchemy
|
|
|
240
256
|
def render_fresh_page?
|
|
241
257
|
must_not_cache? || stale?(
|
|
242
258
|
etag: page_etag,
|
|
243
|
-
public:
|
|
259
|
+
public: page_cache_control.public?,
|
|
244
260
|
template: "pages/show"
|
|
245
261
|
)
|
|
246
262
|
end
|
|
247
263
|
|
|
248
|
-
# don't cache pages if
|
|
264
|
+
# don't cache pages if app caching is off, a flash message is present, or the
|
|
265
|
+
# page's effective Cache-Control forbids storing the response.
|
|
249
266
|
def must_not_cache?
|
|
250
|
-
!caching_enabled? ||
|
|
267
|
+
!caching_enabled? || flash.present? || page_cache_control.no_store?
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def page_cache_control
|
|
271
|
+
@page.cache_control
|
|
251
272
|
end
|
|
252
273
|
|
|
253
274
|
def caching_enabled?
|