From 7c01f81f609ffe370ef559f5fcb46b278d847e4d Mon Sep 17 00:00:00 2001
From: Manuel Leduc <manuel.leduc@xwiki.com>
Date: Thu, 15 Dec 2022 10:33:12 +0100
Subject: [PATCH] XWIKI-20455: Image dialog from several editor instances
 should be isolated

- Introduce a target parameter on the attachmentGalleryPicker macro
- Create an image selector modal instance per editor
- Use the source document of the current editor instance to resolve and search for the images
---
 ...ttachmentGalleryPickerMacroParameters.java |  28 ++
 .../AttachmentGalleryPickerMacro.java         |   3 +
 .../AttachmentGalleryPickerMacroTest.java     |   9 +
 .../Picker/Code/CKEditorImagePlugin.xml       |  26 +-
 .../main/webjar/attachmentGalleryPicker.js    |  24 +-
 .../resources/xwiki-image/imageSelector.js    | 296 ++++++++++--------
 .../main/resources/xwiki-image/imageWizard.js |   3 +
 .../CKEditor/ImageSelectorService.xml         |  10 +-
 .../ImageSelectorServiceUIX/DocumentTree.xml  |  51 +--
 .../CKEditor/ImageSelectorServiceUIX/Icon.xml |  20 +-
 .../ImageSelectorServiceUIX/Upload.xml        |  73 +++--
 .../CKEditor/ImageSelectorServiceUIX/Url.xml  |  20 +-
 12 files changed, 358 insertions(+), 205 deletions(-)

diff --git a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/main/java/org/xwiki/attachment/picker/AttachmentGalleryPickerMacroParameters.java b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/main/java/org/xwiki/attachment/picker/AttachmentGalleryPickerMacroParameters.java
index 8290f4cd126..b83cdcc40a2 100644
--- a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/main/java/org/xwiki/attachment/picker/AttachmentGalleryPickerMacroParameters.java
+++ b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/main/java/org/xwiki/attachment/picker/AttachmentGalleryPickerMacroParameters.java
@@ -23,7 +23,9 @@
 import java.util.List;
 
 import org.xwiki.attachment.picker.internal.AttachmentGalleryPickerMacro;
+import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.properties.annotation.PropertyDescription;
+import org.xwiki.properties.annotation.PropertyDisplayType;
 import org.xwiki.properties.annotation.PropertyName;
 import org.xwiki.stability.Unstable;
 
@@ -42,6 +44,8 @@ public class AttachmentGalleryPickerMacroParameters
 
     private Integer limit = 20;
 
+    private String targetDocumentReference;
+
     /**
      * @return the id of the attachment picker macro
      */
@@ -95,4 +99,28 @@ public void setLimit(Integer limit)
     {
         this.limit = limit;
     }
+
+    /**
+     * @return the reference to the document that will be set as the current document to display the macro content
+     * @since 14.10.2
+     * @since 15.0RC1
+     */
+    @Unstable
+    public String getTarget()
+    {
+        return this.targetDocumentReference;
+    }
+
+    /**
+     * @param targetDocumentReference refer to {@link #getTarget()}
+     * @since 14.10.2
+     * @since 15.0RC1
+     */
+    @PropertyDescription("The reference to the document serving as the current document")
+    @PropertyDisplayType(DocumentReference.class)
+    @Unstable
+    public void setTarget(String targetDocumentReference)
+    {
+        this.targetDocumentReference = targetDocumentReference;
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/main/java/org/xwiki/attachment/picker/internal/AttachmentGalleryPickerMacro.java b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/main/java/org/xwiki/attachment/picker/internal/AttachmentGalleryPickerMacro.java
index 90c799a3996..bcf7036ed66 100644
--- a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/main/java/org/xwiki/attachment/picker/internal/AttachmentGalleryPickerMacro.java
+++ b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/main/java/org/xwiki/attachment/picker/internal/AttachmentGalleryPickerMacro.java
@@ -110,6 +110,9 @@ public List<Block> execute(AttachmentGalleryPickerMacroParameters parameters, St
         if (parameters.getLimit() != null) {
             attachmentPickerParameters.put("data-xwiki-attachment-picker-limit", String.valueOf(parameters.getLimit()));
         }
+        if (parameters.getTarget() != null) {
+            attachmentPickerParameters.put("data-xwiki-attachment-picker-target", parameters.getTarget());
+        }
 
         return List.of(new GroupBlock(List.of(
             // Search block.
diff --git a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/test/java/org/xwiki/attachment/picker/internal/AttachmentGalleryPickerMacroTest.java b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/test/java/org/xwiki/attachment/picker/internal/AttachmentGalleryPickerMacroTest.java
index b3001a25a0a..c947188dc37 100644
--- a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/test/java/org/xwiki/attachment/picker/internal/AttachmentGalleryPickerMacroTest.java
+++ b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-macro/src/test/java/org/xwiki/attachment/picker/internal/AttachmentGalleryPickerMacroTest.java
@@ -134,4 +134,13 @@ void executeWithFilter()
             this.attachmentGalleryPickerMacro.execute(params, null, this.macroTransformationContext);
         assertEquals("image/*,image/jpeg", actual.get(0).getParameter("data-xwiki-attachment-picker-filter"));
     }
+
+    @Test
+    void executeWithTarget()
+    {
+        AttachmentGalleryPickerMacroParameters params = new AttachmentGalleryPickerMacroParameters();
+        params.setTarget("xwiki:Space.Page");
+        List<Block> actual = this.attachmentGalleryPickerMacro.execute(params, null, this.macroTransformationContext);
+        assertEquals("xwiki:Space.Page", actual.get(0).getParameter("data-xwiki-attachment-picker-target"));
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-ui/src/main/resources/Attachment/Picker/Code/CKEditorImagePlugin.xml b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-ui/src/main/resources/Attachment/Picker/Code/CKEditorImagePlugin.xml
index e1ffb187cb0..33c0465b951 100644
--- a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-ui/src/main/resources/Attachment/Picker/Code/CKEditorImagePlugin.xml
+++ b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-ui/src/main/resources/Attachment/Picker/Code/CKEditorImagePlugin.xml
@@ -125,13 +125,21 @@
     </property>
     <property>
       <code>require(['jquery', 'imageSelector'], function($, imageSelector) {
-   const picker = $('#attachmentPickerCKEditor')
-   picker.on('xwiki:attachmentGalleryPicker:selected', function(_target, attachmentReference) {
-     imageSelector.updateSelectedImageReferences([attachmentReference]);
-   });
+  function init(element) {
+    const picker = element.find('.attachmentPickerCKEditor');
+    picker.on('xwiki:attachmentGalleryPicker:selected', function(_target, attachmentReference) {
+      imageSelector.updateSelectedImageReferences([attachmentReference], $(this));
+    });
 
-  picker.on('xwiki:attachmentGalleryPicker:unselected', function() {
-    imageSelector.updateSelectedImageReferences([]);
+    picker.on('xwiki:attachmentGalleryPicker:unselected', function() {
+      imageSelector.updateSelectedImageReferences([], $(this));
+    });
+  }
+
+  init($(document).find('.image-selector'));
+
+  $(document).on('xwiki:dom:updated', (event, data) =&gt; {
+    data.elements.forEach(element =&gt; init($(element)));
   });
 });</code>
     </property>
@@ -273,7 +281,11 @@
 #set ($discard = $xwiki.jsx.use('Attachment.Picker.Code.CKEditorImagePlugin'))
 {{/velocity}}
 
-{{attachmentGalleryPicker id='attachmentPickerCKEditor' filter="image/*"/}}</content>
+{{velocity}}
+(% class='attachmentPickerCKEditor' %)(((
+  {{attachmentGalleryPicker filter="image/*" target="$services.rendering.escape($request.documentReference, 'xwiki/2.1')"/}}
+)))
+{{/velocity}}</content>
     </property>
     <property>
       <extensionPointId>org.xwiki.contrib.ckeditor.plugins.imageSelector</extensionPointId>
diff --git a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-webjar/src/main/webjar/attachmentGalleryPicker.js b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-webjar/src/main/webjar/attachmentGalleryPicker.js
index e122192570e..bb88a1c8dd7 100644
--- a/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-webjar/src/main/webjar/attachmentGalleryPicker.js
+++ b/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-picker/xwiki-platform-attachment-picker-webjar/src/main/webjar/attachmentGalleryPicker.js
@@ -60,15 +60,17 @@ define('xwiki-attachment-picker',
         this.sugestSolrServiceURL = doc.getURL('get');
         this.options = options || {};
         this.limit = options.limit || 20;
+        this.target = options.target;
         this.solrOptions = this.options.solrOptions || {};
       }
 
       search(query, isGlobal) {
+        const {currentWiki, currentSpace, currentPage} = this.resolveTargetFqs();
         const localDocumentOnly = this.searchSolr(query, Object.assign({}, this.solrOptions, {
           fqs: [
-            `wiki:${XWiki.currentWiki}`,
-            `space:"${XWiki.currentSpace}"`,
-            `name:"${XWiki.currentPage}"`
+            `wiki:${currentWiki}`,
+            `space:"${currentSpace}"`,
+            `name:"${currentPage}"`
           ]
         }));
 
@@ -100,6 +102,21 @@ define('xwiki-attachment-picker',
         });
       }
 
+      resolveTargetFqs() {
+        let currentWiki = XWiki.currentWiki;
+        let currentSpace = XWiki.currentSpace;
+        let currentPage = XWiki.currentPage;
+        if (this.target) {
+          const target = new XWiki.Document(XWiki.Model.resolve(this.target, XWiki.EntityType.DOCUMENT));
+          if (target.wiki) {
+            currentWiki = target.wiki;
+          }
+          currentSpace = target.space;
+          currentPage = target.page;
+        }
+        return {currentWiki, currentSpace, currentPage};
+      }
+
       searchSolr(input, options) {
         options = options || {};
         const optionsFqs = options.fqs || [];
@@ -183,6 +200,7 @@ define('xwiki-attachment-picker',
         this.searchBlock.append(inputGroup);
         this.solrSearch = new SolrSearch({
           limit: parseInt(this.rootBlock.data('xwiki-attachment-picker-limit')),
+          target: this.rootBlock.data('xwiki-attachment-picker-target'),
           solrOptions: {
             filter: this.rootBlock.data('xwiki-attachment-picker-filter')
           }
diff --git a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/resources/xwiki-image/imageSelector.js b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/resources/xwiki-image/imageSelector.js
index fb0e3156abc..43a8535a34b 100644
--- a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/resources/xwiki-image/imageSelector.js
+++ b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/resources/xwiki-image/imageSelector.js
@@ -31,78 +31,90 @@ define('imageSelectorTranslationKeys', [], [
 define('imageSelector', ['jquery', 'modal', 'resource', 'l10n!imageSelector'],
   function($, $modal, resource, translations) {
     'use strict';
+    
+    // Counter of the image selector instances. Each new instantiation increments this counter.
+    var counter = 0;
+    
+    function scopedImageSelector(documentReference) {
+
+      var index = counter;
+      counter = counter + 1;
+      
+      function getEntityReference(referenceStr) {
+        var reference;
+        if (referenceStr.startsWith("attachment:")) {
+          var separatorIndex = referenceStr.indexOf(':');
+          reference = referenceStr.substr(separatorIndex + 1);
+        } else {
+          reference = referenceStr;
+        }
 
-    function getEntityReference(referenceStr) {
-      var reference;
-      if (referenceStr.startsWith("attachment:")) {
-        var separatorIndex = referenceStr.indexOf(':');
-        reference = referenceStr.substr(separatorIndex + 1);
-      } else {
-        reference = referenceStr;
+        return XWiki.Model.resolve(reference, XWiki.EntityType.ATTACHMENT);
       }
 
-      return XWiki.Model.resolve(reference, XWiki.EntityType.ATTACHMENT);
-    }
-
-    function getCurrentTabId() {
-      return $(".image-selector .tab-pane.active").attr('id');
-    }
-
-    // Internal map of the tab ids and their corresponding image references.
-    var mapTabReference = {};
-
-    function setImageReferenceValue(value) {
-      if (value) {
-        modal.data('imageReference', {
-          value: value
-        });
-        $('.image-selector-modal button.btn-primary').prop('disabled', false);
-      } else {
-        modal.data('imageReference', {});
-        $('.image-selector-modal button.btn-primary').prop('disabled', true);
+      function getCurrentTabId() {
+        return modal.find(".image-selector .tab-pane.active").attr('id');
       }
-    }
 
-    /**
-     * Can be called by the image selector tab UIXs. Indicates the list of selected images. Passing an empty array 
-     * indicates that not images are currently selected. The images can either be a string that will be parsed to a 
-     * resource reference, or a reference object (e.g., "{type: 'icon', reference: 'accept'}");
-     * Note: Currently, only the first image of the list is taken into account.
-     *
-     * @param imageReferences the selected image references
-     */
-    function updateSelectedImageReferences(imageReferences) {
-      imageReferences = imageReferences || [];
-      var imageReferenceValue;
-      if(imageReferences.length > 0) {
-        // TODO:  Support the selection of several images (see CKEDITOR-445).
-        var imageReference = imageReferences[0];
-        var value;
-        if (typeof imageReference === 'string') {
-          value = resource.convertEntityReferenceToResourceReference(getEntityReference(imageReference));
+      // Internal map of the tab ids and their corresponding image references.
+      var mapTabReference = {};
+
+      function setImageReferenceValue(value) {
+        if (value) {
+          modal.data('imageReference', {
+            value: value
+          });
+          $('.image-selector-modal button.btn-primary').prop('disabled', false);
         } else {
-          value = imageReference;
-          value.typed = true;
+          modal.data('imageReference', {});
+          $('.image-selector-modal button.btn-primary').prop('disabled', true);
         }
-        imageReferenceValue = value;
       }
 
-      // Save the value in a map to be able to retrieve it later in case of tab change.
-      mapTabReference[getCurrentTabId()] = imageReferenceValue;
+      /**
+       * Can be called by the image selector tab UIXs. Indicates the list of selected images. Passing an empty array
+       * indicates that not images are currently selected. The images can either be a string that will be parsed to a
+       * resource reference, or a reference object (e.g., "{type: 'icon', reference: 'accept'}");
+       * Note: Currently, only the first image of the list is taken into account.
+       *
+       * @param imageReferences the selected image references
+       */
+      function updateSelectedImageReferences(imageReferences) {
+        imageReferences = imageReferences || [];
+        var imageReferenceValue;
+        if (imageReferences.length > 0) {
+          // TODO:  Support the selection of several images (see CKEDITOR-445).
+          var imageReference = imageReferences[0];
+          var value;
+          if (typeof imageReference === 'string') {
+            value = resource.convertEntityReferenceToResourceReference(getEntityReference(imageReference));
+          } else {
+            value = imageReference;
+            value.typed = true;
+          }
+          imageReferenceValue = value;
+        }
 
-      setImageReferenceValue(imageReferenceValue);
-    }
+        // Save the value in a map to be able to retrieve it later in case of tab change.
+        mapTabReference[getCurrentTabId()] = imageReferenceValue;
 
-    function initialize(modal) {
-      if (!modal.data('initialized')) {
-        var url = new XWiki.Document(XWiki.Model.resolve('CKEditor.ImageSelectorService', XWiki.EntityType.DOCUMENT))
-          .getURL('get');
-        $.get(url, $.param({language: $('html').attr('lang')}))
-          .done(function(html, textState, jqXHR) {
-            var imageSelector = $('.image-selector');
+        setImageReferenceValue(imageReferenceValue);
+      }
+
+      function initialize(modal) {
+        if (!modal.data('initialized')) {
+          var url = new XWiki.Document(XWiki.Model.resolve('CKEditor.ImageSelectorService', XWiki.EntityType.DOCUMENT))
+            .getURL('get');
+          $.get(url, $.param({
+            language: $('html').attr('lang'),
+            index: index,
+            documentReference: documentReference
+          })).done(function (html, textState, jqXHR) {
+            var imageSelector = modal.find('.image-selector');
             var requiredSkinExtensions = jqXHR.getResponseHeader('X-XWIKI-HTML-HEAD');
             $(document).loadRequiredSkinExtensions(requiredSkinExtensions);
             imageSelector.html(html);
+            $(document).trigger('xwiki:dom:updated', {'elements': imageSelector.toArray()});
             imageSelector.removeClass('loading');
 
             // Update the selection with the value of the current tab on tab change.
@@ -111,83 +123,113 @@ define('imageSelector', ['jquery', 'modal', 'resource', 'l10n!imageSelector'],
             });
 
             modal.data('initialized', true);
-          }).fail(function(error) {
-          console.log('Failed to retrieve the image selection form.', error);
-          new XWiki.widgets.Notification(translations.get('modal.initialization.fail'), 'error');
-          modal.data('initialized', true);
-        });
+          }).fail(function (error) {
+            console.log('Failed to retrieve the image selection form.', error);
+            new XWiki.widgets.Notification(translations.get('modal.initialization.fail'), 'error');
+            modal.data('initialized', true);
+          });
+        }
       }
-    }
 
-    // Defined once the modal is initialized.
-    var modal;
-
-    // Initialize the modal.
-    var createModal = $modal.createModalStep({
-      'class': 'image-selector-modal',
-      title: translations.get('modal.title'),
-      acceptLabel: translations.get('modal.selectButton'),
-      content: '<div class="image-selector loading"></div>',
-      onLoad: function() {
-        modal = this;
-        var selectButton = modal.find('.modal-footer .btn-primary');
-        // Make the modal larger.
-        modal.find('.modal-dialog').addClass('modal-lg');
-
-        modal.on('shown.bs.modal', function() {
-          initialize(modal);
+      // Defined once the modal is initialized.
+      var modal;
+
+      // Initialize the modal.
+      var createModal = $modal.createModalStep({
+        'class': 'image-selector-modal',
+        title: translations.get('modal.title'),
+        acceptLabel: translations.get('modal.selectButton'),
+        content: '<div class="image-selector loading"></div>',
+        onLoad: function () {
+          modal = this;
+          var selectButton = modal.find('.modal-footer .btn-primary');
+          // Make the modal larger.
+          modal.find('.modal-dialog').addClass('modal-lg');
+
+          modal.on('shown.bs.modal', function () {
+            initialize(modal);
+          });
+          selectButton.on('click', function () {
+            var imageData = modal.data('input').imageData || {};
+            imageData.resourceReference = modal.data('imageReference').value;
+            var output = {
+              imageData: imageData,
+              editor: modal.data('input').editor,
+              newImage: modal.data('input').newImage
+            };
+            modal.data('output', output).modal('hide');
+          });
+        }
+      });
+
+      /**
+       * Initialize a loader for the provided upload field. Three optional callbacks can be provided in the options
+       * object:
+       * - onSuccess is called when the upload is successful, the entity reference is passed as argument.
+       * - onError is called when the upload fails
+       * - onAbort is called when the upload is aborted
+       * @param uploadField the upload field to create the loader for
+       * @param options an option object with callback actions
+       */
+      function createLoader(uploadField, options) {
+        options = options || {};
+        var editor = modal.data('input').editor;
+        var loader = editor.uploadRepository.create(uploadField);
+        loader.on('uploaded', function (evt) {
+          var resourceReference = evt.sender.responseData.message.resourceReference;
+          var entityReference = resource.convertResourceReferenceToEntityReference(resourceReference);
+          if (options.onSuccess) {
+            options.onSuccess(entityReference);
+          }
+        });
+
+        loader.on('error', function (error) {
+          console.log('Failed to upload a file', error);
+          if (options.onError) {
+            options.onError();
+          }
         });
-        selectButton.on('click', function() {
-          var imageData = modal.data('input').imageData || {};
-          imageData.resourceReference = modal.data('imageReference').value;
-          var output = {
-            imageData: imageData,
-            editor: modal.data('input').editor,
-            newImage: modal.data('input').newImage
-          };
-          modal.data('output', output).modal('hide');
+        loader.on('abort', function (error) {
+          console.log('Failed to upload a file', error);
+          if (options.onAbort) {
+            options.onAbort();
+          }
         });
+
+        loader.loadAndUpload(editor.config.filebrowserUploadUrl);
       }
-    });
-
-
-    /**
-     * Initialize a loader for the provided upload field. Three optional callbacks can be provided in the options 
-     * object:
-     * - onSuccess is called when the upload is successful, the entity reference is passed as argument.
-     * - onError is called when the upload fails
-     * - onAbort is called when the upload is aborted
-     * @param uploadField the upload field to create the loader for
-     * @param options an option object with callback actions
-     */
-    function createLoader(uploadField, options) {
-      options = options || {};
-      var editor = modal.data('input').editor;
-      var loader = editor.uploadRepository.create(uploadField);
-      loader.on('uploaded', function(evt) {
-        var resourceReference = evt.sender.responseData.message.resourceReference;
-        var entityReference = resource.convertResourceReferenceToEntityReference(resourceReference);
-        if (options.onSuccess) {
-          options.onSuccess(entityReference);
-        }
-      });
 
-      loader.on('error', function(error) {
-        console.log('Failed to upload a file', error);
-        if (options.onError) {
-          options.onError();
-        }
-      });
-      loader.on('abort', function(error) {
-        console.log('Failed to upload a file', error);
-        if (options.onAbort) {
-          options.onAbort();
-        }
-      });
+      return {
+        open: createModal,
+        updateSelectedImageReferences: updateSelectedImageReferences,
+        createLoader: createLoader
+      };
+    }
 
-      loader.loadAndUpload(editor.config.filebrowserUploadUrl);
+    function getDocumentReference(params) {
+      return XWiki.Model.serialize(params.editor.config.sourceDocument.documentReference);
     }
-    
+
+    var mapScopes = {};
+
+    function createModal(params) {
+      var documentReference = getDocumentReference(params);
+      if (!mapScopes[documentReference]) {
+        mapScopes[documentReference] = scopedImageSelector(documentReference);
+      }
+      return mapScopes[documentReference].open(params);
+    }
+
+    function updateSelectedImageReferences(imageReferences, element) {
+      var documentReference = getDocumentReference(element.parents('.image-selector-modal').data('input'));
+      mapScopes[documentReference].updateSelectedImageReferences(imageReferences);
+    }
+
+    function createLoader(uploadField, options, element) {
+      var documentReference = getDocumentReference(element.parents('.image-selector-modal').data('input'));
+      mapScopes[documentReference].createLoader(uploadField, options);
+    }
+
     return {
       open: createModal,
       updateSelectedImageReferences: updateSelectedImageReferences,
diff --git a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/resources/xwiki-image/imageWizard.js b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/resources/xwiki-image/imageWizard.js
index f802e23897a..9edcd781969 100644
--- a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/resources/xwiki-image/imageWizard.js
+++ b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/resources/xwiki-image/imageWizard.js
@@ -43,6 +43,9 @@ define('imageWizard', ['imageSelector', 'imageEditor'], function(imageSelector,
   }
 
   return function(params) {
+    if (CKEDITOR.currentInstance) {
+      params.currentDocument = CKEDITOR.currentInstance.config.sourceDocument.documentReference;
+    }
     if (params.isInsert === false) {
       return editOnly(params);
     } else {
diff --git a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorService.xml b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorService.xml
index 5b03c0d6447..0f41451f036 100644
--- a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorService.xml
+++ b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorService.xml
@@ -53,9 +53,10 @@
   &lt;ul class="nav nav-tabs" role="tablist"&gt;
     &lt;!-- Nav tabs --&gt;
     #foreach ($uix in $selectorUIXs)
+      #set ($tabId = "${uix.parameters.id}-$request.index")
       &lt;li role="presentation" #if($foreach.index == 0)class="active"#end&gt;
-        &lt;a href="#$escapetool.url($uix.parameters.id)"
-          aria-controls="$escapetool.xml($uix.parameters.id)"
+        &lt;a href="#$escapetool.url($tabId)"
+          aria-controls="$escapetool.xml($tabId)"
           role="tab" data-toggle="tab"&gt;
           $escapetool.xml($services.localization.render($uix.parameters.title))
         &lt;/a&gt;
@@ -66,7 +67,10 @@
   &lt;!-- Tab panes --&gt;
   &lt;div class="tab-content"&gt;
     #foreach ($uix in $selectorUIXs)
-      &lt;div role="tabpanel" class="tab-pane#if($foreach.index == 0) active#end" id="$escapetool.url($uix.parameters.id)"&gt;
+      #set ($tabId = "${uix.parameters.id}-$request.index")
+      &lt;div role="tabpanel" 
+        class="$escapetool.xml(${uix.parameters.id}) tab-pane#if($foreach.index == 0) active#end" 
+        id="$escapetool.xml($tabId)"&gt;
       $services.rendering.render($uix.execute(), 'xhtml/1.0')
       &lt;/div&gt;
     #end
diff --git a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/DocumentTree.xml b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/DocumentTree.xml
index acd37985d03..da46f6eeb9d 100644
--- a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/DocumentTree.xml
+++ b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/DocumentTree.xml
@@ -129,26 +129,35 @@
     return selected &amp;&amp; selected.startsWith("attachment:");
   }
 
-  $('.attachments-tree').xtree()
-    .one('ready.jstree', function (event, data) {
-      data.instance.openTo("document:" + XWiki.Model.serialize(XWiki.currentDocument.getDocumentReference()));
-    })
-    .on('changed.jstree', function (event, data) {
-      if (validateSelection(data.instance)) {
-        imageSelector.updateSelectedImageReferences([getSelected(data.instance)]);
-      } else {
-        imageSelector.updateSelectedImageReferences([]);
-      }
-    })
-    .on('load_node.jstree', function (node, status) {
-      for (var child of status.node.children) {
-        var childNode = status.instance.get_node(child);
-        if (childNode.data.mimetype &amp;&amp; !childNode.data.mimetype.startsWith('image/')) {
-          // Disable the nodes instead of hiding them because they still can be search in the search field.
-          status.instance.disable_node(childNode);
+  function init(element) {
+    element.find('.attachments-tree').xtree()
+      .one('ready.jstree', function (event, data) {
+        const documentReference = element.find('input[name="documentReference"]').val()
+        data.instance.openTo(documentReference);
+      })
+      .on('changed.jstree', function (event, data) {
+        if (validateSelection(data.instance)) {
+          imageSelector.updateSelectedImageReferences([getSelected(data.instance)], $(this));
+        } else {
+          imageSelector.updateSelectedImageReferences([], $(this));
         }
-      }
-    });
+      })
+      .on('load_node.jstree', function (node, status) {
+        for (var child of status.node.children) {
+          var childNode = status.instance.get_node(child);
+          if (childNode.data.mimetype &amp;&amp; !childNode.data.mimetype.startsWith('image/')) {
+            // Disable the nodes instead of hiding them because they still can be search in the search field.
+            status.instance.disable_node(childNode);
+          }
+        }
+      });
+  }
+
+  init($(document).find('.image-selector'));
+
+  $(document).on('xwiki:dom:updated', (event, data) =&gt; {
+    data.elements.forEach(element =&gt; init($(element)));
+  });
 });</code>
     </property>
     <property>
@@ -236,12 +245,12 @@
     <property>
       <content>{{velocity}}
 #set ($discard = $xwiki.jsx.use('CKEditor.ImageSelectorServiceUIX.DocumentTree'))
-{{html clean="false"}}
+{{html clean="false" wiki="false"}}
+  &lt;input type='hidden' name='documentReference' value='$escapetool.xml("document:$services.model.serialize($request.documentReference, 'default')")' /&gt;
   #template('documentTree_macros.vm')
   #documentTree({
     'class': 'attachments-tree',
     'finder': true,
-    'openTo': "document:$services.model.serialize($doc.documentReference, 'default')",
     'showWikis': true,
     'showTranslations': false
   })
diff --git a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Icon.xml b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Icon.xml
index abe897b14a4..466ab5c6f41 100644
--- a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Icon.xml
+++ b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Icon.xml
@@ -120,12 +120,20 @@
     </property>
     <property>
       <code>require(['jquery', 'imageSelector'], function($, imageSelector) {
-  $('#iconTab input[type="text"]').on('input', function () {
-    if ($(this).val() === '') {
-      imageSelector.updateSelectedImageReferences([]);
-    } else {
-      imageSelector.updateSelectedImageReferences([{type: 'icon', reference: $(this).val()}]);
-    }
+  function init(element) {
+    element.find('.iconTab input[type="text"]').on('input', function () {
+      if ($(this).val() === '') {
+        imageSelector.updateSelectedImageReferences([], $(this));
+      } else {
+        imageSelector.updateSelectedImageReferences([{type: 'icon', reference: $(this).val()}], $(this));
+      }
+    });
+  }
+
+  init($(document).find('.image-selector'));
+
+  $(document).on('xwiki:dom:updated', (event, data) =&gt; {
+    data.elements.forEach(element =&gt; init($(element)));
   });
 });</code>
     </property>
diff --git a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Upload.xml b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Upload.xml
index 2bfb56fd21f..44ca367adaa 100644
--- a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Upload.xml
+++ b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Upload.xml
@@ -124,48 +124,57 @@
       <cache>long</cache>
     </property>
     <property>
-      <code>require(['jquery', 'imageSelector', 'l10n!imageSelector', 'resource'], function($, imageSelector, translations, resource) {
-  const imageSelectorElement = $('.image-selector');
-  const uploadButton = $("#upload form input.button");
+      <code>require(['jquery', 'imageSelector', 'l10n!imageSelector', 'resource'],
+  function ($, imageSelector, translations, resource) {
+    function init(element) {
+      const uploadButton = element.find(".upload form input.button");
 
-  uploadButton.prop("disabled", true);
-
-  imageSelectorElement.on('change', function() {
-    if($("#fileUploadField").prop('files').length &gt; 0) {
-      uploadButton.prop("disabled", false);
-    } else {
       uploadButton.prop("disabled", true);
-    }
-  });
 
-  uploadButton.on('click', function(event) {
-    event.preventDefault();
+      element.on('change', function () {
+        if ($(this).find("input[type='file']").prop('files').length &gt; 0) {
+          uploadButton.prop("disabled", false);
+        } else {
+          uploadButton.prop("disabled", true);
+        }
+      });
+
+      uploadButton.on('click', function (event) {
+        event.preventDefault();
 
-    const uploadedFile = $("#fileUploadField").prop('files')[0];
+        const uploadedFile = element.find("input[type='file']").prop('files')[0];
 
-    const beforeUploadEvent = $.Event("xwiki:actions:beforeUpload");
-    $(document).trigger(beforeUploadEvent, {file: uploadedFile});
+        const beforeUploadEvent = $.Event("xwiki:actions:beforeUpload");
+        $(document).trigger(beforeUploadEvent, {file: uploadedFile});
 
-    if (!beforeUploadEvent.isDefaultPrevented()) {
-      imageSelectorElement.addClass('loading');
-      imageSelector.createLoader(uploadedFile, {
-        onSuccess: function(entityReference) {
-          imageSelector.updateSelectedImageReferences([XWiki.Model.serialize(entityReference)]);
-          imageSelectorElement.removeClass('loading');
-          new XWiki.widgets.Notification(translations.get('modal.fileUpload.success'), 'done');
-        },
-        onError: function() {
-          new XWiki.widgets.Notification(translations.get('modal.fileUpload.fail'), 'error');
-          imageSelectorElement.removeClass('loading');
-        },
-        onAbort: function() {
-          new XWiki.widgets.Notification(translations.get('modal.fileUpload.abort'), 'error');
-          imageSelectorElement.removeClass('loading');
+        if (!beforeUploadEvent.isDefaultPrevented()) {
+          element.addClass('loading');
+          imageSelector.createLoader(uploadedFile, {
+            onSuccess: function (entityReference) {
+              imageSelector.updateSelectedImageReferences([XWiki.Model.serialize(entityReference)],
+                element);
+              element.removeClass('loading');
+              new XWiki.widgets.Notification(translations.get('modal.fileUpload.success'), 'done');
+            },
+            onError: function () {
+              new XWiki.widgets.Notification(translations.get('modal.fileUpload.fail'), 'error');
+              element.removeClass('loading');
+            },
+            onAbort: function () {
+              new XWiki.widgets.Notification(translations.get('modal.fileUpload.abort'), 'error');
+              element.removeClass('loading');
+            }
+          }, element);
         }
       });
     }
+
+    init($(document).find('.image-selector'));
+
+    $(document).on('xwiki:dom:updated', (event, data) =&gt; {
+      data.elements.forEach(element =&gt; init($(element)));
+    });
   });
-});
 </code>
     </property>
     <property>
diff --git a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Url.xml b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Url.xml
index 7235bd51e65..974147a6fa0 100644
--- a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Url.xml
+++ b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-ui/src/main/resources/CKEditor/ImageSelectorServiceUIX/Url.xml
@@ -120,12 +120,20 @@
     </property>
     <property>
       <code>require(['jquery', 'imageSelector'], function($, imageSelector) {
-  $('#urlTab input[type="text"]').on('input', function () {
-    if ($(this).val() === '') {
-      imageSelector.updateSelectedImageReferences([]);
-    } else {
-      imageSelector.updateSelectedImageReferences([{type: 'url', reference: $(this).val()}]);
-    }
+  function init(element) {
+    element.find('.urlTab input[type="text"]').on('input', function () {
+      if ($(this).val() === '') {
+        imageSelector.updateSelectedImageReferences([], $(this));
+      } else {
+        imageSelector.updateSelectedImageReferences([{type: 'url', reference: $(this).val()}], $(this));
+      }
+    });
+  }
+
+  init($(document).find('.image-selector'));
+
+  $(document).on('xwiki:dom:updated', (event, data) =&gt; {
+    data.elements.forEach(element =&gt; init($(element)));
   });
 });</code>
     </property>
-- 
GitLab