diff --git a/.stylelintrc b/.stylelintrc index 3c593e83..c0c673c7 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,6 +1,6 @@ extends: stylelint-config-standard rules: - color-hex-case: upper + color-hex-case: lower declaration-colon-newline-after: null selector-list-comma-newline-after: null diff --git a/frontend/src/fileReceiver.js b/frontend/src/fileReceiver.js index 155a8dd9..ad1611df 100644 --- a/frontend/src/fileReceiver.js +++ b/frontend/src/fileReceiver.js @@ -58,41 +58,46 @@ class FileReceiver extends EventEmitter { true, ['encrypt', 'decrypt'] ) - ]).then(([fdata, key]) => { - this.emit('decrypting', true); - return Promise.all([ - window.crypto.subtle.decrypt( - { - name: 'AES-GCM', - iv: hexToArray(fdata.iv), - additionalData: hexToArray(fdata.aad) - }, - key, - fdata.data - ).then(decrypted => { - this.emit('decrypting', false); - return Promise.resolve(decrypted) - }), - fdata.filename, - hexToArray(fdata.aad) - ]); - }).then(([decrypted, fname, proposedHash]) => { - this.emit('hashing', true); - return window.crypto.subtle.digest('SHA-256', decrypted).then(calculatedHash => { - this.emit('hashing', false); - const integrity = new Uint8Array(calculatedHash).toString() === proposedHash.toString(); - if (!integrity) { - this.emit('unsafe', true) - return Promise.reject(); - } else { - this.emit('safe', true); - return Promise.all([ - decrypted, - decodeURIComponent(fname) - ]); - } + ]) + .then(([fdata, key]) => { + this.emit('decrypting', true); + return Promise.all([ + window.crypto.subtle + .decrypt( + { + name: 'AES-GCM', + iv: hexToArray(fdata.iv), + additionalData: hexToArray(fdata.aad) + }, + key, + fdata.data + ) + .then(decrypted => { + this.emit('decrypting', false); + return Promise.resolve(decrypted); + }), + fdata.filename, + hexToArray(fdata.aad) + ]); }) - }) + .then(([decrypted, fname, proposedHash]) => { + this.emit('hashing', true); + return window.crypto.subtle + .digest('SHA-256', decrypted) + .then(calculatedHash => { + this.emit('hashing', false); + const integrity = + new Uint8Array(calculatedHash).toString() === + proposedHash.toString(); + if (!integrity) { + this.emit('unsafe', true); + return Promise.reject(); + } else { + this.emit('safe', true); + return Promise.all([decrypted, decodeURIComponent(fname)]); + } + }); + }); } } diff --git a/frontend/src/upload.js b/frontend/src/upload.js index 194d50af..3f6ba2cf 100644 --- a/frontend/src/upload.js +++ b/frontend/src/upload.js @@ -40,7 +40,9 @@ $(document).ready(function() { //disable button for 3s $copyBtn.attr('disabled', true); $('#link').attr('disabled', true); - $copyBtn.html(''); + $copyBtn.html( + '' + ); window.setTimeout(() => { $copyBtn.attr('disabled', false); $('#link').attr('disabled', false); @@ -71,12 +73,14 @@ $(document).ready(function() { event.preventDefault(); let file = ''; if (event.type === 'drop') { - if (event.originalEvent.dataTransfer.files.length > 1 || event.originalEvent.dataTransfer.files[0].size === 0){ + if ( + event.originalEvent.dataTransfer.files.length > 1 || + event.originalEvent.dataTransfer.files[0].size === 0 + ) { $('.upload-window').removeClass('ondrag'); - document.l10n.formatValue('uploadPageMultipleFilesAlert') - .then(str => { - alert(str); - }); + document.l10n.formatValue('uploadPageMultipleFilesAlert').then(str => { + alert(str); + }); return; } file = event.originalEvent.dataTransfer.files[0]; @@ -95,10 +99,9 @@ $(document).ready(function() { $('#cancel-upload').click(() => { fileSender.cancel(); location.reload(); - document.l10n.formatValue('uploadCancelNotification') - .then(str => { - notify(str); - }); + document.l10n.formatValue('uploadCancelNotification').then(str => { + notify(str); + }); }); fileSender.on('progress', progress => { @@ -110,15 +113,21 @@ $(document).ready(function() { }); if (progress[1] < 1000000) { $('.progress-text').text( - `${file.name} (${(progress[0] / 1000).toFixed(1)}KB of ${(progress[1] / 1000).toFixed(1)}KB)` + `${file.name} (${(progress[0] / 1000).toFixed( + 1 + )}KB of ${(progress[1] / 1000).toFixed(1)}KB)` ); } else if (progress[1] < 1000000000) { $('.progress-text').text( - `${file.name} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000).toFixed(1)}MB)` + `${file.name} (${(progress[0] / 1000000).toFixed( + 1 + )}MB of ${(progress[1] / 1000000).toFixed(1)}MB)` ); } else { $('.progress-text').text( - `${file.name} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000000).toFixed(1)}GB)` + `${file.name} (${(progress[0] / 1000000).toFixed( + 1 + )}MB of ${(progress[1] / 1000000000).toFixed(1)}GB)` ); } }); @@ -163,7 +172,10 @@ $(document).ready(function() { expiry: expiration }; localStorage.setItem(info.fileId, JSON.stringify(fileData)); - $('#upload-filename').attr('data-l10n-id', 'uploadSuccessConfirmHeader'); + $('#upload-filename').attr( + 'data-l10n-id', + 'uploadSuccessConfirmHeader' + ); t = window.setTimeout(() => { $('#page-one').attr('hidden', true); $('#upload-progress').attr('hidden', true); @@ -172,10 +184,9 @@ $(document).ready(function() { }, 1000); populateFileList(JSON.stringify(fileData)); - document.l10n.formatValue('notifyUploadDone') - .then(str => { - notify(str); - }); + document.l10n.formatValue('notifyUploadDone').then(str => { + notify(str); + }); }) .catch(err => { Raven.captureException(err); @@ -219,10 +230,18 @@ $(document).ready(function() { const row = document.createElement('tr'); const name = document.createElement('td'); const link = document.createElement('td'); - const $copyIcon = $('', { src: '/resources/copy-16.svg', class: 'icon-copy', 'data-l10n-id': 'copyUrlHover'}); + const $copyIcon = $('', { + src: '/resources/copy-16.svg', + class: 'icon-copy', + 'data-l10n-id': 'copyUrlHover' + }); const expiry = document.createElement('td'); const del = document.createElement('td'); - const $delIcon = $('', { src: '/resources/close-16.svg', class: 'icon-delete', 'data-l10n-id': 'deleteButtonHover' }); + const $delIcon = $('', { + src: '/resources/close-16.svg', + class: 'icon-delete', + 'data-l10n-id': 'deleteButtonHover' + }); const popupDiv = document.createElement('div'); const $popupText = $('
', { class: 'popuptext' }); const cellText = document.createTextNode(file.name); @@ -230,14 +249,8 @@ $(document).ready(function() { const url = file.url.trim() + `#${file.secretKey}`.trim(); $('#link').attr('value', url); - $('#copy-text').attr( - 'data-l10n-args', - '{"filename": "' + file.name + '"}' - ); - $('#copy-text').attr( - 'data-l10n-id', - 'copyUrlFormLabelWithName' - ); + $('#copy-text').attr('data-l10n-args', '{"filename": "' + file.name + '"}'); + $('#copy-text').attr('data-l10n-id', 'copyUrlFormLabelWithName'); $popupText.attr('tabindex', '-1'); name.appendChild(cellText); @@ -264,10 +277,9 @@ $(document).ready(function() { aux.select(); document.execCommand('copy'); document.body.removeChild(aux); - document.l10n.formatValue('copiedUrl') - .then(translated => { - link.innerHTML = translated; - }); + document.l10n.formatValue('copiedUrl').then(translated => { + link.innerHTML = translated; + }); window.setTimeout(() => { const linkImg = document.createElement('img'); $(linkImg).addClass('icon-copy'); @@ -327,13 +339,7 @@ $(document).ready(function() { $(popupNvmSpan).addClass('nvm'); $(popupNvmSpan).attr('data-l10n-id', 'nevermindButton'); - $popupText.html([ - popupDelSpan, - ' ', - ' ', - popupNvmSpan - ]); - + $popupText.html([popupDelSpan, ' ', ' ', popupNvmSpan]); // add data cells to table row row.appendChild(name); @@ -378,7 +384,6 @@ $(document).ready(function() { $popupText.removeClass('show'); }); - toggleHeader(); } function toggleHeader() { diff --git a/public/locales/send.en-US.ftl b/public/locales/send.en-US.ftl index a3873fbb..a68ab579 100644 --- a/public/locales/send.en-US.ftl +++ b/public/locales/send.en-US.ftl @@ -1,4 +1,6 @@ title = Firefox Send +siteSubtitle = web experiment +siteFeedback = Feedback uploadPageHeader = Private, Encrypted File Sharing uploadPageExplainer = Send files through a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever. diff --git a/public/main.css b/public/main.css index e1a1bf7b..18ea52a0 100644 --- a/public/main.css +++ b/public/main.css @@ -1,7 +1,12 @@ /*** index.html ***/ html { background: url('resources/send_bg.svg'); - font-family: 'SF Pro Text', sans-serif; + font-family: -apple-system, + BlinkMacSystemFont, + 'SF Pro Text', + Helvetica, + Arial, + sans-serif; font-weight: 200; background-size: 100%; background-repeat: no-repeat; @@ -10,24 +15,94 @@ html { } body { - min-height: 100%; - position: relative; + display: flex; + flex-direction: column; margin: 0; + min-height: 100vh; + position: relative; +} + +.header { + align-items: flex-start; + box-sizing: border-box; + display: flex; + justify-content: space-between; + padding: 31px; + width: 100%; } .send-logo { + display: flex; position: relative; - top: 31px; - left: 31px; - display: inline-block; + align-items: center; +} + +.site-title { + font-size: 34px; + font-weight: 500; + margin: 0; + position: relative; + top: -1px; +} + +.site-subtitle { + font-size: 12px; + margin: 0 8px; + color: #0c0c0d; +} + +.site-subtitle a { + font-weight: bold; + color: #0c0c0d; + transition: color 50ms; +} + +.send-logo:hover a { + color: #0297f8; +} + +.feedback { + background-color: #0297f8; + background-image: url('resources/feedback.svg'); + background-position: 4px 6px; + background-repeat: no-repeat; + background-size: 14px; + border-radius: 3px; + border: 1px solid #0297f8; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + color: #fff; + cursor: pointer; + display: block; + float: right; + font-size: 12px; + line-height: 12px; + opacity: 0.9; + padding: 6px 6px 5px 20px; +} + +.feedback:hover, +.feedback:focus { + background-color: #0287e8; +} + +.feedback:active { + background-color: #0277d8; } .all { - padding-top: 10%; - padding-bottom: 51px; + flex: 1; + display: flex; + flex-direction: column; + justify-content: flex-start; + max-width: 630px; + margin: 0 auto; + width: 96%; } -input, select, textarea, button { +input, +select, +textarea, +button { font-family: inherit; } @@ -38,24 +113,26 @@ a { /** page-one **/ .title { font-size: 33px; + line-height: 40px; margin: 20px auto; text-align: center; + max-width: 520px; font-family: 'SF Pro Display', sans-serif; } .description { font-size: 15px; line-height: 23px; - width: 630px; + max-width: 630px; text-align: center; margin: 0 auto 60px; - color: #0C0C0D; + color: #0c0c0d; + width: 92%; } .upload-window { border: 1px dashed rgba(0, 148, 251, 0.5); margin: 0 auto; - width: 640px; height: 255px; border-radius: 4px; display: flex; @@ -63,14 +140,15 @@ a { align-items: center; flex-direction: column; text-align: center; + transition: transform 150ms; + padding: 15px; } .upload-window.ondrag { border: 3px dashed rgba(0, 148, 251, 0.5); margin: 0 auto; - width: 636px; height: 251px; - transform: scale(1.05); + transform: scale(1.04); border-radius: 4.2px; display: flex; justify-content: center; @@ -80,7 +158,7 @@ a { } .link { - color: #0094FB; + color: #0094fb; text-decoration: none; } @@ -92,10 +170,10 @@ a { } #browse { - background: #0297F8; + background: #0297f8; border-radius: 5px; font-size: 15px; - color: #FFF; + color: #fff; width: 240px; height: 44px; display: flex; @@ -110,6 +188,7 @@ input[type="file"] { #file-size-msg { font-size: 12px; + line-height: 16px; color: #737373; margin-bottom: 22px; } @@ -129,7 +208,7 @@ th { td { font-size: 15px; vertical-align: top; - color: #4A4A4A; + color: #4a4a4a; padding: 17px 19px 0; line-height: 23px; } @@ -144,12 +223,13 @@ tbody { } #uploaded-files { - width: 640px; margin: 45.3px auto; table-layout: fixed; } -.icon-delete, .icon-copy, .icon-check { +.icon-delete, +.icon-copy, +.icon-check { cursor: pointer; } @@ -165,7 +245,7 @@ tbody { visibility: hidden; width: 160px; background-color: #555; - color: #FFF; + color: #fff; text-align: center; border-radius: 6px; padding: 8px 0; @@ -239,14 +319,13 @@ tbody { } #cancel-upload { - color: #D70022; + color: #d70022; cursor: pointer; text-decoration: underline; } /** share-link **/ #share-window { - width: 645px; margin: 0 auto; display: flex; justify-content: center; @@ -262,53 +341,59 @@ tbody { #copy { display: flex; flex-wrap: nowrap; + width: 640px; } #copy-text { align-self: flex-start; margin-top: 60px; margin-bottom: 10px; - color: #0C0C0D; + color: #0c0c0d; } #link { - width: 480px; + flex: 1; height: 56px; - border: 1px solid #0297F8; + border: 1px solid #0297f8; border-radius: 6px 0 0 6px; font-size: 24px; color: #737373; font-family: 'SF Pro Display', sans-serif; letter-spacing: 0; line-height: 23px; + padding-left: 5px; + padding-right: 5px; } #link:disabled { - border: 1px solid #05A700; - background: #FFF; + border: 1px solid #05a700; + background: #fff; } #copy-btn { - width: 165px; - height: 60px; - background: #0297F8; - border: 1px solid #0297F8; + flex: 0 1 165px; + background: #0297f8; border-radius: 0 6px 6px 0; + border: 1px solid #0297f8; color: white; cursor: pointer; font-size: 15px; + height: 60px; + padding-left: 10px; + padding-right: 10px; + white-space: nowrap; } #copy-btn:disabled { - background: #05A700; - border: 1px solid #05A700; + background: #05a700; + border: 1px solid #05a700; cursor: auto; } #delete-file { width: 176px; height: 44px; - background: #FFF; + background: #fff; border: 1px solid rgba(12, 12, 13, 0.3); border-radius: 5px; font-size: 15px; @@ -322,7 +407,7 @@ tbody { font-size: 15px; margin: auto; text-align: center; - color: #0094FB; + color: #0094fb; cursor: pointer; text-decoration: underline; } @@ -336,7 +421,8 @@ tbody { text-align: center; } -#upload-error[hidden], #unsupported-browser[hidden] { +#upload-error[hidden], +#unsupported-browser[hidden] { display: none; } @@ -356,9 +442,8 @@ tbody { .unsupported-description { font-size: 13px; line-height: 23px; - width: 630px; text-align: center; - color: #7D7D7D; + color: #7d7d7d; margin: 0 auto 23px; } @@ -370,14 +455,14 @@ tbody { margin-bottom: 181px; width: 260px; height: 80px; - background: #12BC00; + background: #12bc00; border-radius: 3px; cursor: pointer; border: 0; box-shadow: 0 5px 3px rgb(234, 234, 234); font-family: 'Fira Sans'; font-weight: 500; - color: #FFF; + color: #fff; font-size: 26px; display: flex; justify-content: center; @@ -406,15 +491,15 @@ tbody { margin-top: 20px; margin-bottom: 30px; text-align: center; - background: #0297F8; - border: 1px solid #0297F8; + background: #0297f8; + border: 1px solid #0297f8; border-radius: 5px; font-weight: 300; cursor: pointer; } #download-btn:disabled { - background: #47B04B; + background: #47b04b; cursor: auto; } @@ -434,9 +519,8 @@ tbody { .expired-description { font-size: 15px; line-height: 23px; - width: 630px; text-align: center; - color: #7D7D7D; + color: #7d7d7d; margin: 0 auto 23px; } @@ -460,14 +544,13 @@ tbody { /* footer */ .footer { - position: absolute; right: 0; bottom: 0; left: 0; font-size: 15px; display: flex; align-items: flex-end; - padding: 10px; + padding: 50px 10px 10px; } .mozilla-logo { @@ -495,8 +578,60 @@ tbody { margin-left: 30px; } -.github, .twitter { +.github, +.twitter { width: 32px; height: 32px; margin-bottom: -5px; } + +@media (max-width: 768px) { + .description { + margin: 0 auto 25px; + } + + #copy { + width: 100%; + } + + #link { + font-size: 18px; + } + + .mozilla-logo { + margin-left: -7px; + } + + .legal-links > * { + display: block; + padding: 10px 0; + } +} + +@media (max-width: 520px) { + #copy { + width: 100%; + flex-direction: column; + } + + #link { + font-size: 22px; + padding: 15px 10px; + border-radius: 6px 6px 0 0; + } + + #copy-btn { + border-radius: 0 0 6px 6px; + flex: 0 1 65px; + } + + th { + font-size: 14px; + padding: 0 5px; + } + + td { + font-size: 13px; + padding: 17px 5px 0; + } +} diff --git a/public/resources/feedback.svg b/public/resources/feedback.svg new file mode 100644 index 00000000..1e3e3fe5 --- /dev/null +++ b/public/resources/feedback.svg @@ -0,0 +1 @@ +Combined Shape \ No newline at end of file diff --git a/public/resources/send_logo_type.svg b/public/resources/send_logo_type.svg deleted file mode 100644 index 07091885..00000000 --- a/public/resources/send_logo_type.svg +++ /dev/null @@ -1 +0,0 @@ -logo design_01 copy \ No newline at end of file diff --git a/server/server.js b/server/server.js index 24e1d418..97787255 100644 --- a/server/server.js +++ b/server/server.js @@ -34,10 +34,12 @@ app.engine( app.set('view engine', 'handlebars'); app.use(helmet()); -app.use(helmet.hsts({ - maxAge: 31536000, - force: conf.env === 'production' -})); +app.use( + helmet.hsts({ + maxAge: 31536000, + force: conf.env === 'production' + }) +); app.use( helmet.contentSecurityPolicy({ directives: { @@ -241,7 +243,7 @@ app.post('/upload', (req, res, next) => { .catch(err => { log.info('DeleteError:', newId); }); - }) + }); }); app.get('/__lbheartbeat__', (req, res) => { diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 285ad3c7..5eb891ff 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -13,10 +13,17 @@ - +
+ + +
{{{body}}}