From 4e706018ad8783d4e3d97d2e825205ca008a784f Mon Sep 17 00:00:00 2001 From: Haocen Xu Date: Sun, 5 Jul 2020 06:15:39 -0400 Subject: [PATCH] Completely rework attatchment preview related logic --- css/bootstrap/privatebin.css | 15 +- css/privatebin.css | 1 - js/privatebin.js | 293 +++++++++++++++++++++-------------- tpl/bootstrap.php | 116 +++++++------- tpl/page.php | 4 +- 5 files changed, 249 insertions(+), 180 deletions(-) diff --git a/css/bootstrap/privatebin.css b/css/bootstrap/privatebin.css index 72e420ed..6fac793a 100644 --- a/css/bootstrap/privatebin.css +++ b/css/bootstrap/privatebin.css @@ -71,13 +71,11 @@ body.loading { #attachmentPreview img { max-width: 100%; height: auto; - margin-bottom: 20px; } #attachmentPreview .pdfPreview { width: 100%; height: 100vh; - margin-bottom: 20px; } #dropzone { @@ -168,6 +166,10 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { list-style-type: decimal !important; } + +.nowarp { + white-space: nowrap; +} .dark-theme .alert-info .alert-link { color: #fff; } @@ -205,3 +207,12 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { .modal .modal-content button { margin: 0.5em 0; } + +.file-name { + word-wrap: break-word; +} + +/* file name may also be displayed in alert */ +.alert { + word-wrap: break-word; +} \ No newline at end of file diff --git a/css/privatebin.css b/css/privatebin.css index a3ab5ea6..8b8cfc0f 100644 --- a/css/privatebin.css +++ b/css/privatebin.css @@ -113,7 +113,6 @@ h3.title { #attachmentPreview .pdfPreview { width: 100%; height: 100vh; - margin-bottom: 20px; } #dropzone { diff --git a/js/privatebin.js b/js/privatebin.js index 2ee4ca29..164f83db 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -25,7 +25,6 @@ jQuery.fn.draghover = function() { } collection = collection.add(e.target); }); - self.on('dragleave drop', function(e) { collection = collection.not(e.target); if (collection.length === 0) { @@ -362,6 +361,36 @@ jQuery.PrivateBin = (function($, RawDeflate) { } }; + /** + * converts size in bytes to human readable string + * + * The string is expected to be optional digits, followed by a time. + * Supported times are: min, hour, day, month, year, never + * Examples: 5min, 13hour, never + * + * @name Helper.humanizeBytes + * @function + * @param {number} bytes + * @param {decimals} decimals - optional + * @return {String} + */ + me.humanizeBytes = function(bytes, decimals = 2) { + const sizes = [ + I18n._('Bytes'), I18n._('KB'), I18n._('MB'), I18n._('GB'), + I18n._('TB'), I18n._('PB'), I18n._('EB'), I18n._('ZB'), I18n._('YB') + ]; + const signifierByte = I18n._('Byte'); + if (bytes === 0) return me.sprintf('0 %s', sizes[0]); + if (bytes === 1) return me.sprintf('1 %s', signifierByte); + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + }; + /** * text range selection * @@ -2302,38 +2331,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { } } - /** - * view the Editor tab - * - * @name Editor.viewEditor - * @function - * @param {Event} event - optional - */ - function viewEditor(event) - { - // toggle buttons - $messageEdit.addClass('active'); - $messagePreview.removeClass('active'); - - $('#messageedit').attr('aria-selected','true'); - $('#messagepreview').attr('aria-selected','false'); - - PasteViewer.hide(); - - // reshow input - $message.removeClass('hidden'); - - me.focusInput(); - - // finish - isPreview = false; - - // prevent jumping of page to top - if (typeof event !== 'undefined') { - event.preventDefault(); - } - } - /** * view the preview tab * @@ -2347,22 +2344,16 @@ jQuery.PrivateBin = (function($, RawDeflate) { $messageEdit.removeClass('active'); $messagePreview.addClass('active'); - $('#messageedit').attr('aria-selected','false'); - $('#messagepreview').attr('aria-selected','true'); + $('#messageedit').prop('aria-selected','false'); + $('#messagepreview').prop('aria-selected','true'); // hide input as now preview is shown $message.addClass('hidden'); // show preview PasteViewer.setText($message.val()); - if (AttachmentViewer.hasAttachmentData()) { - const attachment = AttachmentViewer.getAttachment(); - AttachmentViewer.handleBlobAttachmentPreview( - AttachmentViewer.getAttachmentPreview(), - attachment[0], attachment[1] - ); - } PasteViewer.run(); + AttachmentViewer.showAttachmentPreview(); // finish isPreview = true; @@ -2373,6 +2364,39 @@ jQuery.PrivateBin = (function($, RawDeflate) { } } + /** + * view the Editor tab + * + * @name Editor.viewEditor + * @function + * @param {Event} event - optional + */ + me.viewEditor = function(event) + { + // toggle buttons + $messageEdit.addClass('active'); + $messagePreview.removeClass('active'); + + $('#messageedit').prop('aria-selected','true'); + $('#messagepreview').prop('aria-selected','false'); + + PasteViewer.hide(); + AttachmentViewer.hideAttachmentPreview(); + + // reshow input + $message.removeClass('hidden'); + + me.focusInput(); + + // finish + isPreview = false; + + // prevent jumping of page to top + if (typeof event !== 'undefined') { + event.preventDefault(); + } + } + /** * get the state of the preview * @@ -2394,7 +2418,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { { // go back to input if (isPreview) { - viewEditor(); + me.viewEditor(); } // clear content @@ -2478,7 +2502,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // bind click events to tab switchers (a), but save parent of them // (li) - $messageEdit = $('#messageedit').click(viewEditor).parent(); + $messageEdit = $('#messageedit').click(me.viewEditor).parent(); $messagePreview = $('#messagepreview').click(viewPreview).parent(); }; @@ -2763,8 +2787,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // extract mediaType const mediaType = attachmentData.substring(5, mediaTypeEnd); // extract data and convert to binary - const rawData = attachmentData.substring(base64Start); - const decodedData = rawData.length > 0 ? atob(rawData) : ''; + const decodedData = atob(attachmentData.substring(base64Start)); // Transform into a Blob const buf = new Uint8Array(decodedData.length); @@ -2783,14 +2806,14 @@ jQuery.PrivateBin = (function($, RawDeflate) { navigator.msSaveBlob(blob, fileName); }); } else { - $attachmentLink.attr('href', blobUrl); + $attachmentLink.prop('href', blobUrl); } if (typeof fileName !== 'undefined') { - $attachmentLink.attr('download', fileName); + $attachmentLink.prop('download', fileName); } - me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mediaType); + me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mediaType, fileName, blob.size); }; /** @@ -2802,7 +2825,18 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.showAttachment = function() { $attachment.removeClass('hidden'); + me.showAttachmentPreview(); + }; + + /** + * displays the attachment preview + * + * @name AttachmentViewer.showAttachmentPreview + * @function + */ + me.showAttachmentPreview = function() + { if (attachmentHasPreview) { $attachmentPreview.removeClass('hidden'); } @@ -2822,13 +2856,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { if (!$attachment.length) { return; } + TopNav.clearCustomAttachment(); + me.clearDragAndDropFileName(); me.hideAttachment(); me.hideAttachmentPreview(); - $attachmentLink.removeAttr('href'); - $attachmentLink.removeAttr('download'); - $attachmentLink.off('click'); - $attachmentPreview.html(''); - $dragAndDropFileName.text(''); + $attachmentPreview.empty(); + $fileInput.val(''); + AttachmentViewer.removeAttachmentData(); }; @@ -2845,17 +2879,20 @@ jQuery.PrivateBin = (function($, RawDeflate) { { file = undefined; attachmentData = undefined; + $attachmentLink.removeAttr('href'); + $attachmentLink.removeAttr('download'); + $attachmentLink.off('click'); }; /** - * Cleares the drag & drop data. + * Cleares the drag & drop fileName * * @name AttachmentViewer.clearDragAndDrop * @function */ - me.clearDragAndDrop = function() + me.clearDragAndDropFileName = function() { - $dragAndDropFileName.text(''); + I18n._($dragAndDropFileName, 'Choose file or drag & drop') }; /** @@ -2948,7 +2985,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $attachmentLink.appendTo($element); // update text - ensuring no HTML is inserted into the text node - I18n._($attachmentLink, label, $attachmentLink.attr('download')); + I18n._($attachmentLink, label, $attachmentLink.prop('download')); }; /** @@ -2969,30 +3006,28 @@ jQuery.PrivateBin = (function($, RawDeflate) { return; } - const fileReader = new FileReader(); - if (loadedFile === undefined) { - loadedFile = $fileInput[0].files[0]; - $dragAndDropFileName.text(''); - } else { - $dragAndDropFileName.text(loadedFile.name); - } + me.removeAttachment(); if (typeof loadedFile !== 'undefined') { + const fileReader = new FileReader(); + $dragAndDropFileName.text(loadedFile.name); file = loadedFile; fileReader.onload = function (event) { const dataURL = event.target.result; attachmentData = dataURL; + const blobUrl = URL.createObjectURL(loadedFile); + $attachmentLink.prop('href', blobUrl); + $attachmentLink.prop('download', loadedFile.name); + me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, loadedFile.type, loadedFile.name, loadedFile.size); + if (Editor.isPreview()) { - me.handleAttachmentPreview($attachmentPreview, dataURL); - $attachmentPreview.removeClass('hidden'); + AttachmentViewer.showAttachmentPreview(); } TopNav.highlightFileupload(); }; fileReader.readAsDataURL(loadedFile); - } else { - me.removeAttachmentData(); } } @@ -3001,54 +3036,77 @@ jQuery.PrivateBin = (function($, RawDeflate) { * * @name AttachmentViewer.handleBlobAttachmentPreview * @function - * @argument {jQuery} $targetElement element where the preview should be appended - * @argument {string} file as a blob URL - * @argument {string} mime type + * @param {jQuery} $targetElement element where the preview should be appended + * @param {string} blobUrl as a blob URL + * @param {string} mimeType type + * @param {string} fileName + * @param {number} sizeInBytes */ - me.handleBlobAttachmentPreview = function ($targetElement, blobUrl, mimeType) { + me.handleBlobAttachmentPreview = function ($targetElement, blobUrl, mimeType, fileName = null, sizeInBytes = 0) { if (blobUrl) { attachmentHasPreview = true; + $targetElement.empty(); if (mimeType.match(/image\//i)) { - $targetElement.html( - $(document.createElement('img')) - .attr('src', blobUrl) - .attr('class', 'img-thumbnail') + $targetElement.append( + $('') + .prop('src', blobUrl) + .addClass('img-responsive center-block') ); } else if (mimeType.match(/video\//i)) { - $targetElement.html( - $(document.createElement('video')) - .attr('controls', 'true') - .attr('autoplay', 'true') - .attr('class', 'img-thumbnail') + $targetElement.append( + $('