Compare commits

...

13 Commits

Author SHA1 Message Date
Danny Coates
490a1e88eb don't disable copy while setting password. fixes #638 2017-11-14 19:07:24 -08:00
Danny Coates
2f8a3c9904 Merge pull request #641 from mozilla/banners
Added experiment for firefox download promo
2017-11-14 18:59:21 -08:00
Danny Coates
e7fdf76120 Added experiment for firefox download promo 2017-11-14 12:24:54 -08:00
Sahithi
d0d41b743a Pontoon: Update Telugu (te) localization of Test Pilot: Firefox Send
Localization authors:
- Sahithi <sahithi.thinker@gmail.com>
2017-11-13 10:30:34 +00:00
Enol
a2995411d6 Pontoon: Update Asturian (ast) localization of Test Pilot: Firefox Send
Localization authors:
- Enol <enolp@softastur.org>
2017-11-11 16:30:55 +00:00
Merike Sell
3246c4a621 Pontoon: Update Estonian (et) localization of Test Pilot: Firefox Send
Localization authors:
- Merike Sell <merikes@gmail.com>
2017-11-11 14:10:36 +00:00
Juraj Cigáň
48faf929a4 Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2017-11-10 22:50:36 +00:00
Danny Coates
b7f922a999 Merge pull request #640 from mozilla/i586
use fluent-langneg for subtag support
2017-11-10 13:01:20 -08:00
Danny Coates
bfcdf9340d use fluent-langneg for subtag support 2017-11-10 12:40:18 -08:00
Danny Coates
4ed515f5a3 updated deps 2017-11-09 15:07:03 -08:00
Danny Coates
84b2737ffb Merge pull request #639 from mozilla/i586
wrap number localization in try/catch
2017-11-09 14:17:11 -08:00
Danny Coates
deabca5a94 wrap number localization in try/catch 2017-11-09 13:58:20 -08:00
صفا الفليج
e9a49e23e8 Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- صفا الفليج <safa1996alfulaij@gmail.com>
2017-11-09 16:31:40 +00:00
30 changed files with 1464 additions and 1203 deletions

View File

@@ -1,6 +1,28 @@
import hash from 'string-hash'; import hash from 'string-hash';
const experiments = {}; const experiments = {
'SyI-hI7gT9agiH-f3f0BYg': {
id: 'SyI-hI7gT9agiH-f3f0BYg',
run: function(variant, state, emitter) {
state.promo = variant === 1 ? 'body' : 'header';
emitter.emit('render');
},
eligible: function() {
return (
!/firefox/i.test(navigator.userAgent) &&
document.querySelector('html').lang === 'en-US'
);
},
variant: function(state) {
return this.luckyNumber(state) > 0.5 ? 1 : 0;
},
luckyNumber: function(state) {
return luckyNumber(
`${this.id}:${state.storage.get('testpilot_ga__cid')}`
);
}
}
};
//Returns a number between 0 and 1 //Returns a number between 0 and 1
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@@ -32,12 +54,12 @@ export default function initialize(state, emitter) {
checkExperiments(state, emitter); checkExperiments(state, emitter);
}); });
} else { } else {
const enrolled = state.storage.enrolled; const enrolled = state.storage.enrolled.filter(([id, variant]) => {
enrolled.forEach(([id, variant]) => {
const xp = experiments[id]; const xp = experiments[id];
if (xp) { if (xp) {
xp.run(variant, state, emitter); xp.run(variant, state, emitter);
} }
return !!xp;
}); });
// single experiment per session for now // single experiment per session for now
if (enrolled.length === 0) { if (enrolled.length === 0) {

View File

@@ -47,4 +47,4 @@ app.use(fileManager);
app.use(dragManager); app.use(dragManager);
app.use(experiments); app.use(experiments);
app.mount('#page-one'); app.mount('body');

View File

@@ -20,7 +20,7 @@ let experiment = null;
export default function initialize(state, emitter) { export default function initialize(state, emitter) {
appState = state; appState = state;
emitter.on('DOMContentLoaded', () => { emitter.on('DOMContentLoaded', () => {
addExitHandlers(); // addExitHandlers();
experiment = storage.enrolled[0]; experiment = storage.enrolled[0];
sendEvent(category(), 'visit', { sendEvent(category(), 'visit', {
cm5: storage.totalUploads, cm5: storage.totalUploads,
@@ -29,6 +29,9 @@ export default function initialize(state, emitter) {
}); });
//TODO restart handlers... somewhere //TODO restart handlers... somewhere
}); });
emitter.on('exit', evt => {
exitEvent(evt);
});
} }
function category() { function category() {
@@ -81,6 +84,8 @@ function urlToMetric(url) {
case 'https://testpilot.firefox.com/': case 'https://testpilot.firefox.com/':
case 'https://testpilot.firefox.com/experiments/send': case 'https://testpilot.firefox.com/experiments/send':
return 'testpilot'; return 'testpilot';
case 'https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com':
return 'promo';
default: default:
return 'other'; return 'other';
} }
@@ -244,6 +249,7 @@ function exitEvent(target) {
}); });
} }
// eslint-disable-next-line no-unused-vars
function addExitHandlers() { function addExitHandlers() {
const links = Array.from(document.querySelectorAll('a')); const links = Array.from(document.querySelectorAll('a'));
links.forEach(l => { links.forEach(l => {

View File

@@ -1,17 +1,43 @@
const choo = require('choo'); const choo = require('choo');
const html = require('choo/html');
const download = require('./download'); const download = require('./download');
const header = require('../templates/header');
const footer = require('../templates/footer');
const fxPromo = require('../templates/fxPromo');
const app = choo(); const app = choo();
app.route('/', require('./home')); function body(template) {
app.route('/share/:id', require('../templates/share')); return function(state, emit) {
app.route('/download/:id', download); const b = html`<body>
app.route('/download/:id/:key', download); ${state.promo === 'header' ? fxPromo(state, emit) : ''}
app.route('/completed', require('../templates/completed')); ${header(state)}
app.route('/unsupported/:reason', require('../templates/unsupported')); <div class="all">
app.route('/legal', require('../templates/legal')); <noscript>
app.route('/error', require('../templates/error')); <h2>Firefox Send requires JavaScript</h2>
app.route('/blank', require('../templates/blank')); <p><a href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-does-firefox-send-require-javascript">Why does Firefox Send require JavaScript?</a></p>
app.route('*', require('../templates/notFound')); <p>Please enable JavaScript and try again.</p>
</noscript>
${template(state, emit)}
</div>
${footer(state)}
</body>`;
if (state.layout) {
return state.layout(state, b);
}
return b;
};
}
app.route('/', body(require('./home')));
app.route('/share/:id', body(require('../templates/share')));
app.route('/download/:id', body(download));
app.route('/download/:id/:key', body(download));
app.route('/completed', body(require('../templates/completed')));
app.route('/unsupported/:reason', body(require('../templates/unsupported')));
app.route('/legal', body(require('../templates/legal')));
app.route('/error', body(require('../templates/error')));
app.route('/blank', body(require('../templates/blank')));
app.route('*', body(require('../templates/notFound')));
module.exports = app; module.exports = app;

View File

@@ -1,9 +1,6 @@
const html = require('choo/html'); const html = require('choo/html');
module.exports = function(state) { module.exports = function() {
const div = html`<div id="page-one"></div>`; const div = html`<div id="page-one"></div>`;
if (state.layout) {
return state.layout(state, div);
}
return div; return div;
}; };

View File

@@ -1,9 +1,11 @@
const html = require('choo/html'); const html = require('choo/html');
const progress = require('./progress'); const progress = require('./progress');
const { fadeOut } = require('../utils'); const { fadeOut } = require('../utils');
const fxPromo = require('./fxPromo');
module.exports = function(state, emit) { module.exports = function(state, emit) {
const div = html` const div = html`
<div id="page-one">
<div id="download" class="fadeIn"> <div id="download" class="fadeIn">
<div id="download-progress"> <div id="download-progress">
<div id="dl-title" class="title">${state.translate( <div id="dl-title" class="title">${state.translate(
@@ -15,9 +17,11 @@ module.exports = function(state, emit) {
<div class="progress-text"></div> <div class="progress-text"></div>
</div> </div>
</div> </div>
<a class="send-new" data-state="completed" href="/" onclick=${sendNew}>${state.translate( <a class="send-new" data-state="completed" href="/" onclick=${
'sendYourFilesLink' sendNew
)}</a> }>${state.translate('sendYourFilesLink')}</a>
</div>
${state.promo === 'body' ? fxPromo(state, emit) : ''}
</div> </div>
`; `;

View File

@@ -1,10 +1,12 @@
const html = require('choo/html'); const html = require('choo/html');
const progress = require('./progress'); const progress = require('./progress');
const { bytes } = require('../utils'); const { bytes } = require('../utils');
const fxPromo = require('./fxPromo');
module.exports = function(state) { module.exports = function(state, emit) {
const transfer = state.transfer; const transfer = state.transfer;
const div = html` const div = html`
<div id="page-one">
<div id="download-progress" class="fadeIn"> <div id="download-progress" class="fadeIn">
<div id="dl-title" class="title">${state.translate( <div id="dl-title" class="title">${state.translate(
'downloadingPageProgress', 'downloadingPageProgress',
@@ -21,6 +23,8 @@ module.exports = function(state) {
transfer.sizes transfer.sizes
)}</div> )}</div>
</div> </div>
</div>
${state.promo === 'body' ? fxPromo(state, emit) : ''}
</div> </div>
`; `;

31
app/templates/footer.js Normal file
View File

@@ -0,0 +1,31 @@
const html = require('choo/html');
const assets = require('../../common/assets');
module.exports = function(state) {
return html`<div class="footer">
<div class="legal-links">
<a href="https://www.mozilla.org" role="presentation"><img class="mozilla-logo" src="${assets.get(
'mozilla-logo.svg'
)}" alt="mozilla"/></a>
<a href="https://www.mozilla.org/about/legal">${state.translate(
'footerLinkLegal'
)}</a>
<a href="https://testpilot.firefox.com/about">${state.translate(
'footerLinkAbout'
)}</a>
<a href="/legal">${state.translate('footerLinkPrivacy')}</a>
<a href="/legal">${state.translate('footerLinkTerms')}</a>
<a href="https://www.mozilla.org/privacy/websites/#cookies">${state.translate(
'footerLinkCookies'
)}</a>
</div>
<div class="social-links">
<a href="https://github.com/mozilla/send" role="presentation"><img class="github" src="${assets.get(
'github-icon.svg'
)}" alt="github"/></a>
<a href="https://twitter.com/FxTestPilot" role="presentation"><img class="twitter" src="${assets.get(
'twitter-icon.svg'
)}" alt="twitter"/></a>
</div>
</div>`;
};

44
app/templates/fxPromo.js Normal file
View File

@@ -0,0 +1,44 @@
const html = require('choo/html');
const assets = require('../../common/assets');
// function replaceLinks(str, urls) {
// let i = -1;
// const s = str.replace(/<a>([^<]+)<\/a>/g, (m, v) => {
// i++;
// return `<a class="link" href="${urls[i]}">${v}</a>`;
// });
// return [`<span>${s}</span>`];
// }
module.exports = function(state, emit) {
// function close() {
// document.querySelector('.banner').remove();
// }
function clicked(evt) {
emit('exit', evt);
}
return html`
<div class="banner">
<div>
<img
src="${assets.get('firefox_logo-only.svg')}"
class="firefox-logo-small"
alt="Firefox"/>
<span>Send is brought to you by the all-new Firefox.
<a
class="link"
href="https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com"
onclick=${clicked}
>Download Firefox now ≫</a></span>
</div>
</div>`;
};
/*
<img
src="${assets.get('close-16.svg')}"
class="icon-delete"
onclick=${close}>
*/

21
app/templates/header.js Normal file
View File

@@ -0,0 +1,21 @@
const html = require('choo/html');
const assets = require('../../common/assets');
module.exports = function(state) {
return html`<header class="header">
<div class="send-logo">
<a href="/">
<img src="${assets.get(
'send_logo.svg'
)}" alt="Send"/><h1 class="site-title">Send</h1>
</a>
<div class="site-subtitle">
<a href="https://testpilot.firefox.com">Firefox Test Pilot</a>
<div>${state.translate('siteSubtitle')}</div>
</div>
</div>
<a href="https://qsurvey.mozilla.com/s3/txp-firefox-send" rel="noreferrer noopener" class="feedback" target="_blank">${state.translate(
'siteFeedback'
)}</a>
</header>`;
};

View File

@@ -30,9 +30,5 @@ module.exports = function(state) {
</div> </div>
</div> </div>
`; `;
if (state.layout) {
return state.layout(state, div);
}
return div; return div;
}; };

View File

@@ -17,9 +17,5 @@ module.exports = function(state) {
)}</a> )}</a>
</div> </div>
</div>`; </div>`;
if (state.layout) {
return state.layout(state, div);
}
return div; return div;
}; };

View File

@@ -3,6 +3,7 @@ const assets = require('../../common/assets');
const notFound = require('./notFound'); const notFound = require('./notFound');
const downloadPassword = require('./downloadPassword'); const downloadPassword = require('./downloadPassword');
const { bytes } = require('../utils'); const { bytes } = require('../utils');
const fxPromo = require('./fxPromo');
function getFileFromDOM() { function getFileFromDOM() {
const el = document.getElementById('dl-file'); const el = document.getElementById('dl-file');
@@ -61,6 +62,7 @@ module.exports = function(state, emit) {
</div> </div>
<a class="send-new" href="/">${state.translate('sendYourFilesLink')}</a> <a class="send-new" href="/">${state.translate('sendYourFilesLink')}</a>
</div> </div>
${state.promo === 'body' ? fxPromo(state, emit) : ''}
</div> </div>
`; `;
@@ -68,9 +70,5 @@ module.exports = function(state, emit) {
event.preventDefault(); event.preventDefault();
emit('download', fileInfo); emit('download', fileInfo);
} }
if (state.layout) {
return state.layout(state, div);
}
return div; return div;
}; };

View File

@@ -10,10 +10,18 @@ module.exports = function(progressRatio) {
const percent = Math.floor(progressRatio * 100); const percent = Math.floor(progressRatio * 100);
const div = html` const div = html`
<div class="progress-bar"> <div class="progress-bar">
<svg id="progress" width="${oDiameter}" height="${oDiameter}" viewPort="0 0 ${oDiameter} ${oDiameter}" version="1.1"> <svg id="progress" width="${oDiameter}" height="${
oDiameter
}" viewPort="0 0 ${oDiameter} ${oDiameter}" version="1.1">
<circle r="${radius}" cx="${oRadius}" cy="${oRadius}" fill="transparent"/> <circle r="${radius}" cx="${oRadius}" cy="${oRadius}" fill="transparent"/>
<circle id="bar" r="${radius}" cx="${oRadius}" cy="${oRadius}" fill="transparent" transform="rotate(-90 ${oRadius} ${oRadius})" stroke-dasharray="${circumference}" stroke-dashoffset="${dashOffset}"/> <circle id="bar" r="${radius}" cx="${oRadius}" cy="${
<text class="percentage" text-anchor="middle" x="50%" y="98"><tspan class="percent-number">${percent}</tspan><tspan class="percent-sign">%</tspan></text> oRadius
}" fill="transparent" transform="rotate(-90 ${oRadius} ${
oRadius
})" stroke-dasharray="${circumference}" stroke-dashoffset="${dashOffset}"/>
<text class="percentage" text-anchor="middle" x="50%" y="98"><tspan class="percent-number">${
percent
}</tspan><tspan class="percent-sign">%</tspan></text>
</svg> </svg>
</div> </div>
`; `;

View File

@@ -42,9 +42,5 @@ module.exports = function(state) {
)}</div> )}</div>
</div>`; </div>`;
const div = html`<div id="page-one">${msg}</div>`; const div = html`<div id="page-one">${msg}</div>`;
if (state.layout) {
return state.layout(state, div);
}
return div; return div;
}; };

View File

@@ -5,7 +5,9 @@ module.exports = function(state, emit) {
const div = html` const div = html`
<div class="selectPassword"> <div class="selectPassword">
<div id="addPasswordWrapper"> <div id="addPasswordWrapper">
<input id="addPassword" type="checkbox" autocomplete="off" onchange=${togglePasswordInput}/> <input id="addPassword" type="checkbox" autocomplete="off" onchange=${
togglePasswordInput
}/>
<label for="addPassword"> <label for="addPassword">
${state.translate('requirePasswordCheckbox')}</label> ${state.translate('requirePasswordCheckbox')}</label>
</div> </div>
@@ -41,10 +43,6 @@ module.exports = function(state, emit) {
document document
.querySelector('.setPassword') .querySelector('.setPassword')
.classList.toggle('hidden', !boxChecked); .classList.toggle('hidden', !boxChecked);
document
.getElementById('copy')
.classList.toggle('wait-password', boxChecked);
document.getElementById('copy-btn').disabled = boxChecked;
if (boxChecked) { if (boxChecked) {
unlockInput.focus(); unlockInput.focus();
} else { } else {

View File

@@ -1,6 +1,7 @@
const html = require('choo/html'); const html = require('choo/html');
const assets = require('../../common/assets'); const assets = require('../../common/assets');
const fileList = require('./fileList'); const fileList = require('./fileList');
const fxPromo = require('./fxPromo');
const { fadeOut } = require('../utils'); const { fadeOut } = require('../utils');
module.exports = function(state, emit) { module.exports = function(state, emit) {
@@ -35,6 +36,7 @@ module.exports = function(state, emit) {
title="${state.translate('uploadPageBrowseButton1')}"> title="${state.translate('uploadPageBrowseButton1')}">
${state.translate('uploadPageBrowseButton1')}</label> ${state.translate('uploadPageBrowseButton1')}</label>
</div> </div>
${state.promo === 'body' ? fxPromo(state, emit) : ''}
${fileList(state, emit)} ${fileList(state, emit)}
</div> </div>
`; `;
@@ -67,9 +69,5 @@ module.exports = function(state, emit) {
await fadeOut('page-one'); await fadeOut('page-one');
emit('upload', { file, type: 'click' }); emit('upload', { file, type: 'click' });
} }
if (state.layout) {
return state.layout(state, div);
}
return div; return div;
}; };

View File

@@ -105,19 +105,31 @@ function bytes(num) {
} }
const exponent = Math.min(Math.floor(Math.log10(num) / 3), UNITS.length - 1); const exponent = Math.min(Math.floor(Math.log10(num) / 3), UNITS.length - 1);
const n = Number(num / Math.pow(1000, exponent)); const n = Number(num / Math.pow(1000, exponent));
const nStr = LOCALIZE_NUMBERS let nStr = n.toFixed(1);
? n.toLocaleString(navigator.languages, { if (LOCALIZE_NUMBERS) {
try {
const locale = document.querySelector('html').lang;
nStr = n.toLocaleString(locale, {
minimumFractionDigits: 1, minimumFractionDigits: 1,
maximumFractionDigits: 1 maximumFractionDigits: 1
}) });
: n.toFixed(1); } catch (e) {
// fall through
}
}
return `${nStr}${UNITS[exponent]}`; return `${nStr}${UNITS[exponent]}`;
} }
function percent(ratio) { function percent(ratio) {
return LOCALIZE_NUMBERS if (LOCALIZE_NUMBERS) {
? ratio.toLocaleString(navigator.languages, { style: 'percent' }) try {
: `${Math.floor(ratio * 100)}%`; const locale = document.querySelector('html').lang;
return ratio.toLocaleString(locale, { style: 'percent' });
} catch (e) {
// fall through
}
}
return `${Math.floor(ratio * 100)}%`;
} }
function allowedCopy() { function allowedCopy() {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -8,7 +8,6 @@ html {
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center top; background-position: center top;
height: 100%; height: 100%;
max-width: 1440px;
margin: auto; margin: auto;
} }
@@ -130,7 +129,7 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
max-width: 630px; max-width: 650px;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 20px;
box-sizing: border-box; box-sizing: border-box;
@@ -213,7 +212,7 @@ a {
.upload-window { .upload-window {
border: 3px dashed rgba(0, 148, 251, 0.5); border: 3px dashed rgba(0, 148, 251, 0.5);
margin: 0 auto; margin: 0 auto 10px;
height: 255px; height: 255px;
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
@@ -709,6 +708,10 @@ tbody {
width: 70px; width: 70px;
} }
.firefox-logo-small {
width: 24px;
}
#dl-firefox, #dl-firefox,
#update-firefox { #update-firefox {
margin-bottom: 181px; margin-bottom: 181px;
@@ -766,7 +769,7 @@ tbody {
} }
#download { #download {
margin: 0 auto; margin: 0 auto 30px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -955,6 +958,29 @@ tbody {
background-position: 2px 1px; background-position: 2px 1px;
} }
.banner {
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;
}
.banner > div {
display: flex;
align-items: center;
margin: 0 auto;
}
.banner > div > span {
margin-left: 10px;
}
@media (max-device-width: 992px), (max-width: 992px) { @media (max-device-width: 992px), (max-width: 992px) {
.popup .popuptext { .popup .popuptext {
left: auto; left: auto;

2200
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,66 +43,66 @@
"node": ">=8.2.0" "node": ">=8.2.0"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^7.1.5", "autoprefixer": "^7.1.6",
"babel-core": "^6.26.0", "babel-core": "^6.26.0",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.2",
"babel-plugin-yo-yoify": "^1.0.1", "babel-plugin-yo-yoify": "^1.0.1",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.24.1", "babel-preset-stage-2": "^6.24.1",
"base64-js": "^1.2.1", "base64-js": "^1.2.1",
"copy-webpack-plugin": "^4.1.1", "copy-webpack-plugin": "^4.2.0",
"cross-env": "^5.0.5", "cross-env": "^5.1.1",
"css-loader": "^0.28.7", "css-loader": "^0.28.7",
"css-mqpacker": "^6.0.1", "css-mqpacker": "^6.0.1",
"cssnano": "^3.10.0", "cssnano": "^3.10.0",
"eslint": "^4.8.0", "eslint": "^4.10.0",
"eslint-plugin-mocha": "^4.11.0", "eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-node": "^5.2.0", "eslint-plugin-node": "^5.2.1",
"eslint-plugin-security": "^1.4.0", "eslint-plugin-security": "^1.4.0",
"expose-loader": "^0.7.3", "expose-loader": "^0.7.3",
"extract-loader": "^1.0.1", "extract-loader": "^1.0.1",
"file-loader": "^1.1.5", "file-loader": "^1.1.5",
"git-rev-sync": "^1.9.1", "git-rev-sync": "^1.9.1",
"github-changes": "^1.1.0", "github-changes": "^1.1.1",
"html-loader": "^0.5.1", "html-loader": "^0.5.1",
"husky": "^0.14.3", "husky": "^0.14.3",
"lint-staged": "^4.2.3", "lint-staged": "^4.3.0",
"mocha": "^3.5.3", "mocha": "^3.5.3",
"nanobus": "^4.2.0", "nanobus": "^4.3.0",
"npm-run-all": "^4.1.1", "npm-run-all": "^4.1.2",
"postcss-loader": "^2.0.6", "postcss-loader": "^2.0.8",
"prettier": "^1.7.4", "prettier": "^1.8.2",
"proxyquire": "^1.8.0", "proxyquire": "^1.8.0",
"raven-js": "^3.18.1", "raven-js": "^3.19.1",
"redis-mock": "^0.20.0", "redis-mock": "^0.20.0",
"require-from-string": "^2.0.1", "require-from-string": "^2.0.1",
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"selenium-webdriver": "^3.6.0", "selenium-webdriver": "^3.6.0",
"sinon": "^4.0.1", "sinon": "^4.1.2",
"string-hash": "^1.1.3", "string-hash": "^1.1.3",
"stylelint-config-standard": "^17.0.0", "stylelint-config-standard": "^17.0.0",
"stylelint-no-unsupported-browser-features": "^1.0.1", "stylelint-no-unsupported-browser-features": "^1.0.1",
"supertest": "^3.0.0", "supertest": "^3.0.0",
"testpilot-ga": "^0.3.0", "testpilot-ga": "^0.3.0",
"val-loader": "^1.0.2", "val-loader": "^1.0.2",
"webpack": "^3.6.0", "webpack": "^3.8.1",
"webpack-dev-server": "^2.9.1", "webpack-dev-server": "2.9.1",
"webpack-manifest-plugin": "^1.3.2", "webpack-manifest-plugin": "^1.3.2",
"webpack-unassert-loader": "^1.2.0" "webpack-unassert-loader": "^1.2.0"
}, },
"dependencies": { "dependencies": {
"aws-sdk": "^2.130.0", "aws-sdk": "^2.149.0",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
"choo": "^6.4.2", "choo": "^6.5.1",
"cldr-core": "^32.0.0",
"connect-busboy": "0.0.2", "connect-busboy": "0.0.2",
"convict": "^4.0.1", "convict": "^4.0.1",
"express": "^4.16.2", "express": "^4.16.2",
"express-request-language": "^1.1.12",
"fluent": "^0.4.1", "fluent": "^0.4.1",
"fluent-langneg": "^0.1.0", "fluent-langneg": "^0.1.0",
"helmet": "^3.8.2", "helmet": "^3.9.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"mozlog": "^2.1.1", "mozlog": "^2.1.1",
"raven": "^2.2.1", "raven": "^2.2.1",
@@ -112,6 +112,7 @@
"en-US", "en-US",
"ast", "ast",
"az", "az",
"bs",
"ca", "ca",
"cak", "cak",
"cs", "cs",
@@ -132,6 +133,7 @@
"id", "id",
"it", "it",
"ja", "ja",
"ka",
"kab", "kab",
"ko", "ko",
"ms", "ms",
@@ -145,6 +147,7 @@
"sl", "sl",
"sr", "sr",
"sv-SE", "sv-SE",
"tl",
"tr", "tr",
"uk", "uk",
"vi", "vi",

View File

@@ -76,3 +76,5 @@ footerLinkTerms = الشروط
footerLinkCookies = الكعكات footerLinkCookies = الكعكات
requirePasswordCheckbox = اطلب كلمة سر لتنزيل هذا الملف requirePasswordCheckbox = اطلب كلمة سر لتنزيل هذا الملف
addPasswordButton = أضِف كلمة سر addPasswordButton = أضِف كلمة سر
// This label is followed by the password needed to download a file
passwordResult = كلمة السر: { $password }

View File

@@ -87,3 +87,5 @@ footerLinkCookies = Cookies
requirePasswordCheckbox = Riquir una contraseña pa baxar esti ficheru requirePasswordCheckbox = Riquir una contraseña pa baxar esti ficheru
addPasswordButton = Amestar contraseña addPasswordButton = Amestar contraseña
passwordTryAgain = Contraseña incorreuta. Volvi tentalo. passwordTryAgain = Contraseña incorreuta. Volvi tentalo.
// This label is followed by the password needed to download a file
passwordResult = Contraseña: { $password }

View File

@@ -87,3 +87,5 @@ footerLinkCookies = Küpsistest
requirePasswordCheckbox = Selle faili allalaadimiseks nõutakse parooli requirePasswordCheckbox = Selle faili allalaadimiseks nõutakse parooli
addPasswordButton = Lisa parool addPasswordButton = Lisa parool
passwordTryAgain = Vale parool. Palun proovi uuesti. passwordTryAgain = Vale parool. Palun proovi uuesti.
// This label is followed by the password needed to download a file
passwordResult = Parool: { $password }

View File

@@ -87,3 +87,5 @@ footerLinkCookies = Cookies
requirePasswordCheckbox = Pri preberaní súboru vyžadovať heslo requirePasswordCheckbox = Pri preberaní súboru vyžadovať heslo
addPasswordButton = Pridať heslo addPasswordButton = Pridať heslo
passwordTryAgain = Nesprávne heslo. Skúste to znova. passwordTryAgain = Nesprávne heslo. Skúste to znova.
// This label is followed by the password needed to download a file
passwordResult = Heslo: { $password }

View File

@@ -31,6 +31,9 @@ sendAnotherFileLink = మరో ఫైలును పంపండి
downloadAltText = దిగుమతి downloadAltText = దిగుమతి
downloadFileName = దిగుమతి { $filename } downloadFileName = దిగుమతి { $filename }
downloadFileSize = ({ $size }) downloadFileSize = ({ $size })
unlockInputLabel = సంకేతపదాన్ని తెలపండి
unlockInputPlaceholder = సంకేతపదం
unlockButtonLabel = తాళం తీయి
// Text and title used on the download link/button (indicates an action). // Text and title used on the download link/button (indicates an action).
downloadButtonLabel = దిగుమతి downloadButtonLabel = దిగుమతి
downloadNotification = మీ దిగుమతి పూర్తయ్యింది. downloadNotification = మీ దిగుమతి పూర్తయ్యింది.
@@ -69,3 +72,8 @@ footerLinkAbout = టెస్ట్ పైలట్ గురించి
footerLinkPrivacy = గోప్యత footerLinkPrivacy = గోప్యత
footerLinkTerms = నియమాలు footerLinkTerms = నియమాలు
footerLinkCookies = కుకీలు footerLinkCookies = కుకీలు
requirePasswordCheckbox = ఈ ఫైల్ను దింపుకోటానికి సంకేతపదం అవసరం
addPasswordButton = సంకేతపదం జోడించండి
passwordTryAgain = సరికాని సంకేతపదం. మళ్ళీ ప్రయత్నించండి.
// This label is followed by the password needed to download a file
passwordResult = సంకేతపదం: { $password }

View File

@@ -4,11 +4,9 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
function allLangs() { function allLangs() {
const langs = fs.readdirSync( return fs.readdirSync(
path.join(__dirname, '..', 'dist', 'public', 'locales') path.join(__dirname, '..', 'dist', 'public', 'locales')
); );
langs.unshift('en-US'); // default first, TODO change for fluent-langneg
return langs;
} }
if (config.l10n_dev) { if (config.l10n_dev) {

View File

@@ -8,7 +8,7 @@ module.exports = function(state, body = '') {
: ''; : '';
return html` return html`
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="${state.locale}">
<head> <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -71,58 +71,7 @@ module.exports = function(state, body = '') {
<script defer src="${locales.get(state.locale)}"></script> <script defer src="${locales.get(state.locale)}"></script>
<script defer src="${assets.get('app.js')}"></script> <script defer src="${assets.get('app.js')}"></script>
</head> </head>
<body> ${body}
<header class="header">
<div class="send-logo">
<a href="/">
<img src="${assets.get(
'send_logo.svg'
)}" alt="Send"/><h1 class="site-title">Send</h1>
</a>
<div class="site-subtitle">
<a href="https://testpilot.firefox.com">Firefox Test Pilot</a>
<div>${state.translate('siteSubtitle')}</div>
</div>
</div>
<a href="https://qsurvey.mozilla.com/s3/txp-firefox-send" rel="noreferrer noopener" class="feedback" target="_blank">${state.translate(
'siteFeedback'
)}</a>
</header>
<div class="all">
<noscript>
<h2>Firefox Send requires JavaScript</h2>
<p><a href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-does-firefox-send-require-javascript">Why does Firefox Send require JavaScript?</a></p>
<p>Please enable JavaScript and try again.</p>
</noscript>
${body}
</div>
<div class="footer">
<div class="legal-links">
<a href="https://www.mozilla.org" role="presentation"><img class="mozilla-logo" src="${assets.get(
'mozilla-logo.svg'
)}" alt="mozilla"/></a>
<a href="https://www.mozilla.org/about/legal">${state.translate(
'footerLinkLegal'
)}</a>
<a href="https://testpilot.firefox.com/about">${state.translate(
'footerLinkAbout'
)}</a>
<a href="/legal">${state.translate('footerLinkPrivacy')}</a>
<a href="/legal">${state.translate('footerLinkTerms')}</a>
<a href="https://www.mozilla.org/privacy/websites/#cookies">${state.translate(
'footerLinkCookies'
)}</a>
</div>
<div class="social-links">
<a href="https://github.com/mozilla/send" role="presentation"><img class="github" src="${assets.get(
'github-icon.svg'
)}" alt="github"/></a>
<a href="https://twitter.com/FxTestPilot" role="presentation"><img class="twitter" src="${assets.get(
'twitter-icon.svg'
)}" alt="twitter"/></a>
</div>
</div>
</body>
</html> </html>
`; `;
}; };

View File

@@ -1,20 +1,40 @@
const busboy = require('connect-busboy'); const busboy = require('connect-busboy');
const helmet = require('helmet'); const helmet = require('helmet');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const requestLanguage = require('express-request-language');
const languages = require('../languages'); const languages = require('../languages');
const storage = require('../storage'); const storage = require('../storage');
const config = require('../config'); const config = require('../config');
const pages = require('./pages'); const pages = require('./pages');
// const lang = require('fluent-langneg') const { negotiateLanguages } = require('fluent-langneg');
const IS_DEV = config.env === 'development'; const IS_DEV = config.env === 'development';
const acceptLanguages = /(([a-zA-Z]+(-[a-zA-Z0-9]+){0,2})|\*)(;q=[0-1](\.[0-9]+)?)?/g;
const langData = require('cldr-core/supplemental/likelySubtags.json');
module.exports = function(app) { module.exports = function(app) {
app.use( app.use(function(req, res, next) {
requestLanguage({ const header = req.headers['accept-language'] || 'en-US';
languages if (header.length > 255) {
}) req.language = 'en-US';
); return next();
}
const langs = header.replace(/\s/g, '').match(acceptLanguages);
const preferred = langs
.map(l => {
const parts = l.split(';');
return {
locale: parts[0],
q: parts[1] ? parseFloat(parts[1].split('=')[1]) : 1
};
})
.sort((a, b) => b.q - a.q)
.map(x => x.locale);
req.language = negotiateLanguages(preferred, languages, {
strategy: 'lookup',
likelySubtags: langData.supplemental.likelySubtags,
defaultLocale: 'en-US'
})[0];
next();
});
app.use(helmet()); app.use(helmet());
app.use( app.use(
helmet.hsts({ helmet.hsts({