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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemy
|
|
4
|
+
class Upgrader
|
|
5
|
+
module EightFour
|
|
6
|
+
# Alchemy does not depend on the dragonfly gem anymore.
|
|
7
|
+
# Apps still using the dragonfly storage adapter need to
|
|
8
|
+
# declare the gem in their own Gemfile.
|
|
9
|
+
def add_dragonfly_gem
|
|
10
|
+
return unless Alchemy.storage_adapter.dragonfly?
|
|
11
|
+
|
|
12
|
+
run %(bundle add dragonfly --version "~> 1.4")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# The attachment uploader used to accept every file type.
|
|
16
|
+
def notify_attachment_filetypes_default
|
|
17
|
+
default = Alchemy::Configurations::Uploader::AllowedFileTypes::DEFAULT_ATTACHMENT_FILE_TYPES
|
|
18
|
+
current = Alchemy.config.uploader.allowed_filetypes.alchemy_attachments.to_a
|
|
19
|
+
return unless current == default || current.include?("*")
|
|
20
|
+
|
|
21
|
+
closing = if current.include?("*")
|
|
22
|
+
<<~TEXT.strip
|
|
23
|
+
Your app configures `["*"]`, so it still accepts every file type.
|
|
24
|
+
Please consider adopting the new default.
|
|
25
|
+
TEXT
|
|
26
|
+
else
|
|
27
|
+
<<~TEXT.strip
|
|
28
|
+
Setting it back to `["*"]` restores the old behaviour and disables
|
|
29
|
+
file type validation entirely.
|
|
30
|
+
TEXT
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
todo(<<~TODO.strip, "Attachment uploads are now restricted to an allowlist")
|
|
34
|
+
The `uploader.allowed_filetypes.alchemy_attachments` setting used to
|
|
35
|
+
default to `["*"]`, which accepts every file type, including
|
|
36
|
+
executables. It now defaults to a list of document, media and archive
|
|
37
|
+
formats:
|
|
38
|
+
|
|
39
|
+
#{default.each_slice(8).map { |slice| " #{slice.join(" ")}" }.join("\n")}
|
|
40
|
+
|
|
41
|
+
Please check whether your editors upload file types that are not on
|
|
42
|
+
this list. If they do, add them in `config/initializers/alchemy.rb`:
|
|
43
|
+
|
|
44
|
+
config.uploader.allowed_filetypes.alchemy_attachments = %w[#{default.first(3).join(" ")} ...]
|
|
45
|
+
|
|
46
|
+
#{closing}
|
|
47
|
+
TODO
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def notify_admin_content_security_policy
|
|
51
|
+
todo(<<~TODO.strip, "The admin can send a Content Security Policy")
|
|
52
|
+
Alchemy 9.0 will send a Content Security Policy with its admin
|
|
53
|
+
responses by default. Opt in now to find out what it would block:
|
|
54
|
+
|
|
55
|
+
config.admin_content_security_policy = "Alchemy::Admin::ContentSecurityPolicy"
|
|
56
|
+
|
|
57
|
+
If you have inline scripts or anything else in your admin that a CSP
|
|
58
|
+
would block, subclass `Alchemy::Admin::ContentSecurityPolicy`.
|
|
59
|
+
TODO
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def notify_picture_format_setting
|
|
63
|
+
todo(<<~TODO.strip, "Picture ingredients now respects the `format` setting")
|
|
64
|
+
New formats create new image variants. To generate them upfront instead
|
|
65
|
+
of on the first request, run the thumbnail task:
|
|
66
|
+
|
|
67
|
+
bin/rake alchemy:generate:ingredient_picture_thumbnails
|
|
68
|
+
|
|
69
|
+
Set `ELEMENTS=element1,element2` to only generate thumbnails for some
|
|
70
|
+
elements, and `ASYNC=true` to enqueue them as background jobs
|
|
71
|
+
(active_storage only). `bin/rake alchemy:generate:thumbnails`
|
|
72
|
+
generates the thumbnails of the picture library as well.
|
|
73
|
+
TODO
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Element partials that render nested elements through the
|
|
77
|
+
# +nested_elements+ association issue one database query per parent
|
|
78
|
+
# element. Rendering through the block helper's +nested_elements+
|
|
79
|
+
# instead reuses the page version's preloaded element set.
|
|
80
|
+
def upgrade_nested_elements_rendering
|
|
81
|
+
rewritten = []
|
|
82
|
+
manual = []
|
|
83
|
+
Dir.glob(Rails.root.join("app/views/alchemy/elements/*")).each do |file|
|
|
84
|
+
next unless File.file?(file)
|
|
85
|
+
|
|
86
|
+
content = File.read(file)
|
|
87
|
+
block_variable = content[/element_view_for\b.*?do\s*\|\s*(\w+)\s*\|/m, 1]
|
|
88
|
+
|
|
89
|
+
if block_variable
|
|
90
|
+
pattern = /render\s+(?!#{block_variable}\b)\w+\.nested_elements(?:\.published)?/
|
|
91
|
+
if content.match?(pattern)
|
|
92
|
+
gsub_file(file, pattern, "render #{block_variable}.nested_elements")
|
|
93
|
+
rewritten << file
|
|
94
|
+
end
|
|
95
|
+
elsif content.match?(/render\s+\w+\.nested_elements/)
|
|
96
|
+
manual << file
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
list = ->(files) { files.map { |file| " - #{Pathname.new(file).relative_path_from(Rails.root)}" }.join("\n") }
|
|
101
|
+
|
|
102
|
+
if rewritten.any?
|
|
103
|
+
todo(<<~TODO.strip, "Nested elements are now rendered through the block helper")
|
|
104
|
+
We rewrote the following element partials to render nested elements
|
|
105
|
+
through the `element_view_for` block helper instead of the
|
|
106
|
+
`nested_elements` association, which issues one query per element:
|
|
107
|
+
|
|
108
|
+
#{list.call(rewritten)}
|
|
109
|
+
|
|
110
|
+
Please review these changes and commit them. Also check your views
|
|
111
|
+
for other uses of `element.nested_elements` that were not rewritten
|
|
112
|
+
automatically (for example assigned to a variable first).
|
|
113
|
+
TODO
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if manual.any?
|
|
117
|
+
todo(<<~TODO.strip, "Nested elements rendered outside a block helper")
|
|
118
|
+
The following element partials render nested elements through the
|
|
119
|
+
`nested_elements` association but not inside an `element_view_for`
|
|
120
|
+
block, so we could not rewrite them automatically:
|
|
121
|
+
|
|
122
|
+
#{list.call(manual)}
|
|
123
|
+
|
|
124
|
+
Please render them through the block helper (`render el.nested_elements`)
|
|
125
|
+
so nested rendering reuses the preloaded element set instead of
|
|
126
|
+
querying per parent.
|
|
127
|
+
TODO
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
NAVIGATION_PARTIALS = %w[
|
|
132
|
+
app/views/alchemy/admin/_main_navi.html.erb
|
|
133
|
+
app/views/alchemy/admin/partials/_main_navigation_entry.html.erb
|
|
134
|
+
app/views/alchemy/admin/partials/_sub_navigation.html.erb
|
|
135
|
+
].freeze
|
|
136
|
+
|
|
137
|
+
# +Alchemy::Modules.register_module+ is deprecated in favor of
|
|
138
|
+
# +Alchemy.config.menu+. Calls with a static literal argument are
|
|
139
|
+
# rewritten, everything else has to be migrated by hand.
|
|
140
|
+
def upgrade_menu_config
|
|
141
|
+
rewritten = []
|
|
142
|
+
manual = []
|
|
143
|
+
rewriter = begin
|
|
144
|
+
require "prism"
|
|
145
|
+
RegisterModuleRewriter.new
|
|
146
|
+
rescue LoadError
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
Dir.glob(Rails.root.join("config/initializers/**/*.rb")).sort.each do |file|
|
|
151
|
+
content = File.read(file)
|
|
152
|
+
next unless content.include?("register_module")
|
|
153
|
+
|
|
154
|
+
unless rewriter
|
|
155
|
+
manual << file
|
|
156
|
+
next
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
new_content, rewritten_count, manual_count = rewriter.rewrite(content)
|
|
160
|
+
if rewritten_count > 0
|
|
161
|
+
File.write(file, new_content)
|
|
162
|
+
rewritten << file
|
|
163
|
+
end
|
|
164
|
+
manual << file if manual_count > 0
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
overridden_partials = NAVIGATION_PARTIALS.map { |partial| Rails.root.join(partial).to_s }.select { |file| File.exist?(file) }
|
|
168
|
+
|
|
169
|
+
list = ->(files) { files.map { |file| " - #{Pathname.new(file).relative_path_from(Rails.root)}" }.join("\n") }
|
|
170
|
+
|
|
171
|
+
if rewritten.any?
|
|
172
|
+
todo(<<~TODO.strip, "`register_module` calls were rewritten to `Alchemy.config.menu.add`")
|
|
173
|
+
`Alchemy::Modules.register_module` is deprecated and will be removed in
|
|
174
|
+
Alchemy 9.0. We rewrote its calls in the following initializers to
|
|
175
|
+
`Alchemy.config.menu.add`:
|
|
176
|
+
|
|
177
|
+
#{list.call(rewritten)}
|
|
178
|
+
|
|
179
|
+
Please review these changes and commit them.
|
|
180
|
+
TODO
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
if manual.any?
|
|
184
|
+
todo(<<~TODO.strip, "The admin main navigation is now configured with `Alchemy.config.menu`")
|
|
185
|
+
`Alchemy::Modules.register_module` is deprecated and will be removed in
|
|
186
|
+
Alchemy 9.0. The following initializers call it in a way we could not
|
|
187
|
+
rewrite automatically:
|
|
188
|
+
|
|
189
|
+
#{list.call(manual)}
|
|
190
|
+
|
|
191
|
+
Please replace these `register_module` calls with
|
|
192
|
+
`Alchemy.config.menu.add` in `config/initializers/alchemy.rb`:
|
|
193
|
+
|
|
194
|
+
Alchemy.configure do |config|
|
|
195
|
+
config.menu.add(
|
|
196
|
+
name: "events",
|
|
197
|
+
title: "Events",
|
|
198
|
+
icon: "calendar",
|
|
199
|
+
url: -> { main_app.admin_events_path },
|
|
200
|
+
condition: -> { can?(:index, :admin_events) },
|
|
201
|
+
children: [
|
|
202
|
+
{
|
|
203
|
+
title: "Locations",
|
|
204
|
+
url: -> { main_app.admin_locations_path },
|
|
205
|
+
condition: -> { can?(:index, :admin_locations) }
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
`url` and `condition` are evaluated in the view context. The keys of
|
|
212
|
+
the former `navigation` hash now live on the entry itself, its `name`
|
|
213
|
+
is now `title`, `sub_navigation` is now `children`, `icon-style` is now
|
|
214
|
+
`icon_style`, `data: {turbo: false}` is now `turbo: false`, and `params`
|
|
215
|
+
is no longer supported.
|
|
216
|
+
|
|
217
|
+
Call `config.menu.add` once at boot and not inside `to_prepare`, as it
|
|
218
|
+
does not skip entries that are already present like `register_module`
|
|
219
|
+
did.
|
|
220
|
+
TODO
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
if overridden_partials.any?
|
|
224
|
+
todo(<<~TODO.strip, "The admin main navigation partials have been replaced")
|
|
225
|
+
Your app overrides the following partials, which have been replaced by
|
|
226
|
+
the `Alchemy::Admin::MainNavigation` components and are no longer
|
|
227
|
+
rendered:
|
|
228
|
+
|
|
229
|
+
#{list.call(overridden_partials)}
|
|
230
|
+
|
|
231
|
+
Please adapt your changes.
|
|
232
|
+
TODO
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemy
|
|
4
|
+
class Upgrader
|
|
5
|
+
# Rewrites +Alchemy::Modules.register_module+ calls with a static literal
|
|
6
|
+
# argument into +Alchemy.config.menu.add+ calls.
|
|
7
|
+
#
|
|
8
|
+
# Calls inside a +to_prepare+ block are guarded by the entry's name, because
|
|
9
|
+
# +Alchemy.config.menu.add+ does not skip entries that are already present.
|
|
10
|
+
#
|
|
11
|
+
# Requires Prism, which only ships with Ruby >= 3.3.
|
|
12
|
+
class RegisterModuleRewriter
|
|
13
|
+
class NonLiteralError < StandardError; end
|
|
14
|
+
|
|
15
|
+
RECEIVERS = ["Alchemy::Modules", "::Alchemy::Modules"].freeze
|
|
16
|
+
|
|
17
|
+
KEYS = %w[
|
|
18
|
+
name title engine_name position icon icon_style image inline_image turbo
|
|
19
|
+
controller action nested_controllers nested_actions children nested
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
# @param [String] source
|
|
23
|
+
# @return [Array(String, Integer, Integer)]
|
|
24
|
+
# The new source, the number of rewritten and of untouched calls
|
|
25
|
+
def rewrite(source)
|
|
26
|
+
require "prism"
|
|
27
|
+
|
|
28
|
+
result = Prism.parse(source)
|
|
29
|
+
return [source, 0, 1] if result.errors.any?
|
|
30
|
+
|
|
31
|
+
replacements = []
|
|
32
|
+
manual = 0
|
|
33
|
+
each_register_module_call(result.value) do |call, in_to_prepare|
|
|
34
|
+
replacement = replacement_for(call, source, in_to_prepare:)
|
|
35
|
+
if replacement
|
|
36
|
+
replacements << [call.location.start_offset, call.location.end_offset, replacement]
|
|
37
|
+
else
|
|
38
|
+
manual += 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
new_source = source.b
|
|
43
|
+
replacements.sort_by(&:first).reverse_each do |start_offset, end_offset, replacement|
|
|
44
|
+
new_source[start_offset...end_offset] = replacement.b
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
[new_source.force_encoding(source.encoding), replacements.size, manual]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def each_register_module_call(node, in_to_prepare = false, &block)
|
|
53
|
+
if node.is_a?(Prism::CallNode) && node.name.to_s == "register_module"
|
|
54
|
+
yield node, in_to_prepare
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
node.compact_child_nodes.each do |child|
|
|
58
|
+
child_in_to_prepare = in_to_prepare ||
|
|
59
|
+
(node.is_a?(Prism::CallNode) && node.name.to_s == "to_prepare" && child.equal?(node.block))
|
|
60
|
+
each_register_module_call(child, child_in_to_prepare, &block)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def replacement_for(call, source, in_to_prepare:)
|
|
65
|
+
return unless RECEIVERS.include?(call.receiver&.slice)
|
|
66
|
+
return if call.block
|
|
67
|
+
|
|
68
|
+
arguments = call.arguments&.arguments || []
|
|
69
|
+
return unless arguments.size == 1
|
|
70
|
+
return unless arguments.first.is_a?(Prism::HashNode) || arguments.first.is_a?(Prism::KeywordHashNode)
|
|
71
|
+
return unless heredocs_inside?(arguments.first, call, source)
|
|
72
|
+
|
|
73
|
+
item = Alchemy::Configurations::MenuItem.from_legacy_hash(literal(arguments.first))
|
|
74
|
+
return if in_to_prepare && item.name.blank?
|
|
75
|
+
|
|
76
|
+
indent = " " * call.location.start_column
|
|
77
|
+
if in_to_prepare
|
|
78
|
+
<<~RUBY.chomp
|
|
79
|
+
unless Alchemy.config.menu.any? { |item| item.name == #{item.name.inspect} }
|
|
80
|
+
#{indent} #{menu_add(item, "#{indent} ")}
|
|
81
|
+
#{indent}end
|
|
82
|
+
RUBY
|
|
83
|
+
else
|
|
84
|
+
menu_add(item, indent)
|
|
85
|
+
end
|
|
86
|
+
rescue NonLiteralError, Alchemy::Configuration::ConfigurationError
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def menu_add(item, indent)
|
|
91
|
+
pairs = attributes(item).map do |key, value|
|
|
92
|
+
if key == "children" || key == "nested"
|
|
93
|
+
entries = value.map { |entry| "#{indent} #{inline_hash(entry)}" }
|
|
94
|
+
"#{indent} #{key}: [\n#{entries.join(",\n")}\n#{indent} ]"
|
|
95
|
+
else
|
|
96
|
+
"#{indent} #{key}: #{value.inspect}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
"Alchemy.config.menu.add(\n#{pairs.join(",\n")}\n#{indent})"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def inline_hash(item)
|
|
103
|
+
pairs = attributes(item).map do |key, value|
|
|
104
|
+
if key == "children" || key == "nested"
|
|
105
|
+
"#{key}: [#{value.map { |entry| inline_hash(entry) }.join(", ")}]"
|
|
106
|
+
else
|
|
107
|
+
"#{key}: #{value.inspect}"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
"{#{pairs.join(", ")}}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def attributes(item)
|
|
114
|
+
KEYS.filter_map do |key|
|
|
115
|
+
value = item.public_send(key)
|
|
116
|
+
value = value.to_a if value.respond_to?(:to_a) && !value.is_a?(String)
|
|
117
|
+
next if key == "turbo" && value != false
|
|
118
|
+
next if value.nil? || value == []
|
|
119
|
+
|
|
120
|
+
[key, value]
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def literal(node)
|
|
125
|
+
case node
|
|
126
|
+
when Prism::StringNode, Prism::SymbolNode
|
|
127
|
+
node.unescaped
|
|
128
|
+
# Squiggly heredocs and adjacent string literals are made of static parts
|
|
129
|
+
when Prism::InterpolatedStringNode
|
|
130
|
+
node.compact_child_nodes.map do |part|
|
|
131
|
+
raise NonLiteralError unless part.is_a?(Prism::StringNode)
|
|
132
|
+
|
|
133
|
+
part.unescaped
|
|
134
|
+
end.join
|
|
135
|
+
when Prism::IntegerNode
|
|
136
|
+
Integer(node.slice)
|
|
137
|
+
when Prism::FloatNode
|
|
138
|
+
Float(node.slice)
|
|
139
|
+
when Prism::TrueNode
|
|
140
|
+
true
|
|
141
|
+
when Prism::FalseNode
|
|
142
|
+
false
|
|
143
|
+
when Prism::NilNode
|
|
144
|
+
nil
|
|
145
|
+
when Prism::ArrayNode
|
|
146
|
+
node.elements.map { |element| literal(element) }
|
|
147
|
+
when Prism::HashNode, Prism::KeywordHashNode
|
|
148
|
+
node.elements.to_h do |element|
|
|
149
|
+
raise NonLiteralError unless element.is_a?(Prism::AssocNode)
|
|
150
|
+
raise NonLiteralError unless element.key.is_a?(Prism::SymbolNode) || element.key.is_a?(Prism::StringNode)
|
|
151
|
+
|
|
152
|
+
[element.key.unescaped, literal(element.value)]
|
|
153
|
+
end
|
|
154
|
+
else
|
|
155
|
+
raise NonLiteralError
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# A heredoc body starts on the line after its opening, so it is only part
|
|
160
|
+
# of the call if the call ends on a later line.
|
|
161
|
+
def heredocs_inside?(node, call, source)
|
|
162
|
+
heredocs = []
|
|
163
|
+
collect_heredocs(node, heredocs)
|
|
164
|
+
heredocs.all? do |heredoc|
|
|
165
|
+
source.byteslice(heredoc.location.end_offset...call.location.end_offset).include?("\n")
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def collect_heredocs(node, heredocs)
|
|
170
|
+
if (node.is_a?(Prism::StringNode) || node.is_a?(Prism::InterpolatedStringNode)) && node.slice.start_with?("<<")
|
|
171
|
+
heredocs << node
|
|
172
|
+
end
|
|
173
|
+
node.compact_child_nodes.each { |child| collect_heredocs(child, heredocs) }
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
data/lib/alchemy/upgrader.rb
CHANGED
|
@@ -13,7 +13,8 @@ module Alchemy
|
|
|
13
13
|
|
|
14
14
|
VERSION_MODULE_MAP = {
|
|
15
15
|
"8.0" => "Alchemy::Upgrader::EightZero",
|
|
16
|
-
"8.1" => "Alchemy::Upgrader::EightOne"
|
|
16
|
+
"8.1" => "Alchemy::Upgrader::EightOne",
|
|
17
|
+
"8.4" => "Alchemy::Upgrader::EightFour"
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
source_root Alchemy::Engine.root.join("lib/generators/alchemy/install")
|
data/lib/alchemy/version.rb
CHANGED
data/lib/alchemy.rb
CHANGED
data/lib/alchemy_cms.rb
CHANGED
|
@@ -10,7 +10,6 @@ require "active_model_serializers"
|
|
|
10
10
|
require "active_storage/engine"
|
|
11
11
|
require "awesome_nested_set"
|
|
12
12
|
require "cancan"
|
|
13
|
-
require "dragonfly"
|
|
14
13
|
require "gutentag"
|
|
15
14
|
require "importmap-rails"
|
|
16
15
|
require "kaminari"
|
|
@@ -21,31 +20,19 @@ require "userstamp"
|
|
|
21
20
|
require "view_component"
|
|
22
21
|
|
|
23
22
|
# Require globally used Alchemy mixins
|
|
24
|
-
require_relative "alchemy/ability_helper"
|
|
25
|
-
require_relative "alchemy/admin/locale"
|
|
26
|
-
require_relative "alchemy/admin/timezone"
|
|
27
|
-
require_relative "alchemy/admin/preview_time"
|
|
28
|
-
require_relative "alchemy/admin/preview_url"
|
|
29
23
|
require_relative "alchemy/auth_accessors"
|
|
30
24
|
require_relative "alchemy/cache_digests/template_tracker"
|
|
31
|
-
require_relative "alchemy/configuration_methods"
|
|
32
|
-
require_relative "alchemy/controller_actions"
|
|
33
25
|
require_relative "alchemy/deprecation"
|
|
34
|
-
require_relative "alchemy/elements_finder"
|
|
35
26
|
require_relative "alchemy/error_tracking"
|
|
36
27
|
require_relative "alchemy/errors"
|
|
37
|
-
require_relative "alchemy/filetypes"
|
|
38
28
|
require_relative "alchemy/forms/builder"
|
|
39
|
-
require_relative "alchemy/hints"
|
|
40
29
|
require_relative "alchemy/i18n"
|
|
41
30
|
require_relative "alchemy/logger"
|
|
42
31
|
require_relative "alchemy/modules"
|
|
43
|
-
require_relative "alchemy/name_conversions"
|
|
44
32
|
require_relative "alchemy/on_page_layout"
|
|
45
33
|
require_relative "alchemy/on_page_layout/callbacks_runner"
|
|
46
34
|
require_relative "alchemy/paths"
|
|
47
35
|
require_relative "alchemy/tinymce"
|
|
48
|
-
require_relative "alchemy/taggable"
|
|
49
36
|
require_relative "alchemy/version"
|
|
50
37
|
|
|
51
38
|
# Finally require Alchemy itself
|
|
@@ -88,16 +88,9 @@ module Alchemy
|
|
|
88
88
|
copy_file "alchemy.en.yml", app_config_path.join("locales", "alchemy.en.yml")
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
def copy_dragonfly_config
|
|
92
|
-
template(
|
|
93
|
-
"#{__dir__}/templates/dragonfly.rb.tt",
|
|
94
|
-
app_config_path.join("initializers", "dragonfly.rb"),
|
|
95
|
-
skip: options[:auto_accept]
|
|
96
|
-
)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
91
|
def install_gutentag_migrations
|
|
100
|
-
rake "gutentag:install:migrations"
|
|
92
|
+
rake "gutentag:install:migrations", abort_on_failure: true
|
|
93
|
+
generate "gutentag:migration_versions", abort_on_failure: true
|
|
101
94
|
end
|
|
102
95
|
|
|
103
96
|
def setup_database
|
|
@@ -144,10 +137,6 @@ module Alchemy
|
|
|
144
137
|
puts " #{something}"
|
|
145
138
|
end
|
|
146
139
|
|
|
147
|
-
def gem_config_path
|
|
148
|
-
@_config_path ||= File.expand_path("../../../../config/alchemy", __dir__)
|
|
149
|
-
end
|
|
150
|
-
|
|
151
140
|
def app_config_path
|
|
152
141
|
@_app_config_path ||= app_root.join("config")
|
|
153
142
|
end
|
|
@@ -28,10 +28,12 @@ Alchemy.configure do |config|
|
|
|
28
28
|
#
|
|
29
29
|
# show_root [Boolean] # Show language root page in sitemap?
|
|
30
30
|
# show_flag [Boolean] # Enables the Checkbox in Page#update overlay. So your customer can set the visibility of pages in the sitemap.
|
|
31
|
+
# max_age [Integer] # How long the sitemap is cached, in seconds. Set to 0 to disable sitemap caching.
|
|
31
32
|
#
|
|
32
33
|
# config.sitemap.tap do |sitemap|
|
|
33
34
|
# sitemap.show_root = <%= @default_config.sitemap.show_root.inspect %>
|
|
34
35
|
# sitemap.show_flag = <%= @default_config.sitemap.show_flag.inspect %>
|
|
36
|
+
# sitemap.max_age = <%= @default_config.sitemap.max_age.inspect %>
|
|
35
37
|
# end
|
|
36
38
|
|
|
37
39
|
# === Default items per page in admin views
|
|
@@ -164,27 +166,29 @@ Alchemy.configure do |config|
|
|
|
164
166
|
# upload_limit [Integer] # Set an amount of files upload limit of files which can be uploaded at once. Set 0 for unlimited.
|
|
165
167
|
# file_size_limit* [Integer] # Set a file size limit in mega bytes for a per file limit.
|
|
166
168
|
#
|
|
167
|
-
# *) Allow filetypes to upload.
|
|
169
|
+
# *) Allow filetypes to upload. Passing * disables file type validation and
|
|
170
|
+
# allows every kind of file, including executables.
|
|
168
171
|
#
|
|
169
172
|
# config.uploader.tap do |uploader|
|
|
170
173
|
# uploader.upload_limit = <%= @default_config.uploader.upload_limit.inspect %>
|
|
171
174
|
# uploader.file_size_limit = <%= @default_config.uploader.file_size_limit.inspect %>
|
|
172
175
|
# uploader.allowed_filetypes.tap do |file_types|
|
|
173
|
-
# file_types.alchemy_attachments =
|
|
176
|
+
# file_types.alchemy_attachments = %w[
|
|
177
|
+
<% @default_config.uploader.allowed_filetypes.alchemy_attachments.each_slice(8) do |extensions| -%>
|
|
178
|
+
# <%= extensions.join(" ") %>
|
|
179
|
+
<% end -%>
|
|
180
|
+
# ]
|
|
174
181
|
# file_types.alchemy_pictures = <%= @default_config.uploader.allowed_filetypes.alchemy_pictures.map(&:to_s) %>
|
|
175
182
|
# end
|
|
176
183
|
# end
|
|
177
184
|
|
|
178
185
|
# === Link Target Options
|
|
179
186
|
#
|
|
180
|
-
# Values for the link target selectbox inside the
|
|
181
|
-
# The value gets attached as a data-link-target attribute to the link.
|
|
187
|
+
# Values for the link target selectbox inside the link dialog.
|
|
182
188
|
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
# jQuery(a[data-link-target="overlay"]).dialog();
|
|
189
|
+
# Each value is stored with a leading underscore, so that it can be used as
|
|
190
|
+
# the link's `target` attribute right away: `blank` is stored as `_blank` and
|
|
191
|
+
# rendered as `target="_blank"`.
|
|
188
192
|
#
|
|
189
193
|
# config.link_target_options = <%= @default_config.link_target_options.map(&:to_s) %>
|
|
190
194
|
|
|
@@ -252,6 +256,17 @@ Alchemy.configure do |config|
|
|
|
252
256
|
# Additional stylesheets to be included in the Alchemy admin UI
|
|
253
257
|
# config.admin_stylesheets.add("my_app/admin_extension")
|
|
254
258
|
|
|
259
|
+
# === Admin Content Security Policy
|
|
260
|
+
#
|
|
261
|
+
# The class building the Content Security Policy Alchemy sends with its own
|
|
262
|
+
# admin responses. No policy is sent by default, until Alchemy 9.0.
|
|
263
|
+
#
|
|
264
|
+
# Alchemy never replaces a policy your application has configured itself.
|
|
265
|
+
# Your asset host, your admin stylesheets and modules pinned to a CDN are
|
|
266
|
+
# allowed automatically. Subclass it to allow anything else.
|
|
267
|
+
#
|
|
268
|
+
# config.admin_content_security_policy = "Alchemy::Admin::ContentSecurityPolicy"
|
|
269
|
+
|
|
255
270
|
# Define page publish targets
|
|
256
271
|
#
|
|
257
272
|
# A publish target is a ActiveJob that gets performed
|
|
@@ -14,6 +14,7 @@ module Alchemy
|
|
|
14
14
|
@class_name = @module_name.singularize.classify
|
|
15
15
|
@controller_name = @module_name.tableize
|
|
16
16
|
@controller_class = @controller_name.classify.pluralize
|
|
17
|
+
@index_route = (@controller_name.singularize == @controller_name) ? "#{@controller_name}_index" : @controller_name
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def make_route
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
name:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Alchemy.register_ability(<%= @controller_class %>Ability)
|
|
1
|
+
Alchemy.configure do |config|
|
|
2
|
+
config.menu.add(
|
|
3
|
+
name: "<%= @module_name %>",
|
|
4
|
+
title: "modules.<%= @module_name %>",
|
|
5
|
+
image: "alchemy/<%= @module_name %>_module.png",
|
|
6
|
+
url: -> { main_app.admin_<%= @index_route %>_path },
|
|
7
|
+
condition: -> { can?(:index, :admin_<%= @controller_name %>) },
|
|
8
|
+
children: [
|
|
9
|
+
{
|
|
10
|
+
title: "modules.<%= @module_name %>",
|
|
11
|
+
url: -> { main_app.admin_<%= @index_route %>_path },
|
|
12
|
+
condition: -> { can?(:index, :admin_<%= @controller_name %>) }
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
)
|
|
16
|
+
config.abilities.add("<%= @controller_class %>Ability")
|
|
19
17
|
end
|