Compare commits
1362 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc1a0c554f | ||
|
|
08cdf21aee | ||
|
|
cd7bc3bfd3 | ||
|
|
dea557619c | ||
|
|
961f279a82 | ||
|
|
e0be012737 | ||
|
|
d176e83e94 | ||
|
|
c2d2287541 | ||
|
|
4d8bdfa07e | ||
|
|
51de4a3347 | ||
|
|
3d45aa7455 | ||
|
|
8f86cfa2d3 | ||
|
|
cad2d72bfe | ||
|
|
0c8223da03 | ||
|
|
7ceaabb166 | ||
|
|
67e14ff363 | ||
|
|
b20f6ff8c1 | ||
|
|
0050e1557d | ||
|
|
654a025a9f | ||
|
|
5368c87dc1 | ||
|
|
1c2de1e483 | ||
|
|
a64e1cb936 | ||
|
|
3903954bf2 | ||
|
|
7c27153ec5 | ||
|
|
4c3a723aa8 | ||
|
|
c4a590f6af | ||
|
|
00c26088b1 | ||
|
|
c6def48fe0 | ||
|
|
874e32699a | ||
|
|
b137c82a7a | ||
|
|
23060a6833 | ||
|
|
0db97b8f5b | ||
|
|
3404521d5d | ||
|
|
6369d76a74 | ||
|
|
6374526f3c | ||
|
|
78105bb4db | ||
|
|
87db89b67a | ||
|
|
87f7324065 | ||
|
|
02d6a10f67 | ||
|
|
029602fc73 | ||
|
|
5aa9592c71 | ||
|
|
bb70622b55 | ||
|
|
b16fa4171a | ||
|
|
7850f7e505 | ||
|
|
f29e0bfe6d | ||
|
|
f9db7f017d | ||
|
|
71b149de76 | ||
|
|
baed5bec46 | ||
|
|
3c91f90dc9 | ||
|
|
ba5e7d4013 | ||
|
|
9797c2fb4b | ||
|
|
972a391296 | ||
|
|
4e41accc56 | ||
|
|
c1290fd24d | ||
|
|
0a9f02abcc | ||
|
|
0021fb37f3 | ||
|
|
527c988574 | ||
|
|
1c926941d3 | ||
|
|
80c1ec0502 | ||
|
|
eaff0abfb0 | ||
|
|
2ca871a028 | ||
|
|
e225d3e370 | ||
|
|
9a933e2ce5 | ||
|
|
efbea0e942 | ||
|
|
28939d3146 | ||
|
|
307c4f3dd1 | ||
|
|
536167a6c8 | ||
|
|
fe64b04339 | ||
|
|
31878d8a44 | ||
|
|
6f3b70dbb0 | ||
|
|
597705716b | ||
|
|
6db5329d9c | ||
|
|
ea82b90d35 | ||
|
|
706ffbec3c | ||
|
|
f3df86872b | ||
|
|
4c3ea13413 | ||
|
|
c31b65d093 | ||
|
|
bc714ba3a0 | ||
|
|
b7204bf868 | ||
|
|
c4d246cd72 | ||
|
|
b582cf0a5f | ||
|
|
f025afc297 | ||
|
|
57126efe86 | ||
|
|
2ecf65f057 | ||
|
|
d907421ad4 | ||
|
|
ef519231d6 | ||
|
|
83272f5cc5 | ||
|
|
5b1fea46cb | ||
|
|
a9c575b4d0 | ||
|
|
a24b568b73 | ||
|
|
afcf9de8d4 | ||
|
|
9ffedf5e7d | ||
|
|
956deca844 | ||
|
|
ea063ab8b0 | ||
|
|
378126e746 | ||
|
|
41c6294be2 | ||
|
|
ba1e166ac8 | ||
|
|
ea860807c4 | ||
|
|
01d4846526 | ||
|
|
9b70e2e25f | ||
|
|
4d8f5d4df1 | ||
|
|
fa535996ae | ||
|
|
17e215036d | ||
|
|
8ad0ff24f8 | ||
|
|
693285ac0e | ||
|
|
53a16158c7 | ||
|
|
beba4d2ae3 | ||
|
|
36450764f3 | ||
|
|
3996d23b19 | ||
|
|
bf887e82fe | ||
|
|
f5d7f3ca9f | ||
|
|
16bb75be66 | ||
|
|
537194608e | ||
|
|
789c7b60cc | ||
|
|
81d437ac1e | ||
|
|
61c5253dbf | ||
|
|
608c1b0208 | ||
|
|
d11bcdad5f | ||
|
|
686a3c693a | ||
|
|
bee4ca2b28 | ||
|
|
231adbcd31 | ||
|
|
89d514a532 | ||
|
|
6b1d138918 | ||
|
|
5978aff632 | ||
|
|
92096c40ac | ||
|
|
6c4bf58ba5 | ||
|
|
5f8170f4cd | ||
|
|
e08f97a549 | ||
|
|
971314d75a | ||
|
|
c3fa6ff805 | ||
|
|
d1fbbf90c0 | ||
|
|
c5032ba1bc | ||
|
|
237f4df08e | ||
|
|
5b95986705 | ||
|
|
ec1a2f6abf | ||
|
|
811086ac8e | ||
|
|
f20efc57f3 | ||
|
|
c0e5a1ba3b | ||
|
|
5828ab1077 | ||
|
|
acb85b7b72 | ||
|
|
e9884768f6 | ||
|
|
f10bc8eef1 | ||
|
|
708fd640f7 | ||
|
|
14eec6d982 | ||
|
|
69bedf0d37 | ||
|
|
83145e80e5 | ||
|
|
5206410f21 | ||
|
|
0cd895b417 | ||
|
|
6c88201e23 | ||
|
|
7bcd9ff3d6 | ||
|
|
641a5c244c | ||
|
|
323c5d0732 | ||
|
|
6257019926 | ||
|
|
c9c58ab0ec | ||
|
|
cf92e7f64b | ||
|
|
8371006d90 | ||
|
|
3b109f7612 | ||
|
|
ebc2ad54d6 | ||
|
|
4a1b04e57b | ||
|
|
205676a97d | ||
|
|
0456cadff6 | ||
|
|
aa6d584ae3 | ||
|
|
ef07a6035e | ||
|
|
86b66120d0 | ||
|
|
3bdb330f5b | ||
|
|
4f8d3b0e2b | ||
|
|
87fd136e21 | ||
|
|
cab5bf8849 | ||
|
|
d74dfc9ee7 | ||
|
|
c305b72b91 | ||
|
|
dc1563d916 | ||
|
|
906d42688e | ||
|
|
b3bff92cc2 | ||
|
|
ae7820e8f2 | ||
|
|
3023ea5335 | ||
|
|
22060ac5a4 | ||
|
|
3f3a31e475 | ||
|
|
231306ea7c | ||
|
|
002339fb86 | ||
|
|
8b38a326cb | ||
|
|
5edb5f6487 | ||
|
|
61251d20c1 | ||
|
|
ccf7db7cc2 | ||
|
|
e1c1937855 | ||
|
|
9471c30f89 | ||
|
|
40545bd48e | ||
|
|
6b02bf8e8c | ||
|
|
0d05e607f0 | ||
|
|
717aa94ba9 | ||
|
|
9caba57c14 | ||
|
|
70f48343ee | ||
|
|
f17f103d12 | ||
|
|
27e311f7e8 | ||
|
|
5258bf9c84 | ||
|
|
10f0631487 | ||
|
|
97fc92b1bb | ||
|
|
06718c580e | ||
|
|
15528ef8d7 | ||
|
|
b71f96e4e5 | ||
|
|
278de1398b | ||
|
|
c5459a2f19 | ||
|
|
c1d4a0dd28 | ||
|
|
691fe611d6 | ||
|
|
6a1af891b7 | ||
|
|
35b3013386 | ||
|
|
d1a9695a35 | ||
|
|
c6beaa1631 | ||
|
|
33f0eaada0 | ||
|
|
c5fa84cd28 | ||
|
|
bacb284415 | ||
|
|
b0319ec0f1 | ||
|
|
6e0f83a298 | ||
|
|
747408871d | ||
|
|
0129da4dd6 | ||
|
|
3f3772463b | ||
|
|
4de042bf58 | ||
|
|
8c523be6f5 | ||
|
|
078493912c | ||
|
|
cbf105f2a0 | ||
|
|
bba6664b1c | ||
|
|
b06d045188 | ||
|
|
79d164309f | ||
|
|
f2afcc0bc8 | ||
|
|
43f82cd95d | ||
|
|
2b70cb436e | ||
|
|
9a861f46d0 | ||
|
|
bb28903c8e | ||
|
|
e8eadc5d20 | ||
|
|
e741a4f294 | ||
|
|
1678a25fd6 | ||
|
|
ff70b0a2c8 | ||
|
|
3803c64226 | ||
|
|
35a541c824 | ||
|
|
ab8420f5a9 | ||
|
|
fa46757830 | ||
|
|
ed35a90cfb | ||
|
|
d8bce0f202 | ||
|
|
e6f600bb47 | ||
|
|
519af6a5d0 | ||
|
|
c6c4aff8ae | ||
|
|
6d4abac016 | ||
|
|
d34cb80b68 | ||
|
|
57641f7228 | ||
|
|
9ae86f2585 | ||
|
|
1e7e9d6101 | ||
|
|
8bbc16ed4b | ||
|
|
4745b3efeb | ||
|
|
09f79b94dd | ||
|
|
42357dee0b | ||
|
|
2e73cd6c4d | ||
|
|
161978ab05 | ||
|
|
994bc9279f | ||
|
|
fb08910db3 | ||
|
|
e607f49ae6 | ||
|
|
52cf1f6a4b | ||
|
|
61f9966fe0 | ||
|
|
5f7cdb8d42 | ||
|
|
901c0e7e41 | ||
|
|
98cad8779c | ||
|
|
fc597ba86e | ||
|
|
f5bd8abfda | ||
|
|
8be72e6c27 | ||
|
|
2740013625 | ||
|
|
119e03b64f | ||
|
|
c6dbeee245 | ||
|
|
feb3a13a71 | ||
|
|
c80d1c15d2 | ||
|
|
b9ba795cb0 | ||
|
|
598b841bf7 | ||
|
|
93b3857a9b | ||
|
|
5414b5d189 | ||
|
|
b4e362d700 | ||
|
|
eabb91da1e | ||
|
|
fe574b008b | ||
|
|
b8fe8a4363 | ||
|
|
6acbdc442d | ||
|
|
f7cd5cb76f | ||
|
|
e2195de719 | ||
|
|
3921aa4e54 | ||
|
|
48ca0254fe | ||
|
|
375a7605ae | ||
|
|
8097b150a0 | ||
|
|
67cf4230ac | ||
|
|
b06cff7928 | ||
|
|
d65374f7f9 | ||
|
|
f0b0999f65 | ||
|
|
e55845ac52 | ||
|
|
229693dd71 | ||
|
|
5e8e6f4c8b | ||
|
|
98f9e2f870 | ||
|
|
2b51cda6da | ||
|
|
65a31cc8a9 | ||
|
|
737f912abb | ||
|
|
f6957aa367 | ||
|
|
ad79f67ab6 | ||
|
|
a9fcf106ab | ||
|
|
0f47c9956c | ||
|
|
d4ac5f829f | ||
|
|
d31a675ca8 | ||
|
|
96f620829f | ||
|
|
5fa0f62ccc | ||
|
|
b825862f95 | ||
|
|
8d06b54091 | ||
|
|
df5143c818 | ||
|
|
ca6a3a2ad9 | ||
|
|
6934b7ed56 | ||
|
|
987822bd5a | ||
|
|
189c8aca79 | ||
|
|
b6aa7f430c | ||
|
|
d8e772cfc4 | ||
|
|
ec959f0052 | ||
|
|
3d813e68e9 | ||
|
|
37b4734bbe | ||
|
|
b613a742f5 | ||
|
|
72de35a2a1 | ||
|
|
970a029cea | ||
|
|
d5a5abe202 | ||
|
|
6ff253d0d8 | ||
|
|
749d26c8f4 | ||
|
|
f1998497f2 | ||
|
|
d0c2ab4cf1 | ||
|
|
7871a3edb5 | ||
|
|
9f51e2c407 | ||
|
|
2930a94c79 | ||
|
|
9026cc4977 | ||
|
|
e3290c1117 | ||
|
|
bacd467b4c | ||
|
|
4988f0603f | ||
|
|
6a944d3e7d | ||
|
|
0e18a12472 | ||
|
|
f202197545 | ||
|
|
496f507684 | ||
|
|
55062c7ec5 | ||
|
|
6c21391ff5 | ||
|
|
a1347e7bf1 | ||
|
|
c7635a362f | ||
|
|
97c0e2dfcd | ||
|
|
dfa7c3b72e | ||
|
|
4073688ba6 | ||
|
|
693867f074 | ||
|
|
f9afa79b01 | ||
|
|
a870ea2389 | ||
|
|
ac2911c222 | ||
|
|
c786980454 | ||
|
|
47534decb3 | ||
|
|
0df242e0e9 | ||
|
|
10650d2cd1 | ||
|
|
186b4abfd8 | ||
|
|
9ce2adceef | ||
|
|
5cd4f88b8c | ||
|
|
683ecb3326 | ||
|
|
67c882648f | ||
|
|
8b0390f354 | ||
|
|
6d954ad7e0 | ||
|
|
646608bf8b | ||
|
|
be90badc8d | ||
|
|
dace762a6c | ||
|
|
7577d2eb05 | ||
|
|
90019023c7 | ||
|
|
5d02c72687 | ||
|
|
81d70a921d | ||
|
|
363453fd06 | ||
|
|
70d383fb1b | ||
|
|
b0c1097f86 | ||
|
|
68a3505091 | ||
|
|
6e47d2f1b5 | ||
|
|
2bcb27b24f | ||
|
|
6ac5fe60d9 | ||
|
|
8356ad7bfa | ||
|
|
69eee7ca0a | ||
|
|
4f944cf01c | ||
|
|
8676481aab | ||
|
|
e66e8b11cb | ||
|
|
1401de2e7d | ||
|
|
15c730a22a | ||
|
|
3b06563960 | ||
|
|
cf33ed58c1 | ||
|
|
c8a6be1454 | ||
|
|
5b7f629996 | ||
|
|
a4c16b896b | ||
|
|
d150ee0d05 | ||
|
|
7bf69d067a | ||
|
|
ccc43cc0ed | ||
|
|
f7091d2fdd | ||
|
|
1e2cd9c2d1 | ||
|
|
a2a3d7a0f9 | ||
|
|
abc306be6c | ||
|
|
ccea483f4f | ||
|
|
84aac88677 | ||
|
|
b30df2115c | ||
|
|
8d46077ac2 | ||
|
|
0441fcf3df | ||
|
|
b223d3b385 | ||
|
|
e033ce6c43 | ||
|
|
6681205337 | ||
|
|
6da1a1077d | ||
|
|
80bae0563d | ||
|
|
9a00ec128c | ||
|
|
bc092d5c77 | ||
|
|
1c9a78a2f1 | ||
|
|
11eb0a89eb | ||
|
|
d7d6b3b4a2 | ||
|
|
f5d95f7314 | ||
|
|
a6d029c556 | ||
|
|
ed9c29d365 | ||
|
|
527c390152 | ||
|
|
a6c794cb11 | ||
|
|
ff462580b0 | ||
|
|
6869c679ef | ||
|
|
4dab36d3b2 | ||
|
|
cfe63fa274 | ||
|
|
c1469f4e39 | ||
|
|
3601b44429 | ||
|
|
b3510d6973 | ||
|
|
d9780239b6 | ||
|
|
fb3ca441ba | ||
|
|
efd0dab316 | ||
|
|
f1b72dfa09 | ||
|
|
4d0b492ba0 | ||
|
|
3bcb447e03 | ||
|
|
108af83ae8 | ||
|
|
275a55266b | ||
|
|
18504ca14e | ||
|
|
6aba9f8eda | ||
|
|
e8494c3dc7 | ||
|
|
caa3cb7d89 | ||
|
|
67dbd9fba9 | ||
|
|
19238b9326 | ||
|
|
8c3fed7559 | ||
|
|
17bb47676e | ||
|
|
757604fd60 | ||
|
|
c75118caa2 | ||
|
|
0b5085ecbb | ||
|
|
64fdb290eb | ||
|
|
a714edbf2b | ||
|
|
735c298de3 | ||
|
|
db07ef7899 | ||
|
|
03caaeef07 | ||
|
|
91793a7a8e | ||
|
|
7323ae5bf5 | ||
|
|
d04cc03c1c | ||
|
|
4fb273284d | ||
|
|
3bfdbad5ba | ||
|
|
890751951c | ||
|
|
ba7e02c3b1 | ||
|
|
622ab27254 | ||
|
|
f17e851435 | ||
|
|
5e84a6c39e | ||
|
|
a86f2720bc | ||
|
|
b174d49f9d | ||
|
|
31ed719df1 | ||
|
|
c19538d6ce | ||
|
|
3adf5fe3fd | ||
|
|
bd0e121f0e | ||
|
|
de1f7861d6 | ||
|
|
5a58c8bda4 | ||
|
|
6396c60645 | ||
|
|
f700bb4efa | ||
|
|
53e5894759 | ||
|
|
8fe05fdff3 | ||
|
|
e002575eae | ||
|
|
e1919c5ea3 | ||
|
|
fcf683c8a7 | ||
|
|
e564d34ca8 | ||
|
|
61d7db6eb6 | ||
|
|
5768f73bbd | ||
|
|
df94dd7487 | ||
|
|
4221781d9b | ||
|
|
e0556789d2 | ||
|
|
9310d92605 | ||
|
|
8a2f57d234 | ||
|
|
d72323bfc5 | ||
|
|
dfd0356609 | ||
|
|
20a7aa03ed | ||
|
|
e02e0219a7 | ||
|
|
d04201d069 | ||
|
|
82de2ca4ec | ||
|
|
18450058d7 | ||
|
|
f20b2593bc | ||
|
|
4d51a5de7e | ||
|
|
8e0b61ca35 | ||
|
|
61a543a694 | ||
|
|
5608e97719 | ||
|
|
d6f27add66 | ||
|
|
740ab7e479 | ||
|
|
ea09a25563 | ||
|
|
5a08c62f1b | ||
|
|
be1d7781c5 | ||
|
|
40ac80a3e9 | ||
|
|
5f747e1a90 | ||
|
|
0c3537ac1a | ||
|
|
e6caf9ece4 | ||
|
|
34c60c69d8 | ||
|
|
5ff915476a | ||
|
|
e5d2ebc57c | ||
|
|
64815b1b0b | ||
|
|
59120d3019 | ||
|
|
9245ab01f1 | ||
|
|
59fe9279d1 | ||
|
|
046c9ef920 | ||
|
|
bca22f26c5 | ||
|
|
2b68b88b14 | ||
|
|
97cedfc712 | ||
|
|
86cb3e9376 | ||
|
|
42993a78ed | ||
|
|
2a5ca9d3df | ||
|
|
323bd79d0e | ||
|
|
60adbffacf | ||
|
|
f827a2963e | ||
|
|
5e293c0f45 | ||
|
|
cd7adfed0a | ||
|
|
902bf3a0a6 | ||
|
|
f736e5243c | ||
|
|
fb2c2ed09a | ||
|
|
ea38968be9 | ||
|
|
562b047362 | ||
|
|
381c9009fb | ||
|
|
c0a4574069 | ||
|
|
de9bf4bb47 | ||
|
|
c57bfccb9e | ||
|
|
52cae92dd7 | ||
|
|
b80b08f04f | ||
|
|
304e5b997a | ||
|
|
6b4dbfbd62 | ||
|
|
000ca35727 | ||
|
|
776fe2ad70 | ||
|
|
9174b32ef1 | ||
|
|
602727b7ad | ||
|
|
56deea9f10 | ||
|
|
8e0be29c13 | ||
|
|
b71b1b5535 | ||
|
|
5b3524f174 | ||
|
|
f9a70a8b04 | ||
|
|
b652ea5024 | ||
|
|
2a37a0cb19 | ||
|
|
761600f325 | ||
|
|
65f14c7d21 | ||
|
|
864ba52bc5 | ||
|
|
fdb5020c0c | ||
|
|
62344b5194 | ||
|
|
0db12fcd22 | ||
|
|
455ee4f91b | ||
|
|
5ca391f914 | ||
|
|
a32abae5a3 | ||
|
|
b2dd3ecf3a | ||
|
|
cc7017b62d | ||
|
|
f852e8a01d | ||
|
|
6ff1c30a4b | ||
|
|
85ea45a64a | ||
|
|
7b258bc229 | ||
|
|
5ba3499f56 | ||
|
|
f4a6a3c4b2 | ||
|
|
5967dc4983 | ||
|
|
bf58c340bc | ||
|
|
dc0b15bdfa | ||
|
|
be6fbc2432 | ||
|
|
51a5542446 | ||
|
|
3938abc5dd | ||
|
|
4961a97ed4 | ||
|
|
fb477fad1e | ||
|
|
a070bccffb | ||
|
|
8e1105b12a | ||
|
|
93e4a04118 | ||
|
|
6e3313b461 | ||
|
|
2e77b0a3c7 | ||
|
|
16c22e07b8 | ||
|
|
99b0f9eb7c | ||
|
|
5248bdd974 | ||
|
|
157ae836c0 | ||
|
|
2639d10f97 | ||
|
|
cc5c636fa5 | ||
|
|
c4a2df75eb | ||
|
|
06c4cae016 | ||
|
|
3eed711b34 | ||
|
|
d9c51832ab | ||
|
|
5011d3b158 | ||
|
|
c10b11a22c | ||
|
|
a5ee48d857 | ||
|
|
343fc1b164 | ||
|
|
55fde43d5d | ||
|
|
940eaf2b7d | ||
|
|
855ecbe11d | ||
|
|
fc8f3eb0ee | ||
|
|
df90921d6c | ||
|
|
0f2c41dddc | ||
|
|
1c76870dcf | ||
|
|
a99c9f4db2 | ||
|
|
7e5373e194 | ||
|
|
560174b99f | ||
|
|
d2947a637a | ||
|
|
8e4dcd9b41 | ||
|
|
9391dc6c2a | ||
|
|
f19044fef2 | ||
|
|
60179aab52 | ||
|
|
de38a32969 | ||
|
|
5f6cc9b37b | ||
|
|
e2fc2b1b09 | ||
|
|
1d97272874 | ||
|
|
69da6a6dd8 | ||
|
|
6cb7ffcd3b | ||
|
|
0ab1dacb1e | ||
|
|
af230b5fa8 | ||
|
|
4720da3f8e | ||
|
|
e2b3fc3519 | ||
|
|
ab3c1509c5 | ||
|
|
91dd029483 | ||
|
|
53fd3f52fa | ||
|
|
d0618c4f49 | ||
|
|
0bce7f3102 | ||
|
|
3d97061d78 | ||
|
|
50f05db29e | ||
|
|
2fdec51a5b | ||
|
|
6d332256b5 | ||
|
|
2b6fbb038a | ||
|
|
48fb578952 | ||
|
|
7fc4b1cb35 | ||
|
|
4725fa4c2d | ||
|
|
5acb6b0b37 | ||
|
|
838608afab | ||
|
|
a11516a984 | ||
|
|
196b352b9a | ||
|
|
e8a12e7ddc | ||
|
|
135c22c99d | ||
|
|
6a13155013 | ||
|
|
6e2a371017 | ||
|
|
de26d1a8ce | ||
|
|
7af765976b | ||
|
|
8f6d98886c | ||
|
|
72e108c4f9 | ||
|
|
e2b695827e | ||
|
|
4b50a8a56e | ||
|
|
737d1d8843 | ||
|
|
eea9e366b8 | ||
|
|
24d0b95cb2 | ||
|
|
77085882fa | ||
|
|
9375ebfb75 | ||
|
|
e2f94c8722 | ||
|
|
b0fd65f442 | ||
|
|
f56a6039e8 | ||
|
|
c44994c0ee | ||
|
|
eeb91374cf | ||
|
|
cd4564d3d3 | ||
|
|
190cd884b5 | ||
|
|
1741cc915e | ||
|
|
37fc73b1ea | ||
|
|
c1e83da35d | ||
|
|
5282796d84 | ||
|
|
866164c4d9 | ||
|
|
5ceb1e641f | ||
|
|
f3cbb9fe90 | ||
|
|
fe4371bf45 | ||
|
|
cde3f33db3 | ||
|
|
82e257b4ea | ||
|
|
3907da064e | ||
|
|
f43530e1ef | ||
|
|
f6fb1561d7 | ||
|
|
a1e3ec1d19 | ||
|
|
43b87e1f82 | ||
|
|
2a85b2271c | ||
|
|
cb2c2c1b36 | ||
|
|
c5a52d89be | ||
|
|
efa574c452 | ||
|
|
66ef5213b8 | ||
|
|
370612135f | ||
|
|
ec05d1b9bd | ||
|
|
089951012b | ||
|
|
c6a9614372 | ||
|
|
a623430c2b | ||
|
|
b247c8a87f | ||
|
|
56606c01a0 | ||
|
|
eb2a55445c | ||
|
|
3fe8b503e5 | ||
|
|
bdee2d3b28 | ||
|
|
15ee2578c5 | ||
|
|
f8c9a28194 | ||
|
|
768f11fe58 | ||
|
|
acdcb21830 | ||
|
|
1530568354 | ||
|
|
7b25f4e069 | ||
|
|
cbab2c142b | ||
|
|
81bbc05028 | ||
|
|
f59dbe40ec | ||
|
|
562c3b0e04 | ||
|
|
f119a57952 | ||
|
|
c6fa1e2e21 | ||
|
|
0bdc026b05 | ||
|
|
20eb28f052 | ||
|
|
bfd3ef0e44 | ||
|
|
abda8c77ad | ||
|
|
9e5d090ddb | ||
|
|
906be376c9 | ||
|
|
695930efa5 | ||
|
|
7f2c74e7a8 | ||
|
|
f299572dc7 | ||
|
|
d65b791b2b | ||
|
|
2bf177dbcc | ||
|
|
5775552bab | ||
|
|
dd4617a7c6 | ||
|
|
64dedef2c7 | ||
|
|
89f8ff4988 | ||
|
|
3a8c94de5a | ||
|
|
dd39813cc2 | ||
|
|
e6c4273404 | ||
|
|
e1c32536ff | ||
|
|
013f68519e | ||
|
|
ab9786cc02 | ||
|
|
57804f4e02 | ||
|
|
c4421f6bc7 | ||
|
|
965d9aaf47 | ||
|
|
2476f87e1d | ||
|
|
cd36800426 | ||
|
|
18eb9d9330 | ||
|
|
7e379e9877 | ||
|
|
f2ca249b44 | ||
|
|
09ce3a79ec | ||
|
|
bfa59c6c04 | ||
|
|
e75148e799 | ||
|
|
8376f0d75a | ||
|
|
bf02a21c7d | ||
|
|
ac365622b8 | ||
|
|
7cb48e0d2d | ||
|
|
38ac520e1e | ||
|
|
6182ce48c9 | ||
|
|
9e771ddd07 | ||
|
|
74aef1fdad | ||
|
|
8860c9c7fd | ||
|
|
a8d7d23dd6 | ||
|
|
f5ce053f42 | ||
|
|
d833c7632d | ||
|
|
22bf6e7969 | ||
|
|
a1f8116e3b | ||
|
|
1d32dd72ed | ||
|
|
a3b77475c7 | ||
|
|
761552430c | ||
|
|
cc720b9797 | ||
|
|
75c81b369f | ||
|
|
7c74b0124b | ||
|
|
48501d91d2 | ||
|
|
aefef2ba56 | ||
|
|
31dd49ffd7 | ||
|
|
ace0e01d86 | ||
|
|
c0ce7663a9 | ||
|
|
e3d5ca34c8 | ||
|
|
1fb53565ef | ||
|
|
a41a8d32b8 | ||
|
|
835d13c696 | ||
|
|
6153e795bf | ||
|
|
d12498f418 | ||
|
|
c0eb69963d | ||
|
|
1f19ee88c6 | ||
|
|
24351537d2 | ||
|
|
d309a7a67b | ||
|
|
f6f68bc348 | ||
|
|
d6a324ede7 | ||
|
|
7378904f00 | ||
|
|
059f5198e5 | ||
|
|
2acbad36a6 | ||
|
|
519dd16135 | ||
|
|
fb103cb9e1 | ||
|
|
5e9a95fdfe | ||
|
|
b8610ab466 | ||
|
|
ee3fdbee5b | ||
|
|
2d827a75a6 | ||
|
|
50bb4edd1c | ||
|
|
e52f5b5d08 | ||
|
|
fd81ce126b | ||
|
|
7ae821e2b5 | ||
|
|
cb98e2421e | ||
|
|
5417385c83 | ||
|
|
c2d8067523 | ||
|
|
52bd61a2f0 | ||
|
|
aee56a5bd1 | ||
|
|
7f52fa74a4 | ||
|
|
d1dd4bd3d8 | ||
|
|
ab9f48cd47 | ||
|
|
c597f05e71 | ||
|
|
80e6cd6d7c | ||
|
|
cb101b0a3b | ||
|
|
bf4e3364b4 | ||
|
|
0494dff545 | ||
|
|
d5865421bd | ||
|
|
8578203073 | ||
|
|
e63e6e7016 | ||
|
|
dec62ab92c | ||
|
|
67d38bfab8 | ||
|
|
d2e2c726eb | ||
|
|
00d53e4824 | ||
|
|
4ce80a0837 | ||
|
|
9aa59fa358 | ||
|
|
6c8a492637 | ||
|
|
9709a38250 | ||
|
|
06ca94c1e6 | ||
|
|
67255e3f84 | ||
|
|
a20962ac14 | ||
|
|
b4cf86a362 | ||
|
|
bf8d7050cc | ||
|
|
49e5fa41ad | ||
|
|
e069d808f8 | ||
|
|
e85674711c | ||
|
|
7a89328ccb | ||
|
|
e77e6c2af0 | ||
|
|
ad26631874 | ||
|
|
cc75eb8b0e | ||
|
|
735da3c15e | ||
|
|
56dfdbeb6b | ||
|
|
eaf47ea05c | ||
|
|
37c46cf02f | ||
|
|
9d72a7cb35 | ||
|
|
a047f81b84 | ||
|
|
a2694113d3 | ||
|
|
0aae88d753 | ||
|
|
c7eba0f292 | ||
|
|
1bc1fc7e55 | ||
|
|
b91a0dd7a5 | ||
|
|
45a813b795 | ||
|
|
7b7a77bad0 | ||
|
|
ff3ad47be7 | ||
|
|
f2e8607433 | ||
|
|
b96ab58eb3 | ||
|
|
5f233d75f2 | ||
|
|
eb88245fe7 | ||
|
|
45c013f2f2 | ||
|
|
05f8b49eec | ||
|
|
ef8f9ecbf3 | ||
|
|
93fff53e29 | ||
|
|
4ae424f37a | ||
|
|
36a608952d | ||
|
|
900b4b4cf8 | ||
|
|
b782e7b9ad | ||
|
|
8d4268754e | ||
|
|
9b8e127e23 | ||
|
|
88dba1bf6c | ||
|
|
8d7daf61b9 | ||
|
|
ee1fae8ced | ||
|
|
13983900c0 | ||
|
|
881739b478 | ||
|
|
e06caa9ca1 | ||
|
|
efc5462131 | ||
|
|
2199fe5cbf | ||
|
|
30f774cf9c | ||
|
|
be9aa44ca8 | ||
|
|
dc4459083f | ||
|
|
2f375e4f67 | ||
|
|
879c325b83 | ||
|
|
7ad3f4a577 | ||
|
|
5b9d3b119f | ||
|
|
5467fd89d8 | ||
|
|
d2937c2aed | ||
|
|
a0236a9b76 | ||
|
|
c6d4eb7fd3 | ||
|
|
4cb1f8a226 | ||
|
|
abca5faf78 | ||
|
|
21ffe22375 | ||
|
|
64bbebb135 | ||
|
|
ebad3da6dd | ||
|
|
4633d16a6b | ||
|
|
df026aede2 | ||
|
|
d495a5b747 | ||
|
|
042a6b58c4 | ||
|
|
d87cf6c02b | ||
|
|
0c99c5563f | ||
|
|
3934e37f77 | ||
|
|
2ee4fa484e | ||
|
|
bc7948d75f | ||
|
|
5348b8a980 | ||
|
|
f2936dc15c | ||
|
|
864f9747f0 | ||
|
|
3ea62eb73a | ||
|
|
2fae966884 | ||
|
|
f888ec6e81 | ||
|
|
d2e8201d79 | ||
|
|
65d7d01dfa | ||
|
|
2cf2df20f6 | ||
|
|
1d1f89e0de | ||
|
|
ee73bc3aa4 | ||
|
|
d3fa680373 | ||
|
|
ef9d6d45b6 | ||
|
|
a980864886 | ||
|
|
ca4d506391 | ||
|
|
554c33a883 | ||
|
|
56813eb11e | ||
|
|
4d5e3bc22b | ||
|
|
ff55a951c9 | ||
|
|
895172d498 | ||
|
|
71f73d8df2 | ||
|
|
97daca4b31 | ||
|
|
e9eafc3380 | ||
|
|
600a1cae79 | ||
|
|
0b1fc1b925 | ||
|
|
bdaa9faea0 | ||
|
|
dbadb07adf | ||
|
|
4a2c899d05 | ||
|
|
85084f5fd7 | ||
|
|
78692c83a6 | ||
|
|
173e6011e8 | ||
|
|
9e542ba232 | ||
|
|
5005675561 | ||
|
|
29759ca71e | ||
|
|
be28a96d8d | ||
|
|
9b5e5c9acb | ||
|
|
fbbbd44dc3 | ||
|
|
0bb3eaaf67 | ||
|
|
e48d68a449 | ||
|
|
67175c5a72 | ||
|
|
6b0aeefc66 | ||
|
|
9a7f5388d9 | ||
|
|
d1649771cc | ||
|
|
08e5f61b10 | ||
|
|
012f085f33 | ||
|
|
9f570152e5 | ||
|
|
3099ef68a9 | ||
|
|
f0bac3a3b0 | ||
|
|
cd0ed879e3 | ||
|
|
95418089f3 | ||
|
|
650a0f1c1f | ||
|
|
757e885abc | ||
|
|
2c8e7782c5 | ||
|
|
d3eccc1d6f | ||
|
|
40aee5b03d | ||
|
|
4c9ade5ecb | ||
|
|
1d435890af | ||
|
|
8a2b2daad7 | ||
|
|
21334cab28 | ||
|
|
a17df609f3 | ||
|
|
0afdf5d8e4 | ||
|
|
17733a66f9 | ||
|
|
3a819a29b7 | ||
|
|
9d77fab18f | ||
|
|
3650c3f08b | ||
|
|
5512fd4220 | ||
|
|
678393e2bd | ||
|
|
25d8a7999f | ||
|
|
014f7ade86 | ||
|
|
2b671fcafc | ||
|
|
df5b243e75 | ||
|
|
5e43f2752a | ||
|
|
0cb14ba5ec | ||
|
|
d65477891e | ||
|
|
bb1854814c | ||
|
|
9e15bda803 | ||
|
|
e7ac66ad04 | ||
|
|
4a0a911f3b | ||
|
|
7286aeb013 | ||
|
|
244265d52b | ||
|
|
ab93e96a1f | ||
|
|
5a17d8b450 | ||
|
|
43e3662e60 | ||
|
|
19d5063814 | ||
|
|
da7a556629 | ||
|
|
c2cb4dff4c | ||
|
|
dd6868c255 | ||
|
|
b411a2e489 | ||
|
|
28343aaa33 | ||
|
|
3f615c6664 | ||
|
|
804b17fbf1 | ||
|
|
9798e14733 | ||
|
|
b742342062 | ||
|
|
f8fa19ed47 | ||
|
|
fe4bb3e413 | ||
|
|
ff13782ab5 | ||
|
|
b1dd7efed8 | ||
|
|
699a58e0b3 | ||
|
|
6a19655886 | ||
|
|
afc889ff4d | ||
|
|
b06c9f037e | ||
|
|
1c812b340d | ||
|
|
bd9f5d3e06 | ||
|
|
cf8164bcc3 | ||
|
|
33612b7076 | ||
|
|
5a97786cc6 | ||
|
|
ee2329d236 | ||
|
|
66f80b2239 | ||
|
|
62ebeb9fd8 | ||
|
|
72ba708bfe | ||
|
|
d0d7f2d2d2 | ||
|
|
b3459be707 | ||
|
|
d1e22d50f0 | ||
|
|
bf46c3cfab | ||
|
|
7880734d77 | ||
|
|
5ce1aba493 | ||
|
|
c929076a83 | ||
|
|
99625067fe | ||
|
|
c9ab977d73 | ||
|
|
b99e7598f9 | ||
|
|
b9e06bcf66 | ||
|
|
eb108c7866 | ||
|
|
a190862ed3 | ||
|
|
34a31a71fd | ||
|
|
7774756ed1 | ||
|
|
ee9f78d156 | ||
|
|
70754db27a | ||
|
|
16f8143f3e | ||
|
|
be1f014294 | ||
|
|
f53f9af1c5 | ||
|
|
58b1d62976 | ||
|
|
8e0f7f18a0 | ||
|
|
75ea62f351 | ||
|
|
ee8572559f | ||
|
|
f640be90ad | ||
|
|
472acd4792 | ||
|
|
8087e99808 | ||
|
|
51b74251f9 | ||
|
|
8c7aede0cc | ||
|
|
2829d95705 | ||
|
|
28057fd086 | ||
|
|
2e3ad3206c | ||
|
|
4f94cf5dfb | ||
|
|
51e8c28ab6 | ||
|
|
0f73228d55 | ||
|
|
d21e4fb86a | ||
|
|
645db7fa2f | ||
|
|
0d4f35bed1 | ||
|
|
2123fec8ed | ||
|
|
3860488bb5 | ||
|
|
ef5ea46830 | ||
|
|
d4faacf462 | ||
|
|
4a7d2901ac | ||
|
|
f382946138 | ||
|
|
bb93a59cfb | ||
|
|
0ad84fd7b0 | ||
|
|
87bddcd8ce | ||
|
|
18701a2dae | ||
|
|
2f65064688 | ||
|
|
2f08f2441f | ||
|
|
6c4c0bf57a | ||
|
|
55a10ee275 | ||
|
|
49545ce0c2 | ||
|
|
b87058508e | ||
|
|
fd53f10fbd | ||
|
|
6329e274ab | ||
|
|
b5fdaac947 | ||
|
|
22bf74dc65 | ||
|
|
d376df478a | ||
|
|
53f4da1d30 | ||
|
|
e34db7b8c6 | ||
|
|
074bbc7149 | ||
|
|
4b33164ab6 | ||
|
|
dab707a893 | ||
|
|
5cf164fcc1 | ||
|
|
37a7ce809a | ||
|
|
15f9f5dbe8 | ||
|
|
187818aaa0 | ||
|
|
30b1e7078f | ||
|
|
791980cd1f | ||
|
|
6c19504c8b | ||
|
|
497ebce88a | ||
|
|
2768cd2010 | ||
|
|
204e42494a | ||
|
|
72b4a86eed | ||
|
|
ce5311191f | ||
|
|
7eff6d968e | ||
|
|
d01e2506f5 | ||
|
|
53fe372a0c | ||
|
|
4cb04d1e40 | ||
|
|
24e021b91f | ||
|
|
e988f5ca3b | ||
|
|
633a3f4867 | ||
|
|
df163d8cb7 | ||
|
|
73bb317925 | ||
|
|
ad74d264a3 | ||
|
|
e9db975d7d | ||
|
|
0958a6bb62 | ||
|
|
f3586a79c2 | ||
|
|
882d09bf85 | ||
|
|
25eb2e2daf | ||
|
|
d8d9912f2d | ||
|
|
8a28da1986 | ||
|
|
384425582a | ||
|
|
b87b356722 | ||
|
|
6ae0dda9d3 | ||
|
|
6c680ff424 | ||
|
|
c6b455f470 | ||
|
|
f6d7052928 | ||
|
|
2ee1a9c440 | ||
|
|
8fd12d530d | ||
|
|
3f7bd48c0a | ||
|
|
28108476bd | ||
|
|
722cedc92e | ||
|
|
f182e32e3d | ||
|
|
3cfcc13387 | ||
|
|
953bb64e0b | ||
|
|
54f4443428 | ||
|
|
a039450d10 | ||
|
|
1e963a6c3a | ||
|
|
80ad45df06 | ||
|
|
348f133e77 | ||
|
|
cd865bbe8f | ||
|
|
e668d7685d | ||
|
|
6607bee91a | ||
|
|
3a89a5af0b | ||
|
|
35e3621ae7 | ||
|
|
040ef73886 | ||
|
|
74c9ac0872 | ||
|
|
adbad509f4 | ||
|
|
15a97a653f | ||
|
|
9fb97a6b10 | ||
|
|
579f210cfc | ||
|
|
2fb1e156ed | ||
|
|
707ddc61bf | ||
|
|
09e861637f | ||
|
|
1f43f904d5 | ||
|
|
446f74b3dd | ||
|
|
d2391f999e | ||
|
|
e1779ca8bc | ||
|
|
65371c9a39 | ||
|
|
a4dc844338 | ||
|
|
0a4af647c8 | ||
|
|
503bf541c7 | ||
|
|
1fbc249de5 | ||
|
|
959dcd0c49 | ||
|
|
ef4f2f10d9 | ||
|
|
19dbd85d44 | ||
|
|
ef4b604caf | ||
|
|
74f459f8a4 | ||
|
|
fef81748bb | ||
|
|
3e53879adc | ||
|
|
145e61b00f | ||
|
|
49dd93ffab | ||
|
|
7174879ac9 | ||
|
|
486adb717b | ||
|
|
773f592c3f | ||
|
|
2ca9d87b95 | ||
|
|
4bc4292ceb | ||
|
|
cbb72c2f29 | ||
|
|
5f477b313b | ||
|
|
d45e44d01c | ||
|
|
c12839dc7b | ||
|
|
c35c9f7c3a | ||
|
|
ff5c7072d7 | ||
|
|
eca453ee5a | ||
|
|
ad2541299f | ||
|
|
dc840fdf48 | ||
|
|
510bb5785e | ||
|
|
9556795611 | ||
|
|
4c6fa740f3 | ||
|
|
f4a3e9a39b | ||
|
|
bb820bebd1 | ||
|
|
664f809362 | ||
|
|
034c045b37 | ||
|
|
4dfb0e9a90 | ||
|
|
654429dbdb | ||
|
|
c7d0214aaa | ||
|
|
607923b58f | ||
|
|
895fb63d5d | ||
|
|
b172018d08 | ||
|
|
95a0bc92d6 | ||
|
|
a90492e393 | ||
|
|
ec7067e7bd | ||
|
|
42b7410a5d | ||
|
|
4cfcdfa040 | ||
|
|
795986f146 | ||
|
|
d38c338f89 | ||
|
|
a373849b5b | ||
|
|
8cd4637316 | ||
|
|
0635a6f562 | ||
|
|
a8cee87c08 | ||
|
|
a7598ea815 | ||
|
|
22cef7a6a0 | ||
|
|
e507339324 | ||
|
|
b643d8ff6a | ||
|
|
3547bd8d00 | ||
|
|
ab1f37b0bb | ||
|
|
eb37032d8a | ||
|
|
f6ed21559a | ||
|
|
a5986ade51 | ||
|
|
d7504aeda5 | ||
|
|
b2459b2dc6 | ||
|
|
819e06e2cd | ||
|
|
b7f1a3db57 | ||
|
|
a030e46c69 | ||
|
|
f61cfbc542 | ||
|
|
c4b7571c45 | ||
|
|
4dd477e064 | ||
|
|
f595f6f141 | ||
|
|
24602119c5 | ||
|
|
3040d0a474 | ||
|
|
e4ea00ca23 | ||
|
|
4fc311da90 | ||
|
|
7894e52529 | ||
|
|
7999a70cab | ||
|
|
f6aa9a7ea4 | ||
|
|
b3ae9cc9d4 | ||
|
|
5f29729e82 | ||
|
|
e24851456a | ||
|
|
1e40fd750f | ||
|
|
ed1554f4af | ||
|
|
c6cf5febd5 | ||
|
|
f9aaf7d903 | ||
|
|
a2e73cceee | ||
|
|
c672919d1e | ||
|
|
7ab2449ac1 | ||
|
|
e68a2b5e1d | ||
|
|
ddc4ac187c | ||
|
|
ded66bbdfc | ||
|
|
1a11c402fa | ||
|
|
4ec77eeca7 | ||
|
|
635fd927cd | ||
|
|
481a7b160d | ||
|
|
cadedd2919 | ||
|
|
e0bf23fa7c | ||
|
|
a53acb3b58 | ||
|
|
f6ec858ac9 | ||
|
|
d976046e6a | ||
|
|
1902b631c7 | ||
|
|
dda2129354 | ||
|
|
12157edd62 | ||
|
|
7e563b89c7 | ||
|
|
6a6118e136 | ||
|
|
bede9a814b | ||
|
|
d70842c3c7 | ||
|
|
a288c5b85d | ||
|
|
c4d408d095 | ||
|
|
ac24d6707f | ||
|
|
e5835d2731 | ||
|
|
d8c32db14b | ||
|
|
2b3606d44d | ||
|
|
5feb31911a | ||
|
|
9483e42508 | ||
|
|
e640f65640 | ||
|
|
8a9c85c97d | ||
|
|
bbae809012 | ||
|
|
bc166f19b7 | ||
|
|
8fb521c83c | ||
|
|
d9077584cd | ||
|
|
8467d5d760 | ||
|
|
d96e5a55e1 | ||
|
|
4f64f70a12 | ||
|
|
9cd8f7c7f3 | ||
|
|
c64f71a3cb | ||
|
|
af9838408b | ||
|
|
974a187e74 | ||
|
|
efe1c767f0 | ||
|
|
4bfefa9396 | ||
|
|
4cddda67d9 | ||
|
|
c2d4409241 | ||
|
|
ee88fe55c1 | ||
|
|
a72d0c5b7f | ||
|
|
3cb092051e | ||
|
|
b8018942fc | ||
|
|
3136eb0962 | ||
|
|
02f58ef9e3 | ||
|
|
b5f029d10e | ||
|
|
caff20cbb3 | ||
|
|
e71ca328e7 | ||
|
|
a0b460b084 | ||
|
|
abca28c80b | ||
|
|
5627089a2a | ||
|
|
da7909f1ce | ||
|
|
210cb31852 | ||
|
|
aeb438dc62 | ||
|
|
10a053019d | ||
|
|
58c431abc2 | ||
|
|
d512e25cca | ||
|
|
65d9333104 | ||
|
|
fbd974df55 | ||
|
|
fdf83a5ad5 | ||
|
|
c98e06e1aa | ||
|
|
b58265a69c | ||
|
|
37fbad0dbe | ||
|
|
756da03b9a | ||
|
|
48e082e124 | ||
|
|
c606912a8d | ||
|
|
7cd24e7dbd | ||
|
|
c7d717f0a4 | ||
|
|
cf3cdaccf3 | ||
|
|
d0d4760ddc | ||
|
|
51bc18aef0 | ||
|
|
26d12bebe4 | ||
|
|
90ae024a4e | ||
|
|
57c7d81f43 | ||
|
|
eab206c3bd | ||
|
|
72745b05dc | ||
|
|
f8d5101dbc | ||
|
|
cc1e30c963 | ||
|
|
121fe34180 | ||
|
|
5450223cc7 | ||
|
|
25b5c14527 | ||
|
|
6bc4c87ce4 | ||
|
|
0f0c3d0ca1 | ||
|
|
96c4a24d3d | ||
|
|
c6b501811f | ||
|
|
0996a0b140 | ||
|
|
8557a3b70e | ||
|
|
8b6cf1fc41 | ||
|
|
4eb762d52b | ||
|
|
4d221c6099 | ||
|
|
314bfbd541 | ||
|
|
5cdd234bf2 | ||
|
|
b6d5849bec | ||
|
|
035b15f330 | ||
|
|
77355cbeb4 | ||
|
|
ff5dff45f5 | ||
|
|
0deb52ac5e | ||
|
|
29ff9c11a8 | ||
|
|
cb3ae0e069 | ||
|
|
bf31d6d5fa | ||
|
|
181a6a61ff | ||
|
|
322af6513d | ||
|
|
69ce3c43cf | ||
|
|
438453e61a | ||
|
|
50f94eb040 | ||
|
|
5794c30def | ||
|
|
a512e600a7 | ||
|
|
429d110212 | ||
|
|
b5248c06a7 | ||
|
|
18bd1058d3 | ||
|
|
b18fcf7f9e | ||
|
|
05e963d1e2 | ||
|
|
5d9c8f3726 | ||
|
|
be55882f46 | ||
|
|
356a4a4392 | ||
|
|
34bdd40953 | ||
|
|
c5524851f3 | ||
|
|
cff1c3010b | ||
|
|
46572ae793 | ||
|
|
b1ba69fd00 | ||
|
|
8c619fedeb | ||
|
|
efd01d6929 | ||
|
|
a1b78f93fe | ||
|
|
cdc89c0623 | ||
|
|
d107151f8a | ||
|
|
48abc75665 | ||
|
|
41373f30f7 | ||
|
|
ad9d032f82 | ||
|
|
d7eb23db53 | ||
|
|
333f1e46ca | ||
|
|
d414127f80 | ||
|
|
ff2885087d | ||
|
|
a5258978d6 | ||
|
|
8c0a23dd8b | ||
|
|
d434ea55a8 | ||
|
|
4331fbf422 | ||
|
|
cf17ea6254 | ||
|
|
8247bb4a76 | ||
|
|
08a41bf093 | ||
|
|
d7157696f4 | ||
|
|
bf055688b7 | ||
|
|
28b9892486 | ||
|
|
512a9125bf | ||
|
|
00a92452e8 | ||
|
|
32576e97d5 | ||
|
|
20f93e761b | ||
|
|
bdf8f655fb | ||
|
|
8603dd4bb4 | ||
|
|
212a070a02 | ||
|
|
e15358f77e | ||
|
|
851b601d2c | ||
|
|
f52a1cf311 | ||
|
|
0ddb2cf183 | ||
|
|
cf0340c1c7 | ||
|
|
6c5b4a298b | ||
|
|
c5c5e6d811 | ||
|
|
2462ede539 | ||
|
|
b6e4c59877 | ||
|
|
0bc1624d4e | ||
|
|
f81f7db6cd | ||
|
|
9e95d2e4ac | ||
|
|
95a46ae201 | ||
|
|
8764b44325 | ||
|
|
090db5490b | ||
|
|
cfcb050822 | ||
|
|
66e36e9d40 |
4
.babelrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["react", "es2015", "es2016"],
|
||||
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-generator", "transform-runtime", "add-module-exports"]
|
||||
}
|
||||
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
src/vector/modernizr.js
|
||||
src/component-index.js
|
||||
3
.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ["./node_modules/matrix-react-sdk/.eslintrc.js"],
|
||||
}
|
||||
20
.gitignore
vendored
@@ -1,8 +1,14 @@
|
||||
node_modules
|
||||
vector/bundle.*
|
||||
lib
|
||||
/build
|
||||
/cert.pem
|
||||
/dist
|
||||
/karma-reports
|
||||
/key.pem
|
||||
/lib
|
||||
/node_modules
|
||||
/packages/
|
||||
/webapp
|
||||
/.npmrc
|
||||
.DS_Store
|
||||
key.pem
|
||||
cert.pem
|
||||
vector/components.css
|
||||
packages/
|
||||
npm-debug.log
|
||||
electron/dist
|
||||
electron/pub
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
example
|
||||
examples
|
||||
build/.module-cache
|
||||
|
||||
7
.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 6 # node v6, to match jenkins
|
||||
install:
|
||||
- npm install
|
||||
- (cd node_modules/matrix-js-sdk && npm install)
|
||||
- (cd node_modules/matrix-react-sdk && npm install)
|
||||
682
CHANGELOG.md
@@ -1,3 +1,683 @@
|
||||
Changes in [0.9.7](https://github.com/vector-im/riot-web/releases/tag/v0.9.7) (2017-02-04)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7-rc.3...v0.9.7)
|
||||
|
||||
* Update to matrix-js-sdk 0.7.5 (no changes from 0.7.5-rc.3)
|
||||
* Update to matrix-react-sdk 0.8.6 (no changes from 0.8.6-rc.3)
|
||||
|
||||
Changes in [0.9.7-rc.3](https://github.com/vector-im/riot-web/releases/tag/v0.9.7-rc.3) (2017-02-03)
|
||||
====================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7-rc.2...v0.9.7-rc.3)
|
||||
* Update to latest Olm to fix key import/export and use of megolm sessions
|
||||
created on more recent versions
|
||||
* Update to latest matrix-react-sdk and matrix-js-sdk to fix e2e device
|
||||
handling
|
||||
|
||||
Changes in [0.9.7-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.7-rc.2) (2017-02-03)
|
||||
====================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7-rc.1...v0.9.7-rc.2)
|
||||
|
||||
* Update matrix-js-sdk to get new device change
|
||||
notifications interface for more reliable e2e crypto
|
||||
|
||||
Changes in [0.9.7-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.7-rc.1) (2017-02-03)
|
||||
====================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.6...v0.9.7-rc.1)
|
||||
|
||||
* Better user interface for screen readers and keyboard navigation
|
||||
[\#2946](https://github.com/vector-im/riot-web/pull/2946)
|
||||
* Allow mxc: URLs for icons in the NetworkDropdown
|
||||
[\#3118](https://github.com/vector-im/riot-web/pull/3118)
|
||||
* make TopRightMenu more intuitive
|
||||
[\#3117](https://github.com/vector-im/riot-web/pull/3117)
|
||||
* Handle icons with width > height
|
||||
[\#3110](https://github.com/vector-im/riot-web/pull/3110)
|
||||
* Fix jenkins build
|
||||
[\#3105](https://github.com/vector-im/riot-web/pull/3105)
|
||||
* Add CSS for a support box in login
|
||||
[\#3081](https://github.com/vector-im/riot-web/pull/3081)
|
||||
* Allow a custom login logo to be displayed on login
|
||||
[\#3082](https://github.com/vector-im/riot-web/pull/3082)
|
||||
* Fix the width of input fields within login/reg box
|
||||
[\#3080](https://github.com/vector-im/riot-web/pull/3080)
|
||||
* Set BaseAvatar_image bg colour = #fff
|
||||
[\#3057](https://github.com/vector-im/riot-web/pull/3057)
|
||||
* only recalculate favicon if it changes
|
||||
[\#3067](https://github.com/vector-im/riot-web/pull/3067)
|
||||
* CSS tweak for email address lookup
|
||||
[\#3064](https://github.com/vector-im/riot-web/pull/3064)
|
||||
* Glue the dialog to rageshake: honour sendLogs flag.
|
||||
[\#3061](https://github.com/vector-im/riot-web/pull/3061)
|
||||
* Don't use hash-named directory for dev server
|
||||
[\#3049](https://github.com/vector-im/riot-web/pull/3049)
|
||||
* Implement bug reporting logic
|
||||
[\#3000](https://github.com/vector-im/riot-web/pull/3000)
|
||||
* Add css for bug report dialog
|
||||
[\#3045](https://github.com/vector-im/riot-web/pull/3045)
|
||||
* Increase the max-height of the expanded status bar
|
||||
[\#3043](https://github.com/vector-im/riot-web/pull/3043)
|
||||
* Hopefully, fix intermittent test failure
|
||||
[\#3040](https://github.com/vector-im/riot-web/pull/3040)
|
||||
* CSS for 'searching known users'
|
||||
[\#2971](https://github.com/vector-im/riot-web/pull/2971)
|
||||
* Animate status bar max-height and margin-top
|
||||
[\#2981](https://github.com/vector-im/riot-web/pull/2981)
|
||||
* Add eslint config
|
||||
[\#3032](https://github.com/vector-im/riot-web/pull/3032)
|
||||
* Re-position typing avatars relative to "is typing"
|
||||
[\#3030](https://github.com/vector-im/riot-web/pull/3030)
|
||||
* CSS for avatars that appear when users are typing
|
||||
[\#2998](https://github.com/vector-im/riot-web/pull/2998)
|
||||
* Add StartupWMClass
|
||||
[\#3001](https://github.com/vector-im/riot-web/pull/3001)
|
||||
* Fix link to image for event options menu
|
||||
[\#3002](https://github.com/vector-im/riot-web/pull/3002)
|
||||
* Make riot desktop single instance
|
||||
[\#2999](https://github.com/vector-im/riot-web/pull/2999)
|
||||
* Add electron tray icon
|
||||
[\#2997](https://github.com/vector-im/riot-web/pull/2997)
|
||||
* Fixes to electron desktop notifs
|
||||
[\#2994](https://github.com/vector-im/riot-web/pull/2994)
|
||||
* Auto-hide the electron menu bar
|
||||
[\#2975](https://github.com/vector-im/riot-web/pull/2975)
|
||||
* A couple of tweaks to the karma config
|
||||
[\#2987](https://github.com/vector-im/riot-web/pull/2987)
|
||||
* Deploy script
|
||||
[\#2974](https://github.com/vector-im/riot-web/pull/2974)
|
||||
* Use the postcss-webpack-loader
|
||||
[\#2990](https://github.com/vector-im/riot-web/pull/2990)
|
||||
* Switch CSS to using postcss, and implement a dark theme.
|
||||
[\#2973](https://github.com/vector-im/riot-web/pull/2973)
|
||||
* Update redeploy script to keep old bundles
|
||||
[\#2969](https://github.com/vector-im/riot-web/pull/2969)
|
||||
* Include current version in update check explicitly
|
||||
[\#2967](https://github.com/vector-im/riot-web/pull/2967)
|
||||
* Add another layer of directory to webpack chunks
|
||||
[\#2966](https://github.com/vector-im/riot-web/pull/2966)
|
||||
* Fix links to fonts and images from CSS
|
||||
[\#2965](https://github.com/vector-im/riot-web/pull/2965)
|
||||
* Put parent build hash in webpack output filenames
|
||||
[\#2961](https://github.com/vector-im/riot-web/pull/2961)
|
||||
* update README to point to new names/locations
|
||||
[\#2846](https://github.com/vector-im/riot-web/pull/2846)
|
||||
|
||||
Changes in [0.9.6](https://github.com/vector-im/riot-web/releases/tag/v0.9.6) (2017-01-16)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.6-rc.1...v0.9.6)
|
||||
|
||||
* Update to matrix-js-sdk 0.9.6 for video calling fix
|
||||
|
||||
Changes in [0.9.6-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.6-rc.1) (2017-01-13)
|
||||
====================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.5...v0.9.6-rc.1)
|
||||
|
||||
* Build the js-sdk in the CI script
|
||||
[\#2920](https://github.com/vector-im/riot-web/pull/2920)
|
||||
* Hopefully fix Windows shortcuts
|
||||
[\#2917](https://github.com/vector-im/riot-web/pull/2917)
|
||||
* Update README now the js-sdk has a transpile step
|
||||
[\#2921](https://github.com/vector-im/riot-web/pull/2921)
|
||||
* Use the role for 'toggle dev tools'
|
||||
[\#2915](https://github.com/vector-im/riot-web/pull/2915)
|
||||
* Enable screen sharing easter-egg in desktop app
|
||||
[\#2909](https://github.com/vector-im/riot-web/pull/2909)
|
||||
* make electron send email validation URLs with a nextlink of riot.im
|
||||
[\#2808](https://github.com/vector-im/riot-web/pull/2808)
|
||||
* add Debian Stretch install steps to readme
|
||||
[\#2809](https://github.com/vector-im/riot-web/pull/2809)
|
||||
* Update desktop build instructions fixes #2792
|
||||
[\#2793](https://github.com/vector-im/riot-web/pull/2793)
|
||||
* CSS for the delete threepid button
|
||||
[\#2784](https://github.com/vector-im/riot-web/pull/2784)
|
||||
|
||||
Changes in [0.9.5](https://github.com/vector-im/riot-web/releases/tag/v0.9.5) (2016-12-24)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.4...v0.9.5)
|
||||
|
||||
* make electron send email validation URLs with a nextlink of riot.im rather than file:///
|
||||
* add gnu-tar to debian electron build deps
|
||||
* fix win32 shortcut in start menu
|
||||
|
||||
Changes in [0.9.4](https://github.com/vector-im/riot-web/releases/tag/v0.9.4) (2016-12-22)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.3...v0.9.4)
|
||||
|
||||
* Update to libolm 2.1.0. This should help resolve a problem with browser
|
||||
sessions being logged out ([\#2726](https://github.com/vector-im/riot-web/issues/2726)).
|
||||
|
||||
Changes in [0.9.3](https://github.com/vector-im/riot-web/releases/tag/v0.9.3) (2016-12-22)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.2...v0.9.3)
|
||||
|
||||
* (from matrix-react-sdk) Fix regression where the date separator would be displayed
|
||||
at the wrong time of day.
|
||||
* README.md: fix GFMD for nativefier
|
||||
[\#2755](https://github.com/vector-im/riot-web/pull/2755)
|
||||
|
||||
Changes in [0.9.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.2) (2016-12-16)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1...v0.9.2)
|
||||
|
||||
* Remove the client side filtering from the room dir
|
||||
[\#2750](https://github.com/vector-im/riot-web/pull/2750)
|
||||
* Configure olm memory size
|
||||
[\#2745](https://github.com/vector-im/riot-web/pull/2745)
|
||||
* Support room dir 3rd party network filtering
|
||||
[\#2747](https://github.com/vector-im/riot-web/pull/2747)
|
||||
|
||||
Changes in [0.9.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.1) (2016-12-09)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1-rc.2...v0.9.1)
|
||||
|
||||
* Update README to say how to build the desktop app
|
||||
[\#2732](https://github.com/vector-im/riot-web/pull/2732)
|
||||
* Add signing ID in release_config.yaml
|
||||
[\#2731](https://github.com/vector-im/riot-web/pull/2731)
|
||||
* Makeover!
|
||||
[\#2722](https://github.com/vector-im/riot-web/pull/2722)
|
||||
* Fix broken tests
|
||||
[\#2730](https://github.com/vector-im/riot-web/pull/2730)
|
||||
* Make the 'loading' tests work in isolation
|
||||
[\#2727](https://github.com/vector-im/riot-web/pull/2727)
|
||||
* Use a PNG for the icon on non-Windows
|
||||
[\#2708](https://github.com/vector-im/riot-web/pull/2708)
|
||||
* Add missing brackets to call to toUpperCase
|
||||
[\#2703](https://github.com/vector-im/riot-web/pull/2703)
|
||||
|
||||
Changes in [0.9.1-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.1-rc.2) (2016-12-06)
|
||||
====================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1-rc.1...v0.9.1-rc.2)
|
||||
|
||||
* Fix clicking on notifications
|
||||
[\#2700](https://github.com/vector-im/riot-web/pull/2700)
|
||||
* Desktop app: Only show window when ready
|
||||
[\#2697](https://github.com/vector-im/riot-web/pull/2697)
|
||||
|
||||
Changes in [0.9.1-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.1-rc.1) (2016-12-05)
|
||||
====================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.0...v0.9.1-rc.1)
|
||||
|
||||
* Final bits to prepare electron distribtion:
|
||||
[\#2653](https://github.com/vector-im/riot-web/pull/2653)
|
||||
* Update name & repo to reflect renamed repository
|
||||
[\#2692](https://github.com/vector-im/riot-web/pull/2692)
|
||||
* Document cross_origin_renderer_url
|
||||
[\#2680](https://github.com/vector-im/riot-web/pull/2680)
|
||||
* Add css for the iframes for e2e attachments
|
||||
[\#2659](https://github.com/vector-im/riot-web/pull/2659)
|
||||
* Fix config location in some more places
|
||||
[\#2670](https://github.com/vector-im/riot-web/pull/2670)
|
||||
* CSS updates for s/block/blacklist for e2e
|
||||
[\#2662](https://github.com/vector-im/riot-web/pull/2662)
|
||||
* Update to electron 1.4.8
|
||||
[\#2660](https://github.com/vector-im/riot-web/pull/2660)
|
||||
* Add electron config
|
||||
[\#2644](https://github.com/vector-im/riot-web/pull/2644)
|
||||
* Move getDefaultDeviceName into the Platforms
|
||||
[\#2643](https://github.com/vector-im/riot-web/pull/2643)
|
||||
* Add Freenode & Mozilla domains
|
||||
[\#2641](https://github.com/vector-im/riot-web/pull/2641)
|
||||
* Include config.sample.json in dist tarball
|
||||
[\#2614](https://github.com/vector-im/riot-web/pull/2614)
|
||||
|
||||
Changes in [0.9.0](https://github.com/vector-im/vector-web/releases/tag/v0.9.0) (2016-11-19)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4...v0.9.0)
|
||||
|
||||
* Add a cachebuster to /version
|
||||
[\#2596](https://github.com/vector-im/vector-web/pull/2596)
|
||||
* Add a 'View decrypted source' button
|
||||
[\#2587](https://github.com/vector-im/vector-web/pull/2587)
|
||||
* Fix changelog dialog to read new version format
|
||||
[\#2577](https://github.com/vector-im/vector-web/pull/2577)
|
||||
* Build all of the vector dir in the build process
|
||||
[\#2558](https://github.com/vector-im/vector-web/pull/2558)
|
||||
* Support for get_app_version
|
||||
[\#2553](https://github.com/vector-im/vector-web/pull/2553)
|
||||
* Add CSS for mlist truncation
|
||||
[\#2565](https://github.com/vector-im/vector-web/pull/2565)
|
||||
* Add menu option for `external_url` if present
|
||||
[\#2560](https://github.com/vector-im/vector-web/pull/2560)
|
||||
* Make auto-update configureable
|
||||
[\#2555](https://github.com/vector-im/vector-web/pull/2555)
|
||||
* Missed files electron windows fixes
|
||||
[\#2556](https://github.com/vector-im/vector-web/pull/2556)
|
||||
* Add some CSS for scalar error popup
|
||||
[\#2554](https://github.com/vector-im/vector-web/pull/2554)
|
||||
* Catch unhandled errors in the electron process
|
||||
[\#2552](https://github.com/vector-im/vector-web/pull/2552)
|
||||
* Slight grab-bag of fixes for electron on Windows
|
||||
[\#2551](https://github.com/vector-im/vector-web/pull/2551)
|
||||
* Electron app (take 3)
|
||||
[\#2535](https://github.com/vector-im/vector-web/pull/2535)
|
||||
* Use webpack-dev-server instead of http-server
|
||||
[\#2542](https://github.com/vector-im/vector-web/pull/2542)
|
||||
* Better support no-config when loading from file
|
||||
[\#2541](https://github.com/vector-im/vector-web/pull/2541)
|
||||
* Fix loading with no config from HTTP
|
||||
[\#2540](https://github.com/vector-im/vector-web/pull/2540)
|
||||
* Move 'new version' support into Platform
|
||||
[\#2532](https://github.com/vector-im/vector-web/pull/2532)
|
||||
* Add Notification support to the Web Platform
|
||||
[\#2533](https://github.com/vector-im/vector-web/pull/2533)
|
||||
* Use the defaults if given a blank config file
|
||||
[\#2534](https://github.com/vector-im/vector-web/pull/2534)
|
||||
* Implement Platforms
|
||||
[\#2531](https://github.com/vector-im/vector-web/pull/2531)
|
||||
|
||||
Changes in [0.8.4](https://github.com/vector-im/vector-web/releases/tag/v0.8.4) (2016-11-04)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4-rc.2...v0.8.4)
|
||||
|
||||
* No changes
|
||||
|
||||
Changes in [0.8.4-rc.2](https://github.com/vector-im/vector-web/releases/tag/v0.8.4-rc.2) (2016-11-02)
|
||||
======================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4-rc.1...v0.8.4-rc.2)
|
||||
|
||||
* Fix the version in the generated distribution package
|
||||
|
||||
Changes in [0.8.4-rc.1](https://github.com/vector-im/vector-web/releases/tag/v0.8.4-rc.1) (2016-11-02)
|
||||
======================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.3...v0.8.4-rc.1)
|
||||
|
||||
Breaking Changes
|
||||
----------------
|
||||
* End-to-end encryption now requires one-time keys to be
|
||||
signed, so end-to-end encryption will not interoperate
|
||||
with previous releases of vector-web. End-to-end encryption
|
||||
remains in beta.
|
||||
|
||||
Other Changes
|
||||
-------------
|
||||
* Rename the package script/output dir to 'dist'
|
||||
[\#2528](https://github.com/vector-im/vector-web/pull/2528)
|
||||
* Avoid errors if olm is missing
|
||||
[\#2518](https://github.com/vector-im/vector-web/pull/2518)
|
||||
* Put a cachebuster in the names of CSS and JS files
|
||||
[\#2515](https://github.com/vector-im/vector-web/pull/2515)
|
||||
* Bump to olm 2.0.0
|
||||
[\#2517](https://github.com/vector-im/vector-web/pull/2517)
|
||||
* Don't include the world in the published packages
|
||||
[\#2516](https://github.com/vector-im/vector-web/pull/2516)
|
||||
* Use webpack to copy olm.js
|
||||
[\#2514](https://github.com/vector-im/vector-web/pull/2514)
|
||||
* Don't include two copies of the CSS in the tarball
|
||||
[\#2513](https://github.com/vector-im/vector-web/pull/2513)
|
||||
* Correct text alignment on room directory search
|
||||
[\#2512](https://github.com/vector-im/vector-web/pull/2512)
|
||||
* Correct spelling of 'rel'
|
||||
[\#2510](https://github.com/vector-im/vector-web/pull/2510)
|
||||
* readme tweaks
|
||||
[\#2507](https://github.com/vector-im/vector-web/pull/2507)
|
||||
* s/vector/riot/ in the readme
|
||||
[\#2491](https://github.com/vector-im/vector-web/pull/2491)
|
||||
* Switch to babel 6, again
|
||||
[\#2480](https://github.com/vector-im/vector-web/pull/2480)
|
||||
* Revert "Switch to babel 6"
|
||||
[\#2472](https://github.com/vector-im/vector-web/pull/2472)
|
||||
* Switch to babel 6
|
||||
[\#2461](https://github.com/vector-im/vector-web/pull/2461)
|
||||
|
||||
Changes in [0.8.3](https://github.com/vector-im/vector-web/releases/tag/v0.8.3) (2016-10-12)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.2...v0.8.3)
|
||||
|
||||
* Centre images in dialog buttons
|
||||
[\#2453](https://github.com/vector-im/vector-web/pull/2453)
|
||||
* Only show quote option if RTE is enabled
|
||||
[\#2448](https://github.com/vector-im/vector-web/pull/2448)
|
||||
* Fix join button for 'matrix' networks
|
||||
[\#2443](https://github.com/vector-im/vector-web/pull/2443)
|
||||
* Don't stop paginating if no rooms match
|
||||
[\#2422](https://github.com/vector-im/vector-web/pull/2422)
|
||||
|
||||
Changes in [0.8.2](https://github.com/vector-im/vector-web/releases/tag/v0.8.2) (2016-10-05)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.1...v0.8.2)
|
||||
|
||||
* Add native joining of 3p networks to room dir
|
||||
[\#2379](https://github.com/vector-im/vector-web/pull/2379)
|
||||
* Update to linkify 2.1.3
|
||||
[\#2406](https://github.com/vector-im/vector-web/pull/2406)
|
||||
* Use 'Sign In' / 'Sign Out' universally
|
||||
[\#2383](https://github.com/vector-im/vector-web/pull/2383)
|
||||
* Prevent network dropdown resizing slightly
|
||||
[\#2382](https://github.com/vector-im/vector-web/pull/2382)
|
||||
* Room directory: indicate when there are no results
|
||||
[\#2380](https://github.com/vector-im/vector-web/pull/2380)
|
||||
* Room dir: New filtering & 3rd party networks
|
||||
[\#2362](https://github.com/vector-im/vector-web/pull/2362)
|
||||
* Update linkify version
|
||||
[\#2359](https://github.com/vector-im/vector-web/pull/2359)
|
||||
* Directory search join button
|
||||
[\#2339](https://github.com/vector-im/vector-web/pull/2339)
|
||||
|
||||
Changes in [0.8.1](https://github.com/vector-im/vector-web/releases/tag/v0.8.1) (2016-09-21)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.0...v0.8.1)
|
||||
|
||||
|
||||
Changes in [0.8.0](https://github.com/vector-im/vector-web/releases/tag/v0.8.0) (2016-09-21)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.5-r3...v0.8.0)
|
||||
|
||||
* Dbkr/rebrand
|
||||
[\#2285](https://github.com/vector-im/vector-web/pull/2285)
|
||||
* Listen for close_scalar and close the dialog box when received
|
||||
[\#2282](https://github.com/vector-im/vector-web/pull/2282)
|
||||
* Revert "improve lipstick and support scalar logout"
|
||||
[\#2281](https://github.com/vector-im/vector-web/pull/2281)
|
||||
* improve lipstick and support scalar logout
|
||||
[\#2280](https://github.com/vector-im/vector-web/pull/2280)
|
||||
* Fix changelog links
|
||||
[\#2071](https://github.com/vector-im/vector-web/pull/2071)
|
||||
* Paginate Room Directory
|
||||
[\#2241](https://github.com/vector-im/vector-web/pull/2241)
|
||||
* Make redeploy script symlink config
|
||||
[\#2240](https://github.com/vector-im/vector-web/pull/2240)
|
||||
* Update the version of olm to 1.3.0
|
||||
[\#2210](https://github.com/vector-im/vector-web/pull/2210)
|
||||
* Directory network selector
|
||||
[\#2219](https://github.com/vector-im/vector-web/pull/2219)
|
||||
* Wmwragg/two state sublist headers
|
||||
[\#2235](https://github.com/vector-im/vector-web/pull/2235)
|
||||
* Wmwragg/correct incoming call positioning
|
||||
[\#2222](https://github.com/vector-im/vector-web/pull/2222)
|
||||
* Wmwragg/remove old filter
|
||||
[\#2211](https://github.com/vector-im/vector-web/pull/2211)
|
||||
* Wmwragg/multi invite bugfix
|
||||
[\#2198](https://github.com/vector-im/vector-web/pull/2198)
|
||||
* Wmwragg/chat multi invite
|
||||
[\#2181](https://github.com/vector-im/vector-web/pull/2181)
|
||||
* shuffle bottomleftmenu around a bit
|
||||
[\#2182](https://github.com/vector-im/vector-web/pull/2182)
|
||||
* Improve autocomplete behaviour (styling)
|
||||
[\#2175](https://github.com/vector-im/vector-web/pull/2175)
|
||||
* First wave of E2E visuals
|
||||
[\#2163](https://github.com/vector-im/vector-web/pull/2163)
|
||||
* FilePanel and NotificationPanel support
|
||||
[\#2113](https://github.com/vector-im/vector-web/pull/2113)
|
||||
* Cursor: pointer on member info create room button
|
||||
[\#2151](https://github.com/vector-im/vector-web/pull/2151)
|
||||
* Support for adding DM rooms to the MemberInfo Panel
|
||||
[\#2147](https://github.com/vector-im/vector-web/pull/2147)
|
||||
* Wmwragg/one to one indicators
|
||||
[\#2139](https://github.com/vector-im/vector-web/pull/2139)
|
||||
* Added back the Directory listing button, with new tootlip
|
||||
[\#2136](https://github.com/vector-im/vector-web/pull/2136)
|
||||
* wmwragg/chat invite dialog fix
|
||||
[\#2134](https://github.com/vector-im/vector-web/pull/2134)
|
||||
* Wmwragg/one to one chat
|
||||
[\#2110](https://github.com/vector-im/vector-web/pull/2110)
|
||||
* Support toggling DM status of rooms
|
||||
[\#2111](https://github.com/vector-im/vector-web/pull/2111)
|
||||
* Formatting toolbar for RTE message composer.
|
||||
[\#2082](https://github.com/vector-im/vector-web/pull/2082)
|
||||
* jenkins.sh: install olm from jenkins artifacts
|
||||
[\#2092](https://github.com/vector-im/vector-web/pull/2092)
|
||||
* e2e device CSS
|
||||
[\#2085](https://github.com/vector-im/vector-web/pull/2085)
|
||||
* Bump to olm 1.1.0
|
||||
[\#2069](https://github.com/vector-im/vector-web/pull/2069)
|
||||
* Improve readability of the changelog dialog
|
||||
[\#2056](https://github.com/vector-im/vector-web/pull/2056)
|
||||
* Turn react consistency checks back on in develop builds
|
||||
[\#2009](https://github.com/vector-im/vector-web/pull/2009)
|
||||
* Wmwragg/direct chat sublist
|
||||
[\#2028](https://github.com/vector-im/vector-web/pull/2028)
|
||||
|
||||
Changes in [0.7.5-r3](https://github.com/vector-im/vector-web/releases/tag/v0.7.5-r3) (2016-09-02)
|
||||
==================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.5-r2...v0.7.5-r3)
|
||||
|
||||
* Bump to matrix-react-sdk 0.6.5-r3 in order to fix bug #2020 (tightloop when flooded with join events)
|
||||
|
||||
|
||||
Changes in [0.7.5-r2](https://github.com/vector-im/vector-web/releases/tag/v0.7.5-r2) (2016-09-01)
|
||||
==================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.5-r1...v0.7.5-r2)
|
||||
|
||||
* Bump to matrix-react-sdk 0.6.5-r1 in order to fix guest access
|
||||
|
||||
Changes in [0.7.5-r1](https://github.com/vector-im/vector-web/releases/tag/v0.7.5-r1) (2016-08-28)
|
||||
==================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.5...v0.7.5-r1)
|
||||
|
||||
* Correctly pin deps :(
|
||||
|
||||
Changes in [0.7.5](https://github.com/vector-im/vector-web/releases/tag/v0.7.5) (2016-08-28)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.4-r1...v0.7.5)
|
||||
|
||||
* re-add leave button in RoomSettings
|
||||
* add /user URLs
|
||||
* recognise matrix.to links and other vector links
|
||||
* fix linkify dependency
|
||||
* fix avatar clicking in MemberInfo
|
||||
* fix RoomTagContextMenu so it works on historical rooms
|
||||
* warn people to put their Matrix HS on a separate domain to Vector
|
||||
* fix zalgos again
|
||||
* Add .travis.yml
|
||||
[\#2007](https://github.com/vector-im/vector-web/pull/2007)
|
||||
* add fancy changelog dialog
|
||||
[\#1972](https://github.com/vector-im/vector-web/pull/1972)
|
||||
* Update autocomplete design
|
||||
[\#1978](https://github.com/vector-im/vector-web/pull/1978)
|
||||
* Update encryption info in README
|
||||
[\#2001](https://github.com/vector-im/vector-web/pull/2001)
|
||||
* Added event/info message avatars back in
|
||||
[\#2000](https://github.com/vector-im/vector-web/pull/2000)
|
||||
* Wmwragg/chat message presentation
|
||||
[\#1987](https://github.com/vector-im/vector-web/pull/1987)
|
||||
* Make the notification slider work
|
||||
[\#1982](https://github.com/vector-im/vector-web/pull/1982)
|
||||
* Use cpx to copy olm.js, and add watcher
|
||||
[\#1966](https://github.com/vector-im/vector-web/pull/1966)
|
||||
* Make up a device display name
|
||||
[\#1959](https://github.com/vector-im/vector-web/pull/1959)
|
||||
|
||||
Changes in [0.7.4-r1](https://github.com/vector-im/vector-web/releases/tag/v0.7.4-r1) (2016-08-12)
|
||||
==================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.4...v0.7.4-r1)
|
||||
* Update to matrix-react-sdk 0.6.4-r1 to fix inviting multiple people
|
||||
|
||||
|
||||
Changes in [0.7.4](https://github.com/vector-im/vector-web/releases/tag/v0.7.4) (2016-08-11)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.3...v0.7.4)
|
||||
|
||||
* Don't show border on composer when not in RTE mode
|
||||
[\#1954](https://github.com/vector-im/vector-web/pull/1954)
|
||||
* Wmwragg/room tag menu
|
||||
[\#1941](https://github.com/vector-im/vector-web/pull/1941)
|
||||
* Don't redirect to mobile app if verifying 3pid
|
||||
[\#1951](https://github.com/vector-im/vector-web/pull/1951)
|
||||
* Make sure that we clear localstorage before *all* tests
|
||||
[\#1950](https://github.com/vector-im/vector-web/pull/1950)
|
||||
* Basic CSS for multi-invite dialog
|
||||
[\#1942](https://github.com/vector-im/vector-web/pull/1942)
|
||||
* More tests for the loading process:
|
||||
[\#1947](https://github.com/vector-im/vector-web/pull/1947)
|
||||
* Support for refactored login token handling
|
||||
[\#1946](https://github.com/vector-im/vector-web/pull/1946)
|
||||
* Various fixes and improvements to emojification.
|
||||
[\#1935](https://github.com/vector-im/vector-web/pull/1935)
|
||||
* More app-loading tests
|
||||
[\#1938](https://github.com/vector-im/vector-web/pull/1938)
|
||||
* Some tests of the application load process
|
||||
[\#1936](https://github.com/vector-im/vector-web/pull/1936)
|
||||
* Add 'enable labs' setting to sample config
|
||||
[\#1930](https://github.com/vector-im/vector-web/pull/1930)
|
||||
* Matthew/scalar
|
||||
[\#1928](https://github.com/vector-im/vector-web/pull/1928)
|
||||
* Fix unit tests
|
||||
[\#1929](https://github.com/vector-im/vector-web/pull/1929)
|
||||
* Wmwragg/mute mention state fix
|
||||
[\#1926](https://github.com/vector-im/vector-web/pull/1926)
|
||||
* CSS for deactivate account dialog
|
||||
[\#1919](https://github.com/vector-im/vector-web/pull/1919)
|
||||
* Wmwragg/mention state menu
|
||||
[\#1900](https://github.com/vector-im/vector-web/pull/1900)
|
||||
* Fix UnknownBody styling for #1901
|
||||
[\#1913](https://github.com/vector-im/vector-web/pull/1913)
|
||||
* Exclude olm from the webpack
|
||||
[\#1914](https://github.com/vector-im/vector-web/pull/1914)
|
||||
* Wmwragg/button updates
|
||||
[\#1912](https://github.com/vector-im/vector-web/pull/1912)
|
||||
* Wmwragg/button updates
|
||||
[\#1828](https://github.com/vector-im/vector-web/pull/1828)
|
||||
* CSS for device management UI
|
||||
[\#1909](https://github.com/vector-im/vector-web/pull/1909)
|
||||
* Fix a warning from RoomSubList
|
||||
[\#1908](https://github.com/vector-im/vector-web/pull/1908)
|
||||
* Fix notifications warning layout
|
||||
[\#1907](https://github.com/vector-im/vector-web/pull/1907)
|
||||
* Remove relayoutOnUpdate prop on gemini-scrollbar
|
||||
[\#1883](https://github.com/vector-im/vector-web/pull/1883)
|
||||
* Bump dependency versions
|
||||
[\#1842](https://github.com/vector-im/vector-web/pull/1842)
|
||||
* Wmwragg/mention state indicator round 2
|
||||
[\#1835](https://github.com/vector-im/vector-web/pull/1835)
|
||||
* Wmwragg/spinner fix
|
||||
[\#1822](https://github.com/vector-im/vector-web/pull/1822)
|
||||
* Wmwragg/mention state indicator
|
||||
[\#1823](https://github.com/vector-im/vector-web/pull/1823)
|
||||
* Revert "Presentation for inline link"
|
||||
[\#1809](https://github.com/vector-im/vector-web/pull/1809)
|
||||
* Wmwragg/modal restyle
|
||||
[\#1806](https://github.com/vector-im/vector-web/pull/1806)
|
||||
* Presentation for inline link
|
||||
[\#1799](https://github.com/vector-im/vector-web/pull/1799)
|
||||
* CSS for offline user colours
|
||||
[\#1798](https://github.com/vector-im/vector-web/pull/1798)
|
||||
* Wmwragg/typography updates
|
||||
[\#1776](https://github.com/vector-im/vector-web/pull/1776)
|
||||
* webpack: always use the olm from vector-web
|
||||
[\#1766](https://github.com/vector-im/vector-web/pull/1766)
|
||||
* feat: large emoji support
|
||||
[\#1718](https://github.com/vector-im/vector-web/pull/1718)
|
||||
* Autocomplete
|
||||
[\#1717](https://github.com/vector-im/vector-web/pull/1717)
|
||||
* #1664 Set a maximum height for codeblocks
|
||||
[\#1670](https://github.com/vector-im/vector-web/pull/1670)
|
||||
* CSS for device blocking
|
||||
[\#1688](https://github.com/vector-im/vector-web/pull/1688)
|
||||
* Fix joining rooms by typing the alias
|
||||
[\#1685](https://github.com/vector-im/vector-web/pull/1685)
|
||||
* Add ability to delete an alias from room directory
|
||||
[\#1680](https://github.com/vector-im/vector-web/pull/1680)
|
||||
* package.json: add olm as optionalDependency
|
||||
[\#1678](https://github.com/vector-im/vector-web/pull/1678)
|
||||
* Another go at enabling olm on vector.im/develop
|
||||
[\#1675](https://github.com/vector-im/vector-web/pull/1675)
|
||||
* CSS for unverify button
|
||||
[\#1661](https://github.com/vector-im/vector-web/pull/1661)
|
||||
* CSS fix for rooms with crypto enabled
|
||||
[\#1660](https://github.com/vector-im/vector-web/pull/1660)
|
||||
* Karma: fix warning by ignoring olm
|
||||
[\#1652](https://github.com/vector-im/vector-web/pull/1652)
|
||||
* Update for react-sdk dbkr/fix_peeking branch
|
||||
[\#1639](https://github.com/vector-im/vector-web/pull/1639)
|
||||
* Update README.md
|
||||
[\#1641](https://github.com/vector-im/vector-web/pull/1641)
|
||||
* Fix karma tests
|
||||
[\#1643](https://github.com/vector-im/vector-web/pull/1643)
|
||||
* Rich Text Editor
|
||||
[\#1553](https://github.com/vector-im/vector-web/pull/1553)
|
||||
* Fix RoomDirectory to join by alias whenever possible.
|
||||
[\#1615](https://github.com/vector-im/vector-web/pull/1615)
|
||||
* Make the config optional
|
||||
[\#1612](https://github.com/vector-im/vector-web/pull/1612)
|
||||
* CSS support for device verification
|
||||
[\#1610](https://github.com/vector-im/vector-web/pull/1610)
|
||||
* Don't use SdkConfig
|
||||
[\#1609](https://github.com/vector-im/vector-web/pull/1609)
|
||||
* serve config.json statically instead of bundling it
|
||||
[\#1516](https://github.com/vector-im/vector-web/pull/1516)
|
||||
|
||||
Changes in [0.7.3](https://github.com/vector-im/vector-web/releases/tag/v0.7.3) (2016-06-03)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.2...v0.7.3)
|
||||
|
||||
* Update to react-sdk 0.6.3
|
||||
|
||||
Changes in [0.7.2](https://github.com/vector-im/vector-web/releases/tag/v0.7.2) (2016-06-02)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.1...v0.7.2)
|
||||
|
||||
* Correctly bump the dep on new matrix-js-sdk and matrix-react-sdk
|
||||
|
||||
Changes in [0.7.1](https://github.com/vector-im/vector-web/releases/tag/v0.7.1) (2016-06-02)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.0...v0.7.1)
|
||||
|
||||
* Fix accidentally committed local changes to the default config.json (doh!)
|
||||
|
||||
Changes in [0.7.0](https://github.com/vector-im/vector-web/releases/tag/v0.7.0) (2016-06-02)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.6.1...v0.7.0)
|
||||
|
||||
* Update to matrix-react-sdk 0.6.0 - see
|
||||
[changelog](https://github.com/matrix-org/matrix-react-sdk/blob/v0.6.0/CHANGELOG.md)
|
||||
* Style selection color.
|
||||
[\#1557](https://github.com/vector-im/vector-web/pull/1557)
|
||||
* Fix NPE when loading the Settings page which infini-spinnered
|
||||
[\#1518](https://github.com/vector-im/vector-web/pull/1518)
|
||||
* Add option to enable email notifications
|
||||
[\#1469](https://github.com/vector-im/vector-web/pull/1469)
|
||||
|
||||
Changes in [0.6.1](https://github.com/vector-im/vector-web/releases/tag/v0.6.1) (2016-04-22)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.6.0...v0.6.1)
|
||||
|
||||
* Update to matrix-react-sdk 0.5.2 - see
|
||||
[changelog](https://github.com/matrix-org/matrix-react-sdk/blob/v0.5.2/CHANGELOG.md)
|
||||
* Don't relayout scrollpanels every time something changes
|
||||
[\#1438](https://github.com/vector-im/vector-web/pull/1438)
|
||||
* Include react-addons-perf for non-production builds
|
||||
[\#1431](https://github.com/vector-im/vector-web/pull/1431)
|
||||
|
||||
Changes in [0.6.0](https://github.com/vector-im/vector-web/releases/tag/v0.6.0) (2016-04-19)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.5.0...v0.6.0)
|
||||
|
||||
* Matthew/design tweaks
|
||||
[\#1402](https://github.com/vector-im/vector-web/pull/1402)
|
||||
* Improve handling of notification rules we can't parse
|
||||
[\#1399](https://github.com/vector-im/vector-web/pull/1399)
|
||||
* Do less mangling of jenkins builds
|
||||
[\#1391](https://github.com/vector-im/vector-web/pull/1391)
|
||||
* Start Notifications component refactor
|
||||
[\#1386](https://github.com/vector-im/vector-web/pull/1386)
|
||||
* make the UI fadable to help with decluttering
|
||||
[\#1376](https://github.com/vector-im/vector-web/pull/1376)
|
||||
* Get and display a user's pushers in settings
|
||||
[\#1374](https://github.com/vector-im/vector-web/pull/1374)
|
||||
* URL previewing support
|
||||
[\#1343](https://github.com/vector-im/vector-web/pull/1343)
|
||||
* 😄 Emoji autocomplete and unicode emoji to image conversion using emojione.
|
||||
[\#1332](https://github.com/vector-im/vector-web/pull/1332)
|
||||
* Show full-size avatar on MemberInfo avatar click
|
||||
[\#1340](https://github.com/vector-im/vector-web/pull/1340)
|
||||
* Numerous other changes via [matrix-react-sdk 0.5.1](https://github.com/matrix-org/matrix-react-sdk/blob/v0.5.1/CHANGELOG.md)
|
||||
|
||||
Changes in [0.5.0](https://github.com/vector-im/vector-web/releases/tag/v0.5.0) (2016-03-30)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.4.1...v0.5.0)
|
||||
|
||||
* Prettier, animated placeholder :D
|
||||
[\#1292](https://github.com/vector-im/vector-web/pull/1292)
|
||||
(Disabled for now due to high CPU usage)
|
||||
* RoomDirectory: use SimpleRoomHeader instead of RoomHeader
|
||||
[\#1307](https://github.com/vector-im/vector-web/pull/1307)
|
||||
* Tell webpack not to parse the highlight.js languages
|
||||
[\#1277](https://github.com/vector-im/vector-web/pull/1277)
|
||||
* CSS for https://github.com/matrix-org/matrix-react-sdk/pull/247
|
||||
[\#1249](https://github.com/vector-im/vector-web/pull/1249)
|
||||
* URI-decode the hash-fragment
|
||||
[\#1254](https://github.com/vector-im/vector-web/pull/1254)
|
||||
|
||||
Changes in [0.4.1](https://github.com/vector-im/vector-web/releases/tag/v0.4.1) (2016-03-23)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.4.0...v0.4.1)
|
||||
@@ -55,7 +735,7 @@ Changes in vector v0.1.2 (2015-10-28)
|
||||
* Better hover-over on member list
|
||||
* Support CAS auth
|
||||
* Many other bug fixes
|
||||
|
||||
|
||||
Changes in vector v0.1.1 (2015-08-10)
|
||||
======================================
|
||||
|
||||
|
||||
276
README.md
@@ -1,65 +1,204 @@
|
||||
Vector/Web
|
||||
==========
|
||||
Riot
|
||||
====
|
||||
|
||||
Vector is a Matrix web client built using the Matrix React SDK (https://github.com/matrix-org/matrix-react-sdk).
|
||||
Riot (formerly known as Vector) is a Matrix web client built using the Matrix
|
||||
React SDK (https://github.com/matrix-org/matrix-react-sdk).
|
||||
|
||||
Getting started
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
The easiest way to test Riot is to just use the hosted copy at
|
||||
https://riot.im/app. The develop branch is continuously deployed by Jenkins at
|
||||
https://riot.im/develop for those who like living dangerously.
|
||||
|
||||
To host your own copy of Riot, the quickest bet is to use a pre-built
|
||||
released version of Riot:
|
||||
|
||||
1. Download the latest version from https://github.com/vector-im/riot-web/releases
|
||||
1. Untar the tarball on your web server
|
||||
1. Move (or symlink) the vector-x.x.x directory to an appropriate name
|
||||
1. If desired, copy `config.sample.json` to `config.json` and edit it
|
||||
as desired. See below for details.
|
||||
1. Enter the URL into your browser and log into Riot!
|
||||
|
||||
Note that Chrome does not allow microphone or webcam access for sites served
|
||||
over http (except localhost), so for working VoIP you will need to serve Riot
|
||||
over https.
|
||||
|
||||
### Installation Steps for Debian Stretch
|
||||
1. Add the repository to your sources.list using either of the following two options:
|
||||
- Directly to sources.list: `echo "deb https://riot.im/packages/debian/ stretch main" | sudo tee -a /etc/apt/sources.list`
|
||||
- As a separate entry in sources.list.d: `echo "deb https://riot.im/packages/debian/ stretch main" | sudo tee /etc/apt/sources.list.d/riot.list`
|
||||
2. Add the gpg signing key for the riot repository: `curl -s https://riot.im/packages/debian/repo-key.asc | sudo apt-key add -`
|
||||
3. Update your package lists: `sudo apt-get update`
|
||||
4. Install Riot: `sudo apt-get install riot-web`
|
||||
|
||||
Important Security Note
|
||||
=======================
|
||||
|
||||
We do not recommend running Riot from the same domain name as your Matrix
|
||||
homeserver. The reason is the risk of XSS (cross-site-scripting)
|
||||
vulnerabilities that could occur if someone caused Riot to load and render
|
||||
malicious user generated content from a Matrix API which then had trusted
|
||||
access to Riot (or other apps) due to sharing the same domain.
|
||||
|
||||
We have put some coarse mitigations into place to try to protect against this
|
||||
situation, but it's still not good practice to do it in the first place. See
|
||||
https://github.com/vector-im/riot-web/issues/1977 for more details.
|
||||
|
||||
Building From Source
|
||||
====================
|
||||
|
||||
Riot is a modular webapp built with modern ES6 and requires a npm build system
|
||||
to build.
|
||||
|
||||
1. Install or update `node.js` so that your `npm` is at least at version `2.0.0`
|
||||
1. Clone the repo: `git clone https://github.com/vector-im/vector-web.git`
|
||||
1. Switch to the vector directory: `cd vector-web`
|
||||
1. Clone the repo: `git clone https://github.com/vector-im/riot-web.git`
|
||||
1. Switch to the riot-web directory: `cd riot-web`
|
||||
1. Install the prerequisites: `npm install`
|
||||
1. If you are using the `develop` branch of vector, you will probably need to
|
||||
rebuild one of the dependencies, due to https://github.com/npm/npm/issues/3055:
|
||||
`(cd node_modules/matrix-react-sdk && npm install)`
|
||||
1. Start the development builder and a testing server: `npm start`
|
||||
1. Wait a few seconds for the initial build to finish (the command won't
|
||||
terminate: it's running a web server for you).
|
||||
1. Open http://127.0.0.1:8080/ in your browser to see your newly built Vector.
|
||||
1. If you are using the `develop` branch of vector-web, you will probably need
|
||||
to rebuild some of the dependencies, due to
|
||||
https://github.com/npm/npm/issues/3055:
|
||||
|
||||
With `npm start`, any changes you make to the source files will cause a rebuild so
|
||||
your changes will show up when you refresh. This development server also disables
|
||||
caching, so do NOT use it in production.
|
||||
```
|
||||
(cd node_modules/matrix-js-sdk && npm install)
|
||||
(cd node_modules/matrix-react-sdk && npm install)
|
||||
```
|
||||
1. Configure the app by copying `config.sample.json` to `config.json` and
|
||||
modifying it (see below for details)
|
||||
1. `npm run dist` to build a tarball to deploy. Untaring this file will give
|
||||
a version-specific directory containing all the files that need to go on your
|
||||
web server.
|
||||
|
||||
Configuring
|
||||
Note that `npm run dist` is not supported on Windows, so Windows users can run `npm
|
||||
run build`, which will build all the necessary files into the `webapp`
|
||||
directory. The version of Riot will not appear in Settings without
|
||||
using the dist script. You can then mount the `webapp` directory on your
|
||||
webserver to actually serve up the app, which is entirely static content.
|
||||
|
||||
config.json
|
||||
===========
|
||||
|
||||
Configure the app by modifying the `config.json` file:
|
||||
You can configure the app by copying `config.sample.json` to
|
||||
`config.json` and customising it:
|
||||
|
||||
1. `default_hs_url` is the default home server url.
|
||||
1. `default_is_url` is the default identity server url (this is the server used
|
||||
for verifying third party identifiers like email addresses). If this is blank,
|
||||
registering with an email address or adding an email address to your account
|
||||
will not work.
|
||||
registering with an email address, adding an email address to your account,
|
||||
or inviting users via email address will not work. Matrix identity servers are
|
||||
very simple web services which map third party identifiers (currently only email
|
||||
addresses) to matrix IDs: see http://matrix.org/docs/spec/identity_service/unstable.html
|
||||
for more details. Currently the only public matrix identity servers are https://matrix.org
|
||||
and https://vector.im. In future identity servers will be decentralised.
|
||||
1. `integrations_ui_url`: URL to the web interface for the integrations server.
|
||||
1. `integrations_rest_url`: URL to the REST interface for the integrations server.
|
||||
1. `roomDirectory`: config for the public room directory. This section is optional.
|
||||
1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop
|
||||
down list. Optional.
|
||||
1. `update_base_url` (electron app only): HTTPS URL to a web server to download
|
||||
updates from. This should be the path to the directory containing `macos`
|
||||
and `win32` (for update packages, not installer packages).
|
||||
1. `cross_origin_renderer_url`: URL to a static HTML page hosting code to help display
|
||||
encrypted file attachments. This MUST be hosted on a completely separate domain to
|
||||
anything else since it is used to isolate the privileges of file attachments to this
|
||||
domain. Default: `usercontent.riot.im`. This needs to contain v1.html from
|
||||
https://github.com/matrix-org/usercontent/blob/master/v1.html
|
||||
|
||||
You will need to re-run `npm run build` after editing `config.json`.
|
||||
Running as a Desktop app
|
||||
========================
|
||||
|
||||
Deployment
|
||||
==========
|
||||
Riot can also be run as a desktop app, wrapped in electron. You can download a
|
||||
pre-built version from https://riot.im/desktop.html or, if you prefer,
|
||||
built it yourself.
|
||||
|
||||
On a Unix-based OS, run `npm run package` to build a tarball package. Untaring
|
||||
this file will give a version-specific directory containing all the files that
|
||||
need to go on your web server.
|
||||
To run as a desktop app:
|
||||
|
||||
The package script is not supported on Windows, so Windows users can run `npm
|
||||
run build`, which will build all the necessary files into the `vector`
|
||||
directory. Note that the version of Vector will not appear in Settings without
|
||||
using the package script. You can then mount the vector directory on your
|
||||
webserver to actually serve up the app, which is entirely static content.
|
||||
1. Follow the instructions in 'Building From Source' above
|
||||
2. Install electron and run it:
|
||||
|
||||
```
|
||||
npm install electron
|
||||
node_modules/.bin/electron .
|
||||
```
|
||||
|
||||
To build packages, use electron-builder. This is configured to output:
|
||||
* dmg + zip for macOS
|
||||
* exe + nupkg for Windows
|
||||
* deb for Linux
|
||||
But this can be customised by editing the `build` section of package.json
|
||||
as per https://github.com/electron-userland/electron-builder/wiki/Options
|
||||
|
||||
See https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build
|
||||
for dependencies required for building packages for various platforms.
|
||||
|
||||
The only platform that can build packages for all three platforms is macOS:
|
||||
```
|
||||
brew install wine --without-x11
|
||||
brew install mono
|
||||
brew install gnu-tar
|
||||
npm install
|
||||
npm run build:electron
|
||||
```
|
||||
|
||||
For other packages, use electron-builder manually. For example, to build a package
|
||||
for 64 bit Linux:
|
||||
|
||||
1. Follow the instructions in 'Building From Source' above
|
||||
2. `node_modules/.bin/build -l --x64`
|
||||
|
||||
All electron packages go into `electron/dist/`
|
||||
|
||||
Many thanks to @aviraldg for the initial work on the electron integration.
|
||||
|
||||
Other options for running as a desktop app:
|
||||
* https://github.com/krisak/vector-electron-desktop
|
||||
* @asdf:matrix.org points out that you can use nativefier and it just works(tm)
|
||||
|
||||
```
|
||||
sudo npm install nativefier -g
|
||||
nativefier https://riot.im/app/
|
||||
```
|
||||
|
||||
Development
|
||||
===========
|
||||
|
||||
For simple tweaks, you can work on any of the source files within Vector with
|
||||
the setup above, and your changes will cause an instant rebuild.
|
||||
Before attempting to develop on Riot you **must** read the developer guide
|
||||
for `matrix-react-sdk` at https://github.com/matrix-org/matrix-react-sdk, which
|
||||
also defines the design, architecture and style for Riot too.
|
||||
|
||||
However, much of the functionality in Vector is actually in the
|
||||
`matrix-react-sdk` and `matrix-js-sdk` modules. It is possible to set these up
|
||||
in a way that makes it easy to track the `develop` branches in git and to make
|
||||
local changes without having to manually rebuild each time.
|
||||
The idea of Riot is to be a relatively lightweight "skin" of customisations on
|
||||
top of the underlying `matrix-react-sdk`. `matrix-react-sdk` provides both the
|
||||
higher and lower level React components useful for building Matrix communication
|
||||
apps using React.
|
||||
|
||||
[Be aware that there may be problems with this process under npm version 3.]
|
||||
After creating a new component you must run `npm run reskindex` to regenerate
|
||||
the `component-index.js` for the app (used in future for skinning)
|
||||
|
||||
**However, as of July 2016 this layering abstraction is broken due to rapid
|
||||
development on Riot forcing `matrix-react-sdk` to move fast at the expense of
|
||||
maintaining a clear abstraction between the two.** Hacking on Riot inevitably
|
||||
means hacking equally on `matrix-react-sdk`, and there are bits of
|
||||
`matrix-react-sdk` behaviour incorrectly residing in the `riot-web` project
|
||||
(e.g. matrix-react-sdk specific CSS), and a bunch of Riot specific behaviour
|
||||
in the `matrix-react-sdk` (grep for `vector` / `riot`). This separation problem will be
|
||||
solved asap once development on Riot (and thus matrix-react-sdk) has
|
||||
stabilised. Until then, the two projects should basically be considered as a
|
||||
single unit. In particular, `matrix-react-sdk` issues are currently filed
|
||||
against `riot-web` in github.
|
||||
|
||||
Please note that Riot is intended to run correctly without access to the public
|
||||
internet. So please don't depend on resources (JS libs, CSS, images, fonts)
|
||||
hosted by external CDNs or servers but instead please package all dependencies
|
||||
into Riot itself.
|
||||
|
||||
Setting up a dev environment
|
||||
============================
|
||||
|
||||
Much of the functionality in Riot is actually in the `matrix-react-sdk` and
|
||||
`matrix-js-sdk` modules. It is possible to set these up in a way that makes it
|
||||
easy to track the `develop` branches in git and to make local changes without
|
||||
having to manually rebuild each time.
|
||||
|
||||
First clone and build `matrix-js-sdk`:
|
||||
|
||||
@@ -79,10 +218,10 @@ Then similarly with `matrix-react-sdk`:
|
||||
1. `rm -r node_modules/matrix-js-sdk; ln -s ../../matrix-js-sdk node_modules/`
|
||||
1. `popd`
|
||||
|
||||
Finally, build and start vector itself:
|
||||
Finally, build and start Riot itself:
|
||||
|
||||
1. `git clone git@github.com:vector-im/vector-web.git`
|
||||
1. `cd vector-web`
|
||||
1. `git clone git@github.com:vector-im/riot-web.git`
|
||||
1. `cd riot-web`
|
||||
1. `git checkout develop`
|
||||
1. `npm install`
|
||||
1. `rm -r node_modules/matrix-js-sdk; ln -s ../../matrix-js-sdk node_modules/`
|
||||
@@ -102,42 +241,43 @@ Finally, build and start vector itself:
|
||||
+ 1013 hidden modules
|
||||
```
|
||||
Remember, the command will not terminate since it runs the web server
|
||||
and rebuilds source files when they change.
|
||||
1. Open http://127.0.0.1:8080/ in your browser to see your newly built Vector.
|
||||
and rebuilds source files when they change. This development server also
|
||||
disables caching, so do NOT use it in production.
|
||||
1. Open http://127.0.0.1:8080/ in your browser to see your newly built Riot.
|
||||
|
||||
When you make changes to `matrix-js-sdk` or `matrix-react-sdk`, you will need
|
||||
When you make changes to `matrix-react-sdk` or `matrix-js-sdk`, you will need
|
||||
to run `npm run build` in the relevant directory. You can do this automatically
|
||||
by instead running `npm start` in each directory, to start a development
|
||||
builder which will watch for changes to the files and rebuild automatically.
|
||||
by instead running `npm start` in the directory, to start a development builder
|
||||
which will watch for changes to the files and rebuild automatically.
|
||||
|
||||
If you add or remove any components from the Vector skin, you will need to rebuild
|
||||
If you add or remove any components from the Riot skin, you will need to rebuild
|
||||
the skin's index by running, `npm run reskindex`.
|
||||
|
||||
Enabling encryption
|
||||
===================
|
||||
If any of these steps error with, `file table overflow`, you are probably on a mac
|
||||
which has a very low limit on max open files. Run `ulimit -Sn 1024` and try again.
|
||||
You'll need to do this in each new terminal you open before building Riot.
|
||||
|
||||
End-to-end encryption in Vector and Matrix is not yet considered ready for
|
||||
day-to-day use; it is experimental and should be considered only as a
|
||||
proof-of-concept. See https://matrix.org/jira/browse/SPEC-162 for an overview
|
||||
of the current progress.
|
||||
Triaging issues
|
||||
===============
|
||||
|
||||
To build a version of vector with support for end-to-end encryption, install
|
||||
the olm module with `npm i https://matrix.org/packages/npm/olm/olm-0.1.0.tgz`
|
||||
before running `npm start`. The olm library will be detected and used if
|
||||
available.
|
||||
Issues will be triaged by the core team using the following primary set of tags:
|
||||
|
||||
To enable encryption for a room, type
|
||||
priority:
|
||||
P1: top priority; typically blocks releases.
|
||||
P2: one below that
|
||||
P3: non-urgent
|
||||
P4/P5: bluesky some day, who knows.
|
||||
|
||||
```
|
||||
/encrypt on
|
||||
```
|
||||
bug or feature:
|
||||
bug severity:
|
||||
* cosmetic - feature works functionally but UI/UX is broken.
|
||||
* critical - whole app doesn't work
|
||||
* major - entire feature doesn't work
|
||||
* minor - partially broken feature (but still usable)
|
||||
|
||||
in the message bar in that room. Vector will then generate a set of keys, and
|
||||
encrypt all outgoing messages in that room. (Note that other people in that
|
||||
room will send messages in the clear unless they also `/encrypt on`.)
|
||||
* release blocker
|
||||
|
||||
Note that historical encrypted messages cannot currently be decoded - history
|
||||
is therefore lost when the page is reloaded.
|
||||
* ui/ux (think of this as cosmetic)
|
||||
|
||||
There is currently no visual indication of whether encryption is enabled for a
|
||||
room, or whether a particular message was encrypted.
|
||||
* network (specific to network conditions)
|
||||
* platform (platform specific)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"default_hs_url": "https://matrix.org",
|
||||
"default_is_url": "https://vector.im"
|
||||
}
|
||||
14
config.sample.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"default_hs_url": "https://matrix.org",
|
||||
"default_is_url": "https://vector.im",
|
||||
"brand": "Riot",
|
||||
"integrations_ui_url": "https://scalar.vector.im/",
|
||||
"integrations_rest_url": "https://scalar.vector.im/api",
|
||||
"bug_report_endpoint_url": "https://vector.im/bugs",
|
||||
"enableLabs": true,
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org"
|
||||
]
|
||||
}
|
||||
}
|
||||
25
docs/theming.md
Normal file
@@ -0,0 +1,25 @@
|
||||
Theming Riot
|
||||
============
|
||||
|
||||
Themes are a very basic way of providing simple alternative look & feels to the
|
||||
riot-web app via CSS & custom imagery.
|
||||
|
||||
They are *NOT* co be confused with 'skins', which describe apps which sit on top
|
||||
of matrix-react-sdk - e.g. in theory Riot itself is a react-sdk skin.
|
||||
As of Jan 2017, skins are not fully supported; riot is the only available skin.
|
||||
|
||||
To define a theme for Riot:
|
||||
|
||||
1. Pick a name, e.g. `teal`. at time of writing we have `light` and `dark`.
|
||||
2. Fork `src/skins/vector/css/themes/dark.scss` to be teal.scss
|
||||
3. Fork `src/skins/vector/css/themes/_base.scss` to be _teal.scss
|
||||
4. Override variables in _teal.scss as desired. You may wish to delete ones
|
||||
which don't differ from _base.scss, to make it clear which are being
|
||||
overridden. If every single colour is being changed (as per _dark.scss)
|
||||
then you might as well keep them all.
|
||||
5. Add the theme to the list of entrypoints in webpack.config.js
|
||||
6. Add the theme to the list of themes in matrix-react-sdk's UserSettings.js
|
||||
7. Sit back and admire your handywork.
|
||||
|
||||
In future, the assets for a theme will probably be gathered together into a
|
||||
single directory tree.
|
||||
BIN
electron/build/icon.icns
Normal file
BIN
electron/build/icon.ico
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
electron/build/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
electron/build/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
electron/build/icons/24x24.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
electron/build/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
electron/build/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
electron/build/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
electron/build/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
electron/build/icons/96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
electron/build/install-spinner.gif
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
electron/img/riot.ico
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
electron/img/riot.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
4
electron/riot.im/README
Normal file
@@ -0,0 +1,4 @@
|
||||
This directory contains the config file for the official riot.im distribution
|
||||
of Riot Desktop. You probably do not want to build with this config unless
|
||||
you're building the official riot.im distribution, or you'll find your builds
|
||||
will replace themselves with the riot.im build.
|
||||
72
electron/riot.im/config.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"update_base_url": "https://riot.im/download/desktop/update/",
|
||||
"default_hs_url": "https://matrix.org",
|
||||
"default_is_url": "https://vector.im",
|
||||
"brand": "Riot",
|
||||
"integrations_ui_url": "https://scalar.vector.im/",
|
||||
"integrations_rest_url": "https://scalar.vector.im/api",
|
||||
"enableLabs": true,
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org"
|
||||
],
|
||||
"serverConfig": {
|
||||
"matrix.org": {
|
||||
"networks": [
|
||||
"_matrix",
|
||||
"gitter",
|
||||
"irc:freenode",
|
||||
"irc:mozilla",
|
||||
"irc:snoonet",
|
||||
"irc:oftc"
|
||||
]
|
||||
}
|
||||
},
|
||||
"networks": {
|
||||
"gitter": {
|
||||
"protocol": "gitter",
|
||||
"portalRoomPattern": "#gitter_.*:matrix.org",
|
||||
"name": "Gitter",
|
||||
"icon": "https://gitter.im/favicon.ico",
|
||||
"example": "org/community",
|
||||
"nativePattern": "[^\\s]+/[^\\s]+$"
|
||||
},
|
||||
"irc:freenode": {
|
||||
"protocol": "irc",
|
||||
"domain": "chat.freenode.net",
|
||||
"portalRoomPattern": "#freenode_.*:matrix.org",
|
||||
"name": "Freenode",
|
||||
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"example": "#channel",
|
||||
"nativePattern": "^#[^\\s]+$"
|
||||
},
|
||||
"irc:mozilla": {
|
||||
"protocol": "irc",
|
||||
"domain": "chat.freenode.net",
|
||||
"portalRoomPattern": "#mozilla_.*:matrix.org",
|
||||
"name": "Mozilla",
|
||||
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"example": "#channel",
|
||||
"nativePattern": "^#[^\\s]+$"
|
||||
},
|
||||
"irc:snoonet": {
|
||||
"protocol": "irc",
|
||||
"domain": "ipv6-irc.snoonet.org",
|
||||
"portalRoomPattern": "#_snoonet_.*:matrix.org",
|
||||
"name": "Snoonet",
|
||||
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"example": "#channel",
|
||||
"nativePattern": "^#[^\\s]+$"
|
||||
},
|
||||
"irc:oftc": {
|
||||
"protocol": "irc",
|
||||
"domain": "irc.oftc.net",
|
||||
"portalRoomPattern": "#_oftc_.*:matrix.org",
|
||||
"name": "OFTC",
|
||||
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"example": "#channel",
|
||||
"nativePattern": "^#[^\\s]+$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
247
electron/src/electron-main.js
Normal file
@@ -0,0 +1,247 @@
|
||||
// @flow
|
||||
|
||||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
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.
|
||||
*/
|
||||
|
||||
// Squirrel on windows starts the app with various flags
|
||||
// as hooks to tell us when we've been installed/uninstalled
|
||||
// etc.
|
||||
const check_squirrel_hooks = require('./squirrelhooks');
|
||||
if (check_squirrel_hooks()) return;
|
||||
|
||||
const electron = require('electron');
|
||||
const url = require('url');
|
||||
|
||||
const tray = require('./tray');
|
||||
|
||||
const VectorMenu = require('./vectormenu');
|
||||
|
||||
let vectorConfig = {};
|
||||
try {
|
||||
vectorConfig = require('../../webapp/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
|
||||
// file or invalid json, so node is just very unhelpful.
|
||||
// Continue with the defaults (ie. an empty config)
|
||||
}
|
||||
|
||||
const PERMITTED_URL_SCHEMES = [
|
||||
'http:',
|
||||
'https:',
|
||||
'mailto:',
|
||||
];
|
||||
|
||||
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
|
||||
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
|
||||
|
||||
let mainWindow = null;
|
||||
let appQuitting = false;
|
||||
|
||||
function safeOpenURL(target) {
|
||||
// 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
|
||||
// with a shell, albeit with no arguments)
|
||||
const parsed_url = url.parse(target);
|
||||
if (PERMITTED_URL_SCHEMES.indexOf(parsed_url.protocol) > -1) {
|
||||
// explicitly use the URL re-assembled by the url library,
|
||||
// so we know the url parser has understood all the parts
|
||||
// of the input string
|
||||
const new_target = url.format(parsed_url);
|
||||
electron.shell.openExternal(new_target);
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowOrNavigate(ev, target) {
|
||||
// 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
|
||||
// url in a window that has node scripting access.
|
||||
ev.preventDefault();
|
||||
safeOpenURL(target);
|
||||
}
|
||||
|
||||
function onLinkContextMenu(ev, params) {
|
||||
const popup_menu = new electron.Menu();
|
||||
popup_menu.append(new electron.MenuItem({
|
||||
label: params.linkURL,
|
||||
click() {
|
||||
safeOpenURL(params.linkURL);
|
||||
},
|
||||
}));
|
||||
popup_menu.popup();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function installUpdate() {
|
||||
// for some reason, quitAndInstall does not fire the
|
||||
// before-quit event, so we need to set the flag here.
|
||||
appQuitting = true;
|
||||
electron.autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
function pollForUpdates() {
|
||||
try {
|
||||
electron.autoUpdater.checkForUpdates();
|
||||
} catch (e) {
|
||||
console.log("Couldn't check for update", e);
|
||||
}
|
||||
}
|
||||
|
||||
function startAutoUpdate(update_base_url) {
|
||||
if (update_base_url.slice(-1) !== '/') {
|
||||
update_base_url = update_base_url + '/';
|
||||
}
|
||||
try {
|
||||
// For reasons best known to Squirrel, the way it checks for updates
|
||||
// is completely different between macOS and windows. On macOS, it
|
||||
// hits a URL that either gives it a 200 with some json or
|
||||
// 204 No Content. On windows it takes a base path and looks for
|
||||
// files under that path.
|
||||
if (process.platform == 'darwin') {
|
||||
// include the current version in the URL we hit. Electron doesn't add
|
||||
// it anywhere (apart from the User-Agent) so it's up to us. We could
|
||||
// (and previously did) just use the User-Agent, but this doesn't
|
||||
// rely on NSURLConnection setting the User-Agent to what we expect,
|
||||
// and also acts as a convenient cache-buster to ensure that when the
|
||||
// app updates it always gets a fresh value to avoid update-looping.
|
||||
electron.autoUpdater.setFeedURL(
|
||||
update_base_url +
|
||||
'macos/?localVersion=' + encodeURIComponent(electron.app.getVersion())
|
||||
);
|
||||
} else if (process.platform == 'win32') {
|
||||
electron.autoUpdater.setFeedURL(update_base_url + 'win32/' + process.arch + '/');
|
||||
} else {
|
||||
// Squirrel / electron only supports auto-update on these two platforms.
|
||||
// I'm not even going to try to guess which feed style they'd use if they
|
||||
// implemented it on Linux, or if it would be different again.
|
||||
console.log("Auto update not supported on this platform");
|
||||
}
|
||||
// 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:
|
||||
// every hour should be just fine for a desktop app)
|
||||
// However, we still let the main window listen for the update events.
|
||||
// We also wait a short time before checking for updates the first time because
|
||||
// of squirrel on windows and it taking a small amount of time to release a
|
||||
// lock file.
|
||||
setTimeout(pollForUpdates, INITIAL_UPDATE_DELAY_MS);
|
||||
setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS);
|
||||
} catch (err) {
|
||||
// will fail if running in debug mode
|
||||
console.log("Couldn't enable update checking", err);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// no other way to catch this error).
|
||||
// Assuming we generally run from the console when developing,
|
||||
// this is far preferable.
|
||||
process.on('uncaughtException', function (error) {
|
||||
console.log("Unhandled exception", error);
|
||||
});
|
||||
|
||||
electron.ipcMain.on('install_update', installUpdate);
|
||||
|
||||
electron.app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
||||
|
||||
const shouldQuit = electron.app.makeSingleInstance((commandLine, workingDirectory) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (mainWindow) {
|
||||
if (!mainWindow.isVisible()) mainWindow.show();
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldQuit) {
|
||||
electron.app.quit()
|
||||
}
|
||||
|
||||
electron.app.on('ready', () => {
|
||||
if (vectorConfig.update_base_url) {
|
||||
console.log("Starting auto update with base URL: " + vectorConfig.update_base_url);
|
||||
startAutoUpdate(vectorConfig.update_base_url);
|
||||
} else {
|
||||
console.log("No update_base_url is defined: auto update is disabled");
|
||||
}
|
||||
|
||||
const icon_path = `${__dirname}/../img/riot.` + (
|
||||
process.platform == 'win32' ? 'ico' : 'png'
|
||||
);
|
||||
|
||||
mainWindow = new electron.BrowserWindow({
|
||||
icon: icon_path,
|
||||
width: 1024, height: 768,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
});
|
||||
mainWindow.loadURL(`file://${__dirname}/../../webapp/index.html`);
|
||||
electron.Menu.setApplicationMenu(VectorMenu);
|
||||
|
||||
// Create trayIcon icon
|
||||
tray.create(mainWindow, {
|
||||
icon_path: icon_path,
|
||||
brand: vectorConfig.brand || 'Riot'
|
||||
});
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
mainWindow.on('close', (e) => {
|
||||
if (!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();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('new-window', onWindowOrNavigate);
|
||||
mainWindow.webContents.on('will-navigate', onWindowOrNavigate);
|
||||
|
||||
mainWindow.webContents.on('context-menu', function(ev, params) {
|
||||
if (params.linkURL) {
|
||||
onLinkContextMenu(ev, params);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
electron.app.on('window-all-closed', () => {
|
||||
electron.app.quit();
|
||||
});
|
||||
|
||||
electron.app.on('activate', () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
electron.app.on('before-quit', () => {
|
||||
appQuitting = true;
|
||||
});
|
||||
|
||||
// Set the App User Model ID to match what the squirrel
|
||||
// installer uses for the shortcut icon.
|
||||
// This makes notifications work on windows 8.1 (and is
|
||||
// a noop on other platforms).
|
||||
electron.app.setAppUserModelId('com.squirrel.riot-web.Riot');
|
||||
51
electron/src/squirrelhooks.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2017 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 path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
const app = require('electron').app;
|
||||
|
||||
function run_update_exe(args, done) {
|
||||
// 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
|
||||
// information about the app by inspecting the directory it's run from.
|
||||
const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe');
|
||||
console.log('Spawning `%s` with args `%s`', updateExe, args);
|
||||
spawn(updateExe, args, {
|
||||
detached: true
|
||||
}).on('close', done);
|
||||
};
|
||||
|
||||
function check_squirrel_hooks() {
|
||||
if (process.platform != 'win32') return false;
|
||||
|
||||
const cmd = process.argv[1];
|
||||
const target = path.basename(process.execPath);
|
||||
if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
|
||||
run_update_exe(['--createShortcut=' + target + ''], app.quit);
|
||||
return true;
|
||||
} else if (cmd === '--squirrel-uninstall') {
|
||||
run_update_exe(['--removeShortcut=' + target + ''], app.quit);
|
||||
return true;
|
||||
} else if (cmd === '--squirrel-obsolete') {
|
||||
app.quit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = check_squirrel_hooks;
|
||||
67
electron/src/tray.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2017 Karl Glatz <karl@glatz.biz>
|
||||
Copyright 2017 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 path = require('path');
|
||||
const electron = require('electron');
|
||||
|
||||
const app = electron.app;
|
||||
const Tray = electron.Tray;
|
||||
const MenuItem = electron.MenuItem;
|
||||
|
||||
let trayIcon = null;
|
||||
|
||||
exports.hasTray = function hasTray() {
|
||||
return (trayIcon !== null);
|
||||
}
|
||||
|
||||
exports.create = function (win, config) {
|
||||
// no trays on darwin
|
||||
if (process.platform === 'darwin' || trayIcon) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toggleWin = function () {
|
||||
if (win.isVisible() && !win.isMinimized()) {
|
||||
win.hide();
|
||||
} else {
|
||||
if (win.isMinimized()) win.restore();
|
||||
if (!win.isVisible()) win.show();
|
||||
win.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const contextMenu = electron.Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Show/Hide ' + config.brand,
|
||||
click: toggleWin
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
click: function () {
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
trayIcon = new Tray(config.icon_path);
|
||||
trayIcon.setToolTip(config.brand);
|
||||
trayIcon.setContextMenu(contextMenu);
|
||||
trayIcon.on('click', toggleWin);
|
||||
};
|
||||
197
electron/src/vectormenu.js
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
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 electron = 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'
|
||||
},
|
||||
{
|
||||
role: 'zoomout'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'togglefullscreen'
|
||||
},
|
||||
{
|
||||
role: 'toggledevtools'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{
|
||||
role: 'minimize'
|
||||
},
|
||||
{
|
||||
role: 'close'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'riot.im',
|
||||
click () { electron.shell.openExternal('https://riot.im/') }
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// macOS has specific menu conventions...
|
||||
if (process.platform === 'darwin') {
|
||||
// first macOS menu is the name of the app
|
||||
const name = electron.app.getName()
|
||||
template.unshift({
|
||||
label: 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 = electron.Menu.buildFromTemplate(template)
|
||||
|
||||
30
jenkins.sh
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
export NVM_DIR="/home/jenkins/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||
nvm use 4
|
||||
|
||||
set -x
|
||||
|
||||
npm install
|
||||
|
||||
# we may be using a dev branch of react-sdk, in which case we need to build it
|
||||
(cd node_modules/matrix-react-sdk && npm run build)
|
||||
|
||||
# build our artifacts; dumps them in ./vector
|
||||
npm run build
|
||||
|
||||
# gzip up ./vector
|
||||
rm vector-*.tar.gz || true # rm previous artifacts without failing if it doesn't exist
|
||||
|
||||
# node_modules deps from 'npm install' don't have a .git dir so can't
|
||||
# rev-parse; but they do set the commit in package.json under 'gitHead' which
|
||||
# we're grabbing here.
|
||||
REACT_SHA=$(grep 'gitHead' node_modules/matrix-react-sdk/package.json | cut -d \" -f 4 | head -c 12)
|
||||
JSSDK_SHA=$(grep 'gitHead' node_modules/matrix-js-sdk/package.json | cut -d \" -f 4 | head -c 12)
|
||||
|
||||
VECTOR_SHA=$(git rev-parse --short=12 HEAD) # use the ACTUAL SHA rather than assume develop
|
||||
|
||||
tar -zcvhf vector-$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA.tar.gz vector #g[z]ip, [c]reate archive, [v]erbose, [f]ilename, [h]ard-dereference (do not archive symlinks)
|
||||
147
karma.conf.js
Normal file
@@ -0,0 +1,147 @@
|
||||
// karma.conf.js - the config file for karma, which runs our tests.
|
||||
|
||||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
var webpack_config = require('./webpack.config');
|
||||
|
||||
/*
|
||||
* We use webpack to build our tests. It's a pain to have to wait for webpack
|
||||
* to build everything; however it's the easiest way to load our dependencies
|
||||
* from node_modules.
|
||||
*
|
||||
* If you run karma in multi-run mode (with `npm run test-multi`), it will watch
|
||||
* the tests for changes, and webpack will rebuild using a cache. This is much quicker
|
||||
* than a clean rebuild.
|
||||
*/
|
||||
|
||||
// the name of the test file. By default, a special file which runs all tests.
|
||||
var testFile = process.env.KARMA_TEST_FILE || 'test/all-tests.js';
|
||||
|
||||
process.env.PHANTOMJS_BIN = 'node_modules/.bin/phantomjs';
|
||||
process.env.Q_DEBUG = 1;
|
||||
|
||||
/* the webpack config is based on the real one, to (a) try to simulate the
|
||||
* deployed environment as closely as possible, and (b) to avoid a shedload of
|
||||
* cut-and-paste.
|
||||
*/
|
||||
|
||||
// find out if we're shipping olm, and where it is, if so.
|
||||
const olm_entry = webpack_config.entry['olm'];
|
||||
|
||||
// remove the default entries - karma provides its own (via the 'files' and
|
||||
// 'preprocessors' config below)
|
||||
delete webpack_config['entry'];
|
||||
|
||||
// add ./test as a search path for js
|
||||
webpack_config.module.loaders.unshift({
|
||||
test: /\.js$/, loader: "babel",
|
||||
include: [path.resolve('./src'), path.resolve('./test')],
|
||||
});
|
||||
|
||||
// disable parsing for sinon, because it
|
||||
// tries to do voodoo with 'require' which upsets
|
||||
// webpack (https://github.com/webpack/webpack/issues/304)
|
||||
webpack_config.module.noParse.push(/sinon\/pkg\/sinon\.js$/);
|
||||
|
||||
// ?
|
||||
webpack_config.resolve.alias['sinon'] = 'sinon/pkg/sinon.js';
|
||||
|
||||
webpack_config.resolve.root = [
|
||||
path.resolve('./src'),
|
||||
path.resolve('./test'),
|
||||
];
|
||||
|
||||
webpack_config.devtool = 'inline-source-map';
|
||||
|
||||
module.exports = function (config) {
|
||||
const myconfig = {
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['mocha'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'node_modules/babel-polyfill/browser.js',
|
||||
testFile,
|
||||
|
||||
// make the images available via our httpd. They will be avaliable
|
||||
// below http://localhost:[PORT]/base/. See also `proxies` which
|
||||
// defines alternative URLs for them.
|
||||
//
|
||||
// This isn't required by any of the tests, but it stops karma
|
||||
// logging warnings when it serves a 404 for them.
|
||||
{
|
||||
pattern: 'src/skins/vector/img/*',
|
||||
watched: false, included: false, served: true, nocache: false,
|
||||
},
|
||||
],
|
||||
|
||||
proxies: {
|
||||
// redirect img links to the karma server. See above.
|
||||
"/img/": "/base/src/skins/vector/img/",
|
||||
},
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors:
|
||||
// https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
'{src,test}/**/*.js': ['webpack'],
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['progress', 'junit'],
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR ||
|
||||
// config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file
|
||||
// changes
|
||||
autoWatch: true,
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers:
|
||||
// https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: [
|
||||
'Chrome',
|
||||
//'PhantomJS',
|
||||
],
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
// singleRun: false,
|
||||
|
||||
// Concurrency level
|
||||
// how many browser should be started simultaneous
|
||||
concurrency: Infinity,
|
||||
|
||||
junitReporter: {
|
||||
outputDir: 'karma-reports',
|
||||
},
|
||||
|
||||
webpack: webpack_config,
|
||||
|
||||
webpackMiddleware: {
|
||||
stats: {
|
||||
// don't fill the console up with a mahoosive list of modules
|
||||
chunks: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// include the olm loader if we have it.
|
||||
if (olm_entry) {
|
||||
myconfig.files.unshift(olm_entry);
|
||||
}
|
||||
|
||||
config.set(myconfig);
|
||||
};
|
||||
169
package.json
@@ -1,64 +1,167 @@
|
||||
{
|
||||
"name": "vector-web",
|
||||
"version": "0.4.1",
|
||||
"description": "Vector webapp",
|
||||
"author": "matrix.org",
|
||||
"name": "riot-web",
|
||||
"productName": "Riot",
|
||||
"main": "electron/src/electron-main.js",
|
||||
"version": "0.9.7",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "Vector Creations Ltd.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vector-im/vector-web"
|
||||
"url": "https://github.com/vector-im/riot-web"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"files": [
|
||||
"AUTHORS.rst",
|
||||
"CONTRIBUTING.rst",
|
||||
"deploy",
|
||||
"docs",
|
||||
"karma.conf.js",
|
||||
"lib",
|
||||
"release.sh",
|
||||
"scripts",
|
||||
"src",
|
||||
"test",
|
||||
"webpack.config.js"
|
||||
],
|
||||
"style": "bundle.css",
|
||||
"matrix-react-parent": "matrix-react-sdk",
|
||||
"scripts": {
|
||||
"reskindex": "reskindex -h src/header",
|
||||
"build:res": "node scripts/copy-res.js",
|
||||
"build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js",
|
||||
"build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css --no-watch",
|
||||
"build:compile": "babel --source-maps -d lib src",
|
||||
"build:bundle": "NODE_ENV=production webpack -p lib/vector/index.js vector/bundle.js",
|
||||
"build": "npm run build:css && npm run build:compile && npm run build:bundle",
|
||||
"package": "scripts/package.sh",
|
||||
"start:js": "webpack -w src/vector/index.js vector/bundle.js",
|
||||
"start:js:prod": "NODE_ENV=production webpack -w src/vector/index.js vector/bundle.js",
|
||||
"start:skins:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css",
|
||||
"//cache": "Note the -c 1 below due to https://code.google.com/p/chromium/issues/detail?id=508270",
|
||||
"start": "parallelshell \"npm run start:js\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
|
||||
"start:prod": "parallelshell \"npm run start:js:prod\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
|
||||
"clean": "rimraf lib vector/bundle.css vector/bundle.js vector/bundle.js.map vector/webpack.css*",
|
||||
"prepublish": "npm run build:css && npm run build:compile"
|
||||
"build:bundle": "NODE_ENV=production webpack -p --progress",
|
||||
"build:bundle:dev": "webpack --optimize-occurence-order --progress",
|
||||
"build:electron": "npm run clean && npm run build && build -wml --ia32 --x64",
|
||||
"build": "node scripts/babelcheck.js && npm run build:res && npm run build:bundle",
|
||||
"build:dev": "node scripts/babelcheck.js && npm run build:res && npm run build:bundle:dev",
|
||||
"dist": "scripts/package.sh",
|
||||
"start:res": "node scripts/copy-res.js -w",
|
||||
"start:js": "webpack-dev-server --output-filename=bundles/_dev_/[name].js --output-chunk-file=bundles/_dev_/[name].js -w --progress",
|
||||
"start:js:prod": "NODE_ENV=production webpack-dev-server -w --progress",
|
||||
"start": "node scripts/babelcheck.js && parallelshell \"npm run start:res\" \"npm run start:js\"",
|
||||
"start:prod": "parallelshell \"npm run start:res\" \"npm run start:js:prod\"",
|
||||
"lint": "eslint src/",
|
||||
"lintall": "eslint src/ test/",
|
||||
"clean": "rimraf lib webapp electron/dist",
|
||||
"prepublish": "npm run build:compile",
|
||||
"test": "karma start --single-run=true --autoWatch=false --browsers PhantomJS --colors=false",
|
||||
"test-multi": "karma start"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.5.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"browser-request": "^0.3.3",
|
||||
"classnames": "^2.1.2",
|
||||
"draft-js": "^0.8.1",
|
||||
"extract-text-webpack-plugin": "^0.9.1",
|
||||
"favico.js": "^0.3.10",
|
||||
"filesize": "^3.1.2",
|
||||
"flux": "~2.0.3",
|
||||
"gemini-scrollbar": "matrix-org/gemini-scrollbar#7dc736d",
|
||||
"gemini-scrollbar": "matrix-org/gemini-scrollbar#b302279",
|
||||
"gfm.css": "^1.1.1",
|
||||
"highlight.js": "^9.0.0",
|
||||
"linkifyjs": "^2.0.0-beta.4",
|
||||
"matrix-js-sdk": "^0.5.0",
|
||||
"matrix-react-sdk": "^0.3.1",
|
||||
"linkifyjs": "^2.1.3",
|
||||
"matrix-js-sdk": "0.7.5",
|
||||
"matrix-react-sdk": "0.8.6",
|
||||
"modernizr": "^3.1.0",
|
||||
"q": "^1.4.1",
|
||||
"react": "^0.14.2",
|
||||
"react-dnd": "^2.0.2",
|
||||
"react-dnd-html5-backend": "^2.0.0",
|
||||
"react-dom": "^0.14.2",
|
||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#869a86b",
|
||||
"sanitize-html": "^1.11.1"
|
||||
"react": "^15.4.0",
|
||||
"react-dnd": "^2.1.4",
|
||||
"react-dnd-html5-backend": "^2.1.2",
|
||||
"react-dom": "^15.4.0",
|
||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
||||
"sanitize-html": "^1.11.1",
|
||||
"ua-parser-js": "^0.7.10",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^5.8.23",
|
||||
"babel-core": "^5.8.25",
|
||||
"babel-loader": "^5.3.2",
|
||||
"catw": "^1.0.1",
|
||||
"autoprefixer": "^6.6.0",
|
||||
"babel-cli": "^6.5.2",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-eslint": "^6.1.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-plugin-transform-async-to-generator": "^6.16.0",
|
||||
"babel-plugin-transform-class-properties": "^6.16.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
||||
"babel-plugin-transform-runtime": "^6.15.0",
|
||||
"babel-preset-es2015": "^6.16.0",
|
||||
"babel-preset-es2016": "^6.16.0",
|
||||
"babel-preset-es2017": "^6.16.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"babel-preset-stage-2": "^6.17.0",
|
||||
"chokidar": "^1.6.1",
|
||||
"cpx": "^1.3.2",
|
||||
"css-raw-loader": "^0.1.1",
|
||||
"http-server": "^0.8.4",
|
||||
"electron-builder": "^11.2.4",
|
||||
"electron-builder-squirrel-windows": "^11.2.1",
|
||||
"emojione": "^2.2.3",
|
||||
"eslint": "^3.14.0",
|
||||
"eslint-config-google": "^0.7.1",
|
||||
"eslint-plugin-flowtype": "^2.30.0",
|
||||
"eslint-plugin-react": "^6.9.0",
|
||||
"expect": "^1.16.0",
|
||||
"fs-extra": "^0.30.0",
|
||||
"html-webpack-plugin": "^2.24.0",
|
||||
"json-loader": "^0.5.3",
|
||||
"karma": "^0.13.22",
|
||||
"karma-chrome-launcher": "^0.2.3",
|
||||
"karma-cli": "^0.1.2",
|
||||
"karma-junit-reporter": "^0.4.1",
|
||||
"karma-mocha": "^0.2.2",
|
||||
"karma-phantomjs-launcher": "^1.0.0",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mocha": "^2.4.5",
|
||||
"parallelshell": "^1.2.0",
|
||||
"phantomjs-prebuilt": "^2.1.7",
|
||||
"postcss-extend": "^1.0.5",
|
||||
"postcss-import": "^9.0.0",
|
||||
"postcss-loader": "^1.2.2",
|
||||
"postcss-mixins": "^5.4.1",
|
||||
"postcss-nested": "^1.0.0",
|
||||
"postcss-scss": "^0.4.0",
|
||||
"postcss-simple-vars": "^3.0.0",
|
||||
"postcss-strip-inline-comments": "^0.1.5",
|
||||
"react-addons-perf": "^15.4.0",
|
||||
"react-addons-test-utils": "^15.4.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"source-map-loader": "^0.1.5",
|
||||
"webpack": "^1.12.13"
|
||||
"webpack": "^1.12.14",
|
||||
"webpack-dev-server": "^1.16.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"olm": "https://matrix.org/packages/npm/olm/olm-2.2.1.tgz"
|
||||
},
|
||||
"build": {
|
||||
"appId": "im.riot.app",
|
||||
"category": "Network",
|
||||
"electronVersion": "1.4.14",
|
||||
"//asar=false": "https://github.com/electron-userland/electron-builder/issues/675",
|
||||
"asar": false,
|
||||
"dereference": true,
|
||||
"//files": "We bundle everything, so we only need to include webapp/",
|
||||
"files": [
|
||||
"electron/src/**",
|
||||
"electron/img/**",
|
||||
"webapp/**",
|
||||
"package.json"
|
||||
],
|
||||
"linux": {
|
||||
"target": "deb",
|
||||
"maintainer": "support@riot.im",
|
||||
"desktop": {
|
||||
"StartupWMClass": "riot-web"
|
||||
}
|
||||
},
|
||||
"win": {
|
||||
"target": "squirrel"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"buildResources": "electron/build",
|
||||
"output": "electron/dist"
|
||||
}
|
||||
}
|
||||
|
||||
13
postcss.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require("postcss-import")(),
|
||||
require("autoprefixer")(),
|
||||
require("postcss-simple-vars")(),
|
||||
require("postcss-extend")(),
|
||||
require("postcss-nested")(),
|
||||
require("postcss-mixins")(),
|
||||
require("postcss-strip-inline-comments")(),
|
||||
],
|
||||
"parser": "postcss-scss",
|
||||
"local-plugins": true,
|
||||
};
|
||||
1
release_config.yaml
Normal file
@@ -0,0 +1 @@
|
||||
signing_id: packages@riot.im
|
||||
BIN
res/vector-icons/android-chrome-144x144.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
res/vector-icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
res/vector-icons/android-chrome-36x36.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
res/vector-icons/android-chrome-48x48.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
res/vector-icons/android-chrome-72x72.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
res/vector-icons/android-chrome-96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
res/vector-icons/apple-touch-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
res/vector-icons/apple-touch-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
res/vector-icons/apple-touch-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
res/vector-icons/apple-touch-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
res/vector-icons/apple-touch-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/vector-icons/apple-touch-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
res/vector-icons/apple-touch-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
res/vector-icons/apple-touch-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
res/vector-icons/apple-touch-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
res/vector-icons/apple-touch-icon-precomposed.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/vector-icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/vector-icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
res/vector-icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
res/vector-icons/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
res/vector-icons/favicon.ico
Normal file
|
After Width: | Height: | Size: 102 KiB |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Vector",
|
||||
"name": "Riot",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/icons\/android-chrome-36x36.png",
|
||||
BIN
res/vector-icons/mstile-144x144.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
res/vector-icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
res/vector-icons/mstile-310x150.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
res/vector-icons/mstile-310x310.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
res/vector-icons/mstile-70x70.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
26
scripts/babelcheck.js
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
// Makes sure the babel executable in the path is babel 6 (or greater), not
|
||||
// babel 5, which it is if you upgrade from an older version of react-sdk and
|
||||
// run 'npm install' since the package has changed to babel-cli, so 'babel'
|
||||
// remains installed and the executable in node_modules/.bin remains as babel
|
||||
// 5.
|
||||
|
||||
// This script is duplicated from matrix-react-sdk because it can't reliably
|
||||
// be pulled in from react-sdk while npm install is failing, as it will do
|
||||
// if the environment is in the erroneous state this script checks for.
|
||||
|
||||
exec("babel -V", function (error, stdout, stderr) {
|
||||
if ((error && error.code) || parseInt(stdout.substr(0,1), 10) < 6) {
|
||||
console.log("\033[31m\033[1m"+
|
||||
'*****************************************\n'+
|
||||
'* vector-web has moved to babel 6 *\n'+
|
||||
'* Please "rm -rf node_modules && npm i" *\n'+
|
||||
'* then restore links as appropriate *\n'+
|
||||
'*****************************************\n'+
|
||||
"\033[91m");
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
81
scripts/copy-res.js
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// copies the resources into the webapp directory.
|
||||
//
|
||||
|
||||
// cpx includes globbed parts of the filename in the destination, but excludes
|
||||
// common parents. Hence, "res/{a,b}/**": the output will be "dest/a/..." and
|
||||
// "dest/b/...".
|
||||
const COPY_LIST = [
|
||||
["res/{media,vector-icons}/**", "webapp"],
|
||||
["src/skins/vector/{fonts,img}/**", "webapp"],
|
||||
["node_modules/emojione/assets/svg/*", "webapp/emojione/svg/"],
|
||||
["./config.json", "webapp", {directwatch: 1}],
|
||||
];
|
||||
|
||||
const parseArgs = require('minimist');
|
||||
const Cpx = require('cpx');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
const argv = parseArgs(
|
||||
process.argv.slice(2), {}
|
||||
);
|
||||
|
||||
var watch = argv.w;
|
||||
var verbose = argv.v;
|
||||
|
||||
function errCheck(err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function next(i, err) {
|
||||
errCheck(err);
|
||||
|
||||
if (i >= COPY_LIST.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ent = COPY_LIST[i];
|
||||
const source = ent[0];
|
||||
const dest = ent[1];
|
||||
const opts = ent[2] || {};
|
||||
|
||||
const cpx = new Cpx.Cpx(source, dest);
|
||||
|
||||
if (verbose) {
|
||||
cpx.on("copy", (event) => {
|
||||
console.log(`Copied: ${event.srcPath} --> ${event.dstPath}`);
|
||||
});
|
||||
cpx.on("remove", (event) => {
|
||||
console.log(`Removed: ${event.path}`);
|
||||
});
|
||||
}
|
||||
|
||||
const cb = (err) => {next(i+1, err)};
|
||||
|
||||
if (watch) {
|
||||
if (opts.directwatch) {
|
||||
// cpx -w creates a watcher for the parent of any files specified,
|
||||
// which in the case of config.json is '.', which inevitably takes
|
||||
// ages to crawl. So we create our own watcher on the files
|
||||
// instead.
|
||||
const copy = () => {cpx.copy(errCheck)};
|
||||
chokidar.watch(source)
|
||||
.on('add', copy)
|
||||
.on('change', copy)
|
||||
.on('ready', cb)
|
||||
.on('error', errCheck);
|
||||
} else {
|
||||
cpx.on('watch-ready', cb);
|
||||
cpx.on("watch-error", cb);
|
||||
cpx.watch();
|
||||
}
|
||||
} else {
|
||||
cpx.copy(cb);
|
||||
}
|
||||
}
|
||||
|
||||
next(0);
|
||||
183
scripts/deploy.py
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# download and unpack a riot-web tarball.
|
||||
#
|
||||
# Allows `bundles` to be extracted to a common directory, and a link to
|
||||
# config.json to be added.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
|
||||
try:
|
||||
# python3
|
||||
from urllib.request import urlretrieve
|
||||
except ImportError:
|
||||
# python2
|
||||
from urllib import urlretrieve
|
||||
|
||||
class DeployException(Exception):
|
||||
pass
|
||||
|
||||
def create_relative_symlink(linkname, target):
|
||||
relpath = os.path.relpath(target, os.path.dirname(linkname))
|
||||
print ("Symlink %s -> %s" % (linkname, relpath))
|
||||
os.symlink(relpath, linkname)
|
||||
|
||||
|
||||
def move_bundles(source, dest):
|
||||
"""Move the contents of the 'bundles' directory to a common dir
|
||||
|
||||
We check that we will not be overwriting anything before we proceed.
|
||||
|
||||
Args:
|
||||
source (str): path to 'bundles' within the extracted tarball
|
||||
dest (str): target common directory
|
||||
"""
|
||||
|
||||
if not os.path.isdir(dest):
|
||||
os.mkdir(dest)
|
||||
|
||||
# build a map from source to destination, checking for non-existence as we go.
|
||||
renames = {}
|
||||
for f in os.listdir(source):
|
||||
dst = os.path.join(dest, f)
|
||||
if os.path.exists(dst):
|
||||
raise DeployException(
|
||||
"Not deploying. The bundle includes '%s' which we have previously deployed."
|
||||
% f
|
||||
)
|
||||
renames[os.path.join(source, f)] = dst
|
||||
|
||||
for (src, dst) in renames.iteritems():
|
||||
print ("Move %s -> %s" % (src, dst))
|
||||
os.rename(src, dst)
|
||||
|
||||
class Deployer:
|
||||
def __init__(self):
|
||||
self.packages_path = "."
|
||||
self.bundles_path = None
|
||||
self.should_clean = False
|
||||
self.config_location = None
|
||||
self.verify_signature = True
|
||||
|
||||
def deploy(self, tarball, extract_path):
|
||||
"""Download a tarball if necessary, and unpack it
|
||||
|
||||
Returns:
|
||||
(str) the path to the unpacked deployment
|
||||
"""
|
||||
print("Deploying %s to %s" % (tarball, extract_path))
|
||||
|
||||
name_str = os.path.basename(tarball).replace(".tar.gz", "")
|
||||
extracted_dir = os.path.join(extract_path, name_str)
|
||||
if os.path.exists(extracted_dir):
|
||||
raise DeployException('Cannot unpack %s: %s already exists' % (
|
||||
tarball, extracted_dir))
|
||||
|
||||
downloaded = False
|
||||
if tarball.startswith("http://") or tarball.startswith("https://"):
|
||||
tarball = self.download_and_verify(tarball)
|
||||
print("Downloaded file: %s" % tarball)
|
||||
downloaded = True
|
||||
|
||||
try:
|
||||
with tarfile.open(tarball) as tar:
|
||||
tar.extractall(extract_path)
|
||||
finally:
|
||||
if self.should_clean and downloaded:
|
||||
os.remove(tarball)
|
||||
|
||||
print ("Extracted into: %s" % extracted_dir)
|
||||
|
||||
if self.config_location:
|
||||
create_relative_symlink(
|
||||
target=self.config_location,
|
||||
linkname=os.path.join(extracted_dir, 'config.json')
|
||||
)
|
||||
|
||||
if self.bundles_path:
|
||||
extracted_bundles = os.path.join(extracted_dir, 'bundles')
|
||||
move_bundles(source=extracted_bundles, dest=self.bundles_path)
|
||||
|
||||
# replace the (hopefully now empty) extracted_bundles dir with a
|
||||
# symlink to the common dir.
|
||||
os.rmdir(extracted_bundles)
|
||||
create_relative_symlink(
|
||||
target=self.bundles_path,
|
||||
linkname=extracted_bundles,
|
||||
)
|
||||
return extracted_dir
|
||||
|
||||
def download_and_verify(self, url):
|
||||
tarball = self.download_file(url)
|
||||
|
||||
if self.verify_signature:
|
||||
sigfile = self.download_file(url + ".asc")
|
||||
subprocess.check_call(["gpg", "--verify", sigfile, tarball])
|
||||
|
||||
return tarball
|
||||
|
||||
def download_file(self, url):
|
||||
if not os.path.isdir(self.packages_path):
|
||||
os.mkdir(self.packages_path)
|
||||
local_filename = os.path.join(self.packages_path,
|
||||
url.split('/')[-1])
|
||||
sys.stdout.write("Downloading %s -> %s..." % (url, local_filename))
|
||||
sys.stdout.flush()
|
||||
urlretrieve(url, local_filename)
|
||||
print ("Done")
|
||||
return local_filename
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser("Deploy a Riot build on a web server.")
|
||||
parser.add_argument(
|
||||
"-p", "--packages-dir", default="./packages", help=(
|
||||
"The directory to download the tarball into. (Default: '%(default)s')"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e", "--extract-path", default="./deploys", help=(
|
||||
"The location to extract .tar.gz files to. (Default: '%(default)s')"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b", "--bundles-dir", nargs='?', default="./bundles", help=(
|
||||
"A directory to move the contents of the 'bundles' directory to. A \
|
||||
symlink to the bundles directory will also be written inside the \
|
||||
extracted tarball. Example: './bundles'. \
|
||||
(Default: '%(default)s')"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--clean", action="store_true", default=False, help=(
|
||||
"Remove .tar.gz files after they have been downloaded and extracted. \
|
||||
(Default: %(default)s)"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config", nargs='?', default='./config.json', help=(
|
||||
"Write a symlink at config.json in the extracted tarball to this \
|
||||
location. (Default: '%(default)s')"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"tarball", help=(
|
||||
"filename of tarball, or URL to download."
|
||||
),
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
deployer = Deployer()
|
||||
deployer.packages_path = args.packages_dir
|
||||
deployer.bundles_path = args.bundles_dir
|
||||
deployer.should_clean = args.clean
|
||||
deployer.config_location = args.config
|
||||
|
||||
deployer.deploy(args.tarball, args.extract_path)
|
||||
130
scripts/electron-package.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 -v <version> -c <config file> [-n]"
|
||||
echo
|
||||
echo "version: commit-ish to check out and build"
|
||||
echo "config file: a path to a json config file to"
|
||||
echo "ship with the build. In addition, update_base_url:"
|
||||
echo "from this file is used to set up auto-update."
|
||||
echo "-n: build with no config file."
|
||||
echo
|
||||
echo "Values may also be passed as environment variables"
|
||||
}
|
||||
|
||||
conffile=
|
||||
version=
|
||||
skipcfg=0
|
||||
while getopts "c:v:n" opt; do
|
||||
case $opt in
|
||||
c)
|
||||
conffile=$OPTARG
|
||||
;;
|
||||
v)
|
||||
version=$OPTARG
|
||||
;;
|
||||
n)
|
||||
skipcfg=1
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
usage
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo "No version supplied"
|
||||
usage
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -z "$conffile" ] && [ "$skipcfg" = 0 ]; then
|
||||
echo "No config file given. Use -c to supply a config file or"
|
||||
echo "-n to build with no config file (and no auto update)."
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -n "$conffile" ]; then
|
||||
update_base_url=`jq -r .update_base_url $conffile`
|
||||
|
||||
if [ -z "$update_base_url" ]; then
|
||||
echo "No update URL supplied. Use update_base_url: null if you really"
|
||||
echo "want a build with no auto-update."
|
||||
usage
|
||||
exit
|
||||
fi
|
||||
# Make sure the base URL ends in a slash if it doesn't already
|
||||
update_base_url=`echo $update_base_url | sed -e 's#\([^\/]\)$#\1\/#'`
|
||||
fi
|
||||
|
||||
if [ ! -f package.json ]; then
|
||||
echo "No package.json found. This script must be run from"
|
||||
echo "the vector-web directory."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "Building $version using Update base URL $update_base_url"
|
||||
|
||||
projdir=`pwd`
|
||||
builddir=`mktemp -d 2>/dev/null || mktemp -d -t 'buildtmp'`
|
||||
pushd "$builddir"
|
||||
|
||||
git clone "$projdir" .
|
||||
git checkout "$version"
|
||||
|
||||
# Figure out what version we're building
|
||||
vername=`jq -r .version package.json`
|
||||
|
||||
if [ -n "$conffile" ]; then
|
||||
popd
|
||||
cp "$conffile" "$builddir/"
|
||||
pushd "$builddir"
|
||||
fi
|
||||
|
||||
npm install
|
||||
npm run build:electron
|
||||
|
||||
popd
|
||||
|
||||
distdir="$builddir/electron/dist"
|
||||
pubdir="$projdir/electron/pub"
|
||||
rm -r "$pubdir" || true
|
||||
mkdir -p "$pubdir"
|
||||
|
||||
# Install packages: what the user downloads the first time,
|
||||
# (DMGs for mac, exe installer for windows)
|
||||
mkdir -p "$pubdir/install/macos"
|
||||
cp $distdir/mac/*.dmg "$pubdir/install/macos/"
|
||||
|
||||
mkdir -p "$pubdir/install/win32/ia32/"
|
||||
cp $distdir/win-ia32/*.exe "$pubdir/install/win32/ia32/"
|
||||
|
||||
mkdir -p "$pubdir/install/win32/x64/"
|
||||
cp $distdir/win/*.exe "$pubdir/install/win32/x64/"
|
||||
|
||||
# Packages for auto-update
|
||||
mkdir -p "$pubdir/update/macos"
|
||||
cp $distdir/mac/*.zip "$pubdir/update/macos/"
|
||||
echo "$vername" > "$pubdir/update/macos/latest"
|
||||
|
||||
mkdir -p "$pubdir/update/win32/ia32/"
|
||||
cp $distdir/win-ia32/*.nupkg "$pubdir/update/win32/ia32/"
|
||||
cp $distdir/win-ia32/RELEASES "$pubdir/update/win32/ia32/"
|
||||
|
||||
mkdir -p "$pubdir/update/win32/x64/"
|
||||
cp $distdir/win/*.nupkg "$pubdir/update/win32/x64/"
|
||||
cp $distdir/win/RELEASES "$pubdir/update/win32/x64/"
|
||||
|
||||
# Move the debs to the main project dir's dist folder
|
||||
rm -r "$projdir/electron/dist" || true
|
||||
mkdir -p "$projdir/electron/dist"
|
||||
cp $distdir/*.deb "$projdir/electron/dist/"
|
||||
|
||||
rm -rf "$builddir"
|
||||
|
||||
echo "Riot Desktop is ready to go in $pubdir: this directory can be hosted on your web server."
|
||||
echo "deb archives are in electron/dist/ - these should be added into your debian repository"
|
||||
124
scripts/issues-burndown.pl
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
use Net::GitHub;
|
||||
use DateTime;
|
||||
use DateTime::Format::ISO8601;
|
||||
|
||||
my $gh = Net::GitHub->new(
|
||||
login => 'ara4n', pass => 'secret'
|
||||
);
|
||||
|
||||
$gh->set_default_user_repo('vector-im', 'vector-web');
|
||||
|
||||
my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
|
||||
while ($gh->issue->has_next_page) {
|
||||
push @issues, $gh->issue->next_page;
|
||||
}
|
||||
|
||||
# we want:
|
||||
# day by day:
|
||||
# split by { open, closed }
|
||||
# split by { bug, feature, neither }
|
||||
# each split by { p1, p2, p3, p4, p5, unprioritised } <- priority
|
||||
# each split by { minor, major, critical, cosmetic, network, no-severity } <- severity
|
||||
# then split (with overlap between the groups) as { total, tag1, tag2, ... }?
|
||||
|
||||
# ...and then all over again split by milestone.
|
||||
|
||||
my $days = {};
|
||||
my $schema = {};
|
||||
my $now = DateTime->now();
|
||||
|
||||
foreach my $issue (@issues) {
|
||||
next if ($issue->{pull_request});
|
||||
|
||||
# use Data::Dumper;
|
||||
# print STDERR Dumper($issue);
|
||||
|
||||
my @label_list = map { $_->{name} } @{$issue->{labels}};
|
||||
my $labels = {};
|
||||
$labels->{$_} = 1 foreach (@label_list);
|
||||
$labels->{bug}++ if ($labels->{cosmetic} && !$labels->{bug} && !$labels->{feature});
|
||||
|
||||
my $extract_labels = sub {
|
||||
my $label = undef;
|
||||
foreach (@_) {
|
||||
$label ||= $_ if (delete $labels->{$_});
|
||||
}
|
||||
return $label;
|
||||
};
|
||||
|
||||
my $state = $issue->{state};
|
||||
my $type = &$extract_labels(qw(bug feature)) || "neither";
|
||||
my $priority = &$extract_labels(qw(p1 p2 p3 p4 p5)) || "unprioritised";
|
||||
my $severity = &$extract_labels(qw(minor major critical cosmetic network)) || "no-severity";
|
||||
|
||||
my $start = DateTime::Format::ISO8601->parse_datetime($issue->{created_at});
|
||||
|
||||
do {
|
||||
my $ymd = $start->ymd();
|
||||
|
||||
$days->{ $ymd }->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
|
||||
$schema->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
|
||||
foreach (keys %$labels) {
|
||||
$days->{ $ymd }->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
|
||||
$schema->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
|
||||
}
|
||||
|
||||
$start = $start->add(days => 1);
|
||||
} while (DateTime->compare($start, $now) < 0);
|
||||
|
||||
if ($state eq 'closed') {
|
||||
my $end = DateTime::Format::ISO8601->parse_datetime($issue->{closed_at});
|
||||
do {
|
||||
my $ymd = $end->ymd();
|
||||
|
||||
$days->{ $ymd }->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
|
||||
$schema->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
|
||||
foreach (keys %$labels) {
|
||||
$days->{ $ymd }->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
|
||||
$schema->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
|
||||
}
|
||||
|
||||
$end = $end->add(days => 1);
|
||||
} while (DateTime->compare($end, $now) < 0);
|
||||
}
|
||||
}
|
||||
|
||||
print "day,";
|
||||
foreach my $state (sort keys %{$schema}) {
|
||||
foreach my $type (grep { /^(bug|feature)$/ } sort keys %{$schema->{$state}}) {
|
||||
foreach my $priority (grep { /^(p1|p2)$/ } sort keys %{$schema->{$state}->{$type}}) {
|
||||
foreach my $severity (sort keys %{$schema->{$state}->{$type}->{$priority}}) {
|
||||
# foreach my $tag (sort keys %{$schema->{$state}->{$type}->{$priority}->{$severity}}) {
|
||||
# print "\"$type\n$priority\n$severity\n$tag\",";
|
||||
# }
|
||||
print "\"$state\n$type\n$priority\n$severity\",";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
print "\n";
|
||||
|
||||
foreach my $day (sort keys %$days) {
|
||||
print "$day,";
|
||||
foreach my $state (sort keys %{$schema}) {
|
||||
foreach my $type (grep { /^(bug|feature)$/ } sort keys %{$schema->{$state}}) {
|
||||
foreach my $priority (grep { /^(p1|p2)$/ } sort keys %{$schema->{$state}->{$type}}) {
|
||||
foreach my $severity (sort keys %{$schema->{$state}->{$type}->{$priority}}) {
|
||||
# foreach my $tag (sort keys %{$schema->{$state}->{$type}->{$priority}->{$severity}}) {
|
||||
# print $days->{$day}->{$state}->{$type}->{$priority}->{$severity}->{$tag} || 0;
|
||||
# print ",";
|
||||
# }
|
||||
print $days->{$day}->{$state}->{$type}->{$priority}->{$severity}->{total} || 0;
|
||||
print ",";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
104
scripts/issues-no-state.pl
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
use Net::GitHub;
|
||||
use DateTime;
|
||||
use DateTime::Format::ISO8601;
|
||||
|
||||
my $gh = Net::GitHub->new(
|
||||
login => 'ara4n', pass => 'secret'
|
||||
);
|
||||
|
||||
$gh->set_default_user_repo('vector-im', 'vector-web');
|
||||
|
||||
my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
|
||||
while ($gh->issue->has_next_page) {
|
||||
push @issues, $gh->issue->next_page;
|
||||
}
|
||||
|
||||
# we want:
|
||||
# day by day:
|
||||
# split by { open, closed }
|
||||
# split by { bug, feature, neither }
|
||||
# each split by { p1, p2, p3, p4, p5, unprioritised } <- priority
|
||||
# each split by { minor, major, critical, cosmetic, network, no-severity } <- severity
|
||||
# then split (with overlap between the groups) as { total, tag1, tag2, ... }?
|
||||
|
||||
# ...and then all over again split by milestone.
|
||||
|
||||
my $days = {};
|
||||
my $schema = {};
|
||||
my $now = DateTime->now();
|
||||
|
||||
foreach my $issue (@issues) {
|
||||
next if ($issue->{pull_request});
|
||||
|
||||
use Data::Dumper;
|
||||
print STDERR Dumper($issue);
|
||||
|
||||
my @label_list = map { $_->{name} } @{$issue->{labels}};
|
||||
my $labels = {};
|
||||
$labels->{$_} = 1 foreach (@label_list);
|
||||
$labels->{bug}++ if ($labels->{cosmetic} && !$labels->{bug} && !$labels->{feature});
|
||||
|
||||
my $extract_labels = sub {
|
||||
my $label = undef;
|
||||
foreach (@_) {
|
||||
$label ||= $_ if (delete $labels->{$_});
|
||||
}
|
||||
return $label;
|
||||
};
|
||||
|
||||
my $type = &$extract_labels(qw(bug feature)) || "neither";
|
||||
my $priority = &$extract_labels(qw(p1 p2 p3 p4 p5)) || "unprioritised";
|
||||
my $severity = &$extract_labels(qw(minor major critical cosmetic network)) || "no-severity";
|
||||
|
||||
my $start = DateTime::Format::ISO8601->parse_datetime($issue->{created_at});
|
||||
my $end = $issue->{closed_at} ? DateTime::Format::ISO8601->parse_datetime($issue->{closed_at}) : $now;
|
||||
|
||||
do {
|
||||
my $ymd = $start->ymd();
|
||||
|
||||
$days->{ $ymd }->{ $type }->{ $priority }->{ $severity }->{ total }++;
|
||||
$schema->{ $type }->{ $priority }->{ $severity }->{ total }++;
|
||||
foreach (keys %$labels) {
|
||||
$days->{ $ymd }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
|
||||
$schema->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
|
||||
}
|
||||
|
||||
$start = $start->add(days => 1);
|
||||
} while (DateTime->compare($start, $end) < 0);
|
||||
}
|
||||
|
||||
print "day,";
|
||||
foreach my $type (sort keys %{$schema}) {
|
||||
foreach my $priority (sort keys %{$schema->{$type}}) {
|
||||
foreach my $severity (sort keys %{$schema->{$type}->{$priority}}) {
|
||||
# foreach my $tag (sort keys %{$schema->{$type}->{$priority}->{$severity}}) {
|
||||
# print "\"$type\n$priority\n$severity\n$tag\",";
|
||||
# }
|
||||
print "\"$type\n$priority\n$severity\",";
|
||||
}
|
||||
}
|
||||
}
|
||||
print "\n";
|
||||
|
||||
foreach my $day (sort keys %$days) {
|
||||
print "$day,";
|
||||
foreach my $type (sort keys %{$schema}) {
|
||||
foreach my $priority (sort keys %{$schema->{$type}}) {
|
||||
foreach my $severity (sort keys %{$schema->{$type}->{$priority}}) {
|
||||
# foreach my $tag (sort keys %{$schema->{$type}->{$priority}->{$severity}}) {
|
||||
# print $days->{$day}->{$type}->{$priority}->{$severity}->{$tag} || 0;
|
||||
# print ",";
|
||||
# }
|
||||
print $days->{$day}->{$type}->{$priority}->{$severity}->{total} || 0;
|
||||
print ",";
|
||||
}
|
||||
}
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
42
scripts/jenkins.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
export NVM_DIR="/home/jenkins/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||
nvm use 6
|
||||
|
||||
set -x
|
||||
|
||||
npm install
|
||||
|
||||
# apparently npm 3.10.3 on node 6.4.0 doesn't upgrade #develop target with npm install unless explicitly asked.
|
||||
npm install matrix-react-sdk matrix-js-sdk
|
||||
|
||||
# install olm. A naive 'npm i ./olm/olm-*.tgz' fails because it uses the url
|
||||
# from our package.json (or even matrix-js-sdk's) in preference.
|
||||
tar -C olm -xz < olm/olm-*.tgz
|
||||
rm -r node_modules/olm
|
||||
cp -r olm/package node_modules/olm
|
||||
|
||||
# we may be using dev branches of js-sdk and react-sdk, in which case we need to build them
|
||||
(cd node_modules/matrix-js-sdk && npm install)
|
||||
(cd node_modules/matrix-react-sdk && npm install)
|
||||
|
||||
# run the mocha tests
|
||||
npm run test
|
||||
|
||||
# run eslint
|
||||
npm run lintall -- -f checkstyle -o eslint.xml || true
|
||||
|
||||
rm dist/vector-*.tar.gz || true # rm previous artifacts without failing if it doesn't exist
|
||||
|
||||
# node_modules deps from 'npm install' don't have a .git dir so can't
|
||||
# rev-parse; but they do set the commit in package.json under 'gitHead' which
|
||||
# we're grabbing here.
|
||||
REACT_SHA=$(grep 'gitHead' node_modules/matrix-react-sdk/package.json | cut -d \" -f 4 | head -c 12)
|
||||
JSSDK_SHA=$(grep 'gitHead' node_modules/matrix-js-sdk/package.json | cut -d \" -f 4 | head -c 12)
|
||||
|
||||
VECTOR_SHA=$(git rev-parse --short=12 HEAD) # use the ACTUAL SHA rather than assume develop
|
||||
|
||||
DIST_VERSION=$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA scripts/package.sh -d
|
||||
93
scripts/make-icons.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $# != 1 ]
|
||||
then
|
||||
echo "Usage: $0 <svg file>"
|
||||
exit
|
||||
fi
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
tmpdir=`mktemp -d 2>/dev/null || mktemp -d -t 'icontmp'`
|
||||
|
||||
for i in 1024 512 310 256 192 180 152 150 144 128 120 114 96 76 72 70 64 60 57 48 36 32 24 16
|
||||
do
|
||||
#convert -background none -density 1000 -resize $i -extent $i -gravity center "$1" "$tmpdir/$i.png"
|
||||
|
||||
# Above is the imagemagick command to render an svg to png. Unfortunately, its support for SVGs
|
||||
# with CSS isn't very good (with rsvg and even moreso the built in renderer) so we use cairosvg.
|
||||
# This can be installed with:
|
||||
# pip install cairosvg==1.0.22 # Version 2 doesn't support python 2
|
||||
# pip install tinycss
|
||||
# pip install cssselect # These are necessary for CSS support
|
||||
# You'll also need xmlstarlet from your favourite package manager
|
||||
#
|
||||
# Cairosvg doesn't suport rendering at a specific size (https://github.com/Kozea/CairoSVG/issues/83#issuecomment-215720176)
|
||||
# so we have to 'resize the svg' first (add width and height attributes to the svg element) to make it render at the
|
||||
# size we need.
|
||||
# XXX: This will break if the svg already has width and height attributes
|
||||
cp "$1" "$tmpdir/tmp.svg"
|
||||
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n width -v $i "$tmpdir/tmp.svg" > "$tmpdir/tmp2.svg"
|
||||
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n height -v $i "$tmpdir/tmp2.svg" > "$tmpdir/tmp3.svg"
|
||||
cairosvg -f png -o "$tmpdir/$i.png" "$tmpdir/tmp3.svg"
|
||||
rm "$tmpdir/tmp.svg" "$tmpdir/tmp2.svg" "$tmpdir/tmp3.svg"
|
||||
done
|
||||
|
||||
# one more for the non-square mstile
|
||||
cp "$1" "$tmpdir/tmp.svg"
|
||||
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n width -v 310 "$tmpdir/tmp.svg" > "$tmpdir/tmp2.svg"
|
||||
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n height -v 150 "$tmpdir/tmp2.svg" > "$tmpdir/tmp3.svg"
|
||||
cairosvg -f png -o "$tmpdir/310x150.png" "$tmpdir/tmp3.svg"
|
||||
rm "$tmpdir/tmp.svg" "$tmpdir/tmp2.svg" "$tmpdir/tmp3.svg"
|
||||
|
||||
mkdir "$tmpdir/Riot.iconset"
|
||||
cp "$tmpdir/16.png" "$tmpdir/Riot.iconset/icon_16x16.png"
|
||||
cp "$tmpdir/32.png" "$tmpdir/Riot.iconset/icon_16x16@2x.png"
|
||||
cp "$tmpdir/32.png" "$tmpdir/Riot.iconset/icon_32x32.png"
|
||||
cp "$tmpdir/64.png" "$tmpdir/Riot.iconset/icon_32x32@2x.png"
|
||||
cp "$tmpdir/128.png" "$tmpdir/Riot.iconset/icon_128x128.png"
|
||||
cp "$tmpdir/256.png" "$tmpdir/Riot.iconset/icon_128x128@2x.png"
|
||||
cp "$tmpdir/256.png" "$tmpdir/Riot.iconset/icon_256x256.png"
|
||||
cp "$tmpdir/512.png" "$tmpdir/Riot.iconset/icon_256x256@2x.png"
|
||||
cp "$tmpdir/512.png" "$tmpdir/Riot.iconset/icon_512x512.png"
|
||||
cp "$tmpdir/1024.png" "$tmpdir/Riot.iconset/icon_512x512@2x.png"
|
||||
iconutil -c icns -o electron/build/icon.icns "$tmpdir/Riot.iconset"
|
||||
|
||||
cp "$tmpdir/36.png" "res/vector-icons/android-chrome-36x36.png"
|
||||
cp "$tmpdir/48.png" "res/vector-icons/android-chrome-48x48.png"
|
||||
cp "$tmpdir/72.png" "res/vector-icons/android-chrome-72x72.png"
|
||||
cp "$tmpdir/96.png" "res/vector-icons/android-chrome-96x96.png"
|
||||
cp "$tmpdir/144.png" "res/vector-icons/android-chrome-144x144.png"
|
||||
cp "$tmpdir/192.png" "res/vector-icons/android-chrome-192x192.png"
|
||||
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon.png"
|
||||
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon-precomposed.png"
|
||||
cp "$tmpdir/57.png" "res/vector-icons/apple-touch-icon-57x57.png"
|
||||
cp "$tmpdir/60.png" "res/vector-icons/apple-touch-icon-60x60.png"
|
||||
cp "$tmpdir/72.png" "res/vector-icons/apple-touch-icon-72x72.png"
|
||||
cp "$tmpdir/76.png" "res/vector-icons/apple-touch-icon-76x76.png"
|
||||
cp "$tmpdir/114.png" "res/vector-icons/apple-touch-icon-114x114.png"
|
||||
cp "$tmpdir/120.png" "res/vector-icons/apple-touch-icon-120x120.png"
|
||||
cp "$tmpdir/144.png" "res/vector-icons/apple-touch-icon-144x144.png"
|
||||
cp "$tmpdir/152.png" "res/vector-icons/apple-touch-icon-152x152.png"
|
||||
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon-180x180.png"
|
||||
cp "$tmpdir/16.png" "res/vector-icons/favicon-16x16.png"
|
||||
cp "$tmpdir/32.png" "res/vector-icons/favicon-32x32.png"
|
||||
cp "$tmpdir/96.png" "res/vector-icons/favicon-96x96.png"
|
||||
cp "$tmpdir/70.png" "res/vector-icons/mstile-70x70.png"
|
||||
cp "$tmpdir/144.png" "res/vector-icons/mstile-144x144.png"
|
||||
cp "$tmpdir/150.png" "res/vector-icons/mstile-150x150.png"
|
||||
cp "$tmpdir/310.png" "res/vector-icons/mstile-310x310.png"
|
||||
cp "$tmpdir/310x150.png" "res/vector-icons/mstile-310x150.png"
|
||||
|
||||
convert "$tmpdir/16.png" "$tmpdir/32.png" "$tmpdir/64.png" "$tmpdir/128.png" "$tmpdir/256.png" "res/vector-icons/favicon.ico"
|
||||
|
||||
cp "res/vector-icons/favicon.ico" "electron/build/icon.ico"
|
||||
|
||||
# https://github.com/electron-userland/electron-builder/blob/3f97b86993d4ea5172e562b182230a194de0f621/src/targets/LinuxTargetHelper.ts#L127
|
||||
for i in 24 96 16 48 64 128 256 512
|
||||
do
|
||||
cp "$tmpdir/$i.png" "electron/build/icons/${i}x${i}.png"
|
||||
done
|
||||
|
||||
rm -r "$tmpdir"
|
||||
@@ -2,14 +2,29 @@
|
||||
|
||||
set -e
|
||||
|
||||
version=`git describe --dirty --tags || echo unknown`
|
||||
dev=""
|
||||
if [ "$1" = '-d' ]; then
|
||||
dev=":dev"
|
||||
fi
|
||||
|
||||
npm run build
|
||||
mkdir -p packages
|
||||
cp -r vector vector-$version
|
||||
if [ -n "$DIST_VERSION" ]; then
|
||||
version=$DIST_VERSION
|
||||
else
|
||||
version=`git describe --dirty --tags || echo unknown`
|
||||
fi
|
||||
|
||||
npm run clean
|
||||
npm run build$dev
|
||||
|
||||
# include the sample config in the tarball. Arguably this should be done by
|
||||
# `npm run build`, but it's just too painful.
|
||||
cp config.sample.json webapp/
|
||||
|
||||
mkdir -p dist
|
||||
cp -r webapp vector-$version
|
||||
echo $version > vector-$version/version
|
||||
tar chvzf packages/vector-$version.tar.gz vector-$version
|
||||
tar chvzf dist/vector-$version.tar.gz vector-$version
|
||||
rm -r vector-$version
|
||||
|
||||
echo
|
||||
echo "Packaged packages/vector-$version.tar.gz"
|
||||
echo "Packaged dist/vector-$version.tar.gz"
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# auto-deploy script for https://riot.im/develop
|
||||
#
|
||||
# Listens for HTTP hits. When it gets one, downloads the artifact from jenkins
|
||||
# and deploys it as the new version.
|
||||
#
|
||||
# Requires the following python packages:
|
||||
#
|
||||
# - requests
|
||||
# - flask
|
||||
#
|
||||
from __future__ import print_function
|
||||
import json, requests, tarfile, argparse, os, errno
|
||||
import time
|
||||
from urlparse import urljoin
|
||||
|
||||
from flask import Flask, jsonify, request, abort
|
||||
|
||||
from deploy import Deployer, DeployException
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
arg_jenkins_url, arg_extract_path, arg_should_clean, arg_symlink = (
|
||||
None, None, None, None
|
||||
)
|
||||
|
||||
def download_file(url):
|
||||
local_filename = url.split('/')[-1]
|
||||
r = requests.get(url, stream=True)
|
||||
with open(local_filename, 'wb') as f:
|
||||
for chunk in r.iter_content(chunk_size=1024):
|
||||
if chunk: # filter out keep-alive new chunks
|
||||
f.write(chunk)
|
||||
return local_filename
|
||||
|
||||
def untar_to(tarball, dest):
|
||||
with tarfile.open(tarball) as tar:
|
||||
tar.extractall(dest)
|
||||
arg_jenkins_url = None
|
||||
deployer = None
|
||||
arg_extract_path = None
|
||||
arg_symlink = None
|
||||
|
||||
def create_symlink(source, linkname):
|
||||
try:
|
||||
@@ -57,6 +61,9 @@ def on_receive_jenkins_poke():
|
||||
abort(400, "Missing or bad build number")
|
||||
return
|
||||
|
||||
return fetch_jenkins_build(job_name, build_num)
|
||||
|
||||
def fetch_jenkins_build(job_name, build_num):
|
||||
artifact_url = urljoin(
|
||||
arg_jenkins_url, "job/%s/%s/api/json" % (job_name, build_num)
|
||||
)
|
||||
@@ -106,28 +113,45 @@ def on_receive_jenkins_poke():
|
||||
arg_jenkins_url, "job/%s/%s/artifact/%s" % (job_name, build_num, tar_gz_path)
|
||||
)
|
||||
|
||||
print("Retrieving .tar.gz file: %s" % tar_gz_url)
|
||||
filename = download_file(tar_gz_url)
|
||||
print("Downloaded file: %s" % filename)
|
||||
name_str = filename.replace(".tar.gz", "")
|
||||
untar_location = os.path.join(arg_extract_path, name_str)
|
||||
untar_to(filename, untar_location)
|
||||
# we extract into a directory based on the build number. This avoids the
|
||||
# problem of multiple builds building the same git version and thus having
|
||||
# the same tarball name. That would lead to two potential problems:
|
||||
# (a) sometimes jenkins serves corrupted artifacts; we would replace
|
||||
# a good deploy with a bad one
|
||||
# (b) we'll be overwriting the live deployment, which means people might
|
||||
# see half-written files.
|
||||
build_dir = os.path.join(arg_extract_path, "%s-#%s" % (job_name, build_num))
|
||||
try:
|
||||
extracted_dir = deploy_tarball(tar_gz_url, build_dir)
|
||||
except DeployException as e:
|
||||
abort(400, e.message)
|
||||
|
||||
if arg_should_clean:
|
||||
os.remove(filename)
|
||||
|
||||
# stamp the version somewhere JS can get to it
|
||||
with open(os.path.join(untar_location, "vector/version"), "w") as stamp_file:
|
||||
stamp_file.write(name_str)
|
||||
|
||||
create_symlink(source=os.path.join(untar_location, "vector"), linkname=arg_symlink)
|
||||
create_symlink(source=extracted_dir, linkname=arg_symlink)
|
||||
|
||||
return jsonify({})
|
||||
|
||||
def deploy_tarball(tar_gz_url, build_dir):
|
||||
"""Download a tarball from jenkins and unpack it
|
||||
|
||||
Returns:
|
||||
(str) the path to the unpacked deployment
|
||||
"""
|
||||
if os.path.exists(build_dir):
|
||||
raise DeployException(
|
||||
"Not deploying. We have previously deployed this build."
|
||||
)
|
||||
os.mkdir(build_dir)
|
||||
|
||||
# we rely on the fact that flask only serves one request at a time to
|
||||
# ensure that we do not overwrite a tarball from a concurrent request.
|
||||
|
||||
return deployer.deploy(tar_gz_url, build_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser("Runs a Vector redeployment server.")
|
||||
parser.add_argument(
|
||||
"-j", "--jenkins", dest="jenkins", default="http://matrix.org/jenkins/", help=(
|
||||
"-j", "--jenkins", dest="jenkins", default="https://matrix.org/jenkins/", help=(
|
||||
"The base URL of the Jenkins web server. This will be hit to get the\
|
||||
built artifacts (the .gz file) for redeploying."
|
||||
)
|
||||
@@ -142,6 +166,13 @@ if __name__ == "__main__":
|
||||
"The location to extract .tar.gz files to."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b", "--bundles-dir", dest="bundles_dir", help=(
|
||||
"A directory to move the contents of the 'bundles' directory to. A \
|
||||
symlink to the bundles directory will also be written inside the \
|
||||
extracted tarball. Example: './bundles'."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--clean", dest="clean", action="store_true", default=False, help=(
|
||||
"Remove .tar.gz files after they have been downloaded and extracted."
|
||||
@@ -154,17 +185,53 @@ if __name__ == "__main__":
|
||||
to the /vector directory INSIDE the tarball."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config", dest="config", help=(
|
||||
"Write a symlink to config.json in the extracted tarball. \
|
||||
To this location."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--test", dest="tarball_uri", help=(
|
||||
"Don't start an HTTP listener. Instead download a build from Jenkins \
|
||||
immediately."
|
||||
),
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.jenkins.endswith("/"): # important for urljoin
|
||||
arg_jenkins_url = args.jenkins
|
||||
else:
|
||||
arg_jenkins_url = args.jenkins + "/"
|
||||
arg_extract_path = args.extract
|
||||
arg_should_clean = args.clean
|
||||
arg_symlink = args.symlink
|
||||
print(
|
||||
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s" %
|
||||
(args.port, arg_extract_path,
|
||||
" (clean after)" if arg_should_clean else "", arg_symlink, arg_jenkins_url)
|
||||
)
|
||||
app.run(host="0.0.0.0", port=args.port, debug=True)
|
||||
|
||||
if not os.path.isdir(arg_extract_path):
|
||||
os.mkdir(arg_extract_path)
|
||||
|
||||
deployer = Deployer()
|
||||
deployer.bundles_path = args.bundles_dir
|
||||
deployer.should_clean = args.clean
|
||||
deployer.config_location = args.config
|
||||
|
||||
# we don't pgp-sign jenkins artifacts; instead we rely on HTTPS access to
|
||||
# the jenkins server (and the jenkins server not being compromised and/or
|
||||
# github not serving it compromised source). If that's not good enough for
|
||||
# you, don't use riot.im/develop.
|
||||
deployer.verify_signature = False
|
||||
|
||||
if args.tarball_uri is not None:
|
||||
build_dir = os.path.join(arg_extract_path, "test-%i" % (time.time()))
|
||||
deploy_tarball(args.tarball_uri, build_dir)
|
||||
else:
|
||||
print(
|
||||
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Config location: %s" %
|
||||
(args.port,
|
||||
arg_extract_path,
|
||||
" (clean after)" if deployer.should_clean else "",
|
||||
arg_symlink,
|
||||
arg_jenkins_url,
|
||||
deployer.config_location,
|
||||
)
|
||||
)
|
||||
app.run(host="0.0.0.0", port=args.port, debug=True)
|
||||
@@ -44,6 +44,7 @@ ConferenceCall.prototype.setup = function() {
|
||||
// looking for a 1:1 room with this conf user ID!)
|
||||
var call = Matrix.createNewMatrixCall(self.client, room.roomId);
|
||||
call.confUserId = self.confUserId;
|
||||
call.groupRoomId = self.groupRoomId;
|
||||
return call;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -19,32 +19,70 @@ limitations under the License.
|
||||
* You can edit it you like, but your changes will be overwritten,
|
||||
* so you'd just be trying to swim upstream like a salmon.
|
||||
* You are not a salmon.
|
||||
*
|
||||
* To update it, run:
|
||||
* ./reskindex.js -h header
|
||||
*/
|
||||
|
||||
module.exports.components = require('matrix-react-sdk/lib/component-index').components;
|
||||
|
||||
module.exports.components['structures.BottomLeftMenu'] = require('./components/structures/BottomLeftMenu');
|
||||
module.exports.components['structures.CompatibilityPage'] = require('./components/structures/CompatibilityPage');
|
||||
module.exports.components['structures.LeftPanel'] = require('./components/structures/LeftPanel');
|
||||
module.exports.components['structures.RightPanel'] = require('./components/structures/RightPanel');
|
||||
module.exports.components['structures.RoomDirectory'] = require('./components/structures/RoomDirectory');
|
||||
module.exports.components['structures.RoomSubList'] = require('./components/structures/RoomSubList');
|
||||
module.exports.components['structures.ViewSource'] = require('./components/structures/ViewSource');
|
||||
module.exports.components['views.elements.ImageView'] = require('./components/views/elements/ImageView');
|
||||
module.exports.components['views.elements.Spinner'] = require('./components/views/elements/Spinner');
|
||||
module.exports.components['views.globals.GuestWarningBar'] = require('./components/views/globals/GuestWarningBar');
|
||||
module.exports.components['views.globals.MatrixToolbar'] = require('./components/views/globals/MatrixToolbar');
|
||||
module.exports.components['views.globals.NewVersionBar'] = require('./components/views/globals/NewVersionBar');
|
||||
module.exports.components['views.login.VectorCustomServerDialog'] = require('./components/views/login/VectorCustomServerDialog');
|
||||
module.exports.components['views.login.VectorLoginFooter'] = require('./components/views/login/VectorLoginFooter');
|
||||
module.exports.components['views.login.VectorLoginHeader'] = require('./components/views/login/VectorLoginHeader');
|
||||
module.exports.components['views.messages.DateSeparator'] = require('./components/views/messages/DateSeparator');
|
||||
module.exports.components['views.messages.MessageTimestamp'] = require('./components/views/messages/MessageTimestamp');
|
||||
module.exports.components['views.messages.SenderProfile'] = require('./components/views/messages/SenderProfile');
|
||||
module.exports.components['views.rooms.BottomLeftMenuTile'] = require('./components/views/rooms/BottomLeftMenuTile');
|
||||
module.exports.components['views.rooms.MessageContextMenu'] = require('./components/views/rooms/MessageContextMenu');
|
||||
module.exports.components['views.rooms.RoomDNDView'] = require('./components/views/rooms/RoomDNDView');
|
||||
module.exports.components['views.rooms.RoomDropTarget'] = require('./components/views/rooms/RoomDropTarget');
|
||||
module.exports.components['views.rooms.RoomTooltip'] = require('./components/views/rooms/RoomTooltip');
|
||||
module.exports.components['views.rooms.SearchBar'] = require('./components/views/rooms/SearchBar');
|
||||
module.exports.components['views.settings.Notifications'] = require('./components/views/settings/Notifications');
|
||||
import structures$BottomLeftMenu from './components/structures/BottomLeftMenu';
|
||||
structures$BottomLeftMenu && (module.exports.components['structures.BottomLeftMenu'] = structures$BottomLeftMenu);
|
||||
import structures$CompatibilityPage from './components/structures/CompatibilityPage';
|
||||
structures$CompatibilityPage && (module.exports.components['structures.CompatibilityPage'] = structures$CompatibilityPage);
|
||||
import structures$LeftPanel from './components/structures/LeftPanel';
|
||||
structures$LeftPanel && (module.exports.components['structures.LeftPanel'] = structures$LeftPanel);
|
||||
import structures$RightPanel from './components/structures/RightPanel';
|
||||
structures$RightPanel && (module.exports.components['structures.RightPanel'] = structures$RightPanel);
|
||||
import structures$RoomDirectory from './components/structures/RoomDirectory';
|
||||
structures$RoomDirectory && (module.exports.components['structures.RoomDirectory'] = structures$RoomDirectory);
|
||||
import structures$RoomSubList from './components/structures/RoomSubList';
|
||||
structures$RoomSubList && (module.exports.components['structures.RoomSubList'] = structures$RoomSubList);
|
||||
import structures$SearchBox from './components/structures/SearchBox';
|
||||
structures$SearchBox && (module.exports.components['structures.SearchBox'] = structures$SearchBox);
|
||||
import structures$ViewSource from './components/structures/ViewSource';
|
||||
structures$ViewSource && (module.exports.components['structures.ViewSource'] = structures$ViewSource);
|
||||
import views$context_menus$MessageContextMenu from './components/views/context_menus/MessageContextMenu';
|
||||
views$context_menus$MessageContextMenu && (module.exports.components['views.context_menus.MessageContextMenu'] = views$context_menus$MessageContextMenu);
|
||||
import views$context_menus$NotificationStateContextMenu from './components/views/context_menus/NotificationStateContextMenu';
|
||||
views$context_menus$NotificationStateContextMenu && (module.exports.components['views.context_menus.NotificationStateContextMenu'] = views$context_menus$NotificationStateContextMenu);
|
||||
import views$context_menus$RoomTagContextMenu from './components/views/context_menus/RoomTagContextMenu';
|
||||
views$context_menus$RoomTagContextMenu && (module.exports.components['views.context_menus.RoomTagContextMenu'] = views$context_menus$RoomTagContextMenu);
|
||||
import views$dialogs$BugReportDialog from './components/views/dialogs/BugReportDialog';
|
||||
views$dialogs$BugReportDialog && (module.exports.components['views.dialogs.BugReportDialog'] = views$dialogs$BugReportDialog);
|
||||
import views$dialogs$ChangelogDialog from './components/views/dialogs/ChangelogDialog';
|
||||
views$dialogs$ChangelogDialog && (module.exports.components['views.dialogs.ChangelogDialog'] = views$dialogs$ChangelogDialog);
|
||||
import views$directory$NetworkDropdown from './components/views/directory/NetworkDropdown';
|
||||
views$directory$NetworkDropdown && (module.exports.components['views.directory.NetworkDropdown'] = views$directory$NetworkDropdown);
|
||||
import views$elements$ImageView from './components/views/elements/ImageView';
|
||||
views$elements$ImageView && (module.exports.components['views.elements.ImageView'] = views$elements$ImageView);
|
||||
import views$elements$Spinner from './components/views/elements/Spinner';
|
||||
views$elements$Spinner && (module.exports.components['views.elements.Spinner'] = views$elements$Spinner);
|
||||
import views$globals$GuestWarningBar from './components/views/globals/GuestWarningBar';
|
||||
views$globals$GuestWarningBar && (module.exports.components['views.globals.GuestWarningBar'] = views$globals$GuestWarningBar);
|
||||
import views$globals$MatrixToolbar from './components/views/globals/MatrixToolbar';
|
||||
views$globals$MatrixToolbar && (module.exports.components['views.globals.MatrixToolbar'] = views$globals$MatrixToolbar);
|
||||
import views$globals$NewVersionBar from './components/views/globals/NewVersionBar';
|
||||
views$globals$NewVersionBar && (module.exports.components['views.globals.NewVersionBar'] = views$globals$NewVersionBar);
|
||||
import views$login$VectorCustomServerDialog from './components/views/login/VectorCustomServerDialog';
|
||||
views$login$VectorCustomServerDialog && (module.exports.components['views.login.VectorCustomServerDialog'] = views$login$VectorCustomServerDialog);
|
||||
import views$login$VectorLoginFooter from './components/views/login/VectorLoginFooter';
|
||||
views$login$VectorLoginFooter && (module.exports.components['views.login.VectorLoginFooter'] = views$login$VectorLoginFooter);
|
||||
import views$login$VectorLoginHeader from './components/views/login/VectorLoginHeader';
|
||||
views$login$VectorLoginHeader && (module.exports.components['views.login.VectorLoginHeader'] = views$login$VectorLoginHeader);
|
||||
import views$messages$DateSeparator from './components/views/messages/DateSeparator';
|
||||
views$messages$DateSeparator && (module.exports.components['views.messages.DateSeparator'] = views$messages$DateSeparator);
|
||||
import views$messages$MessageTimestamp from './components/views/messages/MessageTimestamp';
|
||||
views$messages$MessageTimestamp && (module.exports.components['views.messages.MessageTimestamp'] = views$messages$MessageTimestamp);
|
||||
import views$rooms$DNDRoomTile from './components/views/rooms/DNDRoomTile';
|
||||
views$rooms$DNDRoomTile && (module.exports.components['views.rooms.DNDRoomTile'] = views$rooms$DNDRoomTile);
|
||||
import views$rooms$RoomDropTarget from './components/views/rooms/RoomDropTarget';
|
||||
views$rooms$RoomDropTarget && (module.exports.components['views.rooms.RoomDropTarget'] = views$rooms$RoomDropTarget);
|
||||
import views$rooms$RoomTooltip from './components/views/rooms/RoomTooltip';
|
||||
views$rooms$RoomTooltip && (module.exports.components['views.rooms.RoomTooltip'] = views$rooms$RoomTooltip);
|
||||
import views$rooms$SearchBar from './components/views/rooms/SearchBar';
|
||||
views$rooms$SearchBar && (module.exports.components['views.rooms.SearchBar'] = views$rooms$SearchBar);
|
||||
import views$settings$IntegrationsManager from './components/views/settings/IntegrationsManager';
|
||||
views$settings$IntegrationsManager && (module.exports.components['views.settings.IntegrationsManager'] = views$settings$IntegrationsManager);
|
||||
import views$settings$Notifications from './components/views/settings/Notifications';
|
||||
views$settings$Notifications && (module.exports.components['views.settings.Notifications'] = views$settings$Notifications);
|
||||
|
||||
@@ -17,42 +17,107 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'BottomLeftMenu',
|
||||
|
||||
propTypes: {
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return({
|
||||
directoryHover : false,
|
||||
roomsHover : false,
|
||||
peopleHover : false,
|
||||
settingsHover : false,
|
||||
});
|
||||
},
|
||||
|
||||
// Room events
|
||||
onDirectoryClick: function() {
|
||||
dis.dispatch({ action: 'view_room_directory' });
|
||||
},
|
||||
|
||||
onDirectoryMouseEnter: function() {
|
||||
this.setState({ directoryHover: true });
|
||||
},
|
||||
|
||||
onDirectoryMouseLeave: function() {
|
||||
this.setState({ directoryHover: false });
|
||||
},
|
||||
|
||||
onRoomsClick: function() {
|
||||
dis.dispatch({ action: 'view_create_room' });
|
||||
},
|
||||
|
||||
onRoomsMouseEnter: function() {
|
||||
this.setState({ roomsHover: true });
|
||||
},
|
||||
|
||||
onRoomsMouseLeave: function() {
|
||||
this.setState({ roomsHover: false });
|
||||
},
|
||||
|
||||
// People events
|
||||
onPeopleClick: function() {
|
||||
dis.dispatch({ action: 'view_create_chat' });
|
||||
},
|
||||
|
||||
onPeopleMouseEnter: function() {
|
||||
this.setState({ peopleHover: true });
|
||||
},
|
||||
|
||||
onPeopleMouseLeave: function() {
|
||||
this.setState({ peopleHover: false });
|
||||
},
|
||||
|
||||
// Settings events
|
||||
onSettingsClick: function() {
|
||||
dis.dispatch({action: 'view_user_settings'});
|
||||
dis.dispatch({ action: 'view_user_settings' });
|
||||
},
|
||||
|
||||
onRoomDirectoryClick: function() {
|
||||
dis.dispatch({action: 'view_room_directory'});
|
||||
onSettingsMouseEnter: function() {
|
||||
this.setState({ settingsHover: true });
|
||||
},
|
||||
|
||||
onCreateRoomClick: function() {
|
||||
dis.dispatch({action: 'view_create_room'});
|
||||
onSettingsMouseLeave: function() {
|
||||
this.setState({ settingsHover: false });
|
||||
},
|
||||
|
||||
getLabel: function(name) {
|
||||
if (!this.props.collapsed) {
|
||||
return <div className="mx_RoomTile_name">{name}</div>
|
||||
}
|
||||
else if (this.state.hover) {
|
||||
// Get the label/tooltip to show
|
||||
getLabel: function(label, show) {
|
||||
if (show) {
|
||||
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||
return <RoomTooltip name={name}/>;
|
||||
return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var BottomLeftMenuTile = sdk.getComponent('rooms.BottomLeftMenuTile');
|
||||
var TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
return (
|
||||
<div className="mx_BottomLeftMenu">
|
||||
<div className="mx_BottomLeftMenu_options">
|
||||
<BottomLeftMenuTile collapsed={ this.props.collapsed } img="img/create-big.svg" label="Start chat" onClick={ this.onCreateRoomClick }/>
|
||||
<BottomLeftMenuTile collapsed={ this.props.collapsed } img="img/directory-big.svg" label="Directory" onClick={ this.onRoomDirectoryClick }/>
|
||||
<BottomLeftMenuTile collapsed={ this.props.collapsed } img="img/settings-big.svg" label="Settings" onClick={ this.onSettingsClick }/>
|
||||
<AccessibleButton className="mx_BottomLeftMenu_people" onClick={ this.onPeopleClick } onMouseEnter={ this.onPeopleMouseEnter } onMouseLeave={ this.onPeopleMouseLeave } >
|
||||
<TintableSvg src="img/icons-people.svg" width="25" height="25" />
|
||||
{ this.getLabel("Start chat", this.state.peopleHover) }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className="mx_BottomLeftMenu_directory" onClick={ this.onDirectoryClick } onMouseEnter={ this.onDirectoryMouseEnter } onMouseLeave={ this.onDirectoryMouseLeave } >
|
||||
<TintableSvg src="img/icons-directory.svg" width="25" height="25"/>
|
||||
{ this.getLabel("Room directory", this.state.directoryHover) }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className="mx_BottomLeftMenu_createRoom" onClick={ this.onRoomsClick } onMouseEnter={ this.onRoomsMouseEnter } onMouseLeave={ this.onRoomsMouseLeave } >
|
||||
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
|
||||
{ this.getLabel("Create new room", this.state.roomsHover) }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className="mx_BottomLeftMenu_settings" onClick={ this.onSettingsClick } onMouseEnter={ this.onSettingsMouseEnter } onMouseLeave={ this.onSettingsMouseLeave } >
|
||||
<TintableSvg src="img/icons-settings.svg" width="25" height="25" />
|
||||
{ this.getLabel("Settings", this.state.settingsHover) }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -39,16 +39,14 @@ module.exports = React.createClass({
|
||||
return (
|
||||
<div className="mx_CompatibilityPage">
|
||||
<div className="mx_CompatibilityPage_box">
|
||||
<p>Sorry, your browser is <b>not</b> able to run Vector.</p>
|
||||
<p>Sorry, your browser is <b>not</b> able to run Riot.</p>
|
||||
<p>
|
||||
Vector uses many advanced browser features, some of which are not
|
||||
Riot uses many advanced browser features, some of which are not
|
||||
available or experimental in your current browser.
|
||||
</p>
|
||||
<p>
|
||||
Please install <a href="https://www.google.com/chrome">Chrome</a> for
|
||||
the best experience. <a href="https://getfirefox.com">Firefox</a>,
|
||||
<a href="http://apple.com/safari">Safari</a> and
|
||||
<a href="http://opera.com">Opera</a> work too.
|
||||
Please install <a href="https://www.google.com/chrome">Chrome</a> or <a href="https://getfirefox.com">Firefox</a> for
|
||||
the best experience. <a href="http://apple.com/safari">Safari</a> and <a href="http://opera.com">Opera</a> work too.
|
||||
</p>
|
||||
<p>
|
||||
With your current browser, the look and feel of the application may
|
||||
|
||||
@@ -31,6 +31,7 @@ var LeftPanel = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showCallElement: null,
|
||||
searchFilter: '',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -79,17 +80,22 @@ var LeftPanel = React.createClass({
|
||||
if (call) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: call.roomId,
|
||||
room_id: call.groupRoomId || call.roomId,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onSearch: function(term) {
|
||||
this.setState({ searchFilter: term });
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var RoomList = sdk.getComponent('rooms.RoomList');
|
||||
var BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
|
||||
var SearchBox = sdk.getComponent('structures.SearchBox');
|
||||
|
||||
var collapseButton;
|
||||
var classes = "mx_LeftPanel";
|
||||
var classes = "mx_LeftPanel mx_fadable";
|
||||
if (this.props.collapsed) {
|
||||
classes += " collapsed";
|
||||
}
|
||||
@@ -103,18 +109,20 @@ var LeftPanel = React.createClass({
|
||||
var CallView = sdk.getComponent('voip.CallView');
|
||||
callPreview = (
|
||||
<CallView
|
||||
className="mx_LeftPanel_callView" onClick={this.onCallViewClick}
|
||||
className="mx_LeftPanel_callView" showVoice={true} onClick={this.onCallViewClick}
|
||||
ConferenceHandler={VectorConferenceHandler} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className={classes}>
|
||||
<aside className={classes} style={{ opacity: this.props.opacity }}>
|
||||
<SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />
|
||||
{ collapseButton }
|
||||
{ callPreview }
|
||||
<RoomList
|
||||
selectedRoom={this.props.selectedRoom}
|
||||
collapsed={this.props.collapsed}
|
||||
searchFilter={this.state.searchFilter}
|
||||
ConferenceHandler={VectorConferenceHandler} />
|
||||
<BottomLeftMenu collapsed={this.props.collapsed}/>
|
||||
</aside>
|
||||
|
||||
@@ -17,17 +17,27 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var sdk = require('matrix-react-sdk');
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
|
||||
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');
|
||||
var Modal = require('matrix-react-sdk/lib/Modal');
|
||||
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RightPanel',
|
||||
|
||||
propTypes: {
|
||||
userId: React.PropTypes.string, // if showing an orphaned MemberInfo page, this is set
|
||||
roomId: React.PropTypes.string, // if showing panels for a given room, this is set
|
||||
collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel
|
||||
},
|
||||
|
||||
Phase : {
|
||||
MemberList: 'MemberList',
|
||||
FileList: 'FileList',
|
||||
FilePanel: 'FilePanel',
|
||||
NotificationPanel: 'NotificationPanel',
|
||||
MemberInfo: 'MemberInfo',
|
||||
},
|
||||
|
||||
@@ -45,23 +55,53 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
phase : this.Phase.MemberList
|
||||
if (this.props.userId) {
|
||||
var member = new Matrix.RoomMember(null, this.props.userId);
|
||||
return {
|
||||
phase: this.Phase.MemberInfo,
|
||||
member: member,
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
phase: this.Phase.MemberList
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onMemberListButtonClick: function() {
|
||||
if (this.props.collapsed) {
|
||||
this.setState({ phase: this.Phase.MemberList });
|
||||
dis.dispatch({
|
||||
action: 'show_right_panel',
|
||||
});
|
||||
}
|
||||
else {
|
||||
dis.dispatch({
|
||||
action: 'hide_right_panel',
|
||||
this.setState({ phase: this.Phase.MemberList });
|
||||
},
|
||||
|
||||
onFileListButtonClick: function() {
|
||||
this.setState({ phase: this.Phase.FilePanel });
|
||||
},
|
||||
|
||||
onNotificationListButtonClick: function() {
|
||||
this.setState({ phase: this.Phase.NotificationPanel });
|
||||
},
|
||||
|
||||
onCollapseClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'hide_right_panel',
|
||||
});
|
||||
},
|
||||
|
||||
onInviteButtonClick: function() {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "Guest users can't invite users. Please register to invite."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// call ChatInviteDialog
|
||||
dis.dispatch({
|
||||
action: 'view_invite',
|
||||
roomId: this.props.roomId,
|
||||
});
|
||||
},
|
||||
|
||||
onRoomStateMember: function(ev, state, member) {
|
||||
@@ -97,7 +137,7 @@ module.exports = React.createClass({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (payload.action === "view_room") {
|
||||
else if (payload.action === "view_room") {
|
||||
if (this.state.phase === this.Phase.MemberInfo) {
|
||||
this.setState({
|
||||
phase: this.Phase.MemberList
|
||||
@@ -108,69 +148,116 @@ module.exports = React.createClass({
|
||||
|
||||
render: function() {
|
||||
var MemberList = sdk.getComponent('rooms.MemberList');
|
||||
var NotificationPanel = sdk.getComponent('structures.NotificationPanel');
|
||||
var FilePanel = sdk.getComponent('structures.FilePanel');
|
||||
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
var buttonGroup;
|
||||
var inviteGroup;
|
||||
var panel;
|
||||
|
||||
var filesHighlight;
|
||||
var membersHighlight;
|
||||
var notificationsHighlight;
|
||||
if (!this.props.collapsed) {
|
||||
if (this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) {
|
||||
membersHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>;
|
||||
}
|
||||
else if (this.state.phase == this.Phase.FileList) {
|
||||
else if (this.state.phase == this.Phase.FilePanel) {
|
||||
filesHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>;
|
||||
}
|
||||
else if (this.state.phase == this.Phase.NotificationPanel) {
|
||||
notificationsHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>;
|
||||
}
|
||||
}
|
||||
|
||||
var membersBadge;
|
||||
if ((this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) && this.props.roomId) {
|
||||
var cli = MatrixClientPeg.get();
|
||||
var room = cli.getRoom(this.props.roomId);
|
||||
var user_is_in_room;
|
||||
if (room) {
|
||||
membersBadge = <div className="mx_RightPanel_headerButton_badge">{ room.getJoinedMembers().length }</div>;
|
||||
membersBadge = room.getJoinedMembers().length;
|
||||
user_is_in_room = room.hasMembershipState(
|
||||
MatrixClientPeg.get().credentials.userId, 'join'
|
||||
);
|
||||
}
|
||||
|
||||
if (user_is_in_room) {
|
||||
inviteGroup =
|
||||
<AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
|
||||
<div className="mx_RightPanel_icon" >
|
||||
<TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
|
||||
</div>
|
||||
<div className="mx_RightPanel_message">Invite to this room</div>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (this.props.roomId) {
|
||||
buttonGroup =
|
||||
<div className="mx_RightPanel_headerButtonGroup">
|
||||
<div className="mx_RightPanel_headerButton" title="Members" onClick={ this.onMemberListButtonClick }>
|
||||
<TintableSvg src="img/members.svg" width="17" height="22"/>
|
||||
{ membersBadge }
|
||||
<AccessibleButton className="mx_RightPanel_headerButton"
|
||||
title="Members" onClick={ this.onMemberListButtonClick }>
|
||||
<div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span> </span>}</div>
|
||||
<TintableSvg src="img/icons-people.svg" width="25" height="25"/>
|
||||
{ membersHighlight }
|
||||
</div>
|
||||
<div className="mx_RightPanel_headerButton mx_RightPanel_filebutton" title="Files">
|
||||
<TintableSvg src="img/files.svg" width="17" height="22"/>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_RightPanel_headerButton mx_RightPanel_filebutton"
|
||||
title="Files" onClick={ this.onFileListButtonClick }>
|
||||
<div className="mx_RightPanel_headerButton_badge"> </div>
|
||||
<TintableSvg src="img/icons-files.svg" width="25" height="25"/>
|
||||
{ filesHighlight }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton"
|
||||
title="Notifications" onClick={ this.onNotificationListButtonClick }>
|
||||
<div className="mx_RightPanel_headerButton_badge"> </div>
|
||||
<TintableSvg src="img/icons-notifications.svg" width="25" height="25"/>
|
||||
{ notificationsHighlight }
|
||||
</AccessibleButton>
|
||||
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" title="Hide panel" onClick={ this.onCollapseClick }>
|
||||
<TintableSvg src="img/minimise.svg" width="10" height="16"/>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
if (!this.props.collapsed) {
|
||||
if(this.state.phase == this.Phase.MemberList) {
|
||||
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
|
||||
}
|
||||
else if(this.state.phase == this.Phase.MemberInfo) {
|
||||
var MemberInfo = sdk.getComponent('rooms.MemberInfo');
|
||||
panel = <MemberInfo roomId={this.props.roomId} member={this.state.member} key={this.props.roomId} />
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var classes = "mx_RightPanel";
|
||||
if (!this.props.collapsed) {
|
||||
if(this.props.roomId && this.state.phase == this.Phase.MemberList) {
|
||||
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
|
||||
}
|
||||
else if(this.state.phase == this.Phase.MemberInfo) {
|
||||
var MemberInfo = sdk.getComponent('rooms.MemberInfo');
|
||||
panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.props.userId} />
|
||||
}
|
||||
else if (this.state.phase == this.Phase.NotificationPanel) {
|
||||
panel = <NotificationPanel />
|
||||
}
|
||||
else if (this.state.phase == this.Phase.FilePanel) {
|
||||
panel = <FilePanel roomId={this.props.roomId} />
|
||||
}
|
||||
}
|
||||
|
||||
if (!panel) {
|
||||
panel = <div className="mx_RightPanel_blank"></div>;
|
||||
}
|
||||
|
||||
var classes = "mx_RightPanel mx_fadable";
|
||||
if (this.props.collapsed) {
|
||||
classes += " collapsed";
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className={classes}>
|
||||
<aside className={classes} style={{ opacity: this.props.opacity }}>
|
||||
<div className="mx_RightPanel_header">
|
||||
{ buttonGroup }
|
||||
</div>
|
||||
{ panel }
|
||||
<div className="mx_RightPanel_footer">
|
||||
{ inviteGroup }
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ var React = require('react');
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var ContentRepo = require("matrix-js-sdk").ContentRepo;
|
||||
var Modal = require('matrix-react-sdk/lib/Modal');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var sdk = require('matrix-react-sdk');
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
|
||||
@@ -29,91 +29,355 @@ var linkify = require('linkifyjs');
|
||||
var linkifyString = require('linkifyjs/string');
|
||||
var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
|
||||
var sanitizeHtml = require('sanitize-html');
|
||||
var q = require('q');
|
||||
|
||||
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomDirectory',
|
||||
|
||||
propTypes: {
|
||||
config: React.PropTypes.object,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
config: {},
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
publicRooms: [],
|
||||
roomAlias: '',
|
||||
loading: true,
|
||||
protocolsLoading: true,
|
||||
instanceId: null,
|
||||
includeAll: false,
|
||||
roomServer: null,
|
||||
filterString: null,
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var self = this;
|
||||
MatrixClientPeg.get().publicRooms(function (err, data) {
|
||||
if (err) {
|
||||
self.setState({ loading: false });
|
||||
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to get public room list",
|
||||
description: err.message
|
||||
});
|
||||
componentWillMount: function() {
|
||||
this.nextBatch = null;
|
||||
this.filterTimeout = null;
|
||||
this.scrollPanel = null;
|
||||
this.protocols = null;
|
||||
|
||||
this.setState({protocolsLoading: true});
|
||||
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
||||
this.protocols = response;
|
||||
this.setState({protocolsLoading: false});
|
||||
}, (err) => {
|
||||
this.setState({protocolsLoading: false});
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
// Guests currently aren't allowed to use this API, so
|
||||
// ignore this as otherwise this error is literally the
|
||||
// thing you see when loading the client!
|
||||
return;
|
||||
}
|
||||
else {
|
||||
self.setState({
|
||||
publicRooms: data.chunk,
|
||||
loading: false,
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to get protocol list from Home Server",
|
||||
description: "The Home Server may be too old to support third party networks",
|
||||
});
|
||||
});
|
||||
|
||||
// dis.dispatch({
|
||||
// action: 'ui_opacity',
|
||||
// sideOpacity: 0.3,
|
||||
// middleOpacity: 0.3,
|
||||
// });
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
// dis.dispatch({
|
||||
// action: 'ui_opacity',
|
||||
// sideOpacity: 1.0,
|
||||
// middleOpacity: 1.0,
|
||||
// });
|
||||
},
|
||||
|
||||
refreshRoomList: function() {
|
||||
this.nextBatch = null;
|
||||
this.setState({
|
||||
publicRooms: [],
|
||||
loading: true,
|
||||
});
|
||||
this.getMoreRooms().done();
|
||||
},
|
||||
|
||||
getMoreRooms: function() {
|
||||
if (!MatrixClientPeg.get()) return q();
|
||||
|
||||
const my_filter_string = this.state.filterString;
|
||||
const my_server = this.state.roomServer;
|
||||
// remember the next batch token when we sent the request
|
||||
// too. If it's changed, appending to the list will corrupt it.
|
||||
const my_next_batch = this.nextBatch;
|
||||
const opts = {limit: 20};
|
||||
if (my_server != MatrixClientPeg.getHomeServerName()) {
|
||||
opts.server = my_server;
|
||||
}
|
||||
if (this.state.instanceId) {
|
||||
opts.third_party_instance_id = this.state.instanceId;
|
||||
} else if (this.state.includeAll) {
|
||||
opts.include_all_networks = true;
|
||||
}
|
||||
if (this.nextBatch) opts.since = this.nextBatch;
|
||||
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string } ;
|
||||
return MatrixClientPeg.get().publicRooms(opts).then((data) => {
|
||||
if (
|
||||
my_filter_string != this.state.filterString ||
|
||||
my_server != this.state.roomServer ||
|
||||
my_next_batch != this.nextBatch)
|
||||
{
|
||||
// if the filter or server has changed since this request was sent,
|
||||
// throw away the result (don't even clear the busy flag
|
||||
// since we must still have a request in flight)
|
||||
return;
|
||||
}
|
||||
|
||||
this.nextBatch = data.next_batch;
|
||||
this.setState((s) => {
|
||||
s.publicRooms.push(...data.chunk);
|
||||
s.loading = false;
|
||||
return s;
|
||||
});
|
||||
return Boolean(data.next_batch);
|
||||
}, (err) => {
|
||||
if (
|
||||
my_filter_string != this.state.filterString ||
|
||||
my_server != this.state.roomServer ||
|
||||
my_next_batch != this.nextBatch)
|
||||
{
|
||||
// as above: we don't care about errors for old
|
||||
// requests either
|
||||
return;
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to get public room list",
|
||||
description: err.message
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* A limited interface for removing rooms from the directory.
|
||||
* Will set the room to not be publicly visible and delete the
|
||||
* default alias. In the long term, it would be better to allow
|
||||
* HS admins to do this through the RoomSettings interface, but
|
||||
* this needs SPEC-417.
|
||||
*/
|
||||
removeFromDirectory: function(room) {
|
||||
var alias = get_display_alias_for_room(room);
|
||||
var name = room.name || alias || "Unnamed room";
|
||||
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
||||
var desc;
|
||||
if (alias) {
|
||||
desc = `Delete the room alias '${alias}' and remove '${name}' from the directory?`;
|
||||
} else {
|
||||
desc = `Remove '${name}' from the directory?`;
|
||||
}
|
||||
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Remove from Directory",
|
||||
description: desc,
|
||||
onFinished: (should_delete) => {
|
||||
if (!should_delete) return;
|
||||
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
var modal = Modal.createDialog(Loader);
|
||||
var step = `remove '${name}' from the directory.`;
|
||||
|
||||
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
|
||||
if (!alias) return;
|
||||
step = 'delete the alias.';
|
||||
return MatrixClientPeg.get().deleteAlias(alias);
|
||||
}).done(() => {
|
||||
modal.close();
|
||||
this.refreshRoomList();
|
||||
}, function(err) {
|
||||
modal.close();
|
||||
this.refreshRoomList();
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to "+step,
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
self.forceUpdate();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showRoom: function(roomId) {
|
||||
// extract the metadata from the publicRooms structure to pass
|
||||
// as out-of-band data to view_room, because we get information
|
||||
// here that we can't get other than by joining the room in some
|
||||
// cases.
|
||||
var room;
|
||||
for (var i = 0; i < this.state.publicRooms.length; ++i) {
|
||||
if (this.state.publicRooms[i].room_id == roomId) {
|
||||
room = this.state.publicRooms[i];
|
||||
break;
|
||||
}
|
||||
onRoomClicked: function(room, ev) {
|
||||
if (ev.shiftKey) {
|
||||
ev.preventDefault();
|
||||
this.removeFromDirectory(room);
|
||||
} else {
|
||||
this.showRoom(room);
|
||||
}
|
||||
var oob_data = {};
|
||||
},
|
||||
|
||||
onOptionChange: function(server, instanceId, includeAll) {
|
||||
// clear next batch so we don't try to load more rooms
|
||||
this.nextBatch = null;
|
||||
this.setState({
|
||||
// Clear the public rooms out here otherwise we needlessly
|
||||
// spend time filtering lots of rooms when we're about to
|
||||
// to clear the list anyway.
|
||||
publicRooms: [],
|
||||
roomServer: server,
|
||||
instanceId: instanceId,
|
||||
includeAll: includeAll,
|
||||
}, this.refreshRoomList);
|
||||
// We also refresh the room list each time even though this
|
||||
// filtering is client-side. It hopefully won't be client side
|
||||
// for very long, and we may have fetched a thousand rooms to
|
||||
// find the five gitter ones, at which point we do not want
|
||||
// to render all those rooms when switching back to 'all networks'.
|
||||
// Easiest to just blow away the state & re-fetch.
|
||||
},
|
||||
|
||||
onFillRequest: function(backwards) {
|
||||
if (backwards || !this.nextBatch) return q(false);
|
||||
|
||||
return this.getMoreRooms();
|
||||
},
|
||||
|
||||
onFilterChange: function(alias) {
|
||||
this.setState({
|
||||
filterString: alias || null,
|
||||
});
|
||||
|
||||
// don't send the request for a little bit,
|
||||
// no point hammering the server with a
|
||||
// request for every keystroke, let the
|
||||
// user finish typing.
|
||||
if (this.filterTimeout) {
|
||||
clearTimeout(this.filterTimeout);
|
||||
}
|
||||
this.filterTimeout = setTimeout(() => {
|
||||
this.filterTimeout = null;
|
||||
this.refreshRoomList();
|
||||
}, 700);
|
||||
},
|
||||
|
||||
onFilterClear: function() {
|
||||
// update immediately
|
||||
this.setState({
|
||||
filterString: null,
|
||||
}, this.refreshRoomList);
|
||||
|
||||
if (this.filterTimeout) {
|
||||
clearTimeout(this.filterTimeout);
|
||||
}
|
||||
},
|
||||
|
||||
onJoinClick: function(alias) {
|
||||
// If we don't have a particular instance id selected, just show that rooms alias
|
||||
if (!this.state.instanceId) {
|
||||
// If the user specified an alias without a domain, add on whichever server is selected
|
||||
// in the dropdown
|
||||
if (alias.indexOf(':') == -1) {
|
||||
alias = alias + ':' + this.state.roomServer;
|
||||
}
|
||||
this.showRoomAlias(alias);
|
||||
} else {
|
||||
// This is a 3rd party protocol. Let's see if we can join it
|
||||
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
|
||||
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
|
||||
const fields = protocolName ? this._getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) : null;
|
||||
if (!fields) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to join network",
|
||||
description: "Riot does not know how to join a room on this network",
|
||||
});
|
||||
return;
|
||||
}
|
||||
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
|
||||
if (resp.length > 0 && resp[0].alias) {
|
||||
this.showRoomAlias(resp[0].alias);
|
||||
} else {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Room not found",
|
||||
description: "Couldn't find a matching Matrix room",
|
||||
});
|
||||
}
|
||||
}, (e) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Fetching third party location failed",
|
||||
description: "Unable to look up room ID from server",
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showRoomAlias: function(alias) {
|
||||
this.showRoom(null, alias);
|
||||
},
|
||||
|
||||
showRoom: function(room, room_alias) {
|
||||
var payload = {action: 'view_room'};
|
||||
if (room) {
|
||||
oob_data = {
|
||||
// Don't let the user view a room they won't be able to either
|
||||
// peek or join: fail earlier so they don't have to click back
|
||||
// to the directory.
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
if (!room.world_readable && !room.guest_can_join) {
|
||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Failed to join the room",
|
||||
description: "This room is inaccessible to guests. You may be able to join if you register."
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!room_alias) {
|
||||
room_alias = get_display_alias_for_room(room);
|
||||
}
|
||||
|
||||
payload.oob_data = {
|
||||
avatarUrl: room.avatar_url,
|
||||
// XXX: This logic is duplicated from the JS SDK which
|
||||
// would normally decide what the name is.
|
||||
name: room.name || room.canonical_alias || (room.aliases ? room.aliases[0] : "Unnamed room"),
|
||||
name: room.name || room_alias || "Unnamed room",
|
||||
};
|
||||
}
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: roomId,
|
||||
oob_data: oob_data,
|
||||
});
|
||||
// It's not really possible to join Matrix rooms by ID because the HS has no way to know
|
||||
// which servers to start querying. However, there's no other way to join rooms in
|
||||
// this list without aliases at present, so if roomAlias isn't set here we have no
|
||||
// choice but to supply the ID.
|
||||
if (room_alias) {
|
||||
payload.room_alias = room_alias;
|
||||
} else {
|
||||
payload.room_id = room.room_id;
|
||||
}
|
||||
dis.dispatch(payload);
|
||||
},
|
||||
|
||||
getRows: function(filter) {
|
||||
getRows: function() {
|
||||
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
|
||||
if (!this.state.publicRooms) return [];
|
||||
|
||||
var rooms = this.state.publicRooms.filter(function(a) {
|
||||
// FIXME: if incrementally typing, keep narrowing down the search set
|
||||
// incrementally rather than starting over each time.
|
||||
return (((a.name && a.name.toLowerCase().search(filter.toLowerCase()) >= 0) ||
|
||||
(a.aliases && a.aliases[0].toLowerCase().search(filter.toLowerCase()) >= 0)) &&
|
||||
a.num_joined_members > 0);
|
||||
}).sort(function(a,b) {
|
||||
return a.num_joined_members - b.num_joined_members;
|
||||
});
|
||||
var rooms = this.state.publicRooms;
|
||||
var rows = [];
|
||||
var self = this;
|
||||
var guestRead, guestJoin, perms;
|
||||
for (var i = 0; i < rooms.length; i++) {
|
||||
var alias = rooms[i].canonical_alias || (rooms[i].aliases ? rooms[i].aliases[0] : "");
|
||||
var name = rooms[i].name || alias || "Unnamed room";
|
||||
var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || "Unnamed room";
|
||||
guestRead = null;
|
||||
guestJoin = null;
|
||||
|
||||
@@ -136,8 +400,12 @@ module.exports = React.createClass({
|
||||
var topic = rooms[i].topic || '';
|
||||
topic = linkifyString(sanitizeHtml(topic));
|
||||
|
||||
rows.unshift(
|
||||
<tr key={ rooms[i].room_id } onClick={self.showRoom.bind(null, rooms[i].room_id)}>
|
||||
rows.push(
|
||||
<tr key={ rooms[i].room_id }
|
||||
onClick={self.onRoomClicked.bind(self, rooms[i])}
|
||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||
>
|
||||
<td className="mx_RoomDirectory_roomAvatar">
|
||||
<BaseAvatar width={24} height={24} resizeMethod='crop'
|
||||
name={ name } idName={ name }
|
||||
@@ -151,7 +419,7 @@ module.exports = React.createClass({
|
||||
<div className="mx_RoomDirectory_topic"
|
||||
onClick={ function(e) { e.stopPropagation() } }
|
||||
dangerouslySetInnerHTML={{ __html: topic }}/>
|
||||
<div className="mx_RoomDirectory_alias">{ alias }</div>
|
||||
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomMemberCount">
|
||||
{ rooms[i].num_joined_members }
|
||||
@@ -162,40 +430,132 @@ module.exports = React.createClass({
|
||||
return rows;
|
||||
},
|
||||
|
||||
onKeyUp: function(ev) {
|
||||
this.forceUpdate();
|
||||
this.setState({ roomAlias : this.refs.roomAlias.value })
|
||||
if (ev.key == "Enter") {
|
||||
this.showRoom(this.refs.roomAlias.value);
|
||||
collectScrollPanel: function(element) {
|
||||
this.scrollPanel = element;
|
||||
},
|
||||
|
||||
_stringLooksLikeId: function(s, field_type) {
|
||||
let pat = /^#[^\s]+:[^\s]/;
|
||||
if (field_type && field_type.regexp) {
|
||||
pat = new RegExp(field_type.regexp);
|
||||
}
|
||||
|
||||
return pat.test(s);
|
||||
},
|
||||
|
||||
_getFieldsForThirdPartyLocation: function(userInput, protocol, instance) {
|
||||
// make an object with the fields specified by that protocol. We
|
||||
// require that the values of all but the last field come from the
|
||||
// instance. The last is the user input.
|
||||
const requiredFields = protocol.location_fields;
|
||||
if (!requiredFields) return null;
|
||||
const fields = {};
|
||||
for (let i = 0; i < requiredFields.length - 1; ++i) {
|
||||
const thisField = requiredFields[i];
|
||||
if (instance.fields[thisField] === undefined) return null;
|
||||
fields[thisField] = instance.fields[thisField];
|
||||
}
|
||||
fields[requiredFields[requiredFields.length - 1]] = userInput;
|
||||
return fields;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.loading) {
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
|
||||
if (this.state.protocolsLoading) {
|
||||
return (
|
||||
<div className="mx_RoomDirectory">
|
||||
<SimpleRoomHeader title="Directory" />
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||
let content;
|
||||
if (this.state.loading) {
|
||||
content = <div className="mx_RoomDirectory">
|
||||
<Loader />
|
||||
</div>;
|
||||
} else {
|
||||
const rows = this.getRows();
|
||||
// we still show the scrollpanel, at least for now, because
|
||||
// otherwise we don't fetch more because we don't get a fill
|
||||
// request from the scrollpanel because there isn't one
|
||||
let scrollpanel_content;
|
||||
if (rows.length == 0) {
|
||||
scrollpanel_content = <i>No rooms to show</i>;
|
||||
} else {
|
||||
scrollpanel_content = <table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows() }
|
||||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||
content = <ScrollPanel ref={this.collectScrollPanel}
|
||||
className="mx_RoomDirectory_tableWrapper"
|
||||
onFillRequest={ this.onFillRequest }
|
||||
stickyBottom={false}
|
||||
startAtBottom={false}
|
||||
onResize={function(){}}
|
||||
>
|
||||
{ scrollpanel_content }
|
||||
</ScrollPanel>;
|
||||
}
|
||||
|
||||
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
|
||||
let instance_expected_field_type;
|
||||
if (
|
||||
protocolName &&
|
||||
this.protocols &&
|
||||
this.protocols[protocolName] &&
|
||||
this.protocols[protocolName].location_fields.length > 0 &&
|
||||
this.protocols[protocolName].field_types
|
||||
) {
|
||||
const last_field = this.protocols[protocolName].location_fields.slice(-1)[0];
|
||||
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
|
||||
}
|
||||
|
||||
|
||||
let placeholder = 'Search for a room';
|
||||
if (!this.state.instanceId) {
|
||||
placeholder = '#example:' + this.state.roomServer;
|
||||
} else if (instance_expected_field_type) {
|
||||
placeholder = instance_expected_field_type.placeholder;
|
||||
}
|
||||
|
||||
let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type);
|
||||
if (protocolName) {
|
||||
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
|
||||
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) {
|
||||
showJoinButton = false;
|
||||
}
|
||||
}
|
||||
|
||||
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
|
||||
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
|
||||
return (
|
||||
<div className="mx_RoomDirectory">
|
||||
<RoomHeader simpleHeader="Directory" />
|
||||
<SimpleRoomHeader title="Directory" />
|
||||
<div className="mx_RoomDirectory_list">
|
||||
<input ref="roomAlias" placeholder="Join a room (e.g. #foo:domain.com)" className="mx_RoomDirectory_input" size="64" onKeyUp={ this.onKeyUp }/>
|
||||
<GeminiScrollbar className="mx_RoomDirectory_tableWrapper">
|
||||
<table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows(this.state.roomAlias) }
|
||||
</tbody>
|
||||
</table>
|
||||
</GeminiScrollbar>
|
||||
<div className="mx_RoomDirectory_listheader">
|
||||
<DirectorySearchBox
|
||||
className="mx_RoomDirectory_searchbox"
|
||||
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick}
|
||||
placeholder={placeholder} showJoinButton={showJoinButton}
|
||||
/>
|
||||
<NetworkDropdown config={this.props.config} protocols={this.protocols} onOptionChange={this.onOptionChange} />
|
||||
</div>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
||||
// but works with the objects we get from the public room list
|
||||
function get_display_alias_for_room(room) {
|
||||
return room.canonical_alias || (room.aliases ? room.aliases[0] : "");
|
||||
}
|
||||
|
||||
@@ -17,15 +17,22 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
var classNames = require('classnames');
|
||||
var DropTarget = require('react-dnd').DropTarget;
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var Unread = require('matrix-react-sdk/lib/Unread');
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
|
||||
var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
|
||||
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
|
||||
|
||||
// turn this on for drop & drag console debugging galore
|
||||
var debug = false;
|
||||
|
||||
const TRUNCATE_AT = 10;
|
||||
|
||||
var roomListTarget = {
|
||||
canDrop: function() {
|
||||
return true;
|
||||
@@ -61,26 +68,26 @@ var RoomSubList = React.createClass({
|
||||
label: React.PropTypes.string.isRequired,
|
||||
tagName: React.PropTypes.string,
|
||||
editable: React.PropTypes.bool,
|
||||
|
||||
order: React.PropTypes.string.isRequired,
|
||||
selectedRoom: React.PropTypes.string.isRequired,
|
||||
|
||||
// undefined if no room is selected (eg we are showing settings)
|
||||
selectedRoom: React.PropTypes.string,
|
||||
|
||||
startAsHidden: React.PropTypes.bool,
|
||||
showSpinner: React.PropTypes.bool, // true to show a spinner if 0 elements when expanded
|
||||
|
||||
// TODO: Fix the name of this. This is too easily confused with the
|
||||
// "hidden" state which is the expanded (or not) view of the list of rooms.
|
||||
// What this prop *really* does is control whether the room name is displayed
|
||||
// so it should be named as such.
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed?
|
||||
onHeaderClick: React.PropTypes.func,
|
||||
alwaysShowHeader: React.PropTypes.bool,
|
||||
incomingCall: React.PropTypes.object,
|
||||
onShowMoreRooms: React.PropTypes.func
|
||||
onShowMoreRooms: React.PropTypes.func,
|
||||
searchFilter: React.PropTypes.string,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hidden: this.props.startAsHidden || false,
|
||||
truncateAt: 20,
|
||||
truncateAt: TRUNCATE_AT,
|
||||
sortedList: [],
|
||||
};
|
||||
},
|
||||
@@ -93,26 +100,50 @@ var RoomSubList = React.createClass({
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.sortList(this.props.list, this.props.order);
|
||||
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
// order the room list appropriately before we re-render
|
||||
//if (debug) console.log("received new props, list = " + newProps.list);
|
||||
this.sortList(newProps.list, newProps.order);
|
||||
this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order);
|
||||
},
|
||||
|
||||
applySearchFilter: function(list, filter) {
|
||||
if (filter === "") return list;
|
||||
return list.filter((room) => {
|
||||
return room.name && room.name.toLowerCase().indexOf(filter.toLowerCase()) >= 0
|
||||
});
|
||||
},
|
||||
|
||||
// The header is collapsable if it is hidden or not stuck
|
||||
// The dataset elements are added in the RoomList _initAndPositionStickyHeaders method
|
||||
isCollapsableOnClick: function() {
|
||||
var stuck = this.refs.header.dataset.stuck;
|
||||
if (this.state.hidden || stuck === undefined || stuck === "none") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
var isHidden = !this.state.hidden;
|
||||
this.setState({ hidden : isHidden });
|
||||
if (this.isCollapsableOnClick()) {
|
||||
// The header isCollapsable, so the click is to be interpreted as collapse and truncation logic
|
||||
var isHidden = !this.state.hidden;
|
||||
this.setState({ hidden : isHidden });
|
||||
|
||||
if (isHidden) {
|
||||
// as good a way as any to reset the truncate state
|
||||
this.setState({ truncateAt : TRUNCATE_AT });
|
||||
}
|
||||
|
||||
if (isHidden) {
|
||||
// as good a way as any to reset the truncate state
|
||||
this.setState({ truncateAt : 20 });
|
||||
this.props.onShowMoreRooms();
|
||||
this.props.onHeaderClick(isHidden);
|
||||
} else {
|
||||
// The header is stuck, so the click is to be interpreted as a scroll to the header
|
||||
this.props.onHeaderClick(this.state.hidden, this.refs.header.dataset.originalPosition);
|
||||
}
|
||||
|
||||
this.props.onHeaderClick(isHidden);
|
||||
},
|
||||
|
||||
tsOfNewestEvent: function(room) {
|
||||
@@ -142,11 +173,26 @@ var RoomSubList = React.createClass({
|
||||
return this.tsOfNewestEvent(roomB) - this.tsOfNewestEvent(roomA);
|
||||
},
|
||||
|
||||
lexicographicalComparator: function(roomA, roomB) {
|
||||
return roomA.name > roomB.name ? 1 : -1;
|
||||
},
|
||||
|
||||
// Generates the manual comparator using the given list
|
||||
manualComparator: function(roomA, roomB) {
|
||||
if (!roomA.tags[this.props.tagName] || !roomB.tags[this.props.tagName]) return 0;
|
||||
|
||||
// Make sure the room tag has an order element, if not set it to be the bottom
|
||||
var a = roomA.tags[this.props.tagName].order;
|
||||
var b = roomB.tags[this.props.tagName].order;
|
||||
return a == b ? this.recentsComparator(roomA, roomB) : ( a > b ? 1 : -1);
|
||||
|
||||
// Order undefined room tag orders to the bottom
|
||||
if (a === undefined && b !== undefined) {
|
||||
return 1;
|
||||
} else if (a !== undefined && b === undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return a == b ? this.lexicographicalComparator(roomA, roomB) : ( a > b ? 1 : -1);
|
||||
},
|
||||
|
||||
sortList: function(list, order) {
|
||||
@@ -157,10 +203,59 @@ var RoomSubList = React.createClass({
|
||||
if (order === "manual") comparator = this.manualComparator;
|
||||
if (order === "recent") comparator = this.recentsComparator;
|
||||
|
||||
// Fix undefined orders here, and make sure the backend gets updated as well
|
||||
this._fixUndefinedOrder(list);
|
||||
|
||||
//if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
|
||||
this.setState({ sortedList: list.sort(comparator) });
|
||||
},
|
||||
|
||||
_shouldShowNotifBadge: function(roomNotifState) {
|
||||
const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
|
||||
return showBadgeInStates.indexOf(roomNotifState) > -1;
|
||||
},
|
||||
|
||||
_shouldShowMentionBadge: function(roomNotifState) {
|
||||
return roomNotifState != RoomNotifs.MUTE;
|
||||
},
|
||||
|
||||
/**
|
||||
* Total up all the notification counts from the rooms
|
||||
*
|
||||
* @param {Number} If supplied will only total notifications for rooms outside the truncation number
|
||||
* @returns {Array} The array takes the form [total, highlight] where highlight is a bool
|
||||
*/
|
||||
roomNotificationCount: function(truncateAt) {
|
||||
var self = this;
|
||||
|
||||
return this.props.list.reduce(function(result, room, index) {
|
||||
if (truncateAt === undefined || index >= truncateAt) {
|
||||
var roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId);
|
||||
var highlight = room.getUnreadNotificationCount('highlight') > 0 || self.props.label === 'Invites';
|
||||
var notificationCount = room.getUnreadNotificationCount();
|
||||
|
||||
const notifBadges = notificationCount > 0 && self._shouldShowNotifBadge(roomNotifState);
|
||||
const mentionBadges = highlight && self._shouldShowMentionBadge(roomNotifState);
|
||||
const badges = notifBadges || mentionBadges;
|
||||
|
||||
if (badges) {
|
||||
result[0] += notificationCount;
|
||||
if (highlight) {
|
||||
result[1] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, [0, false]);
|
||||
},
|
||||
|
||||
_updateSubListCount: function() {
|
||||
// Force an update by setting the state to the current state
|
||||
// Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate()
|
||||
// method is honoured
|
||||
this.setState(this.state);
|
||||
},
|
||||
|
||||
moveRoomTile: function(room, atIndex) {
|
||||
if (debug) console.log("moveRoomTile: id " + room.roomId + ", atIndex " + atIndex);
|
||||
//console.log("moveRoomTile before: " + JSON.stringify(this.state.rooms));
|
||||
@@ -254,12 +349,12 @@ var RoomSubList = React.createClass({
|
||||
|
||||
makeRoomTiles: function() {
|
||||
var self = this;
|
||||
var RoomTile = sdk.getComponent("rooms.RoomTile");
|
||||
var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile");
|
||||
return this.state.sortedList.map(function(room) {
|
||||
var selected = room.roomId == self.props.selectedRoom;
|
||||
// XXX: is it evil to pass in self as a prop to RoomTile?
|
||||
return (
|
||||
<RoomTile
|
||||
<DNDRoomTile
|
||||
room={ room }
|
||||
roomSubList={ self }
|
||||
key={ room.roomId }
|
||||
@@ -268,33 +363,98 @@ var RoomSubList = React.createClass({
|
||||
unread={ Unread.doesRoomHaveUnreadMessages(room) }
|
||||
highlight={ room.getUnreadNotificationCount('highlight') > 0 || self.props.label === 'Invites' }
|
||||
isInvite={ self.props.label === 'Invites' }
|
||||
incomingCall={ self.props.incomingCall && (self.props.incomingCall.roomId === room.roomId) ? self.props.incomingCall : null } />
|
||||
refreshSubList={ self._updateSubListCount }
|
||||
incomingCall={ null } />
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
_getHeaderJsx: function() {
|
||||
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
var subListNotifications = this.roomNotificationCount();
|
||||
var subListNotifCount = subListNotifications[0];
|
||||
var subListNotifHighlight = subListNotifications[1];
|
||||
|
||||
var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
|
||||
|
||||
var chevronClasses = classNames({
|
||||
'mx_RoomSubList_chevron': true,
|
||||
'mx_RoomSubList_chevronRight': this.state.hidden,
|
||||
'mx_RoomSubList_chevronDown': !this.state.hidden,
|
||||
});
|
||||
|
||||
var badgeClasses = classNames({
|
||||
'mx_RoomSubList_badge': true,
|
||||
'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
|
||||
});
|
||||
|
||||
var badge;
|
||||
if (subListNotifCount > 0) {
|
||||
badge = <div className={badgeClasses}>{ FormattingUtils.formatCount(subListNotifCount) }</div>;
|
||||
}
|
||||
|
||||
// When collapsed, allow a long hover on the header to show user
|
||||
// the full tag name and room count
|
||||
var title;
|
||||
if (this.props.collapsed) {
|
||||
title = this.props.label;
|
||||
if (roomCount !== '') {
|
||||
title += " [" + roomCount + "]";
|
||||
}
|
||||
}
|
||||
|
||||
var incomingCall;
|
||||
if (this.props.incomingCall) {
|
||||
var self = this;
|
||||
// Check if the incoming call is for this section
|
||||
var incomingCallRoom = this.state.sortedList.filter(function(room) {
|
||||
return self.props.incomingCall.roomId === room.roomId;
|
||||
});
|
||||
|
||||
if (incomingCallRoom.length === 1) {
|
||||
var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
|
||||
incomingCall = <IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={ this.props.incomingCall }/>;
|
||||
}
|
||||
}
|
||||
|
||||
var tabindex = this.props.searchFilter === "" ? "0" : "-1";
|
||||
|
||||
return (
|
||||
<h2 onClick={ this.onClick } className="mx_RoomSubList_label">
|
||||
{ this.props.collapsed ? '' : this.props.label }
|
||||
<TintableSvg className="mx_RoomSubList_chevron"
|
||||
src={ this.state.hidden ? "img/list-close.svg" : "img/list-open.svg" }
|
||||
width="10" height="10" />
|
||||
</h2>
|
||||
<div className="mx_RoomSubList_labelContainer" title={ title } ref="header">
|
||||
<AccessibleButton onClick={ this.onClick } className="mx_RoomSubList_label" tabIndex={tabindex}>
|
||||
{ this.props.collapsed ? '' : this.props.label }
|
||||
<div className="mx_RoomSubList_roomCount">{ roomCount }</div>
|
||||
<div className={chevronClasses}></div>
|
||||
{ badge }
|
||||
{ incomingCall }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_createOverflowTile: function(overflowCount, totalCount) {
|
||||
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
// XXX: this is duplicated from RoomTile - factor it out
|
||||
var content = <div className="mx_RoomSubList_chevronDown"></div>;
|
||||
|
||||
var overflowNotifications = this.roomNotificationCount(TRUNCATE_AT);
|
||||
var overflowNotifCount = overflowNotifications[0];
|
||||
var overflowNotifHighlight = overflowNotifications[1];
|
||||
if (overflowNotifCount && !this.props.collapsed) {
|
||||
content = FormattingUtils.formatCount(overflowNotifCount);
|
||||
}
|
||||
|
||||
var badgeClasses = classNames({
|
||||
'mx_RoomSubList_moreBadge': true,
|
||||
'mx_RoomSubList_moreBadgeNotify': overflowNotifCount && !this.props.collapsed,
|
||||
'mx_RoomSubList_moreBadgeHighlight': overflowNotifHighlight && !this.props.collapsed,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx_RoomTile mx_RoomTile_ellipsis" onClick={this._showFullMemberList}>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
<BaseAvatar url="img/ellipsis.svg" name="..." width={24} height={24} />
|
||||
</div>
|
||||
<div className="mx_RoomTile_name">and { overflowCount } others...</div>
|
||||
</div>
|
||||
<AccessibleButton className="mx_RoomSubList_ellipsis" onClick={this._showFullMemberList}>
|
||||
<div className="mx_RoomSubList_line"></div>
|
||||
<div className="mx_RoomSubList_more">more</div>
|
||||
<div className={ badgeClasses }>{ content }</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -302,7 +462,49 @@ var RoomSubList = React.createClass({
|
||||
this.setState({
|
||||
truncateAt: -1
|
||||
});
|
||||
|
||||
this.props.onShowMoreRooms();
|
||||
this.props.onHeaderClick(false);
|
||||
},
|
||||
|
||||
// Fix any undefined order elements of a room in a manual ordered list
|
||||
// room.tag[tagname].order
|
||||
_fixUndefinedOrder: function(list) {
|
||||
if (this.props.order === "manual") {
|
||||
var order = 0.0;
|
||||
var self = this;
|
||||
|
||||
// Find the highest (lowest position) order of a room in a manual ordered list
|
||||
list.forEach(function(room) {
|
||||
if (room.tags.hasOwnProperty(self.props.tagName)) {
|
||||
if (order < room.tags[self.props.tagName].order) {
|
||||
order = room.tags[self.props.tagName].order;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fix any undefined order elements of a room in a manual ordered list
|
||||
// Do this one at a time, as each time a rooms tag data is updated the RoomList
|
||||
// gets triggered and another list is passed in. Doing it one at a time means that
|
||||
// we always correctly calculate the highest order for the list - stops multiple
|
||||
// rooms getting the same order. This is only really relevant for the first time this
|
||||
// is run with historical room tag data, after that there should only be undefined
|
||||
// in the list at a time anyway.
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) {
|
||||
MatrixClientPeg.get().setRoomTag(list[i].roomId, self.props.tagName, {order: (order + 1.0) / 2.0}).finally(function() {
|
||||
// Do any final stuff here
|
||||
}).fail(function(err) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to add tag " + self.props.tagName + " to room",
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
break;
|
||||
};
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
146
src/components/structures/SearchBox.js
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
Copyright 2015, 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');
|
||||
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'SearchBox',
|
||||
|
||||
propTypes: {
|
||||
collapsed: React.PropTypes.bool,
|
||||
onSearch: React.PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
searchTerm: "",
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
// Clear up the text field when a room is selected.
|
||||
case 'view_room':
|
||||
if (this.refs.search) {
|
||||
this._clearSearch();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onChange: function() {
|
||||
if (!this.refs.search) return;
|
||||
this.setState({ searchTerm: this.refs.search.value });
|
||||
this.onSearch();
|
||||
},
|
||||
|
||||
onSearch: new rate_limited_func(
|
||||
function() {
|
||||
this.props.onSearch(this.refs.search.value);
|
||||
},
|
||||
100
|
||||
),
|
||||
|
||||
onToggleCollapse: function(show) {
|
||||
if (show) {
|
||||
dis.dispatch({
|
||||
action: 'show_left_panel',
|
||||
});
|
||||
}
|
||||
else {
|
||||
dis.dispatch({
|
||||
action: 'hide_left_panel',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_clearSearch: function() {
|
||||
this.refs.search.value = "";
|
||||
this.onChange();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
|
||||
var collapseTabIndex = this.refs.search && this.refs.search.value !== "" ? "-1" : "0";
|
||||
|
||||
var toggleCollapse;
|
||||
if (this.props.collapsed) {
|
||||
toggleCollapse =
|
||||
<AccessibleButton className="mx_SearchBox_maximise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, true) }>
|
||||
<TintableSvg src="img/maximise.svg" width="10" height="16" alt="Expand panel"/>
|
||||
</AccessibleButton>
|
||||
}
|
||||
else {
|
||||
toggleCollapse =
|
||||
<AccessibleButton className="mx_SearchBox_minimise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, false) }>
|
||||
<TintableSvg src="img/minimise.svg" width="10" height="16" alt="Collapse panel"/>
|
||||
</AccessibleButton>
|
||||
}
|
||||
|
||||
var searchControls;
|
||||
if (!this.props.collapsed) {
|
||||
searchControls = [
|
||||
this.state.searchTerm.length > 0 ?
|
||||
<AccessibleButton key="button"
|
||||
className="mx_SearchBox_closeButton"
|
||||
onClick={ ()=>{ this._clearSearch(); } }>
|
||||
<TintableSvg
|
||||
className="mx_SearchBox_searchButton"
|
||||
src="img/icons-close.svg" width="24" height="24"
|
||||
/>
|
||||
</AccessibleButton>
|
||||
:
|
||||
<TintableSvg
|
||||
key="button"
|
||||
className="mx_SearchBox_searchButton"
|
||||
src="img/icons-search-copy.svg" width="13" height="13"
|
||||
/>,
|
||||
<input
|
||||
key="searchfield"
|
||||
type="text"
|
||||
ref="search"
|
||||
className="mx_SearchBox_search"
|
||||
value={ this.state.searchTerm }
|
||||
onChange={ this.onChange }
|
||||
placeholder="Filter room names"
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return (
|
||||
<div className="mx_SearchBox">
|
||||
{ searchControls }
|
||||
{ toggleCollapse }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -22,7 +22,8 @@ module.exports = React.createClass({
|
||||
displayName: 'ViewSource',
|
||||
|
||||
propTypes: {
|
||||
onFinished: React.PropTypes.func.isRequired
|
||||
content: React.PropTypes.object.isRequired,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@@ -45,10 +46,9 @@ module.exports = React.createClass({
|
||||
return (
|
||||
<div className="mx_ViewSource">
|
||||
<pre>
|
||||
{JSON.stringify(this.props.mxEvent.event, null, 2)}
|
||||
{JSON.stringify(this.props.content, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
203
src/components/views/context_menus/MessageContextMenu.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
Copyright 2015, 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var sdk = require('matrix-react-sdk');
|
||||
var Modal = require('matrix-react-sdk/lib/Modal');
|
||||
var Resend = require("matrix-react-sdk/lib/Resend");
|
||||
import * as UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MessageContextMenu',
|
||||
|
||||
propTypes: {
|
||||
/* the MatrixEvent associated with the context menu */
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
|
||||
/* an optional EventTileOps implementation that can be used to unhide preview widgets */
|
||||
eventTileOps: React.PropTypes.object,
|
||||
|
||||
/* callback called when the menu is dismissed */
|
||||
onFinished: React.PropTypes.func,
|
||||
},
|
||||
|
||||
onResendClick: function() {
|
||||
Resend.resend(this.props.mxEvent);
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onViewSourceClick: function() {
|
||||
var ViewSource = sdk.getComponent('structures.ViewSource');
|
||||
Modal.createDialog(ViewSource, {
|
||||
content: this.props.mxEvent.event,
|
||||
}, 'mx_Dialog_viewsource');
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onViewClearSourceClick: function() {
|
||||
const ViewSource = sdk.getComponent('structures.ViewSource');
|
||||
Modal.createDialog(ViewSource, {
|
||||
// FIXME: _clearEvent is private
|
||||
content: this.props.mxEvent._clearEvent,
|
||||
}, 'mx_Dialog_viewsource');
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onRedactClick: function() {
|
||||
MatrixClientPeg.get().redactEvent(
|
||||
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
|
||||
).done(function() {
|
||||
// message should disappear by itself
|
||||
}, function(e) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
// display error message stating you couldn't delete this.
|
||||
var code = e.errcode || e.statusCode;
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: "You cannot delete this message. (" + code + ")"
|
||||
});
|
||||
});
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onCancelSendClick: function() {
|
||||
Resend.removeFromQueue(this.props.mxEvent);
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
closeMenu: function() {
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onUnhidePreviewClick: function() {
|
||||
if (this.props.eventTileOps) {
|
||||
this.props.eventTileOps.unhideWidget();
|
||||
}
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onQuoteClick: function () {
|
||||
console.log(this.props.mxEvent);
|
||||
dis.dispatch({
|
||||
action: 'quote',
|
||||
event: this.props.mxEvent,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var eventStatus = this.props.mxEvent.status;
|
||||
var resendButton;
|
||||
var viewSourceButton;
|
||||
var viewClearSourceButton;
|
||||
var redactButton;
|
||||
var cancelButton;
|
||||
var permalinkButton;
|
||||
var unhidePreviewButton;
|
||||
var externalURLButton;
|
||||
|
||||
if (eventStatus === 'not_sent') {
|
||||
resendButton = (
|
||||
<div className="mx_MessageContextMenu_field" onClick={this.onResendClick}>
|
||||
Resend
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!eventStatus) { // sent
|
||||
redactButton = (
|
||||
<div className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
|
||||
Redact
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventStatus === "queued" || eventStatus === "not_sent") {
|
||||
cancelButton = (
|
||||
<div className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}>
|
||||
Cancel Sending
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
viewSourceButton = (
|
||||
<div className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
|
||||
View Source
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.mxEvent.getType() !== this.props.mxEvent.getWireType()) {
|
||||
viewClearSourceButton = (
|
||||
<div className="mx_MessageContextMenu_field" onClick={this.onViewClearSourceClick}>
|
||||
View Decrypted Source
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.eventTileOps) {
|
||||
if (this.props.eventTileOps.isWidgetHidden()) {
|
||||
unhidePreviewButton = (
|
||||
<div className="mx_MessageContextMenu_field" onClick={this.onUnhidePreviewClick}>
|
||||
Unhide Preview
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
|
||||
permalinkButton = (
|
||||
<div className="mx_MessageContextMenu_field">
|
||||
<a href={ "https://matrix.to/#/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }
|
||||
target="_blank" rel="noopener" onClick={ this.closeMenu }>Permalink</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
const quoteButton = (
|
||||
<div className="mx_MessageContextMenu_field" onClick={this.onQuoteClick}>
|
||||
Quote
|
||||
</div>
|
||||
);
|
||||
|
||||
// Bridges can provide a 'external_url' to link back to the source.
|
||||
if( typeof(this.props.mxEvent.event.content.external_url) === "string") {
|
||||
externalURLButton = (
|
||||
<div className="mx_MessageContextMenu_field">
|
||||
<a href={ this.props.mxEvent.event.content.external_url }
|
||||
rel="noopener" target="_blank" onClick={ this.closeMenu }>Source URL</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resendButton}
|
||||
{redactButton}
|
||||
{cancelButton}
|
||||
{viewSourceButton}
|
||||
{viewClearSourceButton}
|
||||
{unhidePreviewButton}
|
||||
{permalinkButton}
|
||||
{UserSettingsStore.isFeatureEnabled('rich_text_editor') ? quoteButton : null}
|
||||
{externalURLButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright 2015, 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var q = require("q");
|
||||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'NotificationStateContextMenu',
|
||||
|
||||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
/* callback called when the menu is dismissed */
|
||||
onFinished: React.PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
roomNotifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._unmounted = false;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._unmounted = true;
|
||||
},
|
||||
|
||||
_save: function(newState) {
|
||||
const oldState = this.state.roomNotifState;
|
||||
const roomId = this.props.room.roomId;
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
if (cli.isGuest()) return;
|
||||
|
||||
this.setState({
|
||||
roomNotifState: newState,
|
||||
});
|
||||
RoomNotifs.setRoomNotifsState(this.props.room.roomId, newState).done(() => {
|
||||
// delay slightly so that the user can see their state change
|
||||
// before closing the menu
|
||||
return q.delay(500).then(() => {
|
||||
if (this._unmounted) return;
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
};
|
||||
});
|
||||
}, (error) => {
|
||||
// TODO: some form of error notification to the user
|
||||
// to inform them that their state change failed.
|
||||
// For now we at least set the state back
|
||||
if (this._unmounted) return;
|
||||
this.setState({
|
||||
roomNotifState: oldState,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onClickAlertMe: function() {
|
||||
this._save(RoomNotifs.ALL_MESSAGES_LOUD);
|
||||
},
|
||||
|
||||
_onClickAllNotifs: function() {
|
||||
this._save(RoomNotifs.ALL_MESSAGES);
|
||||
},
|
||||
|
||||
_onClickMentions: function() {
|
||||
this._save(RoomNotifs.MENTIONS_ONLY);
|
||||
},
|
||||
|
||||
_onClickMute: function() {
|
||||
this._save(RoomNotifs.MUTE);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var alertMeClasses = classNames({
|
||||
'mx_NotificationStateContextMenu_field': true,
|
||||
'mx_NotificationStateContextMenu_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES_LOUD,
|
||||
});
|
||||
|
||||
var allNotifsClasses = classNames({
|
||||
'mx_NotificationStateContextMenu_field': true,
|
||||
'mx_NotificationStateContextMenu_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES,
|
||||
});
|
||||
|
||||
var mentionsClasses = classNames({
|
||||
'mx_NotificationStateContextMenu_field': true,
|
||||
'mx_NotificationStateContextMenu_fieldSet': this.state.roomNotifState == RoomNotifs.MENTIONS_ONLY,
|
||||
});
|
||||
|
||||
var muteNotifsClasses = classNames({
|
||||
'mx_NotificationStateContextMenu_field': true,
|
||||
'mx_NotificationStateContextMenu_fieldSet': this.state.roomNotifState == RoomNotifs.MUTE,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mx_NotificationStateContextMenu_picker" >
|
||||
<img src="img/notif-slider.svg" width="20" height="107" />
|
||||
</div>
|
||||
<div className={ alertMeClasses } onClick={this._onClickAlertMe} >
|
||||
<img className="mx_NotificationStateContextMenu_activeIcon" src="img/notif-active.svg" width="12" height="12" />
|
||||
<img className="mx_NotificationStateContextMenu_icon mx_filterFlipColor" src="img/icon-context-mute-off-copy.svg" width="16" height="12" />
|
||||
All messages (loud)
|
||||
</div>
|
||||
<div className={ allNotifsClasses } onClick={this._onClickAllNotifs} >
|
||||
<img className="mx_NotificationStateContextMenu_activeIcon" src="img/notif-active.svg" width="12" height="12" />
|
||||
<img className="mx_NotificationStateContextMenu_icon mx_filterFlipColor" src="img/icon-context-mute-off.svg" width="16" height="12" />
|
||||
All messages
|
||||
</div>
|
||||
<div className={ mentionsClasses } onClick={this._onClickMentions} >
|
||||
<img className="mx_NotificationStateContextMenu_activeIcon" src="img/notif-active.svg" width="12" height="12" />
|
||||
<img className="mx_NotificationStateContextMenu_icon mx_filterFlipColor" src="img/icon-context-mute-mentions.svg" width="16" height="12" />
|
||||
Mentions only
|
||||
</div>
|
||||
<div className={ muteNotifsClasses } onClick={this._onClickMute} >
|
||||
<img className="mx_NotificationStateContextMenu_activeIcon" src="img/notif-active.svg" width="12" height="12" />
|
||||
<img className="mx_NotificationStateContextMenu_icon mx_filterFlipColor" src="img/icon-context-mute.svg" width="16" height="12" />
|
||||
Mute
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
253
src/components/views/context_menus/RoomTagContextMenu.js
Normal file
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
Copyright 2015, 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var q = require("q");
|
||||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var DMRoomMap = require('matrix-react-sdk/lib/utils/DMRoomMap');
|
||||
var Rooms = require('matrix-react-sdk/lib/Rooms');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomTagContextMenu',
|
||||
|
||||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
/* callback called when the menu is dismissed */
|
||||
onFinished: React.PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||
return {
|
||||
isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"),
|
||||
isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"),
|
||||
isDirectMessage: Boolean(dmRoomMap.getUserIdForRoomId(this.props.room.roomId)),
|
||||
};
|
||||
},
|
||||
|
||||
_toggleTag: function(tagNameOn, tagNameOff) {
|
||||
var self = this;
|
||||
const roomId = this.props.room.roomId;
|
||||
var cli = MatrixClientPeg.get();
|
||||
if (!cli.isGuest()) {
|
||||
q.delay(500).then(function() {
|
||||
if (tagNameOff !== null && tagNameOff !== undefined) {
|
||||
cli.deleteRoomTag(roomId, tagNameOff).finally(function() {
|
||||
// Close the context menu
|
||||
if (self.props.onFinished) {
|
||||
self.props.onFinished();
|
||||
};
|
||||
}).fail(function(err) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to remove tag " + tagNameOff + " from room",
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (tagNameOn !== null && tagNameOn !== undefined) {
|
||||
// If the tag ordering meta data is required, it is added by
|
||||
// the RoomSubList when it sorts its rooms
|
||||
cli.setRoomTag(roomId, tagNameOn, {}).finally(function() {
|
||||
// Close the context menu
|
||||
if (self.props.onFinished) {
|
||||
self.props.onFinished();
|
||||
};
|
||||
}).fail(function(err) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to add tag " + tagNameOn + " to room",
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onClickFavourite: function() {
|
||||
// Tag room as 'Favourite'
|
||||
if (!this.state.isFavourite && this.state.isLowPriority) {
|
||||
this.setState({
|
||||
isFavourite: true,
|
||||
isLowPriority: false,
|
||||
});
|
||||
this._toggleTag("m.favourite", "m.lowpriority");
|
||||
} else if (this.state.isFavourite) {
|
||||
this.setState({isFavourite: false});
|
||||
this._toggleTag(null, "m.favourite");
|
||||
} else if (!this.state.isFavourite) {
|
||||
this.setState({isFavourite: true});
|
||||
this._toggleTag("m.favourite");
|
||||
}
|
||||
},
|
||||
|
||||
_onClickLowPriority: function() {
|
||||
// Tag room as 'Low Priority'
|
||||
if (!this.state.isLowPriority && this.state.isFavourite) {
|
||||
this.setState({
|
||||
isFavourite: false,
|
||||
isLowPriority: true,
|
||||
});
|
||||
this._toggleTag("m.lowpriority", "m.favourite");
|
||||
} else if (this.state.isLowPriority) {
|
||||
this.setState({isLowPriority: false});
|
||||
this._toggleTag(null, "m.lowpriority");
|
||||
} else if (!this.state.isLowPriority) {
|
||||
this.setState({isLowPriority: true});
|
||||
this._toggleTag("m.lowpriority");
|
||||
}
|
||||
},
|
||||
|
||||
_onClickDM: function() {
|
||||
const newIsDirectMessage = !this.state.isDirectMessage;
|
||||
this.setState({
|
||||
isDirectMessage: newIsDirectMessage,
|
||||
});
|
||||
|
||||
if (MatrixClientPeg.get().isGuest()) return;
|
||||
|
||||
let newTarget;
|
||||
if (newIsDirectMessage) {
|
||||
const guessedTarget = Rooms.guessDMRoomTarget(
|
||||
this.props.room,
|
||||
this.props.room.getMember(MatrixClientPeg.get().credentials.userId),
|
||||
);
|
||||
newTarget = guessedTarget.userId;
|
||||
} else {
|
||||
newTarget = null;
|
||||
}
|
||||
|
||||
// give some time for the user to see the icon change first, since
|
||||
// this will hide the context menu once it completes
|
||||
q.delay(500).done(() => {
|
||||
return Rooms.setDMRoom(this.props.room.roomId, newTarget).finally(() => {
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
};
|
||||
}, (err) => {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to set Direct Message status of room",
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onClickLeave: function() {
|
||||
// Leave room
|
||||
dis.dispatch({
|
||||
action: 'leave_room',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
};
|
||||
},
|
||||
|
||||
_onClickForget: function() {
|
||||
// FIXME: duplicated with RoomSettings (and dead code in RoomView)
|
||||
MatrixClientPeg.get().forget(this.props.room.roomId).done(function() {
|
||||
dis.dispatch({ action: 'view_next_room' });
|
||||
}, function(err) {
|
||||
var errCode = err.errcode || "unknown error code";
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: `Failed to forget room (${errCode})`
|
||||
});
|
||||
});
|
||||
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
const myMember = this.props.room.getMember(myUserId);
|
||||
|
||||
const favouriteClasses = classNames({
|
||||
'mx_RoomTagContextMenu_field': true,
|
||||
'mx_RoomTagContextMenu_fieldSet': this.state.isFavourite,
|
||||
'mx_RoomTagContextMenu_fieldDisabled': false,
|
||||
});
|
||||
|
||||
const lowPriorityClasses = classNames({
|
||||
'mx_RoomTagContextMenu_field': true,
|
||||
'mx_RoomTagContextMenu_fieldSet': this.state.isLowPriority,
|
||||
'mx_RoomTagContextMenu_fieldDisabled': false,
|
||||
});
|
||||
|
||||
const leaveClasses = classNames({
|
||||
'mx_RoomTagContextMenu_field': true,
|
||||
'mx_RoomTagContextMenu_fieldSet': false,
|
||||
'mx_RoomTagContextMenu_fieldDisabled': false,
|
||||
});
|
||||
|
||||
const dmClasses = classNames({
|
||||
'mx_RoomTagContextMenu_field': true,
|
||||
'mx_RoomTagContextMenu_fieldSet': this.state.isDirectMessage,
|
||||
'mx_RoomTagContextMenu_fieldDisabled': false,
|
||||
});
|
||||
|
||||
if (myMember && myMember.membership === "leave") {
|
||||
return (
|
||||
<div>
|
||||
<div className={ leaveClasses } onClick={ this._onClickForget } >
|
||||
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_delete.svg" width="15" height="15" />
|
||||
Forget
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={ favouriteClasses } onClick={this._onClickFavourite} >
|
||||
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_fave.svg" width="15" height="15" />
|
||||
<img className="mx_RoomTagContextMenu_icon_set" src="img/icon_context_fave_on.svg" width="15" height="15" />
|
||||
Favourite
|
||||
</div>
|
||||
<div className={ lowPriorityClasses } onClick={this._onClickLowPriority} >
|
||||
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_low.svg" width="15" height="15" />
|
||||
<img className="mx_RoomTagContextMenu_icon_set" src="img/icon_context_low_on.svg" width="15" height="15" />
|
||||
Low Priority
|
||||
</div>
|
||||
<div className={ dmClasses } onClick={this._onClickDM} >
|
||||
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_person.svg" width="15" height="15" />
|
||||
<img className="mx_RoomTagContextMenu_icon_set" src="img/icon_context_person_on.svg" width="15" height="15" />
|
||||
Direct Chat
|
||||
</div>
|
||||
<hr className="mx_RoomTagContextMenu_separator" />
|
||||
<div className={ leaveClasses } onClick={(myMember && myMember.membership === "join") ? this._onClickLeave : null} >
|
||||
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_delete.svg" width="15" height="15" />
|
||||
Leave
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
127
src/components/views/dialogs/BugReportDialog.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2017 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 React from 'react';
|
||||
import sdk from 'matrix-react-sdk';
|
||||
import rageshake from '../../../vector/rageshake';
|
||||
|
||||
export default class BugReportDialog extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
sendLogs: true,
|
||||
busy: false,
|
||||
err: null,
|
||||
text: "",
|
||||
};
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onTextChange = this._onTextChange.bind(this);
|
||||
this._onSendLogsChange = this._onSendLogsChange.bind(this);
|
||||
}
|
||||
|
||||
_onCancel(ev) {
|
||||
this.props.onFinished(false);
|
||||
}
|
||||
|
||||
_onSubmit(ev) {
|
||||
const sendLogs = this.state.sendLogs;
|
||||
const userText = this.state.text;
|
||||
if (!sendLogs && userText.trim().length === 0) {
|
||||
this.setState({
|
||||
err: "Please describe the bug and/or send logs.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({ busy: true, err: null });
|
||||
rageshake.sendBugReport(userText, sendLogs).then(() => {
|
||||
this.setState({ busy: false });
|
||||
this.props.onFinished(false);
|
||||
}, (err) => {
|
||||
this.setState({ busy: false, err: `Failed: ${err.message}` });
|
||||
});
|
||||
}
|
||||
|
||||
_onTextChange(ev) {
|
||||
this.setState({ text: ev.target.value });
|
||||
}
|
||||
|
||||
_onSendLogsChange(ev) {
|
||||
this.setState({ sendLogs: ev.target.checked });
|
||||
}
|
||||
|
||||
render() {
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
|
||||
let error = null;
|
||||
if (this.state.err) {
|
||||
error = <div className="error">
|
||||
{this.state.err}
|
||||
</div>;
|
||||
}
|
||||
|
||||
const okLabel = this.state.busy ? <Loader /> : 'Send';
|
||||
|
||||
let cancelButton = null;
|
||||
if (!this.state.busy) {
|
||||
cancelButton = <button onClick={this._onCancel}>
|
||||
Cancel
|
||||
</button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_BugReportDialog">
|
||||
<div className="mx_Dialog_title">
|
||||
Report a bug
|
||||
</div>
|
||||
<div className="mx_Dialog_content">
|
||||
<p>Please describe the bug. What did you do?
|
||||
What did you expect to happen?
|
||||
What actually happened?</p>
|
||||
<textarea
|
||||
className="mx_BugReportDialog_input"
|
||||
rows={5}
|
||||
onChange={this._onTextChange}
|
||||
value={this.state.text}
|
||||
placeholder="Describe your problem here."
|
||||
/>
|
||||
<p>In order to diagnose problems, logs from this client will be sent with
|
||||
this bug report.
|
||||
If you would prefer to only send the text above, please untick:</p>
|
||||
<input type="checkbox" checked={this.state.sendLogs}
|
||||
onChange={this._onSendLogsChange} id="mx_BugReportDialog_logs"/>
|
||||
<label htmlFor="mx_BugReportDialog_logs">Send logs</label>
|
||||
{error}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button
|
||||
className="mx_Dialog_primary danger"
|
||||
onClick={this._onSubmit}
|
||||
autoFocus={true}
|
||||
>
|
||||
{okLabel}
|
||||
</button>
|
||||
|
||||
{cancelButton}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BugReportDialog.propTypes = {
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
};
|
||||
93
src/components/views/dialogs/ChangelogDialog.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
|
||||
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 React from 'react';
|
||||
import sdk from 'matrix-react-sdk';
|
||||
import request from 'browser-request';
|
||||
|
||||
const REPOS = ['vector-im/vector-web', 'matrix-org/matrix-react-sdk', 'matrix-org/matrix-js-sdk'];
|
||||
|
||||
export default class ChangelogDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const version = this.props.newVersion.split('-');
|
||||
const version2 = this.props.version.split('-');
|
||||
if(version == null || version2 == null) return;
|
||||
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
|
||||
for(let i=0; i<REPOS.length; i++) {
|
||||
const oldVersion = version2[2*i];
|
||||
const newVersion = version[2*i];
|
||||
request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => {
|
||||
if(body == null) return;
|
||||
this.setState({[REPOS[i]]: JSON.parse(body).commits});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_elementsForCommit(commit) {
|
||||
return (
|
||||
<li key={commit.sha} className="mx_ChangelogDialog_li">
|
||||
<a href={commit.html_url} target="_blank" rel="noopener">
|
||||
{commit.commit.message}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||
|
||||
const logs = REPOS.map(repo => {
|
||||
if (this.state[repo] == null) return <Spinner key={repo} />;
|
||||
return (
|
||||
<div key={repo}>
|
||||
<h2>{repo}</h2>
|
||||
<ul>
|
||||
{this.state[repo].map(this._elementsForCommit)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
const content = (
|
||||
<div className="mx_ChangelogDialog_content">
|
||||
{this.props.version == null || this.props.newVersion == null ? <h2>Unavailable</h2> : logs}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<QuestionDialog
|
||||
title="Changelog"
|
||||
description={content}
|
||||
button="Update"
|
||||
onFinished={this.props.onFinished}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ChangelogDialog.propTypes = {
|
||||
version: React.PropTypes.string.isRequired,
|
||||
newVersion: React.PropTypes.string.isRequired,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
};
|
||||
240
src/components/views/directory/NetworkDropdown.js
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
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 React from 'react';
|
||||
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
|
||||
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
|
||||
|
||||
const DEFAULT_ICON_URL = "img/network-matrix.svg";
|
||||
|
||||
export default class NetworkDropdown extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.dropdownRootElement = null;
|
||||
this.ignoreEvent = null;
|
||||
|
||||
this.onInputClick = this.onInputClick.bind(this);
|
||||
this.onRootClick = this.onRootClick.bind(this);
|
||||
this.onDocumentClick = this.onDocumentClick.bind(this);
|
||||
this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
|
||||
this.onInputKeyUp = this.onInputKeyUp.bind(this);
|
||||
this.collectRoot = this.collectRoot.bind(this);
|
||||
this.collectInputTextBox = this.collectInputTextBox.bind(this);
|
||||
|
||||
this.inputTextBox = null;
|
||||
|
||||
const server = MatrixClientPeg.getHomeServerName();
|
||||
this.state = {
|
||||
expanded: false,
|
||||
selectedServer: server,
|
||||
selectedInstance: null,
|
||||
includeAllNetworks: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// Listen for all clicks on the document so we can close the
|
||||
// menu when the user clicks somewhere else
|
||||
document.addEventListener('click', this.onDocumentClick, false);
|
||||
|
||||
// fire this now so the defaults can be set up
|
||||
this.props.onOptionChange(this.state.selectedServer, this.state.selectedInstance, this.state.includeAllNetworks);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.onDocumentClick, false);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.expanded && this.inputTextBox) {
|
||||
this.inputTextBox.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onDocumentClick(ev) {
|
||||
// Close the dropdown if the user clicks anywhere that isn't
|
||||
// within our root element
|
||||
if (ev !== this.ignoreEvent) {
|
||||
this.setState({
|
||||
expanded: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onRootClick(ev) {
|
||||
// This captures any clicks that happen within our elements,
|
||||
// such that we can then ignore them when they're seen by the
|
||||
// click listener on the document handler, ie. not close the
|
||||
// dropdown immediately after opening it.
|
||||
// NB. We can't just stopPropagation() because then the event
|
||||
// doesn't reach the React onClick().
|
||||
this.ignoreEvent = ev;
|
||||
}
|
||||
|
||||
onInputClick(ev) {
|
||||
this.setState({
|
||||
expanded: !this.state.expanded,
|
||||
});
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
onMenuOptionClick(server, instance, includeAll) {
|
||||
this.setState({
|
||||
expanded: false,
|
||||
selectedServer: server,
|
||||
selectedInstanceId: instance ? instance.instance_id : null,
|
||||
includeAll: includeAll,
|
||||
});
|
||||
this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll);
|
||||
}
|
||||
|
||||
onInputKeyUp(e) {
|
||||
if (e.key == 'Enter') {
|
||||
this.setState({
|
||||
expanded: false,
|
||||
selectedServer: e.target.value,
|
||||
selectedNetwork: null,
|
||||
});
|
||||
this.props.onOptionChange(e.target.value, null);
|
||||
}
|
||||
}
|
||||
|
||||
collectRoot(e) {
|
||||
if (this.dropdownRootElement) {
|
||||
this.dropdownRootElement.removeEventListener('click', this.onRootClick, false);
|
||||
}
|
||||
if (e) {
|
||||
e.addEventListener('click', this.onRootClick, false);
|
||||
}
|
||||
this.dropdownRootElement = e;
|
||||
}
|
||||
|
||||
collectInputTextBox(e) {
|
||||
this.inputTextBox = e;
|
||||
}
|
||||
|
||||
_getMenuOptions() {
|
||||
const options = [];
|
||||
|
||||
let servers = [];
|
||||
if (this.props.config.servers) {
|
||||
servers = servers.concat(this.props.config.servers);
|
||||
}
|
||||
|
||||
if (servers.indexOf(MatrixClientPeg.getHomeServerName()) == -1) {
|
||||
servers.unshift(MatrixClientPeg.getHomeServerName());
|
||||
}
|
||||
|
||||
// For our own HS, we can use the instance_ids given in the third party protocols
|
||||
// response to get the server to filter the room list by network for us.
|
||||
// We can't get thirdparty protocols for remote server yet though, so for those
|
||||
// we can only show the default room list.
|
||||
for (const server of servers) {
|
||||
options.push(this._makeMenuOption(server, null, true));
|
||||
if (server == MatrixClientPeg.getHomeServerName()) {
|
||||
options.push(this._makeMenuOption(server, null, false));
|
||||
if (this.props.protocols) {
|
||||
for (const proto of Object.keys(this.props.protocols)) {
|
||||
if (!this.props.protocols[proto].instances) continue;
|
||||
for (const instance of this.props.protocols[proto].instances) {
|
||||
if (!instance.instance_id) continue;
|
||||
options.push(this._makeMenuOption(server, instance, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
_makeMenuOption(server, instance, includeAll, handleClicks) {
|
||||
if (handleClicks === undefined) handleClicks = true;
|
||||
|
||||
let icon;
|
||||
let name;
|
||||
let span_class;
|
||||
let key;
|
||||
|
||||
if (!instance && includeAll) {
|
||||
key = server;
|
||||
name = server;
|
||||
span_class = 'mx_NetworkDropdown_menu_all';
|
||||
} else if (!instance) {
|
||||
key = server + '_all';
|
||||
name = 'Matrix';
|
||||
icon = <img src="img/network-matrix.svg" />;
|
||||
span_class = 'mx_NetworkDropdown_menu_network';
|
||||
} else {
|
||||
key = server + '_inst_' + instance.instance_id;
|
||||
const imgUrl = instance.icon ?
|
||||
MatrixClientPeg.get().mxcUrlToHttp(instance.icon, 25, 25, 'crop', true) :
|
||||
DEFAULT_ICON_URL;
|
||||
icon = <img src={imgUrl} />;
|
||||
name = instance.desc;
|
||||
span_class = 'mx_NetworkDropdown_menu_network';
|
||||
}
|
||||
|
||||
const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null;
|
||||
|
||||
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
||||
{icon}
|
||||
<span className="mx_NetworkDropdown_menu_network">{name}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
render() {
|
||||
let current_value;
|
||||
|
||||
let menu;
|
||||
if (this.state.expanded) {
|
||||
const menu_options = this._getMenuOptions();
|
||||
menu = <div className="mx_NetworkDropdown_menu">
|
||||
{menu_options}
|
||||
</div>;
|
||||
current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
|
||||
ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
|
||||
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
||||
/>
|
||||
} else {
|
||||
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
|
||||
current_value = this._makeMenuOption(
|
||||
this.state.selectedServer, instance, this.state.includeAll, false
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="mx_NetworkDropdown" ref={this.collectRoot}>
|
||||
<div className="mx_NetworkDropdown_input" onClick={this.onInputClick}>
|
||||
{current_value}
|
||||
<span className="mx_NetworkDropdown_arrow"></span>
|
||||
{menu}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
NetworkDropdown.propTypes = {
|
||||
onOptionChange: React.PropTypes.func.isRequired,
|
||||
protocols: React.PropTypes.object,
|
||||
// The room directory config. May have a 'servers' key that is a list of server names to include in the dropdown
|
||||
config: React.PropTypes.object,
|
||||
};
|
||||
|
||||
NetworkDropdown.defaultProps = {
|
||||
protocols: {},
|
||||
config: {},
|
||||
};
|
||||
@@ -22,12 +22,25 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
|
||||
var DateUtils = require('matrix-react-sdk/lib/DateUtils');
|
||||
var filesize = require('filesize');
|
||||
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ImageView',
|
||||
|
||||
propTypes: {
|
||||
onFinished: React.PropTypes.func.isRequired
|
||||
src: React.PropTypes.string.isRequired, // the source of the image being displayed
|
||||
name: React.PropTypes.string, // the main title ('name') for the image
|
||||
link: React.PropTypes.string, // the link (if any) applied to the name of the image
|
||||
width: React.PropTypes.number, // width of the image src in pixels
|
||||
height: React.PropTypes.number, // height of the image src in pixels
|
||||
fileSize: React.PropTypes.number, // size of the image src in bytes
|
||||
onFinished: React.PropTypes.func.isRequired, // callback when the lightbox is dismissed
|
||||
|
||||
// the event (if any) that the Image is displaying. Used for event-specific stuff like
|
||||
// redactions, senders, timestamps etc. Other descriptors are taken from the explicit
|
||||
// properties above, which let us use lightboxes to display images which aren't associated
|
||||
// with events.
|
||||
mxEvent: React.PropTypes.object,
|
||||
},
|
||||
|
||||
// XXX: keyboard shortcuts for managing dialogs should be done by the modal
|
||||
@@ -65,6 +78,14 @@ module.exports = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
getName: function () {
|
||||
var name = this.props.name;
|
||||
if (name && this.props.link) {
|
||||
name = <a href={ this.props.link } target="_blank" rel="noopener">{ name }</a>;
|
||||
}
|
||||
return name;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
/*
|
||||
@@ -102,12 +123,36 @@ module.exports = React.createClass({
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
};
|
||||
res = ", " + style.width + "x" + style.height + "px";
|
||||
res = style.width + "x" + style.height + "px";
|
||||
}
|
||||
|
||||
var size;
|
||||
if (this.props.mxEvent.getContent().info && this.props.mxEvent.getContent().info.size) {
|
||||
size = filesize(this.props.mxEvent.getContent().info.size);
|
||||
if (this.props.fileSize) {
|
||||
size = filesize(this.props.fileSize);
|
||||
}
|
||||
|
||||
var size_res;
|
||||
if (size && res) {
|
||||
size_res = size + ", " + res;
|
||||
}
|
||||
else {
|
||||
size_res = size || res;
|
||||
}
|
||||
|
||||
var showEventMeta = !!this.props.mxEvent;
|
||||
|
||||
var eventMeta;
|
||||
if(showEventMeta) {
|
||||
eventMeta = (<div className="mx_ImageView_metadata">
|
||||
Uploaded on { DateUtils.formatDate(new Date(this.props.mxEvent.getTs())) } by { this.props.mxEvent.getSender() }
|
||||
</div>);
|
||||
}
|
||||
|
||||
var eventRedact;
|
||||
if(showEventMeta) {
|
||||
eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
|
||||
Redact
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -118,29 +163,20 @@ module.exports = React.createClass({
|
||||
<img src={this.props.src} style={style}/>
|
||||
<div className="mx_ImageView_labelWrapper">
|
||||
<div className="mx_ImageView_label">
|
||||
<img className="mx_ImageView_cancel" src="img/cancel-white.svg" width="18" height="18" alt="Close" onClick={ this.props.onFinished }/>
|
||||
<AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt="Close"/></AccessibleButton>
|
||||
<div className="mx_ImageView_shim">
|
||||
</div>
|
||||
<div className="mx_ImageView_name">
|
||||
{ this.props.mxEvent.getContent().body }
|
||||
{ this.getName() }
|
||||
</div>
|
||||
<div className="mx_ImageView_metadata">
|
||||
Uploaded on { DateUtils.formatDate(new Date(this.props.mxEvent.getTs())) } by { this.props.mxEvent.getSender() }
|
||||
</div>
|
||||
<a className="mx_ImageView_link" href={ this.props.src } target="_blank">
|
||||
{ eventMeta }
|
||||
<a className="mx_ImageView_link" href={ this.props.src } target="_blank" rel="noopener">
|
||||
<div className="mx_ImageView_download">
|
||||
Download this file<br/>
|
||||
<span className="mx_ImageView_size">{ size } { res }</span>
|
||||
<span className="mx_ImageView_size">{ size_res }</span>
|
||||
</div>
|
||||
</a>
|
||||
<div className="mx_ImageView_button">
|
||||
<a className="mx_ImageView_link" href={ this.props.src } target="_blank">
|
||||
View full screen
|
||||
</a>
|
||||
</div>
|
||||
<div className="mx_ImageView_button" onClick={this.onRedactClick}>
|
||||
Redact
|
||||
</div>
|
||||
{ eventRedact }
|
||||
<div className="mx_ImageView_shim">
|
||||
</div>
|
||||
</div>
|
||||
|
||||