alchemy_cms 7.0.16 → 7.1.0.pre.b1
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/.github/workflows/backport.yml +36 -0
- data/.github/workflows/brakeman-analysis.yml +5 -13
- data/.github/workflows/lint.yml +2 -9
- data/.github/workflows/stale.yml +2 -5
- data/.github/workflows/test.yml +7 -15
- data/.gitignore +0 -1
- data/.standard.yml +1 -1
- data/CHANGELOG.md +144 -51
- data/Gemfile +7 -18
- data/README.md +10 -8
- data/alchemy_cms.gemspec +4 -3
- data/app/assets/config/alchemy_manifest.js +0 -1
- data/app/assets/javascripts/alchemy/admin.js +1 -19
- data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +2 -3
- data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +19 -34
- data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +38 -13
- data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +32 -25
- data/app/assets/javascripts/alchemy/alchemy.growler.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +3 -5
- data/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee +0 -57
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +22 -63
- data/app/assets/javascripts/alchemy/alchemy.list_filter.js.coffee +2 -2
- data/app/assets/javascripts/alchemy/alchemy.preview.js.coffee +5 -4
- data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +5 -5
- data/app/assets/javascripts/alchemy/templates/index.js +0 -2
- data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
- data/app/assets/javascripts/alchemy/templates/page.hbs +1 -1
- data/app/assets/javascripts/alchemy/templates/page_folder.hbs +2 -2
- data/app/assets/stylesheets/alchemy/_custom-properties.scss +82 -0
- data/app/assets/stylesheets/alchemy/_mixins.scss +38 -30
- data/app/assets/stylesheets/alchemy/_variables.scss +12 -5
- data/app/assets/stylesheets/alchemy/admin.scss +3 -4
- data/app/assets/stylesheets/alchemy/archive.scss +107 -50
- data/app/assets/stylesheets/alchemy/attachments.scss +5 -4
- data/app/assets/stylesheets/alchemy/buttons.scss +38 -164
- data/app/assets/stylesheets/alchemy/dashboard.scss +31 -6
- data/app/assets/stylesheets/alchemy/dialogs.scss +12 -28
- data/app/assets/stylesheets/alchemy/elements.scss +273 -282
- data/app/assets/stylesheets/alchemy/flash.scss +20 -12
- data/app/assets/stylesheets/alchemy/forms.scss +21 -34
- data/app/assets/stylesheets/alchemy/frame.scss +11 -32
- data/app/assets/stylesheets/alchemy/hints.scss +4 -62
- data/app/assets/stylesheets/alchemy/image_library.scss +36 -33
- data/app/assets/stylesheets/alchemy/labels.scss +4 -1
- data/app/assets/stylesheets/alchemy/menubar.scss +7 -6
- data/app/assets/stylesheets/alchemy/navigation.scss +27 -15
- data/app/assets/stylesheets/alchemy/nodes.scss +11 -7
- data/app/assets/stylesheets/alchemy/notices.scss +16 -4
- data/app/assets/stylesheets/alchemy/page-select.scss +10 -2
- data/app/assets/stylesheets/alchemy/pagination.scss +22 -13
- data/app/assets/stylesheets/alchemy/preview_window.scss +4 -8
- data/app/assets/stylesheets/alchemy/resource_info.scss +7 -5
- data/app/assets/stylesheets/alchemy/selects.scss +49 -42
- data/app/assets/stylesheets/alchemy/shoelace.scss +345 -0
- data/app/assets/stylesheets/alchemy/sitemap.scss +24 -14
- data/app/assets/stylesheets/alchemy/spinner.scss +9 -19
- data/app/assets/stylesheets/alchemy/tables.scss +16 -24
- data/app/assets/stylesheets/alchemy/tags.scss +4 -0
- data/app/assets/stylesheets/alchemy/toolbar.scss +29 -25
- data/app/assets/stylesheets/alchemy/upload.scss +140 -89
- data/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +80 -108
- data/app/components/alchemy/admin/node_select.rb +39 -0
- data/app/components/alchemy/admin/page_select.rb +42 -0
- data/app/components/alchemy/ingredients/audio_view.rb +1 -1
- data/app/components/alchemy/ingredients/base_view.rb +1 -1
- data/app/components/alchemy/ingredients/boolean_view.rb +1 -1
- data/app/components/alchemy/ingredients/datetime_view.rb +3 -4
- data/app/components/alchemy/ingredients/file_view.rb +1 -1
- data/app/components/alchemy/ingredients/headline_view.rb +7 -16
- data/app/components/alchemy/ingredients/link_view.rb +1 -1
- data/app/components/alchemy/ingredients/page_view.rb +1 -1
- data/app/components/alchemy/ingredients/picture_view.rb +1 -1
- data/app/components/alchemy/ingredients/richtext_view.rb +1 -1
- data/app/components/alchemy/ingredients/text_view.rb +1 -1
- data/app/components/alchemy/ingredients/video_view.rb +1 -1
- data/app/controllers/alchemy/admin/base_controller.rb +7 -32
- data/app/controllers/alchemy/admin/elements_controller.rb +63 -35
- data/app/controllers/alchemy/admin/languages_controller.rb +2 -3
- data/app/controllers/alchemy/admin/layoutpages_controller.rb +0 -19
- data/app/controllers/alchemy/admin/pages_controller.rb +4 -5
- data/app/controllers/alchemy/admin/resources_controller.rb +1 -1
- data/app/controllers/alchemy/base_controller.rb +4 -2
- data/app/controllers/alchemy/messages_controller.rb +1 -1
- data/app/controllers/concerns/alchemy/admin/current_language.rb +1 -5
- data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +1 -1
- data/app/decorators/alchemy/element_editor.rb +0 -2
- data/app/helpers/alchemy/admin/attachments_helper.rb +6 -5
- data/app/helpers/alchemy/admin/base_helper.rb +17 -12
- data/app/helpers/alchemy/admin/ingredients_helper.rb +4 -1
- data/app/helpers/alchemy/admin/pages_helper.rb +5 -11
- data/app/helpers/alchemy/base_helper.rb +47 -13
- data/app/javascript/alchemy_admin/components/alchemy_html_element.js +129 -0
- data/app/javascript/alchemy_admin/components/button.js +59 -0
- data/app/javascript/alchemy_admin/components/char_counter.js +40 -0
- data/app/javascript/alchemy_admin/components/datepicker.js +39 -0
- data/app/javascript/alchemy_admin/components/dialog_link.js +45 -0
- data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +36 -0
- data/app/javascript/alchemy_admin/components/element_editor.js +553 -0
- data/app/javascript/alchemy_admin/components/ingredient_group.js +54 -0
- data/app/javascript/alchemy_admin/components/link_buttons/link_button.js +48 -0
- data/app/javascript/alchemy_admin/components/link_buttons/unlink_button.js +38 -0
- data/app/javascript/alchemy_admin/components/link_buttons.js +79 -0
- data/app/javascript/alchemy_admin/components/node_select.js +45 -0
- data/app/javascript/alchemy_admin/components/overlay.js +18 -0
- data/app/javascript/alchemy_admin/components/page_select.js +63 -0
- data/app/javascript/alchemy_admin/components/remote_select.js +134 -0
- data/app/javascript/alchemy_admin/components/select.js +12 -0
- data/app/javascript/alchemy_admin/components/spinner.js +31 -0
- data/app/javascript/alchemy_admin/components/tinymce.js +146 -0
- data/app/javascript/alchemy_admin/components/uploader/file_upload.js +266 -0
- data/app/javascript/alchemy_admin/components/uploader/progress.js +258 -0
- data/app/javascript/alchemy_admin/components/uploader.js +132 -0
- data/app/javascript/alchemy_admin/dirty.js +49 -0
- data/app/javascript/alchemy_admin/file_editors.js +1 -1
- data/app/javascript/alchemy_admin/gui.js +14 -0
- data/app/javascript/alchemy_admin/i18n.js +12 -8
- data/app/javascript/alchemy_admin/image_cropper.js +6 -3
- data/app/javascript/alchemy_admin/image_loader.js +7 -15
- data/app/javascript/alchemy_admin/ingredient_anchor_link.js +2 -5
- data/app/javascript/alchemy_admin/initializer.js +65 -0
- data/app/javascript/alchemy_admin/locales/en.js +31 -0
- data/app/javascript/alchemy_admin/picture_editors.js +2 -2
- data/app/javascript/alchemy_admin/picture_selector.js +38 -0
- data/app/javascript/alchemy_admin/please_wait_overlay.js +8 -0
- data/app/javascript/alchemy_admin/sortable_elements.js +78 -0
- data/app/javascript/alchemy_admin/spinner.js +36 -0
- data/app/javascript/alchemy_admin/tags_autocomplete.js +46 -0
- data/app/javascript/alchemy_admin/utils/ajax.js +6 -5
- data/app/javascript/alchemy_admin/utils/dom_helpers.js +20 -0
- data/app/javascript/alchemy_admin/utils/format.js +11 -0
- data/app/javascript/alchemy_admin/utils/string_conversions.js +10 -0
- data/app/javascript/alchemy_admin.js +70 -13
- data/app/javascript/menubar.js +10 -0
- data/app/models/alchemy/attachment.rb +9 -11
- data/app/models/alchemy/element.rb +11 -0
- data/app/models/alchemy/ingredients/audio.rb +0 -11
- data/app/models/alchemy/ingredients/datetime.rb +1 -1
- data/app/models/alchemy/ingredients/richtext.rb +1 -10
- data/app/models/alchemy/ingredients/video.rb +0 -12
- data/app/models/alchemy/node.rb +4 -0
- data/app/models/alchemy/page/page_elements.rb +2 -11
- data/app/models/alchemy/page/page_natures.rb +10 -2
- data/app/models/alchemy/page.rb +12 -54
- data/app/models/alchemy/picture/url.rb +1 -9
- data/app/models/concerns/alchemy/picture_thumbnails.rb +5 -4
- data/app/serializers/alchemy/page_tree_serializer.rb +2 -1
- data/app/services/alchemy/copy_page.rb +98 -0
- data/app/views/alchemy/_menubar.html.erb +17 -13
- data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +14 -10
- data/app/views/alchemy/admin/attachments/_attachment.html.erb +44 -36
- data/app/views/alchemy/admin/attachments/_replace_button.html.erb +15 -21
- data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -1
- data/app/views/alchemy/admin/attachments/assign.js.erb +1 -1
- data/app/views/alchemy/admin/attachments/index.html.erb +6 -4
- data/app/views/alchemy/admin/attachments/show.html.erb +8 -8
- data/app/views/alchemy/admin/clipboard/clear.js.erb +1 -1
- data/app/views/alchemy/admin/clipboard/index.html.erb +3 -7
- data/app/views/alchemy/admin/clipboard/insert.js.erb +1 -1
- data/app/views/alchemy/admin/crop.html.erb +1 -1
- data/app/views/alchemy/admin/dashboard/_locked_pages.html.erb +1 -1
- data/app/views/alchemy/admin/dashboard/index.html.erb +13 -11
- data/app/views/alchemy/admin/dashboard/info.html.erb +7 -7
- data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +21 -23
- data/app/views/alchemy/admin/elements/_element.html.erb +52 -44
- data/app/views/alchemy/admin/elements/_footer.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_form.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_header.html.erb +11 -12
- data/app/views/alchemy/admin/elements/_toolbar.html.erb +33 -45
- data/app/views/alchemy/admin/elements/create.js.erb +7 -15
- data/app/views/alchemy/admin/elements/destroy.js.erb +0 -2
- data/app/views/alchemy/admin/elements/index.html.erb +27 -24
- data/app/views/alchemy/admin/elements/new.html.erb +9 -11
- data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +2 -2
- data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +3 -3
- data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +1 -2
- data/app/views/alchemy/admin/languages/_form.html.erb +2 -3
- data/app/views/alchemy/admin/languages/_language.html.erb +15 -8
- data/app/views/alchemy/admin/languages/_table.html.erb +1 -0
- data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +28 -16
- data/app/views/alchemy/admin/layoutpages/edit.html.erb +1 -1
- data/app/views/alchemy/admin/layoutpages/index.html.erb +2 -2
- data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +12 -8
- data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +1 -1
- data/app/views/alchemy/admin/nodes/_form.html.erb +20 -21
- data/app/views/alchemy/admin/nodes/_node.html.erb +39 -34
- data/app/views/alchemy/admin/nodes/index.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_anchor_link.html.erb +4 -4
- data/app/views/alchemy/admin/pages/_create_language_form.html.erb +2 -2
- data/app/views/alchemy/admin/pages/_current_page.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_external_link.html.erb +4 -4
- data/app/views/alchemy/admin/pages/_file_link.html.erb +5 -5
- data/app/views/alchemy/admin/pages/_form.html.erb +10 -21
- data/app/views/alchemy/admin/pages/_internal_link.html.erb +4 -4
- data/app/views/alchemy/admin/pages/_locked_page.html.erb +2 -2
- data/app/views/alchemy/admin/pages/_new_page_form.html.erb +4 -17
- data/app/views/alchemy/admin/pages/_page.html.erb +76 -72
- data/app/views/alchemy/admin/pages/_page_infos.html.erb +23 -7
- data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +2 -1
- data/app/views/alchemy/admin/pages/_page_status.html.erb +11 -21
- data/app/views/alchemy/admin/pages/_publication_fields.html.erb +2 -5
- data/app/views/alchemy/admin/pages/_table.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_table_row.html.erb +43 -39
- data/app/views/alchemy/admin/pages/_toolbar.html.erb +43 -38
- data/app/views/alchemy/admin/pages/configure.html.erb +12 -14
- data/app/views/alchemy/admin/pages/edit.html.erb +80 -103
- data/app/views/alchemy/admin/pages/info.html.erb +20 -11
- data/app/views/alchemy/admin/pages/link.html.erb +22 -16
- data/app/views/alchemy/admin/pages/new.html.erb +9 -11
- data/app/views/alchemy/admin/pages/unlock.js.erb +10 -3
- data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +15 -13
- data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +3 -5
- data/app/views/alchemy/admin/partials/_routes.html.erb +10 -2
- data/app/views/alchemy/admin/partials/_site_select.html.erb +6 -5
- data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +28 -23
- data/app/views/alchemy/admin/pictures/_archive.html.erb +5 -5
- data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +21 -23
- data/app/views/alchemy/admin/pictures/_infos.html.erb +2 -6
- data/app/views/alchemy/admin/pictures/_picture.html.erb +15 -17
- data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +17 -16
- data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +1 -1
- data/app/views/alchemy/admin/pictures/assign.js.erb +1 -1
- data/app/views/alchemy/admin/pictures/index.html.erb +34 -30
- data/app/views/alchemy/admin/pictures/show.html.erb +3 -3
- data/app/views/alchemy/admin/resources/_filter.html.erb +2 -2
- data/app/views/alchemy/admin/resources/_form.html.erb +2 -2
- data/app/views/alchemy/admin/resources/_per_page_select.html.erb +1 -1
- data/app/views/alchemy/admin/resources/_resource.html.erb +16 -9
- data/app/views/alchemy/admin/resources/_table.html.erb +4 -1
- data/app/views/alchemy/admin/resources/index.html.erb +22 -19
- data/app/views/alchemy/admin/sites/index.html.erb +2 -1
- data/app/views/alchemy/admin/styleguide/index.html.erb +54 -28
- data/app/views/alchemy/admin/tags/_tag.html.erb +16 -18
- data/app/views/alchemy/admin/tags/index.html.erb +15 -12
- data/app/views/alchemy/admin/tinymce/_setup.html.erb +29 -0
- data/app/views/alchemy/admin/uploader/_button.html.erb +23 -29
- data/app/views/alchemy/admin/uploader/_setup.html.erb +3 -8
- data/app/views/alchemy/base/500.html.erb +1 -1
- data/app/views/alchemy/base/error_notice.js.erb +0 -1
- data/app/views/alchemy/ingredients/_boolean_editor.html.erb +1 -1
- data/app/views/alchemy/ingredients/_datetime_editor.html.erb +2 -3
- data/app/views/alchemy/ingredients/_file_editor.html.erb +5 -5
- data/app/views/alchemy/ingredients/_link_editor.html.erb +1 -1
- data/app/views/alchemy/ingredients/_node_editor.html.erb +6 -19
- data/app/views/alchemy/ingredients/_page_editor.html.erb +7 -19
- data/app/views/alchemy/ingredients/_picture_editor.html.erb +2 -2
- data/app/views/alchemy/ingredients/_richtext_editor.html.erb +6 -15
- data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
- data/app/views/alchemy/ingredients/_text_editor.html.erb +1 -1
- data/app/views/alchemy/ingredients/shared/_anchor.html.erb +1 -1
- data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +10 -20
- data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +42 -49
- data/app/views/kaminari/alchemy/_first_page.html.erb +4 -2
- data/app/views/kaminari/alchemy/_gap.html.erb +1 -1
- data/app/views/kaminari/alchemy/_last_page.html.erb +4 -2
- data/app/views/kaminari/alchemy/_next_page.html.erb +4 -2
- data/app/views/kaminari/alchemy/_prev_page.html.erb +4 -2
- data/app/views/layouts/alchemy/admin.html.erb +10 -29
- data/config/alchemy/modules.yml +30 -30
- data/config/importmap.rb +10 -1
- data/config/initializers/rails_live_reload.rb +13 -0
- data/config/locales/alchemy.en.yml +23 -9
- data/config/routes.rb +3 -2
- data/lib/alchemy/auth_accessors.rb +6 -1
- data/lib/alchemy/controller_actions.rb +17 -4
- data/lib/alchemy/dev_support/live_reload_watcher.rb +5 -0
- data/lib/alchemy/engine.rb +8 -2
- data/lib/alchemy/forms/builder.rb +18 -12
- data/lib/alchemy/modules.rb +2 -2
- data/lib/alchemy/permissions.rb +1 -1
- data/lib/alchemy/resources_helper.rb +3 -3
- data/lib/alchemy/routing_constraints.rb +1 -1
- data/lib/alchemy/test_support/capybara_helpers.rb +8 -5
- data/lib/alchemy/test_support/rspec_matchers.rb +14 -0
- data/lib/alchemy/test_support/shared_uploader_examples.rb +1 -1
- data/lib/alchemy/tinymce.rb +8 -3
- data/lib/alchemy/version.rb +1 -1
- data/lib/tasks/alchemy/tidy.rake +1 -0
- data/package.json +14 -5
- data/vendor/assets/fonts/remixicon.eot +0 -0
- data/vendor/assets/fonts/remixicon.svg +7816 -0
- data/vendor/assets/fonts/remixicon.ttf +0 -0
- data/vendor/assets/fonts/remixicon.woff +0 -0
- data/vendor/assets/fonts/remixicon.woff2 +0 -0
- data/vendor/assets/stylesheets/remixicon.scss +10480 -0
- metadata +87 -97
- data/.gem_release.yml +0 -8
- data/app/assets/javascripts/alchemy/alchemy.autocomplete.js.coffee +0 -30
- data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +0 -53
- data/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee +0 -45
- data/app/assets/javascripts/alchemy/alchemy.char_counter.js.coffee +0 -19
- data/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee +0 -59
- data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +0 -79
- data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +0 -267
- data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +0 -27
- data/app/assets/javascripts/alchemy/alchemy.spinner.js +0 -32
- data/app/assets/javascripts/alchemy/alchemy.tooltips.coffee +0 -10
- data/app/assets/javascripts/alchemy/alchemy.uploader.js.coffee +0 -131
- data/app/assets/javascripts/alchemy/menubar.js.coffee +0 -8
- data/app/assets/javascripts/alchemy/node_select.js +0 -39
- data/app/assets/javascripts/alchemy/page_select.js +0 -46
- data/app/assets/javascripts/alchemy/templates/node.hbs +0 -16
- data/app/assets/javascripts/alchemy/templates/spinner.hbs +0 -7
- data/app/assets/stylesheets/alchemy/jquery-ui.scss +0 -435
- data/app/javascript/alchemy_admin/datepicker.js +0 -40
- data/app/javascript/alchemy_admin/tinymce.js +0 -146
- data/app/javascript/alchemy_admin/translations.js +0 -32
- data/app/views/alchemy/admin/elements/fold.js.erb +0 -33
- data/app/views/alchemy/admin/elements/order.js.erb +0 -11
- data/app/views/alchemy/admin/elements/publish.js.erb +0 -21
- data/app/views/alchemy/admin/elements/update.js.erb +0 -27
- data/vendor/assets/fonts/fa-regular-400.eot +0 -0
- data/vendor/assets/fonts/fa-regular-400.svg +0 -803
- data/vendor/assets/fonts/fa-regular-400.ttf +0 -0
- data/vendor/assets/fonts/fa-regular-400.woff +0 -0
- data/vendor/assets/fonts/fa-regular-400.woff2 +0 -0
- data/vendor/assets/fonts/fa-solid-900.eot +0 -0
- data/vendor/assets/fonts/fa-solid-900.svg +0 -4938
- data/vendor/assets/fonts/fa-solid-900.ttf +0 -0
- data/vendor/assets/fonts/fa-solid-900.woff +0 -0
- data/vendor/assets/fonts/fa-solid-900.woff2 +0 -0
- data/vendor/assets/javascripts/fileupload/jquery.fileupload-process.js +0 -178
- data/vendor/assets/javascripts/fileupload/jquery.fileupload-validate.js +0 -125
- data/vendor/assets/javascripts/fileupload/jquery.fileupload.js +0 -1502
- data/vendor/assets/javascripts/fileupload/jquery.iframe-transport.js +0 -224
- data/vendor/assets/javascripts/jquery-ui/data.js +0 -45
- data/vendor/assets/javascripts/jquery-ui/ie.js +0 -20
- data/vendor/assets/javascripts/jquery-ui/keycode.js +0 -51
- data/vendor/assets/javascripts/jquery-ui/plugin.js +0 -49
- data/vendor/assets/javascripts/jquery-ui/safe-active-element.js +0 -46
- data/vendor/assets/javascripts/jquery-ui/safe-blur.js +0 -27
- data/vendor/assets/javascripts/jquery-ui/scroll-parent.js +0 -50
- data/vendor/assets/javascripts/jquery-ui/unique-id.js +0 -54
- data/vendor/assets/javascripts/jquery-ui/version.js +0 -20
- data/vendor/assets/javascripts/jquery-ui/widget.js +0 -754
- data/vendor/assets/javascripts/jquery-ui/widgets/draggable.js +0 -1268
- data/vendor/assets/javascripts/jquery-ui/widgets/mouse.js +0 -241
- data/vendor/assets/javascripts/jquery-ui/widgets/sortable.js +0 -1623
- data/vendor/assets/javascripts/jquery-ui/widgets/tabs.js +0 -931
- data/vendor/assets/javascripts/jquery_plugins/jquery.scrollTo.min.js +0 -7
- data/vendor/assets/javascripts/jquery_plugins/jquery.ui.tabspaging.js +0 -296
- data/vendor/assets/stylesheets/fontawesome/_animated.scss +0 -20
- data/vendor/assets/stylesheets/fontawesome/_bordered-pulled.scss +0 -20
- data/vendor/assets/stylesheets/fontawesome/_core.scss +0 -21
- data/vendor/assets/stylesheets/fontawesome/_fixed-width.scss +0 -6
- data/vendor/assets/stylesheets/fontawesome/_icons.scss +0 -1441
- data/vendor/assets/stylesheets/fontawesome/_larger.scss +0 -23
- data/vendor/assets/stylesheets/fontawesome/_list.scss +0 -18
- data/vendor/assets/stylesheets/fontawesome/_mixins.scss +0 -56
- data/vendor/assets/stylesheets/fontawesome/_rotated-flipped.scss +0 -24
- data/vendor/assets/stylesheets/fontawesome/_screen-reader.scss +0 -5
- data/vendor/assets/stylesheets/fontawesome/_stacked.scss +0 -31
- data/vendor/assets/stylesheets/fontawesome/_variables.scss +0 -1458
- data/vendor/assets/stylesheets/fontawesome/fontawesome.scss +0 -16
- data/vendor/assets/stylesheets/fontawesome/regular.scss +0 -23
- data/vendor/assets/stylesheets/fontawesome/solid.scss +0 -24
@@ -0,0 +1,553 @@
|
|
1
|
+
import TagsAutocomplete from "alchemy_admin/tags_autocomplete"
|
2
|
+
import ImageLoader from "alchemy_admin/image_loader"
|
3
|
+
import fileEditors from "alchemy_admin/file_editors"
|
4
|
+
import pictureEditors from "alchemy_admin/picture_editors"
|
5
|
+
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
|
6
|
+
import { post } from "alchemy_admin/utils/ajax"
|
7
|
+
import { createHtmlElement } from "../utils/dom_helpers"
|
8
|
+
|
9
|
+
import "./element_editor/publish_element_button"
|
10
|
+
|
11
|
+
export class ElementEditor extends HTMLElement {
|
12
|
+
constructor() {
|
13
|
+
super()
|
14
|
+
|
15
|
+
// Add event listeners
|
16
|
+
this.addEventListener("click", this)
|
17
|
+
// Triggered by child elements
|
18
|
+
this.addEventListener("alchemy:element-update-title", this)
|
19
|
+
// We use of @rails/ujs for Rails remote forms
|
20
|
+
this.addEventListener("ajax:success", this)
|
21
|
+
// Dirty observer
|
22
|
+
this.addEventListener("change", this)
|
23
|
+
|
24
|
+
this.header?.addEventListener("dblclick", () => {
|
25
|
+
this.toggle()
|
26
|
+
})
|
27
|
+
this.toggleButton?.addEventListener("click", (evt) => {
|
28
|
+
const elementEditor = evt.target.closest("alchemy-element-editor")
|
29
|
+
if (elementEditor === this) {
|
30
|
+
this.toggle()
|
31
|
+
}
|
32
|
+
})
|
33
|
+
}
|
34
|
+
|
35
|
+
connectedCallback() {
|
36
|
+
// The placeholder while be being dragged is empty.
|
37
|
+
if (this.classList.contains("ui-sortable-placeholder")) {
|
38
|
+
return
|
39
|
+
}
|
40
|
+
|
41
|
+
// Init GUI elements
|
42
|
+
ImageLoader.init(this)
|
43
|
+
fileEditors(
|
44
|
+
`#${this.id} .ingredient-editor.file, #${this.id} .ingredient-editor.audio, #${this.id} .ingredient-editor.video`
|
45
|
+
)
|
46
|
+
pictureEditors(`#${this.id} .ingredient-editor.picture`)
|
47
|
+
TagsAutocomplete(this)
|
48
|
+
}
|
49
|
+
|
50
|
+
handleEvent(event) {
|
51
|
+
switch (event.type) {
|
52
|
+
case "click":
|
53
|
+
const elementEditor = event.target.closest("alchemy-element-editor")
|
54
|
+
if (elementEditor === this) {
|
55
|
+
this.onClickElement()
|
56
|
+
}
|
57
|
+
break
|
58
|
+
case "ajax:success":
|
59
|
+
if (event.target === this.body) {
|
60
|
+
const responseJSON = event.detail[0]
|
61
|
+
event.stopPropagation()
|
62
|
+
this.onSaveElement(responseJSON)
|
63
|
+
}
|
64
|
+
break
|
65
|
+
case "alchemy:element-update-title":
|
66
|
+
if (!this.hasEditors && event.target == this.firstChild) {
|
67
|
+
this.setTitle(event.detail.title)
|
68
|
+
}
|
69
|
+
break
|
70
|
+
case "change":
|
71
|
+
// SortableJS fires a native change event :/
|
72
|
+
// and we do not want to set the element editor dirty
|
73
|
+
// when this happens
|
74
|
+
if (event.target.classList.contains("nested-elements")) {
|
75
|
+
return
|
76
|
+
}
|
77
|
+
event.stopPropagation()
|
78
|
+
event.target.classList.add("dirty")
|
79
|
+
this.setDirty()
|
80
|
+
break
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Scrolls to and highlights element
|
86
|
+
* Expands if collapsed
|
87
|
+
* Also chooses the right fixed elements tab, if necessary.
|
88
|
+
* Can be triggered through custom event 'FocusElementEditor.Alchemy'
|
89
|
+
* Used by the elements on click events in the preview frame.
|
90
|
+
*/
|
91
|
+
async focusElement() {
|
92
|
+
// Select tab if necessary
|
93
|
+
if (document.querySelector("#fixed-elements")) {
|
94
|
+
await this.selectTabForElement()
|
95
|
+
}
|
96
|
+
// Expand if necessary
|
97
|
+
await this.expand()
|
98
|
+
this.selectElement(true)
|
99
|
+
}
|
100
|
+
|
101
|
+
focusElementPreview() {
|
102
|
+
Alchemy.PreviewWindow.postMessage({
|
103
|
+
message: "Alchemy.focusElement",
|
104
|
+
element_id: this.elementId
|
105
|
+
})
|
106
|
+
}
|
107
|
+
|
108
|
+
onClickElement() {
|
109
|
+
this.selectElement()
|
110
|
+
this.focusElementPreview()
|
111
|
+
}
|
112
|
+
|
113
|
+
/**
|
114
|
+
* Sets the element to saved state
|
115
|
+
* Updates title
|
116
|
+
* Shows error messages if ingredient validations fail
|
117
|
+
* @argument {JSON} data
|
118
|
+
*/
|
119
|
+
onSaveElement(data) {
|
120
|
+
// JS event bubbling will also update the parents element quote.
|
121
|
+
this.setClean()
|
122
|
+
// Reset errors that might be visible from last save attempt
|
123
|
+
this.errorsDisplay.innerHTML = ""
|
124
|
+
this.body
|
125
|
+
.querySelectorAll(".ingredient-editor")
|
126
|
+
.forEach((el) => el.classList.remove("validation_failed"))
|
127
|
+
// If validation failed
|
128
|
+
if (data.errors) {
|
129
|
+
const warning = data.warning
|
130
|
+
// Create error messages
|
131
|
+
data.errors.forEach((message) => {
|
132
|
+
this.errorsDisplay.append(createHtmlElement(`<li>${message}</li>`))
|
133
|
+
})
|
134
|
+
// Mark ingredients as failed
|
135
|
+
data.ingredientsWithErrors.forEach((id) => {
|
136
|
+
this.querySelector(`[data-ingredient-id="${id}"]`)?.classList.add(
|
137
|
+
"validation_failed"
|
138
|
+
)
|
139
|
+
})
|
140
|
+
// Show message
|
141
|
+
Alchemy.growl(warning, "warn")
|
142
|
+
this.elementErrors.classList.remove("hidden")
|
143
|
+
} else {
|
144
|
+
Alchemy.growl(data.notice)
|
145
|
+
Alchemy.PreviewWindow.refresh(() => this.focusElementPreview())
|
146
|
+
this.updateTitle(data.previewText)
|
147
|
+
data.ingredientAnchors.forEach((anchor) => {
|
148
|
+
IngredientAnchorLink.updateIcon(anchor.ingredientId, anchor.active)
|
149
|
+
})
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
/**
|
154
|
+
* Smoothly scrolls to element
|
155
|
+
*/
|
156
|
+
scrollToElement() {
|
157
|
+
// The timeout gives the browser some time to calculate the position
|
158
|
+
// of nested elements correctly
|
159
|
+
setTimeout(() => {
|
160
|
+
this.scrollIntoView({
|
161
|
+
behavior: "smooth"
|
162
|
+
})
|
163
|
+
}, 50)
|
164
|
+
}
|
165
|
+
|
166
|
+
/**
|
167
|
+
* Highlight element and optionally scroll into view
|
168
|
+
* @param {boolean} scroll smoothly scroll element into view. Default (false)
|
169
|
+
*/
|
170
|
+
selectElement(scroll = false) {
|
171
|
+
document
|
172
|
+
.querySelectorAll("alchemy-element-editor.selected")
|
173
|
+
.forEach((el) => {
|
174
|
+
el.classList.remove("selected")
|
175
|
+
})
|
176
|
+
window.requestAnimationFrame(() => {
|
177
|
+
this.classList.add("selected")
|
178
|
+
})
|
179
|
+
if (scroll) this.scrollToElement()
|
180
|
+
}
|
181
|
+
|
182
|
+
/**
|
183
|
+
* Selects tab for given element
|
184
|
+
* Resolves the promise if this is done.
|
185
|
+
* @returns {Promise}
|
186
|
+
*/
|
187
|
+
selectTabForElement() {
|
188
|
+
return new Promise((resolve, reject) => {
|
189
|
+
const tabs = document.querySelector("#fixed-elements")
|
190
|
+
const panel = this.closest("sl-tab-panel")
|
191
|
+
if (tabs && panel) {
|
192
|
+
tabs.show(panel.getAttribute("name"))
|
193
|
+
resolve()
|
194
|
+
} else {
|
195
|
+
reject(new Error("No tabs present"))
|
196
|
+
}
|
197
|
+
})
|
198
|
+
}
|
199
|
+
|
200
|
+
/**
|
201
|
+
* Sets the element into clean (safed) state
|
202
|
+
*/
|
203
|
+
setClean() {
|
204
|
+
this.dirty = false
|
205
|
+
window.onbeforeunload = null
|
206
|
+
if (this.hasEditors) {
|
207
|
+
this.body.querySelectorAll(".dirty").forEach((el) => {
|
208
|
+
el.classList.remove("dirty")
|
209
|
+
})
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
/**
|
214
|
+
* Sets the element into dirty (unsafed) state
|
215
|
+
*/
|
216
|
+
setDirty() {
|
217
|
+
if (this.hasEditors) {
|
218
|
+
this.dirty = true
|
219
|
+
window.onbeforeunload = () => Alchemy.t("page_dirty_notice")
|
220
|
+
}
|
221
|
+
}
|
222
|
+
|
223
|
+
/**
|
224
|
+
* Sets the title quote
|
225
|
+
* @param {string} title
|
226
|
+
*/
|
227
|
+
setTitle(title) {
|
228
|
+
const quote = this.querySelector(".element-header .preview_text_quote")
|
229
|
+
quote.textContent = title
|
230
|
+
}
|
231
|
+
|
232
|
+
/**
|
233
|
+
* Expands or collapses element editor
|
234
|
+
* If the element is dirty (has unsaved changes) it displays a confirm first.
|
235
|
+
*/
|
236
|
+
async toggle() {
|
237
|
+
if (this.collapsed) {
|
238
|
+
await this.expand()
|
239
|
+
} else {
|
240
|
+
await this.collapse()
|
241
|
+
}
|
242
|
+
}
|
243
|
+
|
244
|
+
/**
|
245
|
+
* Collapses the element editor and persists the state on the server
|
246
|
+
* @returns {Promise}
|
247
|
+
*/
|
248
|
+
collapse() {
|
249
|
+
if (this.collapsed || this.compact || this.fixed) {
|
250
|
+
return Promise.resolve("Element is already collapsed.")
|
251
|
+
}
|
252
|
+
|
253
|
+
const spinner = new Alchemy.Spinner("small")
|
254
|
+
spinner.spin(this.toggleButton)
|
255
|
+
this.toggleIcon?.classList?.add("hidden")
|
256
|
+
return post(Alchemy.routes.collapse_admin_element_path(this.elementId))
|
257
|
+
.then((response) => {
|
258
|
+
const data = response.data
|
259
|
+
|
260
|
+
this.collapsed = true
|
261
|
+
this.toggleButton?.setAttribute("title", data.title)
|
262
|
+
|
263
|
+
// Collapse all nested elements if necessarry
|
264
|
+
if (data.nestedElementIds.length) {
|
265
|
+
const selector = data.nestedElementIds
|
266
|
+
.map((id) => `#element_${id}`)
|
267
|
+
.join(", ")
|
268
|
+
this.querySelectorAll(selector).forEach((nestedElement) => {
|
269
|
+
nestedElement.collapsed = true
|
270
|
+
nestedElement.toggleButton?.setAttribute("title", data.title)
|
271
|
+
})
|
272
|
+
}
|
273
|
+
})
|
274
|
+
.catch((error) => {
|
275
|
+
Alchemy.growl(error.message, "error")
|
276
|
+
console.error(error)
|
277
|
+
})
|
278
|
+
.finally(() => {
|
279
|
+
this.toggleIcon?.classList?.remove("hidden")
|
280
|
+
spinner.stop()
|
281
|
+
})
|
282
|
+
}
|
283
|
+
|
284
|
+
/**
|
285
|
+
* Collapses the element editor and persists the state on the server
|
286
|
+
* @* @returns {Promise}
|
287
|
+
*/
|
288
|
+
expand() {
|
289
|
+
if (this.expanded && !this.compact) {
|
290
|
+
return Promise.resolve("Element is already expanded.")
|
291
|
+
}
|
292
|
+
|
293
|
+
if (this.compact && this.parentElementEditor) {
|
294
|
+
return this.parentElementEditor.expand()
|
295
|
+
} else {
|
296
|
+
const spinner = new Alchemy.Spinner("small")
|
297
|
+
spinner.spin(this.toggleButton)
|
298
|
+
this.toggleIcon?.classList.add("hidden")
|
299
|
+
|
300
|
+
return new Promise((resolve, reject) => {
|
301
|
+
post(Alchemy.routes.expand_admin_element_path(this.elementId))
|
302
|
+
.then((response) => {
|
303
|
+
const data = response.data
|
304
|
+
|
305
|
+
// First expand all parent elements if necessary
|
306
|
+
if (data.parentElementIds.length) {
|
307
|
+
const selector = data.parentElementIds
|
308
|
+
.map((id) => `#element_${id}`)
|
309
|
+
.join(", ")
|
310
|
+
document.querySelectorAll(selector).forEach((parentElement) => {
|
311
|
+
parentElement.collapsed = false
|
312
|
+
parentElement.toggleButton?.setAttribute("title", data.title)
|
313
|
+
})
|
314
|
+
}
|
315
|
+
// Finally expand ourselve
|
316
|
+
this.collapsed = false
|
317
|
+
this.toggleButton?.setAttribute("title", data.title)
|
318
|
+
// Resolve the promise that scrolls to the element very last
|
319
|
+
resolve()
|
320
|
+
})
|
321
|
+
.catch((error) => {
|
322
|
+
Alchemy.growl(error.message, "error")
|
323
|
+
console.error(error)
|
324
|
+
reject(error)
|
325
|
+
})
|
326
|
+
.finally(() => {
|
327
|
+
this.toggleIcon?.classList?.remove("hidden")
|
328
|
+
spinner.stop()
|
329
|
+
})
|
330
|
+
})
|
331
|
+
}
|
332
|
+
}
|
333
|
+
|
334
|
+
/**
|
335
|
+
* Updates the quote in the element header and dispatches event
|
336
|
+
* to parent elements
|
337
|
+
* @param {string} title
|
338
|
+
*/
|
339
|
+
updateTitle(title) {
|
340
|
+
this.setTitle(title)
|
341
|
+
this.dispatchEvent(
|
342
|
+
new CustomEvent("alchemy:element-update-title", {
|
343
|
+
bubbles: true,
|
344
|
+
detail: { title }
|
345
|
+
})
|
346
|
+
)
|
347
|
+
}
|
348
|
+
|
349
|
+
/**
|
350
|
+
* Sets element published or hidden
|
351
|
+
* @param {boolean}
|
352
|
+
*/
|
353
|
+
set published(isPublished) {
|
354
|
+
if (isPublished) {
|
355
|
+
this.classList.remove("hidden")
|
356
|
+
} else {
|
357
|
+
this.classList.add("hidden")
|
358
|
+
}
|
359
|
+
}
|
360
|
+
|
361
|
+
/**
|
362
|
+
* Is element published or hidden
|
363
|
+
* @returns {boolean}
|
364
|
+
*/
|
365
|
+
get published() {
|
366
|
+
return !this.classList.contains("hidden")
|
367
|
+
}
|
368
|
+
|
369
|
+
/**
|
370
|
+
* @returns {boolean}
|
371
|
+
*/
|
372
|
+
get compact() {
|
373
|
+
return this.getAttribute("compact") !== null
|
374
|
+
}
|
375
|
+
|
376
|
+
/**
|
377
|
+
* @returns {boolean}
|
378
|
+
*/
|
379
|
+
get fixed() {
|
380
|
+
return this.getAttribute("fixed") !== null
|
381
|
+
}
|
382
|
+
|
383
|
+
/**
|
384
|
+
* @param {boolean} value
|
385
|
+
*/
|
386
|
+
set collapsed(value) {
|
387
|
+
this.classList.toggle("folded", value)
|
388
|
+
this.classList.toggle("expanded", !value)
|
389
|
+
this.toggleIcon?.classList?.toggle("ri-arrow-down-s-line", !value)
|
390
|
+
this.toggleIcon?.classList?.toggle("ri-arrow-left-s-line", value)
|
391
|
+
}
|
392
|
+
|
393
|
+
/**
|
394
|
+
* @returns {boolean}
|
395
|
+
*/
|
396
|
+
get collapsed() {
|
397
|
+
return this.classList.contains("folded")
|
398
|
+
}
|
399
|
+
|
400
|
+
/**
|
401
|
+
* @returns {boolean}
|
402
|
+
*/
|
403
|
+
get expanded() {
|
404
|
+
return !this.collapsed
|
405
|
+
}
|
406
|
+
|
407
|
+
/**
|
408
|
+
* Toggles the dirty class
|
409
|
+
*
|
410
|
+
* @param {boolean} value
|
411
|
+
*/
|
412
|
+
set dirty(value) {
|
413
|
+
this.classList.toggle("dirty", value)
|
414
|
+
}
|
415
|
+
|
416
|
+
/**
|
417
|
+
* Returns the dirty state of this element
|
418
|
+
*
|
419
|
+
* @returns {boolean}
|
420
|
+
*/
|
421
|
+
get dirty() {
|
422
|
+
return this.classList.contains("dirty")
|
423
|
+
}
|
424
|
+
|
425
|
+
/**
|
426
|
+
* Returns the element header
|
427
|
+
*
|
428
|
+
* @returns {HTMLElement|undefined}
|
429
|
+
*/
|
430
|
+
get header() {
|
431
|
+
return this.querySelector(`.element-header`)
|
432
|
+
}
|
433
|
+
|
434
|
+
/**
|
435
|
+
* Returns the immediate body container of this element if present
|
436
|
+
*
|
437
|
+
* Makes sure it does not return a nested elements body
|
438
|
+
* by scoping the selector to this elements id.
|
439
|
+
*
|
440
|
+
* @returns {HTMLElement|undefined}
|
441
|
+
*/
|
442
|
+
get body() {
|
443
|
+
return this.querySelector(this.bodySelector)
|
444
|
+
}
|
445
|
+
|
446
|
+
get bodySelector() {
|
447
|
+
return `#${this.id} > .element-body`
|
448
|
+
}
|
449
|
+
|
450
|
+
/**
|
451
|
+
* Returns the immediate footer container of this element if present
|
452
|
+
*
|
453
|
+
* Makes sure it does not return a nested elements footer
|
454
|
+
* by scoping the selector to this elements id.
|
455
|
+
*
|
456
|
+
* @returns {HTMLElement|undefined}
|
457
|
+
*/
|
458
|
+
get footer() {
|
459
|
+
return this.querySelector(`#${this.id} > .element-footer`)
|
460
|
+
}
|
461
|
+
|
462
|
+
/**
|
463
|
+
* The collapse/expand toggle button
|
464
|
+
*
|
465
|
+
* @returns {HTMLButtonElement|undefined}
|
466
|
+
*/
|
467
|
+
get toggleButton() {
|
468
|
+
return this.querySelector(".element-toggle")
|
469
|
+
}
|
470
|
+
|
471
|
+
/**
|
472
|
+
* The collapse/expand toggle buttons icon
|
473
|
+
*
|
474
|
+
* @returns {HTMLElement|undefined}
|
475
|
+
*/
|
476
|
+
get toggleIcon() {
|
477
|
+
return this.toggleButton?.querySelector(".icon")
|
478
|
+
}
|
479
|
+
|
480
|
+
/**
|
481
|
+
* The error messages container
|
482
|
+
*
|
483
|
+
* @returns {HTMLElement}
|
484
|
+
*/
|
485
|
+
get errorsDisplay() {
|
486
|
+
return this.body.querySelector(".error-messages")
|
487
|
+
}
|
488
|
+
|
489
|
+
/**
|
490
|
+
* The validation messages list container
|
491
|
+
*
|
492
|
+
* @returns {HTMLElement}
|
493
|
+
*/
|
494
|
+
get elementErrors() {
|
495
|
+
return this.body.querySelector(".element_errors")
|
496
|
+
}
|
497
|
+
|
498
|
+
/**
|
499
|
+
* The element database id
|
500
|
+
*
|
501
|
+
* @returns {string}
|
502
|
+
*/
|
503
|
+
get elementId() {
|
504
|
+
return this.dataset.elementId
|
505
|
+
}
|
506
|
+
|
507
|
+
/**
|
508
|
+
* The element defintion name
|
509
|
+
*
|
510
|
+
* @returns {string}
|
511
|
+
*/
|
512
|
+
get elementName() {
|
513
|
+
return this.dataset.elementName
|
514
|
+
}
|
515
|
+
|
516
|
+
/**
|
517
|
+
* Does this element have ingredient editor fields?
|
518
|
+
*
|
519
|
+
* @returns {boolean}
|
520
|
+
*/
|
521
|
+
get hasEditors() {
|
522
|
+
return !!this.body?.querySelector(".element-ingredient-editors")
|
523
|
+
}
|
524
|
+
|
525
|
+
/**
|
526
|
+
* Does this element have nested elements?
|
527
|
+
*
|
528
|
+
* @returns {boolean}
|
529
|
+
*/
|
530
|
+
get hasChildren() {
|
531
|
+
return !!this.querySelector(".nested-elements")
|
532
|
+
}
|
533
|
+
|
534
|
+
/**
|
535
|
+
* The first child element editor if present
|
536
|
+
*
|
537
|
+
* @returns {HTMLButtonElement|undefined}
|
538
|
+
*/
|
539
|
+
get firstChild() {
|
540
|
+
return this.querySelector("alchemy-element-editor")
|
541
|
+
}
|
542
|
+
|
543
|
+
/**
|
544
|
+
* The parent element editor if present
|
545
|
+
*
|
546
|
+
* @returns {ElementEditor|undefined}
|
547
|
+
*/
|
548
|
+
get parentElementEditor() {
|
549
|
+
return this.parentElement?.closest("alchemy-element-editor")
|
550
|
+
}
|
551
|
+
}
|
552
|
+
|
553
|
+
customElements.define("alchemy-element-editor", ElementEditor)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
export class IngredientGroup extends HTMLDetailsElement {
|
2
|
+
#localStorageKey = "Alchemy.expanded_ingredient_groups"
|
3
|
+
|
4
|
+
constructor() {
|
5
|
+
super()
|
6
|
+
|
7
|
+
this.addEventListener("toggle", this)
|
8
|
+
|
9
|
+
if (this.isInLocalStorage) {
|
10
|
+
this.open = true
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Toggle visibility of the ingredient fields in this group
|
16
|
+
*/
|
17
|
+
handleEvent() {
|
18
|
+
let expanded_ingredient_groups = this.localStorageItem
|
19
|
+
|
20
|
+
if (this.open) {
|
21
|
+
if (!this.isInLocalStorage) expanded_ingredient_groups.push(this.id)
|
22
|
+
} else {
|
23
|
+
expanded_ingredient_groups = expanded_ingredient_groups.filter(
|
24
|
+
(value) => value !== this.id
|
25
|
+
)
|
26
|
+
}
|
27
|
+
|
28
|
+
localStorage.setItem(
|
29
|
+
this.#localStorageKey,
|
30
|
+
JSON.stringify(expanded_ingredient_groups)
|
31
|
+
)
|
32
|
+
}
|
33
|
+
|
34
|
+
get isInLocalStorage() {
|
35
|
+
return this.localStorageItem.includes(this.id)
|
36
|
+
}
|
37
|
+
|
38
|
+
get localStorageItem() {
|
39
|
+
const item = localStorage.getItem(this.#localStorageKey)
|
40
|
+
|
41
|
+
if (!item) return []
|
42
|
+
|
43
|
+
try {
|
44
|
+
return JSON.parse(item)
|
45
|
+
} catch (error) {
|
46
|
+
console.error(error)
|
47
|
+
return []
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
customElements.define("alchemy-ingredient-group", IngredientGroup, {
|
53
|
+
extends: "details"
|
54
|
+
})
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class LinkButton extends HTMLButtonElement {
|
2
|
+
constructor() {
|
3
|
+
super()
|
4
|
+
this.addEventListener("click", this)
|
5
|
+
this.classList.add("icon_button")
|
6
|
+
// Prevent accidental form submits if this component is wrapped inside a form
|
7
|
+
this.setAttribute("type", "button")
|
8
|
+
this.innerHTML = '<i class="icon ri-link-m ri-fw"></i>'
|
9
|
+
}
|
10
|
+
|
11
|
+
handleEvent(event) {
|
12
|
+
const dialog = new Alchemy.LinkDialog(this)
|
13
|
+
dialog.open()
|
14
|
+
event.preventDefault()
|
15
|
+
}
|
16
|
+
|
17
|
+
setLink(url, title, target, type) {
|
18
|
+
this.classList.add("linked")
|
19
|
+
this.dispatchEvent(
|
20
|
+
new CustomEvent("alchemy:link", {
|
21
|
+
bubbles: true,
|
22
|
+
detail: { url, title, target, type }
|
23
|
+
})
|
24
|
+
)
|
25
|
+
}
|
26
|
+
|
27
|
+
get linkUrl() {
|
28
|
+
return this.linkButtons.linkUrlField.value
|
29
|
+
}
|
30
|
+
|
31
|
+
get linkTitle() {
|
32
|
+
return this.linkButtons.linkTitleField.value
|
33
|
+
}
|
34
|
+
|
35
|
+
get linkTarget() {
|
36
|
+
return this.linkButtons.linkTargetField.value
|
37
|
+
}
|
38
|
+
|
39
|
+
get linkClass() {
|
40
|
+
return this.linkButtons.linkClassField.value
|
41
|
+
}
|
42
|
+
|
43
|
+
get linkButtons() {
|
44
|
+
return this.closest("alchemy-link-buttons")
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
customElements.define("alchemy-link-button", LinkButton, { extends: "button" })
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class UnlinkButton extends HTMLButtonElement {
|
2
|
+
constructor() {
|
3
|
+
super()
|
4
|
+
this.addEventListener("click", this)
|
5
|
+
this.classList.add("icon_button")
|
6
|
+
// Prevent accidental form submits if this component is wrapped inside a form
|
7
|
+
this.setAttribute("type", "button")
|
8
|
+
this.linked = this.linked
|
9
|
+
this.innerHTML = '<i class="icon ri-link-unlink-m ri-fw"></i>'
|
10
|
+
}
|
11
|
+
|
12
|
+
handleEvent(event) {
|
13
|
+
if (this.linked) {
|
14
|
+
this.linked = false
|
15
|
+
this.blur()
|
16
|
+
this.dispatchEvent(new CustomEvent("alchemy:unlink", { bubbles: true }))
|
17
|
+
}
|
18
|
+
event.preventDefault()
|
19
|
+
}
|
20
|
+
|
21
|
+
set linked(isLinked) {
|
22
|
+
if (isLinked) {
|
23
|
+
this.classList.replace("disabled", "linked")
|
24
|
+
this.removeAttribute("tabindex")
|
25
|
+
} else {
|
26
|
+
this.classList.replace("linked", "disabled")
|
27
|
+
this.setAttribute("tabindex", "-1")
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
get linked() {
|
32
|
+
return this.classList.contains("linked")
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
customElements.define("alchemy-unlink-button", UnlinkButton, {
|
37
|
+
extends: "button"
|
38
|
+
})
|