diff --git a/.stylelintrc b/.stylelintrc index 9ade6144..0af67b03 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -10,3 +10,4 @@ rules: declaration-colon-newline-after: null selector-list-comma-newline-after: null value-list-comma-newline-after: null + at-rule-no-unknown: null diff --git a/app/archive.js b/app/archive.js index 6cdce5f4..dea25cd1 100644 --- a/app/archive.js +++ b/app/archive.js @@ -62,7 +62,10 @@ export default class Archive { return true; } - remove(index) { - this.files.splice(index, 1); + remove(file) { + const index = this.files.indexOf(file); + if (index > -1) { + this.files.splice(index, 1); + } } } diff --git a/app/base.css b/app/base.css deleted file mode 100644 index 27707143..00000000 --- a/app/base.css +++ /dev/null @@ -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; - } -} diff --git a/app/dragManager.js b/app/dragManager.js index 3002e9c1..f5133a0a 100644 --- a/app/dragManager.js +++ b/app/dragManager.js @@ -3,20 +3,14 @@ export default function(state, emitter) { document.body.addEventListener('dragover', event => { if (state.route === '/') { event.preventDefault(); - const files = document.querySelector('.uploadedFilesWrapper'); - files.classList.add('uploadArea--noEvents'); } }); document.body.addEventListener('drop', event => { if (state.route === '/' && !state.uploading) { event.preventDefault(); - document - .querySelector('.uploadArea') - .classList.remove('uploadArea--dragging'); - - const files = Array.from(event.dataTransfer.files); - - emitter.emit('addFiles', { files }); + emitter.emit('addFiles', { + files: Array.from(event.dataTransfer.files) + }); } }); }); diff --git a/app/fileManager.js b/app/fileManager.js index bd45b252..b43a91ca 100644 --- a/app/fileManager.js +++ b/app/fileManager.js @@ -5,7 +5,8 @@ import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils'; import * as metrics from './metrics'; import Archive from './archive'; import { bytes } from './utils'; -import okDialog from './templates/okDialog'; +import okDialog from './ui/okDialog'; +import copyDialog from './ui/copyDialog'; export default function(state, emitter) { let lastRender = 0; @@ -66,8 +67,11 @@ export default function(state, emitter) { metrics.changedDownloadLimit(file); }); - emitter.on('removeUpload', async ({ index }) => { - state.archive.remove(index); + emitter.on('removeUpload', file => { + state.archive.remove(file); + if (state.archive.numFiles === 0) { + state.archive = null; + } render(); }); @@ -86,6 +90,7 @@ export default function(state, emitter) { } catch (e) { state.raven.captureException(e); } + render(); }); emitter.on('cancel', () => { @@ -104,6 +109,9 @@ export default function(state, emitter) { count: LIMITS.MAX_FILES_PER_ARCHIVE }) ); + if (state.archive.numFiles === 0) { + state.archive = null; + } } render(); }); @@ -149,15 +157,7 @@ export default function(state, emitter) { if (password) { emitter.emit('password', { password, file: ownedFile }); } - - const cancelBtn = document.getElementById('cancel-upload'); - if (cancelBtn) { - cancelBtn.hidden = 'hidden'; - } - if (document.querySelector('.page')) { - await delay(1000); - } - emitter.emit('pushState', `/share/${ownedFile.id}`); + state.modal = copyDialog(ownedFile.name, ownedFile.url); } catch (err) { if (err.message === '0') { //cancelled. do nothing @@ -176,6 +176,7 @@ export default function(state, emitter) { state.password = ''; state.uploading = false; state.transfer = null; + render(); } }); @@ -232,11 +233,7 @@ export default function(state, emitter) { await dl; const time = Date.now() - start; const speed = size / (time / 1000); - if (document.querySelector('.page')) { - await delay(1000); - } state.storage.totalDownloads += 1; - state.transfer.reset(); metrics.completedDownload({ size, time, speed }); } catch (err) { if (err.message === '0') { diff --git a/app/main.css b/app/main.css index 3a98d958..95ca22ad 100644 --- a/app/main.css +++ b/app/main.css @@ -1,25 +1,158 @@ -@import './base.css'; -@import './pages/share/share.css'; -@import './pages/signin/signin.css'; -@import './pages/unsupported/unsupported.css'; -@import './pages/welcome/welcome.css'; -@import './templates/downloadButton/downloadButton.css'; -@import './templates/downloadPassword/downloadPassword.css'; -@import './templates/file/file.css'; -@import './templates/fileIcon/fileIcon.css'; -@import './templates/fileList/fileList.css'; -@import './templates/footer/footer.css'; -@import './templates/fxPromo/fxPromo.css'; -@import './templates/header/header.css'; -@import './templates/modal/modal.css'; -@import './templates/okDialog/okDialog.css'; -@import './templates/passwordInput/passwordInput.css'; -@import './templates/popup/popup.css'; -@import './templates/selectbox/selectbox.css'; -@import './templates/setPasswordSection/setPasswordSection.css'; -@import './templates/signupDialog/signupDialog.css'; -@import './templates/signupPromo/signupPromo.css'; -@import './templates/title/title.css'; -@import './templates/uploadedFile/uploadedFile.css'; -@import './templates/uploadedFileList/uploadedFileList.css'; -@import './templates/userAccount/userAccount.css'; +@tailwind preflight; +@tailwind components; +@tailwind utilities; + +a { + color: inherit; + text-decoration: none; +} + +progress { + @apply bg-grey-light; + @apply rounded-sm; + @apply w-full; + @apply h-1; +} + +progress::-moz-progress-bar { + @apply bg-blue; + @apply rounded-sm; +} + +progress::-webkit-progress-bar { + @apply bg-grey-light; + @apply rounded-sm; + @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; + } +} diff --git a/app/main.js b/app/main.js index 8febde65..d16c4f07 100644 --- a/app/main.js +++ b/app/main.js @@ -34,6 +34,7 @@ import User from './user'; window.appState = state; window.appEmit = emitter.emit.bind(emitter); let unsupportedReason = null; + if ( // Firefox < 50 /firefox/i.test(navigator.userAgent) && diff --git a/app/pages/error/index.js b/app/pages/error/index.js deleted file mode 100644 index 5b6167f5..00000000 --- a/app/pages/error/index.js +++ /dev/null @@ -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` -
- - ${title(state)} - -
${state.translate('errorPageHeader')}
- - -
- ${state.translate('uploadPageExplainer')} -
- - ${state.translate('sendYourFilesLink')} - - -
`; -}; diff --git a/app/pages/legal.js b/app/pages/legal.js deleted file mode 100644 index f18a926a..00000000 --- a/app/pages/legal.js +++ /dev/null @@ -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` -
- - - - ${title(state)} -
${state.translate('legalHeader')}
- ${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/' - ]) - )} -
- `; -}; - -function replaceLinks(str, urls) { - let i = 0; - const s = str.replace( - /([^<]+)<\/a>/g, - (m, v) => `${v}` - ); - return `
${s}
`; -} diff --git a/app/pages/notFound/index.js b/app/pages/notFound/index.js deleted file mode 100644 index 46ab6753..00000000 --- a/app/pages/notFound/index.js +++ /dev/null @@ -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` -
- - ${title(state)} - -
${state.translate('expiredPageHeader')}
- -
- ${state.translate('uploadPageExplainer')} -
- - ${state.translate('sendYourFilesLink')} - -
`; -}; diff --git a/app/pages/password/index.js b/app/pages/password/index.js deleted file mode 100644 index e4c06949..00000000 --- a/app/pages/password/index.js +++ /dev/null @@ -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` -
- ${titleSection(state)} - -
${state.translate('downloadMessage2')}
- ${downloadPassword(state, emit)} - - - ${state.translate('sendYourFilesLink')} - - -
- `; -}; diff --git a/app/pages/preview/index.js b/app/pages/preview/index.js deleted file mode 100644 index 076d26c8..00000000 --- a/app/pages/preview/index.js +++ /dev/null @@ -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` - - ${state.translate('sendYourFilesLink')} - `; - const cancelButton = html` - - `; - - const bottomLink = - state.transfer.state === 'downloading' ? cancelButton : trySendLink; - - return html` -
- ${titleSection(state)} - - ${downloadedFiles(fileInfo, state, emit)} -
${state.translate('downloadMessage2')}
- ${downloadButton(state, emit)} - - ${bottomLink} - -
- `; - - function cancel() { - if (state.transfer.state === 'downloading') { - emit('cancel'); - } - } -}; diff --git a/app/pages/share/index.js b/app/pages/share/index.js deleted file mode 100644 index 153c3a98..00000000 --- a/app/pages/share/index.js +++ /dev/null @@ -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` -
- - - - ${expireInfo(file, state.translate)} - - ${uploadedFileList(file, state, emit)} - -
- ${state.translate('copyUrlLabel')} -
(don't forget the password too)
-
- - - - - -
- ${deletePopup( - state.translate('deletePopupText'), - state.translate('deletePopupYes'), - state.translate('deletePopupCancel'), - deleteFile - )} -
- - - -
- - `; - - 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``, - 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`
- ${raw( - translate('expireInfo', { - downloadCount: translate('downloadCount', { num: file.dlimit }), - timespan: timeLimitText(translate, file.timeLimit) - }) - )} -
`; - - return el; -} diff --git a/app/pages/share/share.css b/app/pages/share/share.css deleted file mode 100644 index 2ad3f468..00000000 --- a/app/pages/share/share.css +++ /dev/null @@ -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; -} diff --git a/app/pages/signin/index.js b/app/pages/signin/index.js deleted file mode 100644 index 75588071..00000000 --- a/app/pages/signin/index.js +++ /dev/null @@ -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` -
- - - - ${title(state)} -
- ${state.translate('accountBenefitTitle')} - -
-
- - - ${state.translate('signInContinueMessage')} -
- - -
-
- -
- `; - - 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 = ''; - } -}; diff --git a/app/pages/signin/signin.css b/app/pages/signin/signin.css deleted file mode 100644 index 85745267..00000000 --- a/app/pages/signin/signin.css +++ /dev/null @@ -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); -} diff --git a/app/pages/unsupported/index.js b/app/pages/unsupported/index.js deleted file mode 100644 index 61c724f5..00000000 --- a/app/pages/unsupported/index.js +++ /dev/null @@ -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` -
- - ${state.translate('notSupportedLink')} - -
`; - url = - 'https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com'; - buttonAction = html` -
- Firefox
${strings.button} -
`; - } else { - strings = outdatedStrings(state); - url = 'https://support.mozilla.org/kb/update-firefox-latest-version'; - buttonAction = html` -
- ${strings.button} -
`; - } - - return html` -
- ${title(state)} -
${strings.header}
-
- ${strings.description} - ${why} -
- -
- - - ${buttonAction} - -
- -
- ${strings.explainer} -
-
`; -}; - -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') - }; -} diff --git a/app/pages/unsupported/unsupported.css b/app/pages/unsupported/unsupported.css deleted file mode 100644 index b34adcae..00000000 --- a/app/pages/unsupported/unsupported.css +++ /dev/null @@ -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; -} diff --git a/app/pages/welcome/index.js b/app/pages/welcome/index.js deleted file mode 100644 index ffe60ba1..00000000 --- a/app/pages/welcome/index.js +++ /dev/null @@ -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` -
- ${title(state)} - - - -
- ${expireInfo(state, emit)} - ${setPasswordSection(state)} -
- - - - - - - -
- `; - - 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 - }); - } - } -}; diff --git a/app/pages/welcome/welcome.css b/app/pages/welcome/welcome.css deleted file mode 100644 index defa299b..00000000 --- a/app/pages/welcome/welcome.css +++ /dev/null @@ -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; -} diff --git a/app/pasteManager.js b/app/pasteManager.js index 33b180b4..cdde7a81 100644 --- a/app/pasteManager.js +++ b/app/pasteManager.js @@ -7,6 +7,7 @@ function getString(item) { export default function(state, emitter) { window.addEventListener('paste', async event => { if (state.route !== '/' || state.uploading) return; + if (event.target.type === 'password') return; const items = Array.from(event.clipboardData.items); const transferFiles = items.filter(item => item.kind === 'file'); diff --git a/app/routes.js b/app/routes.js new file mode 100644 index 00000000..b2619599 --- /dev/null +++ b/app/routes.js @@ -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` + ${banner(state, emit)} + ${header(state, emit)} + ${main(state, emit)} + ${footer(state)} + `; + 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; +}; diff --git a/app/routes/download.js b/app/routes/download.js deleted file mode 100644 index 722a6b45..00000000 --- a/app/routes/download.js +++ /dev/null @@ -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); - } -}; diff --git a/app/routes/index.js b/app/routes/index.js deleted file mode 100644 index 180f8ca3..00000000 --- a/app/routes/index.js +++ /dev/null @@ -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` - ${modalDialog(state, emit)} - ${banner(state, emit)} -
- - ${signupPromo(state)} -
-
- - ${profile(state, emit)} - - ${template(state, emit)} -
-
- -
-
- ${fileList(state)} -
-
- ${footer(state)} - `; - 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; -}; diff --git a/app/templates/downloadButton/downloadButton.css b/app/templates/downloadButton/downloadButton.css deleted file mode 100644 index 821bcf18..00000000 --- a/app/templates/downloadButton/downloadButton.css +++ /dev/null @@ -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; -} diff --git a/app/templates/downloadButton/index.js b/app/templates/downloadButton/index.js deleted file mode 100644 index fd020cb6..00000000 --- a/app/templates/downloadButton/index.js +++ /dev/null @@ -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` - `; - - function download(event) { - event.preventDefault(); - event.target.disabled = true; - if (downloadState === 'ready') { - emit('download', state.fileInfo); - } - } -}; diff --git a/app/templates/downloadPassword/downloadPassword.css b/app/templates/downloadPassword/downloadPassword.css deleted file mode 100644 index fd07616d..00000000 --- a/app/templates/downloadPassword/downloadPassword.css +++ /dev/null @@ -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; -} diff --git a/app/templates/downloadPassword/index.js b/app/templates/downloadPassword/index.js deleted file mode 100644 index a4f74002..00000000 --- a/app/templates/downloadPassword/index.js +++ /dev/null @@ -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` -
- - - -
- - - - -
-
`; - - 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; -}; diff --git a/app/templates/file/file.css b/app/templates/file/file.css deleted file mode 100644 index 67e436a0..00000000 --- a/app/templates/file/file.css +++ /dev/null @@ -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; - } -} diff --git a/app/templates/file/index.js b/app/templates/file/index.js deleted file mode 100644 index b0221a42..00000000 --- a/app/templates/file/index.js +++ /dev/null @@ -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` -
  • - -
    - ${fileIcon(file.name, file._hasPassword)} -
    -

    ${fileName}

    -

    - ${bytes(file.size)} · - ${state.translate('downloadCount', { - num: `${number(totalDownloads)} / ${number(downloadLimit)}` - })} - ${remainingTime} -

    -
    -
    -
    -
  • - `; - - 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; -} diff --git a/app/templates/fileIcon/fileIcon.css b/app/templates/fileIcon/fileIcon.css deleted file mode 100644 index ac87674d..00000000 --- a/app/templates/fileIcon/fileIcon.css +++ /dev/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; -} diff --git a/app/templates/fileIcon/index.js b/app/templates/fileIcon/index.js deleted file mode 100644 index 2ce9e110..00000000 --- a/app/templates/fileIcon/index.js +++ /dev/null @@ -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` -
    -
    ${type}
    - -
    `; -}; diff --git a/app/templates/fileList/fileList.css b/app/templates/fileList/fileList.css deleted file mode 100644 index 787f9c31..00000000 --- a/app/templates/fileList/fileList.css +++ /dev/null @@ -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; - } -} diff --git a/app/templates/fileList/index.js b/app/templates/fileList/index.js deleted file mode 100644 index 55229f5f..00000000 --- a/app/templates/fileList/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const html = require('choo/html'); -const file = require('../file'); - -module.exports = function(state) { - if (state.storage.files.length) { - return html` - - `; - } -}; diff --git a/app/templates/footer/footer.css b/app/templates/footer/footer.css deleted file mode 100644 index 9a0262a7..00000000 --- a/app/templates/footer/footer.css +++ /dev/null @@ -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; - } -} diff --git a/app/templates/footer/index.js b/app/templates/footer/index.js deleted file mode 100644 index 281010f4..00000000 --- a/app/templates/footer/index.js +++ /dev/null @@ -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``; - // 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'); - }); - } -}; diff --git a/app/templates/fxPromo/fxPromo.css b/app/templates/fxPromo/fxPromo.css deleted file mode 100644 index c1582fcc..00000000 --- a/app/templates/fxPromo/fxPromo.css +++ /dev/null @@ -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; -} diff --git a/app/templates/fxPromo/index.js b/app/templates/fxPromo/index.js deleted file mode 100644 index 1b0532d5..00000000 --- a/app/templates/fxPromo/index.js +++ /dev/null @@ -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` -
    -
    - - Send is brought to you by the all-new Firefox. - Download Firefox now ≫ -
    -
    `; - - function clicked() { - emit('experiment', { cd3: 'promo' }); - } -}; diff --git a/app/templates/header/header.css b/app/templates/header/header.css deleted file mode 100644 index 7b7db949..00000000 --- a/app/templates/header/header.css +++ /dev/null @@ -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%; -} diff --git a/app/templates/header/index.js b/app/templates/header/index.js deleted file mode 100644 index be296ffe..00000000 --- a/app/templates/header/index.js +++ /dev/null @@ -1,14 +0,0 @@ -const html = require('choo/html'); - -module.exports = function() { - const header = html` -
    -
    `; - // 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; -}; diff --git a/app/templates/modal/index.js b/app/templates/modal/index.js deleted file mode 100644 index 005824bc..00000000 --- a/app/templates/modal/index.js +++ /dev/null @@ -1,15 +0,0 @@ -const html = require('choo/html'); - -module.exports = function(state, emit) { - return html` - `; - - function close(event) { - state.modal = null; - emit('render'); - } -}; diff --git a/app/templates/modal/modal.css b/app/templates/modal/modal.css deleted file mode 100644 index ae1608a1..00000000 --- a/app/templates/modal/modal.css +++ /dev/null @@ -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); -} diff --git a/app/templates/okDialog/index.js b/app/templates/okDialog/index.js deleted file mode 100644 index 4986c1b9..00000000 --- a/app/templates/okDialog/index.js +++ /dev/null @@ -1,13 +0,0 @@ -const html = require('choo/html'); - -module.exports = function(message) { - return function(state, emit, close) { - return html` -
    -
    ${message}
    - -
    `; - }; -}; diff --git a/app/templates/okDialog/okDialog.css b/app/templates/okDialog/okDialog.css deleted file mode 100644 index 5b04d83b..00000000 --- a/app/templates/okDialog/okDialog.css +++ /dev/null @@ -1,11 +0,0 @@ -.okDialog { - display: flex; - flex-direction: column; - max-width: 400px; - padding: 16px; -} - -.okDialog__message { - margin: 32px; - text-align: center; -} diff --git a/app/templates/passwordInput/index.js b/app/templates/passwordInput/index.js deleted file mode 100644 index a1b9c01c..00000000 --- a/app/templates/passwordInput/index.js +++ /dev/null @@ -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` -
    -
    - - -
    -
    `; - - 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, '•') : '••••••••••••'; -} diff --git a/app/templates/passwordInput/passwordInput.css b/app/templates/passwordInput/passwordInput.css deleted file mode 100644 index ccfab210..00000000 --- a/app/templates/passwordInput/passwordInput.css +++ /dev/null @@ -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); -} diff --git a/app/templates/popup/index.js b/app/templates/popup/index.js deleted file mode 100644 index cdf65a89..00000000 --- a/app/templates/popup/index.js +++ /dev/null @@ -1,24 +0,0 @@ -const html = require('choo/html'); - -module.exports = function(msg, confirmText, cancelText, confirmCallback) { - return html` - `; - - function hide(e) { - e.stopPropagation(); - const popup = document.querySelector('.popup.popup--show'); - if (popup) { - popup.classList.remove('popup--show'); - } - } -}; diff --git a/app/templates/popup/popup.css b/app/templates/popup/popup.css deleted file mode 100644 index 548aedb6..00000000 --- a/app/templates/popup/popup.css +++ /dev/null @@ -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; -} diff --git a/app/templates/selectbox/selectbox.css b/app/templates/selectbox/selectbox.css deleted file mode 100644 index fcbb196b..00000000 --- a/app/templates/selectbox/selectbox.css +++ /dev/null @@ -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; -} diff --git a/app/templates/setPasswordSection/index.js b/app/templates/setPasswordSection/index.js deleted file mode 100644 index 8fad4337..00000000 --- a/app/templates/setPasswordSection/index.js +++ /dev/null @@ -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` -
    -
    - - -
    - - ${passwordInput(state)} - -
    `; - - 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 = ''; - } - } -}; diff --git a/app/templates/setPasswordSection/setPasswordSection.css b/app/templates/setPasswordSection/setPasswordSection.css deleted file mode 100644 index 28233cfe..00000000 --- a/app/templates/setPasswordSection/setPasswordSection.css +++ /dev/null @@ -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; -} diff --git a/app/templates/signupDialog/signupDialog.css b/app/templates/signupDialog/signupDialog.css deleted file mode 100644 index 018dbcad..00000000 --- a/app/templates/signupDialog/signupDialog.css +++ /dev/null @@ -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); -} diff --git a/app/templates/signupPromo/index.js b/app/templates/signupPromo/index.js deleted file mode 100644 index 20122946..00000000 --- a/app/templates/signupPromo/index.js +++ /dev/null @@ -1,16 +0,0 @@ -const html = require('choo/html'); - -module.exports = function(state) { - if (state.user.loggedIn || !state.capabilities.account) { - return null; - } - return html` - -
    ${state.translate('signInPromoText')}
    -
    ${state.translate('signInExplanation')}
    - -
    - `; -}; diff --git a/app/templates/signupPromo/signupPromo.css b/app/templates/signupPromo/signupPromo.css deleted file mode 100644 index 231736db..00000000 --- a/app/templates/signupPromo/signupPromo.css +++ /dev/null @@ -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; - } -} diff --git a/app/templates/timeLimitText/index.js b/app/templates/timeLimitText/index.js deleted file mode 100644 index 3de88db5..00000000 --- a/app/templates/timeLimitText/index.js +++ /dev/null @@ -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; -}; diff --git a/app/templates/title/index.js b/app/templates/title/index.js deleted file mode 100644 index 6e0b0e01..00000000 --- a/app/templates/title/index.js +++ /dev/null @@ -1,11 +0,0 @@ -const html = require('choo/html'); - -module.exports = function(state) { - return html` -
    - ${state.translate('uploadPageHeader')} -
    - ${state.translate('pageHeaderCredits')} -
    -
    `; -}; diff --git a/app/templates/title/title.css b/app/templates/title/title.css deleted file mode 100644 index 8b0c90a7..00000000 --- a/app/templates/title/title.css +++ /dev/null @@ -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; -} diff --git a/app/templates/uploadedFile/index.js b/app/templates/uploadedFile/index.js deleted file mode 100644 index 40c8345d..00000000 --- a/app/templates/uploadedFile/index.js +++ /dev/null @@ -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` -
  • - - ${fileIcon(file.name, hasPassword)} - -
    - cancel -
    - -
    -

    ${file.name}

    -

    - ${bytes(file.size)} -

    -
    - - -
  • - `; -}; diff --git a/app/templates/uploadedFile/uploadedFile.css b/app/templates/uploadedFile/uploadedFile.css deleted file mode 100644 index f3fe4dcb..00000000 --- a/app/templates/uploadedFile/uploadedFile.css +++ /dev/null @@ -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; - } -} diff --git a/app/templates/uploadedFileList/index.js b/app/templates/uploadedFileList/index.js deleted file mode 100644 index 1175af02..00000000 --- a/app/templates/uploadedFileList/index.js +++ /dev/null @@ -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` - - `; -}; diff --git a/app/templates/uploadedFileList/uploadedFileList.css b/app/templates/uploadedFileList/uploadedFileList.css deleted file mode 100644 index 2784a3c7..00000000 --- a/app/templates/uploadedFileList/uploadedFileList.css +++ /dev/null @@ -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; -} diff --git a/app/templates/userAccount/index.js b/app/templates/userAccount/index.js deleted file mode 100644 index f852bdec..00000000 --- a/app/templates/userAccount/index.js +++ /dev/null @@ -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` - ` - : html` - - `; - - return html` -
    -
    - -
    - ${menu} -
    `; - - 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'); - } -}; diff --git a/app/templates/userAccount/userAccount.css b/app/templates/userAccount/userAccount.css deleted file mode 100644 index 44ae4feb..00000000 --- a/app/templates/userAccount/userAccount.css +++ /dev/null @@ -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; -} diff --git a/app/ui/account.js b/app/ui/account.js new file mode 100644 index 00000000..d9f92ff6 --- /dev/null +++ b/app/ui/account.js @@ -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`
  • ${user.email}
  • `); + menuItems.push( + html`
  • ${state.translate( + 'logOut' + )}
  • ` + ); + } else { + menuItems.push( + html`
  • ${state.translate( + 'signInMenuOption' + )}
  • ` + ); + } + return html`
    + + +
    `; + + 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'); + } +}; diff --git a/app/ui/archiveTile.js b/app/ui/archiveTile.js new file mode 100644 index 00000000..4b401a39 --- /dev/null +++ b/app/ui/archiveTile.js @@ -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` +
    +
    + + +
    + + +
    `; + + 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` +
    + +

    +

    ${file.name}

    +
    ${bytes( + file.size + )}
    + +

    + ${action} +
    `; +} + +function archiveDetails(translate, archive) { + if (archive.manifest.files.length > 1) { + return html` +
    + ${translate('fileCount', { + num: archive.manifest.files.length + })} + ${list(archive.manifest.files.map(f => fileInfo(f)), 'list-reset h-full')} +
    `; + } + function toggled(event) { + event.stopPropagation(); + archive.open = event.target.open; + } +} + +module.exports = function(state, emit, archive) { + return html` +
    +

    + + +

    ${archive.name}

    +
    ${bytes( + archive.size + )}
    +

    +
    + ${expiryInfo(state.translate, archive)} +
    + ${archiveDetails(state.translate, archive)} +
    + +
    `; + + 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` +
    + ${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' + )} +
    + + +
    + ${expiryOptions(state, emit)} + ${password(state, emit)} + +
    `; + + 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` + `; + 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` +
    +

    + +

    ${archive.name}

    +
    ${bytes( + archive.size + )}
    +

    +
    + ${expiryInfo(state.translate, { + dlimit: state.downloadCount || 1, + dtotal: 0, + expiresAt: Date.now() + 500 + state.timeLimit * 1000 + })} +
    +
    ${progressPercent}
    + ${progressPercent} + +
    `; + + function cancel(event) { + event.stopPropagation(); + event.target.disabled = true; + emit('cancel'); + } +}; + +module.exports.empty = function(state, emit) { + return html` +
    + +
    ${state.translate( + 'uploadDropDragMessage' + )}
    +
    ${state.translate( + 'uploadDropClickMessage' + )}
    + + +
    `; + + 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` +
    +

    + +

    ${archive.name}

    +
    ${bytes( + archive.size + )}
    +

    + ${archiveDetails(state.translate, archive)} + +
    `; + + 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` +
    +

    + +

    ${archive.name}

    +
    ${bytes( + archive.size + )}
    +

    +
    ${progressPercent}
    + ${progressPercent} + +
    `; + + function cancel(event) { + event.preventDefault(); + event.target.disabled = true; + emit('download', archive); + } +}; diff --git a/app/pages/blank.js b/app/ui/blank.js similarity index 56% rename from app/pages/blank.js rename to app/ui/blank.js index edaadcac..f6411022 100644 --- a/app/pages/blank.js +++ b/app/ui/blank.js @@ -1,5 +1,5 @@ const html = require('choo/html'); module.exports = function() { - return html`
    `; + return html`
    `; }; diff --git a/app/ui/copyDialog.js b/app/ui/copyDialog.js new file mode 100644 index 00000000..87f1456b --- /dev/null +++ b/app/ui/copyDialog.js @@ -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` +
    +

    ${state.translate('notifyUploadDone')}

    +

    ${state.translate( + 'copyUrlFormLabelWithName', + { filename: name } + )}

    + + + ${state.translate( + 'okButton' + )} +
    `; + + function copy(event) { + event.stopPropagation(); + copyToClipboard(url); + event.target.textContent = state.translate('copiedUrl'); + setTimeout(close, 1000); + } + }; +}; diff --git a/app/ui/download.js b/app/ui/download.js new file mode 100644 index 00000000..a4d89639 --- /dev/null +++ b/app/ui/download.js @@ -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` +
    + + +
    + + + +
    +
    `; + + 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` +
    +

    ${state.translate( + 'downloadFinish' + )}

    +

    + ${state.translate( + 'sendYourFilesLink' + )} +

    +
    `; + break; + default: + content = archiveTile.preview(state, emit); + } + } else if (state.fileInfo.requiresPassword && !state.fileInfo.password) { + content = password(state, emit); + } + return html` +
    +
    +
    ${content}
    +
    ${intro(state)}
    +
    +
    `; +}; diff --git a/app/ui/error.js b/app/ui/error.js new file mode 100644 index 00000000..c0189d9c --- /dev/null +++ b/app/ui/error.js @@ -0,0 +1,18 @@ +const html = require('choo/html'); +const assets = require('../../common/assets'); + +module.exports = function(state) { + return html` +
    +
    +

    ${state.translate('errorPageHeader')}

    + +

    + ${state.translate('uploadPageExplainer')} +

    + + ${state.translate('sendYourFilesLink')} + +
    +
    `; +}; diff --git a/app/templates/expireInfo/index.js b/app/ui/expiryOptions.js similarity index 74% rename from app/templates/expireInfo/index.js rename to app/ui/expiryOptions.js index d0aa7ac7..b94ffe7b 100644 --- a/app/templates/expireInfo/index.js +++ b/app/ui/expiryOptions.js @@ -1,17 +1,19 @@ /* globals DEFAULTS */ const html = require('choo/html'); const raw = require('choo/html/raw'); -const selectbox = require('../selectbox'); -const timeLimitText = require('../timeLimitText'); -const signupDialog = require('../signupDialog'); +const { secondsToL10nId } = require('../utils'); +const selectbox = require('./selectbox'); +const signupDialog = require('./signupDialog'); module.exports = function(state, emit) { - const el = html`
    ${raw( - state.translate('frontPageExpireInfo', { - downloadCount: '', - timespan: '' - }) - )} + const el = html` +
    + ${raw( + state.translate('frontPageExpireInfo', { + downloadCount: '', + timespan: '' + }) + )}
    `; if (el.__encoded) { // we're rendering on the server @@ -50,7 +52,10 @@ module.exports = function(state, emit) { selectbox( state.timeLimit || 86400, expires, - num => timeLimitText(state.translate, num), + num => { + const l10n = secondsToL10nId(num); + return state.translate(l10n.id, l10n); + }, value => { const max = state.user.maxExpireSeconds; if (value > max) { diff --git a/app/ui/footer.js b/app/ui/footer.js new file mode 100644 index 00000000..b1023718 --- /dev/null +++ b/app/ui/footer.js @@ -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``; + // 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; +}; diff --git a/app/ui/fxPromo.js b/app/ui/fxPromo.js new file mode 100644 index 00000000..cd472e17 --- /dev/null +++ b/app/ui/fxPromo.js @@ -0,0 +1,19 @@ +const html = require('choo/html'); +const assets = require('../../common/assets'); + +module.exports = function() { + return html` +
    +
    + Firefox + Send is brought to you by the all-new Firefox. + Download Firefox now ≫ + +
    +
    `; +}; diff --git a/app/ui/header.js b/app/ui/header.js new file mode 100644 index 00000000..6f25fa41 --- /dev/null +++ b/app/ui/header.js @@ -0,0 +1,25 @@ +const html = require('choo/html'); +const account = require('./account'); + +module.exports = function(state, emit) { + const header = html` +
    + + ${account(state, emit)} + +
    `; + // 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; +}; diff --git a/app/ui/home.js b/app/ui/home.js new file mode 100644 index 00000000..864887bb --- /dev/null +++ b/app/ui/home.js @@ -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` +
    + ${state.modal && modal(state, emit)} +
    +
    ${left}
    +
    ${right}
    +
    +
    `; +}; diff --git a/app/ui/intro.js b/app/ui/intro.js new file mode 100644 index 00000000..d1bba348 --- /dev/null +++ b/app/ui/intro.js @@ -0,0 +1,20 @@ +const html = require('choo/html'); +const assets = require('../../common/assets'); + +module.exports = function intro(state) { + return html` +
    +

    +

    ${state.translate( + 'uploadPageHeader' + )}
    +
    ${state.translate( + 'pageHeaderCredits' + )}
    +

    + +

    ${state.translate( + 'uploadPageExplainer' + )}

    +
    `; +}; diff --git a/app/ui/legal.js b/app/ui/legal.js new file mode 100644 index 00000000..f00d74d7 --- /dev/null +++ b/app/ui/legal.js @@ -0,0 +1,33 @@ +const html = require('choo/html'); +const raw = require('choo/html/raw'); + +module.exports = function(state) { + return html` +
    +
    +

    ${state.translate('legalHeader')}

    + ${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/' + ]) + )} +
    +
    `; +}; + +function replaceLinks(str, urls) { + let i = 0; + const s = str.replace( + /([^<]+)<\/a>/g, + (m, v) => `${v}` + ); + return `

    ${s}

    `; +} diff --git a/app/ui/modal.js b/app/ui/modal.js new file mode 100644 index 00000000..94416666 --- /dev/null +++ b/app/ui/modal.js @@ -0,0 +1,21 @@ +const html = require('choo/html'); + +module.exports = function(state, emit) { + return html` +
    +
    +
    e.stopPropagation()}> + ${state.modal(state, emit, close)} +
    +
    +
    `; + + function close(event) { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + state.modal = null; + emit('render'); + } +}; diff --git a/app/ui/notFound.js b/app/ui/notFound.js new file mode 100644 index 00000000..78a63361 --- /dev/null +++ b/app/ui/notFound.js @@ -0,0 +1,22 @@ +const html = require('choo/html'); +const assets = require('../../common/assets'); + +module.exports = function(state) { + return html` +
    +
    +

    ${state.translate( + 'expiredPageHeader' + )}

    + +

    + ${state.translate('uploadPageExplainer')} +

    + + ${state.translate('sendYourFilesLink')} + +
    +
    `; +}; diff --git a/app/ui/okDialog.js b/app/ui/okDialog.js new file mode 100644 index 00000000..0bca4442 --- /dev/null +++ b/app/ui/okDialog.js @@ -0,0 +1,13 @@ +const html = require('choo/html'); + +module.exports = function(message) { + return function(state, emit, close) { + return html` +
    +
    ${message}
    + +
    `; + }; +}; diff --git a/app/templates/selectbox/index.js b/app/ui/selectbox.js similarity index 75% rename from app/templates/selectbox/index.js rename to app/ui/selectbox.js index 43d2d666..9f529708 100644 --- a/app/templates/selectbox/index.js +++ b/app/ui/selectbox.js @@ -1,11 +1,10 @@ const html = require('choo/html'); module.exports = function(selected, options, translate, changed) { - const id = `select-${Math.random()}`; let x = selected; return html` - ${options.map( i => html`