# Changes for page Attachments

Last modified by superadmin on 2017/11/28 14:52

#### Export

#### Office Formats

Export as PDF Export as ODT Export as RTF

#### Other Formats

Export as HTML
From empty
To version 1.1
edited by superadmin
on 2017/11/28 14:52
Change comment: Install extension [org.xwiki.platform:xwiki-platform-attachment-ui/9.9]

## Summary

- Page properties (5 modified, 0 added, 0 removed)
- Objects (0 modified, 19 added, 0 removed)
  - XWiki.JavaScriptExtension[0]
  - XWiki.JavaScriptExtension[1]
  - XWiki.StyleSheetExtension[0]
  - XWiki.WikiMacroClass[0]
  - XWiki.WikiMacroParameterClass[0]
  - XWiki.WikiMacroParameterClass[1]
  - XWiki.WikiMacroParameterClass[2]
  - XWiki.WikiMacroParameterClass[5]
  - XWiki.WikiMacroParameterClass[6]
  - XWiki.WikiMacroParameterClass[7]
  - XWiki.WikiMacroParameterClass[10]
  - XWiki.WikiMacroParameterClass[12]
  - XWiki.WikiMacroParameterClass[13]
  - XWiki.WikiMacroParameterClass[14]
  - XWiki.WikiMacroParameterClass[15]
  - XWiki.WikiMacroParameterClass[16]
  - XWiki.WikiMacroParameterClass[17]
  - XWiki.WikiMacroParameterClass[18]
  - XWiki.WikiMacroParameterClass[19]

## Details

Page properties
  Title

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +#if ($request.docname)$xwiki.getDocument($request.docname).plainTitle ($request.docname): #end$services.localization.render('xe.attachmentSelector.gallery.title')

  Parent

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Main.WebHome

  Author

    ... | ... | @@ -1,1 +1,1 @@
    1 |  | -XWiki.XWikiGuest
    | 1 | +xwiki:XWiki.superadmin

  Hidden

    ... | ... | @@ -1,1 +1,1 @@
    1 |  | -false
    | 1 | +true

  Content

    ... | ... | @@ -1,0 +1,302 @@
    | 1 | +{{velocity output="false"}}
    | 2 | +#if ($request.xaction == 'postUpload')
    | 3 | + #set ($targetDocument = $xwiki.getDocument($request.get('docname')))
    | 4 | + #set ($targetAttachDocument = $xwiki.getDocument($request.get('targetdocname')))
    | 5 | +
    | 6 | + #set ($fieldname = $request.get('fieldname'))
    | 7 | + #set ($docAction = $request.get('docAction'))
    | 8 | + #set ($attachmentList = $targetAttachDocument.getAttachmentList())
    | 9 | + #if ($attachmentList && $attachmentList.size() > 0)
    | 10 | + #set ($sortedAttachments = $sorttool.sort($attachmentList, 'date:desc'))
    | 11 | + #set ($lastAttachment = $sortedAttachments.get(0))
    | 12 | + #end
    | 13 | + $response.sendRedirect($targetDocument.getURL($docAction, $escapetool.url({
    | 14 | + $fieldname: $lastAttachment.filename,
    | 15 | + 'form_token': $request.form_token
    | 16 | + })))
    | 17 | + #stop
    | 18 | +#end
    | 19 | +{{/velocity}}
    | 20 | +
    | 21 | +{{velocity output="false"}}
    | 22 | +##
    | 23 | +## Macros
    | 24 | +##
    | 25 | +#set ($translationPrefix = 'xe.attachmentSelector')
    | 26 | +#set ($attachmentPickerDocName = 'XWiki.AttachmentSelector')
    | 27 | +
    | 28 | +$xwiki.ssx.use($attachmentPickerDocName)
    | 29 | +$xwiki.jsx.use($attachmentPickerDocName)
    | 30 | +
    | 31 | +#**
    | 32 | + * Displays the attachment gallery as a list of attachment boxes, starting with special boxes for uploading a new attachment and for setting a default value.
    | 33 | + *
    | 34 | + * @param $targetDocument the document to recieve the field value being modified
    | 35 | + * @param $targetAttachDocument the document to list/save attachments to
    | 36 | + * @param $options generic picker options
    | 37 | + *#
    | 38 | +#macro (attachmentPicker_displayAttachmentGallery $targetDocument, $targetAttachDocument, $options)
    | 39 | + #set ($currentValue = $targetDocument.getValue($options.property))
    | 40 | + #if ("$!{targetAttachDocument.getAttachment($currentValue)}" == '')
    | 41 | + #set ($currentValue = "$!{options.defaultValue}")
    | 42 | + #end
    | 43 | + (% class="gallery" %)(((
    | 44 | + ## Only display the upload form if they have edit permission on targetAttachDocument
    | 45 | + #if ($xwiki.hasAccessLevel('edit',$xcontext.user,${targetAttachDocument.fullName}))
    | 46 | + #attachmentPicker_displayUploadForm($targetDocument, $targetAttachDocument, $options)
    | 47 | + #end
    | 48 | + #attachmentPicker_displayAttachmentGalleryEmptyValue($targetDocument, $targetAttachDocument, $options, $currentValue)
    | 49 | + #set ($sortedAttachments = $sorttool.sort($targetAttachDocument.getAttachmentList(), "${options.sortAttachmentsBy}") )
    | 50 | + #foreach ($attachment in $sortedAttachments)
    | 51 | + #set ($extension = $attachment.getFilename())
    | 52 | + #set ($extension = $extension.substring($mathtool.add($extension.lastIndexOf('.'), 1)).toLowerCase())
    | 53 | + #if ($options.filter.size() == 0 || $options.filter.contains($extension))
    | 54 | + #attachmentPicker_displayAttachmentBox($attachment $targetDocument $targetAttachDocument, $options $currentValue)
    | 55 | + #end
    | 56 | + #end
    | 57 | + )))
    | 58 | +#end
    | 59 | +
    | 60 | +#**
    | 61 | + * Displays an attachment box.
    | 62 | + *
    | 63 | + * @param $attachment the target attachment to display
    | 64 | + * @param $targetDocument the document being modified
    | 65 | + * @param $options generic picker options
    | 66 | + * @param $currentValue the currently selected file, used for determining if the box should be highlighted as the current value
    | 67 | + *#
    | 68 | +#macro (attachmentPicker_displayAttachmentBox $attachment $targetDocument $targetAttachDocument, $options $currentValue)
    | 69 | + #if ($options.displayImage && $attachment.isImage())
    | 70 | + #set ($cssClass = 'gallery_image')
    | 71 | + #else
    | 72 | + #set ($cssClass = '')
    | 73 | + #end
    | 74 | + #attachmentPicker_displayStartFrame({'value' : $attachment.filename, 'text' : $attachment.filename, 'cssClass' : "$!{cssClass}"} $currentValue)
    | 75 | + #attachmentPicker_displayAttachmentDetails($attachment $options)
    | 76 | + #set ($returnURL = $escapetool.url($doc.getURL('view', $request.queryString)))
    | 77 | + #set ($deleteURL = $targetAttachDocument.getAttachmentURL($attachment.filename, 'delattachment', "xredirect=${returnURL}&form_token=$!{services.csrf.getToken()}") )
    | 78 | + #set ($viewURL = $targetAttachDocument.getAttachmentURL($attachment.filename) )##{'name' : 'download', 'url' : $viewURL, 'rel' : '__blank'}
    | 79 | + #set ($selectURL = $targetDocument.getURL(${options.get('docAction')}, "${options.get('classname')}_${options.get('object')}_${options.get('property')}=${attachment.filename}&form_token=$!{services.csrf.getToken()}"))
    | 80 | + #attachmentPicker_displayEndFrame ([{'name' : 'select', 'url' : $selectURL}, {'name' : 'delete', 'url' : $deleteURL}])
    | 81 | +#end
    | 82 | +
    | 83 | +#**
    | 84 | + * Writes the wiki code used at the start of an attachment box. Outputs the attachment title bar, and opens the inner frame div.
    | 85 | + *
    | 86 | + * @param $boxOptions a map of parameters/options for the current attachment, holding, for example, the attachment name (boxOptions.value),
    | 87 | + * the title to display (boxOptions.text), optional extra CSS classnames to put on the box (boxOptions.cssClass)
    | 88 | + * @param $currentValue the currently selected file, used for determining if this attachment should be highlighted as the current value
    | 89 | + *#
    | 90 | +#macro (attachmentPicker_displayStartFrame $boxOptions $currentValue)
    | 91 | + (% class="gallery_attachmentbox $!{boxOptions.cssClass} #if ("$!{boxOptions.value}" == $currentValue) current#{end}" %)(((
    | 92 | + (% class="gallery_attachmenttitle" title="$!{boxOptions.value}" %)((($boxOptions.text)))
    | 93 | + (% class="gallery_attachmentframe" %)(((
    | 94 | +#end
    | 95 | +
    | 96 | +#**
    | 97 | + * Displays details about an attachment inside the attachment box. If the attachment is an image and the "displayImage" option is on,
    | 98 | + * then the image is displayed. Otherwise, some basic information is displayed: the version, the size, the date and the author.
    | 99 | + *
    | 100 | + * @param $attachment the target attachment to display
    | 101 | + * @param $options generic picker options
    | 102 | + *#
    | 103 | +#macro (attachmentPicker_displayAttachmentDetails $attachment $options)
    | 104 | + #if ($attachment)
    | 105 | + ## Compute the attachment reference because there's no getter.
    | 106 | + #set ($attachmentReference = $services.model.createAttachmentReference($attachment.document.documentReference,
    | 107 | + $attachment.filename))
    | 108 | + #set ($attachmentStringReference = $services.model.serialize($attachmentReference, 'default'))
    | 109 | + #if ($attachment.isImage() && $options.displayImage)
    | 110 | + ## We add the version to the query string in order to invalidate the cache when an image attachment is replaced.
    | 111 | + #set ($queryString = $escapetool.url({'version': $attachment.version}))
    | 112 | + [[[[image:$attachmentStringReference||width=180 queryString="$queryString"]]>>attach:$attachmentStringReference]]
    | 113 | + #else
    | 114 | + * (% class="mime" %){{html wiki=false clean=false}}#mimetypeimg($attachment.getMimeType().toLowerCase() $attachment.getFilename().toLowerCase()){{/html}}(%%) (% class="filename" %)$attachment.getFilename()(% %)
    | 115 | + * v$attachment.getVersion() (#dynamicsize($attachment.longSize))
    | 116 | + * $services.localization.render('core.viewers.attachments.author', [$!{xwiki.getUserName($attachment.author, false)}]) $services.localization.render('core.viewers.attachments.date', [$!{xwiki.formatDate($attachment.date, 'dd/MM/yyyy hh:mm')}])
    | 117 | + * (% class="buttonwrapper" %)[[${services.localization.render("${translationPrefix}.actions.download")}>>attach:$attachmentStringReference||title="$services.localization.render("${translationPrefix}.actions.download")" rel="__blank" class="button"]](%%)
    | 118 | + #end
    | 119 | + #end
    | 120 | +#end
    | 121 | +
    | 122 | +#**
    | 123 | + * Writes the wiki code used at the end of an attachment box. Closes the inner frame div, and outputs the attachment actions.
    | 124 | + *
    | 125 | + * @param $actions a list of maps defining action buttons, where each entry contains a subset of the following:
    | 126 | + * <dl>
    | 127 | + * <dt>name</dt>
    | 128 | + * <dd>identifies the action; the name is used as a CSS classname, and in the translation key for the display text, as "xe.attachmentSelector.actions.<name>"</dd>
    | 129 | + * <dt>url</dt>
    | 130 | + * <dd>the destination of the button</dd>
    | 131 | + * <dt>rel</dt>
    | 132 | + * <dd>an optional parameter to be used in the "rel" HTML attribute; for example "__blank" can be used to open the link in a new tab/window</dd>
    | 133 | + * </dl>
    | 134 | + *#
    | 135 | +#macro (attachmentPicker_displayEndFrame $actions)
    | 136 | + )))## attachmentframe
    | 137 | + (% class="gallery_actions" %)(((
    | 138 | + #foreach ($action in $actions)
    | 139 | + #set( $actionname = $services.localization.render("${translationPrefix}.actions.${action.name}") )
    | 140 | + [[${actionname}>>path:${action.url}||class="tool ${action.name}" title="${actionname}" #if($action.rel) rel="${action.rel}"#end]]##
    | 141 | + #end
    | 142 | + )))## actions
    | 143 | + )))## attachmentbox
    | 144 | +#end
    | 145 | +
    | 146 | +#**
    | 147 | + * Displays the upload box used for adding and selecting a new attachment.
    | 148 | + *
    | 149 | + * @param $targetDocument the document with the property being modified
    | 150 | + * @param $targetAttachDocument the document to upload the attachment to
    | 151 | + * @param $options generic picker options
    | 152 | + *#
    | 153 | +#macro (attachmentPicker_displayUploadForm $targetDocument, $targetAttachDocument, $options)
    | 154 | +#attachmentPicker_displayStartFrame({
    | 155 | + 'value' : $services.localization.render("${translationPrefix}.upload.title"),
    | 156 | + 'text' : $services.localization.render("${translationPrefix}.upload.title"),
    | 157 | + 'cssClass' : 'gallery_upload'
    | 158 | + } $NULL)
    | 159 | +{{html clean="false"}}
    | 160 | +<form action="$targetAttachDocument.getURL('upload')" enctype="multipart/form-data" method="post" id="uploadAttachment" class="uploadAttachment xform">
    | 161 | + <div class="gallery_upload_input">
    | 162 | + #if (${options.rawfilter} != '')
    | 163 | + <span class="xHint">$escapetool.xml($services.localization.render("${translationPrefix}.upload.hint", [${options.rawfilter}]))</span>
    | 164 | + #end
    | 165 | + <input type="file" name="filepath" id="attachfile" class="noitems" title="$!{escapetool.xml($options.rawfilter)}"/>
    | 166 | + <input type="hidden" name="xredirect" value="$xwiki.getDocument($attachmentPickerDocName).getURL('get', "xaction=postUpload&amp;docAction=$!{escapetool.url($options.get('docAction'))}&amp;targetdocname=$!{escapetool.url($targetAttachDocument.fullName)}&amp;docname=$!{escapetool.url($targetDocument.fullName)}&amp;fieldname=$!{escapetool.url($options.get('classname'))}_$!{escapetool.url($options.get('object'))}_$!{escapetool.url($options.get('property'))}&amp;form_token=$!{services.csrf.getToken()}")" />
    | 167 | + <input type="hidden" name="docname" value="$!{escapetool.xml($targetDocument.fullName)}" />
    | 168 | + <input type="hidden" name="classname" value="$!{escapetool.xml($options.get('classname'))}" />
    | 169 | + <input type="hidden" name="object" value="$!{escapetool.xml($options.get('object'))}" />
    | 170 | + <input type="hidden" name="property" value="$!{escapetool.xml($options.get('property'))}" />
    | 171 | + <input type="hidden" name="form_token" value="$!{services.csrf.getToken()}" />
    | 172 | + </div>
    | 173 | + #if ("$!currentValue" != '' && $currentValue != $options.defaultValue)
    | 174 | + <div>
    | 175 | + <label>
    | 176 | + <input type="checkbox" name="filename" value="$!escapetool.xml($currentValue)"
    | 177 | + />$services.localization.render('attachmentSelector.replace',
    | 178 | + ["<strong>$!escapetool.xml($currentValue)</strong>"])
    | 179 | + </label>
    | 180 | + <span class="xHint">$escapetool.xml($services.localization.render('attachmentSelector.replace.hint'))</span>
    | 181 | + </div>
    | 182 | + #end
    | 183 | + #if ($xwiki.hasEditComment() && $options.versionSummary)
    | 184 | + <div>
    | 185 | + #if ($xwiki.isEditCommentFieldHidden())
    | 186 | + <input type="hidden" name="comment" value="$!escapetool.xml($request.comment)" />
    | 187 | + #else
    | 188 | + <label for="commentinput">$services.localization.render('core.comment')</label>
    | 189 | + <input type="text" name="comment" id="commentinput" value="$!escapetool.xml($request.comment)"
    | 190 | + title="$services.localization.render('core.comment.tooltip')" />
    | 191 | + #end
    | 192 | + </div>
    | 193 | + #end
    | 194 | + <div class="buttons">
    | 195 | + <span class="buttonwrapper">
    | 196 | + <input type="submit" name="action_upload" class="button " value='$services.localization.render("${translationPrefix}.upload.submit")' title='$services.localization.render("${translationPrefix}.upload.submit")'/>
    | 197 | + </span>
    | 198 | + </div>
    | 199 | +</form>
    | 200 | +{{/html}}
    | 201 | +#attachmentPicker_displayEndFrame ([])
    | 202 | +#end
    | 203 | +
    | 204 | +#**
    | 205 | + * Displays the "empty value" box, used for unsetting the field value.
    | 206 | + *
    | 207 | + * @param $targetDocument the document being modified
    | 208 | + * @param $targetAttachDocument the document that the attachments will the loaded from/saved to
    | 209 | + * @param $options generic picker options
    | 210 | + * @param $currentValue the currently selected file, used for determining if the empty box should be highlighted as the current value
    | 211 | + *#
    | 212 | +#macro (attachmentPicker_displayAttachmentGalleryEmptyValue $targetDocument, $targetAttachDocument, $options, $currentValue)
    | 213 | + #if ("$!{options.get('defaultValue')}" != '')
    | 214 | + #set ($reference = ${options.get('defaultValue')})
    | 215 | + #set ($docNameLimit = $reference.indexOf('@'))
    | 216 | + #if ($docNameLimit > 0)
    | 217 | + #set ($docName = $reference.substring(0, $docNameLimit))
    | 218 | + #else
    | 219 | + #set ($docName = $targetAttachDocument.fullName)
    | 220 | + #end
    | 221 | + #set ($attachmentName = $reference.substring($mathtool.add($docNameLimit, 1)))
    | 222 | + #set ($defaultAttachment = $xwiki.getDocument($docName).getAttachment($attachmentName))
    | 223 | + #if ($defaultAttachment.isImage())
    | 224 | + #set($dcssClass = 'gallery_image')
    | 225 | + #end
    | 226 | + #end
    | 227 | + #attachmentPicker_displayStartFrame({'cssClass' : "gallery_emptyChoice $!{dcssClass}", 'text' : $services.localization.render("${translationPrefix}.default"), 'value' : "${options.defaultValue}"} $currentValue)
    | 228 | + #attachmentPicker_displayAttachmentDetails($defaultAttachment $options)
    | 229 | + #set ($returnURL = $escapetool.url($doc.getURL('view', $request.queryString)))
    | 230 | + #set ($selectURL = $targetDocument.getURL(${options.get('docAction')}, "${options.get('classname')}_${options.get('object')}_${options.get('property')}=&form_token=$!{services.csrf.getToken()}"))
    | 231 | + #attachmentPicker_displayEndFrame ([{'name' : 'select', 'url' : $selectURL}])
    | 232 | +#end
    | 233 | +{{/velocity}}
    | 234 | +
    | 235 | +{{velocity}}
    | 236 | +#if ($request.docname)
    | 237 | + #set ($targetDocument = $xwiki.getDocument($request.docname))
    | 238 | + #if ($request.targetdocname)
    | 239 | + ## Use the target document if it exists.
    | 240 | + #set ($targetAttachDocument = $xwiki.getDocument($request.targetdocname))
    | 241 | + #else
    | 242 | + ## Otherwise, just use the current document as the target to save/load attachments
    | 243 | + #set ($targetAttachDocument = $targetDocument)
    | 244 | + #end
    | 245 | + #if ("$!{request.savemode}" == 'direct')
    | 246 | + #set($docAction = 'save')
    | 247 | + #else
    | 248 | + #set($docAction = $targetAttachDocument.getDefaultEditMode())
    | 249 | + #end
    | 250 | + #set ($filter = [])
    | 251 | + #set ($rawfilter = '')
    | 252 | + #if ("$!{request.filter}" != '')
    | 253 | + #foreach ($value in $request.filter.trim().split('\s*+[,|; ]\s*+'))
    | 254 | + #if ("$!value" != '')
    | 255 | + #set ($discard = $filter.add($value.toLowerCase()))
    | 256 | + #set ($rawfilter = "${rawfilter}, ${value}")
    | 257 | + #end
    | 258 | + #end
    | 259 | + #if ($rawfilter != '')
    | 260 | + #set ($rawfilter = $rawfilter.substring(2))
    | 261 | + #end
    | 262 | + #end
    | 263 | + #if ("$!{request.displayImage}" == 'true')
    | 264 | + #set ($displayImage = true)
    | 265 | + #else
    | 266 | + #set ($displayImage = false)
    | 267 | + #end
    | 268 | + ### Determine attachment sorting
    | 269 | + #set($sortAttachmentsBy = "$!{request.sortAttachmentsBy}")
    | 270 | + #set ($validAttachmentProperties = ['filename', 'date', 'filesize', 'author', 'version', 'mimeType'])
    | 271 | + #if($sortAttachmentsBy == '' || $validAttachmentProperties.indexOf($sortAttachmentsBy) == -1)
    | 272 | + ### Default to sorting by filename, sort not requested.
    | 273 | + #set($sortAttachmentsBy = "filename")
    | 274 | + #end
    | 275 | + ### Set attachment sorting direction
    | 276 | + #if($sortAttachmentsBy == 'date')
    | 277 | + ### Sort the date descending
    | 278 | + #set($sortAttachmentsBy = "date:desc")
    | 279 | + #else
    | 280 | + ### Sort everything else ascending
    | 281 | + #set($sortAttachmentsBy = "${sortAttachmentsBy}:asc")
    | 282 | + #end
    | 283 | + #set ($options = {
    | 284 | + 'classname' : ${request.get('classname')},
    | 285 | + 'object' : $!{mathtool.toInteger($request.object)},
    | 286 | + 'property' : ${request.property},
    | 287 | + 'displayImage' : ${displayImage},
    | 288 | + 'docAction' : ${docAction},
    | 289 | + 'defaultValue' : "$!{request.defaultValue}",
    | 290 | + 'rawfilter': "$!{rawfilter}",
    | 291 | + 'filter': ${filter},
    | 292 | + 'sortAttachmentsBy': ${sortAttachmentsBy},
    | 293 | + 'versionSummary': $request.versionSummary.equals('true')
    | 294 | + })
    | 295 | + $!targetDocument.use($targetDocument.getObject($options.classname, $options.object))##
    | 296 | + #attachmentPicker_displayAttachmentGallery($targetDocument, $targetAttachDocument, $options)
    | 297 | +
    | 298 | + (% class="gallery_buttons buttons" %)(((
    | 299 | + (% class="buttonwrapper secondary" %)[[$services.localization.render("${translationPrefix}.cancel")>>$targetDocument||class="button secondary" id="attachment-picker-close"]]
    | 300 | + )))
    | 301 | +#end
    | 302 | +{{/velocity}}

XWiki.JavaScriptExtension[0]
  Caching policy

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +long

  Code

    ... | ... | @@ -1,0 +1,301 @@
    | 1 | +var XWiki = (function(XWiki) {
    | 2 | + /** Handles the gallery buttons directly in the current page without reloading the window. */
    | 3 | + XWiki.AttachmentPicker = Class.create({
    | 4 | + /**
    | 5 | + * Constructor.
    | 6 | + *
    | 7 | + * @param property the property being edited, as the containing div with class="attachment-picker"
    | 8 | + * @param dialog the modal dialog where the gallery was loaded
    | 9 | + */
    | 10 | + initialize : function(property, dialog) {
    | 11 | + this.property = property;
    | 12 | + this.dialog = dialog;
    | 13 | + this.gallery = dialog.dialogBox;
    | 14 | + if (!this.property.down('input')) {
    | 15 | + this.directSave = true;
    | 16 | + }
    | 17 | + this.defaultValue = '';
    | 18 | + var defaultValueElement = this.gallery.down('.gallery_emptyChoice .gallery_attachmenttitle');
    | 19 | + if (defaultValueElement) {
    | 20 | + this.defaultValue = defaultValueElement.title;
    | 21 | + }
    | 22 | + this.defaultImageSource = '';
    | 23 | + var defaultImage = this.gallery.down('.gallery_emptyChoice.gallery_image img');
    | 24 | + if (defaultImage) {
    | 25 | + this.defaultImageSource = defaultImage.src.replace(/\?.*$/, '');
    | 26 | + }
    | 27 | + this.filterFormUpload();
    | 28 | + this.addDeleteListeners();
    | 29 | + this.addSelectListeners();
    | 30 | + this.updateCurrentSelection();
    | 31 | + this.addCloseListener();
    | 32 | + var img = this.property.down('img');
    | 33 | + if (img) {
    | 34 | + img.observe('load', function() {img.up('div').removeClassName('hidden');});
    | 35 | + img.observe('error', function() {img.up('div').addClassName('hidden');});
    | 36 | + }
    | 37 | + },
    | 38 | + /**
    | 39 | + * Catches the upload form submission and stops it if the selected file is not of the accepted types (or not selected at all),
    | 40 | + * and saves the underlying form (if any) so that any unsaved changes won't be lost.
    | 41 | + */
    | 42 | + filterFormUpload : function() {
    | 43 | + this.gallery.select('.uploadAttachment input[type="file"]').each(function(fileInput) {
    | 44 | + fileInput.__allowedExtensions = fileInput.title.toLowerCase().replace(/\s*[,|; ]\s*/g, " ").strip();
    | 45 | + if (fileInput.__allowedExtensions != '') {
    | 46 | + fileInput.__allowedExtensions = fileInput.__allowedExtensions.split(" ");
    | 47 | + } else {
    | 48 | + fileInput.__allowedExtensions = false;
    | 49 | + }
    | 50 | + });
    | 51 | + this.gallery.select(".uploadAttachment").invoke('observe', 'submit', function(event) {
    | 52 | + event.stop();
    | 53 | + var uploadForm = event.element();
    | 54 | + var hasErrors = false;
    | 55 | + uploadForm.select('input[type="file"]').each(function(fileInput) {
    | 56 | + if (fileInput.value == '') {
    | 57 | + new XWiki.widgets.Notification("$services.localization.render('xe.attachmentSelector.upload.error.noFile')", 'error');
    | 58 | + hasErrors = true;
    | 59 | + } else if (fileInput.__allowedExtensions && fileInput.__allowedExtensions.indexOf(this.getFileExtension(fileInput.value)) == -1) {
    | 60 | + new XWiki.widgets.Notification("$services.localization.render('xe.attachmentSelector.upload.error.badExtension')", 'error');
    | 61 | + hasErrors = true;
    | 62 | + }
    | 63 | + }.bind(this));
    | 64 | + // No form submission by AJAX right now, because file uploads can't be done this way without HTML5, this is future work
    | 65 | + // Save the document before submitting, since the current form data will be lost otherwise
    | 66 | + if (!hasErrors) {
    | 67 | + if (this.directSave) {
    | 68 | + uploadForm.submit();
    | 69 | + } else {
    | 70 | + // FIXME This fails in HTML5, will deal with it later:
    | 71 | + // this.property.down('input').value = uploadForm.down('input[type="file"]').value;
    | 72 | + // uploadForm.xredirect.value = window.location.pathname;
    | 73 | + document.observe('xwiki:document:saved', function() {
    | 74 | + new XWiki.widgets.Notification("$services.localization.render('xe.attachmentSelector.upload.inProgress')", 'inprogress');
    | 75 | + uploadForm.submit();
    | 76 | + })
    | 77 | + document.fire('xwiki:actions:save', {'continue': true, form: this.property.up('form')});
    | 78 | + }
    | 79 | + }
    | 80 | + }.bindAsEventListener(this));
    | 81 | + },
    | 82 | + getFileExtension : function(filename) {
    | 83 | + var fpath = filename.replace('\\', '/');
    | 84 | + var pieces = fpath.split('/');
    | 85 | + if (pieces.length == 0) {
    | 86 | + return null;
    | 87 | + }
    | 88 | + var basename = pieces[pieces.length-1];
    | 89 | + pieces = basename.split('.');
    | 90 | + return pieces[pieces.length-1].toLowerCase();
    | 91 | + },
    | 92 | +
    | 93 | + /** Catch attachment delete requests. */
    | 94 | + addDeleteListeners : function() {
    | 95 | + this.gallery.select('.gallery_actions .tool.delete').invoke('observe', 'click', this.onDelete.bindAsEventListener(this));
    | 96 | + },
    | 97 | +
    | 98 | + /** Catch attachment selection requests. */
    | 99 | + addSelectListeners : function() {
    | 100 | + this.gallery.select('.gallery_actions .tool.select').invoke('observe', 'click', this.onSelect.bindAsEventListener(this));
    | 101 | + },
    | 102 | +
    | 103 | + /** AJAX deletion of attachments. */
    | 104 | + onDelete : function(event) {
    | 105 | + event.stop();
    | 106 | + deleteTool = event.element();
    | 107 | + if (!deleteTool.disabled) {
    | 108 | + new XWiki.widgets.ConfirmedAjaxRequest(
    | 109 | + deleteTool.readAttribute('href'),
    | 110 | + {
    | 111 | + onCreate : function() { deleteTool.disabled = true },
    | 112 | + onSuccess : function() {
    | 113 | + var attachmentBox = deleteTool.up('.gallery_attachmentbox');
    | 114 | + if (attachmentBox) {
    | 115 | + if (attachmentBox.hasClassName('current')) {
    | 116 | + this.updateAttachment(this.defaultValue, this.defaultImageSource);
    | 117 | + if (this.gallery.down('.gallery_emptyChoice')) {
    | 118 | + this.gallery.down('.gallery_emptyChoice').addClassName('current');
    | 119 | + }
    | 120 | + // Disable the replace feature because there's no selection.
    | 121 | + var fileNameInput = this.gallery.down('input[type="checkbox"][name="filename"]');
    | 122 | + fileNameInput.disabled = true;
    | 123 | + // Empty the displayed selected file name.
    | 124 | + fileNameInput.up('label').down('strong').update('');
    | 125 | + if (!this.directSave) {
    | 126 | + this.property.down('input').value = this.defaultValue;
    | 127 | + }
    | 128 | + }
    | 129 | + attachmentBox.remove();
    | 130 | + }
    | 131 | + }.bind(this),
    | 132 | + onComplete : function() {
    | 133 | + deleteTool.disabled = false;
    | 134 | + }
    | 135 | + },
    | 136 | + {
    | 137 | + confirmationText: "$services.localization.render('core.viewers.attachments.delete.confirm')",
    | 138 | + progressMessageText : "$services.localization.render('core.viewers.attachments.delete.inProgress')",
    | 139 | + successMessageText : "$services.localization.render('core.viewers.attachments.delete.done')",
    | 140 | + failureMessageText : "$services.localization.render('core.viewers.attachments.delete.failed')"
    | 141 | + }
    | 142 | + );
    | 143 | + }
    | 144 | + },
    | 145 | +
    | 146 | + /** Update the property with the selected value without reloading the page. */
    | 147 | + onSelect : function(event) {
    | 148 | + event.stop();
    | 149 | + var targetElement = event.element();
    | 150 | + var attachmentName = targetElement.up('.gallery_attachmentbox').down('.gallery_attachmenttitle').title;
    | 151 | + var imageSource = targetElement.up('.gallery_attachmentbox.gallery_image');
    | 152 | + if (imageSource) {
    | 153 | + imageSource = imageSource.down('.gallery_attachmentframe img');
    | 154 | + if (imageSource) {
    | 155 | + imageSource = imageSource.src.replace(/\?.*$/, '');
    | 156 | + }
    | 157 | + }
    | 158 | + if (!imageSource) {
    | 159 | + imageSource = '';
    | 160 | + }
    | 161 | + if (this.directSave) {
    | 162 | + // save via ajax
    | 163 | + if (!targetElement.disabled) {
    | 164 | + new Ajax.Request(event.element().href, {
    | 165 | + onCreate : function() {
    | 166 | + targetElement.disabled = true;
    | 167 | + targetElement._x_notif = new XWiki.widgets.Notification("$services.localization.render('core.widgets.confirmationBox.notification.inProgress')", 'inprogress');
    | 168 | + },
    | 169 | + onSuccess : function () {
    | 170 | + targetElement._x_notif.hide();
    | 171 | + // on success, update image
    | 172 | + this.updateAttachment(attachmentName, imageSource);
    | 173 | + this.dialog.closeDialog();
    | 174 | + }.bindAsEventListener(this),
    | 175 | + onFailure : function(response) {
    | 176 | + var failureReason = response.statusText;
    | 177 | + if (response.statusText == '' /* No response */ || response.status == 12031 /* In IE */) {
    | 178 | + failureReason = 'Server not responding';
    | 179 | + }
    | 180 | + if (targetElement._x_notif) {
    | 181 | + targetElement._x_notif.replace(new XWiki.widgets.Notification("$services.localization.render('core.widgets.confirmationBox.notification.failed')" + failureReason, "error"));
    | 182 | + } else {
    | 183 | + new XWiki.widgets.Notification(this.interactionParameters.failureMessageText + failureReason, "error");
    | 184 | + }
    | 185 | + },
    | 186 | + on1223 : function(response) {
    | 187 | + response.request.options.onSuccess(response);
    | 188 | + },
    | 189 | + on0 : function(response) {
    | 190 | + response.request.options.onFailure(response);
    | 191 | + },
    | 192 | + onComplete : function() {
    | 193 | + targetElement.disabled = false;
    | 194 | + }
    | 195 | + });
    | 196 | + }
    | 197 | + } else {
    | 198 | + this.updateAttachment(attachmentName, imageSource);
    | 199 | + this.property.down('input').value = attachmentName;
    | 200 | + this.dialog.closeDialog();
    | 201 | + }
    | 202 | + },
    | 203 | +
    | 204 | + /** Update the property display. */
    | 205 | + updateAttachment : function(attachmentName, imageSource) {
    | 206 | + var img = this.property.down('img');
    | 207 | + if (img) {
    | 208 | + img.src = img.src.replace(/^[^\?]+($|\?)/, imageSource + "$1");
    | 209 | + var link = img.up('a');
    | 210 | + if (link) {
    | 211 | + link.href = link.href.replace(/^[^\?]+($|\?)/, imageSource + "$1");
    | 212 | + }
    | 213 | + } else {
    | 214 | + var displayed = this.property.down('.displayed');
    | 215 | + if (displayed) {
    | 216 | + displayed.update(attachmentName);
    | 217 | + var link = displayed.up('a');
    | 218 | + if (link) {
    | 219 | + link.href = link.href.replace(/^[^\?]+($|\?)/, attachmentName + "$1");
    | 220 | + }
    | 221 | + }
    | 222 | + }
    | 223 | + },
    | 224 | +
    | 225 | + /** Update the gallery to reflect the currently selected value. */
    | 226 | + updateCurrentSelection : function() {
    | 227 | + if (!this.directSave) {
    | 228 | + var selectedName = this.property.down('input').value;
    | 229 | + var selectedElement = this.gallery.down('.gallery_attachmenttitle[title="' + selectedName.replace('"', '\\22 ') + '"]');
    | 230 | + if (selectedElement) {
    | 231 | + selectedElement = selectedElement.up('.gallery_attachmentbox');
    | 232 | + } else {
    | 233 | + selectedElement = this.gallery.down('.gallery_emptyChoice');
    | 234 | + }
    | 235 | + if (!selectedElement.hasClassName('current')) {
    | 236 | + this.gallery.select('.gallery_attachmentbox.current .tool.select').invoke('removeClassName', 'hidden');
    | 237 | + this.gallery.select('.gallery_attachmentbox.current').invoke('removeClassName', 'current');
    | 238 | + selectedElement.addClassName('current');
    | 239 | + }
    | 240 | + selectedElement.down('.tool.select').addClassName('hidden');
    | 241 | + } else {
    | 242 | + this.gallery.down('.current .tool.select').addClassName('hidden');
    | 243 | + }
    | 244 | + },
    | 245 | + addCloseListener : function () {
    | 246 | + var closeButton = $('attachment-picker-close');
    | 247 | + if (closeButton) {
    | 248 | + closeButton.observe('click', function(event) {
    | 249 | + event.stop();
    | 250 | + this.dialog.closeDialog();
    | 251 | + }.bindAsEventListener(this));
    | 252 | + }
    | 253 | + }
    | 254 | + });
    | 255 | +
    | 256 | + var loading = new Element('div', {'class' : 'imgcenter'}).update("<img src=\"$xwiki.getSkinFile('icons/xwiki/ajax-loader-large.gif')\"/>");
    | 257 | + var dialog;
    | 258 | +
    | 259 | + /* Hook onto the picker buttons so that the gallery is loaded via AJAX in a modal dialog. */
    | 260 | + document.observe('xwiki:dom:loaded', function() {
    | 261 | + $$('.attachment-picker-start').invoke('observe', 'click', function(event) {
    | 262 | + event.stop();
    | 263 | + var targetElement = event.element();
    | 264 | + var url = targetElement.href;
    | 265 | + if (url.indexOf('?') < 0) {
    | 266 | + url += '?';
    | 267 | + }
    | 268 | + url += "&xpage=plain";
    | 269 | + dialog = new XWiki.widgets.ModalPopup(
    | 270 | + loading, {}, {
    | 271 | + 'verticalPosition' : 'top',
    | 272 | + 'removeOnClose' : true,
    | 273 | + 'title' : "$services.localization.render('xe.attachmentSelector.gallery.title')"
    | 274 | + }
    | 275 | + );
    | 276 | + dialog.shortcuts.close.keys = [];
    | 277 | + dialog.showDialog();
    | 278 | + dialog.dialog.down().setStyle({position: "fixed"});
    | 279 | + dialog.dialog.setStyle({top: document.viewport.getScrollOffsets().top + "px", position: "absolute"});
    | 280 | + dialog.dialogBox.setStyle({overflow: "hidden", width: "80%", margin: "0 10%"});
    | 281 | + new Ajax.Updater(loading.up(), url, {
    | 282 | + onComplete : function() {
    | 283 | + // Play nice with the Lightbox widget
    | 284 | + if (window.gallery_lb) {
    | 285 | + window.gallery_lb.updateImageList();
    | 286 | + }
    | 287 | + // Manually convert the special rel values into target attributes for the newly inserted content (rel="_something" => target="something")
    | 288 | + if (typeof (XWiki.fixLinksTargetAttribute) == 'function') {
    | 289 | + // Trick the function into updating link targets in edit mode; normally the conversion only takes place in view mode
    | 290 | + var __tmp = XWiki.contextaction;
    | 291 | + XWiki.contextaction = 'view';
    | 292 | + XWiki.fixLinksTargetAttribute(dialog.dialog);
    | 293 | + XWiki.contextaction = __tmp;
    | 294 | + }
    | 295 | + new XWiki.AttachmentPicker(targetElement.up('.attachment-picker'), dialog);
    | 296 | + }
    | 297 | + });
    | 298 | + });
    | 299 | + });
    | 300 | + return XWiki;
    | 301 | +}(XWiki || {}));

  Name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Loads the image picker in a modal dialog

  Parse content

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Use this extension

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +currentPage

XWiki.JavaScriptExtension[1]
  Caching policy

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +long

  Code

    ... | ... | @@ -1,0 +1,33 @@
    | 1 | +XWiki.widgets.ModalPopup.prototype.showDialog = function(event) {
    | 2 | + if (event) {
    | 3 | + Event.stop(event);
    | 4 | + }
    | 5 | + // Only do this if the dialog is not already active.
    | 6 | + if (!this.active) {
    | 7 | + this.active = true;
    | 8 | + if (!this.dialog) {
    | 9 | + // The dialog wasn't loaded, create it.
    | 10 | + this.createDialog();
    | 11 | + }
    | 12 | + // Start listening to keyboard events
    | 13 | + this.attachKeyListeners();
    | 14 | + // Display the dialog
    | 15 | + this.dialog.show();
    | 16 | + }
    | 17 | +};
    | 18 | +XWiki.widgets.ModalPopup.prototype.closeDialog = function(event) {
    | 19 | + if (event) {
    | 20 | + Event.stop(event);
    | 21 | + }
    | 22 | + // Call optional callback
    | 23 | + this.options.onClose.call(this);
    | 24 | + // Hide the dialog, without removing it from the DOM.
    | 25 | + this.dialog.hide();
    | 26 | + if (this.options.removeOnClose) {
    | 27 | + this.dialog.remove();
    | 28 | + }
    | 29 | + // Stop the UI shortcuts (except the initial Show Dialog one).
    | 30 | + this.detachKeyListeners();
    | 31 | + // Re-enable the 'show' behavior.
    | 32 | + this.active = false;
    | 33 | +}

  Name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Patch for modalPopup.js, allowing to display several widgets on the same screen and removing position:fixed in IE

  Parse content

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Use this extension

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +currentPage

XWiki.StyleSheetExtension[0]
  Caching policy

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +long

  Code

    ... | ... | @@ -1,0 +1,195 @@
    | 1 | +#template("colorThemeInit.vm")
    | 2 | +#set ($boxSize = 170)
    | 3 | +#set ($boxPadding = 5)
    | 4 | +#set ($boxMargin = 5)
    | 5 | +## Make the upload box two times as wide, so that the input fits nicely.
    | 6 | +## | |
    | 7 | +## | | | |
    | 8 | +## | 1 width + 1 border + 2 margins + 1 border + 1 width |
    | 9 | +#set ($uploadBoxSize = $mathtool.add(2, $mathtool.add($mathtool.mul($boxSize, 2), $mathtool.mul($boxMargin, 2))))
    | 10 | +#set ($imgSize = $mathtool.sub($boxSize, $mathtool.mul($boxPadding, 2)))
    | 11 | +#set ($actionsHeight = 20)
    | 12 | +#set ($actionsWidth = 16)
    | 13 | +/*--------------------------------------------------------*/
    | 14 | +/* Attachment picker layout fixes */
    | 15 | +.attachment-picker p {
    | 16 | + margin: 0;
    | 17 | + padding : 0;
    | 18 | +}
    | 19 | +.attachment-picker picture {
    | 20 | + max-width: 100%;
    | 21 | +}
    | 22 | +/*--------------------------------------------------------*/
    | 23 | +/* Gallery attachmentboxes */
    | 24 | +.gallery p {
    | 25 | + margin: 0;
    | 26 | +}
    | 27 | +.gallery_attachmentbox {
    | 28 | + background: $theme.pageContentBackgroundColor;
    | 29 | + border: 1px solid $theme.borderColor;
    | 30 | + border-radius: 5px;
    | 31 | + float: left;
    | 32 | + margin: ${boxMargin}px;
    | 33 | + overflow: hidden;
    | 34 | + position: relative;
    | 35 | + width: ${boxSize}px;
    | 36 | +}
    | 37 | +.gallery .current {
    | 38 | + background-color: $theme.highlightColor;
    | 39 | + border-width: 3px;
    | 40 | + margin: 3px;
    | 41 | +}
    | 42 | +.gallery .current .gallery_attachmenttitle {
    | 43 | + font-weight: bold;
    | 44 | +}
    | 45 | +.gallery_attachmentbox:hover {
    | 46 | + background-color: $theme.highlightColor;
    | 47 | +}
    | 48 | +
    | 49 | +.gallery_attachmenttitle {
    | 50 | + background: $theme.backgroundSecondaryColor;
    | 51 | + border-bottom: 1px dotted $theme.borderColor;
    | 52 | + border-radius: 5px 5px 0px 0px;
    | 53 | + font-size: 85%;
    | 54 | + padding: 3px ${boxPadding}px;
    | 55 | + overflow: hidden;
    | 56 | + text-overflow: ellipsis;
    | 57 | + white-space: nowrap;
    | 58 | +}
    | 59 | +.gallery_attachmenttitle p {
    | 60 | + text-align: center;
    | 61 | + overflow: hidden;
    | 62 | +}
    | 63 | +
    | 64 | +.gallery_attachmentframe {
    | 65 | + padding: ${boxPadding}px;
    | 66 | + height: ${imgSize}px;
    | 67 | + overflow: hidden;
    | 68 | + position: relative;
    | 69 | +}
    | 70 | +
    | 71 | +.gallery_attachmentframe a {
    | 72 | + display: block;
    | 73 | + text-align: center;
    | 74 | +}
    | 75 | +
    | 76 | +.gallery_attachmentbox img {
    | 77 | + max-height: ${imgSize}px;
    | 78 | + max-width: ${imgSize}px;
    | 79 | + width: auto;
    | 80 | +}
    | 81 | +.gallery_attachmentframe ul {
    | 82 | + list-style-type: none;
    | 83 | + margin: 0;
    | 84 | + padding: 0;
    | 85 | +}
    | 86 | +.gallery_attachmentframe li {
    | 87 | + margin: 0.5em 0 0.5em 20px;
    | 88 | + font-size: 0.8em;
    | 89 | +}
    | 90 | +.gallery_attachmentframe .mime {
    | 91 | + margin: 0 2px 0 -20px;
    | 92 | +}
    | 93 | +.gallery_attachmentframe .mime img {
    | 94 | + vertical-align: top;
    | 95 | +}
    | 96 | +.gallery_attachmentframe li .filename {
    | 97 | + display: inline-block;
    | 98 | + font-weight: bold;
    | 99 | + width: $mathtool.sub($imgSize, 22)px;
    | 100 | + word-wrap: break-word;
    | 101 | +}
    | 102 | +
    | 103 | +/* Actions */
    | 104 | +.gallery_actions {
    | 105 | + width: auto;
    | 106 | + position: absolute;
    | 107 | + bottom: 0px;
    | 108 | + right: ${boxPadding}px;
    | 109 | +}
    | 110 | +.gallery_actions .tool {
    | 111 | + background: none no-repeat 50% transparent;
    | 112 | + cursor: pointer;
    | 113 | + display: block;
    | 114 | + float: left;
    | 115 | + height: ${actionsHeight}px;
    | 116 | + padding: 0 !important;
    | 117 | + overflow: hidden;
    | 118 | + text-indent: -1000em;
    | 119 | + opacity: 0.6;
    | 120 | + width: ${actionsWidth}px;
    | 121 | +}
    | 122 | +.gallery_actions .tool:hover {
    | 123 | + opacity: 1;
    | 124 | +}
    | 125 | +.gallery_actions .select {
    | 126 | + background-image: url("$xwiki.getSkinFile('icons/silk/tick.png')");
    | 127 | +}
    | 128 | +.gallery_actions .delete {
    | 129 | + background-image: url("$xwiki.getSkinFile('icons/silk/cross.png')");
    | 130 | +}
    | 131 | +.gallery_actions .view {
    | 132 | + background-image: url("$xwiki.getSkinFile('icons/silk/link.png')");
    | 133 | +}
    | 134 | +.gallery_actions .download {
    | 135 | + background-image: url("$xwiki.getSkinFile('icons/silk/arrow_down.png')");
    | 136 | +}
    | 137 | +/*--------------------------------------------------*/
    | 138 | +/* Upload form */
    | 139 | +.gallery_upload {
    | 140 | + ## Make this box twice as large as the others
    | 141 | + width: ${uploadBoxSize}px;
    | 142 | +}
    | 143 | +* html .gallery_upload {
    | 144 | + width: ${mathtool.add($uploadBoxSize, 2)}px;
    | 145 | +}
    | 146 | +.gallery_upload, .gallery_upload:hover {
    | 147 | + background-color: $theme.backgroundSecondaryColor;
    | 148 | +}
    | 149 | +
    | 150 | +.gallery_upload .gallery_attachmenttitle {
    | 151 | + background-position: 1px center;
    | 152 | + background-image: url("$xwiki.getSkinFile('icons/silk/bullet_add.png')");
    | 153 | + background-repeat: no-repeat;
    | 154 | + padding-left: 16px;
    | 155 | +}
    | 156 | +.gallery_upload .gallery_attachmentframe {
    | 157 | + height: auto;
    | 158 | +}
    | 159 | +.gallery_upload_input {
    | 160 | + margin-bottom: 10px;
    | 161 | +}
    | 162 | +.gallery_upload input[type="file"] {
    | 163 | + font-size: 86%;
    | 164 | + width: ${mathtool.sub($uploadBoxSize, $mathtool.mul($boxPadding, 2))}px;
    | 165 | +}
    | 166 | +.gallery_upload .buttonwrapper {
    | 167 | + margin: 0;
    | 168 | +}
    | 169 | +.gallery_upload .button,
    | 170 | +.gallery_upload label {
    | 171 | + font-size: 86%;
    | 172 | +}
    | 173 | +.gallery_upload input[type="checkbox"] {
    | 174 | + vertical-align: text-bottom;
    | 175 | +}
    | 176 | +.gallery_upload label[for="commentinput"] {
    | 177 | + margin-top: 10px;
    | 178 | +}
    | 179 | +
    | 180 | +/* ------------------------------------ */
    | 181 | +/* Buttons on top: */
    | 182 | +.gallery_actions {
    | 183 | + top: 1px;
    | 184 | + right: 3px;
    | 185 | +}
    | 186 | +.gallery_attachmenttitle p {
    | 187 | + text-align: left;
    | 188 | + margin-right: ${mathtool.add($mathtool.mul(2, $actionsWidth), $boxPadding)}px;
    | 189 | +}
    | 190 | +/* ------------------------------------ */
    | 191 | +/* Cancel button */
    | 192 | +.gallery_buttons {
    | 193 | + clear: both;
    | 194 | + margin: ${boxMargin}px;
    | 195 | +}

  Name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Attachment galery styling

  Parse content

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Use this extension

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +onDemand

XWiki.WikiMacroClass[0]
  Macro code

    ... | ... | @@ -1,0 +1,158 @@
    | 1 | +{{velocity output="false"}}
    | 2 | +$xwiki.jsfx.use('js/xwiki/widgets/modalPopup.js', true)##
    | 3 | +$xwiki.ssfx.use('js/xwiki/widgets/modalPopup.css', true)##
    | 4 | +$xwiki.jsx.use($xcontext.macro.doc.fullName)##
    | 5 | +$xwiki.ssx.use($xcontext.macro.doc.fullName)##
    | 6 | +$xwiki.jsfx.use('js/scriptaculous/builder.js')##
    | 7 | +## Integrates the optional Lightbox widget (from http://extensions.xwiki.org/xwiki/bin/view/Extension/Lightbox+Application )
    | 8 | +#if ($xwiki.exists('XWiki.Lightbox'))
    | 9 | + $xwiki.jsx.use('XWiki.Lightbox')
    | 10 | + $xwiki.ssx.use('XWiki.Lightbox')
    | 11 | +#end
    | 12 | +
    | 13 | +##
    | 14 | +## Prepare parameters
    | 15 | +##
    | 16 | +## Keep track for when we have a target document
    | 17 | +#set ($hasTargetDoc = false)
    | 18 | +#set ($targetdoc = "$!{xcontext.macro.params.targetdocname}")
    | 19 | +###
    | 20 | +## Set the targetdoc we want to save to (default to current doc if not set)
    | 21 | +#if ("$!{targetdoc}" != '')
    | 22 | + ## Check access on targetdocname
    | 23 | + #set ($targetPermView = $xwiki.hasAccessLevel('view',$xcontext.user,${targetdoc}))
    | 24 | + #set ($targetPermEdit = $xwiki.hasAccessLevel('edit',$xcontext.user,${targetdoc}))
    | 25 | + #set ($targetdoc = $xwiki.getDocument($targetdoc) )
    | 26 | + #set ($hasTargetDoc = true)
    | 27 | +#else
    | 28 | + ## The user should have edit and view permissions for the current doc
    | 29 | + #set ($targetPermView = true)
    | 30 | + #set ($targetPermEdit = true)
    | 31 | + #set($targetdoc = $doc)
    | 32 | +#end
    | 33 | +#set ($classname = "$!{xcontext.macro.params.classname}")
    | 34 | +#set ($property = "$!{xcontext.macro.params.property}")
    | 35 | +#set ($object = $mathtool.toInteger("$!{xcontext.macro.params.object}"))
    | 36 | +#if ("$!{object}" != $!{xcontext.macro.params.object})
    | 37 | + #set ($object = ${doc.getObject($classname).number})
    | 38 | + #if ("$!{object}" == '')
    | 39 | + #set ($object = 0)
    | 40 | + #end
    | 41 | +#end
    | 42 | +
    | 43 | +#if ("$!{xcontext.macro.params.displayImage}" == 'true' && $targetPermView)
    | 44 | + #set ($displayImage = true)
    | 45 | +#else
    | 46 | + #set ($displayImage = false)
    | 47 | +#end
    | 48 | +#if ($displayImage)
    | 49 | + #set ($alt = "$!{xcontext.macro.params.alternateText}")
    | 50 | + #set ($width = "$!{xcontext.macro.params.width}")
    | 51 | + #set ($height = "$!{xcontext.macro.params.height}")
    | 52 | + #set ($imageParams = '')
    | 53 | + #if ("${width}" != '')
    | 54 | + #set($imageParams = "$!{imageParams} width=${width}")
    | 55 | + #end
    | 56 | + #if ("${height}" != '')
    | 57 | + #set ($imageParams = "$!{imageParams} height=${height}")
    | 58 | + #end
    | 59 | + #set ($imageParams = "$!{imageParams} alt='${alt}'")
    | 60 | + #if ("$!{imageParams}" != '')
    | 61 | + #set ($imageParams = "||${imageParams.trim()}")
    | 62 | + #end
    | 63 | +#end
    | 64 | +#if ("$!{xcontext.macro.params.link}" == 'true')
    | 65 | + #set ($link = true)
    | 66 | +#else
    | 67 | + #set ($link = false)
    | 68 | +#end
    | 69 | +
    | 70 | +#set ($cssClass = "displayed x-attachment $!{xcontext.macro.params.cssClass}")
    | 71 | +#set ($defaultValue = "$!{xcontext.macro.params.defaultValue}")
    | 72 | +#if ($defaultValue != '')
    | 73 | + #set ($defaultReference = $services.model.resolveAttachment($defaultValue))
    | 74 | +#else
    | 75 | + #set ($defaultReference = false)
    | 76 | +#end
    | 77 | +#set ($filter = ${xcontext.macro.params.filter})
    | 78 | +
    | 79 | +#set ($buttontext = "$!{xcontext.macro.params.buttontext}")
    | 80 | +#if ($buttontext == '')
    | 81 | + #set ($buttontext = $services.localization.render('xe.attachmentSelector.selectFile'))
    | 82 | +#end
    | 83 | +
    | 84 | +#set ($savemode = "$!{xcontext.macro.params.savemode}")
    | 85 | +#if ($savemode != 'direct')
    | 86 | + #set ($savemode = 'form')
    | 87 | +#end
    | 88 | +
    | 89 | +#set ($propValue = "$!{doc.getObject($classname, $object).getProperty($property).value}")
    | 90 | +##
    | 91 | +
    | 92 | +#macro (attachmentPicker_displayAttachment $name $displayImage $withLink $forceElement)
    | 93 | + #set ($attachment = $targetdoc.getAttachment("$!{name}"))
    | 94 | + #if ("$!{name}" != '' && "$!{attachment}" != '')
    | 95 | + #set ($attachmentRef = $services.model.createAttachmentReference(${targetdoc.documentReference}, ${name}))
    | 96 | + #set ($attachmentName = $name)
    | 97 | + #else
    | 98 | + #set ($attachmentRef = $defaultReference)
    | 99 | + #if ($attachmentRef)
    | 100 | + #set ($docReference = $attachmentRef.documentReference)
    | 101 | + #else
    | 102 | + #set ($docReference = $targetdoc.documentReference)
    | 103 | + #end
    | 104 | + #set ($attachmentName = $attachmentRef.name)
    | 105 | + #set ($attachment = $xwiki.getDocument($docReference).getAttachment($attachmentName))
    | 106 | + #if (!$attachment)
    | 107 | + #set ($withLink = false)
    | 108 | + #end
    | 109 | + #end
    | 110 | + #if ($attachmentRef)
    | 111 | + #set ($attachmentResource = $services.model.serialize($attachmentRef, 'local'))
    | 112 | + #else
    | 113 | + #set ($attachmentResource = '')
    | 114 | + #end
    | 115 | + #if ($displayImage)
    | 116 | + (% class="$!{cssClass}#if (!$attachment) hidden#end" %)(((#if ("$!{attachmentResource}" != '' || $forceElement)#if($withLink)[[#end[[image:${attachmentResource}$!{imageParams}]]#if($withLink)>>attach:${attachmentResource}||rel=lightbox]]#{end}#end)))##
    | 117 | + #else
    | 118 | + (% class="$!{cssClass}" %)#if ("$!{attachmentResource}" != '' || $forceElement)#if ($withLink)[[attach:${attachmentResource}||rel=__blank]]#{else}(% class="displayed" %)#if($targetPermView)$!{attachmentName}#{else}Access Denied#{end}(% %)#{end}#end(%%)##
    | 119 | + #end
    | 120 | +#end
    | 121 | +
    | 122 | +## Display the "Choose an attachment" button if they can:
    | 123 | +## 1. Edit the current page
    | 124 | +## 2. View the target attachment page. (can be the same page)
    | 125 | +#macro (attachmentPicker_displayButton)
    | 126 | + #if ($hasEdit && $targetPermView)
    | 127 | + #set ($queryString = {
    | 128 | + 'docname': $doc.fullName,
    | 129 | + 'classname': $classname,
    | 130 | + 'property': $property,
    | 131 | + 'object': $object,
    | 132 | + 'savemode': $savemode,
    | 133 | + 'defaultValue': $defaultValue,
    | 134 | + 'filter': $filter,
    | 135 | + 'displayImage': $displayImage,
    | 136 | + 'versionSummary': $xcontext.macro.params.versionSummary.equals('true')
    | 137 | + })
    | 138 | + #if ($hasTargetDoc)
    | 139 | + #set ($queryString.targetdocname = $targetdoc.fullName)
    | 140 | + #end
    | 141 | + (% class="buttonwrapper" %)[[$buttontext>>$xcontext.macro.doc.fullName||queryString="$escapetool.url($queryString)"
    | 142 | + class="attachment-picker-start button" title="$buttontext"]](%%)##
    | 143 | + #end
    | 144 | +#end
    | 145 | +{{/velocity}}
    | 146 | +
    | 147 | +{{velocity}}
    | 148 | +#if ("${savemode}" == 'direct')
    | 149 | + (% class="attachment-picker" %)(((#attachmentPicker_displayAttachment($propValue $displayImage $link true) #attachmentPicker_displayButton())))
    | 150 | +#elseif ($xcontext.action == 'inline' || $xcontext.action == 'edit')
    | 151 | + (% class="attachment-picker" %)(((##
    | 152 | + #attachmentPicker_displayAttachment($propValue $displayImage false true) #attachmentPicker_displayButton()##
    | 153 | + {{html}}<input type="hidden" name="${classname}_${object}_${property}" value="${propValue}"/>{{/html}}##
    | 154 | + )))
    | 155 | +#else
    | 156 | + #attachmentPicker_displayAttachment($propValue $displayImage $link false)
    | 157 | +#end
    | 158 | +{{/velocity}}

  Macro content type

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No content

  Default category

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Development

  Macro description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +A control to be used for object properties of the current document that are supposed to contain the name of an attachment from the current (or target) document. Allows uploading new attachments, and deleting attachments from the target document. If no target document is specified, the current document will be used. Object properties are only saved to the current document.

  Macro id

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +attachmentSelector

  Macro name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Attachment Selector

  Supports inline mode

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Macro visibility

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Current Wiki

XWiki.WikiMacroParameterClass[0]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +The full name of the document holding the XClass that contains the property associated with this picker.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +classname

XWiki.WikiMacroParameterClass[1]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +The name of the property associated with the picker.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +property

XWiki.WikiMacroParameterClass[2]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +The identifier (number) of the object for which the property is displayed by this picker. If missing, the first instance of the class given by the parameter classname found in the document will be considered.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +object

XWiki.WikiMacroParameterClass[5]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +A CSS class for the element surrounding the property value.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +cssClass

XWiki.WikiMacroParameterClass[6]
  Parameter default value

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +form

  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +States how the property is updated. Accepted values: "form" (default) meaning that the selected value is stored in an input that will be saved via an external form; "direct" meas that the picker is responsible with updating the property value.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +savemode

XWiki.WikiMacroParameterClass[7]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Text of the button that triggers the picker. Defaults to $services.localization.render('xe.attachmentSelector.selectFile').

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +buttontext

XWiki.WikiMacroParameterClass[10]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +What attachment is displayed in view mode if the property is empty. Should either be empty or in the form of a wiki attachment reference (e.g. "attachment.txt", "Another.Document@attachment.txt").

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +defaultValue

XWiki.WikiMacroParameterClass[12]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Comma separated list of file extensions accepted by the property (to become a comma separated list of mimetypes when XWiki will use HTML5). All files are accepted if this parameter is empty.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +filter

XWiki.WikiMacroParameterClass[13]
  Parameter default value

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +false

  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +States whether images are displayed or just their name is printed like for other attachments. Possible values: true, false (default).

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +displayImage

XWiki.WikiMacroParameterClass[14]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +The width of the displayed image, only taken into account if displayImage=true.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +width

XWiki.WikiMacroParameterClass[15]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +The height of the displayed image, only taken into account if displayImage=true

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +height

XWiki.WikiMacroParameterClass[16]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +The alternate text of the displayed image, only taken into account if displayImage=true

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +alternateText

XWiki.WikiMacroParameterClass[17]
  Parameter default value

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +false

  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +States whether a link to the attachment is associated in view mode with the displayed attachment name/image. Possible values: true, false (default).

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +link

XWiki.WikiMacroParameterClass[18]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +The target document name to save/list attachments from

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +targetdocname

XWiki.WikiMacroParameterClass[19]
  Parameter default value

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +false

  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Whether to allow the user to enter a version summary that will be recorded in the history. An automatic message is used when no version summary is provided or if the version summary input is not displayed.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +versionSummary

# Applications

# Tips

Jump to any page in the wiki with Ctrl+G or Meta+G.
See more shortcuts.

# Need help?

If you need help with XWiki you can contact:

- Community Support
- Professional Support