Allow multiple files

This commit is contained in:
Ribas160 2025-06-02 14:35:54 +03:00
parent 6ff08b6884
commit 095a5be0b6
7 changed files with 256 additions and 191 deletions

View File

@ -8,6 +8,12 @@
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
*/
#attachmentPreview {
display: flex;
flex-direction: column;
align-items: center;
}
#attachmentPreview img {
max-width: 100%;
height: auto;

View File

@ -2536,11 +2536,18 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// show preview
PasteViewer.setText($message.val());
if (AttachmentViewer.hasAttachmentData()) {
const attachment = AttachmentViewer.getAttachment();
AttachmentViewer.handleBlobAttachmentPreview(
AttachmentViewer.getAttachmentPreview(),
attachment[0], attachment[1]
);
const attachmentsData = AttachmentViewer.getAttachmentsData();
attachmentsData.forEach(attachmentData => {
const mimeType = AttachmentViewer.getAttachmentMimeType(attachmentData);
AttachmentViewer.handleBlobAttachmentPreview(
AttachmentViewer.getAttachmentPreview(),
attachmentData, mimeType
);
});
AttachmentViewer.showAttachment();
}
PasteViewer.run();
@ -2925,14 +2932,12 @@ jQuery.PrivateBin = (function($, RawDeflate) {
const AttachmentViewer = (function () {
const me = {};
let $attachmentLink,
$attachmentPreview,
let $attachmentPreview,
$attachment,
attachmentData,
file,
attachmentsData = [],
files,
$fileInput,
$dragAndDropFileName,
attachmentHasPreview = false,
$dragAndDropFileNames,
$dropzone;
/**
@ -2974,26 +2979,28 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.setAttachment = function(attachmentData, fileName)
{
// skip, if attachments got disabled
if (!$attachmentLink || !$attachmentPreview) return;
if (!$attachment || !$attachmentPreview) return;
// data URI format: data:[<mimeType>][;base64],<data>
const template = Model.getTemplate('attachment');
const attachmentLink = template.find('a');
// position in data URI string of where data begins
const base64Start = attachmentData.indexOf(',') + 1;
// position in data URI string of where mimeType ends
const mimeTypeEnd = attachmentData.indexOf(';');
// extract mimeType
const mimeType = attachmentData.substring(5, mimeTypeEnd);
const mimeType = me.getAttachmentMimeType(attachmentData);
// extract data and convert to binary
const rawData = attachmentData.substring(base64Start);
const decodedData = rawData.length > 0 ? atob(rawData) : '';
let blobUrl = getBlobUrl(decodedData, mimeType);
$attachmentLink.attr('href', blobUrl);
attachmentLink.attr('href', blobUrl);
if (typeof fileName !== 'undefined') {
$attachmentLink.attr('download', fileName);
attachmentLink.attr('download', fileName);
template.append(fileName);
}
// sanitize SVG preview
@ -3008,6 +3015,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
blobUrl = getBlobUrl(sanitizedData, mimeType);
}
template.removeClass('hidden');
$attachment.append(template);
me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mimeType);
};
@ -3024,7 +3034,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$attachment.removeClass('hidden');
if (attachmentHasPreview) {
if (me.hasAttachmentPreview()) {
$attachmentPreview.removeClass('hidden');
}
};
@ -3045,11 +3055,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}
me.hideAttachment();
me.hideAttachmentPreview();
$attachmentLink.removeAttr('href');
$attachmentLink.removeAttr('download');
$attachmentLink.off('click');
$attachment.html('');
$attachmentPreview.html('');
$dragAndDropFileName.text('');
$dragAndDropFileNames.html('');
AttachmentViewer.removeAttachmentData();
};
@ -3064,8 +3072,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
me.removeAttachmentData = function()
{
file = undefined;
attachmentData = undefined;
files = undefined;
attachmentsData = [];
};
/**
@ -3076,9 +3084,21 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
me.clearDragAndDrop = function()
{
$dragAndDropFileName.text('');
$dragAndDropFileNames.html('');
};
/**
* Print file names added via drag & drop
*
* @name AttachmentViewer.printDragAndDropFileNames
* @private
* @function
* @param {array} fileNames
*/
function printDragAndDropFileNames(fileNames) {
$dragAndDropFileNames.html(fileNames.join("<br>"));
}
/**
* hides the attachment
*
@ -3107,6 +3127,18 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}
};
/**
* checks if has any attachment preview
*
* @name AttachmentViewer.hasAttachmentPreview
* @function
* @return {JQuery}
*/
me.hasAttachmentPreview = function()
{
return $attachmentPreview.children().length > 0;
}
/**
* checks if there is an attachment displayed
*
@ -3118,8 +3150,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
if (!$attachment.length) {
return false;
}
const link = $attachmentLink.prop('href');
return (typeof link !== 'undefined' && link !== '');
return [...$attachment.children()].length > 0;
};
/**
@ -3139,20 +3170,38 @@ jQuery.PrivateBin = (function($, RawDeflate) {
};
/**
* return the attachment
* return the attachments
*
* @name AttachmentViewer.getAttachment
* @name AttachmentViewer.getAttachments
* @function
* @returns {array}
*/
me.getAttachment = function()
me.getAttachments = function()
{
return [
$attachmentLink.prop('href'),
$attachmentLink.prop('download')
];
return [...$attachment.find('a')].map(link => (
[
$(link).prop('href'),
$(link).prop('download')
]
));
};
/**
* Get attachment mime type
*
* @name AttachmentViewer.getAttachmentMimeType
* @function
* @param {string} attachmentData - Base64 string
*/
me.getAttachmentMimeType = function(attachmentData)
{
// position in data URI string of where mimeType ends
const mimeTypeEnd = attachmentData.indexOf(';');
// extract mimeType
return attachmentData.substring(5, mimeTypeEnd);
}
/**
* moves the attachment link to another element
*
@ -3161,27 +3210,33 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @name AttachmentViewer.moveAttachmentTo
* @function
* @param {jQuery} $element - the wrapper/container element where this should be moved to
* @param {array} attachment - attachment data
* @param {string} label - the text to show (%s will be replaced with the file name), will automatically be translated
*/
me.moveAttachmentTo = function($element, label)
me.moveAttachmentTo = function($element, attachment, label)
{
const attachmentLink = $(document.createElement('a'))
.addClass('alert-link')
.prop('href', attachment[0])
.prop('download', attachment[1]);
// move elemement to new place
$attachmentLink.appendTo($element);
attachmentLink.appendTo($element);
// update text - ensuring no HTML is inserted into the text node
I18n._($attachmentLink, label, $attachmentLink.attr('download'));
I18n._(attachmentLink, label, attachment[1]);
};
/**
* read file data as data URL using the FileReader API
* read files data as data URL using the FileReader API
*
* @name AttachmentViewer.readFileData
* @private
* @function
* @param {object} loadedFile (optional) loaded file object
* @param {FileList[]} loadedFiles (optional) loaded files array
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader#readAsDataURL()}
*/
function readFileData(loadedFile) {
function readFileData(loadedFiles) {
if (typeof FileReader === 'undefined') {
// revert loading status…
me.hideAttachment();
@ -3190,28 +3245,35 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return;
}
const fileReader = new FileReader();
if (loadedFile === undefined) {
loadedFile = $fileInput[0].files[0];
$dragAndDropFileName.text('');
if (loadedFiles === undefined) {
loadedFiles = [...$fileInput[0].files];
me.clearDragAndDrop();
} else {
$dragAndDropFileName.text(loadedFile.name);
const fileNames = loadedFiles.map((loadedFile => loadedFile.name));
printDragAndDropFileNames(fileNames);
}
if (typeof loadedFile !== 'undefined') {
file = loadedFile;
fileReader.onload = function (event) {
const dataURL = event.target.result;
attachmentData = dataURL;
if (typeof loadedFiles !== 'undefined') {
files = loadedFiles;
loadedFiles.forEach(loadedFile => {
const fileReader = new FileReader();
if (Editor.isPreview()) {
me.handleAttachmentPreview($attachmentPreview, dataURL);
$attachmentPreview.removeClass('hidden');
}
fileReader.onload = function (event) {
const dataURL = event.target.result;
if (dataURL) {
attachmentsData.push(dataURL);
}
TopNav.highlightFileupload();
};
fileReader.readAsDataURL(loadedFile);
if (Editor.isPreview()) {
me.handleAttachmentPreview($attachmentPreview, dataURL);
$attachmentPreview.removeClass('hidden');
}
TopNav.highlightFileupload();
};
fileReader.readAsDataURL(loadedFile);
});
} else {
me.removeAttachmentData();
}
@ -3227,16 +3289,17 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @argument {string} mime type
*/
me.handleBlobAttachmentPreview = function ($targetElement, blobUrl, mimeType) {
if (blobUrl) {
attachmentHasPreview = true;
const alreadyIncludesCurrentAttachment = $targetElement.find(`[src='${blobUrl}']`).length > 0;
if (blobUrl && !alreadyIncludesCurrentAttachment) {
if (mimeType.match(/^image\//i)) {
$targetElement.html(
$targetElement.append(
$(document.createElement('img'))
.attr('src', blobUrl)
.attr('class', 'img-thumbnail')
);
} else if (mimeType.match(/^video\//i)) {
$targetElement.html(
$targetElement.append(
$(document.createElement('video'))
.attr('controls', 'true')
.attr('autoplay', 'true')
@ -3247,7 +3310,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
.attr('src', blobUrl))
);
} else if (mimeType.match(/^audio\//i)) {
$targetElement.html(
$targetElement.append(
$(document.createElement('audio'))
.attr('controls', 'true')
.attr('autoplay', 'true')
@ -3260,15 +3323,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// Fallback for browsers, that don't support the vh unit
const clientHeight = $(window).height();
$targetElement.html(
$targetElement.append(
$(document.createElement('embed'))
.attr('src', blobUrl)
.attr('type', 'application/pdf')
.attr('class', 'pdfPreview')
.css('height', clientHeight)
);
} else {
attachmentHasPreview = false;
}
}
};
@ -3301,14 +3362,14 @@ jQuery.PrivateBin = (function($, RawDeflate) {
}
if ($fileInput) {
const file = evt.dataTransfer.files[0];
const files = [...evt.dataTransfer.files];
//Clear the file input:
$fileInput.wrap('<form>').closest('form').get(0).reset();
$fileInput.unwrap();
//Only works in Chrome:
//fileInput[0].files = e.dataTransfer.files;
readFileData(file);
readFileData(files);
}
};
@ -3362,23 +3423,12 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/**
* getter for attachment data
*
* @name AttachmentViewer.getAttachmentData
* @name AttachmentViewer.getAttachmentsData
* @function
* @return {jQuery}
* @return {string[]}
*/
me.getAttachmentData = function () {
return attachmentData;
};
/**
* getter for attachment link
*
* @name AttachmentViewer.getAttachmentLink
* @function
* @return {jQuery}
*/
me.getAttachmentLink = function () {
return $attachmentLink;
me.getAttachmentsData = function () {
return attachmentsData;
};
/**
@ -3393,14 +3443,14 @@ jQuery.PrivateBin = (function($, RawDeflate) {
};
/**
* getter for file data, returns the file contents
* getter for files data, returns the file list
*
* @name AttachmentViewer.getFile
* @name AttachmentViewer.getFiles
* @function
* @return {string}
* @return {FileList[]}
*/
me.getFile = function () {
return file;
me.getFiles = function () {
return files;
};
/**
@ -3414,9 +3464,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.init = function()
{
$attachment = $('#attachment');
$dragAndDropFileName = $('#dragAndDropFileName');
$dragAndDropFileNames = $('#dragAndDropFileName');
$dropzone = $('#dropzone');
$attachmentLink = $('#attachment a') || $('<a>');
if($attachment.length) {
$attachmentPreview = $('#attachmentPreview');
@ -5135,7 +5184,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
const plainText = Editor.getText(),
format = PasteViewer.getFormat(),
// the methods may return different values if no files are attached (null, undefined or false)
files = TopNav.getFileList() || AttachmentViewer.getFile() || AttachmentViewer.hasAttachment();
files = TopNav.getFileList() || AttachmentViewer.getFiles() || AttachmentViewer.hasAttachment();
// do not send if there is no data
if (plainText.length === 0 && !files) {
@ -5175,62 +5224,64 @@ jQuery.PrivateBin = (function($, RawDeflate) {
PasteViewer.setFormat(format);
// prepare cypher message
let file = AttachmentViewer.getAttachmentData(),
let attachmentsData = AttachmentViewer.getAttachmentsData(),
cipherMessage = {
'paste': plainText
};
if (typeof file !== 'undefined' && file !== null) {
cipherMessage['attachment'] = file;
cipherMessage['attachment_name'] = AttachmentViewer.getFile().name;
if (attachmentsData.length) {
cipherMessage['attachment'] = attachmentsData;
cipherMessage['attachment_name'] = AttachmentViewer.getFiles().map((fileInfo => fileInfo.name));
} else if (AttachmentViewer.hasAttachment()) {
// fall back to cloned part
let attachment = AttachmentViewer.getAttachment();
cipherMessage['attachment'] = attachment[0];
cipherMessage['attachment_name'] = attachment[1];
let attachments = AttachmentViewer.getAttachments();
cipherMessage['attachment'] = attachments.map(attachment => attachment[0]);
cipherMessage['attachment_name'] = attachments.map(attachment => attachment[1]);
// we need to retrieve data from blob if browser already parsed it in memory
if (typeof attachment[0] === 'string' && attachment[0].startsWith('blob:')) {
Alert.showStatus(
[
'Retrieving cloned file \'%s\' from memory...',
attachment[1]
],
'copy'
);
try {
const blobData = await $.ajax({
type: 'GET',
url: `${attachment[0]}`,
processData: false,
timeout: 10000,
xhrFields: {
withCredentials: false,
responseType: 'blob'
}
});
if (blobData instanceof window.Blob) {
const fileReading = new Promise(function(resolve, reject) {
const fileReader = new FileReader();
fileReader.onload = function (event) {
resolve(event.target.result);
};
fileReader.onerror = function (error) {
reject(error);
cipherMessage['attachment'] = await Promise.all(cipherMessage['attachment'].map(async (attachment) => {
// we need to retrieve data from blob if browser already parsed it in memory
if (typeof attachment === 'string' && attachment.startsWith('blob:')) {
Alert.showStatus(
[
'Retrieving cloned file \'%s\' from memory...',
attachment[1]
],
'copy'
);
try {
const blobData = await $.ajax({
type: 'GET',
url: `${attachment}`,
processData: false,
timeout: 10000,
xhrFields: {
withCredentials: false,
responseType: 'blob'
}
fileReader.readAsDataURL(blobData);
});
cipherMessage['attachment'] = await fileReading;
} else {
const error = 'Cannot process attachment data.';
Alert.showError(error);
throw new TypeError(error);
if (blobData instanceof window.Blob) {
const fileReading = new Promise(function(resolve, reject) {
const fileReader = new FileReader();
fileReader.onload = function (event) {
resolve(event.target.result);
};
fileReader.onerror = function (error) {
reject(error);
}
fileReader.readAsDataURL(blobData);
});
return await fileReading;
} else {
const error = 'Cannot process attachment data.';
Alert.showError(error);
throw new TypeError(error);
}
} catch (error) {
Alert.showError('Cannot retrieve attachment.');
throw error;
}
} catch (error) {
console.error(error);
Alert.showError('Cannot retrieve attachment.');
throw error;
}
}
}));
}
// encrypt message
@ -5325,7 +5376,15 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// version 2 paste
const pasteMessage = JSON.parse(pastePlain);
if (pasteMessage.hasOwnProperty('attachment') && pasteMessage.hasOwnProperty('attachment_name')) {
AttachmentViewer.setAttachment(pasteMessage.attachment, pasteMessage.attachment_name);
if (Array.isArray(pasteMessage.attachment) && Array.isArray(pasteMessage.attachment_name)) {
pasteMessage.attachment.forEach((attachment, key) => {
const attachment_name = pasteMessage.attachment_name[key];
AttachmentViewer.setAttachment(attachment, attachment_name);
});
} else {
// Continue to process attachment parameters as strings to ensure backward compatibility
AttachmentViewer.setAttachment(pasteMessage.attachment, pasteMessage.attachment_name);
}
AttachmentViewer.showAttachment();
}
pastePlain = pasteMessage.paste;
@ -5808,10 +5867,14 @@ jQuery.PrivateBin = (function($, RawDeflate) {
history.pushState({type: 'clone'}, document.title, Helper.baseUri());
if (AttachmentViewer.hasAttachment()) {
AttachmentViewer.moveAttachmentTo(
TopNav.getCustomAttachment(),
'Cloned: \'%s\''
);
const attachments = AttachmentViewer.getAttachments();
attachments.forEach(attachment => {
AttachmentViewer.moveAttachmentTo(
TopNav.getCustomAttachment(),
attachment,
'Cloned: \'%s\''
);
});
TopNav.hideFileSelector();
AttachmentViewer.hideAttachment();
// NOTE: it also looks nice without removing the attachment
@ -5819,12 +5882,12 @@ jQuery.PrivateBin = (function($, RawDeflate) {
AttachmentViewer.hideAttachmentPreview();
TopNav.showCustomAttachment();
// show another status message to make the user aware that the
// file was cloned too!
// show another status messages to make the user aware that the
// files were cloned too!
Alert.showStatus(
[
'The cloned file \'%s\' was attached to this paste.',
AttachmentViewer.getAttachment()[1]
attachments.map(attachment => attachment[1]).join(', '),
],
'copy'
);

View File

@ -27,11 +27,14 @@ describe('AttachmentViewer', function () {
prefix = prefix.replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%');
$('body').html(
'<div id="attachment" role="alert" class="hidden alert ' +
'alert-info"><span class="glyphicon glyphicon-download-' +
'alt" aria-hidden="true"></span> <a class="alert-link">' +
'Download attachment</a></div><div id="attachmentPrevie' +
'w" class="hidden"></div>'
'<div id="attachmentPreview" class="col-md-12 text-center hidden"></div>' +
'<div id="attachment" class="hidden"></div>' +
'<div id="templates">' +
'<div id="attachmenttemplate" role="alert" class="attachment hidden alert alert-info">' +
'<span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span>' +
'<a class="alert-link">Download attachment</a>' +
'</div>' +
'</div>'
);
// mock createObjectURL for jsDOM
if (typeof window.URL.createObjectURL === 'undefined') {
@ -44,9 +47,12 @@ describe('AttachmentViewer', function () {
)
}
$.PrivateBin.AttachmentViewer.init();
$.PrivateBin.Model.init();
results.push(
!$.PrivateBin.AttachmentViewer.hasAttachment() &&
$('#attachment').hasClass('hidden') &&
$('#attachment').children().length === 0 &&
$('#attachmenttemplate').hasClass('hidden') &&
$('#attachmentPreview').hasClass('hidden')
);
global.atob = common.atob;
@ -55,19 +61,21 @@ describe('AttachmentViewer', function () {
} else {
$.PrivateBin.AttachmentViewer.setAttachment(data);
}
// beyond this point we will get the blob URL instead of the data
// // beyond this point we will get the blob URL instead of the data
data = window.URL.createObjectURL(data);
const attachment = $.PrivateBin.AttachmentViewer.getAttachment();
const attachment = $.PrivateBin.AttachmentViewer.getAttachments();
results.push(
$.PrivateBin.AttachmentViewer.hasAttachment() &&
$('#attachment').hasClass('hidden') &&
$('#attachment').children().length > 0 &&
$('#attachmentPreview').hasClass('hidden') &&
attachment[0] === data &&
attachment[1] === filename
attachment[0][0] === data &&
attachment[0][1] === filename
);
$.PrivateBin.AttachmentViewer.showAttachment();
results.push(
!$('#attachment').hasClass('hidden') &&
$('#attachment').children().length > 0 &&
(previewSupported ? !$('#attachmentPreview').hasClass('hidden') : $('#attachmentPreview').hasClass('hidden'))
);
$.PrivateBin.AttachmentViewer.hideAttachment();
@ -85,7 +93,7 @@ describe('AttachmentViewer', function () {
(previewSupported ? !$('#attachmentPreview').hasClass('hidden') : $('#attachmentPreview').hasClass('hidden'))
);
let element = $('<div>');
$.PrivateBin.AttachmentViewer.moveAttachmentTo(element, prefix + '%s' + postfix);
$.PrivateBin.AttachmentViewer.moveAttachmentTo(element, attachment[0], prefix + '%s' + postfix);
// messageIDs with links get a relaxed treatment
if (prefix.indexOf('<a') === -1 && postfix.indexOf('<a') === -1) {
result = $('<textarea>').text((prefix + filename + postfix)).text();
@ -99,16 +107,17 @@ describe('AttachmentViewer', function () {
}
if (filename.length) {
results.push(
element.children()[0].href === data &&
element.children()[0].getAttribute('download') === filename &&
element.children()[0].text === result
element.find('a')[0].href === data &&
element.find('a')[0].getAttribute('download') === filename &&
element.find('a')[0].text === result
);
} else {
results.push(element.children()[0].href === data);
results.push(element.find('a')[0].href === data);
}
$.PrivateBin.AttachmentViewer.removeAttachment();
results.push(
$('#attachment').hasClass('hidden') &&
$('#attachment').children().length === 0 &&
$('#attachmentPreview').hasClass('hidden')
);
clean();

View File

@ -119,7 +119,7 @@ class Configuration
'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==',
'js/legacy.js' => 'sha512-UxW/TOZKon83n6dk/09GsYKIyeO5LeBHokxyIq+r7KFS5KMBeIB/EM7NrkVYIezwZBaovnyNtY2d9tKFicRlXg==',
'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
'js/privatebin.js' => 'sha512-QkOUM8rg4MI60YRwHqWmayBzCdf/e3XnbHtrX17h2nn0EcyOQNhtSq8a0dXR1hoQFHFfF+9PiT73nZ6qoogjQA==',
'js/privatebin.js' => 'm6RrsOsz4RgIWXDzgRghQDx6aegFCpkpqURwhfXwE/rNWhe/1rPJaLR+FXII82iTWo0n9JCzSbqrDqkYVPI50w==',
'js/purify-3.2.5.js' => 'sha512-eLlLLL/zYuf5JuG0x4WQm687MToqOGP9cDQHIdmOy1ZpjiY4J48BBcOM7DtZheKk1UogW920+9RslWYB4KGuuA==',
'js/rawinflate-0.3.js' => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==',
'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',

View File

@ -386,7 +386,7 @@ if ($FILEUPLOAD) :
<ul class="dropdown-menu">
<li id="filewrap">
<div>
<input type="file" id="file" name="file" />
<input type="file" id="file" name="file" multiple />
</div>
<div id="dragAndDropFileName" class="dragAndDropFile"><?php echo I18n::_('alternatively drag & drop a file or paste an image from the clipboard'); ?></div>
</li>
@ -505,10 +505,7 @@ endif;
<?php
if ($FILEUPLOAD) :
?>
<div id="attachment" role="alert" class="hidden alert alert-info">
<span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span>
<a class="alert-link"><?php echo I18n::_('Download attachment'); ?></a>
</div>
<div id="attachment" class="hidden"></div>
<?php
endif;
?>
@ -656,9 +653,6 @@ endif;
</div>
</footer>
</main>
<?php
if ($DISCUSSION) :
?>
<div id="serverdata" class="hidden" aria-hidden="true">
<div id="templates">
<article id="commenttemplate" class="comment">
@ -680,12 +674,13 @@ if ($DISCUSSION) :
</div>
<button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button>
</div>
<div id="attachmenttemplate" role="alert" class="attachment hidden alert alert-info">
<span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span>
<a class="alert-link"><?php echo I18n::_('Download attachment'); ?></a>
</div>
</div>
</div>
<?php
endif;
?>
<?php
if ($FILEUPLOAD) :
?>
<div id="dropzone" class="hidden" tabindex="-1" aria-hidden="true"></div>

View File

@ -261,11 +261,11 @@ if ($FILEUPLOAD) :
<ul class="dropdown-menu px-2">
<li id="filewrap">
<div>
<input type="file" id="file" name="file" class="form-control" />
<input type="file" id="file" name="file" class="form-control" multiple />
</div>
<div id="dragAndDropFileName" class="dragAndDropFile"><?php echo I18n::_('alternatively drag & drop a file or paste an image from the clipboard'); ?></div>
</li>
<li id="customattachment" class="hidden"></li>
<li id="customattachment" class="hidden d-flex flex-column px-3"></li>
<li>
<a id="fileremovebutton" href="#" class="dropdown-item">
<?php echo I18n::_('Remove attachment'), PHP_EOL; ?>
@ -370,10 +370,7 @@ endif;
<?php
if ($FILEUPLOAD) :
?>
<div id="attachment" role="alert" class="hidden alert alert-info">
<svg width="16" height="16" fill="currentColor" aria-hidden="true"><use href="img/bootstrap-icons.svg#download" /></svg>
<a class="alert-link"><?php echo I18n::_('Download attachment'); ?></a>
</div>
<div id="attachment" class="hidden"></div>
<?php
endif;
?>
@ -514,9 +511,6 @@ endif;
</p>
</div>
</footer>
<?php
if ($DISCUSSION) :
?>
<div id="serverdata" class="hidden" aria-hidden="true">
<div id="templates">
<article id="commenttemplate" class="comment px-2 pb-3">
@ -538,12 +532,13 @@ if ($DISCUSSION) :
</div>
<button id="replybutton" class="btn btn-secondary btn-sm"><?php echo I18n::_('Post comment'); ?></button>
</div>
<div id="attachmenttemplate" role="alert" class="hidden alert alert-info">
<svg width="16" height="16" fill="currentColor" aria-hidden="true"><use href="img/bootstrap-icons.svg#download" /></svg>
<a class="alert-link"><?php echo I18n::_('Download attachment'); ?></a>
</div>
</div>
</div>
<?php
endif;
?>
<?php
if ($FILEUPLOAD) :
?>
<div id="dropzone" class="hidden" tabindex="-1" aria-hidden="true"></div>

View File

@ -265,10 +265,10 @@ endif;
<?php
if ($FILEUPLOAD):
?>
<div id="attachment" class="hidden"><a><?php echo I18n::_('Download attachment'); ?></a></div>
<div id="attachment" class="hidden"></div>
<div id="attach" class="hidden">
<span id="clonedfile" class="hidden"><?php echo I18n::_('Cloned file attached.'); ?></span>
<span id="filewrap"><?php echo I18n::_('Attach a file'); ?>: <input type="file" id="file" name="file" /></span>
<span id="filewrap"><?php echo I18n::_('Attach a file'); ?>: <input type="file" id="file" name="file" multiple /></span>
<span id="dragAndDropFileName" class="dragAndDropFile"><?php echo I18n::_('alternatively drag & drop a file or paste an image from the clipboard'); ?></span>
<button id="fileremovebutton"><?php echo I18n::_('Remove attachment'); ?></button>
</div>
@ -297,9 +297,6 @@ endif;
<div id="commentcontainer"></div>
</div>
</section>
<?php
if ($DISCUSSION):
?>
<div id="serverdata" class="hidden" aria-hidden="true">
<div id="templates">
<article id="commenttemplate" class="comment">
@ -321,12 +318,12 @@ if ($DISCUSSION):
</div>
<button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button>
</div>
<div id="attachmenttemplate" class="attachment">
<a><?php echo I18n::_('Download attachment'); ?></a>
</div>
</div>
</div>
<?php
endif;
?>
<?php
if ($FILEUPLOAD):
?>
<div id="dropzone" class="hidden" tabindex="-1" aria-hidden="true"></div>