diff --git a/css/common.css b/css/common.css
index 5f032aed..b5019c47 100644
--- a/css/common.css
+++ b/css/common.css
@@ -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;
diff --git a/js/privatebin.js b/js/privatebin.js
index 439afefd..cfe1e4c6 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -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:[
"));
+ }
+
/**
* 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('