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 * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
*/ */
#attachmentPreview {
display: flex;
flex-direction: column;
align-items: center;
}
#attachmentPreview img { #attachmentPreview img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;

View File

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

View File

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

View File

@ -119,7 +119,7 @@ class Configuration
'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==', 'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==',
'js/legacy.js' => 'sha512-UxW/TOZKon83n6dk/09GsYKIyeO5LeBHokxyIq+r7KFS5KMBeIB/EM7NrkVYIezwZBaovnyNtY2d9tKFicRlXg==', 'js/legacy.js' => 'sha512-UxW/TOZKon83n6dk/09GsYKIyeO5LeBHokxyIq+r7KFS5KMBeIB/EM7NrkVYIezwZBaovnyNtY2d9tKFicRlXg==',
'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==', '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/purify-3.2.5.js' => 'sha512-eLlLLL/zYuf5JuG0x4WQm687MToqOGP9cDQHIdmOy1ZpjiY4J48BBcOM7DtZheKk1UogW920+9RslWYB4KGuuA==',
'js/rawinflate-0.3.js' => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==', 'js/rawinflate-0.3.js' => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==',
'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==', 'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',

View File

@ -386,7 +386,7 @@ if ($FILEUPLOAD) :
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li id="filewrap"> <li id="filewrap">
<div> <div>
<input type="file" id="file" name="file" /> <input type="file" id="file" name="file" multiple />
</div> </div>
<div id="dragAndDropFileName" class="dragAndDropFile"><?php echo I18n::_('alternatively drag & drop a file or paste an image from the clipboard'); ?></div> <div id="dragAndDropFileName" class="dragAndDropFile"><?php echo I18n::_('alternatively drag & drop a file or paste an image from the clipboard'); ?></div>
</li> </li>
@ -505,10 +505,7 @@ endif;
<?php <?php
if ($FILEUPLOAD) : if ($FILEUPLOAD) :
?> ?>
<div id="attachment" role="alert" class="hidden alert alert-info"> <div id="attachment" class="hidden"></div>
<span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span>
<a class="alert-link"><?php echo I18n::_('Download attachment'); ?></a>
</div>
<?php <?php
endif; endif;
?> ?>
@ -656,9 +653,6 @@ endif;
</div> </div>
</footer> </footer>
</main> </main>
<?php
if ($DISCUSSION) :
?>
<div id="serverdata" class="hidden" aria-hidden="true"> <div id="serverdata" class="hidden" aria-hidden="true">
<div id="templates"> <div id="templates">
<article id="commenttemplate" class="comment"> <article id="commenttemplate" class="comment">
@ -680,12 +674,13 @@ if ($DISCUSSION) :
</div> </div>
<button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button> <button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button>
</div> </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>
</div> </div>
<?php <?php
endif;
?>
<?php
if ($FILEUPLOAD) : if ($FILEUPLOAD) :
?> ?>
<div id="dropzone" class="hidden" tabindex="-1" aria-hidden="true"></div> <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"> <ul class="dropdown-menu px-2">
<li id="filewrap"> <li id="filewrap">
<div> <div>
<input type="file" id="file" name="file" class="form-control" /> <input type="file" id="file" name="file" class="form-control" multiple />
</div> </div>
<div id="dragAndDropFileName" class="dragAndDropFile"><?php echo I18n::_('alternatively drag & drop a file or paste an image from the clipboard'); ?></div> <div id="dragAndDropFileName" class="dragAndDropFile"><?php echo I18n::_('alternatively drag & drop a file or paste an image from the clipboard'); ?></div>
</li> </li>
<li id="customattachment" class="hidden"></li> <li id="customattachment" class="hidden d-flex flex-column px-3"></li>
<li> <li>
<a id="fileremovebutton" href="#" class="dropdown-item"> <a id="fileremovebutton" href="#" class="dropdown-item">
<?php echo I18n::_('Remove attachment'), PHP_EOL; ?> <?php echo I18n::_('Remove attachment'), PHP_EOL; ?>
@ -370,10 +370,7 @@ endif;
<?php <?php
if ($FILEUPLOAD) : if ($FILEUPLOAD) :
?> ?>
<div id="attachment" role="alert" class="hidden alert alert-info"> <div id="attachment" class="hidden"></div>
<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>
<?php <?php
endif; endif;
?> ?>
@ -514,9 +511,6 @@ endif;
</p> </p>
</div> </div>
</footer> </footer>
<?php
if ($DISCUSSION) :
?>
<div id="serverdata" class="hidden" aria-hidden="true"> <div id="serverdata" class="hidden" aria-hidden="true">
<div id="templates"> <div id="templates">
<article id="commenttemplate" class="comment px-2 pb-3"> <article id="commenttemplate" class="comment px-2 pb-3">
@ -538,12 +532,13 @@ if ($DISCUSSION) :
</div> </div>
<button id="replybutton" class="btn btn-secondary btn-sm"><?php echo I18n::_('Post comment'); ?></button> <button id="replybutton" class="btn btn-secondary btn-sm"><?php echo I18n::_('Post comment'); ?></button>
</div> </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>
</div> </div>
<?php <?php
endif;
?>
<?php
if ($FILEUPLOAD) : if ($FILEUPLOAD) :
?> ?>
<div id="dropzone" class="hidden" tabindex="-1" aria-hidden="true"></div> <div id="dropzone" class="hidden" tabindex="-1" aria-hidden="true"></div>

View File

@ -265,10 +265,10 @@ endif;
<?php <?php
if ($FILEUPLOAD): 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"> <div id="attach" class="hidden">
<span id="clonedfile" class="hidden"><?php echo I18n::_('Cloned file attached.'); ?></span> <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> <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> <button id="fileremovebutton"><?php echo I18n::_('Remove attachment'); ?></button>
</div> </div>
@ -297,9 +297,6 @@ endif;
<div id="commentcontainer"></div> <div id="commentcontainer"></div>
</div> </div>
</section> </section>
<?php
if ($DISCUSSION):
?>
<div id="serverdata" class="hidden" aria-hidden="true"> <div id="serverdata" class="hidden" aria-hidden="true">
<div id="templates"> <div id="templates">
<article id="commenttemplate" class="comment"> <article id="commenttemplate" class="comment">
@ -321,12 +318,12 @@ if ($DISCUSSION):
</div> </div>
<button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button> <button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button>
</div> </div>
<div id="attachmenttemplate" class="attachment">
<a><?php echo I18n::_('Download attachment'); ?></a>
</div>
</div> </div>
</div> </div>
<?php <?php
endif;
?>
<?php
if ($FILEUPLOAD): if ($FILEUPLOAD):
?> ?>
<div id="dropzone" class="hidden" tabindex="-1" aria-hidden="true"></div> <div id="dropzone" class="hidden" tabindex="-1" aria-hidden="true"></div>