diff --git a/addons/website/static/src/snippets/s_image_gallery/000.xml b/addons/website/static/src/snippets/s_image_gallery/000.xml index 4d1c55e8874..e78d7b1c5d3 100644 --- a/addons/website/static/src/snippets/s_image_gallery/000.xml +++ b/addons/website/static/src/snippets/s_image_gallery/000.xml @@ -14,7 +14,7 @@ diff --git a/addons/website/static/src/snippets/s_image_gallery/options.js b/addons/website/static/src/snippets/s_image_gallery/options.js index e19f7c9aaa6..a52591554e5 100644 --- a/addons/website/static/src/snippets/s_image_gallery/options.js +++ b/addons/website/static/src/snippets/s_image_gallery/options.js @@ -43,14 +43,14 @@ options.registry.GalleryLayout = options.registry.CarouselHandler.extend({ * @private */ _grid() { - const imgs = this._getItemsGallery(); + const imgs = this._getImgHolderEls(); var $row = $('
', {class: 'row s_nb_column_fixed'}); var columns = this._getColumns(); var colClass = 'col-lg-' + (12 / columns); var $container = this._replaceContent($row); imgs.forEach((img, index) => { - const $img = $(img.cloneNode()); + const $img = $(img.cloneNode(true)); var $col = $('
', {class: colClass}); $col.append($img).appendTo($row); if ((index + 1) % columns === 0) { @@ -67,7 +67,7 @@ options.registry.GalleryLayout = options.registry.CarouselHandler.extend({ * @returns {Promise} */ _masonry() { - const imgs = this._getItemsGallery(); + const imgs = this._getImgHolderEls(); var columns = this._getColumns(); var colClass = 'col-lg-' + (12 / columns); var cols = []; @@ -100,7 +100,7 @@ options.registry.GalleryLayout = options.registry.CarouselHandler.extend({ // Only on Chrome: appended images are sometimes invisible // and not correctly loaded from cache, we use a clone of the // image to force the loading. - smallestColEl.append(imgEl.cloneNode()); + smallestColEl.append(imgEl.cloneNode(true)); await wUtils.onceAllImagesLoaded(this.$target); } resolve(); @@ -138,15 +138,16 @@ options.registry.GalleryLayout = options.registry.CarouselHandler.extend({ _nomode() { var $row = $('
', {class: 'row s_nb_column_fixed'}); const imgs = this._getItemsGallery(); + const imgHolderEls = this._getImgHolderEls(); this._replaceContent($row); - imgs.forEach((img) => { + imgs.forEach((img, index) => { var wrapClass = 'col-lg-3'; if (img.width >= img.height * 2 || img.width > 600) { wrapClass = 'col-lg-6'; } - var $wrap = $('
', {class: wrapClass}).append(img); + var $wrap = $('
', {class: wrapClass}).append(imgHolderEls[index]); $row.append($wrap); }); }, @@ -157,10 +158,14 @@ options.registry.GalleryLayout = options.registry.CarouselHandler.extend({ */ _slideshow() { const imageEls = this._getItemsGallery(); + const imgHolderEls = this._getImgHolderEls(); const images = Array.from(imageEls).map((img) => ({ // Use getAttribute to get the attribute value otherwise .src // returns the absolute url. src: img.getAttribute('src'), + // TODO: remove me in master. This is not needed anymore as the + // images of the rendered `website.gallery.slideshow` are replaced + // by the elements of `imgHolderEls`. alt: img.getAttribute('alt'), })); var currentInterval = this.$target.find('.carousel:first').attr('data-bs-interval'); @@ -170,10 +175,23 @@ options.registry.GalleryLayout = options.registry.CarouselHandler.extend({ title: "", interval: currentInterval || 0, id: 'slideshow_' + new Date().getTime(), + // TODO: in master, remove `attrClass` and `attStyle` from `params`. + // This is not needed anymore as the images of the rendered + // `website.gallery.slideshow` are replaced by the elements of + // `imgHolderEls`. attrClass: imageEls.length > 0 ? imageEls[0].className : '', attrStyle: imageEls.length > 0 ? imageEls[0].style.cssText : '', }, $slideshow = $(renderToElement('website.gallery.slideshow', params)); + const imgSlideshowEls = $slideshow[0].querySelectorAll("img[data-o-main-image]"); + imgSlideshowEls.forEach((imgSlideshowEl, index) => { + // Replace the template image by the original one. This is needed in + // order to keep the characteristics of the image such as the + // filter, the width, the quality, the link on which the users are + // redirected once they click on the image etc... + imgSlideshowEl.after(imgHolderEls[index]); + imgSlideshowEl.remove(); + }); this._replaceContent($slideshow); this.$("img").toArray().forEach((img, index) => { $(img).attr({contenteditable: true, 'data-index': index}); @@ -192,6 +210,17 @@ options.registry.GalleryLayout = options.registry.CarouselHandler.extend({ imgs.sort((a, b) => this._getIndex(a) - this._getIndex(b)); return imgs; }, + /** + * Returns the images, or the images holder if this holder is an anchor, + * sorted by index. + * + * @private + * @returns {Array.} + */ + _getImgHolderEls: function () { + const imgEls = this._getItemsGallery(); + return imgEls.map(imgEl => imgEl.closest("a") || imgEl); + }, /** * Returns the index associated to a given image. * diff --git a/addons/website/static/tests/tours/snippet_image_gallery.js b/addons/website/static/tests/tours/snippet_image_gallery.js index 677b891587c..31999540fce 100644 --- a/addons/website/static/tests/tours/snippet_image_gallery.js +++ b/addons/website/static/tests/tours/snippet_image_gallery.js @@ -17,7 +17,7 @@ wTourUtils.registerWebsitePreviewTour('snippet_image_gallery', { { content: 'Check that the modal has opened properly', trigger: 'iframe .s_gallery_lightbox img', - run: () => {}, // This is a check. + isCheck: true, }, ]); @@ -52,12 +52,42 @@ wTourUtils.registerWebsitePreviewTour("snippet_image_gallery_remove", { }, { content: "Check that the Snippet Editor of the clicked image has been loaded", trigger: "we-customizeblock-options span:contains('Image'):not(:contains('Image Gallery'))", - run: () => null, + isCheck: true, }, { content: "Click on Remove Block", trigger: ".o_we_customize_panel we-title:has(span:contains('Image Gallery')) we-button[title='Remove Block']", }, { content: "Check that the Image Gallery snippet has been removed", trigger: "iframe #wrap:not(:has(.s_image_gallery))", - run: () => null, + isCheck: true, +}]); + +wTourUtils.registerWebsitePreviewTour("snippet_image_gallery_reorder", { + test: true, + url: "/", + edition: true, +}, () => [ + wTourUtils.dragNDrop({ + id: "s_image_gallery", + name: "Image Gallery", + }), +{ + content: "Click on the first image of the snippet", + trigger: "iframe .s_image_gallery .carousel-item.active img", +}, + wTourUtils.changeOption('ImageTools', 'we-select:contains("Filter") we-toggler'), + wTourUtils.changeOption('ImageTools', '[data-gl-filter="blur"]'), +{ + content: "Check that the image has the correct filter", + trigger: ".snippet-option-ImageTools we-select:contains('Filter') we-toggler:contains('Blur')", + isCheck: true, +}, { + content: "Click on move to next", + trigger: ".snippet-option-GalleryElement we-button[data-position='next']", +}, { + content: "Check that the moved image still has the correct filter", + // FIXME somehow checking what the editor panel shows here is not reliable + // unless you add a big delay before checking. + trigger: "iframe .s_image_gallery .carousel-item.active img[data-index='1'][data-gl-filter='blur']", + isCheck: true, }]); diff --git a/addons/website/static/tests/tours/snippet_images_wall.js b/addons/website/static/tests/tours/snippet_images_wall.js index 2f4885b1747..44605257c4e 100644 --- a/addons/website/static/tests/tours/snippet_images_wall.js +++ b/addons/website/static/tests/tours/snippet_images_wall.js @@ -35,6 +35,7 @@ const reselectSignImageSteps = [ ...preventRaceConditionSteps, { content: "Select footer", + extra_trigger: "iframe .s_image_gallery .o_masonry_col:nth-child(3):has(img[data-index='5'])", trigger: "iframe footer", }, selectSignImageStep]; @@ -52,11 +53,18 @@ wTourUtils.registerWebsitePreviewTour("snippet_images_wall", { }), selectSignImageStep, { + content: "Click on add a link", + trigger: ".snippet-option-ReplaceMedia we-button[data-set-link]", +}, { + content: "Change the link of the image", + trigger: ".snippet-option-ReplaceMedia [data-set-url] input", + run: "text /contactus", +}, { content: "Click on move to previous", trigger: ".snippet-option-GalleryElement we-button[data-position='prev']", }, { content: "Check if sign is in second column", - trigger: "iframe .s_image_gallery .o_masonry_col:nth-child(2):has(img[data-index='1'][data-original-src*='library_image_14'])", + trigger: "iframe .s_image_gallery .o_masonry_col:nth-child(2):has(a[href='/contactus'] img[data-index='1'][data-original-src*='library_image_14'])", isCheck: true, }, ...reselectSignImageSteps, diff --git a/addons/website/tests/test_snippets.py b/addons/website/tests/test_snippets.py index 1c52b76b2f8..30ad0d656fa 100644 --- a/addons/website/tests/test_snippets.py +++ b/addons/website/tests/test_snippets.py @@ -112,3 +112,6 @@ class TestSnippets(HttpCase): def test_drag_and_drop_on_non_editable(self): self.start_tour(self.env['website'].get_client_action_url('/'), 'test_drag_and_drop_on_non_editable', login='admin') + + def test_snippet_image_gallery_reorder(self): + self.start_tour(self.env['website'].get_client_action_url('/'), "snippet_image_gallery_reorder", login='admin')