зеркало из https://github.com/mozilla/send.git
a new approach for the ui
This commit is contained in:
Родитель
cc85486414
Коммит
f0cfc19f8c
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,16 @@ 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');
|
||||
// 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');
|
||||
// document
|
||||
// .querySelector('.uploadArea')
|
||||
// .classList.remove('uploadArea--dragging');
|
||||
|
||||
const files = Array.from(event.dataTransfer.files);
|
||||
|
||||
|
|
|
@ -66,8 +66,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 +89,7 @@ export default function(state, emitter) {
|
|||
} catch (e) {
|
||||
state.raven.captureException(e);
|
||||
}
|
||||
render();
|
||||
});
|
||||
|
||||
emitter.on('cancel', () => {
|
||||
|
@ -149,15 +153,26 @@ 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.animation = () => {
|
||||
const x = document.querySelector('.foo');
|
||||
const y = x.previousElementSibling;
|
||||
x.animate(
|
||||
[
|
||||
{ transform: `translateY(-${y.getBoundingClientRect().height}px)` },
|
||||
{ transform: 'translateY(0)' }
|
||||
],
|
||||
{
|
||||
duration: 400,
|
||||
easing: 'ease'
|
||||
}
|
||||
);
|
||||
y.animate([{ opacity: 0 }, { opacity: 1 }], {
|
||||
delay: 300,
|
||||
duration: 100,
|
||||
fill: 'both'
|
||||
});
|
||||
};
|
||||
// emitter.emit('pushState', `/share/${ownedFile.id}`);
|
||||
} catch (err) {
|
||||
if (err.message === '0') {
|
||||
//cancelled. do nothing
|
||||
|
@ -176,6 +191,7 @@ export default function(state, emitter) {
|
|||
state.password = '';
|
||||
state.uploading = false;
|
||||
state.transfer = null;
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
109
app/main.css
109
app/main.css
|
@ -1,28 +1,81 @@
|
|||
@import './base.css';
|
||||
@import './pages/share/share.css';
|
||||
@import './pages/signin/signin.css';
|
||||
@import './pages/uploads/uploads.css';
|
||||
@import './pages/unsupported/unsupported.css';
|
||||
@import './templates/archiveTile/archiveTile.css';
|
||||
@import './templates/controlArea/controlArea.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/fileManager/fileManager.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';
|
||||
@import 'tailwindcss/preflight';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/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 {
|
||||
@apply bg-blue-lightest;
|
||||
|
||||
min-height: calc(100vh - 6rem);
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
background-image: url('../assets/send_logo.svg');
|
||||
background-position: left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 2rem;
|
||||
padding-left: 2.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.bg-shades {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.main {
|
||||
@apply flex-1;
|
||||
@apply self-center;
|
||||
@apply bg-white;
|
||||
@apply shadow-md;
|
||||
@apply m-auto;
|
||||
|
||||
min-width: 30rem;
|
||||
max-width: 60rem;
|
||||
min-height: 30rem;
|
||||
max-height: 38rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,15 @@ import User from './user';
|
|||
window.appState = state;
|
||||
window.appEmit = emitter.emit.bind(emitter);
|
||||
let unsupportedReason = null;
|
||||
|
||||
emitter.on('render', () => {
|
||||
if (state.animation) {
|
||||
window.requestAnimationFrame(() => {
|
||||
state.animation();
|
||||
state.animation = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
if (
|
||||
// Firefox < 50
|
||||
/firefox/i.test(navigator.userAgent) &&
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
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 modal = require('./ui/modal');
|
||||
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 font-sans bg-white md:h-screen md:bg-grey-lightest">
|
||||
${state.modal && modal(state, emit)}
|
||||
${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/welcome')));
|
||||
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,65 +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 modal = require('../templates/modal');
|
||||
const header = require('../templates/header');
|
||||
|
||||
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(page) {
|
||||
return function(state, emit) {
|
||||
const b = html`<body>
|
||||
${modalDialog(state, emit)}
|
||||
${banner(state, emit)}
|
||||
${header(state, emit)}
|
||||
${page(state, emit)}
|
||||
${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('/uploads', body(require('../pages/uploads')));
|
||||
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;
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
const html = require('choo/html');
|
||||
const itemClass =
|
||||
'block p-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">
|
||||
<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,41 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../common/assets');
|
||||
const { list } = require('../utils');
|
||||
const archiveTile = require('./archiveTile');
|
||||
|
||||
function intro(state) {
|
||||
return html`
|
||||
<article class="flex flex-col items-center justify-center bg-white border border-grey-light p-2">
|
||||
<p class="text-center">
|
||||
<div class="font-semibold">${state.translate('uploadPageHeader')}</div>
|
||||
<div class="italic">${state.translate('pageHeaderCredits')}</div>
|
||||
</p>
|
||||
<img src="${assets.get('illustration_download.svg')}"/>
|
||||
<p class="m-4 max-w-sm text-sm font-light">${state.translate(
|
||||
'uploadPageExplainer'
|
||||
)}</p>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
const archives = state.storage.files.map(archive =>
|
||||
archiveTile(state, emit, archive)
|
||||
);
|
||||
let wip = '';
|
||||
if (state.uploading) {
|
||||
wip = archiveTile.uploading(state, emit);
|
||||
} else if (state.archive) {
|
||||
wip = archiveTile.wip(state, emit);
|
||||
} else {
|
||||
wip = archiveTile.empty(state, emit);
|
||||
}
|
||||
archives.reverse();
|
||||
if (archives.length < 1) {
|
||||
archives.push(intro(state));
|
||||
}
|
||||
return html`
|
||||
<section class="relative h-full w-full px-6">
|
||||
<div class="pt-4 pb-2">${wip}</div>
|
||||
${list(archives, 'list-reset h-full overflow-y-scroll foo', 'py-2')}
|
||||
</section>`;
|
||||
};
|
|
@ -0,0 +1,260 @@
|
|||
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 fileInfo(file, action) {
|
||||
return html`
|
||||
<article class="flex flex-row items-start p-3">
|
||||
<img class="" src="${assets.get('blue_file.svg')}"/>
|
||||
<p class="ml-3 w-full">
|
||||
<h1 class="text-base font-semibold">${file.name}</h1>
|
||||
<div class="text-sm font-light">${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">
|
||||
<summary>${translate('fileCount', {
|
||||
num: archive.manifest.files.length
|
||||
})}</summary>
|
||||
${list(archive.manifest.files.map(f => fileInfo(f)), 'list-reset')}
|
||||
</details>`;
|
||||
}
|
||||
}
|
||||
|
||||
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-3">
|
||||
<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-base font-semibold">${archive.name}</h1>
|
||||
<div class="text-sm font-light">${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">
|
||||
<button
|
||||
class="text-blue self-end"
|
||||
onclick=${copy}>
|
||||
<img src="${assets.get('copy-16.svg')}" class="mr-1"/>
|
||||
${state.translate('copyUrlHover')}
|
||||
</button>
|
||||
</article>`;
|
||||
|
||||
function copy(event) {
|
||||
event.stopPropagation();
|
||||
copyToClipboard(archive.url);
|
||||
}
|
||||
|
||||
function del(event) {
|
||||
event.stopPropagation();
|
||||
emit('delete', { file: archive, location: 'success-screen' });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.wip = function(state, emit) {
|
||||
return html`
|
||||
<article class="relative flex flex-col bg-white border border-grey-light p-2 z-20">
|
||||
${list(state.archive.files.map(f => fileInfo(f, remove(f))), 'list-reset')}
|
||||
<div class="border border-dashed border-blue-light mb-2">
|
||||
<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 text-blue p-2"
|
||||
title="${state.translate('addFilesButton')}">
|
||||
<img src="${assets.get('addfile.svg')}" class="w-6 h-6 mr-2"/>
|
||||
${state.translate('addFilesButton')}
|
||||
</label>
|
||||
</div>
|
||||
${expiryOptions(state, emit)}
|
||||
<button
|
||||
class="border rounded bg-blue text-white mt-2 py-2 px-6"
|
||||
title="${state.translate('uploadFilesButton')}"
|
||||
onclick=${upload}>
|
||||
${state.translate('uploadFilesButton')}
|
||||
</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"
|
||||
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="relative z-20 flex flex-col items-start border border-grey-light bg-white p-3">
|
||||
<p class="w-full">
|
||||
<img class="float-left mr-3" src="${assets.get('blue_file.svg')}"/>
|
||||
<h1 class="text-base font-semibold">${archive.name}</h1>
|
||||
<div class="text-sm font-light">${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">${progressPercent}</div>
|
||||
<progress class="mb-1" value="${progress}">${progressPercent}</progress>
|
||||
<button
|
||||
class="text-blue self-end"
|
||||
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 p-8">
|
||||
<div class="p-1">${state.translate('uploadDropDragMessage')}</div>
|
||||
<input
|
||||
id="file-upload"
|
||||
class="hidden"
|
||||
type="file"
|
||||
multiple
|
||||
onchange=${add} />
|
||||
<label
|
||||
for="file-upload"
|
||||
class="border rounded bg-blue text-white py-2 px-6"
|
||||
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;
|
||||
return html`
|
||||
<article class="relative flex flex-col bg-white border border-grey-light p-2 z-20">
|
||||
<p class="w-full mb-4">
|
||||
<img class="float-left mr-3" src="${assets.get('blue_file.svg')}"/>
|
||||
<h1 class="text-base font-semibold">${archive.name}</h1>
|
||||
<div class="text-sm font-light">${bytes(archive.size)}</div>
|
||||
</p>
|
||||
${archiveDetails(state.translate, archive)}
|
||||
<hr class="w-full border-t">
|
||||
<button
|
||||
class="border rounded bg-blue text-white mt-2 py-2 px-6"
|
||||
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="relative flex flex-col bg-white border border-grey-light p-2 z-20">
|
||||
<p class="w-full mb-4">
|
||||
<img class="float-left mr-3" src="${assets.get('blue_file.svg')}"/>
|
||||
<h1 class="text-base font-semibold">${archive.name}</h1>
|
||||
<div class="text-sm font-light">${bytes(archive.size)}</div>
|
||||
</p>
|
||||
<div class="text-blue text-sm font-medium">${progressPercent}</div>
|
||||
<progress class="" value="${progress}">${progressPercent}</progress>
|
||||
<button
|
||||
class="border rounded bg-grey-dark text-white mt-2 py-2 px-6"
|
||||
title="${state.translate('downloadCancel')}"
|
||||
onclick=${cancel}>
|
||||
${state.translate('downloadCancel')}
|
||||
</button>
|
||||
</article>`;
|
||||
|
||||
function cancel(event) {
|
||||
event.preventDefault();
|
||||
event.target.disabled = true;
|
||||
emit('download', archive);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
const html = require('choo/html');
|
||||
|
||||
module.exports = function() {
|
||||
return html`<main class="main"></main>`;
|
||||
};
|
|
@ -0,0 +1,103 @@
|
|||
/* global downloadMetadata */
|
||||
const html = require('choo/html');
|
||||
const archiveTile = require('./archiveTile');
|
||||
|
||||
function password(state, emit) {
|
||||
const fileInfo = state.fileInfo;
|
||||
const invalid = fileInfo.password === null;
|
||||
|
||||
const visible = invalid ? 'visible' : 'invisible';
|
||||
const invalidBtn = invalid ? '' : '';
|
||||
|
||||
const div = html`
|
||||
<div class="">
|
||||
<label
|
||||
class="${visible}"
|
||||
for="password-input">
|
||||
${state.translate('passwordTryAgain')}
|
||||
</label>
|
||||
|
||||
<form class="" onsubmit=${checkPassword} data-no-csrf>
|
||||
<input id="password-input"
|
||||
class=""
|
||||
maxlength="64"
|
||||
autocomplete="off"
|
||||
placeholder="${state.translate('unlockInputPlaceholder')}"
|
||||
oninput=${inputChanged}
|
||||
type="password" />
|
||||
|
||||
<input type="submit"
|
||||
id="password-btn"
|
||||
class="${invalidBtn}"
|
||||
value="${state.translate('unlockInputLabel')}"/>
|
||||
</form>
|
||||
</div>`;
|
||||
|
||||
if (!(div instanceof String)) {
|
||||
setTimeout(() => document.getElementById('password-input').focus());
|
||||
}
|
||||
|
||||
function inputChanged() {
|
||||
//TODO
|
||||
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;
|
||||
}
|
||||
|
||||
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 = ''; //TODO
|
||||
break;
|
||||
default:
|
||||
content = archiveTile.preview(state, emit);
|
||||
}
|
||||
} else if (state.fileInfo.requiresPassword && !state.fileInfo.password) {
|
||||
content = password(state, emit);
|
||||
}
|
||||
return html`
|
||||
<main class="main">
|
||||
<section class="relative h-full w-full my-4 px-6">
|
||||
${content}
|
||||
</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">
|
||||
<div class="flex flex-col items-center text-center bg-white m-6 p-4 border border-grey-light md:border-none md:px-12">
|
||||
<h1 class="">${state.translate('errorPageHeader')}</h1>
|
||||
<img class="my-8" src="${assets.get('illustration_error.svg')}"/>
|
||||
<p class="">
|
||||
${state.translate('uploadPageExplainer')}
|
||||
</p>
|
||||
<a class="text-blue mt-4" href="/">
|
||||
${state.translate('sendYourFilesLink')}
|
||||
</a>
|
||||
</div>
|
||||
</main>`;
|
||||
};
|
|
@ -0,0 +1,73 @@
|
|||
/* globals DEFAULTS */
|
||||
const html = require('choo/html');
|
||||
const raw = require('choo/html/raw');
|
||||
const { secondsToL10nId } = require('../utils');
|
||||
const selectbox = require('./selectbox');
|
||||
const signupDialog = require('./signupDialog');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
const el = html`
|
||||
<div class="">
|
||||
${raw(
|
||||
state.translate('frontPageExpireInfo', {
|
||||
downloadCount: '<select id="dlCount"></select>',
|
||||
timespan: '<select id="timespan"></select>'
|
||||
})
|
||||
)}
|
||||
</div>`;
|
||||
if (el.__encoded) {
|
||||
// we're rendering on the server
|
||||
return el;
|
||||
}
|
||||
|
||||
const counts = DEFAULTS.DOWNLOAD_COUNTS.filter(
|
||||
i => state.capabilities.account || i <= state.user.maxDownloads
|
||||
);
|
||||
|
||||
const dlCountSelect = el.querySelector('#dlCount');
|
||||
el.replaceChild(
|
||||
selectbox(
|
||||
state.downloadCount || 1,
|
||||
counts,
|
||||
num => state.translate('downloadCount', { num }),
|
||||
value => {
|
||||
const max = state.user.maxDownloads;
|
||||
if (value > max) {
|
||||
state.modal = signupDialog();
|
||||
value = max;
|
||||
}
|
||||
state.downloadCount = value;
|
||||
emit('render');
|
||||
}
|
||||
),
|
||||
dlCountSelect
|
||||
);
|
||||
|
||||
const expires = DEFAULTS.EXPIRE_TIMES_SECONDS.filter(
|
||||
i => state.capabilities.account || i <= state.user.maxExpireSeconds
|
||||
);
|
||||
|
||||
const timeSelect = el.querySelector('#timespan');
|
||||
el.replaceChild(
|
||||
selectbox(
|
||||
state.timeLimit || 86400,
|
||||
expires,
|
||||
num => {
|
||||
const l10n = secondsToL10nId(num);
|
||||
return state.translate(l10n.id, l10n);
|
||||
},
|
||||
value => {
|
||||
const max = state.user.maxExpireSeconds;
|
||||
if (value > max) {
|
||||
state.modal = signupDialog();
|
||||
value = max;
|
||||
}
|
||||
state.timeLimit = value;
|
||||
emit('render');
|
||||
}
|
||||
),
|
||||
timeSelect
|
||||
);
|
||||
|
||||
return el;
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
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-none m-2 font-medium text-xs text-grey-dark">
|
||||
<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="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,21 @@
|
|||
const html = require('choo/html');
|
||||
const account = require('./account');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
const header = html`
|
||||
<header class="flex-none flex flex-row items-center bg-white w-full px-4 h-12 shadow-md justify-between">
|
||||
<a
|
||||
class="header-logo"
|
||||
href="/">
|
||||
<h1 class="text-black font-normal">Firefox <b>Send</b></h1>
|
||||
</a>
|
||||
${account(state, emit)}
|
||||
</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 raw = require('choo/html/raw');
|
||||
|
||||
module.exports = function(state) {
|
||||
return html`
|
||||
<main class="main">
|
||||
<div class="flex flex-col items-center bg-white m-6 p-4 border border-grey-light md:border-none md:px-12">
|
||||
<h1 class="">${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="m-4">${s}</p>`;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
const html = require('choo/html');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
return html`
|
||||
<div class="fixed pin flex items-center justify-center overflow-hidden z-40 bg-shades" onclick=${close}>
|
||||
<div class="rounded max-w-md bg-white" onclick=${e => e.stopPropagation()}>
|
||||
${state.modal(state, emit, close)}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
function close(event) {
|
||||
state.modal = null;
|
||||
emit('render');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../common/assets');
|
||||
|
||||
module.exports = function(state) {
|
||||
return html`
|
||||
<main class="main">
|
||||
<div class="flex flex-col items-center text-center bg-white m-6 p-4 border border-grey-light md:border-none md:px-12">
|
||||
<h1 class="text-pink-dark">${state.translate('expiredPageHeader')}</h1>
|
||||
<img src="${assets.get('illustration_expired.svg')}" id="expired-img">
|
||||
<p class="">
|
||||
${state.translate('uploadPageExplainer')}
|
||||
</p>
|
||||
<a class="text-blue mt-4" href="/">
|
||||
${state.translate('sendYourFilesLink')}
|
||||
</a>
|
||||
</div>
|
||||
</main>`;
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
const html = require('choo/html');
|
||||
|
||||
module.exports = function(selected, options, translate, changed) {
|
||||
let x = selected;
|
||||
|
||||
return html`
|
||||
<select class="appearance-none cursor-pointer border rounded-sm bg-blue-lightest p-1" onchange=${choose}>
|
||||
${options.map(
|
||||
i =>
|
||||
html`<option value="${i}" ${
|
||||
i === selected ? 'selected' : ''
|
||||
}>${translate(i)}</option>`
|
||||
)}
|
||||
</select>`;
|
||||
|
||||
function choose(event) {
|
||||
const target = event.target;
|
||||
const value = +target.value;
|
||||
|
||||
if (x !== value) {
|
||||
x = value;
|
||||
changed(value);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
/* global LIMITS */
|
||||
const html = require('choo/html');
|
||||
const { bytes } = require('../utils');
|
||||
|
||||
module.exports = function() {
|
||||
return function(state, emit, close) {
|
||||
return html`
|
||||
<div class="flex flex-col p-4">
|
||||
<p class="p-8">
|
||||
${state.translate('accountBenefitTitle')}
|
||||
<ul class="my-2">
|
||||
<li>${state.translate('accountBenefitLargeFiles', {
|
||||
size: bytes(LIMITS.MAX_FILE_SIZE)
|
||||
})}</li>
|
||||
<li>${state.translate('accountBenefitExpiry')}</li>
|
||||
<li>${state.translate('accountBenefitSync')}</li>
|
||||
</ul>
|
||||
</p>
|
||||
<form
|
||||
onsubmit=${submitEmail}
|
||||
data-no-csrf>
|
||||
<input
|
||||
id="email-input"
|
||||
type="text"
|
||||
class="border rounded w-full px-2 text-lg text-grey-darker leading-loose"
|
||||
placeholder=${state.translate('emailEntryPlaceholder')}/>
|
||||
<input
|
||||
class="hidden"
|
||||
id="email-submit"
|
||||
type="submit"/>
|
||||
</form>
|
||||
<label class="border rounded bg-blue text-white leading-loose text-center my-2" for="email-submit">
|
||||
${state.translate('signInMenuOption')}
|
||||
</label>
|
||||
<button
|
||||
class=""
|
||||
title="${state.translate('deletePopupCancel')}"
|
||||
onclick=${close}>${state.translate('deletePopupCancel')}
|
||||
</button>
|
||||
</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 = '';
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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-3 font-bold">
|
||||
Firefox<br><span class="font-light">${strings.button}</span>
|
||||
</p>`;
|
||||
} else {
|
||||
strings = outdatedStrings(state);
|
||||
url = 'https://support.mozilla.org/kb/update-firefox-latest-version';
|
||||
buttonAction = html`
|
||||
<p class="ml-3">
|
||||
${strings.button}
|
||||
</p>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<main class="main">
|
||||
<div class="flex flex-col items-center bg-white m-6 p-4 border border-grey-light md:border-none md:px-12">
|
||||
<h1 class="text-center">${strings.header}</h1>
|
||||
<p class="my-16">
|
||||
${strings.description}
|
||||
</p>
|
||||
${why}
|
||||
<a href="${url}" class="border border-green-light rounded bg-green flex items-center justify-center text-2xl text-white my-16 p-2">
|
||||
<img
|
||||
src="${assets.get('firefox_logo-only.svg')}"
|
||||
class="w-16"
|
||||
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')
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
const html = require('choo/html');
|
||||
const archiveList = require('./archiveList');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
return html`<main class="main">${archiveList(state, emit)}</main>`;
|
||||
};
|
48
app/utils.js
48
app/utils.js
|
@ -1,3 +1,4 @@
|
|||
const html = require('choo/html');
|
||||
const b64 = require('base64-js');
|
||||
|
||||
function arrayToB64(array) {
|
||||
|
@ -182,6 +183,48 @@ async function streamToArrayBuffer(stream, size) {
|
|||
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 = {
|
||||
fadeOut,
|
||||
delay,
|
||||
|
@ -196,5 +239,8 @@ module.exports = {
|
|||
isFile,
|
||||
openLinksInNewTab,
|
||||
browserName,
|
||||
streamToArrayBuffer
|
||||
streamToArrayBuffer,
|
||||
list,
|
||||
secondsToL10nId,
|
||||
timeLeft
|
||||
};
|
||||
|
|
|
@ -2339,6 +2339,12 @@
|
|||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
||||
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
|
||||
},
|
||||
"camelcase-css": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-1.0.1.tgz",
|
||||
"integrity": "sha1-FXxCOCZfXPlKHf/ehkRlUsvz9wU=",
|
||||
"dev": true
|
||||
},
|
||||
"camelcase-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||
|
@ -2762,6 +2768,12 @@
|
|||
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
|
||||
"dev": true
|
||||
},
|
||||
"comment-regex": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/comment-regex/-/comment-regex-1.0.1.tgz",
|
||||
"integrity": "sha512-IWlN//Yfby92tOIje7J18HkNmWRR7JESA/BK8W7wqY/akITpU5B0JQWnbTjCfdChSrDNb0DrdA9jfAxiiBXyiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
|
@ -3469,6 +3481,12 @@
|
|||
"integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"css.escape": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||
"integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
|
||||
"dev": true
|
||||
},
|
||||
"cssesc": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
|
||||
|
@ -3659,6 +3677,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"defined": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
|
||||
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
|
||||
"dev": true
|
||||
},
|
||||
"del": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz",
|
||||
|
@ -6167,6 +6191,12 @@
|
|||
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
|
||||
"dev": true
|
||||
},
|
||||
"gather-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/gather-stream/-/gather-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-szmUr0V6gRVwDUEPMXczy+egkEs=",
|
||||
"dev": true
|
||||
},
|
||||
"gaze": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
|
||||
|
@ -7757,6 +7787,12 @@
|
|||
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
|
||||
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.4.9",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz",
|
||||
"integrity": "sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==",
|
||||
"dev": true
|
||||
},
|
||||
"js-levenshtein": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz",
|
||||
|
@ -11290,6 +11326,62 @@
|
|||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
|
||||
"dev": true
|
||||
},
|
||||
"perfectionist": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/perfectionist/-/perfectionist-2.4.0.tgz",
|
||||
"integrity": "sha1-wUetNxThJkZ/F2QSnuct+GHUfqA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"comment-regex": "^1.0.0",
|
||||
"defined": "^1.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"postcss": "^5.0.8",
|
||||
"postcss-scss": "^0.3.0",
|
||||
"postcss-value-parser": "^3.3.0",
|
||||
"read-file-stdin": "^0.2.0",
|
||||
"string.prototype.repeat": "^0.2.0",
|
||||
"vendors": "^1.0.0",
|
||||
"write-file-stdout": "0.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
|
||||
"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "5.2.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz",
|
||||
"integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^1.1.3",
|
||||
"js-base64": "^2.1.9",
|
||||
"source-map": "^0.5.6",
|
||||
"supports-color": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"postcss-scss": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-0.3.1.tgz",
|
||||
"integrity": "sha1-ZcYQ2OKn7g5isYNbcbiHBzSBbks=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"postcss": "^5.2.4"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
|
||||
"integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
|
@ -11830,6 +11922,18 @@
|
|||
"postcss": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"postcss-functions": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-functions/-/postcss-functions-3.0.0.tgz",
|
||||
"integrity": "sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.2",
|
||||
"object-assign": "^4.1.1",
|
||||
"postcss": "^6.0.9",
|
||||
"postcss-value-parser": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"postcss-html": {
|
||||
"version": "0.34.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.34.0.tgz",
|
||||
|
@ -11919,6 +12023,16 @@
|
|||
"postcss": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"postcss-js": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-1.0.1.tgz",
|
||||
"integrity": "sha512-smhUUMF5o5W1ZCQSyh5A3lNOXFLdNrxqyhWbLsGolZH2AgVmlyhxhYbIixfsdKE6r1vG5i7O40DPcvEvE1mvjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase-css": "^1.0.1",
|
||||
"postcss": "^6.0.11"
|
||||
}
|
||||
},
|
||||
"postcss-jsx": {
|
||||
"version": "0.35.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.35.0.tgz",
|
||||
|
@ -12185,6 +12299,29 @@
|
|||
"postcss": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"postcss-nested": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-3.0.0.tgz",
|
||||
"integrity": "sha512-1xxmLHSfubuUi6xZZ0zLsNoiKfk3BWQj6fkNMaBJC529wKKLcdeCxXt6KJmDLva+trNyQNwEaE/ZWMA7cve1fA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"postcss": "^6.0.14",
|
||||
"postcss-selector-parser": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz",
|
||||
"integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dot-prop": "^4.1.1",
|
||||
"indexes-of": "^1.0.1",
|
||||
"uniq": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"postcss-nesting": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-4.2.1.tgz",
|
||||
|
@ -12848,6 +12985,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"read-file-stdin": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/read-file-stdin/-/read-file-stdin-0.2.1.tgz",
|
||||
"integrity": "sha1-JezP86FTtoCa+ssj7hU4fbng7mE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"gather-stream": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
||||
|
@ -14348,6 +14494,12 @@
|
|||
"function-bind": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"string.prototype.repeat": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz",
|
||||
"integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=",
|
||||
"dev": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
|
@ -15080,6 +15232,48 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-0.6.6.tgz",
|
||||
"integrity": "sha512-g6xb7kcPIom85K7ak16AUBrwN3tPdhrQoKJ7Jl7OJ3zBOQNHthquZ1/q+0V6fj9jsC66jrDCQxn72DIjK4aYgg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.11.0",
|
||||
"css.escape": "^1.5.1",
|
||||
"fs-extra": "^4.0.2",
|
||||
"lodash": "^4.17.5",
|
||||
"perfectionist": "^2.4.0",
|
||||
"postcss": "^6.0.9",
|
||||
"postcss-functions": "^3.0.0",
|
||||
"postcss-js": "^1.0.1",
|
||||
"postcss-nested": "^3.0.0",
|
||||
"postcss-selector-parser": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
|
||||
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"postcss-selector-parser": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz",
|
||||
"integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dot-prop": "^4.1.1",
|
||||
"indexes-of": "^1.0.1",
|
||||
"uniq": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tapable": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz",
|
||||
|
@ -15941,6 +16135,12 @@
|
|||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"vendors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz",
|
||||
"integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
|
@ -16895,6 +17095,12 @@
|
|||
"signal-exit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"write-file-stdout": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/write-file-stdout/-/write-file-stdout-0.0.2.tgz",
|
||||
"integrity": "sha1-wlLXx8WxtAKJdjDjRTx7/mkNnKE=",
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
"stylelint-no-unsupported-browser-features": "^3.0.2",
|
||||
"svgo": "^1.1.1",
|
||||
"svgo-loader": "^2.2.0",
|
||||
"tailwindcss": "^0.6.6",
|
||||
"testpilot-ga": "^0.3.0",
|
||||
"val-loader": "^1.1.1",
|
||||
"wdio-docker-service": "^1.4.2",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const options = {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
tailwindcss: {},
|
||||
'postcss-cssnext': {},
|
||||
'css-mqpacker': {}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@ timespanMinutes = { $num ->
|
|||
[one] 1 minute
|
||||
*[other] { $num } minutes
|
||||
}
|
||||
timespanDays = { $num ->
|
||||
[one] 1 day
|
||||
*[other] { $num } days
|
||||
}
|
||||
timespanWeeks = { $num ->
|
||||
[one] 1 week
|
||||
*[other] { $num } weeks
|
||||
|
|
|
@ -9,7 +9,10 @@ async function getFxaConfig() {
|
|||
if (fxaConfig && Date.now() - lastConfigRefresh < 1000 * 60 * 5) {
|
||||
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();
|
||||
lastConfigRefresh = Date.now();
|
||||
return fxaConfig;
|
||||
|
|
|
@ -31,9 +31,13 @@ if (config.analytics_id) {
|
|||
module.exports = async function(req, res) {
|
||||
let authConfig = '';
|
||||
if (config.fxa_client_id) {
|
||||
const fxaConfig = await getFxaConfig();
|
||||
fxaConfig.client_id = config.fxa_client_id;
|
||||
authConfig = `var AUTH_CONFIG = ${JSON.stringify(fxaConfig)};`;
|
||||
try {
|
||||
const fxaConfig = await getFxaConfig();
|
||||
fxaConfig.client_id = config.fxa_client_id;
|
||||
authConfig = `var AUTH_CONFIG = ${JSON.stringify(fxaConfig)};`;
|
||||
} catch (e) {
|
||||
// continue without accounts
|
||||
}
|
||||
}
|
||||
/* eslint-disable no-useless-escape */
|
||||
const jsconfig = `
|
||||
|
|
|
@ -0,0 +1,920 @@
|
|||
/*
|
||||
|
||||
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': '#0f2f21',
|
||||
'green-darker': '#1a4731',
|
||||
'green-dark': '#1f9d55',
|
||||
green: '#38c172',
|
||||
'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': '#12283a',
|
||||
'blue-darker': '#1c3d5a',
|
||||
'blue-dark': '#2779bd',
|
||||
blue: '#3490dc',
|
||||
'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'
|
||||
};
|
||||
|
||||
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%',
|
||||
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'
|
||||
},
|
||||
|
||||
/*
|
||||
|-----------------------------------------------------------------------------
|
||||
| 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: ':'
|
||||
}
|
||||
};
|
Загрузка…
Ссылка в новой задаче