Compare commits

..

333 Commits

Author SHA1 Message Date
Danny Coates
7013f5cf80 v2.6.1 2018-11-14 16:58:45 -08:00
Danny Coates
6184a70ba4 fixes #1005
The upstream gcp aggressively closes the connection once it has
received Content-Length bytes. However the @google-cloud/storage
module doesn't handle this well and emits no event in this case.
We were setting Content-Length because it's slightly more
efficient and was important for our download progress
bar (not anymore). The download should function fine without
setting the Content-Length, and allows the storage stream to finish
before closing the upstream socket.
2018-11-14 16:38:46 -08:00
Rodrigo
e264d0da62 Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-11-08 22:54:41 +00:00
Anesu Chiodza
23a6e338e8 Pontoon: Update Shona (sn) localization of Test Pilot: Firefox Send
Localization authors:
- Anesu Chiodza <anesuchiodza@gmail.com>
2018-11-08 18:13:26 +00:00
Anesu Chiodza
2c1dfdbe07 Pontoon: Update Shona (sn) localization of Test Pilot: Firefox Send
Localization authors:
- Anesu Chiodza <anesuchiodza@gmail.com>
2018-11-08 17:53:30 +00:00
pyup.io bot
994e77a38b Scheduled weekly dependency update for week 44 (#996)
* Update selenium from 3.14.1 to 3.141.0

* Update pytest from 3.9.3 to 3.10.0

* Update pytest-xdist from 1.23.2 to 1.24.0
2018-11-07 15:20:00 +01:00
Danny Coates
173ca461a9 bump version to v2.6.0 2018-11-06 11:49:27 -08:00
Danny Coates
53426b950a added gcs 2018-11-02 14:24:10 -07:00
pyup.io bot
9bb36cd827 Scheduled weekly dependency update for week 43 (#978)
* Update selenium from 3.14.0 to 3.14.1

* Update flake8 from 3.5.0 to 3.6.0

* Update pypom from 2.1.0 to 2.2.0

* Update pytest from 3.8.0 to 3.9.3

* Update pytest-xdist from 1.23.0 to 1.23.2
2018-10-29 14:11:02 -04:00
hi
103aa8a0c8 Pontoon: Update Vietnamese (vi) localization of Test Pilot: Firefox Send
Localization authors:
- hi <hi@duonganhtuan.com>
- Quế Tùng <best.cloney.1301@gmail.com>
- nguyễn việt anh <hatsune141p@gmail.com>
2018-10-28 12:34:28 +00:00
Cristian Silaghi
62d507120c Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Cristian Silaghi <cristian.silaghi@mozilla.ro>
2018-10-22 14:35:32 +00:00
Georgianizator
8ccb1c449a Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-10-21 05:34:07 +00:00
新垣结衣松冈茉优长泽雅美门胁麦上野树里石原里美
a07eb1ad1c Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- 新垣结衣松冈茉优长泽雅美门胁麦上野树里石原里美 <eloli@foxmail.com>
2018-10-10 09:34:06 +00:00
Danny Coates
78c6d83462 Merge pull request #965 from mozilla/255
updated deps
2018-10-08 12:13:20 -07:00
Danny Coates
5d41da0e16 bump version & updated deps 2018-10-08 12:06:09 -07:00
新垣结衣松冈茉优长泽雅美门胁麦上野树里石原里美
5afe9ff2af Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- 新垣结衣松冈茉优长泽雅美门胁麦上野树里石原里美 <eloli@foxmail.com>
2018-10-05 23:02:22 +00:00
Myungjae Won
a2eee15a7d Pontoon: Update Korean (ko) localization of Test Pilot: Firefox Send
Localization authors:
- Myungjae Won <breadmj@gmail.com>
2018-09-27 02:13:30 +00:00
alamanda
603a352595 Pontoon: Update Indonesian (id) localization of Test Pilot: Firefox Send
Localization authors:
- alamanda <dian.ina@gmail.com>
2018-09-24 07:12:00 +00:00
صفا الفليج
7b8655a079 Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- صفا الفليج <safa1996alfulaij@gmail.com>
2018-09-21 19:53:21 +00:00
Ruba
d3ba54d05a Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- Ruba <ruba.awayes@gmail.com>
2018-09-21 17:52:14 +00:00
صفا الفليج
83e2aec3f5 Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- صفا الفليج <safa1996alfulaij@gmail.com>
2018-09-20 22:52:43 +00:00
risger
0abf890fc4 Pontoon: Update Chinese (Taiwan) (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- risger <risger@live.com>
2018-09-20 02:32:18 +00:00
pyup.io bot
38fcd7227d Scheduled weekly dependency update for week 37 (#926)
* Update selenium from 3.13.0 to 3.14.0

* Update pypom from 2.0.0 to 2.1.0

* Update pytest from 3.6.3 to 3.8.0

* Update pytest-selenium from 1.13.0 to 1.14.0

* Update pytest-xdist from 1.22.2 to 1.23.0
2018-09-18 11:05:26 -04:00
Enol
6d29cebabb Pontoon: Update Asturian (ast) localization of Test Pilot: Firefox Send
Localization authors:
- Enol <enolp@softastur.org>
2018-09-13 22:22:17 +00:00
Georgianizator
8305b9bd2f Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-08-24 20:31:40 +00:00
Mozinet
5c542008ab Pontoon: Update French (fr) localization of Test Pilot: Firefox Send
Localization authors:
- Mozinet <mozinet@gmail.com>
2018-08-20 00:12:16 +00:00
Melo46
e1d6467de4 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Melo46 <melo@carmu.com>
2018-08-07 07:52:18 +00:00
Rodrigo Guerra
7dc34ba646 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo Guerra <rodmguerra@gmail.com>
2018-08-05 02:14:14 +00:00
Rodrigo Guerra
d5a30b710d Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo Guerra <rodmguerra@gmail.com>
2018-08-05 01:53:47 +00:00
Georgianizator
c90310405c Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-07-25 05:32:42 +00:00
Arash Mousavi
1bd85ee656 Pontoon: Update Persian (fa) localization of Test Pilot: Firefox Send
Localization authors:
- Arash Mousavi <mousavi.arash@gmail.com>
2018-07-20 14:31:37 +00:00
Rodrigo
45452c7153 Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-07-19 21:13:18 +00:00
Emily Hou
59ba89262f fix download count on server (#863) 2018-07-17 09:48:47 -07:00
Danny Coates
d906e927ed Merge pull request #862 from mozilla/i844
fixes #844
2018-07-13 09:01:07 -07:00
Emily
527e9f09c9 add a test 2018-07-12 16:07:18 -07:00
Emily
5ff92c6452 fix cancelled downloads increasing count 2018-07-12 14:02:05 -07:00
avelper
dfea1e96fb Pontoon: Update Spanish (Spain) (es-ES) localization of Test Pilot: Firefox Send
Localization authors:
- avelper <avelper@mozilla-hispano.org>
- jesferman1993 <jesferman1993@hotmail.com>
2018-07-10 21:33:30 +00:00
pyup.io bot
7e30fe8d33 Scheduled weekly dependency update for week 27 (#861)
* Update selenium from 3.12.0 to 3.13.0

* Update pytest from 3.6.2 to 3.6.3
2018-07-09 15:55:57 -04:00
వీవెన్
a74a560f0a Pontoon: Update Telugu (te) localization of Test Pilot: Firefox Send
Localization authors:
- వీవెన్ <veeven@gmail.com>
2018-06-28 10:15:36 +00:00
వీవెన్
93072c0c1e Pontoon: Update Telugu (te) localization of Test Pilot: Firefox Send
Localization authors:
- వీవెన్ <veeven@gmail.com>
2018-06-26 16:12:41 +00:00
pyup.io bot
b3ad207326 Update pytest from 3.6.1 to 3.6.2 (#857) 2018-06-25 13:19:15 -04:00
Yongmin H
dced61eb30 Pontoon: Update Korean (ko) localization of Test Pilot: Firefox Send
Localization authors:
- Yongmin H. <firefox@kumul.pe.kr>
2018-06-25 01:52:27 +00:00
Selim Şumlu
42574af2cc Pontoon: Update Turkish (tr) localization of Test Pilot: Firefox Send
Localization authors:
- Selim Şumlu <selim@sum.lu>
2018-06-22 11:33:08 +00:00
Kohei Yoshino
ab699ebcc6 Pontoon: Update English (Canada) (en-CA) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2018-06-22 07:32:39 +00:00
Tymur Faradzhev
de37804973 Pontoon: Update Ukrainian (uk) localization of Test Pilot: Firefox Send
Localization authors:
- Lobodzets <Lobodzets@meta.ua>
- Artem Polivanchuk <artem@mozilla.org.ua>
- Tymur Faradzhev <faradzhev.timur@gmail.com>
- Vitaliy Krutko <asmforce@ukr.net>
2018-06-20 19:18:49 +00:00
Frederick Villaluna
7654ec3b7c Pontoon: Update Tagalog (tl) localization of Test Pilot: Firefox Send
Localization authors:
- Frederick Villaluna <fv_comscie@yahoo.com>
2018-06-20 19:18:42 +00:00
Andreas Pettersson
fa84c653ea Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Andreas Pettersson <az@kth.se>
- Luna Jernberg <bittin@cafe8bitar.se>
2018-06-20 19:18:39 +00:00
avelper
7f9b43753e Pontoon: Update Spanish (Spain) (es-ES) localization of Test Pilot: Firefox Send
Localization authors:
- RickieES <rpmdisguise-nave@yahoo.es>
- avelper <avelper@mozilla-hispano.org>
- Jon Vadillo <vadillo.jon@gmail.com>
- jlG <jlg.l10n.es@gmail.com>
- Jordi Cuevas <jordicuevas@gmail.com>
- xxx <fxhelp@yahoo.com>
2018-06-20 19:18:35 +00:00
ravmn
a0c221750b Pontoon: Update Spanish (Chile) (es-CL) localization of Test Pilot: Firefox Send
Localization authors:
- josotrix <josotrix@ravmn.cl>
- ravmn <ravmn@ravmn.cl>
2018-06-20 19:18:28 +00:00
Marcelo Poli
bcb3936e08 Pontoon: Update Spanish (Argentina) (es-AR) localization of Test Pilot: Firefox Send
Localization authors:
- Gabriela <gmontagu@gmail.com>
- Marcelo Poli <enzomatrix@gmail.com>
2018-06-20 19:18:25 +00:00
Michael Wolf
66ec29eee9 Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-06-20 19:18:21 +00:00
Michael Wolf
58535c8c2e Pontoon: Update Sorbian, Lower (dsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-06-20 19:18:17 +00:00
Rok Žerdin
563686849f Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
- Lan Glad <upwinxp@gmail.com>
- Matjaž Horvat <matjaz.horvat@gmail.com>
2018-06-20 19:18:14 +00:00
Juraj Cigáň
5b607af8d1 Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2018-06-20 19:18:10 +00:00
Марко Костић (Marko Kostić)
c5061ec51e Pontoon: Update Serbian (sr) localization of Test Pilot: Firefox Send
Localization authors:
- Marko Andrejić <marko.andrejic93@gmail.com>
- Марко Костић (Marko Kostić) <marko.m.kostic@gmail.com>
2018-06-20 19:18:06 +00:00
Maykon Chagas
32074a9bab Pontoon: Update Portuguese (Brazil) (pt-BR) localization of Test Pilot: Firefox Send
Localization authors:
- Maykon Chagas <mchagas@riseup.net>
- Luiz Carlos de Morais <lcom_flip@hotmail.com>
- dgadelha <dgadelha@live.com>
- xxx <fxhelp@yahoo.com>
- Cynthia Pereira <cynthiacpereira@gmail.com>
2018-06-20 19:17:55 +00:00
Bjørn I
26478fc444 Pontoon: Update Norwegian Nynorsk (nn-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Bjørn I. <bjorn.svindseth@online.no>
2018-06-20 19:17:49 +00:00
Håvar Henriksen
7c66c07634 Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2018-06-20 19:17:45 +00:00
manxmensch
4ef6f8e3bd Pontoon: Update Malay (ms) localization of Test Pilot: Firefox Send
Localization authors:
- manxmensch <manxmensch@gmail.com>
2018-06-20 19:17:42 +00:00
Juan Esteban Ajsivinac Sián
5591fb03f1 Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send
Localization authors:
- Juan Esteban Ajsivinac Sián <ajtzibsyan@yahoo.com>
2018-06-20 19:17:37 +00:00
Muḥend Belqasem
fc35acc6e4 Pontoon: Update Kabyle (kab) localization of Test Pilot: Firefox Send
Localization authors:
- Muḥend Belqasem <belkacem77@gmail.com>
- ybouhamam <ybouhamam@gmail.com>
- Uccen Marzuq <merzouk.ouchene@laposte.net>
- Slimane AMIRI <slimane.amiri@gmail.com>
2018-06-20 19:17:33 +00:00
Kohei Yoshino
5c8c6d56a4 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
- aefgh39622 <aefgh39622@gmail.com>
2018-06-20 19:17:29 +00:00
Francesco Lodolo
4f9e63beca Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Francesco Lodolo <francesco.lodolo@mozillaitalia.org>
- Sav22999 <saverio.morelli@outlook.it>
- Sara Todaro <sara.todaro@mozillaitalia.org>
- Sandro <gialloporpora@mozillaitalia.org>
- Winfox <openlib@email.it>
2018-06-20 19:17:25 +00:00
Melo46
6b7f6426a1 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Melo46 <melo@carmu.com>
- Rodrigo Guerra <rodmguerra@gmail.com>
2018-06-20 19:17:21 +00:00
Balázs Meskó
1b6fad9d87 Pontoon: Update Hungarian (hu) localization of Test Pilot: Firefox Send
Localization authors:
- siparon <siparon@gmail.com>
- Balázs Meskó <meskobalazs@gmail.com>
2018-06-20 19:17:14 +00:00
hello
016d9bcf91 Pontoon: Update Hebrew (he) localization of Test Pilot: Firefox Send
Localization authors:
- Yaron Shahrabani <sh.yaron@gmail.com>
- hello <hello@ira.abramov.org>
2018-06-20 19:17:11 +00:00
Jim Spentzos
f199c8b96a Pontoon: Update Greek (el) localization of Test Pilot: Firefox Send
Localization authors:
- Jim Spentzos <jim.spentzos@outlook.com>
- Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
- Giorgos S. <giorgos.skafidas@gmx.com>
- Μιχάλης  ĺ ľfb <mikem132@protonmail.com>
2018-06-20 19:17:04 +00:00
Fjoerfoks
06321126e7 Pontoon: Update Frisian (fy-NL) localization of Test Pilot: Firefox Send
Localization authors:
- Fjoerfoks <fryskefirefox@gmail.com>
2018-06-20 19:16:55 +00:00
Sander Lepik
08f9ad5046 Pontoon: Update Estonian (et) localization of Test Pilot: Firefox Send
Localization authors:
- Merike Sell <merikes@gmail.com>
- Sander Lepik <sander.lepik@eesti.ee>
2018-06-20 19:16:49 +00:00
Ton
9aefe7f7c9 Pontoon: Update Dutch (nl) localization of Test Pilot: Firefox Send
Localization authors:
- Mark Heijl <markh@babelzilla.org>
- Ton <tonnes.mb@gmail.com>
2018-06-20 19:16:42 +00:00
Pin-guang Chen
ea2ff28f32 Pontoon: Update Chinese (Taiwan) (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2018-06-20 19:16:35 +00:00
YFdyh000
339bf12857 Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
- xcffl <xcffl@outlook.com>
2018-06-20 19:16:31 +00:00
Nihad Suljić
7c605ac7af Pontoon: Update Bosnian (bs) localization of Test Pilot: Firefox Send
Localization authors:
- Mirzet Omerović <mirzet.omerovic.1992@gmail.com>
- Kerim Kalamujić <kerim@mozilla.ba>
- Nihad Suljić <nihad.suljic92@gmail.com>
- Adnan Kičin <adnankicin92@gmail.com>
2018-06-20 19:16:23 +00:00
Belayet Hossain
288b878700 Pontoon: Update Bengali (Bangladesh) (bn-BD) localization of Test Pilot: Firefox Send
Localization authors:
- Ashikur Rahman <ashikurrahman068@gmail.com>
- S M Sarwar Nobin <smsarwar1996@gmail.com>
- Belayet Hossain <bellayet@gmail.com>
2018-06-20 19:16:19 +00:00
Emin Mastizada
db84b331c2 Pontoon: Update Azerbaijani (az) localization of Test Pilot: Firefox Send
Localization authors:
- Emin Mastizada <emin@mastizada.com>
2018-06-20 19:16:15 +00:00
Besnik Bleta
2fde21c522 Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send
Localization authors:
- Besnik Bleta <besnik@programeshqip.org>
2018-06-20 19:16:03 +00:00
Danny Coates
fb3747785b Merge pull request #846 from pd4d10/patch-1
Fix #843: Upload image on paste
2018-06-18 12:27:58 -07:00
Danny Coates
93d151a29a Merge pull request #852 from Vimal-Raghubir/master
Remove bad role attribute
2018-06-18 10:07:37 -07:00
Vimal Raghubir
e28a92848d Remove bad role attribute 2018-06-15 10:12:34 -04:00
pyup.io bot
f83784e033 Scheduled weekly dependency update for week 23 (#849)
* Update pytest from 3.6.0 to 3.6.1

* Update pytest-html from 1.18.0 to 1.19.0
2018-06-12 15:36:58 -07:00
Rongjian Zhang
11173c520b Add return to prevent multiple uploads 2018-06-07 10:46:55 +08:00
Rongjian Zhang
608112d56c Fix #843: Upload image on paste 2018-06-06 12:39:26 +08:00
Danny Coates
480a06c426 track bytes sent on download 2018-06-05 15:26:24 -07:00
Danny Coates
f0530975ac updated aws-sdk 2018-06-05 13:20:41 -07:00
Danny Coates
6d4973391a Merge pull request #840 from brainlulz/fix/ally-focus-downloadFile
fix: added a tabindex to the CopyUrl and Delete icon
2018-06-04 09:31:02 -07:00
Brainlulz
0edfc8405f fix: added a tabindex to the CopyUrl and Delete icon 2018-06-03 11:36:20 +02:00
eljuno
5274b732b2 Pontoon: Update Indonesian (id) localization of Test Pilot: Firefox Send
Localization authors:
- eljuno <eljunotrie_anggoro@yahoo.co.id>
2018-06-02 19:11:16 +00:00
Michal Stanke
0a71c8c724 Pontoon: Update Czech (cs) localization of Test Pilot: Firefox Send
Localization authors:
- Michal Stanke <mstanke@mozilla.cz>
2018-06-02 11:12:06 +00:00
Arash Mousavi
9feb6866ee Pontoon: Update Persian (fa) localization of Test Pilot: Firefox Send
Localization authors:
- Arash Mousavi <mousavi.arash@gmail.com>
2018-05-31 14:14:28 +00:00
Danny Coates
196d4211b6 an additional tweak to the position of the selectbox arrow 2018-05-29 15:12:00 -07:00
Danny Coates
a50762ebd7 Merge pull request #833 from dashokkumar93/master
changes related to arrow positioning
2018-05-29 14:56:06 -07:00
Danny Coates
3e65f3a906 only include js files in frontend tests. fixes #834 2018-05-29 14:39:54 -07:00
pyup.io bot
bea7d30836 Scheduled weekly dependency update for week 21 (#832)
* Update pytest from 3.5.1 to 3.6.0
* Update pytest-html from 1.17.0 to 1.18.0
* Update pytest-selenium from 1.12.0 to 1.13.0
2018-05-29 09:05:23 -04:00
Ashok kumar
6acf58f9e9 changes related to arrow positioning
Issue fixes related to https://github.com/mozilla/send/issues/820.
2018-05-29 16:42:34 +05:30
Théo Chevalier
33993eda88 Pontoon: Update French (fr) localization of Test Pilot: Firefox Send
Localization authors:
- Théo Chevalier <theo.chevalier11@gmail.com>
2018-05-28 20:12:06 +00:00
Danny Coates
8242e2088d bump version 2018-05-21 13:59:46 -07:00
emily-hou1
48457f7ac1 Merge pull request #817 from mozilla/i798
fixes #798
2018-05-21 13:28:52 -07:00
Emily Hou
8a496022f4 fixes #798 2018-05-21 13:20:33 -07:00
pyup.io bot
9c398ad98b Update selenium from 3.11.0 to 3.12.0 (#816) 2018-05-14 12:05:18 -04:00
pyup.io bot
dc1b754692 Scheduled weekly dependency update for week 18 (#815)
* Update pypom from 1.3.0 to 2.0.0

* Update pytest from 3.5.0 to 3.5.1

* Update pytest-html from 1.16.1 to 1.17.0
2018-05-07 13:22:08 -04:00
Joergen
dc7203ea59 Pontoon: Update Danish (da) localization of Test Pilot: Firefox Send
Localization authors:
- Joergen <joergenr@stofanet.dk>
- Kim Ludvigsen <kim@kimsside.dk>
2018-05-07 08:51:26 +00:00
chilledfrogs
af9973e35b Pontoon: Update Danish (da) localization of Test Pilot: Firefox Send
Localization authors:
- Joergen <joergenr@stofanet.dk>
- chilledfrogs <chilledfrogs@gmail.com>
2018-05-07 08:31:49 +00:00
Danny Coates
aeb44379c8 Merge pull request #813 from mozilla/docker-int
moved integration text execution to docker
2018-05-04 12:18:54 -07:00
Danny Coates
7841dec5d8 more docker integration test tweaks 2018-05-04 09:18:43 -07:00
Danny Coates
7d62a23b36 moved integration text execution to docker 2018-05-03 16:41:50 -07:00
Cristian Silaghi
f36ac24ac5 Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Cristian Silaghi <cristian.silaghi@mozilla.ro>
2018-05-01 20:50:43 +00:00
Cristian Silaghi
e6c2736f1f Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Cristian Silaghi <cristian.silaghi@mozilla.ro>
2018-05-01 20:30:53 +00:00
Selim Şumlu
0d46997ab3 Pontoon: Update Turkish (tr) localization of Test Pilot: Firefox Send
Localization authors:
- Selim Şumlu <selim@sum.lu>
2018-05-01 00:31:26 +00:00
Georgianizator
896a0f035b Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-04-30 17:31:32 +00:00
Georgianizator
fdd07a06be Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-04-30 17:11:21 +00:00
Rhoslyn Prys
8f8595e750 Pontoon: Update Welsh (cy) localization of Test Pilot: Firefox Send
Localization authors:
- Rhoslyn Prys <rprys@posteo.net>
2018-04-30 10:31:55 +00:00
Mozilla Pontoon
7a83fc6d8f Pontoon: Update Welsh (cy) localization of Test Pilot: Firefox Send 2018-04-30 07:34:31 +00:00
Mozilla Pontoon
30df33e189 Pontoon: Update Turkish (tr) localization of Test Pilot: Firefox Send 2018-04-30 07:34:23 +00:00
Mozilla Pontoon
7aac0426e0 Pontoon: Update Persian (fa) localization of Test Pilot: Firefox Send 2018-04-30 07:34:18 +00:00
Mozilla Pontoon
bc423d4d78 Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send 2018-04-30 07:34:13 +00:00
hi
3ddfc822d1 Pontoon: Update Vietnamese (vi) localization of Test Pilot: Firefox Send
Localization authors:
- hi <hi@duonganhtuan.com>
- Trung Backup <backup.36a91519+tech@gmail.com>
2018-04-30 01:31:08 +00:00
Benjamin Forehand Jr
f99e4db25f Added footer link tests. (#811) 2018-04-27 15:44:03 -04:00
pyup.io bot
fd71e7f957 Config file for pyup.io (#809)
* create pyup.io config file

* Updated requirements directory.
2018-04-26 15:03:57 -04:00
paul.trevor
2bfeb75380 Pontoon: Update German (de) localization of Test Pilot: Firefox Send
Localization authors:
- paul.trevor <paul.trevor@gmx.de>
2018-04-25 07:11:48 +00:00
Danny Coates
21f7fd7dbc Merge pull request #800 from jrbenny35/add_initial_ui_tests
Initial user integration tests.
2018-04-24 13:34:34 -07:00
Benjamin Forehand Jr
3e08c35740 Updated strings and descriptions. 2018-04-24 11:07:11 -04:00
Benjamin Forehand Jr
54e78b6274 This adds some user integration tests to aide the SoftVision team a bit. Right now I have 3 tests.
test_upload: This will create a file and make sure it uploads by verifying a file uploads and is assigned a URL.

    test_download: This will create a file, upload it and then download it making sure it is the same filename that was uploaded. We can expand this later to maybe check the sizes and such.

    test_progress: This will create a file and make sure the progress bar shows up after it begins uploading.

    These are python tests and use Pipenv to manage dependencies as well as tox as the virtualenv manager, and finally pytest as the test runner.
2018-04-24 11:00:19 -04:00
Victor Bychek
f1499abbe8 Pontoon: Update Russian (ru) localization of Test Pilot: Firefox Send
Localization authors:
- Victor Bychek <a@bychek.ru>
- mail <mail@janitorrb.com>
2018-04-20 18:11:05 +00:00
Yongmin H
1ad78e2844 Pontoon: Update Korean (ko) localization of Test Pilot: Firefox Send
Localization authors:
- Yongmin H. <firefox@kumul.pe.kr>
2018-04-19 01:51:11 +00:00
eljuno
af436f9506 Pontoon: Update Indonesian (id) localization of Test Pilot: Firefox Send
Localization authors:
- eljuno <eljunotrie_anggoro@yahoo.co.id>
2018-04-16 20:11:06 +00:00
eljuno
4dfdc8b0c7 Pontoon: Update Indonesian (id) localization of Test Pilot: Firefox Send
Localization authors:
- eljuno <eljunotrie_anggoro@yahoo.co.id>
2018-04-15 18:31:25 +00:00
eljuno
b4c0c36f3a Pontoon: Update Indonesian (id) localization of Test Pilot: Firefox Send
Localization authors:
- eljuno <eljunotrie_anggoro@yahoo.co.id>
2018-04-12 19:32:37 +00:00
Sahithi
d52ca850cb Pontoon: Update Telugu (te) localization of Test Pilot: Firefox Send
Localization authors:
- Sahithi <sahithi.thinker@gmail.com>
2018-04-10 09:50:44 +00:00
Jordi Serratosa
c3e0787d12 Pontoon: Update Catalan (ca) localization of Test Pilot: Firefox Send
Localization authors:
- Jordi Serratosa <jordis@softcatala.cat>
2018-04-09 00:11:02 +00:00
Danny Coates
3f65e55f86 Merge pull request #801 from robtec/patch-2
Update faq.md
2018-04-07 19:07:59 -08:00
Rob Powell
2db56fac3a Update faq.md
Updated information around download limits
2018-04-07 23:21:55 +01:00
Cristian Silaghi
464c0c4c47 Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Cristian Silaghi <cristian.silaghi@mozilla.ro>
2018-04-03 12:11:31 +00:00
Danny Coates
71ed7d71fb added browserstack badge to readme 2018-03-31 19:50:45 -07:00
Danny Coates
cda38f9bcf v2.5.3 2018-03-29 00:03:04 -07:00
Danny Coates
cc9b622bde fixed cryptofill on edge 2018-03-28 22:03:46 -07:00
Danny Coates
fb91fd03cc adjusted selectbox styles 2018-03-27 23:19:07 -07:00
rcmainak
77e3b5a3e6 Replaced the selectbox with native HTML <select> 2018-03-27 21:40:59 -07:00
صفا الفليج
0ed5c7f1e7 Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- صفا الفليج <safa1996alfulaij@gmail.com>
2018-03-26 01:32:08 +00:00
Alberto Castro
5afadd4ff1 Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Alberto Castro <albertdcastro@gmail.com>
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-03-22 17:10:58 +00:00
వీవెన్
0f53c718a2 Pontoon: Update Telugu (te) localization of Test Pilot: Firefox Send
Localization authors:
- వీవెన్ <veeven@gmail.com>
2018-03-22 16:31:46 +00:00
Gonçalo Matos
ad4e6c8dec Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Alberto Castro <albertdcastro@gmail.com>
- Gonçalo Matos <goncalo.matos@me.com>
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-03-22 02:31:41 +00:00
Rodrigo
9e0195deaa Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-03-22 02:12:00 +00:00
Rodrigo
253216e6fc Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-03-21 20:10:48 +00:00
Roberto Alvarado
78eab6335d Pontoon: Update Spanish (Mexico) (es-MX) localization of Test Pilot: Firefox Send
Localization authors:
- Roberto Alvarado <ralv888@gmail.com>
2018-03-19 22:11:09 +00:00
Enol
1d20b5ba11 Pontoon: Update Asturian (ast) localization of Test Pilot: Firefox Send
Localization authors:
- Enol <enolp@softastur.org>
2018-03-15 15:11:13 +00:00
Danny Coates
1edc571b36 v2.5.2 2018-03-14 09:12:26 -07:00
Danny Coates
e3556aa7e1 updated cryptofill to support Android Firefox. fixes #790 2018-03-13 21:11:27 -07:00
Jobava
aa94a75da9 Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Jobava <jobaval10n@gmail.com>
2018-03-13 20:51:11 +00:00
Danny Coates
ecd61830aa v2.5.1 2018-03-12 12:26:37 -07:00
Danny Coates
da82ef814b MS Edge hacks 2018-03-12 12:24:43 -07:00
Danny Coates
b840173429 Merge pull request #789 from RCMainak/issue_775
Fixed #775 : Made text not-selectable
2018-03-12 10:43:53 -07:00
rcmainak
e1dc1687fc Fixed #775 : Made text not-selectable 2018-03-12 23:09:00 +05:30
Danny Coates
3e6a88d31d render header and footer only once. fixes #788 2018-03-12 10:15:11 -07:00
Melo46
94714ecb62 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Melo46 <melo@carmu.com>
2018-03-09 00:50:44 +00:00
Danny Coates
07a817266c v2.5.0 2018-03-08 11:31:35 -08:00
Vitaliy Krutko
706708876c Pontoon: Update Ukrainian (uk) localization of Test Pilot: Firefox Send
Localization authors:
- Vitaliy Krutko <asmforce@ukr.net>
2018-03-08 17:52:01 +00:00
Frederick Villaluna
fc4cbe4b74 Pontoon: Update Tagalog (tl) localization of Test Pilot: Firefox Send
Localization authors:
- Frederick Villaluna <fv_comscie@yahoo.com>
2018-03-08 17:51:57 +00:00
ravmn
9e8429cff3 Pontoon: Update Spanish (Chile) (es-CL) localization of Test Pilot: Firefox Send
Localization authors:
- ravmn <ravmn@ravmn.cl>
2018-03-08 17:51:54 +00:00
Gabriela
f8db7ca923 Pontoon: Update Spanish (Argentina) (es-AR) localization of Test Pilot: Firefox Send
Localization authors:
- Gabriela <gmontagu@gmail.com>
2018-03-08 17:51:51 +00:00
Rok Žerdin
70e4d9eeb0 Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
2018-03-08 17:51:48 +00:00
Jobava
410ec72fff Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Jobava <jobaval10n@gmail.com>
2018-03-08 17:51:45 +00:00
Arash Mousavi
a42a517896 Pontoon: Update Persian (fa) localization of Test Pilot: Firefox Send
Localization authors:
- Arash Mousavi <mousavi.arash@gmail.com>
2018-03-08 17:51:42 +00:00
Håvar Henriksen
d9c9d95b89 Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2018-03-08 17:51:39 +00:00
Alfredos-Panagiotis Damkalis
0ed10649ef Pontoon: Update Greek (el) localization of Test Pilot: Firefox Send
Localization authors:
- Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
2018-03-08 17:51:36 +00:00
Fjoerfoks
4edf82cc21 Pontoon: Update Frisian (fy-NL) localization of Test Pilot: Firefox Send
Localization authors:
- Fjoerfoks <fryskefirefox@gmail.com>
2018-03-08 17:51:33 +00:00
Merike Sell
775726ae6d Pontoon: Update Estonian (et) localization of Test Pilot: Firefox Send
Localization authors:
- Merike Sell <merikes@gmail.com>
2018-03-08 17:51:30 +00:00
Michal Vašíček
5aeaf2e987 Pontoon: Update Czech (cs) localization of Test Pilot: Firefox Send
Localization authors:
- Michal Vašíček <michalvasicek@icloud.com>
- Michal Stanke <mstanke@mozilla.cz>
2018-03-08 17:51:27 +00:00
Emin Mastizada
773244f320 Pontoon: Update Azerbaijani (az) localization of Test Pilot: Firefox Send
Localization authors:
- Emin Mastizada <emin@mastizada.com>
2018-03-08 17:51:23 +00:00
Danny Coates
5079d9a317 Merge pull request #782 from mozilla/docs
updated docs
2018-03-07 15:20:11 -08:00
Danny Coates
18e1609cb3 updated docs 2018-03-07 15:01:08 -08:00
Danny Coates
41796840c4 Merge pull request #781 from timvisee/master
Don't translate URL-safe chars, b64 is doing it for us
2018-03-06 08:44:53 -08:00
timvisee
171b64bc98 Don't translate URL-safe chars, b64 is doing it for us 2018-03-06 14:38:29 +01:00
Danny Coates
cfc94fd9af built assets are webpack cacheable 2018-03-02 13:58:05 -08:00
Danny Coates
914394054e encapsulate translate function creation 2018-03-02 13:40:29 -08:00
Danny Coates
7a237b9b68 reduced the size of cryptofill 2018-03-02 12:21:59 -08:00
Danny Coates
80e9f129d8 ignore some lint warnings 2018-03-01 21:36:45 -08:00
Danny Coates
fddf1c40dc fixed password input style on small screens 2018-03-01 21:33:59 -08:00
Danny Coates
557db53d39 updated browserlist 2018-03-01 14:52:00 -08:00
Danny Coates
c16e00e5af Merge pull request #779 from mozilla/edgy
implemented crypto polyfills for ms edge
2018-03-01 13:21:45 -08:00
Danny Coates
cd7da20024 implemented crypto polyfills for ms edge 2018-03-01 13:10:57 -08:00
Danny Coates
5f44ed2598 v2.4.1 2018-02-28 09:05:44 -08:00
Danny Coates
5c0cfdcf38 Merge pull request #777 from mozilla/i771
use a separate circle in the progress svg for indefinite progress
2018-02-28 09:02:54 -08:00
Danny Coates
2de5378208 use a separate circle in the progress svg for indefinite progress. fixes #771 2018-02-28 08:57:35 -08:00
Danny Coates
a2aca89550 set removeViewBox: false on svgo 2018-02-26 20:28:51 -08:00
Danny Coates
8d658dc159 v2.4.0 2018-02-26 17:55:29 -08:00
Danny Coates
fb8e0afd85 fixed checkFiles bug 2018-02-26 17:50:58 -08:00
Danny Coates
5263839731 undo a fup from previous commit 2018-02-26 13:54:41 -08:00
Danny Coates
484063a596 fixed some html nits 2018-02-26 13:49:26 -08:00
Danny Coates
5650c7f778 Merge pull request #769 from mozilla/i740
removed unsafe-inline styles via svgo-loader
2018-02-26 11:52:42 -08:00
Danny Coates
74728782f3 removed unsafe-inline styles via svgo-loader. fixes #740 2018-02-26 11:48:28 -08:00
Danny Coates
0a0980f9e3 Merge pull request #767 from mozilla/circl
added coverage artifact to circleci
2018-02-26 10:15:37 -08:00
Danny Coates
26c46b8488 added coverage artifact to circleci 2018-02-26 10:09:28 -08:00
Danny Coates
c469696687 ignore all branch pushes for stage builds 2018-02-25 17:14:37 -08:00
Danny Coates
27550a7781 fix? circleci deploy 2018-02-25 17:06:28 -08:00
Danny Coates
e79bacd268 Merge pull request #766 from mozilla/frontend-tests
Some frontend unit tests [WIP]
2018-02-25 16:53:02 -08:00
Danny Coates
fd2dfcc4f2 circleci v2 2018-02-25 16:39:45 -08:00
Danny Coates
74b9e364fe updated deps 2018-02-25 10:59:35 -08:00
Filip Hruška
d2412679ea Pontoon: Update Czech (cs) localization of Test Pilot: Firefox Send
Localization authors:
- Michal Stanke <mstanke@mozilla.cz>
- Filip Hruška <fhr@fhrnet.eu>
2018-02-25 12:10:47 +00:00
Марко Костић (Marko Kostić)
98e6fc3b4a Pontoon: Update Serbian (sr) localization of Test Pilot: Firefox Send
Localization authors:
- Марко Костић (Marko Kostić) <marko.m.kostic@gmail.com>
2018-02-25 08:10:19 +00:00
Slimane Amiri
b64b2a3091 Pontoon: Update Kabyle (kab) localization of Test Pilot: Firefox Send
Localization authors:
- Slimane Amiri <slimane.amiri@gmail.com>
2018-02-25 07:50:44 +00:00
Slimane Amiri
6275f3abf7 Pontoon: Update Kabyle (kab) localization of Test Pilot: Firefox Send
Localization authors:
- Slimane Amiri <slimane.amiri@gmail.com>
2018-02-25 07:31:42 +00:00
Danny Coates
22e836c98a removed unused deps 2018-02-24 18:00:43 -08:00
Danny Coates
d6c0489fa3 more frontend tests and some factoring based on them 2018-02-24 12:57:19 -08:00
Danny Coates
78728ce4ca some frontend unit tests 2018-02-24 11:21:48 -08:00
Bjørn I
6803a34b51 Pontoon: Update Norwegian Nynorsk (nn-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Bjørn I. <bjorn.svindseth@online.no>
2018-02-24 09:32:28 +00:00
jlG
42b6383283 Pontoon: Update Spanish (Spain) (es-ES) localization of Test Pilot: Firefox Send
Localization authors:
- jlG <jlg.l10n.es@gmail.com>
2018-02-23 21:32:57 +00:00
Georgianizator
147f009279 Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-02-23 17:52:11 +00:00
Andreas Pettersson
b899781b69 Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Andreas Pettersson <az@kth.se>
2018-02-23 16:51:56 +00:00
Alexander Slovesnik
64d0819ab4 Pontoon: Update Russian (ru) localization of Test Pilot: Firefox Send
Localization authors:
- Alexander Slovesnik <unghost@mozilla-russia.org>
2018-02-23 13:31:50 +00:00
Rhoslyn Prys
3ea4af816a Pontoon: Update Welsh (cy) localization of Test Pilot: Firefox Send
Localization authors:
- Rhoslyn Prys <rprys@yahoo.com>
2018-02-23 11:51:38 +00:00
Pin-guang Chen
037d2c6974 Pontoon: Update Chinese (Taiwan) (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2018-02-23 11:51:24 +00:00
Ton
fe2c664474 Pontoon: Update Dutch (nl) localization of Test Pilot: Firefox Send
Localization authors:
- Ton <tonnes.mb@gmail.com>
2018-02-23 09:50:46 +00:00
Nihad Suljić
a3d429b9c3 Pontoon: Update Bosnian (bs) localization of Test Pilot: Firefox Send
Localization authors:
- Nihad Suljić <nihad.suljic92@gmail.com>
2018-02-23 06:51:18 +00:00
manxmensch
d0e6f4118d Pontoon: Update Malay (ms) localization of Test Pilot: Firefox Send
Localization authors:
- manxmensch <manxmensch@gmail.com>
2018-02-23 02:12:08 +00:00
Kohei Yoshino
75fb58e454 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2018-02-23 01:31:39 +00:00
Selim Şumlu
6563b7fd11 Pontoon: Update Turkish (tr) localization of Test Pilot: Firefox Send
Localization authors:
- Selim Şumlu <selim@sum.lu>
2018-02-22 23:30:59 +00:00
Maykon Chagas
e1b78174e2 Pontoon: Update Portuguese (Brazil) (pt-BR) localization of Test Pilot: Firefox Send
Localization authors:
- Maykon Chagas <mchagas@riseup.net>
2018-02-22 20:51:34 +00:00
Michael Wolf
aab7e25f80 Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-02-22 20:12:32 +00:00
Michael Wolf
24b2ba7b35 Pontoon: Update Sorbian, Lower (dsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-02-22 20:12:28 +00:00
Juan Esteban Ajsivinac Sián
20ae667b3b Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send
Localization authors:
- Juan Esteban Ajsivinac Sián <ajtzibsyan@yahoo.com>
2018-02-22 20:12:24 +00:00
Juraj Cigáň
d8cb9da483 Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2018-02-22 19:51:15 +00:00
Besnik Bleta
8abce2ccf3 Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send
Localization authors:
- Besnik Bleta <besnik@programeshqip.org>
2018-02-22 19:31:23 +00:00
Rodrigo
aa3ebd3bd2 Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-02-22 19:12:18 +00:00
Michael Köhler
4929437283 Pontoon: Update German (de) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Köhler <michael.koehler1@gmx.de>
2018-02-22 18:51:36 +00:00
Balázs Meskó
389dd19a8a Pontoon: Update Hungarian (hu) localization of Test Pilot: Firefox Send
Localization authors:
- Balázs Meskó <meskobalazs@gmail.com>
2018-02-22 18:31:38 +00:00
Francesco Lodolo
a8b6b3335b Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Francesco Lodolo <francesco.lodolo@mozillaitalia.org>
2018-02-22 17:50:43 +00:00
Théo Chevalier
a925e2fae0 Pontoon: Update French (fr) localization of Test Pilot: Firefox Send
Localization authors:
- Théo Chevalier <theo.chevalier11@gmail.com>
2018-02-22 17:31:29 +00:00
YFdyh000
dc79b5923e Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2018-02-22 17:31:25 +00:00
Danny Coates
b39bbaf6fb Merge pull request #761 from mozilla/i741
added maxPasswordLength and passwordError messages
2018-02-22 09:10:26 -08:00
Danny Coates
14b40d820b added password setting error UI 2018-02-22 09:01:29 -08:00
Rhoslyn Prys
354d5963ec Pontoon: Update Welsh (cy) localization of Test Pilot: Firefox Send
Localization authors:
- Rhoslyn Prys <rprys@yahoo.com>
2018-02-22 15:51:26 +00:00
Victor Bychek
6a32b94336 Pontoon: Update Russian (ru) localization of Test Pilot: Firefox Send
Localization authors:
- Victor Bychek <a@bychek.ru>
2018-02-22 10:31:30 +00:00
Danny Coates
5c68437b1c trimmed progress.css 2018-02-21 16:59:54 -08:00
Danny Coates
0106f280f0 removed unused css class on preview page 2018-02-21 16:53:14 -08:00
Danny Coates
b567aaac69 updated preview page 2018-02-21 16:40:52 -08:00
Danny Coates
36b419202b updated notFound page 2018-02-21 16:33:06 -08:00
Danny Coates
c80d01c648 updated error page 2018-02-21 16:28:40 -08:00
Danny Coates
1d00646b17 go back to faster createObjectURL for saveFile on non-iOS 2018-02-21 15:43:32 -08:00
Danny Coates
e4b98fe65a moved saveFile from utils to fileReceiver 2018-02-21 14:58:41 -08:00
Danny Coates
12443db891 Merge pull request #764 from mozilla/prog
added indefinite progress mode
2018-02-21 14:17:11 -08:00
Danny Coates
03f08de32f added indefinite progress mode 2018-02-21 13:59:06 -08:00
Danny Coates
c18f488be7 added maxPasswordLength and passwordError messages 2018-02-21 10:03:19 -08:00
Danny Coates
099012fac9 updated password input behavior. fixes #762 & fixes #763 2018-02-21 09:55:04 -08:00
Jordi Serratosa
fa510af65a Pontoon: Update Catalan (ca) localization of Test Pilot: Firefox Send
Localization authors:
- Jordi Serratosa <jordis@softcatala.cat>
2018-02-21 17:11:33 +00:00
Luna Jernberg
46b514cc61 Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Luna Jernberg <bittin@cafe8bitar.se>
2018-02-21 14:12:13 +00:00
Danny Coates
019c8814f6 disable nanotiming without localstorage (as of 7.3.0) 2018-02-20 16:00:19 -08:00
Danny Coates
46249935b2 moved babel-polyfill to prod deps 2018-02-20 12:52:44 -08:00
Danny Coates
ebecc6bb81 updated download password input style 2018-02-20 12:21:00 -08:00
Danny Coates
130ddca135 make 'change' button visible by default after pwd set 2018-02-20 11:27:56 -08:00
Danny Coates
f2661989dc updated deps 2018-02-20 10:56:16 -08:00
Roberto Alvarado
3f6cb8c356 Pontoon: Update Spanish (Mexico) (es-MX) localization of Test Pilot: Firefox Send
Localization authors:
- Roberto Alvarado <ralv888@gmail.com>
2018-02-20 18:51:01 +00:00
YFdyh000
228d9cca6c Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2018-02-20 18:33:03 +00:00
Марко Костић (Marko Kostić)
d08a1dd2ca Pontoon: Update Serbian (sr) localization of Test Pilot: Firefox Send
Localization authors:
- Марко Костић (Marko Kostić) <marko.m.kostic@gmail.com>
2018-02-20 18:11:41 +00:00
Selim Şumlu
7d76a60db7 Pontoon: Update Turkish (tr) localization of Test Pilot: Firefox Send
Localization authors:
- Selim Şumlu <selim@sum.lu>
2018-02-20 13:11:34 +00:00
Ton
e4f0865067 Pontoon: Update Dutch (nl) localization of Test Pilot: Firefox Send
Localization authors:
- Ton <tonnes.mb@gmail.com>
2018-02-20 10:50:53 +00:00
Rok Žerdin
cad4bd7c04 Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
2018-02-20 10:11:54 +00:00
Nihad Suljić
f85aaf0370 Pontoon: Update Bosnian (bs) localization of Test Pilot: Firefox Send
Localization authors:
- Nihad Suljić <nihad.suljic92@gmail.com>
2018-02-20 10:11:50 +00:00
Michael Köhler
312d78617d Pontoon: Update German (de) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Köhler <michael.koehler1@gmx.de>
2018-02-20 08:50:58 +00:00
Balázs Meskó
af5aa12fa1 Pontoon: Update Hungarian (hu) localization of Test Pilot: Firefox Send
Localization authors:
- Balázs Meskó <meskobalazs@gmail.com>
2018-02-20 08:31:19 +00:00
Håvar Henriksen
1c7e2edae0 Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2018-02-20 08:11:41 +00:00
Fjoerfoks
a49eee9685 Pontoon: Update Frisian (fy-NL) localization of Test Pilot: Firefox Send
Localization authors:
- Fjoerfoks <fryskefirefox@gmail.com>
2018-02-20 07:50:53 +00:00
Danny Coates
61938c8e66 fixed css bundle filename hash 2018-02-19 23:44:18 -08:00
manxmensch
76d10f5920 Pontoon: Update Malay (ms) localization of Test Pilot: Firefox Send
Localization authors:
- manxmensch <manxmensch@gmail.com>
2018-02-20 07:11:54 +00:00
صفا الفليج
2cf85926e9 Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- صفا الفليج <safa1996alfulaij@gmail.com>
2018-02-20 07:11:51 +00:00
Danny Coates
afb099f9df updated noscript style 2018-02-19 23:10:03 -08:00
Pin-guang Chen
343627eb82 Pontoon: Update Chinese (Taiwan) (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2018-02-20 05:31:40 +00:00
Francesco Lodolo
86f2477c63 Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Francesco Lodolo <francesco.lodolo@mozillaitalia.org>
2018-02-20 04:50:41 +00:00
Frederick Villaluna
2684150141 Pontoon: Update Tagalog (tl) localization of Test Pilot: Firefox Send
Localization authors:
- Frederick Villaluna <fv_comscie@yahoo.com>
2018-02-20 03:11:09 +00:00
Maykon Chagas
3f8d8d055d Pontoon: Update Portuguese (Brazil) (pt-BR) localization of Test Pilot: Firefox Send
Localization authors:
- Maykon Chagas <mchagas@riseup.net>
2018-02-20 01:32:00 +00:00
Georgianizator
e775e0542e Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-02-20 00:51:15 +00:00
Danny Coates
7992ba5bc1 tweaked password input style 2018-02-19 16:22:17 -08:00
Rodrigo
d24bbaa65a Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-02-20 00:10:28 +00:00
Kohei Yoshino
58a91e1b86 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2018-02-19 23:31:29 +00:00
Kohei Yoshino
cfed1d0230 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2018-02-19 23:10:56 +00:00
Danny Coates
b14c70f4b6 fixed file button style on input focus 2018-02-19 15:06:46 -08:00
Danny Coates
ad77fc20c6 fixed dnd after css changes 2018-02-19 14:29:13 -08:00
Danny Coates
029633f3d3 tweaked input styles 2018-02-19 14:02:26 -08:00
Michael Wolf
78459d759c Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-02-19 21:51:15 +00:00
Michael Wolf
a0d5a3dd07 Pontoon: Update Sorbian, Lower (dsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-02-19 21:51:13 +00:00
Juan Esteban Ajsivinac Sián
d76d983d5d Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send
Localization authors:
- Juan Esteban Ajsivinac Sián <ajtzibsyan@yahoo.com>
2018-02-19 21:51:10 +00:00
Juraj Cigáň
68b8e7dc7c Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2018-02-19 21:31:59 +00:00
Théo Chevalier
1a7aff5102 Pontoon: Update French (fr) localization of Test Pilot: Firefox Send
Localization authors:
- Théo Chevalier <theo.chevalier11@gmail.com>
2018-02-19 21:31:56 +00:00
Danny Coates
677edffb80 Merge pull request #760 from mozilla/refactor-css
refactored css: phase 1
2018-02-19 13:10:10 -08:00
ybouhamam
2cd90c998b Pontoon: Update Kabyle (kab) localization of Test Pilot: Firefox Send
Localization authors:
- ybouhamam <ybouhamam@gmail.com>
2018-02-18 20:32:16 +00:00
Danny Coates
346e604f34 updated password input UI 2018-02-17 15:07:47 -08:00
Danny Coates
8d41111cd6 refactored css, including some markup changes 2018-02-15 15:54:59 -08:00
Danny Coates
3163edcbe4 exclude fxios from fxPromo banner. fixes #753 2018-02-15 11:49:41 -08:00
Danny Coates
18df43c9cb Merge pull request #759 from flodolo/ftl_en
Switch en-US FTL file to new syntax
2018-02-15 08:56:56 -08:00
Francesco Lodolo [:flod]
4f2179c4d7 Switch en-US FTL file to new syntax 2018-02-15 11:24:21 +01:00
Danny Coates
b89546ac22 ignore stylelint until refactor-css is merged 2018-02-14 10:47:09 -08:00
Danny Coates
c0ad7635f2 change saveFile. attempting to fix for iOS 2018-02-14 09:29:37 -08:00
Bjørn I
a82688163e Pontoon: Update Norwegian Nynorsk (nn-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Bjørn I. <bjorn.svindseth@online.no>
2018-02-14 11:11:09 +00:00
Danny Coates
29c36ee110 updated deps 2018-02-12 11:39:35 -08:00
Merike Sell
43698f8d61 Pontoon: Update Estonian (et) localization of Test Pilot: Firefox Send
Localization authors:
- Merike Sell <merikes@gmail.com>
2018-02-12 18:10:42 +00:00
Danny Coates
b5b29aeb17 focus download password input on render. fixes #745 2018-02-12 09:53:34 -08:00
Melo46
fdcf4c152a Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Melo46 <melo@carmu.com>
2018-02-12 08:10:33 +00:00
Danny Coates
dcfda9521b nit: use 'html/raw' instead of html() where possible 2018-02-11 14:03:00 -08:00
Jim Spentzos
950c9cdaeb Pontoon: Update Greek (el) localization of Test Pilot: Firefox Send
Localization authors:
- Jim Spentzos <jamesspentzos@hotmail.com>
2018-02-10 20:32:06 +00:00
Danny Coates
1e9641a40e Merge pull request #758 from mozilla/refactor-backend
refactored server
2018-02-09 15:10:22 -08:00
Danny Coates
3fd2537311 refactored server 2018-02-09 15:03:05 -08:00
Danny Coates
6d470b8eba Merge pull request #757 from stasm/update-fluent-0.4.3
Update to fluent 0.4.3
2018-02-09 09:33:52 -08:00
Staś Małolepszy
71ad81a67d Update to fluent 0.4.3 2018-02-09 15:22:41 +01:00
Juan Esteban Ajsivinac Sián
9a1852ea05 Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send
Localization authors:
- Juan Esteban Ajsivinac Sián <ajtzibsyan@yahoo.com>
2018-02-08 20:10:59 +00:00
Besnik Bleta
629a86de99 Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send
Localization authors:
- Besnik Bleta <besnik@programeshqip.org>
2018-02-08 15:31:55 +00:00
Mozilla Pontoon
3539868683 Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send 2018-02-08 07:50:55 +00:00
Mozilla Pontoon
71d4566df5 Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send 2018-02-08 07:50:52 +00:00
Mozilla Pontoon
caaa613ce9 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send 2018-02-08 07:50:49 +00:00
Danny Coates
cf36a33aea localize other displayed numbers 2018-02-07 19:46:18 -08:00
Danny Coates
a777a808ee renamed localization ctx global to reduce the chance of collision (seen in sentry) 2018-02-07 19:18:11 -08:00
Danny Coates
a78150b7ad disable nanotiming 2018-02-07 18:52:41 -08:00
Danny Coates
deb177c6bb updated availableLanguages and localized progress percentage. fixes #747 2018-02-07 12:09:23 -08:00
Danny Coates
1c5e47b4c4 validate id param without middleware 2018-02-05 17:21:32 -08:00
Danny Coates
aae61f9451 extracted server id validation 2018-02-05 16:37:06 -08:00
Khaled Hosny
807c44f057 Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- Khaled Hosny <khaledhosny@eglug.org>
2018-02-06 00:11:42 +00:00
Khaled Hosny
9782007f7e Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- Khaled Hosny <khaledhosny@eglug.org>
2018-02-05 23:50:56 +00:00
Danny Coates
77d5f1e603 fixed welcome page flicker 2018-02-05 13:28:38 -08:00
Michal Vašíček
81ee6de0a3 Pontoon: Update Czech (cs) localization of Test Pilot: Firefox Send
Localization authors:
- Michal Vašíček <michalvasicek@icloud.com>
- Filip Hruška <fhr@fhrnet.eu>
2018-02-05 18:10:59 +00:00
Danny Coates
82fe65ada2 v2.3.1 2018-02-05 09:34:22 -08:00
Danny Coates
ce79d7b745 reset FileReceiver on cancel. fixes #751 2018-02-05 09:11:42 -08:00
Rhoslyn Prys
755cc4f5ec Pontoon: Update Welsh (cy) localization of Test Pilot: Firefox Send
Localization authors:
- Rhoslyn Prys <rprys@yahoo.com>
2018-02-05 12:12:00 +00:00
Danny Coates
fde4d311e3 added FileReceiver.reset 2018-02-04 18:30:33 -08:00
Sara Todaro
b08f40aaa3 Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Sara Todaro <sara.todaro@mozillaitalia.org>
2018-02-04 21:11:34 +00:00
Victor Bychek
e12ade6b31 Pontoon: Update Russian (ru) localization of Test Pilot: Firefox Send
Localization authors:
- Victor Bychek <a@bychek.ru>
2018-02-04 11:32:24 +00:00
Sara Todaro
43fc80ef41 Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Sara Todaro <sara.todaro@mozillaitalia.org>
- Winfox <openlib@email.it>
2018-02-04 10:50:45 +00:00
Georgianizator
6ca96157f6 Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-02-04 10:12:00 +00:00
jlG
856181ea54 Pontoon: Update Spanish (Spain) (es-ES) localization of Test Pilot: Firefox Send
Localization authors:
- jlG <jlg.l10n.es@gmail.com>
- Jordi Cuevas <jordicuevas@gmail.com>
2018-02-03 23:31:26 +00:00
Jordi Cuevas
f84bd46cdc Pontoon: Update Spanish (Spain) (es-ES) localization of Test Pilot: Firefox Send
Localization authors:
- Jordi Cuevas <jordicuevas@gmail.com>
2018-02-03 23:12:07 +00:00
Victor Bychek
0b43924ee2 Pontoon: Update Russian (ru) localization of Test Pilot: Firefox Send
Localization authors:
- Victor Bychek <a@bychek.ru>
2018-02-03 18:32:13 +00:00
Selim Şumlu
16d3fd3828 Pontoon: Update Turkish (tr) localization of Test Pilot: Firefox Send
Localization authors:
- Selim Şumlu <selim@sum.lu>
2018-02-03 17:50:50 +00:00
Danny Coates
cf5defa6a9 fixed file list time wrap. fixes #749 2018-02-02 10:37:28 -08:00
Danny Coates
3de760db12 added fileTooBig alert to drop handler. fixes #578 2018-02-02 10:22:32 -08:00
Danny Coates
1366f0b68e moved ownedFile.type assignment 2018-02-02 10:15:17 -08:00
Danny Coates
be498e0bd3 /api/info values should be numbers. fixes #748 2018-02-02 10:10:51 -08:00
220 changed files with 19320 additions and 11513 deletions

View File

@@ -1,8 +1,11 @@
node_modules node_modules
.git .git
.tox
.DS_Store .DS_Store
firefox firefox
assets assets
docs docs
public public
test test
coverage
.nyc_output

View File

@@ -1,3 +1,4 @@
dist dist
assets assets
firefox firefox
coverage

5
.gitignore vendored
View File

@@ -1,3 +1,8 @@
node_modules node_modules
coverage
dist dist
.idea .idea
.DS_Store
.nyc_output
.tox
.pytest_cache

3
.nsprc
View File

@@ -1,3 +0,0 @@
{
"exceptions": ["https://nodesecurity.io/advisories/534"]
}

View File

@@ -1,2 +1,3 @@
dist dist
assets/*.js assets/*.js
coverage

7
.pyup.yml Normal file
View File

@@ -0,0 +1,7 @@
# autogenerated pyup.io config file
# see https://pyup.io/docs/configuration/ for all available options
schedule: every week
requirements:
- test/integration/Pipfile
- test/integration/pipenv.txt

View File

@@ -1,5 +1,98 @@
## Change Log ## Change Log
### v2.5.1 (2018/03/12 19:26 +00:00)
- [#789](https://github.com/mozilla/send/pull/789) Fixed #775 : Made text not-selectable (@RCMainak)
### v2.5.0 (2018/03/08 19:31 +00:00)
- [#782](https://github.com/mozilla/send/pull/782) updated docs (@dannycoates)
- [#781](https://github.com/mozilla/send/pull/781) Don't translate URL-safe chars, b64 is doing it for us (@timvisee)
- [#779](https://github.com/mozilla/send/pull/779) implemented crypto polyfills for ms edge (@dannycoates)
### v2.4.1 (2018/02/28 17:05 +00:00)
- [#777](https://github.com/mozilla/send/pull/777) use a separate circle in the progress svg for indefinite progress (@dannycoates)
### v2.4.0 (2018/02/27 01:55 +00:00)
- [#769](https://github.com/mozilla/send/pull/769) removed unsafe-inline styles via svgo-loader (@dannycoates)
- [#767](https://github.com/mozilla/send/pull/767) added coverage artifact to circleci (@dannycoates)
- [#766](https://github.com/mozilla/send/pull/766) Some frontend unit tests [WIP] (@dannycoates)
- [#761](https://github.com/mozilla/send/pull/761) added maxPasswordLength and passwordError messages (@dannycoates)
- [#764](https://github.com/mozilla/send/pull/764) added indefinite progress mode (@dannycoates)
- [#760](https://github.com/mozilla/send/pull/760) refactored css: phase 1 (@dannycoates)
- [#759](https://github.com/mozilla/send/pull/759) Switch en-US FTL file to new syntax (@flodolo)
- [#758](https://github.com/mozilla/send/pull/758) refactored server (@dannycoates)
- [#757](https://github.com/mozilla/send/pull/757) Update to fluent 0.4.3 (@stasm)
### v2.3.0 (2018/02/01 23:27 +00:00)
- [#536](https://github.com/mozilla/send/pull/536) use redis expire event to delete stored data immediately (@ehuggett)
- [#744](https://github.com/mozilla/send/pull/744) Gradient experiment (@dannycoates)
- [#739](https://github.com/mozilla/send/pull/739) added /api/info/:id route (@dannycoates)
- [#737](https://github.com/mozilla/send/pull/737) big refactor (@dannycoates)
- [#722](https://github.com/mozilla/send/pull/722) Add localization note to 'Time' and 'Downloads' string (@flodolo)
- [#721](https://github.com/mozilla/send/pull/721) show download Limits on page; Fixes #661 (@shikhar-scs)
- [#694](https://github.com/mozilla/send/pull/694) Passwords can now be changed (#687) (@himanish-star)
- [#702](https://github.com/mozilla/send/pull/702) Restricted the banner from showing on unsupported browsers (@himanish-star)
- [#701](https://github.com/mozilla/send/pull/701) improved popup for mobile display; Fixes #699 (@shikhar-scs)
- [#683](https://github.com/mozilla/send/pull/683) API changes to accommodate 3rd party clients (@ehuggett)
- [#698](https://github.com/mozilla/send/pull/698) Popup for delete button attached (@himanish-star)
- [#695](https://github.com/mozilla/send/pull/695) Show Warning, Cancel and Redirect on size > 2GB ; fixes #578 (@shikhar-scs)
- [#684](https://github.com/mozilla/send/pull/684) delete btn popup attached (@himanish-star)
- [#686](https://github.com/mozilla/send/pull/686) Hide password while Typing and after Entering: Fixes #670 (@shikhar-scs)
- [#679](https://github.com/mozilla/send/pull/679) changed font to sans sherif: Solves #676 (@shikhar-scs)
- [#693](https://github.com/mozilla/send/pull/693) README: Fix query link for "good first bugs" (@jspam)
- [#685](https://github.com/mozilla/send/pull/685) checkbox now has a hover effect: fixes #635 (@himanish-star)
- [#668](https://github.com/mozilla/send/pull/668) Add possibility to bind to a specific IP address (@TwizzyDizzy)
- [#682](https://github.com/mozilla/send/pull/682) [Docs] - README.md - minor spelling fixes (@tmm2018)
- [#672](https://github.com/mozilla/send/pull/672) Use EXPIRE_SECONDS to calculate file ttl for static content (@derektamsen)
- [#680](https://github.com/mozilla/send/pull/680) adjusted line height of label : fixes #609 (@himanish-star)
### v2.2.2 (2017/12/19 18:06 +00:00)
- [#667](https://github.com/mozilla/send/pull/667) Make develop the default NODE_ENV (@claudijd)
### v2.2.1 (2017/12/08 18:00 +00:00)
- [#665](https://github.com/mozilla/send/pull/665) stop drag target from flickering when dragging over children (@ericawright)
### v2.2.0 (2017/12/06 23:57 +00:00)
- [#654](https://github.com/mozilla/send/pull/654) Multiple download UI (@dannycoates)
- [#650](https://github.com/mozilla/send/pull/650) #634: overwrite appearance of password submit input (@ovlb)
- [#649](https://github.com/mozilla/send/pull/649) #609 share interface: align text in input and button (@ovlb)
### v2.1.2 (2017/11/16 19:03 +00:00)
- [#645](https://github.com/mozilla/send/pull/645) Remove the leak of the password into the console (@laurentj)
### v2.1.0 (2017/11/15 03:07 +00:00)
- [#641](https://github.com/mozilla/send/pull/641) Added experiment for firefox download promo (@dannycoates)
- [#640](https://github.com/mozilla/send/pull/640) use fluent-langneg for subtag support (@dannycoates)
- [#639](https://github.com/mozilla/send/pull/639) wrap number localization in try/catch (@dannycoates)
### v2.0.0 (2017/11/08 05:31 +00:00)
- [#633](https://github.com/mozilla/send/pull/633) Keyboard navigation/visual feedback regression (@ehuggett)
- [#632](https://github.com/mozilla/send/pull/632) display the 'add password' button only when the input field isn't empty (@dannycoates)
- [#626](https://github.com/mozilla/send/pull/626) Partial fix for #623 (@ehuggett)
- [#624](https://github.com/mozilla/send/pull/624) set a default MIME type in file metadata (@ehuggett)
- [#612](https://github.com/mozilla/send/pull/612) Password UI nits (@dannycoates, @ericawright)
- [#617](https://github.com/mozilla/send/pull/617) allow drag and drop if navigating from shared page (@ericawright)
- [#608](https://github.com/mozilla/send/pull/608) disable copying link when password not completed (@ericawright)
- [#605](https://github.com/mozilla/send/pull/605) align the "Password" and "Copy to clipboard" fields. (@ericawright)
- [#582](https://github.com/mozilla/send/pull/582) Add optional password to the download url (@dannycoates)
### v1.2.4 (2017/10/10 17:34 +00:00)
- [#583](https://github.com/mozilla/send/pull/583) Promote the beefy UI to default (@dannycoates)
- [#581](https://github.com/mozilla/send/pull/581) introducing ToC to README.md (@tmm2018)
- [#579](https://github.com/mozilla/send/pull/579) Hide cancel button when upload reaches 100% (@ericawright)
- [#580](https://github.com/mozilla/send/pull/580) Change Favicon in to look better in a variety of cases (@ericawright)
- [#571](https://github.com/mozilla/send/pull/571) Centre logo (@ehuggett)
- [#574](https://github.com/mozilla/send/pull/574) Make upload button focusable (accessibility/tab navigation) (@ehuggett)
### v1.2.0 (2017/09/12 22:42 +00:00)
- [#559](https://github.com/mozilla/send/pull/559) added first A/B experiment (@dannycoates)
- [#542](https://github.com/mozilla/send/pull/542) fix docker link typo (@ehuggett)
- [#541](https://github.com/mozilla/send/pull/541) removed .title and .alt attributes from ftl (@dannycoates)
- [#537](https://github.com/mozilla/send/pull/537) a few changes to make A/B testing easier (@dannycoates)
- [#533](https://github.com/mozilla/send/pull/533) minor UI fixes (@youwenliang)
- [#531](https://github.com/mozilla/send/pull/531) Add CHANGELOG script (@pdehaan)
- [#535](https://github.com/mozilla/send/pull/535) Fixed minimum NodeJS version in README (@LuFlo)
- [#528](https://github.com/mozilla/send/pull/528) adding separators to README (@tmm2018)
### v1.1.1 (2017/08/17 01:29 +00:00) ### v1.1.1 (2017/08/17 01:29 +00:00)
- [#516](https://github.com/mozilla/send/pull/516) cache assets (@dannycoates) - [#516](https://github.com/mozilla/send/pull/516) cache assets (@dannycoates)
- [#520](https://github.com/mozilla/send/pull/520) fix drag & drop (@dannycoates) - [#520](https://github.com/mozilla/send/pull/520) fix drag & drop (@dannycoates)

View File

@@ -1,74 +1,126 @@
Abdalrahman Hwoij
Abhinav Adduri Abhinav Adduri
Adnan Kičin
Alberto Castro
Alexander Slovesnik Alexander Slovesnik
Alfredos-Panagiotis Damkalis
Amin Mahmudian Amin Mahmudian
Andreas Pettersson Andreas Pettersson
Arash Mousavi Arash Mousavi
Artem Polivanchuk
Ashikur Rahman
Balázs Meskó Balázs Meskó
Belayet Hossain Belayet Hossain
Besnik Bleta
Bjørn I Bjørn I
Boopesh Mahendran Boopesh Mahendran
Breana Gonzales
Chuck Harmston Chuck Harmston
Cláudio Esperança Cláudio Esperança
Cristian Silaghi
Cynthia Pereira Cynthia Pereira
Daniel Thorn Daniel Thorn
Daniela Arcese Daniela Arcese
Danny Coates Danny Coates
Derek Tamsen
Edmund Huggett
Elisa X
Emin Mastizada Emin Mastizada
Enol Enol
Erica Erica
Erica Wright Erica Wright
Filip Hruška
Fjoerfoks Fjoerfoks
Francesco Lodolo Francesco Lodolo
Francesco Lodolo [:flod] Francesco Lodolo [:flod]
Frederick Villaluna
Gabriela
Gautam krishna.R Gautam krishna.R
Georgianizator
Gonçalo Matos
Hyeonseok Shin
Håvar Henriksen Håvar Henriksen
Jae Hyeon Park Jae Hyeon Park
Jakub Rychlý Jakub Rychlý
Jamie Jamie
Jim Spentzos Jim Spentzos
Jobava
Johann-S Johann-S
John Gruen John Gruen
Jon Vadillo Jon Vadillo
Jonathan Claudius
Jordi Cuevas
Jordi Serratosa Jordi Serratosa
Juan Esteban Ajsivinac Sián
Juraj Cigáň Juraj Cigáň
Kerim Kalamujić
Khaled Hosny
Kohei Yoshino Kohei Yoshino
Lan Glad Lan Glad
Laurent Jouanneau
Lobodzets
LuFlo
Luiz Carlos de Morais
Luna Jernberg Luna Jernberg
Marcelo Poli Marcelo Poli
Marco Aurélio Marco Aurélio
Mark Heijl
Mark Liang Mark Liang
Marko Andrejić
Matjaž Horvat Matjaž Horvat
Maykon Chagas Maykon Chagas
Melo46
Merike Sell
Michael Köhler Michael Köhler
Michael Wolf Michael Wolf
Michal Stanke Michal Stanke
Michal Vašíček Michal Vašíček
Mozilla Pontoon
Moḥend Belqasem Moḥend Belqasem
Muḥend Belqasem
Nicholas Skinsacos Nicholas Skinsacos
Nihad
Nihad Suljić
Oscar
Peter deHaan Peter deHaan
Pierre Neter Pierre Neter
Pin-guang Chen Pin-guang Chen
Radu Popescu
Rhoslyn Prys Rhoslyn Prys
RickieES
Rizky Ariestiyansyah Rizky Ariestiyansyah
Roberto Alvarado Roberto Alvarado
Rodrigo Rodrigo
Rodrigo Guerra
Rok Žerdin Rok Žerdin
Sahithi Sahithi
Sairam Raavi Sairam Raavi
Sander Lepik
Sandro Sandro
Sara Todaro
Sav22999
Schieck :) Schieck :)
Selim Şumlu Selim Şumlu
Slimane Amiri Slimane Amiri
Soumya Himanish Mohapatra
Staś Małolepszy
Tema
Thomas Dalichow
Théo Chevalier Théo Chevalier
Tiago Morais Morgado
Tomáš Zelina Tomáš Zelina
Ton Ton
Tymur Faradzhev Tymur Faradzhev
Uccen Marzuq
Varghese Thomas Varghese Thomas
Victor Bychek Victor Bychek
Vitaliy Krutko
Weihang Lo Weihang Lo
Wil Clouser Wil Clouser
YFdyh000 YFdyh000
You-Wen Liang (Mark) You-Wen Liang (Mark)
aefgh39622
albertdcastro
alex_mayorga alex_mayorga
ariestiyansyah ariestiyansyah
avelper avelper
@@ -77,16 +129,29 @@ ehuggett
eljuno eljuno
erdem cobanoglu erdem cobanoglu
gautamkrishnar gautamkrishnar
gmontagu
goofy goofy
hello
hi hi
jesferman1993 jesferman1993
jlG
josotrix josotrix
jspam
kenrick95 kenrick95
manxmensch manxmensch
mirzet.omerovic.1992
ravmn ravmn
rcmainak
reza.habibi2008 reza.habibi2008
savemore99.sm
shikhar-scs
siparon siparon
skystar-p skystar-p
tiagomoraismorgado
timvisee
xcffl xcffl
ybouhamam
Μιχάλης Μιχάλης
Марко Костић (Marko Kostić) Марко Костић (Marko Kostić)
صفا الفليج
వీవెన్

View File

@@ -1,9 +1,10 @@
# Firefox Send # Firefox Send
[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=aFFIMHNEWFcrNHJaMU1LRkJnUDhOQkNHMmh2WHBscjJsZHcwK1h0dkhwdz0tLXRpN1RXcysybUtxTFFTVGRtWjVGeHc9PQ==--c56129be8c75941b115c5b5e5d3ed10b3c7dca6b)](https://www.browserstack.com/automate/public-build/aFFIMHNEWFcrNHJaMU1LRkJnUDhOQkNHMmh2WHBscjJsZHcwK1h0dkhwdz0tLXRpN1RXcysybUtxTFFTVGRtWjVGeHc9PQ==--c56129be8c75941b115c5b5e5d3ed10b3c7dca6b)
[![CircleCI](https://img.shields.io/circleci/project/github/mozilla/send.svg)](https://circleci.com/gh/mozilla/send) [![CircleCI](https://img.shields.io/circleci/project/github/mozilla/send.svg)](https://circleci.com/gh/mozilla/send)
[![Available on Test Pilot](https://img.shields.io/badge/available_on-Test_Pilot-0996F8.svg)](https://testpilot.firefox.com/experiments/send) [![Available on Test Pilot](https://img.shields.io/badge/available_on-Test_Pilot-0996F8.svg)](https://testpilot.firefox.com/experiments/send)
**Docs:** [Docker](docs/docker.md), [Metrics](docs/metrics.md) **Docs:** [FAQ](docs/faq.md), [Encryption](docs/encryption.md), [Build](docs/build.md), [Docker](docs/docker.md), [Metrics](docs/metrics.md), [More](docs/)
--- ---
@@ -71,6 +72,8 @@ The server is configured with environment variables. See [server/config.js](serv
Firefox Send localization is managed via [Pontoon](https://pontoon.mozilla.org/projects/test-pilot-firefox-send/), not direct pull requests to the repository. If you want to fix a typo, add a new language, or simply know more about localization, please get in touch with the [existing localization team](https://pontoon.mozilla.org/teams/) for your language or Mozillas [l10n-drivers](https://wiki.mozilla.org/L10n:Mozilla_Team#Mozilla_Corporation) for guidance. Firefox Send localization is managed via [Pontoon](https://pontoon.mozilla.org/projects/test-pilot-firefox-send/), not direct pull requests to the repository. If you want to fix a typo, add a new language, or simply know more about localization, please get in touch with the [existing localization team](https://pontoon.mozilla.org/teams/) for your language or Mozillas [l10n-drivers](https://wiki.mozilla.org/L10n:Mozilla_Team#Mozilla_Corporation) for guidance.
see also [docs/localization.md](docs/localization.md)
--- ---
## Contributing ## Contributing

View File

@@ -91,10 +91,15 @@ export async function setPassword(id, owner_token, keychain) {
return response.ok; return response.ok;
} }
export function uploadFile(encrypted, metadata, verifierB64, keychain) { export function uploadFile(
encrypted,
metadata,
verifierB64,
keychain,
onprogress
) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const upload = { const upload = {
onprogress: function() {},
cancel: function() { cancel: function() {
xhr.abort(); xhr.abort();
}, },
@@ -122,7 +127,7 @@ export function uploadFile(encrypted, metadata, verifierB64, keychain) {
fd.append('data', blob); fd.append('data', blob);
xhr.upload.addEventListener('progress', function(event) { xhr.upload.addEventListener('progress', function(event) {
if (event.lengthComputable) { if (event.lengthComputable) {
upload.onprogress([event.loaded, event.total]); onprogress([event.loaded, event.total]);
} }
}); });
xhr.open('post', '/api/upload', true); xhr.open('post', '/api/upload', true);
@@ -132,82 +137,65 @@ export function uploadFile(encrypted, metadata, verifierB64, keychain) {
return upload; return upload;
} }
function download(id, keychain) { function download(id, keychain, onprogress, canceller) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const download = { canceller.oncancel = function() {
onprogress: function() {}, xhr.abort();
cancel: function() {
xhr.abort();
},
result: new Promise(async function(resolve, reject) {
xhr.addEventListener('loadend', function() {
const authHeader = xhr.getResponseHeader('WWW-Authenticate');
if (authHeader) {
keychain.nonce = parseNonce(authHeader);
}
if (xhr.status === 404) {
return reject(new Error('notfound'));
}
if (xhr.status !== 200) {
return reject(new Error(xhr.status));
}
const blob = new Blob([xhr.response]);
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(blob);
fileReader.onload = function() {
resolve(this.result);
};
});
xhr.addEventListener('progress', function(event) {
if (event.lengthComputable && event.target.status === 200) {
download.onprogress([event.loaded, event.total]);
}
});
const auth = await keychain.authHeader();
xhr.open('get', `/api/download/${id}`);
xhr.setRequestHeader('Authorization', auth);
xhr.responseType = 'blob';
xhr.send();
})
}; };
return new Promise(async function(resolve, reject) {
xhr.addEventListener('loadend', function() {
canceller.oncancel = function() {};
const authHeader = xhr.getResponseHeader('WWW-Authenticate');
if (authHeader) {
keychain.nonce = parseNonce(authHeader);
}
if (xhr.status !== 200) {
return reject(new Error(xhr.status));
}
return download; const blob = new Blob([xhr.response]);
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(blob);
fileReader.onload = function() {
resolve(this.result);
};
});
xhr.addEventListener('progress', function(event) {
if (event.target.status === 200) {
onprogress(event.loaded);
}
});
const auth = await keychain.authHeader();
xhr.open('get', `/api/download/${id}`);
xhr.setRequestHeader('Authorization', auth);
xhr.responseType = 'blob';
xhr.send();
onprogress(0);
});
} }
async function tryDownload(id, keychain, onprogress, tries = 1) { async function tryDownload(id, keychain, onprogress, canceller, tries = 1) {
const dl = download(id, keychain);
dl.onprogress = onprogress;
try { try {
const result = await dl.result; const result = await download(id, keychain, onprogress, canceller);
return result; return result;
} catch (e) { } catch (e) {
if (e.message === '401' && --tries > 0) { if (e.message === '401' && --tries > 0) {
return tryDownload(id, keychain, onprogress, tries); return tryDownload(id, keychain, onprogress, canceller, tries);
} }
throw e; throw e;
} }
} }
export function downloadFile(id, keychain) { export function downloadFile(id, keychain, onprogress) {
let cancelled = false; const canceller = {
function updateProgress(p) { oncancel: function() {} // download() sets this
if (cancelled) { };
// This is a bit of a hack function cancel() {
// We piggyback off of the progress event as a chance to cancel. canceller.oncancel();
// Otherwise wiring the xhr abort up while allowing retries }
// gets pretty nasty. return {
// 'this' here is the object returned by download(id, keychain) cancel,
return this.cancel(); result: tryDownload(id, keychain, onprogress, canceller, 2)
}
dl.onprogress(p);
}
const dl = {
onprogress: function() {},
cancel: function() {
cancelled = true;
},
result: tryDownload(id, keychain, updateProgress, 2)
}; };
return dl;
} }

268
app/base.css Normal file
View File

@@ -0,0 +1,268 @@
:root {
--pageBGColor: #fff;
--primaryControlBGColor: #0297f8;
--primaryControlFGColor: #fff;
--primaryControlHoverColor: #0287e8;
--inputTextColor: #737373;
--errorColor: #d70022;
--linkColor: #0094fb;
--textColor: #0c0c0d;
--lightTextColor: #737373;
--successControlBGColor: #05a700;
--successControlFGColor: #fff;
}
html {
background: url('../assets/send_bg.svg');
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'segoe ui',
'helvetica neue', helvetica, ubuntu, roboto, noto, arial, sans-serif;
font-weight: 200;
background-size: 110%;
background-repeat: no-repeat;
background-position: center top;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'segoe ui',
'helvetica neue', helvetica, ubuntu, roboto, noto, arial, sans-serif;
display: flex;
flex-direction: column;
margin: 0;
min-height: 100vh;
}
input,
select,
textarea,
button {
font-family: inherit;
margin: 0;
}
a {
text-decoration: none;
}
.main {
flex: auto;
max-width: 650px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
width: 96%;
}
.noscript {
text-align: center;
border: 3px solid var(--errorColor);
border-radius: 6px;
}
.btn {
font-size: 15px;
font-weight: 500;
color: var(--primaryControlFGColor);
cursor: pointer;
text-align: center;
background: var(--primaryControlBGColor);
border: 1px solid var(--primaryControlBGColor);
border-radius: 5px;
}
.btn:hover {
background-color: var(--primaryControlHoverColor);
}
.btn--cancel {
color: var(--errorColor);
background: var(--pageBGColor);
font-size: 15px;
border: 0;
cursor: pointer;
text-decoration: underline;
}
.btn--cancel:disabled {
text-decoration: none;
cursor: auto;
}
.btn--cancel:hover {
background-color: var(--pageBGColor);
}
.input {
flex: 2 0 auto;
border: 1px solid var(--primaryControlBGColor);
border-radius: 6px 0 0 6px;
font-size: 20px;
color: var(--inputTextColor);
font-family: 'SF Pro Text', sans-serif;
letter-spacing: 0;
line-height: 23px;
font-weight: 300;
height: 46px;
padding-left: 10px;
padding-right: 10px;
}
.input--error {
border-color: var(--errorColor);
}
.input--noBtn {
border-radius: 6px;
}
.inputBtn {
flex: auto;
background: var(--primaryControlBGColor);
border-radius: 0 6px 6px 0;
border: 1px solid var(--primaryControlBGColor);
color: var(--primaryControlFGColor);
cursor: pointer;
/* Force flat button look */
/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
appearance: none;
font-size: 15px;
padding-bottom: 3px;
padding-left: 10px;
padding-right: 10px;
white-space: nowrap;
}
.inputBtn:disabled {
cursor: auto;
}
.inputBtn:hover {
background-color: var(--primaryControlHoverColor);
}
.inputBtn--hidden {
display: none;
}
.cursor--pointer {
cursor: pointer;
}
.link {
color: var(--linkColor);
text-decoration: none;
}
.link:focus,
.link:active,
.link:hover {
color: var(--primaryControlHoverColor);
}
.link--action {
text-decoration: underline;
text-align: center;
}
.page {
margin: 0 auto 30px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
}
.progressSection {
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
font-size: 15px;
}
.progressSection__text {
color: var(--lightTextColor);
letter-spacing: -0.4px;
margin-top: 24px;
margin-bottom: 74px;
}
.effect--fadeOut {
opacity: 0;
animation: fadeout 200ms linear;
}
@keyframes fadeout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.effect--fadeIn {
opacity: 1;
animation: fadein 200ms linear;
}
@keyframes fadein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.error {
color: var(--errorColor);
}
.title {
font-size: 33px;
line-height: 40px;
margin: 20px auto;
text-align: center;
max-width: 520px;
font-family: 'SF Pro Text', sans-serif;
word-wrap: break-word;
}
.description {
font-size: 15px;
line-height: 23px;
max-width: 630px;
text-align: center;
margin: 0 auto 60px;
color: var(--textColor);
width: 92%;
}
@media (max-device-width: 768px), (max-width: 768px) {
.description {
margin: 0 auto 25px;
}
}
@media (max-device-width: 520px), (max-width: 520px) {
.input {
font-size: 22px;
padding: 10px 10px;
border-radius: 6px 6px 0 0;
}
.inputBtn {
border-radius: 0 0 6px 6px;
flex: 0 1 65px;
}
.input--noBtn {
border-radius: 6px;
}
}

View File

@@ -1,3 +1,6 @@
/* global MAXFILESIZE */
const { bytes } = require('./utils');
export default function(state, emitter) { export default function(state, emitter) {
emitter.on('DOMContentLoaded', () => { emitter.on('DOMContentLoaded', () => {
document.body.addEventListener('dragover', event => { document.body.addEventListener('dragover', event => {
@@ -8,15 +11,26 @@ export default function(state, emitter) {
document.body.addEventListener('drop', event => { document.body.addEventListener('drop', event => {
if (state.route === '/' && !state.uploading) { if (state.route === '/' && !state.uploading) {
event.preventDefault(); event.preventDefault();
document.querySelector('.upload-window').classList.remove('ondrag'); document
.querySelector('.uploadArea')
.classList.remove('uploadArea--dragging');
const target = event.dataTransfer; const target = event.dataTransfer;
if (target.files.length === 0) { if (target.files.length === 0) {
return; return;
} }
if (target.files.length > 1 || target.files[0].size === 0) { if (target.files.length > 1) {
// eslint-disable-next-line no-alert
return alert(state.translate('uploadPageMultipleFilesAlert')); return alert(state.translate('uploadPageMultipleFilesAlert'));
} }
const file = target.files[0]; const file = target.files[0];
if (file.size === 0) {
return;
}
if (file.size > MAXFILESIZE) {
// eslint-disable-next-line no-alert
alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
return;
}
emitter.emit('upload', { file, type: 'drop' }); emitter.emit('upload', { file, type: 'drop' });
} }
}); });

View File

@@ -18,7 +18,7 @@ const experiments = {
}, },
eligible: function() { eligible: function() {
return ( return (
!/firefox/i.test(navigator.userAgent) && !/firefox|fxios/i.test(navigator.userAgent) &&
document.querySelector('html').lang === 'en-US' document.querySelector('html').lang === 'en-US'
); );
}, },

View File

@@ -5,8 +5,7 @@ import {
delay, delay,
fadeOut, fadeOut,
openLinksInNewTab, openLinksInNewTab,
percent, percent
saveFile
} from './utils'; } from './utils';
import * as metrics from './metrics'; import * as metrics from './metrics';
@@ -19,7 +18,7 @@ export default function(state, emitter) {
} }
async function checkFiles() { async function checkFiles() {
const files = state.storage.files; const files = state.storage.files.slice();
let rerender = false; let rerender = false;
for (const file of files) { for (const file of files) {
const oldLimit = file.dlimit; const oldLimit = file.dlimit;
@@ -100,28 +99,32 @@ export default function(state, emitter) {
try { try {
metrics.startedUpload({ size, type }); metrics.startedUpload({ size, type });
const ownedFile = await sender.upload(); const ownedFile = await sender.upload();
ownedFile.type = type;
state.storage.totalUploads += 1; state.storage.totalUploads += 1;
metrics.completedUpload(ownedFile); metrics.completedUpload(ownedFile);
state.storage.addFile(ownedFile); state.storage.addFile(ownedFile);
const cancelBtn = document.getElementById('cancel-upload');
document.getElementById('cancel-upload').hidden = 'hidden'; if (cancelBtn) {
cancelBtn.hidden = 'hidden';
}
await delay(1000); await delay(1000);
await fadeOut('upload-progress'); await fadeOut('.page');
openLinksInNewTab(links, false);
emitter.emit('pushState', `/share/${ownedFile.id}`); emitter.emit('pushState', `/share/${ownedFile.id}`);
} catch (err) { } catch (err) {
console.error(err);
if (err.message === '0') { if (err.message === '0') {
//cancelled. do nothing //cancelled. do nothing
metrics.cancelledUpload({ size, type }); metrics.cancelledUpload({ size, type });
return render(); render();
} else {
// eslint-disable-next-line no-console
console.error(err);
state.raven.captureException(err);
metrics.stoppedUpload({ size, type, err });
emitter.emit('pushState', '/error');
} }
state.raven.captureException(err);
metrics.stoppedUpload({ size, type, err });
emitter.emit('pushState', '/error');
} finally { } finally {
openLinksInNewTab(links, false);
state.uploading = false; state.uploading = false;
state.transfer = null; state.transfer = null;
} }
@@ -129,11 +132,18 @@ export default function(state, emitter) {
emitter.on('password', async ({ password, file }) => { emitter.on('password', async ({ password, file }) => {
try { try {
state.settingPassword = true;
render();
await file.setPassword(password); await file.setPassword(password);
state.storage.writeFile(file); state.storage.writeFile(file);
metrics.addedPassword({ size: file.size }); metrics.addedPassword({ size: file.size });
await delay(1000);
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console
console.error(err); console.error(err);
state.passwordSetError = err;
} finally {
state.settingPassword = false;
} }
render(); render();
}); });
@@ -143,8 +153,6 @@ export default function(state, emitter) {
const receiver = new FileReceiver(file); const receiver = new FileReceiver(file);
try { try {
await receiver.getMetadata(); await receiver.getMetadata();
receiver.on('progress', updateProgress);
receiver.on('decrypting', render);
state.transfer = receiver; state.transfer = receiver;
} catch (e) { } catch (e) {
if (e.message === '401') { if (e.message === '401') {
@@ -158,36 +166,41 @@ export default function(state, emitter) {
}); });
emitter.on('download', async file => { emitter.on('download', async file => {
state.transfer.on('progress', render); state.transfer.on('progress', updateProgress);
state.transfer.on('decrypting', render); state.transfer.on('decrypting', render);
const links = openLinksInNewTab(); const links = openLinksInNewTab();
const size = file.size; const size = file.size;
try { try {
const start = Date.now(); const start = Date.now();
metrics.startedDownload({ size: file.size, ttl: file.ttl }); metrics.startedDownload({ size: file.size, ttl: file.ttl });
const f = await state.transfer.download(); const dl = state.transfer.download();
render();
await dl;
const time = Date.now() - start; const time = Date.now() - start;
const speed = size / (time / 1000); const speed = size / (time / 1000);
await delay(1000); await delay(1000);
await fadeOut('download-progress'); await fadeOut('.page');
saveFile(f);
state.storage.totalDownloads += 1; state.storage.totalDownloads += 1;
state.transfer.reset();
metrics.completedDownload({ size, time, speed }); metrics.completedDownload({ size, time, speed });
emitter.emit('pushState', '/completed'); emitter.emit('pushState', '/completed');
} catch (err) { } catch (err) {
if (err.message === '0') { if (err.message === '0') {
// download cancelled // download cancelled
return render(); state.transfer.reset();
render();
} else {
// eslint-disable-next-line no-console
console.error(err);
state.transfer = null;
const location = err.message === '404' ? '/404' : '/error';
if (location === '/error') {
state.raven.captureException(err);
metrics.stoppedDownload({ size, err });
}
emitter.emit('pushState', location);
} }
console.error(err);
const location = err.message === 'notfound' ? '/404' : '/error';
if (location === '/error') {
state.raven.captureException(err);
metrics.stoppedDownload({ size, err });
}
emitter.emit('pushState', location);
} finally { } finally {
state.transfer = null;
openLinksInNewTab(links, false); openLinksInNewTab(links, false);
} }
}); });

View File

@@ -11,17 +11,17 @@ export default class FileReceiver extends Nanobus {
this.keychain.setPassword(fileInfo.password, fileInfo.url); this.keychain.setPassword(fileInfo.password, fileInfo.url);
} }
this.fileInfo = fileInfo; this.fileInfo = fileInfo;
this.fileDownload = null; this.reset();
this.msg = 'fileSizeProgress';
this.state = 'initialized';
this.progress = [0, 1];
this.cancelled = false;
} }
get progressRatio() { get progressRatio() {
return this.progress[0] / this.progress[1]; return this.progress[0] / this.progress[1];
} }
get progressIndefinite() {
return this.state !== 'downloading';
}
get sizes() { get sizes() {
return { return {
partialSize: bytes(this.progress[0]), partialSize: bytes(this.progress[0]),
@@ -30,56 +30,95 @@ export default class FileReceiver extends Nanobus {
} }
cancel() { cancel() {
this.cancelled = true; if (this.downloadRequest) {
if (this.fileDownload) { this.downloadRequest.cancel();
this.fileDownload.cancel();
} }
} }
reset() {
this.msg = 'fileSizeProgress';
this.state = 'initialized';
this.progress = [0, 1];
}
async getMetadata() { async getMetadata() {
const meta = await metadata(this.fileInfo.id, this.keychain); const meta = await metadata(this.fileInfo.id, this.keychain);
if (meta) { this.keychain.setIV(meta.iv);
this.keychain.setIV(meta.iv); this.fileInfo.name = meta.name;
this.fileInfo.name = meta.name; this.fileInfo.type = meta.type;
this.fileInfo.type = meta.type; this.fileInfo.iv = meta.iv;
this.fileInfo.iv = meta.iv; this.fileInfo.size = +meta.size;
this.fileInfo.size = meta.size; this.state = 'ready';
this.state = 'ready';
return;
}
this.state = 'invalid';
return;
} }
async download() { async download(noSave = false) {
this.state = 'downloading'; this.state = 'downloading';
this.emit('progress', this.progress); this.downloadRequest = await downloadFile(
this.fileInfo.id,
this.keychain,
p => {
this.progress = [p, this.fileInfo.size];
this.emit('progress');
}
);
try { try {
const download = await downloadFile(this.fileInfo.id, this.keychain); const ciphertext = await this.downloadRequest.result;
download.onprogress = p => { this.downloadRequest = null;
this.progress = p;
this.emit('progress', p);
};
this.fileDownload = download;
const ciphertext = await download.result;
this.fileDownload = null;
this.msg = 'decryptingFile'; this.msg = 'decryptingFile';
this.state = 'decrypting'; this.state = 'decrypting';
this.emit('decrypting'); this.emit('decrypting');
const plaintext = await this.keychain.decryptFile(ciphertext); const plaintext = await this.keychain.decryptFile(ciphertext);
if (this.cancelled) { if (!noSave) {
throw new Error(0); await saveFile({
plaintext,
name: decodeURIComponent(this.fileInfo.name),
type: this.fileInfo.type
});
} }
this.msg = 'downloadFinish'; this.msg = 'downloadFinish';
this.state = 'complete'; this.state = 'complete';
return {
plaintext,
name: decodeURIComponent(this.fileInfo.name),
type: this.fileInfo.type
};
} catch (e) { } catch (e) {
this.state = 'invalid'; this.downloadRequest = null;
throw e; throw e;
} }
} }
} }
async function saveFile(file) {
return new Promise(function(resolve, reject) {
const dataView = new DataView(file.plaintext);
const blob = new Blob([dataView], { type: file.type });
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, file.name);
return resolve();
} else if (/iPhone|fxios/i.test(navigator.userAgent)) {
// This method is much slower but createObjectURL
// is buggy on iOS
const reader = new FileReader();
reader.addEventListener('loadend', function() {
if (reader.error) {
return reject(reader.error);
}
if (reader.result) {
const a = document.createElement('a');
a.href = reader.result;
a.download = file.name;
document.body.appendChild(a);
a.click();
}
resolve();
});
reader.readAsDataURL(blob);
} else {
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = file.name;
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(downloadUrl);
setTimeout(resolve, 100);
}
});
}

View File

@@ -9,17 +9,18 @@ export default class FileSender extends Nanobus {
constructor(file) { constructor(file) {
super('FileSender'); super('FileSender');
this.file = file; this.file = file;
this.uploadRequest = null;
this.msg = 'importingFile';
this.progress = [0, 1];
this.cancelled = false;
this.keychain = new Keychain(); this.keychain = new Keychain();
this.reset();
} }
get progressRatio() { get progressRatio() {
return this.progress[0] / this.progress[1]; return this.progress[0] / this.progress[1];
} }
get progressIndefinite() {
return ['fileSizeProgress', 'notifyUploadDone'].indexOf(this.msg) === -1;
}
get sizes() { get sizes() {
return { return {
partialSize: bytes(this.progress[0]), partialSize: bytes(this.progress[0]),
@@ -27,6 +28,13 @@ export default class FileSender extends Nanobus {
}; };
} }
reset() {
this.uploadRequest = null;
this.msg = 'importingFile';
this.progress = [0, 1];
this.cancelled = false;
}
cancel() { cancel() {
this.cancelled = true; this.cancelled = true;
if (this.uploadRequest) { if (this.uploadRequest) {
@@ -67,13 +75,14 @@ export default class FileSender extends Nanobus {
encrypted, encrypted,
metadata, metadata,
authKeyB64, authKeyB64,
this.keychain this.keychain,
p => {
this.progress = p;
this.emit('progress');
}
); );
this.msg = 'fileSizeProgress'; this.msg = 'fileSizeProgress';
this.uploadRequest.onprogress = p => { this.emit('progress'); // HACK to kick MS Edge
this.progress = p;
this.emit('progress', p);
};
try { try {
const result = await this.uploadRequest.result; const result = await this.uploadRequest.result;
const time = Date.now() - start; const time = Date.now() - start;
@@ -86,7 +95,6 @@ export default class FileSender extends Nanobus {
url: `${result.url}#${secretKey}`, url: `${result.url}#${secretKey}`,
name: this.file.name, name: this.file.name,
size: this.file.size, size: this.file.size,
type: this.file.type, //TODO 'click' ?
time: time, time: time,
speed: this.file.size / (time / 1000), speed: this.file.size / (time / 1000),
createdAt: Date.now(), createdAt: Date.now(),

16
app/main.css Normal file
View File

@@ -0,0 +1,16 @@
@import './base.css';
@import './templates/header/header.css';
@import './templates/downloadButton/downloadButton.css';
@import './templates/progress/progress.css';
@import './templates/passwordInput/passwordInput.css';
@import './templates/downloadPassword/downloadPassword.css';
@import './templates/setPasswordSection/setPasswordSection.css';
@import './templates/footer/footer.css';
@import './templates/fxPromo/fxPromo.css';
@import './templates/selectbox/selectbox.css';
@import './templates/fileList/fileList.css';
@import './templates/file/file.css';
@import './templates/popup/popup.css';
@import './pages/welcome/welcome.css';
@import './pages/share/share.css';
@import './pages/unsupported/unsupported.css';

View File

@@ -1,10 +1,11 @@
import 'fast-text-encoding'; // MS Edge support
import 'fluent-intl-polyfill'; import 'fluent-intl-polyfill';
import app from './routes'; import app from './routes';
import locale from '../common/locales'; import locale from '../common/locales';
import fileManager from './fileManager'; import fileManager from './fileManager';
import dragManager from './dragManager'; import dragManager from './dragManager';
import pasteManager from './pasteManager';
import { canHasSend } from './utils'; import { canHasSend } from './utils';
import assets from '../common/assets';
import storage from './storage'; import storage from './storage';
import metrics from './metrics'; import metrics from './metrics';
import experiments from './experiments'; import experiments from './experiments';
@@ -30,10 +31,7 @@ app.use((state, emitter) => {
) { ) {
unsupportedReason = 'outdated'; unsupportedReason = 'outdated';
} }
if (/edge\/\d+/i.test(navigator.userAgent)) { const ok = await canHasSend();
unsupportedReason = 'edge';
}
const ok = await canHasSend(assets.get('cryptofill.js'));
if (!ok) { if (!ok) {
unsupportedReason = /firefox/i.test(navigator.userAgent) unsupportedReason = /firefox/i.test(navigator.userAgent)
? 'outdated' ? 'outdated'
@@ -51,5 +49,6 @@ app.use(metrics);
app.use(fileManager); app.use(fileManager);
app.use(dragManager); app.use(dragManager);
app.use(experiments); app.use(experiments);
app.use(pasteManager);
app.mount('body'); app.mount('body');

View File

@@ -16,16 +16,22 @@ export default class OwnedFile {
this.ownerToken = obj.ownerToken; this.ownerToken = obj.ownerToken;
this.dlimit = obj.dlimit || 1; this.dlimit = obj.dlimit || 1;
this.dtotal = obj.dtotal || 0; this.dtotal = obj.dtotal || 0;
this.keychain = new Keychain(obj.secretKey); this.keychain = new Keychain(obj.secretKey, obj.nonce);
this._hasPassword = !!obj.hasPassword; this._hasPassword = !!obj.hasPassword;
} }
async setPassword(password) { async setPassword(password) {
this.password = password; try {
this._hasPassword = true; this.password = password;
this.keychain.setPassword(password, this.url); this._hasPassword = true;
const result = await setPassword(this.id, this.ownerToken, this.keychain); this.keychain.setPassword(password, this.url);
return result; const result = await setPassword(this.id, this.ownerToken, this.keychain);
return result;
} catch (e) {
this.password = null;
this._hasPassword = false;
throw e;
}
} }
del() { del() {
@@ -53,6 +59,7 @@ export default class OwnedFile {
if (e.message === '404') { if (e.message === '404') {
this.dtotal = this.dlimit; this.dtotal = this.dlimit;
} }
// ignore other errors
} }
} }

View File

@@ -1,6 +1,5 @@
const html = require('choo/html'); const html = require('choo/html');
module.exports = function() { module.exports = function() {
const div = html`<div></div>`; return html`<div></div>`;
return div;
}; };

View File

@@ -1,34 +0,0 @@
const html = require('choo/html');
const progress = require('../templates/progress');
const { fadeOut } = require('../utils');
module.exports = function(state, emit) {
const div = html`
<div id="page-one">
<div id="download" class="fadeIn">
<div id="download-progress">
<div id="dl-title" class="title">
${state.translate('downloadFinish')}
</div>
<div class="description"></div>
${progress(1)}
<div class="upload">
<div class="progress-text"></div>
</div>
</div>
<a class="send-new"
data-state="completed"
href="/"
onclick=${sendNew}>${state.translate('sendYourFilesLink')}</a>
</div>
</div>
`;
async function sendNew(e) {
e.preventDefault();
await fadeOut('download');
emit('pushState', '/');
}
return div;
};

View File

@@ -0,0 +1,26 @@
const html = require('choo/html');
const progress = require('../../templates/progress');
const { fadeOut } = require('../../utils');
module.exports = function(state, emit) {
return html`
<div class="page effect--fadeIn">
<div class="title">
${state.translate('downloadFinish')}
</div>
<div class="description"></div>
${progress(1)}
<div class="progressSection">
<div class="progressSection__text"></div>
</div>
<a class="link link--action"
href="/"
onclick=${sendNew}>${state.translate('sendYourFilesLink')}</a>
</div>`;
async function sendNew(e) {
e.preventDefault();
await fadeOut('.page');
emit('pushState', '/');
}
};

View File

@@ -1,46 +0,0 @@
const html = require('choo/html');
const progress = require('../templates/progress');
const { bytes } = require('../utils');
module.exports = function(state, emit) {
const transfer = state.transfer;
const cancelBtn = html`
<button
id="cancel-upload"
title="${state.translate('deletePopupCancel')}"
onclick=${cancel}>
${state.translate('deletePopupCancel')}
</button>`;
const div = html`
<div id="page-one">
<div id="download">
<div id="download-progress" class="fadeIn">
<div id="dl-title" class="title">
${state.translate('downloadingPageProgress', {
filename: state.fileInfo.name,
size: bytes(state.fileInfo.size)
})}
</div>
<div class="description">
${state.translate('downloadingPageMessage')}
</div>
${progress(transfer.progressRatio)}
<div class="upload">
<div class="progress-text">
${state.translate(transfer.msg, transfer.sizes)}
</div>
${transfer.state === 'downloading' ? cancelBtn : null}
</div>
</div>
</div>
</div>
`;
function cancel() {
const btn = document.getElementById('cancel-upload');
btn.remove();
emit('cancel');
}
return div;
};

View File

@@ -0,0 +1,42 @@
const html = require('choo/html');
const progress = require('../../templates/progress');
const { bytes } = require('../../utils');
module.exports = function(state, emit) {
const transfer = state.transfer;
const cancelBtn = html`
<button
id="cancel"
class="btn btn--cancel"
title="${state.translate('deletePopupCancel')}"
onclick=${cancel}>
${state.translate('deletePopupCancel')}
</button>`;
return html`
<div class="page effect--fadeIn">
<div class="title">
${state.translate('downloadingPageProgress', {
filename: state.fileInfo.name,
size: bytes(state.fileInfo.size)
})}
</div>
<div class="description">
${state.translate('downloadingPageMessage')}
</div>
${progress(transfer.progressRatio, transfer.progressIndefinite)}
<div class="progressSection">
<div class="progressSection__text">
${state.translate(transfer.msg, transfer.sizes)}
</div>
${transfer.state === 'downloading' ? cancelBtn : null}
</div>
</div>
`;
function cancel() {
const btn = document.getElementById('cancel');
btn.remove();
emit('cancel');
}
};

View File

@@ -1,10 +1,10 @@
const html = require('choo/html'); const html = require('choo/html');
const assets = require('../../common/assets'); const assets = require('../../../common/assets');
module.exports = function(state) { module.exports = function(state) {
return html` return html`
<div id="upload-error"> <div class="page">
<div class="title">${state.translate('errorPageHeader')}</div> <div class="title">${state.translate('errorPageHeader')}</div>
<img id="upload-error-img" src="${assets.get('illustration_error.svg')}"/> <img src="${assets.get('illustration_error.svg')}"/>
</div>`; </div>`;
}; };

View File

@@ -1,34 +1,32 @@
const html = require('choo/html'); const html = require('choo/html');
const raw = require('choo/html/raw');
function replaceLinks(str, urls) {
let i = -1;
const s = str.replace(/<a>([^<]+)<\/a>/g, (m, v) => {
i++;
return `<a href="${urls[i]}">${v}</a>`;
});
return [`<div class="description">${s}</div>`];
}
module.exports = function(state) { module.exports = function(state) {
const div = html` return html`
<div id="page-one"> <div>
<div id="legal"> <div class="title">${state.translate('legalHeader')}</div>
<div class="title">${state.translate('legalHeader')}</div> ${raw(
${html( replaceLinks(state.translate('legalNoticeTestPilot'), [
replaceLinks(state.translate('legalNoticeTestPilot'), [ 'https://testpilot.firefox.com/terms',
'https://testpilot.firefox.com/terms', 'https://testpilot.firefox.com/privacy',
'https://testpilot.firefox.com/privacy', 'https://testpilot.firefox.com/experiments/send'
'https://testpilot.firefox.com/experiments/send' ])
]) )}
)} ${raw(
${html( replaceLinks(state.translate('legalNoticeMozilla'), [
replaceLinks(state.translate('legalNoticeMozilla'), [ 'https://www.mozilla.org/privacy/websites/',
'https://www.mozilla.org/privacy/websites/', 'https://www.mozilla.org/about/legal/terms/mozilla/'
'https://www.mozilla.org/about/legal/terms/mozilla/' ])
]) )}
)}
</div>
</div> </div>
`; `;
return div;
}; };
function replaceLinks(str, urls) {
let i = 0;
const s = str.replace(
/<a>([^<]+)<\/a>/g,
(m, v) => `<a href="${urls[i++]}">${v}</a>`
);
return `<div class="description">${s}</div>`;
}

View File

@@ -1,21 +0,0 @@
const html = require('choo/html');
const assets = require('../../common/assets');
module.exports = function(state) {
const div = html`
<div id="page-one">
<div id="download">
<div class="title">${state.translate('expiredPageHeader')}</div>
<div class="share-window">
<img src="${assets.get('illustration_expired.svg')}" id="expired-img">
</div>
<div class="expired-description">
${state.translate('uploadPageExplainer')}
</div>
<a class="send-new" href="/" data-state="notfound">
${state.translate('sendYourFilesLink')}
</a>
</div>
</div>`;
return div;
};

View File

@@ -0,0 +1,16 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
module.exports = function(state) {
return html`
<div class="page">
<div class="title">${state.translate('expiredPageHeader')}</div>
<img src="${assets.get('illustration_expired.svg')}" id="expired-img">
<div class="description">
${state.translate('uploadPageExplainer')}
</div>
<a class="link link--action" href="/">
${state.translate('sendYourFilesLink')}
</a>
</div>`;
};

View File

@@ -1,44 +0,0 @@
const html = require('choo/html');
const assets = require('../../common/assets');
const { bytes } = require('../utils');
module.exports = function(state, pageAction) {
const fileInfo = state.fileInfo;
const size = fileInfo.size
? state.translate('downloadFileSize', { size: bytes(fileInfo.size) })
: '';
const title = fileInfo.name
? state.translate('downloadFileName', { filename: fileInfo.name })
: state.translate('downloadFileTitle');
const info = html`
<div id="dl-file"
data-nonce="${fileInfo.nonce}"
data-requires-password="${fileInfo.requiresPassword}"></div>`;
if (!pageAction) {
return info;
}
const div = html`
<div id="page-one">
<div id="download">
<div id="download-page-one">
<div class="title">
<span>${title}</span>
<span id="dl-filesize">${' ' + size}</span>
</div>
<div class="description">${state.translate('downloadMessage')}</div>
<img
src="${assets.get('illustration_download.svg')}"
id="download-img"
title="${state.translate('downloadAltText')}"/>
${pageAction}
</div>
<a class="send-new" href="/">${state.translate('sendYourFilesLink')}</a>
</div>
${info}
</div>
`;
return div;
};

View File

@@ -0,0 +1,40 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
const { bytes } = require('../../utils');
module.exports = function(state, pageAction) {
const fileInfo = state.fileInfo;
const size = fileInfo.size
? state.translate('downloadFileSize', { size: bytes(fileInfo.size) })
: '';
const title = fileInfo.name
? state.translate('downloadFileName', { filename: fileInfo.name })
: state.translate('downloadFileTitle');
const info = html`
<div id="dl-file"
data-nonce="${fileInfo.nonce}"
data-requires-password="${fileInfo.requiresPassword}"></div>`;
if (!pageAction) {
return info;
}
return html`
<div class="page">
<div class="title">
<span>${title}</span>
<span>${' ' + size}</span>
</div>
<div class="description">${state.translate('downloadMessage')}</div>
<img
src="${assets.get('illustration_download.svg')}"
title="${state.translate('downloadAltText')}"/>
${pageAction}
<a class="link link--action" href="/">
${state.translate('sendYourFilesLink')}
</a>
${info}
</div>
`;
};

View File

@@ -1,126 +0,0 @@
/* global EXPIRE_SECONDS */
const html = require('choo/html');
const assets = require('../../common/assets');
const notFound = require('./notFound');
const uploadPasswordSet = require('../templates/uploadPasswordSet');
const uploadPasswordUnset = require('../templates/uploadPasswordUnset');
const selectbox = require('../templates/selectbox');
const { allowedCopy, delay, fadeOut } = require('../utils');
function expireInfo(file, translate, emit) {
const hours = Math.floor(EXPIRE_SECONDS / 60 / 60);
const el = html([
`<div>${translate('expireInfo', {
downloadCount: '<select></select>',
timespan: translate('timespanHours', { num: hours })
})}</div>`
]);
const select = el.querySelector('select');
const options = [1, 2, 3, 4, 5, 20].filter(i => i > (file.dtotal || 0));
const t = num => translate('downloadCount', { num });
const changed = value => emit('changeLimit', { file, value });
select.parentNode.replaceChild(
selectbox(file.dlimit || 1, options, t, changed),
select
);
return el;
}
module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id);
if (!file) {
return notFound(state, emit);
}
const passwordSection = file.hasPassword
? uploadPasswordSet(state, emit)
: uploadPasswordUnset(state, emit);
const div = html`
<div id="share-link" class="fadeIn">
<div class="title">${expireInfo(file, state.translate, emit)}</div>
<div id="share-window">
<div id="copy-text">
${state.translate('copyUrlFormLabelWithName', { filename: file.name })}
</div>
<div id="copy">
<input id="link" type="url" value="${file.url}" readonly="true"/>
<button id="copy-btn"
class="btn"
title="${state.translate('copyUrlFormButton')}"
onclick=${copyLink}>${state.translate('copyUrlFormButton')}</button>
</div>
${passwordSection}
<button id="delete-file"
class="btn"
title="${state.translate('deleteFileButton')}"
onclick=${showPopup}>${state.translate('deleteFileButton')}
</button>
<div id="deletePopup" class="popup">
<div class="popuptext" onblur=${cancel} tabindex="-1">
<div class="popup-message">${state.translate('deletePopupText')}
</div>
<div class="popup-action">
<span class="popup-no" onclick=${cancel}>
${state.translate('deletePopupCancel')}
</span>
<span class="popup-yes" onclick=${deleteFile}>
${state.translate('deletePopupYes')}
</span>
</div>
</div>
</div>
<a class="send-new"
data-state="completed"
href="/"
onclick=${sendNew}>${state.translate('sendAnotherFileLink')}</a>
</div>
</div>
`;
function showPopup() {
const popupText = document.querySelector('.popuptext');
popupText.classList.add('show');
popupText.focus();
}
function cancel(e) {
e.stopPropagation();
const popupText = document.querySelector('.popuptext');
popupText.classList.remove('show');
}
async function sendNew(e) {
e.preventDefault();
await fadeOut('share-link');
emit('pushState', '/');
}
async function copyLink() {
if (allowedCopy()) {
emit('copy', { url: file.url, location: 'success-screen' });
const input = document.getElementById('link');
input.disabled = true;
const copyBtn = document.getElementById('copy-btn');
copyBtn.disabled = true;
copyBtn.classList.add('success');
copyBtn.replaceChild(
html`<img src="${assets.get('check-16.svg')}" class="icon-check">`,
copyBtn.firstChild
);
await delay(2000);
input.disabled = false;
if (!copyBtn.parentNode.classList.contains('wait-password')) {
copyBtn.disabled = false;
}
copyBtn.classList.remove('success');
copyBtn.textContent = state.translate('copyUrlFormButton');
}
}
async function deleteFile() {
emit('delete', { file, location: 'success-screen' });
await fadeOut('share-link');
emit('pushState', '/');
}
return div;
};

112
app/pages/share/index.js Normal file
View File

@@ -0,0 +1,112 @@
/* global EXPIRE_SECONDS */
const html = require('choo/html');
const raw = require('choo/html/raw');
const assets = require('../../../common/assets');
const notFound = require('../notFound');
const setPasswordSection = require('../../templates/setPasswordSection');
const selectbox = require('../../templates/selectbox');
const deletePopup = require('../../templates/popup');
const { allowedCopy, delay, fadeOut } = require('../../utils');
module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id);
if (!file) {
return notFound(state, emit);
}
return html`
<div id="shareWrapper" class="effect--fadeIn">
${expireInfo(file, state.translate, emit)}
<div class="sharePage">
<div class="sharePage__copyText">
${state.translate('copyUrlFormLabelWithName', { filename: file.name })}
</div>
<div class="copySection">
<input
id="fileUrl"
class="copySection__url"
type="url"
value="${file.url}"
readonly="true"/>
<button id="copyBtn"
class="inputBtn inputBtn--copy"
title="${state.translate('copyUrlFormButton')}"
onclick=${copyLink}>${state.translate('copyUrlFormButton')}</button>
</div>
${setPasswordSection(state, emit)}
<button
class="btn btn--delete"
title="${state.translate('deleteFileButton')}"
onclick=${showPopup}>${state.translate('deleteFileButton')}
</button>
<div class="sharePage__deletePopup">
${deletePopup(
state.translate('deletePopupText'),
state.translate('deletePopupYes'),
state.translate('deletePopupCancel'),
deleteFile
)}
</div>
<a class="link link--action"
href="/"
onclick=${sendNew}>${state.translate('sendAnotherFileLink')}</a>
</div>
</div>
`;
function showPopup() {
const popup = document.querySelector('.popup');
popup.classList.add('popup--show');
popup.focus();
}
async function sendNew(e) {
e.preventDefault();
await fadeOut('#shareWrapper');
emit('pushState', '/');
}
async function copyLink() {
if (allowedCopy()) {
emit('copy', { url: file.url, location: 'success-screen' });
const input = document.getElementById('fileUrl');
input.disabled = true;
input.classList.add('input--copied');
const copyBtn = document.getElementById('copyBtn');
copyBtn.disabled = true;
copyBtn.classList.add('inputBtn--copied');
copyBtn.replaceChild(
html`<img src="${assets.get('check-16.svg')}" class="cursor--pointer">`,
copyBtn.firstChild
);
await delay(2000);
input.disabled = false;
input.classList.remove('input--copied');
copyBtn.disabled = false;
copyBtn.classList.remove('inputBtn--copied');
copyBtn.textContent = state.translate('copyUrlFormButton');
}
}
async function deleteFile() {
emit('delete', { file, location: 'success-screen' });
await fadeOut('#shareWrapper');
emit('pushState', '/');
}
};
function expireInfo(file, translate, emit) {
const hours = Math.floor(EXPIRE_SECONDS / 60 / 60);
const el = html`<div class="title">${raw(
translate('expireInfo', {
downloadCount: '<select></select>',
timespan: translate('timespanHours', { num: hours })
})
)}</div>`;
const select = el.querySelector('select');
const options = [1, 2, 3, 4, 5, 20].filter(i => i > (file.dtotal || 0));
const t = num => translate('downloadCount', { num });
const changed = value => emit('changeLimit', { file, value });
el.replaceChild(selectbox(file.dlimit || 1, options, t, changed), select);
return el;
}

112
app/pages/share/share.css Normal file
View File

@@ -0,0 +1,112 @@
.sharePage {
margin: 0 auto;
display: flex;
justify-content: center;
flex-direction: column;
width: 100%;
max-width: 640px;
}
.sharePage__copyText {
align-self: flex-start;
margin-top: 60px;
margin-bottom: 10px;
color: var(--textColor);
max-width: 614px;
word-wrap: break-word;
}
.sharePage__deletePopup {
position: relative;
align-self: center;
bottom: 50px;
}
.copySection {
display: flex;
flex-wrap: nowrap;
width: 100%;
}
.copySection__url {
flex: 1;
height: 56px;
border: 1px solid var(--primaryControlBGColor);
border-radius: 6px 0 0 6px;
font-size: 20px;
color: var(--inputTextColor);
font-family: 'SF Pro Text', sans-serif;
letter-spacing: 0;
line-height: 23px;
font-weight: 300;
padding-left: 10px;
}
.copySection__url:disabled {
border: 1px solid var(--successControlBGColor);
background: var(--successControlFGColor);
}
.inputBtn--copy {
flex: 0 1 165px;
padding-bottom: 4px;
}
.input--copied {
border-color: var(--successControlBGColor);
}
.inputBtn--copied,
.inputBtn--copied:hover {
background: var(--successControlBGColor);
border: 1px solid var(--successControlBGColor);
color: var(--successControlFGColor);
}
.btn--delete {
align-self: center;
width: 176px;
height: 44px;
background: #fff;
border-color: rgba(12, 12, 13, 0.3);
margin-top: 50px;
margin-bottom: 12px;
color: #313131;
}
.btn--delete:hover {
background: #efeff1;
}
@media (max-device-width: 768px), (max-width: 768px) {
.copySection {
width: 100%;
}
.copySection__url {
font-size: 18px;
}
}
@media (max-device-width: 520px), (max-width: 520px) {
.copySection {
width: 100%;
flex-direction: column;
padding-left: 0;
}
.copySection__url {
font-size: 22px;
padding: 15px 10px;
border-radius: 6px 6px 0 0;
}
.sharePage__copyText {
text-align: center;
}
.inputBtn--copy {
border-radius: 0 0 6px 6px;
flex: 0 1 65px;
}
}

View File

@@ -1,52 +0,0 @@
const html = require('choo/html');
const assets = require('../../common/assets');
module.exports = function(state) {
const msg =
state.params.reason === 'outdated'
? html`
<div id="unsupported-browser">
<div class="title">${state.translate('notSupportedHeader')}</div>
<div class="description">
${state.translate('notSupportedOutdatedDetail')}
</div>
<a
id="update-firefox"
href="https://support.mozilla.org/kb/update-firefox-latest-version">
<img
src="${assets.get('firefox_logo-only.svg')}"
class="firefox-logo"
alt="Firefox"/>
<div class="unsupported-button-text">
${state.translate('updateFirefox')}
</div>
</a>
<div class="unsupported-description">
${state.translate('uploadPageExplainer')}
</div>
</div>`
: html`
<div id="unsupported-browser">
<div class="title">${state.translate('notSupportedHeader')}</div>
<div class="description">${state.translate('notSupportedDetail')}</div>
<div class="description">
<a href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-is-my-browser-not-supported">
${state.translate('notSupportedLink')}
</a>
</div>
<a id="dl-firefox" href="https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com">
<img
src="${assets.get('firefox_logo-only.svg')}"
class="firefox-logo"
alt="Firefox"/>
<div class="unsupported-button-text">Firefox<br>
<span>${state.translate('downloadFirefoxButtonSub')}</span>
</div>
</a>
<div class="unsupported-description">
${state.translate('uploadPageExplainer')}
</div>
</div>`;
const div = html`<div id="page-one">${msg}</div>`;
return div;
};

View File

@@ -0,0 +1,67 @@
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`
<div class="description">
<a href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-is-my-browser-not-supported">
${state.translate('notSupportedLink')}
</a>
</div>`;
url =
'https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com';
buttonAction = html`
<div class="firefoxDownload__action">
Firefox<br><span class="firefoxDownload__text">${strings.button}</span>
</div>`;
} else {
strings = outdatedStrings(state);
url = 'https://support.mozilla.org/kb/update-firefox-latest-version';
buttonAction = html`
<div class="firefoxDownload__action">
${strings.button}
</div>`;
}
return html`
<div class="unsupportedPage">
<div class="title">${strings.title}</div>
<div class="description">
${strings.description}
</div>
${why}
<a href="${url}" class="firefoxDownload">
<img
src="${assets.get('firefox_logo-only.svg')}"
class="firefoxDownload__logo"
alt="Firefox"/>
${buttonAction}
</a>
<div class="unsupportedPage__info">
${strings.explainer}
</div>
</div>`;
};
function outdatedStrings(state) {
return {
title: state.translate('notSupportedHeader'),
description: state.translate('notSupportedOutdatedDetail'),
button: state.translate('updateFirefox'),
explainer: state.translate('uploadPageExplainer')
};
}
function unsupportedStrings(state) {
return {
title: state.translate('notSupportedHeader'),
description: state.translate('notSupportedDetail'),
button: state.translate('downloadFirefoxButtonSub'),
explainer: state.translate('uploadPageExplainer')
};
}

View File

@@ -0,0 +1,49 @@
.unsupportedPage {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.unsupportedPage__info {
font-size: 13px;
line-height: 23px;
text-align: center;
color: var(--lightTextColor);
margin: 0 auto 23px;
}
.firefoxDownload {
margin-bottom: 181px;
height: 80px;
background: #98e02b;
border-radius: 3px;
cursor: pointer;
border: 0;
box-shadow: 0 5px 3px rgb(234, 234, 234);
font-family: 'Fira Sans', 'segoe ui', sans-serif;
font-weight: 500;
color: var(--primaryControlFGColor);
font-size: 26px;
display: flex;
justify-content: center;
align-items: center;
line-height: 1;
padding: 0 25px;
}
.firefoxDownload__logo {
width: 70px;
}
.firefoxDownload__action {
text-align: left;
margin-left: 20.4px;
}
.firefoxDownload__text {
font-family: 'Fira Sans', 'segoe ui', sans-serif;
font-weight: 300;
font-size: 18px;
letter-spacing: -0.69px;
}

View File

@@ -1,41 +0,0 @@
const html = require('choo/html');
const progress = require('../templates/progress');
const { bytes } = require('../utils');
module.exports = function(state, emit) {
const transfer = state.transfer;
const div = html`
<div id="download">
<div id="upload-progress" class="fadeIn">
<div class="title" id="upload-filename">
${state.translate('uploadingPageProgress', {
filename: transfer.file.name,
size: bytes(transfer.file.size)
})}
</div>
<div class="description"></div>
${progress(transfer.progressRatio)}
<div class="upload">
<div class="progress-text">
${state.translate(transfer.msg, transfer.sizes)}
</div>
<button
id="cancel-upload"
title="${state.translate('uploadingPageCancel')}"
onclick=${cancel}>
${state.translate('uploadingPageCancel')}
</button>
</div>
</div>
</div>
`;
function cancel() {
const btn = document.getElementById('cancel-upload');
btn.disabled = true;
btn.textContent = state.translate('uploadCancelNotification');
emit('cancel');
}
return div;
};

39
app/pages/upload/index.js Normal file
View File

@@ -0,0 +1,39 @@
const html = require('choo/html');
const progress = require('../../templates/progress');
const { bytes } = require('../../utils');
module.exports = function(state, emit) {
const transfer = state.transfer;
return html`
<div class="page effect--fadeIn">
<div class="title">
${state.translate('uploadingPageProgress', {
filename: transfer.file.name,
size: bytes(transfer.file.size)
})}
</div>
<div class="description"></div>
${progress(transfer.progressRatio, transfer.progressIndefinite)}
<div class="progressSection">
<div class="progressSection__text">
${state.translate(transfer.msg, transfer.sizes)}
</div>
<button
id="cancel-upload"
class="btn btn--cancel"
title="${state.translate('uploadingPageCancel')}"
onclick=${cancel}>
${state.translate('uploadingPageCancel')}
</button>
</div>
</div>
`;
function cancel() {
const btn = document.getElementById('cancel-upload');
btn.disabled = true;
btn.textContent = state.translate('uploadCancelNotification');
emit('cancel');
}
};

View File

@@ -1,12 +1,14 @@
/* global MAXFILESIZE */ /* global MAXFILESIZE */
const html = require('choo/html'); const html = require('choo/html');
const assets = require('../../common/assets'); const assets = require('../../../common/assets');
const fileList = require('../templates/fileList'); const fileList = require('../../templates/fileList');
const { bytes, fadeOut } = require('../utils'); const { bytes, fadeOut } = require('../../utils');
module.exports = function(state, emit) { module.exports = function(state, emit) {
const div = html` // the page flickers if both the server and browser set 'effect--fadeIn'
<div id="page-one" class="fadeIn"> const fade = state.layout ? '' : 'effect--fadeIn';
return html`
<div id="page-one" class="${fade}">
<div class="title">${state.translate('uploadPageHeader')}</div> <div class="title">${state.translate('uploadPageHeader')}</div>
<div class="description"> <div class="description">
<div>${state.translate('uploadPageExplainer')}</div> <div>${state.translate('uploadPageExplainer')}</div>
@@ -16,27 +18,27 @@ module.exports = function(state, emit) {
${state.translate('uploadPageLearnMore')} ${state.translate('uploadPageLearnMore')}
</a> </a>
</div> </div>
<div class="upload-window" <div class="uploadArea"
ondragover=${dragover} ondragover=${dragover}
ondragleave=${dragleave}> ondragleave=${dragleave}>
<div id="upload-img"> <img
<img src="${assets.get('upload.svg')}"
src="${assets.get('upload.svg')}" title="${state.translate('uploadSvgAlt')}"/>
title="${state.translate('uploadSvgAlt')}"/> <div class="uploadArea__msg">
${state.translate('uploadPageDropMessage')}
</div> </div>
<div id="upload-text">${state.translate('uploadPageDropMessage')}</div> <span class="uploadArea__sizeMsg">
<span id="file-size-msg"> ${state.translate('uploadPageSizeMessage')}
<em>${state.translate('uploadPageSizeMessage')}</em>
</span> </span>
<input id="file-upload" <input id="file-upload"
class="inputFile"
type="file" type="file"
name="fileUploaded" name="fileUploaded"
onfocus=${onfocus} onfocus=${onfocus}
onblur=${onblur} onblur=${onblur}
onchange=${upload} /> onchange=${upload} />
<label for="file-upload" <label for="file-upload"
id="browse" class="btn btn--file"
class="btn browse"
title="${state.translate('uploadPageBrowseButton1')}"> title="${state.translate('uploadPageBrowseButton1')}">
${state.translate('uploadPageBrowseButton1')} ${state.translate('uploadPageBrowseButton1')}
</label> </label>
@@ -46,21 +48,21 @@ module.exports = function(state, emit) {
`; `;
function dragover(event) { function dragover(event) {
const div = document.querySelector('.upload-window'); const div = document.querySelector('.uploadArea');
div.classList.add('ondrag'); div.classList.add('uploadArea--dragging');
} }
function dragleave(event) { function dragleave(event) {
const div = document.querySelector('.upload-window'); const div = document.querySelector('.uploadArea');
div.classList.remove('ondrag'); div.classList.remove('uploadArea--dragging');
} }
function onfocus(event) { function onfocus(event) {
event.target.classList.add('has-focus'); event.target.classList.add('inputFile--focused');
} }
function onblur(event) { function onblur(event) {
event.target.classList.remove('has-focus'); event.target.classList.remove('inputFile--focused');
} }
async function upload(event) { async function upload(event) {
@@ -71,12 +73,12 @@ module.exports = function(state, emit) {
return; return;
} }
if (file.size > MAXFILESIZE) { if (file.size > MAXFILESIZE) {
window.alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) })); // eslint-disable-next-line no-alert
alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
return; return;
} }
await fadeOut('page-one'); await fadeOut('#page-one');
emit('upload', { file, type: 'click' }); emit('upload', { file, type: 'click' });
} }
return div;
}; };

View File

@@ -0,0 +1,65 @@
.uploadArea {
border: 3px dashed rgba(0, 148, 251, 0.5);
margin: 0 auto 10px;
height: 255px;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
transition: transform 150ms;
padding: 15px;
}
.uploadArea__msg {
font-size: 22px;
color: var(--lightTextColor);
margin: 20px 0 10px;
font-family: 'SF Pro Text', sans-serif;
}
.uploadArea__sizeMsg {
font-style: italic;
font-size: 12px;
line-height: 16px;
color: var(--lightTextColor);
margin-bottom: 22px;
}
.uploadArea--dragging {
border: 5px dashed rgba(0, 148, 251, 0.5);
height: 251px;
transform: scale(1.04);
border-radius: 4.2px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
}
.uploadArea--dragging * {
pointer-events: none;
}
.btn--file {
font-size: 20px;
min-width: 240px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
padding: 0 10px;
}
.inputFile {
opacity: 0;
position: absolute;
}
.inputFile--focused + .btn--file {
background-color: var(--primaryControlHoverColor);
outline: 1px dotted #000;
outline: -webkit-focus-ring-color auto 5px;
}

25
app/pasteManager.js Normal file
View File

@@ -0,0 +1,25 @@
/* global MAXFILESIZE */
import { bytes } from './utils';
export default function(state, emitter) {
window.addEventListener('paste', event => {
if (state.route !== '/' || state.uploading) return;
for (const item of event.clipboardData.items) {
if (!item.type.includes('image')) continue;
const file = item.getAsFile();
if (!file) continue; // Sometimes null
if (file.size > MAXFILESIZE) {
// eslint-disable-next-line no-alert
alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
continue;
}
emitter.emit('upload', { file, type: 'paste' });
return; // return here since only one file is allowed to be uploaded at a time
}
});
}

9
app/readme.md Normal file
View File

@@ -0,0 +1,9 @@
# Application Code
`app/` contains the browser code that gets bundled into `app.[hash].js`. It's got all the logic, crypto, and UI. All of it gets used in the browser, and some of it by the server for server side rendering.
The main entrypoint for the browser is [main.js](./main.js) and on the server [routes/index.js](./routes/index.js) gets imported by [/server/routes/pages.js](../server/routes/pages.js)
- `pages` contains display logic an markup for pages
- `routes` contains route definitions and logic
- `templates` contains ui elements smaller than pages

View File

@@ -1,10 +1,12 @@
const choo = require('choo'); const choo = require('choo');
const html = require('choo/html'); const html = require('choo/html');
const nanotiming = require('nanotiming');
const download = require('./download'); const download = require('./download');
const header = require('../templates/header'); const header = require('../templates/header');
const footer = require('../templates/footer'); const footer = require('../templates/footer');
const fxPromo = require('../templates/fxPromo'); const fxPromo = require('../templates/fxPromo');
nanotiming.disabled = true;
const app = choo(); const app = choo();
function banner(state, emit) { function banner(state, emit) {
@@ -18,18 +20,20 @@ function body(template) {
const b = html`<body> const b = html`<body>
${banner(state, emit)} ${banner(state, emit)}
${header(state)} ${header(state)}
<div class="all"> <main class="main">
<noscript> <noscript>
<h2>${state.translate('javascriptRequired')}</h2> <div class="noscript">
<p> <h2>${state.translate('javascriptRequired')}</h2>
<a href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-does-firefox-send-require-javascript"> <p>
${state.translate('whyJavascript')} <a class="link" href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-does-firefox-send-require-javascript">
</a> ${state.translate('whyJavascript')}
</p> </a>
<p>${state.translate('enableJavascript')}</p> </p>
<p>${state.translate('enableJavascript')}</p>
</div>
</noscript> </noscript>
${template(state, emit)} ${template(state, emit)}
</div> </main>
${footer(state)} ${footer(state)}
</body>`; </body>`;
if (state.layout) { if (state.layout) {

View File

@@ -0,0 +1,6 @@
.btn--download {
width: 180px;
height: 44px;
margin-top: 20px;
margin-bottom: 30px;
}

View File

@@ -1,16 +1,13 @@
const html = require('choo/html'); const html = require('choo/html');
module.exports = function(state, emit) { module.exports = function(state, emit) {
return html`
<button class="btn btn--download"
onclick=${download}>${state.translate('downloadButtonLabel')}
</button>`;
function download(event) { function download(event) {
event.preventDefault(); event.preventDefault();
emit('download', state.fileInfo); emit('download', state.fileInfo);
} }
return html`
<div>
<button id="download-btn"
class="btn"
onclick=${download}>${state.translate('downloadButtonLabel')}
</button>
</div>`;
}; };

View File

@@ -1,59 +0,0 @@
const html = require('choo/html');
module.exports = function(state, emit) {
const fileInfo = state.fileInfo;
const label =
fileInfo.password === null
? html`
<label class="red" for="unlock-input">
${state.translate('passwordTryAgain')}
</label>`
: html`
<label for="unlock-input">
${state.translate('unlockInputLabel')}
</label>`;
const div = html`
<div class="enterPassword">
${label}
<form id="unlock" onsubmit=${checkPassword} data-no-csrf>
<input id="unlock-input"
class="unlock-input input-no-btn"
maxlength="64"
autocomplete="off"
placeholder="${state.translate('unlockInputPlaceholder')}"
oninput=${inputChanged}
type="password"
autofocus />
<input type="submit"
id="unlock-btn"
class="btn btn-hidden"
value="${state.translate('unlockButtonLabel')}"/>
</form>
</div>`;
function inputChanged() {
const input = document.getElementById('unlock-input');
const btn = document.getElementById('unlock-btn');
if (input.value.length > 0) {
btn.classList.remove('btn-hidden');
input.classList.remove('input-no-btn');
} else {
btn.classList.add('btn-hidden');
input.classList.add('input-no-btn');
}
}
function checkPassword(event) {
event.preventDefault();
const password = document.getElementById('unlock-input').value;
if (password.length > 0) {
document.getElementById('unlock-btn').disabled = true;
state.fileInfo.url = window.location.href;
state.fileInfo.password = password;
emit('getMetadata');
}
return false;
}
return div;
};

View File

@@ -0,0 +1,22 @@
.passwordSection {
text-align: left;
padding: 40px 0;
width: 80%;
}
.passwordForm {
display: flex;
flex-wrap: nowrap;
width: 100%;
padding: 10px 0;
}
@media (max-device-width: 520px), (max-width: 520px) {
.passwordSection {
width: 100%;
}
.passwordForm {
flex-direction: column;
}
}

View File

@@ -0,0 +1,66 @@
const html = require('choo/html');
module.exports = function(state, emit) {
const fileInfo = state.fileInfo;
const invalid = fileInfo.password === null;
const label = invalid
? html`
<label class="error" for="password-input">
${state.translate('passwordTryAgain')}
</label>`
: html`
<label for="password-input">
${state.translate('unlockInputLabel')}
</label>`;
const inputClass = invalid
? 'input input--noBtn input--error'
: 'input input--noBtn';
const div = html`
<div class="passwordSection">
${label}
<form class="passwordForm" onsubmit=${checkPassword} data-no-csrf>
<input id="password-input"
class="${inputClass}"
maxlength="64"
autocomplete="off"
placeholder="${state.translate('unlockInputPlaceholder')}"
oninput=${inputChanged}
type="password" />
<input type="submit"
id="password-btn"
class="inputBtn inputBtn--hidden"
value="${state.translate('unlockButtonLabel')}"/>
</form>
</div>`;
if (!(div instanceof String)) {
setTimeout(() => document.getElementById('password-input').focus());
}
function inputChanged() {
const input = document.getElementById('password-input');
const btn = document.getElementById('password-btn');
input.classList.remove('input--error');
if (input.value.length > 0) {
btn.classList.remove('inputBtn--hidden');
input.classList.remove('input--noBtn');
} else {
btn.classList.add('inputBtn--hidden');
input.classList.add('input--noBtn');
}
}
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;
};

View File

@@ -0,0 +1,26 @@
.fileData {
font-size: 15px;
vertical-align: top;
color: var(--lightTextColor);
padding: 17px 19px 0;
line-height: 23px;
position: relative;
}
.fileData--overflow {
text-overflow: ellipsis;
max-width: 0;
overflow: hidden;
white-space: nowrap;
}
.fileData--center {
text-align: center;
}
@media (max-device-width: 520px), (max-width: 520px) {
.fileData {
font-size: 13px;
padding: 17px 5px 0;
}
}

View File

@@ -1,22 +1,7 @@
const html = require('choo/html'); const html = require('choo/html');
const assets = require('../../common/assets'); const assets = require('../../../common/assets');
const number = require('../../utils').number;
function timeLeft(milliseconds, state) { const deletePopup = require('../popup');
const minutes = Math.floor(milliseconds / 1000 / 60);
const hours = Math.floor(minutes / 60);
if (hours >= 1) {
return state.translate('expiresHoursMinutes', {
hours,
minutes: minutes % 60
});
} else if (hours === 0) {
if (minutes === 0) {
return state.translate('expiresMinutes', { minutes: '< 1' });
}
return state.translate('expiresMinutes', { minutes });
}
return null;
}
module.exports = function(file, state, emit) { module.exports = function(file, state, emit) {
const ttl = file.expiresAt - Date.now(); const ttl = file.expiresAt - Date.now();
@@ -24,42 +9,39 @@ module.exports = function(file, state, emit) {
timeLeft(ttl, state) || state.translate('linkExpiredAlt'); timeLeft(ttl, state) || state.translate('linkExpiredAlt');
const downloadLimit = file.dlimit || 1; const downloadLimit = file.dlimit || 1;
const totalDownloads = file.dtotal || 0; const totalDownloads = file.dtotal || 0;
const row = html` return html`
<tr id="${file.id}"> <tr id="${file.id}">
<td class="overflow-col" title="${file.name}"> <td class="fileData fileData--overflow" title="${file.name}">
<a class="link" href="/share/${file.id}">${file.name}</a> <a class="link" href="/share/${file.id}">${file.name}</a>
</td> </td>
<td class="center-col"> <td class="fileData fileData--center">
<img <img
onclick=${copyClick} onclick=${copyClick}
src="${assets.get('copy-16.svg')}" src="${assets.get('copy-16.svg')}"
class="icon-copy" class="cursor--pointer"
title="${state.translate('copyUrlHover')}"> title="${state.translate('copyUrlHover')}"
<span class="text-copied" hidden="true"> tabindex="0">
<span hidden="true">
${state.translate('copiedUrl')} ${state.translate('copiedUrl')}
</span> </span>
</td> </td>
<td>${remainingTime}</td> <td class="fileData fileData--overflow">${remainingTime}</td>
<td class="center-col">${totalDownloads} / ${downloadLimit}</td> <td class="fileData fileData--center">${number(totalDownloads)} / ${number(
<td class="center-col"> downloadLimit
)}</td>
<td class="fileData fileData--center">
<img <img
onclick=${showPopup} onclick=${showPopup}
src="${assets.get('close-16.svg')}" src="${assets.get('close-16.svg')}"
class="icon-delete" class="cursor--pointer"
title="${state.translate('deleteButtonHover')}"> title="${state.translate('deleteButtonHover')}"
<div class="popup"> tabindex="0">
<div class="popuptext" onblur=${cancel} tabindex="-1"> ${deletePopup(
<div class="popup-message">${state.translate('deletePopupText')}</div> state.translate('deletePopupText'),
<div class="popup-action"> state.translate('deletePopupYes'),
<span class="popup-no" onclick=${cancel}> state.translate('deletePopupCancel'),
${state.translate('deletePopupCancel')} deleteFile
</span> )}
<span class="popup-yes" onclick=${deleteFile}>
${state.translate('deletePopupYes')}
</span>
</div>
</div>
</div>
</td> </td>
</tr> </tr>
`; `;
@@ -78,22 +60,30 @@ module.exports = function(file, state, emit) {
function showPopup() { function showPopup() {
const tr = document.getElementById(file.id); const tr = document.getElementById(file.id);
const popup = tr.querySelector('.popuptext'); const popup = tr.querySelector('.popup');
popup.classList.add('show'); popup.classList.add('popup--show');
popup.focus(); popup.focus();
} }
function cancel(e) {
e.stopPropagation();
const tr = document.getElementById(file.id);
const popup = tr.querySelector('.popuptext');
popup.classList.remove('show');
}
function deleteFile() { function deleteFile() {
emit('delete', { file, location: 'upload-list' }); emit('delete', { file, location: 'upload-list' });
emit('render'); emit('render');
} }
return row;
}; };
function timeLeft(milliseconds, state) {
const minutes = Math.floor(milliseconds / 1000 / 60);
const hours = Math.floor(minutes / 60);
if (hours >= 1) {
return state.translate('expiresHoursMinutes', {
hours,
minutes: minutes % 60
});
} else if (hours === 0) {
if (minutes === 0) {
return state.translate('expiresMinutes', { minutes: '< 1' });
}
return state.translate('expiresMinutes', { minutes });
}
return null;
}

View File

@@ -0,0 +1,52 @@
.fileList {
margin: 45.3px auto;
table-layout: fixed;
border-collapse: collapse;
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
}
.fileList__header {
font-size: 16px;
color: var(--lightTextColor);
font-weight: lighter;
text-align: left;
background: rgba(0, 148, 251, 0.05);
height: 40px;
border-top: 1px solid rgba(0, 148, 251, 0.1);
padding: 0 19px;
white-space: nowrap;
}
.fileList__body {
word-wrap: break-word;
word-break: break-all;
}
.fileList__nameCol {
width: 35%;
}
.fileList__copyCol {
text-align: center;
width: 25%;
}
.fileList__expireCol {
width: 25%;
}
.fileList__dlCol {
width: 8%;
}
.fileList__delCol {
text-align: center;
width: 7%;
}
@media (max-device-width: 520px), (max-width: 520px) {
.fileList__header {
font-size: 14px;
padding: 0 5px;
}
}

View File

@@ -1,37 +1,33 @@
const html = require('choo/html'); const html = require('choo/html');
const file = require('./file'); const file = require('../file');
module.exports = function(state, emit) { module.exports = function(state, emit) {
let table = '';
if (state.storage.files.length) { if (state.storage.files.length) {
table = html` return html`
<table id="uploaded-files"> <table class="fileList">
<thead> <thead>
<tr> <tr>
<th id="uploaded-file">${state.translate('uploadedFile')}</th> <th class="fileList__header fileList__nameCol">
<th id="copy-file-list" class="center-col"> ${state.translate('uploadedFile')}
</th>
<th class="fileList__header fileList__copyCol">
${state.translate('copyFileList')} ${state.translate('copyFileList')}
</th> </th>
<th id="expiry-time-file-list" > <th class="fileList__header fileList__expireCol" >
${state.translate('timeFileList')} ${state.translate('timeFileList')}
</th> </th>
<th id="expiry-downloads-file-list" > <th class="fileList__header fileList__dlCol" >
${state.translate('downloadsFileList')} ${state.translate('downloadsFileList')}
</th> </th>
<th id="delete-file-list" class="center-col"> <th class="fileList__header fileList__delCol">
${state.translate('deleteFileList')} ${state.translate('deleteFileList')}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="fileList__body">
${state.storage.files.map(f => file(f, state, emit))} ${state.storage.files.map(f => file(f, state, emit))}
</tbody> </tbody>
</table> </table>
`; `;
} }
return html`
<div id="file-list">
${table}
</div>
`;
}; };

View File

@@ -1,43 +0,0 @@
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>
<a href="https://www.mozilla.org/about/legal/report-infringement/">
${state.translate('reportIPInfringement')}
</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>`;
};

View File

@@ -0,0 +1,93 @@
.footer {
right: 0;
bottom: 0;
left: 0;
font-size: 13px;
display: flex;
align-items: flex-end;
flex-direction: row;
justify-content: space-between;
padding: 50px 31px 41px;
width: 100%;
box-sizing: border-box;
}
.legalSection {
max-width: 81vw;
display: flex;
align-items: center;
flex-direction: row;
}
.legalSection__link {
color: var(--lightTextColor);
opacity: 0.9;
white-space: nowrap;
margin-right: 2vw;
}
.legalSection__link:hover {
opacity: 1;
}
.legalSection__link:visited {
color: var(--lightTextColor);
}
.legalSection__mozLogo {
width: 112px;
height: 32px;
margin-bottom: -5px;
}
.socialSection {
display: flex;
justify-content: space-between;
width: 94px;
}
.socialSection__link {
opacity: 0.9;
}
.socialSection__link:hover {
opacity: 1;
}
.socialSection__icon {
width: 32px;
height: 32px;
margin-bottom: -5px;
}
@media (max-device-width: 768px), (max-width: 768px) {
.footer {
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
max-width: 630px;
margin: auto;
}
.legalSection__mozLogo {
margin-left: -7px;
}
.legalSection {
flex-direction: column;
margin: auto;
width: 100%;
max-width: 100%;
}
.legalSection__link {
display: block;
padding: 10px 0;
align-self: flex-start;
}
.socialSection {
margin-top: 20px;
align-self: flex-start;
}
}

View File

@@ -0,0 +1,68 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
module.exports = function(state) {
const footer = html`<footer class="footer">
<div class="legalSection">
<a
href="https://www.mozilla.org"
class="legalSection__link">
<img
class="legalSection__mozLogo"
src="${assets.get('mozilla-logo.svg')}"
alt="mozilla"/>
</a>
<a
href="https://www.mozilla.org/about/legal"
class="legalSection__link">
${state.translate('footerLinkLegal')}
</a>
<a
href="https://testpilot.firefox.com/about"
class="legalSection__link">
${state.translate('footerLinkAbout')}
</a>
<a
href="/legal"
class="legalSection__link">${state.translate('footerLinkPrivacy')}</a>
<a
href="/legal"
class="legalSection__link">${state.translate('footerLinkTerms')}</a>
<a
href="https://www.mozilla.org/privacy/websites/#cookies"
class="legalSection__link">
${state.translate('footerLinkCookies')}
</a>
<a
href="https://www.mozilla.org/about/legal/report-infringement/"
class="legalSection__link">
${state.translate('reportIPInfringement')}
</a>
</div>
<div class="socialSection">
<a
href="https://github.com/mozilla/send"
class="socialSection__link">
<img
class="socialSection__icon"
src="${assets.get('github-icon.svg')}"
alt="github"/>
</a>
<a
href="https://twitter.com/FxTestPilot"
class="socialSection__link">
<img
class="socialSection__icon"
src="${assets.get('twitter-icon.svg')}"
alt="twitter"/>
</a>
</div>
</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;
};

View File

@@ -0,0 +1,56 @@
.fxPromo {
padding: 0 15px;
height: 48px;
background-color: #efeff1;
color: #4a4a4f;
font-size: 13px;
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
justify-content: center;
}
.fxPromo > div {
display: flex;
align-items: center;
margin: 0 auto;
}
.fxPromo > div > span {
margin-left: 10px;
}
.fxPromo__logo {
width: 24px;
}
.fxPromo--blue {
background: linear-gradient(-180deg, #45a1ff 0%, #00feff 94%);
color: #fff;
}
.fxPromo--pink {
background: linear-gradient(-180deg, #ff9400 0%, #ff1ad9 94%);
color: #fff;
}
.fxPromo--blue a {
color: #fff;
font-weight: bold;
}
.fxPromo--pink a {
color: #fff;
font-weight: bold;
}
.fxPromo--blue a:hover {
color: #eee;
font-weight: bold;
}
.fxPromo--pink a:hover {
color: #eee;
font-weight: bold;
}

View File

@@ -1,17 +1,14 @@
const html = require('choo/html'); const html = require('choo/html');
const assets = require('../../common/assets'); const assets = require('../../../common/assets');
module.exports = function(state, emit) { module.exports = function(state, emit) {
function clicked() { let classes = 'fxPromo';
emit('experiment', { cd3: 'promo' });
}
let classes = 'banner';
switch (state.promo) { switch (state.promo) {
case 'blue': case 'blue':
classes = 'banner banner-blue'; classes = 'fxPromo fxPromo--blue';
break; break;
case 'pink': case 'pink':
classes = 'banner banner-pink'; classes = 'fxPromo fxPromo--pink';
break; break;
} }
@@ -20,7 +17,7 @@ module.exports = function(state, emit) {
<div> <div>
<img <img
src="${assets.get('firefox_logo-only.svg')}" src="${assets.get('firefox_logo-only.svg')}"
class="firefox-logo-small" class="fxPromo__logo"
alt="Firefox"/> alt="Firefox"/>
<span>Send is brought to you by the all-new Firefox. <span>Send is brought to you by the all-new Firefox.
<a <a
@@ -30,4 +27,8 @@ module.exports = function(state, emit) {
>Download Firefox now </a></span> >Download Firefox now </a></span>
</div> </div>
</div>`; </div>`;
function clicked() {
emit('experiment', { cd3: 'promo' });
}
}; };

View File

@@ -1,59 +0,0 @@
const html = require('choo/html');
const assets = require('../../common/assets');
/*
The current weback config uses package.json to generate
version.json for /__version__ meaning `require` returns the
string 'version.json' in the frontend context but the json
on the server.
We want `version` to be constant at build time so this file
has a custom loader (/build/version_loader.js) just to replace
string with the value from package.json. 🤢
*/
const version = require('../../package.json').version || 'VERSION';
function browserName() {
try {
if (/firefox/i.test(navigator.userAgent)) {
return 'firefox';
}
if (/edge/i.test(navigator.userAgent)) {
return 'edge';
}
if (/trident/i.test(navigator.userAgent)) {
return 'ie';
}
if (/chrome/i.test(navigator.userAgent)) {
return 'chrome';
}
if (/safari/i.test(navigator.userAgent)) {
return 'safari';
}
return 'other';
} catch (e) {
return 'unknown';
}
}
const browser = browserName();
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?ver=${version}&browser=${browser}"
rel="noreferrer noopener"
class="feedback"
target="_blank">${state.translate('siteFeedback')}</a>
</header>`;
};

View File

@@ -0,0 +1,104 @@
.header {
align-items: flex-start;
box-sizing: border-box;
display: flex;
justify-content: space-between;
padding: 31px;
width: 100%;
}
.logo {
display: flex;
position: relative;
align-items: center;
}
.logo__link {
display: flex;
flex-direction: row;
}
.logo__title {
color: #3e3d40;
font-size: 32px;
font-weight: 500;
margin: 0;
position: relative;
top: -1px;
letter-spacing: 1px;
margin-left: 8px;
transition: color 50ms;
}
.logo__title:hover {
color: var(--primaryControlBGColor);
}
.logo__subtitle {
color: #3e3d40;
font-size: 12px;
margin: 0 8px;
}
.logo__subtitle-link {
font-weight: bold;
color: #3e3d40;
transition: color 50ms;
}
.logo__subtitle-link:hover {
color: var(--primaryControlBGColor);
}
.feedback {
background-color: var(--primaryControlBGColor);
background-image: url('../assets/feedback.svg');
background-position: 2px 4px;
background-repeat: no-repeat;
background-size: 18px;
border-radius: 3px;
border: 1px solid var(--primaryControlBGColor);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
color: var(--primaryControlFGColor);
cursor: pointer;
display: block;
float: right;
font-size: 12px;
line-height: 12px;
opacity: 0.9;
padding: 5px;
overflow: hidden;
min-width: 12px;
max-width: 12px;
text-indent: 17px;
transition: all 250ms ease-in-out;
white-space: nowrap;
}
.feedback:hover,
.feedback:focus {
min-width: 30px;
max-width: 300px;
text-indent: 2px;
padding: 5px 5px 5px 20px;
background-color: var(--primaryControlHoverColor);
}
.feedback:active {
background-color: var(--primaryControlHoverColor);
}
@media (max-device-width: 520px), (max-width: 520px) {
.header {
flex-direction: column;
justify-content: flex-start;
}
.feedback {
margin-top: 10px;
min-width: 30px;
max-width: 300px;
text-indent: 2px;
padding: 5px 5px 5px 20px;
}
}

View File

@@ -0,0 +1,67 @@
const html = require('choo/html');
const assets = require('../../../common/assets');
/*
The current weback config uses package.json to generate
version.json for /__version__ meaning `require` returns the
string 'version.json' in the frontend context but the json
on the server.
We want `version` to be constant at build time so this file
has a custom loader (/build/version_loader.js) just to replace
string with the value from package.json. 🤢
*/
const version = require('../../../package.json').version || 'VERSION';
const browser = browserName();
module.exports = function(state) {
const feedbackUrl = `https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}`;
const header = html`
<header class="header">
<div class="logo">
<a class="logo__link" href="/">
<img
src="${assets.get('send_logo.svg')}"
alt="Send"/>
<h1 class="logo__title">Send</h1>
</a>
<div class="logo__subtitle">
<a class="logo__subtitle-link" href="https://testpilot.firefox.com">Firefox Test Pilot</a>
<div>${state.translate('siteSubtitle')}</div>
</div>
</div>
<a href="${feedbackUrl}"
rel="noreferrer noopener"
class="feedback"
target="_blank">${state.translate('siteFeedback')}</a>
</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;
};
function browserName() {
try {
if (/firefox/i.test(navigator.userAgent)) {
return 'firefox';
}
if (/edge/i.test(navigator.userAgent)) {
return 'edge';
}
if (/trident/i.test(navigator.userAgent)) {
return 'ie';
}
if (/chrome/i.test(navigator.userAgent)) {
return 'chrome';
}
if (/safari/i.test(navigator.userAgent)) {
return 'safari';
}
return 'other';
} catch (e) {
return 'unknown';
}
}

View File

@@ -0,0 +1,109 @@
const html = require('choo/html');
const MAX_LENGTH = 32;
module.exports = function(file, state, emit) {
const loading = state.settingPassword;
const pwd = file.hasPassword;
const sectionClass =
pwd || state.passwordSetError
? 'passwordInput'
: 'passwordInput passwordInput--hidden';
const inputClass = loading || pwd ? 'input' : 'input input--noBtn';
let btnClass = 'inputBtn inputBtn--password inputBtn--hidden';
if (loading) {
btnClass = 'inputBtn inputBtn--password inputBtn--loading';
} else if (pwd) {
btnClass = 'inputBtn inputBtn--password';
}
const action = pwd
? state.translate('changePasswordButton')
: state.translate('addPasswordButton');
return html`
<div class="${sectionClass}">
<form
class="passwordInput__form"
onsubmit=${setPassword}
data-no-csrf>
<input id="password-input"
${loading ? 'disabled' : ''}
class="${inputClass}"
maxlength="${MAX_LENGTH}"
autocomplete="off"
type="password"
oninput=${inputChanged}
onfocus=${focused}
placeholder="${
pwd && !state.passwordSetError
? passwordPlaceholder(file.password)
: state.translate('unlockInputPlaceholder')
}">
<input type="submit"
id="password-btn"
${loading ? 'disabled' : ''}
class="${btnClass}"
value="${loading ? '' : action}">
</form>
<label
class="passwordInput__msg ${
state.passwordSetError ? 'passwordInput__msg--error' : ''
}"
for="password-input">${message(state, pwd)}</label>
</div>`;
function inputChanged() {
state.passwordSetError = null;
const resetInput = document.getElementById('password-input');
const resetBtn = document.getElementById('password-btn');
const pwdmsg = document.querySelector('.passwordInput__msg');
const length = resetInput.value.length;
if (length === MAX_LENGTH) {
pwdmsg.textContent = state.translate('maxPasswordLength', {
length: MAX_LENGTH
});
} else {
pwdmsg.textContent = '';
}
if (length > 0) {
resetBtn.classList.remove('inputBtn--hidden');
resetInput.classList.remove('input--noBtn');
} else {
resetBtn.classList.add('inputBtn--hidden');
resetInput.classList.add('input--noBtn');
}
}
function focused(event) {
event.preventDefault();
const el = document.getElementById('password-input');
if (el.placeholder !== state.translate('unlockInputPlaceholder')) {
el.placeholder = '';
}
}
function setPassword(event) {
event.preventDefault();
const el = document.getElementById('password-input');
const password = el.value;
if (password.length > 0) {
emit('password', { password, file });
} else {
el.focus();
}
return false;
}
};
function passwordPlaceholder(password) {
return password ? password.replace(/./g, '●') : '●●●●●●●●●●●●';
}
function message(state, pwd) {
if (state.passwordSetError) {
return state.translate('passwordSetError');
}
if (state.settingPassword || !pwd) {
return '';
}
return state.translate('passwordIsSet');
}

View File

@@ -0,0 +1,41 @@
.passwordInput {
width: 90%;
height: 100px;
padding: 10px 5px 5px;
}
.passwordInput--hidden {
visibility: hidden;
}
.passwordInput__form {
display: flex;
flex-wrap: nowrap;
padding-bottom: 5px;
}
.passwordInput__msg {
font-size: 15px;
color: var(--lightTextColor);
}
.passwordInput__msg--error {
color: var(--errorColor);
}
.inputBtn--loading {
background-image: url('../assets/spinner.svg');
background-position: center;
background-size: 30px 30px;
background-repeat: no-repeat;
}
.inputBtn--password {
flex: 0 0 200px;
}
@media (max-device-width: 520px), (max-width: 520px) {
.passwordInput__form {
flex-direction: column;
}
}

View File

@@ -0,0 +1,26 @@
const html = require('choo/html');
module.exports = function(msg, confirmText, cancelText, confirmCallback) {
return html`
<div class="popup__wrapper">
<div class="popup" onblur=${hide} tabindex="-1">
<div class="popup__message">${msg}</div>
<div class="popup__action">
<span class="popup__no" onclick=${hide}>
${cancelText}
</span>
<span class="popup__yes" onclick=${confirmCallback}>
${confirmText}
</span>
</div>
</div>
</div>`;
function hide(e) {
e.stopPropagation();
const popup = document.querySelector('.popup.popup--show');
if (popup) {
popup.classList.remove('popup--show');
}
}
};

View File

@@ -0,0 +1,122 @@
.popup {
visibility: hidden;
min-width: 204px;
min-height: 105px;
background-color: var(--pageBGColor);
color: var(--textColor);
border: 1px solid #d7d7db;
padding: 15px 24px;
box-sizing: content-box;
text-align: center;
border-radius: 5px;
position: absolute;
z-index: 1;
bottom: 20px;
left: -40px;
transition: opacity 0.5s;
opacity: 0;
outline: 0;
box-shadow: 3px 3px 7px rgba(136, 136, 136, 0.3);
}
.popup::after {
content: '';
position: absolute;
bottom: -11px;
left: 20px;
background-color: #fff;
display: block;
width: 20px;
height: 20px;
transform: rotate(45deg);
border-radius: 0 0 5px;
border-right: 1px solid #d7d7db;
border-bottom: 1px solid #d7d7db;
border-left: 1px solid #fff;
border-top: 1px solid #fff;
}
.popup__wrapper {
position: absolute;
display: inline-block;
}
.popup__message {
height: 40px;
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px #ebebeb solid;
color: var(--textColor);
font-size: 15px;
font-weight: normal;
padding-bottom: 15px;
white-space: nowrap;
width: calc(100% + 48px);
margin-left: -24px;
}
.popup__action {
margin-top: 15px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.popup__no {
color: #4a4a4a;
background-color: #fbfbfb;
border: 1px #c1c1c1 solid;
border-radius: 5px;
padding: 5px 25px;
font-weight: normal;
min-width: 94px;
box-sizing: border-box;
cursor: pointer;
white-space: nowrap;
}
.popup__no:hover {
background-color: #efeff1;
}
.popup__yes {
color: var(--primaryControlFGColor);
background-color: var(--primaryControlBGColor);
border-radius: 5px;
padding: 5px 25px;
font-weight: normal;
cursor: pointer;
min-width: 94px;
box-sizing: border-box;
white-space: nowrap;
margin-left: 12px;
}
.popup__yes:hover {
background-color: var(--primaryControlHoverColor);
}
.popup--show {
visibility: visible;
opacity: 1;
}
@media (max-device-width: 992px), (max-width: 992px) {
.popup {
left: auto;
right: -40px;
}
.popup::after {
left: auto;
right: 36px;
}
}
@media (max-device-width: 520px), (max-width: 520px) {
.popup::after {
left: 125px;
}
}

View File

@@ -1,41 +0,0 @@
const html = require('choo/html');
const radius = 73;
const oRadius = radius + 10;
const oDiameter = oRadius * 2;
const circumference = 2 * Math.PI * radius;
module.exports = function(progressRatio) {
const dashOffset = (1 - progressRatio) * circumference;
const percent = Math.floor(progressRatio * 100);
const div = html`
<div class="progress-bar">
<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
id="bar"
r="${radius}"
cx="${oRadius}"
cy="${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>
</div>
`;
return div;
};

View File

@@ -0,0 +1,56 @@
const html = require('choo/html');
const percent = require('../../utils').percent;
const radius = 73;
const oRadius = radius + 10;
const oDiameter = oRadius * 2;
const circumference = 2 * Math.PI * radius;
module.exports = function(progressRatio, indefinite = false) {
// HACK - never indefinite for MS Edge
if (/edge/i.test(navigator.userAgent)) {
indefinite = false;
}
const p = indefinite ? 0.2 : progressRatio;
const dashOffset = (1 - p) * circumference;
const progressPercent = html`
<text class="progress__percent" text-anchor="middle" x="50%" y="98">
${percent(progressRatio)}
</text>`;
return html`
<div class="progress">
<svg
width="${oDiameter}"
height="${oDiameter}"
viewPort="0 0 ${oDiameter} ${oDiameter}"
version="1.1">
<circle
class="progress__bg"
r="${radius}"
cx="${oRadius}"
cy="${oRadius}"
fill="transparent"/>
<circle
class="progress__indefinite ${indefinite ? '' : 'progress--invisible'}"
r="${radius}"
cx="${oRadius}"
cy="${oRadius}"
fill="transparent"
transform="rotate(-90 ${oRadius} ${oRadius})"
stroke-dasharray="${circumference}"
stroke-dashoffset="${dashOffset}"/>
<circle
class="progress__bar ${indefinite ? 'progress--invisible' : ''}"
r="${radius}"
cx="${oRadius}"
cy="${oRadius}"
fill="transparent"
transform="rotate(-90 ${oRadius} ${oRadius})"
stroke-dasharray="${circumference}"
stroke-dashoffset="${dashOffset}"/>
${indefinite ? '' : progressPercent}
</svg>
</div>
`;
};

View File

@@ -0,0 +1,43 @@
.progress {
margin-top: 3px;
}
.progress__bg {
stroke: #eee;
stroke-width: 0.75em;
}
.progress__bar {
stroke: #3b9dff;
stroke-width: 0.75em;
transition: stroke-dashoffset 300ms linear;
}
.progress__indefinite {
stroke: #3b9dff;
stroke-width: 0.75em;
animation: 1s linear infinite spin;
transform-origin: center;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.progress__percent {
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
font-size: 43.2px;
letter-spacing: -0.78px;
line-height: 58px;
user-select: none;
}
.progress--invisible {
display: none;
}

View File

@@ -1,54 +0,0 @@
const html = require('choo/html');
module.exports = function(selected, options, translate, changed) {
const id = `select-${Math.random()}`;
let x = selected;
function close() {
const ul = document.getElementById(id);
const body = document.querySelector('body');
ul.classList.remove('active');
body.removeEventListener('click', close);
}
function toggle(event) {
event.stopPropagation();
const ul = document.getElementById(id);
if (ul.classList.contains('active')) {
close();
} else {
ul.classList.add('active');
const body = document.querySelector('body');
body.addEventListener('click', close);
}
}
function choose(event) {
event.stopPropagation();
const target = event.target;
const value = +target.dataset.value;
target.parentNode.previousSibling.firstElementChild.textContent = translate(
value
);
if (x !== value) {
x = value;
changed(value);
}
close();
}
return html`
<div class="selectbox">
<div onclick=${toggle}>
<span class="link">${translate(selected)}</span>
<svg width="32" height="32">
<polygon points="8 18 17 28 26 18" fill="#0094fb"/>
</svg>
</div>
<ul id="${id}" class="selectOptions">
${options.map(
i =>
html`<li class="selectOption" onclick=${choose} data-value="${i}">${i}</li>`
)}
</ul>
</div>`;
};

View File

@@ -0,0 +1,28 @@
const html = require('choo/html');
module.exports = function(selected, options, translate, changed) {
const id = `select-${Math.random()}`;
let x = selected;
return html`
<div class="select">
<select id="${id}" onchange=${choose}>
${options.map(
i =>
html`<option value="${i}" ${
i === selected ? 'selected' : ''
}>${translate(i)}</option>`
)}
</select>
</div>`;
function choose(event) {
const target = event.target;
const value = +target.value;
if (x !== value) {
x = value;
changed(value);
}
}
};

View File

@@ -0,0 +1,46 @@
.select {
background-color: var(--pageBGColor);
overflow: hidden;
padding: 4px 2px 4px 2px;
border: 1px dotted #0094fb88;
border-radius: 4px;
display: inline;
position: relative;
}
.select::after {
color: #0094fb;
content: '\25BC';
pointer-events: none;
font-size: 20px;
margin-left: -30px;
padding-right: 10px;
}
option {
padding: 0;
}
select {
appearance: none;
outline: 0;
box-shadow: none;
border: 0;
background: #fff;
background-image: none;
font-size: 1em;
font-weight: 200;
margin: 0;
color: #0094fb;
cursor: pointer;
padding-right: 40px;
}
select:active {
background-color: var(--pageBGColor);
border: 0;
}
#arrow {
position: relative;
}

View File

@@ -0,0 +1,37 @@
const html = require('choo/html');
const passwordInput = require('../passwordInput');
module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id);
return html`
<div class="setPasswordSection">
<div class="checkbox">
<input
${file.hasPassword ? 'disabled' : ''}
${file.hasPassword || state.passwordSetError ? 'checked' : ''}
class="checkbox__input"
id="add-password"
type="checkbox"
autocomplete="off"
onchange=${togglePasswordInput}/>
<label class="checkbox__label" for="add-password">
${state.translate('requirePasswordCheckbox')}
</label>
</div>
${passwordInput(file, state, emit)}
</div>`;
function togglePasswordInput(e) {
const unlockInput = document.getElementById('password-input');
const boxChecked = e.target.checked;
document
.querySelector('.passwordInput')
.classList.toggle('passwordInput--hidden', !boxChecked);
if (boxChecked) {
unlockInput.focus();
} else {
unlockInput.value = '';
}
}
};

View File

@@ -0,0 +1,69 @@
.setPasswordSection {
padding: 10px 0;
max-width: 100%;
overflow-wrap: break-word;
}
.checkbox {
min-height: 24px;
}
.checkbox__input {
position: absolute;
opacity: 0;
}
.checkbox__label {
line-height: 23px;
cursor: pointer;
color: var(--lightTextColor);
user-select: none;
}
.checkbox__label::before {
content: '';
height: 20px;
width: 20px;
margin-right: 10px;
margin-left: 5px;
float: left;
border: 1px solid rgba(12, 12, 13, 0.3);
border-radius: 2px;
}
.checkbox__input:focus + .checkbox__label::before,
.checkbox:hover .checkbox__label::before {
border: 1px solid var(--primaryControlBGColor);
}
.checkbox__input:checked + .checkbox__label {
color: var(--textColor);
}
.checkbox__input:checked + .checkbox__label::before {
background-image: url('../assets/check-16-blue.svg');
background-position: 2px 1px;
}
.checkbox__input:disabled + .checkbox__label {
cursor: auto;
}
.checkbox__input:disabled + .checkbox__label::before {
background-image: url('../assets/check-16-blue.svg');
background-repeat: no-repeat;
background-size: 26px 26px;
border: none;
cursor: auto;
}
@media (max-device-width: 520px), (max-width: 520px) {
.setPasswordSection {
align-self: center;
min-width: 95%;
}
.checkbox__label::before {
margin-left: 0;
}
}

View File

@@ -1,80 +0,0 @@
const html = require('choo/html');
module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id);
return html`<div class="selectPassword">
${passwordSpan(file.password)}
<button
id="resetButton"
onclick=${toggleResetInput}
>${state.translate('changePasswordButton')}</button>
<form
id='reset-form'
class="setPassword hidden"
onsubmit=${resetPassword}
data-no-csrf>
<input id="unlock-reset-input"
class="unlock-input input-no-btn"
maxlength="32"
autocomplete="off"
type="password"
oninput=${inputChanged}
placeholder="${state.translate('unlockInputPlaceholder')}">
<input type="submit"
id="unlock-reset-btn"
class="btn btn-hidden"
value="${state.translate('changePasswordButton')}"/>
</form>
</div>`;
function passwordSpan(password) {
password = password || '●●●●●';
const span = html([
`<span>${state.translate('passwordResult', {
password:
'<pre class="passwordOriginal"></pre><pre class="passwordMask"></pre>'
})}</span>`
]);
const og = span.querySelector('.passwordOriginal');
const masked = span.querySelector('.passwordMask');
og.textContent = password;
masked.textContent = password.replace(/./g, '●');
return span;
}
function inputChanged() {
const resetInput = document.getElementById('unlock-reset-input');
const resetBtn = document.getElementById('unlock-reset-btn');
if (resetInput.value.length > 0) {
resetBtn.classList.remove('btn-hidden');
resetInput.classList.remove('input-no-btn');
} else {
resetBtn.classList.add('btn-hidden');
resetInput.classList.add('input-no-btn');
}
}
function resetPassword(event) {
event.preventDefault();
const password = document.querySelector('#unlock-reset-input').value;
if (password.length > 0) {
document.getElementById('copy').classList.remove('wait-password');
document.getElementById('copy-btn').disabled = false;
emit('password', { password, file });
}
return false;
}
function toggleResetInput(event) {
const form = event.target.parentElement.querySelector('form');
const input = document.getElementById('unlock-reset-input');
if (form.style.visibility === 'hidden' || form.style.visibility === '') {
form.style.visibility = 'visible';
input.focus();
} else {
form.style.visibility = 'hidden';
}
inputChanged();
}
};

View File

@@ -1,70 +0,0 @@
const html = require('choo/html');
module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id);
const div = html`
<div class="selectPassword">
<div id="addPasswordWrapper">
<input
id="addPassword"
type="checkbox"
autocomplete="off"
onchange=${togglePasswordInput}/>
<label for="addPassword">
${state.translate('requirePasswordCheckbox')}
</label>
</div>
<form class="setPassword hidden" onsubmit=${setPassword} data-no-csrf>
<input id="unlock-input"
class="unlock-input input-no-btn"
maxlength="32"
autocomplete="off"
placeholder="${state.translate('unlockInputPlaceholder')}"
type="password"
oninput=${inputChanged}/>
<input type="submit"
id="unlock-btn"
class="btn btn-hidden"
value="${state.translate('addPasswordButton')}"/>
</form>
</div>`;
function inputChanged() {
const input = document.getElementById('unlock-input');
const btn = document.getElementById('unlock-btn');
if (input.value.length > 0) {
btn.classList.remove('btn-hidden');
input.classList.remove('input-no-btn');
} else {
btn.classList.add('btn-hidden');
input.classList.add('input-no-btn');
}
}
function togglePasswordInput(e) {
const unlockInput = document.getElementById('unlock-input');
const boxChecked = e.target.checked;
document
.querySelector('.setPassword')
.classList.toggle('hidden', !boxChecked);
if (boxChecked) {
unlockInput.focus();
} else {
unlockInput.value = '';
}
inputChanged();
}
function setPassword(event) {
event.preventDefault();
const password = document.getElementById('unlock-input').value;
if (password.length > 0) {
document.getElementById('copy').classList.remove('wait-password');
document.getElementById('copy-btn').disabled = false;
emit('password', { password, file });
}
return false;
}
return div;
};

View File

@@ -9,10 +9,7 @@ function arrayToB64(array) {
} }
function b64ToArray(str) { function b64ToArray(str) {
str = (str + '==='.slice((str.length + 3) % 4)) return b64.toByteArray(str + '==='.slice((str.length + 3) % 4));
.replace(/-/g, '+')
.replace(/_/g, '/');
return b64.toByteArray(str);
} }
function loadShim(polyfill) { function loadShim(polyfill) {
@@ -25,7 +22,7 @@ function loadShim(polyfill) {
}); });
} }
async function canHasSend(polyfill) { async function canHasSend() {
try { try {
const key = await window.crypto.subtle.generateKey( const key = await window.crypto.subtle.generateKey(
{ {
@@ -35,7 +32,6 @@ async function canHasSend(polyfill) {
true, true,
['encrypt', 'decrypt'] ['encrypt', 'decrypt']
); );
await window.crypto.subtle.encrypt( await window.crypto.subtle.encrypt(
{ {
name: 'AES-GCM', name: 'AES-GCM',
@@ -45,9 +41,24 @@ async function canHasSend(polyfill) {
key, key,
new ArrayBuffer(8) new ArrayBuffer(8)
); );
await window.crypto.subtle.importKey(
'raw',
window.crypto.getRandomValues(new Uint8Array(16)),
'PBKDF2',
false,
['deriveKey']
);
await window.crypto.subtle.importKey(
'raw',
window.crypto.getRandomValues(new Uint8Array(16)),
'HKDF',
false,
['deriveKey']
);
return true; return true;
} catch (err) { } catch (err) {
return loadShim(polyfill); console.error(err);
return false;
} }
} }
@@ -117,6 +128,14 @@ function percent(ratio) {
return `${Math.floor(ratio * 100)}%`; return `${Math.floor(ratio * 100)}%`;
} }
function number(n) {
if (LOCALIZE_NUMBERS) {
const locale = document.querySelector('html').lang;
return n.toLocaleString(locale);
}
return n.toString();
}
function allowedCopy() { function allowedCopy() {
const support = !!document.queryCommandSupported; const support = !!document.queryCommandSupported;
return support ? document.queryCommandSupported('copy') : false; return support ? document.queryCommandSupported('copy') : false;
@@ -126,29 +145,13 @@ function delay(delay = 100) {
return new Promise(resolve => setTimeout(resolve, delay)); return new Promise(resolve => setTimeout(resolve, delay));
} }
function fadeOut(id) { function fadeOut(selector) {
const classes = document.getElementById(id).classList; const classes = document.querySelector(selector).classList;
classes.remove('fadeIn'); classes.remove('effect--fadeIn');
classes.add('fadeOut'); classes.add('effect--fadeOut');
return delay(300); return delay(300);
} }
function saveFile(file) {
const dataView = new DataView(file.plaintext);
const blob = new Blob([dataView], { type: file.type });
const downloadUrl = URL.createObjectURL(blob);
if (window.navigator.msSaveBlob) {
return window.navigator.msSaveBlob(blob, file.name);
}
const a = document.createElement('a');
a.href = downloadUrl;
a.download = file.name;
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(downloadUrl);
}
function openLinksInNewTab(links, should = true) { function openLinksInNewTab(links, should = true) {
links = links || Array.from(document.querySelectorAll('a:not([target])')); links = links || Array.from(document.querySelectorAll('a:not([target])'));
if (should) { if (should) {
@@ -171,11 +174,12 @@ module.exports = {
allowedCopy, allowedCopy,
bytes, bytes,
percent, percent,
number,
copyToClipboard, copyToClipboard,
arrayToB64, arrayToB64,
b64ToArray, b64ToArray,
loadShim,
canHasSend, canHasSend,
isFile, isFile,
saveFile,
openLinksInNewTab openLinksInNewTab
}; };

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path class="icon-copy" fill="#0A8DFF" d="M14.707 8.293l-3-3A1 1 0 0 0 11 5h-1V4a1 1 0 0 0-.293-.707l-3-3A1 1 0 0 0 6 0H3a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3v3a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2V9a1 1 0 0 0-.293-.707zM12.586 9H11V7.414zm-5-5H6V2.414zM6 7v2H3V2h2v2.5a.5.5 0 0 0 .5.5H8a2 2 0 0 0-2 2zm2 7V7h2v2.5a.5.5 0 0 0 .5.5H13v4z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#0A8DFF" d="M14.707 8.293l-3-3A1 1 0 0 0 11 5h-1V4a1 1 0 0 0-.293-.707l-3-3A1 1 0 0 0 6 0H3a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3v3a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2V9a1 1 0 0 0-.293-.707zM12.586 9H11V7.414zm-5-5H6V2.414zM6 7v2H3V2h2v2.5a.5.5 0 0 0 .5.5H8a2 2 0 0 0-2 2zm2 7V7h2v2.5a.5.5 0 0 0 .5.5H13v4z"/></svg>

Before

Width:  |  Height:  |  Size: 416 B

After

Width:  |  Height:  |  Size: 398 B

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

17
assets/spinner.svg Normal file
View File

@@ -0,0 +1,17 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 694 B

View File

@@ -1,5 +1,5 @@
last 2 chrome versions last 2 chrome versions
last 2 firefox versions last 2 firefox versions
last 2 safari versions
last 2 edge versions
firefox esr firefox esr
ie >= 9
safari >= 9

View File

@@ -1,4 +1,7 @@
const { MessageContext } = require('fluent'); // TODO: when node supports 'for await' we can remove babel-polyfill
// and use 'fluent' instead of 'fluent/compat' (also below near line 42)
require('babel-polyfill');
const { MessageContext } = require('fluent/compat');
const fs = require('fs'); const fs = require('fs');
function toJSON(map) { function toJSON(map) {
@@ -36,22 +39,25 @@ module.exports = function(source) {
return ` return `
module.exports = \` module.exports = \`
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
var fluent = require('fluent'); require('babel-polyfill');
var fluent = require('fluent/compat');
} }
var ctx = new fluent.MessageContext('${locale}', {useIsolating: false}); (function () {
ctx._messages = new Map(${toJSON(merged)}); var ctx = new fluent.MessageContext('${locale}', {useIsolating: false});
function translate(id, data) { ctx._messages = new Map(${toJSON(merged)});
var msg = ctx.getMessage(id); function translate(id, data) {
if (typeof(msg) !== 'string' && !msg.val && msg.attrs) { var msg = ctx.getMessage(id);
msg = msg.attrs.title || msg.attrs.alt if (typeof(msg) !== 'string' && !msg.val && msg.attrs) {
msg = msg.attrs.title || msg.attrs.alt
}
return ctx.format(msg, data);
} }
return ctx.format(msg, data); if (typeof window === 'undefined') {
} module.exports = translate;
if (typeof window === 'undefined') { }
module.exports = translate; else {
} window.translate = translate;
else { }
window.translate = translate; })();
}
\``; \``;
}; };

View File

@@ -1,3 +1,14 @@
/*
This code is included by both the server and frontend via
common/assets.js
When included from the server the export will be the function.
When included from the frontend (via webpack) the export will
be an object mapping file names to hashed file names. Example:
"send_logo.svg": "send_logo.5fcfdf0e.svg"
*/
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@@ -14,6 +25,6 @@ module.exports = function() {
return { return {
code, code,
dependencies: files.map(f => require.resolve('../assets/' + f)), dependencies: files.map(f => require.resolve('../assets/' + f)),
cacheable: false cacheable: true
}; };
}; };

View File

@@ -1,3 +1,14 @@
/*
This code is included by both the server and frontend via
common/locales.js
When included from the server the export will be the function.
When included from the frontend (via webpack) the export will
be an object mapping ftl files to js files. Example:
"public/locales/en-US/send.ftl":"public/locales/en-US/send.6b4f8354.js"
*/
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@@ -17,6 +28,6 @@ module.exports = function() {
dependencies: dirs.map(d => dependencies: dirs.map(d =>
require.resolve(`../public/locales/${d}/send.ftl`) require.resolve(`../public/locales/${d}/send.ftl`)
), ),
cacheable: false cacheable: true
}; };
}; };

26
build/readme.md Normal file
View File

@@ -0,0 +1,26 @@
# Custom Loaders
## Fluent Loader
The fluent loader "compiles" `.ftl` files into `.js` files directly usable by both the frontend and server for localization.
## Generate Asset Map
This loader enumerates all the files in `assets/` so that `common/assets.js` can provide mappings from the source filename to the hashed filename used on the site.
## Generate L10N Map
This loader enumerates all the ftl files in `public/locales` so that the fluent loader can create it's js files.
## Package.json Loader
This loader creates a `version.json` file that gets exposed by the `/__version__` route from the `package.json` file and current git commit hash.
## Version Loader
This loader substitutes the string "VERSION" for the version string specified in `package.json`. This is a workaround because `package.json` already uses the `package_json_loader`. See [app/templates/header/index.js](../app/templates/header/index.js) for more info.
# See Also
- [docs/build.md](../docs/build.md)
- [webpack.config.js](../webpack.config.js)

View File

@@ -1,35 +1,136 @@
machine: version: 2.0
node: jobs:
version: 8 build:
services: docker:
- docker - image: circleci/node:8
- redis steps:
environment: - checkout
PATH: "/home/ubuntu/send/firefox:$PATH" - restore_cache:
key: send-{{ checksum "package-lock.json" }}
dependencies: - run: npm install
pre: - save_cache:
- npm i -g get-firefox geckodriver nsp key: send-{{ checksum "package-lock.json" }}
- get-firefox --platform linux --extract --target /home/ubuntu/send paths:
- node_modules
deployment: - run: npm run build
latest: - persist_to_workspace:
branch: master root: .
commands: paths:
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS - ./dist
- docker build -t mozilla/send:latest . test:
- docker push mozilla/send:latest docker:
tags: - image: circleci/node:8-browsers
tag: /.*/ steps:
owner: mozilla - checkout
commands: - restore_cache:
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS key: send-{{ checksum "package-lock.json" }}
- docker build -t mozilla/send:$CIRCLE_TAG . - run: npm install
- docker push mozilla/send:$CIRCLE_TAG - save_cache:
key: send-{{ checksum "package-lock.json" }}
test: paths:
override: - node_modules
- npm run build - run: npm run check
- npm run lint - run: npm run lint
- npm test - run: npm run test
- nsp check - store_artifacts:
path: coverage
integration_tests:
machine: true
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Install Docker Compose
command: |
set -x
pip install docker-compose>=1.18
docker-compose --version
- run:
command: npm run test-integration
- store_artifacts:
path: coverage/send-test.html
deploy_dev:
machine: true
steps:
- checkout
- attach_workspace:
at: .
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
- run: docker build -t mozilla/send:latest .
- run: docker push mozilla/send:latest
deploy_stage:
machine: true
steps:
- checkout
- attach_workspace:
at: .
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
- run: docker build -t mozilla/send:$CIRCLE_TAG .
- run: docker push mozilla/send:$CIRCLE_TAG
workflows:
version: 2
test_pr:
jobs:
- build:
filters:
branches:
ignore: master
- test:
filters:
branches:
ignore: master
- integration_tests:
filters:
branches:
ignore: master
requires:
- build
build_and_deploy_dev:
jobs:
- build:
filters:
branches:
only: master
tags:
ignore: /^v.*/
- deploy_dev:
requires:
- build
filters:
branches:
only: master
tags:
ignore: /^v.*/
build_and_deploy_stage:
jobs:
- build:
filters:
branches:
ignore: /.*/
tags:
only: /^v.*/
- test:
filters:
branches:
ignore: /.*/
tags:
only: /^v.*/
- integration_tests:
requires:
- build
filters:
branches:
ignore: /.*/
tags:
only: /^v.*/
- deploy_stage:
requires:
- build
- test
- integration_tests
filters:
branches:
ignore: /.*/
tags:
only: /^v.*/

View File

@@ -4,7 +4,7 @@ const isServer = typeof gen === 'function';
const prefix = isServer ? '/' : ''; const prefix = isServer ? '/' : '';
let manifest = {}; let manifest = {};
try { try {
//eslint-disable-next-line node/no-missing-require // eslint-disable-next-line node/no-missing-require
manifest = require('../dist/manifest.json'); manifest = require('../dist/manifest.json');
} catch (e) { } catch (e) {
// use middleware // use middleware
@@ -17,6 +17,7 @@ function getLocale(name) {
} }
function serverTranslator(name) { function serverTranslator(name) {
// eslint-disable-next-line security/detect-non-literal-require
return require(`../dist/${locales[`public/locales/${name}/send.ftl`]}`); return require(`../dist/${locales[`public/locales/${name}/send.ftl`]}`);
} }

3
common/readme.md Normal file
View File

@@ -0,0 +1,3 @@
# Common Code
This directory contains code loaded by both the frontend `app` and backend `server`. The code here can be challenging to understand at first because the contexts for the two (three counting the dev server) environments that include them are quite different, but the purpose of these modules are quite simple, to provide mappings from the source assets (`copy-16.png`) to the concrete production assets (`copy-16.db66e0bf.svg`), similarly for localizations.

View File

@@ -10,3 +10,17 @@ services:
- REDIS_HOST=redis - REDIS_HOST=redis
redis: redis:
image: redis:alpine image: redis:alpine
selenium:
image: b4handjr/selenium-firefox
ports:
- "${VNC_PORT:-5900}:5900"
shm_size: 2g
integration-tests:
build: ./test/integration
environment:
- BASE_URL=${BASE_URL:-http://web:1443}
links:
- web
- selenium
volumes:
- "./coverage:/coverage"

22
docs/build.md Normal file
View File

@@ -0,0 +1,22 @@
Send has two build configurations, development and production. Both can be run via `npm` scripts, `npm start` for development and `npm run build` for production. Webpack is our only build tool and all configuration lives in [webpack.config.js](../webpack.config.js).
# Development
`npm start` launches a `webpack-dev-server` on port 8080 that compiles the assets and watches files for changes. It also serves the backend API and frontend unit tests via the `server/dev.js` entrypoint. The frontend tests can be run in the browser by navigating to http://localhost:8080/test and will rerun automatically as the watched files are saved with changes.
# Production
`npm run build` compiles the assets and writes the files to the `dist/` directory. `npm run prod` launches an Express server on port 1443 that serves the backend API and frontend static assets from `dist/` via the `server/prod.js` entrypoint.
# Notable differences
- Development compiles assets in memory, so no `dist/` directory is generated
- Development does not enable CSP headers
- Development frontend source is instrumented for code coverage
- Only development includes sourcemaps
- Only development exposes the `/test` route
- Production sets Cache-Control immutable headers on the hashed static assets
# Custom Loaders
The `build/` directory contains custom webpack loaders specific to Send. See [build/readme.md](../build/readme.md) for details on each loader.

46
docs/encryption.md Normal file
View File

@@ -0,0 +1,46 @@
# File Encryption
Send use 128-bit AES-GCM encryption via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) to encrypt files in the browser before uploading them to the server. The code is in [app/keychain.js](../app/keychain.js).
## Steps
### Uploading
1. A new secret key is generated with `crypto.getRandomValues`
2. The secret key is used to derive 3 more keys via HKDF SHA-256
- an encryption key for the file (AES-GCM)
- an encryption key for the file metadata (AES-GCM)
- a signing key for request authentication (HMAC SHA-256)
3. The file and metadata are encrypted with their corresponding keys
4. The encrypted data and signing key are uploaded to the server
5. An owner token and the share url are returned by the server and stored in local storage
6. The secret key is appended to the share url as a [#fragment](https://en.wikipedia.org/wiki/Fragment_identifier) and presented to the UI
### Downloading
1. The browser loads the share url page, which includes an authentication nonce
2. The browser imports the secret key from the url fragment
3. The same 3 keys as above are derived
4. The browser signs the nonce with it's signing key and requests the metadata
5. The encrypted metadata is decrypted and presented on the page
6. The browser makes another authenticated request to download the encrypted file
7. The browser downloads and decrypts the file
8. The file prompts the save dialog or automatically saves depending on the browser settings
### Passwords
A password may optionally be set to authenticate the download request. When a password is set the following steps occur.
#### Sender
1. The original signing key derived from the secret key is discarded
2. A new signing key is generated via PBKDF2 from the user entered password and the full share url (including secret key fragment)
3. The new key is sent to the server, authenticated by the owner token
4. The server stores the new key and marks the record as needing a password
#### Downloader
1. The browser loads the share url page, which includes an authentication nonce and indicator that the file requires a password
2. The user is prompted for the password and the signing key is derived
3. The browser requests the metadata using the key to sign the nonce
4. If the password was correct the metadata is returned, otherwise a 401

84
docs/experiments.md Normal file
View File

@@ -0,0 +1,84 @@
# A/B experiment testing
We're using Google Analytics Experiments for A/B testing.
## Creating an experiment
Navigate to the Behavior > Experiments section of Google Analytics and click the "Create experiment" button.
The "Objective for this experiment" is the most complicated part. See the "Promo click (Goal ID 4 / Goal Set 1)" for an example.
In step 2 add as many variants as you plan to test. The urls are not important since we aren't using their js library to choose the variants. The name will show up in the report so choose good ones. "Original page" becomes variant 0 and each variant increments by one. We'll use the numbers in our `app/experiments.js` code.
Step 3 contains some script that we'll ignore. The important thing here is the **Experiment ID**. This is the value we need to name our experiment in `app/experiments.js`. Save the changes so far and wait until the code containing the experiment has been deployed to production **before** starting the experiment.
## Experiment code
Code for experiments live in [app/experiments.js](../app/experiments.js). There's an `experiments` object that contains the logic for deciding whether an experiment should run, which variant to use, and what to do. Each object needs to have these functions:
### `eligible` function
This function returns a boolean of whether this experiment should be active for this session. Any data available to the page can be used determine the result.
### `variant` function
This function returns which experimental group this session is placed in. The variant values need to match the values set up in Google Analytics, usually 0 thru N-1. This value is usually picked at random based on what percentage of each variant is desired.
### `run` function
This function gets the `variant` value chosen by the variant function and the `state` and `emitter` objects from the app. This function can do anything needed to change the app based on the experiment. A common pattern is to set or change a value on `state` that will be picked up by other parts of the app, like ui templates, to change how it looks or behaves.
### Example
Here's a full example of the experiment object:
```js
const experiments = {
S9wqVl2SQ4ab2yZtqDI3Dw: { // The Experiment ID from Google Analytics
id: 'S9wqVl2SQ4ab2yZtqDI3Dw',
run: function(variant, state, emitter) {
switch (variant) {
case 1:
state.promo = 'blue';
break;
case 2:
state.promo = 'pink';
break;
default:
state.promo = 'grey';
}
emitter.emit('render');
},
eligible: function() {
return (
!/firefox|fxios/i.test(navigator.userAgent) &&
document.querySelector('html').lang === 'en-US'
);
},
variant: function(state) {
const n = this.luckyNumber(state);
if (n < 0.33) {
return 0;
}
return n < 0.66 ? 1 : 2;
},
luckyNumber: function(state) {
return luckyNumber(
`${this.id}:${state.storage.get('testpilot_ga__cid')}`
);
}
}
};
```
## Reporting results
All metrics pings will include the variant and experiment id, but it's usually important to trigger a specific event to be counted as the experiment goal (the "Objective for this experiment" part from setup). Use an 'experiment' event to do this. For example:
```js
emit('experiment', { cd3: 'promo' });
```
where `emit` is the app emitter function passed to the [route handler](https://github.com/choojs/choo#approuteroutename-handlerstate-emit)
The second argument can be an object with any additional parameters. It usually includes a custom dimension that we chose to filter on while creating the experiment in Google Analytics.

View File

@@ -31,12 +31,11 @@ Since Send is an open source project, you can see all of the cool ways we use Ja
## How long are files available for? ## How long are files available for?
Files are available to be downloaded for 24 hours, after which they are removed Files are available to be downloaded for 24 hours, after which they are removed
from the server. They are also removed immediately after a download completes. from the server. They are also removed immediately once the download limit is reached.
## Can a file be downloaded more than once? ## Can a file be downloaded more than once?
Not currently, but we're considering multiple download support in a future Yes, once a file is submitted to Send you can select the download limit.
release.
*Disclaimer: Send is an experiment and under active development. The answers *Disclaimer: Send is an experiment and under active development. The answers

29
docs/localization.md Normal file
View File

@@ -0,0 +1,29 @@
# Localization
Send is localized in over 50 languages. We use the [fluent](http://projectfluent.org/) library and store our translations in [FTL](http://projectfluent.org/fluent/guide/) files in `public/locales/`. `en-US` is our base language, and other languages are managed by [pontoon](https://pontoon.mozilla.org/projects/test-pilot-firefox-send/).
## Process
Strings are added or removed from [public/locales/en-US/send.ftl] as needed. Strings **MUST NOT** be *changed* after they've been commited and pushed to master. Changing a string requires creating a new ID with a new name (preferably descriptive instead of incremented) and deletion of the obsolete ID. It's often useful to add a comment above the string with info about how and where the string is used.
Once new strings are commited to master they are available for translators in Pontoon. All languages other than `en-US` should be edited via Pontoon. Translations get automatically commited to the github master branch.
### Activation
The development environment includes all locales in `public/locales` via the `L10N_DEV` environment variable. Production uses `package.json` as the list of locales to use. Once a locale has enough string coverage it should be added to `package.json`.
## Code
In `app/` we use the `state.translate()` function to translate strings to the best matching language base on the user's `Accept-Language` header. It's a wrapper around fluent's [MessageContext.format](http://projectfluent.org/fluent.js/fluent/MessageContext.html). It works the same for both server and client side rendering.
### Examples
```js
// simple string
const finishedString = state.translate('downloadFinish')
// with parameters
const progressString = state.translate('downloadingPageProgress', {
filename: state.fileInfo.name,
size: bytes(state.fileInfo.size)
})
```

19022
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "firefox-send", "name": "firefox-send",
"description": "File Sharing Experiment", "description": "File Sharing Experiment",
"version": "2.3.0", "version": "2.6.1",
"author": "Mozilla (https://mozilla.org)", "author": "Mozilla (https://mozilla.org)",
"repository": "mozilla/send", "repository": "mozilla/send",
"homepage": "https://github.com/mozilla/send/", "homepage": "https://github.com/mozilla/send/",
@@ -9,10 +9,12 @@
"private": true, "private": true,
"scripts": { "scripts": {
"precommit": "lint-staged", "precommit": "lint-staged",
"prepush": "npm test",
"check": "nsp check",
"clean": "rimraf dist", "clean": "rimraf dist",
"build": "npm run clean && webpack -p", "build": "npm run clean && webpack -p",
"lint": "npm-run-all lint:*", "lint": "npm-run-all lint:*",
"lint:css": "stylelint 'assets/*.css'", "lint:css": "stylelint app/*.css app/**/*.css",
"lint:js": "eslint .", "lint:js": "eslint .",
"lint-locales": "node scripts/lint-locales", "lint-locales": "node scripts/lint-locales",
"lint-locales:dev": "npm run lint-locales", "lint-locales:dev": "npm run lint-locales",
@@ -20,11 +22,14 @@
"format": "prettier '**/*.js' 'assets/*.css' --single-quote --write", "format": "prettier '**/*.js' 'assets/*.css' --single-quote --write",
"get-prod-locales": "node scripts/get-prod-locales", "get-prod-locales": "node scripts/get-prod-locales",
"get-prod-locales:write": "npm run get-prod-locales -- --write", "get-prod-locales:write": "npm run get-prod-locales -- --write",
"changelog": "github-changes -o mozilla -r send --only-pulls --use-commit-body --no-merges",
"contributors": "git shortlog -s | awk -F\\t '{print $2}' > CONTRIBUTORS", "contributors": "git shortlog -s | awk -F\\t '{print $2}' > CONTRIBUTORS",
"release": "npm-run-all contributors changelog", "release": "npm-run-all contributors changelog",
"test": "mocha test/unit", "test": "npm-run-all test:*",
"start": "cross-env NODE_ENV=development webpack-dev-server", "test:backend": "nyc mocha --reporter=min test/backend",
"test:frontend": "cross-env NODE_ENV=development node test/frontend/runner.js && nyc report --reporter=html",
"test-integration": "docker-compose up --abort-on-container-exit --exit-code-from integration-tests --build --remove-orphans --quiet-pull && docker-compose down",
"test-integration-stage": "cross-env BASE_URL=https://send.stage.mozaws.net npm run test-integration",
"start": "npm run clean && cross-env NODE_ENV=development webpack-dev-server",
"prod": "node server/prod.js" "prod": "node server/prod.js"
}, },
"lint-staged": { "lint-staged": {
@@ -39,78 +44,96 @@
"git add" "git add"
] ]
}, },
"nyc": {
"reporter": [
"text"
],
"cache": true
},
"engines": { "engines": {
"node": ">=8.2.0" "node": ">=8.2.0"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^7.2.5", "asmcrypto.js": "^0.22.0",
"babel-core": "^6.26.0", "babel-core": "^6.26.3",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.5",
"babel-plugin-yo-yoify": "^1.0.2", "babel-plugin-istanbul": "^4.1.6",
"babel-polyfill": "^6.26.0", "babel-plugin-yo-yoify": "^1.0.3",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.7.0",
"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", "babel-preset-stage-3": "^6.24.1",
"copy-webpack-plugin": "^4.3.1", "base64-js": "^1.3.0",
"cross-env": "^5.1.3", "copy-webpack-plugin": "^4.5.2",
"css-loader": "^0.28.9", "cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"css-mqpacker": "^6.0.2", "css-mqpacker": "^6.0.2",
"cssnano": "^3.10.0", "eslint": "^4.19.1",
"eslint": "^4.16.0", "eslint-plugin-mocha": "^4.12.1",
"eslint-plugin-mocha": "^4.11.0", "eslint-plugin-node": "^6.0.1",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-security": "^1.4.0", "eslint-plugin-security": "^1.4.0",
"expose-loader": "^0.7.4", "expose-loader": "^0.7.5",
"extract-loader": "^1.0.2", "extract-loader": "^1.0.2",
"file-loader": "^1.1.6", "extract-text-webpack-plugin": "^3.0.2",
"fast-text-encoding": "^1.0.0",
"file-loader": "^1.1.11",
"fluent-intl-polyfill": "^0.1.0", "fluent-intl-polyfill": "^0.1.0",
"git-rev-sync": "^1.9.1", "git-rev-sync": "^1.12.0",
"github-changes": "^1.1.2",
"html-loader": "^0.5.5", "html-loader": "^0.5.5",
"husky": "^0.14.3", "husky": "^0.14.3",
"lint-staged": "^4.3.0", "lint-staged": "^7.3.0",
"mocha": "^3.5.3", "mocha": "^5.2.0",
"nanobus": "^4.3.2", "nanobus": "^4.3.4",
"npm-run-all": "^4.1.2", "nanotiming": "^7.3.1",
"postcss-loader": "^2.0.10", "npm-run-all": "^4.1.3",
"prettier": "^1.10.2", "nsp": "^3.2.1",
"nyc": "^11.9.0",
"postcss-cssnext": "^3.1.0",
"postcss-import": "^11.1.0",
"postcss-loader": "^2.1.6",
"prettier": "^1.14.3",
"proxyquire": "^1.8.0", "proxyquire": "^1.8.0",
"raven-js": "^3.22.1", "puppeteer": "^1.9.0",
"redis-mock": "^0.20.0", "raven-js": "^3.27.0",
"require-from-string": "^2.0.1", "redis-mock": "^0.21.0",
"require-from-string": "^2.0.2",
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"selenium-webdriver": "^3.6.0", "sinon": "^4.5.0",
"sinon": "^4.2.2",
"string-hash": "^1.1.3", "string-hash": "^1.1.3",
"stylelint-config-standard": "^17.0.0", "stylelint": "^9.6.0",
"stylelint-no-unsupported-browser-features": "^1.0.1", "stylelint-config-standard": "^18.2.0",
"supertest": "^3.0.0", "stylelint-no-unsupported-browser-features": "^2.0.0",
"svgo": "^1.1.1",
"svgo-loader": "^2.2.0",
"testpilot-ga": "^0.3.0", "testpilot-ga": "^0.3.0",
"val-loader": "^1.1.0", "val-loader": "^1.1.1",
"webpack": "^3.10.0", "webpack": "^3.12.0",
"webpack-dev-middleware": "^2.0.6",
"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.188.0", "@google-cloud/storage": "^2.3.0",
"body-parser": "^1.18.2", "aws-sdk": "^2.349.0",
"choo": "^6.7.0", "babel-polyfill": "^6.26.0",
"choo": "^6.13.0",
"cldr-core": "^32.0.0", "cldr-core": "^32.0.0",
"connect-busboy": "0.0.2", "connect-busboy": "0.0.2",
"convict": "^4.0.1", "convict": "^4.4.0",
"express": "^4.16.2", "express": "^4.16.3",
"fluent": "^0.4.1", "fast-crc32c": "^1.0.4",
"fluent": "^0.6.4",
"fluent-langneg": "^0.1.0", "fluent-langneg": "^0.1.0",
"helmet": "^3.10.0", "helmet": "^3.13.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"mozlog": "^2.2.0", "mozlog": "^2.2.0",
"raven": "^2.4.0", "raven": "^2.6.4",
"redis": "^2.8.0" "redis": "^2.8.0"
}, },
"availableLanguages": [ "availableLanguages": [
"en-US", "en-US",
"ar",
"ast", "ast",
"az", "az",
"bs", "bs",
@@ -118,9 +141,11 @@
"cak", "cak",
"cs", "cs",
"cy", "cy",
"da",
"de", "de",
"dsb", "dsb",
"el", "el",
"en-CA",
"es-AR", "es-AR",
"es-CL", "es-CL",
"es-ES", "es-ES",
@@ -131,6 +156,7 @@
"fy-NL", "fy-NL",
"hsb", "hsb",
"hu", "hu",
"ia",
"id", "id",
"it", "it",
"ja", "ja",
@@ -143,11 +169,14 @@
"nn-NO", "nn-NO",
"pt-BR", "pt-BR",
"pt-PT", "pt-PT",
"ro",
"ru", "ru",
"sk", "sk",
"sl", "sl",
"sq",
"sr", "sr",
"sv-SE", "sv-SE",
"te",
"tl", "tl",
"tr", "tr",
"uk", "uk",

View File

@@ -1,14 +1,12 @@
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const mqpacker = require('css-mqpacker');
const config = require('./server/config');
const options = { const options = {
plugins: [autoprefixer, mqpacker, cssnano] plugins: {
'postcss-import': {},
'postcss-cssnext': {},
'css-mqpacker': {}
}
}; };
if (config.env === 'development') { if (process.env.NODE_ENV === 'development') {
options.map = { inline: true }; options.map = { inline: true };
} }

View File

@@ -1,4 +1,4 @@
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
title = فَيَرفُكس سِنْد title = فَيَرفُكس سِنْد
siteSubtitle = تجربة وِبّيّة siteSubtitle = تجربة وِبّيّة
siteFeedback = الانطباعات siteFeedback = الانطباعات
@@ -7,8 +7,7 @@ uploadPageExplainer = أرسل الملفات عبر رابط آمن خاص وم
uploadPageLearnMore = اطّلع على المزيد uploadPageLearnMore = اطّلع على المزيد
uploadPageDropMessage = أسقِط ملفّك هنا لبدء الرفع uploadPageDropMessage = أسقِط ملفّك هنا لبدء الرفع
uploadPageSizeMessage = لتتحصل على أفضل تجربة، من المستحسن أن يكون الملف أصغر من 1 غ.بايت uploadPageSizeMessage = لتتحصل على أفضل تجربة، من المستحسن أن يكون الملف أصغر من 1 غ.بايت
uploadPageBrowseButton = اختر ملفّا على حاسوبك uploadPageBrowseButton = اختر ملفًا على حاسوبك
.title = اختر ملفّا على حاسوبك
uploadPageBrowseButton1 = اختر ملفّا لرفعه uploadPageBrowseButton1 = اختر ملفّا لرفعه
uploadPageMultipleFilesAlert = رفع عدة ملفات (أو رفع مجلد) ليس مدعوما حاليا. uploadPageMultipleFilesAlert = رفع عدة ملفات (أو رفع مجلد) ليس مدعوما حاليا.
uploadPageBrowseButtonTitle = ارفع ملفًا uploadPageBrowseButtonTitle = ارفع ملفًا
@@ -20,16 +19,15 @@ decryptingFile = يفك التعمية…
notifyUploadDone = انتهى الرفع. notifyUploadDone = انتهى الرفع.
uploadingPageMessage = ما إن يُرفع الملف سيُتاح ضبط خيارات انتهاء صلاحيته. uploadingPageMessage = ما إن يُرفع الملف سيُتاح ضبط خيارات انتهاء صلاحيته.
uploadingPageCancel = ألغِ الرفع uploadingPageCancel = ألغِ الرفع
.title = ألغِ الرفع
uploadCancelNotification = أُلغي الرفع. uploadCancelNotification = أُلغي الرفع.
uploadingPageLargeFileMessage = هذا الملف كبير الحجم وسيأخذ رفعه وقتا. انتظر رجاءً. uploadingPageLargeFileMessage = هذا الملف كبير الحجم وسيأخذ رفعه وقتا. انتظر رجاءً.
uploadingFileNotification = أعلِمني عندما يكتمل الرفع. uploadingFileNotification = أعلِمني عندما يكتمل الرفع.
uploadSuccessConfirmHeader = جاهز للإرسال uploadSuccessConfirmHeader = جاهز للإرسال
uploadSvgAlt uploadSvgAlt = ارفع
.alt = ارفع
uploadSuccessTimingHeader = ستنتهي صلاحية الرابط الذي يشير إلى الملف في حال: نُزِّل لأول مرة، أو مرّ ٢٤ ساعة على رفعه. uploadSuccessTimingHeader = ستنتهي صلاحية الرابط الذي يشير إلى الملف في حال: نُزِّل لأول مرة، أو مرّ ٢٤ ساعة على رفعه.
expireInfo = ستنتهي صلاحية رابط الملف بعد { $downloadCount } أو { $timespan }. expireInfo = ستنتهي صلاحية رابط الملف بعد { $downloadCount } أو { $timespan }.
downloadCount = { $num -> downloadCount =
{ $num ->
[zero] لا تنزيلات [zero] لا تنزيلات
[one] تنزيل واحد [one] تنزيل واحد
[two] تنزيلين [two] تنزيلين
@@ -37,7 +35,8 @@ downloadCount = { $num ->
[many] { $num } تنزيلًا [many] { $num } تنزيلًا
*[other] { $num } تنزيل *[other] { $num } تنزيل
} }
timespanHours = { $num -> timespanHours =
{ $num ->
[zero] أقل من ساعة [zero] أقل من ساعة
[one] ساعة [one] ساعة
[two] ساعتين [two] ساعتين
@@ -47,42 +46,36 @@ timespanHours = { $num ->
} }
copyUrlFormLabelWithName = انسخ الرابط وشاركه لإرسال الملف: { $filename } copyUrlFormLabelWithName = انسخ الرابط وشاركه لإرسال الملف: { $filename }
copyUrlFormButton = انسخ إلى الحافظة copyUrlFormButton = انسخ إلى الحافظة
.title = انسخ إلى الحافظة
copiedUrl = نُسخ! copiedUrl = نُسخ!
deleteFileButton = احذف الملف deleteFileButton = احذف الملف
.title = احذف الملف sendAnotherFileLink = أرسِل ملفًا آخر
sendAnotherFileLink = أرسل ملفّا آخر # Alternative text used on the download link/button (indicates an action).
.title = أرسل ملفّا آخر downloadAltText = نزّل
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = نزّل
downloadsFileList = التنزيلات downloadsFileList = التنزيلات
// Used as header in a column indicating the amount of time left before a # Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m") # download link expires (e.g. "10h 5m")
timeFileList = الوقت timeFileList = الوقت
// Used as header in a column indicating the number of times a file has been # Used as header in a column indicating the number of times a file has been
// downloaded # downloaded
downloadFileName = نزّل { $filename } downloadFileName = نزّل { $filename }
downloadFileSize = ({ $size }) downloadFileSize = ({ $size })
unlockInputLabel = أدخل كلمة السر unlockInputLabel = أدخل كلمة السر
unlockInputPlaceholder = كلمة السر unlockInputPlaceholder = كلمة السر
unlockButtonLabel = افتح القفل unlockButtonLabel = افتح القفل
downloadFileTitle = نزِّل الملف المعمّى downloadFileTitle = نزِّل الملف المعمّى
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
downloadMessage = يُرسل إليك صديقك ملفا عبر «فَيَرفُكس سِنْد»، وهي خدمة تتيح لك مشاركة الملفات عبر رابط آمن وخاص ومعمّى، حيث تنتهي صلاحياتها تلقائيا لتضمن عدم بقاء ما ترسله إلى الأبد. downloadMessage = يُرسل إليك صديقك ملفا عبر «فَيَرفُكس سِنْد»، وهي خدمة تتيح لك مشاركة الملفات عبر رابط آمن وخاص ومعمّى، حيث تنتهي صلاحياتها تلقائيا لتضمن عدم بقاء ما ترسله إلى الأبد.
// 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 = نزّل
.title = نزّل
downloadNotification = لقد اكتمل التنزيل. downloadNotification = لقد اكتمل التنزيل.
downloadFinish = اكتمل التنزيل downloadFinish = اكتمل التنزيل
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)". # This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
fileSizeProgress = ({ $partialSize } من أصل { $totalSize }) fileSizeProgress = ({ $partialSize } من أصل { $totalSize })
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
sendYourFilesLink = جرِّب «فَيَرفُكس سِنْد» sendYourFilesLink = جرِّب «فَيَرفُكس سِنْد»
downloadingPageProgress = ينزّل { $filename } ({ $size }) downloadingPageProgress = ينزّل { $filename } ({ $size })
downloadingPageMessage = رجاء أبقِ هذا اللسان مفتوحا حتى نجلب الملف ونفك تعميته. downloadingPageMessage = رجاء أبقِ هذا اللسان مفتوحا حتى نجلب الملف ونفك تعميته.
errorAltText errorAltText = خطأ أثناء الرفع
.alt = خطأ أثناء الرفع
errorPageHeader = حدث خطب ما. errorPageHeader = حدث خطب ما.
errorPageMessage = حدث خطب ما أثناء رفع الملف. errorPageMessage = حدث خطب ما أثناء رفع الملف.
errorPageLink = أرسل ملفا آخر errorPageLink = أرسل ملفا آخر
@@ -90,7 +83,7 @@ fileTooBig = حجم الملف كبير للغاية لرفعه. يجب أن ي
linkExpiredAlt = انتهت صلاحية الرابط linkExpiredAlt = انتهت صلاحية الرابط
expiredPageHeader = انتهت صلاحية هذا الرابط أو لم يكن موجودا في المقام الأول! expiredPageHeader = انتهت صلاحية هذا الرابط أو لم يكن موجودا في المقام الأول!
notSupportedHeader = متصفحك غير مدعوم. notSupportedHeader = متصفحك غير مدعوم.
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
notSupportedDetail = للأسف فإن متصفحك لا يدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تجربة متصفح آخر، ونحن ننصحك بِفَيَرفُكس! notSupportedDetail = للأسف فإن متصفحك لا يدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تجربة متصفح آخر، ونحن ننصحك بِفَيَرفُكس!
notSupportedLink = لماذا متصفحي غير مدعوم؟ notSupportedLink = لماذا متصفحي غير مدعوم؟
notSupportedOutdatedDetail = للأسف فإن إصدارة فَيَرفُكس هذه لا تدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تحديث متصفحك. notSupportedOutdatedDetail = للأسف فإن إصدارة فَيَرفُكس هذه لا تدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تحديث متصفحك.
@@ -98,7 +91,7 @@ updateFirefox = حدّث فَيَرفُكس
downloadFirefoxButtonSub = تنزيل مجاني downloadFirefoxButtonSub = تنزيل مجاني
uploadedFile = ملف uploadedFile = ملف
copyFileList = انسخ الرابط copyFileList = انسخ الرابط
// expiryFileList is used as a column header # expiryFileList is used as a column header
expiryFileList = ينتهي في expiryFileList = ينتهي في
deleteFileList = احذف deleteFileList = احذف
nevermindButton = لا بأس nevermindButton = لا بأس
@@ -108,12 +101,10 @@ legalNoticeMozilla = يخضع استخدام موقع «فَيَرفُكس سِ
deletePopupText = أأحذف هذا الملف؟ deletePopupText = أأحذف هذا الملف؟
deletePopupYes = نعم deletePopupYes = نعم
deletePopupCancel = ألغِ deletePopupCancel = ألغِ
deleteButtonHover deleteButtonHover = احذف
.title = احذف copyUrlHover = انسخ الرابط
copyUrlHover
.title = انسخ الرابط
footerLinkLegal = القانونية footerLinkLegal = القانونية
// Test Pilot is a proper name and should not be localized. # Test Pilot is a proper name and should not be localized.
footerLinkAbout = حول الاختبار التجريبي footerLinkAbout = حول الاختبار التجريبي
footerLinkPrivacy = الخصوصية footerLinkPrivacy = الخصوصية
footerLinkTerms = الشروط footerLinkTerms = الشروط
@@ -122,6 +113,17 @@ requirePasswordCheckbox = اطلب كلمة سر لتنزيل هذا الملف
addPasswordButton = أضِف كلمة سر addPasswordButton = أضِف كلمة سر
changePasswordButton = غيّر changePasswordButton = غيّر
passwordTryAgain = كلمة السر خاطئة. أعِد المحاولة. passwordTryAgain = كلمة السر خاطئة. أعِد المحاولة.
// This label is followed by the password needed to download a file
passwordResult = كلمة السر: { $password }
reportIPInfringement = أبلغ عن انتهاك للملكية الفكرية reportIPInfringement = أبلغ عن انتهاك للملكية الفكرية
javascriptRequired = يتطلب فَيَرفُكس سِنْد جافاسكربت
whyJavascript = لماذا يتطلب فَيَرفُكس سِنْد جافاسكربت؟
enableJavascript = رجاء فعّل جافاسكربت ثم أعد المحاولة.
# A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours }س { $minutes }د
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }د
# A short status message shown when a password is successfully set
passwordIsSet = ضُبطت كلمة السر
# A short status message shown when the user enters a long password
maxPasswordLength = أقصر طول لكلمة السر: { $length }
# A short status message shown when there was an error setting the password
passwordSetError = يجب ألا تُضبط كلمة السر هذه

View File

@@ -1,4 +1,4 @@
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
title = Firefox Send title = Firefox Send
siteSubtitle = esperimentu web siteSubtitle = esperimentu web
siteFeedback = Feedback siteFeedback = Feedback
@@ -25,28 +25,45 @@ uploadingFileNotification = Avísame cuando se complete la xuba.
uploadSuccessConfirmHeader = Preparáu pa unviar uploadSuccessConfirmHeader = Preparáu pa unviar
uploadSvgAlt = Xubir uploadSvgAlt = Xubir
uploadSuccessTimingHeader = L'enllaz del to ficheru caducará dempués d'una descarga o en 24 hores. uploadSuccessTimingHeader = L'enllaz del to ficheru caducará dempués d'una descarga o en 24 hores.
expireInfo = L'enllaz al ficheru va caducar tres { $downloadCount } o { $timespan }.
downloadCount =
{ $num ->
[one] 1 descarga
*[other] { $num } descargues
}
timespanHours =
{ $num ->
[one] 1 hora
*[other] { $num } hores
}
copyUrlFormLabelWithName = Copia y comparti l'enllaz pa unviar el to ficheru: { $filename } copyUrlFormLabelWithName = Copia y comparti l'enllaz pa unviar el to ficheru: { $filename }
copyUrlFormButton = Copiar al cartafueyu copyUrlFormButton = Copiar al cartafueyu
copiedUrl = ¡Copióse! copiedUrl = ¡Copióse!
deleteFileButton = Desaniciar ficheru deleteFileButton = Desaniciar ficheru
sendAnotherFileLink = Unviar otru ficheru sendAnotherFileLink = Unviar otru ficheru
// Alternative text used on the download link/button (indicates an action). # Alternative text used on the download link/button (indicates an action).
downloadAltText = Baxar downloadAltText = Baxar
downloadsFileList = Descargues
# Used as header in a column indicating the amount of time left before a
# download link expires (e.g. "10h 5m")
timeFileList = Tiempu
# Used as header in a column indicating the number of times a file has been
# downloaded
downloadFileName = Baxar { $filename } downloadFileName = Baxar { $filename }
downloadFileSize = ({ $size }) downloadFileSize = ({ $size })
unlockInputLabel = Introducir contraseña unlockInputLabel = Introducir contraseña
unlockInputPlaceholder = Contraseña unlockInputPlaceholder = Contraseña
unlockButtonLabel = Desbloquiar unlockButtonLabel = Desbloquiar
downloadFileTitle = Baxar ficheru cifráu downloadFileTitle = Baxar ficheru cifráu
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
downloadMessage = El to collaciu unvióte un ficheru usando Firefox Send, un serviciu que te permite compartir ficheros con un enllaz seguru, priváu y cifráu que caduca automáticamente p'asegurar que les to coses nun queden siempres na rede. downloadMessage = El to collaciu unvióte un ficheru usando Firefox Send, un serviciu que te permite compartir ficheros con un enllaz seguru, priváu y cifráu que caduca automáticamente p'asegurar que les to coses nun queden siempres na rede.
// 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 = Baxar downloadButtonLabel = Baxar
downloadNotification = Completóse la to descarga. downloadNotification = Completóse la to descarga.
downloadFinish = Descarga completada downloadFinish = Descarga completada
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)". # This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
fileSizeProgress = ({ $partialSize } de { $totalSize }) fileSizeProgress = ({ $partialSize } de { $totalSize })
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
sendYourFilesLink = Prueba Firefox Send sendYourFilesLink = Prueba Firefox Send
downloadingPageProgress = Baxando { $filename } ({ $size }) downloadingPageProgress = Baxando { $filename } ({ $size })
downloadingPageMessage = Dexa esta llingüeta abierta entrín vamos en cata del to ficheru y lu desciframos, por favor. downloadingPageMessage = Dexa esta llingüeta abierta entrín vamos en cata del to ficheru y lu desciframos, por favor.
@@ -58,7 +75,7 @@ fileTooBig = Esti ficheru ye mui grande como pa xubilu. Debería tener menos de
linkExpiredAlt = Enllaz caducáu linkExpiredAlt = Enllaz caducáu
expiredPageHeader = ¡Esti enllaz caducó o enxamás nun esistó! expiredPageHeader = ¡Esti enllaz caducó o enxamás nun esistó!
notSupportedHeader = El to restolador nun ta sofitáu. notSupportedHeader = El to restolador nun ta sofitáu.
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
notSupportedDetail = Desafortunadamente esti restolador nun sofita la teunoloxía web qu'usa Firefox Send. Precisarás d'usar otru restolador. ¡Aconseyámoste Firefox! notSupportedDetail = Desafortunadamente esti restolador nun sofita la teunoloxía web qu'usa Firefox Send. Precisarás d'usar otru restolador. ¡Aconseyámoste Firefox!
notSupportedLink = ¿Por qué'l mio restolador nun ta sofitáu? notSupportedLink = ¿Por qué'l mio restolador nun ta sofitáu?
notSupportedOutdatedDetail = Desafortunadamente esta versión de Firefox nun sofita la teunoloxía web qu'usa Firefox Send. Precisarás d'anovar Firefox. notSupportedOutdatedDetail = Desafortunadamente esta versión de Firefox nun sofita la teunoloxía web qu'usa Firefox Send. Precisarás d'anovar Firefox.
@@ -66,7 +83,7 @@ updateFirefox = Anovar Firefox
downloadFirefoxButtonSub = Descarga de baldre downloadFirefoxButtonSub = Descarga de baldre
uploadedFile = Ficheru uploadedFile = Ficheru
copyFileList = Copiar URL copyFileList = Copiar URL
// expiryFileList is used as a column header # expiryFileList is used as a column header
expiryFileList = Caduca en expiryFileList = Caduca en
deleteFileList = Desaniciar deleteFileList = Desaniciar
nevermindButton = Nun m'importa nevermindButton = Nun m'importa
@@ -79,13 +96,25 @@ deletePopupCancel = Encaboxar
deleteButtonHover = Desaniciar deleteButtonHover = Desaniciar
copyUrlHover = Copiar URL copyUrlHover = Copiar URL
footerLinkLegal = Llegal footerLinkLegal = Llegal
// Test Pilot is a proper name and should not be localized. # Test Pilot is a proper name and should not be localized.
footerLinkAbout = Tocante a Test Pilot footerLinkAbout = Tocante a Test Pilot
footerLinkPrivacy = Privacidá footerLinkPrivacy = Privacidá
footerLinkTerms = Términos footerLinkTerms = Términos
footerLinkCookies = Cookies 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
changePasswordButton = Camudar
passwordTryAgain = Contraseña incorreuta. Volvi tentalo. passwordTryAgain = Contraseña incorreuta. Volvi tentalo.
// This label is followed by the password needed to download a file javascriptRequired = Firefox Send rique JavaScript
passwordResult = Contraseña: { $password } whyJavascript = ¿Por qué Firefox Send rique JavaScript?
enableJavascript = Activa JavaScript y volvi tentalo, por favor.
# A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours }h { $minutes }m
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }m
# A short status message shown when a password is successfully set
passwordIsSet = Afitóse la contraseña
# A short status message shown when the user enters a long password
maxPasswordLength = Llargor máximu de la contraseña: { $length }
# A short status message shown when there was an error setting the password
passwordSetError = Nun pudo afitase esta contraseña

View File

@@ -1,4 +1,4 @@
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
title = Firefox Send title = Firefox Send
siteSubtitle = web eksperiment siteSubtitle = web eksperiment
siteFeedback = Geri dönüş siteFeedback = Geri dönüş
@@ -26,11 +26,13 @@ uploadSuccessConfirmHeader = Göndərməyə hazır
uploadSvgAlt = Yüklə uploadSvgAlt = Yüklə
uploadSuccessTimingHeader = Faylınızın keçidinin 1 endirmədən və ya 24 saatdan sonra vaxtı çıxacaq. uploadSuccessTimingHeader = Faylınızın keçidinin 1 endirmədən və ya 24 saatdan sonra vaxtı çıxacaq.
expireInfo = Faylınız üçün keçidin vaxtı { $downloadCount } sonra və ya { $timespan } tarixində keçəcək. expireInfo = Faylınız üçün keçidin vaxtı { $downloadCount } sonra və ya { $timespan } tarixində keçəcək.
downloadCount = { $num -> downloadCount =
{ $num ->
[one] 1 endirmə [one] 1 endirmə
*[other] { $num } endirmə *[other] { $num } endirmə
} }
timespanHours = { $num -> timespanHours =
{ $num ->
[one] 1 saat [one] 1 saat
*[other] { $num } saat *[other] { $num } saat
} }
@@ -39,29 +41,29 @@ copyUrlFormButton = Buferə köçür
copiedUrl = Köçürüldü! copiedUrl = Köçürüldü!
deleteFileButton = Faylı sil deleteFileButton = Faylı sil
sendAnotherFileLink = Başqa fayl göndər sendAnotherFileLink = Başqa fayl göndər
// Alternative text used on the download link/button (indicates an action). # Alternative text used on the download link/button (indicates an action).
downloadAltText = Endir downloadAltText = Endir
downloadsFileList = Endirmələr downloadsFileList = Endirmələr
// Used as header in a column indicating the amount of time left before a # Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m") # download link expires (e.g. "10h 5m")
timeFileList = Vaxt timeFileList = Vaxt
// Used as header in a column indicating the number of times a file has been # Used as header in a column indicating the number of times a file has been
// downloaded # downloaded
downloadFileName = { $filename } faylını endir downloadFileName = { $filename } faylını endir
downloadFileSize = ({ $size }) downloadFileSize = ({ $size })
unlockInputLabel = Parol daxil edin unlockInputLabel = Parol daxil edin
unlockInputPlaceholder = Parol unlockInputPlaceholder = Parol
unlockButtonLabel = Aç unlockButtonLabel = Aç
downloadFileTitle = Şifrələnmiş Faylı Endir downloadFileTitle = Şifrələnmiş Faylı Endir
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
downloadMessage = Yoldaşınız Firefox Send ilə sizə fayl göndərir, fayllarınızı təhlükəsiz, məxfi, şifrələnmiş və daima onlayn qalmaması üçün avtomatik silən fayl göndərmə xidməti. downloadMessage = Yoldaşınız Firefox Send ilə sizə fayl göndərir, fayllarınızı təhlükəsiz, məxfi, şifrələnmiş və daima onlayn qalmaması üçün avtomatik silən fayl göndərmə xidməti.
// 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 = Endir downloadButtonLabel = Endir
downloadNotification = Endirməniz tamamlandı. downloadNotification = Endirməniz tamamlandı.
downloadFinish = Endirmə Tamamlandı downloadFinish = Endirmə Tamamlandı
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)". # This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
fileSizeProgress = ({ $partialSize } / { $totalSize }) fileSizeProgress = ({ $partialSize } / { $totalSize })
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
sendYourFilesLink = Firefox Send Yoxla sendYourFilesLink = Firefox Send Yoxla
downloadingPageProgress = { $filename } faylı ({ $size }) endirilir downloadingPageProgress = { $filename } faylı ({ $size }) endirilir
downloadingPageMessage = Lütfən faylı endirib şifrəsini açarkən vərəqi açıq buraxın. downloadingPageMessage = Lütfən faylı endirib şifrəsini açarkən vərəqi açıq buraxın.
@@ -73,7 +75,7 @@ fileTooBig = Fayl yükləmək üçün çox böyükdür. Fayl { $size }-dan az ol
linkExpiredAlt = Keçidin vaxtı çıxıb linkExpiredAlt = Keçidin vaxtı çıxıb
expiredPageHeader = Keçidin vaxtı çıxıb və ya heç vaxt olmayıb! expiredPageHeader = Keçidin vaxtı çıxıb və ya heç vaxt olmayıb!
notSupportedHeader = Səyyahınız dəstəklənmir. notSupportedHeader = Səyyahınız dəstəklənmir.
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
notSupportedDetail = Heyf ki, bu səyyah Firefox Send-ə güc verən web texnologiyalarını dəstəkləmir. Fərqli bir səyyah yoxlamalısınız. Biz Firefox məsləhət görürük! notSupportedDetail = Heyf ki, bu səyyah Firefox Send-ə güc verən web texnologiyalarını dəstəkləmir. Fərqli bir səyyah yoxlamalısınız. Biz Firefox məsləhət görürük!
notSupportedLink = Səyyahım niyə dəstəklənmir? notSupportedLink = Səyyahım niyə dəstəklənmir?
notSupportedOutdatedDetail = Heyf ki, Firefox səyyahının bu versiyası Firefox Send-ə güc verən web texnologiyalarını dəstəkləmir. Səyyahınızı yeniləməlisiniz. notSupportedOutdatedDetail = Heyf ki, Firefox səyyahının bu versiyası Firefox Send-ə güc verən web texnologiyalarını dəstəkləmir. Səyyahınızı yeniləməlisiniz.
@@ -81,7 +83,7 @@ updateFirefox = Firefox-u Yenilə
downloadFirefoxButtonSub = Pulsuz Endir downloadFirefoxButtonSub = Pulsuz Endir
uploadedFile = Fayl uploadedFile = Fayl
copyFileList = Keçidi Köçürt copyFileList = Keçidi Köçürt
// expiryFileList is used as a column header # expiryFileList is used as a column header
expiryFileList = Vaxtı çıxma tarixi expiryFileList = Vaxtı çıxma tarixi
deleteFileList = Sil deleteFileList = Sil
nevermindButton = Vacib deyil nevermindButton = Vacib deyil
@@ -94,7 +96,7 @@ deletePopupCancel = Ləğv et
deleteButtonHover = Sil deleteButtonHover = Sil
copyUrlHover = Keçidi Köçürt copyUrlHover = Keçidi Köçürt
footerLinkLegal = Hüquqi footerLinkLegal = Hüquqi
// Test Pilot is a proper name and should not be localized. # Test Pilot is a proper name and should not be localized.
footerLinkAbout = Test Pilot Haqqında footerLinkAbout = Test Pilot Haqqında
footerLinkPrivacy = Məxfilik footerLinkPrivacy = Məxfilik
footerLinkTerms = Şərtlər footerLinkTerms = Şərtlər
@@ -103,13 +105,17 @@ requirePasswordCheckbox = Bu faylı endirmək üçün parol tələb et
addPasswordButton = Parol əlavə et addPasswordButton = Parol əlavə et
changePasswordButton = Dəyişdir changePasswordButton = Dəyişdir
passwordTryAgain = Səhv parol. Təkrar yoxlayın. passwordTryAgain = Səhv parol. Təkrar yoxlayın.
// This label is followed by the password needed to download a file
passwordResult = Parol: { $password }
reportIPInfringement = Əqli-mülkiyyət pozuntusu bildir reportIPInfringement = Əqli-mülkiyyət pozuntusu bildir
javascriptRequired = Firefox Send üçün JavaScript lazımdır javascriptRequired = Firefox Send üçün JavaScript lazımdır
whyJavascript = Firefox Send niyə JavaScript tələb edir? whyJavascript = Firefox Send niyə JavaScript tələb edir?
enableJavascript = Lütfən JavaScript-i aktiv edib təkrar yoxlayın. enableJavascript = Lütfən JavaScript-i aktiv edib təkrar yoxlayın.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" # A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours } saat { $minutes } dəq expiresHoursMinutes = { $hours } saat { $minutes } dəq
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m" # A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes } dəq expiresMinutes = { $minutes } dəq
# A short status message shown when a password is successfully set
passwordIsSet = Parol quruldu
# A short status message shown when the user enters a long password
maxPasswordLength = Maksimum parol uzunluğu: { $length }
# A short status message shown when there was an error setting the password
passwordSetError = Parol qurula bilmədi

View File

@@ -1,4 +1,4 @@
// Firefox Send is a brand name and should not be localized. # Firefox Send is a brand name and should not be localized.
title = Firefox Send title = Firefox Send
siteSubtitle = ওয়েব গবেষণা siteSubtitle = ওয়েব গবেষণা
siteFeedback = প্রতিক্রিয়া siteFeedback = প্রতিক্রিয়া
@@ -15,11 +15,13 @@ uploadingPageCancel = আপলোড বাতিল করুন
uploadCancelNotification = আপনার অাপলোড বাতিল করা হয়েছে। uploadCancelNotification = আপনার অাপলোড বাতিল করা হয়েছে।
uploadSuccessConfirmHeader = পাঠানোর জন্য প্রস্তুত uploadSuccessConfirmHeader = পাঠানোর জন্য প্রস্তুত
uploadSvgAlt = আপলোড uploadSvgAlt = আপলোড
downloadCount = { $num -> downloadCount =
{ $num ->
[one] 1 ডাউনলোড [one] 1 ডাউনলোড
*[other] { $num } ডাউনলোডগুলো *[other] { $num } ডাউনলোডগুলো
} }
timespanHours = { $num -> timespanHours =
{ $num ->
[one] 1 ঘন্টা [one] 1 ঘন্টা
*[other] { $num } ঘন্টা *[other] { $num } ঘন্টা
} }
@@ -27,20 +29,20 @@ copyUrlFormButton = ক্লিপবোর্ডে কপি করুন
copiedUrl = কপি করা হয়েছে! copiedUrl = কপি করা হয়েছে!
deleteFileButton = ফাইল মুছুন deleteFileButton = ফাইল মুছুন
sendAnotherFileLink = আরেকটি ফাইল পাঠান sendAnotherFileLink = আরেকটি ফাইল পাঠান
// Alternative text used on the download link/button (indicates an action). # Alternative text used on the download link/button (indicates an action).
downloadAltText = ডাউনলোড downloadAltText = ডাউনলোড
downloadsFileList = ডাউনলোডগুলো downloadsFileList = ডাউনলোডগুলো
// Used as header in a column indicating the amount of time left before a # Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m") # download link expires (e.g. "10h 5m")
timeFileList = সময় timeFileList = সময়
// Used as header in a column indicating the number of times a file has been # Used as header in a column indicating the number of times a file has been
// downloaded # downloaded
downloadFileName = ডাউনলোড { $filename } downloadFileName = ডাউনলোড { $filename }
downloadFileSize = ({ $size }) downloadFileSize = ({ $size })
unlockInputLabel = পাসওয়ার্ড লিখুন unlockInputLabel = পাসওয়ার্ড লিখুন
unlockInputPlaceholder = পাসওয়ার্ড unlockInputPlaceholder = পাসওয়ার্ড
unlockButtonLabel = আনলক করুন 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 = আপনার ডাউনলোড সম্পন্ন হয়েছে।
downloadFinish = ডাউনলোড সম্পন্ন downloadFinish = ডাউনলোড সম্পন্ন
@@ -53,7 +55,7 @@ updateFirefox = Firefox হালনাগাদ করুন
downloadFirefoxButtonSub = বিনামূল্যে ডাউনলোড downloadFirefoxButtonSub = বিনামূল্যে ডাউনলোড
uploadedFile = ফাইল uploadedFile = ফাইল
copyFileList = URL অনুলিপি করুন copyFileList = URL অনুলিপি করুন
// expiryFileList is used as a column header # expiryFileList is used as a column header
expiryFileList = মেয়াদোত্তীর্ণ তারিখ expiryFileList = মেয়াদোত্তীর্ণ তারিখ
deleteFileList = মুছে ফেলুন deleteFileList = মুছে ফেলুন
nevermindButton = কিছু মনে করবেন না nevermindButton = কিছু মনে করবেন না
@@ -64,7 +66,7 @@ deletePopupCancel = বাতিল
deleteButtonHover = মুছে ফেলুন deleteButtonHover = মুছে ফেলুন
copyUrlHover = URL অনুলিপি করুন copyUrlHover = URL অনুলিপি করুন
footerLinkLegal = আইনগত footerLinkLegal = আইনগত
// Test Pilot is a proper name and should not be localized. # Test Pilot is a proper name and should not be localized.
footerLinkAbout = Test Pilot পরিচিতি footerLinkAbout = Test Pilot পরিচিতি
footerLinkPrivacy = গোপনীয়তা footerLinkPrivacy = গোপনীয়তা
footerLinkTerms = শর্তাবলী footerLinkTerms = শর্তাবলী

Some files were not shown because too many files have changed in this diff Show More