diff --git a/app/capabilities.js b/app/capabilities.js index 6e3af90f..3367540d 100644 --- a/app/capabilities.js +++ b/app/capabilities.js @@ -1,5 +1,5 @@ -/* global AUTH_CONFIG LOCALE */ -import { browserName } from './utils'; +/* global AUTH_CONFIG */ +import { browserName, locale } from './utils'; async function checkCrypto() { try { @@ -91,7 +91,7 @@ export default async function getCapabilities() { account = false; } const share = - typeof navigator.share === 'function' && LOCALE.startsWith('en'); // en until strings merge + typeof navigator.share === 'function' && locale().startsWith('en'); // en until strings merge const standalone = window.matchMedia('(display-mode: standalone)').matches || diff --git a/app/controller.js b/app/controller.js index 47ec3ecc..0fe285d7 100644 --- a/app/controller.js +++ b/app/controller.js @@ -2,11 +2,12 @@ import FileSender from './fileSender'; import FileReceiver from './fileReceiver'; import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils'; import * as metrics from './metrics'; -import { bytes } from './utils'; +import { bytes, locale } from './utils'; import okDialog from './ui/okDialog'; import copyDialog from './ui/copyDialog'; import shareDialog from './ui/shareDialog'; import signupDialog from './ui/signupDialog'; +import surveyDialog from './ui/surveyDialog'; export default function(state, emitter) { let lastRender = 0; @@ -281,6 +282,22 @@ export default function(state, emitter) { // metrics.copiedLink({ location }); }); + emitter.on('closeModal', () => { + if ( + state.PREFS.surveyUrl && + ['copy', 'share'].includes(state.modal.type) && + locale().startsWith('en') && + (state.storage.totalUploads > 1 || state.storage.totalDownloads > 0) && + !state.user.surveyed + ) { + state.user.surveyed = true; + state.modal = surveyDialog(); + } else { + state.modal = null; + } + render(); + }); + setInterval(() => { // poll for updates of the upload list if (!state.modal && state.route === '/') { diff --git a/app/main.js b/app/main.js index 33a89c04..40b3af57 100644 --- a/app/main.js +++ b/app/main.js @@ -1,4 +1,4 @@ -/* global DEFAULTS LIMITS LOCALE */ +/* global DEFAULTS LIMITS PREFS */ import 'core-js'; import 'fast-text-encoding'; // MS Edge support import 'fluent-intl-polyfill'; @@ -17,7 +17,7 @@ import './main.css'; import User from './user'; import { getTranslator } from './locale'; import Archive from './archive'; -import { setTranslate } from './utils'; +import { setTranslate, locale } from './utils'; if (navigator.doNotTrack !== '1' && window.RAVEN_CONFIG) { Raven.config(window.SENTRY_ID, window.RAVEN_CONFIG).install(); @@ -45,11 +45,12 @@ if (process.env.NODE_ENV === 'production') { } } - const translate = await getTranslator(LOCALE); + const translate = await getTranslator(locale()); setTranslate(translate); window.initialState = { LIMITS, DEFAULTS, + PREFS, archive: new Archive([], DEFAULTS.EXPIRE_SECONDS), capabilities, translate, diff --git a/app/metrics.js b/app/metrics.js index c256bf85..f348a4bd 100644 --- a/app/metrics.js +++ b/app/metrics.js @@ -1,5 +1,5 @@ import storage from './storage'; -import { platform } from './utils'; +import { platform, locale } from './utils'; import { sendMetrics } from './api'; let appState = null; @@ -7,7 +7,7 @@ let appState = null; const HOUR = 1000 * 60 * 60; const events = []; let session_id = Date.now(); -const lang = document.querySelector('html').lang; +const lang = locale(); export default function initialize(state, emitter) { appState = state; diff --git a/app/ui/copyDialog.js b/app/ui/copyDialog.js index 77bff125..58c3cff9 100644 --- a/app/ui/copyDialog.js +++ b/app/ui/copyDialog.js @@ -2,7 +2,7 @@ const html = require('choo/html'); const { copyToClipboard } = require('../utils'); module.exports = function(name, url) { - return function(state, emit, close) { + const dialog = function(state, emit, close) { return html`

- Share the link to your file:
+ ${state.translate('shareLinkDescription')}
${name}

- Share link + ${state.translate('shareLinkButton')} + + `; + }; +}; diff --git a/app/user.js b/app/user.js index 4bf32bbc..c4303941 100644 --- a/app/user.js +++ b/app/user.js @@ -44,6 +44,14 @@ export default class User { this.storage.set('firstAction', action); } + get surveyed() { + return this.storage.get('surveyed'); + } + + set surveyed(yes) { + this.storage.set('surveyed', yes); + } + get avatar() { const defaultAvatar = assets.get('user.svg'); if (this.info.avatarDefault) { diff --git a/app/utils.js b/app/utils.js index feaa80ce..65a17262 100644 --- a/app/utils.js +++ b/app/utils.js @@ -14,6 +14,10 @@ function b64ToArray(str) { return b64.toByteArray(str + '==='.slice((str.length + 3) % 4)); } +function locale() { + return document.querySelector('html').lang; +} + function loadShim(polyfill) { return new Promise((resolve, reject) => { const shim = document.createElement('script'); @@ -67,8 +71,7 @@ function bytes(num) { let nStr = n.toFixed(decimalDigits); if (LOCALIZE_NUMBERS) { try { - const locale = document.querySelector('html').lang; - nStr = n.toLocaleString(locale, { + nStr = n.toLocaleString(locale(), { minimumFractionDigits: decimalDigits, maximumFractionDigits: decimalDigits }); @@ -85,8 +88,7 @@ function bytes(num) { function percent(ratio) { if (LOCALIZE_NUMBERS) { try { - const locale = document.querySelector('html').lang; - return ratio.toLocaleString(locale, { style: 'percent' }); + return ratio.toLocaleString(locale(), { style: 'percent' }); } catch (e) { // fall through } @@ -96,8 +98,7 @@ function percent(ratio) { function number(n) { if (LOCALIZE_NUMBERS) { - const locale = document.querySelector('html').lang; - return n.toLocaleString(locale); + return n.toLocaleString(locale()); } return n.toString(); } @@ -267,6 +268,7 @@ function setTranslate(t) { } module.exports = { + locale, fadeOut, delay, allowedCopy, diff --git a/public/locales/en-US/send.ftl b/public/locales/en-US/send.ftl index 96a74cce..e0caef15 100644 --- a/public/locales/en-US/send.ftl +++ b/public/locales/en-US/send.ftl @@ -138,3 +138,8 @@ noStreamsOptionCopy = Copy the link to open in another browser noStreamsOptionFirefox = Try our favorite browser noStreamsOptionDownload = Continue with this browser downloadFirefoxPromo = { -send-short-brand } is brought to you by the all-new { -firefox }. +# the next line after the colon contains a file name +shareLinkDescription = Share the link to your file: +shareLinkButton = Share link +# $name is the name of the file +shareMessage = Download “{ $name }” with { -send-brand }: simple, safe file sharing diff --git a/server/config.js b/server/config.js index 2eec44a8..55cefc62 100644 --- a/server/config.js +++ b/server/config.js @@ -144,6 +144,11 @@ const conf = convict({ format: String, default: 'https://identity.mozilla.com/apps/send', env: 'FXA_KEY_SCOPE' + }, + survey_url: { + format: String, + default: '', + env: 'SURVEY_URL' } }); diff --git a/server/initScript.js b/server/initScript.js index 67cc29f5..e76f931a 100644 --- a/server/initScript.js +++ b/server/initScript.js @@ -47,8 +47,8 @@ module.exports = function(state) { var LIMITS = ${JSON.stringify(clientConstants.LIMITS)}; var DEFAULTS = ${JSON.stringify(clientConstants.DEFAULTS)}; - const LOCALE = '${state.locale}'; - const downloadMetadata = ${ + var PREFS = ${JSON.stringify(state.prefs)}; + var downloadMetadata = ${ state.downloadMetadata ? raw(JSON.stringify(state.downloadMetadata)) : '{}' }; ${authConfig}; diff --git a/server/state.js b/server/state.js index f2da0175..6947a721 100644 --- a/server/state.js +++ b/server/state.js @@ -19,6 +19,10 @@ module.exports = async function(req) { // continue without accounts } } + const prefs = {}; + if (config.survey_url) { + prefs.surveyUrl = config.survey_url; + } return { archive: { numFiles: 0 @@ -39,6 +43,7 @@ module.exports = async function(req) { user: { avatar: assets.get('user.svg'), loggedIn: false }, robots, authConfig, + prefs, layout }; };