mirror of
https://github.com/element-hq/element-desktop.git
synced 2026-01-03 13:07:55 -05:00
Compare commits
685 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31b0bc1d06 | ||
|
|
281ecc0662 | ||
|
|
d8de60e17b | ||
|
|
cf6140ac40 | ||
|
|
b431898dcd | ||
|
|
153d0badf5 | ||
|
|
c6b25e86eb | ||
|
|
0a070ac6ed | ||
|
|
08f8f88508 | ||
|
|
6e8aa5e308 | ||
|
|
e1651f30b4 | ||
|
|
e7cccf6ad1 | ||
|
|
ce8d31f4ea | ||
|
|
07c11853f5 | ||
|
|
60e42e4cfa | ||
|
|
7fea56ed8b | ||
|
|
193487eb9e | ||
|
|
0f54e90250 | ||
|
|
467e6189f7 | ||
|
|
5c527e3bc9 | ||
|
|
f0107d64a9 | ||
|
|
fb8fff7799 | ||
|
|
031043f2d8 | ||
|
|
926ecec65f | ||
|
|
fb5490e8e8 | ||
|
|
65a446db75 | ||
|
|
ca91532635 | ||
|
|
8f71628533 | ||
|
|
9359c38939 | ||
|
|
634c5c66a6 | ||
|
|
a80bc76e22 | ||
|
|
230657b1db | ||
|
|
da6b3b02f2 | ||
|
|
e2b9922d44 | ||
|
|
3421979db4 | ||
|
|
47f59a4840 | ||
|
|
e3f3814a25 | ||
|
|
ec4057f7e5 | ||
|
|
275e228387 | ||
|
|
61ed2a21c6 | ||
|
|
8fd24d2fa2 | ||
|
|
bfb96a33ba | ||
|
|
0d37d8d8e9 | ||
|
|
f4ee34cc56 | ||
|
|
379a9e9969 | ||
|
|
4b6983747e | ||
|
|
8027988239 | ||
|
|
025a8808b8 | ||
|
|
528a93a3a9 | ||
|
|
5b3301bb0e | ||
|
|
747b596e8b | ||
|
|
9aaeab2221 | ||
|
|
87c5232ba5 | ||
|
|
771483d324 | ||
|
|
90359643ba | ||
|
|
ccade5eccf | ||
|
|
79b0f14984 | ||
|
|
989e030d97 | ||
|
|
531dde8bff | ||
|
|
99ce48be8c | ||
|
|
f18be113c3 | ||
|
|
8b25178aed | ||
|
|
eef3dfc724 | ||
|
|
37d7559d86 | ||
|
|
326e6577e1 | ||
|
|
18500e7ec3 | ||
|
|
1cd299b98e | ||
|
|
3c8650065c | ||
|
|
e2bdedfec1 | ||
|
|
aa6e4d5ce2 | ||
|
|
846a8df9a6 | ||
|
|
eb8429ae9d | ||
|
|
89b1e39b80 | ||
|
|
b9880e2463 | ||
|
|
7f292b12ea | ||
|
|
3599a015d7 | ||
|
|
0681fa81a4 | ||
|
|
b79645adb4 | ||
|
|
f52b10a63a | ||
|
|
e100388495 | ||
|
|
aacb8cb349 | ||
|
|
20d9e2b14b | ||
|
|
9b96700daf | ||
|
|
ffd6cf5ad7 | ||
|
|
4a44ad0a8f | ||
|
|
548f22d438 | ||
|
|
145485326a | ||
|
|
8dd564a7dc | ||
|
|
12809ff4fd | ||
|
|
26e02eff94 | ||
|
|
4b23aaa31e | ||
|
|
fc86ad3db0 | ||
|
|
0af82dbbbd | ||
|
|
0c3736f3af | ||
|
|
7af662cd38 | ||
|
|
4bc4eb74e2 | ||
|
|
c68dcab7c6 | ||
|
|
f53e12624e | ||
|
|
5cefdb028d | ||
|
|
48b01f7070 | ||
|
|
1122c41cc1 | ||
|
|
54d13516b7 | ||
|
|
b662846bf1 | ||
|
|
f5ee82a8ca | ||
|
|
c1bc40d9f8 | ||
|
|
62cb1a36c0 | ||
|
|
859889c3f3 | ||
|
|
69c946132d | ||
|
|
74a3e4fd8b | ||
|
|
048b7932bd | ||
|
|
2fbb9ca630 | ||
|
|
67f6c60e52 | ||
|
|
452ab97314 | ||
|
|
f48c5e7de4 | ||
|
|
eaa515de07 | ||
|
|
b498885b2d | ||
|
|
db86a3f726 | ||
|
|
6b35146f84 | ||
|
|
1659ce7624 | ||
|
|
4e29b24f1e | ||
|
|
4fe261b3d0 | ||
|
|
bf8a648fd4 | ||
|
|
58d56f0068 | ||
|
|
0e5e92015c | ||
|
|
6a4c1afe27 | ||
|
|
bd7e74545a | ||
|
|
c16c473dbf | ||
|
|
96e5389779 | ||
|
|
798a042481 | ||
|
|
fdf99f3d53 | ||
|
|
fd16f79a23 | ||
|
|
2b5208f4e6 | ||
|
|
a6b038e207 | ||
|
|
2b4a8a6ca9 | ||
|
|
87ac970ee5 | ||
|
|
010ad9825f | ||
|
|
31245c7c3a | ||
|
|
f498c7de30 | ||
|
|
58611e41d3 | ||
|
|
31a43aeb24 | ||
|
|
836a99745c | ||
|
|
093dde799d | ||
|
|
df6fb175b3 | ||
|
|
de6570c58e | ||
|
|
c9eb2379ef | ||
|
|
bc75eac336 | ||
|
|
4117f3f858 | ||
|
|
96203a882c | ||
|
|
2b6d064fb5 | ||
|
|
7a03dd2b0c | ||
|
|
03862cda8b | ||
|
|
6fd0c7c87e | ||
|
|
0dac5a3384 | ||
|
|
bdfc6ac35c | ||
|
|
71c46e7365 | ||
|
|
ea30e1ab96 | ||
|
|
9fde6791e5 | ||
|
|
13db0f5ea1 | ||
|
|
24a27bbd17 | ||
|
|
8146de344b | ||
|
|
30634ffedd | ||
|
|
3e1a9e8db4 | ||
|
|
ec8a406348 | ||
|
|
53d25e14e7 | ||
|
|
240c07c65d | ||
|
|
019f4fa22e | ||
|
|
45c5a4acb5 | ||
|
|
af8a686fac | ||
|
|
18a9b07218 | ||
|
|
f26ca3d61d | ||
|
|
7e59fa6e21 | ||
|
|
3a8bde850a | ||
|
|
609677d9c1 | ||
|
|
d24ae7b4c1 | ||
|
|
368cdd4d20 | ||
|
|
2631ceecb8 | ||
|
|
9133260b61 | ||
|
|
e4afc40887 | ||
|
|
bedc4be8bd | ||
|
|
1ac971112f | ||
|
|
00bbd043d6 | ||
|
|
a978ca52ec | ||
|
|
b1085f7a8c | ||
|
|
986f728019 | ||
|
|
254ac4e0b8 | ||
|
|
394e3cde3a | ||
|
|
b725ac99de | ||
|
|
63cefd4540 | ||
|
|
bf5d3f0dc6 | ||
|
|
d8f14a4644 | ||
|
|
f8f6a3c36d | ||
|
|
cce5816da5 | ||
|
|
291205170b | ||
|
|
f174abb8b0 | ||
|
|
ab6b21d47b | ||
|
|
f1dd3d95ea | ||
|
|
bdae96d939 | ||
|
|
4ceb91b3ae | ||
|
|
dbac457d14 | ||
|
|
511867e5f0 | ||
|
|
731e5718e1 | ||
|
|
0c823f5c2d | ||
|
|
937f1c4b40 | ||
|
|
984a98b9df | ||
|
|
905223517e | ||
|
|
15600584ec | ||
|
|
2be7b56a2b | ||
|
|
39c56e6d6a | ||
|
|
c7dea97893 | ||
|
|
928d8c5d56 | ||
|
|
a76d7796a8 | ||
|
|
6aef1fc203 | ||
|
|
3fb802e563 | ||
|
|
1a38ca6fa7 | ||
|
|
3e69d1ec63 | ||
|
|
0bdb69372f | ||
|
|
0ac9454e3a | ||
|
|
63065761ed | ||
|
|
71134c448d | ||
|
|
290907a427 | ||
|
|
bfcb1e6bfb | ||
|
|
494200d576 | ||
|
|
28cbc01d61 | ||
|
|
ba0dd9e2a6 | ||
|
|
45eb1252b6 | ||
|
|
b9e2fbbd23 | ||
|
|
5febbeb681 | ||
|
|
768066e764 | ||
|
|
e436b87e80 | ||
|
|
d5c395f509 | ||
|
|
7107f45cee | ||
|
|
bd85f19c15 | ||
|
|
e2697cd8b4 | ||
|
|
15ca3a3145 | ||
|
|
e2829822a3 | ||
|
|
fac9f53bb6 | ||
|
|
3c324ad5f7 | ||
|
|
d82377e2de | ||
|
|
79a426b1c0 | ||
|
|
00a91489a8 | ||
|
|
b315b71e43 | ||
|
|
e132e1c610 | ||
|
|
8592840143 | ||
|
|
a8f44fd6e9 | ||
|
|
a22ba39e63 | ||
|
|
3dd611b5d5 | ||
|
|
eed1252f33 | ||
|
|
9913b0ff78 | ||
|
|
e07bfc1d6a | ||
|
|
b5725da9ea | ||
|
|
5bbce91e51 | ||
|
|
0aed8eac36 | ||
|
|
ad337b1f7c | ||
|
|
8d380fe533 | ||
|
|
b6cd3c4cee | ||
|
|
6e7913c7d1 | ||
|
|
9cb171f953 | ||
|
|
44310712f3 | ||
|
|
9b65962d26 | ||
|
|
8302e284a3 | ||
|
|
390e2306d0 | ||
|
|
60e61415ea | ||
|
|
f4be51959b | ||
|
|
397bc66522 | ||
|
|
4db0a0ac0c | ||
|
|
d353c68a75 | ||
|
|
0143b4b114 | ||
|
|
93dd8aa2ba | ||
|
|
140b0b8c29 | ||
|
|
744050d8f4 | ||
|
|
39f3e3b9c2 | ||
|
|
43c8b13d75 | ||
|
|
2d568f9688 | ||
|
|
3f49046980 | ||
|
|
53e7100033 | ||
|
|
b8cb53e11b | ||
|
|
c751470abf | ||
|
|
9860ac6b75 | ||
|
|
6e76d658b1 | ||
|
|
3aff9cb9eb | ||
|
|
6579ba80b4 | ||
|
|
37410e6bc4 | ||
|
|
b036113786 | ||
|
|
c579031afc | ||
|
|
ebcd68428e | ||
|
|
f63436a2cb | ||
|
|
c5dd6195f2 | ||
|
|
c3a36bb17d | ||
|
|
f3c1db3313 | ||
|
|
c57a173649 | ||
|
|
48dc1ab396 | ||
|
|
997f2c21bf | ||
|
|
a171fa417b | ||
|
|
115f25165a | ||
|
|
c1ca909c7c | ||
|
|
f61370505b | ||
|
|
02d0999b18 | ||
|
|
5b15bc9b5d | ||
|
|
6b4e6f6be6 | ||
|
|
10a11242ff | ||
|
|
e59a2588ec | ||
|
|
a7938ae514 | ||
|
|
cf3112a9a9 | ||
|
|
b23048c5ce | ||
|
|
c689529641 | ||
|
|
7b6ead738e | ||
|
|
b6b5547ad0 | ||
|
|
e28390fddb | ||
|
|
b18bcd9bed | ||
|
|
b470657cdb | ||
|
|
fc51063f7a | ||
|
|
099ecc468a | ||
|
|
99b5947d7d | ||
|
|
7418161475 | ||
|
|
40be024c9f | ||
|
|
b2f09570f7 | ||
|
|
c3f150f9f0 | ||
|
|
8a90a12683 | ||
|
|
e7bc785ae4 | ||
|
|
d1cb3092e8 | ||
|
|
a136ccbf4c | ||
|
|
231db351ec | ||
|
|
06b0f0fe01 | ||
|
|
4b71142a87 | ||
|
|
9726be5754 | ||
|
|
2293b6794c | ||
|
|
492bbcbe06 | ||
|
|
9cc1d91011 | ||
|
|
c08b62015e | ||
|
|
03abf9f9d0 | ||
|
|
69e4ec7287 | ||
|
|
cf5e1eb4d4 | ||
|
|
dd494103de | ||
|
|
57ba22f874 | ||
|
|
225456b8e0 | ||
|
|
03e59aa60c | ||
|
|
330e230f2c | ||
|
|
dee2ecdf6c | ||
|
|
f109065606 | ||
|
|
344800d835 | ||
|
|
8c99f0ad12 | ||
|
|
bc0e3bb317 | ||
|
|
b7e4e9e075 | ||
|
|
5055ad0aec | ||
|
|
8a1a315938 | ||
|
|
5958166348 | ||
|
|
767445e6f6 | ||
|
|
99a3a132b3 | ||
|
|
1b1c869419 | ||
|
|
b67d48d661 | ||
|
|
922bed58ab | ||
|
|
7f28e3f1e2 | ||
|
|
2fca2151b9 | ||
|
|
303a3d46f5 | ||
|
|
8de0368c7c | ||
|
|
950657a364 | ||
|
|
bc0f370821 | ||
|
|
1455687384 | ||
|
|
3efed87d38 | ||
|
|
0c43287f98 | ||
|
|
c539f603dc | ||
|
|
b2d00f6605 | ||
|
|
27bce3b92b | ||
|
|
439ea006bf | ||
|
|
315a2107d3 | ||
|
|
43527fb609 | ||
|
|
3c946967ce | ||
|
|
0a17382901 | ||
|
|
d3508f3039 | ||
|
|
0f6f947254 | ||
|
|
bebed00200 | ||
|
|
40a248a275 | ||
|
|
c86ad29b3d | ||
|
|
8ade746fec | ||
|
|
2733b2ad11 | ||
|
|
d74631a555 | ||
|
|
944bb3c717 | ||
|
|
7fad439078 | ||
|
|
f98d1672af | ||
|
|
debeef6ee6 | ||
|
|
28096f3a22 | ||
|
|
5efc4528a2 | ||
|
|
b216eca07a | ||
|
|
5ad645f024 | ||
|
|
75b41d65c1 | ||
|
|
cc5dc4d801 | ||
|
|
c4aeaadfbd | ||
|
|
7e34425d78 | ||
|
|
5f09505056 | ||
|
|
9def502c95 | ||
|
|
e09b01f303 | ||
|
|
32aacbdd9a | ||
|
|
e1554cbbe0 | ||
|
|
2b8c378773 | ||
|
|
fe7bf57565 | ||
|
|
cbaad79515 | ||
|
|
a7a8115b17 | ||
|
|
e694a8e30e | ||
|
|
cda4f8ccb4 | ||
|
|
4369b3a5ad | ||
|
|
933d0b80bf | ||
|
|
043e006f3b | ||
|
|
2a1acf0525 | ||
|
|
820e32e250 | ||
|
|
20e4459bb5 | ||
|
|
913c954eeb | ||
|
|
b5fdeddec3 | ||
|
|
38dab479b1 | ||
|
|
f385b1f1d5 | ||
|
|
298ccbe16c | ||
|
|
b8c99c6186 | ||
|
|
ca70486323 | ||
|
|
4bee175f16 | ||
|
|
628b37a2d1 | ||
|
|
9daf5ae413 | ||
|
|
19c8c5f580 | ||
|
|
989b4df6c6 | ||
|
|
21c40b796f | ||
|
|
ccb302b951 | ||
|
|
7d29046cba | ||
|
|
9c3e557333 | ||
|
|
6a7735247e | ||
|
|
c0f5ed15e2 | ||
|
|
56d9a86152 | ||
|
|
82def5ce37 | ||
|
|
5ec91d3a53 | ||
|
|
8b69982023 | ||
|
|
7e69c81e62 | ||
|
|
e64e6e642c | ||
|
|
7f24661357 | ||
|
|
c19baadcd3 | ||
|
|
ada58f826a | ||
|
|
46b1015478 | ||
|
|
8ae641bc59 | ||
|
|
8f4c827089 | ||
|
|
20ce3d10a3 | ||
|
|
72483a60cb | ||
|
|
de16099360 | ||
|
|
ae0213b663 | ||
|
|
b9510d0a0b | ||
|
|
c56aa9100f | ||
|
|
d59384f3e7 | ||
|
|
00ba42186b | ||
|
|
e950303966 | ||
|
|
105070716e | ||
|
|
3b4eb5c18e | ||
|
|
658304cc51 | ||
|
|
b7dd4e53a3 | ||
|
|
1fa6671a17 | ||
|
|
6526127998 | ||
|
|
b640d5e7e4 | ||
|
|
3f9495f19d | ||
|
|
1ff5387f1d | ||
|
|
5ae736e861 | ||
|
|
29d22aefd7 | ||
|
|
11602bb578 | ||
|
|
46ee80837a | ||
|
|
97178f4297 | ||
|
|
cd93f29691 | ||
|
|
48132c9eab | ||
|
|
4f58ba1b22 | ||
|
|
955be8417b | ||
|
|
c136f2cafc | ||
|
|
adfa845d9c | ||
|
|
017cfd70f4 | ||
|
|
d63c27c50a | ||
|
|
7edea00d4e | ||
|
|
ce26e7964d | ||
|
|
14a65fb0af | ||
|
|
cdbda52d9a | ||
|
|
55dffc8c52 | ||
|
|
100f08161d | ||
|
|
d4bf14b47f | ||
|
|
dabca5dc33 | ||
|
|
d11a7e77ac | ||
|
|
adfb4a424a | ||
|
|
5fc871492e | ||
|
|
3c68fe796f | ||
|
|
a996aff19b | ||
|
|
84990559ec | ||
|
|
fdf216ca75 | ||
|
|
f460cfa99a | ||
|
|
d2bc3a8a03 | ||
|
|
6a107f7095 | ||
|
|
ff4ebd4b39 | ||
|
|
76eef82274 | ||
|
|
d986555517 | ||
|
|
939c5b4588 | ||
|
|
f51001a460 | ||
|
|
db2fd9dd53 | ||
|
|
3d18ff16d1 | ||
|
|
9cdea580d3 | ||
|
|
68587e84ed | ||
|
|
58be4c62f7 | ||
|
|
c9c0ceb757 | ||
|
|
a46698559f | ||
|
|
c18b213699 | ||
|
|
b309562244 | ||
|
|
6c6a7d1bd6 | ||
|
|
6bff9f9a20 | ||
|
|
42139ec30d | ||
|
|
6edc4cb965 | ||
|
|
121bba1f35 | ||
|
|
b8adc66898 | ||
|
|
323c42bb72 | ||
|
|
8e51f6299c | ||
|
|
888ce434ff | ||
|
|
a223489511 | ||
|
|
9ca19d7de6 | ||
|
|
dce524f5bc | ||
|
|
48d66c0031 | ||
|
|
36438479fc | ||
|
|
f51acb78e0 | ||
|
|
343215925a | ||
|
|
a535efc1cc | ||
|
|
57496d89e5 | ||
|
|
5dc9ba94ca | ||
|
|
3d519f571f | ||
|
|
d9702e2f92 | ||
|
|
23ecc8573d | ||
|
|
7199a697f4 | ||
|
|
ecbcafe9ea | ||
|
|
8828838e8a | ||
|
|
c7c56f2f97 | ||
|
|
2c619a3a3f | ||
|
|
a4f6d68001 | ||
|
|
d4c48f68ec | ||
|
|
6ce89b667e | ||
|
|
d9bfd239c5 | ||
|
|
030fe38834 | ||
|
|
70c3446339 | ||
|
|
de0e7483a9 | ||
|
|
0b01b32e98 | ||
|
|
f7e5d0aa5f | ||
|
|
21f3847ccc | ||
|
|
e826c04c03 | ||
|
|
39404a62e6 | ||
|
|
4b7a442a73 | ||
|
|
6de76f5b02 | ||
|
|
7058196c25 | ||
|
|
1cd2ce9d34 | ||
|
|
542e2c6891 | ||
|
|
03ce222464 | ||
|
|
00fd261dc1 | ||
|
|
74ebc6cb22 | ||
|
|
a64516483b | ||
|
|
44c8f2c30f | ||
|
|
faa217c767 | ||
|
|
5ad0967b07 | ||
|
|
cda9f3f190 | ||
|
|
2c2addb282 | ||
|
|
19515b614f | ||
|
|
b646f78897 | ||
|
|
3b496c362b | ||
|
|
c4b7e856db | ||
|
|
cb1626cb9f | ||
|
|
a36321b697 | ||
|
|
353f5b35df | ||
|
|
8ae1a518fa | ||
|
|
edea42e30e | ||
|
|
fafd8fb206 | ||
|
|
f9e61d09c9 | ||
|
|
974514e2e6 | ||
|
|
b8d0d990bb | ||
|
|
ed1b9247c1 | ||
|
|
d3adfe3777 | ||
|
|
d45007cd1b | ||
|
|
ed00cfada3 | ||
|
|
3a660b2461 | ||
|
|
c6bbff5ffe | ||
|
|
fa8aef94af | ||
|
|
b7c31f17b5 | ||
|
|
62b6aa26ff | ||
|
|
d69f2a6d44 | ||
|
|
c1ed8ab377 | ||
|
|
df1c7f2498 | ||
|
|
0d00f3ea6c | ||
|
|
2ef3c8c1ab | ||
|
|
e373c03b2a | ||
|
|
99c13923ea | ||
|
|
80cb503c48 | ||
|
|
107efba6f3 | ||
|
|
239bab38ca | ||
|
|
e952b479ac | ||
|
|
231ac2bbb9 | ||
|
|
58543cacc8 | ||
|
|
8582e360b5 | ||
|
|
0e919f9992 | ||
|
|
b0a5003eaa | ||
|
|
6c0d8d5bda | ||
|
|
302f25054a | ||
|
|
82c88ca654 | ||
|
|
b9b7dcb264 | ||
|
|
90d5612548 | ||
|
|
e4583ed752 | ||
|
|
20dace2679 | ||
|
|
bc7231fb00 | ||
|
|
270daf1356 | ||
|
|
4957e9e24f | ||
|
|
7be314caf8 | ||
|
|
2b0ec9eb2f | ||
|
|
5220abbfd4 | ||
|
|
272f317415 | ||
|
|
0023fb25a2 | ||
|
|
783117fea9 | ||
|
|
4e61aad299 | ||
|
|
94dee7bece | ||
|
|
92f57ca880 | ||
|
|
7bd70e2eb6 | ||
|
|
8add8d7717 | ||
|
|
1d811b6f4b | ||
|
|
50a4069893 | ||
|
|
14415e2707 | ||
|
|
367dd5640c | ||
|
|
08ee24dfb6 | ||
|
|
9bb386f2b5 | ||
|
|
0f94667dea | ||
|
|
a71ac70a13 | ||
|
|
5a0f686b28 | ||
|
|
2170e494fb | ||
|
|
6ced94914f | ||
|
|
5ca16bf2fc | ||
|
|
f56dadd0ad | ||
|
|
215babb081 | ||
|
|
f71e2bf232 | ||
|
|
b146f50e91 | ||
|
|
e86e784359 | ||
|
|
afd578df71 | ||
|
|
b54ebc9008 | ||
|
|
30d03d64b3 | ||
|
|
e14c43a2fe | ||
|
|
8e16b1b5e0 | ||
|
|
161b024bf2 | ||
|
|
434215b24b | ||
|
|
df958ff56b | ||
|
|
89d4be6cb6 | ||
|
|
e9ecdde704 | ||
|
|
0e6ae2514a | ||
|
|
f5b51aecfe | ||
|
|
a88d1a838c | ||
|
|
3a3b9641e9 | ||
|
|
6e9407e7c1 | ||
|
|
7d3c203464 | ||
|
|
e9b4fb2245 | ||
|
|
2382cc56b1 | ||
|
|
b7a2810dc3 | ||
|
|
9eb6932f11 | ||
|
|
d1883aa2e8 | ||
|
|
2308ab9a83 | ||
|
|
b33205d607 | ||
|
|
df3d37586e | ||
|
|
fd68a83c08 | ||
|
|
b0f4a4debc | ||
|
|
7fac3a9b7c | ||
|
|
711967d19b | ||
|
|
e00eb4b952 | ||
|
|
caff0ef9f0 | ||
|
|
065f5d7adc | ||
|
|
67f0570af4 | ||
|
|
87125f4c59 | ||
|
|
bae3f08936 | ||
|
|
efc8baf1df | ||
|
|
c0ca8cd240 | ||
|
|
501611fa30 | ||
|
|
9d6cd6c7c0 | ||
|
|
d67f75779e | ||
|
|
26f283b56d | ||
|
|
e1b1020700 | ||
|
|
53d46343b8 | ||
|
|
aaddcef169 | ||
|
|
ba0210f8c1 | ||
|
|
99d1ed4ff1 | ||
|
|
045f122688 | ||
|
|
cbfb247ade | ||
|
|
eb4bf9e401 | ||
|
|
0cdf85e20b | ||
|
|
6e455f6bd4 | ||
|
|
0892ea5006 | ||
|
|
f344d8cac4 | ||
|
|
3decdb060e | ||
|
|
92f48eced9 | ||
|
|
045f6f8b42 | ||
|
|
358e3b8f7d | ||
|
|
c6ab88508f | ||
|
|
a4cbe3acab |
@@ -1,8 +0,0 @@
|
||||
steps:
|
||||
- label: ":eslint: Lint"
|
||||
command:
|
||||
- "yarn install"
|
||||
- "yarn lint"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:10"
|
||||
29
.eslintrc.js
29
.eslintrc.js
@@ -1,6 +1,10 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org"],
|
||||
extends: [
|
||||
"plugin:matrix-org/javascript",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
ecmaVersion: 2021,
|
||||
},
|
||||
env: {
|
||||
es6: true,
|
||||
@@ -8,15 +12,24 @@ module.exports = {
|
||||
// we also have some browser code (ie. the preload script)
|
||||
browser: true,
|
||||
},
|
||||
extends: ["matrix-org"],
|
||||
rules: {
|
||||
// js-sdk uses a babel rule which we can't use because we
|
||||
// don't use babel, so remove it & put the original back
|
||||
"babel/no-invalid-this": "off",
|
||||
"no-invalid-this": "error",
|
||||
"quotes": "off",
|
||||
"indent": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
}
|
||||
}
|
||||
},
|
||||
overrides: [{
|
||||
files: ["{src,scripts,hak}/**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
"plugin:matrix-org/typescript",
|
||||
],
|
||||
rules: {
|
||||
// Things we do that break the ideal style
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"quotes": "off",
|
||||
|
||||
// We disable this while we're transitioning
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @vector-im/element-web
|
||||
12
.github/workflows/preview_changelog.yaml
vendored
Normal file
12
.github/workflows/preview_changelog.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Preview Changelog
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ opened, edited, labeled ]
|
||||
jobs:
|
||||
changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Preview Changelog
|
||||
uses: matrix-org/allchange@main
|
||||
with:
|
||||
ghToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
/dist
|
||||
/lib
|
||||
/webapp
|
||||
/webapp.asar
|
||||
/packages
|
||||
@@ -10,3 +11,5 @@
|
||||
/.yarnrc
|
||||
/docker
|
||||
/.npmrc
|
||||
.vscode
|
||||
.vscode/
|
||||
|
||||
1570
CHANGELOG.md
1570
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
63
README.md
63
README.md
@@ -17,10 +17,10 @@ Since this package is just the Electron wrapper for Element Web, it doesn't cont
|
||||
so the first step is to get a working copy of Element Web. There are a few ways of doing this:
|
||||
|
||||
```
|
||||
# Fetch the prebuilt release Element package from the riot.im GitHub releases page. The version
|
||||
# Fetch the prebuilt release Element package from the element-web GitHub releases page. The version
|
||||
# fetched will be the same as the local element-desktop package.
|
||||
# We're explicitly asking for no config, so the packaged Element will have no config.json.
|
||||
yarn run fetch --noverify --cfgdir ''
|
||||
yarn run fetch --noverify --cfgdir ""
|
||||
```
|
||||
|
||||
...or if you'd like to use GPG to verify the downloaded package:
|
||||
@@ -30,31 +30,36 @@ yarn run fetch --noverify --cfgdir ''
|
||||
# once.
|
||||
yarn run fetch --importkey
|
||||
# Fetch the package and verify the signature
|
||||
yarn run fetch --cfgdir ''
|
||||
yarn run fetch --cfgdir ""
|
||||
```
|
||||
|
||||
...or either of the above, but fetching a specific version of Element:
|
||||
```
|
||||
# Fetch the prebuilt release Element package from the riot.im GitHub releases page. The version
|
||||
# Fetch the prebuilt release Element package from the element-web GitHub releases page. The version
|
||||
# fetched will be the same as the local element-desktop package.
|
||||
yarn run fetch --noverify --cfgdir '' v1.5.6
|
||||
yarn run fetch --noverify --cfgdir "" v1.5.6
|
||||
```
|
||||
|
||||
If you only want to run the app locally and don't need to build packages, you can
|
||||
provide the `webapp` directory directly:
|
||||
```
|
||||
# Assuming you've checked out and built a copy of riot-web in ../riot-web
|
||||
ln -s ../riot-web/webapp ./
|
||||
# Assuming you've checked out and built a copy of element-web in ../element-web
|
||||
ln -s ../element-web/webapp ./
|
||||
```
|
||||
|
||||
[TODO: add support for fetching develop builds, arbitrary URLs and arbitrary paths]
|
||||
|
||||
|
||||
Building
|
||||
========
|
||||
Now you have a copy of Element, you're ready to build packages. If you'd just like to
|
||||
run Element locally, skip to the next section.
|
||||
|
||||
## Native Build
|
||||
|
||||
TODO: List native pre-requisites
|
||||
|
||||
Optionally, [build the native modules](https://github.com/vector-im/element-desktop/blob/develop/docs/native-node-modules.md),
|
||||
which include support for searching in encrypted rooms and secure storage. Skipping this step is fine, you just won't have those features.
|
||||
|
||||
Then, run
|
||||
```
|
||||
yarn run build
|
||||
```
|
||||
@@ -64,35 +69,16 @@ This will do a couple of things:
|
||||
* Run electron-builder to build a package. The package built will match the operating system
|
||||
you're running the build process on.
|
||||
|
||||
If you're on Windows, you can choose to build specifically for 32 or 64 bit:
|
||||
```
|
||||
yarn run build32
|
||||
```
|
||||
or
|
||||
```
|
||||
yarn run build64
|
||||
```
|
||||
## Docker
|
||||
|
||||
This build step will not build any native modules.
|
||||
|
||||
If you'd like to build the native modules (for searching in encrypted rooms):
|
||||
This will take 10 minutes or so, and will require a number of native tools
|
||||
to be installed, depending on your OS (eg. rust, tcl, make/nmake).
|
||||
```
|
||||
yarn run build:native
|
||||
```
|
||||
|
||||
On Windows, this will automatically determine the architecture to build for based
|
||||
on the environment (ie. set up by vcvarsall.bat).
|
||||
|
||||
You can also build using docker, which will always produce the linux package:
|
||||
Alternatively, you can also build using docker, which will always produce the linux package:
|
||||
```
|
||||
# Run this once to make the docker image
|
||||
yarn run docker:setup
|
||||
|
||||
yarn run docker:install
|
||||
# if you want to build the native modules (this will take a while)
|
||||
yarn run docker:buildnative
|
||||
yarn run docker:build:native
|
||||
yarn run docker:build
|
||||
```
|
||||
|
||||
@@ -118,7 +104,7 @@ mkdir myconfig
|
||||
cp /path/to/my/config.json myconfig/
|
||||
yarn run fetch --cfgdir myconfig
|
||||
```
|
||||
The config dir for the official Element.im app is in `riot.im`. If you use this,
|
||||
The config dir for the official Element app is in `element.io`. If you use this,
|
||||
your app will auto-update itself using builds from element.io.
|
||||
|
||||
Profiles
|
||||
@@ -144,9 +130,18 @@ $PROFILE` in which case it becomes `Element-$PROFILE`, or it is using one of
|
||||
the above created by a pre-1.7 install, in which case it will be `Riot` or
|
||||
`Riot-$PROFILE`.
|
||||
|
||||
Translations
|
||||
==========================
|
||||
|
||||
To add a new translation, head to the [translating doc](https://github.com/vector-im/element-web/blob/develop/docs/translating.md).
|
||||
|
||||
For a developer guide, see the [translating dev doc](https://github.com/vector-im/element-web/blob/develop/docs/translating-dev.md).
|
||||
|
||||
[<img src="https://translate.element.io/widgets/element-desktop/-/multi-auto.svg" alt="translationsstatus" width="340">](https://translate.element.io/engage/element-desktop/?utm_source=widget)
|
||||
|
||||
Report bugs & give feedback
|
||||
==========================
|
||||
|
||||
If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.
|
||||
|
||||
To help avoid duplicate issues, please [view existing issues](https://github.com/vector-im/riot-web/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc) first (and add a +1) or [create a new issue](https://github.com/vector-im/riot-web/issues/new) if you can't find it. Please note that this issue tracker is associated with the [Riot-Web](https://github.com/vector-im/riot-web) repo, but is also applied to the code in this repo as well.
|
||||
To help avoid duplicate issues, please [view existing issues](https://github.com/vector-im/element-web/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc) first (and add a +1) or [create a new issue](https://github.com/vector-im/element-web/issues/new/choose) if you can't find it. Please note that this issue tracker is associated with the [element-web](https://github.com/vector-im/element-web) repo, but is also applied to the code in this repo as well.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <tchar.h>
|
||||
|
||||
/*
|
||||
* This just runs 'Element (Riot).exe' with the same args as
|
||||
* This just runs 'Element.exe' with the same args as
|
||||
* this process was invoked with. This gets around the fact that
|
||||
* squirrel always tries to run an executable with the same name,
|
||||
* so fails to restart if the app's name has changed.
|
||||
@@ -15,7 +15,7 @@ void _tmain( int argc, TCHAR *argv[] )
|
||||
char cmdLine[32767];
|
||||
|
||||
LPSTR cmdLinePos = cmdLine;
|
||||
LPSTR toRun = "\"Element (Riot).exe\" ";
|
||||
LPSTR toRun = "\"Element.exe\" ";
|
||||
strncpy(cmdLinePos, toRun, strlen(toRun));
|
||||
cmdLinePos += strlen(toRun);
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
FROM buildpack-deps:xenial-curl
|
||||
FROM buildpack-deps:bionic-curl
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
@@ -10,7 +10,7 @@ RUN apt-get -qq update && apt-get -qq dist-upgrade && \
|
||||
# python for node-gyp
|
||||
# rpm is required for FPM to build rpm package
|
||||
# libsecret-1-dev and libgnome-keyring-dev are required even for prebuild keytar
|
||||
apt-get -qq install --no-install-recommends qtbase5-dev bsdtar build-essential autoconf libssl-dev gcc-multilib g++-multilib lzip rpm python libcurl3 git git-lfs ssh unzip \
|
||||
apt-get -qq install --no-install-recommends qtbase5-dev bsdtar build-essential autoconf libssl-dev gcc-multilib g++-multilib lzip rpm python libcurl4 git git-lfs ssh unzip \
|
||||
libsecret-1-dev libgnome-keyring-dev \
|
||||
libopenjp2-tools \
|
||||
# Used by Seshat
|
||||
@@ -30,7 +30,7 @@ ENV LC_ALL C.UTF-8
|
||||
|
||||
ENV DEBUG_COLORS true
|
||||
ENV FORCE_COLOR true
|
||||
ENV NODE_VERSION 12.16.1
|
||||
ENV NODE_VERSION 14.17.0
|
||||
|
||||
# this package is used for snapcraft and we should not clear apt list - to avoid apt-get update during snap build
|
||||
RUN curl -L https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz | tar xz -C /usr/local --strip-components=1 && \
|
||||
|
||||
138
docs/native-node-modules.md
Normal file
138
docs/native-node-modules.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Native Node Modules
|
||||
|
||||
For some features, the desktop version of Element can make use of native Node
|
||||
modules. These allow Element to integrate with the desktop in ways that a browser
|
||||
cannot.
|
||||
|
||||
While native modules enable powerful new features, they must be complied for
|
||||
each operating system. For official Element releases, we will always build these
|
||||
modules from source to ensure we can trust the compiled output. In the future,
|
||||
we may offer a pre-compiled path for those who want to use these features in a
|
||||
custom build of Element without installing the various build tools required.
|
||||
|
||||
The process is automated by [vector-im/element-builder](https://github.com/vector-im/element-builder)
|
||||
when releasing.
|
||||
|
||||
## Building
|
||||
|
||||
Install the pre-requisites for your system:
|
||||
|
||||
* [Windows pre-requisites](https://github.com/vector-im/element-desktop/blob/develop/docs/windows-requirements.md)
|
||||
* Linux: TODO
|
||||
* OS X: TODO
|
||||
|
||||
Then optionally, [add seshat and dependencies to support search in E2E rooms](#adding-seshat-for-search-in-e2e-encrypted-rooms).
|
||||
|
||||
Then, to build for an architecture selected automatically based on your system (recommended), run:
|
||||
```
|
||||
yarn run build:native
|
||||
```
|
||||
|
||||
If you need to build for a specific architecture, see [here](#compiling-for-specific-architectures).
|
||||
|
||||
## Adding Seshat for search in E2E encrypted rooms
|
||||
|
||||
Seshat is a native Node module that adds support for local event indexing and
|
||||
full text search in E2E encrypted rooms.
|
||||
|
||||
Since Seshat is written in Rust, the Rust compiler and related tools need to be
|
||||
installed before installing Seshat itself. To install Rust please consult the
|
||||
official Rust [documentation](https://www.rust-lang.org/tools/install).
|
||||
|
||||
Seshat also depends on the SQLCipher library to store its data in encrypted form
|
||||
on disk. You'll need to install it via your OS package manager.
|
||||
|
||||
After installing the Rust compiler and SQLCipher, Seshat support can be added
|
||||
using yarn at the root of this project:
|
||||
|
||||
yarn add matrix-seshat
|
||||
|
||||
You will have to rebuild the native libraries against electron's version of
|
||||
of node rather than your system node, using the `electron-build-env` tool.
|
||||
This is also needed to when pulling in changes to Seshat using `yarn link`.
|
||||
|
||||
yarn add electron-build-env
|
||||
|
||||
Recompiling Seshat itself can be done like so:
|
||||
|
||||
yarn run electron-build-env -- --electron 6.1.1 -- neon build matrix-seshat --release
|
||||
|
||||
Please make sure to include all the `--` as well as the `--release` command line
|
||||
switch at the end. Modify your electron version accordingly depending on the
|
||||
version that is installed on your system.
|
||||
|
||||
After this is done the Electron version of Element can be run from the main folder
|
||||
as usual using:
|
||||
|
||||
yarn start
|
||||
|
||||
## Compiling for specific architectures
|
||||
|
||||
### macOS
|
||||
|
||||
On macOS, you can build universal native modules too:
|
||||
```
|
||||
yarn run build:native:universal
|
||||
```
|
||||
|
||||
...or you can build for a specific architecture:
|
||||
```
|
||||
yarn run build:native --target x86_64-apple-darwin
|
||||
```
|
||||
or
|
||||
```
|
||||
yarn run build:native --target aarch64-apple-darwin
|
||||
```
|
||||
|
||||
You'll then need to create a built bundle with the same architecture.
|
||||
To bundle a universal build for macOS, run:
|
||||
|
||||
```
|
||||
yarn run build:universal
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
If you're on Windows, you can choose to build specifically for 32 or 64 bit:
|
||||
```
|
||||
yarn run build:32
|
||||
```
|
||||
or
|
||||
```
|
||||
yarn run build:64
|
||||
```
|
||||
|
||||
### Cross compiling
|
||||
|
||||
Compiling a module for a particular operating system (Linux/macOS/Windows) needs
|
||||
to be done on that operating system. Cross-compiling from a host OS for a different
|
||||
target OS may be possible, but we don't support this flow with Element dependencies
|
||||
at this time.
|
||||
|
||||
### Switching between architectures
|
||||
|
||||
The native module build system keeps the different architectures
|
||||
separate, so you can keep native modules for several architectures at the same
|
||||
time and switch which are active using a `yarn run hak copy` command, passing
|
||||
the appropriate architectures. This will error if you haven't yet built those
|
||||
architectures. eg:
|
||||
|
||||
```
|
||||
yarn run build:native --target x86_64-apple-darwin
|
||||
# We've now built & linked into place native modules for Intel
|
||||
yarn run build:native --target aarch64-apple-darwin
|
||||
# We've now built Apple Silicon modules too, and linked them into place as the active ones
|
||||
|
||||
yarn run hak copy --target x86_64-apple-darwin
|
||||
# We've now switched back to our Intel modules
|
||||
yarn run hak copy --target x86_64-apple-darwin --target aarch64-apple-darwin
|
||||
# Now our native modules are universal x86_64+aarch64 binaries
|
||||
```
|
||||
|
||||
The current set of native modules are stored in `.hak/hakModules`,
|
||||
so you can use this to check what architecture is currently in place, eg:
|
||||
|
||||
```
|
||||
$ lipo -info .hak/hakModules/keytar/build/Release/keytar.node
|
||||
Architectures in the fat file: .hak/hakModules/keytar/build/Release/keytar.node are: x86_64 arm64
|
||||
```
|
||||
26
docs/windows-requirements.md
Normal file
26
docs/windows-requirements.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Windows
|
||||
|
||||
## Requirements to build native modules
|
||||
|
||||
If you want to build native modules, make sure that the following tools are installed on your system.
|
||||
|
||||
- [Node 14](https://nodejs.org)
|
||||
- [Python 3](https://www.python.org/downloads/)
|
||||
- [Strawberry Perl](https://strawberryperl.com/)
|
||||
- [Rust](https://rustup.rs/)
|
||||
- [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019) with the following configuration:
|
||||
- On the Workloads tab:
|
||||
- Desktop & Mobile -> C++ build tools
|
||||
- On the Individual components tab:
|
||||
- MSVC VS 2019 C++ build tools
|
||||
- Windows 10 SDK (latest version available)
|
||||
- C++ CMake tools for Windows
|
||||
|
||||
Once installed make sure all those utilities are accessible in your `PATH`.
|
||||
In order to load all the C++ utilities installed by Visual Studio you can run the following in a terminal window.
|
||||
|
||||
```
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
```
|
||||
|
||||
You can replace `amd64` with `x86` depending on your CPU architecture.
|
||||
@@ -1,34 +1,34 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF0jCCBLqgAwIBAgIRAISYBqZi3VvCUeSfHXF+cbwwDQYJKoZIhvcNAQELBQAw
|
||||
MIIF8DCCBNigAwIBAgIRAIZSd8hNVs3w8AhJIsQSoYYwDQYJKoZIhvcNAQELBQAw
|
||||
gZExCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
|
||||
BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTcwNQYD
|
||||
VQQDEy5DT01PRE8gUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gQ29kZSBTaWduaW5n
|
||||
IENBMB4XDTE3MDgyMzAwMDAwMFoXDTIwMDgyMjIzNTk1OVowgdgxETAPBgNVBAUT
|
||||
IENBMB4XDTIwMDczMDAwMDAwMFoXDTIzMDczMDIzNTk1OVowgfMxETAPBgNVBAUT
|
||||
CDEwODczNjYxMRMwEQYLKwYBBAGCNzwCAQMTAkdCMR0wGwYDVQQPExRQcml2YXRl
|
||||
IE9yZ2FuaXphdGlvbjELMAkGA1UEBhMCR0IxETAPBgNVBBEMCFdDMVIgNEFHMQ8w
|
||||
DQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEbMBkGA1UECQwSMjYgUmVk
|
||||
IExpb24gU3F1YXJlMRcwFQYDVQQKDA5OZXcgVmVjdG9yIEx0ZDEXMBUGA1UEAwwO
|
||||
TmV3IFZlY3RvciBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7
|
||||
X0HP3oM/SVr6PboD03ndtYTONZDcJ/GJ3EyYi6UNrcbKjuDHwPktx9hjAhNjcVkG
|
||||
lmuTEPluPj9DbvjaTrers0cQsAS1vJ0RHjLfA93Flg1ys9Q6OThUMw77FtFPtiJU
|
||||
z5cSYzfFAhn/4dv7BcgGptn+Mv/8CaTu+RUZJUgoSlRWcT1TREmxkzWotbblqsHO
|
||||
zjDmUg20tL5/qpt6BSWsNespf5udKQFXMtqkczBcLvBLmql0vurVcQy8BibB+Q89
|
||||
QKwRzwLgaIa7O8WEssFcW8uJe9s0SNtUy8ehbuoSxpA/DbHFwsiDbNA78vp7HrqM
|
||||
qY6t6OIgLtDYBFCfe/btAgMBAAGjggHaMIIB1jAfBgNVHSMEGDAWgBTfj/MgDOnK
|
||||
pgTYW1g3Kj2rRtyDSTAdBgNVHQ4EFgQUH+mDOdRkF3bYDxCWEaGB4lxiCxcwDgYD
|
||||
VR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMw
|
||||
EQYJYIZIAYb4QgEBBAQDAgQQMEYGA1UdIAQ/MD0wOwYMKwYBBAGyMQECAQYBMCsw
|
||||
KQYIKwYBBQUHAgEWHWh0dHBzOi8vc2VjdXJlLmNvbW9kby5jb20vQ1BTMFUGA1Ud
|
||||
HwROMEwwSqBIoEaGRGh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET1JTQUV4
|
||||
dGVuZGVkVmFsaWRhdGlvbkNvZGVTaWduaW5nQ0EuY3JsMIGGBggrBgEFBQcBAQR6
|
||||
MHgwUAYIKwYBBQUHMAKGRGh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9ET1JT
|
||||
QUV4dGVuZGVkVmFsaWRhdGlvbkNvZGVTaWduaW5nQ0EuY3J0MCQGCCsGAQUFBzAB
|
||||
hhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wJgYDVR0RBB8wHaAbBggrBgEFBQcI
|
||||
A6APMA0MC0dCLTEwODczNjYxMA0GCSqGSIb3DQEBCwUAA4IBAQBJ2aH4aixh0aiz
|
||||
4WKlK+LMVLHpQ2POE3FZYNpAW7o1q2YDGEADXdGrygPE9NCGNBXKo0CAemCYNWfX
|
||||
Ov/jdoiMfeqW3vrZ66oEy8OqbvJSwK1xmomWuYw3wYPWcPVG+YbWYD2CGdQu8jTz
|
||||
fzAJCpvAuY3Wji3fQjiecAC7JCSB4fBHa0ALJOmiSqKQUUpkXs5kW7O0lPBnHzNF
|
||||
2tQGltXMSIrq1QfFtcreMyKlwDOxPIh360dv5aHhaeSRDRKxq7uq5ikQF2gjKx4k
|
||||
ieg2HRbAW6fVPpFr4zRS5umpeZV3i06i11VQQPS/mA/OBEXyaqzx4mr6B7U6ptrp
|
||||
jMqiUv2w
|
||||
IE9yZ2FuaXphdGlvbjELMAkGA1UEBhMCR0IxDzANBgNVBBEMBlc0IDFRVTEYMBYG
|
||||
A1UECAwPTG9uZG9uLCBDaXR5IG9mMQ8wDQYDVQQHDAZMb25kb24xJzAlBgNVBAkM
|
||||
HjE0IFR1cm5oYW0gR3JlZW4sIFRlcnJhY2UgTWV3czEbMBkGA1UECgwSTmV3IFZl
|
||||
Y3RvciBMaW1pdGVkMRswGQYDVQQDDBJOZXcgVmVjdG9yIExpbWl0ZWQwggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKDnGul2M7oCCN+3veGf7yfakSfMjK
|
||||
Kqeylyo0Nj8dUleNQvsRo0OHxIWJlbHDYglxebT109MqgyASr0SoeiqvXOSSFACD
|
||||
MYFoyerRGMnXEuomTU0y7+FE3p/hcim8+C5gS+rHr3YaJJuzzXRztjjCBGoY4was
|
||||
h9V8kYiyMMK0xi2WftOCBa1yYS32CInHIZVmdhzoK4k4YzSYIp57BWvnIUlDyCYk
|
||||
slNfp0SFbDrOGa7kbmy8HRfWPLjNmW5PPIrsf8LlnVRBfmTeUIJtV31w/FuMjeir
|
||||
pzYjzooXmpIrj96ecxdc9thP6etCUazvpowjfewu7UNWRUhGPtYn8v8rAgMBAAGj
|
||||
ggHdMIIB2TAfBgNVHSMEGDAWgBTfj/MgDOnKpgTYW1g3Kj2rRtyDSTAdBgNVHQ4E
|
||||
FgQUB9su4pCQXE5ZWFhB1eo48992LEcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB
|
||||
/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEQYJYIZIAYb4QgEBBAQDAgQQMEkG
|
||||
A1UdIARCMEAwNQYMKwYBBAGyMQECAQYBMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8v
|
||||
c2VjdGlnby5jb20vQ1BTMAcGBWeBDAEDMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6
|
||||
Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET1JTQUV4dGVuZGVkVmFsaWRhdGlvbkNv
|
||||
ZGVTaWduaW5nQ0EuY3JsMIGGBggrBgEFBQcBAQR6MHgwUAYIKwYBBQUHMAKGRGh0
|
||||
dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9ET1JTQUV4dGVuZGVkVmFsaWRhdGlv
|
||||
bkNvZGVTaWduaW5nQ0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
|
||||
ZG9jYS5jb20wJgYDVR0RBB8wHaAbBggrBgEFBQcIA6APMA0MC0dCLTEwODczNjYx
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQBZ8/YtqW/+VUoV8knKpdhMR2uTn8AEyVmfmoZA
|
||||
Ly5kdCRoKvFm/z4VK4cqF7gsUDxRIgDuMKKbWTtr1FEXuaZkUkOjicNcdYxc0pDn
|
||||
nFKBWAv5pN5OmnC9cVqIG7PtvD+8bKVzDdQRjpGMy6PY4rN2PYGfQ7KGgddEDD6m
|
||||
oM51jI/OTvCeU0Tyl0bixKEmpUJvbeQM9Ul2Y1o5Enx1Q9uda8xATb0HCMKgJ+GC
|
||||
iFHL2DNC3j1xK4QoZEIYgbTscj9rK4OMEov3PT/e1FwQyB5V9xdJ5i1MDBDD9fAf
|
||||
OiVgf90SffT7TgWUbA4Z+PtQHq/qNma0+dZWyeq7zYn2IIX3
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"update_base_url": "https://packages.riot.im/nightly/update/",
|
||||
"update_base_url": "https://packages.element.io/nightly/update/",
|
||||
"default_server_name": "matrix.org",
|
||||
"brand": "Element Nightly",
|
||||
"integrations_ui_url": "https://scalar-staging.vector.im/",
|
||||
"integrations_rest_url": "https://scalar-staging.vector.im/api",
|
||||
"integrations_ui_url": "https://scalar.vector.im/",
|
||||
"integrations_rest_url": "https://scalar.vector.im/api",
|
||||
"integrations_widgets_urls": [
|
||||
"https://scalar.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar.vector.im/api",
|
||||
@@ -12,28 +12,19 @@
|
||||
"https://scalar-staging.riot.im/scalar/api"
|
||||
],
|
||||
"hosting_signup_link": "https://element.io/matrix-services?utm_source=element-web&utm_medium=web",
|
||||
"bug_report_endpoint_url": "https://riot.im/bugreports/submit",
|
||||
"features": {
|
||||
"feature_new_spinner": "labs",
|
||||
"feature_pinning": "labs",
|
||||
"feature_custom_status": "labs",
|
||||
"feature_custom_tags": "labs",
|
||||
"feature_state_counters": "labs",
|
||||
"feature_many_integration_managers": "labs",
|
||||
"feature_mjolnir": "labs",
|
||||
"feature_dm_verification": "labs",
|
||||
"feature_bridge_state": "labs",
|
||||
"feature_presence_in_room_list": "labs",
|
||||
"feature_custom_themes": "labs"
|
||||
},
|
||||
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"showLabsSettings": true,
|
||||
"piwik": {
|
||||
"url": "https://piwik.riot.im/",
|
||||
"siteId": 1,
|
||||
"policyUrl": "https://matrix.org/legal/riot-im-cookie-policy"
|
||||
"policyUrl": "https://element.io/cookie-policy"
|
||||
},
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org"
|
||||
"matrix.org",
|
||||
"gitter.im",
|
||||
"libera.chat"
|
||||
]
|
||||
},
|
||||
"enable_presence_by_hs_url": {
|
||||
@@ -42,12 +33,24 @@
|
||||
},
|
||||
"terms_and_conditions_links": [
|
||||
{
|
||||
"url": "https://riot.im/privacy",
|
||||
"url": "https://element.io/privacy",
|
||||
"text": "Privacy Policy"
|
||||
},
|
||||
{
|
||||
"url": "https://matrix.org/legal/riot-im-cookie-policy",
|
||||
"url": "https://element.io/cookie-policy",
|
||||
"text": "Cookie Policy"
|
||||
}
|
||||
]
|
||||
],
|
||||
"sentry": {
|
||||
"dsn": "https://029a0eb289f942508ae0fb17935bd8c5@sentry.matrix.org/6",
|
||||
"environment": "nightly"
|
||||
},
|
||||
"posthog": {
|
||||
"projectApiKey": "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
|
||||
"apiHost": "https://posthog.element.io"
|
||||
},
|
||||
"features": {
|
||||
"feature_spotlight": true
|
||||
},
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ License: Apache-2.0
|
||||
Vendor: support@element.io
|
||||
Architecture: amd64
|
||||
Maintainer: support@element.io
|
||||
Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libappindicator3-1, libsecret-1-0, libsqlcipher0
|
||||
Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libsecret-1-0, libsqlcipher0
|
||||
Recommends: libappindicator3-1
|
||||
Section: net
|
||||
Priority: extra
|
||||
Homepage: https://element.io/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"update_base_url": "https://packages.riot.im/desktop/update/",
|
||||
"update_base_url": "https://packages.element.io/desktop/update/",
|
||||
"default_server_name": "matrix.org",
|
||||
"brand": "Element",
|
||||
"integrations_ui_url": "https://scalar.vector.im/",
|
||||
@@ -12,16 +12,20 @@
|
||||
"https://scalar-staging.riot.im/scalar/api"
|
||||
],
|
||||
"hosting_signup_link": "https://element.io/matrix-services?utm_source=element-web&utm_medium=web",
|
||||
"bug_report_endpoint_url": "https://riot.im/bugreports/submit",
|
||||
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org"
|
||||
"matrix.org",
|
||||
"gitter.im",
|
||||
"libera.chat"
|
||||
]
|
||||
},
|
||||
"showLabsSettings": false,
|
||||
"piwik": {
|
||||
"url": "https://piwik.riot.im/",
|
||||
"siteId": 1,
|
||||
"policyUrl": "https://matrix.org/legal/riot-im-cookie-policy"
|
||||
"policyUrl": "https://element.io/cookie-policy"
|
||||
},
|
||||
"enable_presence_by_hs_url": {
|
||||
"https://matrix.org": false,
|
||||
@@ -29,12 +33,17 @@
|
||||
},
|
||||
"terms_and_conditions_links": [
|
||||
{
|
||||
"url": "https://riot.im/privacy",
|
||||
"url": "https://element.io/privacy",
|
||||
"text": "Privacy Policy"
|
||||
},
|
||||
{
|
||||
"url": "https://matrix.org/legal/riot-im-cookie-policy",
|
||||
"url": "https://element.io/cookie-policy",
|
||||
"text": "Cookie Policy"
|
||||
}
|
||||
]
|
||||
],
|
||||
"posthog": {
|
||||
"projectApiKey": "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
|
||||
"apiHost": "https://posthog.element.io"
|
||||
},
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ License: Apache-2.0
|
||||
Vendor: support@element.io
|
||||
Architecture: amd64
|
||||
Maintainer: support@element.io
|
||||
Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libappindicator3-1, libsecret-1-0, libsqlcipher0
|
||||
Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libsecret-1-0, libsqlcipher0
|
||||
Recommends: libappindicator3-1
|
||||
Replaces: riot-desktop (<< 1.7.0), riot-web (<< 1.7.0)
|
||||
Breaks: riot-desktop (<< 1.7.0), riot-web (<< 1.7.0)
|
||||
Section: net
|
||||
|
||||
@@ -14,18 +14,17 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
await buildKeytar(hakEnv, moduleInfo);
|
||||
};
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
|
||||
async function buildKeytar(hakEnv, moduleInfo) {
|
||||
export default async function buildKeytar(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const env = hakEnv.makeGypEnv();
|
||||
|
||||
console.log("Running yarn with env", env);
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(moduleInfo.nodeModuleBinDir, 'node-gyp' + (hakEnv.isWin() ? '.cmd' : '')),
|
||||
['rebuild'],
|
||||
@@ -14,13 +14,16 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const childProcess = require('child_process');
|
||||
import childProcess from 'child_process';
|
||||
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const tools = [['python', '--version']]; // node-gyp uses python for reasons beyond comprehension
|
||||
|
||||
for (const tool of tools) {
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(tool[0], tool.slice(1), {
|
||||
stdio: ['ignore'],
|
||||
});
|
||||
@@ -33,4 +36,4 @@ module.exports = async function(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"scripts": {
|
||||
"check": "check.js",
|
||||
"build": "build.js"
|
||||
"check": "check.ts",
|
||||
"build": "build.ts"
|
||||
},
|
||||
"copy": "build/Release/keytar.node",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,13 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
import mkdirp from 'mkdirp';
|
||||
import fsExtra from 'fs-extra';
|
||||
|
||||
const mkdirp = require('mkdirp');
|
||||
const fsExtra = require('fs-extra');
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (hakEnv.isWin()) {
|
||||
await buildOpenSslWin(hakEnv, moduleInfo);
|
||||
await buildSqlCipherWin(hakEnv, moduleInfo);
|
||||
@@ -28,24 +30,24 @@ module.exports = async function(hakEnv, moduleInfo) {
|
||||
await buildSqlCipherUnix(hakEnv, moduleInfo);
|
||||
}
|
||||
await buildMatrixSeshat(hakEnv, moduleInfo);
|
||||
};
|
||||
}
|
||||
|
||||
async function buildOpenSslWin(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.openssl;
|
||||
const openSslDir = path.join(moduleInfo.moduleDotHakDir, `openssl-${version}`);
|
||||
const openSslDir = path.join(moduleInfo.moduleTargetDotHakDir, `openssl-${version}`);
|
||||
|
||||
const openSslArch = hakEnv.arch === 'x64' ? 'VC-WIN64A' : 'VC-WIN32';
|
||||
const openSslArch = hakEnv.getTargetArch() === 'x64' ? 'VC-WIN64A' : 'VC-WIN32';
|
||||
|
||||
console.log("Building openssl in " + openSslDir);
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'perl',
|
||||
[
|
||||
'Configure',
|
||||
'--prefix=' + moduleInfo.depPrefix,
|
||||
// sqlcipher only uses about a tiny part of openssl. We link statically
|
||||
// so will only pull in the symbols we use, but we may as well turn off
|
||||
// as much as possible to save on build time.
|
||||
// sqlcipher only uses about a tiny part of openssl. We link statically
|
||||
// so will only pull in the symbols we use, but we may as well turn off
|
||||
// as much as possible to save on build time.
|
||||
'no-afalgeng',
|
||||
'no-capieng',
|
||||
'no-cms',
|
||||
@@ -103,7 +105,7 @@ async function buildOpenSslWin(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['build_libs'],
|
||||
@@ -117,7 +119,7 @@ async function buildOpenSslWin(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['install_dev'],
|
||||
@@ -134,12 +136,12 @@ async function buildOpenSslWin(hakEnv, moduleInfo) {
|
||||
|
||||
async function buildSqlCipherWin(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.sqlcipher;
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleDotHakDir, `sqlcipher-${version}`);
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleTargetDotHakDir, `sqlcipher-${version}`);
|
||||
const buildDir = path.join(sqlCipherDir, 'bld');
|
||||
|
||||
await mkdirp(buildDir);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['/f', path.join('..', 'Makefile.msc'), 'libsqlite3.lib', 'TOP=..'],
|
||||
@@ -171,7 +173,7 @@ async function buildSqlCipherWin(hakEnv, moduleInfo) {
|
||||
|
||||
async function buildSqlCipherUnix(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.sqlcipher;
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleDotHakDir, `sqlcipher-${version}`);
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleTargetDotHakDir, `sqlcipher-${version}`);
|
||||
|
||||
const args = [
|
||||
'--prefix=' + moduleInfo.depPrefix + '',
|
||||
@@ -182,12 +184,39 @@ async function buildSqlCipherUnix(hakEnv, moduleInfo) {
|
||||
if (hakEnv.isMac()) {
|
||||
args.push('--with-crypto-lib=commoncrypto');
|
||||
}
|
||||
args.push('CFLAGS=-DSQLITE_HAS_CODEC');
|
||||
if (hakEnv.isMac()) {
|
||||
args.push('LDFLAGS=-framework Security -framework Foundation');
|
||||
|
||||
if (!hakEnv.isHost()) {
|
||||
// In the nonsense world of `configure`, it is assumed you are building
|
||||
// a compiler like `gcc`, so the `host` option actually means the target
|
||||
// the build output runs on.
|
||||
args.push(`--host=${hakEnv.getTargetId()}`);
|
||||
}
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const cflags = [
|
||||
'-DSQLITE_HAS_CODEC',
|
||||
];
|
||||
|
||||
if (!hakEnv.isHost()) {
|
||||
// `clang` uses more logical option naming.
|
||||
cflags.push(`--target=${hakEnv.getTargetId()}`);
|
||||
}
|
||||
|
||||
if (cflags.length) {
|
||||
args.push(`CFLAGS=${cflags.join(' ')}`);
|
||||
}
|
||||
|
||||
const ldflags = [];
|
||||
|
||||
if (hakEnv.isMac()) {
|
||||
ldflags.push('-framework Security');
|
||||
ldflags.push('-framework Foundation');
|
||||
}
|
||||
|
||||
if (ldflags.length) {
|
||||
args.push(`LDFLAGS=${ldflags.join(' ')}`);
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(sqlCipherDir, 'configure'),
|
||||
args,
|
||||
@@ -201,7 +230,7 @@ async function buildSqlCipherUnix(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'make',
|
||||
[],
|
||||
@@ -215,7 +244,7 @@ async function buildSqlCipherUnix(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'make',
|
||||
['install'],
|
||||
@@ -231,6 +260,9 @@ async function buildSqlCipherUnix(hakEnv, moduleInfo) {
|
||||
}
|
||||
|
||||
async function buildMatrixSeshat(hakEnv, moduleInfo) {
|
||||
// seshat now uses n-api so we shouldn't need to specify a node version to
|
||||
// build against, but it does seems to still need something in here, so leaving
|
||||
// it for now: we should confirm how much of this it still actually needs.
|
||||
const env = hakEnv.makeGypEnv();
|
||||
|
||||
if (!hakEnv.isLinux()) {
|
||||
@@ -248,11 +280,15 @@ async function buildMatrixSeshat(hakEnv, moduleInfo) {
|
||||
// the build scripts since they run on the host, but vcvarsall.bat sets the c
|
||||
// compiler in the path to be the one for the target, so we just use the matching
|
||||
// toolchain for the target architecture which makes everything happy.
|
||||
env.RUSTUP_TOOLCHAIN = hakEnv.arch == 'x64' ? 'stable-x86_64-pc-windows-msvc' : 'stable-i686-pc-windows-msvc';
|
||||
env.RUSTUP_TOOLCHAIN = `stable-${hakEnv.getTargetId()}`;
|
||||
}
|
||||
|
||||
if (!hakEnv.isHost()) {
|
||||
env.CARGO_BUILD_TARGET = hakEnv.getTargetId();
|
||||
}
|
||||
|
||||
console.log("Running neon with env", env);
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(moduleInfo.nodeModuleBinDir, 'neon' + (hakEnv.isWin() ? '.cmd' : '')),
|
||||
['build', '--release'],
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,12 +14,16 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const childProcess = require('child_process');
|
||||
import childProcess from 'child_process';
|
||||
import fsProm from 'fs/promises';
|
||||
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
// of course tcl doesn't have a --version
|
||||
if (!hakEnv.isLinux()) {
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn('tclsh', [], {
|
||||
stdio: ['pipe', 'ignore', 'ignore'],
|
||||
});
|
||||
@@ -34,7 +38,10 @@ module.exports = async function(hakEnv, moduleInfo) {
|
||||
});
|
||||
}
|
||||
|
||||
const tools = [['python', '--version']]; // node-gyp uses python for reasons beyond comprehension
|
||||
const tools = [
|
||||
['rustc', '--version'],
|
||||
['python', '--version'], // node-gyp uses python for reasons beyond comprehension
|
||||
];
|
||||
if (hakEnv.isWin()) {
|
||||
tools.push(['perl', '--version']); // for openssl configure
|
||||
tools.push(['patch', '--version']); // to patch sqlcipher Makefile.msc
|
||||
@@ -44,7 +51,7 @@ module.exports = async function(hakEnv, moduleInfo) {
|
||||
}
|
||||
|
||||
for (const tool of tools) {
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(tool[0], tool.slice(1), {
|
||||
stdio: ['ignore'],
|
||||
});
|
||||
@@ -57,4 +64,22 @@ module.exports = async function(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure Rust target exists (nb. we avoid depending on rustup)
|
||||
await new Promise((resolve, reject) => {
|
||||
const rustc = childProcess.execFile('rustc', [
|
||||
'--target', hakEnv.getTargetId(), '-o', 'tmp', '-',
|
||||
], (err, out) => {
|
||||
if (err) {
|
||||
reject(
|
||||
"rustc can't build for target " + hakEnv.getTargetId() +
|
||||
": ensure target is installed via `rustup target add " + hakEnv.getTargetId() + "` " +
|
||||
"or your package manager if not using `rustup`",
|
||||
);
|
||||
}
|
||||
fsProm.unlink('tmp').then(resolve);
|
||||
});
|
||||
rustc.stdin.write('fn main() {}');
|
||||
rustc.stdin.end();
|
||||
});
|
||||
}
|
||||
@@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
import fs from 'fs';
|
||||
import fsProm from 'fs/promises';
|
||||
import needle from 'needle';
|
||||
import tar from 'tar';
|
||||
|
||||
const fs = require('fs');
|
||||
const fsProm = require('fs').promises;
|
||||
const needle = require('needle');
|
||||
const tar = require('tar');
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (!hakEnv.isLinux()) {
|
||||
await getSqlCipher(hakEnv, moduleInfo);
|
||||
}
|
||||
@@ -30,11 +32,11 @@ module.exports = async function(hakEnv, moduleInfo) {
|
||||
if (hakEnv.isWin()) {
|
||||
await getOpenSsl(hakEnv, moduleInfo);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function getSqlCipher(hakEnv, moduleInfo) {
|
||||
async function getSqlCipher(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const version = moduleInfo.cfg.dependencies.sqlcipher;
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleDotHakDir, `sqlcipher-${version}`);
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleTargetDotHakDir, `sqlcipher-${version}`);
|
||||
|
||||
let haveSqlcipher;
|
||||
try {
|
||||
@@ -62,9 +64,10 @@ async function getSqlCipher(hakEnv, moduleInfo) {
|
||||
await bob;
|
||||
}
|
||||
|
||||
// Extract the tarball to per-target directories, then we avoid cross-contaiminating archs
|
||||
await tar.x({
|
||||
file: sqlCipherTarball,
|
||||
cwd: moduleInfo.moduleDotHakDir,
|
||||
cwd: moduleInfo.moduleTargetDotHakDir,
|
||||
});
|
||||
|
||||
if (hakEnv.isWin()) {
|
||||
@@ -73,8 +76,8 @@ async function getSqlCipher(hakEnv, moduleInfo) {
|
||||
// set it to 2 (default to memory).
|
||||
const patchFile = path.join(moduleInfo.moduleHakDir, `sqlcipher-${version}-win.patch`);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const readStream = fs.createReadStream(patchFile);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const readStream = fs.createReadStream(patchFile);
|
||||
|
||||
const proc = childProcess.spawn(
|
||||
'patch',
|
||||
@@ -92,9 +95,9 @@ async function getSqlCipher(hakEnv, moduleInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getOpenSsl(hakEnv, moduleInfo) {
|
||||
async function getOpenSsl(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const version = moduleInfo.cfg.dependencies.openssl;
|
||||
const openSslDir = path.join(moduleInfo.moduleDotHakDir, `openssl-${version}`);
|
||||
const openSslDir = path.join(moduleInfo.moduleTargetDotHakDir, `openssl-${version}`);
|
||||
|
||||
let haveOpenSsl;
|
||||
try {
|
||||
@@ -121,9 +124,9 @@ async function getOpenSsl(hakEnv, moduleInfo) {
|
||||
});
|
||||
}
|
||||
|
||||
console.log("extracting " + openSslTarball + " in " + moduleInfo.moduleDotHakDir);
|
||||
console.log("extracting " + openSslTarball + " in " + moduleInfo.moduleTargetDotHakDir);
|
||||
await tar.x({
|
||||
file: openSslTarball,
|
||||
cwd: moduleInfo.moduleDotHakDir,
|
||||
cwd: moduleInfo.moduleTargetDotHakDir,
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"scripts": {
|
||||
"check": "check.js",
|
||||
"fetchDeps": "fetchDeps.js",
|
||||
"build": "build.js"
|
||||
"check": "check.ts",
|
||||
"fetchDeps": "fetchDeps.ts",
|
||||
"build": "build.ts"
|
||||
},
|
||||
"prune": "native",
|
||||
"copy": "native/index.node",
|
||||
|
||||
17
hak/tsconfig.json
Normal file
17
hak/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2016",
|
||||
"sourceMap": false,
|
||||
"lib": [
|
||||
"es2019",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
86
package.json
86
package.json
@@ -1,77 +1,101 @@
|
||||
{
|
||||
"name": "element-desktop",
|
||||
"productName": "Element",
|
||||
"main": "src/electron-main.js",
|
||||
"version": "1.7.2",
|
||||
"main": "lib/electron-main.js",
|
||||
"version": "1.10.8",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "Element",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vector-im/riot-desktop"
|
||||
"url": "https://github.com/vector-im/element-desktop"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"files": [],
|
||||
"scripts": {
|
||||
"i18n": "matrix-gen-i18n",
|
||||
"prunei18n": "matrix-prune-i18n",
|
||||
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
||||
"mkdirs": "mkdirp packages deploys",
|
||||
"fetch": "yarn run mkdirs && node scripts/fetch-package.js",
|
||||
"asar-webapp": "asar p webapp webapp.asar",
|
||||
"start": "electron .",
|
||||
"lint": "eslint src/ scripts/ hak/",
|
||||
"start": "yarn run build:ts && yarn run build:res && electron .",
|
||||
"lint": "yarn lint:types && yarn lint:js",
|
||||
"lint:js": "eslint --max-warnings 0 src scripts hak",
|
||||
"lint:js-fix": "eslint --fix src scripts hak",
|
||||
"lint:types": "tsc --noEmit && tsc -p scripts/hak/tsconfig.json --noEmit && tsc -p hak/tsconfig.json --noEmit",
|
||||
"build:native": "yarn run hak",
|
||||
"build32": "electron-builder --ia32",
|
||||
"build64": "electron-builder --x64",
|
||||
"build": "electron-builder",
|
||||
"docker:setup": "docker build -t riot-desktop-dockerbuild dockerbuild",
|
||||
"build:native:universal": "yarn run hak --target x86_64-apple-darwin fetchandbuild && yarn run hak --target aarch64-apple-darwin fetchandbuild && yarn run hak --target x86_64-apple-darwin --target aarch64-apple-darwin copyandlink",
|
||||
"build:32": "yarn run build:ts && yarn run build:res && electron-builder --ia32",
|
||||
"build:64": "yarn run build:ts && yarn run build:res && electron-builder --x64",
|
||||
"build:universal": "yarn run build:ts && yarn run build:res && electron-builder --universal",
|
||||
"build": "yarn run build:ts && yarn run build:res && electron-builder",
|
||||
"build:ts": "tsc",
|
||||
"build:res": "node scripts/copy-res.js",
|
||||
"docker:setup": "docker build -t element-desktop-dockerbuild dockerbuild",
|
||||
"docker:build:native": "scripts/in-docker.sh yarn run hak",
|
||||
"docker:build": "scripts/in-docker.sh yarn run build",
|
||||
"docker:install": "scripts/in-docker.sh yarn install",
|
||||
"debrepo": "scripts/mkrepo.sh",
|
||||
"clean": "rimraf webapp.asar dist packages deploys",
|
||||
"hak": "node scripts/hak/index.js"
|
||||
"clean": "rimraf webapp.asar dist packages deploys lib",
|
||||
"hak": "ts-node scripts/hak/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"auto-launch": "^5.0.1",
|
||||
"electron-store": "^2.0.0",
|
||||
"electron-window-state": "^4.1.0",
|
||||
"minimist": "^1.2.3",
|
||||
"png-to-ico": "^1.0.2"
|
||||
"auto-launch": "^5.0.5",
|
||||
"counterpart": "^0.18.6",
|
||||
"electron-store": "^6.0.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"minimist": "^1.2.6",
|
||||
"png-to-ico": "^2.1.1",
|
||||
"request": "^2.88.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
"@types/counterpart": "^0.18.1",
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@types/mkdirp": "^1.0.2",
|
||||
"@types/pacote": "^11.1.1",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||
"@typescript-eslint/parser": "^5.6.0",
|
||||
"allchange": "^1.0.6",
|
||||
"app-builder-lib": "^22.14.10",
|
||||
"asar": "^2.0.1",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-builder-squirrel-windows": "^22.7.0",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-notarize": "^0.2.0",
|
||||
"eslint": "7.3.1",
|
||||
"eslint-config-google": "^0.7.1",
|
||||
"eslint-config-matrix-org": "^0.1.2",
|
||||
"eslint-plugin-babel": "^4.1.2",
|
||||
"chokidar": "^3.5.2",
|
||||
"electron": "^17",
|
||||
"electron-builder": "22.11.4",
|
||||
"electron-builder-squirrel-windows": "22.11.4",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"eslint": "7.18.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-matrix-org": "^0.4.0",
|
||||
"find-npm-prefix": "^1.0.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"glob": "^7.1.6",
|
||||
"matrix-js-sdk": "8.0.0",
|
||||
"matrix-web-i18n": "^1.2.0",
|
||||
"mkdirp": "^1.0.3",
|
||||
"needle": "^2.5.0",
|
||||
"node-pre-gyp": "^0.15.0",
|
||||
"npm": "^6.14.6",
|
||||
"pacote": "^11.3.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.1.3",
|
||||
"tar": "^6.0.1"
|
||||
"tar": "^6.1.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.5.3"
|
||||
},
|
||||
"hakDependencies": {
|
||||
"matrix-seshat": "^2.1.0",
|
||||
"matrix-seshat": "^2.3.0",
|
||||
"keytar": "^5.6.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "im.riot.app",
|
||||
"electronVersion": "9.0.5",
|
||||
"files": [
|
||||
"package.json",
|
||||
{
|
||||
"from": ".hak/hakModules",
|
||||
"to": "node_modules"
|
||||
},
|
||||
"src/**"
|
||||
"lib/**"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
|
||||
48
release.sh
48
release.sh
@@ -9,52 +9,4 @@ set -e
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
for i in matrix-js-sdk
|
||||
do
|
||||
echo "Checking version of $i..."
|
||||
depver=`cat package.json | jq -r .devDependencies[\"$i\"]`
|
||||
latestver=`yarn info -s $i dist-tags.next`
|
||||
if [ "$depver" != "$latestver" ]
|
||||
then
|
||||
echo "The latest version of $i is $latestver but package.json depends on $depver."
|
||||
echo -n "Type 'u' to auto-upgrade, 'c' to continue anyway, or 'a' to abort:"
|
||||
read resp
|
||||
if [ "$resp" != "u" ] && [ "$resp" != "c" ]
|
||||
then
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$resp" == "u" ]
|
||||
then
|
||||
echo "Upgrading $i to $latestver..."
|
||||
yarn add -ED $i@$latestver
|
||||
git add -u
|
||||
# The `-e` flag opens the editor and gives you a chance to check
|
||||
# the upgrade for correctness.
|
||||
git commit -m "Upgrade $i to $latestver" -e
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
./node_modules/matrix-js-sdk/release.sh -n -z "$@"
|
||||
|
||||
release="${1#v}"
|
||||
prerelease=0
|
||||
# We check if this build is a prerelease by looking to
|
||||
# see if the version has a hyphen in it. Crude,
|
||||
# but semver doesn't support postreleases so anything
|
||||
# with a hyphen is a prerelease.
|
||||
echo $release | grep -q '-' && prerelease=1
|
||||
|
||||
if [ $prerelease -eq 0 ]
|
||||
then
|
||||
# For a release, reset SDK deps back to the `develop` branch.
|
||||
for i in matrix-js-sdk
|
||||
do
|
||||
echo "Resetting $i to develop branch..."
|
||||
yarn add -D github:matrix-org/$i#develop
|
||||
git add -u
|
||||
git commit -m "Reset $i back to develop branch"
|
||||
done
|
||||
git push origin develop
|
||||
fi
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
signing_id: releases@riot.im
|
||||
subprojects:
|
||||
element-web:
|
||||
includeByDefault: true
|
||||
# Because element-web is not in our dependencies, but the versions
|
||||
# follow those of this project (well, vice-versa really)
|
||||
mirrorVersion: true
|
||||
|
||||
@@ -2,12 +2,4 @@
|
||||
|
||||
set -ex
|
||||
|
||||
scripts/fetchdep.sh matrix-org matrix-js-sdk
|
||||
|
||||
pushd matrix-js-sdk
|
||||
yarn link
|
||||
yarn install $@
|
||||
popd
|
||||
|
||||
yarn link matrix-js-sdk
|
||||
yarn install $@
|
||||
yarn install --pure-lockfile $@
|
||||
|
||||
121
scripts/copy-res.js
Executable file
121
scripts/copy-res.js
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// copies resources into the lib directory.
|
||||
|
||||
const parseArgs = require('minimist');
|
||||
const chokidar = require('chokidar');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {});
|
||||
|
||||
const watch = argv.w;
|
||||
const verbose = argv.v;
|
||||
|
||||
function errCheck(err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const I18N_BASE_PATH = "src/i18n/strings/";
|
||||
const INCLUDE_LANGS = fs.readdirSync(I18N_BASE_PATH).filter(fn => fn.endsWith(".json"));
|
||||
|
||||
// Ensure lib, lib/i18n and lib/i18n/strings all exist
|
||||
fs.mkdirSync('lib/i18n/strings', { recursive: true });
|
||||
|
||||
function genLangFile(file, dest) {
|
||||
let translations = {};
|
||||
[file].forEach(function(f) {
|
||||
if (fs.existsSync(f)) {
|
||||
try {
|
||||
Object.assign(
|
||||
translations,
|
||||
JSON.parse(fs.readFileSync(f).toString()),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Failed: " + f, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
translations = weblateToCounterpart(translations);
|
||||
|
||||
const json = JSON.stringify(translations, null, 4);
|
||||
const filename = path.basename(file);
|
||||
|
||||
fs.writeFileSync(dest + filename, json);
|
||||
if (verbose) {
|
||||
console.log("Generated language file: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert translation key from weblate format
|
||||
* (which only supports a single level) to counterpart
|
||||
* which requires object values for 'count' translations.
|
||||
*
|
||||
* eg.
|
||||
* "there are %(count)s badgers|one": "a badger",
|
||||
* "there are %(count)s badgers|other": "%(count)s badgers"
|
||||
* becomes
|
||||
* "there are %(count)s badgers": {
|
||||
* "one": "a badger",
|
||||
* "other": "%(count)s badgers"
|
||||
* }
|
||||
*/
|
||||
function weblateToCounterpart(inTrs) {
|
||||
const outTrs = {};
|
||||
|
||||
for (const key of Object.keys(inTrs)) {
|
||||
const keyParts = key.split('|', 2);
|
||||
if (keyParts.length === 2) {
|
||||
let obj = outTrs[keyParts[0]];
|
||||
if (obj === undefined) {
|
||||
obj = {};
|
||||
outTrs[keyParts[0]] = obj;
|
||||
}
|
||||
obj[keyParts[1]] = inTrs[key];
|
||||
} else {
|
||||
outTrs[key] = inTrs[key];
|
||||
}
|
||||
}
|
||||
|
||||
return outTrs;
|
||||
}
|
||||
|
||||
/*
|
||||
watch the input files for a given language,
|
||||
regenerate the file, and regenerating languages.json with the new filename
|
||||
*/
|
||||
function watchLanguage(file, dest) {
|
||||
// XXX: Use a debounce because for some reason if we read the language
|
||||
// file immediately after the FS event is received, the file contents
|
||||
// appears empty. Possibly https://github.com/nodejs/node/issues/6112
|
||||
let makeLangDebouncer;
|
||||
const makeLang = () => {
|
||||
if (makeLangDebouncer) {
|
||||
clearTimeout(makeLangDebouncer);
|
||||
}
|
||||
makeLangDebouncer = setTimeout(() => {
|
||||
genLangFile(file, dest);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
chokidar.watch(file)
|
||||
.on('add', makeLang)
|
||||
.on('change', makeLang)
|
||||
.on('error', errCheck);
|
||||
}
|
||||
|
||||
// language resources
|
||||
const I18N_DEST = "lib/i18n/strings/";
|
||||
INCLUDE_LANGS.forEach((file) => {
|
||||
genLangFile(I18N_BASE_PATH + file, I18N_DEST);
|
||||
}, {});
|
||||
|
||||
if (watch) {
|
||||
INCLUDE_LANGS.forEach(file => watchLanguage(I18N_BASE_PATH + file, I18N_DEST));
|
||||
}
|
||||
@@ -10,16 +10,15 @@ const asar = require('asar');
|
||||
const needle = require('needle');
|
||||
|
||||
const riotDesktopPackageJson = require('../package.json');
|
||||
const { setPackageVersion } = require('./set-version.js');
|
||||
|
||||
const PUB_KEY_URL = "https://packages.riot.im/riot-release-key.asc";
|
||||
const PACKAGE_URL_PREFIX = "https://github.com/vector-im/riot-web/releases/download/";
|
||||
const PUB_KEY_URL = "https://packages.riot.im/element-release-key.asc";
|
||||
const PACKAGE_URL_PREFIX = "https://github.com/vector-im/element-web/releases/download/";
|
||||
const ASAR_PATH = 'webapp.asar';
|
||||
|
||||
const {setPackageVersion, setDebVersion} = require('./set-version.js');
|
||||
|
||||
async function getLatestDevelopUrl(bkToken) {
|
||||
const buildsResult = await needle('get',
|
||||
"https://api.buildkite.com/v2/organizations/matrix-dot-org/pipelines/riot-web/builds",
|
||||
"https://api.buildkite.com/v2/organizations/matrix-dot-org/pipelines/element-web/builds",
|
||||
{
|
||||
branch: 'develop',
|
||||
state: 'passed',
|
||||
@@ -55,7 +54,7 @@ async function getLatestDevelopUrl(bkToken) {
|
||||
let dlUrl;
|
||||
let dlFilename;
|
||||
for (const artifact of artifactsResult.body) {
|
||||
if (artifact.filename && /^riot-.*\.tar.gz$/.test(artifact.filename)) {
|
||||
if (artifact.filename && /^element-.*\.tar.gz$/.test(artifact.filename)) {
|
||||
dlUrl = artifact.download_url;
|
||||
dlFilename = artifact.filename;
|
||||
break;
|
||||
@@ -149,7 +148,7 @@ async function main() {
|
||||
|
||||
if (targetVersion === undefined) {
|
||||
targetVersion = 'v' + riotDesktopPackageJson.version;
|
||||
filename = 'riot-' + targetVersion + '.tar.gz';
|
||||
filename = 'element-' + targetVersion + '.tar.gz';
|
||||
url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
|
||||
} else if (targetVersion === 'develop') {
|
||||
const buildKiteApiKey = process.env.BUILDKITE_API_KEY;
|
||||
@@ -164,7 +163,7 @@ async function main() {
|
||||
[filename, url] = await getLatestDevelopUrl(buildKiteApiKey);
|
||||
verify = false; // develop builds aren't signed
|
||||
} else {
|
||||
filename = 'riot-' + targetVersion + '.tar.gz';
|
||||
filename = 'element-' + targetVersion + '.tar.gz';
|
||||
url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
|
||||
setVersion = true;
|
||||
}
|
||||
@@ -283,7 +282,6 @@ async function main() {
|
||||
const semVer = targetVersion.slice(1);
|
||||
console.log("Updating version to " + semVer);
|
||||
await setPackageVersion(semVer);
|
||||
await setDebVersion(semVer);
|
||||
}
|
||||
|
||||
console.log("Done!");
|
||||
|
||||
@@ -17,7 +17,8 @@ clone() {
|
||||
if [ -n "$branch" ]
|
||||
then
|
||||
echo "Trying to use $org/$repo#$branch"
|
||||
git clone git://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
# Disable auth prompts: https://serverfault.com/a/665959
|
||||
GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
async function build(hakEnv, moduleInfo) {
|
||||
import { DependencyInfo } from "./dep";
|
||||
import HakEnv from "./hakEnv";
|
||||
|
||||
export default async function build(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
await moduleInfo.scripts.build(hakEnv, moduleInfo);
|
||||
}
|
||||
|
||||
module.exports = build;
|
||||
@@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
async function check(hakEnv, moduleInfo) {
|
||||
import { DependencyInfo } from "./dep";
|
||||
import HakEnv from "./hakEnv";
|
||||
|
||||
export default async function check(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (moduleInfo.scripts.check) {
|
||||
await moduleInfo.scripts.check(hakEnv, moduleInfo);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = check;
|
||||
@@ -14,13 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
|
||||
const rimraf = require('rimraf');
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from './hakEnv';
|
||||
|
||||
async function clean(hakEnv, moduleInfo) {
|
||||
await new Promise((resolve, reject) => {
|
||||
rimraf(moduleInfo.moduleDotHakDir, (err) => {
|
||||
export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(moduleInfo.moduleDotHakDir, (err: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -29,8 +31,8 @@ async function clean(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.dotHakDir, 'links', moduleInfo.name), (err) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.dotHakDir, 'links', moduleInfo.name), (err: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -39,8 +41,8 @@ async function clean(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.projectRoot, 'node_modules', moduleInfo.name), (err) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.projectRoot, 'node_modules', moduleInfo.name), (err: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -49,5 +51,3 @@ async function clean(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = clean;
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fsProm = require('fs').promises;
|
||||
|
||||
const rimraf = require('rimraf');
|
||||
const glob = require('glob');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
async function copy(hakEnv, moduleInfo) {
|
||||
if (moduleInfo.cfg.prune) {
|
||||
console.log("Removing " + moduleInfo.cfg.prune + " from " + moduleInfo.moduleOutDir);
|
||||
// rimraf doesn't have a 'cwd' option: it always uses process.cwd()
|
||||
// (and if you set glob.cwd it just breaks because it can't find the files)
|
||||
const oldCwd = process.cwd();
|
||||
try {
|
||||
process.chdir(moduleInfo.moduleOutDir);
|
||||
await new Promise((resolve, reject) => {
|
||||
rimraf(moduleInfo.cfg.prune, {}, err => {
|
||||
err ? reject(err) : resolve();
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
process.chdir(oldCwd);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleInfo.cfg.copy) {
|
||||
console.log(
|
||||
"Copying " + moduleInfo.cfg.prune + " from " +
|
||||
moduleInfo.moduleOutDir + " to " + moduleInfo.moduleOutDir,
|
||||
);
|
||||
const files = await new Promise(async (resolve, reject) => {
|
||||
glob(moduleInfo.cfg.copy, {
|
||||
nosort: true,
|
||||
silent: true,
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
}, (err, files) => {
|
||||
err ? reject(err) : resolve(files);
|
||||
});
|
||||
});
|
||||
for (const f of files) {
|
||||
console.log("\t" + f);
|
||||
const src = path.join(moduleInfo.moduleBuildDir, f);
|
||||
const dst = path.join(moduleInfo.moduleOutDir, f);
|
||||
|
||||
await mkdirp(path.dirname(dst));
|
||||
await fsProm.copyFile(src, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = copy;
|
||||
100
scripts/hak/copy.ts
Normal file
100
scripts/hak/copy.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
import rimraf from 'rimraf';
|
||||
import glob from 'glob';
|
||||
import mkdirp from 'mkdirp';
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
|
||||
export default async function copy(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (moduleInfo.cfg.prune) {
|
||||
console.log("Removing " + moduleInfo.cfg.prune + " from " + moduleInfo.moduleOutDir);
|
||||
// rimraf doesn't have a 'cwd' option: it always uses process.cwd()
|
||||
// (and if you set glob.cwd it just breaks because it can't find the files)
|
||||
const oldCwd = process.cwd();
|
||||
try {
|
||||
process.chdir(moduleInfo.moduleOutDir);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(moduleInfo.cfg.prune, {}, err => {
|
||||
err ? reject(err) : resolve();
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
process.chdir(oldCwd);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleInfo.cfg.copy) {
|
||||
// If there are multiple moduleBuildDirs, singular moduleBuildDir
|
||||
// is the same as moduleBuildDirs[0], so we're just listing the contents
|
||||
// of the first one.
|
||||
const files = await new Promise<string[]>((resolve, reject) => {
|
||||
glob(moduleInfo.cfg.copy, {
|
||||
nosort: true,
|
||||
silent: true,
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
}, (err, files) => {
|
||||
err ? reject(err) : resolve(files);
|
||||
});
|
||||
});
|
||||
|
||||
if (moduleInfo.moduleBuildDirs.length > 1) {
|
||||
if (!hakEnv.isMac()) {
|
||||
console.error(
|
||||
"You asked me to copy multiple targets but I've only been taught " +
|
||||
"how to do that on macOS.",
|
||||
);
|
||||
throw new Error("Can't copy multiple targets on this platform");
|
||||
}
|
||||
|
||||
for (const f of files) {
|
||||
const components = moduleInfo.moduleBuildDirs.map(dir => path.join(dir, f));
|
||||
const dst = path.join(moduleInfo.moduleOutDir, f);
|
||||
|
||||
await mkdirp(path.dirname(dst));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
childProcess.execFile('lipo',
|
||||
['-create', '-output', dst, ...components], (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"Copying files from " +
|
||||
moduleInfo.moduleBuildDir + " to " + moduleInfo.moduleOutDir,
|
||||
);
|
||||
for (const f of files) {
|
||||
console.log("\t" + f);
|
||||
const src = path.join(moduleInfo.moduleBuildDir, f);
|
||||
const dst = path.join(moduleInfo.moduleOutDir, f);
|
||||
|
||||
await mkdirp(path.dirname(dst));
|
||||
await fsProm.copyFile(src, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
scripts/hak/dep.ts
Normal file
32
scripts/hak/dep.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import HakEnv from "./hakEnv";
|
||||
|
||||
export interface DependencyInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
cfg: Record<string, any>;
|
||||
moduleHakDir: string;
|
||||
moduleDotHakDir: string;
|
||||
moduleTargetDotHakDir: string;
|
||||
moduleBuildDir: string;
|
||||
moduleBuildDirs: string[];
|
||||
moduleOutDir: string;
|
||||
nodeModuleBinDir: string;
|
||||
depPrefix: string;
|
||||
scripts: Record<string, (hakEnv: HakEnv, moduleInfo: DependencyInfo) => Promise<void> >;
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const fsProm = require('fs').promises;
|
||||
const childProcess = require('child_process');
|
||||
|
||||
const npm = require('npm');
|
||||
const semver = require('semver');
|
||||
const needle = require('needle');
|
||||
const mkdirp = require('mkdirp');
|
||||
const tar = require('tar');
|
||||
|
||||
async function fetch(hakEnv, moduleInfo) {
|
||||
let haveModuleBuildDir;
|
||||
try {
|
||||
const stats = await fsProm.stat(moduleInfo.moduleBuildDir);
|
||||
haveModuleBuildDir = stats.isDirectory();
|
||||
} catch (e) {
|
||||
haveModuleBuildDir = false;
|
||||
}
|
||||
|
||||
if (haveModuleBuildDir) return;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
npm.load({'loglevel': 'silent'}, resolve);
|
||||
});
|
||||
|
||||
console.log("Fetching " + moduleInfo.name + " at version " + moduleInfo.version);
|
||||
const versions = await new Promise((resolve, reject) => {
|
||||
npm.view([
|
||||
moduleInfo.name + '@' + moduleInfo.version,
|
||||
'dist.tarball',
|
||||
(err, versions) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(versions);
|
||||
}
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const orderedVersions = Object.keys(versions);
|
||||
semver.sort(orderedVersions);
|
||||
|
||||
console.log("Resolved version " + orderedVersions[0] + " for " + moduleInfo.name);
|
||||
|
||||
const tarballUrl = versions[orderedVersions[0]]['dist.tarball'];
|
||||
|
||||
await mkdirp(moduleInfo.moduleDotHakDir);
|
||||
|
||||
const parsedUrl = url.parse(tarballUrl);
|
||||
const tarballFile = path.join(moduleInfo.moduleDotHakDir, path.basename(parsedUrl.path));
|
||||
|
||||
let haveTarball;
|
||||
try {
|
||||
await fsProm.stat(tarballFile);
|
||||
haveTarball = true;
|
||||
} catch (e) {
|
||||
haveTarball = false;
|
||||
}
|
||||
if (!haveTarball) {
|
||||
console.log("Downloading " + tarballUrl);
|
||||
await needle('get', tarballUrl, { output: tarballFile });
|
||||
} else {
|
||||
console.log(tarballFile + " already exists.");
|
||||
}
|
||||
|
||||
await mkdirp(moduleInfo.moduleBuildDir);
|
||||
|
||||
await tar.x({
|
||||
file: tarballFile,
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
strip: 1,
|
||||
});
|
||||
|
||||
console.log("Running yarn install in " + moduleInfo.moduleBuildDir);
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
hakEnv.isWin() ? 'yarn.cmd' : 'yarn',
|
||||
['install', '--ignore-scripts'],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
},
|
||||
);
|
||||
proc.on('exit', code => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// also extract another copy to the output directory at this point
|
||||
// nb. we do not yarn install in the output copy: we could install in
|
||||
// production mode to get only runtime dependencies and not devDependencies,
|
||||
// but usually native modules come with dependencies that are needed for
|
||||
// building/fetching the native modules (eg. node-pre-gyp) rather than
|
||||
// actually used at runtime: we do not want to bundle these into our app.
|
||||
// We therefore just install no dependencies at all, and accept that any
|
||||
// actual runtime dependencies will have to be added to the main app's
|
||||
// dependencies. We can't tell what dependencies are real runtime deps
|
||||
// and which are just used for native module building.
|
||||
await mkdirp(moduleInfo.moduleOutDir);
|
||||
await tar.x({
|
||||
file: tarballFile,
|
||||
cwd: moduleInfo.moduleOutDir,
|
||||
strip: 1,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = fetch;
|
||||
70
scripts/hak/fetch.ts
Normal file
70
scripts/hak/fetch.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
import pacote from 'pacote';
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
|
||||
export default async function fetch(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
let haveModuleBuildDir;
|
||||
try {
|
||||
const stats = await fsProm.stat(moduleInfo.moduleBuildDir);
|
||||
haveModuleBuildDir = stats.isDirectory();
|
||||
} catch (e) {
|
||||
haveModuleBuildDir = false;
|
||||
}
|
||||
|
||||
if (haveModuleBuildDir) return;
|
||||
|
||||
console.log("Fetching " + moduleInfo.name + "@" + moduleInfo.version);
|
||||
|
||||
const packumentCache = new Map();
|
||||
await pacote.extract(`${moduleInfo.name}@${moduleInfo.version}`, moduleInfo.moduleBuildDir, {
|
||||
packumentCache,
|
||||
});
|
||||
|
||||
console.log("Running yarn install in " + moduleInfo.moduleBuildDir);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
hakEnv.isWin() ? 'yarn.cmd' : 'yarn',
|
||||
['install', '--ignore-scripts'],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
},
|
||||
);
|
||||
proc.on('exit', code => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// also extract another copy to the output directory at this point
|
||||
// nb. we do not yarn install in the output copy: we could install in
|
||||
// production mode to get only runtime dependencies and not devDependencies,
|
||||
// but usually native modules come with dependencies that are needed for
|
||||
// building/fetching the native modules (eg. node-pre-gyp) rather than
|
||||
// actually used at runtime: we do not want to bundle these into our app.
|
||||
// We therefore just install no dependencies at all, and accept that any
|
||||
// actual runtime dependencies will have to be added to the main app's
|
||||
// dependencies. We can't tell what dependencies are real runtime deps
|
||||
// and which are just used for native module building.
|
||||
await pacote.extract(`${moduleInfo.name}@${moduleInfo.version}`, moduleInfo.moduleOutDir, {
|
||||
packumentCache,
|
||||
});
|
||||
}
|
||||
@@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const mkdirp = require('mkdirp');
|
||||
import mkdirp from 'mkdirp';
|
||||
|
||||
async function fetchDeps(hakEnv, moduleInfo) {
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from './hakEnv';
|
||||
|
||||
export default async function fetchDeps(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
await mkdirp(moduleInfo.moduleDotHakDir);
|
||||
if (moduleInfo.scripts.fetchDeps) {
|
||||
await moduleInfo.scripts.fetchDeps(hakEnv, moduleInfo);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = fetchDeps;
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const nodePreGypVersioning = require('node-pre-gyp/lib/util/versioning');
|
||||
|
||||
function getElectronVersion(packageJson) {
|
||||
// should we pick the version of an installed electron
|
||||
// dependency, and if so, before or after electronVersion?
|
||||
if (packageJson.build && packageJson.build.electronVersion) {
|
||||
return packageJson.build.electronVersion;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRuntime(packageJson) {
|
||||
const electronVersion = getElectronVersion(packageJson);
|
||||
return electronVersion ? 'electron' : 'node-webkit';
|
||||
}
|
||||
|
||||
function getTarget(packageJson) {
|
||||
const electronVersion = getElectronVersion(packageJson);
|
||||
if (electronVersion) {
|
||||
return electronVersion;
|
||||
} else {
|
||||
return process.version.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
function detectArch() {
|
||||
if (process.platform === 'win32') {
|
||||
// vcvarsall.bat (the script that sets up the environment for
|
||||
// visual studio build tools) sets an env var to tell us what
|
||||
// architecture the active build tools target, so we auto-detect
|
||||
// this.
|
||||
const targetArch = process.env.VSCMD_ARG_TGT_ARCH;
|
||||
if (targetArch === 'x86') {
|
||||
return 'ia32';
|
||||
} else if (targetArch === 'x64') {
|
||||
return 'x64';
|
||||
}
|
||||
}
|
||||
return process.arch;
|
||||
}
|
||||
|
||||
module.exports = class HakEnv {
|
||||
constructor(prefix, packageJson) {
|
||||
Object.assign(this, {
|
||||
// what we're targeting
|
||||
runtime: getRuntime(packageJson),
|
||||
target: getTarget(packageJson),
|
||||
platform: process.platform,
|
||||
arch: detectArch(),
|
||||
|
||||
// paths
|
||||
projectRoot: prefix,
|
||||
dotHakDir: path.join(prefix, '.hak'),
|
||||
});
|
||||
}
|
||||
|
||||
getRuntimeAbi() {
|
||||
return nodePreGypVersioning.get_runtime_abi(
|
||||
this.runtime,
|
||||
this.target,
|
||||
);
|
||||
}
|
||||
|
||||
// {node_abi}-{platform}-{arch}
|
||||
getNodeTriple() {
|
||||
return this.getRuntimeAbi() + '-' + this.platform + '-' + this.arch;
|
||||
}
|
||||
|
||||
isWin() {
|
||||
return this.platform === 'win32';
|
||||
}
|
||||
|
||||
isMac() {
|
||||
return this.platform === 'darwin';
|
||||
}
|
||||
|
||||
isLinux() {
|
||||
return this.platform === 'linux';
|
||||
}
|
||||
|
||||
makeGypEnv() {
|
||||
return Object.assign({}, process.env, {
|
||||
npm_config_target: this.target,
|
||||
npm_config_arch: this.arch,
|
||||
npm_config_target_arch: this.arch,
|
||||
npm_config_disturl: 'https://atom.io/download/electron',
|
||||
npm_config_runtime: this.runtime,
|
||||
npm_config_build_from_source: true,
|
||||
npm_config_devdir: path.join(os.homedir(), ".electron-gyp"),
|
||||
});
|
||||
}
|
||||
|
||||
getNodeModuleBin(name) {
|
||||
return path.join(this.projectRoot, 'node_modules', '.bin', name);
|
||||
}
|
||||
};
|
||||
117
scripts/hak/hakEnv.ts
Normal file
117
scripts/hak/hakEnv.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import nodePreGypVersioning from "node-pre-gyp/lib/util/versioning";
|
||||
import { getElectronVersion } from "app-builder-lib/out/electron/electronVersion";
|
||||
|
||||
import { Arch, Target, TARGETS, getHost, isHostId, TargetId } from './target';
|
||||
|
||||
async function getRuntime(projectRoot: string): Promise<string> {
|
||||
const electronVersion = await getElectronVersion(projectRoot);
|
||||
return electronVersion ? 'electron' : 'node-webkit';
|
||||
}
|
||||
|
||||
async function getRuntimeVersion(projectRoot: string): Promise<string> {
|
||||
const electronVersion = await getElectronVersion(projectRoot);
|
||||
if (electronVersion) {
|
||||
return electronVersion;
|
||||
} else {
|
||||
return process.version.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
export default class HakEnv {
|
||||
public target: Target;
|
||||
public projectRoot: string;
|
||||
public runtime: string;
|
||||
public runtimeVersion: string;
|
||||
public dotHakDir: string;
|
||||
|
||||
constructor(prefix: string, targetId: TargetId) {
|
||||
let target;
|
||||
if (targetId) {
|
||||
target = TARGETS[targetId];
|
||||
} else {
|
||||
target = getHost();
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
throw new Error(`Unknown target ${targetId}!`);
|
||||
}
|
||||
this.target = target;
|
||||
this.projectRoot = prefix;
|
||||
this.dotHakDir = path.join(this.projectRoot, '.hak');
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.runtime = await getRuntime(this.projectRoot);
|
||||
this.runtimeVersion = await getRuntimeVersion(this.projectRoot);
|
||||
}
|
||||
|
||||
getRuntimeAbi(): string {
|
||||
return nodePreGypVersioning.get_runtime_abi(
|
||||
this.runtime,
|
||||
this.runtimeVersion,
|
||||
);
|
||||
}
|
||||
|
||||
// {node_abi}-{platform}-{arch}
|
||||
getNodeTriple(): string {
|
||||
return this.getRuntimeAbi() + '-' + this.target.platform + '-' + this.target.arch;
|
||||
}
|
||||
|
||||
getTargetId(): TargetId {
|
||||
return this.target.id;
|
||||
}
|
||||
|
||||
isWin(): boolean {
|
||||
return this.target.platform === 'win32';
|
||||
}
|
||||
|
||||
isMac(): boolean {
|
||||
return this.target.platform === 'darwin';
|
||||
}
|
||||
|
||||
isLinux(): boolean {
|
||||
return this.target.platform === 'linux';
|
||||
}
|
||||
|
||||
getTargetArch(): Arch {
|
||||
return this.target.arch;
|
||||
}
|
||||
|
||||
isHost(): boolean {
|
||||
return isHostId(this.target.id);
|
||||
}
|
||||
|
||||
makeGypEnv(): Record<string, string> {
|
||||
return Object.assign({}, process.env, {
|
||||
npm_config_arch: this.target.arch,
|
||||
npm_config_target_arch: this.target.arch,
|
||||
npm_config_disturl: 'https://atom.io/download/electron',
|
||||
npm_config_runtime: this.runtime,
|
||||
npm_config_target: this.runtimeVersion,
|
||||
npm_config_build_from_source: true,
|
||||
npm_config_devdir: path.join(os.homedir(), ".electron-gyp"),
|
||||
});
|
||||
}
|
||||
|
||||
getNodeModuleBin(name: string): string {
|
||||
return path.join(this.projectRoot, 'node_modules', '.bin', name);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
import path from 'path';
|
||||
import findNpmPrefix from 'find-npm-prefix';
|
||||
|
||||
const findNpmPrefix = require('find-npm-prefix');
|
||||
|
||||
const HakEnv = require('./hakEnv');
|
||||
import HakEnv from './hakEnv';
|
||||
import { TargetId } from './target';
|
||||
import { DependencyInfo } from './dep';
|
||||
|
||||
const GENERALCOMMANDS = [
|
||||
'target',
|
||||
@@ -35,6 +36,13 @@ const MODULECOMMANDS = [
|
||||
'clean',
|
||||
];
|
||||
|
||||
// Shortcuts for multiple commands at once (useful for building universal binaries
|
||||
// because you can run the fetch/fetchDeps/build for each arch and then copy/link once)
|
||||
const METACOMMANDS = {
|
||||
'fetchandbuild': ['check', 'fetch', 'fetchDeps', 'build'],
|
||||
'copyandlink': ['copy', 'link'],
|
||||
};
|
||||
|
||||
// Scripts valid in a hak.json 'scripts' section
|
||||
const HAKSCRIPTS = [
|
||||
'check',
|
||||
@@ -53,9 +61,30 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hakEnv = new HakEnv(prefix, packageJson);
|
||||
const targetIds = [] as TargetId[];
|
||||
// Apply `--target <target>` option if specified
|
||||
// Can be specified multiple times for the copy command to bundle
|
||||
// multiple archs into a single universal output module)
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const targetIndex = process.argv.indexOf('--target');
|
||||
if (targetIndex === -1) break;
|
||||
|
||||
const deps = {};
|
||||
if ((targetIndex + 1) >= process.argv.length) {
|
||||
console.error("--target option specified without a target");
|
||||
process.exit(1);
|
||||
}
|
||||
// Extract target ID and remove from args
|
||||
targetIds.push(process.argv.splice(targetIndex, 2)[1] as TargetId);
|
||||
}
|
||||
|
||||
const hakEnvs = targetIds.map(tid => new HakEnv(prefix, tid));
|
||||
if (hakEnvs.length == 0) hakEnvs.push(new HakEnv(prefix, null));
|
||||
for (const h of hakEnvs) {
|
||||
await h.init();
|
||||
}
|
||||
const hakEnv = hakEnvs[0];
|
||||
|
||||
const deps = {} as Record<string, DependencyInfo>;
|
||||
|
||||
const hakDepsCfg = packageJson.hakDependencies || {};
|
||||
|
||||
@@ -75,16 +104,23 @@ async function main() {
|
||||
cfg: hakJson,
|
||||
moduleHakDir: path.join(prefix, 'hak', dep),
|
||||
moduleDotHakDir: path.join(hakEnv.dotHakDir, dep),
|
||||
moduleBuildDir: path.join(hakEnv.dotHakDir, dep, 'build'),
|
||||
moduleTargetDotHakDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId()),
|
||||
moduleBuildDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'build'),
|
||||
moduleBuildDirs: hakEnvs.map(h => path.join(h.dotHakDir, dep, h.getTargetId(), 'build')),
|
||||
moduleOutDir: path.join(hakEnv.dotHakDir, 'hakModules', dep),
|
||||
nodeModuleBinDir: path.join(hakEnv.dotHakDir, dep, 'build', 'node_modules', '.bin'),
|
||||
depPrefix: path.join(hakEnv.dotHakDir, dep, 'opt'),
|
||||
nodeModuleBinDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'build', 'node_modules', '.bin'),
|
||||
depPrefix: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'opt'),
|
||||
scripts: {},
|
||||
};
|
||||
|
||||
for (const s of HAKSCRIPTS) {
|
||||
if (hakJson.scripts && hakJson.scripts[s]) {
|
||||
deps[dep].scripts[s] = require(path.join(prefix, 'hak', dep, hakJson.scripts[s]));
|
||||
const scriptModule = await import(path.join(prefix, 'hak', dep, hakJson.scripts[s]));
|
||||
if (scriptModule.__esModule) {
|
||||
deps[dep].scripts[s] = scriptModule.default;
|
||||
} else {
|
||||
deps[dep].scripts[s] = scriptModule;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,10 +128,18 @@ async function main() {
|
||||
let cmds;
|
||||
if (process.argv.length < 3) {
|
||||
cmds = ['check', 'fetch', 'fetchDeps', 'build', 'copy', 'link'];
|
||||
} else if (METACOMMANDS[process.argv[2]]) {
|
||||
cmds = METACOMMANDS[process.argv[2]];
|
||||
} else {
|
||||
cmds = [process.argv[2]];
|
||||
}
|
||||
|
||||
if (hakEnvs.length > 1 && cmds.some(c => !['copy', 'link'].includes(c))) {
|
||||
// We allow link here too for convenience because it's completely arch independent
|
||||
console.error("Multiple targets only supported with the copy command");
|
||||
return;
|
||||
}
|
||||
|
||||
let modules = process.argv.slice(3);
|
||||
if (modules.length === 0) modules = Object.keys(deps);
|
||||
|
||||
@@ -116,7 +160,7 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cmdFunc = require('./' + cmd);
|
||||
const cmdFunc = (await import('./' + cmd)).default;
|
||||
|
||||
for (const mod of modules) {
|
||||
const depInfo = deps[mod];
|
||||
@@ -133,4 +177,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(() => process.exit(1));
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -14,12 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fsProm = require('fs').promises;
|
||||
const childProcess = require('child_process');
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
|
||||
async function link(hakEnv, moduleInfo) {
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
|
||||
export default async function link(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const yarnrc = path.join(hakEnv.projectRoot, '.yarnrc');
|
||||
// this is fairly terrible but it's reasonably clunky to either parse a yarnrc
|
||||
// properly or get yarn to do it, so this will probably suffice for now.
|
||||
@@ -46,7 +49,7 @@ async function link(hakEnv, moduleInfo) {
|
||||
|
||||
const yarnCmd = 'yarn' + (hakEnv.isWin() ? '.cmd' : '');
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(yarnCmd, ['link'], {
|
||||
cwd: moduleInfo.moduleOutDir,
|
||||
stdio: 'inherit',
|
||||
@@ -56,7 +59,7 @@ async function link(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(yarnCmd, ['link', moduleInfo.name], {
|
||||
cwd: hakEnv.projectRoot,
|
||||
stdio: 'inherit',
|
||||
@@ -66,5 +69,3 @@ async function link(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = link;
|
||||
126
scripts/hak/target.ts
Normal file
126
scripts/hak/target.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// We borrow Rust's target naming scheme as a way of expressing all target
|
||||
// details in a single string.
|
||||
// See https://doc.rust-lang.org/rustc/platform-support.html.
|
||||
export type TargetId =
|
||||
'aarch64-apple-darwin' |
|
||||
'x86_64-apple-darwin' |
|
||||
'universal-apple-darwin' |
|
||||
'i686-pc-windows-msvc' |
|
||||
'x86_64-pc-windows-msvc' |
|
||||
'x86_64-unknown-linux-gnu';
|
||||
|
||||
// Values are expected to match those used in `process.platform`.
|
||||
export type Platform = 'darwin' | 'linux' | 'win32';
|
||||
|
||||
// Values are expected to match those used in `process.arch`.
|
||||
export type Arch = 'arm64' | 'ia32' | 'x64' | 'universal';
|
||||
|
||||
// Values are expected to match those used by Visual Studio's `vcvarsall.bat`.
|
||||
// See https://docs.microsoft.com/cpp/build/building-on-the-command-line?view=msvc-160#vcvarsall-syntax
|
||||
export type VcVarsArch = 'amd64' | 'arm64' | 'x86';
|
||||
|
||||
export type Target = {
|
||||
id: TargetId;
|
||||
platform: Platform;
|
||||
arch: Arch;
|
||||
};
|
||||
|
||||
export type WindowsTarget = Target & {
|
||||
platform: 'win32';
|
||||
vcVarsArch: VcVarsArch;
|
||||
};
|
||||
|
||||
export type UniversalTarget = Target & {
|
||||
arch: 'universal';
|
||||
subtargets: Target[];
|
||||
};
|
||||
|
||||
const aarch64AppleDarwin: Target = {
|
||||
id: 'aarch64-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'arm64',
|
||||
};
|
||||
|
||||
const x8664AppleDarwin: Target = {
|
||||
id: 'x86_64-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'x64',
|
||||
};
|
||||
|
||||
const universalAppleDarwin: UniversalTarget = {
|
||||
id: 'universal-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'universal',
|
||||
subtargets: [
|
||||
aarch64AppleDarwin,
|
||||
x8664AppleDarwin,
|
||||
],
|
||||
};
|
||||
|
||||
const i686PcWindowsMsvc: WindowsTarget = {
|
||||
id: 'i686-pc-windows-msvc',
|
||||
platform: 'win32',
|
||||
arch: 'ia32',
|
||||
vcVarsArch: 'x86',
|
||||
};
|
||||
|
||||
const x8664PcWindowsMsvc: WindowsTarget = {
|
||||
id: 'x86_64-pc-windows-msvc',
|
||||
platform: 'win32',
|
||||
arch: 'x64',
|
||||
vcVarsArch: 'amd64',
|
||||
};
|
||||
|
||||
const x8664UnknownLinuxGnu: Target = {
|
||||
id: 'x86_64-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
};
|
||||
|
||||
export const TARGETS: Record<TargetId, Target> = {
|
||||
'aarch64-apple-darwin': aarch64AppleDarwin,
|
||||
'x86_64-apple-darwin': x8664AppleDarwin,
|
||||
'universal-apple-darwin': universalAppleDarwin,
|
||||
'i686-pc-windows-msvc': i686PcWindowsMsvc,
|
||||
'x86_64-pc-windows-msvc': x8664PcWindowsMsvc,
|
||||
'x86_64-unknown-linux-gnu': x8664UnknownLinuxGnu,
|
||||
};
|
||||
|
||||
// The set of targets we build by default, sorted by increasing complexity so
|
||||
// that we fail fast when the native host target fails.
|
||||
export const ENABLED_TARGETS: Target[] = [
|
||||
TARGETS['universal-apple-darwin'],
|
||||
TARGETS['x86_64-unknown-linux-gnu'],
|
||||
TARGETS['x86_64-pc-windows-msvc'],
|
||||
];
|
||||
|
||||
export function getHost(): Target {
|
||||
return Object.values(TARGETS).find(target => (
|
||||
target.platform === process.platform &&
|
||||
target.arch === process.arch
|
||||
));
|
||||
}
|
||||
|
||||
export function isHostId(id: TargetId): boolean {
|
||||
return getHost()?.id === id;
|
||||
}
|
||||
|
||||
export function isHost(target: Target): boolean {
|
||||
return getHost()?.id === target.id;
|
||||
}
|
||||
18
scripts/hak/tsconfig.json
Normal file
18
scripts/hak/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"sourceMap": false,
|
||||
"lib": [
|
||||
"es2019",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker inspect riot-desktop-dockerbuild 2> /dev/null > /dev/null
|
||||
docker inspect element-desktop-dockerbuild 2> /dev/null > /dev/null
|
||||
if [ $? != 0 ]; then
|
||||
echo "Docker image riot-desktop-dockerbuild not found. Have you run yarn run docker:setup?"
|
||||
echo "Docker image element-desktop-dockerbuild not found. Have you run yarn run docker:setup?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Taken from https://www.electron.build/multi-platform-build#docker
|
||||
# Pass through any vars prefixed with INDOCKER_, removing the prefix
|
||||
docker run --rm -ti \
|
||||
--env-file <(env | grep -iE '^BUILDKITE_API_KEY=') \
|
||||
--env-file <(env | grep -E '^INDOCKER_' | sed -e 's/^INDOCKER_//') \
|
||||
--env ELECTRON_CACHE="/root/.cache/electron" \
|
||||
--env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \
|
||||
-v ${PWD}:/project \
|
||||
@@ -17,4 +18,4 @@ docker run --rm -ti \
|
||||
-v ${PWD}/docker/.gnupg:/root/.gnupg \
|
||||
-v ~/.cache/electron:/root/.cache/electron \
|
||||
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
||||
riot-desktop-dockerbuild "$@"
|
||||
element-desktop-dockerbuild "$@"
|
||||
|
||||
@@ -22,10 +22,10 @@ cp $confdir/conf_distributions $repodir/conf/distributions
|
||||
pushd $repodir
|
||||
for i in `cat conf/distributions | grep Codename | cut -d ' ' -f 2`
|
||||
do
|
||||
reprepro includedeb $i $distdir/riot-desktop_${ver}_amd64.deb
|
||||
reprepro includedeb $i $distdir/element-desktop_${ver}_amd64.deb
|
||||
done
|
||||
|
||||
tar cvzf $distdir/riot-desktop_repo_$ver.tar.gz .
|
||||
tar cvzf $distdir/element-desktop_repo_$ver.tar.gz .
|
||||
|
||||
popd
|
||||
|
||||
|
||||
@@ -52,4 +52,4 @@ if (require.main === module) {
|
||||
main(process.argv.slice(2)).then((ret) => process.exit(ret));
|
||||
}
|
||||
|
||||
module.exports = {versionFromAsar, setPackageVersion};
|
||||
module.exports = { versionFromAsar, setPackageVersion };
|
||||
|
||||
14
src/preload.js → src/@types/global.d.ts
vendored
14
src/preload.js → src/@types/global.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
Copyright 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
import { BrowserWindow } from "electron";
|
||||
|
||||
// expose ipcRenderer to the renderer process
|
||||
window.ipcRenderer = ipcRenderer;
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
mainWindow: BrowserWindow;
|
||||
appQuitting: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
Copyright 2017, 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2018 - 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -18,35 +17,42 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Squirrel on windows starts the app with various flags
|
||||
// as hooks to tell us when we've been installed/uninstalled
|
||||
// etc.
|
||||
const checkSquirrelHooks = require('./squirrelhooks');
|
||||
if (checkSquirrelHooks()) return;
|
||||
// Squirrel on windows starts the app with various flags as hooks to tell us when we've been installed/uninstalled etc.
|
||||
import "./squirrelhooks";
|
||||
import {
|
||||
app,
|
||||
ipcMain,
|
||||
powerSaveBlocker,
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
autoUpdater,
|
||||
protocol,
|
||||
dialog,
|
||||
desktopCapturer,
|
||||
} from "electron";
|
||||
import AutoLaunch from "auto-launch";
|
||||
import path from "path";
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
import Store from 'electron-store';
|
||||
import fs, { promises as afs } from "fs";
|
||||
import crypto from "crypto";
|
||||
import { URL } from "url";
|
||||
import minimist from "minimist";
|
||||
|
||||
const argv = require('minimist')(process.argv, {
|
||||
alias: {help: "h"},
|
||||
import * as tray from "./tray";
|
||||
import { buildMenuTemplate } from './vectormenu';
|
||||
import webContentsHandler from './webcontents-handler';
|
||||
import * as updater from './updater';
|
||||
import { getProfileFromDeeplink, protocolInit, recordSSOSession } from './protocol';
|
||||
import { _t, AppLocalization } from './language-helper';
|
||||
|
||||
const argv = minimist(process.argv, {
|
||||
alias: { help: "h" },
|
||||
});
|
||||
|
||||
const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol} = require('electron');
|
||||
const AutoLaunch = require('auto-launch');
|
||||
const path = require('path');
|
||||
|
||||
const tray = require('./tray');
|
||||
const vectorMenu = require('./vectormenu');
|
||||
const webContentsHandler = require('./webcontents-handler');
|
||||
const updater = require('./updater');
|
||||
const {getProfileFromDeeplink, protocolInit, recordSSOSession} = require('./protocol');
|
||||
|
||||
const windowStateKeeper = require('electron-window-state');
|
||||
const Store = require('electron-store');
|
||||
|
||||
const fs = require('fs');
|
||||
const afs = fs.promises;
|
||||
|
||||
const crypto = require('crypto');
|
||||
let keytar;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require('keytar');
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
@@ -61,9 +67,8 @@ let Seshat;
|
||||
let SeshatRecovery;
|
||||
let ReindexError;
|
||||
|
||||
const seshatPassphrase = "DEFAULT_PASSPHRASE";
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const seshatModule = require('matrix-seshat');
|
||||
Seshat = seshatModule.Seshat;
|
||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||
@@ -85,6 +90,7 @@ let vectorConfig;
|
||||
let iconPath;
|
||||
let trayConfig;
|
||||
let launcher;
|
||||
let appLocalization;
|
||||
|
||||
if (argv["help"]) {
|
||||
console.log("Options:");
|
||||
@@ -179,10 +185,11 @@ async function setupGlobals() {
|
||||
]);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
vectorConfig = require(asarPath + 'config.json');
|
||||
} catch (e) {
|
||||
// it would be nice to check the error code here and bail if the config
|
||||
// is unparseable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||
// file or invalid json, so node is just very unhelpful.
|
||||
// Continue with the defaults (ie. an empty config)
|
||||
vectorConfig = {};
|
||||
@@ -190,6 +197,7 @@ async function setupGlobals() {
|
||||
|
||||
try {
|
||||
// Load local config and use it to override values from the one baked with the build
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const localConfig = require(path.join(app.getPath('userData'), 'config.json'));
|
||||
|
||||
// If the local config has a homeserver defined, don't use the homeserver from the build
|
||||
@@ -205,6 +213,16 @@ async function setupGlobals() {
|
||||
|
||||
vectorConfig = Object.assign(vectorConfig, localConfig);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: `Your ${vectorConfig.brand || 'Element'} is misconfigured`,
|
||||
message: `Your custom ${vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${vectorConfig.brand || 'Element'}.`,
|
||||
detail: e.message || "",
|
||||
});
|
||||
}
|
||||
|
||||
// Could not load local config, this is expected in most cases.
|
||||
}
|
||||
|
||||
@@ -247,13 +265,44 @@ async function moveAutoLauncher() {
|
||||
}
|
||||
|
||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
||||
const store = new Store({ name: "electron-config" });
|
||||
const store = new Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
}>({ name: "electron-config" });
|
||||
|
||||
let eventIndex = null;
|
||||
|
||||
let mainWindow = null;
|
||||
global.appQuitting = false;
|
||||
|
||||
const exitShortcuts = [
|
||||
(input, platform) => platform !== 'darwin' && input.alt && input.key.toUpperCase() === 'F4',
|
||||
(input, platform) => platform !== 'darwin' && input.control && input.key.toUpperCase() === 'Q',
|
||||
(input, platform) => platform === 'darwin' && input.meta && input.key.toUpperCase() === 'Q',
|
||||
];
|
||||
|
||||
const warnBeforeExit = (event, input) => {
|
||||
const shouldWarnBeforeExit = store.get('warnBeforeExit', true);
|
||||
const exitShortcutPressed =
|
||||
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(mainWindow, {
|
||||
type: "question",
|
||||
buttons: [_t("Cancel"), _t("Close Element")],
|
||||
message: _t("Are you sure you want to quit?"),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
}) === 0;
|
||||
|
||||
if (shouldCancelCloseRequest) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteContents = async (p) => {
|
||||
for (const entry of await afs.readdir(p)) {
|
||||
@@ -262,6 +311,18 @@ const deleteContents = async (p) => {
|
||||
}
|
||||
};
|
||||
|
||||
async function randomArray(size) {
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(size, (err, buf) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// handle uncaught errors otherwise it displays
|
||||
// stack traces in popup dialogs, which is terrible (which
|
||||
// it will do any time the auto update poke fails, and there's
|
||||
@@ -274,7 +335,12 @@ process.on('uncaughtException', function(error) {
|
||||
|
||||
let focusHandlerAttached = false;
|
||||
ipcMain.on('setBadgeCount', function(ev, count) {
|
||||
app.badgeCount = count;
|
||||
if (process.platform !== 'win32') {
|
||||
// only set badgeCount on Mac/Linux, the docs say that only those platforms support it but turns out Electron
|
||||
// has some Windows support too, and in some Windows environments this leads to two badges rendering atop
|
||||
// each other. See https://github.com/vector-im/element-web/issues/16942
|
||||
app.badgeCount = count;
|
||||
}
|
||||
if (count === 0 && mainWindow) {
|
||||
mainWindow.flashFrame(false);
|
||||
}
|
||||
@@ -329,6 +395,15 @@ ipcMain.on('ipcCall', async function(ev, payload) {
|
||||
launcher.disable();
|
||||
}
|
||||
break;
|
||||
case 'setLanguage':
|
||||
appLocalization.setAppLocale(args[0]);
|
||||
break;
|
||||
case 'shouldWarnBeforeExit':
|
||||
ret = store.get('warnBeforeExit', true);
|
||||
break;
|
||||
case 'setWarnBeforeExit':
|
||||
store.set('warnBeforeExit', args[0]);
|
||||
break;
|
||||
case 'getMinimizeToTrayEnabled':
|
||||
ret = tray.hasTray();
|
||||
break;
|
||||
@@ -374,50 +449,32 @@ ipcMain.on('ipcCall', async function(ev, payload) {
|
||||
mainWindow.webContents.goForward();
|
||||
}
|
||||
break;
|
||||
case 'setLanguage': {
|
||||
// work around `setSpellCheckerLanguages` being case-sensitive by converting to expected case
|
||||
const caseMap = {};
|
||||
const availableLanguages = mainWindow.webContents.session.availableSpellCheckerLanguages;
|
||||
availableLanguages.forEach(lang => {
|
||||
caseMap[lang.toLowerCase()] = lang;
|
||||
});
|
||||
case 'setSpellCheckLanguages':
|
||||
if (args[0] && args[0].length > 0) {
|
||||
mainWindow.webContents.session.setSpellCheckerEnabled(true);
|
||||
store.set("spellCheckerEnabled", true);
|
||||
|
||||
if (!caseMap["en"]) {
|
||||
// default special-case for `en` as in Riot is actually implies `en-GB`. `en-US` is distinct.
|
||||
// this way if `en` is requested and not available and `en-GB` is available it'll be used.
|
||||
caseMap["en"] = caseMap["en-gb"];
|
||||
}
|
||||
|
||||
const languages = new Set();
|
||||
args[0].forEach(lang => {
|
||||
const lcLang = lang.toLowerCase();
|
||||
if (caseMap[lcLang]) {
|
||||
languages.add(caseMap[lcLang]);
|
||||
return;
|
||||
try {
|
||||
mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
||||
} catch (er) {
|
||||
console.log("There were problems setting the spellcheck languages", er);
|
||||
}
|
||||
|
||||
// as a fallback if the language is unknown check if the language group is known, e.g en for en-AU
|
||||
const langGroup = lcLang.split("-")[0];
|
||||
if (caseMap[langGroup]) {
|
||||
languages.add(caseMap[langGroup]);
|
||||
return;
|
||||
}
|
||||
|
||||
// as a further fallback, pick all other matching variants from the same language group
|
||||
// this means that if we cannot find `ar-dz` or `ar` for example, we will pick `ar-*` to
|
||||
// offer a spellcheck which is least likely to wrongly red underline something.
|
||||
availableLanguages.forEach(availableLang => {
|
||||
if (availableLang.startsWith(langGroup)) {
|
||||
languages.add(availableLang);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (languages.size > 0) {
|
||||
mainWindow.webContents.session.setSpellCheckerLanguages([...languages]);
|
||||
} else {
|
||||
mainWindow.webContents.session.setSpellCheckerEnabled(false);
|
||||
store.set("spellCheckerEnabled", false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'getSpellCheckLanguages':
|
||||
if (store.get("spellCheckerEnabled", true)) {
|
||||
ret = mainWindow.webContents.session.getSpellCheckerLanguages();
|
||||
} else {
|
||||
ret = [];
|
||||
}
|
||||
break;
|
||||
case 'getAvailableSpellCheckLanguages':
|
||||
ret = mainWindow.webContents.session.availableSpellCheckerLanguages;
|
||||
break;
|
||||
|
||||
case 'startSSOFlow':
|
||||
recordSSOSession(args[0]);
|
||||
break;
|
||||
@@ -439,16 +496,7 @@ ipcMain.on('ipcCall', async function(ev, payload) {
|
||||
|
||||
case 'createPickleKey':
|
||||
try {
|
||||
const randomArray = await new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(32, (err, buf) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(buf);
|
||||
}
|
||||
});
|
||||
});
|
||||
const pickleKey = randomArray.toString("base64").replace(/=+$/g, '');
|
||||
const pickleKey = await randomArray(32);
|
||||
await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch (e) {
|
||||
@@ -464,6 +512,13 @@ ipcMain.on('ipcCall', async function(ev, payload) {
|
||||
await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
} catch (e) {}
|
||||
break;
|
||||
case 'getDesktopCapturerSources':
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
thumbnailURL: source.thumbnail.toDataURL(),
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
mainWindow.webContents.send('ipcReply', {
|
||||
@@ -479,6 +534,26 @@ ipcMain.on('ipcCall', async function(ev, payload) {
|
||||
});
|
||||
});
|
||||
|
||||
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
||||
async function getOrCreatePassphrase(key) {
|
||||
if (keytar) {
|
||||
try {
|
||||
const storedPassphrase = await keytar.getPassword("element.io", key);
|
||||
if (storedPassphrase !== null) {
|
||||
return storedPassphrase;
|
||||
} else {
|
||||
const newPassphrase = await randomArray(32);
|
||||
await keytar.setPassword("element.io", key, newPassphrase);
|
||||
return newPassphrase;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error getting the event index passphrase out of the secret store", e);
|
||||
}
|
||||
} else {
|
||||
return seshatDefaultPassphrase;
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.on('seshat', async function(ev, payload) {
|
||||
if (!mainWindow) return;
|
||||
|
||||
@@ -503,42 +578,41 @@ ipcMain.on('seshat', async function(ev, payload) {
|
||||
|
||||
case 'initEventIndex':
|
||||
if (eventIndex === null) {
|
||||
const userId = args[0];
|
||||
const deviceId = args[1];
|
||||
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
||||
|
||||
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
||||
|
||||
try {
|
||||
await afs.mkdir(eventStorePath, {recursive: true});
|
||||
eventIndex = new Seshat(eventStorePath, {passphrase: seshatPassphrase});
|
||||
await afs.mkdir(eventStorePath, { recursive: true });
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} catch (e) {
|
||||
if (e instanceof ReindexError) {
|
||||
// If this is a reindex error, the index schema
|
||||
// changed. Try to open the database in recovery mode,
|
||||
// reindex the database and finally try to open the
|
||||
// database again.
|
||||
try {
|
||||
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
||||
passphrase: seshatPassphrase,
|
||||
});
|
||||
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
||||
passphrase,
|
||||
});
|
||||
|
||||
const userVersion = await recoveryIndex.getUserVersion();
|
||||
const userVersion = await recoveryIndex.getUserVersion();
|
||||
|
||||
// If our user version is 0 we'll delete the db
|
||||
// anyways so reindexing it is a waste of time.
|
||||
if (userVersion === 0) {
|
||||
await recoveryIndex.shutdown();
|
||||
// If our user version is 0 we'll delete the db
|
||||
// anyways so reindexing it is a waste of time.
|
||||
if (userVersion === 0) {
|
||||
await recoveryIndex.shutdown();
|
||||
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
} else {
|
||||
await recoveryIndex.reindex();
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
eventIndex = new Seshat(eventStorePath, {
|
||||
passphrase: seshatPassphrase,
|
||||
});
|
||||
} catch (e) {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
} else {
|
||||
await recoveryIndex.reindex();
|
||||
}
|
||||
|
||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||
} else {
|
||||
sendError(payload.id, e);
|
||||
return;
|
||||
@@ -728,6 +802,9 @@ ipcMain.on('seshat', async function(ev, payload) {
|
||||
});
|
||||
|
||||
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
||||
if (!app.commandLine.hasSwitch('enable-features')) {
|
||||
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
||||
}
|
||||
|
||||
const gotLock = app.requestSingleInstanceLock();
|
||||
if (!gotLock) {
|
||||
@@ -763,6 +840,9 @@ protocol.registerSchemesAsPrivileged([{
|
||||
// transition into the user's browser.
|
||||
app.enableSandbox();
|
||||
|
||||
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
|
||||
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
||||
|
||||
app.on('ready', async () => {
|
||||
try {
|
||||
await setupGlobals();
|
||||
@@ -779,6 +859,7 @@ app.on('ready', async () => {
|
||||
|
||||
if (argv['devtools']) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require('electron-devtools-installer');
|
||||
installExt(REACT_DEVELOPER_TOOLS)
|
||||
.then((name) => console.log(`Added Extension: ${name}`))
|
||||
@@ -793,17 +874,17 @@ app.on('ready', async () => {
|
||||
|
||||
protocol.registerFileProtocol('vector', (request, callback) => {
|
||||
if (request.method !== 'GET') {
|
||||
callback({error: -322}); // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h
|
||||
callback({ error: -322 }); // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(request.url);
|
||||
if (parsedUrl.protocol !== 'vector:') {
|
||||
callback({error: -302}); // UNKNOWN_URL_SCHEME
|
||||
callback({ error: -302 }); // UNKNOWN_URL_SCHEME
|
||||
return;
|
||||
}
|
||||
if (parsedUrl.host !== 'vector') {
|
||||
callback({error: -105}); // NAME_NOT_RESOLVED
|
||||
callback({ error: -105 }); // NAME_NOT_RESOLVED
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -811,7 +892,7 @@ app.on('ready', async () => {
|
||||
|
||||
// path starts with a '/'
|
||||
if (target[0] !== '') {
|
||||
callback({error: -6}); // FILE_NOT_FOUND
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -823,7 +904,7 @@ app.on('ready', async () => {
|
||||
if (target[1] === 'webapp') {
|
||||
baseDir = asarPath;
|
||||
} else {
|
||||
callback({error: -6}); // FILE_NOT_FOUND
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -833,7 +914,7 @@ app.on('ready', async () => {
|
||||
|
||||
const relTarget = path.normalize(path.join(...target.slice(2)));
|
||||
if (relTarget.startsWith('..')) {
|
||||
callback({error: -6}); // FILE_NOT_FOUND
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
return;
|
||||
}
|
||||
const absTarget = path.join(baseDir, relTarget);
|
||||
@@ -875,18 +956,15 @@ app.on('ready', async () => {
|
||||
preload: preloadScript,
|
||||
nodeIntegration: false,
|
||||
//sandbox: true, // We enable sandboxing from app.enableSandbox() above
|
||||
enableRemoteModule: false,
|
||||
// We don't use this: it's useful for the preload script to
|
||||
// share a context with the main page so we can give select
|
||||
// objects to the main page. The sandbox option isolates the
|
||||
// main page from the background script.
|
||||
contextIsolation: false,
|
||||
webgl: false,
|
||||
spellcheck: true,
|
||||
contextIsolation: true,
|
||||
webgl: true,
|
||||
},
|
||||
});
|
||||
mainWindow.loadURL('vector://vector/webapp/');
|
||||
Menu.setApplicationMenu(vectorMenu);
|
||||
|
||||
// Handle spellchecker
|
||||
// For some reason spellCheckerEnabled isn't persisted so we have to use the store here
|
||||
mainWindow.webContents.session.setSpellCheckerEnabled(store.get("spellCheckerEnabled", true));
|
||||
|
||||
// Create trayIcon icon
|
||||
if (store.get('minimizeToTray', true)) tray.create(trayConfig);
|
||||
@@ -902,17 +980,27 @@ app.on('ready', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = global.mainWindow = null;
|
||||
});
|
||||
mainWindow.on('close', (e) => {
|
||||
mainWindow.on('close', async (e) => {
|
||||
// If we are not quitting and have a tray icon then minimize to tray
|
||||
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
|
||||
// On Mac, closing the window just hides it
|
||||
// (this is generally how single-window Mac apps
|
||||
// behave, eg. Mail.app)
|
||||
e.preventDefault();
|
||||
mainWindow.hide();
|
||||
|
||||
if (mainWindow.isFullScreen()) {
|
||||
mainWindow.once('leave-full-screen', () => mainWindow.hide());
|
||||
|
||||
mainWindow.setFullScreen(false);
|
||||
} else {
|
||||
mainWindow.hide();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -929,6 +1017,14 @@ app.on('ready', async () => {
|
||||
}
|
||||
|
||||
webContentsHandler(mainWindow.webContents);
|
||||
|
||||
appLocalization = new AppLocalization({
|
||||
store,
|
||||
components: [
|
||||
() => tray.initApplicationMenu(),
|
||||
() => Menu.setApplicationMenu(buildMenuTemplate()),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
@@ -947,7 +1043,7 @@ function beforeQuit() {
|
||||
}
|
||||
|
||||
app.on('before-quit', beforeQuit);
|
||||
app.on('before-quit-for-update', beforeQuit);
|
||||
autoUpdater.on('before-quit-for-update', beforeQuit);
|
||||
|
||||
app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
||||
// If other instance launched with --hidden then skip showing window
|
||||
46
src/i18n/strings/ar.json
Normal file
46
src/i18n/strings/ar.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"File": "ملف",
|
||||
"Close": "أغلِق",
|
||||
"Actual Size": "المقاس الفعلي",
|
||||
"View": "منظور",
|
||||
"Select All": "حدّد الكل",
|
||||
"Delete": "احذف",
|
||||
"Copy": "انسخ",
|
||||
"Edit": "تحرير",
|
||||
"Close Element": "أغلِق Element",
|
||||
"Cancel": "ألغِ",
|
||||
"Bring All to Front": "ضَع الكل في المقدّمة",
|
||||
"Speech": "نطق",
|
||||
"Add to dictionary": "أضِف إلى القاموس",
|
||||
"The image failed to save": "فشل حفظ الصورة",
|
||||
"Failed to save image": "فشل حفظ الصورة",
|
||||
"Save image as...": "احفظ الصورة كَ...",
|
||||
"Copy link address": "انسخ عنوان الرابط",
|
||||
"Copy email address": "انسخ عنوان البريد الإلكتروني",
|
||||
"Copy image": "انسخ الصورة",
|
||||
"Zoom": "تقريب",
|
||||
"Stop Speaking": "أوقِف النطق",
|
||||
"Start Speaking": "ابدأ النطق",
|
||||
"Unhide": "اعرض",
|
||||
"Hide Others": "أخفِ البقية",
|
||||
"Hide": "أخفِ",
|
||||
"Services": "الخدمات",
|
||||
"About": "عن",
|
||||
"Element Help": "مساعدة Element",
|
||||
"Help": "مساعدة",
|
||||
"Minimize": "صغّر",
|
||||
"Window": "نافذة",
|
||||
"Toggle Developer Tools": "فعّل/عطّل أدوات المطوّرين",
|
||||
"Toggle Full Screen": "فعّل/عطّل ملء الشاشة",
|
||||
"Preferences": "التفضيلات",
|
||||
"Zoom In": "قرّب",
|
||||
"Zoom Out": "بعّد",
|
||||
"Paste and Match Style": "ألصِق وطابِق النمط",
|
||||
"Paste": "ألصِق",
|
||||
"Cut": "قصّ",
|
||||
"Redo": "أعِد",
|
||||
"Undo": "تراجَع",
|
||||
"Quit": "غادِر",
|
||||
"Show/Hide": "اعرض/أخفِ",
|
||||
"Are you sure you want to quit?": "أمتأكّد من الإغلاق؟"
|
||||
}
|
||||
1
src/i18n/strings/basefile.json
Normal file
1
src/i18n/strings/basefile.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
46
src/i18n/strings/be.json
Normal file
46
src/i18n/strings/be.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Add to dictionary": "Дадаць у слоўнік",
|
||||
"The image failed to save": "Не атрымалася захаваць малюнак",
|
||||
"Failed to save image": "Не атрымалася захаваць малюнак",
|
||||
"Save image as...": "Захаваць малюнак як...",
|
||||
"Copy link address": "Скапіраваць спасылку",
|
||||
"Copy email address": "Скапіраваць адрас пошты",
|
||||
"Copy image": "Скапіраваць малюнак",
|
||||
"File": "Файл",
|
||||
"Bring All to Front": "Вынесці ўсё наперад",
|
||||
"Zoom": "Маштаб",
|
||||
"Stop Speaking": "Перастаць гаварыць",
|
||||
"Start Speaking": "Гаварыць",
|
||||
"Speech": "Голас",
|
||||
"Unhide": "Паказаць",
|
||||
"Hide Others": "Схаваць іншыя",
|
||||
"Hide": "Схаваць",
|
||||
"Services": "Сервісы",
|
||||
"About": "Аб праграме",
|
||||
"Element Help": "Даведка Element",
|
||||
"Help": "Даведка",
|
||||
"Close": "Зачыніць",
|
||||
"Minimize": "Згарнуць",
|
||||
"Window": "Акно",
|
||||
"Toggle Developer Tools": "Пераключэнне інструментаў распрацоўніка",
|
||||
"Toggle Full Screen": "Пераключэнне на ўвесь экран",
|
||||
"Preferences": "Параметры",
|
||||
"Zoom Out": "Паменшыць",
|
||||
"Zoom In": "Павялічыць",
|
||||
"Actual Size": "Фактычны Памер",
|
||||
"View": "Прагляд",
|
||||
"Select All": "Выбраць усё",
|
||||
"Delete": "Выдаліць",
|
||||
"Paste and Match Style": "Уставіць і супаставіць стыль",
|
||||
"Paste": "Уставіць",
|
||||
"Copy": "Капіяваць",
|
||||
"Cut": "Выразаць",
|
||||
"Redo": "Паўтарыць",
|
||||
"Undo": "Адмяніць",
|
||||
"Edit": "Змяніць",
|
||||
"Quit": "Выйсці",
|
||||
"Show/Hide": "Паказаць / схаваць",
|
||||
"Are you sure you want to quit?": "Вы ўпэўненыя, што хочаце выйсці?",
|
||||
"Close Element": "Зачыніць Element",
|
||||
"Cancel": "Адмена"
|
||||
}
|
||||
46
src/i18n/strings/ca.json
Normal file
46
src/i18n/strings/ca.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Add to dictionary": "Afegeix al diccionari",
|
||||
"The image failed to save": "S'ha fallat en desar la imatge",
|
||||
"Failed to save image": "S'ha fallat en desar la imatge",
|
||||
"Save image as...": "Anomena i desa la imatge...",
|
||||
"Copy link address": "Copia l'adreça de l'enllaç",
|
||||
"Copy email address": "Copia l'adreça de correu electrònic",
|
||||
"Copy image": "Copia la imatge",
|
||||
"File": "Fitxer",
|
||||
"Bring All to Front": "Porta-ho tot al davant",
|
||||
"Zoom": "Escala",
|
||||
"Stop Speaking": "Para la veu",
|
||||
"Start Speaking": "Comença la veu",
|
||||
"Speech": "Veu",
|
||||
"Unhide": "Deixa d'amagar",
|
||||
"Hide Others": "Amaga les altres",
|
||||
"Hide": "Amaga",
|
||||
"Services": "Serveis",
|
||||
"About": "Quant a",
|
||||
"Element Help": "Ajuda sobre l'Element",
|
||||
"Help": "Ajuda",
|
||||
"Close": "Tanca",
|
||||
"Minimize": "Minimitza",
|
||||
"Window": "Finestra",
|
||||
"Toggle Developer Tools": "Commuta les eines per a desenvolupadors",
|
||||
"Toggle Full Screen": "Commuta la pantalla completa",
|
||||
"Preferences": "Preferències",
|
||||
"Zoom Out": "Allunya",
|
||||
"Zoom In": "Apropia",
|
||||
"Actual Size": "Mida real",
|
||||
"View": "Visualitza",
|
||||
"Select All": "Selecciona-ho tot",
|
||||
"Delete": "Suprimeix",
|
||||
"Paste and Match Style": "Enganxa i fes coincidir l'estil",
|
||||
"Paste": "Enganxa",
|
||||
"Copy": "Copia",
|
||||
"Cut": "Retalla",
|
||||
"Redo": "Refés",
|
||||
"Undo": "Desfés",
|
||||
"Edit": "Edita",
|
||||
"Quit": "Surt",
|
||||
"Show/Hide": "Mostra/Amaga",
|
||||
"Are you sure you want to quit?": "Esteu segur que voleu sortir?",
|
||||
"Close Element": "Tanca l'Element",
|
||||
"Cancel": "Cancel·la"
|
||||
}
|
||||
47
src/i18n/strings/de.json
Normal file
47
src/i18n/strings/de.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Speech": "Sprache",
|
||||
"Paste and Match Style": "Einfügen und Formatierung beibehalten",
|
||||
"Stop Speaking": "Aufnahme beenden",
|
||||
"Start Speaking": "Aufnahme starten",
|
||||
"Services": "Dienste",
|
||||
"Are you sure you want to quit?": "Wirklich beenden?",
|
||||
"Add to dictionary": "Wörterbuch hinzufügen",
|
||||
"The image failed to save": "Das Bild konnte nicht gespeichert werden",
|
||||
"Failed to save image": "Bild kann nicht gespeichert werden",
|
||||
"Save image as...": "Bild speichern unter...",
|
||||
"Copy link address": "Link-Adresse kopieren",
|
||||
"Copy email address": "Email-Adresse kopieren",
|
||||
"Copy image": "Bild kopieren",
|
||||
"File": "Datei",
|
||||
"Bring All to Front": "Alles in den Vordergrund",
|
||||
"Zoom": "Zoom",
|
||||
"Unhide": "Wieder anzeigen",
|
||||
"Hide Others": "Andere verstecken",
|
||||
"Hide": "Verstecken",
|
||||
"About": "Über",
|
||||
"Element Help": "Hilfe zu Element",
|
||||
"Help": "Hilfe",
|
||||
"Close": "Schließen",
|
||||
"Minimize": "Minimieren",
|
||||
"Window": "Fenster",
|
||||
"Toggle Developer Tools": "Developer-Tools an/aus",
|
||||
"Toggle Full Screen": "Vollbildschirm an/aus",
|
||||
"Preferences": "Präferenzen",
|
||||
"Zoom Out": "Verkleinern",
|
||||
"Zoom In": "Vergrößern",
|
||||
"Actual Size": "Tatsächliche Größe",
|
||||
"View": "Ansicht",
|
||||
"Select All": "Alles auswählen",
|
||||
"Delete": "Löschen",
|
||||
"Paste": "Einfügen",
|
||||
"Copy": "Kopieren",
|
||||
"Cut": "Ausschneiden",
|
||||
"Redo": "Wiederherstellen",
|
||||
"Undo": "Rückgängig",
|
||||
"Edit": "Bearbeiten",
|
||||
"Quit": "Beenden",
|
||||
"Show/Hide": "Anzeigen/Ausblenden",
|
||||
"Close Element": "Element schließen",
|
||||
"Cancel": "Abbrechen",
|
||||
"Copy image address": "Bild-Adresse kopieren"
|
||||
}
|
||||
47
src/i18n/strings/en_EN.json
Normal file
47
src/i18n/strings/en_EN.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Cancel": "Cancel",
|
||||
"Close Element": "Close Element",
|
||||
"Are you sure you want to quit?": "Are you sure you want to quit?",
|
||||
"Show/Hide": "Show/Hide",
|
||||
"Quit": "Quit",
|
||||
"Edit": "Edit",
|
||||
"Undo": "Undo",
|
||||
"Redo": "Redo",
|
||||
"Cut": "Cut",
|
||||
"Copy": "Copy",
|
||||
"Paste": "Paste",
|
||||
"Paste and Match Style": "Paste and Match Style",
|
||||
"Delete": "Delete",
|
||||
"Select All": "Select All",
|
||||
"View": "View",
|
||||
"Actual Size": "Actual Size",
|
||||
"Zoom In": "Zoom In",
|
||||
"Zoom Out": "Zoom Out",
|
||||
"Preferences": "Preferences",
|
||||
"Toggle Full Screen": "Toggle Full Screen",
|
||||
"Toggle Developer Tools": "Toggle Developer Tools",
|
||||
"Window": "Window",
|
||||
"Minimize": "Minimize",
|
||||
"Close": "Close",
|
||||
"Help": "Help",
|
||||
"Element Help": "Element Help",
|
||||
"About": "About",
|
||||
"Services": "Services",
|
||||
"Hide": "Hide",
|
||||
"Hide Others": "Hide Others",
|
||||
"Unhide": "Unhide",
|
||||
"Speech": "Speech",
|
||||
"Start Speaking": "Start Speaking",
|
||||
"Stop Speaking": "Stop Speaking",
|
||||
"Zoom": "Zoom",
|
||||
"Bring All to Front": "Bring All to Front",
|
||||
"File": "File",
|
||||
"Copy image": "Copy image",
|
||||
"Copy email address": "Copy email address",
|
||||
"Copy image address": "Copy image address",
|
||||
"Copy link address": "Copy link address",
|
||||
"Save image as...": "Save image as...",
|
||||
"Failed to save image": "Failed to save image",
|
||||
"The image failed to save": "The image failed to save",
|
||||
"Add to dictionary": "Add to dictionary"
|
||||
}
|
||||
46
src/i18n/strings/en_US.json
Normal file
46
src/i18n/strings/en_US.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Close": "Close",
|
||||
"Add to dictionary": "Add to dictionary",
|
||||
"The image failed to save": "The image failed to save",
|
||||
"Failed to save image": "Failed to save image",
|
||||
"Save image as...": "Save image as...",
|
||||
"Copy link address": "Copy link address",
|
||||
"Copy email address": "Copy email address",
|
||||
"Copy image": "Copy image",
|
||||
"File": "File",
|
||||
"Bring All to Front": "Bring All to Front",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Stop Speaking",
|
||||
"Start Speaking": "Start Speaking",
|
||||
"Speech": "Speech",
|
||||
"Unhide": "Unhide",
|
||||
"Hide Others": "Hide Others",
|
||||
"Hide": "Hide",
|
||||
"Services": "Services",
|
||||
"About": "About",
|
||||
"Element Help": "Element Help",
|
||||
"Help": "Help",
|
||||
"Minimize": "Minimize",
|
||||
"Window": "Window",
|
||||
"Toggle Developer Tools": "Toggle Developer Tools",
|
||||
"Toggle Full Screen": "Toggle Full Screen",
|
||||
"Preferences": "Preferences",
|
||||
"Zoom Out": "Zoom Out",
|
||||
"Zoom In": "Zoom In",
|
||||
"Actual Size": "Actual Size",
|
||||
"View": "View",
|
||||
"Select All": "Select All",
|
||||
"Delete": "Delete",
|
||||
"Paste and Match Style": "Paste and Match Style",
|
||||
"Paste": "Paste",
|
||||
"Copy": "Copy",
|
||||
"Cut": "Cut",
|
||||
"Redo": "Redo",
|
||||
"Undo": "Undo",
|
||||
"Edit": "Edit",
|
||||
"Quit": "Quit",
|
||||
"Show/Hide": "Show/Hide",
|
||||
"Are you sure you want to quit?": "Are you sure you want to quit?",
|
||||
"Close Element": "Close Element",
|
||||
"Cancel": "Cancel"
|
||||
}
|
||||
47
src/i18n/strings/es.json
Normal file
47
src/i18n/strings/es.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Add to dictionary": "Añadir al diccionario",
|
||||
"The image failed to save": "La imagen no se ha podido guardar",
|
||||
"Failed to save image": "No se ha podido guardar la imagen",
|
||||
"Save image as...": "Guardar imagen como...",
|
||||
"Copy link address": "Copiar dirección de enlace",
|
||||
"Copy email address": "Copiar dirección de correo",
|
||||
"Copy image": "Copiar imagen",
|
||||
"File": "Archivo",
|
||||
"Bring All to Front": "Traer todas al primer plano",
|
||||
"Zoom": "Zoom",
|
||||
"Start Speaking": "Empezar a hablar",
|
||||
"Stop Speaking": "Parar de hablar",
|
||||
"Speech": "Dictado",
|
||||
"Unhide": "Mostrar",
|
||||
"Hide Others": "Ocultar otros",
|
||||
"Hide": "Ocultar",
|
||||
"Services": "Servicios",
|
||||
"About": "Acerca de",
|
||||
"Element Help": "Ayuda de Element",
|
||||
"Help": "Ayuda",
|
||||
"Close": "Cerrar",
|
||||
"Minimize": "Minimizar",
|
||||
"Window": "Ventana",
|
||||
"Toggle Developer Tools": "Abrir/cerrar herramientas de desarrollo",
|
||||
"Toggle Full Screen": "Entrar/salir de pantalla completa",
|
||||
"Preferences": "Preferencias",
|
||||
"Zoom Out": "Alejar",
|
||||
"Zoom In": "Acercar",
|
||||
"Actual Size": "Tamaño real",
|
||||
"View": "Ver",
|
||||
"Select All": "Seleccionar todo",
|
||||
"Delete": "Eliminar",
|
||||
"Paste and Match Style": "Pegar manteniendo estilo",
|
||||
"Paste": "Pegar",
|
||||
"Copy": "Copiar",
|
||||
"Cut": "Cortar",
|
||||
"Redo": "Rehacer",
|
||||
"Undo": "Deshacer",
|
||||
"Edit": "Editar",
|
||||
"Quit": "Salir",
|
||||
"Show/Hide": "Ver/Ocultar",
|
||||
"Are you sure you want to quit?": "¿Quieres salir?",
|
||||
"Close Element": "Cerrar Element",
|
||||
"Cancel": "Cancelar",
|
||||
"Copy image address": "Copiar dirección de la imagen"
|
||||
}
|
||||
47
src/i18n/strings/et.json
Normal file
47
src/i18n/strings/et.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Element Help": "Rakenduse Element abiteave",
|
||||
"About": "Rakenduse teave",
|
||||
"The image failed to save": "Seda pilti ei õnnestunud salvestada",
|
||||
"Add to dictionary": "Lisa sõnastikku",
|
||||
"Failed to save image": "Pildi salvestamine ei õnnestunud",
|
||||
"Save image as...": "Salvesta pilt kui...",
|
||||
"Copy link address": "Kopeeri lingi aadress",
|
||||
"Copy email address": "Kopeeri e-posti aadress",
|
||||
"Copy image": "Kopeeri pilt",
|
||||
"File": "Fail",
|
||||
"Bring All to Front": "Too kõik esiplaanile",
|
||||
"Zoom": "Suumi",
|
||||
"Stop Speaking": "Lõpeta rääkimine",
|
||||
"Start Speaking": "Alusta rääkimist",
|
||||
"Speech": "Kõne",
|
||||
"Unhide": "Näita uuesti",
|
||||
"Hide Others": "Peida muud",
|
||||
"Hide": "Peida",
|
||||
"Services": "Teenused",
|
||||
"Help": "Abiteave",
|
||||
"Close": "Sulge",
|
||||
"Minimize": "Vähenda",
|
||||
"Window": "Aken",
|
||||
"Toggle Developer Tools": "Arendaja töövahendid sisse/välja",
|
||||
"Toggle Full Screen": "Täisekraanivaade sisse/välja",
|
||||
"Preferences": "Seadistused",
|
||||
"Zoom Out": "Vähenda",
|
||||
"Zoom In": "Suurenda",
|
||||
"Actual Size": "Näita tavasuuruses",
|
||||
"View": "Vaata",
|
||||
"Select All": "Vali kõik",
|
||||
"Delete": "Kustuta",
|
||||
"Paste and Match Style": "Aseta kasutades sama stiili",
|
||||
"Paste": "Aseta",
|
||||
"Copy": "Kopeeri",
|
||||
"Cut": "Lõika",
|
||||
"Redo": "Tee uuesti",
|
||||
"Undo": "Võta tagasi",
|
||||
"Edit": "Muuda",
|
||||
"Quit": "Välju",
|
||||
"Show/Hide": "Näita/peida",
|
||||
"Are you sure you want to quit?": "Kas sa kindlasti soovid rakendusest väljuda?",
|
||||
"Close Element": "Sulge Element",
|
||||
"Cancel": "Tühista",
|
||||
"Copy image address": "Kopeeri pildi aadress"
|
||||
}
|
||||
47
src/i18n/strings/fa.json
Normal file
47
src/i18n/strings/fa.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Paste and Match Style": "جایگذاری و تطبیق سَبک",
|
||||
"Add to dictionary": "افزودن به لغتنامه",
|
||||
"The image failed to save": "تصویر ذخیره نشد",
|
||||
"Failed to save image": "ذخیرهٔ تصویر شکست خورد",
|
||||
"Save image as...": "ذخیرهٔ تصویر به عنوان...",
|
||||
"Copy link address": "رونوشت نشانی پیوند",
|
||||
"Copy image address": "رونوشت نشانی تصویر",
|
||||
"Copy email address": "رونوشت نشانی رایانامه",
|
||||
"Copy image": "رونوشت تصویر",
|
||||
"File": "پرونده",
|
||||
"Bring All to Front": "همه را به جلو بیاورید",
|
||||
"Zoom": "بزرگنمایی",
|
||||
"Speech": "صحبت کردن",
|
||||
"Stop Speaking": "صحبت کردن را تمام کنید",
|
||||
"Start Speaking": "صحبت کردن را شروع کنید",
|
||||
"Unhide": "آشکار",
|
||||
"Hide Others": "پنهان کردن دیگران",
|
||||
"Hide": "پنهان",
|
||||
"Services": "خدمات",
|
||||
"About": "درباره",
|
||||
"Element Help": "راهنمای المنت",
|
||||
"Help": "راهنما",
|
||||
"Close": "بستن",
|
||||
"Minimize": "کمینه",
|
||||
"Window": "پنجره",
|
||||
"Toggle Developer Tools": "تغییر وضعیت ابزارهای توسعهدهنده",
|
||||
"Toggle Full Screen": "تغییر وضعیت تمامصفحه",
|
||||
"Preferences": "ترجیحات",
|
||||
"Zoom Out": "بزرگنمایی به خارج",
|
||||
"Zoom In": "بزرگنمایی به داخل",
|
||||
"Actual Size": "اندازهٔ واقعی",
|
||||
"View": "دیدن",
|
||||
"Select All": "گزینش همه",
|
||||
"Delete": "حذف",
|
||||
"Paste": "جایگذاری",
|
||||
"Copy": "رونوشت",
|
||||
"Cut": "برش",
|
||||
"Redo": "انجام دوباره",
|
||||
"Undo": "بازگردانی",
|
||||
"Edit": "ویرایش",
|
||||
"Quit": "خروج",
|
||||
"Show/Hide": "نمایش/پنهان",
|
||||
"Are you sure you want to quit?": "آیا مطمئنید که میخواهید خارج شوید؟",
|
||||
"Close Element": "بستن المنت",
|
||||
"Cancel": "لغو"
|
||||
}
|
||||
46
src/i18n/strings/fi.json
Normal file
46
src/i18n/strings/fi.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Paste": "Liitä",
|
||||
"Paste and Match Style": "Liitä ja sovita tyyli",
|
||||
"Add to dictionary": "Lisää sanakirjaan",
|
||||
"The image failed to save": "Kuvan tallennus epäonnistui",
|
||||
"Failed to save image": "Kuvan tallennus epäonnistui",
|
||||
"Save image as...": "Tallenna kuva nimellä...",
|
||||
"Copy link address": "Kopioi linkin osoite",
|
||||
"Copy email address": "Kopioi sähköpostiosoite",
|
||||
"Copy image": "Kopioi kuva",
|
||||
"File": "Tiedosto",
|
||||
"Bring All to Front": "Tuo kaikki eteen",
|
||||
"Zoom": "Suurennus",
|
||||
"Stop Speaking": "Lopeta puhe",
|
||||
"Start Speaking": "Aloita puhe",
|
||||
"Speech": "Puhe",
|
||||
"Unhide": "Palauta näkyviin",
|
||||
"Hide Others": "Piilota muut",
|
||||
"Hide": "Piilota",
|
||||
"Services": "Palvelut",
|
||||
"About": "Tietoja",
|
||||
"Element Help": "Elementin ohjeet",
|
||||
"Help": "Apua",
|
||||
"Close": "Sulje",
|
||||
"Minimize": "Pienennä",
|
||||
"Window": "Ikkuna",
|
||||
"Toggle Developer Tools": "Näytä tai piilota kehittäjätyökalut",
|
||||
"Toggle Full Screen": "Vaihda koko näytön tilaa",
|
||||
"Preferences": "Asetukset",
|
||||
"Zoom Out": "Pienennä",
|
||||
"Zoom In": "Suurenna",
|
||||
"Actual Size": "Alkuperäinen koko",
|
||||
"View": "Näytä",
|
||||
"Select All": "Valitse kaikki",
|
||||
"Delete": "Poista",
|
||||
"Copy": "Kopioi",
|
||||
"Cut": "Leikkaa",
|
||||
"Redo": "Tee uudestaan",
|
||||
"Undo": "Peru",
|
||||
"Edit": "Muokkaa",
|
||||
"Quit": "Lopeta",
|
||||
"Show/Hide": "Näytä/piilota",
|
||||
"Are you sure you want to quit?": "Haluatko varmasti poistua?",
|
||||
"Close Element": "Sulje Element",
|
||||
"Cancel": "Peruuta"
|
||||
}
|
||||
45
src/i18n/strings/fr.json
Normal file
45
src/i18n/strings/fr.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"Undo": "Annuler",
|
||||
"Edit": "Modifier",
|
||||
"Quit": "Quitter",
|
||||
"Show/Hide": "Afficher/Masquer",
|
||||
"Are you sure you want to quit?": "Êtes-vous sûr de vouloir quitter ?",
|
||||
"Close Element": "Fermer Element",
|
||||
"Cancel": "Annuler",
|
||||
"Unhide": "Dé-masquer",
|
||||
"Hide Others": "Masquer les autres",
|
||||
"Hide": "Masquer",
|
||||
"Services": "Services",
|
||||
"About": "À propos",
|
||||
"Element Help": "Aide d’Element",
|
||||
"Help": "Aide",
|
||||
"Close": "Fermer",
|
||||
"Minimize": "Minimiser",
|
||||
"Window": "Fenêtre",
|
||||
"Toggle Developer Tools": "Basculer les outils de développement",
|
||||
"Toggle Full Screen": "Basculer le plein écran",
|
||||
"Preferences": "Préférences",
|
||||
"Zoom Out": "Dé-zoomer",
|
||||
"Zoom In": "Zoomer",
|
||||
"Actual Size": "Taille réelle",
|
||||
"View": "Afficher",
|
||||
"Select All": "Tout sélectionner",
|
||||
"Delete": "Supprimer",
|
||||
"Paste and Match Style": "Copier avec le style de destination",
|
||||
"Paste": "Coller",
|
||||
"Copy": "Copier",
|
||||
"Cut": "Couper",
|
||||
"Speech": "Dictée",
|
||||
"Add to dictionary": "Ajouter au dictionnaire",
|
||||
"The image failed to save": "L’image n’a pas pu être sauvegardée",
|
||||
"Failed to save image": "Échec de la sauvegarde de l’image",
|
||||
"Save image as...": "Enregistrer l’image sous…",
|
||||
"Copy link address": "Copier l’adresse du lien",
|
||||
"Copy email address": "Copier l’adresse e-mail",
|
||||
"Copy image": "Copier l’image",
|
||||
"File": "Fichier",
|
||||
"Bring All to Front": "Tout amener au premier plan",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Arrêter la dictée",
|
||||
"Start Speaking": "Commencer la dictée"
|
||||
}
|
||||
46
src/i18n/strings/fy.json
Normal file
46
src/i18n/strings/fy.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Copy image": "Ofbylding kopiearje",
|
||||
"Speech": "Spraak",
|
||||
"View": "Byld",
|
||||
"Paste and Match Style": "Plakke en lit stilen oerienkomme",
|
||||
"Add to dictionary": "Oan wurdlist tafoegje",
|
||||
"The image failed to save": "It is net slagge de ôfbylding te bewarjen",
|
||||
"Failed to save image": "Ofbylding bewarjen mislearre",
|
||||
"Save image as...": "Ofbylding bewarje as…",
|
||||
"Copy link address": "Keppeling kopiearje",
|
||||
"Copy email address": "E-mailadres kopiearje",
|
||||
"File": "Bestân",
|
||||
"Bring All to Front": "Alles nei foaren bringe",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Stopje mei praten",
|
||||
"Start Speaking": "Begjin mei praten",
|
||||
"Unhide": "Wer toane",
|
||||
"Hide Others": "Oare ferbergje",
|
||||
"Hide": "Ferbergje",
|
||||
"Services": "Tsjinsten",
|
||||
"About": "Oer",
|
||||
"Element Help": "Element help",
|
||||
"Help": "Help",
|
||||
"Close": "Slute",
|
||||
"Minimize": "Minimalisearje",
|
||||
"Window": "Finster",
|
||||
"Toggle Developer Tools": "Untwikkelersark yn-/útskeakelje",
|
||||
"Toggle Full Screen": "Folslein skerm yn-/útskeakelje",
|
||||
"Preferences": "Foarkarren",
|
||||
"Zoom Out": "Utzoome",
|
||||
"Zoom In": "Ynzoome",
|
||||
"Actual Size": "Werklike grutte",
|
||||
"Select All": "Alles selektearje",
|
||||
"Delete": "Fuortsmite",
|
||||
"Paste": "Plakke",
|
||||
"Copy": "Kopiearje",
|
||||
"Cut": "Knippe",
|
||||
"Redo": "Opnij útfiere",
|
||||
"Undo": "Ungedien meitsje",
|
||||
"Edit": "Bewurkje",
|
||||
"Quit": "Ofslute",
|
||||
"Show/Hide": "Toane/Ferbergje",
|
||||
"Are you sure you want to quit?": "Binne jo der wis fan dat jo ôfslute wolle?",
|
||||
"Close Element": "Element ôfslute",
|
||||
"Cancel": "Annulearje"
|
||||
}
|
||||
46
src/i18n/strings/gl.json
Normal file
46
src/i18n/strings/gl.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Add to dictionary": "Engadir ao dicionario",
|
||||
"The image failed to save": "Non se gardou a imaxe",
|
||||
"Failed to save image": "Fallou o gardado da imaxe",
|
||||
"Save image as...": "Gardar imaxe como...",
|
||||
"Copy link address": "Copiar enderezo da ligazón",
|
||||
"Copy email address": "Copiar enderezo de email",
|
||||
"Copy image": "Copiar imaxe",
|
||||
"File": "Ficheiro",
|
||||
"Bring All to Front": "Traer todo á fronte",
|
||||
"Zoom": "Aumento",
|
||||
"Stop Speaking": "Deixa de falar",
|
||||
"Start Speaking": "Comeza a falar",
|
||||
"Speech": "Falar",
|
||||
"Unhide": "Desagochar",
|
||||
"Hide Others": "Agochar outras",
|
||||
"Hide": "Agochar",
|
||||
"Services": "Servizos",
|
||||
"About": "Acerca de",
|
||||
"Element Help": "Axuda de Element",
|
||||
"Help": "Axuda",
|
||||
"Close": "Pechar",
|
||||
"Minimize": "Minimizar",
|
||||
"Window": "Ventá",
|
||||
"Toggle Developer Tools": "Activar ferramentas de desenvolvemento",
|
||||
"Toggle Full Screen": "Activar pantalla completa",
|
||||
"Preferences": "Preferencias",
|
||||
"Zoom Out": "Diminuir",
|
||||
"Zoom In": "Aumentar",
|
||||
"Actual Size": "Tamaño real",
|
||||
"View": "Ver",
|
||||
"Select All": "Elexir todo",
|
||||
"Delete": "Eliminar",
|
||||
"Paste and Match Style": "Pegar e imitar estilo",
|
||||
"Paste": "Pegar",
|
||||
"Copy": "Copiar",
|
||||
"Cut": "Cortar",
|
||||
"Redo": "Refacer",
|
||||
"Undo": "Desfacer",
|
||||
"Edit": "Editar",
|
||||
"Quit": "Saír",
|
||||
"Show/Hide": "Mostrar/Agochar",
|
||||
"Are you sure you want to quit?": "Tes a certeza de que queres saír?",
|
||||
"Close Element": "Pechar Element",
|
||||
"Cancel": "Cancelar"
|
||||
}
|
||||
46
src/i18n/strings/he.json
Normal file
46
src/i18n/strings/he.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Actual Size": "גודל ממשי",
|
||||
"Add to dictionary": "הוסף למילון",
|
||||
"The image failed to save": "שמירת התמונה נכשלה",
|
||||
"Failed to save image": "נכשל בשמירת התמונה",
|
||||
"Save image as...": "שמור תמונה בשם...",
|
||||
"Copy link address": "העתק קישור",
|
||||
"Copy email address": "העתק כתובת אימייל",
|
||||
"Copy image": "העתק תמונה",
|
||||
"File": "קובץ",
|
||||
"Bring All to Front": "הבא הכל לחזית",
|
||||
"Zoom": "גודל תצוגה",
|
||||
"Stop Speaking": "הפסק לדבר",
|
||||
"Start Speaking": "התחל לדבר",
|
||||
"Speech": "דיבור",
|
||||
"Unhide": "בטל הסתרה",
|
||||
"Hide Others": "הסתר אחרים",
|
||||
"Hide": "הסתר",
|
||||
"Services": "שרותים",
|
||||
"About": "אודות",
|
||||
"Element Help": "עזרה של אלמנט",
|
||||
"Help": "עזרה",
|
||||
"Close": "סגור",
|
||||
"Minimize": "מזער",
|
||||
"Window": "חלון",
|
||||
"Toggle Developer Tools": "הפעל כלי מפתחים",
|
||||
"Toggle Full Screen": "הפעל מצב מסך מלא",
|
||||
"Preferences": "העדפות",
|
||||
"Zoom Out": "התרחק",
|
||||
"Zoom In": "התקרב",
|
||||
"View": "צפה",
|
||||
"Select All": "בחר הכל",
|
||||
"Delete": "מחק",
|
||||
"Paste": "הדבק",
|
||||
"Copy": "העתק",
|
||||
"Cut": "גזור",
|
||||
"Undo": "בטל ביצוע",
|
||||
"Redo": "בצע שוב",
|
||||
"Edit": "עריכה",
|
||||
"Quit": "יציאה",
|
||||
"Show/Hide": "הצג\\הסתר",
|
||||
"Are you sure you want to quit?": "האם אתה בטוח שברצונך לצאת?",
|
||||
"Close Element": "סגור את אלמנט",
|
||||
"Cancel": "ביטול",
|
||||
"Paste and Match Style": "הדבק והתאם סגנון"
|
||||
}
|
||||
13
src/i18n/strings/hr.json
Normal file
13
src/i18n/strings/hr.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Paste": "Zalijepiti",
|
||||
"Copy": "Kopirati",
|
||||
"Cut": "Izrezati",
|
||||
"Redo": "Preurediti",
|
||||
"Undo": "Poništi",
|
||||
"Edit": "Uredi",
|
||||
"Quit": "Prestati",
|
||||
"Show/Hide": "Pokaži/sakrij",
|
||||
"Are you sure you want to quit?": "Jesi li siguran da želiš odustati?",
|
||||
"Close Element": "Zatvori Element",
|
||||
"Cancel": "Otkazati"
|
||||
}
|
||||
47
src/i18n/strings/hu.json
Normal file
47
src/i18n/strings/hu.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Add to dictionary": "Hozzáadás a szótárhoz",
|
||||
"The image failed to save": "A kép mentése sikertelen",
|
||||
"Failed to save image": "Kép mentése sikertelen",
|
||||
"Save image as...": "Kép mentése másként...",
|
||||
"Copy link address": "Hivatkozás másolása",
|
||||
"Copy email address": "E-mail cím másolása",
|
||||
"Copy image": "Kép másolása",
|
||||
"File": "Fájl",
|
||||
"Bring All to Front": "Mindent előtérbe hoz",
|
||||
"Zoom": "Nagyítás",
|
||||
"Stop Speaking": "Fejezze be a beszédet",
|
||||
"Start Speaking": "Kezdjen beszélni",
|
||||
"Speech": "Beszéd",
|
||||
"Unhide": "Felfed",
|
||||
"Hide Others": "Minden mást eltakar",
|
||||
"Hide": "Eltakar",
|
||||
"Services": "Szolgáltatás",
|
||||
"About": "Névjegy",
|
||||
"Element Help": "Element segítség",
|
||||
"Help": "Segítség",
|
||||
"Close": "Bezár",
|
||||
"Minimize": "Lecsukás",
|
||||
"Window": "Ablak",
|
||||
"Toggle Developer Tools": "Fejlesztői eszközök",
|
||||
"Toggle Full Screen": "Teljes képernyő",
|
||||
"Preferences": "Beállítások",
|
||||
"Zoom Out": "Kicsinyít",
|
||||
"Zoom In": "Nagyít",
|
||||
"Actual Size": "Jelenlegi méret",
|
||||
"View": "Nézet",
|
||||
"Select All": "Mind kijelölése",
|
||||
"Delete": "Töröl",
|
||||
"Paste and Match Style": "Beillesztés formázással",
|
||||
"Paste": "Beillesztés",
|
||||
"Copy": "Másol",
|
||||
"Cut": "Kivág",
|
||||
"Redo": "Újra",
|
||||
"Undo": "Visszavon",
|
||||
"Edit": "Szerkeszt",
|
||||
"Quit": "Kilép",
|
||||
"Show/Hide": "Megmutat/Elrejt",
|
||||
"Are you sure you want to quit?": "Biztos, hogy kilép?",
|
||||
"Close Element": "Element bezárása",
|
||||
"Cancel": "Mégsem",
|
||||
"Copy image address": "Kép címének másolása"
|
||||
}
|
||||
47
src/i18n/strings/id.json
Normal file
47
src/i18n/strings/id.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Add to dictionary": "Tambah ke kamus",
|
||||
"The image failed to save": "Gambar gagal disimpan",
|
||||
"Failed to save image": "Gagal menyimpan gambar",
|
||||
"Save image as...": "Simpan gambar sebagai...",
|
||||
"Copy link address": "Salin alamat tautan",
|
||||
"Copy email address": "Salin surel",
|
||||
"Copy image": "Salin gambar",
|
||||
"File": "File",
|
||||
"Hide Others": "Sembunyikan yang Lain",
|
||||
"Bring All to Front": "Bawa Semua ke Depan",
|
||||
"Zoom": "Perbesar",
|
||||
"Stop Speaking": "Berhenti Berbicara",
|
||||
"Start Speaking": "Mulai Berbicara",
|
||||
"Speech": "Dikte",
|
||||
"Unhide": "Tampilkan",
|
||||
"Hide": "Sembunyikan",
|
||||
"Services": "Layanan",
|
||||
"About": "Tentang",
|
||||
"Element Help": "Bantuan Element",
|
||||
"Help": "Bantuan",
|
||||
"Close": "Tutup",
|
||||
"Minimize": "Minimalkan",
|
||||
"Window": "Jendela",
|
||||
"Toggle Developer Tools": "Beralih Alat Pengembang",
|
||||
"Toggle Full Screen": "Beralih Layar Penuh",
|
||||
"Preferences": "Pengaturan",
|
||||
"Zoom Out": "Perkecil",
|
||||
"Zoom In": "Perbesar",
|
||||
"Cut": "Potong",
|
||||
"Redo": "Ulangi",
|
||||
"Undo": "Urungkan",
|
||||
"Actual Size": "Ukuran Sebenarnya",
|
||||
"View": "Tampilan",
|
||||
"Select All": "Pilih Semua",
|
||||
"Delete": "Hapus",
|
||||
"Paste and Match Style": "Tempel dan Cocokkan Gaya",
|
||||
"Paste": "Tempel",
|
||||
"Copy": "Salin",
|
||||
"Edit": "Edit",
|
||||
"Quit": "Keluar",
|
||||
"Show/Hide": "Tampilkan/Sembunyikan",
|
||||
"Are you sure you want to quit?": "Apakah Anda yakin ingin keluar?",
|
||||
"Close Element": "Tutup Element",
|
||||
"Cancel": "Batal",
|
||||
"Copy image address": "Salin alamat gambar"
|
||||
}
|
||||
46
src/i18n/strings/is.json
Normal file
46
src/i18n/strings/is.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Add to dictionary": "Bæta við orðasafn",
|
||||
"The image failed to save": "Myndina var ekki hægt að vista",
|
||||
"Failed to save image": "Mistókst að vista mynd",
|
||||
"Save image as...": "Vista mynd sem...",
|
||||
"Copy link address": "Afrita vistfang tengils",
|
||||
"Copy email address": "Afrita tölvupóstfang",
|
||||
"Copy image": "Afrita mynd",
|
||||
"File": "Skrá",
|
||||
"Bring All to Front": "Setja allt fremst",
|
||||
"Zoom": "Stærð",
|
||||
"Stop Speaking": "Hætta tali",
|
||||
"Start Speaking": "Byrja tal",
|
||||
"Speech": "Tal",
|
||||
"Unhide": "Birta",
|
||||
"Hide Others": "Fela aðra",
|
||||
"Hide": "Fela",
|
||||
"Services": "Þjónustur",
|
||||
"About": "Um hugbúnaðinn",
|
||||
"Element Help": "Hjálp við Element",
|
||||
"Help": "Hjálp",
|
||||
"Close": "Loka",
|
||||
"Minimize": "Lágmarka",
|
||||
"Window": "Gluggi",
|
||||
"Toggle Developer Tools": "Víxla forritunarverkfærum af/á",
|
||||
"Toggle Full Screen": "Víxla fullum skjá af/á",
|
||||
"Preferences": "Stillingar",
|
||||
"Zoom Out": "Minnka",
|
||||
"Zoom In": "Stækka",
|
||||
"Actual Size": "Raunstærð",
|
||||
"View": "Skoða",
|
||||
"Select All": "Velja allt",
|
||||
"Delete": "Eyða",
|
||||
"Paste and Match Style": "Líma og samsvara stíl",
|
||||
"Paste": "Líma",
|
||||
"Copy": "Afrita",
|
||||
"Cut": "Klippa",
|
||||
"Redo": "Endurgera",
|
||||
"Undo": "Afturkalla",
|
||||
"Edit": "Breyta",
|
||||
"Quit": "Hætta",
|
||||
"Show/Hide": "Sýna/Fela",
|
||||
"Are you sure you want to quit?": "Ertu viss um að þú viljir hætta?",
|
||||
"Close Element": "Loka Element",
|
||||
"Cancel": "Hætta við"
|
||||
}
|
||||
47
src/i18n/strings/it.json
Normal file
47
src/i18n/strings/it.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Add to dictionary": "Aggiungi al dizionario",
|
||||
"The image failed to save": "Non è stato possibile salvare l'immagine",
|
||||
"Failed to save image": "Salvataggio immagine fallito",
|
||||
"Save image as...": "Salva immagine come...",
|
||||
"Copy link address": "Copia indirizzo collegamento",
|
||||
"Copy email address": "Copia indirizzo email",
|
||||
"Copy image": "Copia immagine",
|
||||
"File": "File",
|
||||
"Bring All to Front": "Porta tutto in primo piano",
|
||||
"Zoom": "Zoom",
|
||||
"Start Speaking": "Inizia a parlare",
|
||||
"Unhide": "Mostra",
|
||||
"Hide Others": "Nascondi gli altri",
|
||||
"Hide": "Nascondi",
|
||||
"Services": "Servizi",
|
||||
"About": "Al riguardo",
|
||||
"Element Help": "Aiuto di Element",
|
||||
"Help": "Aiuto",
|
||||
"Close": "Chiudi",
|
||||
"Minimize": "Riduci",
|
||||
"Window": "Finestra",
|
||||
"Toggle Developer Tools": "Attiva strumenti per sviluppatori",
|
||||
"Toggle Full Screen": "Passa a schermo intero",
|
||||
"Preferences": "Preferenze",
|
||||
"Zoom Out": "Rimpicciolisci",
|
||||
"Zoom In": "Ingrandisci",
|
||||
"Actual Size": "Dimensione effettiva",
|
||||
"View": "Vedi",
|
||||
"Select All": "Seleziona tutto",
|
||||
"Delete": "Elimina",
|
||||
"Paste and Match Style": "Incolla e abbina lo stile",
|
||||
"Paste": "Incolla",
|
||||
"Copy": "Copia",
|
||||
"Cut": "Taglia",
|
||||
"Redo": "Ripeti",
|
||||
"Undo": "Annulla",
|
||||
"Edit": "Modifica",
|
||||
"Quit": "Esci",
|
||||
"Show/Hide": "Mostra/Nascondi",
|
||||
"Are you sure you want to quit?": "Vuoi veramente uscire?",
|
||||
"Close Element": "Chiudi Element",
|
||||
"Cancel": "Annulla",
|
||||
"Stop Speaking": "Smetti di parlare",
|
||||
"Speech": "Dettatura",
|
||||
"Copy image address": "Copia indirizzo immagine"
|
||||
}
|
||||
46
src/i18n/strings/lv.json
Normal file
46
src/i18n/strings/lv.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Start Speaking": "Runājiet...",
|
||||
"Add to dictionary": "Pievienot vārdnīcai",
|
||||
"The image failed to save": "Attēlu saglabāt neizdevās",
|
||||
"Failed to save image": "Neizdevās saglabāt attēlu",
|
||||
"Save image as...": "Saglabāt attēlu kā...",
|
||||
"Copy link address": "Kopēt saiti",
|
||||
"Copy email address": "Kopēt e-pastu",
|
||||
"Copy image": "Kopēt attēlu",
|
||||
"File": "Fails",
|
||||
"Bring All to Front": "Iznest visu priekšplānā",
|
||||
"Zoom": "Mērogošana",
|
||||
"Stop Speaking": "Beidziet runāt",
|
||||
"Speech": "Runa",
|
||||
"Unhide": "Rādīt",
|
||||
"Hide Others": "Slēpt citus",
|
||||
"Hide": "Slēpt",
|
||||
"Services": "Servisi/pakalpojumi",
|
||||
"About": "Par programmu",
|
||||
"Element Help": "Element palīdzība",
|
||||
"Help": "Palīdzība",
|
||||
"Close": "Aizvērt",
|
||||
"Minimize": "Minimizēt",
|
||||
"Window": "Logs",
|
||||
"Toggle Developer Tools": "Pārslēgt uz Izstrādātāja rīkiem",
|
||||
"Toggle Full Screen": "Pārslēgt uz pilnekrānu",
|
||||
"Preferences": "Parametri/iestatījumi",
|
||||
"Zoom Out": "Samazināt",
|
||||
"Zoom In": "Palielināt",
|
||||
"Actual Size": "Faktiskais izmērs",
|
||||
"View": "Skats",
|
||||
"Select All": "Atzīmēt visus",
|
||||
"Delete": "Dzēst",
|
||||
"Paste and Match Style": "Ievietot, saglabājot stilu",
|
||||
"Paste": "Ievietot",
|
||||
"Copy": "Kopēt",
|
||||
"Cut": "Izgriezt",
|
||||
"Redo": "Atatdarīt/atatgriezt (redo)",
|
||||
"Undo": "Atgreizt/atdarīt (undo)",
|
||||
"Edit": "Rediģēt",
|
||||
"Quit": "Iziet",
|
||||
"Show/Hide": "Rādīt/nerādīt",
|
||||
"Are you sure you want to quit?": "Tiešām vēlaties iziet?",
|
||||
"Close Element": "Aizvērt Elementu",
|
||||
"Cancel": "Atcelt"
|
||||
}
|
||||
37
src/i18n/strings/nb_NO.json
Normal file
37
src/i18n/strings/nb_NO.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"Toggle Developer Tools": "Veksle Utvikleralternativer",
|
||||
"Add to dictionary": "Legg til i ordbok",
|
||||
"The image failed to save": "Bildet kunne ikke lagres",
|
||||
"Failed to save image": "Kunne ikke lagre bildet",
|
||||
"Save image as...": "Lagre bildet som...",
|
||||
"Copy email address": "Kopier e-postadressen",
|
||||
"Copy image": "Kopier bildet",
|
||||
"File": "Fil",
|
||||
"Stop Speaking": "Slutt å snakke",
|
||||
"Start Speaking": "Begynn å snakke",
|
||||
"Speech": "Tale",
|
||||
"Hide": "Skjul",
|
||||
"About": "Om",
|
||||
"Element Help": "Element Hjelp",
|
||||
"Help": "Hjelp",
|
||||
"Close": "Lukk",
|
||||
"Minimize": "Minimere",
|
||||
"Window": "Vindu",
|
||||
"Zoom Out": "Zoom ut",
|
||||
"Zoom In": "Zoom inn",
|
||||
"Actual Size": "Faktisk størrelse",
|
||||
"View": "Se",
|
||||
"Select All": "Velg alle",
|
||||
"Delete": "Slett",
|
||||
"Paste": "Lim inn",
|
||||
"Copy": "Kopier",
|
||||
"Undo": "Angre",
|
||||
"Edit": "Rediger",
|
||||
"Quit": "Avslutt",
|
||||
"Show/Hide": "Vis/Skjul",
|
||||
"Are you sure you want to quit?": "Er du sikker på at du vil slutte?",
|
||||
"Close Element": "Lukk Element",
|
||||
"Cancel": "Avbryt",
|
||||
"Services": "Tjenester",
|
||||
"Hide Others": "Skjul Andre"
|
||||
}
|
||||
46
src/i18n/strings/nl.json
Normal file
46
src/i18n/strings/nl.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Add to dictionary": "Aan woordenboek toevoegen",
|
||||
"The image failed to save": "De afbeelding opslaan is mislukt",
|
||||
"Failed to save image": "Afbeelding opslaan is mislukt",
|
||||
"Save image as...": "Afbeelding opslaan als...",
|
||||
"Copy link address": "Link kopiëren",
|
||||
"Copy email address": "E-mailadres kopiëren",
|
||||
"Copy image": "Afbeelding kopiëren",
|
||||
"File": "Bestand",
|
||||
"Bring All to Front": "Alles naar voren brengen",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Stop met praten",
|
||||
"Start Speaking": "Begin met praten",
|
||||
"Speech": "Spraak",
|
||||
"Unhide": "Weer laten zien",
|
||||
"Hide Others": "Andere verbergen",
|
||||
"Hide": "Verbergen",
|
||||
"Services": "Diensten",
|
||||
"About": "Over",
|
||||
"Element Help": "Element Help",
|
||||
"Help": "Help",
|
||||
"Close": "Sluiten",
|
||||
"Minimize": "Minimaliseren",
|
||||
"Window": "Venster",
|
||||
"Toggle Developer Tools": "Developer Tools wisselen",
|
||||
"Toggle Full Screen": "Volledig scherm wisselen",
|
||||
"Preferences": "Voorkeuren",
|
||||
"Zoom Out": "Uitzoomen",
|
||||
"Zoom In": "Inzoomen",
|
||||
"Actual Size": "Werkelijke grootte",
|
||||
"View": "Bekijken",
|
||||
"Select All": "Alles selecteren",
|
||||
"Delete": "Verwijderen",
|
||||
"Paste and Match Style": "Plakken zonder stijl",
|
||||
"Paste": "Plakken",
|
||||
"Copy": "Kopiëren",
|
||||
"Cut": "Knippen",
|
||||
"Redo": "Opnieuw doen",
|
||||
"Undo": "Ongedaan maken",
|
||||
"Edit": "Bewerken",
|
||||
"Quit": "Sluiten",
|
||||
"Show/Hide": "Tonen/Verbergen",
|
||||
"Are you sure you want to quit?": "Weet u zeker dat u wilt stoppen?",
|
||||
"Close Element": "Element sluiten",
|
||||
"Cancel": "Annuleren"
|
||||
}
|
||||
46
src/i18n/strings/nn.json
Normal file
46
src/i18n/strings/nn.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"The image failed to save": "Biletet vart ikkje lagra",
|
||||
"Paste and Match Style": "Lim inn og tilpass stil",
|
||||
"Redo": "Gjer om",
|
||||
"Undo": "Angre",
|
||||
"Are you sure you want to quit?": "Er du sikker på at du vil avslutta?",
|
||||
"Add to dictionary": "Legg til i ordlista",
|
||||
"Failed to save image": "Klarte ikkje å lagra biletet",
|
||||
"Save image as...": "Lagre bilete som…",
|
||||
"Copy link address": "Kopier lenkjeadresse",
|
||||
"Copy email address": "Kopier e-postadresse",
|
||||
"Copy image": "Kopier bilete",
|
||||
"File": "Fil",
|
||||
"Bring All to Front": "Plasser lengst fram",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Stopp snakka",
|
||||
"Start Speaking": "Byrja snakka",
|
||||
"Speech": "Tale",
|
||||
"Unhide": "Ikkje gøym",
|
||||
"Hide Others": "Gøym andre",
|
||||
"Hide": "Gøym",
|
||||
"Services": "Tenester",
|
||||
"About": "Om",
|
||||
"Element Help": "Hjelp med Element",
|
||||
"Help": "Hjelp",
|
||||
"Close": "Lat att",
|
||||
"Minimize": "Minimer",
|
||||
"Window": "Vindauga",
|
||||
"Toggle Developer Tools": "Developer Tools av/på",
|
||||
"Toggle Full Screen": "Fullskjerm av/på",
|
||||
"Preferences": "Innstillingar",
|
||||
"Zoom Out": "Zoom ut",
|
||||
"Zoom In": "Zoom inn",
|
||||
"Actual Size": "Faktisk storleik",
|
||||
"View": "Vis",
|
||||
"Select All": "Marker alt",
|
||||
"Delete": "Slett",
|
||||
"Paste": "Lim inn",
|
||||
"Copy": "Lim inn",
|
||||
"Cut": "Klipp ut",
|
||||
"Edit": "Rediger",
|
||||
"Quit": "Avslutt",
|
||||
"Show/Hide": "Vis/Gøym",
|
||||
"Close Element": "Lat att Element",
|
||||
"Cancel": "Avbryt"
|
||||
}
|
||||
46
src/i18n/strings/pl.json
Normal file
46
src/i18n/strings/pl.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Bring All to Front": "Wyciągnij wszystko do przodu",
|
||||
"Add to dictionary": "Dodaj do słownika",
|
||||
"The image failed to save": "Obraz nie został zapisany",
|
||||
"Failed to save image": "Nie udało się zapisać obrazu",
|
||||
"Save image as...": "Zapisz obraz jako...",
|
||||
"Copy link address": "Skopiuj adres łącza",
|
||||
"Copy email address": "Skopiuj adres email",
|
||||
"Copy image": "Skopiuj obraz",
|
||||
"File": "Plik",
|
||||
"Zoom": "Powiększenie",
|
||||
"Stop Speaking": "Przestań mówić",
|
||||
"Start Speaking": "Zacznij mówić",
|
||||
"Speech": "Mowa",
|
||||
"Unhide": "Odkryj",
|
||||
"Hide Others": "Ukryj inne",
|
||||
"Hide": "Ukryj",
|
||||
"Services": "Usługi",
|
||||
"About": "O nas",
|
||||
"Element Help": "Pomoc Element",
|
||||
"Help": "Pomoc",
|
||||
"Close": "Zamknij",
|
||||
"Minimize": "Zminimalizuj",
|
||||
"Window": "Okno",
|
||||
"Toggle Developer Tools": "Przełącz na narzędzia deweloperskie",
|
||||
"Toggle Full Screen": "Przełącz na pełny ekran",
|
||||
"Preferences": "Preferencje",
|
||||
"Zoom Out": "Pomniejsz",
|
||||
"Zoom In": "Powiększ",
|
||||
"Actual Size": "Rzeczywisty rozmiar",
|
||||
"View": "Pokaż",
|
||||
"Select All": "Zaznacz wszystko",
|
||||
"Delete": "Usuń",
|
||||
"Paste and Match Style": "Wklej i dopasuj styl",
|
||||
"Paste": "Wklej",
|
||||
"Copy": "Skopiuj",
|
||||
"Cut": "Wytnij",
|
||||
"Redo": "Powtórz",
|
||||
"Undo": "Cofnij",
|
||||
"Edit": "Edytuj",
|
||||
"Quit": "Zamknij",
|
||||
"Show/Hide": "Pokaż/Ukryj",
|
||||
"Are you sure you want to quit?": "Czy na pewno chcesz zamknąć?",
|
||||
"Close Element": "Zamknij Elementa",
|
||||
"Cancel": "Anuluj"
|
||||
}
|
||||
47
src/i18n/strings/pt_BR.json
Normal file
47
src/i18n/strings/pt_BR.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Add to dictionary": "Adicionar a dicionário",
|
||||
"The image failed to save": "A imagem falhou para salvar",
|
||||
"Failed to save image": "Falha para salvar imagem",
|
||||
"Save image as...": "Salvar imagem como...",
|
||||
"Copy link address": "Copiar endereço de link",
|
||||
"Copy email address": "Copiar endereço de email",
|
||||
"Copy image": "Copiar imagem",
|
||||
"File": "Arquivo",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Parar de Falar",
|
||||
"Start Speaking": "Começar a Falar",
|
||||
"Speech": "Fala",
|
||||
"Unhide": "Desesconder",
|
||||
"Hide": "Esconder",
|
||||
"Services": "Serviços",
|
||||
"About": "Sobre",
|
||||
"Element Help": "Ajuda de Element",
|
||||
"Help": "Ajuda",
|
||||
"Close": "Fechar",
|
||||
"Minimize": "Minimizar",
|
||||
"Window": "Janela",
|
||||
"Toggle Developer Tools": "Ativar/Desativar Ferramentas de Desenvolvimento",
|
||||
"Toggle Full Screen": "Pôr em/Tirar de Tela Cheia",
|
||||
"Preferences": "Preferências",
|
||||
"Zoom Out": "Dar Zoom Out",
|
||||
"Zoom In": "Dar Zoom In",
|
||||
"Actual Size": "Tamanho de Verdade",
|
||||
"View": "Visualizar",
|
||||
"Select All": "Selecionar Todas",
|
||||
"Delete": "Deletar",
|
||||
"Paste and Match Style": "Colar e Adequar Estilo",
|
||||
"Paste": "Colar",
|
||||
"Copy": "Copiar",
|
||||
"Cut": "Cortar",
|
||||
"Redo": "Refazer",
|
||||
"Undo": "Desfazer",
|
||||
"Edit": "Editar",
|
||||
"Quit": "Sair",
|
||||
"Show/Hide": "Mostrar/Esconder",
|
||||
"Are you sure you want to quit?": "Você tem certeza que você quer sair?",
|
||||
"Close Element": "Fechar Element",
|
||||
"Cancel": "Cancelar",
|
||||
"Bring All to Front": "Trazer Todas Para Frente",
|
||||
"Hide Others": "Esconder Outras(os)",
|
||||
"Copy image address": "Copiar endereço de imagem"
|
||||
}
|
||||
43
src/i18n/strings/ro.json
Normal file
43
src/i18n/strings/ro.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"Add to dictionary": "Adăugați la dicționar",
|
||||
"Failed to save image": "Eroare in salvarea imaginii",
|
||||
"Save image as...": "Salvează imagine ca ...",
|
||||
"Copy link address": "Copiază link",
|
||||
"Copy email address": "Copiază adresă de email",
|
||||
"Copy image": "Copiază imagine",
|
||||
"File": "Fișier",
|
||||
"Bring All to Front": "Aduce-ți totul in față",
|
||||
"Zoom": "zoom",
|
||||
"Stop Speaking": "Oprire Voce",
|
||||
"Start Speaking": "Pornire Voce",
|
||||
"Speech": "Voce",
|
||||
"Hide Others": "Ascunde restul",
|
||||
"Hide": "Ascunde",
|
||||
"Services": "Servicii",
|
||||
"About": "Despre",
|
||||
"Element Help": "Ajutor Element",
|
||||
"Help": "Ajutor",
|
||||
"Close": "Inchide",
|
||||
"Minimize": "Minimizare",
|
||||
"Window": "Fereastră",
|
||||
"Toggle Developer Tools": "Comutare unelte dezvoltator",
|
||||
"Toggle Full Screen": "Comutare pe tot ecranul",
|
||||
"Preferences": "Preferințe",
|
||||
"Zoom Out": "Micșorează",
|
||||
"Zoom In": "Mărește",
|
||||
"Actual Size": "Mărime reală",
|
||||
"View": "Vizualizează",
|
||||
"Select All": "Selectează tot",
|
||||
"Delete": "Șterge",
|
||||
"Paste and Match Style": "Lipește si potrivește stilul",
|
||||
"Paste": "Lipește",
|
||||
"Copy": "Copiere",
|
||||
"Redo": "Refă",
|
||||
"Undo": "Anulare",
|
||||
"Edit": "Editare",
|
||||
"Quit": "Închide",
|
||||
"Show/Hide": "Arată/Ascunde",
|
||||
"Are you sure you want to quit?": "Sunte-ți sigur ca doriți sa inchideți aplicația ?",
|
||||
"Close Element": "închide aplicația",
|
||||
"Cancel": "Anulare"
|
||||
}
|
||||
47
src/i18n/strings/ru.json
Normal file
47
src/i18n/strings/ru.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Edit": "Изменить",
|
||||
"Quit": "Выйти",
|
||||
"Close Element": "Закрыть Element",
|
||||
"Cancel": "Отмена",
|
||||
"Show/Hide": "Показать/скрыть",
|
||||
"Are you sure you want to quit?": "Вы уверены, что хотите выйти?",
|
||||
"Copy email address": "Копировать адрес почты",
|
||||
"Copy image": "Копировать изображение",
|
||||
"File": "Файл",
|
||||
"Zoom": "Масштаб",
|
||||
"Unhide": "Показать",
|
||||
"Hide": "Скрыть",
|
||||
"Services": "Сервисы",
|
||||
"About": "О программе",
|
||||
"Element Help": "Справка Element",
|
||||
"Help": "Справка",
|
||||
"Close": "Закрыть",
|
||||
"Minimize": "Свернуть",
|
||||
"Window": "Окно",
|
||||
"Toggle Developer Tools": "Переключить инструменты разработчика",
|
||||
"Toggle Full Screen": "Переключить полноэкранный режим",
|
||||
"Preferences": "Параметры",
|
||||
"Zoom Out": "Уменьшить",
|
||||
"Zoom In": "Увеличить",
|
||||
"Actual Size": "Фактический размер",
|
||||
"View": "Просмотр",
|
||||
"Select All": "Выбрать все",
|
||||
"Delete": "Удалить",
|
||||
"Paste": "Вставить",
|
||||
"Copy": "Копировать",
|
||||
"Cut": "Вырезать",
|
||||
"Redo": "Повторить",
|
||||
"Undo": "Отменить",
|
||||
"Save image as...": "Сохранить изображение как...",
|
||||
"Copy link address": "Копировать ссылку",
|
||||
"Add to dictionary": "Добавить в словарь",
|
||||
"The image failed to save": "Не удалось сохранить изображение",
|
||||
"Failed to save image": "Не удалось сохранить изображение",
|
||||
"Bring All to Front": "Вынести всё вперёд",
|
||||
"Stop Speaking": "Перестаньте говорить",
|
||||
"Start Speaking": "Говорите",
|
||||
"Speech": "Голос",
|
||||
"Hide Others": "Скрыть прочие",
|
||||
"Paste and Match Style": "Вставить с тем же стилем",
|
||||
"Copy image address": "Копировать адрес изображения"
|
||||
}
|
||||
31
src/i18n/strings/si.json
Normal file
31
src/i18n/strings/si.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"Show/Hide": "පෙන්වන්න/සඟවන්න",
|
||||
"Are you sure you want to quit?": "ඔබට ඉවත් වීමට අවශ්ය බව විශ්වාස ද?",
|
||||
"Close Element": "ඉලමෙන්ට් වසන්න",
|
||||
"Cancel": "අවලංගු කරන්න",
|
||||
"Add to dictionary": "ශබ්ද කෝෂයට එකතු කරන්න",
|
||||
"Copy link address": "සබැඳියේ ලිපිනය පිටපත් කරන්න",
|
||||
"Copy email address": "වි-තැපෑල පිටපත් කරන්න",
|
||||
"File": "ගොනුව",
|
||||
"Zoom": "විශාල කරන්න",
|
||||
"Hide Others": "වෙනත් දෑ සඟවන්න",
|
||||
"Hide": "සඟවන්න",
|
||||
"Services": "සේවා",
|
||||
"About": "පිළිබඳව",
|
||||
"Element Help": "ඉලමෙන්ට් උපකාර",
|
||||
"Help": "උපකාර",
|
||||
"Close": "වසන්න",
|
||||
"Minimize": "හකුලන්න",
|
||||
"Window": "කවුළුව",
|
||||
"Zoom Out": "කුඩාලනය කරන්න",
|
||||
"Zoom In": "විශාලනය කරන්න",
|
||||
"Actual Size": "සැබෑ ප්රමාණය",
|
||||
"Select All": "සියල්ල තෝරන්න",
|
||||
"Paste": "අලවන්න",
|
||||
"Copy": "පිටපත්",
|
||||
"Cut": "කපන්න",
|
||||
"Redo": "පසුසේ",
|
||||
"Undo": "පෙරසේ",
|
||||
"Edit": "සංස්කරණය",
|
||||
"Quit": "ඉවත් වන්න"
|
||||
}
|
||||
47
src/i18n/strings/sk.json
Normal file
47
src/i18n/strings/sk.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Unhide": "Odkryť",
|
||||
"Stop Speaking": "Zastaviť nahrávanie hlasu",
|
||||
"Start Speaking": "Spustiť nahrávanie hlasu",
|
||||
"Speech": "Reč",
|
||||
"Element Help": "Pomocník pre aplikáciu Element",
|
||||
"Paste and Match Style": "Vložiť a prispôsobiť štýl",
|
||||
"Close Element": "Zavrieť aplikáciu Element",
|
||||
"Add to dictionary": "Pridať do slovníka",
|
||||
"The image failed to save": "Obrázok sa nepodarilo uložiť",
|
||||
"Failed to save image": "Chyba pri ukladaní obrázka",
|
||||
"Save image as...": "Uložiť obrázok ako...",
|
||||
"Copy link address": "Kopírovať adresu odkazu",
|
||||
"Copy email address": "Kopírovať e-mailovú adresu",
|
||||
"Copy image": "Kopírovať obrázok",
|
||||
"File": "Súbor",
|
||||
"Bring All to Front": "Preniesť všetky do popredia",
|
||||
"Zoom": "Lupa",
|
||||
"Hide Others": "Skryť ostatné",
|
||||
"Hide": "Skryť",
|
||||
"Services": "Služby",
|
||||
"About": "O aplikácii",
|
||||
"Help": "Pomocník",
|
||||
"Close": "Zavrieť",
|
||||
"Minimize": "Minimalizovať",
|
||||
"Window": "Okno",
|
||||
"Toggle Developer Tools": "Nástroje pre vývojárov",
|
||||
"Toggle Full Screen": "Celá obrazovka",
|
||||
"Preferences": "Vlastnosti",
|
||||
"Zoom Out": "Oddialiť",
|
||||
"Zoom In": "Priblížiť",
|
||||
"Actual Size": "Aktuálna veľkosť",
|
||||
"View": "Zobraziť",
|
||||
"Select All": "Vybrať všetko",
|
||||
"Delete": "Odstrániť",
|
||||
"Paste": "Vložiť",
|
||||
"Copy": "Kopírovať",
|
||||
"Cut": "Vystrihnúť",
|
||||
"Redo": "Opakovať",
|
||||
"Undo": "Späť",
|
||||
"Edit": "Úpravy",
|
||||
"Quit": "Ukončiť",
|
||||
"Show/Hide": "Zobraziť/Skryť",
|
||||
"Are you sure you want to quit?": "Naozaj chcete zavrieť aplikáciu?",
|
||||
"Cancel": "Zrušiť",
|
||||
"Copy image address": "Kopírovať adresu obrázka"
|
||||
}
|
||||
46
src/i18n/strings/sv.json
Normal file
46
src/i18n/strings/sv.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Save image as...": "Spara bild som...",
|
||||
"Copy link address": "Kopiera länkadress",
|
||||
"Copy email address": "Kopiera e-postadress",
|
||||
"Copy image": "Kopiera bild",
|
||||
"File": "Arkiv",
|
||||
"Bring All to Front": "Lägg alla överst",
|
||||
"Stop Speaking": "Sluta tala",
|
||||
"Start Speaking": "Börja tala",
|
||||
"Speech": "Tal",
|
||||
"Hide Others": "Göm övriga",
|
||||
"Hide": "Göm",
|
||||
"Services": "Tjänster",
|
||||
"About": "Om",
|
||||
"Element Help": "Element Hjälp",
|
||||
"Help": "Hjälp",
|
||||
"Close": "Stäng",
|
||||
"Minimize": "Minimera",
|
||||
"Window": "Fönster",
|
||||
"Preferences": "Inställningar",
|
||||
"Actual Size": "Verklig storlek",
|
||||
"View": "Visa",
|
||||
"Select All": "Markera allt",
|
||||
"Delete": "Radera",
|
||||
"Paste and Match Style": "Klistra in och matcha stilen",
|
||||
"Paste": "Klistra in",
|
||||
"Copy": "Kopiera",
|
||||
"Cut": "Klipp ut",
|
||||
"Redo": "Gör om",
|
||||
"Undo": "Ångra",
|
||||
"Edit": "Redigera",
|
||||
"Quit": "Avsluta",
|
||||
"Cancel": "Avbryt",
|
||||
"Zoom": "Zooma",
|
||||
"Toggle Developer Tools": "Växla utvecklarverktyg",
|
||||
"Toggle Full Screen": "Växla helskärm",
|
||||
"Unhide": "Göm inte",
|
||||
"Zoom Out": "Zooma ut",
|
||||
"Zoom In": "Zooma in",
|
||||
"Close Element": "Stäng Element",
|
||||
"Show/Hide": "Visa/dölj",
|
||||
"Add to dictionary": "Lägg till i ordlistan",
|
||||
"The image failed to save": "Bilden sparades inte",
|
||||
"Failed to save image": "Misslyckades med att spara bilden",
|
||||
"Are you sure you want to quit?": "Är du säker att du vill avsluta?"
|
||||
}
|
||||
46
src/i18n/strings/ta.json
Normal file
46
src/i18n/strings/ta.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Zoom": "பெரிதாக்குதல்",
|
||||
"Minimize": "சிறிதாக்கு",
|
||||
"Toggle Developer Tools": "படைப்பாளர் கருவிகளை நிலைமாற்று",
|
||||
"Toggle Full Screen": "முழு திரையை நிலைமாற்று",
|
||||
"Paste and Match Style": "ஒட்டு மற்றும் நடையை பொறுத்து",
|
||||
"Add to dictionary": "அகராதியில் சேர்",
|
||||
"The image failed to save": "படம் சேமிக்கத் தவறிவிட்டது",
|
||||
"Failed to save image": "படத்தைச் சேமிப்பதில் தோல்வி",
|
||||
"Save image as...": "படத்தை இவ்வாறு சேமி...",
|
||||
"Copy link address": "இணைப்பு முகவரியை நகலெடு",
|
||||
"Copy email address": "மின்னஞ்சல் முகவரியை நகலெடு",
|
||||
"Copy image": "படத்தை நகலெடு",
|
||||
"File": "கோப்பு",
|
||||
"Bring All to Front": "அனைத்தையும் முன்னால் கொண்டுவா",
|
||||
"Stop Speaking": "பேசுவதை நிறுத்து",
|
||||
"Start Speaking": "பேசத் துவங்கு",
|
||||
"Speech": "பேச்சு",
|
||||
"Unhide": "காட்டு",
|
||||
"Hide Others": "மற்றதை மறை",
|
||||
"Hide": "மறை",
|
||||
"Services": "சேவைகள்",
|
||||
"About": "இதனைப் பற்றி",
|
||||
"Element Help": "எலிமெண்ட் உதவி",
|
||||
"Help": "உதவி",
|
||||
"Close": "மூடு",
|
||||
"Window": "சாளரம்",
|
||||
"Preferences": "விருப்பத்தேர்வுகள்",
|
||||
"Zoom Out": "சிறிதாக்கு",
|
||||
"Zoom In": "பெரிதாக்கு",
|
||||
"Actual Size": "உண்மையான அளவு",
|
||||
"View": "காட்சி",
|
||||
"Select All": "அனைத்தையும் தெரிவுசெய்",
|
||||
"Delete": "அழி",
|
||||
"Paste": "ஒட்டு",
|
||||
"Copy": "நகலெடு",
|
||||
"Cut": "வெட்டு",
|
||||
"Redo": "மீண்டும் செய்",
|
||||
"Undo": "செயல்தவிர்",
|
||||
"Edit": "திருத்து",
|
||||
"Quit": "வெளியேறு",
|
||||
"Show/Hide": "காட்டு/மறை",
|
||||
"Are you sure you want to quit?": "நீங்கள் நிச்சயம் வெளியேற விரும்புகிறீர்களா?",
|
||||
"Close Element": "எலிமெண்ட் ஐ மூடு",
|
||||
"Cancel": "ரத்துசெய்"
|
||||
}
|
||||
46
src/i18n/strings/tr.json
Normal file
46
src/i18n/strings/tr.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Add to dictionary": "Sözlüğe ekle",
|
||||
"The image failed to save": "Bu resim kaydedilemedi",
|
||||
"Failed to save image": "Resim kaydedilemedi",
|
||||
"Save image as...": "Resmi ... olarak farklı kaydet",
|
||||
"Copy link address": "Bağlantılı adresi kopyala",
|
||||
"Copy email address": "E-posta adresini kopyala",
|
||||
"Copy image": "Resmi kopyala",
|
||||
"File": "Dosya",
|
||||
"Bring All to Front": "Hepsini öne getir",
|
||||
"Zoom": "Yaklaştır",
|
||||
"Stop Speaking": "Konuşmayı durdur",
|
||||
"Start Speaking": "Konuşmaya başla",
|
||||
"Speech": "Konuşma",
|
||||
"Unhide": "Gizlemeyi bırak",
|
||||
"Hide Others": "Diğerlerini gizle",
|
||||
"Hide": "Gizle",
|
||||
"Services": "Hizmetler",
|
||||
"About": "Hakkında",
|
||||
"Element Help": "Element yardımı",
|
||||
"Help": "Yardım",
|
||||
"Close": "Kapat",
|
||||
"Minimize": "Küçült",
|
||||
"Window": "Pencere",
|
||||
"Toggle Developer Tools": "Geliştirici araçları",
|
||||
"Toggle Full Screen": "Tam ekran",
|
||||
"Preferences": "Tercihler",
|
||||
"Zoom Out": "Uzaklaştır",
|
||||
"Zoom In": "Yaklaştır",
|
||||
"Actual Size": "Gerçek boyut",
|
||||
"View": "Görünüm",
|
||||
"Select All": "Tümünü seç",
|
||||
"Delete": "Sil",
|
||||
"Paste and Match Style": "Biçimiyle bir yapıştır",
|
||||
"Paste": "Yapıştır",
|
||||
"Copy": "Kopyala",
|
||||
"Cut": "Kes",
|
||||
"Redo": "Yinele",
|
||||
"Undo": "Geri al",
|
||||
"Edit": "Düzenle",
|
||||
"Quit": "Çık",
|
||||
"Show/Hide": "Göster/Gizle",
|
||||
"Are you sure you want to quit?": "Çıkmak istediğinize emin misiniz?",
|
||||
"Close Element": "Element'i kapat",
|
||||
"Cancel": "İptal"
|
||||
}
|
||||
46
src/i18n/strings/vi.json
Normal file
46
src/i18n/strings/vi.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Add to dictionary": "Thêm vào từ điển",
|
||||
"The image failed to save": "Ảnh không lưu được",
|
||||
"Failed to save image": "Lưu ảnh thất bại",
|
||||
"Save image as...": "Lưu ảnh…",
|
||||
"Copy link address": "Sao chép địa chỉ liên kết",
|
||||
"Copy email address": "Sao chép địa chỉ email",
|
||||
"Copy image": "Sao chép ảnh",
|
||||
"File": "Tệp",
|
||||
"Bring All to Front": "Đưa tất cả lên trước",
|
||||
"Zoom": "Thu phóng",
|
||||
"Stop Speaking": "Dừng nói",
|
||||
"Start Speaking": "Bắt đầu nói",
|
||||
"Speech": "Đọc màn hình",
|
||||
"Unhide": "Bỏ ẩn",
|
||||
"Hide Others": "Ẩn cái khác",
|
||||
"Hide": "Ẩn",
|
||||
"Services": "Dịch vụ",
|
||||
"About": "Giới thiệu",
|
||||
"Element Help": "Trợ giúp Element",
|
||||
"Help": "Hỗ trợ",
|
||||
"Close": "Đóng",
|
||||
"Minimize": "Thu nhỏ",
|
||||
"Window": "Cửa sổ",
|
||||
"Toggle Developer Tools": "Công cụ cho Nhà phát triển",
|
||||
"Toggle Full Screen": "Toàn màn hình",
|
||||
"Preferences": "Tùy chọn",
|
||||
"Zoom Out": "Thu nhỏ",
|
||||
"Zoom In": "Phóng to",
|
||||
"Actual Size": "Kích thước thực",
|
||||
"View": "Xem",
|
||||
"Select All": "Chọn tất cả",
|
||||
"Delete": "Xóa",
|
||||
"Paste and Match Style": "Dán và khớp kiểu",
|
||||
"Paste": "Dán",
|
||||
"Copy": "Sao chép",
|
||||
"Cut": "Cắt",
|
||||
"Redo": "Làm lại",
|
||||
"Undo": "Hoàn tác",
|
||||
"Edit": "Chỉnh sửa",
|
||||
"Quit": "Thoát",
|
||||
"Show/Hide": "Hiển thị/Ẩn",
|
||||
"Are you sure you want to quit?": "Bạn có chắc chắn muốn thoát?",
|
||||
"Close Element": "Đóng Element",
|
||||
"Cancel": "Hủy bỏ"
|
||||
}
|
||||
46
src/i18n/strings/zh_Hans.json
Normal file
46
src/i18n/strings/zh_Hans.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Add to dictionary": "添加到字典",
|
||||
"The image failed to save": "图片保存失败",
|
||||
"Failed to save image": "图片保存失败",
|
||||
"Save image as...": "保存图片为……",
|
||||
"Copy link address": "复制链接地址",
|
||||
"Copy email address": "复制邮箱地址",
|
||||
"Copy image": "复制图片",
|
||||
"File": "文件",
|
||||
"Bring All to Front": "置前",
|
||||
"Zoom": "放大",
|
||||
"Stop Speaking": "停止讲话",
|
||||
"Start Speaking": "开始讲话",
|
||||
"Speech": "讲话",
|
||||
"Unhide": "显示",
|
||||
"Hide Others": "隐藏其他",
|
||||
"Hide": "隐藏",
|
||||
"Services": "服务",
|
||||
"About": "关于",
|
||||
"Element Help": "Element 帮助",
|
||||
"Help": "帮助",
|
||||
"Close": "关闭",
|
||||
"Minimize": "最小化",
|
||||
"Window": "窗口",
|
||||
"Toggle Developer Tools": "切换开发工具",
|
||||
"Toggle Full Screen": "切换全屏",
|
||||
"Preferences": "外观",
|
||||
"Zoom Out": "缩小",
|
||||
"Zoom In": "放大",
|
||||
"Actual Size": "实际大小",
|
||||
"View": "查看",
|
||||
"Select All": "选中全部",
|
||||
"Delete": "删除",
|
||||
"Paste and Match Style": "粘贴并匹配样式",
|
||||
"Paste": "粘贴",
|
||||
"Copy": "复制",
|
||||
"Cut": "剪贴",
|
||||
"Redo": "重做",
|
||||
"Undo": "撤销",
|
||||
"Edit": "编辑",
|
||||
"Quit": "退出",
|
||||
"Show/Hide": "显示/隐藏",
|
||||
"Are you sure you want to quit?": "你确定要退出吗?",
|
||||
"Close Element": "关闭 Element",
|
||||
"Cancel": "取消"
|
||||
}
|
||||
46
src/i18n/strings/zh_Hant.json
Normal file
46
src/i18n/strings/zh_Hant.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Bring All to Front": "全部移至最前",
|
||||
"Add to dictionary": "加入至字典",
|
||||
"The image failed to save": "儲存圖片失敗",
|
||||
"Failed to save image": "儲存圖片失敗",
|
||||
"Save image as...": "另存圖片為...",
|
||||
"Copy link address": "複製連結",
|
||||
"Copy email address": "複製電子郵件地址",
|
||||
"Copy image": "複製圖片",
|
||||
"File": "檔案",
|
||||
"Zoom": "縮放",
|
||||
"Stop Speaking": "停止說話",
|
||||
"Start Speaking": "開始說話",
|
||||
"Speech": "語音",
|
||||
"Unhide": "取消隱藏",
|
||||
"Hide Others": "隱藏其他",
|
||||
"Hide": "隱藏",
|
||||
"Services": "服務",
|
||||
"About": "關於",
|
||||
"Element Help": "Element 協助",
|
||||
"Help": "協助",
|
||||
"Close": "關閉",
|
||||
"Minimize": "最小化",
|
||||
"Window": "視窗",
|
||||
"Toggle Developer Tools": "切換開發工具",
|
||||
"Toggle Full Screen": "切換全螢幕",
|
||||
"Preferences": "偏好設定",
|
||||
"Zoom Out": "縮小",
|
||||
"Zoom In": "放大",
|
||||
"Actual Size": "實際大小",
|
||||
"View": "檢視",
|
||||
"Select All": "全選",
|
||||
"Delete": "刪除",
|
||||
"Paste and Match Style": "貼上並保留格式",
|
||||
"Paste": "貼上",
|
||||
"Copy": "複製",
|
||||
"Cut": "剪下",
|
||||
"Redo": "取消復原",
|
||||
"Undo": "復原",
|
||||
"Edit": "編輯",
|
||||
"Quit": "退出",
|
||||
"Show/Hide": "顯示/隱藏",
|
||||
"Are you sure you want to quit?": "您確定要退出嗎?",
|
||||
"Close Element": "關閉 Element",
|
||||
"Cancel": "取消"
|
||||
}
|
||||
135
src/language-helper.ts
Normal file
135
src/language-helper.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import counterpart from "counterpart";
|
||||
|
||||
import type Store from 'electron-store';
|
||||
|
||||
const DEFAULT_LOCALE = "en";
|
||||
|
||||
export function _td(text: string): string {
|
||||
return text;
|
||||
}
|
||||
|
||||
type SubstitutionValue = number | string;
|
||||
|
||||
interface IVariables {
|
||||
[key: string]: SubstitutionValue;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export function _t(text: string, variables: IVariables = {}): string {
|
||||
const args = Object.assign({ interpolate: false }, variables);
|
||||
|
||||
const { count } = args;
|
||||
|
||||
// Horrible hack to avoid https://github.com/vector-im/element-web/issues/4191
|
||||
// The interpolation library that counterpart uses does not support undefined/null
|
||||
// values and instead will throw an error. This is a problem since everywhere else
|
||||
// in JS land passing undefined/null will simply stringify instead, and when converting
|
||||
// valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null
|
||||
// if there are no existing null guards. To avoid this making the app completely inoperable,
|
||||
// we'll check all the values for undefined/null and stringify them here.
|
||||
Object.keys(args).forEach((key) => {
|
||||
if (args[key] === undefined) {
|
||||
console.warn("safeCounterpartTranslate called with undefined interpolation name: " + key);
|
||||
args[key] = 'undefined';
|
||||
}
|
||||
if (args[key] === null) {
|
||||
console.warn("safeCounterpartTranslate called with null interpolation name: " + key);
|
||||
args[key] = 'null';
|
||||
}
|
||||
});
|
||||
let translated = counterpart.translate(text, args);
|
||||
if (translated === undefined && count !== undefined) {
|
||||
// counterpart does not do fallback if no pluralisation exists
|
||||
// in the preferred language, so do it here
|
||||
translated = counterpart.translate(text, Object.assign({}, args, { locale: DEFAULT_LOCALE }));
|
||||
}
|
||||
|
||||
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
|
||||
return translated;
|
||||
}
|
||||
|
||||
type Component = () => void;
|
||||
|
||||
type TypedStore = Store<{ locale?: string | string[] }>;
|
||||
|
||||
export class AppLocalization {
|
||||
private static readonly STORE_KEY = "locale";
|
||||
|
||||
private readonly store: TypedStore;
|
||||
private readonly localizedComponents: Set<Component>;
|
||||
|
||||
constructor({ store, components = [] }: { store: TypedStore, components: Component[] }) {
|
||||
counterpart.registerTranslations("en", this.fetchTranslationJson("en_EN"));
|
||||
counterpart.setFallbackLocale('en');
|
||||
counterpart.setSeparator('|');
|
||||
|
||||
if (Array.isArray(components)) {
|
||||
this.localizedComponents = new Set(components);
|
||||
}
|
||||
|
||||
this.store = store;
|
||||
if (this.store.has(AppLocalization.STORE_KEY)) {
|
||||
const locales = this.store.get(AppLocalization.STORE_KEY);
|
||||
this.setAppLocale(locales);
|
||||
}
|
||||
|
||||
this.resetLocalizedUI();
|
||||
}
|
||||
|
||||
public fetchTranslationJson(locale: string): Record<string, string> {
|
||||
try {
|
||||
console.log("Fetching translation json for locale: " + locale);
|
||||
return require(`./i18n/strings/${locale}.json`);
|
||||
} catch (e) {
|
||||
console.log(`Could not fetch translation json for locale: '${locale}'`, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public setAppLocale(locales: string | string[]): void {
|
||||
console.log(`Changing application language to ${locales}`);
|
||||
|
||||
if (!Array.isArray(locales)) {
|
||||
locales = [locales];
|
||||
}
|
||||
|
||||
locales.forEach(locale => {
|
||||
const translations = this.fetchTranslationJson(locale);
|
||||
if (translations !== null) {
|
||||
counterpart.registerTranslations(locale, translations);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - this looks like a bug but is out of scope for this conversion
|
||||
counterpart.setLocale(locales);
|
||||
this.store.set(AppLocalization.STORE_KEY, locales);
|
||||
|
||||
this.resetLocalizedUI();
|
||||
}
|
||||
|
||||
public resetLocalizedUI(): void {
|
||||
console.log("Resetting the UI components after locale change");
|
||||
this.localizedComponents.forEach(componentSetup => {
|
||||
if (typeof componentSetup === "function") {
|
||||
componentSetup();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
57
src/preload.ts
Normal file
57
src/preload.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2018, 2019, 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ipcRenderer, contextBridge, IpcRendererEvent } from 'electron';
|
||||
|
||||
// Expose only expected IPC wrapper APIs to the renderer process to avoid
|
||||
// handing out generalised messaging access.
|
||||
|
||||
const CHANNELS = [
|
||||
"app_onAction",
|
||||
"before-quit",
|
||||
"check_updates",
|
||||
"install_update",
|
||||
"ipcCall",
|
||||
"ipcReply",
|
||||
"loudNotification",
|
||||
"preferences",
|
||||
"seshat",
|
||||
"seshatReply",
|
||||
"setBadgeCount",
|
||||
"update-downloaded",
|
||||
"userDownloadCompleted",
|
||||
"userDownloadAction",
|
||||
];
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
"electron",
|
||||
{
|
||||
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
|
||||
if (!CHANNELS.includes(channel)) {
|
||||
console.error(`Unknown IPC channel ${channel} ignored`);
|
||||
return;
|
||||
}
|
||||
ipcRenderer.on(channel, listener);
|
||||
},
|
||||
send(channel: string, ...args: any[]): void {
|
||||
if (!CHANNELS.includes(channel)) {
|
||||
console.error(`Unknown IPC channel ${channel} ignored`);
|
||||
return;
|
||||
}
|
||||
ipcRenderer.send(channel, ...args);
|
||||
},
|
||||
},
|
||||
);
|
||||
102
src/protocol.js
102
src/protocol.js
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const {app} = require("electron");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const PROTOCOL = "element://";
|
||||
const SEARCH_PARAM = "element-desktop-ssoid";
|
||||
const STORE_FILE_NAME = "sso-sessions.json";
|
||||
|
||||
// we getPath userData before electron-main changes it, so this is the default value
|
||||
const storePath = path.join(app.getPath("userData"), STORE_FILE_NAME);
|
||||
|
||||
const processUrl = (url) => {
|
||||
if (!global.mainWindow) return;
|
||||
console.log("Handling link: ", url);
|
||||
global.mainWindow.loadURL(url.replace(PROTOCOL, "vector://"));
|
||||
};
|
||||
|
||||
const readStore = () => {
|
||||
try {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const writeStore = (data) => {
|
||||
fs.writeFileSync(storePath, JSON.stringify(data));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
recordSSOSession: (sessionID) => {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const store = readStore();
|
||||
for (const key in store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
if (store[key] === userDataPath) {
|
||||
delete store[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
store[sessionID] = userDataPath;
|
||||
writeStore(store);
|
||||
},
|
||||
getProfileFromDeeplink: (args) => {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find(arg => arg.startsWith('element://'));
|
||||
if (deeplinkUrl && deeplinkUrl.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === 'element:') {
|
||||
const ssoID = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
const store = readStore();
|
||||
console.log("Forwarding to profile: ", store[ssoID]);
|
||||
return store[ssoID];
|
||||
}
|
||||
}
|
||||
},
|
||||
protocolInit: () => {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter(arg => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, args);
|
||||
} else if (process.platform === 'win32') { // on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
// Protocol handler for macos
|
||||
app.on('open-url', function(ev, url) {
|
||||
ev.preventDefault();
|
||||
processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on('second-instance', (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(PROTOCOL)) return;
|
||||
processUrl(url);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
123
src/protocol.ts
Normal file
123
src/protocol.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { URL } from "url";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
const PROTOCOL = "element:";
|
||||
const SEARCH_PARAM = "element-desktop-ssoid";
|
||||
const STORE_FILE_NAME = "sso-sessions.json";
|
||||
|
||||
// we getPath userData before electron-main changes it, so this is the default value
|
||||
const storePath = path.join(app.getPath("userData"), STORE_FILE_NAME);
|
||||
|
||||
function processUrl(url: string): void {
|
||||
if (!global.mainWindow) return;
|
||||
|
||||
const parsed = new URL(url);
|
||||
// sanity check: we only register for the one protocol, so we shouldn't
|
||||
// be getting anything else unless the user is forcing a URL to open
|
||||
// with the Element app.
|
||||
if (parsed.protocol !== PROTOCOL) {
|
||||
console.log("Ignoring unexpected protocol: ", parsed.protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
const urlToLoad = new URL("vector://vector/webapp/");
|
||||
// ignore anything other than the search (used for SSO login redirect)
|
||||
// and the hash (for general element deep links)
|
||||
// There's no reason to allow anything else, particularly other paths,
|
||||
// since this would allow things like the internal jitsi wrapper to
|
||||
// be loaded, which would get the app stuck on that page and generally
|
||||
// be a bit strange and confusing.
|
||||
urlToLoad.search = parsed.search;
|
||||
urlToLoad.hash = parsed.hash;
|
||||
|
||||
console.log("Opening URL: ", urlToLoad.href);
|
||||
global.mainWindow.loadURL(urlToLoad.href);
|
||||
}
|
||||
|
||||
function readStore(): object {
|
||||
try {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function writeStore(data: object): void {
|
||||
fs.writeFileSync(storePath, JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function recordSSOSession(sessionID: string): void {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const store = readStore();
|
||||
for (const key in store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
if (store[key] === userDataPath) {
|
||||
delete store[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
store[sessionID] = userDataPath;
|
||||
writeStore(store);
|
||||
}
|
||||
|
||||
export function getProfileFromDeeplink(args): string | undefined {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find(arg => arg.startsWith(PROTOCOL + '//'));
|
||||
if (deeplinkUrl && deeplinkUrl.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === PROTOCOL) {
|
||||
const ssoID = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
const store = readStore();
|
||||
console.log("Forwarding to profile: ", store[ssoID]);
|
||||
return store[ssoID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function protocolInit(): void {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter(arg => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, args);
|
||||
} else if (process.platform === 'win32') { // on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
// Protocol handler for macos
|
||||
app.on('open-url', function(ev, url) {
|
||||
ev.preventDefault();
|
||||
processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on('second-instance', (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(PROTOCOL + '//')) return;
|
||||
processUrl(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
const {app} = require('electron');
|
||||
const fsProm = require('fs').promises;
|
||||
import path from "path";
|
||||
import { spawn } from "child_process";
|
||||
import { app } from "electron";
|
||||
import { promises as fsProm } from "fs";
|
||||
|
||||
function runUpdateExe(args) {
|
||||
function runUpdateExe(args: string[]): Promise<void> {
|
||||
// Invokes Squirrel's Update.exe which will do things for us like create shortcuts
|
||||
// Note that there's an Update.exe in the app-x.x.x directory and one in the parent
|
||||
// directory: we need to run the one in the parent directory, because it discovers
|
||||
@@ -28,20 +28,18 @@ function runUpdateExe(args) {
|
||||
console.log(`Spawning '${updateExe}' with args '${args}'`);
|
||||
return new Promise(resolve => {
|
||||
spawn(updateExe, args, {
|
||||
detached: true,
|
||||
detached: true,
|
||||
}).on('close', resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function checkSquirrelHooks() {
|
||||
function checkSquirrelHooks(): boolean {
|
||||
if (process.platform !== 'win32') return false;
|
||||
|
||||
const cmd = process.argv[1];
|
||||
const target = path.basename(process.execPath);
|
||||
if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
|
||||
Promise.resolve().then(() => {
|
||||
return runUpdateExe(['--createShortcut=' + target]);
|
||||
}).then(() => {
|
||||
runUpdateExe(['--createShortcut=' + target]).then(() => {
|
||||
// remove the old 'Riot' shortcuts, if they exist (update.exe --removeShortcut doesn't work
|
||||
// because it always uses the name of the product as the name of the shortcut: the only variable
|
||||
// is what executable you're linking to)
|
||||
@@ -51,6 +49,19 @@ function checkSquirrelHooks() {
|
||||
appDataDir, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'New Vector Ltd',
|
||||
);
|
||||
return fsProm.rmdir(startMenuDir, { recursive: true });
|
||||
}).then(() => {
|
||||
// same for 'Element (Riot) which is old now too (we have to try to delete both because
|
||||
// we don't know what version we're updating from, but of course we do know this version
|
||||
// is 'Element' so the two old ones are all safe to delete).
|
||||
const appDataDir = process.env.APPDATA;
|
||||
if (!appDataDir) return;
|
||||
const oldStartMenuLink = path.join(
|
||||
appDataDir, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Element', 'Element (Riot).lnk',
|
||||
);
|
||||
return fsProm.unlink(oldStartMenuLink).catch(() => {});
|
||||
}).then(() => {
|
||||
const oldDesktopShortcut = path.join(app.getPath('desktop'), 'Element (Riot).lnk');
|
||||
return fsProm.unlink(oldDesktopShortcut).catch(() => {});
|
||||
}).then(() => {
|
||||
const oldDesktopShortcut = path.join(app.getPath('desktop'), 'Riot.lnk');
|
||||
return fsProm.unlink(oldDesktopShortcut).catch(() => {});
|
||||
@@ -70,4 +81,6 @@ function checkSquirrelHooks() {
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = checkSquirrelHooks;
|
||||
if (checkSquirrelHooks()) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -15,57 +15,49 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const {app, Tray, Menu, nativeImage} = require('electron');
|
||||
const pngToIco = require('png-to-ico');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
import { app, Tray, Menu, nativeImage } from "electron";
|
||||
import pngToIco from "png-to-ico";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
let trayIcon = null;
|
||||
import { _t } from "./language-helper";
|
||||
|
||||
exports.hasTray = function hasTray() {
|
||||
let trayIcon: Tray = null;
|
||||
|
||||
export function hasTray(): boolean {
|
||||
return (trayIcon !== null);
|
||||
};
|
||||
}
|
||||
|
||||
exports.destroy = function() {
|
||||
export function destroy(): void {
|
||||
if (trayIcon) {
|
||||
trayIcon.destroy();
|
||||
trayIcon = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.create = function(config) {
|
||||
function toggleWin(): void {
|
||||
if (global.mainWindow.isVisible() && !global.mainWindow.isMinimized() && global.mainWindow.isFocused()) {
|
||||
global.mainWindow.hide();
|
||||
} else {
|
||||
if (global.mainWindow.isMinimized()) global.mainWindow.restore();
|
||||
if (!global.mainWindow.isVisible()) global.mainWindow.show();
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
}
|
||||
|
||||
interface IConfig {
|
||||
icon_path: string; // eslint-disable-line camelcase
|
||||
brand: string;
|
||||
}
|
||||
|
||||
export function create(config: IConfig): void {
|
||||
// no trays on darwin
|
||||
if (process.platform === 'darwin' || trayIcon) return;
|
||||
|
||||
const toggleWin = function() {
|
||||
if (global.mainWindow.isVisible() && !global.mainWindow.isMinimized()) {
|
||||
global.mainWindow.hide();
|
||||
} else {
|
||||
if (global.mainWindow.isMinimized()) global.mainWindow.restore();
|
||||
if (!global.mainWindow.isVisible()) global.mainWindow.show();
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: `Show/Hide ${config.brand}`,
|
||||
click: toggleWin,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Quit',
|
||||
click: function() {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const defaultIcon = nativeImage.createFromPath(config.icon_path);
|
||||
|
||||
trayIcon = new Tray(defaultIcon);
|
||||
trayIcon.setToolTip(config.brand);
|
||||
trayIcon.setContextMenu(contextMenu);
|
||||
initApplicationMenu();
|
||||
trayIcon.on('click', toggleWin);
|
||||
|
||||
let lastFavicon = null;
|
||||
@@ -103,4 +95,26 @@ exports.create = function(config) {
|
||||
global.mainWindow.webContents.on('page-title-updated', function(ev, title) {
|
||||
trayIcon.setToolTip(title);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function initApplicationMenu(): void {
|
||||
if (!trayIcon) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: _t('Show/Hide'),
|
||||
click: toggleWin,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: _t('Quit'),
|
||||
click: function() {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
trayIcon.setContextMenu(contextMenu);
|
||||
}
|
||||
@@ -1,16 +1,32 @@
|
||||
const { app, autoUpdater, ipcMain } = require('electron');
|
||||
/*
|
||||
Copyright 2016-2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { app, autoUpdater, ipcMain } from "electron";
|
||||
|
||||
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
|
||||
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
|
||||
|
||||
function installUpdate() {
|
||||
function installUpdate(): void {
|
||||
// for some reason, quitAndInstall does not fire the
|
||||
// before-quit event, so we need to set the flag here.
|
||||
global.appQuitting = true;
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
function pollForUpdates() {
|
||||
function pollForUpdates(): void {
|
||||
try {
|
||||
autoUpdater.checkForUpdates();
|
||||
} catch (e) {
|
||||
@@ -18,8 +34,7 @@ function pollForUpdates() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {};
|
||||
module.exports.start = function startAutoUpdate(updateBaseUrl) {
|
||||
export function start(updateBaseUrl: string): void {
|
||||
if (updateBaseUrl.slice(-1) !== '/') {
|
||||
updateBaseUrl = updateBaseUrl + '/';
|
||||
}
|
||||
@@ -48,6 +63,7 @@ module.exports.start = function startAutoUpdate(updateBaseUrl) {
|
||||
}
|
||||
|
||||
if (url) {
|
||||
console.log(`Update URL: ${url}`);
|
||||
autoUpdater.setFeedURL(url);
|
||||
// We check for updates ourselves rather than using 'updater' because we need to
|
||||
// do it in the main process (and we don't really need to check every 10 minutes:
|
||||
@@ -63,18 +79,25 @@ module.exports.start = function startAutoUpdate(updateBaseUrl) {
|
||||
// will fail if running in debug mode
|
||||
console.log('Couldn\'t enable update checking', err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ipcMain.on('install_update', installUpdate);
|
||||
ipcMain.on('check_updates', pollForUpdates);
|
||||
|
||||
function ipcChannelSendUpdateStatus(status) {
|
||||
function ipcChannelSendUpdateStatus(status: boolean | string): void {
|
||||
if (!global.mainWindow) return;
|
||||
global.mainWindow.webContents.send('check_updates', status);
|
||||
}
|
||||
|
||||
interface ICachedUpdate {
|
||||
releaseNotes: string;
|
||||
releaseName: string;
|
||||
releaseDate: Date;
|
||||
updateURL: string;
|
||||
}
|
||||
|
||||
// cache the latest update which has been downloaded as electron offers no api to read it
|
||||
let latestUpdateDownloaded;
|
||||
let latestUpdateDownloaded: ICachedUpdate;
|
||||
autoUpdater.on('update-available', function() {
|
||||
ipcChannelSendUpdateStatus(true);
|
||||
}).on('update-not-available', function() {
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const {app, shell, Menu} = require('electron');
|
||||
|
||||
// Menu template from http://electron.atom.io/docs/api/menu/, edited
|
||||
const template = [
|
||||
{
|
||||
label: '&Edit',
|
||||
submenu: [
|
||||
{ role: 'undo' },
|
||||
{ role: 'redo' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'cut' },
|
||||
{ role: 'copy' },
|
||||
{ role: 'paste' },
|
||||
{ role: 'pasteandmatchstyle' },
|
||||
{ role: 'delete' },
|
||||
{ role: 'selectall' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '&View',
|
||||
submenu: [
|
||||
{ type: 'separator' },
|
||||
{ role: 'resetzoom' },
|
||||
{ role: 'zoomin', accelerator: 'CommandOrControl+=' },
|
||||
{ role: 'zoomout' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Preferences',
|
||||
accelerator: 'Command+,', // Mac-only accelerator
|
||||
click() { global.mainWindow.webContents.send('preferences'); },
|
||||
},
|
||||
{ role: 'togglefullscreen' },
|
||||
{ role: 'toggledevtools' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '&Window',
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{ role: 'minimize' },
|
||||
{ role: 'close' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '&Help',
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Element Help',
|
||||
click() { shell.openExternal('https://element.io/help'); },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// macOS has specific menu conventions...
|
||||
if (process.platform === 'darwin') {
|
||||
template.unshift({
|
||||
// first macOS menu is the name of the app
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{ role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
role: 'services',
|
||||
submenu: [],
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideothers' },
|
||||
{ role: 'unhide' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'quit' },
|
||||
],
|
||||
});
|
||||
// Edit menu.
|
||||
// This has a 'speech' section on macOS
|
||||
template[1].submenu.push(
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Speech',
|
||||
submenu: [
|
||||
{ role: 'startspeaking' },
|
||||
{ role: 'stopspeaking' },
|
||||
],
|
||||
});
|
||||
|
||||
// Window menu.
|
||||
// This also has specific functionality on macOS
|
||||
template[3].submenu = [
|
||||
{
|
||||
label: 'Close',
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
role: 'close',
|
||||
},
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'CmdOrCtrl+M',
|
||||
role: 'minimize',
|
||||
},
|
||||
{
|
||||
label: 'Zoom',
|
||||
role: 'zoom',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Bring All to Front',
|
||||
role: 'front',
|
||||
},
|
||||
];
|
||||
} else {
|
||||
template.unshift({
|
||||
label: '&File',
|
||||
submenu: [
|
||||
// For some reason, 'about' does not seem to work on windows.
|
||||
/*{
|
||||
role: 'about'
|
||||
},*/
|
||||
{ role: 'quit' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Menu.buildFromTemplate(template);
|
||||
|
||||
246
src/vectormenu.ts
Normal file
246
src/vectormenu.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { app, shell, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
|
||||
|
||||
import { _t } from './language-helper';
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
export function buildMenuTemplate(): Menu {
|
||||
// Menu template from http://electron.atom.io/docs/api/menu/, edited
|
||||
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
|
||||
{
|
||||
label: _t('Edit'),
|
||||
accelerator: 'e',
|
||||
submenu: [
|
||||
{
|
||||
role: 'undo',
|
||||
label: _t('Undo'),
|
||||
},
|
||||
{
|
||||
role: 'redo',
|
||||
label: _t('Redo'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
role: 'cut',
|
||||
label: _t('Cut'),
|
||||
},
|
||||
{
|
||||
role: 'copy',
|
||||
label: _t('Copy'),
|
||||
},
|
||||
{
|
||||
role: 'paste',
|
||||
label: _t('Paste'),
|
||||
},
|
||||
{
|
||||
role: 'pasteAndMatchStyle',
|
||||
label: _t('Paste and Match Style'),
|
||||
},
|
||||
{
|
||||
role: 'delete',
|
||||
label: _t('Delete'),
|
||||
},
|
||||
{
|
||||
role: 'selectAll',
|
||||
label: _t('Select All'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: _t('View'),
|
||||
accelerator: 'V',
|
||||
submenu: [
|
||||
{ type: 'separator' },
|
||||
{
|
||||
role: 'resetZoom',
|
||||
accelerator: 'CmdOrCtrl+Num0',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'zoomIn',
|
||||
accelerator: 'CmdOrCtrl+NumAdd',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'zoomOut',
|
||||
accelerator: 'CmdOrCtrl+NumSub',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'resetZoom',
|
||||
label: _t('Actual Size'),
|
||||
},
|
||||
{
|
||||
role: 'zoomIn',
|
||||
label: _t('Zoom In'),
|
||||
},
|
||||
{
|
||||
role: 'zoomOut',
|
||||
label: _t('Zoom Out'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
// in macOS the Preferences menu item goes in the first menu
|
||||
...(!isMac ? [{
|
||||
label: _t('Preferences'),
|
||||
click() { global.mainWindow.webContents.send('preferences'); },
|
||||
}] : []),
|
||||
{
|
||||
role: 'togglefullscreen',
|
||||
label: _t('Toggle Full Screen'),
|
||||
},
|
||||
{
|
||||
role: 'toggleDevTools',
|
||||
label: _t('Toggle Developer Tools'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: _t('Window'),
|
||||
accelerator: 'w',
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{
|
||||
role: 'minimize',
|
||||
label: _t('Minimize'),
|
||||
},
|
||||
{
|
||||
role: 'close',
|
||||
label: _t('Close'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: _t('Help'),
|
||||
accelerator: 'h',
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: _t('Element Help'),
|
||||
click() { shell.openExternal('https://element.io/help'); },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// macOS has specific menu conventions...
|
||||
if (isMac) {
|
||||
template.unshift({
|
||||
// first macOS menu is the name of the app
|
||||
role: 'appMenu',
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{
|
||||
role: 'about',
|
||||
label: _t('About') + ' ' + app.name,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: _t('Preferences') + '…',
|
||||
accelerator: 'Command+,', // Mac-only accelerator
|
||||
click() { global.mainWindow.webContents.send('preferences'); },
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
role: 'services',
|
||||
label: _t('Services'),
|
||||
submenu: [],
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
role: 'hide',
|
||||
label: _t('Hide'),
|
||||
},
|
||||
{
|
||||
role: 'hideOthers',
|
||||
label: _t('Hide Others'),
|
||||
},
|
||||
{
|
||||
role: 'unhide',
|
||||
label: _t('Unhide'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
role: 'quit',
|
||||
label: _t('Quit'),
|
||||
},
|
||||
],
|
||||
});
|
||||
// Edit menu.
|
||||
// This has a 'speech' section on macOS
|
||||
(template[1].submenu as MenuItemConstructorOptions[]).push(
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: _t('Speech'),
|
||||
submenu: [
|
||||
{
|
||||
role: 'startSpeaking',
|
||||
label: _t('Start Speaking'),
|
||||
},
|
||||
{
|
||||
role: 'stopSpeaking',
|
||||
label: _t('Stop Speaking'),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Window menu.
|
||||
// This also has specific functionality on macOS
|
||||
template[3].submenu = [
|
||||
{
|
||||
label: _t('Close'),
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
role: 'close',
|
||||
},
|
||||
{
|
||||
label: _t('Minimize'),
|
||||
accelerator: 'CmdOrCtrl+M',
|
||||
role: 'minimize',
|
||||
},
|
||||
{
|
||||
label: _t('Zoom'),
|
||||
role: 'zoom',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: _t('Bring All to Front'),
|
||||
role: 'front',
|
||||
},
|
||||
];
|
||||
} else {
|
||||
template.unshift({
|
||||
label: _t('File'),
|
||||
accelerator: 'f',
|
||||
submenu: [
|
||||
// For some reason, 'about' does not seem to work on windows.
|
||||
/*{
|
||||
role: 'about',
|
||||
label: _t('About'),
|
||||
},*/
|
||||
{
|
||||
role: 'quit',
|
||||
label: _t('Quit'),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return Menu.buildFromTemplate(template);
|
||||
}
|
||||
@@ -1,18 +1,50 @@
|
||||
const {clipboard, nativeImage, Menu, MenuItem, shell, dialog, ipcMain} = require('electron');
|
||||
const url = require('url');
|
||||
const fs = require('fs');
|
||||
const request = require('request');
|
||||
const path = require('path');
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
clipboard,
|
||||
nativeImage,
|
||||
Menu,
|
||||
MenuItem,
|
||||
shell,
|
||||
dialog,
|
||||
ipcMain,
|
||||
NativeImage,
|
||||
WebContents,
|
||||
ContextMenuParams,
|
||||
DownloadItem,
|
||||
MenuItemConstructorOptions,
|
||||
IpcMainEvent,
|
||||
} from 'electron';
|
||||
import url from 'url';
|
||||
import fs from 'fs';
|
||||
import request from 'request';
|
||||
import path from 'path';
|
||||
|
||||
import { _t } from './language-helper';
|
||||
|
||||
const MAILTO_PREFIX = "mailto:";
|
||||
|
||||
const PERMITTED_URL_SCHEMES = [
|
||||
const PERMITTED_URL_SCHEMES: string[] = [
|
||||
'http:',
|
||||
'https:',
|
||||
MAILTO_PREFIX,
|
||||
];
|
||||
|
||||
function safeOpenURL(target) {
|
||||
function safeOpenURL(target: string): void {
|
||||
// openExternal passes the target to open/start/xdg-open,
|
||||
// so put fairly stringent limits on what can be opened
|
||||
// (for instance, open /bin/sh does indeed open a terminal
|
||||
@@ -27,7 +59,7 @@ function safeOpenURL(target) {
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowOrNavigate(ev, target) {
|
||||
function onWindowOrNavigate(ev: Event, target: string): void {
|
||||
// always prevent the default: if something goes wrong,
|
||||
// we don't want to end up opening it in the electron
|
||||
// app, as we could end up opening any sort of random
|
||||
@@ -36,7 +68,7 @@ function onWindowOrNavigate(ev, target) {
|
||||
safeOpenURL(target);
|
||||
}
|
||||
|
||||
function writeNativeImage(filePath, img) {
|
||||
function writeNativeImage(filePath: string, img: NativeImage): Promise<void> {
|
||||
switch (filePath.split('.').pop().toLowerCase()) {
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
@@ -49,8 +81,7 @@ function writeNativeImage(filePath, img) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onLinkContextMenu(ev, params) {
|
||||
function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: WebContents): void {
|
||||
let url = params.linkURL || params.srcURL;
|
||||
|
||||
if (url.startsWith('vector://vector/webapp')) {
|
||||
@@ -73,9 +104,10 @@ function onLinkContextMenu(ev, params) {
|
||||
|
||||
if (params.hasImageContents) {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: '&Copy image',
|
||||
label: _t('Copy image'),
|
||||
accelerator: 'c',
|
||||
click() {
|
||||
ev.sender.copyImageAt(params.x, params.y);
|
||||
webContents.copyImageAt(params.x, params.y);
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -85,14 +117,18 @@ function onLinkContextMenu(ev, params) {
|
||||
// Special-case e-mail URLs to strip the `mailto:` like modern browsers do
|
||||
if (url.startsWith(MAILTO_PREFIX)) {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: 'Copy email &address',
|
||||
label: _t('Copy email address'),
|
||||
accelerator: 'a',
|
||||
click() {
|
||||
clipboard.writeText(url.substr(MAILTO_PREFIX.length));
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: 'Copy link &address',
|
||||
label: params.hasImageContents
|
||||
? _t('Copy image address')
|
||||
: _t('Copy link address'),
|
||||
accelerator: 'a',
|
||||
click() {
|
||||
clipboard.writeText(url);
|
||||
},
|
||||
@@ -104,10 +140,11 @@ function onLinkContextMenu(ev, params) {
|
||||
// only the renderer can resolve them so don't give the user an option to.
|
||||
if (params.hasImageContents && !url.startsWith('blob:')) {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: 'Sa&ve image as...',
|
||||
label: _t('Save image as...'),
|
||||
accelerator: 's',
|
||||
async click() {
|
||||
const targetFileName = params.titleText || "image.png";
|
||||
const {filePath} = await dialog.showSaveDialog({
|
||||
const { filePath } = await dialog.showSaveDialog({
|
||||
defaultPath: targetFileName,
|
||||
});
|
||||
|
||||
@@ -123,8 +160,8 @@ function onLinkContextMenu(ev, params) {
|
||||
console.error(err);
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: "Failed to save image",
|
||||
message: "The image failed to save",
|
||||
title: _t("Failed to save image"),
|
||||
message: _t("The image failed to save"),
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -136,8 +173,8 @@ function onLinkContextMenu(ev, params) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function _CutCopyPasteSelectContextMenus(params) {
|
||||
const options = [];
|
||||
function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemConstructorOptions[] {
|
||||
const options: MenuItemConstructorOptions[] = [];
|
||||
|
||||
if (params.misspelledWord) {
|
||||
params.dictionarySuggestions.forEach(word => {
|
||||
@@ -151,7 +188,7 @@ function _CutCopyPasteSelectContextMenus(params) {
|
||||
options.push({
|
||||
type: 'separator',
|
||||
}, {
|
||||
label: 'Add to dictionary',
|
||||
label: _t('Add to dictionary'),
|
||||
click: (menuItem, browserWindow) => {
|
||||
browserWindow.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord);
|
||||
},
|
||||
@@ -162,29 +199,33 @@ function _CutCopyPasteSelectContextMenus(params) {
|
||||
|
||||
options.push({
|
||||
role: 'cut',
|
||||
label: 'Cu&t',
|
||||
label: _t('Cut'),
|
||||
accelerator: 't',
|
||||
enabled: params.editFlags.canCut,
|
||||
}, {
|
||||
role: 'copy',
|
||||
label: '&Copy',
|
||||
label: _t('Copy'),
|
||||
accelerator: 'c',
|
||||
enabled: params.editFlags.canCopy,
|
||||
}, {
|
||||
role: 'paste',
|
||||
label: '&Paste',
|
||||
label: _t('Paste'),
|
||||
accelerator: 'p',
|
||||
enabled: params.editFlags.canPaste,
|
||||
}, {
|
||||
role: 'pasteandmatchstyle',
|
||||
role: 'pasteAndMatchStyle',
|
||||
enabled: params.editFlags.canPaste,
|
||||
}, {
|
||||
role: 'selectall',
|
||||
label: "Select &All",
|
||||
role: 'selectAll',
|
||||
label: _t("Select All"),
|
||||
accelerator: 'a',
|
||||
enabled: params.editFlags.canSelectAll,
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
function onSelectedContextMenu(ev, params) {
|
||||
const items = _CutCopyPasteSelectContextMenus(params);
|
||||
const items = cutCopyPasteSelectContextMenus(params);
|
||||
const popupMenu = Menu.buildFromTemplate(items);
|
||||
|
||||
// popup() requires an options object even for no options
|
||||
@@ -192,12 +233,13 @@ function onSelectedContextMenu(ev, params) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function onEditableContextMenu(ev, params) {
|
||||
const items = [
|
||||
function onEditableContextMenu(ev: Event, params: ContextMenuParams) {
|
||||
const items: MenuItemConstructorOptions[] = [
|
||||
{ role: 'undo' },
|
||||
{ role: 'redo', enabled: params.editFlags.canRedo },
|
||||
{ type: 'separator' },
|
||||
].concat(_CutCopyPasteSelectContextMenus(params));
|
||||
...cutCopyPasteSelectContextMenus(params),
|
||||
];
|
||||
|
||||
const popupMenu = Menu.buildFromTemplate(items);
|
||||
|
||||
@@ -206,20 +248,25 @@ function onEditableContextMenu(ev, params) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
ipcMain.on('userDownloadOpen', function(ev, {path}) {
|
||||
shell.openPath(path);
|
||||
let userDownloadIndex = 0;
|
||||
const userDownloadMap = new Map<number, string>(); // Map from id to path
|
||||
ipcMain.on('userDownloadAction', function(ev: IpcMainEvent, { id, open = false }) {
|
||||
if (open) {
|
||||
shell.openPath(userDownloadMap.get(id));
|
||||
}
|
||||
userDownloadMap.delete(id);
|
||||
});
|
||||
|
||||
module.exports = (webContents) => {
|
||||
export default (webContents: WebContents): void => {
|
||||
webContents.on('new-window', onWindowOrNavigate);
|
||||
webContents.on('will-navigate', (ev, target) => {
|
||||
webContents.on('will-navigate', (ev: Event, target: string): void => {
|
||||
if (target.startsWith("vector://")) return;
|
||||
return onWindowOrNavigate(ev, target);
|
||||
});
|
||||
|
||||
webContents.on('context-menu', function(ev, params) {
|
||||
webContents.on('context-menu', function(ev: Event, params: ContextMenuParams): void {
|
||||
if (params.linkURL || params.srcURL) {
|
||||
onLinkContextMenu(ev, params);
|
||||
onLinkContextMenu(ev, params, webContents);
|
||||
} else if (params.selectionText) {
|
||||
onSelectedContextMenu(ev, params);
|
||||
} else if (params.isEditable) {
|
||||
@@ -227,12 +274,14 @@ module.exports = (webContents) => {
|
||||
}
|
||||
});
|
||||
|
||||
webContents.session.on('will-download', (event, item) => {
|
||||
webContents.session.on('will-download', (event: Event, item: DownloadItem): void => {
|
||||
item.once('done', (event, state) => {
|
||||
if (state === 'completed') {
|
||||
const savePath = item.getSavePath();
|
||||
const id = userDownloadIndex++;
|
||||
userDownloadMap.set(id, savePath);
|
||||
webContents.send('userDownloadCompleted', {
|
||||
path: savePath,
|
||||
id,
|
||||
name: path.basename(savePath),
|
||||
});
|
||||
}
|
||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2016",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"lib": [
|
||||
"es2019",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user