mirror of
https://github.com/wekan/wekan.git
synced 2026-11-08 11:25:19 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c5436087 | ||
|
|
6592102e8f | ||
|
|
06a5a8f70d | ||
|
|
ef54ebada6 | ||
|
|
d4f13de1d9 | ||
|
|
4fcedde529 | ||
|
|
a4518bbefc | ||
|
|
95f771aa26 | ||
|
|
da98942cce | ||
|
|
f3efaf59e1 | ||
|
|
d64032f2a3 | ||
|
|
abad8cc4d5 | ||
|
|
0d9536e2f9 | ||
|
|
67b078b805 | ||
|
|
6f02eeae53 | ||
|
|
5bc03b23ea | ||
|
|
3d4acd8c8f | ||
|
|
448bec8181 | ||
|
|
0a34ee1b64 | ||
|
|
34e8e4d4c3 | ||
|
|
32627c03f4 | ||
|
|
63c314ca18 | ||
|
|
e8453783da | ||
|
|
96522ec3a3 | ||
|
|
17dedab391 | ||
|
|
283d2ee09c | ||
|
|
289ff0127e | ||
|
|
931d7217b1 | ||
|
|
3149a3927e | ||
|
|
c57bced7b1 | ||
|
|
8d794a59dc | ||
|
|
7bb1e24bda | ||
|
|
e0013b9b63 | ||
|
|
7d81aab900 | ||
|
|
cc99da5357 | ||
|
|
9bd21e1d1b | ||
|
|
2148aeea42 | ||
|
|
6a7a5505f9 | ||
|
|
5a6faafa30 | ||
|
|
e2f3dad779 | ||
|
|
ae2aa1f5cd | ||
|
|
0e3a17d922 | ||
|
|
033919a270 | ||
|
|
f5d40a0a12 | ||
|
|
0fd781e80a | ||
|
|
a8f6170fdf | ||
|
|
bd8c565415 | ||
|
|
ffb02fe0ec | ||
|
|
114520302c | ||
|
|
317138ab72 | ||
|
|
a990109f43 | ||
|
|
da68b01502 | ||
|
|
e90bc744d9 | ||
|
|
2b5c56484a | ||
|
|
ea30612013 | ||
|
|
4445612bb8 | ||
|
|
da56393078 | ||
|
|
a86ff1e8d0 | ||
|
|
7fce5650b8 | ||
|
|
b949357e66 | ||
|
|
8e71b68dbf | ||
|
|
b77eed221f | ||
|
|
1fa91474d0 | ||
|
|
fc32a89292 | ||
|
|
ea24010298 | ||
|
|
ae1f80a52c | ||
|
|
2f95431c9b | ||
|
|
d59683eff1 | ||
|
|
de77776cd0 | ||
|
|
74ccfea570 | ||
|
|
1f5a549589 | ||
|
|
3ccdc2e307 | ||
|
|
688b725807 | ||
|
|
ae01ea576c | ||
|
|
cd0cd64849 | ||
|
|
aab671398c | ||
|
|
d64aeb25d5 | ||
|
|
aa4fa83127 | ||
|
|
81c3dc1d95 | ||
|
|
746eecf3d8 | ||
|
|
cd948fb576 | ||
|
|
4ec4e19e63 | ||
|
|
d88d197de9 | ||
|
|
2a24918a9c | ||
|
|
d2a85b41e4 | ||
|
|
9bcd991a61 | ||
|
|
1a7bd65e59 | ||
|
|
f8ee929cf7 | ||
|
|
734165f3c7 | ||
|
|
8f9ef1bde8 | ||
|
|
2119c6ab0c | ||
|
|
a85479f73c | ||
|
|
21ba0a9606 | ||
|
|
82ba0f9593 | ||
|
|
7769124401 | ||
|
|
d9c978e7fc | ||
|
|
90899f0928 | ||
|
|
f0421da517 | ||
|
|
573d4bf2cb | ||
|
|
f1e1fd3593 | ||
|
|
c387c5bc34 | ||
|
|
0549bc0b0c | ||
|
|
c2a3e11324 | ||
|
|
107e2ac900 | ||
|
|
3b60bdea14 | ||
|
|
f6591d7820 | ||
|
|
6bbd622066 | ||
|
|
ee79cab7b2 | ||
|
|
a0b94065c5 | ||
|
|
e1fa607f87 | ||
|
|
2e91a359f5 | ||
|
|
9720e703fd | ||
|
|
f88898d5b8 | ||
|
|
30c1597b65 | ||
|
|
5bc5171220 | ||
|
|
d0f118e7af | ||
|
|
c481443667 | ||
|
|
b87cff1289 | ||
|
|
0c080830bc | ||
|
|
cdd7d69c66 | ||
|
|
c1cbcdcc72 | ||
|
|
1c84b19f24 | ||
|
|
2c73dae019 | ||
|
|
d285850a04 | ||
|
|
719ef87efc | ||
|
|
039b9abf5e | ||
|
|
39daf56811 | ||
|
|
a8de2f224f | ||
|
|
dda013844c | ||
|
|
3e9481c5bd | ||
|
|
85ac03a892 | ||
|
|
752699d1c2 | ||
|
|
339ca581ab | ||
|
|
20e9cf1144 | ||
|
|
9737884c4b | ||
|
|
2f5670e830 | ||
|
|
108b18cebf | ||
|
|
814ac22a0d | ||
|
|
3814a218c2 | ||
|
|
3fda90612d | ||
|
|
05762aa50c | ||
|
|
9a31371de0 | ||
|
|
b9b7143185 | ||
|
|
972721bdb5 | ||
|
|
3a48982916 | ||
|
|
6b3a6f9f7d | ||
|
|
48fddecb62 | ||
|
|
7f0d1cc5b3 | ||
|
|
70e82be0b4 | ||
|
|
5fc0809844 | ||
|
|
c79990bcd9 | ||
|
|
cd9e7412ea | ||
|
|
3e472606b5 | ||
|
|
2966bc3204 | ||
|
|
5dc6093eec | ||
|
|
86fc60f5ba | ||
|
|
8189a3f9ab | ||
|
|
76a65e2533 | ||
|
|
ad2914934e | ||
|
|
2769ba2874 | ||
|
|
294f1e178e | ||
|
|
c994d34bb8 | ||
|
|
42b5dd0751 | ||
|
|
f884d6d471 | ||
|
|
abe915a8d6 | ||
|
|
a2e8de2fd8 | ||
|
|
e34ca6a8a5 | ||
|
|
596c9de603 | ||
|
|
4170361a1c | ||
|
|
978a36ea77 | ||
|
|
8745ef7de8 | ||
|
|
2ce929b7b3 | ||
|
|
2dcdfa2862 | ||
|
|
b18daac9c9 | ||
|
|
606041a00d | ||
|
|
4ed5952a81 | ||
|
|
37bfce2eb7 | ||
|
|
59541699ce | ||
|
|
af73c16e37 | ||
|
|
673b09b84b | ||
|
|
09f8edfead | ||
|
|
e1ebdeca4f | ||
|
|
31769aa4af | ||
|
|
b32ad86ce7 | ||
|
|
7350473a65 | ||
|
|
1c87e337d6 | ||
|
|
dec038e7ed | ||
|
|
ebe6f7813e | ||
|
|
706dbfacac | ||
|
|
62ad588aa9 | ||
|
|
3c776e2ab6 | ||
|
|
ab4e2d1ed6 | ||
|
|
5e90c4a142 | ||
|
|
9a7fa25ca3 | ||
|
|
65cb0641f3 | ||
|
|
6f2b891214 | ||
|
|
44d7ab83ec | ||
|
|
b328cc3f6a | ||
|
|
836226a8d3 | ||
|
|
2083f6630a | ||
|
|
25e1e9028f | ||
|
|
a5651c686f | ||
|
|
93f0e0b125 | ||
|
|
730bb6d166 | ||
|
|
855bac59bf | ||
|
|
870900b92a | ||
|
|
a1d5802d36 | ||
|
|
6e16fa3992 | ||
|
|
ee3b49ce59 | ||
|
|
3253dd5010 | ||
|
|
a10d8e0438 | ||
|
|
5151814e98 | ||
|
|
2e2a0f01f4 | ||
|
|
dec9b82da0 | ||
|
|
33f4db5871 | ||
|
|
9a086c0397 | ||
|
|
013ddb8978 | ||
|
|
c4161c5ce6 | ||
|
|
392b8dd5b2 | ||
|
|
d19eb801f3 | ||
|
|
ed416ca555 | ||
|
|
106009316f | ||
|
|
cb10ad3a20 | ||
|
|
2fad0099e4 | ||
|
|
454416b0bd | ||
|
|
dd80b7308b | ||
|
|
fe5c5f1bdc | ||
|
|
79074deb34 | ||
|
|
db0294c95e | ||
|
|
8f0fa88eac | ||
|
|
946f7045e9 | ||
|
|
dda8c90d44 | ||
|
|
701485d76a | ||
|
|
b25198b87e | ||
|
|
ad7e3774c2 | ||
|
|
ee67dea96c | ||
|
|
b3d3bf1593 | ||
|
|
53726fed24 | ||
|
|
7d2ca2c811 | ||
|
|
c0d10a0ec3 | ||
|
|
8e01e3c762 | ||
|
|
04be599893 | ||
|
|
f6cb7fddf1 | ||
|
|
a662283b7d | ||
|
|
f34490fe28 | ||
|
|
1a98c86a37 | ||
|
|
97b5931a4f | ||
|
|
2a4dd9b693 | ||
|
|
ecf31c3675 | ||
|
|
9b0d0a2b7c | ||
|
|
e2ffca2e39 | ||
|
|
a8435879cf | ||
|
|
e16695585a | ||
|
|
a7bcfc6db7 | ||
|
|
21bed98d69 | ||
|
|
044793cc44 | ||
|
|
083a7050b9 | ||
|
|
cc78e19740 | ||
|
|
ee01a07b0a | ||
|
|
ce528b1826 | ||
|
|
92c0543614 | ||
|
|
b6638f4a29 | ||
|
|
96be56a620 | ||
|
|
2e555e420a | ||
|
|
a849951c53 | ||
|
|
435cb04717 | ||
|
|
25a375261e | ||
|
|
1a1a44616f | ||
|
|
e6347a305c | ||
|
|
b137108ae1 | ||
|
|
4ce7ff7cef | ||
|
|
184415a026 | ||
|
|
3977adcb8f | ||
|
|
1c47f98f59 | ||
|
|
9f0c37352e | ||
|
|
7a20fa4805 | ||
|
|
6a713d9e8d | ||
|
|
51b0544752 | ||
|
|
1022a7aa3a | ||
|
|
c048678844 | ||
|
|
f9fb02457c | ||
|
|
ce419ce64e | ||
|
|
ecb6ff57fc | ||
|
|
663866562f | ||
|
|
dfdd0b6cbb | ||
|
|
c81ca82822 | ||
|
|
66b067eb49 | ||
|
|
4e9f2dc46a | ||
|
|
f02d8b61b5 | ||
|
|
088de1877c | ||
|
|
4611abffed | ||
|
|
f206f2f45c | ||
|
|
9c5f5757be | ||
|
|
ec2e0f6fed | ||
|
|
ae0d059b6f | ||
|
|
0132b801b2 | ||
|
|
06ca564ec2 | ||
|
|
d83ce5e633 | ||
|
|
5287319fde | ||
|
|
c75a23e693 | ||
|
|
180bf930c1 | ||
|
|
c16e99ba1e | ||
|
|
b9887fc917 | ||
|
|
96b36bd8ef | ||
|
|
0e32f666eb | ||
|
|
1341fc6a98 | ||
|
|
81c9e96bf3 | ||
|
|
20f91ef893 | ||
|
|
1187254573 | ||
|
|
f22fb4f4a9 | ||
|
|
a14762e005 | ||
|
|
9a91214043 | ||
|
|
30795a46cf | ||
|
|
b0865c8d06 | ||
|
|
c2aabbc6db | ||
|
|
4aad3dafd4 | ||
|
|
62269a9902 | ||
|
|
4bc2fd4a7f | ||
|
|
0e43a08689 | ||
|
|
77b1416413 | ||
|
|
200545822c | ||
|
|
ac33f2164f | ||
|
|
e82ca8a1f4 | ||
|
|
f894f9ef6d | ||
|
|
ccfd8ef566 | ||
|
|
6c6b66c802 | ||
|
|
2cb2e7cb10 | ||
|
|
8744f47a0e | ||
|
|
b742b25f96 | ||
|
|
6d7c20f397 | ||
|
|
f3d384c1dd | ||
|
|
69ce79a7b9 | ||
|
|
1cd191f76b | ||
|
|
afc3c29341 | ||
|
|
fedb3040d1 | ||
|
|
0a969c59d3 | ||
|
|
d9fb9f49fd | ||
|
|
fc77d0d201 | ||
|
|
56700d1d06 | ||
|
|
84259c5116 | ||
|
|
6cc746df8b | ||
|
|
b3ad06692f | ||
|
|
bb0814bb11 | ||
|
|
722faf8aaa | ||
|
|
159324858b | ||
|
|
f73a370719 | ||
|
|
dfc658b910 | ||
|
|
0a52f470b7 | ||
|
|
e4822d8bc4 | ||
|
|
9d1cab3ac2 | ||
|
|
6d16ec238a | ||
|
|
e0284f225b | ||
|
|
a5acd0801a | ||
|
|
d59337ae47 | ||
|
|
2f27a3f839 | ||
|
|
450b33f98a | ||
|
|
9adf5027fa | ||
|
|
1610b84845 | ||
|
|
40b2cf88fc | ||
|
|
55ef6c0c56 | ||
|
|
a9c67ac200 | ||
|
|
3892be47ac | ||
|
|
a2654b2e29 | ||
|
|
a052cc8fa5 | ||
|
|
5d09100cb6 | ||
|
|
76769d1b85 | ||
|
|
2cf57250e8 | ||
|
|
5d0a0c931f | ||
|
|
fa1adfec76 | ||
|
|
9271b07383 | ||
|
|
4fe955650a | ||
|
|
6b401000b9 | ||
|
|
aa681cde88 | ||
|
|
0921c6ea53 | ||
|
|
2df73bedee | ||
|
|
938fe49b2c | ||
|
|
7e2e649a8e | ||
|
|
6e11aa27f8 | ||
|
|
57ee624398 | ||
|
|
3cba95b60a | ||
|
|
bd796ce642 | ||
|
|
3a8473e632 | ||
|
|
89480b61ce | ||
|
|
c5e5bb613e | ||
|
|
b52d1ca6c4 | ||
|
|
790bafbb09 | ||
|
|
c256ce6968 | ||
|
|
85465e0dfb | ||
|
|
b7409c1d0b | ||
|
|
4060d33bbc | ||
|
|
6526d99123 | ||
|
|
1b44c848ad | ||
|
|
e6980be061 | ||
|
|
81c77650b4 | ||
|
|
791e37dd9c | ||
|
|
f926448bbb | ||
|
|
7128f067d0 | ||
|
|
59ab6c1cd8 | ||
|
|
89105b6e72 | ||
|
|
51e43f1c25 | ||
|
|
9b24d644a4 | ||
|
|
2549e0e4c8 | ||
|
|
d974fa638c | ||
|
|
f399b5119f | ||
|
|
12d3fabdb7 | ||
|
|
d5727f5b15 | ||
|
|
1908d04fd4 | ||
|
|
c7f8c828cb | ||
|
|
054d0913fd | ||
|
|
ca7dc69c85 | ||
|
|
b2ff51d99c | ||
|
|
abd4b6bd87 | ||
|
|
b92d07c9a6 | ||
|
|
c6204417a3 | ||
|
|
3a23401443 | ||
|
|
12e6e46924 | ||
|
|
be1b959519 | ||
|
|
7841c9e801 | ||
|
|
bc486e2a7d | ||
|
|
d01885e2ad | ||
|
|
358ffe0601 | ||
|
|
8d0bf1b758 | ||
|
|
c7961cf2f4 | ||
|
|
2ac374e578 | ||
|
|
ad3306e19e | ||
|
|
f54cbac370 | ||
|
|
4bfc0140be | ||
|
|
cc7459b983 | ||
|
|
11415cd517 | ||
|
|
9aa028f32d | ||
|
|
2a415cbd26 | ||
|
|
40a52da2d3 | ||
|
|
31e043c1d7 | ||
|
|
985afafed0 | ||
|
|
3a4d20ecb2 | ||
|
|
35b524f179 | ||
|
|
d2f17ef140 | ||
|
|
d99864ca95 | ||
|
|
e6e1191f8a | ||
|
|
1f10c20874 | ||
|
|
c699f7a940 | ||
|
|
7115c292c7 | ||
|
|
7744b14b48 | ||
|
|
6fed9e73d8 | ||
|
|
e14a3f9c2d | ||
|
|
7bf75b1ff7 | ||
|
|
6905e24199 | ||
|
|
9eb25c09da | ||
|
|
c97f5ffca3 | ||
|
|
eb19168b5f | ||
|
|
e421b68766 | ||
|
|
6b86625c56 | ||
|
|
254b6dd5c7 | ||
|
|
7b545d12c7 | ||
|
|
01ed20b400 | ||
|
|
fad930833a | ||
|
|
9192fbee44 | ||
|
|
aaa5f9885d | ||
|
|
1979b1692d | ||
|
|
fc31eeca88 | ||
|
|
6d4de76a9a | ||
|
|
9245ba4a5b | ||
|
|
973a788c21 | ||
|
|
0d5df8d3cf | ||
|
|
b67a236657 | ||
|
|
170617d457 | ||
|
|
cc2f359b6c | ||
|
|
c0d44a8686 | ||
|
|
a1700f38b1 | ||
|
|
b82ba63532 | ||
|
|
dbd1324a90 | ||
|
|
e52158b729 | ||
|
|
68a717cf47 | ||
|
|
c0a9780f80 | ||
|
|
0520129a40 | ||
|
|
6b36e126ab | ||
|
|
3cb4662363 | ||
|
|
cef14f301f | ||
|
|
eca4a0c440 | ||
|
|
cea04b2ada | ||
|
|
b7d9af9ae7 | ||
|
|
0046b3c802 | ||
|
|
25384c8ac3 | ||
|
|
2eee72da8a | ||
|
|
d65c422e3f | ||
|
|
2ab9bd3172 | ||
|
|
a0b5c2e88c | ||
|
|
0bd4108661 | ||
|
|
7d1a1475ba | ||
|
|
6418263165 | ||
|
|
b32c835cf9 | ||
|
|
a927685bc6 | ||
|
|
6827d40e84 | ||
|
|
9aa12b0eb6 | ||
|
|
8ca1a36d78 | ||
|
|
394f2eae69 | ||
|
|
7588459c3e | ||
|
|
2c33f45a6a | ||
|
|
43cf0b66ac | ||
|
|
c05f6fcff2 | ||
|
|
a4eb32158b | ||
|
|
072ced9d0e | ||
|
|
c5fefc1414 | ||
|
|
c825895ceb | ||
|
|
9488972ed8 | ||
|
|
1f4bed38f7 | ||
|
|
747112957a | ||
|
|
18228096f9 | ||
|
|
d0ea5bf500 | ||
|
|
74fe91fa3a | ||
|
|
d4e3075fcc | ||
|
|
413ae5f20e | ||
|
|
e23f86f542 | ||
|
|
b3c47dbe84 | ||
|
|
b10c2b9a15 | ||
|
|
bb6910acd1 | ||
|
|
76b000820e | ||
|
|
50b874629c | ||
|
|
4d145ab4ad | ||
|
|
4d67a4641f | ||
|
|
bc2eab6f9d | ||
|
|
3ccce75863 | ||
|
|
b390b266e2 | ||
|
|
561af8926c | ||
|
|
5717dc2cf4 | ||
|
|
d0e64ad6d2 | ||
|
|
8e1a735ec7 | ||
|
|
1301d67295 | ||
|
|
7c239f5218 | ||
|
|
61235c953c | ||
|
|
ea65a0308d | ||
|
|
f840b07c02 | ||
|
|
8af719d39e | ||
|
|
c1a4250bd2 | ||
|
|
b4b442f8a8 | ||
|
|
b7e76fcfa1 | ||
|
|
01950cc796 | ||
|
|
14f2022e21 | ||
|
|
23bac73559 | ||
|
|
2de9b94b01 | ||
|
|
05d1736f5f | ||
|
|
db5346fc5c | ||
|
|
f09f5bd737 | ||
|
|
d96a1a5c9e | ||
|
|
ad3cc9c087 | ||
|
|
d0c7bf65a4 | ||
|
|
bb6ac70f63 | ||
|
|
c38b9da7d6 | ||
|
|
19153ca489 | ||
|
|
8e7a5e8cb5 | ||
|
|
1407059937 | ||
|
|
a2911bc9c3 | ||
|
|
f99b735746 | ||
|
|
4510ddda15 | ||
|
|
028ec46f46 | ||
|
|
c0e4e01deb | ||
|
|
cfcf682134 | ||
|
|
27bd9817d7 | ||
|
|
dc389a68ab | ||
|
|
6d982cdfd9 | ||
|
|
827ee615c1 | ||
|
|
f5f0ba721e | ||
|
|
1344f85986 | ||
|
|
e7462ada12 | ||
|
|
11199b6225 | ||
|
|
345aece0f2 | ||
|
|
912c84422b | ||
|
|
b402676079 | ||
|
|
908a5fc60d | ||
|
|
38e57d3635 | ||
|
|
14167b19b9 | ||
|
|
fc548b426d | ||
|
|
094f13934c | ||
|
|
71acd9603e | ||
|
|
341f655ab7 | ||
|
|
0c5323c106 | ||
|
|
7f871fdf30 | ||
|
|
6347b40a02 | ||
|
|
bce84a432a | ||
|
|
b6fc4deb63 | ||
|
|
6b48f9e259 | ||
|
|
44b7666426 | ||
|
|
ef70ed76a2 | ||
|
|
a1a1b3d1ee | ||
|
|
0326757399 | ||
|
|
edbc8ed92b | ||
|
|
07e9ec0617 | ||
|
|
18d0fa4327 | ||
|
|
666ee84033 | ||
|
|
120642f47d | ||
|
|
148b81262d | ||
|
|
c41467f76e | ||
|
|
c83e83b8b1 | ||
|
|
563a508e26 | ||
|
|
1d8347cc23 | ||
|
|
874abf2c41 | ||
|
|
3e18f820ea | ||
|
|
b571f1c953 | ||
|
|
fd69c7ceb8 | ||
|
|
d15faa1890 | ||
|
|
0c7e12c5e7 | ||
|
|
36a3077853 | ||
|
|
51bc23dbd9 | ||
|
|
0b1e0bd395 | ||
|
|
151aafd114 | ||
|
|
16c8a65a1c | ||
|
|
f3133e9cd8 | ||
|
|
de84aa7674 | ||
|
|
1bdbfe86bb | ||
|
|
397b9f439e | ||
|
|
261e0aee0f | ||
|
|
a6d9bde658 | ||
|
|
8d0a07e1a8 | ||
|
|
659615e6f2 | ||
|
|
9f0b82c0d5 | ||
|
|
9a23dcc991 | ||
|
|
980d345305 | ||
|
|
41f76ee974 | ||
|
|
83a60e6303 | ||
|
|
7b23c85117 | ||
|
|
ff3900d100 | ||
|
|
e5e4b8ebfb | ||
|
|
9516b75d65 | ||
|
|
6b1a92001a | ||
|
|
e4e1fdb187 | ||
|
|
58ad80218a | ||
|
|
3322d3b33d | ||
|
|
bd9d0c2d1b | ||
|
|
02b99e0555 | ||
|
|
f803190dd2 | ||
|
|
deb3a8389a | ||
|
|
e7d02c12eb | ||
|
|
169eaa180b | ||
|
|
08cde06ce5 | ||
|
|
4607bfe454 | ||
|
|
de0eece27c | ||
|
|
25ad901fb3 | ||
|
|
99e579f47d | ||
|
|
7df278e805 | ||
|
|
34325dde8c | ||
|
|
c7e3cc7e0a | ||
|
|
826876ff11 | ||
|
|
b031da6c6d | ||
|
|
43e15a1ef4 | ||
|
|
7c32188968 | ||
|
|
78d38bc4e2 | ||
|
|
f942631fd4 | ||
|
|
5d64c28e89 | ||
|
|
485c2f0a7d | ||
|
|
3d894d0e56 | ||
|
|
b265701844 | ||
|
|
468760bd31 | ||
|
|
f03744d99b | ||
|
|
26e78a1381 | ||
|
|
44aeb323d9 | ||
|
|
503298a33f | ||
|
|
58b456d5d5 | ||
|
|
d370cac14c | ||
|
|
b29c19f5e0 | ||
|
|
a3ba0cf6d5 | ||
|
|
8bb089fe07 | ||
|
|
7ba66f6386 | ||
|
|
b537f9b20a | ||
|
|
7abe8d71a8 | ||
|
|
3e01231874 | ||
|
|
fb8e23dc4b | ||
|
|
bf0d6cad2c | ||
|
|
5a9d36341c | ||
|
|
c7bf0b24a7 | ||
|
|
d69a505928 | ||
|
|
7980899f37 | ||
|
|
91d245a414 | ||
|
|
5db8d45f4d | ||
|
|
ee4f09845c | ||
|
|
01a717f143 | ||
|
|
efd22df657 | ||
|
|
2948e0aa4f | ||
|
|
b253683b3c | ||
|
|
189ebd4201 | ||
|
|
f1810e47e0 | ||
|
|
37f7924524 | ||
|
|
6427a5e5f1 | ||
|
|
96e11cb727 | ||
|
|
a5f9157217 | ||
|
|
133066c900 | ||
|
|
cf75623184 | ||
|
|
fecae8d0a3 | ||
|
|
f1d8220ab7 | ||
|
|
d51e8d1d1d | ||
|
|
9a761d2a77 | ||
|
|
407d018067 | ||
|
|
028b8b606a | ||
|
|
ad0e86d725 | ||
|
|
8aeed8b033 | ||
|
|
989c73f1f1 | ||
|
|
f6a3a04dba | ||
|
|
8ff11ccc88 | ||
|
|
5d145d0dd1 | ||
|
|
275ac445d0 | ||
|
|
7fc364cb63 | ||
|
|
93868fd905 | ||
|
|
a862486ec3 | ||
|
|
14c9b70149 | ||
|
|
b60c655b98 | ||
|
|
fe5475d5ec | ||
|
|
955a46ca60 | ||
|
|
0d0c88ceaa | ||
|
|
fb008569ab | ||
|
|
295c9c8617 | ||
|
|
658e43ab54 | ||
|
|
41b1227b17 | ||
|
|
68c7a41906 | ||
|
|
18f6d4cb16 | ||
|
|
7c60d7932a | ||
|
|
b9182a1fc7 | ||
|
|
e45d35ba60 | ||
|
|
8b73c702c3 | ||
|
|
8e744e860d | ||
|
|
94391d4cde | ||
|
|
4e73c56afe | ||
|
|
5606414f89 | ||
|
|
8ebb1a7d7f | ||
|
|
586043e00b | ||
|
|
e70c51a1f0 | ||
|
|
ea8f8de391 | ||
|
|
0332ef3298 | ||
|
|
751b0aa073 | ||
|
|
965db42170 | ||
|
|
73f16692da | ||
|
|
aab80e67bd | ||
|
|
3e3b629aa2 | ||
|
|
adeec24252 | ||
|
|
ba0fdaef72 | ||
|
|
c357c77e7e | ||
|
|
702375856d | ||
|
|
c936d83b38 | ||
|
|
83d22c9ebf | ||
|
|
02f45f4e65 | ||
|
|
da2ba45456 | ||
|
|
0566f7c89b | ||
|
|
30a2e8b990 | ||
|
|
81b2bce385 | ||
|
|
a5347cfcac | ||
|
|
3422db31ee | ||
|
|
755880ec90 | ||
|
|
30273709ae | ||
|
|
17f4bbde20 | ||
|
|
575e3750f1 | ||
|
|
d7c8eced81 | ||
|
|
86fda62a48 | ||
|
|
a4ec20a7c8 | ||
|
|
90653c1472 | ||
|
|
30ec59140c | ||
|
|
f689d1688a | ||
|
|
57e545e7c4 | ||
|
|
d0ea8e3a81 | ||
|
|
c188d2bf65 | ||
|
|
75b3421222 | ||
|
|
c062bd63bb | ||
|
|
0c753e85a8 | ||
|
|
32770c02ad | ||
|
|
133dd55f4c | ||
|
|
d1e2db9cb8 | ||
|
|
52a02409f0 | ||
|
|
eb3377deb8 | ||
|
|
c06bcac9f3 | ||
|
|
f9f0523112 | ||
|
|
f7aa5d0871 | ||
|
|
9576f6807b | ||
|
|
efe50a65ee | ||
|
|
fb34dd6114 | ||
|
|
2ebff3a864 | ||
|
|
a3f70735ac | ||
|
|
e1fd915ecd | ||
|
|
0097674fc0 | ||
|
|
f6341de610 | ||
|
|
17d5fae7bb | ||
|
|
05eca83b15 | ||
|
|
6d004b2095 | ||
|
|
46327f19a1 | ||
|
|
d4c8ea9361 | ||
|
|
7af0ddc226 | ||
|
|
80ea1782f9 | ||
|
|
aa46b42356 | ||
|
|
3cd069ffb9 | ||
|
|
e621ad6d7c | ||
|
|
729d8fb435 | ||
|
|
de2ddbe8b5 | ||
|
|
57ddd82ef1 | ||
|
|
79f7ec2715 | ||
|
|
ab4c3bd2fc | ||
|
|
bd3a7e1068 | ||
|
|
aa33ead7b2 | ||
|
|
94cf2a80a5 | ||
|
|
2ee959a08e | ||
|
|
54b7591ca0 | ||
|
|
b3f0392e7d | ||
|
|
77e7350e96 | ||
|
|
9c87572f90 | ||
|
|
65765f6c2f | ||
|
|
785b312009 | ||
|
|
1d9a710e3b | ||
|
|
909bf811d1 | ||
|
|
90abe01286 | ||
|
|
2c13d74c5f | ||
|
|
5d975c5da4 | ||
|
|
35c1057527 | ||
|
|
3912181857 | ||
|
|
b9a01eb5d6 | ||
|
|
3027f0acb0 | ||
|
|
609c2a5ddb | ||
|
|
f76d648012 | ||
|
|
203bf92a0a | ||
|
|
3e8cc9e905 | ||
|
|
b9f32c1739 | ||
|
|
7e1c65f616 | ||
|
|
447ae93476 | ||
|
|
6df40f41ac | ||
|
|
3e0ef3d070 | ||
|
|
33a17a9a1b | ||
|
|
aed4bfb7f4 | ||
|
|
78f7c3e5bd | ||
|
|
d764047c1f | ||
|
|
65015ea7c7 | ||
|
|
6ef2d9cb9d | ||
|
|
c39fdab11d | ||
|
|
547261abb6 | ||
|
|
7696b7b769 | ||
|
|
585635494a | ||
|
|
a36e80099a | ||
|
|
f5f8a4f6fb | ||
|
|
e207bff91f | ||
|
|
a2c7c230ef | ||
|
|
83a12a9a26 | ||
|
|
096fe130f6 | ||
|
|
12d22f92b2 | ||
|
|
017628e2a6 | ||
|
|
332b9f5816 | ||
|
|
09a96c1118 | ||
|
|
82750ee8a2 | ||
|
|
4e89f27768 | ||
|
|
6f60235a3f | ||
|
|
42be851d64 | ||
|
|
6c32c210f8 | ||
|
|
aca665ae4e | ||
|
|
09bee30610 | ||
|
|
87d53f6f93 | ||
|
|
98cf7ec715 | ||
|
|
12e50d93df | ||
|
|
dfcdb1994b | ||
|
|
30a5b87370 | ||
|
|
465e2b1b6a | ||
|
|
1ae52bbdb1 | ||
|
|
d0024d397a | ||
|
|
0b2cdc4d56 | ||
|
|
92af6f71d9 | ||
|
|
8879b8498f | ||
|
|
ad7e0e0bf9 | ||
|
|
edb7398295 | ||
|
|
cfca28e25a | ||
|
|
a811e22f95 | ||
|
|
fbb2e6e261 | ||
|
|
c44b99d515 | ||
|
|
0f283e6cdf | ||
|
|
b7e8f796a3 | ||
|
|
d63507dd97 | ||
|
|
e1168b5107 | ||
|
|
cecb4b8336 | ||
|
|
5bbbf9d0e6 | ||
|
|
29775eef9a | ||
|
|
3f3e645c96 | ||
|
|
ebece3f961 | ||
|
|
4951d409db | ||
|
|
cf90511f3c | ||
|
|
4ec0e63d99 | ||
|
|
08843a9dd6 | ||
|
|
b3f229dc86 | ||
|
|
18b74e7a61 | ||
|
|
c60961cbb1 | ||
|
|
3338fc366d | ||
|
|
79cbf25b73 | ||
|
|
7963118032 | ||
|
|
35e6c938dc | ||
|
|
c632bc4555 | ||
|
|
7a34bc3eb0 | ||
|
|
6e0d41834c | ||
|
|
4bcbaf9113 | ||
|
|
5448a15691 | ||
|
|
c63feb3ff3 | ||
|
|
fb4d95672e | ||
|
|
bfb1658abe | ||
|
|
79e2c9175f | ||
|
|
b9c2d49a1c | ||
|
|
a25eb4ded6 | ||
|
|
3851de2774 | ||
|
|
0783b733b0 | ||
|
|
bb17feaa17 | ||
|
|
7623c8dcb7 | ||
|
|
1d7eb8a03b | ||
|
|
c5712f5ae0 | ||
|
|
00e5808a4c | ||
|
|
0a591564fd | ||
|
|
8448068d22 | ||
|
|
9fa36c3991 | ||
|
|
d949753d54 | ||
|
|
f6d2b08025 | ||
|
|
8a446de3e9 | ||
|
|
0196f46094 | ||
|
|
bd83b3bc8e | ||
|
|
89347abf53 | ||
|
|
a27f8ecfa9 | ||
|
|
75ca4920c5 | ||
|
|
324be07b85 | ||
|
|
282a5b30f9 | ||
|
|
b24acefa6f | ||
|
|
751b519167 | ||
|
|
330aec2bdd | ||
|
|
d5635c3a89 | ||
|
|
1e86d67bfe | ||
|
|
8ac9353c53 | ||
|
|
1557970170 | ||
|
|
508bbb37ce | ||
|
|
a90fc396a9 | ||
|
|
96627540da | ||
|
|
db6ebe0470 | ||
|
|
559251eb0d | ||
|
|
23396d1bd6 | ||
|
|
9d92a79a28 | ||
|
|
90a64a7403 | ||
|
|
f709cc332c | ||
|
|
c925a27870 | ||
|
|
d31403a918 | ||
|
|
f9051d768c | ||
|
|
06eb2adeaa | ||
|
|
12af3a5d10 | ||
|
|
8bf94d6ac6 | ||
|
|
15af5d2c2e | ||
|
|
60c5c5c723 | ||
|
|
f972da7442 | ||
|
|
948537cb75 | ||
|
|
ec5d0d00f5 | ||
|
|
0e7c454013 | ||
|
|
6e8e581ceb | ||
|
|
2ec435af41 | ||
|
|
5aa38c2e40 | ||
|
|
0ff3952a8b | ||
|
|
e32d2daa45 | ||
|
|
57780801aa | ||
|
|
ecdfc68170 | ||
|
|
3b29678ef1 | ||
|
|
6c54b38cdf | ||
|
|
dfcabc5a36 | ||
|
|
5c8cf2ebbd | ||
|
|
a9d41217bd | ||
|
|
75f44a821c | ||
|
|
0ab16764d1 | ||
|
|
b6e7e03c95 | ||
|
|
718c1a393d | ||
|
|
50f3316088 | ||
|
|
2727651897 | ||
|
|
e72646a4d4 |
No files matched your search
@@ -1,257 +0,0 @@
|
||||
name: wekan
|
||||
version: 0
|
||||
version-script: git describe --tags | cut -c 2-
|
||||
summary: The open-source kanban
|
||||
description: |
|
||||
Wekan is an open-source and collaborative kanban board application.
|
||||
|
||||
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.
|
||||
Depending on target environment, some configuration settings might need to be adjusted.
|
||||
For full list of configuration options call:
|
||||
$ wekan.help
|
||||
|
||||
confinement: strict
|
||||
grade: stable
|
||||
|
||||
architectures:
|
||||
- amd64
|
||||
|
||||
plugs:
|
||||
mongodb-plug:
|
||||
interface: content
|
||||
target: $SNAP_DATA/shared
|
||||
|
||||
hooks:
|
||||
configure:
|
||||
plugs:
|
||||
- network
|
||||
- network-bind
|
||||
|
||||
slots:
|
||||
mongodb-slot:
|
||||
interface: content
|
||||
write:
|
||||
- $SNAP_DATA/share
|
||||
|
||||
apps:
|
||||
wekan:
|
||||
command: wekan-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
mongodb:
|
||||
command: mongodb-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
caddy:
|
||||
command: caddy-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
help:
|
||||
command: wekan-help
|
||||
|
||||
database-backup:
|
||||
command: mongodb-backup
|
||||
plugs: [network, network-bind]
|
||||
|
||||
database-list-backups:
|
||||
command: ls -al $SNAP_COMMON/db-backups/
|
||||
|
||||
database-restore:
|
||||
command: mongodb-restore
|
||||
plugs: [network, network-bind]
|
||||
|
||||
parts:
|
||||
mongodb:
|
||||
source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.2.6.tgz
|
||||
plugin: dump
|
||||
stage-packages: [libssl1.0.0, libcurl3]
|
||||
filesets:
|
||||
mongo:
|
||||
- usr
|
||||
- bin
|
||||
- lib
|
||||
stage:
|
||||
- $mongo
|
||||
prime:
|
||||
- $mongo
|
||||
|
||||
wekan:
|
||||
source: .
|
||||
plugin: nodejs
|
||||
node-engine: 14.21.3
|
||||
node-packages:
|
||||
- node-gyp
|
||||
- node-pre-gyp
|
||||
- fibers
|
||||
build-packages:
|
||||
- ca-certificates
|
||||
- apt-utils
|
||||
- python
|
||||
- python3
|
||||
- g++
|
||||
- capnproto
|
||||
- curl
|
||||
- libcurl3
|
||||
- execstack
|
||||
- nodejs
|
||||
- npm
|
||||
stage-packages:
|
||||
- libfontconfig1
|
||||
override-build: |
|
||||
echo "Cleaning environment first"
|
||||
rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
|
||||
# Create the OpenAPI specification
|
||||
rm -rf .build
|
||||
## Use Meteor 1.8.x on Snap
|
||||
#rm -rf .meteor
|
||||
#mv .snap-meteor-1.8/.meteor .
|
||||
#mv .snap-meteor-1.8/package.json .
|
||||
#mv .snap-meteor-1.8/package-lock.json .
|
||||
## Meteor 1.9.x has changes to Buffer() => Buffer.alloc(), so reverting those
|
||||
#mv .snap-meteor-1.8/cfs_access-point.txt fix-download-unicode/
|
||||
#mv .snap-meteor-1.8/export.js models/
|
||||
#mv .snap-meteor-1.8/wekanCreator.js models/
|
||||
#mv .snap-meteor-1.8/ldap.js packages/wekan-ldap/server/ldap.js
|
||||
#mv .snap-meteor-1.8/oidc_server.js packages/wekan-oidc/oidc_server.js
|
||||
rm -rf .snap-meteor-1.8
|
||||
#mkdir -p .build/python
|
||||
#cd .build/python
|
||||
#git clone --depth 1 -b master https://github.com/Kronuz/esprima-python
|
||||
#cd esprima-python
|
||||
#python3 setup.py install
|
||||
#cd ../../..
|
||||
#mkdir -p ./public/api
|
||||
#python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml
|
||||
# we temporary need api2html and mkdirp
|
||||
#npm install -g api2html@0.3.0
|
||||
#npm install -g mkdirp
|
||||
#api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml
|
||||
#npm uninstall -g mkdirp
|
||||
#npm uninstall -g api2html
|
||||
# Node Fibers 100% CPU usage issue:
|
||||
# https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161
|
||||
# https://github.com/meteor/meteor/issues/9796#issuecomment-381676326
|
||||
# https://github.com/sandstorm-io/sandstorm/blob/0f1fec013fe7208ed0fd97eb88b31b77e3c61f42/shell/server/00-startup.js#L99-L129
|
||||
# Also see beginning of wekan/server/authentication.js
|
||||
# import Fiber from "fibers";
|
||||
# Fiber.poolSize = 1e9;
|
||||
# OLD: Download node version 8.12.0 prerelease build => Official node 8.12.0 has been released
|
||||
# Description at https://releases.wekan.team/node.txt
|
||||
##echo "375bd8db50b9c692c0bbba6e96d4114cd29bee3770f901c1ff2249d1038f1348 node" >> node-SHASUMS256.txt.asc
|
||||
##curl https://releases.wekan.team/node -o node
|
||||
# Verify Fibers patched node authenticity
|
||||
##echo "Fibers 100% CPU issue patched node authenticity:"
|
||||
##grep node node-SHASUMS256.txt.asc | shasum -a 256 -c -
|
||||
##rm -f node-SHASUMS256.txt.asc
|
||||
##chmod +x node
|
||||
##mv node `which node`
|
||||
# DOES NOT WORK: paxctl fix.
|
||||
# Removed from build-packages: - paxctl
|
||||
#echo "Applying paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303"
|
||||
#paxctl -mC `which node`
|
||||
#echo "Installing npm"
|
||||
#curl -L https://www.npmjs.com/install.sh | sh
|
||||
echo "Installing meteor"
|
||||
curl https://install.meteor.com/ -o install_meteor.sh
|
||||
#sed -i "s|RELEASE=.*|RELEASE=\"1.8.1-beta.0\"|g" install_meteor.sh
|
||||
chmod +x install_meteor.sh
|
||||
sh install_meteor.sh
|
||||
rm install_meteor.sh
|
||||
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
|
||||
#if [ ! -d "packages" ]; then
|
||||
# mkdir packages
|
||||
#fi
|
||||
#if [ ! -d "packages/kadira-flow-router" ]; then
|
||||
# cd packages
|
||||
# git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router
|
||||
# cd ..
|
||||
#fi
|
||||
#if [ ! -d "packages/meteor-useraccounts-core" ]; then
|
||||
# cd packages
|
||||
# git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core
|
||||
# sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' meteor-useraccounts-core/package.js
|
||||
# cd ..
|
||||
#fi
|
||||
#if [ ! -d "packages/meteor-accounts-cas" ]; then
|
||||
# cd packages
|
||||
# git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git meteor-accounts-cas
|
||||
# cd ..
|
||||
#fi
|
||||
#if [ ! -d "packages/wekan-ldap" ]; then
|
||||
# cd packages
|
||||
# git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git
|
||||
# cd ..
|
||||
#fi
|
||||
#if [ ! -d "packages/wekan-scrollbar" ]; then
|
||||
# cd packages
|
||||
# git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git
|
||||
# cd ..
|
||||
#fi
|
||||
#if [ ! -d "packages/wekan_accounts-oidc" ]; then
|
||||
# cd packages
|
||||
# git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git
|
||||
# mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan-accounts-oidc
|
||||
# mv meteor-accounts-oidc/packages/switch_oidc wekan-oidc
|
||||
# rm -rf meteor-accounts-oidc
|
||||
# cd ..
|
||||
#fi
|
||||
#if [ ! -d "packages/markdown" ]; then
|
||||
# cd packages
|
||||
# git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
|
||||
# cd ..
|
||||
#fi
|
||||
rm -rf .build
|
||||
meteor add standard-minifier-js --allow-superuser
|
||||
meteor npm install --allow-superuser
|
||||
meteor npm install --allow-superuser --save babel-runtime
|
||||
meteor build .build --directory --allow-superuser
|
||||
cp -f fix-download-unicode/cfs_access-point.txt .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
|
||||
#https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
|
||||
#cd .build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt
|
||||
#rm -rf node_modules/bcrypt
|
||||
#meteor npm install --save bcrypt
|
||||
# Change from npm-bcrypt directory back to .build/bundle/programs/server directory.
|
||||
#cd ../../../../
|
||||
# Change to directory .build/bundle/programs/server
|
||||
cd .build/bundle/programs/server
|
||||
npm install
|
||||
npm install --allow-superuser --save babel-runtime
|
||||
#meteor npm install --save bcrypt
|
||||
# Change back to Wekan source directory
|
||||
cd ../../../..
|
||||
cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
|
||||
cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
# Meteor 1.8.x additional .swp remove
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
|
||||
organize:
|
||||
README: README.wekan
|
||||
prime:
|
||||
- -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
|
||||
|
||||
helpers:
|
||||
source: snap-src
|
||||
plugin: dump
|
||||
|
||||
caddy:
|
||||
plugin: dump
|
||||
source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
|
||||
source-type: tar
|
||||
organize:
|
||||
caddy: bin/caddy
|
||||
CHANGES.txt: CADDY_CHANGES.txt
|
||||
EULA.txt: CADDY_EULA.txt
|
||||
LICENSES.txt: CADDY_LICENSES.txt
|
||||
README.txt: CADDY_README.txt
|
||||
stage:
|
||||
- -init
|
||||
@@ -1,198 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Note: If you use other locale than en_US.UTF-8 , you need to additionally install en_US.UTF-8"
|
||||
echo " with 'sudo dpkg-reconfigure locales' , so that MongoDB works correctly."
|
||||
echo " You can still use any other locale as your main locale."
|
||||
|
||||
#Below script installs newest node 8.x for Debian/Ubuntu/Mint.
|
||||
#NODE_VERSION=12.21.0
|
||||
#X64NODE="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz"
|
||||
|
||||
function pause(){
|
||||
read -p "$*"
|
||||
}
|
||||
|
||||
function cprec(){
|
||||
if [[ -d "$1" ]]; then
|
||||
if [[ ! -d "$2" ]]; then
|
||||
sudo mkdir -p "$2"
|
||||
fi
|
||||
|
||||
for i in $(ls -A "$1"); do
|
||||
cprec "$1/$i" "$2/$i"
|
||||
done
|
||||
else
|
||||
sudo cp "$1" "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
# sudo npm doesn't work right, so this is a workaround
|
||||
function npm_call(){
|
||||
TMPDIR="/tmp/tmp_npm_prefix"
|
||||
if [[ -d "$TMPDIR" ]]; then
|
||||
rm -rf $TMPDIR
|
||||
fi
|
||||
mkdir $TMPDIR
|
||||
NPM_PREFIX="$(npm config get prefix)"
|
||||
npm config set prefix $TMPDIR
|
||||
npm "$@"
|
||||
npm config set prefix "$NPM_PREFIX"
|
||||
|
||||
echo "Moving files to $NPM_PREFIX"
|
||||
for i in $(ls -A $TMPDIR); do
|
||||
cprec "$TMPDIR/$i" "$NPM_PREFIX/$i"
|
||||
done
|
||||
rm -rf $TMPDIR
|
||||
}
|
||||
|
||||
#function wekan_repo_check(){
|
||||
## UNCOMMENTING, IT'S NOT REQUIRED THAT /HOME/USERNAME IS /HOME/WEKAN
|
||||
# git_remotes="$(git remote show 2>/dev/null)"
|
||||
# res=""
|
||||
# for i in $git_remotes; do
|
||||
# res="$(git remote get-url $i | sed 's/.*wekan\/wekan.*/wekan\/wekan/')"
|
||||
# if [[ "$res" == "wekan/wekan" ]]; then
|
||||
# break
|
||||
# fi
|
||||
# done
|
||||
#
|
||||
# if [[ "$res" != "wekan/wekan" ]]; then
|
||||
# echo "$PWD is not a wekan repository"
|
||||
# exit;
|
||||
# fi
|
||||
#}
|
||||
|
||||
echo
|
||||
PS3='Please enter your choice: '
|
||||
options=("Install Wekan dependencies" "Build Wekan" "Run Meteor for dev on http://localhost:4000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Quit")
|
||||
|
||||
select opt in "${options[@]}"
|
||||
do
|
||||
case $opt in
|
||||
"Install Wekan dependencies")
|
||||
|
||||
if [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||
echo "Linux";
|
||||
# Debian, Ubuntu, Mint
|
||||
sudo apt-get install -y build-essential gcc g++ make git curl wget
|
||||
# npm nodejs
|
||||
#sudo npm -g install npm
|
||||
curl -0 -L https://npmjs.org/install.sh | sudo sh
|
||||
sudo chown -R $(id -u):$(id -g) $HOME/.npm
|
||||
sudo npm -g install n
|
||||
sudo n 12.21.0
|
||||
#curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
|
||||
#sudo apt-get install -y nodejs
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
echo "macOS";
|
||||
pause '1) Install XCode 2) Install Node 8.x from https://nodejs.org/en/ 3) Press [Enter] key to continue.'
|
||||
elif [[ "$OSTYPE" == "cygwin" ]]; then
|
||||
# POSIX compatibility layer and Linux environment emulation for Windows
|
||||
echo "TODO: Add Cygwin";
|
||||
exit;
|
||||
elif [[ "$OSTYPE" == "msys" ]]; then
|
||||
# Lightweight shell and GNU utilities compiled for Windows (part of MinGW)
|
||||
echo "TODO: Add msys on Windows";
|
||||
exit;
|
||||
elif [[ "$OSTYPE" == "win32" ]]; then
|
||||
# I'm not sure this can happen.
|
||||
echo "TODO: Add Windows";
|
||||
exit;
|
||||
elif [[ "$OSTYPE" == "freebsd"* ]]; then
|
||||
echo "TODO: Add FreeBSD";
|
||||
exit;
|
||||
else
|
||||
echo "Unknown"
|
||||
echo ${OSTYPE}
|
||||
exit;
|
||||
fi
|
||||
|
||||
## Latest npm with Meteor 1.8.x
|
||||
npm_call -g install npm
|
||||
npm_call -g install node-gyp
|
||||
# Latest fibers for Meteor 1.8.x
|
||||
sudo mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp
|
||||
npm_call -g install fibers
|
||||
# Install Meteor, if it's not yet installed
|
||||
curl https://install.meteor.com | bash
|
||||
sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor
|
||||
break
|
||||
;;
|
||||
|
||||
"Build Wekan")
|
||||
echo "Building Wekan."
|
||||
#wekan_repo_check
|
||||
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
|
||||
#rm -rf packages/kadira-flow-router packages/meteor-useraccounts-core packages/meteor-accounts-cas packages/wekan-ldap packages/wekan-ldap packages/wekan-scrfollbar packages/meteor-accounts-oidc packages/markdown
|
||||
#mkdir packages
|
||||
#cd packages
|
||||
#git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router
|
||||
#git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core
|
||||
#git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git
|
||||
#git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git
|
||||
#git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git
|
||||
#git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git
|
||||
#git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
|
||||
#mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan_accounts-oidc
|
||||
#mv meteor-accounts-oidc/packages/switch_oidc wekan_oidc
|
||||
#rm -rf meteor-accounts-oidc
|
||||
#if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# echo "sed at macOS";
|
||||
# sed -i '' 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js
|
||||
#else
|
||||
# echo "sed at ${OSTYPE}"
|
||||
# sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js
|
||||
#fi
|
||||
#cd ..
|
||||
sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor
|
||||
rm -rf node_modules .meteor/local
|
||||
npm install
|
||||
rm -rf .build
|
||||
meteor build .build --directory
|
||||
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
|
||||
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
|
||||
rm -rf .build/bundle/programs/web.browser.legacy
|
||||
#Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
|
||||
#https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
|
||||
#https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
|
||||
#cd ~/repos/wekan/.build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt
|
||||
#rm -rf node_modules/bcrypt
|
||||
#meteor npm install bcrypt
|
||||
cd .build/bundle/programs/server
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
#meteor npm install bcrypt
|
||||
cd ../../../..
|
||||
echo Done.
|
||||
break
|
||||
;;
|
||||
|
||||
"Run Meteor for dev on http://localhost:4000")
|
||||
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000
|
||||
break
|
||||
;;
|
||||
|
||||
"Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000")
|
||||
IPADDRESS=$(ip a | grep 'noprefixroute' | grep 'inet ' | cut -d: -f2 | awk '{ print $2}' | cut -d '/' -f 1)
|
||||
echo "Your IP address is $IPADDRESS"
|
||||
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000
|
||||
break
|
||||
;;
|
||||
|
||||
"Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT")
|
||||
ip address
|
||||
echo "From above list, what is your IP address?"
|
||||
read IPADDRESS
|
||||
echo "On what port you would like to run Wekan?"
|
||||
read PORT
|
||||
echo "ROOT_URL=http://$IPADDRESS:$PORT"
|
||||
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:$PORT meteor run --exclude-archs web.browser.legacy,web.cordova --port $PORT
|
||||
break
|
||||
;;
|
||||
|
||||
"Quit")
|
||||
break
|
||||
;;
|
||||
*) echo invalid option;;
|
||||
esac
|
||||
done
|
||||
@@ -1,155 +0,0 @@
|
||||
name: wekan
|
||||
version: git
|
||||
summary: The open-source kanban
|
||||
description: |
|
||||
Wekan is an open-source and collaborative kanban board application.
|
||||
|
||||
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.
|
||||
Depending on target environment, some configuration settings might need to be adjusted.
|
||||
For full list of configuration options call:
|
||||
$ wekan.help
|
||||
|
||||
confinement: strict
|
||||
grade: stable
|
||||
base: core18
|
||||
|
||||
architectures:
|
||||
- amd64
|
||||
|
||||
plugs:
|
||||
mongodb-plug:
|
||||
interface: content
|
||||
target: $SNAP_DATA/shared
|
||||
|
||||
hooks:
|
||||
configure:
|
||||
plugs:
|
||||
- network
|
||||
- network-bind
|
||||
|
||||
slots:
|
||||
mongodb-slot:
|
||||
interface: content
|
||||
write:
|
||||
- $SNAP_DATA/share
|
||||
|
||||
apps:
|
||||
wekan:
|
||||
command: wekan-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
mongodb:
|
||||
command: mongodb-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
caddy:
|
||||
command: caddy-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
help:
|
||||
command: wekan-help
|
||||
|
||||
database-backup:
|
||||
command: mongodb-backup
|
||||
plugs: [network, network-bind]
|
||||
|
||||
database-list-backups:
|
||||
command: ls -al $SNAP_COMMON/db-backups/
|
||||
|
||||
database-restore:
|
||||
command: mongodb-restore
|
||||
plugs: [network, network-bind]
|
||||
|
||||
parts:
|
||||
mongodb:
|
||||
source: https://repo.mongodb.org/apt/ubuntu/dists/xenial/mongodb-org/4.2/multiverse/binary-amd64/mongodb-org-server_4.2.2_amd64.deb
|
||||
#https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.14.tgz
|
||||
#https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.22.tgz
|
||||
plugin: dump
|
||||
stage-packages: [libssl1.0.0, libcurl3]
|
||||
filesets:
|
||||
mongo:
|
||||
- usr
|
||||
- bin
|
||||
- lib
|
||||
stage:
|
||||
- $mongo
|
||||
prime:
|
||||
- $mongo
|
||||
|
||||
wekan:
|
||||
source: .
|
||||
plugin: nodejs
|
||||
node-engine: 14.19.0
|
||||
node-packages:
|
||||
- node-gyp
|
||||
- node-pre-gyp
|
||||
- fibers
|
||||
build-packages:
|
||||
- ca-certificates
|
||||
- apt-utils
|
||||
- build-essential
|
||||
- python
|
||||
- python3
|
||||
- g++
|
||||
- capnproto
|
||||
- curl
|
||||
- libcurl3
|
||||
- execstack
|
||||
- nodejs
|
||||
- npm
|
||||
stage-packages:
|
||||
- libfontconfig1
|
||||
override-build: |
|
||||
echo "Cleaning environment first"
|
||||
rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
|
||||
rm -rf .build
|
||||
echo "Installing meteor"
|
||||
curl https://install.meteor.com/ -o install_meteor.sh
|
||||
chmod +x install_meteor.sh
|
||||
sh install_meteor.sh
|
||||
rm install_meteor.sh
|
||||
rm -rf .build
|
||||
meteor add standard-minifier-js --allow-superuser
|
||||
meteor npm install --allow-superuser
|
||||
meteor npm install --allow-superuser --save babel-runtime
|
||||
meteor build .build --directory --allow-superuser
|
||||
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
|
||||
cd .build/bundle/programs/server
|
||||
npm install
|
||||
npm install --allow-superuser --save babel-runtime
|
||||
# Change back to Wekan source directory
|
||||
cd ../../../..
|
||||
cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
|
||||
cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
|
||||
organize:
|
||||
README: README.wekan
|
||||
prime:
|
||||
- -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
|
||||
|
||||
helpers:
|
||||
source: snap-src
|
||||
plugin: dump
|
||||
|
||||
caddy:
|
||||
plugin: dump
|
||||
source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
|
||||
source-type: tar
|
||||
organize:
|
||||
caddy: bin/caddy
|
||||
CHANGES.txt: CADDY_CHANGES.txt
|
||||
EULA.txt: CADDY_EULA.txt
|
||||
LICENSES.txt: CADDY_LICENSES.txt
|
||||
README.txt: CADDY_README.txt
|
||||
stage:
|
||||
- -init
|
||||
@@ -1,183 +0,0 @@
|
||||
name: wekan
|
||||
version: '6.21'
|
||||
base: core20
|
||||
summary: Open Source kanban
|
||||
description: |
|
||||
WeKan ® is an Open Source and collaborative kanban board application.
|
||||
|
||||
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.
|
||||
Depending on target environment, some configuration settings might need to be adjusted.
|
||||
For full list of configuration options call:
|
||||
$ wekan.help
|
||||
|
||||
confinement: strict
|
||||
grade: stable
|
||||
|
||||
architectures:
|
||||
- build-on: amd64
|
||||
run-on: amd64
|
||||
|
||||
- build-on: arm64
|
||||
run-on: arm64
|
||||
|
||||
- build-on: ppc64el
|
||||
run-on: ppc64el
|
||||
|
||||
- build-on: s390x
|
||||
run-on: s390x
|
||||
|
||||
plugs:
|
||||
mongodb-plug:
|
||||
interface: content
|
||||
target: $SNAP_DATA/shared
|
||||
|
||||
hooks:
|
||||
configure:
|
||||
plugs:
|
||||
- network
|
||||
- network-bind
|
||||
|
||||
slots:
|
||||
mongodb-slot:
|
||||
interface: content
|
||||
write:
|
||||
- $SNAP_DATA/share
|
||||
|
||||
apps:
|
||||
wekan:
|
||||
command: wekan-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
|
||||
restart-condition: on-failure
|
||||
|
||||
mongodb:
|
||||
command: mongodb-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
|
||||
restart-condition: on-failure
|
||||
|
||||
caddy:
|
||||
command: caddy-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
help:
|
||||
command: wekan-help
|
||||
|
||||
database-backup:
|
||||
command: mongodb-backup
|
||||
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
|
||||
|
||||
database-list-backups:
|
||||
command: ls -al $SNAP_COMMON/db-backups/
|
||||
|
||||
database-restore:
|
||||
command: mongodb-restore
|
||||
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
|
||||
|
||||
parts:
|
||||
mongodb:
|
||||
plugin: dump
|
||||
source:
|
||||
- on amd64: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-amd64/mongodb-org-server_4.4.13_amd64.deb
|
||||
- on arm64: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-arm64/mongodb-org-server_4.4.13_arm64.deb
|
||||
- on ppc64el: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-ppc64el/mongodb-org-server_4.4.13_ppc64el.deb
|
||||
- on s390x: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-s390x/mongodb-org-server_4.4.13_s390x.deb
|
||||
stage-packages:
|
||||
- libssl1.1
|
||||
- libcurl3-dev
|
||||
- libcurl4-openssl-dev
|
||||
filesets:
|
||||
mongo:
|
||||
- usr
|
||||
- bin
|
||||
- lib
|
||||
stage:
|
||||
- $mongo
|
||||
prime:
|
||||
- $mongo
|
||||
|
||||
wekan:
|
||||
#plugin: npm
|
||||
plugin: dump
|
||||
source:
|
||||
# Fixed URLs to some allowed GitHub releases URL.
|
||||
# Non-GitHub build server file urls are not allowed at 2022-03-02 and later.
|
||||
- on amd64: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-amd64.zip
|
||||
- on arm64: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-arm64.zip
|
||||
- on ppc64el: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-ppc64el.zip
|
||||
- on s390x: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-s390x.zip
|
||||
# npm-node-version: 14.19.1
|
||||
# node-packages:
|
||||
# - node-gyp
|
||||
# - node-pre-gyp
|
||||
# - fibers
|
||||
# build-packages:
|
||||
# - npm
|
||||
# - build-essential
|
||||
# - ca-certificates
|
||||
# - apt-utils
|
||||
# - python
|
||||
# - python3
|
||||
# - g++
|
||||
# - capnproto
|
||||
# - curl
|
||||
# - execstack
|
||||
# - nodejs
|
||||
# - npm
|
||||
# - p7zip-full
|
||||
# stage-packages:
|
||||
# - libfontconfig1
|
||||
override-build: |
|
||||
cp -r bundle/* $SNAPCRAFT_PART_INSTALL/
|
||||
cp bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
|
||||
snapcraftctl build
|
||||
organize:
|
||||
README: README.wekan
|
||||
prime:
|
||||
- -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
|
||||
- -lib/node_modules/weka*
|
||||
|
||||
helpers:
|
||||
source: snap-src
|
||||
plugin: dump
|
||||
|
||||
caddy:
|
||||
plugin: dump
|
||||
## Caddy v1 is not developed anymore. TODO: Sometime migrate to Caddy v2.
|
||||
## https://caddy.community/t/caddyfile-v1-adapter/9129
|
||||
## https://github.com/caddyserver/caddy/tree/v1
|
||||
#source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
|
||||
#source-type: tar
|
||||
# Using last working binary that was downloaded from above URL to Wekan Snap,
|
||||
# and .txt files from https://github.com/caddyserver/caddy/tree/v1/dist
|
||||
source: https://wekan.github.io/caddy-v1-linux-amd64.7z
|
||||
source-type: 7z
|
||||
organize:
|
||||
caddy: bin/caddy
|
||||
CHANGES.txt: CADDY_CHANGES.txt
|
||||
EULA.txt: CADDY_EULA.txt
|
||||
LICENSES.txt: CADDY_LICENSES.txt
|
||||
README.txt: CADDY_README.txt
|
||||
stage:
|
||||
- -init
|
||||
|
||||
caddy2:
|
||||
plugin: dump
|
||||
source:
|
||||
# Fixed URLs to some allowed GitHub releases URL.
|
||||
# Non-GitHub build server file urls are not allowed at 2022-03-02 and later.
|
||||
- on amd64: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-amd64.zip
|
||||
- on arm64: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-arm64.zip
|
||||
- on ppc64el: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-ppc64el.zip
|
||||
- on s390x: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-s390x.zip
|
||||
source-type: zip
|
||||
organize:
|
||||
caddy: bin/caddy
|
||||
CHANGES.txt: CADDY_CHANGES.txt
|
||||
EULA.txt: CADDY_EULA.txt
|
||||
LICENSES.txt: CADDY_LICENSES.txt
|
||||
README.txt: CADDY_README.txt
|
||||
stage:
|
||||
- -init
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
custom: ['https://wekan.team/commercial-support/']
|
||||
custom: ['https://wekan.fi/commercial-support/']
|
||||
@@ -1,12 +1,15 @@
|
||||
## Issue
|
||||
|
||||
UPGRADE: https://wekan.fi/upgrade/
|
||||
|
||||
Pull requests welcome to fix any broken links at docs directory, and organizing docs/Features and their screenshots to subdirectories of each feature.
|
||||
|
||||
Please report these issues elsewhere:
|
||||
|
||||
- SECURITY ISSUES, PGP EMAIL: https://github.com/wekan/wekan/blob/main/SECURITY.md
|
||||
- UCS: https://github.com/wekan/univention/issues
|
||||
|
||||
**[PLEASE UPGRADE](https://github.com/wekan/wekan/wiki/Backup)** to the newest
|
||||
WeKan ® before reporting an issue, if possible.
|
||||
If WeKan Snap is slow, try this: https://github.com/wekan/wekan/wiki/Cron
|
||||
|
||||
Please search existing Open and Closed issues, most questions have already been answered.
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v4
|
||||
@@ -32,13 +32,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -48,14 +48,14 @@ jobs:
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
# Build and push Docker image with Buildx (don't push on PR)
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
@@ -15,6 +15,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file Dockerfile --tag wekan:$(date +%s)
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -25,6 +25,6 @@ jobs:
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Run chart-releaser
|
||||
uses: helm/chart-releaser-action@v1.6.0
|
||||
uses: helm/chart-releaser-action@v1.7.0
|
||||
env:
|
||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: checkout
|
||||
# uses: actions/checkout@v4
|
||||
# uses: actions/checkout@v5
|
||||
#
|
||||
# - name: setup node
|
||||
# uses: actions/setup-node@v1
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
# needs: [lintcode]
|
||||
# steps:
|
||||
# - name: checkout
|
||||
# uses: actions/checkout@v4
|
||||
# uses: actions/checkout@v5
|
||||
#
|
||||
# - name: setup node
|
||||
# uses: actions/setup-node@v1
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
# needs: [lintcode,lintstyle]
|
||||
# steps:
|
||||
# - name: checkout
|
||||
# uses: actions/checkout@v4
|
||||
# uses: actions/checkout@v5
|
||||
#
|
||||
# - name: setup node
|
||||
# uses: actions/setup-node@v1
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
|
||||
# CHECKOUTS
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# CACHING
|
||||
- name: Install Meteor
|
||||
@@ -147,10 +147,10 @@ jobs:
|
||||
needs: [tests]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download coverage
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: coverage-folder
|
||||
path: .coverage/
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, sl_SI: sl, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB
|
||||
lang_map = te_IN: te-IN, es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, sl_SI: sl, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB
|
||||
|
||||
[o:wekan:p:wekan:r:application]
|
||||
file_filter = imports/i18n/data/<lang>.i18n.json
|
||||
|
||||
Vendored
+48
-36
@@ -1,45 +1,57 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Meteor: Chrome",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Meteor: Node",
|
||||
"runtimeExecutable": "meteor",
|
||||
"runtimeArgs": [
|
||||
"--port=4000",
|
||||
"--exclude-archs=web.browser.legacy,web.cordova",
|
||||
"--raw-logs"
|
||||
],
|
||||
"env": {
|
||||
"WRITABLE_PATH": "/tmp/uploads",
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Meteor: Node",
|
||||
"runtimeExecutable": "/home/wekan/.meteor/meteor",
|
||||
"runtimeArgs": ["run", "--inspect-brk=9229"],
|
||||
"outputCapture": "std",
|
||||
"port": 9229,
|
||||
"timeout": 60000
|
||||
"outputCapture": "std",
|
||||
"restart": true,
|
||||
"timeout": 60000
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Meteor: Chrome",
|
||||
"url": "http://localhost:4000",
|
||||
"sourceMapPathOverrides": {
|
||||
"meteor://💻app/*": "${workspaceFolder}/*"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Test: Node",
|
||||
"runtimeExecutable": "meteor",
|
||||
"runtimeArgs": [
|
||||
"test",
|
||||
"--inspect-brk=9229",
|
||||
"--port=4040",
|
||||
"--exclude-archs=web.browser.legacy,web.cordova",
|
||||
"--driver-package=meteortesting:mocha",
|
||||
"--settings=settings.json"
|
||||
],
|
||||
"outputCapture": "std",
|
||||
"port": 9229,
|
||||
"timeout": 60000
|
||||
}
|
||||
"userDataDir": "${env:HOME}/.vscode/chrome"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Test: Node",
|
||||
"runtimeExecutable": "meteor",
|
||||
"runtimeArgs": [
|
||||
"test",
|
||||
"--port=4040",
|
||||
"--exclude-archs=web.browser.legacy,web.cordova",
|
||||
"--driver-package=meteortesting:mocha",
|
||||
"--settings=settings.json",
|
||||
"--raw-logs"
|
||||
],
|
||||
"env": {
|
||||
"TEST_WATCH": "1"
|
||||
},
|
||||
"outputCapture": "std",
|
||||
"timeout": 60000
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Meteor: All",
|
||||
"configurations": ["Meteor: Node", "Meteor: Chrome"]
|
||||
}
|
||||
{
|
||||
"name": "Meteor: All",
|
||||
"configurations": ["Meteor: Node", "Meteor: Chrome"]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+12
-11
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.rulers": [80],
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"node_modules": true,
|
||||
".meteor/local": true
|
||||
}
|
||||
}
|
||||
"terminal.integrated.env.linux": {
|
||||
"TERM": "xterm-256color"
|
||||
},
|
||||
"terminal.integrated.env.osx": {
|
||||
"TERM": "xterm-256color"
|
||||
},
|
||||
"terminal.integrated.env.windows": {
|
||||
"TERM": "xterm-256color"
|
||||
},
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"terminal.integrated.shellArgs.linux": ["-l"]
|
||||
}
|
||||
+1047
-10
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -11,7 +11,7 @@ For all code at WeKan GitHub Organization https://github.com/wekan
|
||||
|
||||
## Private reports
|
||||
|
||||
- Email support@wekan.team
|
||||
- Email support@wekan.fi
|
||||
- Security issues: [SECURITY.md](SECURITY.md)
|
||||
- License violations
|
||||
- Anything private, sensitive or negative
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ Not paid:
|
||||
|
||||
Paid by customers of WeKan Team:
|
||||
|
||||
- Commercial Support at https://wekan.team/commercial-support/
|
||||
- Commercial Support at https://wekan.fi/commercial-support/
|
||||
- Support
|
||||
- Private Chat
|
||||
- Features
|
||||
|
||||
+51
-30
@@ -13,7 +13,7 @@ LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
|
||||
# to Quay to avoid Docker Hub rate limits.
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3"
|
||||
ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
|
||||
|
||||
ENV \
|
||||
DEBUG=false \
|
||||
@@ -66,6 +66,7 @@ ENV \
|
||||
OAUTH2_LOGIN_STYLE=redirect \
|
||||
OAUTH2_CLIENT_ID="" \
|
||||
OAUTH2_SECRET="" \
|
||||
OAUTH2_SECRET_FILE="" \
|
||||
OAUTH2_SERVER_URL="" \
|
||||
OAUTH2_AUTH_ENDPOINT="" \
|
||||
OAUTH2_USERINFO_ENDPOINT="" \
|
||||
@@ -91,6 +92,7 @@ ENV \
|
||||
LDAP_AUTHENTIFICATION=false \
|
||||
LDAP_AUTHENTIFICATION_USERDN="" \
|
||||
LDAP_AUTHENTIFICATION_PASSWORD="" \
|
||||
LDAP_AUTHENTIFICATION_PASSWORD_FILE="" \
|
||||
LDAP_LOG_ENABLED=false \
|
||||
LDAP_BACKGROUND_SYNC=false \
|
||||
LDAP_BACKGROUND_SYNC_INTERVAL="" \
|
||||
@@ -156,7 +158,10 @@ ENV \
|
||||
ORACLE_OIM_ENABLED=false \
|
||||
WAIT_SPINNER="" \
|
||||
WRITABLE_PATH=/data \
|
||||
S3=""
|
||||
S3="" \
|
||||
MAIL_SERVICE_PASSWORD_FILE="" \
|
||||
MONGO_PASSWORD_FILE="" \
|
||||
S3_SECRET_FILE=""
|
||||
|
||||
# NODE_OPTIONS="--max_old_space_size=4096"
|
||||
|
||||
@@ -167,7 +172,7 @@ ENV \
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Copy the app to the image
|
||||
COPY ${SRC_PATH} /home/wekan/app
|
||||
#COPY ${SRC_PATH} /home/wekan/app
|
||||
|
||||
# Install OS
|
||||
RUN <<EOR
|
||||
@@ -188,58 +193,73 @@ ln -sf $(which bsdtar) $(which tar)
|
||||
cd /tmp
|
||||
|
||||
# Download nodejs
|
||||
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz"
|
||||
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt"
|
||||
#wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz"
|
||||
wget "https://github.com/wekan/node-v14-esm/releases/download/v14.21.4/node-v14.21.4-linux-x64.tar.gz"
|
||||
#wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt"
|
||||
wget "https://github.com/wekan/node-v14-esm/releases/download/v14.21.4/SHASUMS256.txt"
|
||||
|
||||
# Verify nodejs authenticity
|
||||
grep "node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c -
|
||||
#grep "node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c -
|
||||
grep "node-v14.21.4-linux-x64.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c -
|
||||
rm -f "SHASUMS256.txt"
|
||||
|
||||
# Install Node
|
||||
tar xzf "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
|
||||
rm "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" "SHASUMS256.txt"
|
||||
#tar xzf "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
|
||||
tar xzf "node-v14.21.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
|
||||
#rm "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" "SHASUMS256.txt"
|
||||
rm "node-v14.21.4-linux-x64.tar.gz" "SHASUMS256.txt"
|
||||
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
|
||||
mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/${NODE_VERSION} /home/wekan/.config"
|
||||
#mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/${NODE_VERSION} /home/wekan/.config"
|
||||
#mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/v14.21.4 /home/wekan/.config"
|
||||
|
||||
# Install node dependencies
|
||||
npm install -g npm@${NPM_VERSION} --production
|
||||
#npm install -g npm@${NPM_VERSION} --production
|
||||
npm install -g npm@$6.14.17 --production
|
||||
chown --recursive wekan:wekan /home/wekan/.config
|
||||
|
||||
# Install Meteor
|
||||
cd /home/wekan
|
||||
chown --recursive wekan:wekan /home/wekan
|
||||
echo "Starting meteor ${METEOR_RELEASE} installation... \n"
|
||||
gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh
|
||||
mv /root/.meteor /home/wekan/
|
||||
chown --recursive wekan:wekan /home/wekan/.meteor
|
||||
#gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh
|
||||
# Specify Meteor version 2.14 to be compatible: https://github.com/wekan/wekan/pull/5816/files
|
||||
#gosu wekan:wekan npm -g install meteor@2.14 --unsafe-perm
|
||||
#mv /root/.meteor /home/wekan/
|
||||
#chown --recursive wekan:wekan /home/wekan/.meteor
|
||||
|
||||
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
|
||||
#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
|
||||
|
||||
# Build app (Production)
|
||||
#cd /home/wekan/app
|
||||
mkdir -p /home/wekan/app
|
||||
cd /home/wekan/app
|
||||
mkdir -p /home/wekan/.npm
|
||||
chown --recursive wekan:wekan /home/wekan/.npm
|
||||
chmod u+w *.json
|
||||
gosu wekan:wekan meteor npm install --production
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build
|
||||
cd /home/wekan/app_build/bundle/programs/server/
|
||||
chmod u+w *.json
|
||||
gosu wekan:wekan meteor npm install --production
|
||||
cd node_modules/fibers
|
||||
node build.js
|
||||
cd ../..
|
||||
#mkdir -p /home/wekan/.npm
|
||||
#chown --recursive wekan:wekan /home/wekan/.npm
|
||||
#chmod u+w *.json
|
||||
#gosu wekan:wekan meteor npm install --production
|
||||
#gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build
|
||||
#cd /home/wekan/app_build/bundle/programs/server/
|
||||
#chmod u+w *.json
|
||||
#gosu wekan:wekan meteor npm install --production
|
||||
#cd node_modules/fibers
|
||||
#node build.js
|
||||
#cd ../..
|
||||
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
|
||||
rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy
|
||||
mv /home/wekan/app_build/bundle /build
|
||||
#rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy
|
||||
#mv /home/wekan/app_build/bundle /build
|
||||
wget "https://github.com/wekan/wekan/releases/download/v8.02/wekan-8.02-amd64.zip"
|
||||
unzip wekan-8.02-amd64.zip
|
||||
rm wekan-8.02-amd64.zip
|
||||
mv /home/wekan/app/bundle /build
|
||||
|
||||
# Put back the original tar
|
||||
mv $(which tar)~ $(which tar)
|
||||
|
||||
# Cleanup
|
||||
apt-get remove --purge --assume-yes ${BUILD_DEPS}
|
||||
npm uninstall -g api2html
|
||||
#npm uninstall -g api2html
|
||||
apt-get autoremove --assume-yes
|
||||
apt-get clean --assume-yes
|
||||
rm -Rf /tmp/*
|
||||
@@ -272,4 +292,5 @@ WORKDIR /home/wekan/app
|
||||
#
|
||||
# CMD ["node", "/build/main.js"]
|
||||
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
|
||||
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /build/main.js"]
|
||||
CMD ["bash", "-c", "ulimit -s 65500; exec node /build/main.js"]
|
||||
+7
-1
@@ -53,7 +53,12 @@ ENV QEMU_ARCHITECTURE=aarch64 \
|
||||
WITH_API=true \
|
||||
PORT=8080 \
|
||||
ROOT_URL=http://localhost \
|
||||
MONGO_URL=mongodb://127.0.0.1:27017/wekan
|
||||
MONGO_URL=mongodb://127.0.0.1:27017/wekan \
|
||||
LDAP_AUTHENTIFICATION_PASSWORD_FILE="" \
|
||||
OAUTH2_SECRET_FILE="" \
|
||||
MAIL_SERVICE_PASSWORD_FILE="" \
|
||||
MONGO_PASSWORD_FILE="" \
|
||||
S3_SECRET_FILE=""
|
||||
|
||||
# Copy qemu-static to image
|
||||
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
|
||||
@@ -88,5 +93,6 @@ RUN \
|
||||
EXPOSE $PORT
|
||||
USER wekan
|
||||
|
||||
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /home/wekan/bundle/main.js"]
|
||||
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]
|
||||
|
||||
+6
-1
@@ -56,7 +56,12 @@ ENV QEMU_ARCHITECTURE=s390x \
|
||||
WITH_API=true \
|
||||
PORT=8080 \
|
||||
ROOT_URL=http://localhost \
|
||||
MONGO_URL=mongodb://127.0.0.1:27017/wekan
|
||||
MONGO_URL=mongodb://127.0.0.1:27017/wekan \
|
||||
LDAP_AUTHENTIFICATION_PASSWORD_FILE="" \
|
||||
OAUTH2_SECRET_FILE="" \
|
||||
MAIL_SERVICE_PASSWORD_FILE="" \
|
||||
MONGO_PASSWORD_FILE="" \
|
||||
S3_SECRET_FILE=""
|
||||
|
||||
# Copy qemu-static to image
|
||||
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2024 The Wekan Team
|
||||
Copyright (c) Lauri Ojansivu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
[Gitpod Ready-to-Code](https://gitpod.io/#https://github.com/wekan/wekan)
|
||||
|
||||
# WeKan ® - Open Source kanban
|
||||
|
||||
## Downloads
|
||||
|
||||
https://wekan.github.io / Install WeKan ® Server
|
||||
https://wekan.fi/install/
|
||||
|
||||
## Docker Containers
|
||||
|
||||
@@ -16,7 +14,7 @@ docker-compose.yml at https://github.com/wekan/wekan/blob/main/docker-compose.ym
|
||||
|
||||
## Standards
|
||||
|
||||
- [WeKan and Standard for Public Code](https://wekan.github.io/standard-for-public-code/) assessment was made at 2023-11.
|
||||
- [WeKan and Standard for Public Code](https://wekan.fi/standard-for-public-code/) assessment was made at 2023-11.
|
||||
Currently Wekan meets 8 out of 16 criteria out of the box.
|
||||
Some others could be met with small changes.
|
||||
|
||||
@@ -27,21 +25,19 @@ docker-compose.yml at https://github.com/wekan/wekan/blob/main/docker-compose.ym
|
||||
- [Open Hub](https://www.openhub.net/p/wekan)
|
||||
- [OSS Insight](https://ossinsight.io/analyze/wekan/wekan)
|
||||
|
||||
## [Translate WeKan ® at Transifex](https://app.transifex.com/wekan/)
|
||||
## Translate WeKan ® at Transifex
|
||||
|
||||
Translations to non-English languages are accepted only at [Transifex](https://app.transifex.com/wekan/wekan) using webbrowser.
|
||||
New English strings of new features can be added as PRs to master branch file wekan/imports/i18n/data/en.i18n.json .
|
||||
|
||||
## [WeKan ® feature requests and bugs](https://github.com/wekan/wekan/issues)
|
||||
## WeKan ® feature requests and bugs
|
||||
|
||||
Please add most of your questions as GitHub issue: [WeKan ® Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
|
||||
It's better than at chat where details get lost when chat scrolls up.
|
||||
|
||||
## Chat
|
||||
## Discussions
|
||||
|
||||
[Discussions][discussions] - WeKan Community GitHub Discussions, that are not [Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
|
||||
|
||||
[WeKan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ)
|
||||
[IRC](https://github.com/wekan/wekan/blob/main/docs/FAQ/IRC-FAQ.md)
|
||||
|
||||
## Docker: Latest tag has newest release
|
||||
|
||||
@@ -51,8 +47,8 @@ See bottom of https://github.com/wekan/wekan/issues/3874
|
||||
## FAQ
|
||||
|
||||
**NOTE**:
|
||||
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first
|
||||
- Please don't feed the [trolls](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
|
||||
- Please read the [FAQ](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md) first
|
||||
- Please don't feed the [trolls](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
|
||||
|
||||
## About WeKan ®
|
||||
|
||||
@@ -72,14 +68,11 @@ that by providing one-click installation on various platforms.
|
||||
- WeKan ® largest user has 30k users using WeKan ® in their company.
|
||||
- WeKan ® has been [translated](https://app.transifex.com/wekan/) to about 105 languages.
|
||||
- [Features][features]: WeKan ® has real-time user interface.
|
||||
- [Platforms][platforms]: WeKan ® supports many platforms.
|
||||
- [Platforms](https://wekan.fi/install/): WeKan ® supports many platforms.
|
||||
WeKan ® is critical part of new platforms Wekan is currently being integrated to.
|
||||
|
||||
## 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), bundle for RasPi3 ARM and other CPUs where Node.js and MongoDB exists.
|
||||
- 1 GB RAM minimum free for WeKan ®. Production server should have minimum total 4 GB RAM.
|
||||
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/main/docker-compose.yml): 3 frontend servers,
|
||||
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
|
||||
@@ -89,7 +82,7 @@ that by providing one-click installation on various platforms.
|
||||
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/main/CHANGELOG.md).
|
||||
- [Backups](https://github.com/wekan/wekan/wiki/Backup) of WeKan ® database once a day miminum.
|
||||
- [Backups](https://github.com/wekan/wekan/blob/main/docs/Backup/Backup.md) 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.
|
||||
|
||||
@@ -102,14 +95,22 @@ that by providing one-click installation on various platforms.
|
||||
- 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/main/CHANGELOG.md).
|
||||
- [Please add Add new Feature Requests and Bug Reports immediately](https://github.com/wekan/wekan/issues).
|
||||
- [Commercial Support](https://wekan.team/commercial-support/).
|
||||
- [Commercial Support](https://wekan.fi/commercial-support/).
|
||||
|
||||
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.
|
||||
|
||||
## Getting Started with Development
|
||||
|
||||
The default branch uses [Meteor 2 with Node.js 14](https://wekan.fi/install/).
|
||||
|
||||
To contribute, [create a fork](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#2-create-fork-of-httpsgithubcomwekanwekan-at-github-web-page) and run `./rebuild-wekan.sh` (or `./rebuild-wekan.bat` on Windows) as detailed [here](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#3-select-option-1-to-install-dependencies-and-then-enter). Once you're ready, please test your code and [submit a pull request (PR)](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#7-test).
|
||||
|
||||
Please refer to the [developer documentation](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Developer-Documentation.md) for more information.
|
||||
|
||||
## Screenshot
|
||||
|
||||
[More screenshots at Features page](https://github.com/wekan/wekan/wiki/Features)
|
||||
[More screenshots at Features page](https://github.com/wekan/wekan/tree/main/docs/Features)
|
||||
|
||||
[![Screenshot of WeKan ®][screenshot_wekan]][roadmap_wekan]
|
||||
|
||||
@@ -118,16 +119,12 @@ By working directly with WeKan ® you get the benefit of active maintenance and
|
||||
WeKan ® is released under the very permissive [MIT license](LICENSE), and made
|
||||
with [Meteor](https://www.meteor.com).
|
||||
|
||||
[platforms]: https://github.com/wekan/wekan/wiki/Platforms
|
||||
[dev_docs]: https://github.com/wekan/wekan/wiki/Developer-Documentation
|
||||
[screenshot_wekan]: https://wekan.github.io/wekan-dark-mode.png
|
||||
[dev_docs]: https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Developer-Documentation.md
|
||||
[screenshot_wekan]: https://wekan.fi/wekan-dark-mode.png
|
||||
[features]: https://github.com/wekan/wekan/wiki/Features
|
||||
[roadmap_wekan]: https://boards.wekan.team/b/D2SzJKZDS4Z48yeQH/wekan-open-source-kanban-board-with-mit-license
|
||||
[wekan_issues]: https://github.com/wekan/wekan/issues
|
||||
[wekan_issues]: https://github.com/wekan/wekan/issues
|
||||
[docker_image]: https://hub.docker.com/r/wekanteam/wekan/
|
||||
[wekan_wiki]: https://github.com/wekan/wekan/wiki
|
||||
[translate_wekan]: https://app.transifex.com/wekan/
|
||||
[translate_wekan]: https://app.transifex.com/wekan/wekan/
|
||||
[open_source]: https://en.wikipedia.org/wiki/Open-source_software
|
||||
[free_software]: https://en.wikipedia.org/wiki/Free_software
|
||||
[discussions]: https://github.com/wekan/wekan/discussions
|
||||
@@ -0,0 +1,113 @@
|
||||
# 🔐 WeKan — Login System Overview
|
||||
|
||||
This document provides a detailed overview of WeKan’s **login and authentication system**, covering client-side UI, server-side logic, external authentication methods, and potential upgrade paths.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Login Web UI
|
||||
|
||||
WeKan's login interface is implemented using a combination of:
|
||||
|
||||
- `layouts.jade` – Login HTML structure
|
||||
- `layouts.js` – Login logic and interactivity
|
||||
- `layouts.css` – Styling and layout
|
||||
|
||||
📁 Source: [`client/components/main`](https://github.com/wekan/wekan/tree/main/client/components/main)
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Server-Side Authentication
|
||||
|
||||
Server-side login functionality is handled in:
|
||||
|
||||
- [`server/authentication.js`](https://github.com/wekan/wekan/blob/main/server/authentication.js)
|
||||
|
||||
Other related configurations:
|
||||
|
||||
- 🔧 Account config: [`config/accounts.js`](https://github.com/wekan/wekan/blob/main/config/accounts.js)
|
||||
- 📨 Sign-up invitations: [`models/settings.js#L275`](https://github.com/wekan/wekan/blob/main/models/settings.js#L275)
|
||||
- 👤 User creation logic: [`models/users.js#L1339`](https://github.com/wekan/wekan/blob/main/models/users.js#L1339)
|
||||
|
||||
---
|
||||
|
||||
## 👥 Meteor User Accounts
|
||||
|
||||
WeKan utilizes Meteor’s `accounts` system. Relevant resources:
|
||||
|
||||
- 📚 Meteor 2.x Accounts Docs: [v2-docs.meteor.com/api/accounts](https://v2-docs.meteor.com/api/accounts)
|
||||
- 🔍 Meteor Packages:
|
||||
- [`packages`](https://github.com/wekan/wekan/blob/main/.meteor/packages)
|
||||
- [`versions`](https://github.com/wekan/wekan/blob/main/.meteor/versions)
|
||||
- 📦 Meteor 2.14 core packages: [Meteor 2.14 packages](https://github.com/meteor/meteor/tree/release/METEOR%402.14/packages)
|
||||
|
||||
---
|
||||
|
||||
## 🔐 External Authentication (OIDC, LDAP, etc.)
|
||||
|
||||
WeKan supports external authentication methods via internal packages.
|
||||
|
||||
📁 See [`packages/`](https://github.com/wekan/wekan/tree/main/packages) for:
|
||||
- OpenID Connect (OIDC)
|
||||
- LDAP
|
||||
- OAuth and other integrations
|
||||
|
||||
---
|
||||
|
||||
## 📦 NPM & AtmosphereJS Dependencies
|
||||
|
||||
- 🔗 `package.json`: [Dependencies list](https://github.com/wekan/wekan/blob/main/package.json)
|
||||
- 🧩 WekanTeam scoped NPM packages: [@wekanteam on npm](https://www.npmjs.com/search?q=%40wekanteam)
|
||||
- ☁️ AtmosphereJS Meteor packages: [atmospherejs.com](https://atmospherejs.com)
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Meteor Version & Upgrade Notes
|
||||
|
||||
- 📌 Current Version: **Meteor 2.14**
|
||||
- [`.meteor/release`](https://github.com/wekan/wekan/blob/main/.meteor/release)
|
||||
- 🔧 Maintained with only **critical fixes** until ~Summer 2025
|
||||
- 🚀 Migration to **Meteor 3** or a new framework is under consideration
|
||||
|
||||
📘 Meteor 3 API: [docs.meteor.com/api/accounts](https://docs.meteor.com/api/accounts)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Prototypes & Examples
|
||||
|
||||
### 🐘 PHP Prototype Sign-Up
|
||||
|
||||
Used in experimental versions:
|
||||
|
||||
- Step 1: [`sign-up1.php`](https://github.com/wekan/php/blob/main/page/sign-up1.php)
|
||||
- Step 2: [`sign-up2.php`](https://github.com/wekan/php/blob/main/page/sign-up2.php)
|
||||
- Main entry: [`index.php#L72-L83`](https://github.com/wekan/php/blob/main/public/index.php#L72-L83)
|
||||
|
||||
---
|
||||
|
||||
### 🎨 WeKan Studio Prototype
|
||||
|
||||
Sign-up logic in the **WeKan Studio** version:
|
||||
|
||||
- [`signUp.fmt`](https://github.com/wekan/wekanstudio/blob/main/srv/templates/login/signUp.fmt)
|
||||
|
||||
---
|
||||
|
||||
## 📎 Future Considerations
|
||||
|
||||
- Upgrading to **Meteor 3.x**
|
||||
- Refactoring frontend logic to fix translation rendering order
|
||||
- Exploring **simplified authentication systems** in future prototypes
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Project Links
|
||||
|
||||
- 🔧 Main Repo: [github.com/wekan/wekan](https://github.com/wekan/wekan)
|
||||
- 🌐 Website: [wekan.github.io](https://wekan.github.io)
|
||||
- 📚 Documentation: [Wekan Wiki](https://github.com/wekan/wekan/wiki)
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
+6
-5
@@ -1,11 +1,12 @@
|
||||
About money, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
|
||||
Security is very important to us. If you discover any issue regarding security, please disclose
|
||||
the information responsibly by sending an email to support@wekan.team and not by
|
||||
creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
|
||||
the information responsibly by sending an email from Protonmail to security@wekan.fi
|
||||
that is Protomail email address, or by using this PGP key
|
||||
[security-at-wekan.fi.asc](security-at-wekan.fi.asc) to security@wekan.fi
|
||||
and not by creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
|
||||
|
||||
We thank you with a place at our hall of fame page, that is
|
||||
at https://wekan.github.io/hall-of-fame
|
||||
We thank you with a place at our hall of fame page, that is at https://wekan.fi/hall-of-fame
|
||||
|
||||
## How should reports be formatted?
|
||||
|
||||
@@ -217,7 +218,7 @@ Typical already known or "no impact" bugs such as:
|
||||
- Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server.
|
||||
|
||||
Wekan is Open Source with MIT license, and free to use also for commercial use.
|
||||
We welcome all fixes to improve security by email to security (at) wekan.team .
|
||||
We welcome all fixes to improve security by email to security@wekan.team
|
||||
|
||||
## Bonus Points
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v7.59.0"
|
||||
appVersion: "v8.02.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -39,6 +39,12 @@ If *nix: chmod +x api.py => ./api.py users
|
||||
python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board
|
||||
python3 api.py editcustomfield BOARDID LISTID CARDID CUSTOMFIELDID NEWCUSTOMFIELDVALUE # Edit Custom Field
|
||||
python3 api.py listattachments BOARDID # List attachments
|
||||
python3 api.py uploadattachment BOARDID SWIMLANEID LISTID CARDID FILEPATH [STORAGE_BACKEND] # Upload attachment to card
|
||||
python3 api.py downloadattachment ATTACHMENTID OUTPUTPATH # Download attachment to local file
|
||||
python3 api.py attachmentinfo ATTACHMENTID # Get attachment information
|
||||
python3 api.py listcardattachments BOARDID SWIMLANEID LISTID CARDID # List attachments for specific card
|
||||
python3 api.py copymoveattachment ATTACHMENTID TARGETBOARDID TARGETSWIMLANEID TARGETLISTID TARGETCARDID [copy|move] # Copy or move attachment
|
||||
python3 api.py deleteattachment ATTACHMENTID # Delete attachment
|
||||
python3 api.py cardsbyswimlane SWIMLANEID LISTID # Retrieve cards list on a swimlane
|
||||
python3 api.py getcard BOARDID LISTID CARDID # Get card info
|
||||
python3 api.py addlabel BOARDID LISTID CARDID LABELID # Add label to a card
|
||||
@@ -750,3 +756,273 @@ if arguments == 1:
|
||||
data2 = body.text.replace('}',"}\n")
|
||||
print(data2)
|
||||
# ------- LIST OF PUBLIC BOARDS END -----------
|
||||
|
||||
# ------- NEW ATTACHMENT API ENDPOINTS START -----------
|
||||
|
||||
if sys.argv[1] == 'uploadattachment':
|
||||
# ------- UPLOAD ATTACHMENT START -----------
|
||||
if arguments < 5:
|
||||
print("Usage: python3 api.py uploadattachment BOARDID SWIMLANEID LISTID CARDID FILEPATH [STORAGE_BACKEND]")
|
||||
print("Storage backends: fs, gridfs, s3")
|
||||
exit(1)
|
||||
|
||||
boardid = sys.argv[2]
|
||||
swimlaneid = sys.argv[3]
|
||||
listid = sys.argv[4]
|
||||
cardid = sys.argv[5]
|
||||
filepath = sys.argv[6]
|
||||
storage_backend = sys.argv[7] if arguments > 6 else None
|
||||
|
||||
# Read file and convert to base64
|
||||
try:
|
||||
with open(filepath, 'rb') as f:
|
||||
file_data = f.read()
|
||||
import base64
|
||||
base64_data = base64.b64encode(file_data).decode('utf-8')
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File '{filepath}' not found")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error reading file: {e}")
|
||||
exit(1)
|
||||
|
||||
# Get file info
|
||||
import os
|
||||
filename = os.path.basename(filepath)
|
||||
import mimetypes
|
||||
file_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
|
||||
|
||||
# Prepare request data
|
||||
upload_data = {
|
||||
'boardId': boardid,
|
||||
'swimlaneId': swimlaneid,
|
||||
'listId': listid,
|
||||
'cardId': cardid,
|
||||
'fileData': base64_data,
|
||||
'fileName': filename,
|
||||
'fileType': file_type
|
||||
}
|
||||
|
||||
if storage_backend:
|
||||
upload_data['storageBackend'] = storage_backend
|
||||
|
||||
# Make API call
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey), 'Content-Type': 'application/json'}
|
||||
upload_url = wekanurl + 'api/attachment/upload'
|
||||
|
||||
try:
|
||||
response = requests.post(upload_url, headers=headers, json=upload_data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
print(f"Upload successful!")
|
||||
print(f"Attachment ID: {result.get('attachmentId')}")
|
||||
print(f"File: {result.get('fileName')}")
|
||||
print(f"Size: {result.get('fileSize')} bytes")
|
||||
print(f"Storage: {result.get('storageBackend')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Upload failed: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
print(f"Response: {e.response.text}")
|
||||
# ------- UPLOAD ATTACHMENT END -----------
|
||||
|
||||
if sys.argv[1] == 'downloadattachment':
|
||||
# ------- DOWNLOAD ATTACHMENT START -----------
|
||||
if arguments < 3:
|
||||
print("Usage: python3 api.py downloadattachment ATTACHMENTID OUTPUTPATH")
|
||||
exit(1)
|
||||
|
||||
attachmentid = sys.argv[2]
|
||||
outputpath = sys.argv[3]
|
||||
|
||||
# Make API call
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
download_url = wekanurl + f'api/attachment/download/{attachmentid}'
|
||||
|
||||
try:
|
||||
response = requests.get(download_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get('success'):
|
||||
# Decode base64 data and save to file
|
||||
import base64
|
||||
file_data = base64.b64decode(result.get('base64Data'))
|
||||
|
||||
with open(outputpath, 'wb') as f:
|
||||
f.write(file_data)
|
||||
|
||||
print(f"Download successful!")
|
||||
print(f"File saved to: {outputpath}")
|
||||
print(f"Original filename: {result.get('fileName')}")
|
||||
print(f"Size: {result.get('fileSize')} bytes")
|
||||
print(f"Storage: {result.get('storageBackend')}")
|
||||
else:
|
||||
print(f"Download failed: {result.get('message', 'Unknown error')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Download failed: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
print(f"Response: {e.response.text}")
|
||||
# ------- DOWNLOAD ATTACHMENT END -----------
|
||||
|
||||
if sys.argv[1] == 'attachmentinfo':
|
||||
# ------- ATTACHMENT INFO START -----------
|
||||
if arguments < 2:
|
||||
print("Usage: python3 api.py attachmentinfo ATTACHMENTID")
|
||||
exit(1)
|
||||
|
||||
attachmentid = sys.argv[2]
|
||||
|
||||
# Make API call
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
info_url = wekanurl + f'api/attachment/info/{attachmentid}'
|
||||
|
||||
try:
|
||||
response = requests.get(info_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get('success'):
|
||||
print("=== ATTACHMENT INFO ===")
|
||||
print(f"Attachment ID: {result.get('attachmentId')}")
|
||||
print(f"File Name: {result.get('fileName')}")
|
||||
print(f"File Size: {result.get('fileSize')} bytes")
|
||||
print(f"File Type: {result.get('fileType')}")
|
||||
print(f"Storage Backend: {result.get('storageBackend')}")
|
||||
print(f"Board ID: {result.get('boardId')}")
|
||||
print(f"Swimlane ID: {result.get('swimlaneId')}")
|
||||
print(f"List ID: {result.get('listId')}")
|
||||
print(f"Card ID: {result.get('cardId')}")
|
||||
print(f"Created At: {result.get('createdAt')}")
|
||||
print(f"Is Image: {result.get('isImage')}")
|
||||
print(f"Versions: {len(result.get('versions', []))}")
|
||||
else:
|
||||
print(f"Failed to get attachment info: {result.get('message', 'Unknown error')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Request failed: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
print(f"Response: {e.response.text}")
|
||||
# ------- ATTACHMENT INFO END -----------
|
||||
|
||||
if sys.argv[1] == 'listcardattachments':
|
||||
# ------- LIST CARD ATTACHMENTS START -----------
|
||||
if arguments < 5:
|
||||
print("Usage: python3 api.py listcardattachments BOARDID SWIMLANEID LISTID CARDID")
|
||||
exit(1)
|
||||
|
||||
boardid = sys.argv[2]
|
||||
swimlaneid = sys.argv[3]
|
||||
listid = sys.argv[4]
|
||||
cardid = sys.argv[5]
|
||||
|
||||
# Make API call
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
list_url = wekanurl + f'api/attachment/list/{boardid}/{swimlaneid}/{listid}/{cardid}'
|
||||
|
||||
try:
|
||||
response = requests.get(list_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get('success'):
|
||||
attachments = result.get('attachments', [])
|
||||
print(f"=== CARD ATTACHMENTS ({len(attachments)}) ===")
|
||||
for attachment in attachments:
|
||||
print(f"ID: {attachment.get('attachmentId')}")
|
||||
print(f"Name: {attachment.get('fileName')}")
|
||||
print(f"Size: {attachment.get('fileSize')} bytes")
|
||||
print(f"Type: {attachment.get('fileType')}")
|
||||
print(f"Storage: {attachment.get('storageBackend')}")
|
||||
print(f"Created: {attachment.get('createdAt')}")
|
||||
print("---")
|
||||
else:
|
||||
print(f"Failed to list attachments: {result.get('message', 'Unknown error')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Request failed: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
print(f"Response: {e.response.text}")
|
||||
# ------- LIST CARD ATTACHMENTS END -----------
|
||||
|
||||
if sys.argv[1] == 'copymoveattachment':
|
||||
# ------- COPY/MOVE ATTACHMENT START -----------
|
||||
if arguments < 6:
|
||||
print("Usage: python3 api.py copymoveattachment ATTACHMENTID TARGETBOARDID TARGETSWIMLANEID TARGETLISTID TARGETCARDID [copy|move]")
|
||||
exit(1)
|
||||
|
||||
attachmentid = sys.argv[2]
|
||||
targetboardid = sys.argv[3]
|
||||
targetswimlaneid = sys.argv[4]
|
||||
targetlistid = sys.argv[5]
|
||||
targetcardid = sys.argv[6]
|
||||
operation = sys.argv[7] if arguments > 6 else 'copy'
|
||||
|
||||
if operation not in ['copy', 'move']:
|
||||
print("Operation must be 'copy' or 'move'")
|
||||
exit(1)
|
||||
|
||||
# Prepare request data
|
||||
request_data = {
|
||||
'attachmentId': attachmentid,
|
||||
'targetBoardId': targetboardid,
|
||||
'targetSwimlaneId': targetswimlaneid,
|
||||
'targetListId': targetlistid,
|
||||
'targetCardId': targetcardid
|
||||
}
|
||||
|
||||
# Make API call
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey), 'Content-Type': 'application/json'}
|
||||
api_url = wekanurl + f'api/attachment/{operation}'
|
||||
|
||||
try:
|
||||
response = requests.post(api_url, headers=headers, json=request_data)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get('success'):
|
||||
print(f"{operation.capitalize()} successful!")
|
||||
if operation == 'copy':
|
||||
print(f"Source Attachment ID: {result.get('sourceAttachmentId')}")
|
||||
print(f"New Attachment ID: {result.get('newAttachmentId')}")
|
||||
else:
|
||||
print(f"Attachment ID: {result.get('attachmentId')}")
|
||||
print(f"Source Board: {result.get('sourceBoardId')}")
|
||||
print(f"Target Board: {result.get('targetBoardId')}")
|
||||
print(f"File: {result.get('fileName')}")
|
||||
print(f"Size: {result.get('fileSize')} bytes")
|
||||
else:
|
||||
print(f"{operation.capitalize()} failed: {result.get('message', 'Unknown error')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"{operation.capitalize()} failed: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
print(f"Response: {e.response.text}")
|
||||
# ------- COPY/MOVE ATTACHMENT END -----------
|
||||
|
||||
if sys.argv[1] == 'deleteattachment':
|
||||
# ------- DELETE ATTACHMENT START -----------
|
||||
if arguments < 2:
|
||||
print("Usage: python3 api.py deleteattachment ATTACHMENTID")
|
||||
exit(1)
|
||||
|
||||
attachmentid = sys.argv[2]
|
||||
|
||||
# Make API call
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
delete_url = wekanurl + f'api/attachment/delete/{attachmentid}'
|
||||
|
||||
try:
|
||||
response = requests.delete(delete_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get('success'):
|
||||
print("Delete successful!")
|
||||
print(f"Attachment ID: {result.get('attachmentId')}")
|
||||
print(f"File: {result.get('fileName')}")
|
||||
else:
|
||||
print(f"Delete failed: {result.get('message', 'Unknown error')}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Delete failed: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
print(f"Response: {e.response.text}")
|
||||
# ------- DELETE ATTACHMENT END -----------
|
||||
|
||||
# ------- NEW ATTACHMENT API ENDPOINTS END -----------
|
||||
@@ -4,3 +4,14 @@ if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/pwa-service-worker.js');
|
||||
});
|
||||
}
|
||||
|
||||
// Import board converter for on-demand conversion
|
||||
import '/client/lib/boardConverter';
|
||||
import '/client/components/boardConversionProgress';
|
||||
|
||||
// Import migration manager and progress UI
|
||||
import '/client/lib/migrationManager';
|
||||
import '/client/components/migrationProgress';
|
||||
|
||||
// Import cron settings
|
||||
import '/client/components/settings/cronSettings';
|
||||
@@ -1,16 +1,16 @@
|
||||
.activity-title {
|
||||
margin: 0 0.5em 0.8em;
|
||||
margin: 0 0.7vw 1vh;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.reactions-popup .add-comment-reaction {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
font-size: 22px;
|
||||
border-radius: 0.7vw;
|
||||
font-size: clamp(18px, 4vw, 22px);
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
width: 40px;
|
||||
line-height: 1.3;
|
||||
width: 5vw;
|
||||
}
|
||||
.reactions-popup .add-comment-reaction:hover {
|
||||
background-color: #b0c4de;
|
||||
@@ -19,13 +19,13 @@
|
||||
clear: both;
|
||||
}
|
||||
.activities .activity {
|
||||
margin: 0.5px 0;
|
||||
padding: 6px 0;
|
||||
margin: 0.1vh 0;
|
||||
padding: 0.8vh 0;
|
||||
display: flex;
|
||||
}
|
||||
.activities .activity .member {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 4vw;
|
||||
height: 4vw;
|
||||
}
|
||||
.activities .activity .activity-member {
|
||||
font-weight: 700;
|
||||
@@ -36,55 +36,18 @@
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
margin-left: 3px;
|
||||
margin-left: 0.4vw;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
}
|
||||
.activities .activity .activity-desc .activity-comment {
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
border-radius: 0.4vw;
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions {
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
gap: 5px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .open-comment-reaction-popup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
height: 24px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
|
||||
font-size: 8px;
|
||||
margin-top: -7px;
|
||||
margin-left: 1px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .reaction {
|
||||
cursor: pointer;
|
||||
border: 1px solid #808080;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .reaction.selected {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .reaction:hover {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .reaction .reaction-count {
|
||||
font-size: 12px;
|
||||
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.2);
|
||||
margin-top: 0.7vh;
|
||||
padding: 0.7vh 0.7vw;
|
||||
}
|
||||
.activities .activity .activity-desc .activity-checklist {
|
||||
display: block;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
template(name="activities")
|
||||
.activities.js-sidebar-activities
|
||||
//- We should use Template.dynamic here but there is a bug with
|
||||
//- blaze-components: https://github.com/peerlibrary/meteor-blaze-components/issues/30
|
||||
if $eq mode "board"
|
||||
+boardActivities
|
||||
else
|
||||
+cardActivities
|
||||
if showActivities
|
||||
.activities.js-sidebar-activities
|
||||
//- We should use Template.dynamic here but there is a bug with
|
||||
//- blaze-components: https://github.com/peerlibrary/meteor-blaze-components/issues/30
|
||||
if $eq mode "board"
|
||||
+boardActivities
|
||||
else
|
||||
+cardActivities
|
||||
|
||||
template(name="boardActivities")
|
||||
each activityData in currentBoard.activities
|
||||
@@ -15,31 +16,6 @@ template(name="cardActivities")
|
||||
each activityData in activities
|
||||
+activity(activity=activityData card=card mode=mode)
|
||||
|
||||
template(name="editOrDeleteComment")
|
||||
a.js-open-inlined-form {{_ "edit"}}
|
||||
= ' - '
|
||||
a.js-delete-comment {{_ "delete"}}
|
||||
|
||||
template(name="deleteCommentPopup")
|
||||
p {{_ "comment-delete"}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
||||
|
||||
template(name="commentReactions")
|
||||
.reactions
|
||||
each reaction in reactions
|
||||
span.reaction(class="{{#if isSelected reaction.userIds}}selected{{/if}}" data-codepoint="#{reaction.reactionCodepoint}" title="{{userNames reaction.userIds}}")
|
||||
span.reaction-codepoint !{reaction.reactionCodepoint}
|
||||
span.reaction-count #{reaction.userIds.length}
|
||||
if (currentUser.isBoardMember)
|
||||
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
|
||||
i.fa.fa-smile-o
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="addReactionPopup")
|
||||
.reactions-popup
|
||||
each codepoint in codepoints
|
||||
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
|
||||
|
||||
template(name="activity")
|
||||
.activity(data-id=activity._id)
|
||||
+userAvatar(userId=activity.user._id)
|
||||
@@ -129,38 +105,17 @@ template(name="activity")
|
||||
| {{{_ 'activity-checklist-item-removed' (sanitize activity.checklist.title) cardLink}}}.
|
||||
|
||||
//- comment activity ----------------------------------------------------
|
||||
if($eq mode 'card')
|
||||
//- if we are in card mode we display the comment in a way that it
|
||||
//- can be edited by the owner
|
||||
if($eq activity.activityType 'addComment')
|
||||
+inlinedForm(classNames='js-edit-comment')
|
||||
+editor(autofocus=true)
|
||||
= activity.comment.text
|
||||
.edit-controls
|
||||
button.primary(type="submit") {{_ 'edit'}}
|
||||
.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
.activity-comment
|
||||
+viewer
|
||||
= activity.comment.text
|
||||
+commentReactions(reactions=activity.comment.reactions commentId=activity.comment._id)
|
||||
if($eq currentUser._id activity.comment.userId)
|
||||
+editOrDeleteComment
|
||||
else if currentUser.isBoardAdmin
|
||||
+editOrDeleteComment
|
||||
if($eq activity.activityType 'deleteComment')
|
||||
| {{{_ 'activity-deleteComment' activity.commentId}}}.
|
||||
|
||||
if($eq activity.activityType 'deleteComment')
|
||||
| {{{_ 'activity-deleteComment' activity.commentId}}}.
|
||||
if($eq activity.activityType 'editComment')
|
||||
| {{{_ 'activity-editComment' activity.commentId}}}.
|
||||
|
||||
if($eq activity.activityType 'editComment')
|
||||
| {{{_ 'activity-editComment' activity.commentId}}}.
|
||||
else
|
||||
//- if we are not in card mode we only display a summary of the comment
|
||||
if($eq activity.activityType 'addComment')
|
||||
| {{{_ 'activity-on' cardLink}}}
|
||||
a.activity-comment(href="{{ activity.card.originRelativeUrl }}")
|
||||
+viewer
|
||||
= activity.comment.text
|
||||
if($eq activity.activityType 'addComment')
|
||||
| {{{_ 'activity-on' cardLink}}}
|
||||
a.activity-comment(href="{{ activity.card.originRelativeUrl }}")
|
||||
+viewer
|
||||
= activity.comment.text
|
||||
|
||||
//- date activity ------------------------------------------------
|
||||
if($eq activity.activityType 'a-receivedAt')
|
||||
@@ -206,6 +161,9 @@ template(name="activity")
|
||||
if($eq activity.activityType 'archivedList')
|
||||
| {{_ 'activity-archived' (sanitize listLabel)}}.
|
||||
|
||||
if($eq activity.activityType 'changedListTitle')
|
||||
| {{_ 'activity-changedListTitle' (sanitize listLabel) boardLabelLink}}
|
||||
|
||||
//- member activity ----------------------------------------------------
|
||||
if($eq activity.activityType 'joinMember')
|
||||
if($eq user._id activity.member._id)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { sanitizeHTML, sanitizeText } from '/imports/lib/secureDOMPurify';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
|
||||
const activitiesPerPage = 500;
|
||||
@@ -17,8 +18,10 @@ BlazeComponent.extendComponent({
|
||||
if (mode) {
|
||||
const capitalizedMode = Utils.capitalize(mode);
|
||||
let searchId;
|
||||
const showActivities = this.showActivities();
|
||||
if (mode === 'linkedcard' || mode === 'linkedboard') {
|
||||
searchId = Utils.getCurrentCard().linkedId;
|
||||
const currentCard = Utils.getCurrentCard();
|
||||
searchId = currentCard.linkedId;
|
||||
mode = mode.replace('linked', '');
|
||||
} else if (mode === 'card') {
|
||||
searchId = Utils.getCurrentCardId();
|
||||
@@ -26,11 +29,9 @@ BlazeComponent.extendComponent({
|
||||
searchId = Session.get(`current${capitalizedMode}`);
|
||||
}
|
||||
const limit = this.page.get() * activitiesPerPage;
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const hideSystem = user ? user.hasHiddenSystemMessages() : false;
|
||||
if (searchId === null) return;
|
||||
|
||||
this.subscribe('activities', mode, searchId, limit, hideSystem, () => {
|
||||
this.subscribe('activities', mode, searchId, limit, showActivities, () => {
|
||||
this.loadNextPageLocked = false;
|
||||
|
||||
// TODO the guard can be removed as soon as the TODO above is resolved
|
||||
@@ -56,14 +57,26 @@ BlazeComponent.extendComponent({
|
||||
this.loadNextPageLocked = true;
|
||||
}
|
||||
},
|
||||
}).register('activities');
|
||||
|
||||
Template.activities.helpers({
|
||||
activities() {
|
||||
const ret = this.card.activities();
|
||||
showActivities() {
|
||||
let ret = false;
|
||||
let mode = this.data()?.mode;
|
||||
if (mode) {
|
||||
if (mode === 'linkedcard' || mode === 'linkedboard') {
|
||||
const currentCard = Utils.getCurrentCard();
|
||||
ret = currentCard.showActivities ?? false;
|
||||
} else if (mode === 'card') {
|
||||
ret = this.data()?.card?.showActivities ?? false;
|
||||
} else {
|
||||
ret = Utils.getCurrentBoard().showActivities ?? false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
});
|
||||
activities() {
|
||||
const ret = this.data().card.activities();
|
||||
return ret;
|
||||
},
|
||||
}).register('activities');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
checkItem() {
|
||||
@@ -204,15 +217,11 @@ BlazeComponent.extendComponent({
|
||||
{
|
||||
href: source.url,
|
||||
},
|
||||
DOMPurify.sanitize(source.system, {
|
||||
ALLOW_UNKNOWN_PROTOCOLS: true,
|
||||
}),
|
||||
sanitizeHTML(source.system),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return DOMPurify.sanitize(source.system, {
|
||||
ALLOW_UNKNOWN_PROTOCOLS: true,
|
||||
});
|
||||
return sanitizeHTML(source.system);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -236,10 +245,10 @@ BlazeComponent.extendComponent({
|
||||
href: `${attachment.link()}?download=true`,
|
||||
target: '_blank',
|
||||
},
|
||||
DOMPurify.sanitize(attachment.name),
|
||||
sanitizeText(attachment.name),
|
||||
),
|
||||
)) ||
|
||||
DOMPurify.sanitize(this.currentData().activity.attachmentName)
|
||||
sanitizeText(this.currentData().activity.attachmentName)
|
||||
);
|
||||
},
|
||||
|
||||
@@ -249,37 +258,11 @@ BlazeComponent.extendComponent({
|
||||
return customField.name;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
// XXX We should use Popup.afterConfirmation here
|
||||
'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
|
||||
const commentId = this.data().activity.commentId;
|
||||
CardComments.remove(commentId);
|
||||
Popup.back();
|
||||
}),
|
||||
'submit .js-edit-comment'(evt) {
|
||||
evt.preventDefault();
|
||||
const commentText = this.currentComponent()
|
||||
.getValue()
|
||||
.trim();
|
||||
const commentId = Template.parentData().activity.commentId;
|
||||
if (commentText) {
|
||||
CardComments.update(commentId, {
|
||||
$set: {
|
||||
text: commentText,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('activity');
|
||||
|
||||
Template.activity.helpers({
|
||||
sanitize(value) {
|
||||
return DOMPurify.sanitize(value, { ALLOW_UNKNOWN_PROTOCOLS: true });
|
||||
return sanitizeHTML(value);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -350,7 +333,7 @@ function createCardLink(card, board) {
|
||||
href: card.originRelativeUrl(),
|
||||
class: 'action-card',
|
||||
},
|
||||
DOMPurify.sanitize(text, { ALLOW_UNKNOWN_PROTOCOLS: true }),
|
||||
sanitizeHTML(text),
|
||||
),
|
||||
)
|
||||
);
|
||||
@@ -367,7 +350,7 @@ function createBoardLink(board, list) {
|
||||
href: board.originRelativeUrl(),
|
||||
class: 'action-board',
|
||||
},
|
||||
DOMPurify.sanitize(text, { ALLOW_UNKNOWN_PROTOCOLS: true }),
|
||||
sanitizeHTML(text),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -63,3 +63,78 @@
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
.comments {
|
||||
clear: both;
|
||||
}
|
||||
.comments .comment {
|
||||
margin: 0.5px 0;
|
||||
padding: 6px 0;
|
||||
display: flex;
|
||||
}
|
||||
.comments .comment .member {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.comments .comment .comment-member {
|
||||
font-weight: 700;
|
||||
}
|
||||
.comments .comment .comment-desc {
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
margin-left: 3px;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
}
|
||||
.comments .comment .comment-desc .comment-text {
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions {
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
gap: 5px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
height: 24px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
|
||||
font-size: 8px;
|
||||
margin-top: -7px;
|
||||
margin-left: 1px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction {
|
||||
cursor: pointer;
|
||||
border: 1px solid #808080;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction.selected {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction:hover {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction .reaction-count {
|
||||
font-size: 12px;
|
||||
}
|
||||
.comments .comment .comment-desc .comment-meta {
|
||||
font-size: 0.8em;
|
||||
color: #999;
|
||||
}
|
||||
@@ -7,3 +7,59 @@ template(name="commentForm")
|
||||
| {{getUnsavedValue 'cardComment' currentCard._id}}
|
||||
.add-controls
|
||||
button.primary.confirm.clear.js-add-comment(type="submit") {{_ 'comment'}}
|
||||
|
||||
template(name="comments")
|
||||
.comments
|
||||
each commentData in getComments
|
||||
+comment(commentData)
|
||||
|
||||
template(name="comment")
|
||||
.comment
|
||||
+userAvatar(userId=userId)
|
||||
p.comment-desc
|
||||
span.comment-member
|
||||
+memberName(user=user)
|
||||
|
||||
+inlinedForm(classNames='js-edit-comment')
|
||||
+editor(autofocus=true)
|
||||
= text
|
||||
.edit-controls
|
||||
button.primary(type="submit") {{_ 'edit'}}
|
||||
.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
.comment-text
|
||||
+viewer
|
||||
= text
|
||||
+commentReactions(reactions=reactions commentId=_id)
|
||||
span(title=createdAt).comment-meta {{ moment createdAt }}
|
||||
if($eq currentUser._id userId)
|
||||
+editOrDeleteComment
|
||||
else if currentUser.isBoardAdmin
|
||||
+editOrDeleteComment
|
||||
|
||||
template(name="editOrDeleteComment")
|
||||
= ' - '
|
||||
a.js-open-inlined-form {{_ "edit"}}
|
||||
= ' - '
|
||||
a.js-delete-comment {{_ "delete"}}
|
||||
|
||||
template(name="deleteCommentPopup")
|
||||
p {{_ "comment-delete"}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
||||
|
||||
template(name="commentReactions")
|
||||
.reactions
|
||||
each reaction in reactions
|
||||
span.reaction(class="{{#if isSelected reaction.userIds}}selected{{/if}}" data-codepoint="#{reaction.reactionCodepoint}" title="{{userNames reaction.userIds}}")
|
||||
span.reaction-codepoint !{reaction.reactionCodepoint}
|
||||
span.reaction-count #{reaction.userIds.length}
|
||||
if (currentUser.isBoardMember)
|
||||
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
|
||||
i.fa.fa-smile-o
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="addReactionPopup")
|
||||
.reactions-popup
|
||||
each codepoint in codepoints
|
||||
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
|
||||
|
||||
@@ -55,6 +55,41 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
}).register('commentForm');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
getComments() {
|
||||
const ret = this.data().comments();
|
||||
return ret;
|
||||
},
|
||||
}).register("comments");
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
|
||||
const commentId = this.data()._id;
|
||||
CardComments.remove(commentId);
|
||||
Popup.back();
|
||||
}),
|
||||
'submit .js-edit-comment'(evt) {
|
||||
evt.preventDefault();
|
||||
const commentText = this.currentComponent()
|
||||
.getValue()
|
||||
.trim();
|
||||
const commentId = this.data()._id;
|
||||
if (commentText) {
|
||||
CardComments.update(commentId, {
|
||||
$set: {
|
||||
text: commentText,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register("comment");
|
||||
|
||||
// XXX This should be a static method of the `commentForm` component
|
||||
function resetCommentInput(input) {
|
||||
input.val(''); // without manually trigger, input event won't be fired
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/* Board Conversion Progress Styles */
|
||||
.board-conversion-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
z-index: 9999;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.board-conversion-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.board-conversion-modal {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.board-conversion-header {
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.board-conversion-header h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.board-conversion-header h3 i {
|
||||
margin-right: 8px;
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
.board-conversion-header p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.board-conversion-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.conversion-progress {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #2196F3, #21CBF3);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.3),
|
||||
transparent
|
||||
);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #2196F3;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.conversion-status {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.conversion-status i {
|
||||
margin-right: 8px;
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
.conversion-time {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
background-color: #f5f5f5;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.conversion-time i {
|
||||
margin-right: 6px;
|
||||
color: #FF9800;
|
||||
}
|
||||
|
||||
.board-conversion-footer {
|
||||
padding: 16px 24px 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.conversion-info {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.conversion-info i {
|
||||
margin-right: 6px;
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 600px) {
|
||||
.board-conversion-modal {
|
||||
width: 95%;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.board-conversion-header,
|
||||
.board-conversion-content,
|
||||
.board-conversion-footer {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.board-conversion-header h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
template(name="boardConversionProgress")
|
||||
.board-conversion-overlay(class="{{#if isConverting}}active{{/if}}")
|
||||
.board-conversion-modal
|
||||
.board-conversion-header
|
||||
h3
|
||||
i.fa.fa-cogs
|
||||
| {{_ 'converting-board'}}
|
||||
p {{_ 'converting-board-description'}}
|
||||
|
||||
.board-conversion-content
|
||||
.conversion-progress
|
||||
.progress-bar
|
||||
.progress-fill(style="width: {{conversionProgress}}%")
|
||||
.progress-text {{conversionProgress}}%
|
||||
|
||||
.conversion-status
|
||||
i.fa.fa-spinner.fa-spin
|
||||
| {{conversionStatus}}
|
||||
|
||||
.conversion-time(style="{{#unless conversionEstimatedTime}}display: none;{{/unless}}")
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'estimated-time-remaining'}}: {{conversionEstimatedTime}}
|
||||
|
||||
.board-conversion-footer
|
||||
.conversion-info
|
||||
i.fa.fa-info-circle
|
||||
| {{_ 'conversion-info-text'}}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import {
|
||||
boardConverter,
|
||||
isConverting,
|
||||
conversionProgress,
|
||||
conversionStatus,
|
||||
conversionEstimatedTime
|
||||
} from '/client/lib/boardConverter';
|
||||
|
||||
Template.boardConversionProgress.helpers({
|
||||
isConverting() {
|
||||
return isConverting.get();
|
||||
},
|
||||
|
||||
conversionProgress() {
|
||||
return conversionProgress.get();
|
||||
},
|
||||
|
||||
conversionStatus() {
|
||||
return conversionStatus.get();
|
||||
},
|
||||
|
||||
conversionEstimatedTime() {
|
||||
return conversionEstimatedTime.get();
|
||||
}
|
||||
});
|
||||
|
||||
Template.boardConversionProgress.onCreated(function() {
|
||||
// Subscribe to conversion state changes
|
||||
this.autorun(() => {
|
||||
isConverting.get();
|
||||
conversionProgress.get();
|
||||
conversionStatus.get();
|
||||
conversionEstimatedTime.get();
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,27 @@
|
||||
bottom: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
/* When zoom is 50% or lower, ensure full width like content */
|
||||
.board-wrapper[style*="transform: scale(0.5)"] {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.board-wrapper[style*="transform: scale(0.4)"] {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.board-wrapper[style*="transform: scale(0.3)"] {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.board-wrapper .board-canvas {
|
||||
position: absolute;
|
||||
@@ -15,9 +36,219 @@
|
||||
bottom: 0;
|
||||
transition: margin 0.1s;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
.board-wrapper .board-canvas.is-sibling-sidebar-open {
|
||||
margin-right: 248px;
|
||||
|
||||
/* Ensure horizontal scrollbar is visible for high zoom levels */
|
||||
.board-wrapper .board-canvas[style*="overflow-x: auto"] {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
/* Style the scrollbar for better visibility */
|
||||
.board-wrapper .board-canvas::-webkit-scrollbar {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.board-wrapper .board-canvas::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.board-wrapper .board-canvas::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.board-wrapper .board-canvas::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Ensure content area can scroll horizontally for high zoom levels */
|
||||
#content[style*="overflow-x: auto"] {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
/* Style content area scrollbar */
|
||||
#content::-webkit-scrollbar {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
#content::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
#content::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
#content::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Ensure scrollbars are always visible for high zoom levels */
|
||||
#content[style*="overflow-x: auto"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#content[style*="overflow-x: auto"]::-webkit-scrollbar {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
/* Force vertical scrollbar to always be visible */
|
||||
#content[style*="overflow-y: auto"] {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
/* Mobile - make all text 2x bigger inside #content by default (icons stay same size) */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
#content {
|
||||
font-size: 2em !important; /* 2x bigger base font size for content area */
|
||||
}
|
||||
|
||||
/* Make all text elements 2x bigger */
|
||||
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6,
|
||||
#content p, #content span, #content div, #content a, #content button,
|
||||
#content .minicard, #content .list-header-name, #content .board-header-btn,
|
||||
#content .card-title, #content .card-details, #content .card-description,
|
||||
#content .swimlane-header, #content .list-title, #content .card-text,
|
||||
#content .member, #content .member-name, #content .member-initials,
|
||||
#content .checklist-item, #content .checklist-title, #content .comment,
|
||||
#content .activity, #content .activity-text, #content .activity-time,
|
||||
#content .board-title, #content .board-description, #content .list-name,
|
||||
#content .card-text, #content .card-title, #content .card-description,
|
||||
#content .swimlane-title, #content .swimlane-description,
|
||||
#content .board-header-title, #content .board-header-description,
|
||||
#content .card-detail-title, #content .card-detail-description,
|
||||
#content .list-header-title, #content .list-header-description,
|
||||
#content .swimlane-header-title, #content .swimlane-header-description,
|
||||
#content .minicard-title, #content .minicard-description,
|
||||
#content .card-comment, #content .card-comment-text,
|
||||
#content .checklist-item-text, #content .checklist-item-title,
|
||||
#content .activity-item, #content .activity-item-text,
|
||||
#content .board-member, #content .board-member-name,
|
||||
#content .team-member, #content .team-member-name,
|
||||
#content .org-member, #content .org-member-name,
|
||||
#content .template-member, #content .template-member-name,
|
||||
#content .user-name, #content .user-email, #content .user-role,
|
||||
#content .setting-title, #content .setting-description,
|
||||
#content .popup-title, #content .popup-description,
|
||||
#content .modal-title, #content .modal-description,
|
||||
#content .notification-title, #content .notification-text,
|
||||
#content .announcement-title, #content .announcement-text,
|
||||
#content .offline-warning-title, #content .offline-warning-text,
|
||||
#content .error-title, #content .error-text,
|
||||
#content .success-title, #content .success-text,
|
||||
#content .info-title, #content .info-text,
|
||||
#content .warning-title, #content .warning-text {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* Keep icons the same size (don't scale them) */
|
||||
#content .fa, #content .icon, #content i {
|
||||
font-size: 1em !important; /* Keep original icon size */
|
||||
}
|
||||
|
||||
/* Reset specific icon sizes to prevent double scaling */
|
||||
#content .fa-home, #content .fa-bars, #content .fa-search,
|
||||
#content .fa-bell, #content .fa-user, #content .fa-cog,
|
||||
#content .fa-plus, #content .fa-minus, #content .fa-edit,
|
||||
#content .fa-trash, #content .fa-save, #content .fa-cancel,
|
||||
#content .fa-arrow-left, #content .fa-arrow-right,
|
||||
#content .fa-arrow-up, #content .fa-arrow-down,
|
||||
#content .fa-check, #content .fa-times, #content .fa-close,
|
||||
#content .fa-star, #content .fa-heart, #content .fa-thumbs-up,
|
||||
#content .fa-thumbs-down, #content .fa-comment, #content .fa-reply,
|
||||
#content .fa-share, #content .fa-download, #content .fa-upload,
|
||||
#content .fa-copy, #content .fa-paste, #content .fa-cut,
|
||||
#content .fa-undo, #content .fa-redo, #content .fa-refresh,
|
||||
#content .fa-sync, #content .fa-spinner, #content .fa-loading,
|
||||
#content .fa-info, #content .fa-question, #content .fa-exclamation,
|
||||
#content .fa-warning, #content .fa-error, #content .fa-success,
|
||||
#content .fa-check-circle, #content .fa-times-circle,
|
||||
#content .fa-exclamation-circle, #content .fa-question-circle,
|
||||
#content .fa-info-circle, #content .fa-warning-circle,
|
||||
#content .fa-error-circle, #content .fa-success-circle {
|
||||
font-size: 1em !important; /* Keep original icon size */
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback for iPhone devices using JavaScript detection */
|
||||
.iphone-device #content {
|
||||
font-size: 2em !important; /* 2x bigger base font size for content area */
|
||||
}
|
||||
|
||||
.iphone-device #content h1, .iphone-device #content h2, .iphone-device #content h3, .iphone-device #content h4, .iphone-device #content h5, .iphone-device #content h6,
|
||||
.iphone-device #content p, .iphone-device #content span, .iphone-device #content div, .iphone-device #content a, .iphone-device #content button,
|
||||
.iphone-device #content .minicard, .iphone-device #content .list-header-name, .iphone-device #content .board-header-btn,
|
||||
.iphone-device #content .card-title, .iphone-device #content .card-details, .iphone-device #content .card-description,
|
||||
.iphone-device #content .swimlane-header, .iphone-device #content .list-title, .iphone-device #content .card-text,
|
||||
.iphone-device #content .member, .iphone-device #content .member-name, .iphone-device #content .member-initials,
|
||||
.iphone-device #content .checklist-item, .iphone-device #content .checklist-title, .iphone-device #content .comment,
|
||||
.iphone-device #content .activity, .iphone-device #content .activity-text, .iphone-device #content .activity-time,
|
||||
.iphone-device #content .board-title, .iphone-device #content .board-description, .iphone-device #content .list-name,
|
||||
.iphone-device #content .card-text, .iphone-device #content .card-title, .iphone-device #content .card-description,
|
||||
.iphone-device #content .swimlane-title, .iphone-device #content .swimlane-description,
|
||||
.iphone-device #content .board-header-title, .iphone-device #content .board-header-description,
|
||||
.iphone-device #content .card-detail-title, .iphone-device #content .card-detail-description,
|
||||
.iphone-device #content .list-header-title, .iphone-device #content .list-header-description,
|
||||
.iphone-device #content .swimlane-header-title, .iphone-device #content .swimlane-header-description,
|
||||
.iphone-device #content .minicard-title, .iphone-device #content .minicard-description,
|
||||
.iphone-device #content .card-comment, .iphone-device #content .card-comment-text,
|
||||
.iphone-device #content .checklist-item-text, .iphone-device #content .checklist-item-title,
|
||||
.iphone-device #content .activity-item, .iphone-device #content .activity-item-text,
|
||||
.iphone-device #content .board-member, .iphone-device #content .board-member-name,
|
||||
.iphone-device #content .team-member, .iphone-device #content .team-member-name,
|
||||
.iphone-device #content .org-member, .iphone-device #content .org-member-name,
|
||||
.iphone-device #content .template-member, .iphone-device #content .template-member-name,
|
||||
.iphone-device #content .user-name, .iphone-device #content .user-email, .iphone-device #content .user-role,
|
||||
.iphone-device #content .setting-title, .iphone-device #content .setting-description,
|
||||
.iphone-device #content .popup-title, .iphone-device #content .popup-description,
|
||||
.iphone-device #content .modal-title, .iphone-device #content .modal-description,
|
||||
.iphone-device #content .notification-title, .iphone-device #content .notification-text,
|
||||
.iphone-device #content .announcement-title, .iphone-device #content .announcement-text,
|
||||
.iphone-device #content .offline-warning-title, .iphone-device #content .offline-warning-text,
|
||||
.iphone-device #content .error-title, .iphone-device #content .error-text,
|
||||
.iphone-device #content .success-title, .iphone-device #content .success-text,
|
||||
.iphone-device #content .info-title, .iphone-device #content .info-text,
|
||||
.iphone-device #content .warning-title, .iphone-device #content .warning-text {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* Keep icons the same size for iPhone devices */
|
||||
.iphone-device #content .fa, .iphone-device #content .icon, .iphone-device #content i {
|
||||
font-size: 1em !important; /* Keep original icon size */
|
||||
}
|
||||
|
||||
/* Ensure scrollbars are positioned correctly */
|
||||
#content[style*="overflow-x: auto"]::-webkit-scrollbar:vertical {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
#content[style*="overflow-x: auto"]::-webkit-scrollbar:horizontal {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Force both scrollbars to always be visible for high zoom levels */
|
||||
#content[style*="overflow-x: auto"][style*="overflow-y: auto"] {
|
||||
overflow-x: scroll !important;
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
/* Ensure the right scrollbar is always visible on the inner right edge */
|
||||
#content[style*="overflow-y: auto"] {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
.board-wrapper .board-canvas .board-overlay {
|
||||
position: fixed;
|
||||
@@ -36,15 +267,58 @@
|
||||
.board-wrapper .board-canvas.is-dragging-active .minicard-wrapper.is-checked {
|
||||
display: none;
|
||||
}
|
||||
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
|
||||
.board-wrapper.mobile-view {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
.board-wrapper.mobile-view .board-canvas {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
.board-wrapper.mobile-view .board-canvas.mobile-view .swimlane {
|
||||
border-bottom: 1px solid #ccc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.board-wrapper {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
.board-wrapper .board-canvas {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
.board-wrapper .board-canvas .swimlane {
|
||||
border-bottom: 1px solid #ccc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0 0px 0px 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
.calendar-event-green {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
template(name="board")
|
||||
if isBoardReady.get
|
||||
|
||||
if isMigrating.get
|
||||
+migrationProgress
|
||||
else if isConverting.get
|
||||
+boardConversionProgress
|
||||
else if isBoardReady.get
|
||||
if currentBoard
|
||||
if onlyShowCurrentCard
|
||||
+cardDetails(currentCard)
|
||||
@@ -16,26 +21,42 @@ template(name="boardBody")
|
||||
if notDisplayThisBoard
|
||||
| {{_ 'tableVisibilityMode-allowPrivateOnly'}}
|
||||
else
|
||||
.board-wrapper(class=currentBoard.colorClass)
|
||||
+sidebar
|
||||
.board-canvas.js-swimlanes.dragscroll(
|
||||
// Debug information (remove in production)
|
||||
if debugBoardState
|
||||
.debug-info(style="position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.8); color: white; padding: 10px; z-index: 9999; font-size: 12px;")
|
||||
| Board: {{currentBoard.title}} | View: {{boardView}} | HasSwimlanes: {{hasSwimlanes}} | Swimlanes: {{currentBoard.swimlanes.length}}
|
||||
.board-wrapper(class=currentBoard.colorClass class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
.board-canvas.js-swimlanes(
|
||||
class="{{#if hasSwimlanes}}dragscroll{{/if}}"
|
||||
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
|
||||
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
|
||||
class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
|
||||
class="{{#if draggingActive.get}}is-dragging-active{{/if}}"
|
||||
class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}"
|
||||
class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
if showOverlay.get
|
||||
.board-overlay
|
||||
if currentBoard.isTemplatesBoard
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else if isViewSwimlanes
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
if hasSwimlanes
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else
|
||||
// Fallback: If no swimlanes exist, show lists instead of empty message
|
||||
+listsGroup(currentBoard)
|
||||
else if isViewLists
|
||||
+listsGroup(currentBoard)
|
||||
else if isViewCalendar
|
||||
+calendarView
|
||||
else
|
||||
+listsGroup(currentBoard)
|
||||
// Default view - show swimlanes if they exist, otherwise show lists
|
||||
if hasSwimlanes
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else
|
||||
+listsGroup(currentBoard)
|
||||
+sidebar
|
||||
|
||||
template(name="calendarView")
|
||||
if isViewCalendar
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
import { boardConverter } from '/client/lib/boardConverter';
|
||||
import { migrationManager } from '/client/lib/migrationManager';
|
||||
import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
import Lists from '/models/lists';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
const { calculateIndex } = Utils;
|
||||
@@ -9,6 +14,9 @@ const swimlaneWhileSortingHeight = 150;
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.isBoardReady = new ReactiveVar(false);
|
||||
this.isConverting = new ReactiveVar(false);
|
||||
this.isMigrating = new ReactiveVar(false);
|
||||
this._swimlaneCreated = new Set(); // Track boards where we've created swimlanes
|
||||
|
||||
// The pattern we use to manually handle data loading is described here:
|
||||
// https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager
|
||||
@@ -17,22 +25,175 @@ BlazeComponent.extendComponent({
|
||||
this.autorun(() => {
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId) return;
|
||||
|
||||
const handle = subManager.subscribe('board', currentBoardId, false);
|
||||
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
this.isBoardReady.set(handle.ready());
|
||||
if (handle.ready()) {
|
||||
// Ensure default swimlane exists (only once per board)
|
||||
this.ensureDefaultSwimlane(currentBoardId);
|
||||
// Check if board needs conversion
|
||||
this.checkAndConvertBoard(currentBoardId);
|
||||
} else {
|
||||
this.isBoardReady.set(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
ensureDefaultSwimlane(boardId) {
|
||||
// Only create swimlane once per board
|
||||
if (this._swimlaneCreated.has(boardId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
if (!board) return;
|
||||
|
||||
const swimlanes = board.swimlanes();
|
||||
|
||||
if (swimlanes.length === 0) {
|
||||
const swimlaneId = Swimlanes.insert({
|
||||
title: 'Default',
|
||||
boardId: boardId,
|
||||
});
|
||||
this._swimlaneCreated.add(boardId);
|
||||
} else {
|
||||
this._swimlaneCreated.add(boardId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating default swimlane:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async checkAndConvertBoard(boardId) {
|
||||
try {
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
if (!board) {
|
||||
this.isBoardReady.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if board needs migration based on migration version
|
||||
const needsMigration = !board.migrationVersion || board.migrationVersion < 1;
|
||||
|
||||
if (needsMigration) {
|
||||
// Start background migration for old boards
|
||||
this.isMigrating.set(true);
|
||||
await this.startBackgroundMigration(boardId);
|
||||
this.isMigrating.set(false);
|
||||
}
|
||||
|
||||
// Check if board needs conversion (for old structure)
|
||||
if (boardConverter.isBoardConverted(boardId)) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has already been converted, skipping conversion`);
|
||||
}
|
||||
this.isBoardReady.set(true);
|
||||
} else {
|
||||
const needsConversion = boardConverter.needsConversion(boardId);
|
||||
|
||||
if (needsConversion) {
|
||||
this.isConverting.set(true);
|
||||
const success = await boardConverter.convertBoard(boardId);
|
||||
this.isConverting.set(false);
|
||||
|
||||
if (success) {
|
||||
this.isBoardReady.set(true);
|
||||
} else {
|
||||
console.error('Board conversion failed, setting ready to true anyway');
|
||||
this.isBoardReady.set(true); // Still show board even if conversion failed
|
||||
}
|
||||
} else {
|
||||
this.isBoardReady.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Start attachment migration in background if needed
|
||||
this.startAttachmentMigrationIfNeeded(boardId);
|
||||
} catch (error) {
|
||||
console.error('Error during board conversion check:', error);
|
||||
this.isConverting.set(false);
|
||||
this.isMigrating.set(false);
|
||||
this.isBoardReady.set(true); // Show board even if conversion check failed
|
||||
}
|
||||
},
|
||||
|
||||
async startBackgroundMigration(boardId) {
|
||||
try {
|
||||
// Start background migration using the cron system
|
||||
Meteor.call('boardMigration.startBoardMigration', boardId, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to start background migration:', error);
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Background migration started for board:', boardId);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error starting background migration:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async startAttachmentMigrationIfNeeded(boardId) {
|
||||
try {
|
||||
// Check if board has already been migrated
|
||||
if (attachmentMigrationManager.isBoardMigrated(boardId)) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has already been migrated, skipping`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there are unconverted attachments
|
||||
const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId);
|
||||
|
||||
if (unconvertedAttachments.length > 0) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`);
|
||||
}
|
||||
await attachmentMigrationManager.startAttachmentMigration(boardId);
|
||||
} else {
|
||||
// No attachments to migrate, mark board as migrated
|
||||
// This will be handled by the migration manager itself
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Board ${boardId} has no attachments to migrate`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error starting attachment migration:', error);
|
||||
}
|
||||
},
|
||||
|
||||
onlyShowCurrentCard() {
|
||||
return Utils.isMiniScreen() && Utils.getCurrentCardId(true);
|
||||
const isMiniScreen = Utils.isMiniScreen();
|
||||
const currentCardId = Utils.getCurrentCardId(true);
|
||||
return isMiniScreen && currentCardId;
|
||||
},
|
||||
|
||||
goHome() {
|
||||
FlowRouter.go('home');
|
||||
},
|
||||
|
||||
isConverting() {
|
||||
return this.isConverting.get();
|
||||
},
|
||||
|
||||
isMigrating() {
|
||||
return this.isMigrating.get();
|
||||
},
|
||||
|
||||
isBoardReady() {
|
||||
return this.isBoardReady.get();
|
||||
},
|
||||
|
||||
currentBoard() {
|
||||
return Utils.getCurrentBoard();
|
||||
},
|
||||
}).register('board');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
@@ -46,36 +207,227 @@ BlazeComponent.extendComponent({
|
||||
|
||||
// fix swimlanes sort field if there are null values
|
||||
const currentBoardData = Utils.getCurrentBoard();
|
||||
const nullSortSwimlanes = currentBoardData.nullSortSwimlanes();
|
||||
if (nullSortSwimlanes.length > 0) {
|
||||
const swimlanes = currentBoardData.swimlanes();
|
||||
let count = 0;
|
||||
swimlanes.forEach(s => {
|
||||
Swimlanes.update(s._id, {
|
||||
$set: {
|
||||
sort: count,
|
||||
},
|
||||
if (currentBoardData && Swimlanes) {
|
||||
const nullSortSwimlanes = currentBoardData.nullSortSwimlanes();
|
||||
if (nullSortSwimlanes.length > 0) {
|
||||
const swimlanes = currentBoardData.swimlanes();
|
||||
let count = 0;
|
||||
swimlanes.forEach(s => {
|
||||
Swimlanes.update(s._id, {
|
||||
$set: {
|
||||
sort: count,
|
||||
},
|
||||
});
|
||||
count += 1;
|
||||
});
|
||||
count += 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// fix lists sort field if there are null values
|
||||
const nullSortLists = currentBoardData.nullSortLists();
|
||||
if (nullSortLists.length > 0) {
|
||||
const lists = currentBoardData.lists();
|
||||
let count = 0;
|
||||
lists.forEach(l => {
|
||||
Lists.update(l._id, {
|
||||
$set: {
|
||||
sort: count,
|
||||
},
|
||||
if (currentBoardData && Lists) {
|
||||
const nullSortLists = currentBoardData.nullSortLists();
|
||||
if (nullSortLists.length > 0) {
|
||||
const lists = currentBoardData.lists();
|
||||
let count = 0;
|
||||
lists.forEach(l => {
|
||||
Lists.update(l._id, {
|
||||
$set: {
|
||||
sort: count,
|
||||
},
|
||||
});
|
||||
count += 1;
|
||||
});
|
||||
count += 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
onRendered() {
|
||||
// Initialize user settings (zoom and mobile mode)
|
||||
Utils.initializeUserSettings();
|
||||
|
||||
// Detect iPhone devices and add class for better CSS targeting
|
||||
const isIPhone = /iPhone|iPod/.test(navigator.userAgent);
|
||||
if (isIPhone) {
|
||||
document.body.classList.add('iphone-device');
|
||||
}
|
||||
|
||||
// Accessibility: Focus management for popups and menus
|
||||
function focusFirstInteractive(container) {
|
||||
if (!container) return;
|
||||
// Find first focusable element
|
||||
const focusable = container.querySelectorAll('button, [role="button"], a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||
for (let i = 0; i < focusable.length; i++) {
|
||||
if (!focusable[i].disabled && focusable[i].offsetParent !== null) {
|
||||
focusable[i].focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observe for new popups/menus and set focus (but exclude swimlane content)
|
||||
const popupObserver = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
mutation.addedNodes.forEach(function(node) {
|
||||
if (node.nodeType === 1 &&
|
||||
(node.classList.contains('popup') || node.classList.contains('modal') || node.classList.contains('menu')) &&
|
||||
!node.closest('.js-swimlanes') &&
|
||||
!node.closest('.swimlane') &&
|
||||
!node.closest('.list') &&
|
||||
!node.closest('.minicard')) {
|
||||
setTimeout(function() { focusFirstInteractive(node); }, 10);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
popupObserver.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// Remove tabindex from non-interactive elements (e.g., user abbreviations, labels)
|
||||
document.querySelectorAll('.user-abbreviation, .user-label, .card-header-label, .edit-label, .private-label').forEach(function(el) {
|
||||
if (el.hasAttribute('tabindex')) {
|
||||
el.removeAttribute('tabindex');
|
||||
}
|
||||
});
|
||||
/*
|
||||
// Add a toggle button for keyboard shortcuts accessibility
|
||||
if (!document.getElementById('wekan-shortcuts-toggle')) {
|
||||
const toggleContainer = document.createElement('div');
|
||||
toggleContainer.id = 'wekan-shortcuts-toggle';
|
||||
toggleContainer.style.position = 'fixed';
|
||||
toggleContainer.style.top = '10px';
|
||||
toggleContainer.style.right = '10px';
|
||||
toggleContainer.style.zIndex = '1000';
|
||||
toggleContainer.style.background = '#fff';
|
||||
toggleContainer.style.border = '2px solid #005fcc';
|
||||
toggleContainer.style.borderRadius = '6px';
|
||||
toggleContainer.style.padding = '8px 12px';
|
||||
toggleContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
|
||||
toggleContainer.style.fontSize = '16px';
|
||||
toggleContainer.style.color = '#005fcc';
|
||||
toggleContainer.setAttribute('role', 'region');
|
||||
toggleContainer.setAttribute('aria-label', 'Keyboard Shortcuts Settings');
|
||||
toggleContainer.innerHTML = `
|
||||
<label for="shortcuts-toggle-checkbox" style="cursor:pointer;">
|
||||
<input type="checkbox" id="shortcuts-toggle-checkbox" ${window.wekanShortcutsEnabled ? 'checked' : ''} style="margin-right:8px;" />
|
||||
Enable keyboard shortcuts
|
||||
</label>
|
||||
`;
|
||||
document.body.appendChild(toggleContainer);
|
||||
const checkbox = document.getElementById('shortcuts-toggle-checkbox');
|
||||
checkbox.addEventListener('change', function(e) {
|
||||
window.toggleWekanShortcuts(e.target.checked);
|
||||
});
|
||||
}
|
||||
*/
|
||||
// Ensure toggle-buttons, color choices, reactions, renaming, and calendar controls are focusable and have ARIA roles
|
||||
document.querySelectorAll('.js-toggle').forEach(function(el) {
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('role', 'button');
|
||||
// Short, descriptive label for favorite/star toggle
|
||||
if (el.classList.contains('js-favorite-toggle')) {
|
||||
el.setAttribute('aria-label', TAPi18n.__('favorite-toggle-label'));
|
||||
} else {
|
||||
el.setAttribute('aria-label', 'Toggle');
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('.js-color-choice').forEach(function(el) {
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('role', 'button');
|
||||
el.setAttribute('aria-label', 'Choose color');
|
||||
});
|
||||
document.querySelectorAll('.js-reaction').forEach(function(el) {
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('role', 'button');
|
||||
el.setAttribute('aria-label', 'React');
|
||||
});
|
||||
document.querySelectorAll('.js-rename-swimlane').forEach(function(el) {
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('role', 'button');
|
||||
el.setAttribute('aria-label', 'Rename swimlane');
|
||||
});
|
||||
document.querySelectorAll('.js-rename-list').forEach(function(el) {
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('role', 'button');
|
||||
el.setAttribute('aria-label', 'Rename list');
|
||||
});
|
||||
document.querySelectorAll('.fc-button').forEach(function(el) {
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('role', 'button');
|
||||
});
|
||||
// Set the language attribute on the <html> element for accessibility
|
||||
document.documentElement.lang = TAPi18n.getLanguage();
|
||||
|
||||
// Ensure the accessible name for the board view switcher matches the visible label "Swimlanes"
|
||||
// This fixes WCAG 2.5.3: Label in Name
|
||||
const swimlanesSwitcher = this.$('.js-board-view-swimlanes');
|
||||
if (swimlanesSwitcher.length) {
|
||||
swimlanesSwitcher.attr('aria-label', swimlanesSwitcher.text().trim() || 'Swimlanes');
|
||||
}
|
||||
|
||||
// Add a highly visible focus indicator and improve contrast for interactive elements
|
||||
if (!document.getElementById('wekan-accessible-focus-style')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'wekan-accessible-focus-style';
|
||||
style.innerHTML = `
|
||||
/* Focus indicator */
|
||||
button:focus, [role="button"]:focus, a:focus, input:focus, select:focus, textarea:focus, .dropdown-menu:focus, .js-board-view-swimlanes:focus, .js-add-card:focus {
|
||||
outline: 3px solid #005fcc !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
/* Input borders */
|
||||
input, textarea, select {
|
||||
border: 2px solid #222 !important;
|
||||
}
|
||||
/* Plus icon for adding a new card */
|
||||
.js-add-card {
|
||||
color: #005fcc !important; /* dark blue for contrast */
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
.js-add-card[tabindex] {
|
||||
outline: none;
|
||||
}
|
||||
/* Hamburger menu */
|
||||
.fa-bars, .icon-hamburger {
|
||||
color: #222 !important;
|
||||
}
|
||||
/* Grey icons in card detail header */
|
||||
.card-detail-header .fa, .card-detail-header .icon {
|
||||
color: #444 !important;
|
||||
}
|
||||
/* Grey operating elements in card detail */
|
||||
.card-detail .fa, .card-detail .icon {
|
||||
color: #444 !important;
|
||||
}
|
||||
/* Blue bar in checklists */
|
||||
.checklist-progress-bar {
|
||||
background-color: #005fcc !important;
|
||||
}
|
||||
/* Green checkmark in checklists */
|
||||
.checklist .fa-check {
|
||||
color: #007a33 !important;
|
||||
}
|
||||
/* X-Button and arrow button in menus */
|
||||
.close, .fa-arrow-left, .icon-arrow-left {
|
||||
color: #005fcc !important;
|
||||
}
|
||||
/* Cross icon to move boards */
|
||||
.js-move-board {
|
||||
color: #005fcc !important;
|
||||
}
|
||||
/* Current date background */
|
||||
.current-date {
|
||||
background-color: #005fcc !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
// Ensure plus/add elements are focusable and have ARIA roles
|
||||
document.querySelectorAll('.js-add-card').forEach(function(el) {
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('role', 'button');
|
||||
el.setAttribute('aria-label', 'Add new card');
|
||||
});
|
||||
|
||||
const boardComponent = this;
|
||||
const $swimlanesDom = boardComponent.$('.js-swimlanes');
|
||||
|
||||
@@ -195,6 +547,9 @@ BlazeComponent.extendComponent({
|
||||
});
|
||||
|
||||
this.autorun(() => {
|
||||
// Always reset dragscroll on view switch
|
||||
dragscroll.reset();
|
||||
|
||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||
$swimlanesDom.sortable({
|
||||
handle: '.js-swimlane-header-handle',
|
||||
@@ -227,42 +582,122 @@ BlazeComponent.extendComponent({
|
||||
notDisplayThisBoard() {
|
||||
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
|
||||
let currentBoard = Utils.getCurrentBoard();
|
||||
if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue && currentBoard.permission == 'public') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue && currentBoard && currentBoard.permission == 'public';
|
||||
},
|
||||
|
||||
isViewSwimlanes() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
let boardView;
|
||||
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).boardView === 'board-view-swimlanes';
|
||||
boardView = (currentUser.profile || {}).boardView;
|
||||
} else {
|
||||
return (
|
||||
window.localStorage.getItem('boardView') === 'board-view-swimlanes'
|
||||
);
|
||||
boardView = window.localStorage.getItem('boardView');
|
||||
}
|
||||
|
||||
// If no board view is set, default to swimlanes
|
||||
if (!boardView) {
|
||||
boardView = 'board-view-swimlanes';
|
||||
}
|
||||
|
||||
return boardView === 'board-view-swimlanes';
|
||||
},
|
||||
|
||||
isViewLists() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
let boardView;
|
||||
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).boardView === 'board-view-lists';
|
||||
boardView = (currentUser.profile || {}).boardView;
|
||||
} else {
|
||||
return window.localStorage.getItem('boardView') === 'board-view-lists';
|
||||
boardView = window.localStorage.getItem('boardView');
|
||||
}
|
||||
|
||||
return boardView === 'board-view-lists';
|
||||
},
|
||||
|
||||
isViewCalendar() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
let boardView;
|
||||
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).boardView === 'board-view-cal';
|
||||
boardView = (currentUser.profile || {}).boardView;
|
||||
} else {
|
||||
return window.localStorage.getItem('boardView') === 'board-view-cal';
|
||||
boardView = window.localStorage.getItem('boardView');
|
||||
}
|
||||
|
||||
return boardView === 'board-view-cal';
|
||||
},
|
||||
|
||||
hasSwimlanes() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
if (!currentBoard) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('hasSwimlanes: No current board');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const swimlanes = currentBoard.swimlanes();
|
||||
const hasSwimlanes = swimlanes && swimlanes.length > 0;
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('hasSwimlanes: Board has', swimlanes ? swimlanes.length : 0, 'swimlanes');
|
||||
}
|
||||
return hasSwimlanes;
|
||||
} catch (error) {
|
||||
console.error('hasSwimlanes: Error getting swimlanes:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
isVerticalScrollbars() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
boardView() {
|
||||
return Utils.boardView();
|
||||
},
|
||||
|
||||
debugBoardState() {
|
||||
// Enable debug mode by setting ?debug=1 in URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get('debug') === '1';
|
||||
},
|
||||
|
||||
debugBoardStateData() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
const isBoardReady = this.isBoardReady.get();
|
||||
const isConverting = this.isConverting.get();
|
||||
const isMigrating = this.isMigrating.get();
|
||||
const boardView = Utils.boardView();
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('=== BOARD DEBUG STATE ===');
|
||||
console.log('currentBoardId:', currentBoardId);
|
||||
console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none');
|
||||
console.log('isBoardReady:', isBoardReady);
|
||||
console.log('isConverting:', isConverting);
|
||||
console.log('isMigrating:', isMigrating);
|
||||
console.log('boardView:', boardView);
|
||||
console.log('========================');
|
||||
}
|
||||
|
||||
return {
|
||||
currentBoardId,
|
||||
hasCurrentBoard: !!currentBoard,
|
||||
currentBoardTitle: currentBoard ? currentBoard.title : 'none',
|
||||
isBoardReady,
|
||||
isConverting,
|
||||
isMigrating,
|
||||
boardView
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
openNewListForm() {
|
||||
if (this.isViewSwimlanes()) {
|
||||
// The form had been removed in 416b17062e57f215206e93a85b02ef9eb1ab4902
|
||||
@@ -285,6 +720,7 @@ BlazeComponent.extendComponent({
|
||||
this._isDragging = false;
|
||||
}
|
||||
},
|
||||
'click .js-empty-board-add-swimlane': Popup.open('swimlaneAdd'),
|
||||
},
|
||||
];
|
||||
},
|
||||
@@ -313,8 +749,109 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
}).register('boardBody');
|
||||
|
||||
// Accessibility: Allow users to enable/disable keyboard shortcuts
|
||||
window.wekanShortcutsEnabled = true;
|
||||
window.toggleWekanShortcuts = function(enabled) {
|
||||
window.wekanShortcutsEnabled = !!enabled;
|
||||
};
|
||||
|
||||
// Example: Wrap your character key shortcut handler like this
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Example: "W" key shortcut (replace with your actual shortcut logic)
|
||||
if (!window.wekanShortcutsEnabled) return;
|
||||
if (e.key === 'w' || e.key === 'W') {
|
||||
// ...existing shortcut logic...
|
||||
// e.g. open swimlanes view, etc.
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard accessibility for card actions (favorite, archive, duplicate, etc.)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (!window.wekanShortcutsEnabled) return;
|
||||
// Only proceed if focus is on a card action element
|
||||
const active = document.activeElement;
|
||||
if (active && active.classList.contains('js-card-action')) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
active.click();
|
||||
}
|
||||
// Move card up/down with arrow keys
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (active.dataset.cardId) {
|
||||
Meteor.call('moveCardUp', active.dataset.cardId);
|
||||
}
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (active.dataset.cardId) {
|
||||
Meteor.call('moveCardDown', active.dataset.cardId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Make plus/add elements keyboard accessible
|
||||
if (active && active.classList.contains('js-add-card')) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
active.click();
|
||||
}
|
||||
}
|
||||
// Keyboard move for cards (alternative to drag & drop)
|
||||
if (active && active.classList.contains('js-move-card')) {
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (active.dataset.cardId) {
|
||||
Meteor.call('moveCardUp', active.dataset.cardId);
|
||||
}
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (active.dataset.cardId) {
|
||||
Meteor.call('moveCardDown', active.dataset.cardId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure move card buttons are focusable and have ARIA roles
|
||||
document.querySelectorAll('.js-move-card').forEach(function(el) {
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('role', 'button');
|
||||
el.setAttribute('aria-label', 'Move card');
|
||||
});
|
||||
// Make toggle-buttons, color choices, reactions, and X-buttons keyboard accessible
|
||||
if (active && (active.classList.contains('js-toggle') || active.classList.contains('js-color-choice') || active.classList.contains('js-reaction') || active.classList.contains('close'))) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
active.click();
|
||||
}
|
||||
}
|
||||
// Prevent scripts from removing focus when received
|
||||
if (active) {
|
||||
active.addEventListener('focus', function(e) {
|
||||
// Do not remove focus
|
||||
// No-op: This prevents F55 failure
|
||||
}, { once: true });
|
||||
}
|
||||
// Make swimlane/list renaming keyboard accessible
|
||||
if (active && (active.classList.contains('js-rename-swimlane') || active.classList.contains('js-rename-list'))) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
active.click();
|
||||
}
|
||||
}
|
||||
// Calendar navigation buttons
|
||||
if (active && active.classList.contains('fc-button')) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
active.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onRendered() {
|
||||
// Set the language attribute on the <html> element for accessibility
|
||||
document.documentElement.lang = TAPi18n.getLanguage();
|
||||
|
||||
this.autorun(function () {
|
||||
$('#calendar-view').fullCalendar('refetchEvents');
|
||||
});
|
||||
@@ -328,11 +865,19 @@ BlazeComponent.extendComponent({
|
||||
timezone: 'local',
|
||||
weekNumbers: true,
|
||||
header: {
|
||||
left: 'title today prev,next',
|
||||
left: 'title today prev,next',
|
||||
center:
|
||||
'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,listMonth',
|
||||
right: '',
|
||||
},
|
||||
buttonText: {
|
||||
prev: TAPi18n.__('calendar-previous-month-label'), // e.g. "Previous month"
|
||||
next: TAPi18n.__('calendar-next-month-label'), // e.g. "Next month"
|
||||
},
|
||||
ariaLabel: {
|
||||
prev: TAPi18n.__('calendar-previous-month-label'),
|
||||
next: TAPi18n.__('calendar-next-month-label'),
|
||||
},
|
||||
// height: 'parent', nope, doesn't work as the parent might be small
|
||||
height: 'auto',
|
||||
/* TODO: lists as resources: https://fullcalendar.io/docs/vertical-resource-view */
|
||||
@@ -463,6 +1008,9 @@ BlazeComponent.extendComponent({
|
||||
document.body.appendChild(modalElement);
|
||||
const openModal = function() {
|
||||
modalElement.style.display = 'flex';
|
||||
// Set focus to the input field for better keyboard accessibility
|
||||
const input = modalElement.querySelector('#card-title-input');
|
||||
if (input) input.focus();
|
||||
};
|
||||
const closeModal = function() {
|
||||
modalElement.style.display = 'none';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - NEPHRITIS
|
||||
=================*/
|
||||
|
||||
@@ -99,7 +99,7 @@ THEME - NEPHRITIS
|
||||
|
||||
/* === END NEPHRITIS THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Pomegranate
|
||||
=================*/
|
||||
|
||||
@@ -200,7 +200,7 @@ THEME - Pomegranate
|
||||
|
||||
/* === END Pomegranate THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Belize
|
||||
=================*/
|
||||
|
||||
@@ -301,7 +301,7 @@ THEME - Belize
|
||||
|
||||
/* === END Belize THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Wisteria
|
||||
=================*/
|
||||
|
||||
@@ -402,7 +402,7 @@ THEME - Wisteria
|
||||
|
||||
/* === END Wisteria THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Midnight
|
||||
=================*/
|
||||
|
||||
@@ -503,7 +503,7 @@ THEME - Midnight
|
||||
|
||||
/* === END Midnight THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Pumpkin
|
||||
=================*/
|
||||
|
||||
@@ -604,7 +604,7 @@ THEME - Pumpkin
|
||||
|
||||
/* === END Pumpkin THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Moderate Pink
|
||||
=================*/
|
||||
|
||||
@@ -705,7 +705,7 @@ THEME - Moderate Pink
|
||||
|
||||
/* === END Moderatepink THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Strong Cyan
|
||||
=================*/
|
||||
|
||||
@@ -806,7 +806,7 @@ THEME - Strong Cyan
|
||||
|
||||
/* === END Strongcyan THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Lime Green
|
||||
=================*/
|
||||
|
||||
@@ -907,7 +907,7 @@ THEME - Lime Green
|
||||
|
||||
/* === END Limegreen THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Dark
|
||||
=================*/
|
||||
|
||||
@@ -1111,7 +1111,7 @@ THEME - Dark
|
||||
|
||||
/* === END Dark THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Relax
|
||||
=================*/
|
||||
|
||||
@@ -1241,7 +1241,7 @@ THEME - Relax
|
||||
|
||||
/* === END Relax THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Corteza
|
||||
=================*/
|
||||
|
||||
@@ -1342,7 +1342,7 @@ THEME - Corteza
|
||||
|
||||
/* === END Corteza THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Clear Blue
|
||||
=================*/
|
||||
|
||||
@@ -1609,7 +1609,7 @@ THEME - Clear Blue
|
||||
|
||||
/* === END Clearblue THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Natural
|
||||
=================*/
|
||||
|
||||
@@ -1719,7 +1719,7 @@ THEME - Natural
|
||||
|
||||
/* === END Natural THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Modern
|
||||
=================*/
|
||||
|
||||
@@ -1958,7 +1958,7 @@ THEME - Modern
|
||||
.board-color-modern .pop-over-list li>a .sub-name {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.board-color-modern .sidebar .sidebar-shadow {
|
||||
.board-color-modern .sidebar {
|
||||
box-shadow: 0 0 60px rgba(0,0,0,0.2);
|
||||
}
|
||||
.board-color-modern .board-color-modern section#notifications-drawer {
|
||||
@@ -2006,7 +2006,7 @@ THEME - Modern
|
||||
|
||||
/* === END Modern THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Modern Dark
|
||||
=================*/
|
||||
|
||||
@@ -2431,7 +2431,7 @@ THEME - Modern Dark
|
||||
background-color: #ccc;
|
||||
color: #222;
|
||||
}
|
||||
.board-color-moderndark .sidebar .sidebar-shadow {
|
||||
.board-color-moderndark .sidebar {
|
||||
background-color: #222;
|
||||
box-shadow: -10px 0 5px -10px #444;
|
||||
border-left: 1px solid #333;
|
||||
@@ -2496,7 +2496,7 @@ THEME - Modern Dark
|
||||
/* === END ModernDark THEME === */
|
||||
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Exodark
|
||||
=================*/
|
||||
|
||||
@@ -2544,8 +2544,8 @@ THEME - Exodark
|
||||
background: #1b1b1b;
|
||||
}
|
||||
.board-color-exodark .materialCheckBox.is-checked {
|
||||
border-bottom: 2px solid #222;
|
||||
border-right: 2px solid #222;
|
||||
border-bottom: 2px solid #dbdbdb!important;/*Fix contrast of checkbox*/
|
||||
border-right: 2px solid #dbdbdb!important;
|
||||
}
|
||||
.board-color-exodark .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
|
||||
background: #e9e9e9;
|
||||
@@ -2648,6 +2648,9 @@ THEME - Exodark
|
||||
background: #2b2b2b !important;
|
||||
color: #fff;
|
||||
}
|
||||
.board-color-exodark .card-details .comment-text {
|
||||
color:#2b2b2b
|
||||
} /*Fixes issue with comment text colour blending into background*/
|
||||
.board-color-exodark .card-details .card-details-header {
|
||||
background: #2b2b2b;
|
||||
color: #fff;
|
||||
@@ -2741,7 +2744,7 @@ THEME - Exodark
|
||||
|
||||
/* === END Exodark THEME === */
|
||||
|
||||
/* ===============
|
||||
/* ===============
|
||||
THEME - Clean Dark
|
||||
=================*/
|
||||
|
||||
@@ -2751,14 +2754,12 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.board-color-cleandark#header-main-bar h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px !important;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
@@ -2771,7 +2772,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
@@ -2779,39 +2779,39 @@ THEME - Clean Dark
|
||||
background: #0A0A14;
|
||||
}
|
||||
|
||||
.board-color-cleandark .sidebar .sidebar-shadow {
|
||||
.board-color-cleandark .sidebar {
|
||||
background: rgba(35, 35, 43, 1) !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.board-color-cleandark .sidebar .sidebar-shadow hr {
|
||||
.board-color-cleandark .sidebar hr {
|
||||
background:rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.board-color-cleandark .sidebar .sidebar-shadow .tab-item {
|
||||
.board-color-cleandark .sidebar .tab-item {
|
||||
border-radius: 16px;
|
||||
padding: 4px 12px 4px 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
background: rgba(57, 57, 71, 1);
|
||||
}
|
||||
|
||||
.board-color-cleandark .sidebar .sidebar-shadow .tab-item.active {
|
||||
.board-color-cleandark .sidebar .tab-item.active {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
color: rgba(10, 10, 20, 1);
|
||||
border: none;
|
||||
padding: 4px 12px 4px 12px !important;
|
||||
}
|
||||
|
||||
.board-color-cleandark .sidebar .sidebar-shadow .tabs-content-container {
|
||||
.board-color-cleandark .sidebar .tabs-content-container {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.board-color-cleandark .card-details {
|
||||
background: #23232B;
|
||||
scrollbar-color: #ffffff #2e2e39;
|
||||
border-radius: 20px;
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -2820,7 +2820,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
@@ -2873,7 +2872,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
@@ -2888,7 +2886,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
@@ -2930,7 +2927,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
@@ -2939,7 +2935,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
@@ -2992,7 +2987,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
@@ -3000,6 +2994,10 @@ THEME - Clean Dark
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.board-color-cleandark .comments .comment .comment-desc .comment-text {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.board-color-cleandark .activity-checklist,
|
||||
.board-color-cleandark .activity-comment {
|
||||
background: none !important;
|
||||
@@ -3013,7 +3011,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
padding: 6px 12px 6px 12px;
|
||||
background: #FFFFFF;
|
||||
@@ -3024,7 +3021,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
background: rgba(57, 57, 71, 1) !important;
|
||||
border: none !important;
|
||||
@@ -3039,7 +3035,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 0.85) !important;
|
||||
background: rgba(57, 57, 71, 1) !important;
|
||||
border-radius: 12px !important;
|
||||
@@ -3054,7 +3049,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
background: rgba(57, 57, 71, 1);
|
||||
border-radius: 12px;
|
||||
@@ -3069,7 +3063,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.85);
|
||||
}
|
||||
|
||||
@@ -3085,7 +3078,6 @@ THEME - Clean Dark
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
@@ -3188,10 +3180,17 @@ THEME - Clean Dark
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* ===============
|
||||
.board-color-cleandark.background-box {
|
||||
background-color:#23232B;
|
||||
} /*Fixes contrast issues with background box in theme selection list*/
|
||||
/* ===============
|
||||
THEME - Clean Light
|
||||
=================*/
|
||||
/* Please note Clean Light theme elements also contain references to some cleandark theme elements so if unable to find code you're looking for under CleanDark it might be here. This should probably be cleaned up*/
|
||||
.board-color-cleanlight.background-box {
|
||||
background-color:#e0e0e0;
|
||||
color:#010101!important;
|
||||
} /*Fixes issues with text colour/background box being similar no contrast */
|
||||
|
||||
.board-color-cleanlight {
|
||||
background: #E0E0E0;
|
||||
@@ -3219,7 +3218,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.board-color-cleanlight#header ul li:hover,
|
||||
@@ -3233,7 +3231,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px !important;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 1) !important;
|
||||
}
|
||||
|
||||
@@ -3246,7 +3243,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 1);
|
||||
}
|
||||
|
||||
@@ -3280,6 +3276,11 @@ THEME - Clean Light
|
||||
background: none;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .list .list-header div:has(.list-header-name),
|
||||
.board-color-cleandark .list .list-header div:has(.list-header-name) {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .list .list-header-name {
|
||||
color: rgba(10, 10, 20, 1);
|
||||
}
|
||||
@@ -3288,11 +3289,6 @@ THEME - Clean Light
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .list:first-child,
|
||||
.board-color-cleandark .list:first-child {
|
||||
margin-right: -32px;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .list .list-header .list-header-menu,
|
||||
.board-color-cleandark .list .list-header .list-header-menu {
|
||||
display: flex;
|
||||
@@ -3326,7 +3322,13 @@ THEME - Clean Light
|
||||
|
||||
.board-color-cleanlight .list .list-header .list-header-menu .js-collapse,
|
||||
.board-color-cleandark .list .list-header .list-header-menu .js-collapse {
|
||||
display: none;
|
||||
/* Make collapse button visible in Clean Light / Clean Dark themes.
|
||||
Previously this was hidden which caused the missing Collapse button
|
||||
when using these themes. Use inline-block so it lines up with other
|
||||
header controls. */
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .list-header-add,
|
||||
@@ -3376,7 +3378,6 @@ THEME - Clean Light
|
||||
.board-color-cleanlight .swimlane,
|
||||
.board-color-cleandark .swimlane {
|
||||
background: none;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .swimlane-height-apply,
|
||||
@@ -3439,34 +3440,33 @@ THEME - Clean Light
|
||||
display: none;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .sidebar .sidebar-shadow {
|
||||
.board-color-cleanlight .sidebar {
|
||||
background: rgba(248, 248, 249, 1) !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .sidebar .sidebar-shadow hr {
|
||||
.board-color-cleanlight .sidebar hr {
|
||||
background: rgba(23, 23, 28, 0.05);
|
||||
}
|
||||
|
||||
.board-color-cleanlight .sidebar .sidebar-shadow .tab-item {
|
||||
.board-color-cleanlight .sidebar .tab-item {
|
||||
border-radius: 16px;
|
||||
padding: 4px 12px 4px 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.85);
|
||||
background: rgba(234, 234, 237, 1);
|
||||
}
|
||||
|
||||
.board-color-cleanlight .sidebar .sidebar-shadow .tab-item.active {
|
||||
.board-color-cleanlight .sidebar .tab-item.active {
|
||||
background: rgba(23, 23, 28, 1);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
border: none;
|
||||
padding: 4px 12px 4px 12px !important;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .sidebar .sidebar-shadow .tabs-content-container {
|
||||
.board-color-cleanlight .sidebar .tabs-content-container {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -3480,7 +3480,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.5);
|
||||
}
|
||||
|
||||
@@ -3489,7 +3488,6 @@ THEME - Clean Light
|
||||
font-size: 24px !important;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
text-align: left;
|
||||
border-bottom: none !important;
|
||||
padding: 12px 20px !important;
|
||||
}
|
||||
@@ -3501,7 +3499,7 @@ THEME - Clean Light
|
||||
|
||||
.board-color-cleandark .card-details-header {
|
||||
background: #2E2E39 !important;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
color: #FFF !important;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .card-details-header .card-details-title,
|
||||
@@ -3518,7 +3516,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .card-details .card-details-item-title {
|
||||
@@ -3578,7 +3575,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.5);
|
||||
}
|
||||
|
||||
@@ -3597,7 +3593,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.board-color-cleanlight.pop-over .header {
|
||||
@@ -3614,7 +3609,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.8);
|
||||
}
|
||||
|
||||
@@ -3657,7 +3651,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
|
||||
color: rgba(10, 10, 20, 0.85) !important;
|
||||
}
|
||||
@@ -3666,7 +3659,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
|
||||
color: rgba(10, 10, 20, 0.5) !important;
|
||||
}
|
||||
@@ -3725,7 +3717,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.5);
|
||||
}
|
||||
|
||||
@@ -3746,7 +3737,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
padding: 6px 12px 6px 12px;
|
||||
background: rgba(23, 23, 28, 1);
|
||||
@@ -3757,7 +3747,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
padding: 6px 12px 6px 12px;
|
||||
background: rgba(23, 23, 28, 1) !important;
|
||||
@@ -3772,7 +3761,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.85);
|
||||
background: rgba(234, 234, 237, 1);
|
||||
border: none !important;
|
||||
@@ -3793,7 +3781,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.85) !important;
|
||||
background: rgba(234, 234, 237, 1) !important;
|
||||
border-radius: 12px !important;
|
||||
@@ -3814,7 +3801,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(10, 10, 20, 0.85);
|
||||
background: rgba(234, 234, 237, 1);
|
||||
border-radius: 12px;
|
||||
@@ -3829,7 +3815,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
@@ -3845,7 +3830,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
@@ -3981,7 +3965,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
@@ -4007,7 +3990,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -4021,7 +4003,6 @@ THEME - Clean Light
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
@@ -4173,11 +4154,6 @@ THEME - Clean Light
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .card-details-left .js-open-inlined-form,
|
||||
.board-color-cleandark .card-details-left .js-open-inlined-form {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.board-color-cleanlight .new-comment .fa,
|
||||
.board-color-cleandark .new-comment .fa {
|
||||
display: none;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.integration-form {
|
||||
padding: 5px;
|
||||
padding: 0.7vh 0.7vw;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.flex,
|
||||
@@ -12,12 +12,928 @@
|
||||
display: flex;
|
||||
}
|
||||
.option {
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
-webkit-border-radius: 0.4vw;
|
||||
border-radius: 0.4vw;
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
-webkit-box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.2);
|
||||
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.2);
|
||||
margin-top: 0.7vh;
|
||||
padding: 0.7vh 0.7vw;
|
||||
}
|
||||
|
||||
/* Zoom and Mobile Mode Controls */
|
||||
.board-header-btns.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5vw;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.5vh 1vw;
|
||||
border-radius: 0.5vw;
|
||||
box-shadow: 0 0.2vh 0.5vh rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn {
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
border-radius: 0.3vw !important;
|
||||
background: #fff !important;
|
||||
border: 1px solid #000 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
color: #000 !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
float: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn i {
|
||||
color: #000 !important;
|
||||
float: none !important;
|
||||
display: inline !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn:hover {
|
||||
background: #000 !important;
|
||||
border-color: #000 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn:hover i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn.is-active {
|
||||
background: #0079bf;
|
||||
color: white;
|
||||
border-color: #005a8a;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn.is-active i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
min-width: 3vw;
|
||||
text-align: center;
|
||||
font-size: clamp(12px, 2vw, 14px);
|
||||
cursor: pointer;
|
||||
padding: 0.3vh 0.5vw;
|
||||
border-radius: 0.3vw;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.zoom-level:hover {
|
||||
background: #f0f0f0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Mobile Mode Styles */
|
||||
.mobile-mode .board-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mobile-mode .board-canvas {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard {
|
||||
font-size: clamp(16px, 4vw, 20px);
|
||||
padding: 1.2vh 1.5vw 0.5vh;
|
||||
min-height: 3vh;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header-name {
|
||||
font-size: clamp(18px, 4.5vw, 24px);
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn {
|
||||
padding: 1vh 1.5vw;
|
||||
font-size: clamp(14px, 3.5vw, 18px);
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls {
|
||||
padding: 1vh 1.5vw;
|
||||
gap: 1vw;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls .board-header-btn {
|
||||
padding: 1vh 1.5vw !important;
|
||||
font-size: clamp(14px, 3.5vw, 18px) !important;
|
||||
background: #fff !important;
|
||||
border: 1px solid #000 !important;
|
||||
color: #000 !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
float: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls .board-header-btn i {
|
||||
color: #000 !important;
|
||||
float: none !important;
|
||||
display: inline !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls .board-header-btn:hover {
|
||||
background: #000 !important;
|
||||
border-color: #000 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-controls .board-header-btn:hover i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.mobile-mode .zoom-level {
|
||||
font-size: clamp(14px, 3.5vw, 18px);
|
||||
min-width: 4vw;
|
||||
}
|
||||
|
||||
/* Comprehensive Mobile Mode Styles - Works on all screen sizes */
|
||||
.mobile-mode .board-wrapper {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
transform: none !important;
|
||||
transform-origin: initial !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-canvas {
|
||||
height: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin-bottom: 2rem !important;
|
||||
display: block !important;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane-header {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
padding: 1rem !important;
|
||||
margin-bottom: 1rem !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: block !important;
|
||||
float: none !important;
|
||||
margin-bottom: 2rem !important;
|
||||
border-left: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
clear: both !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 1rem !important;
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
display: grid !important;
|
||||
grid-template-columns: 30px 1fr auto auto !important;
|
||||
gap: 10px !important;
|
||||
align-items: center !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .list-header-name {
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
font-weight: bold !important;
|
||||
grid-row: 1 !important;
|
||||
grid-column: 2 !important;
|
||||
align-self: end !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .cardCount {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
grid-row: 2 !important;
|
||||
grid-column: 2 !important;
|
||||
align-self: start !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .list-header-menu-icon {
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 3 !important;
|
||||
padding: 14px !important;
|
||||
font-size: clamp(24px, 3vw, 48px) !important;
|
||||
text-align: center !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .list-header-handle {
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 4 !important;
|
||||
padding: 14px !important;
|
||||
font-size: clamp(28px, 3.5vw, 56px) !important;
|
||||
text-align: center !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-body {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 1rem !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
font-size: clamp(16px, 2vw, 24px) !important;
|
||||
padding: 1.2vh 1.5vw 0.5vh !important;
|
||||
min-height: 3vh !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
display: block !important;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard .minicard-title {
|
||||
font-size: clamp(16px, 2vw, 24px) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard .minicard-members {
|
||||
font-size: clamp(12px, 1.5vw, 18px) !important;
|
||||
}
|
||||
|
||||
.mobile-mode .minicard .minicard-lists {
|
||||
font-size: clamp(12px, 1.5vw, 18px) !important;
|
||||
}
|
||||
|
||||
/* Desktop Mode Styles */
|
||||
.desktop-mode .board-wrapper {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.desktop-mode .swimlane {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
display: flex !important;
|
||||
float: left !important;
|
||||
margin-bottom: 0 !important;
|
||||
border-left: 1px solid #ccc !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
padding: 2.5vh 1.5vw 0.5vh !important;
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header .list-header-name {
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
display: inline !important;
|
||||
grid-row: auto !important;
|
||||
grid-column: auto !important;
|
||||
align-self: auto !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header .cardCount {
|
||||
font-size: 12px !important;
|
||||
grid-row: auto !important;
|
||||
grid-column: auto !important;
|
||||
align-self: auto !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header .list-header-menu-icon {
|
||||
position: absolute !important;
|
||||
right: 60px !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
grid-row: auto !important;
|
||||
grid-column: auto !important;
|
||||
padding: 14px !important;
|
||||
font-size: 40px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-header .list-header-handle {
|
||||
position: absolute !important;
|
||||
right: 10px !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
grid-row: auto !important;
|
||||
grid-column: auto !important;
|
||||
padding: 7px !important;
|
||||
font-size: clamp(16px, 3vw, 20px) !important;
|
||||
}
|
||||
|
||||
.desktop-mode .list-body {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
padding: 5px 11px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .minicard {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
min-height: auto !important;
|
||||
margin-bottom: 9px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .minicard .minicard-title {
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
}
|
||||
|
||||
.desktop-mode .minicard .minicard-members {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .minicard .minicard-lists {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
/* Additional Mobile Mode Styles for Other Elements - Works on all screen sizes */
|
||||
.mobile-mode .swimlane-header .swimlane-title {
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
font-weight: bold !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane-header .swimlane-description {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header {
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
padding: 1rem !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-title {
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
font-weight: bold !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-description {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
padding: 1vh 1.5vw !important;
|
||||
display: inline-block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn i {
|
||||
font-size: clamp(14px, 2vw, 24px) !important;
|
||||
display: inline !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* Force mobile mode visibility on all screen sizes */
|
||||
.mobile-mode .list-header .fa-angle-right,
|
||||
.mobile-mode .list-header .fa-arrows {
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .fa-angle-right {
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 3 !important;
|
||||
padding: 14px !important;
|
||||
font-size: clamp(24px, 3vw, 48px) !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .fa-arrows {
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 4 !important;
|
||||
padding: 14px !important;
|
||||
font-size: clamp(28px, 3.5vw, 56px) !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
/* Override any media queries that might hide elements in mobile mode */
|
||||
.mobile-mode * {
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list,
|
||||
.mobile-mode .swimlane,
|
||||
.mobile-mode .board-wrapper,
|
||||
.mobile-mode .board-canvas {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Force mobile mode list styling on all screen sizes - override desktop CSS */
|
||||
.mobile-mode .board-canvas {
|
||||
display: block !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane .swimlane-header {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 1rem 0 !important;
|
||||
padding: 1rem !important;
|
||||
font-size: clamp(18px, 2.5vw, 32px) !important;
|
||||
font-weight: bold !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane .lists {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
position: static !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
bottom: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list:first-child {
|
||||
margin-left: 0 !important;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list:last-child {
|
||||
margin-right: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list.ui-sortable-helper {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
height: auto !important;
|
||||
min-height: 60px !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list.placeholder {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
height: auto !important;
|
||||
min-height: 60px !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
}
|
||||
|
||||
/* Override any existing responsive CSS that might interfere with mobile mode */
|
||||
.mobile-mode .board-canvas .swimlane .lists {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-canvas .swimlane .lists .list {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
position: static !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
bottom: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* Force mobile mode to override any media query styles */
|
||||
@media screen and (min-width: 801px) {
|
||||
.mobile-mode .board-canvas {
|
||||
display: block !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
}
|
||||
|
||||
.mobile-mode .swimlane .lists {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
margin: 0 0 2rem 0 !important;
|
||||
padding: 0 !important;
|
||||
float: none !important;
|
||||
clear: both !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: 2px solid #ccc !important;
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
position: static !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
bottom: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide desktop-only elements in mobile mode (like mobile media queries do) */
|
||||
.mobile-mode .board-header-btn i.fa + span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn .fa + span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn .fa + .board-header-btn-text {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn .fa + .board-header-btn-label {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show only icons in mobile mode */
|
||||
.mobile-mode .board-header-btn {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
padding: 8px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btn i {
|
||||
display: inline-block !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Hide desktop-specific elements that shouldn't show in mobile mode */
|
||||
.mobile-mode .desktop-only,
|
||||
.mobile-mode .board-header .desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn.desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide desktop-specific board header buttons in mobile mode */
|
||||
.mobile-mode .board-header-btns.left {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.center {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show only the right section buttons in mobile mode, but hide text labels */
|
||||
.mobile-mode .board-header-btns.right {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn .fa + span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn .fa + .board-header-btn-text {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn .fa + .board-header-btn-label {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide specific desktop-only buttons that shouldn't show in mobile mode */
|
||||
.mobile-mode .board-header-btn.js-star-board span,
|
||||
.mobile-mode .board-header-btn.js-change-visibility span,
|
||||
.mobile-mode .board-header-btn.js-watch-board span,
|
||||
.mobile-mode .board-header-btn.js-sort-cards span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show only icons for mobile mode buttons */
|
||||
.mobile-mode .board-header-btns.right .board-header-btn {
|
||||
width: auto !important;
|
||||
min-width: auto !important;
|
||||
padding: 8px !important;
|
||||
text-align: center !important;
|
||||
margin: 0 2px !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header-btns.right .board-header-btn i {
|
||||
display: inline-block !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Ensure mobile mode looks like small screen mobile view */
|
||||
.mobile-mode .board-header {
|
||||
height: 40px !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btns {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn {
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
font-size: 15px !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn i.fa {
|
||||
line-height: 32px !important;
|
||||
}
|
||||
|
||||
/* Copy mobile media query styles to mobile mode for consistent appearance */
|
||||
.mobile-mode .board-header {
|
||||
height: 40px !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btns {
|
||||
margin-top: 0px !important;
|
||||
height: 40px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: flex-end !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn {
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
font-size: 15px !important;
|
||||
margin: 0 2px !important;
|
||||
padding: 4px 8px !important;
|
||||
border-radius: 4px !important;
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||||
color: #fff !important;
|
||||
text-decoration: none !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
min-width: 32px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2) !important;
|
||||
border-color: rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn i.fa {
|
||||
line-height: 32px !important;
|
||||
font-size: 15px !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn i.fa + span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-mode .board-header .board-header-btn span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide the board title in mobile mode to match mobile view */
|
||||
.mobile-mode .header-board-menu {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Ensure the board header takes full width in mobile mode */
|
||||
.mobile-mode .board-header {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: space-between !important;
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
||||
/* Additional Desktop Mode Styles for Other Elements */
|
||||
.desktop-mode .swimlane-header .swimlane-title {
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
}
|
||||
|
||||
.desktop-mode .swimlane-header .swimlane-description {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header {
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
padding: 2.5vh 1.5vw 0.5vh !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header .board-header-title {
|
||||
font-size: clamp(14px, 3vw, 18px) !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header .board-header-description {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header .board-header-btn {
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
}
|
||||
|
||||
.desktop-mode .board-header .board-header-btn i {
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ template(name="boardHeaderBar")
|
||||
i.fa.fa-pencil-square-o
|
||||
|
||||
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'}}")
|
||||
title="{{#if isStarred}}{{_ 'star-board-short-unstar'}}{{else}}{{_ 'star-board-short-star'}}{{/if}}" aria-label="{{#if isStarred}}{{_ 'star-board-short-unstar'}}{{else}}{{_ 'star-board-short-star'}}{{/if}}")
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
if showStarCounter
|
||||
span
|
||||
@@ -51,6 +51,8 @@ template(name="boardHeaderBar")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
|
||||
.board-header-btns.center
|
||||
|
||||
.board-header-btns.right
|
||||
if currentBoard
|
||||
if isMiniScreen
|
||||
@@ -62,15 +64,11 @@ template(name="boardHeaderBar")
|
||||
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.js-watch-board(
|
||||
title="{{_ watchLevel }}")
|
||||
@@ -80,10 +78,8 @@ template(name="boardHeaderBar")
|
||||
i.fa.fa-bell
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
span {{_ watchLevel}}
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
|
||||
i.fa.fa-sort
|
||||
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
|
||||
if isSortActive
|
||||
a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}")
|
||||
i.fa.fa-times-thin
|
||||
@@ -92,13 +88,11 @@ template(name="boardHeaderBar")
|
||||
a.board-header-btn.js-log-in(
|
||||
title="{{_ 'log-in'}}")
|
||||
i.fa.fa-sign-in
|
||||
span {{_ 'log-in'}}
|
||||
|
||||
if isSandstorm
|
||||
if currentUser
|
||||
a.board-header-btn.js-open-archived-board
|
||||
i.fa.fa-archive
|
||||
span {{_ 'archives'}}
|
||||
|
||||
//if showSort
|
||||
// a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
|
||||
@@ -109,14 +103,12 @@ template(name="boardHeaderBar")
|
||||
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
|
||||
class="{{#if Filter.isActive}}emphasis{{/if}}")
|
||||
i.fa.fa-filter
|
||||
span {{#if Filter.isActive}}{{_ 'filter-on'}}{{else}}{{_ 'filter'}}{{/if}}
|
||||
if Filter.isActive
|
||||
a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
|
||||
i.fa.fa-search
|
||||
span {{_ 'search'}}
|
||||
|
||||
unless currentBoard.isTemplatesBoard
|
||||
a.board-header-btn.js-toggle-board-view(
|
||||
@@ -128,14 +120,12 @@ template(name="boardHeaderBar")
|
||||
i.fa.fa-trello
|
||||
if $eq boardView 'board-view-cal'
|
||||
i.fa.fa-calendar
|
||||
span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-swimlanes'}}{{/if}}
|
||||
|
||||
if canModifyBoard
|
||||
a.board-header-btn.js-multiselection-activate(
|
||||
title="{{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
|
||||
class="{{#if MultiSelection.isActive}}emphasis{{/if}}")
|
||||
i.fa.fa-check-square-o
|
||||
span {{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}
|
||||
if MultiSelection.isActive
|
||||
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
|
||||
/*
|
||||
const DOWNCLS = 'fa-sort-down';
|
||||
@@ -32,6 +33,8 @@ BlazeComponent.extendComponent({
|
||||
return currentBoard && currentBoard.getWatchLevel(Meteor.userId());
|
||||
},
|
||||
|
||||
|
||||
|
||||
isStarred() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
@@ -79,10 +82,29 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
'click .js-toggle-board-view': Popup.open('boardChangeView'),
|
||||
'click .js-toggle-sidebar'() {
|
||||
Sidebar.toggle();
|
||||
console.log('Hamburger menu clicked');
|
||||
// Use the same approach as keyboard shortcuts
|
||||
if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') {
|
||||
console.log('Using Sidebar.toggle()');
|
||||
Sidebar.toggle();
|
||||
} else {
|
||||
console.warn('Sidebar not available, trying alternative approach');
|
||||
// Try to trigger the sidebar through the global Blaze helper
|
||||
if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) {
|
||||
const sidebar = Blaze._globalHelpers.Sidebar();
|
||||
if (sidebar && typeof sidebar.toggle === 'function') {
|
||||
console.log('Using Blaze helper Sidebar.toggle()');
|
||||
sidebar.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'click .js-open-filter-view'() {
|
||||
Sidebar.setView('filter');
|
||||
if (Sidebar) {
|
||||
Sidebar.setView('filter');
|
||||
} else {
|
||||
console.warn('Sidebar not available for setView');
|
||||
}
|
||||
},
|
||||
'click .js-sort-cards': Popup.open('cardsSort'),
|
||||
/*
|
||||
@@ -99,14 +121,22 @@ BlazeComponent.extendComponent({
|
||||
*/
|
||||
'click .js-filter-reset'(event) {
|
||||
event.stopPropagation();
|
||||
Sidebar.setView();
|
||||
if (Sidebar) {
|
||||
Sidebar.setView();
|
||||
} else {
|
||||
console.warn('Sidebar not available for setView');
|
||||
}
|
||||
Filter.reset();
|
||||
},
|
||||
'click .js-sort-reset'() {
|
||||
Session.set('sortBy', '');
|
||||
},
|
||||
'click .js-open-search-view'() {
|
||||
Sidebar.setView('search');
|
||||
if (Sidebar) {
|
||||
Sidebar.setView('search');
|
||||
} else {
|
||||
console.warn('Sidebar not available for setView');
|
||||
}
|
||||
},
|
||||
'click .js-multiselection-activate'() {
|
||||
const currentCard = Utils.getCurrentCardId();
|
||||
@@ -200,6 +230,7 @@ const CreateBoard = BlazeComponent.extendComponent({
|
||||
title: title,
|
||||
permission: 'private',
|
||||
type: 'template-container',
|
||||
migrationVersion: 1, // Latest version - no migration needed
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -243,6 +274,7 @@ const CreateBoard = BlazeComponent.extendComponent({
|
||||
Boards.insert({
|
||||
title,
|
||||
permission: visibility,
|
||||
migrationVersion: 1, // Latest version - no migration needed
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,86 @@
|
||||
@import url("../../../css/reset.css") print, screen;
|
||||
|
||||
/* Board List Header with Zoom Controls */
|
||||
.board-list-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1vh 0 2vh 0;
|
||||
padding: 1vh 0;
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5vw;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.5vh 1vw;
|
||||
border-radius: 0.5vw;
|
||||
box-shadow: 0 0.2vh 0.5vh rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn {
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
border-radius: 0.3vw !important;
|
||||
background: #fff !important;
|
||||
border: 1px solid #000 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
text-decoration: none !important;
|
||||
color: #000 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0.3vw !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
float: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn i {
|
||||
color: #000 !important;
|
||||
float: none !important;
|
||||
display: inline !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn:hover {
|
||||
background: #000 !important;
|
||||
border-color: #000 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn:hover i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn.is-active {
|
||||
background: #0079bf;
|
||||
color: white;
|
||||
border-color: #005a8a;
|
||||
}
|
||||
|
||||
.zoom-controls .board-header-btn.is-active i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
min-width: 3vw;
|
||||
text-align: center;
|
||||
font-size: clamp(12px, 2vw, 14px);
|
||||
cursor: pointer;
|
||||
padding: 0.3vh 0.5vw;
|
||||
border-radius: 0.3vw;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.zoom-level:hover {
|
||||
background: #f0f0f0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.board-list {
|
||||
margin: 0 8px;
|
||||
}
|
||||
@@ -192,17 +273,131 @@
|
||||
font-size: 25px;
|
||||
color: #fff;
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
|
||||
.board-list.mobile-view {
|
||||
height: calc(100vh - 120px);
|
||||
overflow-y: scroll !important;
|
||||
overflow-x: hidden;
|
||||
padding: 0 1rem;
|
||||
margin: 0;
|
||||
scrollbar-width: auto !important;
|
||||
scrollbar-color: #888 #f1f1f1;
|
||||
}
|
||||
.board-list.mobile-view li {
|
||||
width: 100%;
|
||||
float: none;
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
padding-right: 50px; /* Space for drag handle */
|
||||
}
|
||||
.board-list.mobile-view .board-list-item {
|
||||
overflow: visible;
|
||||
height: 8rem;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding-right: 50px; /* Ensure content doesn't overlap with drag handle */
|
||||
}
|
||||
|
||||
.board-list.mobile-view .board-list-item .details {
|
||||
padding-right: 50px; /* Extra space for drag handle */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.board-list.mobile-view .board-list-item-sub-name {
|
||||
position: relative;
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
}
|
||||
.board-list.mobile-view .board-handle {
|
||||
position: absolute;
|
||||
padding: 7px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 10px;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.board-list.mobile-view .board-handle:hover {
|
||||
background: rgba(255, 255, 0, 0.8) !important; /* Yellow hover */
|
||||
}
|
||||
|
||||
/* Force scrollbar to be visible on mobile view */
|
||||
.board-list.mobile-view::-webkit-scrollbar {
|
||||
width: 12px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list.mobile-view::-webkit-scrollbar-track {
|
||||
background: #f1f1f1 !important;
|
||||
border-radius: 6px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list.mobile-view::-webkit-scrollbar-thumb {
|
||||
background: #888 !important;
|
||||
border-radius: 6px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list.mobile-view::-webkit-scrollbar-thumb:hover {
|
||||
background: #555 !important;
|
||||
}
|
||||
|
||||
/* Force mobile view to have scrollable content */
|
||||
.board-list.mobile-view {
|
||||
min-height: 100vh; /* Force content to be tall enough to scroll */
|
||||
}
|
||||
|
||||
.board-list.mobile-view::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
.board-list {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
.board-list li {
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
float: none;
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
padding-right: 50px; /* Space for drag handle */
|
||||
}
|
||||
.board-list .board-list-item {
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
height: 8rem;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding-right: 50px; /* Ensure content doesn't overlap with drag handle */
|
||||
}
|
||||
|
||||
.board-list .board-list-item .details {
|
||||
padding-right: 50px; /* Extra space for drag handle */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.board-list .board-list-item-sub-name {
|
||||
position: relative;
|
||||
@@ -216,19 +411,296 @@
|
||||
transform: translateY(-50%);
|
||||
right: 10px;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.board-list .board-handle:hover {
|
||||
background: rgba(255, 255, 0, 0.8) !important; /* Yellow hover */
|
||||
}
|
||||
}
|
||||
/* Very small screens - ensure one board per row */
|
||||
@media screen and (max-width: 360px) {
|
||||
li {
|
||||
width: 100%;
|
||||
.board-list li {
|
||||
width: 100% !important;
|
||||
float: none !important;
|
||||
display: block !important;
|
||||
}
|
||||
.board-handle {
|
||||
.board-list .board-handle {
|
||||
position: absolute;
|
||||
padding: 7px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 10px;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile - make all text and icons 2x bigger above #content on All Boards page */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
.wrapper {
|
||||
font-size: 2em !important; /* 2x bigger base font size for All Boards page */
|
||||
}
|
||||
|
||||
.wrapper * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper .fa, .wrapper .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons */
|
||||
}
|
||||
|
||||
.board-list-header {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list-header h1 {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.AllBoardTeamsOrgs {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.AllBoardTeamsOrgs select {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.AllBoardTeamsOrgs input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.AllBoardTeamsOrgs .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .board-list-item {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .board-list-item-name {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .board-list-item-desc {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .minicard-members {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .minicard-lists {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .board-handle {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback for iPhone devices using JavaScript detection - All Boards page */
|
||||
.iphone-device .wrapper {
|
||||
font-size: 2em !important; /* 2x bigger base font size for All Boards page */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper .fa, .iphone-device .wrapper .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons */
|
||||
}
|
||||
|
||||
.iphone-device .board-list-header {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list-header h1 {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .AllBoardTeamsOrgs {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .AllBoardTeamsOrgs select {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .AllBoardTeamsOrgs input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .AllBoardTeamsOrgs .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .board-list-item {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .board-list-item-name {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .board-list-item-desc {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .minicard-members {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .minicard-lists {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .board-handle {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini and very small screens - make everything much larger */
|
||||
@media screen and (max-width: 400px) and (max-height: 900px) {
|
||||
.board-list {
|
||||
height: calc(100vh - 120px) !important;
|
||||
overflow-y: scroll !important;
|
||||
overflow-x: hidden !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.board-list li {
|
||||
width: 100% !important;
|
||||
float: none !important;
|
||||
display: block !important;
|
||||
margin-bottom: 1.5rem !important;
|
||||
}
|
||||
|
||||
.board-list .board-list-item {
|
||||
height: 12rem !important; /* Much taller */
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 1rem !important; /* More padding */
|
||||
font-size: 18px !important; /* Much larger text */
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
|
||||
.board-list .board-list-item .board-list-item-name {
|
||||
font-size: 20px !important; /* Larger board names */
|
||||
font-weight: bold !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
|
||||
.board-list .board-list-item .board-list-item-desc {
|
||||
font-size: 16px !important; /* Larger descriptions */
|
||||
line-height: 1.3 !important;
|
||||
}
|
||||
|
||||
.board-list .board-list-item .minicard-members {
|
||||
font-size: 14px !important; /* Larger member avatars */
|
||||
}
|
||||
|
||||
.board-list .board-list-item .minicard-lists {
|
||||
font-size: 14px !important; /* Larger list counters */
|
||||
}
|
||||
|
||||
.board-list .board-handle {
|
||||
position: absolute;
|
||||
padding: 10px !important;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 15px !important;
|
||||
font-size: 28px !important; /* Much larger drag handle */
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.4) !important;
|
||||
border-radius: 50%;
|
||||
width: 50px !important; /* Larger handle */
|
||||
height: 50px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.board-list .board-handle:hover {
|
||||
background: rgba(255, 255, 0, 0.8) !important; /* Yellow hover */
|
||||
}
|
||||
|
||||
/* Force scrollbar to be visible and larger */
|
||||
.board-list::-webkit-scrollbar {
|
||||
width: 16px !important; /* Much wider scrollbar */
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-track {
|
||||
background: #f1f1f1 !important;
|
||||
border-radius: 8px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-thumb {
|
||||
background: #666 !important; /* Darker for better visibility */
|
||||
border-radius: 8px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
min-height: 50px !important; /* Minimum thumb size */
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-thumb:hover {
|
||||
background: #333 !important;
|
||||
}
|
||||
|
||||
/* Ensure scrollbar is always visible */
|
||||
.board-list {
|
||||
scrollbar-gutter: stable;
|
||||
scrollbar-width: auto !important;
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
|
||||
.board-list::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 200px !important; /* More space to ensure scrolling */
|
||||
}
|
||||
}
|
||||
.AllBoardTeamsOrgs {
|
||||
@@ -279,3 +751,78 @@
|
||||
padding-right: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Fix multiple scrollbars issue on mobile */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.board-list {
|
||||
overflow-y: scroll !important;
|
||||
overflow-x: hidden !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #888 #f1f1f1;
|
||||
height: calc(100vh - 120px); /* Ensure there's content to scroll */
|
||||
}
|
||||
|
||||
/* Force scrollbar to always be visible */
|
||||
.board-list::-webkit-scrollbar {
|
||||
width: 12px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-track {
|
||||
background: #f1f1f1 !important;
|
||||
border-radius: 6px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-thumb {
|
||||
background: #888 !important;
|
||||
border-radius: 6px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-thumb:hover {
|
||||
background: #555 !important;
|
||||
}
|
||||
|
||||
/* Ensure scrollbar is always visible */
|
||||
.board-list::-webkit-scrollbar-corner {
|
||||
background: #f1f1f1 !important;
|
||||
}
|
||||
|
||||
/* Force scrollbar to be visible */
|
||||
.board-list {
|
||||
scrollbar-gutter: stable;
|
||||
scrollbar-width: auto !important;
|
||||
min-height: 100vh; /* Force content to be tall enough to scroll */
|
||||
}
|
||||
|
||||
/* Ensure there's always content to scroll */
|
||||
.board-list::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
/* Ensure only one scrollbar is visible */
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
template(name="boardList")
|
||||
.wrapper
|
||||
.board-list-header
|
||||
|
||||
ul.AllBoardTeamsOrgs
|
||||
li.AllBoardTeams
|
||||
if userHasTeams
|
||||
@@ -29,7 +31,7 @@ template(name="boardList")
|
||||
input#filterBtn(type="button" value="{{_ 'filter'}}")
|
||||
input#resetBtn(type="button" value="{{_ 'filter-clear'}}")
|
||||
|
||||
ul.board-list.clearfix.js-boards
|
||||
ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
li.js-add-board
|
||||
a.board-list-item.label(title="{{_ 'add-board'}}")
|
||||
| {{_ 'add-board'}}
|
||||
|
||||
@@ -22,6 +22,9 @@ Template.boardListHeaderBar.events({
|
||||
},
|
||||
});
|
||||
|
||||
Template.boardList.events({
|
||||
});
|
||||
|
||||
Template.boardListHeaderBar.helpers({
|
||||
title() {
|
||||
//if (FlowRouter.getRouteName() === 'template-container') {
|
||||
@@ -239,34 +242,40 @@ BlazeComponent.extendComponent({
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click .js-clone-board'(evt) {
|
||||
let title = getSlug(ReactiveCache.getBoard(this.currentData()._id).title) || 'cloned-board';
|
||||
Meteor.call(
|
||||
'copyBoard',
|
||||
this.currentData()._id,
|
||||
{
|
||||
sort: ReactiveCache.getBoards({ archived: false }).length,
|
||||
type: 'board',
|
||||
title: ReactiveCache.getBoard(this.currentData()._id).title,
|
||||
},
|
||||
(err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
Session.set('fromBoard', null);
|
||||
subManager.subscribe('board', res, false);
|
||||
FlowRouter.go('board', {
|
||||
id: res,
|
||||
slug: title,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
evt.preventDefault();
|
||||
if (confirm(TAPi18n.__('duplicate-board-confirm'))) {
|
||||
let title =
|
||||
getSlug(ReactiveCache.getBoard(this.currentData()._id).title) ||
|
||||
'cloned-board';
|
||||
Meteor.call(
|
||||
'copyBoard',
|
||||
this.currentData()._id,
|
||||
{
|
||||
sort: ReactiveCache.getBoards({ archived: false }).length,
|
||||
type: 'board',
|
||||
title: ReactiveCache.getBoard(this.currentData()._id).title,
|
||||
},
|
||||
(err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
Session.set('fromBoard', null);
|
||||
subManager.subscribe('board', res, false);
|
||||
FlowRouter.go('board', {
|
||||
id: res,
|
||||
slug: title,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
evt.preventDefault();
|
||||
}
|
||||
},
|
||||
'click .js-archive-board'(evt) {
|
||||
const boardId = this.currentData()._id;
|
||||
Meteor.call('archiveBoard', boardId);
|
||||
evt.preventDefault();
|
||||
if (confirm(TAPi18n.__('archive-board-confirm'))) {
|
||||
const boardId = this.currentData()._id;
|
||||
Meteor.call('archiveBoard', boardId);
|
||||
evt.preventDefault();
|
||||
}
|
||||
},
|
||||
'click .js-accept-invite'() {
|
||||
const boardId = this.currentData()._id;
|
||||
|
||||
@@ -106,6 +106,142 @@
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 4em;
|
||||
}
|
||||
|
||||
/* Upload progress indicators for drag-and-drop uploads */
|
||||
.minicard-upload-progress,
|
||||
.card-details-upload-progress {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin: 8px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.upload-progress-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.upload-progress-header i {
|
||||
margin-right: 8px;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.upload-progress-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.upload-progress-item.upload-error {
|
||||
border-color: #dc3545;
|
||||
background: #f8d7da;
|
||||
}
|
||||
|
||||
.upload-progress-filename {
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
color: #495057;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.upload-progress-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: #e9ecef;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.upload-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #007bff, #0056b3);
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.upload-progress-item.upload-error .upload-progress-fill {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.upload-progress-error,
|
||||
.upload-progress-success {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.upload-progress-error {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.upload-progress-success {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.upload-progress-error i,
|
||||
.upload-progress-success i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* Minicard specific styles */
|
||||
.minicard-upload-progress {
|
||||
margin: 4px 0;
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.minicard-upload-progress .upload-progress-item {
|
||||
padding: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.minicard-upload-progress .upload-progress-filename {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Card details specific styles */
|
||||
.card-details-upload-progress {
|
||||
margin: 12px 0;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card-details-upload-progress .upload-progress-header {
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-details-upload-progress .upload-progress-item {
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-details-upload-progress .upload-progress-filename {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Drag over state for minicards */
|
||||
.minicard.is-dragging-over {
|
||||
border: 2px dashed #007bff !important;
|
||||
background: rgba(0, 123, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Drag over state for card details */
|
||||
.js-card-details.is-dragging-over {
|
||||
border: 2px dashed #007bff !important;
|
||||
background: rgba(0, 123, 255, 0.05) !important;
|
||||
}
|
||||
top: 0;
|
||||
right: 8px;
|
||||
position: absolute;
|
||||
@@ -200,3 +336,36 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Attachment migration styles */
|
||||
.attachment-item.migrating {
|
||||
position: relative;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.attachment-migration-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.migration-spinner {
|
||||
font-size: 24px;
|
||||
color: #007cba;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.migration-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -57,7 +57,7 @@ template(name="attachmentGallery")
|
||||
|
||||
each attachments
|
||||
|
||||
.attachment-item
|
||||
.attachment-item(class="{{#if isAttachmentMigrating _id}}migrating{{/if}}")
|
||||
.attachment-thumbnail-container.open-preview(data-attachment-id="{{_id}}" data-card-id="{{ meta.cardId }}")
|
||||
if link
|
||||
if(isImage)
|
||||
@@ -97,6 +97,12 @@ template(name="attachmentGallery")
|
||||
i.fa.fa-trash.icon(title="{{_ 'delete'}}")
|
||||
a.fa.fa-navicon.icon.js-open-attachment-menu(data-attachment-link="{{link}}" title="{{_ 'attachmentActionsPopup-title'}}")
|
||||
|
||||
// Migration spinner overlay
|
||||
if isAttachmentMigrating _id
|
||||
.attachment-migration-overlay
|
||||
.migration-spinner
|
||||
i.fa.fa-cog.fa-spin
|
||||
.migration-text {{_ 'migrating-attachment'}}
|
||||
|
||||
template(name="attachmentActionsPopup")
|
||||
ul.pop-over-list
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { ObjectID } from 'bson';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { sanitizeHTML, sanitizeText } from '/imports/lib/secureDOMPurify';
|
||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager';
|
||||
|
||||
const filesize = require('filesize');
|
||||
const prettyMilliseconds = require('pretty-ms');
|
||||
@@ -268,7 +271,7 @@ Template.attachmentGallery.helpers({
|
||||
return ret;
|
||||
},
|
||||
sanitize(value) {
|
||||
return DOMPurify.sanitize(value);
|
||||
return sanitizeHTML(value);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -296,44 +299,13 @@ Template.cardAttachmentsPopup.events({
|
||||
const files = event.currentTarget.files;
|
||||
if (files) {
|
||||
let uploads = [];
|
||||
for (const file of files) {
|
||||
const fileId = new ObjectID().toString();
|
||||
let fileName = DOMPurify.sanitize(file.name);
|
||||
const uploaders = handleFileUpload(card, files);
|
||||
|
||||
// If sanitized filename is not same as original filename,
|
||||
// it could be XSS that is already fixed with sanitize,
|
||||
// or just normal mistake, so it is not a problem.
|
||||
// That is why here is no warning.
|
||||
if (fileName !== file.name) {
|
||||
// If filename is empty, only in that case add some filename
|
||||
if (fileName.length === 0) {
|
||||
fileName = 'Empty-filename-after-sanitize.txt';
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
file: file,
|
||||
fileId: fileId,
|
||||
fileName: fileName,
|
||||
meta: Utils.getCommonAttachmentMetaFrom(card),
|
||||
chunkSize: 'dynamic',
|
||||
};
|
||||
config.meta.fileId = fileId;
|
||||
const uploader = Attachments.insert(
|
||||
config,
|
||||
false,
|
||||
);
|
||||
uploaders.forEach(uploader => {
|
||||
uploader.on('start', function() {
|
||||
uploads.push(this);
|
||||
templateInstance.uploads.set(uploads);
|
||||
});
|
||||
uploader.on('uploaded', (error, fileRef) => {
|
||||
if (!error) {
|
||||
if (fileRef.isImage) {
|
||||
card.setCover(fileRef._id);
|
||||
}
|
||||
}
|
||||
});
|
||||
uploader.on('end', (error, fileRef) => {
|
||||
uploads = uploads.filter(_upload => _upload.config.fileId != fileRef._id);
|
||||
templateInstance.uploads.set(uploads);
|
||||
@@ -341,8 +313,7 @@ Template.cardAttachmentsPopup.events({
|
||||
Popup.back();
|
||||
}
|
||||
});
|
||||
uploader.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
'click .js-computer-upload'(event, templateInstance) {
|
||||
@@ -356,6 +327,105 @@ const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
|
||||
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
|
||||
let pastedResults = null;
|
||||
|
||||
// Shared upload logic for drag-and-drop functionality
|
||||
export function handleFileUpload(card, files) {
|
||||
if (!files || files.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check if board allows attachments
|
||||
const board = card.board();
|
||||
if (!board || !board.allowsAttachments) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.warn('Attachments not allowed on this board');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check if user can modify the card
|
||||
if (!card.canModifyCard()) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.warn('User does not have permission to modify this card');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const uploads = [];
|
||||
|
||||
for (const file of files) {
|
||||
// Basic file validation
|
||||
if (!file || !file.name) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.warn('Invalid file object');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const fileId = new ObjectID().toString();
|
||||
let fileName = sanitizeText(file.name);
|
||||
|
||||
// If sanitized filename is not same as original filename,
|
||||
// it could be XSS that is already fixed with sanitize,
|
||||
// or just normal mistake, so it is not a problem.
|
||||
// That is why here is no warning.
|
||||
if (fileName !== file.name) {
|
||||
// If filename is empty, only in that case add some filename
|
||||
if (fileName.length === 0) {
|
||||
fileName = 'Empty-filename-after-sanitize.txt';
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
file: file,
|
||||
fileId: fileId,
|
||||
fileName: fileName,
|
||||
meta: Utils.getCommonAttachmentMetaFrom(card),
|
||||
chunkSize: 'dynamic',
|
||||
};
|
||||
config.meta.fileId = fileId;
|
||||
|
||||
try {
|
||||
const uploader = Attachments.insert(
|
||||
config,
|
||||
false,
|
||||
);
|
||||
|
||||
// Add to progress manager for tracking
|
||||
const uploadId = uploadProgressManager.addUpload(card._id, uploader, file);
|
||||
|
||||
uploader.on('uploaded', (error, fileRef) => {
|
||||
if (!error) {
|
||||
if (fileRef.isImage) {
|
||||
card.setCover(fileRef._id);
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Set cover image for card ${card._id}: ${fileRef.name}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.error('Upload error:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
uploader.on('error', (error) => {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.error('Upload error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
uploads.push(uploader);
|
||||
uploader.start();
|
||||
} catch (error) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.error('Failed to create uploader:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uploads;
|
||||
}
|
||||
|
||||
Template.previewClipboardImagePopup.onRendered(() => {
|
||||
// we can paste image from clipboard
|
||||
const handle = results => {
|
||||
@@ -498,7 +568,7 @@ BlazeComponent.extendComponent({
|
||||
const name = this.$('.js-edit-attachment-name')[0]
|
||||
.value
|
||||
.trim() + this.data().extensionWithDot;
|
||||
if (name === DOMPurify.sanitize(name)) {
|
||||
if (name === sanitizeText(name)) {
|
||||
Meteor.call('renameAttachment', this.data()._id, name);
|
||||
}
|
||||
Popup.back();
|
||||
@@ -507,3 +577,20 @@ BlazeComponent.extendComponent({
|
||||
]
|
||||
}
|
||||
}).register('attachmentRenamePopup');
|
||||
|
||||
// Template helpers for attachment migration status
|
||||
Template.registerHelper('attachmentMigrationStatus', function(attachmentId) {
|
||||
return attachmentMigrationManager.getAttachmentMigrationStatus(attachmentId);
|
||||
});
|
||||
|
||||
Template.registerHelper('isAttachmentMigrating', function(attachmentId) {
|
||||
return attachmentMigrationManager.isAttachmentBeingMigrated(attachmentId);
|
||||
});
|
||||
|
||||
Template.registerHelper('attachmentMigrationProgress', function() {
|
||||
return attachmentMigrationManager.attachmentMigrationProgress.get();
|
||||
});
|
||||
|
||||
Template.registerHelper('attachmentMigrationStatusText', function() {
|
||||
return attachmentMigrationManager.attachmentMigrationStatus.get();
|
||||
});
|
||||
@@ -79,13 +79,14 @@ template(name="cardCustomField-currency")
|
||||
|
||||
template(name="cardCustomField-date")
|
||||
if canModifyCard
|
||||
a.js-edit-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
|
||||
a.js-edit-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
|
||||
if value
|
||||
div.card-date
|
||||
time(datetime="{{showISODate}}")
|
||||
| {{showDate}}
|
||||
b
|
||||
| {{showWeek}}
|
||||
if showWeekOfYear
|
||||
b
|
||||
| {{showWeek}}
|
||||
else
|
||||
| {{_ 'edit'}}
|
||||
else
|
||||
@@ -93,8 +94,9 @@ template(name="cardCustomField-date")
|
||||
div.card-date
|
||||
time(datetime="{{showISODate}}")
|
||||
| {{showDate}}
|
||||
b
|
||||
| {{showWeek}}
|
||||
if showWeekOfYear
|
||||
b
|
||||
| {{showWeek}}
|
||||
|
||||
template(name="cardCustomField-dropdown")
|
||||
if canModifyCard
|
||||
|
||||
@@ -148,6 +148,10 @@ CardCustomField.register('cardCustomField');
|
||||
return this.date.get().week().toString();
|
||||
}
|
||||
|
||||
showWeekOfYear() {
|
||||
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
|
||||
}
|
||||
|
||||
showDate() {
|
||||
// this will start working once mquandalle:moment
|
||||
// is updated to at least moment.js 2.10.5
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
template(name="dateBadge")
|
||||
if canModifyCard
|
||||
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
|
||||
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
|
||||
time(datetime="{{showISODate}}")
|
||||
| {{showDate}}
|
||||
b
|
||||
| {{showWeek}}
|
||||
if showWeekOfYear
|
||||
b
|
||||
| {{showWeek}}
|
||||
else
|
||||
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
|
||||
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
|
||||
time(datetime="{{showISODate}}")
|
||||
| {{showDate}}
|
||||
b
|
||||
| {{showWeek}}
|
||||
if showWeekOfYear
|
||||
b
|
||||
| {{showWeek}}
|
||||
|
||||
template(name="dateCustomField")
|
||||
a(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
|
||||
a(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
|
||||
time(datetime="{{showISODate}}")
|
||||
| {{showDate}}
|
||||
b
|
||||
| {{showWeek}}
|
||||
if showWeekOfYear
|
||||
b
|
||||
| {{showWeek}}
|
||||
@@ -11,7 +11,7 @@ import { DatePicker } from '/client/lib/datepicker';
|
||||
}
|
||||
|
||||
_storeDate(date) {
|
||||
this.card.setReceived(date);
|
||||
this.card.setReceived(moment(date).format('YYYY-MM-DD HH:mm'));
|
||||
}
|
||||
|
||||
_deleteDate() {
|
||||
@@ -37,7 +37,7 @@ import { DatePicker } from '/client/lib/datepicker';
|
||||
}
|
||||
|
||||
_storeDate(date) {
|
||||
this.card.setStart(date);
|
||||
this.card.setStart(moment(date).format('YYYY-MM-DD HH:mm'));
|
||||
}
|
||||
|
||||
_deleteDate() {
|
||||
@@ -60,7 +60,7 @@ import { DatePicker } from '/client/lib/datepicker';
|
||||
}
|
||||
|
||||
_storeDate(date) {
|
||||
this.card.setDue(date);
|
||||
this.card.setDue(moment(date).format('YYYY-MM-DD HH:mm'));
|
||||
}
|
||||
|
||||
_deleteDate() {
|
||||
@@ -83,7 +83,7 @@ import { DatePicker } from '/client/lib/datepicker';
|
||||
}
|
||||
|
||||
_storeDate(date) {
|
||||
this.card.setEnd(date);
|
||||
this.card.setEnd(moment(date).format('YYYY-MM-DD HH:mm'));
|
||||
}
|
||||
|
||||
_deleteDate() {
|
||||
@@ -110,6 +110,10 @@ const CardDate = BlazeComponent.extendComponent({
|
||||
return this.date.get().week().toString();
|
||||
},
|
||||
|
||||
showWeekOfYear() {
|
||||
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
|
||||
},
|
||||
|
||||
showDate() {
|
||||
// this will start working once mquandalle:moment
|
||||
// is updated to at least moment.js 2.10.5
|
||||
@@ -283,6 +287,10 @@ class CardCustomFieldDate extends CardDate {
|
||||
return this.date.get().week().toString();
|
||||
}
|
||||
|
||||
showWeekOfYear() {
|
||||
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
|
||||
}
|
||||
|
||||
showDate() {
|
||||
// this will start working once mquandalle:moment
|
||||
// is updated to at least moment.js 2.10.5
|
||||
@@ -314,19 +322,19 @@ CardCustomFieldDate.register('cardCustomFieldDate');
|
||||
|
||||
(class extends CardStartDate {
|
||||
showDate() {
|
||||
return this.date.get().format('L');
|
||||
return this.date.get().format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
}.register('minicardStartDate'));
|
||||
|
||||
(class extends CardDueDate {
|
||||
showDate() {
|
||||
return this.date.get().format('L');
|
||||
return this.date.get().format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
}.register('minicardDueDate'));
|
||||
|
||||
(class extends CardEndDate {
|
||||
showDate() {
|
||||
return this.date.get().format('L');
|
||||
return this.date.get().format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
}.register('minicardEndDate'));
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.assignee {
|
||||
border-radius: 3px;
|
||||
border-radius: 0.4vw;
|
||||
display: block;
|
||||
position: relative;
|
||||
float: left;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
margin: 0 4px 4px 0;
|
||||
height: 4vw;
|
||||
width: 4vw;
|
||||
margin: 0.4vh;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
@@ -34,11 +34,11 @@
|
||||
background-color: #b3b3b3;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 50%;
|
||||
height: 7px;
|
||||
width: 7px;
|
||||
height: 1vw;
|
||||
width: 1vw;
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
right: -0.1vw;
|
||||
bottom: -0.1vw;
|
||||
border: 1px solid #fff;
|
||||
z-index: 15;
|
||||
}
|
||||
@@ -70,23 +70,23 @@
|
||||
}
|
||||
.copied-tooltip {
|
||||
display: none;
|
||||
padding: 0px 10px;
|
||||
padding: 0 1.3vw;
|
||||
background-color: rgba(0,0,0,0.875);
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
border-radius: 0.7vw;
|
||||
}
|
||||
.card-details {
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 600px;
|
||||
flex-basis: min(600px, 80vw);
|
||||
will-change: flex-basis;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
background: #f7f7f7;
|
||||
border-radius: bottom 3px;
|
||||
border-radius: bottom 0.4vw;
|
||||
z-index: 30;
|
||||
animation: flexGrowIn 0.1s;
|
||||
box-shadow: 0 0 7px 0 #b3b3b3;
|
||||
box-shadow: 0 0 0.9vh 0 #b3b3b3;
|
||||
transition: flex-basis 0.1s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -95,7 +95,7 @@
|
||||
}
|
||||
.card-details .card-details-canvas {
|
||||
width: auto;
|
||||
padding: 0 20px;
|
||||
padding: 0 2.5vw;
|
||||
}
|
||||
.card-details .card-details-header {
|
||||
margin: 0 -20px 5px;
|
||||
@@ -596,3 +596,11 @@ input[type="submit"].attachment-add-link-submit {
|
||||
overflow: hidden;
|
||||
background-color: #cecece;
|
||||
}
|
||||
|
||||
/* Drag and drop file upload visual feedback */
|
||||
.js-card-details.is-dragging-over {
|
||||
border: 2px dashed #0079bf;
|
||||
background-color: #e3f2fd !important;
|
||||
transform: scale(1.01);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ template(name="cardDetails")
|
||||
|
||||
+attachmentViewer
|
||||
|
||||
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}'): .card-details-canvas
|
||||
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}'): .card-details-canvas
|
||||
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
|
||||
+inlinedForm(classNames="js-card-details-title")
|
||||
+editCardTitleForm
|
||||
@@ -65,6 +65,26 @@ template(name="cardDetails")
|
||||
else
|
||||
p.warning {{_ 'card-archived'}}
|
||||
|
||||
// Upload progress indicator for drag-and-drop uploads
|
||||
if hasActiveUploads
|
||||
.card-details-upload-progress
|
||||
.upload-progress-header
|
||||
i.fa.fa-upload
|
||||
span {{_ 'uploading-files'}} ({{uploadCount}})
|
||||
each uploads
|
||||
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
|
||||
.upload-progress-filename {{file.name}}
|
||||
.upload-progress-bar
|
||||
.upload-progress-fill(style="width: {{progress}}%")
|
||||
if $eq status 'error'
|
||||
.upload-progress-error
|
||||
i.fa.fa-exclamation-triangle
|
||||
span {{_ 'upload-failed'}}
|
||||
else if $eq status 'completed'
|
||||
.upload-progress-success
|
||||
i.fa.fa-check
|
||||
span {{_ 'upload-completed'}}
|
||||
|
||||
.card-details-left
|
||||
|
||||
.card-details-items
|
||||
@@ -549,7 +569,7 @@ template(name="cardDetails")
|
||||
.card-checklist-attachmentGallery.card-checklists
|
||||
if currentBoard.allowsChecklists
|
||||
hr
|
||||
+checklists(cardId = _id)
|
||||
+checklists(cardId = _id card = this)
|
||||
if currentBoard.allowsSubtasks
|
||||
hr
|
||||
+subtasks(cardId = _id)
|
||||
@@ -569,25 +589,34 @@ template(name="cardDetails")
|
||||
+attachmentGallery
|
||||
hr
|
||||
|
||||
unless currentUser.isNoComments
|
||||
.comment-title
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-comment-o
|
||||
| {{_ 'comments'}}
|
||||
|
||||
if currentBoard.allowsComments
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isNoComments
|
||||
+commentForm
|
||||
+comments
|
||||
hr
|
||||
|
||||
.card-details-right
|
||||
|
||||
unless currentUser.isNoComments
|
||||
.activity-title
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-history
|
||||
| {{ _ 'activity'}}
|
||||
| {{ _ 'activities'}}
|
||||
if currentUser.isBoardMember
|
||||
.material-toggle-switch(title="{{_ 'hide-system-messages'}}")
|
||||
//span.toggle-switch-title
|
||||
if hiddenSystemMessages
|
||||
input.toggle-switch(type="checkbox" id="toggleButton" checked="checked")
|
||||
.material-toggle-switch(title="{{_ 'show-activities'}}")
|
||||
if showActivities
|
||||
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleButton")
|
||||
label.toggle-label(for="toggleButton")
|
||||
if currentBoard.allowsComments
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isNoComments
|
||||
+commentForm
|
||||
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard")
|
||||
label.toggle-label(for="toggleShowActivitiesCard")
|
||||
|
||||
unless currentUser.isNoComments
|
||||
if isLoaded.get
|
||||
if isLinkedCard
|
||||
@@ -666,6 +695,14 @@ template(name="cardDetailsActionsPopup")
|
||||
a.js-set-card-color
|
||||
i.fa.fa-paint-brush
|
||||
| {{_ 'setCardColorPopup-title'}}
|
||||
li
|
||||
a.js-toggle-show-list-on-minicard
|
||||
if showListOnMinicard
|
||||
i.fa.fa-eye
|
||||
| {{_ 'hide-list-on-minicard'}}
|
||||
else
|
||||
i.fa.fa-eye-slash
|
||||
| {{_ 'show-list-on-minicard'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
|
||||
@@ -12,6 +12,8 @@ import CardComments from '/models/cardComments';
|
||||
import { ALLOWED_COLORS } from '/config/const';
|
||||
import { UserAvatar } from '../users/userAvatar';
|
||||
import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList';
|
||||
import { handleFileUpload } from './attachments';
|
||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
const { calculateIndexData } = Utils;
|
||||
@@ -63,10 +65,6 @@ BlazeComponent.extendComponent({
|
||||
return card.findWatcher(Meteor.userId());
|
||||
},
|
||||
|
||||
hiddenSystemMessages() {
|
||||
return ReactiveCache.getCurrentUser().hasHiddenSystemMessages();
|
||||
},
|
||||
|
||||
customFieldsGrid() {
|
||||
return ReactiveCache.getCurrentUser().hasCustomFieldsGrid();
|
||||
},
|
||||
@@ -118,6 +116,11 @@ BlazeComponent.extendComponent({
|
||||
);
|
||||
},
|
||||
|
||||
isVerticalScrollbars() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
/** returns if the list id is the current list id
|
||||
* @param listId list id to check
|
||||
* @return is the list id the current list id ?
|
||||
@@ -377,8 +380,11 @@ BlazeComponent.extendComponent({
|
||||
Session.set('cardDetailsIsDragging', false);
|
||||
Session.set('cardDetailsIsMouseDown', false);
|
||||
},
|
||||
'click #toggleButton'() {
|
||||
Meteor.call('toggleSystemMessages');
|
||||
'click #toggleShowActivitiesCard'() {
|
||||
this.data().toggleShowActivities();
|
||||
},
|
||||
'click #toggleHideCheckedChecklistItems'() {
|
||||
this.data().toggleHideCheckedChecklistItems();
|
||||
},
|
||||
'click #toggleCustomFieldsGridButton'() {
|
||||
Meteor.call('toggleCustomFieldsGrid');
|
||||
@@ -477,6 +483,59 @@ BlazeComponent.extendComponent({
|
||||
}
|
||||
}
|
||||
},
|
||||
// Drag and drop file upload handlers
|
||||
'dragover .js-card-details'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
},
|
||||
'dragenter .js-card-details'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const card = this.data();
|
||||
const board = card.board();
|
||||
// Only allow drag-and-drop if user can modify card and board allows attachments
|
||||
if (card.canModifyCard() && board && board.allowsAttachments) {
|
||||
// Check if the drag contains files
|
||||
const dataTransfer = event.originalEvent.dataTransfer;
|
||||
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
|
||||
$(event.currentTarget).addClass('is-dragging-over');
|
||||
}
|
||||
}
|
||||
},
|
||||
'dragleave .js-card-details'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$(event.currentTarget).removeClass('is-dragging-over');
|
||||
},
|
||||
'drop .js-card-details'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$(event.currentTarget).removeClass('is-dragging-over');
|
||||
|
||||
const card = this.data();
|
||||
const board = card.board();
|
||||
|
||||
// Check permissions
|
||||
if (!card.canModifyCard() || !board || !board.allowsAttachments) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a file drop (not a checklist item reorder)
|
||||
const dataTransfer = event.originalEvent.dataTransfer;
|
||||
if (!dataTransfer || !dataTransfer.files || dataTransfer.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the drop contains files (not just text/HTML)
|
||||
if (!dataTransfer.types.includes('Files')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = dataTransfer.files;
|
||||
if (files && files.length > 0) {
|
||||
handleFileUpload(card, files);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
@@ -486,6 +545,16 @@ Template.cardDetails.helpers({
|
||||
isPopup() {
|
||||
let ret = !!Utils.getPopupCardId();
|
||||
return ret;
|
||||
},
|
||||
// Upload progress helpers
|
||||
hasActiveUploads() {
|
||||
return uploadProgressManager.hasActiveUploads(this._id);
|
||||
},
|
||||
uploads() {
|
||||
return uploadProgressManager.getUploadsForCard(this._id);
|
||||
},
|
||||
uploadCount() {
|
||||
return uploadProgressManager.getUploadCountForCard(this._id);
|
||||
}
|
||||
});
|
||||
Template.cardDetailsPopup.onDestroyed(() => {
|
||||
@@ -588,6 +657,10 @@ Template.cardDetailsActionsPopup.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
},
|
||||
|
||||
showListOnMinicard() {
|
||||
return this.showListOnMinicard;
|
||||
},
|
||||
});
|
||||
|
||||
Template.cardDetailsActionsPopup.events({
|
||||
@@ -633,6 +706,12 @@ Template.cardDetailsActionsPopup.events({
|
||||
if (!err && ret) Popup.close();
|
||||
});
|
||||
},
|
||||
'click .js-toggle-show-list-on-minicard'() {
|
||||
const currentCard = this;
|
||||
const newValue = !currentCard.showListOnMinicard;
|
||||
Cards.update(currentCard._id, { $set: { showListOnMinicard: newValue } });
|
||||
Popup.close();
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
|
||||
@@ -8,20 +8,6 @@ textarea.js-edit-checklist-item {
|
||||
resize: none;
|
||||
height: 34px;
|
||||
}
|
||||
.card-details .text-show-at-minicard {
|
||||
width: 350px;
|
||||
text-align: left;
|
||||
}
|
||||
.minicard .text-show-at-minicard {
|
||||
display: none;
|
||||
}
|
||||
.text-some-space {
|
||||
width: 20px;
|
||||
}
|
||||
.text-hide-checked-items {
|
||||
width: 400px;
|
||||
text-align: left;
|
||||
}
|
||||
.delete-text,
|
||||
.js-delete-checklist-item,
|
||||
.js-convert-checklist-item-to-card {
|
||||
@@ -40,8 +26,6 @@ textarea.js-edit-checklist-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
.checklist-progress-bar-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -61,6 +45,9 @@ textarea.js-edit-checklist-item {
|
||||
border-radius: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
.checklist-title {
|
||||
padding: 10px;
|
||||
}
|
||||
.checklist-title .checkbox {
|
||||
float: left;
|
||||
width: 30px;
|
||||
|
||||
@@ -9,10 +9,19 @@ template(name="checklists")
|
||||
else
|
||||
a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}")
|
||||
i.fa.fa-plus
|
||||
if currentUser.isBoardMember
|
||||
.material-toggle-switch(title="{{_ 'hide-finished-checklist'}}")
|
||||
//span.toggle-switch-title
|
||||
if card.hideFinishedChecklistIfItemsAreHidden
|
||||
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist")
|
||||
label.toggle-label(for="toggleHideFinishedChecklist")
|
||||
|
||||
.card-checklist-items
|
||||
each checklist in checklists
|
||||
+checklistDetail(checklist=checklist)
|
||||
if checklist.showChecklist card.hideFinishedChecklistIfItemsAreHidden
|
||||
+checklistDetail(checklist = checklist card = card)
|
||||
|
||||
if canModifyCard
|
||||
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
|
||||
@@ -47,7 +56,7 @@ template(name="checklistDetail")
|
||||
.checklist-progress-text {{finishedPercent}}%
|
||||
.checklist-progress-bar
|
||||
.checklist-progress(style="width:{{finishedPercent}}%")
|
||||
+checklistItems(checklist = checklist)
|
||||
+checklistItems(checklist = checklist card = card)
|
||||
|
||||
template(name="checklistDeletePopup")
|
||||
p {{_ 'confirm-checklist-delete-popup'}}
|
||||
@@ -64,6 +73,12 @@ template(name="addChecklistItemForm")
|
||||
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
|
||||
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
|
||||
label.toggle-label(for="toggleNewlineBecomesNewChecklistItem")
|
||||
| {{_ 'newLineNewItem'}}
|
||||
if $eq position 'top'
|
||||
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItemOriginOrder'}}")
|
||||
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItemOriginOrder")
|
||||
label.toggle-label(for="toggleNewlineBecomesNewChecklistItemOriginOrder")
|
||||
| {{_ 'originOrder'}}
|
||||
|
||||
template(name="editChecklistItemForm")
|
||||
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
|
||||
@@ -87,7 +102,7 @@ template(name="checklistItems")
|
||||
if checklist.items.length
|
||||
if canModifyCard
|
||||
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist position="top")
|
||||
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
|
||||
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top")
|
||||
else
|
||||
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
|
||||
i.fa.fa-plus
|
||||
@@ -96,7 +111,7 @@ template(name="checklistItems")
|
||||
+inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
|
||||
+editChecklistItemForm(type = 'item' item = item checklist = checklist)
|
||||
else
|
||||
+checklistItemDetail(item = item checklist = checklist)
|
||||
+checklistItemDetail(item = item checklist = checklist card = card)
|
||||
if canModifyCard
|
||||
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
|
||||
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
|
||||
@@ -105,7 +120,7 @@ template(name="checklistItems")
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name='checklistItemDetail')
|
||||
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if hideCheckedItems}} invisible{{/if}}{{/if}}"
|
||||
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
|
||||
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
|
||||
if canModifyCard
|
||||
.check-box-container
|
||||
@@ -122,27 +137,6 @@ template(name='checklistItemDetail')
|
||||
= item.title
|
||||
|
||||
template(name="checklistActionsPopup")
|
||||
if currentUser.isBoardMember
|
||||
span.text-show-at-minicard
|
||||
| {{_ 'show-at-minicard'}}
|
||||
.material-toggle-switch(title="{{_ 'show-checklist-at-minicard'}}")
|
||||
if showAtMinicard
|
||||
input.toggle-switch(type="checkbox" id="toggleShowChecklistAtMinicardButton" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleShowChecklistAtMinicardButton")
|
||||
label.toggle-label(for="toggleShowChecklistAtMinicardButton")
|
||||
hr
|
||||
span.text-hide-checked-items
|
||||
| {{_ 'hide-checked-items'}}
|
||||
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
|
||||
//span.toggle-switch-title
|
||||
//.check-square-icon.i.fa.fa-check-square-o
|
||||
if hideCheckedItems
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton")
|
||||
label.toggle-label(for="toggleHideCheckedItemsButton")
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-delete-checklist.delete-checklist
|
||||
@@ -154,6 +148,24 @@ template(name="checklistActionsPopup")
|
||||
a.js-copy-checklist.copy-checklist
|
||||
i.fa.fa-copy
|
||||
| {{_ "copyChecklist"}} ...
|
||||
a.js-hide-checked-checklist-items
|
||||
i.fa.fa-eye-slash
|
||||
| {{_ "hideCheckedChecklistItems"}} ...
|
||||
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
|
||||
if checklist.hideCheckedChecklistItems
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}")
|
||||
label.toggle-label(for="toggleHideCheckedChecklistItems_{{checklist._id}}")
|
||||
a.js-hide-all-checklist-items
|
||||
i.fa.fa-ban
|
||||
| {{_ "hideAllChecklistItems"}} ...
|
||||
.material-toggle-switch(title="{{_ 'hideAllChecklistItems'}}")
|
||||
if checklist.hideAllChecklistItems
|
||||
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}")
|
||||
label.toggle-label(for="toggleHideAllChecklistItems_{{checklist._id}}")
|
||||
|
||||
template(name="copyChecklistPopup")
|
||||
+copyAndMoveChecklist
|
||||
|
||||
@@ -119,6 +119,7 @@ BlazeComponent.extendComponent({
|
||||
event.preventDefault();
|
||||
const textarea = this.find('textarea.js-add-checklist-item');
|
||||
const newlineBecomesNewChecklistItem = this.find('input#toggleNewlineBecomesNewChecklistItem');
|
||||
const newlineBecomesNewChecklistItemOriginOrder = this.find('input#toggleNewlineBecomesNewChecklistItemOriginOrder');
|
||||
const title = textarea.value.trim();
|
||||
const checklist = this.currentData().checklist;
|
||||
|
||||
@@ -127,22 +128,28 @@ BlazeComponent.extendComponent({
|
||||
if (newlineBecomesNewChecklistItem.checked) {
|
||||
checklistItems = title.split('\n').map(_value => _value.trim());
|
||||
if (this.currentData().position === 'top') {
|
||||
checklistItems = checklistItems.reverse();
|
||||
if (newlineBecomesNewChecklistItemOriginOrder.checked === false) {
|
||||
checklistItems = checklistItems.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
let addIndex;
|
||||
let sortIndex;
|
||||
if (this.currentData().position === 'top') {
|
||||
sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base;
|
||||
addIndex = -1;
|
||||
} else {
|
||||
sortIndex = Utils.calculateIndexData(checklist.lastItem(), null).base;
|
||||
addIndex = 1;
|
||||
}
|
||||
for (let checklistItem of checklistItems) {
|
||||
let sortIndex;
|
||||
if (this.currentData().position === 'top') {
|
||||
sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base;
|
||||
} else {
|
||||
sortIndex = Utils.calculateIndexData(checklist.lastItem(), null).base;
|
||||
}
|
||||
ChecklistItems.insert({
|
||||
title: checklistItem,
|
||||
checklistId: checklist._id,
|
||||
cardId: checklist.cardId,
|
||||
sort: sortIndex,
|
||||
});
|
||||
sortIndex += addIndex;
|
||||
}
|
||||
}
|
||||
// We keep the form opened, empty it.
|
||||
@@ -201,12 +208,8 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
events() {
|
||||
const events = {
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
...events,
|
||||
'click .js-open-checklist-details-menu': Popup.open('checklistActions'),
|
||||
'submit .js-add-checklist': this.addChecklist,
|
||||
'submit .js-edit-checklist-title': this.editChecklist,
|
||||
@@ -217,6 +220,10 @@ BlazeComponent.extendComponent({
|
||||
'focus .js-add-checklist-item': this.focusChecklistItem,
|
||||
// add and delete checklist / checklist-item
|
||||
'click .js-open-inlined-form': this.closeAllInlinedForms,
|
||||
'click #toggleHideFinishedChecklist'(event) {
|
||||
event.preventDefault();
|
||||
this.data().card.toggleHideFinishedChecklist();
|
||||
},
|
||||
keydown: this.pressKey,
|
||||
},
|
||||
];
|
||||
@@ -271,16 +278,6 @@ Template.checklists.helpers({
|
||||
const ret = card.checklists();
|
||||
return ret;
|
||||
},
|
||||
showAtMinicard() {
|
||||
const card = ReactiveCache.getCard(this.cardId);
|
||||
const ret = card.checklists({'showAtMinicard':1});
|
||||
return ret;
|
||||
},
|
||||
hideCheckedItems() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) return currentUser.hasHideCheckedItems();
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
@@ -303,26 +300,9 @@ BlazeComponent.extendComponent({
|
||||
}).register('addChecklistItemForm');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
toggleItem() {
|
||||
const checklist = this.currentData().checklist;
|
||||
const item = this.currentData().item;
|
||||
if (checklist && item && item._id) {
|
||||
item.toggleItem();
|
||||
}
|
||||
},
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-checklist-item .check-box-container': this.toggleItem,
|
||||
'click #toggleShowChecklistAtMinicardButton'() {
|
||||
const checklist = this.checklist;
|
||||
if (checklist && checklist._id) {
|
||||
Meteor.call('toggleShowChecklistAtMinicard', checklist._id);
|
||||
}
|
||||
},
|
||||
'click #toggleHideCheckedItemsButton'() {
|
||||
Meteor.call('toggleHideCheckedItems');
|
||||
},
|
||||
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
|
||||
Popup.back(2);
|
||||
const checklist = this.checklist;
|
||||
@@ -332,6 +312,16 @@ BlazeComponent.extendComponent({
|
||||
}),
|
||||
'click .js-move-checklist': Popup.open('moveChecklist'),
|
||||
'click .js-copy-checklist': Popup.open('copyChecklist'),
|
||||
'click .js-hide-checked-checklist-items'(event) {
|
||||
event.preventDefault();
|
||||
this.data().checklist.toggleHideCheckedChecklistItems();
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-hide-all-checklist-items'(event) {
|
||||
event.preventDefault();
|
||||
this.data().checklist.toggleHideAllChecklistItems();
|
||||
Popup.back();
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -357,11 +347,6 @@ BlazeComponent.extendComponent({
|
||||
}).register('editChecklistItemForm');
|
||||
|
||||
Template.checklistItemDetail.helpers({
|
||||
hideCheckedItems() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user) return user.hasHideCheckedItems();
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
.palette-colors {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start; /* left-align color chips in wider popovers */
|
||||
}
|
||||
.palette-colors .palette-color {
|
||||
flex-grow: 1;
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
.minicard .checklists-title,
|
||||
.minicard .add-checklist,
|
||||
.minicard .add-checklist-item,
|
||||
.minicard .checklist-details-menu {
|
||||
display: none;
|
||||
}
|
||||
.minicard .checklist-progress-bar-container {
|
||||
width: 190px; /* TODO: Add adjustable width https://github.com/wekan/wekan/pull/4964 */
|
||||
}
|
||||
.minicard-wrapper {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 9px;
|
||||
margin-bottom: 1.2vh;
|
||||
}
|
||||
.minicard-wrapper.placeholder {
|
||||
background: #ccc;
|
||||
border-radius: 9px;
|
||||
border-radius: 1.2vw;
|
||||
}
|
||||
.minicard-wrapper.ui-sortable-helper {
|
||||
cursor: grabbing;
|
||||
@@ -25,28 +16,28 @@
|
||||
}
|
||||
.minicard-wrapper.ui-sortable-helper .and-n-other {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
padding: 4px;
|
||||
height: 2.5vh;
|
||||
padding: 0.5vh;
|
||||
background-color: #f2f2f2;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
border-radius: 0.4vw;
|
||||
}
|
||||
.minicard-wrapper.ui-sortable-helper .multi-selection-checkbox {
|
||||
display: none;
|
||||
}
|
||||
.minicard-wrapper .multi-selection-checkbox + .minicard {
|
||||
margin-left: 8px;
|
||||
margin-left: 1vw;
|
||||
}
|
||||
@media only screen {
|
||||
.minicard {
|
||||
padding: 6px 8px 2px;
|
||||
padding: 0.8vh 1vw 0.3vh;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
background-color: #fff;
|
||||
min-height: 20px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
||||
border-radius: 2px;
|
||||
min-height: 2.5vh;
|
||||
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.15);
|
||||
border-radius: 0.3vw;
|
||||
color: #4d4d4d;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s, border-radius 0.2s;
|
||||
@@ -54,14 +45,14 @@
|
||||
}
|
||||
.minicard-details-menu-with-handle {
|
||||
float: right;
|
||||
font-size: 18px;
|
||||
padding-right: 30px;
|
||||
padding-left: 5px;
|
||||
font-size: clamp(14px, 3vw, 18px);
|
||||
padding-right: 4vw;
|
||||
padding-left: 0.7vw;
|
||||
}
|
||||
.minicard-details-menu {
|
||||
float: right;
|
||||
font-size: 18px;
|
||||
padding-left: 5px;
|
||||
font-size: clamp(14px, 3vw, 18px);
|
||||
padding-left: 0.7vw;
|
||||
}
|
||||
@media print {
|
||||
.minicard-details-menu,
|
||||
@@ -72,7 +63,7 @@
|
||||
.minicard.linked-board .linked-icon,
|
||||
.minicard.linked-card .linked-icon {
|
||||
display: inline-block;
|
||||
margin-right: 11px;
|
||||
margin-right: 1.5vw;
|
||||
vertical-align: baseline;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@@ -81,11 +72,11 @@
|
||||
color: #937760;
|
||||
}
|
||||
.is-selected .minicard {
|
||||
transform: translateX(11px);
|
||||
transform: translateX(1.5vw);
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
z-index: 25;
|
||||
box-shadow: -2px 1px 2px rgba(0,0,0,0.2);
|
||||
box-shadow: -0.3vw 0.2vh 0.3vh rgba(0,0,0,0.2);
|
||||
}
|
||||
.minicard:hover:not(.minicard-composer),
|
||||
.is-selected .minicard,
|
||||
@@ -99,20 +90,20 @@
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 145px;
|
||||
height: 20vh;
|
||||
user-select: none;
|
||||
margin: 6px -8px 6px -8px;
|
||||
border-radius: top 2px;
|
||||
margin: 0.8vh -1vw 0.8vh -1vw;
|
||||
border-radius: top 0.3vw;
|
||||
}
|
||||
.minicard .minicard-labels {
|
||||
float: none;
|
||||
}
|
||||
.minicard .minicard-labels .minicard-label {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 2px;
|
||||
margin-right: 3px;
|
||||
margin-bottom: 3px;
|
||||
width: 1.5vw;
|
||||
height: 1.5vw;
|
||||
border-radius: 0.3vw;
|
||||
margin-right: 0.4vw;
|
||||
margin-bottom: 0.4vh;
|
||||
}
|
||||
.minicard .minicard-labels-no-text {
|
||||
display: flex;
|
||||
@@ -128,30 +119,22 @@
|
||||
flex-grow: 1;
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
max-width: 100px;
|
||||
margin-right: 4px;
|
||||
max-width: 13vw;
|
||||
margin-right: 0.5vw;
|
||||
}
|
||||
.minicard .minicard-custom-field-item-fullwidth {
|
||||
flex-grow: 1;
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
margin-right: 4px;
|
||||
margin-right: 0.5vw;
|
||||
}
|
||||
/*
|
||||
.minicard .checklists-title,
|
||||
.minicard .add-checklist,
|
||||
.minicard .add-checklist-item,
|
||||
.minicard .checklist-details-menu {
|
||||
display: none;
|
||||
}
|
||||
*/
|
||||
.minicard .handle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 2.5vw;
|
||||
height: 2.5vw;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
right: 0.7vw;
|
||||
top: 0.7vh;
|
||||
display: none;
|
||||
}
|
||||
@media only screen {
|
||||
@@ -160,13 +143,13 @@
|
||||
}
|
||||
}
|
||||
.minicard .handle .fa-arrows {
|
||||
font-size: 20px;
|
||||
font-size: clamp(16px, 3vw, 20px);
|
||||
color: #ccc;
|
||||
}
|
||||
.minicard .minicard-title .card-number {
|
||||
color: #b3b3b3;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
margin-right: 0.7vw;
|
||||
}
|
||||
@media only screen {
|
||||
.minicard .minicard-title p:last-child {
|
||||
@@ -183,11 +166,11 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.minicard .date {
|
||||
margin-right: 3px;
|
||||
margin-right: 0.4vw;
|
||||
}
|
||||
.minicard .badges {
|
||||
float: left;
|
||||
margin-top: 7px;
|
||||
margin-top: 1vh;
|
||||
color: #808080;
|
||||
}
|
||||
.minicard .badges:empty {
|
||||
@@ -195,14 +178,14 @@
|
||||
}
|
||||
.minicard .badges .badge {
|
||||
float: left;
|
||||
margin-right: 11px;
|
||||
margin-bottom: 3px;
|
||||
margin-right: 1.5vw;
|
||||
margin-bottom: 0.4vh;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.minicard .badges .badge.is-finished {
|
||||
background: #3cb500;
|
||||
padding: 0px 3px;
|
||||
border-radius: 3px;
|
||||
padding: 0 0.4vw;
|
||||
border-radius: 0.4vw;
|
||||
color: #fff;
|
||||
}
|
||||
.minicard .badges .badge:last-of-type {
|
||||
@@ -218,41 +201,41 @@
|
||||
}
|
||||
.minicard .badges .badge .badge-text {
|
||||
font-size: 0.9em;
|
||||
padding-left: 2px;
|
||||
line-height: 14px;
|
||||
padding-left: 0.3vw;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.minicard .badges .badge .check-list-text {
|
||||
padding-left: 0px;
|
||||
line-height: 12px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.minicard .minicard-members,
|
||||
.minicard .minicard-assignees,
|
||||
.minicard .minicard-creator {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
margin-bottom: 4px;
|
||||
margin-left: 0.7vw;
|
||||
margin-bottom: 0.5vh;
|
||||
}
|
||||
.minicard .minicard-members .member,
|
||||
.minicard .minicard-assignees .member,
|
||||
.minicard .minicard-creator .member {
|
||||
float: right;
|
||||
border-radius: 50%;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
margin-bottom: 4px;
|
||||
height: 3.5vw;
|
||||
width: 3.5vw;
|
||||
margin-bottom: 0.5vh;
|
||||
}
|
||||
.minicard .minicard-members .assignee,
|
||||
.minicard .minicard-assignees .assignee,
|
||||
.minicard .minicard-creator .assignee {
|
||||
float: right;
|
||||
border-radius: 50%;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
height: 3.5vw;
|
||||
width: 3.5vw;
|
||||
}
|
||||
.minicard .minicard-members + .badges,
|
||||
.minicard .minicard-assignees + .badges,
|
||||
.minicard .minicard-creator + .badges {
|
||||
margin-top: 5px;
|
||||
margin-top: 0.7vh;
|
||||
}
|
||||
.minicard .minicard-assignees {
|
||||
border-bottom: 1px solid #f00;
|
||||
@@ -265,13 +248,13 @@
|
||||
display: none;
|
||||
}
|
||||
.minicard .minicard-description {
|
||||
padding: 6px 0 0 8px;
|
||||
padding: 0.8vh 0 0 1vw;
|
||||
color: #000;
|
||||
background-color: #eee;
|
||||
width: 100%;
|
||||
margin-bottom: 2px;
|
||||
margin-left: -4px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 0.3vh;
|
||||
margin-left: -0.5vw;
|
||||
border-radius: 0.4vw;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
@@ -279,7 +262,7 @@
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.minicard.minicard-composer {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 1.3vh;
|
||||
}
|
||||
.minicard.minicard-composer textarea.minicard-composer-textarea,
|
||||
.minicard.minicard-composer textarea.minicard-composer-textarea:focus {
|
||||
@@ -290,9 +273,9 @@
|
||||
height: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 162px;
|
||||
min-height: 36px;
|
||||
margin-bottom: 20px;
|
||||
max-height: 22vh;
|
||||
min-height: 5vh;
|
||||
margin-bottom: 2.5vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.parent-prefix {
|
||||
@@ -597,3 +580,26 @@
|
||||
.text-green {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
/* Drag and drop file upload visual feedback */
|
||||
.minicard.is-dragging-over {
|
||||
border: 2px dashed #0079bf;
|
||||
background-color: #e3f2fd !important;
|
||||
transform: scale(1.02);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* List name display on minicard */
|
||||
.minicard-list-name {
|
||||
font-size: 0.75em;
|
||||
color: #8c8c8c;
|
||||
margin-top: 0.2vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3vw;
|
||||
}
|
||||
|
||||
.minicard-list-name i.fa {
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
@@ -31,6 +31,27 @@ template(name="minicard")
|
||||
if cover
|
||||
if currentBoard.allowsCoverAttachmentOnMinicard
|
||||
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
|
||||
|
||||
// Upload progress indicator for drag-and-drop uploads
|
||||
if hasActiveUploads
|
||||
.minicard-upload-progress
|
||||
.upload-progress-header
|
||||
i.fa.fa-upload
|
||||
span {{_ 'uploading-files'}} ({{uploadCount}})
|
||||
each uploads
|
||||
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
|
||||
.upload-progress-filename {{file.name}}
|
||||
.upload-progress-bar
|
||||
.upload-progress-fill(style="width: {{progress}}%")
|
||||
if $eq status 'error'
|
||||
.upload-progress-error
|
||||
i.fa.fa-exclamation-triangle
|
||||
span {{_ 'upload-failed'}}
|
||||
else if $eq status 'completed'
|
||||
.upload-progress-success
|
||||
i.fa.fa-check
|
||||
span {{_ 'upload-completed'}}
|
||||
|
||||
.minicard-title
|
||||
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
||||
.parent-prefix
|
||||
@@ -51,12 +72,6 @@ template(name="minicard")
|
||||
span.card-number
|
||||
| ##{getCardNumber}
|
||||
= getTitle
|
||||
if $eq 'subtext-with-full-path' currentBoard.presentParentTask
|
||||
.parent-subtext
|
||||
| {{ parentString ' > ' }}
|
||||
if $eq 'subtext-with-parent' currentBoard.presentParentTask
|
||||
.parent-subtext
|
||||
| {{ parentCardName }}
|
||||
if labels
|
||||
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
|
||||
each labels
|
||||
@@ -112,12 +127,6 @@ template(name="minicard")
|
||||
+viewer
|
||||
= trueValue
|
||||
|
||||
.card-checklist-attachmentGalleries
|
||||
.card-checklist-attachmentGallery.card-checklists
|
||||
if currentBoard.allowsChecklists
|
||||
//hr
|
||||
//+checklists(cardId=_id showAtMinicard=true)
|
||||
|
||||
if showAssignee
|
||||
if getAssignees
|
||||
.minicard-assignees.js-minicard-assignees
|
||||
@@ -182,6 +191,16 @@ template(name="minicard")
|
||||
.minicard-description
|
||||
+viewer
|
||||
| {{ getDescription }}
|
||||
if shouldShowListOnMinicard
|
||||
.minicard-list-name
|
||||
i.fa.fa-list
|
||||
| {{ listName }}
|
||||
if $eq 'subtext-with-full-path' currentBoard.presentParentTask
|
||||
.parent-subtext
|
||||
| {{ parentString ' > ' }}
|
||||
if $eq 'subtext-with-parent' currentBoard.presentParentTask
|
||||
.parent-subtext
|
||||
| {{ parentCardName }}
|
||||
|
||||
template(name="editCardSortOrderPopup")
|
||||
input.js-edit-card-sort-popup(type='text' autofocus value=sort dir="auto")
|
||||
@@ -231,3 +250,12 @@ template(name="minicardDetailsActionsPopup")
|
||||
a.js-link
|
||||
i.fa.fa-link
|
||||
| {{_ 'link-card'}}
|
||||
li
|
||||
a.js-toggle-watch-card
|
||||
if isWatching
|
||||
i.fa.fa-eye
|
||||
| {{_ 'unwatch'}}
|
||||
else
|
||||
i.fa.fa-eye-slash
|
||||
| {{_ 'watch'}}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { CustomFieldStringTemplate } from '/client/lib/customFields'
|
||||
import { CustomFieldStringTemplate } from '/client/lib/customFields';
|
||||
import { handleFileUpload } from './attachments';
|
||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
|
||||
// Template.cards.events({
|
||||
// 'click .member': Popup.open('cardMember')
|
||||
@@ -46,6 +48,10 @@ BlazeComponent.extendComponent({
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
isWatching() {
|
||||
const card = this.currentData();
|
||||
return card.findWatcher(Meteor.userId());
|
||||
},
|
||||
|
||||
showMembers() {
|
||||
// cache "board" to reduce the mini-mongodb access
|
||||
@@ -88,11 +94,6 @@ BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .minicard-checklists'() {
|
||||
// Prevents clicking checklist at minicard from opening card details,
|
||||
// while still allowing checking checlist items.
|
||||
event.preventDefault();
|
||||
},
|
||||
'click .js-linked-link'() {
|
||||
if (this.data().isLinkedCard()) Utils.goCardId(this.data().linkedId);
|
||||
else if (this.data().isLinkedBoard())
|
||||
@@ -108,6 +109,59 @@ BlazeComponent.extendComponent({
|
||||
'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"),
|
||||
'click .minicard-labels' : this.cardLabelsPopup,
|
||||
'click .js-open-minicard-details-menu': Popup.open('minicardDetailsActions'),
|
||||
// Drag and drop file upload handlers
|
||||
'dragover .minicard'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
},
|
||||
'dragenter .minicard'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const card = this.data();
|
||||
const board = card.board();
|
||||
// Only allow drag-and-drop if user can modify card and board allows attachments
|
||||
if (card.canModifyCard() && board && board.allowsAttachments) {
|
||||
// Check if the drag contains files
|
||||
const dataTransfer = event.originalEvent.dataTransfer;
|
||||
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
|
||||
$(event.currentTarget).addClass('is-dragging-over');
|
||||
}
|
||||
}
|
||||
},
|
||||
'dragleave .minicard'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$(event.currentTarget).removeClass('is-dragging-over');
|
||||
},
|
||||
'drop .minicard'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$(event.currentTarget).removeClass('is-dragging-over');
|
||||
|
||||
const card = this.data();
|
||||
const board = card.board();
|
||||
|
||||
// Check permissions
|
||||
if (!card.canModifyCard() || !board || !board.allowsAttachments) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a file drop (not a card reorder)
|
||||
const dataTransfer = event.originalEvent.dataTransfer;
|
||||
if (!dataTransfer || !dataTransfer.files || dataTransfer.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the drop contains files (not just text/HTML)
|
||||
if (!dataTransfer.types.includes('Files')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = dataTransfer.files;
|
||||
if (files && files.length > 0) {
|
||||
handleFileUpload(card, files);
|
||||
}
|
||||
},
|
||||
}
|
||||
];
|
||||
},
|
||||
@@ -130,6 +184,32 @@ Template.minicard.helpers({
|
||||
? Meteor.connection._lastSessionId
|
||||
: null;
|
||||
},
|
||||
isWatching() {
|
||||
return this.findWatcher(Meteor.userId());
|
||||
},
|
||||
// Upload progress helpers
|
||||
hasActiveUploads() {
|
||||
return uploadProgressManager.hasActiveUploads(this._id);
|
||||
},
|
||||
uploads() {
|
||||
return uploadProgressManager.getUploadsForCard(this._id);
|
||||
},
|
||||
uploadCount() {
|
||||
return uploadProgressManager.getUploadCountForCard(this._id);
|
||||
},
|
||||
listName() {
|
||||
const list = this.list();
|
||||
return list ? list.title : '';
|
||||
},
|
||||
|
||||
shouldShowListOnMinicard() {
|
||||
// Show list name if either:
|
||||
// 1. Board-wide setting is enabled, OR
|
||||
// 2. This specific card has the setting enabled
|
||||
const currentBoard = this.currentBoard;
|
||||
if (!currentBoard) return false;
|
||||
return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard;
|
||||
}
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
@@ -183,4 +263,11 @@ Template.minicardDetailsActionsPopup.events({
|
||||
this.archive();
|
||||
Utils.goBoardId(this.boardId);
|
||||
}),
|
||||
'click .js-toggle-watch-card'() {
|
||||
const currentCard = this;
|
||||
const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching';
|
||||
Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => {
|
||||
if (!err && ret) Popup.back();
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -5,11 +5,11 @@ button {
|
||||
box-sizing: border-box;
|
||||
background-color: #ebebeb;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
border-radius: 0.4vw;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
min-height: 34px;
|
||||
padding: 7px;
|
||||
margin-bottom: 1.5vh;
|
||||
min-height: 4.5vh;
|
||||
padding: 1vh 1vw;
|
||||
}
|
||||
select.full,
|
||||
textarea.full,
|
||||
@@ -31,17 +31,18 @@ button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
input[type="file"] {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
input[type="radio"] {
|
||||
-webkit-appearance: radio;
|
||||
appearance: radio;
|
||||
min-height: inherit;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"] {
|
||||
transition: background 85ms ease-in, border-color 85ms ease-in;
|
||||
width: 250px;
|
||||
width: min(250px, 30vw);
|
||||
}
|
||||
input[type="text"].inline-input,
|
||||
input[type="password"].inline-input,
|
||||
@@ -49,10 +50,10 @@ input[type="email"].inline-input {
|
||||
background: none;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
padding: 0.3vh;
|
||||
min-height: 0;
|
||||
height: 18px;
|
||||
width: 200px;
|
||||
height: 2.5vh;
|
||||
width: min(200px, 25vw);
|
||||
}
|
||||
input[type="text"].full-line,
|
||||
input[type="password"].full-line,
|
||||
@@ -96,30 +97,31 @@ input[type="email"]:disabled,
|
||||
textarea:disabled {
|
||||
background-color: #dcdcdc;
|
||||
border-color: #bfbfbf;
|
||||
color: #8c8c8c;
|
||||
color: #222;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
select {
|
||||
max-height: 300px;
|
||||
width: 256px;
|
||||
margin-bottom: 8px;
|
||||
max-height: 40vh;
|
||||
width: min(256px, 32vw);
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
select.inline {
|
||||
width: 100%;
|
||||
}
|
||||
option[disabled] {
|
||||
color: #8c8c8c;
|
||||
color: #222;
|
||||
}
|
||||
textarea {
|
||||
height: 150px;
|
||||
height: 20vh;
|
||||
transition: background 85ms ease-in, border-color 85ms ease-in;
|
||||
resize: vertical;
|
||||
width: 100%;
|
||||
}
|
||||
textarea.editor {
|
||||
resize: none;
|
||||
padding-bottom: 22px;
|
||||
padding-bottom: 3vh;
|
||||
}
|
||||
.button {
|
||||
border-radius: 3px;
|
||||
@@ -134,14 +136,14 @@ button {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
padding: 7px 20px;
|
||||
line-height: 1.3;
|
||||
padding: 1vh 2.5vw;
|
||||
text-align: center;
|
||||
}
|
||||
input[type="submit"] .wide,
|
||||
button .wide {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
padding-left: 4vw;
|
||||
padding-right: 4vw;
|
||||
}
|
||||
input[type="submit"]:hover,
|
||||
button:hover,
|
||||
@@ -222,8 +224,8 @@ button.disabled:active,
|
||||
}
|
||||
fieldset {
|
||||
border: 1px solid #bfbfbf;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
padding: 2vh 2vw;
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
input[type="hidden"] {
|
||||
display: none;
|
||||
@@ -231,18 +233,18 @@ input[type="hidden"] {
|
||||
.radio-div,
|
||||
.check-div {
|
||||
display: block;
|
||||
margin: 0 0 4px 20px;
|
||||
min-height: 20px;
|
||||
margin: 0 0 0.5vh 2.5vw;
|
||||
min-height: 2.5vh;
|
||||
position: relative;
|
||||
}
|
||||
.radio-div input,
|
||||
.check-div input {
|
||||
left: -18px;
|
||||
left: -2.5vw;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
top: 0.3vh;
|
||||
}
|
||||
.radio-div label,
|
||||
.check-div label {
|
||||
@@ -251,23 +253,23 @@ input[type="hidden"] {
|
||||
label {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 0.5vh;
|
||||
}
|
||||
label.form-error {
|
||||
color: #ba1212;
|
||||
color: #d32f2f;
|
||||
}
|
||||
input::-webkit-input-placeholder,
|
||||
textarea::-webkit-input-placeholder,
|
||||
input::-moz-placeholder,
|
||||
textarea::-moz-placeholder {
|
||||
color: #8c8c8c;
|
||||
color: #333 !important;
|
||||
}
|
||||
.edit-controls,
|
||||
.add-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 1.5vh;
|
||||
}
|
||||
@media print {
|
||||
.add-controls {
|
||||
@@ -279,13 +281,13 @@ textarea::-moz-placeholder {
|
||||
.edit-controls input[type=button],
|
||||
.add-controls input[type=button] {
|
||||
float: left;
|
||||
height: 32px;
|
||||
height: 4.5vh;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.edit-controls .fa-times-thin,
|
||||
.add-controls .fa-times-thin {
|
||||
font-size: 26px;
|
||||
margin: 3px 10px;
|
||||
font-size: clamp(20px, 4vw, 26px);
|
||||
margin: 0.5vh 1.5vw;
|
||||
}
|
||||
[type="checkbox"]:not(:checked),
|
||||
[type="checkbox"]:checked {
|
||||
@@ -315,6 +317,7 @@ textarea::-moz-placeholder {
|
||||
border-left: 2px solid transparent;
|
||||
transform: rotate(40deg);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
transform-origin: 100% 100%;
|
||||
}
|
||||
.button-link {
|
||||
@@ -322,6 +325,7 @@ textarea::-moz-placeholder {
|
||||
background: linear-gradient(#fff, #f5f5f5);
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
border: 1px solid #e3e3e3;
|
||||
border-bottom-color: #c2c2c2;
|
||||
@@ -374,14 +378,14 @@ textarea::-moz-placeholder {
|
||||
.button-link.setting.disabled {
|
||||
background: #fff;
|
||||
border-color: #e9e9e9;
|
||||
color: #8c8c8c;
|
||||
color: #222;
|
||||
cursor: default;
|
||||
}
|
||||
.button-link.setting.disabled select {
|
||||
display: none;
|
||||
}
|
||||
.button-link.setting.disabled:hover .label {
|
||||
color: #8c8c8c;
|
||||
color: #222;
|
||||
}
|
||||
.button-link.setting.disabled,
|
||||
.button-link.setting.disabled:hover,
|
||||
@@ -397,7 +401,7 @@ textarea::-moz-placeholder {
|
||||
color: #a8a8a8;
|
||||
}
|
||||
.button-link.setting .label {
|
||||
color: #8c8c8c;
|
||||
color: #222;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
@@ -505,8 +509,9 @@ button.loud-text-button:hover {
|
||||
.emphasis-button,
|
||||
.quiet-button {
|
||||
border-radius: 3px;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
color: #8c8c8c;
|
||||
color: #222;
|
||||
display: block;
|
||||
margin: 2px 0;
|
||||
padding: 6px 8px;
|
||||
@@ -571,7 +576,7 @@ button.loud-text-button:hover {
|
||||
top: 6px;
|
||||
}
|
||||
.big-link.none {
|
||||
color: #8c8c8c;
|
||||
color: #222;
|
||||
text-decoration: none;
|
||||
}
|
||||
.big-link.none:hover {
|
||||
@@ -601,7 +606,7 @@ button.loud-text-button:hover {
|
||||
}
|
||||
.show-more {
|
||||
border-radius: 3px;
|
||||
color: #8c8c8c;
|
||||
color: #222;
|
||||
display: block;
|
||||
padding: 16px 8px 16px 40px;
|
||||
margin: 8px 0;
|
||||
|
||||
Vendored
+8
-8
@@ -5,17 +5,17 @@
|
||||
.map-members .mapping-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -4px;
|
||||
margin: 0 -0.5vw;
|
||||
}
|
||||
.map-members .mapping-list .mapping-item {
|
||||
max-width: 300px;
|
||||
min-width: 200px;
|
||||
padding: 6px;
|
||||
margin: 5px;
|
||||
max-width: min(300px, 40vw);
|
||||
min-width: min(200px, 26vw);
|
||||
padding: 0.8vh 0.8vw;
|
||||
margin: 0.7vh;
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
||||
border-radius: 0.4vw;
|
||||
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.15);
|
||||
}
|
||||
.map-members .mapping-list .mapping-item:hover {
|
||||
background: #f2f2f2;
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
.map-members .mapping-list .wekan {
|
||||
display: inline-block;
|
||||
width: 35px;
|
||||
width: 4.5vw;
|
||||
}
|
||||
.map-members .mapping-list .wekan .member {
|
||||
float: none;
|
||||
|
||||
@@ -8,11 +8,228 @@
|
||||
padding: 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* List resize handle */
|
||||
.list-resize-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -3px;
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
z-index: 10;
|
||||
background: transparent;
|
||||
transition: background-color 0.2s ease;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.list-resize-handle:hover {
|
||||
background: rgba(0, 123, 255, 0.4);
|
||||
box-shadow: 0 0 4px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.list-resize-handle:active {
|
||||
background: rgba(0, 123, 255, 0.6);
|
||||
box-shadow: 0 0 6px rgba(0, 123, 255, 0.4);
|
||||
}
|
||||
|
||||
/* Show resize handle only on hover */
|
||||
.list:hover .list-resize-handle {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.list:hover .list-resize-handle:hover {
|
||||
background: rgba(0, 123, 255, 0.4);
|
||||
box-shadow: 0 0 4px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Add a subtle indicator line */
|
||||
.list-resize-handle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 1px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.list-resize-handle:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Disable resize handle for collapsed lists and mobile view */
|
||||
.list.list-collapsed .list-resize-handle,
|
||||
.list.mobile-view .list-resize-handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Disable resize handle for auto-width lists */
|
||||
.list.list-auto-width .list-resize-handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Visual feedback during resize */
|
||||
.list.list-resizing {
|
||||
transition: none !important;
|
||||
box-shadow: 0 0 10px rgba(0, 123, 255, 0.3);
|
||||
/* Ensure the list maintains its new width during resize */
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
/* Override any conflicting layout properties */
|
||||
float: left !important;
|
||||
display: block !important;
|
||||
position: relative !important;
|
||||
/* Force width to be respected */
|
||||
width: var(--list-width, auto) !important;
|
||||
min-width: var(--list-width, auto) !important;
|
||||
max-width: var(--list-width, auto) !important;
|
||||
}
|
||||
|
||||
body.list-resizing-active {
|
||||
cursor: col-resize !important;
|
||||
}
|
||||
|
||||
body.list-resizing-active * {
|
||||
cursor: col-resize !important;
|
||||
}
|
||||
|
||||
/* Ensure swimlane container doesn't interfere with list resizing */
|
||||
.swimlane .list.list-resizing {
|
||||
/* Override any swimlane flex properties */
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
/* Ensure width is respected */
|
||||
width: var(--list-width, auto) !important;
|
||||
min-width: var(--list-width, auto) !important;
|
||||
max-width: var(--list-width, auto) !important;
|
||||
}
|
||||
|
||||
/* More aggressive override for any container that might interfere */
|
||||
.js-swimlane .list.list-resizing,
|
||||
.dragscroll .list.list-resizing,
|
||||
[id^="swimlane-"] .list.list-resizing {
|
||||
/* Force the width to be applied */
|
||||
width: var(--list-width, auto) !important;
|
||||
min-width: var(--list-width, auto) !important;
|
||||
max-width: var(--list-width, auto) !important;
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
float: left !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Ensure the width persists after resize is complete */
|
||||
.js-swimlane .list[style*="--list-width"],
|
||||
.dragscroll .list[style*="--list-width"],
|
||||
[id^="swimlane-"] .list[style*="--list-width"] {
|
||||
/* Maintain the width after resize */
|
||||
width: var(--list-width, auto) !important;
|
||||
min-width: var(--list-width, auto) !important;
|
||||
max-width: var(--list-width, auto) !important;
|
||||
flex: none !important;
|
||||
flex-basis: auto !important;
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
float: left !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Ensure consistent header height for all lists */
|
||||
.list-header {
|
||||
/* Maintain consistent height and padding for all lists */
|
||||
min-height: 2.5vh !important;
|
||||
height: auto !important;
|
||||
padding: 2.5vh 1.5vw 0.5vh !important;
|
||||
/* Make sure the background covers the full height */
|
||||
background-color: #e4e4e4 !important;
|
||||
border-bottom: 0.8vh solid #e4e4e4 !important;
|
||||
/* Use original display for consistent button positioning */
|
||||
display: block !important;
|
||||
position: relative !important;
|
||||
/* Prevent vertical expansion but allow normal height */
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Ensure title text doesn't cause height changes for all lists */
|
||||
.list-header .list-header-name {
|
||||
/* Prevent text wrapping to maintain consistent height */
|
||||
white-space: nowrap !important;
|
||||
/* Truncate text with ellipsis if too long */
|
||||
text-overflow: ellipsis !important;
|
||||
/* Ensure proper line height */
|
||||
line-height: 1.2 !important;
|
||||
/* Ensure it doesn't overflow */
|
||||
overflow: hidden !important;
|
||||
/* Add margin to prevent overlap with buttons */
|
||||
margin-right: 120px !important;
|
||||
}
|
||||
|
||||
/* Position drag handle at top-right corner for ALL lists */
|
||||
.list-header .list-header-handle {
|
||||
/* Position at top-right corner, aligned with title text top */
|
||||
position: absolute !important;
|
||||
top: 2.5vh !important;
|
||||
right: 1.5vw !important;
|
||||
/* Ensure it's above other elements */
|
||||
z-index: 15 !important;
|
||||
/* Remove margin since it's absolutely positioned */
|
||||
margin-right: 0 !important;
|
||||
/* Ensure proper display */
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* Ensure buttons maintain original positioning */
|
||||
.js-swimlane .list[style*="--list-width"] .list-header .list-header-plus-top,
|
||||
.js-swimlane .list[style*="--list-width"] .list-header .js-collapse,
|
||||
.js-swimlane .list[style*="--list-width"] .list-header .js-open-list-menu,
|
||||
.dragscroll .list[style*="--list-width"] .list-header .list-header-plus-top,
|
||||
.dragscroll .list[style*="--list-width"] .list-header .js-collapse,
|
||||
.dragscroll .list[style*="--list-width"] .list-header .js-open-list-menu,
|
||||
[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-plus-top,
|
||||
[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-collapse,
|
||||
[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-open-list-menu {
|
||||
/* Use original positioning to maintain layout */
|
||||
position: relative !important;
|
||||
/* Maintain original spacing */
|
||||
margin-right: 15px !important;
|
||||
/* Ensure proper display */
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* Ensure watch icon and card count maintain original positioning */
|
||||
.js-swimlane .list[style*="--list-width"] .list-header .list-header-watch-icon,
|
||||
.dragscroll .list[style*="--list-width"] .list-header .list-header-watch-icon,
|
||||
[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-watch-icon,
|
||||
.js-swimlane .list[style*="--list-width"] .list-header .cardCount,
|
||||
.dragscroll .list[style*="--list-width"] .list-header .cardCount,
|
||||
[id^="swimlane-"] .list[style*="--list-width"] .list-header .cardCount {
|
||||
/* Use original positioning to maintain layout */
|
||||
position: relative !important;
|
||||
/* Maintain original spacing */
|
||||
margin-right: 15px !important;
|
||||
/* Ensure proper display */
|
||||
display: inline-block !important;
|
||||
}
|
||||
[id^="swimlane-"] .list:first-child {
|
||||
min-width: 20px;
|
||||
min-width: 2.5vw;
|
||||
}
|
||||
.list.list-auto-width {
|
||||
flex: 1;
|
||||
}
|
||||
.list:first-child {
|
||||
border-left: none;
|
||||
flex: none;
|
||||
}
|
||||
.card-details + .list {
|
||||
border-left: none;
|
||||
@@ -29,7 +246,10 @@
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
height: 100px;
|
||||
height: 15vh;
|
||||
}
|
||||
.list.list-collapsed {
|
||||
flex: none;
|
||||
}
|
||||
.list.list-composer .open-list-composer,
|
||||
.list .list-composer .open-list-composer {
|
||||
@@ -38,24 +258,24 @@
|
||||
.list.list-composer .list-name-input,
|
||||
.list .list-composer .list-name-input {
|
||||
background: #fff;
|
||||
margin: -3px 0 8px;
|
||||
margin: -0.4vh 0 1vh;
|
||||
}
|
||||
.list-header-add {
|
||||
flex: 0 0 auto;
|
||||
padding: 12px;
|
||||
padding: 1.5vh 1.5vw;
|
||||
position: relative;
|
||||
min-height: 20px;
|
||||
min-height: 2.5vh;
|
||||
}
|
||||
.list-header {
|
||||
flex: 0 0 auto;
|
||||
padding: 20px 12px 4px;
|
||||
padding: 2.5vh 1.5vw 0.5vh;
|
||||
position: relative;
|
||||
min-height: 20px;
|
||||
min-height: 2.5vh;
|
||||
background-color: #e4e4e4;
|
||||
border-bottom: 6px solid #e4e4e4;
|
||||
border-bottom: 0.8vh solid #e4e4e4;
|
||||
}
|
||||
.list-header.list-header-card-count {
|
||||
min-height: 35px;
|
||||
min-height: 4.5vh;
|
||||
height: auto;
|
||||
}
|
||||
.list-header.ui-sortable-handle {
|
||||
@@ -66,21 +286,21 @@
|
||||
}
|
||||
.list-header .list-header-name {
|
||||
display: inline;
|
||||
font-size: 16px;
|
||||
line-height: 17px;
|
||||
font-size: clamp(14px, 3vw, 18px);
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
min-height: 9px;
|
||||
min-width: 30px;
|
||||
min-height: 1.2vh;
|
||||
min-width: 4vw;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.list-rotated {
|
||||
width: 10px;
|
||||
height: 250px;
|
||||
margin-top: -90px;
|
||||
margin-left: -110px;
|
||||
width: 1.3vw;
|
||||
height: 35vh;
|
||||
margin-top: -12vh;
|
||||
margin-left: -14vw;
|
||||
margin-right: 0;
|
||||
transform: rotate(90deg);
|
||||
position: relative;
|
||||
@@ -207,11 +427,137 @@
|
||||
#js-list-width-edit .list-width-error {
|
||||
display: none;
|
||||
}
|
||||
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
|
||||
.mini-list.mobile-view {
|
||||
flex: 0 0 60px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
border-left: 0px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.list.mobile-view {
|
||||
display: contents;
|
||||
flex-basis: auto;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
border-left: 0px;
|
||||
}
|
||||
.list.mobile-view:first-child {
|
||||
margin-left: 0px;
|
||||
}
|
||||
.list.mobile-view.ui-sortable-helper {
|
||||
flex: 0 0 60px;
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
border-left: 0px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.list.mobile-view.ui-sortable-helper .list-header.ui-sortable-handle {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.list.mobile-view.placeholder {
|
||||
flex: 0 0 60px;
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
border-left: 0px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.list.mobile-view .list-body {
|
||||
padding: 15px 19px;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
.list.mobile-view .list-header {
|
||||
/*Updated padding values for mobile devices, this should fix text grouping issue*/
|
||||
padding: 20px 0px 20px 0px;
|
||||
border-bottom: 0px solid #e4e4e4;
|
||||
min-height: 30px;
|
||||
margin-top: 10px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
/* Force grid layout for iPhone */
|
||||
display: grid !important;
|
||||
grid-template-columns: 30px 1fr auto auto !important;
|
||||
gap: 10px !important;
|
||||
}
|
||||
.list.mobile-view .list-header .list-header-left-icon {
|
||||
padding: 7px;
|
||||
padding-right: 27px;
|
||||
margin-top: 1px;
|
||||
top: -7px;
|
||||
left: -7px;
|
||||
}
|
||||
.list.mobile-view .list-header .list-header-menu-icon {
|
||||
padding: 14px;
|
||||
font-size: 40px !important;
|
||||
text-align: center;
|
||||
/* Force positioning for iPhone */
|
||||
position: absolute !important;
|
||||
right: 60px !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
z-index: 10;
|
||||
}
|
||||
.list.mobile-view .list-header .list-header-handle {
|
||||
padding: 14px;
|
||||
font-size: 48px !important;
|
||||
text-align: center;
|
||||
/* Force positioning for iPhone */
|
||||
position: absolute !important;
|
||||
right: 10px !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
z-index: 10;
|
||||
}
|
||||
.list.mobile-view .list-header .list-header-left-icon {
|
||||
display: grid;
|
||||
grid-row: 1/3;
|
||||
grid-column: 1;
|
||||
}
|
||||
.list.mobile-view .list-header .list-header-name {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
align-self: end;
|
||||
font-size: 20px !important;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
.list.mobile-view .list-header .cardCount {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
align-self: start;
|
||||
font-size: 16px !important;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.list.mobile-view .list-header .list-header-menu {
|
||||
grid-row: 1/3;
|
||||
grid-column: 3;
|
||||
}
|
||||
.list.mobile-view .list-header .list-header-menu-icon {
|
||||
grid-row: 1/3;
|
||||
grid-column: 3;
|
||||
}
|
||||
.list.mobile-view .list-header .list-header-handle {
|
||||
grid-row: 1/3;
|
||||
grid-column: 4;
|
||||
}
|
||||
.list.mobile-view .list-header .inlined-form {
|
||||
grid-row: 1/3;
|
||||
grid-column: 1/4;
|
||||
}
|
||||
.list.mobile-view .list-header .edit-controls {
|
||||
align-items: initial;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.mini-list {
|
||||
flex: 0 0 60px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
border-left: 0px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
@@ -219,6 +565,7 @@
|
||||
display: contents;
|
||||
flex-basis: auto;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
border-left: 0px;
|
||||
}
|
||||
.list:first-child {
|
||||
@@ -243,6 +590,8 @@
|
||||
}
|
||||
.list-body {
|
||||
padding: 15px 19px;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
.list-header {
|
||||
/*Updated padding values for mobile devices, this should fix text grouping issue*/
|
||||
@@ -251,6 +600,8 @@
|
||||
min-height: 30px;
|
||||
margin-top: 10px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
.list-header .list-header-left-icon {
|
||||
padding: 7px;
|
||||
@@ -260,24 +611,29 @@
|
||||
left: -7px;
|
||||
}
|
||||
.list-header .list-header-menu-icon {
|
||||
padding: 14px;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
/* iOS Safari fallback positioning */
|
||||
position: absolute;
|
||||
padding: 7px;
|
||||
right: 60px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 47px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.list-header .list-header-handle {
|
||||
padding: 14px;
|
||||
font-size: 48px;
|
||||
text-align: center;
|
||||
/* iOS Safari fallback positioning */
|
||||
position: absolute;
|
||||
padding: 7px;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 10px;
|
||||
font-size: 24px;
|
||||
}
|
||||
.list-header {
|
||||
display: grid;
|
||||
grid-template-columns: 30px 5fr 1fr;
|
||||
grid-template-columns: 30px 1fr auto auto;
|
||||
gap: 10px;
|
||||
}
|
||||
.list-header .list-header-left-icon {
|
||||
display: grid;
|
||||
@@ -288,16 +644,30 @@
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
align-self: end;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
.list-header .cardCount {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
align-self: start;
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.list-header .list-header-menu {
|
||||
grid-row: 1/3;
|
||||
grid-column: 3;
|
||||
}
|
||||
.list-header .list-header-menu-icon {
|
||||
grid-row: 1/3;
|
||||
grid-column: 3;
|
||||
}
|
||||
.list-header .list-header-handle {
|
||||
grid-row: 1/3;
|
||||
grid-column: 4;
|
||||
}
|
||||
.list-header .inlined-form {
|
||||
grid-row: 1/3;
|
||||
grid-column: 1/4;
|
||||
@@ -306,6 +676,132 @@
|
||||
align-items: initial;
|
||||
}
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini specific - fix icon positioning in stacked lists view */
|
||||
@media screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
|
||||
screen and (max-width: 375px) and (max-height: 812px), /* iPhone 12 Mini viewport */
|
||||
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 375px) /* iPhone 12 Mini Retina */ {
|
||||
.list.mobile-view .list-header {
|
||||
/* Force grid layout for iPhone 12 Mini */
|
||||
display: grid !important;
|
||||
grid-template-columns: 30px 1fr auto auto !important;
|
||||
gap: 10px !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.list.mobile-view .list-header .list-header-menu-icon {
|
||||
/* Remove absolute positioning for iPhone 12 Mini */
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
/* Use grid positioning */
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 3 !important;
|
||||
padding: 14px !important;
|
||||
font-size: 40px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.list.mobile-view .list-header .list-header-handle {
|
||||
/* Remove absolute positioning for iPhone 12 Mini */
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
/* Use grid positioning */
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 4 !important;
|
||||
padding: 14px !important;
|
||||
font-size: 48px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.list.mobile-view .list-header .list-header-name {
|
||||
grid-row: 1 !important;
|
||||
grid-column: 2 !important;
|
||||
align-self: end !important;
|
||||
font-size: 20px !important;
|
||||
font-weight: bold !important;
|
||||
line-height: 1.2 !important;
|
||||
padding-bottom: 2px !important;
|
||||
}
|
||||
|
||||
.list.mobile-view .list-header .cardCount {
|
||||
grid-row: 2 !important;
|
||||
grid-column: 2 !important;
|
||||
align-self: start !important;
|
||||
font-size: 16px !important;
|
||||
line-height: 1.2 !important;
|
||||
}
|
||||
|
||||
.list.mobile-view .list-header .list-header-left-icon {
|
||||
display: grid !important;
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* iPhone device JavaScript detection fallback - fix icon positioning */
|
||||
.iphone-device .list.mobile-view .list-header {
|
||||
/* Force grid layout for iPhone devices */
|
||||
display: grid !important;
|
||||
grid-template-columns: 30px 1fr auto auto !important;
|
||||
gap: 10px !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.iphone-device .list.mobile-view .list-header .list-header-menu-icon {
|
||||
/* Remove absolute positioning for iPhone devices */
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
/* Use grid positioning */
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 3 !important;
|
||||
padding: 14px !important;
|
||||
font-size: 40px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.iphone-device .list.mobile-view .list-header .list-header-handle {
|
||||
/* Remove absolute positioning for iPhone devices */
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
/* Use grid positioning */
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 4 !important;
|
||||
padding: 14px !important;
|
||||
font-size: 48px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.iphone-device .list.mobile-view .list-header .list-header-name {
|
||||
grid-row: 1 !important;
|
||||
grid-column: 2 !important;
|
||||
align-self: end !important;
|
||||
font-size: 20px !important;
|
||||
font-weight: bold !important;
|
||||
line-height: 1.2 !important;
|
||||
padding-bottom: 2px !important;
|
||||
}
|
||||
|
||||
.iphone-device .list.mobile-view .list-header .cardCount {
|
||||
grid-row: 2 !important;
|
||||
grid-column: 2 !important;
|
||||
align-self: start !important;
|
||||
font-size: 16px !important;
|
||||
line-height: 1.2 !important;
|
||||
}
|
||||
|
||||
.iphone-device .list.mobile-view .list-header .list-header-left-icon {
|
||||
display: grid !important;
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 1 !important;
|
||||
}
|
||||
.link-board-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
template(name='list')
|
||||
.list.js-list(id="js-list-{{_id}}"
|
||||
style="{{#unless collapsed}}width:{{listWidth}}px;{{/unless}}"
|
||||
class="{{#if collapsed}}list-collapsed{{/if}}")
|
||||
style="{{#unless collapsed}}min-width:{{listWidth}}px;max-width:{{listConstraint}}px;{{/unless}}"
|
||||
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
+listHeader
|
||||
+listBody
|
||||
.list-resize-handle.js-list-resize-handle
|
||||
|
||||
template(name='miniList')
|
||||
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}")
|
||||
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}" class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||
+listHeader
|
||||
@@ -24,6 +24,9 @@ BlazeComponent.extendComponent({
|
||||
onRendered() {
|
||||
const boardComponent = this.parentComponent().parentComponent();
|
||||
|
||||
// Initialize list resize functionality
|
||||
this.initializeListResize();
|
||||
|
||||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||
const $cards = this.$('.js-minicards');
|
||||
|
||||
@@ -200,6 +203,158 @@ BlazeComponent.extendComponent({
|
||||
const list = Template.currentData();
|
||||
return user.getListWidth(list.boardId, list._id);
|
||||
},
|
||||
|
||||
listConstraint() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
return user.getListConstraint(list.boardId, list._id);
|
||||
},
|
||||
|
||||
autoWidth() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
return user.isAutoWidth(list.boardId);
|
||||
},
|
||||
|
||||
initializeListResize() {
|
||||
const list = Template.currentData();
|
||||
const $list = this.$('.js-list');
|
||||
const $resizeHandle = this.$('.js-list-resize-handle');
|
||||
|
||||
// Only enable resize for non-collapsed, non-auto-width lists
|
||||
if (list.collapsed || this.autoWidth()) {
|
||||
$resizeHandle.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let isResizing = false;
|
||||
let startX = 0;
|
||||
let startWidth = 0;
|
||||
let minWidth = 100; // Minimum width as defined in the existing code
|
||||
let maxWidth = this.listConstraint() || 1000; // Use constraint as max width
|
||||
let listConstraint = this.listConstraint(); // Store constraint value for use in event handlers
|
||||
const component = this; // Store reference to component for use in event handlers
|
||||
|
||||
const startResize = (e) => {
|
||||
isResizing = true;
|
||||
startX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
startWidth = $list.outerWidth();
|
||||
|
||||
// Add visual feedback
|
||||
$list.addClass('list-resizing');
|
||||
$('body').addClass('list-resizing-active');
|
||||
|
||||
// Prevent text selection during resize
|
||||
$('body').css('user-select', 'none');
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const doResize = (e) => {
|
||||
if (!isResizing) return;
|
||||
|
||||
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
const deltaX = currentX - startX;
|
||||
const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
|
||||
|
||||
// Apply the new width immediately for real-time feedback using CSS custom properties
|
||||
$list[0].style.setProperty('--list-width', `${newWidth}px`);
|
||||
$list.css({
|
||||
'width': `${newWidth}px`,
|
||||
'min-width': `${newWidth}px`,
|
||||
'max-width': `${newWidth}px`,
|
||||
'flex': 'none',
|
||||
'flex-basis': 'auto',
|
||||
'flex-grow': '0',
|
||||
'flex-shrink': '0'
|
||||
});
|
||||
|
||||
// Debug: log the width change
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Resizing list to ${newWidth}px`);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const stopResize = (e) => {
|
||||
if (!isResizing) return;
|
||||
|
||||
isResizing = false;
|
||||
|
||||
// Calculate final width
|
||||
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
const deltaX = currentX - startX;
|
||||
const finalWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
|
||||
|
||||
// Ensure the final width is applied using CSS custom properties
|
||||
$list[0].style.setProperty('--list-width', `${finalWidth}px`);
|
||||
$list.css({
|
||||
'width': `${finalWidth}px`,
|
||||
'min-width': `${finalWidth}px`,
|
||||
'max-width': `${finalWidth}px`,
|
||||
'flex': 'none',
|
||||
'flex-basis': 'auto',
|
||||
'flex-grow': '0',
|
||||
'flex-shrink': '0'
|
||||
});
|
||||
|
||||
// Remove visual feedback but keep the width
|
||||
$list.removeClass('list-resizing');
|
||||
$('body').removeClass('list-resizing-active');
|
||||
$('body').css('user-select', '');
|
||||
|
||||
// Keep the CSS custom property for persistent width
|
||||
// The CSS custom property will remain on the element to maintain the width
|
||||
|
||||
// Save the new width using the existing system
|
||||
const boardId = list.boardId;
|
||||
const listId = list._id;
|
||||
|
||||
// Use the same method as the hamburger menu
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Saving list width: ${finalWidth}px for list ${listId}`);
|
||||
}
|
||||
Meteor.call('applyListWidth', boardId, listId, finalWidth, listConstraint, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Error saving list width:', error);
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('List width saved successfully:', result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// Mouse events
|
||||
$resizeHandle.on('mousedown', startResize);
|
||||
$(document).on('mousemove', doResize);
|
||||
$(document).on('mouseup', stopResize);
|
||||
|
||||
// Touch events for mobile
|
||||
$resizeHandle.on('touchstart', startResize);
|
||||
$(document).on('touchmove', doResize);
|
||||
$(document).on('touchend', stopResize);
|
||||
|
||||
// Reactively update resize handle visibility when auto-width changes
|
||||
component.autorun(() => {
|
||||
if (component.autoWidth()) {
|
||||
$resizeHandle.hide();
|
||||
} else {
|
||||
$resizeHandle.show();
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up on component destruction
|
||||
component.onDestroyed(() => {
|
||||
$(document).off('mousemove', doResize);
|
||||
$(document).off('mouseup', stopResize);
|
||||
$(document).off('touchmove', doResize);
|
||||
$(document).off('touchend', stopResize);
|
||||
});
|
||||
},
|
||||
}).register('list');
|
||||
|
||||
Template.miniList.events({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
template(name="listBody")
|
||||
unless collapsed
|
||||
.list-body
|
||||
.list-body(class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}")
|
||||
.minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}")
|
||||
if cards.length
|
||||
+inlinedForm(autoclose=false position="top")
|
||||
|
||||
@@ -231,6 +231,11 @@ BlazeComponent.extendComponent({
|
||||
);
|
||||
},
|
||||
|
||||
isVerticalScrollbars() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
cardDetailsPopup(event) {
|
||||
if (!Popup.isOpen()) {
|
||||
Popup.open("cardDetails")(event);
|
||||
@@ -309,7 +314,8 @@ BlazeComponent.extendComponent({
|
||||
// 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();
|
||||
// Prevent custom focus movement on Tab key for accessibility
|
||||
// evt.preventDefault();
|
||||
const isReverse = evt.shiftKey;
|
||||
const list = $(`#js-list-${this.data().listId}`);
|
||||
const listSelector = '.js-list:not(.js-list-composer)';
|
||||
@@ -408,13 +414,16 @@ BlazeComponent.extendComponent({
|
||||
// or `Enter` to validation the auto-completion. We also need to stop the
|
||||
// event propagation to prevent the card from submitting (on `Enter`) or
|
||||
// going on the next column (on `Tab`).
|
||||
/*
|
||||
onKeydown(evt, commands) {
|
||||
if (evt.keyCode === 9 || evt.keyCode === 13) {
|
||||
evt.stopPropagation();
|
||||
return commands.KEY_ENTER;
|
||||
}
|
||||
// Prevent custom focus movement on Tab key for accessibility
|
||||
// if (evt.keyCode === 9 || evt.keyCode === 13) {
|
||||
// evt.stopPropagation();
|
||||
// return commands.KEY_ENTER;
|
||||
//}
|
||||
return null;
|
||||
},
|
||||
*/
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -463,6 +472,14 @@ BlazeComponent.extendComponent({
|
||||
if (!this.selectedBoardId.get()) {
|
||||
return [];
|
||||
}
|
||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
||||
if (!board) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Ensure default swimlane exists
|
||||
board.getDefaultSwimline();
|
||||
|
||||
const swimlanes = ReactiveCache.getSwimlanes(
|
||||
{
|
||||
boardId: this.selectedBoardId.get()
|
||||
|
||||
@@ -191,8 +191,14 @@ template(name="setListWidthPopup")
|
||||
label {{_ 'set-list-width-value'}}
|
||||
p
|
||||
input.list-width-value(type="number" value="{{ listWidthValue }}" min="100")
|
||||
input.list-constraint-value(type="number" value="{{ listConstraintValue }}" min="100")
|
||||
input.list-width-apply(type="submit" value="{{_ 'apply'}}")
|
||||
input.list-width-error
|
||||
br
|
||||
a.js-auto-width-board(
|
||||
title="{{#if isAutoWidth}}{{_ 'click-to-disable-auto-width'}}{{else}}{{_ 'click-to-enable-auto-width'}}{{/if}}")
|
||||
i.fa(class="fa-solid fa-{{#if isAutoWidth}}compress{{else}}expand{{/if}}")
|
||||
span {{_ 'auto-list-width'}}
|
||||
|
||||
template(name="listWidthErrorPopup")
|
||||
.list-width-invalid
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
|
||||
let listsColors;
|
||||
Meteor.startup(() => {
|
||||
@@ -155,7 +156,7 @@ Template.listActionPopup.helpers({
|
||||
|
||||
isWatching() {
|
||||
return this.findWatcher(Meteor.userId());
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Template.listActionPopup.events({
|
||||
@@ -347,14 +348,20 @@ BlazeComponent.extendComponent({
|
||||
.val(),
|
||||
10,
|
||||
);
|
||||
const constraint = parseInt(
|
||||
Template.instance()
|
||||
.$('.list-constraint-value')
|
||||
.val(),
|
||||
10,
|
||||
);
|
||||
|
||||
// FIXME(mark-i-m): where do we put constants?
|
||||
if (width < 100 || !width) {
|
||||
if (width < 100 || !width || constraint < 100 || !constraint) {
|
||||
Template.instance()
|
||||
.$('.list-width-error')
|
||||
.click();
|
||||
} else {
|
||||
Meteor.call('applyListWidth', board, list._id, width);
|
||||
Meteor.call('applyListWidth', board, list._id, width, constraint);
|
||||
Popup.back();
|
||||
}
|
||||
},
|
||||
@@ -365,9 +372,25 @@ BlazeComponent.extendComponent({
|
||||
return ReactiveCache.getCurrentUser().getListWidth(board, list._id);
|
||||
},
|
||||
|
||||
listConstraintValue() {
|
||||
const list = Template.currentData();
|
||||
const board = list.boardId;
|
||||
return ReactiveCache.getCurrentUser().getListConstraint(board, list._id);
|
||||
},
|
||||
|
||||
isAutoWidth() {
|
||||
const boardId = Utils.getCurrentBoardId();
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isAutoWidth(boardId);
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-auto-width-board'() {
|
||||
dragscroll.reset();
|
||||
ReactiveCache.getCurrentUser().toggleAutoWidth(Utils.getCurrentBoardId());
|
||||
},
|
||||
'click .list-width-apply': this.applyListWidth,
|
||||
'click .list-width-error': Popup.open('listWidthError'),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
.my-cards-board-wrapper {
|
||||
border-radius: 0 0 0.5vw 0.5vw;
|
||||
min-width: min(400px, 52vw);
|
||||
margin-bottom: 2.5vh;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
border-width: 0.3vw;
|
||||
border-style: solid;
|
||||
border-color: #a2a2a2;
|
||||
}
|
||||
.my-cards-board-title {
|
||||
font-size: clamp(1.2rem, 3vw, 1.6rem);
|
||||
font-weight: bold;
|
||||
padding: 0.7vh 0.7vw;
|
||||
background-color: #808080;
|
||||
color: #fff;
|
||||
}
|
||||
.my-cards-swimlane-title {
|
||||
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||
font-weight: bold;
|
||||
padding: 0.7vh 0.7vw;
|
||||
padding-bottom: 0.5vh;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.7vh;
|
||||
text-align: center;
|
||||
}
|
||||
.swimlane-default-color {
|
||||
background-color: #d3d3d3;
|
||||
}
|
||||
.my-cards-list-title {
|
||||
font-weight: bold;
|
||||
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||
text-align: center;
|
||||
margin-bottom: 0.9vh;
|
||||
}
|
||||
.my-cards-list-wrapper {
|
||||
margin: 1.3vh 1.3vw;
|
||||
border-radius: 0.7vw;
|
||||
display: inline-grid;
|
||||
min-width: min(250px, 32vw);
|
||||
max-width: min(350px, 45vw);
|
||||
}
|
||||
.my-cards-card-wrapper {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.3vh;
|
||||
}
|
||||
.my-cards-dueat-list-wrapper {
|
||||
max-width: min(500px, 65vw);
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
.my-cards-board-table thead {
|
||||
border-bottom: 3px solid #4d4d4d;
|
||||
background-color: transparent;
|
||||
}
|
||||
.my-cards-board-table th,
|
||||
.my-cards-board-table td {
|
||||
border: 0;
|
||||
}
|
||||
.my-cards-board-table tr {
|
||||
border-bottom: 2px solid #a2a2a2;
|
||||
}
|
||||
.my-cards-card-title-table {
|
||||
font-weight: bold;
|
||||
padding-left: 2px;
|
||||
max-width: 243px;
|
||||
}
|
||||
.my-cards-board-badge {
|
||||
width: 36px;
|
||||
height: 24px;
|
||||
float: left;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Accessibility page styles */
|
||||
.accessibility-page {
|
||||
padding: 20px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.accessibility-page h2 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
|
||||
.accessibility-page-content {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.accessibility-page-content p {
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
template(name="accessibilityHeaderBar")
|
||||
if currentUser
|
||||
h1
|
||||
if isAccessibilityEnabled
|
||||
= accessibilityTitle
|
||||
else
|
||||
| {{_ 'accessibility'}}
|
||||
|
||||
template(name="accessibility")
|
||||
if currentUser
|
||||
.accessibility-page
|
||||
if isAccessibilityEnabled
|
||||
.accessibility-page-content
|
||||
+viewer
|
||||
| {{accessibilityContent}}
|
||||
else
|
||||
.accessibility-page-content
|
||||
| {{_ 'accessibility-info-not-added-yet'}}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
|
||||
// Shared helpers for both accessibility templates
|
||||
const accessibilityHelpers = {
|
||||
accessibilityTitle() {
|
||||
const setting = AccessibilitySettings.findOne({});
|
||||
return setting && setting.title ? setting.title : TAPi18n.__('accessibility-title');
|
||||
},
|
||||
accessibilityContent() {
|
||||
const setting = AccessibilitySettings.findOne({});
|
||||
return setting && setting.body ? setting.body : TAPi18n.__('accessibility-content');
|
||||
},
|
||||
isAccessibilityEnabled() {
|
||||
const setting = AccessibilitySettings.findOne({});
|
||||
return setting && setting.enabled;
|
||||
}
|
||||
};
|
||||
|
||||
// Main accessibility page component
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.error = new ReactiveVar('');
|
||||
this.loading = new ReactiveVar(false);
|
||||
|
||||
Meteor.subscribe('setting');
|
||||
Meteor.subscribe('accessibilitySettings');
|
||||
},
|
||||
...accessibilityHelpers
|
||||
}).register('accessibility');
|
||||
|
||||
// Header bar component
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
Meteor.subscribe('accessibilitySettings');
|
||||
},
|
||||
...accessibilityHelpers
|
||||
}).register('accessibilityHeaderBar');
|
||||
@@ -0,0 +1,29 @@
|
||||
template(name="bookmarks")
|
||||
.panel
|
||||
h2 {{_ 'bookmarks'}}
|
||||
if currentUser
|
||||
if hasStarredBoards
|
||||
ul
|
||||
each starredBoards
|
||||
li
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")= title
|
||||
a.js-toggle-star(title="{{_ 'star-board-short-unstar'}}")
|
||||
i.fa.fa-star
|
||||
else
|
||||
p {{_ 'no-starred-boards'}}
|
||||
else
|
||||
p {{_ 'please-sign-in'}}
|
||||
|
||||
// Desktop popup
|
||||
template(name="bookmarksPopup")
|
||||
ul.pop-over-list
|
||||
if hasStarredBoards
|
||||
each starredBoards
|
||||
li
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
i.fa.fa-star
|
||||
| #{title}
|
||||
a.js-toggle-star.right(title="{{_ 'star-board-short-unstar'}}")
|
||||
i.fa.fa-star
|
||||
else
|
||||
li {{_ 'no-starred-boards'}}
|
||||
@@ -0,0 +1,55 @@
|
||||
Template.bookmarks.helpers({
|
||||
hasStarredBoards() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (!user) return false;
|
||||
const { starredBoards = [] } = user.profile || {};
|
||||
return Array.isArray(starredBoards) && starredBoards.length > 0;
|
||||
},
|
||||
starredBoards() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (!user) return [];
|
||||
const { starredBoards = [] } = user.profile || {};
|
||||
if (!Array.isArray(starredBoards) || starredBoards.length === 0) return [];
|
||||
return Boards.find({ _id: { $in: starredBoards } }, { sort: { sort: 1 } });
|
||||
},
|
||||
});
|
||||
|
||||
Template.bookmarks.events({
|
||||
'click .js-toggle-star'(e) {
|
||||
e.preventDefault();
|
||||
const boardId = this._id;
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user && boardId) {
|
||||
user.toggleBoardStar(boardId);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.bookmarksPopup.helpers({
|
||||
hasStarredBoards() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (!user) return false;
|
||||
const { starredBoards = [] } = user.profile || {};
|
||||
return Array.isArray(starredBoards) && starredBoards.length > 0;
|
||||
},
|
||||
starredBoards() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (!user) return [];
|
||||
const { starredBoards = [] } = user.profile || {};
|
||||
if (!Array.isArray(starredBoards) || starredBoards.length === 0) return [];
|
||||
return Boards.find({ _id: { $in: starredBoards } }, { sort: { sort: 1 } });
|
||||
},
|
||||
});
|
||||
|
||||
Template.bookmarksPopup.events({
|
||||
'click .js-toggle-star'(e) {
|
||||
e.preventDefault();
|
||||
const boardId = this._id;
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user && boardId) {
|
||||
user.toggleBoardStar(boardId);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
.broken-cards-card-wrapper {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
border-width: 3px !important;
|
||||
margin-bottom: 1.3vh;
|
||||
border-width: 0.4vw !important;
|
||||
border-color: #808080 !important;
|
||||
border-style: solid;
|
||||
border-radius: 5px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.7vw;
|
||||
padding: 2vh 2vw;
|
||||
background-color: #fff;
|
||||
}
|
||||
.broken-cards-wrapper {
|
||||
max-width: 500px;
|
||||
max-width: min(500px, 65vw);
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.due-cards-dueat-list-wrapper {
|
||||
max-width: 500px;
|
||||
max-width: min(500px, 65vw);
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -79,7 +79,6 @@ BlazeComponent.extendComponent({
|
||||
autosize($textarea);
|
||||
$textarea.escapeableTextComplete(mentions);
|
||||
};
|
||||
/*
|
||||
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === true || Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === 'true') {
|
||||
const isSmall = Utils.isMiniScreen();
|
||||
const toolbar = isSmall
|
||||
@@ -117,7 +116,7 @@ BlazeComponent.extendComponent({
|
||||
].join('|');
|
||||
const badPatterns = new RegExp(
|
||||
`(?:${[
|
||||
`<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
|
||||
`<(${badTags})\\s*[^>][\\s\\S]*?<\\/\\1>`,
|
||||
`<(${badTags})[^>]*?\\/>`,
|
||||
].join('|')})`,
|
||||
'gi',
|
||||
@@ -128,9 +127,9 @@ BlazeComponent.extendComponent({
|
||||
// remove attributes ' style="..."'
|
||||
const badAttributes = new RegExp(
|
||||
`(?:${[
|
||||
'on\\S+=([\'"]?).*?\\1',
|
||||
'href=([\'"]?)javascript:.*?\\2',
|
||||
'style=([\'"]?).*?\\3',
|
||||
'on\\S+=([\'\"]?).*?\\1',
|
||||
'href=([\'\"]?)javascript:.*?\\2',
|
||||
'style=([\'\"]?).*?\\3',
|
||||
'target=\\S+',
|
||||
].join('|')})`,
|
||||
'gi',
|
||||
@@ -300,7 +299,6 @@ BlazeComponent.extendComponent({
|
||||
} else {
|
||||
enableTextarea();
|
||||
}
|
||||
*/
|
||||
enableTextarea();
|
||||
},
|
||||
events() {
|
||||
@@ -327,6 +325,7 @@ BlazeComponent.extendComponent({
|
||||
}).register('editor');
|
||||
|
||||
import DOMPurify from 'dompurify';
|
||||
import { sanitizeHTML } from '/imports/lib/secureDOMPurify';
|
||||
|
||||
// Additional safeAttrValue function to allow for other specific protocols
|
||||
// See https://github.com/leizongmin/js-xss/issues/52#issuecomment-241354114
|
||||
@@ -373,9 +372,7 @@ Blaze.Template.registerHelper(
|
||||
let content = Blaze.toHTML(view.templateContentBlock);
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
if (!currentBoard)
|
||||
return HTML.Raw(
|
||||
DOMPurify.sanitize(content, { ALLOW_UNKNOWN_PROTOCOLS: true }),
|
||||
);
|
||||
return HTML.Raw(sanitizeHTML(content));
|
||||
const knowedUsers = _.union(currentBoard.members.map(member => {
|
||||
const u = ReactiveCache.getUser(member.userId);
|
||||
if (u) {
|
||||
@@ -419,9 +416,7 @@ Blaze.Template.registerHelper(
|
||||
content = content.replace(fullMention, Blaze.toHTML(link));
|
||||
}
|
||||
|
||||
return HTML.Raw(
|
||||
DOMPurify.sanitize(content, { ALLOW_UNKNOWN_PROTOCOLS: true }),
|
||||
);
|
||||
return HTML.Raw(sanitizeHTML(content));
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -99,7 +99,36 @@
|
||||
display: flex;
|
||||
z-index: 1000;
|
||||
padding: 10px 0px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap; /* Allow wrapping on mobile */
|
||||
min-height: 28px; /* Allow height to grow */
|
||||
}
|
||||
#header-quick-access .home-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon .fa-home {
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#header-quick-access .allBoards {
|
||||
font-size: 14px;
|
||||
padding: 4px 15px;
|
||||
@@ -196,6 +225,197 @@
|
||||
margin: 4px 8px 0 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* Zoom Controls in Header */
|
||||
#header-quick-access .zoom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5vw;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.5vh 1vw;
|
||||
border-radius: 0.5vw;
|
||||
box-shadow: 0 0.2vh 0.5vh rgba(0,0,0,0.1);
|
||||
margin: 0 1vw;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .board-header-btn {
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
border-radius: 0.3vw !important;
|
||||
background: #fff !important;
|
||||
border: 1px solid #000 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
color: #000 !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
float: none !important;
|
||||
overflow: visible !important;
|
||||
text-decoration: none !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0.3vw !important;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .board-header-btn i {
|
||||
color: #000 !important;
|
||||
float: none !important;
|
||||
display: inline !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .board-header-btn:hover {
|
||||
background: #000 !important;
|
||||
border-color: #000 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .board-header-btn:hover i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-level {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
min-width: 3vw;
|
||||
text-align: center;
|
||||
font-size: clamp(12px, 2vw, 14px);
|
||||
cursor: pointer;
|
||||
padding: 0.3vh 0.5vw;
|
||||
border-radius: 0.3vw;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-level:hover {
|
||||
background: #f0f0f0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-display {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-input {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 0.3vw;
|
||||
padding: 0.3vh 0.5vw;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
min-width: 3vw;
|
||||
font-size: clamp(12px, 2vw, 14px);
|
||||
}
|
||||
|
||||
/* Make zoom input wider on all mobile screens */
|
||||
@media screen and (max-width: 800px) {
|
||||
#header-quick-access .zoom-controls .zoom-input {
|
||||
min-width: 50px !important; /* Wider on mobile */
|
||||
width: 50px !important; /* Fixed width to show all numbers */
|
||||
font-size: 14px !important; /* Slightly larger text */
|
||||
}
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-input:focus {
|
||||
outline: 2px solid #005fcc;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/* Mobile Mode Toggle in Header */
|
||||
#header-quick-access .mobile-mode-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 1vw;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn {
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
border-radius: 0.3vw !important;
|
||||
background: #fff !important;
|
||||
border: 1px solid #000 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
color: #000 !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
float: none !important;
|
||||
overflow: visible !important;
|
||||
text-decoration: none !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
gap: 6px !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn i {
|
||||
color: #666 !important;
|
||||
float: none !important;
|
||||
display: inline !important;
|
||||
line-height: normal !important;
|
||||
margin: 0 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
font-size: clamp(14px, 2.8vw, 18px) !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn i.active {
|
||||
color: #000 !important;
|
||||
font-weight: bold !important;
|
||||
transform: scale(1.1) !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn:hover {
|
||||
background: #000 !important;
|
||||
border-color: #000 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn:hover i {
|
||||
color: #ccc !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn:hover i.active {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn.mobile-active {
|
||||
background: #fff !important;
|
||||
border-color: #000 !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn.mobile-active i.mobile-icon {
|
||||
color: #000 !important;
|
||||
font-weight: bold !important;
|
||||
transform: scale(1.1) !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn.mobile-active i.desktop-icon {
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn.desktop-active {
|
||||
background: #fff !important;
|
||||
border-color: #000 !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn.desktop-active i.mobile-icon {
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn.desktop-active i.desktop-icon {
|
||||
color: #000 !important;
|
||||
font-weight: bold !important;
|
||||
transform: scale(1.1) !important;
|
||||
}
|
||||
#header-quick-access #header-user-bar .header-user-bar-name i.fa-chevron-down {
|
||||
margin-right: 4px;
|
||||
}
|
||||
@@ -227,6 +447,650 @@
|
||||
width: 100%;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
/* Mobile home icon styling */
|
||||
#header-quick-access .home-icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon .fa-home {
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon a {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Ensure All Boards text is visible on mobile */
|
||||
#header-quick-access .home-icon.allBoards {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Adjust for very small screens */
|
||||
@media screen and (max-width: 480px) {
|
||||
#header-quick-access .home-icon a {
|
||||
font-size: 11px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon .fa-home {
|
||||
font-size: 14px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile - make all text and icons 2x bigger above #content by default */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
#header-quick-access {
|
||||
height: auto !important; /* Allow height to grow */
|
||||
min-height: 48px !important; /* Minimum height for mobile */
|
||||
flex-wrap: wrap !important; /* Force wrapping */
|
||||
align-items: flex-start !important; /* Align to top when wrapping */
|
||||
padding: 8px 0px !important; /* Adjust padding for mobile */
|
||||
}
|
||||
#header-quick-access {
|
||||
font-size: 2em !important; /* 2x bigger base font size */
|
||||
}
|
||||
|
||||
#header-quick-access * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .fa,
|
||||
#header-quick-access .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons */
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon a {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon .fa-home {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-level {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn i {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* Mobile header wrapping and spacing */
|
||||
#header-quick-access .home-icon {
|
||||
flex-shrink: 0 !important;
|
||||
margin-right: 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
#header-quick-access #notifications {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
#header-quick-access #header-user-bar {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
#header-quick-access ul.header-quick-access-list {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile All Boards page - make logo row elements 2x bigger */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
.wrapper ~ #header-quick-access,
|
||||
body:not(.board-view) #header-quick-access {
|
||||
font-size: 2em !important; /* 2x bigger base font size for logo row */
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini specific - 3x bigger for All Boards page */
|
||||
@media screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
|
||||
screen and (max-width: 375px) and (max-height: 812px), /* iPhone 12 Mini viewport */
|
||||
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 375px) /* iPhone 12 Mini Retina */ {
|
||||
.wrapper ~ #header-quick-access,
|
||||
body:not(.board-view) #header-quick-access {
|
||||
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini All Boards page */
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access *,
|
||||
body:not(.board-view) #header-quick-access * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .fa,
|
||||
.wrapper ~ #header-quick-access .icon,
|
||||
body:not(.board-view) #header-quick-access .fa,
|
||||
body:not(.board-view) #header-quick-access .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons in logo row */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .home-icon a,
|
||||
body:not(.board-view) #header-quick-access .home-icon a {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .home-icon .fa-home,
|
||||
body:not(.board-view) #header-quick-access .home-icon .fa-home {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .zoom-controls,
|
||||
body:not(.board-view) #header-quick-access .zoom-controls {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .zoom-controls .zoom-level,
|
||||
body:not(.board-view) #header-quick-access .zoom-controls .zoom-level {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .zoom-controls .zoom-input,
|
||||
body:not(.board-view) #header-quick-access .zoom-controls .zoom-input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .mobile-mode-toggle .board-header-btn,
|
||||
body:not(.board-view) #header-quick-access .mobile-mode-toggle .board-header-btn {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .mobile-mode-toggle .board-header-btn i,
|
||||
body:not(.board-view) #header-quick-access .mobile-mode-toggle .board-header-btn i {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access #notifications,
|
||||
body:not(.board-view) #header-quick-access #notifications {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access #notifications .fa,
|
||||
body:not(.board-view) #header-quick-access #notifications .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access #header-user-bar,
|
||||
body:not(.board-view) #header-quick-access #header-user-bar {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access #header-user-bar .fa,
|
||||
body:not(.board-view) #header-quick-access #header-user-bar .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini specific - make header elements 3x bigger */
|
||||
@media screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
|
||||
screen and (max-width: 375px) and (max-height: 812px), /* iPhone 12 Mini viewport */
|
||||
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 375px), /* iPhone 12 Mini Retina */
|
||||
screen and (max-width: 375px) and (orientation: portrait), /* iPhone 12 Mini portrait */
|
||||
screen and (max-width: 375px) and (orientation: landscape) /* iPhone 12 Mini landscape */ {
|
||||
#header-quick-access {
|
||||
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini */
|
||||
height: auto !important; /* Allow height to grow */
|
||||
min-height: 84px !important; /* Much taller minimum height for iPhone 12 Mini */
|
||||
flex-wrap: wrap !important; /* Force wrapping */
|
||||
align-items: flex-start !important; /* Align to top when wrapping */
|
||||
padding: 18px 0px !important; /* More padding for iPhone 12 Mini */
|
||||
}
|
||||
|
||||
#header-quick-access * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .fa,
|
||||
#header-quick-access .icon {
|
||||
font-size: 3em !important; /* 3x bigger icons for iPhone 12 Mini */
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon a {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon .fa-home {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-level {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn i {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access #notifications {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access #notifications .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access #header-user-bar {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access #header-user-bar .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini header wrapping and spacing */
|
||||
#header-quick-access .home-icon {
|
||||
flex-shrink: 0 !important;
|
||||
margin-right: 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
#header-quick-access #notifications {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
#header-quick-access #header-user-bar {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
#header-quick-access ul.header-quick-access-list {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini and very small screens - make header elements much larger */
|
||||
@media screen and (max-width: 400px) and (max-height: 900px),
|
||||
screen and (max-device-width: 400px) and (max-device-height: 900px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 400px),
|
||||
screen and (max-width: 400px) and (orientation: portrait),
|
||||
screen and (max-width: 400px) and (orientation: landscape),
|
||||
screen and (max-width: 430px) and (max-height: 950px), /* iPhone 12 Mini range */
|
||||
screen and (max-width: 450px) and (max-height: 1000px), /* iPhone range */
|
||||
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 450px), /* Retina displays */
|
||||
screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
|
||||
screen and (device-width: 390px) and (device-height: 844px), /* iPhone 12/13 */
|
||||
screen and (device-width: 428px) and (device-height: 926px) /* iPhone 12 Pro Max */ {
|
||||
#header-quick-access {
|
||||
height: 40px !important; /* Taller header */
|
||||
padding: 12px 0px !important;
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon a {
|
||||
font-size: 16px !important; /* Much larger text */
|
||||
padding: 8px 12px !important;
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon .fa-home {
|
||||
font-size: 20px !important; /* Much larger icon */
|
||||
margin-right: 6px !important;
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon {
|
||||
margin-right: 1rem !important;
|
||||
}
|
||||
|
||||
/* Make zoom controls larger */
|
||||
#header-quick-access .zoom-controls {
|
||||
padding: 0.8vh 1.5vw !important;
|
||||
margin: 0 1.5vw !important;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-level {
|
||||
font-size: 16px !important; /* Larger zoom text */
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
min-width: 4vw !important;
|
||||
}
|
||||
|
||||
#header-quick-access .zoom-controls .zoom-input {
|
||||
font-size: 16px !important; /* Larger input text */
|
||||
padding: 0.5vh 0.8vw !important;
|
||||
min-width: 6vw !important; /* Much wider for mobile */
|
||||
width: 60px !important; /* Fixed width to show all numbers */
|
||||
}
|
||||
|
||||
/* Make mobile mode toggle larger */
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn {
|
||||
padding: 0.8vh 1.2vw !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
#header-quick-access .mobile-mode-toggle .board-header-btn i {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback for iPhone devices using JavaScript detection */
|
||||
.iphone-device #header-quick-access {
|
||||
font-size: 2em !important; /* 2x bigger base font size */
|
||||
height: auto !important; /* Allow height to grow */
|
||||
min-height: 48px !important; /* Minimum height for mobile */
|
||||
flex-wrap: wrap !important; /* Force wrapping */
|
||||
align-items: flex-start !important; /* Align to top when wrapping */
|
||||
padding: 8px 0px !important; /* Adjust padding for mobile */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .fa,
|
||||
.iphone-device #header-quick-access .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .home-icon a {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .home-icon .fa-home {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .zoom-controls {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .zoom-controls .zoom-level {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .zoom-controls .zoom-input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .mobile-mode-toggle .board-header-btn {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .mobile-mode-toggle .board-header-btn i {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* iPhone device header wrapping and spacing */
|
||||
.iphone-device #header-quick-access .home-icon {
|
||||
flex-shrink: 0 !important;
|
||||
margin-right: 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .zoom-controls {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .mobile-mode-toggle {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access #notifications {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access #header-user-bar {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access ul.header-quick-access-list {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 4px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini specific - JavaScript detection fallback */
|
||||
.iphone-device #header-quick-access {
|
||||
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini */
|
||||
height: auto !important; /* Allow height to grow */
|
||||
min-height: 84px !important; /* Much taller minimum height for iPhone 12 Mini */
|
||||
flex-wrap: wrap !important; /* Force wrapping */
|
||||
align-items: flex-start !important; /* Align to top when wrapping */
|
||||
padding: 18px 0px !important; /* More padding for iPhone 12 Mini */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .fa,
|
||||
.iphone-device #header-quick-access .icon {
|
||||
font-size: 3em !important; /* 3x bigger icons for iPhone 12 Mini */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .home-icon a {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .home-icon .fa-home {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .zoom-controls {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .zoom-controls .zoom-level {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .zoom-controls .zoom-input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .mobile-mode-toggle .board-header-btn {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .mobile-mode-toggle .board-header-btn i {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access #notifications {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access #notifications .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access #header-user-bar {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access #header-user-bar .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini header wrapping and spacing - JavaScript fallback */
|
||||
.iphone-device #header-quick-access .home-icon {
|
||||
flex-shrink: 0 !important;
|
||||
margin-right: 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .zoom-controls {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .mobile-mode-toggle {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access #notifications {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access #header-user-bar {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access ul.header-quick-access-list {
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 0.5rem !important;
|
||||
margin-bottom: 6px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini All Boards page - make logo row elements 3x bigger */
|
||||
.iphone-device .wrapper ~ #header-quick-access,
|
||||
.iphone-device body:not(.board-view) #header-quick-access {
|
||||
font-size: 3em !important; /* 3x bigger base font size for logo row */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access *,
|
||||
.iphone-device body:not(.board-view) #header-quick-access * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .fa,
|
||||
.iphone-device .wrapper ~ #header-quick-access .icon,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .fa,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons in logo row */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .home-icon a,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .home-icon a {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .home-icon .fa-home,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .home-icon .fa-home {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .zoom-controls,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .zoom-controls {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .zoom-controls .zoom-level,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .zoom-controls .zoom-level {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .zoom-controls .zoom-input,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .zoom-controls .zoom-input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .mobile-mode-toggle .board-header-btn,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .mobile-mode-toggle .board-header-btn {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .mobile-mode-toggle .board-header-btn i,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .mobile-mode-toggle .board-header-btn i {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access #notifications,
|
||||
.iphone-device body:not(.board-view) #header-quick-access #notifications {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access #notifications .fa,
|
||||
.iphone-device body:not(.board-view) #header-quick-access #notifications .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access #header-user-bar,
|
||||
.iphone-device body:not(.board-view) #header-quick-access #header-user-bar {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access #header-user-bar .fa,
|
||||
.iphone-device body:not(.board-view) #header-quick-access #header-user-bar .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
#header-quick-access ul {
|
||||
width: calc(100% - 60px);
|
||||
margin-right: 10px;
|
||||
@@ -237,12 +1101,6 @@
|
||||
#header-quick-access ul li a {
|
||||
height: 100%;
|
||||
}
|
||||
#header-quick-access span .fa-home {
|
||||
font-size: 26px;
|
||||
margin-top: -2px;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#header-quick-access #header-new-board-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,31 @@ template(name="header")
|
||||
The first link goes to the boards page.
|
||||
if currentUser
|
||||
#header-quick-access(class=currentBoard.colorClass)
|
||||
// Home icon - always at left side of logo
|
||||
span.home-icon.allBoards
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
|
||||
// Logo - visible; on mobile constrained by CSS
|
||||
unless currentSetting.hideLogo
|
||||
if currentSetting.customTopLeftCornerLogoImageUrl
|
||||
if currentSetting.customTopLeftCornerLogoLinkUrl
|
||||
a(href="{{currentSetting.customTopLeftCornerLogoLinkUrl}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0")
|
||||
unless currentSetting.customTopLeftCornerLogoLinkUrl
|
||||
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
unless currentSetting.customTopLeftCornerLogoImageUrl
|
||||
div#headerIsSettingDatabaseCallDone
|
||||
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
|
||||
// Zoom controls - always visible
|
||||
.zoom-controls
|
||||
span.zoom-level.js-zoom-level-click(title="{{_ 'click-to-change-zoom'}}")
|
||||
span.zoom-display {{zoomLevel}}%
|
||||
input.zoom-input.js-zoom-input(type="number" value=zoomLevel min="50" max="300" step="10" style="display: none;")
|
||||
|
||||
if isMiniScreen
|
||||
span
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
ul.header-quick-access-list
|
||||
if currentList
|
||||
each currentBoard.lists
|
||||
@@ -23,32 +44,14 @@ template(name="header")
|
||||
a(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
+viewer
|
||||
= title
|
||||
a.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}" alt="{{_ 'show-desktop-drag-handles'}}")
|
||||
i.fa.fa-arrows
|
||||
if isShowDesktopDragHandles
|
||||
i.fa.fa-check-square-o
|
||||
unless isShowDesktopDragHandles
|
||||
i.fa.fa-ban
|
||||
//a.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}" alt="{{_ 'show-desktop-drag-handles'}}")
|
||||
// i.fa.fa-arrows
|
||||
// if isShowDesktopDragHandles
|
||||
// i.fa.fa-check-square-o
|
||||
// unless isShowDesktopDragHandles
|
||||
// i.fa.fa-ban
|
||||
#header-new-board-icon
|
||||
else
|
||||
//-
|
||||
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.
|
||||
unless currentSetting.hideLogo
|
||||
if currentSetting.customTopLeftCornerLogoImageUrl
|
||||
if currentSetting.customTopLeftCornerLogoLinkUrl
|
||||
a(href="{{currentSetting.customTopLeftCornerLogoLinkUrl}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0")
|
||||
unless currentSetting.customTopLeftCornerLogoLinkUrl
|
||||
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
unless currentSetting.customTopLeftCornerLogoImageUrl
|
||||
div#headerIsSettingDatabaseCallDone
|
||||
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
|
||||
span.allBoards
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
| {{_ 'all-boards'}}
|
||||
ul.header-quick-access-list
|
||||
//li
|
||||
// a(href="{{pathFor 'public'}}")
|
||||
@@ -61,12 +64,12 @@ template(name="header")
|
||||
= title
|
||||
else
|
||||
li.current.empty {{_ 'quick-access-description'}}
|
||||
a.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}" alt="{{_ 'show-desktop-drag-handles'}}")
|
||||
i.fa.fa-arrows
|
||||
if isShowDesktopDragHandles
|
||||
i.fa.fa-check-square-o
|
||||
unless isShowDesktopDragHandles
|
||||
i.fa.fa-ban
|
||||
//a.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}" alt="{{_ 'show-desktop-drag-handles'}}")
|
||||
// i.fa.fa-arrows
|
||||
// if isShowDesktopDragHandles
|
||||
// i.fa.fa-check-square-o
|
||||
// unless isShowDesktopDragHandles
|
||||
// i.fa.fa-ban
|
||||
// Next line is used only for spacing at header,
|
||||
// there is no visible clickable icon.
|
||||
#header-new-board-icon
|
||||
@@ -75,6 +78,16 @@ template(name="header")
|
||||
//a#header-new-board-icon.js-create-board
|
||||
// i.fa.fa-plus(title="Create a new board")
|
||||
|
||||
.mobile-mode-toggle
|
||||
a.board-header-btn.js-mobile-mode-toggle(title="{{_ 'mobile-desktop-toggle'}}" class="{{#if mobileMode}}mobile-active{{else}}desktop-active{{/if}}")
|
||||
i.fa.fa-mobile.mobile-icon(class="{{#if mobileMode}}active{{/if}}")
|
||||
i.fa.fa-desktop.desktop-icon(class="{{#unless mobileMode}}active{{/unless}}")
|
||||
|
||||
// Bookmarks button - desktop opens popup, mobile routes to page
|
||||
a.board-header-btn.js-open-bookmarks(title="{{_ 'bookmarks'}}")
|
||||
i.fa.fa-bookmark
|
||||
|
||||
// Notifications
|
||||
+notifications
|
||||
|
||||
if currentSetting.customHelpLinkUrl
|
||||
|
||||
@@ -44,10 +44,69 @@ Template.header.helpers({
|
||||
const announcements = Announcements.findOne();
|
||||
return announcements && announcements.body;
|
||||
},
|
||||
|
||||
zoomLevel() {
|
||||
const sessionZoom = Session.get('wekan-zoom-level');
|
||||
if (sessionZoom !== undefined) {
|
||||
return Math.round(sessionZoom * 100);
|
||||
}
|
||||
return Math.round(Utils.getZoomLevel() * 100);
|
||||
},
|
||||
|
||||
mobileMode() {
|
||||
const sessionMode = Session.get('wekan-mobile-mode');
|
||||
if (sessionMode !== undefined) {
|
||||
return sessionMode;
|
||||
}
|
||||
return Utils.getMobileMode();
|
||||
},
|
||||
});
|
||||
|
||||
Template.header.events({
|
||||
'click .js-create-board': Popup.open('headerBarCreateBoard'),
|
||||
'click .js-zoom-level-click'(evt) {
|
||||
const $zoomDisplay = $(evt.currentTarget).find('.zoom-display');
|
||||
const $zoomInput = $(evt.currentTarget).find('.zoom-input');
|
||||
|
||||
// Hide display, show input
|
||||
$zoomDisplay.hide();
|
||||
$zoomInput.show().focus().select();
|
||||
},
|
||||
|
||||
'keypress .js-zoom-input'(evt) {
|
||||
if (evt.which === 13) { // Enter key
|
||||
const newZoomPercent = parseInt(evt.target.value);
|
||||
|
||||
if (!isNaN(newZoomPercent) && newZoomPercent >= 50 && newZoomPercent <= 300) {
|
||||
const newZoom = newZoomPercent / 100;
|
||||
Utils.setZoomLevel(newZoom);
|
||||
|
||||
// Hide input, show display
|
||||
const $zoomDisplay = $(evt.target).siblings('.zoom-display');
|
||||
const $zoomInput = $(evt.target);
|
||||
$zoomInput.hide();
|
||||
$zoomDisplay.show();
|
||||
} else {
|
||||
alert('Please enter a zoom level between 50% and 300%');
|
||||
evt.target.focus().select();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'blur .js-zoom-input'(evt) {
|
||||
// When input loses focus, hide it and show display
|
||||
const $zoomDisplay = $(evt.target).siblings('.zoom-display');
|
||||
const $zoomInput = $(evt.target);
|
||||
$zoomInput.hide();
|
||||
$zoomDisplay.show();
|
||||
},
|
||||
'click .js-mobile-mode-toggle'() {
|
||||
const currentMode = Utils.getMobileMode();
|
||||
Utils.setMobileMode(!currentMode);
|
||||
},
|
||||
'click .js-open-bookmarks'(evt) {
|
||||
// Already added but ensure single definition -- safe guard
|
||||
},
|
||||
'click .js-close-announcement'() {
|
||||
$('.announcement').hide();
|
||||
},
|
||||
@@ -68,6 +127,14 @@ Template.header.events({
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
'click .js-open-bookmarks'(evt) {
|
||||
// Desktop: open popup, Mobile: route to page
|
||||
if (Utils.isMiniScreen()) {
|
||||
FlowRouter.go('bookmarks');
|
||||
} else {
|
||||
Popup.open('bookmarksPopup')(evt);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.offlineWarning.events({
|
||||
|
||||
@@ -52,15 +52,25 @@ input,
|
||||
select,
|
||||
textarea,
|
||||
button {
|
||||
font: 14px Roboto, Poppins, "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
line-height: 18px;
|
||||
font: clamp(14px, 2.5vw, 18px) Roboto, Poppins, "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
line-height: 1.4;
|
||||
color: #4d4d4d;
|
||||
/* Improve text rendering */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Better text selection */
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
html {
|
||||
font-size: 100%;
|
||||
max-height: 100%;
|
||||
user-select: none;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
|
||||
}
|
||||
body {
|
||||
background: #dedede;
|
||||
@@ -81,8 +91,8 @@ body {
|
||||
margin-top: 30vh;
|
||||
}
|
||||
#content > .wrapper {
|
||||
margin-top: 10px;
|
||||
padding: 15px;
|
||||
margin-top: 1vh;
|
||||
padding: 2vh 2vw;
|
||||
}
|
||||
#modal {
|
||||
position: absolute;
|
||||
@@ -95,56 +105,56 @@ body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
#modal .modal-content {
|
||||
width: 500px;
|
||||
min-height: 160px;
|
||||
margin: 42px auto;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
width: min(500px, 90vw);
|
||||
min-height: 20vh;
|
||||
margin: 5vh auto;
|
||||
padding: 2vh 2vw;
|
||||
border-radius: 0.5vw;
|
||||
background: #dedede;
|
||||
z-index: 110;
|
||||
}
|
||||
#modal .modal-content h2 {
|
||||
margin-bottom: 25px;
|
||||
margin-bottom: 3vh;
|
||||
}
|
||||
#modal .modal-content .modal-close-btn {
|
||||
display: block;
|
||||
float: right;
|
||||
font-size: 24px;
|
||||
font-size: clamp(18px, 4vw, 24px);
|
||||
}
|
||||
#modal .modal-content-wide {
|
||||
width: 800px;
|
||||
width: min(800px, 95vw);
|
||||
min-height: 0px;
|
||||
margin: 42px auto;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin: 5vh auto;
|
||||
padding: 2vh 2vw;
|
||||
border-radius: 0.5vw;
|
||||
background: #dedede;
|
||||
z-index: 110;
|
||||
}
|
||||
#modal .modal-content-wide h2 {
|
||||
margin-bottom: 25px;
|
||||
margin-bottom: 3vh;
|
||||
}
|
||||
#modal .modal-content-wide .modal-close-btn {
|
||||
display: block;
|
||||
float: right;
|
||||
font-size: 24px;
|
||||
font-size: clamp(18px, 4vw, 24px);
|
||||
}
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
font-size: clamp(18px, 4vw, 24px);
|
||||
line-height: 1.2em;
|
||||
margin: 0 0 10px;
|
||||
margin: 0 0 1vh;
|
||||
}
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
font-size: clamp(16px, 3.5vw, 20px);
|
||||
line-height: 1.2em;
|
||||
margin: 0 0 9px;
|
||||
margin: 0 0 0.8vh;
|
||||
}
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 16px;
|
||||
font-size: clamp(14px, 3vw, 18px);
|
||||
line-height: 1.25em;
|
||||
margin: 0 0 6px;
|
||||
margin: 0 0 0.6vh;
|
||||
}
|
||||
.quiet,
|
||||
.quiet a {
|
||||
@@ -156,14 +166,14 @@ h6 {
|
||||
}
|
||||
.no-items-message {
|
||||
color: #666;
|
||||
margin: 30px 0;
|
||||
margin: 4vh 0;
|
||||
text-align: center;
|
||||
}
|
||||
.warning {
|
||||
background: #f0ecdb;
|
||||
border-radius: 3px;
|
||||
border-radius: 0.4vw;
|
||||
color: #aa8f09;
|
||||
padding: 6px 8px;
|
||||
padding: 1vh 1.2vw;
|
||||
}
|
||||
.warning a {
|
||||
color: #aa8f09;
|
||||
@@ -188,7 +198,10 @@ strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
p {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
|
||||
|
||||
}
|
||||
p a {
|
||||
text-decoration: underline;
|
||||
@@ -196,24 +209,24 @@ p a {
|
||||
}
|
||||
table,
|
||||
p {
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
pre {
|
||||
margin: 15px 0;
|
||||
margin: 2vh 0;
|
||||
white-space: pre;
|
||||
max-height: 516px;
|
||||
max-height: 60vh;
|
||||
}
|
||||
pre,
|
||||
code,
|
||||
tt {
|
||||
font-family: lucida console, monospace;
|
||||
line-height: 1.25em;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
blockquote {
|
||||
margin: 8px 0 8px 8px;
|
||||
margin: 1vh 0 1vh 1vw;
|
||||
border-left: 1px solid #ccc;
|
||||
color: #666;
|
||||
padding: 0 0 0 8px;
|
||||
padding: 0 0 0 1vw;
|
||||
}
|
||||
hr {
|
||||
height: 1px;
|
||||
@@ -222,7 +235,7 @@ hr {
|
||||
width: 100%;
|
||||
background: #dbdbdb;
|
||||
color: #dbdbdb;
|
||||
margin: 15px 0;
|
||||
margin: 2vh 0;
|
||||
padding: 0;
|
||||
}
|
||||
table,
|
||||
@@ -234,7 +247,7 @@ th {
|
||||
}
|
||||
td,
|
||||
th {
|
||||
padding: 5px;
|
||||
padding: 0.8vh 0.8vw;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
@@ -250,20 +263,20 @@ tbody {
|
||||
}
|
||||
dl,
|
||||
dt {
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
dd {
|
||||
margin: 0 0 16px 24px;
|
||||
margin: 0 0 2vh 3vw;
|
||||
}
|
||||
kbd {
|
||||
padding: 1px 3px;
|
||||
margin: 3px;
|
||||
padding: 0.2vh 0.5vw;
|
||||
margin: 0.5vh;
|
||||
font-weight: bold;
|
||||
background: #fafafa;
|
||||
border-radius: 3px;
|
||||
border-radius: 0.4vw;
|
||||
border: 1px solid #e6e6e6;
|
||||
color: unset;
|
||||
box-shadow: 0px 2px 3px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0.3vh 0.5vh rgba(0,0,0,0.15);
|
||||
}
|
||||
.clear {
|
||||
clear: both;
|
||||
@@ -303,7 +316,7 @@ kbd {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.wrapper {
|
||||
width: calc(100% - 20px);
|
||||
width: calc(100% - 2vw);
|
||||
margin: 0 auto;
|
||||
}
|
||||
.relative {
|
||||
@@ -394,23 +407,23 @@ a:not(.disabled).is-active i.fa {
|
||||
}
|
||||
.big-message {
|
||||
display: block;
|
||||
margin: 75px auto;
|
||||
margin: 10vh auto;
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
max-width: min(600px, 90vw);
|
||||
}
|
||||
.big-message h1 {
|
||||
font-size: 26px;
|
||||
margin-bottom: 24px;
|
||||
font-size: clamp(20px, 5vw, 28px);
|
||||
margin-bottom: 3vh;
|
||||
}
|
||||
.big-message p {
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
font-size: clamp(16px, 3.5vw, 20px);
|
||||
line-height: 1.3;
|
||||
}
|
||||
.gutter {
|
||||
margin-left: 38px;
|
||||
margin-left: 5vw;
|
||||
}
|
||||
.viewer {
|
||||
min-height: 18px;
|
||||
min-height: 2.5vh;
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
@@ -420,18 +433,19 @@ a:not(.disabled).is-active i.fa {
|
||||
}
|
||||
.viewer ol {
|
||||
list-style-type: decimal;
|
||||
padding-left: 20px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 3vw;
|
||||
padding-bottom: 1.5vh;
|
||||
}
|
||||
.viewer ul {
|
||||
list-style-type: initial;
|
||||
padding-left: 20px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 3vw;
|
||||
padding-bottom: 1.5vh;
|
||||
}
|
||||
.viewer em {
|
||||
font-style: italic;
|
||||
}
|
||||
.viewer pre {
|
||||
.viewer pre,
|
||||
.viewer code {
|
||||
padding: 10px 12px 7px;
|
||||
background: #dedede;
|
||||
overflow-y: auto;
|
||||
@@ -446,20 +460,297 @@ a:not(.disabled).is-active i.fa {
|
||||
padding: 0;
|
||||
padding-top: 15px;
|
||||
}
|
||||
.no-scrollbars {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.no-scrollbars::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
/* ========================================
|
||||
MOBILE & TABLET RESPONSIVE IMPROVEMENTS
|
||||
======================================== */
|
||||
|
||||
/* Mobile devices (up to 800px) */
|
||||
@media screen and (max-width: 800px) {
|
||||
#content {
|
||||
margin: 1px 0px 0px 0px;
|
||||
height: calc(100% - 0px);
|
||||
/* Improve touch scrolling */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
#content > .wrapper {
|
||||
margin-top: 0px;
|
||||
padding: 8px;
|
||||
}
|
||||
.wrapper {
|
||||
height: calc(100% - 31px);
|
||||
margin: 0px;
|
||||
padding: 8px;
|
||||
}
|
||||
.panel-default {
|
||||
width: 83vw;
|
||||
width: 95vw;
|
||||
max-width: 95vw;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Improve touch targets */
|
||||
button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px; /* Prevent zoom on iOS */
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
/* Form elements */
|
||||
input, select, textarea {
|
||||
font-size: 16px; /* Prevent zoom on iOS */
|
||||
padding: 12px;
|
||||
min-height: 44px;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
/* Cards and lists */
|
||||
.minicard {
|
||||
min-height: 48px;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 8px;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
/* Board canvas */
|
||||
.board-canvas {
|
||||
padding: 8px;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Header mobile layout */
|
||||
#header {
|
||||
padding: 8px;
|
||||
/* Keep top bar on a single row on small screens */
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#header-quick-access {
|
||||
/* Keep quick-access items in one row */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Hide elements that should move to the hamburger menu on mobile */
|
||||
#header-quick-access .header-quick-access-list,
|
||||
#header-quick-access #header-help {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show only the home icon (hide the trailing text) on mobile */
|
||||
#header-quick-access .home-icon a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 28px; /* enough to display the icon */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Hide text in home icon on mobile, show only icon */
|
||||
#header-quick-access .home-icon a span:not(.fa) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Ensure proper spacing for mobile header elements */
|
||||
#header-quick-access .zoom-controls {
|
||||
margin-left: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mobile-mode-toggle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#header-user-bar {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Ensure header elements don't wrap on very small screens */
|
||||
#header-quick-access {
|
||||
min-width: 0; /* Allow flexbox to shrink */
|
||||
}
|
||||
|
||||
/* Make sure logo doesn't take too much space on mobile */
|
||||
#header-quick-access img {
|
||||
max-height: 24px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
/* Ensure zoom controls are compact on mobile */
|
||||
.zoom-controls .zoom-level {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Modal mobile optimization */
|
||||
#modal .modal-content,
|
||||
#modal .modal-content-wide {
|
||||
width: 95vw;
|
||||
max-width: 95vw;
|
||||
margin: 2vh auto;
|
||||
padding: 16px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Table mobile optimization */
|
||||
table {
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Admin panel mobile optimization */
|
||||
.setting-content .content-body {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.setting-content .content-body .side-menu {
|
||||
width: 100%;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.setting-content .content-body .main-body {
|
||||
order: 1;
|
||||
min-height: 60vh;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet devices (768px - 1024px) */
|
||||
@media screen and (min-width: 768px) and (max-width: 1024px) {
|
||||
#content > .wrapper {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.panel-default {
|
||||
width: 90vw;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
/* Touch-friendly but more compact */
|
||||
button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
|
||||
min-height: 48px;
|
||||
min-width: 48px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.minicard {
|
||||
min-height: 40px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 12px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.board-canvas {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#header {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
#modal .modal-content {
|
||||
width: 80vw;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
#modal .modal-content-wide {
|
||||
width: 90vw;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.setting-content .content-body {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.setting-content .content-body .side-menu {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Large displays and digital signage (1920px+) */
|
||||
@media screen and (min-width: 1920px) {
|
||||
body {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
|
||||
min-height: 56px;
|
||||
min-width: 56px;
|
||||
padding: 16px 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.minicard {
|
||||
min-height: 56px;
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 20px;
|
||||
min-width: 360px;
|
||||
}
|
||||
|
||||
.board-canvas {
|
||||
padding: 20px;
|
||||
max-width: 2400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#header {
|
||||
padding: 20px 32px;
|
||||
max-width: 2400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#modal .modal-content {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
#modal .modal-content-wide {
|
||||
width: 1000px;
|
||||
}
|
||||
|
||||
.setting-content .content-body {
|
||||
gap: 32px;
|
||||
max-width: 2400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.setting-content .content-body .side-menu {
|
||||
width: 320px;
|
||||
}
|
||||
}
|
||||
.inline-input {
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
head
|
||||
title
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1")
|
||||
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
|
||||
difficult to do that cleanly with Blaze -- at least without adding extra
|
||||
packages.
|
||||
link(rel="shortcut icon" type="image/x-icon" href="/favicon.ico")
|
||||
link(rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png")
|
||||
link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png")
|
||||
link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png")
|
||||
link(rel="manifest" crossOrigin="use-credentials" href="/site.webmanifest")
|
||||
link(rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5")
|
||||
meta(name="apple-mobile-web-app-title" content="Wekan")
|
||||
meta(name="application-name" content="Wekan")
|
||||
meta(name="msapplication-TileColor" content="#00aba9")
|
||||
meta(name="theme-color" content="#ffffff")
|
||||
//link(rel="stylesheet" type="text/css" class="__meteor-css__" href="css/html5-default-theme.css")
|
||||
template(name="main")
|
||||
html(lang="{{TAPi18n.getLanguage}}")
|
||||
head
|
||||
title
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes")
|
||||
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
|
||||
difficult to do that cleanly with Blaze -- at least without adding extra
|
||||
packages.
|
||||
link(rel="shortcut icon" type="image/x-icon" href="/favicon.ico")
|
||||
link(rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png")
|
||||
link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png")
|
||||
link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png")
|
||||
link(rel="manifest" crossOrigin="use-credentials" href="/site.webmanifest")
|
||||
link(rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5")
|
||||
meta(name="apple-mobile-web-app-title" content="Wekan")
|
||||
meta(name="application-name" content="Wekan")
|
||||
meta(name="msapplication-TileColor" content="#00aba9")
|
||||
meta(name="theme-color" content="#ffffff")
|
||||
//link(rel="stylesheet" type="text/css" class="__meteor-css__" href="css/html5-default-theme.css")
|
||||
|
||||
template(name="userFormsLayout")
|
||||
section.auth-layout
|
||||
@@ -48,6 +50,8 @@ template(name="userFormsLayout")
|
||||
if isLoading
|
||||
+loader
|
||||
else
|
||||
// ARIA live region for error messages
|
||||
div#login-error-message(role="alert" aria-live="assertive" style="color: #d32f2f; margin-bottom: 1em;")
|
||||
+Template.dynamic(template=content)
|
||||
if currentSetting.displayAuthenticationMethod
|
||||
+connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
|
||||
@@ -59,7 +63,8 @@ template(name="userFormsLayout")
|
||||
if getLegalNoticeWithWritTraduction
|
||||
div
|
||||
div.at-form-lang
|
||||
select.select-lang.js-userform-set-language
|
||||
label(for="userform-set-language-select") {{_ 'changeLanguagePopup-title'}}
|
||||
select.select-lang.js-userform-set-language#userform-set-language-select(aria-label="{{_ 'changeLanguagePopup-title'}}")
|
||||
each languages
|
||||
if isCurrentLanguage
|
||||
option(value="{{tag}}" selected="selected") {{name}}
|
||||
@@ -72,6 +77,8 @@ template(name="defaultLayout")
|
||||
| {{{afterBodyStart}}}
|
||||
+Template.dynamic(template=content)
|
||||
| {{{beforeBodyEnd}}}
|
||||
+migrationProgress
|
||||
+boardConversionProgress
|
||||
if (Modal.isOpen)
|
||||
#modal
|
||||
.overlay
|
||||
|
||||
@@ -86,6 +86,12 @@ Template.userFormsLayout.onRendered(() => {
|
||||
validator,
|
||||
);
|
||||
EscapeActions.executeAll();
|
||||
|
||||
// Add autocomplete attribute to login input for WCAG compliance
|
||||
const loginInput = document.querySelector('input[type="text"], input[type="email"]');
|
||||
if (loginInput && loginInput.name && (loginInput.name.toLowerCase().includes('user') || loginInput.name.toLowerCase().includes('email'))) {
|
||||
loginInput.setAttribute('autocomplete', 'username email');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
.my-cards-board-wrapper {
|
||||
border-radius: 0 0 4px 4px;
|
||||
min-width: 400px;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 0 0 0.5vw 0.5vw;
|
||||
min-width: min(400px, 52vw);
|
||||
margin-bottom: 2.5vh;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
border-width: 2px;
|
||||
border-width: 0.3vw;
|
||||
border-style: solid;
|
||||
border-color: #a2a2a2;
|
||||
}
|
||||
.my-cards-board-title {
|
||||
font-size: 1.4rem;
|
||||
font-size: clamp(1.2rem, 3vw, 1.6rem);
|
||||
font-weight: bold;
|
||||
padding: 0.5rem;
|
||||
padding: 0.7vh 0.7vw;
|
||||
background-color: #808080;
|
||||
color: #fff;
|
||||
}
|
||||
.my-cards-swimlane-title {
|
||||
font-size: 1.1rem;
|
||||
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||
font-weight: bold;
|
||||
padding: 0.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
padding: 0.7vh 0.7vw;
|
||||
padding-bottom: 0.5vh;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.7vh;
|
||||
text-align: center;
|
||||
}
|
||||
.swimlane-default-color {
|
||||
@@ -29,23 +29,23 @@
|
||||
}
|
||||
.my-cards-list-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||
text-align: center;
|
||||
margin-bottom: 0.7rem;
|
||||
margin-bottom: 0.9vh;
|
||||
}
|
||||
.my-cards-list-wrapper {
|
||||
margin: 1rem;
|
||||
border-radius: 5px;
|
||||
margin: 1.3vh 1.3vw;
|
||||
border-radius: 0.7vw;
|
||||
display: inline-grid;
|
||||
min-width: 250px;
|
||||
max-width: 350px;
|
||||
min-width: min(250px, 32vw);
|
||||
max-width: min(350px, 45vw);
|
||||
}
|
||||
.my-cards-card-wrapper {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 1.3vh;
|
||||
}
|
||||
.my-cards-dueat-list-wrapper {
|
||||
max-width: 500px;
|
||||
max-width: min(500px, 65vw);
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
.pop-over {
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
border-radius: 0.4vw;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-bottom-color: #c2c2c2;
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.3);
|
||||
box-shadow: 0 0.2vh 0.8vh rgba(0,0,0,0.3);
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
/* Wider default to fit full color palette */
|
||||
width: min(380px, 55vw);
|
||||
z-index: 99999;
|
||||
margin-top: 5px;
|
||||
margin-top: 0.7vh;
|
||||
}
|
||||
.pop-over hr {
|
||||
margin: 4px 0px;
|
||||
margin: 0.5vh 0px;
|
||||
}
|
||||
.pop-over p,
|
||||
.pop-over textarea,
|
||||
@@ -22,27 +23,27 @@
|
||||
}
|
||||
.pop-over select {
|
||||
width: 100%;
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 1.8vh;
|
||||
}
|
||||
.pop-over textarea {
|
||||
height: 72px;
|
||||
height: 9vh;
|
||||
}
|
||||
.pop-over form a span {
|
||||
padding: 0 0.5rem;
|
||||
padding: 0 0.7vw;
|
||||
}
|
||||
.pop-over .header {
|
||||
height: 36px;
|
||||
height: 4.5vh;
|
||||
position: relative;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 1vh;
|
||||
background: #f7f7f7;
|
||||
border-bottom: 1px solid #dcdcdc;
|
||||
color: #666;
|
||||
}
|
||||
.pop-over .header .header-title {
|
||||
display: block;
|
||||
line-height: 32px;
|
||||
padding-top: 4px;
|
||||
margin: 0 10px;
|
||||
line-height: 4vh;
|
||||
padding-top: 0.5vh;
|
||||
margin: 0 1.3vw;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -51,18 +52,18 @@
|
||||
.pop-over .header .back-btn {
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
width: 30px;
|
||||
width: 4vw;
|
||||
transition: width 0.2s;
|
||||
}
|
||||
.pop-over .header .back-btn i.fa {
|
||||
margin: 10px;
|
||||
margin-top: 12px;
|
||||
margin: 1.3vw;
|
||||
margin-top: 1.5vh;
|
||||
}
|
||||
.pop-over .header .back-btn.is-hidden {
|
||||
width: 0;
|
||||
}
|
||||
.pop-over .header .close-btn {
|
||||
padding: 10px 10px 10px 4px;
|
||||
padding: 1.3vh 1.3vw 1.3vh 0.5vw;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@@ -76,23 +77,48 @@
|
||||
}
|
||||
.pop-over .content-container {
|
||||
width: 5000px;
|
||||
max-height: 550px;
|
||||
max-height: 70vh;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.pop-over .content-container .content {
|
||||
width: 280px;
|
||||
padding: 0 10px 10px;
|
||||
/* Match wider popover, leave padding */
|
||||
width: min(360px, 52vw);
|
||||
padding: 0 1.3vw 1.3vh;
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* Utility: remove left gutter inside specific popups */
|
||||
.pop-over .content .flush-left {
|
||||
margin-left: -1.3vw;
|
||||
padding-left: 0;
|
||||
width: calc(100% + 1.3vw);
|
||||
}
|
||||
|
||||
/* Swimlane popups: remove left gutter, align content fully left */
|
||||
.pop-over .content form.swimlane-color-popup,
|
||||
.pop-over .content .swimlane-height-popup {
|
||||
margin-left: -1.3vw;
|
||||
padding-left: 0;
|
||||
width: calc(100% + 1.3vw);
|
||||
}
|
||||
|
||||
/* Ensure buttons don’t reserve left space; align to flow */
|
||||
.pop-over .content form.swimlane-color-popup .primary.confirm,
|
||||
.pop-over .content form.swimlane-color-popup .negate.wide.right,
|
||||
.pop-over .content .swimlane-height-popup .primary.confirm,
|
||||
.pop-over .content .swimlane-height-popup .negate.wide.right {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
.pop-over .content-container .content.no-height {
|
||||
height: 20px;
|
||||
height: 2.5vh;
|
||||
}
|
||||
.pop-over .quiet {
|
||||
/* padding: 6px 6px 4px;*/
|
||||
}
|
||||
.pop-over.search-over {
|
||||
background: #f0f0f0;
|
||||
min-height: 114px;
|
||||
min-height: 14vh;
|
||||
}
|
||||
.pop-over.search-over .header {
|
||||
display: none;
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
/* Migration Progress Styles */
|
||||
.migration-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.migration-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.migration-modal {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
||||
max-width: 800px;
|
||||
width: 95%;
|
||||
max-height: 90vh;
|
||||
overflow: hidden;
|
||||
animation: slideInScale 0.4s ease-out;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
@keyframes slideInScale {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.migration-header {
|
||||
padding: 24px 32px 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.migration-header h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.migration-header h3 i {
|
||||
margin-right: 12px;
|
||||
color: #FFD700;
|
||||
}
|
||||
|
||||
.migration-header p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.migration-content {
|
||||
padding: 24px 32px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.migration-overview {
|
||||
margin-bottom: 32px;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.overall-progress {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 6px;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.4),
|
||||
transparent
|
||||
);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.current-step {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.current-step i {
|
||||
margin-right: 8px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.estimated-time {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
background-color: #fff3cd;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
.estimated-time i {
|
||||
margin-right: 6px;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.migration-steps {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.migration-steps h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.steps-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.migration-step {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.migration-step:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.migration-step.completed {
|
||||
background-color: #d4edda;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
||||
.migration-step.current {
|
||||
background-color: #cce7ff;
|
||||
border-left: 4px solid #667eea;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(102, 126, 234, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
margin-right: 12px;
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-icon i.fa-check-circle {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.step-icon i.fa-cog.fa-spin {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.step-icon i.fa-circle-o {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.step-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.step-progress {
|
||||
text-align: right;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.step-progress .progress-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-progress-bar {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.step-progress-bar .progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.migration-status {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
background-color: #e3f2fd;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #bbdefb;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.migration-status i {
|
||||
margin-right: 8px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.migration-footer {
|
||||
padding: 16px 32px 24px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.migration-info {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.migration-info i {
|
||||
margin-right: 6px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.migration-warning {
|
||||
text-align: center;
|
||||
color: #856404;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
background-color: #fff3cd;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
.migration-warning i {
|
||||
margin-right: 6px;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.migration-modal {
|
||||
width: 98%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.migration-header,
|
||||
.migration-content,
|
||||
.migration-footer {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.migration-header h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.step-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.step-progress {
|
||||
text-align: left;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.steps-list {
|
||||
max-height: 200px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
template(name="migrationProgress")
|
||||
.migration-overlay(class="{{#if isMigrating}}active{{/if}}")
|
||||
.migration-modal
|
||||
.migration-header
|
||||
h3
|
||||
i.fa.fa-database
|
||||
| {{_ 'database-migration'}}
|
||||
p {{_ 'database-migration-description'}}
|
||||
|
||||
.migration-content
|
||||
.migration-overview
|
||||
.overall-progress
|
||||
.progress-bar
|
||||
.progress-fill(style="width: {{migrationProgress}}%")
|
||||
.progress-text {{migrationProgress}}%
|
||||
.progress-label {{_ 'overall-progress'}}
|
||||
|
||||
.current-step
|
||||
i.fa.fa-cog.fa-spin
|
||||
| {{migrationCurrentStep}}
|
||||
|
||||
.estimated-time(style="{{#unless migrationEstimatedTime}}display: none;{{/unless}}")
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'estimated-time-remaining'}}: {{migrationEstimatedTime}}
|
||||
|
||||
.migration-steps
|
||||
h4 {{_ 'migration-steps'}}
|
||||
.steps-list
|
||||
each migrationSteps
|
||||
.migration-step(class="{{#if completed}}completed{{/if}}" class="{{#if isCurrentStep}}current{{/if}}")
|
||||
.step-header
|
||||
.step-icon
|
||||
if completed
|
||||
i.fa.fa-check-circle
|
||||
else if isCurrentStep
|
||||
i.fa.fa-cog.fa-spin
|
||||
else
|
||||
i.fa.fa-circle-o
|
||||
.step-info
|
||||
.step-name {{name}}
|
||||
.step-description {{description}}
|
||||
.step-progress
|
||||
if completed
|
||||
.progress-text 100%
|
||||
else if isCurrentStep
|
||||
.progress-text {{progress}}%
|
||||
else
|
||||
.progress-text 0%
|
||||
if isCurrentStep
|
||||
.step-progress-bar
|
||||
.progress-fill(style="width: {{progress}}%")
|
||||
|
||||
.migration-status
|
||||
i.fa.fa-info-circle
|
||||
| {{migrationStatus}}
|
||||
|
||||
.migration-footer
|
||||
.migration-info
|
||||
i.fa.fa-lightbulb-o
|
||||
| {{_ 'migration-info-text'}}
|
||||
.migration-warning
|
||||
i.fa.fa-exclamation-triangle
|
||||
| {{_ 'migration-warning-text'}}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import {
|
||||
migrationManager,
|
||||
isMigrating,
|
||||
migrationProgress,
|
||||
migrationStatus,
|
||||
migrationCurrentStep,
|
||||
migrationEstimatedTime,
|
||||
migrationSteps
|
||||
} from '/client/lib/migrationManager';
|
||||
|
||||
Template.migrationProgress.helpers({
|
||||
isMigrating() {
|
||||
return isMigrating.get();
|
||||
},
|
||||
|
||||
migrationProgress() {
|
||||
return migrationProgress.get();
|
||||
},
|
||||
|
||||
migrationStatus() {
|
||||
return migrationStatus.get();
|
||||
},
|
||||
|
||||
migrationCurrentStep() {
|
||||
return migrationCurrentStep.get();
|
||||
},
|
||||
|
||||
migrationEstimatedTime() {
|
||||
return migrationEstimatedTime.get();
|
||||
},
|
||||
|
||||
migrationSteps() {
|
||||
const steps = migrationSteps.get();
|
||||
const currentStep = migrationCurrentStep.get();
|
||||
|
||||
return steps.map(step => ({
|
||||
...step,
|
||||
isCurrentStep: step.name === currentStep
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
Template.migrationProgress.onCreated(function() {
|
||||
// Subscribe to migration state changes
|
||||
this.autorun(() => {
|
||||
isMigrating.get();
|
||||
migrationProgress.get();
|
||||
migrationStatus.get();
|
||||
migrationCurrentStep.get();
|
||||
migrationEstimatedTime.get();
|
||||
migrationSteps.get();
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
#notifications-drawer .notification {
|
||||
display: flex;
|
||||
float: none;
|
||||
padding: 15px 8px 8px;
|
||||
padding: 2vh 1vw 1vh;
|
||||
color: #000;
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
}
|
||||
@@ -12,18 +12,18 @@
|
||||
display: none;
|
||||
}
|
||||
#notifications-drawer .notification .read-status {
|
||||
width: 30px;
|
||||
padding: 0px 10px 0px 0px;
|
||||
width: 4vw;
|
||||
padding: 0 1.3vw 0 0;
|
||||
}
|
||||
#notifications-drawer .notification .read-status input {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 3vw;
|
||||
height: 3vw;
|
||||
}
|
||||
#notifications-drawer .notification .read-status .activity-type {
|
||||
margin: 16px 0 0;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
font-size: 17px;
|
||||
margin: 2vh 0 0;
|
||||
width: 2.2vw;
|
||||
height: 2.2vw;
|
||||
font-size: clamp(14px, 2.5vw, 17px);
|
||||
display: block;
|
||||
color: #bbb;
|
||||
}
|
||||
@@ -32,20 +32,20 @@
|
||||
padding: 0px;
|
||||
}
|
||||
#notifications-drawer .notification .details .activity a.member svg {
|
||||
padding: 3px;
|
||||
padding: 0.4vw;
|
||||
}
|
||||
#notifications-drawer .notification .details .activity .activity-desc {
|
||||
margin: 0px 0px 0px 5px;
|
||||
margin: 0 0 0 0.7vw;
|
||||
}
|
||||
#notifications-drawer .notification .details .activity .activity-comment {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
border-radius: 0.4vw;
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.2);
|
||||
margin-top: 0.7vh;
|
||||
padding: 0.7vh 0.7vw;
|
||||
}
|
||||
#notifications-drawer .notification .details .activity .activity-meta {
|
||||
display: block;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
.rules-list {
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
max-height: 400px;
|
||||
padding-right: 5px;
|
||||
max-height: 50vh;
|
||||
padding-right: 0.7vw;
|
||||
}
|
||||
.rules-lists-item {
|
||||
display: block;
|
||||
@@ -33,14 +33,14 @@
|
||||
.rules-add {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 2vh;
|
||||
margin-bottom: 0.7vh;
|
||||
}
|
||||
.rules-add input {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin: auto;
|
||||
margin-right: 10px;
|
||||
margin-right: 1.3vw;
|
||||
}
|
||||
.rules-add button {
|
||||
display: inline-block;
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
template(name="attachmentSettings")
|
||||
ul#attachment-setting.setting-detail
|
||||
li
|
||||
h3 {{_ 'attachment-storage-configuration'}}
|
||||
.form-group
|
||||
label {{_ 'writable-path'}}
|
||||
input.wekan-form-control#filesystem-path(type="text" value="{{filesystemPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'filesystem-path-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'attachments-path'}}
|
||||
input.wekan-form-control#attachments-path(type="text" value="{{attachmentsPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'attachments-path-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'avatars-path'}}
|
||||
input.wekan-form-control#avatars-path(type="text" value="{{avatarsPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'avatars-path-description'}}
|
||||
|
||||
li
|
||||
h3 {{_ 'mongodb-gridfs-storage'}}
|
||||
.form-group
|
||||
label {{_ 'gridfs-enabled'}}
|
||||
input.wekan-form-control#gridfs-enabled(type="checkbox" checked="{{gridfsEnabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 'gridfs-enabled-description'}}
|
||||
|
||||
li
|
||||
h3 {{_ 's3-minio-storage'}}
|
||||
.form-group
|
||||
label {{_ 's3-enabled'}}
|
||||
input.wekan-form-control#s3-enabled(type="checkbox" checked="{{s3Enabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 's3-enabled-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-endpoint'}}
|
||||
input.wekan-form-control#s3-endpoint(type="text" value="{{s3Endpoint}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-endpoint-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-bucket'}}
|
||||
input.wekan-form-control#s3-bucket(type="text" value="{{s3Bucket}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-bucket-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-region'}}
|
||||
input.wekan-form-control#s3-region(type="text" value="{{s3Region}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-region-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-access-key'}}
|
||||
input.wekan-form-control#s3-access-key(type="text" placeholder="{{_ 's3-access-key-placeholder'}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-access-key-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-secret-key'}}
|
||||
input.wekan-form-control#s3-secret-key(type="password" placeholder="{{_ 's3-secret-key-placeholder'}}")
|
||||
small.form-text.text-muted {{_ 's3-secret-key-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-ssl-enabled'}}
|
||||
input.wekan-form-control#s3-ssl-enabled(type="checkbox" checked="{{s3SslEnabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 's3-ssl-enabled-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-port'}}
|
||||
input.wekan-form-control#s3-port(type="number" value="{{s3Port}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-port-description'}}
|
||||
|
||||
.form-group
|
||||
button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}}
|
||||
button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}}
|
||||
|
||||
template(name="storageSettings")
|
||||
.storage-settings
|
||||
h3 {{_ 'attachment-storage-configuration'}}
|
||||
|
||||
.storage-config-section
|
||||
h4 {{_ 'filesystem-storage'}}
|
||||
.form-group
|
||||
label {{_ 'writable-path'}}
|
||||
input.wekan-form-control#filesystem-path(type="text" value="{{filesystemPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'filesystem-path-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'attachments-path'}}
|
||||
input.wekan-form-control#attachments-path(type="text" value="{{attachmentsPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'attachments-path-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'avatars-path'}}
|
||||
input.wekan-form-control#avatars-path(type="text" value="{{avatarsPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'avatars-path-description'}}
|
||||
|
||||
.storage-config-section
|
||||
h4 {{_ 'mongodb-gridfs-storage'}}
|
||||
.form-group
|
||||
label {{_ 'gridfs-enabled'}}
|
||||
input.wekan-form-control#gridfs-enabled(type="checkbox" checked="{{gridfsEnabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 'gridfs-enabled-description'}}
|
||||
|
||||
.storage-config-section
|
||||
h4 {{_ 's3-minio-storage'}}
|
||||
.form-group
|
||||
label {{_ 's3-enabled'}}
|
||||
input.wekan-form-control#s3-enabled(type="checkbox" checked="{{s3Enabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 's3-enabled-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-endpoint'}}
|
||||
input.wekan-form-control#s3-endpoint(type="text" value="{{s3Endpoint}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-endpoint-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-bucket'}}
|
||||
input.wekan-form-control#s3-bucket(type="text" value="{{s3Bucket}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-bucket-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-region'}}
|
||||
input.wekan-form-control#s3-region(type="text" value="{{s3Region}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-region-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-access-key'}}
|
||||
input.wekan-form-control#s3-access-key(type="text" placeholder="{{_ 's3-access-key-placeholder'}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-access-key-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-secret-key'}}
|
||||
input.wekan-form-control#s3-secret-key(type="password" placeholder="{{_ 's3-secret-key-placeholder'}}")
|
||||
small.form-text.text-muted {{_ 's3-secret-key-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-ssl-enabled'}}
|
||||
input.wekan-form-control#s3-ssl-enabled(type="checkbox" checked="{{s3SslEnabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 's3-ssl-enabled-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-port'}}
|
||||
input.wekan-form-control#s3-port(type="number" value="{{s3Port}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-port-description'}}
|
||||
|
||||
.storage-actions
|
||||
button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}}
|
||||
button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}}
|
||||
|
||||
template(name="attachmentMigration")
|
||||
.attachment-migration
|
||||
h3 {{_ 'attachment-migration'}}
|
||||
|
||||
.migration-controls
|
||||
.form-group
|
||||
label {{_ 'migration-batch-size'}}
|
||||
input.wekan-form-control#migration-batch-size(type="number" value="{{migrationBatchSize}}" min="1" max="100")
|
||||
small.form-text.text-muted {{_ 'migration-batch-size-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'migration-delay-ms'}}
|
||||
input.wekan-form-control#migration-delay-ms(type="number" value="{{migrationDelayMs}}" min="100" max="10000")
|
||||
small.form-text.text-muted {{_ 'migration-delay-ms-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'migration-cpu-threshold'}}
|
||||
input.wekan-form-control#migration-cpu-threshold(type="number" value="{{migrationCpuThreshold}}" min="10" max="90")
|
||||
small.form-text.text-muted {{_ 'migration-cpu-threshold-description'}}
|
||||
|
||||
.migration-actions
|
||||
.migration-buttons
|
||||
button.js-migrate-all-to-filesystem.btn.btn-primary {{_ 'migrate-all-to-filesystem'}}
|
||||
button.js-migrate-all-to-gridfs.btn.btn-primary {{_ 'migrate-all-to-gridfs'}}
|
||||
button.js-migrate-all-to-s3.btn.btn-primary {{_ 'migrate-all-to-s3'}}
|
||||
|
||||
.migration-controls
|
||||
button.js-pause-migration.btn.btn-warning {{_ 'pause-migration'}}
|
||||
button.js-resume-migration.btn.btn-success {{_ 'resume-migration'}}
|
||||
button.js-stop-migration.btn.btn-danger {{_ 'stop-migration'}}
|
||||
|
||||
.migration-progress
|
||||
h4 {{_ 'migration-progress'}}
|
||||
.progress
|
||||
.progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
|
||||
| {{migrationProgress}}%
|
||||
|
||||
.migration-stats
|
||||
.stat-item
|
||||
span.label {{_ 'total-attachments'}}:
|
||||
span.value {{totalAttachments}}
|
||||
.stat-item
|
||||
span.label {{_ 'migrated-attachments'}}:
|
||||
span.value {{migratedAttachments}}
|
||||
.stat-item
|
||||
span.label {{_ 'remaining-attachments'}}:
|
||||
span.value {{remainingAttachments}}
|
||||
.stat-item
|
||||
span.label {{_ 'migration-status'}}:
|
||||
span.value {{migrationStatus}}
|
||||
|
||||
.migration-log
|
||||
h4 {{_ 'migration-log'}}
|
||||
.log-container
|
||||
pre#migration-log-content {{migrationLog}}
|
||||
|
||||
template(name="attachmentMonitoring")
|
||||
.attachment-monitoring
|
||||
h3 {{_ 'attachment-monitoring'}}
|
||||
|
||||
.monitoring-stats
|
||||
.stats-grid
|
||||
.stat-card
|
||||
h5 {{_ 'total-attachments'}}
|
||||
.stat-value {{totalAttachments}}
|
||||
.stat-card
|
||||
h5 {{_ 'filesystem-attachments'}}
|
||||
.stat-value {{filesystemAttachments}}
|
||||
.stat-card
|
||||
h5 {{_ 'gridfs-attachments'}}
|
||||
.stat-value {{gridfsAttachments}}
|
||||
.stat-card
|
||||
h5 {{_ 's3-attachments'}}
|
||||
.stat-value {{s3Attachments}}
|
||||
.stat-card
|
||||
h5 {{_ 'total-size'}}
|
||||
.stat-value {{totalSize}}
|
||||
.stat-card
|
||||
h5 {{_ 'filesystem-size'}}
|
||||
.stat-value {{filesystemSize}}
|
||||
.stat-card
|
||||
h5 {{_ 'gridfs-size'}}
|
||||
.stat-value {{gridfsSize}}
|
||||
.stat-card
|
||||
h5 {{_ 's3-size'}}
|
||||
.stat-value {{s3Size}}
|
||||
|
||||
.monitoring-charts
|
||||
h4 {{_ 'storage-distribution'}}
|
||||
.chart-container
|
||||
canvas#storage-distribution-chart
|
||||
|
||||
.monitoring-actions
|
||||
button.js-refresh-monitoring.btn.btn-secondary {{_ 'refresh-monitoring'}}
|
||||
button.js-export-monitoring.btn.btn-primary {{_ 'export-monitoring'}}
|
||||
@@ -0,0 +1,864 @@
|
||||
/* Cron Settings Styles */
|
||||
.cron-settings-content {
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.cron-migrations {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.migration-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.migration-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.migration-header h2 i {
|
||||
margin-right: 10px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.migration-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.migration-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.migration-controls .btn-primary {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.migration-controls .btn-primary:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.migration-controls .btn-warning {
|
||||
background-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.migration-controls .btn-warning:hover {
|
||||
background-color: #e0a800;
|
||||
}
|
||||
|
||||
.migration-controls .btn-danger {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.migration-controls .btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.migration-progress {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.progress-overview {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 6px;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.4),
|
||||
transparent
|
||||
);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.current-step {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.current-step i {
|
||||
margin-right: 8px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.migration-status {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
background-color: #e3f2fd;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #bbdefb;
|
||||
}
|
||||
|
||||
.migration-status i {
|
||||
margin-right: 8px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.migration-steps {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.migration-steps h3 {
|
||||
margin: 0 0 20px 0;
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.steps-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.migration-step {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.migration-step:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.migration-step.completed {
|
||||
background-color: #d4edda;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
||||
.migration-step.current {
|
||||
background-color: #cce7ff;
|
||||
border-left: 4px solid #667eea;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(102, 126, 234, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
margin-right: 12px;
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-icon i.fa-check-circle {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.step-icon i.fa-cog.fa-spin {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.step-icon i.fa-circle-o {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.step-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.step-progress {
|
||||
text-align: right;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.step-progress .progress-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-progress-bar {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.step-progress-bar .progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* Cron Jobs Styles */
|
||||
.cron-jobs {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.jobs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.jobs-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.jobs-header h2 i {
|
||||
margin-right: 10px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.jobs-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.jobs-controls .btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.jobs-controls .btn-success:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.jobs-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.table thead {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.table th {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table td {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-badge.status-running {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-badge.status-stopped {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.status-badge.status-paused {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status-badge.status-completed {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.status-badge.status-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.btn-group .btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-group .btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-group .btn-success:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.btn-group .btn-warning {
|
||||
background-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-group .btn-warning:hover {
|
||||
background-color: #e0a800;
|
||||
}
|
||||
|
||||
.btn-group .btn-danger {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-group .btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
/* Add Job Form Styles */
|
||||
.cron-add-job {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.add-job-header {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.add-job-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.add-job-header h2 i {
|
||||
margin-right: 10px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.add-job-form {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.form-control[type="number"] {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.form-actions .btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-actions .btn-primary {
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-actions .btn-primary:hover {
|
||||
background-color: #5a6fd8;
|
||||
}
|
||||
|
||||
.form-actions .btn-default {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-actions .btn-default:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
/* Board Operations Styles */
|
||||
.cron-board-operations {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.board-operations-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.board-operations-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.board-operations-header h2 i {
|
||||
margin-right: 10px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.board-operations-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn-success:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn-primary {
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn-primary:hover {
|
||||
background-color: #5a6fd8;
|
||||
}
|
||||
|
||||
.board-operations-stats {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.system-resources {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.resource-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.resource-label {
|
||||
min-width: 120px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.resource-bar {
|
||||
flex: 1;
|
||||
height: 12px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin: 0 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.resource-fill {
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.resource-item:nth-child(1) .resource-fill {
|
||||
background: linear-gradient(90deg, #28a745, #20c997);
|
||||
}
|
||||
|
||||
.resource-item:nth-child(2) .resource-fill {
|
||||
background: linear-gradient(90deg, #007bff, #6f42c1);
|
||||
}
|
||||
|
||||
.resource-value {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.board-operations-search {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.search-box .form-control {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.board-operations-list {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.operations-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.operations-header h3 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.operations-table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.operations-table .table {
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.operations-table .table th {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.operations-table .table td {
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.board-id {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: #f8f9fa;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operation-type {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.progress-container .progress-bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-container .progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-container .progress-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
min-width: 35px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.pagination .btn {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.pagination .btn:hover {
|
||||
background: #f8f9fa;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.pagination .btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.migration-header,
|
||||
.jobs-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.migration-controls,
|
||||
.jobs-controls {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.add-job-form {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
template(name="cronSettings")
|
||||
ul#cron-setting.setting-detail
|
||||
li
|
||||
h3 {{_ 'cron-migrations'}}
|
||||
.form-group
|
||||
label {{_ 'migration-status'}}
|
||||
.status-indicator
|
||||
span.status-label {{_ 'status'}}:
|
||||
span.status-value {{migrationStatus}}
|
||||
.progress-section
|
||||
.progress
|
||||
.progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
|
||||
| {{migrationProgress}}%
|
||||
.progress-text
|
||||
| {{migrationProgress}}% {{_ 'complete'}}
|
||||
|
||||
.form-group
|
||||
button.js-start-all-migrations.btn.btn-primary {{_ 'start-all-migrations'}}
|
||||
button.js-pause-all-migrations.btn.btn-warning {{_ 'pause-all-migrations'}}
|
||||
button.js-stop-all-migrations.btn.btn-danger {{_ 'stop-all-migrations'}}
|
||||
|
||||
li
|
||||
h3 {{_ 'board-operations'}}
|
||||
.form-group
|
||||
label {{_ 'scheduled-board-operations'}}
|
||||
button.js-schedule-board-cleanup.btn.btn-primary {{_ 'schedule-board-cleanup'}}
|
||||
button.js-schedule-board-archive.btn.btn-warning {{_ 'schedule-board-archive'}}
|
||||
button.js-schedule-board-backup.btn.btn-info {{_ 'schedule-board-backup'}}
|
||||
|
||||
li
|
||||
h3 {{_ 'cron-jobs'}}
|
||||
.form-group
|
||||
label {{_ 'active-cron-jobs'}}
|
||||
each cronJobs
|
||||
.job-item
|
||||
.job-info
|
||||
.job-name {{name}}
|
||||
.job-schedule {{schedule}}
|
||||
.job-description {{description}}
|
||||
.job-actions
|
||||
button.js-pause-job.btn.btn-sm.btn-warning(data-job-id="{{_id}}") {{_ 'pause'}}
|
||||
button.js-delete-job.btn.btn-sm.btn-danger(data-job-id="{{_id}}") {{_ 'delete'}}
|
||||
.add-job-section
|
||||
button.js-add-cron-job.btn.btn-success {{_ 'add-cron-job'}}
|
||||
|
||||
template(name="cronMigrations")
|
||||
.cron-migrations
|
||||
.migration-header
|
||||
h2
|
||||
i.fa.fa-database
|
||||
| {{_ 'database-migrations'}}
|
||||
.migration-controls
|
||||
button.btn.btn-primary.js-start-all-migrations
|
||||
i.fa.fa-play
|
||||
| {{_ 'start-all-migrations'}}
|
||||
button.btn.btn-warning.js-pause-all-migrations
|
||||
i.fa.fa-pause
|
||||
| {{_ 'pause-all-migrations'}}
|
||||
button.btn.btn-danger.js-stop-all-migrations
|
||||
i.fa.fa-stop
|
||||
| {{_ 'stop-all-migrations'}}
|
||||
|
||||
.migration-progress
|
||||
.progress-overview
|
||||
.progress-bar
|
||||
.progress-fill(style="width: {{migrationProgress}}%")
|
||||
.progress-text {{migrationProgress}}%
|
||||
.progress-label {{_ 'overall-progress'}}
|
||||
|
||||
.current-step
|
||||
i.fa.fa-cog.fa-spin
|
||||
| {{migrationCurrentStep}}
|
||||
|
||||
.migration-status
|
||||
i.fa.fa-info-circle
|
||||
| {{migrationStatus}}
|
||||
|
||||
.migration-steps
|
||||
h3 {{_ 'migration-steps'}}
|
||||
.steps-list
|
||||
each migrationSteps
|
||||
.migration-step(class="{{#if completed}}completed{{/if}}" class="{{#if isCurrentStep}}current{{/if}}")
|
||||
.step-header
|
||||
.step-icon
|
||||
if completed
|
||||
i.fa.fa-check-circle
|
||||
else if isCurrentStep
|
||||
i.fa.fa-cog.fa-spin
|
||||
else
|
||||
i.fa.fa-circle-o
|
||||
.step-info
|
||||
.step-name {{name}}
|
||||
.step-description {{description}}
|
||||
.step-progress
|
||||
if completed
|
||||
.progress-text 100%
|
||||
else if isCurrentStep
|
||||
.progress-text {{progress}}%
|
||||
else
|
||||
.progress-text 0%
|
||||
if isCurrentStep
|
||||
.step-progress-bar
|
||||
.progress-fill(style="width: {{progress}}%")
|
||||
|
||||
template(name="cronBoardOperations")
|
||||
.cron-board-operations
|
||||
.board-operations-header
|
||||
h2
|
||||
i.fa.fa-tasks
|
||||
| {{_ 'board-operations'}}
|
||||
.board-operations-controls
|
||||
button.btn.btn-success.js-refresh-board-operations
|
||||
i.fa.fa-refresh
|
||||
| {{_ 'refresh'}}
|
||||
button.btn.btn-primary.js-start-test-operation
|
||||
i.fa.fa-play
|
||||
| {{_ 'start-test-operation'}}
|
||||
button.btn.btn-info.js-force-board-scan
|
||||
i.fa.fa-search
|
||||
| {{_ 'force-board-scan'}}
|
||||
|
||||
.board-operations-stats
|
||||
.stats-grid
|
||||
.stat-item
|
||||
.stat-value {{operationStats.total}}
|
||||
.stat-label {{_ 'total-operations'}}
|
||||
.stat-item
|
||||
.stat-value {{operationStats.running}}
|
||||
.stat-label {{_ 'running'}}
|
||||
.stat-item
|
||||
.stat-value {{operationStats.completed}}
|
||||
.stat-label {{_ 'completed'}}
|
||||
.stat-item
|
||||
.stat-value {{operationStats.error}}
|
||||
.stat-label {{_ 'errors'}}
|
||||
.stat-item
|
||||
.stat-value {{queueStats.pending}}
|
||||
.stat-label {{_ 'pending'}}
|
||||
.stat-item
|
||||
.stat-value {{queueStats.maxConcurrent}}
|
||||
.stat-label {{_ 'max-concurrent'}}
|
||||
.stat-item
|
||||
.stat-value {{boardMigrationStats.unmigratedCount}}
|
||||
.stat-label {{_ 'unmigrated-boards'}}
|
||||
.stat-item
|
||||
.stat-value {{boardMigrationStats.isScanning}}
|
||||
.stat-label {{_ 'scanning-status'}}
|
||||
|
||||
.system-resources
|
||||
.resource-item
|
||||
.resource-label {{_ 'cpu-usage'}}
|
||||
.resource-bar
|
||||
.resource-fill(style="width: {{systemResources.cpuUsage}}%")
|
||||
.resource-value {{systemResources.cpuUsage}}%
|
||||
.resource-item
|
||||
.resource-label {{_ 'memory-usage'}}
|
||||
.resource-bar
|
||||
.resource-fill(style="width: {{systemResources.memoryUsage}}%")
|
||||
.resource-value {{systemResources.memoryUsage}}%
|
||||
.resource-item
|
||||
.resource-label {{_ 'cpu-cores'}}
|
||||
.resource-value {{systemResources.cpuCores}}
|
||||
|
||||
.board-operations-search
|
||||
.search-box
|
||||
input.form-control.js-search-board-operations(type="text" placeholder="{{_ 'search-boards-or-operations'}}")
|
||||
i.fa.fa-search.search-icon
|
||||
|
||||
.board-operations-list
|
||||
.operations-header
|
||||
h3 {{_ 'board-operations'}} ({{pagination.total}})
|
||||
.pagination-info
|
||||
| {{_ 'showing'}} {{pagination.start}} - {{pagination.end}} {{_ 'of'}} {{pagination.total}}
|
||||
|
||||
.operations-table
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th {{_ 'board-id'}}
|
||||
th {{_ 'operation-type'}}
|
||||
th {{_ 'status'}}
|
||||
th {{_ 'progress'}}
|
||||
th {{_ 'start-time'}}
|
||||
th {{_ 'duration'}}
|
||||
th {{_ 'actions'}}
|
||||
tbody
|
||||
each boardOperations
|
||||
tr
|
||||
td
|
||||
.board-id {{boardId}}
|
||||
td
|
||||
.operation-type {{operationType}}
|
||||
td
|
||||
span.status-badge(class="status-{{status}}") {{status}}
|
||||
td
|
||||
.progress-container
|
||||
.progress-bar
|
||||
.progress-fill(style="width: {{progress}}%")
|
||||
.progress-text {{progress}}%
|
||||
td {{formatDateTime startTime}}
|
||||
td {{formatDuration startTime endTime}}
|
||||
td
|
||||
.btn-group
|
||||
if isRunning
|
||||
button.btn.btn-sm.btn-warning.js-pause-operation(data-operation="{{id}}")
|
||||
i.fa.fa-pause
|
||||
else
|
||||
button.btn.btn-sm.btn-success.js-resume-operation(data-operation="{{id}}")
|
||||
i.fa.fa-play
|
||||
button.btn.btn-sm.btn-danger.js-stop-operation(data-operation="{{id}}")
|
||||
i.fa.fa-stop
|
||||
button.btn.btn-sm.btn-info.js-view-details(data-operation="{{id}}")
|
||||
i.fa.fa-info-circle
|
||||
|
||||
.pagination
|
||||
if pagination.hasPrev
|
||||
button.btn.btn-sm.btn-default.js-prev-page
|
||||
i.fa.fa-chevron-left
|
||||
| {{_ 'previous'}}
|
||||
.page-info
|
||||
| {{_ 'page'}} {{pagination.page}} {{_ 'of'}} {{pagination.totalPages}}
|
||||
if pagination.hasNext
|
||||
button.btn.btn-sm.btn-default.js-next-page
|
||||
| {{_ 'next'}}
|
||||
i.fa.fa-chevron-right
|
||||
|
||||
template(name="cronJobs")
|
||||
.cron-jobs
|
||||
.jobs-header
|
||||
h2
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'cron-jobs'}}
|
||||
.jobs-controls
|
||||
button.btn.btn-success.js-refresh-jobs
|
||||
i.fa.fa-refresh
|
||||
| {{_ 'refresh'}}
|
||||
|
||||
.jobs-list
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th {{_ 'job-name'}}
|
||||
th {{_ 'schedule'}}
|
||||
th {{_ 'status'}}
|
||||
th {{_ 'last-run'}}
|
||||
th {{_ 'next-run'}}
|
||||
th {{_ 'actions'}}
|
||||
tbody
|
||||
each cronJobs
|
||||
tr
|
||||
td {{name}}
|
||||
td {{schedule}}
|
||||
td
|
||||
span.status-badge(class="status-{{status}}") {{status}}
|
||||
td {{formatDate lastRun}}
|
||||
td {{formatDate nextRun}}
|
||||
td
|
||||
.btn-group
|
||||
if isRunning
|
||||
button.btn.btn-sm.btn-warning.js-pause-job(data-job="{{name}}")
|
||||
i.fa.fa-pause
|
||||
else
|
||||
button.btn.btn-sm.btn-success.js-start-job(data-job="{{name}}")
|
||||
i.fa.fa-play
|
||||
button.btn.btn-sm.btn-danger.js-stop-job(data-job="{{name}}")
|
||||
i.fa.fa-stop
|
||||
button.btn.btn-sm.btn-danger.js-remove-job(data-job="{{name}}")
|
||||
i.fa.fa-trash
|
||||
|
||||
template(name="cronAddJob")
|
||||
.cron-add-job
|
||||
.add-job-header
|
||||
h2
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-cron-job'}}
|
||||
|
||||
.add-job-form
|
||||
form.js-add-cron-job-form
|
||||
.form-group
|
||||
label(for="job-name") {{_ 'job-name'}}
|
||||
input.form-control#job-name(type="text" name="name" required)
|
||||
|
||||
.form-group
|
||||
label(for="job-description") {{_ 'job-description'}}
|
||||
textarea.form-control#job-description(name="description" rows="3")
|
||||
|
||||
.form-group
|
||||
label(for="job-schedule") {{_ 'schedule'}}
|
||||
select.form-control#job-schedule(name="schedule")
|
||||
option(value="every 1 minute") {{_ 'every-1-minute'}}
|
||||
option(value="every 5 minutes") {{_ 'every-5-minutes'}}
|
||||
option(value="every 10 minutes") {{_ 'every-10-minutes'}}
|
||||
option(value="every 30 minutes") {{_ 'every-30-minutes'}}
|
||||
option(value="every 1 hour") {{_ 'every-1-hour'}}
|
||||
option(value="every 6 hours") {{_ 'every-6-hours'}}
|
||||
option(value="every 1 day") {{_ 'every-1-day'}}
|
||||
option(value="once") {{_ 'run-once'}}
|
||||
|
||||
.form-group
|
||||
label(for="job-weight") {{_ 'weight'}}
|
||||
input.form-control#job-weight(type="number" name="weight" value="1" min="1" max="10")
|
||||
|
||||
.form-actions
|
||||
button.btn.btn-primary(type="submit")
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-job'}}
|
||||
button.btn.btn-default.js-cancel-add-job
|
||||
i.fa.fa-times
|
||||
| {{_ 'cancel'}}
|
||||
@@ -0,0 +1,47 @@
|
||||
.text-red {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
td i.fa-lock.text-red,
|
||||
li i.fa-lock.text-red {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.locked-users-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.locked-users-table th,
|
||||
.locked-users-table td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.locked-users-table th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.locked-users-table tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-indicator i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.locked-users-settings {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
button.js-unlock-all-users {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import LockoutSettings from '/models/lockoutSettings';
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.lockedUsers = new ReactiveVar([]);
|
||||
this.isLoadingLockedUsers = new ReactiveVar(false);
|
||||
|
||||
// Don't load immediately to prevent unnecessary spinner
|
||||
// The data will be loaded when the tab is selected in peopleBody.js switchMenu
|
||||
},
|
||||
|
||||
refreshLockedUsers() {
|
||||
// Set loading state initially, but we'll hide it if no users are found
|
||||
this.isLoadingLockedUsers.set(true);
|
||||
|
||||
Meteor.call('getLockedUsers', (err, users) => {
|
||||
if (err) {
|
||||
this.isLoadingLockedUsers.set(false);
|
||||
const reason = err.reason || '';
|
||||
const message = `${TAPi18n.__(err.error)}\n${reason}`;
|
||||
alert(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// If no users are locked, don't show loading spinner and set empty array
|
||||
if (!users || users.length === 0) {
|
||||
this.isLoadingLockedUsers.set(false);
|
||||
this.lockedUsers.set([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the remaining time to be more human-readable
|
||||
users.forEach(user => {
|
||||
if (user.remainingLockTime > 60) {
|
||||
const minutes = Math.floor(user.remainingLockTime / 60);
|
||||
const seconds = user.remainingLockTime % 60;
|
||||
user.remainingTimeFormatted = `${minutes}m ${seconds}s`;
|
||||
} else {
|
||||
user.remainingTimeFormatted = `${user.remainingLockTime}s`;
|
||||
}
|
||||
});
|
||||
|
||||
this.lockedUsers.set(users);
|
||||
this.isLoadingLockedUsers.set(false);
|
||||
});
|
||||
},
|
||||
|
||||
unlockUser(event) {
|
||||
const userId = $(event.currentTarget).data('user-id');
|
||||
if (!userId) return;
|
||||
|
||||
if (confirm(TAPi18n.__('accounts-lockout-confirm-unlock'))) {
|
||||
Meteor.call('unlockUser', userId, (err, result) => {
|
||||
if (err) {
|
||||
const reason = err.reason || '';
|
||||
const message = `${TAPi18n.__(err.error)}\n${reason}`;
|
||||
alert(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
alert(TAPi18n.__('accounts-lockout-user-unlocked'));
|
||||
this.refreshLockedUsers();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
unlockAllUsers() {
|
||||
if (confirm(TAPi18n.__('accounts-lockout-confirm-unlock-all'))) {
|
||||
Meteor.call('unlockAllUsers', (err, result) => {
|
||||
if (err) {
|
||||
const reason = err.reason || '';
|
||||
const message = `${TAPi18n.__(err.error)}\n${reason}`;
|
||||
alert(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
alert(TAPi18n.__('accounts-lockout-user-unlocked'));
|
||||
this.refreshLockedUsers();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
saveLockoutSettings() {
|
||||
// Get values from form
|
||||
const knownFailuresBeforeLockout = parseInt($('#known-failures-before-lockout').val(), 10) || 3;
|
||||
const knownLockoutPeriod = parseInt($('#known-lockout-period').val(), 10) || 60;
|
||||
const knownFailureWindow = parseInt($('#known-failure-window').val(), 10) || 15;
|
||||
|
||||
const unknownFailuresBeforeLockout = parseInt($('#unknown-failures-before-lockout').val(), 10) || 3;
|
||||
const unknownLockoutPeriod = parseInt($('#unknown-lockout-period').val(), 10) || 60;
|
||||
const unknownFailureWindow = parseInt($('#unknown-failure-window').val(), 10) || 15;
|
||||
|
||||
// Update the database
|
||||
LockoutSettings.update('known-failuresBeforeLockout', {
|
||||
$set: { value: knownFailuresBeforeLockout },
|
||||
});
|
||||
LockoutSettings.update('known-lockoutPeriod', {
|
||||
$set: { value: knownLockoutPeriod },
|
||||
});
|
||||
LockoutSettings.update('known-failureWindow', {
|
||||
$set: { value: knownFailureWindow },
|
||||
});
|
||||
|
||||
LockoutSettings.update('unknown-failuresBeforeLockout', {
|
||||
$set: { value: unknownFailuresBeforeLockout },
|
||||
});
|
||||
LockoutSettings.update('unknown-lockoutPeriod', {
|
||||
$set: { value: unknownLockoutPeriod },
|
||||
});
|
||||
LockoutSettings.update('unknown-failureWindow', {
|
||||
$set: { value: unknownFailureWindow },
|
||||
});
|
||||
|
||||
// Reload the AccountsLockout configuration
|
||||
Meteor.call('reloadAccountsLockout', (err, ret) => {
|
||||
if (!err && ret) {
|
||||
const message = TAPi18n.__('accounts-lockout-settings-updated');
|
||||
alert(message);
|
||||
} else {
|
||||
const reason = err?.reason || '';
|
||||
const message = `${TAPi18n.__(err?.error || 'error-updating-settings')}\n${reason}`;
|
||||
alert(message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
knownFailuresBeforeLockout() {
|
||||
return LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3;
|
||||
},
|
||||
|
||||
knownLockoutPeriod() {
|
||||
return LockoutSettings.findOne('known-lockoutPeriod')?.value || 60;
|
||||
},
|
||||
|
||||
knownFailureWindow() {
|
||||
return LockoutSettings.findOne('known-failureWindow')?.value || 15;
|
||||
},
|
||||
|
||||
unknownFailuresBeforeLockout() {
|
||||
return LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3;
|
||||
},
|
||||
|
||||
unknownLockoutPeriod() {
|
||||
return LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60;
|
||||
},
|
||||
|
||||
unknownFailureWindow() {
|
||||
return LockoutSettings.findOne('unknown-failureWindow')?.value || 15;
|
||||
},
|
||||
|
||||
lockedUsers() {
|
||||
return this.lockedUsers.get();
|
||||
},
|
||||
|
||||
isLoadingLockedUsers() {
|
||||
return this.isLoadingLockedUsers.get();
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click button.js-refresh-locked-users': this.refreshLockedUsers,
|
||||
'click button#refreshLockedUsers': this.refreshLockedUsers,
|
||||
'click button.js-unlock-user': this.unlockUser,
|
||||
'click button.js-unlock-all-users': this.unlockAllUsers,
|
||||
'click button.js-lockout-save': this.saveLockoutSettings,
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('lockedUsersGeneral');
|
||||
@@ -1,11 +1,16 @@
|
||||
.main-body {
|
||||
overflow: scroll;
|
||||
}
|
||||
/* Scrollbar styles are now handled by settingBody.css for all admin pages */
|
||||
table {
|
||||
color: #000;
|
||||
min-width: 1200px !important;
|
||||
width: max-content !important;
|
||||
table-layout: auto !important;
|
||||
}
|
||||
|
||||
/* Force People/Organizations/Teams tables to always trigger horizontal scrolling */
|
||||
table td,
|
||||
table th {
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
border: 1px solid #d2d0d0;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
@@ -89,3 +94,94 @@ table tr:nth-child(even) {
|
||||
#deleteAction {
|
||||
margin-left: 5% !important;
|
||||
}
|
||||
|
||||
.divLockedUsersFilter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.divLockedUsersFilter .flex-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.divLockedUsersFilter .people-filter {
|
||||
margin-bottom: 0;
|
||||
color: #777;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
.divLockedUsersFilter .user-filter {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
padding: 4px 8px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.unlock-all-btn {
|
||||
margin-left: 15px;
|
||||
background-color: white;
|
||||
color: #4d4d4d;
|
||||
border: 1px solid rgba(0,0,0,.15);
|
||||
border-radius: 2px;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
height: 28px;
|
||||
min-width: 90px;
|
||||
/* box-shadow: 0 1px 6px rgba(0,0,0,.3); */
|
||||
}
|
||||
|
||||
.unlock-all-btn:hover {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.account-active-status {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.js-toggle-active-status {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.unlock-all-success {
|
||||
position: fixed;
|
||||
top: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
animation: fadeOut 3s ease-in forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% { opacity: 1; }
|
||||
70% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.account-status {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-green {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.js-toggle-lock-status {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.unlock-all-btn .fa {
|
||||
color: #4d4d4d;
|
||||
}
|
||||
@@ -38,12 +38,28 @@ template(name="people")
|
||||
button#searchButton
|
||||
i.fa.fa-search
|
||||
| {{_ 'search'}}
|
||||
.divLockedUsersFilter
|
||||
.flex-container
|
||||
span.people-filter {{_ 'admin-people-filter-show'}}
|
||||
select.user-filter#userFilterSelect
|
||||
option(value="all") {{_ 'admin-people-filter-all'}}
|
||||
option(value="locked") {{_ 'admin-people-filter-locked'}}
|
||||
option(value="active") {{_ 'admin-people-filter-active'}}
|
||||
option(value="inactive") {{_ 'admin-people-filter-inactive'}}
|
||||
button#unlockAllUsers.unlock-all-btn
|
||||
i.fa.fa-unlock
|
||||
| {{_ 'accounts-lockout-unlock-all'}}
|
||||
.ext-box-right
|
||||
span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber}
|
||||
.divAddOrRemoveTeam#divAddOrRemoveTeam
|
||||
button#addOrRemoveTeam
|
||||
i.fa.fa-edit
|
||||
| {{_ 'add'}} / {{_ 'delete'}} {{_ 'teams'}}
|
||||
else if lockedUsersSetting.get
|
||||
span
|
||||
i.fa.fa-lock.text-red
|
||||
unless isMiniScreen
|
||||
| {{_ 'accounts-lockout-locked-users'}}
|
||||
|
||||
.content-body
|
||||
.side-menu
|
||||
@@ -60,6 +76,10 @@ template(name="people")
|
||||
a.js-people-menu(data-id="people-setting")
|
||||
i.fa.fa-user
|
||||
| {{_ 'people'}}
|
||||
li
|
||||
a.js-locked-users-menu(data-id="locked-users-setting")
|
||||
i.fa.fa-lock.text-red
|
||||
| {{_ 'accounts-lockout-locked-users'}}
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
@@ -69,6 +89,8 @@ template(name="people")
|
||||
+teamGeneral
|
||||
else if peopleSetting.get
|
||||
+peopleGeneral
|
||||
else if lockedUsersSetting.get
|
||||
+lockedUsersGeneral
|
||||
|
||||
|
||||
template(name="orgGeneral")
|
||||
@@ -114,6 +136,8 @@ template(name="peopleGeneral")
|
||||
tr
|
||||
th
|
||||
+selectAllUser
|
||||
th {{_ 'accounts-lockout-status'}}
|
||||
th {{_ 'admin-people-active-status'}}
|
||||
th {{_ 'username'}}
|
||||
th {{_ 'fullname'}}
|
||||
th {{_ 'initials'}}
|
||||
@@ -232,8 +256,20 @@ template(name="peopleRow")
|
||||
else
|
||||
td
|
||||
input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
|
||||
td.account-status
|
||||
if isUserLocked
|
||||
i.fa.fa-lock.text-red.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}")
|
||||
else
|
||||
i.fa.fa-unlock.text-green.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}")
|
||||
td.account-active-status
|
||||
if userData.loginDisabled
|
||||
i.fa.fa-ban.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}")
|
||||
else
|
||||
i.fa.fa-check-circle.text-green.js-toggle-active-status(data-user-id=userData._id, data-is-active="true", title="{{_ 'admin-people-user-active'}}")
|
||||
if userData.loginDisabled
|
||||
td.username <s>{{ userData.username }}</s>
|
||||
else if isUserLocked
|
||||
td.username {{ userData.username }}
|
||||
else
|
||||
td.username {{ userData.username }}
|
||||
if userData.loginDisabled
|
||||
@@ -645,3 +681,32 @@ template(name="settingsUserPopup")
|
||||
// that does now remove member from board, card members and assignees correctly,
|
||||
// but that should be used to remove user from all boards similarly
|
||||
// - wekan/models/users.js Delete is not enabled
|
||||
|
||||
template(name="lockedUsersGeneral")
|
||||
.locked-users-settings
|
||||
h3 {{_ 'accounts-lockout-settings'}}
|
||||
p {{_ 'accounts-lockout-info'}}
|
||||
|
||||
h4 {{_ 'accounts-lockout-known-users'}}
|
||||
.title {{_ 'accounts-lockout-failures-before'}}
|
||||
.form-group
|
||||
input.wekan-form-control#known-failures-before-lockout(type="number", min="1", max="10", placeholder="3" value="{{knownFailuresBeforeLockout}}")
|
||||
.title {{_ 'accounts-lockout-period'}}
|
||||
.form-group
|
||||
input.wekan-form-control#known-lockout-period(type="number", min="10", max="600", placeholder="60" value="{{knownLockoutPeriod}}")
|
||||
.title {{_ 'accounts-lockout-failure-window'}}
|
||||
.form-group
|
||||
input.wekan-form-control#known-failure-window(type="number", min="1", max="60", placeholder="15" value="{{knownFailureWindow}}")
|
||||
|
||||
h4 {{_ 'accounts-lockout-unknown-users'}}
|
||||
.title {{_ 'accounts-lockout-failures-before'}}
|
||||
.form-group
|
||||
input.wekan-form-control#unknown-failures-before-lockout(type="number", min="1", max="10", placeholder="3" value="{{unknownFailuresBeforeLockout}}")
|
||||
.title {{_ 'accounts-lockout-period'}}
|
||||
.form-group
|
||||
input.wekan-form-control#unknown-lockout-period(type="number", min="10", max="600", placeholder="60" value="{{unknownLockoutPeriod}}")
|
||||
.title {{_ 'accounts-lockout-failure-window'}}
|
||||
.form-group
|
||||
input.wekan-form-control#unknown-failure-window(type="number", min="1", max="60", placeholder="15" value="{{unknownFailureWindow}}")
|
||||
|
||||
button.js-lockout-save.primary {{_ 'save'}}
|
||||
Loaded 100 of 642 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user