Compare commits
639 Commits
v0.0.1-alp
...
v1.3.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58f97038b1 | ||
|
|
6030d4c62a | ||
|
|
206f726e4a | ||
|
|
9a36fffaac | ||
|
|
30af4277e0 | ||
|
|
98d23aaae1 | ||
|
|
20feb9bd38 | ||
|
|
56090151c2 | ||
|
|
84a69ce455 | ||
|
|
2424f7a995 | ||
|
|
c3c60c98d6 | ||
|
|
24017631c0 | ||
|
|
82a9d13a3c | ||
|
|
c064322234 | ||
|
|
32542d500a | ||
|
|
5e8a971789 | ||
|
|
a7a9e96a58 | ||
|
|
347f6e50eb | ||
|
|
b873b358fe | ||
|
|
caf4545902 | ||
|
|
9c2b7eeb27 | ||
|
|
d25d71cb91 | ||
|
|
289417e5a9 | ||
|
|
fd549e44a6 | ||
|
|
ae1ffe7ccc | ||
|
|
a04e14545e | ||
|
|
9b928a16b7 | ||
|
|
b54cedbdbb | ||
|
|
3f9ba0a2bb | ||
|
|
9e8ec3b6d4 | ||
|
|
c3072854fd | ||
|
|
197e9a0520 | ||
|
|
ff84792374 | ||
|
|
a0ae29b34a | ||
|
|
f880683d9c | ||
|
|
d80b4a813c | ||
|
|
3567f6be6c | ||
|
|
03358b1e50 | ||
|
|
b098f9e616 | ||
|
|
d2fc5bd5e8 | ||
|
|
f5e15e3c85 | ||
|
|
f7002ecdf3 | ||
|
|
48e5396092 | ||
|
|
6901b5f128 | ||
|
|
df0adb373d | ||
|
|
1c0cf148f8 | ||
|
|
c644fa2778 | ||
|
|
7962ccf19a | ||
|
|
3ad73a1eaa | ||
|
|
4445baafa9 | ||
|
|
63cfcbbaf1 | ||
|
|
769972fc4b | ||
|
|
2c8cf2b959 | ||
|
|
068fe249ea | ||
|
|
d8c08c1c5b | ||
|
|
56189806a9 | ||
|
|
0f6e48c65f | ||
|
|
fa50243dbb | ||
|
|
9e4e5e9bfd | ||
|
|
c413a65f07 | ||
|
|
53c91e890a | ||
|
|
6e3017d5e7 | ||
|
|
1e57daa8bf | ||
|
|
f1ed6695fb | ||
|
|
c5d9eceb6d | ||
|
|
f2a7ce188d | ||
|
|
3dd9c89a0e | ||
|
|
697948c9d8 | ||
|
|
5150b7c57c | ||
|
|
ce88da3de9 | ||
|
|
0617f41a2d | ||
|
|
975bcbbe31 | ||
|
|
e44af311ce | ||
|
|
297ada5565 | ||
|
|
1fe64cb8d7 | ||
|
|
6557a7d606 | ||
|
|
9a0f6648ff | ||
|
|
3cd4890e60 | ||
|
|
dc287e7e57 | ||
|
|
ac0d998804 | ||
|
|
1d38ebdf05 | ||
|
|
d70783600d | ||
|
|
527196cdbb | ||
|
|
ffeb960851 | ||
|
|
a36b39ec73 | ||
|
|
2deb63b557 | ||
|
|
fcd97f3a32 | ||
|
|
67dc1e2a11 | ||
|
|
c37ae73392 | ||
|
|
9e667e3cb0 | ||
|
|
505fea0e91 | ||
|
|
70ff8db756 | ||
|
|
d6975f7b2a | ||
|
|
6cbea1acba | ||
|
|
d4decfb6af | ||
|
|
5bbf710529 | ||
|
|
b057bffe42 | ||
|
|
4e04c85258 | ||
|
|
bfbd9d4578 | ||
|
|
6594da6ddd | ||
|
|
38abf2deab | ||
|
|
0004152e18 | ||
|
|
e8be57d710 | ||
|
|
64e8419410 | ||
|
|
f35a3df63b | ||
|
|
9e15ed2699 | ||
|
|
cdc99cda26 | ||
|
|
7e08af5ced | ||
|
|
0ee85bea16 | ||
|
|
592584fcf4 | ||
|
|
8e1b7a0289 | ||
|
|
715cf8d86f | ||
|
|
0dc20cc66c | ||
|
|
c860832a73 | ||
|
|
27a29aeba6 | ||
|
|
d0bd7e1f1c | ||
|
|
72974b075f | ||
|
|
947bab657f | ||
|
|
5a2975ca4d | ||
|
|
499743e99d | ||
|
|
6bd4c44893 | ||
|
|
5b82a82313 | ||
|
|
0208e407f0 | ||
|
|
e3a622fc07 | ||
|
|
f39839618d | ||
|
|
3f93a3346f | ||
|
|
9ab75b9800 | ||
|
|
f600c4db0e | ||
|
|
4e5816697e | ||
|
|
dc3c446a46 | ||
|
|
4cd8efa396 | ||
|
|
541ba335ae | ||
|
|
3e226400c4 | ||
|
|
890d7caea5 | ||
|
|
45e7993d0c | ||
|
|
8210a7c472 | ||
|
|
9e6bb1b48f | ||
|
|
d3c2da7961 | ||
|
|
a5017456f2 | ||
|
|
76cd62d0c8 | ||
|
|
6b68217494 | ||
|
|
8148d83448 | ||
|
|
a55cda3b1f | ||
|
|
e381960206 | ||
|
|
091b641abb | ||
|
|
af0ec80998 | ||
|
|
5653a38d9b | ||
|
|
01ea3beb99 | ||
|
|
cd387aaf9c | ||
|
|
24b5e0412b | ||
|
|
8174f7b37e | ||
|
|
d3a5eceaf9 | ||
|
|
31f285eba9 | ||
|
|
b1365f9669 | ||
|
|
21351125fa | ||
|
|
50b9ec7220 | ||
|
|
46cf5c9a5c | ||
|
|
35a42f3873 | ||
|
|
14b9fcf8d7 | ||
|
|
47cfe86a06 | ||
|
|
57ad2d63e0 | ||
|
|
2fcc5d9649 | ||
|
|
45523f41aa | ||
|
|
15b3af7b97 | ||
|
|
81d3aa8a1b | ||
|
|
321860a232 | ||
|
|
1c290fc2cd | ||
|
|
a74b17b989 | ||
|
|
ffba6b68f8 | ||
|
|
b553b29328 | ||
|
|
d7b44b23d1 | ||
|
|
b991fac136 | ||
|
|
cda8aa3b09 | ||
|
|
1e60643ae9 | ||
|
|
d9fbcaf38e | ||
|
|
9e5a67c36b | ||
|
|
a16181be33 | ||
|
|
cd0a7741b1 | ||
|
|
6da523a18b | ||
|
|
86302308a9 | ||
|
|
b0294db133 | ||
|
|
252586cf71 | ||
|
|
52ea1a48fd | ||
|
|
98e73f807c | ||
|
|
f96dd6e6bc | ||
|
|
1511ce4610 | ||
|
|
c2db6fc0f0 | ||
|
|
937a193a61 | ||
|
|
3823ac7f78 | ||
|
|
878cc3fe82 | ||
|
|
eace637f29 | ||
|
|
316b5fda9e | ||
|
|
e3b8d5ea2a | ||
|
|
c8434cfd21 | ||
|
|
fbc048e8cb | ||
|
|
8a4483da80 | ||
|
|
8252c9ae6c | ||
|
|
5beb425f1c | ||
|
|
44337dd04c | ||
|
|
bb76e2c2f4 | ||
|
|
71305bca4e | ||
|
|
9cff5c5a4d | ||
|
|
f3cf2229c6 | ||
|
|
a62fc3d3bf | ||
|
|
e538543512 | ||
|
|
f85bca9879 | ||
|
|
f548a0ae53 | ||
|
|
7192dc69f6 | ||
|
|
47eec89a9b | ||
|
|
48ff506344 | ||
|
|
c2e6e9603f | ||
|
|
f4f4836887 | ||
|
|
02bc5e41a5 | ||
|
|
68c90434b3 | ||
|
|
7f8d933ab7 | ||
|
|
935b37705a | ||
|
|
798235fb06 | ||
|
|
743d689281 | ||
|
|
4c188bbdc8 | ||
|
|
26e0543ae2 | ||
|
|
463701c5f8 | ||
|
|
4c33f0779c | ||
|
|
4888efb9f2 | ||
|
|
ea332a9ff3 | ||
|
|
f1b2fdcf99 | ||
|
|
9f73160f74 | ||
|
|
b18e3ad5d2 | ||
|
|
ed6013fb5d | ||
|
|
39b436819c | ||
|
|
3e74fc9b0a | ||
|
|
0708519816 | ||
|
|
84808313fe | ||
|
|
e7e55596c6 | ||
|
|
1b8eb099ab | ||
|
|
1fa276a400 | ||
|
|
0751f6ac72 | ||
|
|
3d8da55648 | ||
|
|
318a729a4a | ||
|
|
aab581f204 | ||
|
|
c0075b4f1c | ||
|
|
7bd2e751c5 | ||
|
|
5ff2492c79 | ||
|
|
a26df708f4 | ||
|
|
57df256a4a | ||
|
|
7f1890d8a1 | ||
|
|
ff12b041a1 | ||
|
|
606e407aee | ||
|
|
b0db81095a | ||
|
|
2208b03612 | ||
|
|
7cf2422d76 | ||
|
|
57d4e5c930 | ||
|
|
deafa315df | ||
|
|
4c09da791c | ||
|
|
e3490dbfa5 | ||
|
|
f6358a06fd | ||
|
|
3ef346495f | ||
|
|
64fcc51c7e | ||
|
|
a5299a6973 | ||
|
|
154aa323ab | ||
|
|
c52318f5d6 | ||
|
|
bcd4048709 | ||
|
|
f6cb262d4c | ||
|
|
ca21912374 | ||
|
|
472ba9a199 | ||
|
|
f3a4d4225d | ||
|
|
62178b6035 | ||
|
|
2068ac8905 | ||
|
|
51352be1f6 | ||
|
|
8c3bfcbdd5 | ||
|
|
06ed522714 | ||
|
|
d5526944fe | ||
|
|
616ed89d90 | ||
|
|
92362653d3 | ||
|
|
06a09574a5 | ||
|
|
c19a7f81c2 | ||
|
|
635b6f6128 | ||
|
|
996084cd36 | ||
|
|
6a403e52e1 | ||
|
|
aed58ed1a8 | ||
|
|
0412702e35 | ||
|
|
cf0086e324 | ||
|
|
d156ba99c8 | ||
|
|
7d71e2e04d | ||
|
|
546abb28c0 | ||
|
|
a43d008aa9 | ||
|
|
777ed5c561 | ||
|
|
61db04c574 | ||
|
|
93123401fe | ||
|
|
dca4debee2 | ||
|
|
bef1503b9e | ||
|
|
f9d430b2d2 | ||
|
|
0d551861c7 | ||
|
|
4ced663f01 | ||
|
|
06f15eba2d | ||
|
|
3008cd1f24 | ||
|
|
ea449965af | ||
|
|
06ecdb678c | ||
|
|
dd99560426 | ||
|
|
cebea28ba8 | ||
|
|
da3e7e39b9 | ||
|
|
4b4e3a3d01 | ||
|
|
aff4632927 | ||
|
|
eeb9ee512f | ||
|
|
11785d78d2 | ||
|
|
f8d8d0ce2e | ||
|
|
b5af5d413d | ||
|
|
d962bd6e60 | ||
|
|
0271ada591 | ||
|
|
42fedf2d73 | ||
|
|
0b17dbb014 | ||
|
|
ae3c595d82 | ||
|
|
de34a22740 | ||
|
|
865553fa45 | ||
|
|
23fd7ba2b3 | ||
|
|
e43b651060 | ||
|
|
c0fc7718aa | ||
|
|
3fe23e84b3 | ||
|
|
6b29139805 | ||
|
|
0bfa202763 | ||
|
|
8c494f329b | ||
|
|
35bf2b0012 | ||
|
|
5ac4ea71c9 | ||
|
|
f7eb4128cb | ||
|
|
f9f21cd626 | ||
|
|
4a84f17d86 | ||
|
|
d5a92110db | ||
|
|
e9cf591559 | ||
|
|
ba60c04452 | ||
|
|
f4567c762d | ||
|
|
b2aec4b27e | ||
|
|
0fc58b4cc9 | ||
|
|
e9d7bfe48b | ||
|
|
070eb099d0 | ||
|
|
e35661993d | ||
|
|
f6234cd2f6 | ||
|
|
66475cf46c | ||
|
|
06734d2e56 | ||
|
|
1c012c7a28 | ||
|
|
7fdccd278b | ||
|
|
9a4556a59c | ||
|
|
92b9388f9a | ||
|
|
869600e3d4 | ||
|
|
3c22d5462d | ||
|
|
92ff92f501 | ||
|
|
47b3dd04fb | ||
|
|
9ed09c9e1c | ||
|
|
239cce8a4c | ||
|
|
d6a35408b8 | ||
|
|
35e6f7dcdd | ||
|
|
a38c933bc8 | ||
|
|
720ccf5d00 | ||
|
|
8dc87d0485 | ||
|
|
cd9f1c0c47 | ||
|
|
331452edbb | ||
|
|
d18885ecc9 | ||
|
|
1aef53ef94 | ||
|
|
11086210de | ||
|
|
e0d693fa19 | ||
|
|
81c71b1f83 | ||
|
|
cf8c83e3cf | ||
|
|
3dade768a7 | ||
|
|
7b3c7ba5fa | ||
|
|
616bc0f73b | ||
|
|
8ecdf1f18a | ||
|
|
d2daa65059 | ||
|
|
487ee538e3 | ||
|
|
6382c6d2b3 | ||
|
|
cbcff67d28 | ||
|
|
61a429365b | ||
|
|
e55f38a962 | ||
|
|
81798583ae | ||
|
|
9cab61cebc | ||
|
|
f290732cb6 | ||
|
|
2dd44852fa | ||
|
|
bd2f17deec | ||
|
|
06faf46bcc | ||
|
|
468e9d539b | ||
|
|
449f407236 | ||
|
|
7efe90e709 | ||
|
|
184e1a5bc4 | ||
|
|
85e7b337a7 | ||
|
|
83759bde1c | ||
|
|
d9b1d45e0e | ||
|
|
df91c20f36 | ||
|
|
c1f6159d69 | ||
|
|
5a0461211a | ||
|
|
1394f790c3 | ||
|
|
4d374ff40c | ||
|
|
0d15435408 | ||
|
|
d4448ba086 | ||
|
|
4bd6fde5b6 | ||
|
|
e5097ace06 | ||
|
|
ca078cbbfd | ||
|
|
6d45105b69 | ||
|
|
40f81af2dd | ||
|
|
d5e6184e75 | ||
|
|
0ec38ba41d | ||
|
|
460a64710a | ||
|
|
9ec62a748f | ||
|
|
a7a80cef99 | ||
|
|
bcaf54b349 | ||
|
|
1f6d0762bb | ||
|
|
1aa1655676 | ||
|
|
0ad66399d0 | ||
|
|
3a6bb14224 | ||
|
|
53eb8051ad | ||
|
|
ed9174f57c | ||
|
|
260d6a1906 | ||
|
|
dc53319c8e | ||
|
|
77369536b3 | ||
|
|
531afcb1e5 | ||
|
|
d6c4eeccf8 | ||
|
|
496b906fd0 | ||
|
|
1be29faea6 | ||
|
|
bc9a7c9890 | ||
|
|
5745276dbb | ||
|
|
acf0282aa0 | ||
|
|
54d942178a | ||
|
|
d84ada373e | ||
|
|
d4d36d0582 | ||
|
|
63e6c634b9 | ||
|
|
4bc558cdbc | ||
|
|
34293fd66b | ||
|
|
5eba4b8200 | ||
|
|
a949307820 | ||
|
|
a714977b5a | ||
|
|
bfcaa51c46 | ||
|
|
d7d3017bc1 | ||
|
|
c7ce8fb7c8 | ||
|
|
65a80de01d | ||
|
|
f6bf210451 | ||
|
|
8c23ec3417 | ||
|
|
97bbd809f7 | ||
|
|
76381cac08 | ||
|
|
13c4ceedc2 | ||
|
|
f0889edf9c | ||
|
|
ab8367c946 | ||
|
|
c5887c8f71 | ||
|
|
7b7ab03d0b | ||
|
|
394caa7934 | ||
|
|
db2860b53e | ||
|
|
6d20df3557 | ||
|
|
2942cd1244 | ||
|
|
174049f489 | ||
|
|
21eae28999 | ||
|
|
f256cbcd5d | ||
|
|
ff8c20f0b4 | ||
|
|
9e0c17a793 | ||
|
|
b4fb00aa52 | ||
|
|
13d0b5e51c | ||
|
|
dc15e4578c | ||
|
|
673da66ee9 | ||
|
|
e397e9bfb4 | ||
|
|
4eca2e9254 | ||
|
|
a1407826b6 | ||
|
|
6d8f83798b | ||
|
|
bfc03c7e95 | ||
|
|
06737ce629 | ||
|
|
6b09840347 | ||
|
|
19b5eecdcd | ||
|
|
f443918433 | ||
|
|
bb74e58d63 | ||
|
|
612e670bb5 | ||
|
|
34bb55cb9f | ||
|
|
1457e82649 | ||
|
|
0bdeaaba18 | ||
|
|
e2286b6110 | ||
|
|
27ba3f3068 | ||
|
|
9859315fea | ||
|
|
73d18dde9e | ||
|
|
0372befc5a | ||
|
|
063799a053 | ||
|
|
297b307726 | ||
|
|
deed66973f | ||
|
|
03486e438d | ||
|
|
e113f59aad | ||
|
|
29ece4824a | ||
|
|
66c62c55e2 | ||
|
|
40852942d2 | ||
|
|
15c8591691 | ||
|
|
e21902a5e3 | ||
|
|
a5d42e8ccd | ||
|
|
0923df7250 | ||
|
|
37f5258210 | ||
|
|
19819f7d48 | ||
|
|
40f74cdac2 | ||
|
|
e4ba3b9721 | ||
|
|
9a221585b9 | ||
|
|
069a0ff306 | ||
|
|
3153fb91da | ||
|
|
92d4d27fa8 | ||
|
|
209fc4a65c | ||
|
|
5e9ecedecd | ||
|
|
613df32bf1 | ||
|
|
7606f37695 | ||
|
|
a2f758a46b | ||
|
|
2a477abe5f | ||
|
|
5f027820f4 | ||
|
|
9e75861546 | ||
|
|
4060596474 | ||
|
|
2e5888c8af | ||
|
|
03d1188e14 | ||
|
|
c91b0c209a | ||
|
|
531973194c | ||
|
|
4d1face275 | ||
|
|
ca7503f1f0 | ||
|
|
9c163b4166 | ||
|
|
742afb1c09 | ||
|
|
f9f70f001b | ||
|
|
edf34efd86 | ||
|
|
72ebed95da | ||
|
|
975a6ab8bf | ||
|
|
3352301b67 | ||
|
|
358260f766 | ||
|
|
b58052ce34 | ||
|
|
de9ad8082b | ||
|
|
d6bf84c821 | ||
|
|
d5cba4096d | ||
|
|
afa12cc266 | ||
|
|
42ede5e54b | ||
|
|
1549db5ce0 | ||
|
|
a0de440c2e | ||
|
|
7e54eb89c2 | ||
|
|
f8e77dfa72 | ||
|
|
db6d1f300a | ||
|
|
eac2b92cb6 | ||
|
|
a349e0e4e0 | ||
|
|
58f35569c8 | ||
|
|
4a9f51aa1b | ||
|
|
5bb05906b5 | ||
|
|
f80095d953 | ||
|
|
181803755a | ||
|
|
ef30cd9624 | ||
|
|
7c82f41e87 | ||
|
|
61dfcfc3b1 | ||
|
|
e3deb93730 | ||
|
|
d6a3e5fe1b | ||
|
|
dc15edf0cd | ||
|
|
e5a60cc077 | ||
|
|
4d5c57fa0b | ||
|
|
98bd4fd9b9 | ||
|
|
8b808ff9b2 | ||
|
|
4a2a495738 | ||
|
|
309816e40a | ||
|
|
79a31000df | ||
|
|
8410769fdd | ||
|
|
fd25ac0cc4 | ||
|
|
87797cf81e | ||
|
|
4638810127 | ||
|
|
e0c2d9f6c7 | ||
|
|
2c34eb8ff8 | ||
|
|
630bafca42 | ||
|
|
9b13547336 | ||
|
|
07d64fa8c0 | ||
|
|
d4cb4b1572 | ||
|
|
de1a24c726 | ||
|
|
e18d30a3a9 | ||
|
|
d9b6109607 | ||
|
|
6593417189 | ||
|
|
779c0bcf5d | ||
|
|
39ab6aff62 | ||
|
|
b037d1ca1f | ||
|
|
92d2ba7ee9 | ||
|
|
557e05db7c | ||
|
|
c0df294dec | ||
|
|
34700969ae | ||
|
|
1a36230cac | ||
|
|
1b9c3afdd4 | ||
|
|
b33e36b415 | ||
|
|
7ee9bf833e | ||
|
|
978128ae3e | ||
|
|
ca754d5eb9 | ||
|
|
be41b4069c | ||
|
|
a7cf1ddbb0 | ||
|
|
f098ae8250 | ||
|
|
84ae25ed06 | ||
|
|
5e5270e5ed | ||
|
|
157ffb7f34 | ||
|
|
70e4a11fe6 | ||
|
|
f142b5bb92 | ||
|
|
f9c9f59d09 | ||
|
|
22ea0dd9f7 | ||
|
|
1a63839f6d | ||
|
|
8ca092c403 | ||
|
|
474e9f0194 | ||
|
|
9f70068184 | ||
|
|
04f6e9e6e5 | ||
|
|
c05885baac | ||
|
|
9908ef51eb | ||
|
|
a4471f35ee | ||
|
|
b9c4910dd4 | ||
|
|
5c95e35839 | ||
|
|
e068d8e96f | ||
|
|
dda491938e | ||
|
|
71637d8e92 | ||
|
|
7cbdd9cae8 | ||
|
|
b59cd3cf23 | ||
|
|
88d50fa6a6 | ||
|
|
ef94998d21 | ||
|
|
2306514507 | ||
|
|
f7e3ed8a0c | ||
|
|
88036f864f | ||
|
|
82c6782c0d | ||
|
|
a72bccadf9 | ||
|
|
e55928183f | ||
|
|
ef5a44dec2 | ||
|
|
042afdf94f | ||
|
|
e09102456f | ||
|
|
4119f1238d | ||
|
|
c9b2ec0bac | ||
|
|
54bc001d46 | ||
|
|
e20194fc72 | ||
|
|
c1ef006e40 | ||
|
|
2523f8c5d0 | ||
|
|
30f44d2791 | ||
|
|
3c7369189f | ||
|
|
c8bbcf2248 | ||
|
|
62963ae563 | ||
|
|
2d43ce08ad | ||
|
|
2c5459233d | ||
|
|
56bf6fe097 | ||
|
|
89bc8401db | ||
|
|
39f3df5b8f | ||
|
|
5cc3bf0dc8 | ||
|
|
c594979d9b | ||
|
|
e12ad6e1d5 | ||
|
|
d62b87ec0c | ||
|
|
14c130d102 | ||
|
|
c4449570a9 | ||
|
|
011d368fd5 | ||
|
|
0d0518fcf5 | ||
|
|
f1a5bfcabf | ||
|
|
05d483b44c | ||
|
|
34a62e2d10 | ||
|
|
99e444aec0 | ||
|
|
56940dacf4 | ||
|
|
6e6ace049a | ||
|
|
5e40043e3e | ||
|
|
55b45420bd |
24
.gitignore
vendored
@@ -1,8 +1,26 @@
|
|||||||
node_modules
|
# Dependency directories
|
||||||
npm-debug.log
|
node_modules/
|
||||||
domain.json
|
|
||||||
|
|
||||||
|
# npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Compiled binary build directory
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
// osx garbage
|
// osx garbage
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# miscellaneous
|
||||||
|
.idea
|
||||||
|
config.gypi
|
||||||
1
.node-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
6.9.4
|
||||||
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
2.7.9
|
||||||
35
.travis.yml
@@ -1,3 +1,36 @@
|
|||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
|
os:
|
||||||
|
- osx
|
||||||
|
- linux
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- build-essential
|
||||||
|
- libxext-dev
|
||||||
|
- libxtst-dev
|
||||||
|
- libxkbfile-dev
|
||||||
|
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 'node'
|
- '6'
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- npm install -g gulp
|
||||||
|
- npm install
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
- app/node_modules
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm run travis
|
||||||
|
notifications:
|
||||||
|
webhooks:
|
||||||
|
urls:
|
||||||
|
- https://zulip.org/zulipbot/travis
|
||||||
|
on_success: always
|
||||||
|
on_failure: always
|
||||||
@@ -2,36 +2,46 @@
|
|||||||
|
|
||||||
Thanks for taking the time to contribute!
|
Thanks for taking the time to contribute!
|
||||||
|
|
||||||
The following is a set of guidelines for contributing to zulip-electron. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request.
|
The following is a set of guidelines for contributing to Zulip Electron Desktop Client. These are just guidelines, not rules, so use your best judgement and feel free to propose changes to this document in a pull request.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron please head over to [this](http://jlord.us/essential-electron/) great article.
|
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](http://jlord.us/essential-electron/) great article.
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
|
||||||
|
|
||||||
|
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/electron).
|
||||||
|
|
||||||
## Issue
|
## Issue
|
||||||
Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/zulip/zulip-electron/issues/new). Please pay attention to following points while opening an issue.
|
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-electron/issues/new).
|
||||||
|
|
||||||
|
The [zulipbot](https://github.com/zulip/zulipbot) helps to claim an issue by commenting the following in the comment section: "**@zulipbot** claim". **@zulipbot** will assign you to the issue and label the issue as **in progress**. For more details, check out [**@zulipbot**](https://github.com/zulip/zulipbot).
|
||||||
|
|
||||||
|
Please pay attention to the following points while opening an issue.
|
||||||
|
|
||||||
### Does it happen on web browsers? (especially Chrome)
|
### Does it happen on web browsers? (especially Chrome)
|
||||||
Zulip-Desktop is based on Electron, which integrates the Chrome engine within a standalone application.
|
Zulip's desktop client is based on Electron, which integrates the Chrome engine within a standalone application.
|
||||||
If the problem you encounter can be reproduced on web browsers, it may be an issue with Zulip web app.
|
If the problem you encounter can be reproduced on web browsers, it may be an issue with [Zulip web app](https://github.com/zulip/zulip).
|
||||||
|
|
||||||
### Write detailed information
|
### Write detailed information
|
||||||
Detailed information is very helpful to understand the problem.
|
Detailed information is very helpful to understand an issue.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
* How to reproduce, step-by-step
|
* How to reproduce the issue, step-by-step.
|
||||||
* Expected behavior (or what is wrong)
|
* The expected behavior (or what is wrong).
|
||||||
* Screenshots (for GUI issues)
|
* Screenshots for GUI issues.
|
||||||
* Application version
|
* The application version.
|
||||||
* Operating system
|
* The operating system.
|
||||||
* Zulip-Desktop version
|
* The Zulip-Desktop version.
|
||||||
|
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
Pull Requests are welcome.
|
Pull Requests are always welcome.
|
||||||
|
|
||||||
1. When you edit the code, please run `npm run test` to check formatting of your code before git commit.
|
1. When you edit the code, please run `npm run test` to check the formatting of your code before you `git commit`.
|
||||||
2. Ensure the PR description clearly describes the problem and solution. It should include:
|
2. Ensure the PR description clearly describes the problem and solution. It should include:
|
||||||
* Operating System version on which you tested
|
* The operating system on which you tested.
|
||||||
* Zulip-Desktop version on which you tested
|
* The Zulip-Desktop version on which you tested.
|
||||||
* The relevant issue number if applicable
|
* The relevant issue number, if applicable.
|
||||||
|
|||||||
76
README.md
@@ -1,63 +1,39 @@
|
|||||||
# Zulip Desktop Client
|
# Zulip Desktop Client
|
||||||
|
[](https://travis-ci.org/zulip/zulip-electron)
|
||||||
|
[](https://ci.appveyor.com/project/akashnimare/zulip-electron/branch/master)
|
||||||
|
[](https://github.com/sindresorhus/xo)
|
||||||
|
|
||||||
This is an experimental replacement for the [Zulip Desktop
|
Desktop client for Zulip. Available for Mac, Linux and Windows.
|
||||||
app](https://github.com/zulip/zulip-desktop) implemented in
|
|
||||||
[Electron](http://electron.atom.io/).
|
|
||||||
|
|
||||||
The goal is to achieve feature-compatibility with the old desktop app
|
<img src="http://i.imgur.com/ChzTq4F.png"/>
|
||||||
and then start adding cool features like easy support for
|
|
||||||
multi-account, auto-updates etc.
|
|
||||||
|
|
||||||
## Installation
|
# Download
|
||||||
|
You can download the latest version from the [Releases](https://github.com/zulip/zulip-electron/releases/latest) page.
|
||||||
|
|
||||||
Clone the source locally:
|
# Features
|
||||||
|
* Sign in to multiple teams
|
||||||
|
* Native desktop Notifications
|
||||||
|
* SpellChecker
|
||||||
|
* OSX/Win/Linux installers
|
||||||
|
* Automatic Updates (macOS/Windows)
|
||||||
|
* Keyboard shortcuts
|
||||||
|
|
||||||
```sh
|
Description | Keys
|
||||||
$ git clone https://github.com/zulip/zulip-electron
|
-----------------------| -----------------------
|
||||||
$ cd zulip-electron
|
Default shortcuts | <kbd>Cmd/Ctrl</kbd> <kbd>k</kbd>
|
||||||
```
|
Manage Zulip Servers | <kbd>Cmd/Ctrl</kbd> <kbd>,</kbd>
|
||||||
If you're on Debian or Ubuntu, you'll also need to install
|
Back | <kbd>Cmd/Ctrl</kbd> <kbd>[</kbd>
|
||||||
`nodejs-legacy`:
|
Forward | <kbd>Cmd/Ctrl</kbd> <kbd>]</kbd>
|
||||||
|
|
||||||
Use your package manager to install `npm`.
|
# Development
|
||||||
|
Please see our [development guide](./development.md) to get started and run app locally.
|
||||||
|
|
||||||
```sh
|
# Contribute
|
||||||
$ sudo apt-get install npm nodejs-legacy
|
|
||||||
```
|
|
||||||
|
|
||||||
Install project dependencies:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ npm install
|
|
||||||
```
|
|
||||||
Start the app:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Start and watch changes
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- [x] Native Notifications
|
|
||||||
- [x] Spell Checker
|
|
||||||
- [x] Keyboard Shortcuts
|
|
||||||
- Default shortcuts - <kbd>cmdOrctrl + k </kbd>
|
|
||||||
- Change Zulip Server: <kbd>cmdOrctrl + ,</kbd>
|
|
||||||
- Back: <kbd>cmdOrctrl + [</kbd>
|
|
||||||
- Forward: <kbd>cmdOrctrl + ]</kbd>
|
|
||||||
- [ ] OSX/Win/Linux installer
|
|
||||||
- [ ] Launch on OS startup
|
|
||||||
- [ ] Automatic Updates
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
If you want to contribute please make sure to read [our documentation about contributing](./CONTRIBUTING.md) first.
|
If you want to contribute please make sure to read [our documentation about contributing](./CONTRIBUTING.md) first.
|
||||||
|
|
||||||
* [Issue Tracker](https://github.com/zulip/zulip-electron/issues)
|
* [Issue Tracker](https://github.com/zulip/zulip-electron/issues)
|
||||||
* [Source Code](https://github.com/zulip/zulip-electron/)
|
* [Source Code](https://github.com/zulip/zulip-electron/)
|
||||||
|
|
||||||
|
# License
|
||||||
|
Released under the [Apache-2.0](./LICENSE) license.
|
||||||
|
|||||||
BIN
app/.DS_Store
vendored
37
app/main/autoupdater.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
'use strict';
|
||||||
|
const {app, dialog} = require('electron');
|
||||||
|
const {autoUpdater} = require('electron-updater');
|
||||||
|
|
||||||
|
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
|
||||||
|
|
||||||
|
function appUpdater() {
|
||||||
|
// Log whats happening
|
||||||
|
const log = require('electron-log');
|
||||||
|
log.transports.file.level = 'info';
|
||||||
|
autoUpdater.logger = log;
|
||||||
|
// Handle auto updates for beta/pre releases
|
||||||
|
autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false;
|
||||||
|
|
||||||
|
// Ask the user if update is available
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
autoUpdater.on('update-downloaded', (event, info) => {
|
||||||
|
// Ask user to update the app
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'question',
|
||||||
|
buttons: ['Install and Relaunch', 'Later'],
|
||||||
|
defaultId: 0,
|
||||||
|
message: 'A new version of ' + app.getName() + ' has been downloaded',
|
||||||
|
detail: 'It will be installed the next time you restart the application'
|
||||||
|
}, response => {
|
||||||
|
if (response === 0) {
|
||||||
|
setTimeout(() => autoUpdater.quitAndInstall(), 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Init for updates
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
appUpdater
|
||||||
|
};
|
||||||
@@ -2,141 +2,148 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
const {app} = require('electron');
|
const {app} = require('electron');
|
||||||
const electronLocalshortcut = require('electron-localshortcut');
|
|
||||||
const ipc = require('electron').ipcMain;
|
const ipc = require('electron').ipcMain;
|
||||||
const Configstore = require('configstore');
|
const electronLocalshortcut = require('electron-localshortcut');
|
||||||
const JsonDB = require('node-json-db');
|
const isDev = require('electron-is-dev');
|
||||||
const SpellChecker = require('simple-spellchecker');
|
const windowStateKeeper = require('electron-window-state');
|
||||||
const tray = require('./tray');
|
|
||||||
const appMenu = require('./menu');
|
const appMenu = require('./menu');
|
||||||
const link = require('./link-helper');
|
const {appUpdater} = require('./autoupdater');
|
||||||
|
|
||||||
const {linkIsInternal} = link;
|
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||||
|
|
||||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
|
||||||
const data = db.getData('/');
|
|
||||||
|
|
||||||
// adds debug features like hotkeys for triggering dev tools and reload
|
|
||||||
require('electron-debug')();
|
require('electron-debug')();
|
||||||
require('electron-context-menu')();
|
|
||||||
|
|
||||||
const conf = new Configstore('Zulip-Desktop');
|
// Prevent window being garbage collected
|
||||||
|
|
||||||
// spellchecker enabled
|
|
||||||
let myDictionary = null;
|
|
||||||
|
|
||||||
// prevent window being garbage collected
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
let targetLink;
|
|
||||||
|
let isQuitting = false;
|
||||||
|
|
||||||
// Load this url in main window
|
// Load this url in main window
|
||||||
const targetUrl = 'file://' + path.join(__dirname, '../renderer', 'index.html');
|
const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
|
||||||
|
|
||||||
function checkWindowURL() {
|
const isAlreadyRunning = app.makeSingleInstance(() => {
|
||||||
if (data.domain !== undefined) {
|
if (mainWindow) {
|
||||||
return data.domain;
|
if (mainWindow.isMinimized()) {
|
||||||
|
mainWindow.restore();
|
||||||
}
|
}
|
||||||
return targetLink;
|
|
||||||
|
mainWindow.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isAlreadyRunning) {
|
||||||
|
return app.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWindowsOrmacOS() {
|
||||||
|
return process.platform === 'darwin' || process.platform === 'win32';
|
||||||
}
|
}
|
||||||
|
|
||||||
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
|
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
|
||||||
|
|
||||||
const spellDict = path.join(__dirname, '../../node_modules/simple-spellchecker/dict');
|
|
||||||
|
|
||||||
const iconPath = () => {
|
const iconPath = () => {
|
||||||
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
|
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
|
||||||
};
|
};
|
||||||
|
|
||||||
function onClosed() {
|
|
||||||
// dereference the window
|
|
||||||
// for multiple windows store them in an array
|
|
||||||
mainWindow = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDockBadge(title) {
|
|
||||||
if (title.indexOf('Zulip') === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let messageCount = (/\(([0-9]+)\)/).exec(title);
|
|
||||||
messageCount = messageCount ? Number(messageCount[1]) : 0;
|
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
app.setBadgeCount(messageCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMainWindow() {
|
function createMainWindow() {
|
||||||
|
// Load the previous state with fallback to defaults
|
||||||
|
const mainWindowState = windowStateKeeper({
|
||||||
|
defaultWidth: 1000,
|
||||||
|
defaultHeight: 600
|
||||||
|
});
|
||||||
const win = new electron.BrowserWindow({
|
const win = new electron.BrowserWindow({
|
||||||
// This settings needs to be saved in config
|
// This settings needs to be saved in config
|
||||||
title: 'Zulip',
|
title: 'Zulip',
|
||||||
width: conf.get('width') || 1000,
|
|
||||||
height: conf.get('height') || 600,
|
|
||||||
icon: iconPath(),
|
icon: iconPath(),
|
||||||
|
x: mainWindowState.x,
|
||||||
|
y: mainWindowState.y,
|
||||||
|
width: mainWindowState.width,
|
||||||
|
height: mainWindowState.height,
|
||||||
minWidth: 600,
|
minWidth: 600,
|
||||||
minHeight: 400,
|
minHeight: 500,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
plugins: true,
|
||||||
nodeIntegration: true,
|
allowDisplayingInsecureContent: true,
|
||||||
plugins: true
|
nodeIntegration: true
|
||||||
}
|
},
|
||||||
|
show: false
|
||||||
|
});
|
||||||
|
|
||||||
|
win.on('focus', () => {
|
||||||
|
win.webContents.send('focus');
|
||||||
|
});
|
||||||
|
|
||||||
|
win.once('ready-to-show', () => {
|
||||||
|
win.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
win.loadURL(mainURL);
|
||||||
|
|
||||||
|
// Keep the app running in background on close event
|
||||||
|
win.on('close', e => {
|
||||||
|
if (!isQuitting) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
app.hide();
|
||||||
|
} else {
|
||||||
|
win.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister all the shortcuts so that they don't interfare with other apps
|
||||||
|
electronLocalshortcut.unregisterAll(mainWindow);
|
||||||
});
|
});
|
||||||
|
|
||||||
win.loadURL(targetUrl);
|
|
||||||
win.on('closed', onClosed);
|
|
||||||
win.setTitle('Zulip');
|
win.setTitle('Zulip');
|
||||||
|
|
||||||
// Let's save browser window position
|
win.on('enter-full-screen', () => {
|
||||||
if (conf.get('x') || conf.get('y')) {
|
win.webContents.send('enter-fullscreen');
|
||||||
win.setPosition(conf.get('x'), conf.get('y'));
|
});
|
||||||
|
|
||||||
|
win.on('leave-full-screen', () => {
|
||||||
|
win.webContents.send('leave-fullscreen');
|
||||||
|
});
|
||||||
|
|
||||||
|
// To destroy tray icon when navigate to a new URL
|
||||||
|
win.webContents.on('will-navigate', e => {
|
||||||
|
if (e) {
|
||||||
|
win.webContents.send('destroytray');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conf.get('maximize')) {
|
|
||||||
win.maximize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle sizing events so we can persist them.
|
|
||||||
win.on('maximize', () => {
|
|
||||||
conf.set('maximize', true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
win.on('unmaximize', () => {
|
// Let us register listeners on the window, so we can update the state
|
||||||
conf.set('maximize', false);
|
// automatically (the listeners will be removed when the window is closed)
|
||||||
});
|
// and restore the maximized or full screen state
|
||||||
|
mainWindowState.manage(win);
|
||||||
win.on('resize', function () {
|
|
||||||
const size = this.getSize();
|
|
||||||
conf.set({
|
|
||||||
width: size[0],
|
|
||||||
height: size[1]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// on osx it's 'moved'
|
|
||||||
win.on('move', function () {
|
|
||||||
const pos = this.getPosition();
|
|
||||||
conf.set({
|
|
||||||
x: pos[0],
|
|
||||||
y: pos[1]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// stop page to update it's title
|
|
||||||
win.on('page-title-updated', (e, title) => {
|
|
||||||
e.preventDefault();
|
|
||||||
updateDockBadge(title);
|
|
||||||
});
|
|
||||||
|
|
||||||
return win;
|
return win;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - fix certificate errors
|
function registerLocalShortcuts(page) {
|
||||||
app.commandLine.appendSwitch('ignore-certificate-errors', 'true');
|
// Somehow, reload action cannot be overwritten by the menu item
|
||||||
|
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
|
||||||
|
page.send('reload-viewer');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also adding these shortcuts because some users might want to use it instead of CMD/Left-Right
|
||||||
|
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
|
||||||
|
page.send('back');
|
||||||
|
});
|
||||||
|
|
||||||
|
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
|
||||||
|
page.send('forward');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-params
|
||||||
|
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
|
||||||
|
event.preventDefault();
|
||||||
|
callback(true);
|
||||||
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
// Unregister all the shortcuts so that they don't interfare with other apps
|
||||||
app.quit();
|
electronLocalshortcut.unregisterAll(mainWindow);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
@@ -146,70 +153,104 @@ app.on('activate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('ready', () => {
|
app.on('ready', () => {
|
||||||
electron.Menu.setApplicationMenu(appMenu);
|
appMenu.setMenu({
|
||||||
|
tabs: []
|
||||||
|
});
|
||||||
mainWindow = createMainWindow();
|
mainWindow = createMainWindow();
|
||||||
tray.create(mainWindow);
|
|
||||||
|
|
||||||
const page = mainWindow.webContents;
|
const page = mainWindow.webContents;
|
||||||
|
|
||||||
// Add spellcheck dictionary
|
registerLocalShortcuts(page);
|
||||||
SpellChecker.getDictionary('en-US', spellDict, (err, result) => {
|
|
||||||
if (!err) {
|
page.on('dom-ready', () => {
|
||||||
myDictionary = result;
|
mainWindow.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
page.once('did-frame-finish-load', () => {
|
||||||
|
const checkOS = isWindowsOrmacOS();
|
||||||
|
if (checkOS && !isDev) {
|
||||||
|
// Initate auto-updates on MacOS and Windows
|
||||||
|
appUpdater();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define function for consult the dictionary.
|
electron.powerMonitor.on('resume', () => {
|
||||||
ipc.on('checkspell', (event, word) => {
|
page.send('reload-viewer');
|
||||||
if (myDictionary !== null && word !== null) {
|
|
||||||
event.returnValue = myDictionary.spellCheck(word);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO - use global shortcut instead
|
ipc.on('focus-app', () => {
|
||||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
|
mainWindow.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.on('quit-app', () => {
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reload full app not just webview, useful in debugging
|
||||||
|
ipc.on('reload-full-app', () => {
|
||||||
mainWindow.reload();
|
mainWindow.reload();
|
||||||
|
page.send('destroytray');
|
||||||
});
|
});
|
||||||
|
|
||||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
|
ipc.on('toggle-app', () => {
|
||||||
if (page.canGoBack()) {
|
if (mainWindow.isVisible()) {
|
||||||
page.goBack();
|
mainWindow.hide();
|
||||||
|
} else {
|
||||||
|
mainWindow.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
|
ipc.on('update-badge', (event, messageCount) => {
|
||||||
if (page.canGoForward()) {
|
if (process.platform === 'darwin') {
|
||||||
page.goForward();
|
app.setBadgeCount(messageCount);
|
||||||
|
}
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
if (!mainWindow.isFocused()) {
|
||||||
|
mainWindow.flashFrame(true);
|
||||||
|
}
|
||||||
|
if (messageCount === 0) {
|
||||||
|
mainWindow.setOverlayIcon(null, '');
|
||||||
|
} else {
|
||||||
|
page.send('render-taskbar-icon', messageCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page.send('tray', messageCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.on('update-taskbar-icon', (event, data, text) => {
|
||||||
|
const img = electron.nativeImage.createFromDataURL(data);
|
||||||
|
mainWindow.setOverlayIcon(img, text);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.on('forward-message', (event, listener, ...params) => {
|
||||||
|
page.send(listener, ...params);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.on('update-menu', (event, props) => {
|
||||||
|
appMenu.setMenu(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.on('register-server-tab-shortcut', (event, index) => {
|
||||||
|
electronLocalshortcut.register(mainWindow, `CommandOrControl+${index}`, () => {
|
||||||
|
// Array index == Shown index - 1
|
||||||
|
page.send('switch-server-tab', index - 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.on('local-shortcuts', (event, enable) => {
|
||||||
|
if (enable) {
|
||||||
|
registerLocalShortcuts(page);
|
||||||
|
} else {
|
||||||
|
electronLocalshortcut.unregisterAll(mainWindow);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// electronLocalshortcut.register(mainWindow, 'CommandOrControl+=', () => {
|
|
||||||
// page.send('zoomIn');
|
|
||||||
// });
|
|
||||||
|
|
||||||
// electronLocalshortcut.register(mainWindow, 'CommandOrControl+-', () => {
|
|
||||||
// page.send('zoomOut');
|
|
||||||
// });
|
|
||||||
|
|
||||||
// electronLocalshortcut.register(mainWindow, 'CommandOrControl+0', () => {
|
|
||||||
// page.send('zoomActualSize');
|
|
||||||
// });
|
|
||||||
|
|
||||||
page.on('new-window', (event, url) => {
|
|
||||||
if (mainWindow.useDefaultWindowBehaviour) {
|
|
||||||
mainWindow.useDefaultWindowBehaviour = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (linkIsInternal(checkWindowURL(), url)) {
|
|
||||||
event.preventDefault();
|
|
||||||
return mainWindow.loadURL(url);
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
electron.shell.openExternal(url);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('new-domain', (e, domain) => {
|
app.on('will-quit', () => {
|
||||||
mainWindow.loadURL(domain);
|
// Unregister all the shortcuts so that they don't interfare with other apps
|
||||||
targetLink = domain;
|
electronLocalshortcut.unregisterAll(mainWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
isQuitting = true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
const wurl = require('wurl');
|
|
||||||
|
|
||||||
// Check link if it's internal/external
|
|
||||||
function linkIsInternal(currentUrl, newUrl) {
|
|
||||||
const currentDomain = wurl('domain', currentUrl);
|
|
||||||
const newDomain = wurl('domain', newUrl);
|
|
||||||
return currentDomain === newDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
exports = module.exports = {
|
|
||||||
linkIsInternal
|
|
||||||
};
|
|
||||||
448
app/main/menu.js
@@ -1,93 +1,125 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const electron = require('electron');
|
const {dialog, app, shell, BrowserWindow, Menu} = require('electron');
|
||||||
|
|
||||||
|
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
|
||||||
|
|
||||||
const app = electron.app;
|
|
||||||
const BrowserWindow = electron.BrowserWindow;
|
|
||||||
const shell = electron.shell;
|
|
||||||
const appName = app.getName();
|
const appName = app.getName();
|
||||||
|
|
||||||
const {addDomain, about} = require('./windowmanager');
|
class AppMenu {
|
||||||
|
getHistorySubmenu() {
|
||||||
function sendAction(action) {
|
return [{
|
||||||
const win = BrowserWindow.getAllWindows()[0];
|
label: 'Back',
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Command+Left' : 'Alt+Left',
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
win.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
win.webContents.send(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewSubmenu = [
|
|
||||||
{
|
|
||||||
label: 'Reload',
|
|
||||||
accelerator: 'CmdOrCtrl+R',
|
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
focusedWindow.reload();
|
AppMenu.sendAction('back');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Forward',
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Command+Right' : 'Alt+Right',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('forward');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewSubmenu() {
|
||||||
|
return [{
|
||||||
|
label: 'Reload',
|
||||||
|
accelerator: 'CommandOrControl+R',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('reload-viewer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Hard Reload',
|
||||||
|
accelerator: 'CommandOrControl+Shift+R',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('hard-reload');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'togglefullscreen'
|
role: 'togglefullscreen'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Zoom In',
|
label: 'Zoom In',
|
||||||
accelerator: 'CommandOrControl+=',
|
accelerator: 'CommandOrControl+=',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('zoomIn');
|
AppMenu.sendAction('zoomIn');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Zoom Out',
|
label: 'Zoom Out',
|
||||||
accelerator: 'CommandOrControl+-',
|
accelerator: 'CommandOrControl+-',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('zoomOut');
|
AppMenu.sendAction('zoomOut');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Actual Size',
|
label: 'Actual Size',
|
||||||
accelerator: 'CommandOrControl+0',
|
accelerator: 'CommandOrControl+0',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('zoomActualSize');
|
AppMenu.sendAction('zoomActualSize');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Toggle Tray Icon',
|
||||||
label: 'Toggle Developer Tools',
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
focusedWindow.webContents.send('toggletray');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Toggle Sidebar',
|
||||||
|
accelerator: 'CommandOrControl+S',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
const newValue = !ConfigUtil.getConfigItem('show-sidebar');
|
||||||
|
focusedWindow.webContents.send('toggle-sidebar', newValue);
|
||||||
|
ConfigUtil.setConfigItem('show-sidebar', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Toggle DevTools for Zulip App',
|
||||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
focusedWindow.webContents.toggleDevTools();
|
focusedWindow.webContents.toggleDevTools();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Toggle DevTools for Active Tab',
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('tab-devtools');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const helpSubmenu = [
|
getHelpSubmenu() {
|
||||||
{
|
return [{
|
||||||
label: `${appName} Website`,
|
label: `${appName} Website`,
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal('https://zulip.org');
|
shell.openExternal('https://zulipchat.com/help/');
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
|
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Report an Issue...',
|
label: 'Report an Issue...',
|
||||||
click() {
|
click() {
|
||||||
const body = `
|
const body = `
|
||||||
@@ -96,240 +128,254 @@ const helpSubmenu = [
|
|||||||
${app.getName()} ${app.getVersion()}
|
${app.getName()} ${app.getVersion()}
|
||||||
Electron ${process.versions.electron}
|
Electron ${process.versions.electron}
|
||||||
${process.platform} ${process.arch} ${os.release()}`;
|
${process.platform} ${process.arch} ${os.release()}`;
|
||||||
|
|
||||||
shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`);
|
shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`);
|
||||||
}
|
}
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const darwinTpl = [
|
getWindowSubmenu(tabs, activeTabIndex) {
|
||||||
|
const initialSubmenu = [{
|
||||||
|
role: 'minimize'
|
||||||
|
}, {
|
||||||
|
role: 'close'
|
||||||
|
}];
|
||||||
|
|
||||||
{
|
if (tabs.length > 0) {
|
||||||
label: `${app.getName()}`,
|
const ShortcutKey = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
|
||||||
submenu: [
|
initialSubmenu.push({
|
||||||
{
|
|
||||||
label: 'Zulip desktop',
|
|
||||||
click() {
|
|
||||||
about();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
|
});
|
||||||
|
for (let i = 0; i < tabs.length; i++) {
|
||||||
|
initialSubmenu.push({
|
||||||
|
label: tabs[i].webview.props.name,
|
||||||
|
accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`,
|
||||||
|
checked: tabs[i].props.index === activeTabIndex,
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('switch-server-tab', tabs[i].props.index);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
type: 'radio'
|
||||||
label: 'Change Zulip Server',
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialSubmenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDarwinTpl(props) {
|
||||||
|
const {tabs, activeTabIndex} = props;
|
||||||
|
|
||||||
|
return [{
|
||||||
|
label: `${app.getName()}`,
|
||||||
|
submenu: [{
|
||||||
|
label: 'Zulip Desktop',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('open-about');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: 'separator'
|
||||||
|
}, {
|
||||||
|
label: 'Settings',
|
||||||
accelerator: 'Cmd+,',
|
accelerator: 'Cmd+,',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
addDomain();
|
AppMenu.sendAction('open-settings');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Keyboard Shortcuts',
|
||||||
label: 'Keyboard shortcuts',
|
|
||||||
accelerator: 'Cmd+K',
|
accelerator: 'Cmd+K',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('shortcut');
|
AppMenu.sendAction('shortcut');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Clear Cache',
|
||||||
|
click() {
|
||||||
|
AppMenu.clearCache();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
label: 'Log Out',
|
label: 'Log Out',
|
||||||
|
accelerator: 'Cmd+L',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('log-out');
|
AppMenu.sendAction('log-out');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'services',
|
role: 'services',
|
||||||
submenu: []
|
submenu: []
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'hide'
|
role: 'hide'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'hideothers'
|
role: 'hideothers'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'unhide'
|
role: 'unhide'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'quit'
|
role: 'quit'
|
||||||
}
|
}]
|
||||||
]
|
}, {
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
submenu: [
|
submenu: [{
|
||||||
{
|
|
||||||
role: 'undo'
|
role: 'undo'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'redo'
|
role: 'redo'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'cut'
|
role: 'cut'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'copy'
|
role: 'copy'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'paste'
|
role: 'paste'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'pasteandmatchstyle'
|
role: 'pasteandmatchstyle'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'delete'
|
role: 'delete'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'selectall'
|
role: 'selectall'
|
||||||
}
|
}]
|
||||||
]
|
}, {
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'View',
|
label: 'View',
|
||||||
submenu: viewSubmenu
|
submenu: this.getViewSubmenu()
|
||||||
},
|
}, {
|
||||||
{
|
label: 'History',
|
||||||
role: 'window',
|
submenu: this.getHistorySubmenu()
|
||||||
submenu: [
|
}, {
|
||||||
{
|
label: 'Window',
|
||||||
role: 'minimize'
|
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'close'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'front'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'help',
|
role: 'help',
|
||||||
submenu: helpSubmenu
|
submenu: this.getHelpSubmenu()
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const otherTpl = [
|
getOtherTpl(props) {
|
||||||
{
|
const {tabs, activeTabIndex} = props;
|
||||||
|
|
||||||
|
return [{
|
||||||
label: 'File',
|
label: 'File',
|
||||||
submenu: [
|
submenu: [{
|
||||||
{
|
label: 'Zulip Desktop',
|
||||||
label: 'Zulip desktop',
|
click(item, focusedWindow) {
|
||||||
click() {
|
if (focusedWindow) {
|
||||||
about();
|
AppMenu.sendAction('open-about');
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
}, {
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Settings',
|
||||||
label: 'Change Zulip Server',
|
|
||||||
accelerator: 'Ctrl+,',
|
accelerator: 'Ctrl+,',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
addDomain();
|
AppMenu.sendAction('open-settings');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Keyboard Shortcuts',
|
||||||
label: 'Keyboard shortcuts',
|
|
||||||
accelerator: 'Ctrl+K',
|
accelerator: 'Ctrl+K',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('shortcut');
|
AppMenu.sendAction('shortcut');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Clear Cache',
|
||||||
|
click() {
|
||||||
|
AppMenu.clearCache();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
label: 'Log Out',
|
label: 'Log Out',
|
||||||
|
accelerator: 'Ctrl+L',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('log-out');
|
AppMenu.sendAction('log-out');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
role: 'quit',
|
||||||
role: 'quit'
|
accelerator: 'Ctrl+Q'
|
||||||
}
|
}]
|
||||||
]
|
}, {
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
submenu: [
|
submenu: [{
|
||||||
{
|
|
||||||
role: 'undo'
|
role: 'undo'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'redo'
|
role: 'redo'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'cut'
|
role: 'cut'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'copy'
|
role: 'copy'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'paste'
|
role: 'paste'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'pasteandmatchstyle'
|
role: 'pasteandmatchstyle'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'delete'
|
role: 'delete'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'selectall'
|
role: 'selectall'
|
||||||
}
|
}]
|
||||||
|
}, {
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'View',
|
label: 'View',
|
||||||
submenu: viewSubmenu
|
submenu: this.getViewSubmenu()
|
||||||
},
|
}, {
|
||||||
{
|
label: 'History',
|
||||||
|
submenu: this.getHistorySubmenu()
|
||||||
|
}, {
|
||||||
|
label: 'Window',
|
||||||
|
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
|
||||||
|
}, {
|
||||||
role: 'help',
|
role: 'help',
|
||||||
submenu: helpSubmenu
|
submenu: this.getHelpSubmenu()
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const tpl = process.platform === 'darwin' ? darwinTpl : otherTpl;
|
static sendAction(action, ...params) {
|
||||||
|
const win = BrowserWindow.getAllWindows()[0];
|
||||||
|
|
||||||
module.exports = electron.Menu.buildFromTemplate(tpl);
|
if (process.platform === 'darwin') {
|
||||||
|
win.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
win.webContents.send(action, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static clearCache() {
|
||||||
|
const win = BrowserWindow.getAllWindows()[0];
|
||||||
|
const ses = win.webContents.session;
|
||||||
|
ses.clearCache(() => {
|
||||||
|
dialog.showMessageBox({type: 'info', buttons: [], message: 'Cache cleared!'});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMenu(props) {
|
||||||
|
const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props);
|
||||||
|
const menu = Menu.buildFromTemplate(tpl);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new AppMenu();
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
const ipcRenderer = require('electron').ipcRenderer;
|
|
||||||
const webFrame = require('electron').webFrame;
|
|
||||||
|
|
||||||
// Implement spellcheck using electron api
|
|
||||||
|
|
||||||
webFrame.setSpellCheckProvider('en-US', false, {
|
|
||||||
spellCheck: text => {
|
|
||||||
const res = ipcRenderer.sendSync('checkspell', text);
|
|
||||||
return res === null ? true : res;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle zooming functionality
|
|
||||||
|
|
||||||
const zoomIn = () => {
|
|
||||||
webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const zoomOut = () => {
|
|
||||||
webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const zoomActualSize = () => {
|
|
||||||
webFrame.setZoomFactor(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get zooming actions from main process
|
|
||||||
ipcRenderer.on('zoomIn', () => {
|
|
||||||
zoomIn();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('zoomOut', () => {
|
|
||||||
zoomOut();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('zoomActualSize', () => {
|
|
||||||
zoomActualSize();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('log-out', () => {
|
|
||||||
// create the menu for the below
|
|
||||||
document.querySelector('.dropdown-toggle').click();
|
|
||||||
|
|
||||||
const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
|
|
||||||
nodes[nodes.length - 1].click();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('shortcut', () => {
|
|
||||||
// create the menu for the below
|
|
||||||
document.querySelector('.dropdown-toggle').click();
|
|
||||||
|
|
||||||
const nodes = document.querySelectorAll('.dropdown-menu li:nth-child(4) a');
|
|
||||||
nodes[nodes.length - 1].click();
|
|
||||||
});
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
const path = require('path');
|
|
||||||
const electron = require('electron');
|
|
||||||
const app = require('electron').app;
|
|
||||||
const {addDomain, about} = require('./windowmanager');
|
|
||||||
|
|
||||||
let tray = null;
|
|
||||||
|
|
||||||
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
|
|
||||||
|
|
||||||
const iconPath = () => {
|
|
||||||
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.create = () => {
|
|
||||||
// Noone is using this feature. so let's hide it for now.
|
|
||||||
// const toggleWin = () => {
|
|
||||||
// if (win.isVisible()) {
|
|
||||||
// win.hide();
|
|
||||||
// } else {
|
|
||||||
// win.show();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
const contextMenu = electron.Menu.buildFromTemplate([
|
|
||||||
{
|
|
||||||
label: 'About',
|
|
||||||
click() {
|
|
||||||
about();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Change Zulip server',
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
addDomain();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Reload',
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
focusedWindow.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Quit',
|
|
||||||
click() {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
tray = new electron.Tray(iconPath());
|
|
||||||
tray.setToolTip(`${app.getName()}`);
|
|
||||||
tray.setContextMenu(contextMenu);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
const path = require('path');
|
|
||||||
const electron = require('electron');
|
|
||||||
|
|
||||||
let domainWindow;
|
|
||||||
let aboutWindow;
|
|
||||||
|
|
||||||
function onClosed() {
|
|
||||||
// dereference the window
|
|
||||||
domainWindow = null;
|
|
||||||
aboutWindow = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change Zulip server Window
|
|
||||||
function createdomainWindow() {
|
|
||||||
const domainwin = new electron.BrowserWindow({
|
|
||||||
frame: false,
|
|
||||||
height: 300,
|
|
||||||
resizable: false,
|
|
||||||
width: 400
|
|
||||||
});
|
|
||||||
const domainURL = 'file://' + path.join(__dirname, '../renderer', 'pref.html');
|
|
||||||
domainwin.loadURL(domainURL);
|
|
||||||
domainwin.on('closed', onClosed);
|
|
||||||
|
|
||||||
return domainwin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this window onClick addDomain in tray
|
|
||||||
function addDomain() {
|
|
||||||
domainWindow = createdomainWindow();
|
|
||||||
domainWindow.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// About window
|
|
||||||
function createAboutWindow() {
|
|
||||||
const aboutwin = new electron.BrowserWindow({
|
|
||||||
width: 500,
|
|
||||||
height: 500,
|
|
||||||
title: 'About Zulip Desktop',
|
|
||||||
show: false,
|
|
||||||
center: true,
|
|
||||||
fullscreen: false,
|
|
||||||
fullscreenable: false,
|
|
||||||
resizable: false
|
|
||||||
});
|
|
||||||
const aboutURL = 'file://' + path.join(__dirname, '../renderer', 'about.html');
|
|
||||||
aboutwin.loadURL(aboutURL);
|
|
||||||
aboutwin.on('closed', onClosed);
|
|
||||||
|
|
||||||
// stop page to update it's title
|
|
||||||
aboutwin.on('page-title-updated', e => {
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
aboutwin.on('closed', onClosed);
|
|
||||||
|
|
||||||
return aboutwin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this onClick About in tray
|
|
||||||
function about() {
|
|
||||||
aboutWindow = createAboutWindow();
|
|
||||||
aboutWindow.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
exports = module.exports = {
|
|
||||||
addDomain, about
|
|
||||||
};
|
|
||||||
41
app/package.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "zulip",
|
||||||
|
"productName": "Zulip",
|
||||||
|
"version": "1.3.0-beta",
|
||||||
|
"description": "Zulip Desktop App",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"email": "<svnitakash@gmail.com>",
|
||||||
|
"copyright": "©2017 Kandra Labs, Inc.",
|
||||||
|
"author": {
|
||||||
|
"name": "Kandra Labs, Inc.",
|
||||||
|
"email": "svnitakash@gmail.com"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/zulip/zulip-electron.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/zulip/zulip-electron/issues"
|
||||||
|
},
|
||||||
|
"main": "main/index.js",
|
||||||
|
"keywords": [
|
||||||
|
"Zulip",
|
||||||
|
"Group Chat app",
|
||||||
|
"electron-app",
|
||||||
|
"electron",
|
||||||
|
"Desktop app",
|
||||||
|
"InstantMessaging"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"electron-debug": "1.4.0",
|
||||||
|
"electron-is-dev": "0.3.0",
|
||||||
|
"electron-localshortcut": "2.0.2",
|
||||||
|
"electron-log": "2.2.7",
|
||||||
|
"electron-spellchecker": "1.2.0",
|
||||||
|
"electron-updater": "2.8.5",
|
||||||
|
"node-json-db": "0.7.3",
|
||||||
|
"request": "2.81.0",
|
||||||
|
"wurl": "2.5.0",
|
||||||
|
"electron-window-state": "4.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,20 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="about">
|
<div class="about">
|
||||||
<center><img src="../resources/zulip.png"></center>
|
<img class="logo" src="../resources/zulip.png" />
|
||||||
<center><p class="detail"> Version : 0.0.1 </p>
|
<p class="detail" id="version">version ?.?.?</p>
|
||||||
<center><p class="detail"> License : Apache </p>
|
<div class="maintenance-info">
|
||||||
<center><p class="detail"> Maintainer : Zulip </p>
|
<p class="detail maintainer">Maintained by Zulip</p>
|
||||||
<p class="left"><a class="bug" onclick="linkInBrowser()" href="#">Found bug?</a></p>
|
<p class="detail license">Available under the Apache License</p>
|
||||||
|
<a class="bug" onclick="linkInBrowser()" href="#">Found bug?</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
const app = require('electron').remote.app;
|
||||||
|
const version_tag = document.getElementById('version');
|
||||||
|
version_tag.innerHTML = 'version ' + app.getVersion();
|
||||||
|
|
||||||
function linkInBrowser(event) {
|
function linkInBrowser(event) {
|
||||||
|
|
||||||
const shell = require('electron').shell;
|
const shell = require('electron').shell;
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
body {
|
body {
|
||||||
background: #6BB6C7;
|
background: #fafafa;
|
||||||
|
font-family: menu, "Helvetica Neue", sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#version {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about {
|
.about {
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
}
|
text-align: center;
|
||||||
|
|
||||||
.left {
|
|
||||||
position: absolute;
|
|
||||||
top:89%;
|
|
||||||
left:76%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.about p {
|
.about p {
|
||||||
@@ -18,10 +25,49 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.about img {
|
.about img {
|
||||||
width:160px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail {
|
.detail {
|
||||||
text-align: left;
|
text-align: center;
|
||||||
margin-left: 35%;
|
}
|
||||||
|
|
||||||
|
.detail.maintainer {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail.license {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-info {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0px;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-info p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-info .bug {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 15px;
|
||||||
|
margin-top: 30px;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #52c2af;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-info .bug:hover {
|
||||||
|
background-color: #32a692;
|
||||||
}
|
}
|
||||||
@@ -1,375 +1,321 @@
|
|||||||
@charset "UTF-8";
|
/*******************
|
||||||
header,
|
* General rules *
|
||||||
section {
|
*******************/
|
||||||
display: block
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
font-size: 100%;
|
|
||||||
overflow-y: scroll;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
html,
|
|
||||||
button,
|
|
||||||
input {
|
|
||||||
font-family: "Helvetica Neue", Arial, sans-serif
|
|
||||||
}
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
-ms-interpolation-mode: bicubic
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
vertical-align: middle
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
fieldset {
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
button,
|
|
||||||
input {
|
#content {
|
||||||
font-size: 100%;
|
display: flex;
|
||||||
margin: 0;
|
height: 100%;
|
||||||
vertical-align: baseline;
|
background: #eee url(../img/ic_loading.gif) no-repeat;
|
||||||
box-sizing: content-box;
|
background-size: 60px 60px;
|
||||||
-webkit-box-sizing: content-box
|
background-position: center;
|
||||||
}
|
}
|
||||||
button,
|
|
||||||
input {
|
#sidebar {
|
||||||
line-height: normal
|
background: #222c31;
|
||||||
|
width: 54px;
|
||||||
|
padding: 27px 0 20px 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
button {
|
|
||||||
cursor: pointer;
|
@font-face {
|
||||||
-webkit-appearance: button;
|
font-family: 'Material Icons';
|
||||||
}
|
font-style: normal;
|
||||||
button::-moz-focus-inner,
|
|
||||||
input::-moz-focus-inner {
|
|
||||||
border: 0;
|
|
||||||
padding: 0
|
|
||||||
}
|
|
||||||
hr {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
max-width: 100%
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #111;
|
|
||||||
line-height: 1em;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||||
text-rendering: optimizelegibility;
|
|
||||||
-webkit-text-stroke: none
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
margin: 0 0 35px
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.container-layout header #logo {
|
|
||||||
|
/*******************
|
||||||
|
* Left Sidebar *
|
||||||
|
*******************/
|
||||||
|
|
||||||
|
#tabs-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-icons {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
/* Preferred icon size */
|
||||||
|
font-size: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
|
/* Support for all WebKit browsers. */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* Support for Safari and Chrome. */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button i {
|
||||||
|
color: #6c8592;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover i {
|
||||||
|
color: #98a9b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:first-child {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
margin: 2px 0;
|
||||||
vertical-align: top
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
body.container-layout header #logo {
|
|
||||||
width: 50px;
|
.tab .server-icons {
|
||||||
height: 50px;
|
border-radius: 50%;
|
||||||
background-position: -790px 0
|
width: 30px;
|
||||||
}
|
padding: 3px;
|
||||||
button[type=submit] {
|
height: 30px;
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
-webkit-box-sizing: border-box;
|
}
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-border-radius: 4px;
|
.tab .server-tab {
|
||||||
border-radius: 4px;
|
width: 100%;
|
||||||
|
height: 35px;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
margin: 5px 0 2px 0;
|
||||||
font-size-adjust: auto;
|
z-index: 11;
|
||||||
vertical-align: bottom;
|
line-height: 31px;
|
||||||
background-color: #e6eaef;
|
color: #eee;
|
||||||
border: 2px solid #e6eaef;
|
text-align: center;
|
||||||
color: #96a0ac;
|
overflow: hidden;
|
||||||
font-size: 20px;
|
opacity: 0.6;
|
||||||
line-height: 26px;
|
padding: 2px 0;
|
||||||
padding: 6px 17px
|
|
||||||
}
|
}
|
||||||
.desktop button[type=submit] {
|
|
||||||
-webkit-transition: all .2s ease-in-out;
|
.tab .server-tab:hover {
|
||||||
transition: all .2s ease-in-out
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
.desktop button[type=submit]:hover {
|
|
||||||
background-color: #eff2f5;
|
.tab.functional-tab {
|
||||||
border: 2px solid #eff2f5;
|
height: 46px;
|
||||||
color: #96a0ac
|
padding: 0;
|
||||||
}
|
}
|
||||||
button[type=submit]:focus {
|
|
||||||
outline: 0
|
.tab.functional-tab.active .server-tab {
|
||||||
|
padding: 2px 0;
|
||||||
|
height: 40px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
button[type=submit].btn-primary {
|
|
||||||
background-color: #20b36c;
|
.tab.functional-tab .server-tab i {
|
||||||
border: 2px solid #20b36c;
|
font-size: 28px;
|
||||||
color: #fff
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
.desktop button[type=submit].btn-primary:hover {
|
|
||||||
background-color: #39ca83;
|
.tab.active .server-tab {
|
||||||
border: 2px solid #39ca83;
|
opacity: 1;
|
||||||
color: #fff
|
background-color: #648478;
|
||||||
}
|
}
|
||||||
button[type=submit].btn-primary:focus {
|
|
||||||
outline: 0
|
.tab .server-tab-badge.active {
|
||||||
}
|
border-radius: 9px;
|
||||||
button[type=submit].btn-large {
|
min-width: 11px;
|
||||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
padding: 0 3px;
|
||||||
font-size: 20px;
|
height: 17px;
|
||||||
line-height: 26px;
|
background-color: #f44336;
|
||||||
padding: 12px 30px
|
font-size: 10px;
|
||||||
}
|
font-family: sans-serif;
|
||||||
input[type=text] {
|
position: absolute;
|
||||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
right: 5px;
|
||||||
font-weight: 200;
|
z-index: 15;
|
||||||
display: inline-block;
|
top: 6px;
|
||||||
vertical-align: top;
|
float: right;
|
||||||
-webkit-border-radius: 4px;
|
color: #fff;
|
||||||
border-radius: 4px;
|
text-align: center;
|
||||||
-webkit-box-sizing: border-box;
|
line-height: 17px;
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #cad0d7;
|
|
||||||
color: #000;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 26px;
|
|
||||||
height: 42px;
|
|
||||||
padding: 10px 10px
|
|
||||||
}
|
|
||||||
.desktop input[type=text]:hover {
|
|
||||||
background-color: #fff;
|
|
||||||
border-color: #bbc3cc;
|
|
||||||
color: #111
|
|
||||||
}
|
|
||||||
input[type=text]:focus {
|
|
||||||
background-color: #fff;
|
|
||||||
border-color: #20b36c!important;
|
|
||||||
color: #000;
|
|
||||||
outline: 0
|
|
||||||
}
|
|
||||||
input[type=text]::-webkit-input-placeholder {
|
|
||||||
color: #8e959e
|
|
||||||
}
|
|
||||||
input[type=text]::-moz-placeholder {
|
|
||||||
color: #8e959e
|
|
||||||
}
|
|
||||||
input[type=text]:-ms-input-placeholder {
|
|
||||||
color: #8e959e
|
|
||||||
}
|
|
||||||
.form-large input[type=text] {
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 26px;
|
|
||||||
height: 54px;
|
|
||||||
padding: 12px 15px
|
|
||||||
}
|
|
||||||
.control-group {
|
|
||||||
margin-top: 40px
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
white-space: normal;
|
|
||||||
word-break: break-all;
|
|
||||||
word-break: break-word;
|
|
||||||
-webkit-hyphens: auto;
|
|
||||||
-moz-hyphens: auto;
|
|
||||||
hyphens: auto
|
|
||||||
}
|
|
||||||
.section-main {
|
|
||||||
position: relative
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
color: #111;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
|
||||||
background: #edf1f3;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box
|
|
||||||
}
|
|
||||||
.desktop body {
|
|
||||||
padding-top: 116px
|
|
||||||
}
|
|
||||||
body.container-layout {
|
|
||||||
padding: 0!important;
|
|
||||||
background-color: #E6EAEF
|
|
||||||
}
|
|
||||||
header {
|
|
||||||
z-index: 800;
|
|
||||||
border-bottom: 1px solid #dae0e7;
|
|
||||||
background: rgba(255, 255, 255, .97);
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
header .container {
|
|
||||||
position: relative
|
|
||||||
}
|
|
||||||
.desktop header {
|
|
||||||
position: fixed
|
|
||||||
}
|
|
||||||
header .container {
|
|
||||||
position: relative;
|
|
||||||
height: 75px
|
|
||||||
}
|
|
||||||
.container-layout header {
|
|
||||||
position: relative;
|
|
||||||
border: 0;
|
|
||||||
background: 0 0;
|
|
||||||
padding: 30px 0
|
|
||||||
}
|
|
||||||
.container-layout header .container {
|
|
||||||
height: auto
|
|
||||||
}
|
|
||||||
header #logo {
|
|
||||||
display: block;
|
display: block;
|
||||||
text-indent: -9999px
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
header #logo {
|
.tab .server-tab-badge {
|
||||||
position: absolute!important;
|
display: none;
|
||||||
top: 50%;
|
|
||||||
left: 10px;
|
|
||||||
-webkit-transform: translate(0, -50%);
|
|
||||||
transform: translate(0, -50%)
|
|
||||||
}
|
}
|
||||||
body.container-layout header #logo {
|
|
||||||
position: relative!important;
|
.tab .server-tab-badge.close-button {
|
||||||
top: 0;
|
width: 16px;
|
||||||
left: 0;
|
padding: 0;
|
||||||
-webkit-transform: translate(0, 0);
|
|
||||||
transform: translate(0, 0);
|
|
||||||
margin: 0 auto;
|
|
||||||
display: block
|
|
||||||
}
|
}
|
||||||
.content {
|
|
||||||
padding-bottom: 40px;
|
.tab .server-tab-badge.close-button i {
|
||||||
overflow: hidden
|
font-size: 13px;
|
||||||
|
line-height: 17px;
|
||||||
}
|
}
|
||||||
.container-layout .content {
|
|
||||||
max-width: 660px;
|
#add-tab {
|
||||||
margin: 0 auto
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.content .server {
|
|
||||||
position: relative;
|
.tab .server-tab-shortcut {
|
||||||
margin: 0 10px
|
color: #648478;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: sans-serif;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
.content .server .container {
|
|
||||||
background: #fff;
|
|
||||||
-webkit-box-sizing: border-box;
|
/*******************
|
||||||
box-sizing: border-box;
|
* Webview Area *
|
||||||
padding: 50px 0 0;
|
*******************/
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
|
#webviews-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
webview {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.3s ease-in;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
webview.onload {
|
||||||
|
transition: opacity 1s cubic-bezier(0.95, 0.05, 0.795, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
|
webview.disabled {
|
||||||
|
flex: 0 1;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
webview:focus {
|
||||||
|
outline: 0px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Tooltip styling */
|
||||||
|
|
||||||
|
#reload-tooltip,
|
||||||
|
#setting-tooltip {
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: #222c31;
|
||||||
|
margin-left: 68px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 0px;
|
||||||
|
z-index: 1000;
|
||||||
|
color: white;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
width: 55px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reload-tooltip:after,
|
||||||
|
#setting-tooltip:after {
|
||||||
|
content: " ";
|
||||||
|
border-top: 8px solid transparent;
|
||||||
|
border-bottom: 8px solid transparent;
|
||||||
|
border-right: 8px solid #222c31;
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#collapse-button {
|
||||||
|
bottom: 30px;
|
||||||
|
left: 20px;
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #222c31;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: #999 1px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#collapse-button i {
|
||||||
|
color: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
|
||||||
max-width: 580px
|
|
||||||
}
|
}
|
||||||
.content .server h1 {
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full screen Popup container */
|
||||||
|
.popup .popuptext {
|
||||||
|
visibility: hidden;
|
||||||
|
background-color: #555;
|
||||||
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0 10%
|
border-radius: 6px;
|
||||||
|
padding: 9px 0;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
font-family: arial;
|
||||||
|
width: 240px;
|
||||||
|
top: 15px;
|
||||||
|
height: 20px;
|
||||||
|
left: 43%;
|
||||||
}
|
}
|
||||||
.content .server h1 {
|
|
||||||
font-size: 2.4em;
|
.popup .show {
|
||||||
line-height: 1.2em;
|
visibility: visible;
|
||||||
margin-bottom: 10px
|
animation: cssAnimation 0s ease-in 5s forwards;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
.content .server fieldset {
|
|
||||||
padding: 25px 10% 80px 39px;
|
@keyframes cssAnimation {
|
||||||
position: relative
|
from {
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.content .server fieldset .control-group .control-field input {
|
to {
|
||||||
width: 100%
|
width: 0;
|
||||||
}
|
height: 0;
|
||||||
.content .server button {
|
overflow: hidden;
|
||||||
width: 100%
|
opacity: 1;
|
||||||
}
|
|
||||||
@media screen and (min-width: 749px) {
|
|
||||||
input[type=text] {
|
|
||||||
width: 60%
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 1071px) {
|
|
||||||
h1 {
|
|
||||||
font-size: 3.2em;
|
|
||||||
line-height: 1.3em
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 1070px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
.container:before,
|
|
||||||
.container:after {
|
|
||||||
content: "";
|
|
||||||
display: table
|
|
||||||
}
|
|
||||||
.container:after {
|
|
||||||
clear: both
|
|
||||||
}
|
|
||||||
.responsive .container {
|
|
||||||
max-width: 1070px;
|
|
||||||
width: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
.responsive .container:before,
|
|
||||||
.responsive .container:after {
|
|
||||||
content: "";
|
|
||||||
display: table
|
|
||||||
}
|
|
||||||
.responsive .container:after {
|
|
||||||
clear: both
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 480px) {
|
|
||||||
.responsive h1 {
|
|
||||||
font-size: 1.7em;
|
|
||||||
line-height: 1.3em
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 481px) and (max-width: 640px) {
|
|
||||||
.responsive h1 {
|
|
||||||
font-size: 1.9em;
|
|
||||||
line-height: 1.3em
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 641px) and (max-width: 748px) {
|
|
||||||
.responsive h1 {
|
|
||||||
font-size: 2.2em;
|
|
||||||
line-height: 1.3em
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 748px) {
|
|
||||||
.responsive input[type=text] {
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 749px) and (max-width: 1070px) {
|
|
||||||
.responsive h1 {
|
|
||||||
font-size: 2.6em;
|
|
||||||
line-height: 1.3em
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#server-status {
|
|
||||||
text-align: center;
|
|
||||||
color: #c71212;
|
|
||||||
}
|
|
||||||
|
|||||||
42
app/renderer/css/network.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
cursor: default;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
background: #fff;
|
||||||
|
user-select:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: "Trebuchet MS", Helvetica, sans-serif;
|
||||||
|
margin: 100px 200px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#description {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reconnect {
|
||||||
|
font-size: 16px;
|
||||||
|
background: #009688;
|
||||||
|
color: #fff;
|
||||||
|
width: 84px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 5px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 20px auto 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reconnect:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
body{
|
|
||||||
background-color: #6BB6C7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form {
|
|
||||||
position: absolute;
|
|
||||||
top: 35%;
|
|
||||||
width: 300px;
|
|
||||||
left: 9%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close {
|
|
||||||
background: transparent url('../img/close.png') no-repeat 4px 4px;
|
|
||||||
background-size: 24px 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
height: 32px;
|
|
||||||
position: absolute;
|
|
||||||
right: 6px;
|
|
||||||
text-indent: -10000px;
|
|
||||||
top: 6px;
|
|
||||||
width: 32px;
|
|
||||||
z-index: 1;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
appearance: none;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: none;
|
|
||||||
color: #646464;
|
|
||||||
}
|
|
||||||
input[type="text"]:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form input[type="text"] {
|
|
||||||
padding: 10px;
|
|
||||||
border: solid 1px #dcdcdc;
|
|
||||||
transition: box-shadow 0.3s, border 0.3s;
|
|
||||||
}
|
|
||||||
.form input[type="text"]:focus,
|
|
||||||
.form input[type="text"].focus {
|
|
||||||
border: solid 1px #707070;
|
|
||||||
box-shadow: 0 0 5px 1px #969696;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
padding: 12px 32px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
margin-left: 107px;
|
|
||||||
margin-top: 24px;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 16px;
|
|
||||||
background: #137b86;
|
|
||||||
}
|
|
||||||
button:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
#urladded {
|
|
||||||
font-size: 20px;
|
|
||||||
position: absolute;
|
|
||||||
font-family: 'opensans';
|
|
||||||
top: -61%;
|
|
||||||
left: 25%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pic {
|
|
||||||
width: 20px;
|
|
||||||
left: 36%;
|
|
||||||
margin-left: 4px;
|
|
||||||
top: 63%;
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
234
app/renderer/css/preference.css
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
cursor: default;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
background: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Material Icons'),
|
||||||
|
local('MaterialIcons-Regular'),
|
||||||
|
url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-icons {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
/* Preferred icon size */
|
||||||
|
font-size: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
|
/* Support for all WebKit browsers. */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* Support for Safari and Chrome. */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar {
|
||||||
|
width: 80px;
|
||||||
|
padding: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-container {
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
padding: 5px 0;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav.active {
|
||||||
|
color: #464e5a;
|
||||||
|
cursor: default;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav.active::before {
|
||||||
|
background: #464e5a;
|
||||||
|
width: 3px;
|
||||||
|
height: 16px;
|
||||||
|
position: absolute;
|
||||||
|
left: -8px;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-header {
|
||||||
|
font-size: 22px;
|
||||||
|
color: #5c6166;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding: 30px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-server-container {
|
||||||
|
margin: 20px 0;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding: 4px 0 6px 0;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1e1e1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
padding: 4px 0 6px 0;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.server-info-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info-left {
|
||||||
|
margin: 10px 20px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info-right {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info-row {
|
||||||
|
display: flex;
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info-alias {
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info-url {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 24px;
|
||||||
|
border: none;
|
||||||
|
border-bottom: #ededed 1px solid;
|
||||||
|
outline-width: 0;
|
||||||
|
background: transparent;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info-value:focus {
|
||||||
|
border-bottom: #388E3C 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-container {
|
||||||
|
display: flex;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #235d3a;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 10px 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action i {
|
||||||
|
margin-right: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-pane {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action.disabled:hover {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action.disabled {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card {
|
||||||
|
display: flex;
|
||||||
|
padding: 16px 30px;
|
||||||
|
margin: 10px 0 20px 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 540px;
|
||||||
|
box-shadow: 1px 2px 4px #bcbcbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
margin: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: #ef5350;
|
||||||
|
background: #ffebee;
|
||||||
|
border: 1px solid #ef5350;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: #388E3C;
|
||||||
|
background: #E8F5E9;
|
||||||
|
border: 1px solid #388E3C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
color: #9E9E9E;
|
||||||
|
background: #FAFAFA;
|
||||||
|
border: 1px solid #9E9E9E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
font-family: Courier New, Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.open-tab-button {
|
||||||
|
padding: 0 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
9
app/renderer/css/preload.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* Override css rules */
|
||||||
|
|
||||||
|
.portico-wrap>.header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portico-container>.footer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
BIN
app/renderer/fonts/MaterialIcons-Regular.ttf
Normal file
|
Before Width: | Height: | Size: 803 B |
BIN
app/renderer/img/ic_loading.gif
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/renderer/img/ic_server_tab_default.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/renderer/img/icon.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 20 KiB |
BIN
app/renderer/img/zulip_network.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
@@ -1,54 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" class="responsive desktop">
|
|
||||||
<!--<![endif]-->
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<title>Login - Zulip</title>
|
|
||||||
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
|
|
||||||
<script type="text/javascript">
|
|
||||||
const JsonDB = require('node-json-db');
|
|
||||||
const {app} = require('electron').remote;
|
|
||||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
|
||||||
const data = db.getData('/');
|
|
||||||
|
|
||||||
if (data.domain) {
|
|
||||||
window.location.href = data.domain;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script>require('electron-connect').client.create()</script>
|
|
||||||
</head>
|
|
||||||
<body class="container-layout">
|
|
||||||
<div class="section-main">
|
|
||||||
<header>
|
|
||||||
<div class="container">
|
|
||||||
<img src="../resources/zulip.png" id="logo"/>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<hr>
|
|
||||||
<section class="content">
|
|
||||||
<section class="server">
|
|
||||||
<div class="container">
|
|
||||||
<h1>Login to Zulip Server</h1>
|
|
||||||
<form id="frm-signInForm" class="form-large" onsubmit="addDomain(); return false">
|
|
||||||
<fieldset>
|
|
||||||
<div class="control-group control-required">
|
|
||||||
<div class="control-field">
|
|
||||||
<input type="text" id="url" autofocus="autofocus" spellcheck="false" placeholder="zulip.tabbott.net">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<div class="control-submit">
|
|
||||||
<button type="submit" id="main" class="btn-primary btn-large" value="Submit" onclick="addDomain();">Connect</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p id="server-status"><p>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<script src="js/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
11
app/renderer/js/components/base.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
class BaseComponent {
|
||||||
|
generateNodeFromTemplate(template) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.innerHTML = template;
|
||||||
|
return wrapper.firstElementChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BaseComponent;
|
||||||
43
app/renderer/js/components/functional-tab.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Tab = require(__dirname + '/../components/tab.js');
|
||||||
|
|
||||||
|
class FunctionalTab extends Tab {
|
||||||
|
template() {
|
||||||
|
return `<div class="tab functional-tab">
|
||||||
|
<div class="server-tab-badge close-button">
|
||||||
|
<i class="material-icons">close</i>
|
||||||
|
</div>
|
||||||
|
<div class="server-tab">
|
||||||
|
<i class="material-icons">${this.props.materialIcon}</i>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.$el = this.generateNodeFromTemplate(this.template());
|
||||||
|
this.props.$root.appendChild(this.$el);
|
||||||
|
|
||||||
|
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerListeners() {
|
||||||
|
super.registerListeners();
|
||||||
|
|
||||||
|
this.$el.addEventListener('mouseover', () => {
|
||||||
|
this.$closeButton.classList.add('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.addEventListener('mouseout', () => {
|
||||||
|
this.$closeButton.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$closeButton.addEventListener('click', e => {
|
||||||
|
this.props.onDestroy();
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FunctionalTab;
|
||||||
58
app/renderer/js/components/server-tab.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Tab = require(__dirname + '/../components/tab.js');
|
||||||
|
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||||
|
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
|
class ServerTab extends Tab {
|
||||||
|
template() {
|
||||||
|
return `<div class="tab">
|
||||||
|
<div class="server-tab-badge"></div>
|
||||||
|
<div class="server-tab">
|
||||||
|
<img class="server-icons" src='${this.props.icon}'/>
|
||||||
|
</div>
|
||||||
|
<div class="server-tab-shortcut">${this.generateShortcutText()}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init();
|
||||||
|
|
||||||
|
this.$badge = this.$el.getElementsByClassName('server-tab-badge')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBadge(count) {
|
||||||
|
if (count > 0) {
|
||||||
|
const formattedCount = count > 999 ? '1K+' : count;
|
||||||
|
|
||||||
|
this.$badge.innerHTML = formattedCount;
|
||||||
|
this.$badge.classList.add('active');
|
||||||
|
} else {
|
||||||
|
this.$badge.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateShortcutText() {
|
||||||
|
// Only provide shortcuts for server [0..10]
|
||||||
|
if (this.props.index >= 10) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const shownIndex = this.props.index + 1;
|
||||||
|
|
||||||
|
let shortcutText = '';
|
||||||
|
|
||||||
|
if (SystemUtil.getOS() === 'Mac') {
|
||||||
|
shortcutText = `⌘ ${shownIndex}`;
|
||||||
|
} else {
|
||||||
|
shortcutText = `Ctrl+${shownIndex}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send('register-server-tab-shortcut', shownIndex);
|
||||||
|
|
||||||
|
return shortcutText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ServerTab;
|
||||||
46
app/renderer/js/components/tab.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||||
|
|
||||||
|
class Tab extends BaseComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.props = props;
|
||||||
|
this.webview = this.props.webview;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.$el = this.generateNodeFromTemplate(this.template());
|
||||||
|
this.props.$root.appendChild(this.$el);
|
||||||
|
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerListeners() {
|
||||||
|
this.$el.addEventListener('click', this.props.onClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading() {
|
||||||
|
return this.webview.isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
activate() {
|
||||||
|
this.$el.classList.add('active');
|
||||||
|
this.webview.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivate() {
|
||||||
|
this.$el.classList.remove('active');
|
||||||
|
this.webview.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.$el.parentNode.removeChild(this.$el);
|
||||||
|
this.webview.$el.parentNode.removeChild(this.webview.$el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Tab;
|
||||||
178
app/renderer/js/components/webview.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
|
||||||
|
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||||
|
const LinkUtil = require(__dirname + '/../utils/link-util.js');
|
||||||
|
const {shell} = require('electron').remote;
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||||
|
|
||||||
|
class WebView extends BaseComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.props = props;
|
||||||
|
|
||||||
|
this.zoomFactor = 1.0;
|
||||||
|
this.loading = false;
|
||||||
|
this.badgeCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template() {
|
||||||
|
return `<webview
|
||||||
|
class="disabled"
|
||||||
|
src="${this.props.url}"
|
||||||
|
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
|
||||||
|
disablewebsecurity
|
||||||
|
${this.props.preload ? 'preload="js/preload.js"' : ''}
|
||||||
|
webpreferences="allowRunningInsecureContent, javascript=yes">
|
||||||
|
</webview>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.$el = this.generateNodeFromTemplate(this.template());
|
||||||
|
this.props.$root.appendChild(this.$el);
|
||||||
|
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerListeners() {
|
||||||
|
this.$el.addEventListener('new-window', event => {
|
||||||
|
const {url} = event;
|
||||||
|
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
|
||||||
|
|
||||||
|
if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.$el.loadURL(url);
|
||||||
|
} else {
|
||||||
|
event.preventDefault();
|
||||||
|
shell.openExternal(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.addEventListener('page-title-updated', event => {
|
||||||
|
const {title} = event;
|
||||||
|
this.badgeCount = this.getBadgeCount(title);
|
||||||
|
this.props.onTitleChange();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.addEventListener('dom-ready', () => {
|
||||||
|
this.$el.classList.add('onload');
|
||||||
|
this.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.addEventListener('did-fail-load', event => {
|
||||||
|
const {errorDescription} = event;
|
||||||
|
const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0);
|
||||||
|
if (hasConnectivityErr) {
|
||||||
|
console.error('error', errorDescription);
|
||||||
|
this.props.onNetworkError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.addEventListener('did-start-loading', () => {
|
||||||
|
let userAgent = SystemUtil.getUserAgent();
|
||||||
|
if (!userAgent) {
|
||||||
|
SystemUtil.setUserAgent(this.$el.getUserAgent());
|
||||||
|
userAgent = SystemUtil.getUserAgent();
|
||||||
|
}
|
||||||
|
this.$el.setUserAgent(userAgent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getBadgeCount(title) {
|
||||||
|
const messageCountInTitle = (/\(([0-9]+)\)/).exec(title);
|
||||||
|
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
// Do not show WebView if another tab was selected and this tab should be in background.
|
||||||
|
if (!this.props.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$el.classList.remove('disabled');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$el.classList.remove('onload');
|
||||||
|
}, 1000);
|
||||||
|
this.focus();
|
||||||
|
this.loading = false;
|
||||||
|
this.props.onTitleChange(this.$el.getTitle());
|
||||||
|
// Injecting preload css in webview to override some css rules
|
||||||
|
this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
// focus Webview and it's contents when Window regain focus.
|
||||||
|
const webContents = this.$el.getWebContents();
|
||||||
|
if (webContents && !webContents.isFocused()) {
|
||||||
|
this.$el.focus();
|
||||||
|
webContents.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.$el.classList.add('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
load() {
|
||||||
|
if (this.$el) {
|
||||||
|
this.show();
|
||||||
|
} else {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomIn() {
|
||||||
|
this.zoomFactor += 0.1;
|
||||||
|
this.$el.setZoomFactor(this.zoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut() {
|
||||||
|
this.zoomFactor -= 0.1;
|
||||||
|
this.$el.setZoomFactor(this.zoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomActualSize() {
|
||||||
|
this.zoomFactor = 1.0;
|
||||||
|
this.$el.setZoomFactor(this.zoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
logOut() {
|
||||||
|
this.$el.executeJavaScript('logout()');
|
||||||
|
}
|
||||||
|
|
||||||
|
showShortcut() {
|
||||||
|
this.$el.executeJavaScript('shortcut()');
|
||||||
|
}
|
||||||
|
|
||||||
|
openDevTools() {
|
||||||
|
this.$el.openDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
back() {
|
||||||
|
if (this.$el.canGoBack()) {
|
||||||
|
this.$el.goBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forward() {
|
||||||
|
if (this.$el.canGoForward()) {
|
||||||
|
this.$el.goForward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
this.hide();
|
||||||
|
this.$el.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
send(...param) {
|
||||||
|
this.$el.send(...param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WebView;
|
||||||
@@ -1,30 +1,334 @@
|
|||||||
/* global app */
|
'use strict';
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
function addDomain() {
|
|
||||||
const request = require('request');
|
|
||||||
const ipcRenderer = require('electron').ipcRenderer;
|
|
||||||
const JsonDB = require('node-json-db');
|
|
||||||
|
|
||||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
require(__dirname + '/js/tray.js');
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
document.getElementById('main').innerHTML = 'checking...';
|
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
|
||||||
|
const WebView = require(__dirname + '/js/components/webview.js');
|
||||||
|
const ServerTab = require(__dirname + '/js/components/server-tab.js');
|
||||||
|
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
|
||||||
|
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
|
||||||
|
|
||||||
let newDomain = document.getElementById('url').value;
|
class ServerManagerView {
|
||||||
newDomain = newDomain.replace(/^https?:\/\//, '');
|
constructor() {
|
||||||
|
this.$addServerButton = document.getElementById('add-tab');
|
||||||
|
this.$tabsContainer = document.getElementById('tabs-container');
|
||||||
|
|
||||||
const domain = 'https://' + newDomain;
|
const $actionsContainer = document.getElementById('actions-container');
|
||||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
|
||||||
|
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
|
||||||
|
this.$webviewsContainer = document.getElementById('webviews-container');
|
||||||
|
|
||||||
request(checkDomain, (error, response) => {
|
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
|
||||||
if (!error && response.statusCode !== 404) {
|
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
|
||||||
document.getElementById('main').innerHTML = 'Connect';
|
this.$sidebar = document.getElementById('sidebar');
|
||||||
db.push('/domain', domain);
|
|
||||||
ipcRenderer.send('new-domain', domain);
|
this.$fullscreenPopup = document.getElementById('fullscreen-popup');
|
||||||
// window.location.href = domain;
|
this.$fullscreenEscapeKey = process.platform === 'darwin' ? '^⌘F' : 'F11';
|
||||||
|
this.$fullscreenPopup.innerHTML = `Press ${this.$fullscreenEscapeKey} to exit full screen`;
|
||||||
|
|
||||||
|
this.activeTabIndex = -1;
|
||||||
|
this.tabs = [];
|
||||||
|
this.functionalTabs = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.initSidebar();
|
||||||
|
this.initTabs();
|
||||||
|
this.initActions();
|
||||||
|
this.registerIpcs();
|
||||||
|
}
|
||||||
|
|
||||||
|
initSidebar() {
|
||||||
|
const showSidebar = ConfigUtil.getConfigItem('show-sidebar', true);
|
||||||
|
this.toggleSidebar(showSidebar);
|
||||||
|
}
|
||||||
|
|
||||||
|
initTabs() {
|
||||||
|
const servers = DomainUtil.getDomains();
|
||||||
|
if (servers.length > 0) {
|
||||||
|
for (let i = 0; i < servers.length; i++) {
|
||||||
|
this.initServer(servers[i], i);
|
||||||
|
}
|
||||||
|
this.activateTab(0);
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('main').innerHTML = 'Connect';
|
this.openSettings('Servers');
|
||||||
document.getElementById('server-status').innerHTML = 'Not a vaild Zulip Server.';
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send('local-shortcuts', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
initServer(server, index) {
|
||||||
|
this.tabs.push(new ServerTab({
|
||||||
|
role: 'server',
|
||||||
|
icon: server.icon,
|
||||||
|
$root: this.$tabsContainer,
|
||||||
|
onClick: this.activateTab.bind(this, index),
|
||||||
|
index,
|
||||||
|
webview: new WebView({
|
||||||
|
$root: this.$webviewsContainer,
|
||||||
|
index,
|
||||||
|
url: server.url,
|
||||||
|
name: server.alias,
|
||||||
|
isActive: () => {
|
||||||
|
return index === this.activeTabIndex;
|
||||||
|
},
|
||||||
|
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
||||||
|
onTitleChange: this.updateBadge.bind(this),
|
||||||
|
nodeIntegration: false,
|
||||||
|
preload: true
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
initActions() {
|
||||||
|
this.$reloadButton.addEventListener('click', () => {
|
||||||
|
this.tabs[this.activeTabIndex].webview.reload();
|
||||||
|
});
|
||||||
|
this.$addServerButton.addEventListener('click', () => {
|
||||||
|
this.openSettings('Servers');
|
||||||
|
});
|
||||||
|
this.$settingsButton.addEventListener('click', () => {
|
||||||
|
this.openSettings('General');
|
||||||
|
});
|
||||||
|
this.$reloadButton.addEventListener('mouseover', () => {
|
||||||
|
this.$reloadTooltip.removeAttribute('style');
|
||||||
|
});
|
||||||
|
this.$reloadButton.addEventListener('mouseout', () => {
|
||||||
|
this.$reloadTooltip.style.display = 'none';
|
||||||
|
});
|
||||||
|
this.$settingsButton.addEventListener('mouseover', () => {
|
||||||
|
this.$settingsTooltip.removeAttribute('style');
|
||||||
|
});
|
||||||
|
this.$settingsButton.addEventListener('mouseout', () => {
|
||||||
|
this.$settingsTooltip.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openFunctionalTab(tabProps) {
|
||||||
|
if (this.functionalTabs[tabProps.name] !== undefined) {
|
||||||
|
this.activateTab(this.functionalTabs[tabProps.name]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.functionalTabs[tabProps.name] = this.tabs.length;
|
||||||
|
|
||||||
|
this.tabs.push(new FunctionalTab({
|
||||||
|
role: 'function',
|
||||||
|
materialIcon: tabProps.materialIcon,
|
||||||
|
$root: this.$tabsContainer,
|
||||||
|
index: this.functionalTabs[tabProps.name],
|
||||||
|
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
|
||||||
|
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
|
||||||
|
webview: new WebView({
|
||||||
|
$root: this.$webviewsContainer,
|
||||||
|
index: this.functionalTabs[tabProps.name],
|
||||||
|
url: tabProps.url,
|
||||||
|
name: tabProps.name,
|
||||||
|
isActive: () => {
|
||||||
|
return this.functionalTabs[tabProps.name] === this.activeTabIndex;
|
||||||
|
},
|
||||||
|
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
||||||
|
onTitleChange: this.updateBadge.bind(this),
|
||||||
|
nodeIntegration: true,
|
||||||
|
preload: false
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.activateTab(this.functionalTabs[tabProps.name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
openSettings(nav = 'General') {
|
||||||
|
this.openFunctionalTab({
|
||||||
|
name: 'Settings',
|
||||||
|
materialIcon: 'settings',
|
||||||
|
url: `file://${__dirname}/preference.html#${nav}`
|
||||||
|
});
|
||||||
|
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
|
||||||
|
}
|
||||||
|
|
||||||
|
openAbout() {
|
||||||
|
this.openFunctionalTab({
|
||||||
|
name: 'About',
|
||||||
|
materialIcon: 'sentiment_very_satisfied',
|
||||||
|
url: `file://${__dirname}/about.html`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openNetworkTroubleshooting() {
|
||||||
|
this.openFunctionalTab({
|
||||||
|
name: 'Network Troubleshooting',
|
||||||
|
materialIcon: 'network_check',
|
||||||
|
url: `file://${__dirname}/network.html`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
activateTab(index, hideOldTab = true) {
|
||||||
|
if (this.tabs[index].webview.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.activeTabIndex !== -1) {
|
||||||
|
if (this.activeTabIndex === index) {
|
||||||
|
return;
|
||||||
|
} else if (hideOldTab) {
|
||||||
|
this.tabs[this.activeTabIndex].deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeTabIndex = index;
|
||||||
|
this.tabs[index].activate();
|
||||||
|
|
||||||
|
ipcRenderer.send('update-menu', {
|
||||||
|
tabs: this.tabs,
|
||||||
|
activeTabIndex: this.activeTabIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyTab(name, index) {
|
||||||
|
if (this.tabs[index].webview.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tabs[index].destroy();
|
||||||
|
|
||||||
|
delete this.tabs[index];
|
||||||
|
delete this.functionalTabs[name];
|
||||||
|
|
||||||
|
// Issue #188: If the functional tab was not focused, do not activate another tab.
|
||||||
|
if (this.activeTabIndex === index) {
|
||||||
|
this.activateTab(0, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyView() {
|
||||||
|
// Clear global variables
|
||||||
|
this.activeTabIndex = -1;
|
||||||
|
this.tabs = [];
|
||||||
|
this.functionalTabs = {};
|
||||||
|
|
||||||
|
// Clear DOM elements
|
||||||
|
this.$tabsContainer.innerHTML = '';
|
||||||
|
this.$webviewsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Destroy shortcuts
|
||||||
|
ipcRenderer.send('local-shortcuts', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadView() {
|
||||||
|
this.destroyView();
|
||||||
|
this.initTabs();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBadge() {
|
||||||
|
let messageCountAll = 0;
|
||||||
|
for (let i = 0; i < this.tabs.length; i++) {
|
||||||
|
if (this.tabs[i] && this.tabs[i].updateBadge) {
|
||||||
|
const count = this.tabs[i].webview.badgeCount;
|
||||||
|
messageCountAll += count;
|
||||||
|
this.tabs[i].updateBadge(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send('update-badge', messageCountAll);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSidebar(show) {
|
||||||
|
if (show) {
|
||||||
|
this.$sidebar.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
this.$sidebar.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerIpcs() {
|
||||||
|
const webviewListeners = {
|
||||||
|
'webview-reload': 'reload',
|
||||||
|
back: 'back',
|
||||||
|
focus: 'focus',
|
||||||
|
forward: 'forward',
|
||||||
|
zoomIn: 'zoomIn',
|
||||||
|
zoomOut: 'zoomOut',
|
||||||
|
zoomActualSize: 'zoomActualSize',
|
||||||
|
'log-out': 'logOut',
|
||||||
|
shortcut: 'showShortcut',
|
||||||
|
'tab-devtools': 'openDevTools'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key in webviewListeners) {
|
||||||
|
ipcRenderer.on(key, () => {
|
||||||
|
const activeWebview = this.tabs[this.activeTabIndex].webview;
|
||||||
|
if (activeWebview) {
|
||||||
|
activeWebview[webviewListeners[key]]();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcRenderer.on('open-settings', (event, settingNav) => {
|
||||||
|
this.openSettings(settingNav);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('open-about', this.openAbout.bind(this));
|
||||||
|
|
||||||
|
ipcRenderer.on('reload-viewer', this.reloadView.bind(this));
|
||||||
|
|
||||||
|
ipcRenderer.on('hard-reload', () => {
|
||||||
|
ipcRenderer.send('reload-full-app');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('switch-server-tab', (event, index) => {
|
||||||
|
this.activateTab(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('toggle-sidebar', (event, show) => {
|
||||||
|
this.toggleSidebar(show);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('enter-fullscreen', () => {
|
||||||
|
this.$fullscreenPopup.classList.add('show');
|
||||||
|
this.$fullscreenPopup.classList.remove('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('leave-fullscreen', () => {
|
||||||
|
this.$fullscreenPopup.classList.remove('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
|
||||||
|
// Create a canvas from unread messagecounts
|
||||||
|
function createOverlayIcon(messageCount) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.height = 128;
|
||||||
|
canvas.width = 128;
|
||||||
|
canvas.style.letterSpacing = '-5px';
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.fillStyle = '#f42020';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(64, 64, 64, 64, 0, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
if (messageCount > 99) {
|
||||||
|
ctx.font = '65px Helvetica';
|
||||||
|
ctx.fillText('99+', 64, 85);
|
||||||
|
} else if (messageCount < 10) {
|
||||||
|
ctx.font = '90px Helvetica';
|
||||||
|
ctx.fillText(String(Math.min(99, messageCount)), 64, 96);
|
||||||
|
} else {
|
||||||
|
ctx.font = '85px Helvetica';
|
||||||
|
ctx.fillText(String(Math.min(99, messageCount)), 64, 90);
|
||||||
|
}
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
const serverManagerView = new ServerManagerView();
|
||||||
|
serverManagerView.init();
|
||||||
|
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
serverManagerView.reloadView();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
22
app/renderer/js/notification.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {remote} = require('electron');
|
||||||
|
|
||||||
|
const ConfigUtil = require(__dirname + '/utils/config-util.js');
|
||||||
|
|
||||||
|
const app = remote.app;
|
||||||
|
|
||||||
|
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
|
||||||
|
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
|
||||||
|
app.setAppUserModelId('org.zulip.zulip-electron');
|
||||||
|
|
||||||
|
const NativeNotification = window.Notification;
|
||||||
|
|
||||||
|
class SilentNotification extends NativeNotification {
|
||||||
|
constructor(title, opts) {
|
||||||
|
opts.silent = ConfigUtil.getConfigItem('silent') || false;
|
||||||
|
super(title, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Notification = SilentNotification;
|
||||||
20
app/renderer/js/pages/network.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
|
class NetworkTroubleshootingView {
|
||||||
|
constructor() {
|
||||||
|
this.$reconnectButton = document.getElementById('reconnect');
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.$reconnectButton.addEventListener('click', () => {
|
||||||
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
const networkTroubleshootingView = new NetworkTroubleshootingView();
|
||||||
|
networkTroubleshootingView.init();
|
||||||
|
};
|
||||||
156
app/renderer/js/pages/preference/general-section.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
|
||||||
|
|
||||||
|
class GeneralSection extends BaseComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
template() {
|
||||||
|
return `
|
||||||
|
<div class="settings-pane" id="server-settings-pane">
|
||||||
|
<div class="title">Tray Options</div>
|
||||||
|
<div id="tray-option-settings" class="settings-card">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-description">Show app icon in system tray</div>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="title">App Updates</div>
|
||||||
|
<div id="betaupdate-option-settings" class="settings-card">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-description">Get beta updates</div>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="title">Desktop Notification</div>
|
||||||
|
<div id="silent-option-settings" class="settings-card">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-description">Mute all sounds from Zulip (requires reload)</div>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="title">User Interface</div>
|
||||||
|
<div id="ui-option-settings" class="settings-card">
|
||||||
|
<div class="setting-row" id="sidebar-option">
|
||||||
|
<div class="setting-description">Show sidebar (<span class="code">CmdOrCtrl+S</span>)</div>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsOptionTemplate(settingOption) {
|
||||||
|
if (settingOption) {
|
||||||
|
return `
|
||||||
|
<div class="action green">
|
||||||
|
<span>On</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return `
|
||||||
|
<div class="action red">
|
||||||
|
<span>Off</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trayOptionTemplate(trayOption) {
|
||||||
|
this.settingsOptionTemplate(trayOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOptionTemplate(updateOption) {
|
||||||
|
this.settingsOptionTemplate(updateOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
silentOptionTemplate(silentOption) {
|
||||||
|
this.settingsOptionTemplate(silentOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebarToggleTemplate(toggleOption) {
|
||||||
|
this.settingsOptionTemplate(toggleOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.props.$root.innerHTML = this.template();
|
||||||
|
this.initTrayOption();
|
||||||
|
this.initUpdateOption();
|
||||||
|
this.initSilentOption();
|
||||||
|
this.initSidebarToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
initTrayOption() {
|
||||||
|
this.$trayOptionSettings = document.querySelector('#tray-option-settings .setting-control');
|
||||||
|
this.$trayOptionSettings.innerHTML = '';
|
||||||
|
|
||||||
|
const trayOption = ConfigUtil.getConfigItem('trayIcon', true);
|
||||||
|
const $trayOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(trayOption));
|
||||||
|
this.$trayOptionSettings.appendChild($trayOption);
|
||||||
|
|
||||||
|
$trayOption.addEventListener('click', () => {
|
||||||
|
const newValue = !ConfigUtil.getConfigItem('trayIcon');
|
||||||
|
ConfigUtil.setConfigItem('trayIcon', newValue);
|
||||||
|
ipcRenderer.send('forward-message', 'toggletray');
|
||||||
|
this.initTrayOption();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initUpdateOption() {
|
||||||
|
this.$updateOptionSettings = document.querySelector('#betaupdate-option-settings .setting-control');
|
||||||
|
this.$updateOptionSettings.innerHTML = '';
|
||||||
|
|
||||||
|
const updateOption = ConfigUtil.getConfigItem('betaUpdate', false);
|
||||||
|
const $updateOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(updateOption));
|
||||||
|
this.$updateOptionSettings.appendChild($updateOption);
|
||||||
|
|
||||||
|
$updateOption.addEventListener('click', () => {
|
||||||
|
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
|
||||||
|
ConfigUtil.setConfigItem('betaUpdate', newValue);
|
||||||
|
this.initUpdateOption();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initSilentOption() {
|
||||||
|
this.$silentOptionSettings = document.querySelector('#silent-option-settings .setting-control');
|
||||||
|
this.$silentOptionSettings.innerHTML = '';
|
||||||
|
|
||||||
|
const silentOption = ConfigUtil.getConfigItem('silent', false);
|
||||||
|
const $silentOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(silentOption));
|
||||||
|
this.$silentOptionSettings.appendChild($silentOption);
|
||||||
|
|
||||||
|
$silentOption.addEventListener('click', () => {
|
||||||
|
const newValue = !ConfigUtil.getConfigItem('silent', true);
|
||||||
|
ConfigUtil.setConfigItem('silent', newValue);
|
||||||
|
this.initSilentOption();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initSidebarToggle() {
|
||||||
|
this.$sidebarOptionSettings = document.querySelector('#ui-option-settings #sidebar-option .setting-control');
|
||||||
|
this.$sidebarOptionSettings.innerHTML = '';
|
||||||
|
|
||||||
|
const sidebarOption = ConfigUtil.getConfigItem('show-sidebar', true);
|
||||||
|
const $sidebarOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(sidebarOption));
|
||||||
|
this.$sidebarOptionSettings.appendChild($sidebarOption);
|
||||||
|
|
||||||
|
$sidebarOption.addEventListener('click', () => {
|
||||||
|
const newValue = !ConfigUtil.getConfigItem('show-sidebar');
|
||||||
|
ConfigUtil.setConfigItem('show-sidebar', newValue);
|
||||||
|
ipcRenderer.send('forward-message', 'toggle-sidebar', newValue);
|
||||||
|
this.initSidebarToggle();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleServerInfoChange() {
|
||||||
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GeneralSection;
|
||||||
67
app/renderer/js/pages/preference/nav.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
|
||||||
|
class PreferenceNav extends BaseComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.props = props;
|
||||||
|
|
||||||
|
this.navItems = ['General', 'Servers'];
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
template() {
|
||||||
|
let navItemsTemplate = '';
|
||||||
|
for (const navItem of this.navItems) {
|
||||||
|
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${navItem}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div>
|
||||||
|
<div id="settings-header">Settings</div>
|
||||||
|
<div id="nav-container">${navItemsTemplate}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.$el = this.generateNodeFromTemplate(this.template());
|
||||||
|
this.props.$root.appendChild(this.$el);
|
||||||
|
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerListeners() {
|
||||||
|
for (const navItem of this.navItems) {
|
||||||
|
const $item = document.getElementById(`nav-${navItem}`);
|
||||||
|
$item.addEventListener('click', () => {
|
||||||
|
this.props.onItemSelected(navItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select(navItemToSelect) {
|
||||||
|
for (const navItem of this.navItems) {
|
||||||
|
if (navItem === navItemToSelect) {
|
||||||
|
this.activate(navItem);
|
||||||
|
} else {
|
||||||
|
this.deactivate(navItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activate(navItem) {
|
||||||
|
const $item = document.getElementById(`nav-${navItem}`);
|
||||||
|
$item.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivate(navItem) {
|
||||||
|
const $item = document.getElementById(`nav-${navItem}`);
|
||||||
|
$item.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PreferenceNav;
|
||||||
57
app/renderer/js/pages/preference/new-server-form.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||||
|
|
||||||
|
class NewServerForm extends BaseComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
template() {
|
||||||
|
return `
|
||||||
|
<div class="settings-card" style="border: solid 1px #4CAF50;">
|
||||||
|
<div class="server-info-right">
|
||||||
|
<div class="server-info-row">
|
||||||
|
<input class="server-info-url" autofocus placeholder="Enter the url of your Zulip server..."/>
|
||||||
|
</div>
|
||||||
|
<div class="server-info-row">
|
||||||
|
<div class="action green server-save-action">
|
||||||
|
<i class="material-icons">check_box</i>
|
||||||
|
<span>Save</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.initForm();
|
||||||
|
this.initActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
initForm() {
|
||||||
|
this.$newServerForm = this.generateNodeFromTemplate(this.template());
|
||||||
|
this.$saveServerButton = this.$newServerForm.getElementsByClassName('server-save-action')[0];
|
||||||
|
this.props.$root.innerHTML = '';
|
||||||
|
this.props.$root.appendChild(this.$newServerForm);
|
||||||
|
|
||||||
|
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-url')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
initActions() {
|
||||||
|
this.$saveServerButton.addEventListener('click', () => {
|
||||||
|
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
|
||||||
|
DomainUtil.addDomain(serverConf).then(() => {
|
||||||
|
this.props.onChange(this.props.index);
|
||||||
|
});
|
||||||
|
}, errorMessage => {
|
||||||
|
alert(errorMessage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NewServerForm;
|
||||||
68
app/renderer/js/pages/preference/preference.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/js/components/base.js');
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
|
const Nav = require(__dirname + '/js/pages/preference/nav.js');
|
||||||
|
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
|
||||||
|
const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js');
|
||||||
|
|
||||||
|
class PreferenceView extends BaseComponent {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.$sidebarContainer = document.getElementById('sidebar');
|
||||||
|
this.$settingsContainer = document.getElementById('settings-container');
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.nav = new Nav({
|
||||||
|
$root: this.$sidebarContainer,
|
||||||
|
onItemSelected: this.handleNavigation.bind(this)
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setDefaultView();
|
||||||
|
this.registerIpcs();
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultView() {
|
||||||
|
let nav = 'General';
|
||||||
|
const hasTag = window.location.hash;
|
||||||
|
if (hasTag) {
|
||||||
|
nav = hasTag.substring(1);
|
||||||
|
}
|
||||||
|
this.handleNavigation(nav);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNavigation(navItem) {
|
||||||
|
this.nav.select(navItem);
|
||||||
|
switch (navItem) {
|
||||||
|
case 'Servers': {
|
||||||
|
this.section = new ServersSection({
|
||||||
|
$root: this.$settingsContainer
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'General': {
|
||||||
|
this.section = new GeneralSection({
|
||||||
|
$root: this.$settingsContainer
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
this.section.init();
|
||||||
|
window.location.hash = `#${navItem}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerIpcs() {
|
||||||
|
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
|
||||||
|
this.handleNavigation(navItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
const preferenceView = new PreferenceView();
|
||||||
|
preferenceView.init();
|
||||||
|
};
|
||||||
77
app/renderer/js/pages/preference/server-info-form.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
'use strict';
|
||||||
|
const {dialog} = require('electron').remote;
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||||
|
|
||||||
|
class ServerInfoForm extends BaseComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
template() {
|
||||||
|
return `
|
||||||
|
<div class="settings-card">
|
||||||
|
<div class="server-info-left">
|
||||||
|
<img class="server-info-icon" src="${this.props.server.icon}"/>
|
||||||
|
</div>
|
||||||
|
<div class="server-info-right">
|
||||||
|
<div class="server-info-row">
|
||||||
|
<span class="server-info-alias">${this.props.server.alias}</span>
|
||||||
|
<i class="material-icons open-tab-button">open_in_new</i>
|
||||||
|
</div>
|
||||||
|
<div class="server-info-row">
|
||||||
|
<input class="server-info-url" disabled value="${this.props.server.url}"/>
|
||||||
|
</div>
|
||||||
|
<div class="server-info-row">
|
||||||
|
<div class="action red server-delete-action">
|
||||||
|
<i class="material-icons">indeterminate_check_box</i>
|
||||||
|
<span>Delete</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.initForm();
|
||||||
|
this.initActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
initForm() {
|
||||||
|
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
|
||||||
|
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0];
|
||||||
|
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
|
||||||
|
this.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0];
|
||||||
|
this.props.$root.appendChild(this.$serverInfoForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
initActions() {
|
||||||
|
this.$deleteServerButton.addEventListener('click', () => {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
buttons: ['YES', 'NO'],
|
||||||
|
defaultId: 0,
|
||||||
|
message: 'Are you sure you want to delete this server?'
|
||||||
|
}, response => {
|
||||||
|
if (response === 0) {
|
||||||
|
DomainUtil.removeDomain(this.props.index);
|
||||||
|
this.props.onChange(this.props.index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$openServerButton.addEventListener('click', () => {
|
||||||
|
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$serverInfoAlias.addEventListener('click', () => {
|
||||||
|
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ServerInfoForm;
|
||||||
81
app/renderer/js/pages/preference/servers-section.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||||
|
const ServerInfoForm = require(__dirname + '/server-info-form.js');
|
||||||
|
const NewServerForm = require(__dirname + '/new-server-form.js');
|
||||||
|
|
||||||
|
class ServersSection extends BaseComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
template() {
|
||||||
|
return `
|
||||||
|
<div class="settings-pane" id="server-settings-pane">
|
||||||
|
<div class="title">Manage Servers</div>
|
||||||
|
<div class="actions-container">
|
||||||
|
<div class="action green" id="new-server-action">
|
||||||
|
<i class="material-icons">add_box</i>
|
||||||
|
<span>Add Server</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="new-server-container" class="hidden"></div>
|
||||||
|
<div class="sub-title" id="existing-servers"></div>
|
||||||
|
<div id="server-info-container"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.initServers();
|
||||||
|
this.initActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
initServers() {
|
||||||
|
this.props.$root.innerHTML = '';
|
||||||
|
|
||||||
|
const servers = DomainUtil.getDomains();
|
||||||
|
this.props.$root.innerHTML = this.template();
|
||||||
|
this.$serverInfoContainer = document.getElementById('server-info-container');
|
||||||
|
this.$existingServers = document.getElementById('existing-servers');
|
||||||
|
this.$newServerContainer = document.getElementById('new-server-container');
|
||||||
|
this.$newServerButton = document.getElementById('new-server-action');
|
||||||
|
|
||||||
|
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
|
||||||
|
// Show Existing servers if servers are there otherwise hide it
|
||||||
|
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing servers';
|
||||||
|
this.initNewServerForm();
|
||||||
|
|
||||||
|
for (let i = 0; i < servers.length; i++) {
|
||||||
|
new ServerInfoForm({
|
||||||
|
$root: this.$serverInfoContainer,
|
||||||
|
server: servers[i],
|
||||||
|
index: i,
|
||||||
|
onChange: this.handleServerInfoChange.bind(this)
|
||||||
|
}).init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initNewServerForm() {
|
||||||
|
new NewServerForm({
|
||||||
|
$root: this.$newServerContainer,
|
||||||
|
onChange: this.handleServerInfoChange.bind(this)
|
||||||
|
}).init();
|
||||||
|
}
|
||||||
|
|
||||||
|
initActions() {
|
||||||
|
this.$newServerContainer.classList.remove('hidden');
|
||||||
|
this.$newServerButton.classList.remove('green');
|
||||||
|
this.$newServerButton.classList.add('grey');
|
||||||
|
}
|
||||||
|
|
||||||
|
handleServerInfoChange() {
|
||||||
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ServersSection;
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
const {remote} = require('electron');
|
|
||||||
|
|
||||||
const prefWindow = remote.getCurrentWindow();
|
|
||||||
|
|
||||||
document.getElementById('close-button').addEventListener('click', () => {
|
|
||||||
prefWindow.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
|
||||||
if (event.key === 'Escape' || event.keyCode === 27) {
|
|
||||||
prefWindow.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
function addDomain() {
|
|
||||||
const request = require('request');
|
|
||||||
const ipcRenderer = require('electron').ipcRenderer;
|
|
||||||
const JsonDB = require('node-json-db');
|
|
||||||
const {app} = require('electron').remote;
|
|
||||||
|
|
||||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
|
||||||
document.getElementById('main').innerHTML = 'checking...';
|
|
||||||
document.getElementById('pic').style.display = 'block';
|
|
||||||
|
|
||||||
let newDomain = document.getElementById('url').value;
|
|
||||||
newDomain = newDomain.replace(/^https?:\/\//, '');
|
|
||||||
|
|
||||||
const domain = 'https://' + newDomain;
|
|
||||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
|
||||||
|
|
||||||
request(checkDomain, (error, response) => {
|
|
||||||
if (!error && response.statusCode !== 404) {
|
|
||||||
document.getElementById('pic').style.display = 'none';
|
|
||||||
document.getElementById('main').innerHTML = 'Switch';
|
|
||||||
document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
|
|
||||||
db.push('/domain', domain);
|
|
||||||
ipcRenderer.send('new-domain', domain);
|
|
||||||
} else {
|
|
||||||
document.getElementById('pic').style.display = 'none';
|
|
||||||
document.getElementById('main').innerHTML = 'Switch';
|
|
||||||
document.getElementById('urladded').innerHTML = 'Not a vaild Zulip server.';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
42
app/renderer/js/preload.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
const {spellChecker} = require('./spellchecker');
|
||||||
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
|
require('./notification');
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
// Create the menu for the below
|
||||||
|
document.querySelector('.dropdown-toggle').click();
|
||||||
|
|
||||||
|
const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
|
||||||
|
nodes[nodes.length - 1].click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const shortcut = () => {
|
||||||
|
// Create the menu for the below
|
||||||
|
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
|
||||||
|
// Additional check
|
||||||
|
if (node.text.trim().toLowerCase() === 'keyboard shortcuts') {
|
||||||
|
node.click();
|
||||||
|
} else {
|
||||||
|
// Atleast click the dropdown
|
||||||
|
document.querySelector('.dropdown-toggle').click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.once('loaded', () => {
|
||||||
|
global.logout = logout;
|
||||||
|
global.shortcut = shortcut;
|
||||||
|
});
|
||||||
|
|
||||||
|
// To prevent failing this script on linux we need to load it after the document loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Init spellchecker
|
||||||
|
spellChecker();
|
||||||
|
|
||||||
|
// redirect users to network troubleshooting page
|
||||||
|
document.querySelector('.restart_get_events_button').addEventListener('click', () => {
|
||||||
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
|
});
|
||||||
|
});
|
||||||
29
app/renderer/js/spellchecker.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
|
||||||
|
|
||||||
|
function spellChecker() {
|
||||||
|
// Implement spellcheck using electron api
|
||||||
|
window.spellCheckHandler = new SpellCheckHandler();
|
||||||
|
window.spellCheckHandler.attachToInput();
|
||||||
|
|
||||||
|
// Start off as US English
|
||||||
|
window.spellCheckHandler.switchLanguage('en-US');
|
||||||
|
|
||||||
|
const contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler);
|
||||||
|
const contextMenuListener = new ContextMenuListener(info => {
|
||||||
|
contextMenuBuilder.showPopupMenu(info);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up events after you navigate away from this page;
|
||||||
|
// otherwise, you may experience errors
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
spellCheckHandler.unsubscribe();
|
||||||
|
contextMenuListener.unsubscribe();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
spellChecker
|
||||||
|
};
|
||||||
223
app/renderer/js/tray.js
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
'use strict';
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {ipcRenderer, remote} = electron;
|
||||||
|
|
||||||
|
const {Tray, Menu, nativeImage, BrowserWindow} = remote;
|
||||||
|
|
||||||
|
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
|
||||||
|
|
||||||
|
const ConfigUtil = require(__dirname + '/utils/config-util.js');
|
||||||
|
|
||||||
|
const iconPath = () => {
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
return APP_ICON + 'linux.png';
|
||||||
|
}
|
||||||
|
return APP_ICON + (process.platform === 'win32' ? 'win.ico' : 'osx.png');
|
||||||
|
};
|
||||||
|
|
||||||
|
let unread = 0;
|
||||||
|
|
||||||
|
const trayIconSize = () => {
|
||||||
|
switch (process.platform) {
|
||||||
|
case 'darwin':
|
||||||
|
return 20;
|
||||||
|
case 'win32':
|
||||||
|
return 100;
|
||||||
|
case 'linux':
|
||||||
|
return 100;
|
||||||
|
default: return 80;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default config for Icon we might make it OS specific if needed like the size
|
||||||
|
const config = {
|
||||||
|
pixelRatio: window.devicePixelRatio,
|
||||||
|
unreadCount: 0,
|
||||||
|
showUnreadCount: true,
|
||||||
|
unreadColor: '#000000',
|
||||||
|
readColor: '#000000',
|
||||||
|
unreadBackgroundColor: '#B9FEEA',
|
||||||
|
readBackgroundColor: '#B9FEEA',
|
||||||
|
size: trayIconSize(),
|
||||||
|
thick: process.platform === 'win32'
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCanvas = function (arg) {
|
||||||
|
config.unreadCount = arg;
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const SIZE = config.size * config.pixelRatio;
|
||||||
|
const PADDING = SIZE * 0.05;
|
||||||
|
const CENTER = SIZE / 2;
|
||||||
|
const HAS_COUNT = config.showUnreadCount && config.unreadCount;
|
||||||
|
const color = config.unreadCount ? config.unreadColor : config.readColor;
|
||||||
|
const backgroundColor = config.unreadCount ? config.unreadBackgroundColor : config.readBackgroundColor;
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = SIZE;
|
||||||
|
canvas.height = SIZE;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// Circle
|
||||||
|
// If (!config.thick || config.thick && HAS_COUNT) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(CENTER, CENTER, (SIZE / 2) - PADDING, 0, 2 * Math.PI, false);
|
||||||
|
ctx.fillStyle = backgroundColor;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.lineWidth = SIZE / (config.thick ? 10 : 20);
|
||||||
|
ctx.strokeStyle = backgroundColor;
|
||||||
|
ctx.stroke();
|
||||||
|
// Count or Icon
|
||||||
|
if (HAS_COUNT) {
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
if (config.unreadCount > 99) {
|
||||||
|
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.4}px Helvetica`;
|
||||||
|
ctx.fillText('99+', CENTER, CENTER + (SIZE * 0.15));
|
||||||
|
} else if (config.unreadCount < 10) {
|
||||||
|
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
|
||||||
|
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.20));
|
||||||
|
} else {
|
||||||
|
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
|
||||||
|
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(canvas);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Renders the tray icon as a native image
|
||||||
|
* @param arg: Unread count
|
||||||
|
* @return the native image
|
||||||
|
*/
|
||||||
|
const renderNativeImage = function (arg) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => renderCanvas(arg))
|
||||||
|
.then(canvas => {
|
||||||
|
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPng();
|
||||||
|
return Promise.resolve(nativeImage.createFromBuffer(pngData, config.pixelRatio));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function sendAction(action) {
|
||||||
|
const win = BrowserWindow.getAllWindows()[0];
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
win.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
win.webContents.send(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTray = function () {
|
||||||
|
window.tray = new Tray(iconPath());
|
||||||
|
const contextMenu = Menu.buildFromTemplate([{
|
||||||
|
label: 'About',
|
||||||
|
click() {
|
||||||
|
// We need to focus the main window first
|
||||||
|
ipcRenderer.send('focus-app');
|
||||||
|
sendAction('open-about');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Focus',
|
||||||
|
click() {
|
||||||
|
ipcRenderer.send('focus-app');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
click() {
|
||||||
|
ipcRenderer.send('focus-app');
|
||||||
|
sendAction('open-settings');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
click() {
|
||||||
|
ipcRenderer.send('quit-app');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
window.tray.setContextMenu(contextMenu);
|
||||||
|
window.tray.on('click', () => {
|
||||||
|
// Click event only works on Windows
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
ipcRenderer.send('toggle-app');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ipcRenderer.on('destroytray', event => {
|
||||||
|
if (!window.tray) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.tray.destroy();
|
||||||
|
if (window.tray.isDestroyed()) {
|
||||||
|
window.tray = null;
|
||||||
|
} else {
|
||||||
|
throw new Error('Tray icon not properly destroyed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('tray', (event, arg) => {
|
||||||
|
if (!window.tray) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We don't want to create tray from unread messages on windows and macOS since these systems already have dock badges and taskbar overlay icon.
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
if (arg === 0) {
|
||||||
|
unread = arg;
|
||||||
|
// Message Count // console.log("message count is zero.");
|
||||||
|
window.tray.setImage(iconPath());
|
||||||
|
window.tray.setToolTip('No unread messages');
|
||||||
|
} else {
|
||||||
|
unread = arg;
|
||||||
|
renderNativeImage(arg).then(image => {
|
||||||
|
window.tray.setImage(image);
|
||||||
|
window.tray.setToolTip(arg + ' unread messages');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleTray() {
|
||||||
|
if (window.tray) {
|
||||||
|
window.tray.destroy();
|
||||||
|
if (window.tray.isDestroyed()) {
|
||||||
|
window.tray = null;
|
||||||
|
}
|
||||||
|
ConfigUtil.setConfigItem('trayIcon', false);
|
||||||
|
} else {
|
||||||
|
createTray();
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
renderNativeImage(unread).then(image => {
|
||||||
|
window.tray.setImage(image);
|
||||||
|
window.tray.setToolTip(unread + ' unread messages');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ConfigUtil.setConfigItem('trayIcon', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.on('toggletray', toggleTray);
|
||||||
|
|
||||||
|
if (ConfigUtil.getConfigItem('trayIcon', true)) {
|
||||||
|
createTray();
|
||||||
|
}
|
||||||
53
app/renderer/js/utils/config-util.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const process = require('process');
|
||||||
|
const JsonDB = require('node-json-db');
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
let app = null;
|
||||||
|
|
||||||
|
/* To make the util runnable in both main and renderer process */
|
||||||
|
if (process.type === 'renderer') {
|
||||||
|
app = require('electron').remote.app;
|
||||||
|
} else {
|
||||||
|
app = require('electron').app;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigUtil {
|
||||||
|
constructor() {
|
||||||
|
if (instance) {
|
||||||
|
return instance;
|
||||||
|
} else {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadDB();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfigItem(key, defaultValue = null) {
|
||||||
|
const value = this.db.getData('/')[key];
|
||||||
|
if (value === undefined) {
|
||||||
|
this.setConfigItem(key, defaultValue);
|
||||||
|
return defaultValue;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfigItem(key, value) {
|
||||||
|
this.db.push(`/${key}`, value, true);
|
||||||
|
this.reloadDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeConfigItem(key) {
|
||||||
|
this.db.delete(`/${key}`);
|
||||||
|
this.reloadDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadDB() {
|
||||||
|
this.db = new JsonDB(app.getPath('userData') + '/settings.json', true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new ConfigUtil();
|
||||||
183
app/renderer/js/utils/domain-util.js
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {app, dialog} = require('electron').remote;
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const JsonDB = require('node-json-db');
|
||||||
|
const request = require('request');
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
const defaultIconUrl = '../renderer/img/icon.png';
|
||||||
|
|
||||||
|
class DomainUtil {
|
||||||
|
constructor() {
|
||||||
|
if (instance) {
|
||||||
|
return instance;
|
||||||
|
} else {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadDB();
|
||||||
|
// Migrate from old schema
|
||||||
|
if (this.db.getData('/').domain) {
|
||||||
|
this.addDomain({
|
||||||
|
alias: 'Zulip',
|
||||||
|
url: this.db.getData('/domain')
|
||||||
|
});
|
||||||
|
this.db.delete('/domain');
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDomains() {
|
||||||
|
this.reloadDB();
|
||||||
|
if (this.db.getData('/').domains === undefined) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return this.db.getData('/domains');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDomain(index) {
|
||||||
|
this.reloadDB();
|
||||||
|
return this.db.getData(`/domains[${index}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
addDomain(server) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (server.icon) {
|
||||||
|
this.saveServerIcon(server.icon).then(localIconUrl => {
|
||||||
|
server.icon = localIconUrl;
|
||||||
|
this.db.push('/domains[]', server, true);
|
||||||
|
this.reloadDB();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
server.icon = defaultIconUrl;
|
||||||
|
this.db.push('/domains[]', server, true);
|
||||||
|
this.reloadDB();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeDomains() {
|
||||||
|
this.db.delete('/domains');
|
||||||
|
this.reloadDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeDomain(index) {
|
||||||
|
this.db.delete(`/domains[${index}]`);
|
||||||
|
this.reloadDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDomain(domain) {
|
||||||
|
const hasPrefix = (domain.indexOf('http') === 0);
|
||||||
|
if (!hasPrefix) {
|
||||||
|
domain = (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkDomain = domain + '/static/audio/zulip.ogg';
|
||||||
|
|
||||||
|
const serverConf = {
|
||||||
|
icon: defaultIconUrl,
|
||||||
|
url: domain,
|
||||||
|
alias: domain
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request(checkDomain, (error, response) => {
|
||||||
|
const certsError =
|
||||||
|
['Error: self signed certificate',
|
||||||
|
'Error: unable to verify the first certificate'
|
||||||
|
];
|
||||||
|
if (!error && response.statusCode !== 404) {
|
||||||
|
// Correct
|
||||||
|
this.getServerSettings(domain).then(serverSettings => {
|
||||||
|
resolve(serverSettings);
|
||||||
|
}, () => {
|
||||||
|
resolve(serverConf);
|
||||||
|
});
|
||||||
|
} else if (certsError.indexOf(error.toString()) >= 0) {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'question',
|
||||||
|
buttons: ['Yes', 'No'],
|
||||||
|
defaultId: 0,
|
||||||
|
message: `Do you trust certificate from ${domain}? \n ${error}`
|
||||||
|
}, response => {
|
||||||
|
if (response === 0) {
|
||||||
|
this.getServerSettings(domain).then(serverSettings => {
|
||||||
|
resolve(serverSettings);
|
||||||
|
}, () => {
|
||||||
|
resolve(serverConf);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject('Untrusted Certificate.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject('Not a valid Zulip server');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getServerSettings(domain) {
|
||||||
|
const serverSettingsUrl = domain + '/api/v1/server_settings';
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request(serverSettingsUrl, (error, response) => {
|
||||||
|
if (!error && response.statusCode === 200) {
|
||||||
|
const data = JSON.parse(response.body);
|
||||||
|
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
|
||||||
|
resolve({
|
||||||
|
icon: data.realm_uri + data.realm_icon,
|
||||||
|
url: data.realm_uri,
|
||||||
|
alias: data.realm_name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject('Zulip server version < 1.6.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveServerIcon(url) {
|
||||||
|
// The save will always succeed. If url is invalid, downgrade to default icon.
|
||||||
|
const dir = `${app.getPath('userData')}/server-icons`;
|
||||||
|
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const filePath = `${dir}/${new Date().getMilliseconds()}${path.extname(url).split('?')[0]}`;
|
||||||
|
const file = fs.createWriteStream(filePath);
|
||||||
|
try {
|
||||||
|
request(url).on('response', response => {
|
||||||
|
response.on('error', err => {
|
||||||
|
console.log(err);
|
||||||
|
resolve(defaultIconUrl);
|
||||||
|
});
|
||||||
|
response.pipe(file).on('finish', () => {
|
||||||
|
resolve(filePath);
|
||||||
|
});
|
||||||
|
}).on('error', err => {
|
||||||
|
console.log(err);
|
||||||
|
resolve(defaultIconUrl);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
resolve(defaultIconUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadDB() {
|
||||||
|
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new DomainUtil();
|
||||||
26
app/renderer/js/utils/link-util.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const wurl = require('wurl');
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
class LinkUtil {
|
||||||
|
constructor() {
|
||||||
|
if (instance) {
|
||||||
|
return instance;
|
||||||
|
} else {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInternal(currentUrl, newUrl) {
|
||||||
|
const currentDomain = wurl('hostname', currentUrl);
|
||||||
|
const newDomain = wurl('hostname', newUrl);
|
||||||
|
|
||||||
|
return (currentDomain === newDomain) && newUrl.includes('/#narrow');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new LinkUtil();
|
||||||
55
app/renderer/js/utils/system-util.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {app} = require('electron').remote;
|
||||||
|
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
class SystemUtil {
|
||||||
|
constructor() {
|
||||||
|
if (instance) {
|
||||||
|
return instance;
|
||||||
|
} else {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectivityERR = [
|
||||||
|
'ERR_INTERNET_DISCONNECTED',
|
||||||
|
'ERR_PROXY_CONNECTION_FAILED',
|
||||||
|
'ERR_CONNECTION_RESET',
|
||||||
|
'ERR_NOT_CONNECTED',
|
||||||
|
'ERR_NAME_NOT_RESOLVED',
|
||||||
|
'ERR_NETWORK_CHANGED'
|
||||||
|
];
|
||||||
|
this.userAgent = null;
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOS() {
|
||||||
|
if (os.platform() === 'darwin') {
|
||||||
|
return 'Mac';
|
||||||
|
}
|
||||||
|
if (os.platform() === 'linux') {
|
||||||
|
return 'Linux';
|
||||||
|
}
|
||||||
|
if (os.platform() === 'win32' || os.platform() === 'win64') {
|
||||||
|
if (parseFloat(os.release()) < 6.2) {
|
||||||
|
return 'Windows 7';
|
||||||
|
} else {
|
||||||
|
return 'Windows 10';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserAgent(webViewUserAgent) {
|
||||||
|
this.userAgent = 'ZulipElectron/' + app.getVersion() + ' ' + webViewUserAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserAgent() {
|
||||||
|
return this.userAgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new SystemUtil();
|
||||||
43
app/renderer/main.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="responsive desktop">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title>Zulip</title>
|
||||||
|
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<div class="popup">
|
||||||
|
<span class="popuptext hidden" id="fullscreen-popup"></span>
|
||||||
|
</div>
|
||||||
|
<div id="sidebar">
|
||||||
|
<div id="view-controls-container">
|
||||||
|
<div id="tabs-container"></div>
|
||||||
|
<div id="add-tab" class="tab functional-tab">
|
||||||
|
<div class="server-tab" id="add-action">
|
||||||
|
<i class="material-icons">add</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="actions-container">
|
||||||
|
<div class="action-button" id="reload-action">
|
||||||
|
<i class="material-icons md-48">refresh</i>
|
||||||
|
<span id="reload-tooltip" style="display:none">Reload</span>
|
||||||
|
</div>
|
||||||
|
<div class="action-button" id="settings-action">
|
||||||
|
<i class="material-icons md-48">settings</i>
|
||||||
|
<span id="setting-tooltip" style="display:none">Settings</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="main-container">
|
||||||
|
<div id="webviews-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
|
||||||
|
</html>
|
||||||
21
app/renderer/network.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="responsive desktop">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title>Zulip - Network Troubleshooting</title>
|
||||||
|
<link rel="stylesheet" href="css/network.css" type="text/css" media="screen">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<div id="picture"><img src="img/zulip_network.png"></div>
|
||||||
|
<div id="title">Zulip can't connect</div>
|
||||||
|
<div id="description">
|
||||||
|
<div>Your computer seems to be offline.</div>
|
||||||
|
<div>We will keep trying to reconnect, or you can try now.</div>
|
||||||
|
</div>
|
||||||
|
<div id="reconnect">Try now</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="js/pages/network.js"></script>
|
||||||
|
</html>
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<link rel="stylesheet" href="css/pref.css">
|
|
||||||
<script type="text/javascript">
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="close" id="close-button">Close</div>
|
|
||||||
<div class="form">
|
|
||||||
<form onsubmit="addDomain(); return false">
|
|
||||||
<input id="url" type="text" placeholder="zulip.example.com">
|
|
||||||
<button type="submit" id="main" value="Submit" onclick="addDomain();">
|
|
||||||
Switch</button>
|
|
||||||
<img id="pic" src="img/loader.gif" />
|
|
||||||
</form>
|
|
||||||
<p id="urladded"><p>
|
|
||||||
</div>
|
|
||||||
<script src="js/pref.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
16
app/renderer/preference.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="responsive desktop">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title>Zulip - Settings</title>
|
||||||
|
<link rel="stylesheet" href="css/preference.css" type="text/css" media="screen">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<div id="sidebar"></div>
|
||||||
|
<div id="settings-container"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="js/pages/preference/preference.js"></script>
|
||||||
|
</html>
|
||||||
BIN
app/resources/.DS_Store
vendored
|
Before Width: | Height: | Size: 333 B After Width: | Height: | Size: 2.6 KiB |
BIN
app/resources/tray/traylinux.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/resources/tray/trayosx.png
Normal file
|
After Width: | Height: | Size: 737 B |
BIN
app/resources/tray/traywin.ico
Normal file
|
After Width: | Height: | Size: 361 KiB |
23
appveyor.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: build{build}
|
||||||
|
|
||||||
|
platform:
|
||||||
|
- x64
|
||||||
|
os: Previous Visual Studio 2015
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- node_modules
|
||||||
|
|
||||||
|
install:
|
||||||
|
- ps: Install-Product node 6 x64
|
||||||
|
- git reset --hard HEAD
|
||||||
|
- npm install npm -g
|
||||||
|
- node --version
|
||||||
|
- npm --version
|
||||||
|
- python --version
|
||||||
|
- npm install
|
||||||
|
- npm install -g gulp
|
||||||
|
|
||||||
|
build: off
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- npm run test
|
||||||
BIN
build/appdmg.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
16
build/entitlements.mas.plist
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-only</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.downloads.read-write</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
build/icon.icns
Normal file
BIN
build/icon.ico
Normal file
|
After Width: | Height: | Size: 361 KiB |
BIN
build/icon.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
3
build/icons/1024x1024.png
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1b92ae5c6e5fb0e5b7fcdb46fe102fa0ca7f4d150016cf1a461c0e86921bc731
|
||||||
|
size 163336
|
||||||
BIN
build/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
build/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 737 B |
BIN
build/icons/24x24.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
build/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
build/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
build/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
build/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
build/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
build/icons/96x96.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
build/zulip.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
84
development.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Development setup
|
||||||
|
|
||||||
|
This is a guide to running the Zulip desktop app from a source tree,
|
||||||
|
in order to contribute to developing it.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To build and run the app from source, you'll need the following:
|
||||||
|
|
||||||
|
* [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||||
|
* [Node.js](https://nodejs.org) >= v6.9.0
|
||||||
|
* [NPM](https://www.npmjs.com/get-npm) and
|
||||||
|
[node-gyp](https://github.com/nodejs/node-gyp#installation),
|
||||||
|
if they don't come bundled with your Node.js installation
|
||||||
|
* [Python](https://www.python.org/downloads/release/python-2713/)
|
||||||
|
(v2.7.x recommended)
|
||||||
|
* A C++ compiler compatible with C++11
|
||||||
|
* Development headers for the libXext, libXtst, and libxkbfile libraries
|
||||||
|
|
||||||
|
### Debian/Ubuntu and friends
|
||||||
|
|
||||||
|
On a system running Debian, Ubuntu, or another Debian-based Linux
|
||||||
|
distribution, you can install all dependencies through the package
|
||||||
|
manager (see [here][nodesource-install] for more on the first command):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
|
||||||
|
$ sudo apt install git nodejs python build-essential libxext-dev libxtst-dev libxkbfile-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
[nodesource-install]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
|
||||||
|
|
||||||
|
### Other OSes
|
||||||
|
|
||||||
|
Other developers run the app on Windows, macOS, and possibly other OSes.
|
||||||
|
PRs to add specific instructions to this doc are welcome!
|
||||||
|
|
||||||
|
On Windows, your C++ compiler should be Visual Studio 2015 or later.
|
||||||
|
|
||||||
|
## Download, build, and run
|
||||||
|
|
||||||
|
Clone the source locally:
|
||||||
|
```sh
|
||||||
|
$ git clone https://github.com/zulip/zulip-electron
|
||||||
|
$ cd zulip-electron
|
||||||
|
```
|
||||||
|
|
||||||
|
Install project dependencies:
|
||||||
|
```sh
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the app:
|
||||||
|
```sh
|
||||||
|
$ npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Start and watch changes:
|
||||||
|
```sh
|
||||||
|
$ npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you have any problems running the app, see the [most common
|
||||||
|
issues](./troubleshooting.md).
|
||||||
|
|
||||||
|
## Making a release
|
||||||
|
|
||||||
|
To package the app into an installer:
|
||||||
|
```
|
||||||
|
npm run dist
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will produce distributable packages or installers for the
|
||||||
|
operating system you're running on:
|
||||||
|
* on Windows, a Windows installer file
|
||||||
|
* on macOS, a `.dmg` file
|
||||||
|
* on Linux, a plain `.zip` file as well as a `.deb` file and an
|
||||||
|
`AppImage` file.
|
||||||
|
To generate all three types, you will need all three operating
|
||||||
|
systems.
|
||||||
|
|
||||||
|
The output files appear in the `dist/` directory.
|
||||||
2
docs/Home.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Installation instructions
|
||||||
|
* [[Windows]]
|
||||||
1
docs/Windows.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
** Windows Set up instructions **
|
||||||
3
docs/_Footer.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
### Want to contribute to this Wiki?
|
||||||
|
|
||||||
|
[Edit `/docs` files and send a pull request.](https://github.com/zulip/zulip-electron/tree/master/docs)
|
||||||
26
docs/desktop-release.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# New release checklist -
|
||||||
|
|
||||||
|
## We need to cross check following things before pushing a new release + after updating electron version. This is just to make sure that nothing gets broken.
|
||||||
|
## - Desktop notifications
|
||||||
|
## - Spellchecker
|
||||||
|
## - Auto updates
|
||||||
|
**Check for the logs in -**
|
||||||
|
- **on Linux:** `~/.config/Zulip/log.log`
|
||||||
|
- **on OS X:** `~/Library/Logs/Zulip/log.log`
|
||||||
|
- **on Windows:** `%USERPROFILE%\AppData\Roaming\Zulip\log.log`
|
||||||
|
## - All the installer i.e.
|
||||||
|
- Linux (.deb, AppImage)
|
||||||
|
- Mac - (.dmg)
|
||||||
|
- Windows - (web installer for 32/64bit)
|
||||||
|
## - Check for errors in console (if any)
|
||||||
|
## - Code signing verification on Mac and Windows
|
||||||
|
## - Tray and menu options
|
||||||
|
# We need to cross check all these things on -
|
||||||
|
- Windows 7
|
||||||
|
- Windows 8
|
||||||
|
- Windows 10
|
||||||
|
- Ubuntu 14.04/16.04
|
||||||
|
- macOSX
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
|
const mocha = require('gulp-mocha');
|
||||||
const electron = require('electron-connect').server.create({
|
const electron = require('electron-connect').server.create({
|
||||||
verbose: true
|
verbose: true
|
||||||
});
|
});
|
||||||
@@ -27,4 +28,9 @@ gulp.task('reload:renderer', done => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('default', ['dev']);
|
// Test app using mocha+spectron
|
||||||
|
gulp.task('test', () => {
|
||||||
|
return gulp.src('tests/index.js').pipe(mocha());
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('default', ['dev', 'test']);
|
||||||
|
|||||||
139
package.json
@@ -1,9 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "Zulip-Desktop",
|
"name": "zulip",
|
||||||
"productName": "Zulip",
|
"productName": "Zulip",
|
||||||
"version": "0.3.1",
|
"version": "1.3.0-beta",
|
||||||
|
"main": "./app/main",
|
||||||
"description": "Zulip Desktop App",
|
"description": "Zulip Desktop App",
|
||||||
"license": "MIT",
|
"license": "Apache-2.0",
|
||||||
|
"email": "<svnitakash@gmail.com>",
|
||||||
|
"copyright": "©2017 Kandra Labs, Inc.",
|
||||||
|
"author": {
|
||||||
|
"name": "Kandra Labs, Inc.",
|
||||||
|
"email": "svnitakash@gmail.com"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zulip/zulip-electron.git"
|
"url": "https://github.com/zulip/zulip-electron.git"
|
||||||
@@ -11,57 +18,129 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/zulip/zulip-electron/issues"
|
"url": "https://github.com/zulip/zulip-electron/issues"
|
||||||
},
|
},
|
||||||
"main": "app/main/index.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "electron app --disable-http-cache",
|
||||||
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"test": "xo",
|
"test": "xo",
|
||||||
"start": "electron .",
|
|
||||||
"dev": "gulp dev",
|
"dev": "gulp dev",
|
||||||
"build:osx": "electron-packager . --out=dist --app-version=$npm_package_version --prune --asar --icon=app/resources/Icon.icns --overwrite --platform=darwin --arch=x64",
|
"pack": "electron-builder --dir",
|
||||||
"build": "electron-packager . --out=dist --app-version=$npm_package_version --prune --asar --overwrite --all"
|
"dist": "electron-builder",
|
||||||
|
"mas": "electron-builder --mac mas",
|
||||||
|
"build:win": "electron-builder --win nsis-web --ia32 --x64",
|
||||||
|
"travis": "cd ./scripts && ./travis-build-test.sh"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "org.zulip.zulip-electron",
|
||||||
|
"asar": true,
|
||||||
|
"files": [
|
||||||
|
"**/*",
|
||||||
|
"!docs${/*}",
|
||||||
|
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
|
||||||
|
],
|
||||||
|
"copyright": "©2017 Kandra Labs, Inc.",
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.productivity"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"category": "",
|
||||||
|
"packageCategory": "GNOME;GTK;Network;InstantMessaging",
|
||||||
|
"description": "Zulip Desktop Client for Linux",
|
||||||
|
"target": [
|
||||||
|
"deb",
|
||||||
|
"zip",
|
||||||
|
"AppImage"
|
||||||
|
],
|
||||||
|
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
|
||||||
|
},
|
||||||
|
"deb": {
|
||||||
|
"synopsis": "Zulip Desktop App"
|
||||||
|
},
|
||||||
|
"dmg": {
|
||||||
|
"background": "build/appdmg.png",
|
||||||
|
"icon": "build/icon.icns",
|
||||||
|
"iconSize": 128,
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"x": 380,
|
||||||
|
"y": 240,
|
||||||
|
"type": "link",
|
||||||
|
"path": "/Applications"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 122,
|
||||||
|
"y": 240,
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": "nsis",
|
||||||
|
"icon": "build/icon.ico",
|
||||||
|
"publisherName": "Kandra Labs, Inc."
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"perMachine": true,
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Zulip",
|
"Zulip",
|
||||||
"Group Chat app",
|
"Group Chat app",
|
||||||
"electron-app",
|
"electron-app",
|
||||||
"electron"
|
"electron",
|
||||||
|
"Desktop app",
|
||||||
|
"InstantMessaging"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
|
||||||
"configstore": "^2.0.0",
|
|
||||||
"dialogs": "1.1.14",
|
|
||||||
"electron-context-menu": "0.4.0",
|
|
||||||
"electron-debug": "^1.0.0",
|
|
||||||
"electron-dl": "^0.2.0",
|
|
||||||
"electron-localshortcut": "^0.6.1",
|
|
||||||
"node-json-db": "^0.7.2",
|
|
||||||
"request": "^2.74.0",
|
|
||||||
"simple-spellchecker": "^0.9.0",
|
|
||||||
"wurl": "^2.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"devtron": "^1.1.0",
|
"assert": "1.4.1",
|
||||||
"electron": "1.3.3",
|
"devtron": "1.4.0",
|
||||||
"electron-connect": "^0.4.6",
|
"electron-builder": "19.19.1",
|
||||||
"electron-packager": "^7.0.0",
|
"electron": "1.6.11",
|
||||||
"gulp": "^3.9.1",
|
"electron-connect": "0.6.2",
|
||||||
"xo": "*"
|
"gulp": "3.9.1",
|
||||||
|
"gulp-mocha": "4.3.1",
|
||||||
|
"chai-as-promised": "7.1.1",
|
||||||
|
"chai": "4.1.1",
|
||||||
|
"spectron": "3.7.2",
|
||||||
|
"xo": "0.18.2"
|
||||||
},
|
},
|
||||||
"xo": {
|
"xo": {
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "script",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"globalReturn": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"esnext": true,
|
"esnext": true,
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": "app/main/*.js",
|
"files": "app*/**/*.js",
|
||||||
"rules": {
|
"rules": {
|
||||||
"max-lines": [
|
"max-lines": [
|
||||||
"warn",
|
"warn",
|
||||||
350
|
500
|
||||||
],
|
],
|
||||||
"no-warning-comments": 0
|
"no-warning-comments": 0,
|
||||||
|
"capitalized-comments": 0,
|
||||||
|
"no-else-return": 0,
|
||||||
|
"no-path-concat": 0,
|
||||||
|
"no-alert": 0,
|
||||||
|
"guard-for-in": 0,
|
||||||
|
"prefer-promise-reject-errors": 0,
|
||||||
|
"import/no-unresolved": 0,
|
||||||
|
"import/no-extraneous-dependencies": 0,
|
||||||
|
"no-prototype-builtins": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"ignore": [
|
||||||
|
"tests/*.js"
|
||||||
|
],
|
||||||
"envs": [
|
"envs": [
|
||||||
"node",
|
"node",
|
||||||
"browser"
|
"browser",
|
||||||
|
"mocha"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
10
scripts/travis-build-test.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||||
|
export {no_proxy,NO_PROXY}="127.0.0.1,localhost"
|
||||||
|
export DISPLAY=:99.0
|
||||||
|
sh -e /etc/init.d/xvfb start
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
npm run test
|
||||||
81
tests/index.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const Application = require('spectron').Application
|
||||||
|
const chai = require('chai')
|
||||||
|
const { expect } = chai
|
||||||
|
const chaiAsPromised = require('chai-as-promised')
|
||||||
|
|
||||||
|
chai.should()
|
||||||
|
chai.use(chaiAsPromised)
|
||||||
|
|
||||||
|
describe('application launch', function () {
|
||||||
|
this.timeout(15000)
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
this.app = new Application({
|
||||||
|
path: require('electron'),
|
||||||
|
args: [__dirname + '/../app/renderer/main.html']
|
||||||
|
})
|
||||||
|
return this.app.start()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
chaiAsPromised.transferPromiseness = this.app.transferPromiseness
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
if (this.app && this.app.isRunning()) {
|
||||||
|
return this.app.stop()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows an initial window', function () {
|
||||||
|
return this.app.client.waitUntilWindowLoaded(5000)
|
||||||
|
.getWindowCount().should.eventually.equal(2)
|
||||||
|
.browserWindow.isMinimized().should.eventually.be.false
|
||||||
|
.browserWindow.isDevToolsOpened().should.eventually.be.false
|
||||||
|
.browserWindow.isVisible().should.eventually.be.true
|
||||||
|
.browserWindow.isFocused().should.eventually.be.true
|
||||||
|
.browserWindow.getBounds().should.eventually.have.property('width').and.be.above(0)
|
||||||
|
.browserWindow.getBounds().should.eventually.have.property('height').and.be.above(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets up a default organization', function () {
|
||||||
|
let app = this.app
|
||||||
|
let self = this
|
||||||
|
app.client.execute(() => {
|
||||||
|
window.confirm = function () { return true }
|
||||||
|
})
|
||||||
|
|
||||||
|
function createOrg (client, name, url, winIndex) {
|
||||||
|
return client
|
||||||
|
// Focus on settings webview
|
||||||
|
.then(switchToWebviewAtIndex.bind(null, self.app.client, winIndex))
|
||||||
|
.pause(1000) // wait for settings to load
|
||||||
|
|
||||||
|
// Fill settings form
|
||||||
|
.click('#new-server-action')
|
||||||
|
.setValue('input[id="server-info-name"]', name)
|
||||||
|
.setValue('input[id="server-info-url"]', url)
|
||||||
|
.click('#save-server-action')
|
||||||
|
.pause(500) // Need to pause while server verification takes place
|
||||||
|
.then(() => app.browserWindow.reload())
|
||||||
|
.pause(1500) // Wait for webview of org to load
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToWebviewAtIndex(client, index) {
|
||||||
|
return client
|
||||||
|
.windowHandles()
|
||||||
|
.then(function (session) {
|
||||||
|
this.window(session.value[index])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.app.client.waitUntilWindowLoaded(5000)
|
||||||
|
.then(() => createOrg(self.app.client, 'Zulip 1', 'chat.zulip.org', 1))
|
||||||
|
.then(switchToWebviewAtIndex.bind(null, self.app.client, 0))
|
||||||
|
.click('#add-action > i').pause(500)
|
||||||
|
.then(switchToWebviewAtIndex.bind(null, self.app.client, 2))
|
||||||
|
.then(() => createOrg(self.app.client, 'Zulip 2', 'chat.zulip.org', 2))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
11
troubleshooting.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
* App icon will only show in the release version. The dev version will use the Electron icon
|
||||||
|
* If you see issue, try deleting `node_modules` and `npm install`
|
||||||
|
* Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I`
|
||||||
|
|
||||||
|
### Error : ChecksumMismatchError
|
||||||
|
- Try deleteing `node_modules` && `app/node_modules` directories. Re-install dependencies using `npm install`.
|
||||||
|
|
||||||
|
### Error : Module version mismatch. Expected 50, got 51
|
||||||
|
- Make sure you have installed [node-gyp](https://github.com/nodejs/node-gyp#installation) dependencies properly.
|
||||||
122
zulip-electron-launcher.sh
Executable file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Zulip Beta Client Launcher
|
||||||
|
|
||||||
|
# This script ensures that you have the latest version of the specified branch
|
||||||
|
# (defaults to master if none specified) and then updates or installs all your
|
||||||
|
# required npm modules.
|
||||||
|
|
||||||
|
# I recommend symlinking this script into your PATH.
|
||||||
|
|
||||||
|
# {{{ showUsage()
|
||||||
|
|
||||||
|
showUsage()
|
||||||
|
{
|
||||||
|
echo "Usage: $0 <branch_name>"
|
||||||
|
echo "Example: $0 dev"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
# {{{ envSetup()
|
||||||
|
|
||||||
|
envSetup()
|
||||||
|
{
|
||||||
|
defaultBranch="master"
|
||||||
|
startingDir=`pwd`
|
||||||
|
requirePop=0
|
||||||
|
|
||||||
|
# Check command line arguments
|
||||||
|
if [ "$#" -gt "1" ]
|
||||||
|
then
|
||||||
|
showUsage
|
||||||
|
elif [ "$#" -eq "1" ]
|
||||||
|
then
|
||||||
|
myBranch=$1
|
||||||
|
else
|
||||||
|
myBranch=$defaultBranch
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set workingDir
|
||||||
|
if [ -L $0 ]
|
||||||
|
then
|
||||||
|
realPath=`ls -l $0 | cut -d '>' -f 2`
|
||||||
|
workingDir=`dirname $realPath`
|
||||||
|
else
|
||||||
|
workingDir="."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set name of upstreamRemote
|
||||||
|
cd $workingDir
|
||||||
|
git remote -v | grep "github\.com.zulip.zulip-electron.git (fetch)" > /dev/null 2>&1
|
||||||
|
if [ $? -eq 0 ]
|
||||||
|
then
|
||||||
|
upstreamRemote=`git remote -v | grep "github\.com.zulip.zulip-electron.git (fetch)" | awk '{ print $1 }'`
|
||||||
|
else
|
||||||
|
upstreamRemote="origin"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
# {{{ gitCheckout()
|
||||||
|
|
||||||
|
gitCheckout()
|
||||||
|
{
|
||||||
|
git fetch $upstreamRemote
|
||||||
|
git checkout $myBranch
|
||||||
|
git rebase $upstreamRemote/master
|
||||||
|
if [ $? -gt 0 ]
|
||||||
|
then
|
||||||
|
echo "Stashing uncommitted changes and doing a new git pull"
|
||||||
|
git stash && requirePop=1
|
||||||
|
git rebase $upstreamRemote/master
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
# {{{ npmInstallStart()
|
||||||
|
|
||||||
|
npmInstallStart()
|
||||||
|
{
|
||||||
|
npm install
|
||||||
|
npm start &
|
||||||
|
}
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
# {{{ cleanUp()
|
||||||
|
|
||||||
|
cleanUp()
|
||||||
|
{
|
||||||
|
# Switch back to branch we started on
|
||||||
|
git checkout -
|
||||||
|
|
||||||
|
# Pop if we stashed
|
||||||
|
if [ $requirePop -eq 1 ]
|
||||||
|
then
|
||||||
|
echo "Popping out uncommitted changes"
|
||||||
|
git stash pop
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return the whatever dir we started in
|
||||||
|
cd $startingDir
|
||||||
|
}
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# this function is called when user hits Ctrl-C
|
||||||
|
catchControl_c () {
|
||||||
|
echo -en "\n## Ctrl-C caught; Quitting \n"
|
||||||
|
# exit shell script
|
||||||
|
exit $?;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
envSetup $*
|
||||||
|
gitCheckout
|
||||||
|
npmInstallStart
|
||||||
|
cleanUp
|
||||||
|
|
||||||
|
# initialise trap to call catchControl_c function and trap keyboard interrupt (control-c)
|
||||||
|
trap catchControl_c SIGINT
|
||||||
|
sleep 1000
|
||||||