diff --git a/CHANGELOG.md b/CHANGELOG.md index 92694ec3..4557f3e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ * ADDED: Translation for Bulgarian (#455) * CHANGED: Improved mobile UI - obscured send button and hard to click shortener button (#477) * CHANGED: Enhanced URL shortener integration (#479) + * CHANGED: Improved file upload drag & drop UI (#317) * FIXED: Clicking 'New' on a previously submitted paste does not blank address bar (#354) * FIXED: Clear address bar when create new paste from existing paste (#479) * FIXED: Discussion section not hiding when new/clone paste is clicked on (#484) * FIXED: Showdown.js error when posting svg qrcode (#485) + * FIXED: Failed to handle the case where user cancelled attachment selection properly (#487) * **1.3 (2019-07-09)** * ADDED: Translation for Czech (#424) * ADDED: Threat modeled the application (#177) diff --git a/CREDITS.md b/CREDITS.md index 03f2a77b..873b0e39 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -25,6 +25,7 @@ Sébastien Sauvage - original idea and main developer * PunKeel - first docker container * thororm - Display of video, audio & PDF, drag & drop, preview of attachments * Harald Leithner - base58 encoding of key +* Haocen - lots of bugfixes and UI improvements ## Translations * Hexalyse - French diff --git a/css/bootstrap/privatebin.css b/css/bootstrap/privatebin.css index c4240705..13897ed6 100644 --- a/css/bootstrap/privatebin.css +++ b/css/bootstrap/privatebin.css @@ -80,10 +80,29 @@ body.loading { margin-bottom: 20px; } +#dropzone { + text-align: center; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + opacity: 0.6; + background-color: #99ccff; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + background-size: 25vh; + outline: 2px dashed #228bff; + outline-offset: -50px; +} + .dragAndDropFile{ - color:#777; - font-size:1em; - display:inline; + color: #777; + font-size: 1em; + display: inline; + white-space: normal; } #deletelink { @@ -124,6 +143,10 @@ body.loading { margin-bottom: 10px; } +#filewrap { + transition: background-color 0.75s ease-out; +} + .comment { border-left: 1px solid #ccc; padding: 5px 0 5px 10px; @@ -131,7 +154,7 @@ body.loading { transition: background-color 0.75s ease-out; } -.comment.highlight { +.highlight { background-color: #ffdd86; transition: background-color 0.2s ease-in; } diff --git a/css/privatebin.css b/css/privatebin.css index ae83de11..e3e23407 100644 --- a/css/privatebin.css +++ b/css/privatebin.css @@ -115,10 +115,29 @@ h3.title { margin-bottom: 20px; } +#dropzone { + text-align: center; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + opacity: 0.6; + background-color: #99ccff; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + background-size: 25vh; + outline: 2px dashed #228bff; + outline-offset: -50px; +} + .dragAndDropFile{ - color:#777; - font-size:1em; - display:inline; + color: #777; + font-size: 1em; + display: inline; + white-space: normal; } #status { @@ -405,6 +424,15 @@ h4.title { .commentdate { color: #bfcede; } +#filewrap { + transition: background-color 0.75s ease-out; +} + +.highlight { + background-color: #ffdd86; + transition: background-color 0.2s ease-in; +} + img.vizhash { width: 16px; height: 16px; diff --git a/js/privatebin.js b/js/privatebin.js index 72b73403..629df1df 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -22,6 +22,27 @@ /** global: showdown */ /** global: kjua */ +jQuery.fn.draghover = function() { + return this.each(function() { + let collection = $(), + self = $(this); + + self.on('dragenter', function(e) { + if (collection.length === 0) { + self.trigger('draghoverstart'); + } + collection = collection.add(e.target); + }); + + self.on('dragleave drop', function(e) { + collection = collection.not(e.target); + if (collection.length === 0) { + self.trigger('draghoverend'); + } + }); + }); +}; + // main application start, called when DOM is fully loaded jQuery(document).ready(function() { 'use strict'; @@ -2492,7 +2513,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { file, $fileInput, $dragAndDropFileName, - attachmentHasPreview = false; + attachmentHasPreview = false, + $dropzone; /** * sets the attachment but does not yet show it @@ -2714,7 +2736,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { // revert loading status… me.hideAttachment(); me.hideAttachmentPreview(); - Alert.showError('Your browser does not support uploading encrypted files. Please use a newer browser.'); + Alert.showError( + I18n._('Your browser does not support uploading encrypted files. Please use a newer browser.') + ); return; } @@ -2726,18 +2750,23 @@ jQuery.PrivateBin = (function($, RawDeflate) { $dragAndDropFileName.text(loadedFile.name); } - file = loadedFile; + if (typeof loadedFile !== 'undefined') { + file = loadedFile; + fileReader.onload = function (event) { + const dataURL = event.target.result; + attachmentData = dataURL; - fileReader.onload = function (event) { - const dataURL = event.target.result; - attachmentData = dataURL; + if (Editor.isPreview()) { + me.handleAttachmentPreview($attachmentPreview, dataURL); + $attachmentPreview.removeClass('hidden'); + } - if (Editor.isPreview()) { - me.setAttachment(dataURL, loadedFile.name || ''); - $attachmentPreview.removeClass('hidden'); - } - }; - fileReader.readAsDataURL(loadedFile); + TopNav.highlightFileupload(); + }; + fileReader.readAsDataURL(loadedFile); + } else { + me.removeAttachmentData(); + } } /** @@ -2781,7 +2810,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { ); } else if (mimeType.match(/\/pdf/i)) { // Fallback for browsers, that don't support the vh unit - var clientHeight = $(window).height(); + const clientHeight = $(window).height(); $targetElement.html( $(document.createElement('embed')) @@ -2808,12 +2837,12 @@ jQuery.PrivateBin = (function($, RawDeflate) { return; } - const ignoreDragDrop = function(event) { + const handleDragEnterOrOver = function(event) { event.stopPropagation(); event.preventDefault(); }; - const drop = function(event) { + const handleDrop = function(event) { const evt = event.originalEvent; evt.stopPropagation(); evt.preventDefault(); @@ -2830,9 +2859,19 @@ jQuery.PrivateBin = (function($, RawDeflate) { } }; - $(document).on('drop', drop); - $(document).on('dragenter', ignoreDragDrop); - $(document).on('dragover', ignoreDragDrop); + $(document).draghover().on({ + 'draghoverstart': function() { + // show dropzone to indicate drop support + $dropzone.removeClass('hidden'); + }, + 'draghoverend': function() { + $dropzone.addClass('hidden'); + } + }); + + $(document).on('drop', handleDrop); + $(document).on('dragenter dragover', handleDragEnterOrOver); + $fileInput.on('change', function () { readFileData(); }); @@ -2920,6 +2959,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $attachmentLink = $('#attachment a'); $attachmentPreview = $('#attachmentPreview'); $dragAndDropFileName = $('#dragAndDropFileName'); + $dropzone = $('#dropzone'); $fileInput = $('#file'); addDragDropHandler(); @@ -3741,6 +3781,25 @@ jQuery.PrivateBin = (function($, RawDeflate) { retryButtonCallback = callback; } + /** + * Highlight file upload + * + * @name TopNav.highlightFileupload + * @function + */ + me.highlightFileupload = function() + { + // visually indicate file uploaded + const $attachDropdownToggle = $attach.children('.dropdown-toggle'); + if ($attachDropdownToggle.attr('aria-expanded') === 'false') { + $attachDropdownToggle.click(); + } + $fileWrap.addClass('highlight'); + setTimeout(function () { + $fileWrap.removeClass('highlight'); + }, 300); + } + /** * init navigation manager * @@ -4479,7 +4538,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { // log detailed error, but display generic translation console.error(message); - Alert.showError('Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.'); + Alert.showError( + I18n._('Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.') + ); // reset password, so it can be re-entered Prompt.reset(); @@ -4548,8 +4609,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @param {object} document * @class */ - var InitialCheck = (function () { - var me = {}; + const InitialCheck = (function () { + const me = {}; /** * blacklist of UserAgents (parts) known to belong to a bot @@ -4762,7 +4823,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { // missing decryption key (or paste ID) in URL? if (window.location.hash.length === 0) { - Alert.showError('Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)'); + Alert.showError( + I18n._('Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)') + ); return; } } diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 8416d71a..780396e0 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -71,7 +71,7 @@ if ($MARKDOWN): endif; ?> - + @@ -552,6 +552,13 @@ if ($DISCUSSION): + + + diff --git a/tpl/page.php b/tpl/page.php index 8c828274..cfda0a48 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -49,7 +49,7 @@ if ($MARKDOWN): endif; ?> - + @@ -254,6 +254,13 @@ if ($DISCUSSION): + + +