Compare commits
963 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbe374bdc6 | ||
|
|
48ab1cdd4e | ||
|
|
54150702da | ||
|
|
981f86946b | ||
|
|
b5865f00e9 | ||
|
|
7797f485f2 | ||
|
|
db169cb9f0 | ||
|
|
f999c4c44f | ||
|
|
e9b50b7682 | ||
|
|
a3e8646ea7 | ||
|
|
a6a3cae5e9 | ||
|
|
8d80ba1f69 | ||
|
|
e5f76a7b1f | ||
|
|
acf82a4e3e | ||
|
|
0acdf3a720 | ||
|
|
305dd2f5ef | ||
|
|
e53571e219 | ||
|
|
0eda8d2082 | ||
|
|
1cd4adfc2a | ||
|
|
0460bd2e97 | ||
|
|
60146541f2 | ||
|
|
79d314146b | ||
|
|
72d12c3d80 | ||
|
|
1469464c43 | ||
|
|
7cdef4bbfc | ||
|
|
24aa1f2e17 | ||
|
|
7a4a4fc849 | ||
|
|
97be1737cb | ||
|
|
ba33e022b0 | ||
|
|
0d6b3731ef | ||
|
|
0be4a65904 | ||
|
|
54c182ab0d | ||
|
|
b2e9907551 | ||
|
|
3b4a4d82e8 | ||
|
|
5747f55876 | ||
|
|
636a239e86 | ||
|
|
f9a1583078 | ||
|
|
0dfbe3566a | ||
|
|
98b84ba05f | ||
|
|
4ad4a65924 | ||
|
|
6e07ecf643 | ||
|
|
a39cfaf3d3 | ||
|
|
97e3d78ba8 | ||
|
|
28c48f51d1 | ||
|
|
239fba452a | ||
|
|
3631bc8f39 | ||
|
|
189f4cfb9b | ||
|
|
deb2d41de8 | ||
|
|
ab53f9cf3e | ||
|
|
facb61a9b5 | ||
|
|
d8ac413064 | ||
|
|
e0f51c7fde | ||
|
|
c44c4ba41c | ||
|
|
4e9625ef36 | ||
|
|
4413fc75a3 | ||
|
|
d13fda1419 | ||
|
|
7f7ba0e33f | ||
|
|
86c5553f58 | ||
|
|
7e173ec23d | ||
|
|
3ae6f9bd5a | ||
|
|
b5ef1785ab | ||
|
|
4b1b7cb821 | ||
|
|
b0b75f5daa | ||
|
|
e2562aec17 | ||
|
|
f64e772145 | ||
|
|
bc0ccc8e5d | ||
|
|
e909a3bae8 | ||
|
|
abc58518ea | ||
|
|
4f273eca03 | ||
|
|
b15c017dcd | ||
|
|
bfaac8f66d | ||
|
|
4ffc65274b | ||
|
|
1d492cd0df | ||
|
|
b4594c5280 | ||
|
|
aa47df79f9 | ||
|
|
7533ab1930 | ||
|
|
f10b2d5064 | ||
|
|
7463aeccf5 | ||
|
|
2d35cd33c9 | ||
|
|
beb194f3e0 | ||
|
|
175040acec | ||
|
|
9e5da3270c | ||
|
|
c6cab13e6a | ||
|
|
dd9d6c1660 | ||
|
|
0dd4f8d2cc | ||
|
|
f035132b95 | ||
|
|
561ed3994e | ||
|
|
e77d2b3722 | ||
|
|
45d5f41731 | ||
|
|
505eb8c585 | ||
|
|
ce04f162a4 | ||
|
|
47cf99140a | ||
|
|
c6fc1483f6 | ||
|
|
2c8ea3ecc8 | ||
|
|
24172a4665 | ||
|
|
daa5a3c5f1 | ||
|
|
806ebbe160 | ||
|
|
3e9be67641 | ||
|
|
402c8351bd | ||
|
|
ccbcb69666 | ||
|
|
e6ff9e486b | ||
|
|
17afd6ad58 | ||
|
|
7dd1fcca9b | ||
|
|
d5319f4fcf | ||
|
|
a3d880fc4f | ||
|
|
8a9a823842 | ||
|
|
3195ee7d16 | ||
|
|
f565954a54 | ||
|
|
4abfd7dd6e | ||
|
|
e930275042 | ||
|
|
960e29f800 | ||
|
|
7e637ae453 | ||
|
|
cca6b470b0 | ||
|
|
829d10f77f | ||
|
|
b7d7bb885d | ||
|
|
31e2e00b24 | ||
|
|
0ab2ec6a5c | ||
|
|
369b0b2614 | ||
|
|
b064152955 | ||
|
|
438b36dd28 | ||
|
|
a429b89322 | ||
|
|
bd0117628f | ||
|
|
c4299d05a1 | ||
|
|
e3027d2c59 | ||
|
|
be055ff7ba | ||
|
|
5a53eb85fb | ||
|
|
c249f26b4a | ||
|
|
b229547057 | ||
|
|
334f225f3d | ||
|
|
e8f3379ddb | ||
|
|
3dd3fa9808 | ||
|
|
9459e4bca8 | ||
|
|
888e99841e | ||
|
|
949dcce5d7 | ||
|
|
0c47991de6 | ||
|
|
63b96310d3 | ||
|
|
da37b9d96f | ||
|
|
931cfd108d | ||
|
|
cc7ea99ca9 | ||
|
|
996a7e4184 | ||
|
|
e5627698c4 | ||
|
|
1fdccd4dde | ||
|
|
b8c26aee06 | ||
|
|
b7d65517b3 | ||
|
|
4f5b804b5d | ||
|
|
a74491af40 | ||
|
|
09955a0231 | ||
|
|
301f6e87f6 | ||
|
|
8c0f1fbe3b | ||
|
|
b81535c820 | ||
|
|
f5f0a430eb | ||
|
|
ca55dedad4 | ||
|
|
76a3d6fc03 | ||
|
|
f9b5cadf17 | ||
|
|
458ef649f3 | ||
|
|
de063f75e5 | ||
|
|
e2876b119d | ||
|
|
25d41073b7 | ||
|
|
5fb9e395d5 | ||
|
|
c73ddcd464 | ||
|
|
3a98c295e0 | ||
|
|
899f921da3 | ||
|
|
97b9d24134 | ||
|
|
ab9dbecbd6 | ||
|
|
0cf77d94cb | ||
|
|
bfe3a4067d | ||
|
|
0226483c30 | ||
|
|
a6dbb9a58f | ||
|
|
c9e691bdc2 | ||
|
|
e88bfb597c | ||
|
|
190ded1ba7 | ||
|
|
7b7f1f3223 | ||
|
|
1f4473c278 | ||
|
|
27e86640be | ||
|
|
74d151d12c | ||
|
|
a370f01908 | ||
|
|
3078e1f90c | ||
|
|
d90830b285 | ||
|
|
74288cdba6 | ||
|
|
a37e59cdb3 | ||
|
|
ee5f63e11c | ||
|
|
b02f2768d3 | ||
|
|
da3a7c1e98 | ||
|
|
8180406467 | ||
|
|
be595de148 | ||
|
|
395b6518ee | ||
|
|
49243506cb | ||
|
|
69948f4a4a | ||
|
|
85aaeb9892 | ||
|
|
95098d5828 | ||
|
|
eb4aa04f7e | ||
|
|
5afb940718 | ||
|
|
b6e956e086 | ||
|
|
681ca8197c | ||
|
|
21e312dac3 | ||
|
|
5a02e32aec | ||
|
|
dfe3631bcd | ||
|
|
b7c53bf8e2 | ||
|
|
9bdf9c4416 | ||
|
|
7116c1c06e | ||
|
|
f210394d84 | ||
|
|
0679a29cdc | ||
|
|
7a9a75794e | ||
|
|
89469e3c9c | ||
|
|
01110b4ec1 | ||
|
|
b54cd58602 | ||
|
|
6cc00be4b5 | ||
|
|
b40594f0ee | ||
|
|
01f4ceca23 | ||
|
|
7aead375d8 | ||
|
|
471c9e8559 | ||
|
|
f45960263f | ||
|
|
0ee9cc69a8 | ||
|
|
2a3db78f97 | ||
|
|
e5f9265979 | ||
|
|
00416b5063 | ||
|
|
5a70362b79 | ||
|
|
97deb78de6 | ||
|
|
2d22573588 | ||
|
|
deec85be14 | ||
|
|
6342203256 | ||
|
|
949fbb68c3 | ||
|
|
222c21ddf9 | ||
|
|
ae20e55c50 | ||
|
|
765da48b0c | ||
|
|
3769a17300 | ||
|
|
cad853b196 | ||
|
|
804128b7c8 | ||
|
|
e136b9dc63 | ||
|
|
b4ffb8cf01 | ||
|
|
e24c1103b3 | ||
|
|
07202e684a | ||
|
|
ca774258d6 | ||
|
|
93d48c5787 | ||
|
|
aa65f20afa | ||
|
|
f3caa9841c | ||
|
|
4c6663948b | ||
|
|
c5a4f920c4 | ||
|
|
c6cbb727da | ||
|
|
830337b403 | ||
|
|
c11f8a059e | ||
|
|
6fd1ed18a1 | ||
|
|
0338382fdf | ||
|
|
aac2b8e6f4 | ||
|
|
40a111a2e9 | ||
|
|
cbb0b86712 | ||
|
|
51465a2797 | ||
|
|
fa99c75313 | ||
|
|
0b4bd40b32 | ||
|
|
c5b2dc826b | ||
|
|
1f7a58ea41 | ||
|
|
ca6e2963e3 | ||
|
|
9f019f100d | ||
|
|
5e91a72cd9 | ||
|
|
01d7383ae0 | ||
|
|
74234fa03c | ||
|
|
49b24294b1 | ||
|
|
5feb07a251 | ||
|
|
28b69c4f14 | ||
|
|
19e5fd269e | ||
|
|
52d708bf3f | ||
|
|
bb66a1bfa5 | ||
|
|
d31b1ebfcd | ||
|
|
4385d29aa0 | ||
|
|
111e1d5ed3 | ||
|
|
27d48f2ec3 | ||
|
|
1a6ddf7e75 | ||
|
|
cd8d01db29 | ||
|
|
dc8f56bc57 | ||
|
|
efb04397f7 | ||
|
|
19b34e8d39 | ||
|
|
b7c7e0ef27 | ||
|
|
b02c5ed4a9 | ||
|
|
59fb485ba6 | ||
|
|
71b22458ba | ||
|
|
ecb9391439 | ||
|
|
aaf341bd81 | ||
|
|
35ef24a40f | ||
|
|
3973f0552c | ||
|
|
767cc166b1 | ||
|
|
f24ad20ffe | ||
|
|
a1f915806a | ||
|
|
669212a9b7 | ||
|
|
59f6e51636 | ||
|
|
2515ba274d | ||
|
|
6c2fbfe6ff | ||
|
|
26fdb271dd | ||
|
|
4504c5cd74 | ||
|
|
2746556dfa | ||
|
|
5ff6266a5e | ||
|
|
e57fc1c2d8 | ||
|
|
097d3c8377 | ||
|
|
43c728d5d4 | ||
|
|
3dffb8da15 | ||
|
|
c49dbd6d6d | ||
|
|
356112e8cc | ||
|
|
ad0d862d92 | ||
|
|
2ca007af1a | ||
|
|
0a42c887d2 | ||
|
|
f54a95c965 | ||
|
|
eec8da890a | ||
|
|
32a58d85a3 | ||
|
|
1252aaed30 | ||
|
|
060ff1cae4 | ||
|
|
1c2955ed9e | ||
|
|
5e5bce8745 | ||
|
|
e43ba60792 | ||
|
|
e94fe58965 | ||
|
|
a21412844a | ||
|
|
666ca8e87b | ||
|
|
71ceffd9b4 | ||
|
|
cdbd669af2 | ||
|
|
027043edb7 | ||
|
|
e4a1f8e86b | ||
|
|
dfd5eb9b1e | ||
|
|
e3ee840b64 | ||
|
|
b8bd13c704 | ||
|
|
6238299a0a | ||
|
|
d40ddc3d3b | ||
|
|
d2746db969 | ||
|
|
95057ef503 | ||
|
|
aec2ec30d5 | ||
|
|
4d36b3d6e9 | ||
|
|
6b89197cfd | ||
|
|
dd2ebdd2de | ||
|
|
c479d326ed | ||
|
|
8434d91891 | ||
|
|
f9c761ea99 | ||
|
|
b14f5f16d0 | ||
|
|
4fbc8e0c89 | ||
|
|
ad2a1715b5 | ||
|
|
ac8f826612 | ||
|
|
c1aaa9d6e5 | ||
|
|
eac95ffff7 | ||
|
|
8121761cc8 | ||
|
|
7c720cd7cc | ||
|
|
4c2f809502 | ||
|
|
d42523651c | ||
|
|
b9775e0471 | ||
|
|
d2f37204e0 | ||
|
|
846dad6662 | ||
|
|
c966010ed3 | ||
|
|
e77fea09bc | ||
|
|
b63291c7d3 | ||
|
|
2ccc044a27 | ||
|
|
4144850f9d | ||
|
|
18630aadf9 | ||
|
|
605fff519e | ||
|
|
0b7ff9551d | ||
|
|
2c9716e01e | ||
|
|
cdde3fe654 | ||
|
|
3537667187 | ||
|
|
66086c460d | ||
|
|
a203a8de67 | ||
|
|
2bba9c53ed | ||
|
|
37af4d33fc | ||
|
|
107f40c8c9 | ||
|
|
cb325022dc | ||
|
|
b62b439218 | ||
|
|
47972fe2a1 | ||
|
|
7ad937266f | ||
|
|
2f34dffab4 | ||
|
|
4a956c0247 | ||
|
|
23ce7a1f84 | ||
|
|
66a8ad2e4f | ||
|
|
e8e2e31b59 | ||
|
|
7259c46d1c | ||
|
|
9d3c3d1924 | ||
|
|
a0d6317747 | ||
|
|
3810e09509 | ||
|
|
bb7678e7e4 | ||
|
|
3dcf01dd1f | ||
|
|
3b8f19b4c9 | ||
|
|
ab957c26a0 | ||
|
|
e835c82c27 | ||
|
|
d69a9e8537 | ||
|
|
1b286d664b | ||
|
|
f8c8a6c2cd | ||
|
|
b71234fc83 | ||
|
|
2eb35733da | ||
|
|
df276d5942 | ||
|
|
e1a6fb9569 | ||
|
|
99b2d636a1 | ||
|
|
78be59adac | ||
|
|
720ab6ae3a | ||
|
|
5e0dcc948c | ||
|
|
90400d72b1 | ||
|
|
8f1fd6cbf5 | ||
|
|
fef8788d55 | ||
|
|
d2f8156bde | ||
|
|
d4fbd48f1c | ||
|
|
a6d3948fbc | ||
|
|
005bbb1792 | ||
|
|
186230d211 | ||
|
|
ee1e8bc204 | ||
|
|
d1366fb764 | ||
|
|
d81dba6407 | ||
|
|
f35ddd8e9a | ||
|
|
ff798463e8 | ||
|
|
6308c386a3 | ||
|
|
5a6c338ab9 | ||
|
|
bc6401e7dd | ||
|
|
0667f22ddd | ||
|
|
594cd339b5 | ||
|
|
762497b867 | ||
|
|
9199a985d8 | ||
|
|
624e1234d9 | ||
|
|
a3d153db66 | ||
|
|
dc25bc5727 | ||
|
|
6a1670b550 | ||
|
|
8cd3a720fe | ||
|
|
9aa7da68be | ||
|
|
063042cd76 | ||
|
|
8ce3a5d236 | ||
|
|
0c58c84625 | ||
|
|
68ddcf3b56 | ||
|
|
a0648d7d91 | ||
|
|
5e674973db | ||
|
|
4f2b634825 | ||
|
|
48bbf07392 | ||
|
|
357f6da3b0 | ||
|
|
def21041d8 | ||
|
|
c44728865a | ||
|
|
f271140c5f | ||
|
|
3593aaf61f | ||
|
|
8b433a008d | ||
|
|
1ab1bf1b00 | ||
|
|
c04d15f806 | ||
|
|
977e8bdc21 | ||
|
|
454d1d03f5 | ||
|
|
f73ff7fa26 | ||
|
|
98317c94aa | ||
|
|
aa91d762af | ||
|
|
5cddd0842b | ||
|
|
4dee3d2283 | ||
|
|
2d20cb3819 | ||
|
|
c28beb3bc1 | ||
|
|
216a7a6ff8 | ||
|
|
a48d2bad4f | ||
|
|
04c60414e1 | ||
|
|
92c3f67020 | ||
|
|
49e6c064ac | ||
|
|
8551139a8a | ||
|
|
96461692ee | ||
|
|
a217ad4be5 | ||
|
|
18216ce430 | ||
|
|
1711a2d1d7 | ||
|
|
8b2080a2f0 | ||
|
|
006ee1d2e6 | ||
|
|
0a7c473965 | ||
|
|
4e124d174c | ||
|
|
524b2a5668 | ||
|
|
844f036a27 | ||
|
|
7d6c781ea5 | ||
|
|
ad9577daaf | ||
|
|
114068c531 | ||
|
|
0daa03e04c | ||
|
|
63348fd36a | ||
|
|
c71c246959 | ||
|
|
2b7504e969 | ||
|
|
b942554c80 | ||
|
|
1ae227fddd | ||
|
|
a63fe18d15 | ||
|
|
ea1ea2aa68 | ||
|
|
90d06d71d3 | ||
|
|
11f2deb7a6 | ||
|
|
0f5edf023a | ||
|
|
3be0621647 | ||
|
|
fc07bfdb85 | ||
|
|
581a989304 | ||
|
|
047d6a85ab | ||
|
|
90f6a07d4a | ||
|
|
8f4a53db64 | ||
|
|
c2dd51c638 | ||
|
|
cdd98af86a | ||
|
|
883f2bc0f9 | ||
|
|
94f1eabbc7 | ||
|
|
902bc6628e | ||
|
|
460b741f17 | ||
|
|
d5c488196d | ||
|
|
9ad9c9feb2 | ||
|
|
6576e4a74c | ||
|
|
950872109e | ||
|
|
87051d27ed | ||
|
|
3451803b37 | ||
|
|
ac15153e8f | ||
|
|
924f5dc682 | ||
|
|
ff9be6a213 | ||
|
|
883728570e | ||
|
|
0435f17f9a | ||
|
|
1e1268fff0 | ||
|
|
252d7817e3 | ||
|
|
ce28c38ebe | ||
|
|
f0407f9beb | ||
|
|
c6f222eb57 | ||
|
|
6dd6135185 | ||
|
|
8df339b66d | ||
|
|
8702fda651 | ||
|
|
807ecff471 | ||
|
|
927c981cd7 | ||
|
|
7073cc8ce6 | ||
|
|
c925fae696 | ||
|
|
966d7a5e35 | ||
|
|
96c750c098 | ||
|
|
0729064753 | ||
|
|
259a5a5f24 | ||
|
|
27be72e0cd | ||
|
|
e4231bbc0f | ||
|
|
1d184f06bf | ||
|
|
f7b46a99ac | ||
|
|
3fadb489c7 | ||
|
|
6378676c2d | ||
|
|
014d84e4c7 | ||
|
|
a08d8435a9 | ||
|
|
40a05c9ecf | ||
|
|
527040afef | ||
|
|
a48a447808 | ||
|
|
f3569d7f98 | ||
|
|
6ca7d11efb | ||
|
|
b71ae4a0ff | ||
|
|
7ba25664b5 | ||
|
|
80fb42ad3d | ||
|
|
f036df5f47 | ||
|
|
20c063db7c | ||
|
|
a6b43c9eef | ||
|
|
c80f9ada65 | ||
|
|
e0ae5cb3c6 | ||
|
|
c0fb3d17be | ||
|
|
116f090b7e | ||
|
|
7cbd9716e2 | ||
|
|
58191975b9 | ||
|
|
76695aee5d | ||
|
|
b356b4cad3 | ||
|
|
63ddbeefc4 | ||
|
|
7b423b24b6 | ||
|
|
b67050b742 | ||
|
|
06242dfd38 | ||
|
|
3b8dbfd81c | ||
|
|
9c2fe39764 | ||
|
|
8b21d43bfa | ||
|
|
e7e39f4d4a | ||
|
|
e4c801d9a1 | ||
|
|
324f275dd4 | ||
|
|
594e0bd28e | ||
|
|
6a7fdfe780 | ||
|
|
0c82741b98 | ||
|
|
fe57734792 | ||
|
|
4754bed9b8 | ||
|
|
a60da467b9 | ||
|
|
362da2bbfc | ||
|
|
1a9ef4a246 | ||
|
|
f51dbbb8f5 | ||
|
|
e497107e59 | ||
|
|
10ad6fc1ae | ||
|
|
c982db4c75 | ||
|
|
cd8130563d | ||
|
|
730a569d43 | ||
|
|
769dae1bdc | ||
|
|
54a8c504ce | ||
|
|
92dc9a0f71 | ||
|
|
80a7c92056 | ||
|
|
c6ebea0100 | ||
|
|
039b5daaed | ||
|
|
4bf4e61c2c | ||
|
|
3c21e2a00f | ||
|
|
38a379d1de | ||
|
|
adeb19d974 | ||
|
|
744c410704 | ||
|
|
99ab3882eb | ||
|
|
7cdf566bcd | ||
|
|
b6e4877d93 | ||
|
|
b9c87fd779 | ||
|
|
6ef5b5133c | ||
|
|
b7d2420765 | ||
|
|
c139531c91 | ||
|
|
88fe3902bc | ||
|
|
645fd062ac | ||
|
|
ccb0b71df5 | ||
|
|
b8ec90a398 | ||
|
|
7ed5f37c66 | ||
|
|
c899129f9a | ||
|
|
1bb91690c5 | ||
|
|
073f6f67d3 | ||
|
|
533bfdb496 | ||
|
|
34de65d7d2 | ||
|
|
209fdf34f8 | ||
|
|
ba5c2a049d | ||
|
|
1528aa3f1b | ||
|
|
b1f1b9bd19 | ||
|
|
2963b2fc05 | ||
|
|
e4f9cfa023 | ||
|
|
09e961219d | ||
|
|
b36a5f6973 | ||
|
|
1a36dc084e | ||
|
|
c66347b3da | ||
|
|
a7d0551509 | ||
|
|
f6f6324aa1 | ||
|
|
a24597695b | ||
|
|
706ac638da | ||
|
|
d66d08f0ed | ||
|
|
ab9af7fd9b | ||
|
|
466a087689 | ||
|
|
71956739f8 | ||
|
|
62689b9556 | ||
|
|
7d1b8e002f | ||
|
|
8c535ce50d | ||
|
|
a3bdcd8478 | ||
|
|
1e77d86e8a | ||
|
|
1af01b1ee3 | ||
|
|
eb1bcde37f | ||
|
|
8def910d61 | ||
|
|
5892532d03 | ||
|
|
8ede5cf05c | ||
|
|
7213fef7c3 | ||
|
|
d3e9bb09df | ||
|
|
2682aec90d | ||
|
|
0ea89a58b0 | ||
|
|
2a2a3ad21f | ||
|
|
36c73bae7b | ||
|
|
f5116cee29 | ||
|
|
72ab5bdbf3 | ||
|
|
94f0b928ae | ||
|
|
5e14a0b45a | ||
|
|
84a382af77 | ||
|
|
8680dc6b3c | ||
|
|
cf787eef9f | ||
|
|
a5dffdba14 | ||
|
|
2dcc3730d9 | ||
|
|
3fb3f6f77f | ||
|
|
0a4a9f674d | ||
|
|
7e2e171f83 | ||
|
|
a64bb4ac8b | ||
|
|
acab58ca1e | ||
|
|
10ed88fa40 | ||
|
|
9b2a7dfcd7 | ||
|
|
517f51f4f0 | ||
|
|
73735010ae | ||
|
|
72497b77b2 | ||
|
|
7c4a00ecd1 | ||
|
|
d96cb1aec4 | ||
|
|
fc99ed584f | ||
|
|
da1ffe581b | ||
|
|
6cbbbab691 | ||
|
|
b91bd2b859 | ||
|
|
14ae124ca8 | ||
|
|
3bd087a66d | ||
|
|
57e9fbcd26 | ||
|
|
bd42445a98 | ||
|
|
0e1027544c | ||
|
|
c7532f3f4a | ||
|
|
97e861f050 | ||
|
|
9f3adbf6ad | ||
|
|
7b4c42a56f | ||
|
|
0f763ca4ec | ||
|
|
97164a4b08 | ||
|
|
ee8baff557 | ||
|
|
637a6d86fb | ||
|
|
f8f200cbcd | ||
|
|
0e4b9f425f | ||
|
|
eb4a2c9f02 | ||
|
|
55f3abaa1b | ||
|
|
bb1ee80c34 | ||
|
|
f9edf196a2 | ||
|
|
9bebab3f66 | ||
|
|
e9bf6e235b | ||
|
|
88a3dc94d5 | ||
|
|
d7143ab63a | ||
|
|
b340969cc1 | ||
|
|
ce0bf26b9e | ||
|
|
193f43ac9d | ||
|
|
e376f978a2 | ||
|
|
bd93fb66a5 | ||
|
|
fc79233ade | ||
|
|
8263a9eaa9 | ||
|
|
5d02f47e41 | ||
|
|
23ecb632eb | ||
|
|
b82177dc44 | ||
|
|
51296d5489 | ||
|
|
f603f40f43 | ||
|
|
19ac0480ee | ||
|
|
7d43f4f145 | ||
|
|
8f17c86611 | ||
|
|
f517c514d8 | ||
|
|
e2d9c0acca | ||
|
|
c3221bddf2 | ||
|
|
5ac10a2057 | ||
|
|
85ce14a991 | ||
|
|
2594581965 | ||
|
|
1a3968476a | ||
|
|
281abfc79b | ||
|
|
6dd4a92467 | ||
|
|
9614c119aa | ||
|
|
1e1c21a45a | ||
|
|
8cff6f9441 | ||
|
|
23c2a61567 | ||
|
|
903cb9cc49 | ||
|
|
d4901662f8 | ||
|
|
686197be61 | ||
|
|
dd848f19b4 | ||
|
|
a7d1607cb3 | ||
|
|
508e252067 | ||
|
|
2cf409ca11 | ||
|
|
3f0fac738a | ||
|
|
9bd14a17f9 | ||
|
|
4505296b35 | ||
|
|
5a91ba47c6 | ||
|
|
b82e2efd33 | ||
|
|
284676d956 | ||
|
|
d91edcb653 | ||
|
|
9ffc792dec | ||
|
|
7d3973a5e4 | ||
|
|
ffc0d57576 | ||
|
|
f28ee8ab78 | ||
|
|
6b02a2167c | ||
|
|
c9b9c338d7 | ||
|
|
d22d54dee0 | ||
|
|
20b9279eec | ||
|
|
ce4157ac08 | ||
|
|
3c74d9958f | ||
|
|
569d110f6e | ||
|
|
e69c91b5eb | ||
|
|
2477b6cde8 | ||
|
|
8cf42a2711 | ||
|
|
5207c3fb15 | ||
|
|
a166f98b66 | ||
|
|
d572c44dd0 | ||
|
|
9286140b15 | ||
|
|
0b738bc152 | ||
|
|
f06ddfd888 | ||
|
|
f10ed62310 | ||
|
|
a5c05daac5 | ||
|
|
a84ce0b0cf | ||
|
|
adecddae77 | ||
|
|
927706d67c | ||
|
|
e70c883673 | ||
|
|
09f583c569 | ||
|
|
6e676404a0 | ||
|
|
daa70a61eb | ||
|
|
2ed4ad6772 | ||
|
|
f0a42c5c07 | ||
|
|
02174c3245 | ||
|
|
8a148747bd | ||
|
|
66750ca9ff | ||
|
|
e84ec0fcb3 | ||
|
|
39edee306e | ||
|
|
464fdc9873 | ||
|
|
38ef8db3f1 | ||
|
|
3fee10c890 | ||
|
|
3a35b2ae2a | ||
|
|
cff4227132 | ||
|
|
b01dbdc119 | ||
|
|
62603f095e | ||
|
|
1e95a9122e | ||
|
|
308fd0cdcb | ||
|
|
a4e8c0b4a3 | ||
|
|
e7964c03ed | ||
|
|
c800257678 | ||
|
|
89a3c984e7 | ||
|
|
05d32ef9d7 | ||
|
|
a2ec22a4e0 | ||
|
|
7ae676ce8a | ||
|
|
b0c2c329e2 | ||
|
|
9c423bb435 | ||
|
|
3cb6952d68 | ||
|
|
147ed4c736 | ||
|
|
f94918bebd | ||
|
|
ee45a835be | ||
|
|
26ad1b8763 | ||
|
|
8b798ce36c | ||
|
|
894ad6cb08 | ||
|
|
98c4bd0f50 | ||
|
|
898d152810 | ||
|
|
9c588ed008 | ||
|
|
1da7e1c112 | ||
|
|
19d1f8cf80 | ||
|
|
29f243fdda | ||
|
|
bd58022b08 | ||
|
|
a6fa2c642c | ||
|
|
890642fcff | ||
|
|
74ecb598d2 | ||
|
|
48b5d85904 | ||
|
|
373da3f090 | ||
|
|
4a69ccbe51 | ||
|
|
4c1496e1cd | ||
|
|
0a243dfb52 | ||
|
|
79bd847042 | ||
|
|
2fe5cad9d5 | ||
|
|
fbde75fd66 | ||
|
|
24167161b1 | ||
|
|
eda132832f | ||
|
|
4839ff3584 | ||
|
|
de2d11cc59 | ||
|
|
95779b3243 | ||
|
|
7f76a279c3 | ||
|
|
219b1d6806 | ||
|
|
efea0e5ab0 | ||
|
|
5d19a9d696 | ||
|
|
17a12e3194 | ||
|
|
b71265b0c5 | ||
|
|
4a62048d64 | ||
|
|
acd48cc2f5 | ||
|
|
364778c516 | ||
|
|
d7ca9b4f84 | ||
|
|
b6350c787a | ||
|
|
ee8a672873 | ||
|
|
67b812a538 | ||
|
|
c811cbd34e | ||
|
|
2ed09d1fda | ||
|
|
557ecb196d | ||
|
|
88e8a234d6 | ||
|
|
34eb0fce2a | ||
|
|
995054a2cb | ||
|
|
e70161bfb0 | ||
|
|
ebf62761bc | ||
|
|
cf08b03a6b | ||
|
|
f3be26d5f5 | ||
|
|
03c68b0c58 | ||
|
|
59d460e80b | ||
|
|
87f947c31d | ||
|
|
7ff0a7654c | ||
|
|
9f829e8467 | ||
|
|
b68762da50 | ||
|
|
caef279fed | ||
|
|
0dfda7c610 | ||
|
|
5c2d9d8673 | ||
|
|
be51ae7ac9 | ||
|
|
1ba6aceb1f | ||
|
|
0a3c3d8c51 | ||
|
|
38aa460a03 | ||
|
|
58b7a17680 | ||
|
|
1ce115c3e9 | ||
|
|
8cf3b89f91 | ||
|
|
eced5be836 | ||
|
|
bb4fa0a75c | ||
|
|
98b62cd1c2 | ||
|
|
62461f50a5 | ||
|
|
1be87a531c | ||
|
|
f70caf814b | ||
|
|
a9595e501d | ||
|
|
40814207ee | ||
|
|
1970a88ece | ||
|
|
a6d41e70b2 | ||
|
|
549ce3281a | ||
|
|
1ec56df7ad | ||
|
|
c78899c387 | ||
|
|
c76fe3ac96 | ||
|
|
49cfd599ec | ||
|
|
74c08200ad | ||
|
|
8cd5839d36 | ||
|
|
cbd7a99e38 | ||
|
|
6a5960c254 | ||
|
|
b70cbd8359 | ||
|
|
ad72fa11a4 | ||
|
|
cc9a5d112a | ||
|
|
122867b926 | ||
|
|
3f7d755070 | ||
|
|
ad9070b7c6 | ||
|
|
30678b824b | ||
|
|
5dedad8ae7 | ||
|
|
748f8cc7f8 | ||
|
|
be9b51adbb | ||
|
|
7f13d4d9b6 | ||
|
|
0e81ef2514 | ||
|
|
b0907095e4 | ||
|
|
c4cc736cff | ||
|
|
c4118c5684 | ||
|
|
fad35cf8fc | ||
|
|
062cbbd1f8 | ||
|
|
f200bd51d2 | ||
|
|
152616574f | ||
|
|
822fcb363d | ||
|
|
e1c6e59495 | ||
|
|
d42d8f8b75 | ||
|
|
ebbb1d05d2 | ||
|
|
67b55d1477 | ||
|
|
e3391ca823 | ||
|
|
0cac3bd0dc | ||
|
|
78de0b7a22 | ||
|
|
14308dc491 | ||
|
|
f690e4a705 | ||
|
|
3e14d3049d | ||
|
|
6a6f8b86e4 | ||
|
|
c4891c3866 | ||
|
|
5d0d5ef858 | ||
|
|
4e26c6ab75 | ||
|
|
7413a3336a | ||
|
|
ef825206a4 | ||
|
|
f6a2e3ef53 | ||
|
|
98bf370ccb | ||
|
|
1e9e9d6494 | ||
|
|
c698b39d5c | ||
|
|
109ee6aa29 | ||
|
|
fe9ed5f001 | ||
|
|
84d74a096c | ||
|
|
770d276f6d | ||
|
|
ddf69120e8 | ||
|
|
6c583e09a7 | ||
|
|
677ff65e8d | ||
|
|
8445657b72 | ||
|
|
33ef1f33a5 | ||
|
|
ff79662b72 | ||
|
|
0012dec277 | ||
|
|
a648c5ead9 | ||
|
|
017bb0d146 | ||
|
|
555c615711 | ||
|
|
ce8dba4200 | ||
|
|
44dcf8a260 | ||
|
|
e702022d7f | ||
|
|
4ee80abce9 | ||
|
|
45d9bdb577 | ||
|
|
013cca5d86 | ||
|
|
0bf162592f | ||
|
|
da6cbc05c2 | ||
|
|
d28da3247d | ||
|
|
44043b54b3 | ||
|
|
813ddc6c6c | ||
|
|
1b9c02d86c | ||
|
|
135b4a9669 | ||
|
|
426464641f | ||
|
|
9ea3b19b8e | ||
|
|
4a8bfd829b | ||
|
|
f40bfe6a96 | ||
|
|
3f8ebdaf4b | ||
|
|
f3fb433531 | ||
|
|
d7f097a2e0 | ||
|
|
1c42f2b397 | ||
|
|
ae7f7de28a | ||
|
|
bfb9b7ea4e | ||
|
|
761c041069 | ||
|
|
85fa38df53 | ||
|
|
d0cd84f5f5 | ||
|
|
35af2cf0a1 | ||
|
|
d2d472e2b4 | ||
|
|
773bea01fb | ||
|
|
158575a2ff | ||
|
|
f2f504c93d | ||
|
|
ff9d45eb71 | ||
|
|
b133be9f01 | ||
|
|
9b1c1feb80 | ||
|
|
f2e8d3e988 | ||
|
|
d29ded2c79 | ||
|
|
160bfed7a9 | ||
|
|
9e3d28a98a | ||
|
|
8728402d14 | ||
|
|
e215738c67 | ||
|
|
f4e185bdc0 | ||
|
|
adf9c7b96f | ||
|
|
f78e025d33 | ||
|
|
6559afba37 | ||
|
|
f07988426b | ||
|
|
bada0707c5 | ||
|
|
9da70931e4 | ||
|
|
43ad9ad057 | ||
|
|
e08146ebfd | ||
|
|
88c97a199d | ||
|
|
5105de66f8 | ||
|
|
cdff2884cd | ||
|
|
862fedb420 | ||
|
|
3e5fa8525f | ||
|
|
7eb43d9046 | ||
|
|
30fc4e7e08 | ||
|
|
8c59e54677 | ||
|
|
30d7ec50f3 |
@@ -1,76 +1,48 @@
|
|||||||
version: 2.0
|
version: 2.0
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
|
||||||
docker:
|
|
||||||
- image: circleci/node:10
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: send-build-{{ checksum "package-lock.json" }}
|
|
||||||
- run: npm install
|
|
||||||
- save_cache:
|
|
||||||
key: send-build-{{ checksum "package-lock.json" }}
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
- run: npm run build
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: .
|
|
||||||
paths:
|
|
||||||
- ./dist
|
|
||||||
test:
|
test:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:10-browsers
|
- image: circleci/node:12-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- run: npm ci
|
||||||
key: send-test-{{ checksum "package-lock.json" }}
|
|
||||||
- run: npm install
|
|
||||||
- save_cache:
|
|
||||||
key: send-test-{{ checksum "package-lock.json" }}
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
- run: npm run test
|
- run: npm test
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: coverage
|
path: coverage
|
||||||
integration_tests:
|
integration_tests:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:10
|
- image: circleci/node:12-browsers
|
||||||
- image: selenium/standalone-firefox
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- run: npm ci
|
||||||
key: send-int-{{ checksum "package-lock.json" }}
|
- run:
|
||||||
- run: npm install
|
name: Run integration test
|
||||||
- save_cache:
|
command: ./scripts/bin/run-integration-test-circleci.sh
|
||||||
key: send-int-{{ checksum "package-lock.json" }}
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
- run: npm run circleci-test-integration
|
|
||||||
deploy_dev:
|
deploy_dev:
|
||||||
machine: true
|
docker:
|
||||||
|
- image: circleci/node:12
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- setup_remote_docker
|
||||||
at: .
|
|
||||||
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
|
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||||
- run: docker build -t mozilla/send:latest .
|
- run: docker build -t mozilla/send:latest .
|
||||||
- run: docker push mozilla/send:latest
|
- run: docker push mozilla/send:latest
|
||||||
deploy_vnext:
|
deploy_vnext:
|
||||||
machine: true
|
docker:
|
||||||
|
- image: circleci/node:12
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- setup_remote_docker
|
||||||
at: .
|
|
||||||
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
|
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||||
- run: docker build -t mozilla/send:vnext .
|
- run: docker build -t mozilla/send:vnext .
|
||||||
- run: docker push mozilla/send:vnext
|
- run: docker push mozilla/send:vnext
|
||||||
deploy_stage:
|
deploy_stage:
|
||||||
machine: true
|
docker:
|
||||||
|
- image: circleci/node:12
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- setup_remote_docker
|
||||||
at: .
|
|
||||||
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
|
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||||
- run: docker build -t mozilla/send:$CIRCLE_TAG .
|
- run: docker build -t mozilla/send:$CIRCLE_TAG .
|
||||||
- run: docker push mozilla/send:$CIRCLE_TAG
|
- run: docker push mozilla/send:$CIRCLE_TAG
|
||||||
@@ -78,12 +50,6 @@ workflows:
|
|||||||
version: 2
|
version: 2
|
||||||
test_pr:
|
test_pr:
|
||||||
jobs:
|
jobs:
|
||||||
- build:
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore:
|
|
||||||
- master
|
|
||||||
- vnext
|
|
||||||
- test:
|
- test:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
@@ -96,25 +62,13 @@ workflows:
|
|||||||
ignore: master
|
ignore: master
|
||||||
build_and_deploy_dev:
|
build_and_deploy_dev:
|
||||||
jobs:
|
jobs:
|
||||||
- build:
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
- vnext
|
|
||||||
tags:
|
|
||||||
ignore: /^v.*/
|
|
||||||
- deploy_dev:
|
- deploy_dev:
|
||||||
requires:
|
|
||||||
- build
|
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master
|
only: master
|
||||||
tags:
|
tags:
|
||||||
ignore: /^v.*/
|
ignore: /^v.*/
|
||||||
- deploy_vnext:
|
- deploy_vnext:
|
||||||
requires:
|
|
||||||
- build
|
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: vnext
|
only: vnext
|
||||||
@@ -122,12 +76,6 @@ workflows:
|
|||||||
ignore: /^v.*/
|
ignore: /^v.*/
|
||||||
build_and_deploy_stage:
|
build_and_deploy_stage:
|
||||||
jobs:
|
jobs:
|
||||||
- build:
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
tags:
|
|
||||||
only: /^v.*/
|
|
||||||
- test:
|
- test:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
@@ -142,7 +90,6 @@ workflows:
|
|||||||
only: /^v.*/
|
only: /^v.*/
|
||||||
- deploy_stage:
|
- deploy_stage:
|
||||||
requires:
|
requires:
|
||||||
- build
|
|
||||||
- test
|
- test
|
||||||
- integration_tests
|
- integration_tests
|
||||||
filters:
|
filters:
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
node_modules
|
.circleci
|
||||||
.git
|
|
||||||
.tox
|
|
||||||
.DS_Store
|
|
||||||
firefox
|
|
||||||
assets
|
|
||||||
docs
|
|
||||||
test
|
|
||||||
coverage
|
|
||||||
.nyc_output
|
.nyc_output
|
||||||
|
.vscode
|
||||||
|
.DS_Store
|
||||||
|
coverage
|
||||||
|
docs
|
||||||
|
firefox
|
||||||
|
node_modules
|
||||||
@@ -2,5 +2,7 @@ dist
|
|||||||
assets
|
assets
|
||||||
firefox
|
firefox
|
||||||
coverage
|
coverage
|
||||||
|
android/app/build
|
||||||
app/locale.js
|
app/locale.js
|
||||||
app/capabilities.js
|
app/capabilities.js
|
||||||
|
app/qrcode.js
|
||||||
@@ -4,6 +4,7 @@ env:
|
|||||||
|
|
||||||
extends:
|
extends:
|
||||||
- eslint:recommended
|
- eslint:recommended
|
||||||
|
- prettier
|
||||||
- plugin:node/recommended
|
- plugin:node/recommended
|
||||||
- plugin:security/recommended
|
- plugin:security/recommended
|
||||||
|
|
||||||
@@ -18,17 +19,10 @@ rules:
|
|||||||
node/no-unsupported-features/es-syntax: off
|
node/no-unsupported-features/es-syntax: off
|
||||||
node/no-unsupported-features/node-builtins: off
|
node/no-unsupported-features/node-builtins: off
|
||||||
node/no-unpublished-require: off
|
node/no-unpublished-require: off
|
||||||
|
node/no-unpublished-import: off
|
||||||
|
|
||||||
security/detect-non-literal-fs-filename: off
|
security/detect-non-literal-fs-filename: off
|
||||||
security/detect-object-injection: off
|
security/detect-object-injection: off
|
||||||
|
|
||||||
eol-last: [error, always]
|
|
||||||
eqeqeq: error
|
|
||||||
no-alert: warn
|
|
||||||
no-console: warn
|
|
||||||
no-path-concat: error
|
|
||||||
no-unused-vars: [error, {argsIgnorePattern: "^_|err|event|next|reject"}]
|
no-unused-vars: [error, {argsIgnorePattern: "^_|err|event|next|reject"}]
|
||||||
no-var: error
|
require-atomic-updates: warn
|
||||||
one-var: [error, never]
|
|
||||||
prefer-const: error
|
|
||||||
quotes: [error, single, {avoidEscape: true}]
|
|
||||||
|
|||||||
71
.gitlab-ci.yml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
image: "node:12-slim"
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- artifact
|
||||||
|
- release
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
# Install dependencies
|
||||||
|
- apt-get update
|
||||||
|
- apt-get install -y git python3 build-essential libxtst6
|
||||||
|
|
||||||
|
# Prepare Chrome for puppeteer
|
||||||
|
- apt-get install -y wget gnupg
|
||||||
|
- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
|
||||||
|
- sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
|
||||||
|
- apt-get update
|
||||||
|
- apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 --no-install-recommends
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- npm ci
|
||||||
|
- npm run lint
|
||||||
|
- npm test
|
||||||
|
|
||||||
|
# Release Docker image artifact for easy testing
|
||||||
|
artifact-docker:
|
||||||
|
stage: artifact
|
||||||
|
image: docker:latest
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
before_script: []
|
||||||
|
script:
|
||||||
|
- export IMG_NAME=registry.gitlab.com/timvisee/send:master-$CI_COMMIT_SHA
|
||||||
|
|
||||||
|
# Login in to registry
|
||||||
|
- 'docker login registry.gitlab.com -u $DOCKER_USER -p $DOCKER_PASS'
|
||||||
|
|
||||||
|
# Build and push image, report image name
|
||||||
|
- docker build -t $IMG_NAME .
|
||||||
|
- docker push $IMG_NAME
|
||||||
|
- 'echo Docker image artifact published, available as:'
|
||||||
|
- 'echo " docker pull $IMG_NAME"'
|
||||||
|
|
||||||
|
# Release public Docker image
|
||||||
|
release-docker:
|
||||||
|
stage: release
|
||||||
|
image: docker:latest
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
only:
|
||||||
|
- /^v(\d+\.)*\d+$/
|
||||||
|
before_script: []
|
||||||
|
script:
|
||||||
|
- export IMG_NAME=registry.gitlab.com/timvisee/send:$CI_COMMIT_REF_NAME
|
||||||
|
- export IMG_NAME_LATEST=registry.gitlab.com/timvisee/send:latest
|
||||||
|
|
||||||
|
# Login in to registry
|
||||||
|
- 'docker login registry.gitlab.com -u $DOCKER_USER -p $DOCKER_PASS'
|
||||||
|
|
||||||
|
# Build and push image, report image name
|
||||||
|
- docker build -t $IMG_NAME .
|
||||||
|
- docker tag $IMG_NAME $IMG_NAME_LATEST
|
||||||
|
- docker push $IMG_NAME
|
||||||
|
- docker push $IMG_NAME_LATEST
|
||||||
|
- 'echo Docker image artifact published, available as:'
|
||||||
|
- 'echo " docker pull $IMG_NAME_LATEST"'
|
||||||
|
- 'echo " docker pull $IMG_NAME"'
|
||||||
15
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Community Participation Guidelines
|
||||||
|
|
||||||
|
This repository is governed by Mozilla's code of conduct and etiquette guidelines.
|
||||||
|
For more details, please read the
|
||||||
|
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
|
||||||
|
|
||||||
|
## How to Report
|
||||||
|
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
## Project Specific Etiquette
|
||||||
|
|
||||||
|
In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
|
||||||
|
Please update for your project.
|
||||||
|
-->
|
||||||
115
CONTRIBUTORS
@@ -1,72 +1,121 @@
|
|||||||
Abdalrahman Hwoij
|
Abdalrahman Hwoij
|
||||||
Abhinav Adduri
|
Abhinav Adduri
|
||||||
Adnan Kičin
|
Adnan Kičin
|
||||||
|
Adolfo Jayme Barrientos
|
||||||
Alberto Castro
|
Alberto Castro
|
||||||
Alexander Slovesnik
|
Alexander Slovesnik
|
||||||
Alfredos-Panagiotis Damkalis
|
Alfredos-Panagiotis Damkalis
|
||||||
|
Aman Alam
|
||||||
Amin Mahmudian
|
Amin Mahmudian
|
||||||
|
Ander Elortondo
|
||||||
Andreas Pettersson
|
Andreas Pettersson
|
||||||
|
Anesu Chiodza
|
||||||
|
Anika Dorn
|
||||||
|
Anish Sheela
|
||||||
Arash Mousavi
|
Arash Mousavi
|
||||||
Artem Polivanchuk
|
Artem Polivanchuk
|
||||||
Ashikur Rahman
|
Ashikur Rahman
|
||||||
|
Ashok kumar
|
||||||
|
Balasankar C
|
||||||
Balázs Meskó
|
Balázs Meskó
|
||||||
Belayet Hossain
|
Belayet Hossain
|
||||||
|
Benjamin Forehand Jr
|
||||||
Besnik Bleta
|
Besnik Bleta
|
||||||
|
Björn I
|
||||||
Bjørn I
|
Bjørn I
|
||||||
Boopesh Mahendran
|
Boopesh Mahendran
|
||||||
|
Brahim Essaidi
|
||||||
|
Brainlulz
|
||||||
Breana Gonzales
|
Breana Gonzales
|
||||||
|
Christian Elbrianno
|
||||||
|
Christoph Kührer
|
||||||
|
Christopher Ramírez
|
||||||
Chuck Harmston
|
Chuck Harmston
|
||||||
|
Cloney 173741
|
||||||
Cláudio Esperança
|
Cláudio Esperança
|
||||||
Cristian Silaghi
|
Cristian Silaghi
|
||||||
Cynthia Pereira
|
Cynthia Pereira
|
||||||
Daniel Thorn
|
Daniel Thorn
|
||||||
Daniela Arcese
|
Daniela Arcese
|
||||||
Danny Coates
|
Danny Coates
|
||||||
|
Davide
|
||||||
Derek Tamsen
|
Derek Tamsen
|
||||||
|
Dhyey Thakore
|
||||||
|
Donovan Preston
|
||||||
|
Edi Santoso
|
||||||
Edmund Huggett
|
Edmund Huggett
|
||||||
Elisa X
|
Elisa X
|
||||||
|
Emily
|
||||||
|
Emily Hou
|
||||||
Emin Mastizada
|
Emin Mastizada
|
||||||
Enol
|
Enol
|
||||||
Erica
|
Erica
|
||||||
Erica Wright
|
Erica Wright
|
||||||
|
Fauzan Alfi
|
||||||
Filip Hruška
|
Filip Hruška
|
||||||
Fjoerfoks
|
Fjoerfoks
|
||||||
Francesco Lodolo
|
Francesco Lodolo
|
||||||
Francesco Lodolo [:flod]
|
Francesco Lodolo [:flod]
|
||||||
Frederick Villaluna
|
Frederick Villaluna
|
||||||
|
G12r
|
||||||
Gabriela
|
Gabriela
|
||||||
Gautam krishna.R
|
Gautam krishna.R
|
||||||
|
George Raptis
|
||||||
Georgianizator
|
Georgianizator
|
||||||
Gonçalo Matos
|
Gonçalo Matos
|
||||||
|
Gwenn
|
||||||
|
Hampus
|
||||||
|
Hugo
|
||||||
|
Hugo Abreu
|
||||||
Hyeonseok Shin
|
Hyeonseok Shin
|
||||||
Håvar Henriksen
|
Håvar Henriksen
|
||||||
|
Ian Neal
|
||||||
|
ItielMaN
|
||||||
Jae Hyeon Park
|
Jae Hyeon Park
|
||||||
|
Jakob Kappel
|
||||||
Jakub Rychlý
|
Jakub Rychlý
|
||||||
Jamie
|
Jamie
|
||||||
|
Jarmo
|
||||||
Jim Spentzos
|
Jim Spentzos
|
||||||
|
Jiri Grönroos
|
||||||
Jobava
|
Jobava
|
||||||
|
Joe Becher
|
||||||
|
Joe ST
|
||||||
|
Joergen
|
||||||
Johann-S
|
Johann-S
|
||||||
John Gruen
|
John Gruen
|
||||||
|
Jon Buckley
|
||||||
Jon Vadillo
|
Jon Vadillo
|
||||||
Jonathan Claudius
|
Jonathan Claudius
|
||||||
Jordi Cuevas
|
Jordi Cuevas
|
||||||
Jordi Serratosa
|
Jordi Serratosa
|
||||||
Juan Esteban Ajsivinac Sián
|
Juan Esteban Ajsivinac Sián
|
||||||
|
Juan Sián
|
||||||
Juraj Cigáň
|
Juraj Cigáň
|
||||||
Kerim Kalamujić
|
Kerim Kalamujić
|
||||||
Khaled Hosny
|
Khaled Hosny
|
||||||
|
Kim Ludvigsen
|
||||||
|
Kim Younggeon
|
||||||
Kohei Yoshino
|
Kohei Yoshino
|
||||||
Lan Glad
|
Lan Glad
|
||||||
|
Lasse Liehu
|
||||||
Laurent Jouanneau
|
Laurent Jouanneau
|
||||||
Lobodzets
|
Lobodzets
|
||||||
LuFlo
|
LuFlo
|
||||||
|
Luis A. Sánchez
|
||||||
Luiz Carlos de Morais
|
Luiz Carlos de Morais
|
||||||
|
Luiz Felipe F M Costa
|
||||||
Luna Jernberg
|
Luna Jernberg
|
||||||
|
Mahay Alam Khan
|
||||||
|
Marcelo Ghelman
|
||||||
Marcelo Poli
|
Marcelo Poli
|
||||||
Marco Aurélio
|
Marco Aurélio
|
||||||
Mark Heijl
|
Mark Heijl
|
||||||
Mark Liang
|
Mark Liang
|
||||||
|
Mark Liang (You-Wen)
|
||||||
Marko Andrejić
|
Marko Andrejić
|
||||||
|
Martijn Dekker
|
||||||
|
Marwan Mohamad
|
||||||
Matjaž Horvat
|
Matjaž Horvat
|
||||||
Maykon Chagas
|
Maykon Chagas
|
||||||
Melo46
|
Melo46
|
||||||
@@ -75,24 +124,43 @@ Michael Köhler
|
|||||||
Michael Wolf
|
Michael Wolf
|
||||||
Michal Stanke
|
Michal Stanke
|
||||||
Michal Vašíček
|
Michal Vašíček
|
||||||
|
Mikeyy
|
||||||
|
Miro Rauhala
|
||||||
Mozilla Pontoon
|
Mozilla Pontoon
|
||||||
|
Mozilla-GitHub-Standards
|
||||||
|
Mozinet
|
||||||
Moḥend Belqasem
|
Moḥend Belqasem
|
||||||
|
Muhend Belkacem
|
||||||
Muḥend Belqasem
|
Muḥend Belqasem
|
||||||
|
Myungjae Won
|
||||||
Nicholas Skinsacos
|
Nicholas Skinsacos
|
||||||
Nihad
|
Nihad
|
||||||
Nihad Suljić
|
Nihad Suljić
|
||||||
|
Niksend Mizuhara
|
||||||
Oscar
|
Oscar
|
||||||
|
Paulius
|
||||||
|
Pedro Burlamaqui Bendahan
|
||||||
Peter deHaan
|
Peter deHaan
|
||||||
Pierre Neter
|
Pierre Neter
|
||||||
Pin-guang Chen
|
Pin-guang Chen
|
||||||
|
Piotr Drąg
|
||||||
|
Quentí
|
||||||
|
Quế Tùng
|
||||||
|
Rachel Tublitz
|
||||||
Radu Popescu
|
Radu Popescu
|
||||||
Rhoslyn Prys
|
Rhoslyn Prys
|
||||||
RickieES
|
RickieES
|
||||||
|
Rimas Kudelis
|
||||||
Rizky Ariestiyansyah
|
Rizky Ariestiyansyah
|
||||||
|
Rob Powell
|
||||||
|
Robert
|
||||||
Roberto Alvarado
|
Roberto Alvarado
|
||||||
Rodrigo
|
Rodrigo
|
||||||
Rodrigo Guerra
|
Rodrigo Guerra
|
||||||
Rok Žerdin
|
Rok Žerdin
|
||||||
|
Romi Hardiyanto
|
||||||
|
Rongjian Zhang
|
||||||
|
Ruba
|
||||||
Sahithi
|
Sahithi
|
||||||
Sairam Raavi
|
Sairam Raavi
|
||||||
Sander Lepik
|
Sander Lepik
|
||||||
@@ -101,57 +169,104 @@ Sara Todaro
|
|||||||
Sav22999
|
Sav22999
|
||||||
Schieck :)
|
Schieck :)
|
||||||
Selim Şumlu
|
Selim Şumlu
|
||||||
|
Selyan Sliman Amiri
|
||||||
|
Sidak Singh Aulakh
|
||||||
Slimane Amiri
|
Slimane Amiri
|
||||||
|
Slimane Selyan AMIRI
|
||||||
Soumya Himanish Mohapatra
|
Soumya Himanish Mohapatra
|
||||||
Staś Małolepszy
|
Staś Małolepszy
|
||||||
|
Suriyaa ✌️️
|
||||||
Tema
|
Tema
|
||||||
Thomas Dalichow
|
Thomas Dalichow
|
||||||
Théo Chevalier
|
Théo Chevalier
|
||||||
Tiago Morais Morgado
|
Tiago Morais Morgado
|
||||||
|
Tim Visée
|
||||||
|
Tomer Cohen
|
||||||
Tomáš Zelina
|
Tomáš Zelina
|
||||||
Ton
|
Ton
|
||||||
|
Top
|
||||||
Tymur Faradzhev
|
Tymur Faradzhev
|
||||||
Uccen Marzuq
|
Uccen Marzuq
|
||||||
Varghese Thomas
|
Varghese Thomas
|
||||||
Victor Bychek
|
Victor Bychek
|
||||||
|
Vimal Raghubir
|
||||||
Vitaliy Krutko
|
Vitaliy Krutko
|
||||||
Weihang Lo
|
Weihang Lo
|
||||||
|
Wiktor Furman
|
||||||
Wil Clouser
|
Wil Clouser
|
||||||
YFdyh000
|
YFdyh000
|
||||||
|
Yassine Aït-El-Mouden
|
||||||
|
Yongmin H
|
||||||
You-Wen Liang (Mark)
|
You-Wen Liang (Mark)
|
||||||
|
aaaaalbert
|
||||||
aefgh39622
|
aefgh39622
|
||||||
|
alamanda
|
||||||
albertdcastro
|
albertdcastro
|
||||||
alex_mayorga
|
alex_mayorga
|
||||||
ariestiyansyah
|
ariestiyansyah
|
||||||
avelper
|
avelper
|
||||||
|
chilledfrogs
|
||||||
|
clouserw-mozilla-owner
|
||||||
dgadelha
|
dgadelha
|
||||||
|
dskmori
|
||||||
ehuggett
|
ehuggett
|
||||||
eljuno
|
eljuno
|
||||||
|
emily-hou1
|
||||||
erdem cobanoglu
|
erdem cobanoglu
|
||||||
gautamkrishnar
|
gautamkrishnar
|
||||||
gmontagu
|
gmontagu
|
||||||
goofy
|
goofy
|
||||||
hello
|
hello
|
||||||
hi
|
hi
|
||||||
|
ivan.pompa
|
||||||
jesferman1993
|
jesferman1993
|
||||||
jlG
|
jlG
|
||||||
josotrix
|
josotrix
|
||||||
jspam
|
jspam
|
||||||
|
julen
|
||||||
|
julenx
|
||||||
kenrick95
|
kenrick95
|
||||||
|
kumincir
|
||||||
|
leo.toneff
|
||||||
|
m4hdi.pdroid
|
||||||
|
mail
|
||||||
manxmensch
|
manxmensch
|
||||||
|
marigalicer
|
||||||
|
marsf
|
||||||
|
merianosnikos
|
||||||
mirzet.omerovic.1992
|
mirzet.omerovic.1992
|
||||||
|
mujeebcpy
|
||||||
|
p.sanroman.bengoetxea
|
||||||
|
passionforlife
|
||||||
|
paul.trevor
|
||||||
|
pyup.io bot
|
||||||
ravmn
|
ravmn
|
||||||
rcmainak
|
rcmainak
|
||||||
reza.habibi2008
|
reza.habibi2008
|
||||||
|
rgpublic
|
||||||
|
risger
|
||||||
|
robbp
|
||||||
|
ruikunai
|
||||||
savemore99.sm
|
savemore99.sm
|
||||||
|
sergio
|
||||||
shikhar-scs
|
shikhar-scs
|
||||||
siparon
|
siparon
|
||||||
skystar-p
|
skystar-p
|
||||||
|
stripTM
|
||||||
|
tatalmondmush
|
||||||
tiagomoraismorgado
|
tiagomoraismorgado
|
||||||
timvisee
|
timvisee
|
||||||
|
victor.gonzalezro
|
||||||
xcffl
|
xcffl
|
||||||
ybouhamam
|
ybouhamam
|
||||||
|
yoshimitsu002
|
||||||
|
yusup.ramdani
|
||||||
Μιχάλης
|
Μιχάλης
|
||||||
Марко Костић (Marko Kostić)
|
Марко Костић (Marko Kostić)
|
||||||
|
Ратко Вујановић
|
||||||
صفا الفليج
|
صفا الفليج
|
||||||
వీవెన్
|
వీవెన్
|
||||||
|
ജോയ്സ്
|
||||||
|
张无忌
|
||||||
|
新垣结衣松冈茉优长泽雅美门胁麦上野树里石原里美
|
||||||
|
莫非前世那一眼
|
||||||
|
|||||||
62
Dockerfile
@@ -1,17 +1,59 @@
|
|||||||
FROM node:10 AS builder
|
##
|
||||||
RUN addgroup --gid 10001 app && adduser --disabled-password --gecos '' --gid 10001 --home /app --uid 10001 app
|
# Send
|
||||||
COPY package*.json /app/
|
#
|
||||||
WORKDIR /app
|
# License https://gitlab.com/timvisee/send/blob/master/LICENSE
|
||||||
RUN npm install --production
|
##
|
||||||
|
|
||||||
FROM node:10-slim
|
|
||||||
RUN addgroup --gid 10001 app && adduser --disabled-password --gecos '' --gid 10001 --home /app --uid 10001 app
|
# Build project
|
||||||
|
FROM node:12 AS builder
|
||||||
|
RUN set -x \
|
||||||
|
# Add user
|
||||||
|
&& addgroup --gid 10001 app \
|
||||||
|
&& adduser --disabled-password \
|
||||||
|
--gecos '' \
|
||||||
|
--gid 10001 \
|
||||||
|
--home /app \
|
||||||
|
--uid 10001 \
|
||||||
|
app
|
||||||
|
COPY --chown=app:app . /app
|
||||||
USER app
|
USER app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --chown=app:app --from=builder /app .
|
RUN set -x \
|
||||||
COPY --chown=app:app . .
|
# Build
|
||||||
|
&& PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm ci \
|
||||||
|
&& npm run build
|
||||||
|
|
||||||
|
|
||||||
|
# Main image
|
||||||
|
FROM node:12-slim
|
||||||
|
RUN set -x \
|
||||||
|
# Add user
|
||||||
|
&& addgroup --gid 10001 app \
|
||||||
|
&& adduser --disabled-password \
|
||||||
|
--gecos '' \
|
||||||
|
--gid 10001 \
|
||||||
|
--home /app \
|
||||||
|
--uid 10001 \
|
||||||
|
app
|
||||||
|
RUN apt-get update && apt-get -y install \
|
||||||
|
git-core \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
USER app
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --chown=app:app package*.json ./
|
||||||
|
COPY --chown=app:app app app
|
||||||
|
COPY --chown=app:app common common
|
||||||
|
COPY --chown=app:app public/locales public/locales
|
||||||
|
COPY --chown=app:app server server
|
||||||
|
COPY --chown=app:app --from=builder /app/dist dist
|
||||||
|
|
||||||
|
RUN npm ci --production && npm cache clean --force
|
||||||
|
RUN mkdir -p /app/.config/configstore
|
||||||
|
RUN ln -s dist/version.json version.json
|
||||||
|
|
||||||
ENV PORT=1443
|
ENV PORT=1443
|
||||||
EXPOSE $PORT
|
|
||||||
|
EXPOSE ${PORT}
|
||||||
|
|
||||||
CMD ["node", "server/bin/prod.js"]
|
CMD ["node", "server/bin/prod.js"]
|
||||||
|
|||||||
74
README.md
@@ -1,6 +1,57 @@
|
|||||||
# Firefox Send
|
# [](https://gitlab.com/timvisee/send/) Send
|
||||||
|
|
||||||
[](https://circleci.com/gh/mozilla/send)
|
[![Build status on GitLab CI][gitlab-ci-master-badge]][gitlab-ci-link]
|
||||||
|
[![Latest release][release-badge]][release-link]
|
||||||
|
[![Docker image][docker-image-badge]][docker-image-link]
|
||||||
|
[![Project license][repo-license-badge]](LICENSE)
|
||||||
|
|
||||||
|
[docker-image-badge]: https://img.shields.io/badge/docker-latest-blue.svg
|
||||||
|
[docker-image-link]: https://gitlab.com/timvisee/send/container_registry/eyJuYW1lIjoidGltdmlzZWUvc2VuZCIsInRhZ3NfcGF0aCI6Ii90aW12aXNlZS9zZW5kL3JlZ2lzdHJ5L3JlcG9zaXRvcnkvMTQxODUwNC90YWdzP2Zvcm1hdD1qc29uIiwiaWQiOjE0MTg1MDQsImNsZWFudXBfcG9saWN5X3N0YXJ0ZWRfYXQiOm51bGx9
|
||||||
|
[gitlab-ci-link]: https://gitlab.com/timvisee/send/pipelines
|
||||||
|
[gitlab-ci-master-badge]: https://gitlab.com/timvisee/send/badges/master/pipeline.svg
|
||||||
|
[release-badge]: https://img.shields.io/github/v/tag/timvisee/send
|
||||||
|
[release-link]: https://gitlab.com/timvisee/send/-/
|
||||||
|
[repo-license-badge]: https://img.shields.io/github/license/timvisee/send.svg
|
||||||
|
|
||||||
|
A fork of Mozilla's [Firefox Send][mozilla-send].
|
||||||
|
Mozilla discontinued Send, this fork is a community effort to keep the project
|
||||||
|
up-to-date and alive.
|
||||||
|
|
||||||
|
- Forked [at][fork-commit] Mozilla's last publicly hosted version
|
||||||
|
- _Mozilla_ & _Firefox_ branding [is][remove-branding-pr] removed so you can legally self-host
|
||||||
|
- Kept compatible with [`ffsend`][ffsend] (CLI for Send)
|
||||||
|
- Dependencies have been updated
|
||||||
|
- Mozilla's [changes][mozilla-patches] since the fork have been selectively [merged][mozilla-patches-pr]
|
||||||
|
- Mozilla's experimental report feature, download tokens, trust warnings and FxA changes are not included
|
||||||
|
|
||||||
|
Find an up-to-date Docker image here: [docs/docker.md](docs/docker.md)
|
||||||
|
|
||||||
|
The original project by Mozilla can be found [here][mozilla-send].
|
||||||
|
The [`mozilla-master`][branch-mozilla-master] branch holds the `master` branch
|
||||||
|
as left by Mozilla.
|
||||||
|
The [`send-v3`][branch-send-v3] branch holds the commit tree of Mozilla's last
|
||||||
|
publicly hosted version, which this fork is based on.
|
||||||
|
The [`send-v4`][branch-send-v4] branch holds the commit tree of Mozilla's last
|
||||||
|
experimental version which was still a work in progress (featuring file
|
||||||
|
reporting, download tokens, trust warnings and FxA changes), this has
|
||||||
|
selectively been merged into this fork.
|
||||||
|
Please consider to [donate][donate] to allow me to keep working on this.
|
||||||
|
|
||||||
|
Thanks [Mozilla][mozilla] for building this amazing tool!
|
||||||
|
|
||||||
|
[branch-mozilla-master]: https://gitlab.com/timvisee/send/-/tree/mozilla-master
|
||||||
|
[branch-send-v3]: https://gitlab.com/timvisee/send/-/tree/send-v3
|
||||||
|
[branch-send-v4]: https://gitlab.com/timvisee/send/-/tree/send-v4
|
||||||
|
[donate]: https://timvisee.com/donate
|
||||||
|
[ffsend]: https://github.com/timvisee/ffsend
|
||||||
|
[fork-commit]: https://gitlab.com/timvisee/send/-/commit/3e9be676413a6e1baaf6a354c180e91899d10bec
|
||||||
|
[mozilla-patches-pr]: https://gitlab.com/timvisee/send/-/merge_requests/3
|
||||||
|
[mozilla-patches]: https://gitlab.com/timvisee/send/-/compare/3e9be676413a6e1baaf6a354c180e91899d10bec...mozilla-master
|
||||||
|
[mozilla-send]: https://github.com/mozilla/send
|
||||||
|
[mozilla]: https://mozilla.org/
|
||||||
|
[remove-branding-pr]: https://gitlab.com/timvisee/send/-/merge_requests/2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Docs:** [FAQ](docs/faq.md), [Encryption](docs/encryption.md), [Build](docs/build.md), [Docker](docs/docker.md), [Metrics](docs/metrics.md), [More](docs/)
|
**Docs:** [FAQ](docs/faq.md), [Encryption](docs/encryption.md), [Build](docs/build.md), [Docker](docs/docker.md), [Metrics](docs/metrics.md), [More](docs/)
|
||||||
|
|
||||||
@@ -16,6 +67,7 @@
|
|||||||
* [Localization](#localization)
|
* [Localization](#localization)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
* [Testing](#testing)
|
* [Testing](#testing)
|
||||||
|
* [Deployment](#deployment)
|
||||||
* [Android](#android)
|
* [Android](#android)
|
||||||
* [License](#license)
|
* [License](#license)
|
||||||
|
|
||||||
@@ -29,22 +81,22 @@ A file sharing experiment which allows you to send encrypted files to other user
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- [Node.js 10.0+](https://nodejs.org/)
|
- [Node.js 12.x](https://nodejs.org/)
|
||||||
- [Redis server](https://redis.io/) (optional for development)
|
- [Redis server](https://redis.io/) (optional for development)
|
||||||
- [AWS S3](https://aws.amazon.com/s3/) or compatible service. (optional)
|
- [AWS S3](https://aws.amazon.com/s3/) or compatible service (optional)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
To start an ephemeral development server run:
|
To start an ephemeral development server, run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Then browse to http://localhost:8080
|
Then, browse to http://localhost:8080
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -69,7 +121,7 @@ The server is configured with environment variables. See [server/config.js](serv
|
|||||||
|
|
||||||
## Localization
|
## 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 Mozilla’s [l10n-drivers](https://wiki.mozilla.org/L10n:Mozilla_Team#Mozilla_Corporation) for guidance.
|
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 Mozilla’s [l10n-drivers](https://wiki.mozilla.org/L10n:Mozilla_Team#Mozilla_Corporation) for guidance.
|
||||||
|
|
||||||
see also [docs/localization.md](docs/localization.md)
|
see also [docs/localization.md](docs/localization.md)
|
||||||
|
|
||||||
@@ -91,6 +143,12 @@ Pull requests are always welcome! Feel free to check out the list of ["good firs
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
see also [docs/deployment.md](docs/deployment.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Android
|
## Android
|
||||||
|
|
||||||
The android implementation is contained in the `android` directory, and can be viewed locally for easy testing and editing by running `ANDROID=1 npm start` and then visiting <http://localhost:8080>. CSS and image files are located in the `android/app/src/main/assets` directory.
|
The android implementation is contained in the `android` directory, and can be viewed locally for easy testing and editing by running `ANDROID=1 npm start` and then visiting <http://localhost:8080>. CSS and image files are located in the `android/app/src/main/assets` directory.
|
||||||
@@ -101,4 +159,6 @@ The android implementation is contained in the `android` directory, and can be v
|
|||||||
|
|
||||||
[Mozilla Public License Version 2.0](LICENSE)
|
[Mozilla Public License Version 2.0](LICENSE)
|
||||||
|
|
||||||
|
[qrcode.js](https://github.com/kazuhikoarase/qrcode-generator) licensed under MIT
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
/* global window, navigator */
|
import 'intl-pluralrules';
|
||||||
import 'fluent-intl-polyfill';
|
|
||||||
import choo from 'choo';
|
import choo from 'choo';
|
||||||
import html from 'choo/html';
|
import html from 'choo/html';
|
||||||
import Raven from 'raven-js';
|
import * as Sentry from '@sentry/browser';
|
||||||
|
|
||||||
import { setApiUrlPrefix, getConstants } from '../app/api';
|
import { setApiUrlPrefix, getConstants } from '../app/api';
|
||||||
import metrics from '../app/metrics';
|
import metrics from '../app/metrics';
|
||||||
@@ -19,10 +18,12 @@ import share from './pages/share';
|
|||||||
import preferences from './pages/preferences';
|
import preferences from './pages/preferences';
|
||||||
import error from './pages/error';
|
import error from './pages/error';
|
||||||
import { getTranslator } from '../app/locale';
|
import { getTranslator } from '../app/locale';
|
||||||
|
import { setTranslate } from '../app/utils';
|
||||||
|
|
||||||
import { delay } from '../app/utils';
|
import { delay } from '../app/utils';
|
||||||
|
|
||||||
if (navigator.userAgent === 'Send Android') {
|
if (navigator.userAgent === 'Send Android') {
|
||||||
setApiUrlPrefix('https://send2.dev.lcip.org');
|
setApiUrlPrefix('https://send.firefox.com');
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = choo();
|
const app = choo();
|
||||||
@@ -51,7 +52,7 @@ function body(main) {
|
|||||||
|
|
||||||
const menu = html`<a
|
const menu = html`<a
|
||||||
id="hamburger"
|
id="hamburger"
|
||||||
class="absolute pin-t pin-r z-50"
|
class="absolute top-0 right-0 z-50"
|
||||||
href="#"
|
href="#"
|
||||||
onclick="${clickPreferences}"
|
onclick="${clickPreferences}"
|
||||||
>
|
>
|
||||||
@@ -59,9 +60,7 @@ function body(main) {
|
|||||||
</a>`;
|
</a>`;
|
||||||
*/
|
*/
|
||||||
return html`
|
return html`
|
||||||
<body
|
<body class="flex flex-col items-center font-sans bg-grey-10 h-screen">
|
||||||
class="flex flex-col items-center font-sans bg-grey-lightest h-screen"
|
|
||||||
>
|
|
||||||
${state.cache(Header, 'header').render()} ${main(state, emit)}
|
${state.cache(Header, 'header').render()} ${main(state, emit)}
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
@@ -69,6 +68,7 @@ function body(main) {
|
|||||||
}
|
}
|
||||||
(async function start() {
|
(async function start() {
|
||||||
const translate = await getTranslator('en-US');
|
const translate = await getTranslator('en-US');
|
||||||
|
setTranslate(translate);
|
||||||
const { LIMITS, DEFAULTS } = await getConstants();
|
const { LIMITS, DEFAULTS } = await getConstants();
|
||||||
app.use(state => {
|
app.use(state => {
|
||||||
state.LIMITS = LIMITS;
|
state.LIMITS = LIMITS;
|
||||||
@@ -80,7 +80,7 @@ function body(main) {
|
|||||||
state.archive = new Archive([], DEFAULTS.EXPIRE_SECONDS);
|
state.archive = new Archive([], DEFAULTS.EXPIRE_SECONDS);
|
||||||
state.storage = storage;
|
state.storage = storage;
|
||||||
state.user = new User(storage, LIMITS);
|
state.user = new User(storage, LIMITS);
|
||||||
state.raven = Raven;
|
state.sentry = Sentry;
|
||||||
});
|
});
|
||||||
app.use(metrics);
|
app.use(metrics);
|
||||||
app.route('/', body(home));
|
app.route('/', body(home));
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
compileSdkVersion 27
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.mozilla.sendandroid"
|
applicationId "org.mozilla.firefoxsend"
|
||||||
minSdkVersion 26
|
minSdkVersion 26
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode 1
|
versionCode 1
|
||||||
@@ -31,7 +29,7 @@ dependencies {
|
|||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
implementation 'com.github.delight-im:Android-AdvancedWebView:v3.0.0'
|
implementation 'com.github.delight-im:Android-AdvancedWebView:v3.0.0'
|
||||||
implementation "org.mozilla.components:service-firefox-accounts:${rootProject.ext.android_components_version}"
|
implementation "org.mozilla.components:service-firefox-accounts:$android_components_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
task generateAndLinkBundle(type: Exec, description: 'Generate the android.js bundle and link it into the assets directory') {
|
task generateAndLinkBundle(type: Exec, description: 'Generate the android.js bundle and link it into the assets directory') {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.mozilla.sendandroid">
|
package="org.mozilla.firefoxsend">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" android:value="false" />
|
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" android:value="false" />
|
||||||
<activity android:name=".MainActivity" android:screenOrientation="portrait">
|
<activity android:name="org.mozilla.firefoxsend.MainActivity" android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
package org.mozilla.firefoxsend
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.app.AppCompatActivity
|
||||||
|
import android.util.Base64
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.webkit.*
|
||||||
|
import im.delight.android.webview.AdvancedWebView
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
|
import mozilla.components.service.fxa.Config
|
||||||
|
import mozilla.components.service.fxa.FirefoxAccount
|
||||||
|
import mozilla.components.service.fxa.FxaResult
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
internal class LoggingWebChromeClient : WebChromeClient() {
|
||||||
|
override fun onConsoleMessage(cm: ConsoleMessage): Boolean {
|
||||||
|
Log.d(TAG, String.format("%s @ %d: %s",
|
||||||
|
cm.message(), cm.lineNumber(), cm.sourceId()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "CONTENT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebAppInterface(private val mContext: MainActivity) {
|
||||||
|
@JavascriptInterface
|
||||||
|
fun beginOAuthFlow() {
|
||||||
|
mContext.beginOAuthFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun shareUrl(url: String) {
|
||||||
|
mContext.shareUrl(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
|
||||||
|
|
||||||
|
private var mToShare: String? = null
|
||||||
|
private var mToCall: String? = null
|
||||||
|
private var mAccount: FirefoxAccount? = null
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
|
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
|
||||||
|
webView.apply {
|
||||||
|
setListener(this@MainActivity, this@MainActivity)
|
||||||
|
addJavascriptInterface(WebAppInterface(this@MainActivity), JS_INTERFACE_NAME)
|
||||||
|
setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||||
|
webChromeClient = LoggingWebChromeClient()
|
||||||
|
|
||||||
|
settings.apply {
|
||||||
|
userAgentString = "Send Android"
|
||||||
|
allowUniversalAccessFromFileURLs = true
|
||||||
|
javaScriptEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val type = intent.type
|
||||||
|
if (Intent.ACTION_SEND == intent.action && type != null) {
|
||||||
|
if (type == "text/plain") {
|
||||||
|
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
|
// Log.d(TAG_INTENT, "text/plain $sharedText")
|
||||||
|
mToShare = "data:text/plain;base64," + Base64.encodeToString(sharedText.toByteArray(), 16).trim()
|
||||||
|
} else if (type.startsWith("image/")) {
|
||||||
|
val imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri
|
||||||
|
// Log.d(TAG_INTENT, "image/ $imageUri")
|
||||||
|
mToShare = "data:text/plain;base64," + Base64.encodeToString(imageUri.path.toByteArray(), 16).trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webView.loadUrl("file:///android_asset/android.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun beginOAuthFlow() {
|
||||||
|
Config.release().then { value ->
|
||||||
|
mAccount = FirefoxAccount(value, "20f7931c9054d833", "https://send.firefox.com/fxa/android-redirect.html")
|
||||||
|
mAccount?.beginOAuthFlow(arrayOf("profile", "https://identity.mozilla.com/apps/send"), true)
|
||||||
|
?.then { url ->
|
||||||
|
// Log.d(TAG_CONFIG, "GOT A URL $url")
|
||||||
|
this@MainActivity.runOnUiThread {
|
||||||
|
webView.loadUrl(url)
|
||||||
|
}
|
||||||
|
FxaResult.fromValue(Unit)
|
||||||
|
}
|
||||||
|
// Log.d(TAG_CONFIG, "CREATED FIREFOXACCOUNT")
|
||||||
|
FxaResult.fromValue(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareUrl(url: String) {
|
||||||
|
val shareIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
type = "text/plain"
|
||||||
|
putExtra(Intent.EXTRA_TEXT, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
val components = arrayOf(ComponentName(applicationContext, MainActivity::class.java))
|
||||||
|
val chooser = Intent.createChooser(shareIntent, "")
|
||||||
|
.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, components)
|
||||||
|
|
||||||
|
startActivity(chooser)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
webView.onResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
webView.onPause()
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
webView.onDestroy()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, intent)
|
||||||
|
webView.onActivityResult(requestCode, resultCode, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (!webView.onBackPressed()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageStarted(url: String, favicon: Bitmap?) {
|
||||||
|
if (url.startsWith("https://send.firefox.com/fxa/android-redirect.html")) {
|
||||||
|
// We load this here so the user doesn't see the android-redirect.html page
|
||||||
|
webView.loadUrl("file:///android_asset/android.html")
|
||||||
|
|
||||||
|
val uri = Uri.parse(url)
|
||||||
|
uri.getQueryParameter("code")?.let { code ->
|
||||||
|
uri.getQueryParameter("state")?.let { state ->
|
||||||
|
mAccount?.completeOAuthFlow(code, state)?.whenComplete { info ->
|
||||||
|
mAccount?.getProfile(false)?.then { profile ->
|
||||||
|
val profileJsonPayload = JSONObject()
|
||||||
|
.put("accessToken", info.accessToken)
|
||||||
|
.put("keys", info.keys)
|
||||||
|
.put("avatar", profile.avatar)
|
||||||
|
.put("displayName", profile.displayName)
|
||||||
|
.put("email", profile.email)
|
||||||
|
.put("uid", profile.uid).toString()
|
||||||
|
mToCall = "finishLogin($profileJsonPayload)"
|
||||||
|
this@MainActivity.runOnUiThread {
|
||||||
|
// Clear the history so that the user can't use the back button to see broken pages
|
||||||
|
// that were inserted into the history by the login process.
|
||||||
|
webView.clearHistory()
|
||||||
|
|
||||||
|
// We also reload this here because we need to make sure onPageFinished runs after mToCall has been set.
|
||||||
|
// We can't guarantee that onPageFinished wasn't already called at this point.
|
||||||
|
webView.loadUrl("file:///android_asset/android.html")
|
||||||
|
}
|
||||||
|
FxaResult.fromValue(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!url.startsWith("file:///android_asset/") && !url.startsWith("https://accounts.firefox.com/")) {
|
||||||
|
// Don't allow loading anything other than the app in our webview
|
||||||
|
// It should be possible to do this with shouldOverrideUrlLoading
|
||||||
|
// but it didn't seem to be working, so this works as a hack.
|
||||||
|
webView.loadUrl("file:///android_asset/android.html")
|
||||||
|
Log.d(TAG_MAIN, "BAD URL " + url)
|
||||||
|
} else {
|
||||||
|
// Log.d(TAG_MAIN, "onPageStarted " + url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageFinished(url: String) {
|
||||||
|
// Log.d(TAG_MAIN, "onPageFinished")
|
||||||
|
if (mToShare != null) {
|
||||||
|
// Log.d(TAG_INTENT, mToShare)
|
||||||
|
|
||||||
|
webView.postWebMessage(WebMessage(mToShare), Uri.EMPTY)
|
||||||
|
mToShare = null
|
||||||
|
}
|
||||||
|
if (mToCall != null) {
|
||||||
|
this@MainActivity.runOnUiThread {
|
||||||
|
webView.evaluateJavascript(mToCall) {
|
||||||
|
mToCall = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageError(errorCode: Int, description: String, failingUrl: String) {
|
||||||
|
Log.d(TAG_MAIN, "onPageError($errorCode, $description, $failingUrl)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDownloadRequested(url: String,
|
||||||
|
suggestedFilename: String,
|
||||||
|
mimeType: String,
|
||||||
|
contentLength: Long,
|
||||||
|
contentDisposition: String,
|
||||||
|
userAgent: String) {
|
||||||
|
// Log.d(TAG_MAIN, "onDownloadRequested")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onExternalPageRequest(url: String) {
|
||||||
|
// Log.d(TAG_MAIN, "onExternalPageRequest($url)")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG_MAIN = "MAIN"
|
||||||
|
private const val TAG_INTENT = "INTENT"
|
||||||
|
private const val TAG_CONFIG = "CONFIG"
|
||||||
|
private const val JS_INTERFACE_NAME = "Android"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
package org.mozilla.sendandroid
|
|
||||||
|
|
||||||
|
|
||||||
import android.support.v7.app.AppCompatActivity
|
|
||||||
import android.os.Bundle
|
|
||||||
import im.delight.android.webview.AdvancedWebView
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.net.Uri
|
|
||||||
import android.webkit.WebView
|
|
||||||
import android.webkit.WebMessage
|
|
||||||
import android.util.Log
|
|
||||||
import android.util.Base64
|
|
||||||
import android.view.View
|
|
||||||
import android.webkit.ConsoleMessage
|
|
||||||
import android.webkit.JavascriptInterface
|
|
||||||
import android.webkit.WebChromeClient
|
|
||||||
import mozilla.components.service.fxa.Config
|
|
||||||
import mozilla.components.service.fxa.FirefoxAccount
|
|
||||||
import mozilla.components.service.fxa.OAuthInfo
|
|
||||||
import mozilla.components.service.fxa.Profile
|
|
||||||
import mozilla.components.service.fxa.FxaResult
|
|
||||||
|
|
||||||
internal class LoggingWebChromeClient : WebChromeClient() {
|
|
||||||
override fun onConsoleMessage(cm: ConsoleMessage): Boolean {
|
|
||||||
Log.w("CONTENT", String.format("%s @ %d: %s",
|
|
||||||
cm.message(), cm.lineNumber(), cm.sourceId()))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WebAppInterface(private val mContext: MainActivity) {
|
|
||||||
@JavascriptInterface
|
|
||||||
fun beginOAuthFlow() {
|
|
||||||
mContext.beginOAuthFlow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@JavascriptInterface
|
|
||||||
fun shareUrl(url: String) {
|
|
||||||
mContext.shareUrl(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
|
|
||||||
private var mWebView: AdvancedWebView? = null
|
|
||||||
private var mToShare: String? = null
|
|
||||||
private var mToCall: String? = null
|
|
||||||
private var mAccount: FirefoxAccount? = null
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
|
|
||||||
// https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews
|
|
||||||
// WebView.setWebContentsDebuggingEnabled(true); // TODO only dev builds
|
|
||||||
|
|
||||||
mWebView = findViewById<WebView>(R.id.webview) as AdvancedWebView
|
|
||||||
mWebView!!.setListener(this, this)
|
|
||||||
mWebView!!.setWebChromeClient(LoggingWebChromeClient())
|
|
||||||
mWebView!!.addJavascriptInterface(WebAppInterface(this), "Android")
|
|
||||||
mWebView!!.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
||||||
|
|
||||||
val webSettings = mWebView!!.getSettings()
|
|
||||||
webSettings.setUserAgentString("Send Android")
|
|
||||||
webSettings.setAllowUniversalAccessFromFileURLs(true)
|
|
||||||
webSettings.setJavaScriptEnabled(true)
|
|
||||||
|
|
||||||
val intent = getIntent()
|
|
||||||
val action = intent.getAction()
|
|
||||||
val type = intent.getType()
|
|
||||||
|
|
||||||
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
|
||||||
if (type.equals("text/plain")) {
|
|
||||||
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
|
|
||||||
Log.w("INTENT", "text/plain " + sharedText)
|
|
||||||
mToShare = "data:text/plain;base64," + Base64.encodeToString(sharedText.toByteArray(), 16).trim()
|
|
||||||
} else if (type.startsWith("image/")) {
|
|
||||||
val imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri
|
|
||||||
Log.w("INTENT", "image/ " + imageUri)
|
|
||||||
mToShare = "data:text/plain;base64," + Base64.encodeToString(imageUri.path.toByteArray(), 16).trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mWebView!!.loadUrl("file:///android_asset/android.html")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun beginOAuthFlow() {
|
|
||||||
Config.release().then(fun (value: Config): FxaResult<Unit> {
|
|
||||||
mAccount = FirefoxAccount(value, "20f7931c9054d833", "https://send.firefox.com/fxa/android-redirect.html")
|
|
||||||
mAccount?.beginOAuthFlow(arrayOf("profile", "https://identity.mozilla.com/apps/send"), true)?.then(fun (url: String): FxaResult<Unit> {
|
|
||||||
Log.w("CONFIG", "GOT A URL " + url)
|
|
||||||
this@MainActivity.runOnUiThread({
|
|
||||||
mWebView!!.loadUrl(url)
|
|
||||||
})
|
|
||||||
return FxaResult.fromValue(Unit)
|
|
||||||
})
|
|
||||||
Log.w("CONFIG", "CREATED FIREFOXACCOUNT")
|
|
||||||
return FxaResult.fromValue(Unit)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shareUrl(url: String) {
|
|
||||||
val shareIntent = Intent()
|
|
||||||
shareIntent.action = Intent.ACTION_SEND
|
|
||||||
shareIntent.type = "text/plain"
|
|
||||||
shareIntent.putExtra(Intent.EXTRA_TEXT, url)
|
|
||||||
val chooser = Intent.createChooser(shareIntent, "")
|
|
||||||
chooser.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(ComponentName(applicationContext, MainActivity::class.java)))
|
|
||||||
startActivity(chooser)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
mWebView!!.onResume()
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
override fun onPause() {
|
|
||||||
mWebView!!.onPause()
|
|
||||||
// ...
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
mWebView!!.onDestroy()
|
|
||||||
// ...
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, intent)
|
|
||||||
mWebView!!.onActivityResult(requestCode, resultCode, intent)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBackPressed() {
|
|
||||||
if (!mWebView!!.onBackPressed()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// ...
|
|
||||||
super.onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPageStarted(url: String, favicon: Bitmap?) {
|
|
||||||
if (url.startsWith("https://send.firefox.com/fxa/android-redirect.html")) {
|
|
||||||
// We load this here so the user doesn't see the android-redirect.html page
|
|
||||||
mWebView!!.loadUrl("file:///android_asset/android.html")
|
|
||||||
|
|
||||||
val parsed = Uri.parse(url)
|
|
||||||
val code = parsed.getQueryParameter("code")
|
|
||||||
val state = parsed.getQueryParameter("state")
|
|
||||||
|
|
||||||
code?.let { code ->
|
|
||||||
state?.let { state ->
|
|
||||||
mAccount?.completeOAuthFlow(code, state)?.whenComplete { info ->
|
|
||||||
//displayAndPersistProfile(code, state)
|
|
||||||
val profile = mAccount?.getProfile(false)?.then(fun (profile: Profile): FxaResult<Unit> {
|
|
||||||
val accessToken = info.accessToken
|
|
||||||
val keys = info.keys
|
|
||||||
val avatar = profile.avatar
|
|
||||||
val displayName = profile.displayName
|
|
||||||
val email = profile.email
|
|
||||||
val uid = profile.uid
|
|
||||||
val toPass = "{\"accessToken\": \"${accessToken}\", \"keys\": '${keys}', \"avatar\": \"${avatar}\", \"displayName\": \"${displayName}\", \"email\": \"${email}\", \"uid\": \"${uid}\"}"
|
|
||||||
mToCall = "finishLogin(${toPass})"
|
|
||||||
this@MainActivity.runOnUiThread({
|
|
||||||
// Clear the history so that the user can't use the back button to see broken pages
|
|
||||||
// that were inserted into the history by the login process.
|
|
||||||
mWebView!!.clearHistory()
|
|
||||||
|
|
||||||
// We also reload this here because we need to make sure onPageFinished runs after mToCall has been set.
|
|
||||||
// We can't guarantee that onPageFinished wasn't already called at this point.
|
|
||||||
mWebView!!.loadUrl("file:///android_asset/android.html")
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
return FxaResult.fromValue(Unit)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.w("MAIN", "onPageStarted");
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPageFinished(url: String) {
|
|
||||||
Log.w("MAIN", "onPageFinished")
|
|
||||||
if (mToShare != null) {
|
|
||||||
Log.w("INTENT", mToShare)
|
|
||||||
|
|
||||||
mWebView?.postWebMessage(WebMessage(mToShare), Uri.EMPTY)
|
|
||||||
mToShare = null
|
|
||||||
}
|
|
||||||
if (mToCall != null) {
|
|
||||||
this@MainActivity.runOnUiThread({
|
|
||||||
mWebView?.evaluateJavascript(mToCall, fun (value: String) {
|
|
||||||
mToCall = null
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPageError(errorCode: Int, description: String, failingUrl: String) {
|
|
||||||
Log.w("MAIN", "onPageError " + description)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDownloadRequested(url: String, suggestedFilename: String, mimeType: String, contentLength: Long, contentDisposition: String, userAgent: String) {
|
|
||||||
Log.w("MAIN", "onDownloadRequested")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onExternalPageRequest(url: String) {
|
|
||||||
Log.w("MAIN", "onExternalPageRequest")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<im.delight.android.webview.AdvancedWebView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/webView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity" />
|
||||||
|
|
||||||
<im.delight.android.webview.AdvancedWebView
|
|
||||||
android:id="@+id/webview"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
||||||
@@ -8,20 +8,15 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.3.1'
|
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
maven {
|
maven { url "https://maven.mozilla.org/maven2" }
|
||||||
url "https://maven.mozilla.org/maven2"
|
|
||||||
}
|
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ module.exports = function(state, emit) {
|
|||||||
let content = '';
|
let content = '';
|
||||||
let button = html`
|
let button = html`
|
||||||
<div
|
<div
|
||||||
class="bg-blue rounded-full m-4 flex items-center justify-center shadow-lg"
|
class="bg-blue-50 rounded-full m-4 flex items-center justify-center shadow-lg"
|
||||||
style="width: 56px; height: 56px"
|
style="width: 56px; height: 56px"
|
||||||
onclick="${onclick}"
|
onclick="${onclick}"
|
||||||
>
|
>
|
||||||
@@ -42,11 +42,7 @@ module.exports = function(state, emit) {
|
|||||||
content =
|
content =
|
||||||
archives.length < 1
|
archives.length < 1
|
||||||
? intro(state)
|
? intro(state)
|
||||||
: list(
|
: list(archives, 'h-full overflow-y-auto w-full', 'mb-3 w-full');
|
||||||
archives,
|
|
||||||
'list-reset h-full overflow-y-auto w-full',
|
|
||||||
'mb-3 w-full'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -57,7 +53,7 @@ module.exports = function(state, emit) {
|
|||||||
>
|
>
|
||||||
${content}
|
${content}
|
||||||
</section>
|
</section>
|
||||||
<div class="fixed pin-r pin-b z-20">
|
<div class="fixed right-0 bottom-0 z-20">
|
||||||
${button}
|
${button}
|
||||||
<input
|
<input
|
||||||
id="file-upload"
|
id="file-upload"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default function initialState(state, emitter) {
|
|||||||
getAsset(name) {
|
getAsset(name) {
|
||||||
return `${state.prefix}/${name}`;
|
return `${state.prefix}/${name}`;
|
||||||
},
|
},
|
||||||
raven: {
|
sentry: {
|
||||||
captureException: e => {
|
captureException: e => {
|
||||||
console.error('ERROR ' + e + ' ' + e.stack);
|
console.error('ERROR ' + e + ' ' + e.stack);
|
||||||
}
|
}
|
||||||
|
|||||||
85
app/api.js
@@ -8,7 +8,16 @@ try {
|
|||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
if (!fileProtocolWssUrl) {
|
if (!fileProtocolWssUrl) {
|
||||||
fileProtocolWssUrl = 'wss://send2.dev.lcip.org/api/ws';
|
fileProtocolWssUrl = 'wss://send.firefox.com/api/ws';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConnectionError extends Error {
|
||||||
|
constructor(cancelled, duration, size) {
|
||||||
|
super(cancelled ? '0' : 'connection closed');
|
||||||
|
this.cancelled = cancelled;
|
||||||
|
this.duration = duration;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setFileProtocolWssUrl(url) {
|
export function setFileProtocolWssUrl(url) {
|
||||||
@@ -52,7 +61,10 @@ async function fetchWithAuth(url, params, keychain) {
|
|||||||
const result = {};
|
const result = {};
|
||||||
params = params || {};
|
params = params || {};
|
||||||
const h = await keychain.authHeader();
|
const h = await keychain.authHeader();
|
||||||
params.headers = new Headers({ Authorization: h });
|
params.headers = new Headers({
|
||||||
|
Authorization: h,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
});
|
||||||
const response = await fetch(url, params);
|
const response = await fetch(url, params);
|
||||||
result.response = response;
|
result.response = response;
|
||||||
result.ok = response.ok;
|
result.ok = response.ok;
|
||||||
@@ -137,17 +149,25 @@ export async function setPassword(id, owner_token, keychain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function asyncInitWebSocket(server) {
|
function asyncInitWebSocket(server) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve, reject) => {
|
||||||
const ws = new WebSocket(server);
|
try {
|
||||||
ws.onopen = () => {
|
const ws = new WebSocket(server);
|
||||||
resolve(ws);
|
ws.addEventListener('open', () => resolve(ws), { once: true });
|
||||||
};
|
} catch (e) {
|
||||||
|
reject(new ConnectionError(false));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function listenForResponse(ws, canceller) {
|
function listenForResponse(ws, canceller) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
function handleClose(event) {
|
||||||
|
// a 'close' event before a 'message' event means the request failed
|
||||||
|
ws.removeEventListener('message', handleMessage);
|
||||||
|
reject(new ConnectionError(canceller.cancelled));
|
||||||
|
}
|
||||||
function handleMessage(msg) {
|
function handleMessage(msg) {
|
||||||
|
ws.removeEventListener('close', handleClose);
|
||||||
try {
|
try {
|
||||||
const response = JSON.parse(msg.data);
|
const response = JSON.parse(msg.data);
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
@@ -156,13 +176,11 @@ function listenForResponse(ws, canceller) {
|
|||||||
resolve(response);
|
resolve(response);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ws.close();
|
|
||||||
canceller.cancelled = true;
|
|
||||||
canceller.error = e;
|
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ws.addEventListener('message', handleMessage, { once: true });
|
ws.addEventListener('message', handleMessage, { once: true });
|
||||||
|
ws.addEventListener('close', handleClose, { once: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +194,8 @@ async function upload(
|
|||||||
onprogress,
|
onprogress,
|
||||||
canceller
|
canceller
|
||||||
) {
|
) {
|
||||||
|
let size = 0;
|
||||||
|
const start = Date.now();
|
||||||
const host = window.location.hostname;
|
const host = window.location.hostname;
|
||||||
const port = window.location.port;
|
const port = window.location.port;
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
@@ -203,31 +223,41 @@ async function upload(
|
|||||||
|
|
||||||
const reader = stream.getReader();
|
const reader = stream.getReader();
|
||||||
let state = await reader.read();
|
let state = await reader.read();
|
||||||
let size = 0;
|
|
||||||
while (!state.done) {
|
while (!state.done) {
|
||||||
const buf = state.value;
|
|
||||||
if (canceller.cancelled) {
|
if (canceller.cancelled) {
|
||||||
throw canceller.error;
|
ws.close();
|
||||||
}
|
}
|
||||||
|
if (ws.readyState !== WebSocket.OPEN) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const buf = state.value;
|
||||||
ws.send(buf);
|
ws.send(buf);
|
||||||
|
|
||||||
onprogress(size);
|
onprogress(size);
|
||||||
size += buf.length;
|
size += buf.length;
|
||||||
state = await reader.read();
|
state = await reader.read();
|
||||||
while (ws.bufferedAmount > ECE_RECORD_SIZE * 2) {
|
while (
|
||||||
|
ws.bufferedAmount > ECE_RECORD_SIZE * 2 &&
|
||||||
|
ws.readyState === WebSocket.OPEN &&
|
||||||
|
!canceller.cancelled
|
||||||
|
) {
|
||||||
await delay();
|
await delay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const footer = new Uint8Array([0]);
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
ws.send(footer);
|
ws.send(new Uint8Array([0])); //EOF
|
||||||
|
}
|
||||||
|
|
||||||
await completedResponse;
|
await completedResponse;
|
||||||
ws.close();
|
uploadInfo.duration = Date.now() - start;
|
||||||
return uploadInfo;
|
return uploadInfo;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ws.close(4000);
|
e.size = size;
|
||||||
|
e.duration = Date.now() - start;
|
||||||
throw e;
|
throw e;
|
||||||
|
} finally {
|
||||||
|
if (![WebSocket.CLOSED, WebSocket.CLOSING].includes(ws.readyState)) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +274,6 @@ export function uploadWs(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
cancel: function() {
|
cancel: function() {
|
||||||
canceller.error = new Error(0);
|
|
||||||
canceller.cancelled = true;
|
canceller.cancelled = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -284,7 +313,7 @@ async function downloadS(id, keychain, signal) {
|
|||||||
return response.body;
|
return response.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryDownloadStream(id, keychain, signal, tries = 1) {
|
async function tryDownloadStream(id, keychain, signal, tries = 2) {
|
||||||
try {
|
try {
|
||||||
const result = await downloadS(id, keychain, signal);
|
const result = await downloadS(id, keychain, signal);
|
||||||
return result;
|
return result;
|
||||||
@@ -306,18 +335,19 @@ export function downloadStream(id, keychain) {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
cancel,
|
cancel,
|
||||||
result: tryDownloadStream(id, keychain, controller.signal, 2)
|
result: tryDownloadStream(id, keychain, controller.signal)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////
|
//////////////////
|
||||||
|
|
||||||
function download(id, keychain, onprogress, canceller) {
|
async function download(id, keychain, onprogress, canceller) {
|
||||||
|
const auth = await keychain.authHeader();
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
canceller.oncancel = function() {
|
canceller.oncancel = function() {
|
||||||
xhr.abort();
|
xhr.abort();
|
||||||
};
|
};
|
||||||
return new Promise(async function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
xhr.addEventListener('loadend', function() {
|
xhr.addEventListener('loadend', function() {
|
||||||
canceller.oncancel = function() {};
|
canceller.oncancel = function() {};
|
||||||
const authHeader = xhr.getResponseHeader('WWW-Authenticate');
|
const authHeader = xhr.getResponseHeader('WWW-Authenticate');
|
||||||
@@ -337,7 +367,6 @@ function download(id, keychain, onprogress, canceller) {
|
|||||||
onprogress(event.loaded);
|
onprogress(event.loaded);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const auth = await keychain.authHeader();
|
|
||||||
xhr.open('get', getApiUrl(`/api/download/blob/${id}`));
|
xhr.open('get', getApiUrl(`/api/download/blob/${id}`));
|
||||||
xhr.setRequestHeader('Authorization', auth);
|
xhr.setRequestHeader('Authorization', auth);
|
||||||
xhr.responseType = 'blob';
|
xhr.responseType = 'blob';
|
||||||
@@ -346,7 +375,7 @@ function download(id, keychain, onprogress, canceller) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryDownload(id, keychain, onprogress, canceller, tries = 1) {
|
async function tryDownload(id, keychain, onprogress, canceller, tries = 2) {
|
||||||
try {
|
try {
|
||||||
const result = await download(id, keychain, onprogress, canceller);
|
const result = await download(id, keychain, onprogress, canceller);
|
||||||
return result;
|
return result;
|
||||||
@@ -367,7 +396,7 @@ export function downloadFile(id, keychain, onprogress) {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
cancel,
|
cancel,
|
||||||
result: tryDownload(id, keychain, onprogress, canceller, 2)
|
result: tryDownload(id, keychain, onprogress, canceller)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* global AUTH_CONFIG LOCALE */
|
/* global AUTH_CONFIG */
|
||||||
import { browserName } from './utils';
|
import { browserName, locale } from './utils';
|
||||||
|
|
||||||
async function checkCrypto() {
|
async function checkCrypto() {
|
||||||
try {
|
try {
|
||||||
@@ -76,8 +76,9 @@ async function polyfillStreams() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function getCapabilities() {
|
export default async function getCapabilities() {
|
||||||
const serviceWorker =
|
const browser = browserName();
|
||||||
'serviceWorker' in navigator && browserName() !== 'edge';
|
const isMobile = /mobi|android/i.test(navigator.userAgent);
|
||||||
|
const serviceWorker = 'serviceWorker' in navigator && browser !== 'edge';
|
||||||
let crypto = await checkCrypto();
|
let crypto = await checkCrypto();
|
||||||
const nativeStreams = checkStreams();
|
const nativeStreams = checkStreams();
|
||||||
let polyStreams = false;
|
let polyStreams = false;
|
||||||
@@ -91,7 +92,15 @@ export default async function getCapabilities() {
|
|||||||
account = false;
|
account = false;
|
||||||
}
|
}
|
||||||
const share =
|
const share =
|
||||||
typeof navigator.share === 'function' && LOCALE.startsWith('en'); // en until strings merge
|
isMobile &&
|
||||||
|
typeof navigator.share === 'function' &&
|
||||||
|
locale().startsWith('en'); // en until strings merge
|
||||||
|
|
||||||
|
const standalone =
|
||||||
|
window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
|
navigator.standalone;
|
||||||
|
|
||||||
|
const mobileFirefox = browser === 'firefox' && isMobile;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
@@ -99,8 +108,9 @@ export default async function getCapabilities() {
|
|||||||
serviceWorker,
|
serviceWorker,
|
||||||
streamUpload: nativeStreams || polyStreams,
|
streamUpload: nativeStreams || polyStreams,
|
||||||
streamDownload:
|
streamDownload:
|
||||||
nativeStreams && serviceWorker && browserName() !== 'safari',
|
nativeStreams && serviceWorker && browser !== 'safari' && !mobileFirefox,
|
||||||
multifile: nativeStreams || polyStreams,
|
multifile: nativeStreams || polyStreams,
|
||||||
share
|
share,
|
||||||
|
standalone
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import FileSender from './fileSender';
|
|
||||||
import FileReceiver from './fileReceiver';
|
|
||||||
import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
|
|
||||||
import * as metrics from './metrics';
|
import * as metrics from './metrics';
|
||||||
import { bytes } from './utils';
|
import FileReceiver from './fileReceiver';
|
||||||
import okDialog from './ui/okDialog';
|
import FileSender from './fileSender';
|
||||||
import copyDialog from './ui/copyDialog';
|
import copyDialog from './ui/copyDialog';
|
||||||
|
import faviconProgressbar from './ui/faviconProgressbar';
|
||||||
|
import okDialog from './ui/okDialog';
|
||||||
import shareDialog from './ui/shareDialog';
|
import shareDialog from './ui/shareDialog';
|
||||||
import signupDialog from './ui/signupDialog';
|
import signupDialog from './ui/signupDialog';
|
||||||
|
import surveyDialog from './ui/surveyDialog';
|
||||||
|
import { bytes, locale } from './utils';
|
||||||
|
import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
|
||||||
|
|
||||||
export default function(state, emitter) {
|
export default function(state, emitter) {
|
||||||
let lastRender = 0;
|
let lastRender = 0;
|
||||||
@@ -28,6 +30,7 @@ export default function(state, emitter) {
|
|||||||
if (updateTitle) {
|
if (updateTitle) {
|
||||||
emitter.emit('DOMTitleChange', percent(state.transfer.progressRatio));
|
emitter.emit('DOMTitleChange', percent(state.transfer.progressRatio));
|
||||||
}
|
}
|
||||||
|
faviconProgressbar.updateFavicon(state.transfer.progressRatio);
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +38,8 @@ export default function(state, emitter) {
|
|||||||
document.addEventListener('blur', () => (updateTitle = true));
|
document.addEventListener('blur', () => (updateTitle = true));
|
||||||
document.addEventListener('focus', () => {
|
document.addEventListener('focus', () => {
|
||||||
updateTitle = false;
|
updateTitle = false;
|
||||||
emitter.emit('DOMTitleChange', 'Firefox Send');
|
emitter.emit('DOMTitleChange', 'Send');
|
||||||
|
faviconProgressbar.updateFavicon(0);
|
||||||
});
|
});
|
||||||
checkFiles();
|
checkFiles();
|
||||||
});
|
});
|
||||||
@@ -48,8 +52,8 @@ export default function(state, emitter) {
|
|||||||
state.user.login(email);
|
state.user.login(email);
|
||||||
});
|
});
|
||||||
|
|
||||||
emitter.on('logout', () => {
|
emitter.on('logout', async () => {
|
||||||
state.user.logout();
|
await state.user.logout();
|
||||||
metrics.loggedOut({ trigger: 'button' });
|
metrics.loggedOut({ trigger: 'button' });
|
||||||
emitter.emit('pushState', '/');
|
emitter.emit('pushState', '/');
|
||||||
});
|
});
|
||||||
@@ -75,13 +79,14 @@ export default function(state, emitter) {
|
|||||||
state.storage.remove(ownedFile.id);
|
state.storage.remove(ownedFile.id);
|
||||||
await ownedFile.del();
|
await ownedFile.del();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.raven.captureException(e);
|
state.sentry.captureException(e);
|
||||||
}
|
}
|
||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
emitter.on('cancel', () => {
|
emitter.on('cancel', () => {
|
||||||
state.transfer.cancel();
|
state.transfer.cancel();
|
||||||
|
faviconProgressbar.updateFavicon(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
emitter.on('addFiles', async ({ files }) => {
|
emitter.on('addFiles', async ({ files }) => {
|
||||||
@@ -160,6 +165,7 @@ export default function(state, emitter) {
|
|||||||
state.storage.totalUploads += 1;
|
state.storage.totalUploads += 1;
|
||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
metrics.completedUpload(archive, duration);
|
metrics.completedUpload(archive, duration);
|
||||||
|
faviconProgressbar.updateFavicon(0);
|
||||||
|
|
||||||
state.storage.addFile(ownedFile);
|
state.storage.addFile(ownedFile);
|
||||||
// TODO integrate password into /upload request
|
// TODO integrate password into /upload request
|
||||||
@@ -175,14 +181,23 @@ export default function(state, emitter) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message === '0') {
|
if (err.message === '0') {
|
||||||
//cancelled. do nothing
|
//cancelled. do nothing
|
||||||
const duration = Date.now() - start;
|
metrics.cancelledUpload(archive, err.duration);
|
||||||
metrics.cancelledUpload(archive, duration);
|
|
||||||
render();
|
render();
|
||||||
|
} else if (err.message === '401') {
|
||||||
|
const refreshed = await state.user.refresh();
|
||||||
|
if (refreshed) {
|
||||||
|
return emitter.emit('upload');
|
||||||
|
}
|
||||||
|
emitter.emit('pushState', '/error');
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
state.raven.captureException(err);
|
state.sentry.withScope(scope => {
|
||||||
metrics.stoppedUpload(archive);
|
scope.setExtra('duration', err.duration);
|
||||||
|
scope.setExtra('size', err.size);
|
||||||
|
state.sentry.captureException(err);
|
||||||
|
});
|
||||||
|
metrics.stoppedUpload(archive, err.duration);
|
||||||
emitter.emit('pushState', '/error');
|
emitter.emit('pushState', '/error');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -225,6 +240,9 @@ export default function(state, emitter) {
|
|||||||
if (!file.requiresPassword) {
|
if (!file.requiresPassword) {
|
||||||
return emitter.emit('pushState', '/404');
|
return emitter.emit('pushState', '/404');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.error(e);
|
||||||
|
return emitter.emit('pushState', '/error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +269,7 @@ export default function(state, emitter) {
|
|||||||
duration,
|
duration,
|
||||||
password_protected: file.requiresPassword
|
password_protected: file.requiresPassword
|
||||||
});
|
});
|
||||||
|
faviconProgressbar.updateFavicon(0);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message === '0') {
|
if (err.message === '0') {
|
||||||
// download cancelled
|
// download cancelled
|
||||||
@@ -261,7 +280,12 @@ export default function(state, emitter) {
|
|||||||
state.transfer = null;
|
state.transfer = null;
|
||||||
const location = err.message === '404' ? '/404' : '/error';
|
const location = err.message === '404' ? '/404' : '/error';
|
||||||
if (location === '/error') {
|
if (location === '/error') {
|
||||||
state.raven.captureException(err);
|
state.sentry.withScope(scope => {
|
||||||
|
scope.setExtra('duration', err.duration);
|
||||||
|
scope.setExtra('size', err.size);
|
||||||
|
scope.setExtra('progress', err.progress);
|
||||||
|
state.sentry.captureException(err);
|
||||||
|
});
|
||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
metrics.stoppedDownload({
|
metrics.stoppedDownload({
|
||||||
size,
|
size,
|
||||||
@@ -281,6 +305,22 @@ export default function(state, emitter) {
|
|||||||
// metrics.copiedLink({ location });
|
// metrics.copiedLink({ location });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
emitter.on('closeModal', () => {
|
||||||
|
if (
|
||||||
|
state.PREFS.surveyUrl &&
|
||||||
|
['copy', 'share'].includes(state.modal.type) &&
|
||||||
|
locale().startsWith('en') &&
|
||||||
|
(state.storage.totalUploads > 1 || state.storage.totalDownloads > 0) &&
|
||||||
|
!state.user.surveyed
|
||||||
|
) {
|
||||||
|
state.user.surveyed = true;
|
||||||
|
state.modal = surveyDialog();
|
||||||
|
} else {
|
||||||
|
state.modal = null;
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
// poll for updates of the upload list
|
// poll for updates of the upload list
|
||||||
if (!state.modal && state.route === '/') {
|
if (!state.modal && state.route === '/') {
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
import hash from 'string-hash';
|
import hash from 'string-hash';
|
||||||
|
import Account from './ui/account';
|
||||||
|
|
||||||
const experiments = {};
|
const experiments = {
|
||||||
|
signin_button_color: {
|
||||||
|
eligible: function() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
variant: function() {
|
||||||
|
return ['white-blue', 'blue', 'white-violet', 'violet'][
|
||||||
|
Math.floor(Math.random() * 4)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
run: function(variant, state) {
|
||||||
|
const account = state.cache(Account, 'account');
|
||||||
|
account.buttonClass = variant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//Returns a number between 0 and 1
|
//Returns a number between 0 and 1
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
@@ -25,23 +41,12 @@ export default function initialize(state, emitter) {
|
|||||||
xp.run(+state.query.v, state, emitter);
|
xp.run(+state.query.v, state, emitter);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const enrolled = state.storage.enrolled;
|
||||||
if (!state.storage.get('testpilot_ga__cid')) {
|
// single experiment per session for now
|
||||||
// first ever visit. check again after cid is assigned.
|
const id = Object.keys(enrolled)[0];
|
||||||
emitter.on('DOMContentLoaded', () => {
|
if (Object.keys(experiments).includes(id)) {
|
||||||
checkExperiments(state, emitter);
|
experiments[id].run(enrolled[id], state, emitter);
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const enrolled = state.storage.enrolled.filter(([id, variant]) => {
|
checkExperiments(state, emitter);
|
||||||
const xp = experiments[id];
|
|
||||||
if (xp) {
|
|
||||||
xp.run(variant, state, emitter);
|
|
||||||
}
|
|
||||||
return !!xp;
|
|
||||||
});
|
|
||||||
// single experiment per session for now
|
|
||||||
if (enrolled.length === 0) {
|
|
||||||
checkExperiments(state, emitter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Nanobus from 'nanobus';
|
import Nanobus from 'nanobus';
|
||||||
import Keychain from './keychain';
|
import Keychain from './keychain';
|
||||||
import { delay, bytes, streamToArrayBuffer } from './utils';
|
import { delay, bytes, streamToArrayBuffer } from './utils';
|
||||||
import { downloadFile, metadata, getApiUrl } from './api';
|
import { downloadFile, metadata, getApiUrl, reportLink } from './api';
|
||||||
import { blobStream } from './streams';
|
import { blobStream } from './streams';
|
||||||
import Zip from './zip';
|
import Zip from './zip';
|
||||||
|
|
||||||
@@ -53,6 +53,10 @@ export default class FileReceiver extends Nanobus {
|
|||||||
this.state = 'ready';
|
this.state = 'ready';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async reportLink(reason) {
|
||||||
|
await reportLink(this.fileInfo.id, this.keychain, reason);
|
||||||
|
}
|
||||||
|
|
||||||
sendMessageToSw(msg) {
|
sendMessageToSw(msg) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const channel = new MessageChannel();
|
const channel = new MessageChannel();
|
||||||
@@ -112,6 +116,7 @@ export default class FileReceiver extends Nanobus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async downloadStream(noSave = false) {
|
async downloadStream(noSave = false) {
|
||||||
|
const start = Date.now();
|
||||||
const onprogress = p => {
|
const onprogress = p => {
|
||||||
this.progress = [p, this.fileInfo.size];
|
this.progress = [p, this.fileInfo.size];
|
||||||
this.emit('progress');
|
this.emit('progress');
|
||||||
@@ -153,9 +158,7 @@ export default class FileReceiver extends Nanobus {
|
|||||||
const downloadPath = `/api/download/${this.fileInfo.id}`;
|
const downloadPath = `/api/download/${this.fileInfo.id}`;
|
||||||
let downloadUrl = getApiUrl(downloadPath);
|
let downloadUrl = getApiUrl(downloadPath);
|
||||||
if (downloadUrl === downloadPath) {
|
if (downloadUrl === downloadPath) {
|
||||||
downloadUrl = `${location.protocol}//${location.host}/api/download/${
|
downloadUrl = `${location.protocol}//${location.host}${downloadPath}`;
|
||||||
this.fileInfo.id
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = downloadUrl;
|
a.href = downloadUrl;
|
||||||
@@ -164,11 +167,29 @@ export default class FileReceiver extends Nanobus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let prog = 0;
|
let prog = 0;
|
||||||
|
let hangs = 0;
|
||||||
while (prog < this.fileInfo.size) {
|
while (prog < this.fileInfo.size) {
|
||||||
const msg = await this.sendMessageToSw({
|
const msg = await this.sendMessageToSw({
|
||||||
request: 'progress',
|
request: 'progress',
|
||||||
id: this.fileInfo.id
|
id: this.fileInfo.id
|
||||||
});
|
});
|
||||||
|
if (msg.progress === prog) {
|
||||||
|
hangs++;
|
||||||
|
} else {
|
||||||
|
hangs = 0;
|
||||||
|
}
|
||||||
|
if (hangs > 30) {
|
||||||
|
// TODO: On Chrome we don't get a cancel
|
||||||
|
// signal so one is indistinguishable from
|
||||||
|
// a hang. We may be able to detect
|
||||||
|
// which end is hung in the service worker
|
||||||
|
// to improve on this.
|
||||||
|
const e = new Error('hung download');
|
||||||
|
e.duration = Date.now() - start;
|
||||||
|
e.size = this.fileInfo.size;
|
||||||
|
e.progress = prog;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
prog = msg.progress;
|
prog = msg.progress;
|
||||||
onprogress(prog);
|
onprogress(prog);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ export default class FileSender extends Nanobus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async upload(archive, bearerToken) {
|
async upload(archive, bearerToken) {
|
||||||
const start = Date.now();
|
|
||||||
if (this.cancelled) {
|
if (this.cancelled) {
|
||||||
throw new Error(0);
|
throw new Error(0);
|
||||||
}
|
}
|
||||||
@@ -76,7 +75,6 @@ export default class FileSender extends Nanobus {
|
|||||||
this.emit('progress'); // HACK to kick MS Edge
|
this.emit('progress'); // HACK to kick MS Edge
|
||||||
try {
|
try {
|
||||||
const result = await this.uploadRequest.result;
|
const result = await this.uploadRequest.result;
|
||||||
const time = Date.now() - start;
|
|
||||||
this.msg = 'notifyUploadEncryptDone';
|
this.msg = 'notifyUploadEncryptDone';
|
||||||
this.uploadRequest = null;
|
this.uploadRequest = null;
|
||||||
this.progress = [1, 1];
|
this.progress = [1, 1];
|
||||||
@@ -87,8 +85,8 @@ export default class FileSender extends Nanobus {
|
|||||||
name: archive.name,
|
name: archive.name,
|
||||||
size: archive.size,
|
size: archive.size,
|
||||||
manifest: archive.manifest,
|
manifest: archive.manifest,
|
||||||
time: time,
|
time: result.duration,
|
||||||
speed: archive.size / (time / 1000),
|
speed: archive.size / (result.duration / 1000),
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
expiresAt: Date.now() + archive.timeLimit * 1000,
|
expiresAt: Date.now() + archive.timeLimit * 1000,
|
||||||
secretKey: secretKey,
|
secretKey: secretKey,
|
||||||
|
|||||||
@@ -18,23 +18,6 @@ export default class Keychain {
|
|||||||
false,
|
false,
|
||||||
['deriveKey']
|
['deriveKey']
|
||||||
);
|
);
|
||||||
this.encryptKeyPromise = this.secretKeyPromise.then(function(secretKey) {
|
|
||||||
return 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) {
|
this.metaKeyPromise = this.secretKeyPromise.then(function(secretKey) {
|
||||||
return crypto.subtle.deriveKey(
|
return crypto.subtle.deriveKey(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FluentBundle } from 'fluent';
|
import { FluentBundle } from '@fluent/bundle';
|
||||||
|
|
||||||
function makeBundle(locale, ftl) {
|
function makeBundle(locale, ftl) {
|
||||||
const bundle = new FluentBundle(locale, { useIsolating: false });
|
const bundle = new FluentBundle(locale, { useIsolating: false });
|
||||||
@@ -10,9 +10,9 @@ export async function getTranslator(locale) {
|
|||||||
const bundles = [];
|
const bundles = [];
|
||||||
const { default: en } = await import('../public/locales/en-US/send.ftl');
|
const { default: en } = await import('../public/locales/en-US/send.ftl');
|
||||||
if (locale !== 'en-US') {
|
if (locale !== 'en-US') {
|
||||||
const {
|
const { default: ftl } = await import(
|
||||||
default: ftl
|
`../public/locales/${locale}/send.ftl`
|
||||||
} = await import(`../public/locales/${locale}/send.ftl`);
|
);
|
||||||
bundles.push(makeBundle(locale, ftl));
|
bundles.push(makeBundle(locale, ftl));
|
||||||
}
|
}
|
||||||
bundles.push(makeBundle('en-US', en));
|
bundles.push(makeBundle('en-US', en));
|
||||||
|
|||||||
192
app/main.css
@@ -1,4 +1,9 @@
|
|||||||
@tailwind preflight;
|
@tailwind base;
|
||||||
|
|
||||||
|
html {
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
|
|
||||||
:not(input) {
|
:not(input) {
|
||||||
@@ -8,6 +13,14 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--violet-gradient: linear-gradient(
|
||||||
|
-180deg,
|
||||||
|
rgba(144, 89, 255, 0.8) 0%,
|
||||||
|
rgba(144, 89, 255, 0.4) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -22,22 +35,24 @@ body {
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
@apply bg-blue-dark;
|
@apply bg-blue-60;
|
||||||
@apply text-white;
|
@apply text-white;
|
||||||
@apply cursor-pointer;
|
@apply cursor-pointer;
|
||||||
@apply py-4;
|
@apply py-4;
|
||||||
@apply px-6;
|
@apply px-6;
|
||||||
|
@apply font-semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
@apply bg-blue-darker;
|
@apply bg-blue-70;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:focus {
|
.btn:focus {
|
||||||
@apply bg-blue-darker;
|
@apply bg-blue-70;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
@@ -55,7 +70,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.checkbox > label::before {
|
.checkbox > label::before {
|
||||||
/* @apply bg-grey-lightest; */
|
/* @apply bg-grey-10; */
|
||||||
@apply border;
|
@apply border;
|
||||||
@apply rounded-sm;
|
@apply rounded-sm;
|
||||||
|
|
||||||
@@ -67,16 +82,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.checkbox > label:hover::before {
|
.checkbox > label:hover::before {
|
||||||
@apply border-blue-dark;
|
@apply border-blue-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox > input:focus + label::before {
|
.checkbox > input:focus + label::before {
|
||||||
@apply border-blue-dark;
|
@apply border-blue-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox > input:checked + label::before {
|
.checkbox > input:checked + label::before {
|
||||||
@apply bg-blue-dark;
|
@apply bg-blue-50;
|
||||||
@apply border-blue-dark;
|
@apply border-blue-50;
|
||||||
|
|
||||||
background-image: url('../assets/lock.svg');
|
background-image: url('../assets/lock.svg');
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@@ -89,8 +104,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.checkbox > input:disabled + label::before {
|
.checkbox > input:disabled + label::before {
|
||||||
@apply bg-blue-dark;
|
@apply bg-blue-50;
|
||||||
@apply border-blue-dark;
|
@apply border-blue-50;
|
||||||
|
|
||||||
background-image: url('../assets/lock.svg');
|
background-image: url('../assets/lock.svg');
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@@ -138,6 +153,23 @@ footer li:hover {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-blue {
|
||||||
|
@apply text-blue-60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-blue:hover {
|
||||||
|
@apply text-blue-70;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-blue:focus {
|
||||||
|
@apply text-blue-70;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-header img {
|
||||||
|
height: 32px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.intro {
|
.intro {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: unset;
|
height: unset;
|
||||||
@@ -155,32 +187,19 @@ footer li:hover {
|
|||||||
@apply bg-white;
|
@apply bg-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mozilla-logo {
|
|
||||||
background-image: url('../assets/mozilla-logo.svg');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100px, 32px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-indent: 120%;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: inline-block;
|
|
||||||
height: 32px;
|
|
||||||
width: 100px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#password-msg::after {
|
#password-msg::after {
|
||||||
content: '\200b';
|
content: '\200b';
|
||||||
}
|
}
|
||||||
|
|
||||||
progress {
|
progress {
|
||||||
@apply bg-grey-light;
|
@apply bg-grey-30;
|
||||||
@apply rounded-sm;
|
@apply rounded-sm;
|
||||||
@apply w-full;
|
@apply w-full;
|
||||||
@apply h-1;
|
@apply h-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress::-webkit-progress-bar {
|
progress::-webkit-progress-bar {
|
||||||
@apply bg-grey-light;
|
@apply bg-grey-30;
|
||||||
@apply rounded-sm;
|
@apply rounded-sm;
|
||||||
@apply w-full;
|
@apply w-full;
|
||||||
@apply h-1;
|
@apply h-1;
|
||||||
@@ -245,6 +264,11 @@ select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@screen md {
|
@screen md {
|
||||||
|
.main-header img {
|
||||||
|
height: 48px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.intro {
|
.intro {
|
||||||
max-width: unset;
|
max-width: unset;
|
||||||
height: unset;
|
height: unset;
|
||||||
@@ -259,12 +283,56 @@ select {
|
|||||||
@apply m-auto;
|
@apply m-auto;
|
||||||
@apply py-8;
|
@apply py-8;
|
||||||
|
|
||||||
min-height: 36rem;
|
min-height: 42rem;
|
||||||
max-height: 40rem;
|
max-height: 42rem;
|
||||||
width: calc(100% - 3rem);
|
width: calc(100% - 3rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@screen dark {
|
||||||
|
body {
|
||||||
|
@apply text-grey-10;
|
||||||
|
|
||||||
|
background-image: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
@apply bg-blue-40;
|
||||||
|
@apply text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
@apply bg-blue-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:focus {
|
||||||
|
@apply bg-blue-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-blue {
|
||||||
|
@apply text-blue-40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-blue:hover {
|
||||||
|
@apply text-blue-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-blue:focus {
|
||||||
|
@apply text-blue-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main > section {
|
||||||
|
@apply bg-grey-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen md {
|
||||||
|
.main > section {
|
||||||
|
@apply border;
|
||||||
|
@apply border-grey-80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@responsive {
|
@responsive {
|
||||||
@@ -273,8 +341,9 @@ select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.shadow-big {
|
.shadow-big {
|
||||||
box-shadow: 0 0 32px 0 rgba(12, 12, 13, 0.1),
|
box-shadow: 0 12px 18px 2px rgba(34, 0, 51, 0.04),
|
||||||
0 2px 16px 0 rgba(12, 12, 13, 0.05);
|
0 6px 22px 4px rgba(7, 48, 114, 0.12),
|
||||||
|
0 6px 10px -4px rgba(14, 13, 26, 0.12);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,4 +355,67 @@ select {
|
|||||||
|
|
||||||
.word-break-all {
|
.word-break-all {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
line-break: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.signin {
|
||||||
|
backface-visibility: hidden;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition-property: transform, background-color;
|
||||||
|
transition-duration: 250ms;
|
||||||
|
transition-timing-function: cubic-bezier(0.07, 0.95, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.signin:hover,
|
||||||
|
.signin:focus {
|
||||||
|
transform: scale(1.0625);
|
||||||
|
}
|
||||||
|
|
||||||
|
.signin:hover:active {
|
||||||
|
transform: scale(0.9375);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* begin signin button color experiment */
|
||||||
|
|
||||||
|
.white-blue {
|
||||||
|
@apply border-blue-60;
|
||||||
|
@apply border-2;
|
||||||
|
@apply text-blue-60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-blue:hover,
|
||||||
|
.white-blue:focus {
|
||||||
|
@apply bg-blue-60;
|
||||||
|
@apply text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
@apply bg-blue-60;
|
||||||
|
@apply text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-violet {
|
||||||
|
@apply border-violet;
|
||||||
|
@apply border-2;
|
||||||
|
@apply text-violet;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-violet:hover,
|
||||||
|
.white-violet:focus {
|
||||||
|
@apply bg-violet;
|
||||||
|
@apply text-white;
|
||||||
|
|
||||||
|
background-image: var(--violet-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
.violet {
|
||||||
|
@apply bg-violet;
|
||||||
|
@apply text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violet:hover,
|
||||||
|
.violet:focus {
|
||||||
|
background-image: var(--violet-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end signin button color experiment */
|
||||||
|
|||||||
35
app/main.js
@@ -1,7 +1,7 @@
|
|||||||
/* global DEFAULTS LIMITS LOCALE */
|
/* global DEFAULTS LIMITS PREFS */
|
||||||
import 'core-js';
|
import 'core-js';
|
||||||
import 'fast-text-encoding'; // MS Edge support
|
import 'fast-text-encoding'; // MS Edge support
|
||||||
import 'fluent-intl-polyfill';
|
import 'intl-pluralrules';
|
||||||
import choo from 'choo';
|
import choo from 'choo';
|
||||||
import nanotiming from 'nanotiming';
|
import nanotiming from 'nanotiming';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
@@ -12,14 +12,15 @@ import pasteManager from './pasteManager';
|
|||||||
import storage from './storage';
|
import storage from './storage';
|
||||||
import metrics from './metrics';
|
import metrics from './metrics';
|
||||||
import experiments from './experiments';
|
import experiments from './experiments';
|
||||||
import Raven from 'raven-js';
|
import * as Sentry from '@sentry/browser';
|
||||||
import './main.css';
|
import './main.css';
|
||||||
import User from './user';
|
import User from './user';
|
||||||
import { getTranslator } from './locale';
|
import { getTranslator } from './locale';
|
||||||
import Archive from './archive';
|
import Archive from './archive';
|
||||||
|
import { setTranslate, locale } from './utils';
|
||||||
|
|
||||||
if (navigator.doNotTrack !== '1' && window.RAVEN_CONFIG) {
|
if (navigator.doNotTrack !== '1' && window.SENTRY_CONFIG) {
|
||||||
Raven.config(window.SENTRY_ID, window.RAVEN_CONFIG).install();
|
Sentry.init(window.SENTRY_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
@@ -35,30 +36,40 @@ if (process.env.NODE_ENV === 'production') {
|
|||||||
return window.location.assign('/unsupported/crypto');
|
return window.location.assign('/unsupported/crypto');
|
||||||
}
|
}
|
||||||
if (capabilities.serviceWorker) {
|
if (capabilities.serviceWorker) {
|
||||||
await navigator.serviceWorker.register('/serviceWorker.js');
|
try {
|
||||||
await navigator.serviceWorker.ready;
|
await navigator.serviceWorker.register('/serviceWorker.js');
|
||||||
|
await navigator.serviceWorker.ready;
|
||||||
|
} catch (e) {
|
||||||
|
// continue but disable streaming downloads
|
||||||
|
capabilities.streamDownload = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const translate = await getTranslator(LOCALE);
|
const translate = await getTranslator(locale());
|
||||||
|
setTranslate(translate);
|
||||||
|
// eslint-disable-next-line require-atomic-updates
|
||||||
window.initialState = {
|
window.initialState = {
|
||||||
LIMITS,
|
LIMITS,
|
||||||
DEFAULTS,
|
DEFAULTS,
|
||||||
|
PREFS,
|
||||||
archive: new Archive([], DEFAULTS.EXPIRE_SECONDS),
|
archive: new Archive([], DEFAULTS.EXPIRE_SECONDS),
|
||||||
capabilities,
|
capabilities,
|
||||||
translate,
|
translate,
|
||||||
storage,
|
storage,
|
||||||
raven: Raven,
|
sentry: Sentry,
|
||||||
user: new User(storage, LIMITS, window.AUTH_CONFIG),
|
user: new User(storage, LIMITS, window.AUTH_CONFIG),
|
||||||
transfer: null,
|
transfer: null,
|
||||||
fileInfo: null
|
fileInfo: null,
|
||||||
|
locale: locale()
|
||||||
};
|
};
|
||||||
|
|
||||||
const app = routes(choo());
|
const app = routes(choo({ hash: true }));
|
||||||
|
// eslint-disable-next-line require-atomic-updates
|
||||||
window.app = app;
|
window.app = app;
|
||||||
|
app.use(experiments);
|
||||||
app.use(metrics);
|
app.use(metrics);
|
||||||
app.use(controller);
|
app.use(controller);
|
||||||
app.use(dragManager);
|
app.use(dragManager);
|
||||||
app.use(experiments);
|
|
||||||
app.use(pasteManager);
|
app.use(pasteManager);
|
||||||
app.mount('body');
|
app.mount('body');
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import storage from './storage';
|
import storage from './storage';
|
||||||
import { platform } from './utils';
|
import { platform, locale } from './utils';
|
||||||
import { sendMetrics } from './api';
|
import { sendMetrics } from './api';
|
||||||
|
|
||||||
let appState = null;
|
let appState = null;
|
||||||
// let experiment = null;
|
let experiment = null;
|
||||||
const HOUR = 1000 * 60 * 60;
|
const HOUR = 1000 * 60 * 60;
|
||||||
const events = [];
|
const events = [];
|
||||||
let session_id = Date.now();
|
let session_id = Date.now();
|
||||||
const lang = document.querySelector('html').lang;
|
const lang = locale();
|
||||||
|
|
||||||
export default function initialize(state, emitter) {
|
export default function initialize(state, emitter) {
|
||||||
appState = state;
|
appState = state;
|
||||||
if (!appState.user.firstAction) {
|
|
||||||
appState.user.firstAction = appState.route === '/' ? 'upload' : 'download';
|
|
||||||
}
|
|
||||||
emitter.on('DOMContentLoaded', () => {
|
emitter.on('DOMContentLoaded', () => {
|
||||||
// experiment = storage.enrolled[0];
|
experiment = storage.enrolled;
|
||||||
|
if (!appState.user.firstAction) {
|
||||||
|
appState.user.firstAction =
|
||||||
|
appState.route === '/' ? 'upload' : 'download';
|
||||||
|
}
|
||||||
const query = appState.query;
|
const query = appState.query;
|
||||||
addEvent('client_visit', {
|
addEvent('client_visit', {
|
||||||
entrypoint: appState.route === '/' ? 'upload' : 'download',
|
entrypoint: appState.route === '/' ? 'upload' : 'download',
|
||||||
@@ -59,6 +61,11 @@ function submitEvents() {
|
|||||||
async function addEvent(event_type, event_properties) {
|
async function addEvent(event_type, event_properties) {
|
||||||
const user_id = await appState.user.metricId();
|
const user_id = await appState.user.metricId();
|
||||||
const device_id = await appState.user.deviceId();
|
const device_id = await appState.user.deviceId();
|
||||||
|
const ab_id = Object.keys(experiment)[0];
|
||||||
|
if (ab_id) {
|
||||||
|
event_properties.experiment = ab_id;
|
||||||
|
event_properties.variant = experiment[ab_id];
|
||||||
|
}
|
||||||
events.push({
|
events.push({
|
||||||
device_id,
|
device_id,
|
||||||
event_properties,
|
event_properties,
|
||||||
@@ -100,9 +107,10 @@ function completedUpload(archive, duration) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function stoppedUpload(archive) {
|
function stoppedUpload(archive, duration = 0) {
|
||||||
return addEvent('client_upload', {
|
return addEvent('client_upload', {
|
||||||
download_limit: archive.dlimit,
|
download_limit: archive.dlimit,
|
||||||
|
duration: sizeOrder(duration),
|
||||||
file_count: archive.numFiles,
|
file_count: archive.numFiles,
|
||||||
password_protected: !!archive.password,
|
password_protected: !!archive.password,
|
||||||
size: sizeOrder(archive.size),
|
size: sizeOrder(archive.size),
|
||||||
|
|||||||
1076
app/qrcode.js
Normal file
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
`app/` contains the browser code that gets bundled into `app.[hash].js`. It's got all the logic, crypto, and UI. All of it gets used in the browser, and some of it by the server for server side rendering.
|
`app/` contains the browser code that gets bundled into `app.[hash].js`. It's got all the logic, crypto, and UI. All of it gets used in the browser, and some of it by the server for server side rendering.
|
||||||
|
|
||||||
The main entrypoint for the browser is [main.js](./main.js) and on the server [routes/index.js](./routes/index.js) gets imported by [/server/routes/pages.js](../server/routes/pages.js)
|
The main entrypoint for the browser is [main.js](./main.js) and on the server [routes.js](./routes.js) is imported by [/server/routes/pages.js](../server/routes/pages.js)
|
||||||
|
|
||||||
- `pages` contains display logic an markup for pages
|
- `pages` contains display logic an markup for pages
|
||||||
- `routes` contains route definitions and logic
|
- `routes` contains route definitions and logic
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const choo = require('choo');
|
|||||||
const download = require('./ui/download');
|
const download = require('./ui/download');
|
||||||
const body = require('./ui/body');
|
const body = require('./ui/body');
|
||||||
|
|
||||||
module.exports = function(app = choo()) {
|
module.exports = function(app = choo({ hash: true })) {
|
||||||
app.route('/', body(require('./ui/home')));
|
app.route('/', body(require('./ui/home')));
|
||||||
app.route('/download/:id', body(download));
|
app.route('/download/:id', body(download));
|
||||||
app.route('/download/:id/:key', body(download));
|
app.route('/download/:id/:key', body(download));
|
||||||
@@ -13,6 +13,10 @@ module.exports = function(app = choo()) {
|
|||||||
app.route('/oauth', function(state, emit) {
|
app.route('/oauth', function(state, emit) {
|
||||||
emit('authenticate', state.query.code, state.query.state);
|
emit('authenticate', state.query.code, state.query.state);
|
||||||
});
|
});
|
||||||
|
app.route('/login', function(state, emit) {
|
||||||
|
emit('replaceState', '/');
|
||||||
|
setTimeout(() => emit('render'));
|
||||||
|
});
|
||||||
app.route('*', body(require('./ui/notFound')));
|
app.route('*', body(require('./ui/notFound')));
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,15 +9,16 @@ import contentDisposition from 'content-disposition';
|
|||||||
let noSave = false;
|
let noSave = false;
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
const IMAGES = /.*\.(png|svg|jpg)$/;
|
const IMAGES = /.*\.(png|svg|jpg)$/;
|
||||||
const VERSIONED_ASSET = /\.[A-Fa-f0-9]{8}\.(js|css|png|svg|jpg)$/;
|
const VERSIONED_ASSET = /\.[A-Fa-f0-9]{8}\.(js|css|png|svg|jpg)(#\w+)?$/;
|
||||||
const DOWNLOAD_URL = /\/api\/download\/([A-Fa-f0-9]{4,})/;
|
const DOWNLOAD_URL = /\/api\/download\/([A-Fa-f0-9]{4,})/;
|
||||||
|
const FONT = /\.woff2?$/;
|
||||||
|
|
||||||
self.addEventListener('install', event => {
|
self.addEventListener('install', () => {
|
||||||
event.waitUntil(precache());
|
self.skipWaiting();
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', event => {
|
self.addEventListener('activate', event => {
|
||||||
event.waitUntil(self.clients.claim());
|
event.waitUntil(self.clients.claim().then(precache));
|
||||||
});
|
});
|
||||||
|
|
||||||
async function decryptStream(id) {
|
async function decryptStream(id) {
|
||||||
@@ -83,16 +84,28 @@ async function decryptStream(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function precache() {
|
async function precache() {
|
||||||
|
try {
|
||||||
|
await cleanCache();
|
||||||
|
const cache = await caches.open(version);
|
||||||
|
const images = assets.match(IMAGES);
|
||||||
|
await cache.addAll(images);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
// cache will get populated on demand
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanCache() {
|
||||||
const oldCaches = await caches.keys();
|
const oldCaches = await caches.keys();
|
||||||
for (const c of oldCaches) {
|
for (const c of oldCaches) {
|
||||||
if (c !== version) {
|
if (c !== version) {
|
||||||
await caches.delete(c);
|
await caches.delete(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const cache = await caches.open(version);
|
}
|
||||||
const images = assets.match(IMAGES);
|
|
||||||
await cache.addAll(images);
|
function cacheable(url) {
|
||||||
return self.skipWaiting();
|
return VERSIONED_ASSET.test(url) || FONT.test(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cachedOrFetched(req) {
|
async function cachedOrFetched(req) {
|
||||||
@@ -102,7 +115,7 @@ async function cachedOrFetched(req) {
|
|||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
const fetched = await fetch(req);
|
const fetched = await fetch(req);
|
||||||
if (fetched.ok && VERSIONED_ASSET.test(req.url)) {
|
if (fetched.ok && cacheable(req.url)) {
|
||||||
cache.put(req, fetched.clone());
|
cache.put(req, fetched.clone());
|
||||||
}
|
}
|
||||||
return fetched;
|
return fetched;
|
||||||
@@ -115,7 +128,7 @@ self.onfetch = event => {
|
|||||||
const dlmatch = DOWNLOAD_URL.exec(url.pathname);
|
const dlmatch = DOWNLOAD_URL.exec(url.pathname);
|
||||||
if (dlmatch) {
|
if (dlmatch) {
|
||||||
event.respondWith(decryptStream(dlmatch[1]));
|
event.respondWith(decryptStream(dlmatch[1]));
|
||||||
} else if (VERSIONED_ASSET.test(url.pathname)) {
|
} else if (cacheable(url.pathname)) {
|
||||||
event.respondWith(cachedOrFetched(req));
|
event.respondWith(cachedOrFetched(req));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,16 +86,13 @@ class Storage {
|
|||||||
this.engine.setItem('referrer', str);
|
this.engine.setItem('referrer', str);
|
||||||
}
|
}
|
||||||
get enrolled() {
|
get enrolled() {
|
||||||
return JSON.parse(this.engine.getItem('experiments') || '[]');
|
return JSON.parse(this.engine.getItem('ab_experiments') || '{}');
|
||||||
}
|
}
|
||||||
|
|
||||||
enroll(id, variant) {
|
enroll(id, variant) {
|
||||||
const enrolled = this.enrolled;
|
const enrolled = {};
|
||||||
// eslint-disable-next-line no-unused-vars
|
enrolled[id] = variant;
|
||||||
if (!enrolled.find(([i, v]) => i === id)) {
|
this.engine.setItem('ab_experiments', JSON.stringify(enrolled));
|
||||||
enrolled.push([id, variant]);
|
|
||||||
this.engine.setItem('experiments', JSON.stringify(enrolled));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get files() {
|
get files() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global ReadableStream TransformStream */
|
/* global TransformStream */
|
||||||
|
|
||||||
export function transformStream(readable, transformer, oncancel) {
|
export function transformStream(readable, transformer, oncancel) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ class Account extends Component {
|
|||||||
this.emit = emit;
|
this.emit = emit;
|
||||||
this.enabled = state.capabilities.account;
|
this.enabled = state.capabilities.account;
|
||||||
this.local = state.components[name] = {};
|
this.local = state.components[name] = {};
|
||||||
this.setState();
|
this.buttonClass = '';
|
||||||
|
this.setLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
avatarClick(event) {
|
avatarClick(event) {
|
||||||
@@ -38,7 +39,7 @@ class Account extends Component {
|
|||||||
return this.local.loggedIn !== this.state.user.loggedIn;
|
return this.local.loggedIn !== this.state.user.loggedIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState() {
|
setLocal() {
|
||||||
const changed = this.changed();
|
const changed = this.changed();
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this.local.loggedIn = this.state.user.loggedIn;
|
this.local.loggedIn = this.state.user.loggedIn;
|
||||||
@@ -47,26 +48,32 @@ class Account extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
return this.setState();
|
return this.setLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
createElement() {
|
createElement() {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
return html`
|
return html`
|
||||||
<div></div>
|
<send-account></send-account>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
const user = this.state.user;
|
const user = this.state.user;
|
||||||
const translate = this.state.translate;
|
const translate = this.state.translate;
|
||||||
|
this.setLocal();
|
||||||
|
if (user.loginRequired && !this.local.loggedIn) {
|
||||||
|
return html`
|
||||||
|
<send-account></send-account>
|
||||||
|
`;
|
||||||
|
}
|
||||||
if (!this.local.loggedIn) {
|
if (!this.local.loggedIn) {
|
||||||
return html`
|
return html`
|
||||||
<send-account>
|
<send-account>
|
||||||
<button
|
<button
|
||||||
class="p-2 md:p-4 border rounded-lg text-blue-dark border-blue-dark hover:text-white hover:bg-blue-dark focus:outline"
|
class="px-4 py-2 md:px-8 md:py-4 focus:outline signin border-2 link-blue border-blue-60 hover:border-blue-70 dark:border-blue-40 dark:hover:border-blue-50"
|
||||||
onclick="${e => this.login(e)}"
|
onclick="${e => this.login(e)}"
|
||||||
title="${translate('signInButton')}"
|
title="${translate('signInOnlyButton')}"
|
||||||
>
|
>
|
||||||
${translate('signInButton')}
|
${translate('signInOnlyButton')}
|
||||||
</button>
|
</button>
|
||||||
</send-account>
|
</send-account>
|
||||||
`;
|
`;
|
||||||
@@ -76,19 +83,19 @@ class Account extends Component {
|
|||||||
<input
|
<input
|
||||||
type="image"
|
type="image"
|
||||||
alt="${user.email}"
|
alt="${user.email}"
|
||||||
class="w-8 h-8 rounded-full border text-blue md:text-white focus:outline"
|
class="w-8 h-8 rounded-full border text-blue-50 md:text-white focus:outline"
|
||||||
src="${user.avatar}"
|
src="${user.avatar}"
|
||||||
onclick="${e => this.avatarClick(e)}"
|
onclick="${e => this.avatarClick(e)}"
|
||||||
/>
|
/>
|
||||||
<ul
|
<ul
|
||||||
id="accountMenu"
|
id="accountMenu"
|
||||||
class="invisible list-reset absolute pin-t pin-r mt-10 pt-2 pb-2 bg-white shadow-md whitespace-no-wrap outline-none z-50"
|
class="invisible absolute top-0 right-0 mt-10 pt-2 pb-2 bg-white shadow-md whitespace-no-wrap outline-none z-50 dark:bg-grey-80"
|
||||||
onblur="${e => this.hideMenu(e)}"
|
onblur="${e => this.hideMenu(e)}"
|
||||||
>
|
>
|
||||||
<li class="p-2 text-grey-dark">${user.email}</li>
|
<li class="p-2 text-grey-60 dark:text-grey-50">${user.email}</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="block w-full text-left px-4 py-2 text-grey-darkest hover:bg-blue hover:text-white cursor-pointer focus:outline"
|
class="block w-full text-left px-4 py-2 text-grey-80 dark:text-grey-30 hover:bg-blue-50 hover:text-white cursor-pointer focus:outline"
|
||||||
onclick="${e => this.logout(e)}"
|
onclick="${e => this.logout(e)}"
|
||||||
title="${translate('signOut')}"
|
title="${translate('signOut')}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ function password(state) {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="mb-2 px-1">
|
<div class="mb-2 px-1">
|
||||||
|
<input
|
||||||
|
id="autocomplete-decoy"
|
||||||
|
class="hidden"
|
||||||
|
type="password"
|
||||||
|
value="lol"
|
||||||
|
/>
|
||||||
<div class="checkbox inline-block mr-3">
|
<div class="checkbox inline-block mr-3">
|
||||||
<input
|
<input
|
||||||
id="add-password"
|
id="add-password"
|
||||||
@@ -42,36 +48,74 @@ function password(state) {
|
|||||||
${state.translate('addPassword')}
|
${state.translate('addPassword')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<div class="relative inline-block my-1">
|
||||||
id="password-input"
|
<input
|
||||||
class="${state.archive.password
|
id="password-input"
|
||||||
? ''
|
class="${state.archive.password
|
||||||
: 'invisible'} border rounded focus:border-blue-dark leading-normal my-1 py-1 px-2 h-8"
|
? ''
|
||||||
autocomplete="off"
|
: 'invisible'} border rounded focus:border-blue-60 leading-normal py-1 pl-2 pr-8 h-8 dark:bg-grey-80"
|
||||||
maxlength="${MAX_LENGTH}"
|
autocomplete="off"
|
||||||
type="password"
|
maxlength="${MAX_LENGTH}"
|
||||||
oninput="${inputChanged}"
|
type="password"
|
||||||
onfocus="${focused}"
|
oninput="${inputChanged}"
|
||||||
placeholder="${state.translate('unlockInputPlaceholder')}"
|
onfocus="${focused}"
|
||||||
value="${state.archive.password || ''}"
|
placeholder="${state.translate('unlockInputPlaceholder')}"
|
||||||
/>
|
value="${state.archive.password || ''}"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
id="password-preview-button"
|
||||||
|
type="button"
|
||||||
|
class="${state.archive.password
|
||||||
|
? ''
|
||||||
|
: 'invisible'} absolute top-0 right-0 w-8 h-8"
|
||||||
|
onclick="${onPasswordPreviewButtonclicked}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="${assets.get('eye.svg')}"
|
||||||
|
width="22"
|
||||||
|
height="22"
|
||||||
|
class="m-auto"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<label
|
<label
|
||||||
id="password-msg"
|
id="password-msg"
|
||||||
for="password-input"
|
for="password-input"
|
||||||
class="block text-xs text-grey-darker"
|
class="block text-xs text-grey-70"
|
||||||
></label>
|
></label>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
function onPasswordPreviewButtonclicked(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const input = document.getElementById('password-input');
|
||||||
|
const eyeIcon = event.currentTarget.querySelector('img');
|
||||||
|
|
||||||
|
if (input.type === 'password') {
|
||||||
|
input.type = 'text';
|
||||||
|
eyeIcon.src = assets.get('eye-off.svg');
|
||||||
|
} else {
|
||||||
|
input.type = 'password';
|
||||||
|
eyeIcon.src = assets.get('eye.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
function togglePasswordInput(event) {
|
function togglePasswordInput(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const checked = event.target.checked;
|
const checked = event.target.checked;
|
||||||
const input = document.getElementById('password-input');
|
const input = document.getElementById('password-input');
|
||||||
|
const passwordPreviewButton = document.getElementById(
|
||||||
|
'password-preview-button'
|
||||||
|
);
|
||||||
if (checked) {
|
if (checked) {
|
||||||
input.classList.remove('invisible');
|
input.classList.remove('invisible');
|
||||||
|
passwordPreviewButton.classList.remove('invisible');
|
||||||
input.focus();
|
input.focus();
|
||||||
} else {
|
} else {
|
||||||
input.classList.add('invisible');
|
input.classList.add('invisible');
|
||||||
|
passwordPreviewButton.classList.add('invisible');
|
||||||
input.value = '';
|
input.value = '';
|
||||||
document.getElementById('password-msg').textContent = '';
|
document.getElementById('password-msg').textContent = '';
|
||||||
state.archive.password = null;
|
state.archive.password = null;
|
||||||
@@ -106,7 +150,9 @@ function password(state) {
|
|||||||
function fileInfo(file, action) {
|
function fileInfo(file, action) {
|
||||||
return html`
|
return html`
|
||||||
<send-file class="flex flex-row items-center p-3 w-full">
|
<send-file class="flex flex-row items-center p-3 w-full">
|
||||||
<img class="h-8" src="${assets.get('blue_file.svg')}"/>
|
<svg class="h-8 w-8 text-white dark:text-grey-90">
|
||||||
|
<use xlink:href="${assets.get('blue_file.svg')}#icon"/>
|
||||||
|
</svg>
|
||||||
<p class="ml-4 w-full">
|
<p class="ml-4 w-full">
|
||||||
<h1 class="text-base font-medium word-break-all">${file.name}</h1>
|
<h1 class="text-base font-medium word-break-all">${file.name}</h1>
|
||||||
<div class="text-sm font-normal opacity-75 pt-1">${bytes(
|
<div class="text-sm font-normal opacity-75 pt-1">${bytes(
|
||||||
@@ -120,7 +166,9 @@ function fileInfo(file, action) {
|
|||||||
function archiveInfo(archive, action) {
|
function archiveInfo(archive, action) {
|
||||||
return html`
|
return html`
|
||||||
<p class="w-full flex items-center">
|
<p class="w-full flex items-center">
|
||||||
<img class="mr-3 flex-no-shrink" src="${assets.get('blue_file.svg')}"/>
|
<svg class="h-8 w-6 mr-3 flex-shrink-0 text-white dark:text-grey-90">
|
||||||
|
<use xlink:href="${assets.get('blue_file.svg')}#icon"/>
|
||||||
|
</svg>
|
||||||
<p class="flex-grow">
|
<p class="flex-grow">
|
||||||
<h1 class="text-base font-medium word-break-all">${archive.name}</h1>
|
<h1 class="text-base font-medium word-break-all">${archive.name}</h1>
|
||||||
<div class="text-sm font-normal opacity-75 pt-1">${bytes(
|
<div class="text-sm font-normal opacity-75 pt-1">${bytes(
|
||||||
@@ -140,7 +188,7 @@ function archiveDetails(translate, archive) {
|
|||||||
ontoggle="${toggled}"
|
ontoggle="${toggled}"
|
||||||
>
|
>
|
||||||
<summary
|
<summary
|
||||||
class="flex items-center text-blue-dark text-sm cursor-pointer outline-none"
|
class="flex items-center link-blue text-sm cursor-pointer outline-none"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="fill-current w-4 h-4 mr-1"
|
class="fill-current w-4 h-4 mr-1"
|
||||||
@@ -155,7 +203,7 @@ function archiveDetails(translate, archive) {
|
|||||||
num: archive.manifest.files.length
|
num: archive.manifest.files.length
|
||||||
})}
|
})}
|
||||||
</summary>
|
</summary>
|
||||||
${list(archive.manifest.files.map(f => fileInfo(f)), 'list-reset')}
|
${list(archive.manifest.files.map(f => fileInfo(f)))}
|
||||||
</details>
|
</details>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -170,20 +218,25 @@ module.exports = function(state, emit, archive) {
|
|||||||
state.capabilities.share || platform() === 'android'
|
state.capabilities.share || platform() === 'android'
|
||||||
? html`
|
? html`
|
||||||
<button
|
<button
|
||||||
class="text-blue-dark hover:text-blue-darker focus:text-blue-darker self-end flex items-end"
|
class="link-blue self-end flex items-start"
|
||||||
onclick=${share}
|
onclick=${share}
|
||||||
title="Share link"
|
title="Share link"
|
||||||
>
|
>
|
||||||
<img src="${assets.get('share-24.svg')}" class="mr-2" />Share link
|
<svg class="h-4 w-4 mr-2">
|
||||||
|
<use xlink:href="${assets.get('share-24.svg')}#icon" />
|
||||||
|
</svg>
|
||||||
|
Share link
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<button
|
<button
|
||||||
class="text-blue-dark hover:text-blue-darker focus:text-blue-darker focus:outline self-end flex items-center"
|
class="link-blue focus:outline self-end flex items-center"
|
||||||
onclick=${copy}
|
onclick=${copy}
|
||||||
title="${state.translate('copyLinkButton')}"
|
title="${state.translate('copyLinkButton')}"
|
||||||
>
|
>
|
||||||
<img src="${assets.get('copy-16.svg')}" class="mr-2" />
|
<svg class="h-4 w-4 mr-2">
|
||||||
|
<use xlink:href="${assets.get('copy-16.svg')}#icon" />
|
||||||
|
</svg>
|
||||||
${state.translate('copyLinkButton')}
|
${state.translate('copyLinkButton')}
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
@@ -191,12 +244,14 @@ module.exports = function(state, emit, archive) {
|
|||||||
platform() === 'web'
|
platform() === 'web'
|
||||||
? html`
|
? html`
|
||||||
<a
|
<a
|
||||||
class="flex items-baseline text-blue-dark hover:text-blue-darker focus:text-blue-darker"
|
class="flex items-baseline link-blue"
|
||||||
href="${archive.url}"
|
href="${archive.url}"
|
||||||
title="${state.translate('downloadButtonLabel')}"
|
title="${state.translate('downloadButtonLabel')}"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<img src="${assets.get('dl.svg')}" class="mr-2" />
|
<svg class="h-4 w-3 mr-2">
|
||||||
|
<use xlink:href="${assets.get('dl.svg')}#icon" />
|
||||||
|
</svg>
|
||||||
${state.translate('downloadButtonLabel')}
|
${state.translate('downloadButtonLabel')}
|
||||||
</a>
|
</a>
|
||||||
`
|
`
|
||||||
@@ -206,14 +261,14 @@ module.exports = function(state, emit, archive) {
|
|||||||
return html`
|
return html`
|
||||||
<send-archive
|
<send-archive
|
||||||
id="archive-${archive.id}"
|
id="archive-${archive.id}"
|
||||||
class="flex flex-col items-start rounded shadow-light bg-white p-4 w-full"
|
class="flex flex-col items-start rounded shadow-light bg-white p-4 w-full dark:bg-grey-90 dark:border dark:border-grey-70"
|
||||||
>
|
>
|
||||||
${archiveInfo(
|
${archiveInfo(
|
||||||
archive,
|
archive,
|
||||||
html`
|
html`
|
||||||
<input
|
<input
|
||||||
type="image"
|
type="image"
|
||||||
class="self-start flex-no-shrink text-white hover:opacity-75 focus:outline"
|
class="self-start flex-shrink-0 text-white hover:opacity-75 focus:outline"
|
||||||
alt="${state.translate('deleteButtonHover')}"
|
alt="${state.translate('deleteButtonHover')}"
|
||||||
title="${state.translate('deleteButtonHover')}"
|
title="${state.translate('deleteButtonHover')}"
|
||||||
src="${assets.get('close-16.svg')}"
|
src="${assets.get('close-16.svg')}"
|
||||||
@@ -225,7 +280,7 @@ module.exports = function(state, emit, archive) {
|
|||||||
${expiryInfo(state.translate, archive)}
|
${expiryInfo(state.translate, archive)}
|
||||||
</div>
|
</div>
|
||||||
${archiveDetails(state.translate, archive)}
|
${archiveDetails(state.translate, archive)}
|
||||||
<hr class="w-full border-t my-4" />
|
<hr class="w-full border-t my-4 dark:border-grey-70" />
|
||||||
<div class="flex justify-between w-full">
|
<div class="flex justify-between w-full">
|
||||||
${dl} ${copyOrShare}
|
${dl} ${copyOrShare}
|
||||||
</div>
|
</div>
|
||||||
@@ -256,9 +311,7 @@ module.exports = function(state, emit, archive) {
|
|||||||
try {
|
try {
|
||||||
await navigator.share({
|
await navigator.share({
|
||||||
title: state.translate('-send-brand'),
|
title: state.translate('-send-brand'),
|
||||||
text: `Download "${
|
text: `Download "${archive.name}" with Send: simple, safe file sharing`,
|
||||||
archive.name
|
|
||||||
}" with Firefox Send: simple, safe file sharing`,
|
|
||||||
//state.translate('shareMessage', { name }),
|
//state.translate('shareMessage', { name }),
|
||||||
url: archive.url
|
url: archive.url
|
||||||
});
|
});
|
||||||
@@ -271,22 +324,25 @@ module.exports = function(state, emit, archive) {
|
|||||||
|
|
||||||
module.exports.wip = function(state, emit) {
|
module.exports.wip = function(state, emit) {
|
||||||
return html`
|
return html`
|
||||||
<send-upload-area class="flex flex-col bg-white h-full w-full" id="wip">
|
<send-upload-area
|
||||||
|
class="flex flex-col bg-white h-full w-full dark:bg-grey-90"
|
||||||
|
id="wip"
|
||||||
|
>
|
||||||
${list(
|
${list(
|
||||||
Array.from(state.archive.files)
|
Array.from(state.archive.files)
|
||||||
.reverse()
|
.reverse()
|
||||||
.map(f =>
|
.map(f =>
|
||||||
fileInfo(f, remove(f, state.translate('deleteButtonHover')))
|
fileInfo(f, remove(f, state.translate('deleteButtonHover')))
|
||||||
),
|
),
|
||||||
'flex-shrink bg-grey-lightest rounded-t list-reset overflow-y-auto px-6 py-4 md:h-full md:max-h-half-screen',
|
'flex-shrink bg-grey-10 rounded-t overflow-y-auto px-6 py-4 md:h-full md:max-h-half-screen dark:bg-black',
|
||||||
'bg-white px-2 my-2 shadow-light rounded'
|
'bg-white px-2 my-2 shadow-light rounded dark:bg-grey-90 dark:border dark:border-grey-80'
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
class="flex-no-shrink flex-grow flex items-end p-4 bg-grey-lightest rounded-b mb-1 font-medium"
|
class="flex-shrink-0 flex-grow flex items-end p-4 bg-grey-10 rounded-b mb-1 font-medium dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id="file-upload"
|
id="file-upload"
|
||||||
class="opacity-0 w-0 h-0 appearance-none absolute"
|
class="opacity-0 w-0 h-0 appearance-none absolute overflow-hidden"
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
onfocus="${focus}"
|
onfocus="${focus}"
|
||||||
@@ -302,10 +358,12 @@ module.exports.wip = function(state, emit) {
|
|||||||
class="flex items-center cursor-pointer"
|
class="flex items-center cursor-pointer"
|
||||||
title="${state.translate('addFilesButton')}"
|
title="${state.translate('addFilesButton')}"
|
||||||
>
|
>
|
||||||
<img src="${assets.get('addfiles.svg')}" class="w-6 h-6 mr-2" />
|
<svg class="w-6 h-6 mr-2 link-blue">
|
||||||
|
<use xlink:href="${assets.get('addfiles.svg')}#plus" />
|
||||||
|
</svg>
|
||||||
${state.translate('addFilesButton')}
|
${state.translate('addFilesButton')}
|
||||||
</label>
|
</label>
|
||||||
<div class="font-normal text-sm text-grey-darker">
|
<div class="font-normal text-sm text-grey-70 dark:text-grey-40">
|
||||||
${state.translate('totalSize', {
|
${state.translate('totalSize', {
|
||||||
size: bytes(state.archive.size)
|
size: bytes(state.archive.size)
|
||||||
})}
|
})}
|
||||||
@@ -315,7 +373,7 @@ module.exports.wip = function(state, emit) {
|
|||||||
${expiryOptions(state, emit)} ${password(state, emit)}
|
${expiryOptions(state, emit)} ${password(state, emit)}
|
||||||
<button
|
<button
|
||||||
id="upload-btn"
|
id="upload-btn"
|
||||||
class="btn rounded-lg flex-no-shrink focus:outline"
|
class="btn rounded-lg flex-shrink-0 focus:outline"
|
||||||
title="${state.translate('uploadButton')}"
|
title="${state.translate('uploadButton')}"
|
||||||
onclick="${upload}"
|
onclick="${upload}"
|
||||||
>
|
>
|
||||||
@@ -380,22 +438,22 @@ module.exports.uploading = function(state, emit) {
|
|||||||
return html`
|
return html`
|
||||||
<send-upload-area
|
<send-upload-area
|
||||||
id="${archive.id}"
|
id="${archive.id}"
|
||||||
class="flex flex-col items-start rounded shadow-light bg-white p-4 w-full"
|
class="flex flex-col items-start rounded shadow-light bg-white p-4 w-full dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
${archiveInfo(archive)}
|
${archiveInfo(archive)}
|
||||||
<div class="text-xs text-grey-dark w-full mt-2 mb-2">
|
<div class="text-xs opacity-75 w-full mt-2 mb-2">
|
||||||
${expiryInfo(state.translate, {
|
${expiryInfo(state.translate, {
|
||||||
dlimit: state.archive.dlimit,
|
dlimit: state.archive.dlimit,
|
||||||
dtotal: 0,
|
dtotal: 0,
|
||||||
expiresAt: Date.now() + 500 + state.archive.timeLimit * 1000
|
expiresAt: Date.now() + 500 + state.archive.timeLimit * 1000
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-blue-dark text-sm font-medium mt-2">
|
<div class="link-blue text-sm font-medium mt-2">
|
||||||
${progressPercent}
|
${progressPercent}
|
||||||
</div>
|
</div>
|
||||||
<progress class="my-3" value="${progress}">${progressPercent}</progress>
|
<progress class="my-3" value="${progress}">${progressPercent}</progress>
|
||||||
<button
|
<button
|
||||||
class="text-blue-dark hover:text-blue-darker focus:text-blue-darker self-end font-medium"
|
class="link-blue self-end font-medium"
|
||||||
onclick=${cancel}
|
onclick=${cancel}
|
||||||
title="${state.translate('deletePopupCancel')}"
|
title="${state.translate('deletePopupCancel')}"
|
||||||
>
|
>
|
||||||
@@ -417,7 +475,7 @@ module.exports.empty = function(state, emit) {
|
|||||||
? ''
|
? ''
|
||||||
: html`
|
: html`
|
||||||
<button
|
<button
|
||||||
class="center font-medium text-sm text-blue-dark hover:text-blue-darker focus:text-blue-darker mt-4 mb-2"
|
class="center font-medium text-sm link-blue mt-4 mb-2"
|
||||||
onclick="${event => {
|
onclick="${event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
emit('signup-cta', 'drop');
|
emit('signup-cta', 'drop');
|
||||||
@@ -430,14 +488,16 @@ module.exports.empty = function(state, emit) {
|
|||||||
`;
|
`;
|
||||||
return html`
|
return html`
|
||||||
<send-upload-area
|
<send-upload-area
|
||||||
class="flex flex-col items-center justify-center border-2 border-dashed border-grey rounded px-6 py-16 h-full w-full"
|
class="flex flex-col items-center justify-center border-2 border-dashed border-grey-transparent rounded px-6 py-16 h-full w-full dark:border-grey-60"
|
||||||
onclick="${e => {
|
onclick="${e => {
|
||||||
if (e.target.tagName !== 'LABEL') {
|
if (e.target.tagName !== 'LABEL') {
|
||||||
document.getElementById('file-upload').click();
|
document.getElementById('file-upload').click();
|
||||||
}
|
}
|
||||||
}}"
|
}}"
|
||||||
>
|
>
|
||||||
<img src="${assets.get('addfiles.svg')}" width="48" height="48" />
|
<svg class="w-10 h-10 link-blue">
|
||||||
|
<use xlink:href="/${assets.get('addfiles.svg')}#plus" />
|
||||||
|
</svg>
|
||||||
<div class="pt-6 pb-2 text-center text-lg font-bold tracking-wide">
|
<div class="pt-6 pb-2 text-center text-lg font-bold tracking-wide">
|
||||||
${state.translate('dragAndDropFiles')}
|
${state.translate('dragAndDropFiles')}
|
||||||
</div>
|
</div>
|
||||||
@@ -448,7 +508,7 @@ module.exports.empty = function(state, emit) {
|
|||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
id="file-upload"
|
id="file-upload"
|
||||||
class="opacity-0 w-0 h-0 appearance-none absolute"
|
class="opacity-0 w-0 h-0 appearance-none absolute overflow-hidden"
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
onfocus="${focus}"
|
onfocus="${focus}"
|
||||||
@@ -471,14 +531,11 @@ module.exports.empty = function(state, emit) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function focus(event) {
|
function focus(event) {
|
||||||
event.target.nextElementSibling.classList.add('bg-blue-darker', 'outline');
|
event.target.nextElementSibling.classList.add('bg-blue-70', 'outline');
|
||||||
}
|
}
|
||||||
|
|
||||||
function blur(event) {
|
function blur(event) {
|
||||||
event.target.nextElementSibling.classList.remove(
|
event.target.nextElementSibling.classList.remove('bg-blue-70', 'outline');
|
||||||
'bg-blue-darker',
|
|
||||||
'outline'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function add(event) {
|
function add(event) {
|
||||||
@@ -503,13 +560,15 @@ module.exports.preview = function(state, emit) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
return html`
|
return html`
|
||||||
<send-archive class="flex flex-col max-h-full bg-white p-4 w-full md:w-128">
|
<send-archive
|
||||||
<div class="border rounded py-3 px-6">
|
class="flex flex-col max-h-full bg-white p-4 w-full md:w-128 dark:bg-grey-90"
|
||||||
|
>
|
||||||
|
<div class="border rounded py-3 px-6 dark:border-grey-70">
|
||||||
${archiveInfo(archive)} ${details}
|
${archiveInfo(archive)} ${details}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
id="download-btn"
|
id="download-btn"
|
||||||
class="btn rounded-lg mt-4 w-full flex-no-shrink focus:outline"
|
class="btn rounded-lg mt-4 w-full flex-shrink-0 focus:outline"
|
||||||
title="${state.translate('downloadButtonLabel')}"
|
title="${state.translate('downloadButtonLabel')}"
|
||||||
onclick=${download}
|
onclick=${download}
|
||||||
>
|
>
|
||||||
@@ -531,10 +590,10 @@ module.exports.downloading = function(state) {
|
|||||||
const progressPercent = percent(progress);
|
const progressPercent = percent(progress);
|
||||||
return html`
|
return html`
|
||||||
<send-archive
|
<send-archive
|
||||||
class="flex flex-col bg-white rounded shadow-light p-4 w-full max-w-sm md:w-128"
|
class="flex flex-col bg-white rounded shadow-light p-4 w-full max-w-sm md:w-128 dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
${archiveInfo(archive)}
|
${archiveInfo(archive)}
|
||||||
<div class="text-blue-dark text-sm font-medium mt-2">
|
<div class="link-blue text-sm font-medium mt-2">
|
||||||
${progressPercent}
|
${progressPercent}
|
||||||
</div>
|
</div>
|
||||||
<progress class="my-3" value="${progress}">${progressPercent}</progress>
|
<progress class="my-3" value="${progress}">${progressPercent}</progress>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module.exports = function() {
|
|||||||
return html`
|
return html`
|
||||||
<main class="main">
|
<main class="main">
|
||||||
<section
|
<section
|
||||||
class="h-full w-full p-6 z-10 md:flex md:flex-row md:rounded-lg md:shadow-big"
|
class="h-full w-full p-6 md:p-8 md:flex md:flex-row md:rounded-xl md:shadow-big"
|
||||||
>
|
>
|
||||||
<div class="md:mr-6 md:w-1/2 w-full"></div>
|
<div class="md:mr-6 md:w-1/2 w-full"></div>
|
||||||
<div class="md:w-1/2 mt-6 md:mt-0 w-full"></div>
|
<div class="md:w-1/2 mt-6 md:mt-0 w-full"></div>
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
const Promo = require('./promo');
|
|
||||||
const Header = require('./header');
|
const Header = require('./header');
|
||||||
const Footer = require('./footer');
|
const Footer = require('./footer');
|
||||||
|
|
||||||
function banner(state) {
|
|
||||||
if (state.promo && !state.route.startsWith('/unsupported/')) {
|
|
||||||
return state.cache(Promo, 'promo').render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function body(main) {
|
module.exports = function body(main) {
|
||||||
return function(state, emit) {
|
return function(state, emit) {
|
||||||
const b = html`
|
const b = html`
|
||||||
<body
|
<body
|
||||||
class="flex flex-col items-center font-sans md:h-screen md:bg-grey-lightest"
|
class="flex flex-col items-center font-sans md:h-screen md:bg-grey-10 dark:bg-black"
|
||||||
>
|
>
|
||||||
${banner(state, emit)} ${state.cache(Header, 'header').render()}
|
${state.cache(Header, 'header').render()} ${main(state, emit)}
|
||||||
${main(state, emit)} ${state.cache(Footer, 'footer').render()}
|
${state.cache(Footer, 'footer').render()}
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
if (state.layout) {
|
if (state.layout) {
|
||||||
|
|||||||
@@ -1,35 +1,48 @@
|
|||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
const { copyToClipboard } = require('../utils');
|
const { copyToClipboard } = require('../utils');
|
||||||
|
const qr = require('./qr');
|
||||||
|
|
||||||
module.exports = function(name, url) {
|
module.exports = function(name, url) {
|
||||||
return function(state, emit, close) {
|
const dialog = function(state, emit, close) {
|
||||||
return html`
|
return html`
|
||||||
<send-copy-dialog
|
<send-copy-dialog
|
||||||
class="flex flex-col items-center text-center p-4 max-w-sm m-auto"
|
class="flex flex-col items-center text-center p-4 max-w-sm m-auto"
|
||||||
>
|
>
|
||||||
<h1 class="font-bold my-4">
|
<h1 class="text-3xl font-bold my-4">
|
||||||
${state.translate('notifyUploadEncryptDone')}
|
${state.translate('notifyUploadEncryptDone')}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="font-normal leading-normal text-grey-darkest word-break-all">
|
<p
|
||||||
|
class="font-normal leading-normal text-grey-80 word-break-all dark:text-grey-40"
|
||||||
|
>
|
||||||
${state.translate('copyLinkDescription')} <br />
|
${state.translate('copyLinkDescription')} <br />
|
||||||
${name}
|
${name}
|
||||||
</p>
|
</p>
|
||||||
<input
|
<div class="flex flex-row items-center justify-center w-full">
|
||||||
type="text"
|
<input
|
||||||
id="share-url"
|
type="text"
|
||||||
class="w-full my-4 border rounded-lg leading-loose h-12 px-2 py-1"
|
id="share-url"
|
||||||
value="${url}"
|
class="block w-full my-4 border rounded-lg leading-loose h-12 px-2 py-1 dark:bg-grey-80"
|
||||||
readonly="true"
|
value="${url}"
|
||||||
/>
|
readonly="true"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
id="qr-btn"
|
||||||
|
class="w-16 m-1 p-1"
|
||||||
|
onclick="${toggleQR}"
|
||||||
|
title="QR code"
|
||||||
|
>
|
||||||
|
${qr(url)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
class="btn rounded-lg w-full flex-no-shrink focus:outline"
|
class="btn rounded-lg w-full flex-shrink-0 focus:outline"
|
||||||
onclick="${copy}"
|
onclick="${copy}"
|
||||||
title="${state.translate('copyLinkButton')}"
|
title="${state.translate('copyLinkButton')}"
|
||||||
>
|
>
|
||||||
${state.translate('copyLinkButton')}
|
${state.translate('copyLinkButton')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="text-blue-dark hover:text-blue-darker focus:text-blue-darker my-4 font-medium cursor-pointer focus:outline"
|
class="link-blue my-4 font-medium cursor-pointer focus:outline"
|
||||||
onclick="${close}"
|
onclick="${close}"
|
||||||
title="${state.translate('okButton')}"
|
title="${state.translate('okButton')}"
|
||||||
>
|
>
|
||||||
@@ -38,6 +51,19 @@ module.exports = function(name, url) {
|
|||||||
</send-copy-dialog>
|
</send-copy-dialog>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
function toggleQR(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const shareUrl = document.getElementById('share-url');
|
||||||
|
const qrBtn = document.getElementById('qr-btn');
|
||||||
|
if (shareUrl.classList.contains('hidden')) {
|
||||||
|
shareUrl.classList.replace('hidden', 'block');
|
||||||
|
qrBtn.classList.replace('w-48', 'w-16');
|
||||||
|
} else {
|
||||||
|
shareUrl.classList.replace('block', 'hidden');
|
||||||
|
qrBtn.classList.replace('w-16', 'w-48');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function copy(event) {
|
function copy(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
copyToClipboard(url);
|
copyToClipboard(url);
|
||||||
@@ -45,4 +71,6 @@ module.exports = function(name, url) {
|
|||||||
setTimeout(close, 1000);
|
setTimeout(close, 1000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
dialog.type = 'copy';
|
||||||
|
return dialog;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ function downloading(state, emit) {
|
|||||||
<div
|
<div
|
||||||
class="flex flex-col w-full h-full items-center md:justify-center md:-mt-8"
|
class="flex flex-col w-full h-full items-center md:justify-center md:-mt-8"
|
||||||
>
|
>
|
||||||
<h1 class="mb-4">${state.translate('downloadingTitle')}</h1>
|
<h1 class="text-3xl font-bold mb-4">
|
||||||
|
${state.translate('downloadingTitle')}
|
||||||
|
</h1>
|
||||||
${archiveTile.downloading(state, emit)}
|
${archiveTile.downloading(state, emit)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -36,8 +38,12 @@ function preview(state, emit) {
|
|||||||
<div
|
<div
|
||||||
class="flex flex-col w-full max-w-md h-full mx-auto items-center justify-center"
|
class="flex flex-col w-full max-w-md h-full mx-auto items-center justify-center"
|
||||||
>
|
>
|
||||||
<h1 class="mb-4">${state.translate('downloadTitle')}</h1>
|
<h1 class="text-3xl font-bold mb-4">
|
||||||
<p class="w-full text-grey-darkest text-center leading-normal">
|
${state.translate('downloadTitle')}
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
class="w-full text-grey-80 text-center leading-normal dark:text-grey-40"
|
||||||
|
>
|
||||||
${state.translate('downloadDescription')}
|
${state.translate('downloadDescription')}
|
||||||
</p>
|
</p>
|
||||||
${archiveTile.preview(state, emit)}
|
${archiveTile.preview(state, emit)}
|
||||||
@@ -49,9 +55,13 @@ module.exports = function(state, emit) {
|
|||||||
let content = '';
|
let content = '';
|
||||||
if (!state.fileInfo) {
|
if (!state.fileInfo) {
|
||||||
state.fileInfo = createFileInfo(state);
|
state.fileInfo = createFileInfo(state);
|
||||||
if (!state.fileInfo.nonce) {
|
if (downloadMetadata.status === 404) {
|
||||||
return notFound(state);
|
return notFound(state);
|
||||||
}
|
}
|
||||||
|
if (!state.fileInfo.nonce) {
|
||||||
|
// coming from something like the browser back button
|
||||||
|
return location.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.transfer && !state.fileInfo.requiresPassword) {
|
if (!state.transfer && !state.fileInfo.requiresPassword) {
|
||||||
@@ -76,7 +86,9 @@ module.exports = function(state, emit) {
|
|||||||
return html`
|
return html`
|
||||||
<main class="main">
|
<main class="main">
|
||||||
${state.modal && modal(state, emit)}
|
${state.modal && modal(state, emit)}
|
||||||
<section class="relative h-full w-full p-6 md:rounded-lg md:shadow-big">
|
<section
|
||||||
|
class="relative h-full w-full p-6 md:p-8 md:rounded-xl md:shadow-big"
|
||||||
|
>
|
||||||
${content}
|
${content}
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -2,21 +2,27 @@ const html = require('choo/html');
|
|||||||
const assets = require('../../common/assets');
|
const assets = require('../../common/assets');
|
||||||
|
|
||||||
module.exports = function(state) {
|
module.exports = function(state) {
|
||||||
|
const btnText = state.user.loggedIn ? 'okButton' : 'sendYourFilesLink';
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
id="download-complete"
|
id="download-complete"
|
||||||
class="flex flex-col items-center justify-center h-full w-full bg-white p-2"
|
class="flex flex-col items-center justify-center h-full w-full bg-white p-2 dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
<h1 class="text-center font-bold my-2">
|
<h1 class="text-center text-3xl font-bold my-2">
|
||||||
${state.translate('downloadFinish')}
|
${state.translate('downloadFinish')}
|
||||||
</h1>
|
</h1>
|
||||||
<img src="${assets.get('completed.svg')}" class="my-12 h-48" />
|
<img src="${assets.get('completed.svg')}" class="my-8 h-48" />
|
||||||
<p class="text-grey-darkest leading-normal">
|
<p
|
||||||
|
class="text-grey-80 leading-normal dark:text-grey-40 ${state.user
|
||||||
|
.loggedIn
|
||||||
|
? 'hidden'
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
${state.translate('trySendDescription')}
|
${state.translate('trySendDescription')}
|
||||||
</p>
|
</p>
|
||||||
<p class="my-5">
|
<p class="my-5">
|
||||||
<a href="/" class="btn rounded-lg flex items-center mt-4" role="button"
|
<a href="/" class="btn rounded-lg flex items-center mt-4" role="button"
|
||||||
>${state.translate('sendYourFilesLink')}</a
|
>${state.translate(btnText)}</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ module.exports = function(state, emit) {
|
|||||||
|
|
||||||
const div = html`
|
const div = html`
|
||||||
<div
|
<div
|
||||||
class="h-full w-full flex flex-col items-center justify-center bg-white py-8 max-w-md mx-auto"
|
class="h-full w-full flex flex-col items-center justify-center bg-white py-8 max-w-md mx-auto dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
<h1 class="mb-4">${state.translate('downloadTitle')}</h1>
|
<h1 class="text-3xl font-bold mb-4">
|
||||||
<p class="w-full mb-4 text-center text-grey-darkest leading-normal">
|
${state.translate('downloadTitle')}
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
class="w-full mb-4 text-center text-grey-80 dark:text-grey-40 leading-normal"
|
||||||
|
>
|
||||||
${state.translate('downloadDescription')}
|
${state.translate('downloadDescription')}
|
||||||
</p>
|
</p>
|
||||||
<form
|
<form
|
||||||
@@ -17,11 +21,17 @@ module.exports = function(state, emit) {
|
|||||||
onsubmit="${checkPassword}"
|
onsubmit="${checkPassword}"
|
||||||
data-no-csrf
|
data-no-csrf
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
id="autocomplete-decoy"
|
||||||
|
class="hidden"
|
||||||
|
type="password"
|
||||||
|
value="lol"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
id="password-input"
|
id="password-input"
|
||||||
class="w-full border-l border-t border-b rounded-l-lg rounded-r-none ${invalid
|
class="w-full border-l border-t border-b rounded-l-lg rounded-r-none ${invalid
|
||||||
? 'border-red'
|
? 'border-red dark:border-red-40'
|
||||||
: 'border-grey'} leading-loose px-2 py-1"
|
: 'border-grey'} leading-loose px-2 py-1 dark:bg-grey-80"
|
||||||
maxlength="32"
|
maxlength="32"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
placeholder="${state.translate('unlockInputPlaceholder')}"
|
placeholder="${state.translate('unlockInputPlaceholder')}"
|
||||||
@@ -32,7 +42,7 @@ module.exports = function(state, emit) {
|
|||||||
type="submit"
|
type="submit"
|
||||||
id="password-btn"
|
id="password-btn"
|
||||||
class="btn rounded-r-lg rounded-l-none ${invalid
|
class="btn rounded-r-lg rounded-l-none ${invalid
|
||||||
? 'bg-red hover:bg-red focus:bg-red'
|
? 'bg-red hover:bg-red focus:bg-red dark:bg-red-40'
|
||||||
: ''}"
|
: ''}"
|
||||||
value="${state.translate('unlockButtonLabel')}"
|
value="${state.translate('unlockButtonLabel')}"
|
||||||
title="${state.translate('unlockButtonLabel')}"
|
title="${state.translate('unlockButtonLabel')}"
|
||||||
@@ -40,7 +50,7 @@ module.exports = function(state, emit) {
|
|||||||
</form>
|
</form>
|
||||||
<label
|
<label
|
||||||
id="password-error"
|
id="password-error"
|
||||||
class="${invalid ? '' : 'invisible'} text-red my-4"
|
class="${invalid ? '' : 'invisible'} text-red dark:text-red-40 my-4"
|
||||||
for="password-input"
|
for="password-input"
|
||||||
>
|
>
|
||||||
${state.translate('passwordTryAgain')}
|
${state.translate('passwordTryAgain')}
|
||||||
@@ -59,8 +69,13 @@ module.exports = function(state, emit) {
|
|||||||
const input = document.getElementById('password-input');
|
const input = document.getElementById('password-input');
|
||||||
const btn = document.getElementById('password-btn');
|
const btn = document.getElementById('password-btn');
|
||||||
label.classList.add('invisible');
|
label.classList.add('invisible');
|
||||||
input.classList.remove('border-red');
|
input.classList.remove('border-red', 'dark:border-red-40');
|
||||||
btn.classList.remove('bg-red', 'hover:bg-red', 'focus:bg-red');
|
btn.classList.remove(
|
||||||
|
'bg-red',
|
||||||
|
'hover:bg-red',
|
||||||
|
'focus:bg-red',
|
||||||
|
'dark:bg-red-40'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPassword(event) {
|
function checkPassword(event) {
|
||||||
@@ -70,7 +85,9 @@ module.exports = function(state, emit) {
|
|||||||
const password = el.value;
|
const password = el.value;
|
||||||
if (password.length > 0) {
|
if (password.length > 0) {
|
||||||
document.getElementById('password-btn').disabled = true;
|
document.getElementById('password-btn').disabled = true;
|
||||||
state.fileInfo.url = window.location.href;
|
// Strip any url parameters between fileId and secretKey
|
||||||
|
const fileInfoUrl = window.location.href.replace(/\?.+#/, '#');
|
||||||
|
state.fileInfo.url = fileInfoUrl;
|
||||||
state.fileInfo.password = password;
|
state.fileInfo.password = password;
|
||||||
emit('getMetadata');
|
emit('getMetadata');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,28 @@ const assets = require('../../common/assets');
|
|||||||
const modal = require('./modal');
|
const modal = require('./modal');
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
module.exports = function(state, emit) {
|
||||||
|
const btnText = state.user.loggedIn ? 'okButton' : 'sendYourFilesLink';
|
||||||
return html`
|
return html`
|
||||||
<main class="main">
|
<main class="main">
|
||||||
${state.modal && modal(state, emit)}
|
${state.modal && modal(state, emit)}
|
||||||
<section
|
<section
|
||||||
class="flex flex-col items-center justify-center h-full w-full p-6 z-10 overflow-hidden md:rounded-lg md:shadow-big"
|
class="flex flex-col items-center justify-center h-full w-full p-6 md:p-8 overflow-hidden md:rounded-xl md:shadow-big"
|
||||||
>
|
>
|
||||||
<h1 class="text-center font-bold my-2">
|
<h1 class="text-center text-3xl font-bold my-2">
|
||||||
${state.translate('errorPageHeader')}
|
${state.translate('errorPageHeader')}
|
||||||
</h1>
|
</h1>
|
||||||
<img class="my-12 h-48" src="${assets.get('error.svg')}" />
|
<img class="my-12 h-48" src="${assets.get('error.svg')}" />
|
||||||
<p class="max-w-md text-center text-grey-darkest leading-normal">
|
<p
|
||||||
|
class="max-w-md text-center text-grey-80 leading-normal dark:text-grey-40 ${state
|
||||||
|
.user.loggedIn
|
||||||
|
? 'hidden'
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
${state.translate('trySendDescription')}
|
${state.translate('trySendDescription')}
|
||||||
</p>
|
</p>
|
||||||
<p class="my-5">
|
<p class="my-5">
|
||||||
<a href="/" class="btn rounded-lg flex items-center" role="button"
|
<a href="/" class="btn rounded-lg flex items-center" role="button"
|
||||||
>${state.translate('sendYourFilesLink')}</a
|
>${state.translate(btnText)}</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
41
app/ui/faviconProgressbar.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const { platform } = require('../utils');
|
||||||
|
const assets = require('../../common/assets');
|
||||||
|
|
||||||
|
const size = 32;
|
||||||
|
const loaderWidth = 5;
|
||||||
|
const loaderColor = '#0090ed';
|
||||||
|
|
||||||
|
function drawCircle(canvas, context, color, lineWidth, outerWidth, percent) {
|
||||||
|
canvas.width = canvas.height = outerWidth;
|
||||||
|
context.translate(outerWidth * 0.5, outerWidth * 0.5);
|
||||||
|
context.rotate(-Math.PI * 0.5);
|
||||||
|
const radius = (outerWidth - lineWidth) * 0.5;
|
||||||
|
context.beginPath();
|
||||||
|
context.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
|
||||||
|
context.strokeStyle = color;
|
||||||
|
context.lineCap = 'square';
|
||||||
|
context.lineWidth = lineWidth;
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawNewFavicon(progressRatio) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
drawCircle(canvas, context, '#efefef', loaderWidth, size, 1);
|
||||||
|
drawCircle(canvas, context, loaderColor, loaderWidth, size, progressRatio);
|
||||||
|
return canvas.toDataURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.updateFavicon = function(progressRatio) {
|
||||||
|
if (platform() === 'web') {
|
||||||
|
const link = document.querySelector("link[rel='icon'][sizes='32x32']");
|
||||||
|
const progress = progressRatio * 100;
|
||||||
|
if (progress === 0 || progress === 100) {
|
||||||
|
link.type = 'image/png';
|
||||||
|
link.href = assets.get('favicon-32x32.png');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
link.href = drawNewFavicon(progressRatio);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
const Component = require('choo/component');
|
const Component = require('choo/component');
|
||||||
const version = require('../../package.json').version;
|
|
||||||
const { browserName } = require('../utils');
|
|
||||||
|
|
||||||
class Footer extends Component {
|
class Footer extends Component {
|
||||||
constructor(name, state) {
|
constructor(name, state) {
|
||||||
@@ -15,47 +13,19 @@ class Footer extends Component {
|
|||||||
|
|
||||||
createElement() {
|
createElement() {
|
||||||
const translate = this.state.translate;
|
const translate = this.state.translate;
|
||||||
const browser = browserName();
|
|
||||||
const feedbackUrl = `https://qsurvey.mozilla.com/s3/Firefox-Send-Product-Feedback?ver=${version}&browser=${browser}`;
|
|
||||||
return html`
|
return html`
|
||||||
<footer
|
<footer
|
||||||
class="flex flex-col md:flex-row items-start w-full flex-none self-start p-6 font-medium text-xs text-grey-dark md:items-center justify-between"
|
class="flex flex-col md:flex-row items-start w-full flex-none self-start p-6 md:p-8 font-medium text-xs text-grey-60 dark:text-grey-40 md:items-center justify-between"
|
||||||
>
|
>
|
||||||
<a
|
<div></div>
|
||||||
class="mozilla-logo pb-10 md:pb-0 m-2"
|
|
||||||
href="https://www.mozilla.org/"
|
|
||||||
>
|
|
||||||
Mozilla
|
|
||||||
</a>
|
|
||||||
<ul
|
<ul
|
||||||
class="list-reset flex flex-col md:flex-row items-start md:items-center md:justify-end"
|
class="flex flex-col md:flex-row items-start md:items-center md:justify-end"
|
||||||
>
|
>
|
||||||
<li class="m-2">
|
|
||||||
<a href="https://www.mozilla.org/about/legal/terms/services/#send">
|
|
||||||
${translate('footerLinkLegal')}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="m-2">
|
<li class="m-2">
|
||||||
<a href="/legal"> ${translate('footerLinkPrivacy')} </a>
|
<a href="/legal"> ${translate('footerLinkPrivacy')} </a>
|
||||||
</li>
|
</li>
|
||||||
<li class="m-2">
|
<li class="m-2">
|
||||||
<a href="https://www.mozilla.org/privacy/websites/#cookies">
|
<a href="https://gitlab.com/timvisee/send">Source</a>
|
||||||
${translate('footerLinkCookies')}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="m-2">
|
|
||||||
<a href="https://github.com/mozilla/send">GitHub </a>
|
|
||||||
</li>
|
|
||||||
<li class="m-2">
|
|
||||||
<a
|
|
||||||
href="${feedbackUrl}"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
class="feedback-link"
|
|
||||||
alt="Feedback"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
${translate('siteFeedback')}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -16,19 +16,31 @@ class Header extends Component {
|
|||||||
this.account.render();
|
this.account.render();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
createElement() {
|
createElement() {
|
||||||
const title =
|
const title =
|
||||||
platform() === 'android'
|
platform() === 'android'
|
||||||
? html`
|
? html`
|
||||||
<a class=""><img src="${assets.get('logo.svg')}"/></a>
|
<a class="flex flex-row items-center">
|
||||||
|
<img src="${assets.get('icon.svg')}" />
|
||||||
|
<svg class="w-48">
|
||||||
|
<use xlink:href="${assets.get('wordmark.svg')}#logo" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<a class="" href="/"><img src="${assets.get('logo.svg')}"/></a>
|
<a class="flex flex-row items-center" href="/">
|
||||||
|
<img
|
||||||
|
alt="${this.state.translate('title')}"
|
||||||
|
src="${assets.get('icon.svg')}"
|
||||||
|
/>
|
||||||
|
<svg viewBox="66 0 340 64" class="w-48 md:w-64">
|
||||||
|
<use xlink:href="${assets.get('wordmark.svg')}#logo" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
`;
|
`;
|
||||||
return html`
|
return html`
|
||||||
<header
|
<header
|
||||||
class="relative flex-none flex flex-row items-center justify-between w-full px-6 h-16 md:h-24 z-20 bg-transparent"
|
class="main-header relative flex-none flex flex-row items-center justify-between w-full px-6 md:px-8 h-16 md:h-24 z-20 bg-transparent"
|
||||||
>
|
>
|
||||||
${title} ${this.account.render()}
|
${title} ${this.account.render()}
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -20,19 +20,15 @@ module.exports = function(state, emit) {
|
|||||||
const right =
|
const right =
|
||||||
archives.length === 0
|
archives.length === 0
|
||||||
? intro(state)
|
? intro(state)
|
||||||
: list(
|
: list(archives, 'p-2 h-full overflow-y-auto w-full', 'mb-4 w-full');
|
||||||
archives,
|
|
||||||
'list-reset p-2 h-full overflow-y-auto w-full',
|
|
||||||
'mb-4 w-full'
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<main class="main">
|
<main class="main">
|
||||||
${state.modal && modal(state, emit)}
|
${state.modal && modal(state, emit)}
|
||||||
<section
|
<section
|
||||||
class="h-full w-full p-6 z-10 overflow-hidden md:flex md:flex-row md:rounded-lg md:shadow-big"
|
class="h-full w-full p-6 md:p-8 overflow-hidden md:flex md:flex-row md:rounded-xl md:shadow-big"
|
||||||
>
|
>
|
||||||
<div class="px-2 w-full md:px-0 md:mr-6 md:w-1/2">${left}</div>
|
<div class="px-2 w-full md:px-0 md:mr-8 md:w-1/2">${left}</div>
|
||||||
<div class="mt-6 w-full md:w-1/2 md:-m-2">${right}</div>
|
<div class="mt-6 w-full md:w-1/2 md:-m-2">${right}</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ const assets = require('../../common/assets');
|
|||||||
module.exports = function intro(state) {
|
module.exports = function intro(state) {
|
||||||
return html`
|
return html`
|
||||||
<send-intro
|
<send-intro
|
||||||
class="flex flex-col items-center justify-center bg-white px-6 md:py-0 py-6 mb-0 h-full w-full"
|
class="flex flex-col items-center justify-center bg-white px-6 md:py-0 py-6 mb-0 h-full w-full dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
<div class="mt-12 flex flex-col h-full">
|
<div class="mt-12 flex flex-col h-full">
|
||||||
<h1 class="font-bold">
|
<h1 class="text-3xl font-bold md:pb-2">
|
||||||
${state.translate('introTitle')}
|
${state.translate('introTitle')}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="max-w-sm leading-normal mt-6 md:mt-2 md:pr-16">
|
<p class="max-w-sm leading-loose mt-6 md:mt-2 md:pr-14">
|
||||||
${state.translate('introDescription')}
|
${state.translate('introDescription')}
|
||||||
</p>
|
</p>
|
||||||
<img class="intro" src="${assets.get('intro.svg')}" />
|
<img class="intro" src="${assets.get('intro.svg')}" />
|
||||||
|
|||||||
@@ -2,34 +2,37 @@ const html = require('choo/html');
|
|||||||
const modal = require('./modal');
|
const modal = require('./modal');
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
module.exports = function(state, emit) {
|
||||||
|
state.modal = null;
|
||||||
return html`
|
return html`
|
||||||
<main class="main">
|
<main class="main">
|
||||||
${state.modal && modal(state, emit)}
|
${state.modal && modal(state, emit)}
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center bg-white m-4 px-6 py-8 border border-grey-light md:border-none md:px-12 md:py-16 shadow w-full md:h-full"
|
class="flex flex-col items-center bg-white m-4 px-6 py-8 border border-grey-30 md:border-none md:px-12 md:py-16 shadow w-full md:h-full dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
<h1 class="text-center">${state.translate('legalTitle')}</h1>
|
<h1 class="text-center text-3xl font-bold">
|
||||||
|
${state.translate('legalTitle')}
|
||||||
|
</h1>
|
||||||
<p class="mt-2">${state.translate('legalDateStamp')}</p>
|
<p class="mt-2">${state.translate('legalDateStamp')}</p>
|
||||||
<div class="overflow-scroll py-8 px-12">
|
<div class="overflow-y-scroll py-8 px-12">
|
||||||
<p class="leading-normal">
|
<p class="leading-normal">
|
||||||
<span>When Mozilla receives information from you, our</span>
|
<span>When Mozilla receives information from you, our</span>
|
||||||
<a
|
<a
|
||||||
href="https://www.mozilla.org/privacy/"
|
href="https://www.mozilla.org/privacy/"
|
||||||
target="__blank"
|
target="__blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="text-blue-dark hover:underline"
|
class="link-blue hover:underline"
|
||||||
>Mozilla Privacy Policy</a
|
>Mozilla Privacy Policy</a
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>describes how we handle that information. Below are the top
|
>describes how we handle that information. Below are the top
|
||||||
things you should know about Firefox Send. You can also view the
|
things you should know about Send. You can also view the
|
||||||
code</span
|
code</span
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/mozilla/send/blob/master/docs/metrics.md"
|
href="https://github.com/mozilla/send/blob/master/docs/metrics.md"
|
||||||
target="__blank"
|
target="__blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="text-blue-dark hover:underline"
|
class="link-blue hover:underline"
|
||||||
>here</a
|
>here</a
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ const html = require('choo/html');
|
|||||||
module.exports = function(state, emit) {
|
module.exports = function(state, emit) {
|
||||||
return html`
|
return html`
|
||||||
<send-modal
|
<send-modal
|
||||||
class="absolute pin flex items-center justify-center overflow-hidden z-40 bg-white md:rounded-lg md:my-8"
|
class="absolute inset-0 flex items-center justify-center overflow-hidden z-40 bg-white md:rounded-xl md:my-8 dark:bg-grey-90"
|
||||||
onclick="${close}"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="h-full w-full max-h-screen absolute pin-t flex items-center justify-center"
|
class="h-full w-full max-h-screen absolute top-0 flex justify-center md:items-center"
|
||||||
>
|
>
|
||||||
<div class="w-full" onclick="${e => e.stopPropagation()}">
|
<div class="w-full">
|
||||||
${state.modal(state, emit, close)}
|
${state.modal(state, emit, close)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +20,6 @@ module.exports = function(state, emit) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
state.modal = null;
|
emit('closeModal');
|
||||||
emit('render');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,18 +8,20 @@ module.exports = function(state, emit) {
|
|||||||
<div
|
<div
|
||||||
class="flex flex-col w-full max-w-md h-full mx-auto items-center justify-center"
|
class="flex flex-col w-full max-w-md h-full mx-auto items-center justify-center"
|
||||||
>
|
>
|
||||||
<h1 class="mb-4">${state.translate('downloadTitle')}</h1>
|
<h1 class="mb-4 text-3xl font-bold">${state.translate(
|
||||||
|
'downloadTitle'
|
||||||
|
)}</h1>
|
||||||
<p
|
<p
|
||||||
class="w-full p-2 border border-yellow-light rounded md:w-4/5 text-orange-dark bg-yellow-lighter text-center leading-normal"
|
class="w-full p-2 border border-yellow-50 rounded md:w-4/5 text-orange-60 bg-yellow-40 text-center leading-normal"
|
||||||
>
|
>
|
||||||
⚠️ ${state.translate('noStreamsWarning')} ⚠️
|
⚠️ ${state.translate('noStreamsWarning')} ⚠️
|
||||||
</p>
|
</p>
|
||||||
<form class="md:w-128" onsubmit=${submit}>
|
<form class="md:w-128" onsubmit=${submit}>
|
||||||
<fieldset class="border rounded p-4 my-4" onchange=${optionChanged}>
|
<fieldset class="border rounded p-4 my-4" onchange=${optionChanged}>
|
||||||
<div class="flex items-center mb-2">
|
<div class="flex items-center mb-2">
|
||||||
<img class="mr-3 flex-no-shrink" src="${assets.get(
|
<svg class="h-8 w-6 mr-3 flex-shrink-0 text-white dark:text-grey-90">
|
||||||
'blue_file.svg'
|
<use xlink:href="${assets.get('blue_file.svg')}#icon"/>
|
||||||
)}"/>
|
</svg>
|
||||||
<p class="flex-grow">
|
<p class="flex-grow">
|
||||||
<h1 class="text-base font-medium word-break-all">${
|
<h1 class="text-base font-medium word-break-all">${
|
||||||
archive.name
|
archive.name
|
||||||
@@ -49,10 +51,15 @@ module.exports = function(state, emit) {
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<input
|
<input
|
||||||
class="btn rounded-lg w-full flex flex-no-shrink items-center justify-center"
|
class="btn rounded-lg w-full flex flex-shrink-0 items-center justify-center"
|
||||||
value="${state.translate('copyLinkButton')}"
|
value="${state.translate('copyLinkButton')}"
|
||||||
title="${state.translate('copyLinkButton')}"
|
title="${state.translate('copyLinkButton')}"
|
||||||
type="submit" />
|
type="submit" />
|
||||||
|
<p
|
||||||
|
class="text-grey-80 leading-normal dark:text-grey-40 font-semibold text-center md:my-8 md:text-left"
|
||||||
|
>
|
||||||
|
${state.translate('downloadConfirmDescription')}
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -62,6 +69,7 @@ module.exports = function(state, emit) {
|
|||||||
const choice = event.target.value;
|
const choice = event.target.value;
|
||||||
const button = event.currentTarget.nextElementSibling;
|
const button = event.currentTarget.nextElementSibling;
|
||||||
let title = button.title;
|
let title = button.title;
|
||||||
|
console.error(choice, title);
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
case 'copy':
|
case 'copy':
|
||||||
title = state.translate('copyLinkButton');
|
title = state.translate('copyLinkButton');
|
||||||
|
|||||||
@@ -3,22 +3,28 @@ const assets = require('../../common/assets');
|
|||||||
const modal = require('./modal');
|
const modal = require('./modal');
|
||||||
|
|
||||||
module.exports = function(state, emit) {
|
module.exports = function(state, emit) {
|
||||||
|
const btnText = state.user.loggedIn ? 'okButton' : 'sendYourFilesLink';
|
||||||
return html`
|
return html`
|
||||||
<main class="main">
|
<main class="main">
|
||||||
${state.modal && modal(state, emit)}
|
${state.modal && modal(state, emit)}
|
||||||
<section
|
<section
|
||||||
class="flex flex-col items-center justify-center h-full w-full p-6 z-10 overflow-hidden md:rounded-lg md:shadow-big"
|
class="flex flex-col items-center justify-center h-full w-full p-6 md:p-8 overflow-hidden md:rounded-xl md:shadow-big"
|
||||||
>
|
>
|
||||||
<h1 class="text-center font-bold my-2">
|
<h1 class="text-center text-3xl font-bold my-2">
|
||||||
${state.translate('expiredTitle')}
|
${state.translate('expiredTitle')}
|
||||||
</h1>
|
</h1>
|
||||||
<img src="${assets.get('notFound.svg')}" class="my-12" />
|
<img src="${assets.get('notFound.svg')}" class="my-12" />
|
||||||
<p class="max-w-md text-center text-grey-darkest leading-normal">
|
<p
|
||||||
|
class="max-w-md text-center text-grey-80 leading-normal dark:text-grey-40 ${state
|
||||||
|
.user.loggedIn
|
||||||
|
? 'hidden'
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
${state.translate('trySendDescription')}
|
${state.translate('trySendDescription')}
|
||||||
</p>
|
</p>
|
||||||
<p class="my-5">
|
<p class="my-5">
|
||||||
<a href="/" class="btn rounded-lg flex items-center" role="button"
|
<a href="/" class="btn rounded-lg flex items-center" role="button"
|
||||||
>${state.translate('sendYourFilesLink')}</a
|
>${state.translate(btnText)}</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ module.exports = function(message) {
|
|||||||
return function(state, emit, close) {
|
return function(state, emit, close) {
|
||||||
return html`
|
return html`
|
||||||
<send-ok-dialog class="flex flex-col max-w-sm p-4 m-auto">
|
<send-ok-dialog class="flex flex-col max-w-sm p-4 m-auto">
|
||||||
<h2 class="text-center m-8 leading-normal">${message}</h2>
|
<h2 class="text-center text-xl font-bold m-8 leading-normal">
|
||||||
|
${message}
|
||||||
|
</h2>
|
||||||
<button
|
<button
|
||||||
class="btn rounded-lg w-full flex-no-shrink"
|
class="btn rounded-lg w-full flex-shrink-0"
|
||||||
onclick="${close}"
|
onclick="${close}"
|
||||||
title="${state.translate('okButton')}"
|
title="${state.translate('okButton')}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
const html = require('choo/html');
|
|
||||||
const Component = require('choo/component');
|
|
||||||
const assets = require('../../common/assets');
|
|
||||||
|
|
||||||
class Promo extends Component {
|
|
||||||
constructor(name) {
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
createElement() {
|
|
||||||
return html`
|
|
||||||
<send-promo
|
|
||||||
class="w-full flex-none flex flex-row items-center content-center justify-center text-sm bg-grey-light leading-tight text-grey-darkest px-4 py-3"
|
|
||||||
>
|
|
||||||
<div class="flex items-center mx-auto">
|
|
||||||
<img
|
|
||||||
src="${assets.get('firefox_logo-only.svg')}"
|
|
||||||
class="w-6"
|
|
||||||
alt="Firefox"
|
|
||||||
/>
|
|
||||||
<span class="ml-3"
|
|
||||||
>Send is brought to you by the all-new Firefox.
|
|
||||||
<a
|
|
||||||
class="text-blue"
|
|
||||||
href="https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com"
|
|
||||||
>Download Firefox now ≫</a
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</send-promo>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Promo;
|
|
||||||
10
app/ui/qr.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const raw = require('choo/html/raw');
|
||||||
|
const qrcode = require('../qrcode');
|
||||||
|
|
||||||
|
module.exports = function(url) {
|
||||||
|
const gen = qrcode(5, 'L');
|
||||||
|
gen.addData(url);
|
||||||
|
gen.make();
|
||||||
|
const qr = gen.createSvgTag({ scalable: true });
|
||||||
|
return raw(qr);
|
||||||
|
};
|
||||||
@@ -6,7 +6,7 @@ module.exports = function(selected, options, translate, changed, htmlId) {
|
|||||||
return html`
|
return html`
|
||||||
<select
|
<select
|
||||||
id="${htmlId}"
|
id="${htmlId}"
|
||||||
class="appearance-none cursor-pointer border rounded bg-grey-lightest hover:border-blue-dark focus:border-blue-dark pl-1 pr-8 py-1 my-1 h-8"
|
class="appearance-none cursor-pointer border rounded bg-grey-10 hover:border-blue-50 focus:border-blue-50 pl-1 pr-8 py-1 my-1 h-8 dark:bg-grey-80"
|
||||||
onchange="${choose}"
|
onchange="${choose}"
|
||||||
>
|
>
|
||||||
${options.map(
|
${options.map(
|
||||||
|
|||||||
@@ -1,40 +1,36 @@
|
|||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
|
|
||||||
/* Possible strings for l10n
|
|
||||||
shareLinkDescription = Share the link to your file:
|
|
||||||
shareLinkButton = Share link
|
|
||||||
shareMessage = Download "{ $name }" with { -send-brand }: simple, safe file sharing
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function(name, url) {
|
module.exports = function(name, url) {
|
||||||
return function(state, emit, close) {
|
const dialog = function(state, emit, close) {
|
||||||
return html`
|
return html`
|
||||||
<send-share-dialog
|
<send-share-dialog
|
||||||
class="flex flex-col items-center text-center p-4 max-w-sm m-auto"
|
class="flex flex-col items-center text-center p-4 max-w-sm m-auto"
|
||||||
>
|
>
|
||||||
<h1 class="font-bold my-4">
|
<h1 class="text-3xl font-bold my-4">
|
||||||
${state.translate('notifyUploadEncryptDone')}
|
${state.translate('notifyUploadEncryptDone')}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="font-normal leading-normal text-grey-darkest word-break-all">
|
<p
|
||||||
Share the link to your file:<br />
|
class="font-normal leading-normal text-grey-80 word-break-all dark:text-grey-40"
|
||||||
|
>
|
||||||
|
${state.translate('shareLinkDescription')}<br />
|
||||||
${name}
|
${name}
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="share-url"
|
id="share-url"
|
||||||
class="w-full my-4 border rounded-lg leading-loose h-12 px-2 py-1"
|
class="w-full my-4 border rounded-lg leading-loose h-12 px-2 py-1 dark:bg-grey-80"
|
||||||
value="${url}"
|
value="${url}"
|
||||||
readonly="true"
|
readonly="true"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="btn rounded-lg w-full flex-no-shrink focus:outline"
|
class="btn rounded-lg w-full flex-shrink-0 focus:outline"
|
||||||
onclick="${share}"
|
onclick="${share}"
|
||||||
title="Share link"
|
title="${state.translate('shareLinkButton')}"
|
||||||
>
|
>
|
||||||
Share link
|
${state.translate('shareLinkButton')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="text-blue-dark hover:text-blue-darker focus:text-blue-darker my-4 font-medium cursor-pointer focus:outline"
|
class="link-blue my-4 font-medium cursor-pointer focus:outline"
|
||||||
onclick="${close}"
|
onclick="${close}"
|
||||||
title="${state.translate('okButton')}"
|
title="${state.translate('okButton')}"
|
||||||
>
|
>
|
||||||
@@ -48,8 +44,7 @@ module.exports = function(name, url) {
|
|||||||
try {
|
try {
|
||||||
await navigator.share({
|
await navigator.share({
|
||||||
title: state.translate('-send-brand'),
|
title: state.translate('-send-brand'),
|
||||||
text: `Download "${name}" with Firefox Send: simple, safe file sharing`,
|
text: state.translate('shareMessage', { name }),
|
||||||
//state.translate('shareMessage', { name }),
|
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -61,4 +56,6 @@ module.exports = function(name, url) {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
dialog.type = 'share';
|
||||||
|
return dialog;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,29 +1,23 @@
|
|||||||
const html = require('choo/html');
|
const html = require('choo/html');
|
||||||
const assets = require('../../common/assets');
|
const assets = require('../../common/assets');
|
||||||
const { bytes, platform } = require('../utils');
|
const { bytes } = require('../utils');
|
||||||
const { canceledSignup, submittedSignup } = require('../metrics');
|
const { canceledSignup, submittedSignup } = require('../metrics');
|
||||||
|
|
||||||
module.exports = function(trigger) {
|
module.exports = function(trigger) {
|
||||||
return function(state, emit, close) {
|
return function(state, emit, close) {
|
||||||
const DAYS = Math.floor(state.LIMITS.MAX_EXPIRE_SECONDS / 86400);
|
const DAYS = Math.floor(state.LIMITS.MAX_EXPIRE_SECONDS / 86400);
|
||||||
const hidden = platform() === 'android' ? 'hidden' : '';
|
|
||||||
let submitting = false;
|
let submitting = false;
|
||||||
return html`
|
return html`
|
||||||
<send-signup-dialog
|
<send-signup-dialog
|
||||||
class="flex flex-col lg:flex-row justify-center px-8 md:px-24 w-full h-full"
|
class="flex flex-col justify-center my-16 md:my-0 px-8 md:px-24 w-full h-full"
|
||||||
>
|
>
|
||||||
<img
|
<img src="${assets.get('master-logo.svg')}" class="h-16 mt-1 mb-4" />
|
||||||
src="${assets.get('firefox_logo-only.svg')}"
|
<section class="flex flex-col flex-shrink-0 self-center">
|
||||||
class="h-16 mt-1 mb-4"
|
<h1 class="text-3xl font-bold text-center">
|
||||||
/>
|
|
||||||
<section
|
|
||||||
class="flex flex-col flex-no-shrink self-center lg:mx-6 lg:max-w-xs"
|
|
||||||
>
|
|
||||||
<h1 class="font-bold text-center lg:text-left">
|
|
||||||
${state.translate('accountBenefitTitle')}
|
${state.translate('accountBenefitTitle')}
|
||||||
</h1>
|
</h1>
|
||||||
<ul
|
<ul
|
||||||
class="leading-normal text-grey-darkest my-2 mt-4 pl-4 md:self-center"
|
class="leading-normal list-disc text-grey-80 my-2 mt-4 pl-4 md:self-center dark:text-grey-40"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
${state.translate('accountBenefitLargeFiles', {
|
${state.translate('accountBenefitLargeFiles', {
|
||||||
@@ -35,34 +29,35 @@ module.exports = function(trigger) {
|
|||||||
${state.translate('accountBenefitTimeLimit', { count: DAYS })}
|
${state.translate('accountBenefitTimeLimit', { count: DAYS })}
|
||||||
</li>
|
</li>
|
||||||
<li>${state.translate('accountBenefitSync')}</li>
|
<li>${state.translate('accountBenefitSync')}</li>
|
||||||
<li>${state.translate('accountBenefitMoz')}</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section
|
<section class="flex flex-col flex-grow m-4 md:self-center md:w-128">
|
||||||
class="flex flex-col flex-grow m-4 md:self-center md:w-128 lg:max-w-xs"
|
|
||||||
>
|
|
||||||
<form onsubmit=${submitEmail} data-no-csrf>
|
<form onsubmit=${submitEmail} data-no-csrf>
|
||||||
<input
|
<input
|
||||||
id="email-input"
|
id="email-input"
|
||||||
type="email"
|
type="email"
|
||||||
class="${hidden} border rounded-lg w-full px-2 py-1 h-12 mb-3 text-lg text-grey-darker leading-loose"
|
class="hidden border rounded-lg w-full px-2 py-1 h-12 mb-3 text-lg text-grey-70 leading-loose dark:bg-grey-80 dark:text-white"
|
||||||
placeholder=${state.translate('emailPlaceholder')}
|
placeholder=${state.translate('emailPlaceholder')}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
class="btn rounded-lg w-full flex flex-no-shrink items-center justify-center"
|
class="btn rounded-lg w-full flex flex-shrink-0 items-center justify-center"
|
||||||
value="${state.translate('signInButton')}"
|
value="${state.translate('signInOnlyButton')}"
|
||||||
title="${state.translate('signInButton')}"
|
title="${state.translate('signInOnlyButton')}"
|
||||||
id="email-submit"
|
id="email-submit"
|
||||||
type="submit"
|
type="submit"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
<button
|
${state.user.loginRequired
|
||||||
class="my-3 text-blue-dark hover:text-blue-darker focus:text-blue-darker font-medium"
|
? ''
|
||||||
title="${state.translate('deletePopupCancel')}"
|
: html`
|
||||||
onclick=${cancel}
|
<button
|
||||||
>
|
class="my-3 link-blue font-medium"
|
||||||
${state.translate('deletePopupCancel')}
|
title="${state.translate('deletePopupCancel')}"
|
||||||
</button>
|
onclick=${cancel}
|
||||||
|
>
|
||||||
|
${state.translate('deletePopupCancel')}
|
||||||
|
</button>
|
||||||
|
`}
|
||||||
</section>
|
</section>
|
||||||
</send-signup-dialog>
|
</send-signup-dialog>
|
||||||
`;
|
`;
|
||||||
|
|||||||
42
app/ui/surveyDialog.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
const html = require('choo/html');
|
||||||
|
const version = require('../../package.json').version;
|
||||||
|
const { browserName } = require('../utils');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
return function(state, emit, close) {
|
||||||
|
const surveyUrl = `${
|
||||||
|
state.PREFS.surveyUrl
|
||||||
|
}?ver=${version}&browser=${browserName()}&anon=${
|
||||||
|
state.user.loggedIn
|
||||||
|
}&active_count=${state.storage.files.length}`;
|
||||||
|
return html`
|
||||||
|
<send-survey-dialog
|
||||||
|
class="flex flex-col items-center text-center p-4 max-w-sm m-auto"
|
||||||
|
>
|
||||||
|
<h1 class="text-3xl font-bold my-4">
|
||||||
|
Tell us what you think.
|
||||||
|
</h1>
|
||||||
|
<p class="font-normal leading-normal text-grey-80 px-4">
|
||||||
|
Love Send? Take a quick survey to let us know how we can make it
|
||||||
|
better.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
class="btn rounded-lg w-full flex-shrink-0 focus:outline my-5"
|
||||||
|
onclick="${() => emit('closeModal')}"
|
||||||
|
title="Give feedback"
|
||||||
|
href="${surveyUrl}"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Give feedback
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
class="link-blue font-medium cursor-pointer focus:outline"
|
||||||
|
onclick="${close}"
|
||||||
|
title="Skip"
|
||||||
|
>
|
||||||
|
Skip
|
||||||
|
</button>
|
||||||
|
</send-survey-dialog>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -27,9 +27,9 @@ module.exports = function(state, emit) {
|
|||||||
<main class="main">
|
<main class="main">
|
||||||
${state.modal && modal(state, emit)}
|
${state.modal && modal(state, emit)}
|
||||||
<section
|
<section
|
||||||
class="flex flex-col items-center justify-center text-center bg-white m-6 px-6 py-8 border border-grey-light md:border-none md:px-12 md:py-16 shadow w-full md:h-full"
|
class="flex flex-col items-center justify-center text-center bg-white m-6 px-6 py-8 border border-grey-30 md:border-none md:px-12 md:py-16 shadow w-full md:h-full dark:bg-grey-90"
|
||||||
>
|
>
|
||||||
<h1 class="">${strings.header}</h1>
|
<h1 class="text-3xl font-bold">${strings.header}</h1>
|
||||||
<p class="mt-4 mb-8 max-w-md leading-normal">${strings.description}</p>
|
<p class="mt-4 mb-8 max-w-md leading-normal">${strings.description}</p>
|
||||||
${why}
|
${why}
|
||||||
<a href="${url}" class="btn rounded-lg mt-8 px-8">
|
<a href="${url}" class="btn rounded-lg mt-8 px-8">
|
||||||
|
|||||||
98
app/user.js
@@ -44,6 +44,14 @@ export default class User {
|
|||||||
this.storage.set('firstAction', action);
|
this.storage.set('firstAction', action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get surveyed() {
|
||||||
|
return this.storage.get('surveyed');
|
||||||
|
}
|
||||||
|
|
||||||
|
set surveyed(yes) {
|
||||||
|
this.storage.set('surveyed', yes);
|
||||||
|
}
|
||||||
|
|
||||||
get avatar() {
|
get avatar() {
|
||||||
const defaultAvatar = assets.get('user.svg');
|
const defaultAvatar = assets.get('user.svg');
|
||||||
if (this.info.avatarDefault) {
|
if (this.info.avatarDefault) {
|
||||||
@@ -68,6 +76,10 @@ export default class User {
|
|||||||
return this.info.access_token;
|
return this.info.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get refreshToken() {
|
||||||
|
return this.info.refresh_token;
|
||||||
|
}
|
||||||
|
|
||||||
get maxSize() {
|
get maxSize() {
|
||||||
return this.loggedIn
|
return this.loggedIn
|
||||||
? this.limits.MAX_FILE_SIZE
|
? this.limits.MAX_FILE_SIZE
|
||||||
@@ -94,10 +106,12 @@ export default class User {
|
|||||||
return this.loggedIn ? hashId(this.storage.id) : hashId(anonId);
|
return this.loggedIn ? hashId(this.storage.id) : hashId(anonId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async startAuthFlow(source, utms = {}) {
|
async startAuthFlow(trigger, utms = {}) {
|
||||||
|
this.utms = utms;
|
||||||
|
this.trigger = trigger;
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
entrypoint: `send-${source}`,
|
entrypoint: `send-${trigger}`,
|
||||||
form_type: 'email',
|
form_type: 'email',
|
||||||
utm_source: utms.source || 'send',
|
utm_source: utms.source || 'send',
|
||||||
utm_campaign: utms.campaign || 'none'
|
utm_campaign: utms.campaign || 'none'
|
||||||
@@ -111,12 +125,10 @@ export default class User {
|
|||||||
const { flowId, flowBeginTime } = await res.json();
|
const { flowId, flowBeginTime } = await res.json();
|
||||||
this.flowId = flowId;
|
this.flowId = flowId;
|
||||||
this.flowBeginTime = flowBeginTime;
|
this.flowBeginTime = flowBeginTime;
|
||||||
this.utms = utms;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
this.flowId = null;
|
this.flowId = null;
|
||||||
this.flowBeginTime = null;
|
this.flowBeginTime = null;
|
||||||
this.utms = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +139,7 @@ export default class User {
|
|||||||
const code_challenge = await preparePkce(this.storage);
|
const code_challenge = await preparePkce(this.storage);
|
||||||
const options = {
|
const options = {
|
||||||
action: 'email',
|
action: 'email',
|
||||||
|
access_type: 'offline',
|
||||||
client_id: this.authConfig.client_id,
|
client_id: this.authConfig.client_id,
|
||||||
code_challenge,
|
code_challenge,
|
||||||
code_challenge_method: 'S256',
|
code_challenge_method: 'S256',
|
||||||
@@ -142,6 +155,9 @@ export default class User {
|
|||||||
options.flow_id = this.flowId;
|
options.flow_id = this.flowId;
|
||||||
options.flow_begin_time = this.flowBeginTime;
|
options.flow_begin_time = this.flowBeginTime;
|
||||||
}
|
}
|
||||||
|
if (this.trigger) {
|
||||||
|
options.entrypoint = `send-${this.trigger}`;
|
||||||
|
}
|
||||||
if (this.utms) {
|
if (this.utms) {
|
||||||
options.utm_campaign = this.utms.campaign || 'none';
|
options.utm_campaign = this.utms.campaign || 'none';
|
||||||
options.utm_content = this.utms.content || 'none';
|
options.utm_content = this.utms.content || 'none';
|
||||||
@@ -181,12 +197,69 @@ export default class User {
|
|||||||
});
|
});
|
||||||
const userInfo = await infoResponse.json();
|
const userInfo = await infoResponse.json();
|
||||||
userInfo.access_token = auth.access_token;
|
userInfo.access_token = auth.access_token;
|
||||||
|
userInfo.refresh_token = auth.refresh_token;
|
||||||
userInfo.fileListKey = await getFileListKey(this.storage, auth.keys_jwe);
|
userInfo.fileListKey = await getFileListKey(this.storage, auth.keys_jwe);
|
||||||
this.info = userInfo;
|
this.info = userInfo;
|
||||||
this.storage.remove('pkceVerifier');
|
this.storage.remove('pkceVerifier');
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
async refresh() {
|
||||||
|
if (!this.refreshToken) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const tokenResponse = await fetch(this.authConfig.token_endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
client_id: this.authConfig.client_id,
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: this.refreshToken
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (tokenResponse.ok) {
|
||||||
|
const auth = await tokenResponse.json();
|
||||||
|
const info = { ...this.info, access_token: auth.access_token };
|
||||||
|
this.info = info;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
await this.logout();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
try {
|
||||||
|
if (this.refreshToken) {
|
||||||
|
await fetch(this.authConfig.revocation_endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
refresh_token: this.refreshToken
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.bearerToken) {
|
||||||
|
await fetch(this.authConfig.revocation_endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: this.bearerToken
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
// oh well, we tried
|
||||||
|
}
|
||||||
this.storage.clearLocalFiles();
|
this.storage.clearLocalFiles();
|
||||||
this.info = {};
|
this.info = {};
|
||||||
}
|
}
|
||||||
@@ -200,6 +273,14 @@ export default class User {
|
|||||||
const key = b64ToArray(this.info.fileListKey);
|
const key = b64ToArray(this.info.fileListKey);
|
||||||
const sha = await crypto.subtle.digest('SHA-256', key);
|
const sha = await crypto.subtle.digest('SHA-256', key);
|
||||||
const kid = arrayToB64(new Uint8Array(sha)).substring(0, 16);
|
const kid = arrayToB64(new Uint8Array(sha)).substring(0, 16);
|
||||||
|
const retry = async () => {
|
||||||
|
const refreshed = await this.refresh();
|
||||||
|
if (refreshed) {
|
||||||
|
return await this.syncFileList();
|
||||||
|
} else {
|
||||||
|
return { incoming: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
const encrypted = await getFileList(this.bearerToken, kid);
|
const encrypted = await getFileList(this.bearerToken, kid);
|
||||||
const decrypted = await streamToArrayBuffer(
|
const decrypted = await streamToArrayBuffer(
|
||||||
@@ -208,8 +289,7 @@ export default class User {
|
|||||||
list = JSON.parse(textDecoder.decode(decrypted));
|
list = JSON.parse(textDecoder.decode(decrypted));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message === '401') {
|
if (e.message === '401') {
|
||||||
this.logout();
|
return retry(e);
|
||||||
return { incoming: true };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changes = await this.storage.merge(list);
|
changes = await this.storage.merge(list);
|
||||||
@@ -225,7 +305,9 @@ export default class User {
|
|||||||
);
|
);
|
||||||
await setFileList(this.bearerToken, kid, encrypted);
|
await setFileList(this.bearerToken, kid, encrypted);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
if (e.message === '401') {
|
||||||
|
return retry(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|||||||
44
app/utils.js
@@ -1,5 +1,10 @@
|
|||||||
/* global Android */
|
/* global Android */
|
||||||
const html = require('choo/html');
|
let html;
|
||||||
|
try {
|
||||||
|
html = require('choo/html');
|
||||||
|
} catch (e) {
|
||||||
|
// running in the service worker
|
||||||
|
}
|
||||||
const b64 = require('base64-js');
|
const b64 = require('base64-js');
|
||||||
|
|
||||||
function arrayToB64(array) {
|
function arrayToB64(array) {
|
||||||
@@ -14,6 +19,10 @@ function b64ToArray(str) {
|
|||||||
return b64.toByteArray(str + '==='.slice((str.length + 3) % 4));
|
return b64.toByteArray(str + '==='.slice((str.length + 3) % 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function locale() {
|
||||||
|
return document.querySelector('html').lang;
|
||||||
|
}
|
||||||
|
|
||||||
function loadShim(polyfill) {
|
function loadShim(polyfill) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const shim = document.createElement('script');
|
const shim = document.createElement('script');
|
||||||
@@ -25,7 +34,7 @@ function loadShim(polyfill) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isFile(id) {
|
function isFile(id) {
|
||||||
return /^[0-9a-fA-F]{10}$/.test(id);
|
return /^[0-9a-fA-F]{10,16}$/.test(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyToClipboard(str) {
|
function copyToClipboard(str) {
|
||||||
@@ -56,7 +65,7 @@ const LOCALIZE_NUMBERS = !!(
|
|||||||
typeof navigator === 'object'
|
typeof navigator === 'object'
|
||||||
);
|
);
|
||||||
|
|
||||||
const UNITS = ['B', 'kB', 'MB', 'GB'];
|
const UNITS = ['bytes', 'kb', 'mb', 'gb'];
|
||||||
function bytes(num) {
|
function bytes(num) {
|
||||||
if (num < 1) {
|
if (num < 1) {
|
||||||
return '0B';
|
return '0B';
|
||||||
@@ -67,8 +76,7 @@ function bytes(num) {
|
|||||||
let nStr = n.toFixed(decimalDigits);
|
let nStr = n.toFixed(decimalDigits);
|
||||||
if (LOCALIZE_NUMBERS) {
|
if (LOCALIZE_NUMBERS) {
|
||||||
try {
|
try {
|
||||||
const locale = document.querySelector('html').lang;
|
nStr = n.toLocaleString(locale(), {
|
||||||
nStr = n.toLocaleString(locale, {
|
|
||||||
minimumFractionDigits: decimalDigits,
|
minimumFractionDigits: decimalDigits,
|
||||||
maximumFractionDigits: decimalDigits
|
maximumFractionDigits: decimalDigits
|
||||||
});
|
});
|
||||||
@@ -76,14 +84,16 @@ function bytes(num) {
|
|||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `${nStr}${UNITS[exponent]}`;
|
return translate('fileSize', {
|
||||||
|
num: nStr,
|
||||||
|
units: translate(UNITS[exponent])
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function percent(ratio) {
|
function percent(ratio) {
|
||||||
if (LOCALIZE_NUMBERS) {
|
if (LOCALIZE_NUMBERS) {
|
||||||
try {
|
try {
|
||||||
const locale = document.querySelector('html').lang;
|
return ratio.toLocaleString(locale(), { style: 'percent' });
|
||||||
return ratio.toLocaleString(locale, { style: 'percent' });
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
@@ -93,8 +103,7 @@ function percent(ratio) {
|
|||||||
|
|
||||||
function number(n) {
|
function number(n) {
|
||||||
if (LOCALIZE_NUMBERS) {
|
if (LOCALIZE_NUMBERS) {
|
||||||
const locale = document.querySelector('html').lang;
|
return n.toLocaleString(locale());
|
||||||
return n.toLocaleString(locale);
|
|
||||||
}
|
}
|
||||||
return n.toString();
|
return n.toString();
|
||||||
}
|
}
|
||||||
@@ -133,12 +142,16 @@ function openLinksInNewTab(links, should = true) {
|
|||||||
|
|
||||||
function browserName() {
|
function browserName() {
|
||||||
try {
|
try {
|
||||||
|
// order of these matters
|
||||||
if (/firefox/i.test(navigator.userAgent)) {
|
if (/firefox/i.test(navigator.userAgent)) {
|
||||||
return 'firefox';
|
return 'firefox';
|
||||||
}
|
}
|
||||||
if (/edge/i.test(navigator.userAgent)) {
|
if (/edge/i.test(navigator.userAgent)) {
|
||||||
return 'edge';
|
return 'edge';
|
||||||
}
|
}
|
||||||
|
if (/edg/i.test(navigator.userAgent)) {
|
||||||
|
return 'edgium';
|
||||||
|
}
|
||||||
if (/trident/i.test(navigator.userAgent)) {
|
if (/trident/i.test(navigator.userAgent)) {
|
||||||
return 'ie';
|
return 'ie';
|
||||||
}
|
}
|
||||||
@@ -256,7 +269,15 @@ function encryptedSize(size, rs = ECE_RECORD_SIZE, tagLength = TAG_LENGTH) {
|
|||||||
return 21 + size + chunk_meta * Math.ceil(size / (rs - chunk_meta));
|
return 21 + size + chunk_meta * Math.ceil(size / (rs - chunk_meta));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let translate = function() {
|
||||||
|
throw new Error('uninitialized translate function. call setTranslate first');
|
||||||
|
};
|
||||||
|
function setTranslate(t) {
|
||||||
|
translate = t;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
locale,
|
||||||
fadeOut,
|
fadeOut,
|
||||||
delay,
|
delay,
|
||||||
allowedCopy,
|
allowedCopy,
|
||||||
@@ -275,5 +296,6 @@ module.exports = {
|
|||||||
secondsToL10nId,
|
secondsToL10nId,
|
||||||
timeLeft,
|
timeLeft,
|
||||||
platform,
|
platform,
|
||||||
encryptedSize
|
encryptedSize,
|
||||||
|
setTranslate
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class File {
|
|||||||
v.setUint32(0, 0x08074b50, true); // sig
|
v.setUint32(0, 0x08074b50, true); // sig
|
||||||
v.setUint32(4, this.crc, true); // crc32
|
v.setUint32(4, this.crc, true); // crc32
|
||||||
v.setUint32(8, this.size, true); // compressed size
|
v.setUint32(8, this.size, true); // compressed size
|
||||||
v.setUint16(12, this.size, true); // uncompressed size
|
v.setUint32(12, this.size, true); // uncompressed size
|
||||||
return new Uint8Array(dd);
|
return new Uint8Array(dd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class File {
|
|||||||
v.setUint32(0, 0x02014b50, true); // sig
|
v.setUint32(0, 0x02014b50, true); // sig
|
||||||
v.setUint16(4, 20, true); // version made
|
v.setUint16(4, 20, true); // version made
|
||||||
v.setUint16(6, 20, true); // version required
|
v.setUint16(6, 20, true); // version required
|
||||||
v.setUint16(8, 0, true); // bit flags
|
v.setUint16(8, 8, true); // bit flags (8 = use data descriptor)
|
||||||
v.setUint16(10, 0, true); // compression
|
v.setUint16(10, 0, true); // compression
|
||||||
v.setUint16(12, this.dateTime.time, true); // modified time
|
v.setUint16(12, this.dateTime.time, true); // modified time
|
||||||
v.setUint16(14, this.dateTime.date, true); // modified date
|
v.setUint16(14, this.dateTime.date, true); // modified date
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<defs>
|
<symbol id="plus" viewBox="0 0 24 24">
|
||||||
<polygon id="addfiles-a" points="11.143 6 11.143 11.143 6 11.143 6 12.857 11.143 12.857 11.143 18 12.857 18 12.857 12.857 18 12.857 18 11.143 12.857 11.143 12.857 6"/>
|
|
||||||
</defs>
|
|
||||||
<g fill="none" fill-rule="evenodd">
|
<g fill="none" fill-rule="evenodd">
|
||||||
<path fill="#0060df" fill-rule="nonzero" d="M12,22.6666667 C17.8910373,22.6666667 22.6666667,17.8910373 22.6666667,12 C22.6666667,6.10896267 17.8910373,1.33333333 12,1.33333333 C6.10896267,1.33333333 1.33333333,6.10896267 1.33333333,12 C1.33333333,17.8910373 6.10896267,22.6666667 12,22.6666667 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z"/>
|
<path fill="currentColor" fill-rule="nonzero" d="M12,22.6666667 C17.8910373,22.6666667 22.6666667,17.8910373 22.6666667,12 C22.6666667,6.10896267 17.8910373,1.33333333 12,1.33333333 C6.10896267,1.33333333 1.33333333,6.10896267 1.33333333,12 C1.33333333,17.8910373 6.10896267,22.6666667 12,22.6666667 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z"/>
|
||||||
<use fill="#0060df" xlink:href="#addfiles-a"/>
|
<polygon fill="currentColor" points="11.143 6 11.143 11.143 6 11.143 6 12.857 11.143 12.857 11.143 18 12.857 18 12.857 12.857 18 12.857 18 11.143 12.857 11.143 12.857 6"/>
|
||||||
</g>
|
</g>
|
||||||
|
</symbol>
|
||||||
|
<use xlink:href="#plus"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 856 B After Width: | Height: | Size: 831 B |
@@ -1,8 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="26px" height="32px" viewBox="0 0 26 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<!-- Generator: sketchtool 53 (72520) - https://sketchapp.com -->
|
<symbol id="icon" viewBox="0 0 26 32">
|
||||||
<title>6AF7DAB4-6456-44F2-AABE-F001D910B641</title>
|
|
||||||
<desc>Created with sketchtool.</desc>
|
|
||||||
<defs>
|
<defs>
|
||||||
<path d="M21.848475,31.6653183 L2.89005487,31.6653183 C1.32717435,31.6653183 0.104050469,30.3742431 0.104050469,28.8793139 L0.104050469,2.7860044 C0.104050469,1.22312388 1.39512568,0 2.89005487,0 L14.7135857,0 C15.4610503,0 16.2085149,0.271805307 16.6841742,0.815415921 L23.8190635,7.95030523 C24.3626741,8.49391584 24.6344794,9.17342911 24.6344794,9.9208937 L24.6344794,28.8793139 C24.6344794,30.3742431 23.4113555,31.6653183 21.848475,31.6653183 Z" id="path-1"></path>
|
<path d="M21.848475,31.6653183 L2.89005487,31.6653183 C1.32717435,31.6653183 0.104050469,30.3742431 0.104050469,28.8793139 L0.104050469,2.7860044 C0.104050469,1.22312388 1.39512568,0 2.89005487,0 L14.7135857,0 C15.4610503,0 16.2085149,0.271805307 16.6841742,0.815415921 L23.8190635,7.95030523 C24.3626741,8.49391584 24.6344794,9.17342911 24.6344794,9.9208937 L24.6344794,28.8793139 C24.6344794,30.3742431 23.4113555,31.6653183 21.848475,31.6653183 Z" id="path-1"></path>
|
||||||
</defs>
|
</defs>
|
||||||
@@ -14,14 +12,15 @@
|
|||||||
<mask id="mask-2" fill="white">
|
<mask id="mask-2" fill="white">
|
||||||
<use xlink:href="#path-1"></use>
|
<use xlink:href="#path-1"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<use id="Mask" fill="#45A1FF" opacity="0.6" xlink:href="#path-1"></use>
|
<use id="Mask" fill="#45a1ff" xlink:href="#path-1"></use>
|
||||||
<path d="M24.3031318,10.6474633 L16.7826187,10.6474633 C15.2742552,10.6474633 14.051485,9.42469306 14.051485,7.91632957 L14.051485,0.395816478" id="Path" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></path>
|
<path d="M24.3031318,10.6474633 L16.7826187,10.6474633 C15.2742552,10.6474633 14.051485,9.42469306 14.051485,7.91632957 L14.051485,0.395816478" id="Path" stroke="currentColor" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></path>
|
||||||
<path d="M7.2830232,8.93885547 L8.91906464,8.93885547" id="Path" stroke="#0060DF" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></path>
|
<path d="M7.2830232,8.93885547 L8.91906464,8.93885547" id="Path" stroke="currentColor" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></path>
|
||||||
<path d="M7.2830232,15.7798836 L17.5412669,15.7798836" id="Path" stroke="#0060DF" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></path>
|
<path d="M7.2830232,15.7798836 L17.5412669,15.7798836" id="Path" stroke="currentColor" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></path>
|
||||||
<path d="M7.2830232,22.6209117 L17.5412669,22.6209117" id="Path" stroke="#0060DF" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></path>
|
<path d="M7.2830232,22.6209117 L17.5412669,22.6209117" id="Path" stroke="currentColor" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></path>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16"><path fill="#4A4A4A" d="M9.414 8l5.293-5.293a1 1 0 0 0-1.414-1.414L8 6.586 2.707 1.293a1 1 0 0 0-1.414 1.414L6.586 8l-5.293 5.293a1 1 0 1 0 1.414 1.414L8 9.414l5.293 5.293a1 1 0 0 0 1.414-1.414z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16"><path fill="#737373" d="M9.414 8l5.293-5.293a1 1 0 0 0-1.414-1.414L8 6.586 2.707 1.293a1 1 0 0 0-1.414 1.414L6.586 8l-5.293 5.293a1 1 0 1 0 1.414 1.414L8 9.414l5.293 5.293a1 1 0 0 0 1.414-1.414z"/></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 287 B |
@@ -1 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#0060df" d="M14.707 8.293l-3-3A1 1 0 0 0 11 5h-1V4a1 1 0 0 0-.293-.707l-3-3A1 1 0 0 0 6 0H3a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3v3a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2V9a1 1 0 0 0-.293-.707zM12.586 9H11V7.414zm-5-5H6V2.414zM6 7v2H3V2h2v2.5a.5.5 0 0 0 .5.5H8a2 2 0 0 0-2 2zm2 7V7h2v2.5a.5.5 0 0 0 .5.5H13v4z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="icon" viewBox="0 0 16 16">
|
||||||
|
<path fill="currentColor" d="M14.707 8.293l-3-3A1 1 0 0 0 11 5h-1V4a1 1 0 0 0-.293-.707l-3-3A1 1 0 0 0 6 0H3a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3v3a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2V9a1 1 0 0 0-.293-.707zM12.586 9H11V7.414zm-5-5H6V2.414zM6 7v2H3V2h2v2.5a.5.5 0 0 0 .5.5H8a2 2 0 0 0-2 2zm2 7V7h2v2.5a.5.5 0 0 0 .5.5H13v4z"/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 398 B After Width: | Height: | Size: 411 B |
@@ -1,15 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="12px" height="16px" viewBox="0 0 12 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="12px" height="16px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<!-- Generator: sketchtool 53 (72520) - https://sketchapp.com -->
|
<symbol id="icon" viewBox="0 0 12 16">
|
||||||
<title>728D143F-562C-4AE5-80DA-9DBBD9D647D8</title>
|
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<desc>Created with sketchtool.</desc>
|
<g transform="translate(-770.000000, -481.000000)" fill="currentColor">
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g transform="translate(744.000000, 350.000000)">
|
||||||
<g id="Send_Sending" transform="translate(-770.000000, -481.000000)" fill="#0060DF">
|
<g transform="translate(26.000000, 131.000000)">
|
||||||
<g id="List-Item-3" transform="translate(744.000000, 350.000000)">
|
<path d="M5.293,12.707 C5.6835,13.097382 6.316501,13.097383 6.707001,12.707 L11.707,7.707 C12.08597,7.31462 12.08054,6.69092 11.69481,6.305189 C11.30908,5.91946 10.68538,5.91403 10.293,6.293 L7,9.586 L7,1 C7,0.44772 6.552285,0 6,0 C5.447715,0 5,0.44772 5,1 L5,9.586 L1.707,6.293 C1.314621,5.91403 0.690915,5.91945 0.305181,6.305182 C-0.080553,6.690915 -0.085973,7.314622 0.293,7.707001 L5.293,12.707 Z M11,14 L1,14 C0.447715,14 0,14.447715 0,15 C0,15.5522852 0.447715,16 1,16 L11,16 C11.55228,16 12,15.5522852 12,15 C12,14.447715 11.55229,14 11,14 Z"></path>
|
||||||
<g id="Download-Icon" transform="translate(26.000000, 131.000000)">
|
|
||||||
<path d="M5.293,12.707 C5.6835,13.097382 6.316501,13.097383 6.707001,12.707 L11.707,7.707 C12.08597,7.31462 12.08054,6.69092 11.69481,6.305189 C11.30908,5.91946 10.68538,5.91403 10.293,6.293 L7,9.586 L7,1 C7,0.44772 6.552285,0 6,0 C5.447715,0 5,0.44772 5,1 L5,9.586 L1.707,6.293 C1.314621,5.91403 0.690915,5.91945 0.305181,6.305182 C-0.080553,6.690915 -0.085973,7.314622 0.293,7.707001 L5.293,12.707 Z M11,14 L1,14 C0.447715,14 0,14.447715 0,15 C0,15.5522852 0.447715,16 1,16 L11,16 C11.55228,16 12,15.5522852 12,15 C12,14.447715 11.55229,14 11,14 Z" id="Fill-1"></path>
|
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
1
assets/eye-off.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#8795a1" d="M256.1 144.8c56.2 0 101.9 45.3 101.9 101.1 0 13.1-2.6 25.5-7.3 37l59.5 59c30.8-25.5 55-58.4 69.9-96-35.3-88.7-122.3-151.6-224.2-151.6-28.5 0-55.8 5.1-81.1 14.1l44 43.7c11.6-4.6 24.1-7.3 37.3-7.3zM52.4 89.7l46.5 46.1 9.4 9.3c-33.9 26-60.4 60.8-76.3 100.8 35.2 88.7 122.2 151.6 224.1 151.6 31.6 0 61.7-6.1 89.2-17l8.6 8.5 59.7 59 25.9-25.7L78.2 64 52.4 89.7zM165 201.4l31.6 31.3c-1 4.2-1.6 8.7-1.6 13.1 0 33.5 27.3 60.6 61.1 60.6 4.5 0 9-.6 13.2-1.6l31.6 31.3c-13.6 6.7-28.7 10.7-44.8 10.7-56.2 0-101.9-45.3-101.9-101.1 0-15.8 4.1-30.7 10.8-44.3zm87.8-15.7l64.2 63.7.4-3.2c0-33.5-27.3-60.6-61.1-60.6l-3.5.1z"/></svg>
|
||||||
|
After Width: | Height: | Size: 701 B |
1
assets/eye.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#8795a1" d="M256 105c-101.8 0-188.4 62.4-224 151 35.6 88.6 122.2 151 224 151s188.4-62.4 224-151c-35.6-88.6-122.2-151-224-151zm0 251.7c-56 0-101.8-45.3-101.8-100.7S200 155.3 256 155.3 357.8 200.6 357.8 256 312 356.7 256 356.7zm0-161.1c-33.6 0-61.1 27.2-61.1 60.4s27.5 60.4 61.1 60.4 61.1-27.2 61.1-60.4-27.5-60.4-61.1-60.4z"/></svg>
|
||||||
|
After Width: | Height: | Size: 406 B |
41
assets/icon.svg
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<svg viewBox="0 0 65 64" height="64" width="65" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="linear-gradient" x1="46.37" y1="59.71" x2="15.42" y2="6.11" gradientTransform="matrix(1, 0, 0, -1, 0, 64)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#ff980e"/>
|
||||||
|
<stop offset="0.21" stop-color="#ff7139"/>
|
||||||
|
<stop offset="0.36" stop-color="#ff5854"/>
|
||||||
|
<stop offset="0.46" stop-color="#ff4f5e"/>
|
||||||
|
<stop offset="0.69" stop-color="#ff3750"/>
|
||||||
|
<stop offset="0.86" stop-color="#f92261"/>
|
||||||
|
<stop offset="1" stop-color="#f5156c"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linear-gradient-2" x1="46.37" y1="59.71" x2="15.42" y2="6.11" gradientTransform="matrix(1, 0, 0, -1, 0, 64)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#fff44f" stop-opacity="0.8"/>
|
||||||
|
<stop offset="0.09" stop-color="#fff44f" stop-opacity="0.7"/>
|
||||||
|
<stop offset="0.75" stop-color="#fff44f" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linear-gradient-3" x1="40.19" y1="26.08" x2="54.32" y2="50.55" gradientTransform="matrix(1, 0, 0, -1, 0, 64)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#3a8ee6"/>
|
||||||
|
<stop offset="0.24" stop-color="#5c79f0"/>
|
||||||
|
<stop offset="0.63" stop-color="#9059ff"/>
|
||||||
|
<stop offset="1" stop-color="#c139e6"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linear-gradient-4" x1="54.32" y1="50.58" x2="48.39" y2="40.31" gradientTransform="matrix(1, 0, 0, -1, 0, 64)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#6e008b" stop-opacity="0.5"/>
|
||||||
|
<stop offset="0.5" stop-color="#c846cb" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linear-gradient-5" x1="56.96" y1="22.5" x2="41.98" y2="37.46" gradientTransform="matrix(1, 0, 0, -1, 0, 64)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.14" stop-color="#6a2bea" stop-opacity="0"/>
|
||||||
|
<stop offset="0.34" stop-color="#642de4" stop-opacity="0.03"/>
|
||||||
|
<stop offset="0.55" stop-color="#5131d3" stop-opacity="0.12"/>
|
||||||
|
<stop offset="0.76" stop-color="#3139b7" stop-opacity="0.27"/>
|
||||||
|
<stop offset="0.98" stop-color="#054490" stop-opacity="0.48"/>
|
||||||
|
<stop offset="1" stop-color="#00458b" stop-opacity="0.5"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path d="M32.56,0C14.61,0,0,14.33,0,31.94a3.42,3.42,0,0,0,3.42,3.42H29.14V52.3l-8-8a3.42,3.42,0,1,0-4.84,4.84L30.14,63a2.73,2.73,0,0,0,.25.23l.23.17h.05l.24.14.27.13.28.1.29.07L32,64h1.27l.29-.07.29-.1.27-.13.24-.14.24-.17L35,63,48.8,49.18A3.43,3.43,0,0,0,44,44.34l-8,8v-17H61.7a3.42,3.42,0,0,0,3.42-3.42h0C65.12,14.33,50.51,0,32.56,0ZM7.09,28.51A25.54,25.54,0,0,1,32.56,6.85,25.55,25.55,0,0,1,58,28.51Z" fill="url(#linear-gradient)"/>
|
||||||
|
<path d="M32.56,0C14.61,0,0,14.33,0,31.94a3.42,3.42,0,0,0,3.42,3.42H29.14V52.3l-8-8a3.42,3.42,0,1,0-4.84,4.84L30.14,63a2.73,2.73,0,0,0,.25.23l.23.17h.05l.24.14.27.13.28.1.29.07L32,64h1.27l.29-.07.29-.1.27-.13.24-.14.24-.17L35,63,48.8,49.18A3.43,3.43,0,0,0,44,44.34l-8,8v-17H61.7a3.42,3.42,0,0,0,3.42-3.42h0C65.12,14.33,50.51,0,32.56,0ZM7.09,28.51A25.54,25.54,0,0,1,32.56,6.85,25.55,25.55,0,0,1,58,28.51Z" fill="url(#linear-gradient-2)"/>
|
||||||
|
<path d="M36,7.08A25.45,25.45,0,0,1,58,28.51H42.85A6.85,6.85,0,0,0,36,35.36H61.7a3.43,3.43,0,0,0,3.42-3.43c0-12.2-8.63-18.13-16-21.59A35.75,35.75,0,0,0,36,7.08Z" fill="url(#linear-gradient-3)"/>
|
||||||
|
<path d="M57.89,27.76c2.31,1.86,4.63,3.71,6.9,5.61a3.27,3.27,0,0,0,.33-1.44c0-12.2-8.63-18.13-16-21.59A35.75,35.75,0,0,0,36,7.08,25.47,25.47,0,0,1,57.89,27.76Z" fill="url(#linear-gradient-4)"/>
|
||||||
|
<path d="M57.5,28.51H42.85A6.85,6.85,0,0,0,36,35.36H61.7a3.36,3.36,0,0,0,2.64-1.28C62.09,32.19,59.79,30.36,57.5,28.51Z" opacity=".9" fill="url(#linear-gradient-5)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -1,74 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="201px" height="36px" viewBox="0 0 201 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<!-- Generator: sketchtool 53 (72520) - https://sketchapp.com -->
|
|
||||||
<title>56F8E94A-92C7-41A4-BF32-064D0F1FE0CE</title>
|
|
||||||
<desc>Created with sketchtool.</desc>
|
|
||||||
<defs>
|
|
||||||
<linearGradient x1="71.1627137%" y1="8.3795633%" x2="23.6307692%" y2="89.0750207%" id="linearGradient-1">
|
|
||||||
<stop stop-color="#FF980E" offset="0%"></stop>
|
|
||||||
<stop stop-color="#FF7139" offset="20.75%"></stop>
|
|
||||||
<stop stop-color="#FF5854" offset="36.33%"></stop>
|
|
||||||
<stop stop-color="#FF4F5E" offset="45.63%"></stop>
|
|
||||||
<stop stop-color="#FF3750" offset="69.29%"></stop>
|
|
||||||
<stop stop-color="#F92261" offset="85.75%"></stop>
|
|
||||||
<stop stop-color="#F5156C" offset="100%"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient x1="71.1627137%" y1="8.3795633%" x2="23.6307692%" y2="89.0750207%" id="linearGradient-2">
|
|
||||||
<stop stop-color="#FFF44F" stop-opacity="0.8" offset="0%"></stop>
|
|
||||||
<stop stop-color="#FFF44F" stop-opacity="0" offset="75%"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient x1="14.3945107%" y1="105.534329%" x2="62.8615752%" y2="24.3294529%" id="linearGradient-3">
|
|
||||||
<stop stop-color="#3A8EE6" offset="0%"></stop>
|
|
||||||
<stop stop-color="#5C79F0" offset="23.59%"></stop>
|
|
||||||
<stop stop-color="#9059FF" offset="62.93%"></stop>
|
|
||||||
<stop stop-color="#C139E6" offset="100%"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient x1="62.8713604%" y1="29.0228725%" x2="42.5369928%" y2="60.7429512%" id="linearGradient-4">
|
|
||||||
<stop stop-color="#6E008B" stop-opacity="0.5" offset="0%"></stop>
|
|
||||||
<stop stop-color="#C846CB" stop-opacity="0" offset="50%"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient x1="73.7626536%" y1="58.1514822%" x2="20.9130221%" y2="45.4476366%" id="linearGradient-5">
|
|
||||||
<stop stop-color="#6A2BEA" stop-opacity="0" offset="13.6%"></stop>
|
|
||||||
<stop stop-color="#662CE6" stop-opacity="0.09459748" offset="29.95%"></stop>
|
|
||||||
<stop stop-color="#592FDB" stop-opacity="0.1926" offset="46.89%"></stop>
|
|
||||||
<stop stop-color="#4534C9" stop-opacity="0.2923" offset="64.1%"></stop>
|
|
||||||
<stop stop-color="#283BAF" stop-opacity="0.393" offset="81.5%"></stop>
|
|
||||||
<stop stop-color="#03448D" stop-opacity="0.4936" offset="98.9%"></stop>
|
|
||||||
<stop stop-color="#00458B" stop-opacity="0.5" offset="100%"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
||||||
<g id="Send_Home-With-Files" transform="translate(-24.000000, -31.000000)">
|
|
||||||
<g id="Header" transform="translate(24.000000, 24.000000)">
|
|
||||||
<g id="Send" transform="translate(0.000000, 7.000000)">
|
|
||||||
<g id="FF_Send_Logo">
|
|
||||||
<g id="Group">
|
|
||||||
<g>
|
|
||||||
<path d="M18.4801773,0.309677419 C8.4846097,0.309677419 0.34868259,8.28387097 0.34868259,18.0774194 C0.34868259,19.1225806 1.20101781,19.9741935 2.24706558,19.9741935 L16.5817943,19.9741935 L16.5817943,25.1612903 L16.5817943,25.1612903 L16.5817943,29.4193548 L12.1264056,25.0064516 C11.390298,24.2709677 10.1892801,24.2709677 9.41442994,25.0064516 C8.67832225,25.7419355 8.67832225,26.9419355 9.41442994,27.716129 L17.1241894,35.4193548 C17.162932,35.4580645 17.2016745,35.4967742 17.2791595,35.5354839 C17.2791595,35.5354839 17.2791595,35.5354839 17.317902,35.5354839 C17.3566445,35.5741935 17.395387,35.6129032 17.4341295,35.6129032 C17.4341295,35.6129032 17.4341295,35.6129032 17.472872,35.6129032 C17.5116145,35.6516129 17.5503571,35.6516129 17.5890996,35.6903226 C17.5890996,35.6903226 17.5890996,35.6903226 17.6278421,35.6903226 C17.6665846,35.7290323 17.7440696,35.7290323 17.7828121,35.7677419 C17.7828121,35.7677419 17.7828121,35.7677419 17.7828121,35.7677419 C17.8215546,35.8064516 17.8990396,35.8064516 17.9377822,35.8064516 C17.9377822,35.8064516 17.9377822,35.8064516 17.9377822,35.8064516 C17.9765247,35.8064516 18.0540097,35.8451613 18.0927522,35.8451613 C18.0927522,35.8451613 18.1314947,35.8451613 18.1314947,35.8451613 C18.1702372,35.8451613 18.2089797,35.8451613 18.2864647,35.883871 C18.3639498,35.883871 18.4026923,35.883871 18.4801773,35.883871 C18.5576623,35.883871 18.5964048,35.883871 18.6738898,35.883871 C18.7126324,35.883871 18.7513749,35.883871 18.8288599,35.8451613 C18.8288599,35.8451613 18.8676024,35.8451613 18.8676024,35.8451613 C18.9063449,35.8451613 18.9838299,35.8064516 19.0225724,35.8064516 C19.0225724,35.8064516 19.0225724,35.8064516 19.0225724,35.8064516 C19.0613149,35.8064516 19.1388,35.7677419 19.1775425,35.7290323 C19.1775425,35.7290323 19.1775425,35.7290323 19.1775425,35.7290323 C19.216285,35.6903226 19.29377,35.6903226 19.3325125,35.6516129 C19.3325125,35.6516129 19.3325125,35.6516129 19.3325125,35.6516129 C19.371255,35.6129032 19.4099975,35.6129032 19.44874,35.5741935 C19.44874,35.5741935 19.44874,35.5741935 19.4874826,35.5741935 C19.5262251,35.5354839 19.5649676,35.4967742 19.6037101,35.4967742 C19.6037101,35.4967742 19.6037101,35.4967742 19.6424526,35.4967742 C19.6811951,35.4580645 19.7199376,35.4193548 19.7974226,35.3806452 L27.5071821,27.6774194 C28.2432898,26.9419355 28.2432898,25.7419355 27.5071821,24.9677419 C26.7710744,24.2322581 25.5700566,24.2322581 24.7952064,24.9677419 L20.3785603,29.4193548 L20.3785603,20.0129032 L34.713289,20.0129032 C35.7593368,20.0129032 36.611672,19.1612903 36.611672,18.116129 C36.611672,8.28387097 28.4757449,0.309677419 18.4801773,0.309677419 Z M4.26167611,16.1806452 C5.23023886,9.40645161 11.2353279,4.14193548 18.4801773,4.14193548 C25.7250267,4.14193548 31.6913732,9.40645161 32.659936,16.1806452 L4.26167611,16.1806452 Z" id="Shape" fill="url(#linearGradient-1)" fill-rule="nonzero"></path>
|
|
||||||
<path d="M18.4801773,0.309677419 C8.4846097,0.309677419 0.34868259,8.28387097 0.34868259,18.0774194 C0.34868259,19.1225806 1.20101781,19.9741935 2.24706558,19.9741935 L16.5817943,19.9741935 L16.5817943,25.1612903 L16.5817943,25.1612903 L16.5817943,29.4193548 L12.1264056,25.0064516 C11.390298,24.2709677 10.1892801,24.2709677 9.41442994,25.0064516 C8.67832225,25.7419355 8.67832225,26.9419355 9.41442994,27.716129 L17.1241894,35.4193548 C17.162932,35.4580645 17.2016745,35.4967742 17.2791595,35.5354839 C17.2791595,35.5354839 17.2791595,35.5354839 17.317902,35.5354839 C17.3566445,35.5741935 17.395387,35.6129032 17.4341295,35.6129032 C17.4341295,35.6129032 17.4341295,35.6129032 17.472872,35.6129032 C17.5116145,35.6516129 17.5503571,35.6516129 17.5890996,35.6903226 C17.5890996,35.6903226 17.5890996,35.6903226 17.6278421,35.6903226 C17.6665846,35.7290323 17.7440696,35.7290323 17.7828121,35.7677419 C17.7828121,35.7677419 17.7828121,35.7677419 17.7828121,35.7677419 C17.8215546,35.8064516 17.8990396,35.8064516 17.9377822,35.8064516 C17.9377822,35.8064516 17.9377822,35.8064516 17.9377822,35.8064516 C17.9765247,35.8064516 18.0540097,35.8451613 18.0927522,35.8451613 C18.0927522,35.8451613 18.1314947,35.8451613 18.1314947,35.8451613 C18.1702372,35.8451613 18.2089797,35.8451613 18.2864647,35.883871 C18.3639498,35.883871 18.4026923,35.883871 18.4801773,35.883871 C18.5576623,35.883871 18.5964048,35.883871 18.6738898,35.883871 C18.7126324,35.883871 18.7513749,35.883871 18.8288599,35.8451613 C18.8288599,35.8451613 18.8676024,35.8451613 18.8676024,35.8451613 C18.9063449,35.8451613 18.9838299,35.8064516 19.0225724,35.8064516 C19.0225724,35.8064516 19.0225724,35.8064516 19.0225724,35.8064516 C19.0613149,35.8064516 19.1388,35.7677419 19.1775425,35.7290323 C19.1775425,35.7290323 19.1775425,35.7290323 19.1775425,35.7290323 C19.216285,35.6903226 19.29377,35.6903226 19.3325125,35.6516129 C19.3325125,35.6516129 19.3325125,35.6516129 19.3325125,35.6516129 C19.371255,35.6129032 19.4099975,35.6129032 19.44874,35.5741935 C19.44874,35.5741935 19.44874,35.5741935 19.4874826,35.5741935 C19.5262251,35.5354839 19.5649676,35.4967742 19.6037101,35.4967742 C19.6037101,35.4967742 19.6037101,35.4967742 19.6424526,35.4967742 C19.6811951,35.4580645 19.7199376,35.4193548 19.7974226,35.3806452 L27.5071821,27.6774194 C28.2432898,26.9419355 28.2432898,25.7419355 27.5071821,24.9677419 C26.7710744,24.2322581 25.5700566,24.2322581 24.7952064,24.9677419 L20.3785603,29.4193548 L20.3785603,20.0129032 L34.713289,20.0129032 C35.7593368,20.0129032 36.611672,19.1612903 36.611672,18.116129 C36.611672,8.28387097 28.4757449,0.309677419 18.4801773,0.309677419 Z M4.26167611,16.1806452 C5.23023886,9.40645161 11.2353279,4.14193548 18.4801773,4.14193548 C25.7250267,4.14193548 31.6913732,9.40645161 32.659936,16.1806452 L4.26167611,16.1806452 Z" id="Shape" fill="url(#linearGradient-2)" fill-rule="nonzero"></path>
|
|
||||||
<path d="M20.3785603,4.25806452 C26.7323319,5.10967742 31.7688582,9.98709677 32.659936,16.1806452 L24.2140688,16.1806452 C22.1219732,16.1806452 20.4173028,17.883871 20.4173028,19.9741935 L20.4173028,19.9741935 L34.713289,19.9741935 C35.7593368,19.9741935 36.611672,19.1225806 36.611672,18.0774194 C36.611672,11.3032258 31.8076008,7.97419355 27.7396372,6.07741935 C23.9816137,4.33548387 20.3785603,4.25806452 20.3785603,4.25806452 Z" id="Path" fill="url(#linearGradient-3)"></path>
|
|
||||||
<path d="M32.582451,15.7548387 C33.8609538,16.8 35.1781991,17.8064516 36.4179595,18.8903226 C36.534187,18.6580645 36.611672,18.3870968 36.611672,18.0774194 C36.611672,11.3032258 31.8076008,7.97419355 27.7396372,6.07741935 C23.9816137,4.33548387 20.3785603,4.25806452 20.3785603,4.25806452 C26.5773619,5.07096774 31.5364032,9.79354839 32.582451,15.7548387 Z" id="Path" fill="url(#linearGradient-4)"></path>
|
|
||||||
</g>
|
|
||||||
<path d="M32.3499959,16.1806452 L24.2140688,16.1806452 C22.1219732,16.1806452 20.4173028,17.883871 20.4173028,19.9741935 L34.713289,19.9741935 C35.2944267,19.9741935 35.8368218,19.7032258 36.1855044,19.2774194 C34.9070016,18.2322581 33.6284987,17.2258065 32.3499959,16.1806452 Z" id="Path" fill="url(#linearGradient-5)" opacity="0.9"></path>
|
|
||||||
</g>
|
|
||||||
<g id="Group" transform="translate(50.365263, 8.129032)" fill="#220033">
|
|
||||||
<polygon id="Path" points="0.30994008 1.74193548 11.6614955 1.74193548 11.6614955 4.99354839 3.71928097 4.99354839 3.71928097 9.09677419 11.6614955 9.09677419 11.6614955 12.2709677 3.71928097 12.2709677 3.71928097 19.3935484 0.27119757 19.3935484 0.27119757 1.74193548"></polygon>
|
|
||||||
<path d="M13.7535911,2.67096774 C13.7535911,2.0516129 13.9473036,1.50967742 14.3734712,1.12258065 C14.7608963,0.696774194 15.3032915,0.503225806 15.8844291,0.503225806 C16.5430518,0.503225806 17.0467044,0.696774194 17.4341295,1.12258065 C17.8215546,1.5483871 18.0152672,2.0516129 18.0152672,2.67096774 C18.0152672,3.29032258 17.8215546,3.79354839 17.4341295,4.18064516 C17.0467044,4.56774194 16.5043093,4.8 15.8844291,4.8 C15.264549,4.8 14.7608963,4.60645161 14.3347287,4.18064516 C13.9473036,3.79354839 13.7535911,3.2516129 13.7535911,2.67096774 Z M14.1797587,6.3483871 L17.5890996,6.3483871 L17.5890996,19.3935484 L14.1797587,19.3935484 L14.1797587,6.3483871 Z" id="Shape" fill-rule="nonzero"></path>
|
|
||||||
<path d="M20.2623328,6.3483871 L23.5554461,6.3483871 L23.5554461,8.36129032 C23.8653862,7.58709677 24.3302963,7.00645161 24.988919,6.65806452 C25.6087991,6.30967742 26.3449068,6.11612903 27.1584995,6.11612903 C27.5846672,6.11612903 28.0108348,6.15483871 28.3982599,6.27096774 C28.785685,6.3483871 29.0956251,6.50322581 29.3668226,6.61935484 L28.1658048,9.90967742 C27.9720923,9.79354839 27.7396372,9.67741935 27.4296971,9.6 C27.119757,9.52258065 26.7710744,9.48387097 26.4223919,9.48387097 C25.5700566,9.48387097 24.8726915,9.71612903 24.3690388,10.1806452 C23.8653862,10.6451613 23.5941886,11.4580645 23.5941886,12.5419355 L23.5941886,19.4322581 L20.2623328,19.4322581 L20.2623328,6.3483871 L20.2623328,6.3483871 Z" id="Path"></path>
|
|
||||||
<path d="M36.4954445,19.6645161 C35.4493967,19.6645161 34.4808339,19.5096774 33.6284987,19.1612903 C32.7761635,18.8129032 32.0400558,18.3483871 31.4589182,17.7290323 C30.839038,17.1096774 30.3741279,16.4129032 30.0641878,15.5612903 C29.7542477,14.7483871 29.5992777,13.8580645 29.5992777,12.8903226 C29.5992777,11.9225806 29.7542477,11.0322581 30.1029303,10.2193548 C30.4516129,9.40645161 30.916523,8.67096774 31.5364032,8.0516129 C32.1562833,7.43225806 32.892391,6.96774194 33.7059837,6.61935484 C34.558319,6.27096774 35.4493967,6.07741935 36.4179595,6.07741935 C37.3865222,6.07741935 38.3163424,6.23225806 39.1299352,6.58064516 C39.9435279,6.92903226 40.6796356,7.39354839 41.2607732,7.97419355 C41.8419109,8.55483871 42.306821,9.2516129 42.6555036,10.1032258 C43.0041862,10.916129 43.1591562,11.8064516 43.1591562,12.7741935 L43.1591562,14.0129032 L32.7761635,14.0129032 C33.0086186,14.8645161 33.4347862,15.5225806 34.0934088,16.0258065 C34.7520315,16.5290323 35.5656242,16.7612903 36.534187,16.7612903 C37.3090372,16.7612903 37.9676599,16.6064516 38.510055,16.2967742 C39.0524501,15.9870968 39.4786177,15.5612903 39.7885578,15.0193548 L42.539276,16.6064516 C41.8806534,17.5354839 41.0670607,18.2709677 40.1372404,18.8129032 C39.1299352,19.3548387 37.9289173,19.6645161 36.4954445,19.6645161 Z M36.4179595,8.90322581 C35.5656242,8.90322581 34.8295165,9.13548387 34.2096364,9.56129032 C33.5897562,10.0258065 33.1635886,10.6064516 32.892391,11.3806452 L39.8660428,11.3806452 C39.6335878,10.5677419 39.2461627,9.9483871 38.6262825,9.52258065 C38.0064024,9.09677419 37.2702947,8.90322581 36.4179595,8.90322581 Z" id="Shape" fill-rule="nonzero"></path>
|
|
||||||
<path d="M44.127719,6.3483871 L46.2972995,6.3483871 L46.2972995,5.88387097 C46.2972995,4.83870968 46.3747845,3.9483871 46.5684971,3.21290323 C46.7622096,2.51612903 47.0721497,1.93548387 47.4983173,1.50967742 C47.9244849,1.08387097 48.5056226,0.774193548 49.2029878,0.619354839 C49.9390955,0.425806452 50.8301732,0.348387097 51.876221,0.348387097 L52.7285562,0.348387097 L52.7285562,3.40645161 L52.186161,3.40645161 C51.6050234,3.40645161 51.1401133,3.44516129 50.7914307,3.48387097 C50.4427481,3.52258065 50.1715505,3.63870968 50.0165805,3.83225806 C49.8228679,3.98709677 49.7066404,4.21935484 49.6678979,4.52903226 C49.6291554,4.83870968 49.5904129,5.22580645 49.5904129,5.72903226 L49.5904129,6.3483871 L52.7285562,6.3483871 L52.7285562,9.32903226 L49.5904129,9.32903226 L49.5904129,19.3935484 L46.258557,19.3935484 L46.258557,9.32903226 L44.0889764,9.32903226 L44.0889764,6.3483871 L44.127719,6.3483871 Z" id="Path"></path>
|
|
||||||
<path d="M60.3995732,19.6645161 C59.4310104,19.6645161 58.5011902,19.4709677 57.648855,19.1225806 C56.7965197,18.7741935 56.0991546,18.2709677 55.4792744,17.6516129 C54.8593942,17.0322581 54.3557416,16.2967742 54.0458015,15.483871 C53.6971189,14.6709677 53.5421489,13.7806452 53.5421489,12.8516129 C53.5421489,11.9225806 53.6971189,11.0322581 54.0458015,10.2193548 C54.3944841,9.40645161 54.8593942,8.67096774 55.4792744,8.0516129 C56.0991546,7.43225806 56.8352623,6.96774194 57.648855,6.61935484 C58.5011902,6.27096774 59.3922679,6.07741935 60.3995732,6.07741935 C61.3681359,6.07741935 62.2592137,6.27096774 63.1115489,6.61935484 C63.9638841,6.96774194 64.6999918,7.47096774 65.319872,8.0516129 C65.9397521,8.67096774 66.4434047,9.36774194 66.7920873,10.2193548 C67.1407699,11.0322581 67.3344825,11.9225806 67.3344825,12.8516129 C67.3344825,13.7806452 67.1407699,14.6709677 66.7920873,15.483871 C66.4434047,16.2967742 65.9397521,17.0322581 65.3586145,17.6516129 C64.7387343,18.2709677 64.0026266,18.7741935 63.1890339,19.1225806 C62.2979562,19.4709677 61.4068784,19.6645161 60.3995732,19.6645161 Z M60.4383157,16.4903226 C60.9419683,16.4903226 61.4068784,16.4129032 61.833046,16.2193548 C62.2592137,16.0258065 62.6466388,15.7548387 62.9565788,15.4451613 C63.2665189,15.1354839 63.5377165,14.7483871 63.731429,14.283871 C63.9251416,13.8580645 64.0026266,13.3548387 64.0026266,12.8516129 C64.0026266,12.3483871 63.9251416,11.883871 63.731429,11.4193548 C63.5377165,10.9548387 63.3052614,10.5677419 62.9565788,10.2580645 C62.6466388,9.9483871 62.2592137,9.67741935 61.833046,9.48387097 C61.4068784,9.29032258 60.9419683,9.21290323 60.4383157,9.21290323 C59.9346631,9.21290323 59.4697529,9.29032258 59.0435853,9.48387097 C58.6174177,9.67741935 58.2299926,9.9483871 57.9200525,10.2580645 C57.6101125,10.5677419 57.3389149,10.9548387 57.1452023,11.4193548 C56.9514898,11.883871 56.8740048,12.3483871 56.8740048,12.8516129 C56.8740048,13.3548387 56.9514898,13.8580645 57.1452023,14.283871 C57.3389149,14.7096774 57.5713699,15.0967742 57.9200525,15.4451613 C58.2299926,15.7548387 58.6174177,16.0258065 59.0435853,16.2193548 C59.4697529,16.3741935 59.9346631,16.4903226 60.4383157,16.4903226 Z" id="Shape" fill-rule="nonzero"></path>
|
|
||||||
<polygon id="Path" points="72.3322663 12.6967742 67.7219076 6.3483871 71.6736436 6.3483871 74.4243618 10.2193548 77.17508 6.3483871 81.0880735 6.3483871 76.4389723 12.7354839 81.4367561 19.3935484 77.4075351 19.3935484 74.3856193 15.2903226 71.4411885 19.3935484 67.4119675 19.3935484"></polygon>
|
|
||||||
<g transform="translate(89.107773, 0.000000)">
|
|
||||||
<path d="M7.78724452,19.6258065 C6.00508906,19.6258065 4.49413117,19.2774194 3.25437084,18.6193548 C2.01461052,17.9612903 1.00730526,17.0709677 0.15497004,15.9483871 L1.89838299,14.5548387 C3.25437084,16.683871 5.19149635,17.7290323 7.7097595,17.7290323 C9.06574735,17.7290323 10.1117951,17.4193548 10.8479028,16.8 C11.545268,16.1806452 11.8939506,15.3677419 11.8939506,14.4 C11.8939506,13.8193548 11.7389805,13.3548387 11.467783,12.9677419 C11.1965854,12.5806452 10.8091603,12.3096774 10.3055077,12.0387097 C9.84059755,11.8064516 9.29820241,11.6129032 8.67832225,11.4580645 C8.05844209,11.3032258 7.39981942,11.1870968 6.74119675,11.0322581 C6.04383157,10.8774194 5.3852089,10.7225806 4.68784372,10.5290323 C4.02922105,10.3354839 3.40934088,10.0645161 2.90568825,9.75483871 C2.40203562,9.44516129 1.9371255,8.98064516 1.62718542,8.43870968 C1.27850283,7.89677419 1.12353279,7.2 1.12353279,6.3483871 C1.12353279,5.69032258 1.27850283,5.03225806 1.5497004,4.4516129 C1.82089797,3.87096774 2.24706558,3.32903226 2.75071821,2.90322581 C3.29311335,2.47741935 3.91299352,2.12903226 4.6103587,1.85806452 C5.34646639,1.58709677 6.1600591,1.47096774 7.05113683,1.47096774 C8.60083723,1.47096774 9.87934006,1.74193548 10.9253878,2.32258065 C11.9714356,2.90322581 12.9012558,3.63870968 13.676106,4.60645161 L12.0489206,5.88387097 C11.5065255,5.07096774 10.8479028,4.4516129 10.0730526,3.98709677 C9.29820241,3.52258065 8.29089715,3.29032258 7.08987934,3.29032258 C5.73389149,3.29032258 4.76532874,3.56129032 4.14544858,4.14193548 C3.52556842,4.72258065 3.21562833,5.41935484 3.21562833,6.30967742 C3.21562833,6.8516129 3.33185586,7.27741935 3.56431093,7.58709677 C3.79676599,7.93548387 4.14544858,8.20645161 4.57161619,8.4 C4.9977838,8.59354839 5.50143643,8.78709677 6.04383157,8.90322581 C6.62496922,9.01935484 7.20610687,9.17419355 7.86472954,9.32903226 C8.56209472,9.48387097 9.2594599,9.63870968 9.99556759,9.83225806 C10.7316753,10.0258065 11.390298,10.2967742 11.9714356,10.683871 C12.5525733,11.0322581 13.0562259,11.5354839 13.4049085,12.116129 C13.7923336,12.6967742 13.9860461,13.4322581 13.9860461,14.3612903 C13.9860461,15.0580645 13.8310761,15.716129 13.5598785,16.3354839 C13.2886809,16.9548387 12.8625133,17.5354839 12.3201182,18 C11.7777231,18.4645161 11.1578429,18.8516129 10.3829927,19.1612903 C9.60814249,19.4709677 8.75580727,19.6258065 7.78724452,19.6258065 Z" id="Path"></path>
|
|
||||||
<path d="M23.4779611,19.6645161 C22.4319133,19.6645161 21.5020931,19.5096774 20.6497579,19.1612903 C19.7974226,18.8129032 19.0613149,18.3483871 18.4801773,17.7290323 C17.8602971,17.1096774 17.395387,16.4129032 17.0854469,15.6 C16.7755069,14.7870968 16.6205368,13.8967742 16.6205368,12.9290323 C16.6205368,11.9612903 16.7755069,11.0709677 17.1241894,10.2193548 C17.472872,9.40645161 17.9377822,8.67096774 18.5576623,8.0516129 C19.1775425,7.43225806 19.8749077,6.96774194 20.7272429,6.61935484 C21.5408356,6.27096774 22.4706558,6.07741935 23.4392186,6.07741935 C24.4077813,6.07741935 25.2601166,6.23225806 26.0737093,6.58064516 C26.887302,6.92903226 27.5846672,7.35483871 28.1658048,7.97419355 C28.7469425,8.55483871 29.2118526,9.2516129 29.5605352,10.0645161 C29.9092178,10.8774194 30.0641878,11.7677419 30.0641878,12.7354839 L30.0641878,13.6258065 L18.5189198,13.6258065 C18.5964048,14.2451613 18.7513749,14.8258065 19.0225724,15.3290323 C19.29377,15.8322581 19.6424526,16.2967742 20.0686202,16.683871 C20.4947878,17.0709677 20.9984404,17.3419355 21.5795781,17.5741935 C22.1607158,17.7677419 22.7805959,17.883871 23.4779611,17.883871 C24.5240089,17.883871 25.4150866,17.6516129 26.1124518,17.2258065 C26.809817,16.8 27.3909546,16.1806452 27.8558647,15.3677419 L29.5605352,16.3741935 C28.9019125,17.4193548 28.0495773,18.2322581 27.0810145,18.8129032 C26.1124518,19.3935484 24.9501765,19.6645161 23.4779611,19.6645161 Z M23.4392186,7.81935484 C22.8193384,7.81935484 22.2382008,7.93548387 21.6958056,8.12903226 C21.1534105,8.32258065 20.6885004,8.59354839 20.2623328,8.98064516 C19.8361651,9.32903226 19.4874826,9.75483871 19.216285,10.2580645 C18.9450874,10.7612903 18.7513749,11.3032258 18.6351473,11.883871 L28.0883198,11.883871 C27.9333497,10.6451613 27.4296971,9.67741935 26.6161044,8.94193548 C25.7637692,8.20645161 24.6789789,7.81935484 23.4392186,7.81935484 Z" id="Shape" fill-rule="nonzero"></path>
|
|
||||||
<path d="M33.0473611,6.3483871 L34.9844866,6.3483871 L34.9844866,8.51612903 C35.4493967,7.78064516 36.0692769,7.2 36.8441271,6.73548387 C37.6189773,6.27096774 38.510055,6.07741935 39.5561028,6.07741935 C40.408438,6.07741935 41.1832882,6.19354839 41.8806534,6.46451613 C42.5780186,6.73548387 43.1591562,7.12258065 43.6240663,7.62580645 C44.0889764,8.12903226 44.4764015,8.7483871 44.7475991,9.48387097 C45.0187967,10.2193548 45.1350242,11.0322581 45.1350242,11.9612903 L45.1350242,19.3935484 L43.1204137,19.3935484 L43.1204137,12.0387097 C43.1204137,10.7612903 42.7717311,9.75483871 42.1131084,8.98064516 C41.4544858,8.20645161 40.4471805,7.85806452 39.1686777,7.85806452 C38.58754,7.85806452 38.0451449,7.97419355 37.5414922,8.16774194 C37.0378396,8.36129032 36.611672,8.67096774 36.2242469,9.05806452 C35.8368218,9.44516129 35.5656242,9.87096774 35.3719117,10.4129032 C35.1781991,10.9548387 35.0619716,11.4967742 35.0619716,12.1548387 L35.0619716,19.3935484 L33.0861036,19.3935484 L33.0861036,6.3483871 L33.0473611,6.3483871 Z" id="Path"></path>
|
|
||||||
<path d="M54.5881967,19.6645161 C53.6583764,19.6645161 52.8060412,19.5096774 51.9924485,19.1612903 C51.1788558,18.8129032 50.4814906,18.3483871 49.8616104,17.7677419 C49.2804728,17.1483871 48.7768202,16.4516129 48.4281376,15.6 C48.079455,14.7870968 47.9244849,13.8580645 47.9244849,12.8516129 C47.9244849,11.8451613 48.079455,10.9548387 48.4281376,10.1032258 C48.7768202,9.29032258 49.2417303,8.55483871 49.8616104,7.93548387 C50.4814906,7.31612903 51.1788558,6.8516129 51.9924485,6.54193548 C52.8060412,6.19354839 53.6583764,6.03870968 54.5881967,6.03870968 C55.0531068,6.03870968 55.5180169,6.07741935 56.0216695,6.19354839 C56.4865797,6.30967742 56.9514898,6.46451613 57.3776574,6.69677419 C57.803825,6.92903226 58.1912501,7.16129032 58.5786752,7.50967742 C58.9273578,7.81935484 59.2372979,8.16774194 59.5084954,8.59354839 L59.5084954,0.309677419 L61.4843635,0.309677419 L61.4843635,19.3548387 L59.5084954,19.3548387 L59.5084954,17.0322581 C59.2372979,17.4193548 58.9273578,17.8064516 58.5786752,18.116129 C58.2299926,18.4258065 57.803825,18.6967742 57.3776574,18.9290323 C56.9514898,19.1612903 56.4865797,19.316129 56.0216695,19.4322581 C55.5180169,19.5870968 55.0531068,19.6645161 54.5881967,19.6645161 Z M54.7431667,17.7677419 C55.4405319,17.7677419 56.0991546,17.6516129 56.6802922,17.3806452 C57.2614299,17.1096774 57.803825,16.7612903 58.2299926,16.3354839 C58.6561602,15.9096774 59.0048428,15.3677419 59.2372979,14.7483871 C59.4697529,14.1290323 59.5859805,13.5096774 59.5859805,12.8129032 C59.5859805,12.1548387 59.4697529,11.4967742 59.2372979,10.8774194 C59.0048428,10.2580645 58.6561602,9.75483871 58.2299926,9.29032258 C57.803825,8.86451613 57.2614299,8.47741935 56.6802922,8.24516129 C56.0991546,7.97419355 55.4405319,7.85806452 54.7431667,7.85806452 C54.084544,7.85806452 53.4259214,7.97419355 52.8447837,8.24516129 C52.2636461,8.51612903 51.7599934,8.86451613 51.2950833,9.29032258 C50.8689157,9.71612903 50.5202331,10.2580645 50.2877781,10.8774194 C50.055323,11.4967742 49.9390955,12.116129 49.9390955,12.8129032 C49.9390955,13.5096774 50.055323,14.1677419 50.2877781,14.7870968 C50.5202331,15.4064516 50.8689157,15.9096774 51.2950833,16.3741935 C51.7212509,16.8 52.2249036,17.1483871 52.8447837,17.4193548 C53.4646639,17.6516129 54.084544,17.7677419 54.7431667,17.7677419 Z" id="Shape" fill-rule="nonzero"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB |
99
assets/master-logo.svg
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="a" cx="-1186.91" cy="-517.676" r="90.78" gradientTransform="translate(1258.441 534.061)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#fff36e"/>
|
||||||
|
<stop offset=".5" stop-color="#fc4055"/>
|
||||||
|
<stop offset="1" stop-color="#e31587"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="b" cx="-1251.812" cy="-513.921" r="53.726" gradientTransform="translate(1258.441 534.061)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".001" stop-color="#c60084"/>
|
||||||
|
<stop offset="1" stop-color="#fc4055" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="c" cx="-1179.15" cy="-522.842" r="106.599" gradientTransform="translate(1258.441 534.061)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#ffde67" stop-opacity=".6"/>
|
||||||
|
<stop offset=".093" stop-color="#ffd966" stop-opacity=".581"/>
|
||||||
|
<stop offset=".203" stop-color="#ffca65" stop-opacity=".525"/>
|
||||||
|
<stop offset=".321" stop-color="#feb262" stop-opacity=".432"/>
|
||||||
|
<stop offset=".446" stop-color="#fe8f5e" stop-opacity=".302"/>
|
||||||
|
<stop offset=".573" stop-color="#fd6459" stop-opacity=".137"/>
|
||||||
|
<stop offset=".664" stop-color="#fc4055" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="d" cx="42.285" cy="44.499" r="137.521" gradientTransform="translate(0 -.095)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".153" stop-color="#810220"/>
|
||||||
|
<stop offset=".167" stop-color="#920b27" stop-opacity=".861"/>
|
||||||
|
<stop offset=".216" stop-color="#cb2740" stop-opacity=".398"/>
|
||||||
|
<stop offset=".253" stop-color="#ef394f" stop-opacity=".11"/>
|
||||||
|
<stop offset=".272" stop-color="#fc4055" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="e" cx="31.878" cy="42.77" r="137.521" gradientTransform="translate(0 -.095)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".113" stop-color="#810220"/>
|
||||||
|
<stop offset=".133" stop-color="#920b27" stop-opacity=".861"/>
|
||||||
|
<stop offset=".204" stop-color="#cb2740" stop-opacity=".398"/>
|
||||||
|
<stop offset=".257" stop-color="#ef394f" stop-opacity=".11"/>
|
||||||
|
<stop offset=".284" stop-color="#fc4055" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="f" x1="-1212.61" y1="-525.103" x2="-1189.052" y2="-484.299" gradientTransform="translate(1258.441 532.89)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#ffbd4f"/>
|
||||||
|
<stop offset=".508" stop-color="#ff9640" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="g" cx="-1255.933" cy="-77.395" r="88.863" gradientTransform="matrix(.959 0 0 .961 1273.896 86.468)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#ff9640"/>
|
||||||
|
<stop offset=".8" stop-color="#fc4055"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="h" cx="-1255.933" cy="-77.395" r="88.863" gradientTransform="matrix(.959 0 0 .961 1273.896 86.468)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".084" stop-color="#ffde67"/>
|
||||||
|
<stop offset=".147" stop-color="#ffdc66" stop-opacity=".968"/>
|
||||||
|
<stop offset=".246" stop-color="#ffd562" stop-opacity=".879"/>
|
||||||
|
<stop offset=".369" stop-color="#ffcb5d" stop-opacity=".734"/>
|
||||||
|
<stop offset=".511" stop-color="#ffbc55" stop-opacity=".533"/>
|
||||||
|
<stop offset=".667" stop-color="#ffaa4b" stop-opacity=".28"/>
|
||||||
|
<stop offset=".822" stop-color="#ff9640" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="i" cx="49.941" cy="38.654" r="41.79" gradientTransform="matrix(.247 .971 -1.011 .259 76.681 -19.851)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".363" stop-color="#fc4055"/>
|
||||||
|
<stop offset=".443" stop-color="#fd604d" stop-opacity=".633"/>
|
||||||
|
<stop offset=".545" stop-color="#fe8644" stop-opacity=".181"/>
|
||||||
|
<stop offset=".59" stop-color="#ff9640" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="j" cx="42.737" cy="42.193" r="41.79" gradientTransform="translate(0 -.095)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".216" stop-color="#fc4055" stop-opacity=".8"/>
|
||||||
|
<stop offset=".267" stop-color="#fd5251" stop-opacity=".633"/>
|
||||||
|
<stop offset=".41" stop-color="#fe8345" stop-opacity=".181"/>
|
||||||
|
<stop offset=".474" stop-color="#ff9640" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="k" cx="-1238.198" cy="-87.433" r="150.195" gradientTransform="matrix(.959 0 0 .961 1273.896 86.468)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".054" stop-color="#fff36e"/>
|
||||||
|
<stop offset=".457" stop-color="#ff9640"/>
|
||||||
|
<stop offset=".639" stop-color="#ff9640"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="l" x1="59.052" y1="7.083" x2="18.155" y2="77.92" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#fff36e" stop-opacity=".8"/>
|
||||||
|
<stop offset=".094" stop-color="#fff36e" stop-opacity=".699"/>
|
||||||
|
<stop offset=".752" stop-color="#fff36e" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="m" x1="40.585" y1="-.67" x2="62.3" y2="62.203" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#b833e1"/>
|
||||||
|
<stop offset=".371" stop-color="#9059ff"/>
|
||||||
|
<stop offset=".614" stop-color="#5b6df8"/>
|
||||||
|
<stop offset="1" stop-color="#0090ed"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="n" x1="-1230.731" y1="-532.566" x2="-1190.37" y2="-492.205" gradientTransform="translate(1258.441 532.89)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".805" stop-color="#722291" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#592acb" stop-opacity=".5"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path d="M71.944 15.7A39.47 39.47 0 0 0 41.588.009C32.3-.177 25.884 2.614 22.254 4.858 27.111 2.041 34.14.443 40.294.522c15.83.2 32.832 10.981 35.357 30.413 2.9 22.306-12.637 40.923-34.493 40.98-24.045.061-38.67-21.229-34.847-40.352a19.735 19.735 0 0 1 .413-2.787 37.815 37.815 0 0 1 4.193-14.018c-2.769 1.433-6.295 5.965-8.035 10.163A41.355 41.355 0 0 0 .284 45.1c.06.518.114 1.035.182 1.549A40.062 40.062 0 1 0 71.944 15.7zm-63.4 3.487z" fill="url(#a)"/>
|
||||||
|
<path d="M71.944 15.7A39.47 39.47 0 0 0 41.588.009C32.3-.177 25.884 2.614 22.254 4.858 27.111 2.041 34.14.443 40.294.522c15.83.2 32.832 10.981 35.357 30.413 2.9 22.306-12.637 40.923-34.493 40.98-24.045.061-38.67-21.229-34.847-40.352a19.735 19.735 0 0 1 .413-2.787 37.815 37.815 0 0 1 4.193-14.018c-2.769 1.433-6.295 5.965-8.035 10.163A41.355 41.355 0 0 0 .284 45.1c.06.518.114 1.035.182 1.549A40.062 40.062 0 1 0 71.944 15.7zm-63.4 3.487z" fill="url(#b)" opacity=".67"/>
|
||||||
|
<path d="M71.944 15.7A39.47 39.47 0 0 0 41.588.009C32.3-.177 25.884 2.614 22.254 4.858 27.111 2.041 34.14.443 40.294.522c15.83.2 32.832 10.981 35.357 30.413 2.9 22.306-12.637 40.923-34.493 40.98-24.045.061-38.67-21.229-34.847-40.352a19.735 19.735 0 0 1 .413-2.787 37.815 37.815 0 0 1 4.193-14.018c-2.769 1.433-6.295 5.965-8.035 10.163A41.355 41.355 0 0 0 .284 45.1c.06.518.114 1.035.182 1.549A40.062 40.062 0 1 0 71.944 15.7zm-63.4 3.487z" fill="url(#c)"/>
|
||||||
|
<path d="M71.944 15.7A39.47 39.47 0 0 0 41.588.009C32.3-.177 25.884 2.614 22.254 4.858 27.111 2.041 34.14.443 40.294.522c15.83.2 32.832 10.981 35.357 30.413 2.9 22.306-12.637 40.923-34.493 40.98-24.045.061-38.67-21.229-34.847-40.352a19.735 19.735 0 0 1 .413-2.787 37.815 37.815 0 0 1 4.193-14.018c-2.769 1.433-6.295 5.965-8.035 10.163A41.355 41.355 0 0 0 .284 45.1c.06.518.114 1.035.182 1.549A40.062 40.062 0 1 0 71.944 15.7zm-63.4 3.487z" fill="url(#d)"/>
|
||||||
|
<path d="M71.944 15.7A39.47 39.47 0 0 0 41.588.009C32.3-.177 25.884 2.614 22.254 4.858 27.111 2.041 34.14.443 40.294.522c15.83.2 32.832 10.981 35.357 30.413 2.9 22.306-12.637 40.923-34.493 40.98-24.045.061-38.67-21.229-34.847-40.352a19.735 19.735 0 0 1 .413-2.787 37.815 37.815 0 0 1 4.193-14.018c-2.769 1.433-6.295 5.965-8.035 10.163A41.355 41.355 0 0 0 .284 45.1c.06.518.114 1.035.182 1.549A40.062 40.062 0 1 0 71.944 15.7zm-63.4 3.487z" fill="url(#e)"/>
|
||||||
|
<path d="M75.651 30.935a41.01 41.01 0 0 1 .3 7.247q1.99-.3 3.987-.53A40.01 40.01 0 0 0 71.944 15.7 39.47 39.47 0 0 0 41.588.009C32.3-.177 25.884 2.614 22.254 4.858 27.111 2.041 34.14.443 40.294.522 56.124.724 73.126 11.5 75.651 30.935z" fill="url(#f)"/>
|
||||||
|
<path d="M76.625 29.826C74.374 9.518 56.263.39 40.294.522c-6.155.05-13.183 1.519-18.04 4.336a19.7 19.7 0 0 0-3.56 2.7c.129-.107.514-.424 1.152-.862l.063-.043.056-.038a26.655 26.655 0 0 1 7.692-3.572A43.5 43.5 0 0 1 40.84 1.5a33.254 33.254 0 0 1 31.25 31.993C72.457 46.7 61.648 57.23 49.188 57.84c-9.062.444-17.6-3.941-21.77-12.713a21.68 21.68 0 0 1-1.964-6.333c-1.976-13.35 6.989-24.735 15.21-27.554-4.435-3.874-15.548-3.611-23.819 2.474-5.956 4.382-9.82 11.049-11.1 19a32.945 32.945 0 0 0 2.34 18 35.3 35.3 0 0 0 30.089 21.443q1.489.114 2.984.113c26.462 0 37.942-20.087 35.467-42.444z" fill="url(#g)"/>
|
||||||
|
<path d="M76.625 29.826C74.374 9.518 56.263.39 40.294.522c-6.155.05-13.183 1.519-18.04 4.336a19.7 19.7 0 0 0-3.56 2.7c.129-.107.514-.424 1.152-.862l.063-.043.056-.038a26.655 26.655 0 0 1 7.692-3.572A43.5 43.5 0 0 1 40.84 1.5a33.254 33.254 0 0 1 31.25 31.993C72.457 46.7 61.648 57.23 49.188 57.84c-9.062.444-17.6-3.941-21.77-12.713a21.68 21.68 0 0 1-1.964-6.333c-1.976-13.35 6.989-24.735 15.21-27.554-4.435-3.874-15.548-3.611-23.819 2.474-5.956 4.382-9.82 11.049-11.1 19a32.945 32.945 0 0 0 2.34 18 35.3 35.3 0 0 0 30.089 21.443q1.489.114 2.984.113c26.462 0 37.942-20.087 35.467-42.444z" fill="url(#h)"/>
|
||||||
|
<path d="M76.625 29.826C74.374 9.518 56.263.39 40.294.522c-6.155.05-13.183 1.519-18.04 4.336a19.7 19.7 0 0 0-3.56 2.7c.129-.107.514-.424 1.152-.862l.063-.043.056-.038a26.655 26.655 0 0 1 7.692-3.572A43.5 43.5 0 0 1 40.84 1.5a33.254 33.254 0 0 1 31.25 31.993C72.457 46.7 61.648 57.23 49.188 57.84c-9.062.444-17.6-3.941-21.77-12.713a21.68 21.68 0 0 1-1.964-6.333c-1.976-13.35 6.989-24.735 15.21-27.554-4.435-3.874-15.548-3.611-23.819 2.474-5.956 4.382-9.82 11.049-11.1 19a32.945 32.945 0 0 0 2.34 18 35.3 35.3 0 0 0 30.089 21.443q1.489.114 2.984.113c26.462 0 37.942-20.087 35.467-42.444z" style="mix-blend-mode:multiply" opacity=".53" fill="url(#i)"/>
|
||||||
|
<path d="M76.625 29.826C74.374 9.518 56.263.39 40.294.522c-6.155.05-13.183 1.519-18.04 4.336a19.7 19.7 0 0 0-3.56 2.7c.129-.107.514-.424 1.152-.862l.063-.043.056-.038a26.655 26.655 0 0 1 7.692-3.572A43.5 43.5 0 0 1 40.84 1.5a33.254 33.254 0 0 1 31.25 31.993C72.457 46.7 61.648 57.23 49.188 57.84c-9.062.444-17.6-3.941-21.77-12.713a21.68 21.68 0 0 1-1.964-6.333c-1.976-13.35 6.989-24.735 15.21-27.554-4.435-3.874-15.548-3.611-23.819 2.474-5.956 4.382-9.82 11.049-11.1 19a32.945 32.945 0 0 0 2.34 18 35.3 35.3 0 0 0 30.089 21.443q1.489.114 2.984.113c26.462 0 37.942-20.087 35.467-42.444z" style="mix-blend-mode:multiply" opacity=".53" fill="url(#j)"/>
|
||||||
|
<path d="M49.188 57.84c17.1-1.04 24.42-15.2 24.879-25.245C74.783 16.9 65.472-.02 40.84 1.5a43.5 43.5 0 0 0-13.183 1.546 28.855 28.855 0 0 0-7.692 3.572l-.056.038-.063.043q-.574.4-1.123.842A33.482 33.482 0 0 1 39.7 3.605c14.142 1.856 27.072 12.857 27.072 27.373 0 11.169-8.631 19.7-18.738 19.087-15.015-.9-18.8-16.3-10.989-22.954-2.106-.453-6.064.435-8.82 4.555-2.473 3.7-2.333 9.41-.807 13.461a22.118 22.118 0 0 0 21.77 12.713z" fill="url(#k)"/>
|
||||||
|
<path d="M71.944 15.7a39.958 39.958 0 0 0-3.482-3.982 31.342 31.342 0 0 0-3.177-2.926 24.393 24.393 0 0 1 1.849 1.79 22.466 22.466 0 0 1 4.882 8.144c2.089 6.329 1.953 14.25-2.036 20.471a23.539 23.539 0 0 1-20.855 10.895c-.361 0-.725 0-1.091-.027-15.015-.9-18.8-16.3-10.988-22.954-2.107-.453-6.065.435-8.821 4.555-2.473 3.7-2.333 9.41-.807 13.461a21.679 21.679 0 0 1-1.963-6.333c-1.977-13.35 6.988-24.735 15.209-27.554-4.435-3.874-15.548-3.611-23.819 2.474a27.845 27.845 0 0 0-10.087 14.6 38.5 38.5 0 0 1 4.159-13.553c-2.769 1.433-6.295 5.965-8.035 10.163A41.355 41.355 0 0 0 .284 45.1c.06.518.114 1.035.182 1.549A40.062 40.062 0 1 0 71.944 15.7z" fill="url(#l)"/>
|
||||||
|
<path d="M72.016 18.726a22.458 22.458 0 0 0-4.882-8.144 30.224 30.224 0 0 0-9.094-6.493A40.518 40.518 0 0 0 49.1.92a39.834 39.834 0 0 0-16.565-.1c-5.683 1.2-10.68 3.659-13.841 6.733a32.1 32.1 0 0 1 8.031-3.2 33.565 33.565 0 0 1 31.173 8.1 27.01 27.01 0 0 1 4.329 5.3c4.895 7.959 4.432 17.965.615 23.866-2.835 4.384-8.907 8.5-14.572 8.452A23.629 23.629 0 0 0 69.98 39.2c3.989-6.224 4.125-14.145 2.036-20.474z" fill="url(#m)"/>
|
||||||
|
<path d="M72.016 18.726a22.458 22.458 0 0 0-4.882-8.144 30.224 30.224 0 0 0-9.094-6.493A40.518 40.518 0 0 0 49.1.92a39.834 39.834 0 0 0-16.565-.1c-5.683 1.2-10.68 3.659-13.841 6.733a32.1 32.1 0 0 1 8.031-3.2 33.565 33.565 0 0 1 31.173 8.1 27.01 27.01 0 0 1 4.329 5.3c4.895 7.959 4.432 17.965.615 23.866-2.835 4.384-8.907 8.5-14.572 8.452A23.629 23.629 0 0 0 69.98 39.2c3.989-6.224 4.125-14.145 2.036-20.474z" fill="url(#n)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 12 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 578.55 185.54"><path d="M503.5 117.21c0 4.92 2.37 8.82 9 8.82 7.8 0 16.11-5.6 16.61-18.31a80.86 80.86 0 0 0-11-1c-7.83-.01-14.61 2.19-14.61 10.49z"/><path d="M0 0v185.54h578.55V0zm163.78 139.93h-32V96.87c0-13.22-4.41-18.31-13.05-18.31-10.51 0-14.75 7.46-14.75 18.14v26.64h10.12v16.61h-32V96.87c0-13.22-4.4-18.31-13.05-18.31-10.51 0-14.75 7.46-14.75 18.14v26.64h14.54v16.61H22.22v-16.61h10.17V80.09h-11V63.48h32.87V75c4.58-8.13 12.55-13.05 23.22-13.05 11 0 21.19 5.26 24.92 16.45 4.24-10.17 12.88-16.45 24.92-16.45 13.73 0 26.28 8.31 26.28 26.45v34.94h10.17zm48.65 1.69c-23.56 0-39.84-14.41-39.84-38.82 0-22.38 13.56-40.86 41-40.86s40.86 18.48 40.86 39.84c.02 24.42-17.61 39.85-42.02 39.85zm121.72-1.69h-66.8l-2.2-11.53 42-48.32h-23.9l-3.39 11.87-15.77-1.69 2.71-26.79H334L335.69 75l-42.4 48.34H318l3.56-11.87 17.29 1.69zm41.36 0h-22.89v-27.46h22.89zm0-49h-22.89V63.48h22.89zm12 49L420.6 23.34h21.53l-33.06 116.59zm44.42 0L465 23.34h21.53l-33.04 116.59zm113.92 1.69c-10.17 0-15.76-5.94-16.78-15.26-4.41 7.8-12.21 15.26-24.58 15.26-11 0-23.56-5.94-23.56-21.87 0-18.82 18.14-23.22 35.6-23.22a100.23 100.23 0 0 1 12.55.68v-2.54c0-7.8-.17-17.12-12.55-17.12-4.58 0-8.14.34-11.7 2.2L502 90.6l-17.46-1.87 3.39-19.83c13.39-5.43 20.17-7 32.72-7 16.45 0 30.35 8.48 30.35 25.94v33.23c0 4.41 1.69 5.94 5.26 5.94a11.5 11.5 0 0 0 3.22-.51l.17 11.53a29.57 29.57 0 0 1-13.77 3.6z"/><path d="M213.27 78.73c-11.19 0-18.14 8.3-18.14 22.72 0 13.22 6.1 23.39 18 23.39 11.36 0 18.82-9.15 18.82-23.73-.03-15.43-8.33-22.38-18.68-22.38z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,8 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="8px" height="6px" viewBox="0 0 8 6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="8px" height="6px" viewBox="0 0 8 6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<!-- Generator: sketchtool 53 (72520) - https://sketchapp.com -->
|
|
||||||
<title>37845F0A-2932-4C2D-98E1-89BE1B168092</title>
|
|
||||||
<desc>Created with sketchtool.</desc>
|
|
||||||
<defs>
|
<defs>
|
||||||
<path d="M6,8.48771202 C5.73480519,8.48765538 5.48049273,8.3822614 5.293,8.19471202 L2.293,5.19471202 C1.91402779,4.80233313 1.91944763,4.17862724 2.30518142,3.79289345 C2.69091522,3.40715965 3.31462111,3.40173981 3.707,3.78071202 L6,6.07371202 L8.293,3.78071202 C8.68537889,3.40173981 9.30908478,3.40715965 9.69481858,3.79289345 C10.0805524,4.17862724 10.0859722,4.80233313 9.707,5.19471202 L6.707,8.19471202 C6.51950727,8.3822614 6.26519481,8.48765538 6,8.48771202 Z" id="path-1"></path>
|
<path d="M6,8.48771202 C5.73480519,8.48765538 5.48049273,8.3822614 5.293,8.19471202 L2.293,5.19471202 C1.91402779,4.80233313 1.91944763,4.17862724 2.30518142,3.79289345 C2.69091522,3.40715965 3.31462111,3.40173981 3.707,3.78071202 L6,6.07371202 L8.293,3.78071202 C8.68537889,3.40173981 9.30908478,3.40715965 9.69481858,3.79289345 C10.0805524,4.17862724 10.0859722,4.80233313 9.707,5.19471202 L6.707,8.19471202 C6.51950727,8.3822614 6.26519481,8.48765538 6,8.48771202 Z" id="path-1"></path>
|
||||||
</defs>
|
</defs>
|
||||||
@@ -14,7 +11,7 @@
|
|||||||
<mask id="mask-2" fill="white">
|
<mask id="mask-2" fill="white">
|
||||||
<use xlink:href="#path-1"></use>
|
<use xlink:href="#path-1"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<use id="Shape" fill="#0C0C0D" fill-rule="nonzero" opacity="0.599283854" xlink:href="#path-1"></use>
|
<use id="Shape" fill="#b1b1b3" fill-rule="nonzero" opacity="0.599283854" xlink:href="#path-1"></use>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,4 +1,5 @@
|
|||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M18 9a3 3 0 1 0-2.977-2.63l-6.94 3.47a3 3 0 1 0 0 4.319l6.94 3.47a3 3 0 1 0 .895-1.789l-6.94-3.47a3.03 3.03 0 0 0 0-.74l6.94-3.47C16.456 8.68 17.19 9 18 9z" fill="#0060df" fill-opacity=".8"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="icon" viewBox="0 0 24 24"><path fill-rule="evenodd" clip-rule="evenodd" d="M18 9a3 3 0 1 0-2.977-2.63l-6.94 3.47a3 3 0 1 0 0 4.319l6.94 3.47a3 3 0 1 0 .895-1.789l-6.94-3.47a3.03 3.03 0 0 0 0-.74l6.94-3.47C16.456 8.68 17.19 9 18 9z" fill="currentColor"></path></symbol></svg>
|
||||||
|
Before Width: | Height: | Size: 559 B After Width: | Height: | Size: 539 B |
6
assets/wordmark.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<symbol id="logo">
|
||||||
|
<path d="m 105.17,33.27 c -1.04895,-0.638175 -2.18377,-1.123082 -3.37,-1.44 -1.25,-0.34 -2.46,-0.63 -3.63,-0.88 l -3.08,-0.7 C 94.22073,30.069182 93.37751,29.78027 92.58,29.39 91.90449,29.074134 91.31719,28.596738 90.87,28 c -0.43741,-0.644047 -0.65489,-1.412243 -0.62,-2.19 -0.0406,-1.405196 0.53693,-2.75754 1.58,-3.7 1.06,-1 2.73,-1.44 5,-1.44 1.76437,-0.07198 3.51559,0.332147 5.07,1.17 1.35935,0.80694 2.51833,1.911219 3.39,3.23 l 2.79,-2.18 c -1.26761,-1.5933 -2.84201,-2.916072 -4.63,-3.89 -2.04373,-1.017745 -4.30804,-1.512526 -6.59,-1.44 -1.40785,-0.02195 -2.80876,0.201387 -4.14,0.66 -1.16063,0.399115 -2.24085,1.001871 -3.19,1.78 -0.8713,0.712445 -1.5718,1.611145 -2.05,2.63 -0.4819,1.011666 -0.72807,2.119452 -0.72,3.24 -0.05,1.231532 0.24064,2.452997 0.84,3.53 0.55827,0.895068 1.31002,1.653654 2.2,2.22 0.94422,0.612326 1.97599,1.077636 3.06,1.38 1.13,0.32 2.29,0.6 3.47,0.84 l 3.26,0.74 c 0.96945,0.22193 1.90929,0.557589 2.8,1 0.77256,0.367753 1.45522,0.900225 2,1.56 0.51019,0.701297 0.77072,1.553301 0.74,2.42 0.0438,1.566414 -0.62122,3.069031 -1.81,4.09 -1.52512,1.147855 -3.41702,1.699065 -5.32,1.55 -4.03416,0.15747 -7.83041,-1.90763 -9.89,-5.38 l -3,2.34 c 1.3876,1.880136 3.1735,3.430427 5.23,4.54 2.3855,1.197767 5.03194,1.782045 7.7,1.7 1.49114,0.02151 2.97422,-0.222285 4.38,-0.72 1.21788,-0.44929 2.33816,-1.128248 3.3,-2 0.88604,-0.797749 1.60053,-1.767412 2.1,-2.85 0.48895,-1.06318 0.74142,-2.219779 0.74,-3.39 0.0397,-1.336553 -0.30755,-2.656119 -1,-3.8 -0.62101,-0.95962 -1.44763,-1.769154 -2.42,-2.37 z m 27.51,-4.72 c -1.0207,-1.016684 -2.23916,-1.813109 -3.58,-2.34 -1.42831,-0.567565 -2.95311,-0.852828 -4.49,-0.84 -1.58532,-0.01887 -3.15769,0.287432 -4.62,0.9 -1.3691,0.572827 -2.61257,1.408599 -3.66,2.46 -2.1451,2.217513 -3.33989,5.184759 -3.33,8.27 -0.0138,1.54162 0.26439,3.071916 0.82,4.51 0.5255,1.363982 1.32922,2.603618 2.36,3.64 1.06096,1.043663 2.31862,1.866239 3.7,2.42 1.53222,0.610739 3.17082,0.909903 4.82,0.88 2.13421,0.08534 4.25095,-0.416179 6.12,-1.45 1.69947,-1.049265 3.13073,-2.480527 4.18,-4.18 l -2.88,-1.69 c -1.41279,2.768876 -4.32635,4.443291 -7.43,4.27 -1.09666,0.02103 -2.18793,-0.158593 -3.22,-0.53 -0.93382,-0.341463 -1.79784,-0.849713 -2.55,-1.5 -0.72694,-0.645531 -1.33013,-1.418157 -1.78,-2.28 -0.47812,-0.903522 -0.77374,-1.892313 -0.87,-2.91 h 19.59 v -1.52 c 0.0166,-1.555338 -0.27566,-3.098506 -0.86,-4.54 -0.54053,-1.333176 -1.33916,-2.54641 -2.35,-3.57 z m -16.28,6.67 c 0.18109,-0.958759 0.51895,-1.881119 1,-2.73 0.47186,-0.820757 1.07675,-1.557447 1.79,-2.18 0.72195,-0.61779 1.5482,-1.102022 2.44,-1.43 0.95944,-0.356614 1.97651,-0.532906 3,-0.52 4.04346,-0.224227 7.5255,2.82256 7.84,6.86 z M 158.82,28 c -0.83726,-0.883328 -1.8626,-1.566885 -3,-2 -1.25447,-0.462049 -2.58329,-0.689169 -3.92,-0.67 -1.60057,-0.03131 -3.18086,0.362037 -4.58,1.14 -1.28188,0.720594 -2.36173,1.752297 -3.14,3 v -3.65 h -3.29 V 48 h 3.37 V 35.67 c -0.0102,-1.001391 0.16968,-1.995625 0.53,-2.93 0.3373,-0.856524 0.84023,-1.638106 1.48,-2.3 0.62704,-0.649648 1.38331,-1.160636 2.22,-1.5 0.87089,-0.363534 1.8063,-0.547214 2.75,-0.54 1.87023,-0.128793 3.70135,0.578019 5,1.93 1.22147,1.441484 1.85048,3.292756 1.76,5.18 V 48 h 3.41 V 35.34 c 0.0211,-1.424123 -0.20214,-2.84132 -0.66,-4.19 -0.40985,-1.176324 -1.06809,-2.250653 -1.93,-3.15 z m 27,-12.42 v 14.1 c -0.43264,-0.685249 -0.96517,-1.302051 -1.58,-1.83 -0.60967,-0.53196 -1.28117,-0.98858 -2,-1.36 -0.73088,-0.369676 -1.5029,-0.651634 -2.3,-0.84 -0.78611,-0.187908 -1.59174,-0.281898 -2.4,-0.28 -1.50724,-0.0078 -3.00162,0.277523 -4.4,0.84 -1.34071,0.551089 -2.56038,1.35967 -3.59,2.38 -1.03697,1.047216 -1.85907,2.287165 -2.42,3.65 -1.17023,2.996466 -1.17023,6.323534 0,9.32 0.55964,1.361695 1.37424,2.603955 2.4,3.66 1.02081,1.031107 2.2428,1.841226 3.59,2.38 1.40561,0.561607 2.90636,0.846817 4.42,0.84 0.80981,-0.0026 1.6161,-0.106786 2.4,-0.31 0.79636,-0.199929 1.56783,-0.488392 2.3,-0.86 0.72123,-0.371416 1.39312,-0.831661 2,-1.37 0.61025,-0.540083 1.14205,-1.162767 1.58,-1.85 v 4 h 3.33 V 15.59 Z m -0.37,24.58 c -1.76276,4.229524 -6.6195,6.23041 -10.85,4.47 v 0 c -0.97862,-0.401365 -1.86378,-1.000551 -2.6,-1.76 -0.7522,-0.76312 -1.34086,-1.671634 -1.73,-2.67 -0.41974,-1.066531 -0.63023,-2.203893 -0.62,-3.35 -0.0103,-1.129892 0.20027,-2.250911 0.62,-3.3 0.39328,-0.993283 0.98151,-1.897738 1.73,-2.66 0.74207,-0.76001 1.62521,-1.368023 2.6,-1.79 2.07874,-0.890012 4.43126,-0.890012 6.51,0 0.98149,0.434716 1.87338,1.048526 2.63,1.81 0.74927,0.763509 1.33458,1.672102 1.72,2.67 0.41464,1.036611 0.62516,2.14355 0.62,3.26 -1.3e-4,1.141508 -0.22084,2.272237 -0.65,3.33 z" fill="currentColor"/>
|
||||||
|
</symbol>
|
||||||
|
<use xlink:href="#logo"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -2,4 +2,5 @@ last 2 chrome versions
|
|||||||
last 2 firefox versions
|
last 2 firefox versions
|
||||||
last 2 safari versions
|
last 2 safari versions
|
||||||
last 2 edge versions
|
last 2 edge versions
|
||||||
|
edge 18
|
||||||
firefox esr
|
firefox esr
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class AndroidIndexPlugin {
|
|||||||
const page = html`
|
const page = html`
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
<title>Firefox Send</title>
|
<title>Send</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Custom Loaders
|
# Custom Loaders
|
||||||
|
|
||||||
## Generate Asset Map
|
## Android Index Plugin
|
||||||
|
|
||||||
This loader enumerates all the files in `assets/` so that `common/assets.js` can provide mappings from the source filename to the hashed filename used on the site.
|
Generates the `index.html` page for the native android client
|
||||||
|
|
||||||
## Version Plugin
|
## Version Plugin
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
const commit = require('git-rev-sync').short();
|
const gitRevSync = require('git-rev-sync');
|
||||||
const pkg = require('../package.json');
|
const pkg = require('../package.json');
|
||||||
|
|
||||||
|
let commit = 'unknown';
|
||||||
|
|
||||||
|
try {
|
||||||
|
commit = gitRevSync.short();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Error fetching current git commit: ' + e);
|
||||||
|
}
|
||||||
|
|
||||||
const version = JSON.stringify({
|
const version = JSON.stringify({
|
||||||
commit,
|
commit,
|
||||||
source: pkg.homepage,
|
source: pkg.homepage,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const genmap = require('../build/generate_asset_map');
|
const genmap = require('./generate_asset_map');
|
||||||
const isServer = typeof genmap === 'function';
|
const isServer = typeof genmap === 'function';
|
||||||
let prefix = '';
|
let prefix = '';
|
||||||
let manifest = {};
|
let manifest = {};
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
# Common Code
|
# Common Code
|
||||||
|
|
||||||
This directory contains code loaded by both the frontend `app` and backend `server`. The code here can be challenging to understand at first because the contexts for the two (three counting the dev server) environments that include them are quite different, but the purpose of these modules are quite simple, to provide mappings from the source assets (`copy-16.png`) to the concrete production assets (`copy-16.db66e0bf.svg`).
|
This directory contains code loaded by both the frontend `app` and backend `server`. The code here can be challenging to understand at first because the contexts for the two (three counting the dev server) environments that include them are quite different, but the purpose of these modules are quite simple, to provide mappings from the source assets (`copy-16.png`) to the concrete production assets (`copy-16.db66e0bf.svg`).
|
||||||
|
|
||||||
|
## Generate Asset Map
|
||||||
|
|
||||||
|
This loader enumerates all the files in `assets/` so that `common/assets.js` can provide mappings from the source filename to the hashed filename used on the site.
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
Adapted from [this spreadsheet](https://airtable.com/shrkcBPOLkvNFOrpp)
|
Adapted from [this spreadsheet](https://airtable.com/shrkcBPOLkvNFOrpp)
|
||||||
|
|
||||||
- [ ] It should look and feel of an Android App
|
- [ ] It should look and feel of an Android App
|
||||||
- [ ] It should look and feel like the Firefox Send Web Client
|
- [ ] It should look and feel like the Send Web Client
|
||||||
|
|
||||||
### Main Screen
|
### Main Screen
|
||||||
- [ ] It should clearly Indicate the name of the product
|
- [ ] It should clearly Indicate the name of the product
|
||||||
@@ -21,7 +21,7 @@ Adapted from [this spreadsheet](https://airtable.com/shrkcBPOLkvNFOrpp)
|
|||||||
- [ ] It should allow users to select and send multiple files in one URL
|
- [ ] It should allow users to select and send multiple files in one URL
|
||||||
- [ ] It should limit the sendable file size to 1GB
|
- [ ] It should limit the sendable file size to 1GB
|
||||||
- [ ] It should allow users to set an expiration time of 5 minutes, 1 hour, or 24 hours
|
- [ ] It should allow users to set an expiration time of 5 minutes, 1 hour, or 24 hours
|
||||||
- [ ] It should allow users to set an download count of 1 downloads
|
- [ ] It should allow users to set a download count of 1 downloads
|
||||||
|
|
||||||
### Authenticated Users
|
### Authenticated Users
|
||||||
- [ ] It should indicate that the user is signed in via Firefox Account
|
- [ ] It should indicate that the user is signed in via Firefox Account
|
||||||
@@ -59,12 +59,12 @@ Adapted from [this spreadsheet](https://airtable.com/shrkcBPOLkvNFOrpp)
|
|||||||
- [ ] It should allow users to opt into notifications when their link is downloaded
|
- [ ] It should allow users to opt into notifications when their link is downloaded
|
||||||
|
|
||||||
## Annotations on Mobile Spec
|
## Annotations on Mobile Spec
|
||||||
This document tracks differences between the UX spec for Firefox Send and the intended MVP.
|
This document tracks differences between the UX spec for Send and the intended MVP.
|
||||||
|
|
||||||
[Spec Link](https://mozilla.invisionapp.com/share/GNN6KKOQ5XS)
|
[Spec Link](https://mozilla.invisionapp.com/share/GNN6KKOQ5XS)
|
||||||
|
|
||||||
* 1.1: Spec describes toolbar which may not be possible given the application framework we're using. In particular, issues with the spec include the color, logo and different font weights may be at issue.
|
* 1.1: Spec describes toolbar which may not be possible given the application framework we're using. In particular, issues with the spec include the color, logo and different font weights may be an issue.
|
||||||
* 1.2: Spec's treatment of FxA UI may be difficult to match. We should use the default OAuth implementation and re-evaluate UX once we see an implementation demo. Also, the landing page UI should display a log-in CTA directly and not require users click into the the hamburger menu.
|
* 1.2: Spec's treatment of FxA UI may be difficult to match. We should use the default OAuth implementation and re-evaluate UX once we see an implementation demo. Also, the landing page UI should display a log-in CTA directly and not require users click into the hamburger menu.
|
||||||
* 2.1: MVP will only include file picker. Signed in users will be able to select multiple files. File selection flow will be Android-native. Probably don't have the ability to add notifications as in the last screen on this page.
|
* 2.1: MVP will only include file picker. Signed in users will be able to select multiple files. File selection flow will be Android-native. Probably don't have the ability to add notifications as in the last screen on this page.
|
||||||
* 2.1: @fzzzy will provide screenshots of this flow for UX evaluation and comment.
|
* 2.1: @fzzzy will provide screenshots of this flow for UX evaluation and comment.
|
||||||
* 3.1.4: The spec shows deleting the last item in an unshared set returning the user to the picker menu. Instead, it should return to the app home page.
|
* 3.1.4: The spec shows deleting the last item in an unshared set returning the user to the picker menu. Instead, it should return to the app home page.
|
||||||
|
|||||||
68
docs/deployment.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
## Requirements
|
||||||
|
This document describes how to do a full deployment of Send on your own Linux server. You will need:
|
||||||
|
|
||||||
|
* A working (and ideally somewhat recent) installation of NodeJS and NPM
|
||||||
|
* GIT
|
||||||
|
* An Apache webserver
|
||||||
|
* Optionally telnet, to be able to quickly check your installation
|
||||||
|
|
||||||
|
For Debian/Ubuntu systems this probably just means something like this:
|
||||||
|
|
||||||
|
* apt install git apache2 nodejs npm telnet
|
||||||
|
|
||||||
|
## Building
|
||||||
|
* We assume an already configured virtual-host on your webserver with an existing empty htdocs folder
|
||||||
|
* First, remove that htdocs folder - we will replace it with Send's version now
|
||||||
|
* git clone https://github.com/mozilla/send.git htdocs
|
||||||
|
* Make now sure you are NOT root but rather the user your webserver is serving files under (e.g. "su www-data" or whoever the owner of your htdocs folder is)
|
||||||
|
* npm install
|
||||||
|
* npm run build
|
||||||
|
|
||||||
|
## Running
|
||||||
|
To have a permanently running version of Send as a background process:
|
||||||
|
|
||||||
|
* Create a file "run.sh" with:
|
||||||
|
```
|
||||||
|
#!/bin/bash
|
||||||
|
nohup su www-data -c "npm run prod" 2>/dev/null &
|
||||||
|
```
|
||||||
|
* chmod +x run.sh
|
||||||
|
* ./run.sh
|
||||||
|
|
||||||
|
Now the Send backend should be running on port 1443. You can check with:
|
||||||
|
* telnet localhost 1443
|
||||||
|
|
||||||
|
## Reverse Proxy
|
||||||
|
Of course, we don't want to expose the service on port 1443. Instead we want our normal webserver to forward all requests to Send ("Reverse proxy").
|
||||||
|
|
||||||
|
# Apache webserver
|
||||||
|
|
||||||
|
* a2enmod proxy
|
||||||
|
* a2enmod proxy_http
|
||||||
|
* a2enmod proxy_wstunnel
|
||||||
|
|
||||||
|
In your Apache virtual host configuration file, insert this:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Enable rewrite engine
|
||||||
|
RewriteEngine on
|
||||||
|
|
||||||
|
# Make sure the original domain name is forwarded to Send
|
||||||
|
# Otherwise the generated URLs will be wrong
|
||||||
|
ProxyPreserveHost on
|
||||||
|
|
||||||
|
# Make sure the generated URL is https://
|
||||||
|
RequestHeader set X-Forwarded-Proto https
|
||||||
|
|
||||||
|
# If it's a normal file (e.g. PNG, CSS) just return it
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -f
|
||||||
|
RewriteRule .* - [L]
|
||||||
|
|
||||||
|
# If it's a websocket connection, redirect it to a Send WS connection
|
||||||
|
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||||
|
RewriteRule /(.*) ws://127.0.0.1:1443/$1 [P,L]
|
||||||
|
|
||||||
|
# Otherwise redirect it to a normal HTTP connection
|
||||||
|
RewriteRule ^/(.*)$ http://127.0.0.1:1443/$1 [P,QSA]
|
||||||
|
ProxyPassReverse "/" "http://127.0.0.1:1443"
|
||||||
|
```
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
Before building the Docker image, you must build the production assets:
|
Use `registry.gitlab.com/timvisee/send:latest` from [`timvisee/send`'s registry](https://gitlab.com/timvisee/send/container_registry) for the latest Docker image.
|
||||||
|
|
||||||
```sh
|
```bash
|
||||||
npm run build
|
docker pull registry.gitlab.com/timvisee/send:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can run either `docker build` or `docker-compose up`.
|
Or run `docker build -t send:latest .` to create an image locally or `docker-compose up` to run a full testable stack. *We don't recommend using docker-compose for production.*
|
||||||
|
|
||||||
|
|
||||||
## Environment variables:
|
## Environment variables:
|
||||||
|
|
||||||
@@ -20,6 +19,7 @@ Then you can run either `docker build` or `docker-compose up`.
|
|||||||
| `SENTRY_DSN` | Sentry DSN
|
| `SENTRY_DSN` | Sentry DSN
|
||||||
| `MAX_FILE_SIZE` | in bytes (defaults to 2147483648)
|
| `MAX_FILE_SIZE` | in bytes (defaults to 2147483648)
|
||||||
| `NODE_ENV` | "production"
|
| `NODE_ENV` | "production"
|
||||||
|
| `BASE_URL` | The HTTPS URL where traffic will be served (e.g. `https://send.firefox.com`)
|
||||||
|
|
||||||
## Example:
|
## Example:
|
||||||
|
|
||||||
@@ -29,5 +29,6 @@ $ docker run --net=host -e 'NODE_ENV=production' \
|
|||||||
-e 'REDIS_HOST=dyf9s2r4vo3.bolxr4.0001.usw2.cache.amazonaws.com' \
|
-e 'REDIS_HOST=dyf9s2r4vo3.bolxr4.0001.usw2.cache.amazonaws.com' \
|
||||||
-e 'SENTRY_CLIENT=https://51e23d7263e348a7a3b90a5357c61cb2@sentry.prod.mozaws.net/168' \
|
-e 'SENTRY_CLIENT=https://51e23d7263e348a7a3b90a5357c61cb2@sentry.prod.mozaws.net/168' \
|
||||||
-e 'SENTRY_DSN=https://51e23d7263e348a7a3b90a5357c61cb2:65e23d7263e348a7a3b90a5357c61c44@sentry.prod.mozaws.net/168' \
|
-e 'SENTRY_DSN=https://51e23d7263e348a7a3b90a5357c61cb2:65e23d7263e348a7a3b90a5357c61c44@sentry.prod.mozaws.net/168' \
|
||||||
mozilla/send:latest
|
-e 'BASE_URL=https://send.firefox.com' \
|
||||||
|
registry.gitlab.com/timvisee/send:latest
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# File Encryption
|
# File Encryption
|
||||||
|
|
||||||
Send use 128-bit AES-GCM encryption via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) to encrypt files in the browser before uploading them to the server. The code is in [app/keychain.js](../app/keychain.js).
|
Send uses 128-bit AES-GCM encryption via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) to encrypt files in the browser before uploading them to the server. The code is in [app/keychain.js](../app/keychain.js).
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
### Uploading
|
### Uploading
|
||||||
|
|
||||||
1. A new secret key is generated with `crypto.getRandomValues`
|
1. A new secret key is generated with `crypto.getRandomValues`
|
||||||
2. The secret key is used to derive 3 more keys via HKDF SHA-256
|
2. The secret key is used to derive more keys via HKDF SHA-256
|
||||||
- an encryption key for the file (AES-GCM)
|
- a series of encryption keys for the file, via [ECE](https://tools.ietf.org/html/rfc8188) (AES-GCM)
|
||||||
- an encryption key for the file metadata (AES-GCM)
|
- an encryption key for the file metadata (AES-GCM)
|
||||||
- a signing key for request authentication (HMAC SHA-256)
|
- a signing key for request authentication (HMAC SHA-256)
|
||||||
3. The file and metadata are encrypted with their corresponding keys
|
3. The file and metadata are encrypted with their corresponding keys
|
||||||
@@ -21,7 +21,7 @@ Send use 128-bit AES-GCM encryption via the [Web Crypto API](https://developer.m
|
|||||||
1. The browser loads the share url page, which includes an authentication nonce
|
1. The browser loads the share url page, which includes an authentication nonce
|
||||||
2. The browser imports the secret key from the url fragment
|
2. The browser imports the secret key from the url fragment
|
||||||
3. The same 3 keys as above are derived
|
3. The same 3 keys as above are derived
|
||||||
4. The browser signs the nonce with it's signing key and requests the metadata
|
4. The browser signs the nonce with its signing key and requests the metadata
|
||||||
5. The encrypted metadata is decrypted and presented on the page
|
5. The encrypted metadata is decrypted and presented on the page
|
||||||
6. The browser makes another authenticated request to download the encrypted file
|
6. The browser makes another authenticated request to download the encrypted file
|
||||||
7. The browser downloads and decrypts the file
|
7. The browser downloads and decrypts the file
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
## How big of a file can I transfer with Firefox Send?
|
## How big of a file can I transfer with Send?
|
||||||
|
|
||||||
There is a 2GB file size limit built in to Send, however, in practice you may
|
There is a 2.5GB file size limit built in to Send(1GB for non-signed in users), however, in practice you may
|
||||||
be unable to send files that large. Send encrypts and decrypts the files in
|
be unable to send files that large. Send encrypts and decrypts the files in
|
||||||
the browser which is great for security but will tax your system resources. In
|
the browser which is great for security but will tax your system resources. In
|
||||||
particular you can expect to see your memory usage go up by at least the size
|
particular you can expect to see your memory usage go up by at least the size
|
||||||
@@ -17,9 +17,9 @@ Many browsers support this standard and should work fine, but some have not
|
|||||||
implemented it yet (mobile browsers lag behind on this, in
|
implemented it yet (mobile browsers lag behind on this, in
|
||||||
particular).
|
particular).
|
||||||
|
|
||||||
## Why does Firefox Send require JavaScript?
|
## Why does Send require JavaScript?
|
||||||
|
|
||||||
Firefox Send uses JavaScript to:
|
Send uses JavaScript to:
|
||||||
|
|
||||||
- Encrypt and decrypt files locally on the client instead of the server.
|
- Encrypt and decrypt files locally on the client instead of the server.
|
||||||
- Render the user interface.
|
- Render the user interface.
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ Server events allow us to aggregate data about file lifecycle without collecting
|
|||||||
* `event_properties`
|
* `event_properties`
|
||||||
* `download_count` downloads completed
|
* `download_count` downloads completed
|
||||||
* `ttl` time remaining before expiry truncated to hour
|
* `ttl` time remaining before expiry truncated to hour
|
||||||
|
* `agent` the browser name or first 6 characters of the user agent that made the request
|
||||||
|
|
||||||
### Client Events
|
### Client Events
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
<title>Firefox Send</title>
|
<title>Send</title>
|
||||||
<link href="index.css" rel="stylesheet">
|
<link href="index.css" rel="stylesheet">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
</head>
|
</head>
|
||||||
@@ -14,4 +14,4 @@
|
|||||||
<script src="ios.js"></script>
|
<script src="ios.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
17585
package-lock.json
generated
185
package.json
@@ -1,10 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "firefox-send",
|
"name": "send",
|
||||||
"description": "File Sharing Experiment",
|
"description": "File Sharing Experiment",
|
||||||
"version": "3.0.2",
|
"version": "3.3.0",
|
||||||
"author": "Mozilla (https://mozilla.org)",
|
"author": "Mozilla (https://mozilla.org)",
|
||||||
"repository": "mozilla/send",
|
"contributors": [
|
||||||
"homepage": "https://github.com/mozilla/send/",
|
"Tim Visee <3a4fb3964f@sinenomine.email> (https://timvisee.com)"
|
||||||
|
],
|
||||||
|
"repository": "gitlab:timvisee/send",
|
||||||
|
"homepage": "https://gitlab.com/timvisee/send/",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -26,8 +29,8 @@
|
|||||||
"test:frontend": "cross-env NODE_ENV=development node test/frontend/runner.js",
|
"test:frontend": "cross-env NODE_ENV=development node test/frontend/runner.js",
|
||||||
"test:report": "nyc report --reporter=html",
|
"test:report": "nyc report --reporter=html",
|
||||||
"test-integration": "cross-env NODE_ENV=development wdio test/wdio.docker.conf.js",
|
"test-integration": "cross-env NODE_ENV=development wdio test/wdio.docker.conf.js",
|
||||||
"circleci-test-integration": "cross-env NODE_ENV=development wdio test/wdio.circleci.conf.js",
|
"circleci-test-integration": "echo 'webdriverio tests need to be updated to node 12'",
|
||||||
"start": "npm run clean && cross-env NODE_ENV=development FXA_CLIENT_ID=fced6b5e3f4c66b9 BASE_URL=http://localhost:8080 webpack-dev-server --mode=development",
|
"start": "npm run clean && cross-env NODE_ENV=development L10N_DEV=true FXA_CLIENT_ID=fced6b5e3f4c66b9 BASE_URL=http://localhost:8080 webpack-dev-server --mode=development",
|
||||||
"android": "cross-env ANDROID=1 npm start",
|
"android": "cross-env ANDROID=1 npm start",
|
||||||
"prod": "node server/bin/prod.js"
|
"prod": "node server/bin/prod.js"
|
||||||
},
|
},
|
||||||
@@ -35,7 +38,8 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "lint-staged",
|
"pre-commit": "lint-staged",
|
||||||
"pre-push": "npm test",
|
"pre-push": "npm test",
|
||||||
"post-merge": "npm install"
|
"post-merge": "npm install",
|
||||||
|
"post-checkout": "scripts/sync-npm-dependencies.sh"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
@@ -57,138 +61,169 @@
|
|||||||
"cache": true
|
"cache": true
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": "^12.16.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.3.4",
|
"@babel/core": "^7.12.3",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.3.4",
|
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/preset-env": "^7.3.4",
|
"@babel/preset-env": "^7.12.1",
|
||||||
"@dannycoates/webcrypto-liner": "^0.1.34",
|
"@dannycoates/webcrypto-liner": "^0.1.37",
|
||||||
"@dannycoates/webpack-dev-server": "^3.1.4",
|
"@fullhuman/postcss-purgecss": "^1.3.0",
|
||||||
"@fullhuman/postcss-purgecss": "^1.1.0",
|
|
||||||
"@mattiasbuelens/web-streams-polyfill": "0.2.1",
|
"@mattiasbuelens/web-streams-polyfill": "0.2.1",
|
||||||
|
"@sentry/browser": "^5.26.0",
|
||||||
"asmcrypto.js": "^0.22.0",
|
"asmcrypto.js": "^0.22.0",
|
||||||
"babel-loader": "^8.0.5",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-plugin-istanbul": "^5.1.1",
|
"babel-plugin-istanbul": "^5.2.0",
|
||||||
"base64-js": "^1.3.0",
|
"base64-js": "^1.3.1",
|
||||||
"content-disposition": "^0.5.3",
|
"content-disposition": "^0.5.3",
|
||||||
"copy-webpack-plugin": "^5.0.0",
|
"copy-webpack-plugin": "^5.1.2",
|
||||||
"core-js": "^2.6.5",
|
"core-js": "^3.4.0",
|
||||||
"crc": "^3.8.0",
|
"crc": "^3.8.0",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^6.0.3",
|
||||||
"css-loader": "^2.1.1",
|
"css-loader": "^3.6.0",
|
||||||
"css-mqpacker": "^7.0.0",
|
"css-mqpacker": "^7.0.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"eslint": "^5.15.1",
|
"eslint": "^6.6.0",
|
||||||
"eslint-plugin-mocha": "^5.3.0",
|
"eslint-config-prettier": "^6.13.0",
|
||||||
"eslint-plugin-node": "^8.0.1",
|
"eslint-plugin-mocha": "^6.2.1",
|
||||||
|
"eslint-plugin-node": "^10.0.0",
|
||||||
"eslint-plugin-security": "^1.4.0",
|
"eslint-plugin-security": "^1.4.0",
|
||||||
"expose-loader": "^0.7.5",
|
"expose-loader": "^0.7.5",
|
||||||
"extract-loader": "^3.1.0",
|
"extract-loader": "^3.2.0",
|
||||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
"fast-text-encoding": "^1.0.0",
|
"fast-text-encoding": "^1.0.3",
|
||||||
"file-loader": "^3.0.1",
|
"file-loader": "^4.2.0",
|
||||||
"fluent-intl-polyfill": "^0.1.0",
|
|
||||||
"git-rev-sync": "^1.12.0",
|
"git-rev-sync": "^1.12.0",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"http_ece": "^1.1.0",
|
"http_ece": "^1.1.0",
|
||||||
"husky": "^1.3.1",
|
"husky": "^3.0.9",
|
||||||
"lint-staged": "^8.1.5",
|
"intl-pluralrules": "^1.2.2",
|
||||||
"mocha": "^6.0.2",
|
"lint-staged": "^9.4.2",
|
||||||
|
"mocha": "^6.2.2",
|
||||||
"morgan": "^1.9.1",
|
"morgan": "^1.9.1",
|
||||||
"nanobus": "^4.4.0",
|
"nanobus": "^4.4.0",
|
||||||
"nanohtml": "^1.4.0",
|
"nanohtml": "^1.9.0",
|
||||||
"nanotiming": "^7.3.1",
|
"nanotiming": "^7.3.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"nyc": "^13.3.0",
|
"nyc": "^14.1.1",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-preset-env": "^6.6.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.19.1",
|
||||||
"proxyquire": "^2.1.0",
|
"proxyquire": "^2.1.3",
|
||||||
"puppeteer": "1.11.0",
|
"puppeteer": "^2.0.0",
|
||||||
"raven-js": "^3.27.0",
|
"raw-loader": "^3.1.0",
|
||||||
"raw-loader": "^1.0.0",
|
"redis-mock": "^0.47.0",
|
||||||
"redis-mock": "^0.43.0",
|
"rimraf": "^3.0.0",
|
||||||
"rimraf": "^2.6.3",
|
|
||||||
"script-loader": "^0.7.2",
|
"script-loader": "^0.7.2",
|
||||||
"sinon": "^7.2.7",
|
"sinon": "^7.5.0",
|
||||||
"string-hash": "^1.1.3",
|
"string-hash": "^1.1.3",
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^11.1.1",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^19.0.0",
|
||||||
"stylelint-no-unsupported-browser-features": "^3.0.2",
|
"stylelint-no-unsupported-browser-features": "^3.0.2",
|
||||||
"svgo": "^1.2.0",
|
"svgo": "^1.3.2",
|
||||||
"svgo-loader": "^2.2.0",
|
"svgo-loader": "^2.2.1",
|
||||||
"tailwindcss": "^0.7.4",
|
"tailwindcss": "^1.9.4",
|
||||||
"val-loader": "^1.1.1",
|
"val-loader": "^1.1.1",
|
||||||
"wdio-docker-service": "^1.4.2",
|
"webpack": "4.38.0",
|
||||||
"wdio-dot-reporter": "0.0.10",
|
"webpack-cli": "^3.3.12",
|
||||||
"wdio-firefox-profile-service": "^0.1.3",
|
"webpack-dev-middleware": "^3.7.2",
|
||||||
"wdio-mocha-framework": "^0.6.3",
|
"webpack-dev-server": "^3.11.0",
|
||||||
"wdio-sauce-service": "^0.4.14",
|
"webpack-manifest-plugin": "^2.2.0",
|
||||||
"wdio-spec-reporter": "^0.1.5",
|
|
||||||
"webdriverio": "^4.14.3",
|
|
||||||
"webpack": "4.28.4",
|
|
||||||
"webpack-cli": "^3.2.3",
|
|
||||||
"webpack-dev-middleware": "^3.6.1",
|
|
||||||
"webpack-manifest-plugin": "^2.0.4",
|
|
||||||
"webpack-unassert-loader": "^1.2.0"
|
"webpack-unassert-loader": "^1.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/storage": "^2.4.2",
|
"@dannycoates/express-ws": "^5.0.3",
|
||||||
"aws-sdk": "^2.417.0",
|
"@fluent/bundle": "^0.13.0",
|
||||||
"body-parser": "^1.18.3",
|
"@fluent/langneg": "^0.3.0",
|
||||||
"choo": "^6.12.1",
|
"@google-cloud/storage": "^5.1.2",
|
||||||
"cldr-core": "^34.0.0",
|
"@sentry/node": "^5.26.0",
|
||||||
"convict": "^4.4.1",
|
"aws-sdk": "^2.773.0",
|
||||||
"express": "^4.16.3",
|
"body-parser": "^1.19.0",
|
||||||
"express-ws": "github:dannycoates/express-ws",
|
"choo": "^7.0.0",
|
||||||
"fluent": "^0.11.0",
|
"cldr-core": "^35.1.0",
|
||||||
"fluent-langneg": "^0.1.1",
|
"configstore": "github:dannycoates/configstore#master",
|
||||||
"fxa-geodb": "^1.0.4",
|
"convict": "^5.2.0",
|
||||||
"helmet": "^3.15.1",
|
"express": "^4.17.1",
|
||||||
|
"helmet": "^3.23.3",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mozlog": "^2.2.0",
|
"mozlog": "^2.2.0",
|
||||||
"node-fetch": "^2.3.0",
|
"node-fetch": "^2.6.1",
|
||||||
"raven": "^2.6.4",
|
|
||||||
"redis": "^2.8.0",
|
"redis": "^2.8.0",
|
||||||
"ua-parser-js": "^0.7.19",
|
"selenium-standalone": "^6.20.1",
|
||||||
"websocket-stream": "^5.1.2"
|
"ua-parser-js": "^0.7.22"
|
||||||
},
|
},
|
||||||
"availableLanguages": [
|
"availableLanguages": [
|
||||||
"en-US",
|
"en-US",
|
||||||
"bn-BD",
|
"an",
|
||||||
|
"ar",
|
||||||
|
"ast",
|
||||||
|
"azz",
|
||||||
|
"be",
|
||||||
|
"bn",
|
||||||
|
"br",
|
||||||
|
"ca",
|
||||||
"cak",
|
"cak",
|
||||||
"cs",
|
"cs",
|
||||||
"cy",
|
"cy",
|
||||||
"da",
|
"da",
|
||||||
"de",
|
"de",
|
||||||
"dsb",
|
"dsb",
|
||||||
|
"el",
|
||||||
"en-CA",
|
"en-CA",
|
||||||
"en-GB",
|
"en-GB",
|
||||||
"es-AR",
|
"es-AR",
|
||||||
|
"es-CL",
|
||||||
"es-ES",
|
"es-ES",
|
||||||
"es-MX",
|
"es-MX",
|
||||||
|
"et",
|
||||||
|
"eu",
|
||||||
|
"fa",
|
||||||
|
"fi",
|
||||||
"fr",
|
"fr",
|
||||||
"fy-NL",
|
"fy-NL",
|
||||||
|
"gn",
|
||||||
|
"he",
|
||||||
"hr",
|
"hr",
|
||||||
"hsb",
|
"hsb",
|
||||||
"hu",
|
"hu",
|
||||||
|
"hus",
|
||||||
|
"hy-AM",
|
||||||
|
"ia",
|
||||||
"id",
|
"id",
|
||||||
"it",
|
"it",
|
||||||
"ja",
|
"ja",
|
||||||
"ka",
|
"ka",
|
||||||
|
"kab",
|
||||||
|
"ko",
|
||||||
"lt",
|
"lt",
|
||||||
|
"meh",
|
||||||
|
"mix",
|
||||||
|
"ml",
|
||||||
|
"nb-NO",
|
||||||
"nl",
|
"nl",
|
||||||
|
"nn-NO",
|
||||||
|
"oc",
|
||||||
|
"pa-IN",
|
||||||
|
"pl",
|
||||||
|
"ppl",
|
||||||
"pt-BR",
|
"pt-BR",
|
||||||
"pt-PT",
|
"pt-PT",
|
||||||
|
"quc",
|
||||||
"ro",
|
"ro",
|
||||||
"ru",
|
"ru",
|
||||||
|
"sk",
|
||||||
"sl",
|
"sl",
|
||||||
|
"sq",
|
||||||
|
"sr",
|
||||||
|
"su",
|
||||||
"sv-SE",
|
"sv-SE",
|
||||||
|
"te",
|
||||||
|
"th",
|
||||||
"tr",
|
"tr",
|
||||||
"uk",
|
"uk",
|
||||||
"vi",
|
"vi",
|
||||||
|
"zgh",
|
||||||
"zh-CN",
|
"zh-CN",
|
||||||
"zh-TW"
|
"zh-TW"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class TailwindExtractor {
|
|||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
plugins: [
|
plugins: [
|
||||||
require('tailwindcss')('./tailwind.js'),
|
require('tailwindcss')('./tailwind.config.js'),
|
||||||
require('postcss-preset-env')
|
require('postcss-preset-env')
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
152
public/locales/an/send.ftl
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
title = Send
|
||||||
|
importingFile = Se ye importando…
|
||||||
|
encryptingFile = Se ye cifrando…
|
||||||
|
decryptingFile = Se ye descifrando…
|
||||||
|
downloadCount =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 descarga
|
||||||
|
*[other] { $num } descargas
|
||||||
|
}
|
||||||
|
timespanHours =
|
||||||
|
{ $num ->
|
||||||
|
[one] hora
|
||||||
|
*[other] { $num } horas
|
||||||
|
}
|
||||||
|
copiedUrl = Copiau!
|
||||||
|
unlockInputPlaceholder = Clau
|
||||||
|
unlockButtonLabel = Desblocar
|
||||||
|
downloadButtonLabel = Descargar
|
||||||
|
downloadFinish = Descarga completa
|
||||||
|
fileSizeProgress = ({ $partialSize } de { $totalSize })
|
||||||
|
sendYourFilesLink = Preba Send
|
||||||
|
errorPageHeader = I ha habiu bell problema!
|
||||||
|
fileTooBig = Ixe fichero ye masiau gran pa cargar-lo. Ha de tener menos de { $size }
|
||||||
|
linkExpiredAlt = Lo vinclo ye caducau
|
||||||
|
notSupportedHeader = Lo suyo navegador no ye compatible
|
||||||
|
notSupportedLink = Per qué no ye compatible lo mío navegador?
|
||||||
|
notSupportedOutdatedDetail = Esta versión de Firefox no admite la tecnolochía web con que funciona lo Send. Habrás d'esviellar lo navegador.
|
||||||
|
updateFirefox = Esviellar Firefox
|
||||||
|
deletePopupCancel = Cancelar
|
||||||
|
deleteButtonHover = Borrar
|
||||||
|
footerLinkLegal = Aviso legal
|
||||||
|
footerLinkPrivacy = Privacidat
|
||||||
|
footerLinkCookies = Cookies
|
||||||
|
passwordTryAgain = La contrasenya ye incorrecta. Torne-lo a intentar.
|
||||||
|
javascriptRequired = Send necesita JavaScript
|
||||||
|
whyJavascript = Per qué Send necesita JavaScript?
|
||||||
|
enableJavascript = Activa JavaScript y torna-lo a intentar.
|
||||||
|
# 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
|
||||||
|
# A short status message shown when the user enters a long password
|
||||||
|
maxPasswordLength = Maxima lonchitut d'a clau: { $length }
|
||||||
|
# A short status message shown when there was an error setting the password
|
||||||
|
passwordSetError = No s'ha puesto definir la clau
|
||||||
|
|
||||||
|
## Send version 2 strings
|
||||||
|
|
||||||
|
-send-brand = Send
|
||||||
|
-send-short-brand = Send
|
||||||
|
-firefox = Firefox
|
||||||
|
-mozilla = Mozilla
|
||||||
|
introTitle = Compartición de fichers simpla y privada
|
||||||
|
introDescription = { -send-brand } te permite de compartir fichers cifraus de cabo a cabo, y tamién un vinclo que expira automaticament. Asinas, puetz mantener en privau lo que compartes y asegurar-te de que los tuyos contenius no se quedan pa cutio en linia.
|
||||||
|
notifyUploadEncryptDone = Lo fichero s'ha cifrau y ye presto pa ninviar-se
|
||||||
|
# downloadCount is from the downloadCount string and timespan is a timespanMinutes string. ex. 'Expires after 2 downloads or 25 minutes'
|
||||||
|
archiveExpiryInfo = Caduca dimpués de { $downloadCount } u { $timespan }
|
||||||
|
timespanMinutes =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 minuto
|
||||||
|
*[other] { $num } minutos
|
||||||
|
}
|
||||||
|
timespanDays =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 día
|
||||||
|
*[other] { $num } días
|
||||||
|
}
|
||||||
|
timespanWeeks =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 semana
|
||||||
|
*[other] { $num } semanas
|
||||||
|
}
|
||||||
|
fileCount =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 fichero
|
||||||
|
*[other] { $num } fichers
|
||||||
|
}
|
||||||
|
# byte abbreviation
|
||||||
|
bytes = B
|
||||||
|
# kibibyte abbreviation
|
||||||
|
kb = KB
|
||||||
|
# mebibyte abbreviation
|
||||||
|
mb = MB
|
||||||
|
# gibibyte abbreviation
|
||||||
|
gb = GB
|
||||||
|
# localized number and byte abbreviation. example "2.5MB"
|
||||||
|
fileSize = { $num }{ $units }
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
totalSize = Mida total: { $size }
|
||||||
|
# the next line after the colon contains a file name
|
||||||
|
copyLinkDescription = Copiar lo vinclo que quiers compartir
|
||||||
|
copyLinkButton = Copiar lo vinclo
|
||||||
|
downloadTitle = Descargar los fichers
|
||||||
|
downloadDescription = Este fichero s'ha compartiu per medio de { -send-brand } con cifrau de cabo a cabo y un vinclo que caduca automaticament.
|
||||||
|
trySendDescription = Preba { -send-brand } pa una compartición de fichers simpla y segura.
|
||||||
|
# count will always be > 10
|
||||||
|
tooManyFiles =
|
||||||
|
{ $count ->
|
||||||
|
[one] Nomás se puet puyar 1 fitxer de vez.
|
||||||
|
*[other] Nomás se pueden puyar { $count } fichers de vez.
|
||||||
|
}
|
||||||
|
# count will always be > 10
|
||||||
|
tooManyArchives =
|
||||||
|
{ $count ->
|
||||||
|
[one] Nomás se permite 1 ficher.
|
||||||
|
*[other] Nomás se permiten { $count } fichers.
|
||||||
|
}
|
||||||
|
expiredTitle = Este vinclo ye caducau.
|
||||||
|
notSupportedDescription = { -send-brand } no funcionará con este navegador. { -send-short-brand } funciona millor con a zaguera versión de { -firefox } y funcionará con a versión mas recient d'a mayor parte de navegadors.
|
||||||
|
downloadFirefox = Descargar { -firefox }
|
||||||
|
legalTitle = Aviso de privacidat de { -send-short-brand }
|
||||||
|
legalDateStamp = Versió 1.0, con data d'o 12 de marzo de 2019
|
||||||
|
# A short representation of a countdown timer containing the number of days, hours, and minutes remaining as digits, example "2d 11h 56m"
|
||||||
|
expiresDaysHoursMinutes = { $days } d { $hours } h { $minutes } min
|
||||||
|
addFilesButton = Triar los fichers a cargar
|
||||||
|
uploadButton = Cargar
|
||||||
|
# the first part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
|
dragAndDropFiles = Arrociega y suelta los fichers
|
||||||
|
# the second part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
orClickWithSize = u fes clic aquí pa ninviar dica { $size }
|
||||||
|
addPassword = Protecher con una clau
|
||||||
|
emailPlaceholder = Escribe la tuya adreza de correu
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
signInSizeBump = Inicia una sesión pa ninviar dica { $size }
|
||||||
|
signInOnlyButton = Iniciar la sesión
|
||||||
|
accountBenefitTitle = Crea una cuenta de { -firefox } u dentra-ie
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
accountBenefitLargeFiles = Compartir fichers dica { $size }
|
||||||
|
accountBenefitDownloadCount = Compartir fichers con mas chent
|
||||||
|
accountBenefitTimeLimit =
|
||||||
|
{ $count ->
|
||||||
|
[one] Mantiene los vinclos activos dica 1 dia
|
||||||
|
*[other] Mantiene los vinclos activos dica { $count } días
|
||||||
|
}
|
||||||
|
accountBenefitSync = Chestiona los fichers compartius dende qualsequier dispositivo
|
||||||
|
accountBenefitMoz = Descubre mas cosas sobre los atros servicios de { -mozilla }
|
||||||
|
signOut = Zarrar la sesión
|
||||||
|
okButton = Vale
|
||||||
|
downloadingTitle = Se ye descargando
|
||||||
|
noStreamsWarning = Este navegador talment no pueda descifrar un fichero tant gran.
|
||||||
|
noStreamsOptionCopy = Copia lo vinclo pa ubrir-lo en belatro navegador
|
||||||
|
noStreamsOptionFirefox = Preba lo nuestro navegador favorito
|
||||||
|
noStreamsOptionDownload = Continar con este navegador
|
||||||
|
downloadFirefoxPromo = Lo nuevo { -firefox } t'ofreix { -send-short-brand }.
|
||||||
|
# the next line after the colon contains a file name
|
||||||
|
shareLinkDescription = Comparte lo vinclo enta lo tuyo fichero:
|
||||||
|
shareLinkButton = Compartir lo vinclo
|
||||||
|
# $name is the name of the file
|
||||||
|
shareMessage = Baixa-te «{ $name }» con { -send-brand }: compartición de fiches simpla y segura
|
||||||
|
trailheadPromo = I hai una manera de protecher la tuya privacidat. Une-te a Firefox.
|
||||||
|
learnMore = Mas información
|
||||||
@@ -1,31 +1,7 @@
|
|||||||
# Firefox Send is a brand name and should not be localized.
|
|
||||||
title = فَيَرفُكس سِنْد
|
title = فَيَرفُكس سِنْد
|
||||||
siteSubtitle = تجربة وِبّيّة
|
|
||||||
siteFeedback = الانطباعات
|
|
||||||
uploadPageHeader = شارِك ملفاتك بخصوصية وتعمية
|
|
||||||
uploadPageExplainer = أرسل الملفات عبر رابط آمن خاص ومعمّى تنتهي صلاحيته تلقائيا لتضمن عدم بقاء ما ترسله إلى الأبد.
|
|
||||||
uploadPageLearnMore = اطّلع على المزيد
|
|
||||||
uploadPageDropMessage = أسقِط ملفّك هنا لبدء الرفع
|
|
||||||
uploadPageSizeMessage = لتتحصل على أفضل تجربة، من المستحسن أن يكون الملف أصغر من 1 غ.بايت
|
|
||||||
uploadPageBrowseButton = اختر ملفًا على حاسوبك
|
|
||||||
uploadPageBrowseButton1 = اختر ملفّا لرفعه
|
|
||||||
uploadPageMultipleFilesAlert = رفع عدة ملفات (أو رفع مجلد) ليس مدعوما حاليا.
|
|
||||||
uploadPageBrowseButtonTitle = ارفع ملفًا
|
|
||||||
uploadingPageProgress = يرفع { $filename } ({ $size })
|
|
||||||
importingFile = يستورد…
|
importingFile = يستورد…
|
||||||
verifyingFile = يتحقق…
|
|
||||||
encryptingFile = يعمّي…
|
encryptingFile = يعمّي…
|
||||||
decryptingFile = يفك التعمية…
|
decryptingFile = يفك التعمية…
|
||||||
notifyUploadDone = انتهى الرفع.
|
|
||||||
uploadingPageMessage = ما إن يُرفع الملف سيُتاح ضبط خيارات انتهاء صلاحيته.
|
|
||||||
uploadingPageCancel = ألغِ الرفع
|
|
||||||
uploadCancelNotification = أُلغي الرفع.
|
|
||||||
uploadingPageLargeFileMessage = هذا الملف كبير الحجم وسيأخذ رفعه وقتا. انتظر رجاءً.
|
|
||||||
uploadingFileNotification = أعلِمني عندما يكتمل الرفع.
|
|
||||||
uploadSuccessConfirmHeader = جاهز للإرسال
|
|
||||||
uploadSvgAlt = ارفع
|
|
||||||
uploadSuccessTimingHeader = ستنتهي صلاحية الرابط الذي يشير إلى الملف في حال: نُزِّل لأول مرة، أو مرّ ٢٤ ساعة على رفعه.
|
|
||||||
expireInfo = ستنتهي صلاحية رابط الملف بعد { $downloadCount } أو { $timespan }.
|
|
||||||
downloadCount =
|
downloadCount =
|
||||||
{ $num ->
|
{ $num ->
|
||||||
[zero] لا تنزيلات
|
[zero] لا تنزيلات
|
||||||
@@ -44,76 +20,26 @@ timespanHours =
|
|||||||
[many] { $num } ساعة
|
[many] { $num } ساعة
|
||||||
*[other] { $num } ساعة
|
*[other] { $num } ساعة
|
||||||
}
|
}
|
||||||
copyUrlFormLabelWithName = انسخ الرابط وشاركه لإرسال الملف: { $filename }
|
|
||||||
copyUrlFormButton = انسخ إلى الحافظة
|
|
||||||
copiedUrl = نُسخ!
|
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 = كلمة السر
|
unlockInputPlaceholder = كلمة السر
|
||||||
unlockButtonLabel = افتح القفل
|
unlockButtonLabel = افتح القفل
|
||||||
downloadFileTitle = نزِّل الملف المعمّى
|
|
||||||
# Firefox Send is a brand name and should not be localized.
|
|
||||||
downloadMessage = يُرسل إليك صديقك ملفا عبر «فَيَرفُكس سِنْد»، وهي خدمة تتيح لك مشاركة الملفات عبر رابط آمن وخاص ومعمّى، حيث تنتهي صلاحياتها تلقائيا لتضمن عدم بقاء ما ترسله إلى الأبد.
|
|
||||||
# Text and title used on the download link/button (indicates an action).
|
|
||||||
downloadButtonLabel = نزّل
|
downloadButtonLabel = نزّل
|
||||||
downloadNotification = لقد اكتمل التنزيل.
|
|
||||||
downloadFinish = اكتمل التنزيل
|
downloadFinish = اكتمل التنزيل
|
||||||
# This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
|
||||||
fileSizeProgress = ({ $partialSize } من أصل { $totalSize })
|
fileSizeProgress = ({ $partialSize } من أصل { $totalSize })
|
||||||
# Firefox Send is a brand name and should not be localized.
|
|
||||||
sendYourFilesLink = جرِّب «فَيَرفُكس سِنْد»
|
sendYourFilesLink = جرِّب «فَيَرفُكس سِنْد»
|
||||||
downloadingPageProgress = ينزّل { $filename } ({ $size })
|
|
||||||
downloadingPageMessage = رجاء أبقِ هذا اللسان مفتوحا حتى نجلب الملف ونفك تعميته.
|
|
||||||
errorAltText = خطأ أثناء الرفع
|
|
||||||
errorPageHeader = حدث خطب ما.
|
errorPageHeader = حدث خطب ما.
|
||||||
errorPageMessage = حدث خطب ما أثناء رفع الملف.
|
|
||||||
errorPageLink = أرسل ملفا آخر
|
|
||||||
fileTooBig = حجم الملف كبير للغاية لرفعه. يجب أن يكون أصغر من { $size }.
|
fileTooBig = حجم الملف كبير للغاية لرفعه. يجب أن يكون أصغر من { $size }.
|
||||||
linkExpiredAlt = انتهت صلاحية الرابط
|
linkExpiredAlt = انتهت صلاحية الرابط
|
||||||
expiredPageHeader = انتهت صلاحية هذا الرابط أو لم يكن موجودا في المقام الأول!
|
|
||||||
notSupportedHeader = متصفحك غير مدعوم.
|
notSupportedHeader = متصفحك غير مدعوم.
|
||||||
# Firefox Send is a brand name and should not be localized.
|
|
||||||
notSupportedDetail = للأسف فإن متصفحك لا يدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تجربة متصفح آخر، ونحن ننصحك بِفَيَرفُكس!
|
|
||||||
notSupportedLink = لماذا متصفحي غير مدعوم؟
|
notSupportedLink = لماذا متصفحي غير مدعوم؟
|
||||||
notSupportedOutdatedDetail = للأسف فإن إصدارة فَيَرفُكس هذه لا تدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تحديث متصفحك.
|
notSupportedOutdatedDetail = للأسف فإن إصدارة فَيَرفُكس هذه لا تدعم تقنية الوِب التي يعتمد عليها «فَيَرفُكس سِنْد». عليك تحديث متصفحك.
|
||||||
updateFirefox = حدّث فَيَرفُكس
|
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 = ألغِ
|
deletePopupCancel = ألغِ
|
||||||
deleteButtonHover = احذف
|
deleteButtonHover = احذف
|
||||||
copyUrlHover = انسخ الرابط
|
|
||||||
footerLinkLegal = القانونية
|
footerLinkLegal = القانونية
|
||||||
# Test Pilot is a proper name and should not be localized.
|
|
||||||
footerLinkAbout = حول الاختبار التجريبي
|
|
||||||
footerLinkPrivacy = الخصوصية
|
footerLinkPrivacy = الخصوصية
|
||||||
footerLinkTerms = الشروط
|
|
||||||
footerLinkCookies = الكعكات
|
footerLinkCookies = الكعكات
|
||||||
requirePasswordCheckbox = اطلب كلمة سر لتنزيل هذا الملف
|
|
||||||
addPasswordButton = أضِف كلمة سر
|
|
||||||
changePasswordButton = غيّر
|
|
||||||
passwordTryAgain = كلمة السر خاطئة. أعِد المحاولة.
|
passwordTryAgain = كلمة السر خاطئة. أعِد المحاولة.
|
||||||
reportIPInfringement = أبلغ عن انتهاك للملكية الفكرية
|
|
||||||
javascriptRequired = يتطلب فَيَرفُكس سِنْد جافاسكربت
|
javascriptRequired = يتطلب فَيَرفُكس سِنْد جافاسكربت
|
||||||
whyJavascript = لماذا يتطلب فَيَرفُكس سِنْد جافاسكربت؟
|
whyJavascript = لماذا يتطلب فَيَرفُكس سِنْد جافاسكربت؟
|
||||||
enableJavascript = رجاء فعّل جافاسكربت ثم أعد المحاولة.
|
enableJavascript = رجاء فعّل جافاسكربت ثم أعد المحاولة.
|
||||||
@@ -121,9 +47,142 @@ enableJavascript = رجاء فعّل جافاسكربت ثم أعد المحاو
|
|||||||
expiresHoursMinutes = { $hours }س { $minutes }د
|
expiresHoursMinutes = { $hours }س { $minutes }د
|
||||||
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
|
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
|
||||||
expiresMinutes = { $minutes }د
|
expiresMinutes = { $minutes }د
|
||||||
# A short status message shown when a password is successfully set
|
|
||||||
passwordIsSet = ضُبطت كلمة السر
|
|
||||||
# A short status message shown when the user enters a long password
|
# A short status message shown when the user enters a long password
|
||||||
maxPasswordLength = أقصر طول لكلمة السر: { $length }
|
maxPasswordLength = أقصر طول لكلمة السر: { $length }
|
||||||
# A short status message shown when there was an error setting the password
|
# A short status message shown when there was an error setting the password
|
||||||
passwordSetError = يجب ألا تُضبط كلمة السر هذه
|
passwordSetError = يجب ألا تُضبط كلمة السر هذه
|
||||||
|
|
||||||
|
## Send version 2 strings
|
||||||
|
|
||||||
|
-send-brand = Send
|
||||||
|
-send-short-brand = Send
|
||||||
|
-firefox = Firefox
|
||||||
|
-mozilla = Mozilla
|
||||||
|
introTitle = شارِك ملفاتك بلا عناء وبخصوصية تامة
|
||||||
|
introDescription = يتيح لك { -send-brand } مشاركة الملفات عبر تعميتها من الطرفين وإتاحتها في رابط ينقضي أجله تلقائيا. هكذا يمكنك إبقاء ما شاركته خاصًا فتضمن بأن ملفاتك لن تبقى في الوِب أبد الدهر.
|
||||||
|
notifyUploadEncryptDone = اكتملت تعمية الملف وأصبح جاهزًا لإرساله
|
||||||
|
# downloadCount is from the downloadCount string and timespan is a timespanMinutes string. ex. 'Expires after 2 downloads or 25 minutes'
|
||||||
|
archiveExpiryInfo = ينقضي بعد { $downloadCount } أو { $timespan }
|
||||||
|
timespanMinutes =
|
||||||
|
{ $num ->
|
||||||
|
[zero] أقل من دقيقة
|
||||||
|
[one] دقيقة واحدة
|
||||||
|
[two] دقيقتين اثنتين
|
||||||
|
[few] { $num } دقائق
|
||||||
|
[many] { $num } دقيقة
|
||||||
|
*[other] { $num } دقيقة
|
||||||
|
}
|
||||||
|
timespanDays =
|
||||||
|
{ $num ->
|
||||||
|
[zero] أقل من يوم
|
||||||
|
[one] يوم واحد
|
||||||
|
[two] يومين اثنين
|
||||||
|
[few] { $num } أيام
|
||||||
|
[many] { $num } يومًا
|
||||||
|
*[other] { $num } يوم
|
||||||
|
}
|
||||||
|
timespanWeeks =
|
||||||
|
{ $num ->
|
||||||
|
[zero] أقل من أسبوع
|
||||||
|
[one] أسبوع واحد
|
||||||
|
[two] أسبوعين اثنين
|
||||||
|
[few] { $num } أسابيع
|
||||||
|
[many] { $num } أسبوعًا
|
||||||
|
*[other] { $num } أسبوع
|
||||||
|
}
|
||||||
|
fileCount =
|
||||||
|
{ $num ->
|
||||||
|
[zero] { $num } ملف
|
||||||
|
[one] ملف واحد
|
||||||
|
[two] ملفان اثنان
|
||||||
|
[few] { $num } ملفات
|
||||||
|
[many] { $num } ملفًا
|
||||||
|
*[other] { $num } ملف
|
||||||
|
}
|
||||||
|
# byte abbreviation
|
||||||
|
bytes = بايت
|
||||||
|
# kibibyte abbreviation
|
||||||
|
kb = ك.بايت
|
||||||
|
# mebibyte abbreviation
|
||||||
|
mb = م.بايت
|
||||||
|
# gibibyte abbreviation
|
||||||
|
gb = ج.بايت
|
||||||
|
# localized number and byte abbreviation. example "2.5MB"
|
||||||
|
fileSize = { $num } { $units }
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
totalSize = إجمالي الحجم: { $size }
|
||||||
|
# the next line after the colon contains a file name
|
||||||
|
copyLinkDescription = انسخ هذا الرابط لتُشارك الملف:
|
||||||
|
copyLinkButton = انسخ الرابط
|
||||||
|
downloadTitle = نزّل الملفات
|
||||||
|
downloadDescription = شارك أحد هذا الملف معك عبر { -send-brand } وعمّاه بتعمية من الطرفين وبرابط ينقضي أجله تلقائيا.
|
||||||
|
trySendDescription = جرِّب { -send-brand } وشارِك ملفاتك بلا عناء وبخصوصية تامة.
|
||||||
|
# count will always be > 10
|
||||||
|
tooManyFiles =
|
||||||
|
{ $count ->
|
||||||
|
[zero] لا يمكنك تنزيل أي ملف في آن واحد.
|
||||||
|
[one] لا يمكنك تنزيل ما يزيد على ملف واحد في آن واحد.
|
||||||
|
[two] لا يمكنك تنزيل ما يزيد على ملفين اثنين في آن واحد.
|
||||||
|
[few] لا يمكنك تنزيل ما يزيد على { $count } ملفات في آن واحد.
|
||||||
|
[many] لا يمكنك تنزيل ما يزيد على { $count } ملفًا في آن واحد.
|
||||||
|
*[other] لا يمكنك تنزيل ما يزيد على { $count } ملف في آن واحد.
|
||||||
|
}
|
||||||
|
# count will always be > 10
|
||||||
|
tooManyArchives =
|
||||||
|
{ $count ->
|
||||||
|
[zero] الأرشيفات ممنوعة.
|
||||||
|
[one] لا يُسمح إلا بأرشيف واحد.
|
||||||
|
[two] لا يُسمح إلا بأرشيفين اثنين.
|
||||||
|
[few] لا يُسمح إلا ب { $count } أرشيفات.
|
||||||
|
[many] لا يُسمح إلا ب { $count } أرشيفًا.
|
||||||
|
*[other] لا يُسمح إلا ب { $count } أرشيف.
|
||||||
|
}
|
||||||
|
expiredTitle = انقضى وقت الرابط.
|
||||||
|
notSupportedDescription = لن يعمل { -send-brand } في هذا المتصفح. أفضل المتصفحات التي يعمل معها { -send-short-brand } هو { -firefox } بآخر إصدارة، كما وأحدث إصدارة من أغلب المتصفحات الموجودة.
|
||||||
|
downloadFirefox = نزِّل { -firefox }
|
||||||
|
legalTitle = تنويه خصوصية { -send-short-brand }
|
||||||
|
legalDateStamp = الإصدارة ١٫٠ بتاريخ ١٢ مارس ٢٠١٩
|
||||||
|
# A short representation of a countdown timer containing the number of days, hours, and minutes remaining as digits, example "2d 11h 56m"
|
||||||
|
expiresDaysHoursMinutes = { $days }يوم { $hours }سا { $minutes }دق
|
||||||
|
addFilesButton = حدّد الملفات التي تريد رفعها
|
||||||
|
uploadButton = ارفع
|
||||||
|
# the first part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
|
dragAndDropFiles = اسحب الملفات وأفلِتها هنا
|
||||||
|
# the second part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
orClickWithSize = أو انقر لإرسال ملفات يصل حجمها { $size }
|
||||||
|
addPassword = احمِه بكلمة سر
|
||||||
|
emailPlaceholder = أدخل بريدك الإلكتروني
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
signInSizeBump = لِج وأرسِل ملفات يصل حجمها { $size }
|
||||||
|
signInOnlyButton = لِج
|
||||||
|
accountBenefitTitle = أنشِئ حساب { -firefox } أو لِج إلى حسابك
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
accountBenefitLargeFiles = شارِك ملفات يصل حجمها { $size }
|
||||||
|
accountBenefitDownloadCount = شارِك الملفات مع أناس أكثر وأكثر
|
||||||
|
accountBenefitTimeLimit =
|
||||||
|
{ $count ->
|
||||||
|
[zero] لا تُبقِ أي روابط نشطة
|
||||||
|
[one] أبقِ الروابط نشطة لمدة تصل إلى يوم واحد
|
||||||
|
[two] أبقِ الروابط نشطة لمدة تصل إلى يومين اثنين
|
||||||
|
[few] أبقِ الروابط نشطة لمدة تصل إلى { $count } أيام
|
||||||
|
[many] أبقِ الروابط نشطة لمدة تصل إلى { $count } يومًا
|
||||||
|
*[other] أبقِ الروابط نشطة لمدة تصل إلى { $count } يوم
|
||||||
|
}
|
||||||
|
accountBenefitSync = أدِر ملفاتك التي شاركتها من أيّ جهاز تريد
|
||||||
|
accountBenefitMoz = اطّلع على المزيد حول خدمات { -mozilla }
|
||||||
|
signOut = اخرج
|
||||||
|
okButton = حسنًا
|
||||||
|
downloadingTitle = يجري التنزيل
|
||||||
|
noStreamsWarning = هناك احتمال بألا يقدر هذا المتصفح على فكّ تعمية الملفات الكبيرة كهذا.
|
||||||
|
noStreamsOptionCopy = انسخ الرابط لتفتحه في متصفح آخر
|
||||||
|
noStreamsOptionFirefox = جرّب متصفّحنا المفضل
|
||||||
|
noStreamsOptionDownload = واصِل بهذا المتصفح
|
||||||
|
downloadFirefoxPromo = { -send-short-brand } تقدمة { -firefox } الجديد الأنيق.
|
||||||
|
# the next line after the colon contains a file name
|
||||||
|
shareLinkDescription = شارِك الرابط الذي يصل إلى الملف:
|
||||||
|
shareLinkButton = شارِك الرابط
|
||||||
|
# $name is the name of the file
|
||||||
|
shareMessage = نزِّل ”{ $name }“ عبر { -send-brand }: خدمة لمشاركة الملفات بلا عناء وبخصوصية تامة
|
||||||
|
trailheadPromo = يمكنك حماية خصوصيتك، طبعا. انضم إلى فَيَرفُكس.
|
||||||
|
learnMore = اطّلع على المزيد.
|
||||||
|
|||||||
@@ -1,31 +1,7 @@
|
|||||||
# Firefox Send is a brand name and should not be localized.
|
title = Send
|
||||||
title = Firefox Send
|
|
||||||
siteSubtitle = esperimentu web
|
|
||||||
siteFeedback = Feedback
|
|
||||||
uploadPageHeader = Compartición privada y cifrada de ficheros
|
|
||||||
uploadPageExplainer = Unvia ficheros pente un enllaz seguru, priváu y cifráu que caduca automáticamente p'asegurar que les tos coses nun queden siempres na rede.
|
|
||||||
uploadPageLearnMore = Deprendi más
|
|
||||||
uploadPageDropMessage = Suelta equí'l to ficheru p'aniciar la xuba
|
|
||||||
uploadPageSizeMessage = Pal meyor funcionamientu, lo meyor ye que'l to ficheru seya menor de 1GB
|
|
||||||
uploadPageBrowseButton = Esbilla un ficheru nel to ordenador
|
|
||||||
uploadPageBrowseButton1 = Esbilla un ficheru pa unviar
|
|
||||||
uploadPageMultipleFilesAlert = Anguaño nun se sofita la xuba múltiple de ficheros o carpetes.
|
|
||||||
uploadPageBrowseButtonTitle = Xubir ficheru
|
|
||||||
uploadingPageProgress = Xubiendo { $filename } ({ $size })
|
|
||||||
importingFile = Importando...
|
importingFile = Importando...
|
||||||
verifyingFile = Verificando...
|
|
||||||
encryptingFile = Cifrando...
|
encryptingFile = Cifrando...
|
||||||
decryptingFile = Descifrando...
|
decryptingFile = Descifrando...
|
||||||
notifyUploadDone = Finó la to xuba.
|
|
||||||
uploadingPageMessage = Namái que'l ficheru xuba, sedrás a afitar les opciones de caducidá.
|
|
||||||
uploadingPageCancel = Encaboxar xuba
|
|
||||||
uploadCancelNotification = Encaboxóse la to xuba.
|
|
||||||
uploadingPageLargeFileMessage = Esti ficheru ye grande y pue entardar daqué en xubir. ¡Paciencia!
|
|
||||||
uploadingFileNotification = Avísame cuando se complete la xuba.
|
|
||||||
uploadSuccessConfirmHeader = Preparáu pa unviar
|
|
||||||
uploadSvgAlt = Xubir
|
|
||||||
uploadSuccessTimingHeader = L'enllaz del to ficheru caducará dempués d'una descarga o en 24 hores.
|
|
||||||
expireInfo = L'enllaz al ficheru va caducar tres { $downloadCount } o { $timespan }.
|
|
||||||
downloadCount =
|
downloadCount =
|
||||||
{ $num ->
|
{ $num ->
|
||||||
[one] 1 descarga
|
[one] 1 descarga
|
||||||
@@ -36,85 +12,132 @@ timespanHours =
|
|||||||
[one] 1 hora
|
[one] 1 hora
|
||||||
*[other] { $num } hores
|
*[other] { $num } hores
|
||||||
}
|
}
|
||||||
copyUrlFormLabelWithName = Copia y comparti l'enllaz pa unviar el to ficheru: { $filename }
|
|
||||||
copyUrlFormButton = Copiar al cartafueyu
|
|
||||||
copiedUrl = ¡Copióse!
|
copiedUrl = ¡Copióse!
|
||||||
deleteFileButton = Desaniciar ficheru
|
|
||||||
sendAnotherFileLink = Unviar otru ficheru
|
|
||||||
# Alternative text used on the download link/button (indicates an action).
|
|
||||||
downloadAltText = Baxar
|
|
||||||
downloadsFileList = Descargues
|
|
||||||
# Used as header in a column indicating the amount of time left before a
|
|
||||||
# download link expires (e.g. "10h 5m")
|
|
||||||
timeFileList = Tiempu
|
|
||||||
# Used as header in a column indicating the number of times a file has been
|
|
||||||
# downloaded
|
|
||||||
downloadFileName = Baxar { $filename }
|
|
||||||
downloadFileSize = ({ $size })
|
|
||||||
unlockInputLabel = Introducir contraseña
|
|
||||||
unlockInputPlaceholder = Contraseña
|
unlockInputPlaceholder = Contraseña
|
||||||
unlockButtonLabel = Desbloquiar
|
unlockButtonLabel = Desbloquiar
|
||||||
downloadFileTitle = Baxar ficheru cifráu
|
|
||||||
# Firefox Send is a brand name and should not be localized.
|
|
||||||
downloadMessage = El to collaciu unvióte un ficheru usando Firefox Send, un serviciu que te permite compartir ficheros con un enllaz seguru, priváu y cifráu que caduca automáticamente p'asegurar que les to coses nun queden siempres na rede.
|
|
||||||
# Text and title used on the download link/button (indicates an action).
|
|
||||||
downloadButtonLabel = Baxar
|
downloadButtonLabel = Baxar
|
||||||
downloadNotification = Completóse la to descarga.
|
downloadFinish = Completóse la descarga
|
||||||
downloadFinish = Descarga completada
|
|
||||||
# This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
|
||||||
fileSizeProgress = ({ $partialSize } de { $totalSize })
|
fileSizeProgress = ({ $partialSize } de { $totalSize })
|
||||||
# Firefox Send is a brand name and should not be localized.
|
sendYourFilesLink = Probar Send
|
||||||
sendYourFilesLink = Prueba Firefox Send
|
errorPageHeader = ¡Asocedió daqué malo!
|
||||||
downloadingPageProgress = Baxando { $filename } ({ $size })
|
|
||||||
downloadingPageMessage = Dexa esta llingüeta abierta entrín vamos en cata del to ficheru y lu desciframos, por favor.
|
|
||||||
errorAltText = Fallu de xuba
|
|
||||||
errorPageHeader = ¡Daqué foi mal!
|
|
||||||
errorPageMessage = Hebo un fallu xubiendo'l ficheru.
|
|
||||||
errorPageLink = Unviar otru ficheru
|
|
||||||
fileTooBig = Esti ficheru ye mui grande como pa xubilu. Debería tener menos de { $size }.
|
fileTooBig = Esti ficheru ye mui grande como pa xubilu. Debería tener menos de { $size }.
|
||||||
linkExpiredAlt = Enllaz caducáu
|
linkExpiredAlt = Caducó l'enllaz
|
||||||
expiredPageHeader = ¡Esti enllaz caducó o enxamás nun esistó!
|
|
||||||
notSupportedHeader = El to restolador nun ta sofitáu.
|
notSupportedHeader = El to restolador nun ta sofitáu.
|
||||||
# Firefox Send is a brand name and should not be localized.
|
|
||||||
notSupportedDetail = Desafortunadamente esti restolador nun sofita la teunoloxía web qu'usa Firefox Send. Precisarás d'usar otru restolador. ¡Aconseyámoste Firefox!
|
|
||||||
notSupportedLink = ¿Por qué'l mio restolador nun ta sofitáu?
|
notSupportedLink = ¿Por qué'l mio restolador nun ta sofitáu?
|
||||||
notSupportedOutdatedDetail = Desafortunadamente esta versión de Firefox nun sofita la teunoloxía web qu'usa Firefox Send. Precisarás d'anovar Firefox.
|
notSupportedOutdatedDetail = Desafortunadamente esta versión de Firefox nun sofita la teunoloxía web qu'usa Send. Vas precisar anovar el restolador.
|
||||||
updateFirefox = Anovar Firefox
|
updateFirefox = Anovar Firefox
|
||||||
downloadFirefoxButtonSub = Descarga de baldre
|
|
||||||
uploadedFile = Ficheru
|
|
||||||
copyFileList = Copiar URL
|
|
||||||
# expiryFileList is used as a column header
|
|
||||||
expiryFileList = Caduca en
|
|
||||||
deleteFileList = Desaniciar
|
|
||||||
nevermindButton = Nun m'importa
|
|
||||||
legalHeader = Términos y privacidá
|
|
||||||
legalNoticeTestPilot = Anguaño Firefox Send ye un esperimentu de Test Pilot y ta suxetu a los <a>Términos de serviciu</a> y l'<a>Avisu de privacidá</a> de Test Pilot. <a>Equí</a> pues deprender más tocante a esti esperimentu y la so recoyida de datos.
|
|
||||||
legalNoticeMozilla = L'usu de Firefox Send tamién ta suxetu al <a>Avisu de privacidá</a> y a los <a>Términos d'usu de la páxina web</a> de Mozilla.
|
|
||||||
deletePopupText = ¿Desaniciar esti ficheru?
|
|
||||||
deletePopupYes = Sí
|
|
||||||
deletePopupCancel = Encaboxar
|
deletePopupCancel = Encaboxar
|
||||||
deleteButtonHover = Desaniciar
|
deleteButtonHover = Desaniciar
|
||||||
copyUrlHover = Copiar URL
|
|
||||||
footerLinkLegal = Llegal
|
footerLinkLegal = Llegal
|
||||||
# Test Pilot is a proper name and should not be localized.
|
|
||||||
footerLinkAbout = Tocante a Test Pilot
|
|
||||||
footerLinkPrivacy = Privacidá
|
footerLinkPrivacy = Privacidá
|
||||||
footerLinkTerms = Términos
|
|
||||||
footerLinkCookies = Cookies
|
footerLinkCookies = Cookies
|
||||||
requirePasswordCheckbox = Riquir una contraseña pa baxar esti ficheru
|
passwordTryAgain = La contraseña ye incorreuta. Volvi tentalo.
|
||||||
addPasswordButton = Amestar contraseña
|
javascriptRequired = Send rique JavaScript
|
||||||
changePasswordButton = Camudar
|
whyJavascript = ¿Por qué Send rique JavaScript?
|
||||||
passwordTryAgain = Contraseña incorreuta. Volvi tentalo.
|
|
||||||
javascriptRequired = Firefox Send rique JavaScript
|
|
||||||
whyJavascript = ¿Por qué Firefox Send rique JavaScript?
|
|
||||||
enableJavascript = Activa JavaScript y volvi tentalo, por favor.
|
enableJavascript = Activa JavaScript y volvi tentalo, por favor.
|
||||||
# A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
|
# 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
|
expiresHoursMinutes = { $hours }h { $minutes }m
|
||||||
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
|
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
|
||||||
expiresMinutes = { $minutes }m
|
expiresMinutes = { $minutes }m
|
||||||
# A short status message shown when a password is successfully set
|
|
||||||
passwordIsSet = Afitóse la contraseña
|
|
||||||
# A short status message shown when the user enters a long password
|
# A short status message shown when the user enters a long password
|
||||||
maxPasswordLength = Llargor máximu de la contraseña: { $length }
|
maxPasswordLength = Llargor máximu de la contraseña: { $length }
|
||||||
# A short status message shown when there was an error setting the password
|
# A short status message shown when there was an error setting the password
|
||||||
passwordSetError = Nun pudo afitase esta contraseña
|
passwordSetError = Nun pudo afitase esta contraseña
|
||||||
|
|
||||||
|
## Send version 2 strings
|
||||||
|
|
||||||
|
-send-brand = Send
|
||||||
|
-send-short-brand = Send
|
||||||
|
-firefox = Firefox
|
||||||
|
-mozilla = Mozilla
|
||||||
|
introTitle = Compartición de ficheros privada y cenciella
|
||||||
|
introDescription = { -send-brand } déxate compartir ficheros con cifráu puntu a puntu y un enllaz que caduca automáticamente. D'esti mou, asegúreste de que lo que compartes ye privao y nun va tar siempres en llinia.
|
||||||
|
notifyUploadEncryptDone = El ficheru ta cifráu y preparáu pa unviase
|
||||||
|
# downloadCount is from the downloadCount string and timespan is a timespanMinutes string. ex. 'Expires after 2 downloads or 25 minutes'
|
||||||
|
archiveExpiryInfo = Caduca dempués de { $downloadCount } ó { $timespan }
|
||||||
|
timespanMinutes =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 minutu
|
||||||
|
*[other] { $num } minutos
|
||||||
|
}
|
||||||
|
timespanDays =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 día
|
||||||
|
*[other] { $num } díes
|
||||||
|
}
|
||||||
|
timespanWeeks =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 selmana
|
||||||
|
*[other] { $num } selmanes
|
||||||
|
}
|
||||||
|
fileCount =
|
||||||
|
{ $num ->
|
||||||
|
[one] 1 ficheru
|
||||||
|
*[other] { $num } ficheros
|
||||||
|
}
|
||||||
|
# byte abbreviation
|
||||||
|
bytes = B
|
||||||
|
# kibibyte abbreviation
|
||||||
|
kb = KB
|
||||||
|
# mebibyte abbreviation
|
||||||
|
mb = MB
|
||||||
|
# gibibyte abbreviation
|
||||||
|
gb = GB
|
||||||
|
# localized number and byte abbreviation. example "2.5MB"
|
||||||
|
fileSize = { $num }{ $units }
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
totalSize = Tamañu total: { $size }
|
||||||
|
# the next line after the colon contains a file name
|
||||||
|
copyLinkDescription = Copia l'enllaz pa compartir el ficheru:
|
||||||
|
copyLinkButton = Copiar l'enllaz
|
||||||
|
downloadTitle = Descarga de ficheros
|
||||||
|
downloadDescription = Esti ficheru compartióse per { -send-brand } con cifráu puntu a puntu y un enllaz que caduca automáticamente.
|
||||||
|
trySendDescription = Prueba { -send-brand } pa una compartición de ficheros cenciella y segura.
|
||||||
|
# count will always be > 10
|
||||||
|
tooManyFiles =
|
||||||
|
{ $count ->
|
||||||
|
[one] Namái pue xubise 1 ficheru al empar.
|
||||||
|
*[other] Namái puen xubise { $count } ficheros al empar.
|
||||||
|
}
|
||||||
|
# count will always be > 10
|
||||||
|
tooManyArchives =
|
||||||
|
{ $count ->
|
||||||
|
[one] Namái se permite 1 archivu
|
||||||
|
*[other] Namái se permiten { $count } archivos
|
||||||
|
}
|
||||||
|
expiredTitle = Esti enllaz caducó.
|
||||||
|
notSupportedDescription = { -send-brand } nun va funcionar con esti restolador. { -send-short-brand } funciona meyor cola última versión de { -firefox } y l'actual de la mayoría de restoladores.
|
||||||
|
downloadFirefox = Baxar { -firefox }
|
||||||
|
legalTitle = Avisu de privacidá de { -send-short-brand }
|
||||||
|
legalDateStamp = Versión 1.0, con data del 12 de marzu de 2019
|
||||||
|
# A short representation of a countdown timer containing the number of days, hours, and minutes remaining as digits, example "2d 11h 56m"
|
||||||
|
expiresDaysHoursMinutes = { $days }d { $hours }h { $minutes }m
|
||||||
|
addFilesButton = Esbillar los ficheros a unviar
|
||||||
|
uploadButton = Xubir
|
||||||
|
# the first part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
|
dragAndDropFiles = Arrastra y suelta ficheros
|
||||||
|
# the second part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
orClickWithSize = o calca pa unviar hasta { $size }
|
||||||
|
addPassword = Protexer con una contraseña
|
||||||
|
emailPlaceholder = Introduz el to corréu
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
signInSizeBump = Anicia sesión pa unviar hasta { $size }
|
||||||
|
signInOnlyButton = Aniciar sesión
|
||||||
|
accountBenefitTitle = Creación d'una cuenta de { -firefox } o aniciu de sesión nella
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
accountBenefitLargeFiles = Comparti ficheros d'hasta { $size }
|
||||||
|
accountBenefitDownloadCount = Comparti ficheros con más xente
|
||||||
|
accountBenefitTimeLimit =
|
||||||
|
{ $count ->
|
||||||
|
[one] Caltién activos los enllaces demientres 1 día
|
||||||
|
*[other] Caltién activos los enllaces demientres { $count } díes
|
||||||
|
}
|
||||||
|
accountBenefitSync = Xestiona los ficheros compartíos dende cualesquier preséu
|
||||||
|
accountBenefitMoz = Deprendi más tocante a otros servicios de { -mozilla }
|
||||||
|
signOut = Zarrar sesión
|
||||||
|
okButton = Aceutar
|
||||||
|
downloadingTitle = Baxando
|
||||||
|
noStreamsWarning = Esti restolador quiciabes nun seya a descifrar un ficheru d'esti tamañu.
|
||||||
|
trailheadPromo = Hai un mou de protexer la to privacidá. Xúnite a Firefox.
|
||||||
|
learnMore = Deprender más.
|
||||||
|
|||||||
@@ -1,31 +1,7 @@
|
|||||||
# Firefox Send is a brand name and should not be localized.
|
title = Send
|
||||||
title = Firefox Send
|
|
||||||
siteSubtitle = web eksperiment
|
|
||||||
siteFeedback = Geri dönüş
|
|
||||||
uploadPageHeader = Məxfi, Şifrələnmiş Fayl Paylaşma
|
|
||||||
uploadPageExplainer = Fayllarınızı təhlükəsiz, məxfi, şifrələnmiş və daima onlayn qalmaması üçün avtomatik silinən keçidlə göndərin.
|
|
||||||
uploadPageLearnMore = Ətraflı öyrən
|
|
||||||
uploadPageDropMessage = Yükləmək üçün faylınızı buraya daşıyın
|
|
||||||
uploadPageSizeMessage = Xidmətin daha yaxşı işləməsi üçün faylınız 1 GB-dan az olmalıdır
|
|
||||||
uploadPageBrowseButton = Kompüterinizdən fayl seçin
|
|
||||||
uploadPageBrowseButton1 = Yüklənəcək faylı seçin
|
|
||||||
uploadPageMultipleFilesAlert = Birdən çox fayl və ya qovluq yükləmə hələlik dəstəklənmir.
|
|
||||||
uploadPageBrowseButtonTitle = Fayl yüklə
|
|
||||||
uploadingPageProgress = { $filename } ({ $size }) yüklənir
|
|
||||||
importingFile = İdxal edilir…
|
importingFile = İdxal edilir…
|
||||||
verifyingFile = Təsdiqlənir…
|
|
||||||
encryptingFile = Şifrələnir...
|
encryptingFile = Şifrələnir...
|
||||||
decryptingFile = Şifrə açılır...
|
decryptingFile = Şifrə açılır...
|
||||||
notifyUploadDone = Yükləməniz hazırdır.
|
|
||||||
uploadingPageMessage = Faylınız yükləndikdən sonra vaxtı çıxma seçimlərini qura biləcəksiz.
|
|
||||||
uploadingPageCancel = Yükləməni ləğv et
|
|
||||||
uploadCancelNotification = Yükləməniz ləğv edildi.
|
|
||||||
uploadingPageLargeFileMessage = Fayl böyükdür və yükləmək çox vaxt ala bilər. Səbirli olun!
|
|
||||||
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 =
|
downloadCount =
|
||||||
{ $num ->
|
{ $num ->
|
||||||
[one] 1 endirmə
|
[one] 1 endirmə
|
||||||
@@ -36,86 +12,58 @@ timespanHours =
|
|||||||
[one] 1 saat
|
[one] 1 saat
|
||||||
*[other] { $num } 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ü!
|
copiedUrl = Köçürüldü!
|
||||||
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
|
|
||||||
unlockInputPlaceholder = Parol
|
unlockInputPlaceholder = Parol
|
||||||
unlockButtonLabel = Aç
|
unlockButtonLabel = Aç
|
||||||
downloadFileTitle = Şifrələnmiş Faylı Endir
|
|
||||||
# Firefox Send is a brand name and should not be localized.
|
|
||||||
downloadMessage = Yoldaşınız Firefox Send ilə sizə fayl göndərir, fayllarınızı təhlükəsiz, məxfi, şifrələnmiş və daima onlayn qalmaması üçün avtomatik silən fayl göndərmə xidməti.
|
|
||||||
# Text and title used on the download link/button (indicates an action).
|
|
||||||
downloadButtonLabel = Endir
|
downloadButtonLabel = Endir
|
||||||
downloadNotification = Endirməniz tamamlandı.
|
|
||||||
downloadFinish = Endirmə Tamamlandı
|
downloadFinish = Endirmə Tamamlandı
|
||||||
# This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
|
||||||
fileSizeProgress = ({ $partialSize } / { $totalSize })
|
fileSizeProgress = ({ $partialSize } / { $totalSize })
|
||||||
# Firefox Send is a brand name and should not be localized.
|
sendYourFilesLink = Send Yoxla
|
||||||
sendYourFilesLink = Firefox Send Yoxla
|
|
||||||
downloadingPageProgress = { $filename } faylı ({ $size }) endirilir
|
|
||||||
downloadingPageMessage = Lütfən faylı endirib şifrəsini açarkən vərəqi açıq buraxın.
|
|
||||||
errorAltText = Yükləmə xətası
|
|
||||||
errorPageHeader = Nəsə səhv getdi!
|
errorPageHeader = Nəsə səhv getdi!
|
||||||
errorPageMessage = Faylı yüklərkən xəta baş verdi.
|
|
||||||
errorPageLink = Başqa fayl göndər
|
|
||||||
fileTooBig = Fayl yükləmək üçün çox böyükdür. Fayl { $size }-dan az olmalıdır.
|
fileTooBig = Fayl yükləmək üçün çox böyükdür. Fayl { $size }-dan az olmalıdır.
|
||||||
linkExpiredAlt = Keçidin vaxtı çıxıb
|
linkExpiredAlt = Keçidin vaxtı çıxıb
|
||||||
expiredPageHeader = Keçidin vaxtı çıxıb və ya heç vaxt olmayıb!
|
|
||||||
notSupportedHeader = Səyyahınız dəstəklənmir.
|
notSupportedHeader = Səyyahınız dəstəklənmir.
|
||||||
# Firefox Send is a brand name and should not be localized.
|
|
||||||
notSupportedDetail = Heyf ki, bu səyyah Firefox Send-ə güc verən web texnologiyalarını dəstəkləmir. Fərqli bir səyyah yoxlamalısınız. Biz Firefox məsləhət görürük!
|
|
||||||
notSupportedLink = Səyyahım niyə dəstəklənmir?
|
notSupportedLink = Səyyahım niyə dəstəklənmir?
|
||||||
notSupportedOutdatedDetail = Heyf ki, Firefox səyyahının bu versiyası Firefox Send-ə güc verən web texnologiyalarını dəstəkləmir. Səyyahınızı yeniləməlisiniz.
|
notSupportedOutdatedDetail = Heyf ki, Firefox səyyahının bu versiyası Send-ə güc verən web texnologiyalarını dəstəkləmir. Səyyahınızı yeniləməlisiniz.
|
||||||
updateFirefox = Firefox-u Yenilə
|
updateFirefox = Firefox-u Yenilə
|
||||||
downloadFirefoxButtonSub = Pulsuz Endir
|
|
||||||
uploadedFile = Fayl
|
|
||||||
copyFileList = Keçidi Köçürt
|
|
||||||
# expiryFileList is used as a column header
|
|
||||||
expiryFileList = Vaxtı çıxma tarixi
|
|
||||||
deleteFileList = Sil
|
|
||||||
nevermindButton = Vacib deyil
|
|
||||||
legalHeader = Şərtlər və Məxfilik
|
|
||||||
legalNoticeTestPilot = Firefox Send Test Pilot eksperimentidir, Test Pilot <a>Xidmət Şərtləri</a> və <a>Məxfilik Bildirişi</a>-nə tabedir. Bu eksperiment və məlumat yığma haqqında <a>buradan</a> öyrənə bilərsiz.
|
|
||||||
legalNoticeMozilla = Firefox Send saytının istifadəsi həmçinin Mozilla-nın <a>Saytlar üçün Məxfilik Bildirişi</a> və <a>Sayt İstifadə Şərtləri</a>-nə tabedir.
|
|
||||||
deletePopupText = Fayl silinsin?
|
|
||||||
deletePopupYes = Bəli
|
|
||||||
deletePopupCancel = Ləğv et
|
deletePopupCancel = Ləğv et
|
||||||
deleteButtonHover = Sil
|
deleteButtonHover = Sil
|
||||||
copyUrlHover = Keçidi Köçürt
|
|
||||||
footerLinkLegal = Hüquqi
|
footerLinkLegal = Hüquqi
|
||||||
# Test Pilot is a proper name and should not be localized.
|
|
||||||
footerLinkAbout = Test Pilot Haqqında
|
|
||||||
footerLinkPrivacy = Məxfilik
|
footerLinkPrivacy = Məxfilik
|
||||||
footerLinkTerms = Şərtlər
|
|
||||||
footerLinkCookies = Çərəzlər
|
footerLinkCookies = Çərəzlər
|
||||||
requirePasswordCheckbox = Bu faylı endirmək üçün parol tələb et
|
|
||||||
addPasswordButton = Parol əlavə et
|
|
||||||
changePasswordButton = Dəyişdir
|
|
||||||
passwordTryAgain = Səhv parol. Təkrar yoxlayın.
|
passwordTryAgain = Səhv parol. Təkrar yoxlayın.
|
||||||
reportIPInfringement = Əqli-mülkiyyət pozuntusu bildir
|
javascriptRequired = Send üçün JavaScript lazımdır
|
||||||
javascriptRequired = Firefox Send üçün JavaScript lazımdır
|
whyJavascript = Send niyə JavaScript tələb edir?
|
||||||
whyJavascript = Firefox Send niyə JavaScript tələb edir?
|
|
||||||
enableJavascript = Lütfən JavaScript-i aktiv edib təkrar yoxlayın.
|
enableJavascript = Lütfən JavaScript-i aktiv edib təkrar yoxlayın.
|
||||||
# A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
|
# A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
|
||||||
expiresHoursMinutes = { $hours } saat { $minutes } dəq
|
expiresHoursMinutes = { $hours } saat { $minutes } dəq
|
||||||
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
|
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
|
||||||
expiresMinutes = { $minutes } dəq
|
expiresMinutes = { $minutes } dəq
|
||||||
# A short status message shown when a password is successfully set
|
|
||||||
passwordIsSet = Parol quruldu
|
|
||||||
# A short status message shown when the user enters a long password
|
# A short status message shown when the user enters a long password
|
||||||
maxPasswordLength = Maksimum parol uzunluğu: { $length }
|
maxPasswordLength = Maksimum parol uzunluğu: { $length }
|
||||||
# A short status message shown when there was an error setting the password
|
# A short status message shown when there was an error setting the password
|
||||||
passwordSetError = Parol qurula bilmədi
|
passwordSetError = Parol qurula bilmədi
|
||||||
|
|
||||||
|
## Send version 2 strings
|
||||||
|
|
||||||
|
-send-brand = Send
|
||||||
|
-send-short-brand = Send
|
||||||
|
-firefox = Firefox
|
||||||
|
-mozilla = Mozilla
|
||||||
|
# byte abbreviation
|
||||||
|
bytes = B
|
||||||
|
# kibibyte abbreviation
|
||||||
|
kb = KB
|
||||||
|
# mebibyte abbreviation
|
||||||
|
mb = MB
|
||||||
|
# gibibyte abbreviation
|
||||||
|
gb = GB
|
||||||
|
# localized number and byte abbreviation. example "2.5MB"
|
||||||
|
fileSize = { $num }{ $units }
|
||||||
|
copyLinkButton = Keçidi köçür
|
||||||
|
uploadButton = Yüklə
|
||||||
|
signInOnlyButton = Daxil ol
|
||||||
|
signOut = Çıx
|
||||||
|
okButton = Tamam
|
||||||
|
downloadingTitle = Endirilir
|
||||||
|
shareLinkButton = Keçidi paylaş
|
||||||
|
|||||||
143
public/locales/azz/send.ftl
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
title = Send
|
||||||
|
importingFile = Mokalakijtok…
|
||||||
|
encryptingFile = Motatijtok…
|
||||||
|
decryptingFile = Kichiujtok se uelis kiixtajtoltis ya…
|
||||||
|
downloadCount =
|
||||||
|
{ $num ->
|
||||||
|
*[undefined] 1 kitemouijtok / { $num } kintemouijtok
|
||||||
|
}
|
||||||
|
timespanHours =
|
||||||
|
{ $num ->
|
||||||
|
*[undefined] 1 hora / { $num } hora
|
||||||
|
}
|
||||||
|
copiedUrl = ¡Moixkopinak!
|
||||||
|
unlockInputPlaceholder = Ichtakatajtol
|
||||||
|
unlockButtonLabel = Xikajchiua tein amo kikaua maj tekiti
|
||||||
|
downloadButtonLabel = Xiktemoui
|
||||||
|
downloadFinish = Nochi motemouij ya
|
||||||
|
fileSizeProgress = ({ $partialSize } itech { $totalSize })
|
||||||
|
sendYourFilesLink = Xikejeko Send
|
||||||
|
errorPageHeader = ¡Tensa amo kuali kisak!
|
||||||
|
fileTooBig = Nejin tajkuilol semi ueyi. Moneki amo panos { $size }
|
||||||
|
linkExpiredAlt = Nejin tein tikpatskilij amo tekititok ya
|
||||||
|
notSupportedHeader = Monavegador amo kualtia.
|
||||||
|
notSupportedLink = ¿Keyej nonavegador amo kualtia?
|
||||||
|
notSupportedOutdatedDetail = Tetayokoltij, Firefox tein tikuitok amo kiselia tepostekitilis tecnología web tein ika tekiti Send. Moneki tikyankuilis monavegador.
|
||||||
|
updateFirefox = Maj Firefox moyankuili
|
||||||
|
deletePopupCancel = Maj motsakuili uan amo tami tein kichiujtok
|
||||||
|
deleteButtonHover = Maj majchiua
|
||||||
|
footerLinkLegal = Keniuj motekitiltis
|
||||||
|
footerLinkPrivacy = Keniuj tikyekpiaj tein tikseliaj
|
||||||
|
footerLinkCookies = Cookies
|
||||||
|
passwordTryAgain = Amo yektik ichtakatajtol. Oksepa xikijkuilo.
|
||||||
|
javascriptRequired = Send kineki maj moajsi JavaScript
|
||||||
|
whyJavascript = ¿Keyej Send kineki maj moajsi JavaScript?
|
||||||
|
enableJavascript = Se kualtakayot, xikaua maj peua tekiti JavaScript uan oksepa xikejeko.
|
||||||
|
# A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m"
|
||||||
|
expiresHoursMinutes = { $hours }h { $minutes }m
|
||||||
|
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
|
||||||
|
expiresMinutes = { $minutes }m
|
||||||
|
# A short status message shown when the user enters a long password
|
||||||
|
maxPasswordLength = Keniuj ueyak ichtakatajtol, maj amo pano: { $length }
|
||||||
|
# A short status message shown when there was an error setting the password
|
||||||
|
passwordSetError = Nejin ichtakatajtol amo uel kiixtaliani
|
||||||
|
|
||||||
|
## Send version 2 strings
|
||||||
|
|
||||||
|
-send-brand = Send
|
||||||
|
-send-short-brand = Xiktitani
|
||||||
|
-firefox = Firefox
|
||||||
|
-mozilla = Mozilla
|
||||||
|
introTitle = Amo ouij uan ichtaka xikinpanoltili oksekin motajkuiloluan archivos
|
||||||
|
introDescription = { -send-brand } mitspaleuia uan ijkon tikinpanoltilis oksekin motajkuiloluan archivos ika tapoualmej tein amo aksa uelis kiajsikamatis, uan no kitemaka kampa se kipatskilis tein niman ixpoliui. Ijkuin uelis tikichtakaeuas tein tikintitanilis oksekin uan tikyekmatis tein moaxka amo nochipaya mokauas itech Internet.
|
||||||
|
notifyUploadEncryptDone = Moarchivo moijkuiloj ya kemej amo akin uelis kiixtajtoltis uan se uelis kititanis ya
|
||||||
|
# downloadCount is from the downloadCount string and timespan is a timespanMinutes string. ex. 'Expires after 2 downloads or 25 minutes'
|
||||||
|
archiveExpiryInfo = Poliui ijkuak tiajsis { $downloadCount } oso { $timespan }
|
||||||
|
timespanMinutes =
|
||||||
|
{ $num ->
|
||||||
|
*[undefined] 1 minuto / { $num } minuto
|
||||||
|
}
|
||||||
|
timespanDays =
|
||||||
|
{ $num ->
|
||||||
|
*[undefined] 1 tonal / { $num } tonalmej
|
||||||
|
}
|
||||||
|
timespanWeeks =
|
||||||
|
{ $num ->
|
||||||
|
*[undefined] 1 semana / { $num } semana
|
||||||
|
}
|
||||||
|
fileCount =
|
||||||
|
{ $num ->
|
||||||
|
*[undefined] 1 archivo / { $num } archivos
|
||||||
|
}
|
||||||
|
# byte abbreviation
|
||||||
|
bytes = B
|
||||||
|
# kibibyte abbreviation
|
||||||
|
kb = KB
|
||||||
|
# mebibyte abbreviation
|
||||||
|
mb = MB
|
||||||
|
# gibibyte abbreviation
|
||||||
|
gb = GB
|
||||||
|
# localized number and byte abbreviation. example "2.5MB"
|
||||||
|
fileSize = { $num }{ $units }
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
totalSize = Nochi tamachiua: { $size }
|
||||||
|
# the next line after the colon contains a file name
|
||||||
|
copyLinkDescription = Xikixkopina tein se kipatskilis uan xikinpanoltili oksekin moarchivo:
|
||||||
|
copyLinkButton = Xikixkopina tein se kipatskilis
|
||||||
|
downloadTitle = Xiktemoui tajkuilolmej archivos
|
||||||
|
downloadDescription = Nejin archivo mopanoltij itechkopa { -send-brand } ika tapoualmej tein amo aksa uelis kiajsikamatis, uan no tein ika se kipatskilis tein niman ixpoliui.
|
||||||
|
trySendDescription = Xikejeko { -send-brand } ijkon amo ouij uelis tikinpanoltilis oksekin motajkuiloluan archivos uan tikyekmatis ke amo tej kipanos.
|
||||||
|
# count will always be > 10
|
||||||
|
tooManyFiles =
|
||||||
|
{ $count ->
|
||||||
|
*[other] Sayoj { $count } tajkuilolmej archivos uelis tikolochtejkoltis saj.
|
||||||
|
}
|
||||||
|
# count will always be > 10
|
||||||
|
tooManyArchives =
|
||||||
|
{ $count ->
|
||||||
|
*[other] Sayoj { $count } tajkuilolmej archivos uelis moajsiskej saj.
|
||||||
|
}
|
||||||
|
expiredTitle = Nejin tein tikpatskilij amo tekititos ok.
|
||||||
|
notSupportedDescription = { -send-brand } amo tekiti ika nejin navegador. { -send-short-brand } okachi kuali tekiti tein ika okachi yankuik { -firefox }, uan no tekitis tein ika okachi yankuikej tel miak navegadores.
|
||||||
|
downloadFirefox = Xiktemoui { -firefox }
|
||||||
|
legalTitle = { -send-short-brand } tanauatia ika yekpialis tein moaxka itech tepos
|
||||||
|
legalDateStamp = Versión 1.0 tein kikixtijkej 12 tonal metsti marzo 2019
|
||||||
|
# A short representation of a countdown timer containing the number of days, hours, and minutes remaining as digits, example "2d 11h 56m"
|
||||||
|
expiresDaysHoursMinutes = { $days }t { $hours }h { $minutes }m
|
||||||
|
addFilesButton = Xikinixpejpena tajkuilolmej archivos tein tikintejkoltis
|
||||||
|
uploadButton = Xiktejkolti
|
||||||
|
# the first part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
|
dragAndDropFiles = Xikintilana uan xikinkajkaua tajkuilolmej archivos
|
||||||
|
# the second part of the string 'Drag and drop files or click to send up to 1GB'
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
orClickWithSize = oso xikpatskili uan tiktitanis, sayoj tein amo panoua { $size }
|
||||||
|
addPassword = Xikyekpia ika se ichtakatajtol
|
||||||
|
emailPlaceholder = Xikijkuilo mocorreo itech tepos
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
signInSizeBump = Xikalakteua uan uelis tiktitanis tein amo panos { $size }
|
||||||
|
signInOnlyButton = Kampa se kalakteua
|
||||||
|
accountBenefitTitle = Ximochiuili se cuenta itech { -firefox } oso xikalakteua
|
||||||
|
# $size is the size of the file, displayed using the fileSize message as format (e.g. "2.5MB")
|
||||||
|
accountBenefitLargeFiles = Xikintitani tajkuilolmej archivos tein amo panouaj { $size }
|
||||||
|
accountBenefitDownloadCount = Xikintitanili tajkuilolmej archivos oksekin
|
||||||
|
accountBenefitTimeLimit =
|
||||||
|
{ $count ->
|
||||||
|
*[undefined] Kampa se kipatskilis maj kisentokakan kualtiakan se tonal ok / { $count } tonalmej ok
|
||||||
|
}
|
||||||
|
accountBenefitSync = Itech tein yeski tepos xikixyekana motajkuiloluan archivos tein tikinpanoltilij oksekin
|
||||||
|
accountBenefitMoz = Okachi tikmatis okseki tapaleuilmej tein kitemaka { -mozilla }
|
||||||
|
signOut = Kampa se kisa
|
||||||
|
okButton = Kuali yetok
|
||||||
|
downloadingTitle = Kitemouijtok
|
||||||
|
noStreamsWarning = Xa navegador amo uelis kitalij nejin tajkuilol archivo tein tel ueyi kemej se uelis kiyekixtajtoltis ya.
|
||||||
|
noStreamsOptionCopy = Xikixkopina tein se kipatskilis uan ijkon se uelis kitatapos itech okse navegador
|
||||||
|
noStreamsOptionFirefox = Xikejeko navegador tein semi techuelita
|
||||||
|
noStreamsOptionDownload = Maj niksentoka niktatekiujti nejin navegador
|
||||||
|
downloadFirefoxPromo = Yankuik { -firefox } mitsixpantilia { -send-short-brand }.
|
||||||
|
# the next line after the colon contains a file name
|
||||||
|
shareLinkDescription = Xikinpanoltili oksekin tein se kipatskilis uan teuika motajkuilol archivo:
|
||||||
|
shareLinkButton = Kampa se kipatskilis tein uelis tikinpanoltilis oksekin
|
||||||
|
# $name is the name of the file
|
||||||
|
shareMessage = Xiktemoui “{ $name }” ika { -send-brand }: amo ouij uelis tikinpanoltilis oksekin motajkuiloluan archivos uan tikyekmatis ke amo tej kipanos
|
||||||
|
trailheadPromo = Kemaj, uelis tikyekpias tein moaxka itech tepos. Xipoui Firefox.
|
||||||
|
learnMore = Xiktemoui tajkuilolmej archivos.
|
||||||