alchemy_cms 8.0.11 → 8.1.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/README.md +14 -10
- data/app/assets/builds/alchemy/admin.css +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/{views/alchemy/admin/elements/_element.html.erb → components/alchemy/admin/element_editor.html.erb} +34 -29
- data/app/components/alchemy/admin/element_editor.rb +115 -0
- data/app/components/alchemy/admin/element_select.rb +12 -9
- data/app/components/alchemy/admin/ingredient_editor.rb +54 -0
- data/app/components/alchemy/admin/list_filter.rb +16 -5
- data/app/components/alchemy/admin/page_node.html.erb +214 -0
- data/app/components/alchemy/admin/page_node.rb +70 -0
- data/app/components/alchemy/admin/picture_thumbnail.rb +36 -0
- data/app/components/alchemy/admin/publish_page_button.html.erb +15 -0
- data/app/components/alchemy/admin/publish_page_button.rb +54 -0
- data/app/{helpers/alchemy/admin/tags_helper.rb → components/alchemy/admin/tags_list.rb} +19 -11
- data/app/components/alchemy/admin/toolbar_button.rb +17 -13
- data/app/components/alchemy/ingredients/audio_editor.rb +8 -0
- data/app/components/alchemy/ingredients/base_editor.rb +222 -0
- data/app/components/alchemy/ingredients/boolean_editor.rb +21 -0
- data/app/components/alchemy/ingredients/color_editor.rb +80 -0
- data/app/components/alchemy/ingredients/color_view.rb +13 -0
- data/app/components/alchemy/ingredients/datetime_editor.rb +28 -0
- data/app/components/alchemy/ingredients/file_editor.rb +69 -0
- data/app/components/alchemy/ingredients/headline_editor.rb +88 -0
- data/app/components/alchemy/ingredients/html_editor.rb +11 -0
- data/app/components/alchemy/ingredients/link_editor.rb +29 -0
- data/app/components/alchemy/ingredients/node_editor.rb +23 -0
- data/app/components/alchemy/ingredients/number_editor.rb +28 -0
- data/app/components/alchemy/ingredients/page_editor.rb +19 -0
- data/app/components/alchemy/ingredients/picture_editor.rb +81 -0
- data/app/components/alchemy/ingredients/richtext_editor.rb +31 -0
- data/app/components/alchemy/ingredients/select_editor.rb +37 -0
- data/app/components/alchemy/ingredients/select_view.rb +7 -0
- data/app/components/alchemy/ingredients/text_editor.rb +41 -0
- data/app/components/alchemy/ingredients/video_editor.rb +8 -0
- data/app/controllers/alchemy/admin/attachments_controller.rb +8 -6
- data/app/controllers/alchemy/admin/base_controller.rb +7 -18
- data/app/controllers/alchemy/admin/clipboard_controller.rb +15 -11
- data/app/controllers/alchemy/admin/dashboard_controller.rb +2 -2
- data/app/controllers/alchemy/admin/elements_controller.rb +34 -32
- data/app/controllers/alchemy/admin/ingredients_controller.rb +1 -0
- data/app/controllers/alchemy/admin/languages_controller.rb +0 -3
- data/app/controllers/alchemy/admin/layoutpages_controller.rb +2 -1
- data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +1 -1
- data/app/controllers/alchemy/admin/nodes_controller.rb +24 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +36 -42
- data/app/controllers/alchemy/admin/pictures_controller.rb +2 -5
- data/app/controllers/alchemy/admin/resources_controller.rb +1 -1
- data/app/controllers/alchemy/api/ingredients_controller.rb +1 -1
- data/app/controllers/alchemy/api/pages_controller.rb +5 -3
- data/app/controllers/alchemy/base_controller.rb +6 -6
- data/app/controllers/alchemy/pages_controller.rb +12 -6
- data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +0 -1
- data/app/controllers/concerns/alchemy/admin/clipboard.rb +57 -0
- data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +2 -2
- data/app/controllers/concerns/alchemy/site_redirects.rb +1 -1
- data/app/decorators/alchemy/ingredient_editor.rb +37 -4
- data/app/helpers/alchemy/admin/base_helper.rb +10 -6
- data/app/helpers/alchemy/admin/ingredients_helper.rb +6 -3
- data/app/helpers/alchemy/base_helper.rb +1 -1
- data/app/helpers/alchemy/pages_helper.rb +1 -1
- data/app/javascript/alchemy_admin/components/action.js +5 -1
- data/app/javascript/alchemy_admin/components/color_select.js +73 -0
- data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +11 -3
- data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +7 -2
- data/app/javascript/alchemy_admin/components/element_editor.js +11 -12
- data/app/javascript/alchemy_admin/components/element_select.js +39 -17
- data/app/javascript/alchemy_admin/components/elements_window.js +0 -2
- data/app/javascript/alchemy_admin/components/file_editor.js +26 -0
- data/app/javascript/alchemy_admin/components/index.js +9 -0
- data/app/javascript/alchemy_admin/components/list_filter.js +57 -8
- data/app/javascript/alchemy_admin/components/message.js +9 -3
- data/app/javascript/alchemy_admin/components/page_node.js +119 -0
- data/app/javascript/alchemy_admin/{page_publication_fields.js → components/page_publication_fields.js} +9 -8
- data/app/javascript/alchemy_admin/{picture_editors.js → components/picture_editor.js} +30 -45
- data/app/javascript/alchemy_admin/components/picture_thumbnail.js +107 -0
- data/app/javascript/alchemy_admin/components/publish_page_button.js +41 -0
- data/app/javascript/alchemy_admin/components/select.js +3 -1
- data/app/javascript/alchemy_admin/components/sitemap.js +210 -0
- data/app/javascript/alchemy_admin/{sortable_elements.js → components/sortable_elements.js} +22 -25
- data/app/javascript/alchemy_admin/components/tinymce.js +10 -5
- data/app/javascript/alchemy_admin/components/uploader.js +30 -0
- data/app/javascript/alchemy_admin/image_overlay.js +0 -2
- data/app/javascript/alchemy_admin/initializer.js +0 -3
- data/app/javascript/alchemy_admin/link_dialog.js +1 -6
- data/app/javascript/alchemy_admin/templates/compiled.js +1 -1
- data/app/javascript/alchemy_admin/utils/ajax.js +15 -3
- data/app/javascript/alchemy_admin.js +0 -6
- data/app/models/alchemy/attachment.rb +4 -4
- data/app/models/alchemy/element/definitions.rb +1 -2
- data/app/models/alchemy/element/element_ingredients.rb +6 -2
- data/app/models/alchemy/element.rb +54 -13
- data/app/models/alchemy/element_definition.rb +4 -1
- data/app/models/alchemy/elements_repository.rb +6 -0
- data/app/models/alchemy/folded_page.rb +2 -2
- data/app/models/alchemy/ingredient.rb +38 -1
- data/app/models/alchemy/ingredient_definition.rb +4 -1
- data/app/models/alchemy/ingredient_validator.rb +6 -2
- data/app/models/alchemy/ingredients/color.rb +10 -0
- data/app/models/alchemy/ingredients/headline.rb +2 -17
- data/app/models/alchemy/ingredients/picture.rb +4 -4
- data/app/models/alchemy/ingredients/select.rb +19 -0
- data/app/models/alchemy/language/code.rb +0 -1
- data/app/models/alchemy/node.rb +28 -1
- data/app/models/alchemy/page/page_naming.rb +0 -7
- data/app/models/alchemy/page/page_natures.rb +7 -3
- data/app/models/alchemy/page/page_scopes.rb +13 -1
- data/app/models/alchemy/page/publisher.rb +14 -2
- data/app/models/alchemy/page.rb +102 -23
- data/app/models/alchemy/page_definition.rb +4 -1
- data/app/models/alchemy/page_version.rb +22 -6
- data/app/models/alchemy/picture.rb +10 -11
- data/app/models/alchemy/picture_variant.rb +1 -3
- data/app/models/alchemy/resource.rb +1 -1
- data/app/models/alchemy/storage_adapter/active_storage.rb +14 -2
- data/app/models/alchemy/storage_adapter/dragonfly.rb +12 -0
- data/app/models/alchemy/storage_adapter.rb +2 -0
- data/app/models/concerns/alchemy/picture_thumbnails.rb +4 -4
- data/app/models/concerns/alchemy/publishable.rb +54 -0
- data/app/serializers/alchemy/page_tree_serializer.rb +11 -31
- data/app/services/alchemy/copy_page.rb +17 -0
- data/app/services/alchemy/duplicate_element.rb +1 -1
- data/app/services/alchemy/page_tree_preloader.rb +105 -0
- data/app/stylesheets/alchemy/_extends.scss +3 -9
- data/app/stylesheets/alchemy/_mixins.scss +3 -1
- data/app/stylesheets/alchemy/_themes.scss +19 -10
- data/app/stylesheets/alchemy/admin/archive.scss +1 -0
- data/app/stylesheets/alchemy/admin/base.scss +5 -2
- data/app/stylesheets/alchemy/admin/buttons.scss +3 -3
- data/app/stylesheets/alchemy/admin/element-select.scss +18 -0
- data/app/stylesheets/alchemy/admin/elements.scss +123 -23
- data/app/stylesheets/alchemy/admin/errors.scss +1 -1
- data/app/stylesheets/alchemy/admin/flash.scss +6 -4
- data/app/stylesheets/alchemy/admin/images.scss +9 -5
- data/app/stylesheets/alchemy/admin/list_filter.scss +4 -4
- data/app/stylesheets/alchemy/admin/navigation.scss +1 -1
- data/app/stylesheets/alchemy/admin/notices.scss +1 -2
- data/app/stylesheets/alchemy/admin/selects.scss +36 -21
- data/app/stylesheets/alchemy/admin/shoelace.scss +14 -1
- data/app/stylesheets/alchemy/admin/sitemap.scss +11 -3
- data/app/stylesheets/alchemy/admin/tags.scss +3 -1
- data/app/stylesheets/alchemy/admin/toolbar.scss +1 -1
- data/app/views/alchemy/_edit_mode.html.erb +1 -1
- data/app/views/alchemy/_menubar.html.erb +1 -1
- data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +35 -31
- data/app/views/alchemy/admin/attachments/_library_sidebar.html.erb +6 -0
- data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
- data/app/views/alchemy/admin/attachments/_replace_button.html.erb +1 -8
- data/app/views/alchemy/admin/attachments/_sorting_select.html.erb +13 -0
- data/app/views/alchemy/admin/attachments/_tag_list.html.erb +2 -3
- data/app/views/alchemy/admin/attachments/index.html.erb +5 -11
- data/app/views/alchemy/admin/attachments/show.html.erb +1 -1
- data/app/views/alchemy/admin/clipboard/_button.html.erb +1 -0
- data/app/views/alchemy/admin/clipboard/index.html.erb +4 -5
- data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +1 -1
- data/app/views/alchemy/admin/crop.html.erb +5 -7
- data/app/views/alchemy/admin/dashboard/widgets/_locked_pages.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +6 -6
- data/app/views/alchemy/admin/elements/_fixed_element.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_footer.html.erb +7 -1
- data/app/views/alchemy/admin/elements/_header.html.erb +5 -5
- data/app/views/alchemy/admin/elements/_toolbar.html.erb +33 -8
- data/app/views/alchemy/admin/elements/create.turbo_stream.erb +10 -10
- data/app/views/alchemy/admin/elements/index.html.erb +29 -16
- data/app/views/alchemy/admin/elements/new.html.erb +2 -2
- data/app/views/alchemy/admin/ingredients/update.turbo_stream.erb +3 -5
- data/app/views/alchemy/admin/leave.html.erb +1 -1
- data/app/views/alchemy/admin/nodes/_node.html.erb +19 -0
- data/app/views/alchemy/admin/nodes/edit.html.erb +1 -1
- data/app/views/alchemy/admin/nodes/index.html.erb +3 -1
- data/app/views/alchemy/admin/nodes/new.html.erb +14 -1
- data/app/views/alchemy/admin/pages/_current_page.html.erb +3 -1
- data/app/views/alchemy/admin/pages/_form.html.erb +21 -9
- data/app/views/alchemy/admin/pages/_page_status.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_publication_fields.html.erb +28 -26
- data/app/views/alchemy/admin/pages/_table.html.erb +0 -7
- data/app/views/alchemy/admin/pages/_toolbar.html.erb +3 -6
- data/app/views/alchemy/admin/pages/edit.html.erb +5 -11
- data/app/views/alchemy/admin/pages/flush.turbo_stream.erb +2 -0
- data/app/views/alchemy/admin/pages/fold.turbo_stream.erb +5 -0
- data/app/views/alchemy/admin/pages/index.html.erb +5 -3
- data/app/views/alchemy/admin/pages/new.html.erb +2 -12
- data/app/views/alchemy/admin/pages/publish.turbo_stream.erb +12 -0
- data/app/views/alchemy/admin/pages/tree.html.erb +13 -0
- data/app/views/alchemy/admin/pages/update.turbo_stream.erb +5 -16
- data/app/views/alchemy/admin/partials/_flash_notices.html.erb +1 -1
- data/app/views/alchemy/admin/partials/{_remote_search_form.html.erb → _overlay_search_form.html.erb} +1 -2
- data/app/views/alchemy/admin/partials/_paste_from_clipboard_form.html.erb +12 -0
- data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +24 -21
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +18 -26
- data/app/views/alchemy/admin/pictures/_picture.html.erb +11 -15
- data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +3 -6
- data/app/views/alchemy/admin/pictures/_tag_list.html.erb +2 -3
- data/app/views/alchemy/admin/pictures/index.html.erb +0 -1
- data/app/views/alchemy/admin/pictures/update.turbo_stream.erb +1 -1
- data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +1 -1
- data/app/views/alchemy/admin/resources/_tag_list.html.erb +2 -3
- data/app/views/alchemy/admin/styleguide/index.html.erb +25 -20
- data/app/views/alchemy/admin/tags/edit.html.erb +1 -1
- data/app/views/alchemy/admin/tinymce/_setup.html.erb +2 -2
- data/app/views/alchemy/admin/uploader/_button.html.erb +1 -15
- data/app/views/alchemy/attachments/show.html.erb +1 -1
- data/app/views/alchemy/base/permission_denied.js.erb +1 -1
- data/app/views/alchemy/ingredients/shared/_anchor.html.erb +9 -7
- data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +12 -5
- data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +10 -11
- data/app/views/alchemy/language_links/_spacer.html.erb +1 -1
- data/app/views/alchemy/messages_mailer/new.html.erb +1 -1
- data/app/views/alchemy/welcome.html.erb +1 -1
- data/config/locales/alchemy.en.yml +12 -3
- data/config/routes.rb +2 -2
- data/db/migrate/20230123112425_add_searchable_to_alchemy_pages.rb +1 -1
- data/db/migrate/20230505132743_add_indexes_to_alchemy_pictures.rb +1 -1
- data/db/migrate/20231113104432_create_page_mutexes.rb +1 -1
- data/db/migrate/20240314105244_create_alchemy_picture_descriptions.rb +1 -1
- data/db/migrate/20250626160259_add_unique_index_to_picture_descriptions.rb +1 -1
- data/db/migrate/20250905140323_add_created_at_index_to_pictures_and_attachments.rb +1 -1
- data/db/migrate/20251106150010_convert_select_value_for_multiple.rb +11 -0
- data/db/migrate/20260102121232_add_metadata_to_page_versions.rb +9 -0
- data/db/migrate/20260115164704_add_publication_timestamps_to_alchemy_elements.rb +30 -0
- data/db/migrate/20260115164705_add_index_to_element_publication_timestamps.rb +13 -0
- data/lib/alchemy/ability_helper.rb +1 -3
- data/lib/alchemy/auth_accessors.rb +51 -117
- data/lib/alchemy/configuration.rb +1 -0
- data/lib/alchemy/configurations/main.rb +63 -0
- data/lib/alchemy/controller_actions.rb +2 -3
- data/lib/alchemy/engine.rb +9 -12
- data/lib/alchemy/error_tracking/error_logger.rb +1 -1
- data/lib/alchemy/errors.rb +1 -1
- data/lib/alchemy/logger.rb +34 -4
- data/lib/alchemy/name_conversions.rb +0 -6
- data/lib/alchemy/seeder.rb +2 -2
- data/lib/alchemy/tasks/usage.rb +4 -4
- data/lib/alchemy/test_support/factories/page_version_factory.rb +3 -0
- data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +30 -0
- data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +26 -6
- data/lib/alchemy/test_support/shared_publishable_examples.rb +114 -0
- data/lib/alchemy/upgrader/eight_one.rb +56 -0
- data/lib/alchemy/upgrader.rb +9 -1
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +1 -4
- data/lib/alchemy_cms.rb +0 -1
- data/lib/generators/alchemy/elements/templates/view.html.erb +3 -3
- data/lib/generators/alchemy/ingredient/ingredient_generator.rb +6 -8
- data/lib/generators/alchemy/ingredient/templates/editor_component.rb.tt +22 -0
- data/lib/generators/alchemy/page_layouts/templates/layout.html.erb +1 -1
- data/lib/generators/alchemy/site_layouts/templates/layout.html.erb +1 -1
- data/lib/tasks/alchemy/upgrade.rake +21 -7
- data/vendor/javascript/shoelace.min.js +713 -31
- data/vendor/javascript/tinymce.min.js +1 -1
- metadata +104 -84
- data/app/decorators/alchemy/element_editor.rb +0 -90
- data/app/helpers/alchemy/admin/pictures_helper.rb +0 -14
- data/app/javascript/alchemy_admin/file_editors.js +0 -28
- data/app/javascript/alchemy_admin/image_loader.js +0 -54
- data/app/javascript/alchemy_admin/page_sorter.js +0 -71
- data/app/javascript/alchemy_admin/sitemap.js +0 -154
- data/app/javascript/alchemy_admin/templates/page_folder.hbs +0 -3
- data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -4
- data/app/views/alchemy/admin/pages/_page.html.erb +0 -163
- data/app/views/alchemy/admin/pages/_sitemap.html.erb +0 -30
- data/app/views/alchemy/admin/pages/flush.js.erb +0 -2
- data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +0 -5
- data/app/views/alchemy/admin/pictures/index.js.erb +0 -2
- data/app/views/alchemy/ingredients/_audio_editor.html.erb +0 -5
- data/app/views/alchemy/ingredients/_boolean_editor.html.erb +0 -11
- data/app/views/alchemy/ingredients/_datetime_editor.html.erb +0 -20
- data/app/views/alchemy/ingredients/_file_editor.html.erb +0 -52
- data/app/views/alchemy/ingredients/_headline_editor.html.erb +0 -44
- data/app/views/alchemy/ingredients/_html_editor.html.erb +0 -8
- data/app/views/alchemy/ingredients/_link_editor.html.erb +0 -30
- data/app/views/alchemy/ingredients/_node_editor.html.erb +0 -13
- data/app/views/alchemy/ingredients/_number_editor.html.erb +0 -24
- data/app/views/alchemy/ingredients/_page_editor.html.erb +0 -13
- data/app/views/alchemy/ingredients/_picture_editor.html.erb +0 -59
- data/app/views/alchemy/ingredients/_richtext_editor.html.erb +0 -15
- data/app/views/alchemy/ingredients/_select_editor.html.erb +0 -31
- data/app/views/alchemy/ingredients/_text_editor.html.erb +0 -29
- data/app/views/alchemy/ingredients/_video_editor.html.erb +0 -5
- data/lib/generators/alchemy/ingredient/templates/editor.html.erb +0 -14
- /data/{lib → app/models}/alchemy/permissions.rb +0 -0
|
@@ -2,32 +2,28 @@ import debounce from "alchemy_admin/utils/debounce"
|
|
|
2
2
|
import max from "alchemy_admin/utils/max"
|
|
3
3
|
import { get } from "alchemy_admin/utils/ajax"
|
|
4
4
|
import { growl } from "alchemy_admin/growler"
|
|
5
|
-
import ImageLoader from "alchemy_admin/image_loader"
|
|
6
5
|
|
|
7
6
|
const UPDATE_DELAY = 125
|
|
8
7
|
const IMAGE_PLACEHOLDER = '<alchemy-icon name="image" size="xl"></alchemy-icon>'
|
|
9
8
|
const THUMBNAIL_SIZE = "160x120"
|
|
10
9
|
|
|
11
|
-
export class PictureEditor {
|
|
12
|
-
constructor(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
10
|
+
export class PictureEditor extends HTMLElement {
|
|
11
|
+
constructor() {
|
|
12
|
+
super()
|
|
13
|
+
|
|
14
|
+
this.cropFromField = this.querySelector("[data-crop-from]")
|
|
15
|
+
this.cropSizeField = this.querySelector("[data-crop-size]")
|
|
16
|
+
this.pictureIdField = this.querySelector("[data-picture-id]")
|
|
17
|
+
this.targetSizeField = this.querySelector("[data-target-size]")
|
|
18
|
+
this.imageCropperField = this.querySelector("[data-image-cropper]")
|
|
19
|
+
this.image = this.querySelector("img")
|
|
20
|
+
this.pictureThumbnail = this.querySelector("alchemy-picture-thumbnail")
|
|
21
|
+
this.deleteButton = this.querySelector(".picture_tool.delete")
|
|
22
|
+
this.cropLink = this.querySelector(".crop_link")
|
|
23
23
|
|
|
24
24
|
this.targetSize = this.targetSizeField.dataset.targetSize
|
|
25
25
|
this.pictureId = this.pictureIdField.value
|
|
26
26
|
|
|
27
|
-
if (this.image) {
|
|
28
|
-
this.imageLoader = new ImageLoader(this.image)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
27
|
// The mutation observer is observing multiple fields that all get updated
|
|
32
28
|
// simultaneously. We only want to update the image once, so we debounce.
|
|
33
29
|
this.update = debounce(() => {
|
|
@@ -35,15 +31,19 @@ export class PictureEditor {
|
|
|
35
31
|
this.updateCropLink()
|
|
36
32
|
}, UPDATE_DELAY)
|
|
37
33
|
|
|
38
|
-
this.deleteButton
|
|
34
|
+
this.deleteButton?.addEventListener("click", this.removeImage.bind(this))
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
connectedCallback() {
|
|
38
|
+
this.observer = new MutationObserver(this.mutationCallback.bind(this))
|
|
39
|
+
|
|
40
|
+
this.observer.observe(this.cropFromField, { attributes: true })
|
|
41
|
+
this.observer.observe(this.cropSizeField, { attributes: true })
|
|
42
|
+
this.observer.observe(this.pictureIdField, { attributes: true })
|
|
43
|
+
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
observer.
|
|
46
|
-
observer.observe(this.pictureIdField, { attributes: true })
|
|
45
|
+
disconnectedCallback() {
|
|
46
|
+
this.observer.disconnect()
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
mutationCallback(mutationsList) {
|
|
@@ -60,10 +60,7 @@ export class PictureEditor {
|
|
|
60
60
|
updateImage() {
|
|
61
61
|
if (!this.pictureId) return
|
|
62
62
|
|
|
63
|
-
this.
|
|
64
|
-
this.image.removeAttribute("alt")
|
|
65
|
-
this.image.removeAttribute("src")
|
|
66
|
-
this.imageLoader.load(true)
|
|
63
|
+
this.pictureThumbnail.loading = true
|
|
67
64
|
get(Alchemy.routes.url_admin_picture_path(this.pictureId), {
|
|
68
65
|
crop: this.imageCropperEnabled,
|
|
69
66
|
crop_from: this.cropFrom,
|
|
@@ -72,9 +69,9 @@ export class PictureEditor {
|
|
|
72
69
|
size: THUMBNAIL_SIZE
|
|
73
70
|
})
|
|
74
71
|
.then(({ data }) => {
|
|
75
|
-
this.
|
|
76
|
-
this.image.alt = data.alt
|
|
77
|
-
this.image.title = data.title
|
|
72
|
+
this.pictureThumbnail.src = data.url
|
|
73
|
+
this.pictureThumbnail.image.alt = data.alt
|
|
74
|
+
this.pictureThumbnail.image.title = data.title
|
|
78
75
|
this.setElementDirty()
|
|
79
76
|
})
|
|
80
77
|
.catch((error) => {
|
|
@@ -83,15 +80,8 @@ export class PictureEditor {
|
|
|
83
80
|
})
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
ensureImage() {
|
|
87
|
-
const img = new Image()
|
|
88
|
-
this.thumbnailBackground.replaceChildren(img)
|
|
89
|
-
this.image = img
|
|
90
|
-
this.imageLoader = new ImageLoader(img)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
83
|
removeImage() {
|
|
94
|
-
this.
|
|
84
|
+
this.pictureThumbnail.innerHTML = IMAGE_PLACEHOLDER
|
|
95
85
|
this.pictureIdField.value = ""
|
|
96
86
|
this.image = null
|
|
97
87
|
this.cropLink.classList.add("disabled")
|
|
@@ -99,7 +89,7 @@ export class PictureEditor {
|
|
|
99
89
|
}
|
|
100
90
|
|
|
101
91
|
setElementDirty() {
|
|
102
|
-
this.
|
|
92
|
+
this.closest(".element-editor").setDirty(this)
|
|
103
93
|
}
|
|
104
94
|
|
|
105
95
|
updateCropLink() {
|
|
@@ -167,9 +157,4 @@ export class PictureEditor {
|
|
|
167
157
|
}
|
|
168
158
|
}
|
|
169
159
|
|
|
170
|
-
|
|
171
|
-
document.querySelectorAll(selector).forEach((node) => {
|
|
172
|
-
const thumbnail = new PictureEditor(node)
|
|
173
|
-
thumbnail.observe()
|
|
174
|
-
})
|
|
175
|
-
}
|
|
160
|
+
customElements.define("alchemy-picture-editor", PictureEditor)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Shows spinner while loading images and
|
|
2
|
+
// fades the image after its been loaded
|
|
3
|
+
|
|
4
|
+
import Spinner from "alchemy_admin/spinner"
|
|
5
|
+
|
|
6
|
+
export default class PictureThumbnail extends HTMLElement {
|
|
7
|
+
constructor() {
|
|
8
|
+
super()
|
|
9
|
+
|
|
10
|
+
this.classList.add("thumbnail_background")
|
|
11
|
+
this.spinner = new Spinner("small")
|
|
12
|
+
|
|
13
|
+
if (this.src) {
|
|
14
|
+
this.start()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
handleEvent(evt) {
|
|
19
|
+
switch (evt.type) {
|
|
20
|
+
case "load":
|
|
21
|
+
this.#onLoaded()
|
|
22
|
+
break
|
|
23
|
+
case "error":
|
|
24
|
+
this.#onError(evt)
|
|
25
|
+
break
|
|
26
|
+
default:
|
|
27
|
+
break
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
connectedCallback() {
|
|
32
|
+
if (this.image) {
|
|
33
|
+
this.replaceChildren(this.image)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
disconnectedCallback() {
|
|
38
|
+
this.image?.removeEventListener("load", this)
|
|
39
|
+
this.image?.removeEventListener("error", this)
|
|
40
|
+
this.stop()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
createImage(src = this.src, alt = this.name) {
|
|
44
|
+
this.image = new Image()
|
|
45
|
+
this.image.src = src
|
|
46
|
+
if (alt) {
|
|
47
|
+
this.image.alt = alt
|
|
48
|
+
}
|
|
49
|
+
this.image.loading = "lazy"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
start(src) {
|
|
53
|
+
this.createImage(src)
|
|
54
|
+
this.image.addEventListener("load", this)
|
|
55
|
+
this.image.addEventListener("error", this)
|
|
56
|
+
this.load()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
load() {
|
|
60
|
+
if (this.image?.complete) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
this.setAttribute("loading", "loading")
|
|
64
|
+
this.innerHTML = ""
|
|
65
|
+
this.spinner.spin(this)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
stop() {
|
|
69
|
+
this.classList.remove("loading")
|
|
70
|
+
this.spinner.stop()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#onLoaded() {
|
|
74
|
+
this.spinner.stop()
|
|
75
|
+
this.removeAttribute("loading")
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#onError(evt) {
|
|
79
|
+
const message = `Could not load ${this.image.src}`
|
|
80
|
+
this.spinner.stop()
|
|
81
|
+
this.innerHTML = `
|
|
82
|
+
<sl-tooltip content="${message}">
|
|
83
|
+
<alchemy-icon name="alert" class="error"></alchemy-icon>
|
|
84
|
+
</sl-tooltip>
|
|
85
|
+
`
|
|
86
|
+
console.error(message, evt)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
set loading(value) {
|
|
90
|
+
value ? this.load() : this.stop()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
set src(src) {
|
|
94
|
+
this.start(src)
|
|
95
|
+
this.replaceChildren(this.image)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get name() {
|
|
99
|
+
return this.getAttribute("name")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get src() {
|
|
103
|
+
return this.getAttribute("src")
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
customElements.define("alchemy-picture-thumbnail", PictureThumbnail)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class PublishPageButton extends HTMLElement {
|
|
2
|
+
constructor() {
|
|
3
|
+
super()
|
|
4
|
+
this.addEventListener("submit", this)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
connectedCallback() {
|
|
8
|
+
document.addEventListener("alchemy:page-dirty", this)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
disconnectedCallback() {
|
|
12
|
+
document.removeEventListener("alchemy:page-dirty", this)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
handleEvent(event) {
|
|
16
|
+
switch (event.type) {
|
|
17
|
+
case "alchemy:page-dirty":
|
|
18
|
+
this.markDirty(event.detail)
|
|
19
|
+
break
|
|
20
|
+
case "submit":
|
|
21
|
+
this.button.loading = true
|
|
22
|
+
break
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
markDirty(detail) {
|
|
27
|
+
this.button.variant = "primary"
|
|
28
|
+
this.button.disabled = false
|
|
29
|
+
this.tooltip.content = detail.tooltip
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get button() {
|
|
33
|
+
return this.querySelector("sl-button")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get tooltip() {
|
|
37
|
+
return this.querySelector("sl-tooltip")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
customElements.define("alchemy-publish-page-button", PublishPageButton)
|
|
@@ -10,7 +10,9 @@ class Select extends HTMLSelectElement {
|
|
|
10
10
|
allowClear: !!this.allowClear
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
-
if
|
|
13
|
+
// For single selects, remove the close button if allowClear is not set
|
|
14
|
+
// For multiple selects, always keep the close buttons
|
|
15
|
+
if (!this.allowClear && !this.multiple) {
|
|
14
16
|
this.#select2Element
|
|
15
17
|
.prev(".select2-container")
|
|
16
18
|
.find(".select2-search-choice-close")
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import Sortable from "sortablejs"
|
|
2
|
+
import { growl } from "alchemy_admin/growler"
|
|
3
|
+
import { patch } from "alchemy_admin/utils/ajax"
|
|
4
|
+
import { translate } from "alchemy_admin/i18n"
|
|
5
|
+
import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Custom element for the sitemap container
|
|
9
|
+
* Handles search/filter functionality and drag-and-drop sorting
|
|
10
|
+
*/
|
|
11
|
+
export class AlchemySitemap extends HTMLElement {
|
|
12
|
+
connectedCallback() {
|
|
13
|
+
this.searchInput = document.querySelector(".search_input_field")
|
|
14
|
+
this.clearButton = document.querySelector("#search_field_clear")
|
|
15
|
+
this.resultCounter = document.querySelector("#page_filter_result")
|
|
16
|
+
|
|
17
|
+
this.setupSearch()
|
|
18
|
+
|
|
19
|
+
// Wait for child custom elements to be defined before setting up sortables
|
|
20
|
+
requestAnimationFrame(() => {
|
|
21
|
+
this.setupSortables()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Set up MutationObserver to re-initialize sortables when children containers are added
|
|
25
|
+
this.observer = new MutationObserver((mutations) => {
|
|
26
|
+
mutations.forEach((mutation) => {
|
|
27
|
+
mutation.addedNodes.forEach((node) => {
|
|
28
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return
|
|
29
|
+
|
|
30
|
+
// If the added node itself is a children container, initialize it
|
|
31
|
+
if (node.classList?.contains("children")) {
|
|
32
|
+
this.setupSortable(node)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Also check for children containers nested within the added node
|
|
36
|
+
// This handles cases where a parent element with nested children is added at once
|
|
37
|
+
node
|
|
38
|
+
.querySelectorAll(".children")
|
|
39
|
+
.forEach((el) => this.setupSortable(el))
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Observe the sitemap for added nodes
|
|
45
|
+
this.observer.observe(this, {
|
|
46
|
+
childList: true,
|
|
47
|
+
subtree: true
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
disconnectedCallback() {
|
|
52
|
+
this.teardownSearch()
|
|
53
|
+
this.observer?.disconnect()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setupSearch() {
|
|
57
|
+
this.searchInput?.addEventListener("input", this)
|
|
58
|
+
this.clearButton?.addEventListener("click", this)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
teardownSearch() {
|
|
62
|
+
this.searchInput?.removeEventListener("input", this)
|
|
63
|
+
this.clearButton?.removeEventListener("click", this)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
handleEvent(event) {
|
|
67
|
+
if (event.type === "input" && event.target === this.searchInput) {
|
|
68
|
+
this.handleSearch(event)
|
|
69
|
+
} else if (event.type === "click" && event.target === this.clearButton) {
|
|
70
|
+
this.handleClearSearch(event)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
handleSearch(event) {
|
|
75
|
+
const term = event.target.value.toLowerCase().trim()
|
|
76
|
+
|
|
77
|
+
if (term === "") {
|
|
78
|
+
this.clearFilter()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.filterPages(term)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
filterPages(term) {
|
|
86
|
+
const allPages = this.querySelectorAll(".sitemap_page")
|
|
87
|
+
let matchCount = 0
|
|
88
|
+
let firstMatch = null
|
|
89
|
+
|
|
90
|
+
allPages.forEach((pageElement) => {
|
|
91
|
+
const pageName = pageElement.getAttribute("name") || ""
|
|
92
|
+
|
|
93
|
+
if (pageName.toLowerCase().includes(term)) {
|
|
94
|
+
pageElement.classList.add("highlight")
|
|
95
|
+
pageElement.classList.remove("no-match")
|
|
96
|
+
matchCount++
|
|
97
|
+
if (!firstMatch) firstMatch = pageElement
|
|
98
|
+
} else {
|
|
99
|
+
pageElement.classList.remove("highlight")
|
|
100
|
+
pageElement.classList.add("no-match")
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Update result counter
|
|
105
|
+
|
|
106
|
+
if (matchCount === 1) {
|
|
107
|
+
this.resultCounter.textContent = `1 ${translate("page_found")}`
|
|
108
|
+
this.resultCounter.style.display = "block"
|
|
109
|
+
} else if (matchCount > 1) {
|
|
110
|
+
this.resultCounter.textContent = `${matchCount} ${translate("pages_found")}`
|
|
111
|
+
this.resultCounter.style.display = "block"
|
|
112
|
+
} else {
|
|
113
|
+
this.resultCounter.style.display = "none"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Scroll first match into view
|
|
117
|
+
if (firstMatch) {
|
|
118
|
+
firstMatch.scrollIntoView({ behavior: "smooth", block: "center" })
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
clearFilter() {
|
|
123
|
+
const allPages = this.querySelectorAll(".sitemap_page")
|
|
124
|
+
allPages.forEach((pageElement) => {
|
|
125
|
+
pageElement.classList.remove("highlight", "no-match")
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
this.resultCounter.style.display = "none"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
handleClearSearch(event) {
|
|
132
|
+
event.preventDefault()
|
|
133
|
+
this.searchInput.value = ""
|
|
134
|
+
this.clearFilter()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
setupSortable(container) {
|
|
138
|
+
new Sortable(container, {
|
|
139
|
+
group: "pages",
|
|
140
|
+
animation: 150,
|
|
141
|
+
fallbackOnBody: true,
|
|
142
|
+
swapThreshold: 0.65,
|
|
143
|
+
handle: ".page-icon.handle",
|
|
144
|
+
draggable: "alchemy-page-node",
|
|
145
|
+
onEnd: (evt) => this.handleSort(evt)
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setupSortables() {
|
|
150
|
+
const sortables = this.querySelectorAll(".children")
|
|
151
|
+
sortables.forEach((el) => this.setupSortable(el))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async handleSort(evt) {
|
|
155
|
+
// Only proceed if actually moved to different position/container
|
|
156
|
+
if (evt.from === evt.to && evt.oldIndex === evt.newIndex) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// evt.item is the <alchemy-page-node> element being dragged
|
|
161
|
+
const pageNode = evt.item
|
|
162
|
+
const pageId = pageNode.pageId
|
|
163
|
+
const url = Alchemy.routes.move_admin_page_path(pageId)
|
|
164
|
+
const data = {
|
|
165
|
+
target_parent_id: evt.to.dataset.parentId,
|
|
166
|
+
new_position: evt.newIndex
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pleaseWaitOverlay(true)
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const response = await patch(url, data)
|
|
173
|
+
const pageData = await response.data
|
|
174
|
+
|
|
175
|
+
// Update the URL path of the moved page
|
|
176
|
+
const pageEl = pageNode.querySelector(`#page_${pageId}`)
|
|
177
|
+
if (pageEl) {
|
|
178
|
+
const urlPathEl = pageEl.querySelector(".sitemap_url")
|
|
179
|
+
if (urlPathEl && pageData.url_path) {
|
|
180
|
+
urlPathEl.textContent = pageData.url_path
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Update folder icons for affected parent pages
|
|
185
|
+
this.updateFolderIcons(evt.from, evt.to)
|
|
186
|
+
|
|
187
|
+
growl(translate("Successfully moved page"))
|
|
188
|
+
} catch (error) {
|
|
189
|
+
growl(error.message || error, "error")
|
|
190
|
+
// Revert the DOM change by reloading on error
|
|
191
|
+
window.location.reload()
|
|
192
|
+
} finally {
|
|
193
|
+
pleaseWaitOverlay(false)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
updateFolderIcons(fromContainer, toContainer) {
|
|
198
|
+
// Update folder icon for source parent (might now have no children)
|
|
199
|
+
const fromParent = fromContainer.closest("alchemy-page-node")
|
|
200
|
+
fromParent?.updateFolderButton()
|
|
201
|
+
|
|
202
|
+
// Update folder icon for destination parent (now definitely has children)
|
|
203
|
+
if (fromContainer !== toContainer) {
|
|
204
|
+
const toParent = toContainer.closest("alchemy-page-node")
|
|
205
|
+
toParent?.updateFolderButton()
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
customElements.define("alchemy-sitemap", AlchemySitemap)
|
|
@@ -2,10 +2,11 @@ import Sortable from "sortablejs"
|
|
|
2
2
|
import { growl } from "alchemy_admin/growler"
|
|
3
3
|
import { post } from "alchemy_admin/utils/ajax"
|
|
4
4
|
import { reloadPreview } from "alchemy_admin/components/preview_window"
|
|
5
|
+
import { dispatchPageDirtyEvent } from "alchemy_admin/components/element_editor"
|
|
5
6
|
|
|
6
7
|
const SORTABLE_OPTIONS = {
|
|
7
8
|
draggable: ".element-editor",
|
|
8
|
-
handle: ".element-handle",
|
|
9
|
+
handle: ".element-handle.draggable",
|
|
9
10
|
ghostClass: "dragged",
|
|
10
11
|
animation: 150,
|
|
11
12
|
swapThreshold: 0.65,
|
|
@@ -38,6 +39,9 @@ function onSort(event) {
|
|
|
38
39
|
post(Alchemy.routes.order_admin_elements_path, params).then((response) => {
|
|
39
40
|
const data = response.data
|
|
40
41
|
growl(data.message)
|
|
42
|
+
if (data.pageHasUnpublishedChanges) {
|
|
43
|
+
dispatchPageDirtyEvent(data)
|
|
44
|
+
}
|
|
41
45
|
reloadPreview()
|
|
42
46
|
item.updateTitle(data.preview_text)
|
|
43
47
|
})
|
|
@@ -51,31 +55,24 @@ function onEnd() {
|
|
|
51
55
|
)
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.
|
|
60
|
-
|
|
58
|
+
class SortableElements extends HTMLElement {
|
|
59
|
+
connectedCallback() {
|
|
60
|
+
const group = {
|
|
61
|
+
name: this.dataset.elementName,
|
|
62
|
+
put(to, _from, item) {
|
|
63
|
+
return to.el.dataset.droppableElements
|
|
64
|
+
.split(" ")
|
|
65
|
+
.includes(item.dataset.elementName)
|
|
66
|
+
}
|
|
61
67
|
}
|
|
68
|
+
new Sortable(this, {
|
|
69
|
+
...SORTABLE_OPTIONS,
|
|
70
|
+
onStart,
|
|
71
|
+
onSort,
|
|
72
|
+
onEnd,
|
|
73
|
+
group
|
|
74
|
+
})
|
|
62
75
|
}
|
|
63
|
-
new Sortable(element, {
|
|
64
|
-
...SORTABLE_OPTIONS,
|
|
65
|
-
...options,
|
|
66
|
-
onStart,
|
|
67
|
-
onSort,
|
|
68
|
-
onEnd,
|
|
69
|
-
group
|
|
70
|
-
})
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
if (selector == null) selector = ".sortable-elements"
|
|
75
|
-
|
|
76
|
-
const sortable_areas = document.querySelectorAll(selector, {
|
|
77
|
-
direction: "vertical"
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
sortable_areas.forEach((element) => createSortable(element))
|
|
81
|
-
}
|
|
78
|
+
customElements.define("alchemy-sortable-elements", SortableElements)
|
|
@@ -162,11 +162,16 @@ class Tinymce extends AlchemyHTMLElement {
|
|
|
162
162
|
const config = this.getAttribute(attributeName)
|
|
163
163
|
const key = attributeName.replaceAll("-", "_")
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
// Handle boolean HTML attributes (e.g., readonly="readonly" or readonly="")
|
|
166
|
+
if (config === attributeName || config === "") {
|
|
167
|
+
customConfig[key] = true
|
|
168
|
+
} else {
|
|
169
|
+
try {
|
|
170
|
+
customConfig[key] = JSON.parse(config)
|
|
171
|
+
} catch (e) {
|
|
172
|
+
// also string values as parameter
|
|
173
|
+
customConfig[key] = config
|
|
174
|
+
}
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
})
|
|
@@ -21,6 +21,32 @@ export class Uploader extends AlchemyHTMLElement {
|
|
|
21
21
|
if (this.dropzone) {
|
|
22
22
|
this._dragAndDropBehavior()
|
|
23
23
|
}
|
|
24
|
+
this.addEventListener("Alchemy.upload.successful", this)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
handleEvent(evt) {
|
|
28
|
+
switch (evt.type) {
|
|
29
|
+
case "Alchemy.upload.successful":
|
|
30
|
+
this._handleUploadComplete()
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_handleUploadComplete() {
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
const url = this.redirectUrl
|
|
38
|
+
const turboFrame = this.closest("turbo-frame")
|
|
39
|
+
this.uploadProgress.visible = false
|
|
40
|
+
|
|
41
|
+
if (!url) return
|
|
42
|
+
|
|
43
|
+
if (turboFrame) {
|
|
44
|
+
turboFrame.setAttribute("src", url)
|
|
45
|
+
turboFrame.reload()
|
|
46
|
+
} else {
|
|
47
|
+
Turbo.visit(url)
|
|
48
|
+
}
|
|
49
|
+
}, 750)
|
|
24
50
|
}
|
|
25
51
|
|
|
26
52
|
/**
|
|
@@ -126,6 +152,10 @@ export class Uploader extends AlchemyHTMLElement {
|
|
|
126
152
|
get fileInput() {
|
|
127
153
|
return this.querySelector("input[type='file']")
|
|
128
154
|
}
|
|
155
|
+
|
|
156
|
+
get redirectUrl() {
|
|
157
|
+
return this.getAttribute("redirect-url")
|
|
158
|
+
}
|
|
129
159
|
}
|
|
130
160
|
|
|
131
161
|
customElements.define("alchemy-uploader", Uploader)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import ImageLoader from "alchemy_admin/image_loader"
|
|
2
1
|
import { Dialog } from "alchemy_admin/dialog"
|
|
3
2
|
|
|
4
3
|
export default class ImageOverlay extends Dialog {
|
|
@@ -7,7 +6,6 @@ export default class ImageOverlay extends Dialog {
|
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
init() {
|
|
10
|
-
ImageLoader.init(this.dialog_body[0])
|
|
11
9
|
$(".zoomed-picture-background").on("click", (e) => {
|
|
12
10
|
e.stopPropagation()
|
|
13
11
|
if (e.target.nodeName === "IMG") {
|
|
@@ -45,9 +45,6 @@ export default function Initializer() {
|
|
|
45
45
|
$(this.form).submit()
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
-
// Attaches the image loader on all images
|
|
49
|
-
Alchemy.ImageLoader("#main_content")
|
|
50
|
-
|
|
51
48
|
// Override the filter of keymaster.js so we can blur the fields on esc key.
|
|
52
49
|
key.filter = function (event) {
|
|
53
50
|
let tagName = (event.target || event.srcElement).tagName
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { translate } from "alchemy_admin/i18n"
|
|
2
2
|
import { Dialog } from "alchemy_admin/dialog"
|
|
3
3
|
|
|
4
|
-
// Matches a URL fragment (#anchor) at the end of a string.
|
|
5
|
-
// Covers RFC 3986 unreserved characters (ALPHA, DIGIT, "-", ".", "_", "~")
|
|
6
|
-
// which are the characters valid in URL fragments and common in DOM element IDs.
|
|
7
|
-
const ANCHOR_REGEX = /#[\w.~-]+$/
|
|
8
|
-
|
|
9
4
|
// Represents the link Dialog that appears, if a user clicks the link buttons
|
|
10
5
|
// in TinyMCE or on an Ingredient that has links enabled (e.g. Picture)
|
|
11
6
|
//
|
|
@@ -108,7 +103,7 @@ export class LinkDialog extends Dialog {
|
|
|
108
103
|
|
|
109
104
|
if (linkType === "internal" && elementAnchor.value !== "") {
|
|
110
105
|
// remove possible fragments on the url and attach the fragment (which contains the #)
|
|
111
|
-
url = url.replace(
|
|
106
|
+
url = url.replace(/#\w+$/, "") + elementAnchor.value
|
|
112
107
|
} else if (linkType === "external" && !url.match(Alchemy.link_url_regexp)) {
|
|
113
108
|
// show validation error and prevent link creation
|
|
114
109
|
this.#showValidationError()
|