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) => { + data.elements.forEach(element => 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 @@ <ul class="nav nav-tabs" role="tablist"> <!-- Nav tabs --> #foreach ($uix in $selectorUIXs) + #set ($tabId = "${uix.parameters.id}-$request.index") <li role="presentation" #if($foreach.index == 0)class="active"#end> - <a href="#$escapetool.url($uix.parameters.id)" - aria-controls="$escapetool.xml($uix.parameters.id)" + <a href="#$escapetool.url($tabId)" + aria-controls="$escapetool.xml($tabId)" role="tab" data-toggle="tab"> $escapetool.xml($services.localization.render($uix.parameters.title)) </a> @@ -66,7 +67,10 @@ <!-- Tab panes --> <div class="tab-content"> #foreach ($uix in $selectorUIXs) - <div role="tabpanel" class="tab-pane#if($foreach.index == 0) active#end" id="$escapetool.url($uix.parameters.id)"> + #set ($tabId = "${uix.parameters.id}-$request.index") + <div role="tabpanel" + class="$escapetool.xml(${uix.parameters.id}) tab-pane#if($foreach.index == 0) active#end" + id="$escapetool.xml($tabId)"> $services.rendering.render($uix.execute(), 'xhtml/1.0') </div> #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 && 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 && !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 && !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) => { + data.elements.forEach(element => 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"}} + <input type='hidden' name='documentReference' value='$escapetool.xml("document:$services.model.serialize($request.documentReference, 'default')")' /> #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) => { + data.elements.forEach(element => 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 > 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 > 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) => { + data.elements.forEach(element => 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) => { + data.elements.forEach(element => init($(element))); }); });</code> </property> -- GitLab