hook multifile to ui

This commit is contained in:
Emily 2018-07-31 11:09:18 -07:00
parent e42ad175db
commit c9ae76b209
77 changed files with 1528 additions and 1111 deletions

View File

@ -65,6 +65,14 @@ export async function fileInfo(id, owner_token) {
throw new Error(response.status);
}
export async function hasPassword(id) {
const response = await fetch(`/api/exists/${id}`);
if (response.ok) {
return response.json();
}
throw new Error(response.status);
}
export async function metadata(id, keychain) {
const result = await fetchWithAuthAndRetry(
`/api/metadata/${id}`,

View File

@ -17,6 +17,10 @@ export default class Archive {
return this.files.reduce((total, file) => total + file.size, 0);
}
get numFiles() {
return this.files.length;
}
get manifest() {
return {
files: this.files.map(file => ({

View File

@ -1,14 +1,16 @@
:root {
--pageBGColor: #fff;
--primaryControlBGColor: #0297f8;
--lightControlBGColor: #e6e6e6;
--primaryControlBGColor: #0a84ff;
--primaryControlFGColor: #fff;
--primaryControlHoverColor: #0287e8;
--primaryControlHoverColor: #0473e2;
--inputTextColor: #737373;
--errorColor: #d70022;
--linkColor: #0094fb;
--textColor: #0c0c0d;
--lightBorderColor: rgba(12, 12, 12, 0.2);
--lightTextColor: #737373;
--successControlBGColor: #05a700;
--successControlBGColor: #12bc00;
--successControlFGColor: #fff;
}
@ -41,17 +43,21 @@ a {
.main {
display: flex;
flex-direction: row;
flex: auto;
padding: 0 20px;
padding: 0 25px;
box-sizing: border-box;
min-height: 500px;
max-height: 630px;
height: 100px;
}
.stripedBox {
width: 480px;
flex: none;
position: relative;
width: 400px;
background-color: white;
border-radius: 6px;
box-shadow: 0 0 0 3px rgba(155, 155, 155, 0.4);
box-shadow: 0 0 0 3px rgba(12, 12, 13, 0.2);
background-image: repeating-linear-gradient(
45deg,
white,
@ -68,8 +74,11 @@ a {
.mainContent {
height: 100%;
background-color: white;
box-sizing: border-box;
margin: 0 10px;
padding: 1px 10px 0 10px; /* top wtf? */
padding: 10px 10px 28px;
display: flex;
flex-direction: column;
}
.spacer {
@ -77,7 +86,8 @@ a {
}
.uploads {
flex: auto;
flex: 0 0 262px;
position: relative;
}
.noscript {
@ -87,13 +97,19 @@ a {
}
.btn {
font-size: 15px;
display: block;
width: 100%;
height: 70px;
line-height: 70px;
font-size: 21px;
font-weight: 500;
color: var(--primaryControlFGColor);
cursor: pointer;
text-transform: uppercase;
text-align: center;
letter-spacing: 0.56px;
color: var(--primaryControlFGColor);
background: var(--primaryControlBGColor);
border: 1px solid var(--primaryControlBGColor);
cursor: pointer;
border: 0;
border-radius: 5px;
}
@ -101,75 +117,52 @@ a {
background-color: var(--primaryControlHoverColor);
}
.btn--cancel {
color: var(--errorColor);
background: var(--pageBGColor);
font-size: 15px;
border: 0;
cursor: pointer;
text-decoration: underline;
.btn--stripes {
background: repeating-linear-gradient(
-65deg,
#7c7c7c 0,
#7c7c7c 17px,
#737373 17px,
#737373 30px
);
background-size: 300% 300%;
animation: barberpole 12s linear infinite;
}
.btn--cancel:disabled {
text-decoration: none;
cursor: auto;
}
@keyframes barberpole {
0% {
background-position: 100% 0%;
}
.btn--cancel:hover {
background-color: var(--pageBGColor);
100% {
background-position: 0% 0%;
}
}
.input {
flex: 2 0 auto;
border: 1px solid var(--primaryControlBGColor);
border-radius: 6px 0 0 6px;
border: 1px solid var(--lightBorderColor);
font-size: 20px;
color: var(--inputTextColor);
font-family: 'SF Pro Text', sans-serif;
letter-spacing: 0;
line-height: 23px;
font-weight: 300;
height: 46px;
padding-left: 10px;
padding-right: 10px;
}
.input--error {
border-color: var(--errorColor);
}
.input--noBtn {
border-radius: 6px;
}
.inputBtn {
flex: auto;
background: var(--primaryControlBGColor);
border-radius: 0 6px 6px 0;
border: 1px solid var(--primaryControlBGColor);
color: var(--primaryControlFGColor);
cursor: pointer;
/* Force flat button look */
/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
appearance: none;
font-size: 15px;
padding-bottom: 3px;
padding-left: 10px;
padding-right: 10px;
white-space: nowrap;
.input--error {
border-color: var(--errorColor);
}
.inputBtn:disabled {
cursor: auto;
.inputBtn.inputError {
background-color: var(--errorColor);
}
.inputBtn:hover {
background-color: var(--primaryControlHoverColor);
}
.inputBtn--hidden {
display: none;
.inputBtn.inputError:hover {
background-color: var(--errorColor);
}
.cursor--pointer {
@ -188,15 +181,15 @@ a {
}
.link--action {
text-decoration: underline;
font-weight: 500;
font-size: 14px;
text-align: center;
}
.page {
margin: 0 auto 30px;
height: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
}
@ -223,6 +216,13 @@ a {
animation: fadeout 200ms linear;
}
.goBackButton {
position: absolute;
top: 0;
left: 0;
margin: 18px;
}
@keyframes fadeout {
0% {
opacity: 1;
@ -250,6 +250,7 @@ a {
.error {
color: var(--errorColor);
font-weight: 600;
}
.title {
@ -264,34 +265,50 @@ a {
}
.description {
font-size: 15px;
line-height: 23px;
max-width: 630px;
text-align: center;
margin: 0 auto 60px;
color: var(--textColor);
width: 92%;
font-size: 13px;
text-align: left;
margin: 14px auto;
color: var(--lightTextColor);
width: 95%;
}
@media (max-device-width: 768px), (max-width: 768px) {
.visible {
visibility: visible !important;
}
.noDisplay {
display: none;
}
.flexible {
flex: 1;
}
@media (max-device-width: 750px), (max-width: 750px) {
.description {
margin: 0 auto 25px;
}
}
@media (max-device-width: 520px), (max-width: 520px) {
.input {
font-size: 22px;
padding: 10px 10px;
border-radius: 6px 6px 0 0;
.main {
flex-direction: column;
min-height: 700px;
}
.inputBtn {
border-radius: 0 0 6px 6px;
flex: 0 1 65px;
.spacer {
flex: none;
height: 0;
}
.input--noBtn {
border-radius: 6px;
.stripedBox {
max-height: 550px;
flex: 1;
}
.uploads {
flex: none;
}
.footer {
margin: 15px;
}
}

View File

@ -1,12 +1,12 @@
/* global MAXFILESIZE */
import Archive from './archive';
import { bytes } from './utils';
import { checkSize } from './utils';
export default function(state, emitter) {
emitter.on('DOMContentLoaded', () => {
document.body.addEventListener('dragover', event => {
if (state.route === '/') {
event.preventDefault();
const files = document.querySelector('.uploadedFilesWrapper');
files.classList.add('uploadArea--noEvents');
}
});
document.body.addEventListener('drop', event => {
@ -15,21 +15,12 @@ export default function(state, emitter) {
document
.querySelector('.uploadArea')
.classList.remove('uploadArea--dragging');
const target = event.dataTransfer;
if (target.files.length === 0) {
return;
}
const file = new Archive(target.files);
if (file.size === 0) {
return;
}
if (file.size > MAXFILESIZE) {
// eslint-disable-next-line no-alert
alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
return;
}
emitter.emit('upload', { file, type: 'drop' });
const target = event.dataTransfer;
checkSize(target.files, state.files);
emitter.emit('addFiles', { files: target.files });
}
});
});

View File

@ -1,17 +1,14 @@
import FileSender from './fileSender';
import FileReceiver from './fileReceiver';
import {
copyToClipboard,
delay,
fadeOut,
openLinksInNewTab,
percent
} from './utils';
import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
import * as metrics from './metrics';
import { hasPassword } from './api';
import Archive from './archive';
export default function(state, emitter) {
let lastRender = 0;
let updateTitle = false;
state.files = [];
function render() {
emitter.emit('render');
@ -64,6 +61,16 @@ export default function(state, emitter) {
metrics.changedDownloadLimit(file);
});
emitter.on('removeUpload', async ({ file }) => {
for (let i = 0; i < state.files.length; i++) {
if (state.files[i] === file) {
state.files.splice(i, 1);
render();
return;
}
}
});
emitter.on('delete', async ({ file, location }) => {
try {
metrics.deletedUpload({
@ -85,11 +92,22 @@ export default function(state, emitter) {
state.transfer.cancel();
});
emitter.on('upload', async ({ file, type }) => {
emitter.on('addFiles', async ({ files }) => {
for (let i = 0; i < files.length; i++) {
state.files.push(files[i]);
}
render();
});
//TODO: hook up to multi-file upload functionality
emitter.on('upload', async ({ files, type, dlCount, password }) => {
const file = new Archive(files);
const size = file.size;
const sender = new FileSender(file);
sender.on('progress', updateProgress);
sender.on('encrypting', render);
sender.on('complete', render);
state.transfer = sender;
state.uploading = true;
render();
@ -98,19 +116,25 @@ export default function(state, emitter) {
await delay(200);
try {
metrics.startedUpload({ size, type });
const ownedFile = await sender.upload();
ownedFile.type = type;
state.storage.totalUploads += 1;
metrics.completedUpload(ownedFile);
state.storage.addFile(ownedFile);
if (password) {
emitter.emit('password', { password, file: ownedFile });
}
emitter.emit('changeLimit', { file: ownedFile, value: dlCount });
const cancelBtn = document.getElementById('cancel-upload');
if (cancelBtn) {
cancelBtn.hidden = 'hidden';
}
if (document.querySelector('.page')) {
await delay(1000);
await fadeOut('.page');
}
emitter.emit('pushState', `/share/${ownedFile.id}`);
} catch (err) {
@ -127,6 +151,8 @@ export default function(state, emitter) {
}
} finally {
openLinksInNewTab(links, false);
state.files = [];
state.password = '';
state.uploading = false;
state.transfer = null;
}
@ -150,6 +176,17 @@ export default function(state, emitter) {
render();
});
emitter.on('getPasswordExist', async ({ id }) => {
try {
state.fileInfo = await hasPassword(id);
render();
} catch (e) {
if (e.message === '404') {
return emitter.emit('pushState', '/404');
}
}
});
emitter.on('getMetadata', async () => {
const file = state.fileInfo;
@ -172,6 +209,7 @@ export default function(state, emitter) {
emitter.on('download', async file => {
state.transfer.on('progress', updateProgress);
state.transfer.on('decrypting', render);
state.transfer.on('complete', render);
const links = openLinksInNewTab();
const size = file.size;
try {
@ -186,12 +224,11 @@ export default function(state, emitter) {
const speed = size / (time / 1000);
if (document.querySelector('.page')) {
await delay(1000);
await fadeOut('.page');
}
state.storage.totalDownloads += 1;
state.transfer.reset();
metrics.completedDownload({ size, time, speed });
emitter.emit('pushState', '/completed');
//emitter.emit('pushState', '/completed');
} catch (err) {
if (err.message === '0') {
// download cancelled

View File

@ -172,6 +172,7 @@ export default class FileReceiver extends Nanobus {
this.downloadRequest = null;
this.msg = 'downloadFinish';
this.emit('complete');
this.state = 'complete';
} catch (e) {
this.downloadRequest = null;

View File

@ -93,6 +93,7 @@ export default class FileSender extends Nanobus {
url: `${result.url}#${secretKey}`,
name: this.file.name,
size: this.file.size,
manifest: this.file.manifest,
time: time,
speed: this.file.size / (time / 1000),
createdAt: Date.now(),
@ -101,6 +102,7 @@ export default class FileSender extends Nanobus {
nonce: this.keychain.nonce,
ownerToken: result.ownerToken
});
return ownedFile;
} catch (e) {
this.msg = 'errorPageHeader';

View File

@ -2,7 +2,6 @@
@import './templates/activeBackground/activeBackground.css';
@import './templates/header/header.css';
@import './templates/downloadButton/downloadButton.css';
@import './templates/progress/progress.css';
@import './templates/passwordInput/passwordInput.css';
@import './templates/downloadPassword/downloadPassword.css';
@import './templates/setPasswordSection/setPasswordSection.css';
@ -11,7 +10,14 @@
@import './templates/selectbox/selectbox.css';
@import './templates/fileList/fileList.css';
@import './templates/file/file.css';
@import './templates/uploadedFile/uploadedFile.css';
@import './templates/uploadedFileList/uploadedFileList.css';
@import './templates/popup/popup.css';
@import './templates/title/title.css';
@import './templates/fileIcon/fileIcon.css';
@import './templates/signupPromo/signupPromo.css';
@import './templates/userAccount/userAccount.css';
@import './pages/welcome/welcome.css';
@import './pages/share/share.css';
@import './pages/signin/signin.css';
@import './pages/unsupported/unsupported.css';

View File

@ -9,6 +9,7 @@ export default class OwnedFile {
this.name = obj.name;
this.size = obj.size;
this.type = obj.type;
this.manifest = obj.manifest;
this.time = obj.time;
this.speed = obj.speed;
this.createdAt = obj.createdAt;
@ -70,6 +71,7 @@ export default class OwnedFile {
name: this.name,
size: this.size,
type: this.type,
manifest: this.manifest,
time: this.time,
speed: this.speed,
createdAt: this.createdAt,

View File

@ -1,26 +0,0 @@
const html = require('choo/html');
const progress = require('../../templates/progress');
const { fadeOut } = require('../../utils');
module.exports = function(state, emit) {
return html`
<div class="page effect--fadeIn">
<div class="title">
${state.translate('downloadFinish')}
</div>
<div class="description"></div>
${progress(1)}
<div class="progressSection">
<div class="progressSection__text"></div>
</div>
<a class="link link--action"
href="/"
onclick=${sendNew}>${state.translate('sendYourFilesLink')}</a>
</div>`;
async function sendNew(e) {
e.preventDefault();
await fadeOut('.page');
emit('pushState', '/');
}
};

View File

@ -1,42 +0,0 @@
const html = require('choo/html');
const progress = require('../../templates/progress');
const { bytes } = require('../../utils');
module.exports = function(state, emit) {
const transfer = state.transfer;
const cancelBtn = html`
<button
id="cancel"
class="btn btn--cancel"
title="${state.translate('deletePopupCancel')}"
onclick=${cancel}>
${state.translate('deletePopupCancel')}
</button>`;
return html`
<div class="page effect--fadeIn">
<div class="title">
${state.translate('downloadingPageProgress', {
filename: state.fileInfo.name,
size: bytes(state.fileInfo.size)
})}
</div>
<div class="description">
${state.translate('downloadingPageMessage')}
</div>
${progress(transfer.progressRatio, transfer.progressIndefinite)}
<div class="progressSection">
<div class="progressSection__text">
${state.translate(transfer.msg, transfer.sizes)}
</div>
${transfer.state === 'downloading' ? cancelBtn : null}
</div>
</div>
`;
function cancel() {
const btn = document.getElementById('cancel');
btn.remove();
emit('cancel');
}
};

View File

@ -1,10 +1,22 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
const title = require('../../templates/title');
module.exports = function(state) {
return html`
<div class="page">
<div class="title">${state.translate('errorPageHeader')}</div>
<img src="${assets.get('illustration_error.svg')}"/>
${title(state)}
<div class="error">${state.translate('errorPageHeader')}</div>
<img class="flexible" src="${assets.get('illustration_error.svg')}"/>
<div class="description">
${state.translate('uploadPageExplainer')}
</div>
<a class="link link--action" href="/">
${state.translate('sendYourFilesLink')}
</a>
</div>`;
};

View File

@ -1,11 +1,17 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
const title = require('../../templates/title');
module.exports = function(state) {
return html`
<div class="page">
<div class="title">${state.translate('expiredPageHeader')}</div>
<img src="${assets.get('illustration_expired.svg')}" id="expired-img">
${title(state)}
<div class="error">${state.translate('expiredPageHeader')}</div>
<img src="${assets.get(
'illustration_expired.svg'
)}" class="flexible" id="expired-img">
<div class="description">
${state.translate('uploadPageExplainer')}
</div>

View File

@ -0,0 +1,19 @@
const html = require('choo/html');
const titleSection = require('../../templates/title');
const downloadPassword = require('../../templates/downloadPassword');
module.exports = function(state, emit) {
return html`
<div class="page">
${titleSection(state)}
<div class="description">${state.translate('downloadMessage2')}</div>
${downloadPassword(state, emit)}
<a class="link link--action" href="/">
${state.translate('sendYourFilesLink')}
</a>
</div>
`;
};

View File

@ -1,40 +1,25 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
const { bytes } = require('../../utils');
const titleSection = require('../../templates/title');
const downloadButton = require('../../templates/downloadButton');
const downloadedFiles = require('../../templates/uploadedFileList');
module.exports = function(state, pageAction) {
const fileInfo = state.fileInfo;
module.exports = function(state, emit) {
const storageFile = state.storage.getFileById(state.params.id);
const size = fileInfo.size
? state.translate('downloadFileSize', { size: bytes(fileInfo.size) })
: '';
const multifiles = Array.from(storageFile.manifest.files);
const title = fileInfo.name
? state.translate('downloadFileName', { filename: fileInfo.name })
: state.translate('downloadFileTitle');
const info = html`
<div id="dl-file"
data-nonce="${fileInfo.nonce}"
data-requires-password="${fileInfo.requiresPassword}"></div>`;
if (!pageAction) {
return info;
}
return html`
<div class="page">
<div class="title">
<span>${title}</span>
<span>${' ' + size}</span>
</div>
<div class="description">${state.translate('downloadMessage')}</div>
<img
src="${assets.get('illustration_download.svg')}"
title="${state.translate('downloadAltText')}"/>
${pageAction}
${titleSection(state)}
${downloadedFiles(multifiles, state, emit)}
<div class="description">${state.translate('downloadMessage2')}</div>
${downloadButton(state, emit)}
<a class="link link--action" href="/">
${state.translate('sendYourFilesLink')}
</a>
${info}
</div>
`;
};

View File

@ -3,42 +3,51 @@ const html = require('choo/html');
const raw = require('choo/html/raw');
const assets = require('../../../common/assets');
const notFound = require('../notFound');
const setPasswordSection = require('../../templates/setPasswordSection');
const selectbox = require('../../templates/selectbox');
const deletePopup = require('../../templates/popup');
const uploadedFiles = require('../../templates/uploadedFileList');
const { allowedCopy, delay, fadeOut } = require('../../utils');
module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id);
if (!file) {
return notFound(state, emit);
return notFound(state);
}
const passwordReminderClass = file._hasPassword
? ''
: 'passwordReminder--hidden';
const multifiles = Array.from(file.manifest.files);
return html`
<div id="shareWrapper" class="effect--fadeIn">
${expireInfo(file, state.translate, emit)}
<div class="sharePage">
<div class="page effect--fadeIn" id="shareWrapper">
<a href="/" class="goBackButton">
<img src="${assets.get('back-arrow.svg')}"/>
</a>
${expireInfo(file, state.translate)}
${uploadedFiles(multifiles, state, emit)}
<div class="sharePage__copyText">
${state.translate('copyUrlFormLabelWithName', { filename: file.name })}
${state.translate('copyUrlFormLabelWithName', { filename: '' })}
<div class="sharePage__passwordReminder ${passwordReminderClass}">(don't forget the password too)</div>
</div>
<div class="copySection">
<input
id="fileUrl"
class="copySection__url"
type="url"
value="${file.url}"
readonly="true"/>
<button id="copyBtn"
class="inputBtn inputBtn--copy"
title="${state.translate('copyUrlFormButton')}"
onclick=${copyLink}>${state.translate('copyUrlFormButton')}</button>
</div>
${setPasswordSection(state, emit)}
<button
class="btn btn--delete"
title="${state.translate('deleteFileButton')}"
onclick=${showPopup}>${state.translate('deleteFileButton')}
<input
id="fileUrl"
class="copySection__url"
type="url"
value="${file.url}"
readonly="true"/>
<button id="copyBtn"
class="btn copyBtn"
title="${state.translate('copyUrlFormButton')}"
onclick=${copyLink}>${state.translate('copyUrlFormButton')}
</button>
<div class="sharePage__deletePopup">
${deletePopup(
state.translate('deletePopupText'),
@ -47,11 +56,15 @@ module.exports = function(state, emit) {
deleteFile
)}
</div>
<a class="link link--action"
href="/"
onclick=${sendNew}>${state.translate('sendAnotherFileLink')}</a>
<a
class="error btn--delete"
title="${state.translate('deleteFileButton')}"
onclick=${showPopup}>${state.translate('deleteFileButton')}
</a>
</div>
</div>
`;
function showPopup() {
@ -60,30 +73,22 @@ module.exports = function(state, emit) {
popup.focus();
}
async function sendNew(e) {
e.preventDefault();
await fadeOut('#shareWrapper');
emit('pushState', '/');
}
async function copyLink() {
if (allowedCopy()) {
emit('copy', { url: file.url, location: 'success-screen' });
const input = document.getElementById('fileUrl');
input.disabled = true;
input.classList.add('input--copied');
const copyBtn = document.getElementById('copyBtn');
copyBtn.disabled = true;
copyBtn.classList.add('inputBtn--copied');
copyBtn.classList.add('copyBtn--copied');
copyBtn.replaceChild(
html`<img src="${assets.get('check-16.svg')}" class="cursor--pointer">`,
html`<label>${state.translate('copiedUrl')}</label>`,
copyBtn.firstChild
);
await delay(2000);
input.disabled = false;
input.classList.remove('input--copied');
copyBtn.disabled = false;
copyBtn.classList.remove('inputBtn--copied');
copyBtn.classList.remove('copyBtn--copied');
copyBtn.textContent = state.translate('copyUrlFormButton');
}
}
@ -95,18 +100,14 @@ module.exports = function(state, emit) {
}
};
function expireInfo(file, translate, emit) {
function expireInfo(file, translate) {
const hours = Math.floor(EXPIRE_SECONDS / 60 / 60);
const el = html`<div class="title">${raw(
const el = html`<div class="shareTitle">${raw(
translate('expireInfo', {
downloadCount: '<select></select>',
downloadCount: translate('downloadCount', { num: file.dlimit }),
timespan: translate('timespanHours', { num: hours })
})
)}</div>`;
const select = el.querySelector('select');
const options = [1, 2, 3, 4, 5, 20].filter(i => i > (file.dtotal || 0));
const t = num => translate('downloadCount', { num });
const changed = value => emit('changeLimit', { file, value });
el.replaceChild(selectbox(file.dlimit || 1, options, t, changed), select);
return el;
}

View File

@ -1,112 +1,86 @@
.sharePage {
margin: 0 auto;
display: flex;
justify-content: center;
flex-direction: column;
width: 100%;
max-width: 640px;
.sharePage__copyText {
margin: 8px 0 8px;
word-wrap: break-word;
font-size: 15px;
color: var(--lightTextColor);
text-align: center;
}
.sharePage__copyText {
align-self: flex-start;
margin-top: 60px;
margin-bottom: 10px;
color: var(--textColor);
max-width: 614px;
word-wrap: break-word;
.sharePage__passwordReminder {
font-size: 11px;
font-style: italic;
}
.passwordReminder--hidden {
display: none;
}
.sharePage__deletePopup {
position: relative;
align-self: center;
bottom: 50px;
margin-top: -70px;
pointer-events: none;
}
.shareTitle {
color: var(--textColor);
margin: 8px auto 15px;
text-align: center;
font-family: 'SF Pro Text', sans-serif;
font-size: 12px;
font-style: italic;
width: 280px;
}
.copySection {
display: flex;
flex-wrap: nowrap;
width: 100%;
}
.copySection__url {
flex: 1;
height: 56px;
border: 1px solid var(--primaryControlBGColor);
border-radius: 6px 0 0 6px;
font-size: 20px;
color: var(--inputTextColor);
box-sizing: border-box;
height: 30px;
border: 1px solid var(--lightBorderColor);
border-radius: 4px;
font-size: 15px;
color: var(--primaryControlBGColor);
margin: 0 0 6px;
padding: 6px;
font-family: 'SF Pro Text', sans-serif;
letter-spacing: 0;
line-height: 23px;
font-weight: 300;
padding-left: 10px;
line-height: 18px;
font-weight: 500;
}
.copySection__url:disabled {
border: 1px solid var(--successControlBGColor);
background: var(--successControlFGColor);
}
.inputBtn--copy {
flex: 0 1 165px;
padding-bottom: 4px;
}
.input--copied {
border-color: var(--successControlBGColor);
}
.inputBtn--copied,
.inputBtn--copied:hover {
.copyBtn {
transition: background 0.5s;
}
.copyBtn--copied,
.copyBtn--copied:hover {
background: var(--successControlBGColor);
border: 1px solid var(--successControlBGColor);
color: var(--successControlFGColor);
transition: background 0s;
}
.btn--delete {
border: none;
align-self: center;
width: 176px;
height: 44px;
background: #fff;
border-color: rgba(12, 12, 13, 0.3);
margin-top: 50px;
margin-bottom: 12px;
color: #313131;
margin: 10px 0 0;
font-size: 14px;
line-height: 16px;
color: var(--errorColor);
cursor: pointer;
}
.btn--delete:hover {
background: #efeff1;
}
@media (max-device-width: 768px), (max-width: 768px) {
.copySection {
width: 100%;
}
.copySection__url {
font-size: 18px;
}
}
@media (max-device-width: 520px), (max-width: 520px) {
.copySection {
width: 100%;
flex-direction: column;
padding-left: 0;
}
.copySection__url {
font-size: 22px;
padding: 15px 10px;
border-radius: 6px 6px 0 0;
}
.sharePage__copyText {
text-align: center;
}
.inputBtn--copy {
border-radius: 0 0 6px 6px;
flex: 0 1 65px;
}
text-decoration: underline;
}

60
app/pages/signin/index.js Normal file
View File

@ -0,0 +1,60 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
const title = require('../../templates/title');
// eslint-disable-next-line no-unused-vars
module.exports = function(state, emit) {
return html`
<div class="page signInPage">
<a href="/" class="goBackButton">
<img src="${assets.get('back-arrow.svg')}"/>
</a>
${title(state)}
<div class="signIn__info flexible">
${state.translate('accountBenefitTitle')}
<ul>
<li>${state.translate('accountBenefitMultiFile')}</li>
<li>${state.translate('accountBenefitLargeFiles')}</li>
<li>${state.translate('accountBenefitExpiry')}</li>
<li>${state.translate('accountBenefitSync')}</li>
<li>${state.translate('accountBenefitNotify')}</li>
<li>${state.translate('accountBenefitMore')}</li>
</ul>
</div>
<div class="signIn__form flexible">
<img class="signIn__firefoxLogo"
src="${assets.get('firefox_logo-only.svg')}"
width=56 height=56
alt="Firefox logo"/>
<div class="signIn__emailLabel">
${state.translate('signInEmailEnter')}
</div>
${state.translate('signInContinueMessage')}
<form
data-no-csrf>
<input
type="text"
class="signIn__emailInput"
placeholder=${state.translate('emailEntryPlaceholder')}/>
<input
class='noDisplay'
id="emailSubmit"
type="submit"/>
</form>
</div>
<label class="btn" for="emailSubmit">
${state.translate('signInContinueButton')}
</label>
</div>
`;
};

View File

@ -0,0 +1,33 @@
.signInPage {
font-size: 13px;
line-height: 18px;
color: var(--lightTextColor);
}
.signIn__info {
width: 308px;
margin: 16px auto 0;
text-align: left;
}
.signIn__firefoxLogo {
display: block;
margin: 0 auto;
}
.signIn__emailLabel {
font-size: 22px;
margin: 8px 0;
}
.signIn__emailInput {
box-sizing: border-box;
width: 310px;
height: 40px;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
margin: 0;
padding: 0 8px;
font-size: 18px;
color: var(--lightTextColor);
}

View File

@ -1,11 +1,13 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
const title = require('../../templates/title');
module.exports = function(state) {
let strings = {};
let why = '';
let url = '';
let buttonAction = '';
if (state.params.reason !== 'outdated') {
strings = unsupportedStrings(state);
why = html`
@ -28,20 +30,26 @@ module.exports = function(state) {
${strings.button}
</div>`;
}
return html`
<div class="unsupportedPage">
<div class="title">${strings.title}</div>
<div class="description">
<div class="page unsupportedPage">
${title(state)}
<div class="error unsupportedPage__error">${strings.header}</div>
<div class="description flexible">
${strings.description}
${why}
</div>
${why}
<a href="${url}" class="firefoxDownload">
<img
src="${assets.get('firefox_logo-only.svg')}"
class="firefoxDownload__logo"
alt="Firefox"/>
${buttonAction}
</a>
<div class="flexible firefoxDownload">
<a href="${url}" class="firefoxDownload__button">
<img
src="${assets.get('firefox_logo-only.svg')}"
class="firefoxDownload__logo"
alt="Firefox"/>
${buttonAction}
</a>
</div>
<div class="unsupportedPage__info">
${strings.explainer}
</div>
@ -50,7 +58,7 @@ module.exports = function(state) {
function outdatedStrings(state) {
return {
title: state.translate('notSupportedHeader'),
header: state.translate('notSupportedHeader'),
description: state.translate('notSupportedOutdatedDetail'),
button: state.translate('updateFirefox'),
explainer: state.translate('uploadPageExplainer')
@ -59,7 +67,7 @@ function outdatedStrings(state) {
function unsupportedStrings(state) {
return {
title: state.translate('notSupportedHeader'),
header: state.translate('notSupportedHeader'),
description: state.translate('notSupportedDetail'),
button: state.translate('downloadFirefoxButtonSub'),
explainer: state.translate('uploadPageExplainer')

View File

@ -1,26 +1,30 @@
.unsupportedPage {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.unsupportedPage__error {
margin: 10px 0 20px;
}
.unsupportedPage__info {
font-size: 13px;
line-height: 23px;
text-align: center;
color: var(--lightTextColor);
margin: 0 auto 23px;
margin: 0 auto 10px;
}
.firefoxDownload {
margin-bottom: 181px;
height: 80px;
background: #98e02b;
flex: 2;
}
.firefoxDownload__button {
margin: 0 auto 20px;
height: 70px;
width: 250px;
background: #12bc00;
border-radius: 3px;
cursor: pointer;
border: 0;
box-shadow: 0 5px 3px rgb(234, 234, 234);
font-family: 'Fira Sans', 'segoe ui', sans-serif;
font-weight: 500;
color: var(--primaryControlFGColor);
@ -29,21 +33,22 @@
justify-content: center;
align-items: center;
line-height: 1;
padding: 0 25px;
padding: 8px;
}
.firefoxDownload__logo {
width: 70px;
width: 55px;
}
.firefoxDownload__action {
text-align: left;
margin-left: 20.4px;
text-transform: uppercase;
margin-left: 20px;
}
.firefoxDownload__text {
text-transform: none;
font-family: 'Fira Sans', 'segoe ui', sans-serif;
font-weight: 300;
font-size: 18px;
letter-spacing: -0.69px;
font-size: 17px;
}

View File

@ -1,39 +0,0 @@
const html = require('choo/html');
const progress = require('../../templates/progress');
const { bytes } = require('../../utils');
module.exports = function(state, emit) {
const transfer = state.transfer;
return html`
<div class="page effect--fadeIn">
<div class="title">
${state.translate('uploadingPageProgress', {
filename: transfer.file.name,
size: bytes(transfer.file.size)
})}
</div>
<div class="description"></div>
${progress(transfer.progressRatio, transfer.progressIndefinite)}
<div class="progressSection">
<div class="progressSection__text">
${state.translate(transfer.msg, transfer.sizes)}
</div>
<button
id="cancel-upload"
class="btn btn--cancel"
title="${state.translate('uploadingPageCancel')}"
onclick=${cancel}>
${state.translate('uploadingPageCancel')}
</button>
</div>
</div>
`;
function cancel() {
const btn = document.getElementById('cancel-upload');
btn.disabled = true;
btn.textContent = state.translate('uploadCancelNotification');
emit('cancel');
}
};

View File

@ -1,50 +1,86 @@
/* global MAXFILESIZE */
const html = require('choo/html');
const assets = require('../../../common/assets');
const fileList = require('../../templates/fileList');
const { bytes, fadeOut } = require('../../utils');
const { checkSize } = require('../../utils');
const title = require('../../templates/title');
const setPasswordSection = require('../../templates/setPasswordSection');
const uploadBox = require('../../templates/uploadedFileList');
const expireInfo = require('../../templates/expireInfo');
module.exports = function(state, emit) {
// the page flickers if both the server and browser set 'effect--fadeIn'
const fade = state.layout ? '' : 'effect--fadeIn';
const files = state.files ? state.files : [];
const optionClass = state.uploading ? 'uploadOptions--faded' : '';
const btnUploading = state.uploading ? 'btn--stripes' : '';
const faded = files.length > 0 ? 'uploadArea--faded' : '';
const selectFileClass = files.length > 0 ? 'btn--hidden' : '';
const sendFileClass = files.length > 0 ? '' : 'btn--hidden';
let btnText = '';
if (state.encrypting) {
btnText = state.translate('encryptingFile');
} else if (state.uploading) {
btnText = `sending... ${Math.floor(state.transfer.progressRatio * 100)}%`;
} else {
//default pre-upload text
btnText = state.translate('uploadSuccessConfirmHeader');
}
return html`
<div id="page-one" class="${fade}">
<div class="title">${state.translate('uploadPageHeader')}</div>
<div class="description">
<div>${state.translate('uploadPageExplainer')}</div>
<a
href="https://testpilot.firefox.com/experiments/send"
class="link">
${state.translate('uploadPageLearnMore')}
</a>
</div>
<div class="uploadArea"
<div id="page-one" class="page ${fade}">
${title(state)}
<label class="uploadArea"
ondragover=${dragover}
ondragleave=${dragleave}>
<img
src="${assets.get('upload.svg')}"
title="${state.translate('uploadSvgAlt')}"/>
<div class="uploadArea__msg">
${state.translate('uploadPageDropMessage')}
${uploadBox(files, state, emit)}
<div class="uploadedFilesWrapper ${faded}">
<img
class="uploadArea__icon"
src="${assets.get('addfile.svg')}"
title="${state.translate('uploadSvgAlt')}"/>
<div class="uploadArea__msg">
${state.translate('uploadDropDragMessage')}
</div>
<span class="uploadArea__clickMsg">
${state.translate('uploadDropClickMessage')}
</span>
</div>
<span class="uploadArea__sizeMsg">
${state.translate('uploadPageSizeMessage')}
</span>
<input id="file-upload"
class="inputFile"
class="inputFile fileBox"
type="file"
multiple
name="fileUploaded"
onfocus=${onfocus}
onblur=${onblur}
onchange=${upload} />
<label for="file-upload"
class="btn btn--file"
title="${state.translate('uploadPageBrowseButton1')}">
${state.translate('uploadPageBrowseButton1')}
</label>
onchange=${addFiles} />
</label>
<div class="uploadOptions ${optionClass}">
${expireInfo(state)}
${setPasswordSection(state)}
</div>
${fileList(state, emit)}
<label for="file-upload"
class="btn btn--file ${selectFileClass}"
title="${state.translate('uploadPageBrowseButton1')}">
${state.translate('uploadPageBrowseButton1')}
</label>
<button
class="btn ${btnUploading} ${sendFileClass}"
onclick=${upload}
title="${btnText}">
${btnText}
</button>
</div>
`;
@ -66,22 +102,23 @@ module.exports = function(state, emit) {
event.target.classList.remove('inputFile--focused');
}
async function addFiles(event) {
event.preventDefault();
const target = event.target;
checkSize(target.files, state.files);
emit('addFiles', { files: target.files });
}
async function upload(event) {
event.preventDefault();
const Archive = require('../../archive').default;
const target = event.target;
const file = new Archive(target.files);
if (file.size === 0) {
return;
if (files.length > 0) {
emit('upload', {
files,
type: 'click',
dlCount: state.downloadCount,
password: state.password
});
}
if (file.size > MAXFILESIZE) {
// eslint-disable-next-line no-alert
alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
return;
}
await fadeOut('#page-one');
emit('upload', { file, type: 'click' });
}
};

View File

@ -1,61 +1,64 @@
.uploadArea {
border: 3px dashed rgba(0, 148, 251, 0.5);
margin: 0 auto 10px;
height: 255px;
border-radius: 4px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
justify-content: center;
text-align: center;
border: 1px dashed rgba(12, 12, 13, 0.2);
margin: 0 0 10px;
height: 400px;
border-radius: 4px;
overflow: scroll;
transition: transform 150ms;
padding: 15px;
flex: 1;
}
.uploadArea__msg {
font-size: 22px;
font-size: 15px;
color: var(--lightTextColor);
margin: 20px 0 10px;
margin: 12px 0 0;
font-family: 'SF Pro Text', sans-serif;
text-transform: uppercase;
font-weight: bold;
}
.uploadArea__sizeMsg {
.uploadArea__clickMsg {
font-style: italic;
font-size: 12px;
line-height: 16px;
line-height: 12px;
color: var(--lightTextColor);
margin-bottom: 22px;
margin: 5px;
}
.uploadArea--dragging {
border: 5px dashed rgba(0, 148, 251, 0.5);
height: 251px;
border: 1px dashed rgba(12, 12, 13, 0.4);
transform: scale(1.04);
border-radius: 4.2px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
}
.uploadArea--dragging * {
.uploadArea--faded * {
opacity: 0.5;
}
.uploadArea--noEvents,
.uploadArea--noEvents * {
pointer-events: none;
}
.btn--file {
font-size: 20px;
min-width: 240px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
padding: 0 10px;
display: inline-block;
background-color: #737373;
}
.btn--file:hover {
background-color: #636363;
}
.btn--hidden {
display: none;
}
.inputFile {
opacity: 0;
position: absolute;
display: none;
}
.inputFile--focused + .btn--file {
@ -63,3 +66,24 @@
outline: 1px dotted #000;
outline: -webkit-focus-ring-color auto 5px;
}
.uploadArea > .uploadedFiles {
position: absolute;
top: 0;
left: 0;
flex: none;
width: 100%;
border: none;
z-index: 1;
}
.uploadOptions {
text-align: left;
font-size: 13px;
color: var(--lightTextColor);
}
.uploadOptions--faded {
opacity: 0.5;
pointer-events: none;
}

View File

@ -1,60 +1,24 @@
const preview = require('../pages/preview');
const download = require('../pages/download');
const notFound = require('../pages/notFound');
const downloadPassword = require('../templates/downloadPassword');
const downloadButton = require('../templates/downloadButton');
function hasFileInfo() {
return !!document.getElementById('dl-file');
}
function getFileInfoFromDOM() {
const el = document.getElementById('dl-file');
if (!el) {
return null;
}
return {
nonce: el.getAttribute('data-nonce'),
requiresPassword: !!+el.getAttribute('data-requires-password')
};
}
function createFileInfo(state) {
const metadata = getFileInfoFromDOM();
return {
id: state.params.id,
secretKey: state.params.key,
nonce: metadata.nonce,
requiresPassword: metadata.requiresPassword
};
}
const password = require('../pages/password');
module.exports = function(state, emit) {
if (!state.fileInfo) {
// This is a fresh page load
// We need to parse the file info from the server's html
if (!hasFileInfo()) {
return notFound(state, emit);
}
state.fileInfo = createFileInfo(state);
if (!state.fileInfo.requiresPassword) {
emit('getMetadata');
}
emit('getPasswordExist', { id: state.params.id });
return;
}
state.fileInfo.id = state.params.id;
state.fileInfo.secretKey = state.params.key;
if (!state.transfer && !state.fileInfo.requiresPassword) {
emit('getMetadata');
}
let pageAction = null; //default state: we don't have file metadata
if (state.transfer) {
const s = state.transfer.state;
if (['downloading', 'decrypting', 'complete'].indexOf(s) > -1) {
// Downloading is in progress
return download(state, emit);
}
// we have file metadata
pageAction = downloadButton(state, emit);
} else if (state.fileInfo.requiresPassword && !state.fileInfo.password) {
// we're waiting on the user for a valid password
pageAction = downloadPassword(state, emit);
return preview(state, emit);
}
if (state.fileInfo.requiresPassword && !state.fileInfo.password) {
return password(state, emit);
}
return preview(state, pageAction);
};

View File

@ -1,9 +1,5 @@
const welcome = require('../pages/welcome');
const upload = require('../pages/upload');
module.exports = function(state, emit) {
if (state.uploading) {
return upload(state, emit);
}
return welcome(state, emit);
};

View File

@ -5,7 +5,10 @@ const download = require('./download');
const header = require('../templates/header');
const footer = require('../templates/footer');
const fxPromo = require('../templates/fxPromo');
const signupPromo = require('../templates/signupPromo');
const activeBackground = require('../templates/activeBackground');
const fileList = require('../templates/fileList');
const profile = require('../templates/userAccount');
nanotiming.disabled = true;
const app = choo();
@ -20,6 +23,7 @@ function body(template) {
return function(state, emit) {
const b = html`<body class="background ${activeBackground(state)}">
${banner(state, emit)}
${signupPromo(state)}
${header(state)}
<main class="main">
<noscript>
@ -35,11 +39,17 @@ function body(template) {
</noscript>
<div class="stripedBox">
<div class="mainContent">
${profile(state)}
${template(state, emit)}
</div>
</div>
<div class="spacer"></div>
<div class="uploads"></div>
<div class="uploads">
${fileList(state)}
</div>
</main>
${footer(state)}
</body>`;
@ -55,11 +65,11 @@ app.route('/', body(require('./home')));
app.route('/share/:id', body(require('../pages/share')));
app.route('/download/:id', body(download));
app.route('/download/:id/:key', body(download));
app.route('/completed', body(require('../pages/completed')));
app.route('/unsupported/:reason', body(require('../pages/unsupported')));
app.route('/legal', body(require('../pages/legal')));
app.route('/error', body(require('../pages/error')));
app.route('/blank', body(require('../pages/blank')));
app.route('*', body(require('../pages/notFound')));
app.route('/signin', body(require('../pages/signin')));
module.exports = app;

View File

@ -47,6 +47,7 @@ class Storage {
if (!f.id) {
f.id = f.fileId;
}
fs.push(f);
} catch (err) {
// obviously you're not a golfer

View File

@ -1,6 +1,20 @@
.btn--download {
width: 180px;
height: 44px;
margin-top: 20px;
margin-bottom: 30px;
margin: 0 0 13px;
}
.btn--complete,
.btn--complete:hover {
background-color: var(--successControlBGColor);
}
.btn--blueStripes {
background: repeating-linear-gradient(
-65deg,
#3282f2 0,
#3282f2 17px,
#3c87eb 17px,
#3c87eb 30px
);
background-size: 300% 300%;
animation: barberpole 12s linear infinite;
}

View File

@ -1,13 +1,36 @@
const html = require('choo/html');
const percent = require('../../utils').percent;
module.exports = function(state, emit) {
const downloadState = state.transfer.state;
const progress = percent(state.transfer.progressRatio);
let btnText = '';
let btnClass = '';
if (downloadState === 'complete') {
btnText = state.translate('downloadFinish');
btnClass = 'btn--complete';
} else if (downloadState === 'decrypting') {
btnText = state.translate('decryptingFile');
btnClass = 'btn--blueStripes';
} else if (downloadState === 'downloading') {
btnText = state.translate('downloadProgressButton', { progress });
btnClass = 'btn--blueStripes';
} else {
btnText = state.translate('downloadButtonLabel');
}
return html`
<button class="btn btn--download"
onclick=${download}>${state.translate('downloadButtonLabel')}
<button class="btn btn--download ${btnClass}"
onclick=${download}>
${btnText}
</button>`;
function download(event) {
event.preventDefault();
emit('download', state.fileInfo);
if (downloadState !== 'complete') {
emit('download', state.fileInfo);
}
}
};

View File

@ -1,22 +1,31 @@
.passwordSection {
text-align: left;
margin: auto;
text-align: center;
padding: 40px 0;
width: 80%;
}
.passwordForm {
display: flex;
flex-wrap: nowrap;
margin: 13px;
}
.passwordForm__input {
width: 100%;
padding: 10px 0;
height: 40px;
box-sizing: border-box;
}
@media (max-device-width: 520px), (max-width: 520px) {
.passwordSection {
width: 100%;
}
.passwordForm {
flex-direction: column;
}
.unlockBtn {
margin-top: 48px;
}
.unlockBtn--error,
.unlockBtn--error:hover {
background-color: var(--errorColor);
}
.passwordForm__error {
font-size: 13px;
font-weight: 600;
visibility: hidden;
}

View File

@ -3,33 +3,33 @@ const html = require('choo/html');
module.exports = function(state, emit) {
const fileInfo = state.fileInfo;
const invalid = fileInfo.password === null;
const label = invalid
? html`
<label class="error" for="password-input">
${state.translate('passwordTryAgain')}
</label>`
: html`
<label for="password-input">
${state.translate('unlockInputLabel')}
</label>`;
const inputClass = invalid
? 'input input--noBtn input--error'
: 'input input--noBtn';
const visible = invalid ? 'visible' : '';
const invalidBtn = invalid ? 'unlockBtn--error' : '';
const div = html`
<div class="passwordSection">
${label}
<label
class="error passwordForm__error ${visible}"
for="password-input">
${state.translate('passwordTryAgain')}
</label>
<form class="passwordForm" onsubmit=${checkPassword} data-no-csrf>
<input id="password-input"
class="${inputClass}"
class="input passwordForm__input"
maxlength="64"
autocomplete="off"
placeholder="${state.translate('unlockInputPlaceholder')}"
oninput=${inputChanged}
type="password" />
<input type="submit"
id="password-btn"
class="inputBtn inputBtn--hidden"
value="${state.translate('unlockButtonLabel')}"/>
class="btn unlockBtn ${invalidBtn}"
value="${state.translate('unlockInputLabel')}"/>
</form>
</div>`;
@ -38,16 +38,10 @@ module.exports = function(state, emit) {
}
function inputChanged() {
const input = document.getElementById('password-input');
const input = document.querySelector('.passwordForm__error');
input.classList.remove('visible');
const btn = document.getElementById('password-btn');
input.classList.remove('input--error');
if (input.value.length > 0) {
btn.classList.remove('inputBtn--hidden');
input.classList.remove('input--noBtn');
} else {
btn.classList.add('inputBtn--hidden');
input.classList.add('input--noBtn');
}
btn.classList.remove('unlockBtn--error');
}
function checkPassword(event) {

View File

@ -0,0 +1,36 @@
const html = require('choo/html');
const raw = require('choo/html/raw');
const selectbox = require('../selectbox');
module.exports = function(state) {
const el = html`<div> ${raw(
state.translate('frontPageExpireInfo', {
downloadCount: '<select id=dlCount></select>',
timespan: state.translate('timespanHours', { num: 24 }) //'<select id=timespan></select>'
})
)}
</div>`;
const dlCountSelect = el.querySelector('#dlCount');
el.replaceChild(
selectbox(
state.downloadCount || 1,
[1, 2, 3, 4, 5, 20],
num => state.translate('downloadCount', { num }),
value => {
state.downloadCount = value;
}
),
dlCountSelect
);
/*
const timeSelect = el.querySelector('#timespan');
el.replaceChild(
selectbox(1, [1, 2, 3, 4, 5], num => num, () => {}),
timeSelect
);
*/
return el;
};

View File

@ -1,26 +1,94 @@
.fileData {
font-size: 15px;
vertical-align: top;
color: var(--lightTextColor);
padding: 17px 19px 0;
line-height: 23px;
position: relative;
}
.fileData--overflow {
text-overflow: ellipsis;
max-width: 0;
.fileToast {
margin: 13px 0 0;
overflow: hidden;
font-size: 11px;
line-height: 18px;
color: var(--lightTextColor);
background-color: var(--pageBGColor);
position: relative;
box-shadow: 0 0 0 3px rgba(12, 12, 12, 0.2);
box-sizing: border-box;
height: 53px;
border-radius: 4px;
}
.fileToast__content {
position: relative;
z-index: 2;
}
.fileToast::after {
position: absolute;
z-index: 1;
content: '';
transition: all 0.25s;
top: 0;
left: 50%;
width: 0;
height: 100%;
background-color: var(--primaryControlBGColor);
}
.fileToast:hover {
background-color: #eee;
}
.fileToast--active {
color: var(--primaryControlFGColor);
}
.fileToast--active::after {
left: 0%;
width: 100%;
}
.fileData {
margin: 8px 16px 8px 44px;
overflow: hidden;
}
.fileName {
margin: 0;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.fileData--center {
text-align: center;
.fileInfo {
margin: 0;
}
@media (max-device-width: 520px), (max-width: 520px) {
.fileToast .fileIcon {
margin: 2px 8px;
}
@media (max-device-width: 750px), (max-width: 750px) {
.fileToast {
height: 32px;
width: 400px;
}
.fileToast__content {
display: flex;
}
.fileData {
font-size: 13px;
padding: 17px 5px 0;
flex: auto;
display: flex;
flex-wrap: nowrap;
margin-left: 8px;
}
.fileInfo {
flex-shrink: 0;
margin-left: auto;
}
.fileToast .fileIcon {
margin: 0;
transform: scale(0.5);
color: transparent;
}
}

View File

@ -1,73 +1,50 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
const number = require('../../utils').number;
const deletePopup = require('../popup');
const bytes = require('../../utils').bytes;
const fileIcon = require('../fileIcon');
module.exports = function(file, state, emit) {
module.exports = function(file, state) {
const ttl = file.expiresAt - Date.now();
const remainingTime =
timeLeft(ttl, state) || state.translate('linkExpiredAlt');
const downloadLimit = file.dlimit || 1;
const totalDownloads = file.dtotal || 0;
const multiFiles = file.manifest.files;
const fileName =
multiFiles.length > 1
? `${multiFiles[0].name} + ${state.translate('fileCount', {
num: multiFiles.length - 1
})}`
: file.name;
const activeClass = isOnSharePage() ? 'fileToast--active' : '';
return html`
<tr id="${file.id}">
<td class="fileData fileData--overflow" title="${file.name}">
<a class="link" href="/share/${file.id}">${file.name}</a>
</td>
<td class="fileData fileData--center">
<img
onclick=${copyClick}
src="${assets.get('copy-16.svg')}"
class="cursor--pointer"
title="${state.translate('copyUrlHover')}"
tabindex="0">
<span hidden="true">
${state.translate('copiedUrl')}
</span>
</td>
<td class="fileData fileData--overflow">${remainingTime}</td>
<td class="fileData fileData--center">${number(totalDownloads)} / ${number(
downloadLimit
)}</td>
<td class="fileData fileData--center">
<img
onclick=${showPopup}
src="${assets.get('close-16.svg')}"
class="cursor--pointer"
title="${state.translate('deleteButtonHover')}"
tabindex="0">
${deletePopup(
state.translate('deletePopupText'),
state.translate('deletePopupYes'),
state.translate('deletePopupCancel'),
deleteFile
)}
</td>
</tr>
<a href=${toastClick()}>
<li class="fileToast ${activeClass}" id="${file.id}">
<div class="fileToast__content">
${fileIcon(file.name, file._hasPassword)}
<div class="fileData">
<p class="fileName">${fileName}</p>
<p class="fileInfo">
<span>${bytes(file.size)}</span> ·
<span>${state.translate('downloadCount', {
num: `${number(totalDownloads)} / ${number(downloadLimit)}`
})}</span>
<span>${remainingTime}</span>
</p>
</div>
</div>
</li>
</a>
`;
function copyClick(e) {
emit('copy', { url: file.url, location: 'upload-list' });
const icon = e.target;
const text = e.target.nextSibling;
icon.hidden = true;
text.hidden = false;
setTimeout(() => {
icon.hidden = false;
text.hidden = true;
}, 500);
function toastClick() {
return isOnSharePage() ? '/' : `/share/${file.id}`;
}
function showPopup() {
const tr = document.getElementById(file.id);
const popup = tr.querySelector('.popup');
popup.classList.add('popup--show');
popup.focus();
}
function deleteFile() {
emit('delete', { file, location: 'upload-list' });
emit('render');
function isOnSharePage() {
return state.href.includes('/share/') && state.params.id === file.id;
}
};

View File

@ -0,0 +1,28 @@
.fileIcon {
position: relative;
float: left;
pointer-events: none;
margin: 8px;
color: #fff;
background-image: url('../assets/red_file.svg');
width: 22px;
height: 32px;
overflow: hidden;
}
.fileIcon__lock {
margin: 7px 0 0 5px;
visibility: hidden;
}
.fileIcon__lock--visible {
visibility: visible;
}
.fileIcon__fileType {
position: absolute;
margin: 16px 0 0 2px;
font-size: 7px;
font-weight: 600;
text-transform: uppercase;
}

View File

@ -0,0 +1,17 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
module.exports = function(name, hasPassword) {
let type = '';
if (name) {
type = name.split('.').pop();
}
const lockClass = hasPassword ? 'fileIcon__lock--visible' : '';
return html`
<div class="fileIcon">
<div class="fileIcon__fileType">${type}</div>
<img class="fileIcon__lock ${lockClass}"src="${assets.get(
'lock-white.svg'
)}"/>
</div>`;
};

View File

@ -1,52 +1,21 @@
.fileList {
margin: 45.3px auto;
table-layout: fixed;
border-collapse: collapse;
position: absolute;
bottom: 0;
list-style-type: none;
margin: 0;
padding: 3px;
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
width: 262px;
max-height: 80%;
overflow-y: scroll;
overflow-x: hidden;
}
.fileList__header {
font-size: 16px;
color: var(--lightTextColor);
font-weight: lighter;
text-align: left;
background: rgba(0, 148, 251, 0.05);
height: 40px;
border-top: 1px solid rgba(0, 148, 251, 0.1);
padding: 0 19px;
white-space: nowrap;
}
.fileList__body {
word-wrap: break-word;
word-break: break-all;
}
.fileList__nameCol {
width: 35%;
}
.fileList__copyCol {
text-align: center;
width: 25%;
}
.fileList__expireCol {
width: 25%;
}
.fileList__dlCol {
width: 8%;
}
.fileList__delCol {
text-align: center;
width: 7%;
}
@media (max-device-width: 520px), (max-width: 520px) {
.fileList__header {
font-size: 14px;
padding: 0 5px;
@media (max-device-width: 750px), (max-width: 750px) {
.fileList {
position: static;
width: 400px;
max-height: 200px;
margin: 6px 0 0 -3px;
}
}

View File

@ -1,33 +1,12 @@
const html = require('choo/html');
const file = require('../file');
module.exports = function(state, emit) {
module.exports = function(state) {
if (state.storage.files.length) {
return html`
<table class="fileList">
<thead>
<tr>
<th class="fileList__header fileList__nameCol">
${state.translate('uploadedFile')}
</th>
<th class="fileList__header fileList__copyCol">
${state.translate('copyFileList')}
</th>
<th class="fileList__header fileList__expireCol" >
${state.translate('timeFileList')}
</th>
<th class="fileList__header fileList__dlCol" >
${state.translate('downloadsFileList')}
</th>
<th class="fileList__header fileList__delCol">
${state.translate('deleteFileList')}
</th>
</tr>
</thead>
<tbody class="fileList__body">
${state.storage.files.map(f => file(f, state, emit))}
</tbody>
</table>
<ul class="fileList">
${state.storage.files.map(f => file(f, state))}
</ul>
`;
}
};

View File

@ -3,13 +3,14 @@
bottom: 0;
left: 0;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: flex-end;
flex-direction: row;
justify-content: space-between;
padding: 50px 31px 41px;
width: 100%;
box-sizing: border-box;
justify-content: flex-end;
}
.legalSection {
@ -20,18 +21,18 @@
}
.legalSection__link {
color: var(--lightTextColor);
opacity: 0.9;
color: #fff;
text-shadow: 0 0 3px #000;
white-space: nowrap;
margin-right: 2vw;
}
.legalSection__link:hover {
opacity: 1;
.legalSection__link:visited {
color: #ededf0;
}
.legalSection__link:visited {
color: var(--lightTextColor);
.legalSection__link:hover {
color: #d7d7db;
}
.legalSection__mozLogo {
@ -60,12 +61,12 @@
margin-bottom: -5px;
}
@media (max-device-width: 768px), (max-width: 768px) {
@media (max-device-width: 750px), (max-width: 750px) {
.footer {
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
max-width: 630px;
padding: 20px 31px;
margin: auto;
}

View File

@ -38,8 +38,6 @@ module.exports = function(state) {
class="legalSection__link">
${state.translate('reportIPInfringement')}
</a>
</div>
<div class="socialSection">
<a
href="https://github.com/mozilla/send"
class="socialSection__link">
@ -52,9 +50,9 @@ module.exports = function(state) {
href="https://twitter.com/FxTestPilot"
class="socialSection__link">
<img
class="socialSection__icon"
src="${assets.get('twitter-icon.svg')}"
alt="twitter"/>
class="legalSection__mozLogo"
src="${assets.get('mozilla-logo.svg')}"
alt="mozilla"/>
</a>
</div>
</footer>`;

View File

@ -3,7 +3,7 @@
box-sizing: border-box;
display: flex;
justify-content: space-between;
padding: 31px;
padding: 20px;
width: 100%;
}
@ -62,7 +62,7 @@
color: var(--primaryControlFGColor);
cursor: pointer;
display: block;
float: right;
float: left;
font-size: 12px;
line-height: 12px;
opacity: 0.9;
@ -88,17 +88,10 @@
background-color: var(--primaryControlHoverColor);
}
@media (max-device-width: 520px), (max-width: 520px) {
@media (max-device-width: 750px), (max-width: 750px) {
.header {
padding-top: 60px;
flex-direction: column;
justify-content: flex-start;
}
.feedback {
margin-top: 10px;
min-width: 30px;
max-width: 300px;
text-indent: 2px;
padding: 5px 5px 5px 20px;
}
}

View File

@ -6,8 +6,6 @@ module.exports = function(state) {
const feedbackUrl = `https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}`;
const header = html`
<header class="header">
<div class="logo">
</div>
<a href="${feedbackUrl}"
rel="noreferrer noopener"
class="feedback"

View File

@ -1,76 +1,39 @@
const html = require('choo/html');
const MAX_LENGTH = 32;
module.exports = function(file, state, emit) {
const loading = state.settingPassword;
const pwd = file.hasPassword;
const sectionClass =
pwd || state.passwordSetError
? 'passwordInput'
: 'passwordInput passwordInput--hidden';
const inputClass = loading || pwd ? 'input' : 'input input--noBtn';
let btnClass = 'inputBtn inputBtn--password inputBtn--hidden';
if (loading) {
btnClass = 'inputBtn inputBtn--password inputBtn--loading';
} else if (pwd) {
btnClass = 'inputBtn inputBtn--password';
}
const action = pwd
? state.translate('changePasswordButton')
: state.translate('addPasswordButton');
module.exports = function(state) {
const placeholder =
state.route === '/' ? '' : state.translate('unlockInputPlaceholder');
const hasPassword = !!state.password;
const sectionClass = hasPassword
? 'passwordInput'
: 'passwordInput passwordInput--hidden';
return html`
<div class="${sectionClass}">
<form
class="passwordInput__form"
onsubmit=${setPassword}
onsubmit=${onSubmit}
data-no-csrf>
<input id="password-input"
${loading ? 'disabled' : ''}
class="${inputClass}"
maxlength="${MAX_LENGTH}"
class="input passwordInput__fill"
autocomplete="off"
type="password"
oninput=${inputChanged}
onfocus=${focused}
placeholder="${
pwd && !state.passwordSetError
? passwordPlaceholder(file.password)
: state.translate('unlockInputPlaceholder')
}">
<input type="submit"
id="password-btn"
${loading ? 'disabled' : ''}
class="${btnClass}"
value="${loading ? '' : action}">
hasPassword ? passwordPlaceholder(state.password) : placeholder
}"
>
</form>
<label
class="passwordInput__msg ${
state.passwordSetError ? 'passwordInput__msg--error' : ''
}"
for="password-input">${message(state, pwd)}</label>
</div>`;
function inputChanged() {
state.passwordSetError = null;
const resetInput = document.getElementById('password-input');
const resetBtn = document.getElementById('password-btn');
const pwdmsg = document.querySelector('.passwordInput__msg');
const length = resetInput.value.length;
function onSubmit() {
event.preventDefault();
}
if (length === MAX_LENGTH) {
pwdmsg.textContent = state.translate('maxPasswordLength', {
length: MAX_LENGTH
});
} else {
pwdmsg.textContent = '';
}
if (length > 0) {
resetBtn.classList.remove('inputBtn--hidden');
resetInput.classList.remove('input--noBtn');
} else {
resetBtn.classList.add('inputBtn--hidden');
resetInput.classList.add('input--noBtn');
}
function inputChanged() {
const password = document.getElementById('password-input').value;
state.password = password;
}
function focused(event) {
@ -80,30 +43,8 @@ module.exports = function(file, state, emit) {
el.placeholder = '';
}
}
function setPassword(event) {
event.preventDefault();
const el = document.getElementById('password-input');
const password = el.value;
if (password.length > 0) {
emit('password', { password, file });
} else {
el.focus();
}
return false;
}
};
function passwordPlaceholder(password) {
return password ? password.replace(/./g, '●') : '●●●●●●●●●●●●';
}
function message(state, pwd) {
if (state.passwordSetError) {
return state.translate('passwordSetError');
}
if (state.settingPassword || !pwd) {
return '';
}
return state.translate('passwordIsSet');
return password ? password.replace(/./g, '•') : '••••••••••••';
}

View File

@ -1,41 +1,31 @@
.passwordInput {
width: 90%;
height: 100px;
padding: 10px 5px 5px;
display: inline;
}
.passwordInput--hidden {
visibility: hidden;
}
.passwordInput__form {
display: flex;
flex-wrap: nowrap;
padding-bottom: 5px;
.passwordInput__fill {
height: 24px;
box-sizing: border-box;
padding: 4px;
font-size: 18px;
border: none;
background-color: var(--lightControlBGColor);
outline: none;
}
.passwordInput__fill:focus {
border: 1px solid rgba(12, 12, 13, 0.2);
background-color: var(--pageBGColor);
}
.passwordInput__msg {
font-size: 15px;
font-size: 12px;
color: var(--lightTextColor);
}
.passwordInput__msg--error {
color: var(--errorColor);
}
.inputBtn--loading {
background-image: url('../assets/spinner.svg');
background-position: center;
background-size: 30px 30px;
background-repeat: no-repeat;
}
.inputBtn--password {
flex: 0 0 200px;
}
@media (max-device-width: 520px), (max-width: 520px) {
.passwordInput__form {
flex-direction: column;
}
}

View File

@ -2,19 +2,17 @@ const html = require('choo/html');
module.exports = function(msg, confirmText, cancelText, confirmCallback) {
return html`
<div class="popup__wrapper">
<div class="popup" onblur=${hide} tabindex="-1">
<div class="popup__message">${msg}</div>
<div class="popup__action">
<span class="popup__no" onclick=${hide}>
${cancelText}
</span>
<span class="popup__yes" onclick=${confirmCallback}>
${confirmText}
</span>
<div>
<span class="popup__no" onclick=${hide}>${cancelText}</span>
</div>
<div>
<span class="popup__yes" onclick=${confirmCallback}>${confirmText}</span>
</div>
</div>
</div>
</div>`;
</div>`;
function hide(e) {
e.stopPropagation();

View File

@ -1,122 +1,79 @@
.popup {
visibility: hidden;
min-width: 204px;
min-height: 105px;
background-color: var(--pageBGColor);
display: block;
width: 100%;
height: 70px;
background-color: var(--errorColor);
color: var(--textColor);
border: 1px solid #d7d7db;
padding: 15px 24px;
box-sizing: content-box;
padding: 0;
box-sizing: border-box;
text-align: center;
border-radius: 5px;
position: absolute;
z-index: 1;
bottom: 20px;
left: -40px;
border-radius: 4px;
transition: opacity 0.5s;
opacity: 0;
outline: 0;
box-shadow: 3px 3px 7px rgba(136, 136, 136, 0.3);
opacity: 0;
visibility: hidden;
}
.popup::after {
content: '';
position: absolute;
bottom: -11px;
left: 20px;
background-color: #fff;
display: block;
width: 20px;
height: 20px;
transform: rotate(45deg);
border-radius: 0 0 5px;
border-right: 1px solid #d7d7db;
border-bottom: 1px solid #d7d7db;
border-left: 1px solid #fff;
border-top: 1px solid #fff;
}
.popup__wrapper {
position: absolute;
display: inline-block;
top: 100%;
left: 50%;
width: 0;
height: 0;
border: 8px solid;
border-color: var(--errorColor) transparent transparent;
margin-left: -8px;
pointer-events: none;
}
.popup__message {
height: 40px;
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px #ebebeb solid;
color: var(--textColor);
padding: 10px;
box-sizing: border-box;
text-align: center;
color: var(--primaryControlFGColor);
font-size: 15px;
font-weight: normal;
padding-bottom: 15px;
font-style: italic;
white-space: nowrap;
width: calc(100% + 48px);
margin-left: -24px;
}
.popup__action {
margin-top: 15px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
text-transform: uppercase;
}
.popup__action > div {
flex: auto;
}
.popup__no {
color: #4a4a4a;
background-color: #fbfbfb;
border: 1px #c1c1c1 solid;
border-radius: 5px;
padding: 5px 25px;
font-weight: normal;
min-width: 94px;
box-sizing: border-box;
color: var(--primaryControlFGColor);
padding: 5px;
font-weight: bold;
cursor: pointer;
white-space: nowrap;
}
.popup__no:hover {
background-color: #efeff1;
text-decoration: underline;
}
.popup__yes {
color: var(--primaryControlFGColor);
background-color: var(--primaryControlBGColor);
border-radius: 5px;
padding: 5px 25px;
padding: 5px;
font-weight: normal;
cursor: pointer;
min-width: 94px;
box-sizing: border-box;
white-space: nowrap;
margin-left: 12px;
}
.popup__yes:hover {
background-color: var(--primaryControlHoverColor);
text-decoration: underline;
}
.popup--show {
visibility: visible;
opacity: 1;
}
@media (max-device-width: 992px), (max-width: 992px) {
.popup {
left: auto;
right: -40px;
}
.popup::after {
left: auto;
right: 36px;
}
}
@media (max-device-width: 520px), (max-width: 520px) {
.popup::after {
left: 125px;
}
pointer-events: auto;
}

View File

@ -1,56 +0,0 @@
const html = require('choo/html');
const percent = require('../../utils').percent;
const radius = 73;
const oRadius = radius + 10;
const oDiameter = oRadius * 2;
const circumference = 2 * Math.PI * radius;
module.exports = function(progressRatio, indefinite = false) {
// HACK - never indefinite for MS Edge
if (/edge/i.test(navigator.userAgent)) {
indefinite = false;
}
const p = indefinite ? 0.2 : progressRatio;
const dashOffset = (1 - p) * circumference;
const progressPercent = html`
<text class="progress__percent" text-anchor="middle" x="50%" y="98">
${percent(progressRatio)}
</text>`;
return html`
<div class="progress">
<svg
width="${oDiameter}"
height="${oDiameter}"
viewPort="0 0 ${oDiameter} ${oDiameter}"
version="1.1">
<circle
class="progress__bg"
r="${radius}"
cx="${oRadius}"
cy="${oRadius}"
fill="transparent"/>
<circle
class="progress__indefinite ${indefinite ? '' : 'progress--invisible'}"
r="${radius}"
cx="${oRadius}"
cy="${oRadius}"
fill="transparent"
transform="rotate(-90 ${oRadius} ${oRadius})"
stroke-dasharray="${circumference}"
stroke-dashoffset="${dashOffset}"/>
<circle
class="progress__bar ${indefinite ? 'progress--invisible' : ''}"
r="${radius}"
cx="${oRadius}"
cy="${oRadius}"
fill="transparent"
transform="rotate(-90 ${oRadius} ${oRadius})"
stroke-dasharray="${circumference}"
stroke-dashoffset="${dashOffset}"/>
${indefinite ? '' : progressPercent}
</svg>
</div>
`;
};

View File

@ -1,43 +0,0 @@
.progress {
margin-top: 3px;
}
.progress__bg {
stroke: #eee;
stroke-width: 0.75em;
}
.progress__bar {
stroke: #3b9dff;
stroke-width: 0.75em;
transition: stroke-dashoffset 300ms linear;
}
.progress__indefinite {
stroke: #3b9dff;
stroke-width: 0.75em;
animation: 1s linear infinite spin;
transform-origin: center;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.progress__percent {
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
font-size: 43.2px;
letter-spacing: -0.78px;
line-height: 58px;
user-select: none;
}
.progress--invisible {
display: none;
}

View File

@ -5,16 +5,14 @@ module.exports = function(selected, options, translate, changed) {
let x = selected;
return html`
<div class="select">
<select id="${id}" onchange=${choose}>
<select class="selectBox" id="${id}" onchange=${choose}>
${options.map(
i =>
html`<option value="${i}" ${
i === selected ? 'selected' : ''
}>${translate(i)}</option>`
)}
</select>
</div>`;
</select>`;
function choose(event) {
const target = event.target;

View File

@ -1,46 +1,22 @@
.select {
background-color: var(--pageBGColor);
overflow: hidden;
padding: 4px 2px 4px 2px;
border: 1px dotted #0094fb88;
border-radius: 4px;
display: inline;
position: relative;
}
.select::after {
color: #0094fb;
content: '\25BC';
pointer-events: none;
font-size: 20px;
margin-left: -30px;
padding-right: 10px;
}
option {
padding: 0;
}
select {
.selectBox {
appearance: none;
outline: 0;
box-shadow: none;
border: 0;
background: #fff;
background-image: none;
border: none;
border-radius: 0;
background-color: #e6e6e6;
font-size: 1em;
font-weight: 200;
margin: 0;
color: #0094fb;
padding: 4px 2px 4px 2px;
cursor: pointer;
padding-right: 40px;
}
select:active {
background-color: var(--pageBGColor);
border: 0;
}
#arrow {
position: relative;
}

View File

@ -1,25 +1,26 @@
const html = require('choo/html');
const passwordInput = require('../passwordInput');
module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id);
module.exports = function(state) {
const checked = state.password ? 'checked' : '';
const label = state.password ? 'addPasswordLabel' : 'addPasswordMessage';
return html`
<div class="setPasswordSection">
<div class="checkbox">
<input
${file.hasPassword ? 'disabled' : ''}
${file.hasPassword || state.passwordSetError ? 'checked' : ''}
class="checkbox__input"
id="add-password"
class="checkbox__input" id="add-password"
type="checkbox"
${checked}
autocomplete="off"
onchange=${togglePasswordInput}/>
<label class="checkbox__label" for="add-password">
${state.translate('requirePasswordCheckbox')}
${state.translate(label)}
</label>
</div>
${passwordInput(file, state, emit)}
${passwordInput(state)}
</div>`;
function togglePasswordInput(e) {
@ -28,9 +29,13 @@ module.exports = function(state, emit) {
document
.querySelector('.passwordInput')
.classList.toggle('passwordInput--hidden', !boxChecked);
const label = document.querySelector('.checkbox__label');
if (boxChecked) {
label.innerHTML = state.translate('addPasswordLabel');
unlockInput.focus();
} else {
label.innerHTML = state.translate('addPasswordMessage');
unlockInput.value = '';
}
}

View File

@ -1,11 +1,12 @@
.setPasswordSection {
display: flex;
padding: 10px 0;
max-width: 100%;
overflow-wrap: break-word;
}
.checkbox {
min-height: 24px;
flex: auto;
height: 24px;
}
.checkbox__input {
@ -14,7 +15,8 @@
}
.checkbox__label {
line-height: 23px;
font-size: 13px;
line-height: 20px;
cursor: pointer;
color: var(--lightTextColor);
user-select: none;
@ -22,27 +24,21 @@
.checkbox__label::before {
content: '';
height: 20px;
width: 20px;
height: 24px;
width: 24px;
margin-right: 10px;
margin-left: 5px;
float: left;
border: 1px solid rgba(12, 12, 13, 0.3);
border-radius: 2px;
background-color: #e6e6e6;
}
.checkbox__input:focus + .checkbox__label::before,
.checkbox:hover .checkbox__label::before {
border: 1px solid var(--primaryControlBGColor);
}
.checkbox__input:checked + .checkbox__label {
color: var(--textColor);
.checkbox__label:hover::before {
background-color: #d6d6d6;
}
.checkbox__input:checked + .checkbox__label::before {
background-image: url('../assets/check-16-blue.svg');
background-position: 2px 1px;
background-image: url('../assets/lock.svg');
background-position: 2px 2px;
background-repeat: no-repeat;
}
.checkbox__input:disabled + .checkbox__label {
@ -50,20 +46,13 @@
}
.checkbox__input:disabled + .checkbox__label::before {
background-image: url('../assets/check-16-blue.svg');
background-image: url('../assets/lock.svg');
background-repeat: no-repeat;
background-size: 26px 26px;
border: none;
cursor: auto;
}
@media (max-device-width: 520px), (max-width: 520px) {
.setPasswordSection {
align-self: center;
min-width: 95%;
}
.checkbox__label::before {
margin-left: 0;
}
.setPasswordSection > .passwordInput--hidden {
display: none;
}

View File

@ -0,0 +1,15 @@
const html = require('choo/html');
module.exports = function(state) {
return html`
<div class="signupPromo">
<div class="signupPromo__title">${state.translate('signInPromoText')}</div>
<div class="signupPromo__info">${state.translate('signInExplanation')}</div>
<a href="/signin"
class="link signupPromo__link"
>
${state.translate('signInLearnMore')}
</a>
</div>
`;
};

View File

@ -0,0 +1,85 @@
.signupPromo {
display: flex;
flex-direction: column;
position: absolute;
right: 0;
height: 140px;
width: 150px;
background: #ffe900;
font-size: 13px;
font-weight: 500;
text-align: center;
justify-content: center;
}
.signupPromo::before {
content: '';
width: 0;
height: 0;
border-style: solid;
border-width: 0 35px 140px 0;
border-color: transparent #ffe900 transparent;
position: absolute;
right: 100%;
}
.signupPromo::after {
content: '';
width: 0;
height: 0;
border-style: solid;
border-width: 0 150px 35px 0;
border-color: transparent #ffe900 transparent;
position: absolute;
top: 100%;
left: 0%;
}
.signupPromo__title {
color: #0a84ff;
font-size: 22px;
font-style: italic;
font-weight: 600;
padding: 6px;
}
.signupPromo__info {
color: var(--lightTextColor);
padding: 6px;
}
.signupPromo__link {
z-index: 5;
}
.signupPromo__link:hover {
text-decoration: underline;
}
@media (max-device-width: 750px), (max-width: 750px) {
.signupPromo {
flex-direction: row;
align-items: center;
height: 40px;
width: 100%;
}
.signupPromo::before {
visibility: hidden;
}
.signupPromo::after {
border-width: 15px 50vw 0 50vw;
border-color: #ffe900 transparent transparent;
}
}
@media (max-device-width: 500px), (max-width: 500px) {
.signupPromo__link {
display: none;
}
.signupPromo {
overflow: hidden;
}
}

View File

@ -0,0 +1,11 @@
const html = require('choo/html');
module.exports = function(state) {
return html`
<div class="boxTitle">
${state.translate('uploadPageHeader')}
<div class="boxSubtitle">
${state.translate('pageHeaderCredits')}
</div>
</div>`;
};

View File

@ -0,0 +1,13 @@
.boxTitle {
font-size: 15px;
text-align: center;
font-weight: 500;
margin: 9px 0 19px 0;
color: var(--lightTextColor);
}
.boxSubtitle {
font-size: 12px;
font-weight: 300;
font-style: italic;
}

View File

@ -0,0 +1,64 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
const bytes = require('../../utils').bytes;
const fileIcon = require('../fileIcon');
module.exports = function(file, state, emit) {
const transfer = state.transfer;
const transferState = transfer ? transfer.state : null;
const transferring = state.uploading || state.downloading;
const share = state.route.includes('share/');
const complete = share ? 'uploadedFile--completed' : '';
const cancelVisible =
transferring || state.route === '/' ? 'uploadedFile__cancel--visible' : '';
const stampClass =
share || transferState === 'complete' ? 'uploadedFile__stamp--visible' : '';
function cancel(event) {
event.preventDefault();
const btn = document.querySelector('.uploadedFile__cancel');
btn.disabled = true;
if (transferring) {
emit('cancel');
} else if (state.route === '/') {
emit('removeUpload', { file });
}
}
//const percent = share ? 100 : Math.floor(progressRatio * 100);
/*
style="
background: linear-gradient(to right,
#e8f2fe 0%,
#e8f2fe ${percent}%,
#fff ${percent}%,
#fff 100%);"
*/
return html`
<li class="uploadedFile ${complete}" id="${file.id}"
>
${fileIcon(file.name, file._hasPassword)}
<div class="uploadedFile__cancel ${cancelVisible}"
onclick=${cancel}>
<img
src="${assets.get('close-16.svg')}"
alt="cancel"/>
</div>
<div class="uploadedFile__fileData">
<p class="uploadedFile__fileName">${file.name}</p>
<p class="uploadedFile__fileInfo">
<span>${bytes(file.size)}</span>
</p>
</div>
<img src="${assets.get('sent-done.svg')}"
class="uploadedFile__stamp ${stampClass}"/>
</li>
`;
};

View File

@ -0,0 +1,70 @@
.uploadedFile {
margin: 11px;
list-style-type: none;
font-size: 11px;
line-height: 18px;
text-align: initial;
color: var(--lightTextColor);
background-color: var(--pageBGColor);
border: 1px solid #cececf;
box-sizing: border-box;
height: 53px;
border-radius: 4px;
position: relative;
}
.uploadedFile--completed {
background-color: #e8f2fe;
}
.uploadedFile__fileData {
margin: 8px 16px 8px 44px;
}
.uploadedFile__fileName {
margin: 0;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.uploadedFile__fileInfo {
margin: 0;
}
.uploadedFile__cancel {
float: right;
margin: 6px;
visibility: hidden;
}
.uploadedFile:hover .uploadedFile__cancel--visible {
visibility: visible;
}
.uploadedFile__stamp {
position: absolute;
top: -4px;
right: -8px;
visibility: hidden;
opacity: 0;
}
.uploadedFile__stamp--visible {
visibility: visible;
opacity: 1;
animation: stampDown 0.2s linear;
}
@keyframes stampDown {
0% {
opacity: 0;
transform: scale(1.5);
}
100% {
opacity: 1;
}
}

View File

@ -0,0 +1,12 @@
const html = require('choo/html');
const file = require('../uploadedFile');
module.exports = function(files, state, emit) {
//const progressRatio = state.transfer ? state.transfer.progressRatio : 0;
return html`
<ul class="uploadedFiles">
${files.map(f => file(f, state, emit))}
</ul>
`;
};

View File

@ -0,0 +1,10 @@
.uploadedFiles {
border: 1px solid rgba(12, 12, 13, 0.1);
border-radius: 4px;
box-sizing: border-box;
margin: 0;
padding: 0;
align-content: center;
flex: 1;
overflow-y: scroll;
}

View File

@ -0,0 +1,24 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
// eslint-disable-next-line no-unused-vars
module.exports = function(state) {
return html`
<div class="account">
<img
src="${assets.get('user.svg')}"
onclick=${onclick}
alt="account"/>
<ul class=account_dropdown>
<li class=account_dropdown__item>Placeholder</li>
<li class=account_dropdown__item>Placeholder</li>
</ul>
</div>`;
function onclick(event) {
event.preventDefault();
const dropdown = document.querySelector('.account_dropdown');
dropdown.classList.toggle('visible');
}
};

View File

@ -0,0 +1,33 @@
.account {
position: absolute;
right: 0;
margin: 0 21px;
padding: 0;
}
.account_dropdown {
z-index: 1;
position: absolute;
top: 25px;
left: -10px;
width: 150px;
list-style-type: none;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
background-color: var(--pageBGColor);
box-shadow: 0 5px 12px 0 rgba(0, 0, 0, 0.2);
padding: 11px 0;
visibility: hidden;
}
.account_dropdown__item {
padding: 0 14px;
color: var(--lightTextColor);
font-size: 13px;
line-height: 24px;
}
.account_dropdown__item:hover {
background-color: var(--primaryControlBGColor);
color: var(--primaryControlFGColor);
}

View File

@ -1,3 +1,4 @@
/* global MAXFILESIZE */
const b64 = require('base64-js');
function arrayToB64(array) {
@ -128,6 +129,27 @@ function openLinksInNewTab(links, should = true) {
return links;
}
function checkSize(files, oldfiles, translate) {
function size(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total += arr[i].size;
}
return total;
}
const addSize = size(files);
if (addSize === 0) {
return;
}
const totalSize = addSize + size(oldfiles);
if (totalSize > MAXFILESIZE) {
// eslint-disable-next-line no-alert
alert(translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
return;
}
}
module.exports = {
fadeOut,
delay,
@ -140,5 +162,6 @@ module.exports = {
b64ToArray,
loadShim,
isFile,
openLinksInNewTab
openLinksInNewTab,
checkSize
};

10
assets/addfile.svg Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="33px" height="33px" viewBox="0 0 33 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>Shape</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M13.799417,16.627417 L9.213417,21.213417 C8.45547258,21.9981748 8.46631225,23.2455866 9.23777985,24.0170541 C10.0092474,24.7885217 11.2566592,24.7993614 12.041417,24.041417 L16.627417,19.455417 L21.213417,24.041417 C21.9981748,24.7993614 23.2455866,24.7885217 24.0170541,24.0170541 C24.7885217,23.2455866 24.7993614,21.9981748 24.041417,21.213417 L19.455417,16.627417 L24.041417,12.041417 C24.5613302,11.5392681 24.7698422,10.7956612 24.5868113,10.0964024 C24.4037804,9.39714349 23.8576905,8.85105357 23.1584316,8.66802268 C22.4591728,8.4849918 21.7155659,8.69350383 21.213417,9.213417 L16.627417,13.799417 L12.041417,9.213417 C11.2566592,8.45547258 10.0092474,8.46631225 9.23777985,9.23777985 C8.46631225,10.0092474 8.45547258,11.2566592 9.213417,12.041417 L13.799417,16.627417 Z M16.627417,0.627416998 C25.463973,0.627416998 32.627417,7.790861 32.627417,16.627417 C32.627417,25.463973 25.463973,32.627417 16.627417,32.627417 C7.790861,32.627417 0.627416998,25.463973 0.627416998,16.627417 C0.627416998,7.790861 7.790861,0.627416998 16.627417,0.627416998 Z" id="Shape" fill="#737373" transform="translate(16.627417, 16.627417) rotate(45.000000) translate(-16.627417, -16.627417) "></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

4
assets/back-arrow.svg Normal file
View File

@ -0,0 +1,4 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M15.707 4.293a1 1 0 0 1 0 1.414L9.414 12l6.293 6.293a1 1 0 1 1-1.414 1.414l-7-7a1 1 0 0 1 0-1.414l7-7a1 1 0 0 1 1.414 0z" fill="#0A84FF" fill-opacity="1"></path></svg>

After

Width:  |  Height:  |  Size: 523 B

13
assets/blue_file.svg Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="32px" viewBox="0 0 22 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>Group 8</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="0.5">
<g id="Group-8" fill="#0A84FF">
<path d="M2,0 L11.9428571,0 L21.665206,8.66172902 C21.8781869,8.85147558 22,9.12314407 22,9.40838886 L22,30 C22,31.1045695 21.1045695,32 20,32 L2,32 C0.8954305,32 1.3527075e-16,31.1045695 0,30 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z" id="Rectangle-14"></path>
<path d="M12,0 L22,9 L15,9 C13.3431458,9 12,7.65685425 12,6 L12,0 Z" id="Triangle"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 917 B

4
assets/lock-white.svg Normal file
View File

@ -0,0 +1,4 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M9 7a3 3 0 1 1 6 0v3H9V7zm-2 3V7a5 5 0 1 1 10 0v3h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h1z" fill="#FFFFFF" fill-opacity="1"></path></svg>

After

Width:  |  Height:  |  Size: 520 B

4
assets/lock.svg Normal file
View File

@ -0,0 +1,4 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M9 7a3 3 0 1 1 6 0v3H9V7zm-2 3V7a5 5 0 1 1 10 0v3h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h1z" fill="#0A84FF" fill-opacity="1"></path></svg>

After

Width:  |  Height:  |  Size: 520 B

13
assets/red_file.svg Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="32px" viewBox="0 0 22 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>Group 8</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="0.5">
<g id="Group-8" fill="#D70022">
<path d="M2,0 L11.9428571,0 L21.665206,8.66172902 C21.8781869,8.85147558 22,9.12314407 22,9.40838886 L22,30 C22,31.1045695 21.1045695,32 20,32 L2,32 C0.8954305,32 1.3527075e-16,31.1045695 0,30 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z" id="Rectangle-14"></path>
<path d="M12,0 L22,9 L15,9 C13.3431458,9 12,7.65685425 12,6 L12,0 Z" id="Triangle"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 917 B

19
assets/sent-done.svg Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="118px" height="59px" viewBox="0 0 118 59" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>Group</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(2.000000, -5.000000)">
<g id="Group-5" transform="translate(86.841503, 34.526577) rotate(3.000000) translate(-86.841503, -34.526577) translate(54.341503, 2.526577)">
<circle id="Oval-2" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" transform="translate(32.499214, 31.954331) rotate(11.000000) translate(-32.499214, -31.954331) " cx="32.4992143" cy="31.9543312" r="27.0650466"></circle>
<path d="M28.020207,44.1875925 C27.4974152,44.1874763 26.9960763,43.9712589 26.6264624,43.5864984 L20.7124121,37.431951 C19.9653252,36.6269796 19.9760096,35.3474371 20.736426,34.5560981 C21.4968423,33.7647591 22.726385,33.7536403 23.4999012,34.5311077 L27.7521034,38.9562273 L40.2031506,20.4454003 C40.831247,19.5292815 42.051282,19.3107606 42.9375457,19.9556419 C43.8238095,20.6005231 44.0454042,21.868032 43.4341935,22.7964374 L29.6347427,43.3115953 C29.3022783,43.8117461 28.7737147,44.1326132 28.1917144,44.1875925 C28.1345816,44.1905933 28.0773398,44.1905933 28.020207,44.1875925 Z" id="Shape" fill="#12BC00" fill-rule="nonzero"></path>
</g>
<path d="M0,19.8728801 C3.80326861,19.2140466 7.96993528,19.2140466 12.5,19.8728801 C19.2950971,20.8611303 28.3355757,25.3728801 35,25.3728801 C41.6644243,25.3728801 47.8955852,19.5814393 55.5,18.3728801 C60.5696098,17.5671739 65.4029432,18.0671739 70,19.8728801" id="Line" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" stroke-linecap="square"></path>
<path d="M0,29.8728801 C3.80326861,29.2140466 7.96993528,29.2140466 12.5,29.8728801 C19.2950971,30.8611303 28.3355757,35.3728801 35,35.3728801 C41.6644243,35.3728801 47.8955852,29.5814393 55.5,28.3728801 C60.5696098,27.5671739 65.4029432,28.0671739 70,29.8728801" id="Line" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" stroke-linecap="square"></path>
<path d="M0,38.8728801 C3.80326861,38.2140466 7.96993528,38.2140466 12.5,38.8728801 C19.2950971,39.8611303 28.3355757,44.3728801 35,44.3728801 C41.6644243,44.3728801 47.8955852,38.5814393 55.5,37.3728801 C60.5696098,36.5671739 65.4029432,37.0671739 70,38.8728801" id="Line" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" stroke-linecap="square"></path>
<path d="M0,48.8728801 C3.80326861,48.2140466 7.96993528,48.2140466 12.5,48.8728801 C19.2950971,49.8611303 28.3355757,54.3728801 35,54.3728801 C41.6644243,54.3728801 47.8955852,48.5814393 55.5,47.3728801 C60.5696098,46.5671739 65.4029432,47.0671739 70,48.8728801" id="Line" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" stroke-linecap="square"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1 +0,0 @@
<svg width="57" height="57" viewBox="0 0 57 57" xmlns="http://www.w3.org/2000/svg"><title>upload</title><g transform="translate(1 1)" stroke-width="2" stroke="#7FC9FD" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path d="M18 24l10-9 10 9M28 39.545V15"/><circle cx="27.5" cy="27.5" r="27.5"/></g></svg>

Before

Width:  |  Height:  |  Size: 336 B

1
assets/user.svg Normal file
View File

@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="194 -104 1000 1000"><style>.st0{fill:#C3CFD8;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}</style><path class="st0" d="M694-104.3c276.1 0 500 223.9 500 500s-223.9 500-500 500-500-223.9-500-500c0-276.2 223.9-500 500-500z"/><path class="st1" d="M892.4 585.9c10 3.1 19.1 5.7 27.5 8.2 34.5 10 44.8 54.6 17.5 78.1-65.4 56.5-150.7 90.8-244 90.8-92.8 0-177.6-33.8-242.9-89.8-27.4-23.5-17.3-68.2 17.4-78.3 9.2-2.7 19.2-5.5 30.2-9 62.6-19.5 92.6-43.7 98.2-68.7 0-.1 0-.2.1-.2 3.6-16.1-2.8-32.9-15.5-43.5-26.4-22.1-37.1-59.8-44.1-87.5-.8-3.2-1.7-6.5-2.5-9.8-12.1-2.1-25.4-17.3-32.2-38.5-8.2-25.5-3.9-49.8 9.6-54.1 1.3-.4 2.6-.4 3.9-.5-3.1-18.2-6.9-45.4-7.3-69.3-.1-5.2-.2-10.9-.2-16.9 0-3 .1-6.1.1-9.3 0-1.6.1-3.2.2-4.8.1-1.6.2-3.2.3-4.9.9-13.1 2.9-26.8 7-40 7.4-23.7 21.6-45.4 47.4-57.3 5.8-2.7 11-6.4 15.1-11.3 22.4-26.4 49.1-39.6 74.2-45.4 6.9-1.6 13.6-2.6 20.1-3.2 3.2-.3 6.4-.5 9.5-.6 1.6-.1 3.1-.1 4.6-.1h4.5c11.7.3 22 1.8 29.6 3.7 50 12.3 89.2 38 116.4 69.5 13.5 15.8 23.9 33 30.7 50.7 3.4 8.9 5.9 17.9 7.4 26.9.8 4.5 1.3 9 1.6 13.5.3 4.5.3 8.9.1 13.4-1.5 27.1-4.4 45.9-7.3 60.1-2.3 11.1.1 22.2 5 32.4 4.9 10.3 5.3 26.7.2 43.9-6.1 20.3-18.3 35.3-29.8 38.7-2.2 8.1-3.8 13.5-3.9 13.5-3.8 29-10.7 59.8-35.3 82.9-10.5 9.8-15 24.5-13.1 38.7.5 3.5 1 6.6 1.6 9.2 5.6 25.1 35.5 49.3 98.1 68.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -26,6 +26,7 @@ uploadSuccessConfirmHeader = Ready to Send
uploadSvgAlt = Upload
uploadSuccessTimingHeader = The link to your file will expire after 1 download or in 24 hours.
expireInfo = The link to your file will expire after { $downloadCount } or { $timespan }.
frontPageExpireInfo = Expires after { $downloadCount } or { $timespan }
downloadCount = { $num ->
[one] 1 download
*[other] { $num } downloads
@ -34,6 +35,14 @@ timespanHours = { $num ->
[one] 1 hour
*[other] { $num } hours
}
timespanMinutes = { $num ->
[one] 1 minute
*[other] { $num } minutes
}
timespanWeeks = { $num ->
[one] 1 week
*[other] { $num } weeks
}
copyUrlFormLabelWithName = Copy and share the link to send your file: { $filename }
copyUrlFormButton = Copy to clipboard
copiedUrl = Copied!
@ -117,3 +126,30 @@ passwordIsSet = Password set
maxPasswordLength = Maximum password length: { $length }
# A short status message shown when there was an error setting the password
passwordSetError = This password could not be set
pageHeaderCredits = from the makers of Firefox
addFilesButton = Add file(s)
uploadFilesButton = Send
uploadFileProgress = Sending
uploadDropDragMessage = Drop files here
uploadDropClickMessage = or click to select a file
addPasswordMessage = Protect with password
addPasswordLabel = Password:
copyUrlLabel = Copy and share this link:
passwordReminder = don't forget the password too
signInPromoText = Sign In/Up!
signInExplanation = It's free and gives you many more Send options
signInLearnMore = Learn more!
downloadProgressButton = Downloading... { $progress }
downloadMessage2 = Firefox Send lets you share files with a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever.
signInEmailEnter = Enter your Email
emailEntryPlaceholder = Email
signInContinueMessage = to continue to Firefox Send
signInContinueButton = Continue
accountBenefitTitle = With a free Firefox Account with Send you can:
accountBenefitMultiFile = Send multiple files at once
accountBenefitLargeFiles = Upload larger files (up to { $size } GB)
accountBenefitExpiry = Have more expiry options
accountBenefitSync = Manage your uploads across devices
accountBenefitNotify = Be notified when your files are downloaded
accountBenefitMore = Do a lot more!

View File

@ -5,7 +5,7 @@ module.exports = async (req, res) => {
const meta = await storage.metadata(req.params.id);
res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`);
res.send({
password: meta.pwd
requiresPassword: meta.pwd
});
} catch (e) {
res.sendStatus(404);

View File

@ -49,7 +49,7 @@ module.exports = function(app) {
next();
});
app.use(express.json());
app.get('/', language, pages.index);
app.get('/', language, pages.blank);
app.get('/legal', language, pages.legal);
app.get('/jsconfig.js', require('./jsconfig'));
app.get(`/share/:id${ID_REGEX}`, language, pages.blank);

View File

@ -19,16 +19,15 @@ module.exports = {
download: async function(req, res, next) {
const id = req.params.id;
try {
const { nonce, pwd } = await storage.metadata(id);
res.set('WWW-Authenticate', `send-v1 ${nonce}`);
res.send(
stripEvents(
routes.toString(
`/download/${req.params.id}`,
`/download/${id}`,
Object.assign(state(req), {
fileInfo: { nonce, requiresPassword: +pwd }
fileInfo: { nonce, requiresPassword: pwd }
})
)
)