Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff8a10c231 | ||
|
|
6a94500170 | ||
|
|
41eb47bc15 | ||
|
|
211f772447 | ||
|
|
6d6e3fd127 | ||
|
|
14ef0ab6d1 | ||
|
|
8434069539 | ||
|
|
c7621f251f | ||
|
|
a5122cc076 | ||
|
|
5993d6862b | ||
|
|
348b612f98 | ||
|
|
f714b59c88 | ||
|
|
b627499acd | ||
|
|
43beccc2eb | ||
|
|
b8275b3404 | ||
|
|
6e4da08164 | ||
|
|
f2ac52bcdf | ||
|
|
46e976513b | ||
|
|
816ccf6509 | ||
|
|
73e265d8fd | ||
|
|
6117097a93 | ||
|
|
fee732dba1 | ||
|
|
1138e8062f | ||
|
|
123cf0d7b8 | ||
|
|
bd14ee3b1f | ||
|
|
e63eee0c68 | ||
|
|
070feb4b66 | ||
|
|
308417852c | ||
|
|
9c3a976250 | ||
|
|
688973bae5 | ||
|
|
d93b0be0d4 | ||
|
|
773abbae13 | ||
|
|
0f3579fd7b | ||
|
|
db7b7158fa | ||
|
|
b5a61f2dd8 | ||
|
|
8347f15de2 | ||
|
|
26309fad10 | ||
|
|
ba4d8b0b35 | ||
|
|
dc11e0c79c | ||
|
|
a13d931cfd | ||
|
|
fea2ad3d7d | ||
|
|
b8c10def8e | ||
|
|
80afc28719 | ||
|
|
561cd77e68 | ||
|
|
d8554ec67e | ||
|
|
a43237d4f9 | ||
|
|
6af7d782d4 | ||
|
|
6d2b778ac5 | ||
|
|
eada773048 | ||
|
|
9bff4e061e | ||
|
|
753ce8e119 | ||
|
|
4be6719db4 | ||
|
|
a6e1dc4269 | ||
|
|
ad241b9f84 | ||
|
|
53e76288cb | ||
|
|
0f15b6d198 | ||
|
|
143e93f483 | ||
|
|
93a5bb2baa | ||
|
|
ebfc8e5a1b | ||
|
|
972b9b6e91 | ||
|
|
56cccc6781 | ||
|
|
b680bb5372 | ||
|
|
217b3ae488 | ||
|
|
959392f815 | ||
|
|
d1e13e48fe | ||
|
|
1c04502963 | ||
|
|
34da23783e | ||
|
|
2b603cfbb5 | ||
|
|
a1fe22c08c | ||
|
|
bc04ae24a7 | ||
|
|
48216e1653 | ||
|
|
8aca82a593 | ||
|
|
68d8cbe68a | ||
|
|
4bc3acb14d | ||
|
|
a974ec9dee | ||
|
|
e91c43466d | ||
|
|
dfc4ab6dd8 | ||
|
|
9f5ffde2dd | ||
|
|
678bef2360 | ||
|
|
3b12cdc179 | ||
|
|
261754f18b | ||
|
|
980db9cb0c | ||
|
|
a80cd455de | ||
|
|
289e3d6286 | ||
|
|
4b181620d8 | ||
|
|
088ec7fd94 | ||
|
|
289f1fe134 | ||
|
|
6b9ac52af6 | ||
|
|
0e422eb70d | ||
|
|
df32545b07 | ||
|
|
bab340a849 | ||
|
|
494d44f8bb | ||
|
|
ad1a81e8a0 | ||
|
|
b0d507c2dd | ||
|
|
619e5e944b | ||
|
|
e2d0faa539 | ||
|
|
cbb6c82113 | ||
|
|
00376b43f8 | ||
|
|
fb163a2493 | ||
|
|
14c493fc63 | ||
|
|
625682a4da | ||
|
|
eec8f45de5 | ||
|
|
0855e3e32f | ||
|
|
fab3ce46b8 | ||
|
|
08c8ebc100 | ||
|
|
0039fe09be | ||
|
|
97beed6fd1 | ||
|
|
994314cfa3 | ||
|
|
db6dbe34c3 | ||
|
|
fa7ef361e2 | ||
|
|
296349a83b | ||
|
|
563458e0b5 | ||
|
|
169301fa18 | ||
|
|
0363e6f122 | ||
|
|
c259324185 | ||
|
|
57767d9906 | ||
|
|
1a26f9fb82 | ||
|
|
19ab7db239 | ||
|
|
ded430c155 | ||
|
|
506acda70b | ||
|
|
d173844f98 | ||
|
|
7919ae3628 | ||
|
|
188d42dcd6 | ||
|
|
7735394373 | ||
|
|
30bf8a1724 | ||
|
|
0310560938 | ||
|
|
2969161afb | ||
|
|
a6213d4a6f | ||
|
|
12268bd065 | ||
|
|
35a66c3550 | ||
|
|
402a974a70 | ||
|
|
079cd95e0e | ||
|
|
e9f5aaa397 | ||
|
|
ca0236fdfd | ||
|
|
da3edb35cc | ||
|
|
ef6054b859 | ||
|
|
1046976c08 | ||
|
|
f8cbbfc2cd | ||
|
|
f083fc0d7b | ||
|
|
72cb820f1f | ||
|
|
d5d33247fb | ||
|
|
9f95a10591 | ||
|
|
5ca083105c | ||
|
|
6bc8d8dd56 | ||
|
|
dce89bcfa8 | ||
|
|
e5370ce859 | ||
|
|
6f35d73fe8 | ||
|
|
af2fb5f399 | ||
|
|
e976565550 | ||
|
|
7a6b4adcc1 | ||
|
|
ec30665bc4 | ||
|
|
9651d62b96 | ||
|
|
a6e3c8984d | ||
|
|
777d9ac353 | ||
|
|
d01fccd949 | ||
|
|
4cd0d1c397 | ||
|
|
bf3ec060f7 | ||
|
|
40a3267615 | ||
|
|
ff19d6744e | ||
|
|
c26af16688 | ||
|
|
216b3cfe01 | ||
|
|
fd70556c82 | ||
|
|
0fee20cc12 | ||
|
|
32f6de1eec | ||
|
|
8671f08a0e | ||
|
|
299484f7b2 | ||
|
|
d16086fcdb | ||
|
|
1968b7da31 | ||
|
|
95a40a45e5 | ||
|
|
0ad7f080b7 | ||
|
|
aca3048036 | ||
|
|
1158cd42cc | ||
|
|
0e2b7458d7 | ||
|
|
cf7d740004 | ||
|
|
dc4c4d9ade | ||
|
|
a347ae3676 | ||
|
|
7da2a8a15e | ||
|
|
0002559d6e | ||
|
|
afda0ed4da | ||
|
|
473f389bbe | ||
|
|
b7c000b78b | ||
|
|
4ac8247db0 | ||
|
|
0471d1ac97 | ||
|
|
c0221dafde | ||
|
|
8f337f17e4 | ||
|
|
de5a63f7b2 | ||
|
|
52a744b443 | ||
|
|
4be5fa598e | ||
|
|
d03a7e7a98 | ||
|
|
ebc4d6fdbd | ||
|
|
35ffd02814 | ||
|
|
4c72479d12 | ||
|
|
907c761f71 | ||
|
|
97206aa8f6 | ||
|
|
2ec1664408 | ||
|
|
97822f35fd | ||
|
|
c5e72d1a2b | ||
|
|
117c9e069e | ||
|
|
951a9f81d6 | ||
|
|
6f30c33f11 | ||
|
|
8050117adf | ||
|
|
98031aacce | ||
|
|
2781ce6fa0 | ||
|
|
08db39d76a | ||
|
|
da267e1488 | ||
|
|
ff825d6123 | ||
|
|
6d6bb8fc57 | ||
|
|
7836ab83d0 | ||
|
|
8568728152 | ||
|
|
8783983945 | ||
|
|
4055f451fd | ||
|
|
31777ce804 | ||
|
|
97a9e92969 | ||
|
|
c5ca414e8f | ||
|
|
2c5628b5fb | ||
|
|
a338e937e5 | ||
|
|
1089572d66 | ||
|
|
ddb2460187 | ||
|
|
745f39ed20 | ||
|
|
18cccd514f | ||
|
|
9c0eba4f00 | ||
|
|
f19625d835 | ||
|
|
c4754d9f85 | ||
|
|
acdc5dc1f4 | ||
|
|
4927609af0 | ||
|
|
e845fe3e71 | ||
|
|
d807efd4f8 | ||
|
|
5f76695298 | ||
|
|
aadb1f3109 | ||
|
|
cb986912c4 | ||
|
|
7e451d9033 | ||
|
|
a024b6d692 | ||
|
|
a45ccf1db7 | ||
|
|
30bf430a91 | ||
|
|
df00776e6c | ||
|
|
1dc0d0c12e | ||
|
|
002fd411ea | ||
|
|
6311cf0c5a | ||
|
|
d71543beb6 | ||
|
|
5a9837f676 | ||
|
|
77754cf32f | ||
|
|
43a74fb99b | ||
|
|
1bef3a3f8f | ||
|
|
b6b6be34f6 | ||
|
|
f1cde3e873 | ||
|
|
dcd6ae2e01 | ||
|
|
8984172ff6 | ||
|
|
87a5e7f030 | ||
|
|
115d07da5a | ||
|
|
e67ed023cd | ||
|
|
1f1d753791 | ||
|
|
34fcce60af | ||
|
|
e7a90d9a7c | ||
|
|
9d6d5e1a14 | ||
|
|
763cf81c97 | ||
|
|
0b64a46bdc | ||
|
|
d417bf058d | ||
|
|
46088bfbb5 | ||
|
|
a6ea622f78 | ||
|
|
847ed8570b | ||
|
|
3a9ab817a9 | ||
|
|
2a5c2ed192 | ||
|
|
8867bec8e6 | ||
|
|
262da85399 | ||
|
|
51a70f2164 | ||
|
|
0854a658d8 | ||
|
|
d99056ced5 | ||
|
|
4a5473b4e7 | ||
|
|
d3f368efd7 | ||
|
|
ca052566de | ||
|
|
c101e262d6 | ||
|
|
1f942301ad | ||
|
|
b6db1e12b7 | ||
|
|
3bfc6ea8d7 | ||
|
|
ec30dd4788 | ||
|
|
990ebbdbcb | ||
|
|
99a856be97 | ||
|
|
260d0641df | ||
|
|
16963859e1 | ||
|
|
010a201d0c | ||
|
|
95014e6348 | ||
|
|
724912ec48 | ||
|
|
46d8a11c51 | ||
|
|
065cd05c96 | ||
|
|
a4564bed3a | ||
|
|
4ef3e587cb | ||
|
|
d6259836e0 | ||
|
|
ef246cfef3 | ||
|
|
90dd0ddb94 | ||
|
|
bd2c54398b | ||
|
|
e65776f9f0 | ||
|
|
5cafdd9878 | ||
|
|
49882a05d1 | ||
|
|
8e36a2bc83 | ||
|
|
24e47195e5 | ||
|
|
4b37958140 | ||
|
|
3edff32bde | ||
|
|
78c5e0f7ef | ||
|
|
49229e1723 | ||
|
|
14e2bfd1bf | ||
|
|
3c49e2d0ed | ||
|
|
d5bc66c9a4 | ||
|
|
4f6977668b | ||
|
|
17826c28e5 | ||
|
|
a3d71dd694 | ||
|
|
a5c39342ba | ||
|
|
ad75c541cf | ||
|
|
cd46cbeb8f | ||
|
|
888e1ad5d3 | ||
|
|
abb7108321 | ||
|
|
da21a2a410 | ||
|
|
904b5bf0f5 | ||
|
|
d21aa97219 | ||
|
|
6ee75fbdba | ||
|
|
c3b2a0c955 | ||
|
|
0ac42c145e | ||
|
|
4a77bc930f | ||
|
|
29c3080eb6 | ||
|
|
57516177a0 | ||
|
|
34d8235551 | ||
|
|
8d51245161 | ||
|
|
55ad98ecc4 | ||
|
|
ed04181bb9 | ||
|
|
868fba55cd | ||
|
|
05351c0ac1 | ||
|
|
a707129146 | ||
|
|
2f3138207c | ||
|
|
dc7286a0ef | ||
|
|
5e66119bf0 | ||
|
|
b751fd11e4 | ||
|
|
5e238bfbfe | ||
|
|
7e20a3cac6 | ||
|
|
7c1d6e4d2e | ||
|
|
13c2157e36 | ||
|
|
7033315cd3 | ||
|
|
775476f97c | ||
|
|
eb62c9ce6a | ||
|
|
60be4df76e | ||
|
|
f888cfd565 | ||
|
|
044126188d | ||
|
|
0fec711545 | ||
|
|
7a6afb8aea | ||
|
|
1e72177991 | ||
|
|
cdf070189e | ||
|
|
64bf455b29 | ||
|
|
0a53ee87b9 | ||
|
|
ef85b71ee4 | ||
|
|
ac0133e8fa | ||
|
|
25ba6df007 | ||
|
|
6d750897a8 | ||
|
|
a7747c537b | ||
|
|
65b6a21938 | ||
|
|
4e6e78ccd2 | ||
|
|
fd5d6546b2 | ||
|
|
402d484182 | ||
|
|
4bf0914f1e | ||
|
|
96519296e1 | ||
|
|
03d06685b0 | ||
|
|
7d57c56b17 | ||
|
|
4ce766853c | ||
|
|
dc7bbd848c | ||
|
|
81744e6523 | ||
|
|
b66f471e53 | ||
|
|
202b8a92af | ||
|
|
1e98f6b781 | ||
|
|
f53e8fbd21 | ||
|
|
8e95871534 | ||
|
|
595a2d2baa | ||
|
|
227772bc33 | ||
|
|
2ca98e477d | ||
|
|
477d71e0b9 | ||
|
|
677bfc3f6b | ||
|
|
c7721b048d | ||
|
|
1417f091fe | ||
|
|
d22964bcfd | ||
|
|
59314ab17d | ||
|
|
5c083d99e2 | ||
|
|
d3abb0756e | ||
|
|
4de9848e34 | ||
|
|
bdbbb12f96 | ||
|
|
c1ea3f49ca | ||
|
|
905c45f225 | ||
|
|
352e5c6cb0 | ||
|
|
2d291c8f5e | ||
|
|
86630fa1e0 | ||
|
|
8e02170dd1 | ||
|
|
9edf95ec93 | ||
|
|
2add875a38 | ||
|
|
b20309f7aa | ||
|
|
8996df5c09 | ||
|
|
7b5f02b4c8 | ||
|
|
fc0287a829 | ||
|
|
ed8bca5394 | ||
|
|
9458f4011d | ||
|
|
f1bd36a3b8 | ||
|
|
f060e02fc0 | ||
|
|
0fa2e5d3a0 | ||
|
|
6d0055ecb5 | ||
|
|
2d3ce9c290 | ||
|
|
796b276d02 | ||
|
|
ed98b5c807 | ||
|
|
f1da7dcb0c | ||
|
|
35a539eee2 | ||
|
|
79ffb7d502 | ||
|
|
a2e6f621a2 | ||
|
|
96e97143f2 | ||
|
|
4126a1a808 | ||
|
|
3ce902123f | ||
|
|
7125357a44 | ||
|
|
ec453b89b8 | ||
|
|
303404acc6 | ||
|
|
f11d42e72d | ||
|
|
329c8d64a8 | ||
|
|
698ce8f0e6 | ||
|
|
b06e11ba09 | ||
|
|
d689a7408b | ||
|
|
c899218fb9 | ||
|
|
c42f2a05d2 | ||
|
|
70ed552e1a | ||
|
|
9a07649b8a | ||
|
|
9a6ac544dd | ||
|
|
06a1e26655 | ||
|
|
626e7c2c61 | ||
|
|
a7aeb491ba | ||
|
|
7ba8f72d7b | ||
|
|
d0f74d7a99 | ||
|
|
7f33fa5368 | ||
|
|
0ab97266f6 | ||
|
|
32f896f19d | ||
|
|
de9965213a | ||
|
|
c2118f4830 | ||
|
|
6f0e0748e1 | ||
|
|
8e5fc13ba4 | ||
|
|
7a35099fb9 | ||
|
|
d08bee6817 | ||
|
|
4fc96904d6 | ||
|
|
66bc1f28dd | ||
|
|
30d082e709 | ||
|
|
1b11123797 | ||
|
|
654c2add0d | ||
|
|
6d179d6d3d | ||
|
|
361faa6646 | ||
|
|
7fc22ba0b7 | ||
|
|
9c82e6ab68 | ||
|
|
78c18d4ffe | ||
|
|
978dafc62b | ||
|
|
8919d8fb7d | ||
|
|
cdb7dd9175 | ||
|
|
d6b7c17402 | ||
|
|
7e247c14b2 | ||
|
|
e6af48fc4a | ||
|
|
9703843602 | ||
|
|
d53a7ce5f1 | ||
|
|
6e4a6515e0 | ||
|
|
bf304ffde5 | ||
|
|
7cefe9d367 | ||
|
|
12cccc25cc | ||
|
|
34f62cb3b3 | ||
|
|
820d327093 | ||
|
|
04279342a4 | ||
|
|
1cfe084cc2 | ||
|
|
4629cc1b19 | ||
|
|
b4e87466ba | ||
|
|
ae82f43078 | ||
|
|
7cc185ac57 | ||
|
|
4d37e1d640 | ||
|
|
74cf9e2573 | ||
|
|
3c2de2a38d | ||
|
|
9dd8216dfb | ||
|
|
905ddafe41 | ||
|
|
62a0e57f38 | ||
|
|
be03a191c4 | ||
|
|
cf7d3b5a7e | ||
|
|
939d8a7078 | ||
|
|
db62a51d5a | ||
|
|
9ebf5751fd | ||
|
|
f7c6b7fce2 | ||
|
|
850b34ce33 | ||
|
|
1a64a88d3d | ||
|
|
be8f00ab8a | ||
|
|
ddedb8a48b | ||
|
|
ba392362ee | ||
|
|
8fbd47e199 | ||
|
|
6aaf0c812a | ||
|
|
33977b2282 | ||
|
|
37f70bdf0f | ||
|
|
5d6203f5f9 | ||
|
|
082aabc735 | ||
|
|
ba15526516 | ||
|
|
4a2576fbc2 | ||
|
|
97d95b4bcb | ||
|
|
d0a9d8c581 | ||
|
|
78c779faaf | ||
|
|
8d81aca439 | ||
|
|
6c3dbc3c6f | ||
|
|
b5411841cf | ||
|
|
5c6a725712 | ||
|
|
03efeaeb1a | ||
|
|
c075187088 | ||
|
|
416b17062e | ||
|
|
dd88eb4cc1 | ||
|
|
5fa0821e07 | ||
|
|
6e9bad5772 | ||
|
|
5769d438a0 | ||
|
|
8a48ff96ef | ||
|
|
0782c97d4f | ||
|
|
542cc75dc4 | ||
|
|
1b445ad789 | ||
|
|
9baed4256a | ||
|
|
2b4df7e8c7 | ||
|
|
6e28dec712 | ||
|
|
ba9f0ca672 | ||
|
|
7261ccdc90 | ||
|
|
6fbadbe5bc | ||
|
|
26d7ba72aa | ||
|
|
44e4df2492 | ||
|
|
d8482b533a | ||
|
|
2082480ddd | ||
|
|
e8c4e394fd | ||
|
|
0c5052ebae | ||
|
|
d3b2ae1975 | ||
|
|
f4f0f489eb | ||
|
|
3368ebf067 | ||
|
|
b0ac10d94a | ||
|
|
c87a8b86ae | ||
|
|
d5d71d7097 | ||
|
|
c960a8b909 | ||
|
|
3257f78d24 | ||
|
|
a9133bf933 | ||
|
|
8e8147b5ac | ||
|
|
711f4de3d7 | ||
|
|
68998e062e | ||
|
|
f40d1f6bd5 | ||
|
|
048c3cd14d | ||
|
|
08ca353205 | ||
|
|
c83cdc9335 | ||
|
|
e91e3c076d | ||
|
|
8be7eec2ca | ||
|
|
acc4493517 | ||
|
|
ff467402c0 | ||
|
|
49d3eb5a3f | ||
|
|
889aa6d652 | ||
|
|
78e0457843 | ||
|
|
7f568faef6 | ||
|
|
be045f3156 | ||
|
|
3b5e81d514 | ||
|
|
346bdf2ca8 | ||
|
|
501289786c | ||
|
|
24ffe223be | ||
|
|
32c561d78e | ||
|
|
ff65b37336 | ||
|
|
3f698ca012 | ||
|
|
785bc2556e | ||
|
|
e198282b4d | ||
|
|
24e59c378b | ||
|
|
c1b4aae573 | ||
|
|
f5339ef97f | ||
|
|
2b53fae16f | ||
|
|
1d3466c7a0 | ||
|
|
4d8b2029d2 | ||
|
|
8ad0da2109 | ||
|
|
12827ef0a6 | ||
|
|
4c399a41f7 | ||
|
|
25968a35cc | ||
|
|
548d095223 | ||
|
|
1683bcb3e5 | ||
|
|
991843acb0 | ||
|
|
8b85437b6b | ||
|
|
6259f0de70 | ||
|
|
d86bc1d84d | ||
|
|
88bdb0815d | ||
|
|
97f64fe5e6 | ||
|
|
196fef3a1b | ||
|
|
a2d756074f | ||
|
|
b2f23d619d | ||
|
|
9d1d66b0f5 | ||
|
|
c61e44d55b | ||
|
|
76c5c283b9 | ||
|
|
2d88afb77e | ||
|
|
7fd09761cb | ||
|
|
cb53c53c2b | ||
|
|
77ff6349f6 | ||
|
|
879ad4001d | ||
|
|
e1436ab964 | ||
|
|
007bea0a4b | ||
|
|
67b76558fc | ||
|
|
f60429efdf | ||
|
|
f140e20c6f | ||
|
|
c502ab9500 | ||
|
|
425ae55422 | ||
|
|
e9006a234b | ||
|
|
ef6aa4e415 | ||
|
|
1901c3c798 | ||
|
|
6cb046f29e | ||
|
|
e7e67e300b | ||
|
|
3f948ba49b | ||
|
|
96f33e4052 | ||
|
|
68d60f4f64 | ||
|
|
f7153da83d | ||
|
|
16d53897c7 | ||
|
|
32d405bca4 | ||
|
|
2f747fc09d | ||
|
|
32d5a1d3f7 | ||
|
|
db44b8ea54 | ||
|
|
1f512c8ba1 | ||
|
|
417dc9dc42 | ||
|
|
bd66ea8acc | ||
|
|
dba9d13b00 | ||
|
|
1e034a794f | ||
|
|
a261c66f34 | ||
|
|
b94bd73d33 | ||
|
|
a9be6b17b9 | ||
|
|
ac6f8e8551 | ||
|
|
23fb2bfc7f | ||
|
|
ff1c3722a8 | ||
|
|
1712368f6a | ||
|
|
d7529bf6b5 | ||
|
|
6b145bb3cc | ||
|
|
72e905675d | ||
|
|
2fbcf7e9b6 | ||
|
|
0ade4dbf00 | ||
|
|
d70ad9d733 | ||
|
|
51a19878dd | ||
|
|
ddca6a1ad8 | ||
|
|
cac74122f2 | ||
|
|
991b2b9721 | ||
|
|
b6663acf91 | ||
|
|
7f74e72cea | ||
|
|
bd1df642fa | ||
|
|
575b0241e4 | ||
|
|
f1ed6304a4 | ||
|
|
a42d9871bd | ||
|
|
7a2ea7cace | ||
|
|
fb39811d95 | ||
|
|
78e88f4c42 | ||
|
|
db63af5f7a | ||
|
|
c3e04da796 | ||
|
|
2a59043ef1 | ||
|
|
3d76a35a68 | ||
|
|
c1733fc89c | ||
|
|
388c1bec45 | ||
|
|
a2869e9d2a | ||
|
|
3e5ff42474 | ||
|
|
9366eaf374 | ||
|
|
cb4c93a632 | ||
|
|
adaffd7189 | ||
|
|
821dc22e80 | ||
|
|
b0a71e96cc | ||
|
|
40e156992a | ||
|
|
ab031d9da1 | ||
|
|
209a76c5ce | ||
|
|
4bd74b5817 | ||
|
|
dbb1a86ca3 | ||
|
|
c066883dbd | ||
|
|
0b6b6dc7fd | ||
|
|
c546464d9f | ||
|
|
7b3e2c58e2 | ||
|
|
d6127082e9 | ||
|
|
67a5fdaf96 | ||
|
|
af9156b18a | ||
|
|
7af20fd2d9 | ||
|
|
2a5a428bc0 | ||
|
|
4b7d47465a | ||
|
|
5f4e2d8e06 | ||
|
|
f8ef8507b5 | ||
|
|
2ae143e6da | ||
|
|
38e8f52293 | ||
|
|
b143fc9297 | ||
|
|
dd10f78401 | ||
|
|
90208b774a | ||
|
|
bc35d9fe1a | ||
|
|
472d637c9f | ||
|
|
20c86613a7 | ||
|
|
5ac69ed7de | ||
|
|
0620fe5e11 | ||
|
|
44a819226f | ||
|
|
cf2cf7898a | ||
|
|
ee212b69a9 | ||
|
|
87d91560f8 | ||
|
|
bccca00aaf | ||
|
|
d6e36ed50a | ||
|
|
83f7fa768d | ||
|
|
2d49006aee | ||
|
|
c2e58dcb62 | ||
|
|
f154c5e521 | ||
|
|
eadbebb4fa | ||
|
|
40cf1c7549 | ||
|
|
9c4e305a84 | ||
|
|
167197fcda | ||
|
|
e3a40aca6f | ||
|
|
0611e0c4bd | ||
|
|
d6857693bb | ||
|
|
b5ccf7a58f | ||
|
|
bfca1822a0 | ||
|
|
30151de269 | ||
|
|
fe7c4528d7 | ||
|
|
1bad630641 | ||
|
|
8a9a82fc80 |
No files matched your search
+1
-1
@@ -6,7 +6,7 @@
|
||||
"browser": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 2017,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
## Issue
|
||||
|
||||
Add these issues to elsewhere:
|
||||
- LDAP: https://github.com/wekan/wekan-ldap/issues
|
||||
- Snap: https://github.com/wekan/wekan-snap/issues
|
||||
|
||||
Other Wekan issues can be added here.
|
||||
|
||||
+13
-1
@@ -8,7 +8,6 @@ npm-debug.log
|
||||
.vscode/
|
||||
.idea/
|
||||
.build/*
|
||||
packages/
|
||||
package-lock.json
|
||||
**/parts/
|
||||
**/stage
|
||||
@@ -17,3 +16,16 @@ package-lock.json
|
||||
snap/.snapcraft/
|
||||
.idea
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
.build*
|
||||
*.browserify.js.cached
|
||||
*.browserify.js.map
|
||||
.build*
|
||||
versions.json
|
||||
.versions
|
||||
.npm
|
||||
.build*
|
||||
._*
|
||||
.Trashes
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
+9
-7
@@ -31,7 +31,9 @@ kenton:accounts-sandstorm
|
||||
service-configuration@1.0.11
|
||||
useraccounts:unstyled
|
||||
useraccounts:flow-routing
|
||||
salleman:accounts-oidc
|
||||
wekan-ldap
|
||||
wekan-accounts-cas
|
||||
wekan-accounts-oidc
|
||||
|
||||
# Utilities
|
||||
check@1.2.5
|
||||
@@ -63,9 +65,7 @@ mousetrap:mousetrap
|
||||
mquandalle:jquery-textcomplete
|
||||
mquandalle:jquery-ui-drag-drop-sort
|
||||
mquandalle:mousetrap-bindglobal
|
||||
mquandalle:perfect-scrollbar
|
||||
peerlibrary:blaze-components@=0.15.1
|
||||
perak:markdown
|
||||
templates:tabs
|
||||
verron:autosize
|
||||
simple:json-routes
|
||||
@@ -81,12 +81,14 @@ staringatlights:fast-render
|
||||
mixmax:smart-disconnect
|
||||
accounts-password@1.5.0
|
||||
cfs:gridfs
|
||||
eluck:accounts-lockout
|
||||
rzymek:fullcalendar
|
||||
momentjs:moment@2.22.2
|
||||
browser-policy-framing
|
||||
mquandalle:moment
|
||||
msavin:usercache
|
||||
wekan:wekan-ldap
|
||||
wekan:accounts-cas
|
||||
msavin:sjobs
|
||||
wekan-scrollbar
|
||||
mquandalle:perfect-scrollbar
|
||||
mdg:meteor-apm-agent
|
||||
meteorhacks:unblock
|
||||
lucasantoniassi:accounts-lockout
|
||||
wekan-markdown
|
||||
+9
-7
@@ -60,7 +60,6 @@ ecmascript-runtime@0.5.0
|
||||
ecmascript-runtime-client@0.5.0
|
||||
ecmascript-runtime-server@0.5.0
|
||||
ejson@1.1.0
|
||||
eluck:accounts-lockout@0.9.0
|
||||
email@1.2.3
|
||||
es5-shim@4.6.15
|
||||
fastclick@1.0.13
|
||||
@@ -82,8 +81,10 @@ launch-screen@1.1.1
|
||||
livedata@1.0.18
|
||||
localstorage@1.2.0
|
||||
logging@1.1.19
|
||||
lucasantoniassi:accounts-lockout@1.0.0
|
||||
matb33:collection-hooks@0.8.4
|
||||
matteodem:easy-search@1.6.4
|
||||
mdg:meteor-apm-agent@3.1.2
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.8.2
|
||||
meteor-base@1.2.0
|
||||
@@ -93,6 +94,7 @@ meteorhacks:collection-utils@1.2.0
|
||||
meteorhacks:meteorx@1.4.1
|
||||
meteorhacks:picker@1.0.3
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
meteorhacks:unblock@1.1.0
|
||||
meteorspark:util@0.2.0
|
||||
minifier-css@1.2.16
|
||||
minifier-js@2.2.2
|
||||
@@ -117,7 +119,6 @@ mquandalle:jquery-ui-drag-drop-sort@0.2.0
|
||||
mquandalle:moment@1.0.1
|
||||
mquandalle:mousetrap-bindglobal@0.0.1
|
||||
mquandalle:perfect-scrollbar@0.6.5_2
|
||||
msavin:sjobs@3.0.6
|
||||
msavin:usercache@1.0.0
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@2.2.33
|
||||
@@ -131,7 +132,6 @@ peerlibrary:base-component@0.16.0
|
||||
peerlibrary:blaze-components@0.15.1
|
||||
peerlibrary:computed-field@0.7.0
|
||||
peerlibrary:reactive-field@0.3.0
|
||||
perak:markdown@1.0.5
|
||||
promise@0.10.0
|
||||
raix:eventemitter@0.1.3
|
||||
raix:handlebar-helpers@0.2.5
|
||||
@@ -144,8 +144,6 @@ reload@1.1.11
|
||||
retry@1.0.9
|
||||
routepolicy@1.0.12
|
||||
rzymek:fullcalendar@3.8.0
|
||||
salleman:accounts-oidc@1.0.9
|
||||
salleman:oidc@1.0.9
|
||||
service-configuration@1.0.11
|
||||
session@1.1.7
|
||||
sha@1.0.9
|
||||
@@ -180,7 +178,11 @@ useraccounts:unstyled@1.14.2
|
||||
verron:autosize@3.0.8
|
||||
webapp@1.4.0
|
||||
webapp-hashing@1.0.9
|
||||
wekan:accounts-cas@0.1.0
|
||||
wekan:wekan-ldap@0.0.2
|
||||
wekan-accounts-oidc@1.0.10
|
||||
wekan-markdown@1.0.7
|
||||
wekan-oidc@1.0.12
|
||||
wekan-scrollbar@3.1.3
|
||||
wekan-accounts-cas@0.1.0
|
||||
wekan-ldap@0.0.2
|
||||
yasaricli:slugify@0.0.7
|
||||
zimme:active-route@2.3.2
|
||||
+1049
-10
File diff suppressed because it is too large.
Load diff
+59
-82
@@ -1,81 +1,12 @@
|
||||
FROM debian:buster-slim
|
||||
FROM ubuntu:disco
|
||||
LABEL maintainer="wekan"
|
||||
|
||||
# Declare Arguments
|
||||
ARG NODE_VERSION
|
||||
ARG METEOR_RELEASE
|
||||
ARG METEOR_EDGE
|
||||
ARG USE_EDGE
|
||||
ARG NPM_VERSION
|
||||
ARG FIBERS_VERSION
|
||||
ARG ARCHITECTURE
|
||||
ARG SRC_PATH
|
||||
ARG WITH_API
|
||||
ARG MATOMO_ADDRESS
|
||||
ARG MATOMO_SITE_ID
|
||||
ARG MATOMO_DO_NOT_TRACK
|
||||
ARG MATOMO_WITH_USERNAME
|
||||
ARG BROWSER_POLICY_ENABLED
|
||||
ARG TRUSTED_URL
|
||||
ARG WEBHOOKS_ATTRIBUTES
|
||||
ARG OAUTH2_ENABLED
|
||||
ARG OAUTH2_CLIENT_ID
|
||||
ARG OAUTH2_SECRET
|
||||
ARG OAUTH2_SERVER_URL
|
||||
ARG OAUTH2_AUTH_ENDPOINT
|
||||
ARG OAUTH2_USERINFO_ENDPOINT
|
||||
ARG OAUTH2_TOKEN_ENDPOINT
|
||||
ARG LDAP_ENABLE
|
||||
ARG LDAP_PORT
|
||||
ARG LDAP_HOST
|
||||
ARG LDAP_BASEDN
|
||||
ARG LDAP_LOGIN_FALLBACK
|
||||
ARG LDAP_RECONNECT
|
||||
ARG LDAP_TIMEOUT
|
||||
ARG LDAP_IDLE_TIMEOUT
|
||||
ARG LDAP_CONNECT_TIMEOUT
|
||||
ARG LDAP_AUTHENTIFICATION
|
||||
ARG LDAP_AUTHENTIFICATION_USERDN
|
||||
ARG LDAP_AUTHENTIFICATION_PASSWORD
|
||||
ARG LDAP_LOG_ENABLED
|
||||
ARG LDAP_BACKGROUND_SYNC
|
||||
ARG LDAP_BACKGROUND_SYNC_INTERVAL
|
||||
ARG LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED
|
||||
ARG LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS
|
||||
ARG LDAP_ENCRYPTION
|
||||
ARG LDAP_CA_CERT
|
||||
ARG LDAP_REJECT_UNAUTHORIZED
|
||||
ARG LDAP_USER_SEARCH_FILTER
|
||||
ARG LDAP_USER_SEARCH_SCOPE
|
||||
ARG LDAP_USER_SEARCH_FIELD
|
||||
ARG LDAP_SEARCH_PAGE_SIZE
|
||||
ARG LDAP_SEARCH_SIZE_LIMIT
|
||||
ARG LDAP_GROUP_FILTER_ENABLE
|
||||
ARG LDAP_GROUP_FILTER_OBJECTCLASS
|
||||
ARG LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE
|
||||
ARG LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE
|
||||
ARG LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT
|
||||
ARG LDAP_GROUP_FILTER_GROUP_NAME
|
||||
ARG LDAP_UNIQUE_IDENTIFIER_FIELD
|
||||
ARG LDAP_UTF8_NAMES_SLUGIFY
|
||||
ARG LDAP_USERNAME_FIELD
|
||||
ARG LDAP_FULLNAME_FIELD
|
||||
ARG LDAP_MERGE_EXISTING_USERS
|
||||
ARG LDAP_SYNC_USER_DATA
|
||||
ARG LDAP_SYNC_USER_DATA_FIELDMAP
|
||||
ARG LDAP_SYNC_GROUP_ROLES
|
||||
ARG LDAP_DEFAULT_DOMAIN
|
||||
ARG LOGOUT_WITH_TIMER
|
||||
ARG LOGOUT_IN
|
||||
ARG LOGOUT_ON_HOURS
|
||||
ARG LOGOUT_ON_MINUTES
|
||||
ARG CORS
|
||||
|
||||
# Set the environment variables (defaults where required)
|
||||
# DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
|
||||
# ENV BUILD_DEPS="paxctl"
|
||||
ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python git ca-certificates gcc-7" \
|
||||
NODE_VERSION=v8.12.0 \
|
||||
ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python python3 python3-distutils git ca-certificates gcc-7" \
|
||||
DEBUG=false \
|
||||
NODE_VERSION=v8.16.0 \
|
||||
METEOR_RELEASE=1.6.0.1 \
|
||||
USE_EDGE=false \
|
||||
METEOR_EDGE=1.5-beta.17 \
|
||||
@@ -84,6 +15,13 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
|
||||
ARCHITECTURE=linux-x64 \
|
||||
SRC_PATH=./ \
|
||||
WITH_API=true \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
|
||||
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \
|
||||
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \
|
||||
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \
|
||||
EMAIL_NOTIFICATION_TIMEOUT=30000 \
|
||||
MATOMO_ADDRESS="" \
|
||||
MATOMO_SITE_ID="" \
|
||||
MATOMO_DO_NOT_TRACK=true \
|
||||
@@ -92,12 +30,17 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
|
||||
TRUSTED_URL="" \
|
||||
WEBHOOKS_ATTRIBUTES="" \
|
||||
OAUTH2_ENABLED=false \
|
||||
OAUTH2_LOGIN_STYLE=redirect \
|
||||
OAUTH2_CLIENT_ID="" \
|
||||
OAUTH2_SECRET="" \
|
||||
OAUTH2_SERVER_URL="" \
|
||||
OAUTH2_AUTH_ENDPOINT="" \
|
||||
OAUTH2_USERINFO_ENDPOINT="" \
|
||||
OAUTH2_TOKEN_ENDPOINT="" \
|
||||
OAUTH2_ID_MAP="" \
|
||||
OAUTH2_USERNAME_MAP="" \
|
||||
OAUTH2_FULLNAME_MAP="" \
|
||||
OAUTH2_EMAIL_MAP="" \
|
||||
LDAP_ENABLE=false \
|
||||
LDAP_PORT=389 \
|
||||
LDAP_HOST="" \
|
||||
@@ -134,15 +77,26 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
|
||||
LDAP_USERNAME_FIELD="" \
|
||||
LDAP_FULLNAME_FIELD="" \
|
||||
LDAP_MERGE_EXISTING_USERS=false \
|
||||
LDAP_EMAIL_FIELD="" \
|
||||
LDAP_EMAIL_MATCH_ENABLE=false \
|
||||
LDAP_EMAIL_MATCH_REQUIRE=false \
|
||||
LDAP_EMAIL_MATCH_VERIFIED=false \
|
||||
LDAP_SYNC_USER_DATA=false \
|
||||
LDAP_SYNC_USER_DATA_FIELDMAP="" \
|
||||
LDAP_SYNC_GROUP_ROLES="" \
|
||||
LDAP_DEFAULT_DOMAIN=""
|
||||
LOGOUT_WITH_TIMER="false" \
|
||||
LDAP_DEFAULT_DOMAIN="" \
|
||||
LDAP_SYNC_ADMIN_STATUS="" \
|
||||
LDAP_SYNC_ADMIN_GROUPS="" \
|
||||
HEADER_LOGIN_ID="" \
|
||||
HEADER_LOGIN_FIRSTNAME="" \
|
||||
HEADER_LOGIN_LASTNAME="" \
|
||||
HEADER_LOGIN_EMAIL="" \
|
||||
LOGOUT_WITH_TIMER=false \
|
||||
LOGOUT_IN="" \
|
||||
LOGOUT_ON_HOURS="" \
|
||||
LOGOUT_ON_MINUTES="" \
|
||||
CORS=""
|
||||
CORS="" \
|
||||
DEFAULT_AUTHENTICATION_METHOD=""
|
||||
|
||||
# Copy the app to the image
|
||||
COPY ${SRC_PATH} /home/wekan/app
|
||||
@@ -237,23 +191,43 @@ RUN \
|
||||
fi; \
|
||||
\
|
||||
# Get additional packages
|
||||
mkdir -p /home/wekan/app/packages && \
|
||||
#mkdir -p /home/wekan/app/packages && \
|
||||
chown wekan:wekan --recursive /home/wekan && \
|
||||
cd /home/wekan/app/packages && \
|
||||
gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/flow-router.git kadira-flow-router && \
|
||||
gosu wekan:wekan git clone --depth 1 -b master git://github.com/meteor-useraccounts/core.git meteor-useraccounts-core && \
|
||||
gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/meteor-accounts-cas.git && \
|
||||
gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/wekan-ldap.git && \
|
||||
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
|
||||
#cd /home/wekan/app/packages && \
|
||||
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router && \
|
||||
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core && \
|
||||
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git && \
|
||||
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git && \
|
||||
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git && \
|
||||
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git && \
|
||||
#gosu wekan:wekan git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git && \
|
||||
#gosu wekan:wekan mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan-accounts-oidc && \
|
||||
#gosu wekan:wekan mv meteor-accounts-oidc/packages/switch_oidc wekan-oidc && \
|
||||
#gosu wekan:wekan rm -rf meteor-accounts-oidc && \
|
||||
sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js && \
|
||||
cd /home/wekan/.meteor && \
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \
|
||||
\
|
||||
# extract the OpenAPI specification
|
||||
npm install -g api2html@0.3.0 && \
|
||||
mkdir -p /home/wekan/python && \
|
||||
chown wekan:wekan --recursive /home/wekan/python && \
|
||||
cd /home/wekan/python && \
|
||||
gosu wekan:wekan git clone --depth 1 -b master https://github.com/Kronuz/esprima-python && \
|
||||
cd /home/wekan/python/esprima-python && \
|
||||
python3 setup.py install --record files.txt && \
|
||||
cd /home/wekan/app &&\
|
||||
mkdir -p ./public/api && \
|
||||
python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \
|
||||
/opt/nodejs/bin/api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \
|
||||
# Build app
|
||||
cd /home/wekan/app && \
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor npm install && \
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build && \
|
||||
cp /home/wekan/app/fix-download-unicode/cfs_access-point.txt /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
|
||||
rm /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs && \
|
||||
chown wekan:wekan /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
|
||||
#Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
|
||||
#https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
|
||||
@@ -272,10 +246,13 @@ RUN \
|
||||
# Cleanup
|
||||
apt-get remove --purge -y ${BUILD_DEPS} && \
|
||||
apt-get autoremove -y && \
|
||||
npm uninstall -g api2html &&\
|
||||
rm -R /var/lib/apt/lists/* && \
|
||||
rm -R /home/wekan/.meteor && \
|
||||
rm -R /home/wekan/app && \
|
||||
rm -R /home/wekan/app_build && \
|
||||
cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \
|
||||
rm -R /home/wekan/python && \
|
||||
rm /home/wekan/install_meteor.sh
|
||||
|
||||
ENV PORT=8080
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2018 The Wekan Team
|
||||
Copyright (c) 2014-2019 The Wekan Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
[](https://codeclimate.com/github/wekan/wekan)
|
||||
[](https://david-dm.org/wekan/wekan)
|
||||
[](https://www.openhub.net/p/wekan)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_shield)
|
||||
|
||||
## [Translate Wekan at Transifex](https://transifex.com/wekan/wekan)
|
||||
|
||||
@@ -22,9 +23,8 @@ It's better than at chat where details get lost when chat scrolls up.
|
||||
|
||||
## Chat
|
||||
|
||||
[![Wekan Vanila Chat][vanila_badge]][vanila_chat] - Most Wekan community and developers are here at #wekan chat channel.
|
||||
Use webbrowser to register, and after that you can also alternatively use mobile app Rocket.Chat by Rocket.Chat with
|
||||
address https://chat.vanila.io and same username and password.
|
||||
[![Wekan Chat][vanila_badge]][wekan_chat] - Most Wekan community and developers are here. Works on webbrowser
|
||||
and PWA app that can be added as icon on Android and bookmark on iOS, used like native app.
|
||||
|
||||
[Wekan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ)
|
||||
|
||||
@@ -39,33 +39,55 @@ address https://chat.vanila.io and same username and password.
|
||||
Wekan is an completely [Open Source][open_source] and [Free software][free_software]
|
||||
collaborative kanban board application with MIT license.
|
||||
|
||||
Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
|
||||
Whether you’re maintaining a personal todo list, planning your holidays with some friends,
|
||||
or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool
|
||||
to keep your things organized. They give you a visual overview of the current state of your project,
|
||||
and make you productive by allowing you to focus on the few items that matter the most.
|
||||
|
||||
Since Wekan is a free software, you don’t have to trust us with your data and can
|
||||
install Wekan on your own computer or server. In fact we encourage you to do
|
||||
that by providing one-click installation on various platforms.
|
||||
|
||||
- [Features][features]: Wekan has real-time user interface. Not all features are implemented, yet.
|
||||
- [Platforms][platforms]: Wekan supports many platforms and plan is to add more. This will be the first place to look if you want to **install** it, test out and learn more in depth.
|
||||
- Wekan is used in [most countries of the world](https://snapcraft.io/wekan).
|
||||
- Wekan largest user has 13k users using Wekan in their company.
|
||||
- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 50 languages.
|
||||
- [Features][features]: Wekan has real-time user interface.
|
||||
- [Platforms][platforms]: Wekan supports many platforms.
|
||||
Wekan is critical part of new platforms Wekan is currently being integrated to.
|
||||
- [Integrations][integrations]: Current possible integrations and future plans.
|
||||
- [Team](https://github.com/wekan/wekan/wiki/Team): The people who spends their time and make Wekan into what it is right now.
|
||||
|
||||
## Requirements
|
||||
|
||||
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) /
|
||||
[Mac](https://github.com/wekan/wekan/wiki/Mac) / [Windows](https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows).
|
||||
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms). [ARM progress](https://github.com/wekan/wekan/issues/1053#issuecomment-410919264).
|
||||
- 1 GB RAM minimum free for Wekan. Production server should have miminum total 4 GB RAM.
|
||||
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/devel/docker-compose.yml): 3 frontend servers,
|
||||
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
|
||||
- Enough disk space and alerts about low disk space. If you run out disk space, MongoDB database gets corrupted.
|
||||
- SECURITY: Updating to newest Wekan version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off.
|
||||
Old versions have security issues because of old versions Node.js etc. Only newest Wekan is supported.
|
||||
Wekan on Sandstorm is not usually affected by any Standalone Wekan (Snap/Docker/Source) security issues.
|
||||
- [Reporting all new bugs immediately](https://github.com/wekan/wekan/issues).
|
||||
New features and fixes are added to Wekan [many times a day](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md).
|
||||
- [Backups](https://github.com/wekan/wekan/wiki/Backup) of Wekan database once a day miminum.
|
||||
Bugs, updates, users deleting list or card, harddrive full, harddrive crash etc can eat your data. There is no undo yet.
|
||||
Some bug can cause Wekan board to not load at all, requiring manual fixing of database content.
|
||||
|
||||
## Roadmap
|
||||
|
||||
[Roadmap](https://github.com/wekan/wekan/wiki/Roadmap)
|
||||
[Roadmap Milestones](https://github.com/wekan/wekan/milestones)
|
||||
|
||||
Upcoming Wekan App Development Platform will make possible many use cases. If you don't find your feature or integration in
|
||||
GitHub issues and [Features][features] or [Integrations][integrations] page at wiki, please add them.
|
||||
[Developer Documentation][dev_docs]
|
||||
|
||||
We are very welcoming to new developers and teams to submit new pull requests to devel branch to make this Wekan App Development Platform possible faster. Please see [Developer Documentation][dev_docs] to get started.
|
||||
- There is many companies and individuals contributing code to Wekan, to add features and bugfixes
|
||||
[many times a day](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md).
|
||||
- [Please add Add new Feature Requests and Bug Reports immediately](https://github.com/wekan/wekan/issues).
|
||||
- [Commercial Support](https://wekan.team).
|
||||
- [Bounties](https://wekan.team/bounties/index.html).
|
||||
|
||||
We also welcome sponsors for features and bugfixes. By working directly with Wekan you get the benefit of active maintenance and new features added by growing Wekan developer community.
|
||||
|
||||
Actual work happens at [Wekan GitHub issues][wekan_issues].
|
||||
|
||||
See [Development links on Wekan wiki](https://github.com/wekan/wekan/wiki#Development) bottom of the page for more info.
|
||||
|
||||
If you want to know what is going on exactly this moment, you can check out the [project page](https://github.com/wekan/wekan/projects/2).
|
||||
We also welcome sponsors for features and bugfixes.
|
||||
By working directly with Wekan you get the benefit of active maintenance and new features added by growing Wekan developer community.
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -111,4 +133,7 @@ with [Meteor](https://www.meteor.com).
|
||||
[open_source]: https://en.wikipedia.org/wiki/Open-source_software
|
||||
[free_software]: https://en.wikipedia.org/wiki/Free_software
|
||||
[vanila_badge]: https://vanila.io/img/join-chat-button2.png
|
||||
[vanila_chat]: https://chat.vanila.io/channel/wekan
|
||||
[wekan_chat]: https://community.vanila.io/wekan
|
||||
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_large)
|
||||
@@ -0,0 +1,9 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v2.63.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
userScripts:
|
||||
build: stacksmith/user-scripts/build.sh
|
||||
boot: stacksmith/user-scripts/boot.sh
|
||||
run: stacksmith/user-scripts/run.sh
|
||||
@@ -99,6 +99,9 @@ template(name="boardActivities")
|
||||
else
|
||||
| {{{_ 'activity-added' memberLink cardLink}}}.
|
||||
|
||||
if($eq activityType 'moveCardBoard')
|
||||
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
|
||||
|
||||
if($eq activityType 'moveCard')
|
||||
| {{{_ 'activity-moved' cardLink oldList.title list.title}}}.
|
||||
|
||||
@@ -114,6 +117,12 @@ template(name="boardActivities")
|
||||
if($eq activityType 'removedLabel')
|
||||
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
|
||||
|
||||
if($eq activityType 'setCustomField')
|
||||
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
|
||||
|
||||
if($eq activityType 'unsetCustomField')
|
||||
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
|
||||
|
||||
if($eq activityType 'unjoinMember')
|
||||
if($eq user._id member._id)
|
||||
| {{{_ 'activity-unjoined' cardLink}}}.
|
||||
@@ -129,7 +138,7 @@ template(name="cardActivities")
|
||||
p.activity-desc
|
||||
+memberName(user=user)
|
||||
if($eq activityType 'createCard')
|
||||
| {{_ 'activity-added' cardLabel list.title}}.
|
||||
| {{_ 'activity-added' cardLabel listName}}.
|
||||
if($eq activityType 'importCard')
|
||||
| {{{_ 'activity-imported' cardLabel list.title sourceLink}}}.
|
||||
if($eq activityType 'joinMember')
|
||||
@@ -170,6 +179,10 @@ template(name="cardActivities")
|
||||
| {{_ 'activity-sent' cardLabel boardLabel}}.
|
||||
if($eq activityType 'moveCard')
|
||||
| {{_ 'activity-moved' cardLabel oldList.title list.title}}.
|
||||
|
||||
if($eq activityType 'moveCardBoard')
|
||||
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
|
||||
|
||||
if($eq activityType 'addAttachment')
|
||||
| {{{_ 'activity-attached' attachmentLink cardLabel}}}.
|
||||
if attachment.isImage
|
||||
|
||||
@@ -74,6 +74,8 @@ BlazeComponent.extendComponent({
|
||||
|
||||
lastLabel(){
|
||||
const lastLabelId = this.currentData().labelId;
|
||||
if (!lastLabelId)
|
||||
return null;
|
||||
const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(lastLabelId);
|
||||
if(lastLabel.name === undefined || lastLabel.name === ''){
|
||||
return lastLabel.color;
|
||||
@@ -82,6 +84,28 @@ BlazeComponent.extendComponent({
|
||||
}
|
||||
},
|
||||
|
||||
lastCustomField(){
|
||||
const lastCustomField = CustomFields.findOne(this.currentData().customFieldId);
|
||||
if (!lastCustomField)
|
||||
return null;
|
||||
return lastCustomField.name;
|
||||
},
|
||||
|
||||
lastCustomFieldValue(){
|
||||
const lastCustomField = CustomFields.findOne(this.currentData().customFieldId);
|
||||
if (!lastCustomField)
|
||||
return null;
|
||||
const value = this.currentData().value;
|
||||
if (lastCustomField.settings.dropdownItems && lastCustomField.settings.dropdownItems.length > 0) {
|
||||
const dropDownValue = _.find(lastCustomField.settings.dropdownItems, (item) => {
|
||||
return item._id === value;
|
||||
});
|
||||
if (dropDownValue)
|
||||
return dropDownValue.name;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
listLabel() {
|
||||
return this.currentData().list().title;
|
||||
},
|
||||
@@ -117,6 +141,8 @@ BlazeComponent.extendComponent({
|
||||
|
||||
customField() {
|
||||
const customField = this.currentData().customField();
|
||||
if (!customField)
|
||||
return null;
|
||||
return customField.name;
|
||||
},
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
Template.boardListHeaderBar.events({
|
||||
'click .js-open-archived-board'() {
|
||||
Modal.open('archivedBoards');
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.subscribe('archivedBoards');
|
||||
|
||||
@@ -20,16 +20,20 @@ template(name="boardBody")
|
||||
class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
|
||||
if showOverlay.get
|
||||
.board-overlay
|
||||
if isViewSwimlanes
|
||||
if currentBoard.isTemplatesBoard
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
if isViewLists
|
||||
+listsGroup
|
||||
if isViewCalendar
|
||||
else if isViewSwimlanes
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else if isViewLists
|
||||
+listsGroup(currentBoard)
|
||||
else if isViewCalendar
|
||||
+calendarView
|
||||
|
||||
template(name="calendarView")
|
||||
.calendar-view.swimlane
|
||||
if currentCard
|
||||
+cardDetails(currentCard)
|
||||
+fullcalendar(calendarOptions)
|
||||
if isViewCalendar
|
||||
.calendar-view.swimlane
|
||||
if currentCard
|
||||
+cardDetails(currentCard)
|
||||
+fullcalendar(calendarOptions)
|
||||
@@ -1,5 +1,6 @@
|
||||
const subManager = new SubsManager();
|
||||
const { calculateIndex, enableClickOnTouch } = Utils;
|
||||
const swimlaneWhileSortingHeight = 150;
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
@@ -35,6 +36,37 @@ BlazeComponent.extendComponent({
|
||||
this._isDragging = false;
|
||||
// Used to set the overlay
|
||||
this.mouseHasEnterCardDetails = false;
|
||||
|
||||
// fix swimlanes sort field if there are null values
|
||||
const currentBoardData = Boards.findOne(Session.get('currentBoard'));
|
||||
const nullSortSwimlanes = currentBoardData.nullSortSwimlanes();
|
||||
if (nullSortSwimlanes.count() > 0) {
|
||||
const swimlanes = currentBoardData.swimlanes();
|
||||
let count = 0;
|
||||
swimlanes.forEach((s) => {
|
||||
Swimlanes.update(s._id, {
|
||||
$set: {
|
||||
sort: count,
|
||||
},
|
||||
});
|
||||
count += 1;
|
||||
});
|
||||
}
|
||||
|
||||
// fix lists sort field if there are null values
|
||||
const nullSortLists = currentBoardData.nullSortLists();
|
||||
if (nullSortLists.count() > 0) {
|
||||
const lists = currentBoardData.lists();
|
||||
let count = 0;
|
||||
lists.forEach((l) => {
|
||||
Lists.update(l._id, {
|
||||
$set: {
|
||||
sort: count,
|
||||
},
|
||||
});
|
||||
count += 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
onRendered() {
|
||||
const boardComponent = this;
|
||||
@@ -43,21 +75,64 @@ BlazeComponent.extendComponent({
|
||||
$swimlanesDom.sortable({
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
helper: 'clone',
|
||||
helper(evt, item) {
|
||||
const helper = $(`<div class="swimlane"
|
||||
style="flex-direction: column;
|
||||
height: ${swimlaneWhileSortingHeight}px;
|
||||
width: $(boardComponent.width)px;
|
||||
overflow: hidden;"/>`);
|
||||
helper.append(item.clone());
|
||||
// Also grab the list of lists of cards
|
||||
const list = item.next();
|
||||
helper.append(list.clone());
|
||||
return helper;
|
||||
},
|
||||
handle: '.js-swimlane-header',
|
||||
items: '.js-swimlane:not(.placeholder)',
|
||||
items: '.swimlane:not(.placeholder)',
|
||||
placeholder: 'swimlane placeholder',
|
||||
distance: 7,
|
||||
start(evt, ui) {
|
||||
const listDom = ui.placeholder.next('.js-swimlane');
|
||||
const parentOffset = ui.item.parent().offset();
|
||||
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
listDom.addClass('moving-swimlane');
|
||||
boardComponent.setIsDragging(true);
|
||||
|
||||
ui.placeholder.insertAfter(ui.placeholder.next());
|
||||
boardComponent.origPlaceholderIndex = ui.placeholder.index();
|
||||
|
||||
// resize all swimlanes + headers to be a total of 150 px per row
|
||||
// this could be achieved by setIsDragging(true) but we want immediate
|
||||
// result
|
||||
ui.item.siblings('.js-swimlane').css('height', `${swimlaneWhileSortingHeight - 26}px`);
|
||||
|
||||
// set the new scroll height after the resize and insertion of
|
||||
// the placeholder. We want the element under the cursor to stay
|
||||
// at the same place on the screen
|
||||
ui.item.parent().get(0).scrollTop = ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
|
||||
},
|
||||
beforeStop(evt, ui) {
|
||||
const parentOffset = ui.item.parent().offset();
|
||||
const siblings = ui.item.siblings('.js-swimlane');
|
||||
siblings.css('height', '');
|
||||
|
||||
// compute the new scroll height after the resize and removal of
|
||||
// the placeholder
|
||||
const scrollTop = ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
|
||||
|
||||
// then reset the original view of the swimlane
|
||||
siblings.removeClass('moving-swimlane');
|
||||
|
||||
// and apply the computed scrollheight
|
||||
ui.item.parent().get(0).scrollTop = scrollTop;
|
||||
},
|
||||
stop(evt, ui) {
|
||||
// To attribute the new index number, we need to get the DOM element
|
||||
// of the previous and the following card -- if any.
|
||||
const prevSwimlaneDom = ui.item.prev('.js-swimlane').get(0);
|
||||
const nextSwimlaneDom = ui.item.next('.js-swimlane').get(0);
|
||||
const prevSwimlaneDom = ui.item.prevAll('.js-swimlane').get(0);
|
||||
const nextSwimlaneDom = ui.item.nextAll('.js-swimlane').get(0);
|
||||
const sortIndex = calculateIndex(prevSwimlaneDom, nextSwimlaneDom, 1);
|
||||
|
||||
$swimlanesDom.sortable('cancel');
|
||||
@@ -72,6 +147,30 @@ BlazeComponent.extendComponent({
|
||||
|
||||
boardComponent.setIsDragging(false);
|
||||
},
|
||||
sort(evt, ui) {
|
||||
// get the mouse position in the sortable
|
||||
const parentOffset = ui.item.parent().offset();
|
||||
const cursorY = evt.pageY - parentOffset.top + ui.item.parent().scrollTop();
|
||||
|
||||
// compute the intended index of the placeholder (we need to skip the
|
||||
// slots between the headers and the list of cards)
|
||||
const newplaceholderIndex = Math.floor(cursorY / swimlaneWhileSortingHeight);
|
||||
let destPlaceholderIndex = (newplaceholderIndex + 1) * 2;
|
||||
|
||||
// if we are scrolling far away from the bottom of the list
|
||||
if (destPlaceholderIndex >= ui.item.parent().get(0).childElementCount) {
|
||||
destPlaceholderIndex = ui.item.parent().get(0).childElementCount - 1;
|
||||
}
|
||||
|
||||
// update the placeholder position in the DOM tree
|
||||
if (destPlaceholderIndex !== ui.placeholder.index()) {
|
||||
if (destPlaceholderIndex < boardComponent.origPlaceholderIndex) {
|
||||
ui.placeholder.insertBefore(ui.placeholder.siblings().slice(destPlaceholderIndex - 2, destPlaceholderIndex - 1));
|
||||
} else {
|
||||
ui.placeholder.insertAfter(ui.placeholder.siblings().slice(destPlaceholderIndex - 1, destPlaceholderIndex));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ugly touch event hotfix
|
||||
@@ -103,7 +202,7 @@ BlazeComponent.extendComponent({
|
||||
|
||||
isViewCalendar() {
|
||||
const currentUser = Meteor.user();
|
||||
if (!currentUser) return true;
|
||||
if (!currentUser) return false;
|
||||
return (currentUser.profile.boardView === 'board-view-cal');
|
||||
},
|
||||
|
||||
@@ -233,4 +332,9 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
};
|
||||
},
|
||||
isViewCalendar() {
|
||||
const currentUser = Meteor.user();
|
||||
if (!currentUser) return false;
|
||||
return (currentUser.profile.boardView === 'board-view-cal');
|
||||
},
|
||||
}).register('calendarView');
|
||||
@@ -50,6 +50,6 @@ position()
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin: 0
|
||||
padding: 0 40px 0px 0
|
||||
padding: 0 0px 0px 0
|
||||
overflow-x: hidden
|
||||
overflow-y: auto
|
||||
@@ -7,71 +7,69 @@ template(name="boardHeaderBar")
|
||||
|
||||
.board-header-btns.left
|
||||
unless isMiniScreen
|
||||
unless isSandstorm
|
||||
if currentBoard
|
||||
if currentUser
|
||||
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
|
||||
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
if showStarCounter
|
||||
span
|
||||
= currentBoard.stars
|
||||
if currentBoard
|
||||
if currentUser
|
||||
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
|
||||
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
if showStarCounter
|
||||
span
|
||||
= currentBoard.stars
|
||||
|
||||
a.board-header-btn(
|
||||
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
|
||||
title="{{_ currentBoard.permission}}")
|
||||
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
span {{_ currentBoard.permission}}
|
||||
a.board-header-btn(
|
||||
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
|
||||
title="{{_ currentBoard.permission}}")
|
||||
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
span {{_ currentBoard.permission}}
|
||||
|
||||
a.board-header-btn.js-watch-board(
|
||||
title="{{_ watchLevel }}")
|
||||
if $eq watchLevel "watching"
|
||||
i.fa.fa-eye
|
||||
if $eq watchLevel "tracking"
|
||||
i.fa.fa-bell
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
span {{_ watchLevel}}
|
||||
a.board-header-btn.js-watch-board(
|
||||
title="{{_ watchLevel }}")
|
||||
if $eq watchLevel "watching"
|
||||
i.fa.fa-eye
|
||||
if $eq watchLevel "tracking"
|
||||
i.fa.fa-bell
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
span {{_ watchLevel}}
|
||||
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
|
||||
.board-header-btns.right
|
||||
if currentBoard
|
||||
if isMiniScreen
|
||||
unless isSandstorm
|
||||
if currentUser
|
||||
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
|
||||
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
if showStarCounter
|
||||
span
|
||||
= currentBoard.stars
|
||||
if currentUser
|
||||
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
|
||||
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
if showStarCounter
|
||||
span
|
||||
= currentBoard.stars
|
||||
|
||||
a.board-header-btn(
|
||||
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
|
||||
title="{{_ currentBoard.permission}}")
|
||||
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
span {{_ currentBoard.permission}}
|
||||
a.board-header-btn(
|
||||
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
|
||||
title="{{_ currentBoard.permission}}")
|
||||
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
span {{_ currentBoard.permission}}
|
||||
|
||||
a.board-header-btn.js-watch-board(
|
||||
title="{{_ watchLevel }}")
|
||||
if $eq watchLevel "watching"
|
||||
i.fa.fa-eye
|
||||
if $eq watchLevel "tracking"
|
||||
i.fa.fa-bell
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
span {{_ watchLevel}}
|
||||
a.board-header-btn.js-watch-board(
|
||||
title="{{_ watchLevel }}")
|
||||
if $eq watchLevel "watching"
|
||||
i.fa.fa-eye
|
||||
if $eq watchLevel "tracking"
|
||||
i.fa.fa-bell
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
span {{_ watchLevel}}
|
||||
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
|
||||
if isSandstorm
|
||||
if currentUser
|
||||
@@ -96,10 +94,11 @@ template(name="boardHeaderBar")
|
||||
i.fa.fa-search
|
||||
span {{_ 'search'}}
|
||||
|
||||
a.board-header-btn.js-toggle-board-view(
|
||||
title="{{_ 'board-view'}}")
|
||||
i.fa.fa-th-large
|
||||
span {{_ currentUser.profile.boardView}}
|
||||
unless currentBoard.isTemplatesBoard
|
||||
a.board-header-btn.js-toggle-board-view(
|
||||
title="{{_ 'board-view'}}")
|
||||
i.fa.fa-th-large
|
||||
span {{_ currentUser.profile.boardView}}
|
||||
|
||||
if canModifyBoard
|
||||
a.board-header-btn.js-multiselection-activate(
|
||||
@@ -112,40 +111,8 @@ template(name="boardHeaderBar")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
.separator
|
||||
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}")
|
||||
i.board-header-btn-icon.fa.fa-navicon
|
||||
|
||||
template(name="boardMenuPopup")
|
||||
ul.pop-over-list
|
||||
li: a.js-custom-fields {{_ 'custom-fields'}}
|
||||
li: a.js-open-archives {{_ 'archived-items'}}
|
||||
if currentUser.isBoardAdmin
|
||||
li: a.js-change-board-color {{_ 'board-change-color'}}
|
||||
//-
|
||||
XXX Language should be handled by sandstorm, but for now display a
|
||||
language selection link in the board menu. This link is normally present
|
||||
in the header bar that is not displayed on sandstorm.
|
||||
if isSandstorm
|
||||
li: a.js-change-language {{_ 'language'}}
|
||||
unless isSandstorm
|
||||
if currentUser.isBoardAdmin
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
|
||||
li: a.js-archive-board {{_ 'archive-board'}}
|
||||
li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-subtask-settings {{_ 'subtask-settings'}}
|
||||
|
||||
if isSandstorm
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
|
||||
li: a.js-import-board {{_ 'import-board-c'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-subtask-settings {{_ 'subtask-settings'}}
|
||||
a.board-header-btn.js-toggle-sidebar
|
||||
i.fa.fa-navicon
|
||||
|
||||
template(name="boardVisibilityList")
|
||||
ul.pop-over-list
|
||||
@@ -196,64 +163,6 @@ template(name="boardChangeWatchPopup")
|
||||
i.fa.fa-check
|
||||
span.sub-name {{_ 'muted-info'}}
|
||||
|
||||
template(name="boardChangeColorPopup")
|
||||
.board-backgrounds-list.clearfix
|
||||
each backgroundColors
|
||||
.board-background-select.js-select-background
|
||||
span.background-box(class="board-color-{{this}}")
|
||||
if isSelected
|
||||
i.fa.fa-check
|
||||
|
||||
template(name="boardSubtaskSettingsPopup")
|
||||
form.board-subtask-settings
|
||||
h3 {{_ 'show-parent-in-minicard'}}
|
||||
a#prefix-with-full-path.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
|
||||
span {{_ 'prefix-with-full-path'}}
|
||||
a#prefix-with-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
|
||||
span {{_ 'prefix-with-parent'}}
|
||||
a#subtext-with-full-path.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
|
||||
span {{_ 'subtext-with-full-path'}}
|
||||
a#subtext-with-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
|
||||
span {{_ 'subtext-with-parent'}}
|
||||
a#no-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
|
||||
span {{_ 'no-parent'}}
|
||||
div
|
||||
hr
|
||||
|
||||
div.check-div
|
||||
a.flex.js-field-has-subtasks(class="{{#if allowsSubtasks}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsSubtasks}}is-checked{{/if}}")
|
||||
span {{_ 'show-subtasks-field'}}
|
||||
|
||||
label
|
||||
| {{_ 'deposit-subtasks-board'}}
|
||||
select.js-field-deposit-board(disabled="{{#unless allowsSubtasks}}disabled{{/unless}}")
|
||||
each boards
|
||||
if isBoardSelected
|
||||
option(value=_id selected="selected") {{title}}
|
||||
else
|
||||
option(value=_id) {{title}}
|
||||
if isNullBoardSelected
|
||||
option(value='null' selected="selected") {{_ 'custom-field-dropdown-none'}}
|
||||
else
|
||||
option(value='null') {{_ 'custom-field-dropdown-none'}}
|
||||
div
|
||||
hr
|
||||
|
||||
label
|
||||
| {{_ 'deposit-subtasks-list'}}
|
||||
select.js-field-deposit-list(disabled="{{#unless hasLists}}disabled{{/unless}}")
|
||||
each lists
|
||||
if isListSelected
|
||||
option(value=_id selected="selected") {{title}}
|
||||
else
|
||||
option(value=_id) {{title}}
|
||||
|
||||
template(name="createBoard")
|
||||
form
|
||||
label
|
||||
@@ -275,14 +184,10 @@ template(name="createBoard")
|
||||
input.primary.wide(type="submit" value="{{_ 'create'}}")
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-import-board {{_ 'import-board'}}
|
||||
|
||||
template(name="chooseBoardSource")
|
||||
ul.pop-over-list
|
||||
li
|
||||
a(href="{{pathFor '/import/trello'}}") {{_ 'from-trello'}}
|
||||
li
|
||||
a(href="{{pathFor '/import/wekan'}}") {{_ 'from-wekan'}}
|
||||
a.js-import-board {{_ 'import'}}
|
||||
span.quiet
|
||||
| /
|
||||
a.js-board-template {{_ 'template'}}
|
||||
|
||||
template(name="boardChangeTitlePopup")
|
||||
form
|
||||
@@ -297,28 +202,3 @@ template(name="boardChangeTitlePopup")
|
||||
template(name="boardCreateRulePopup")
|
||||
p {{_ 'close-board-pop'}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
|
||||
|
||||
|
||||
template(name="archiveBoardPopup")
|
||||
p {{_ 'close-board-pop'}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
|
||||
|
||||
template(name="outgoingWebhooksPopup")
|
||||
each integrations
|
||||
form.integration-form
|
||||
if title
|
||||
h4 {{title}}
|
||||
else
|
||||
h4 {{_ 'no-name'}}
|
||||
label
|
||||
| URL
|
||||
input.js-outgoing-webhooks-url(type="text" name="url" value=url)
|
||||
input(type="hidden" value=_id name="id")
|
||||
input.primary.wide(type="submit" value="{{_ 'save'}}")
|
||||
form.integration-form
|
||||
h4
|
||||
| {{_ 'new-outgoing-webhook'}}
|
||||
label
|
||||
| URL
|
||||
input.js-outgoing-webhooks-url(type="text" name="url" autofocus)
|
||||
input.primary.wide(type="submit" value="{{_ 'save'}}")
|
||||
@@ -97,6 +97,9 @@ BlazeComponent.extendComponent({
|
||||
currentUser.setBoardView('board-view-lists');
|
||||
}
|
||||
},
|
||||
'click .js-toggle-sidebar'() {
|
||||
Sidebar.toggle();
|
||||
},
|
||||
'click .js-open-filter-view'() {
|
||||
Sidebar.setView('filter');
|
||||
},
|
||||
@@ -135,124 +138,6 @@ Template.boardHeaderBar.helpers({
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
backgroundColors() {
|
||||
return Boards.simpleSchema()._schema.color.allowedValues;
|
||||
},
|
||||
|
||||
isSelected() {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
return currentBoard.color === this.currentData().toString();
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-select-background'(evt) {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
const newColor = this.currentData().toString();
|
||||
currentBoard.setColor(newColor);
|
||||
evt.preventDefault();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('boardChangeColorPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
},
|
||||
|
||||
allowsSubtasks() {
|
||||
return this.currentBoard.allowsSubtasks;
|
||||
},
|
||||
|
||||
isBoardSelected() {
|
||||
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
|
||||
},
|
||||
|
||||
isNullBoardSelected() {
|
||||
return (this.currentBoard.subtasksDefaultBoardId === null) || (this.currentBoard.subtasksDefaultBoardId === undefined);
|
||||
},
|
||||
|
||||
boards() {
|
||||
return Boards.find({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
},
|
||||
|
||||
lists() {
|
||||
return Lists.find({
|
||||
boardId: this.currentBoard._id,
|
||||
archived: false,
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
},
|
||||
|
||||
hasLists() {
|
||||
return this.lists().count() > 0;
|
||||
},
|
||||
|
||||
isListSelected() {
|
||||
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
|
||||
},
|
||||
|
||||
presentParentTask() {
|
||||
let result = this.currentBoard.presentParentTask;
|
||||
if ((result === null) || (result === undefined)) {
|
||||
result = 'no-parent';
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-field-has-subtasks'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
|
||||
this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
|
||||
$('.js-field-has-subtasks .materialCheckBox').toggleClass('is-checked', this.currentBoard.allowsSubtasks);
|
||||
$('.js-field-has-subtasks').toggleClass('is-checked', this.currentBoard.allowsSubtasks);
|
||||
$('.js-field-deposit-board').prop('disabled', !this.currentBoard.allowsSubtasks);
|
||||
},
|
||||
'change .js-field-deposit-board'(evt) {
|
||||
let value = evt.target.value;
|
||||
if (value === 'null') {
|
||||
value = null;
|
||||
}
|
||||
this.currentBoard.setSubtasksDefaultBoardId(value);
|
||||
evt.preventDefault();
|
||||
},
|
||||
'change .js-field-deposit-list'(evt) {
|
||||
this.currentBoard.setSubtasksDefaultListId(evt.target.value);
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click .js-field-show-parent-in-minicard'(evt) {
|
||||
const value = evt.target.id || $(evt.target).parent()[0].id || $(evt.target).parent()[0].parent()[0].id;
|
||||
const options = [
|
||||
'prefix-with-full-path',
|
||||
'prefix-with-parent',
|
||||
'subtext-with-full-path',
|
||||
'subtext-with-parent',
|
||||
'no-parent'];
|
||||
options.forEach(function(element) {
|
||||
if (element !== value) {
|
||||
$(`#${element} .materialCheckBox`).toggleClass('is-checked', false);
|
||||
$(`#${element}`).toggleClass('is-checked', false);
|
||||
}
|
||||
});
|
||||
$(`#${value} .materialCheckBox`).toggleClass('is-checked', true);
|
||||
$(`#${value}`).toggleClass('is-checked', true);
|
||||
this.currentBoard.setPresentParentTask(value);
|
||||
evt.preventDefault();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('boardSubtaskSettingsPopup');
|
||||
|
||||
const CreateBoard = BlazeComponent.extendComponent({
|
||||
template() {
|
||||
return 'createBoard';
|
||||
@@ -304,16 +189,11 @@ const CreateBoard = BlazeComponent.extendComponent({
|
||||
'click .js-import': Popup.open('boardImportBoard'),
|
||||
submit: this.onSubmit,
|
||||
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||
'click .js-board-template': Popup.open('searchElement'),
|
||||
}];
|
||||
},
|
||||
}).register('createBoardPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
template() {
|
||||
return 'chooseBoardSource';
|
||||
},
|
||||
}).register('chooseBoardSourcePopup');
|
||||
|
||||
(class HeaderBarCreateBoard extends CreateBoard {
|
||||
onSubmit(evt) {
|
||||
super.onSubmit(evt);
|
||||
@@ -363,50 +243,3 @@ BlazeComponent.extendComponent({
|
||||
}];
|
||||
},
|
||||
}).register('boardChangeWatchPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
integrations() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
return Integrations.find({ boardId: `${boardId}` }).fetch();
|
||||
},
|
||||
|
||||
integration(id) {
|
||||
const boardId = Session.get('currentBoard');
|
||||
return Integrations.findOne({ _id: id, boardId: `${boardId}` });
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'submit'(evt) {
|
||||
evt.preventDefault();
|
||||
const url = evt.target.url.value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
let id = null;
|
||||
let integration = null;
|
||||
if (evt.target.id) {
|
||||
id = evt.target.id.value;
|
||||
integration = this.integration(id);
|
||||
if (url) {
|
||||
Integrations.update(integration._id, {
|
||||
$set: {
|
||||
url: `${url}`,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
Integrations.remove(integration._id);
|
||||
}
|
||||
} else if (url) {
|
||||
Integrations.insert({
|
||||
userId: Meteor.userId(),
|
||||
enabled: true,
|
||||
type: 'outgoing-webhooks',
|
||||
url: `${url}`,
|
||||
boardId: `${boardId}`,
|
||||
activities: ['all'],
|
||||
});
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('outgoingWebhooksPopup');
|
||||
@@ -22,13 +22,16 @@ template(name="boardList")
|
||||
i.fa.js-star-board(
|
||||
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
|
||||
title="{{_ 'star-board-title'}}")
|
||||
|
||||
p.board-list-item-desc= description
|
||||
if hasSpentTimeCards
|
||||
i.fa.js-has-spenttime-cards(
|
||||
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
|
||||
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
|
||||
i.fa.js-clone-board(
|
||||
class="fa-clone"
|
||||
title="{{_ 'duplicate-board'}}")
|
||||
|
||||
|
||||
p.board-list-item-desc= description
|
||||
|
||||
template(name="boardListHeaderBar")
|
||||
h1 {{_ 'my-boards'}}
|
||||
@@ -36,3 +39,6 @@ template(name="boardListHeaderBar")
|
||||
a.board-header-btn.js-open-archived-board
|
||||
i.fa.fa-archive
|
||||
span {{_ 'archives'}}
|
||||
a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
|
||||
i.fa.fa-clone
|
||||
span {{_ 'templates'}}
|
||||
@@ -1,5 +1,20 @@
|
||||
const subManager = new SubsManager();
|
||||
|
||||
Template.boardListHeaderBar.events({
|
||||
'click .js-open-archived-board'() {
|
||||
Modal.open('archivedBoards');
|
||||
},
|
||||
});
|
||||
|
||||
Template.boardListHeaderBar.helpers({
|
||||
templatesBoardId() {
|
||||
return Meteor.user().getTemplatesBoardId();
|
||||
},
|
||||
templatesBoardSlug() {
|
||||
return Meteor.user().getTemplatesBoardSlug();
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
Meteor.subscribe('setting');
|
||||
@@ -9,11 +24,9 @@ BlazeComponent.extendComponent({
|
||||
return Boards.find({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
type: 'board',
|
||||
}, { sort: ['title'] });
|
||||
},
|
||||
|
||||
isStarred() {
|
||||
const user = Meteor.user();
|
||||
return user && user.hasStarred(this.currentData()._id);
|
||||
@@ -42,6 +55,21 @@ BlazeComponent.extendComponent({
|
||||
Meteor.user().toggleBoardStar(boardId);
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click .js-clone-board'(evt) {
|
||||
Meteor.call('cloneBoard',
|
||||
this.currentData()._id,
|
||||
Session.get('fromBoard'),
|
||||
(err, res) => {
|
||||
if (err) {
|
||||
this.setError(err.error);
|
||||
} else {
|
||||
Session.set('fromBoard', null);
|
||||
Utils.goBoardId(res);
|
||||
}
|
||||
}
|
||||
);
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click .js-accept-invite'() {
|
||||
const boardId = this.currentData()._id;
|
||||
Meteor.user().removeInvite(boardId);
|
||||
|
||||
@@ -94,13 +94,27 @@ $spaceBetweenTiles = 16px
|
||||
.is-star-active
|
||||
color: white
|
||||
|
||||
.fa-clone
|
||||
position: absolute;
|
||||
bottom: 0
|
||||
font-size: 14px
|
||||
height: 18px
|
||||
line-height: 18px
|
||||
opacity: 0
|
||||
right: 0
|
||||
padding: 9px 9px
|
||||
transition-duration: .15s
|
||||
transition-property: color, font-size, background
|
||||
|
||||
li:hover a
|
||||
&:hover
|
||||
.fa-star,
|
||||
.fa-clone,
|
||||
.fa-star-o
|
||||
color: white
|
||||
|
||||
.fa-star,
|
||||
.fa-clone,
|
||||
.fa-star-o
|
||||
color: white
|
||||
opacity: .75
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
template(name="miniboard")
|
||||
.minicard(
|
||||
class="minicard-{{colorClass}}")
|
||||
.minicard-title
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
+viewer
|
||||
= title
|
||||
@@ -1,6 +1,6 @@
|
||||
template(name="cardDetails")
|
||||
section.card-details.js-card-details.js-perfect-scrollbar: .card-details-canvas
|
||||
.card-details-header
|
||||
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
|
||||
+inlinedForm(classNames="js-card-details-title")
|
||||
+editCardTitleForm
|
||||
else
|
||||
@@ -19,16 +19,14 @@ template(name="cardDetails")
|
||||
a.js-parent-card(href=linkForCard) {{title}}
|
||||
// else
|
||||
{{_ 'top-level-card'}}
|
||||
unless isSandstorm
|
||||
if isLinkedCard
|
||||
h3.linked-card-location
|
||||
+viewer
|
||||
| {{getBoardTitle}} > {{getTitle}}
|
||||
if isLinkedCard
|
||||
h3.linked-card-location
|
||||
+viewer
|
||||
| {{getBoardTitle}} > {{getTitle}}
|
||||
|
||||
if getArchived
|
||||
if isLinkedBoard
|
||||
unless isSandstorm
|
||||
p.warning {{_ 'board-archived'}}
|
||||
p.warning {{_ 'board-archived'}}
|
||||
else
|
||||
p.warning {{_ 'card-archived'}}
|
||||
|
||||
@@ -192,11 +190,9 @@ template(name="cardDetails")
|
||||
unless currentUser.isNoComments
|
||||
if isLoaded.get
|
||||
if isLinkedCard
|
||||
unless isSandstorm
|
||||
+activities(card=this mode="linkedcard")
|
||||
+activities(card=this mode="linkedcard")
|
||||
else if isLinkedBoard
|
||||
unless isSandstorm
|
||||
+activities(card=this mode="linkedboard")
|
||||
+activities(card=this mode="linkedboard")
|
||||
else
|
||||
+activities(card=this mode="card")
|
||||
|
||||
@@ -234,6 +230,7 @@ template(name="cardDetailsActionsPopup")
|
||||
li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
|
||||
li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
|
||||
li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}}
|
||||
li: a.js-set-card-color {{_ 'setCardColorPopup-title'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}}
|
||||
@@ -264,7 +261,7 @@ template(name="copyChecklistToManyCardsPopup")
|
||||
|
||||
template(name="boardsAndLists")
|
||||
label {{_ 'boards'}}:
|
||||
select.js-select-boards
|
||||
select.js-select-boards(autofocus)
|
||||
each boards
|
||||
if $eq _id currentBoard._id
|
||||
option(value="{{_id}}" selected) {{_ 'current'}}
|
||||
@@ -302,40 +299,48 @@ template(name="cardMorePopup")
|
||||
span {{_ 'link-card'}}
|
||||
= ' '
|
||||
i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
|
||||
input.inline-input(type="text" id="cardURL" readonly value="{{ absoluteUrl }}")
|
||||
input.inline-input(type="text" id="cardURL" readonly value="{{ absoluteUrl }}" autofocus="autofocus")
|
||||
button.js-copy-card-link-to-clipboard(class="btn") {{_ 'copy-card-link-to-clipboard'}}
|
||||
span.clearfix
|
||||
br
|
||||
h2 {{_ 'change-card-parent'}}
|
||||
label {{_ 'source-board'}}:
|
||||
select.js-field-parent-board
|
||||
if isTopLevel
|
||||
option(value="none" selected) {{_ 'custom-field-dropdown-none'}}
|
||||
else
|
||||
option(value="none") {{_ 'custom-field-dropdown-none'}}
|
||||
each boards
|
||||
if isParentBoard
|
||||
option(value="{{_id}}" selected) {{title}}
|
||||
else
|
||||
option(value="{{_id}}") {{title}}
|
||||
if isTopLevel
|
||||
option(value="none" selected) {{_ 'custom-field-dropdown-none'}}
|
||||
else
|
||||
option(value="none") {{_ 'custom-field-dropdown-none'}}
|
||||
|
||||
label {{_ 'parent-card'}}:
|
||||
select.js-field-parent-card
|
||||
if isTopLevel
|
||||
option(value="none" selected) {{_ 'custom-field-dropdown-none'}}
|
||||
else
|
||||
option(value="none") {{_ 'custom-field-dropdown-none'}}
|
||||
each cards
|
||||
if isParentCard
|
||||
option(value="{{_id}}" selected) {{title}}
|
||||
else
|
||||
option(value="{{_id}}") {{title}}
|
||||
option(value="none") {{_ 'custom-field-dropdown-none'}}
|
||||
br
|
||||
| {{_ 'added'}}
|
||||
span.date(title=card.createdAt) {{ moment createdAt 'LLL' }}
|
||||
a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}}
|
||||
|
||||
|
||||
template(name="setCardColorPopup")
|
||||
form.edit-label
|
||||
.palette-colors: each colors
|
||||
unless $eq color 'white'
|
||||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
|
||||
|
||||
template(name="cardDeletePopup")
|
||||
p {{_ "card-delete-pop"}}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
const subManager = new SubsManager();
|
||||
const { calculateIndexData, enableClickOnTouch } = Utils;
|
||||
|
||||
let cardColors;
|
||||
Meteor.startup(() => {
|
||||
cardColors = Cards.simpleSchema()._schema.color.allowedValues;
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
mixins() {
|
||||
return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar];
|
||||
@@ -73,14 +78,13 @@ BlazeComponent.extendComponent({
|
||||
//Scroll top
|
||||
const cardViewStartTop = $cardView.offset().top;
|
||||
const cardContainerScrollTop = $cardContainer.scrollTop();
|
||||
|
||||
let topOffset = false;
|
||||
if(cardViewStartTop < 0){
|
||||
topOffset = 0;
|
||||
} else if(cardViewStartTop - cardContainerScrollTop > 100) {
|
||||
topOffset = cardViewStartTop - cardContainerScrollTop - 100;
|
||||
if(cardViewStartTop !== 100){
|
||||
topOffset = cardViewStartTop - 100;
|
||||
}
|
||||
if(topOffset !== false) {
|
||||
bodyBoardComponent.scrollTop(topOffset);
|
||||
bodyBoardComponent.scrollTop(cardContainerScrollTop + topOffset);
|
||||
}
|
||||
|
||||
},
|
||||
@@ -112,6 +116,7 @@ BlazeComponent.extendComponent({
|
||||
onRendered() {
|
||||
if (!Utils.isMiniScreen()) {
|
||||
Meteor.setTimeout(() => {
|
||||
$('.card-details').mCustomScrollbar({theme:'minimal-dark', setWidth: false, setLeft: 0, scrollbarPosition: 'outside', mouseWheel: true });
|
||||
this.scrollParentContainer();
|
||||
}, 500);
|
||||
}
|
||||
@@ -336,6 +341,7 @@ Template.cardDetailsActionsPopup.events({
|
||||
'click .js-move-card': Popup.open('moveCard'),
|
||||
'click .js-copy-card': Popup.open('copyCard'),
|
||||
'click .js-copy-checklist-cards': Popup.open('copyChecklistToManyCards'),
|
||||
'click .js-set-card-color': Popup.open('setCardColor'),
|
||||
'click .js-move-card-to-top' (evt) {
|
||||
evt.preventDefault();
|
||||
const minOrder = _.min(this.list().cards(this.swimlaneId).map((c) => c.sort));
|
||||
@@ -406,11 +412,13 @@ Template.moveCardPopup.events({
|
||||
// XXX We should *not* get the currentCard from the global state, but
|
||||
// instead from a “component” state.
|
||||
const card = Cards.findOne(Session.get('currentCard'));
|
||||
const bSelect = $('.js-select-boards')[0];
|
||||
const boardId = bSelect.options[bSelect.selectedIndex].value;
|
||||
const lSelect = $('.js-select-lists')[0];
|
||||
const newListId = lSelect.options[lSelect.selectedIndex].value;
|
||||
const listId = lSelect.options[lSelect.selectedIndex].value;
|
||||
const slSelect = $('.js-select-swimlanes')[0];
|
||||
card.swimlaneId = slSelect.options[slSelect.selectedIndex].value;
|
||||
card.move(card.swimlaneId, newListId, 0);
|
||||
const swimlaneId = slSelect.options[slSelect.selectedIndex].value;
|
||||
card.move(boardId, swimlaneId, listId, 0);
|
||||
Popup.close();
|
||||
},
|
||||
});
|
||||
@@ -424,6 +432,7 @@ BlazeComponent.extendComponent({
|
||||
const boards = Boards.find({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {$ne: Meteor.user().getTemplatesBoardId()},
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
@@ -450,32 +459,15 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
}).register('boardsAndLists');
|
||||
|
||||
|
||||
function cloneCheckList(_id, checklist) {
|
||||
'use strict';
|
||||
const checklistId = checklist._id;
|
||||
checklist.cardId = _id;
|
||||
checklist._id = null;
|
||||
const newChecklistId = Checklists.insert(checklist);
|
||||
ChecklistItems.find({checklistId}).forEach(function(item) {
|
||||
item._id = null;
|
||||
item.checklistId = newChecklistId;
|
||||
item.cardId = _id;
|
||||
ChecklistItems.insert(item);
|
||||
});
|
||||
}
|
||||
|
||||
Template.copyCardPopup.events({
|
||||
'click .js-done'() {
|
||||
const card = Cards.findOne(Session.get('currentCard'));
|
||||
const oldId = card._id;
|
||||
card._id = null;
|
||||
const lSelect = $('.js-select-lists')[0];
|
||||
card.listId = lSelect.options[lSelect.selectedIndex].value;
|
||||
listId = lSelect.options[lSelect.selectedIndex].value;
|
||||
const slSelect = $('.js-select-swimlanes')[0];
|
||||
card.swimlaneId = slSelect.options[slSelect.selectedIndex].value;
|
||||
const swimlaneId = slSelect.options[slSelect.selectedIndex].value;
|
||||
const bSelect = $('.js-select-boards')[0];
|
||||
card.boardId = bSelect.options[bSelect.selectedIndex].value;
|
||||
const boardId = bSelect.options[bSelect.selectedIndex].value;
|
||||
const textarea = $('#copy-card-title');
|
||||
const title = textarea.val().trim();
|
||||
// insert new card to the bottom of new list
|
||||
@@ -484,38 +476,13 @@ Template.copyCardPopup.events({
|
||||
if (title) {
|
||||
card.title = title;
|
||||
card.coverId = '';
|
||||
const _id = Cards.insert(card);
|
||||
const _id = card.copy(boardId, swimlaneId, listId);
|
||||
// In case the filter is active we need to add the newly inserted card in
|
||||
// the list of exceptions -- cards that are not filtered. Otherwise the
|
||||
// card will disappear instantly.
|
||||
// See https://github.com/wekan/wekan/issues/80
|
||||
Filter.addException(_id);
|
||||
|
||||
// copy checklists
|
||||
let cursor = Checklists.find({cardId: oldId});
|
||||
cursor.forEach(function() {
|
||||
cloneCheckList(_id, arguments[0]);
|
||||
});
|
||||
|
||||
// copy subtasks
|
||||
cursor = Cards.find({parentId: oldId});
|
||||
cursor.forEach(function() {
|
||||
'use strict';
|
||||
const subtask = arguments[0];
|
||||
subtask.parentId = _id;
|
||||
subtask._id = null;
|
||||
/* const newSubtaskId = */ Cards.insert(subtask);
|
||||
});
|
||||
|
||||
// copy card comments
|
||||
cursor = CardComments.find({cardId: oldId});
|
||||
cursor.forEach(function () {
|
||||
'use strict';
|
||||
const comment = arguments[0];
|
||||
comment.cardId = _id;
|
||||
comment._id = null;
|
||||
CardComments.insert(comment);
|
||||
});
|
||||
Popup.close();
|
||||
}
|
||||
},
|
||||
@@ -552,9 +519,8 @@ Template.copyChecklistToManyCardsPopup.events({
|
||||
Filter.addException(_id);
|
||||
|
||||
// copy checklists
|
||||
let cursor = Checklists.find({cardId: oldId});
|
||||
cursor.forEach(function() {
|
||||
cloneCheckList(_id, arguments[0]);
|
||||
Checklists.find({cardId: oldId}).forEach((ch) => {
|
||||
ch.copy(_id);
|
||||
});
|
||||
|
||||
// copy subtasks
|
||||
@@ -568,13 +534,8 @@ Template.copyChecklistToManyCardsPopup.events({
|
||||
});
|
||||
|
||||
// copy card comments
|
||||
cursor = CardComments.find({cardId: oldId});
|
||||
cursor.forEach(function () {
|
||||
'use strict';
|
||||
const comment = arguments[0];
|
||||
comment.cardId = _id;
|
||||
comment._id = null;
|
||||
CardComments.insert(comment);
|
||||
CardComments.find({cardId: oldId}).forEach((cmt) => {
|
||||
cmt.copy(_id);
|
||||
});
|
||||
}
|
||||
Popup.close();
|
||||
@@ -585,11 +546,48 @@ Template.copyChecklistToManyCardsPopup.events({
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentCard = this.currentData();
|
||||
this.currentColor = new ReactiveVar(this.currentCard.color);
|
||||
},
|
||||
|
||||
colors() {
|
||||
return cardColors.map((color) => ({ color, name: '' }));
|
||||
},
|
||||
|
||||
isSelected(color) {
|
||||
if (this.currentColor.get() === null) {
|
||||
return color === 'white';
|
||||
}
|
||||
return this.currentColor.get() === color;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-palette-color'() {
|
||||
this.currentColor.set(this.currentData().color);
|
||||
},
|
||||
'click .js-submit' () {
|
||||
this.currentCard.setColor(this.currentColor.get());
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-remove-color'() {
|
||||
this.currentCard.setColor(null);
|
||||
Popup.close();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('setCardColorPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentCard = this.currentData();
|
||||
this.parentBoard = new ReactiveVar(null);
|
||||
this.parentCard = this.currentCard.parentCard();
|
||||
if (this.parentCard) {
|
||||
this.parentBoard = this.parentCard.board();
|
||||
const list = $('.js-field-parent-card');
|
||||
list.val(this.parentCard._id);
|
||||
this.parentBoard.set(this.parentCard.board()._id);
|
||||
} else {
|
||||
this.parentBoard = null;
|
||||
this.parentBoard.set(null);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -597,6 +595,9 @@ BlazeComponent.extendComponent({
|
||||
const boards = Boards.find({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {
|
||||
$ne: Meteor.user().getTemplatesBoardId(),
|
||||
},
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
@@ -604,8 +605,12 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
cards() {
|
||||
if (this.parentBoard) {
|
||||
return this.parentBoard.cards();
|
||||
const currentId = Session.get('currentCard');
|
||||
if (this.parentBoard.get()) {
|
||||
return Cards.find({
|
||||
boardId: this.parentBoard.get(),
|
||||
_id: {$ne: currentId},
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
@@ -613,8 +618,8 @@ BlazeComponent.extendComponent({
|
||||
|
||||
isParentBoard() {
|
||||
const board = this.currentData();
|
||||
if (this.parentBoard) {
|
||||
return board._id === this.parentBoard;
|
||||
if (this.parentBoard.get()) {
|
||||
return board._id === this.parentBoard.get();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
@@ -628,11 +633,10 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
setParentCardId(cardId) {
|
||||
if (cardId === 'null') {
|
||||
cardId = null;
|
||||
this.parentCard = null;
|
||||
} else {
|
||||
if (cardId) {
|
||||
this.parentCard = Cards.findOne(cardId);
|
||||
} else {
|
||||
this.parentCard = null;
|
||||
}
|
||||
this.currentCard.setParentId(cardId);
|
||||
},
|
||||
@@ -669,23 +673,14 @@ BlazeComponent.extendComponent({
|
||||
'change .js-field-parent-board'(evt) {
|
||||
const selection = $(evt.currentTarget).val();
|
||||
const list = $('.js-field-parent-card');
|
||||
list.empty();
|
||||
if (selection === 'none') {
|
||||
this.parentBoard = null;
|
||||
list.prop('disabled', true);
|
||||
this.parentBoard.set(null);
|
||||
} else {
|
||||
this.parentBoard = Boards.findOne(selection);
|
||||
this.parentBoard.cards().forEach(function(card) {
|
||||
list.append(
|
||||
$('<option></option>').val(card._id).html(card.title)
|
||||
);
|
||||
});
|
||||
subManager.subscribe('board', $(evt.currentTarget).val());
|
||||
this.parentBoard.set(selection);
|
||||
list.prop('disabled', false);
|
||||
}
|
||||
list.append(
|
||||
`<option value='none' selected='selected'>${TAPi18n.__('custom-field-dropdown-none')}</option>`
|
||||
);
|
||||
this.setParentCardId('null');
|
||||
this.setParentCardId(null);
|
||||
},
|
||||
'change .js-field-parent-card'(evt) {
|
||||
const selection = $(evt.currentTarget).val();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import 'nib'
|
||||
|
||||
.card-details
|
||||
padding: 0 20px
|
||||
padding: 0
|
||||
flex-shrink: 0
|
||||
flex-basis: 470px
|
||||
flex-basis: 510px
|
||||
will-change: flex-basis
|
||||
overflow-y: scroll
|
||||
overflow-x: hidden
|
||||
@@ -14,8 +14,16 @@
|
||||
box-shadow: 0 0 7px 0 darken(white, 30%)
|
||||
transition: flex-basis 0.1s
|
||||
|
||||
.mCustomScrollBox
|
||||
padding-left: 0
|
||||
|
||||
.ps-scrollbar-y-rail
|
||||
pointer-event: all
|
||||
position: absolute;
|
||||
|
||||
.card-details-canvas
|
||||
width: 470px
|
||||
padding-left: 20px;
|
||||
|
||||
.card-details-header
|
||||
margin: 0 -20px 5px
|
||||
@@ -125,10 +133,92 @@ input[type="submit"].attachment-add-link-submit
|
||||
|
||||
.card-details-canvas
|
||||
width: 100%
|
||||
|
||||
padding-left: 0px;
|
||||
|
||||
.card-details-header
|
||||
.close-card-details
|
||||
margin-right: 0px
|
||||
|
||||
.card-details-menu
|
||||
margin-right: 10px
|
||||
|
||||
card-details-color(background, color...)
|
||||
background: background !important
|
||||
if color
|
||||
color: color !important //overwrite text for better visibility
|
||||
|
||||
.card-details-white
|
||||
card-details-color(unset, #000) //Black text for better visibility
|
||||
border: 1px solid #eee
|
||||
|
||||
.card-details-green
|
||||
card-details-color(#3cb500, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-yellow
|
||||
card-details-color(#fad900, #000) //Black text for better visibility
|
||||
|
||||
.card-details-orange
|
||||
card-details-color(#ff9f19, #000) //Black text for better visibility
|
||||
|
||||
.card-details-red
|
||||
card-details-color(#eb4646, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-purple
|
||||
card-details-color(#a632db, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-blue
|
||||
card-details-color(#0079bf, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-pink
|
||||
card-details-color(#ff78cb, #000) //Black text for better visibility
|
||||
|
||||
.card-details-sky
|
||||
card-details-color(#00c2e0, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-black
|
||||
card-details-color(#4d4d4d, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-lime
|
||||
card-details-color(#51e898, #000) //Black text for better visibility
|
||||
|
||||
.card-details-silver
|
||||
card-details-color(#c0c0c0, #000) //Black text for better visibility
|
||||
|
||||
.card-details-peachpuff
|
||||
card-details-color(#ffdab9, #000) //Black text for better visibility
|
||||
|
||||
.card-details-crimson
|
||||
card-details-color(#dc143c, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-plum
|
||||
card-details-color(#dda0dd, #000) //Black text for better visibility
|
||||
|
||||
.card-details-darkgreen
|
||||
card-details-color(#006400, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-slateblue
|
||||
card-details-color(#6a5acd, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-magenta
|
||||
card-details-color(#ff00ff, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-gold
|
||||
card-details-color(#ffd700, #000) //Black text for better visibility
|
||||
|
||||
.card-details-navy
|
||||
card-details-color(#000080, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-gray
|
||||
card-details-color(#808080, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-saddlebrown
|
||||
card-details-color(#8b4513, #ffffff) //White text for better visibility
|
||||
|
||||
.card-details-paleturquoise
|
||||
card-details-color(#afeeee, #000) //Black text for better visibility
|
||||
|
||||
.card-details-mistyrose
|
||||
card-details-color(#ffe4e1, #000) //Black text for better visibility
|
||||
|
||||
.card-details-indigo
|
||||
card-details-color(#4b0082, #ffffff) //White text for better visibility
|
||||
@@ -51,7 +51,6 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
|
||||
padding-right: 3%
|
||||
z-index: 17
|
||||
border-radius: 3px
|
||||
top: 50%
|
||||
|
||||
p
|
||||
position: relative
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
template(name="minicard")
|
||||
.minicard(
|
||||
class="{{#if isLinkedCard}}linked-card{{/if}}"
|
||||
class="{{#if isLinkedBoard}}linked-board{{/if}}")
|
||||
class="{{#if isLinkedBoard}}linked-board{{/if}}"
|
||||
class="minicard-{{colorClass}}")
|
||||
if cover
|
||||
.minicard-cover(style="background-image: url('{{cover.url}}');")
|
||||
if labels
|
||||
@@ -9,6 +10,8 @@ template(name="minicard")
|
||||
each labels
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
.minicard-title
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
||||
.parent-prefix
|
||||
| {{ parentString ' > ' }}
|
||||
|
||||
@@ -94,6 +94,19 @@
|
||||
.minicard-custom-field-item
|
||||
max-width:50%;
|
||||
flex-grow:1;
|
||||
.handle
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
display:none;
|
||||
@media only screen and (max-width: 1199px) {
|
||||
display:block;
|
||||
}
|
||||
.fa-arrows
|
||||
font-size:20px;
|
||||
color: #ccc;
|
||||
.minicard-title
|
||||
p:last-child
|
||||
margin-bottom: 0
|
||||
@@ -189,3 +202,86 @@
|
||||
border-top-right-radius: 0
|
||||
z-index: 15
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.15)
|
||||
|
||||
minicard-color(background, color...)
|
||||
background-color: background
|
||||
if color
|
||||
color: color //overwrite text for better visibility
|
||||
&:hover:not(.minicard-composer),
|
||||
.is-selected &,
|
||||
.draggable-hover-card &
|
||||
background: darken(background, 3%)
|
||||
.draggable-hover-card &
|
||||
background: darken(background, 7%)
|
||||
|
||||
.minicard-green
|
||||
minicard-color(#3cb500, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-yellow
|
||||
minicard-color(#fad900)
|
||||
|
||||
.minicard-orange
|
||||
minicard-color(#ff9f19)
|
||||
|
||||
.minicard-red
|
||||
minicard-color(#eb4646, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-purple
|
||||
minicard-color(#a632db, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-blue
|
||||
minicard-color(#0079bf, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-pink
|
||||
minicard-color(#ff78cb)
|
||||
|
||||
.minicard-sky
|
||||
minicard-color(#00c2e0, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-black
|
||||
minicard-color(#4d4d4d, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-lime
|
||||
minicard-color(#51e898)
|
||||
|
||||
.minicard-silver
|
||||
minicard-color(#c0c0c0)
|
||||
|
||||
.minicard-peachpuff
|
||||
minicard-color(#ffdab9)
|
||||
|
||||
.minicard-crimson
|
||||
minicard-color(#dc143c, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-plum
|
||||
minicard-color(#dda0dd)
|
||||
|
||||
.minicard-darkgreen
|
||||
minicard-color(#006400, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-slateblue
|
||||
minicard-color(#6a5acd, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-magenta
|
||||
minicard-color(#ff00ff, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-gold
|
||||
minicard-color(#ffd700)
|
||||
|
||||
.minicard-navy
|
||||
minicard-color(#000080, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-gray
|
||||
minicard-color(#808080, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-saddlebrown
|
||||
minicard-color(#8b4513, #ffffff) //White text for better visibility
|
||||
|
||||
.minicard-paleturquoise
|
||||
minicard-color(#afeeee)
|
||||
|
||||
.minicard-mistyrose
|
||||
minicard-color(#ffe4e1)
|
||||
|
||||
.minicard-indigo
|
||||
minicard-color(#4b0082, #ffffff) //White text for better visibility
|
||||
@@ -26,6 +26,13 @@ BlazeComponent.extendComponent({
|
||||
|
||||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||
const $cards = this.$('.js-minicards');
|
||||
|
||||
if(window.matchMedia('(max-width: 1199px)').matches) {
|
||||
$( '.js-minicards' ).sortable({
|
||||
handle: '.handle',
|
||||
});
|
||||
}
|
||||
|
||||
$cards.sortable({
|
||||
connectWith: '.js-minicards:not(.js-list-full)',
|
||||
tolerance: 'pointer',
|
||||
@@ -60,7 +67,13 @@ BlazeComponent.extendComponent({
|
||||
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
|
||||
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
|
||||
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
|
||||
const swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
let swimlaneId = '';
|
||||
const boardView = Meteor.user().profile.boardView;
|
||||
if (boardView === 'board-view-swimlanes' || currentBoard.isTemplatesBoard())
|
||||
swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
|
||||
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal'))
|
||||
swimlaneId = currentBoard.getDefaultSwimline()._id;
|
||||
|
||||
// Normally the jquery-ui sortable library moves the dragged DOM element
|
||||
// to its new position, which disrupts Blaze reactive updates mechanism
|
||||
@@ -73,12 +86,12 @@ BlazeComponent.extendComponent({
|
||||
|
||||
if (MultiSelection.isActive()) {
|
||||
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => {
|
||||
card.move(swimlaneId, listId, sortIndex.base + i * sortIndex.increment);
|
||||
card.move(currentBoard._id, swimlaneId, listId, sortIndex.base + i * sortIndex.increment);
|
||||
});
|
||||
} else {
|
||||
const cardDomElement = ui.item.get(0);
|
||||
const card = Blaze.getData(cardDomElement);
|
||||
card.move(swimlaneId, listId, sortIndex.base);
|
||||
card.move(currentBoard._id, swimlaneId, listId, sortIndex.base);
|
||||
}
|
||||
boardComponent.setIsDragging(false);
|
||||
},
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
// transparent, because that won't work during a list drag.
|
||||
background: darken(white, 13%)
|
||||
border-left: 1px solid darken(white, 20%)
|
||||
border-bottom: 1px solid #CCC
|
||||
padding: 0
|
||||
float: left
|
||||
|
||||
@@ -44,12 +43,24 @@
|
||||
background: white
|
||||
margin: -3px 0 8px
|
||||
|
||||
.list-header
|
||||
.list-header-card-count
|
||||
height: 35px
|
||||
|
||||
.list-header-add
|
||||
flex: 0 0 auto
|
||||
margin: 20px 12px 4px
|
||||
padding: 20px 12px 4px
|
||||
position: relative
|
||||
min-height: 20px
|
||||
|
||||
.list-header
|
||||
flex: 0 0 auto
|
||||
padding: 20px 12px 4px
|
||||
position: relative
|
||||
min-height: 20px
|
||||
background-color: #e4e4e4;
|
||||
border-bottom: 6px solid #e4e4e4;
|
||||
|
||||
|
||||
&.ui-sortable-handle
|
||||
cursor: grab
|
||||
|
||||
@@ -68,16 +79,18 @@
|
||||
text-overflow: ellipsis
|
||||
word-wrap: break-word
|
||||
|
||||
|
||||
.list-header-watch-icon
|
||||
padding-left: 10px
|
||||
color: #a6a6a6
|
||||
|
||||
|
||||
.list-header-menu
|
||||
position: absolute
|
||||
padding: 7px
|
||||
padding: 27px 19px
|
||||
margin-top: 1px
|
||||
top: -@padding
|
||||
right: -@padding
|
||||
top: -7px
|
||||
right: -7px
|
||||
|
||||
.list-header-plus-icon
|
||||
color: #a6a6a6
|
||||
@@ -143,9 +156,12 @@
|
||||
float: left
|
||||
|
||||
@media screen and (max-width: 800px)
|
||||
.list-header-menu
|
||||
margin-right: 30px
|
||||
|
||||
.mini-list
|
||||
flex: 0 0 60px
|
||||
height: 60px
|
||||
height: auto
|
||||
width: 100%
|
||||
border-left: 0px
|
||||
border-bottom: 1px solid darken(white, 20%)
|
||||
@@ -154,6 +170,8 @@
|
||||
display: block
|
||||
width: 100%
|
||||
border-left: 0px
|
||||
&:first-child
|
||||
margin-left: 0px
|
||||
|
||||
&.ui-sortable-helper
|
||||
flex: 0 0 60px
|
||||
@@ -172,8 +190,16 @@
|
||||
border-left: 0px
|
||||
border-bottom: 1px solid darken(white, 20%)
|
||||
|
||||
.list-header
|
||||
.list-body
|
||||
padding: 15px 19px;
|
||||
|
||||
.list-header
|
||||
padding: 0 12px 0px
|
||||
border-bottom: 0px solid #e4e4e4
|
||||
height: 60px
|
||||
margin-top: 10px
|
||||
display: flex
|
||||
align-items: center
|
||||
.list-header-left-icon
|
||||
display: inline
|
||||
padding: 7px
|
||||
@@ -185,8 +211,10 @@
|
||||
.list-header-menu-icon
|
||||
position: absolute
|
||||
padding: 7px
|
||||
top: -@padding
|
||||
top: 50%
|
||||
transform: translateY(-50%)
|
||||
right: 17px
|
||||
font-size: 20px
|
||||
|
||||
.link-board-wrapper
|
||||
display: flex
|
||||
@@ -198,3 +226,85 @@
|
||||
.search-card-results
|
||||
max-height: 250px
|
||||
overflow: hidden
|
||||
|
||||
.sk-spinner-list
|
||||
margin-top: unset !important
|
||||
|
||||
list-header-color(background, color...)
|
||||
border-bottom: 6px solid background
|
||||
|
||||
.list-header-white
|
||||
list-header-color(#ffffff, #4d4d4d) //Black text for better visibility
|
||||
border: 1px solid #eee
|
||||
|
||||
.list-header-green
|
||||
list-header-color(#3cb500, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-yellow
|
||||
list-header-color(#fad900, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-orange
|
||||
list-header-color(#ff9f19, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-red
|
||||
list-header-color(#eb4646, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-purple
|
||||
list-header-color(#a632db, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-blue
|
||||
list-header-color(#0079bf, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-pink
|
||||
list-header-color(#ff78cb, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-sky
|
||||
list-header-color(#00c2e0, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-black
|
||||
list-header-color(#4d4d4d, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-lime
|
||||
list-header-color(#51e898, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-silver
|
||||
list-header-color(unset, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-peachpuff
|
||||
list-header-color(#ffdab9, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-crimson
|
||||
list-header-color(#dc143c, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-plum
|
||||
list-header-color(#dda0dd, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-darkgreen
|
||||
list-header-color(#006400, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-slateblue
|
||||
list-header-color(#6a5acd, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-magenta
|
||||
list-header-color(#ff00ff, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-gold
|
||||
list-header-color(#ffd700, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-navy
|
||||
list-header-color(#000080, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-gray
|
||||
list-header-color(#808080, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-saddlebrown
|
||||
list-header-color(#8b4513, #ffffff) //White text for better visibility
|
||||
|
||||
.list-header-paleturquoise
|
||||
list-header-color(#afeeee, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-mistyrose
|
||||
list-header-color(#ffe4e1, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.list-header-indigo
|
||||
list-header-color(#4b0082, #ffffff) //White text for better visibility
|
||||
@@ -4,7 +4,7 @@ template(name="listBody")
|
||||
if cards.count
|
||||
+inlinedForm(autoclose=false position="top")
|
||||
+addCardForm(listId=_id position="top")
|
||||
each (cards (idOrNull ../../_id))
|
||||
each (cardsWithLimit (idOrNull ../../_id))
|
||||
a.minicard-wrapper.js-minicard(href=absoluteUrl
|
||||
class="{{#if cardIsSelected}}is-selected{{/if}}"
|
||||
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
@@ -12,6 +12,9 @@ template(name="listBody")
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection(
|
||||
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
+minicard(this)
|
||||
if (showSpinner (idOrNull ../../_id))
|
||||
+spinnerList
|
||||
|
||||
if canSeeAddCard
|
||||
+inlinedForm(autoclose=false position="bottom")
|
||||
+addCardForm(listId=_id position="bottom")
|
||||
@@ -20,6 +23,16 @@ template(name="listBody")
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-card'}}
|
||||
|
||||
template(name="spinnerList")
|
||||
.sk-spinner.sk-spinner-wave.sk-spinner-list(
|
||||
class=currentBoard.colorClass
|
||||
id="showMoreResults")
|
||||
.sk-rect1
|
||||
.sk-rect2
|
||||
.sk-rect3
|
||||
.sk-rect4
|
||||
.sk-rect5
|
||||
|
||||
template(name="addCardForm")
|
||||
.minicard.minicard-composer.js-composer
|
||||
if getLabels
|
||||
@@ -34,14 +47,19 @@ template(name="addCardForm")
|
||||
|
||||
.add-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'add'}}
|
||||
unless isSandstorm
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-link {{_ 'link'}}
|
||||
span.quiet
|
||||
|
|
||||
| /
|
||||
a.js-search {{_ 'search'}}
|
||||
unless currentBoard.isTemplatesBoard
|
||||
unless currentBoard.isTemplateBoard
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-link {{_ 'link'}}
|
||||
span.quiet
|
||||
|
|
||||
| /
|
||||
a.js-search {{_ 'search'}}
|
||||
span.quiet
|
||||
|
|
||||
| /
|
||||
a.js-card-template {{_ 'template'}}
|
||||
|
||||
template(name="autocompleteLabelLine")
|
||||
.minicard-label(class="card-label-{{colorName}}" title=labelName)
|
||||
@@ -51,11 +69,9 @@ template(name="linkCardPopup")
|
||||
label {{_ 'boards'}}:
|
||||
.link-board-wrapper
|
||||
select.js-select-boards
|
||||
option(value="")
|
||||
each boards
|
||||
if $eq _id currentBoard._id
|
||||
option(value="{{_id}}" selected) {{_ 'current'}}
|
||||
else
|
||||
option(value="{{_id}}") {{title}}
|
||||
option(value="{{_id}}") {{title}}
|
||||
input.primary.confirm.js-link-board(type="button" value="{{_ 'link'}}")
|
||||
|
||||
label {{_ 'swimlanes'}}:
|
||||
@@ -74,22 +90,41 @@ template(name="linkCardPopup")
|
||||
option(value="{{getId}}") {{getTitle}}
|
||||
|
||||
.edit-controls.clearfix
|
||||
unless isSandstorm
|
||||
input.primary.confirm.js-done(type="button" value="{{_ 'link'}}")
|
||||
input.primary.confirm.js-done(type="button" value="{{_ 'link'}}")
|
||||
|
||||
template(name="searchCardPopup")
|
||||
label {{_ 'boards'}}:
|
||||
.link-board-wrapper
|
||||
select.js-select-boards
|
||||
each boards
|
||||
if $eq _id currentBoard._id
|
||||
option(value="{{_id}}" selected) {{_ 'current'}}
|
||||
else
|
||||
template(name="searchElementPopup")
|
||||
form
|
||||
label
|
||||
| {{_ 'title'}}
|
||||
input.js-element-title(type="text" placeholder="{{_ 'title'}}" autofocus required)
|
||||
unless isTemplateSearch
|
||||
label {{_ 'boards'}}:
|
||||
.link-board-wrapper
|
||||
select.js-select-boards
|
||||
option(value="")
|
||||
each boards
|
||||
option(value="{{_id}}") {{title}}
|
||||
form.js-search-term-form
|
||||
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus)
|
||||
.list-body.js-perfect-scrollbar.search-card-results
|
||||
.minicards.clearfix.js-minicards
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
if isBoardTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+miniboard(this)
|
||||
if isListTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minilist(this)
|
||||
if isSwimlaneTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+miniswimlane(this)
|
||||
if isCardTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
unless isTemplateSearch
|
||||
each results
|
||||
a.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
@@ -1,6 +1,12 @@
|
||||
const subManager = new SubsManager();
|
||||
const InfiniteScrollIter = 10;
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
// for infinite scrolling
|
||||
this.cardlimit = new ReactiveVar(InfiniteScrollIter);
|
||||
},
|
||||
|
||||
mixins() {
|
||||
return [Mixins.PerfectScrollbar];
|
||||
},
|
||||
@@ -39,27 +45,56 @@ BlazeComponent.extendComponent({
|
||||
const labelIds = formComponent.labels.get();
|
||||
const customFields = formComponent.customFields.get();
|
||||
|
||||
const boardId = this.data().board();
|
||||
const board = this.data().board();
|
||||
let linkedId = '';
|
||||
let swimlaneId = '';
|
||||
const boardView = Meteor.user().profile.boardView;
|
||||
if (boardView === 'board-view-swimlanes')
|
||||
swimlaneId = this.parentComponent().parentComponent().data()._id;
|
||||
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal'))
|
||||
swimlaneId = boardId.getDefaultSwimline()._id;
|
||||
|
||||
let cardType = 'cardType-card';
|
||||
if (title) {
|
||||
if (board.isTemplatesBoard()) {
|
||||
swimlaneId = this.parentComponent().parentComponent().data()._id; // Always swimlanes view
|
||||
const swimlane = Swimlanes.findOne(swimlaneId);
|
||||
// If this is the card templates swimlane, insert a card template
|
||||
if (swimlane.isCardTemplatesSwimlane())
|
||||
cardType = 'template-card';
|
||||
// If this is the board templates swimlane, insert a board template and a linked card
|
||||
else if (swimlane.isBoardTemplatesSwimlane()) {
|
||||
linkedId = Boards.insert({
|
||||
title,
|
||||
permission: 'private',
|
||||
type: 'template-board',
|
||||
});
|
||||
Swimlanes.insert({
|
||||
title: TAPi18n.__('default'),
|
||||
boardId: linkedId,
|
||||
});
|
||||
cardType = 'cardType-linkedBoard';
|
||||
}
|
||||
} else if (boardView === 'board-view-swimlanes')
|
||||
swimlaneId = this.parentComponent().parentComponent().data()._id;
|
||||
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal'))
|
||||
swimlaneId = board.getDefaultSwimline()._id;
|
||||
|
||||
const _id = Cards.insert({
|
||||
title,
|
||||
members,
|
||||
labelIds,
|
||||
customFields,
|
||||
listId: this.data()._id,
|
||||
boardId: boardId._id,
|
||||
boardId: board._id,
|
||||
sort: sortIndex,
|
||||
swimlaneId,
|
||||
type: 'cardType-card',
|
||||
type: cardType,
|
||||
linkedId,
|
||||
});
|
||||
|
||||
// if the displayed card count is less than the total cards in the list,
|
||||
// we need to increment the displayed card count to prevent the spinner
|
||||
// to appear
|
||||
const cardCount = this.data().cards(this.idOrNull(swimlaneId)).count();
|
||||
if (this.cardlimit.get() < cardCount) {
|
||||
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
|
||||
}
|
||||
|
||||
// In case the filter is active we need to add the newly inserted card in
|
||||
// the list of exceptions -- cards that are not filtered. Otherwise the
|
||||
@@ -92,9 +127,9 @@ BlazeComponent.extendComponent({
|
||||
const methodName = evt.shiftKey ? 'toggleRange' : 'toggle';
|
||||
MultiSelection[methodName](this.currentData()._id);
|
||||
|
||||
// If the card is already selected, we want to de-select it.
|
||||
// XXX We should probably modify the minicard href attribute instead of
|
||||
// overwriting the event in case the card is already selected.
|
||||
// If the card is already selected, we want to de-select it.
|
||||
// XXX We should probably modify the minicard href attribute instead of
|
||||
// overwriting the event in case the card is already selected.
|
||||
} else if (Session.equals('currentCard', this.currentData()._id)) {
|
||||
evt.stopImmediatePropagation();
|
||||
evt.preventDefault();
|
||||
@@ -114,11 +149,31 @@ BlazeComponent.extendComponent({
|
||||
|
||||
idOrNull(swimlaneId) {
|
||||
const currentUser = Meteor.user();
|
||||
if (currentUser.profile.boardView === 'board-view-swimlanes')
|
||||
if (currentUser.profile.boardView === 'board-view-swimlanes' ||
|
||||
this.data().board().isTemplatesBoard())
|
||||
return swimlaneId;
|
||||
return undefined;
|
||||
},
|
||||
|
||||
cardsWithLimit(swimlaneId) {
|
||||
const limit = this.cardlimit.get();
|
||||
const selector = {
|
||||
listId: this.currentData()._id,
|
||||
archived: false,
|
||||
};
|
||||
if (swimlaneId)
|
||||
selector.swimlaneId = swimlaneId;
|
||||
return Cards.find(Filter.mongoSelector(selector), {
|
||||
sort: ['sort'],
|
||||
limit,
|
||||
});
|
||||
},
|
||||
|
||||
showSpinner(swimlaneId) {
|
||||
const list = Template.currentData();
|
||||
return list.cards(swimlaneId).count() > this.cardlimit.get();
|
||||
},
|
||||
|
||||
canSeeAddCard() {
|
||||
return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
|
||||
},
|
||||
@@ -179,7 +234,7 @@ BlazeComponent.extendComponent({
|
||||
|
||||
pressKey(evt) {
|
||||
// Pressing Enter should submit the card
|
||||
if (evt.keyCode === 13) {
|
||||
if (evt.keyCode === 13 && !evt.shiftKey) {
|
||||
evt.preventDefault();
|
||||
const $form = $(evt.currentTarget).closest('form');
|
||||
// XXX For some reason $form.submit() does not work (it's probably a bug
|
||||
@@ -188,8 +243,8 @@ BlazeComponent.extendComponent({
|
||||
// work.
|
||||
$form.find('button[type=submit]').click();
|
||||
|
||||
// Pressing Tab should open the form of the next column, and Maj+Tab go
|
||||
// in the reverse order
|
||||
// Pressing Tab should open the form of the next column, and Maj+Tab go
|
||||
// in the reverse order
|
||||
} else if (evt.keyCode === 9) {
|
||||
evt.preventDefault();
|
||||
const isReverse = evt.shiftKey;
|
||||
@@ -211,7 +266,8 @@ BlazeComponent.extendComponent({
|
||||
return [{
|
||||
keydown: this.pressKey,
|
||||
'click .js-link': Popup.open('linkCard'),
|
||||
'click .js-search': Popup.open('searchCard'),
|
||||
'click .js-search': Popup.open('searchElement'),
|
||||
'click .js-card-template': Popup.open('searchElement'),
|
||||
}];
|
||||
},
|
||||
|
||||
@@ -249,7 +305,7 @@ BlazeComponent.extendComponent({
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
callback($.map(currentBoard.labels, (label) => {
|
||||
if (label.name.indexOf(term) > -1 ||
|
||||
label.color.indexOf(term) > -1) {
|
||||
label.color.indexOf(term) > -1) {
|
||||
return label;
|
||||
}
|
||||
return null;
|
||||
@@ -286,17 +342,7 @@ BlazeComponent.extendComponent({
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
// Prefetch first non-current board id
|
||||
const boardId = Boards.findOne({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {$ne: Session.get('currentBoard')},
|
||||
}, {
|
||||
sort: ['title'],
|
||||
})._id;
|
||||
// Subscribe to this board
|
||||
subManager.subscribe('board', boardId);
|
||||
this.selectedBoardId = new ReactiveVar(boardId);
|
||||
this.selectedBoardId = new ReactiveVar('');
|
||||
this.selectedSwimlaneId = new ReactiveVar('');
|
||||
this.selectedListId = new ReactiveVar('');
|
||||
|
||||
@@ -322,6 +368,7 @@ BlazeComponent.extendComponent({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {$ne: Session.get('currentBoard')},
|
||||
type: 'board',
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
@@ -329,7 +376,7 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
swimlanes() {
|
||||
if (!this.selectedBoardId) {
|
||||
if (!this.selectedBoardId.get()) {
|
||||
return [];
|
||||
}
|
||||
const swimlanes = Swimlanes.find({boardId: this.selectedBoardId.get()});
|
||||
@@ -339,7 +386,7 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
lists() {
|
||||
if (!this.selectedBoardId) {
|
||||
if (!this.selectedBoardId.get()) {
|
||||
return [];
|
||||
}
|
||||
const lists = Lists.find({boardId: this.selectedBoardId.get()});
|
||||
@@ -360,6 +407,7 @@ BlazeComponent.extendComponent({
|
||||
archived: false,
|
||||
linkedId: {$nin: ownCardsIds},
|
||||
_id: {$nin: ownCardsIds},
|
||||
type: {$nin: ['template-card']},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -427,12 +475,25 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
onCreated() {
|
||||
// Prefetch first non-current board id
|
||||
let board = Boards.findOne({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {$ne: Session.get('currentBoard')},
|
||||
});
|
||||
this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-card-template');
|
||||
this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-list-template');
|
||||
this.isSwimlaneTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-open-add-swimlane-menu');
|
||||
this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-add-board');
|
||||
this.isTemplateSearch = this.isCardTemplateSearch ||
|
||||
this.isListTemplateSearch ||
|
||||
this.isSwimlaneTemplateSearch ||
|
||||
this.isBoardTemplateSearch;
|
||||
let board = {};
|
||||
if (this.isTemplateSearch) {
|
||||
board = Boards.findOne(Meteor.user().profile.templatesBoardId);
|
||||
} else {
|
||||
// Prefetch first non-current board id
|
||||
board = Boards.findOne({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {$nin: [Session.get('currentBoard'), Meteor.user().profile.templatesBoardId]},
|
||||
});
|
||||
}
|
||||
if (!board) {
|
||||
Popup.close();
|
||||
return;
|
||||
@@ -442,20 +503,21 @@ BlazeComponent.extendComponent({
|
||||
subManager.subscribe('board', boardId);
|
||||
this.selectedBoardId = new ReactiveVar(boardId);
|
||||
|
||||
this.boardId = Session.get('currentBoard');
|
||||
// In order to get current board info
|
||||
subManager.subscribe('board', this.boardId);
|
||||
board = Boards.findOne(this.boardId);
|
||||
// List where to insert card
|
||||
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
this.listId = Blaze.getData(list[0])._id;
|
||||
// Swimlane where to insert card
|
||||
const swimlane = $(Popup._getTopStack().openerElement).closest('.js-swimlane');
|
||||
this.swimlaneId = '';
|
||||
if (board.view === 'board-view-swimlanes')
|
||||
this.swimlaneId = Blaze.getData(swimlane[0])._id;
|
||||
else
|
||||
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
|
||||
if (!this.isBoardTemplateSearch) {
|
||||
this.boardId = Session.get('currentBoard');
|
||||
// In order to get current board info
|
||||
subManager.subscribe('board', this.boardId);
|
||||
this.swimlaneId = '';
|
||||
// Swimlane where to insert card
|
||||
const swimlane = $(Popup._getTopStack().openerElement).parents('.js-swimlane');
|
||||
if (Meteor.user().profile.boardView === 'board-view-swimlanes')
|
||||
this.swimlaneId = Blaze.getData(swimlane[0])._id;
|
||||
else
|
||||
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
|
||||
// List where to insert card
|
||||
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
this.listId = Blaze.getData(list[0])._id;
|
||||
}
|
||||
this.term = new ReactiveVar('');
|
||||
},
|
||||
|
||||
@@ -464,6 +526,7 @@ BlazeComponent.extendComponent({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {$ne: Session.get('currentBoard')},
|
||||
type: 'board',
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
@@ -475,7 +538,21 @@ BlazeComponent.extendComponent({
|
||||
return [];
|
||||
}
|
||||
const board = Boards.findOne(this.selectedBoardId.get());
|
||||
return board.searchCards(this.term.get(), false);
|
||||
if (!this.isTemplateSearch || this.isCardTemplateSearch) {
|
||||
return board.searchCards(this.term.get(), false);
|
||||
} else if (this.isListTemplateSearch) {
|
||||
return board.searchLists(this.term.get());
|
||||
} else if (this.isSwimlaneTemplateSearch) {
|
||||
return board.searchSwimlanes(this.term.get());
|
||||
} else if (this.isBoardTemplateSearch) {
|
||||
const boards = board.searchBoards(this.term.get());
|
||||
boards.forEach((board) => {
|
||||
subManager.subscribe('board', board.linkedId);
|
||||
});
|
||||
return boards;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
events() {
|
||||
@@ -489,20 +566,99 @@ BlazeComponent.extendComponent({
|
||||
this.term.set(evt.target.searchTerm.value);
|
||||
},
|
||||
'click .js-minicard'(evt) {
|
||||
// LINK CARD
|
||||
const card = Blaze.getData(evt.currentTarget);
|
||||
const _id = Cards.insert({
|
||||
title: card.title, //dummy
|
||||
listId: this.listId,
|
||||
swimlaneId: this.swimlaneId,
|
||||
boardId: this.boardId,
|
||||
sort: Lists.findOne(this.listId).cards().count(),
|
||||
type: 'cardType-linkedCard',
|
||||
linkedId: card.linkedId || card._id,
|
||||
});
|
||||
Filter.addException(_id);
|
||||
// 0. Common
|
||||
const title = $('.js-element-title').val().trim();
|
||||
if (!title)
|
||||
return;
|
||||
const element = Blaze.getData(evt.currentTarget);
|
||||
element.title = title;
|
||||
let _id = '';
|
||||
if (!this.isTemplateSearch || this.isCardTemplateSearch) {
|
||||
// Card insertion
|
||||
// 1. Common
|
||||
element.sort = Lists.findOne(this.listId).cards().count();
|
||||
// 1.A From template
|
||||
if (this.isTemplateSearch) {
|
||||
element.type = 'cardType-card';
|
||||
element.linkedId = '';
|
||||
_id = element.copy(this.boardId, this.swimlaneId, this.listId);
|
||||
// 1.B Linked card
|
||||
} else {
|
||||
delete element._id;
|
||||
element.type = 'cardType-linkedCard';
|
||||
element.linkedId = element.linkedId || element._id;
|
||||
_id = Cards.insert(element);
|
||||
}
|
||||
Filter.addException(_id);
|
||||
// List insertion
|
||||
} else if (this.isListTemplateSearch) {
|
||||
element.sort = Swimlanes.findOne(this.swimlaneId).lists().count();
|
||||
element.type = 'list';
|
||||
_id = element.copy(this.boardId, this.swimlaneId);
|
||||
} else if (this.isSwimlaneTemplateSearch) {
|
||||
element.sort = Boards.findOne(this.boardId).swimlanes().count();
|
||||
element.type = 'swimlalne';
|
||||
_id = element.copy(this.boardId);
|
||||
} else if (this.isBoardTemplateSearch) {
|
||||
board = Boards.findOne(element.linkedId);
|
||||
board.sort = Boards.find({archived: false}).count();
|
||||
board.type = 'board';
|
||||
board.title = element.title;
|
||||
delete board.slug;
|
||||
_id = board.copy();
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('searchCardPopup');
|
||||
}).register('searchElementPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.cardlimit = this.parentComponent().cardlimit;
|
||||
|
||||
this.listId = this.parentComponent().data()._id;
|
||||
this.swimlaneId = '';
|
||||
|
||||
const boardView = Meteor.user().profile.boardView;
|
||||
if (boardView === 'board-view-swimlanes')
|
||||
this.swimlaneId = this.parentComponent().parentComponent().parentComponent().data()._id;
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
this.spinner = this.find('.sk-spinner-list');
|
||||
this.container = this.$(this.spinner).parents('.js-perfect-scrollbar')[0];
|
||||
|
||||
$(this.container).on(`scroll.spinner_${this.swimlaneId}_${this.listId}`, () => this.updateList());
|
||||
$(window).on(`resize.spinner_${this.swimlaneId}_${this.listId}`, () => this.updateList());
|
||||
|
||||
this.updateList();
|
||||
},
|
||||
|
||||
onDestroyed() {
|
||||
$(this.container).off(`scroll.spinner_${this.swimlaneId}_${this.listId}`);
|
||||
$(window).off(`resize.spinner_${this.swimlaneId}_${this.listId}`);
|
||||
},
|
||||
|
||||
updateList() {
|
||||
if (this.spinnerInView()) {
|
||||
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
|
||||
window.requestIdleCallback(() => this.updateList());
|
||||
}
|
||||
},
|
||||
|
||||
spinnerInView() {
|
||||
const parentViewHeight = this.container.clientHeight;
|
||||
const bottomViewPosition = this.container.scrollTop + parentViewHeight;
|
||||
|
||||
const threshold = this.spinner.offsetTop;
|
||||
|
||||
// spinner deleted
|
||||
if (!this.spinner.offsetTop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bottomViewPosition > threshold;
|
||||
},
|
||||
|
||||
}).register('spinnerList');
|
||||
@@ -1,5 +1,7 @@
|
||||
template(name="listHeader")
|
||||
.list-header.js-list-header
|
||||
.list-header.js-list-header(
|
||||
class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}"
|
||||
class="{{#if colorClass}}list-header-{{colorClass}}{{/if}}")
|
||||
+inlinedForm
|
||||
+editListTitleForm
|
||||
else
|
||||
@@ -49,6 +51,9 @@ template(name="listActionPopup")
|
||||
li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
|
||||
unless currentUser.isCommentOnly
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-set-color-list {{_ 'set-color-list'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
if cards.count
|
||||
li: a.js-select-cards {{_ 'list-select-cards'}}
|
||||
@@ -111,3 +116,13 @@ template(name="wipLimitErrorPopup")
|
||||
p {{_ 'wipLimitErrorPopup-dialog-pt1'}}
|
||||
p {{_ 'wipLimitErrorPopup-dialog-pt2'}}
|
||||
button.full.js-back-view(type="submit") {{_ 'cancel'}}
|
||||
|
||||
template(name="setListColorPopup")
|
||||
form.edit-label
|
||||
.palette-colors: each colors
|
||||
// note: we use the swimlane palette to have more than just the border
|
||||
span.card-label.palette-color.js-palette-color(class="swimlane-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
|
||||
@@ -1,3 +1,8 @@
|
||||
let listsColors;
|
||||
Meteor.startup(() => {
|
||||
listsColors = Lists.simpleSchema()._schema.color.allowedValues;
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
canSeeAddCard() {
|
||||
const list = Template.currentData();
|
||||
@@ -72,6 +77,7 @@ Template.listActionPopup.helpers({
|
||||
|
||||
Template.listActionPopup.events({
|
||||
'click .js-list-subscribe' () {},
|
||||
'click .js-set-color-list': Popup.open('setListColor'),
|
||||
'click .js-select-cards' () {
|
||||
const cardIds = this.allCards().map((card) => card._id);
|
||||
MultiSelection.add(cardIds);
|
||||
@@ -154,3 +160,34 @@ Template.listMorePopup.events({
|
||||
Utils.goBoardId(this.boardId);
|
||||
}),
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentList = this.currentData();
|
||||
this.currentColor = new ReactiveVar(this.currentList.color);
|
||||
},
|
||||
|
||||
colors() {
|
||||
return listsColors.map((color) => ({ color, name: '' }));
|
||||
},
|
||||
|
||||
isSelected(color) {
|
||||
return this.currentColor.get() === color;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-palette-color'() {
|
||||
this.currentColor.set(this.currentData().color);
|
||||
},
|
||||
'click .js-submit' () {
|
||||
this.currentList.setColor(this.currentColor.get());
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-remove-color'() {
|
||||
this.currentList.setColor(null);
|
||||
Popup.close();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('setListColorPopup');
|
||||
@@ -0,0 +1,8 @@
|
||||
template(name="minilist")
|
||||
.minicard(
|
||||
class="minicard-{{colorClass}}")
|
||||
.minicard-title
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
+viewer
|
||||
= title
|
||||
@@ -36,7 +36,10 @@ import sanitizeXss from 'xss';
|
||||
const at = HTML.CharRef({html: '@', str: '@'});
|
||||
Blaze.Template.registerHelper('mentions', new Template('mentions', function() {
|
||||
const view = this;
|
||||
let content = Blaze.toHTML(view.templateContentBlock);
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
if (!currentBoard)
|
||||
return HTML.Raw(sanitizeXss(content));
|
||||
const knowedUsers = currentBoard.members.map((member) => {
|
||||
const u = Users.findOne(member.userId);
|
||||
if(u){
|
||||
@@ -45,7 +48,6 @@ Blaze.Template.registerHelper('mentions', new Template('mentions', function() {
|
||||
return member;
|
||||
});
|
||||
const mentionRegex = /\B@([\w.]*)/gi;
|
||||
let content = Blaze.toHTML(view.templateContentBlock);
|
||||
|
||||
let currentMention;
|
||||
while ((currentMention = mentionRegex.exec(content)) !== null) {
|
||||
|
||||
@@ -4,39 +4,38 @@ template(name="header")
|
||||
list all starred boards with a link to go there. This is inspired by the
|
||||
Reddit "subreddit" bar.
|
||||
The first link goes to the boards page.
|
||||
unless isSandstorm
|
||||
if currentUser
|
||||
#header-quick-access(class=currentBoard.colorClass)
|
||||
if isMiniScreen
|
||||
ul
|
||||
li
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
if currentUser
|
||||
#header-quick-access(class=currentBoard.colorClass)
|
||||
if isMiniScreen
|
||||
ul
|
||||
li
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
|
||||
if currentList
|
||||
each currentBoard.lists
|
||||
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
|
||||
a.js-select-list
|
||||
= title
|
||||
#header-new-board-icon
|
||||
else
|
||||
ul
|
||||
li
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
each currentUser.starredBoards
|
||||
li.separator -
|
||||
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
if currentList
|
||||
each currentBoard.lists
|
||||
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
|
||||
a.js-select-list
|
||||
= title
|
||||
else
|
||||
li.current {{_ 'quick-access-description'}}
|
||||
#header-new-board-icon
|
||||
else
|
||||
ul
|
||||
li
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
each currentUser.starredBoards
|
||||
li.separator -
|
||||
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
= title
|
||||
else
|
||||
li.current {{_ 'quick-access-description'}}
|
||||
|
||||
a#header-new-board-icon.js-create-board
|
||||
i.fa.fa-plus(title="Create a new board")
|
||||
a#header-new-board-icon.js-create-board
|
||||
i.fa.fa-plus(title="Create a new board")
|
||||
|
||||
+headerUserBar
|
||||
+headerUserBar
|
||||
|
||||
#header(class=currentBoard.colorClass)
|
||||
//-
|
||||
@@ -46,19 +45,16 @@ template(name="header")
|
||||
#header-main-bar(class="{{#if wrappedHeader}}wrapper{{/if}}")
|
||||
+Template.dynamic(template=headerBar)
|
||||
|
||||
unless hideLogo
|
||||
//unless hideLogo
|
||||
|
||||
//-
|
||||
On sandstorm, the logo shouldn't be clickable, because we only have one
|
||||
page/document on it, and we don't want to see the home page containing
|
||||
the list of all boards.
|
||||
if isSandstorm
|
||||
.wekan-logo
|
||||
img(src="{{pathFor '/wekan-logo-header.png'}}" alt="Wekan")
|
||||
else
|
||||
unless currentSetting.hideLogo
|
||||
a.wekan-logo(href="{{pathFor 'home'}}" title="{{_ 'header-logo-title'}}")
|
||||
img(src="{{pathFor '/wekan-logo-header.png'}}" alt="Wekan")
|
||||
|
||||
// unless currentSetting.hideLogo
|
||||
// a.wekan-logo(href="{{pathFor 'home'}}" title="{{_ 'header-logo-title'}}")
|
||||
// img(src="{{pathFor '/logo-header.png'}}" alt="")
|
||||
|
||||
if appIsOffline
|
||||
+offlineWarning
|
||||
|
||||
@@ -188,8 +188,6 @@
|
||||
width: 100%
|
||||
padding: 10px 0px
|
||||
z-index: 30
|
||||
position: absolute
|
||||
bottom: 0px
|
||||
|
||||
ul
|
||||
width: calc(100% - 60px)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
head
|
||||
title Wekan
|
||||
meta(name="viewport"
|
||||
content="maximum-scale=1.0,width=device-width,initial-scale=1.0,user-scalable=0")
|
||||
title
|
||||
meta(name="viewport" content="maximum-scale=1.0,width=device-width,initial-scale=1.0,user-scalable=0")
|
||||
meta(http-equiv="X-UA-Compatible" content="IE=edge")
|
||||
//- XXX We should use pathFor in the following `href` to support the case
|
||||
where the application is deployed with a path prefix, but it seems to be
|
||||
@@ -9,23 +8,18 @@ head
|
||||
packages.
|
||||
link(rel="shortcut icon" href="/wekan-favicon.png")
|
||||
link(rel="apple-touch-icon" href="/wekan-favicon.png")
|
||||
link(rel="mask-icon" href="/wekan-150.svg")
|
||||
link(rel="mask-icon" href="/wekan-logo-150.svg")
|
||||
link(rel="manifest" href="/wekan-manifest.json")
|
||||
|
||||
template(name="userFormsLayout")
|
||||
section.auth-layout
|
||||
unless currentSetting.hideLogo
|
||||
h1.at-form-landing-logo
|
||||
img(src="{{pathFor '/wekan-logo.png'}}" alt="Wekan")
|
||||
if currentSetting.hideLogo
|
||||
h1
|
||||
br
|
||||
br
|
||||
h1
|
||||
br
|
||||
br
|
||||
section.auth-dialog
|
||||
+Template.dynamic(template=content)
|
||||
if isCas
|
||||
.at-form
|
||||
button#cas(class='at-btn submit' type='submit') {{casSignInLabel}}
|
||||
if currentSetting.displayAuthenticationMethod
|
||||
+connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
|
||||
div.at-form-lang
|
||||
select.select-lang.js-userform-set-language
|
||||
each languages
|
||||
@@ -37,7 +31,9 @@ template(name="userFormsLayout")
|
||||
template(name="defaultLayout")
|
||||
+header
|
||||
#content
|
||||
| {{{afterBodyStart}}}
|
||||
+Template.dynamic(template=content)
|
||||
| {{{beforeBodyEnd}}}
|
||||
if (Modal.isOpen)
|
||||
#modal
|
||||
.overlay
|
||||
|
||||
@@ -6,14 +6,35 @@ const i18nTagToT9n = (i18nTag) => {
|
||||
return i18nTag;
|
||||
};
|
||||
|
||||
const validator = {
|
||||
set(obj, prop, value) {
|
||||
if (prop === 'state' && value !== 'signIn') {
|
||||
$('.at-form-authentication').hide();
|
||||
} else if (prop === 'state' && value === 'signIn') {
|
||||
$('.at-form-authentication').show();
|
||||
}
|
||||
// The default behavior to store the value
|
||||
obj[prop] = value;
|
||||
// Indicate success
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
Template.userFormsLayout.onCreated(function() {
|
||||
Meteor.call('getDefaultAuthenticationMethod', (error, result) => {
|
||||
this.data.defaultAuthenticationMethod = new ReactiveVar(error ? undefined : result);
|
||||
const instance = this;
|
||||
instance.currentSetting = new ReactiveVar();
|
||||
|
||||
Meteor.subscribe('setting', {
|
||||
onReady() {
|
||||
instance.currentSetting.set(Settings.findOne());
|
||||
return this.stop();
|
||||
},
|
||||
});
|
||||
Meteor.subscribe('setting');
|
||||
});
|
||||
|
||||
Template.userFormsLayout.onRendered(() => {
|
||||
AccountsTemplates.state.form.keys = new Proxy(AccountsTemplates.state.form.keys, validator);
|
||||
|
||||
const i18nTag = navigator.language;
|
||||
if (i18nTag) {
|
||||
T9n.setLanguage(i18nTagToT9n(i18nTag));
|
||||
@@ -23,7 +44,15 @@ Template.userFormsLayout.onRendered(() => {
|
||||
|
||||
Template.userFormsLayout.helpers({
|
||||
currentSetting() {
|
||||
return Settings.findOne();
|
||||
return Template.instance().currentSetting.get();
|
||||
},
|
||||
|
||||
afterBodyStart() {
|
||||
return currentSetting.customHTMLafterBodyStart;
|
||||
},
|
||||
|
||||
beforeBodyEnd() {
|
||||
return currentSetting.customHTMLbeforeBodyEnd;
|
||||
},
|
||||
|
||||
languages() {
|
||||
@@ -50,17 +79,6 @@ Template.userFormsLayout.helpers({
|
||||
const curLang = T9n.getLanguage() || 'en';
|
||||
return t9nTag === curLang;
|
||||
},
|
||||
/*
|
||||
isCas() {
|
||||
return Meteor.settings.public &&
|
||||
Meteor.settings.public.cas &&
|
||||
Meteor.settings.public.cas.loginUrl;
|
||||
},
|
||||
|
||||
casSignInLabel() {
|
||||
return TAPi18n.__('casSignIn', {}, T9n.getLanguage() || 'en');
|
||||
},
|
||||
*/
|
||||
});
|
||||
|
||||
Template.userFormsLayout.events({
|
||||
@@ -69,33 +87,10 @@ Template.userFormsLayout.events({
|
||||
T9n.setLanguage(i18nTagToT9n(i18nTag));
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click button#cas'() {
|
||||
Meteor.loginWithCas(function() {
|
||||
if (FlowRouter.getRouteName() === 'atSignIn') {
|
||||
FlowRouter.go('/');
|
||||
}
|
||||
});
|
||||
},
|
||||
'click #at-btn'(event, instance) {
|
||||
/* All authentication method can be managed/called here.
|
||||
!! DON'T FORGET to correctly fill the fields of the user during its creation if necessary authenticationMethod : String !!
|
||||
*/
|
||||
const email = $('#at-field-username_and_email').val();
|
||||
const password = $('#at-field-password').val();
|
||||
|
||||
if (FlowRouter.getRouteName() !== 'atSignIn' || password === '' || email === '') {
|
||||
return;
|
||||
if (FlowRouter.getRouteName() === 'atSignIn') {
|
||||
authentication(event, instance);
|
||||
}
|
||||
|
||||
// Stop submit #at-pwd-form
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
Meteor.subscribe('user-authenticationMethod', email, {
|
||||
onReady() {
|
||||
return authentication.call(this, instance, email, password);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -105,37 +100,61 @@ Template.defaultLayout.events({
|
||||
},
|
||||
});
|
||||
|
||||
function authentication(instance, email, password) {
|
||||
let user = Users.findOne();
|
||||
// Authentication with password
|
||||
if (user && user.authenticationMethod === 'password') {
|
||||
$('#at-pwd-form').submit();
|
||||
// Meteor.call('logoutWithTimer', user._id, () => {});
|
||||
return this.stop();
|
||||
}
|
||||
async function authentication(event, instance) {
|
||||
const match = $('#at-field-username_and_email').val();
|
||||
const password = $('#at-field-password').val();
|
||||
|
||||
// If user doesn't exist, uses the default authentication method if it defined
|
||||
if (user === undefined) {
|
||||
user = {
|
||||
'authenticationMethod': instance.data.defaultAuthenticationMethod.get(),
|
||||
};
|
||||
}
|
||||
if (!match || !password) return;
|
||||
|
||||
// Authentication with LDAP
|
||||
if (user.authenticationMethod === 'ldap') {
|
||||
// Use the ldap connection package
|
||||
Meteor.loginWithLDAP(email, password, function(error) {
|
||||
if (!error) {
|
||||
// Meteor.call('logoutWithTimer', Users.findOne()._id, () => {});
|
||||
return FlowRouter.go('/');
|
||||
}
|
||||
return error;
|
||||
const result = await getAuthenticationMethod(instance.currentSetting.get(), match);
|
||||
|
||||
if (result === 'password') return;
|
||||
|
||||
// Stop submit #at-pwd-form
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
switch (result) {
|
||||
case 'ldap':
|
||||
Meteor.loginWithLDAP(match, password, function() {
|
||||
FlowRouter.go('/');
|
||||
});
|
||||
break;
|
||||
|
||||
case 'cas':
|
||||
Meteor.loginWithCas(function() {
|
||||
FlowRouter.go('/');
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* else {
|
||||
process.env.DEFAULT_AUTHENTICATION_METHOD is not defined
|
||||
} */
|
||||
|
||||
return this.stop();
|
||||
}
|
||||
|
||||
function getAuthenticationMethod({displayAuthenticationMethod, defaultAuthenticationMethod}, match) {
|
||||
if (displayAuthenticationMethod) {
|
||||
return $('.select-authentication').val();
|
||||
}
|
||||
return getUserAuthenticationMethod(defaultAuthenticationMethod, match);
|
||||
}
|
||||
|
||||
function getUserAuthenticationMethod(defaultAuthenticationMethod, match) {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
Meteor.subscribe('user-authenticationMethod', match, {
|
||||
onReady() {
|
||||
const user = Users.findOne();
|
||||
|
||||
const authenticationMethod = user
|
||||
? user.authenticationMethod
|
||||
: defaultAuthenticationMethod;
|
||||
|
||||
resolve(authenticationMethod);
|
||||
},
|
||||
});
|
||||
} catch(error) {
|
||||
resolve(defaultAuthenticationMethod);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -385,8 +385,8 @@ a
|
||||
|
||||
@media screen and (max-width: 800px)
|
||||
#content
|
||||
margin: 1px 0px 49px 0px
|
||||
height: calc(100% - 96px)
|
||||
margin: 1px 0px 0px 0px
|
||||
height: calc(100% - 0px)
|
||||
|
||||
> .wrapper
|
||||
margin-top: 0px
|
||||
|
||||
@@ -36,7 +36,33 @@ template(name="boardActions")
|
||||
div.trigger-text
|
||||
| {{_'r-card'}}
|
||||
div.trigger-button.js-add-arch-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{_'r-add-swimlane'}}
|
||||
div.trigger-dropdown
|
||||
input(id="swimlane-name",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-button.js-add-swimlane-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{_'r-create-card'}}
|
||||
div.trigger-dropdown
|
||||
input(id="card-name",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-text
|
||||
| {{_'r-in-list'}}
|
||||
div.trigger-dropdown
|
||||
input(id="list-name",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-text
|
||||
| {{_'r-in-swimlane'}}
|
||||
div.trigger-dropdown
|
||||
input(id="swimlane-name2",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-button.js-create-card-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,52 @@ BlazeComponent.extendComponent({
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-create-card-action' (event) {
|
||||
const ruleName = this.data().ruleName.get();
|
||||
const trigger = this.data().triggerVar.get();
|
||||
const cardName = this.find('#card-name').value;
|
||||
const listName = this.find('#list-name').value;
|
||||
const swimlaneName = this.find('#swimlane-name2').value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
const desc = Utils.getTriggerActionDesc(event, this);
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'createCard',
|
||||
swimlaneName,
|
||||
cardName,
|
||||
listName,
|
||||
boardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
title: ruleName,
|
||||
triggerId,
|
||||
actionId,
|
||||
boardId,
|
||||
});
|
||||
|
||||
},
|
||||
'click .js-add-swimlane-action' (event) {
|
||||
const ruleName = this.data().ruleName.get();
|
||||
const trigger = this.data().triggerVar.get();
|
||||
const swimlaneName = this.find('#swimlane-name').value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
const desc = Utils.getTriggerActionDesc(event, this);
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'addSwimlane',
|
||||
swimlaneName,
|
||||
boardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
title: ruleName,
|
||||
triggerId,
|
||||
actionId,
|
||||
boardId,
|
||||
});
|
||||
|
||||
},
|
||||
'click .js-add-spec-move-action' (event) {
|
||||
const ruleName = this.data().ruleName.get();
|
||||
const trigger = this.data().triggerVar.get();
|
||||
|
||||
@@ -35,9 +35,21 @@ template(name="cardActions")
|
||||
div.trigger-button.js-add-removeall-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{{_'r-set-color'}}}
|
||||
button.trigger-button.trigger-button-color.js-show-color-palette(
|
||||
id="color-action"
|
||||
class="card-details-{{cardColorButton}}")
|
||||
| {{{_ cardColorButtonText }}}
|
||||
div.trigger-button.js-set-color-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template(name="setCardActionsColorPopup")
|
||||
form.edit-label
|
||||
.palette-colors: each colors
|
||||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
@@ -1,6 +1,20 @@
|
||||
let cardColors;
|
||||
Meteor.startup(() => {
|
||||
cardColors = Cards.simpleSchema()._schema.color.allowedValues;
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.subscribe('allRules');
|
||||
this.cardColorButtonValue = new ReactiveVar('green');
|
||||
},
|
||||
|
||||
cardColorButton() {
|
||||
return this.cardColorButtonValue.get();
|
||||
},
|
||||
|
||||
cardColorButtonText() {
|
||||
return `color-${ this.cardColorButtonValue.get() }`;
|
||||
},
|
||||
|
||||
labels() {
|
||||
@@ -112,7 +126,63 @@ BlazeComponent.extendComponent({
|
||||
boardId,
|
||||
});
|
||||
},
|
||||
'click .js-show-color-palette'(event){
|
||||
const funct = Popup.open('setCardActionsColor');
|
||||
const colorButton = this.find('#color-action');
|
||||
if (colorButton.value === '') {
|
||||
colorButton.value = 'green';
|
||||
}
|
||||
funct.call(this, event);
|
||||
},
|
||||
'click .js-set-color-action' (event) {
|
||||
const ruleName = this.data().ruleName.get();
|
||||
const trigger = this.data().triggerVar.get();
|
||||
const selectedColor = this.cardColorButtonValue.get();
|
||||
const boardId = Session.get('currentBoard');
|
||||
const desc = Utils.getTriggerActionDesc(event, this);
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'setColor',
|
||||
selectedColor,
|
||||
boardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
title: ruleName,
|
||||
triggerId,
|
||||
actionId,
|
||||
boardId,
|
||||
});
|
||||
},
|
||||
}];
|
||||
},
|
||||
|
||||
}).register('cardActions');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentAction = this.currentData();
|
||||
this.colorButtonValue = Popup.getOpenerComponent().cardColorButtonValue;
|
||||
this.currentColor = new ReactiveVar(this.colorButtonValue.get());
|
||||
},
|
||||
|
||||
colors() {
|
||||
return cardColors.map((color) => ({ color, name: '' }));
|
||||
},
|
||||
|
||||
isSelected(color) {
|
||||
return this.currentColor.get() === color;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-palette-color'() {
|
||||
this.currentColor.set(this.currentData().color);
|
||||
},
|
||||
'click .js-submit' () {
|
||||
this.colorButtonValue.set(this.currentColor.get());
|
||||
Popup.close();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('setCardActionsColorPopup');
|
||||
@@ -43,6 +43,25 @@ template(name="checklistActions")
|
||||
div.trigger-button.js-add-check-item-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{{_'r-add-checklist'}}}
|
||||
div.trigger-dropdown
|
||||
input(id="checklist-name-3",type=text,placeholder="{{{_'r-name'}}}")
|
||||
div.trigger-text
|
||||
| {{{_'r-with-items'}}}
|
||||
div.trigger-dropdown
|
||||
input(id="checklist-items",type=text,placeholder="{{{_'r-items-list'}}}")
|
||||
div.trigger-button.js-add-checklist-items-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{{_'r-checklist-note'}}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,29 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
events() {
|
||||
return [{
|
||||
'click .js-add-checklist-items-action' (event) {
|
||||
const ruleName = this.data().ruleName.get();
|
||||
const trigger = this.data().triggerVar.get();
|
||||
const checklistName = this.find('#checklist-name-3').value;
|
||||
const checklistItems = this.find('#checklist-items').value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
const desc = Utils.getTriggerActionDesc(event, this);
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'addChecklistWithItems',
|
||||
checklistName,
|
||||
checklistItems,
|
||||
boardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
title: ruleName,
|
||||
triggerId,
|
||||
actionId,
|
||||
boardId,
|
||||
});
|
||||
|
||||
},
|
||||
'click .js-add-checklist-action' (event) {
|
||||
const ruleName = this.data().ruleName.get();
|
||||
const trigger = this.data().triggerVar.get();
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
display: inline-block
|
||||
float: left
|
||||
margin: revert
|
||||
|
||||
.hide-element
|
||||
display:none !important
|
||||
.user-details
|
||||
display:inline-block
|
||||
.rules-btns-group
|
||||
position: absolute
|
||||
right: 0
|
||||
@@ -120,6 +123,15 @@
|
||||
.trigger-text
|
||||
font-size: 16px
|
||||
display:inline-block
|
||||
.trigger-inline-button
|
||||
font-size: 16px
|
||||
display: inline;
|
||||
padding: 6px;
|
||||
border: 1px solid #eee
|
||||
border-radius: 4px
|
||||
box-shadow: inset -1px -1px 3px rgba(0,0,0,.05)
|
||||
&:hover, &.is-active
|
||||
box-shadow: 0 0 0 2px darken(white, 60%) inset
|
||||
.trigger-text.trigger-text-email
|
||||
margin-left: 5px;
|
||||
margin-top: 10px;
|
||||
@@ -160,6 +172,17 @@
|
||||
box-shadow: 0 0 0 2px darken(white, 60%) inset
|
||||
.trigger-button.trigger-button-email
|
||||
top:30px
|
||||
.trigger-button.trigger-button-person
|
||||
right:-40px
|
||||
.trigger-button.trigger-button-color
|
||||
top: unset
|
||||
position: unset
|
||||
transform: unset
|
||||
font-size: 16px
|
||||
width:auto
|
||||
padding-left: 10px
|
||||
padding-right: 10px
|
||||
height:40px
|
||||
.trigger-item.trigger-item-mail
|
||||
height:300px
|
||||
|
||||
|
||||
@@ -9,7 +9,13 @@ BlazeComponent.extendComponent({
|
||||
setTrigger() {
|
||||
this.rulesCurrentTab.set('trigger');
|
||||
},
|
||||
|
||||
sanitizeObject(obj){
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if(obj[key] === '' || obj[key] === undefined){
|
||||
obj[key] = '*';
|
||||
}}
|
||||
);
|
||||
},
|
||||
setRulesList() {
|
||||
this.rulesCurrentTab.set('rulesList');
|
||||
},
|
||||
@@ -42,8 +48,27 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
'click .js-goto-action' (event) {
|
||||
event.preventDefault();
|
||||
// Add user to the trigger
|
||||
const username = $(event.currentTarget.offsetParent).find('.user-name').val();
|
||||
let trigger = this.triggerVar.get();
|
||||
trigger.userId = '*';
|
||||
if(username !== undefined ){
|
||||
const userFound = Users.findOne({username});
|
||||
if(userFound !== undefined){
|
||||
trigger.userId = userFound._id;
|
||||
this.triggerVar.set(trigger);
|
||||
}
|
||||
}
|
||||
// Sanitize trigger
|
||||
trigger = this.triggerVar.get();
|
||||
this.sanitizeObject(trigger);
|
||||
this.triggerVar.set(trigger);
|
||||
this.setAction();
|
||||
},
|
||||
'click .js-show-user-field' (event) {
|
||||
event.preventDefault();
|
||||
$(event.currentTarget.offsetParent).find('.user-details').removeClass('hide-element');
|
||||
},
|
||||
'click .js-goto-rules' (event) {
|
||||
event.preventDefault();
|
||||
this.setRulesList();
|
||||
@@ -68,3 +93,5 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
}).register('rulesMain');
|
||||
|
||||
|
||||
@@ -1,43 +1,58 @@
|
||||
template(name="boardTriggers")
|
||||
div.trigger-item
|
||||
div.trigger-item#trigger-two
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{_'r-when-a-card-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="gen-action")
|
||||
option(value="created") {{_'r-added-to'}}
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
| {{_'r-when-a-card'}}
|
||||
div.trigger-inline-button.js-open-card-title-popup
|
||||
i.fa.fa-filter
|
||||
div.trigger-text
|
||||
| {{_'r-the-board'}}
|
||||
div.trigger-button.js-add-gen-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
| {{_'r-is'}}
|
||||
div.trigger-text
|
||||
| {{_'r-when-a-card-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="create-action")
|
||||
option(value="created") {{_'r-added-to'}}
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
| {{_'r-added-to'}}
|
||||
div.trigger-text
|
||||
| {{_'r-list'}}
|
||||
div.trigger-dropdown
|
||||
input(id="create-list-name",type=text,placeholder="{{_'r-list-name'}}")
|
||||
input(id="create-list-name",type=text,placeholder="{{_'r-list-name'}}")
|
||||
div.trigger-text
|
||||
| {{_'r-in-swimlane'}}
|
||||
div.trigger-dropdown
|
||||
input(id="create-swimlane-name",type=text,placeholder="{{_'r-swimlane-name'}}")
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-create-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-item#trigger-three
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{_'r-when-a-card-is-moved'}}
|
||||
| {{_'r-when-a-card'}}
|
||||
div.trigger-inline-button.js-open-card-title-popup
|
||||
i.fa.fa-filter
|
||||
div.trigger-text
|
||||
| {{_'r-is-moved'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-gen-moved-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-item#trigger-four
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{_'r-when-a-card-is'}}
|
||||
| {{_'r-when-a-card'}}
|
||||
div.trigger-inline-button.js-open-card-title-popup
|
||||
i.fa.fa-filter
|
||||
div.trigger-text
|
||||
| {{_'r-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="move-action")
|
||||
option(value="moved-to") {{_'r-moved-to'}}
|
||||
@@ -45,21 +60,54 @@ template(name="boardTriggers")
|
||||
div.trigger-text
|
||||
| {{_'r-list'}}
|
||||
div.trigger-dropdown
|
||||
input(id="move-list-name",type=text,placeholder="{{_'r-list-name'}}")
|
||||
input(id="move-list-name",type=text,placeholder="{{_'r-list-name'}}")
|
||||
div.trigger-text
|
||||
| {{_'r-in-swimlane'}}
|
||||
div.trigger-dropdown
|
||||
input(id="create-swimlane-name-2",type=text,placeholder="{{_'r-swimlane-name'}}")
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-moved-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-item#trigger-five
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{_'r-when-a-card-is'}}
|
||||
| {{_'r-when-a-card'}}
|
||||
div.trigger-inline-button.js-open-card-title-popup
|
||||
i.fa.fa-filter
|
||||
div.trigger-text
|
||||
| {{_'r-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="arch-action")
|
||||
option(value="archived") {{_'r-archived'}}
|
||||
option(value="unarchived") {{_'r-unarchived'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-arch-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
| {{{_'r-board-note'}}}
|
||||
|
||||
template(name="boardCardTitlePopup")
|
||||
form
|
||||
label
|
||||
| Card Title Filter
|
||||
input.js-card-filter-name(type="text" value=title autofocus)
|
||||
input.js-card-filter-button.primary.wide(type="submit" value="{{_ 'set-filter'}}")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,67 +1,57 @@
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
|
||||
this.provaVar = new ReactiveVar('');
|
||||
this.currentPopupTriggerId = 'def';
|
||||
this.cardTitleFilters = {};
|
||||
},
|
||||
setNameFilter(name){
|
||||
this.cardTitleFilters[this.currentPopupTriggerId] = name;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-add-gen-trigger' (event) {
|
||||
const desc = Utils.getTriggerActionDesc(event, this);
|
||||
const datas = this.data();
|
||||
const actionSelected = this.find('#gen-action').value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
if (actionSelected === 'created') {
|
||||
datas.triggerVar.set({
|
||||
activityType: 'createCard',
|
||||
boardId,
|
||||
'listName': '*',
|
||||
desc,
|
||||
});
|
||||
}
|
||||
if (actionSelected === 'removed') {
|
||||
datas.triggerVar.set({
|
||||
activityType: 'removeCard',
|
||||
boardId,
|
||||
desc,
|
||||
});
|
||||
}
|
||||
|
||||
'click .js-open-card-title-popup'(event){
|
||||
const funct = Popup.open('boardCardTitle');
|
||||
const divId = $(event.currentTarget.parentNode.parentNode).attr('id');
|
||||
//console.log('current popup');
|
||||
//console.log(this.currentPopupTriggerId);
|
||||
this.currentPopupTriggerId = divId;
|
||||
funct.call(this, event);
|
||||
},
|
||||
'click .js-add-create-trigger' (event) {
|
||||
const desc = Utils.getTriggerActionDesc(event, this);
|
||||
const datas = this.data();
|
||||
const actionSelected = this.find('#create-action').value;
|
||||
const listName = this.find('#create-list-name').value;
|
||||
const swimlaneName = this.find('#create-swimlane-name').value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
if (actionSelected === 'created') {
|
||||
datas.triggerVar.set({
|
||||
activityType: 'createCard',
|
||||
boardId,
|
||||
listName,
|
||||
desc,
|
||||
});
|
||||
}
|
||||
if (actionSelected === 'removed') {
|
||||
datas.triggerVar.set({
|
||||
activityType: 'removeCard',
|
||||
boardId,
|
||||
listName,
|
||||
desc,
|
||||
});
|
||||
}
|
||||
const divId = $(event.currentTarget.parentNode).attr('id');
|
||||
const cardTitle = this.cardTitleFilters[divId];
|
||||
// move to generic funciont
|
||||
datas.triggerVar.set({
|
||||
activityType: 'createCard',
|
||||
boardId,
|
||||
cardTitle,
|
||||
swimlaneName,
|
||||
listName,
|
||||
desc,
|
||||
});
|
||||
},
|
||||
'click .js-add-moved-trigger' (event) {
|
||||
const datas = this.data();
|
||||
const desc = Utils.getTriggerActionDesc(event, this);
|
||||
|
||||
const swimlaneName = this.find('#create-swimlane-name-2').value;
|
||||
const actionSelected = this.find('#move-action').value;
|
||||
const listName = this.find('#move-list-name').value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
const divId = $(event.currentTarget.parentNode).attr('id');
|
||||
const cardTitle = this.cardTitleFilters[divId];
|
||||
if (actionSelected === 'moved-to') {
|
||||
datas.triggerVar.set({
|
||||
activityType: 'moveCard',
|
||||
boardId,
|
||||
listName,
|
||||
cardTitle,
|
||||
swimlaneName,
|
||||
'oldListName': '*',
|
||||
desc,
|
||||
});
|
||||
@@ -70,6 +60,8 @@ BlazeComponent.extendComponent({
|
||||
datas.triggerVar.set({
|
||||
activityType: 'moveCard',
|
||||
boardId,
|
||||
cardTitle,
|
||||
swimlaneName,
|
||||
'listName': '*',
|
||||
'oldListName': listName,
|
||||
desc,
|
||||
@@ -82,8 +74,9 @@ BlazeComponent.extendComponent({
|
||||
const boardId = Session.get('currentBoard');
|
||||
|
||||
datas.triggerVar.set({
|
||||
activityType: 'moveCard',
|
||||
'activityType': 'moveCard',
|
||||
boardId,
|
||||
'swimlaneName': '*',
|
||||
'listName':'*',
|
||||
'oldListName': '*',
|
||||
desc,
|
||||
@@ -114,3 +107,13 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
}).register('boardTriggers');
|
||||
|
||||
|
||||
Template.boardCardTitlePopup.events({
|
||||
submit(evt, tpl) {
|
||||
const title = tpl.$('.js-card-filter-name').val().trim();
|
||||
Popup.getOpenerComponent().setNameFilter(title);
|
||||
evt.preventDefault();
|
||||
Popup.close();
|
||||
},
|
||||
});
|
||||
@@ -1,79 +1,114 @@
|
||||
template(name="cardTriggers")
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-when-a-label-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="label-action")
|
||||
option(value="added") {{_'r-added-to'}}
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-a-card'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-gen-label-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-when-the-label-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="spec-label")
|
||||
each labels
|
||||
option(value="#{_id}")
|
||||
= name
|
||||
div.trigger-text
|
||||
option(value="#{_id}" style="background-color: #{name}")
|
||||
= translatedname
|
||||
div.trigger-text
|
||||
| {{_'r-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="spec-label-action")
|
||||
option(value="added") {{_'r-added-to'}}
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-a-card'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-spec-label-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-when-a-member'}}
|
||||
div.trigger-dropdown
|
||||
select(id="gen-member-action")
|
||||
option(value="added") {{_'r-added-to'}}
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-a-card'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-gen-member-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-when-the-member'}}
|
||||
div.trigger-dropdown
|
||||
input(id="spec-member",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-text
|
||||
input(id="spec-member",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-text
|
||||
| {{_'r-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="spec-member-action")
|
||||
option(value="added") {{_'r-added-to'}}
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-a-card'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-spec-member-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-when-a-attach'}}
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-is'}}
|
||||
div.trigger-dropdown
|
||||
select(id="attach-action")
|
||||
option(value="added") {{_'r-added-to'}}
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-a-card'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-attachment-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
@@ -6,7 +6,10 @@ BlazeComponent.extendComponent({
|
||||
const labels = Boards.findOne(Session.get('currentBoard')).labels;
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
if (labels[i].name === '' || labels[i].name === undefined) {
|
||||
labels[i].name = labels[i].color.toUpperCase();
|
||||
labels[i].name = labels[i].color;
|
||||
labels[i].translatedname = `${TAPi18n.__(`color-${ labels[i].color}`)}`;
|
||||
} else {
|
||||
labels[i].translatedname = labels[i].name;
|
||||
}
|
||||
}
|
||||
return labels;
|
||||
|
||||
@@ -9,6 +9,13 @@ template(name="checklistTriggers")
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
div.trigger-text
|
||||
| {{_'r-a-card'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-gen-check-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
@@ -27,6 +34,13 @@ template(name="checklistTriggers")
|
||||
option(value="removed") {{_'r-removed-from'}}
|
||||
div.trigger-text
|
||||
| {{_'r-a-card'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-spec-check-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
@@ -38,6 +52,13 @@ template(name="checklistTriggers")
|
||||
select(id="gen-comp-check-action")
|
||||
option(value="completed") {{_'r-completed'}}
|
||||
option(value="uncompleted") {{_'r-made-incomplete'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-gen-comp-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
@@ -53,6 +74,13 @@ template(name="checklistTriggers")
|
||||
select(id="spec-comp-check-action")
|
||||
option(value="completed") {{_'r-completed'}}
|
||||
option(value="uncompleted") {{_'r-made-incomplete'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-spec-comp-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
@@ -64,6 +92,13 @@ template(name="checklistTriggers")
|
||||
select(id="check-item-gen-action")
|
||||
option(value="checked") {{_'r-checked'}}
|
||||
option(value="unchecked") {{_'r-unchecked'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-gen-check-item-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
|
||||
@@ -79,5 +114,12 @@ template(name="checklistTriggers")
|
||||
select(id="check-item-spec-action")
|
||||
option(value="checked") {{_'r-checked'}}
|
||||
option(value="unchecked") {{_'r-unchecked'}}
|
||||
div.trigger-button.trigger-button-person.js-show-user-field
|
||||
i.fa.fa-user
|
||||
div.user-details.hide-element
|
||||
div.trigger-text
|
||||
| {{_'r-by'}}
|
||||
div.trigger-dropdown
|
||||
input(class="user-name",type=text,placeholder="{{_'username'}}")
|
||||
div.trigger-button.js-add-spec-check-item-trigger.js-goto-action
|
||||
i.fa.fa-plus
|
||||
@@ -2,5 +2,8 @@ template(name='connectionMethod')
|
||||
div.at-form-authentication
|
||||
label {{_ 'authentication-method'}}
|
||||
select.select-authentication
|
||||
each authentications
|
||||
option(value="{{value}}") {{_ value}}
|
||||
each authentications
|
||||
if isSelected value
|
||||
option(value="{{value}}" selected) {{_ value}}
|
||||
else
|
||||
option(value="{{value}}") {{_ value}}
|
||||
@@ -31,4 +31,7 @@ Template.connectionMethod.helpers({
|
||||
authentications() {
|
||||
return Template.instance().authenticationMethods.get();
|
||||
},
|
||||
isSelected(match) {
|
||||
return Template.instance().data.authenticationMethod === match;
|
||||
},
|
||||
});
|
||||
@@ -17,7 +17,7 @@ template(name='statistics')
|
||||
table
|
||||
tbody
|
||||
tr
|
||||
th {{_ 'Wekan_version'}}
|
||||
th Wekan {{_ 'info'}}
|
||||
td {{statistics.version}}
|
||||
tr
|
||||
th {{_ 'Node_version'}}
|
||||
|
||||
@@ -3,8 +3,13 @@ template(name="people")
|
||||
unless currentUser.isAdmin
|
||||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-title
|
||||
span {{_ 'people'}}
|
||||
.content-title.ext-box
|
||||
.ext-box-left
|
||||
span {{_ 'people'}}
|
||||
input#searchInput(placeholder="{{_ 'search'}}")
|
||||
button#searchButton {{_ 'search'}}
|
||||
.ext-box-right
|
||||
span {{_ 'people-number'}} #{peopleNumber}
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
@@ -103,4 +108,4 @@ template(name="editUserPopup")
|
||||
| {{_ 'password'}}
|
||||
input.js-profile-password(type="password")
|
||||
|
||||
input.primary.wide(type="submit" value="{{_ 'save'}}")
|
||||
input.primary.wide(type="submit" value="{{_ 'save'}}")
|
||||
@@ -8,6 +8,8 @@ BlazeComponent.extendComponent({
|
||||
this.error = new ReactiveVar('');
|
||||
this.loading = new ReactiveVar(false);
|
||||
this.people = new ReactiveVar(true);
|
||||
this.findUsersOptions = new ReactiveVar({});
|
||||
this.number = new ReactiveVar(0);
|
||||
|
||||
this.page = new ReactiveVar(1);
|
||||
this.loadNextPageLocked = false;
|
||||
@@ -26,6 +28,33 @@ BlazeComponent.extendComponent({
|
||||
});
|
||||
});
|
||||
},
|
||||
events() {
|
||||
return [{
|
||||
'click #searchButton'() {
|
||||
this.filterPeople();
|
||||
},
|
||||
'keydown #searchInput'(event) {
|
||||
if (event.keyCode === 13 && !event.shiftKey) {
|
||||
this.filterPeople();
|
||||
}
|
||||
},
|
||||
}];
|
||||
},
|
||||
filterPeople() {
|
||||
const value = $('#searchInput').first().val();
|
||||
if (value === '') {
|
||||
this.findUsersOptions.set({});
|
||||
} else {
|
||||
const regex = new RegExp(value, 'i');
|
||||
this.findUsersOptions.set({
|
||||
$or: [
|
||||
{ username: regex },
|
||||
{ 'profile.fullname': regex },
|
||||
{ 'emails.address': regex },
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
loadNextPage() {
|
||||
if (this.loadNextPageLocked === false) {
|
||||
this.page.set(this.page.get() + 1);
|
||||
@@ -49,9 +78,14 @@ BlazeComponent.extendComponent({
|
||||
this.loading.set(w);
|
||||
},
|
||||
peopleList() {
|
||||
return Users.find({}, {
|
||||
const users = Users.find(this.findUsersOptions.get(), {
|
||||
fields: {_id: true},
|
||||
});
|
||||
this.number.set(users.count());
|
||||
return users;
|
||||
},
|
||||
peopleNumber() {
|
||||
return this.number.get();
|
||||
},
|
||||
}).register('people');
|
||||
|
||||
|
||||
@@ -13,3 +13,24 @@ table
|
||||
|
||||
tr:nth-child(even)
|
||||
background-color: #dddddd;
|
||||
|
||||
.ext-box
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 34px;
|
||||
|
||||
.ext-box-left
|
||||
display: flex;
|
||||
width: 40%
|
||||
|
||||
span
|
||||
vertical-align: center;
|
||||
line-height: 34px;
|
||||
margin-right: 10px;
|
||||
|
||||
input, button
|
||||
margin: 0 10px 0 0;
|
||||
padding: 0;
|
||||
|
||||
button
|
||||
min-width: 60px;
|
||||
@@ -134,16 +134,41 @@ template(name='announcementSettings')
|
||||
|
||||
template(name='layoutSettings')
|
||||
ul#layout-setting.setting-detail
|
||||
li.layout-form
|
||||
//li.layout-form
|
||||
.title {{_ 'hide-logo'}}
|
||||
.form-group.flex
|
||||
input.form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
input.form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
li.layout-form
|
||||
.title {{_ 'display-authentication-method'}}
|
||||
.form-group.flex
|
||||
input.form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="true" checked="{{#if currentSetting.displayAuthenticationMethod}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
input.form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="false" checked="{{#unless currentSetting.displayAuthenticationMethod}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
li.layout-form
|
||||
.title {{_ 'default-authentication-method'}}
|
||||
+selectAuthenticationMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
|
||||
li.layout-form
|
||||
.title {{_ 'custom-product-name'}}
|
||||
.form-group
|
||||
input.form-control#product-name(type="text", placeholder="Wekan" value="{{currentSetting.productName}}")
|
||||
input.form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}")
|
||||
li.layout-form
|
||||
.title {{_ 'add-custom-html-after-body-start'}}
|
||||
textarea#customHTMLafterBodyStart.form-control= currentSetting.customHTMLafterBodyStart
|
||||
li.layout-form
|
||||
.title {{_ 'add-custom-html-before-body-end'}}
|
||||
textarea#customHTMLbeforeBodyEnd.form-control= currentSetting.customHTMLbeforeBodyEnd
|
||||
li
|
||||
button.js-save-layout.primary {{_ 'save'}}
|
||||
|
||||
|
||||
template(name='selectAuthenticationMethod')
|
||||
select#defaultAuthenticationMethod
|
||||
each authentications
|
||||
if isSelected value
|
||||
option(value="{{value}}" selected) {{_ value}}
|
||||
else
|
||||
option(value="{{value}}") {{_ value}}
|
||||
@@ -62,6 +62,9 @@ BlazeComponent.extendComponent({
|
||||
toggleHideLogo() {
|
||||
$('#hide-logo').toggleClass('is-checked');
|
||||
},
|
||||
toggleDisplayAuthenticationMethod() {
|
||||
$('#display-authentication-method').toggleClass('is-checked');
|
||||
},
|
||||
switchMenu(event) {
|
||||
const target = $(event.target);
|
||||
if (!target.hasClass('active')) {
|
||||
@@ -87,7 +90,7 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
inviteThroughEmail() {
|
||||
const emails = $('#email-to-invite').val().trim().split('\n').join(',').split(',');
|
||||
const emails = $('#email-to-invite').val().toLowerCase().trim().split('\n').join(',').split(',');
|
||||
const boardsToInvite = [];
|
||||
$('.js-toggle-board-choose .materialCheckBox.is-checked').each(function () {
|
||||
boardsToInvite.push($(this).data('id'));
|
||||
@@ -140,13 +143,20 @@ BlazeComponent.extendComponent({
|
||||
|
||||
const productName = $('#product-name').val().trim();
|
||||
const hideLogoChange = ($('input[name=hideLogo]:checked').val() === 'true');
|
||||
const displayAuthenticationMethod = ($('input[name=displayAuthenticationMethod]:checked').val() === 'true');
|
||||
const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
|
||||
const customHTMLafterBodyStart = $('#customHTMLafterBodyStart').val().trim();
|
||||
const customHTMLbeforeBodyEnd = $('#customHTMLbeforeBodyEnd').val().trim();
|
||||
|
||||
try {
|
||||
|
||||
Settings.update(Settings.findOne()._id, {
|
||||
$set: {
|
||||
productName,
|
||||
hideLogo: hideLogoChange,
|
||||
customHTMLafterBodyStart,
|
||||
customHTMLbeforeBodyEnd,
|
||||
displayAuthenticationMethod,
|
||||
defaultAuthenticationMethod,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -155,23 +165,20 @@ BlazeComponent.extendComponent({
|
||||
this.setLoading(false);
|
||||
}
|
||||
|
||||
saveMailServerInfo();
|
||||
DocHead.setTitle(productName);
|
||||
|
||||
},
|
||||
|
||||
sendSMTPTestEmail() {
|
||||
Meteor.call('sendSMTPTestEmail', (err, ret) => {
|
||||
if (!err && ret) { /* eslint-disable no-console */
|
||||
if (!err && ret) {
|
||||
const message = `${TAPi18n.__(ret.message)}: ${ret.email}`;
|
||||
console.log(message);
|
||||
alert(message);
|
||||
} else {
|
||||
const reason = err.reason || '';
|
||||
const message = `${TAPi18n.__(err.error)}\n${reason}`;
|
||||
console.log(message, err);
|
||||
alert(message);
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
});
|
||||
},
|
||||
|
||||
@@ -186,6 +193,7 @@ BlazeComponent.extendComponent({
|
||||
'click button.js-send-smtp-test-email': this.sendSMTPTestEmail,
|
||||
'click a.js-toggle-hide-logo': this.toggleHideLogo,
|
||||
'click button.js-save-layout': this.saveLayout,
|
||||
'click a.js-toggle-display-authentication-method': this.toggleDisplayAuthenticationMethod,
|
||||
}];
|
||||
},
|
||||
}).register('setting');
|
||||
@@ -258,3 +266,29 @@ BlazeComponent.extendComponent({
|
||||
}];
|
||||
},
|
||||
}).register('announcementSettings');
|
||||
|
||||
|
||||
Template.selectAuthenticationMethod.onCreated(function() {
|
||||
this.authenticationMethods = new ReactiveVar([]);
|
||||
|
||||
Meteor.call('getAuthenticationsEnabled', (_, result) => {
|
||||
if (result) {
|
||||
// TODO : add a management of different languages
|
||||
// (ex {value: ldap, text: TAPi18n.__('ldap', {}, T9n.getLanguage() || 'en')})
|
||||
this.authenticationMethods.set([
|
||||
{value: 'password'},
|
||||
// Gets only the authentication methods availables
|
||||
...Object.entries(result).filter((e) => e[1]).map((e) => ({value: e[0]})),
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Template.selectAuthenticationMethod.helpers({
|
||||
authentications() {
|
||||
return Template.instance().authenticationMethods.get();
|
||||
},
|
||||
isSelected(match) {
|
||||
return Template.instance().data.authenticationMethod === match;
|
||||
},
|
||||
});
|
||||
@@ -52,6 +52,10 @@
|
||||
|
||||
.main-body
|
||||
padding: 0.1em 1em
|
||||
-webkit-user-select: auto // Safari 3.1+
|
||||
-moz-user-select: auto // Firefox 2+
|
||||
-ms-user-select: auto // IE 10+
|
||||
user-select: auto // Standard syntax
|
||||
|
||||
ul
|
||||
li
|
||||
|
||||
@@ -4,22 +4,21 @@ template(name="settingHeaderBar")
|
||||
|
||||
.setting-header-btns.left
|
||||
unless isMiniScreen
|
||||
unless isSandstorm
|
||||
if currentUser
|
||||
a.setting-header-btn.settings(href="{{pathFor 'setting'}}")
|
||||
i.fa(class="fa-cog")
|
||||
span {{_ 'settings'}}
|
||||
if currentUser
|
||||
a.setting-header-btn.settings(href="{{pathFor 'setting'}}")
|
||||
i.fa(class="fa-cog")
|
||||
span {{_ 'settings'}}
|
||||
|
||||
a.setting-header-btn.people(href="{{pathFor 'people'}}")
|
||||
i.fa(class="fa-users")
|
||||
span {{_ 'people'}}
|
||||
a.setting-header-btn.people(href="{{pathFor 'people'}}")
|
||||
i.fa(class="fa-users")
|
||||
span {{_ 'people'}}
|
||||
|
||||
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
|
||||
i.fa(class="fa-info-circle")
|
||||
span {{_ 'info'}}
|
||||
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
|
||||
i.fa(class="fa-info-circle")
|
||||
span {{_ 'info'}}
|
||||
|
||||
else
|
||||
a.setting-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
else
|
||||
a.setting-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
@@ -1,9 +1,9 @@
|
||||
template(name="sidebar")
|
||||
.board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}")
|
||||
a.sidebar-tongue.js-toggle-sidebar(
|
||||
class="{{#if isTongueHidden}}is-hidden{{/if}}",
|
||||
title="{{showTongueTitle}}")
|
||||
i.fa.fa-angle-left
|
||||
//a.sidebar-tongue.js-toggle-sidebar(
|
||||
// class="{{#if isTongueHidden}}is-hidden{{/if}}",
|
||||
// title="{{showTongueTitle}}")
|
||||
// i.fa.fa-navicon
|
||||
.sidebar-shadow
|
||||
.sidebar-content.sidebar-shortcuts
|
||||
a.board-header-btn.js-shortcuts
|
||||
@@ -11,7 +11,7 @@ template(name="sidebar")
|
||||
span {{_ 'keyboard-shortcuts' }}
|
||||
.sidebar-content.js-board-sidebar-content.js-perfect-scrollbar
|
||||
a.hide-btn.js-hide-sidebar
|
||||
i.fa.fa-angle-right
|
||||
i.fa.fa-navicon
|
||||
unless isDefaultView
|
||||
h2
|
||||
a.fa.fa-chevron-left.js-back-home
|
||||
@@ -34,6 +34,9 @@ template(name="membersWidget")
|
||||
h3
|
||||
i.fa.fa-user
|
||||
| {{_ 'members'}}
|
||||
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
|
||||
i.board-header-btn-icon.fa.fa-cog
|
||||
|
||||
.board-widget-content
|
||||
each currentBoard.activeMembers
|
||||
+userAvatar(userId=this.userId showStatus=true)
|
||||
@@ -53,6 +56,130 @@ template(name="membersWidget")
|
||||
button.js-member-invite-accept.primary {{_ 'accept'}}
|
||||
button.js-member-invite-decline {{_ 'decline'}}
|
||||
|
||||
template(name="boardChangeColorPopup")
|
||||
.board-backgrounds-list.clearfix
|
||||
each backgroundColors
|
||||
.board-background-select.js-select-background
|
||||
span.background-box(class="board-color-{{this}}")
|
||||
if isSelected
|
||||
i.fa.fa-check
|
||||
|
||||
template(name="boardSubtaskSettingsPopup")
|
||||
form.board-subtask-settings
|
||||
h3 {{_ 'show-parent-in-minicard'}}
|
||||
a#prefix-with-full-path.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
|
||||
span {{_ 'prefix-with-full-path'}}
|
||||
a#prefix-with-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
|
||||
span {{_ 'prefix-with-parent'}}
|
||||
a#subtext-with-full-path.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
|
||||
span {{_ 'subtext-with-full-path'}}
|
||||
a#subtext-with-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
|
||||
span {{_ 'subtext-with-parent'}}
|
||||
a#no-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
|
||||
span {{_ 'no-parent'}}
|
||||
div
|
||||
hr
|
||||
|
||||
div.check-div
|
||||
a.flex.js-field-has-subtasks(class="{{#if allowsSubtasks}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsSubtasks}}is-checked{{/if}}")
|
||||
span {{_ 'show-subtasks-field'}}
|
||||
|
||||
label
|
||||
| {{_ 'deposit-subtasks-board'}}
|
||||
select.js-field-deposit-board(disabled="{{#unless allowsSubtasks}}disabled{{/unless}}")
|
||||
each boards
|
||||
if isBoardSelected
|
||||
option(value=_id selected="selected") {{title}}
|
||||
else
|
||||
option(value=_id) {{title}}
|
||||
if isNullBoardSelected
|
||||
option(value='null' selected="selected") {{_ 'custom-field-dropdown-none'}}
|
||||
else
|
||||
option(value='null') {{_ 'custom-field-dropdown-none'}}
|
||||
div
|
||||
hr
|
||||
|
||||
label
|
||||
| {{_ 'deposit-subtasks-list'}}
|
||||
select.js-field-deposit-list(disabled="{{#unless hasLists}}disabled{{/unless}}")
|
||||
each lists
|
||||
if isListSelected
|
||||
option(value=_id selected="selected") {{title}}
|
||||
else
|
||||
option(value=_id) {{title}}
|
||||
|
||||
template(name="chooseBoardSource")
|
||||
ul.pop-over-list
|
||||
li
|
||||
a(href="{{pathFor '/import/trello'}}") {{_ 'from-trello'}}
|
||||
li
|
||||
a(href="{{pathFor '/import/wekan'}}") {{_ 'from-wekan'}}
|
||||
|
||||
template(name="archiveBoardPopup")
|
||||
p {{_ 'close-board-pop'}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
|
||||
|
||||
template(name="outgoingWebhooksPopup")
|
||||
each integrations
|
||||
form.integration-form
|
||||
if title
|
||||
h4 {{title}}
|
||||
else
|
||||
h4 {{_ 'no-name'}}
|
||||
label
|
||||
| URL
|
||||
input.js-outgoing-webhooks-url(type="text" name="url" value=url)
|
||||
input(type="hidden" value=_id name="id")
|
||||
input.primary.wide(type="submit" value="{{_ 'save'}}")
|
||||
form.integration-form
|
||||
h4
|
||||
| {{_ 'new-outgoing-webhook'}}
|
||||
label
|
||||
| URL
|
||||
input.js-outgoing-webhooks-url(type="text" name="url" autofocus)
|
||||
input.primary.wide(type="submit" value="{{_ 'save'}}")
|
||||
|
||||
template(name="boardMenuPopup")
|
||||
ul.pop-over-list
|
||||
li: a.js-custom-fields {{_ 'custom-fields'}}
|
||||
li: a.js-open-archives {{_ 'archived-items'}}
|
||||
if currentUser.isBoardAdmin
|
||||
li: a.js-change-board-color {{_ 'board-change-color'}}
|
||||
//-
|
||||
XXX Language should be handled by sandstorm, but for now display a
|
||||
language selection link in the board menu. This link is normally present
|
||||
in the header bar that is not displayed on sandstorm.
|
||||
if isSandstorm
|
||||
li: a.js-change-language {{_ 'language'}}
|
||||
unless isSandstorm
|
||||
if currentUser.isBoardAdmin
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
|
||||
unless currentBoard.isTemplatesBoard
|
||||
li: a.js-archive-board {{_ 'archive-board'}}
|
||||
li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-subtask-settings {{_ 'subtask-settings'}}
|
||||
|
||||
if isSandstorm
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
|
||||
li: a.js-import-board {{_ 'import-board-c'}}
|
||||
li: a.js-archive-board {{_ 'archive-board'}}
|
||||
li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-subtask-settings {{_ 'subtask-settings'}}
|
||||
|
||||
template(name="labelsWidget")
|
||||
.board-widget.board-widget-labels
|
||||
h3
|
||||
@@ -83,17 +210,16 @@ template(name="memberPopup")
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-filter-member {{_ 'filter-cards'}}
|
||||
unless isSandstorm
|
||||
if currentUser.isBoardAdmin
|
||||
li
|
||||
a.js-change-role
|
||||
| {{_ 'change-permissions'}}
|
||||
span.quiet (#{memberType})
|
||||
if currentUser.isBoardAdmin
|
||||
li
|
||||
if $eq currentUser._id userId
|
||||
a.js-leave-member {{_ 'leave-board'}}
|
||||
else if currentUser.isBoardAdmin
|
||||
a.js-remove-member {{_ 'remove-from-board'}}
|
||||
a.js-change-role
|
||||
| {{_ 'change-permissions'}}
|
||||
span.quiet (#{memberType})
|
||||
li
|
||||
if $eq currentUser._id userId
|
||||
a.js-leave-member {{_ 'leave-board'}}
|
||||
else if currentUser.isBoardAdmin
|
||||
a.js-remove-member {{_ 'remove-from-board'}}
|
||||
|
||||
|
||||
template(name="removeMemberPopup")
|
||||
|
||||
@@ -142,6 +142,52 @@ Template.memberPopup.helpers({
|
||||
},
|
||||
});
|
||||
|
||||
Template.boardMenuPopup.events({
|
||||
'click .js-rename-board': Popup.open('boardChangeTitle'),
|
||||
'click .js-custom-fields'() {
|
||||
Sidebar.setView('customFields');
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-open-archives'() {
|
||||
Sidebar.setView('archives');
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
||||
'click .js-change-language': Popup.open('changeLanguage'),
|
||||
'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
currentBoard.archive();
|
||||
// XXX We should have some kind of notification on top of the page to
|
||||
// confirm that the board was successfully archived.
|
||||
FlowRouter.go('home');
|
||||
}),
|
||||
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
Popup.close();
|
||||
Boards.remove(currentBoard._id);
|
||||
FlowRouter.go('home');
|
||||
}),
|
||||
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
|
||||
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
|
||||
});
|
||||
|
||||
Template.boardMenuPopup.helpers({
|
||||
exportUrl() {
|
||||
const params = {
|
||||
boardId: Session.get('currentBoard'),
|
||||
};
|
||||
const queryParams = {
|
||||
authToken: Accounts._storedLoginToken(),
|
||||
};
|
||||
return FlowRouter.path('/api/boards/:boardId/export', params, queryParams);
|
||||
},
|
||||
exportFilename() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
return `wekan-export-board-${boardId}.json`;
|
||||
},
|
||||
});
|
||||
|
||||
Template.memberPopup.events({
|
||||
'click .js-filter-member'() {
|
||||
Filter.members.toggle(this.userId);
|
||||
@@ -190,7 +236,14 @@ Template.membersWidget.helpers({
|
||||
|
||||
Template.membersWidget.events({
|
||||
'click .js-member': Popup.open('member'),
|
||||
'click .js-open-board-menu': Popup.open('boardMenu'),
|
||||
'click .js-manage-board-members': Popup.open('addMember'),
|
||||
'click .js-import': Popup.open('boardImportBoard'),
|
||||
submit: this.onSubmit,
|
||||
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||
'click .js-open-archived-board'() {
|
||||
Modal.open('archivedBoards');
|
||||
},
|
||||
'click .sandstorm-powerbox-request-identity'() {
|
||||
window.sandstormRequestIdentity();
|
||||
},
|
||||
@@ -209,6 +262,59 @@ Template.membersWidget.events({
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
integrations() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
return Integrations.find({ boardId: `${boardId}` }).fetch();
|
||||
},
|
||||
|
||||
integration(id) {
|
||||
const boardId = Session.get('currentBoard');
|
||||
return Integrations.findOne({ _id: id, boardId: `${boardId}` });
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'submit'(evt) {
|
||||
evt.preventDefault();
|
||||
const url = evt.target.url.value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
let id = null;
|
||||
let integration = null;
|
||||
if (evt.target.id) {
|
||||
id = evt.target.id.value;
|
||||
integration = this.integration(id);
|
||||
if (url) {
|
||||
Integrations.update(integration._id, {
|
||||
$set: {
|
||||
url: `${url}`,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
Integrations.remove(integration._id);
|
||||
}
|
||||
} else if (url) {
|
||||
Integrations.insert({
|
||||
userId: Meteor.userId(),
|
||||
enabled: true,
|
||||
type: 'outgoing-webhooks',
|
||||
url: `${url}`,
|
||||
boardId: `${boardId}`,
|
||||
activities: ['all'],
|
||||
});
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('outgoingWebhooksPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
template() {
|
||||
return 'chooseBoardSource';
|
||||
},
|
||||
}).register('chooseBoardSourcePopup');
|
||||
|
||||
Template.labelsWidget.events({
|
||||
'click .js-label': Popup.open('editLabel'),
|
||||
'click .js-add-label': Popup.open('createLabel'),
|
||||
@@ -258,6 +364,124 @@ function draggableMembersLabelsWidgets() {
|
||||
Template.membersWidget.onRendered(draggableMembersLabelsWidgets);
|
||||
Template.labelsWidget.onRendered(draggableMembersLabelsWidgets);
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
backgroundColors() {
|
||||
return Boards.simpleSchema()._schema.color.allowedValues;
|
||||
},
|
||||
|
||||
isSelected() {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
return currentBoard.color === this.currentData().toString();
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-select-background'(evt) {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
const newColor = this.currentData().toString();
|
||||
currentBoard.setColor(newColor);
|
||||
evt.preventDefault();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('boardChangeColorPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
},
|
||||
|
||||
allowsSubtasks() {
|
||||
return this.currentBoard.allowsSubtasks;
|
||||
},
|
||||
|
||||
isBoardSelected() {
|
||||
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
|
||||
},
|
||||
|
||||
isNullBoardSelected() {
|
||||
return (this.currentBoard.subtasksDefaultBoardId === null) || (this.currentBoard.subtasksDefaultBoardId === undefined);
|
||||
},
|
||||
|
||||
boards() {
|
||||
return Boards.find({
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
},
|
||||
|
||||
lists() {
|
||||
return Lists.find({
|
||||
boardId: this.currentBoard._id,
|
||||
archived: false,
|
||||
}, {
|
||||
sort: ['title'],
|
||||
});
|
||||
},
|
||||
|
||||
hasLists() {
|
||||
return this.lists().count() > 0;
|
||||
},
|
||||
|
||||
isListSelected() {
|
||||
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
|
||||
},
|
||||
|
||||
presentParentTask() {
|
||||
let result = this.currentBoard.presentParentTask;
|
||||
if ((result === null) || (result === undefined)) {
|
||||
result = 'no-parent';
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-field-has-subtasks'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
|
||||
this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
|
||||
$('.js-field-has-subtasks .materialCheckBox').toggleClass('is-checked', this.currentBoard.allowsSubtasks);
|
||||
$('.js-field-has-subtasks').toggleClass('is-checked', this.currentBoard.allowsSubtasks);
|
||||
$('.js-field-deposit-board').prop('disabled', !this.currentBoard.allowsSubtasks);
|
||||
},
|
||||
'change .js-field-deposit-board'(evt) {
|
||||
let value = evt.target.value;
|
||||
if (value === 'null') {
|
||||
value = null;
|
||||
}
|
||||
this.currentBoard.setSubtasksDefaultBoardId(value);
|
||||
evt.preventDefault();
|
||||
},
|
||||
'change .js-field-deposit-list'(evt) {
|
||||
this.currentBoard.setSubtasksDefaultListId(evt.target.value);
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click .js-field-show-parent-in-minicard'(evt) {
|
||||
const value = evt.target.id || $(evt.target).parent()[0].id || $(evt.target).parent()[0].parent()[0].id;
|
||||
const options = [
|
||||
'prefix-with-full-path',
|
||||
'prefix-with-parent',
|
||||
'subtext-with-full-path',
|
||||
'subtext-with-parent',
|
||||
'no-parent'];
|
||||
options.forEach(function(element) {
|
||||
if (element !== value) {
|
||||
$(`#${element} .materialCheckBox`).toggleClass('is-checked', false);
|
||||
$(`#${element}`).toggleClass('is-checked', false);
|
||||
}
|
||||
});
|
||||
$(`#${value} .materialCheckBox`).toggleClass('is-checked', true);
|
||||
$(`#${value}`).toggleClass('is-checked', true);
|
||||
this.currentBoard.setPresentParentTask(value);
|
||||
evt.preventDefault();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('boardSubtaskSettingsPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.error = new ReactiveVar('');
|
||||
|
||||
@@ -2,7 +2,7 @@ BlazeComponent.extendComponent({
|
||||
|
||||
customFields() {
|
||||
return CustomFields.find({
|
||||
boardId: Session.get('currentBoard'),
|
||||
boardIds: {$in: [Session.get('currentBoard')]},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -103,7 +103,6 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
evt.preventDefault();
|
||||
|
||||
const data = {
|
||||
boardId: Session.get('currentBoard'),
|
||||
name: this.find('.js-field-name').value.trim(),
|
||||
type: this.type.get(),
|
||||
settings: this.getSettings(),
|
||||
@@ -114,6 +113,7 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
|
||||
// insert or update
|
||||
if (!this.data()._id) {
|
||||
data.boardIds = [Session.get('currentBoard')];
|
||||
CustomFields.insert(data);
|
||||
} else {
|
||||
CustomFields.update(this.data()._id, {$set: data});
|
||||
@@ -122,8 +122,16 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-delete-custom-field': Popup.afterConfirm('deleteCustomField', function() {
|
||||
const customFieldId = this._id;
|
||||
CustomFields.remove(customFieldId);
|
||||
const customField = CustomFields.findOne(this._id);
|
||||
if (customField.boardIds.length > 1) {
|
||||
CustomFields.update(customField._id, {
|
||||
$pull: {
|
||||
boardIds: Session.get('currentBoard'),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
CustomFields.remove(customField._id);
|
||||
}
|
||||
Popup.close();
|
||||
}),
|
||||
}];
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
template(name="miniswimlane")
|
||||
.minicard(
|
||||
class="minicard-{{colorClass}}")
|
||||
.minicard-title
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
+viewer
|
||||
= title
|
||||
@@ -1,14 +1,21 @@
|
||||
template(name="swimlaneHeader")
|
||||
.swimlane-header-wrap.js-swimlane-header
|
||||
+inlinedForm
|
||||
+editSwimlaneTitleForm
|
||||
.swimlane-header-wrap.js-swimlane-header(class='{{#if colorClass}}swimlane-{{colorClass}}{{/if}}')
|
||||
if this.isTemplateContainer
|
||||
+swimlaneFixedHeader(this)
|
||||
else
|
||||
.swimlane-header(
|
||||
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
|
||||
= title
|
||||
.swimlane-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
a.fa.fa-navicon.js-open-swimlane-menu
|
||||
+inlinedForm
|
||||
+editSwimlaneTitleForm
|
||||
else
|
||||
+swimlaneFixedHeader(this)
|
||||
|
||||
template(name="swimlaneFixedHeader")
|
||||
.swimlane-header(
|
||||
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
|
||||
= title
|
||||
.swimlane-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon
|
||||
a.fa.fa-navicon.js-open-swimlane-menu
|
||||
|
||||
template(name="editSwimlaneTitleForm")
|
||||
.list-composer
|
||||
@@ -20,4 +27,30 @@ template(name="editSwimlaneTitleForm")
|
||||
template(name="swimlaneActionPopup")
|
||||
unless currentUser.isCommentOnly
|
||||
ul.pop-over-list
|
||||
li: a.js-close-swimlane {{_ 'archive-swimlane'}}
|
||||
li: a.js-set-swimlane-color {{_ 'select-color'}}
|
||||
unless this.isTemplateContainer
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-close-swimlane {{_ 'archive-swimlane'}}
|
||||
|
||||
template(name="swimlaneAddPopup")
|
||||
unless currentUser.isCommentOnly
|
||||
form
|
||||
input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}"
|
||||
autocomplete="off" autofocus)
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'add'}}
|
||||
unless currentBoard.isTemplatesBoard
|
||||
unless currentBoard.isTemplateBoard
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-swimlane-template {{_ 'template'}}
|
||||
|
||||
template(name="setSwimlaneColorPopup")
|
||||
form.edit-label
|
||||
.palette-colors: each colors
|
||||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
|
||||
@@ -1,3 +1,10 @@
|
||||
const { calculateIndexData } = Utils;
|
||||
|
||||
let swimlaneColors;
|
||||
Meteor.startup(() => {
|
||||
swimlaneColors = Swimlanes.simpleSchema()._schema.color.allowedValues;
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
editTitle(evt) {
|
||||
evt.preventDefault();
|
||||
@@ -11,15 +18,85 @@ BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [{
|
||||
'click .js-open-swimlane-menu': Popup.open('swimlaneAction'),
|
||||
'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'),
|
||||
submit: this.editTitle,
|
||||
}];
|
||||
},
|
||||
}).register('swimlaneHeader');
|
||||
|
||||
Template.swimlaneActionPopup.events({
|
||||
'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'),
|
||||
'click .js-close-swimlane' (evt) {
|
||||
evt.preventDefault();
|
||||
this.archive();
|
||||
Popup.close();
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentSwimlane = this.currentData();
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
submit(evt) {
|
||||
evt.preventDefault();
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
const nextSwimlane = currentBoard.nextSwimlane(this.currentSwimlane);
|
||||
const titleInput = this.find('.swimlane-name-input');
|
||||
const title = titleInput.value.trim();
|
||||
const sortValue = calculateIndexData(this.currentSwimlane, nextSwimlane, 1);
|
||||
const swimlaneType = (currentBoard.isTemplatesBoard())?'template-swimlane':'swimlane';
|
||||
|
||||
if (title) {
|
||||
Swimlanes.insert({
|
||||
title,
|
||||
boardId: Session.get('currentBoard'),
|
||||
sort: sortValue.base,
|
||||
type: swimlaneType,
|
||||
});
|
||||
|
||||
titleInput.value = '';
|
||||
titleInput.focus();
|
||||
}
|
||||
// XXX ideally, we should move the popup to the newly
|
||||
// created swimlane so a user can add more than one swimlane
|
||||
// with a minimum of interactions
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-swimlane-template': Popup.open('searchElement'),
|
||||
}];
|
||||
},
|
||||
}).register('swimlaneAddPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentSwimlane = this.currentData();
|
||||
this.currentColor = new ReactiveVar(this.currentSwimlane.color);
|
||||
},
|
||||
|
||||
colors() {
|
||||
return swimlaneColors.map((color) => ({ color, name: '' }));
|
||||
},
|
||||
|
||||
isSelected(color) {
|
||||
return this.currentColor.get() === color;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'click .js-palette-color'() {
|
||||
this.currentColor.set(this.currentData().color);
|
||||
},
|
||||
'click .js-submit' () {
|
||||
this.currentSwimlane.setColor(this.currentColor.get());
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-remove-color'() {
|
||||
this.currentSwimlane.setColor(null);
|
||||
Popup.close();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('setSwimlaneColorPopup');
|
||||
@@ -1,21 +1,22 @@
|
||||
template(name="swimlane")
|
||||
.swimlane.js-lists.js-swimlane
|
||||
.swimlane
|
||||
+swimlaneHeader
|
||||
.swimlane.js-lists.js-swimlane
|
||||
if isMiniScreen
|
||||
if currentList
|
||||
if currentListIsInThisSwimlane _id
|
||||
+list(currentList)
|
||||
else
|
||||
each currentBoard.lists
|
||||
unless currentList
|
||||
each lists
|
||||
+miniList(this)
|
||||
if currentUser.isBoardMember
|
||||
+addListForm
|
||||
else
|
||||
each currentBoard.lists
|
||||
each lists
|
||||
+list(this)
|
||||
if currentCardIsInThisList _id ../_id
|
||||
+cardDetails(currentCard)
|
||||
if currentUser.isBoardMember
|
||||
+addListAndSwimlaneForm
|
||||
+addListForm
|
||||
|
||||
template(name="listsGroup")
|
||||
.swimlane.list-group.js-lists
|
||||
@@ -23,52 +24,31 @@ template(name="listsGroup")
|
||||
if currentList
|
||||
+list(currentList)
|
||||
else
|
||||
each currentBoard.lists
|
||||
each lists
|
||||
+miniList(this)
|
||||
if currentUser.isBoardMember
|
||||
+addListForm
|
||||
else
|
||||
each currentBoard.lists
|
||||
each lists
|
||||
+list(this)
|
||||
if currentCardIsInThisList _id null
|
||||
+cardDetails(currentCard)
|
||||
if currentUser.isBoardMember
|
||||
+addListForm
|
||||
|
||||
template(name="addListAndSwimlaneForm")
|
||||
.list.list-composer.js-list-composer
|
||||
.list-header
|
||||
+inlinedForm(autoclose=false)
|
||||
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
|
||||
autocomplete="off" autofocus)
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'save'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
a.open-list-composer.js-open-inlined-form
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-list'}}
|
||||
.list-header
|
||||
+inlinedForm(autoclose=false)
|
||||
input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}"
|
||||
autocomplete="off" autofocus)
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'save'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
a.open-list-composer.js-open-inlined-form
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-swimlane'}}
|
||||
|
||||
template(name="addListForm")
|
||||
.list.list-composer.js-list-composer
|
||||
.list-header
|
||||
.list-header-add
|
||||
+inlinedForm(autoclose=false)
|
||||
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
|
||||
autocomplete="off" autofocus)
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'save'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
unless currentBoard.isTemplatesBoard
|
||||
unless currentBoard.isTemplateBoard
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-list-template {{_ 'template'}}
|
||||
else
|
||||
a.open-list-composer.js-open-inlined-form
|
||||
i.fa.fa-plus
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
const { calculateIndex, enableClickOnTouch } = Utils;
|
||||
|
||||
function currentListIsInThisSwimlane(swimlaneId) {
|
||||
const currentList = Lists.findOne(Session.get('currentList'));
|
||||
return currentList && (currentList.swimlaneId === swimlaneId || currentList.swimlaneId === '');
|
||||
}
|
||||
|
||||
function currentCardIsInThisList(listId, swimlaneId) {
|
||||
const currentCard = Cards.findOne(Session.get('currentCard'));
|
||||
const currentUser = Meteor.user();
|
||||
if (currentUser.profile.boardView === 'board-view-lists')
|
||||
return currentCard && currentCard.listId === listId;
|
||||
else if (currentUser.profile.boardView === 'board-view-swimlanes')
|
||||
if (currentUser && currentUser.profile.boardView === 'board-view-swimlanes')
|
||||
return currentCard && currentCard.listId === listId && currentCard.swimlaneId === swimlaneId;
|
||||
else if (currentUser.profile.boardView === 'board-view-cal')
|
||||
return currentCard;
|
||||
else
|
||||
return false;
|
||||
else // Default view: board-view-lists
|
||||
return currentCard && currentCard.listId === listId;
|
||||
// https://github.com/wekan/wekan/issues/1623
|
||||
// https://github.com/ChronikEwok/wekan/commit/cad9b20451bb6149bfb527a99b5001873b06c3de
|
||||
// TODO: In public board, if you would like to switch between List/Swimlane view, you could
|
||||
// 1) If there is no view cookie, save to cookie board-view-lists
|
||||
// board-view-lists / board-view-swimlanes / board-view-cal
|
||||
// 2) If public user changes clicks board-view-lists then change view and
|
||||
// then change view and save cookie with view value
|
||||
// without using currentuser above, because currentuser is null.
|
||||
}
|
||||
|
||||
function initSortable(boardComponent, $listsDom) {
|
||||
@@ -110,6 +119,10 @@ BlazeComponent.extendComponent({
|
||||
return currentCardIsInThisList(listId, swimlaneId);
|
||||
},
|
||||
|
||||
currentListIsInThisSwimlane(swimlaneId) {
|
||||
return currentListIsInThisSwimlane(swimlaneId);
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
// Click-and-drag action
|
||||
@@ -149,6 +162,12 @@ BlazeComponent.extendComponent({
|
||||
}).register('swimlane');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
this.isListTemplatesSwimlane = this.currentBoard.isTemplatesBoard() && this.currentData().isListTemplatesSwimlane();
|
||||
this.currentSwimlane = this.currentData();
|
||||
},
|
||||
|
||||
// Proxy
|
||||
open() {
|
||||
this.childComponents('inlinedForm')[0].open();
|
||||
@@ -165,58 +184,19 @@ BlazeComponent.extendComponent({
|
||||
title,
|
||||
boardId: Session.get('currentBoard'),
|
||||
sort: $('.list').length,
|
||||
type: (this.isListTemplatesSwimlane)?'template-list':'list',
|
||||
swimlaneId: (this.currentBoard.isTemplatesBoard())?this.currentSwimlane._id:'',
|
||||
});
|
||||
|
||||
titleInput.value = '';
|
||||
titleInput.focus();
|
||||
}
|
||||
},
|
||||
'click .js-list-template': Popup.open('searchElement'),
|
||||
}];
|
||||
},
|
||||
}).register('addListForm');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
// Proxy
|
||||
open() {
|
||||
this.childComponents('inlinedForm')[0].open();
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
submit(evt) {
|
||||
evt.preventDefault();
|
||||
let titleInput = this.find('.list-name-input');
|
||||
if (titleInput) {
|
||||
const title = titleInput.value.trim();
|
||||
if (title) {
|
||||
Lists.insert({
|
||||
title,
|
||||
boardId: Session.get('currentBoard'),
|
||||
sort: $('.list').length,
|
||||
});
|
||||
|
||||
titleInput.value = '';
|
||||
titleInput.focus();
|
||||
}
|
||||
} else {
|
||||
titleInput = this.find('.swimlane-name-input');
|
||||
const title = titleInput.value.trim();
|
||||
if (title) {
|
||||
Swimlanes.insert({
|
||||
title,
|
||||
boardId: Session.get('currentBoard'),
|
||||
sort: $('.swimlane').length,
|
||||
});
|
||||
|
||||
titleInput.value = '';
|
||||
titleInput.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('addListAndSwimlaneForm');
|
||||
|
||||
Template.swimlane.helpers({
|
||||
canSeeAddList() {
|
||||
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
display: flex
|
||||
flex-direction: row
|
||||
overflow: 0;
|
||||
max-height: 100%
|
||||
max-height: calc(100% - 26px)
|
||||
|
||||
&.placeholder
|
||||
background-color: rgba(0, 0, 0, .2)
|
||||
@@ -27,20 +27,15 @@
|
||||
.swimlane-header-wrap
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 0 0 50px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid #CCC
|
||||
flex: 1 0 100%;
|
||||
background-color: #ccc;
|
||||
|
||||
.swimlane-header
|
||||
-ms-writing-mode: tb-rl;
|
||||
writing-mode: vertical-rl;
|
||||
transform: rotate(180deg);
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-top: 50px;
|
||||
padding: 5px 5px
|
||||
font-weight: bold;
|
||||
min-height: 9px;
|
||||
width: 50px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
@@ -49,7 +44,95 @@
|
||||
|
||||
.swimlane-header-menu
|
||||
position: absolute
|
||||
padding: 20px 20px
|
||||
padding: 5px 5px
|
||||
|
||||
.swimlane-header-plus-icon
|
||||
margin-left: 5px
|
||||
margin-right: 10px
|
||||
|
||||
.list-group
|
||||
height: 100%
|
||||
|
||||
.moving-swimlane
|
||||
display: none
|
||||
|
||||
swimlane-color(background, color...)
|
||||
background: background !important
|
||||
if color
|
||||
color: color !important //overwrite text for better visibility
|
||||
|
||||
.swimlane-white
|
||||
swimlane-color(#ffffff, #4d4d4d) //Black text for better visibility
|
||||
border: 1px solid #eee
|
||||
|
||||
.swimlane-green
|
||||
swimlane-color(#3cb500, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-yellow
|
||||
swimlane-color(#fad900, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-orange
|
||||
swimlane-color(#ff9f19, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-red
|
||||
swimlane-color(#eb4646, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-purple
|
||||
swimlane-color(#a632db, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-blue
|
||||
swimlane-color(#0079bf, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-pink
|
||||
swimlane-color(#ff78cb, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-sky
|
||||
swimlane-color(#00c2e0, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-black
|
||||
swimlane-color(#4d4d4d, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-lime
|
||||
swimlane-color(#51e898, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-silver
|
||||
swimlane-color(unset, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-peachpuff
|
||||
swimlane-color(#ffdab9, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-crimson
|
||||
swimlane-color(#dc143c, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-plum
|
||||
swimlane-color(#dda0dd, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-darkgreen
|
||||
swimlane-color(#006400, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-slateblue
|
||||
swimlane-color(#6a5acd, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-magenta
|
||||
swimlane-color(#ff00ff, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-gold
|
||||
swimlane-color(#ffd700, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-navy
|
||||
swimlane-color(#000080, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-gray
|
||||
swimlane-color(#808080, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-saddlebrown
|
||||
swimlane-color(#8b4513, #ffffff) //White text for better visibility
|
||||
|
||||
.swimlane-paleturquoise
|
||||
swimlane-color(#afeeee, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-mistyrose
|
||||
swimlane-color(#ffe4e1, #4d4d4d) //Black text for better visibility
|
||||
|
||||
.swimlane-indigo
|
||||
swimlane-color(#4b0082, #ffffff) //White text for better visibility
|
||||
@@ -4,10 +4,11 @@ template(name="headerUserBar")
|
||||
.header-user-bar-avatar
|
||||
+userAvatar(userId=currentUser._id)
|
||||
unless isMiniScreen
|
||||
if currentUser.profile.fullname
|
||||
= currentUser.profile.fullname
|
||||
else
|
||||
= currentUser.username
|
||||
unless isSandstorm
|
||||
if currentUser.profile.fullname
|
||||
= currentUser.profile.fullname
|
||||
else
|
||||
= currentUser.username
|
||||
|
||||
template(name="memberMenuPopup")
|
||||
ul.pop-over-list
|
||||
@@ -15,13 +16,18 @@ template(name="memberMenuPopup")
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
li: a.js-change-settings {{_ 'change-settings'}}
|
||||
li: a.js-change-avatar {{_ 'edit-avatar'}}
|
||||
li: a.js-change-password {{_ 'changePasswordPopup-title'}}
|
||||
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
|
||||
unless isSandstorm
|
||||
li: a.js-change-password {{_ 'changePasswordPopup-title'}}
|
||||
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
|
||||
if currentUser.isAdmin
|
||||
li: a.js-go-setting(href="{{pathFor 'setting'}}") {{_ 'admin-panel'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-logout {{_ 'log-out'}}
|
||||
li: a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") {{_ 'templates'}}
|
||||
unless isSandstorm
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-logout {{_ 'log-out'}}
|
||||
|
||||
template(name="editProfilePopup")
|
||||
form
|
||||
|
||||
@@ -3,6 +3,15 @@ Template.headerUserBar.events({
|
||||
'click .js-change-avatar': Popup.open('changeAvatar'),
|
||||
});
|
||||
|
||||
Template.memberMenuPopup.helpers({
|
||||
templatesBoardId() {
|
||||
return Meteor.user().getTemplatesBoardId();
|
||||
},
|
||||
templatesBoardSlug() {
|
||||
return Meteor.user().getTemplatesBoardSlug();
|
||||
},
|
||||
});
|
||||
|
||||
Template.memberMenuPopup.events({
|
||||
'click .js-edit-profile': Popup.open('editProfile'),
|
||||
'click .js-change-settings': Popup.open('changeSettings'),
|
||||
|
||||
+5
-7
@@ -4,9 +4,9 @@ window.Popup = new class {
|
||||
this.template = Template.popup;
|
||||
|
||||
// We only want to display one popup at a time and we keep the view object
|
||||
// in this `Popup._current` variable. If there is no popup currently opened
|
||||
// in this `Popup.current` variable. If there is no popup currently opened
|
||||
// the value is `null`.
|
||||
this._current = null;
|
||||
this.current = null;
|
||||
|
||||
// It's possible to open a sub-popup B from a popup A. In that case we keep
|
||||
// the data of popup A so we can return back to it. Every time we open a new
|
||||
@@ -27,11 +27,9 @@ window.Popup = new class {
|
||||
open(name) {
|
||||
const self = this;
|
||||
const popupName = `${name}Popup`;
|
||||
|
||||
function clickFromPopup(evt) {
|
||||
return $(evt.target).closest('.js-pop-over').length !== 0;
|
||||
}
|
||||
|
||||
return function(evt) {
|
||||
// If a popup is already opened, clicking again on the opener element
|
||||
// should close it -- and interrupt the current `open` function.
|
||||
@@ -57,7 +55,6 @@ window.Popup = new class {
|
||||
self._stack = [];
|
||||
openerElement = evt.currentTarget;
|
||||
}
|
||||
|
||||
$(openerElement).addClass('is-active');
|
||||
evt.preventDefault();
|
||||
|
||||
@@ -139,6 +136,7 @@ window.Popup = new class {
|
||||
const openerElement = this._getTopStack().openerElement;
|
||||
$(openerElement).removeClass('is-active');
|
||||
|
||||
|
||||
this._stack = [];
|
||||
}
|
||||
}
|
||||
@@ -186,7 +184,7 @@ window.Popup = new class {
|
||||
// positives.
|
||||
const title = TAPi18n.__(translationKey);
|
||||
// when popup showed as full of small screen, we need a default header to clearly see [X] button
|
||||
const defaultTitle = Utils.isMiniScreen() ? 'Wekan' : false;
|
||||
const defaultTitle = Utils.isMiniScreen() ? '' : false;
|
||||
return title !== translationKey ? title : defaultTitle;
|
||||
};
|
||||
}
|
||||
@@ -200,7 +198,7 @@ escapeActions.forEach((actionName) => {
|
||||
() => Popup[actionName](),
|
||||
() => Popup.isOpen(),
|
||||
{
|
||||
noClickEscapeOn: '.js-pop-over',
|
||||
noClickEscapeOn: '.js-pop-over,.js-open-card-title-popup',
|
||||
enabledOnClick: actionName === 'close',
|
||||
}
|
||||
);
|
||||
|
||||
+31
-1
@@ -145,6 +145,26 @@ Utils = {
|
||||
});
|
||||
},
|
||||
|
||||
manageCustomUI(){
|
||||
Meteor.call('getCustomUI', (err, data) => {
|
||||
if (err && err.error[0] === 'var-not-exist'){
|
||||
Session.set('customUI', false); // siteId || address server not defined
|
||||
}
|
||||
if (!err){
|
||||
Utils.setCustomUI(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setCustomUI(data){
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
if (currentBoard) {
|
||||
DocHead.setTitle(`${currentBoard.title } - ${ data.productName}`);
|
||||
} else {
|
||||
DocHead.setTitle(`${data.productName}`);
|
||||
}
|
||||
},
|
||||
|
||||
setMatomo(data){
|
||||
window._paq = window._paq || [];
|
||||
window._paq.push(['setDoNotTrack', data.doNotTrack]);
|
||||
@@ -198,10 +218,20 @@ Utils = {
|
||||
const element = tempInstance.$(triggerEls[i]);
|
||||
if (element.hasClass('trigger-text')) {
|
||||
finalString += element.text().toLowerCase();
|
||||
} else if (element.hasClass('user-details')) {
|
||||
let username = element.find('input').val();
|
||||
if(username === undefined || username === ''){
|
||||
username = '*';
|
||||
}
|
||||
finalString += `${element.find('.trigger-text').text().toLowerCase() } ${ username}`;
|
||||
} else if (element.find('select').length > 0) {
|
||||
finalString += element.find('select option:selected').text().toLowerCase();
|
||||
} else if (element.find('input').length > 0) {
|
||||
finalString += element.find('input').val();
|
||||
let inputvalue = element.find('input').val();
|
||||
if(inputvalue === undefined || inputvalue === ''){
|
||||
inputvalue = '*';
|
||||
}
|
||||
finalString += inputvalue;
|
||||
}
|
||||
// Add space
|
||||
if (i !== length - 1) {
|
||||
|
||||
+16
-11
@@ -14,6 +14,7 @@ FlowRouter.route('/', {
|
||||
Filter.reset();
|
||||
EscapeActions.executeAll();
|
||||
|
||||
Utils.manageCustomUI();
|
||||
Utils.manageMatomo();
|
||||
|
||||
BlazeLayout.render('defaultLayout', {
|
||||
@@ -40,6 +41,7 @@ FlowRouter.route('/b/:id/:slug', {
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
}
|
||||
|
||||
Utils.manageCustomUI();
|
||||
Utils.manageMatomo();
|
||||
|
||||
BlazeLayout.render('defaultLayout', {
|
||||
@@ -57,6 +59,7 @@ FlowRouter.route('/b/:boardId/:slug/:cardId', {
|
||||
Session.set('currentBoard', params.boardId);
|
||||
Session.set('currentCard', params.cardId);
|
||||
|
||||
Utils.manageCustomUI();
|
||||
Utils.manageMatomo();
|
||||
|
||||
BlazeLayout.render('defaultLayout', {
|
||||
@@ -122,6 +125,7 @@ FlowRouter.route('/setting', {
|
||||
},
|
||||
],
|
||||
action() {
|
||||
Utils.manageCustomUI();
|
||||
BlazeLayout.render('defaultLayout', {
|
||||
headerBar: 'settingHeaderBar',
|
||||
content: 'setting',
|
||||
@@ -199,20 +203,21 @@ _.each(redirections, (newPath, oldPath) => {
|
||||
// using the `kadira:dochead` package. Currently we only use it to display the
|
||||
// board title if we are in a board page (see #364) but we may want to support
|
||||
// some <meta> tags in the future.
|
||||
const appTitle = 'Wekan';
|
||||
//const appTitle = Utils.manageCustomUI();
|
||||
|
||||
// XXX The `Meteor.startup` should not be necessary -- we don't need to wait for
|
||||
// the complete DOM to be ready to call `DocHead.setTitle`. But the problem is
|
||||
// that the global variable `Boards` is undefined when this file loads so we
|
||||
// wait a bit until hopefully all files are loaded. This will be fixed in a
|
||||
// clean way once Meteor will support ES6 modules -- hopefully in Meteor 1.3.
|
||||
Meteor.isClient && Meteor.startup(() => {
|
||||
Tracker.autorun(() => {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
const titleStack = [appTitle];
|
||||
if (currentBoard) {
|
||||
titleStack.push(currentBoard.title);
|
||||
}
|
||||
DocHead.setTitle(titleStack.reverse().join(' - '));
|
||||
});
|
||||
});
|
||||
//Meteor.isClient && Meteor.startup(() => {
|
||||
// Tracker.autorun(() => {
|
||||
|
||||
// const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
// const titleStack = [appTitle];
|
||||
// if (currentBoard) {
|
||||
// titleStack.push(currentBoard.title);
|
||||
// }
|
||||
// DocHead.setTitle(titleStack.reverse().join(' - '));
|
||||
// });
|
||||
//});
|
||||
@@ -1,238 +0,0 @@
|
||||
version: '2'
|
||||
|
||||
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
|
||||
# 1) Edit settings
|
||||
# 2) docker-compose up -d
|
||||
|
||||
services:
|
||||
|
||||
wekandb:
|
||||
image: mongo:3.2.21
|
||||
container_name: wekan-db
|
||||
restart: always
|
||||
command: mongod --smallfiles --oplogSize 128
|
||||
networks:
|
||||
- wekan-tier
|
||||
expose:
|
||||
- 27017
|
||||
volumes:
|
||||
- wekan-db:/data/db
|
||||
- wekan-db-dump:/dump
|
||||
|
||||
wekan:
|
||||
image: quay.io/wekan/wekan
|
||||
container_name: wekan-app
|
||||
restart: always
|
||||
networks:
|
||||
- wekan-tier
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- NODE_VERSION=${NODE_VERSION}
|
||||
- METEOR_RELEASE=${METEOR_RELEASE}
|
||||
- NPM_VERSION=${NPM_VERSION}
|
||||
- ARCHITECTURE=${ARCHITECTURE}
|
||||
- SRC_PATH=${SRC_PATH}
|
||||
- METEOR_EDGE=${METEOR_EDGE}
|
||||
- USE_EDGE=${USE_EDGE}
|
||||
ports:
|
||||
# Docker outsideport:insideport
|
||||
- 80:8080
|
||||
environment:
|
||||
- MONGO_URL=mongodb://wekandb:27017/wekan
|
||||
- ROOT_URL=http://localhost
|
||||
# Wekan Export Board works when WITH_API=true.
|
||||
# If you disable Wekan API with false, Export Board does not work.
|
||||
- WITH_API=true
|
||||
# CORS: Set Access-Control-Allow-Origin header. Example: *
|
||||
#- CORS=*
|
||||
# Optional: Integration with Matomo https://matomo.org that is installed to your server
|
||||
# The address of the server where Matomo is hosted.
|
||||
# example: - MATOMO_ADDRESS=https://example.com/matomo
|
||||
#- MATOMO_ADDRESS=
|
||||
# The value of the site ID given in Matomo server for Wekan
|
||||
# example: - MATOMO_SITE_ID=12345
|
||||
#- MATOMO_SITE_ID=
|
||||
# The option do not track which enables users to not be tracked by matomo
|
||||
# example: - MATOMO_DO_NOT_TRACK=false
|
||||
#- MATOMO_DO_NOT_TRACK=
|
||||
# The option that allows matomo to retrieve the username:
|
||||
# example: MATOMO_WITH_USERNAME=true
|
||||
#- MATOMO_WITH_USERNAME=false
|
||||
# Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
|
||||
# Setting this to false is not recommended, it also disables all other browser policy protections
|
||||
# and allows all iframing etc. See wekan/server/policy.js
|
||||
- BROWSER_POLICY_ENABLED=true
|
||||
# When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
|
||||
#- TRUSTED_URL=
|
||||
# What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId .
|
||||
# example: WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId
|
||||
#- WEBHOOKS_ATTRIBUTES=
|
||||
# Enable the OAuth2 connection
|
||||
# example: OAUTH2_ENABLED=true
|
||||
#- OAUTH2_ENABLED=false
|
||||
# OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2
|
||||
# OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345
|
||||
# example: OAUTH2_CLIENT_ID=abcde12345
|
||||
#- OAUTH2_CLIENT_ID=
|
||||
# OAuth2 Secret, for example from Rocket.Chat: Example: 54321abcde
|
||||
# example: OAUTH2_SECRET=54321abcde
|
||||
#- OAUTH2_SECRET=
|
||||
# OAuth2 Server URL, for example Rocket.Chat. Example: https://chat.example.com
|
||||
# example: OAUTH2_SERVER_URL=https://chat.example.com
|
||||
#- OAUTH2_SERVER_URL=
|
||||
# OAuth2 Authorization Endpoint. Example: /oauth/authorize
|
||||
# example: OAUTH2_AUTH_ENDPOINT=/oauth/authorize
|
||||
#- OAUTH2_AUTH_ENDPOINT=
|
||||
# OAuth2 Userinfo Endpoint. Example: /oauth/userinfo
|
||||
# example: OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
|
||||
#- OAUTH2_USERINFO_ENDPOINT=
|
||||
# OAuth2 Token Endpoint. Example: /oauth/token
|
||||
# example: OAUTH2_TOKEN_ENDPOINT=/oauth/token
|
||||
#- OAUTH2_TOKEN_ENDPOINT=
|
||||
# LDAP_ENABLE : Enable or not the connection by the LDAP
|
||||
# example : LDAP_ENABLE=true
|
||||
#- LDAP_ENABLE=false
|
||||
# LDAP_PORT : The port of the LDAP server
|
||||
# example : LDAP_PORT=389
|
||||
#- LDAP_PORT=389
|
||||
# LDAP_HOST : The host server for the LDAP server
|
||||
# example : LDAP_HOST=localhost
|
||||
#- LDAP_HOST=
|
||||
# LDAP_BASEDN : The base DN for the LDAP Tree
|
||||
# example : LDAP_BASEDN=ou=user,dc=example,dc=org
|
||||
#- LDAP_BASEDN=
|
||||
# LDAP_LOGIN_FALLBACK : Fallback on the default authentication method
|
||||
# example : LDAP_LOGIN_FALLBACK=true
|
||||
#- LDAP_LOGIN_FALLBACK=false
|
||||
# LDAP_RECONNECT : Reconnect to the server if the connection is lost
|
||||
# example : LDAP_RECONNECT=false
|
||||
#- LDAP_RECONNECT=true
|
||||
# LDAP_TIMEOUT : Overall timeout, in milliseconds
|
||||
# example : LDAP_TIMEOUT=12345
|
||||
#- LDAP_TIMEOUT=10000
|
||||
# LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds
|
||||
# example : LDAP_IDLE_TIMEOUT=12345
|
||||
#- LDAP_IDLE_TIMEOUT=10000
|
||||
# LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds
|
||||
# example : LDAP_CONNECT_TIMEOUT=12345
|
||||
#- LDAP_CONNECT_TIMEOUT=10000
|
||||
# LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search
|
||||
# example : LDAP_AUTHENTIFICATION=true
|
||||
#- LDAP_AUTHENTIFICATION=false
|
||||
# LDAP_AUTHENTIFICATION_USERDN : The search user DN
|
||||
# example : LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=
|
||||
# LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user
|
||||
# example : AUTHENTIFICATION_PASSWORD=admin
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD=
|
||||
# LDAP_LOG_ENABLED : Enable logs for the module
|
||||
# example : LDAP_LOG_ENABLED=true
|
||||
#- LDAP_LOG_ENABLED=false
|
||||
# LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background
|
||||
# example : LDAP_BACKGROUND_SYNC=true
|
||||
#- LDAP_BACKGROUND_SYNC=false
|
||||
# LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds
|
||||
# example : LDAP_BACKGROUND_SYNC_INTERVAL=12345
|
||||
#- LDAP_BACKGROUND_SYNC_INTERVAL=100
|
||||
# LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED :
|
||||
# example : LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true
|
||||
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
|
||||
# LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS :
|
||||
# example : LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true
|
||||
#- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
|
||||
# LDAP_ENCRYPTION : If using LDAPS
|
||||
# example : LDAP_ENCRYPTION=ssl
|
||||
#- LDAP_ENCRYPTION=false
|
||||
# LDAP_CA_CERT : The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
|
||||
# example : LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE-----
|
||||
#- LDAP_CA_CERT=
|
||||
# LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
|
||||
# example : LDAP_REJECT_UNAUTHORIZED=true
|
||||
#- LDAP_REJECT_UNAUTHORIZED=false
|
||||
# LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
# example : LDAP_USER_SEARCH_FILTER=
|
||||
#- LDAP_USER_SEARCH_FILTER=
|
||||
# LDAP_USER_SEARCH_SCOPE : base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
|
||||
# example : LDAP_USER_SEARCH_SCOPE=one
|
||||
#- LDAP_USER_SEARCH_SCOPE=
|
||||
# LDAP_USER_SEARCH_FIELD : Which field is used to find the user
|
||||
# example : LDAP_USER_SEARCH_FIELD=uid
|
||||
#- LDAP_USER_SEARCH_FIELD=
|
||||
# LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited)
|
||||
# example : LDAP_SEARCH_PAGE_SIZE=12345
|
||||
#- LDAP_SEARCH_PAGE_SIZE=0
|
||||
# LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited)
|
||||
# example : LDAP_SEARCH_SIZE_LIMIT=12345
|
||||
#- LDAP_SEARCH_SIZE_LIMIT=0
|
||||
# LDAP_GROUP_FILTER_ENABLE : Enable group filtering
|
||||
# example : LDAP_GROUP_FILTER_ENABLE=true
|
||||
#- LDAP_GROUP_FILTER_ENABLE=false
|
||||
# LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering
|
||||
# example : LDAP_GROUP_FILTER_OBJECTCLASS=group
|
||||
#- LDAP_GROUP_FILTER_OBJECTCLASS=
|
||||
# LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE :
|
||||
# example :
|
||||
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE :
|
||||
# example :
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT :
|
||||
# example :
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
|
||||
# LDAP_GROUP_FILTER_GROUP_NAME :
|
||||
# example :
|
||||
#- LDAP_GROUP_FILTER_GROUP_NAME=
|
||||
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier)
|
||||
# example : LDAP_UNIQUE_IDENTIFIER_FIELD=guid
|
||||
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
|
||||
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
|
||||
# example : LDAP_UTF8_NAMES_SLUGIFY=false
|
||||
#- LDAP_UTF8_NAMES_SLUGIFY=true
|
||||
# LDAP_USERNAME_FIELD : Which field contains the ldap username
|
||||
# example : LDAP_USERNAME_FIELD=username
|
||||
#- LDAP_USERNAME_FIELD=
|
||||
# LDAP_FULLNAME_FIELD : Which field contains the ldap fullname
|
||||
# example : LDAP_FULLNAME_FIELD=fullname
|
||||
#- LDAP_FULLNAME_FIELD=
|
||||
# LDAP_MERGE_EXISTING_USERS :
|
||||
# example : LDAP_MERGE_EXISTING_USERS=true
|
||||
#- LDAP_MERGE_EXISTING_USERS=false
|
||||
# LDAP_SYNC_USER_DATA :
|
||||
# example : LDAP_SYNC_USER_DATA=true
|
||||
#- LDAP_SYNC_USER_DATA=false
|
||||
# LDAP_SYNC_USER_DATA_FIELDMAP :
|
||||
# example : LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
|
||||
#- LDAP_SYNC_USER_DATA_FIELDMAP=
|
||||
# LDAP_SYNC_GROUP_ROLES :
|
||||
# example :
|
||||
#- LDAP_SYNC_GROUP_ROLES=
|
||||
# LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
|
||||
# example :
|
||||
#- LDAP_DEFAULT_DOMAIN=
|
||||
# LOGOUT_WITH_TIMER : Enables or not the option logout with timer
|
||||
# example : LOGOUT_WITH_TIMER=true
|
||||
#- LOGOUT_WITH_TIMER=
|
||||
# LOGOUT_IN : The number of days
|
||||
# example : LOGOUT_IN=1
|
||||
#- LOGOUT_IN=
|
||||
# LOGOUT_ON_HOURS : The number of hours
|
||||
# example : LOGOUT_ON_HOURS=9
|
||||
#- LOGOUT_ON_HOURS=
|
||||
# LOGOUT_ON_MINUTES : The number of minutes
|
||||
# example : LOGOUT_ON_MINUTES=55
|
||||
#- LOGOUT_ON_MINUTES=
|
||||
|
||||
depends_on:
|
||||
- wekandb
|
||||
|
||||
volumes:
|
||||
wekan-db:
|
||||
driver: local
|
||||
wekan-db-dump:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
wekan-tier:
|
||||
driver: bridge
|
||||
@@ -1,260 +0,0 @@
|
||||
version: '2'
|
||||
|
||||
# Docker: Wekan <=> MongoDB <=> ToroDB => PostgreSQL read-only mirroring
|
||||
# for reporting with SQL, and accessing with any programming language,
|
||||
# reporting package and Office suite that can connect to PostgreSQL.
|
||||
# https://github.com/wekan/wekan-postgresql
|
||||
|
||||
services:
|
||||
torodb-stampede:
|
||||
image: torodb/stampede:1.0.0-SNAPSHOT
|
||||
networks:
|
||||
- wekan-tier
|
||||
links:
|
||||
- postgres
|
||||
- mongodb
|
||||
environment:
|
||||
- POSTGRES_PASSWORD
|
||||
- TORODB_SETUP=true
|
||||
- TORODB_SYNC_SOURCE=mongodb:27017
|
||||
- TORODB_BACKEND_HOST=postgres
|
||||
- TORODB_BACKEND_PORT=5432
|
||||
- TORODB_BACKEND_DATABASE=wekan
|
||||
- TORODB_BACKEND_USER=wekan
|
||||
- TORODB_BACKEND_PASSWORD=wekan
|
||||
- DEBUG
|
||||
postgres:
|
||||
image: postgres:9.6
|
||||
networks:
|
||||
- wekan-tier
|
||||
environment:
|
||||
- POSTGRES_PASSWORD
|
||||
ports:
|
||||
- "15432:5432"
|
||||
mongodb:
|
||||
image: mongo:3.2
|
||||
networks:
|
||||
- wekan-tier
|
||||
ports:
|
||||
- "28017:27017"
|
||||
entrypoint:
|
||||
- /bin/bash
|
||||
- "-c"
|
||||
- mongo --nodb --eval '
|
||||
var db;
|
||||
while (!db) {
|
||||
try {
|
||||
db = new Mongo("mongodb:27017").getDB("local");
|
||||
} catch(ex) {}
|
||||
sleep(3000);
|
||||
};
|
||||
rs.initiate({_id:"rs1",members:[{_id:0,host:"mongodb:27017"}]});
|
||||
' 1>/dev/null 2>&1 &
|
||||
mongod --replSet rs1
|
||||
wekan:
|
||||
image: quay.io/wekan/wekan
|
||||
container_name: wekan-app
|
||||
restart: always
|
||||
networks:
|
||||
- wekan-tier
|
||||
ports:
|
||||
- 80:8080
|
||||
environment:
|
||||
- MONGO_URL=mongodb://mongodb:27017/wekan
|
||||
- ROOT_URL=http://localhost
|
||||
#---------------------------------------------------------------
|
||||
# == WEKAN API ==
|
||||
# Wekan Export Board works when WITH_API='true'.
|
||||
# If you disable Wekan API, Export Board does not work.
|
||||
- WITH_API=true
|
||||
# CORS: Set Access-Control-Allow-Origin header. Example: *
|
||||
#- CORS=*
|
||||
# Optional: Integration with Matomo https://matomo.org that is installed to your server
|
||||
# The address of the server where Matomo is hosted.
|
||||
# example: - MATOMO_ADDRESS=https://example.com/matomo
|
||||
#- MATOMO_ADDRESS=
|
||||
# The value of the site ID given in Matomo server for Wekan
|
||||
# example: - MATOMO_SITE_ID=12345
|
||||
#- MATOMO_SITE_ID=
|
||||
# The option do not track which enables users to not be tracked by matomo
|
||||
# example: - MATOMO_DO_NOT_TRACK=false
|
||||
#- MATOMO_DO_NOT_TRACK=
|
||||
# The option that allows matomo to retrieve the username:
|
||||
# example: MATOMO_WITH_USERNAME=true
|
||||
#- MATOMO_WITH_USERNAME=false
|
||||
# Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
|
||||
# Setting this to false is not recommended, it also disables all other browser policy protections
|
||||
# and allows all iframing etc. See wekan/server/policy.js
|
||||
- BROWSER_POLICY_ENABLED=true
|
||||
# When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
|
||||
#- TRUSTED_URL=
|
||||
# What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId .
|
||||
# example: WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId
|
||||
#- WEBHOOKS_ATTRIBUTES=
|
||||
# Enable the OAuth2 connection
|
||||
# example: OAUTH2_ENABLED=true
|
||||
#- OAUTH2_ENABLED=false
|
||||
# OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2
|
||||
# OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345
|
||||
# example: OAUTH2_CLIENT_ID=abcde12345
|
||||
#- OAUTH2_CLIENT_ID=
|
||||
# OAuth2 Secret, for example from Rocket.Chat: Example: 54321abcde
|
||||
# example: OAUTH2_SECRET=54321abcde
|
||||
#- OAUTH2_SECRET=
|
||||
# OAuth2 Server URL, for example Rocket.Chat. Example: https://chat.example.com
|
||||
# example: OAUTH2_SERVER_URL=https://chat.example.com
|
||||
#- OAUTH2_SERVER_URL=
|
||||
# OAuth2 Authorization Endpoint. Example: /oauth/authorize
|
||||
# example: OAUTH2_AUTH_ENDPOINT=/oauth/authorize
|
||||
#- OAUTH2_AUTH_ENDPOINT=
|
||||
# OAuth2 Userinfo Endpoint. Example: /oauth/userinfo
|
||||
# example: OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
|
||||
#- OAUTH2_USERINFO_ENDPOINT=
|
||||
# OAuth2 Token Endpoint. Example: /oauth/token
|
||||
# example: OAUTH2_TOKEN_ENDPOINT=/oauth/token
|
||||
#- OAUTH2_TOKEN_ENDPOINT=
|
||||
# LDAP_ENABLE : Enable or not the connection by the LDAP
|
||||
# example : LDAP_ENABLE=true
|
||||
#- LDAP_ENABLE=false
|
||||
# LDAP_PORT : The port of the LDAP server
|
||||
# example : LDAP_PORT=389
|
||||
#- LDAP_PORT=389
|
||||
# LDAP_HOST : The host server for the LDAP server
|
||||
# example : LDAP_HOST=localhost
|
||||
#- LDAP_HOST=
|
||||
# LDAP_BASEDN : The base DN for the LDAP Tree
|
||||
# example : LDAP_BASEDN=ou=user,dc=example,dc=org
|
||||
#- LDAP_BASEDN=
|
||||
# LDAP_LOGIN_FALLBACK : Fallback on the default authentication method
|
||||
# example : LDAP_LOGIN_FALLBACK=true
|
||||
#- LDAP_LOGIN_FALLBACK=false
|
||||
# LDAP_RECONNECT : Reconnect to the server if the connection is lost
|
||||
# example : LDAP_RECONNECT=false
|
||||
#- LDAP_RECONNECT=true
|
||||
# LDAP_TIMEOUT : Overall timeout, in milliseconds
|
||||
# example : LDAP_TIMEOUT=12345
|
||||
#- LDAP_TIMEOUT=10000
|
||||
# LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds
|
||||
# example : LDAP_IDLE_TIMEOUT=12345
|
||||
#- LDAP_IDLE_TIMEOUT=10000
|
||||
# LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds
|
||||
# example : LDAP_CONNECT_TIMEOUT=12345
|
||||
#- LDAP_CONNECT_TIMEOUT=10000
|
||||
# LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search
|
||||
# example : LDAP_AUTHENTIFICATION=true
|
||||
#- LDAP_AUTHENTIFICATION=false
|
||||
# LDAP_AUTHENTIFICATION_USERDN : The search user DN
|
||||
# example : LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=
|
||||
# LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user
|
||||
# example : AUTHENTIFICATION_PASSWORD=admin
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD=
|
||||
# LDAP_LOG_ENABLED : Enable logs for the module
|
||||
# example : LDAP_LOG_ENABLED=true
|
||||
#- LDAP_LOG_ENABLED=false
|
||||
# LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background
|
||||
# example : LDAP_BACKGROUND_SYNC=true
|
||||
#- LDAP_BACKGROUND_SYNC=false
|
||||
# LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds
|
||||
# example : LDAP_BACKGROUND_SYNC_INTERVAL=12345
|
||||
#- LDAP_BACKGROUND_SYNC_INTERVAL=100
|
||||
# LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED :
|
||||
# example : LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true
|
||||
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
|
||||
# LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS :
|
||||
# example : LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true
|
||||
#- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
|
||||
# LDAP_ENCRYPTION : If using LDAPS
|
||||
# example : LDAP_ENCRYPTION=ssl
|
||||
#- LDAP_ENCRYPTION=false
|
||||
# LDAP_CA_CERT : The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
|
||||
# example : LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE-----
|
||||
#- LDAP_CA_CERT=
|
||||
# LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
|
||||
# example : LDAP_REJECT_UNAUTHORIZED=true
|
||||
#- LDAP_REJECT_UNAUTHORIZED=false
|
||||
# LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
# example : LDAP_USER_SEARCH_FILTER=
|
||||
#- LDAP_USER_SEARCH_FILTER=
|
||||
# LDAP_USER_SEARCH_SCOPE : base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
|
||||
# example : LDAP_USER_SEARCH_SCOPE=one
|
||||
#- LDAP_USER_SEARCH_SCOPE=
|
||||
# LDAP_USER_SEARCH_FIELD : Which field is used to find the user
|
||||
# example : LDAP_USER_SEARCH_FIELD=uid
|
||||
#- LDAP_USER_SEARCH_FIELD=
|
||||
# LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited)
|
||||
# example : LDAP_SEARCH_PAGE_SIZE=12345
|
||||
#- LDAP_SEARCH_PAGE_SIZE=0
|
||||
# LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited)
|
||||
# example : LDAP_SEARCH_SIZE_LIMIT=12345
|
||||
#- LDAP_SEARCH_SIZE_LIMIT=0
|
||||
# LDAP_GROUP_FILTER_ENABLE : Enable group filtering
|
||||
# example : LDAP_GROUP_FILTER_ENABLE=true
|
||||
#- LDAP_GROUP_FILTER_ENABLE=false
|
||||
# LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering
|
||||
# example : LDAP_GROUP_FILTER_OBJECTCLASS=group
|
||||
#- LDAP_GROUP_FILTER_OBJECTCLASS=
|
||||
# LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE :
|
||||
# example :
|
||||
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE :
|
||||
# example :
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT :
|
||||
# example :
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
|
||||
# LDAP_GROUP_FILTER_GROUP_NAME :
|
||||
# example :
|
||||
#- LDAP_GROUP_FILTER_GROUP_NAME=
|
||||
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier)
|
||||
# example : LDAP_UNIQUE_IDENTIFIER_FIELD=guid
|
||||
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
|
||||
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
|
||||
# example : LDAP_UTF8_NAMES_SLUGIFY=false
|
||||
#- LDAP_UTF8_NAMES_SLUGIFY=true
|
||||
# LDAP_USERNAME_FIELD : Which field contains the ldap username
|
||||
# example : LDAP_USERNAME_FIELD=username
|
||||
#- LDAP_USERNAME_FIELD=
|
||||
# LDAP_FULLNAME_FIELD : Which field contains the ldap fullname
|
||||
# example : LDAP_FULLNAME_FIELD=fullname
|
||||
#- LDAP_FULLNAME_FIELD=
|
||||
# LDAP_MERGE_EXISTING_USERS :
|
||||
# example : LDAP_MERGE_EXISTING_USERS=true
|
||||
#- LDAP_MERGE_EXISTING_USERS=false
|
||||
# LDAP_SYNC_USER_DATA :
|
||||
# example : LDAP_SYNC_USER_DATA=true
|
||||
#- LDAP_SYNC_USER_DATA=false
|
||||
# LDAP_SYNC_USER_DATA_FIELDMAP :
|
||||
# example : LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
|
||||
#- LDAP_SYNC_USER_DATA_FIELDMAP=
|
||||
# LDAP_SYNC_GROUP_ROLES :
|
||||
# example :
|
||||
#- LDAP_SYNC_GROUP_ROLES=
|
||||
# LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
|
||||
# example :
|
||||
#- LDAP_DEFAULT_DOMAIN=
|
||||
# LOGOUT_WITH_TIMER : Enables or not the option logout with timer
|
||||
# example : LOGOUT_WITH_TIMER=true
|
||||
#- LOGOUT_WITH_TIMER=
|
||||
# LOGOUT_IN : The number of days
|
||||
# example : LOGOUT_IN=1
|
||||
#- LOGOUT_IN=
|
||||
# LOGOUT_ON_HOURS : The number of hours
|
||||
# example : LOGOUT_ON_HOURS=9
|
||||
#- LOGOUT_ON_HOURS=
|
||||
# LOGOUT_ON_MINUTES : The number of minutes
|
||||
# example : LOGOUT_ON_MINUTES=55
|
||||
#- LOGOUT_ON_MINUTES=
|
||||
|
||||
depends_on:
|
||||
- mongodb
|
||||
|
||||
volumes:
|
||||
mongodb:
|
||||
driver: local
|
||||
mongodb-dump:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
wekan-tier:
|
||||
driver: bridge
|
||||
+462
-130
@@ -1,13 +1,101 @@
|
||||
version: '2'
|
||||
|
||||
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
|
||||
# 1) Edit settings
|
||||
# 2) docker-compose up -d
|
||||
#---------------------------------------------------------------------------------------------------------
|
||||
# ==== CREATING USERS AND LOGGING IN TO WEKAN ====
|
||||
# https://github.com/wekan/wekan/wiki/Adding-users
|
||||
#---------------------------------------------------------------------------------------------------------
|
||||
# ==== FORGOT PASSWORD ====
|
||||
# https://github.com/wekan/wekan/wiki/Forgot-Password
|
||||
#---------------------------------------------------------------------------------------------------------
|
||||
# ==== Upgrading Wekan to new version =====
|
||||
# 1) Stop Wekan:
|
||||
# docker-compose stop
|
||||
# 2) Download new version:
|
||||
# docker-compose pull wekan
|
||||
# 3) If you have more networks for VPN etc as described at bottom of
|
||||
# this config, download for them too:
|
||||
# docker-compose pull wekan2
|
||||
# 4) Start Wekan:
|
||||
# docker-compose start
|
||||
#----------------------------------------------------------------------------------
|
||||
# ==== OPTIONAL: DEDICATED DOCKER USER ====
|
||||
# 1) Optionally create a dedicated user for Wekan, for example:
|
||||
# sudo useradd -d /home/wekan -m -s /bin/bash wekan
|
||||
# 2) Add this user to the docker group, then logout+login or reboot:
|
||||
# sudo usermod -aG docker wekan
|
||||
# 3) Then login as user wekan.
|
||||
# 4) Create this file /home/wekan/docker-compose.yml with your modifications.
|
||||
#----------------------------------------------------------------------------------
|
||||
# ==== RUN DOCKER AS SERVICE ====
|
||||
# 1a) Running Docker as service, on Systemd like Debian 9, Ubuntu 16.04, CentOS 7:
|
||||
# sudo systemctl enable docker
|
||||
# sudo systemctl start docker
|
||||
# 1b) Running Docker as service, on init.d like Debian 8, Ubuntu 14.04, CentOS 6:
|
||||
# sudo update-rc.d docker defaults
|
||||
# sudo service docker start
|
||||
# ----------------------------------------------------------------------------------
|
||||
# ==== USAGE OF THIS docker-compose.yml ====
|
||||
# 1) For seeing does Wekan work, try this and check with your webbroser:
|
||||
# docker-compose up
|
||||
# 2) Stop Wekan and start Wekan in background:
|
||||
# docker-compose stop
|
||||
# docker-compose up -d
|
||||
# 3) See running Docker containers:
|
||||
# docker ps
|
||||
# 4) Stop Docker containers:
|
||||
# docker-compose stop
|
||||
# ----------------------------------------------------------------------------------
|
||||
# ===== INSIDE DOCKER CONTAINERS, AND BACKUP/RESTORE ====
|
||||
# https://github.com/wekan/wekan/wiki/Backup
|
||||
# If really necessary, repair MongoDB: https://github.com/wekan/wekan-mongodb/issues/6#issuecomment-424004116
|
||||
# 1) Going inside containers:
|
||||
# a) Wekan app, does not contain data
|
||||
# docker exec -it wekan-app bash
|
||||
# b) MongoDB, contains all data
|
||||
# docker exec -it wekan-db bash
|
||||
# 2) Copying database to outside of container:
|
||||
# docker exec -it wekan-db bash
|
||||
# cd /data
|
||||
# mongodump
|
||||
# exit
|
||||
# docker cp wekan-db:/data/dump .
|
||||
# 3) Restoring database
|
||||
# # 1) Stop wekan
|
||||
# docker stop wekan-app
|
||||
# # 2) Go inside database container
|
||||
# docker exec -it wekan-db bash
|
||||
# # 3) and data directory
|
||||
# cd /data
|
||||
# # 4) Remove previos dump
|
||||
# rm -rf dump
|
||||
# # 5) Exit db container
|
||||
# exit
|
||||
# # 6) Copy dump to inside docker container
|
||||
# docker cp dump wekan-db:/data/
|
||||
# # 7) Go inside database container
|
||||
# docker exec -it wekan-db bash
|
||||
# # 8) and data directory
|
||||
# cd /data
|
||||
# # 9) Restore
|
||||
# mongorestore --drop
|
||||
# # 10) Exit db container
|
||||
# exit
|
||||
# # 11) Start wekan
|
||||
# docker start wekan-app
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
services:
|
||||
|
||||
wekandb:
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== MONGODB AND METEOR VERSION ====
|
||||
# a) For Wekan Meteor 1.8.x version at meteor-1.8 branch, use mongo 4.x
|
||||
#image: mongo:4.0.4
|
||||
# b) For Wekan Meteor 1.6.x version at master/devel/edge branches.
|
||||
# Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x
|
||||
image: mongo:3.2.21
|
||||
#-------------------------------------------------------------------------------------
|
||||
container_name: wekan-db
|
||||
restart: always
|
||||
command: mongod --smallfiles --oplogSize 128
|
||||
@@ -20,202 +108,446 @@ services:
|
||||
- wekan-db-dump:/dump
|
||||
|
||||
wekan:
|
||||
image: quay.io/wekan/wekan
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== MONGODB AND METEOR VERSION ====
|
||||
# a) For Wekan Meteor 1.8.x version at meteor-1.8 branch,
|
||||
# using https://quay.io/wekan/wekan automatic builds
|
||||
#image: quay.io/wekan/wekan:meteor-1.8
|
||||
# b) For Wekan Meteor 1.6.x version at master/devel/edge branches.
|
||||
# Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x
|
||||
image: quay.io/wekan/wekan:master
|
||||
# c) Using specific Meteor 1.6.x version tag:
|
||||
# image: quay.io/wekan/wekan:v1.95
|
||||
# c) Using Docker Hub automatic builds https://hub.docker.com/r/wekanteam/wekan
|
||||
# image: wekanteam/wekan:meteor-1.8
|
||||
# image: wekanteam/wekan:v1.95
|
||||
#-------------------------------------------------------------------------------------
|
||||
container_name: wekan-app
|
||||
restart: always
|
||||
networks:
|
||||
- wekan-tier
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== BUILD wekan-app DOCKER CONTAINER FROM SOURCE, if you uncomment these ====
|
||||
# ==== and use commands: docker-compose up -d --build
|
||||
#build:
|
||||
# context: .
|
||||
# dockerfile: Dockerfile
|
||||
# args:
|
||||
# - NODE_VERSION=${NODE_VERSION}
|
||||
# - METEOR_RELEASE=${METEOR_RELEASE}
|
||||
# - NPM_VERSION=${NPM_VERSION}
|
||||
# - ARCHITECTURE=${ARCHITECTURE}
|
||||
# - SRC_PATH=${SRC_PATH}
|
||||
# - METEOR_EDGE=${METEOR_EDGE}
|
||||
# - USE_EDGE=${USE_EDGE}
|
||||
#-------------------------------------------------------------------------------------
|
||||
ports:
|
||||
# Docker outsideport:insideport
|
||||
# Docker outsideport:insideport. Do not add anything extra here.
|
||||
# For example, if you want to have wekan on port 3001,
|
||||
# use 3001:8080 . Do not add any extra address etc here, that way it does not work.
|
||||
# remove port mapping if you use nginx reverse proxy, port 8080 is already exposed to wekan-tier network
|
||||
- 80:8080
|
||||
environment:
|
||||
- MONGO_URL=mongodb://wekandb:27017/wekan
|
||||
- ROOT_URL=http://localhost
|
||||
#---------------------------------------------------------------
|
||||
# ==== ROOT_URL SETTING ====
|
||||
# Change ROOT_URL to your real Wekan URL, for example:
|
||||
# If you have Caddy/Nginx/Apache providing SSL
|
||||
# - https://example.com
|
||||
# - https://boards.example.com
|
||||
# This can be problematic with avatars https://github.com/wekan/wekan/issues/1776
|
||||
# - https://example.com/wekan
|
||||
# If without https, can be only wekan node, no need for Caddy/Nginx/Apache if you don't need them
|
||||
# - http://example.com
|
||||
# - http://boards.example.com
|
||||
# - http://192.168.1.100 <=== using at local LAN
|
||||
- ROOT_URL=http://localhost # <=== using only at same laptop/desktop where Wekan is installed
|
||||
#---------------------------------------------------------------
|
||||
# ==== EMAIL SETTINGS ====
|
||||
# Email settings are required in both MAIL_URL and Admin Panel,
|
||||
# see https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
|
||||
# For SSL in email, change smtp:// to smtps://
|
||||
# NOTE: Special characters need to be url-encoded in MAIL_URL.
|
||||
# You can encode those characters for example at: https://www.urlencoder.org
|
||||
#- MAIL_URL=smtp://user:pass@mailserver.example.com:25/
|
||||
- MAIL_URL=smtp://<mail_url>:25/?ignoreTLS=true&tls={rejectUnauthorized:false}
|
||||
- MAIL_FROM=Wekan Notifications <noreply.wekan@mydomain.com>
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: MONGO OPLOG SETTINGS =====
|
||||
# https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-378343587
|
||||
# We've fixed our CPU usage problem today with an environment
|
||||
# change around Wekan. I wasn't aware during implementation
|
||||
# that if you're using more than 1 instance of Wekan
|
||||
# (or any MeteorJS based tool) you're supposed to set
|
||||
# MONGO_OPLOG_URL as an environment variable.
|
||||
# Without setting it, Meteor will perform a pull-and-diff
|
||||
# update of it's dataset. With it, Meteor will update from
|
||||
# the OPLOG. See here
|
||||
# https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908
|
||||
# After setting
|
||||
# MONGO_OPLOG_URL=mongodb://<username>:<password>@<mongoDbURL>/local?authSource=admin&replicaSet=rsWekan
|
||||
# the CPU usage for all Wekan instances dropped to an average
|
||||
# of less than 10% with only occasional spikes to high usage
|
||||
# (I guess when someone is doing a lot of work)
|
||||
# - MONGO_OPLOG_URL=mongodb://<username>:<password>@<mongoDbURL>/local?authSource=admin&replicaSet=rsWekan
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: KADIRA PERFORMANCE MONITORING FOR METEOR ====
|
||||
# https://github.com/edemaine/kadira-compose
|
||||
# https://github.com/meteor/meteor-apm-agent
|
||||
# https://blog.meteor.com/kadira-apm-is-now-open-source-490469ffc85f
|
||||
#- APM_OPTIONS_ENDPOINT=http://<kadira-ip>:11011
|
||||
#- APM_APP_ID=
|
||||
#- APM_APP_SECRET=
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: LOGS AND STATS ====
|
||||
# https://github.com/wekan/wekan/wiki/Logs
|
||||
#
|
||||
# Daily export of Wekan changes as JSON to Logstash and ElasticSearch / Kibana (ELK)
|
||||
# https://github.com/wekan/wekan-logstash
|
||||
#
|
||||
# Statistics Python script for Wekan Dashboard
|
||||
# https://github.com/wekan/wekan-stats
|
||||
#
|
||||
# Console, file, and zulip logger on database changes https://github.com/wekan/wekan/pull/1010
|
||||
# with fix to replace console.log by winston logger https://github.com/wekan/wekan/pull/1033
|
||||
# but there could be bug https://github.com/wekan/wekan/issues/1094
|
||||
#
|
||||
# There is Feature Request: Logging date and time of all activity with summary reports,
|
||||
# and requesting reason for changing card to other column https://github.com/wekan/wekan/issues/1598
|
||||
#---------------------------------------------------------------
|
||||
# ==== WEKAN API AND EXPORT BOARD ====
|
||||
# Wekan Export Board works when WITH_API=true.
|
||||
# https://github.com/wekan/wekan/wiki/REST-API
|
||||
# https://github.com/wekan/wekan-gogs
|
||||
# If you disable Wekan API with false, Export Board does not work.
|
||||
- WITH_API=true
|
||||
# CORS: Set Access-Control-Allow-Origin header. Example: *
|
||||
#---------------------------------------------------------------
|
||||
# ==== PASSWORD BRUTE FORCE PROTECTION ====
|
||||
#https://atmospherejs.com/lucasantoniassi/accounts-lockout
|
||||
#Defaults below. Uncomment to change. wekan/server/accounts-lockout.js
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15
|
||||
#---------------------------------------------------------------
|
||||
# ==== EMAIL NOTIFICATION TIMEOUT, ms =====
|
||||
# Defaut: 30000 ms = 30s
|
||||
#- EMAIL_NOTIFICATION_TIMEOUT=30000
|
||||
#-----------------------------------------------------------------
|
||||
# ==== CORS =====
|
||||
# CORS: Set Access-Control-Allow-Origin header.
|
||||
#- CORS=*
|
||||
#-----------------------------------------------------------------
|
||||
# ==== MATOMO INTEGRATION ====
|
||||
# Optional: Integration with Matomo https://matomo.org that is installed to your server
|
||||
# The address of the server where Matomo is hosted.
|
||||
# example: - MATOMO_ADDRESS=https://example.com/matomo
|
||||
#- MATOMO_ADDRESS=
|
||||
#- MATOMO_ADDRESS=https://example.com/matomo
|
||||
# The value of the site ID given in Matomo server for Wekan
|
||||
# example: - MATOMO_SITE_ID=12345
|
||||
#- MATOMO_SITE_ID=
|
||||
#- MATOMO_SITE_ID=1
|
||||
# The option do not track which enables users to not be tracked by matomo
|
||||
# example: - MATOMO_DO_NOT_TRACK=false
|
||||
#- MATOMO_DO_NOT_TRACK=
|
||||
#- MATOMO_DO_NOT_TRACK=true
|
||||
# The option that allows matomo to retrieve the username:
|
||||
# example: MATOMO_WITH_USERNAME=true
|
||||
#- MATOMO_WITH_USERNAME=false
|
||||
#- MATOMO_WITH_USERNAME=true
|
||||
#-----------------------------------------------------------------
|
||||
# ==== BROWSER POLICY AND TRUSTED IFRAME URL ====
|
||||
# Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
|
||||
# Setting this to false is not recommended, it also disables all other browser policy protections
|
||||
# and allows all iframing etc. See wekan/server/policy.js
|
||||
- BROWSER_POLICY_ENABLED=true
|
||||
# When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
|
||||
#- TRUSTED_URL=
|
||||
#- TRUSTED_URL=https://intra.example.com
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OUTGOING WEBHOOKS ====
|
||||
# What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId .
|
||||
# example: WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId
|
||||
#- WEBHOOKS_ATTRIBUTES=
|
||||
#- WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId
|
||||
#-----------------------------------------------------------------
|
||||
# ==== Debug OIDC OAuth2 etc ====
|
||||
#- DEBUG=true
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 AZURE ====
|
||||
# https://github.com/wekan/wekan/wiki/Azure
|
||||
# 1) Register the application with Azure. Make sure you capture
|
||||
# the application ID as well as generate a secret key.
|
||||
# 2) Configure the environment variables. This differs slightly
|
||||
# by installation type, but make sure you have the following:
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
# Application GUID captured during app registration:
|
||||
#- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
|
||||
# Secret key generated during app registration:
|
||||
#- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
#- OAUTH2_SERVER_URL=https://login.microsoftonline.com/
|
||||
#- OAUTH2_AUTH_ENDPOINT=/oauth2/v2.0/authorize
|
||||
#- OAUTH2_USERINFO_ENDPOINT=https://graph.microsoft.com/oidc/userinfo
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/oauth2/v2.0/token
|
||||
# The claim name you want to map to the unique ID field:
|
||||
#- OAUTH2_ID_MAP=email
|
||||
# The claim name you want to map to the username field:
|
||||
#- OAUTH2_USERNAME_MAP=email
|
||||
# The claim name you want to map to the full name field:
|
||||
#- OAUTH2_FULLNAME_MAP=name
|
||||
# Tthe claim name you want to map to the email field:
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 KEYCLOAK ====
|
||||
# https://github.com/wekan/wekan/wiki/Keycloak <== MAPPING INFO, REQUIRED
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
#- OAUTH2_CLIENT_ID=<Keycloak create Client ID>
|
||||
#- OAUTH2_SERVER_URL=<Keycloak server name>/auth
|
||||
#- OAUTH2_AUTH_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/auth
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/userinfo
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/token
|
||||
#- OAUTH2_SECRET=<keycloak client secret>
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 DOORKEEPER ====
|
||||
# https://github.com/wekan/wekan/issues/1874
|
||||
# https://github.com/wekan/wekan/wiki/OAuth2
|
||||
# Enable the OAuth2 connection
|
||||
# example: OAUTH2_ENABLED=true
|
||||
#- OAUTH2_ENABLED=false
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2
|
||||
# OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345
|
||||
# example: OAUTH2_CLIENT_ID=abcde12345
|
||||
#- OAUTH2_CLIENT_ID=
|
||||
# OAuth2 Secret, for example from Rocket.Chat: Example: 54321abcde
|
||||
# example: OAUTH2_SECRET=54321abcde
|
||||
#- OAUTH2_SECRET=
|
||||
# OAuth2 Server URL, for example Rocket.Chat. Example: https://chat.example.com
|
||||
# example: OAUTH2_SERVER_URL=https://chat.example.com
|
||||
#- OAUTH2_SERVER_URL=
|
||||
# OAuth2 Authorization Endpoint. Example: /oauth/authorize
|
||||
# example: OAUTH2_AUTH_ENDPOINT=/oauth/authorize
|
||||
#- OAUTH2_AUTH_ENDPOINT=
|
||||
# OAuth2 Userinfo Endpoint. Example: /oauth/userinfo
|
||||
# example: OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
|
||||
#- OAUTH2_USERINFO_ENDPOINT=
|
||||
# OAuth2 Token Endpoint. Example: /oauth/token
|
||||
# example: OAUTH2_TOKEN_ENDPOINT=/oauth/token
|
||||
#- OAUTH2_TOKEN_ENDPOINT=
|
||||
# LDAP_ENABLE : Enable or not the connection by the LDAP
|
||||
# example : LDAP_ENABLE=true
|
||||
#- LDAP_ENABLE=false
|
||||
# LDAP_PORT : The port of the LDAP server
|
||||
# example : LDAP_PORT=389
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
# OAuth2 Client ID.
|
||||
#- OAUTH2_CLIENT_ID=abcde12345
|
||||
# OAuth2 Secret.
|
||||
#- OAUTH2_SECRET=54321abcde
|
||||
# OAuth2 Server URL.
|
||||
#- OAUTH2_SERVER_URL=https://chat.example.com
|
||||
# OAuth2 Authorization Endpoint.
|
||||
#- OAUTH2_AUTH_ENDPOINT=/oauth/authorize
|
||||
# OAuth2 Userinfo Endpoint.
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
|
||||
# OAuth2 Token Endpoint.
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/oauth/token
|
||||
# OAuth2 ID Mapping
|
||||
#- OAUTH2_ID_MAP=
|
||||
# OAuth2 Username Mapping
|
||||
#- OAUTH2_USERNAME_MAP=
|
||||
# OAuth2 Fullname Mapping
|
||||
#- OAUTH2_FULLNAME_MAP=
|
||||
# OAuth2 Email Mapping
|
||||
#- OAUTH2_EMAIL_MAP=
|
||||
#-----------------------------------------------------------------
|
||||
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
|
||||
# https://github.com/wekan/wekan/wiki/LDAP
|
||||
# For Snap settings see https://github.com/wekan/wekan-snap/wiki/Supported-settings-keys
|
||||
# Most settings work both on Snap and Docker below.
|
||||
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
|
||||
#
|
||||
# The default authentication method used if a user does not exist to create and authenticate. Can be set as ldap.
|
||||
#- DEFAULT_AUTHENTICATION_METHOD=ldap
|
||||
#
|
||||
# Enable or not the connection by the LDAP
|
||||
#- LDAP_ENABLE=true
|
||||
#
|
||||
# The port of the LDAP server
|
||||
#- LDAP_PORT=389
|
||||
# LDAP_HOST : The host server for the LDAP server
|
||||
# example : LDAP_HOST=localhost
|
||||
#- LDAP_HOST=
|
||||
# LDAP_BASEDN : The base DN for the LDAP Tree
|
||||
# example : LDAP_BASEDN=ou=user,dc=example,dc=org
|
||||
#- LDAP_BASEDN=
|
||||
# LDAP_LOGIN_FALLBACK : Fallback on the default authentication method
|
||||
# example : LDAP_LOGIN_FALLBACK=true
|
||||
#
|
||||
# The host server for the LDAP server
|
||||
#- LDAP_HOST=localhost
|
||||
#
|
||||
# The base DN for the LDAP Tree
|
||||
#- LDAP_BASEDN=ou=user,dc=example,dc=org
|
||||
#
|
||||
# Fallback on the default authentication method
|
||||
#- LDAP_LOGIN_FALLBACK=false
|
||||
# LDAP_RECONNECT : Reconnect to the server if the connection is lost
|
||||
# example : LDAP_RECONNECT=false
|
||||
#
|
||||
# Reconnect to the server if the connection is lost
|
||||
#- LDAP_RECONNECT=true
|
||||
# LDAP_TIMEOUT : Overall timeout, in milliseconds
|
||||
# example : LDAP_TIMEOUT=12345
|
||||
#
|
||||
# Overall timeout, in milliseconds
|
||||
#- LDAP_TIMEOUT=10000
|
||||
# LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds
|
||||
# example : LDAP_IDLE_TIMEOUT=12345
|
||||
#
|
||||
# Specifies the timeout for idle LDAP connections in milliseconds
|
||||
#- LDAP_IDLE_TIMEOUT=10000
|
||||
# LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds
|
||||
# example : LDAP_CONNECT_TIMEOUT=12345
|
||||
#
|
||||
# Connection timeout, in milliseconds
|
||||
#- LDAP_CONNECT_TIMEOUT=10000
|
||||
# LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search
|
||||
# example : LDAP_AUTHENTIFICATION=true
|
||||
#- LDAP_AUTHENTIFICATION=false
|
||||
# LDAP_AUTHENTIFICATION_USERDN : The search user DN
|
||||
# example : LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=
|
||||
# LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user
|
||||
# example : AUTHENTIFICATION_PASSWORD=admin
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD=
|
||||
# LDAP_LOG_ENABLED : Enable logs for the module
|
||||
# example : LDAP_LOG_ENABLED=true
|
||||
#- LDAP_LOG_ENABLED=false
|
||||
# LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background
|
||||
# example : LDAP_BACKGROUND_SYNC=true
|
||||
#
|
||||
# If the LDAP needs a user account to search
|
||||
#- LDAP_AUTHENTIFICATION=true
|
||||
#
|
||||
# The search user DN
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=cn=wekan_adm,ou=serviceaccounts,ou=admin,ou=prod,dc=mydomain,dc=com
|
||||
#
|
||||
# The password for the search user
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD=pwd
|
||||
#
|
||||
# Enable logs for the module
|
||||
#- LDAP_LOG_ENABLED=true
|
||||
#
|
||||
# If the sync of the users should be done in the background
|
||||
#- LDAP_BACKGROUND_SYNC=false
|
||||
# LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds
|
||||
# example : LDAP_BACKGROUND_SYNC_INTERVAL=12345
|
||||
#
|
||||
# At which interval does the background task sync in milliseconds
|
||||
#- LDAP_BACKGROUND_SYNC_INTERVAL=100
|
||||
# LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED :
|
||||
# example : LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true
|
||||
#
|
||||
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
|
||||
# LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS :
|
||||
# example : LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true
|
||||
#
|
||||
#- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
|
||||
# LDAP_ENCRYPTION : If using LDAPS
|
||||
# example : LDAP_ENCRYPTION=ssl
|
||||
#
|
||||
# If using LDAPS: LDAP_ENCRYPTION=ssl
|
||||
#- LDAP_ENCRYPTION=false
|
||||
# LDAP_CA_CERT : The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
|
||||
# example : LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE-----
|
||||
#- LDAP_CA_CERT=
|
||||
# LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
|
||||
# example : LDAP_REJECT_UNAUTHORIZED=true
|
||||
#
|
||||
# The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
|
||||
#- LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+G2FIdAgIC...-----END CERTIFICATE-----
|
||||
#
|
||||
# Reject Unauthorized Certificate
|
||||
#- LDAP_REJECT_UNAUTHORIZED=false
|
||||
# LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
# example : LDAP_USER_SEARCH_FILTER=
|
||||
#
|
||||
# Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
#- LDAP_USER_SEARCH_FILTER=
|
||||
# LDAP_USER_SEARCH_SCOPE : base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
|
||||
# example : LDAP_USER_SEARCH_SCOPE=one
|
||||
#- LDAP_USER_SEARCH_SCOPE=
|
||||
# LDAP_USER_SEARCH_FIELD : Which field is used to find the user
|
||||
# example : LDAP_USER_SEARCH_FIELD=uid
|
||||
#- LDAP_USER_SEARCH_FIELD=
|
||||
# LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited)
|
||||
# example : LDAP_SEARCH_PAGE_SIZE=12345
|
||||
#
|
||||
# base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
|
||||
#- LDAP_USER_SEARCH_SCOPE=one
|
||||
#
|
||||
# Which field is used to find the user, like uid / sAMAccountName
|
||||
#- LDAP_USER_SEARCH_FIELD=sAMAccountName
|
||||
#
|
||||
# Used for pagination (0=unlimited)
|
||||
#- LDAP_SEARCH_PAGE_SIZE=0
|
||||
# LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited)
|
||||
# example : LDAP_SEARCH_SIZE_LIMIT=12345
|
||||
#
|
||||
# The limit number of entries (0=unlimited)
|
||||
#- LDAP_SEARCH_SIZE_LIMIT=0
|
||||
# LDAP_GROUP_FILTER_ENABLE : Enable group filtering
|
||||
# example : LDAP_GROUP_FILTER_ENABLE=true
|
||||
#
|
||||
# Enable group filtering
|
||||
#- LDAP_GROUP_FILTER_ENABLE=false
|
||||
# LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering
|
||||
# example : LDAP_GROUP_FILTER_OBJECTCLASS=group
|
||||
#
|
||||
# The object class for filtering. Example: group
|
||||
#- LDAP_GROUP_FILTER_OBJECTCLASS=
|
||||
# LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE :
|
||||
# example :
|
||||
#
|
||||
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE :
|
||||
# example :
|
||||
#
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT :
|
||||
# example :
|
||||
#
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
|
||||
# LDAP_GROUP_FILTER_GROUP_NAME :
|
||||
# example :
|
||||
#
|
||||
#- LDAP_GROUP_FILTER_GROUP_NAME=
|
||||
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier)
|
||||
# example : LDAP_UNIQUE_IDENTIFIER_FIELD=guid
|
||||
#
|
||||
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier). Example: guid
|
||||
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
|
||||
#
|
||||
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
|
||||
# example : LDAP_UTF8_NAMES_SLUGIFY=false
|
||||
#- LDAP_UTF8_NAMES_SLUGIFY=true
|
||||
# LDAP_USERNAME_FIELD : Which field contains the ldap username
|
||||
# example : LDAP_USERNAME_FIELD=username
|
||||
#- LDAP_USERNAME_FIELD=
|
||||
# LDAP_FULLNAME_FIELD : Which field contains the ldap fullname
|
||||
# example : LDAP_FULLNAME_FIELD=fullname
|
||||
#- LDAP_FULLNAME_FIELD=
|
||||
# LDAP_MERGE_EXISTING_USERS :
|
||||
# example : LDAP_MERGE_EXISTING_USERS=true
|
||||
#
|
||||
# LDAP_USERNAME_FIELD : Which field contains the ldap username. username / sAMAccountName
|
||||
#- LDAP_USERNAME_FIELD=sAMAccountName
|
||||
#
|
||||
# LDAP_FULLNAME_FIELD : Which field contains the ldap fullname. fullname / sAMAccountName
|
||||
#- LDAP_FULLNAME_FIELD=fullname
|
||||
#
|
||||
#- LDAP_MERGE_EXISTING_USERS=false
|
||||
# LDAP_SYNC_USER_DATA :
|
||||
# example : LDAP_SYNC_USER_DATA=true
|
||||
#
|
||||
# Allow existing account matching by e-mail address when username does not match
|
||||
#- LDAP_EMAIL_MATCH_ENABLE=true
|
||||
#
|
||||
# LDAP_EMAIL_MATCH_REQUIRE : require existing account matching by e-mail address when username does match
|
||||
#- LDAP_EMAIL_MATCH_REQUIRE=true
|
||||
#
|
||||
# LDAP_EMAIL_MATCH_VERIFIED : require existing account email address to be verified for matching
|
||||
#- LDAP_EMAIL_MATCH_VERIFIED=true
|
||||
#
|
||||
# LDAP_EMAIL_FIELD : which field contains the LDAP e-mail address
|
||||
#- LDAP_EMAIL_FIELD=mail
|
||||
#-----------------------------------------------------------------
|
||||
#- LDAP_SYNC_USER_DATA=false
|
||||
# LDAP_SYNC_USER_DATA_FIELDMAP :
|
||||
# example : LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
|
||||
#- LDAP_SYNC_USER_DATA_FIELDMAP=
|
||||
# LDAP_SYNC_GROUP_ROLES :
|
||||
#
|
||||
#- LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
|
||||
#
|
||||
#- LDAP_SYNC_GROUP_ROLES=''
|
||||
#
|
||||
# The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
|
||||
# example :
|
||||
#- LDAP_SYNC_GROUP_ROLES=
|
||||
# LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
|
||||
# example :
|
||||
#- LDAP_DEFAULT_DOMAIN=
|
||||
#- LDAP_DEFAULT_DOMAIN=mydomain.com
|
||||
#
|
||||
# Enable/Disable syncing of admin status based on ldap groups:
|
||||
#- LDAP_SYNC_ADMIN_STATUS=true
|
||||
#
|
||||
# Comma separated list of admin group names to sync.
|
||||
#- LDAP_SYNC_ADMIN_GROUPS=group1,group2
|
||||
#---------------------------------------------------------------------
|
||||
# Login to LDAP automatically with HTTP header.
|
||||
# In below example for siteminder, at right side of = is header name.
|
||||
#- HEADER_LOGIN_ID=HEADERUID
|
||||
#- HEADER_LOGIN_FIRSTNAME=HEADERFIRSTNAME
|
||||
#- HEADER_LOGIN_LASTNAME=HEADERLASTNAME
|
||||
#- HEADER_LOGIN_EMAIL=HEADEREMAILADDRESS
|
||||
#---------------------------------------------------------------------
|
||||
# ==== LOGOUT TIMER, probably does not work yet ====
|
||||
# LOGOUT_WITH_TIMER : Enables or not the option logout with timer
|
||||
# example : LOGOUT_WITH_TIMER=true
|
||||
#- LOGOUT_WITH_TIMER=
|
||||
#
|
||||
# LOGOUT_IN : The number of days
|
||||
# example : LOGOUT_IN=1
|
||||
#- LOGOUT_IN=
|
||||
#
|
||||
# LOGOUT_ON_HOURS : The number of hours
|
||||
# example : LOGOUT_ON_HOURS=9
|
||||
#- LOGOUT_ON_HOURS=
|
||||
#
|
||||
# LOGOUT_ON_MINUTES : The number of minutes
|
||||
# example : LOGOUT_ON_MINUTES=55
|
||||
#- LOGOUT_ON_MINUTES=
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
depends_on:
|
||||
- wekandb
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# ==== OPTIONAL: SHARE DATABASE TO OFFICE LAN AND REMOTE VPN ====
|
||||
# When using Wekan both at office LAN and remote VPN:
|
||||
# 1) Have above Wekan docker container config with LAN IP address
|
||||
# 2) Copy all of above wekan container config below, look above of this part above and all config below it,
|
||||
# before above depends_on: part:
|
||||
#
|
||||
# wekan:
|
||||
# #-------------------------------------------------------------------------------------
|
||||
# # ==== MONGODB AND METEOR VERSION ====
|
||||
# # a) For Wekan Meteor 1.8.x version at meteor-1.8 branch, .....
|
||||
#
|
||||
#
|
||||
# and change name to different name like wekan2 or wekanvpn, and change ROOT_URL to server VPN IP
|
||||
# address.
|
||||
# 3) This way both Wekan containers can use same MongoDB database
|
||||
# and see the same Wekan boards.
|
||||
# 4) You could also add 3rd Wekan container for 3rd network etc.
|
||||
# EXAMPLE:
|
||||
# wekan2:
|
||||
# ....COPY CONFIG FROM ABOVE TO HERE...
|
||||
# environment:
|
||||
# - ROOT_URL='http://10.10.10.10'
|
||||
# ...COPY CONFIG FROM ABOVE TO HERE...
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
# OPTIONAL NGINX CONFIG FOR REVERSE PROXY
|
||||
# nginx:
|
||||
# image: nginx
|
||||
# container_name: nginx
|
||||
# restart: always
|
||||
# networks:
|
||||
# - wekan-tier
|
||||
# depends_on:
|
||||
# - wekan
|
||||
# ports:
|
||||
# - 80:80
|
||||
# - 443:443
|
||||
# volumes:
|
||||
# - ./nginx/ssl:/etc/nginx/ssl/:ro
|
||||
# - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
## Alternative volume config:
|
||||
## volumes:
|
||||
## - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
## - ./nginx/ssl/ssl.conf:/etc/nginx/conf.d/ssl/ssl.conf:ro
|
||||
## - ./nginx/ssl/testvm-ehu.crt:/etc/nginx/conf.d/ssl/certs/mycert.crt:ro
|
||||
## - ./nginx/ssl/testvm-ehu.key:/etc/nginx/conf.d/ssl/certs/mykey.key:ro
|
||||
## - ./nginx/ssl/pphrase:/etc/nginx/conf.d/ssl/pphrase:ro
|
||||
|
||||
volumes:
|
||||
wekan-db:
|
||||
driver: local
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Find text from all subdirectories
|
||||
# and ignore all temporary directories:
|
||||
# - node-modules = installed node modules
|
||||
# - .build = Wekan bundle that is combined from source. Do not edit these, these are deleted and recreated.
|
||||
# - .meteor = Meteor version, packages etc at .meteor/local
|
||||
# - .git = git history
|
||||
|
||||
# If less or more that 1 parameter, show usage.
|
||||
if (( $# != 1 )); then
|
||||
echo 'Usage: ./find.sh text-to-find'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
find . | grep -v node_modules | grep -v .build | grep -v .meteor | grep -v .git | xargs grep --no-messages $1 | less
|
||||
@@ -0,0 +1,22 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
OWNERS
|
||||
@@ -0,0 +1,13 @@
|
||||
name: wekan
|
||||
version: 1.0.0
|
||||
appVersion: 2.x.x
|
||||
kubeVersion: "^1.8.0-0"
|
||||
description: Open Source kanban
|
||||
home: https://wekan.github.io/
|
||||
icon: https://wekan.github.io/wekan-logo.svg
|
||||
sources:
|
||||
- https://github.com/wekan/wekan
|
||||
maintainers:
|
||||
- name: technotaff
|
||||
email: github@randall.cc
|
||||
engine: gotpl
|
||||
@@ -0,0 +1,4 @@
|
||||
approvers:
|
||||
- technotaff
|
||||
reviewers:
|
||||
- technotaff
|
||||
@@ -0,0 +1,58 @@
|
||||
# Helm Chart for Wekan
|
||||
|
||||
## Features
|
||||
|
||||
o Uses a MongoDB replica set by default - this allows fault-tolerant
|
||||
and scalable MongoDB deployment (or just set the replicas to 1 for
|
||||
a single server install)
|
||||
|
||||
o Optional Horizontal Pod Autoscaler (HPA), so that your Wekan pods
|
||||
will scale automatically with increased CPU load.
|
||||
|
||||
## The configurable values (values.yaml)
|
||||
|
||||
Scaling Wekan:
|
||||
|
||||
```yaml
|
||||
## Configuration for wekan component
|
||||
##
|
||||
|
||||
replicaCount: 1
|
||||
```
|
||||
**replicaCount** Will set the initial number of replicas for the Wekan pod (and container)
|
||||
|
||||
```yaml
|
||||
## Configure an horizontal pod autoscaler
|
||||
##
|
||||
autoscaling:
|
||||
enabled: true
|
||||
config:
|
||||
minReplicas: 1
|
||||
maxReplicas: 16
|
||||
## Note: when setting this, a `resources.request.cpu` is required. You
|
||||
## likely want to set it to `1` or some lower value.
|
||||
##
|
||||
targetCPUUtilizationPercentage: 80
|
||||
```
|
||||
This section (if *enabled* is set to **true**) will enable the Kubernetes Horizontal Pod Autoscaler (HPA).
|
||||
|
||||
**minReplicas:** this is the minimum number of pods to scale down to (We recommend setting this to the same value as **replicaCount**).
|
||||
|
||||
**maxReplicas:** this is the maximum number of pods to scale up to.
|
||||
|
||||
**targetCPUUtilizationPercentage:** This is the CPU at which the HPA will scale-out the number of Wekan pods.
|
||||
|
||||
```yaml
|
||||
mongodb-replicaset:
|
||||
enabled: true
|
||||
replicas: 3
|
||||
replicaSetName: rs0
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
```
|
||||
|
||||
This section controls the scale of the MongoDB redundant Replica Set.
|
||||
|
||||
**replicas:** This is the number of MongoDB instances to include in the set. You can set this to 1 for a single server - this will still allow you to scale-up later with a helm upgrade.
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies:
|
||||
- name: mongodb-replicaset
|
||||
version: 3.6.x
|
||||
repository: "https://kubernetes-charts.storage.googleapis.com/"
|
||||
condition: mongodb-replicaset.enabled
|
||||
@@ -0,0 +1,19 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range .Values.ingress.hosts }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "wekan.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get svc -w {{ template "wekan.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "wekan.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "wekan.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl port-forward $POD_NAME 8080:8080
|
||||
{{- end }}
|
||||
@@ -0,0 +1,82 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "wekan.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "wekan.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified name for the wekan data app.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "wekan.localdata.fullname" -}}
|
||||
{{- if .Values.localdata.fullnameOverride -}}
|
||||
{{- .Values.localdata.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- printf "%s-localdata" .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s-localdata" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "wekan.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use for the api component
|
||||
*/}}
|
||||
{{- define "wekan.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccounts.create -}}
|
||||
{{ default (include "wekan.fullname" .) .Values.serviceAccounts.name }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.serviceAccounts.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified mongodb-replicaset name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "wekan.mongodb-replicaset.fullname" -}}
|
||||
{{- $name := default "mongodb-replicaset" (index .Values "mongodb-replicaset" "nameOverride") -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the MongoDB URL. If MongoDB is installed as part of this chart, use k8s service discovery,
|
||||
else use user-provided URL.
|
||||
*/}}
|
||||
{{- define "mongodb-replicaset.url" -}}
|
||||
{{- if (index .Values "mongodb-replicaset" "enabled") -}}
|
||||
{{- $count := (int (index .Values "mongodb-replicaset" "replicas")) -}}
|
||||
{{- $release := .Release.Name -}}
|
||||
mongodb://{{- range $v := until $count }}{{ $release }}-mongodb-replicaset-{{ $v }}.{{ $release }}-mongodb-replicaset:27017{{ if ne $v (sub $count 1) }},{{- end -}}{{- end -}}?replicaSet={{ index .Values "mongodb-replicaset" "replicaSetName" }}
|
||||
{{- else -}}
|
||||
{{- index .Values "mongodb-replicaset" "url" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,58 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "wekan.fullname" . }}
|
||||
labels:
|
||||
app: {{ template "wekan.name" . }}
|
||||
chart: {{ template "wekan.chart" . }}
|
||||
component: wekan
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ template "wekan.name" . }}
|
||||
component: wekan
|
||||
release: {{ .Release.Name }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
labels:
|
||||
app: {{ template "wekan.name" . }}
|
||||
component: wekan
|
||||
release: {{ .Release.Name }}
|
||||
spec:
|
||||
serviceAccountName: {{ template "wekan.serviceAccountName" . }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
terminationMessagePolicy: FallbackToLogsOnError
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
env:
|
||||
- name: ROOT_URL
|
||||
value: {{ .Values.root_url | default "https://wekan.local" | quote }}
|
||||
- name: MONGO_URL
|
||||
value: "{{ template "mongodb-replicaset.url" . }}"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
initialDelaySeconds: 60
|
||||
resources:
|
||||
{{ toYaml .Values.resources | indent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{ toYaml . | indent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.affinity }}
|
||||
affinity:
|
||||
{{ toYaml .Values.affinity | indent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{ toYaml . | indent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,18 @@
|
||||
{{- if .Values.autoscaling.enabled -}}
|
||||
apiVersion: autoscaling/v1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ template "wekan.fullname" . }}
|
||||
labels:
|
||||
app: {{ template "wekan.name" . }}
|
||||
chart: {{ template "wekan.chart" . }}
|
||||
component: wekan
|
||||
heritage: {{ .Release.Service }}
|
||||
release: {{ .Release.Name }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ template "wekan.fullname" . }}
|
||||
{{ toYaml .Values.autoscaling.config | indent 2 }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,40 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "wekan.fullname" . -}}
|
||||
{{- $servicePort := .Values.service.port -}}
|
||||
{{- $ingressPath := .Values.ingress.path -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
app: {{ template "wekan.name" . }}
|
||||
chart: {{ template "wekan.chart" . }}
|
||||
component: wekan
|
||||
heritage: {{ .Release.Service }}
|
||||
release: {{ .Release.Name }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{ toYaml . | indent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ . }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $ingressPath }}
|
||||
backend:
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: http
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ template "wekan.fullname" . }}
|
||||
labels:
|
||||
app: {{ template "wekan.name" . }}
|
||||
chart: {{ template "wekan.chart" . }}
|
||||
component: wekan
|
||||
heritage: {{ .Release.Service }}
|
||||
release: {{ .Release.Name }}
|
||||
type: Opaque
|
||||
data:
|
||||
accessKey: {{ .Values.credentials.accessKey | b64enc }}
|
||||
secretKey: {{ .Values.credentials.secretKey | b64enc }}
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
{{- if .Values.service.annotations }}
|
||||
annotations:
|
||||
{{ toYaml .Values.service.annotations | indent 4 }}
|
||||
{{- end }}
|
||||
name: {{ template "wekan.fullname" . }}
|
||||
labels:
|
||||
app: {{ template "wekan.name" . }}
|
||||
chart: {{ template "wekan.chart" . }}
|
||||
component: wekan
|
||||
heritage: {{ .Release.Service }}
|
||||
release: {{ .Release.Name }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: {{ template "wekan.name" . }}
|
||||
component: wekan
|
||||
release: {{ .Release.Name }}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccounts.create }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ template "wekan.name" . }}
|
||||
chart: {{ template "wekan.chart" . }}
|
||||
component: wekan
|
||||
heritage: {{ .Release.Service }}
|
||||
release: {{ .Release.Name }}
|
||||
name: {{ template "wekan.serviceAccountName" . }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: {{ template "wekan.fullname" . }}-test
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ template "wekan.fullname" . }}-test
|
||||
imagePullPolicy: IfNotPresent
|
||||
image: "docker.io/mesosphere/aws-cli:1.14.5"
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- aws s3 --endpoint-url=http://{{ include "wekan.fullname" . }} --region=us-east-1 ls
|
||||
env:
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "wekan.fullname" . }}
|
||||
key: accessKey
|
||||
- name: AWS_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "wekan.fullname" . }}
|
||||
key: secretKey
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,110 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Wekan:
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
## Define serviceAccount names to create or use. Defaults to component's fully
|
||||
## qualified name.
|
||||
##
|
||||
serviceAccounts:
|
||||
create: true
|
||||
name: ""
|
||||
|
||||
## Wekan image configuration
|
||||
##
|
||||
image:
|
||||
repository: quay.io/wekan/wekan
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
## Configuration for wekan component
|
||||
##
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
## Specify wekan credentials
|
||||
##
|
||||
credentials:
|
||||
accessKey: access-key
|
||||
secretKey: secret-key
|
||||
|
||||
## Specify additional environmental variables for the Deployment
|
||||
##
|
||||
env: {}
|
||||
|
||||
service:
|
||||
type: NodePort
|
||||
port: 80
|
||||
annotations: {}
|
||||
# prometheus.io/scrape: "true"
|
||||
# prometheus.io/port: "8000"
|
||||
# prometheus.io/path: "/_/monitoring/metrics"
|
||||
|
||||
## Comma-separated string of allowed virtual hosts for external access.
|
||||
## This should match the ingress hosts
|
||||
##
|
||||
endpoint: wekan.local
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
path: /*
|
||||
# This must match 'endpoint', unless your client supports different
|
||||
# hostnames.
|
||||
hosts: [ wekan.local ]
|
||||
# - wekan.local
|
||||
tls: []
|
||||
# - secretName: wekan-example-tls
|
||||
# hosts:
|
||||
# - wekan-example.local
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
cpu: 300m
|
||||
limits:
|
||||
memory: 1Gi
|
||||
cpu: 500m
|
||||
|
||||
## Node labels for pod assignment
|
||||
## ref: https://kubernetes.io/docs/user-guide/node-selection/
|
||||
##
|
||||
nodeSelector: {}
|
||||
|
||||
## Tolerations for pod assignment
|
||||
## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
|
||||
##
|
||||
tolerations: []
|
||||
|
||||
## Affinity for pod assignment
|
||||
## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
|
||||
##
|
||||
affinity: {}
|
||||
|
||||
## Configure an horizontal pod autoscaler
|
||||
##
|
||||
autoscaling:
|
||||
enabled: true
|
||||
config:
|
||||
minReplicas: 1
|
||||
maxReplicas: 16
|
||||
## Note: when setting this, a `resources.request.cpu` is required. You
|
||||
## likely want to set it to `1` or some lower value.
|
||||
##
|
||||
targetCPUUtilizationPercentage: 80
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# MongoDB:
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
mongodb-replicaset:
|
||||
enabled: true
|
||||
replicas: 3
|
||||
replicaSetName: rs0
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
#image:
|
||||
# tag: 3.2.21
|
||||
+146
-80
@@ -1,38 +1,51 @@
|
||||
{
|
||||
"accept": "اقبلboard",
|
||||
"act-activity-notify": "[Wekan] اشعار عن نشاط",
|
||||
"act-addAttachment": "ربط __attachment__ الى __card__",
|
||||
"act-addSubtask": "added subtask __checklist__ to __card__",
|
||||
"act-addChecklist": "added checklist __checklist__ to __card__",
|
||||
"act-addChecklistItem": "added __checklistItem__ to checklist __checklist__ on __card__",
|
||||
"act-addComment": "علق على __comment__ : __card__",
|
||||
"act-createBoard": "احدث __board__",
|
||||
"act-createCard": "اضاف __card__ الى __list__",
|
||||
"act-createCustomField": "created custom field __customField__",
|
||||
"act-createList": "اضاف __list__ الى __board__",
|
||||
"act-addBoardMember": "اضاف __member__ الى __board__",
|
||||
"act-archivedBoard": "__board__ moved to Archive",
|
||||
"act-archivedCard": "__card__ moved to Archive",
|
||||
"act-archivedList": "__list__ moved to Archive",
|
||||
"act-archivedSwimlane": "__swimlane__ moved to Archive",
|
||||
"act-importBoard": "إستورد __board__",
|
||||
"act-importCard": "إستورد __card__",
|
||||
"act-importList": "إستورد __list__",
|
||||
"act-joinMember": "اضاف __member__ الى __card__",
|
||||
"act-moveCard": "حوّل __card__ من __oldList__ إلى __list__",
|
||||
"act-removeBoardMember": "أزال __member__ من __board__",
|
||||
"act-restoredCard": "أعاد __card__ إلى __board__",
|
||||
"act-unjoinMember": "أزال __member__ من __card__",
|
||||
"act-withBoardTitle": "[Wekan] __board__",
|
||||
"accept": "قبول",
|
||||
"act-activity-notify": "اشعارات النشاط",
|
||||
"act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createBoard": "created board __board__",
|
||||
"act-createSwimlane": "created swimlane __swimlane__ to board __board__",
|
||||
"act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createCustomField": "created custom field __customField__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createList": "added list __list__ to board __board__",
|
||||
"act-addBoardMember": "added member __member__ to board __board__",
|
||||
"act-archivedBoard": "Board __board__ moved to Archive",
|
||||
"act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-importBoard": "imported board __board__",
|
||||
"act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__",
|
||||
"act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__",
|
||||
"act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoardMember": "removed member __member__ from board __board__",
|
||||
"act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-withBoardTitle": "__لوح__",
|
||||
"act-withCardTitle": "[__board__] __card__",
|
||||
"actions": "الإجراءات",
|
||||
"activities": "الأنشطة",
|
||||
"activity": "النشاط",
|
||||
"activity-added": "تمت إضافة %s ل %s",
|
||||
"activity-archived": "%s moved to Archive",
|
||||
"activity-archived": "%s انتقل الى الارشيف",
|
||||
"activity-attached": "إرفاق %s ل %s",
|
||||
"activity-created": "أنشأ %s",
|
||||
"activity-customfield-created": "created custom field %s",
|
||||
"activity-customfield-created": "%s احدت حقل مخصص",
|
||||
"activity-excluded": "استبعاد %s عن %s",
|
||||
"activity-imported": "imported %s into %s from %s",
|
||||
"activity-imported-board": "imported %s from %s",
|
||||
@@ -42,19 +55,19 @@
|
||||
"activity-removed": "حذف %s إلى %s",
|
||||
"activity-sent": "إرسال %s إلى %s",
|
||||
"activity-unjoined": "غادر %s",
|
||||
"activity-subtask-added": "added subtask to %s",
|
||||
"activity-checked-item": "checked %s in checklist %s of %s",
|
||||
"activity-unchecked-item": "unchecked %s in checklist %s of %s",
|
||||
"activity-subtask-added": "تم اضافة مهمة فرعية الى %s",
|
||||
"activity-checked-item": "تحقق %s في قائمة التحقق %s من %s",
|
||||
"activity-unchecked-item": "ازالة تحقق %s من قائمة التحقق %s من %s",
|
||||
"activity-checklist-added": "أضاف قائمة تحقق إلى %s",
|
||||
"activity-checklist-removed": "removed a checklist from %s",
|
||||
"activity-checklist-completed": "completed the checklist %s of %s",
|
||||
"activity-checklist-uncompleted": "uncompleted the checklist %s of %s",
|
||||
"activity-checklist-item-added": "added checklist item to '%s' in %s",
|
||||
"activity-checklist-item-removed": "removed a checklist item from '%s' in %s",
|
||||
"activity-checklist-removed": "ازالة قائمة التحقق من %s",
|
||||
"activity-checklist-completed": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"activity-checklist-uncompleted": "لم يتم انجاز قائمة التحقق %s من %s",
|
||||
"activity-checklist-item-added": "تم اضافة عنصر قائمة التحقق الى '%s' في %s",
|
||||
"activity-checklist-item-removed": "تم ازالة عنصر قائمة التحقق الى '%s' في %s",
|
||||
"add": "أضف",
|
||||
"activity-checked-item-card": "checked %s in checklist %s",
|
||||
"activity-unchecked-item-card": "unchecked %s in checklist %s",
|
||||
"activity-checklist-completed-card": "completed the checklist %s",
|
||||
"activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"activity-checklist-uncompleted-card": "uncompleted the checklist %s",
|
||||
"add-attachment": "إضافة مرفق",
|
||||
"add-board": "إضافة لوحة",
|
||||
@@ -78,20 +91,22 @@
|
||||
"and-n-other-card": "And __count__ other بطاقة",
|
||||
"and-n-other-card_plural": "And __count__ other بطاقات",
|
||||
"apply": "طبق",
|
||||
"app-is-offline": "يتمّ تحميل ويكان، يرجى الانتظار. سيؤدي تحديث الصفحة إلى فقدان البيانات. إذا لم يتم تحميل ويكان، يرجى التحقق من أن خادم ويكان لم يتوقف. ",
|
||||
"archive": "Move to Archive",
|
||||
"archive-all": "Move All to Archive",
|
||||
"archive-board": "Move Board to Archive",
|
||||
"archive-card": "Move Card to Archive",
|
||||
"archive-list": "Move List to Archive",
|
||||
"archive-swimlane": "Move Swimlane to Archive",
|
||||
"archive-selection": "Move selection to Archive",
|
||||
"archiveBoardPopup-title": "Move Board to Archive?",
|
||||
"app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.",
|
||||
"archive": "نقل الى الارشيف",
|
||||
"archive-all": "نقل الكل الى الارشيف",
|
||||
"archive-board": "نقل اللوح الى الارشيف",
|
||||
"archive-card": "نقل البطاقة الى الارشيف",
|
||||
"archive-list": "نقل القائمة الى الارشيف",
|
||||
"archive-swimlane": "نقل خط السباحة الى الارشيف",
|
||||
"archive-selection": "نقل التحديد إلى الأرشيف",
|
||||
"archiveBoardPopup-title": "نقل الوح إلى الأرشيف",
|
||||
"archived-items": "أرشيف",
|
||||
"archived-boards": "Boards in Archive",
|
||||
"archived-boards": "الالواح في الأرشيف",
|
||||
"restore-board": "استعادة اللوحة",
|
||||
"no-archived-boards": "No Boards in Archive.",
|
||||
"no-archived-boards": "لا توجد لوحات في الأرشيف.",
|
||||
"archives": "أرشيف",
|
||||
"template": "Template",
|
||||
"templates": "Templates",
|
||||
"assign-member": "تعيين عضو",
|
||||
"attached": "أُرفق)",
|
||||
"attachment": "مرفق",
|
||||
@@ -110,25 +125,25 @@
|
||||
"boardChangeTitlePopup-title": "إعادة تسمية اللوحة",
|
||||
"boardChangeVisibilityPopup-title": "تعديل وضوح الرؤية",
|
||||
"boardChangeWatchPopup-title": "تغيير المتابعة",
|
||||
"boardMenuPopup-title": "قائمة اللوحة",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boards": "لوحات",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Calendar",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view": "عرض اللوحات",
|
||||
"board-view-cal": "التقويم",
|
||||
"board-view-swimlanes": "خطوط السباحة",
|
||||
"board-view-lists": "القائمات",
|
||||
"bucket-example": "مثل « todo list » على سبيل المثال",
|
||||
"cancel": "إلغاء",
|
||||
"card-archived": "This card is moved to Archive.",
|
||||
"board-archived": "This board is moved to Archive.",
|
||||
"card-archived": "البطاقة منقولة الى الارشيف",
|
||||
"board-archived": "اللوحات منقولة الى الارشيف",
|
||||
"card-comments-title": "%s تعليقات لهذه البطاقة",
|
||||
"card-delete-notice": "هذا حذف أبديّ . سوف تفقد كل الإجراءات المنوطة بهذه البطاقة",
|
||||
"card-delete-pop": "سيتم إزالة جميع الإجراءات من تبعات النشاط، وأنك لن تكون قادرا على إعادة فتح البطاقة. لا يوجد التراجع.",
|
||||
"card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.",
|
||||
"card-delete-suggest-archive": "يمكنك نقل بطاقة إلى الأرشيف لإزالتها من اللوحة والمحافظة على النشاط.",
|
||||
"card-due": "مستحق",
|
||||
"card-due-on": "مستحق في",
|
||||
"card-spent": "Spent Time",
|
||||
"card-spent": "امضى وقتا",
|
||||
"card-edit-attachments": "تعديل المرفقات",
|
||||
"card-edit-custom-fields": "Edit custom fields",
|
||||
"card-edit-custom-fields": "تعديل الحقل المعدل",
|
||||
"card-edit-labels": "تعديل العلامات",
|
||||
"card-edit-members": "تعديل الأعضاء",
|
||||
"card-labels-title": "تعديل علامات البطاقة.",
|
||||
@@ -136,18 +151,19 @@
|
||||
"card-start": "بداية",
|
||||
"card-start-on": "يبدأ في",
|
||||
"cardAttachmentsPopup-title": "إرفاق من",
|
||||
"cardCustomField-datePopup-title": "Change date",
|
||||
"cardCustomFieldsPopup-title": "Edit custom fields",
|
||||
"cardCustomField-datePopup-title": "تغير التاريخ",
|
||||
"cardCustomFieldsPopup-title": "تعديل الحقل المعدل",
|
||||
"cardDeletePopup-title": "حذف البطاقة ?",
|
||||
"cardDetailsActionsPopup-title": "إجراءات على البطاقة",
|
||||
"cardLabelsPopup-title": "علامات",
|
||||
"cardMembersPopup-title": "أعضاء",
|
||||
"cardMorePopup-title": "المزيد",
|
||||
"cardTemplatePopup-title": "Create template",
|
||||
"cards": "بطاقات",
|
||||
"cards-count": "بطاقات",
|
||||
"casSignIn": "Sign In with CAS",
|
||||
"cardType-card": "Card",
|
||||
"cardType-linkedCard": "Linked Card",
|
||||
"casSignIn": "تسجيل الدخول مع CAS",
|
||||
"cardType-card": "بطاقة",
|
||||
"cardType-linkedCard": "البطاقة المرتبطة",
|
||||
"cardType-linkedBoard": "Linked Board",
|
||||
"change": "Change",
|
||||
"change-avatar": "تعديل الصورة الشخصية",
|
||||
@@ -169,14 +185,30 @@
|
||||
"close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.",
|
||||
"color-black": "black",
|
||||
"color-blue": "blue",
|
||||
"color-crimson": "crimson",
|
||||
"color-darkgreen": "darkgreen",
|
||||
"color-gold": "gold",
|
||||
"color-gray": "gray",
|
||||
"color-green": "green",
|
||||
"color-indigo": "indigo",
|
||||
"color-lime": "lime",
|
||||
"color-magenta": "magenta",
|
||||
"color-mistyrose": "mistyrose",
|
||||
"color-navy": "navy",
|
||||
"color-orange": "orange",
|
||||
"color-paleturquoise": "paleturquoise",
|
||||
"color-peachpuff": "peachpuff",
|
||||
"color-pink": "pink",
|
||||
"color-plum": "plum",
|
||||
"color-purple": "purple",
|
||||
"color-red": "red",
|
||||
"color-saddlebrown": "saddlebrown",
|
||||
"color-silver": "silver",
|
||||
"color-sky": "sky",
|
||||
"color-slateblue": "slateblue",
|
||||
"color-white": "white",
|
||||
"color-yellow": "yellow",
|
||||
"unset-color": "Unset",
|
||||
"comment": "تعليق",
|
||||
"comment-placeholder": "أكتب تعليق",
|
||||
"comment-only": "التعليق فقط",
|
||||
@@ -187,8 +219,8 @@
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
"copy-card-link-to-clipboard": "نسخ رابط البطاقة إلى الحافظة",
|
||||
"linkCardPopup-title": "Link Card",
|
||||
"searchCardPopup-title": "Search Card",
|
||||
"linkCardPopup-title": "ربط البطاقة",
|
||||
"searchElementPopup-title": "بحث",
|
||||
"copyCardPopup-title": "نسخ البطاقة",
|
||||
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
|
||||
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
|
||||
@@ -283,20 +315,20 @@
|
||||
"import-board": "استيراد لوحة",
|
||||
"import-board-c": "استيراد لوحة",
|
||||
"import-board-title-trello": "Import board from Trello",
|
||||
"import-board-title-wekan": "استيراد لوحة من ويكان",
|
||||
"import-sandstorm-backup-warning": "Do not delete data you import from original Wekan or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
|
||||
"import-board-title-wekan": "Import board from previous export",
|
||||
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
|
||||
"import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
|
||||
"from-trello": "من تريلو",
|
||||
"from-wekan": "من ويكان",
|
||||
"from-wekan": "From previous export",
|
||||
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
|
||||
"import-board-instruction-wekan": "في لوحة ويكان الخاصة بك، انتقل إلى 'القائمة'، ثم 'تصدير اللوحة'، ونسخ النص في الملف الذي تم تنزيله.",
|
||||
"import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
|
||||
"import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.",
|
||||
"import-json-placeholder": "Paste your valid JSON data here",
|
||||
"import-map-members": "رسم خريطة الأعضاء",
|
||||
"import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
|
||||
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
|
||||
"import-show-user-mapping": "Review members mapping",
|
||||
"import-user-select": "Pick the Wekan user you want to use as this member",
|
||||
"importMapMembersAddPopup-title": "حدّد عضو ويكان",
|
||||
"import-user-select": "Pick your existing user you want to use as this member",
|
||||
"importMapMembersAddPopup-title": "Select member",
|
||||
"info": "الإصدار",
|
||||
"initials": "أولية",
|
||||
"invalid-date": "تاريخ غير صالح",
|
||||
@@ -319,15 +351,17 @@
|
||||
"list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.",
|
||||
"list-move-cards": "نقل بطاقات هذه القائمة",
|
||||
"list-select-cards": "تحديد بطاقات هذه القائمة",
|
||||
"set-color-list": "Set Color",
|
||||
"listActionPopup-title": "قائمة الإجراءات",
|
||||
"swimlaneActionPopup-title": "Swimlane Actions",
|
||||
"swimlaneAddPopup-title": "Add a Swimlane below",
|
||||
"listImportCardPopup-title": "Import a Trello card",
|
||||
"listMorePopup-title": "المزيد",
|
||||
"link-list": "رابط إلى هذه القائمة",
|
||||
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
|
||||
"list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.",
|
||||
"lists": "القائمات",
|
||||
"swimlanes": "Swimlanes",
|
||||
"swimlanes": "خطوط السباحة",
|
||||
"log-out": "تسجيل الخروج",
|
||||
"log-in": "تسجيل الدخول",
|
||||
"loginPopup-title": "تسجيل الدخول",
|
||||
@@ -435,6 +469,9 @@
|
||||
"welcome-swimlane": "Milestone 1",
|
||||
"welcome-list1": "المبادئ",
|
||||
"welcome-list2": "متقدم",
|
||||
"card-templates-swimlane": "Card Templates",
|
||||
"list-templates-swimlane": "List Templates",
|
||||
"board-templates-swimlane": "Board Templates",
|
||||
"what-to-do": "ماذا تريد أن تنجز?",
|
||||
"wipLimitErrorPopup-title": "Invalid WIP Limit",
|
||||
"wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.",
|
||||
@@ -460,16 +497,16 @@
|
||||
"send-smtp-test": "Send a test email to yourself",
|
||||
"invitation-code": "رمز الدعوة",
|
||||
"email-invite-register-subject": "__inviter__ أرسل دعوة لك",
|
||||
"email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to Wekan for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.",
|
||||
"email-smtp-test-subject": "SMTP Test Email From Wekan",
|
||||
"email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.",
|
||||
"email-smtp-test-subject": "SMTP Test Email",
|
||||
"email-smtp-test-text": "You have successfully sent an email",
|
||||
"error-invitation-code-not-exist": "رمز الدعوة غير موجود",
|
||||
"error-notAuthorized": "أنتَ لا تملك الصلاحيات لرؤية هذه الصفحة.",
|
||||
"outgoing-webhooks": "الويبهوك الصادرة",
|
||||
"outgoingWebhooksPopup-title": "الويبهوك الصادرة",
|
||||
"boardCardTitlePopup-title": "Card Title Filter",
|
||||
"new-outgoing-webhook": "ويبهوك جديدة ",
|
||||
"no-name": "(غير معروف)",
|
||||
"Wekan_version": "إصدار ويكان",
|
||||
"Node_version": "إصدار النود",
|
||||
"OS_Arch": "معمارية نظام التشغيل",
|
||||
"OS_Cpus": "استهلاك وحدة المعالجة المركزية لنظام التشغيل",
|
||||
@@ -480,6 +517,7 @@
|
||||
"OS_Totalmem": "الذاكرة الكلية لنظام التشغيل",
|
||||
"OS_Type": "نوع نظام التشغيل",
|
||||
"OS_Uptime": "مدة تشغيل نظام التشغيل",
|
||||
"days": "days",
|
||||
"hours": "الساعات",
|
||||
"minutes": "الدقائق",
|
||||
"seconds": "الثواني",
|
||||
@@ -500,6 +538,10 @@
|
||||
"card-end-on": "Ends on",
|
||||
"editCardReceivedDatePopup-title": "Change received date",
|
||||
"editCardEndDatePopup-title": "Change end date",
|
||||
"setCardColorPopup-title": "Set color",
|
||||
"setCardActionsColorPopup-title": "Choose a color",
|
||||
"setSwimlaneColorPopup-title": "Choose a color",
|
||||
"setListColorPopup-title": "Choose a color",
|
||||
"assigned-by": "Assigned By",
|
||||
"requested-by": "Requested By",
|
||||
"board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.",
|
||||
@@ -529,6 +571,8 @@
|
||||
"activity-added-label-card": "added label '%s'",
|
||||
"activity-removed-label-card": "removed label '%s'",
|
||||
"activity-delete-attach-card": "deleted an attachment",
|
||||
"activity-set-customfield": "set custom field '%s' to '%s' in %s",
|
||||
"activity-unset-customfield": "unset custom field '%s' in %s",
|
||||
"r-rule": "Rule",
|
||||
"r-add-trigger": "Add trigger",
|
||||
"r-add-action": "Add action",
|
||||
@@ -538,11 +582,14 @@
|
||||
"r-delete-rule": "Delete rule",
|
||||
"r-new-rule-name": "New rule title",
|
||||
"r-no-rules": "No rules",
|
||||
"r-when-a-card-is": "When a card is",
|
||||
"r-added-to": "Added to",
|
||||
"r-when-a-card": "When a card",
|
||||
"r-is": "is",
|
||||
"r-is-moved": "is moved",
|
||||
"r-added-to": "added to",
|
||||
"r-removed-from": "Removed from",
|
||||
"r-the-board": "the board",
|
||||
"r-list": "list",
|
||||
"set-filter": "Set Filter",
|
||||
"r-moved-to": "Moved to",
|
||||
"r-moved-from": "Moved from",
|
||||
"r-archived": "Moved to Archive",
|
||||
@@ -550,11 +597,10 @@
|
||||
"r-a-card": "a card",
|
||||
"r-when-a-label-is": "When a label is",
|
||||
"r-when-the-label-is": "When the label is",
|
||||
"r-list-name": "List name",
|
||||
"r-list-name": "list name",
|
||||
"r-when-a-member": "When a member is",
|
||||
"r-when-the-member": "When the member",
|
||||
"r-name": "name",
|
||||
"r-is": "is",
|
||||
"r-when-a-attach": "When an attachment",
|
||||
"r-when-a-checklist": "When a checklist is",
|
||||
"r-when-the-checklist": "When the checklist",
|
||||
@@ -568,7 +614,7 @@
|
||||
"r-top-of": "Top of",
|
||||
"r-bottom-of": "Bottom of",
|
||||
"r-its-list": "its list",
|
||||
"r-archive": "Move to Archive",
|
||||
"r-archive": "نقل الى الارشيف",
|
||||
"r-unarchive": "Restore from Archive",
|
||||
"r-card": "card",
|
||||
"r-add": "أضف",
|
||||
@@ -576,6 +622,7 @@
|
||||
"r-label": "label",
|
||||
"r-member": "member",
|
||||
"r-remove-all": "Remove all members from the card",
|
||||
"r-set-color": "Set color to",
|
||||
"r-checklist": "checklist",
|
||||
"r-check-all": "Check all",
|
||||
"r-uncheck-all": "Uncheck all",
|
||||
@@ -600,6 +647,9 @@
|
||||
"r-d-unarchive": "Restore card from Archive",
|
||||
"r-d-add-label": "Add label",
|
||||
"r-d-remove-label": "Remove label",
|
||||
"r-create-card": "Create new card",
|
||||
"r-in-list": "in list",
|
||||
"r-in-swimlane": "in swimlane",
|
||||
"r-d-add-member": "Add member",
|
||||
"r-d-remove-member": "Remove member",
|
||||
"r-d-remove-all-member": "Remove all member",
|
||||
@@ -610,6 +660,14 @@
|
||||
"r-d-check-of-list": "of checklist",
|
||||
"r-d-add-checklist": "Add checklist",
|
||||
"r-d-remove-checklist": "Remove checklist",
|
||||
"r-by": "by",
|
||||
"r-add-checklist": "Add checklist",
|
||||
"r-with-items": "with items",
|
||||
"r-items-list": "item1,item2,item3",
|
||||
"r-add-swimlane": "Add swimlane",
|
||||
"r-swimlane-name": "swimlane name",
|
||||
"r-board-note": "Note: leave a field empty to match every possible value.",
|
||||
"r-checklist-note": "Note: checklist's items have to be written as comma separated values.",
|
||||
"r-when-a-card-is-moved": "When a card is moved to another list",
|
||||
"ldap": "LDAP",
|
||||
"oauth2": "OAuth2",
|
||||
@@ -618,5 +676,13 @@
|
||||
"authentication-type": "Authentication type",
|
||||
"custom-product-name": "Custom Product Name",
|
||||
"layout": "Layout",
|
||||
"hide-logo": "Hide Logo"
|
||||
"hide-logo": "Hide Logo",
|
||||
"add-custom-html-after-body-start": "Add Custom HTML after <body> start",
|
||||
"add-custom-html-before-body-end": "Add Custom HTML before </body> end",
|
||||
"error-undefined": "Something went wrong",
|
||||
"error-ldap-login": "An error occurred while trying to login",
|
||||
"display-authentication-method": "Display Authentication Method",
|
||||
"default-authentication-method": "Default Authentication Method",
|
||||
"duplicate-board": "Duplicate Board",
|
||||
"people-number": "The number of people is:"
|
||||
}
|
||||
+169
-103
@@ -1,35 +1,48 @@
|
||||
{
|
||||
"accept": "Приемам",
|
||||
"act-activity-notify": "[Wekan] Известия за дейности",
|
||||
"act-addAttachment": "прикачи __attachment__ към __card__",
|
||||
"act-addSubtask": "добави задача __checklist__ към __card__",
|
||||
"act-addChecklist": "добави списък със задачи __checklist__ към __card__",
|
||||
"act-addChecklistItem": "добави __checklistItem__ към списък със задачи __checklist__ в __card__",
|
||||
"act-addComment": "Коментира в __card__: __comment__",
|
||||
"act-createBoard": "създаде __board__",
|
||||
"act-createCard": "добави __card__ към __list__",
|
||||
"act-createCustomField": "създаде собствено поле __customField__",
|
||||
"act-createList": "добави __list__ към __board__",
|
||||
"act-addBoardMember": "добави __member__ към __board__",
|
||||
"act-archivedBoard": "__board__ moved to Archive",
|
||||
"act-archivedCard": "__card__ moved to Archive",
|
||||
"act-archivedList": "__list__ moved to Archive",
|
||||
"act-archivedSwimlane": "__swimlane__ moved to Archive",
|
||||
"act-importBoard": "импортира __board__",
|
||||
"act-importCard": "импортира __card__",
|
||||
"act-importList": "импортира __list__",
|
||||
"act-joinMember": "добави __member__ към __card__",
|
||||
"act-moveCard": "премести __card__ от __oldList__ в __list__",
|
||||
"act-removeBoardMember": "премахна __member__ от __board__",
|
||||
"act-restoredCard": "възстанови __card__ в __board__",
|
||||
"act-unjoinMember": "премахна __member__ от __card__",
|
||||
"act-withBoardTitle": "[Wekan] __board__",
|
||||
"act-activity-notify": "Activity Notification",
|
||||
"act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createBoard": "created board __board__",
|
||||
"act-createSwimlane": "created swimlane __swimlane__ to board __board__",
|
||||
"act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createCustomField": "created custom field __customField__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createList": "added list __list__ to board __board__",
|
||||
"act-addBoardMember": "added member __member__ to board __board__",
|
||||
"act-archivedBoard": "Board __board__ moved to Archive",
|
||||
"act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-importBoard": "imported board __board__",
|
||||
"act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__",
|
||||
"act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__",
|
||||
"act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoardMember": "removed member __member__ from board __board__",
|
||||
"act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-withBoardTitle": "__board__ ",
|
||||
"act-withCardTitle": "[__board__] __card__",
|
||||
"actions": "Действия",
|
||||
"activities": "Действия",
|
||||
"activity": "Дейности",
|
||||
"activity-added": "добави %s към %s",
|
||||
"activity-archived": "%s moved to Archive",
|
||||
"activity-archived": "%s е преместена в Архива",
|
||||
"activity-attached": "прикачи %s към %s",
|
||||
"activity-created": "създаде %s",
|
||||
"activity-customfield-created": "създаде собствено поле %s",
|
||||
@@ -47,15 +60,15 @@
|
||||
"activity-unchecked-item": "размаркира %s от списък със задачи %s на %s",
|
||||
"activity-checklist-added": "добави списък със задачи към %s",
|
||||
"activity-checklist-removed": "премахна списък със задачи от %s",
|
||||
"activity-checklist-completed": "завърши списък със задачи %s на %s",
|
||||
"activity-checklist-uncompleted": "uncompleted the checklist %s of %s",
|
||||
"activity-checklist-completed": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"activity-checklist-uncompleted": "\"отзавърши\" чеклистта %s в %s",
|
||||
"activity-checklist-item-added": "добави точка към '%s' в/във %s",
|
||||
"activity-checklist-item-removed": "removed a checklist item from '%s' in %s",
|
||||
"activity-checklist-item-removed": "премахна точка от '%s' в %s",
|
||||
"add": "Добави",
|
||||
"activity-checked-item-card": "checked %s in checklist %s",
|
||||
"activity-unchecked-item-card": "unchecked %s in checklist %s",
|
||||
"activity-checklist-completed-card": "completed the checklist %s",
|
||||
"activity-checklist-uncompleted-card": "uncompleted the checklist %s",
|
||||
"activity-checked-item-card": "отбеляза %s в чеклист %s",
|
||||
"activity-unchecked-item-card": "размаркира %s в чеклист %s",
|
||||
"activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"activity-checklist-uncompleted-card": "\"отзавърши\" чеклистта %s",
|
||||
"add-attachment": "Добави прикачен файл",
|
||||
"add-board": "Добави Табло",
|
||||
"add-card": "Добави карта",
|
||||
@@ -78,20 +91,22 @@
|
||||
"and-n-other-card": "И __count__ друга карта",
|
||||
"and-n-other-card_plural": "И __count__ други карти",
|
||||
"apply": "Приложи",
|
||||
"app-is-offline": "Wekan зарежда, моля изчакайте! Презареждането на страницата може да доведе до загуба на данни. Ако Wekan не се зареди, моля проверете дали сървърът му работи.",
|
||||
"archive": "Move to Archive",
|
||||
"archive-all": "Move All to Archive",
|
||||
"archive-board": "Move Board to Archive",
|
||||
"archive-card": "Move Card to Archive",
|
||||
"archive-list": "Move List to Archive",
|
||||
"archive-swimlane": "Move Swimlane to Archive",
|
||||
"archive-selection": "Move selection to Archive",
|
||||
"archiveBoardPopup-title": "Move Board to Archive?",
|
||||
"app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.",
|
||||
"archive": "Премести в Архива",
|
||||
"archive-all": "Премести всички в Архива",
|
||||
"archive-board": "Премести Таблото в Архива",
|
||||
"archive-card": "Премести Картата в Архива",
|
||||
"archive-list": "Премести Списъка в Архива",
|
||||
"archive-swimlane": "Премести Коридора в Архива",
|
||||
"archive-selection": "Премести избраното в Архива",
|
||||
"archiveBoardPopup-title": "Да преместя ли Таблото в Архива?",
|
||||
"archived-items": "Архив",
|
||||
"archived-boards": "Boards in Archive",
|
||||
"archived-boards": "Табла в Архива",
|
||||
"restore-board": "Възстанови Таблото",
|
||||
"no-archived-boards": "No Boards in Archive.",
|
||||
"no-archived-boards": "Няма Табла в Архива.",
|
||||
"archives": "Архив",
|
||||
"template": "Template",
|
||||
"templates": "Templates",
|
||||
"assign-member": "Възложи на член от екипа",
|
||||
"attached": "прикачен",
|
||||
"attachment": "Прикаченн файл",
|
||||
@@ -110,7 +125,7 @@
|
||||
"boardChangeTitlePopup-title": "Промени името на Таблото",
|
||||
"boardChangeVisibilityPopup-title": "Change Visibility",
|
||||
"boardChangeWatchPopup-title": "Промени наблюдаването",
|
||||
"boardMenuPopup-title": "Меню на Таблото",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boards": "Табла",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Календар",
|
||||
@@ -118,12 +133,12 @@
|
||||
"board-view-lists": "Списъци",
|
||||
"bucket-example": "Like “Bucket List” for example",
|
||||
"cancel": "Cancel",
|
||||
"card-archived": "This card is moved to Archive.",
|
||||
"board-archived": "This board is moved to Archive.",
|
||||
"card-archived": "Тази карта е преместена в Архива.",
|
||||
"board-archived": "Това табло е преместено в Архива.",
|
||||
"card-comments-title": "Тази карта има %s коментар.",
|
||||
"card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.",
|
||||
"card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.",
|
||||
"card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.",
|
||||
"card-delete-suggest-archive": "Можете да преместите картата в Архива, за да я премахнете от Таблото и така да запазите активността в него.",
|
||||
"card-due": "Готова за",
|
||||
"card-due-on": "Готова за",
|
||||
"card-spent": "Изработено време",
|
||||
@@ -143,6 +158,7 @@
|
||||
"cardLabelsPopup-title": "Етикети",
|
||||
"cardMembersPopup-title": "Членове",
|
||||
"cardMorePopup-title": "Още",
|
||||
"cardTemplatePopup-title": "Create template",
|
||||
"cards": "Карти",
|
||||
"cards-count": "Карти",
|
||||
"casSignIn": "Sign In with CAS",
|
||||
@@ -166,17 +182,33 @@
|
||||
"clipboard": "Клипборда или с драг & дроп",
|
||||
"close": "Затвори",
|
||||
"close-board": "Затвори Таблото",
|
||||
"close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.",
|
||||
"close-board-pop": "Ще можете да възстановите Таблото като натиснете на бутона \"Архив\" в началото на хедъра.",
|
||||
"color-black": "черно",
|
||||
"color-blue": "синьо",
|
||||
"color-crimson": "crimson",
|
||||
"color-darkgreen": "darkgreen",
|
||||
"color-gold": "gold",
|
||||
"color-gray": "gray",
|
||||
"color-green": "зелено",
|
||||
"color-indigo": "indigo",
|
||||
"color-lime": "лайм",
|
||||
"color-magenta": "magenta",
|
||||
"color-mistyrose": "mistyrose",
|
||||
"color-navy": "navy",
|
||||
"color-orange": "оранжево",
|
||||
"color-paleturquoise": "paleturquoise",
|
||||
"color-peachpuff": "peachpuff",
|
||||
"color-pink": "розово",
|
||||
"color-plum": "plum",
|
||||
"color-purple": "пурпурно",
|
||||
"color-red": "червено",
|
||||
"color-saddlebrown": "saddlebrown",
|
||||
"color-silver": "silver",
|
||||
"color-sky": "светло синьо",
|
||||
"color-slateblue": "slateblue",
|
||||
"color-white": "бяло",
|
||||
"color-yellow": "жълто",
|
||||
"unset-color": "Unset",
|
||||
"comment": "Коментирай",
|
||||
"comment-placeholder": "Напиши коментар",
|
||||
"comment-only": "Само коментар",
|
||||
@@ -188,7 +220,7 @@
|
||||
"confirm-checklist-delete-dialog": "Сигурни ли сте, че искате да изтриете този чеклист?",
|
||||
"copy-card-link-to-clipboard": "Копирай връзката на картата в клипборда",
|
||||
"linkCardPopup-title": "Свържи картата",
|
||||
"searchCardPopup-title": "Търсене на карта",
|
||||
"searchElementPopup-title": "Търсене",
|
||||
"copyCardPopup-title": "Копирай картата",
|
||||
"copyChecklistToManyCardsPopup-title": "Копирай чеклисти в други карти",
|
||||
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
|
||||
@@ -283,20 +315,20 @@
|
||||
"import-board": "Импортирай Табло",
|
||||
"import-board-c": "Импортирай Табло",
|
||||
"import-board-title-trello": "Импорт на табло от Trello",
|
||||
"import-board-title-wekan": "Импортирай табло от Wekan",
|
||||
"import-sandstorm-backup-warning": "Do not delete data you import from original Wekan or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
|
||||
"import-board-title-wekan": "Import board from previous export",
|
||||
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
|
||||
"import-sandstorm-warning": "Импортирането ще изтрие всичката налична информация в таблото и ще я замени с нова.",
|
||||
"from-trello": "От Trello",
|
||||
"from-wekan": "От Wekan",
|
||||
"from-wekan": "From previous export",
|
||||
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
|
||||
"import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
|
||||
"import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
|
||||
"import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.",
|
||||
"import-json-placeholder": "Копирайте валидната Ви JSON информация тук",
|
||||
"import-map-members": "Map members",
|
||||
"import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
|
||||
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
|
||||
"import-show-user-mapping": "Review members mapping",
|
||||
"import-user-select": "Pick the Wekan user you want to use as this member",
|
||||
"importMapMembersAddPopup-title": "Избери Wekan член",
|
||||
"import-user-select": "Pick your existing user you want to use as this member",
|
||||
"importMapMembersAddPopup-title": "Select member",
|
||||
"info": "Версия",
|
||||
"initials": "Инициали",
|
||||
"invalid-date": "Невалидна дата",
|
||||
@@ -315,17 +347,19 @@
|
||||
"leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.",
|
||||
"leaveBoardPopup-title": "Leave Board ?",
|
||||
"link-card": "Връзка към тази карта",
|
||||
"list-archive-cards": "Move all cards in this list to Archive",
|
||||
"list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.",
|
||||
"list-archive-cards": "Премести всички карти от този списък в Архива",
|
||||
"list-archive-cards-pop": "Това ще премахне всички карти от този Списък от Таблото. За да видите картите в Архива и да ги върнете натиснете на \"Меню\" > \"Архив\".",
|
||||
"list-move-cards": "Премести всички карти в този списък",
|
||||
"list-select-cards": "Избери всички карти в този списък",
|
||||
"set-color-list": "Set Color",
|
||||
"listActionPopup-title": "List Actions",
|
||||
"swimlaneActionPopup-title": "Swimlane Actions",
|
||||
"swimlaneAddPopup-title": "Add a Swimlane below",
|
||||
"listImportCardPopup-title": "Импорт на карта от Trello",
|
||||
"listMorePopup-title": "Още",
|
||||
"link-list": "Връзка към този списък",
|
||||
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
|
||||
"list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.",
|
||||
"list-delete-suggest-archive": "Можете да преместите списъка в Архива, за да го премахнете от Таблото и така да запазите активността в него.",
|
||||
"lists": "Списъци",
|
||||
"swimlanes": "Коридори",
|
||||
"log-out": "Изход",
|
||||
@@ -345,9 +379,9 @@
|
||||
"muted-info": "You will never be notified of any changes in this board",
|
||||
"my-boards": "Моите табла",
|
||||
"name": "Име",
|
||||
"no-archived-cards": "No cards in Archive.",
|
||||
"no-archived-lists": "No lists in Archive.",
|
||||
"no-archived-swimlanes": "No swimlanes in Archive.",
|
||||
"no-archived-cards": "Няма карти в Архива.",
|
||||
"no-archived-lists": "Няма списъци в Архива.",
|
||||
"no-archived-swimlanes": "Няма коридори в Архива.",
|
||||
"no-results": "No results",
|
||||
"normal": "Normal",
|
||||
"normal-desc": "Can view and edit cards. Can't change settings.",
|
||||
@@ -427,7 +461,7 @@
|
||||
"uploaded-avatar": "Качихте аватар",
|
||||
"username": "Потребителско име",
|
||||
"view-it": "View it",
|
||||
"warn-list-archived": "warning: this card is in an list at Archive",
|
||||
"warn-list-archived": "внимание: тази карта е в списък в Архива",
|
||||
"watch": "Наблюдавай",
|
||||
"watching": "Наблюдава",
|
||||
"watching-info": "You will be notified of any change in this board",
|
||||
@@ -435,6 +469,9 @@
|
||||
"welcome-swimlane": "Milestone 1",
|
||||
"welcome-list1": "Basics",
|
||||
"welcome-list2": "Advanced",
|
||||
"card-templates-swimlane": "Card Templates",
|
||||
"list-templates-swimlane": "List Templates",
|
||||
"board-templates-swimlane": "Board Templates",
|
||||
"what-to-do": "What do you want to do?",
|
||||
"wipLimitErrorPopup-title": "Невалиден WIP лимит",
|
||||
"wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.",
|
||||
@@ -446,7 +483,7 @@
|
||||
"disable-self-registration": "Disable Self-Registration",
|
||||
"invite": "Покани",
|
||||
"invite-people": "Покани хора",
|
||||
"to-boards": "To board(s)",
|
||||
"to-boards": "в табло/а",
|
||||
"email-addresses": "Имейл адреси",
|
||||
"smtp-host-description": "Адресът на SMTP сървъра, който обслужва Вашите имейли.",
|
||||
"smtp-port-description": "Портът, който Вашият SMTP сървър използва за изходящи имейли.",
|
||||
@@ -460,16 +497,16 @@
|
||||
"send-smtp-test": "Изпрати тестов имейл на себе си",
|
||||
"invitation-code": "Invitation Code",
|
||||
"email-invite-register-subject": "__inviter__ sent you an invitation",
|
||||
"email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to Wekan for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.",
|
||||
"email-smtp-test-subject": "SMTP тестов имейл, изпратен от Wekan",
|
||||
"email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.",
|
||||
"email-smtp-test-subject": "SMTP Test Email",
|
||||
"email-smtp-test-text": "Успешно изпратихте имейл",
|
||||
"error-invitation-code-not-exist": "Invitation code doesn't exist",
|
||||
"error-notAuthorized": "You are not authorized to view this page.",
|
||||
"outgoing-webhooks": "Outgoing Webhooks",
|
||||
"outgoingWebhooksPopup-title": "Outgoing Webhooks",
|
||||
"boardCardTitlePopup-title": "Card Title Filter",
|
||||
"new-outgoing-webhook": "New Outgoing Webhook",
|
||||
"no-name": "(Unknown)",
|
||||
"Wekan_version": "Версия на Wekan",
|
||||
"Node_version": "Версия на Node",
|
||||
"OS_Arch": "Архитектура на ОС",
|
||||
"OS_Cpus": "Брой CPU ядра",
|
||||
@@ -480,6 +517,7 @@
|
||||
"OS_Totalmem": "ОС Общо памет",
|
||||
"OS_Type": "Тип ОС",
|
||||
"OS_Uptime": "OS Ъптайм",
|
||||
"days": "дни",
|
||||
"hours": "часа",
|
||||
"minutes": "минути",
|
||||
"seconds": "секунди",
|
||||
@@ -500,15 +538,19 @@
|
||||
"card-end-on": "Завършена на",
|
||||
"editCardReceivedDatePopup-title": "Промени датата на получаване",
|
||||
"editCardEndDatePopup-title": "Промени датата на завършване",
|
||||
"assigned-by": "Assigned By",
|
||||
"requested-by": "Requested By",
|
||||
"setCardColorPopup-title": "Set color",
|
||||
"setCardActionsColorPopup-title": "Choose a color",
|
||||
"setSwimlaneColorPopup-title": "Choose a color",
|
||||
"setListColorPopup-title": "Choose a color",
|
||||
"assigned-by": "Разпределена от",
|
||||
"requested-by": "Поискан от",
|
||||
"board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.",
|
||||
"delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.",
|
||||
"boardDeletePopup-title": "Delete Board?",
|
||||
"delete-board": "Delete Board",
|
||||
"boardDeletePopup-title": "Изтриване на Таблото?",
|
||||
"delete-board": "Изтрий таблото",
|
||||
"default-subtasks-board": "Подзадачи за табло __board__",
|
||||
"default": "по подразбиране",
|
||||
"queue": "Queue",
|
||||
"queue": "Опашка",
|
||||
"subtask-settings": "Настройки на Подзадачите",
|
||||
"boardSubtaskSettingsPopup-title": "Настройки за Подзадачите за това Табло",
|
||||
"show-subtasks-field": "Картата може да има подзадачи",
|
||||
@@ -523,38 +565,42 @@
|
||||
"parent-card": "Карта-източник",
|
||||
"source-board": "Source board",
|
||||
"no-parent": "Не показвай източника",
|
||||
"activity-added-label": "added label '%s' to %s",
|
||||
"activity-removed-label": "removed label '%s' from %s",
|
||||
"activity-delete-attach": "deleted an attachment from %s",
|
||||
"activity-added-label-card": "added label '%s'",
|
||||
"activity-removed-label-card": "removed label '%s'",
|
||||
"activity-delete-attach-card": "deleted an attachment",
|
||||
"activity-added-label": "добави етикет '%s' към %s",
|
||||
"activity-removed-label": "премахна етикет '%s' от %s",
|
||||
"activity-delete-attach": "изтри прикачен файл от %s",
|
||||
"activity-added-label-card": "добави етикет '%s'",
|
||||
"activity-removed-label-card": "премахна етикет '%s'",
|
||||
"activity-delete-attach-card": "изтри прикачения файл",
|
||||
"activity-set-customfield": "set custom field '%s' to '%s' in %s",
|
||||
"activity-unset-customfield": "unset custom field '%s' in %s",
|
||||
"r-rule": "Правило",
|
||||
"r-add-trigger": "Add trigger",
|
||||
"r-add-action": "Add action",
|
||||
"r-add-trigger": "Добави спусък",
|
||||
"r-add-action": "Добави действие",
|
||||
"r-board-rules": "Правила за таблото",
|
||||
"r-add-rule": "Добави правилото",
|
||||
"r-view-rule": "Виж правилото",
|
||||
"r-delete-rule": "Изтрий правилото",
|
||||
"r-new-rule-name": "Заглавие за новото правило",
|
||||
"r-no-rules": "Няма правила",
|
||||
"r-when-a-card-is": "When a card is",
|
||||
"r-added-to": "Added to",
|
||||
"r-removed-from": "Removed from",
|
||||
"r-the-board": "the board",
|
||||
"r-list": "list",
|
||||
"r-when-a-card": "Когато карта",
|
||||
"r-is": "е",
|
||||
"r-is-moved": "преместена",
|
||||
"r-added-to": "добавена в",
|
||||
"r-removed-from": "премахната от",
|
||||
"r-the-board": "таблото",
|
||||
"r-list": "списък",
|
||||
"set-filter": "Set Filter",
|
||||
"r-moved-to": "Moved to",
|
||||
"r-moved-from": "Moved from",
|
||||
"r-archived": "Moved to Archive",
|
||||
"r-unarchived": "Restored from Archive",
|
||||
"r-a-card": "a card",
|
||||
"r-archived": "Преместено в Архива",
|
||||
"r-unarchived": "Възстановено от Архива",
|
||||
"r-a-card": "карта",
|
||||
"r-when-a-label-is": "When a label is",
|
||||
"r-when-the-label-is": "When the label is",
|
||||
"r-list-name": "List name",
|
||||
"r-list-name": "list name",
|
||||
"r-when-a-member": "When a member is",
|
||||
"r-when-the-member": "When the member",
|
||||
"r-name": "name",
|
||||
"r-is": "is",
|
||||
"r-name": "име",
|
||||
"r-when-a-attach": "When an attachment",
|
||||
"r-when-a-checklist": "When a checklist is",
|
||||
"r-when-the-checklist": "When the checklist",
|
||||
@@ -564,18 +610,19 @@
|
||||
"r-when-the-item": "When the checklist item",
|
||||
"r-checked": "Checked",
|
||||
"r-unchecked": "Unchecked",
|
||||
"r-move-card-to": "Move card to",
|
||||
"r-top-of": "Top of",
|
||||
"r-bottom-of": "Bottom of",
|
||||
"r-its-list": "its list",
|
||||
"r-archive": "Move to Archive",
|
||||
"r-unarchive": "Restore from Archive",
|
||||
"r-card": "card",
|
||||
"r-move-card-to": "Премести картата в",
|
||||
"r-top-of": "началото на",
|
||||
"r-bottom-of": "края на",
|
||||
"r-its-list": "списъка й",
|
||||
"r-archive": "Премести в Архива",
|
||||
"r-unarchive": "Възстанови от Архива",
|
||||
"r-card": "карта",
|
||||
"r-add": "Добави",
|
||||
"r-remove": "Remove",
|
||||
"r-label": "label",
|
||||
"r-member": "member",
|
||||
"r-remove-all": "Remove all members from the card",
|
||||
"r-set-color": "Set color to",
|
||||
"r-checklist": "checklist",
|
||||
"r-check-all": "Check all",
|
||||
"r-uncheck-all": "Uncheck all",
|
||||
@@ -596,10 +643,13 @@
|
||||
"r-d-send-email-to": "to",
|
||||
"r-d-send-email-subject": "subject",
|
||||
"r-d-send-email-message": "message",
|
||||
"r-d-archive": "Move card to Archive",
|
||||
"r-d-unarchive": "Restore card from Archive",
|
||||
"r-d-archive": "Премести картата в Архива",
|
||||
"r-d-unarchive": "Възстанови картата от Архива",
|
||||
"r-d-add-label": "Add label",
|
||||
"r-d-remove-label": "Remove label",
|
||||
"r-create-card": "Create new card",
|
||||
"r-in-list": "in list",
|
||||
"r-in-swimlane": "in swimlane",
|
||||
"r-d-add-member": "Add member",
|
||||
"r-d-remove-member": "Remove member",
|
||||
"r-d-remove-all-member": "Remove all member",
|
||||
@@ -608,8 +658,16 @@
|
||||
"r-d-check-one": "Check item",
|
||||
"r-d-uncheck-one": "Uncheck item",
|
||||
"r-d-check-of-list": "of checklist",
|
||||
"r-d-add-checklist": "Add checklist",
|
||||
"r-d-remove-checklist": "Remove checklist",
|
||||
"r-d-add-checklist": "Добави чеклист",
|
||||
"r-d-remove-checklist": "Премахни чеклист",
|
||||
"r-by": "by",
|
||||
"r-add-checklist": "Добави чеклист",
|
||||
"r-with-items": "с точки",
|
||||
"r-items-list": "точка1,точка2,точка3",
|
||||
"r-add-swimlane": "Добави коридор",
|
||||
"r-swimlane-name": "име на коридора",
|
||||
"r-board-note": "Note: leave a field empty to match every possible value.",
|
||||
"r-checklist-note": "Note: checklist's items have to be written as comma separated values.",
|
||||
"r-when-a-card-is-moved": "When a card is moved to another list",
|
||||
"ldap": "LDAP",
|
||||
"oauth2": "OAuth2",
|
||||
@@ -618,5 +676,13 @@
|
||||
"authentication-type": "Authentication type",
|
||||
"custom-product-name": "Custom Product Name",
|
||||
"layout": "Layout",
|
||||
"hide-logo": "Hide Logo"
|
||||
"hide-logo": "Hide Logo",
|
||||
"add-custom-html-after-body-start": "Add Custom HTML after <body> start",
|
||||
"add-custom-html-before-body-end": "Add Custom HTML before </body> end",
|
||||
"error-undefined": "Something went wrong",
|
||||
"error-ldap-login": "An error occurred while trying to login",
|
||||
"display-authentication-method": "Display Authentication Method",
|
||||
"default-authentication-method": "Default Authentication Method",
|
||||
"duplicate-board": "Duplicate Board",
|
||||
"people-number": "The number of people is:"
|
||||
}
|
||||
Loaded 100 of 563 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user