alchemy_cms 8.3.11 → 8.4.1
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 -27
- 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/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 +52 -11
- 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 +29 -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
|
@@ -1,29 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const colorIndicator =
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { Select } from "alchemy_admin/components/select"
|
|
2
|
+
|
|
3
|
+
const renderColorOption = (data, escape) => {
|
|
4
|
+
const swatch = data.swatch || data.value
|
|
5
|
+
const colorIndicator =
|
|
6
|
+
data.value === "custom_color"
|
|
7
|
+
? `<alchemy-icon name="palette"></alchemy-icon>`
|
|
8
|
+
: `<span class="color-indicator" style="--color: ${escape(swatch)}"></span>`
|
|
8
9
|
|
|
9
10
|
return `
|
|
10
11
|
<div class="select-color-option">
|
|
11
12
|
${colorIndicator}
|
|
12
|
-
<span>${
|
|
13
|
+
<span>${escape(data.text)}</span>
|
|
13
14
|
</div>`
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
// A color options select enhanced by Tom Select. It inherits from the shared
|
|
18
|
+
// alchemy-select component to reuse the Tom Select setup, renders each option
|
|
19
|
+
// and the selected item with a color swatch (or the custom-color icon), and
|
|
20
|
+
// enables the adjacent color picker while a custom color is selected.
|
|
21
|
+
export class ColorSelect extends Select {
|
|
22
|
+
connectedCallback() {
|
|
23
|
+
super.connectedCallback()
|
|
24
|
+
this.addEventListener("change", this.#onChange)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
disconnectedCallback() {
|
|
28
|
+
super.disconnectedCallback()
|
|
29
|
+
this.removeEventListener("change", this.#onChange)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get renderers() {
|
|
33
|
+
return {
|
|
34
|
+
item: renderColorOption,
|
|
35
|
+
option: renderColorOption
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#onChange = () => {
|
|
40
|
+
const colorPicker = this.parentElement?.querySelector("input[type='color']")
|
|
41
|
+
if (colorPicker) {
|
|
42
|
+
colorPicker.disabled = this.value !== "custom_color"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
customElements.define("alchemy-color-select", ColorSelect, {
|
|
48
|
+
extends: "select"
|
|
49
|
+
})
|
|
18
50
|
|
|
51
|
+
// Free-form color input used when no color options are configured. Keeps the
|
|
52
|
+
// text field and the native color picker in sync and enables the picker.
|
|
53
|
+
export class ColorInput extends HTMLElement {
|
|
19
54
|
connectedCallback() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.colorInput?.addEventListener("input", this)
|
|
25
|
-
this.textInput?.addEventListener("input", this)
|
|
26
|
-
this.#toggleColorPicker(true)
|
|
55
|
+
this.colorInput?.addEventListener("input", this)
|
|
56
|
+
this.textInput?.addEventListener("input", this)
|
|
57
|
+
if (this.colorInput) {
|
|
58
|
+
this.colorInput.disabled = false
|
|
27
59
|
}
|
|
28
60
|
}
|
|
29
61
|
|
|
@@ -41,29 +73,6 @@ class ColorSelect extends HTMLElement {
|
|
|
41
73
|
disconnectedCallback() {
|
|
42
74
|
this.colorInput?.removeEventListener("input", this)
|
|
43
75
|
this.textInput?.removeEventListener("input", this)
|
|
44
|
-
if (this.#select2) {
|
|
45
|
-
this.#select2.off("change", this.#onSelectChange)
|
|
46
|
-
this.#select2.select2("destroy")
|
|
47
|
-
this.#select2 = null
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
#onSelectChange = (event) => {
|
|
52
|
-
this.#toggleColorPicker(event.val === "custom_color")
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
#initializeSelect2() {
|
|
56
|
-
this.select.classList.add("alchemy_selectbox")
|
|
57
|
-
const options = {
|
|
58
|
-
minimumResultsForSearch: 10,
|
|
59
|
-
formatResult: formatItem,
|
|
60
|
-
formatSelection: formatItem
|
|
61
|
-
}
|
|
62
|
-
this.#select2 = $(this.select).select2(options)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
#toggleColorPicker(enabled = true) {
|
|
66
|
-
this.colorInput.disabled = !enabled
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
get colorInput() {
|
|
@@ -73,10 +82,6 @@ class ColorSelect extends HTMLElement {
|
|
|
73
82
|
get textInput() {
|
|
74
83
|
return this.querySelector("input[type='text']")
|
|
75
84
|
}
|
|
76
|
-
|
|
77
|
-
get select() {
|
|
78
|
-
return this.querySelector("select")
|
|
79
|
-
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
customElements.define("alchemy-color-
|
|
87
|
+
customElements.define("alchemy-color-input", ColorInput)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shows its fields only while the controlling checkbox is checked.
|
|
3
|
+
*
|
|
4
|
+
* Hidden fields are disabled as well, so they are not submitted and the stored
|
|
5
|
+
* value is left untouched while the condition does not apply. A disabled
|
|
6
|
+
* control (a fixed page attribute for example) keeps the fields disabled.
|
|
7
|
+
*
|
|
8
|
+
* <alchemy-conditional-field control="page_restricted">
|
|
9
|
+
* <input type="text" name="page[foo]">
|
|
10
|
+
* </alchemy-conditional-field>
|
|
11
|
+
*/
|
|
12
|
+
class ConditionalField extends HTMLElement {
|
|
13
|
+
connectedCallback() {
|
|
14
|
+
this.control?.addEventListener("change", this)
|
|
15
|
+
this.toggle()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
disconnectedCallback() {
|
|
19
|
+
this.control?.removeEventListener("change", this)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
handleEvent(event) {
|
|
23
|
+
if (event.type === "change") this.toggle()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
toggle() {
|
|
27
|
+
const active = !!this.control?.checked && !this.control.disabled
|
|
28
|
+
|
|
29
|
+
this.hidden = !active
|
|
30
|
+
this.fields.forEach((field) => {
|
|
31
|
+
// Custom elements like alchemy-select need their own API to keep their
|
|
32
|
+
// rendered control in sync, but might not be upgraded yet.
|
|
33
|
+
if (typeof field.disable === "function") {
|
|
34
|
+
active ? field.enable() : field.disable()
|
|
35
|
+
} else {
|
|
36
|
+
field.disabled = !active
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get control() {
|
|
42
|
+
return document.getElementById(this.getAttribute("control"))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get fields() {
|
|
46
|
+
return this.querySelectorAll("input, select, textarea")
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
customElements.define("alchemy-conditional-field", ConditionalField)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Turbo } from "@hotwired/turbo-rails"
|
|
2
|
+
import { openConfirmDialog } from "alchemy_admin/confirm_dialog"
|
|
3
|
+
import { translate } from "alchemy_admin/i18n"
|
|
4
|
+
import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
|
|
5
|
+
|
|
6
|
+
// Set once the user agreed to leave, so that the navigation they agreed to is
|
|
7
|
+
// not questioned again: a guarded form's redirect comes back through
|
|
8
|
+
// turbo:before-visit.
|
|
9
|
+
let leaveConfirmed = false
|
|
10
|
+
|
|
11
|
+
// Warns before a wrapped form takes the user away from a page that has unsaved
|
|
12
|
+
// element changes. Links need no wrapping, turbo:before-visit covers them, but
|
|
13
|
+
// Turbo dispatches that for a form only once its request has returned, which is
|
|
14
|
+
// too late for a form that changes something on the server.
|
|
15
|
+
//
|
|
16
|
+
// Guards in the capture phase, so that a cancelled submit never reaches
|
|
17
|
+
// Turbo's document level handlers, nor any component the guard sits in.
|
|
18
|
+
//
|
|
19
|
+
// Wrap an sl-tooltip, never the element inside it: Shoelace anchors to its
|
|
20
|
+
// first slotted child, and this element has no box of its own to anchor to.
|
|
21
|
+
class DirtyGuard extends HTMLElement {
|
|
22
|
+
connectedCallback() {
|
|
23
|
+
this.addEventListener("submit", this.#guard, true)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
disconnectedCallback() {
|
|
27
|
+
this.removeEventListener("submit", this.#guard, true)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#guard = (event) => {
|
|
31
|
+
if (leaveConfirmed || !isPageDirty()) return
|
|
32
|
+
|
|
33
|
+
event.preventDefault()
|
|
34
|
+
event.stopPropagation()
|
|
35
|
+
|
|
36
|
+
const form = event.target
|
|
37
|
+
confirmLeave().then((confirmed) => {
|
|
38
|
+
if (confirmed) {
|
|
39
|
+
pleaseWaitOverlay()
|
|
40
|
+
form.requestSubmit()
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isPageDirty() {
|
|
47
|
+
return document.querySelectorAll("alchemy-element-editor.dirty").length > 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function confirmLeave() {
|
|
51
|
+
const confirmed = await openConfirmDialog(translate("page_dirty_notice"), {
|
|
52
|
+
title: translate("warning"),
|
|
53
|
+
ok_label: translate("ok"),
|
|
54
|
+
cancel_label: translate("cancel")
|
|
55
|
+
})
|
|
56
|
+
if (confirmed) {
|
|
57
|
+
leaveConfirmed = true
|
|
58
|
+
window.onbeforeunload = void 0
|
|
59
|
+
}
|
|
60
|
+
return confirmed
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
document.addEventListener("turbo:before-visit", (event) => {
|
|
64
|
+
if (leaveConfirmed || !isPageDirty()) return
|
|
65
|
+
|
|
66
|
+
event.preventDefault()
|
|
67
|
+
|
|
68
|
+
confirmLeave().then((confirmed) => {
|
|
69
|
+
if (confirmed) Turbo.visit(event.detail.url)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
document.addEventListener("turbo:load", () => {
|
|
74
|
+
leaveConfirmed = false
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
customElements.define("alchemy-dirty-guard", DirtyGuard)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { get } from "alchemy_admin/utils/ajax"
|
|
2
2
|
import { translate } from "alchemy_admin/i18n"
|
|
3
|
+
import { growl } from "alchemy_admin/growler"
|
|
4
|
+
import { routes } from "alchemy_admin/routes"
|
|
3
5
|
|
|
4
6
|
class DomIdSelect extends HTMLElement {
|
|
5
7
|
dataItem(hash) {
|
|
@@ -22,17 +24,22 @@ class DomIdApiSelect extends DomIdSelect {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
async #fetchDomIds() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
try {
|
|
28
|
+
const result = await get(routes.api_ingredients_path, {
|
|
29
|
+
page_id: this.#pageId
|
|
30
|
+
})
|
|
31
|
+
const options = result.data.ingredients
|
|
32
|
+
.filter((ingredient) => ingredient.data?.dom_id)
|
|
33
|
+
.map((ingredient) => this.dataItem(ingredient.data.dom_id))
|
|
34
|
+
const prompt =
|
|
35
|
+
options.length > 0 ? translate("None") : translate("No anchors found")
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
this.selectElement.setOptions(options, prompt)
|
|
38
|
+
this.selectElement.enable()
|
|
39
|
+
} catch (error) {
|
|
40
|
+
growl(error.message || error, "error")
|
|
41
|
+
console.error(error)
|
|
42
|
+
}
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
#reset() {
|
|
@@ -17,8 +17,13 @@ export class DeleteElementButton extends HTMLElement {
|
|
|
17
17
|
async handleEvent() {
|
|
18
18
|
const confirmed = await openConfirmDialog(this.message)
|
|
19
19
|
if (confirmed) {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
try {
|
|
21
|
+
const response = await ajax("DELETE", this.url)
|
|
22
|
+
this.#removeElement(response.data)
|
|
23
|
+
} catch (error) {
|
|
24
|
+
growl(error.message || error, "error")
|
|
25
|
+
console.error(error)
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
|
|
2
1
|
import { post } from "alchemy_admin/utils/ajax"
|
|
3
|
-
import { createHtmlElement } from "alchemy_admin/utils/dom_helpers"
|
|
4
|
-
import { dispatchPageDirtyEvent } from "alchemy_admin/utils/dispatch_page_dirty_event"
|
|
5
2
|
import { growl } from "alchemy_admin/growler"
|
|
6
3
|
|
|
7
4
|
import "alchemy_admin/components/element_editor/publish_element_button"
|
|
8
5
|
import "alchemy_admin/components/element_editor/delete_element_button"
|
|
6
|
+
import { routes } from "alchemy_admin/routes"
|
|
9
7
|
|
|
10
8
|
export class ElementEditor extends HTMLElement {
|
|
11
9
|
#form = null
|
|
@@ -22,14 +20,12 @@ export class ElementEditor extends HTMLElement {
|
|
|
22
20
|
this.addEventListener("click", this)
|
|
23
21
|
// Triggered by child elements
|
|
24
22
|
this.addEventListener("alchemy:element-update-title", this)
|
|
25
|
-
// We use of @rails/ujs for Rails remote forms
|
|
26
|
-
this.addEventListener("ajax:complete", this)
|
|
27
23
|
|
|
28
|
-
// Dirty observer still needs to be jQuery
|
|
29
|
-
// in order to support select2.
|
|
30
24
|
this.#form = this.form
|
|
31
25
|
if (this.#form) {
|
|
32
|
-
|
|
26
|
+
this.#form.addEventListener("change", this.onChange)
|
|
27
|
+
this.#form.addEventListener("turbo:submit-start", this)
|
|
28
|
+
this.#form.addEventListener("turbo:submit-end", this)
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
this.#header = this.header
|
|
@@ -51,9 +47,10 @@ export class ElementEditor extends HTMLElement {
|
|
|
51
47
|
disconnectedCallback() {
|
|
52
48
|
this.removeEventListener("click", this)
|
|
53
49
|
this.removeEventListener("alchemy:element-update-title", this)
|
|
54
|
-
this.removeEventListener("ajax:complete", this)
|
|
55
50
|
if (this.#form) {
|
|
56
|
-
|
|
51
|
+
this.#form.removeEventListener("change", this.onChange)
|
|
52
|
+
this.#form.removeEventListener("turbo:submit-start", this)
|
|
53
|
+
this.#form.removeEventListener("turbo:submit-end", this)
|
|
57
54
|
this.#form = null
|
|
58
55
|
}
|
|
59
56
|
this.#header?.removeEventListener("dblclick", this.#onHeaderDblclick)
|
|
@@ -70,12 +67,11 @@ export class ElementEditor extends HTMLElement {
|
|
|
70
67
|
this.onClickElement()
|
|
71
68
|
}
|
|
72
69
|
break
|
|
73
|
-
case "
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
70
|
+
case "turbo:submit-start":
|
|
71
|
+
this.setClean()
|
|
72
|
+
break
|
|
73
|
+
case "turbo:submit-end":
|
|
74
|
+
this.onSaveElement(event.detail.success)
|
|
79
75
|
break
|
|
80
76
|
case "alchemy:element-update-title":
|
|
81
77
|
if (!this.hasEditors && event.target == this.firstChild) {
|
|
@@ -128,46 +124,20 @@ export class ElementEditor extends HTMLElement {
|
|
|
128
124
|
}
|
|
129
125
|
|
|
130
126
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.setClean()
|
|
141
|
-
// If validation failed
|
|
142
|
-
if (xhr.status === 422) {
|
|
143
|
-
const warning = data.warning
|
|
144
|
-
// Create error messages
|
|
145
|
-
// Mark ingredients as failed
|
|
146
|
-
data.ingredientsWithErrors.forEach((ingredient) => {
|
|
147
|
-
const ingredientEditor = this.querySelector(
|
|
148
|
-
`[data-ingredient-id="${ingredient.id}"]`
|
|
149
|
-
)
|
|
150
|
-
const errorDisplay = createHtmlElement(
|
|
151
|
-
`<small class="error">${ingredient.errorMessage}</small>`
|
|
152
|
-
)
|
|
153
|
-
ingredientEditor?.appendChild(errorDisplay)
|
|
154
|
-
ingredientEditor?.classList.add("validation_failed")
|
|
155
|
-
})
|
|
156
|
-
// Show message
|
|
157
|
-
growl(warning, "warn")
|
|
158
|
-
this.elementErrors.classList.remove("hidden")
|
|
159
|
-
} else {
|
|
160
|
-
growl(data.notice)
|
|
127
|
+
* Applies the client-side effects of a save once the turbo stream rendered.
|
|
128
|
+
*
|
|
129
|
+
* The notice, error markup, header title, anchor icons and publish button are
|
|
130
|
+
* server-rendered turbo streams. Only the preview refresh (which must focus
|
|
131
|
+
* this element afterwards) and the error box toggle remain client-side.
|
|
132
|
+
* @argument {boolean} success
|
|
133
|
+
*/
|
|
134
|
+
onSaveElement(success) {
|
|
135
|
+
if (success) {
|
|
161
136
|
this.previewWindow?.refresh().then(() => {
|
|
162
137
|
this.focusElementPreview()
|
|
163
138
|
})
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
IngredientAnchorLink.updateIcon(anchor.ingredientId, anchor.active)
|
|
167
|
-
})
|
|
168
|
-
if (data.pageHasUnpublishedChanges) {
|
|
169
|
-
dispatchPageDirtyEvent(data)
|
|
170
|
-
}
|
|
139
|
+
} else {
|
|
140
|
+
this.elementErrors.classList.remove("hidden")
|
|
171
141
|
}
|
|
172
142
|
}
|
|
173
143
|
|
|
@@ -286,7 +256,7 @@ export class ElementEditor extends HTMLElement {
|
|
|
286
256
|
const spinner = new Alchemy.Spinner("small")
|
|
287
257
|
spinner.spin(this.toggleButton)
|
|
288
258
|
this.toggleIcon?.classList?.add("hidden")
|
|
289
|
-
return post(
|
|
259
|
+
return post(routes.collapse_admin_element_path(this.elementId))
|
|
290
260
|
.then((response) => {
|
|
291
261
|
const data = response.data
|
|
292
262
|
|
|
@@ -331,7 +301,7 @@ export class ElementEditor extends HTMLElement {
|
|
|
331
301
|
this.toggleIcon?.classList.add("hidden")
|
|
332
302
|
|
|
333
303
|
return new Promise((resolve, reject) => {
|
|
334
|
-
post(
|
|
304
|
+
post(routes.expand_admin_element_path(this.elementId))
|
|
335
305
|
.then((response) => {
|
|
336
306
|
const data = response.data
|
|
337
307
|
|
|
@@ -567,10 +537,13 @@ export class ElementEditor extends HTMLElement {
|
|
|
567
537
|
/**
|
|
568
538
|
* The form element if present
|
|
569
539
|
*
|
|
540
|
+
* Scoped to this element's own form so a wrapper element without ingredients
|
|
541
|
+
* does not return a nested child's form.
|
|
542
|
+
*
|
|
570
543
|
* @returns {HTMLFormElement|undefined}
|
|
571
544
|
*/
|
|
572
545
|
get form() {
|
|
573
|
-
return this.querySelector(
|
|
546
|
+
return this.querySelector(`#${this.id} > form.element-body`)
|
|
574
547
|
}
|
|
575
548
|
|
|
576
549
|
/**
|
|
@@ -1,70 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#select2 = null
|
|
25
|
-
|
|
26
|
-
connectedCallback() {
|
|
27
|
-
const results = this.options
|
|
28
|
-
const options = {
|
|
29
|
-
minimumResultsForSearch: 3,
|
|
30
|
-
dropdownAutoWidth: true,
|
|
31
|
-
data() {
|
|
32
|
-
return { results }
|
|
33
|
-
},
|
|
34
|
-
formatResult: (option, _el, search) => {
|
|
35
|
-
let text
|
|
36
|
-
|
|
37
|
-
if (option.id === "") return option.text
|
|
38
|
-
if (search.term !== "") {
|
|
39
|
-
text = hightlightTerm(option.text, search.term)
|
|
40
|
-
} else {
|
|
41
|
-
text = option.text
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return formatItem(option.icon, text, option.hint)
|
|
45
|
-
},
|
|
46
|
-
formatSelection,
|
|
47
|
-
placeholder: this.placeholder
|
|
1
|
+
import { Select } from "alchemy_admin/components/select"
|
|
2
|
+
|
|
3
|
+
const renderName = (icon, text) => `
|
|
4
|
+
<div class="element-select-name">
|
|
5
|
+
${icon}<span>${text}</span>
|
|
6
|
+
</div>
|
|
7
|
+
`
|
|
8
|
+
|
|
9
|
+
export class ElementSelect extends Select {
|
|
10
|
+
get renderers() {
|
|
11
|
+
return {
|
|
12
|
+
item: (data, escape) => renderName(data.icon, escape(data.text)),
|
|
13
|
+
option: (data, escape) => {
|
|
14
|
+
const description = data.hint
|
|
15
|
+
? `<div class="element-select-description">${escape(data.hint)}</div>`
|
|
16
|
+
: ""
|
|
17
|
+
return `
|
|
18
|
+
<div class="element-select-item">
|
|
19
|
+
${renderName(data.icon, escape(data.text))}
|
|
20
|
+
${description}
|
|
21
|
+
</div>
|
|
22
|
+
`
|
|
23
|
+
}
|
|
48
24
|
}
|
|
49
|
-
this.#select2 = $(this.inputField).select2(options)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
disconnectedCallback() {
|
|
53
|
-
this.#select2?.select2("destroy")
|
|
54
|
-
this.#select2 = null
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
get options() {
|
|
58
|
-
return JSON.parse(this.getAttribute("options"))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
get placeholder() {
|
|
62
|
-
return this.getAttribute("placeholder")
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get inputField() {
|
|
66
|
-
return this.querySelector("input")
|
|
67
25
|
}
|
|
68
26
|
}
|
|
69
27
|
|
|
70
|
-
customElements.define("alchemy-element-select", ElementSelect
|
|
28
|
+
customElements.define("alchemy-element-select", ElementSelect, {
|
|
29
|
+
extends: "select"
|
|
30
|
+
})
|
|
@@ -5,9 +5,10 @@ class Icon extends HTMLElement {
|
|
|
5
5
|
|
|
6
6
|
constructor() {
|
|
7
7
|
super()
|
|
8
|
-
this.spriteUrl =
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
this.spriteUrl =
|
|
9
|
+
document
|
|
10
|
+
.querySelector('link[rel="preload"][as="image"]')
|
|
11
|
+
?.getAttribute("href") ?? ""
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
connectedCallback() {
|
|
@@ -48,4 +49,8 @@ class Icon extends HTMLElement {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
// Standalone components import this module next to the admin bundle, which
|
|
53
|
+
// already defined the element from its own copy.
|
|
54
|
+
if (!customElements.get("alchemy-icon")) {
|
|
55
|
+
customElements.define("alchemy-icon", Icon)
|
|
56
|
+
}
|