Compare commits

...

213 Commits

Author SHA1 Message Date
Danny Coates
a4e13f032a v2.3.0 2018-02-01 15:27:47 -08:00
Danny Coates
fef3136b1b updated dependencies 2018-02-01 13:42:27 -08:00
Danny Coates
6b318c248f tiny password input style fix 2018-02-01 13:20:53 -08:00
Danny Coates
fd6a3a5579 return false from form submit handlers 2018-02-01 12:49:18 -08:00
Danny Coates
2292267e39 remove download cancel button while decrypting 2018-02-01 11:42:07 -08:00
Danny Coates
dbfae53222 added autofocus to download password input 2018-02-01 10:41:52 -08:00
Roberto Alvarado
d39ed267f3 Pontoon: Update Spanish (Mexico) (es-MX) localization of Test Pilot: Firefox Send
Localization authors:
- Roberto Alvarado <ralv888@gmail.com>
2018-02-01 18:09:40 +00:00
Danny Coates
9d04ad704a fixes #746 password input style 2018-01-31 16:57:25 -08:00
Danny Coates
11cbb4df23 refactored dl-file html 2018-01-31 16:26:59 -08:00
Danny Coates
232911f725 fixed download preview page render issues and links 2018-01-31 15:47:34 -08:00
Emin Mastizada
41a0c6c73f Pontoon: Update Azerbaijani (az) localization of Test Pilot: Firefox Send
Localization authors:
- Emin Mastizada <emin@mastizada.com>
2018-01-31 21:50:59 +00:00
Danny Coates
10e80edb1d Merge pull request #536 from ehuggett/on-redis-expire
use redis expire event to delete stored data immediately
2018-01-31 13:23:33 -08:00
Danny Coates
af3848586c Merge branch 'master' into on-redis-expire 2018-01-31 13:10:05 -08:00
Danny Coates
48807bf030 Merge pull request #744 from mozilla/gradients
Gradient experiment
2018-01-31 12:15:15 -08:00
Andreas Pettersson
21a14cb225 Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Andreas Pettersson <az@kth.se>
2018-01-31 19:50:51 +00:00
Matjaž Horvat
b725d07744 Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Matjaž Horvat <matjaz.horvat@gmail.com>
2018-01-31 19:50:48 +00:00
Danny Coates
94e707da8a added gradient experiment 2018-01-31 11:46:29 -08:00
Danny Coates
4fb4041f13 sender no longer needs file nonce 2018-01-31 11:12:36 -08:00
gmontagu
545da556d2 Pontoon: Update Spanish (Argentina) (es-AR) localization of Test Pilot: Firefox Send
Localization authors:
- gmontagu <gmontagu@gmail.com>
2018-01-31 18:32:41 +00:00
ravmn
9f3da3454f Pontoon: Update Spanish (Chile) (es-CL) localization of Test Pilot: Firefox Send
Localization authors:
- ravmn <ravmn@ravmn.cl>
2018-01-31 15:31:47 +00:00
Luna Jernberg
15105426b1 Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Luna Jernberg <bittin@cafe8bitar.se>
2018-01-31 14:30:42 +00:00
Mark Heijl
b2d2e27945 Pontoon: Update Dutch (nl) localization of Test Pilot: Firefox Send
Localization authors:
- Mark Heijl <markh@babelzilla.org>
2018-01-31 10:50:32 +00:00
Balázs Meskó
d93a4cf9f3 Pontoon: Update Hungarian (hu) localization of Test Pilot: Firefox Send
Localization authors:
- siparon <siparon@gmail.com>
- Balázs Meskó <meskobalazs@gmail.com>
2018-01-31 09:10:35 +00:00
Fjoerfoks
8c3ec5da3d Pontoon: Update Frisian (fy-NL) localization of Test Pilot: Firefox Send
Localization authors:
- Fjoerfoks <fryskefirefox@gmail.com>
2018-01-31 08:10:40 +00:00
Nihad Suljić
d067b1d55a Pontoon: Update Bosnian (bs) localization of Test Pilot: Firefox Send
Localization authors:
- Nihad Suljić <nihad.suljic92@gmail.com>
2018-01-31 07:11:19 +00:00
Håvar Henriksen
55070ace8b Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2018-01-31 06:50:33 +00:00
Frederick Villaluna
0f35961173 Pontoon: Update Tagalog (tl) localization of Test Pilot: Firefox Send
Localization authors:
- Frederick Villaluna <fv_comscie@yahoo.com>
2018-01-31 04:50:57 +00:00
Pin-guang Chen
b63f68bf74 Pontoon: Update Chinese (Taiwan) (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2018-01-31 04:50:52 +00:00
reza.habibi2008
8c6d6db07e Pontoon: Update Persian (fa) localization of Test Pilot: Firefox Send
Localization authors:
- reza.habibi2008 <reza.habibi2008@gmail.com>
2018-01-31 03:50:19 +00:00
Danny Coates
a74a70fd8c Merge pull request #739 from mozilla/fileInfo
added /api/info/:id route
2018-01-30 17:46:31 -08:00
Danny Coates
97ad674be2 added /api/info/:id route 2018-01-30 17:29:51 -08:00
Melo46
81534d2c13 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Melo46 <melo@carmu.com>
2018-01-31 00:10:40 +00:00
manxmensch
51b7036456 Pontoon: Update Malay (ms) localization of Test Pilot: Firefox Send
Localization authors:
- manxmensch <manxmensch@gmail.com>
2018-01-30 23:31:27 +00:00
YFdyh000
e40fdad251 Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2018-01-30 22:10:42 +00:00
YFdyh000
e4b0e97ac0 Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2018-01-30 21:50:28 +00:00
Juraj Cigáň
003320ca34 Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2018-01-30 21:31:17 +00:00
Kohei Yoshino
780addc50b Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2018-01-30 21:10:54 +00:00
Maykon Chagas
8892a5f43d Pontoon: Update Portuguese (Brazil) (pt-BR) localization of Test Pilot: Firefox Send
Localization authors:
- Maykon Chagas <mchagas@riseup.net>
2018-01-30 20:11:12 +00:00
Théo Chevalier
75e52ebca7 Pontoon: Update French (fr) localization of Test Pilot: Firefox Send
Localization authors:
- Théo Chevalier <theo.chevalier11@gmail.com>
2018-01-30 20:11:09 +00:00
Juan Esteban Ajsivinac Sián
e7b3bbcd0e Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send
Localization authors:
- Juan Esteban Ajsivinac Sián <ajtzibsyan@yahoo.com>
2018-01-30 19:50:38 +00:00
Michael Köhler
b9d60639e2 Pontoon: Update German (de) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Köhler <michael.koehler1@gmx.de>
2018-01-30 19:50:35 +00:00
Michael Wolf
2ce0ce65e1 Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-01-30 19:10:46 +00:00
Michael Wolf
a32bdcb06a Pontoon: Update Sorbian, Lower (dsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-01-30 19:10:43 +00:00
Марко Костић (Marko Kostić)
f9f8dff8b4 Pontoon: Update Serbian (sr) localization of Test Pilot: Firefox Send
Localization authors:
- Марко Костић (Marko Kostić) <marko.m.kostic@gmail.com>
2018-01-30 19:10:40 +00:00
Rok Žerdin
5c5cd1e501 Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
2018-01-30 18:50:30 +00:00
Rodrigo
89bcd1c4a8 Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-01-30 18:50:27 +00:00
Danny Coates
6b7b142961 Merge pull request #737 from mozilla/refactor
big refactor
2018-01-30 09:52:22 -08:00
Danny Coates
565e47aef8 big refactor 2018-01-30 09:37:42 -08:00
Ashikur Rahman
dd448cb3ed 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>
2018-01-29 16:50:39 +00:00
Frederick Villaluna
7a9a19e3b9 Pontoon: Update Tagalog (tl) localization of Test Pilot: Firefox Send
Localization authors:
- Frederick Villaluna <fv_comscie@yahoo.com>
2018-01-29 02:32:15 +00:00
avelper
a6e94441aa Pontoon: Update Spanish (Spain) (es-ES) localization of Test Pilot: Firefox Send
Localization authors:
- avelper <avelper@mozilla-hispano.org>
- Marco Aurélio <fxhelp@yahoo.com>
2018-01-28 16:50:48 +00:00
Jobava
68d66d8ab3 Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Jobava <jobaval10n@gmail.com>
2018-01-27 17:50:31 +00:00
Khaled Hosny
aa5cc7ff9f Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- Khaled Hosny <khaledhosny@eglug.org>
2018-01-27 14:32:15 +00:00
Rodrigo Guerra
eb8ede8376 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo Guerra <rodmguerra@gmail.com>
2018-01-27 01:11:12 +00:00
Selim Şumlu
4dc92bae4e Pontoon: Update Turkish (tr) localization of Test Pilot: Firefox Send
Localization authors:
- Selim Şumlu <selim@sum.lu>
2018-01-25 19:31:30 +00:00
ravmn
9e1a975b23 Pontoon: Update Spanish (Chile) (es-CL) localization of Test Pilot: Firefox Send
Localization authors:
- ravmn <ravmn@ravmn.cl>
2018-01-25 02:11:13 +00:00
Марко Костић (Marko Kostić)
e5a26fa0be Pontoon: Update Serbian (sr) localization of Test Pilot: Firefox Send
Localization authors:
- Марко Костић (Marko Kostić) <marko.m.kostic@gmail.com>
2018-01-24 19:51:34 +00:00
Arash Mousavi
4035a3a1e5 Pontoon: Update Persian (fa) localization of Test Pilot: Firefox Send
Localization authors:
- Arash Mousavi <mousavi.arash@gmail.com>
2018-01-24 19:51:32 +00:00
Bjørn I
0380d286cd Pontoon: Update Norwegian Nynorsk (nn-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Bjørn I. <bjorn.svindseth@online.no>
2018-01-24 15:51:26 +00:00
Victor Bychek
91dc3aa5d2 Pontoon: Update Russian (ru) localization of Test Pilot: Firefox Send
Localization authors:
- Victor Bychek <a@bychek.ru>
2018-01-24 12:31:06 +00:00
Adnan Kičin
4bbffcf35e Pontoon: Update Bosnian (bs) localization of Test Pilot: Firefox Send
Localization authors:
- Adnan Kičin <adnankicin92@gmail.com>
2018-01-24 06:31:10 +00:00
Juan Esteban Ajsivinac Sián
bb73eeb7ea Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send
Localization authors:
- Juan Esteban Ajsivinac Sián <ajtzibsyan@yahoo.com>
2018-01-23 20:11:42 +00:00
Danny Coates
546f8a12e4 Merge pull request #722 from flodolo/fix_721
Add localization note to 'Time' and 'Downloads' string
2018-01-23 11:16:06 -08:00
Juraj Cigáň
3a865d4aaa Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2018-01-23 19:10:51 +00:00
Luiz Carlos de Morais
05feb6b1b0 Pontoon: Update Portuguese (Brazil) (pt-BR) localization of Test Pilot: Firefox Send
Localization authors:
- Luiz Carlos de Morais <lcom_flip@hotmail.com>
2018-01-23 19:10:47 +00:00
Melo46
efa6fe3ef1 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Melo46 <melo@carmu.com>
2018-01-23 18:50:51 +00:00
Michal Stanke
e917927979 Pontoon: Update Czech (cs) localization of Test Pilot: Firefox Send
Localization authors:
- Michal Stanke <mstanke@mozilla.cz>
2018-01-23 18:31:25 +00:00
Melo46
61689ed451 Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send
Localization authors:
- Melo46 <melo@carmu.com>
2018-01-23 17:31:40 +00:00
gmontagu
b74919c376 Pontoon: Update Spanish (Argentina) (es-AR) localization of Test Pilot: Firefox Send
Localization authors:
- gmontagu <gmontagu@gmail.com>
2018-01-23 15:31:39 +00:00
gmontagu
67cc4f9600 Pontoon: Update Spanish (Argentina) (es-AR) localization of Test Pilot: Firefox Send
Localization authors:
- gmontagu <gmontagu@gmail.com>
2018-01-23 15:11:20 +00:00
Rhoslyn Prys
ec749de3fa Pontoon: Update Welsh (cy) localization of Test Pilot: Firefox Send
Localization authors:
- Rhoslyn Prys <rprys@yahoo.com>
2018-01-23 12:11:23 +00:00
Rodrigo
b99cd7ed3f Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-01-23 11:51:12 +00:00
Pin-guang Chen
87b9c955ca Pontoon: Update Chinese (Taiwan) (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2018-01-23 11:31:44 +00:00
YFdyh000
00aee2771e Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2018-01-23 11:31:37 +00:00
Francesco Lodolo [:flod]
291276494d Add localization note to 'Time' and 'Downloads' strings 2018-01-23 12:22:12 +01:00
Michael Köhler
fc3823f0af Pontoon: Update German (de) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Köhler <michael.koehler1@gmx.de>
2018-01-23 09:50:52 +00:00
Balázs Meskó
855ef73a9a Pontoon: Update Hungarian (hu) localization of Test Pilot: Firefox Send
Localization authors:
- Balázs Meskó <meskobalazs@gmail.com>
2018-01-23 09:10:44 +00:00
Håvar Henriksen
517a8d2104 Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2018-01-23 08:50:55 +00:00
Georgianizator
a042795ec1 Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-01-23 08:50:52 +00:00
Rok Žerdin
e427d65bf5 Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
2018-01-23 08:31:25 +00:00
Fjoerfoks
5ce319456f Pontoon: Update Frisian (fy-NL) localization of Test Pilot: Firefox Send
Localization authors:
- Fjoerfoks <fryskefirefox@gmail.com>
2018-01-23 07:50:48 +00:00
Francesco Lodolo
03828bfdaa Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Francesco Lodolo <francesco.lodolo@mozillaitalia.org>
2018-01-23 07:10:53 +00:00
Michael Wolf
7c836d121c Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-01-23 06:31:22 +00:00
Michael Wolf
b2ee3c5f30 Pontoon: Update Sorbian, Lower (dsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-01-23 06:31:20 +00:00
manxmensch
04205783b8 Pontoon: Update Malay (ms) localization of Test Pilot: Firefox Send
Localization authors:
- manxmensch <manxmensch@gmail.com>
2018-01-23 05:31:36 +00:00
Andreas Pettersson
f27e71fbd8 Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Andreas Pettersson <az@kth.se>
2018-01-23 04:51:14 +00:00
Kohei Yoshino
fbca679334 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2018-01-23 04:10:37 +00:00
Ton
5b594ba0b4 Pontoon: Update Dutch (nl) localization of Test Pilot: Firefox Send
Localization authors:
- Ton <tonnes.mb@gmail.com>
2018-01-23 03:51:10 +00:00
Roberto Alvarado
3d4b5b0c1e Pontoon: Update Spanish (Mexico) (es-MX) localization of Test Pilot: Firefox Send
Localization authors:
- Roberto Alvarado <ralv888@gmail.com>
2018-01-23 03:31:20 +00:00
Pin-guang Chen
c7574de7dc Pontoon: Update Chinese (Taiwan) (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2018-01-23 03:11:11 +00:00
Théo Chevalier
c8a3bb5f81 Pontoon: Update French (fr) localization of Test Pilot: Firefox Send
Localization authors:
- Théo Chevalier <theo.chevalier11@gmail.com>
2018-01-23 02:53:29 +00:00
Danny Coates
118edef773 Merge pull request #721 from shikhar-scs/show-dlimits
show download Limits on page; Fixes #661
2018-01-22 18:24:35 -08:00
shikhar-scs
050a1aff83 show download Limits on page 2018-01-23 07:41:48 +05:30
Fjoerfoks
9ad74e9cbf Pontoon: Update Frisian (fy-NL) localization of Test Pilot: Firefox Send
Localization authors:
- Fjoerfoks <fryskefirefox@gmail.com>
2018-01-22 08:51:07 +00:00
Roberto Alvarado
09f8decc59 Pontoon: Update Spanish (Mexico) (es-MX) localization of Test Pilot: Firefox Send
Localization authors:
- Roberto Alvarado <ralv888@gmail.com>
2018-01-22 02:32:21 +00:00
Sav22999
0decdf156b Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Sav22999 <savemore99.sm@gmail.com>
2018-01-21 12:51:17 +00:00
Jordi Cuevas
9316655b8c Pontoon: Update Spanish (Spain) (es-ES) localization of Test Pilot: Firefox Send
Localization authors:
- Jordi Cuevas <jordicuevas@gmail.com>
2018-01-20 19:31:42 +00:00
Bjørn I
1bcc4fd2d7 Pontoon: Update Norwegian Nynorsk (nn-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Bjørn I. <bjorn.svindseth@online.no>
2018-01-20 19:31:38 +00:00
Victor Bychek
239a4da145 Pontoon: Update Russian (ru) localization of Test Pilot: Firefox Send
Localization authors:
- Victor Bychek <a@bychek.ru>
2018-01-20 16:50:39 +00:00
Selim Şumlu
d8e66c9b6a Pontoon: Update Turkish (tr) localization of Test Pilot: Firefox Send
Localization authors:
- Selim Şumlu <selim@sum.lu>
2018-01-20 14:11:03 +00:00
Danny Coates
72279f4995 a very slight refactor of previous commit 2018-01-19 17:14:14 -08:00
Danny Coates
48b21de011 a few tweaks to password input. fixes #703 2018-01-19 16:53:57 -08:00
Danny Coates
f7dc86ab2b reinstate the 'exited' metric. fixes #653 2018-01-19 15:07:05 -08:00
Juraj Cigáň
39bfe6d2cb Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2018-01-19 22:11:06 +00:00
aefgh39622
d6f534c3c0 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- aefgh39622 <aefgh39622@gmail.com>
2018-01-19 20:11:09 +00:00
Rhoslyn Prys
45067d0354 Pontoon: Update Welsh (cy) localization of Test Pilot: Firefox Send
Localization authors:
- Rhoslyn Prys <rprys@yahoo.com>
2018-01-19 15:53:56 +00:00
YFdyh000
da1ff63f72 Pontoon: Update Chinese (China) (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2018-01-19 15:11:34 +00:00
Rok Žerdin
5d715c50de Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
2018-01-19 14:30:37 +00:00
Balázs Meskó
47072ae1fe Pontoon: Update Hungarian (hu) localization of Test Pilot: Firefox Send
Localization authors:
- Balázs Meskó <meskobalazs@gmail.com>
2018-01-19 14:11:38 +00:00
Pin-guang Chen
72f301fa45 Pontoon: Update Chinese (Taiwan) (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2018-01-19 12:11:08 +00:00
Håvar Henriksen
e9b89629a6 Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2018-01-19 09:51:32 +00:00
Frederick Villaluna
3ef5ef166f Pontoon: Update Tagalog (tl) localization of Test Pilot: Firefox Send
Localization authors:
- Frederick Villaluna <fv_comscie@yahoo.com>
2018-01-19 08:50:42 +00:00
Nihad Suljić
37ca7a706a Pontoon: Update Bosnian (bs) localization of Test Pilot: Firefox Send
Localization authors:
- Nihad Suljić <nihad.suljic92@gmail.com>
2018-01-19 08:31:08 +00:00
Ton
734c65fbda Pontoon: Update Dutch (nl) localization of Test Pilot: Firefox Send
Localization authors:
- Ton <tonnes.mb@gmail.com>
2018-01-19 07:10:40 +00:00
Luna Jernberg
145605d628 Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Luna Jernberg <bittin@cafe8bitar.se>
2018-01-19 06:31:17 +00:00
Georgianizator
7ac432fbc5 Pontoon: Update Georgian (ka) localization of Test Pilot: Firefox Send
Localization authors:
- Georgianizator <georgianization@outlook.com>
2018-01-19 06:31:15 +00:00
manxmensch
0c92cec2ea Pontoon: Update Malay (ms) localization of Test Pilot: Firefox Send
Localization authors:
- manxmensch <manxmensch@gmail.com>
2018-01-19 02:11:30 +00:00
Michael Wolf
f6a788b36f Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-01-18 21:51:05 +00:00
Michael Wolf
72dffcf46b Pontoon: Update Sorbian, Lower (dsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2018-01-18 21:51:01 +00:00
Marcelo Poli
3140cdd148 Pontoon: Update Spanish (Argentina) (es-AR) localization of Test Pilot: Firefox Send
Localization authors:
- Marcelo Poli <enzomatrix@gmail.com>
2018-01-18 21:31:07 +00:00
Michael Köhler
d87adbce63 Pontoon: Update German (de) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Köhler <michael.koehler1@gmx.de>
2018-01-18 21:31:02 +00:00
Théo Chevalier
ffdf2bc0cd Pontoon: Update French (fr) localization of Test Pilot: Firefox Send
Localization authors:
- Théo Chevalier <theo.chevalier11@gmail.com>
2018-01-18 21:30:59 +00:00
Cynthia Pereira
be5d7a1c9f Pontoon: Update Portuguese (Brazil) (pt-BR) localization of Test Pilot: Firefox Send
Localization authors:
- Cynthia Pereira <cynthiacpereira@gmail.com>
2018-01-18 20:31:18 +00:00
Juan Esteban Ajsivinac Sián
94288b5cef Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send
Localization authors:
- Juan Esteban Ajsivinac Sián <ajtzibsyan@yahoo.com>
2018-01-18 20:31:14 +00:00
Марко Костић (Marko Kostić)
63abbf5949 Pontoon: Update Serbian (sr) localization of Test Pilot: Firefox Send
Localization authors:
- Марко Костић (Marko Kostić) <marko.m.kostic@gmail.com>
2018-01-18 20:12:09 +00:00
Rodrigo
49214281f7 Pontoon: Update Portuguese (Portugal) (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2018-01-18 20:12:07 +00:00
Danny Coates
9688dde1a4 Merge pull request #694 from himanish-star/feature-change-password
Passwords can now be changed (#687)
2018-01-18 11:46:11 -08:00
Soumya Himanish Mohapatra
fdcc31f049 Passwords can now be reset 2018-01-18 15:45:04 +05:30
Danny Coates
55ed6100e0 fixes #675. progress on ios 2018-01-16 13:52:09 -08:00
Danny Coates
7fb11ba912 added 'browser' to survey url. fixes #657 2018-01-16 13:21:26 -08:00
Danny Coates
f3d77fdcf2 Merge pull request #702 from himanish-star/remove-banner
Restricted the banner from showing on unsupported browsers
2018-01-13 14:50:17 -08:00
Soumya Himanish Mohapatra
6489ab6a56 Restricted the banner from showing on unsupported browsers 2018-01-13 09:32:55 +05:30
Danny Coates
bace117ada fixed error on metadata 404 2018-01-11 23:20:56 -08:00
Danny Coates
76175d61af Merge pull request #701 from shikhar-scs/delete-popup-mobile-view
improved popup for mobile display; Fixes #699
2018-01-11 21:26:30 -08:00
shikhar-scs
87110095a0 improved popup for mobile display 2018-01-12 08:16:55 +05:30
Danny Coates
50ba8bec5a Merge pull request #683 from ehuggett/issue618
API changes to accommodate 3rd party clients
2018-01-11 13:57:30 -08:00
Danny Coates
1741b1c686 use monospace font for password display 2018-01-10 10:02:21 -08:00
Jordi Serratosa
99097baf9d Pontoon: Update Catalan (ca) localization of Test Pilot: Firefox Send
Localization authors:
- Jordi Serratosa <jordis@softcatala.cat>
2018-01-09 20:32:28 +00:00
Jordi Serratosa
bac1cc8243 Pontoon: Update Catalan (ca) localization of Test Pilot: Firefox Send
Localization authors:
- Jordi Serratosa <jordis@softcatala.cat>
2018-01-09 20:12:02 +00:00
Danny Coates
096489d486 tweaked delete popup 2018-01-09 11:20:36 -08:00
Danny Coates
9811a9a3e1 Merge pull request #698 from himanish-star/delete-btn-background-red
Popup for delete button attached
2018-01-09 10:58:50 -08:00
Soumya Himanish Mohapatra
910cde4380 Popup for delete button attached 2018-01-09 20:56:27 +05:30
Danny Coates
4f1ccf83c8 Merge pull request #695 from shikhar-scs/warning-for-high-file-size
Show Warning, Cancel and Redirect on size > 2GB ; fixes #578
2018-01-08 20:11:44 -08:00
shikhar-scs
9501c1ce4b checking for file size > 2GB
included global MAXFILESIZE
2018-01-09 09:28:23 +05:30
Danny Coates
069f0e53e1 Merge pull request #684 from himanish-star/delete-btn-background-red
delete btn popup attached
2018-01-08 12:22:57 -08:00
Danny Coates
aedfba795e updated takedowns.md to add S3 info 2018-01-08 11:20:28 -08:00
Soumya Himanish Mohapatra
9f162c0703 Popup attached for delete Button 2018-01-08 23:53:16 +05:30
Danny Coates
7b96c46e39 Merge pull request #686 from shikhar-scs/hidePassword
Hide password while Typing and after Entering: Fixes #670
2018-01-08 09:49:40 -08:00
Danny Coates
3d48ea71b9 Merge pull request #679 from shikhar-scs/master
changed font to sans sherif: Solves #676
2018-01-08 09:45:21 -08:00
Kerim Kalamujić
24ee984a2e Pontoon: Update Bosnian (bs) localization of Test Pilot: Firefox Send
Localization authors:
- Kerim Kalamujić <kerim@mozilla.ba>
2018-01-08 07:12:23 +00:00
Khaled Hosny
4255cbe540 Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- Khaled Hosny <khaledhosny@eglug.org>
2018-01-07 16:31:44 +00:00
eljuno
fe16f24c41 Pontoon: Update Indonesian (id) localization of Test Pilot: Firefox Send
Localization authors:
- eljuno <eljunotrie_anggoro@yahoo.co.id>
2018-01-06 04:32:11 +00:00
shikhar-scs
a6e1fc5c44 changed font to sans serif
improved font family

font changes
2018-01-06 08:21:55 +05:30
shikhar-scs
8434312728 Toggleable Password
Changed * to ●
2018-01-06 08:11:16 +05:30
Danny Coates
46f641aaec Added docs/takedowns.md for DMCA removal instructions 2018-01-05 13:42:19 -08:00
Danny Coates
c246d8d517 Merge pull request #693 from jspam/01-good-first-issue
README: Fix query link for "good first bugs"
2018-01-03 10:26:52 -08:00
Danny Coates
4ec1aeafaf Merge pull request #685 from himanish-star/hover-checkbox
checkbox now has a hover effect: fixes #635
2018-01-03 10:23:25 -08:00
eljuno
27cfd04ea7 Pontoon: Update Indonesian (id) localization of Test Pilot: Firefox Send
Localization authors:
- eljuno <eljunotrie_anggoro@yahoo.co.id>
2018-01-03 07:51:07 +00:00
Emin Mastizada
797cfcb98d Pontoon: Update Azerbaijani (az) localization of Test Pilot: Firefox Send
Localization authors:
- Emin Mastizada <emin@mastizada.com>
2018-01-01 06:34:36 +00:00
jspam
96a9b52e6d README: Fix query link for "good first bugs" 2017-12-31 12:10:57 +01:00
Juan Esteban Ajsivinac Sián
3b7462070b Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send
Localization authors:
- Juan Esteban Ajsivinac Sián <ajtzibsyan@yahoo.com>
2017-12-31 01:11:37 +00:00
Jim Spentzos
f08dd5960b Pontoon: Update Greek (el) localization of Test Pilot: Firefox Send
Localization authors:
- Jim Spentzos <jamesspentzos@hotmail.com>
2017-12-23 15:51:52 +00:00
Soumya Himanish Mohapatra
9972196f70 checkbox now has a hover effect 2017-12-23 11:49:15 +05:30
ehuggett
ebbf06787c changes requested in review 2017-12-23 05:09:06 +00:00
Danny Coates
1d2b0cb093 Merge pull request #668 from TwizzyDizzy/master
Add possibility to bind to a specific IP address
2017-12-21 13:25:02 -08:00
Thomas Dalichow
6f27c6e4aa Change default bind address to 0.0.0.0 2017-12-21 22:10:42 +01:00
Danny Coates
7b6008c37e Merge pull request #682 from tiagomoraismorgado88/patch-9
[Docs] - README.md - minor spelling fixes
2017-12-21 12:44:49 -08:00
Danny Coates
cf5405fbe4 Merge pull request #672 from derektamsen/dynamically-generate-file-ttl
Use EXPIRE_SECONDS to calculate file ttl for static content
2017-12-21 12:42:57 -08:00
Danny Coates
b4ec7402fc Merge pull request #680 from himanish-star/adjust-line-height-label
adjusted line height of label : fixes #609
2017-12-21 12:34:36 -08:00
ehuggett
ff9a107a29 API changes to accommodate 3rd party clients 2017-12-21 15:54:19 +00:00
tiagomoraismorgado
417ad87bcc [Docs] - README.md - minor spelling fixes
[Docs] - README.md - minor spelling fixes
2017-12-21 15:35:44 +00:00
Soumya Himanish Mohapatra
265f99f327 adjusted line height of label 2017-12-21 17:46:01 +05:30
Danny Coates
0f8c3caf18 fixed untranslated strings breaking the site 2017-12-19 10:06:45 -08:00
Jobava
3a7677b46d Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Jobava <jobaval10n@gmail.com>
2017-12-14 17:31:55 +00:00
Jobava
3fd64d9956 Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send
Localization authors:
- Jobava <jobaval10n@gmail.com>
2017-12-14 17:11:45 +00:00
Derek Tamsen
1d26f4b24f Use EXPIRE_SECONDS to calculate file ttl for static content 2017-12-13 18:22:17 -08:00
Nihad Suljić
1a4cea7b4f Pontoon: Update Bosnian (bs) localization of Test Pilot: Firefox Send
Localization authors:
- Nihad Suljić <nihad.suljic92@gmail.com>
2017-12-13 08:32:30 +00:00
Hyeonseok Shin
bbb1c42641 Pontoon: Update Korean (ko) localization of Test Pilot: Firefox Send
Localization authors:
- Hyeonseok Shin <hyeonseok@gmail.com>
- minwook-shin <minwook0106@gmail.com>
2017-12-13 02:50:51 +00:00
Hyeonseok Shin
7ce2320eda Pontoon: Update Korean (ko) localization of Test Pilot: Firefox Send
Localization authors:
- Hyeonseok Shin <hyeonseok@gmail.com>
2017-12-13 02:31:46 +00:00
Abdalrahman Hwoij
1729b30f89 Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- Abdalrahman Hwoij <howij1995@gmail.com>
- Zaid Ibhais <s-e-7-s@hotmail.com>
- صفا الفليج <safa1996alfulaij@gmail.com>
- Khaled Hosny <khaledhosny@eglug.org>
2017-12-12 23:51:37 +00:00
Khaled Hosny
25d6595d5e Pontoon: Update Arabic (ar) localization of Test Pilot: Firefox Send
Localization authors:
- صفا الفليج <safa1996alfulaij@gmail.com>
- Khaled Hosny <khaledhosny@eglug.org>
2017-12-12 23:31:40 +00:00
Michal Stanke
3b31b9d65b Pontoon: Update Czech (cs) localization of Test Pilot: Firefox Send
Localization authors:
- Filip Hruška <fhr@fhrnet.eu>
- Michal Stanke <mstanke@mozilla.cz>
2017-12-12 20:11:29 +00:00
eljuno
e9e64fed3f Pontoon: Update Indonesian (id) localization of Test Pilot: Firefox Send
Localization authors:
- Rahmat Subekti <rahmatsubekti@live.com>
- eljuno <eljunotrie_anggoro@yahoo.co.id>
- Kiki <kelimutu.rizki@gmail.com>
2017-12-11 21:00:31 +00:00
Mozilla Pontoon
3b88ea9b1c Pontoon: Update Danish (da) localization of Test Pilot: Firefox Send 2017-12-11 20:59:57 +00:00
Emin Mastizada
623f76fa6c Pontoon: Update Azerbaijani (az) localization of Test Pilot: Firefox Send
Localization authors:
- Emin Mastizada <emin@mastizada.com>
2017-12-11 20:59:37 +00:00
ybouhamam
19efcb1a0d Pontoon: Update Kabyle (kab) localization of Test Pilot: Firefox Send
Localization authors:
- ybouhamam <ybouhamam@gmail.com>
2017-12-10 09:11:16 +00:00
Besnik Bleta
31c8916622 Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send
Localization authors:
- Besnik Bleta <besnik@programeshqip.org>
2017-12-09 17:11:48 +00:00
Jim Spentzos
7a0b189a1d Pontoon: Update Greek (el) localization of Test Pilot: Firefox Send
Localization authors:
- Jim Spentzos <jamesspentzos@hotmail.com>
2017-12-09 14:51:13 +00:00
Besnik Bleta
fa0d56d57a Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send
Localization authors:
- Besnik Bleta <besnik@programeshqip.org>
2017-12-09 11:51:15 +00:00
Besnik Bleta
85670bbc6a Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send
Localization authors:
- Besnik Bleta <besnik@programeshqip.org>
2017-12-09 11:31:30 +00:00
Besnik Bleta
fc9a85b6ad Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send
Localization authors:
- Besnik Bleta <besnik@programeshqip.org>
2017-12-09 11:10:46 +00:00
Besnik Bleta
8760b132da Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send
Localization authors:
- Besnik Bleta <besnik@programeshqip.org>
2017-12-09 10:51:01 +00:00
Thomas Dalichow
b80ee8d778 Make the linter happy :) 2017-12-09 02:34:33 +01:00
Thomas Dalichow
c8e168aa3e Use new config item 'listen_address' in prod 2017-12-09 02:22:36 +01:00
Thomas Dalichow
106aef579f Add new config item: listen_address
The IP address to bind the HTTP server to
2017-12-09 02:21:55 +01:00
Breana Gonzales
d7084829c3 Pontoon: Update Spanish (Mexico) (es-MX) localization of Test Pilot: Firefox Send
Localization authors:
- Breana Gonzales <breanajones@hotmail.com>
2017-12-09 01:10:56 +00:00
Uccen Marzuq
243d72eed5 Pontoon: Update Kabyle (kab) localization of Test Pilot: Firefox Send
Localization authors:
- Uccen Marzuq <merzouk.ouchene@laposte.net>
2017-12-08 21:51:05 +00:00
Danny Coates
aa60e97d5d Merge pull request #667 from claudijd/remove_prod_as_default_env
Make develop the default NODE_ENV
2017-12-08 13:11:11 -08:00
Jonathan Claudius
4ae12db99c Make develop the default NODE_ENV 2017-12-08 16:04:26 -05:00
Andreas Pettersson
98ad058e2e Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Andreas Pettersson <az@kth.se>
2017-12-08 20:11:14 +00:00
Andreas Pettersson
58cc381abf Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Andreas Pettersson <az@kth.se>
2017-12-08 18:10:47 +00:00
Danny Coates
d039c38f00 v2.2.1 2017-12-08 10:00:13 -08:00
Danny Coates
81f3347981 retry setPassword on first nonce failure. fixes #664 2017-12-08 09:45:00 -08:00
Danny Coates
b96d2949f7 Merge pull request #665 from mozilla/dragleave
stop drag target from flickering when dragging over children
2017-12-08 09:18:54 -08:00
Erica Wright
a2e745a349 stop drag target from flickering when dragging over children 2017-12-08 10:48:44 -05:00
RickieES
0fe02b18ce Pontoon: Update Spanish (Spain) (es-ES) localization of Test Pilot: Firefox Send
Localization authors:
- RickieES <rpmdisguise-nave@yahoo.es>
2017-12-08 12:30:51 +00:00
Juraj Cigáň
655eb8c253 Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2017-12-07 23:10:49 +00:00
Fjoerfoks
d9356f8171 Pontoon: Update Frisian (fy-NL) localization of Test Pilot: Firefox Send
Localization authors:
- Fjoerfoks <fryskefirefox@gmail.com>
2017-12-07 21:11:05 +00:00
Frederick Villaluna
be7d23163c Pontoon: Update Tagalog (tl) localization of Test Pilot: Firefox Send
Localization authors:
- Frederick Villaluna <fv_comscie@yahoo.com>
2017-12-07 09:30:56 +00:00
Artem Polivanchuk
7ac2e57484 Pontoon: Update Ukrainian (uk) localization of Test Pilot: Firefox Send
Localization authors:
- Artem Polivanchuk <artem@mozilla.org.ua>
2017-12-07 08:31:03 +00:00
Artem Polivanchuk
727ea1283a Pontoon: Update Ukrainian (uk) localization of Test Pilot: Firefox Send
Localization authors:
- Artem Polivanchuk <artem@mozilla.org.ua>
2017-12-07 08:11:00 +00:00
Lobodzets
3314057059 Pontoon: Update Ukrainian (uk) localization of Test Pilot: Firefox Send
Localization authors:
- Lobodzets <Lobodzets@meta.ua>
2017-12-07 06:51:08 +00:00
Edmund Huggett
58840e2c00 use redis expire event to delete stored data immediately 2017-11-15 12:31:22 +00:00
107 changed files with 3119 additions and 1246 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules
dist
.idea

View File

@@ -69,13 +69,13 @@ The server is configured with environment variables. See [server/config.js](serv
## Localization
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.
---
## Contributing
Pull requests are always welcome! Feel free to check out the list of ["good first bugs"](https://github.com/mozilla/send/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+bug%22).
Pull requests are always welcome! Feel free to check out the list of ["good first issues"](https://github.com/mozilla/send/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
---

213
app/api.js Normal file
View File

@@ -0,0 +1,213 @@
import { arrayToB64, b64ToArray } from './utils';
function post(obj) {
return {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify(obj)
};
}
function parseNonce(header) {
header = header || '';
return header.split(' ')[1];
}
async function fetchWithAuth(url, params, keychain) {
const result = {};
params = params || {};
const h = await keychain.authHeader();
params.headers = new Headers({ Authorization: h });
const response = await fetch(url, params);
result.response = response;
result.ok = response.ok;
const nonce = parseNonce(response.headers.get('WWW-Authenticate'));
result.shouldRetry = response.status === 401 && nonce !== keychain.nonce;
keychain.nonce = nonce;
return result;
}
async function fetchWithAuthAndRetry(url, params, keychain) {
const result = await fetchWithAuth(url, params, keychain);
if (result.shouldRetry) {
return fetchWithAuth(url, params, keychain);
}
return result;
}
export async function del(id, owner_token) {
const response = await fetch(`/api/delete/${id}`, post({ owner_token }));
return response.ok;
}
export async function setParams(id, owner_token, params) {
const response = await fetch(
`/api/params/${id}`,
post({
owner_token,
dlimit: params.dlimit
})
);
return response.ok;
}
export async function fileInfo(id, owner_token) {
const response = await fetch(`/api/info/${id}`, post({ owner_token }));
if (response.ok) {
const obj = await response.json();
return obj;
}
throw new Error(response.status);
}
export async function metadata(id, keychain) {
const result = await fetchWithAuthAndRetry(
`/api/metadata/${id}`,
{ method: 'GET' },
keychain
);
if (result.ok) {
const data = await result.response.json();
const meta = await keychain.decryptMetadata(b64ToArray(data.metadata));
return {
size: data.size,
ttl: data.ttl,
iv: meta.iv,
name: meta.name,
type: meta.type
};
}
throw new Error(result.response.status);
}
export async function setPassword(id, owner_token, keychain) {
const auth = await keychain.authKeyB64();
const response = await fetch(
`/api/password/${id}`,
post({ owner_token, auth })
);
return response.ok;
}
export function uploadFile(encrypted, metadata, verifierB64, keychain) {
const xhr = new XMLHttpRequest();
const upload = {
onprogress: function() {},
cancel: function() {
xhr.abort();
},
result: new Promise(function(resolve, reject) {
xhr.addEventListener('loadend', function() {
const authHeader = xhr.getResponseHeader('WWW-Authenticate');
if (authHeader) {
keychain.nonce = parseNonce(authHeader);
}
if (xhr.status === 200) {
const responseObj = JSON.parse(xhr.responseText);
return resolve({
url: responseObj.url,
id: responseObj.id,
ownerToken: responseObj.owner
});
}
reject(new Error(xhr.status));
});
})
};
const dataView = new DataView(encrypted);
const blob = new Blob([dataView], { type: 'application/octet-stream' });
const fd = new FormData();
fd.append('data', blob);
xhr.upload.addEventListener('progress', function(event) {
if (event.lengthComputable) {
upload.onprogress([event.loaded, event.total]);
}
});
xhr.open('post', '/api/upload', true);
xhr.setRequestHeader('X-File-Metadata', arrayToB64(new Uint8Array(metadata)));
xhr.setRequestHeader('Authorization', `send-v1 ${verifierB64}`);
xhr.send(fd);
return upload;
}
function download(id, keychain) {
const xhr = new XMLHttpRequest();
const download = {
onprogress: function() {},
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 download;
}
async function tryDownload(id, keychain, onprogress, tries = 1) {
const dl = download(id, keychain);
dl.onprogress = onprogress;
try {
const result = await dl.result;
return result;
} catch (e) {
if (e.message === '401' && --tries > 0) {
return tryDownload(id, keychain, onprogress, tries);
}
throw e;
}
}
export function downloadFile(id, keychain) {
let cancelled = false;
function updateProgress(p) {
if (cancelled) {
// This is a bit of a hack
// We piggyback off of the progress event as a chance to cancel.
// Otherwise wiring the xhr abort up while allowing retries
// gets pretty nasty.
// 'this' here is the object returned by download(id, keychain)
return this.cancel();
}
dl.onprogress(p);
}
const dl = {
onprogress: function() {},
cancel: function() {
cancelled = true;
},
result: tryDownload(id, keychain, updateProgress, 2)
};
return dl;
}

View File

@@ -6,7 +6,7 @@ export default function(state, emitter) {
}
});
document.body.addEventListener('drop', event => {
if (state.route === '/' && !state.transfer) {
if (state.route === '/' && !state.uploading) {
event.preventDefault();
document.querySelector('.upload-window').classList.remove('ondrag');
const target = event.dataTransfer;

View File

@@ -1,10 +1,19 @@
import hash from 'string-hash';
const experiments = {
XnN0idVWSxO6A0kiNkxzGw: {
id: 'XnN0idVWSxO6A0kiNkxzGw',
S9wqVl2SQ4ab2yZtqDI3Dw: {
id: 'S9wqVl2SQ4ab2yZtqDI3Dw',
run: function(variant, state, emitter) {
state.promo = variant === 1 ? 'blue' : 'grey';
switch (variant) {
case 1:
state.promo = 'blue';
break;
case 2:
state.promo = 'pink';
break;
default:
state.promo = 'grey';
}
emitter.emit('render');
},
eligible: function() {
@@ -14,7 +23,11 @@ const experiments = {
);
},
variant: function(state) {
return this.luckyNumber(state) > 0.5 ? 1 : 0;
const n = this.luckyNumber(state);
if (n < 0.33) {
return 0;
}
return n < 0.66 ? 1 : 2;
},
luckyNumber: function(state) {
return luckyNumber(

View File

@@ -1,57 +1,15 @@
/* global EXPIRE_SECONDS */
import FileSender from './fileSender';
import FileReceiver from './fileReceiver';
import { copyToClipboard, delay, fadeOut, percent } from './utils';
import {
copyToClipboard,
delay,
fadeOut,
openLinksInNewTab,
percent,
saveFile
} from './utils';
import * as metrics from './metrics';
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) {
links = links || Array.from(document.querySelectorAll('a:not([target])'));
if (should) {
links.forEach(l => {
l.setAttribute('target', '_blank');
l.setAttribute('rel', 'noopener noreferrer');
});
} else {
links.forEach(l => {
l.removeAttribute('target');
l.removeAttribute('rel');
});
}
return links;
}
function exists(id) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
resolve(xhr.status === 200);
}
};
xhr.onerror = () => resolve(false);
xhr.ontimeout = () => resolve(false);
xhr.open('get', '/api/exists/' + id);
xhr.timeout = 2000;
xhr.send();
});
}
export default function(state, emitter) {
let lastRender = 0;
let updateTitle = false;
@@ -64,10 +22,14 @@ export default function(state, emitter) {
const files = state.storage.files;
let rerender = false;
for (const file of files) {
const ok = await exists(file.id);
if (!ok) {
const oldLimit = file.dlimit;
const oldTotal = file.dtotal;
await file.updateDownloadCount();
if (file.dtotal === file.dlimit) {
state.storage.remove(file.id);
rerender = true;
} else if (oldLimit !== file.dlimit || oldTotal !== file.dtotal) {
rerender = true;
}
}
if (rerender) {
@@ -98,9 +60,8 @@ export default function(state, emitter) {
});
emitter.on('changeLimit', async ({ file, value }) => {
await FileSender.changeLimit(file.id, file.ownerToken, value);
file.dlimit = value;
state.storage.writeFiles();
await file.changeLimit(value);
state.storage.writeFile(file);
metrics.changedDownloadLimit(file);
});
@@ -115,11 +76,10 @@ export default function(state, emitter) {
location
});
state.storage.remove(file.id);
await FileSender.delete(file.id, file.ownerToken);
await file.del();
} catch (e) {
state.raven.captureException(e);
}
state.fileInfo = null;
});
emitter.on('cancel', () => {
@@ -132,36 +92,27 @@ export default function(state, emitter) {
sender.on('progress', updateProgress);
sender.on('encrypting', render);
state.transfer = sender;
state.uploading = true;
render();
const links = openLinksInNewTab();
await delay(200);
try {
const start = Date.now();
metrics.startedUpload({ size, type });
const info = await sender.upload();
const time = Date.now() - start;
const speed = size / (time / 1000);
metrics.completedUpload({ size, time, speed, type });
const ownedFile = await sender.upload();
state.storage.totalUploads += 1;
metrics.completedUpload(ownedFile);
state.storage.addFile(ownedFile);
document.getElementById('cancel-upload').hidden = 'hidden';
await delay(1000);
await fadeOut('upload-progress');
info.name = file.name;
info.size = size;
info.type = type;
info.time = time;
info.speed = speed;
info.createdAt = Date.now();
info.url = `${info.url}#${info.secretKey}`;
info.expiresAt = Date.now() + EXPIRE_SECONDS * 1000;
state.fileInfo = info;
state.storage.addFile(state.fileInfo);
openLinksInNewTab(links, false);
state.transfer = null;
state.storage.totalUploads += 1;
emitter.emit('pushState', `/share/${info.id}`);
emitter.emit('pushState', `/share/${ownedFile.id}`);
} catch (err) {
console.error(err);
state.transfer = null;
if (err.message === '0') {
//cancelled. do nothing
metrics.cancelledUpload({ size, type });
@@ -170,34 +121,35 @@ export default function(state, emitter) {
state.raven.captureException(err);
metrics.stoppedUpload({ size, type, err });
emitter.emit('pushState', '/error');
} finally {
state.uploading = false;
state.transfer = null;
}
});
emitter.on('password', async ({ password, file }) => {
try {
await FileSender.setPassword(password, file);
await file.setPassword(password);
state.storage.writeFile(file);
metrics.addedPassword({ size: file.size });
file.password = password;
state.storage.writeFiles();
} catch (e) {
console.error(e);
} catch (err) {
console.error(err);
}
render();
});
emitter.on('preview', async () => {
emitter.on('getMetadata', async () => {
const file = state.fileInfo;
const url = `/api/download/${file.id}`;
const receiver = new FileReceiver(url, file);
receiver.on('progress', updateProgress);
receiver.on('decrypting', render);
state.transfer = receiver;
const receiver = new FileReceiver(file);
try {
await receiver.getMetadata(file.nonce);
await receiver.getMetadata();
receiver.on('progress', updateProgress);
receiver.on('decrypting', render);
state.transfer = receiver;
} catch (e) {
if (e.message === '401') {
file.password = null;
if (!file.pwd) {
if (!file.requiresPassword) {
return emitter.emit('pushState', '/404');
}
}
@@ -213,19 +165,21 @@ export default function(state, emitter) {
try {
const start = Date.now();
metrics.startedDownload({ size: file.size, ttl: file.ttl });
const f = await state.transfer.download(file.nonce);
const f = await state.transfer.download();
const time = Date.now() - start;
const speed = size / (time / 1000);
await delay(1000);
await fadeOut('download-progress');
saveFile(f);
state.storage.totalDownloads += 1;
state.transfer = null;
metrics.completedDownload({ size, time, speed });
emitter.emit('pushState', '/completed');
} catch (err) {
if (err.message === '0') {
// download cancelled
return render();
}
console.error(err);
// TODO cancelled download
const location = err.message === 'notfound' ? '/404' : '/error';
if (location === '/error') {
state.raven.captureException(err);
@@ -243,6 +197,14 @@ export default function(state, emitter) {
metrics.copiedLink({ location });
});
setInterval(() => {
// poll for updates of the download counts
// TODO something for the share page: || state.route === '/share/:id'
if (state.route === '/') {
checkFiles();
}
}, 2 * 60 * 1000);
setInterval(() => {
// poll for rerendering the file list countdown timers
if (

View File

@@ -1,104 +1,21 @@
import Nanobus from 'nanobus';
import { arrayToB64, b64ToArray, bytes } from './utils';
import Keychain from './keychain';
import { bytes } from './utils';
import { metadata, downloadFile } from './api';
export default class FileReceiver extends Nanobus {
constructor(url, file) {
constructor(fileInfo) {
super('FileReceiver');
this.secretKeyPromise = window.crypto.subtle.importKey(
'raw',
b64ToArray(file.key),
'HKDF',
false,
['deriveKey']
);
this.encryptKeyPromise = this.secretKeyPromise.then(sk => {
const encoder = new TextEncoder();
return window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('encryption'),
hash: 'SHA-256'
},
sk,
{
name: 'AES-GCM',
length: 128
},
false,
['decrypt']
);
});
if (file.pwd) {
const encoder = new TextEncoder();
this.authKeyPromise = window.crypto.subtle
.importKey(
'raw',
encoder.encode(file.password),
{ name: 'PBKDF2' },
false,
['deriveKey']
)
.then(pwdKey =>
window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: encoder.encode(file.url),
iterations: 100,
hash: 'SHA-256'
},
pwdKey,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign']
)
);
} else {
this.authKeyPromise = this.secretKeyPromise.then(sk => {
const encoder = new TextEncoder();
return window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('authentication'),
hash: 'SHA-256'
},
sk,
{
name: 'HMAC',
hash: { name: 'SHA-256' }
},
false,
['sign']
);
});
this.keychain = new Keychain(fileInfo.secretKey, fileInfo.nonce);
if (fileInfo.requiresPassword) {
this.keychain.setPassword(fileInfo.password, fileInfo.url);
}
this.metaKeyPromise = this.secretKeyPromise.then(sk => {
const encoder = new TextEncoder();
return window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('metadata'),
hash: 'SHA-256'
},
sk,
{
name: 'AES-GCM',
length: 128
},
false,
['decrypt']
);
});
this.file = file;
this.url = url;
this.fileInfo = fileInfo;
this.fileDownload = null;
this.msg = 'fileSizeProgress';
this.state = 'initialized';
this.progress = [0, 1];
this.cancelled = false;
}
get progressRatio() {
@@ -113,155 +30,52 @@ export default class FileReceiver extends Nanobus {
}
cancel() {
// TODO
}
async fetchMetadata(nonce) {
const authHeader = await this.getAuthHeader(nonce);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
const nonce = xhr.getResponseHeader('WWW-Authenticate').split(' ')[1];
this.file.nonce = nonce;
if (xhr.status === 200) {
return resolve(xhr.response);
}
const err = new Error(xhr.status);
err.nonce = nonce;
reject(err);
}
};
xhr.onerror = () => reject(new Error(0));
xhr.ontimeout = () => reject(new Error(0));
xhr.open('get', `/api/metadata/${this.file.id}`);
xhr.setRequestHeader('Authorization', authHeader);
xhr.responseType = 'json';
xhr.timeout = 2000;
xhr.send();
});
}
async getMetadata(nonce) {
let data = null;
try {
try {
data = await this.fetchMetadata(nonce);
} catch (e) {
if (e.message === '401' && nonce !== e.nonce) {
// allow one retry for changed nonce
data = await this.fetchMetadata(e.nonce);
} else {
throw e;
}
}
const metaKey = await this.metaKeyPromise;
const json = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: new Uint8Array(12),
tagLength: 128
},
metaKey,
b64ToArray(data.metadata)
);
const decoder = new TextDecoder();
const meta = JSON.parse(decoder.decode(json));
this.file.name = meta.name;
this.file.type = meta.type;
this.file.iv = meta.iv;
this.file.size = data.size;
this.file.ttl = data.ttl;
this.state = 'ready';
} catch (e) {
this.state = 'invalid';
throw e;
this.cancelled = true;
if (this.fileDownload) {
this.fileDownload.cancel();
}
}
async downloadFile(nonce) {
const authHeader = await this.getAuthHeader(nonce);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onprogress = event => {
if (event.lengthComputable && event.target.status !== 404) {
this.progress = [event.loaded, event.total];
this.emit('progress', this.progress);
}
};
xhr.onload = event => {
if (xhr.status === 404) {
reject(new Error('notfound'));
return;
}
if (xhr.status !== 200) {
const err = new Error(xhr.status);
err.nonce = xhr.getResponseHeader('WWW-Authenticate').split(' ')[1];
return reject(err);
}
const blob = new Blob([xhr.response]);
const fileReader = new FileReader();
fileReader.onload = function() {
resolve(this.result);
};
fileReader.readAsArrayBuffer(blob);
};
xhr.open('get', this.url);
xhr.setRequestHeader('Authorization', authHeader);
xhr.responseType = 'blob';
xhr.send();
});
async getMetadata() {
const meta = await metadata(this.fileInfo.id, this.keychain);
if (meta) {
this.keychain.setIV(meta.iv);
this.fileInfo.name = meta.name;
this.fileInfo.type = meta.type;
this.fileInfo.iv = meta.iv;
this.fileInfo.size = meta.size;
this.state = 'ready';
return;
}
this.state = 'invalid';
return;
}
async getAuthHeader(nonce) {
const authKey = await this.authKeyPromise;
const sig = await window.crypto.subtle.sign(
{
name: 'HMAC'
},
authKey,
b64ToArray(nonce)
);
return `send-v1 ${arrayToB64(new Uint8Array(sig))}`;
}
async download(nonce) {
async download() {
this.state = 'downloading';
this.emit('progress', this.progress);
try {
const encryptKey = await this.encryptKeyPromise;
let ciphertext = null;
try {
ciphertext = await this.downloadFile(nonce);
} catch (e) {
if (e.message === '401' && nonce !== e.nonce) {
ciphertext = await this.downloadFile(e.nonce);
} else {
throw e;
}
}
const download = await downloadFile(this.fileInfo.id, this.keychain);
download.onprogress = p => {
this.progress = p;
this.emit('progress', p);
};
this.fileDownload = download;
const ciphertext = await download.result;
this.fileDownload = null;
this.msg = 'decryptingFile';
this.state = 'decrypting';
this.emit('decrypting');
const plaintext = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: b64ToArray(this.file.iv),
tagLength: 128
},
encryptKey,
ciphertext
);
const plaintext = await this.keychain.decryptFile(ciphertext);
if (this.cancelled) {
throw new Error(0);
}
this.msg = 'downloadFinish';
this.state = 'complete';
return {
plaintext,
name: decodeURIComponent(this.file.name),
type: this.file.type
name: decodeURIComponent(this.fileInfo.name),
type: this.fileInfo.type
};
} catch (e) {
this.state = 'invalid';

View File

@@ -1,72 +1,19 @@
/* global EXPIRE_SECONDS */
import Nanobus from 'nanobus';
import { arrayToB64, b64ToArray, bytes } from './utils';
async function getAuthHeader(authKey, nonce) {
const sig = await window.crypto.subtle.sign(
{
name: 'HMAC'
},
authKey,
b64ToArray(nonce)
);
return `send-v1 ${arrayToB64(new Uint8Array(sig))}`;
}
import OwnedFile from './ownedFile';
import Keychain from './keychain';
import { arrayToB64, bytes } from './utils';
import { uploadFile } from './api';
export default class FileSender extends Nanobus {
constructor(file) {
super('FileSender');
this.file = file;
this.uploadRequest = null;
this.msg = 'importingFile';
this.progress = [0, 1];
this.cancelled = false;
this.iv = window.crypto.getRandomValues(new Uint8Array(12));
this.uploadXHR = new XMLHttpRequest();
this.rawSecret = window.crypto.getRandomValues(new Uint8Array(16));
this.secretKey = window.crypto.subtle.importKey(
'raw',
this.rawSecret,
'HKDF',
false,
['deriveKey']
);
}
static delete(id, token) {
return new Promise((resolve, reject) => {
if (!id || !token) {
return reject();
}
const xhr = new XMLHttpRequest();
xhr.open('POST', `/api/delete/${id}`);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
resolve();
}
};
xhr.send(JSON.stringify({ owner_token: token }));
});
}
static changeLimit(id, owner_token, dlimit) {
return new Promise((resolve, reject) => {
if (!id || !owner_token) {
return reject();
}
const xhr = new XMLHttpRequest();
xhr.open('POST', `/api/params/${id}`);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
resolve();
}
};
xhr.send(JSON.stringify({ owner_token, dlimit }));
});
this.keychain = new Keychain();
}
get progressRatio() {
@@ -82,8 +29,8 @@ export default class FileSender extends Nanobus {
cancel() {
this.cancelled = true;
if (this.msg === 'fileSizeProgress') {
this.uploadXHR.abort();
if (this.uploadRequest) {
this.uploadRequest.cancel();
}
}
@@ -91,6 +38,7 @@ export default class FileSender extends Nanobus {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(this.file);
// TODO: progress?
reader.onload = function(event) {
const plaintext = new Uint8Array(this.result);
resolve(plaintext);
@@ -101,212 +49,57 @@ export default class FileSender extends Nanobus {
});
}
uploadFile(encrypted, metadata, rawAuth) {
return new Promise((resolve, reject) => {
const dataView = new DataView(encrypted);
const blob = new Blob([dataView], { type: 'application/octet-stream' });
const fd = new FormData();
fd.append('data', blob);
const xhr = this.uploadXHR;
xhr.upload.addEventListener('progress', e => {
if (e.lengthComputable) {
this.progress = [e.loaded, e.total];
this.emit('progress', this.progress);
}
});
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
const nonce = xhr
.getResponseHeader('WWW-Authenticate')
.split(' ')[1];
this.progress = [1, 1];
this.msg = 'notifyUploadDone';
const responseObj = JSON.parse(xhr.responseText);
return resolve({
url: responseObj.url,
id: responseObj.id,
secretKey: arrayToB64(this.rawSecret),
ownerToken: responseObj.owner,
nonce
});
}
this.msg = 'errorPageHeader';
reject(new Error(xhr.status));
}
};
xhr.open('post', '/api/upload', true);
xhr.setRequestHeader(
'X-File-Metadata',
arrayToB64(new Uint8Array(metadata))
);
xhr.setRequestHeader('Authorization', `send-v1 ${arrayToB64(rawAuth)}`);
xhr.send(fd);
this.msg = 'fileSizeProgress';
});
}
async upload() {
const encoder = new TextEncoder();
const secretKey = await this.secretKey;
const encryptKey = await window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('encryption'),
hash: 'SHA-256'
},
secretKey,
{
name: 'AES-GCM',
length: 128
},
false,
['encrypt']
);
const authKey = await window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('authentication'),
hash: 'SHA-256'
},
secretKey,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign']
);
const metaKey = await window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('metadata'),
hash: 'SHA-256'
},
secretKey,
{
name: 'AES-GCM',
length: 128
},
false,
['encrypt']
);
const start = Date.now();
const plaintext = await this.readFile();
if (this.cancelled) {
throw new Error(0);
}
this.msg = 'encryptingFile';
this.emit('encrypting');
const encrypted = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: this.iv,
tagLength: 128
},
encryptKey,
plaintext
);
const metadata = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: new Uint8Array(12),
tagLength: 128
},
metaKey,
encoder.encode(
JSON.stringify({
iv: arrayToB64(this.iv),
name: this.file.name,
type: this.file.type || 'application/octet-stream'
})
)
);
const rawAuth = await window.crypto.subtle.exportKey('raw', authKey);
const encrypted = await this.keychain.encryptFile(plaintext);
const metadata = await this.keychain.encryptMetadata(this.file);
const authKeyB64 = await this.keychain.authKeyB64();
if (this.cancelled) {
throw new Error(0);
}
return this.uploadFile(encrypted, metadata, new Uint8Array(rawAuth));
}
static async setPassword(password, file) {
const encoder = new TextEncoder();
const secretKey = await window.crypto.subtle.importKey(
'raw',
b64ToArray(file.secretKey),
'HKDF',
false,
['deriveKey']
this.uploadRequest = uploadFile(
encrypted,
metadata,
authKeyB64,
this.keychain
);
const authKey = await window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('authentication'),
hash: 'SHA-256'
},
secretKey,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign']
);
const authHeader = await getAuthHeader(authKey, file.nonce);
const pwdKey = await window.crypto.subtle.importKey(
'raw',
encoder.encode(password),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
const newAuthKey = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: encoder.encode(file.url),
iterations: 100,
hash: 'SHA-256'
},
pwdKey,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign']
);
const rawAuth = await window.crypto.subtle.exportKey('raw', newAuthKey);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
return resolve(xhr.response);
}
if (xhr.status === 401) {
const nonce = xhr
.getResponseHeader('WWW-Authenticate')
.split(' ')[1];
file.nonce = nonce;
}
reject(new Error(xhr.status));
}
};
xhr.onerror = () => reject(new Error(0));
xhr.ontimeout = () => reject(new Error(0));
xhr.open('post', `/api/password/${file.id}`);
xhr.setRequestHeader('Authorization', authHeader);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'json';
xhr.timeout = 2000;
xhr.send(JSON.stringify({ auth: arrayToB64(new Uint8Array(rawAuth)) }));
});
this.msg = 'fileSizeProgress';
this.uploadRequest.onprogress = p => {
this.progress = p;
this.emit('progress', p);
};
try {
const result = await this.uploadRequest.result;
const time = Date.now() - start;
this.msg = 'notifyUploadDone';
this.uploadRequest = null;
this.progress = [1, 1];
const secretKey = arrayToB64(this.keychain.rawSecret);
const ownedFile = new OwnedFile({
id: result.id,
url: `${result.url}#${secretKey}`,
name: this.file.name,
size: this.file.size,
type: this.file.type, //TODO 'click' ?
time: time,
speed: this.file.size / (time / 1000),
createdAt: Date.now(),
expiresAt: Date.now() + EXPIRE_SECONDS * 1000,
secretKey: secretKey,
nonce: this.keychain.nonce,
ownerToken: result.ownerToken
});
return ownedFile;
} catch (e) {
this.msg = 'errorPageHeader';
this.uploadRequest = null;
throw e;
}
}
}

209
app/keychain.js Normal file
View File

@@ -0,0 +1,209 @@
import { arrayToB64, b64ToArray } from './utils';
const encoder = new TextEncoder();
const decoder = new TextDecoder();
export default class Keychain {
constructor(secretKeyB64, nonce, ivB64) {
this._nonce = nonce || 'yRCdyQ1EMSA3mo4rqSkuNQ==';
if (ivB64) {
this.iv = b64ToArray(ivB64);
} else {
this.iv = window.crypto.getRandomValues(new Uint8Array(12));
}
if (secretKeyB64) {
this.rawSecret = b64ToArray(secretKeyB64);
} else {
this.rawSecret = window.crypto.getRandomValues(new Uint8Array(16));
}
this.secretKeyPromise = window.crypto.subtle.importKey(
'raw',
this.rawSecret,
'HKDF',
false,
['deriveKey']
);
this.encryptKeyPromise = this.secretKeyPromise.then(function(secretKey) {
return window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('encryption'),
hash: 'SHA-256'
},
secretKey,
{
name: 'AES-GCM',
length: 128
},
false,
['encrypt', 'decrypt']
);
});
this.metaKeyPromise = this.secretKeyPromise.then(function(secretKey) {
return window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('metadata'),
hash: 'SHA-256'
},
secretKey,
{
name: 'AES-GCM',
length: 128
},
false,
['encrypt', 'decrypt']
);
});
this.authKeyPromise = this.secretKeyPromise.then(function(secretKey) {
return window.crypto.subtle.deriveKey(
{
name: 'HKDF',
salt: new Uint8Array(),
info: encoder.encode('authentication'),
hash: 'SHA-256'
},
secretKey,
{
name: 'HMAC',
hash: { name: 'SHA-256' }
},
true,
['sign']
);
});
}
get nonce() {
return this._nonce;
}
set nonce(n) {
if (n && n !== this._nonce) {
this._nonce = n;
}
}
setIV(ivB64) {
this.iv = b64ToArray(ivB64);
}
setPassword(password, shareUrl) {
this.authKeyPromise = window.crypto.subtle
.importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, [
'deriveKey'
])
.then(passwordKey =>
window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: encoder.encode(shareUrl),
iterations: 100,
hash: 'SHA-256'
},
passwordKey,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign']
)
);
}
setAuthKey(authKeyB64) {
this.authKeyPromise = window.crypto.subtle.importKey(
'raw',
b64ToArray(authKeyB64),
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign']
);
}
async authKeyB64() {
const authKey = await this.authKeyPromise;
const rawAuth = await window.crypto.subtle.exportKey('raw', authKey);
return arrayToB64(new Uint8Array(rawAuth));
}
async authHeader() {
const authKey = await this.authKeyPromise;
const sig = await window.crypto.subtle.sign(
{
name: 'HMAC'
},
authKey,
b64ToArray(this.nonce)
);
return `send-v1 ${arrayToB64(new Uint8Array(sig))}`;
}
async encryptFile(plaintext) {
const encryptKey = await this.encryptKeyPromise;
const ciphertext = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: this.iv,
tagLength: 128
},
encryptKey,
plaintext
);
return ciphertext;
}
async encryptMetadata(metadata) {
const metaKey = await this.metaKeyPromise;
const ciphertext = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: new Uint8Array(12),
tagLength: 128
},
metaKey,
encoder.encode(
JSON.stringify({
iv: arrayToB64(this.iv),
name: metadata.name,
type: metadata.type || 'application/octet-stream'
})
)
);
return ciphertext;
}
async decryptFile(ciphertext) {
const encryptKey = await this.encryptKeyPromise;
const plaintext = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: this.iv,
tagLength: 128
},
encryptKey,
ciphertext
);
return plaintext;
}
async decryptMetadata(ciphertext) {
const metaKey = await this.metaKeyPromise;
const plaintext = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: new Uint8Array(12),
tagLength: 128
},
metaKey,
ciphertext
);
return JSON.parse(decoder.decode(plaintext));
}
}

View File

@@ -15,30 +15,34 @@ if (navigator.doNotTrack !== '1' && window.RAVEN_CONFIG) {
}
app.use((state, emitter) => {
// init state
state.transfer = null;
state.fileInfo = null;
state.translate = locale.getTranslator();
state.storage = storage;
state.raven = Raven;
emitter.on('DOMContentLoaded', async () => {
let reason = null;
window.appState = state;
emitter.on('DOMContentLoaded', async function checkSupport() {
let unsupportedReason = null;
if (
// Firefox < 50
/firefox/i.test(navigator.userAgent) &&
parseInt(navigator.userAgent.match(/firefox\/*([^\n\r]*)\./i)[1], 10) <=
49
parseInt(navigator.userAgent.match(/firefox\/*([^\n\r]*)\./i)[1], 10) < 50
) {
reason = 'outdated';
unsupportedReason = 'outdated';
}
if (/edge\/\d+/i.test(navigator.userAgent)) {
reason = 'edge';
unsupportedReason = 'edge';
}
const ok = await canHasSend(assets.get('cryptofill.js'));
if (!ok) {
reason = /firefox/i.test(navigator.userAgent) ? 'outdated' : 'gcm';
unsupportedReason = /firefox/i.test(navigator.userAgent)
? 'outdated'
: 'gcm';
}
if (reason) {
setTimeout(() => emitter.emit('replaceState', `/unsupported/${reason}`));
if (unsupportedReason) {
setTimeout(() =>
emitter.emit('replaceState', `/unsupported/${unsupportedReason}`)
);
}
});
});

View File

@@ -20,7 +20,7 @@ let experiment = null;
export default function initialize(state, emitter) {
appState = state;
emitter.on('DOMContentLoaded', () => {
// addExitHandlers();
addExitHandlers();
experiment = storage.enrolled[0];
sendEvent(category(), 'visit', {
cm5: storage.totalUploads,
@@ -29,9 +29,8 @@ export default function initialize(state, emitter) {
});
//TODO restart handlers... somewhere
});
emitter.on('exit', evt => {
exitEvent(evt);
});
emitter.on('exit', exitEvent);
emitter.on('experiment', experimentEvent);
}
function category() {
@@ -259,6 +258,10 @@ function exitEvent(target) {
});
}
function experimentEvent(params) {
return sendEvent(category(), 'experiment', params);
}
// eslint-disable-next-line no-unused-vars
function addExitHandlers() {
const links = Array.from(document.querySelectorAll('a'));

77
app/ownedFile.js Normal file
View File

@@ -0,0 +1,77 @@
import Keychain from './keychain';
import { arrayToB64 } from './utils';
import { del, fileInfo, setParams, setPassword } from './api';
export default class OwnedFile {
constructor(obj) {
this.id = obj.id;
this.url = obj.url;
this.name = obj.name;
this.size = obj.size;
this.type = obj.type;
this.time = obj.time;
this.speed = obj.speed;
this.createdAt = obj.createdAt;
this.expiresAt = obj.expiresAt;
this.ownerToken = obj.ownerToken;
this.dlimit = obj.dlimit || 1;
this.dtotal = obj.dtotal || 0;
this.keychain = new Keychain(obj.secretKey);
this._hasPassword = !!obj.hasPassword;
}
async setPassword(password) {
this.password = password;
this._hasPassword = true;
this.keychain.setPassword(password, this.url);
const result = await setPassword(this.id, this.ownerToken, this.keychain);
return result;
}
del() {
return del(this.id, this.ownerToken);
}
changeLimit(dlimit) {
if (this.dlimit !== dlimit) {
this.dlimit = dlimit;
return setParams(this.id, this.ownerToken, { dlimit });
}
return Promise.resolve(true);
}
get hasPassword() {
return !!this._hasPassword;
}
async updateDownloadCount() {
try {
const result = await fileInfo(this.id, this.ownerToken);
this.dtotal = result.dtotal;
this.dlimit = result.dlimit;
} catch (e) {
if (e.message === '404') {
this.dtotal = this.dlimit;
}
}
}
toJSON() {
return {
id: this.id,
url: this.url,
name: this.name,
size: this.size,
type: this.type,
time: this.time,
speed: this.speed,
createdAt: this.createdAt,
expiresAt: this.expiresAt,
secretKey: arrayToB64(this.keychain.rawSecret),
ownerToken: this.ownerToken,
dlimit: this.dlimit,
dtotal: this.dtotal,
hasPassword: this.hasPassword
};
}
}

View File

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

34
app/pages/completed.js Normal file
View File

@@ -0,0 +1,34 @@
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;
};

46
app/pages/download.js Normal file
View File

@@ -0,0 +1,46 @@
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

@@ -9,12 +9,12 @@ module.exports = function(state) {
<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 class="expired-description">
${state.translate('uploadPageExplainer')}
</div>
<a class="send-new" href="/" data-state="notfound">
${state.translate('sendYourFilesLink')}
</a>
</div>
</div>`;
return div;

44
app/pages/preview.js Normal file
View File

@@ -0,0 +1,44 @@
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

@@ -1,29 +1,22 @@
/* global EXPIRE_SECONDS */
const html = require('choo/html');
const assets = require('../../common/assets');
const notFound = require('./notFound');
const uploadPassword = require('./uploadPassword');
const selectbox = require('./selectbox');
const uploadPasswordSet = require('../templates/uploadPasswordSet');
const uploadPasswordUnset = require('../templates/uploadPasswordUnset');
const selectbox = require('../templates/selectbox');
const { allowedCopy, delay, fadeOut } = require('../utils');
function passwordComplete(state, password) {
const el = html([
`<div class="selectPassword">${state.translate('passwordResult', {
password: '<pre></pre>'
})}</div>`
]);
el.lastElementChild.textContent = password;
return el;
}
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: 24 })
timespan: translate('timespanHours', { num: hours })
})}</div>`
]);
const select = el.querySelector('select');
const options = [1, 2, 3, 4, 5, 20];
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(
@@ -39,19 +32,16 @@ module.exports = function(state, emit) {
return notFound(state, emit);
}
file.password = file.password || '';
const passwordSection = file.password
? passwordComplete(state, file.password)
: uploadPassword(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>
${state.translate('copyUrlFormLabelWithName', { filename: file.name })}
</div>
<div id="copy">
<input id="link" type="url" value="${file.url}" readonly="true"/>
<button id="copy-btn"
@@ -63,7 +53,22 @@ module.exports = function(state, emit) {
<button id="delete-file"
class="btn"
title="${state.translate('deleteFileButton')}"
onclick=${deleteFile}>${state.translate('deleteFileButton')}</button>
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="/"
@@ -72,6 +77,18 @@ module.exports = function(state, emit) {
</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');

52
app/pages/unsupported.js Normal file
View File

@@ -0,0 +1,52 @@
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;
};

41
app/pages/upload.js Normal file
View File

@@ -0,0 +1,41 @@
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;
};

View File

@@ -1,7 +1,8 @@
/* global MAXFILESIZE */
const html = require('choo/html');
const assets = require('../../common/assets');
const fileList = require('./fileList');
const { fadeOut } = require('../utils');
const fileList = require('../templates/fileList');
const { bytes, fadeOut } = require('../utils');
module.exports = function(state, emit) {
const div = html`
@@ -9,14 +10,18 @@ module.exports = function(state, emit) {
<div class="title">${state.translate('uploadPageHeader')}</div>
<div class="description">
<div>${state.translate('uploadPageExplainer')}</div>
<a href="https://testpilot.firefox.com/experiments/send"
class="link">${state.translate('uploadPageLearnMore')}</a>
<a
href="https://testpilot.firefox.com/experiments/send"
class="link">
${state.translate('uploadPageLearnMore')}
</a>
</div>
<div class="upload-window"
ondragover=${dragover}
ondragleave=${dragleave}>
<div id="upload-img">
<img src="${assets.get('upload.svg')}"
<img
src="${assets.get('upload.svg')}"
title="${state.translate('uploadSvgAlt')}"/>
</div>
<div id="upload-text">${state.translate('uploadPageDropMessage')}</div>
@@ -33,7 +38,8 @@ module.exports = function(state, emit) {
id="browse"
class="btn browse"
title="${state.translate('uploadPageBrowseButton1')}">
${state.translate('uploadPageBrowseButton1')}</label>
${state.translate('uploadPageBrowseButton1')}
</label>
</div>
${fileList(state, emit)}
</div>
@@ -64,6 +70,11 @@ module.exports = function(state, emit) {
if (file.size === 0) {
return;
}
if (file.size > MAXFILESIZE) {
window.alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
return;
}
await fadeOut('page-one');
emit('upload', { file, type: 'click' });
}

View File

@@ -1,12 +1,60 @@
const preview = require('../templates/preview');
const download = require('../templates/download');
const preview = require('../pages/preview');
const download = require('../pages/download');
const notFound = require('../pages/notFound');
const downloadPassword = require('../templates/downloadPassword');
const downloadButton = require('../templates/downloadButton');
function hasFileInfo() {
return !!document.getElementById('dl-file');
}
function getFileInfoFromDOM() {
const el = document.getElementById('dl-file');
if (!el) {
return null;
}
return {
nonce: el.getAttribute('data-nonce'),
requiresPassword: !!+el.getAttribute('data-requires-password')
};
}
function createFileInfo(state) {
const metadata = getFileInfoFromDOM();
return {
id: state.params.id,
secretKey: state.params.key,
nonce: metadata.nonce,
requiresPassword: metadata.requiresPassword
};
}
module.exports = function(state, emit) {
if (state.transfer) {
const s = state.transfer.state;
if (s === 'downloading' || s === 'complete') {
return download(state, emit);
if (!state.fileInfo) {
// This is a fresh page load
// We need to parse the file info from the server's html
if (!hasFileInfo()) {
return notFound(state, emit);
}
state.fileInfo = createFileInfo(state);
if (!state.fileInfo.requiresPassword) {
emit('getMetadata');
}
}
return preview(state, emit);
let pageAction = null; //default state: we don't have file metadata
if (state.transfer) {
const s = state.transfer.state;
if (['downloading', 'decrypting', 'complete'].indexOf(s) > -1) {
// Downloading is in progress
return download(state, emit);
}
// we have file metadata
pageAction = downloadButton(state, emit);
} else if (state.fileInfo.requiresPassword && !state.fileInfo.password) {
// we're waiting on the user for a valid password
pageAction = downloadPassword(state, emit);
}
return preview(state, pageAction);
};

View File

@@ -1,9 +1,8 @@
const welcome = require('../templates/welcome');
const upload = require('../templates/upload');
const welcome = require('../pages/welcome');
const upload = require('../pages/upload');
module.exports = function(state, emit) {
if (state.transfer && state.transfer.iv) {
//TODO relying on 'iv' is gross
if (state.uploading) {
return upload(state, emit);
}
return welcome(state, emit);

View File

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

View File

@@ -1,4 +1,5 @@
import { isFile } from './utils';
import OwnedFile from './ownedFile';
class Mem {
constructor() {
@@ -42,7 +43,7 @@ class Storage {
const k = this.engine.key(i);
if (isFile(k)) {
try {
const f = JSON.parse(this.engine.getItem(k));
const f = new OwnedFile(JSON.parse(this.engine.getItem(k)));
if (!f.id) {
f.id = f.fileId;
}
@@ -108,11 +109,15 @@ class Storage {
addFile(file) {
this._files.push(file);
this.writeFile(file);
}
writeFile(file) {
this.engine.setItem(file.id, JSON.stringify(file));
}
writeFiles() {
this._files.forEach(f => this.engine.setItem(f.id, JSON.stringify(f)));
this._files.forEach(f => this.writeFile(f));
}
}

View File

@@ -1,33 +0,0 @@
const html = require('choo/html');
const progress = require('./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

@@ -1,30 +0,0 @@
const html = require('choo/html');
const progress = require('./progress');
const { bytes } = require('../utils');
module.exports = function(state) {
const transfer = state.transfer;
const div = html`
<div id="page-one">
<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>
</div>
</div>
</div>
`;
return div;
};

View File

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

View File

@@ -5,8 +5,9 @@ module.exports = function(state, emit) {
const label =
fileInfo.password === null
? html`
<label class="red"
for="unlock-input">${state.translate('passwordTryAgain')}</label>`
<label class="red" for="unlock-input">
${state.translate('passwordTryAgain')}
</label>`
: html`
<label for="unlock-input">
${state.translate('unlockInputLabel')}
@@ -21,7 +22,8 @@ module.exports = function(state, emit) {
autocomplete="off"
placeholder="${state.translate('unlockInputPlaceholder')}"
oninput=${inputChanged}
type="password"/>
type="password"
autofocus />
<input type="submit"
id="unlock-btn"
class="btn btn-hidden"
@@ -48,8 +50,9 @@ module.exports = function(state, emit) {
document.getElementById('unlock-btn').disabled = true;
state.fileInfo.url = window.location.href;
state.fileInfo.password = password;
emit('preview');
emit('getMetadata');
}
return false;
}
return div;

View File

@@ -1,49 +1,62 @@
const html = require('choo/html');
const assets = require('../../common/assets');
function timeLeft(milliseconds) {
function timeLeft(milliseconds, state) {
const minutes = Math.floor(milliseconds / 1000 / 60);
const hours = Math.floor(minutes / 60);
const seconds = Math.floor((milliseconds / 1000) % 60);
if (hours >= 1) {
return `${hours}h ${minutes % 60}m`;
return state.translate('expiresHoursMinutes', {
hours,
minutes: minutes % 60
});
} else if (hours === 0) {
return `${minutes}m ${seconds}s`;
if (minutes === 0) {
return state.translate('expiresMinutes', { minutes: '< 1' });
}
return state.translate('expiresMinutes', { minutes });
}
return null;
}
module.exports = function(file, state, emit) {
const ttl = file.expiresAt - Date.now();
const remaining = timeLeft(ttl) || state.translate('linkExpiredAlt');
const remainingTime =
timeLeft(ttl, state) || state.translate('linkExpiredAlt');
const downloadLimit = file.dlimit || 1;
const totalDownloads = file.dtotal || 0;
const row = html`
<tr id="${file.id}">
<td class="overflow-col" title="${
file.name
}"><a class="link" href="/share/${file.id}">${file.name}</a></td>
<td class="center-col">
<img onclick=${copyClick} src="${assets.get(
'copy-16.svg'
)}" class="icon-copy" title="${state.translate('copyUrlHover')}">
<span class="text-copied" hidden="true">${state.translate(
'copiedUrl'
)}</span>
<td class="overflow-col" title="${file.name}">
<a class="link" href="/share/${file.id}">${file.name}</a>
</td>
<td>${remaining}</td>
<td class="center-col">
<img onclick=${showPopup} src="${assets.get(
'close-16.svg'
)}" class="icon-delete" title="${state.translate('deleteButtonHover')}">
<img
onclick=${copyClick}
src="${assets.get('copy-16.svg')}"
class="icon-copy"
title="${state.translate('copyUrlHover')}">
<span class="text-copied" hidden="true">
${state.translate('copiedUrl')}
</span>
</td>
<td>${remainingTime}</td>
<td class="center-col">${totalDownloads} / ${downloadLimit}</td>
<td class="center-col">
<img
onclick=${showPopup}
src="${assets.get('close-16.svg')}"
class="icon-delete"
title="${state.translate('deleteButtonHover')}">
<div 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>
<span class="popup-no" onclick=${cancel}>
${state.translate('deletePopupCancel')}
</span>
<span class="popup-yes" onclick=${deleteFile}>
${state.translate('deletePopupYes')}
</span>
</div>
</div>
</div>

View File

@@ -9,13 +9,18 @@ module.exports = function(state, emit) {
<thead>
<tr>
<th id="uploaded-file">${state.translate('uploadedFile')}</th>
<th id="copy-file-list" class="center-col">${state.translate(
'copyFileList'
)}</th>
<th id="expiry-file-list">${state.translate('expiryFileList')}</th>
<th id="delete-file-list" class="center-col">${state.translate(
'deleteFileList'
)}</th>
<th id="copy-file-list" class="center-col">
${state.translate('copyFileList')}
</th>
<th id="expiry-time-file-list" >
${state.translate('timeFileList')}
</th>
<th id="expiry-downloads-file-list" >
${state.translate('downloadsFileList')}
</th>
<th id="delete-file-list" class="center-col">
${state.translate('deleteFileList')}
</th>
</tr>
</thead>
<tbody>

View File

@@ -4,31 +4,40 @@ 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="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>
<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>
<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

@@ -1,25 +1,19 @@
const html = require('choo/html');
const assets = require('../../common/assets');
// function replaceLinks(str, urls) {
// let i = -1;
// const s = str.replace(/<a>([^<]+)<\/a>/g, (m, v) => {
// i++;
// return `<a class="link" href="${urls[i]}">${v}</a>`;
// });
// return [`<span>${s}</span>`];
// }
module.exports = function(state, emit) {
// function close() {
// document.querySelector('.banner').remove();
// }
function clicked(evt) {
emit('exit', evt);
function clicked() {
emit('experiment', { cd3: 'promo' });
}
let classes = 'banner';
switch (state.promo) {
case 'blue':
classes = 'banner banner-blue';
break;
case 'pink':
classes = 'banner banner-pink';
break;
}
const classes = state.promo === 'blue' ? 'banner banner-blue' : 'banner';
return html`
<div class="${classes}">
@@ -37,10 +31,3 @@ module.exports = function(state, emit) {
</div>
</div>`;
};
/*
<img
src="${assets.get('close-16.svg')}"
class="icon-delete"
onclick=${close}>
*/

View File

@@ -12,20 +12,46 @@ const assets = require('../../common/assets');
*/
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>
<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}"
<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>

View File

@@ -1,72 +0,0 @@
const html = require('choo/html');
const assets = require('../../common/assets');
const notFound = require('./notFound');
const downloadPassword = require('./downloadPassword');
const { bytes } = require('../utils');
function getFileFromDOM() {
const el = document.getElementById('dl-file');
if (!el) {
return null;
}
return {
nonce: el.getAttribute('data-nonce'),
pwd: !!+el.getAttribute('data-requires-password')
};
}
module.exports = function(state, emit) {
state.fileInfo = state.fileInfo || getFileFromDOM();
if (!state.fileInfo) {
return notFound(state, emit);
}
state.fileInfo.id = state.params.id;
state.fileInfo.key = state.params.key;
const fileInfo = state.fileInfo;
const size = fileInfo.size
? state.translate('downloadFileSize', { size: bytes(fileInfo.size) })
: '';
let action = html`
<div>
<img src="${assets.get('illustration_download.svg')}"
id="download-img"
alt="${state.translate('downloadAltText')}"/>
<div>
<button id="download-btn"
class="btn"
onclick=${download}>${state.translate('downloadButtonLabel')}
</button>
</div>
</div>`;
if (fileInfo.pwd && !fileInfo.password) {
action = downloadPassword(state, emit);
} else if (!state.transfer) {
emit('preview');
}
const title = fileInfo.name
? state.translate('downloadFileName', { filename: fileInfo.name })
: state.translate('downloadFileTitle');
const div = html`
<div id="page-one">
<div id="download">
<div id="download-page-one">
<div class="title">
<span id="dl-file"
data-nonce="${fileInfo.nonce}"
data-requires-password="${fileInfo.pwd}">${title}</span>
<span id="dl-filesize">${' ' + size}</span>
</div>
<div class="description">${state.translate('downloadMessage')}</div>
${action}
</div>
<a class="send-new" href="/">${state.translate('sendYourFilesLink')}</a>
</div>
</div>
`;
function download(event) {
event.preventDefault();
emit('download', fileInfo);
}
return div;
};

View File

@@ -10,18 +10,30 @@ module.exports = function(progressRatio) {
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
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>
`;

View File

@@ -47,9 +47,7 @@ module.exports = function(selected, options, translate, changed) {
<ul id="${id}" class="selectOptions">
${options.map(
i =>
html`<li class="selectOption" onclick=${choose} data-value="${i}">${
i
}</li>`
html`<li class="selectOption" onclick=${choose} data-value="${i}">${i}</li>`
)}
</ul>
</div>`;

View File

@@ -1,46 +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

@@ -1,38 +0,0 @@
const html = require('choo/html');
const progress = require('./progress');
const { bytes } = require('../utils');
module.exports = function(state, emit) {
const transfer = state.transfer;
const div = html`
<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>
`;
function cancel() {
const btn = document.getElementById('cancel-upload');
btn.disabled = true;
btn.textContent = state.translate('uploadCancelNotification');
emit('cancel');
}
return div;
};

View File

@@ -0,0 +1,80 @@
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

@@ -5,18 +5,22 @@ module.exports = function(state, emit) {
const div = html`
<div class="selectPassword">
<div id="addPasswordWrapper">
<input id="addPassword" type="checkbox" autocomplete="off" onchange=${
togglePasswordInput
}/>
<input
id="addPassword"
type="checkbox"
autocomplete="off"
onchange=${togglePasswordInput}/>
<label for="addPassword">
${state.translate('requirePasswordCheckbox')}</label>
${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="64"
maxlength="32"
autocomplete="off"
placeholder="${state.translate('unlockInputPlaceholder')}"
type="password"
oninput=${inputChanged}/>
<input type="submit"
id="unlock-btn"
@@ -59,6 +63,7 @@ module.exports = function(state, emit) {
document.getElementById('copy-btn').disabled = false;
emit('password', { password, file });
}
return false;
}
return div;

View File

@@ -15,21 +15,6 @@ function b64ToArray(str) {
return b64.toByteArray(str);
}
function notify(str) {
return str;
/* TODO: enable once we have an opt-in ui element
if (!('Notification' in window)) {
return;
} else if (Notification.permission === 'granted') {
new Notification(str);
} else if (Notification.permission !== 'denied') {
Notification.requestPermission(function(permission) {
if (permission === 'granted') new Notification(str);
});
}
*/
}
function loadShim(polyfill) {
return new Promise((resolve, reject) => {
const shim = document.createElement('script');
@@ -148,7 +133,37 @@ function fadeOut(id) {
return delay(300);
}
const ONE_DAY_IN_MS = 86400000;
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) {
links = links || Array.from(document.querySelectorAll('a:not([target])'));
if (should) {
links.forEach(l => {
l.setAttribute('target', '_blank');
l.setAttribute('rel', 'noopener noreferrer');
});
} else {
links.forEach(l => {
l.removeAttribute('target');
l.removeAttribute('rel');
});
}
return links;
}
module.exports = {
fadeOut,
@@ -159,8 +174,8 @@ module.exports = {
copyToClipboard,
arrayToB64,
b64ToArray,
notify,
canHasSend,
isFile,
ONE_DAY_IN_MS
saveFile,
openLinksInNewTab
};

View File

@@ -146,6 +146,8 @@ button {
}
pre {
font-family: monospace;
font-size: 18px;
font-weight: 600;
display: inline-block;
}
@@ -236,6 +238,10 @@ a {
text-align: center;
}
.upload-window.ondrag * {
pointer-events: none;
}
.link {
color: #0094fb;
text-decoration: none;
@@ -336,10 +342,14 @@ tbody {
width: 25%;
}
#expiry-file-list {
#expiry-time-file-list {
width: 21%;
}
#expiry-downloads-file-list {
width: 8%;
}
#delete-file-list {
width: 12%;
}
@@ -412,6 +422,8 @@ tbody {
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 .show {
@@ -541,7 +553,6 @@ tbody {
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
max-width: 640px;
@@ -620,6 +631,7 @@ tbody {
}
#delete-file {
align-self: center;
width: 176px;
height: 44px;
background: #fff;
@@ -632,10 +644,35 @@ tbody {
color: #313131;
}
#deletePopup {
position: relative;
align-self: center;
bottom: 50px;
}
#delete-file:hover {
background: #efeff1;
}
#resetButton {
width: 80px;
height: 30px;
background: #fff;
border: 1px solid rgba(12, 12, 13, 0.3);
border-radius: 5px;
font-size: 15px;
margin-top: 5px;
margin-left: 15px;
margin-bottom: 12px;
line-height: 24px;
cursor: pointer;
color: #313131;
}
#resetButton:hover {
background: #efeff1;
}
.send-new {
font-size: 15px;
margin: auto;
@@ -662,6 +699,18 @@ tbody {
overflow-wrap: break-word;
}
.passwordOriginal {
display: none;
}
.selectPassword :hover .passwordOriginal {
display: inline;
}
.selectPassword :hover .passwordMask {
display: none;
}
.setPassword {
align-self: left;
display: flex;
@@ -721,7 +770,7 @@ tbody {
cursor: pointer;
border: 0;
box-shadow: 0 5px 3px rgb(234, 234, 234);
font-family: 'Fira Sans';
font-family: 'Fira Sans', 'segoe ui', sans-serif;
font-weight: 500;
color: #fff;
font-size: 26px;
@@ -738,7 +787,7 @@ tbody {
}
.unsupported-button-text > span {
font-family: 'Fira Sans';
font-family: 'Fira Sans', 'segoe ui', sans-serif;
font-weight: 300;
font-size: 18px;
letter-spacing: -0.69px;
@@ -833,7 +882,8 @@ tbody {
padding-right: 10px;
}
#unlock-btn {
#unlock-btn,
#unlock-reset-btn {
flex: 0 1 165px;
background: #0297f8;
border-radius: 0 6px 6px 0;
@@ -850,12 +900,13 @@ tbody {
white-space: nowrap;
}
#unlock-btn:hover {
#unlock-btn:hover,
#unlock-reset-btn:hover {
background-color: #0287e8;
}
.btn-hidden {
visibility: hidden;
display: none;
}
.input-no-btn {
@@ -935,8 +986,12 @@ tbody {
visibility: collapse;
}
#addPasswordWrapper:hover label::before {
border: 1px solid #0297f8;
}
#addPasswordWrapper label {
line-height: 20px;
line-height: 23px;
cursor: pointer;
color: #737373;
}
@@ -985,7 +1040,7 @@ tbody {
}
.banner-blue {
background-color: #0098f7;
background: linear-gradient(-180deg, #45a1ff 0%, #00feff 94%);
color: #fff;
}
@@ -999,6 +1054,21 @@ tbody {
font-weight: bold;
}
.banner-pink {
background: linear-gradient(-180deg, #ff9400 0%, #ff1ad9 94%);
color: #fff;
}
.banner-pink a {
color: #fff;
font-weight: bold;
}
.banner-pink a:hover {
color: #eee;
font-weight: bold;
}
.selectbox {
display: inline-block;
position: relative;
@@ -1128,14 +1198,16 @@ tbody {
}
#link,
#unlock-input {
#unlock-input,
#unlock-reset-input {
font-size: 22px;
padding: 15px 10px;
border-radius: 6px 6px 0 0;
}
#copy-btn,
#unlock-btn {
#unlock-btn,
#unlock-reset-btn {
border-radius: 0 0 6px 6px;
flex: 0 1 65px;
}
@@ -1153,4 +1225,12 @@ tbody {
font-size: 13px;
padding: 17px 5px 0;
}
.popup .popuptext::after {
left: 125px;
}
#deletePopup {
left: 83px;
}
}

View File

@@ -1,26 +1,45 @@
const { MessageContext } = require('fluent');
const fs = require('fs');
function toJSON(map) {
return JSON.stringify(Array.from(map));
}
function merge(m1, m2) {
const result = new Map(m1);
for (const [k, v] of m2) {
result.set(k, v);
}
return result;
}
module.exports = function(source) {
const localeExp = this.options.locale || /([^/]+)\/[^/]+\.ftl$/;
const result = localeExp.exec(this.resourcePath);
const locale = result && result[1];
// pre-parse the ftl
const context = new MessageContext(locale);
context.addMessages(source);
if (!locale) {
throw new Error(`couldn't find locale in: ${this.resourcePath}`);
}
// load default language and "merge" contexts
// TODO: make this configurable
const en_ftl = fs.readFileSync(
require.resolve('../public/locales/en-US/send.ftl'),
'utf8'
);
const en = new MessageContext('en-US');
en.addMessages(en_ftl);
// pre-parse the ftl
const context = new MessageContext(locale);
context.addMessages(source);
const merged = merge(en._messages, context._messages);
return `
module.exports = \`
if (typeof window === 'undefined') {
var fluent = require('fluent');
}
var ctx = new fluent.MessageContext('${locale}', {useIsolating: false});
ctx._messages = new Map(${toJSON(context._messages)});
ctx._messages = new Map(${toJSON(merged)});
function translate(id, data) {
var msg = ctx.getMessage(id);
if (typeof(msg) !== 'string' && !msg.val && msg.attrs) {

View File

@@ -8,6 +8,5 @@ services:
- "1443:1443"
environment:
- REDIS_HOST=redis
- NODE_ENV=production
redis:
image: redis:alpine

19
docs/takedowns.md Normal file
View File

@@ -0,0 +1,19 @@
## Take-down process
In cases of a DMCA notice, or other abuse yet to be determined, a file has to be removed from the service.
Files can be delisted and made inaccessible by removing their record from Redis.
Send share links contain the `id` of the file, for example `https://send.firefox.com/download/3d9d2bb9a1`
From a host with access to the Redis server run a `DEL` command with the file id.
For example:
```sh
redis-cli DEL 3d9d2bb9a1
```
Other redis-cli parameters like `-h` may also be required. See [redis-cli docs](https://redis.io/topics/rediscli) for more info.
The encrypted file resides on S3 as the same `id` under the bucket that the app was configured with as `S3_BUCKET`. The file can be managed if it has not already expired with the [AWS cli](https://docs.aws.amazon.com/cli/latest/reference/s3/index.html) or AWS web console.

851
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "firefox-send",
"description": "File Sharing Experiment",
"version": "2.2.0",
"version": "2.3.0",
"author": "Mozilla (https://mozilla.org)",
"repository": "mozilla/send",
"homepage": "https://github.com/mozilla/send/",
@@ -43,7 +43,7 @@
"node": ">=8.2.0"
},
"devDependencies": {
"autoprefixer": "^7.2.1",
"autoprefixer": "^7.2.5",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-yo-yoify": "^1.0.2",
@@ -52,61 +52,61 @@
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"base64-js": "^1.2.1",
"copy-webpack-plugin": "^4.2.3",
"cross-env": "^5.1.1",
"css-loader": "^0.28.7",
"css-mqpacker": "^6.0.1",
"copy-webpack-plugin": "^4.3.1",
"cross-env": "^5.1.3",
"css-loader": "^0.28.9",
"css-mqpacker": "^6.0.2",
"cssnano": "^3.10.0",
"eslint": "^4.12.0",
"eslint": "^4.16.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-security": "^1.4.0",
"expose-loader": "^0.7.4",
"extract-loader": "^1.0.1",
"file-loader": "^1.1.5",
"extract-loader": "^1.0.2",
"file-loader": "^1.1.6",
"fluent-intl-polyfill": "^0.1.0",
"git-rev-sync": "^1.9.1",
"github-changes": "^1.1.1",
"html-loader": "^0.5.1",
"github-changes": "^1.1.2",
"html-loader": "^0.5.5",
"husky": "^0.14.3",
"lint-staged": "^4.3.0",
"mocha": "^3.5.3",
"nanobus": "^4.3.1",
"nanobus": "^4.3.2",
"npm-run-all": "^4.1.2",
"postcss-loader": "^2.0.9",
"prettier": "^1.8.2",
"postcss-loader": "^2.0.10",
"prettier": "^1.10.2",
"proxyquire": "^1.8.0",
"raven-js": "^3.20.1",
"raven-js": "^3.22.1",
"redis-mock": "^0.20.0",
"require-from-string": "^2.0.1",
"rimraf": "^2.6.2",
"selenium-webdriver": "^3.6.0",
"sinon": "^4.1.2",
"sinon": "^4.2.2",
"string-hash": "^1.1.3",
"stylelint-config-standard": "^17.0.0",
"stylelint-no-unsupported-browser-features": "^1.0.1",
"supertest": "^3.0.0",
"testpilot-ga": "^0.3.0",
"val-loader": "^1.1.0",
"webpack": "^3.8.1",
"webpack": "^3.10.0",
"webpack-dev-server": "2.9.1",
"webpack-manifest-plugin": "^1.3.2",
"webpack-unassert-loader": "^1.2.0"
},
"dependencies": {
"aws-sdk": "^2.162.0",
"aws-sdk": "^2.188.0",
"body-parser": "^1.18.2",
"choo": "^6.6.0",
"choo": "^6.7.0",
"cldr-core": "^32.0.0",
"connect-busboy": "0.0.2",
"convict": "^4.0.1",
"express": "^4.16.2",
"fluent": "^0.4.1",
"fluent-langneg": "^0.1.0",
"helmet": "^3.9.0",
"helmet": "^3.10.0",
"mkdirp": "^0.5.1",
"mozlog": "^2.2.0",
"raven": "^2.2.1",
"raven": "^2.4.0",
"redis": "^2.8.0"
},
"availableLanguages": [

View File

@@ -11,7 +11,10 @@ uploadPageBrowseButton = اختر ملفّا على حاسوبك
.title = اختر ملفّا على حاسوبك
uploadPageBrowseButton1 = اختر ملفّا لرفعه
uploadPageMultipleFilesAlert = رفع عدة ملفات (أو رفع مجلد) ليس مدعوما حاليا.
uploadPageBrowseButtonTitle = ارفع ملفًا
uploadingPageProgress = يرفع { $filename } ({ $size })
importingFile = يستورد…
verifyingFile = يتحقق…
encryptingFile = يعمّي…
decryptingFile = يفك التعمية…
notifyUploadDone = انتهى الرفع.
@@ -21,8 +24,27 @@ uploadingPageCancel = ألغِ الرفع
uploadCancelNotification = أُلغي الرفع.
uploadingPageLargeFileMessage = هذا الملف كبير الحجم وسيأخذ رفعه وقتا. انتظر رجاءً.
uploadingFileNotification = أعلِمني عندما يكتمل الرفع.
uploadSuccessConfirmHeader = جاهز للإرسال
uploadSvgAlt
.alt = ارفع
uploadSuccessTimingHeader = ستنتهي صلاحية الرابط الذي يشير إلى الملف في حال: نُزِّل لأول مرة، أو مرّ ٢٤ ساعة على رفعه.
expireInfo = ستنتهي صلاحية رابط الملف بعد { $downloadCount } أو { $timespan }.
downloadCount = { $num ->
[zero] لا تنزيلات
[one] تنزيل واحد
[two] تنزيلين
[few] { $num } تنزيلات
[many] { $num } تنزيلًا
*[other] { $num } تنزيل
}
timespanHours = { $num ->
[zero] أقل من ساعة
[one] ساعة
[two] ساعتين
[few] { $num } ساعات
[many] { $num } ساعة
*[other] { $num } ساعة
}
copyUrlFormLabelWithName = انسخ الرابط وشاركه لإرسال الملف: { $filename }
copyUrlFormButton = انسخ إلى الحافظة
.title = انسخ إلى الحافظة
@@ -34,9 +56,17 @@ sendAnotherFileLink = أرسل ملفّا آخر
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = نزّل
downloadsFileList = التنزيلات
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = الوقت
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = نزّل { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = أدخل كلمة السر
unlockInputPlaceholder = كلمة السر
unlockButtonLabel = افتح القفل
downloadFileTitle = نزِّل الملف المعمّى
// Firefox Send is a brand name and should not be localized.
downloadMessage = يُرسل إليك صديقك ملفا عبر «فَيَرفُكس سِنْد»، وهي خدمة تتيح لك مشاركة الملفات عبر رابط آمن وخاص ومعمّى، حيث تنتهي صلاحياتها تلقائيا لتضمن عدم بقاء ما ترسله إلى الأبد.
@@ -49,6 +79,7 @@ downloadFinish = اكتمل التنزيل
fileSizeProgress = ({ $partialSize } من أصل { $totalSize })
// Firefox Send is a brand name and should not be localized.
sendYourFilesLink = جرِّب «فَيَرفُكس سِنْد»
downloadingPageProgress = ينزّل { $filename } ({ $size })
downloadingPageMessage = رجاء أبقِ هذا اللسان مفتوحا حتى نجلب الملف ونفك تعميته.
errorAltText
.alt = خطأ أثناء الرفع
@@ -56,15 +87,24 @@ errorPageHeader = حدث خطب ما.
errorPageMessage = حدث خطب ما أثناء رفع الملف.
errorPageLink = أرسل ملفا آخر
fileTooBig = حجم الملف كبير للغاية لرفعه. يجب أن يكون أصغر من { $size }.
linkExpiredAlt = انتهت صلاحية الرابط
expiredPageHeader = انتهت صلاحية هذا الرابط أو لم يكن موجودا في المقام الأول!
notSupportedHeader = متصفحك غير مدعوم.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = للأسف فإن متصفحك لا يدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تجربة متصفح آخر، ونحن ننصحك بِفَيَرفُكس!
notSupportedLink = لماذا متصفحي غير مدعوم؟
notSupportedOutdatedDetail = للأسف فإن إصدارة فَيَرفُكس هذه لا تدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تحديث متصفحك.
updateFirefox = حدّث فَيَرفُكس
downloadFirefoxButtonSub = تنزيل مجاني
uploadedFile = ملف
copyFileList = انسخ الرابط
// expiryFileList is used as a column header
expiryFileList = ينتهي في
deleteFileList = احذف
nevermindButton = لا بأس
legalHeader = الشروط والخصوصية
legalNoticeTestPilot = «فَيَرفُكس سِنْد» جزء من اختبار تجريبي حاليًا و يخضع <a>لبنود خدمة</a> الاختبار التجريبي و <a>تنويه الخصوصية</a>. يمكنك التعرف على مزيد من المعلومات حول هذه التجربة وجمع البيانات<a>هنا</a>.
legalNoticeMozilla = يخضع استخدام موقع «فَيَرفُكس سِنْد» إلى<a>تنويه خصوصية المواقع</a> و <a>بنود خدمة المواقع</a>.
deletePopupText = أأحذف هذا الملف؟
deletePopupYes = نعم
deletePopupCancel = ألغِ
@@ -72,9 +112,16 @@ deleteButtonHover
.title = احذف
copyUrlHover
.title = انسخ الرابط
footerLinkLegal = القانونية
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = حول الاختبار التجريبي
footerLinkPrivacy = الخصوصية
footerLinkTerms = الشروط
footerLinkCookies = الكعكات
requirePasswordCheckbox = اطلب كلمة سر لتنزيل هذا الملف
addPasswordButton = أضِف كلمة سر
changePasswordButton = غيّر
passwordTryAgain = كلمة السر خاطئة. أعِد المحاولة.
// This label is followed by the password needed to download a file
passwordResult = كلمة السر: { $password }
reportIPInfringement = أبلغ عن انتهاك للملكية الفكرية

View File

@@ -25,6 +25,15 @@ uploadingFileNotification = Yükləmə bitdiyində xəbər ver.
uploadSuccessConfirmHeader = Göndərməyə hazır
uploadSvgAlt = Yüklə
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.
downloadCount = { $num ->
[one] 1 endirmə
*[other] { $num } endirmə
}
timespanHours = { $num ->
[one] 1 saat
*[other] { $num } saat
}
copyUrlFormLabelWithName = Faylınızı göndərmək üçün keçidi köçürün: { $filename }
copyUrlFormButton = Buferə köçür
copiedUrl = Köçürüldü!
@@ -32,6 +41,12 @@ deleteFileButton = Faylı sil
sendAnotherFileLink = Başqa fayl göndər
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Endir
downloadsFileList = Endirmələr
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Vaxt
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } faylını endir
downloadFileSize = ({ $size })
unlockInputLabel = Parol daxil edin
@@ -86,4 +101,15 @@ footerLinkTerms = Şərtlər
footerLinkCookies = Çərəzlər
requirePasswordCheckbox = Bu faylı endirmək üçün parol tələb et
addPasswordButton = Parol əlavə et
incorrectPassword = Xətalı parol. Təkrar yoxlayın.
changePasswordButton = Dəyişdir
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
javascriptRequired = Firefox Send üçün JavaScript lazımdır
whyJavascript = Firefox Send niyə JavaScript tələb edir?
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"
expiresHoursMinutes = { $hours } saat { $minutes } dəq
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes } dəq

View File

@@ -2,6 +2,7 @@
title = Firefox Send
siteSubtitle = ওয়েব গবেষণা
siteFeedback = প্রতিক্রিয়া
uploadPageHeader = ব্যক্তিগত, এনক্রিপ্ট করা ফাইল শেয়ারিং
uploadPageLearnMore = আরও জানুন
uploadPageBrowseButton = আপনার কম্পিউটারে ফাইল নির্বাচন করুন
uploadPageBrowseButtonTitle = ফাইল আপলোড
@@ -14,14 +15,31 @@ uploadingPageCancel = আপলোড বাতিল করুন
uploadCancelNotification = আপনার অাপলোড বাতিল করা হয়েছে।
uploadSuccessConfirmHeader = পাঠানোর জন্য প্রস্তুত
uploadSvgAlt = আপলোড
downloadCount = { $num ->
[one] 1 ডাউনলোড
*[other] { $num } ডাউনলোডগুলো
}
timespanHours = { $num ->
[one] 1 ঘন্টা
*[other] { $num } ঘন্টা
}
copyUrlFormButton = ক্লিপবোর্ডে কপি করুন
copiedUrl = কপি করা হয়েছে!
deleteFileButton = ফাইল মুছুন
sendAnotherFileLink = আরেকটি ফাইল পাঠান
// Alternative text used on the download link/button (indicates an action).
downloadAltText = ডাউনলোড
downloadsFileList = ডাউনলোডগুলো
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = সময়
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = ডাউনলোড { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = পাসওয়ার্ড লিখুন
unlockInputPlaceholder = পাসওয়ার্ড
unlockButtonLabel = আনলক করুন
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = ডাউনলোড
downloadNotification = আপনার ডাউনলোড সম্পন্ন হয়েছে।
@@ -29,6 +47,8 @@ downloadFinish = ডাউনলোড সম্পন্ন
errorAltText = আপালোডে ত্রুটি
errorPageHeader = কোন সমস্যা হয়েছে!
errorPageLink = আরেকটি ফাইল পাঠান
expiredPageHeader = এই লিঙ্কটির মেয়াদ শেষ হয়ে গেছে বা কখনই প্রথম স্থানে নেই!
notSupportedHeader = আপনার ব্রাউজার সমর্থিত নয়।
updateFirefox = Firefox হালনাগাদ করুন
downloadFirefoxButtonSub = বিনামূল্যে ডাউনলোড
uploadedFile = ফাইল
@@ -49,3 +69,4 @@ footerLinkAbout = Test Pilot পরিচিতি
footerLinkPrivacy = গোপনীয়তা
footerLinkTerms = শর্তাবলী
footerLinkCookies = কুকি
changePasswordButton = পরিবর্তন

View File

@@ -31,6 +31,11 @@ downloadCount = { $num ->
[few] { $num } preuzimanja
*[other] { $num } preuzimanja
}
timespanHours = { $num ->
[one] 1 sat
[few] { $num } sata
*[other] { $num } sati
}
copyUrlFormLabelWithName = Iskopirajte i podijelite vezu da biste poslali datoteku: { $filename }
copyUrlFormButton = Kopiraj u međuspremnik
copiedUrl = Kopirano!
@@ -38,6 +43,12 @@ deleteFileButton = Izbriši datoteku
sendAnotherFileLink = Pošalji drugu datoteku
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Preuzmi
downloadsFileList = Preuzimanja
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Vrijeme
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Preuzmi { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Unesite lozinku
@@ -92,6 +103,15 @@ footerLinkTerms = Uslovi
footerLinkCookies = Kolačići
requirePasswordCheckbox = Zahtjevaj lozinku za preuzimanje ove datoteke
addPasswordButton = Dodaj lozinku
changePasswordButton = Promijeni
passwordTryAgain = Netačna lozinka. Pokušajte ponovo.
// This label is followed by the password needed to download a file
passwordResult = Lozinka: { $password }
reportIPInfringement = Prijavite IP prekršaj
javascriptRequired = Firefox Send zahtjeva JavaScript
whyJavascript = Zašto Firefox Send zahtjeva JavaScript?
enableJavascript = Molimo omogućite JavaScript i pokušajte ponovo.
// 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

View File

@@ -25,6 +25,15 @@ uploadingFileNotification = Notifica'm quan s'acabi de pujar.
uploadSuccessConfirmHeader = Llest per enviar
uploadSvgAlt = Puja
uploadSuccessTimingHeader = L'enllaç al fitxer caducarà quan es baixi una vegada o d'aquí 24 hores.
expireInfo = L'enllaç al fitxer caducarà en fer { $downloadCount } o d'aquí { $timespan }.
downloadCount = { $num ->
[one] 1 baixada
*[other] { $num } baixades
}
timespanHours = { $num ->
[one] 1 hora
*[other] { $num } hores
}
copyUrlFormLabelWithName = Copieu l'enllaç i compartiu-lo per enviar el fitxer: { $filename }
copyUrlFormButton = Copia al porta-retalls
copiedUrl = Copiat!
@@ -34,6 +43,10 @@ sendAnotherFileLink = Envieu un altre fitxer
downloadAltText = Baixa
downloadFileName = Baixeu { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Introduïu la contrasenya
unlockInputPlaceholder = Contrasenya
unlockButtonLabel = Desbloca
downloadFileTitle = Baixa el fitxer xifrat
// Firefox Send is a brand name and should not be localized.
downloadMessage = Un amic us ha enviat un fitxer amb el Firefox Send, un servei que permet compartir fitxers mitjançant un enllaç segur, privat i xifrat que caduca automàticament per tal que les vostres dades no es conservin a Internet per sempre.
// Text and title used on the download link/button (indicates an action).
@@ -80,3 +93,9 @@ footerLinkAbout = Quant al Test Pilot
footerLinkPrivacy = Privadesa
footerLinkTerms = Condicions d'ús
footerLinkCookies = Galetes
requirePasswordCheckbox = Sol·licita una contrasenya per baixar aquest fitxer
addPasswordButton = Afegeix una contrasenya
passwordTryAgain = La contrasenya és incorrecta. Torneu-ho a provar.
// This label is followed by the password needed to download a file
passwordResult = Contrasenya: { $password }
reportIPInfringement = Denuncieu una infracció de propietat intel·lectual

View File

@@ -25,6 +25,15 @@ uploadingFileNotification = Tiya' pe rutzijol chwe toq xtitz'aqät rujotob'axik.
uploadSuccessConfirmHeader = Ütz chik richin Nitaq
uploadSvgAlt = Tijotob'äx
uploadSuccessTimingHeader = Ri ruximonel yakb'äl xtik'is ruq'ijul toq xtiqasäx jumul o pa 24 ramaj.
expireInfo = Ri ruximöy ayakb'al xtik'is ruq'ijul chi rij ri { $downloadCount } o { $timespan }.
downloadCount = { $num ->
[one] 1 qasanïk
*[other] { $num } taq qasanïk
}
timespanHours = { $num ->
[one] 1 ramaj
*[other] { $num } taq ramaj
}
copyUrlFormLabelWithName = Tiwachib'ëx chuqa' tikomonïx ri ximonel richin nitaq ri ayakb'äl: { $filename }
copyUrlFormButton = Tiwachib'ëx pa molwuj
copiedUrl = ¡Xwachib'ëx!
@@ -32,6 +41,12 @@ deleteFileButton = Tiyuj yakb'äl
sendAnotherFileLink = Titaq jun chik yakb'äl
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Tiqasäx
downloadsFileList = Taq qasanïk
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Q'ijul
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Tiqasäx { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Titz'ib'äx Ewan Tzij
@@ -86,6 +101,15 @@ footerLinkTerms = Taq ojqanem
footerLinkCookies = Taq kaxlanwey
requirePasswordCheckbox = Tik'utüx jun ewan tzij richin niqasäx re yakb'äl re'
addPasswordButton = Titz'aqatisäx Ewan Tzij
changePasswordButton = Tijalwachïx
passwordTryAgain = Itzel ri ewan tzij. Tatojtob'ej chik.
// This label is followed by the password needed to download a file
passwordResult = Ewan tzij: { $password }
reportIPInfringement = Tiya' rutzijol ri Ritzelanik Ajna'oj Ichinil
javascriptRequired = K'atzinel JavaScript chi re ri Firefox Send
whyJavascript =
enableJavascript = Titz'ij JavaScript richin nitojtob'ëx chik.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours }r { $minutes }ch
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }ch

View File

@@ -25,6 +25,12 @@ uploadingFileNotification = Upozornit, až bude nahrávání dokončeno.
uploadSuccessConfirmHeader = Připraveno k odeslání
uploadSvgAlt = Nahrát
uploadSuccessTimingHeader = Platnost odkazu na váš soubor vyprší po jeho prvním stažení, nebo po 24 hodinách.
expireInfo = Platnost odkazu na váš soubor vyprší po { $downloadCount } nebo { $timespan }.
downloadCount = { $num ->
[one] jednom stažení
[few] { $num } staženích
*[other] { $num } staženích
}
timespanHours = { $num ->
[one] hodina
[few] hodiny
@@ -37,6 +43,8 @@ deleteFileButton = Smazat soubor
sendAnotherFileLink = Poslat další soubor
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Stáhnout
downloadsFileList = Stažení
timeFileList = Zbývá
downloadFileName = Stáhnout { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Zadejte heslo
@@ -91,6 +99,8 @@ footerLinkTerms = Podmínky
footerLinkCookies = Cookies
requirePasswordCheckbox = Vyžadovat heslo pro stažení tohoto souboru
addPasswordButton = Přidat heslo
changePasswordButton = Změnit
passwordTryAgain = Špatné heslo. Zkuste to znovu.
// This label is followed by the password needed to download a file
passwordResult = Heslo: { $password }
reportIPInfringement = Nahlásit porušení autorských práv

View File

@@ -45,6 +45,8 @@ deleteFileButton = Dileu ffeil
sendAnotherFileLink = Anfon ffeil arall
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Llwytho i lawr
downloadsFileList = Llwythi
timeFileList = Amser
downloadFileName = Llwytho i lawr { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Rhowch Gyfrinair
@@ -99,6 +101,7 @@ footerLinkTerms = Amodau
footerLinkCookies = Cwcis
requirePasswordCheckbox = Gosod angen cyfrinair i lwytho'r ffeil hon i lawr
addPasswordButton = Ychwanegu Cyfrinair
changePasswordButton = Newid
passwordTryAgain = Cyfrinair anghywir. Ceisiwch eto.
// This label is followed by the password needed to download a file
passwordResult = Cyfrinair: { $password }

View File

View File

@@ -41,6 +41,12 @@ deleteFileButton = Datei löschen
sendAnotherFileLink = Eine weitere Datei senden
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Herunterladen
downloadsFileList = Downloads
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Zeit
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } herunterladen
downloadFileSize = ({ $size })
unlockInputLabel = Passwort eingeben
@@ -95,7 +101,15 @@ footerLinkTerms = Nutzungsbedingungen
footerLinkCookies = Cookies
requirePasswordCheckbox = Zum Herunterladen dieser Datei soll ein Passwort erforderlich sein
addPasswordButton = Passwort hinzufügen
changePasswordButton = Ändern
passwordTryAgain = Falsches Passwort. Versuchen Sie es erneut.
// This label is followed by the password needed to download a file
passwordResult = Passwort: { $password }
reportIPInfringement = IP-Verletzung melden
javascriptRequired = Firefox Send benötigt JavaScript
whyJavascript = Warum benötigt Firefox Send JavaScript?
enableJavascript = Bitte akivieren Sie JavaScript und versuchen Sie es erneut.
// 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

View File

@@ -45,6 +45,12 @@ deleteFileButton = Dataju wulašowaś
sendAnotherFileLink = Drugu dataju pósłaś
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Ześěgnuś
downloadsFileList = Ześěgnjenja
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Cas
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } ześěgnuś
downloadFileSize = ({ $size })
unlockInputLabel = Gronidło zapódaś
@@ -99,7 +105,15 @@ footerLinkTerms = Wuměnjenja
footerLinkCookies = Cookieje
requirePasswordCheckbox = Gronidło za ześěgnjenje toś teje dataje pominaś
addPasswordButton = Gronidło pśidaś
changePasswordButton = Změniś
passwordTryAgain = Wopacne gronidło. Wopytajśo hyšći raz.
// This label is followed by the password needed to download a file
passwordResult = Gronidło: { $password }
reportIPInfringement = Pśekśiwjenje IP k wěsći daś
javascriptRequired = Firefox Send JavaScript trjeba
whyJavascript = Cogodla Firefox Send JavaScript trjeba?
enableJavascript = Pšosym zmóžniśo JavaScript a wopytajśo hyšći raz.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours } góź. { $minutes } min.
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes } min.

View File

@@ -25,6 +25,15 @@ uploadingFileNotification = Ειδοποίηση όταν ολοκληρωθεί
uploadSuccessConfirmHeader = Έτοιμο για αποστολή
uploadSvgAlt = Μεταφόρτωση
uploadSuccessTimingHeader = Ο σύνδεσμος του αρχείου σας θα λήξει έπειτα από 1 λήψη ή 24 ώρες.
expireInfo = Ο σύνδεσμος για το αρχείο σας θα λήξει μετά από { $downloadCount } ή { $timespan }.
downloadCount = { $num ->
[one] 1 λήψη
*[other] { $num } λήψεις
}
timespanHours = { $num ->
[one] 1 ώρα
*[other] { $num } ώρες
}
copyUrlFormLabelWithName = Αντιγράψτε και μοιραστείτε τον σύνδεσμο για αποστολή του αρχείου σας : { $filename }
copyUrlFormButton = Αντιγραφή στο πρόχειρο
copiedUrl = Αντιγράφτηκε!
@@ -86,4 +95,7 @@ footerLinkTerms = Όροι
footerLinkCookies = Cookies
requirePasswordCheckbox = Απαίτηση κωδικού πρόσβασης για λήψη του αρχείου
addPasswordButton = Προσθήκη κωδικού πρόσβασης
incorrectPassword = Λάθος κωδικός πρόσβασης. Προσπάθεια ξανά;
passwordTryAgain = Λάθος κωδικός πρόσβασης. Δοκιμάστε ξανά.
// This label is followed by the password needed to download a file
passwordResult = Κωδικός πρόσβασης: { $password }
reportIPInfringement = Αναφορά παραβίασης IP

View File

@@ -41,6 +41,12 @@ deleteFileButton = Delete file
sendAnotherFileLink = Send another file
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Download
downloadsFileList = Downloads
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Time
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Download { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Enter Password
@@ -95,7 +101,15 @@ footerLinkTerms = Terms
footerLinkCookies = Cookies
requirePasswordCheckbox = Require a password to download this file
addPasswordButton = Add password
changePasswordButton = Change
passwordTryAgain = Incorrect password. Try again.
// This label is followed by the password needed to download a file
passwordResult = Password: { $password }
reportIPInfringement = Report IP Infringement
reportIPInfringement = Report IP Infringement
javascriptRequired = Firefox Send requires JavaScript
whyJavascript = Why does Firefox Send require JavaScript?
enableJavascript = Please enable JavaScript and try again.
// 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

View File

@@ -41,6 +41,12 @@ deleteFileButton = Borrar archivo
sendAnotherFileLink = Enviar otro archivo
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Descargar
downloadsFileList = Descargas
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tiempo
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Descargar { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Ingresar contraseña
@@ -95,7 +101,15 @@ footerLinkTerms = Términos
footerLinkCookies = Cookies
requirePasswordCheckbox = Requerir contraseña para descargar este archivo
addPasswordButton = Agregar contraseña
changePasswordButton = Cambiar
passwordTryAgain = Contraseña incorrecta. Intentá nuevamente.
// This label is followed by the password needed to download a file
passwordResult = Contraseña: { $password }
reportIPInfringement = Informar violación de propiedad intelectual
javascriptRequired = Firefox Send requiere JavaScript
whyJavascript = ¿Por qué Firefox Send requiere Java Script?
enableJavascript = Por favor habilite JavaScript y pruebe de nuevo.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = h { $hours } m { $minutes }
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = m { $minutes }

View File

@@ -41,6 +41,12 @@ deleteFileButton = Eliminar archivo
sendAnotherFileLink = Enviar otro archivo
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Descargar
downloadsFileList = Descargas
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tiempo
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Descargar { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Ingresar contraseña
@@ -95,7 +101,15 @@ footerLinkTerms = Términos
footerLinkCookies = Cookies
requirePasswordCheckbox = Requerir una contraseña para descargar este archivo
addPasswordButton = Añadir contraseña
changePasswordButton = Cambiar
passwordTryAgain = Contraseña incorrecta. Vuelve a intentarlo.
// This label is followed by the password needed to download a file
passwordResult = Contraseña: { $password }
reportIPInfringement = Reportar infracción de PI
javascriptRequired = Firefox Send requiere JavaScript.
whyJavascript = ¿Por qué Firefox Send requiere JavaScript?
enableJavascript = Por favor, activa JavaScript y vuelve a intentarlo.
// 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

View File

@@ -41,6 +41,12 @@ deleteFileButton = Eliminar archivo
sendAnotherFileLink = Enviar otro archivo
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Descargar
downloadsFileList = Descargas
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Fecha
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Descargar { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Introducir contraseña
@@ -95,6 +101,8 @@ footerLinkTerms = Términos
footerLinkCookies = Cookies
requirePasswordCheckbox = Requerir una contraseña para descargar este archivo
addPasswordButton = Añadir contraseña
changePasswordButton = Cambiar
passwordTryAgain = Contraseña incorrecta. Inténtelo de nuevo.
// This label is followed by the password needed to download a file
passwordResult = Contraseña: { $password }
reportIPInfringement = Denunciar vulneración de propiedad intelectual

View File

@@ -40,6 +40,12 @@ deleteFileButton = Eliminar archivo
sendAnotherFileLink = Enviar otro archivo
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Descargar
downloadsFileList = Descargas
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Hora
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Descargar ($filename)
downloadFileSize = ({ $size })
unlockInputLabel = Ingresar contraseña
@@ -94,6 +100,15 @@ footerLinkTerms = Términos
footerLinkCookies = Cookies
requirePasswordCheckbox = Se necesita una contraseña para descargar este archivo
addPasswordButton = Agregar contraseña
changePasswordButton = Cambiar
passwordTryAgain = Contraseña incorrecta. Intenta de nuevo.
// This label is followed by the password needed to download a file
passwordResult = Contraseña: { $password }
reportIPInfringement = Denunciar una infracción de PI
javascriptRequired = Firefox Send requiere JavaScript
whyJavascript = ¿Por qué Firefox Send requiere JavaScript?
enableJavascript = Por favor, habilita JavaScript e intenta de nuevo.
// 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

View File

@@ -39,6 +39,12 @@ deleteFileButton = حذف پرونده
sendAnotherFileLink = ارسال پرونده دیگر
// Alternative text used on the download link/button (indicates an action).
downloadAltText = دریافت
downloadsFileList = دریافت‌ها
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = زمان
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = بارگیری { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = گذرواژه را وارد کنید
@@ -93,7 +99,15 @@ footerLinkTerms = شرایط
footerLinkCookies = کوکی‌ها
requirePasswordCheckbox = دریافت این پرونده نیاز به گذرواژه دارد
addPasswordButton = افزودن گذرواژه
changePasswordButton = تغییر
passwordTryAgain = کلمه عبور اشتباه است. مجدد تلاش کنید.
// This label is followed by the password needed to download a file
passwordResult = گذرواژه: { $password }
reportIPInfringement = گزارش تخلف IP
javascriptRequired = Firefox Send نیازمند جاوااسکریپت است
whyJavascript = چراFirefox Send جاوااسکریپت لازم دارد؟
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 } دقیقه

View File

@@ -27,12 +27,12 @@ uploadSvgAlt = Envoyer
uploadSuccessTimingHeader = Le lien vers votre fichier expirera après le premier téléchargement ou au bout de 24 heures.
expireInfo = Le lien vers votre fichier expirera après { $downloadCount } ou { $timespan }.
downloadCount = { $num ->
[one] 1 téléchargement
*[other] { $num } téléchargements
[one] 1 téléchargement
*[other] { $num } téléchargements
}
timespanHours = { $num ->
[one] 1 heure
*[other] { $num } heures
[one] 1 heure
*[other] { $num } heures
}
copyUrlFormLabelWithName = Copiez et partagez le lien pour envoyer votre fichier : { $filename }
copyUrlFormButton = Copier dans le presse-papiers
@@ -41,6 +41,12 @@ deleteFileButton = Supprimer le fichier
sendAnotherFileLink = Envoyer un autre fichier
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Télécharger
downloadsFileList = Téléchargements
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Heure
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Télécharger { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Saisissez le mot de passe
@@ -95,7 +101,15 @@ footerLinkTerms = Conditions dutilisation
footerLinkCookies = Cookies
requirePasswordCheckbox = Exiger un mot de passe pour télécharger ce fichier
addPasswordButton = Ajouter un mot de passe
changePasswordButton = Changer
passwordTryAgain = Mot de passe incorrect. Veuillez réessayer.
// This label is followed by the password needed to download a file
passwordResult = Mot de passe : { $password }
reportIPInfringement = Signaler une violation de la p.i.
javascriptRequired = Firefox Send nécessite JavaScript
whyJavascript = Pourquoi Firefox Send nécessite-t-il JavaScript ?
enableJavascript = Veuillez activer JavaScript puis réessayer.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours } h { $minutes } min
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes } min

View File

@@ -41,6 +41,12 @@ deleteFileButton = Bestân fuortsmite
sendAnotherFileLink = Noch in bestân ferstjoere
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Downloade
downloadsFileList = Downloads
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tiid
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } downloade
downloadFileSize = ({ $size })
unlockInputLabel = Wachtwurd ynfiere
@@ -95,6 +101,15 @@ footerLinkTerms = Betingsten
footerLinkCookies = Cookies
requirePasswordCheckbox = Om dit bestân te downloaden is in wachtwurd fereaske
addPasswordButton = Wachtwurd tafoegje
changePasswordButton = Wizigje
passwordTryAgain = Net krekt wachtwurd. Probearje it opnij.
// This label is followed by the password needed to download a file
passwordResult = Wachtwurd: { $password }
reportIPInfringement = IP-ynbrek melde
javascriptRequired = Firefox Send fereasket JavaScript.
whyJavascript = Werom hat Firefox Send JavaScript nedich?
enableJavascript = Skeakelje JavaScript yn en probearje nochris.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours }o { $minutes }m
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }m

View File

@@ -45,6 +45,12 @@ deleteFileButton = Dataju zhašeć
sendAnotherFileLink = Druhu dataju pósłać
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Sćahnyć
downloadsFileList = Sćehnjenja
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Čas
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } sćahnyć
downloadFileSize = ({ $size })
unlockInputLabel = Hesło zapodać
@@ -99,7 +105,15 @@ footerLinkTerms = Wuměnjenja
footerLinkCookies = Placki
requirePasswordCheckbox = Žadajće sej hesło za sćehnjenje tuteje dataje
addPasswordButton = Hesło přidać
changePasswordButton = Změnić
passwordTryAgain = Wopačne hesło. Prošu spytajće hišće raz.
// This label is followed by the password needed to download a file
passwordResult = Hesło: { $password }
reportIPInfringement = Zranjenje IP zdźělić
javascriptRequired = Firefox Send JavaScript trjeba
whyJavascript = Čehodla Firefox Send JavaScript trjeba?
enableJavascript = Prošu zmóžńće JavaScript a spytajće hišće raz.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours } hodź. { $minutes } mjeń.
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes } mjeń.

View File

@@ -41,6 +41,12 @@ deleteFileButton = Fájl törlése
sendAnotherFileLink = Még egy fájl küldése
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Letöltés
downloadsFileList = Letöltések
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Idő
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } letöltése
downloadFileSize = ({ $size })
unlockInputLabel = Adja meg a jelszót
@@ -95,7 +101,15 @@ footerLinkTerms = Feltételek
footerLinkCookies = Sütik
requirePasswordCheckbox = Jelszó megkövetelése a fájl letöltéséhez
addPasswordButton = Jelszó hozzáadása
changePasswordButton = Módosítás
passwordTryAgain = Helytelen jelszó. Próbálja meg újra.
// This label is followed by the password needed to download a file
passwordResult = Jelszó: { $password }
reportIPInfringement = Szellemi tulajdon megsértésének bejelentése
javascriptRequired = A Firefox Sendhez JavaScript szükséges
whyJavascript = Miért van szükség JavaScriptre a Firefox Sendhez?
enableJavascript = Kérjük engedélyezze a JavaScriptet, majd próbálkozzon újra.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours }ó { $minutes }p
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }p

115
public/locales/ia/send.ftl Normal file
View File

@@ -0,0 +1,115 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = un experimento web
siteFeedback = Reaction
uploadPageHeader = Compartimento de files private e cryptate
uploadPageExplainer = Invia files per un ligamine secur, private e cryptate que automaticamente expira pro assecurar que tu datos non resta in linea per sempre.
uploadPageLearnMore = Saper plus
uploadPageDropMessage = Depone ci tu file pro comenciar a lo cargar
uploadPageSizeMessage = Pro evitar problemas, mantene tu file sub 1GB
uploadPageBrowseButton = Elige un file sur tu computator
uploadPageBrowseButton1 = Elige un file a cargar
uploadPageMultipleFilesAlert = Le cargamento de plure files o de un plica actualmente non es supportate.
uploadPageBrowseButtonTitle = Cargar le file
uploadingPageProgress = Cargamento de { $filename } ({ $size })
importingFile = Importation…
verifyingFile = Verifica…
encryptingFile = Cryptation...
decryptingFile = Decryptation…
notifyUploadDone = Cargamento terminate
uploadingPageMessage = Post cargate tu file, tu potera definir le optiones de expiration.
uploadingPageCancel = Cancellar le cargamento
uploadCancelNotification = Cargamento cancellate.
uploadingPageLargeFileMessage = Iste file es grande e pote prender multe tempore pro le cargamento. Patientia!
uploadingFileNotification = Notificar me quando le cargamento es complete.
uploadSuccessConfirmHeader = Preste a inviar
uploadSvgAlt = Cargamento
uploadSuccessTimingHeader = Le ligamine a tu file expirara post un discargamento o in 24 horas.
expireInfo = Le ligamine a tu file expirara post { $downloadCount } o { $timespan }
downloadCount = { $num ->
[one] discargamento
*[other] discargamentos
}
timespanHours = { $num ->
[one] hora
*[other] horas
}
copyUrlFormLabelWithName = Copia e comparti le ligamine pro inviar tu file: { $filename }
copyUrlFormButton = Copiar al area de transferentia
copiedUrl = Copiate!
deleteFileButton = Deler le file
sendAnotherFileLink = Inviar un altere file
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Discargar
downloadsFileList = Discargamentos
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tempore
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Discargar { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Insere le contrasigno
unlockInputPlaceholder = Contrasigno
unlockButtonLabel = Disblocar
downloadFileTitle = Discargar le file cryptate
// Firefox Send is a brand name and should not be localized.
downloadMessage = Tu amico te invia un file per Firefox Send, un servicio que te permitte de compartir files per un ligamine secur, private e cryptate, que expira automaticamente pro te assecurar que tu datos non resta online per sempre.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Discargar
downloadNotification = Tu discargamento es completate.
downloadFinish = Discargamento completate
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
fileSizeProgress = ({ $partialSize } de { $totalSize })
// Firefox Send is a brand name and should not be localized.
sendYourFilesLink = Proba Firefox Send
downloadingPageProgress = Discargamento de { $filename } ({ $size })
downloadingPageMessage = Per favor lassa iste scheda aperte durante que nos prende tu file e lo decifra.
errorAltText = Error de cargamento.
errorPageHeader = Un error occurreva!
errorPageMessage = Un error occurreva durante le cargamento del file.
errorPageLink = Inviar un altere file
fileTooBig = Iste file es troppo grande pro lo cargar. Illo debe ser inferior a { $size }.
linkExpiredAlt = Ligamine expirate
expiredPageHeader = Iste ligamine expirava o illo non existeva jammais!
notSupportedHeader = Tu navigator non es supportate
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Infelicemente iste navigator non supporta le nove technologias web que move Firefox Send. Tu besonia de probar un altere navigator. Nos recommenda Firefox!
notSupportedLink = Perque iste navigator non es supportate?
notSupportedOutdatedDetail = Infelicemente iste version de Firefox non supporta le nove technologias web que move Firefox Send. Tu besonia de actualisar tu navigator.
updateFirefox = Actualisar Firefox
downloadFirefoxButtonSub = Discargamento gratuite
uploadedFile = File
copyFileList = Copiar le URL
// expiryFileList is used as a column header
expiryFileList = Expira in
deleteFileList = Deler
nevermindButton = No, gratias
legalHeader = Terminos & confidentialitate
legalNoticeTestPilot = Firefox Send es actualmente un Experimento pilota, e subjecte a <a>Terminos de servicio</a> e <a>Notitia de confidentialitate</a> de Experimento pilota. Tu pote saper plus re iste experimento e su recolta de datos <a>hic</a>.
legalNoticeMozilla = Le uso del sito web de Firefox Send es anque subjecte a <a>Notitia de confidentialitate de sito web</a> e <a>Terminos de servicio sito web</a>.
deletePopupText = Deler iste file?
deletePopupYes =
deletePopupCancel = Cancellar
deleteButtonHover = Deler
copyUrlHover = Copiar le URL
footerLinkLegal = Legal
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Re Test Pilot
footerLinkPrivacy = Confidentialitate
footerLinkTerms = Terminos
footerLinkCookies = Cookies
requirePasswordCheckbox = Requirer un contrasigno pro discargar iste file
addPasswordButton = Adder contrasigno
changePasswordButton = Cambiar
passwordTryAgain = Contrasigno incorrecte. Retenta.
// This label is followed by the password needed to download a file
passwordResult = Contrasigno: { $password }
reportIPInfringement = Reportar un violation de proprietate intellectual
javascriptRequired = Firefox Send require JavaScript
whyJavascript = Proque Firefox Send require JavaScript?
enableJavascript = Por favor activa JavaScript e tenta novemente.
// 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

View File

@@ -26,6 +26,13 @@ uploadingFileNotification = Beri tahu saya ketika unggahan telah selesai.
uploadSuccessConfirmHeader = Siap untuk Dikirim
uploadSvgAlt = Unggah
uploadSuccessTimingHeader = Tautan ke berkas Anda akan berakhir setelah 1 unduhan atau dalam 24 jam.
expireInfo = Tautan ke berkas Anda akan kedaluwarsa setelah { $downloadCount } atau { $timespan }.
downloadCount = { $num ->
*[other] { $number } unduhan
}
timespanHours = { $num ->
*[other] { $number } jam
}
copyUrlFormLabelWithName = Salin dan bagikan tautan untuk mengirim berkas Anda: { $filename }
copyUrlFormButton = Salin ke papan klip
copiedUrl = Tersalin!
@@ -37,6 +44,7 @@ downloadFileName = Unduh { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Masukkan Sandi
unlockInputPlaceholder = Sandi
unlockButtonLabel = Buka
downloadFileTitle = Unduh Berkas Terenkripsi
// Firefox Send is a brand name and should not be localized.
downloadMessage = Teman Anda mengirimkan berkas dengan Firefox Send, layanan yang memungkinkan Anda berbagi berkas dengan tautan yang aman, pribadi, dan terenkripsi yang secara otomatis berakhir untuk memastikan berkas Anda tidak daring selamanya.
@@ -86,4 +94,7 @@ footerLinkTerms = Ketentuan
footerLinkCookies = Kuki
requirePasswordCheckbox = Membutuhkan sandi untuk mengunduh berkas ini
addPasswordButton = Tambahkan Sandi
incorrectPassword = Sandi salah. Coba lagi?
passwordTryAgain = Sandi salah. Silakan coba lagi.
// This label is followed by the password needed to download a file
passwordResult = Sandi: { $password }
reportIPInfringement = Laporkan Pelanggaran IP

View File

@@ -41,6 +41,8 @@ deleteFileButton = Elimina file
sendAnotherFileLink = Invia un altro file
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Scarica
downloadsFileList = Download
timeFileList = Scadenza
downloadFileName = Scarica { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Inserire la password
@@ -95,6 +97,7 @@ footerLinkTerms = Condizioni di utilizzo
footerLinkCookies = Cookie
requirePasswordCheckbox = Richiedi una password per poter scaricare questo file
addPasswordButton = Aggiungi password
changePasswordButton = Modifica
passwordTryAgain = Password errata, riprovare.
// This label is followed by the password needed to download a file
passwordResult = Password: { $password }

View File

@@ -39,6 +39,12 @@ deleteFileButton = ファイルを削除
sendAnotherFileLink = 他のファイルを送信
// Alternative text used on the download link/button (indicates an action).
downloadAltText = ダウンロード
downloadsFileList = ダウンロード
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = 時間
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } をダウンロード
downloadFileSize = ({ $size })
unlockInputLabel = パスワードを入力
@@ -93,7 +99,15 @@ footerLinkTerms = 利用規約
footerLinkCookies = Cookie
requirePasswordCheckbox = このファイルをダウンロードするにはパスワードが必要です
addPasswordButton = パスワードを追加
changePasswordButton = 変更
passwordTryAgain = パスワードが正しくありません。再度入力してください。
// This label is followed by the password needed to download a file
passwordResult = パスワード: { $password }
reportIPInfringement = 知的財産侵害報告
javascriptRequired = Firefox Send を使うには JavaScript が必要です
whyJavascript = Firefox Send が JavaScript を必要とする理由
enableJavascript = JavaScript を有効にして再度試してください。
// 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 } 分

View File

@@ -39,6 +39,8 @@ deleteFileButton = ფაილის წაშლა
sendAnotherFileLink = სხვა ფაილის გაგზავნა
// Alternative text used on the download link/button (indicates an action).
downloadAltText = ჩამოტვირთვა
downloadsFileList = ჩამოტვირთვები
timeFileList = დრო
downloadFileName = { $filename } ჩამოტვირთვა
downloadFileSize = ({ $size })
unlockInputLabel = შეიყვანეთ პაროლი
@@ -93,6 +95,7 @@ footerLinkTerms = პირობები
footerLinkCookies = ფუნთუშები
requirePasswordCheckbox = პაროლის მოთხოვნა, ფაილის ჩამოტვირთვისას
addPasswordButton = პაროლის დამატება
changePasswordButton = შეცვლა
passwordTryAgain = პაროლი არასწორია. სცადეთ ხელახლა.
// This label is followed by the password needed to download a file
passwordResult = პაროლი: { $password }

View File

@@ -25,6 +25,15 @@ uploadingFileNotification = Lɣu-yid ticki yemmed usali.
uploadSuccessConfirmHeader = Ihegga i walluy
uploadSvgAlt = Sali
uploadSuccessTimingHeader = Aseɣwen ar ufaylu-ik ad yemmet ticki yuder-d neɣ deffir n 24 n yisragen.
expireInfo = Aseɣwen icudden ar ufaylu-inek ad yemmet send { $downloadCount } naɣ { $timespan }.
downloadCount = { $num ->
[one] 1 usider
*[other] { $num } isidar
}
timespanHours = { $num ->
[one] 1 usrag
*[other] { $num } isragen
}
copyUrlFormLabelWithName = Nɣel sakin Bḍu aseɣwen akken ad tazneḍ afaylu-ik: { $filename }
copyUrlFormButton = Sers ɣef afus
copiedUrl = Yenɣel!
@@ -87,3 +96,6 @@ footerLinkCookies = Inagan n tuqqna
requirePasswordCheckbox = YEsra awal uffir akken ad isider afaylu-agi
addPasswordButton = rnu awal uffir
passwordTryAgain = Yir awal uffir. Ɛreḍ tikelt nniḍen.
// This label is followed by the password needed to download a file
passwordResult = Awal uffir: { $password }
reportIPInfringement = Neqqes akukel n IP

View File

@@ -26,6 +26,9 @@ uploadingFileNotification = 업로드가 완료되면 알림을 표시해 주세
uploadSuccessConfirmHeader = 보낼 준비 완료
uploadSvgAlt = 업로드
uploadSuccessTimingHeader = 이 파일의 링크는 한 번의 다운로드 후 또는 24시간이 지난 뒤에 만료됩니다.
expireInfo = 이 파일의 링크는 { $downloadCount }나 { $timespan } 후에 만료됩니다.
downloadCount = 1 다운로드
timespanHours = 1 시간
copyUrlFormLabelWithName = 파일을 보내기 위해 이 링크를 복사하고 공유하세요: { $filename }
copyUrlFormButton = 클립보드에 복사
copiedUrl = 복사 완료!
@@ -90,3 +93,4 @@ addPasswordButton = 비밀번호 추가
passwordTryAgain = 비밀번호가 맞지 않습니다. 다시 시도해 주세요.
// This label is followed by the password needed to download a file
passwordResult = 비밀번호: { $password }
reportIPInfringement = 지적 재산권 침해 신고

View File

@@ -39,6 +39,12 @@ deleteFileButton = Buang Fail
sendAnotherFileLink = Hantar fail lain
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Muat turun
downloadsFileList = Muat turun
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Masa
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Muat turun { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Masukkan Kata Laluan
@@ -93,7 +99,15 @@ footerLinkTerms = Terma
footerLinkCookies = Kuki
requirePasswordCheckbox = Perlu kata laluan untuk memuat turun fail ini
addPasswordButton = Tambah Kata laluan
changePasswordButton = Tukar
passwordTryAgain = Kata laluan tidak betul. Cuba lagi.
// This label is followed by the password needed to download a file
passwordResult = Kata laluan: { $password }
reportIPInfringement = Lapor Pencerobohan IP
javascriptRequired = Firefox Send perlukan JavaScript
whyJavascript = Kenapa Firefox Send perlukan JavaScript?
enableJavascript = Sila dayakan JavaScript dan cuba lagi.
// 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

View File

@@ -41,6 +41,12 @@ deleteFileButton = Slett fil
sendAnotherFileLink = Send en annen fil
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Last ned
downloadsFileList = Nedlastinger
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tid
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Last ned { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Skriv inn passord
@@ -95,7 +101,15 @@ footerLinkTerms = Vilkår
footerLinkCookies = Infokapsler
requirePasswordCheckbox = Krever et passord for å laste ned denne filen
addPasswordButton = Legg til passord
changePasswordButton = Endre
passwordTryAgain = Feil passord. Prøv igjen.
// This label is followed by the password needed to download a file
passwordResult = Passord: { $password }
reportIPInfringement = Rapporter brudd på åndsverk
javascriptRequired = Firefox Send krever JavaScript.
whyJavascript = Hvorfor krever Firefox Send JavaScript?
enableJavascript = Slå på JavaScript og prøv igjen.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours }t { $minutes }m
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }m

View File

@@ -41,6 +41,12 @@ deleteFileButton = Bestand verwijderen
sendAnotherFileLink = Nog een bestand verzenden
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Downloaden
downloadsFileList = Downloads
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tijd
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } downloaden
downloadFileSize = ({ $size })
unlockInputLabel = Voer wachtwoord in
@@ -95,7 +101,15 @@ footerLinkTerms = Voorwaarden
footerLinkCookies = Cookies
requirePasswordCheckbox = Een wachtwoord vereisen om dit bestand te downloaden
addPasswordButton = Wachtwoord toevoegen
changePasswordButton = Wijzigen
passwordTryAgain = Onjuist wachtwoord. Probeer het opnieuw.
// This label is followed by the password needed to download a file
passwordResult = Wachtwoord: { $password }
reportIPInfringement = IE-inbreuk melden
javascriptRequired = Firefox Send vereist JavaScript
whyJavascript = Waarom vereist Firefox Send JavaScript?
enableJavascript = Schakel JavaScript in en probeer het opnieuw.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours }u { $minutes }m
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }m

View File

@@ -41,6 +41,12 @@ deleteFileButton = Slett fil
sendAnotherFileLink = Send ei anna fil
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Last ned
downloadsFileList = Nedlastingar
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tid
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Last ned { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Skriv inn passord
@@ -95,6 +101,7 @@ footerLinkTerms = Vilkår
footerLinkCookies = Infokapslar
requirePasswordCheckbox = Krev eit passord for å laste ned denne fila
addPasswordButton = Legg til passord
changePasswordButton = Endre
passwordTryAgain = Feil passord. Prøv på nytt.
// This label is followed by the password needed to download a file
passwordResult = Passord: { $password }

View File

@@ -41,6 +41,12 @@ deleteFileButton = Excluir arquivo
sendAnotherFileLink = Enviar outro arquivo
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Baixar
downloadsFileList = Downloads
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Hora
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Baixar { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Insira a senha
@@ -95,7 +101,15 @@ footerLinkTerms = Termos
footerLinkCookies = Cookies
requirePasswordCheckbox = Para baixar esse arquivo é necessário uma senha
addPasswordButton = Adicionar senha
changePasswordButton = Alterar
passwordTryAgain = Senha incorreta. Tente novamente.
// This label is followed by the password needed to download a file
passwordResult = Senha: { $password }
reportIPInfringement = Reportar violação de IP
javascriptRequired = O Firefox Send requer JavaScript
whyJavascript = Por que o Firefox Send precisa do JavaScript?
enableJavascript = Habilite o JavaScript e tente novamente.
// 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

View File

@@ -38,6 +38,12 @@ deleteFileButton = Apagar ficheiro
sendAnotherFileLink = Enviar outro ficheiro
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Transferir
downloadsFileList = Transferências
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tempo
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Transferir { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Digitar palavra-passe
@@ -92,7 +98,15 @@ footerLinkTerms = Termos
footerLinkCookies = Cookies
requirePasswordCheckbox = Requerer uma palavra-passe para transferir este ficheiro
addPasswordButton = Adicionar palavra-passe
changePasswordButton = Alterar
passwordTryAgain = Palavra-passe incorreta. Tente novamente.
// This label is followed by the password needed to download a file
passwordResult = Palavra-passe: { $password }
reportIPInfringement = Reportar violação de PI
javascriptRequired = O Firefox Send requer JavaScript
whyJavascript = Porque é que o Firefox Send requer JavaScript?
enableJavascript = Por favor ative o JavaScript e tente novamente.
// 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

View File

@@ -3,8 +3,10 @@ title = Firefox Send
siteSubtitle = un experiment web
siteFeedback = Feedback
uploadPageHeader = Partajare de fișiere privată și criptată
uploadPageExplainer = Trimite fișiere printr-un link sigur, privat și criptat care expiră automat pentru ca informațiile să rămână în siguranță.
uploadPageLearnMore = Află mai multe
uploadPageDropMessage = Aruncă fișierul aici pentru a începe încărcarea.
uploadPageSizeMessage = Pentru a lucra mai ușor, recomandăm să păstrezi fișierul sub 1GB
uploadPageBrowseButton = Alege un fișier din calculator.
uploadPageBrowseButton1 = Selectează un fișier pentru încărcare
uploadPageMultipleFilesAlert = Încărcarea mai multor fișiere deodată sau a dosarelor nu este suportată.
@@ -23,18 +25,34 @@ uploadingFileNotification = Notifică-mă când încărcarea este încheiată.
uploadSuccessConfirmHeader = Pregătit pentru trimitere
uploadSvgAlt = Încarcă
uploadSuccessTimingHeader = Linkul către fișierul tău va expira după 1 descărcare sau în 24 de ore.
expireInfo = Linkul la fișier va expira după { $downloadCount } sau { $timespan }.
downloadCount =
timespanHours = { $num ->
[one] 1 oră
[few] ore
*[other] de ore
}
copyUrlFormLabelWithName = Copiază și împărtășește linkul de la fișierul de trimis: { $filename }
copyUrlFormButton = Copiază în clipboard
copiedUrl = Copiat!
deleteFileButton = Șterge fișierul
sendAnotherFileLink = Trimite un alt fișier
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Descarcă
downloadsFileList = Descărcări
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Timp
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Descarcă { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Introdu parola
unlockInputPlaceholder = Parolă
unlockButtonLabel = Deblochează
downloadFileTitle = Descarcă fișierul criptat
// Firefox Send is a brand name and should not be localized.
downloadMessage = Un prieten îți trimite un fișier prin Firefox Send, un serviciu care îți permite să împărtășești un fișier printr-un link sigur, privat și criptat care expiră automat pentru a păstra informațiile tale online doar temporar.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Descarcă
downloadNotification = Descărcarea s-a încheiat.
@@ -44,6 +62,7 @@ fileSizeProgress = ({ $partialSize } din { $totalSize })
// Firefox Send is a brand name and should not be localized.
sendYourFilesLink = Încearcă Firefox Send
downloadingPageProgress = Se descarcă { $filename } ({ $size })
downloadingPageMessage = Te rugăm să păstrezi această file deschisă în timp ce preluăm fișierul și îl decriptăm.
errorAltText = Eroare la încărcare
errorPageHeader = Ceva a mers prost!
errorPageMessage = A apărut o eroare la încărcarea fișierului.
@@ -52,7 +71,10 @@ fileTooBig = Acest fișier este prea mare. Trebuie să fie sub { $size }.
linkExpiredAlt = Link expirat
expiredPageHeader = Acest link a expirat sau nu a existat de la bun început!
notSupportedHeader = Browserul tău nu este suportat.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Din păcate acest browser nu suportă tehnologii web precum Firefox Send. Trebuie să încerci alt browser. Îți recomandăm Firefox!
notSupportedLink = De ce browserul meu nu este suportat?
notSupportedOutdatedDetail = Din păcate această versiune de Firefox nu suportă tehnologiile web din spatele Firefox Sent. Îți recomandăm să actualizezi browserul.
updateFirefox = Actualizează Firefox
downloadFirefoxButtonSub = Descărcare gratuită
uploadedFile = Fișier
@@ -62,6 +84,8 @@ expiryFileList = Expiră în
deleteFileList = Șterge
nevermindButton = Uită
legalHeader = Termeni de utilizare și politica de confidențialitate
legalNoticeTestPilot = Firefox Send este momentan un experiment Test Pilot și supus <a>Termenilor de utilizare</a> Test Pilot și a <a>Politicii de confidențialitate</a>. Poți afla mai multe despre acest experiment <a>aici</a>.
legalNoticeMozilla = Folosirea site-ului Firefox Send mai este supusă <a>Politicii de confidențialitate pentru site-uri web</a> și a <a>Termenilor de folosire a site-urilor web</a>.
deletePopupText = Ștergi aceast fișier?
deletePopupYes = Da
deletePopupCancel = Renunță
@@ -73,4 +97,10 @@ footerLinkAbout = Despre Test Pilot
footerLinkPrivacy = Confidențialitate
footerLinkTerms = Termeni
footerLinkCookies = Cookie-uri
requirePasswordCheckbox = Este necesară o parolă pentru a descărca acest fișier
addPasswordButton = Adaugă parolă
changePasswordButton = Modifică
passwordTryAgain = Parola este incorectă. Încearcă din nou.
// This label is followed by the password needed to download a file
passwordResult = Parola: { $password }
reportIPInfringement = Raportează încălcarea proprietății intelectuale

View File

@@ -43,6 +43,12 @@ deleteFileButton = Удалить файл
sendAnotherFileLink = Отправить другой файл
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Загрузить
downloadsFileList = Загрузки
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Время
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Загрузить { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Введите пароль
@@ -97,6 +103,7 @@ footerLinkTerms = Условия
footerLinkCookies = Куки
requirePasswordCheckbox = Требовать пароль для загрузки этого файла
addPasswordButton = Добавить пароль
changePasswordButton = Изменить
passwordTryAgain = Неверный пароль. Попробуйте снова.
// This label is followed by the password needed to download a file
passwordResult = Пароль: { $password }

View File

@@ -25,6 +25,17 @@ uploadingFileNotification = Upozorniť ma na ukončenie nahrávania
uploadSuccessConfirmHeader = Pripravené na odoslanie
uploadSvgAlt = Nahrať
uploadSuccessTimingHeader = Platnosť odkazu vyprší po 1 prevzatí alebo po uplynutí 24 hodín.
expireInfo = Platnosť odkazu na váš súbor vyprší po { $downloadCount } alebo po { $timespan }.
downloadCount = { $num ->
[one] 1 prevzatí
[few] { $num } prevzatiach
*[other] { $num } prevzatiach
}
timespanHours = { $num ->
[one] 1 hodine
[few] { $num } hodinách
*[other] { $num } hodinách
}
copyUrlFormLabelWithName = Skopírovaním a zdieľaním odkazu odošlete váš súbor: { $filename }
copyUrlFormButton = Kopírovať do schránky
copiedUrl = Skopírované!
@@ -32,6 +43,12 @@ deleteFileButton = Odstrániť súbor
sendAnotherFileLink = Odoslať ďalší súbor
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Prevziať
downloadsFileList = Prevzatí
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Zostáva
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Prevziať { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Zadajte heslo
@@ -86,6 +103,15 @@ footerLinkTerms = Podmienky používania
footerLinkCookies = Cookies
requirePasswordCheckbox = Pri preberaní súboru vyžadovať heslo
addPasswordButton = Pridať heslo
changePasswordButton = Zmeniť
passwordTryAgain = Nesprávne heslo. Skúste to znova.
// This label is followed by the password needed to download a file
passwordResult = Heslo: { $password }
reportIPInfringement = Nahlásiť porušenie práv duševného vlastníctva
javascriptRequired = Firefox Send vyžaduje JavaScript
whyJavascript = Prečo Firefox Send vyžaduje JavaScript?
enableJavascript = Prosím, povoľte JavaScript a skúste to znova.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours } hod. { $minutes } min.
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes } min.

View File

@@ -45,6 +45,12 @@ deleteFileButton = Izbriši datoteko
sendAnotherFileLink = Pošlji drugo datoteko
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Prenesi
downloadsFileList = Prenosi
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Čas
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Prenesi { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Vnesite geslo
@@ -99,7 +105,15 @@ footerLinkTerms = Pogoji
footerLinkCookies = Piškotki
requirePasswordCheckbox = Zahtevaj geslo za prenos te datoteke
addPasswordButton = Dodaj geslo
changePasswordButton = Spremeni
passwordTryAgain = Napačno geslo. Poskusite znova.
// This label is followed by the password needed to download a file
passwordResult = Geslo: { $password }
reportIPInfringement = Prijavite kršitev naslova IP
javascriptRequired = Firefox Send zahteva JavaScript
whyJavascript = Zakaj Firefox Send zahteva JavaScript?
enableJavascript = Omogočite JavaScript in poskusite znova.
// 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

101
public/locales/sq/send.ftl Normal file
View File

@@ -0,0 +1,101 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = eksperiment web
siteFeedback = Përshtypje
uploadPageHeader = Shkëmbim Privat, i Fshehtëzuar, Kartelash
uploadPageExplainer = Dërgoni kartela përmes një lidhjeje të parrezik, private dhe të fshehtëzuar, që skadon automatikisht për të garantuar që gjërat tuaja nuk mbesin në internet përgjithmonë.
uploadPageLearnMore = Mësoni më tepër
uploadPageDropMessage = Që të fillojë ngarkimi, hidheni kartelën tuaj këtu
uploadPageSizeMessage = Për ecurinë më të qëndrueshme, më e mira është ti mbani kartelat tuaja nën 1GB
uploadPageBrowseButton = Përzgjidhni një kartelë nga kompjuteri juaj
uploadPageBrowseButton1 = Përzgjidhni një kartelë për ngarkim
uploadPageMultipleFilesAlert = Ngarkimi i shumë kartelave njëherësh, ose i një dosjeje, hëpërhë nuk mbulohen.
uploadPageBrowseButtonTitle = Ngarkoje kartelën
uploadingPageProgress = Po ngarkohet { $filename } ({ $size })
importingFile = Po importohet…
verifyingFile = Po verifikohet…
encryptingFile = Po fshehtëzohet…
decryptingFile = Po shfshehtëzohet…
notifyUploadDone = Ngarkimi juaj përfundoi.
uploadingPageMessage = Do të jeni në gjendje të caktoni parametra skadimi sapo kartela juaj të jetë ngarkuar.
uploadingPageCancel = Anuloje ngarkimin
uploadCancelNotification = Ngarkimi juaj u anulua.
uploadingPageLargeFileMessage = Kjo kartelë është e madhe dhe mund të dojë ca kohë të ngarkohet. Rrini këtu!
uploadingFileNotification = Njoftomë kur të jetë plotësuar ngarkimi .
uploadSuccessConfirmHeader = Gati për Dërgim
uploadSvgAlt = Ngarkoje
uploadSuccessTimingHeader = Lidhja për te kartela juaj do të skadojë pas 1 shkarkimi ose pas 24 orësh.
expireInfo = Lidhja për te kartela juaj do të skadojë pas { $downloadCount } ose { $timespan }.
downloadCount = { $num ->
[one] 1 shkarkimi
*[other] { $num } shkarkimesh
}
timespanHours = { $num ->
[one] 1 ore
*[other] { $num } orësh
}
copyUrlFormLabelWithName = Kopjojeni dhe jepuani të tjerëve lidhje që të dërgoni kartelën tuaj: { $filename }
copyUrlFormButton = Kopjoje te e papastra
copiedUrl = U kopjua!
deleteFileButton = Fshije kartelën
sendAnotherFileLink = Dërgoni një kartelë tjetër
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Shkarkoje
downloadFileName = Shkarkoje { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Jepni Fjalëkalimin
unlockInputPlaceholder = Fjalëkalim
unlockButtonLabel = Zhbllokoje
downloadFileTitle = Shkarko Kartelën e Fshehtëzuar
// Firefox Send is a brand name and should not be localized.
downloadMessage = Shoku juaj po ju dërgon një kartelë me Firefox Send, një shërbim që ju lejon të shkëmbeni kartela përmes një lidhjeje të parrezik, private, dhe të fshehtëzuar, që skadon automatikisht, për të garantuar që gjërat tuaja të mos mbeten në internet përgjithmonë.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Shkarkoje
downloadNotification = Shkarkimi juaj u plotësua.
downloadFinish = Shkarkim i Plotësuar
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
fileSizeProgress = ({ $partialSize } nga { $totalSize }) gjithsej
// Firefox Send is a brand name and should not be localized.
sendYourFilesLink = Provoni Firefox Send
downloadingPageProgress = Po shkarkohet { $filename } ({ $size })
downloadingPageMessage = Ju lutemi, lëreni hapur këtë skedë ndërkohë që ne sjellim dhe shfshehtëzojmë kartelën tuaj.
errorAltText = Gabim ngarkimi
errorPageHeader = Diç shkoi ters!
errorPageMessage = Pati një gabim gjatë ngarkimit të kartelës.
errorPageLink = Dërgoni një kartelë tjetër
fileTooBig = Kjo kartelë është shumë e madhe për ngarkim. Do të duhej të ishte më pak se { $size }.
linkExpiredAlt = Lidhja skadoi
expiredPageHeader = Kjo lidhje ka skaduar ose ska ekzistuar kurrë!
notSupportedHeader = Shfletuesi juaj nuk mbulohet.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Mjerisht, ky shfletues nuk mbulon teknologjinë web mbi të cilën bazohet Firefox Send.Do tju duhet të provoni një shfletues tjetër. Ju këshillojmë Firefox-in!
notSupportedLink = Pse nuk mbulohet ky shfletues?
notSupportedOutdatedDetail = Mjerisht, ky version i Firefox-it nuk e mbulon teknologjinë web mbi të cilën bazohet Firefox Send. Do tju duhet të përditësoni shfletuesin tuaj.
updateFirefox = Përditësojeni Firefox-in
downloadFirefoxButtonSub = Shkarkim Falas
uploadedFile = Kartelë
copyFileList = Kopjo URL-në
// expiryFileList is used as a column header
expiryFileList = Skadon Më
deleteFileList = Fshije
nevermindButton = Sprish punë
legalHeader = Kushte & Privatësi
legalNoticeTestPilot = Firefox Send është një eksperiment Pilot Testesh dhe subjekt i <a>Kushteve të Shërbimit</a> dhe <a>Shënim Privacësie</a> për Pilot Testesh. <a>Këtu</a> mund të mësoni më tepër mbi këtë eksperiment dhe grumbullimit të të dhënave që ai kryen.
legalNoticeMozilla = Përdorimi i sajtit Firefox Send është gjithashtu subjekt i <a>Shënimit Mbi Privatësi Sajtesh</a> të Mozilla-s dhe <a>Kushteve të Përdorimit të Sajtit</a>.
deletePopupText = Të fshihet kjo kartelë?
deletePopupYes = Po
deletePopupCancel = Anuloje
deleteButtonHover = Fshije
copyUrlHover = Kopjoji URL-në
footerLinkLegal = Ligjore
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Rreth Pilot Testesh
footerLinkPrivacy = Privatësi
footerLinkTerms = Kushte
footerLinkCookies = Cookies
requirePasswordCheckbox = Kërko doemos një fjalëkalim për shkarkim të kësaj kartele
addPasswordButton = Shtoni fjalëkalim
passwordTryAgain = Fjalëkalim i pasaktë. Riprovoni.
// This label is followed by the password needed to download a file
passwordResult = Fjalëkalim: { $password }
reportIPInfringement = Raportoni Cenim IP-je

View File

@@ -43,6 +43,12 @@ deleteFileButton = Обриши датотеку
sendAnotherFileLink = Пошаљи другу датотеку
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Преузми
downloadsFileList = Преузимања
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Време
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Преузимање датотеке { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Унесите лозинку
@@ -97,7 +103,15 @@ footerLinkTerms = Услови
footerLinkCookies = Колачићи
requirePasswordCheckbox = Захтевај лозинку да би преузео ову датотеку
addPasswordButton = Додај лозинку
changePasswordButton = Промени
passwordTryAgain = Нетачна лозинка. Пробајте поново.
// This label is followed by the password needed to download a file
passwordResult = Лозинка: { $password }
reportIPInfringement = Пријавите IP прекршај
javascriptRequired = За Firefox Send је потребан JavaScript
whyJavascript = Зашто је потребан JavaScript за Firefox Send?
enableJavascript = Омогућите JavaScript и пробајте поново.
// 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 }м

View File

@@ -26,6 +26,10 @@ uploadSuccessConfirmHeader = Klar för att skicka
uploadSvgAlt = Ladda upp
uploadSuccessTimingHeader = Länken till din fil upphör att gälla efter 1 nedladdning eller om 24 timmar.
expireInfo = Länken till din fil upphör att gälla efter { $downloadCount } eller { $timespan }.
downloadCount = { $num ->
[one] 1 nedladdning
*[other] { $num } nedladdningar
}
timespanHours = { $num ->
[one] 1 timme
*[other] { $num } timmar
@@ -37,6 +41,12 @@ deleteFileButton = Ta bort fil
sendAnotherFileLink = Skicka en annan fil
// Alternative text used on the download link/button (indicates an action).
downloadAltText = Ladda ner
downloadsFileList = Nedladdningar
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Tid
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = Ladda ner { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Ange lösenord
@@ -91,6 +101,15 @@ footerLinkTerms = Villkor
footerLinkCookies = Kakor
requirePasswordCheckbox = Kräver ett lösenord för att ladda ner den här filen
addPasswordButton = Lägg till lösenord
changePasswordButton = Ändra
passwordTryAgain = Felaktigt lösenord. Försök igen.
// This label is followed by the password needed to download a file
passwordResult = Lösenord: { $password }
reportIPInfringement = Rapportera IP-överträdelse
javascriptRequired = Firefox Send kräver JavaScript
whyJavascript = Varför kräver Firefox Send JavaScript?
enableJavascript = Aktivera JavaScript och försök igen.
// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
expiresHoursMinutes = { $hours }t { $minutes }m
// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }m

View File

@@ -40,6 +40,12 @@ deleteFileButton = Burahin ang file
sendAnotherFileLink = Magpadala ng isang file
// Alternative text used on the download link/button (indicates an action).
downloadAltText = I-download
downloadsFileList = Mga Pag-download
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Oras
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = I-download { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = Ilagay ang Password
@@ -94,6 +100,15 @@ footerLinkTerms = Mga term
footerLinkCookies = Mga cookie
requirePasswordCheckbox = Mangailangan ng isang password upang i-download ang file na ito
addPasswordButton = Magdagdag ng password
changePasswordButton = Palitan
passwordTryAgain = Maling password. Subukan muli.
// This label is followed by the password needed to download a file
passwordResult = Password: { $password }
reportIPInfringement = Report IP Infringement
javascriptRequired = Nangangailangan ang JavaScript sa JavaScript
whyJavascript = Bakit ang JavaScript ay nangangailangan ng JavaScript?
enableJavascript = Mangyaring paganahin ang JavaScript at subukan muli.
// 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

View File

@@ -37,6 +37,12 @@ deleteFileButton = Dosyayı sil
sendAnotherFileLink = Başka bir dosya daha gönder
// Alternative text used on the download link/button (indicates an action).
downloadAltText = İndir
downloadsFileList = İndirme
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = Süre
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = { $filename } dosyasını indir
downloadFileSize = ({ $size })
unlockInputLabel = Parolayı yazın
@@ -91,6 +97,7 @@ footerLinkTerms = Şartlar
footerLinkCookies = Çerezler
requirePasswordCheckbox = Bu dosyayı indirmek için parola iste
addPasswordButton = Parola ekle
changePasswordButton = Değiştir
passwordTryAgain = Yanlış parola. Yeniden deneyin.
// This label is followed by the password needed to download a file
passwordResult = Parola: { $password }

View File

@@ -25,6 +25,17 @@ uploadingFileNotification = Сповістити мене, коли вивант
uploadSuccessConfirmHeader = Готовий до надсилання
uploadSvgAlt = Вивантажити
uploadSuccessTimingHeader = Час дії цього посилання закінчиться після 1 завантаження, або через 24 години.
expireInfo = Посилання на ваш файл стане недійсним після { $downloadCount } файла, або через { $timespan }.
downloadCount = { $num ->
[one] 1 завантаження
[few] { $num } завантаження
*[other] { $num } завантажень
}
timespanHours = { $num ->
[one] 1 година
[few] { $num } години
*[other] { $num } годин
}
copyUrlFormLabelWithName = Скопіювати і поділитися посиланням на ваш файл: { $filename }
copyUrlFormButton = Копіювати у буфер обміну
copiedUrl = Скопійовано!
@@ -89,3 +100,4 @@ addPasswordButton = Додати пароль
passwordTryAgain = Невірний пароль. Спробуйте знову.
// This label is followed by the password needed to download a file
passwordResult = Пароль: { $password }
reportIPInfringement = Повідомити про порушення прав на інтелектуальну власність

View File

@@ -39,6 +39,12 @@ deleteFileButton = 删除文件
sendAnotherFileLink = 发送其他文件
// Alternative text used on the download link/button (indicates an action).
downloadAltText = 下载
downloadsFileList = 下载次数
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = 时间
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = 下载 { $filename }
downloadFileSize = ({ $size })
unlockInputLabel = 请输入密码
@@ -93,7 +99,15 @@ footerLinkTerms = 条款
footerLinkCookies = Cookie
requirePasswordCheckbox = 持有密码才能下载此文件
addPasswordButton = 添加密码
changePasswordButton = 更改
passwordTryAgain = 密码不正确。请重试。
// This label is followed by the password needed to download a file
passwordResult = 密码:{ $password }
reportIPInfringement = 举报知识产权侵权
javascriptRequired = Firefox Send 需要 JavaScript
whyJavascript = 为什么 Firefox Send 需要 JavaScript
enableJavascript = 请启用 JavaScript 并重试。
// 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 }分

View File

@@ -39,6 +39,12 @@ deleteFileButton = 刪除檔案
sendAnotherFileLink = 傳送另一個檔案
// Alternative text used on the download link/button (indicates an action).
downloadAltText = 下載
downloadsFileList = 下載次數
// Used as header in a column indicating the amount of time left before a
// download link expires (e.g. "10h 5m")
timeFileList = 剩餘時間
// Used as header in a column indicating the number of times a file has been
// downloaded
downloadFileName = 下載 { $filename }
downloadFileSize = { $size }
unlockInputLabel = 輸入密碼
@@ -93,7 +99,15 @@ footerLinkTerms = 使用條款
footerLinkCookies = Cookie
requirePasswordCheckbox = 需要密碼才能下載此檔案
addPasswordButton = 新增密碼
changePasswordButton = 變更
passwordTryAgain = 密碼不正確,請再試一次。
// This label is followed by the password needed to download a file
passwordResult = 密碼: { $password }
reportIPInfringement = 回報智慧財產權濫用情況
javascriptRequired = Firefox Send 需要開啟 JavaScript 功能
whyJavascript = 為什麼 Firefox Send 需要 JavaScript 才能使用?
enableJavascript = 請開啟 JavaScript 功能後再試一次。
// 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 } 分鐘

View File

@@ -14,6 +14,16 @@ const conf = convict({
default: 'localhost',
env: 'REDIS_HOST'
},
redis_event_expire: {
format: Boolean,
default: false,
env: 'REDIS_EVENT_EXPIRE'
},
listen_address: {
format: 'ipaddress',
default: '0.0.0.0',
env: 'IP_ADDRESS'
},
listen_port: {
format: 'port',
default: 1443,

View File

@@ -24,4 +24,4 @@ app.use(
app.use(pages.notfound);
app.listen(config.listen_port);
app.listen(config.listen_port, config.listen_address);

View File

@@ -11,8 +11,11 @@ module.exports = async (req, res) => {
}
try {
await storage.exists(id);
res.sendStatus(200);
const meta = await storage.metadata(id);
res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`);
res.send({
password: meta.pwd !== '0'
});
} catch (e) {
res.sendStatus(404);
}

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