Änderungen von Dokument Attachments
Zuletzt geändert von xwikiadmin am 2026/03/05 08:08
Von Version 1.1
bearbeitet von xwikiadmin
am 2023/03/07 16:26
am 2023/03/07 16:26
Änderungskommentar:
Install extension [com.xwiki.pro:xwiki-pro-macros/1.7]
Auf Version 4.1
bearbeitet von xwikiadmin
am 2023/10/26 09:48
am 2023/10/26 09:48
Änderungskommentar:
Install extension [com.xwiki.pro:xwiki-pro-macros/1.12]
Zusammenfassung
-
Seiteneigenschaften (1 geändert, 0 hinzugefügt, 0 gelöscht)
-
Objekte (3 geändert, 1 hinzugefügt, 0 gelöscht)
Details
- Seiteneigenschaften
-
- Inhalt
-
... ... @@ -10,17 +10,17 @@ 10 10 = Parameters = 11 11 12 12 |=Parameter|=Description|=Required|=Default 13 -|**patterns**|Comma-separated list of regular expressions, used to filter attachments by name.|No| 13 +|**patterns**|Comma-separated list of regular expressions, used to filter attachments by name.|No| 14 14 |**sortBy**|Sort attachments by {{{ date }}}, {{{ size }}} or {{{ name }}}|No|{{{ date }}} 15 15 |**sortOrder**|Sort attachments in {{{ ascending }}} or {{{ descending }}} order|No|{{{ ascending }}} 16 16 |**upload**|Allow users to attach new files|No|{{{ true }}} 17 -|**page**|Pages containing the attachments to display. Current page if empty.|No| 17 +|**page**|Pages containing the attachments to display. Current page if empty.|No| 18 18 19 19 = Example Usage = 20 20 21 21 22 22 {{code}} 23 -{{attachments 23 +{{confluence_attachments 24 24 patterns=".*png" 25 25 sortBy="name" 26 26 /}}
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -1,8 +1,95 @@ 1 -require(['jquery', 'xwiki-events-bridge'], function ($) { 2 - $('#xwikiuploadfile').on('xwiki:html5upload:message', function (event, data) { 3 - if (data.content === 'UPLOAD_FINISHED' && data.type === 'done') { 4 - location.reload(); 1 +define('xwiki-confluence-attachments-messages', { 2 + prefix: 'core.viewers.attachments.delete.', 3 + keys: [ 4 + 'inProgress', 5 + 'done', 6 + 'error' 7 + ] 8 +}); 9 + 10 +require(['jquery', 'xwiki-l10n!xwiki-confluence-attachments-messages'], function($, l10n) { 11 + var enhanceUploadInputs = function(liveDataElems) { 12 + $.each(liveDataElems.find('input[type=file]'), function() { 13 + // Since the attachments liveData is refreshed on file upload, there is no need for a response container. 14 + new XWiki.FileUploader(this, { 15 + 'progressAutohide': true, 16 + 'responseContainer' : document.createElement('div'), 17 + 'maxFilesize' : parseInt(this.readAttribute('data-max-file-size')) 18 + }); 19 + }) 20 + }; 21 + 22 + $(function() { 23 + enhanceUploadInputs($('.confluenceAttachmentsMacro')); 24 + }) 25 + 26 + $(document).on('xwiki:dom:updated', function(e, data) { 27 + let liveDataElems = $(data.elements).find('.confluenceAttachmentsMacro'); 28 + enhanceUploadInputs(liveDataElems); 29 + }); 30 + 31 + $(document).on('xwiki:html5upload:done', function(e) { 32 + if ($(e.target).prop('id').startsWith('confluenceAttachments')) { 33 + // Select the livedata above the upload form. 34 + const uploadForm = $(e.target).closest('form'); 35 + // The 'xwiki-livedata' CSS class is present only before XWiki version 14.4. 36 + let associatedLivedata = uploadForm.prevAll('.liveData, .xwiki-livedata').first(); 37 + // We do not have access to a liveData object before XWiki 14.4. 38 + if (associatedLivedata.data('liveData') === undefined) { 39 + location.reload(); 40 + } else { 41 + associatedLivedata.data('liveData').updateEntries(); 42 + } 5 5 } 6 6 }); 45 + 46 + // 47 + // The delete action methods are similar to the ones from the attachments tab, but couldn't be reused. The problem is 48 + // that when the tab is not opened we cannot just load the attachments.js file, since it expects some elements to be 49 + // already loaded and it produces errors. 50 + // 51 + 52 + /** 53 + * Open the delete confirmation modal on liveData attachment delete button. 54 + */ 55 + $(document).on('click', '.confluenceAttachmentsMacro .attachmentActions .actiondelete', function(e) { 56 + e.preventDefault(); 57 + // The 'xwiki-livedata' CSS class is present only before XWiki version 14.4. 58 + let liveData = $(e.currentTarget).closest('.liveData, .xwiki-livedata'); 59 + var modal = liveData.nextAll('.deleteConfluenceAttachment'); 60 + modal.data('relatedTarget', e.currentTarget); 61 + modal.modal('show'); 62 + }); 63 + 64 + /** 65 + * On delete confirmation, delete attachment and refresh liveData. 66 + */ 67 + $(document).on('click', '.confluenceAttachmentsMacro .deleteConfluenceAttachment input.btn-danger', function(e) { 68 + e.preventDefault(); 69 + var modal = $(e.currentTarget).closest('.deleteConfluenceAttachment'); 70 + var button = $(modal.data('relatedTarget')); 71 + // The 'xwiki-livedata' CSS class is present only before XWiki version 14.4. 72 + let liveData = button.closest('.liveData, .xwiki-livedata'); 73 + var notification; 74 + 75 + $.ajax({ 76 + url : button.prop('href'), 77 + beforeSend : function() { 78 + notification = new XWiki.widgets.Notification(l10n['inProgress'], 'inprogress'); 79 + }, 80 + success : function() { 81 + // We do not have access to a liveData object before XWiki 14.4. 82 + if (liveData.data('liveData') !== undefined) { 83 + liveData.data('liveData').updateEntries(); 84 + notification.replace(new XWiki.widgets.Notification(l10n['done'], 'done')); 85 + } else { 86 + location.reload(); 87 + } 88 + }, 89 + error: function() { 90 + notification.replace(new XWiki.widgets.Notification(l10n['failed'], 'error')); 91 + } 92 + }); 93 + }); 7 7 }); 8 8 - Name
-
... ... @@ -1,0 +1,1 @@ 1 +Confluence Attachments Macro
- XWiki.StyleSheetExtension[0]
-
- Code
-
... ... @@ -1,15 +1,15 @@ 1 -.confluence-attachment-macro { 2 - .upload-response { 3 - display: none; 4 - } 5 - 6 - #AddAttachment { 7 - margin-top: 15px; 8 - 9 - #attachform { 10 - .buttonwrapper { 11 - display: none; 12 - } 13 - } 14 - } 1 +.confluenceAttachmentsMacro legend { 2 + font-size: 1em; 15 15 } 4 + /* These should be deleted after upgrading to a XWiki parent >= 14.6. */ 5 +.attachmentMimeType { 6 + color: $theme.textSecondaryColor; 7 + font-size: 32px; 8 + height: 32px; 9 + line-height: 32px; 10 + text-align: center; 11 +} 12 +.attachmentMimeType img { 13 + max-height: 32px; 14 + max-width: 48px; 15 +} - Content Type
-
... ... @@ -1,1 +1,1 @@ 1 - LESS1 +CSS - Name
-
... ... @@ -1,0 +1,1 @@ 1 +Confluence Attachments Macro
- XWiki.WikiMacroClass[0]
-
- Makro-Code
-
... ... @@ -1,169 +1,223 @@ 1 1 {{velocity output="false"}} 2 -$xwiki.ssx.use('Confluence.Macros.Attachments') 3 -$xwiki.jsx.use('Confluence.Macros.Attachments') 4 -#if ("$!{request.forceTestRights}" == "1")#template("xwikivars.vm")#end 5 -#template('attachment_macros.vm') 6 -## Get attachments 7 -#if ("$!wikimacro.parameters.tags" != '') 8 - #set ($tags = $!wikimacro.parameters.tags.split(',')) 9 - #set ($pageReferenceSet = $collectiontool.getSet()) 10 - #foreach ($tag in $tags) 11 - #set ($references = $xwiki.tag.getDocumentsWithTag("$tag")) 12 - #set ($discard = $pageReferenceSet.addAll($references)) 2 +#macro (getLiveDataSort $return) 3 + ##property 4 + #set ($confluenceSortBy = "$!wikimacro.parameters.sortBy") 5 + #set ($invalidSortBy = false) 6 + #if ($confluenceSortBy == 'date') 7 + #set ($sortBy = 'date') 8 + #elseif ($confluenceSortBy == 'size') 9 + #set ($sortBy = 'filesize') 10 + #elseif ($confluenceSortBy == 'name') 11 + #set ($sortBy = 'filename') 12 + #elseif ($confluenceSortBy == 'creation date') 13 + #set ($sortBy = 'date') 14 + #else 15 + #set ($invalidSortBy = true) 13 13 #end 14 - #set ($attachments = []) 15 - #foreach ($pageReference in $pageReferenceSet) 16 - #set ($document = $xwiki.getDocument($pageReference)) 17 - #set ($documentAttachments = $document.attachmentList) 18 - #set ($discard = $attachments.addAll($documentAttachments)) 17 + ## order 18 + #set ($confluenceSortOrder = "$!wikimacro.parameters.sortOrder") 19 + #set ($invalidSortOrder = false) 20 + #set ($reverseSortOrderProperties = ['filesize', 'date']) 21 + #if ($confluenceSortOrder == 'ascending') 22 + #if ($reverseSortOrderProperties.contains($sortBy)) 23 + #set ($sortOrder = 'desc') 24 + #else 25 + #set ($sortOrder = 'asc') 26 + #end 27 + #elseif ($confluenceSortOrder == 'descending') 28 + #if ($reverseSortOrderProperties.contains($sortBy)) 29 + #set ($sortOrder = 'asc') 30 + #else 31 + #set ($sortOrder = 'desc') 32 + #end 33 + #else 34 + #set ($invalidSortOrder = true) 19 19 #end 20 -#e lse21 - # set($attachments=$doc.attachmentList)22 - #i f("$!wikimacro.parameters.page"!= '')23 - #set ($document = $xwiki.getDocument("$!wikimacro.parameters.page"))24 - #set ($attachments=$document.attachmentList)36 + ## return 37 + #if ($invalidSortBy || $invalidSortOrder) 38 + #setVariable("$return", {}) 39 + #else 40 + #setVariable("$return", "$sortBy:$sortOrder") 25 25 #end 26 26 #end 27 -## Filter attachments 28 -#set ($confluencePatterns = "$!wikimacro.parameters.patterns") 29 -#if ($confluencePatterns == '') 30 - #set($attachmentsFiltered = $attachments) 31 -#else 32 - #set ($patterns = $!confluencePatterns.split(',')) 33 - #set($attachmentsFiltered = []) 34 - #foreach ($attachment in $attachments) 35 - #foreach ($pattern in $patterns) 36 - #set ($matches = $attachment.filename.matches("(?i)$!pattern")) 37 - #if ($matches) 38 - #set ($discard = $attachmentsFiltered.add($attachment)) 39 - #break 40 - #end 41 - #end 43 + 44 +#macro (deleteConfluenceAttachmentModal) 45 + ## Copied from the attachments tab code. The original macro cannot be used, since it will interfere with some js 46 + ## specific to attachments tab. Precisely, if the attachments tab is already opened, everything is working correctly. 47 + ## If it is not opened, we need to load the attachments.js code anyway, to use it's listeners, but there are parts 48 + ## that rightfully assume that some elements are present on the page and errors will occur. 49 + <div class="modal fade deleteConfluenceAttachment" tabindex="-1" role="dialog"> 50 + <div class="modal-dialog"> 51 + <div class="modal-content"> 52 + <div class="modal-header"> 53 + <button type="button" class="close" data-dismiss="modal">×</button> 54 + <div class="modal-title">$services.localization.render('core.viewers.attachments.delete')</div> 55 + </div> 56 + <div class="modal-body"> 57 + <div>$services.localization.render('core.viewers.attachments.delete.confirm')</div> 58 + </div> 59 + <div class="modal-footer"> 60 + <input type="button" class="btn btn-danger" data-dismiss="modal" 61 + value="$escapetool.xml($services.localization.render('core.viewers.attachments.delete'))"> 62 + <input type="button" class="btn btn-default" data-dismiss="modal" 63 + value="$escapetool.xml($services.localization.render('cancel'))"> 64 + </div> 65 + </div> 66 + </div> 67 + </div> 68 +#end 69 + 70 +#set ($confluenceAttachmentMacroIndex = -1) 71 +## Code taken and adapted 72 +#macro (showConfluenceAttachments $document) 73 + #template('display_macros.vm') 74 + #set ($confluenceAttachmentMacroIndex = $confluenceAttachmentMacroIndex + 1) 75 + #set ($confluenceAttachmentMacroId = "confluenceAttachments$confluenceAttachmentMacroIndex") 76 + ## 77 + #showConfluenceAttachmentsLiveData($document $confluenceAttachmentMacroId) 78 +#end 79 + 80 +#macro (uploadFileForm $liveDataId) 81 + #set ($upload = "$!wikimacro.parameters.upload") 82 + #if ($upload == 'true' && ($hasEdit || $hasAdmin) && $xcontext.action == 'view') 83 + <form action="$attachmentsDoc.getURL("upload")" enctype="multipart/form-data" method="post"> 84 + <div> 85 + ## CSRF prevention 86 + <input type="hidden" name="form_token" value="$!{services.csrf.getToken()}" /> 87 + <fieldset> 88 + <legend>$services.localization.render('promacros.attachments.upload.title')</legend> 89 + <div> 90 + #set ($inputID = "${liveDataId}-uploadFile") 91 + <label class="sr-only" for="${inputID}"> 92 + $services.localization.render('core.viewers.attachments.upload.file') 93 + </label> 94 + <input id="${inputID}" type="file" name="filepath" size="40" class="noitems" 95 + data-max-file-size="$!escapetool.xml($xwiki.getSpacePreference('upload_maxsize'))"/> 96 + </div> 97 + </fieldset> 98 + </div> 99 + </form> 42 42 #end 43 43 #end 44 -## Sort attachments 45 -#set ($confluenceSortBy = "$!wikimacro.parameters.sortBy") 46 -#set ($invalidSortBy = false) 47 -#if ($confluenceSortBy == 'date') 48 - #set ($sortBy = 'date') 49 -#elseif ($confluenceSortBy == 'size') 50 - #set ($sortBy = 'filesize') 51 -#elseif ($confluenceSortBy == 'name') 52 - #set ($sortBy = 'filename') 53 -#elseif ($confluenceSortBy == 'creation date') 54 - #set ($sortBy = 'date') 55 -#else 56 - #set ($invalidSortBy = true) 102 + 103 +#macro (supportsAttachJSON $supportsAttachJSON) 104 + ## The attachments.json code uses macros from attachment_macro.vm, so the existence of the attachments livedata macro 105 + ## is an indicator that the template exists and is adapted for livedata. 106 + #template('attachment_macros.vm') 107 + #set ($macroResult = "#showAttachmentsLiveData($doc 'id')") 108 + #set ($supportsAttachJSON = $macroResult.startsWith('{{liveData')) 57 57 #end 58 -#set ($confluenceSortOrder = "$!wikimacro.parameters.sortOrder") 59 -#set ($invalidSortOrder = false) 60 -#set ($reverseSortOrderProperties = ['filesize', 'date']) 61 -#if ($confluenceSortOrder == 'ascending') 62 - #if ($reverseSortOrderProperties.contains($sortBy)) 63 - #set ($sortOrder = 'desc') 64 - #else 65 - #set ($sortOrder = 'asc') 110 + 111 +## Display a liveData with attachments from the specified document. 112 +#macro (showConfluenceAttachmentsLiveData $attachmentsDoc $liveDataId) 113 + #set ($liveDataConfig = { 114 + 'meta': { 115 + 'propertyDescriptors': [ 116 + { 'id': 'mimeType', 'displayer': 'html'}, 117 + { 'id': 'filename', 'displayer': 'html' }, 118 + { 'id': 'filesize', 'displayer': 'html' }, 119 + { 'id': 'date', 'filter': 'date'}, 120 + { 'id': 'author', 'displayer': 'html' }, 121 + { 'id': 'actions', 'displayer': 'html' } 122 + ], 123 + 'entryDescriptor': { 124 + 'idProperty': 'id' 125 + } 126 + } 127 + }) 128 + 129 + #if ("$!wikimacro.parameters.patterns" != '') 130 + #set ($liveDataConfig.query = {}) 131 + #set ($liveDataConfig.query.filters = [ 132 + { 133 + "property": "filename", 134 + "matchAll": true, 135 + "constraints": [] 136 + } 137 + ]) 138 + #set ($filters = $stringtool.split($wikimacro.parameters.patterns, ',')) 139 + #foreach ($filter in $filters) 140 + #set ($discard = $liveDataConfig.query.filters[0].constraints.add( 141 + { "operator": "contains", "value": "$!filter.trim()" } 142 + )) 143 + #end 66 66 #end 67 -#elseif ($confluenceSortOrder == 'descending') 68 - #if ($reverseSortOrderProperties.contains($sortBy)) 69 - #set ($sortOrder = 'asc') 145 + #set ($sourceParams = { 146 + 'translationPrefix': 'core.viewers.attachments.livetable.', 147 + 'className': 'XWiki.AllAttachments', 148 + "\$doc": "$attachmentsDoc" 149 + }) 150 + ## Since the correct attachmentsjson.vm was added in XWiki 14.8, we use a copy of its code for backwards compatibility. 151 + #supportsAttachJSON($supportsAttachJSON) 152 + #if ($supportsAttachJSON) 153 + #set ($discard = $sourceParams.put('template', 'xpart.vm')) 154 + #set ($discard = $sourceParams.put('vm', 'attachmentsjson.vm')) 70 70 #else 71 - #set ($sort Order='desc')156 + #set ($discard = $sourceParams.put('resultPage', 'Confluence.Macros.AttachmentsJSON')) 72 72 #end 73 -#else 74 - #set ($invalidSortOrder = false) 75 -#end 76 -#set ($attachmentsFiltered = $collectiontool.sort($attachmentsFiltered, "$sortBy:$sortOrder")) 77 -{{/velocity}} 158 + #getLiveDataSort($liveDataSort) 159 + #if ($invalidSortBy) 160 + {{warning}} 161 + Attachment macro: sortBy parameter must be one of 'date', 'size', or 'name' 162 + {{/warning}} 163 + #end 164 + #if ($invalidSortOrder) 165 + {{warning}} 166 + Attachment macro: sortOrder parameter must be one of 'ascending' or 'descending' 167 + {{/warning}} 168 + #end 78 78 79 -{{velocity}} 80 -#if ($invalidSortBy) 81 - {{error}} 82 - Attachment macro error: sortBy parameter must be one of 'date', 'size', or 'name' 83 - {{/error}} 84 - #break 170 + {{liveData 171 + id="$liveDataId" 172 + properties="mimeType,filename,filesize,date,author,actions" 173 + source='liveTable' 174 + sourceParameters="$escapetool.url($sourceParams)" 175 + sort="$liveDataSort" 176 + limit=5 177 + }}$jsontool.serialize($liveDataConfig){{/liveData}} 178 + 179 + {{html clean="false"}} 180 + #uploadFileForm($liveDataId) 181 + #deleteConfluenceAttachmentModal() 182 + {{/html}} 85 85 #end 86 -#if ($invalidSortOrder) 87 - {{error}} 88 - Attachment macro error: sortOrder parameter must be one of 'ascending' or 'descending' 89 - {{/error}} 90 - #break 91 -#end 92 -## Display attachments 93 -{{html clean="false"}} 94 - <div class="confluence-attachment-macro"> 95 - #if ($attachmentsFiltered.size() > 0) 96 - #displayAttachments($attachmentsFiltered) 97 - #deleteAttachmentModal() 98 - #else 99 - <p class="noitems"> 100 - $!escapetool.xml($services.localization.render('core.viewers.attachments.noAttachments')) 101 - </p> 102 - #end 103 103 104 - ## Allow upload 105 - #set ($upload = "$!wikimacro.parameters.upload") 106 - #if ($upload == 'true' && ($hasEdit || $hasAdmin) && $xcontext.action == 'view') 107 - <form 108 - id="AddAttachment" 109 - action="$doc.getURL("upload")" 110 - method="post" 111 - enctype="multipart/form-data" 112 - > 113 - <div> 114 - ## CSRF prevention 115 - <input 116 - type="hidden" 117 - name="form_token" 118 - value="$!{services.csrf.getToken()}" 119 - /> 120 120 121 - <fieldset id="attachform"> 122 - <legend> 123 - $!escapetool.xml($services.localization.render('core.viewers.attachments.upload.title')) 124 - </legend> 186 +#macro (executeMacro) 187 + #set ($discard = $xwiki.ssfx.use('js/xwiki/viewers/attachments.css', true)) 188 + #set ($discard = $xwiki.ssfx.use('uicomponents/widgets/upload.css', true)) 189 + #set ($discard = $xwiki.jsfx.use('uicomponents/widgets/upload.js', { 190 + 'forceSkinAction': true, 191 + 'language': $xcontext.locale 192 + })) 193 + #set ($discard = $xwiki.jsx.use("Confluence.Macros.Attachments")) 194 + #set ($discard = $xwiki.ssx.use("Confluence.Macros.Attachments")) 195 + #set ($document = $doc) 196 + #if ("$!wikimacro.parameters.page" != '') 197 + #set ($document = $xwiki.getDocument("$!wikimacro.parameters.page")) 198 + #end 125 125 126 - <div class="fileupload-field"> 127 - <label 128 - class="hidden" 129 - for="xwikiuploadfile" 130 - > 131 - $!escapetool.xml($services.localization.render('core.viewers.attachments.upload.file')) 132 - </label> 200 + {{html clean="false" wiki="true"}} 201 + <div class='confluenceAttachmentsMacro'> 202 + #showConfluenceAttachments($document) 203 + </div> 204 + {{/html}} 133 133 134 - <input 135 - id="xwikiuploadfile" 136 - class="uploadFileInput noitems" 137 - type="file" 138 - name="filepath" 139 - size="40" 140 - data-max-file-size="$!escapetool.xml($xwiki.getSpacePreference('upload_maxsize'))" 141 - /> 142 - </div> 206 +#end 207 +{{/velocity}} 143 143 144 - <div> 145 - <span class="buttonwrapper"> 146 - <input 147 - class="button btn btn-primary" 148 - type="submit" 149 - value="$!escapetool.xml($services.localization.render('core.viewers.attachments.upload.submit'))" 150 - /> 151 - </span> 209 +{{include reference="Licenses.Code.VelocityMacros"/}} 152 152 153 - <span class="buttonwrapper"> 154 - <a 155 - class="cancel secondary button btn btn-primary" 156 - href="$doc.getURL()" 157 - > 158 - $!escapetool.xml($services.localization.render('core.viewers.attachments.upload.cancel')) 159 - </a> 160 - </span> 161 - </div> 162 - </fieldset> 163 - </div> 164 - </form> 165 - #end 166 - </div> ## .confluence-attachment-macro 167 -{{/html}} 211 +{{velocity}} 212 +## We need to check if there is a valid license because the macro is registered even if the user doesn't have view right 213 +## on the macro definition page. See XWIKI-14828: Rendering macros defined in wiki pages are available to users that 214 +## don't have view right on those pages. 215 +#if ($services.licensing.licensor.hasLicensureForEntity($xcontext.macro.doc.documentReference)) 216 + #executeMacro 217 +#else 218 + {{error}} 219 + #getMissingLicenseMessage('proMacros.extension.name') 220 + {{/error}} 221 +#end 168 168 {{/velocity}} 169 169 - Standardkategorie
-
... ... @@ -1,1 +1,0 @@ 1 -confluence
- XWiki.WikiMacroParameterClass[9]
-
- Parameter-Vorgabe
-
... ... @@ -1,0 +1,1 @@ 1 +true - Parameter-Beschreibung
-
... ... @@ -1,0 +1,1 @@ 1 +Display an option for uploading files to the selected page. - Parameter verpflichtend
-
... ... @@ -1,0 +1,1 @@ 1 +Nein - Parameter-Name
-
... ... @@ -1,0 +1,1 @@ 1 +upload - Parameter-Typ
-
... ... @@ -1,0 +1,1 @@ 1 +java.lang.Boolean