commit
0cf95b30c0
|
@ -10,3 +10,4 @@ rules:
|
||||||
declaration-colon-newline-after: null
|
declaration-colon-newline-after: null
|
||||||
selector-list-comma-newline-after: null
|
selector-list-comma-newline-after: null
|
||||||
value-list-comma-newline-after: null
|
value-list-comma-newline-after: null
|
||||||
|
at-rule-no-unknown: null
|
||||||
|
|
|
@ -62,7 +62,10 @@ export default class Archive {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(index) {
|
remove(file) {
|
||||||
this.files.splice(index, 1);
|
const index = this.files.indexOf(file);
|
||||||
|
if (index > -1) {
|
||||||
|
this.files.splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
305
app/base.css
305
app/base.css
|
@ -1,305 +0,0 @@
|
||||||
:root {
|
|
||||||
--pageBGColor: #fff;
|
|
||||||
--lightControlBGColor: #e6e6e6;
|
|
||||||
--primaryControlBGColor: #0a84ff;
|
|
||||||
--primaryControlFGColor: #fff;
|
|
||||||
--primaryControlHoverColor: #0473e2;
|
|
||||||
--inputTextColor: #737373;
|
|
||||||
--errorColor: #d70022;
|
|
||||||
--linkColor: #0094fb;
|
|
||||||
--textColor: #0c0c0d;
|
|
||||||
--lightBorderColor: rgba(12, 12, 12, 0.2);
|
|
||||||
--lightTextColor: #737373;
|
|
||||||
--successControlBGColor: #12bc00;
|
|
||||||
--successControlFGColor: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'segoe ui',
|
|
||||||
'helvetica neue', helvetica, ubuntu, roboto, noto, arial, sans-serif;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'segoe ui',
|
|
||||||
'helvetica neue', helvetica, ubuntu, roboto, noto, arial, sans-serif;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
select,
|
|
||||||
textarea,
|
|
||||||
button {
|
|
||||||
font-family: inherit;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
display: flex;
|
|
||||||
flex: auto;
|
|
||||||
padding: 0 25px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-height: 500px;
|
|
||||||
max-height: 800px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stripedBox {
|
|
||||||
flex: none;
|
|
||||||
position: relative;
|
|
||||||
width: 400px;
|
|
||||||
margin-top: 32px;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 3px solid rgba(12, 12, 12, 0.2);
|
|
||||||
background-clip: padding-box;
|
|
||||||
background-image: repeating-linear-gradient(
|
|
||||||
45deg,
|
|
||||||
white,
|
|
||||||
white 5px,
|
|
||||||
#ea000e 5px,
|
|
||||||
#ea000e 25px,
|
|
||||||
white 25px,
|
|
||||||
white 30px,
|
|
||||||
#0083ff 30px,
|
|
||||||
#0083ff 50px
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainContent {
|
|
||||||
height: 100%;
|
|
||||||
background-color: white;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0 10px;
|
|
||||||
padding: 10px 10px 28px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploads {
|
|
||||||
flex: 0 0 262px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-top: 180px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noscript {
|
|
||||||
text-align: center;
|
|
||||||
border: 3px solid var(--errorColor);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 70px;
|
|
||||||
line-height: 1.2;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 500;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
letter-spacing: 0.56px;
|
|
||||||
color: var(--primaryControlFGColor);
|
|
||||||
background: var(--primaryControlBGColor);
|
|
||||||
cursor: pointer;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background-color: var(--primaryControlHoverColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn--stripes {
|
|
||||||
background: repeating-linear-gradient(
|
|
||||||
-65deg,
|
|
||||||
#7c7c7c 0,
|
|
||||||
#7c7c7c 17px,
|
|
||||||
#737373 17px,
|
|
||||||
#737373 30px
|
|
||||||
);
|
|
||||||
background-size: 300% 300%;
|
|
||||||
animation: barberpole 12s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes barberpole {
|
|
||||||
0% {
|
|
||||||
background-position: 100% 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background-position: 0% 0%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn--cancel {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
background: none;
|
|
||||||
color: var(--errorColor);
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
border: 1px solid var(--lightBorderColor);
|
|
||||||
font-size: 20px;
|
|
||||||
color: var(--inputTextColor);
|
|
||||||
font-family: 'SF Pro Text', sans-serif;
|
|
||||||
font-weight: 300;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input--error {
|
|
||||||
border-color: var(--errorColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
color: var(--linkColor);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link:focus,
|
|
||||||
.link:active,
|
|
||||||
.link:hover {
|
|
||||||
color: var(--primaryControlHoverColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link--action {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.effect--fadeOut {
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeout 200ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeout {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.effect--fadeIn {
|
|
||||||
opacity: 1;
|
|
||||||
animation: fadein 200ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadein {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.goBackButton {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
margin: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: var(--errorColor);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 40px;
|
|
||||||
margin: 20px auto;
|
|
||||||
max-width: 520px;
|
|
||||||
font-family: 'SF Pro Text', sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: left;
|
|
||||||
margin: 14px auto;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
width: 95%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visible {
|
|
||||||
visibility: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noDisplay {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexible {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-device-width: 750px), (max-width: 750px) {
|
|
||||||
.description {
|
|
||||||
margin: 0 auto 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
flex: none;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stripedBox {
|
|
||||||
margin-top: 72px;
|
|
||||||
min-height: 400px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploads {
|
|
||||||
flex: none;
|
|
||||||
padding-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-device-width: 700px), (max-width: 700px) {
|
|
||||||
.stripedBox {
|
|
||||||
margin-top: 72px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,20 +3,14 @@ export default function(state, emitter) {
|
||||||
document.body.addEventListener('dragover', event => {
|
document.body.addEventListener('dragover', event => {
|
||||||
if (state.route === '/') {
|
if (state.route === '/') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const files = document.querySelector('.uploadedFilesWrapper');
|
|
||||||
files.classList.add('uploadArea--noEvents');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.body.addEventListener('drop', event => {
|
document.body.addEventListener('drop', event => {
|
||||||
if (state.route === '/' && !state.uploading) {
|
if (state.route === '/' && !state.uploading) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
document
|
emitter.emit('addFiles', {
|
||||||
.querySelector('.uploadArea')
|
files: Array.from(event.dataTransfer.files)
|
||||||
.classList.remove('uploadArea--dragging');
|
});
|
||||||
|
|
||||||
const files = Array.from(event.dataTransfer.files);
|
|
||||||
|
|
||||||
emitter.emit('addFiles', { files });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,8 @@ import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
|
||||||
import * as metrics from './metrics';
|
import * as metrics from './metrics';
|
||||||
import Archive from './archive';
|
import Archive from './archive';
|
||||||
import { bytes } from './utils';
|
import { bytes } from './utils';
|
||||||
import okDialog from './templates/okDialog';
|
import okDialog from './ui/okDialog';
|
||||||
|
import copyDialog from './ui/copyDialog';
|
||||||
|
|
||||||
export default function(state, emitter) {
|
export default function(state, emitter) {
|
||||||
let lastRender = 0;
|
let lastRender = 0;
|
||||||
|
@ -66,8 +67,11 @@ export default function(state, emitter) {
|
||||||
metrics.changedDownloadLimit(file);
|
metrics.changedDownloadLimit(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
emitter.on('removeUpload', async ({ index }) => {
|
emitter.on('removeUpload', file => {
|
||||||
state.archive.remove(index);
|
state.archive.remove(file);
|
||||||
|
if (state.archive.numFiles === 0) {
|
||||||
|
state.archive = null;
|
||||||
|
}
|
||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -86,6 +90,7 @@ export default function(state, emitter) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.raven.captureException(e);
|
state.raven.captureException(e);
|
||||||
}
|
}
|
||||||
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
emitter.on('cancel', () => {
|
emitter.on('cancel', () => {
|
||||||
|
@ -104,6 +109,9 @@ export default function(state, emitter) {
|
||||||
count: LIMITS.MAX_FILES_PER_ARCHIVE
|
count: LIMITS.MAX_FILES_PER_ARCHIVE
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
if (state.archive.numFiles === 0) {
|
||||||
|
state.archive = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
@ -149,15 +157,7 @@ export default function(state, emitter) {
|
||||||
if (password) {
|
if (password) {
|
||||||
emitter.emit('password', { password, file: ownedFile });
|
emitter.emit('password', { password, file: ownedFile });
|
||||||
}
|
}
|
||||||
|
state.modal = copyDialog(ownedFile.name, ownedFile.url);
|
||||||
const cancelBtn = document.getElementById('cancel-upload');
|
|
||||||
if (cancelBtn) {
|
|
||||||
cancelBtn.hidden = 'hidden';
|
|
||||||
}
|
|
||||||
if (document.querySelector('.page')) {
|
|
||||||
await delay(1000);
|
|
||||||
}
|
|
||||||
emitter.emit('pushState', `/share/${ownedFile.id}`);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message === '0') {
|
if (err.message === '0') {
|
||||||
//cancelled. do nothing
|
//cancelled. do nothing
|
||||||
|
@ -176,6 +176,7 @@ export default function(state, emitter) {
|
||||||
state.password = '';
|
state.password = '';
|
||||||
state.uploading = false;
|
state.uploading = false;
|
||||||
state.transfer = null;
|
state.transfer = null;
|
||||||
|
render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -232,11 +233,7 @@ export default function(state, emitter) {
|
||||||
await dl;
|
await dl;
|
||||||
const time = Date.now() - start;
|
const time = Date.now() - start;
|
||||||
const speed = size / (time / 1000);
|
const speed = size / (time / 1000);
|
||||||
if (document.querySelector('.page')) {
|
|
||||||
await delay(1000);
|
|
||||||
}
|
|
||||||
state.storage.totalDownloads += 1;
|
state.storage.totalDownloads += 1;
|
||||||
state.transfer.reset();
|
|
||||||
metrics.completedDownload({ size, time, speed });
|
metrics.completedDownload({ size, time, speed });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message === '0') {
|
if (err.message === '0') {
|
||||||
|
|
183
app/main.css
183
app/main.css
|
@ -1,25 +1,158 @@
|
||||||
@import './base.css';
|
@tailwind preflight;
|
||||||
@import './pages/share/share.css';
|
@tailwind components;
|
||||||
@import './pages/signin/signin.css';
|
@tailwind utilities;
|
||||||
@import './pages/unsupported/unsupported.css';
|
|
||||||
@import './pages/welcome/welcome.css';
|
a {
|
||||||
@import './templates/downloadButton/downloadButton.css';
|
color: inherit;
|
||||||
@import './templates/downloadPassword/downloadPassword.css';
|
text-decoration: none;
|
||||||
@import './templates/file/file.css';
|
}
|
||||||
@import './templates/fileIcon/fileIcon.css';
|
|
||||||
@import './templates/fileList/fileList.css';
|
progress {
|
||||||
@import './templates/footer/footer.css';
|
@apply bg-grey-light;
|
||||||
@import './templates/fxPromo/fxPromo.css';
|
@apply rounded-sm;
|
||||||
@import './templates/header/header.css';
|
@apply w-full;
|
||||||
@import './templates/modal/modal.css';
|
@apply h-1;
|
||||||
@import './templates/okDialog/okDialog.css';
|
}
|
||||||
@import './templates/passwordInput/passwordInput.css';
|
|
||||||
@import './templates/popup/popup.css';
|
progress::-moz-progress-bar {
|
||||||
@import './templates/selectbox/selectbox.css';
|
@apply bg-blue;
|
||||||
@import './templates/setPasswordSection/setPasswordSection.css';
|
@apply rounded-sm;
|
||||||
@import './templates/signupDialog/signupDialog.css';
|
}
|
||||||
@import './templates/signupPromo/signupPromo.css';
|
|
||||||
@import './templates/title/title.css';
|
progress::-webkit-progress-bar {
|
||||||
@import './templates/uploadedFile/uploadedFile.css';
|
@apply bg-grey-light;
|
||||||
@import './templates/uploadedFileList/uploadedFileList.css';
|
@apply rounded-sm;
|
||||||
@import './templates/userAccount/userAccount.css';
|
@apply w-full;
|
||||||
|
@apply h-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-webkit-progress-value {
|
||||||
|
@apply bg-blue;
|
||||||
|
@apply rounded-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
max-width: 64rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main > section {
|
||||||
|
@apply bg-white;
|
||||||
|
@apply shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo {
|
||||||
|
background-image: url('../assets/send_logo.svg');
|
||||||
|
background-position: left;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 1.8rem;
|
||||||
|
padding-left: 2.4rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mozilla-logo {
|
||||||
|
background-image: url('../assets/mozilla-logo.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100px, 32px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-indent: 120%;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
height: 32px;
|
||||||
|
width: 100px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-link {
|
||||||
|
background-color: #000;
|
||||||
|
background-image: url('../assets/feedback.svg');
|
||||||
|
background-position: 0.125rem 0.25rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 1.125rem;
|
||||||
|
color: #fff;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 0.75rem;
|
||||||
|
padding: 0.375rem 0.375rem 0.375rem 1.25rem;
|
||||||
|
text-indent: 0.125rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
@apply leading-normal;
|
||||||
|
@apply select-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox > input[type='checkbox'] {
|
||||||
|
@apply absolute;
|
||||||
|
@apply opacity-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox > label {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox > label::before {
|
||||||
|
@apply bg-blue-lightest;
|
||||||
|
@apply border;
|
||||||
|
@apply rounded-sm;
|
||||||
|
|
||||||
|
content: '';
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox > label:hover::before {
|
||||||
|
@apply border-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox > input:focus + label::before {
|
||||||
|
@apply border-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox > input:checked + label::before {
|
||||||
|
background-image: url('../assets/lock.svg');
|
||||||
|
background-position: center;
|
||||||
|
background-size: 1.25rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox > input:disabled + label {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox > input:disabled + label::before {
|
||||||
|
background-image: url('../assets/lock.svg');
|
||||||
|
background-position: center;
|
||||||
|
background-size: 1.25rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#password-msg::after {
|
||||||
|
content: '\200b';
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen md {
|
||||||
|
.main {
|
||||||
|
@apply flex-1;
|
||||||
|
@apply self-center;
|
||||||
|
@apply items-center;
|
||||||
|
@apply my-10;
|
||||||
|
|
||||||
|
width: calc(100% - 3rem);
|
||||||
|
min-height: 30rem;
|
||||||
|
max-height: 40rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main > section {
|
||||||
|
@apply shadow-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import User from './user';
|
||||||
window.appState = state;
|
window.appState = state;
|
||||||
window.appEmit = emitter.emit.bind(emitter);
|
window.appEmit = emitter.emit.bind(emitter);
|
||||||
let unsupportedReason = null;
|
let unsupportedReason = null;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// Firefox < 50
|
// Firefox < 50
|
||||||
/firefox/i.test(navigator.userAgent) &&
|
/firefox/i.test(navigator.userAgent) &&
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const assets = require('../../../common/assets');
|
|
||||||
const title = require('../../templates/title');
|
|
||||||
|
|
||||||
module.exports = function(state) {
|
|
||||||
return html`
|
|
||||||
<div class="page">
|
|
||||||
|
|
||||||
${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>`;
|
|
||||||
};
|
|
|
@ -1,38 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const raw = require('choo/html/raw');
|
|
||||||
const assets = require('../../common/assets');
|
|
||||||
const title = require('../templates/title');
|
|
||||||
|
|
||||||
module.exports = function(state) {
|
|
||||||
return html`
|
|
||||||
<div>
|
|
||||||
<a href="/" class="goBackButton">
|
|
||||||
<img src="${assets.get('back-arrow.svg')}"/>
|
|
||||||
</a>
|
|
||||||
${title(state)}
|
|
||||||
<div class="title">${state.translate('legalHeader')}</div>
|
|
||||||
${raw(
|
|
||||||
replaceLinks(state.translate('legalNoticeTestPilot'), [
|
|
||||||
'https://testpilot.firefox.com/terms',
|
|
||||||
'https://testpilot.firefox.com/privacy',
|
|
||||||
'https://testpilot.firefox.com/experiments/send'
|
|
||||||
])
|
|
||||||
)}
|
|
||||||
${raw(
|
|
||||||
replaceLinks(state.translate('legalNoticeMozilla'), [
|
|
||||||
'https://www.mozilla.org/privacy/websites/',
|
|
||||||
'https://www.mozilla.org/about/legal/terms/mozilla/'
|
|
||||||
])
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
function replaceLinks(str, urls) {
|
|
||||||
let i = 0;
|
|
||||||
const s = str.replace(
|
|
||||||
/<a>([^<]+)<\/a>/g,
|
|
||||||
(m, v) => `<a href="${urls[i++]}">${v}</a>`
|
|
||||||
);
|
|
||||||
return `<div class="description">${s}</div>`;
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const assets = require('../../../common/assets');
|
|
||||||
const title = require('../../templates/title');
|
|
||||||
|
|
||||||
module.exports = function(state) {
|
|
||||||
return html`
|
|
||||||
<div class="page">
|
|
||||||
|
|
||||||
${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>
|
|
||||||
<a class="link link--action" href="/">
|
|
||||||
${state.translate('sendYourFilesLink')}
|
|
||||||
</a>
|
|
||||||
</div>`;
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
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>
|
|
||||||
`;
|
|
||||||
};
|
|
|
@ -1,42 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const titleSection = require('../../templates/title');
|
|
||||||
const downloadButton = require('../../templates/downloadButton');
|
|
||||||
const downloadedFiles = require('../../templates/uploadedFileList');
|
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
|
||||||
const fileInfo = state.fileInfo;
|
|
||||||
|
|
||||||
const trySendLink = html`
|
|
||||||
<a class="link link--action" href="/">
|
|
||||||
${state.translate('sendYourFilesLink')}
|
|
||||||
</a>`;
|
|
||||||
const cancelButton = html`
|
|
||||||
<button class="btn--cancel"
|
|
||||||
onclick=${cancel}
|
|
||||||
>
|
|
||||||
${state.translate('downloadCancel')}
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const bottomLink =
|
|
||||||
state.transfer.state === 'downloading' ? cancelButton : trySendLink;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="page">
|
|
||||||
${titleSection(state)}
|
|
||||||
|
|
||||||
${downloadedFiles(fileInfo, state, emit)}
|
|
||||||
<div class="description">${state.translate('downloadMessage2')}</div>
|
|
||||||
${downloadButton(state, emit)}
|
|
||||||
|
|
||||||
${bottomLink}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
function cancel() {
|
|
||||||
if (state.transfer.state === 'downloading') {
|
|
||||||
emit('cancel');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,110 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const raw = require('choo/html/raw');
|
|
||||||
const assets = require('../../../common/assets');
|
|
||||||
const notFound = require('../notFound');
|
|
||||||
const deletePopup = require('../../templates/popup');
|
|
||||||
const uploadedFileList = require('../../templates/uploadedFileList');
|
|
||||||
const timeLimitText = require('../../templates/timeLimitText');
|
|
||||||
const { allowedCopy, delay, fadeOut } = require('../../utils');
|
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
|
||||||
const file = state.storage.getFileById(state.params.id);
|
|
||||||
if (!file) {
|
|
||||||
return notFound(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
const passwordReminderClass = file._hasPassword
|
|
||||||
? ''
|
|
||||||
: 'passwordReminder--hidden';
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="page effect--fadeIn" id="shareWrapper">
|
|
||||||
<a href="/" class="goBackButton">
|
|
||||||
<img src="${assets.get('back-arrow.svg')}"/>
|
|
||||||
</a>
|
|
||||||
${expireInfo(file, state.translate)}
|
|
||||||
|
|
||||||
${uploadedFileList(file, state, emit)}
|
|
||||||
|
|
||||||
<div class="sharePage__copyText">
|
|
||||||
${state.translate('copyUrlLabel')}
|
|
||||||
<div class="sharePage__passwordReminder ${passwordReminderClass}">(don't forget the password too)</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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'),
|
|
||||||
state.translate('deletePopupYes'),
|
|
||||||
state.translate('deletePopupCancel'),
|
|
||||||
deleteFile
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="btn--cancel btn--delete"
|
|
||||||
title="${state.translate('deleteFileButton')}"
|
|
||||||
onclick=${showDeletePopup}>${state.translate('deleteFileButton')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
function showDeletePopup() {
|
|
||||||
const popup = document.querySelector('.popup');
|
|
||||||
popup.classList.add('popup--show');
|
|
||||||
popup.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyLink() {
|
|
||||||
if (allowedCopy()) {
|
|
||||||
emit('copy', { url: file.url, location: 'success-screen' });
|
|
||||||
const input = document.getElementById('fileUrl');
|
|
||||||
input.disabled = true;
|
|
||||||
const copyBtn = document.getElementById('copyBtn');
|
|
||||||
copyBtn.disabled = true;
|
|
||||||
copyBtn.classList.add('copyBtn--copied');
|
|
||||||
copyBtn.replaceChild(
|
|
||||||
html`<label>${state.translate('copiedUrl')}</label>`,
|
|
||||||
copyBtn.firstChild
|
|
||||||
);
|
|
||||||
await delay(2000);
|
|
||||||
input.disabled = false;
|
|
||||||
copyBtn.disabled = false;
|
|
||||||
copyBtn.classList.remove('copyBtn--copied');
|
|
||||||
copyBtn.textContent = state.translate('copyUrlFormButton');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteFile() {
|
|
||||||
emit('delete', { file, location: 'success-screen' });
|
|
||||||
await fadeOut('#shareWrapper');
|
|
||||||
emit('pushState', '/');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function expireInfo(file, translate) {
|
|
||||||
const el = html`<div class="shareTitle">
|
|
||||||
${raw(
|
|
||||||
translate('expireInfo', {
|
|
||||||
downloadCount: translate('downloadCount', { num: file.dlimit }),
|
|
||||||
timespan: timeLimitText(translate, file.timeLimit)
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
.sharePage__copyText {
|
|
||||||
margin: 8px 0 8px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
font-size: 15px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sharePage__passwordReminder {
|
|
||||||
font-size: 11px;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordReminder--hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sharePage__deletePopup {
|
|
||||||
position: relative;
|
|
||||||
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 {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copySection__url {
|
|
||||||
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: 18px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copySection__url:disabled {
|
|
||||||
background: var(--successControlFGColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input--copied {
|
|
||||||
border-color: var(--successControlBGColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.copyBtn--copied,
|
|
||||||
.copyBtn--copied:hover {
|
|
||||||
background: var(--successControlBGColor);
|
|
||||||
color: var(--successControlFGColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn--delete {
|
|
||||||
border: none;
|
|
||||||
align-self: center;
|
|
||||||
width: 176px;
|
|
||||||
background: #fff;
|
|
||||||
margin: 10px 0 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 16px;
|
|
||||||
color: var(--errorColor);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn--delete:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/* globals LIMITS */
|
|
||||||
const html = require('choo/html');
|
|
||||||
const assets = require('../../../common/assets');
|
|
||||||
const title = require('../../templates/title');
|
|
||||||
const bytes = require('../../utils').bytes;
|
|
||||||
|
|
||||||
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('accountBenefitLargeFiles', {
|
|
||||||
size: bytes(LIMITS.MAX_FILE_SIZE)
|
|
||||||
})}</li>
|
|
||||||
<li>${state.translate('accountBenefitExpiry')}</li>
|
|
||||||
<li>${state.translate('accountBenefitSync')}</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
|
|
||||||
onsubmit=${submitEmail}
|
|
||||||
data-no-csrf>
|
|
||||||
<input
|
|
||||||
id="email-input"
|
|
||||||
type="text"
|
|
||||||
class="signIn__emailInput"
|
|
||||||
placeholder=${state.translate('emailEntryPlaceholder')}/>
|
|
||||||
<input
|
|
||||||
class='noDisplay'
|
|
||||||
id="email-submit"
|
|
||||||
type="submit"/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<label class="btn" for="email-submit">
|
|
||||||
${state.translate('signInContinueButton')}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
function submitEmail(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const el = document.getElementById('email-input');
|
|
||||||
const email = el.value;
|
|
||||||
if (email) {
|
|
||||||
// just check if it's the right shape
|
|
||||||
const a = email.split('@');
|
|
||||||
if (a.length === 2 && a.every(s => s.length > 0)) {
|
|
||||||
return emit('login', email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
el.value = '';
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,33 +0,0 @@
|
||||||
.signInPage {
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 18px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.signIn__info {
|
|
||||||
width: 308px;
|
|
||||||
margin: 12px 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: 8px 0;
|
|
||||||
padding: 0 8px;
|
|
||||||
font-size: 18px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
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`
|
|
||||||
<div class="description">
|
|
||||||
<a href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-is-my-browser-not-supported">
|
|
||||||
${state.translate('notSupportedLink')}
|
|
||||||
</a>
|
|
||||||
</div>`;
|
|
||||||
url =
|
|
||||||
'https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com';
|
|
||||||
buttonAction = html`
|
|
||||||
<div class="firefoxDownload__action">
|
|
||||||
Firefox<br><span class="firefoxDownload__text">${strings.button}</span>
|
|
||||||
</div>`;
|
|
||||||
} else {
|
|
||||||
strings = outdatedStrings(state);
|
|
||||||
url = 'https://support.mozilla.org/kb/update-firefox-latest-version';
|
|
||||||
buttonAction = html`
|
|
||||||
<div class="firefoxDownload__action">
|
|
||||||
${strings.button}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="page unsupportedPage">
|
|
||||||
${title(state)}
|
|
||||||
<div class="error unsupportedPage__error">${strings.header}</div>
|
|
||||||
<div class="description flexible">
|
|
||||||
${strings.description}
|
|
||||||
${why}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>`;
|
|
||||||
};
|
|
||||||
|
|
||||||
function outdatedStrings(state) {
|
|
||||||
return {
|
|
||||||
header: state.translate('notSupportedHeader'),
|
|
||||||
description: state.translate('notSupportedOutdatedDetail'),
|
|
||||||
button: state.translate('updateFirefox'),
|
|
||||||
explainer: state.translate('uploadPageExplainer')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function unsupportedStrings(state) {
|
|
||||||
return {
|
|
||||||
header: state.translate('notSupportedHeader'),
|
|
||||||
description: state.translate('notSupportedDetail'),
|
|
||||||
button: state.translate('downloadFirefoxButtonSub'),
|
|
||||||
explainer: state.translate('uploadPageExplainer')
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
.unsupportedPage {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unsupportedPage__error {
|
|
||||||
margin: 10px 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unsupportedPage__info {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
margin: 0 auto 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.firefoxDownload {
|
|
||||||
flex: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.firefoxDownload__button {
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
height: 70px;
|
|
||||||
width: 250px;
|
|
||||||
background: #12bc00;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 0;
|
|
||||||
font-family: 'Fira Sans', 'segoe ui', sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--primaryControlFGColor);
|
|
||||||
font-size: 26px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.firefoxDownload__logo {
|
|
||||||
width: 55px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.firefoxDownload__action {
|
|
||||||
text-align: left;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.firefoxDownload__text {
|
|
||||||
text-transform: none;
|
|
||||||
font-family: 'Fira Sans', 'segoe ui', sans-serif;
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const assets = require('../../../common/assets');
|
|
||||||
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 hasAnUpload = state.archive && state.archive.numFiles > 0;
|
|
||||||
|
|
||||||
const optionClass = state.uploading ? 'uploadOptions--faded' : '';
|
|
||||||
const btnUploading = state.uploading ? 'btn--stripes' : '';
|
|
||||||
const cancelVisible = state.uploading ? '' : 'noDisplay';
|
|
||||||
const faded = hasAnUpload ? 'uploadArea--faded' : '';
|
|
||||||
const selectFileClass = hasAnUpload > 0 ? 'btn--hidden' : '';
|
|
||||||
const sendFileClass = hasAnUpload > 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="page ${fade}">
|
|
||||||
${title(state)}
|
|
||||||
|
|
||||||
<label class="uploadArea"
|
|
||||||
|
|
||||||
ondragover=${dragover}
|
|
||||||
ondragleave=${dragleave}>
|
|
||||||
|
|
||||||
${uploadBox(state.archive, 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>
|
|
||||||
|
|
||||||
<input id="file-upload"
|
|
||||||
class="inputFile fileBox"
|
|
||||||
type="file"
|
|
||||||
multiple
|
|
||||||
name="fileUploaded"
|
|
||||||
onfocus=${onfocus}
|
|
||||||
onblur=${onblur}
|
|
||||||
onchange=${addFiles} />
|
|
||||||
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="uploadOptions ${optionClass}">
|
|
||||||
${expireInfo(state, emit)}
|
|
||||||
${setPasswordSection(state)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label for="file-upload"
|
|
||||||
class="btn btn--file ${selectFileClass}"
|
|
||||||
title="${state.translate('uploadPageBrowseButton1')}">
|
|
||||||
${state.translate('uploadPageBrowseButton1')}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="btn ${btnUploading} ${sendFileClass}"
|
|
||||||
onclick=${state.uploading ? noop : upload}
|
|
||||||
title="${btnText}">
|
|
||||||
${btnText}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn--cancel uploadCancel ${cancelVisible}"
|
|
||||||
onclick=${cancel}>
|
|
||||||
${state.translate('uploadingPageCancel')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
function noop() {}
|
|
||||||
|
|
||||||
function dragover(event) {
|
|
||||||
const div = document.querySelector('.uploadArea');
|
|
||||||
div.classList.add('uploadArea--dragging');
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragleave(event) {
|
|
||||||
const div = document.querySelector('.uploadArea');
|
|
||||||
div.classList.remove('uploadArea--dragging');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onfocus(event) {
|
|
||||||
event.target.classList.add('inputFile--focused');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onblur(event) {
|
|
||||||
event.target.classList.remove('inputFile--focused');
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel(event) {
|
|
||||||
if (state.uploading) {
|
|
||||||
emit('cancel');
|
|
||||||
const cancelBtn = document.querySelector('.uploadCancel');
|
|
||||||
cancelBtn.innerHTML = state.translate('uploadCancelNotification');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFiles(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const newFiles = Array.from(event.target.files);
|
|
||||||
|
|
||||||
emit('addFiles', { files: newFiles });
|
|
||||||
}
|
|
||||||
|
|
||||||
function upload(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.target.disabled = true;
|
|
||||||
if (!state.uploading) {
|
|
||||||
emit('upload', {
|
|
||||||
type: 'click',
|
|
||||||
dlimit: state.downloadCount || 1,
|
|
||||||
password: state.password
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,96 +0,0 @@
|
||||||
.uploadArea {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
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;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadArea__msg {
|
|
||||||
font-size: 15px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
margin: 12px 0 0;
|
|
||||||
font-family: 'SF Pro Text', sans-serif;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadArea__clickMsg {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 12px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadArea--dragging {
|
|
||||||
border: 1px dashed rgba(12, 12, 13, 0.4);
|
|
||||||
transform: scale(1.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadArea--faded * {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadArea--noEvents,
|
|
||||||
.uploadArea--noEvents * {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn--file {
|
|
||||||
background-color: #737373;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn--file:hover {
|
|
||||||
background-color: #636363;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn--hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputFile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputFile--focused + .btn--file {
|
|
||||||
background-color: var(--primaryControlHoverColor);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadCancel {
|
|
||||||
margin: 6px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadCancel:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
|
@ -7,6 +7,7 @@ function getString(item) {
|
||||||
export default function(state, emitter) {
|
export default function(state, emitter) {
|
||||||
window.addEventListener('paste', async event => {
|
window.addEventListener('paste', async event => {
|
||||||
if (state.route !== '/' || state.uploading) return;
|
if (state.route !== '/' || state.uploading) return;
|
||||||
|
if (event.target.type === 'password') return;
|
||||||
|
|
||||||
const items = Array.from(event.clipboardData.items);
|
const items = Array.from(event.clipboardData.items);
|
||||||
const transferFiles = items.filter(item => item.kind === 'file');
|
const transferFiles = items.filter(item => item.kind === 'file');
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
const choo = require('choo');
|
||||||
|
const html = require('choo/html');
|
||||||
|
const nanotiming = require('nanotiming');
|
||||||
|
const download = require('./ui/download');
|
||||||
|
const footer = require('./ui/footer');
|
||||||
|
const fxPromo = require('./ui/fxPromo');
|
||||||
|
const header = require('./ui/header');
|
||||||
|
|
||||||
|
nanotiming.disabled = true;
|
||||||
|
|
||||||
|
function banner(state, emit) {
|
||||||
|
if (state.promo && !state.route.startsWith('/unsupported/')) {
|
||||||
|
return fxPromo(state, emit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function body(main) {
|
||||||
|
return function(state, emit) {
|
||||||
|
const b = html`<body class="flex flex-col items-center font-sans bg-blue-lightest md:h-screen md:bg-grey-lightest justify-between">
|
||||||
|
${banner(state, emit)}
|
||||||
|
${header(state, emit)}
|
||||||
|
${main(state, emit)}
|
||||||
|
${footer(state)}
|
||||||
|
</body>`;
|
||||||
|
if (state.layout) {
|
||||||
|
// server side only
|
||||||
|
return state.layout(state, b);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
const app = choo();
|
||||||
|
app.route('/', body(require('./ui/home')));
|
||||||
|
app.route('/download/:id', body(download));
|
||||||
|
app.route('/download/:id/:key', body(download));
|
||||||
|
app.route('/unsupported/:reason', body(require('./ui/unsupported')));
|
||||||
|
app.route('/legal', body(require('./ui/legal')));
|
||||||
|
app.route('/error', body(require('./ui/error')));
|
||||||
|
app.route('/blank', body(require('./ui/blank')));
|
||||||
|
app.route('/oauth', async function(state, emit) {
|
||||||
|
try {
|
||||||
|
await state.user.finishLogin(state.query.code, state.query.state);
|
||||||
|
emit('replaceState', '/');
|
||||||
|
} catch (e) {
|
||||||
|
emit('replaceState', '/error');
|
||||||
|
setTimeout(() => emit('render'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.route('*', body(require('./ui/notFound')));
|
||||||
|
return app;
|
||||||
|
};
|
|
@ -1,30 +0,0 @@
|
||||||
/* global downloadMetadata */
|
|
||||||
const preview = require('../pages/preview');
|
|
||||||
const password = require('../pages/password');
|
|
||||||
|
|
||||||
function createFileInfo(state) {
|
|
||||||
return {
|
|
||||||
id: state.params.id,
|
|
||||||
secretKey: state.params.key,
|
|
||||||
nonce: downloadMetadata.nonce,
|
|
||||||
requiresPassword: downloadMetadata.pwd
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
|
||||||
if (!state.fileInfo) {
|
|
||||||
state.fileInfo = createFileInfo(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.transfer && !state.fileInfo.requiresPassword) {
|
|
||||||
emit('getMetadata');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.transfer) {
|
|
||||||
return preview(state, emit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.fileInfo.requiresPassword && !state.fileInfo.password) {
|
|
||||||
return password(state, emit);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,91 +0,0 @@
|
||||||
const choo = require('choo');
|
|
||||||
const html = require('choo/html');
|
|
||||||
const nanotiming = require('nanotiming');
|
|
||||||
const download = require('./download');
|
|
||||||
const footer = require('../templates/footer');
|
|
||||||
const fxPromo = require('../templates/fxPromo');
|
|
||||||
const signupPromo = require('../templates/signupPromo');
|
|
||||||
const fileList = require('../templates/fileList');
|
|
||||||
const profile = require('../templates/userAccount');
|
|
||||||
const modal = require('../templates/modal');
|
|
||||||
|
|
||||||
nanotiming.disabled = true;
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
const app = choo();
|
|
||||||
|
|
||||||
function banner(state, emit) {
|
|
||||||
if (state.promo && !state.route.startsWith('/unsupported/')) {
|
|
||||||
return fxPromo(state, emit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function modalDialog(state, emit) {
|
|
||||||
if (state.modal) {
|
|
||||||
return modal(state, emit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function body(template) {
|
|
||||||
return function(state, emit) {
|
|
||||||
const b = html`<body>
|
|
||||||
${modalDialog(state, emit)}
|
|
||||||
${banner(state, emit)}
|
|
||||||
<main class="main">
|
|
||||||
<noscript>
|
|
||||||
<div class="noscript">
|
|
||||||
<h2>${state.translate('javascriptRequired')}</h2>
|
|
||||||
<p>
|
|
||||||
<a class="link" href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-does-firefox-send-require-javascript">
|
|
||||||
${state.translate('whyJavascript')}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>${state.translate('enableJavascript')}</p>
|
|
||||||
</div>
|
|
||||||
</noscript>
|
|
||||||
${signupPromo(state)}
|
|
||||||
<div class="stripedBox">
|
|
||||||
<div class="mainContent">
|
|
||||||
|
|
||||||
${profile(state, emit)}
|
|
||||||
|
|
||||||
${template(state, emit)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spacer"></div>
|
|
||||||
<div class="uploads">
|
|
||||||
${fileList(state)}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
${footer(state)}
|
|
||||||
</body>`;
|
|
||||||
if (state.layout) {
|
|
||||||
// server side only
|
|
||||||
return state.layout(state, b);
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
app.route('/', body(require('../pages/welcome')));
|
|
||||||
app.route('/share/:id', body(require('../pages/share')));
|
|
||||||
app.route('/download/:id', body(download));
|
|
||||||
app.route('/download/:id/:key', body(download));
|
|
||||||
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('/signin', body(require('../pages/signin')));
|
|
||||||
app.route('/oauth', async function(state, emit) {
|
|
||||||
try {
|
|
||||||
await state.user.finishLogin(state.query.code, state.query.state);
|
|
||||||
emit('replaceState', '/');
|
|
||||||
} catch (e) {
|
|
||||||
emit('replaceState', '/error');
|
|
||||||
setTimeout(() => emit('render'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.route('*', body(require('../pages/notFound')));
|
|
||||||
return app;
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
.btn--download {
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
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 ${btnClass}"
|
|
||||||
onclick=${download}>
|
|
||||||
${btnText}
|
|
||||||
</button>`;
|
|
||||||
|
|
||||||
function download(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.target.disabled = true;
|
|
||||||
if (downloadState === 'ready') {
|
|
||||||
emit('download', state.fileInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,31 +0,0 @@
|
||||||
.passwordSection {
|
|
||||||
margin: auto;
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 0;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordForm {
|
|
||||||
margin: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordForm__input {
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unlockBtn {
|
|
||||||
margin-top: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unlockBtn--error,
|
|
||||||
.unlockBtn--error:hover {
|
|
||||||
background-color: var(--errorColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordForm__error {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
|
||||||
const fileInfo = state.fileInfo;
|
|
||||||
const invalid = fileInfo.password === null;
|
|
||||||
|
|
||||||
const visible = invalid ? 'visible' : '';
|
|
||||||
const invalidBtn = invalid ? 'unlockBtn--error' : '';
|
|
||||||
|
|
||||||
const div = html`
|
|
||||||
<div class="passwordSection">
|
|
||||||
|
|
||||||
<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="input passwordForm__input"
|
|
||||||
maxlength="64"
|
|
||||||
autocomplete="off"
|
|
||||||
placeholder="${state.translate('unlockInputPlaceholder')}"
|
|
||||||
oninput=${inputChanged}
|
|
||||||
type="password" />
|
|
||||||
|
|
||||||
<input type="submit"
|
|
||||||
id="password-btn"
|
|
||||||
class="btn unlockBtn ${invalidBtn}"
|
|
||||||
value="${state.translate('unlockInputLabel')}"/>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
if (!(div instanceof String)) {
|
|
||||||
setTimeout(() => document.getElementById('password-input').focus());
|
|
||||||
}
|
|
||||||
|
|
||||||
function inputChanged() {
|
|
||||||
const input = document.querySelector('.passwordForm__error');
|
|
||||||
input.classList.remove('visible');
|
|
||||||
const btn = document.getElementById('password-btn');
|
|
||||||
btn.classList.remove('unlockBtn--error');
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPassword(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const password = document.getElementById('password-input').value;
|
|
||||||
if (password.length > 0) {
|
|
||||||
document.getElementById('password-btn').disabled = true;
|
|
||||||
state.fileInfo.url = window.location.href;
|
|
||||||
state.fileInfo.password = password;
|
|
||||||
emit('getMetadata');
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return div;
|
|
||||||
};
|
|
|
@ -1,100 +0,0 @@
|
||||||
.fileToast {
|
|
||||||
flex: none;
|
|
||||||
display: block;
|
|
||||||
margin: 6px 0 0;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 18px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
background-color: var(--pageBGColor);
|
|
||||||
position: relative;
|
|
||||||
border: 3px solid rgba(12, 12, 12, 0.2);
|
|
||||||
background-clip: padding-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 53px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileToast__content {
|
|
||||||
height: 100%;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileInfo {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileToast .fileIcon {
|
|
||||||
margin: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-device-width: 750px), (max-width: 750px) {
|
|
||||||
.fileToast {
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileToast__content {
|
|
||||||
display: flex;
|
|
||||||
line-height: 13px;
|
|
||||||
margin: 0;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileData {
|
|
||||||
flex: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
margin: 0 6px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileInfo {
|
|
||||||
flex: none;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileToast .fileIcon {
|
|
||||||
flex: none;
|
|
||||||
transform: scale(0.5);
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const number = require('../../utils').number;
|
|
||||||
const bytes = require('../../utils').bytes;
|
|
||||||
const fileIcon = require('../fileIcon');
|
|
||||||
|
|
||||||
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`
|
|
||||||
<li id="${file.id}">
|
|
||||||
<a class="fileToast ${activeClass}" href=${toastClick()}>
|
|
||||||
<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>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
|
|
||||||
function toastClick() {
|
|
||||||
return isOnSharePage() ? '/' : `/share/${file.id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOnSharePage() {
|
|
||||||
return state.href.includes('/share/') && state.params.id === file.id;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function timeLeft(milliseconds, state) {
|
|
||||||
const minutes = Math.floor(milliseconds / 1000 / 60);
|
|
||||||
const hours = Math.floor(minutes / 60);
|
|
||||||
const days = Math.floor(hours / 24);
|
|
||||||
if (days >= 1) {
|
|
||||||
return state.translate('expiresDaysHoursMinutes', {
|
|
||||||
days,
|
|
||||||
hours: hours % 24,
|
|
||||||
minutes: minutes % 60
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (hours >= 1) {
|
|
||||||
return state.translate('expiresHoursMinutes', {
|
|
||||||
hours,
|
|
||||||
minutes: minutes % 60
|
|
||||||
});
|
|
||||||
} else if (hours === 0) {
|
|
||||||
if (minutes === 0) {
|
|
||||||
return state.translate('expiresMinutes', { minutes: '< 1' });
|
|
||||||
}
|
|
||||||
return state.translate('expiresMinutes', { minutes });
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
.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;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
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>`;
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
.fileList {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 262px;
|
|
||||||
min-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
list-style-type: none;
|
|
||||||
overflow: scroll;
|
|
||||||
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* hack because justify-content:flex-end and overflow:scroll doesn't work together */
|
|
||||||
.fileList > :first-child {
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-device-width: 750px), (max-width: 750px) {
|
|
||||||
.fileList {
|
|
||||||
flex: none;
|
|
||||||
position: static;
|
|
||||||
width: 406px;
|
|
||||||
max-height: 160px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const file = require('../file');
|
|
||||||
|
|
||||||
module.exports = function(state) {
|
|
||||||
if (state.storage.files.length) {
|
|
||||||
return html`
|
|
||||||
<ul class="fileList">
|
|
||||||
${state.storage.files.map(f => file(f, state))}
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,134 +0,0 @@
|
||||||
.footer {
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 50px 31px 41px;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legalSection {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legalSection__link {
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-right: 2vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legalSection__link:hover {
|
|
||||||
color: var(--textColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__mozLogo {
|
|
||||||
width: 112px;
|
|
||||||
height: 32px;
|
|
||||||
margin-bottom: -5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socialSection__icon {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin: 0 0 -5px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback {
|
|
||||||
background-color: #000;
|
|
||||||
background-image: url('../assets/feedback.svg');
|
|
||||||
background-position: 2px 4px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 18px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid #000;
|
|
||||||
color: var(--primaryControlFGColor);
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 12px;
|
|
||||||
padding: 5px 5px 5px 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
min-width: 30px;
|
|
||||||
max-width: 300px;
|
|
||||||
text-indent: 2px;
|
|
||||||
transition: all 250ms ease-in-out;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback:active {
|
|
||||||
background-color: var(--primaryControlHoverColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropDownArrow {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown__only {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-device-width: 750px), (max-width: 750px) {
|
|
||||||
.footer {
|
|
||||||
align-items: flex-end;
|
|
||||||
padding: 20px 25px;
|
|
||||||
margin: 0;
|
|
||||||
min-width: 455px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer_hiddenIcon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown__only {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropDownArrow {
|
|
||||||
display: initial;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legalSection {
|
|
||||||
flex: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
display: block;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid rgba(12, 12, 13, 0.1);
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: left;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legalSection__link {
|
|
||||||
flex: none;
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 24px;
|
|
||||||
width: 176px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 4px 20px 0 8px;
|
|
||||||
text-shadow: none;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.legalSection__link:visited {
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.legalSection__link:hover {
|
|
||||||
color: var(--primaryControlFGColor);
|
|
||||||
background-color: var(--primaryControlBGColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__noDisplay {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const version = require('../../../package.json').version;
|
|
||||||
const assets = require('../../../common/assets');
|
|
||||||
const { browserName } = require('../../utils');
|
|
||||||
|
|
||||||
module.exports = function(state) {
|
|
||||||
const browser = browserName();
|
|
||||||
const feedbackUrl = `https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}`;
|
|
||||||
|
|
||||||
const footer = html`<footer class="footer">
|
|
||||||
<div class="legalSection"
|
|
||||||
onmouseover=${showDropDown}
|
|
||||||
onmouseout=${hideDropDown}>
|
|
||||||
|
|
||||||
<div class="legalSection__menu">
|
|
||||||
<img class="dropDownArrow" src="${assets.get('dropdown-arrow.svg')}"/>
|
|
||||||
<a class="legalSection__link"
|
|
||||||
href="https://www.mozilla.org/about/legal">
|
|
||||||
${state.translate('footerLinkLegal')}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://testpilot.firefox.com/about"
|
|
||||||
class="legalSection__link footer__dropdown footer__noDisplay">
|
|
||||||
${state.translate('footerLinkAbout')}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/legal"
|
|
||||||
class="legalSection__link footer__dropdown footer__noDisplay">
|
|
||||||
${state.translate('footerLinkTerms')}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://www.mozilla.org/privacy/websites/#cookies"
|
|
||||||
class="legalSection__link footer__dropdown footer__noDisplay">
|
|
||||||
${state.translate('footerLinkCookies')}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://www.mozilla.org/about/legal/report-infringement/"
|
|
||||||
class="legalSection__link footer__dropdown footer__noDisplay">
|
|
||||||
${state.translate('reportIPInfringement')}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/mozilla/send"
|
|
||||||
class="legalSection__link footer__dropdown dropdown__only footer__noDisplay">
|
|
||||||
Github
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/FxTestPilot"
|
|
||||||
class="legalSection__link footer__dropdown dropdown__only footer__noDisplay">
|
|
||||||
Twitter
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a href="${feedbackUrl}"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
class="feedback"
|
|
||||||
alt="Feedback"
|
|
||||||
target="_blank">${state.translate('siteFeedback')}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://github.com/mozilla/send"
|
|
||||||
class="socialSection__link footer_hiddenIcon">
|
|
||||||
<img
|
|
||||||
class="socialSection__icon"
|
|
||||||
src="${assets.get('github-icon.svg')}"
|
|
||||||
alt="Github"/>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/FxTestPilot"
|
|
||||||
class="socialSection__link footer_hiddenIcon">
|
|
||||||
<img
|
|
||||||
class="socialSection__icon"
|
|
||||||
src="${assets.get('twitter-icon.svg')}"
|
|
||||||
alt="Twitter"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://www.mozilla.org"
|
|
||||||
class="socialSection__link">
|
|
||||||
<img
|
|
||||||
class="footer__mozLogo"
|
|
||||||
src="${assets.get('mozilla-logo.svg')}"
|
|
||||||
alt="mozilla"/>
|
|
||||||
</a>
|
|
||||||
</footer>`;
|
|
||||||
// HACK
|
|
||||||
// We only want to render this once because we
|
|
||||||
// toggle the targets of the links with utils/openLinksInNewTab
|
|
||||||
footer.isSameNode = function(target) {
|
|
||||||
return target && target.nodeName && target.nodeName === 'FOOTER';
|
|
||||||
};
|
|
||||||
return footer;
|
|
||||||
|
|
||||||
function showDropDown() {
|
|
||||||
const menus = document.querySelectorAll('.footer__dropdown');
|
|
||||||
menus.forEach(element => {
|
|
||||||
element.classList.remove('footer__noDisplay');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideDropDown() {
|
|
||||||
const menus = document.querySelectorAll('.footer__dropdown');
|
|
||||||
menus.forEach(element => {
|
|
||||||
element.classList.add('footer__noDisplay');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,57 +0,0 @@
|
||||||
.fxPromo {
|
|
||||||
flex: none;
|
|
||||||
padding: 0 15px;
|
|
||||||
height: 48px;
|
|
||||||
background-color: #efeff1;
|
|
||||||
color: #4a4a4f;
|
|
||||||
font-size: 13px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo > div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo > div > span {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo__logo {
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo--blue {
|
|
||||||
background: linear-gradient(-180deg, #45a1ff 0%, #00feff 94%);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo--pink {
|
|
||||||
background: linear-gradient(-180deg, #ff9400 0%, #ff1ad9 94%);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo--blue a {
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo--pink a {
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo--blue a:hover {
|
|
||||||
color: #eee;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fxPromo--pink a:hover {
|
|
||||||
color: #eee;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const assets = require('../../../common/assets');
|
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
|
||||||
let classes = 'fxPromo';
|
|
||||||
switch (state.promo) {
|
|
||||||
case 'blue':
|
|
||||||
classes = 'fxPromo fxPromo--blue';
|
|
||||||
break;
|
|
||||||
case 'pink':
|
|
||||||
classes = 'fxPromo fxPromo--pink';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="${classes}">
|
|
||||||
<div>
|
|
||||||
<img
|
|
||||||
src="${assets.get('firefox_logo-only.svg')}"
|
|
||||||
class="fxPromo__logo"
|
|
||||||
alt="Firefox"/>
|
|
||||||
<span>Send is brought to you by the all-new Firefox.
|
|
||||||
<a
|
|
||||||
class="link"
|
|
||||||
href="https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com"
|
|
||||||
onclick=${clicked}
|
|
||||||
>Download Firefox now ≫</a></span>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function clicked() {
|
|
||||||
emit('experiment', { cd3: 'promo' });
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,9 +0,0 @@
|
||||||
.header {
|
|
||||||
align-items: flex-start;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 32px;
|
|
||||||
padding: 10px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
const header = html`
|
|
||||||
<header class="header">
|
|
||||||
</header>`;
|
|
||||||
// HACK
|
|
||||||
// We only want to render this once because we
|
|
||||||
// toggle the targets of the links with utils/openLinksInNewTab
|
|
||||||
header.isSameNode = function(target) {
|
|
||||||
return target && target.nodeName && target.nodeName === 'HEADER';
|
|
||||||
};
|
|
||||||
return header;
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
|
||||||
return html`
|
|
||||||
<div class="modal" onclick=${close}>
|
|
||||||
<div class="modal__box" onclick=${e => e.stopPropagation()}>
|
|
||||||
${state.modal(state, emit, close)}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function close(event) {
|
|
||||||
state.modal = null;
|
|
||||||
emit('render');
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,21 +0,0 @@
|
||||||
.modal {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 100;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
animation: fade-in 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal__box {
|
|
||||||
max-width: 480px;
|
|
||||||
background: var(--pageBGColor);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--textColor);
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
|
|
||||||
module.exports = function(message) {
|
|
||||||
return function(state, emit, close) {
|
|
||||||
return html`
|
|
||||||
<div class="okDialog">
|
|
||||||
<div class="okDialog__message">${message}</div>
|
|
||||||
<button class="btn" onclick=${close}>${state.translate(
|
|
||||||
'okButton'
|
|
||||||
)}</button>
|
|
||||||
</div>`;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
.okDialog {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 400px;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.okDialog__message {
|
|
||||||
margin: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
|
|
||||||
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
|
|
||||||
onsubmit=${onSubmit}
|
|
||||||
data-no-csrf>
|
|
||||||
|
|
||||||
<input id="password-input"
|
|
||||||
class="input passwordInput__fill"
|
|
||||||
autocomplete="off"
|
|
||||||
type="password"
|
|
||||||
oninput=${inputChanged}
|
|
||||||
onfocus=${focused}
|
|
||||||
placeholder="${
|
|
||||||
hasPassword ? passwordPlaceholder(state.password) : placeholder
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function onSubmit() {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
function inputChanged() {
|
|
||||||
const password = document.getElementById('password-input').value;
|
|
||||||
state.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
function focused(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const el = document.getElementById('password-input');
|
|
||||||
if (el.placeholder !== state.translate('unlockInputPlaceholder')) {
|
|
||||||
el.placeholder = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function passwordPlaceholder(password) {
|
|
||||||
return password ? password.replace(/./g, '•') : '••••••••••••';
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
.passwordInput {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordInput--hidden {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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: 12px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordInput__msg--error {
|
|
||||||
color: var(--errorColor);
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
|
|
||||||
module.exports = function(msg, confirmText, cancelText, confirmCallback) {
|
|
||||||
return html`
|
|
||||||
<div class="popup" onblur=${hide} tabindex="-1">
|
|
||||||
<div class="popup__message">${msg}</div>
|
|
||||||
<div class="popup__action">
|
|
||||||
<div>
|
|
||||||
<span class="popup__no" onclick=${hide}>${cancelText}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="popup__yes" onclick=${confirmCallback}>${confirmText}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function hide(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
const popup = document.querySelector('.popup.popup--show');
|
|
||||||
if (popup) {
|
|
||||||
popup.classList.remove('popup--show');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,79 +0,0 @@
|
||||||
.popup {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 70px;
|
|
||||||
background-color: var(--errorColor);
|
|
||||||
color: var(--textColor);
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: opacity 0.5s;
|
|
||||||
outline: 0;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
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;
|
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--primaryControlFGColor);
|
|
||||||
font-size: 15px;
|
|
||||||
font-style: italic;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup__action {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup__action > div {
|
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup__no {
|
|
||||||
color: var(--primaryControlFGColor);
|
|
||||||
padding: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup__no:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup__yes {
|
|
||||||
color: var(--primaryControlFGColor);
|
|
||||||
padding: 5px;
|
|
||||||
font-weight: normal;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup__yes:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup--show {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
option {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectBox {
|
|
||||||
appearance: none;
|
|
||||||
outline: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 200;
|
|
||||||
margin: 0;
|
|
||||||
padding: 4px 2px 4px 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
select:active {
|
|
||||||
background-color: var(--pageBGColor);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const passwordInput = require('../passwordInput');
|
|
||||||
|
|
||||||
module.exports = function(state) {
|
|
||||||
const checked = state.password ? 'checked' : '';
|
|
||||||
const label = state.password ? 'addPasswordLabel' : 'addPasswordMessage';
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="setPasswordSection">
|
|
||||||
<div class="checkbox">
|
|
||||||
<input
|
|
||||||
class="checkbox__input" id="add-password"
|
|
||||||
type="checkbox"
|
|
||||||
${checked}
|
|
||||||
autocomplete="off"
|
|
||||||
onchange=${togglePasswordInput}/>
|
|
||||||
<label class="checkbox__label" for="add-password">
|
|
||||||
${state.translate(label)}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${passwordInput(state)}
|
|
||||||
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function togglePasswordInput(e) {
|
|
||||||
const unlockInput = document.getElementById('password-input');
|
|
||||||
const boxChecked = e.target.checked;
|
|
||||||
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 = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,58 +0,0 @@
|
||||||
.setPasswordSection {
|
|
||||||
display: flex;
|
|
||||||
padding: 10px 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox {
|
|
||||||
flex: auto;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label {
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label::before {
|
|
||||||
content: '';
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
margin-right: 10px;
|
|
||||||
float: left;
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label:hover::before {
|
|
||||||
background-color: #d6d6d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input:checked + .checkbox__label::before {
|
|
||||||
background-image: url('../assets/lock.svg');
|
|
||||||
background-position: 2px 2px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input:disabled + .checkbox__label {
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input:disabled + .checkbox__label::before {
|
|
||||||
background-image: url('../assets/lock.svg');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 26px 26px;
|
|
||||||
border: none;
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setPasswordSection > .passwordInput--hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
.signupDialog {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 400px;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signupDialog__message {
|
|
||||||
margin: 32px 32px 0 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signupDialog__emailInput {
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 40px;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 16px 0;
|
|
||||||
padding: 0 8px;
|
|
||||||
font-size: 18px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
|
|
||||||
module.exports = function(state) {
|
|
||||||
if (state.user.loggedIn || !state.capabilities.account) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<a href="/signin" class="signupPromo">
|
|
||||||
<div class="signupPromo__title">${state.translate('signInPromoText')}</div>
|
|
||||||
<div class="signupPromo__info">${state.translate('signInExplanation')}</div>
|
|
||||||
<div class="link signupPromo__link">${state.translate(
|
|
||||||
'signInLearnMore'
|
|
||||||
)}</div>
|
|
||||||
</a>
|
|
||||||
`;
|
|
||||||
};
|
|
|
@ -1,85 +0,0 @@
|
||||||
.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: 700px), (max-width: 700px) {
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
module.exports = function(translate, seconds) {
|
|
||||||
const displayText = {
|
|
||||||
300: translate('timespanMinutes', { num: 5 }),
|
|
||||||
3600: translate('timespanHours', { num: 1 }),
|
|
||||||
86400: translate('timespanHours', { num: 24 }),
|
|
||||||
604800: translate('timespanWeeks', { num: 1 }),
|
|
||||||
1209600: translate('timespanWeeks', { num: 2 })
|
|
||||||
};
|
|
||||||
|
|
||||||
if (displayText[seconds]) {
|
|
||||||
return displayText[seconds];
|
|
||||||
}
|
|
||||||
return seconds;
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
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>`;
|
|
||||||
};
|
|
|
@ -1,14 +0,0 @@
|
||||||
.boxTitle {
|
|
||||||
font-size: 15px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 500;
|
|
||||||
margin: 9px 0 19px 0;
|
|
||||||
padding: 0 42px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.boxSubtitle {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const assets = require('../../../common/assets');
|
|
||||||
const bytes = require('../../utils').bytes;
|
|
||||||
const fileIcon = require('../fileIcon');
|
|
||||||
|
|
||||||
module.exports = function(file, index, state, emit, hasPassword) {
|
|
||||||
const transfer = state.transfer;
|
|
||||||
const transferState = transfer ? transfer.state : null;
|
|
||||||
const share = state.route.includes('share/');
|
|
||||||
const complete = share ? 'uploadedFile--completed' : '';
|
|
||||||
|
|
||||||
const cancelVisible =
|
|
||||||
state.route === '/' && !state.uploading
|
|
||||||
? 'uploadedFile__cancel--visible'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const stampClass =
|
|
||||||
share || transferState === 'complete' ? 'uploadedFile__stamp--visible' : '';
|
|
||||||
|
|
||||||
function cancel(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (state.route === '/') {
|
|
||||||
emit('removeUpload', { index });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<li class="uploadedFile ${complete}" id="${file.id}"
|
|
||||||
>
|
|
||||||
|
|
||||||
${fileIcon(file.name, 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>
|
|
||||||
`;
|
|
||||||
};
|
|
|
@ -1,70 +0,0 @@
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
const file = require('../uploadedFile');
|
|
||||||
|
|
||||||
module.exports = function(archive, state, emit) {
|
|
||||||
let files = [];
|
|
||||||
if (archive) {
|
|
||||||
files = Array.from(archive.manifest.files);
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ul class="uploadedFiles">
|
|
||||||
${files.map((f, i) => file(f, i, state, emit, archive._hasPassword))}
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
.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;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
const html = require('choo/html');
|
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
|
||||||
if (!state.capabilities.account) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const user = state.user;
|
|
||||||
const menu = user.loggedIn
|
|
||||||
? html`
|
|
||||||
<ul
|
|
||||||
class="account_dropdown"
|
|
||||||
onblur=${hideMenu}
|
|
||||||
tabindex="-1">
|
|
||||||
<li class="account_dropdown__text">
|
|
||||||
${user.email}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="account_dropdown__link" onclick=${logout}>${state.translate(
|
|
||||||
'logOut'
|
|
||||||
)}</a>
|
|
||||||
</li>
|
|
||||||
</ul>`
|
|
||||||
: html`
|
|
||||||
<ul
|
|
||||||
class="account_dropdown"
|
|
||||||
onblur=${hideMenu}
|
|
||||||
tabindex="-1">
|
|
||||||
<li>
|
|
||||||
<a class="account_dropdown__link" onclick=${login}>${state.translate(
|
|
||||||
'signInMenuOption'
|
|
||||||
)}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="account">
|
|
||||||
<div class="account__avatar">
|
|
||||||
<img
|
|
||||||
class="account__avatar"
|
|
||||||
src="${user.avatar}"
|
|
||||||
onclick=${avatarClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
${menu}
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function avatarClick(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const dropdown = document.querySelector('.account_dropdown');
|
|
||||||
dropdown.classList.toggle('visible');
|
|
||||||
dropdown.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function login(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
emit('login');
|
|
||||||
}
|
|
||||||
|
|
||||||
function logout(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
emit('logout');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideMenu(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
const dropdown = document.querySelector('.account_dropdown');
|
|
||||||
dropdown.classList.remove('visible');
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,78 +0,0 @@
|
||||||
.account {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
margin: 0 21px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account_dropdown {
|
|
||||||
z-index: 2;
|
|
||||||
position: absolute;
|
|
||||||
top: 30px;
|
|
||||||
left: -15px;
|
|
||||||
min-width: 150px;
|
|
||||||
list-style-type: none;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: var(--pageBGColor);
|
|
||||||
box-shadow: 0 5px 12px 0 rgba(0, 0, 0, 0.2);
|
|
||||||
padding: 11px 0;
|
|
||||||
visibility: hidden;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account_dropdown::after,
|
|
||||||
.account_dropdown::before {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
left: 18px;
|
|
||||||
height: 0;
|
|
||||||
width: 0;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
content: '';
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account_dropdown::after {
|
|
||||||
border-bottom-color: var(--pageBGColor);
|
|
||||||
border-width: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account_dropdown::before {
|
|
||||||
border-bottom-color: #ccc;
|
|
||||||
border-width: 13px;
|
|
||||||
margin-left: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account_dropdown__link {
|
|
||||||
display: block;
|
|
||||||
padding: 0 14px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 24px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
position: relative;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account_dropdown__link:visited {
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.account_dropdown__link:hover {
|
|
||||||
background-color: var(--primaryControlBGColor);
|
|
||||||
color: var(--primaryControlFGColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.account_dropdown__text {
|
|
||||||
display: block;
|
|
||||||
padding: 0 14px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--lightTextColor);
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const itemClass =
|
||||||
|
'block px-4 py-2 text-grey-darkest hover:bg-blue hover:text-white cursor-pointer';
|
||||||
|
|
||||||
|
module.exports = function(state, emit) {
|
||||||
|
if (!state.capabilities.account) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const user = state.user;
|
||||||
|
const menuItems = [];
|
||||||
|
if (user.loggedIn) {
|
||||||
|
menuItems.push(html`<li class="p-2 text-grey-dark">${user.email}</li>`);
|
||||||
|
menuItems.push(
|
||||||
|
html`<li><a class="${itemClass}" onclick=${logout}>${state.translate(
|
||||||
|
'logOut'
|
||||||
|
)}</a></li>`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
menuItems.push(
|
||||||
|
html`<li class=""><a class="${itemClass}" onclick=${login}>${state.translate(
|
||||||
|
'signInMenuOption'
|
||||||
|
)}</a></li>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return html`<div class="relative h-8">
|
||||||
|
<input
|
||||||
|
type="image"
|
||||||
|
alt="${user.email}"
|
||||||
|
class="w-8 h-8 rounded-full text-white"
|
||||||
|
src="${user.avatar}"
|
||||||
|
onclick=${avatarClick}/>
|
||||||
|
<ul
|
||||||
|
id="accountMenu"
|
||||||
|
class="invisible list-reset absolute pin-t pin-r mt-10 pt-2 pb-2 bg-white shadow-md whitespace-no-wrap outline-none z-50"
|
||||||
|
onblur="${hideMenu}"
|
||||||
|
tabindex="-1">
|
||||||
|
${menuItems}
|
||||||
|
</ul>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
function avatarClick(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const menu = document.getElementById('accountMenu');
|
||||||
|
menu.classList.toggle('invisible');
|
||||||
|
menu.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideMenu(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const menu = document.getElementById('accountMenu');
|
||||||
|
menu.classList.add('invisible');
|
||||||
|
}
|
||||||
|
|
||||||
|
function login(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
emit('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
emit('logout');
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,372 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const raw = require('choo/html/raw');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
|
const { bytes, copyToClipboard, list, percent, timeLeft } = require('../utils');
|
||||||
|
const expiryOptions = require('./expiryOptions');
|
||||||
|
|
||||||
|
function expiryInfo(translate, archive) {
|
||||||
|
const l10n = timeLeft(archive.expiresAt - Date.now());
|
||||||
|
return raw(
|
||||||
|
translate('frontPageExpireInfo', {
|
||||||
|
downloadCount: translate('downloadCount', {
|
||||||
|
num: archive.dlimit - archive.dtotal
|
||||||
|
}),
|
||||||
|
timespan: translate(l10n.id, l10n)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function password(state) {
|
||||||
|
const MAX_LENGTH = 32;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="my-2">
|
||||||
|
<div class="checkbox inline-block mr-3">
|
||||||
|
<input
|
||||||
|
id="add-password"
|
||||||
|
type="checkbox"
|
||||||
|
${state.password ? 'checked' : ''}
|
||||||
|
autocomplete="off"
|
||||||
|
onchange=${togglePasswordInput}/>
|
||||||
|
<label for="add-password">
|
||||||
|
${state.translate('addPasswordMessage')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="password-input"
|
||||||
|
class="${
|
||||||
|
state.password ? '' : 'invisible'
|
||||||
|
} border rounded-sm focus:border-blue leading-normal mt-2 py-1 px-2 h-8"
|
||||||
|
autocomplete="off"
|
||||||
|
maxlength="${MAX_LENGTH}"
|
||||||
|
type="password"
|
||||||
|
oninput=${inputChanged}
|
||||||
|
onfocus=${focused}
|
||||||
|
placeholder="${state.translate('unlockInputPlaceholder')}"
|
||||||
|
value="${state.password || ''}"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
id="password-msg"
|
||||||
|
for="password-input"
|
||||||
|
class="block text-xs text-grey-darker mt-1"></label>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
function togglePasswordInput(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const checked = event.target.checked;
|
||||||
|
const input = document.getElementById('password-input');
|
||||||
|
if (checked) {
|
||||||
|
input.classList.remove('invisible');
|
||||||
|
input.focus();
|
||||||
|
} else {
|
||||||
|
input.classList.add('invisible');
|
||||||
|
input.value = '';
|
||||||
|
document.getElementById('password-msg').textContent = '';
|
||||||
|
state.password = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputChanged() {
|
||||||
|
const passwordInput = document.getElementById('password-input');
|
||||||
|
const pwdmsg = document.getElementById('password-msg');
|
||||||
|
const password = passwordInput.value;
|
||||||
|
const length = password.length;
|
||||||
|
|
||||||
|
if (length === MAX_LENGTH) {
|
||||||
|
pwdmsg.textContent = state.translate('maxPasswordLength', {
|
||||||
|
length: MAX_LENGTH
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pwdmsg.textContent = '';
|
||||||
|
}
|
||||||
|
state.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
function focused(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const el = document.getElementById('password-input');
|
||||||
|
if (el.placeholder !== state.translate('unlockInputPlaceholder')) {
|
||||||
|
el.placeholder = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileInfo(file, action) {
|
||||||
|
return html`
|
||||||
|
<article class="flex flex-row items-center p-3">
|
||||||
|
<img class="" src="${assets.get('blue_file.svg')}"/>
|
||||||
|
<p class="ml-4 w-full">
|
||||||
|
<h1 class="text-sm font-medium">${file.name}</h1>
|
||||||
|
<div class="text-xs font-normal opacity-75 pt-1">${bytes(
|
||||||
|
file.size
|
||||||
|
)}</div>
|
||||||
|
<div class="hidden">${file.type}</div>
|
||||||
|
</p>
|
||||||
|
${action}
|
||||||
|
</article>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function archiveDetails(translate, archive) {
|
||||||
|
if (archive.manifest.files.length > 1) {
|
||||||
|
return html`
|
||||||
|
<details class="w-full pb-1 overflow-y-scroll" ${
|
||||||
|
archive.open ? 'open' : ''
|
||||||
|
} ontoggle=${toggled}>
|
||||||
|
<summary>${translate('fileCount', {
|
||||||
|
num: archive.manifest.files.length
|
||||||
|
})}</summary>
|
||||||
|
${list(archive.manifest.files.map(f => fileInfo(f)), 'list-reset h-full')}
|
||||||
|
</details>`;
|
||||||
|
}
|
||||||
|
function toggled(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
archive.open = event.target.open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function(state, emit, archive) {
|
||||||
|
return html`
|
||||||
|
<article
|
||||||
|
id="${archive.id}"
|
||||||
|
class="flex flex-col items-start border border-grey-light bg-white p-4">
|
||||||
|
<p class="w-full">
|
||||||
|
<img class="float-left mr-3" src="${assets.get('blue_file.svg')}"/>
|
||||||
|
<input
|
||||||
|
type="image"
|
||||||
|
class="float-right self-center text-white"
|
||||||
|
alt="Delete"
|
||||||
|
src="${assets.get('close-16.svg')}"
|
||||||
|
onclick=${del}/>
|
||||||
|
<h1 class="text-sm font-medium">${archive.name}</h1>
|
||||||
|
<div class="text-xs font-normal opacity-75 pt-1">${bytes(
|
||||||
|
archive.size
|
||||||
|
)}</div>
|
||||||
|
</p>
|
||||||
|
<div class="text-xs text-grey-dark w-full mt-2 mb-2">
|
||||||
|
${expiryInfo(state.translate, archive)}
|
||||||
|
</div>
|
||||||
|
${archiveDetails(state.translate, archive)}
|
||||||
|
<hr class="w-full border-t my-4">
|
||||||
|
<button
|
||||||
|
class="text-blue self-end font-medium flex items-center"
|
||||||
|
onclick=${copy}>
|
||||||
|
<img src="${assets.get('copy-16.svg')}" class="mr-2"/>
|
||||||
|
${state.translate('copyUrlHover')}
|
||||||
|
</button>
|
||||||
|
</article>`;
|
||||||
|
|
||||||
|
function copy(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
copyToClipboard(archive.url);
|
||||||
|
const text = event.target.lastChild;
|
||||||
|
text.textContent = state.translate('copiedUrl');
|
||||||
|
setTimeout(
|
||||||
|
() => (text.textContent = state.translate('copyUrlHover')),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function del(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emit('delete', { file: archive, location: 'success-screen' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.wip = function(state, emit) {
|
||||||
|
return html`
|
||||||
|
<article class="h-full flex flex-col bg-white z-20">
|
||||||
|
${list(
|
||||||
|
state.archive.files.map(f => fileInfo(f, remove(f))),
|
||||||
|
'list-reset h-full overflow-y-scroll p-4 bg-blue-lightest md:max-h-half-screen',
|
||||||
|
'bg-white px-2 mb-3 border border-grey-light rounded'
|
||||||
|
)}
|
||||||
|
<div class="flex-grow p-4 bg-blue-lightest mb-6 font-medium">
|
||||||
|
<input
|
||||||
|
id="file-upload"
|
||||||
|
class="hidden"
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
onchange=${add} />
|
||||||
|
<label
|
||||||
|
for="file-upload"
|
||||||
|
class="flex flex-row items-center w-full h-full p-2 cursor-pointer"
|
||||||
|
title="${state.translate('addFilesButton')}">
|
||||||
|
<img src="${assets.get('addfiles.svg')}" class="w-6 h-6 mr-2"/>
|
||||||
|
${state.translate('addFilesButton')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
${expiryOptions(state, emit)}
|
||||||
|
${password(state, emit)}
|
||||||
|
<button
|
||||||
|
id="upload-btn"
|
||||||
|
class="rounded bg-blue hover\:bg-blue-dark focus\:bg-blue-darker cursor-pointer text-center text-white py-2 px-6 h-12 mt-4 flex flex-no-shrink items-center justify-center font-semibold"
|
||||||
|
title="${state.translate('uploadSuccessConfirmHeader')}"
|
||||||
|
onclick=${upload}>
|
||||||
|
${state.translate('uploadSuccessConfirmHeader')}
|
||||||
|
</button>
|
||||||
|
</article>`;
|
||||||
|
|
||||||
|
function upload(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.target.disabled = true;
|
||||||
|
if (!state.uploading) {
|
||||||
|
emit('upload', {
|
||||||
|
type: 'click',
|
||||||
|
dlimit: state.downloadCount || 1,
|
||||||
|
password: state.password
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const newFiles = Array.from(event.target.files);
|
||||||
|
|
||||||
|
emit('addFiles', { files: newFiles });
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(file) {
|
||||||
|
return html`
|
||||||
|
<input
|
||||||
|
type="image"
|
||||||
|
class="self-center text-white ml-4"
|
||||||
|
alt="Delete"
|
||||||
|
src="${assets.get('close-16.svg')}"
|
||||||
|
onclick=${del}/>`;
|
||||||
|
function del(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emit('removeUpload', file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.uploading = function(state, emit) {
|
||||||
|
const progress = state.transfer.progressRatio;
|
||||||
|
const progressPercent = percent(progress);
|
||||||
|
const archive = state.archive;
|
||||||
|
return html`
|
||||||
|
<article
|
||||||
|
id="${archive.id}"
|
||||||
|
class="z-20 flex flex-col items-start border border-grey-light bg-white p-4">
|
||||||
|
<p class="w-full">
|
||||||
|
<img class="float-left mr-3" src="${assets.get('blue_file.svg')}"/>
|
||||||
|
<h1 class="text-sm font-medium">${archive.name}</h1>
|
||||||
|
<div class="text-xs font-normal opacity-75 pt-1">${bytes(
|
||||||
|
archive.size
|
||||||
|
)}</div>
|
||||||
|
</p>
|
||||||
|
<div class="text-xs text-grey-dark w-full mt-2 mb-2">
|
||||||
|
${expiryInfo(state.translate, {
|
||||||
|
dlimit: state.downloadCount || 1,
|
||||||
|
dtotal: 0,
|
||||||
|
expiresAt: Date.now() + 500 + state.timeLimit * 1000
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div class="text-blue text-sm font-medium mt-2">${progressPercent}</div>
|
||||||
|
<progress class="my-3" value="${progress}">${progressPercent}</progress>
|
||||||
|
<button
|
||||||
|
class="text-blue self-end font-medium"
|
||||||
|
onclick=${cancel}>
|
||||||
|
${state.translate('uploadingPageCancel')}
|
||||||
|
</button>
|
||||||
|
</article>`;
|
||||||
|
|
||||||
|
function cancel(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.target.disabled = true;
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.empty = function(state, emit) {
|
||||||
|
return html`
|
||||||
|
<article class="flex flex-col items-center justify-center border border-dashed border-blue-light px-6 py-16 h-full">
|
||||||
|
<img src="${assets.get('addfiles.svg')}" width="48" height="48"/>
|
||||||
|
<div class="pt-6 pb-2 text-center text-lg font-bold uppercase tracking-wide">${state.translate(
|
||||||
|
'uploadDropDragMessage'
|
||||||
|
)}</div>
|
||||||
|
<div class="pb-6 text-center text-base italic">${state.translate(
|
||||||
|
'uploadDropClickMessage'
|
||||||
|
)}</div>
|
||||||
|
<input
|
||||||
|
id="file-upload"
|
||||||
|
class="hidden"
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
onchange=${add} />
|
||||||
|
<label
|
||||||
|
for="file-upload"
|
||||||
|
class="rounded bg-blue hover\:bg-blue-dark focus\:bg-blue-darker cursor-pointer text-center text-white py-2 px-6 h-12 mt-4 flex flex-no-shrink items-center justify-center font-semibold"
|
||||||
|
title="${state.translate('addFilesButton')}">
|
||||||
|
${state.translate('addFilesButton')}
|
||||||
|
</label>
|
||||||
|
</article>`;
|
||||||
|
|
||||||
|
function add(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const newFiles = Array.from(event.target.files);
|
||||||
|
|
||||||
|
emit('addFiles', { files: newFiles });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.preview = function(state, emit) {
|
||||||
|
const archive = state.fileInfo;
|
||||||
|
if (archive.open === undefined) {
|
||||||
|
archive.open = true;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<article class="flex flex-col max-h-full bg-white border border-grey-light p-4 z-20">
|
||||||
|
<p class="w-full mb-4">
|
||||||
|
<img class="float-left mr-3" src="${assets.get('blue_file.svg')}"/>
|
||||||
|
<h1 class="text-sm font-medium">${archive.name}</h1>
|
||||||
|
<div class="text-xs font-normal opacity-75 pt-1">${bytes(
|
||||||
|
archive.size
|
||||||
|
)}</div>
|
||||||
|
</p>
|
||||||
|
${archiveDetails(state.translate, archive)}
|
||||||
|
<button
|
||||||
|
id="download-btn"
|
||||||
|
class="rounded bg-blue hover\:bg-blue-dark focus\:bg-blue-darker cursor-pointer text-center text-white mt-4 py-2 px-6 h-12 w-full flex flex-no-shrink items-center justify-center font-semibold"
|
||||||
|
title="${state.translate('downloadButtonLabel')}"
|
||||||
|
onclick=${download}>
|
||||||
|
${state.translate('downloadButtonLabel')}
|
||||||
|
</button>
|
||||||
|
</article>`;
|
||||||
|
|
||||||
|
function download(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.target.disabled = true;
|
||||||
|
emit('download', archive);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.downloading = function(state, emit) {
|
||||||
|
const archive = state.fileInfo;
|
||||||
|
const progress = state.transfer.progressRatio;
|
||||||
|
const progressPercent = percent(progress);
|
||||||
|
return html`
|
||||||
|
<article class="flex flex-col bg-white border border-grey-light p-4 z-20">
|
||||||
|
<p class="w-full mb-4">
|
||||||
|
<img class="float-left mr-3" src="${assets.get('blue_file.svg')}"/>
|
||||||
|
<h1 class="text-sm font-medium">${archive.name}</h1>
|
||||||
|
<div class="text-xs font-normal opacity-75 pt-1">${bytes(
|
||||||
|
archive.size
|
||||||
|
)}</div>
|
||||||
|
</p>
|
||||||
|
<div class="text-blue text-sm font-medium mt-2">${progressPercent}</div>
|
||||||
|
<progress class="my-3" value="${progress}">${progressPercent}</progress>
|
||||||
|
<button
|
||||||
|
class="border rounded bg-grey-dark text-white mt-2 text-center py-2 px-6 h-12 w-full flex flex-no-shrink items-center justify-center font-semibold"
|
||||||
|
title="${state.translate('downloadCancel')}"
|
||||||
|
onclick=${cancel}>
|
||||||
|
${state.translate('downloadCancel')}
|
||||||
|
</button>
|
||||||
|
</article>`;
|
||||||
|
|
||||||
|
function cancel(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.target.disabled = true;
|
||||||
|
emit('download', archive);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
|
|
||||||
module.exports = function() {
|
module.exports = function() {
|
||||||
return html`<div></div>`;
|
return html`<main class="main container"></main>`;
|
||||||
};
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const { copyToClipboard } = require('../utils');
|
||||||
|
|
||||||
|
module.exports = function(name, url) {
|
||||||
|
return function(state, emit, close) {
|
||||||
|
return html`
|
||||||
|
<div class="flex flex-col items-center text-center p-4 max-w-sm">
|
||||||
|
<h1 class="font-bold my-4">${state.translate('notifyUploadDone')}</h1>
|
||||||
|
<p class="font-normal leading-normal text-grey-darker">${state.translate(
|
||||||
|
'copyUrlFormLabelWithName',
|
||||||
|
{ filename: name }
|
||||||
|
)}</p>
|
||||||
|
<input type="text" id="share-url" class="w-full my-4 border rounded leading-loose h-12 px-2 py-1" value=${url} readonly="true"/>
|
||||||
|
<button class="rounded bg-blue hover\:bg-blue-dark focus\:bg-blue-darker cursor-pointer text-center text-white py-2 px-6 h-12 w-full flex flex-no-shrink items-center justify-center font-semibold" onclick=${copy}>
|
||||||
|
${state.translate('copyUrlFormButton')}
|
||||||
|
</button>
|
||||||
|
<a class="text-blue my-4 font-medium cursor-pointer" onclick=${close}>${state.translate(
|
||||||
|
'okButton'
|
||||||
|
)}</a>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
function copy(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
copyToClipboard(url);
|
||||||
|
event.target.textContent = state.translate('copiedUrl');
|
||||||
|
setTimeout(close, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,114 @@
|
||||||
|
/* global downloadMetadata */
|
||||||
|
const html = require('choo/html');
|
||||||
|
const archiveTile = require('./archiveTile');
|
||||||
|
const intro = require('./intro');
|
||||||
|
|
||||||
|
function password(state, emit) {
|
||||||
|
const fileInfo = state.fileInfo;
|
||||||
|
const invalid = fileInfo.password === null;
|
||||||
|
|
||||||
|
const div = html`
|
||||||
|
<div class="h-full flex flex-col items-center justify-center border border-grey-light bg-white py-8">
|
||||||
|
<label
|
||||||
|
id="password-error"
|
||||||
|
class="${invalid ? '' : 'invisible'} text-red my-4"
|
||||||
|
for="password-input">
|
||||||
|
${state.translate('passwordTryAgain')}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<form class="w-5/6" onsubmit=${checkPassword} data-no-csrf>
|
||||||
|
<input id="password-input"
|
||||||
|
class="w-full border rounded ${
|
||||||
|
invalid ? 'border-red' : 'border-grey'
|
||||||
|
} leading-loose px-2 py-1"
|
||||||
|
maxlength="32"
|
||||||
|
autocomplete="off"
|
||||||
|
placeholder="${state.translate('unlockInputPlaceholder')}"
|
||||||
|
oninput=${inputChanged}
|
||||||
|
type="password" />
|
||||||
|
|
||||||
|
<input type="submit"
|
||||||
|
id="password-btn"
|
||||||
|
class="hidden"
|
||||||
|
value="${state.translate('unlockInputLabel')}"/>
|
||||||
|
</form>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
if (!(div instanceof String)) {
|
||||||
|
setTimeout(() => document.getElementById('password-input').focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputChanged() {
|
||||||
|
const label = document.getElementById('password-error');
|
||||||
|
const input = document.getElementById('password-input');
|
||||||
|
label.classList.add('invisible');
|
||||||
|
input.classList.remove('border-red');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPassword(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const password = document.getElementById('password-input').value;
|
||||||
|
if (password.length > 0) {
|
||||||
|
document.getElementById('password-btn').disabled = true;
|
||||||
|
state.fileInfo.url = window.location.href;
|
||||||
|
state.fileInfo.password = password;
|
||||||
|
emit('getMetadata');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFileInfo(state) {
|
||||||
|
return {
|
||||||
|
id: state.params.id,
|
||||||
|
secretKey: state.params.key,
|
||||||
|
nonce: downloadMetadata.nonce,
|
||||||
|
requiresPassword: downloadMetadata.pwd
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function(state, emit) {
|
||||||
|
let content = '';
|
||||||
|
if (!state.fileInfo) {
|
||||||
|
state.fileInfo = createFileInfo(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.transfer && !state.fileInfo.requiresPassword) {
|
||||||
|
emit('getMetadata');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.transfer) {
|
||||||
|
switch (state.transfer.state) {
|
||||||
|
case 'downloading':
|
||||||
|
case 'decrypting':
|
||||||
|
content = archiveTile.downloading(state, emit);
|
||||||
|
break;
|
||||||
|
case 'complete':
|
||||||
|
content = html`
|
||||||
|
<div id="download-complete" class="flex flex-col items-center justify-center h-full bg-white border border-grey-light p-2">
|
||||||
|
<h1 class="text-center font-bold my-4 text-2xl">${state.translate(
|
||||||
|
'downloadFinish'
|
||||||
|
)}</h1>
|
||||||
|
<p class="mb-4">
|
||||||
|
<a href="/" class="text-blue font-medium">${state.translate(
|
||||||
|
'sendYourFilesLink'
|
||||||
|
)}</a>
|
||||||
|
</p>
|
||||||
|
</div>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
content = archiveTile.preview(state, emit);
|
||||||
|
}
|
||||||
|
} else if (state.fileInfo.requiresPassword && !state.fileInfo.password) {
|
||||||
|
content = password(state, emit);
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<main class="main container">
|
||||||
|
<section class="relative h-full w-full p-6 md:flex md:flex-row">
|
||||||
|
<div class="md:mr-6 md:w-1/2">${content}</div>
|
||||||
|
<div class="md:w-1/2 mt-6 md:mt-0">${intro(state)}</div>
|
||||||
|
</section>
|
||||||
|
</main>`;
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
|
|
||||||
|
module.exports = function(state) {
|
||||||
|
return html`
|
||||||
|
<main class="main container">
|
||||||
|
<div class="flex flex-col items-center bg-white m-6 p-6 border border-grey-light md:border-none md:px-12 md:py-16 shadow w-full md:h-full">
|
||||||
|
<h1 class="text-2xl">${state.translate('errorPageHeader')}</h1>
|
||||||
|
<img class="my-16" src="${assets.get('illustration_error.svg')}"/>
|
||||||
|
<p class="max-w-md leading-normal">
|
||||||
|
${state.translate('uploadPageExplainer')}
|
||||||
|
</p>
|
||||||
|
<a class="text-blue my-10 font-medium" href="/">
|
||||||
|
${state.translate('sendYourFilesLink')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>`;
|
||||||
|
};
|
|
@ -1,17 +1,19 @@
|
||||||
/* globals DEFAULTS */
|
/* globals DEFAULTS */
|
||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
const raw = require('choo/html/raw');
|
const raw = require('choo/html/raw');
|
||||||
const selectbox = require('../selectbox');
|
const { secondsToL10nId } = require('../utils');
|
||||||
const timeLimitText = require('../timeLimitText');
|
const selectbox = require('./selectbox');
|
||||||
const signupDialog = require('../signupDialog');
|
const signupDialog = require('./signupDialog');
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
module.exports = function(state, emit) {
|
||||||
const el = html`<div> ${raw(
|
const el = html`
|
||||||
state.translate('frontPageExpireInfo', {
|
<div class="">
|
||||||
downloadCount: '<select id=dlCount></select>',
|
${raw(
|
||||||
timespan: '<select id=timespan></select>'
|
state.translate('frontPageExpireInfo', {
|
||||||
})
|
downloadCount: '<select id="dlCount"></select>',
|
||||||
)}
|
timespan: '<select id="timespan"></select>'
|
||||||
|
})
|
||||||
|
)}
|
||||||
</div>`;
|
</div>`;
|
||||||
if (el.__encoded) {
|
if (el.__encoded) {
|
||||||
// we're rendering on the server
|
// we're rendering on the server
|
||||||
|
@ -50,7 +52,10 @@ module.exports = function(state, emit) {
|
||||||
selectbox(
|
selectbox(
|
||||||
state.timeLimit || 86400,
|
state.timeLimit || 86400,
|
||||||
expires,
|
expires,
|
||||||
num => timeLimitText(state.translate, num),
|
num => {
|
||||||
|
const l10n = secondsToL10nId(num);
|
||||||
|
return state.translate(l10n.id, l10n);
|
||||||
|
},
|
||||||
value => {
|
value => {
|
||||||
const max = state.user.maxExpireSeconds;
|
const max = state.user.maxExpireSeconds;
|
||||||
if (value > max) {
|
if (value > max) {
|
|
@ -0,0 +1,56 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const version = require('../../package.json').version;
|
||||||
|
const { browserName } = require('../utils');
|
||||||
|
|
||||||
|
module.exports = function(state) {
|
||||||
|
const browser = browserName();
|
||||||
|
const feedbackUrl = `https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}`;
|
||||||
|
const footer = html`<footer class="flex flex-col md:flex-row items-start w-full flex-none self-start p-6 font-medium text-xs text-grey-dark md:items-center justify-between bg-grey-lightest">
|
||||||
|
<a class="mozilla-logo pb-10 md:pb-0 m-2"
|
||||||
|
href="https://www.mozilla.org/">
|
||||||
|
Mozilla
|
||||||
|
</a>
|
||||||
|
<ul class="list-reset flex flex-col md:flex-row items-start md:items-center md:justify-end">
|
||||||
|
<li class="m-2"><a
|
||||||
|
href="https://www.mozilla.org/about/legal">
|
||||||
|
${state.translate('footerLinkLegal')}
|
||||||
|
</a></li>
|
||||||
|
<li class="m-2"><a
|
||||||
|
href="https://testpilot.firefox.com/about">
|
||||||
|
${state.translate('footerLinkAbout')}
|
||||||
|
</a></li>
|
||||||
|
<li class="m-2"><a
|
||||||
|
href="/legal">
|
||||||
|
${state.translate('footerLinkTerms')}
|
||||||
|
</a></li>
|
||||||
|
<li class="m-2"><a
|
||||||
|
href="https://www.mozilla.org/privacy/websites/#cookies">
|
||||||
|
${state.translate('footerLinkCookies')}
|
||||||
|
</a></li>
|
||||||
|
<li class="m-2"><a
|
||||||
|
href="https://www.mozilla.org/about/legal/report-infringement/">
|
||||||
|
${state.translate('reportIPInfringement')}
|
||||||
|
</a></li>
|
||||||
|
<li class="m-2"><a
|
||||||
|
href="https://github.com/mozilla/send">GitHub
|
||||||
|
</a></li>
|
||||||
|
<li class="m-2"><a
|
||||||
|
href="https://twitter.com/FxTestPilot">Twitter
|
||||||
|
</a></li>
|
||||||
|
<li class="m-2"><a href="${feedbackUrl}"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
class="feedback-link"
|
||||||
|
alt="Feedback"
|
||||||
|
target="_blank">
|
||||||
|
${state.translate('siteFeedback')}
|
||||||
|
</a></li>
|
||||||
|
</ul>
|
||||||
|
</footer>`;
|
||||||
|
// HACK
|
||||||
|
// We only want to render this once because we
|
||||||
|
// toggle the targets of the links with utils/openLinksInNewTab
|
||||||
|
footer.isSameNode = function(target) {
|
||||||
|
return target && target.nodeName && target.nodeName === 'FOOTER';
|
||||||
|
};
|
||||||
|
return footer;
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
return html`
|
||||||
|
<div class="w-full flex-none flex flex-row items-center content-center justify-center text-sm bg-grey-light text-grey-darkest h-12 px-4">
|
||||||
|
<div class="flex items-center mx-auto">
|
||||||
|
<img
|
||||||
|
src="${assets.get('firefox_logo-only.svg')}"
|
||||||
|
class="w-6"
|
||||||
|
alt="Firefox"/>
|
||||||
|
<span class="ml-3">Send is brought to you by the all-new Firefox.
|
||||||
|
<a
|
||||||
|
class="text-blue"
|
||||||
|
href="https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com">Download Firefox now ≫</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const account = require('./account');
|
||||||
|
|
||||||
|
module.exports = function(state, emit) {
|
||||||
|
const header = html`
|
||||||
|
<header class="relative flex-none flex flex-row items-center justify-between bg-white w-full px-6 h-16 shadow z-20">
|
||||||
|
<a
|
||||||
|
class="header-logo"
|
||||||
|
href="/">
|
||||||
|
<h1 class="text-black font-normal">Firefox <b>Send</b></h1>
|
||||||
|
</a>
|
||||||
|
${account(state, emit)}
|
||||||
|
<div class="invisible absolute pin-t pin-l mt-12 w-full flex flex-col items-center pointer-events-none">
|
||||||
|
<div class="border rounded bg-grey-darkest text-white mt-2 p-2">Your upload has finished.<button class="border border-blue rounded-sm bg-blue text-white inline-block p-1 ml-2">Copy Link</button><button class="text-white inline-block p-1 ml-2">ⓧ</button></div>
|
||||||
|
${state.toast ? state.toast() : ''}
|
||||||
|
</div>
|
||||||
|
</header>`;
|
||||||
|
// HACK
|
||||||
|
// We only want to render this once because we
|
||||||
|
// toggle the targets of the links with utils/openLinksInNewTab
|
||||||
|
// header.isSameNode = function(target) {
|
||||||
|
// return target && target.nodeName && target.nodeName === 'HEADER';
|
||||||
|
// };
|
||||||
|
return header;
|
||||||
|
};
|
|
@ -0,0 +1,33 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const { list } = require('../utils');
|
||||||
|
const archiveTile = require('./archiveTile');
|
||||||
|
const modal = require('./modal');
|
||||||
|
const intro = require('./intro');
|
||||||
|
|
||||||
|
module.exports = function(state, emit) {
|
||||||
|
const archives = state.storage.files.map(archive =>
|
||||||
|
archiveTile(state, emit, archive)
|
||||||
|
);
|
||||||
|
let left = '';
|
||||||
|
if (state.uploading) {
|
||||||
|
left = archiveTile.uploading(state, emit);
|
||||||
|
} else if (state.archive) {
|
||||||
|
left = archiveTile.wip(state, emit);
|
||||||
|
} else {
|
||||||
|
left = archiveTile.empty(state, emit);
|
||||||
|
}
|
||||||
|
archives.reverse();
|
||||||
|
const right =
|
||||||
|
archives.length < 1
|
||||||
|
? intro(state)
|
||||||
|
: list(archives, 'list-reset h-full overflow-y-scroll', 'mb-3');
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<main class="main md:relative">
|
||||||
|
${state.modal && modal(state, emit)}
|
||||||
|
<section class="h-full w-full p-6 md:flex md:flex-row z-10">
|
||||||
|
<div class="md:mr-6 md:w-1/2">${left}</div>
|
||||||
|
<div class="md:w-1/2 overflow-y-scroll mt-6 md:mt-0">${right}</div>
|
||||||
|
</section>
|
||||||
|
</main>`;
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
|
|
||||||
|
module.exports = function intro(state) {
|
||||||
|
return html`
|
||||||
|
<article class="flex flex-col items-center justify-between bg-white border border-grey-light md:border-none px-6 md:py-10 py-6 md:mb-0 mb-6">
|
||||||
|
<p class="text-center">
|
||||||
|
<div class="font-semibold leading-normal">${state.translate(
|
||||||
|
'uploadPageHeader'
|
||||||
|
)}</div>
|
||||||
|
<div class="italic text-sm opacity-75 leading-normal">${state.translate(
|
||||||
|
'pageHeaderCredits'
|
||||||
|
)}</div>
|
||||||
|
</p>
|
||||||
|
<img class="my-6" src="${assets.get('illustration_download.svg')}"/>
|
||||||
|
<p class="md:mx-6 max-w-sm text-sm opacity-50 leading-normal">${state.translate(
|
||||||
|
'uploadPageExplainer'
|
||||||
|
)}</p>
|
||||||
|
</article>`;
|
||||||
|
};
|
|
@ -0,0 +1,33 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const raw = require('choo/html/raw');
|
||||||
|
|
||||||
|
module.exports = function(state) {
|
||||||
|
return html`
|
||||||
|
<main class="main container">
|
||||||
|
<div class="flex flex-col items-center bg-white m-6 p-6 border border-grey-light md:border-none md:px-12 md:py-16 shadow w-full md:h-full">
|
||||||
|
<h1 class="text-2xl">${state.translate('legalHeader')}</h1>
|
||||||
|
${raw(
|
||||||
|
replaceLinks(state.translate('legalNoticeTestPilot'), [
|
||||||
|
'https://testpilot.firefox.com/terms',
|
||||||
|
'https://testpilot.firefox.com/privacy',
|
||||||
|
'https://testpilot.firefox.com/experiments/send'
|
||||||
|
])
|
||||||
|
)}
|
||||||
|
${raw(
|
||||||
|
replaceLinks(state.translate('legalNoticeMozilla'), [
|
||||||
|
'https://www.mozilla.org/privacy/websites/',
|
||||||
|
'https://www.mozilla.org/about/legal/terms/mozilla/'
|
||||||
|
])
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
function replaceLinks(str, urls) {
|
||||||
|
let i = 0;
|
||||||
|
const s = str.replace(
|
||||||
|
/<a>([^<]+)<\/a>/g,
|
||||||
|
(m, v) => `<a class="text-blue" href="${urls[i++]}">${v}</a>`
|
||||||
|
);
|
||||||
|
return `<p class="mt-10 max-w-md leading-normal">${s}</p>`;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
|
||||||
|
module.exports = function(state, emit) {
|
||||||
|
return html`
|
||||||
|
<div class="absolute pin flex items-center justify-center overflow-hidden z-40 bg-cloud" onclick=${close}>
|
||||||
|
<div class="h-full max-h-screen absolute pin-t flex items-center">
|
||||||
|
<div class="shadow-cloud bg-white" onclick=${e => e.stopPropagation()}>
|
||||||
|
${state.modal(state, emit, close)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
function close(event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
state.modal = null;
|
||||||
|
emit('render');
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
|
|
||||||
|
module.exports = function(state) {
|
||||||
|
return html`
|
||||||
|
<main class="main container">
|
||||||
|
<div class="flex flex-col items-center bg-white m-6 p-6 border border-grey-light md:border-none md:px-12 md:py-16 shadow w-full md:h-full">
|
||||||
|
<h1 class="text-pink-dark text-2xl">${state.translate(
|
||||||
|
'expiredPageHeader'
|
||||||
|
)}</h1>
|
||||||
|
<img class="my-16" src="${assets.get(
|
||||||
|
'illustration_expired.svg'
|
||||||
|
)}" id="expired-img">
|
||||||
|
<p class="max-w-md leading-normal">
|
||||||
|
${state.translate('uploadPageExplainer')}
|
||||||
|
</p>
|
||||||
|
<a class="text-blue my-10 font-medium" href="/">
|
||||||
|
${state.translate('sendYourFilesLink')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>`;
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
|
||||||
|
module.exports = function(message) {
|
||||||
|
return function(state, emit, close) {
|
||||||
|
return html`
|
||||||
|
<div class="flex flex-col max-w-xs p-4">
|
||||||
|
<div class="text-center m-8 leading-normal">${message}</div>
|
||||||
|
<button class="rounded bg-blue hover\:bg-blue-dark focus\:bg-blue-darker cursor-pointer text-center text-white py-2 px-6 h-12 w-full flex flex-no-shrink items-center justify-center font-semibold" onclick=${close}>${state.translate(
|
||||||
|
'okButton'
|
||||||
|
)}</button>
|
||||||
|
</div>`;
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,11 +1,10 @@
|
||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
|
|
||||||
module.exports = function(selected, options, translate, changed) {
|
module.exports = function(selected, options, translate, changed) {
|
||||||
const id = `select-${Math.random()}`;
|
|
||||||
let x = selected;
|
let x = selected;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<select class="selectBox" id="${id}" onchange=${choose}>
|
<select class="appearance-none cursor-pointer border rounded-sm bg-blue-lightest hover:border-blue focus:border-blue px-2 py-1 mx-1 h-8" onchange=${choose}>
|
||||||
${options.map(
|
${options.map(
|
||||||
i =>
|
i =>
|
||||||
html`<option value="${i}" ${
|
html`<option value="${i}" ${
|
|
@ -1,40 +1,39 @@
|
||||||
/* globals LIMITS */
|
/* global LIMITS */
|
||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
const bytes = require('../../utils').bytes;
|
const { bytes } = require('../utils');
|
||||||
|
|
||||||
// TODO: there's some duplication here with the signin page
|
|
||||||
module.exports = function() {
|
module.exports = function() {
|
||||||
return function(state, emit, close) {
|
return function(state, emit, close) {
|
||||||
return html`
|
return html`
|
||||||
<div class="signupDialog">
|
<div class="flex flex-col p-4">
|
||||||
<div class="signupDialog__message">
|
<p class="p-8">
|
||||||
${state.translate('accountBenefitTitle')}
|
${state.translate('accountBenefitTitle')}
|
||||||
<ul>
|
<ul class="my-2 leading-normal">
|
||||||
<li>${state.translate('accountBenefitLargeFiles', {
|
<li>${state.translate('accountBenefitLargeFiles', {
|
||||||
size: bytes(LIMITS.MAX_FILE_SIZE)
|
size: bytes(LIMITS.MAX_FILE_SIZE)
|
||||||
})}</li>
|
})}</li>
|
||||||
<li>${state.translate('accountBenefitExpiry')}</li>
|
<li>${state.translate('accountBenefitExpiry')}</li>
|
||||||
<li>${state.translate('accountBenefitSync')}</li>
|
<li>${state.translate('accountBenefitSync')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</p>
|
||||||
<form
|
<form
|
||||||
onsubmit=${submitEmail}
|
onsubmit=${submitEmail}
|
||||||
data-no-csrf>
|
data-no-csrf>
|
||||||
<input
|
<input
|
||||||
id="email-input"
|
id="email-input"
|
||||||
type="text"
|
type="text"
|
||||||
class="signupDialog__emailInput"
|
class="border rounded w-full px-2 py-1 h-12 mb-4 text-lg text-grey-darker leading-loose"
|
||||||
placeholder=${state.translate('emailEntryPlaceholder')}/>
|
placeholder=${state.translate('emailEntryPlaceholder')}/>
|
||||||
<input
|
<input
|
||||||
class='noDisplay'
|
class="hidden"
|
||||||
id="email-submit"
|
id="email-submit"
|
||||||
type="submit"/>
|
type="submit"/>
|
||||||
</form>
|
</form>
|
||||||
<label class="btn" for="email-submit">
|
<label class="rounded bg-blue hover\:bg-blue-dark focus\:bg-blue-darker cursor-pointer text-center text-white py-2 px-6 h-12 w-full flex flex-no-shrink items-center justify-center font-semibold" for="email-submit">
|
||||||
${state.translate('signInMenuOption')}
|
${state.translate('signInMenuOption')}
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
class="btn--cancel"
|
class="my-4 text-blue font-medium"
|
||||||
title="${state.translate('deletePopupCancel')}"
|
title="${state.translate('deletePopupCancel')}"
|
||||||
onclick=${close}>${state.translate('deletePopupCancel')}
|
onclick=${close}>${state.translate('deletePopupCancel')}
|
||||||
</button>
|
</button>
|
|
@ -0,0 +1,65 @@
|
||||||
|
const html = require('choo/html');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
|
|
||||||
|
module.exports = function(state) {
|
||||||
|
let strings = {};
|
||||||
|
let why = '';
|
||||||
|
let url = '';
|
||||||
|
let buttonAction = '';
|
||||||
|
|
||||||
|
if (state.params.reason !== 'outdated') {
|
||||||
|
strings = unsupportedStrings(state);
|
||||||
|
why = html`
|
||||||
|
<a
|
||||||
|
class="text-blue" href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-is-my-browser-not-supported">
|
||||||
|
${state.translate('notSupportedLink')}
|
||||||
|
</a>`;
|
||||||
|
url =
|
||||||
|
'https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com';
|
||||||
|
buttonAction = html`
|
||||||
|
<p class="ml-4 font-bold">
|
||||||
|
Firefox<br><span class="font-light text-base">${strings.button}</span>
|
||||||
|
</p>`;
|
||||||
|
} else {
|
||||||
|
strings = outdatedStrings(state);
|
||||||
|
url = 'https://support.mozilla.org/kb/update-firefox-latest-version';
|
||||||
|
buttonAction = html`
|
||||||
|
<p class="ml-4">
|
||||||
|
${strings.button}
|
||||||
|
</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<main class="main container">
|
||||||
|
<div class="flex flex-col items-center bg-white m-6 p-6 border border-grey-light md:border-none md:px-12 md:py-16 shadow w-full md:h-full">
|
||||||
|
<h1 class="text-center text-2xl">${strings.header}</h1>
|
||||||
|
<p class="my-10 max-w-md leading-normal">
|
||||||
|
${strings.description}
|
||||||
|
</p>
|
||||||
|
${why}
|
||||||
|
<a href="${url}" class="border border-green-light rounded bg-green hover\:bg-green-dark focus\:bg-green-darker flex items-center justify-center text-2xl text-white my-10 py-4 px-6">
|
||||||
|
<img
|
||||||
|
src="${assets.get('firefox_logo-only.svg')}"
|
||||||
|
class="w-10"
|
||||||
|
alt="Firefox"/>
|
||||||
|
${buttonAction}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
function outdatedStrings(state) {
|
||||||
|
return {
|
||||||
|
header: state.translate('notSupportedHeader'),
|
||||||
|
description: state.translate('notSupportedOutdatedDetail'),
|
||||||
|
button: state.translate('updateFirefox')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsupportedStrings(state) {
|
||||||
|
return {
|
||||||
|
header: state.translate('notSupportedHeader'),
|
||||||
|
description: state.translate('notSupportedDetail'),
|
||||||
|
button: state.translate('downloadFirefoxButtonSub')
|
||||||
|
};
|
||||||
|
}
|
48
app/utils.js
48
app/utils.js
|
@ -1,3 +1,4 @@
|
||||||
|
const html = require('choo/html');
|
||||||
const b64 = require('base64-js');
|
const b64 = require('base64-js');
|
||||||
|
|
||||||
function arrayToB64(array) {
|
function arrayToB64(array) {
|
||||||
|
@ -182,6 +183,48 @@ async function streamToArrayBuffer(stream, size) {
|
||||||
return result.buffer;
|
return result.buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function list(items, ulStyle = '', liStyle = '') {
|
||||||
|
const lis = items.map(i => html`<li class="${liStyle}">${i}</li>`);
|
||||||
|
return html`<ul class="${ulStyle}">${lis}</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function secondsToL10nId(seconds) {
|
||||||
|
if (seconds < 3600) {
|
||||||
|
return { id: 'timespanMinutes', num: Math.floor(seconds / 60) };
|
||||||
|
} else if (seconds < 86400) {
|
||||||
|
return { id: 'timespanHours', num: Math.floor(seconds / 3600) };
|
||||||
|
} else {
|
||||||
|
return { id: 'timespanDays', num: Math.floor(seconds / 86400) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeLeft(milliseconds) {
|
||||||
|
const minutes = Math.floor(milliseconds / 1000 / 60);
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
if (days >= 1) {
|
||||||
|
return {
|
||||||
|
id: 'expiresDaysHoursMinutes',
|
||||||
|
days,
|
||||||
|
hours: hours % 24,
|
||||||
|
minutes: minutes % 60
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hours >= 1) {
|
||||||
|
return {
|
||||||
|
id: 'expiresHoursMinutes',
|
||||||
|
hours,
|
||||||
|
minutes: minutes % 60
|
||||||
|
};
|
||||||
|
} else if (hours === 0) {
|
||||||
|
if (minutes === 0) {
|
||||||
|
return { id: 'expiresMinutes', minutes: '< 1' };
|
||||||
|
}
|
||||||
|
return { id: 'expiresMinutes', minutes };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
fadeOut,
|
fadeOut,
|
||||||
delay,
|
delay,
|
||||||
|
@ -196,5 +239,8 @@ module.exports = {
|
||||||
isFile,
|
isFile,
|
||||||
openLinksInNewTab,
|
openLinksInNewTab,
|
||||||
browserName,
|
browserName,
|
||||||
streamToArrayBuffer
|
streamToArrayBuffer,
|
||||||
|
list,
|
||||||
|
secondsToL10nId,
|
||||||
|
timeLeft
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<defs>
|
||||||
|
<polygon id="addfiles-a" points="11.143 6 11.143 11.143 6 11.143 6 12.857 11.143 12.857 11.143 18 12.857 18 12.857 12.857 18 12.857 18 11.143 12.857 11.143 12.857 6"/>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#4285F4" fill-rule="nonzero" d="M12,22.6666667 C17.8910373,22.6666667 22.6666667,17.8910373 22.6666667,12 C22.6666667,6.10896267 17.8910373,1.33333333 12,1.33333333 C6.10896267,1.33333333 1.33333333,6.10896267 1.33333333,12 C1.33333333,17.8910373 6.10896267,22.6666667 12,22.6666667 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z"/>
|
||||||
|
<use fill="#4285F4" xlink:href="#addfiles-a"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 856 B |
|
@ -94,8 +94,6 @@ workflows:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: master
|
ignore: master
|
||||||
requires:
|
|
||||||
- build
|
|
||||||
build_and_deploy_dev:
|
build_and_deploy_dev:
|
||||||
jobs:
|
jobs:
|
||||||
- build:
|
- build:
|
||||||
|
@ -137,8 +135,6 @@ workflows:
|
||||||
tags:
|
tags:
|
||||||
only: /^v.*/
|
only: /^v.*/
|
||||||
- integration_tests:
|
- integration_tests:
|
||||||
requires:
|
|
||||||
- build
|
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -64,6 +64,7 @@
|
||||||
"@babel/polyfill": "^7.0.0",
|
"@babel/polyfill": "^7.0.0",
|
||||||
"@babel/preset-env": "^7.1.0",
|
"@babel/preset-env": "^7.1.0",
|
||||||
"@dannycoates/webpack-dev-server": "^3.1.4",
|
"@dannycoates/webpack-dev-server": "^3.1.4",
|
||||||
|
"@fullhuman/postcss-purgecss": "^1.1.0",
|
||||||
"@mattiasbuelens/web-streams-polyfill": "0.1.0",
|
"@mattiasbuelens/web-streams-polyfill": "0.1.0",
|
||||||
"asmcrypto.js": "^2.3.2",
|
"asmcrypto.js": "^2.3.2",
|
||||||
"babel-loader": "^8.0.4",
|
"babel-loader": "^8.0.4",
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"css-mqpacker": "^7.0.0",
|
"css-mqpacker": "^7.0.0",
|
||||||
|
"cssnano": "^4.1.7",
|
||||||
"eslint": "^5.6.1",
|
"eslint": "^5.6.1",
|
||||||
"eslint-plugin-mocha": "^5.2.0",
|
"eslint-plugin-mocha": "^5.2.0",
|
||||||
"eslint-plugin-node": "^7.0.1",
|
"eslint-plugin-node": "^7.0.1",
|
||||||
|
@ -97,9 +99,8 @@
|
||||||
"nanotiming": "^7.3.1",
|
"nanotiming": "^7.3.1",
|
||||||
"npm-run-all": "^4.1.3",
|
"npm-run-all": "^4.1.3",
|
||||||
"nyc": "^13.0.1",
|
"nyc": "^13.0.1",
|
||||||
"postcss-cssnext": "^3.1.0",
|
|
||||||
"postcss-import": "^12.0.0",
|
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
|
"postcss-preset-env": "^6.2.0",
|
||||||
"prettier": "^1.14.3",
|
"prettier": "^1.14.3",
|
||||||
"proxyquire": "^2.1.0",
|
"proxyquire": "^2.1.0",
|
||||||
"puppeteer": "^1.9.0",
|
"puppeteer": "^1.9.0",
|
||||||
|
@ -114,6 +115,7 @@
|
||||||
"stylelint-no-unsupported-browser-features": "^3.0.2",
|
"stylelint-no-unsupported-browser-features": "^3.0.2",
|
||||||
"svgo": "^1.1.1",
|
"svgo": "^1.1.1",
|
||||||
"svgo-loader": "^2.2.0",
|
"svgo-loader": "^2.2.0",
|
||||||
|
"tailwindcss": "^0.6.6",
|
||||||
"testpilot-ga": "^0.3.0",
|
"testpilot-ga": "^0.3.0",
|
||||||
"val-loader": "^1.1.1",
|
"val-loader": "^1.1.1",
|
||||||
"wdio-docker-service": "^1.4.2",
|
"wdio-docker-service": "^1.4.2",
|
||||||
|
|
|
@ -1,13 +1,35 @@
|
||||||
const options = {
|
class TailwindExtractor {
|
||||||
plugins: {
|
static extract(content) {
|
||||||
'postcss-import': {},
|
return content.match(/[A-Za-z0-9-_:/]+/g) || [];
|
||||||
'postcss-cssnext': {},
|
|
||||||
'css-mqpacker': {}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
plugins: [
|
||||||
|
require('tailwindcss')('./tailwind.js'),
|
||||||
|
require('postcss-preset-env')
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
options.map = { inline: true };
|
options.map = { inline: true };
|
||||||
|
} else {
|
||||||
|
options.plugins.push(
|
||||||
|
require('@fullhuman/postcss-purgecss')({
|
||||||
|
content: ['./app/*.js', './app/ui/*.js'],
|
||||||
|
extractors: [
|
||||||
|
{
|
||||||
|
extractor: TailwindExtractor,
|
||||||
|
extensions: ['js']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
options.plugins.push(
|
||||||
|
require('cssnano')({
|
||||||
|
preset: 'default'
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = options;
|
module.exports = options;
|
||||||
|
|
|
@ -40,6 +40,10 @@ timespanMinutes = { $num ->
|
||||||
[one] 1 minute
|
[one] 1 minute
|
||||||
*[other] { $num } minutes
|
*[other] { $num } minutes
|
||||||
}
|
}
|
||||||
|
timespanDays = { $num ->
|
||||||
|
[one] 1 day
|
||||||
|
*[other] { $num } days
|
||||||
|
}
|
||||||
timespanWeeks = { $num ->
|
timespanWeeks = { $num ->
|
||||||
[one] 1 week
|
[one] 1 week
|
||||||
*[other] { $num } weeks
|
*[other] { $num } weeks
|
||||||
|
@ -152,8 +156,8 @@ addPasswordMessage = Protect with password
|
||||||
addPasswordLabel = Password:
|
addPasswordLabel = Password:
|
||||||
copyUrlLabel = Copy and share this link:
|
copyUrlLabel = Copy and share this link:
|
||||||
passwordReminder = don't forget the password too
|
passwordReminder = don't forget the password too
|
||||||
signInPromoText = Sign In/Up!
|
signInPromoText = Sign In/Up
|
||||||
signInExplanation = It's free and gives you many more Send options
|
signInExplanation = It's free and you can send bigger files.
|
||||||
signInLearnMore = Learn more!
|
signInLearnMore = Learn more!
|
||||||
downloadProgressButton = Downloading... { $progress }
|
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.
|
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.
|
||||||
|
@ -170,3 +174,4 @@ accountBenefitSync = Manage your uploads across devices
|
||||||
manageAccount = Manage Account
|
manageAccount = Manage Account
|
||||||
logOut = Sign Out
|
logOut = Sign Out
|
||||||
okButton = Ok
|
okButton = Ok
|
||||||
|
myUploads = My Uploads
|
|
@ -9,7 +9,10 @@ async function getFxaConfig() {
|
||||||
if (fxaConfig && Date.now() - lastConfigRefresh < 1000 * 60 * 5) {
|
if (fxaConfig && Date.now() - lastConfigRefresh < 1000 * 60 * 5) {
|
||||||
return fxaConfig;
|
return fxaConfig;
|
||||||
}
|
}
|
||||||
const res = await fetch(`${config.fxa_url}/.well-known/openid-configuration`);
|
const res = await fetch(
|
||||||
|
`${config.fxa_url}/.well-known/openid-configuration`,
|
||||||
|
{ timeout: 3000 }
|
||||||
|
);
|
||||||
fxaConfig = await res.json();
|
fxaConfig = await res.json();
|
||||||
lastConfigRefresh = Date.now();
|
lastConfigRefresh = Date.now();
|
||||||
return fxaConfig;
|
return fxaConfig;
|
||||||
|
|
|
@ -73,6 +73,17 @@ module.exports = function(state, body = '') {
|
||||||
<script defer src="${assets.get('cryptofill.js')}"></script>
|
<script defer src="${assets.get('cryptofill.js')}"></script>
|
||||||
<script defer src="${assets.get('app.js')}"></script>
|
<script defer src="${assets.get('app.js')}"></script>
|
||||||
</head>
|
</head>
|
||||||
|
<noscript>
|
||||||
|
<div class="noscript">
|
||||||
|
<h2>${state.translate('javascriptRequired')}</h2>
|
||||||
|
<p>
|
||||||
|
<a class="link" href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-does-firefox-send-require-javascript">
|
||||||
|
${state.translate('whyJavascript')}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>${state.translate('enableJavascript')}</p>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
${body}
|
${body}
|
||||||
${initScript(state)}
|
${initScript(state)}
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -67,13 +67,10 @@ module.exports = function(app) {
|
||||||
});
|
});
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.get('/', language, pages.index);
|
app.get('/', language, pages.index);
|
||||||
app.get('/signin', pages.blank);
|
app.get('/oauth', language, pages.blank);
|
||||||
app.get('/oauth', pages.blank);
|
|
||||||
app.get('/legal', language, pages.legal);
|
app.get('/legal', language, pages.legal);
|
||||||
app.get('/jsconfig.js', require('./jsconfig'));
|
app.get('/jsconfig.js', require('./jsconfig'));
|
||||||
app.get(`/share/:id${ID_REGEX}`, language, pages.blank);
|
|
||||||
app.get(`/download/:id${ID_REGEX}`, language, pages.download);
|
app.get(`/download/:id${ID_REGEX}`, language, pages.download);
|
||||||
app.get('/completed', language, pages.blank);
|
|
||||||
app.get('/unsupported/:reason', language, pages.unsupported);
|
app.get('/unsupported/:reason', language, pages.unsupported);
|
||||||
app.get(`/api/download/:id${ID_REGEX}`, auth.hmac, require('./download'));
|
app.get(`/api/download/:id${ID_REGEX}`, auth.hmac, require('./download'));
|
||||||
app.get(
|
app.get(
|
||||||
|
|
|
@ -31,9 +31,13 @@ if (config.analytics_id) {
|
||||||
module.exports = async function(req, res) {
|
module.exports = async function(req, res) {
|
||||||
let authConfig = '';
|
let authConfig = '';
|
||||||
if (config.fxa_client_id) {
|
if (config.fxa_client_id) {
|
||||||
const fxaConfig = await getFxaConfig();
|
try {
|
||||||
fxaConfig.client_id = config.fxa_client_id;
|
const fxaConfig = await getFxaConfig();
|
||||||
authConfig = `var AUTH_CONFIG = ${JSON.stringify(fxaConfig)};`;
|
fxaConfig.client_id = config.fxa_client_id;
|
||||||
|
authConfig = `var AUTH_CONFIG = ${JSON.stringify(fxaConfig)};`;
|
||||||
|
} catch (e) {
|
||||||
|
// continue without accounts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* eslint-disable no-useless-escape */
|
/* eslint-disable no-useless-escape */
|
||||||
const jsconfig = `
|
const jsconfig = `
|
||||||
|
|
|
@ -0,0 +1,923 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Tailwind - The Utility-First CSS Framework
|
||||||
|
|
||||||
|
A project by Adam Wathan (@adamwathan), Jonathan Reinink (@reinink),
|
||||||
|
David Hemphill (@davidhemphill) and Steve Schoger (@steveschoger).
|
||||||
|
|
||||||
|
Welcome to the Tailwind config file. This is where you can customize
|
||||||
|
Tailwind specifically for your project. Don't be intimidated by the
|
||||||
|
length of this file. It's really just a big JavaScript object and
|
||||||
|
we've done our very best to explain each section.
|
||||||
|
|
||||||
|
View the full documentation at https://tailwindcss.com.
|
||||||
|
|
||||||
|
|
||||||
|
|-------------------------------------------------------------------------------
|
||||||
|
| The default config
|
||||||
|
|-------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This variable contains the default Tailwind config. You don't have
|
||||||
|
| to use it, but it can sometimes be helpful to have available. For
|
||||||
|
| example, you may choose to merge your custom configuration
|
||||||
|
| values with some of the Tailwind defaults.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// let defaultConfig = require('tailwindcss/defaultConfig')()
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-------------------------------------------------------------------------------
|
||||||
|
| Colors https://tailwindcss.com/docs/colors
|
||||||
|
|-------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you can specify the colors used in your project. To get you started,
|
||||||
|
| we've provided a generous palette of great looking colors that are perfect
|
||||||
|
| for prototyping, but don't hesitate to change them for your project. You
|
||||||
|
| own these colors, nothing will break if you change everything about them.
|
||||||
|
|
|
||||||
|
| We've used literal color names ("red", "blue", etc.) for the default
|
||||||
|
| palette, but if you'd rather use functional names like "primary" and
|
||||||
|
| "secondary", or even a numeric scale like "100" and "200", go for it.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
transparent: 'transparent',
|
||||||
|
|
||||||
|
black: '#22292f',
|
||||||
|
'grey-darkest': '#3d4852',
|
||||||
|
'grey-darker': '#606f7b',
|
||||||
|
'grey-dark': '#8795a1',
|
||||||
|
grey: '#b8c2cc',
|
||||||
|
'grey-light': '#dae1e7',
|
||||||
|
'grey-lighter': '#f1f5f8',
|
||||||
|
'grey-lightest': '#f8fafc',
|
||||||
|
white: '#ffffff',
|
||||||
|
|
||||||
|
'red-darkest': '#3b0d0c',
|
||||||
|
'red-darker': '#621b18',
|
||||||
|
'red-dark': '#cc1f1a',
|
||||||
|
red: '#e3342f',
|
||||||
|
'red-light': '#ef5753',
|
||||||
|
'red-lighter': '#f9acaa',
|
||||||
|
'red-lightest': '#fcebea',
|
||||||
|
|
||||||
|
'orange-darkest': '#462a16',
|
||||||
|
'orange-darker': '#613b1f',
|
||||||
|
'orange-dark': '#de751f',
|
||||||
|
orange: '#f6993f',
|
||||||
|
'orange-light': '#faad63',
|
||||||
|
'orange-lighter': '#fcd9b6',
|
||||||
|
'orange-lightest': '#fff5eb',
|
||||||
|
|
||||||
|
'yellow-darkest': '#453411',
|
||||||
|
'yellow-darker': '#684f1d',
|
||||||
|
'yellow-dark': '#f2d024',
|
||||||
|
yellow: '#ffed4a',
|
||||||
|
'yellow-light': '#fff382',
|
||||||
|
'yellow-lighter': '#fff9c2',
|
||||||
|
'yellow-lightest': '#fcfbeb',
|
||||||
|
|
||||||
|
'green-darkest': '#003706',
|
||||||
|
'green-darker': '#006504',
|
||||||
|
'green-dark': '#058b00',
|
||||||
|
green: '#12bc00',
|
||||||
|
'green-light': '#51d88a',
|
||||||
|
'green-lighter': '#a2f5bf',
|
||||||
|
'green-lightest': '#e3fcec',
|
||||||
|
|
||||||
|
'teal-darkest': '#0d3331',
|
||||||
|
'teal-darker': '#20504f',
|
||||||
|
'teal-dark': '#38a89d',
|
||||||
|
teal: '#4dc0b5',
|
||||||
|
'teal-light': '#64d5ca',
|
||||||
|
'teal-lighter': '#a0f0ed',
|
||||||
|
'teal-lightest': '#e8fffe',
|
||||||
|
|
||||||
|
'blue-darkest': '#002275',
|
||||||
|
'blue-darker': '#003eaa',
|
||||||
|
'blue-dark': '#0060df',
|
||||||
|
blue: '#0a84ff',
|
||||||
|
'blue-light': '#6cb2eb',
|
||||||
|
'blue-lighter': '#bcdefa',
|
||||||
|
'blue-lightest': '#eff8ff',
|
||||||
|
|
||||||
|
'indigo-darkest': '#191e38',
|
||||||
|
'indigo-darker': '#2f365f',
|
||||||
|
'indigo-dark': '#5661b3',
|
||||||
|
indigo: '#6574cd',
|
||||||
|
'indigo-light': '#7886d7',
|
||||||
|
'indigo-lighter': '#b2b7ff',
|
||||||
|
'indigo-lightest': '#e6e8ff',
|
||||||
|
|
||||||
|
'purple-darkest': '#21183c',
|
||||||
|
'purple-darker': '#382b5f',
|
||||||
|
'purple-dark': '#794acf',
|
||||||
|
purple: '#9561e2',
|
||||||
|
'purple-light': '#a779e9',
|
||||||
|
'purple-lighter': '#d6bbfc',
|
||||||
|
'purple-lightest': '#f3ebff',
|
||||||
|
|
||||||
|
'pink-darkest': '#451225',
|
||||||
|
'pink-darker': '#6f213f',
|
||||||
|
'pink-dark': '#eb5286',
|
||||||
|
pink: '#f66d9b',
|
||||||
|
'pink-light': '#fa7ea8',
|
||||||
|
'pink-lighter': '#ffbbca',
|
||||||
|
'pink-lightest': '#ffebef',
|
||||||
|
cloud: 'rgba(255, 255, 255, 0.8)'
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Colors https://tailwindcss.com/docs/colors
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The color palette defined above is also assigned to the "colors" key of
|
||||||
|
| your Tailwind config. This makes it easy to access them in your CSS
|
||||||
|
| using Tailwind's config helper. For example:
|
||||||
|
|
|
||||||
|
| .error { color: config('colors.red') }
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
colors: colors,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Screens https://tailwindcss.com/docs/responsive-design
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Screens in Tailwind are translated to CSS media queries. They define the
|
||||||
|
| responsive breakpoints for your project. By default Tailwind takes a
|
||||||
|
| "mobile first" approach, where each screen size represents a minimum
|
||||||
|
| viewport width. Feel free to have as few or as many screens as you
|
||||||
|
| want, naming them in whatever way you'd prefer for your project.
|
||||||
|
|
|
||||||
|
| Tailwind also allows for more complex screen definitions, which can be
|
||||||
|
| useful in certain situations. Be sure to see the full responsive
|
||||||
|
| documentation for a complete list of options.
|
||||||
|
|
|
||||||
|
| Class name: .{screen}:{utility}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
screens: {
|
||||||
|
sm: '576px',
|
||||||
|
md: '768px',
|
||||||
|
lg: '992px',
|
||||||
|
xl: '1200px'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Fonts https://tailwindcss.com/docs/fonts
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your project's font stack, or font families.
|
||||||
|
| Keep in mind that Tailwind doesn't actually load any fonts for you.
|
||||||
|
| If you're using custom fonts you'll need to import them prior to
|
||||||
|
| defining them here.
|
||||||
|
|
|
||||||
|
| By default we provide a native font stack that works remarkably well on
|
||||||
|
| any device or OS you're using, since it just uses the default fonts
|
||||||
|
| provided by the platform.
|
||||||
|
|
|
||||||
|
| Class name: .font-{name}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
fonts: {
|
||||||
|
sans: [
|
||||||
|
'system-ui',
|
||||||
|
'BlinkMacSystemFont',
|
||||||
|
'-apple-system',
|
||||||
|
'Segoe UI',
|
||||||
|
'Roboto',
|
||||||
|
'Oxygen',
|
||||||
|
'Ubuntu',
|
||||||
|
'Cantarell',
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
'sans-serif'
|
||||||
|
],
|
||||||
|
serif: [
|
||||||
|
'Constantia',
|
||||||
|
'Lucida Bright',
|
||||||
|
'Lucidabright',
|
||||||
|
'Lucida Serif',
|
||||||
|
'Lucida',
|
||||||
|
'DejaVu Serif',
|
||||||
|
'Bitstream Vera Serif',
|
||||||
|
'Liberation Serif',
|
||||||
|
'Georgia',
|
||||||
|
'serif'
|
||||||
|
],
|
||||||
|
mono: [
|
||||||
|
'Menlo',
|
||||||
|
'Monaco',
|
||||||
|
'Consolas',
|
||||||
|
'Liberation Mono',
|
||||||
|
'Courier New',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Text sizes https://tailwindcss.com/docs/text-sizing
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your text sizes. Name these in whatever way
|
||||||
|
| makes the most sense to you. We use size names by default, but
|
||||||
|
| you're welcome to use a numeric scale or even something else
|
||||||
|
| entirely.
|
||||||
|
|
|
||||||
|
| By default Tailwind uses the "rem" unit type for most measurements.
|
||||||
|
| This allows you to set a root font size which all other sizes are
|
||||||
|
| then based on. That said, you are free to use whatever units you
|
||||||
|
| prefer, be it rems, ems, pixels or other.
|
||||||
|
|
|
||||||
|
| Class name: .text-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
textSizes: {
|
||||||
|
xs: '.75rem', // 12px
|
||||||
|
sm: '.875rem', // 14px
|
||||||
|
base: '1rem', // 16px
|
||||||
|
lg: '1.125rem', // 18px
|
||||||
|
xl: '1.25rem', // 20px
|
||||||
|
'2xl': '1.5rem', // 24px
|
||||||
|
'3xl': '1.875rem', // 30px
|
||||||
|
'4xl': '2.25rem', // 36px
|
||||||
|
'5xl': '3rem' // 48px
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Font weights https://tailwindcss.com/docs/font-weight
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your font weights. We've provided a list of
|
||||||
|
| common font weight names with their respective numeric scale values
|
||||||
|
| to get you started. It's unlikely that your project will require
|
||||||
|
| all of these, so we recommend removing those you don't need.
|
||||||
|
|
|
||||||
|
| Class name: .font-{weight}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
fontWeights: {
|
||||||
|
hairline: 100,
|
||||||
|
thin: 200,
|
||||||
|
light: 300,
|
||||||
|
normal: 400,
|
||||||
|
medium: 500,
|
||||||
|
semibold: 600,
|
||||||
|
bold: 700,
|
||||||
|
extrabold: 800,
|
||||||
|
black: 900
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Leading (line height) https://tailwindcss.com/docs/line-height
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your line height values, or as we call
|
||||||
|
| them in Tailwind, leadings.
|
||||||
|
|
|
||||||
|
| Class name: .leading-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
leading: {
|
||||||
|
none: 1,
|
||||||
|
tight: 1.25,
|
||||||
|
normal: 1.5,
|
||||||
|
loose: 2
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Tracking (letter spacing) https://tailwindcss.com/docs/letter-spacing
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your letter spacing values, or as we call
|
||||||
|
| them in Tailwind, tracking.
|
||||||
|
|
|
||||||
|
| Class name: .tracking-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
tracking: {
|
||||||
|
tight: '-0.05em',
|
||||||
|
normal: '0',
|
||||||
|
wide: '0.05em'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Text colors https://tailwindcss.com/docs/text-color
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your text colors. By default these use the
|
||||||
|
| color palette we defined above, however you're welcome to set these
|
||||||
|
| independently if that makes sense for your project.
|
||||||
|
|
|
||||||
|
| Class name: .text-{color}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
textColors: colors,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Background colors https://tailwindcss.com/docs/background-color
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your background colors. By default these use
|
||||||
|
| the color palette we defined above, however you're welcome to set
|
||||||
|
| these independently if that makes sense for your project.
|
||||||
|
|
|
||||||
|
| Class name: .bg-{color}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
backgroundColors: colors,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Background sizes https://tailwindcss.com/docs/background-size
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your background sizes. We provide some common
|
||||||
|
| values that are useful in most projects, but feel free to add other sizes
|
||||||
|
| that are specific to your project here as well.
|
||||||
|
|
|
||||||
|
| Class name: .bg-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
backgroundSize: {
|
||||||
|
auto: 'auto',
|
||||||
|
cover: 'cover',
|
||||||
|
contain: 'contain'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Border widths https://tailwindcss.com/docs/border-width
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your border widths. Take note that border
|
||||||
|
| widths require a special "default" value set as well. This is the
|
||||||
|
| width that will be used when you do not specify a border width.
|
||||||
|
|
|
||||||
|
| Class name: .border{-side?}{-width?}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
borderWidths: {
|
||||||
|
default: '1px',
|
||||||
|
'0': '0',
|
||||||
|
'2': '2px',
|
||||||
|
'4': '4px',
|
||||||
|
'8': '8px'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Border colors https://tailwindcss.com/docs/border-color
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your border colors. By default these use the
|
||||||
|
| color palette we defined above, however you're welcome to set these
|
||||||
|
| independently if that makes sense for your project.
|
||||||
|
|
|
||||||
|
| Take note that border colors require a special "default" value set
|
||||||
|
| as well. This is the color that will be used when you do not
|
||||||
|
| specify a border color.
|
||||||
|
|
|
||||||
|
| Class name: .border-{color}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
borderColors: global.Object.assign({ default: colors['grey-light'] }, colors),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Border radius https://tailwindcss.com/docs/border-radius
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your border radius values. If a `default` radius
|
||||||
|
| is provided, it will be made available as the non-suffixed `.rounded`
|
||||||
|
| utility.
|
||||||
|
|
|
||||||
|
| If your scale includes a `0` value to reset already rounded corners, it's
|
||||||
|
| a good idea to put it first so other values are able to override it.
|
||||||
|
|
|
||||||
|
| Class name: .rounded{-side?}{-size?}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
borderRadius: {
|
||||||
|
none: '0',
|
||||||
|
sm: '.125rem',
|
||||||
|
default: '.25rem',
|
||||||
|
lg: '.5rem',
|
||||||
|
full: '9999px'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Width https://tailwindcss.com/docs/width
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your width utility sizes. These can be
|
||||||
|
| percentage based, pixels, rems, or any other units. By default
|
||||||
|
| we provide a sensible rem based numeric scale, a percentage
|
||||||
|
| based fraction scale, plus some other common use-cases. You
|
||||||
|
| can, of course, modify these values as needed.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
| It's also worth mentioning that Tailwind automatically escapes
|
||||||
|
| invalid CSS class name characters, which allows you to have
|
||||||
|
| awesome classes like .w-2/3.
|
||||||
|
|
|
||||||
|
| Class name: .w-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
width: {
|
||||||
|
auto: 'auto',
|
||||||
|
px: '1px',
|
||||||
|
'1': '0.25rem',
|
||||||
|
'2': '0.5rem',
|
||||||
|
'3': '0.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'16': '4rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '8rem',
|
||||||
|
'48': '12rem',
|
||||||
|
'64': '16rem',
|
||||||
|
'1/2': '50%',
|
||||||
|
'1/3': '33.33333%',
|
||||||
|
'2/3': '66.66667%',
|
||||||
|
'1/4': '25%',
|
||||||
|
'3/4': '75%',
|
||||||
|
'1/5': '20%',
|
||||||
|
'2/5': '40%',
|
||||||
|
'3/5': '60%',
|
||||||
|
'4/5': '80%',
|
||||||
|
'1/6': '16.66667%',
|
||||||
|
'5/6': '83.33333%',
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vw'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Height https://tailwindcss.com/docs/height
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your height utility sizes. These can be
|
||||||
|
| percentage based, pixels, rems, or any other units. By default
|
||||||
|
| we provide a sensible rem based numeric scale plus some other
|
||||||
|
| common use-cases. You can, of course, modify these values as
|
||||||
|
| needed.
|
||||||
|
|
|
||||||
|
| Class name: .h-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
height: {
|
||||||
|
auto: 'auto',
|
||||||
|
px: '1px',
|
||||||
|
'1': '0.25rem',
|
||||||
|
'2': '0.5rem',
|
||||||
|
'3': '0.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'16': '4rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '8rem',
|
||||||
|
'48': '12rem',
|
||||||
|
'64': '16rem',
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vh'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Minimum width https://tailwindcss.com/docs/min-width
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your minimum width utility sizes. These can
|
||||||
|
| be percentage based, pixels, rems, or any other units. We provide a
|
||||||
|
| couple common use-cases by default. You can, of course, modify
|
||||||
|
| these values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .min-w-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
minWidth: {
|
||||||
|
'0': '0',
|
||||||
|
full: '100%'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Minimum height https://tailwindcss.com/docs/min-height
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your minimum height utility sizes. These can
|
||||||
|
| be percentage based, pixels, rems, or any other units. We provide a
|
||||||
|
| few common use-cases by default. You can, of course, modify these
|
||||||
|
| values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .min-h-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
minHeight: {
|
||||||
|
'0': '0',
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vh'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Maximum width https://tailwindcss.com/docs/max-width
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your maximum width utility sizes. These can
|
||||||
|
| be percentage based, pixels, rems, or any other units. By default
|
||||||
|
| we provide a sensible rem based scale and a "full width" size,
|
||||||
|
| which is basically a reset utility. You can, of course,
|
||||||
|
| modify these values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .max-w-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
maxWidth: {
|
||||||
|
xs: '20rem',
|
||||||
|
sm: '30rem',
|
||||||
|
md: '40rem',
|
||||||
|
lg: '50rem',
|
||||||
|
xl: '60rem',
|
||||||
|
'2xl': '70rem',
|
||||||
|
'3xl': '80rem',
|
||||||
|
'4xl': '90rem',
|
||||||
|
'5xl': '100rem',
|
||||||
|
full: '100%'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Maximum height https://tailwindcss.com/docs/max-height
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your maximum height utility sizes. These can
|
||||||
|
| be percentage based, pixels, rems, or any other units. We provide a
|
||||||
|
| couple common use-cases by default. You can, of course, modify
|
||||||
|
| these values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .max-h-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
maxHeight: {
|
||||||
|
full: '100%',
|
||||||
|
'half-screen': '50vh',
|
||||||
|
screen: '100vh'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Padding https://tailwindcss.com/docs/padding
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your padding utility sizes. These can be
|
||||||
|
| percentage based, pixels, rems, or any other units. By default we
|
||||||
|
| provide a sensible rem based numeric scale plus a couple other
|
||||||
|
| common use-cases like "1px". You can, of course, modify these
|
||||||
|
| values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .p{side?}-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
padding: {
|
||||||
|
px: '1px',
|
||||||
|
'0': '0',
|
||||||
|
'1': '0.25rem',
|
||||||
|
'2': '0.5rem',
|
||||||
|
'3': '0.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'16': '4rem',
|
||||||
|
'20': '5rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '8rem'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Margin https://tailwindcss.com/docs/margin
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your margin utility sizes. These can be
|
||||||
|
| percentage based, pixels, rems, or any other units. By default we
|
||||||
|
| provide a sensible rem based numeric scale plus a couple other
|
||||||
|
| common use-cases like "1px". You can, of course, modify these
|
||||||
|
| values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .m{side?}-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
margin: {
|
||||||
|
auto: 'auto',
|
||||||
|
px: '1px',
|
||||||
|
'0': '0',
|
||||||
|
'1': '0.25rem',
|
||||||
|
'2': '0.5rem',
|
||||||
|
'3': '0.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'16': '4rem',
|
||||||
|
'20': '5rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '8rem'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Negative margin https://tailwindcss.com/docs/negative-margin
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your negative margin utility sizes. These can
|
||||||
|
| be percentage based, pixels, rems, or any other units. By default we
|
||||||
|
| provide matching values to the padding scale since these utilities
|
||||||
|
| generally get used together. You can, of course, modify these
|
||||||
|
| values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .-m{side?}-{size}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
negativeMargin: {
|
||||||
|
px: '1px',
|
||||||
|
'0': '0',
|
||||||
|
'1': '0.25rem',
|
||||||
|
'2': '0.5rem',
|
||||||
|
'3': '0.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'16': '4rem',
|
||||||
|
'20': '5rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '8rem'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Shadows https://tailwindcss.com/docs/shadows
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your shadow utilities. As you can see from
|
||||||
|
| the defaults we provide, it's possible to apply multiple shadows
|
||||||
|
| per utility using comma separation.
|
||||||
|
|
|
||||||
|
| If a `default` shadow is provided, it will be made available as the non-
|
||||||
|
| suffixed `.shadow` utility.
|
||||||
|
|
|
||||||
|
| Class name: .shadow-{size?}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
shadows: {
|
||||||
|
default: '0 2px 4px 0 rgba(0,0,0,0.10)',
|
||||||
|
md: '0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08)',
|
||||||
|
lg: '0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)',
|
||||||
|
inner: 'inset 0 2px 4px 0 rgba(0,0,0,0.06)',
|
||||||
|
outline: '0 0 0 3px rgba(52,144,220,0.5)',
|
||||||
|
none: 'none',
|
||||||
|
cloud: '0 0 5rem 5rem white'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Z-index https://tailwindcss.com/docs/z-index
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your z-index utility values. By default we
|
||||||
|
| provide a sensible numeric scale. You can, of course, modify these
|
||||||
|
| values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .z-{index}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
zIndex: {
|
||||||
|
auto: 'auto',
|
||||||
|
'0': 0,
|
||||||
|
'10': 10,
|
||||||
|
'20': 20,
|
||||||
|
'30': 30,
|
||||||
|
'40': 40,
|
||||||
|
'50': 50
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Opacity https://tailwindcss.com/docs/opacity
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your opacity utility values. By default we
|
||||||
|
| provide a sensible numeric scale. You can, of course, modify these
|
||||||
|
| values as needed.
|
||||||
|
|
|
||||||
|
| Class name: .opacity-{name}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
opacity: {
|
||||||
|
'0': '0',
|
||||||
|
'25': '.25',
|
||||||
|
'50': '.5',
|
||||||
|
'75': '.75',
|
||||||
|
'100': '1'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| SVG fill https://tailwindcss.com/docs/svg
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your SVG fill colors. By default we just provide
|
||||||
|
| `fill-current` which sets the fill to the current text color. This lets you
|
||||||
|
| specify a fill color using existing text color utilities and helps keep the
|
||||||
|
| generated CSS file size down.
|
||||||
|
|
|
||||||
|
| Class name: .fill-{name}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
svgFill: {
|
||||||
|
current: 'currentColor'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| SVG stroke https://tailwindcss.com/docs/svg
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you define your SVG stroke colors. By default we just provide
|
||||||
|
| `stroke-current` which sets the stroke to the current text color. This lets
|
||||||
|
| you specify a stroke color using existing text color utilities and helps
|
||||||
|
| keep the generated CSS file size down.
|
||||||
|
|
|
||||||
|
| Class name: .stroke-{name}
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
svgStroke: {
|
||||||
|
current: 'currentColor'
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Modules https://tailwindcss.com/docs/configuration#modules
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you control which modules are generated and what variants are
|
||||||
|
| generated for each of those modules.
|
||||||
|
|
|
||||||
|
| Currently supported variants:
|
||||||
|
| - responsive
|
||||||
|
| - hover
|
||||||
|
| - focus
|
||||||
|
| - active
|
||||||
|
| - group-hover
|
||||||
|
|
|
||||||
|
| To disable a module completely, use `false` instead of an array.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
modules: {
|
||||||
|
appearance: ['responsive'],
|
||||||
|
backgroundAttachment: ['responsive'],
|
||||||
|
backgroundColors: ['responsive', 'hover', 'focus'],
|
||||||
|
backgroundPosition: ['responsive'],
|
||||||
|
backgroundRepeat: ['responsive'],
|
||||||
|
backgroundSize: ['responsive'],
|
||||||
|
borderCollapse: [],
|
||||||
|
borderColors: ['responsive', 'hover', 'focus'],
|
||||||
|
borderRadius: ['responsive'],
|
||||||
|
borderStyle: ['responsive'],
|
||||||
|
borderWidths: ['responsive'],
|
||||||
|
cursor: ['responsive'],
|
||||||
|
display: ['responsive'],
|
||||||
|
flexbox: ['responsive'],
|
||||||
|
float: ['responsive'],
|
||||||
|
fonts: ['responsive'],
|
||||||
|
fontWeights: ['responsive', 'hover', 'focus'],
|
||||||
|
height: ['responsive'],
|
||||||
|
leading: ['responsive'],
|
||||||
|
lists: ['responsive'],
|
||||||
|
margin: ['responsive'],
|
||||||
|
maxHeight: ['responsive'],
|
||||||
|
maxWidth: ['responsive'],
|
||||||
|
minHeight: ['responsive'],
|
||||||
|
minWidth: ['responsive'],
|
||||||
|
negativeMargin: ['responsive'],
|
||||||
|
opacity: ['responsive'],
|
||||||
|
outline: ['focus'],
|
||||||
|
overflow: ['responsive'],
|
||||||
|
padding: ['responsive'],
|
||||||
|
pointerEvents: ['responsive'],
|
||||||
|
position: ['responsive'],
|
||||||
|
resize: ['responsive'],
|
||||||
|
shadows: ['responsive', 'hover', 'focus'],
|
||||||
|
svgFill: [],
|
||||||
|
svgStroke: [],
|
||||||
|
tableLayout: ['responsive'],
|
||||||
|
textAlign: ['responsive'],
|
||||||
|
textColors: ['responsive', 'hover', 'focus'],
|
||||||
|
textSizes: ['responsive'],
|
||||||
|
textStyle: ['responsive', 'hover', 'focus'],
|
||||||
|
tracking: ['responsive'],
|
||||||
|
userSelect: ['responsive'],
|
||||||
|
verticalAlign: ['responsive'],
|
||||||
|
visibility: ['responsive'],
|
||||||
|
whitespace: ['responsive'],
|
||||||
|
width: ['responsive'],
|
||||||
|
zIndex: ['responsive']
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Plugins https://tailwindcss.com/docs/plugins
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you can register any plugins you'd like to use in your
|
||||||
|
| project. Tailwind's built-in `container` plugin is enabled by default to
|
||||||
|
| give you a Bootstrap-style responsive container component out of the box.
|
||||||
|
|
|
||||||
|
| Be sure to view the complete plugin documentation to learn more about how
|
||||||
|
| the plugin system works.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
require('tailwindcss/plugins/container')({
|
||||||
|
// center: true,
|
||||||
|
// padding: '1rem',
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
| Advanced Options https://tailwindcss.com/docs/configuration#options
|
||||||
|
|-----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you can tweak advanced configuration options. We recommend
|
||||||
|
| leaving these options alone unless you absolutely need to change them.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
options: {
|
||||||
|
prefix: '',
|
||||||
|
important: false,
|
||||||
|
separator: ':'
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,13 +1,13 @@
|
||||||
/* global browser document */
|
/* global browser */
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const DownloadPage = require('./pages/desktop/download_page');
|
const DownloadPage = require('./pages/desktop/download_page');
|
||||||
const HomePage = require('./pages/desktop/home_page');
|
const HomePage = require('./pages/desktop/home_page');
|
||||||
const SharePage = require('./pages/desktop/share_page');
|
|
||||||
|
|
||||||
describe('Firefox Send', function() {
|
describe('Firefox Send', function() {
|
||||||
|
const homePage = new HomePage();
|
||||||
const downloadDir =
|
const downloadDir =
|
||||||
browser.desiredCapabilities['moz:firefoxOptions']['prefs'][
|
browser.desiredCapabilities['moz:firefoxOptions']['prefs'][
|
||||||
'browser.download.dir'
|
'browser.download.dir'
|
||||||
|
@ -16,35 +16,21 @@ describe('Firefox Send', function() {
|
||||||
const testFiles = fs.readdirSync(testFilesPath);
|
const testFiles = fs.readdirSync(testFilesPath);
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
browser.url('/');
|
homePage.open();
|
||||||
browser.execute(() => {
|
|
||||||
document.getElementById('file-upload').style.display = 'block';
|
|
||||||
});
|
|
||||||
browser.waitForExist('#file-upload');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testFiles.forEach(file => {
|
testFiles.forEach(file => {
|
||||||
it(`should upload and download files, file: ${file}`, function() {
|
it(`should upload and download files, file: ${file}`, function() {
|
||||||
browser.execute(() => {
|
browser.chooseFile(homePage.uploadInput, `${testFilesPath}/${file}`);
|
||||||
document.getElementById('file-upload').style.display = 'block';
|
browser.waitForExist(homePage.uploadButton);
|
||||||
});
|
browser.click(homePage.uploadButton);
|
||||||
browser.waitForExist('#file-upload');
|
browser.waitForExist(homePage.shareUrl);
|
||||||
const homePage = new HomePage();
|
const downloadPage = new DownloadPage(
|
||||||
browser.chooseFile('#file-upload', `${testFilesPath}/${file}`);
|
browser.getValue(homePage.shareUrl)
|
||||||
browser.click(homePage.readyToSend);
|
);
|
||||||
const sharePage = new SharePage();
|
downloadPage.open();
|
||||||
browser.waitForExist(sharePage.fileUrl);
|
downloadPage.download();
|
||||||
browser.url(browser.getValue(sharePage.fileUrl));
|
browser.waitForExist(downloadPage.downloadComplete);
|
||||||
const downloadPage = new DownloadPage();
|
|
||||||
downloadPage.waitForPageToLoad();
|
|
||||||
downloadPage.downloadBtn();
|
|
||||||
// Wait for download to complete
|
|
||||||
browser.waitUntil(() => {
|
|
||||||
browser.waitForExist(downloadPage.downloadComplete);
|
|
||||||
return (
|
|
||||||
browser.getText(downloadPage.downloadComplete) === 'DOWNLOAD COMPLETE'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
assert.ok(fs.existsSync(path.join(downloadDir, file)));
|
assert.ok(fs.existsSync(path.join(downloadDir, file)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,45 +3,31 @@ const assert = require('assert');
|
||||||
const HomePage = require('./pages/desktop/home_page');
|
const HomePage = require('./pages/desktop/home_page');
|
||||||
|
|
||||||
describe('Firefox Send homepage', function() {
|
describe('Firefox Send homepage', function() {
|
||||||
|
const homePage = new HomePage();
|
||||||
const baseUrl = browser.options['baseUrl'];
|
const baseUrl = browser.options['baseUrl'];
|
||||||
const legalLinks = [
|
const footerLinks = [
|
||||||
|
'mozilla',
|
||||||
'legal',
|
'legal',
|
||||||
'about',
|
'about',
|
||||||
'legal',
|
'legal',
|
||||||
'cookies',
|
'cookies',
|
||||||
'report-infringement'
|
'report-infringement',
|
||||||
|
'github',
|
||||||
|
'twitter'
|
||||||
];
|
];
|
||||||
const socialLinks = ['github', 'twitter', 'mozilla'];
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
browser.url('/');
|
homePage.open();
|
||||||
browser.pause(500);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have the right title', function() {
|
it('should have the right title', function() {
|
||||||
assert.equal(browser.getTitle(), 'Firefox Send');
|
assert.equal(browser.getTitle(), 'Firefox Send');
|
||||||
});
|
});
|
||||||
|
|
||||||
legalLinks.forEach((link, i) => {
|
footerLinks.forEach((link, i) => {
|
||||||
it(`should navigate to the correct legal pages, page: ${link}`, function() {
|
it(`should navigate to the correct page: ${link}`, function() {
|
||||||
const homePage = new HomePage();
|
|
||||||
// Click links on bottom of page
|
// Click links on bottom of page
|
||||||
const els = browser.elements(homePage.legalLinks);
|
const els = browser.elements(homePage.footerLinks);
|
||||||
browser.elementIdClick(els.value[i].ELEMENT);
|
|
||||||
// Wait for page to load
|
|
||||||
browser.waitUntil(() => {
|
|
||||||
const url = browser.getUrl();
|
|
||||||
return url !== baseUrl;
|
|
||||||
});
|
|
||||||
assert.ok(browser.getUrl().includes(link));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
socialLinks.forEach((link, i) => {
|
|
||||||
it(`should navigate to the correct social pages, page: ${link}`, function() {
|
|
||||||
const homePage = new HomePage();
|
|
||||||
// Click links on bottom of page
|
|
||||||
const els = browser.elements(homePage.socialLinks);
|
|
||||||
browser.elementIdClick(els.value[i].ELEMENT);
|
browser.elementIdClick(els.value[i].ELEMENT);
|
||||||
// Wait for page to load
|
// Wait for page to load
|
||||||
browser.waitUntil(() => {
|
browser.waitUntil(() => {
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
const Page = require('./page');
|
const Page = require('./page');
|
||||||
|
|
||||||
class DownloadPage extends Page {
|
class DownloadPage extends Page {
|
||||||
constructor() {
|
constructor(path) {
|
||||||
super();
|
super(path);
|
||||||
this.downloadBtnLocator = '.btn--download';
|
this.downloadButton = '#download-btn';
|
||||||
this.downloadCompletedLocator = '.btn--complete';
|
this.downloadComplete = '#download-complete';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,21 +14,12 @@ class DownloadPage extends Page {
|
||||||
* @throws ElementNotFound
|
* @throws ElementNotFound
|
||||||
*/
|
*/
|
||||||
waitForPageToLoad() {
|
waitForPageToLoad() {
|
||||||
browser.waitUntil(() => {
|
browser.waitForExist(this.downloadButton);
|
||||||
browser.waitForExist(this.downloadBtnLocator);
|
|
||||||
const el = browser.element(this.downloadBtnLocator);
|
|
||||||
return browser.elementIdDisplayed(el.value.ELEMENT);
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadBtn() {
|
download() {
|
||||||
this.waitForPageToLoad();
|
return browser.click(this.downloadButton);
|
||||||
return browser.click(this.downloadBtnLocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
get downloadComplete() {
|
|
||||||
return this.downloadCompletedLocator;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = DownloadPage;
|
module.exports = DownloadPage;
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
|
/* global browser document */
|
||||||
const Page = require('./page');
|
const Page = require('./page');
|
||||||
|
|
||||||
class HomePage extends Page {
|
class HomePage extends Page {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super('/');
|
||||||
this.legalSectionLinks = '.legalSection .legalSection__link';
|
this.footerLinks = 'footer a';
|
||||||
this.readyToSendLocator = 'div#page-one button.btn';
|
this.uploadInput = '#file-upload';
|
||||||
this.socialLinksLocator = '.socialSection__link';
|
this.uploadButton = '#upload-btn';
|
||||||
|
this.progress = 'progress';
|
||||||
|
this.shareUrl = '#share-url';
|
||||||
}
|
}
|
||||||
|
|
||||||
get legalLinks() {
|
waitForPageToLoad() {
|
||||||
return this.legalSectionLinks;
|
browser.waitForExist(this.uploadInput);
|
||||||
|
this.showUploadInput();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
get readyToSend() {
|
showUploadInput() {
|
||||||
return this.readyToSendLocator;
|
browser.execute(() => {
|
||||||
}
|
document.getElementById('file-upload').style.display = 'block';
|
||||||
|
});
|
||||||
get socialLinks() {
|
|
||||||
return this.socialLinksLocator;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = HomePage;
|
module.exports = HomePage;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
/* global browser */
|
/* global browser */
|
||||||
class Page {
|
class Page {
|
||||||
constructor() {}
|
constructor(path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
open(path) {
|
open() {
|
||||||
browser.url(path);
|
browser.url(this.path);
|
||||||
this.waitForPageToLoad();
|
this.waitForPageToLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +14,8 @@ class Page {
|
||||||
* @returns {Object} An object representing the page.
|
* @returns {Object} An object representing the page.
|
||||||
* @throws ElementNotFound
|
* @throws ElementNotFound
|
||||||
*/
|
*/
|
||||||
waitForPageToLoad() {}
|
waitForPageToLoad() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = Page;
|
module.exports = Page;
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
/* global browser */
|
|
||||||
const Page = require('./page');
|
|
||||||
const SharePage = require('./share_page');
|
|
||||||
|
|
||||||
class ProgressPage extends Page {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.cancelBtnLocator = '.uploadCancel';
|
|
||||||
this.progressIconLocator = '.btn--stripes';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @function waitForPageToLoad
|
|
||||||
* @returns {Object} An object representing the Share page.
|
|
||||||
* @throws ElementNotFound
|
|
||||||
*/
|
|
||||||
waitForPageToLoad() {
|
|
||||||
browser.waitUntil(() => {
|
|
||||||
browser.waitForExist(this.progressIconLocator);
|
|
||||||
const el = browser.element(this.progressIconLocator);
|
|
||||||
return browser.elementIdDisplayed(el.value.ELEMENT);
|
|
||||||
});
|
|
||||||
const sharePage = new SharePage();
|
|
||||||
return sharePage.waitForPageToLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
get cancelBtn() {
|
|
||||||
return this.cancelBtnLocator;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = ProgressPage;
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue