Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#template('display_macros.vm')
#initRequiredSkinExtensions()
$xwiki.jsfx.use('uicomponents/widgets/upload.js', {'forceSkinAction': true, 'language': ${xcontext.locale}})
$xwiki.ssfx.use('uicomponents/widgets/upload.css', true)
$xwiki.ssfx.use('uicomponents/widgets/upload.css')
$xwiki.jsfx.use('js/xwiki/viewers/attachments.js', {'forceSkinAction': true, 'language': ${xcontext.locale}})
$xwiki.ssfx.use('js/xwiki/viewers/attachments.css', true)
###
Expand Down Expand Up @@ -55,7 +55,7 @@ $xwiki.ssfx.use('js/xwiki/viewers/attachments.css', true)
<dd><input type="text" id="xwikiuploadcomment" name="comment" maxlength="1023"></dd>
</dl>
#end
<label class="sr-only" for="xwikiuploadfile">$services.localization.render('core.viewers.attachments.upload.file')</label><input id="xwikiuploadfile" type="file" name="filepath" size="40" class="uploadFileInput noitems" data-max-file-size="$!escapetool.xml($xwiki.getSpacePreference('upload_maxsize'))" />
<label class="sr-only" for="xwikiuploadfile">$services.localization.render('core.viewers.attachments.upload.file')</label><input id="xwikiuploadfile" type="file" name="filepath" size="40" class="uploadFileInput" data-max-file-size="$!escapetool.xml($xwiki.getSpacePreference('upload_maxsize'))" />
</div>
<div>
<span class="buttonwrapper"><input type="submit" value="$services.localization.render('core.viewers.attachments.upload.submit')" class="button btn btn-primary"/></span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
#if("$!doc.space" != "") space-${escapetool.xml($doc.space.replaceAll(' ', '_'))}#end
#if($showLeftPanels == "0") hideleft#end#if($showRightPanels == "0")hideright#end##Take care not to add any space between these two classes, they should combine in only one class if both are present.
#if($hidecolumns && ($!hidecolumns == 1)) hidelefthideright#end
#if($isAdvancedUser) advanced-user#end##
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds interesting, but imo it deserves a forum proposal since it's going to become an API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll propose all the classes on this node as API. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#if($showLeftPanels != "0" && $showRightPanels != "0" && $!hidecolumns != 1) content#end">
<div id="xwikimaincontainer">
<div id="xwikimaincontainerinner">
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@
"font-family-serif": 'Georgia, "Times New Roman", Times, serif',
"font-family-monospace": 'Georgia, "Times New Roman", Times, serif',
"font-size-base": "1rem",
"font-size-small": ".85rem",
"grid-gutter-width": "30px",
"input-bg" : "#fff",
"input-border-focus": "#66afe9",
Expand All @@ -233,7 +234,8 @@
"list-group-link-hover-color": "var(--list-group-link-color)",
"navbar-height" : "50px",
"table-bg-hover": "#f5f5f5",
"table-border-color": "#ddd"
"table-border-color": "#ddd",
"state-danger-text": "#a94442"
})

############################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1440,10 +1440,11 @@ core.widgets.html5upload.error.aborted=The upload of {0} has been canceled
core.widgets.html5upload.status.finishing=Waiting for server confirmation for {0}...
core.widgets.html5upload.status.finished=Attachment uploaded: {0} ({1})
core.widgets.html5upload.status.icon.inprogress=Upload in progress
core.widgets.html5upload.status.icon.done=Upload finished successfully
core.widgets.html5upload.status.icon.done=Upload complete
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update translation values is not a good practice, see https://dev.xwiki.org/xwiki/bin/view/Community/L10N/Conventions/#HUpdatingtranslations
You need to introduce new keys and possibly deprecate the current ones if they are not used anymore.

Copy link
Contributor Author

@Sereza7 Sereza7 Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the default value of two others to take the formulation picked in the redesign. Semantically there's no change so I figured out this was okay to do.

from the PR opening message

The old value is correct. It's meant to be used in pretty much the same place with the same purpose. IMO it's not a major change, it's pretty much just a rewording so that those values are a bit more consistent, at least in english.

core.widgets.html5upload.status.icon.canceled=Upload canceled
core.widgets.html5upload.status.icon.error=Upload ended in an error
core.widgets.html5upload.status.icon.error=Upload error
core.widgets.html5upload.hideStatus=Hide upload status
core.widgets.html5upload.remaining=Remaining

### Watchlist (1.2M2)
watchlist=Watchlist
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
'forceSkinAction': true,
'language': $xcontext.locale
}))
#set ($discard = $xwiki.ssfx.use('uicomponents/widgets/upload.css', true))
#set ($discard = $xwiki.ssfx.use('uicomponents/widgets/upload.css'))
<form action="$uploadDoc.getURL('upload')" enctype="multipart/form-data" method="post"
class="extension-history-source-upload">
<div class="hidden">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
$xwiki.jsfx.use('js/xwiki/importer/import.js', true)##
$xwiki.ssfx.use('js/xwiki/importer/import.css', true)##
$xwiki.jsfx.use('uicomponents/widgets/upload.js', {'forceSkinAction': true, 'language': ${xcontext.locale}})##
$xwiki.ssfx.use('uicomponents/widgets/upload.css', true)##
$xwiki.ssfx.use('uicomponents/widgets/upload.css')##
<div id="import" class="row">
<div id="packagelist" class="col-xs-12 col-sm-6 col-md-6">
<div class="legend">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,156 +42,154 @@
(function(l10n) {
"use strict";

window.XWiki = window.XWiki || {};
var viewers = XWiki.viewers = XWiki.viewers || {};
/**
* Enhancements for the Attachment upload area: adding and removing file fields, resetting with the Cancel button,
* preventing submit if no files are selected.
*/
viewers.Attachments = Class.create({
/** Counter for creating distinct upload field names. */
counter : 1,
/** Constructor. Adds all the JS improvements of the Attachment area. */
initialize : function() {
// Initialize the event listener to prepare the form when the Attachments tab is loaded or reloaded, or prepare the form straight away.
// prepareForm won't be called twice as it is skipped once #attachform exists.
this.addTabLoadListener();
this.prepareForm();
},
/** Enhance the upload form with JS behaviors. */
prepareForm : function() {
if (!$("attachform")) {
return;
require(['jquery', 'xwiki-upload', 'xwiki-events-bridge'], function($, FileUploader) {
window.XWiki = window.XWiki || {};
var viewers = XWiki.viewers = XWiki.viewers || {};
/**
* Enhancements for the Attachment upload area: adding and removing file fields, resetting with the Cancel button,
* preventing submit if no files are selected.
*/

viewers.Attachments = class {
/** Counter for creating distinct upload field names. */
static counter = 1;
/** Constructor. Adds all the JS improvements of the Attachment area. */
constructor() {
// Initialize the event listener to prepare the form when the Attachments tab is loaded or reloaded, or prepare the form straight away.
// prepareForm won't be called twice as it is skipped once #attachform exists.
this.addTabLoadListener();
this.prepareForm();
}
this.form = $("attachform").up("form");
this.defaultFileDiv = this.form.down("input[type='file']").up("div");
this.inputSize = this.form.down("input[type='file']").size;

var html5Uploader = this.attachHTML5Uploader(this.form.down("input[type='file']"));
if (html5Uploader) {
html5Uploader.hideFormButtons();
} else {
this.addInitialRemoveButton();
this.addAddButton();
this.resetOnCancel();
/** Enhance the upload form with JS behaviors. */
prepareForm() {
if (!$("attachform")) return;
this.form = $("#attachform").parents("form");
let fileInput = this.form.find("input[type='file']");
this.defaultFileDiv = fileInput.parent("div");
this.inputSize = fileInput.size;

let html5Uploader = this.attachHTML5Uploader(fileInput);
if (html5Uploader) {
html5Uploader.hideFormButtons();
} else {
this.addInitialRemoveButton();
this.addAddButton();
this.resetOnCancel();
}
this.blockEmptySubmit();
}
/** If available in the current browser, enable HTML5 upload for a given file input */
attachHTML5Uploader(input) {
if (typeof(FileUploader) != 'undefined') {
input.multiple = true;
// Since the attachments liveData is refreshed on file upload, we skip updating the attachments container.
return new FileUploader(input[0], {
'responseContainer' : document.createElement('div'),
'responseURL' : '',
'maxFilesize' : parseInt(input.attr('data-max-file-size'))
});
}
return false;
}
/** By default the form contains one upload field. Add a "remove" button for this one, too. */
addInitialRemoveButton() {
this.defaultFileDiv.appendChild(this.createRemoveButton());
}
/** Add an "Add another file" button below the file fields. */
addAddButton() {
let addButton = $(document.createElement("input"));
addButton.attr('type', 'button');
addButton.attr('value', l10n['core.viewers.attachments.upload.addFileInput']);
addButton.attr('class', "attachmentActionButton add-file-input");
this.addDiv = document.createElement("div");
this.addDiv.append(addButton);
addButton.observe('click', this.addField.bindAsEventListener(this));
this.defaultFileDiv.up().insertBefore(this.addDiv, this.defaultFileDiv.next());
}
this.blockEmptySubmit();
},
/** If available in the current browser, enable HTML5 upload for a given file input */
attachHTML5Uploader : function(input) {
if (typeof(XWiki.FileUploader) != 'undefined') {
input.multiple = true;
// Since the attachments liveData is refreshed on file upload, we skip updating the attachments container.
return new XWiki.FileUploader(input, {
'responseContainer' : document.createElement('div'),
'responseURL' : '',
'maxFilesize' : parseInt(input.readAttribute('data-max-file-size'))
/** Add a submit listener that prevents submitting the form if no file was specified. */
blockEmptySubmit() {
this.form[0].observe('submit', this.onSubmit.bindAsEventListener(this));
}
/** Add a reset listener that resets the number of file fields to 1. */
resetOnCancel() {
this.form[0].observe('reset', this.onReset.bindAsEventListener(this));
this.form.down('.cancel')[0].observe('click', this.onReset.bindAsEventListener(this));
}
/** Creates and inserts a new file input field. */
addField(event) {
let fileInput = document.createElement("input", {
type: "file",
name: "filepath_" + this.counter,
size: this.inputSize,
className: "uploadFileInput"
});
// For the moment, specifying a different name is not used anymore.
var filenameInput = document.createElement("input", {type: "hidden", name : "filename_" + this.counter});
var removeButton = this.createRemoveButton();
var containerDiv = document.createElement("div", {'class' : 'fileupload-field'});
containerDiv.append(filenameInput, fileInput, removeButton);
this.addDiv.parentNode.insertBefore(containerDiv, this.addDiv);
// Remove the focus border from the button
event.target.blur();
this.counter++;
}
return false;
},
/** By default the form contains one upload field. Add a "remove" button for this one, too. */
addInitialRemoveButton : function() {
this.defaultFileDiv.appendChild(this.createRemoveButton());
},
/** Add an "Add another file" button below the file fields. */
addAddButton : function() {
var addButton = new Element("input", {
type: "button",
value: l10n['core.viewers.attachments.upload.addFileInput'],
className: "attachmentActionButton add-file-input"
});
this.addDiv = new Element("div");
this.addDiv.appendChild(addButton);
Event.observe(addButton, 'click', this.addField.bindAsEventListener(this));
this.defaultFileDiv.up().insertBefore(this.addDiv, this.defaultFileDiv.next());
},
/** Add a submit listener that prevents submitting the form if no file was specified. */
blockEmptySubmit : function() {
Event.observe(this.form, 'submit', this.onSubmit.bindAsEventListener(this));
},
/** Add a reset listener that resets the number of file fields to 1. */
resetOnCancel : function() {
Event.observe(this.form, 'reset', this.onReset.bindAsEventListener(this));
Event.observe(this.form.down('.cancel'), 'click', this.onReset.bindAsEventListener(this));
},
/** Creates and inserts a new file input field. */
addField : function(event) {
var fileInput = new Element("input", {
type: "file",
name: "filepath_" + this.counter,
size: this.inputSize,
className: "uploadFileInput"
});
// For the moment, specifying a different name is not used anymore.
var filenameInput = new Element("input", {type: "hidden", name : "filename_" + this.counter});
var removeButton = this.createRemoveButton();
var containerDiv = new Element("div", {'class' : 'fileupload-field'});
containerDiv.insert(filenameInput).insert(fileInput).insert(removeButton);
this.addDiv.parentNode.insertBefore(containerDiv, this.addDiv);
// Remove the focus border from the button
event.element().blur();
this.counter++;
},
/** Remove a file field when pressing the corresponding "Remove" button. */
removeField : function(event) {
event.element().up("div").remove();
},
/** Create a remove button that triggers {@link #removeField} when clicked. */
createRemoveButton : function() {
var removeButton = new Element("input", {
type: "button",
value: l10n['core.viewers.attachments.upload.removeFileInput'],
title: l10n['core.viewers.attachments.upload.removeFileInput.title'],
className: "attachmentActionButton remove-file-input"
});
Event.observe(removeButton, "click", this.removeField.bindAsEventListener(this));
return removeButton;
},
/** Form submit listener. It checks that at least one file item contains a filename. If not, cancel the submission. */
onSubmit : function(event) {
var hasFiles = false;
this.form.getInputs("file").each(function(item) {
if(item.value != '') {
hasFiles = true;
}
});
if(!hasFiles) {
event.stop();
/** Remove a file field when pressing the corresponding "Remove" button. */
removeField(event) {
event.target.up("div").remove();
}
/** Create a remove button that triggers {@link #removeField} when clicked. */
createRemoveButton() {
var removeButton = new Element("input", {
type: "button",
value: l10n['core.viewers.attachments.upload.removeFileInput'],
title: l10n['core.viewers.attachments.upload.removeFileInput.title'],
className: "attachmentActionButton remove-file-input"
});
Event.observe(removeButton, "click", this.removeField.bindAsEventListener(this));
return removeButton;
}
},
/** Form reset listener. It resets the number of file fields to just one. */
onReset : function(event) {
if (event) {
event.stop();
/** Form submit listener. It checks that at least one file item contains a filename. If not, cancel the submission. */
onSubmit(event) {
let hasFiles = false;
this.form.getInputs("file").each(function(item) {
if(item.value !== '') {
hasFiles = true;
}
});
if(!hasFiles) {
event.stop();
}
}
this.form.getInputs("file").each(function(item) {
item.up().remove();
});
this.counter = 1;
this.addField(event);
},
/**
* Registers a listener that watches for the insertion of the Attachments tab and triggers the form enhancement.
*/
addTabLoadListener : function(event) {
var listener = function(event) {
if (event.memo.id == 'Attachments') {
this.prepareForm();
/** Form reset listener. It resets the number of file fields to just one. */
onReset(event) {
if (event) {
event.stop();
}
}.bindAsEventListener(this);
document.observe("xwiki:docextra:loaded", listener);
}
});

// When the document is loaded, trigger the attachment form enhancements.
(XWiki.domIsLoaded && new viewers.Attachments())
|| document.observe("xwiki:dom:loaded", function() { new viewers.Attachments(); });

this.form.getInputs("file").each(function(item) {
item.up().remove();
});
this.counter = 1;
this.addField(event);
}
/**
* Registers a listener that watches for the insertion of the Attachments tab and triggers the form enhancement.
*/
addTabLoadListener() {
let listener = function(event) {
if (event.memo.id === 'Attachments') {
this.prepareForm();
}
}.bindAsEventListener(this);
document.observe("xwiki:docextra:loaded", listener);
}
};

// When the document is loaded, trigger the attachment form enhancements.
(XWiki.domIsLoaded && new viewers.Attachments())
|| document.observe("xwiki:dom:loaded", function() { new viewers.Attachments(); });
/**
* Delete attachments from AttachmentsTab.
*/
require(['jquery', 'xwiki-events-bridge'], function($) {
/**
* Event on deleteAttachment button.
*/
Expand Down Expand Up @@ -260,9 +258,7 @@ require(['jquery', 'xwiki-events-bridge'], function($) {
*/
var updateAttachmentsNumber = function(attachmentsNumber) {
var itemCount = $('#Attachmentstab').find('.itemCount');
if (itemCount) {
itemCount.text(l10n['docextra.extranb'].replace("__number__", attachmentsNumber));
};
itemCount?.text(l10n['docextra.extranb'].replace("__number__", attachmentsNumber));
var tmAttachments = $('#tmAttachments');
if (tmAttachments.length) {
// Calling normalize() because a text node needs to be modified and so all consecutive text nodes are merged.
Expand Down
Loading