mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-01-23 10:45:47 +00:00
Compare commits
4127 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78e87796df | ||
|
|
75ad4415c4 | ||
|
|
c5d9bd7c1f | ||
|
|
f437a52ff0 | ||
|
|
7192a56e94 | ||
|
|
b9dd4f2676 | ||
|
|
e8cf3c92f9 | ||
|
|
d574a93d6d | ||
|
|
8a769fd512 | ||
|
|
3b409074c1 | ||
|
|
3b6b0b5a5b | ||
|
|
8876f050e0 | ||
|
|
8507282d55 | ||
|
|
f61b21eecc | ||
|
|
242820b7cf | ||
|
|
dd240ba9b2 | ||
|
|
4b945c0047 | ||
|
|
23a6a6981a | ||
|
|
9cebb2416f | ||
|
|
a94fa0d465 | ||
|
|
3bdf47a5bb | ||
|
|
0ccd109a50 | ||
|
|
6b0bd2e1de | ||
|
|
c23a6a12c8 | ||
|
|
a523ef65f5 | ||
|
|
0971ac4e94 | ||
|
|
64654e8d5c | ||
|
|
a063353a27 | ||
|
|
add31607f9 | ||
|
|
e36de00ce8 | ||
|
|
2c1210bbb1 | ||
|
|
85ebf21122 | ||
|
|
450f14614d | ||
|
|
f75bf4a401 | ||
|
|
d979e94927 | ||
|
|
8de9bd0847 | ||
|
|
e178321be9 | ||
|
|
4b84d20bb0 | ||
|
|
6e778a35ba | ||
|
|
e27ef51d43 | ||
|
|
917f585137 | ||
|
|
9950cacad9 | ||
|
|
457e23f31d | ||
|
|
cfa0b5e382 | ||
|
|
f903c5c9c5 | ||
|
|
08dd5ac183 | ||
|
|
9e2c5ac635 | ||
|
|
6856207d0d | ||
|
|
f0dcbe946f | ||
|
|
008279df57 | ||
|
|
dc99417c27 | ||
|
|
12751646fe | ||
|
|
8234201a39 | ||
|
|
4bb7d704b4 | ||
|
|
feb5aad36b | ||
|
|
c231fca334 | ||
|
|
14452d05b6 | ||
|
|
5c1ad5f8ff | ||
|
|
5b4bb90d61 | ||
|
|
5cc6f79d6a | ||
|
|
024bc41345 | ||
|
|
fb115c675e | ||
|
|
53f6f9e76f | ||
|
|
6d21c539d4 | ||
|
|
253389ea7b | ||
|
|
cdf11f7483 | ||
|
|
0ff16314d7 | ||
|
|
cc889bda4f | ||
|
|
43edba8cb8 | ||
|
|
06f3b78256 | ||
|
|
dfcd655780 | ||
|
|
ab20a462db | ||
|
|
2ec57132ed | ||
|
|
0222d177fc | ||
|
|
db0e0aef73 | ||
|
|
14ec19e80a | ||
|
|
e6a00979ff | ||
|
|
5b5352c7d9 | ||
|
|
78ddbd8a39 | ||
|
|
0067653109 | ||
|
|
5a2ac765d4 | ||
|
|
3ceb9a8c59 | ||
|
|
2a525e9bbb | ||
|
|
a49e963dc9 | ||
|
|
1037a1a264 | ||
|
|
8477f3e466 | ||
|
|
e5d004c0af | ||
|
|
836e908e11 | ||
|
|
9fd4a451ab | ||
|
|
f4386a6f0f | ||
|
|
91f9c0a1aa | ||
|
|
babeb9fb85 | ||
|
|
2e667ba693 | ||
|
|
07d18cfe96 | ||
|
|
60c56164b0 | ||
|
|
d8453236c2 | ||
|
|
efe81320e1 | ||
|
|
14e6754be3 | ||
|
|
3033aed2e3 | ||
|
|
5b972e2e4a | ||
|
|
543e08a837 | ||
|
|
2a8109f090 | ||
|
|
39a24028ac | ||
|
|
3f3c644500 | ||
|
|
38dd510158 | ||
|
|
64df81bc45 | ||
|
|
8366b3bbe2 | ||
|
|
66db798c35 | ||
|
|
56ed1cd535 | ||
|
|
c5aed16f63 | ||
|
|
1537fa73fc | ||
|
|
511347d3ef | ||
|
|
c963ffefee | ||
|
|
fc6304a1c6 | ||
|
|
a05ecdb406 | ||
|
|
9e4d66df4d | ||
|
|
3f7e17721e | ||
|
|
50b19dcc44 | ||
|
|
7fb130cdbd | ||
|
|
5970f10a84 | ||
|
|
84a51a932c | ||
|
|
b0360d8ef4 | ||
|
|
00a20129de | ||
|
|
5e657e9bd1 | ||
|
|
2559343eef | ||
|
|
ddf9e45567 | ||
|
|
84bfef562d | ||
|
|
2e7bdb8aa0 | ||
|
|
a2f66951a8 | ||
|
|
03631d95f7 | ||
|
|
1fc57fdb91 | ||
|
|
09408af5dd | ||
|
|
4eb47f9d42 | ||
|
|
4fcaf2885d | ||
|
|
eaea183536 | ||
|
|
c69ec16ef0 | ||
|
|
3cf13a92c0 | ||
|
|
08d13c6d6b | ||
|
|
df79dda4d0 | ||
|
|
b4792fc3f2 | ||
|
|
c164766895 | ||
|
|
0584f4f431 | ||
|
|
67e4b8f0e4 | ||
|
|
876bbfded5 | ||
|
|
15b71c1485 | ||
|
|
d252fe5fcb | ||
|
|
25119ac6ea | ||
|
|
fc20b9a4e1 | ||
|
|
faa2f9c765 | ||
|
|
08b5c6b2b5 | ||
|
|
83a1e527e5 | ||
|
|
a6d18ddb88 | ||
|
|
7db31363ff | ||
|
|
2077468a85 | ||
|
|
e3fcb7f24e | ||
|
|
64e4aba414 | ||
|
|
899266a62a | ||
|
|
d568a4d896 | ||
|
|
884c83eb60 | ||
|
|
b34ee44bb1 | ||
|
|
c3251bf5e1 | ||
|
|
54bbd4e34d | ||
|
|
3d13b9c5f8 | ||
|
|
61107329fc | ||
|
|
f0bc286aea | ||
|
|
8eb724b58c | ||
|
|
784bb2ebdd | ||
|
|
8f52376d3f | ||
|
|
ed31dd2409 | ||
|
|
8200874912 | ||
|
|
4b8568b264 | ||
|
|
de41aec77d | ||
|
|
74f5eb0cae | ||
|
|
1d983e9950 | ||
|
|
f9b3844153 | ||
|
|
546d061068 | ||
|
|
b1e231a5bb | ||
|
|
121b114e89 | ||
|
|
8592cedce0 | ||
|
|
a53ab67b7d | ||
|
|
de2cedd99d | ||
|
|
da545ea418 | ||
|
|
db6e8334b8 | ||
|
|
2f0c1a616c | ||
|
|
e100dcf627 | ||
|
|
76c4000890 | ||
|
|
fb5e5a320f | ||
|
|
8551cd54a6 | ||
|
|
c93803190b | ||
|
|
ddc94adbf1 | ||
|
|
ef42330848 | ||
|
|
a54caa1d7c | ||
|
|
9eafa189a8 | ||
|
|
baaf47e62f | ||
|
|
0d61a972fb | ||
|
|
af77eeed8d | ||
|
|
e99d084728 | ||
|
|
b77e9c91d4 | ||
|
|
4b476a6dff | ||
|
|
2057065dbf | ||
|
|
a87e5ceaf6 | ||
|
|
2eb3dc6692 | ||
|
|
5f0391fc44 | ||
|
|
1679b788c2 | ||
|
|
9a4cf388d3 | ||
|
|
f4b0f92f05 | ||
|
|
4ab4be124b | ||
|
|
99ad0c217d | ||
|
|
8ccec23d6c | ||
|
|
2a97ac66fb | ||
|
|
b26c8bba37 | ||
|
|
4b78d70b5c | ||
|
|
8450bfa611 | ||
|
|
84c6935ae4 | ||
|
|
9f52ed795d | ||
|
|
f7379c0562 | ||
|
|
51f51b54de | ||
|
|
e87418adde | ||
|
|
08ab63d704 | ||
|
|
e7cc9b92ae | ||
|
|
368c9bb814 | ||
|
|
43fd5ed660 | ||
|
|
f774d5b290 | ||
|
|
b0a7fc1648 | ||
|
|
f1193955fb | ||
|
|
2d21001e55 | ||
|
|
99118e65a6 | ||
|
|
8ff0ee6a1a | ||
|
|
ac9a27b333 | ||
|
|
84719365b1 | ||
|
|
34fb175d63 | ||
|
|
dbd8c6ab7c | ||
|
|
716765104c | ||
|
|
51a4cee688 | ||
|
|
b2f113bf0a | ||
|
|
2e02ba46e8 | ||
|
|
38666cbb8b | ||
|
|
53acae1a63 | ||
|
|
e2591061af | ||
|
|
3d52ce80f2 | ||
|
|
a598ef3ed0 | ||
|
|
e71550d557 | ||
|
|
56d621b1ff | ||
|
|
a38b3a79ce | ||
|
|
7ae60605bb | ||
|
|
7492b4bfc7 | ||
|
|
3adfec81c2 | ||
|
|
c68a3a7f07 | ||
|
|
4eaeccba1c | ||
|
|
2dea0a3b2d | ||
|
|
467f0a79c3 | ||
|
|
353a1fb6c1 | ||
|
|
8e98b778bd | ||
|
|
3ad6720f5f | ||
|
|
b0be119474 | ||
|
|
161d4f952b | ||
|
|
301252906f | ||
|
|
ddd1722f39 | ||
|
|
907afc6e4a | ||
|
|
d8da7922d9 | ||
|
|
2fc503f71f | ||
|
|
ad8e55d824 | ||
|
|
5d22722010 | ||
|
|
13cb970a55 | ||
|
|
94ebbf84cb | ||
|
|
100e940e4b | ||
|
|
a6ce6d3953 | ||
|
|
da0e99ad8c | ||
|
|
dff0267239 | ||
|
|
03e3ba6ad8 | ||
|
|
4c73315b5c | ||
|
|
71dc8dd6be | ||
|
|
f2ef160ca8 | ||
|
|
be4f6271f4 | ||
|
|
a733d81441 | ||
|
|
06cf0eebce | ||
|
|
59024eea98 | ||
|
|
62af673117 | ||
|
|
f22120dc38 | ||
|
|
1a0af863a2 | ||
|
|
0446a74d28 | ||
|
|
5562b47d84 | ||
|
|
7309ceff37 | ||
|
|
ac9abbd385 | ||
|
|
4bc5a7834e | ||
|
|
0f5fe2d4fe | ||
|
|
f5015e75e8 | ||
|
|
ddf4542b75 | ||
|
|
9dbd812b3d | ||
|
|
3d03efbe63 | ||
|
|
a522c49c1f | ||
|
|
9ec94dee15 | ||
|
|
62ed8411ae | ||
|
|
75a75365cf | ||
|
|
ac3f20c5d1 | ||
|
|
9109511e95 | ||
|
|
b63d6fccde | ||
|
|
88df2f0518 | ||
|
|
5e93bcca1e | ||
|
|
74d1eb7e28 | ||
|
|
544e30dbf8 | ||
|
|
282b3d5cca | ||
|
|
a6aa9bbc44 | ||
|
|
3140b7f998 | ||
|
|
d70362fbf1 | ||
|
|
6e8348b843 | ||
|
|
61ca7f36a4 | ||
|
|
b5e9ae5ad9 | ||
|
|
ba2d0b36af | ||
|
|
4cc47e30de | ||
|
|
d8451e56aa | ||
|
|
6abf327d0d | ||
|
|
79fb49479e | ||
|
|
2ae6ad34fc | ||
|
|
14d9618a64 | ||
|
|
05ef0ae452 | ||
|
|
883eee96a2 | ||
|
|
9c8c0533a4 | ||
|
|
14d46c007b | ||
|
|
25872c3b11 | ||
|
|
610ba8827f | ||
|
|
154b4bd627 | ||
|
|
c409a2db82 | ||
|
|
52df5bfcf4 | ||
|
|
2428853613 | ||
|
|
6fb2102099 | ||
|
|
37ab7068d9 | ||
|
|
d061aa82ea | ||
|
|
b088b84eb1 | ||
|
|
fceddc20f8 | ||
|
|
f7a6a36696 | ||
|
|
91de9b3d27 | ||
|
|
2d37f6c6e9 | ||
|
|
d75818297b | ||
|
|
faa2cb3f90 | ||
|
|
29914c09f1 | ||
|
|
7f5e5c6825 | ||
|
|
6bc4f3ec26 | ||
|
|
35622082a9 | ||
|
|
65cf97a7be | ||
|
|
cf9ed97c3e | ||
|
|
cc134464a4 | ||
|
|
e3f89e8845 | ||
|
|
c45b23fe55 | ||
|
|
b1f74c0057 | ||
|
|
bf2e8f9a7d | ||
|
|
f5f34a85ea | ||
|
|
04e5d5b7e0 | ||
|
|
3e565109b3 | ||
|
|
770a0812aa | ||
|
|
25d8faea63 | ||
|
|
98e0011e8e | ||
|
|
401a669aca | ||
|
|
4e32241d83 | ||
|
|
cfa3d69538 | ||
|
|
e01ee4575b | ||
|
|
a03185e14e | ||
|
|
564de89333 | ||
|
|
0655988ed7 | ||
|
|
c7f9090126 | ||
|
|
1bef0d4381 | ||
|
|
fcce26d4e1 | ||
|
|
6e99c7e3c5 | ||
|
|
3f83f6f09a | ||
|
|
bf90bf2295 | ||
|
|
043ba29b32 | ||
|
|
95c36b0a37 | ||
|
|
98d3a4cc56 | ||
|
|
46c8accd84 | ||
|
|
57d2f8e93d | ||
|
|
e080a54022 | ||
|
|
eff34215ee | ||
|
|
4503237ac4 | ||
|
|
bf614e1dc9 | ||
|
|
857c97006d | ||
|
|
b2478a8145 | ||
|
|
3c5ca3dca4 | ||
|
|
a92a5a0d34 | ||
|
|
c51ba1d8f6 | ||
|
|
a57833aaf8 | ||
|
|
0bcff30faa | ||
|
|
10d6d2e247 | ||
|
|
2047cb7ad8 | ||
|
|
6b793cca04 | ||
|
|
5fa9fcc57c | ||
|
|
bc617c17ef | ||
|
|
0ecd1853d0 | ||
|
|
dc2d3146a1 | ||
|
|
266aff4ffb | ||
|
|
a407e0c3fa | ||
|
|
97627dc2ff | ||
|
|
683c865eda | ||
|
|
b795a1f5dd | ||
|
|
d928c0b83d | ||
|
|
1d3567f3a3 | ||
|
|
33201dade2 | ||
|
|
5ced36b0d6 | ||
|
|
7ce954503c | ||
|
|
1c73e525ff | ||
|
|
da967f080c | ||
|
|
f0a6109a7d | ||
|
|
e84c7dcbab | ||
|
|
796ee627e7 | ||
|
|
aca4119fb1 | ||
|
|
ed95dec964 | ||
|
|
5324a41abd | ||
|
|
d453a1b291 | ||
|
|
71b915bed3 | ||
|
|
1ac4191c66 | ||
|
|
4f25500351 | ||
|
|
a0c398264c | ||
|
|
d79a577611 | ||
|
|
8d92aa91e9 | ||
|
|
5ab5576e68 | ||
|
|
c5cfe68c8a | ||
|
|
2fc241a9fa | ||
|
|
81a6b18165 | ||
|
|
084c84a277 | ||
|
|
ba9915a1df | ||
|
|
60b682b62a | ||
|
|
a3d310a3e4 | ||
|
|
bbaad041be | ||
|
|
b22ad537e2 | ||
|
|
154e3b078e | ||
|
|
a02d288df9 | ||
|
|
3314f9b94f | ||
|
|
a6afa205a9 | ||
|
|
e600c05d18 | ||
|
|
99c009542a | ||
|
|
afdf74342a | ||
|
|
552d95013b | ||
|
|
eb606bb608 | ||
|
|
f4a3add818 | ||
|
|
4462f26925 | ||
|
|
0cc76bd4f7 | ||
|
|
949a09340f | ||
|
|
5d6bed3d66 | ||
|
|
656ebd87e0 | ||
|
|
72df4288ef | ||
|
|
48dba5fa7e | ||
|
|
8a85f99eb2 | ||
|
|
2c7782c06a | ||
|
|
fb2d081402 | ||
|
|
f8f6377551 | ||
|
|
b04cbcd8c5 | ||
|
|
094df666f4 | ||
|
|
75bf8a1275 | ||
|
|
93aa7278b7 | ||
|
|
af9c916e08 | ||
|
|
01dccbfd07 | ||
|
|
ea86bd6a2f | ||
|
|
7857fb720d | ||
|
|
900d4307bb | ||
|
|
570cb8c094 | ||
|
|
828d10a8ee | ||
|
|
88908b3528 | ||
|
|
93f4a07ec6 | ||
|
|
a5f93523c1 | ||
|
|
99980c7f1d | ||
|
|
3b0941bc87 | ||
|
|
0dca7b6419 | ||
|
|
35dedfdf95 | ||
|
|
c37979801a | ||
|
|
3c93b29b86 | ||
|
|
7e2587459f | ||
|
|
621f52da2f | ||
|
|
8ab7e468a8 | ||
|
|
eb3f037a02 | ||
|
|
55032ebd7d | ||
|
|
ee5a0fb4b2 | ||
|
|
34258c6f27 | ||
|
|
47893e3b83 | ||
|
|
4c7dd74abd | ||
|
|
224e539708 | ||
|
|
ea929465a8 | ||
|
|
4b9267f375 | ||
|
|
809ef1cf60 | ||
|
|
354c137de6 | ||
|
|
574b425c02 | ||
|
|
e353fff711 | ||
|
|
4b74b8f286 | ||
|
|
339560fab2 | ||
|
|
ba7133f2e6 | ||
|
|
e8ca8cedc5 | ||
|
|
f9aa9911d5 | ||
|
|
fcd06769b2 | ||
|
|
24dcf78be8 | ||
|
|
13279299c4 | ||
|
|
20a00481d6 | ||
|
|
d55b6fb393 | ||
|
|
d63169ccc0 | ||
|
|
bb9276bec5 | ||
|
|
9aed5f751e | ||
|
|
10f564a315 | ||
|
|
c4f56c59d0 | ||
|
|
6b9d43e9ab | ||
|
|
7aacf203a0 | ||
|
|
4717e035ee | ||
|
|
f1279666b5 | ||
|
|
0e663e1dc5 | ||
|
|
ff9265b78b | ||
|
|
22aa337ac0 | ||
|
|
13350b5517 | ||
|
|
ce196abf1e | ||
|
|
3e18f6be28 | ||
|
|
deedca28a9 | ||
|
|
a1bf40bd83 | ||
|
|
d011b13d30 | ||
|
|
ef6088531a | ||
|
|
895d19a153 | ||
|
|
67c0b81067 | ||
|
|
235dc1b8db | ||
|
|
d0e5fc0cbd | ||
|
|
bd7dfd0c28 | ||
|
|
a6c400ae1c | ||
|
|
b336a4726a | ||
|
|
019e15b8bd | ||
|
|
643543002e | ||
|
|
2743788002 | ||
|
|
49280722d1 | ||
|
|
6b22b2412b | ||
|
|
14b05d1b14 | ||
|
|
88da092fdb | ||
|
|
8e25a02568 | ||
|
|
bdddc5752d | ||
|
|
d7e43d8a18 | ||
|
|
656000e7e0 | ||
|
|
ce49d9d6ad | ||
|
|
ed9af6d7a6 | ||
|
|
fd1eb1dc27 | ||
|
|
a27e4ecf61 | ||
|
|
203117d2b9 | ||
|
|
4a38c64cd6 | ||
|
|
31e1eeea50 | ||
|
|
cf41d7d1dd | ||
|
|
2bfd32a1bd | ||
|
|
5b5889dcf7 | ||
|
|
40f17bb670 | ||
|
|
871b05d4d5 | ||
|
|
2f6b07c469 | ||
|
|
fecfa9fa58 | ||
|
|
09de19b46e | ||
|
|
924f8696d0 | ||
|
|
3135314686 | ||
|
|
c83a5222ad | ||
|
|
7fe5a998c4 | ||
|
|
519aec8afe | ||
|
|
8126b51831 | ||
|
|
1309d825e4 | ||
|
|
e28215ddf3 | ||
|
|
026c292bae | ||
|
|
c2c7c74ae8 | ||
|
|
52f47a2434 | ||
|
|
f5df731491 | ||
|
|
d7ab5b912d | ||
|
|
6f737e83da | ||
|
|
18f0f55a64 | ||
|
|
4e52bb1371 | ||
|
|
e9ab141e30 | ||
|
|
ebadb8deeb | ||
|
|
63ecdcbd86 | ||
|
|
10045f6ffb | ||
|
|
bec0d44429 | ||
|
|
0a345a34a1 | ||
|
|
42ddee49a7 | ||
|
|
0c8bf9f3d0 | ||
|
|
b70aa0cb9c | ||
|
|
3aa52812ab | ||
|
|
35a46d0d07 | ||
|
|
5c84d1616c | ||
|
|
24fbeec62d | ||
|
|
a5229fae06 | ||
|
|
1195e570e4 | ||
|
|
c101b039ea | ||
|
|
444f659076 | ||
|
|
de15ed6766 | ||
|
|
d0a8e2daac | ||
|
|
f085c0eb06 | ||
|
|
cb86e78f8b | ||
|
|
10bd261a3f | ||
|
|
c337e4507d | ||
|
|
ba2d02bd3d | ||
|
|
9f644f82b1 | ||
|
|
b6797907a8 | ||
|
|
33827fea5e | ||
|
|
45d44dc74d | ||
|
|
50c3f04dcd | ||
|
|
404142cc40 | ||
|
|
ab6b93ddca | ||
|
|
2c9b23db4e | ||
|
|
8cc9b0ec66 | ||
|
|
ddee470eba | ||
|
|
bf3a9acc86 | ||
|
|
391269b1ed | ||
|
|
e44e4fa47d | ||
|
|
7fdac390de | ||
|
|
9f4a23e0f1 | ||
|
|
42e8e255c0 | ||
|
|
c345d8b7ef | ||
|
|
975d4eb8a0 | ||
|
|
ef10ebe97c | ||
|
|
81e76857da | ||
|
|
b12e0690cd | ||
|
|
3d4ba373a6 | ||
|
|
ed0c2ad45a | ||
|
|
4e406d758f | ||
|
|
18adf0852d | ||
|
|
42248bc0f4 | ||
|
|
b3f3510b2d | ||
|
|
bbf0c04f55 | ||
|
|
6af8f19bb6 | ||
|
|
95ede62e81 | ||
|
|
0c28633bbd | ||
|
|
42c8fd7bfe | ||
|
|
e129ab6a79 | ||
|
|
6e4667aa85 | ||
|
|
46a84c54ea | ||
|
|
c5e3134f37 | ||
|
|
aa0c15f6b6 | ||
|
|
23fcc2b8f7 | ||
|
|
f5f3048908 | ||
|
|
d8d9025286 | ||
|
|
66fdb550a6 | ||
|
|
e59f3cfa71 | ||
|
|
44422a5206 | ||
|
|
b67b7cf73d | ||
|
|
3ba6d4ae53 | ||
|
|
69c8eecaf1 | ||
|
|
b65b3ff940 | ||
|
|
4f06e6398f | ||
|
|
62d39841b1 | ||
|
|
2cfc4908a2 | ||
|
|
177771df6a | ||
|
|
23171f967e | ||
|
|
09f4090a4b | ||
|
|
f2db9bf2d5 | ||
|
|
0ff8470278 | ||
|
|
9b0ce7c7b7 | ||
|
|
e68b3232d9 | ||
|
|
c32ba39f43 | ||
|
|
c7371d9c3a | ||
|
|
3269cff066 | ||
|
|
75b35139e4 | ||
|
|
838b2e29cf | ||
|
|
9adc0778a9 | ||
|
|
130ce433f4 | ||
|
|
cac774d7ce | ||
|
|
7053e6be0b | ||
|
|
2743f78cd3 | ||
|
|
49f4e5bec7 | ||
|
|
228744bec8 | ||
|
|
2654197681 | ||
|
|
205d641e75 | ||
|
|
5accfeeaa7 | ||
|
|
a024dcdb61 | ||
|
|
5b661140fc | ||
|
|
ed14250fe2 | ||
|
|
21e9736c81 | ||
|
|
8097d96c72 | ||
|
|
efb686c332 | ||
|
|
bd5a8afdd5 | ||
|
|
d15134b272 | ||
|
|
b9b4af0454 | ||
|
|
18eeec0165 | ||
|
|
b2a7a6770c | ||
|
|
c40d2bb096 | ||
|
|
c7242199e7 | ||
|
|
f1fcde75c3 | ||
|
|
0f17c11c04 | ||
|
|
57390d541f | ||
|
|
e4cf91ac1d | ||
|
|
8fc0731900 | ||
|
|
ae552ede60 | ||
|
|
3dd9d6e454 | ||
|
|
7258013b88 | ||
|
|
3172759ca0 | ||
|
|
85015881d0 | ||
|
|
977d9d057f | ||
|
|
111eba8c31 | ||
|
|
8f466b4c9a | ||
|
|
057ec42b25 | ||
|
|
eab9df4490 | ||
|
|
b6549a6484 | ||
|
|
883b4487e0 | ||
|
|
699deb86c6 | ||
|
|
8fcfaec8a0 | ||
|
|
a5ea620ca8 | ||
|
|
c798b42a64 | ||
|
|
13f7fb2200 | ||
|
|
7ca9b8cece | ||
|
|
c2e51f75a1 | ||
|
|
4d9878b574 | ||
|
|
abe512c655 | ||
|
|
b67526f09b | ||
|
|
e236e87171 | ||
|
|
26fad02507 | ||
|
|
e73c327687 | ||
|
|
f3e0337584 | ||
|
|
1736ec7be2 | ||
|
|
fbc8ae25ad | ||
|
|
f92152195c | ||
|
|
bbe790e551 | ||
|
|
954f2c047c | ||
|
|
baac531655 | ||
|
|
262c72d44a | ||
|
|
5e63a524c1 | ||
|
|
5d93d5752c | ||
|
|
241efe971b | ||
|
|
8c59333a02 | ||
|
|
9a724a4bba | ||
|
|
8c8ac3bab4 | ||
|
|
843c319b1e | ||
|
|
879bc2ff8c | ||
|
|
3e303253d2 | ||
|
|
ad689ece31 | ||
|
|
1801689ef7 | ||
|
|
e77d8a709a | ||
|
|
b090b498a8 | ||
|
|
b5618412c3 | ||
|
|
bb3e07def3 | ||
|
|
a3f0f4ecae | ||
|
|
df60dd7ba0 | ||
|
|
21e7ea7d4c | ||
|
|
a091a382ca | ||
|
|
0edbffa093 | ||
|
|
08cc5dbba4 | ||
|
|
edbebd983e | ||
|
|
90eae0e944 | ||
|
|
3976056ee8 | ||
|
|
8f12c6c25f | ||
|
|
c92815d78d | ||
|
|
2fe6dcf166 | ||
|
|
33f2572979 | ||
|
|
a71dca0e70 | ||
|
|
cb37ab871b | ||
|
|
f391a3391f | ||
|
|
ce511812d5 | ||
|
|
89af33fb9d | ||
|
|
fec91e7093 | ||
|
|
a092a2fc9d | ||
|
|
775850c004 | ||
|
|
1a5dc6228c | ||
|
|
6622d508af | ||
|
|
a6a1e80afb | ||
|
|
40f885e7c4 | ||
|
|
f0fd4031bb | ||
|
|
0831723565 | ||
|
|
71a7e9dd93 | ||
|
|
07fe299ad6 | ||
|
|
b18c738f5b | ||
|
|
bcc11bda74 | ||
|
|
a108bd82b5 | ||
|
|
e3d373acf9 | ||
|
|
2a5ebe300d | ||
|
|
04e54f2794 | ||
|
|
2e75ca18f4 | ||
|
|
d8f0e92727 | ||
|
|
70c8ab537c | ||
|
|
f97677f384 | ||
|
|
bbb6d46c09 | ||
|
|
0c7cc3c0a2 | ||
|
|
0128014f51 | ||
|
|
75361de125 | ||
|
|
98be1b01f1 | ||
|
|
3ac0bf23e4 | ||
|
|
983ccac4d1 | ||
|
|
47883b8751 | ||
|
|
955b7f0c4c | ||
|
|
77bd89f8f7 | ||
|
|
135edafaee | ||
|
|
fce254f724 | ||
|
|
60607184d4 | ||
|
|
5e545c87b3 | ||
|
|
2b9705d9fc | ||
|
|
0d79531b69 | ||
|
|
b4f279d07c | ||
|
|
3cd1d67e58 | ||
|
|
2fc6c6e1a1 | ||
|
|
a63ce70d49 | ||
|
|
bc80069f18 | ||
|
|
996d99cbac | ||
|
|
a39ea0d61c | ||
|
|
3b81abed36 | ||
|
|
1b5a6a3721 | ||
|
|
b3b618b2ca | ||
|
|
7d1a35d8b2 | ||
|
|
dae05277d6 | ||
|
|
ac85786b62 | ||
|
|
325706756c | ||
|
|
ee5cbb13b0 | ||
|
|
b0e63fd3fd | ||
|
|
fd32f76bf0 | ||
|
|
49f298bc46 | ||
|
|
a3c18dcdb2 | ||
|
|
38d29a6b27 | ||
|
|
e3cac1a892 | ||
|
|
9b9070b69b | ||
|
|
84b5019dd0 | ||
|
|
ee81cd1533 | ||
|
|
5edd57a390 | ||
|
|
f261a0ea08 | ||
|
|
6182f621d6 | ||
|
|
72ed5bbe4e | ||
|
|
fac9b347c2 | ||
|
|
48ae1c2e63 | ||
|
|
d785952b7e | ||
|
|
5f13f380e9 | ||
|
|
99b93e760a | ||
|
|
11cc63adcd | ||
|
|
c057b950c8 | ||
|
|
b6f30d1ccb | ||
|
|
546b511a9a | ||
|
|
ddef087328 | ||
|
|
2355618409 | ||
|
|
28461fbe19 | ||
|
|
cdb719eb2a | ||
|
|
da692a4798 | ||
|
|
c002fc37c5 | ||
|
|
4e86f7f308 | ||
|
|
462d7bf3b5 | ||
|
|
eb2c15ba17 | ||
|
|
635b195d48 | ||
|
|
301bfa3234 | ||
|
|
4b51f363ae | ||
|
|
57485b5b4b | ||
|
|
cd7bf0fe01 | ||
|
|
595805eeaf | ||
|
|
a2c17e5e0e | ||
|
|
af2548acd1 | ||
|
|
82c576c850 | ||
|
|
0ec17645b6 | ||
|
|
b35a3e029f | ||
|
|
0037ec9606 | ||
|
|
cbd5a2786c | ||
|
|
226d7c93bd | ||
|
|
a2c82c56fb | ||
|
|
dd91c810ad | ||
|
|
fcdb2e3a28 | ||
|
|
b06c9499a3 | ||
|
|
394bf46eca | ||
|
|
0967ad12e8 | ||
|
|
fdcc060861 | ||
|
|
77384f35c4 | ||
|
|
59338a16ac | ||
|
|
a91344595c | ||
|
|
a2586424f9 | ||
|
|
d08921bde3 | ||
|
|
9214bb4f31 | ||
|
|
77aff31bcc | ||
|
|
89570857bb | ||
|
|
84d1db5f1e | ||
|
|
a87fb57188 | ||
|
|
95db6de63c | ||
|
|
bbffe1a9bd | ||
|
|
9d6b6b7c93 | ||
|
|
76e0eab94c | ||
|
|
9a13502597 | ||
|
|
77d02c7844 | ||
|
|
c3339d8ea2 | ||
|
|
7371871f15 | ||
|
|
8f0f6db9d6 | ||
|
|
27ddaf726d | ||
|
|
e6c1d928ec | ||
|
|
ae360b1db3 | ||
|
|
b5be1f3933 | ||
|
|
006344b58a | ||
|
|
b18b6af4dc | ||
|
|
4d8f489b18 | ||
|
|
76380a1f04 | ||
|
|
332593c23e | ||
|
|
fd818c5f31 | ||
|
|
1b7b163a66 | ||
|
|
1c9ca22b93 | ||
|
|
5a1a39478f | ||
|
|
983609f19a | ||
|
|
c719e37cc1 | ||
|
|
4c7f8cc6b6 | ||
|
|
1e853b040d | ||
|
|
fcc59c0674 | ||
|
|
ee091a33c9 | ||
|
|
13e7af779b | ||
|
|
a9e9dd7270 | ||
|
|
75c6abb2b1 | ||
|
|
ca7acdd812 | ||
|
|
57750689ee | ||
|
|
ecd7423ac0 | ||
|
|
f74d3e71a2 | ||
|
|
eba20025fe | ||
|
|
30da4bcd85 | ||
|
|
ab7f266e2b | ||
|
|
32f97392bb | ||
|
|
382ac94a1f | ||
|
|
42046eff3a | ||
|
|
154030c5fa | ||
|
|
38fa908872 | ||
|
|
fd5b2cc6c6 | ||
|
|
514644269c | ||
|
|
6d1b18da07 | ||
|
|
d918ed1f52 | ||
|
|
175d01f3d1 | ||
|
|
1dbc978916 | ||
|
|
14dc7ac3a3 | ||
|
|
f434bd1133 | ||
|
|
d8c2ac4708 | ||
|
|
79f68d01c8 | ||
|
|
b8a83868da | ||
|
|
0b74250248 | ||
|
|
8932378e07 | ||
|
|
1e4761a10a | ||
|
|
0a7c52c75d | ||
|
|
c61681c331 | ||
|
|
19888b3ee5 | ||
|
|
6d7f274bde | ||
|
|
706b823c4a | ||
|
|
aeb5906de3 | ||
|
|
c8d2098e5b | ||
|
|
6cb0da0ce8 | ||
|
|
d181b84c3b | ||
|
|
5732752350 | ||
|
|
0f93c4fa1b | ||
|
|
92e0e4bb91 | ||
|
|
291dc39c25 | ||
|
|
750b7571f5 | ||
|
|
87f4022759 | ||
|
|
d053e60ed2 | ||
|
|
5041930685 | ||
|
|
13307f3861 | ||
|
|
b081dc8b18 | ||
|
|
7fd5e2bc90 | ||
|
|
e973f5fd4e | ||
|
|
b2fa10f2de | ||
|
|
222ce237c5 | ||
|
|
27df34f000 | ||
|
|
50e0152ced | ||
|
|
c49d25229c | ||
|
|
28ceb7f7ff | ||
|
|
d8dd9f5738 | ||
|
|
16a1eade2a | ||
|
|
807a1bc26d | ||
|
|
e7feb83f76 | ||
|
|
a117867307 | ||
|
|
e08a6bead8 | ||
|
|
2ed1c3a9dc | ||
|
|
378d694871 | ||
|
|
8bb746aa0e | ||
|
|
c30bc69f5d | ||
|
|
73dc17318e | ||
|
|
d23118f692 | ||
|
|
b48507d92a | ||
|
|
7d82ef92e9 | ||
|
|
8f9c6906a1 | ||
|
|
f506801337 | ||
|
|
e6c51f61ca | ||
|
|
75a1f9db17 | ||
|
|
e13ec9aaf7 | ||
|
|
5db9bc1606 | ||
|
|
ca06ebc233 | ||
|
|
57b5243509 | ||
|
|
5f95018abe | ||
|
|
2a8b8b57e1 | ||
|
|
13d0b149d1 | ||
|
|
d7a58ae791 | ||
|
|
67eef31520 | ||
|
|
82f3198bdd | ||
|
|
429045e419 | ||
|
|
a645b9317e | ||
|
|
a9e19d3b4b | ||
|
|
2267b3e6b2 | ||
|
|
5d589ee31d | ||
|
|
28caa05292 | ||
|
|
faef643956 | ||
|
|
dee9f02cf5 | ||
|
|
44a5235d30 | ||
|
|
00eecbf7c8 | ||
|
|
798d974677 | ||
|
|
545b5a5135 | ||
|
|
a8bd28318f | ||
|
|
67138dab0f | ||
|
|
2a170584e2 | ||
|
|
b596376051 | ||
|
|
24ab06c005 | ||
|
|
511e40fa72 | ||
|
|
754d4087b4 | ||
|
|
de9a26f8ff | ||
|
|
f69a6ea82b | ||
|
|
0098fb9975 | ||
|
|
b483481c81 | ||
|
|
5b9e250c95 | ||
|
|
2efba6c0d7 | ||
|
|
1614901631 | ||
|
|
96744e9857 | ||
|
|
2d08fb98b7 | ||
|
|
12e813c432 | ||
|
|
1ca0905c52 | ||
|
|
f9f5a948b8 | ||
|
|
31eff2fc92 | ||
|
|
04df87c992 | ||
|
|
ce503c8e9a | ||
|
|
0d3f777507 | ||
|
|
a5aa85acb1 | ||
|
|
8e2bea4230 | ||
|
|
4ce883c507 | ||
|
|
fe163ef5fb | ||
|
|
901fd252f1 | ||
|
|
ed58853acc | ||
|
|
defc0486a5 | ||
|
|
c463960bc1 | ||
|
|
575b9ab425 | ||
|
|
7444678eb3 | ||
|
|
87555ce7ba | ||
|
|
7648aee85f | ||
|
|
83dfba2eb6 | ||
|
|
6f570722f4 | ||
|
|
999412e2da | ||
|
|
d7f20c4b3b | ||
|
|
add3f456c7 | ||
|
|
88d5071400 | ||
|
|
a9ba166bd4 | ||
|
|
8a92a94a3e | ||
|
|
59bc7c4cd4 | ||
|
|
2f6cb2d2ad | ||
|
|
54f9e1271a | ||
|
|
2be3459b08 | ||
|
|
31d7609858 | ||
|
|
7fcc860bea | ||
|
|
a4c8736663 | ||
|
|
ba6dd8e50e | ||
|
|
8f819f1eea | ||
|
|
d5e3621a2a | ||
|
|
e5c33037e5 | ||
|
|
d0fdf1686d | ||
|
|
7eb31f9412 | ||
|
|
aeb6f76611 | ||
|
|
03cfca8a4b | ||
|
|
4aef2b6c38 | ||
|
|
395e4a1ee4 | ||
|
|
c1cf8b414f | ||
|
|
ba0179e62f | ||
|
|
075568d3ec | ||
|
|
132a837717 | ||
|
|
9d6784cebd | ||
|
|
a6e8a83366 | ||
|
|
dbf6167aba | ||
|
|
1abf7c5749 | ||
|
|
2d64b64d68 | ||
|
|
0ba7df4064 | ||
|
|
20e930dd81 | ||
|
|
d836c0a99f | ||
|
|
98734e345f | ||
|
|
314f200477 | ||
|
|
321d3ad306 | ||
|
|
2215cace30 | ||
|
|
56381ccd55 | ||
|
|
e86b5ce0cf | ||
|
|
8b6f93625b | ||
|
|
4be2a53509 | ||
|
|
4dffca0184 | ||
|
|
7fb2435f6b | ||
|
|
a62fef46a0 | ||
|
|
e3d141a619 | ||
|
|
037da11545 | ||
|
|
9778111c58 | ||
|
|
9311918278 | ||
|
|
c1a08d8ad3 | ||
|
|
4dd28a27d3 | ||
|
|
44d82a6c54 | ||
|
|
1ec55052e6 | ||
|
|
3560f5483e | ||
|
|
c44b1f66ee | ||
|
|
d0e2b83b54 | ||
|
|
9e284e1de0 | ||
|
|
0cfdc600e7 | ||
|
|
151cfd85ed | ||
|
|
b72df1d64c | ||
|
|
d062b8c32f | ||
|
|
4749163d9c | ||
|
|
bc2a2a72be | ||
|
|
2538f46dfd | ||
|
|
bb9fc8ba83 | ||
|
|
794caced01 | ||
|
|
d473a7e187 | ||
|
|
43ccc76960 | ||
|
|
e38766f0fa | ||
|
|
8b6d0f27d8 | ||
|
|
22593cd544 | ||
|
|
719119b793 | ||
|
|
edd1665c55 | ||
|
|
b803021ce2 | ||
|
|
8992a1c7bc | ||
|
|
626440523d | ||
|
|
116c965cb4 | ||
|
|
3b2e2a2232 | ||
|
|
0b36981c3c | ||
|
|
4a70219ba4 | ||
|
|
ca12b927b5 | ||
|
|
58cb265c61 | ||
|
|
777b7e7aaf | ||
|
|
29afda8b3a | ||
|
|
6e4bedca2d | ||
|
|
e0df785ece | ||
|
|
cd5188c71e | ||
|
|
715fe59ee5 | ||
|
|
8c318fce0e | ||
|
|
4fd5b38832 | ||
|
|
68d186e686 | ||
|
|
8210c4f05d | ||
|
|
f17af9a920 | ||
|
|
b35c692e3b | ||
|
|
160114a364 | ||
|
|
83cf1b6990 | ||
|
|
2157bbf05f | ||
|
|
be8c584e84 | ||
|
|
5b14794690 | ||
|
|
e2687af6ef | ||
|
|
0ac1724f05 | ||
|
|
a52b0b4115 | ||
|
|
ba78be247e | ||
|
|
e0e1a2480d | ||
|
|
7a2ebe7f13 | ||
|
|
2291e35358 | ||
|
|
9876611eef | ||
|
|
7e29a2f4ee | ||
|
|
3fff765adf | ||
|
|
b543965f35 | ||
|
|
68cda2cd46 | ||
|
|
6879c17edd | ||
|
|
dda32c3a40 | ||
|
|
1f07a199db | ||
|
|
c495db4bc6 | ||
|
|
950028af92 | ||
|
|
089bdc64a0 | ||
|
|
32628ec43f | ||
|
|
139a91b094 | ||
|
|
18111aa744 | ||
|
|
857fe1d950 | ||
|
|
e46d104cc7 | ||
|
|
560dd8fddc | ||
|
|
a29b6d172a | ||
|
|
4b3afb5223 | ||
|
|
ca083963e9 | ||
|
|
33c1fdf629 | ||
|
|
a8793dbbc5 | ||
|
|
3ac7ee4b76 | ||
|
|
7e6b3c6ba5 | ||
|
|
137673c551 | ||
|
|
f22829ef4f | ||
|
|
9abb48526b | ||
|
|
57984fe2d6 | ||
|
|
98e284074c | ||
|
|
99e3f5d95e | ||
|
|
8ba1340e92 | ||
|
|
511cceb9d9 | ||
|
|
64e6b8387a | ||
|
|
b8592d35c0 | ||
|
|
cc76eb9d2b | ||
|
|
4189d20961 | ||
|
|
98a9801644 | ||
|
|
63be7c8743 | ||
|
|
7d5b3463cc | ||
|
|
eee2cb1b10 | ||
|
|
36b8e5903d | ||
|
|
9301506d82 | ||
|
|
bba2f7b570 | ||
|
|
f989880d48 | ||
|
|
b083de710b | ||
|
|
ce8eea7b78 | ||
|
|
196ccb29fc | ||
|
|
a92948ee7e | ||
|
|
9f2989fb62 | ||
|
|
1ab5b189dc | ||
|
|
a493e5a4d1 | ||
|
|
a8e679bf03 | ||
|
|
a61eb22b62 | ||
|
|
b93e759ba6 | ||
|
|
66e0acbb8f | ||
|
|
e75948ec01 | ||
|
|
a3ef3ae60d | ||
|
|
a021090dc9 | ||
|
|
3c87ce0fa3 | ||
|
|
86417d4781 | ||
|
|
7c52d622ba | ||
|
|
ab183d89eb | ||
|
|
4b98fa70c0 | ||
|
|
96cb2875c0 | ||
|
|
74ee33ee4c | ||
|
|
a6c7d443ae | ||
|
|
551fc98498 | ||
|
|
ecd2624d2b | ||
|
|
b4629a86e4 | ||
|
|
af28ddf201 | ||
|
|
2f2cd58c30 | ||
|
|
ed3bdf1586 | ||
|
|
d4ff1f97a9 | ||
|
|
f662ec0e67 | ||
|
|
9483fbeb5d | ||
|
|
fd6d384ba2 | ||
|
|
6d69cd3a48 | ||
|
|
ea8ec97f49 | ||
|
|
83a138f38c | ||
|
|
bed4f1ae13 | ||
|
|
aaeb90372e | ||
|
|
669f412869 | ||
|
|
62053d9a34 | ||
|
|
e4e05e2013 | ||
|
|
1fb920e039 | ||
|
|
b35762b1ec | ||
|
|
17498dbc07 | ||
|
|
d52fe75e43 | ||
|
|
aca5ad0c4b | ||
|
|
b6ed7da0a7 | ||
|
|
2abe7d688d | ||
|
|
ce794fe0be | ||
|
|
d81149dd15 | ||
|
|
f9c659612a | ||
|
|
fe9723fa15 | ||
|
|
4914c22b5d | ||
|
|
8961841847 | ||
|
|
554023c839 | ||
|
|
71ce9fea5a | ||
|
|
24aa355ed0 | ||
|
|
3273c1abe5 | ||
|
|
303213c48b | ||
|
|
744983cfc4 | ||
|
|
3a16715e03 | ||
|
|
a360301759 | ||
|
|
c212acd90b | ||
|
|
b374437fb6 | ||
|
|
fc8eda07f7 | ||
|
|
9149f77a37 | ||
|
|
fc747792a1 | ||
|
|
035a63f19e | ||
|
|
4382d8ca73 | ||
|
|
e1de247dbb | ||
|
|
d617dbfc03 | ||
|
|
0b0f9e80e4 | ||
|
|
10cc40ec5a | ||
|
|
8859587768 | ||
|
|
070ccc4fbb | ||
|
|
f80f87d77d | ||
|
|
093966f692 | ||
|
|
76184c0251 | ||
|
|
7b6e767ccb | ||
|
|
8ac3211a86 | ||
|
|
ef38bd7aa3 | ||
|
|
7e98ad725d | ||
|
|
ba72727fba | ||
|
|
19239655ed | ||
|
|
ee59fbf8d2 | ||
|
|
f7e47d2167 | ||
|
|
a979d532d5 | ||
|
|
9ff5dee979 | ||
|
|
edf525f269 | ||
|
|
1b174d5f2f | ||
|
|
9ff5982a5b | ||
|
|
5fb5652f3d | ||
|
|
dbbeb063e7 | ||
|
|
aa8c944fe9 | ||
|
|
21ac9bb45b | ||
|
|
0b3348ee77 | ||
|
|
6574d45939 | ||
|
|
ffd8a54d1e | ||
|
|
601a7f9310 | ||
|
|
1dbcf92336 | ||
|
|
a727d6f6ea | ||
|
|
e6f173df37 | ||
|
|
f4f88da945 | ||
|
|
aedafe1f4b | ||
|
|
e7f8769f61 | ||
|
|
ee809e1fe7 | ||
|
|
bb73d81256 | ||
|
|
ffbb96301e | ||
|
|
eb4f7c0d7c | ||
|
|
f60af287d3 | ||
|
|
1f97fd27a0 | ||
|
|
c8bbc2400b | ||
|
|
57142324ed | ||
|
|
0876469030 | ||
|
|
f6dcdb72ec | ||
|
|
cde38e3bb0 | ||
|
|
632abeef87 | ||
|
|
370182e3aa | ||
|
|
4ad101dca1 | ||
|
|
9db56a7fd7 | ||
|
|
d1af83e258 | ||
|
|
0e39ecaa77 | ||
|
|
2c6449b14d | ||
|
|
1cfb58d922 | ||
|
|
d94d448ee4 | ||
|
|
372395575c | ||
|
|
768d4bbab8 | ||
|
|
0922649988 | ||
|
|
39318354d3 | ||
|
|
813f1c70b7 | ||
|
|
6df78e1c9b | ||
|
|
3cf12de6c0 | ||
|
|
46102d1a76 | ||
|
|
841e9578b7 | ||
|
|
31e8c85d99 | ||
|
|
009628c3ee | ||
|
|
7da7bc8606 | ||
|
|
d71066b95e | ||
|
|
5f53da58e7 | ||
|
|
e32e3ff47d | ||
|
|
648997851c | ||
|
|
9e2e19dfa8 | ||
|
|
e571c22891 | ||
|
|
c1a3575d44 | ||
|
|
6382044f1e | ||
|
|
0a553558c4 | ||
|
|
6d8e58c5d8 | ||
|
|
f7134f6e2d | ||
|
|
7bffaa8cc3 | ||
|
|
943924adef | ||
|
|
916b971e77 | ||
|
|
16e0b19df7 | ||
|
|
3175d8ab47 | ||
|
|
4121cc49da | ||
|
|
76c7443d07 | ||
|
|
fa6de0ce67 | ||
|
|
c3bbc05f8d | ||
|
|
e4caeb66b4 | ||
|
|
3bac819760 | ||
|
|
d6674aed3e | ||
|
|
f20c7b15a4 | ||
|
|
17b9bde6e5 | ||
|
|
018c97f497 | ||
|
|
6841da5557 | ||
|
|
f9bf2dcbcd | ||
|
|
e6774023ce | ||
|
|
aac1c7ad43 | ||
|
|
32de52042d | ||
|
|
62e2ca2a64 | ||
|
|
d3ecafe0ea | ||
|
|
e17dcd11c9 | ||
|
|
d82e08281e | ||
|
|
19763bb84d | ||
|
|
ea08b92e8b | ||
|
|
4447d43c89 | ||
|
|
d33ee8a7f0 | ||
|
|
d93875b751 | ||
|
|
90cec0169f | ||
|
|
040b2c2914 | ||
|
|
b62984d540 | ||
|
|
ba5fee3f94 | ||
|
|
28277e3c54 | ||
|
|
79a08a36fa | ||
|
|
f223cd7959 | ||
|
|
6d0f129949 | ||
|
|
8dcd80ce67 | ||
|
|
653a7bf2fa | ||
|
|
d5d67b108f | ||
|
|
02f8d75273 | ||
|
|
74d56f795d | ||
|
|
64bf5681e8 | ||
|
|
d0a486d7e7 | ||
|
|
f6ab3ffd39 | ||
|
|
8eeb122a38 | ||
|
|
0ca918d814 | ||
|
|
455f4e77e0 | ||
|
|
d92f5f6cf6 | ||
|
|
d31a8188f5 | ||
|
|
f2b98ba803 | ||
|
|
4c4ec32261 | ||
|
|
bb5898835d | ||
|
|
4d52059c7d | ||
|
|
0c24bb1937 | ||
|
|
ca10cf257c | ||
|
|
4c91d27cd2 | ||
|
|
d925634e17 | ||
|
|
160c4a8864 | ||
|
|
29d1338a6d | ||
|
|
2aafe438fb | ||
|
|
96d8489170 | ||
|
|
b0148003b7 | ||
|
|
a86edf1497 | ||
|
|
e6dee77695 | ||
|
|
4da7e07703 | ||
|
|
47ef82aca1 | ||
|
|
e05fcf07b5 | ||
|
|
1f691f1d7f | ||
|
|
9df87b827b | ||
|
|
0fdbd9052a | ||
|
|
cbfce19f73 | ||
|
|
94ad930187 | ||
|
|
afa34f7bcd | ||
|
|
39822883d2 | ||
|
|
0592e0f653 | ||
|
|
065c2b78d5 | ||
|
|
d9febd8117 | ||
|
|
416d01f6df | ||
|
|
497f7bcd40 | ||
|
|
2f56d2b5ca | ||
|
|
e6195302e2 | ||
|
|
551f0753e8 | ||
|
|
d16e345064 | ||
|
|
cc6e2e626b | ||
|
|
00ddcfe6f1 | ||
|
|
2a01876c9e | ||
|
|
ae6936d3c2 | ||
|
|
bdf344fc2e | ||
|
|
e47d7e48d9 | ||
|
|
f297defea8 | ||
|
|
de3ce19eb9 | ||
|
|
57c57b7243 | ||
|
|
18080f878f | ||
|
|
3721e6ab40 | ||
|
|
eb6a79cf97 | ||
|
|
4ff7973d43 | ||
|
|
4472ea7832 | ||
|
|
14f0fb91ea | ||
|
|
1ed504154b | ||
|
|
8c7cd6f0de | ||
|
|
938938f053 | ||
|
|
c2bdb129e6 | ||
|
|
0908837392 | ||
|
|
ef116a9a53 | ||
|
|
f713c24ff8 | ||
|
|
5505bf238d | ||
|
|
34b659693e | ||
|
|
bb9dde869e | ||
|
|
7fc4e2f807 | ||
|
|
dccf79dc55 | ||
|
|
a9635b49ef | ||
|
|
d4d9a5d30f | ||
|
|
a6f0a8723c | ||
|
|
8ccbff16a9 | ||
|
|
96600b6616 | ||
|
|
b782c0aec1 | ||
|
|
7217b73e4a | ||
|
|
5e9db8eb3c | ||
|
|
6e65ad615b | ||
|
|
9345d80b17 | ||
|
|
f8caecf221 | ||
|
|
a452931ccd | ||
|
|
6348be150a | ||
|
|
359a0344f8 | ||
|
|
63987a0585 | ||
|
|
1302274bba | ||
|
|
c5c99a6f12 | ||
|
|
ded3999a12 | ||
|
|
6d856d1463 | ||
|
|
9450e3468b | ||
|
|
96009cecd9 | ||
|
|
51ee3773f7 | ||
|
|
8a92c97485 | ||
|
|
b4e2ded876 | ||
|
|
9b30b0d30a | ||
|
|
35630621b6 | ||
|
|
c5cd7a2f1d | ||
|
|
562c82dd62 | ||
|
|
9677dcb6bd | ||
|
|
08a296b867 | ||
|
|
aae0025047 | ||
|
|
0d882d31ad | ||
|
|
e630e636ad | ||
|
|
092396cc07 | ||
|
|
0b346cc0cb | ||
|
|
6493cc359f | ||
|
|
2774016518 | ||
|
|
cfc2c84409 | ||
|
|
b7bdb67eb4 | ||
|
|
f38d6221c6 | ||
|
|
b8e354b56c | ||
|
|
1a3c535e89 | ||
|
|
d4ced2f734 | ||
|
|
026233c804 | ||
|
|
2673e745f4 | ||
|
|
c3dd2cb44d | ||
|
|
a2f952ff0e | ||
|
|
7c9d773c73 | ||
|
|
8379a4a9bf | ||
|
|
c73e4f1e29 | ||
|
|
1b8c0e2749 | ||
|
|
131b86c93a | ||
|
|
51c7e414ff | ||
|
|
ca700b82c2 | ||
|
|
8db0a03081 | ||
|
|
77862a55f6 | ||
|
|
49661297ab | ||
|
|
17629f1f15 | ||
|
|
aacf36babd | ||
|
|
2b9fca8485 | ||
|
|
f4a0624470 | ||
|
|
08e5e34815 | ||
|
|
846f4aa78f | ||
|
|
e18ddb2e18 | ||
|
|
52ffc2ac28 | ||
|
|
03685ee390 | ||
|
|
4428cc2232 | ||
|
|
1e40d759fe | ||
|
|
485ba7b8ac | ||
|
|
901f75a499 | ||
|
|
4091346ae3 | ||
|
|
e4d22db19d | ||
|
|
399e9c8f1d | ||
|
|
828830c660 | ||
|
|
331952a8f5 | ||
|
|
9c2ed05bab | ||
|
|
d72ff58fe7 | ||
|
|
b94cd7d1a1 | ||
|
|
b5f9261504 | ||
|
|
15abceefeb | ||
|
|
421b860da5 | ||
|
|
03fda24c03 | ||
|
|
207c9dde45 | ||
|
|
f070833144 | ||
|
|
5048a20760 | ||
|
|
3ef7ba9fdf | ||
|
|
91ab03c0ed | ||
|
|
f1c8082252 | ||
|
|
d367eb46f0 | ||
|
|
ecea6c04e9 | ||
|
|
60ee31a074 | ||
|
|
9218553df5 | ||
|
|
7d552b4089 | ||
|
|
8660c06097 | ||
|
|
3e7021b93e | ||
|
|
5dc31e46bf | ||
|
|
ba760ab52b | ||
|
|
bd33a9d1df | ||
|
|
500cfafca3 | ||
|
|
f7b39ab754 | ||
|
|
5c2c5321bc | ||
|
|
217f3112e4 | ||
|
|
add71a9a7e | ||
|
|
ec97df9a59 | ||
|
|
1705fb7b80 | ||
|
|
a232da999d | ||
|
|
d3b965ec38 | ||
|
|
4a8b6e4f66 | ||
|
|
33828dd155 | ||
|
|
63a0c1e806 | ||
|
|
1865b26b58 | ||
|
|
a1a8f90fdb | ||
|
|
432c571f55 | ||
|
|
abc6f7b22e | ||
|
|
da5bf3ce40 | ||
|
|
3eb3976b54 | ||
|
|
0c5bde5a89 | ||
|
|
2d7722c19f | ||
|
|
95c160ca55 | ||
|
|
df302a799c | ||
|
|
5818cd4375 | ||
|
|
2ed62021f6 | ||
|
|
8872d419e2 | ||
|
|
434b0f30e8 | ||
|
|
f20a781c0b | ||
|
|
8ab8adb400 | ||
|
|
ec2591bff2 | ||
|
|
4cb8a57494 | ||
|
|
35d5e36421 | ||
|
|
99a93d77b3 | ||
|
|
d0f528892b | ||
|
|
2d7b97986f | ||
|
|
537c303a18 | ||
|
|
a7ac83cbd3 | ||
|
|
3db3e61e81 | ||
|
|
507054c9ff | ||
|
|
de42d38b31 | ||
|
|
2935181452 | ||
|
|
f04808c709 | ||
|
|
ce0938ea11 | ||
|
|
c356bc76f4 | ||
|
|
adbfb95012 | ||
|
|
dd7e94cde8 | ||
|
|
e337a34cd7 | ||
|
|
98be918c0f | ||
|
|
3778c80fd6 | ||
|
|
58986eb52e | ||
|
|
ef0a14ba63 | ||
|
|
6e9acd781f | ||
|
|
6cbdddcd61 | ||
|
|
03977fb928 | ||
|
|
aaecb99d36 | ||
|
|
4981220d83 | ||
|
|
ed7255ea0e | ||
|
|
a0a85df559 | ||
|
|
b1c4d5bad0 | ||
|
|
0e8e1edb8e | ||
|
|
f0bfc6cbf8 | ||
|
|
8b10dd3e3d | ||
|
|
c0ff6d7e08 | ||
|
|
7981d4a257 | ||
|
|
c83cbeeb22 | ||
|
|
3250c07a16 | ||
|
|
954db9ad07 | ||
|
|
c1e5b5b1e5 | ||
|
|
e5f9b50d21 | ||
|
|
c9e9dd2854 | ||
|
|
1ca08c33fd | ||
|
|
5daf1124a1 | ||
|
|
9d7d66fc99 | ||
|
|
62412c6d9f | ||
|
|
6f1c834e48 | ||
|
|
ecfb4ff31e | ||
|
|
76368ca861 | ||
|
|
3b6788fc16 | ||
|
|
4887224e45 | ||
|
|
9d20120497 | ||
|
|
2ad2fc4023 | ||
|
|
b8e4f9659a | ||
|
|
571df9cd06 | ||
|
|
3a740330ec | ||
|
|
46c6ca51cf | ||
|
|
eb11192ab7 | ||
|
|
2d5453a532 | ||
|
|
7173808ce4 | ||
|
|
c731f706f8 | ||
|
|
b965ae338c | ||
|
|
d4f2308d77 | ||
|
|
f0a1721069 | ||
|
|
c054033c54 | ||
|
|
441d8ed35e | ||
|
|
7643d11f74 | ||
|
|
ab3a53a4ec | ||
|
|
a69f639541 | ||
|
|
0471d99d13 | ||
|
|
4c453e22be | ||
|
|
2ad777025a | ||
|
|
22da4860f7 | ||
|
|
0e14b02109 | ||
|
|
e034f7c1b6 | ||
|
|
279fff4395 | ||
|
|
13d501b75b | ||
|
|
3b6f5a0abf | ||
|
|
d5266070e7 | ||
|
|
def7a08855 | ||
|
|
619e0828af | ||
|
|
6957a876e8 | ||
|
|
75bbb2399c | ||
|
|
c7baed8395 | ||
|
|
c02b4e35ba | ||
|
|
f32c6fcac8 | ||
|
|
e77d995b35 | ||
|
|
e40d03114f | ||
|
|
4e332163e2 | ||
|
|
df59469aa3 | ||
|
|
bf8523b948 | ||
|
|
ff9eccc38c | ||
|
|
48779742e3 | ||
|
|
01a09a5f90 | ||
|
|
aacb1af6b5 | ||
|
|
ae468b4de5 | ||
|
|
fc8cbf0f07 | ||
|
|
af4df5e64f | ||
|
|
0732063e18 | ||
|
|
374d96053b | ||
|
|
872e4b17b4 | ||
|
|
467d862215 | ||
|
|
e91620d17f | ||
|
|
37d0d70482 | ||
|
|
dc781447a8 | ||
|
|
15e5fb6951 | ||
|
|
133c1484f6 | ||
|
|
3e310724ff | ||
|
|
4b528280ba | ||
|
|
146b32b215 | ||
|
|
1330acbf22 | ||
|
|
5592e9dba5 | ||
|
|
75f6fac284 | ||
|
|
71c65d7354 | ||
|
|
040b45394a | ||
|
|
aa645cdfd2 | ||
|
|
153e2a63e0 | ||
|
|
fa619b59d6 | ||
|
|
255621073d | ||
|
|
5580bcde0b | ||
|
|
97c50d588f | ||
|
|
aa8916aea5 | ||
|
|
9bc578fbbe | ||
|
|
d3cbd442e7 | ||
|
|
1ce28fd500 | ||
|
|
e12359fdaa | ||
|
|
9feb4f0fcd | ||
|
|
cfa7a8b640 | ||
|
|
0202192fc0 | ||
|
|
76c3db3e5f | ||
|
|
37a6ee08dc | ||
|
|
55488ae292 | ||
|
|
a5c7db711c | ||
|
|
62d6095edb | ||
|
|
9f653d0371 | ||
|
|
d401a6c33b | ||
|
|
2239f47d45 | ||
|
|
f7f0b07361 | ||
|
|
d43e0b3e59 | ||
|
|
c015ce6ce3 | ||
|
|
772b82857b | ||
|
|
e2cdcbd5ce | ||
|
|
b5ff6695e1 | ||
|
|
8f5d7f1e7f | ||
|
|
70e6836f35 | ||
|
|
40bf54aec1 | ||
|
|
3a46607c0f | ||
|
|
11eb619fc6 | ||
|
|
54ce29d33e | ||
|
|
82807b2c5c | ||
|
|
ddd44e7753 | ||
|
|
e5d2f75606 | ||
|
|
9da17585e8 | ||
|
|
20185d0fad | ||
|
|
accdcc885d | ||
|
|
0323dd3a91 | ||
|
|
293e8eb6c5 | ||
|
|
b175ea18e2 | ||
|
|
2fb5bc8f5b | ||
|
|
90c96791be | ||
|
|
ff9834cf24 | ||
|
|
530d0ee14c | ||
|
|
4aedc1d481 | ||
|
|
950e54c990 | ||
|
|
b2960285bf | ||
|
|
ad7f7fcc37 | ||
|
|
2fc08a1c6c | ||
|
|
3954bbe862 | ||
|
|
2d028b4969 | ||
|
|
93047e67c0 | ||
|
|
66ef51a47d | ||
|
|
4dd54bab8e | ||
|
|
a2416003d6 | ||
|
|
7e14d61a34 | ||
|
|
0cb953af85 | ||
|
|
dc781a04c0 | ||
|
|
234f7bcac0 | ||
|
|
58c10e201a | ||
|
|
28892b3933 | ||
|
|
62f166f15c | ||
|
|
a193943480 | ||
|
|
dee8810f3a | ||
|
|
16905fd42c | ||
|
|
979a8f32dc | ||
|
|
4118f90b6b | ||
|
|
550f76461e | ||
|
|
b3fbba10b2 | ||
|
|
e5526572ac | ||
|
|
5944521719 | ||
|
|
835717a084 | ||
|
|
907b0aacdf | ||
|
|
e77e2dbcce | ||
|
|
d6c3a240b5 | ||
|
|
f98027e64b | ||
|
|
c45ddd7c7c | ||
|
|
0f3692928a | ||
|
|
5f86875ef2 | ||
|
|
2e0f8604e5 | ||
|
|
2c5ef742cc | ||
|
|
cdc7fed69c | ||
|
|
24ef843b65 | ||
|
|
9b2c276036 | ||
|
|
9c18764178 | ||
|
|
4e99372e6f | ||
|
|
bb44a14780 | ||
|
|
493edf8cc5 | ||
|
|
c25dd8d774 | ||
|
|
6e4115a8d0 | ||
|
|
40f86c1b5a | ||
|
|
f1bc32617d | ||
|
|
e10eab4de4 | ||
|
|
f92964f578 | ||
|
|
9963577a65 | ||
|
|
697df5846f | ||
|
|
c793082ec8 | ||
|
|
1b7811c185 | ||
|
|
fe00e26cc7 | ||
|
|
d54ba5e25f | ||
|
|
aba4dbec0a | ||
|
|
7ee202d666 | ||
|
|
0c7b727c9f | ||
|
|
03a27ecaa9 | ||
|
|
6ffd299343 | ||
|
|
a494028746 | ||
|
|
3425b58855 | ||
|
|
8adca517ce | ||
|
|
5acf252c45 | ||
|
|
88acb4c9a8 | ||
|
|
2ba0fffe85 | ||
|
|
b6e046f223 | ||
|
|
7163f2df4e | ||
|
|
da25d21898 | ||
|
|
4a78738b2a | ||
|
|
56c16666c3 | ||
|
|
7978318992 | ||
|
|
10ed681608 | ||
|
|
f9075ce252 | ||
|
|
753db12b2d | ||
|
|
20afb1c1c9 | ||
|
|
942fa6ff6e | ||
|
|
f11b22d248 | ||
|
|
6b3283e5b2 | ||
|
|
f604927cb6 | ||
|
|
9f0d3b2c4e | ||
|
|
b7262f954b | ||
|
|
44f5a062f7 | ||
|
|
396919fe6c | ||
|
|
6a67e57344 | ||
|
|
5479dce7da | ||
|
|
48c36eb5d3 | ||
|
|
183700a7d4 | ||
|
|
9dcbd7790d | ||
|
|
23f4d4702c | ||
|
|
c413d0ba6e | ||
|
|
44e2ec7f90 | ||
|
|
55c5375067 | ||
|
|
7a4a277aad | ||
|
|
9bda8031d0 | ||
|
|
8ab347f262 | ||
|
|
59d9dc2275 | ||
|
|
9fe2bdee0a | ||
|
|
05af6770b2 | ||
|
|
33b8e65002 | ||
|
|
11184ae086 | ||
|
|
446095591b | ||
|
|
9632b671be | ||
|
|
b5b6931e6b | ||
|
|
34041bb6d2 | ||
|
|
7745fb0e9b | ||
|
|
c7f14d37e9 | ||
|
|
2008f55166 | ||
|
|
fb79abcab7 | ||
|
|
cb0494d2dc | ||
|
|
cf4a273e83 | ||
|
|
f17d5b149a | ||
|
|
e3470dcad9 | ||
|
|
b42715950f | ||
|
|
6f208c5dfb | ||
|
|
567a14f93d | ||
|
|
1e716ef64f | ||
|
|
3b17a16217 | ||
|
|
7eff4f7e41 | ||
|
|
11c249c8f0 | ||
|
|
f694857e63 | ||
|
|
e9b56e5887 | ||
|
|
afcbc17250 | ||
|
|
dd60a3e43f | ||
|
|
f248e29405 | ||
|
|
31737ec68f | ||
|
|
745e9b732c | ||
|
|
ab83db8059 | ||
|
|
0dd475fcff | ||
|
|
63fe6beac2 | ||
|
|
eb13b764cf | ||
|
|
a36f862938 | ||
|
|
e688e9d39e | ||
|
|
21c829adc2 | ||
|
|
bc25f1babb | ||
|
|
cccffc57ba | ||
|
|
eab4d3cc57 | ||
|
|
bfcb075480 | ||
|
|
68e9bd813e | ||
|
|
c0442ce5cf | ||
|
|
cb3bfefd83 | ||
|
|
5568d8f777 | ||
|
|
2283ce9cbd | ||
|
|
c03b465051 | ||
|
|
dd53d178ea | ||
|
|
407892e995 | ||
|
|
293043999c | ||
|
|
f680180d95 | ||
|
|
d4d667a99d | ||
|
|
5aee086b6b | ||
|
|
d9c6da1c5f | ||
|
|
3817ee7ae2 | ||
|
|
92960e7f8a | ||
|
|
6a704a81b8 | ||
|
|
2c467919ed | ||
|
|
62461ad338 | ||
|
|
b269ce95b4 | ||
|
|
d10c024d8d | ||
|
|
05341e2bcb | ||
|
|
016b92381c | ||
|
|
1665b607cf | ||
|
|
f25ccaf67d | ||
|
|
b232a936fb | ||
|
|
22142e00ba | ||
|
|
712e80d53b | ||
|
|
a41fd8cea5 | ||
|
|
dadc294765 | ||
|
|
d6bd52e866 | ||
|
|
efab57df8b | ||
|
|
a5554933f0 | ||
|
|
22f8fbbf95 | ||
|
|
178446a8b3 | ||
|
|
b1bb35bfd9 | ||
|
|
128078ad6f | ||
|
|
1407a9849a | ||
|
|
36f95702e1 | ||
|
|
172b2f9ea2 | ||
|
|
dce3efe71b | ||
|
|
b5f98be162 | ||
|
|
6c9913e5f1 | ||
|
|
8f9867d028 | ||
|
|
9293d7599c | ||
|
|
73ae281bba | ||
|
|
0a8ed76315 | ||
|
|
239f537742 | ||
|
|
a37cb35829 | ||
|
|
7a95e0439e | ||
|
|
dfee961458 | ||
|
|
54a72bd714 | ||
|
|
cd0fad79e8 | ||
|
|
cd1fe7db29 | ||
|
|
44dc379053 | ||
|
|
1887b0e13c | ||
|
|
41e0d3ad8b | ||
|
|
b39bc5a218 | ||
|
|
895e34856d | ||
|
|
196b72448d | ||
|
|
2cd1bd9201 | ||
|
|
8b3d894df1 | ||
|
|
ed2cbfed65 | ||
|
|
06e55831f1 | ||
|
|
3f2cf39f45 | ||
|
|
1e4a58da2a | ||
|
|
73c7f8713b | ||
|
|
7acf191f61 | ||
|
|
c427244469 | ||
|
|
5bf10b60b1 | ||
|
|
103276eda3 | ||
|
|
4ca0be8c21 | ||
|
|
94747c9932 | ||
|
|
10ffddc2f1 | ||
|
|
1a424162c7 | ||
|
|
491e6220ca | ||
|
|
0cea45164e | ||
|
|
ed5aeff441 | ||
|
|
e547187490 | ||
|
|
b972b5eff3 | ||
|
|
60fb02f387 | ||
|
|
c8e9e291ad | ||
|
|
e6e0b6d3be | ||
|
|
5cfd640131 | ||
|
|
e92f791c81 | ||
|
|
0803780a30 | ||
|
|
ce8b075f46 | ||
|
|
1cab380c83 | ||
|
|
f195f8cee1 | ||
|
|
c04867451b | ||
|
|
da48b5a146 | ||
|
|
b8a7cebf08 | ||
|
|
eda607ff21 | ||
|
|
7179f426e2 | ||
|
|
f0dce68553 | ||
|
|
bcb726144f | ||
|
|
5ab570f1f3 | ||
|
|
8f1757bd39 | ||
|
|
0d92aa21fb | ||
|
|
ca53e56ee1 | ||
|
|
5a70d3ddfe | ||
|
|
b2b55dc151 | ||
|
|
ee172502fe | ||
|
|
91590b69ef | ||
|
|
c03fbbf11f | ||
|
|
aa707d9afb | ||
|
|
81f4a207a7 | ||
|
|
af70eb5ee6 | ||
|
|
53d79bfda8 | ||
|
|
d3b6a7505f | ||
|
|
b64ed343c2 | ||
|
|
0d48c6b1e9 | ||
|
|
9c050b3c6e | ||
|
|
45b6d20aaa | ||
|
|
1419a85f83 | ||
|
|
31889030f1 | ||
|
|
b676c67b96 | ||
|
|
a088a6718e | ||
|
|
ca284ed72f | ||
|
|
69807a729d | ||
|
|
81262a0711 | ||
|
|
bdb7bee0b0 | ||
|
|
cc30178aaa | ||
|
|
65cbc8455a | ||
|
|
c5a4914103 | ||
|
|
7c326bcac5 | ||
|
|
a315e2d8c6 | ||
|
|
0b8d271536 | ||
|
|
016b5c79f9 | ||
|
|
10255164d5 | ||
|
|
430307e574 | ||
|
|
c4859cbd03 | ||
|
|
e9fa175d91 | ||
|
|
62c6acbd0c | ||
|
|
5d613886fe | ||
|
|
89efc7b4b3 | ||
|
|
dae5e69f7e | ||
|
|
87ea05ba9a | ||
|
|
64e8a7bae8 | ||
|
|
3d874b9326 | ||
|
|
35b954ab46 | ||
|
|
38291f1fc3 | ||
|
|
ecbf8f2fe5 | ||
|
|
ba7cefc48b | ||
|
|
bba55adcb9 | ||
|
|
eb549a5515 | ||
|
|
691aa7ad04 | ||
|
|
56c2c389c7 | ||
|
|
b3a1f8bd8b | ||
|
|
2d93350d03 | ||
|
|
4d93472df0 | ||
|
|
4f1e70a06b | ||
|
|
430c408383 | ||
|
|
0303129006 | ||
|
|
0c88313682 | ||
|
|
4662bd7312 | ||
|
|
11c6ccb025 | ||
|
|
1ece504bd6 | ||
|
|
83440e6da8 | ||
|
|
193448929f | ||
|
|
b51682fce4 | ||
|
|
74e351475e | ||
|
|
09059a0fe8 | ||
|
|
fb990f4ba7 | ||
|
|
34d266b0fb | ||
|
|
733ac56aa4 | ||
|
|
772f8abc98 | ||
|
|
6a0759227f | ||
|
|
7985c2db16 | ||
|
|
b633688bbd | ||
|
|
3562b6cd6f | ||
|
|
c2c66fb29f | ||
|
|
13495b8c63 | ||
|
|
619b00c8b5 | ||
|
|
d4c520d97a | ||
|
|
9ca6cfcaba | ||
|
|
cce6cd417c | ||
|
|
625e9b7183 | ||
|
|
598817a02e | ||
|
|
6255bed777 | ||
|
|
fb5fb3df16 | ||
|
|
e5e8a566e2 | ||
|
|
743b76d3ec | ||
|
|
df28ac7a78 | ||
|
|
e3e9e92b44 | ||
|
|
1005690acb | ||
|
|
132bfc5d05 | ||
|
|
671101d201 | ||
|
|
29ac0677af | ||
|
|
7c460fc13b | ||
|
|
1283a0c5a9 | ||
|
|
30991d7d49 | ||
|
|
0c2adc7f4b | ||
|
|
c8cbe8c871 | ||
|
|
0cef0c5971 | ||
|
|
192a5dfdcc | ||
|
|
f983a2b405 | ||
|
|
b940d95a01 | ||
|
|
b3aba1f5e3 | ||
|
|
0a5cf9dbc3 | ||
|
|
f20debdc48 | ||
|
|
750ba5571f | ||
|
|
7f7489c6a7 | ||
|
|
588025aa88 | ||
|
|
c86c7a8c88 | ||
|
|
2edf7f8321 | ||
|
|
3693f6f799 | ||
|
|
305e0845ae | ||
|
|
150375160f | ||
|
|
63edc5697e | ||
|
|
dad0b7b904 | ||
|
|
8d4083f038 | ||
|
|
9ff3c0938a | ||
|
|
324022743b | ||
|
|
dc79ab4a25 | ||
|
|
752020de1a | ||
|
|
c039fae343 | ||
|
|
26e1e276ff | ||
|
|
408ad49dc0 | ||
|
|
ed8a88bae3 | ||
|
|
02f3d3b271 | ||
|
|
8bf1ea1d4a | ||
|
|
ee434d9376 | ||
|
|
744b4d0919 | ||
|
|
4a9fa84d77 | ||
|
|
fb349a964e | ||
|
|
02e026c09a | ||
|
|
3b5b023dbb | ||
|
|
fb9b8727a0 | ||
|
|
b3355f1de5 | ||
|
|
a777b33389 | ||
|
|
8ca69c669f | ||
|
|
fc5b454ad3 | ||
|
|
6dba4ba186 | ||
|
|
ad380bb233 | ||
|
|
dd256a9252 | ||
|
|
594d3770b2 | ||
|
|
56a833516d | ||
|
|
f1b750f73b | ||
|
|
4a2f8dd3fe | ||
|
|
2af3fbc31e | ||
|
|
d6bb29d962 | ||
|
|
848502448e | ||
|
|
d39850809b | ||
|
|
e06b2d01ca | ||
|
|
4efdf94daa | ||
|
|
d0e9edccf2 | ||
|
|
8b28bdca22 | ||
|
|
824309f626 | ||
|
|
ef62697950 | ||
|
|
9fc3529511 | ||
|
|
9d6e83875d | ||
|
|
cc1205f6b4 | ||
|
|
98609fde69 | ||
|
|
a0f8d02993 | ||
|
|
83ab65396f | ||
|
|
b8153e9f20 | ||
|
|
4808a18305 | ||
|
|
d2229e965a | ||
|
|
f1bb0bfa71 | ||
|
|
a466c19af1 | ||
|
|
832527d0dc | ||
|
|
cc29c2a318 | ||
|
|
d0a017f6bf | ||
|
|
08d131a2f1 | ||
|
|
b63604882c | ||
|
|
8b7b3f27ed | ||
|
|
abc051cad1 | ||
|
|
e2b1351424 | ||
|
|
8c01cd4605 | ||
|
|
406030544c | ||
|
|
1a5abdef7b | ||
|
|
42c6c37ed8 | ||
|
|
5283af224f | ||
|
|
e0a656864e | ||
|
|
a8fafbf87b | ||
|
|
9dd993fe3f | ||
|
|
7cb681f293 | ||
|
|
b695420114 | ||
|
|
9fde2e3361 | ||
|
|
62f66c0c01 | ||
|
|
73e464848f | ||
|
|
697d256730 | ||
|
|
1197bcfdd4 | ||
|
|
19096e2f14 | ||
|
|
c4b279c9be | ||
|
|
a3f64221df | ||
|
|
afc2569f38 | ||
|
|
d156cf87ec | ||
|
|
b232d4c27e | ||
|
|
a5656d15b4 | ||
|
|
7147e0a85c | ||
|
|
1ffc212583 | ||
|
|
172c6efbf9 | ||
|
|
1fdc441e56 | ||
|
|
868c424f52 | ||
|
|
99e022728d | ||
|
|
9cdca9efe5 | ||
|
|
9ef0dd02dd | ||
|
|
1521abf34b | ||
|
|
9c16e62deb | ||
|
|
98de9c6bfd | ||
|
|
cf36456303 | ||
|
|
99d8487b29 | ||
|
|
dbd09e5f32 | ||
|
|
d5718b5e52 | ||
|
|
d206d5d3cf | ||
|
|
e8b5f46dfd | ||
|
|
df9b7b32cb | ||
|
|
05f8b0b524 | ||
|
|
7b281df7ae | ||
|
|
6c92415de8 | ||
|
|
0787491822 | ||
|
|
11717b61a2 | ||
|
|
3c223e63e6 | ||
|
|
d8a599832c | ||
|
|
a7e139c5f8 | ||
|
|
7a128d4ba4 | ||
|
|
a8005ad4ec | ||
|
|
b6537a5cfd | ||
|
|
3f065f4241 | ||
|
|
3638d2aa94 | ||
|
|
04c13c16ab | ||
|
|
3af24aa339 | ||
|
|
5b758818b6 | ||
|
|
185460341e | ||
|
|
20e76dfc1c | ||
|
|
7d01a4be3d | ||
|
|
3c759b7c14 | ||
|
|
c2a61c1584 | ||
|
|
ab751b7ac8 | ||
|
|
7a43ae5e9d | ||
|
|
03dbb4489b | ||
|
|
039b535098 | ||
|
|
d98156faed | ||
|
|
ea7219f37b | ||
|
|
a76f6d6dee | ||
|
|
18bf29bdcc | ||
|
|
e6cc96e901 | ||
|
|
21ffe6957d | ||
|
|
503b0c0f6e | ||
|
|
e238d52444 | ||
|
|
3137a23a1c | ||
|
|
2b5ec2a949 | ||
|
|
ac33f904c3 | ||
|
|
7e36d208cd | ||
|
|
58763a62d7 | ||
|
|
8162523549 | ||
|
|
1e0d104cda | ||
|
|
616867b3b5 | ||
|
|
981d5b9380 | ||
|
|
103e51bee0 | ||
|
|
d209ad08ea | ||
|
|
fcd327ce7d | ||
|
|
fba5df03d9 | ||
|
|
18c2c4797b | ||
|
|
f9a4770f4c | ||
|
|
6ea54f25c9 | ||
|
|
70986f5a51 | ||
|
|
64e6048d7b | ||
|
|
97c471b71b | ||
|
|
706008fcd7 | ||
|
|
6ab971d51b | ||
|
|
da8281e387 | ||
|
|
56a359a823 | ||
|
|
e6a882d265 | ||
|
|
5b1a514db7 | ||
|
|
6706d359f4 | ||
|
|
63f00db931 | ||
|
|
71d366a6a1 | ||
|
|
ca5a614a55 | ||
|
|
b307461d46 | ||
|
|
8d27102a46 | ||
|
|
84f775620f | ||
|
|
6ccb4f0be1 | ||
|
|
8132bbe3a3 | ||
|
|
fc0c528037 | ||
|
|
4a7b11de86 | ||
|
|
4860e467cb | ||
|
|
7efcf6c93d | ||
|
|
d5d6c5fe77 | ||
|
|
ec9665cdca | ||
|
|
3ec89e0d72 | ||
|
|
fd7a548afb | ||
|
|
22f1ed41f7 | ||
|
|
6e8fb09aa1 | ||
|
|
88dac21b21 | ||
|
|
a5559b1d35 | ||
|
|
8bfecf21f7 | ||
|
|
a73fddfeb2 | ||
|
|
d50823e6a1 | ||
|
|
c9a1441c6d | ||
|
|
708b7251b6 | ||
|
|
77a680ac37 | ||
|
|
c91b4a7e52 | ||
|
|
f47e224d3f | ||
|
|
f5da939bfa | ||
|
|
c41efde6a6 | ||
|
|
4b41832be7 | ||
|
|
052134d392 | ||
|
|
30bdde839f | ||
|
|
0e30e972d1 | ||
|
|
6109edea6e | ||
|
|
190b797751 | ||
|
|
ce73cbfbe7 | ||
|
|
60fe5b600e | ||
|
|
6be2c41823 | ||
|
|
a741795ef3 | ||
|
|
e2f3f83265 | ||
|
|
49622faa7e | ||
|
|
06c9663ae9 | ||
|
|
c068a99e76 | ||
|
|
0cab93c2c2 | ||
|
|
48cc504124 | ||
|
|
daa45cc516 | ||
|
|
e54a722ec5 | ||
|
|
c72c325417 | ||
|
|
ea297fc23e | ||
|
|
fe0210d573 | ||
|
|
8db7a77fdf | ||
|
|
999c79559f | ||
|
|
7ed91a6084 | ||
|
|
d82c49b0d6 | ||
|
|
3ed9b30994 | ||
|
|
32076acbad | ||
|
|
02e4ca294f | ||
|
|
fb9d7cac53 | ||
|
|
9b70958c0c | ||
|
|
fe2e8398c3 | ||
|
|
2c7245298f | ||
|
|
5e209bff32 | ||
|
|
e0855bee7b | ||
|
|
ae5cd0a99d | ||
|
|
0c64282391 | ||
|
|
3d3a479a59 | ||
|
|
16e1fbd9a9 | ||
|
|
1c934d5a82 | ||
|
|
a127ac9b53 | ||
|
|
8548c72d22 | ||
|
|
914ab93ab8 | ||
|
|
124cc050f3 | ||
|
|
0aa54010a9 | ||
|
|
5c1870e9f7 | ||
|
|
958973b57c | ||
|
|
c71d087695 | ||
|
|
bb9b131b2f | ||
|
|
9ac60549c7 | ||
|
|
75a590a411 | ||
|
|
ad65f25950 | ||
|
|
f37ad00984 | ||
|
|
86ce735062 | ||
|
|
0b6897c1ed | ||
|
|
eaa58a5ba0 | ||
|
|
c3d3242001 | ||
|
|
827200c6f9 | ||
|
|
84478220e9 | ||
|
|
5ec7d838ac | ||
|
|
82e379ee7f | ||
|
|
10307291a3 | ||
|
|
a260a921ba | ||
|
|
62b0d2a971 | ||
|
|
ac598c7f95 | ||
|
|
9641415a00 | ||
|
|
4f61036847 | ||
|
|
46a09116ea | ||
|
|
75a62b741e | ||
|
|
67b5cae29f | ||
|
|
905876f865 | ||
|
|
d20128add5 | ||
|
|
110e421dd6 | ||
|
|
b13f1f4de7 | ||
|
|
7b240cab34 | ||
|
|
a3ce48f14f | ||
|
|
3b8500c410 | ||
|
|
37f8c5e48f | ||
|
|
b4f51d1353 | ||
|
|
1cabb14bcc | ||
|
|
d59c998b71 | ||
|
|
f8e868eaaf | ||
|
|
7c38e5b8c6 | ||
|
|
d164830770 | ||
|
|
5232186a3b | ||
|
|
828ab6cf4b | ||
|
|
45a6a08fc1 | ||
|
|
25c480ae22 | ||
|
|
78ac25b752 | ||
|
|
5de00da3ec | ||
|
|
6cddde08eb | ||
|
|
e2950fbdef | ||
|
|
02bac76d1a | ||
|
|
7474ed9b4f | ||
|
|
8b4bc7cdc9 | ||
|
|
27cbed207b | ||
|
|
b781f90046 | ||
|
|
520aa075fa | ||
|
|
0aab46588d | ||
|
|
02e835eb06 | ||
|
|
668a0dad9a | ||
|
|
8249756bcf | ||
|
|
2e4c1dfe4d | ||
|
|
51481fb054 | ||
|
|
3044ac0c88 | ||
|
|
b8334d3463 | ||
|
|
95ac0d9f8f | ||
|
|
6c2ef63847 | ||
|
|
ae3bf436b4 | ||
|
|
084bc60d5e | ||
|
|
ac1c8efd8e | ||
|
|
445bcb70b7 | ||
|
|
d01682da21 | ||
|
|
1404ceb38f | ||
|
|
9a545a9e7c | ||
|
|
91fee46a59 | ||
|
|
8fcb28a0dd | ||
|
|
1e2b9f0623 | ||
|
|
be0f596abe | ||
|
|
5f1c9dbf1f | ||
|
|
076077223a | ||
|
|
f1310f303f | ||
|
|
85676cf023 | ||
|
|
ecc7da732b | ||
|
|
db2a58ca44 | ||
|
|
9c80c0f3ac | ||
|
|
7b7ea2ab5f | ||
|
|
3418bcc3a9 | ||
|
|
55c1299619 | ||
|
|
6242966f44 | ||
|
|
f5fb8497bc | ||
|
|
c165021b49 | ||
|
|
dfc1d2e5cd | ||
|
|
fe36d60c66 | ||
|
|
f765b285f0 | ||
|
|
3d9656c4c2 | ||
|
|
8daa190351 | ||
|
|
b2c73f6d1a | ||
|
|
44aa8ed11b | ||
|
|
6681d17624 | ||
|
|
40097efbb5 | ||
|
|
0f70b5da31 | ||
|
|
02a19fecbf | ||
|
|
4dc5f6a249 | ||
|
|
ebe0183de0 | ||
|
|
62899daa17 | ||
|
|
75b36463f1 | ||
|
|
5f5f72b65d | ||
|
|
e21c920507 | ||
|
|
93bef66767 | ||
|
|
12642d523d | ||
|
|
96e7b665f7 | ||
|
|
d721b41b95 | ||
|
|
7fa1db8982 | ||
|
|
c334873c26 | ||
|
|
9e95d5fe97 | ||
|
|
f6043b5477 | ||
|
|
729dc8464e | ||
|
|
c761d083a7 | ||
|
|
79c171655b | ||
|
|
73a5d22041 | ||
|
|
f53bef95bf | ||
|
|
9bc577a83a | ||
|
|
189d1c7c1d | ||
|
|
9073e4b987 | ||
|
|
56d37467d1 | ||
|
|
3d05a6ee1e | ||
|
|
ab196b35a6 | ||
|
|
1f519121f8 | ||
|
|
08d34a9efe | ||
|
|
4cd8781fd8 | ||
|
|
74ff48840b | ||
|
|
a6e8aa6d01 | ||
|
|
6069c461e8 | ||
|
|
ad67c2958e | ||
|
|
a7dec45c8e | ||
|
|
fa58e1f79b | ||
|
|
b772c6351c | ||
|
|
4e66ceffc3 | ||
|
|
2d669b4361 | ||
|
|
5fd6bc9984 | ||
|
|
5b05c870fb | ||
|
|
d051dc2560 | ||
|
|
14644f91a6 | ||
|
|
36d2fb8723 | ||
|
|
5c99214ae3 | ||
|
|
1aaf9e63ec | ||
|
|
5fbbb114c8 | ||
|
|
f71da9fb42 | ||
|
|
7439befa31 | ||
|
|
c95adcbd01 | ||
|
|
ddd91341b6 | ||
|
|
279fa2ff20 | ||
|
|
f56035caa3 | ||
|
|
d8d5cf21aa | ||
|
|
b29e0b82d1 | ||
|
|
70224401c5 | ||
|
|
354acbbdf6 | ||
|
|
4a35054446 | ||
|
|
60ff870274 | ||
|
|
05659e5873 | ||
|
|
005d6e6cff | ||
|
|
19bb696722 | ||
|
|
3ea5b0ee37 | ||
|
|
4acbab01b6 | ||
|
|
750ab79d4c | ||
|
|
fe1978d289 | ||
|
|
208fb75e1e | ||
|
|
a7d77a9cc7 | ||
|
|
66763faef8 | ||
|
|
5f6d331965 | ||
|
|
cd291cbcac | ||
|
|
07c31ce81e | ||
|
|
59d501a2f0 | ||
|
|
1dfee4eac7 | ||
|
|
56999599ed | ||
|
|
726d38b442 | ||
|
|
57ac38fb09 | ||
|
|
2461cfd745 | ||
|
|
98463a3ce6 | ||
|
|
f25bea90f1 | ||
|
|
774241a179 | ||
|
|
4293eadf63 | ||
|
|
210f49cb4a | ||
|
|
56d92f5c93 | ||
|
|
fb9f96a54b | ||
|
|
fd7111f802 | ||
|
|
5215b50c73 | ||
|
|
4ca3cfe1a5 | ||
|
|
d875cafb78 | ||
|
|
1bf8fbc52b | ||
|
|
ec8cb4c19a | ||
|
|
18222286f0 | ||
|
|
b3988c9aa1 | ||
|
|
7c857fd537 | ||
|
|
efb381eb97 | ||
|
|
56882ee489 | ||
|
|
1b1f2b0bb1 | ||
|
|
f4d1181d99 | ||
|
|
b4193e24b5 | ||
|
|
a676df2c6c | ||
|
|
44efdff9ec | ||
|
|
710ac88a43 | ||
|
|
5a989cd62f | ||
|
|
8bc07ca700 | ||
|
|
9b14d0b4a6 | ||
|
|
9d67bae487 | ||
|
|
95d3cc67cb | ||
|
|
2ecdc752e4 | ||
|
|
6a5b254a75 | ||
|
|
8944d1522e | ||
|
|
3524485249 | ||
|
|
70e713d68b | ||
|
|
16616c65a6 | ||
|
|
01fa5ffec8 | ||
|
|
cb1fb02d86 | ||
|
|
914402aa2a | ||
|
|
04a63da6ab | ||
|
|
61eba36c67 | ||
|
|
82e38cb7c4 | ||
|
|
e88fe2ed61 | ||
|
|
556570a51a | ||
|
|
41cf5904e5 | ||
|
|
ebe949b585 | ||
|
|
2bfaa0092d | ||
|
|
d5aee29558 | ||
|
|
d3caa9dcd4 | ||
|
|
e909c929b1 | ||
|
|
cbad4f651e | ||
|
|
a13e8c4544 | ||
|
|
1994b84810 | ||
|
|
263e9b7337 | ||
|
|
ce7210ea8b | ||
|
|
c121fad03f | ||
|
|
8d81ed3baa | ||
|
|
d6616c8b76 | ||
|
|
18ee400d6f | ||
|
|
d0db63c3b0 | ||
|
|
f603de9030 | ||
|
|
74d12c4303 | ||
|
|
cce68c323e | ||
|
|
a3540aef6f | ||
|
|
7e5fe95928 | ||
|
|
10df024bb2 | ||
|
|
bbb8ca15c6 | ||
|
|
2769ac2ef5 | ||
|
|
e047f62511 | ||
|
|
5f6a94247e | ||
|
|
81357e15e4 | ||
|
|
46233883c3 | ||
|
|
5481454bf6 | ||
|
|
bb0b1f2bce | ||
|
|
ffa8a44320 | ||
|
|
7a9f50c0da | ||
|
|
7888a1e725 | ||
|
|
117ebfd097 | ||
|
|
76487a1244 | ||
|
|
018aec0150 | ||
|
|
c33ccaba98 | ||
|
|
14dcd69a1d | ||
|
|
aea236bd18 | ||
|
|
4e01d3acd6 | ||
|
|
c35f7da532 | ||
|
|
163bd0328a | ||
|
|
94cc923d01 | ||
|
|
01735954dc | ||
|
|
affd9258c5 | ||
|
|
cffa342841 | ||
|
|
596ac24499 | ||
|
|
634def86ee | ||
|
|
6e7e99201e | ||
|
|
ce92cddf0e | ||
|
|
322b103481 | ||
|
|
f35585b3ac | ||
|
|
fc058fcfa7 | ||
|
|
f4332c779e | ||
|
|
7036cb8ea2 | ||
|
|
f1af8a76e5 | ||
|
|
d638ecd111 | ||
|
|
a8b4ef9230 | ||
|
|
cc2a46a19b | ||
|
|
3f603be776 | ||
|
|
e7c5830c48 | ||
|
|
9da829723d | ||
|
|
978b79f38a | ||
|
|
076b2e2ae1 | ||
|
|
ecaf1cba88 | ||
|
|
54619917ae | ||
|
|
bf63b3dc2b | ||
|
|
fa8356aec1 | ||
|
|
53f0f51c7b | ||
|
|
d3ea853e4c | ||
|
|
90ff40fbf0 | ||
|
|
86b6b8ed50 | ||
|
|
55bbf70d3f | ||
|
|
ca2c61ee64 | ||
|
|
f4033c69bd | ||
|
|
a5818e43b5 | ||
|
|
3c18636e23 | ||
|
|
2917bf9fda | ||
|
|
2abb1097e3 | ||
|
|
05fea68f82 | ||
|
|
b5eda738c9 | ||
|
|
4a97a0f999 | ||
|
|
04681237c0 | ||
|
|
5926c4392f | ||
|
|
bfe2879893 | ||
|
|
2ff05c4837 | ||
|
|
3a100f96e7 | ||
|
|
69bfc3f47e | ||
|
|
e3db6c60de | ||
|
|
449eaf70f0 | ||
|
|
4b8f772538 | ||
|
|
485923abd7 | ||
|
|
f24f5cbdd6 | ||
|
|
32ef28f1e6 | ||
|
|
2286436602 | ||
|
|
53a5a88bfa | ||
|
|
f46f7e1e74 | ||
|
|
3e1db58144 | ||
|
|
807813ecdb | ||
|
|
a4b2e18052 | ||
|
|
b72f5d06b6 | ||
|
|
cffc96ca8a | ||
|
|
86d1b4929e | ||
|
|
7bc51655b9 | ||
|
|
2c6a2f232d | ||
|
|
3f72e53bc4 | ||
|
|
272c5b62c2 | ||
|
|
8e3e40d790 | ||
|
|
c6cdd7c66a | ||
|
|
9b36fe0bc8 | ||
|
|
4c0bbba6f7 | ||
|
|
de2144819e | ||
|
|
62618ca6b3 | ||
|
|
5d8e70da95 | ||
|
|
ed3dddd5a5 | ||
|
|
a3cbbd4617 | ||
|
|
b71ec29db6 | ||
|
|
8326475a9f | ||
|
|
5023fc5c90 | ||
|
|
59e9ae9480 | ||
|
|
0ab099825d | ||
|
|
8bca529bd3 | ||
|
|
88ab57eba7 | ||
|
|
383f0ce39f | ||
|
|
3931915ee8 | ||
|
|
82fa47fc6c | ||
|
|
7667b4daa6 | ||
|
|
f63772668e | ||
|
|
f05755d55c | ||
|
|
cc78d19fc9 | ||
|
|
10ecf62ed5 | ||
|
|
47ff924184 | ||
|
|
1c3d7438c1 | ||
|
|
45d7dd7dd9 | ||
|
|
31caeea6d4 | ||
|
|
1f3298b2e5 | ||
|
|
aef6803a76 | ||
|
|
ba93a87eed | ||
|
|
4d14071ede | ||
|
|
39934033b8 | ||
|
|
5a98059387 | ||
|
|
eddb8bec4a | ||
|
|
e8f91f4908 | ||
|
|
94fcdfcded | ||
|
|
741563fa62 | ||
|
|
0feb8c7a3c | ||
|
|
08506e011f | ||
|
|
4d2f54f5df | ||
|
|
34a5022f0c | ||
|
|
40b2265cdc | ||
|
|
32371bf2a7 | ||
|
|
b39c7b2cf0 | ||
|
|
b91054e783 | ||
|
|
913ff1bd63 | ||
|
|
5fdf235ae2 | ||
|
|
851728e46d | ||
|
|
b3ef6ca846 | ||
|
|
0371173830 | ||
|
|
53cc837dcb | ||
|
|
d1a99eee6b | ||
|
|
ce2c45aa44 | ||
|
|
c1ac8c2fbd | ||
|
|
89765d28bb | ||
|
|
4c764ea54a | ||
|
|
cf6b7989b6 | ||
|
|
54eac655af | ||
|
|
377149948e | ||
|
|
ad4d087dde | ||
|
|
0f2e551c7a | ||
|
|
689dd037c1 | ||
|
|
a4d8c19247 | ||
|
|
ec269d9671 | ||
|
|
ccb691edd8 | ||
|
|
084a7292a1 | ||
|
|
e0ad32c044 | ||
|
|
7c3e5aba0c | ||
|
|
324c502ce3 | ||
|
|
76125f45ae | ||
|
|
2c024d10ba | ||
|
|
dabf142759 | ||
|
|
f9a7ca4d19 | ||
|
|
721a427351 | ||
|
|
a6e613bdec | ||
|
|
558096748f | ||
|
|
51fbd7c620 | ||
|
|
629967b7d1 | ||
|
|
7a23dbfb30 | ||
|
|
043aadcb2c | ||
|
|
301a12b57d | ||
|
|
a228944434 | ||
|
|
f59e733fe2 | ||
|
|
c3c008ff72 | ||
|
|
6b26b26115 | ||
|
|
7c4a6caa9a | ||
|
|
9d3556e557 | ||
|
|
e22e09122c | ||
|
|
b4c5b5a77a | ||
|
|
0f79fc3037 | ||
|
|
12bb87931e | ||
|
|
b259102d6c | ||
|
|
91c6396dac | ||
|
|
fe7d9baa38 | ||
|
|
ea8743fa99 | ||
|
|
9e11f02a45 | ||
|
|
0ac7aabf9a | ||
|
|
1488accbe3 | ||
|
|
bd91b28335 | ||
|
|
adc9eb5b09 | ||
|
|
8e5368f83f | ||
|
|
390db416d8 | ||
|
|
1d1a427c7b | ||
|
|
8695bef4f4 | ||
|
|
551584d7e9 | ||
|
|
668561f006 | ||
|
|
79141e173c | ||
|
|
7a6fe0d91e | ||
|
|
efd68b6453 | ||
|
|
a7b861e4be | ||
|
|
b304d86fc4 | ||
|
|
76d18490f2 | ||
|
|
4f1e642eb8 | ||
|
|
e4b28b4d61 | ||
|
|
5ee5366b4b | ||
|
|
be370beb05 | ||
|
|
87895ab946 | ||
|
|
7645daa9f6 | ||
|
|
ac7b345de7 | ||
|
|
3a9963c81b | ||
|
|
b265d1b432 | ||
|
|
41f7b8ee47 | ||
|
|
b2310cfa75 | ||
|
|
41c22deb91 | ||
|
|
8cabdb80ee | ||
|
|
704d33d956 | ||
|
|
bbdb5a72da | ||
|
|
16aa1ebf2c | ||
|
|
96dd511773 | ||
|
|
4b6fc40588 | ||
|
|
8aff5a7f8b | ||
|
|
ff64a49a40 | ||
|
|
8c1da2dd43 | ||
|
|
49a714e3b8 | ||
|
|
c18921a5c2 | ||
|
|
86e5f8dd49 | ||
|
|
aad2e38603 | ||
|
|
5becf2c98c | ||
|
|
2dbbf69a4c | ||
|
|
5364befc75 | ||
|
|
6f0c50312b | ||
|
|
af753bba8a | ||
|
|
335af65709 | ||
|
|
592afff827 | ||
|
|
6d8e1e754f | ||
|
|
a010787d26 | ||
|
|
ae16e052d3 | ||
|
|
a2fd2b8bd8 | ||
|
|
47b12bb07a | ||
|
|
7479ef770c | ||
|
|
8cfd6d6652 | ||
|
|
89163b09e7 | ||
|
|
b6d7d4309a | ||
|
|
19eec3e108 | ||
|
|
d3391efc2b | ||
|
|
b1c370f959 | ||
|
|
c887486970 | ||
|
|
9ece2de5ba | ||
|
|
e7467199f2 | ||
|
|
7dab795f76 | ||
|
|
3c37cede17 | ||
|
|
4582640d68 | ||
|
|
d4afb33d48 | ||
|
|
bbe9c6b77e | ||
|
|
42b187a0ed | ||
|
|
04176f3f23 | ||
|
|
af9d3d3f8a | ||
|
|
eceb0ac2af | ||
|
|
2164a025f3 | ||
|
|
15a02f5664 | ||
|
|
a056753edc | ||
|
|
6a101b4053 | ||
|
|
2299eee106 | ||
|
|
b3f10dd6eb | ||
|
|
383d6f0057 | ||
|
|
20d14c0bdd | ||
|
|
ca81c35486 | ||
|
|
958cd9321a | ||
|
|
8d9f15ca3e | ||
|
|
dbbf061121 | ||
|
|
40f1eb46cc | ||
|
|
ee1c264aac | ||
|
|
2acd7c30f4 | ||
|
|
f69036f5a3 | ||
|
|
caa6bd61cd | ||
|
|
b23a230e4d | ||
|
|
cb753ec048 | ||
|
|
14e3ce8c06 | ||
|
|
8db16c5100 | ||
|
|
5e352fe3e6 | ||
|
|
4e0cccbce0 | ||
|
|
e570d4c93a | ||
|
|
28ad50e43b | ||
|
|
2a1b5c6cd1 | ||
|
|
0ec091a5c8 | ||
|
|
0edd99a0fd | ||
|
|
4e34a96649 | ||
|
|
4ac2fbe0b7 | ||
|
|
13f27e2597 | ||
|
|
32a59c0c26 | ||
|
|
1dc7698746 | ||
|
|
a96ee4a2dd | ||
|
|
c5d76a2cc0 | ||
|
|
3c3954139c | ||
|
|
5151f878f5 | ||
|
|
8044cf04e4 | ||
|
|
857f4e117d | ||
|
|
d60a24c241 | ||
|
|
bb46a77302 | ||
|
|
6876908e4f | ||
|
|
a5e1a9b03d | ||
|
|
dd66e315a7 | ||
|
|
da541ef962 | ||
|
|
55174a60bb | ||
|
|
cb3bb62177 | ||
|
|
2e03d0f81e | ||
|
|
82bb9c63b8 | ||
|
|
d266f7521c | ||
|
|
4d01c50084 | ||
|
|
6b18113cf7 | ||
|
|
50d41031e4 | ||
|
|
5b4f7368ad | ||
|
|
1ce8a78e04 | ||
|
|
0fe40b5283 | ||
|
|
2229be3ca0 | ||
|
|
6af8409f00 | ||
|
|
5db8e18f44 | ||
|
|
a7324f761b | ||
|
|
9ea2fa0517 | ||
|
|
510dbd686a | ||
|
|
e2de114767 | ||
|
|
bf15897cc0 | ||
|
|
a95e6e73e5 | ||
|
|
1e78af657b | ||
|
|
bc4d84f441 | ||
|
|
79dba0f0f2 | ||
|
|
f7629d8ea0 | ||
|
|
ac64f93d77 | ||
|
|
99f1c318b6 | ||
|
|
e86748f193 | ||
|
|
6dd26f2709 | ||
|
|
6b9fdd1204 | ||
|
|
eaed8ccd05 | ||
|
|
22708ce445 | ||
|
|
4e23f1e032 | ||
|
|
ef4fa58b7c | ||
|
|
2c5e217468 | ||
|
|
5c953c81d4 | ||
|
|
a79b221f75 | ||
|
|
fed1b4e1f3 | ||
|
|
30b659ac80 | ||
|
|
4ef13d8670 | ||
|
|
5b9e0f40b5 | ||
|
|
ec68852f4e | ||
|
|
1847024267 | ||
|
|
66fa7006d9 | ||
|
|
7bc38ef685 | ||
|
|
b351135438 | ||
|
|
50a6abd339 | ||
|
|
9febe08fbd | ||
|
|
25277600b0 | ||
|
|
72ee4790a5 | ||
|
|
f3a223bf48 | ||
|
|
49da4272c6 | ||
|
|
f6f4ec33fb | ||
|
|
d4b79c40ae | ||
|
|
26d86f6a99 | ||
|
|
6dd3c9a587 | ||
|
|
68af062875 | ||
|
|
36dcb9a074 | ||
|
|
d2cc38dd50 | ||
|
|
c473a25a48 | ||
|
|
42ed9c70e2 | ||
|
|
71bb6f64ab | ||
|
|
26ee40697b | ||
|
|
2a215481ba | ||
|
|
a71aa83150 | ||
|
|
f9622e5913 | ||
|
|
0dc964854d | ||
|
|
7167c7081a | ||
|
|
481bfe3b2e | ||
|
|
efaec030e6 | ||
|
|
0d272342f2 | ||
|
|
ec5f4d1898 | ||
|
|
94cfadda5e | ||
|
|
f976be0d94 | ||
|
|
853ddd7d68 | ||
|
|
b69684457f | ||
|
|
f751a179a0 | ||
|
|
8c09521bb4 | ||
|
|
ae41963c87 | ||
|
|
4a362d1ba6 | ||
|
|
6699f0e5c2 | ||
|
|
40216c919d | ||
|
|
cf779b78e9 | ||
|
|
ed4f7ed5fe | ||
|
|
759a6573b9 | ||
|
|
ee1627a37e | ||
|
|
a6a10be4e6 | ||
|
|
e20ad2083d | ||
|
|
0164eb4c56 | ||
|
|
3f4c3edbfe | ||
|
|
1c5906d9da | ||
|
|
e29e8b4264 | ||
|
|
02a061ff11 | ||
|
|
dbf0b6bb05 | ||
|
|
19d047d5de | ||
|
|
0efec5ae7d | ||
|
|
efb5cda657 | ||
|
|
2a5002dc8d | ||
|
|
4e3f730254 | ||
|
|
7c82f88055 | ||
|
|
c4a246293e | ||
|
|
34ab8e4aac | ||
|
|
38a6e8113b | ||
|
|
747d61dff6 | ||
|
|
56fe6f2efe | ||
|
|
ea990330e6 | ||
|
|
60a648f501 | ||
|
|
2f1e097599 | ||
|
|
b74e4d4efa | ||
|
|
58f5d14593 | ||
|
|
84fe10a652 | ||
|
|
92e4e1a76d | ||
|
|
923ebcb3a8 | ||
|
|
f33125154f | ||
|
|
0d54074f6b | ||
|
|
343716ce0e | ||
|
|
36e8bbb5f6 | ||
|
|
5826f748a4 | ||
|
|
c4bd976c57 | ||
|
|
f214183042 | ||
|
|
253a0ef51b | ||
|
|
a2a8144ebb | ||
|
|
a906439ab7 | ||
|
|
5944a966e2 | ||
|
|
52a151f4b0 | ||
|
|
704dacb0cf | ||
|
|
b087466feb | ||
|
|
fef8267916 | ||
|
|
1b87f477af | ||
|
|
8c002ab803 | ||
|
|
b924cbddbe | ||
|
|
ce71828a9a | ||
|
|
df88022556 | ||
|
|
bdc68756a2 | ||
|
|
48775ff050 | ||
|
|
7880eecfad | ||
|
|
a6a0e40df0 | ||
|
|
36954a0ae2 | ||
|
|
ad1dc1445f | ||
|
|
995860b963 | ||
|
|
4e7a211b75 | ||
|
|
ea991297bd | ||
|
|
feda90a7f7 | ||
|
|
683e0363e0 | ||
|
|
65c063548e | ||
|
|
db4323427b | ||
|
|
5a2d4a692c | ||
|
|
b2dd1638f6 | ||
|
|
ef936837a9 | ||
|
|
412c57911c | ||
|
|
03c76bf1e8 | ||
|
|
4b19e76fff | ||
|
|
61da12a012 | ||
|
|
22d30ab8aa | ||
|
|
8b692e0e88 | ||
|
|
935612bfb5 | ||
|
|
1c969fedc4 | ||
|
|
6b771e9858 | ||
|
|
842947fef8 | ||
|
|
796ecb4799 | ||
|
|
c2dba516e1 | ||
|
|
8de2880d2d | ||
|
|
b682e9d89d | ||
|
|
ccea2d28c7 | ||
|
|
8ef751f06f | ||
|
|
2248ec193f | ||
|
|
165596255c | ||
|
|
99174b68d0 | ||
|
|
0541d555cc | ||
|
|
39143b6dfc | ||
|
|
055ebafe73 | ||
|
|
9b2a8dbad9 | ||
|
|
d556948c87 | ||
|
|
2ab0df4965 | ||
|
|
54352af728 | ||
|
|
f1ec9fadb6 | ||
|
|
4c7de2edc5 | ||
|
|
5d1f1c4acf | ||
|
|
e1f615c16e | ||
|
|
bf81c27860 | ||
|
|
abcd969443 | ||
|
|
4865b3c8c0 | ||
|
|
7637cf740a | ||
|
|
f08381be21 | ||
|
|
6d5ee6d20c | ||
|
|
c0e193726e | ||
|
|
305fa8a7f4 | ||
|
|
01e0a64ab3 | ||
|
|
765e2e0fcb | ||
|
|
053f9d2ff9 | ||
|
|
015ae51b19 | ||
|
|
395b4ab51d | ||
|
|
d169adb4d0 | ||
|
|
d632ac6f20 | ||
|
|
fb42ab8113 | ||
|
|
8cf54b7e04 | ||
|
|
839fb561bf | ||
|
|
94e24ad98a | ||
|
|
a60fb964f6 | ||
|
|
9bf97a900e | ||
|
|
8eeeb3e6c6 | ||
|
|
de1d74ba37 | ||
|
|
bd3a2a32dc | ||
|
|
b142ebdabd | ||
|
|
b6c116fb4c | ||
|
|
2e9910ddd3 | ||
|
|
7badb3eea0 | ||
|
|
95c63a3e4f | ||
|
|
35930c2a5c | ||
|
|
392a640ea6 | ||
|
|
14b8acda97 | ||
|
|
a1ae289c58 | ||
|
|
0e1da1a631 | ||
|
|
56aef95c0f | ||
|
|
4040e47c21 | ||
|
|
97ada5a4e0 | ||
|
|
b3b06f9d6f | ||
|
|
4a67f4537c | ||
|
|
0f3829fefe | ||
|
|
f3f45130b2 | ||
|
|
7132e362e4 | ||
|
|
52090ddcc3 | ||
|
|
caf20ca7b1 | ||
|
|
955aae36d4 | ||
|
|
5c57c3619b | ||
|
|
e1683f6743 | ||
|
|
7f212b116b | ||
|
|
09440ce56d | ||
|
|
d4e86aea73 | ||
|
|
81ed67d236 | ||
|
|
5198b13904 | ||
|
|
39ffeb2cf5 | ||
|
|
ca587620a1 | ||
|
|
86f82e0b50 | ||
|
|
787e54f22f | ||
|
|
b0c867e283 | ||
|
|
d0f7722606 | ||
|
|
9170b8b6a7 | ||
|
|
8cce30c9ad | ||
|
|
8dff85e291 | ||
|
|
f96723a420 | ||
|
|
148b575c0d | ||
|
|
254350668a | ||
|
|
8d6bcd1c86 | ||
|
|
db9415b699 | ||
|
|
3294c636c0 | ||
|
|
70b8f1ed15 | ||
|
|
fd5ac24a7c | ||
|
|
683da55ad6 | ||
|
|
51685d413d | ||
|
|
af60205bb1 | ||
|
|
7d2809273a | ||
|
|
49f1291086 | ||
|
|
22c511d360 | ||
|
|
0ea9ed49c4 | ||
|
|
8066cab451 | ||
|
|
83a1e9a7ea | ||
|
|
28a2d233fa | ||
|
|
4ed9b256a7 | ||
|
|
6b7d85e49b | ||
|
|
80b337a0fa | ||
|
|
582fbb3125 | ||
|
|
32f2779a6c | ||
|
|
a82aabb0ca | ||
|
|
15a5e57197 | ||
|
|
6bc6c6cdf3 | ||
|
|
7041487b2c | ||
|
|
2e488303b0 | ||
|
|
837868999a | ||
|
|
1a13296ef3 | ||
|
|
ff89f99fcb | ||
|
|
77c29f5bd9 | ||
|
|
cfc1a1e912 | ||
|
|
2c4b0d274a | ||
|
|
033b4c8e3d | ||
|
|
e620cdfb24 | ||
|
|
a494f8e700 | ||
|
|
87b75735c0 | ||
|
|
b6d8395666 | ||
|
|
842fb43e3a | ||
|
|
49302a7f6f | ||
|
|
1e3882506a | ||
|
|
3f60915732 | ||
|
|
3eb1b22937 | ||
|
|
88507156c2 | ||
|
|
b2af840ea3 | ||
|
|
249b8c5729 | ||
|
|
0b942f70d6 | ||
|
|
6514aa9833 | ||
|
|
1718129044 | ||
|
|
57062d6072 | ||
|
|
a14c89d7a1 | ||
|
|
2788671d45 | ||
|
|
e09a8fb0b9 | ||
|
|
7df126ec39 | ||
|
|
e741a6358e | ||
|
|
c1c0389544 | ||
|
|
9f034d8ede | ||
|
|
5a36067bab | ||
|
|
ed6f8b0ee8 | ||
|
|
696ee9e146 | ||
|
|
11b7613c52 | ||
|
|
500b778f09 | ||
|
|
df996ffb47 | ||
|
|
2e4c64ac69 | ||
|
|
b716d30b00 | ||
|
|
ee79a19bed | ||
|
|
a28b985423 | ||
|
|
7e1970c598 | ||
|
|
2a8d7be8e2 | ||
|
|
1c3a117d02 | ||
|
|
5fc8127230 | ||
|
|
ca35d4e6c6 | ||
|
|
2d972468fe | ||
|
|
9325b22198 | ||
|
|
625c257c42 | ||
|
|
0e0d1e2973 | ||
|
|
cec53f5de5 | ||
|
|
cf98f47e8c | ||
|
|
960430c848 | ||
|
|
8414dcec9c | ||
|
|
2656eb9903 | ||
|
|
22f5da680c | ||
|
|
b6f4227553 | ||
|
|
e47a6f1a05 | ||
|
|
d44c079116 | ||
|
|
4d13ac9f4e | ||
|
|
cc4002c182 | ||
|
|
c7a5f943ad | ||
|
|
732d4fa658 | ||
|
|
3e148bf4ec | ||
|
|
50d698a985 | ||
|
|
744d0fa453 | ||
|
|
e9f5d257f1 | ||
|
|
8c1f3ad99e | ||
|
|
63ebe53865 | ||
|
|
f4f3f65701 | ||
|
|
ba26351338 | ||
|
|
54a12b724e | ||
|
|
1d3fcd84c8 | ||
|
|
a7933b755c | ||
|
|
d9c67255a3 | ||
|
|
f7c566a85a | ||
|
|
2ebfba7261 | ||
|
|
67b2c76136 | ||
|
|
5a5f97e687 | ||
|
|
9a0c3056a5 | ||
|
|
8cbb379b6e | ||
|
|
8c793b162b | ||
|
|
983ff4959d | ||
|
|
e297c55e7b | ||
|
|
0ce42cfc9d | ||
|
|
ca1f7c6a26 | ||
|
|
4f5b34eff5 | ||
|
|
e88420c525 | ||
|
|
ea45c6cefe | ||
|
|
e4b345919b | ||
|
|
b99befe1b1 | ||
|
|
d487273990 | ||
|
|
87d7805714 | ||
|
|
7d70fe6d78 | ||
|
|
28e2663aa5 | ||
|
|
28705aa986 | ||
|
|
b8ae309e15 | ||
|
|
8a15d707c7 | ||
|
|
f3088d3947 | ||
|
|
85fd114404 | ||
|
|
635dec1787 | ||
|
|
395e25729f | ||
|
|
23d3441bbd | ||
|
|
91518ab4c7 | ||
|
|
116ed88683 | ||
|
|
a74731f9b1 | ||
|
|
0f9032a190 | ||
|
|
e2db6fc25d | ||
|
|
7ef2eb1037 | ||
|
|
1ae1c5f307 | ||
|
|
aef41a20c4 | ||
|
|
85145b3205 | ||
|
|
3b2efffb2d | ||
|
|
6d42fe9e35 | ||
|
|
555460a13a | ||
|
|
fca61f7314 | ||
|
|
4e1079f272 | ||
|
|
4c306025b4 | ||
|
|
6257910000 | ||
|
|
4850eaf586 | ||
|
|
dee5abc100 | ||
|
|
53b08abc57 | ||
|
|
af9e70a7f1 | ||
|
|
6be62338cb | ||
|
|
7e137538bb | ||
|
|
36bb5cd484 | ||
|
|
7f9acde217 | ||
|
|
077988efac | ||
|
|
4b9863e55e | ||
|
|
01d150185b | ||
|
|
505d343025 | ||
|
|
883b9154d7 | ||
|
|
e8ced37128 | ||
|
|
65cf658652 | ||
|
|
cb3189d443 | ||
|
|
122430dfef | ||
|
|
9095aac713 | ||
|
|
7fa6b8c51c | ||
|
|
dba345859f | ||
|
|
1982625e2f | ||
|
|
63c40e995c | ||
|
|
2c65ee4224 | ||
|
|
116f7359df | ||
|
|
410cb62fac | ||
|
|
ae547ccd23 | ||
|
|
1f3f582217 | ||
|
|
5d3f697c81 | ||
|
|
94432e2803 | ||
|
|
1c8c387d8e | ||
|
|
857fcdef7b | ||
|
|
ab55c8a383 | ||
|
|
baf8291a47 | ||
|
|
d0c5dacdd8 | ||
|
|
f080eeb1e3 | ||
|
|
8149939d6f | ||
|
|
c74618fa5f | ||
|
|
1086ece85d | ||
|
|
34310684f2 | ||
|
|
2bfff528ed | ||
|
|
bba819970a | ||
|
|
71eb4388d1 | ||
|
|
b93209c107 | ||
|
|
83d0ebcba4 | ||
|
|
e276ee0974 | ||
|
|
8ae6e8b8cf | ||
|
|
93e304f5d0 | ||
|
|
49004caa12 | ||
|
|
bc7b2a7b9f | ||
|
|
5c9182a84c | ||
|
|
356094b19c | ||
|
|
cd46dd4a81 | ||
|
|
f5bd52bac9 | ||
|
|
5ff1de40d7 | ||
|
|
4f2a507f2e | ||
|
|
5cb7311b30 | ||
|
|
3a3018a8d2 | ||
|
|
7e0d44b480 | ||
|
|
d7957002ee | ||
|
|
d62049cd59 | ||
|
|
6b3fbbe9fd | ||
|
|
5bdd5f0c17 | ||
|
|
ba24d1251d | ||
|
|
dd5ac3e361 | ||
|
|
8f43c23b96 | ||
|
|
18551c74b5 | ||
|
|
4461463d9d | ||
|
|
a7d6974872 | ||
|
|
4d73f86474 | ||
|
|
83542b8dc4 | ||
|
|
3e52f7f954 | ||
|
|
5b8a83c398 | ||
|
|
b5ee03c816 | ||
|
|
46cc5c2326 | ||
|
|
5547094315 | ||
|
|
81f5cd4446 | ||
|
|
06788687c3 | ||
|
|
c9b692382f | ||
|
|
9c231ad7ed | ||
|
|
24dce00f16 | ||
|
|
4298137694 | ||
|
|
8bc6a98574 | ||
|
|
2bab935044 | ||
|
|
eb4f541400 | ||
|
|
de2c8f6b97 | ||
|
|
1cd136e029 | ||
|
|
5a8252886c | ||
|
|
131c59bfb9 | ||
|
|
ce6be2b141 | ||
|
|
37ca047bff | ||
|
|
cb7212e7d2 | ||
|
|
799029a2bc | ||
|
|
a4a18fe920 | ||
|
|
afeb59b1ca | ||
|
|
1322993cc9 | ||
|
|
1585ddca89 | ||
|
|
d882699045 | ||
|
|
7ca07cdc1d | ||
|
|
8b4fbb1998 | ||
|
|
45b4c8a132 | ||
|
|
c500e7a4c6 | ||
|
|
ab052f52f0 | ||
|
|
ce97ddfd33 | ||
|
|
f2da10d1ca | ||
|
|
253dc5224e | ||
|
|
aa9b722d38 | ||
|
|
1d8998a133 | ||
|
|
ae62423fe0 | ||
|
|
4f14e236cc | ||
|
|
198fd8e638 | ||
|
|
724264d755 | ||
|
|
08df9580f5 | ||
|
|
98bb57a6d9 | ||
|
|
4f8c3591f5 | ||
|
|
44531adfc6 | ||
|
|
2790799b98 | ||
|
|
4378d4f6e2 | ||
|
|
60a8503e76 | ||
|
|
d872a4f0c8 | ||
|
|
bea26927e0 | ||
|
|
2fea5ae5a2 | ||
|
|
65be11a298 | ||
|
|
8345457acc | ||
|
|
844303be04 | ||
|
|
75cd36f7c8 | ||
|
|
d43150ff47 | ||
|
|
c122fb3412 | ||
|
|
6efbef5210 | ||
|
|
08ed4e6621 | ||
|
|
bb42e9b2a0 | ||
|
|
537bff0569 | ||
|
|
f0283aa2ff | ||
|
|
0626522738 | ||
|
|
3f88a0c3f5 | ||
|
|
e5d51cb13e | ||
|
|
88808ccce0 | ||
|
|
450c2598fa | ||
|
|
6081f7a1c4 | ||
|
|
fd2dbbdaa3 | ||
|
|
27ca342407 | ||
|
|
4ae8713f63 | ||
|
|
b96e31f083 | ||
|
|
9a78ce3fe5 | ||
|
|
7c4d5da9d2 | ||
|
|
da80935798 | ||
|
|
2b307fe6dd | ||
|
|
b91aea942b | ||
|
|
7e9ed37b18 | ||
|
|
2c9b092191 | ||
|
|
3da31a2a53 | ||
|
|
043bf9c874 | ||
|
|
70c6d2b970 | ||
|
|
11d9e1fef2 | ||
|
|
6c8c128b38 | ||
|
|
f19c28f69e | ||
|
|
68c663b3b2 | ||
|
|
d05dc44197 | ||
|
|
91b73dafce | ||
|
|
a4d292a988 | ||
|
|
327fa421a0 | ||
|
|
0f4244756e | ||
|
|
12b2c40817 | ||
|
|
48993d235b | ||
|
|
ec8eeff462 | ||
|
|
4365d70411 | ||
|
|
cc8693be60 | ||
|
|
54bf705240 | ||
|
|
73dbba8f17 | ||
|
|
ffba931c1b | ||
|
|
ab6c3543dd | ||
|
|
cbcda32df2 | ||
|
|
6f7155e4d0 | ||
|
|
993c59f398 | ||
|
|
c39e343357 | ||
|
|
05f25e994e | ||
|
|
e685b5739b | ||
|
|
161a363b04 | ||
|
|
ef0fb11a23 | ||
|
|
3c538da449 | ||
|
|
89621c21f5 | ||
|
|
526975c9b9 | ||
|
|
6d73b87ad4 | ||
|
|
97c4c85fc7 | ||
|
|
c27579181d | ||
|
|
e740d2dd04 | ||
|
|
fed8522972 | ||
|
|
fadc651d86 | ||
|
|
592f5b32b9 | ||
|
|
c14c2ac696 | ||
|
|
9219339417 | ||
|
|
b9177e7aec | ||
|
|
239073b97b | ||
|
|
2b7c0069bb | ||
|
|
f6dd602d2e | ||
|
|
e896e84e1f | ||
|
|
61c7afa635 | ||
|
|
080a458712 | ||
|
|
f8973e26c0 | ||
|
|
8ad5359b5b | ||
|
|
59f122f099 | ||
|
|
3544a6c8eb | ||
|
|
82e7f8a220 | ||
|
|
de7f6c56dc | ||
|
|
fb0376e5c5 | ||
|
|
e74a44b68f | ||
|
|
ca4d83f44a | ||
|
|
b9eab52f8b | ||
|
|
74a1da51ff | ||
|
|
a74e49f4ab | ||
|
|
67d9b7981e | ||
|
|
12522095a0 | ||
|
|
9802533a6b | ||
|
|
abf66cba73 | ||
|
|
1f220c8761 | ||
|
|
9257cb4490 | ||
|
|
f0456d15b9 | ||
|
|
6e9c509a4f | ||
|
|
60e80e7f14 | ||
|
|
1849cc227f | ||
|
|
9a7ca5cc27 | ||
|
|
f33cb44306 | ||
|
|
90cc76e6c7 | ||
|
|
ebb4ce2479 | ||
|
|
3eecfd4b96 | ||
|
|
4b5110bb51 | ||
|
|
df122dbcbe | ||
|
|
b9a0e4e409 | ||
|
|
8df7a8a019 | ||
|
|
86357ae748 | ||
|
|
896f9347a1 | ||
|
|
3634e6abe7 | ||
|
|
cb42053a1b | ||
|
|
ce5a9f6796 | ||
|
|
52f6705de6 | ||
|
|
060d430856 | ||
|
|
1b94d1e511 | ||
|
|
ae75d3449f | ||
|
|
81a958c31a | ||
|
|
275d89cf89 | ||
|
|
d533a4259d | ||
|
|
c9bbf0cf2b | ||
|
|
653d0492ae | ||
|
|
5f22fc64cb | ||
|
|
6a41110288 | ||
|
|
92fdfb9803 | ||
|
|
a8ae90934b | ||
|
|
73e6c69c0c | ||
|
|
b274fd17ad | ||
|
|
224bdc4eae | ||
|
|
8fe4ff628b | ||
|
|
2a2a9fbae4 | ||
|
|
e761f4c8c8 | ||
|
|
3fd5ad8682 | ||
|
|
0ebc50479c | ||
|
|
f7b42a3ee9 | ||
|
|
8a5c56db84 | ||
|
|
33f884e206 | ||
|
|
e2d4a95c44 | ||
|
|
0020afcf92 | ||
|
|
bd5ae84ba9 | ||
|
|
2bfb7bb04b | ||
|
|
9ac3eeb332 | ||
|
|
256cf20ab6 | ||
|
|
b53691f91a | ||
|
|
87e77c2654 | ||
|
|
aec152d793 | ||
|
|
9361dac428 | ||
|
|
99edc05cdf | ||
|
|
b823ea371a | ||
|
|
ba04ec275b | ||
|
|
9902e13c11 | ||
|
|
1bcd27db77 | ||
|
|
140efc2a78 | ||
|
|
95b3db34e3 | ||
|
|
43d87a6115 | ||
|
|
1103cde6b3 | ||
|
|
1dc0b60c22 | ||
|
|
e7630f46c7 | ||
|
|
987870eb26 | ||
|
|
53c35c0e02 | ||
|
|
a2eb14a79b | ||
|
|
8ac7231e74 | ||
|
|
bd7259cfe4 | ||
|
|
ff471d0ba4 | ||
|
|
1b8e05c861 | ||
|
|
49af9e3a1e | ||
|
|
83e2a187de | ||
|
|
394314dd3d | ||
|
|
bac66c5e19 | ||
|
|
0c14c78775 | ||
|
|
48c4f3cd6e | ||
|
|
73c699eb2b | ||
|
|
9faa6462b4 | ||
|
|
2c5ab6844a | ||
|
|
3e85d3314f | ||
|
|
17d6efff4d | ||
|
|
2f82c2407f | ||
|
|
66211a7ab2 | ||
|
|
67ad1f9564 | ||
|
|
ab570a9f05 | ||
|
|
4fdbb13010 | ||
|
|
2ac9f3806d | ||
|
|
b0ee5b5f35 | ||
|
|
546a44f216 | ||
|
|
67d3f1f574 | ||
|
|
7673d5934b | ||
|
|
d38f2e2f0e | ||
|
|
8ddec5f792 | ||
|
|
e97365de03 | ||
|
|
7a27b40068 | ||
|
|
1438f2f36a | ||
|
|
d51cd46d1d | ||
|
|
479c1553b8 | ||
|
|
def70b77aa | ||
|
|
4ff7ba5225 | ||
|
|
a41bf555cf | ||
|
|
b199b2fb98 | ||
|
|
e02ffbfd94 | ||
|
|
3b4aa37413 | ||
|
|
38ebf18e1b | ||
|
|
e5c4755b4b | ||
|
|
f787331d19 | ||
|
|
688c7117e8 | ||
|
|
914ac6999d | ||
|
|
20065858fc | ||
|
|
4e7d460716 | ||
|
|
677553f5f0 | ||
|
|
8cb0a95695 | ||
|
|
16cd7f0bf6 | ||
|
|
c2f087a93d | ||
|
|
b210a784e1 | ||
|
|
da7f8fc7f9 | ||
|
|
b22e8e96de | ||
|
|
ba3b80e834 | ||
|
|
b28e45af25 | ||
|
|
da2768d56b | ||
|
|
cb219ae64e | ||
|
|
f63a14aa18 | ||
|
|
ca824c07eb | ||
|
|
d58a3db44c | ||
|
|
f7ab752bcf | ||
|
|
43df69cb9b | ||
|
|
de84664427 | ||
|
|
ecb8b40184 | ||
|
|
44e6cc0384 | ||
|
|
f40f49ba8b | ||
|
|
8a9d20921d | ||
|
|
64ba44e4d2 | ||
|
|
83b7b8b268 | ||
|
|
0e5e4a8f03 | ||
|
|
10cbb16645 | ||
|
|
c8de19c7b4 | ||
|
|
1eaa6f5a47 | ||
|
|
5e1875d355 | ||
|
|
4b712c95c6 | ||
|
|
5441ac8470 | ||
|
|
b02cf140c0 | ||
|
|
11c26c8014 | ||
|
|
dd9db1fe78 | ||
|
|
8e49de26c3 | ||
|
|
fe422743bb | ||
|
|
63a5ad275b | ||
|
|
8e94045cc0 | ||
|
|
30cddd45f5 | ||
|
|
f2f96e1015 | ||
|
|
aca7a36989 | ||
|
|
f9b83e267e | ||
|
|
ea53b519b9 | ||
|
|
094682be87 | ||
|
|
187076c10a | ||
|
|
8df6e4dddd | ||
|
|
04a315bc52 | ||
|
|
1266e99d77 | ||
|
|
9fa1b1accc | ||
|
|
310328febc | ||
|
|
6cd01a0ca1 | ||
|
|
74be4c92d0 | ||
|
|
68114e5ac5 | ||
|
|
6b8235c002 | ||
|
|
8a97369103 | ||
|
|
748bb1b7b2 | ||
|
|
29c63f1f2b | ||
|
|
e36df63a0b | ||
|
|
a3e44763df | ||
|
|
f27a3b7474 | ||
|
|
285632e8ff | ||
|
|
66344a0aaf | ||
|
|
c9e5921173 | ||
|
|
f40162232f | ||
|
|
ccbe94e5cc | ||
|
|
5f3cca5fd3 | ||
|
|
682490ae61 | ||
|
|
6fcbf51cd5 | ||
|
|
0caf78841d | ||
|
|
c4b8ca79f2 | ||
|
|
6327b9fa17 | ||
|
|
61e84764a0 | ||
|
|
57dc28ed73 | ||
|
|
5afcd10313 | ||
|
|
3c9c97560f | ||
|
|
8c6cbcf860 | ||
|
|
b4e24b4b78 | ||
|
|
8d43463215 | ||
|
|
8ce4d0621e | ||
|
|
efc0479d25 | ||
|
|
9ad0be3f9f | ||
|
|
0b5df380d1 | ||
|
|
a5b205cb85 | ||
|
|
86273c129e | ||
|
|
3b546f92ac | ||
|
|
c60c87fef6 | ||
|
|
78092dc067 | ||
|
|
3f39e4ac11 | ||
|
|
0a983e6f7f | ||
|
|
89449e9c02 | ||
|
|
6310fc7fd1 | ||
|
|
092892289c | ||
|
|
e1c4b6eca1 | ||
|
|
2e21e3aa91 | ||
|
|
43cabc4889 | ||
|
|
2d8374fc5c | ||
|
|
53e9227f4a | ||
|
|
685e9b1da5 | ||
|
|
046b4bde90 | ||
|
|
9698dd4dcc | ||
|
|
d00f60f40e | ||
|
|
ab407b22bd | ||
|
|
cb4d00828c | ||
|
|
72598b88f6 | ||
|
|
f0043ca20b | ||
|
|
03319626be | ||
|
|
03402e7a55 | ||
|
|
7c46655843 | ||
|
|
d6e9fa7c88 | ||
|
|
99d55be497 | ||
|
|
29beabb114 | ||
|
|
0c76d2dbdf | ||
|
|
142daeb924 | ||
|
|
3a8c71d363 | ||
|
|
100edf8b41 | ||
|
|
2e6474cf8f | ||
|
|
4334b34637 | ||
|
|
0026971da2 | ||
|
|
2ac5a353aa | ||
|
|
a3cfadb84c | ||
|
|
ca7b3d81e5 | ||
|
|
64f0f4bd23 | ||
|
|
261a568cc6 | ||
|
|
2cbb4f8684 | ||
|
|
2accaf286b | ||
|
|
99796d4f55 | ||
|
|
c17b1add9e | ||
|
|
047556ecb9 | ||
|
|
85d067a753 | ||
|
|
50718c424f | ||
|
|
6b4da4978d | ||
|
|
14d3acc73c | ||
|
|
932c644676 | ||
|
|
11297e03a9 | ||
|
|
695c67d2d4 | ||
|
|
3713b7c4bf | ||
|
|
3e6c802608 | ||
|
|
2d55437bfa | ||
|
|
3dc4a50949 | ||
|
|
b27c9c589a | ||
|
|
2beeb3b7e9 | ||
|
|
ea4860d239 | ||
|
|
cbe2416eb0 | ||
|
|
885c8c03fd | ||
|
|
b5928c3be9 | ||
|
|
ffda8aaddf | ||
|
|
8b7080cc4d | ||
|
|
15b2a62217 | ||
|
|
897f0099b2 | ||
|
|
ed29320b90 | ||
|
|
0e9f15a7c3 | ||
|
|
4b50c00028 | ||
|
|
bb924c86fa | ||
|
|
c12cd62ce7 | ||
|
|
d2c0afccab | ||
|
|
8de9f4b85c | ||
|
|
8ea6c6360e | ||
|
|
54ee683838 | ||
|
|
249e6ccb0d | ||
|
|
b531c82567 | ||
|
|
b716e390e1 | ||
|
|
194796432b | ||
|
|
6146ac8710 | ||
|
|
d1f967b851 | ||
|
|
03e9cbe7f3 | ||
|
|
2cfacf60c0 | ||
|
|
d775b6d736 | ||
|
|
4c06854897 | ||
|
|
56a7c49cbc | ||
|
|
83db9f078b | ||
|
|
58888f57d0 | ||
|
|
2b981f1b1a | ||
|
|
1c0d885603 | ||
|
|
5b9ec5da26 | ||
|
|
784967800f | ||
|
|
45590f2cc1 | ||
|
|
f57c1f3c7e | ||
|
|
1a0ffd499e | ||
|
|
f81b643f29 | ||
|
|
0675da97a2 | ||
|
|
b6d1c7dcd1 | ||
|
|
e03ac50239 | ||
|
|
60ce673ef9 | ||
|
|
66a8e34547 | ||
|
|
e90b59e37f | ||
|
|
cd38a2fff1 | ||
|
|
28928d6271 | ||
|
|
950c66bd44 | ||
|
|
8790ca11ed | ||
|
|
7ad6348542 | ||
|
|
c395c301f3 | ||
|
|
24a0729033 | ||
|
|
9ab74dd5e4 | ||
|
|
f9111d1c61 | ||
|
|
39483316c6 | ||
|
|
8fedd88408 | ||
|
|
a84f140114 | ||
|
|
d1908b5769 | ||
|
|
ec07038446 | ||
|
|
6d2b445867 | ||
|
|
7ed1afa775 | ||
|
|
3da7e63da2 | ||
|
|
15a0da37f2 | ||
|
|
626b741729 | ||
|
|
91daf6edf5 | ||
|
|
af3bf577e4 | ||
|
|
482770af77 | ||
|
|
0d23199136 | ||
|
|
fef6d1a078 | ||
|
|
260f988d2a | ||
|
|
5d8ddc9b57 | ||
|
|
2653a2293e | ||
|
|
3410a98f93 | ||
|
|
fc0b8bdba3 | ||
|
|
281f906e0c | ||
|
|
851b802bbb | ||
|
|
4049116775 | ||
|
|
113dc28e9c | ||
|
|
0a0a5f3d7c | ||
|
|
f695894343 | ||
|
|
8a74b1022d | ||
|
|
1cabc3e582 | ||
|
|
de1c2e207e | ||
|
|
b5ba15a9aa | ||
|
|
d31345203f | ||
|
|
d82ea49e94 | ||
|
|
ca739bb5e0 | ||
|
|
400dbb8b3a | ||
|
|
643b2e07c7 | ||
|
|
7ddf054d9e | ||
|
|
2add21548a | ||
|
|
412537a7ae | ||
|
|
5de8ffc4d9 | ||
|
|
939114466f | ||
|
|
7fd5cf1f10 | ||
|
|
128ef5be05 | ||
|
|
c4f5a251a3 | ||
|
|
797b226345 | ||
|
|
968e90a564 | ||
|
|
1c2ca087f5 | ||
|
|
0406714095 | ||
|
|
22e78638af | ||
|
|
697e08efd1 | ||
|
|
488340857b | ||
|
|
ff3425981c | ||
|
|
97b02eb090 | ||
|
|
d289ad7e49 | ||
|
|
0a19e803b8 | ||
|
|
ed9763a257 | ||
|
|
3b516ea7c7 | ||
|
|
7a7a25d67e | ||
|
|
817d54e591 | ||
|
|
70c9f64e88 | ||
|
|
6cbd251035 | ||
|
|
f8299fa556 | ||
|
|
4e0451bb1c | ||
|
|
e9d2c509a8 | ||
|
|
10af92ec77 | ||
|
|
0cb501c39c | ||
|
|
82d4c5ab29 | ||
|
|
25fb224613 | ||
|
|
2897cb4b4a | ||
|
|
0603393a52 | ||
|
|
01656051e2 | ||
|
|
3483196b3b | ||
|
|
ae1d393dd8 | ||
|
|
4b12c5c2e3 | ||
|
|
57ee959182 | ||
|
|
731c4fefad | ||
|
|
3e53743f1b | ||
|
|
1283a3cd39 | ||
|
|
4ff6373b2c | ||
|
|
f5c67b6773 | ||
|
|
b413796475 | ||
|
|
6f13f2b654 | ||
|
|
bdf50943a4 | ||
|
|
4ed9654f11 | ||
|
|
02953e520d | ||
|
|
e6455767ca | ||
|
|
afb898ac87 | ||
|
|
0f8eeba740 | ||
|
|
0cbca5294d | ||
|
|
14a1ef6f11 | ||
|
|
f62f2f07e2 | ||
|
|
e6eaadf4fc | ||
|
|
0231b83505 | ||
|
|
e0233c27ae | ||
|
|
71bbcbfd96 | ||
|
|
46a76d5412 | ||
|
|
f9c80c709c | ||
|
|
744a9d03a5 | ||
|
|
96b822ef82 | ||
|
|
b1c6233e13 | ||
|
|
3321e948de | ||
|
|
d11724c616 | ||
|
|
6f709ccd58 | ||
|
|
43a4f582d2 | ||
|
|
5328c372bb | ||
|
|
346136d1a4 | ||
|
|
f1d3681cd8 | ||
|
|
ba74104b78 | ||
|
|
b79011f1b0 | ||
|
|
834f565a7d | ||
|
|
c0853c4560 | ||
|
|
a2a308f5ef | ||
|
|
13ab7ef911 | ||
|
|
a8f8ad14ca | ||
|
|
c4a50f97d6 | ||
|
|
4b70db56cf | ||
|
|
ebd4bdd5f3 | ||
|
|
1a5ab92c05 | ||
|
|
ea97da2931 | ||
|
|
55fe83265f | ||
|
|
e64c1fced2 | ||
|
|
9d1a799c23 | ||
|
|
24578ce569 | ||
|
|
a2bb1a48a2 | ||
|
|
cf8f662060 | ||
|
|
fc1b9ebaa6 | ||
|
|
03d0c8c05c | ||
|
|
60971d7cce | ||
|
|
5e0bbf3ecb | ||
|
|
cc8309d046 | ||
|
|
54be194305 | ||
|
|
ec42e12457 | ||
|
|
1392f73ce5 | ||
|
|
a62faf3a3f | ||
|
|
cf1b8d99d5 | ||
|
|
2454d2d781 | ||
|
|
cf42ba4ea4 | ||
|
|
83c8ea7e06 | ||
|
|
1103ea7e4d | ||
|
|
e59745c22c | ||
|
|
cc1f856bc2 | ||
|
|
1a62909eb2 | ||
|
|
859aee9a27 | ||
|
|
480a80f052 | ||
|
|
aed8c978d2 | ||
|
|
01339fd31a | ||
|
|
154ce27239 | ||
|
|
499fca99b6 | ||
|
|
d20291a17f | ||
|
|
6bf04403ae | ||
|
|
dea4abb7a1 | ||
|
|
4aac30c32c | ||
|
|
d1edb8a9c9 | ||
|
|
887c126b02 | ||
|
|
d3ebd0550d | ||
|
|
7975babde3 | ||
|
|
44abe48661 | ||
|
|
919e87315e | ||
|
|
54e6c5a59c | ||
|
|
fd48eb6c96 | ||
|
|
a9223e5336 | ||
|
|
8898ca54a8 | ||
|
|
1150aa7133 | ||
|
|
798e0688d4 | ||
|
|
b718897ff8 | ||
|
|
7d2e04db2f | ||
|
|
8c4577ea93 | ||
|
|
eefeace2eb | ||
|
|
2c7c5e0b3a | ||
|
|
ffba14d2c1 | ||
|
|
043a2b9613 | ||
|
|
fbef38149f | ||
|
|
7b479f7803 | ||
|
|
13526b0916 | ||
|
|
26def8359d | ||
|
|
d9a54469ff | ||
|
|
cd4a7460b0 | ||
|
|
42ab825f46 | ||
|
|
2792d57c28 | ||
|
|
3179b6feea | ||
|
|
c532a818b7 | ||
|
|
302b7fa13e | ||
|
|
71b6ba02a1 | ||
|
|
8a3271af4e | ||
|
|
04bc27438d | ||
|
|
19d0baa26f | ||
|
|
964f7899e9 | ||
|
|
ee6837b953 | ||
|
|
ffa4572ee4 | ||
|
|
22e3e6c50c | ||
|
|
26a3769220 | ||
|
|
c75e8ad6e1 | ||
|
|
47909b13e1 | ||
|
|
7ea7f09e16 | ||
|
|
0e0df8ad95 | ||
|
|
14a84c8956 | ||
|
|
dcdaa89ac1 | ||
|
|
a9947a2e74 | ||
|
|
d9c20ab381 | ||
|
|
c83c7096e7 | ||
|
|
1f04ecc6dc | ||
|
|
4a50e8bff0 | ||
|
|
59c71cf5c0 | ||
|
|
74ae6958d1 | ||
|
|
3392698c79 | ||
|
|
bf6dffe74b | ||
|
|
1fb2824cf3 | ||
|
|
6376a6e97d | ||
|
|
7d19747fa4 | ||
|
|
cb2d09ab07 | ||
|
|
032a8d574c | ||
|
|
82a5ff966e | ||
|
|
b8d4404aac | ||
|
|
897173b721 | ||
|
|
9b6e06fa61 | ||
|
|
592925ffa3 | ||
|
|
dd1df950cc | ||
|
|
b65dfc663e | ||
|
|
f739563699 | ||
|
|
7e1b537b0e | ||
|
|
ece8637275 | ||
|
|
57624cbe73 | ||
|
|
08c5f0471d | ||
|
|
bc1bca7922 | ||
|
|
3a62894312 | ||
|
|
7de1fcf1f4 | ||
|
|
3388c3dfcc | ||
|
|
d66b159793 | ||
|
|
c33435b692 | ||
|
|
a1daf8e884 | ||
|
|
b8d146c62e | ||
|
|
6194b1e96d | ||
|
|
f803a76bfa | ||
|
|
5922b91824 | ||
|
|
d6a4d61c9f | ||
|
|
e219c99dd5 | ||
|
|
e5f4e48471 | ||
|
|
d03196aa96 | ||
|
|
f157c67756 | ||
|
|
c4832161d4 | ||
|
|
4a5ad589eb | ||
|
|
f3e30608fa | ||
|
|
1f01e77072 | ||
|
|
7d846cfe2e | ||
|
|
5462e24c83 | ||
|
|
0f0f4a15a7 | ||
|
|
18594919e2 | ||
|
|
e3e79062fd | ||
|
|
e460b57518 | ||
|
|
0089286b66 | ||
|
|
3f41d0e1f5 | ||
|
|
8913daa21b | ||
|
|
d65a39f722 | ||
|
|
e3dc4b65bf | ||
|
|
be1a18bfe4 | ||
|
|
e1b6bd0467 | ||
|
|
bb6800ec90 | ||
|
|
7857c14c1d | ||
|
|
478cc28731 | ||
|
|
a645f0a3a4 | ||
|
|
7fe855edfe | ||
|
|
102ec0731a | ||
|
|
32b47b9966 | ||
|
|
55d382b0c1 | ||
|
|
fcf758be3c | ||
|
|
ef34412277 | ||
|
|
ca22d8f9ba | ||
|
|
e7822af805 | ||
|
|
ea949f0b81 | ||
|
|
a586640e8d | ||
|
|
28f0283cab | ||
|
|
a832a84f66 | ||
|
|
4bd43154c7 | ||
|
|
24be2e5803 | ||
|
|
52f41017d1 | ||
|
|
0cf714bf59 | ||
|
|
8b7f93cc69 | ||
|
|
68a34f6224 | ||
|
|
9601355097 | ||
|
|
0f8b224f15 | ||
|
|
051b7d2ab5 | ||
|
|
78cbf94668 | ||
|
|
78a1c10421 | ||
|
|
aa2abf7c31 | ||
|
|
56893eacb1 | ||
|
|
58e973c0dd | ||
|
|
2021c85989 | ||
|
|
c26032f088 | ||
|
|
7eca5063d2 | ||
|
|
3474059d00 | ||
|
|
7dfcd38a05 | ||
|
|
083817692a | ||
|
|
190062635b | ||
|
|
bf400be243 | ||
|
|
90997adad2 | ||
|
|
a81d2e3c42 | ||
|
|
f0eb586e38 | ||
|
|
764dcf8464 | ||
|
|
fb73fb1af3 | ||
|
|
40ded6b424 | ||
|
|
35ad8de19d | ||
|
|
faba464a18 | ||
|
|
d99b04009b | ||
|
|
a135c38280 | ||
|
|
01e887ec65 | ||
|
|
bcffb91cd0 | ||
|
|
7feadb6207 | ||
|
|
9ee13cfbbf | ||
|
|
0a7fedba00 | ||
|
|
8ac1cc5837 | ||
|
|
6d84f734d2 | ||
|
|
98a880c621 | ||
|
|
8053b81db7 | ||
|
|
ef90c67ac2 | ||
|
|
21ababdb09 | ||
|
|
acb3e45414 | ||
|
|
2562fc37f1 | ||
|
|
bc4945f804 | ||
|
|
86db4b273e | ||
|
|
18f10740d8 | ||
|
|
1c92735129 | ||
|
|
f38d65c93f | ||
|
|
846f91997b | ||
|
|
220da11388 | ||
|
|
a9eba43418 | ||
|
|
c1888a0a1d | ||
|
|
f1539bc692 | ||
|
|
6cc887d590 | ||
|
|
10e6ae8678 | ||
|
|
c5279d4896 | ||
|
|
b7e979dae4 | ||
|
|
3896abe972 | ||
|
|
6f1d49fcca | ||
|
|
3bdcf2f628 | ||
|
|
4e2631c555 | ||
|
|
9167facd1a | ||
|
|
63b05020b3 | ||
|
|
2dec75e7d9 | ||
|
|
d9647017ae | ||
|
|
eea2b783f7 | ||
|
|
edd2070967 | ||
|
|
82725c0fcc | ||
|
|
308a307408 | ||
|
|
2773466f0f | ||
|
|
709ffd7631 | ||
|
|
8eda081d64 | ||
|
|
6b8c8d9c33 | ||
|
|
bc5df75b30 | ||
|
|
400689bab3 | ||
|
|
b6bec9e9c1 | ||
|
|
9f119eff89 | ||
|
|
75b9ff94ac | ||
|
|
edaa2d1396 | ||
|
|
d4d8e9560b | ||
|
|
d449e86d0e | ||
|
|
27e956175e | ||
|
|
d28d401c6c | ||
|
|
4b2a22dc3b | ||
|
|
138806375a | ||
|
|
ea5b8052fd | ||
|
|
4fb129b7dd | ||
|
|
7e21a44db4 | ||
|
|
4bb4b19cae | ||
|
|
8e94b38103 | ||
|
|
bf825d6bf9 | ||
|
|
38fd38f582 | ||
|
|
275e96ffd2 | ||
|
|
3ad288adf1 | ||
|
|
1c6f2dfd82 | ||
|
|
1a8eba304d | ||
|
|
7292a20e3b | ||
|
|
e5b82e0358 | ||
|
|
adad842d7e | ||
|
|
b98b2e9037 | ||
|
|
1d9d6eef8b | ||
|
|
776f8a5158 | ||
|
|
822d444964 | ||
|
|
fdce2eb201 | ||
|
|
cb707129f5 | ||
|
|
2c443a0145 | ||
|
|
b6216c4c0f | ||
|
|
a3e067481e | ||
|
|
6be2025a66 | ||
|
|
3572866bd3 | ||
|
|
50cca39a04 | ||
|
|
52142ed141 | ||
|
|
bbf8443b9c | ||
|
|
88d28816fd | ||
|
|
bbdf35a17f | ||
|
|
f9ef655429 | ||
|
|
40d3c596e1 | ||
|
|
4debada2db | ||
|
|
c96a57c310 | ||
|
|
0d2c2cb99f | ||
|
|
9e5a44231d | ||
|
|
517b5a7f6f | ||
|
|
a3c10ebe6f |
734 changed files with 27514 additions and 306911 deletions
9
.babelrc.json
Normal file
9
.babelrc.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"plugins": [
|
||||
"module:babel-plugin-macros",
|
||||
"@babel/plugin-transform-optional-chaining"
|
||||
]
|
||||
}
|
||||
4
.browserslistrc
Normal file
4
.browserslistrc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
last 2 Chrome versions
|
||||
last 2 Safari versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
45
.cloudcmd.menu.js
Normal file
45
.cloudcmd.menu.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
'F2 - Rename file': async ({DOM}) => {
|
||||
await DOM.renameCurrent();
|
||||
},
|
||||
'L - Lint': async ({CloudCmd}) => {
|
||||
const {TerminalRun} = CloudCmd;
|
||||
await run(TerminalRun, 'npm run lint');
|
||||
},
|
||||
'F - Fix Lint': async ({CloudCmd}) => {
|
||||
const {TerminalRun} = CloudCmd;
|
||||
await run(TerminalRun, 'npm run fix:lint');
|
||||
},
|
||||
'T - Test': async ({CloudCmd}) => {
|
||||
const {TerminalRun} = CloudCmd;
|
||||
|
||||
await run(TerminalRun, 'npm run test');
|
||||
},
|
||||
'C - Coverage': async ({CloudCmd}) => {
|
||||
const {TerminalRun} = CloudCmd;
|
||||
|
||||
await run(TerminalRun, 'npm run coverage');
|
||||
},
|
||||
'D - Build Dev': async ({CloudCmd}) => {
|
||||
const {TerminalRun} = CloudCmd;
|
||||
|
||||
await run(TerminalRun, 'npm run build:client:dev');
|
||||
CloudCmd.refresh();
|
||||
},
|
||||
'P - Build Prod': async ({CloudCmd}) => {
|
||||
const {TerminalRun} = CloudCmd;
|
||||
|
||||
await run(TerminalRun, 'npm run build:client');
|
||||
CloudCmd.refresh();
|
||||
},
|
||||
};
|
||||
|
||||
async function run(TerminalRun, command) {
|
||||
await TerminalRun.show({
|
||||
command,
|
||||
closeMessage: 'Press any key to close Terminal',
|
||||
autoClose: false,
|
||||
});
|
||||
}
|
||||
23
.dockerignore
Normal file
23
.dockerignore
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
.*
|
||||
*.spec.js
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
coverage
|
||||
test
|
||||
manifest.yml
|
||||
app.json
|
||||
bower.json
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
now.json
|
||||
|
||||
docker
|
||||
|
||||
webpack.config.js
|
||||
cssnano.config.js
|
||||
|
||||
bin/release.js
|
||||
|
||||
client
|
||||
server_
|
||||
|
||||
|
|
@ -9,6 +9,9 @@ root = true
|
|||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
trim_trailing_whitespace = false
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.hbs]
|
||||
insert_final_newline = false
|
||||
|
|
|
|||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
github: coderaiser
|
||||
open_collective: cloudcmd
|
||||
ko_fi: coderaiser
|
||||
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: needs clarification
|
||||
assignees: coderaiser
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
|
||||
- **Version** (`cloudcmd -v`):
|
||||
- **Node Version** `node -v`:
|
||||
- **OS** (`uname -a` on Linux):
|
||||
- **Browser name/version**:
|
||||
- **Used Command Line Parameters**:
|
||||
- **Changed Config**:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
- [ ] 🎁 **I'm ready to donate on https://opencollective.com/cloudcmd**
|
||||
- [ ] 🎁 **I'm ready to donate on https://ko-fi.com/coderaiser**
|
||||
- [ ] 💪 **I'm willing to work on this issue**
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Stack Overflow
|
||||
url: https://stackoverflow.com/search?q=cloudcmd
|
||||
about: Please ask and answer questions here.
|
||||
21
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
24
.github/ISSUE_TEMPLATE/issue_template.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/issue_template.md
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
***
|
||||
|
||||
name: Tracking issue
|
||||
about: Create an issue with bug report or feature request.
|
||||
title: ""
|
||||
labels: needs triage
|
||||
assignees: coderaiser
|
||||
|
||||
***
|
||||
|
||||
- **Version** (`cloudcmd -v`):
|
||||
- **Node Version** `node -v`:
|
||||
- **OS** (`uname -a` on Linux):
|
||||
- **Browser name/version**:
|
||||
- **Used Command Line Parameters**:
|
||||
- **Changed Config**:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
- [ ] 🎁 **I'm ready to donate on https://opencollective.com/cloudcmd**
|
||||
- [ ] 🎁 **I'm ready to donate on https://ko-fi.com/coderaiser**
|
||||
- [ ] 💪 **I'm willing to work on this issue**
|
||||
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<!--
|
||||
Thank you for making pull request. Please fill in the template below. If unsure
|
||||
about something, just do as best as you're able.
|
||||
-->
|
||||
|
||||
- [ ] commit message named according to [Contributing Guide](https://github.com/coderaiser/cloudcmd/blob/master/CONTRIBUTING.md "Contributting Guide")
|
||||
- [ ] `npm run fix:lint` is OK
|
||||
- [ ] `npm test` is OK
|
||||
71
.github/workflows/docker.yml
vendored
Normal file
71
.github/workflows/docker.yml
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
name: Docker CI
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
- name: Install Redrun
|
||||
run: bun i redrun -g --no-save
|
||||
- name: NPM Install
|
||||
run: bun i --no-save
|
||||
- name: Lint
|
||||
run: redrun lint
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
redrun build
|
||||
echo "::set-output name=version::$(grep '"version":' package.json -m1 | cut -d\" -f4)"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push base-image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
coderaiser/cloudcmd:latest
|
||||
coderaiser/cloudcmd:${{ steps.build.outputs.version }}
|
||||
ghcr.io/${{ github.repository }}:latest
|
||||
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}
|
||||
- name: Build and push alpine-image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile.alpine
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
coderaiser/cloudcmd:latest-alpine
|
||||
coderaiser/cloudcmd:${{ steps.build.outputs.version }}-alpine
|
||||
ghcr.io/${{ github.repository }}:latest-alpine
|
||||
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-alpine
|
||||
58
.github/workflows/nodejs.yml
vendored
Normal file
58
.github/workflows/nodejs.yml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
name: Node CI
|
||||
on:
|
||||
- push
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NAME: cloudcmd
|
||||
strategy:
|
||||
matrix:
|
||||
node-version:
|
||||
- 22.x
|
||||
- 24.x
|
||||
- 25.x
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Redrun
|
||||
run: bun i redrun -g --no-save
|
||||
- name: Install
|
||||
run: bun i --no-save
|
||||
- name: Lint
|
||||
run: redrun fix:lint
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Typos Install
|
||||
run: cargo install typos-cli || echo 'already installed'
|
||||
- name: Typos
|
||||
run: typos --write-changes
|
||||
- name: Commit fixes
|
||||
uses: EndBug/add-and-commit@v9
|
||||
continue-on-error: true
|
||||
with:
|
||||
message: "chore: ${{ env.NAME }}: actions: lint ☘️"
|
||||
- name: Build
|
||||
run: redrun build
|
||||
- name: Test
|
||||
run: redrun test
|
||||
- name: Coverage
|
||||
run: redrun coverage coverage:report
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
|
|
@ -1,3 +1,19 @@
|
|||
json/changes.json
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
yarn-error.log
|
||||
node_modules
|
||||
npm-debug.log
|
||||
npm-debug.log*
|
||||
coverage
|
||||
|
||||
modules/execon
|
||||
modules/emitify
|
||||
|
||||
.nyc_output
|
||||
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
dist
|
||||
dist-dev
|
||||
|
||||
.idea
|
||||
|
|
|
|||
18
.jshintrc
18
.jshintrc
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"unused" : true,
|
||||
"bitwise" : false,
|
||||
"browser" : true,
|
||||
"devel" : true,
|
||||
"eqeqeq" : true,
|
||||
"jquery" : false,
|
||||
"newcap" : false,
|
||||
"noarg" : true,
|
||||
"node" : true,
|
||||
"noempty" : true,
|
||||
"nonew" : true,
|
||||
"strict" : true,
|
||||
"undef" : true,
|
||||
"evil" : true,
|
||||
"expr" : true,
|
||||
"quotmark": "single"
|
||||
}
|
||||
63
.madrun.mjs
Normal file
63
.madrun.mjs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import process from 'node:process';
|
||||
import {run, cutEnv} from 'madrun';
|
||||
|
||||
const testEnv = {
|
||||
SUPERTAPE_TIMEOUT: 7000,
|
||||
};
|
||||
|
||||
const is17 = /^v1[789]/.test(process.version);
|
||||
const is20 = process.version.startsWith('v2');
|
||||
|
||||
// fix for ERR_OSSL_EVP_UNSUPPORTED on node v17
|
||||
// flag '--openssl-legacy-provider' not supported
|
||||
// on earlier version of node.js
|
||||
//
|
||||
// https://stackoverflow.com/a/69746937/4536327
|
||||
const buildEnv = (is17 || is20) && {
|
||||
NODE_OPTIONS: '--openssl-legacy-provider',
|
||||
NODE_ENV: 'production',
|
||||
};
|
||||
|
||||
export default {
|
||||
'start': () => 'node bin/cloudcmd.mjs',
|
||||
'start:dev': async () => await run('start', null, {
|
||||
NODE_ENV: 'development',
|
||||
}),
|
||||
'build:start': () => run(['build:client', 'start']),
|
||||
'build:start:dev': () => run(['build:client:dev', 'start:dev']),
|
||||
'lint:all': () => run('lint:progress'),
|
||||
'lint': () => 'putout .',
|
||||
'lint:progress': () => run('lint', '-f progress'),
|
||||
'watch:lint': () => 'nodemon -w client -w server -w test -w common -w .webpack -x "putout -s"',
|
||||
'fresh:lint': () => run('lint', '--fresh'),
|
||||
'lint:fresh': () => run('lint', '--fresh'),
|
||||
'fix:lint': () => run('lint', '--fix'),
|
||||
'lint:stream': () => run('lint', '-f stream'),
|
||||
'test': () => [testEnv, `tape 'test/**/*.{js,mjs}' '{client,static,common,server}/**/*.spec.{js,mjs}' -f fail`],
|
||||
'test:client': () => `tape 'test/client/**/*.js'`,
|
||||
'test:server': () => `tape 'test/**/*.js' 'server/**/*.spec.js' 'common/**/*.spec.js'`,
|
||||
'wisdom': () => run(['lint:all', 'build', 'test']),
|
||||
'wisdom:type': () => 'bin/release.mjs',
|
||||
'coverage': async () => [testEnv, `c8 ${await cutEnv('test')}`],
|
||||
'coverage:report': () => 'c8 report --reporter=lcov',
|
||||
'report': () => 'c8 report --reporter=lcov',
|
||||
'6to5': () => [buildEnv, 'webpack --progress'],
|
||||
'6to5:client': () => run('6to5', '--mode production'),
|
||||
'6to5:client:dev': async () => await run('6to5', '--mode development', {
|
||||
NODE_ENV: 'development',
|
||||
}),
|
||||
'pre6to5:client': () => 'rimraf dist',
|
||||
'pre6to5:client:dev': () => 'rimraf dist-dev',
|
||||
'watch:client': () => run('6to5:client', '--watch'),
|
||||
'watch:client:dev': () => run('6to5:client:dev', '--watch'),
|
||||
'watch:server': () => 'nodemon bin/cloudcmd.js',
|
||||
'watch:test': async () => [testEnv, `nodemon -w client -w server -w test -w common -x ${await cutEnv('test')}`],
|
||||
'watch:test:client': async () => `nodemon -w client -w test/client -x ${await run('test:client')}`,
|
||||
'watch:test:server': async () => `nodemon -w client -w test/client -x ${await run('test:server')}`,
|
||||
'watch:coverage': async () => [testEnv, `nodemon -w server -w test -w common -x ${await cutEnv('coverage')}`],
|
||||
'build': async () => run('6to5:*'),
|
||||
'build:dev': async () => run('build:client:dev'),
|
||||
'build:client': () => run('6to5:client'),
|
||||
'build:client:dev': () => run('6to5:client:dev'),
|
||||
'heroku-postbuild': () => run('6to5:client'),
|
||||
};
|
||||
34
.npmignore
Normal file
34
.npmignore
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
.*
|
||||
*.spec.*
|
||||
*.config.*
|
||||
*.fixture.js*
|
||||
manifest.yml
|
||||
docker
|
||||
docker-compose.yml
|
||||
test
|
||||
fixture
|
||||
fixture-*
|
||||
coverage
|
||||
css
|
||||
html
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
now.json
|
||||
|
||||
app.json
|
||||
bower.json
|
||||
manifest.yml
|
||||
deno.json
|
||||
|
||||
bin/release.mjs
|
||||
|
||||
client
|
||||
img/logo/cloudcmd-hq.png
|
||||
|
||||
webpack.config.js
|
||||
|
||||
*.ai
|
||||
*.cdr
|
||||
*.eps
|
||||
|
||||
*.config.*
|
||||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
v20.15.1
|
||||
15
.nycrc.json
Normal file
15
.nycrc.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"check-coverage": false,
|
||||
"all": false,
|
||||
"exclude": [
|
||||
"**/*.spec.js",
|
||||
"**/fixture",
|
||||
"**/*.*.js",
|
||||
"**/*.config.*",
|
||||
"**/test/**"
|
||||
],
|
||||
"branches": 100,
|
||||
"lines": 100,
|
||||
"functions": 100,
|
||||
"statements": 100
|
||||
}
|
||||
57
.putout.json
Normal file
57
.putout.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"plugins": ["cloudcmd"],
|
||||
"ignore": [
|
||||
"html",
|
||||
"fixture*",
|
||||
"app.json",
|
||||
"fontello.json",
|
||||
"*.md"
|
||||
],
|
||||
"rules": {
|
||||
"package-json/add-type": "off"
|
||||
},
|
||||
"match": {
|
||||
"base64": {
|
||||
"types/convert-typeof-to-is-type": "off"
|
||||
},
|
||||
"*.md": {
|
||||
"nodejs/convert-commonjs-to-esm": "on"
|
||||
},
|
||||
".webpack": {
|
||||
"webpack": "on"
|
||||
},
|
||||
"server": {
|
||||
"nodejs/remove-process-exit": "on"
|
||||
},
|
||||
"server/{server,exit}.js": {
|
||||
"nodejs/remove-process-exit": "off"
|
||||
},
|
||||
"server/{server,exit,terminal,distribute/log}.{js,mjs}": {
|
||||
"remove-console": "off"
|
||||
},
|
||||
"client/{client,cloudcmd,load-module}.{js,mjs}": {
|
||||
"remove-console": "off"
|
||||
},
|
||||
"client": {
|
||||
"nodejs": "off"
|
||||
},
|
||||
"client/sw": {
|
||||
"remove-console": "off"
|
||||
},
|
||||
"test/common/cloudfunc.js": {
|
||||
"remove-console": "off"
|
||||
},
|
||||
"storage.js": {
|
||||
"promises/remove-useless-async": "off"
|
||||
},
|
||||
"docker.yml": {
|
||||
"github/set-node-versions": "off"
|
||||
},
|
||||
"vim.js": {
|
||||
"merge-duplicate-functions": "off"
|
||||
},
|
||||
"common": {
|
||||
"nodejs/declare": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
20
.travis.yml
20
.travis.yml
|
|
@ -1,20 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 0.10
|
||||
- 0.11
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- node_js: '0.11'
|
||||
|
||||
before_install:
|
||||
- npm install gulp -g
|
||||
|
||||
script:
|
||||
- gulp default
|
||||
- node bin/cloudcmd --test
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: change
|
||||
2
.typos.toml
Normal file
2
.typos.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[files]
|
||||
extend-exclude= ["ChangeLog", "*.js"]
|
||||
34
.webpack/css.mjs
Normal file
34
.webpack/css.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import {env} from 'node:process';
|
||||
import OptimizeCssAssetsPlugin from 'optimize-css-assets-webpack-plugin';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
|
||||
const isDev = env.NODE_ENV === 'development';
|
||||
|
||||
const clean = (a) => a.filter(Boolean);
|
||||
|
||||
const plugins = clean([
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
}),
|
||||
!isDev && new OptimizeCssAssetsPlugin(),
|
||||
]);
|
||||
|
||||
const rules = [{
|
||||
test: /\.css$/i,
|
||||
use: [MiniCssExtractPlugin.loader, {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
url: true,
|
||||
},
|
||||
}],
|
||||
}, {
|
||||
test: /\.(png|gif|svg|woff|woff2|eot|ttf)$/,
|
||||
type: 'asset/inline',
|
||||
}];
|
||||
|
||||
export default {
|
||||
plugins,
|
||||
module: {
|
||||
rules,
|
||||
},
|
||||
};
|
||||
36
.webpack/html.mjs
Normal file
36
.webpack/html.mjs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import {env} from 'node:process';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
|
||||
const isDev = env.NODE_ENV === 'development';
|
||||
|
||||
export const plugins = [
|
||||
new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
template: 'html/index.html',
|
||||
minify: !isDev && getMinifyHtmlOptions(),
|
||||
}),
|
||||
];
|
||||
|
||||
function getMinifyHtmlOptions() {
|
||||
return {
|
||||
removeComments: true,
|
||||
removeCommentsFromCDATA: true,
|
||||
removeCDATASectionsFromCDATA: true,
|
||||
collapseWhitespace: true,
|
||||
collapseBooleanAttributes: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
/* оставляем, поскольку у нас
|
||||
* в элемент fm генерируеться
|
||||
* таблица файлов
|
||||
*/
|
||||
removeEmptyElements: false,
|
||||
removeOptionalTags: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
|
||||
minifyJS: true,
|
||||
};
|
||||
}
|
||||
160
.webpack/js.mjs
Normal file
160
.webpack/js.mjs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import {resolve, sep} from 'node:path';
|
||||
import {env} from 'node:process';
|
||||
import webpack from 'webpack';
|
||||
import WebpackBar from 'webpackbar';
|
||||
|
||||
const {
|
||||
EnvironmentPlugin,
|
||||
NormalModuleReplacementPlugin,
|
||||
} = webpack;
|
||||
|
||||
const modules = './modules';
|
||||
const dirModules = './client/modules';
|
||||
const dirCss = './css';
|
||||
const dirThemes = `${dirCss}/themes`;
|
||||
const dirColumns = `${dirCss}/columns`;
|
||||
const dir = './client';
|
||||
const {NODE_ENV} = env;
|
||||
const isDev = NODE_ENV === 'development';
|
||||
|
||||
const rootDir = new URL('..', import.meta.url).pathname;
|
||||
const dist = resolve(rootDir, 'dist');
|
||||
const distDev = resolve(rootDir, 'dist-dev');
|
||||
const devtool = isDev ? 'eval' : 'source-map';
|
||||
|
||||
const notEmpty = (a) => a;
|
||||
const clean = (array) => array.filter(notEmpty);
|
||||
|
||||
const noParse = (a) => a.endsWith('.spec.js');
|
||||
const options = {
|
||||
babelrc: true,
|
||||
};
|
||||
|
||||
const rules = clean([
|
||||
!isDev && {
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
isDev && {
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options,
|
||||
},
|
||||
]);
|
||||
|
||||
const plugins = [
|
||||
new NormalModuleReplacementPlugin(/^node:/, (resource) => {
|
||||
resource.request = resource.request.replace(/^node:/, '');
|
||||
}),
|
||||
new NormalModuleReplacementPlugin(/^putout$/, '@putout/bundle'),
|
||||
new EnvironmentPlugin({
|
||||
NODE_ENV,
|
||||
}),
|
||||
new WebpackBar(),
|
||||
];
|
||||
|
||||
const splitChunks = {
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
abcCommon: {
|
||||
name: 'cloudcmd.common',
|
||||
chunks: (chunk) => {
|
||||
const lazyChunks = [
|
||||
'sw',
|
||||
'nojs',
|
||||
'view',
|
||||
'edit',
|
||||
'terminal',
|
||||
'config',
|
||||
'user-menu',
|
||||
'help',
|
||||
'themes/dark',
|
||||
'themes/light',
|
||||
'columns/name-size',
|
||||
'columns/name-size-date',
|
||||
];
|
||||
|
||||
return !lazyChunks.includes(chunk.name);
|
||||
},
|
||||
minChunks: 1,
|
||||
enforce: true,
|
||||
priority: -1,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
resolve: {
|
||||
symlinks: false,
|
||||
alias: {
|
||||
'node:process': 'process',
|
||||
'node:path': 'path',
|
||||
},
|
||||
fallback: {
|
||||
path: import.meta.resolve('path-browserify'),
|
||||
process: import.meta.resolve('process/browser'),
|
||||
},
|
||||
},
|
||||
devtool,
|
||||
optimization: {
|
||||
splitChunks,
|
||||
},
|
||||
entry: {
|
||||
'themes/dark': `${dirThemes}/dark.css`,
|
||||
'themes/light': `${dirThemes}/light.css`,
|
||||
'columns/name-size': `${dirColumns}/name-size.css`,
|
||||
'columns/name-size-date': `${dirColumns}/name-size-date.css`,
|
||||
'nojs': `${dirCss}/nojs.css`,
|
||||
'help': `${dirCss}/help.css`,
|
||||
'view': `${dirCss}/view.css`,
|
||||
'config': `${dirCss}/config.css`,
|
||||
'terminal': `${dirCss}/terminal.css`,
|
||||
'user-menu': `${dirCss}/user-menu.css`,
|
||||
'sw': `${dir}/sw/sw.js`,
|
||||
'cloudcmd': `${dir}/cloudcmd.mjs`,
|
||||
[`${modules}/edit`]: `${dirModules}/edit.js`,
|
||||
[`${modules}/edit-file`]: `${dirModules}/edit-file.js`,
|
||||
[`${modules}/edit-file-vim`]: `${dirModules}/edit-file-vim.js`,
|
||||
[`${modules}/edit-names`]: `${dirModules}/edit-names.js`,
|
||||
[`${modules}/edit-names-vim`]: `${dirModules}/edit-names-vim.js`,
|
||||
[`${modules}/menu`]: `${dirModules}/menu/index.js`,
|
||||
[`${modules}/view`]: `${dirModules}/view/index.js`,
|
||||
[`${modules}/help`]: `${dirModules}/help.js`,
|
||||
[`${modules}/markdown`]: `${dirModules}/markdown.js`,
|
||||
[`${modules}/config`]: `${dirModules}/config/index.js`,
|
||||
[`${modules}/contact`]: `${dirModules}/contact.js`,
|
||||
[`${modules}/upload`]: `${dirModules}/upload.js`,
|
||||
[`${modules}/operation`]: `${dirModules}/operation/index.mjs`,
|
||||
[`${modules}/konsole`]: `${dirModules}/konsole.js`,
|
||||
[`${modules}/terminal`]: `${dirModules}/terminal.js`,
|
||||
[`${modules}/terminal-run`]: `${dirModules}/terminal-run.js`,
|
||||
[`${modules}/cloud`]: `${dirModules}/cloud.js`,
|
||||
[`${modules}/user-menu`]: `${dirModules}/user-menu/index.js`,
|
||||
[`${modules}/polyfill`]: `${dirModules}/polyfill.js`,
|
||||
[`${modules}/command-line`]: `${dirModules}/command-line.js`,
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: isDev ? distDev : dist,
|
||||
pathinfo: isDev,
|
||||
devtoolModuleFilenameTemplate,
|
||||
publicPath: '/dist/',
|
||||
},
|
||||
module: {
|
||||
rules,
|
||||
noParse,
|
||||
},
|
||||
plugins,
|
||||
performance: {
|
||||
maxEntrypointSize: 600_000,
|
||||
maxAssetSize: 600_000,
|
||||
},
|
||||
};
|
||||
|
||||
function devtoolModuleFilenameTemplate(info) {
|
||||
const resource = info.absoluteResourcePath.replace(rootDir + sep, '');
|
||||
return `file://cloudcmd/${resource}`;
|
||||
}
|
||||
72
.yaspellerrc
Normal file
72
.yaspellerrc
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"ignoreDigits": true,
|
||||
"excludeFiles": [
|
||||
".git",
|
||||
"modules",
|
||||
"lib",
|
||||
"node_modules"
|
||||
],
|
||||
"fileExtensions": [
|
||||
".md"
|
||||
],
|
||||
"dictionary":[
|
||||
"CloudCmd",
|
||||
"Dev",
|
||||
"Dropbox",
|
||||
"Deepword",
|
||||
"Dword",
|
||||
"FilePicker",
|
||||
"GDrive",
|
||||
"Github",
|
||||
"Heroku",
|
||||
"Iptables",
|
||||
"JitSu",
|
||||
"Node",
|
||||
"IO",
|
||||
"Olena",
|
||||
"TarZak",
|
||||
"Termux",
|
||||
"Zalitok",
|
||||
"WebSocket",
|
||||
"auth",
|
||||
"autostart",
|
||||
"binded",
|
||||
"cd",
|
||||
"cloudcmd",
|
||||
"coderaiser",
|
||||
"com",
|
||||
"deepword",
|
||||
"dev",
|
||||
"destructuring",
|
||||
"dropbox",
|
||||
"dword",
|
||||
"edward",
|
||||
"favicon",
|
||||
"github",
|
||||
"gz",
|
||||
"io",
|
||||
"js",
|
||||
"linux",
|
||||
"maintainers",
|
||||
"markdown",
|
||||
"microservice",
|
||||
"minification",
|
||||
"mouseup",
|
||||
"named",
|
||||
"nginx",
|
||||
"npm",
|
||||
"or io",
|
||||
"patreon",
|
||||
"rc",
|
||||
"refactor",
|
||||
"sexualized",
|
||||
"sslPort",
|
||||
"unselect",
|
||||
"util",
|
||||
"v0",
|
||||
"v1",
|
||||
"v2",
|
||||
"yml",
|
||||
"systemd"
|
||||
]
|
||||
}
|
||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
|
@ -1,37 +1,30 @@
|
|||
Branch
|
||||
---------------
|
||||
If you would like to contribute - send pull request to dev branch.
|
||||
Getting dev version of **Cloud Commander**:
|
||||
## Commit
|
||||
|
||||
git clone git://github.com/coderaiser/cloudcmd.git
|
||||
cd cloudcmd && git checkout dev
|
||||
|
||||
or by [link](https://github.com/coderaiser/cloudcmd/tree/dev "Dev version").
|
||||
|
||||
Commit
|
||||
---------------
|
||||
Format of the commit message: **type(scope) subject**
|
||||
|
||||
**Type**:
|
||||
- feature(scope) subject
|
||||
- fix(scope) subject
|
||||
- docs(scope) subject
|
||||
- refactor(scope) subject
|
||||
- test(scope) subject
|
||||
- chore(scope) subject
|
||||
|
||||
- feature: scope: subject
|
||||
- fix: scope: subject
|
||||
- docs: scope: subject
|
||||
- refactor: scope: subject
|
||||
- test: scope: subject
|
||||
- chore: scope: subject
|
||||
|
||||
**Scope**:
|
||||
Scope could be anything specifying place of the commit change.
|
||||
For example util, console, view, edit, style etc...
|
||||
|
||||
**Subject text**:
|
||||
|
||||
- use imperative, present tense: “change” not “changed” nor “changes”
|
||||
- don't capitalize first letter
|
||||
- no dot (.) at the end
|
||||
**Message body**:
|
||||
**Message body**:
|
||||
- just as in <subject> use imperative, present tense: “change” not “changed” nor “changes”
|
||||
- includes motivation for the change and contrasts with previous behavior
|
||||
|
||||
**Examples**:
|
||||
- [fix(style) .name{width}: 37% -> 35%](https://github.com/coderaiser/cloudcmd/commit/94b0642e3990c17b3a0ee3efeb75f343e1e7c050)
|
||||
- [fix(console) dispatch: focus -> mouseup](https://github.com/coderaiser/cloudcmd/commit/f41ec5058d1411e86a881f8e8077e0572e0409ec)
|
||||
|
||||
- [fix: style: .name{width}: 37% -> 35%](https://github.com/coderaiser/cloudcmd/commit/94b0642e3990c17b3a0ee3efeb75f343e1e7c050)
|
||||
- [fix: console: dispatch: focus -> mouseup](https://github.com/coderaiser/cloudcmd/commit/f41ec5058d1411e86a881f8e8077e0572e0409ec)
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 Coderaiser <mnemonic.enemy@gmail.com>
|
||||
Copyright (c) 2012-2025 Coderaiser <mnemonic.enemy@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
|
|
|||
181
README.md
181
README.md
|
|
@ -1,25 +1,24 @@
|
|||
Cloud Commander v1.5.1 [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status][DependencyStatusIMGURL]][DependencyStatusURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL]
|
||||
===============
|
||||
###[Main][MainURL] [Blog][BlogURL] Live(![JitSu][JitSu_LIVE_IMG] [JitSu][JitSuURL], ![Heroku][Heroku_LIVE_IMG] [Heroku][HerokuURL])
|
||||
[NPMIMGURL]: https://img.shields.io/npm/v/cloudcmd.svg?style=flat
|
||||
[BuildStatusIMGURL]: https://img.shields.io/travis/coderaiser/cloudcmd/dev.svg?style=flat
|
||||
[DependencyStatusIMGURL]: https://img.shields.io/gemnasium/coderaiser/cloudcmd.svg?style=flat
|
||||
[LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat
|
||||
[NPM_INFO_IMG]: https://nodei.co/npm/cloudcmd.png
|
||||
[NPMURL]: https://npmjs.org/package/cloudcmd "npm"
|
||||
[BuildStatusURL]: https://travis-ci.org/coderaiser/cloudcmd "Build Status"
|
||||
[DependencyStatusURL]: https://gemnasium.com/coderaiser/cloudcmd "Dependency Status"
|
||||
[LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License"
|
||||
[MainURL]: http://cloudcmd.io "Main"
|
||||
[BlogURL]: http://blog.cloudcmd.io "Blog"
|
||||
[JitSuURL]: http://cloudcmd.jit.su "JitSu"
|
||||
[HerokuURL]: http://cloudcmd.herokuapp.com/ "Heroku"
|
||||
[JitSu_LIVE_IMG]: http://status-io.cloudcmd.io/host/cloudcmd.jit.su/img/txt.png "JitSu"
|
||||
[HEROKU_LIVE_IMG]: http://status-io.cloudcmd.io/host/cloudcmd.herokuapp.com/img/txt.png "Heroku"
|
||||
# Cloud Commander v19.1.9 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL]
|
||||
|
||||
**Cloud Commander** orthodox web file manager with console and editor.
|
||||
### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL]
|
||||
|
||||

|
||||
[MainURL]: https://cloudcmd.io "Main"
|
||||
[BlogURL]: https://blog.cloudcmd.io "Blog"
|
||||
[SupportURL]: https://patreon.com/coderaiser "Patreon"
|
||||
[DemoURL]: https://cloudcmd-zdp6.onrender.com
|
||||
[NPM_INFO_IMG]: https://nodei.co/npm/cloudcmd.png
|
||||
[BuildStatusURL]: https://github.com/coderaiser/cloudcmd/actions/workflows/nodejs.yml "Build Status"
|
||||
[BuildStatusIMGURL]: https://github.com/coderaiser/cloudcmd/actions/workflows/nodejs.yml/badge.svg
|
||||
[CodacyURL]: https://www.codacy.com/app/coderaiser/cloudcmd
|
||||
[CodacyIMG]: https://api.codacy.com/project/badge/Grade/ddda78be780549ce8754f8d47a8c0e36
|
||||
[GitterURL]: https://gitter.im/cloudcmd/hello
|
||||
[GitterIMGURL]: https://img.shields.io/gitter/room/coderaiser/cloudcmd.js.svg
|
||||
[DeployURL]: https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy"
|
||||
[DeployIMG]: https://www.herokucdn.com/deploy/button.png
|
||||
|
||||
**Cloud Commander** a file manager for the web with console and editor.
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
|
|
@ -27,10 +26,152 @@ Cloud Commander v1.5.1 [![License][LicenseIMGURL]][LicenseURL] [![NPM version][N
|
|||
npm i cloudcmd -g
|
||||
```
|
||||
|
||||
## Start
|
||||
|
||||
For starting just type in console:
|
||||
|
||||
```sh
|
||||
cloudcmd
|
||||
```
|
||||
|
||||
## How to use?
|
||||
|
||||
Open url `http://localhost:8000` in browser.
|
||||
|
||||
### View
|
||||
|
||||
You will see something similar to this.
|
||||

|
||||
|
||||
## Deploy
|
||||
|
||||
`Cloud Commander` could be easily deployed to [Heroku][DeployURL].
|
||||
|
||||
[![Deploy][DeployIMG]][DeployURL]
|
||||
|
||||
## Using as Middleware
|
||||
|
||||
Cloud Commander could be used as middleware for `node.js` applications based on [socket.io](http://socket.io "Socket.IO") and [express](http://expressjs.com "Express"):
|
||||
|
||||
Init `package.json`:
|
||||
|
||||
```
|
||||
npm init -y
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```
|
||||
npm i cloudcmd express socket.io -S
|
||||
```
|
||||
|
||||
And create `index.js`:
|
||||
|
||||
```js
|
||||
import http from 'node:http';
|
||||
import cloudcmd from 'cloudcmd';
|
||||
import {Server} from 'socket.io';
|
||||
import express from 'express';
|
||||
|
||||
const app = express();
|
||||
|
||||
const port = 1337;
|
||||
const prefix = '/';
|
||||
|
||||
const server = http.createServer(app);
|
||||
const socket = new Server(server, {
|
||||
path: `${prefix}socket.io`,
|
||||
});
|
||||
|
||||
const config = {
|
||||
name: 'cloudcmd :)',
|
||||
};
|
||||
|
||||
const filePicker = {
|
||||
data: {
|
||||
FilePicker: {
|
||||
key: 'key',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// override option from json/modules.json
|
||||
const modules = {
|
||||
filePicker,
|
||||
};
|
||||
|
||||
const {
|
||||
createConfigManager,
|
||||
configPath,
|
||||
} = cloudcmd;
|
||||
|
||||
const configManager = createConfigManager({
|
||||
configPath,
|
||||
});
|
||||
|
||||
app.use(prefix, cloudcmd({
|
||||
socket, // used by Config, Edit (optional) and Console (required)
|
||||
config, // config data (optional)
|
||||
modules, // optional
|
||||
configManager, // optional
|
||||
}));
|
||||
|
||||
server.listen(port);
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
The docker images are provided for multiple architectures and types. The following list shows all existing images:
|
||||
|
||||
| Architecture | Type |
|
||||
|----------------|--------------|
|
||||
| amd64 | linux |
|
||||
| arm64 (arm/v8) | linux |
|
||||
| amd64 | linux-alpine |
|
||||
| arm64 (arm/v8) | linux-alpine |
|
||||
|
||||
`Cloud Commander` could be used as a [docker container](https://hub.docker.com/r/coderaiser/cloudcmd/ "Docker container") this way:
|
||||
|
||||
```sh
|
||||
docker run -it --rm -v ~:/root -v /:/mnt/fs -w=/root -p 8000:8000 coderaiser/cloudcmd
|
||||
```
|
||||
|
||||
Config would be read from home directory, hosts root file system would be mount to `/mnt/fs`,
|
||||
`8000` port would be exposed to hosts port.
|
||||
|
||||
Also you could use [docker compose](https://docs.docker.com/compose/ "Docker Compose") with `docker-compose.yml`:
|
||||
|
||||
```yml
|
||||
version: '2'
|
||||
services:
|
||||
web:
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- ~:/root
|
||||
- /:/mnt/fs
|
||||
image: coderaiser/cloudcmd
|
||||
```
|
||||
|
||||
When you create this file run:
|
||||
|
||||
```sh
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
More documentation you can find on https://cloudcmd.io/.
|
||||
|
||||
## Get involved
|
||||
|
||||
There is a lot ways to be involved in `Cloud Commander` development:
|
||||
|
||||
- support project on patreon: https://patreon.com/coderaiser;
|
||||
- if you find a bug or got idea to share [create an issue](https://github.com/coderaiser/cloudcmd/issues/new "Create issue");
|
||||
- if you fixed a bug, typo or implemented new feature [create pull request](https://github.com/coderaiser/cloudcmd/compare "Create pull request");
|
||||
- if you know languages you can help with [site translations](https://github.com/coderaiser/cloudcmd/wiki "Cloud Commander community wiki");
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
|
|||
162
app.json
Normal file
162
app.json
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
{
|
||||
"name": "cloudcmd",
|
||||
"description": "Cloud Commander orthodox web file manager with console and editor",
|
||||
"repository": "https://github.com/coderaiser/cloudcmd",
|
||||
"logo": "http://cloudcmd.io/img/logo/cloudcmd.png",
|
||||
"keywords": [
|
||||
"console",
|
||||
"editor",
|
||||
"file",
|
||||
"file manager",
|
||||
"folder",
|
||||
"orthodox"
|
||||
],
|
||||
"stack": "heroku-18",
|
||||
"env": {
|
||||
"NPM_CONFIG_PRODUCTION": {
|
||||
"description": "Keep false to install devDependencies and build frontend",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_EDITOR": {
|
||||
"description": "set editor",
|
||||
"value": "edward",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_CONTACT": {
|
||||
"description": "enable contact",
|
||||
"value": "true",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_CONFIG_DIALOG": {
|
||||
"description": "show config dialog",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_CONFIG_AUTH": {
|
||||
"description": "disable auth change in config dialog",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_CONSOLE": {
|
||||
"description": "enable console",
|
||||
"value": "true",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_SYNC_CONSOLE_PATH": {
|
||||
"description": "sync console path",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_TERMINAL": {
|
||||
"description": "enable terminal",
|
||||
"value": "true",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_TERMINAL_PATH": {
|
||||
"description": "set terminal path",
|
||||
"value": "gritty",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_TERMINAL_COMMAND": {
|
||||
"description": "set command to run in terminal (shell by default)",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_ONE_FILE_PANEL": {
|
||||
"description": "show one file panel",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_KEYS_PANEL": {
|
||||
"description": "show keys panel",
|
||||
"value": "true",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_NAME": {
|
||||
"description": "set tab name in web browser",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_AUTH": {
|
||||
"description": "enable authorization",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_USERNAME": {
|
||||
"description": "set username",
|
||||
"value": "root",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_PASSWORD": {
|
||||
"description": "set password",
|
||||
"value": "toor",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_ROOT": {
|
||||
"description": "set root dir",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_VIM": {
|
||||
"description": "enable vim hot keys",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_COLUMNS": {
|
||||
"description": "set visible columns",
|
||||
"value": "name-size-date-owner-mode",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_OPEN": {
|
||||
"description": "open web browser when server started",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_CONFIRM_COPY": {
|
||||
"description": "confirm copy",
|
||||
"value": "true",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_CONFIRM_MOVE": {
|
||||
"description": "confirm move",
|
||||
"value": "true",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_SHOW_FILE_NAME": {
|
||||
"description": "show file name in view and edit",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_EXPORT": {
|
||||
"description": "enable export of config through a server",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_EXPORT_TOKEN": {
|
||||
"description": "authorization token used by export server",
|
||||
"value": "root",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_IMPORT": {
|
||||
"description": "enable import of config",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_IMPORT_TOKEN": {
|
||||
"description": "authorization token used to connect to export server",
|
||||
"value": "root",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_IMPORT_URL": {
|
||||
"description": "url of an import server",
|
||||
"value": "http://localhost:8000",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_IMPORT_LISTEN": {
|
||||
"description": "enable listen on config updates from import server",
|
||||
"value": "false",
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
}
|
||||
112
bin/cloudcmd.js
112
bin/cloudcmd.js
|
|
@ -1,112 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
(function(){
|
||||
'use strict';
|
||||
|
||||
var Info = require('../package'),
|
||||
|
||||
DIR = __dirname + '/../',
|
||||
DIR_LIB = DIR + 'lib/',
|
||||
|
||||
Util = require(DIR_LIB + 'util'),
|
||||
port,
|
||||
argv = process.argv,
|
||||
argvLength = argv.length,
|
||||
argvLast = argv.slice().pop();
|
||||
|
||||
switch (argvLast) {
|
||||
default:
|
||||
port = argvLast - 0;
|
||||
|
||||
if (argvLength === 2)
|
||||
start();
|
||||
else
|
||||
if (!isPort(argv))
|
||||
help();
|
||||
else
|
||||
if (isNaN(port))
|
||||
console.error('Error: port should be a number.');
|
||||
else
|
||||
start({
|
||||
port: port
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case '--test':
|
||||
Util.log('Cloud Commander testing mode');
|
||||
Util.log('argv: ', argv);
|
||||
|
||||
require('..');
|
||||
break;
|
||||
|
||||
case '-v':
|
||||
version();
|
||||
break;
|
||||
|
||||
case '--version':
|
||||
version();
|
||||
break;
|
||||
|
||||
case '-h':
|
||||
help();
|
||||
break;
|
||||
|
||||
case '--help':
|
||||
help();
|
||||
break;
|
||||
|
||||
case '--repl':
|
||||
repl();
|
||||
break;
|
||||
}
|
||||
|
||||
function version() {
|
||||
console.log('v' + Info.version);
|
||||
}
|
||||
|
||||
function start(config) {
|
||||
var SERVER = '../lib/server';
|
||||
|
||||
require(SERVER)(config);
|
||||
}
|
||||
|
||||
function isPort(argv) {
|
||||
var length = argv.length,
|
||||
str = argv
|
||||
.slice(length - 2, length - 1)
|
||||
.pop(),
|
||||
|
||||
PORT = ['-p', '--port'],
|
||||
is = Util.strCmp(str, PORT);
|
||||
|
||||
return is;
|
||||
}
|
||||
|
||||
function help() {
|
||||
var bin = require('../json/bin'),
|
||||
usage = 'Usage: cloudcmd [OPTION]...',
|
||||
description = Info.description + '.',
|
||||
|
||||
site = Util.render('General help using Cloud Commander: <{{ url }}>', {
|
||||
url: Info.homepage
|
||||
});
|
||||
|
||||
console.log(usage);
|
||||
console.log(description + '\n');
|
||||
|
||||
Object.keys(bin).forEach(function(name) {
|
||||
var line = ' ' + name + ' ' + bin[name];
|
||||
console.log(line);
|
||||
});
|
||||
|
||||
console.log('\n' + site);
|
||||
}
|
||||
|
||||
function repl() {
|
||||
console.log('REPL mode enabled (telnet localhost 1337)');
|
||||
require(DIR_LIB + '/server/repl');
|
||||
start();
|
||||
}
|
||||
|
||||
})();
|
||||
365
bin/cloudcmd.mjs
Executable file
365
bin/cloudcmd.mjs
Executable file
|
|
@ -0,0 +1,365 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import process from 'node:process';
|
||||
import {createRequire} from 'node:module';
|
||||
import {promisify} from 'node:util';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import {createSimport} from 'simport';
|
||||
import parse from 'yargs-parser';
|
||||
import exit from '../server/exit.js';
|
||||
import {createConfig, configPath} from '../server/config.mjs';
|
||||
import * as env from '../server/env.mjs';
|
||||
import prefixer from '../server/prefixer.js';
|
||||
import * as validate from '../server/validate.mjs';
|
||||
|
||||
process.on('unhandledRejection', exit);
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const Info = require('../package.json');
|
||||
const isUndefined = (a) => typeof a === 'undefined';
|
||||
const simport = createSimport(import.meta.url);
|
||||
|
||||
const choose = (a, b) => {
|
||||
if (isUndefined(a))
|
||||
return b;
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
const config = createConfig({
|
||||
configPath,
|
||||
});
|
||||
|
||||
const DIR_SERVER = '../server/';
|
||||
|
||||
const maybeRoot = (a) => {
|
||||
if (a === '.')
|
||||
return process.cwd();
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
const yargsOptions = {
|
||||
configuration: {
|
||||
'strip-aliased': true,
|
||||
'strip-dashed': true,
|
||||
},
|
||||
coerce: {
|
||||
root: maybeRoot,
|
||||
},
|
||||
string: [
|
||||
'name',
|
||||
'port',
|
||||
'password',
|
||||
'username',
|
||||
'config',
|
||||
'editor',
|
||||
'packer',
|
||||
'root',
|
||||
'prefix',
|
||||
'prefix-socket',
|
||||
'terminal-path',
|
||||
'terminal-command',
|
||||
'columns',
|
||||
'menu',
|
||||
'theme',
|
||||
'import-url',
|
||||
'import-token',
|
||||
'export-token',
|
||||
'dropbox-token',
|
||||
],
|
||||
boolean: [
|
||||
'auth',
|
||||
'repl',
|
||||
'save',
|
||||
'server',
|
||||
'online',
|
||||
'open',
|
||||
'config-dialog',
|
||||
'config-auth',
|
||||
'console',
|
||||
'sync-console-path',
|
||||
'contact',
|
||||
'terminal',
|
||||
'terminal-auto-restart',
|
||||
'one-file-panel',
|
||||
'confirm-copy',
|
||||
'confirm-move',
|
||||
'show-config',
|
||||
'show-dot-files',
|
||||
'show-file-name',
|
||||
'vim',
|
||||
'keys-panel',
|
||||
'color',
|
||||
'export',
|
||||
'import',
|
||||
'import-listen',
|
||||
'log',
|
||||
'zip',
|
||||
'dropbox',
|
||||
],
|
||||
default: {
|
||||
'server': true,
|
||||
'name': choose(env.parse('name'), config('name')),
|
||||
'auth': choose(env.bool('auth'), config('auth')),
|
||||
'port': config('port'),
|
||||
'online': config('online'),
|
||||
'open': choose(env.bool('open'), config('open')),
|
||||
'editor': env.parse('editor') || config('editor'),
|
||||
'menu': env.parse('menu') || config('menu'),
|
||||
'packer': config('packer') || 'tar',
|
||||
'zip': config('zip'),
|
||||
'username': env.parse('username') || config('username'),
|
||||
'root': choose(env.parse('root'), config('root')),
|
||||
'prefix': choose(env.parse('prefix'), config('prefix')),
|
||||
'console': choose(env.bool('console'), config('console')),
|
||||
'contact': choose(env.bool('contact'), config('contact')),
|
||||
'terminal': choose(env.bool('terminal'), config('terminal')),
|
||||
'columns': env.parse('columns') || config('columns') || '',
|
||||
'theme': env.parse('theme') || config('theme') || '',
|
||||
'vim': choose(env.bool('vim'), config('vim')),
|
||||
'log': config('log'),
|
||||
|
||||
'import-url': env.parse('import_url') || config('importUrl'),
|
||||
'import-listen': choose(env.bool('import_listen'), config('importListen')),
|
||||
'import': choose(env.bool('import'), config('import')),
|
||||
'export': choose(env.bool('export'), config('export')),
|
||||
|
||||
'prefix-socket': config('prefixSocket'),
|
||||
'show-dot-files': choose(env.bool('show_dot_files'), config('showDotFiles')),
|
||||
'show-file-name': choose(env.bool('show_file_name'), config('showFileName')),
|
||||
'sync-console-path': choose(env.bool('sync_console_path'), config('syncConsolePath')),
|
||||
'config-dialog': choose(env.bool('config_dialog'), config('configDialog')),
|
||||
'config-auth': choose(env.bool('config_auth'), config('configAuth')),
|
||||
'terminal-path': env.parse('terminal_path') || config('terminalPath'),
|
||||
'terminal-command': env.parse('terminal_command') || config('terminalCommand'),
|
||||
'terminal-auto-restart': choose(env.bool('terminal_auto_restart'), config('terminalAutoRestart')),
|
||||
'one-file-panel': choose(env.bool('one_file_panel'), config('oneFilePanel')),
|
||||
'confirm-copy': choose(env.bool('confirm_copy'), config('confirmCopy')),
|
||||
'confirm-move': choose(env.bool('confirm_move'), config('confirmMove')),
|
||||
'keys-panel': env.bool('keys_panel') || config('keysPanel'),
|
||||
'import-token': env.parse('import_token') || config('importToken'),
|
||||
'export-token': env.parse('export_token') || config('exportToken'),
|
||||
|
||||
'dropbox': config('dropbox'),
|
||||
'dropbox-token': config('dropboxToken') || '',
|
||||
},
|
||||
alias: {
|
||||
version: 'v',
|
||||
help: 'h',
|
||||
password: 'p',
|
||||
online: 'o',
|
||||
username: 'u',
|
||||
save: 's',
|
||||
auth: 'a',
|
||||
config: 'c',
|
||||
},
|
||||
};
|
||||
|
||||
const {argv} = process;
|
||||
const args = parse(argv.slice(2), yargsOptions);
|
||||
|
||||
if (args.version)
|
||||
version();
|
||||
else if (args.help)
|
||||
help();
|
||||
else
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
const {validateArgs} = await simport('@putout/cli-validate-args');
|
||||
|
||||
const error = await validateArgs(args, [
|
||||
...yargsOptions.boolean,
|
||||
...yargsOptions.string,
|
||||
]);
|
||||
|
||||
if (error)
|
||||
return exit(error);
|
||||
|
||||
if (args.repl)
|
||||
repl();
|
||||
|
||||
validate.columns(args.columns);
|
||||
validate.theme(args.theme);
|
||||
|
||||
port(args.port);
|
||||
|
||||
config('name', args.name);
|
||||
config('auth', args.auth);
|
||||
config('online', args.online);
|
||||
config('open', args.open);
|
||||
config('username', args.username);
|
||||
config('console', args.console);
|
||||
config('syncConsolePath', args.syncConsolePath);
|
||||
config('showDotFiles', args.showDotFiles);
|
||||
config('showFileName', args.showFileName);
|
||||
config('contact', args.contact);
|
||||
config('terminal', args.terminal);
|
||||
config('terminalPath', args.terminalPath);
|
||||
config('terminalCommand', args.terminalCommand);
|
||||
config('terminalAutoRestart', args.terminalAutoRestart);
|
||||
config('editor', args.editor);
|
||||
config('menu', args.menu);
|
||||
config('prefix', prefixer(args.prefix));
|
||||
config('prefixSocket', prefixer(args.prefixSocket));
|
||||
config('root', args.root || '/');
|
||||
config('vim', args.vim);
|
||||
config('theme', args.theme);
|
||||
config('columns', args.columns);
|
||||
config('log', args.log);
|
||||
config('confirmCopy', args.confirmCopy);
|
||||
config('confirmMove', args.confirmMove);
|
||||
config('oneFilePanel', args.oneFilePanel);
|
||||
config('configDialog', args.configDialog);
|
||||
config('configAuth', args.configAuth);
|
||||
config('keysPanel', args.keysPanel);
|
||||
config('export', args.export);
|
||||
config('exportToken', args.exportToken);
|
||||
config('import', args.import);
|
||||
config('importToken', args.importToken);
|
||||
config('importListen', args.importListen);
|
||||
config('importUrl', args.importUrl);
|
||||
|
||||
config('dropbox', args.dropbox);
|
||||
config('dropboxToken', args.dropboxToken);
|
||||
|
||||
await readConfig(args.config);
|
||||
|
||||
const options = {
|
||||
root: config('root'),
|
||||
editor: config('editor'),
|
||||
packer: config('packer'),
|
||||
prefix: config('prefix'),
|
||||
prefixSocket: config('prefixSocket'),
|
||||
columns: config('columns'),
|
||||
theme: config('theme'),
|
||||
menu: config('menu'),
|
||||
};
|
||||
|
||||
const password = env.parse('password') || args.password;
|
||||
|
||||
if (password)
|
||||
config('password', await getPassword(password));
|
||||
|
||||
validateRoot(options.root, config);
|
||||
|
||||
if (args.showConfig)
|
||||
await showConfig();
|
||||
|
||||
const {distributeImport} = await simport('../server/distribute/import.mjs');
|
||||
const importConfig = promisify(distributeImport);
|
||||
|
||||
await start(options, config);
|
||||
|
||||
if (args.save)
|
||||
config.write();
|
||||
|
||||
await tryToCatch(checkUpdate);
|
||||
await importConfig(config);
|
||||
}
|
||||
|
||||
function validateRoot(root, config) {
|
||||
validate.root(root, config);
|
||||
|
||||
if (root === '/')
|
||||
return;
|
||||
|
||||
console.log(`root: ${root}`);
|
||||
}
|
||||
|
||||
async function getPassword(password) {
|
||||
const criton = await simport('criton');
|
||||
return criton(password, config('algo'));
|
||||
}
|
||||
|
||||
function version() {
|
||||
console.log('v' + Info.version);
|
||||
}
|
||||
|
||||
async function start(options, config) {
|
||||
const SERVER = `${DIR_SERVER}server.mjs`;
|
||||
|
||||
if (!args.server)
|
||||
return;
|
||||
|
||||
const server = await simport(SERVER);
|
||||
server(options, config);
|
||||
}
|
||||
|
||||
function port(arg) {
|
||||
const number = parseInt(arg, 10);
|
||||
|
||||
if (!isNaN(number))
|
||||
return config('port', number);
|
||||
|
||||
exit('cloudcmd --port: should be a number');
|
||||
}
|
||||
|
||||
async function showConfig() {
|
||||
const show = await simport('../server/show-config');
|
||||
const data = show(config('*'));
|
||||
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
async function readConfig(name) {
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
const tryToCatch = await simport('try-to-catch');
|
||||
const forEachKey = await simport('for-each-key');
|
||||
|
||||
const [error, data] = await tryToCatch(simport, name);
|
||||
|
||||
if (error)
|
||||
return exit(error.message);
|
||||
|
||||
forEachKey(config, data);
|
||||
}
|
||||
|
||||
async function help() {
|
||||
const {default: bin} = await import('../json/help.json', {
|
||||
with: {
|
||||
type: 'json',
|
||||
},
|
||||
});
|
||||
|
||||
const forEachKey = await simport('for-each-key');
|
||||
const currify = await simport('currify');
|
||||
|
||||
const usage = 'Usage: cloudcmd [options]';
|
||||
const url = Info.homepage;
|
||||
const log = currify((a, b, c) => console.log(a, b, c));
|
||||
|
||||
console.log(usage);
|
||||
console.log('Options:');
|
||||
forEachKey(log(' %s %s'), bin);
|
||||
console.log('\nGeneral help using Cloud Commander: <%s>', url);
|
||||
}
|
||||
|
||||
function repl() {
|
||||
console.log('REPL mode enabled (telnet localhost 1337)');
|
||||
require(`${DIR_SERVER}repl`);
|
||||
}
|
||||
|
||||
async function checkUpdate() {
|
||||
const load = await simport('package-json');
|
||||
|
||||
const {version} = await load(Info.name, 'latest');
|
||||
await showUpdateInfo(version);
|
||||
}
|
||||
|
||||
async function showUpdateInfo(version) {
|
||||
if (version === Info.version)
|
||||
return;
|
||||
|
||||
const chalk = await simport('chalk');
|
||||
|
||||
const latestVersion = chalk.green.bold(`v${version}`);
|
||||
const latest = `update available: ${latestVersion}`;
|
||||
const current = chalk.dim(`(current: v${Info.version})`);
|
||||
|
||||
console.log('%s %s', latest, current);
|
||||
}
|
||||
75
bin/release.mjs
Executable file
75
bin/release.mjs
Executable file
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import {promisify} from 'node:util';
|
||||
import process from 'node:process';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import {createSimport} from 'simport';
|
||||
import minor from 'minor';
|
||||
import _place from 'place';
|
||||
import rendy from 'rendy';
|
||||
import shortdate from 'shortdate';
|
||||
|
||||
const simport = createSimport(import.meta.url);
|
||||
const place = promisify(_place);
|
||||
|
||||
const Info = await simport('../package.json');
|
||||
|
||||
await main();
|
||||
|
||||
async function main() {
|
||||
const history = '## Version history\n\n';
|
||||
const link = '//github.com/coderaiser/cloudcmd/releases/tag/';
|
||||
const template = '- ' +
|
||||
'*{{ date }}*, ' +
|
||||
'**[v{{ version }}]' +
|
||||
'(' + link +
|
||||
'v{{ version }})**\n';
|
||||
|
||||
const {version} = Info;
|
||||
|
||||
const [error, versionNew] = await tryToCatch(cl);
|
||||
|
||||
if (error)
|
||||
return console.error(error);
|
||||
|
||||
await replaceVersion('README.md', version, versionNew);
|
||||
await replaceVersion('HELP.md', version, versionNew);
|
||||
|
||||
const historyNew = history + rendy(template, {
|
||||
date: shortdate(),
|
||||
version: versionNew,
|
||||
});
|
||||
|
||||
await replaceVersion('HELP.md', history, historyNew);
|
||||
}
|
||||
|
||||
async function replaceVersion(name, version, versionNew) {
|
||||
const [error] = await tryToCatch(place, name, version, versionNew);
|
||||
|
||||
if (error)
|
||||
return console.error(error);
|
||||
|
||||
console.log(`done: ${name}`);
|
||||
}
|
||||
|
||||
async function cl() {
|
||||
const {argv} = process;
|
||||
const length = argv.length - 1;
|
||||
const last = process.argv[length];
|
||||
const regExp = /^--(major|minor|patch)$/;
|
||||
const [, match] = last.match(regExp) || [];
|
||||
|
||||
console.log(last);
|
||||
|
||||
if (!regExp.test(last))
|
||||
throw Error('ERROR: version is missing. release --patch|--minor|--major');
|
||||
|
||||
return getVersionNew(last, match);
|
||||
}
|
||||
|
||||
function getVersionNew(last, match) {
|
||||
if (match)
|
||||
return minor(match, Info.version);
|
||||
|
||||
return last.substr(3);
|
||||
}
|
||||
425
client/client.mjs
Normal file
425
client/client.mjs
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
import process from 'node:process';
|
||||
|
||||
/* global DOM */
|
||||
import Emitify from 'emitify';
|
||||
import inherits from 'inherits';
|
||||
import rendy from 'rendy';
|
||||
import load from 'load.js';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import {addSlashToEnd} from 'format-io';
|
||||
import pascalCase from 'just-pascal-case';
|
||||
import currify from 'currify';
|
||||
import * as Images from './dom/images.mjs';
|
||||
import {unregisterSW} from './sw/register.js';
|
||||
import {getJsonFromFileTable} from './get-json-from-file-table.mjs';
|
||||
import {Key} from './key/index.mjs';
|
||||
import {
|
||||
apiURL,
|
||||
formatMsg,
|
||||
buildFromJSON,
|
||||
} from '../common/cloudfunc.mjs';
|
||||
import {loadModule} from './load-module.mjs';
|
||||
|
||||
const noJS = (a) => a.replace(/.js$/, '');
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
inherits(CloudCmdProto, Emitify);
|
||||
|
||||
export const createCloudCmd = ({DOM, Listeners}) => {
|
||||
return new CloudCmdProto({
|
||||
DOM,
|
||||
Listeners,
|
||||
});
|
||||
};
|
||||
|
||||
load.addErrorListener((e, src) => {
|
||||
const msg = `file ${src} could not be loaded`;
|
||||
Images.show.error(msg);
|
||||
});
|
||||
|
||||
function CloudCmdProto({DOM, Listeners}) {
|
||||
Emitify.call(this);
|
||||
|
||||
const CloudCmd = this;
|
||||
const Info = DOM.CurrentInfo;
|
||||
|
||||
const {Storage, Files} = DOM;
|
||||
|
||||
this.log = () => {
|
||||
if (!isDev)
|
||||
return;
|
||||
};
|
||||
this.prefix = '';
|
||||
this.prefixSocket = '';
|
||||
this.prefixURL = '';
|
||||
|
||||
this.MIN_ONE_PANEL_WIDTH = DOM.getCSSVar('min-one-panel-width');
|
||||
this.HOST = location.origin || location.protocol + '//' + location.host;
|
||||
this.sort = {
|
||||
left: 'name',
|
||||
right: 'name',
|
||||
};
|
||||
|
||||
this.order = {
|
||||
left: 'asc',
|
||||
right: 'asc',
|
||||
};
|
||||
|
||||
this.changeDir = async (path, overrides = {}) => {
|
||||
const {
|
||||
isRefresh,
|
||||
panel,
|
||||
history = true,
|
||||
noCurrent,
|
||||
currentName,
|
||||
} = overrides;
|
||||
|
||||
const refresh = isRefresh;
|
||||
let panelChanged;
|
||||
|
||||
if (!noCurrent && panel && panel !== Info.panel) {
|
||||
DOM.changePanel();
|
||||
panelChanged = true;
|
||||
}
|
||||
|
||||
let imgPosition;
|
||||
|
||||
if (panelChanged || refresh || !history)
|
||||
imgPosition = 'top';
|
||||
|
||||
Images.show.load(imgPosition, panel);
|
||||
|
||||
/* загружаем содержимое каталога */
|
||||
await ajaxLoad(addSlashToEnd(path), {
|
||||
refresh,
|
||||
history,
|
||||
noCurrent,
|
||||
currentName,
|
||||
showDotFiles: CloudCmd.config('showDotFiles'),
|
||||
}, panel);
|
||||
};
|
||||
|
||||
/**
|
||||
* Конструктор CloudClient, который
|
||||
* выполняет весь функционал по
|
||||
* инициализации
|
||||
*/
|
||||
this.init = async (prefix, config) => {
|
||||
CloudCmd.prefix = prefix;
|
||||
CloudCmd.prefixURL = `${prefix}${apiURL}`;
|
||||
CloudCmd.prefixSocket = config.prefixSocket;
|
||||
CloudCmd.DIR_DIST = `${prefix}/dist`;
|
||||
CloudCmd.DIR_MODULES = `${this.DIR_DIST}/modules`;
|
||||
|
||||
CloudCmd.config = (key) => config[key];
|
||||
CloudCmd.config.if = currify((key, fn, a) => config[key] && fn(a));
|
||||
CloudCmd._config = (key, value) => {
|
||||
/*
|
||||
* should be called from config.js only
|
||||
* after successful update on server
|
||||
*/
|
||||
if (key === 'password')
|
||||
return;
|
||||
|
||||
config[key] = value;
|
||||
};
|
||||
|
||||
if (config.oneFilePanel)
|
||||
CloudCmd.MIN_ONE_PANEL_WIDTH = Infinity;
|
||||
|
||||
if (!document.body.scrollIntoViewIfNeeded)
|
||||
await load.js(`${CloudCmd.DIR_MODULES}/polyfill.js`);
|
||||
|
||||
await initModules();
|
||||
await baseInit();
|
||||
|
||||
CloudCmd.route(location.hash);
|
||||
};
|
||||
|
||||
this.route = (path) => {
|
||||
const query = path.split('/');
|
||||
|
||||
if (!path)
|
||||
return;
|
||||
|
||||
const [kebabModule] = query;
|
||||
const module = noJS(pascalCase(kebabModule.slice(1)));
|
||||
|
||||
const [, file] = query;
|
||||
const current = DOM.getCurrentByName(file);
|
||||
|
||||
if (file && !current) {
|
||||
const msg = formatMsg('set current file', file, 'error');
|
||||
CloudCmd.log(msg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DOM.setCurrentFile(current);
|
||||
CloudCmd.execFromModule(module, 'show');
|
||||
};
|
||||
|
||||
this.logOut = async () => {
|
||||
const url = CloudCmd.prefix + '/logout';
|
||||
const error = () => document.location.reload();
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
await DOM.Storage.clear();
|
||||
unregisterSW(prefix);
|
||||
DOM.load.ajax({
|
||||
url,
|
||||
error,
|
||||
});
|
||||
};
|
||||
|
||||
const initModules = async () => {
|
||||
CloudCmd.Key = Key;
|
||||
CloudCmd.Key.bind();
|
||||
|
||||
const [, modules] = await tryToCatch(Files.get, 'modules');
|
||||
const showLoad = Images.show.load;
|
||||
|
||||
const doBefore = {
|
||||
edit: showLoad,
|
||||
menu: showLoad,
|
||||
};
|
||||
|
||||
const load = (name, path, dobefore) => {
|
||||
loadModule({
|
||||
name,
|
||||
path,
|
||||
dobefore,
|
||||
});
|
||||
};
|
||||
|
||||
if (!modules)
|
||||
return;
|
||||
|
||||
for (const module of modules.local) {
|
||||
load(null, module, doBefore[module]);
|
||||
}
|
||||
};
|
||||
|
||||
async function saveCurrentName(currentName) {
|
||||
await Storage.set('current-name', currentName);
|
||||
}
|
||||
|
||||
async function baseInit() {
|
||||
const files = DOM.getFiles();
|
||||
|
||||
CloudCmd.on('current-file', DOM.updateCurrentInfo);
|
||||
CloudCmd.on('current-name', saveCurrentName);
|
||||
|
||||
const name = await Storage.get('current-name');
|
||||
const currentFile = name && DOM.getCurrentByName(name) || files[0];
|
||||
|
||||
/* выделяем строку с первым файлом */
|
||||
if (files)
|
||||
DOM.setCurrentFile(currentFile, {
|
||||
// when hash is present
|
||||
// it should be handled with this.route
|
||||
// overwre otherwise
|
||||
history: !location.hash,
|
||||
});
|
||||
|
||||
const dirPath = DOM.getCurrentDirPath();
|
||||
|
||||
Listeners.init();
|
||||
|
||||
const panels = getPanels();
|
||||
panels.forEach(Listeners.setOnPanel);
|
||||
|
||||
Listeners.initKeysPanel();
|
||||
|
||||
if (!CloudCmd.config('dirStorage'))
|
||||
return;
|
||||
|
||||
const data = await Storage.get(dirPath);
|
||||
|
||||
if (!data)
|
||||
await Storage.setJson(dirPath, getJsonFromFileTable());
|
||||
}
|
||||
|
||||
function getPanels() {
|
||||
const panels = ['left'];
|
||||
|
||||
if (CloudCmd.config('oneFilePanel'))
|
||||
return panels;
|
||||
|
||||
return [
|
||||
...panels,
|
||||
'right',
|
||||
];
|
||||
}
|
||||
|
||||
this.execFromModule = async (moduleName, funcName, ...args) => {
|
||||
await CloudCmd[moduleName]();
|
||||
|
||||
const func = CloudCmd[moduleName][funcName];
|
||||
func(...args);
|
||||
};
|
||||
|
||||
this.refresh = async (options = {}) => {
|
||||
const {panel = Info.panel, currentName} = options;
|
||||
|
||||
const path = DOM.getCurrentDirPath(panel);
|
||||
|
||||
const isRefresh = true;
|
||||
const history = false;
|
||||
const noCurrent = options?.noCurrent;
|
||||
|
||||
await CloudCmd.changeDir(path, {
|
||||
isRefresh,
|
||||
history,
|
||||
panel,
|
||||
noCurrent,
|
||||
currentName,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Функция загружает json-данные о Файловой Системе
|
||||
* через ajax-запрос.
|
||||
* @param path - каталог для чтения
|
||||
* @param options
|
||||
* { refresh, history } - необходимость обновить данные о каталоге
|
||||
* @param panel
|
||||
*
|
||||
*/
|
||||
async function ajaxLoad(path, options = {}, panel) {
|
||||
const {RESTful} = DOM;
|
||||
|
||||
CloudCmd.log(`reading dir: "${path}";`);
|
||||
|
||||
const dirStorage = CloudCmd.config('dirStorage');
|
||||
const json = dirStorage && await Storage.getJson(path);
|
||||
|
||||
const name = options.currentName || Info.name;
|
||||
const {noCurrent, refresh} = options;
|
||||
|
||||
if (!refresh && json)
|
||||
return await createFileTable(json, panel, options);
|
||||
|
||||
const position = DOM.getPanelPosition(panel);
|
||||
const sort = CloudCmd.sort[position];
|
||||
const order = CloudCmd.order[position];
|
||||
|
||||
const query = rendy('?sort={{ sort }}&order={{ order }}', {
|
||||
sort,
|
||||
order,
|
||||
});
|
||||
|
||||
const [, newObj] = await RESTful.read(path + query, 'json');
|
||||
|
||||
if (!newObj)
|
||||
// that's OK, error handled by RESTful
|
||||
return;
|
||||
|
||||
options.sort = sort;
|
||||
options.order = order;
|
||||
|
||||
await createFileTable(newObj, panel, options);
|
||||
|
||||
if (refresh && !noCurrent)
|
||||
DOM.setCurrentByName(name);
|
||||
|
||||
if (!CloudCmd.config('dirStorage'))
|
||||
return;
|
||||
|
||||
Storage.setJson(path, newObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция строит файловую таблицу
|
||||
* @param data - данные о файлах
|
||||
* @param panelParam
|
||||
* @param options - history, noCurrent, showDotFiles
|
||||
*/
|
||||
async function createFileTable(data, panelParam, options) {
|
||||
const {
|
||||
history,
|
||||
noCurrent,
|
||||
showDotFiles,
|
||||
} = options;
|
||||
|
||||
const names = [
|
||||
'file',
|
||||
'path',
|
||||
'link',
|
||||
'pathLink',
|
||||
];
|
||||
|
||||
const [error, [file, path, link, pathLink]] = await tryToCatch(Files.get, names);
|
||||
|
||||
if (error)
|
||||
return DOM.Dialog.alert(error.responseText);
|
||||
|
||||
const panel = panelParam || DOM.getPanel();
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
const {dir, name} = Info;
|
||||
|
||||
const {childNodes} = panel;
|
||||
let i = childNodes.length;
|
||||
|
||||
while (i--)
|
||||
panel.removeChild(panel.lastChild);
|
||||
|
||||
panel.innerHTML = buildFromJSON({
|
||||
sort: options.sort,
|
||||
order: options.order,
|
||||
data,
|
||||
id: panel.id,
|
||||
prefix,
|
||||
showDotFiles,
|
||||
template: {
|
||||
file,
|
||||
path,
|
||||
pathLink,
|
||||
link,
|
||||
},
|
||||
});
|
||||
|
||||
Listeners.setOnPanel(panel);
|
||||
|
||||
if (!noCurrent) {
|
||||
let current;
|
||||
|
||||
if (name === '..' && dir !== '/')
|
||||
current = DOM.getCurrentByName(dir);
|
||||
|
||||
if (!current)
|
||||
[current] = DOM.getFiles(panel);
|
||||
|
||||
DOM.setCurrentFile(current, {
|
||||
history,
|
||||
});
|
||||
|
||||
CloudCmd.emit('active-dir', Info.dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
this.goToParentDir = async () => {
|
||||
const {
|
||||
dir,
|
||||
dirPath,
|
||||
parentDirPath,
|
||||
panel,
|
||||
} = Info;
|
||||
|
||||
if (dirPath === parentDirPath)
|
||||
return;
|
||||
|
||||
const path = parentDirPath;
|
||||
|
||||
await CloudCmd.changeDir(path);
|
||||
|
||||
const current = DOM.getCurrentByName(dir);
|
||||
const [first] = DOM.getFiles(panel);
|
||||
|
||||
DOM.setCurrentFile(current || first, {
|
||||
history,
|
||||
});
|
||||
};
|
||||
}
|
||||
74
client/cloudcmd.mjs
Normal file
74
client/cloudcmd.mjs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import process from 'node:process';
|
||||
import wraptile from 'wraptile';
|
||||
import load from 'load.js';
|
||||
import '../css/main.css';
|
||||
import {registerSW, listenSW} from './sw/register.js';
|
||||
import {initSortPanel, sortPanel} from './sort.mjs';
|
||||
import Util from '../common/util.js';
|
||||
import * as CloudFunc from '../common/cloudfunc.mjs';
|
||||
import DOM from './dom/index.js';
|
||||
import {createCloudCmd} from './client.mjs';
|
||||
import * as Listeners from './listeners/index.js';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
export default init;
|
||||
|
||||
globalThis.CloudCmd = init;
|
||||
|
||||
async function init(config) {
|
||||
globalThis.CloudCmd = createCloudCmd({
|
||||
DOM,
|
||||
Listeners,
|
||||
});
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.Util = Util;
|
||||
globalThis.CloudFunc = CloudFunc;
|
||||
|
||||
await register(config);
|
||||
|
||||
initSortPanel();
|
||||
globalThis.CloudCmd.sortPanel = sortPanel;
|
||||
const prefix = getPrefix(config.prefix);
|
||||
|
||||
globalThis.CloudCmd.init(prefix, config);
|
||||
|
||||
if (globalThis.CloudCmd.config('menu') === 'aleman')
|
||||
setTimeout(() => {
|
||||
import('https://esm.sh/@putout/processor-html');
|
||||
import('https://esm.sh/@putout/bundle');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function getPrefix(prefix) {
|
||||
if (!prefix)
|
||||
return '';
|
||||
|
||||
if (!prefix.indexOf('/'))
|
||||
return prefix;
|
||||
|
||||
return `/${prefix}`;
|
||||
}
|
||||
|
||||
const onUpdateFound = wraptile(async (config) => {
|
||||
if (isDev)
|
||||
return;
|
||||
|
||||
const {DOM} = globalThis;
|
||||
const prefix = getPrefix(config.prefix);
|
||||
|
||||
await load.js(`${prefix}/dist/cloudcmd.common.js`);
|
||||
await load.js(`${prefix}/dist/cloudcmd.js`);
|
||||
|
||||
console.log('cloudcmd: sw: updated');
|
||||
|
||||
DOM.Events.removeAll();
|
||||
globalThis.CloudCmd(config);
|
||||
});
|
||||
|
||||
async function register(config) {
|
||||
const {prefix} = config;
|
||||
const sw = await registerSW(prefix);
|
||||
|
||||
listenSW(sw, 'updatefound', onUpdateFound(config));
|
||||
}
|
||||
124
client/dom/buffer.mjs
Normal file
124
client/dom/buffer.mjs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/* global CloudCmd*/
|
||||
import tryToPromiseAll from '../../common/try-to-promise-all.js';
|
||||
import Storage from './storage.js';
|
||||
|
||||
const CLASS = 'cut-file';
|
||||
const COPY = 'copy';
|
||||
const CUT = 'cut';
|
||||
|
||||
function showMessage(msg) {
|
||||
globalThis.DOM.Dialog.alert(msg);
|
||||
}
|
||||
|
||||
function getNames() {
|
||||
const {DOM} = globalThis;
|
||||
const files = DOM.getActiveFiles();
|
||||
|
||||
return DOM.getFilenames(files);
|
||||
}
|
||||
|
||||
function addCutClass() {
|
||||
const {DOM} = globalThis;
|
||||
const files = DOM.getActiveFiles();
|
||||
|
||||
for (const element of files) {
|
||||
element.classList.add(CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
function rmCutClass() {
|
||||
const {DOM} = globalThis;
|
||||
const files = DOM.getByClassAll(CLASS);
|
||||
|
||||
for (const element of files) {
|
||||
element.classList.remove(CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
const checkEnabled = (fn) => () => {
|
||||
const is = CloudCmd.config('buffer');
|
||||
|
||||
if (is)
|
||||
return fn();
|
||||
|
||||
showMessage('Buffer disabled in config!');
|
||||
};
|
||||
|
||||
async function readBuffer() {
|
||||
const [e, cp, ct] = await tryToPromiseAll([
|
||||
Storage.getJson(COPY),
|
||||
Storage.getJson(CUT),
|
||||
]);
|
||||
|
||||
return [
|
||||
e,
|
||||
cp,
|
||||
ct,
|
||||
];
|
||||
}
|
||||
|
||||
export const copy = checkEnabled(async () => {
|
||||
const Info = globalThis.DOM.CurrentInfo;
|
||||
const names = getNames();
|
||||
const from = Info.dirPath;
|
||||
|
||||
await clear();
|
||||
|
||||
if (!names.length)
|
||||
return;
|
||||
|
||||
await Storage.remove(CUT);
|
||||
await Storage.setJson(COPY, {
|
||||
from,
|
||||
names,
|
||||
});
|
||||
});
|
||||
|
||||
export const cut = checkEnabled(async () => {
|
||||
const Info = globalThis.DOM.CurrentInfo;
|
||||
const names = getNames();
|
||||
const from = Info.dirPath;
|
||||
|
||||
await clear();
|
||||
|
||||
if (!names.length)
|
||||
return;
|
||||
|
||||
addCutClass();
|
||||
|
||||
await Storage.setJson(CUT, {
|
||||
from,
|
||||
names,
|
||||
});
|
||||
});
|
||||
|
||||
export const clear = checkEnabled(async () => {
|
||||
await Storage.remove(COPY);
|
||||
await Storage.remove(CUT);
|
||||
|
||||
rmCutClass();
|
||||
});
|
||||
|
||||
export const paste = checkEnabled(async () => {
|
||||
const Info = globalThis.DOM.CurrentInfo;
|
||||
const [error, cp, ct] = await readBuffer();
|
||||
|
||||
if (error || !cp && !ct)
|
||||
return showMessage(error || 'Buffer is empty!');
|
||||
|
||||
const opStr = cp ? 'copy' : 'move';
|
||||
const data = cp || ct;
|
||||
const {Operation} = CloudCmd;
|
||||
const msg = 'Path is same!';
|
||||
const to = Info.dirPath;
|
||||
|
||||
if (data.from === to)
|
||||
return showMessage(msg);
|
||||
|
||||
Operation.show(opStr, {
|
||||
...data,
|
||||
to,
|
||||
});
|
||||
|
||||
await clear();
|
||||
});
|
||||
310
client/dom/current-file.mjs
Normal file
310
client/dom/current-file.mjs
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
/* global DOM */
|
||||
/* global CloudCmd */
|
||||
import createElement from '@cloudcmd/create-element';
|
||||
import {encode, decode} from '../../common/entity.js';
|
||||
import {getTitle, FS} from '../../common/cloudfunc.mjs';
|
||||
|
||||
let Title;
|
||||
|
||||
const CURRENT_FILE = 'current-file';
|
||||
const encodeNBSP = (a) => a?.replace('\xa0', ' ');
|
||||
const decodeNBSP = (a) => a?.replace(' ', '\xa0');
|
||||
|
||||
export const _CURRENT_FILE = CURRENT_FILE;
|
||||
|
||||
/**
|
||||
* set name from current (or param) file
|
||||
*
|
||||
* @param name
|
||||
* @param current
|
||||
*/
|
||||
export const setCurrentName = (name, current) => {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const {link} = Info;
|
||||
const {prefix} = CloudCmd;
|
||||
const dir = prefix + FS + Info.dirPath;
|
||||
const encoded = encode(name);
|
||||
|
||||
link.title = encoded;
|
||||
link.href = dir + encoded;
|
||||
link.innerHTML = encoded;
|
||||
|
||||
current.setAttribute('data-name', createNameAttribute(name));
|
||||
CloudCmd.emit('current-file', current);
|
||||
|
||||
return link;
|
||||
};
|
||||
|
||||
/**
|
||||
* get name from current (or param) file
|
||||
*
|
||||
* @param currentFile
|
||||
*/
|
||||
export const getCurrentName = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
|
||||
if (!current)
|
||||
return '';
|
||||
|
||||
return parseNameAttribute(current.getAttribute('data-name'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a `data-name` attribute for the given filename
|
||||
* @param name The string name to encode
|
||||
*/
|
||||
const createNameAttribute = (name) => {
|
||||
const encoded = btoa(encodeURI(name));
|
||||
return `js-file-${encoded}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a `data-name` attribute string back into the original filename
|
||||
* @param attribute The string we wish to decode
|
||||
*/
|
||||
const parseNameAttribute = (attribute) => {
|
||||
attribute = attribute.replace('js-file-', '');
|
||||
return decodeNBSP(decodeURI(atob(attribute)));
|
||||
};
|
||||
|
||||
export const _parseNameAttribute = parseNameAttribute;
|
||||
|
||||
const parseHrefAttribute = (prefix, attribute) => {
|
||||
attribute = attribute.replace(RegExp('^' + prefix + FS), '');
|
||||
return decode(decodeNBSP(attribute));
|
||||
};
|
||||
|
||||
export const _parseHrefAttribute = parseHrefAttribute;
|
||||
|
||||
/**
|
||||
* get current direcotory path
|
||||
*/
|
||||
export const getCurrentDirPath = (panel = DOM.getPanel()) => {
|
||||
const path = DOM.getByDataName('js-path', panel);
|
||||
return path.textContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* get link from current (or param) file
|
||||
*
|
||||
* @param currentFile - current file by default
|
||||
*/
|
||||
export const getCurrentPath = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const [element] = DOM.getByTag('a', current);
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
return parseHrefAttribute(prefix, element.getAttribute('href'));
|
||||
};
|
||||
|
||||
/**
|
||||
* get current direcotory name
|
||||
*/
|
||||
export const getCurrentDirName = () => {
|
||||
const href = DOM
|
||||
.getCurrentDirPath()
|
||||
.replace(/\/$/, '');
|
||||
|
||||
const substr = href.substr(href, href.lastIndexOf('/'));
|
||||
|
||||
return href.replace(`${substr}/`, '') || '/';
|
||||
};
|
||||
|
||||
/**
|
||||
* get current direcotory path
|
||||
*/
|
||||
export const getParentDirPath = (panel) => {
|
||||
const path = DOM.getCurrentDirPath(panel);
|
||||
const dirName = DOM.getCurrentDirName() + '/';
|
||||
const index = path.lastIndexOf(dirName);
|
||||
|
||||
if (path === '/')
|
||||
return path;
|
||||
|
||||
return path.slice(0, index);
|
||||
};
|
||||
|
||||
/**
|
||||
* get not current direcotory path
|
||||
*/
|
||||
export const getNotCurrentDirPath = () => {
|
||||
const panel = DOM.getPanel({
|
||||
active: false,
|
||||
});
|
||||
|
||||
return DOM.getCurrentDirPath(panel);
|
||||
};
|
||||
|
||||
/**
|
||||
* unified way to get current file
|
||||
*
|
||||
* @currentFile
|
||||
*/
|
||||
export const getCurrentFile = () => {
|
||||
return DOM.getByClass(CURRENT_FILE);
|
||||
};
|
||||
|
||||
/**
|
||||
* get current file by name
|
||||
*/
|
||||
export const getCurrentByName = (name, panel = DOM.CurrentInfo.panel) => {
|
||||
const dataName = 'js-file-' + btoa(encodeURI(encodeNBSP(name)));
|
||||
return DOM.getByDataName(dataName, panel);
|
||||
};
|
||||
|
||||
/**
|
||||
* private function thet unset currentfile
|
||||
*
|
||||
* @currentFile
|
||||
*/
|
||||
function unsetCurrentFile(currentFile) {
|
||||
const is = DOM.isCurrentFile(currentFile);
|
||||
|
||||
if (!is)
|
||||
return;
|
||||
|
||||
currentFile.classList.remove(CURRENT_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* unified way to set current file
|
||||
*/
|
||||
export const setCurrentFile = (currentFile, options) => {
|
||||
const o = options;
|
||||
const currentFileWas = DOM.getCurrentFile();
|
||||
|
||||
if (!currentFile)
|
||||
return DOM;
|
||||
|
||||
let pathWas = '';
|
||||
|
||||
if (currentFileWas) {
|
||||
pathWas = DOM.getCurrentDirPath();
|
||||
unsetCurrentFile(currentFileWas);
|
||||
}
|
||||
|
||||
currentFile.classList.add(CURRENT_FILE);
|
||||
|
||||
const path = DOM.getCurrentDirPath();
|
||||
const name = CloudCmd.config('name');
|
||||
|
||||
if (path !== pathWas) {
|
||||
DOM.setTitle(getTitle({
|
||||
name,
|
||||
path,
|
||||
}));
|
||||
|
||||
/* history could be present
|
||||
* but it should be false
|
||||
* to prevent default behavior
|
||||
*/
|
||||
if (!o || o.history) {
|
||||
const historyPath = path === '/' ? path : FS + path;
|
||||
DOM.setHistory(historyPath, null, historyPath);
|
||||
}
|
||||
}
|
||||
|
||||
/* scrolling to current file */
|
||||
const CENTER = true;
|
||||
|
||||
DOM.scrollIntoViewIfNeeded(currentFile, CENTER);
|
||||
|
||||
CloudCmd.emit('current-file', currentFile);
|
||||
CloudCmd.emit('current-path', path);
|
||||
CloudCmd.emit('current-name', DOM.getCurrentName(currentFile));
|
||||
|
||||
return DOM;
|
||||
};
|
||||
|
||||
export const setCurrentByName = (name) => {
|
||||
const current = DOM.getCurrentByName(name);
|
||||
return DOM.setCurrentFile(current);
|
||||
};
|
||||
|
||||
/*
|
||||
* set current file by position
|
||||
*
|
||||
* @param layer - element
|
||||
* @param - position {x, y}
|
||||
*/
|
||||
export const getCurrentByPosition = ({x, y}) => {
|
||||
const element = document.elementFromPoint(x, y);
|
||||
|
||||
const getEl = (el) => {
|
||||
const {tagName} = el;
|
||||
const isChild = /A|SPAN|LI/.test(tagName);
|
||||
|
||||
if (!isChild)
|
||||
return null;
|
||||
|
||||
if (tagName === 'A')
|
||||
return el.parentElement.parentElement;
|
||||
|
||||
if (tagName === 'SPAN')
|
||||
return el.parentElement;
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
const el = getEl(element);
|
||||
|
||||
if (el && el.tagName !== 'LI')
|
||||
return null;
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
/**
|
||||
* current file check
|
||||
*
|
||||
* @param currentFile
|
||||
*/
|
||||
export const isCurrentFile = (currentFile) => {
|
||||
if (!currentFile)
|
||||
return false;
|
||||
|
||||
return DOM.isContainClass(currentFile, CURRENT_FILE);
|
||||
};
|
||||
|
||||
/**
|
||||
* set title or create title element
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
export const setTitle = (name) => {
|
||||
if (!Title)
|
||||
Title = DOM.getByTag('title')[0] || createElement('title', {
|
||||
innerHTML: name,
|
||||
parent: document.head,
|
||||
});
|
||||
|
||||
Title.textContent = name;
|
||||
|
||||
return DOM;
|
||||
};
|
||||
|
||||
/**
|
||||
* check is current file is a directory
|
||||
*
|
||||
* @param currentFile
|
||||
*/
|
||||
export const isCurrentIsDir = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const path = DOM.getCurrentPath(current);
|
||||
const fileType = DOM.getCurrentType(current);
|
||||
|
||||
const isZip = path.endsWith('.zip');
|
||||
const isDir = /^directory(-link)?/.test(fileType);
|
||||
|
||||
return isDir || isZip;
|
||||
};
|
||||
|
||||
export const getCurrentType = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const el = DOM.getByDataName('js-type', current);
|
||||
const type = el.className
|
||||
.split(' ')
|
||||
.pop();
|
||||
|
||||
return type;
|
||||
};
|
||||
307
client/dom/current-file.spec.mjs
Normal file
307
client/dom/current-file.spec.mjs
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import {create} from 'auto-globals';
|
||||
import wraptile from 'wraptile';
|
||||
import * as currentFile from './current-file.mjs';
|
||||
|
||||
const id = (a) => a;
|
||||
|
||||
const returns = wraptile(id);
|
||||
const {_CURRENT_FILE} = currentFile;
|
||||
|
||||
test('current-file: setCurrentName: setAttribute', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const current = create();
|
||||
const {setAttribute} = current;
|
||||
|
||||
currentFile.setCurrentName('hello', current);
|
||||
|
||||
t.calledWith(setAttribute, ['data-name', 'js-file-aGVsbG8='], 'should call setAttribute');
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: setCurrentName: setAttribute: cyrillic', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const current = create();
|
||||
const {setAttribute} = current;
|
||||
|
||||
currentFile.setCurrentName('ай', current);
|
||||
|
||||
t.calledWith(setAttribute, ['data-name', 'js-file-JUQwJUIwJUQwJUI5'], 'should call setAttribute');
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: getCurrentName', (t) => {
|
||||
const current = create();
|
||||
current.getAttribute.returns('js-file-Ymlu');
|
||||
|
||||
const result = currentFile.getCurrentName(current);
|
||||
|
||||
t.equal(result, 'bin');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: emit', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
const emit = stub();
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd({
|
||||
emit,
|
||||
});
|
||||
|
||||
const current = create();
|
||||
|
||||
currentFile.setCurrentName('hello', current);
|
||||
|
||||
t.calledWith(emit, ['current-file', current], 'should call emit');
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: setCurrentName: return', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
const link = {};
|
||||
|
||||
globalThis.DOM = getDOM({
|
||||
link,
|
||||
});
|
||||
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const current = create();
|
||||
|
||||
const result = currentFile.setCurrentName('hello', current);
|
||||
|
||||
t.equal(result, link, 'should return link');
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: getParentDirPath: result', (t) => {
|
||||
const {DOM} = globalThis;
|
||||
|
||||
const getCurrentDirPath = returns('/D/Films/+++favorite films/');
|
||||
const getCurrentDirName = returns('+++favorite films');
|
||||
|
||||
globalThis.DOM = getDOM({
|
||||
getCurrentDirPath,
|
||||
getCurrentDirName,
|
||||
});
|
||||
|
||||
const result = currentFile.getParentDirPath();
|
||||
const expected = '/D/Films/';
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
|
||||
t.equal(result, expected, 'should return parent dir path');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: isCurrentFile: no', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const result = currentFile.isCurrentFile();
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.notOk(result);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: isCurrentFile', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
const isContainClass = stub();
|
||||
|
||||
globalThis.DOM = getDOM({
|
||||
isContainClass,
|
||||
});
|
||||
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const current = {};
|
||||
currentFile.isCurrentFile(current);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(isContainClass, [current, _CURRENT_FILE], 'should call isContainClass');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: getCurrentType', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const {getByDataName} = globalThis.DOM;
|
||||
|
||||
getByDataName.returns({
|
||||
className: 'mini-icon directory',
|
||||
});
|
||||
|
||||
const current = create();
|
||||
|
||||
currentFile.getCurrentType(current);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(getByDataName, ['js-type', current]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: isCurrentIsDir: getCurrentType', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const {getCurrentType} = globalThis.DOM;
|
||||
|
||||
const current = create();
|
||||
|
||||
currentFile.isCurrentIsDir(current);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(getCurrentType, [current]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: isCurrentIsDir: directory', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
globalThis.DOM = getDOM({
|
||||
getCurrentType: stub().returns('directory'),
|
||||
});
|
||||
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const current = create();
|
||||
|
||||
const result = currentFile.isCurrentIsDir(current);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.ok(result);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: isCurrentIsDir: directory-link', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
globalThis.DOM = getDOM({
|
||||
getCurrentType: stub().returns('directory-link'),
|
||||
});
|
||||
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const current = create();
|
||||
|
||||
const result = currentFile.isCurrentIsDir(current);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.ok(result);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: isCurrentIsDir: file', (t) => {
|
||||
const {DOM, CloudCmd} = globalThis;
|
||||
|
||||
globalThis.DOM = getDOM({
|
||||
getCurrentType: stub().returns('file'),
|
||||
});
|
||||
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const current = create();
|
||||
|
||||
const result = currentFile.isCurrentIsDir(current);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.notOk(result);
|
||||
t.end();
|
||||
});
|
||||
|
||||
const getCloudCmd = ({emit} = {}) => ({
|
||||
prefix: '',
|
||||
emit: emit || stub(),
|
||||
});
|
||||
|
||||
test('current-file: parseNameAttribute', (t) => {
|
||||
const result = currentFile._parseNameAttribute('js-file-aGVsbG8mbmJzcDt3b3JsZA==');
|
||||
const expected = 'hello\xa0world';
|
||||
|
||||
t.equal(result, expected);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('current-file: parseHrefAttribute', (t) => {
|
||||
const prefix = '/api/v1';
|
||||
const result = currentFile._parseHrefAttribute(prefix, '/api/v1/fs/hello world');
|
||||
const expected = '/hello\xa0world';
|
||||
|
||||
t.equal(result, expected);
|
||||
t.end();
|
||||
});
|
||||
|
||||
function getDOM(overrides = {}) {
|
||||
const {
|
||||
link = {},
|
||||
getCurrentDirPath = stub(),
|
||||
getCurrentDirName = stub(),
|
||||
getByDataName = stub(),
|
||||
isContainClass = stub(),
|
||||
getCurrentType = stub(),
|
||||
getCurrentPath = stub().returns(''),
|
||||
} = overrides;
|
||||
|
||||
return {
|
||||
getCurrentDirPath,
|
||||
getCurrentDirName,
|
||||
getCurrentPath,
|
||||
getByDataName,
|
||||
isContainClass,
|
||||
getCurrentType,
|
||||
CurrentInfo: {
|
||||
link,
|
||||
dirPath: '/',
|
||||
},
|
||||
};
|
||||
}
|
||||
26
client/dom/dialog.js
Normal file
26
client/dom/dialog.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
const {tryToCatch} = require('try-to-catch');
|
||||
|
||||
const {
|
||||
alert,
|
||||
prompt,
|
||||
confirm,
|
||||
progress,
|
||||
} = require('smalltalk');
|
||||
|
||||
const title = 'Cloud Commander';
|
||||
|
||||
module.exports.alert = (...a) => alert(title, ...a, {
|
||||
cancel: false,
|
||||
});
|
||||
|
||||
module.exports.prompt = (...a) => tryToCatch(prompt, title, ...a);
|
||||
module.exports.confirm = (...a) => tryToCatch(confirm, title, ...a);
|
||||
module.exports.progress = (...a) => progress(title, ...a);
|
||||
|
||||
module.exports.alert.noFiles = () => {
|
||||
return alert(title, 'No files selected!', {
|
||||
cancel: false,
|
||||
});
|
||||
};
|
||||
70
client/dom/directory.js
Normal file
70
client/dom/directory.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
const philip = require('philip');
|
||||
|
||||
const Images = require('./images.mjs');
|
||||
const {FS} = require('../../common/cloudfunc.mjs');
|
||||
const DOM = require('.');
|
||||
const Dialog = require('./dialog');
|
||||
|
||||
const {getCurrentDirPath: getPathWhenRootEmpty} = DOM;
|
||||
|
||||
module.exports = (items) => {
|
||||
if (items.length)
|
||||
Images.show('top');
|
||||
|
||||
const entries = Array
|
||||
.from(items)
|
||||
.map((item) => item.webkitGetAsEntry());
|
||||
|
||||
const dirPath = getPathWhenRootEmpty();
|
||||
const path = dirPath.replace(/\/$/, '');
|
||||
|
||||
const progress = Dialog.progress('Uploading...');
|
||||
|
||||
progress.catch(() => {
|
||||
Dialog.alert('Upload aborted');
|
||||
uploader.abort();
|
||||
});
|
||||
|
||||
const uploader = philip(entries, (type, name, data, i, n, callback) => {
|
||||
const {prefixURL} = CloudCmd;
|
||||
const full = prefixURL + FS + path + name;
|
||||
|
||||
let upload;
|
||||
switch(type) {
|
||||
case 'file':
|
||||
upload = uploadFile(full, data);
|
||||
break;
|
||||
|
||||
case 'directory':
|
||||
upload = uploadDir(full);
|
||||
break;
|
||||
}
|
||||
|
||||
upload.on('end', callback);
|
||||
|
||||
upload.on('progress', (count) => {
|
||||
const current = percent(i, n);
|
||||
const next = percent(i + 1, n);
|
||||
const max = next - current;
|
||||
const value = current + percent(count, 100, max);
|
||||
|
||||
progress.setProgress(value);
|
||||
});
|
||||
});
|
||||
|
||||
uploader.on('error', (error) => {
|
||||
Dialog.alert(error);
|
||||
uploader.abort();
|
||||
});
|
||||
|
||||
uploader.on('end', CloudCmd.refresh);
|
||||
};
|
||||
|
||||
const percent = (i, n, per = 100) => Math.round(i * per / n);
|
||||
|
||||
const uploadFile = (url, data) => DOM.load.put(url, data);
|
||||
|
||||
const uploadDir = (url) => DOM.load.put(`${url}?dir`);
|
||||
83
client/dom/dom-tree.js
Normal file
83
client/dom/dom-tree.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
'use strict';
|
||||
|
||||
const currify = require('currify');
|
||||
|
||||
const DOM = module.exports;
|
||||
|
||||
/**
|
||||
* check class of element
|
||||
*
|
||||
* @param element
|
||||
* @param className
|
||||
*/
|
||||
const isContainClass = (element, className) => {
|
||||
if (!element)
|
||||
throw Error('element could not be empty!');
|
||||
|
||||
if (!className)
|
||||
throw Error('className could not be empty!');
|
||||
|
||||
if (Array.isArray(className))
|
||||
return className.some(currify(
|
||||
isContainClass,
|
||||
element,
|
||||
));
|
||||
|
||||
const {classList} = element;
|
||||
|
||||
return classList.contains(className);
|
||||
};
|
||||
|
||||
module.exports.isContainClass = isContainClass;
|
||||
/**
|
||||
* Function search element by tag
|
||||
* @param tag - className
|
||||
* @param element - element
|
||||
*/
|
||||
module.exports.getByTag = (tag, element = document) => {
|
||||
return element.getElementsByTagName(tag);
|
||||
};
|
||||
|
||||
/**
|
||||
* Function search element by id
|
||||
* @param Id - id
|
||||
*/
|
||||
module.exports.getById = (id, element = document) => {
|
||||
return element.querySelector(`#${id}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Function search first element by class name
|
||||
* @param className - className
|
||||
* @param element - element
|
||||
*/
|
||||
module.exports.getByClass = (className, element = document) => DOM.getByClassAll(className, element)[0];
|
||||
|
||||
module.exports.getByDataName = (attribute, element = document) => {
|
||||
const selector = '[' + 'data-name="' + attribute + '"]';
|
||||
return element.querySelector(selector);
|
||||
};
|
||||
|
||||
/**
|
||||
* Function search element by class name
|
||||
* @param pClass - className
|
||||
* @param element - element
|
||||
*/
|
||||
module.exports.getByClassAll = (className, element) => {
|
||||
return (element || document).getElementsByClassName(className);
|
||||
};
|
||||
|
||||
/**
|
||||
* add class=hidden to element
|
||||
*
|
||||
* @param element
|
||||
*/
|
||||
module.exports.hide = (element) => {
|
||||
element.classList.add('hidden');
|
||||
return DOM;
|
||||
};
|
||||
|
||||
module.exports.show = (element) => {
|
||||
element.classList.remove('hidden');
|
||||
return DOM;
|
||||
};
|
||||
43
client/dom/dom-tree.spec.js
Normal file
43
client/dom/dom-tree.spec.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('supertape');
|
||||
const {create} = require('auto-globals');
|
||||
const {tryCatch} = require('try-catch');
|
||||
|
||||
const {isContainClass} = require('./dom-tree');
|
||||
|
||||
test('dom: isContainClass: no element', (t) => {
|
||||
const [e] = tryCatch(isContainClass);
|
||||
|
||||
t.equal(e.message, 'element could not be empty!', 'should throw when no element');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: isContainClass: no className', (t) => {
|
||||
const [e] = tryCatch(isContainClass, {});
|
||||
|
||||
t.equal(e.message, 'className could not be empty!', 'should throw when no element');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: isContainClass: contains', (t) => {
|
||||
const el = create();
|
||||
const {contains} = el.classList;
|
||||
|
||||
const className = 'hello';
|
||||
isContainClass(el, className);
|
||||
|
||||
t.calledWith(contains, [className], 'should call contains');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: isContainClass: contains: array', (t) => {
|
||||
const el = create();
|
||||
const {contains} = el.classList;
|
||||
|
||||
const className = 'hello';
|
||||
isContainClass(el, ['world', className, 'hello']);
|
||||
|
||||
t.calledWith(contains, [className], 'should call contains');
|
||||
t.end();
|
||||
});
|
||||
17
client/dom/events/event-store.js
Normal file
17
client/dom/events/event-store.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
'use strict';
|
||||
|
||||
let list = [];
|
||||
|
||||
module.exports.add = (el, name, fn) => {
|
||||
list.push([
|
||||
el,
|
||||
name,
|
||||
fn,
|
||||
]);
|
||||
};
|
||||
|
||||
module.exports.clear = () => {
|
||||
list = [];
|
||||
};
|
||||
|
||||
module.exports.get = () => list;
|
||||
39
client/dom/events/event-store.spec.js
Normal file
39
client/dom/events/event-store.spec.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('supertape');
|
||||
const eventStore = require('./event-store');
|
||||
|
||||
test('event-store: get', (t) => {
|
||||
const el = {};
|
||||
const name = 'click';
|
||||
const fn = () => {};
|
||||
|
||||
eventStore.add(el, name, fn);
|
||||
const result = eventStore.get();
|
||||
|
||||
const expected = [
|
||||
[
|
||||
el,
|
||||
name,
|
||||
fn,
|
||||
],
|
||||
];
|
||||
|
||||
t.deepEqual(result, expected);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('event-store: clear', (t) => {
|
||||
const el = {};
|
||||
const name = 'click';
|
||||
const fn = () => {};
|
||||
|
||||
eventStore.add(el, name, fn);
|
||||
eventStore.clear();
|
||||
|
||||
const result = eventStore.get();
|
||||
const expected = [];
|
||||
|
||||
t.deepEqual(result, expected);
|
||||
t.end();
|
||||
});
|
||||
203
client/dom/events/index.mjs
Normal file
203
client/dom/events/index.mjs
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
import itype from 'itype';
|
||||
import EventStore from './event-store.js';
|
||||
|
||||
/**
|
||||
* safe add event listener
|
||||
*
|
||||
* @param type
|
||||
* @param element - document by default
|
||||
* @param listener
|
||||
*/
|
||||
export const add = (type, element, listener) => {
|
||||
checkType(type);
|
||||
|
||||
parseArgs(type, element, listener, (element, args) => {
|
||||
const [name, fn, options] = args;
|
||||
|
||||
element.addEventListener(name, fn, options);
|
||||
EventStore.add(element, name, fn);
|
||||
});
|
||||
|
||||
return Events;
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add event listener
|
||||
*
|
||||
* @param type
|
||||
* @param listener
|
||||
* @param element - document by default
|
||||
*/
|
||||
export const addOnce = (type, element, listener) => {
|
||||
const once = (event) => {
|
||||
Events.remove(type, element, once);
|
||||
listener(event);
|
||||
};
|
||||
|
||||
if (!listener) {
|
||||
listener = element;
|
||||
element = null;
|
||||
}
|
||||
|
||||
add(type, element, once);
|
||||
|
||||
return Events;
|
||||
};
|
||||
|
||||
/**
|
||||
* safe remove event listener
|
||||
*
|
||||
* @param type
|
||||
* @param listener
|
||||
* @param element - document by default
|
||||
*/
|
||||
export const remove = (type, element, listener) => {
|
||||
checkType(type);
|
||||
|
||||
parseArgs(type, element, listener, (element, args) => {
|
||||
element.removeEventListener(...args);
|
||||
});
|
||||
|
||||
return Events;
|
||||
};
|
||||
|
||||
/**
|
||||
* remove all added event listeners
|
||||
*/
|
||||
export const removeAll = () => {
|
||||
const events = EventStore.get();
|
||||
|
||||
for (const [el, name, fn] of events)
|
||||
el.removeEventListener(name, fn);
|
||||
|
||||
EventStore.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add event keydown listener
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
export const addKey = function(...args) {
|
||||
return add('keydown', ...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe remove event click listener
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
export const rmKey = function(...args) {
|
||||
return Events.remove('keydown', ...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add event click listener
|
||||
*/
|
||||
export const addClick = function(...args) {
|
||||
return Events.add('click', ...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe remove event click listener
|
||||
*/
|
||||
export const rmClick = function(...args) {
|
||||
return remove('click', ...args);
|
||||
};
|
||||
|
||||
export const addContextMenu = function(...args) {
|
||||
return add('contextmenu', ...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add load listener
|
||||
*/
|
||||
export const addLoad = function(...args) {
|
||||
return add('load', ...args);
|
||||
};
|
||||
|
||||
function checkType(type) {
|
||||
if (!type)
|
||||
throw Error('type could not be empty!');
|
||||
}
|
||||
|
||||
const getEventOptions = (eventName) => {
|
||||
if (eventName !== 'touchstart')
|
||||
return false;
|
||||
|
||||
return {
|
||||
passive: true,
|
||||
};
|
||||
};
|
||||
|
||||
function parseArgs(eventName, element, listener, callback) {
|
||||
let isFunc;
|
||||
const args = [
|
||||
eventName,
|
||||
element,
|
||||
listener,
|
||||
callback,
|
||||
];
|
||||
|
||||
const EVENT_NAME = 1;
|
||||
const ELEMENT = 0;
|
||||
const type = itype(eventName);
|
||||
|
||||
switch(type) {
|
||||
default:
|
||||
if (!type.endsWith('element'))
|
||||
throw Error(`unknown eventName: ${type}`);
|
||||
|
||||
parseArgs(args[EVENT_NAME], args[ELEMENT], listener, callback);
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
isFunc = itype.function(element);
|
||||
|
||||
if (isFunc) {
|
||||
listener = element;
|
||||
element = null;
|
||||
}
|
||||
|
||||
if (!element)
|
||||
element = window;
|
||||
|
||||
callback(element, [
|
||||
eventName,
|
||||
listener,
|
||||
getEventOptions(eventName),
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
|
||||
for (const name of eventName) {
|
||||
parseArgs(name, element, listener, callback);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
|
||||
for (const name of Object.keys(eventName)) {
|
||||
const eventListener = eventName[name];
|
||||
|
||||
parseArgs(name, element, eventListener, callback);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const Events = {
|
||||
add,
|
||||
addClick,
|
||||
addContextMenu,
|
||||
addKey,
|
||||
addLoad,
|
||||
addOnce,
|
||||
remove,
|
||||
removeAll,
|
||||
rmClick,
|
||||
rmKey,
|
||||
};
|
||||
139
client/dom/files.js
Normal file
139
client/dom/files.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
const itype = require('itype');
|
||||
const {promisify} = require('es6-promisify');
|
||||
|
||||
const load = require('./load');
|
||||
const RESTful = require('./rest');
|
||||
|
||||
const Promises = {};
|
||||
const FILES_JSON = 'config|modules';
|
||||
const FILES_HTML = 'file|path|link|pathLink|media';
|
||||
const FILES_HTML_ROOT = 'view/media-tmpl|config-tmpl|upload';
|
||||
const DIR_HTML = '/tmpl/';
|
||||
const DIR_HTML_FS = `${DIR_HTML}fs/`;
|
||||
const DIR_JSON = '/json/';
|
||||
const timeout = getTimeoutOnce(2000);
|
||||
|
||||
module.exports.get = getFile;
|
||||
|
||||
function getFile(name) {
|
||||
const type = itype(name);
|
||||
check(name);
|
||||
|
||||
if (type === 'string')
|
||||
return getModule(name);
|
||||
|
||||
if (type === 'array')
|
||||
return Promise.all(name.map(getFile));
|
||||
}
|
||||
|
||||
function check(name) {
|
||||
if (!name)
|
||||
throw Error('name could not be empty!');
|
||||
}
|
||||
|
||||
function getModule(name) {
|
||||
const regExpHTML = RegExp(FILES_HTML + '|' + FILES_HTML_ROOT);
|
||||
const regExpJSON = RegExp(FILES_JSON);
|
||||
|
||||
const isHTML = regExpHTML.test(name);
|
||||
const isJSON = regExpJSON.test(name);
|
||||
|
||||
if (!isHTML && !isJSON)
|
||||
return showError(name);
|
||||
|
||||
if (name === 'config')
|
||||
return getConfig();
|
||||
|
||||
const path = getPath(name, isHTML, isJSON);
|
||||
|
||||
return getSystemFile(path);
|
||||
}
|
||||
|
||||
function getPath(name, isHTML, isJSON) {
|
||||
let path;
|
||||
const regExp = RegExp(FILES_HTML_ROOT);
|
||||
const isRoot = regExp.test(name);
|
||||
|
||||
if (isHTML) {
|
||||
if (isRoot)
|
||||
path = DIR_HTML + name.replace('-tmpl', '');
|
||||
else
|
||||
path = DIR_HTML_FS + name;
|
||||
|
||||
path += '.hbs';
|
||||
} else if (isJSON) {
|
||||
path = DIR_JSON + name + '.json';
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function showError(name) {
|
||||
const str = `Wrong file name: ${name}`;
|
||||
const error = Error(str);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const getSystemFile = promisify((file, callback) => {
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
if (!Promises[file])
|
||||
Promises[file] = new Promise((success, error) => {
|
||||
const url = prefix + file;
|
||||
|
||||
load.ajax({
|
||||
url,
|
||||
success,
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
Promises[file].then((data) => {
|
||||
callback(null, data);
|
||||
}, (error) => {
|
||||
Promises[file] = null;
|
||||
callback(error);
|
||||
});
|
||||
});
|
||||
|
||||
const getConfig = async () => {
|
||||
let is;
|
||||
|
||||
if (!Promises.config)
|
||||
Promises.config = () => {
|
||||
is = true;
|
||||
return RESTful.Config.read();
|
||||
};
|
||||
|
||||
const [, data] = await Promises.config();
|
||||
|
||||
if (data)
|
||||
is = false;
|
||||
|
||||
timeout(() => {
|
||||
if (!is)
|
||||
Promises.config = null;
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
function getTimeoutOnce(time) {
|
||||
let is;
|
||||
|
||||
return (callback) => {
|
||||
if (is)
|
||||
return;
|
||||
|
||||
is = true;
|
||||
|
||||
setTimeout(() => {
|
||||
is = false;
|
||||
callback();
|
||||
}, time);
|
||||
};
|
||||
}
|
||||
149
client/dom/images.mjs
Normal file
149
client/dom/images.mjs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/* global DOM */
|
||||
import createElement from '@cloudcmd/create-element';
|
||||
|
||||
const LOADING = 'loading';
|
||||
const HIDDEN = 'hidden';
|
||||
const ERROR = 'error';
|
||||
|
||||
const getLoadingType = () => isSVG() ? '-svg' : '-gif';
|
||||
|
||||
export const get = getElement;
|
||||
|
||||
/**
|
||||
* check SVG SMIL animation support
|
||||
*/
|
||||
function isSVG() {
|
||||
const createNS = document.createElementNS;
|
||||
const SVG_URL = 'http://www.w3.org/2000/svg';
|
||||
|
||||
if (!createNS)
|
||||
return false;
|
||||
|
||||
const create = createNS.bind(document);
|
||||
const svgNode = create(SVG_URL, 'animate');
|
||||
const name = svgNode.toString();
|
||||
|
||||
return /SVGAnimate/.test(name);
|
||||
}
|
||||
|
||||
function getElement() {
|
||||
return createElement('span', {
|
||||
id: 'js-status-image',
|
||||
className: 'icon',
|
||||
dataName: 'progress',
|
||||
notAppend: true,
|
||||
});
|
||||
}
|
||||
|
||||
/* Функция создаёт картинку загрузки */
|
||||
export const loading = () => {
|
||||
const element = getElement();
|
||||
const {classList} = element;
|
||||
const loadingImage = LOADING + getLoadingType();
|
||||
|
||||
classList.add(LOADING, loadingImage);
|
||||
classList.remove(ERROR, HIDDEN);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
/* Функция создаёт картинку ошибки загрузки */
|
||||
export const error = () => {
|
||||
const element = getElement();
|
||||
const {classList} = element;
|
||||
const loadingImage = LOADING + getLoadingType();
|
||||
|
||||
classList.add(ERROR);
|
||||
classList.remove(HIDDEN, LOADING, loadingImage);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
show.load = show;
|
||||
show.error = (text) => {
|
||||
const image = Images.error();
|
||||
|
||||
DOM.show(image);
|
||||
image.title = text;
|
||||
|
||||
return image;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function shows loading spinner
|
||||
* position = {top: true};
|
||||
*/
|
||||
export function show(position, panel) {
|
||||
const image = Images.loading();
|
||||
const parent = image.parentElement;
|
||||
const refreshButton = DOM.getRefreshButton(panel);
|
||||
|
||||
let current;
|
||||
|
||||
if (position === 'top') {
|
||||
current = refreshButton.parentElement;
|
||||
} else {
|
||||
current = DOM.getCurrentFile();
|
||||
|
||||
if (current)
|
||||
current = DOM.getByDataName('js-name', current);
|
||||
else
|
||||
current = refreshButton.parentElement;
|
||||
}
|
||||
|
||||
if (!parent || parent && parent !== current)
|
||||
current.appendChild(image);
|
||||
|
||||
DOM.show(image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* hide load image
|
||||
*/
|
||||
export const hide = () => {
|
||||
const element = Images.get();
|
||||
|
||||
DOM.hide(element);
|
||||
|
||||
return Images;
|
||||
};
|
||||
|
||||
export const setProgress = (value, title) => {
|
||||
const DATA = 'data-progress';
|
||||
const element = Images.get();
|
||||
|
||||
if (!element)
|
||||
return Images;
|
||||
|
||||
element.setAttribute(DATA, `${value}%`);
|
||||
|
||||
if (title)
|
||||
element.title = title;
|
||||
|
||||
return Images;
|
||||
};
|
||||
|
||||
export const clearProgress = () => {
|
||||
const DATA = 'data-progress';
|
||||
const element = Images.get();
|
||||
|
||||
if (!element)
|
||||
return Images;
|
||||
|
||||
element.setAttribute(DATA, '');
|
||||
element.title = '';
|
||||
|
||||
return Images;
|
||||
};
|
||||
|
||||
const Images = {
|
||||
clearProgress,
|
||||
setProgress,
|
||||
show,
|
||||
hide,
|
||||
get,
|
||||
error,
|
||||
loading,
|
||||
};
|
||||
840
client/dom/index.js
Normal file
840
client/dom/index.js
Normal file
|
|
@ -0,0 +1,840 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
const Util = require('../../common/util');
|
||||
|
||||
const Images = require('./images.mjs');
|
||||
const RESTful = require('./rest');
|
||||
const Storage = require('./storage');
|
||||
const renameCurrent = require('./operations/rename-current');
|
||||
|
||||
const CurrentFile = require('./current-file.mjs');
|
||||
const DOMTree = require('./dom-tree');
|
||||
|
||||
const Cmd = module.exports;
|
||||
const DOM = {
|
||||
...DOMTree,
|
||||
...CurrentFile,
|
||||
...Cmd,
|
||||
};
|
||||
|
||||
const CurrentInfo = {};
|
||||
|
||||
DOM.Images = Images;
|
||||
DOM.load = require('./load');
|
||||
DOM.Files = require('./files');
|
||||
DOM.RESTful = RESTful;
|
||||
DOM.IO = require('./io');
|
||||
DOM.Storage = Storage;
|
||||
DOM.Dialog = require('./dialog');
|
||||
DOM.CurrentInfo = CurrentInfo;
|
||||
|
||||
module.exports = DOM;
|
||||
|
||||
DOM.uploadDirectory = require('./directory');
|
||||
DOM.Buffer = require('./buffer.mjs');
|
||||
DOM.Events = require('#dom/events');
|
||||
|
||||
const loadRemote = require('./load-remote');
|
||||
const selectByPattern = require('./select-by-pattern');
|
||||
const isString = (a) => typeof a === 'string';
|
||||
const SELECTED_FILE = 'selected-file';
|
||||
|
||||
const TabPanel = {
|
||||
'js-left': null,
|
||||
'js-right': null,
|
||||
};
|
||||
|
||||
module.exports.loadRemote = (name, options, callback) => {
|
||||
loadRemote(name, options, callback);
|
||||
return DOM;
|
||||
};
|
||||
|
||||
module.exports.loadSocket = (callback) => {
|
||||
DOM.loadRemote('socket', {
|
||||
name: 'io',
|
||||
}, callback);
|
||||
|
||||
return DOM;
|
||||
};
|
||||
|
||||
/**
|
||||
* create new folder
|
||||
*
|
||||
*/
|
||||
module.exports.promptNewDir = async function() {
|
||||
await promptNew('directory');
|
||||
};
|
||||
|
||||
/**
|
||||
* create new file
|
||||
*
|
||||
* @typeName
|
||||
* @type
|
||||
*/
|
||||
module.exports.promptNewFile = async () => {
|
||||
await promptNew('file');
|
||||
};
|
||||
|
||||
async function promptNew(typeName) {
|
||||
const {Dialog} = DOM;
|
||||
const dir = DOM.getCurrentDirPath();
|
||||
const msg = `New ${typeName}` || 'File';
|
||||
|
||||
const getName = () => {
|
||||
const name = DOM.getCurrentName();
|
||||
|
||||
if (name === '..')
|
||||
return '';
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
const name = getName();
|
||||
const [cancel, currentName] = await Dialog.prompt(msg, name);
|
||||
|
||||
if (cancel)
|
||||
return;
|
||||
|
||||
const path = `${dir}${currentName}`;
|
||||
|
||||
if (typeName === 'directory')
|
||||
await RESTful.createDirectory(path);
|
||||
else
|
||||
await RESTful.write(path);
|
||||
|
||||
await CloudCmd.refresh({
|
||||
currentName,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get current direcotory name
|
||||
*/
|
||||
module.exports.getCurrentDirName = () => {
|
||||
const href = DOM
|
||||
.getCurrentDirPath()
|
||||
.replace(/\/$/, '');
|
||||
|
||||
const substr = href.substr(href, href.lastIndexOf('/'));
|
||||
|
||||
return href.replace(`${substr}/`, '') || '/';
|
||||
};
|
||||
|
||||
/**
|
||||
* get current direcotory path
|
||||
*/
|
||||
module.exports.getParentDirPath = (panel) => {
|
||||
const path = DOM.getCurrentDirPath(panel);
|
||||
const dirName = DOM.getCurrentDirName() + '/';
|
||||
const index = path.lastIndexOf(dirName);
|
||||
|
||||
if (path !== '/')
|
||||
return path.slice(0, index);
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* get not current direcotory path
|
||||
*/
|
||||
module.exports.getNotCurrentDirPath = () => {
|
||||
const panel = DOM.getPanel({
|
||||
active: false,
|
||||
});
|
||||
|
||||
return DOM.getCurrentDirPath(panel);
|
||||
};
|
||||
|
||||
/**
|
||||
* unified way to get selected files
|
||||
*
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.getSelectedFiles = () => {
|
||||
const panel = DOM.getPanel();
|
||||
const selected = DOM.getByClassAll(SELECTED_FILE, panel);
|
||||
|
||||
return Array.from(selected);
|
||||
};
|
||||
|
||||
/*
|
||||
* unselect all files
|
||||
*/
|
||||
module.exports.unselectFiles = (files) => {
|
||||
files = files || DOM.getSelectedFiles();
|
||||
|
||||
Array
|
||||
.from(files)
|
||||
.forEach(DOM.toggleSelectedFile);
|
||||
};
|
||||
|
||||
/**
|
||||
* get all selected files or current when none selected
|
||||
*
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.getActiveFiles = () => {
|
||||
const current = DOM.getCurrentFile();
|
||||
const files = DOM.getSelectedFiles();
|
||||
const name = DOM.getCurrentName(current);
|
||||
|
||||
if (!files.length && name !== '..')
|
||||
return [current];
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
module.exports.getCurrentDate = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
|
||||
return DOM.getByDataName('js-date', current).textContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* get size
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.getCurrentSize = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
|
||||
/* если это папка - возвращаем слово dir вместо размера*/
|
||||
const size = DOM
|
||||
.getByDataName('js-size', current)
|
||||
.textContent
|
||||
.replace(/^<|>$/g, '');
|
||||
|
||||
return size;
|
||||
};
|
||||
|
||||
/**
|
||||
* get size
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.loadCurrentSize = async (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const query = '?size';
|
||||
const link = DOM.getCurrentPath(current);
|
||||
|
||||
Images.show.load();
|
||||
|
||||
if (name === '..')
|
||||
return;
|
||||
|
||||
const [, size] = await RESTful.read(link + query);
|
||||
|
||||
DOM.setCurrentSize(size, current);
|
||||
Images.hide();
|
||||
|
||||
return current;
|
||||
};
|
||||
|
||||
/**
|
||||
* load hash
|
||||
* @callback
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.loadCurrentHash = async (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const query = '?hash';
|
||||
const link = DOM.getCurrentPath(current);
|
||||
|
||||
const [, data] = await RESTful.read(link + query);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* set size
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.setCurrentSize = (size, currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const sizeElement = DOM.getByDataName('js-size', current);
|
||||
|
||||
sizeElement.textContent = size;
|
||||
};
|
||||
|
||||
/**
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.getCurrentMode = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const mode = DOM.getByDataName('js-mode', current);
|
||||
|
||||
return mode.textContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.getCurrentOwner = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const owner = DOM.getByDataName('js-owner', current);
|
||||
|
||||
return owner.textContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* unified way to get current file content
|
||||
*
|
||||
* @param currentFile
|
||||
*/
|
||||
module.exports.getCurrentData = async (currentFile) => {
|
||||
const {Dialog} = DOM;
|
||||
const Info = DOM.CurrentInfo;
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const path = DOM.getCurrentPath(current);
|
||||
const isDir = DOM.isCurrentIsDir(current);
|
||||
|
||||
if (Info.name === '..') {
|
||||
Dialog.alert.noFiles();
|
||||
return [
|
||||
Error('No Files'),
|
||||
];
|
||||
}
|
||||
|
||||
if (isDir)
|
||||
return await RESTful.read(path);
|
||||
|
||||
const [hashNew, hash] = await DOM.checkStorageHash(path);
|
||||
|
||||
if (!hashNew)
|
||||
return [
|
||||
Error(`Can't get hash of a file`),
|
||||
];
|
||||
|
||||
if (hash === hashNew)
|
||||
return [null, await Storage.get(`${path}-data`)];
|
||||
|
||||
const [e, data] = await RESTful.read(path);
|
||||
|
||||
if (e)
|
||||
return [
|
||||
e,
|
||||
null,
|
||||
];
|
||||
|
||||
const ONE_MEGABYTE = 1024 ** 2 * 1024;
|
||||
const {length} = data;
|
||||
|
||||
if (hash && length < ONE_MEGABYTE)
|
||||
await DOM.saveDataToStorage(path, data, hashNew);
|
||||
|
||||
return [null, data];
|
||||
};
|
||||
|
||||
/**
|
||||
* unified way to get RefreshButton
|
||||
*/
|
||||
module.exports.getRefreshButton = (panel = DOM.getPanel()) => {
|
||||
return DOM.getByDataName('js-refresh', panel);
|
||||
};
|
||||
|
||||
/**
|
||||
* select current file
|
||||
* @param currentFile
|
||||
*/
|
||||
module.exports.selectFile = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
|
||||
current.classList.add(SELECTED_FILE);
|
||||
|
||||
return Cmd;
|
||||
};
|
||||
|
||||
module.exports.unselectFile = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
|
||||
current.classList.remove(SELECTED_FILE);
|
||||
|
||||
return Cmd;
|
||||
};
|
||||
|
||||
module.exports.toggleSelectedFile = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const name = DOM.getCurrentName(current);
|
||||
|
||||
if (name === '..')
|
||||
return Cmd;
|
||||
|
||||
current.classList.toggle(SELECTED_FILE);
|
||||
|
||||
return Cmd;
|
||||
};
|
||||
|
||||
module.exports.toggleAllSelectedFiles = () => {
|
||||
DOM
|
||||
.getAllFiles()
|
||||
.map(DOM.toggleSelectedFile);
|
||||
|
||||
return Cmd;
|
||||
};
|
||||
|
||||
module.exports.selectAllFiles = () => {
|
||||
DOM
|
||||
.getAllFiles()
|
||||
.map(DOM.selectFile);
|
||||
|
||||
return Cmd;
|
||||
};
|
||||
|
||||
module.exports.getAllFiles = () => {
|
||||
const panel = DOM.getPanel();
|
||||
const files = DOM.getFiles(panel);
|
||||
const name = DOM.getCurrentName(files[0]);
|
||||
|
||||
const from = (a) => a === '..' ? 1 : 0;
|
||||
const i = from(name);
|
||||
|
||||
return Array
|
||||
.from(files)
|
||||
.slice(i);
|
||||
};
|
||||
|
||||
/**
|
||||
* open dialog with expand selection
|
||||
*/
|
||||
module.exports.expandSelection = () => {
|
||||
const msg = 'expand';
|
||||
const {files} = CurrentInfo;
|
||||
|
||||
selectByPattern(msg, files);
|
||||
};
|
||||
|
||||
/**
|
||||
* open dialog with shrink selection
|
||||
*/
|
||||
module.exports.shrinkSelection = () => {
|
||||
const msg = 'shrink';
|
||||
const {files} = CurrentInfo;
|
||||
|
||||
selectByPattern(msg, files);
|
||||
};
|
||||
|
||||
/**
|
||||
* setting history wrapper
|
||||
*/
|
||||
module.exports.setHistory = (data, title, url) => {
|
||||
const ret = globalThis.history;
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
url = prefix + url;
|
||||
|
||||
if (ret)
|
||||
history.pushState(data, title, url);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* selected file check
|
||||
*
|
||||
* @param currentFile
|
||||
*/
|
||||
module.exports.isSelected = (selected) => {
|
||||
if (!selected)
|
||||
return false;
|
||||
|
||||
return DOM.isContainClass(selected, SELECTED_FILE);
|
||||
};
|
||||
|
||||
/**
|
||||
* get link from current (or param) file
|
||||
*
|
||||
* @param currentFile - current file by default
|
||||
*/
|
||||
module.exports.getCurrentLink = (currentFile) => {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const link = DOM.getByTag('a', current);
|
||||
|
||||
return link[0];
|
||||
};
|
||||
|
||||
module.exports.getFilenames = (files) => {
|
||||
if (!files)
|
||||
throw Error('AllFiles could not be empty');
|
||||
|
||||
const first = files[0] || DOM.getCurrentFile();
|
||||
const name = DOM.getCurrentName(first);
|
||||
|
||||
const allFiles = Array.from(files);
|
||||
|
||||
if (name === '..')
|
||||
allFiles.shift();
|
||||
|
||||
const names = allFiles.map((current) => {
|
||||
return DOM.getCurrentName(current);
|
||||
});
|
||||
|
||||
return names;
|
||||
};
|
||||
|
||||
/**
|
||||
* check storage hash
|
||||
*/
|
||||
module.exports.checkStorageHash = async (name) => {
|
||||
const nameHash = `${name}-hash`;
|
||||
|
||||
if (!isString(name))
|
||||
throw Error('name should be a string!');
|
||||
|
||||
const [loadHash, storeHash] = await Promise.all([
|
||||
DOM.loadCurrentHash(),
|
||||
Storage.get(nameHash),
|
||||
]);
|
||||
|
||||
return [loadHash, storeHash];
|
||||
};
|
||||
|
||||
/**
|
||||
* save data to storage
|
||||
*
|
||||
* @param name
|
||||
* @param data
|
||||
* @param hash
|
||||
* @param callback
|
||||
*/
|
||||
module.exports.saveDataToStorage = async (name, data, hash) => {
|
||||
const isDir = DOM.isCurrentIsDir();
|
||||
|
||||
if (isDir)
|
||||
return;
|
||||
|
||||
hash = hash || await DOM.loadCurrentHash();
|
||||
|
||||
const nameHash = `${name}-hash`;
|
||||
const nameData = `${name}-data`;
|
||||
|
||||
await Storage.set(nameHash, hash);
|
||||
await Storage.set(nameData, data);
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
||||
module.exports.getFM = () => DOM.getPanel().parentElement;
|
||||
|
||||
module.exports.getPanelPosition = (panel) => {
|
||||
panel = panel || DOM.getPanel();
|
||||
|
||||
return panel.dataset.name.replace('js-', '');
|
||||
};
|
||||
|
||||
module.exports.getCSSVar = (name, {body = document.body} = {}) => {
|
||||
const bodyStyle = getComputedStyle(body);
|
||||
return bodyStyle.getPropertyValue(`--${name}`);
|
||||
};
|
||||
|
||||
/** function getting panel active, or passive
|
||||
* @param options = {active: true}
|
||||
*/
|
||||
module.exports.getPanel = (options) => {
|
||||
let files;
|
||||
let panel;
|
||||
let isLeft;
|
||||
let dataName = 'js-';
|
||||
|
||||
const current = DOM.getCurrentFile();
|
||||
|
||||
if (!current) {
|
||||
panel = DOM.getByDataName('js-left');
|
||||
} else {
|
||||
files = current.parentElement;
|
||||
panel = files.parentElement;
|
||||
isLeft = panel.getAttribute('data-name') === 'js-left';
|
||||
}
|
||||
|
||||
/* if {active : false} getting passive panel */
|
||||
if (options && !options.active) {
|
||||
dataName += isLeft ? 'right' : 'left';
|
||||
panel = DOM.getByDataName(dataName);
|
||||
}
|
||||
|
||||
/* if two panels showed
|
||||
* then always work with passive
|
||||
* panel
|
||||
*/
|
||||
if (globalThis.innerWidth < CloudCmd.MIN_ONE_PANEL_WIDTH)
|
||||
panel = DOM.getByDataName('js-left');
|
||||
|
||||
if (!panel)
|
||||
throw Error('can not find Active Panel!');
|
||||
|
||||
return panel;
|
||||
};
|
||||
|
||||
module.exports.getFiles = (element) => {
|
||||
const files = DOM.getByDataName('js-files', element);
|
||||
return files.children || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* shows panel right or left (or active)
|
||||
*/
|
||||
module.exports.showPanel = (active) => {
|
||||
const panel = DOM.getPanel({
|
||||
active,
|
||||
});
|
||||
|
||||
if (!panel)
|
||||
return false;
|
||||
|
||||
DOM.show(panel);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* hides panel right or left (or active)
|
||||
*/
|
||||
module.exports.hidePanel = (active) => {
|
||||
const panel = DOM.getPanel({
|
||||
active,
|
||||
});
|
||||
|
||||
if (!panel)
|
||||
return false;
|
||||
|
||||
return DOM.hide(panel);
|
||||
};
|
||||
|
||||
/**
|
||||
* remove child of element
|
||||
* @param pChild
|
||||
* @param element
|
||||
*/
|
||||
module.exports.remove = (child, element) => {
|
||||
const parent = element || document.body;
|
||||
|
||||
parent.removeChild(child);
|
||||
|
||||
return DOM;
|
||||
};
|
||||
|
||||
/**
|
||||
* remove current file from file table
|
||||
* @param current
|
||||
*
|
||||
*/
|
||||
module.exports.deleteCurrent = (current) => {
|
||||
if (!current)
|
||||
DOM.getCurrentFile();
|
||||
|
||||
const parent = current?.parentElement;
|
||||
const name = DOM.getCurrentName(current);
|
||||
|
||||
if (current && name !== '..') {
|
||||
const next = current.nextSibling;
|
||||
const prev = current.previousSibling;
|
||||
|
||||
DOM.setCurrentFile(next || prev);
|
||||
parent.removeChild(current);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* remove selected files from file table
|
||||
* @Selected
|
||||
*/
|
||||
module.exports.deleteSelected = (selected) => {
|
||||
selected = selected || DOM.getSelectedFiles();
|
||||
|
||||
if (!selected)
|
||||
return;
|
||||
|
||||
selected.map(DOM.deleteCurrent);
|
||||
};
|
||||
|
||||
/**
|
||||
* rename current file
|
||||
*
|
||||
* @currentFile
|
||||
*/
|
||||
module.exports.renameCurrent = renameCurrent;
|
||||
/**
|
||||
* unified way to scrollIntoViewIfNeeded
|
||||
* (native suporte by webkit only)
|
||||
* @param element
|
||||
* @param center - to scroll as small as possible param should be false
|
||||
*/
|
||||
module.exports.scrollIntoViewIfNeeded = (element, center = false) => {
|
||||
if (!element || !element.scrollIntoViewIfNeeded)
|
||||
return;
|
||||
|
||||
element.scrollIntoViewIfNeeded(center);
|
||||
};
|
||||
|
||||
/* scroll on one page */
|
||||
module.exports.scrollByPages = (element, pPages) => {
|
||||
const ret = element?.scrollByPages && pPages;
|
||||
|
||||
if (ret)
|
||||
element.scrollByPages(pPages);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
module.exports.changePanel = () => {
|
||||
const Info = CurrentInfo;
|
||||
let panel = DOM.getPanel();
|
||||
|
||||
CloudCmd.emit('passive-dir', Info.dirPath);
|
||||
|
||||
const panelPassive = DOM.getPanel({
|
||||
active: false,
|
||||
});
|
||||
|
||||
let name = DOM.getCurrentName();
|
||||
const filesPassive = DOM.getFiles(panelPassive);
|
||||
|
||||
let dataName = panel.getAttribute('data-name');
|
||||
|
||||
TabPanel[dataName] = name;
|
||||
|
||||
panel = panelPassive;
|
||||
dataName = panel.getAttribute('data-name');
|
||||
|
||||
name = TabPanel[dataName];
|
||||
|
||||
let files;
|
||||
let current;
|
||||
|
||||
if (name) {
|
||||
current = DOM.getCurrentByName(name, panel);
|
||||
|
||||
if (current)
|
||||
files = current.parentElement;
|
||||
}
|
||||
|
||||
if (!files || !files.parentElement) {
|
||||
current = DOM.getCurrentByName(name, panel);
|
||||
|
||||
if (!current)
|
||||
[current] = filesPassive;
|
||||
}
|
||||
|
||||
DOM.setCurrentFile(current, {
|
||||
history: true,
|
||||
});
|
||||
|
||||
CloudCmd.emit('active-dir', Info.dirPath);
|
||||
|
||||
return DOM;
|
||||
};
|
||||
|
||||
module.exports.getPackerExt = (type) => {
|
||||
if (type === 'zip')
|
||||
return '.zip';
|
||||
|
||||
return '.tar.gz';
|
||||
};
|
||||
|
||||
module.exports.goToDirectory = async (overrides = {}) => {
|
||||
const {Dialog} = DOM;
|
||||
const {prompt = Dialog.prompt, changeDir = CloudCmd.changeDir} = overrides;
|
||||
|
||||
const msg = 'Go to directory:';
|
||||
const {dirPath} = CurrentInfo;
|
||||
|
||||
const [cancel, path = dirPath] = await prompt(msg, dirPath);
|
||||
|
||||
if (cancel)
|
||||
return;
|
||||
|
||||
await changeDir(path);
|
||||
};
|
||||
|
||||
module.exports.duplicatePanel = async () => {
|
||||
const Info = CurrentInfo;
|
||||
const {isDir} = Info;
|
||||
const panel = Info.panelPassive;
|
||||
const noCurrent = !Info.isOnePanel;
|
||||
|
||||
const getPath = (isDir) => {
|
||||
if (isDir)
|
||||
return Info.path;
|
||||
|
||||
return Info.dirPath;
|
||||
};
|
||||
|
||||
const path = getPath(isDir);
|
||||
|
||||
await CloudCmd.changeDir(path, {
|
||||
panel,
|
||||
noCurrent,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.swapPanels = async () => {
|
||||
const Info = CurrentInfo;
|
||||
const {
|
||||
panel,
|
||||
files,
|
||||
element,
|
||||
panelPassive,
|
||||
} = Info;
|
||||
|
||||
const path = DOM.getCurrentDirPath();
|
||||
const dirPathPassive = DOM.getNotCurrentDirPath();
|
||||
|
||||
let currentIndex = files.indexOf(element);
|
||||
|
||||
await CloudCmd.changeDir(path, {
|
||||
panel: panelPassive,
|
||||
noCurrent: true,
|
||||
});
|
||||
|
||||
await CloudCmd.changeDir(dirPathPassive, {
|
||||
panel,
|
||||
});
|
||||
|
||||
const length = Info.files.length - 1;
|
||||
|
||||
if (currentIndex > length)
|
||||
currentIndex = length;
|
||||
|
||||
const el = Info.files[currentIndex];
|
||||
|
||||
DOM.setCurrentFile(el);
|
||||
};
|
||||
|
||||
module.exports.CurrentInfo = CurrentInfo;
|
||||
|
||||
module.exports.updateCurrentInfo = (currentFile) => {
|
||||
const info = DOM.CurrentInfo;
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const files = current.parentElement;
|
||||
|
||||
const panelPassive = DOM.getPanel({
|
||||
active: false,
|
||||
});
|
||||
|
||||
const filesPassive = DOM.getFiles(panelPassive);
|
||||
const name = DOM.getCurrentName(current);
|
||||
|
||||
info.dir = DOM.getCurrentDirName();
|
||||
info.dirPath = DOM.getCurrentDirPath();
|
||||
info.parentDirPath = DOM.getParentDirPath();
|
||||
info.element = current;
|
||||
info.ext = Util.getExt(name);
|
||||
info.files = Array.from(files.children);
|
||||
info.filesPassive = Array.from(filesPassive);
|
||||
info.first = files.firstChild;
|
||||
info.getData = DOM.getCurrentData;
|
||||
info.last = files.lastChild;
|
||||
info.link = DOM.getCurrentLink(current);
|
||||
info.mode = DOM.getCurrentMode(current);
|
||||
info.name = name;
|
||||
info.path = DOM.getCurrentPath(current);
|
||||
info.panel = files.parentElement || DOM.getPanel();
|
||||
info.panelPassive = panelPassive;
|
||||
info.size = DOM.getCurrentSize(current);
|
||||
info.isDir = DOM.isCurrentIsDir();
|
||||
info.isSelected = DOM.isSelected(current);
|
||||
info.panelPosition = DOM
|
||||
.getPanel()
|
||||
.dataset
|
||||
.name
|
||||
.replace('js-', '');
|
||||
info.isOnePanel = info.panel.getAttribute('data-name') === info.panelPassive.getAttribute('data-name');
|
||||
};
|
||||
56
client/dom/index.spec.js
Normal file
56
client/dom/index.spec.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
'use strict';
|
||||
|
||||
require('css-modules-require-hook/preset');
|
||||
|
||||
const {test, stub} = require('supertape');
|
||||
const {getCSSVar, goToDirectory} = require('./index');
|
||||
|
||||
globalThis.CloudCmd = {};
|
||||
|
||||
test('cloudcmd: client: dom: goToDirectory', async (t) => {
|
||||
const path = '';
|
||||
const changeDir = stub();
|
||||
const prompt = stub().returns([null, path]);
|
||||
|
||||
await goToDirectory({
|
||||
prompt,
|
||||
changeDir,
|
||||
});
|
||||
|
||||
t.calledWith(changeDir, [path]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: dom: getCSSVar', (t) => {
|
||||
const body = {};
|
||||
const getPropertyValue = stub().returns(0);
|
||||
|
||||
globalThis.getComputedStyle = stub().returns({
|
||||
getPropertyValue,
|
||||
});
|
||||
const result = getCSSVar('hello', {
|
||||
body,
|
||||
});
|
||||
|
||||
delete globalThis.getComputedStyle;
|
||||
|
||||
t.notOk(result);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: dom: getCSSVar: 1', (t) => {
|
||||
const body = {};
|
||||
const getPropertyValue = stub().returns(1);
|
||||
|
||||
globalThis.getComputedStyle = stub().returns({
|
||||
getPropertyValue,
|
||||
});
|
||||
const result = getCSSVar('hello', {
|
||||
body,
|
||||
});
|
||||
|
||||
delete globalThis.getComputedStyle;
|
||||
|
||||
t.ok(result);
|
||||
t.end();
|
||||
});
|
||||
155
client/dom/io/index.js
Normal file
155
client/dom/io/index.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
'use strict';
|
||||
|
||||
const {FS} = require('../../../common/cloudfunc.mjs');
|
||||
const _sendRequest = require('./send-request');
|
||||
|
||||
const imgPosition = {
|
||||
top: true,
|
||||
};
|
||||
|
||||
module.exports.delete = async (url, data) => {
|
||||
return await _sendRequest({
|
||||
method: 'DELETE',
|
||||
url: FS + url,
|
||||
data,
|
||||
imgPosition: {
|
||||
top: Boolean(data),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.patch = async (url, data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PATCH',
|
||||
url: FS + url,
|
||||
data,
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.write = async (url, data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: FS + url,
|
||||
data,
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.createDirectory = async (url, overrides = {}) => {
|
||||
const {
|
||||
sendRequest = _sendRequest,
|
||||
} = overrides;
|
||||
|
||||
return await sendRequest({
|
||||
method: 'PUT',
|
||||
url: `${FS}${url}?dir`,
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.read = async (url, dataType = 'text') => {
|
||||
const notLog = !url.includes('?');
|
||||
|
||||
return await _sendRequest({
|
||||
method: 'GET',
|
||||
url: FS + url,
|
||||
notLog,
|
||||
dataType,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.copy = async (from, to, names) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/copy',
|
||||
data: {
|
||||
from,
|
||||
to,
|
||||
names,
|
||||
},
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.pack = async (data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/pack',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.extract = async (data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/extract',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.move = async (from, to, names) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/move',
|
||||
data: {
|
||||
from,
|
||||
to,
|
||||
names,
|
||||
},
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.rename = async (from, to) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/rename',
|
||||
data: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.Config = {
|
||||
read: async () => {
|
||||
return await _sendRequest({
|
||||
method: 'GET',
|
||||
url: '/config',
|
||||
imgPosition,
|
||||
notLog: true,
|
||||
});
|
||||
},
|
||||
|
||||
write: async (data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PATCH',
|
||||
url: '/config',
|
||||
data,
|
||||
imgPosition,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.Markdown = {
|
||||
read: async (url) => {
|
||||
return await _sendRequest({
|
||||
method: 'GET',
|
||||
url: `/markdown${url}`,
|
||||
imgPosition,
|
||||
notLog: true,
|
||||
});
|
||||
},
|
||||
|
||||
render: async (data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/markdown',
|
||||
data,
|
||||
imgPosition,
|
||||
notLog: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
23
client/dom/io/index.spec.js
Normal file
23
client/dom/io/index.spec.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
const {test, stub} = require('supertape');
|
||||
const io = require('.');
|
||||
|
||||
test('client: dom: io', (t) => {
|
||||
const sendRequest = stub();
|
||||
|
||||
io.createDirectory('/hello', {
|
||||
sendRequest,
|
||||
});
|
||||
|
||||
const expected = {
|
||||
imgPosition: {
|
||||
top: true,
|
||||
},
|
||||
method: 'PUT',
|
||||
url: '/fs/hello?dir',
|
||||
};
|
||||
|
||||
t.calledWith(sendRequest, [expected]);
|
||||
t.end();
|
||||
});
|
||||
50
client/dom/io/send-request.js
Normal file
50
client/dom/io/send-request.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
const {promisify} = require('es6-promisify');
|
||||
|
||||
const Images = require('../images.mjs');
|
||||
const load = require('../load');
|
||||
|
||||
module.exports = promisify((params, callback) => {
|
||||
const p = params;
|
||||
const {prefixURL} = CloudCmd;
|
||||
|
||||
p.url = prefixURL + p.url;
|
||||
p.url = encodeURI(p.url);
|
||||
|
||||
p.url = replaceHash(p.url);
|
||||
|
||||
load.ajax({
|
||||
method: p.method,
|
||||
url: p.url,
|
||||
data: p.data,
|
||||
dataType: p.dataType,
|
||||
error: (jqXHR) => {
|
||||
const response = jqXHR.responseText;
|
||||
|
||||
const {statusText, status} = jqXHR;
|
||||
|
||||
const text = status === 404 ? response : statusText;
|
||||
|
||||
callback(Error(text));
|
||||
},
|
||||
success: (data) => {
|
||||
Images.hide();
|
||||
|
||||
if (!p.notLog)
|
||||
CloudCmd.log(data);
|
||||
|
||||
callback(null, data);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
module.exports._replaceHash = replaceHash;
|
||||
function replaceHash(url) {
|
||||
/*
|
||||
* if we send ajax request -
|
||||
* no need in hash so we escape #
|
||||
*/
|
||||
return url.replace(/#/g, '%23');
|
||||
}
|
||||
13
client/dom/io/send-request.spec.js
Normal file
13
client/dom/io/send-request.spec.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('supertape');
|
||||
const {_replaceHash} = require('./send-request');
|
||||
|
||||
test('cloudcmd: client: io: replaceHash', (t) => {
|
||||
const url = '/hello/####world';
|
||||
const result = _replaceHash(url);
|
||||
const expected = '/hello/%23%23%23%23world';
|
||||
|
||||
t.equal(result, expected);
|
||||
t.end();
|
||||
});
|
||||
56
client/dom/load-remote.js
Normal file
56
client/dom/load-remote.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
const rendy = require('rendy');
|
||||
const itype = require('itype');
|
||||
const load = require('load.js');
|
||||
const {tryToCatch} = require('try-to-catch');
|
||||
|
||||
const {findObjByNameInArr} = require('../../common/util');
|
||||
|
||||
const Files = require('./files');
|
||||
|
||||
module.exports = (name, options, callback = options) => {
|
||||
const {prefix, config} = CloudCmd;
|
||||
const o = options;
|
||||
|
||||
if (o.name && window[o.name])
|
||||
return callback();
|
||||
|
||||
Files.get('modules').then(async (modules) => {
|
||||
const online = config('online') && navigator.onLine;
|
||||
const module = findObjByNameInArr(modules.remote, name);
|
||||
|
||||
const isArray = itype.array(module.local);
|
||||
const {version} = module;
|
||||
|
||||
let remoteTmpls;
|
||||
let local;
|
||||
|
||||
if (isArray) {
|
||||
remoteTmpls = module.remote;
|
||||
({local} = module);
|
||||
} else {
|
||||
remoteTmpls = [module.remote];
|
||||
local = [module.local];
|
||||
}
|
||||
|
||||
const localURL = local.map((url) => prefix + url);
|
||||
|
||||
const remoteURL = remoteTmpls.map((tmpl) => {
|
||||
return rendy(tmpl, {
|
||||
version,
|
||||
});
|
||||
});
|
||||
|
||||
if (online) {
|
||||
const [e] = await tryToCatch(load.parallel, remoteURL);
|
||||
|
||||
if (!e)
|
||||
return callback();
|
||||
}
|
||||
|
||||
const [e] = await tryToCatch(load.parallel, localURL);
|
||||
callback(e);
|
||||
});
|
||||
};
|
||||
134
client/dom/load.js
Normal file
134
client/dom/load.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
'use strict';
|
||||
|
||||
const itype = require('itype');
|
||||
const jonny = require('jonny');
|
||||
const Emitify = require('emitify');
|
||||
const exec = require('execon');
|
||||
const Images = require('./images.mjs');
|
||||
|
||||
module.exports.getIdBySrc = getIdBySrc;
|
||||
/**
|
||||
* Function gets id by src
|
||||
* @param src
|
||||
*
|
||||
* Example: http://domain.com/1.js -> 1_js
|
||||
*/
|
||||
function getIdBySrc(src) {
|
||||
const isStr = itype.string(src);
|
||||
|
||||
if (!isStr)
|
||||
return;
|
||||
|
||||
if (src.includes(':'))
|
||||
src += '-join';
|
||||
|
||||
const num = src.lastIndexOf('/') + 1;
|
||||
const sub = src.substr(src, num);
|
||||
|
||||
const id = src
|
||||
.replace(sub, '')
|
||||
.replace(/\./g, '-');
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* load file countent via ajax
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
module.exports.ajax = (params) => {
|
||||
const p = params;
|
||||
const isObject = itype.object(p.data);
|
||||
const isArray = itype.array(p.data);
|
||||
const isArrayBuf = itype(p.data) === 'arraybuffer';
|
||||
const type = p.type || p.method || 'GET';
|
||||
|
||||
const {headers = {}} = p;
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open(type, p.url, true);
|
||||
|
||||
for (const name of Object.keys(headers)) {
|
||||
const value = headers[name];
|
||||
xhr.setRequestHeader(name, value);
|
||||
}
|
||||
|
||||
if (p.responseType)
|
||||
xhr.responseType = p.responseType;
|
||||
|
||||
let data;
|
||||
|
||||
if (!isArrayBuf && isObject || isArray)
|
||||
data = jonny.stringify(p.data);
|
||||
else
|
||||
({data} = p);
|
||||
|
||||
xhr.onreadystatechange = (event) => {
|
||||
const xhr = event.target;
|
||||
const OK = 200;
|
||||
|
||||
if (xhr.readyState !== xhr.DONE)
|
||||
return;
|
||||
|
||||
Images.clearProgress();
|
||||
|
||||
const TYPE_JSON = 'application/json';
|
||||
const type = xhr.getResponseHeader('content-type');
|
||||
|
||||
if (xhr.status !== OK)
|
||||
return exec(p.error, xhr);
|
||||
|
||||
const notText = p.dataType !== 'text';
|
||||
const isContain = type.includes(TYPE_JSON);
|
||||
|
||||
let data = xhr.response;
|
||||
|
||||
if (type && isContain && notText)
|
||||
data = jonny.parse(xhr.response) || xhr.response;
|
||||
|
||||
exec(p.success, data, xhr.statusText, xhr);
|
||||
};
|
||||
|
||||
xhr.send(data);
|
||||
};
|
||||
|
||||
module.exports.put = (url, body) => {
|
||||
const emitter = Emitify();
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
url = encodeURI(url).replace(/#/g, '%23');
|
||||
|
||||
xhr.open('put', url, true);
|
||||
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (!event.lengthComputable)
|
||||
return;
|
||||
|
||||
const percent = event.loaded / event.total * 100;
|
||||
const count = Math.round(percent);
|
||||
|
||||
emitter.emit('progress', count);
|
||||
};
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
const over = xhr.readyState === xhr.DONE;
|
||||
const OK = 200;
|
||||
|
||||
if (!over)
|
||||
return;
|
||||
|
||||
if (xhr.status === OK) {
|
||||
emitter.emit('progress', 100);
|
||||
return emitter.emit('end');
|
||||
}
|
||||
|
||||
const error = Error(xhr.responseText);
|
||||
emitter.emit('error', error);
|
||||
};
|
||||
|
||||
xhr.send(body);
|
||||
|
||||
return emitter;
|
||||
};
|
||||
70
client/dom/operations/rename-current.js
Normal file
70
client/dom/operations/rename-current.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
const capitalize = require('just-capitalize');
|
||||
|
||||
const _Dialog = require('../dialog');
|
||||
const Storage = require('../storage');
|
||||
const RESTful = require('../rest');
|
||||
|
||||
const _currentFile = require('../current-file.mjs');
|
||||
|
||||
module.exports = async (current, overrides = {}) => {
|
||||
const {
|
||||
refresh = CloudCmd.refresh,
|
||||
Dialog = _Dialog,
|
||||
currentFile = _currentFile,
|
||||
} = overrides;
|
||||
|
||||
const {
|
||||
isCurrentFile,
|
||||
getCurrentName,
|
||||
getCurrentFile,
|
||||
getCurrentByName,
|
||||
getCurrentType,
|
||||
getCurrentDirPath,
|
||||
setCurrentName,
|
||||
} = currentFile;
|
||||
|
||||
if (!isCurrentFile(current))
|
||||
current = getCurrentFile();
|
||||
|
||||
const from = getCurrentName(current);
|
||||
|
||||
if (from === '..')
|
||||
return Dialog.alert.noFiles();
|
||||
|
||||
const [cancel, to] = await Dialog.prompt('Rename', from);
|
||||
|
||||
if (cancel)
|
||||
return;
|
||||
|
||||
const nextFile = getCurrentByName(to);
|
||||
|
||||
if (nextFile) {
|
||||
const type = getCurrentType(nextFile);
|
||||
const msg = `${capitalize(type)} "${to}" already exists. Proceed?`;
|
||||
const [cancel] = await Dialog.confirm(msg);
|
||||
|
||||
if (cancel)
|
||||
return;
|
||||
}
|
||||
|
||||
if (from === to)
|
||||
return;
|
||||
|
||||
const dirPath = getCurrentDirPath();
|
||||
|
||||
const fromFull = `${dirPath}${from}`;
|
||||
const toFull = `${dirPath}${to}`;
|
||||
|
||||
const [e] = await RESTful.rename(fromFull, toFull);
|
||||
|
||||
if (e)
|
||||
return;
|
||||
|
||||
setCurrentName(to, current);
|
||||
|
||||
Storage.remove(dirPath);
|
||||
refresh();
|
||||
};
|
||||
89
client/dom/operations/rename-current.spec.js
Normal file
89
client/dom/operations/rename-current.spec.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
'use strict';
|
||||
|
||||
const {test, stub} = require('supertape');
|
||||
|
||||
const renameCurrent = require('./rename-current');
|
||||
|
||||
test('cloudcmd: client: dom: renameCurrent: isCurrentFile', async (t) => {
|
||||
const current = {};
|
||||
const isCurrentFile = stub();
|
||||
|
||||
const currentFile = stubCurrentFile({
|
||||
isCurrentFile,
|
||||
});
|
||||
|
||||
await renameCurrent(current, {
|
||||
Dialog: stubDialog(),
|
||||
currentFile,
|
||||
});
|
||||
|
||||
t.calledWith(isCurrentFile, [current], 'should call isCurrentFile');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: dom: renameCurrent: file exist', async (t) => {
|
||||
const current = {};
|
||||
const name = 'hello';
|
||||
|
||||
const prompt = stub().returns([null, name]);
|
||||
const confirm = stub().returns([true]);
|
||||
|
||||
const getCurrentByName = stub().returns(current);
|
||||
const getCurrentType = stub().returns('directory');
|
||||
|
||||
const Dialog = stubDialog({
|
||||
confirm,
|
||||
prompt,
|
||||
});
|
||||
|
||||
const currentFile = stubCurrentFile({
|
||||
getCurrentByName,
|
||||
getCurrentType,
|
||||
});
|
||||
|
||||
await renameCurrent(null, {
|
||||
Dialog,
|
||||
currentFile,
|
||||
});
|
||||
|
||||
const expected = 'Directory "hello" already exists. Proceed?';
|
||||
|
||||
t.calledWith(confirm, [expected], 'should call confirm');
|
||||
t.end();
|
||||
});
|
||||
|
||||
const stubDialog = (fns = {}) => {
|
||||
const {
|
||||
alert = stub().returns([]),
|
||||
confirm = stub().returns([]),
|
||||
prompt = stub().returns([]),
|
||||
} = fns;
|
||||
|
||||
return {
|
||||
alert,
|
||||
confirm,
|
||||
prompt,
|
||||
};
|
||||
};
|
||||
|
||||
const stubCurrentFile = (fns = {}) => {
|
||||
const {
|
||||
isCurrentFile = stub(),
|
||||
getCurrentName = stub(),
|
||||
getCurrentFile = stub(),
|
||||
getCurrentByName = stub(),
|
||||
getCurrentType = stub(),
|
||||
getCurrentDirPath = stub(),
|
||||
setCurrentName = stub(),
|
||||
} = fns;
|
||||
|
||||
return {
|
||||
isCurrentFile,
|
||||
getCurrentName,
|
||||
getCurrentFile,
|
||||
getCurrentByName,
|
||||
getCurrentType,
|
||||
getCurrentDirPath,
|
||||
setCurrentName,
|
||||
};
|
||||
};
|
||||
44
client/dom/rest.js
Normal file
44
client/dom/rest.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
const {tryToCatch} = require('try-to-catch');
|
||||
|
||||
const {encode} = require('../../common/entity');
|
||||
|
||||
const Images = require('./images.mjs');
|
||||
const IO = require('./io');
|
||||
const Dialog = require('./dialog');
|
||||
|
||||
const handleError = (promise) => async (...args) => {
|
||||
const [e, data] = await tryToCatch(promise, ...args);
|
||||
|
||||
if (!e)
|
||||
return [e, data];
|
||||
|
||||
const encoded = encode(e.message);
|
||||
|
||||
Images.show.error(encoded);
|
||||
Dialog.alert(encoded);
|
||||
|
||||
return [e, data];
|
||||
};
|
||||
|
||||
module.exports.delete = handleError(IO.delete);
|
||||
module.exports.patch = handleError(IO.patch);
|
||||
module.exports.write = handleError(IO.write);
|
||||
module.exports.createDirectory = handleError(IO.createDirectory);
|
||||
module.exports.read = handleError(IO.read);
|
||||
module.exports.copy = handleError(IO.copy);
|
||||
module.exports.pack = handleError(IO.pack);
|
||||
module.exports.extract = handleError(IO.extract);
|
||||
module.exports.move = handleError(IO.move);
|
||||
module.exports.rename = handleError(IO.rename);
|
||||
|
||||
module.exports.Config = {
|
||||
read: handleError(IO.Config.read),
|
||||
write: handleError(IO.Config.write),
|
||||
};
|
||||
|
||||
module.exports.Markdown = {
|
||||
read: handleError(IO.Markdown.read),
|
||||
render: handleError(IO.Markdown.render),
|
||||
};
|
||||
45
client/dom/select-by-pattern.js
Normal file
45
client/dom/select-by-pattern.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
let SelectType = '*.*';
|
||||
|
||||
const {getRegExp} = require('../../common/util');
|
||||
const {alert, prompt} = require('./dialog');
|
||||
|
||||
const DOM = require('.');
|
||||
|
||||
module.exports = async (msg, files) => {
|
||||
if (!files)
|
||||
return;
|
||||
|
||||
const allMsg = `Specify file type for ${msg} selection`;
|
||||
const [cancel, type] = await prompt(allMsg, SelectType);
|
||||
|
||||
if (cancel)
|
||||
return;
|
||||
|
||||
SelectType = type;
|
||||
|
||||
const regExp = getRegExp(type);
|
||||
let matches = 0;
|
||||
|
||||
for (const current of files) {
|
||||
const name = DOM.getCurrentName(current);
|
||||
|
||||
if (name === '..' || !regExp.test(name))
|
||||
continue;
|
||||
|
||||
++matches;
|
||||
|
||||
let isSelected = DOM.isSelected(current);
|
||||
const shouldSel = msg === 'expand';
|
||||
|
||||
if (shouldSel)
|
||||
isSelected = !isSelected;
|
||||
|
||||
if (isSelected)
|
||||
DOM.toggleSelectedFile(current);
|
||||
}
|
||||
|
||||
if (!matches)
|
||||
alert('No matches found!');
|
||||
};
|
||||
28
client/dom/storage.js
Normal file
28
client/dom/storage.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
const {parse, stringify} = JSON;
|
||||
|
||||
module.exports.set = async (name, data) => {
|
||||
localStorage.setItem(name, data);
|
||||
};
|
||||
|
||||
module.exports.setJson = async (name, data) => {
|
||||
localStorage.setItem(name, stringify(data));
|
||||
};
|
||||
|
||||
module.exports.get = async (name) => {
|
||||
return localStorage.getItem(name);
|
||||
};
|
||||
|
||||
module.exports.getJson = async (name) => {
|
||||
const data = localStorage.getItem(name);
|
||||
return parse(data);
|
||||
};
|
||||
|
||||
module.exports.clear = () => {
|
||||
localStorage.clear();
|
||||
};
|
||||
|
||||
module.exports.remove = (item) => {
|
||||
localStorage.removeItem(item);
|
||||
};
|
||||
108
client/dom/storage.spec.js
Normal file
108
client/dom/storage.spec.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
'use strict';
|
||||
|
||||
const {test, stub} = require('supertape');
|
||||
|
||||
const storage = require('./storage');
|
||||
|
||||
const {stringify} = JSON;
|
||||
|
||||
test('cloudcmd: client: storage: set', async (t) => {
|
||||
const {localStorage} = globalThis;
|
||||
const setItem = stub();
|
||||
|
||||
globalThis.localStorage = {
|
||||
setItem,
|
||||
};
|
||||
|
||||
await storage.set('hello', 'world');
|
||||
globalThis.localStorage = localStorage;
|
||||
|
||||
t.calledWith(setItem, ['hello', 'world'], 'should call setItem');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: storage: get', async (t) => {
|
||||
const {localStorage} = globalThis;
|
||||
const getItem = stub().returns('world');
|
||||
|
||||
globalThis.localStorage = {
|
||||
getItem,
|
||||
};
|
||||
|
||||
const result = await storage.get('hello');
|
||||
|
||||
globalThis.localStorage = localStorage;
|
||||
|
||||
t.equal(result, 'world');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: storage: getJson', async (t) => {
|
||||
const {localStorage} = globalThis;
|
||||
const expected = {
|
||||
hello: 'world',
|
||||
};
|
||||
|
||||
const getItem = stub().returns(stringify(expected));
|
||||
|
||||
globalThis.localStorage = {
|
||||
getItem,
|
||||
};
|
||||
|
||||
const result = await storage.getJson('hello');
|
||||
|
||||
globalThis.localStorage = localStorage;
|
||||
|
||||
t.deepEqual(result, expected);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: storage: setJson', async (t) => {
|
||||
const {localStorage} = globalThis;
|
||||
const data = {
|
||||
hello: 'world',
|
||||
};
|
||||
|
||||
const expected = stringify(data);
|
||||
const setItem = stub();
|
||||
|
||||
globalThis.localStorage = {
|
||||
setItem,
|
||||
};
|
||||
|
||||
await storage.setJson('hello', data);
|
||||
globalThis.localStorage = localStorage;
|
||||
|
||||
t.calledWith(setItem, ['hello', expected]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: storage: remove', async (t) => {
|
||||
const {localStorage} = globalThis;
|
||||
const removeItem = stub();
|
||||
|
||||
globalThis.localStorage = {
|
||||
removeItem,
|
||||
};
|
||||
|
||||
await storage.remove('hello');
|
||||
globalThis.localStorage = localStorage;
|
||||
|
||||
t.calledWith(removeItem, ['hello'], 'should call removeItem');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: storage: clear', async (t) => {
|
||||
const {localStorage} = globalThis;
|
||||
const clear = stub();
|
||||
|
||||
globalThis.localStorage = {
|
||||
clear,
|
||||
};
|
||||
|
||||
await storage.clear();
|
||||
globalThis.localStorage = localStorage;
|
||||
|
||||
t.calledWithNoArgs(clear, 'should call clear');
|
||||
t.end();
|
||||
});
|
||||
72
client/dom/upload-files.js
Normal file
72
client/dom/upload-files.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
const {eachSeries} = require('execon');
|
||||
const wraptile = require('wraptile');
|
||||
|
||||
const load = require('./load');
|
||||
const Images = require('./images.mjs');
|
||||
const {alert} = require('./dialog');
|
||||
|
||||
const {FS} = require('../../common/cloudfunc.mjs');
|
||||
|
||||
const {getCurrentDirPath: getPathWhenRootEmpty} = require('.');
|
||||
const loadFile = wraptile(_loadFile);
|
||||
|
||||
const onEnd = wraptile(_onEnd);
|
||||
|
||||
module.exports = (dir, files) => {
|
||||
if (!files) {
|
||||
files = dir;
|
||||
dir = getPathWhenRootEmpty();
|
||||
}
|
||||
|
||||
const n = files.length;
|
||||
|
||||
if (!n)
|
||||
return;
|
||||
|
||||
const array = Array.from(files);
|
||||
const {name} = files[0];
|
||||
|
||||
eachSeries(array, loadFile(dir, n), onEnd(name));
|
||||
};
|
||||
|
||||
function _onEnd(currentName) {
|
||||
CloudCmd.refresh({
|
||||
currentName,
|
||||
});
|
||||
}
|
||||
|
||||
function _loadFile(dir, n, file, callback) {
|
||||
let i = 0;
|
||||
|
||||
const {name} = file;
|
||||
const path = dir + name;
|
||||
const {prefixURL} = CloudCmd;
|
||||
const api = prefixURL + FS;
|
||||
|
||||
const percent = (i, n, per = 100) => {
|
||||
return Math.round(i * per / n);
|
||||
};
|
||||
|
||||
const step = (n) => 100 / n;
|
||||
|
||||
++i;
|
||||
|
||||
load
|
||||
.put(api + path, file)
|
||||
.on('error', showError)
|
||||
.on('end', callback)
|
||||
.on('progress', (count) => {
|
||||
const max = step(n);
|
||||
const value = (i - 1) * max + percent(count, 100, max);
|
||||
|
||||
Images.show.load('top');
|
||||
Images.setProgress(Math.round(value));
|
||||
});
|
||||
}
|
||||
|
||||
function showError({message}) {
|
||||
alert(message);
|
||||
}
|
||||
44
client/get-json-from-file-table.mjs
Normal file
44
client/get-json-from-file-table.mjs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* global DOM */
|
||||
/**
|
||||
* Функция генерирует JSON из html-таблицы файлов и
|
||||
* используеться при первом заходе в корень
|
||||
*/
|
||||
export const getJsonFromFileTable = () => {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const path = DOM.getCurrentDirPath();
|
||||
const infoFiles = Info.files || [];
|
||||
|
||||
const files = infoFiles
|
||||
.filter(notParent)
|
||||
.map(parse);
|
||||
|
||||
const fileTable = {
|
||||
path,
|
||||
files,
|
||||
};
|
||||
|
||||
return fileTable;
|
||||
};
|
||||
|
||||
const notParent = (current) => {
|
||||
const name = DOM.getCurrentName(current);
|
||||
return name !== '..';
|
||||
};
|
||||
|
||||
const parse = (current) => {
|
||||
const name = DOM.getCurrentName(current);
|
||||
const size = DOM.getCurrentSize(current);
|
||||
const owner = DOM.getCurrentOwner(current);
|
||||
const mode = DOM.getCurrentMode(current);
|
||||
const date = DOM.getCurrentDate(current);
|
||||
const type = DOM.getCurrentType(current);
|
||||
|
||||
return {
|
||||
name,
|
||||
size,
|
||||
mode,
|
||||
owner,
|
||||
date,
|
||||
type,
|
||||
};
|
||||
};
|
||||
17
client/key/binder.js
Normal file
17
client/key/binder.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
'use strict';
|
||||
|
||||
module.exports.createBinder = () => {
|
||||
let binded = false;
|
||||
|
||||
return {
|
||||
isBind() {
|
||||
return binded;
|
||||
},
|
||||
setBind() {
|
||||
binded = true;
|
||||
},
|
||||
unsetBind() {
|
||||
binded = false;
|
||||
},
|
||||
};
|
||||
};
|
||||
536
client/key/index.mjs
Normal file
536
client/key/index.mjs
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
/* global CloudCmd, DOM */
|
||||
import clipboard from '@cloudcmd/clipboard';
|
||||
import {fullstore} from 'fullstore';
|
||||
import * as Events from '#dom/events';
|
||||
import * as Buffer from '../dom/buffer.mjs';
|
||||
import KEY from './key.js';
|
||||
import _vim from './vim/index.js';
|
||||
import setCurrentByChar from './set-current-by-char.js';
|
||||
import {createBinder} from './binder.js';
|
||||
|
||||
const Chars = fullstore();
|
||||
|
||||
const toggleVim = (keyCode, overrides = {}) => {
|
||||
const {_config, config} = overrides;
|
||||
|
||||
if (keyCode === KEY.ESC)
|
||||
_config('vim', !config('vim'));
|
||||
};
|
||||
|
||||
const isUndefined = (a) => typeof a === 'undefined';
|
||||
|
||||
Chars([]);
|
||||
|
||||
const {assign} = Object;
|
||||
const binder = createBinder();
|
||||
|
||||
const bind = () => {
|
||||
Events.addKey(listener, true);
|
||||
binder.setBind();
|
||||
};
|
||||
|
||||
export const Key = assign(binder, KEY, {
|
||||
bind,
|
||||
});
|
||||
|
||||
export const _listener = listener;
|
||||
|
||||
function getChar(event) {
|
||||
/*
|
||||
* event.keyIdentifier deprecated in chrome v51
|
||||
* but event.key is absent in chrome <= v51
|
||||
*/
|
||||
const {
|
||||
key,
|
||||
shift,
|
||||
keyCode,
|
||||
keyIdentifier,
|
||||
} = event;
|
||||
|
||||
const char = key || fromCharCode(keyIdentifier);
|
||||
const symbol = getSymbol(shift, keyCode);
|
||||
|
||||
return [symbol, char];
|
||||
}
|
||||
|
||||
async function listener(event, overrides = {}) {
|
||||
const {
|
||||
config = CloudCmd.config,
|
||||
_config = CloudCmd._config,
|
||||
switchKey = _switchKey,
|
||||
vim = _vim,
|
||||
} = overrides;
|
||||
|
||||
const {keyCode} = event;
|
||||
|
||||
// strange chrome bug calles listener twice
|
||||
// in second time event misses a lot fields
|
||||
if (isUndefined(event.altKey))
|
||||
return;
|
||||
|
||||
const alt = event.altKey;
|
||||
const ctrl = event.ctrlKey;
|
||||
const meta = event.metaKey;
|
||||
const isBetween = keyCode >= KEY.ZERO && keyCode <= KEY.Z;
|
||||
const isNumpad = /Numpad/.test(event.code);
|
||||
|
||||
const [symbol, char] = getChar(event);
|
||||
|
||||
if (!binder.isBind())
|
||||
return;
|
||||
|
||||
toggleVim(keyCode, {
|
||||
config,
|
||||
_config,
|
||||
});
|
||||
|
||||
const isVim = config('vim');
|
||||
|
||||
if (!isVim && !isNumpad && !alt && !ctrl && !meta && (isBetween || symbol))
|
||||
return setCurrentByChar(char, Chars);
|
||||
|
||||
Chars([]);
|
||||
await switchKey(event);
|
||||
|
||||
if (keyCode >= KEY.F1 && keyCode <= KEY.F10)
|
||||
return;
|
||||
|
||||
if (isVim)
|
||||
vim(char, event);
|
||||
}
|
||||
|
||||
function getSymbol(shift, keyCode) {
|
||||
switch(keyCode) {
|
||||
case KEY.DOT:
|
||||
return '.';
|
||||
|
||||
case KEY.HYPHEN:
|
||||
return shift ? '_' : '-';
|
||||
|
||||
case KEY.EQUAL:
|
||||
return shift ? '+' : '=';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function fromCharCode(keyIdentifier) {
|
||||
const code = keyIdentifier.substring(2);
|
||||
const hex = parseInt(code, 16);
|
||||
|
||||
return String.fromCharCode(hex);
|
||||
}
|
||||
|
||||
async function _switchKey(event) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
let i;
|
||||
let isSelected;
|
||||
let prev;
|
||||
let next;
|
||||
let current = Info.element;
|
||||
let dataName;
|
||||
|
||||
const {
|
||||
name,
|
||||
panel,
|
||||
path,
|
||||
isDir,
|
||||
} = Info;
|
||||
|
||||
const {
|
||||
Operation,
|
||||
changeDir,
|
||||
config,
|
||||
} = CloudCmd;
|
||||
|
||||
const {keyCode} = event;
|
||||
|
||||
const alt = event.altKey;
|
||||
const shift = event.shiftKey;
|
||||
const ctrl = event.ctrlKey;
|
||||
const meta = event.metaKey;
|
||||
const ctrlMeta = ctrl || meta;
|
||||
|
||||
if (current) {
|
||||
prev = current.previousSibling;
|
||||
next = current.nextSibling;
|
||||
}
|
||||
|
||||
switch(keyCode) {
|
||||
case KEY.TAB:
|
||||
DOM.changePanel();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.INSERT:
|
||||
DOM
|
||||
.toggleSelectedFile(current)
|
||||
.setCurrentFile(next);
|
||||
break;
|
||||
|
||||
case KEY.INSERT_MAC:
|
||||
DOM
|
||||
.toggleSelectedFile(current)
|
||||
.setCurrentFile(next);
|
||||
break;
|
||||
|
||||
case KEY.DELETE:
|
||||
if (shift)
|
||||
Operation.show('delete:silent');
|
||||
else
|
||||
Operation.show('delete');
|
||||
|
||||
break;
|
||||
|
||||
case KEY.ASTERISK:
|
||||
DOM.toggleAllSelectedFiles(current);
|
||||
break;
|
||||
|
||||
case KEY.PLUS:
|
||||
DOM.expandSelection();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.MINUS:
|
||||
DOM.shrinkSelection();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.F1:
|
||||
CloudCmd.Help.show();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.F2:
|
||||
CloudCmd.UserMenu.show();
|
||||
break;
|
||||
|
||||
case KEY.F3:
|
||||
event.preventDefault();
|
||||
|
||||
if (Info.isDir)
|
||||
await changeDir(path);
|
||||
else if (shift)
|
||||
CloudCmd.View.show(null, {
|
||||
raw: true,
|
||||
});
|
||||
else if (ctrlMeta)
|
||||
CloudCmd.sortPanel('name');
|
||||
else
|
||||
CloudCmd.View.show();
|
||||
|
||||
break;
|
||||
|
||||
case KEY.F4:
|
||||
if (config('vim'))
|
||||
CloudCmd.EditFileVim.show();
|
||||
else
|
||||
CloudCmd.EditFile.show();
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.F5:
|
||||
if (ctrlMeta)
|
||||
CloudCmd.sortPanel('date');
|
||||
else if (alt)
|
||||
Operation.show('pack');
|
||||
else
|
||||
Operation.show('copy');
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.F6:
|
||||
if (ctrlMeta)
|
||||
CloudCmd.sortPanel('size');
|
||||
else if (shift)
|
||||
DOM.renameCurrent(current);
|
||||
else
|
||||
Operation.show('move');
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.F7:
|
||||
if (shift)
|
||||
DOM.promptNewFile();
|
||||
else
|
||||
DOM.promptNewDir();
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.F8:
|
||||
Operation.show('delete');
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.F9:
|
||||
if (alt)
|
||||
Operation.show('extract');
|
||||
else
|
||||
CloudCmd.Menu.show();
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.F10:
|
||||
CloudCmd.Config.show();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.TRA:
|
||||
event.preventDefault();
|
||||
|
||||
if (shift)
|
||||
return CloudCmd.Terminal.show();
|
||||
|
||||
CloudCmd.Konsole.show();
|
||||
break;
|
||||
|
||||
case KEY.BRACKET_CLOSE:
|
||||
CloudCmd.Konsole.show();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.SPACE:
|
||||
event.preventDefault();
|
||||
|
||||
if (!isDir || name === '..')
|
||||
isSelected = true;
|
||||
else
|
||||
isSelected = DOM.isSelected(current);
|
||||
|
||||
if (!isSelected)
|
||||
await DOM.loadCurrentSize(current);
|
||||
|
||||
DOM.toggleSelectedFile(current);
|
||||
|
||||
break;
|
||||
|
||||
case KEY.U:
|
||||
if (ctrlMeta) {
|
||||
DOM.swapPanels();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
/* navigation on file table: *
|
||||
* in case of pressing button 'up', *
|
||||
* select previous row */
|
||||
case KEY.UP:
|
||||
if (shift)
|
||||
DOM.toggleSelectedFile(current);
|
||||
|
||||
DOM.setCurrentFile(prev);
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
/* in case of pressing button 'down', *
|
||||
* select next row */
|
||||
case KEY.DOWN:
|
||||
if (shift)
|
||||
DOM.toggleSelectedFile(current);
|
||||
|
||||
DOM.setCurrentFile(next);
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.LEFT:
|
||||
if (!alt)
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
dataName = Info.panel.getAttribute('data-name');
|
||||
|
||||
if (dataName === 'js-right')
|
||||
DOM.duplicatePanel();
|
||||
|
||||
break;
|
||||
|
||||
case KEY.RIGHT:
|
||||
if (!alt)
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
dataName = Info.panel.getAttribute('data-name');
|
||||
|
||||
if (dataName === 'js-left')
|
||||
DOM.duplicatePanel();
|
||||
|
||||
break;
|
||||
|
||||
/* in case of pressing button 'Home', *
|
||||
* go to top element */
|
||||
case KEY.HOME:
|
||||
DOM.setCurrentFile(Info.first);
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
/* in case of pressing button 'End', select last element */
|
||||
case KEY.END:
|
||||
DOM.setCurrentFile(Info.last);
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
/* если нажали клавишу page down проматываем экран */
|
||||
case KEY.PAGE_DOWN:
|
||||
DOM.scrollByPages(panel, 1);
|
||||
|
||||
for (i = 0; i < 30; i++) {
|
||||
if (!current.nextSibling)
|
||||
break;
|
||||
|
||||
current = current.nextSibling;
|
||||
}
|
||||
|
||||
DOM.setCurrentFile(current);
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
/* если нажали клавишу page up проматываем экран */
|
||||
case KEY.PAGE_UP:
|
||||
DOM.scrollByPages(panel, -1);
|
||||
|
||||
for (i = 0; i < 30; i++) {
|
||||
if (!current.previousSibling)
|
||||
break;
|
||||
|
||||
current = current.previousSibling;
|
||||
}
|
||||
|
||||
DOM.setCurrentFile(current);
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.ENTER:
|
||||
if (Info.isDir)
|
||||
await changeDir(path);
|
||||
else
|
||||
CloudCmd.View.show();
|
||||
|
||||
break;
|
||||
|
||||
case KEY.BACKSPACE:
|
||||
CloudCmd.goToParentDir();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case KEY.BACKSLASH:
|
||||
if (ctrlMeta)
|
||||
await changeDir('/');
|
||||
|
||||
break;
|
||||
|
||||
case KEY.A:
|
||||
if (ctrlMeta) {
|
||||
DOM.selectAllFiles();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KEY.G:
|
||||
if (alt) {
|
||||
DOM.goToDirectory();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KEY.M:
|
||||
if (ctrlMeta) {
|
||||
if (config('vim'))
|
||||
CloudCmd.EditNamesVim.show();
|
||||
else
|
||||
CloudCmd.EditNames.show();
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KEY.P:
|
||||
if (!ctrlMeta)
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
clipboard
|
||||
.writeText(Info.dirPath)
|
||||
.catch(CloudCmd.log);
|
||||
|
||||
break;
|
||||
|
||||
/**
|
||||
* обновляем страницу,
|
||||
* загружаем содержимое каталога
|
||||
* при этом данные берём всегда с
|
||||
* сервера, а не из кэша
|
||||
* (обновляем кэш)
|
||||
*/
|
||||
case KEY.R:
|
||||
if (ctrlMeta) {
|
||||
CloudCmd.log('reloading page...\n');
|
||||
CloudCmd.refresh();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KEY.C:
|
||||
if (ctrlMeta)
|
||||
Buffer.copy();
|
||||
|
||||
break;
|
||||
|
||||
case KEY.X:
|
||||
if (ctrlMeta)
|
||||
Buffer.cut();
|
||||
|
||||
break;
|
||||
|
||||
case KEY.V:
|
||||
if (ctrlMeta)
|
||||
Buffer.paste();
|
||||
|
||||
break;
|
||||
|
||||
case KEY.Z:
|
||||
if (ctrlMeta)
|
||||
Buffer.clear();
|
||||
|
||||
break;
|
||||
|
||||
case KEY.COLON:
|
||||
CloudCmd.CommandLine.show();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
/* чистим хранилище */
|
||||
case KEY.D:
|
||||
if (ctrlMeta) {
|
||||
CloudCmd.log('clearing storage...');
|
||||
await DOM.Storage.clear();
|
||||
CloudCmd.log('storage cleared');
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KEY.DOT:
|
||||
if (meta && shift) {
|
||||
const showDotFiles = !CloudCmd.config('showDotFiles');
|
||||
CloudCmd._config('showDotFiles', showDotFiles);
|
||||
CloudCmd.refresh();
|
||||
await DOM.RESTful.Config.write({
|
||||
showDotFiles,
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
61
client/key/index.spec.js
Normal file
61
client/key/index.spec.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
|
||||
require('css-modules-require-hook/preset');
|
||||
|
||||
const autoGlobals = require('auto-globals');
|
||||
const supertape = require('supertape');
|
||||
|
||||
const {ESC} = require('./key');
|
||||
|
||||
const {Key, _listener} = require('./index.mjs');
|
||||
|
||||
const {getDOM, getCloudCmd} = require('./vim/globals.fixture');
|
||||
const test = autoGlobals(supertape);
|
||||
const {stub} = supertape;
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
test('cloudcmd: client: key: enable vim', async (t) => {
|
||||
const vim = stub();
|
||||
const config = stub().returns(true);
|
||||
const _config = stub();
|
||||
|
||||
const event = {
|
||||
keyCode: ESC,
|
||||
key: 'Escape',
|
||||
altKey: false,
|
||||
};
|
||||
|
||||
Key.setBind();
|
||||
|
||||
await _listener(event, {
|
||||
vim,
|
||||
config,
|
||||
_config,
|
||||
switchKey: stub(),
|
||||
});
|
||||
|
||||
t.calledWith(vim, ['Escape', event]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: disable vim', async (t) => {
|
||||
const _config = stub();
|
||||
const config = stub();
|
||||
const event = {
|
||||
keyCode: ESC,
|
||||
key: 'Escape',
|
||||
altKey: false,
|
||||
};
|
||||
|
||||
Key.setBind();
|
||||
await _listener(event, {
|
||||
config,
|
||||
_config,
|
||||
switchKey: stub(),
|
||||
});
|
||||
|
||||
t.calledWith(_config, ['vim', true]);
|
||||
t.end();
|
||||
});
|
||||
81
client/key/key.js
Normal file
81
client/key/key.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
BACKSPACE: 8,
|
||||
TAB: 9,
|
||||
ENTER: 13,
|
||||
CAPSLOCK: 20,
|
||||
ESC: 27,
|
||||
|
||||
SPACE: 32,
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
END: 35,
|
||||
HOME: 36,
|
||||
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
|
||||
INSERT: 45,
|
||||
DELETE: 46,
|
||||
|
||||
ZERO: 48,
|
||||
|
||||
SEMICOLON: 52,
|
||||
|
||||
A: 65,
|
||||
|
||||
C: 67,
|
||||
D: 68,
|
||||
|
||||
G: 71,
|
||||
|
||||
J: 74,
|
||||
K: 75,
|
||||
|
||||
M: 77,
|
||||
|
||||
O: 79,
|
||||
P: 80,
|
||||
Q: 81,
|
||||
R: 82,
|
||||
S: 83,
|
||||
T: 84,
|
||||
U: 85,
|
||||
|
||||
V: 86,
|
||||
|
||||
X: 88,
|
||||
|
||||
Z: 90,
|
||||
|
||||
INSERT_MAC: 96,
|
||||
|
||||
ASTERISK: 106,
|
||||
PLUS: 107,
|
||||
MINUS: 109,
|
||||
|
||||
F1: 112,
|
||||
F2: 113,
|
||||
F3: 114,
|
||||
F4: 115,
|
||||
F5: 116,
|
||||
F6: 117,
|
||||
F7: 118,
|
||||
F8: 119,
|
||||
F9: 120,
|
||||
F10: 121,
|
||||
|
||||
COLON: 186,
|
||||
EQUAL: 187,
|
||||
HYPHEN: 189,
|
||||
DOT: 190,
|
||||
SLASH: 191,
|
||||
/* Typewritten Reverse Apostrophe (`) */
|
||||
TRA: 192,
|
||||
BACKSLASH: 220,
|
||||
|
||||
BRACKET_CLOSE: 221,
|
||||
};
|
||||
60
client/key/set-current-by-char.js
Normal file
60
client/key/set-current-by-char.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/* global DOM */
|
||||
|
||||
'use strict';
|
||||
|
||||
const {escapeRegExp} = require('../../common/util');
|
||||
|
||||
module.exports = function setCurrentByChar(char, charStore) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
let firstByName;
|
||||
let skipCount = 0;
|
||||
let setted = false;
|
||||
let i = 0;
|
||||
|
||||
const escapeChar = escapeRegExp(char);
|
||||
const regExp = new RegExp(`^${escapeChar}.*$`, 'i');
|
||||
const {files} = Info;
|
||||
const chars = charStore();
|
||||
const n = chars.length;
|
||||
|
||||
while (i < n && char === chars[i])
|
||||
i++;
|
||||
|
||||
if (!i)
|
||||
charStore([]);
|
||||
|
||||
const skipN = skipCount = i;
|
||||
|
||||
charStore(charStore().concat(char));
|
||||
|
||||
const names = DOM.getFilenames(files);
|
||||
const isTest = (a) => regExp.test(a);
|
||||
const isRoot = (a) => a === '..';
|
||||
const not = (f) => (a) => !f(a);
|
||||
|
||||
const setCurrent = (name) => {
|
||||
const byName = DOM.getCurrentByName(name);
|
||||
|
||||
if (!skipCount) {
|
||||
setted = true;
|
||||
DOM.setCurrentFile(byName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (skipN === skipCount)
|
||||
firstByName = byName;
|
||||
|
||||
--skipCount;
|
||||
};
|
||||
|
||||
names
|
||||
.filter(isTest)
|
||||
.filter(not(isRoot))
|
||||
.some(setCurrent);
|
||||
|
||||
if (!setted) {
|
||||
DOM.setCurrentFile(firstByName);
|
||||
charStore([char]);
|
||||
}
|
||||
};
|
||||
49
client/key/vim/find.js
Normal file
49
client/key/vim/find.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
'use strict';
|
||||
|
||||
const {fullstore} = require('fullstore');
|
||||
const limier = require('limier');
|
||||
|
||||
const searchStore = fullstore([]);
|
||||
const searchIndex = fullstore(0);
|
||||
|
||||
module.exports.find = (value, names) => {
|
||||
const result = limier(value, names);
|
||||
|
||||
searchStore(result);
|
||||
searchIndex(0);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports.findNext = () => {
|
||||
const names = searchStore();
|
||||
const index = next(searchIndex(), names.length);
|
||||
|
||||
searchIndex(index);
|
||||
return names[searchIndex()];
|
||||
};
|
||||
|
||||
module.exports.findPrevious = () => {
|
||||
const names = searchStore();
|
||||
const index = previous(searchIndex(), names.length);
|
||||
|
||||
searchIndex(index);
|
||||
return names[index];
|
||||
};
|
||||
|
||||
module.exports._next = next;
|
||||
module.exports._previous = previous;
|
||||
|
||||
function next(index, length) {
|
||||
if (index === length - 1)
|
||||
return 0;
|
||||
|
||||
return ++index;
|
||||
}
|
||||
|
||||
function previous(index, length) {
|
||||
if (!index)
|
||||
return length - 1;
|
||||
|
||||
return --index;
|
||||
}
|
||||
24
client/key/vim/find.spec.js
Normal file
24
client/key/vim/find.spec.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('supertape');
|
||||
const dir = './';
|
||||
|
||||
const {getDOM} = require('./globals.fixture');
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
|
||||
const {_next, _previous} = require(`${dir}find`);
|
||||
|
||||
test('cloudcmd: client: vim: _next', (t) => {
|
||||
const result = _next(1, 2);
|
||||
|
||||
t.notOk(result, 'should return 0');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: _previous', (t) => {
|
||||
const result = _previous(0, 2);
|
||||
|
||||
t.equal(result, 1, 'should return 1');
|
||||
t.end();
|
||||
});
|
||||
48
client/key/vim/globals.fixture.js
Normal file
48
client/key/vim/globals.fixture.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
module.exports.getDOM = () => {
|
||||
const prompt = Promise.resolve.bind(Promise);
|
||||
const CurrentInfo = {
|
||||
element: {},
|
||||
files: [],
|
||||
};
|
||||
|
||||
const Buffer = {
|
||||
copy: noop,
|
||||
paste: noop,
|
||||
};
|
||||
|
||||
const Dialog = {
|
||||
prompt,
|
||||
};
|
||||
|
||||
return {
|
||||
Buffer,
|
||||
CurrentInfo,
|
||||
Dialog,
|
||||
selectFile: noop,
|
||||
unselectFile: noop,
|
||||
unselectFiles: noop,
|
||||
setCurrentFile: noop,
|
||||
getCurrentName: noop,
|
||||
setCurrentByName: noop,
|
||||
toggleSelectedFile: noop,
|
||||
prompNewDirectory: noop,
|
||||
promptNewFile: noop,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getCloudCmd = () => {
|
||||
const show = () => {};
|
||||
|
||||
return {
|
||||
Operation: {
|
||||
show,
|
||||
},
|
||||
|
||||
config: noop,
|
||||
_config: noop,
|
||||
};
|
||||
};
|
||||
140
client/key/vim/index.js
Normal file
140
client/key/vim/index.js
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
/* global DOM */
|
||||
const vim = require('./vim');
|
||||
const finder = require('./find');
|
||||
const {
|
||||
setCurrent,
|
||||
selectFileNotParent,
|
||||
} = require('./set-current');
|
||||
|
||||
module.exports = (key, event, overrides = {}) => {
|
||||
const defaults = {
|
||||
...globalThis.DOM,
|
||||
...globalThis.CloudCmd,
|
||||
};
|
||||
|
||||
const deps = {
|
||||
...defaults,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
const operations = getOperations(event, deps);
|
||||
|
||||
vim(key, operations, deps);
|
||||
};
|
||||
|
||||
const getOperations = (event, deps) => {
|
||||
const {
|
||||
Info = globalThis.DOM.CurrentInfo,
|
||||
CloudCmd = globalThis.CloudCmd,
|
||||
Operation,
|
||||
unselectFiles,
|
||||
setCurrentFile,
|
||||
setCurrentByName,
|
||||
getCurrentName,
|
||||
prompt = globalThis.DOM.Dialog.prompt,
|
||||
preventDefault = event?.preventDefault?.bind(event),
|
||||
|
||||
toggleSelectedFile,
|
||||
Buffer = {},
|
||||
createFindNext = _createFindNext,
|
||||
} = deps;
|
||||
|
||||
return {
|
||||
findNext: createFindNext({
|
||||
setCurrentByName,
|
||||
}),
|
||||
escape: unselectFiles,
|
||||
|
||||
remove: () => {
|
||||
Operation.show('delete');
|
||||
},
|
||||
|
||||
makeDirectory: () => {
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
DOM.promptNewDir();
|
||||
},
|
||||
|
||||
makeFile: () => {
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
DOM.promptNewFile();
|
||||
},
|
||||
|
||||
terminal: () => {
|
||||
CloudCmd.Terminal.show();
|
||||
},
|
||||
|
||||
edit: () => {
|
||||
CloudCmd.EditFileVim.show();
|
||||
},
|
||||
|
||||
copy: () => {
|
||||
Buffer.copy();
|
||||
unselectFiles();
|
||||
},
|
||||
|
||||
select: () => {
|
||||
const current = Info.element;
|
||||
toggleSelectedFile(current);
|
||||
},
|
||||
|
||||
paste: Buffer.paste,
|
||||
|
||||
moveNext: ({count, isVisual, isDelete}) => {
|
||||
setCurrent('next', {
|
||||
count,
|
||||
isVisual,
|
||||
isDelete,
|
||||
}, {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles,
|
||||
Operation,
|
||||
});
|
||||
},
|
||||
|
||||
movePrevious: ({count, isVisual, isDelete}) => {
|
||||
setCurrent('previous', {
|
||||
count,
|
||||
isVisual,
|
||||
isDelete,
|
||||
}, {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles,
|
||||
Operation,
|
||||
});
|
||||
},
|
||||
|
||||
find: async () => {
|
||||
preventDefault();
|
||||
const [, value] = await prompt('Find', '');
|
||||
|
||||
if (!value)
|
||||
return;
|
||||
|
||||
const names = Info.files.map(getCurrentName);
|
||||
const [result] = finder.find(value, names);
|
||||
|
||||
setCurrentByName(result);
|
||||
},
|
||||
|
||||
findPrevious: () => {
|
||||
const name = finder.findPrevious();
|
||||
setCurrentByName(name);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.selectFile = selectFileNotParent;
|
||||
|
||||
const _createFindNext = (overrides = {}) => () => {
|
||||
const {setCurrentByName} = overrides;
|
||||
const name = finder.findNext();
|
||||
|
||||
setCurrentByName(name);
|
||||
};
|
||||
681
client/key/vim/index.spec.js
Normal file
681
client/key/vim/index.spec.js
Normal file
|
|
@ -0,0 +1,681 @@
|
|||
'use strict';
|
||||
|
||||
const {join} = require('node:path');
|
||||
const {test, stub} = require('supertape');
|
||||
const mockRequire = require('mock-require');
|
||||
|
||||
const dir = '../';
|
||||
|
||||
const pathVim = join(dir, 'vim');
|
||||
|
||||
const {getDOM, getCloudCmd} = require('./globals.fixture');
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const vim = require('./index.js');
|
||||
|
||||
const {assign} = Object;
|
||||
const {DOM} = globalThis;
|
||||
const {Buffer} = DOM;
|
||||
const pathFind = join(dir, 'vim', 'find');
|
||||
const {reRequire, stopAll} = mockRequire;
|
||||
|
||||
test('cloudcmd: client: key: set next file: no', (t) => {
|
||||
const element = {};
|
||||
const setCurrentFile = stub();
|
||||
const unselectFiles = stub();
|
||||
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
vim('j', {}, {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles,
|
||||
});
|
||||
|
||||
t.calledWith(setCurrentFile, [element], 'should set next file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set next file current: j', async (t) => {
|
||||
const nextSibling = 'hello';
|
||||
const element = {
|
||||
nextSibling,
|
||||
};
|
||||
|
||||
const setCurrentFile = stub();
|
||||
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
await vim('j', {}, {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles: stub(),
|
||||
});
|
||||
|
||||
t.calledWith(setCurrentFile, [nextSibling], 'should set next file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set next file current: mjj', (t) => {
|
||||
const nextSibling = 'hello';
|
||||
const element = {
|
||||
nextSibling,
|
||||
};
|
||||
|
||||
const setCurrentFile = stub();
|
||||
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
const deps = {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles: stub(),
|
||||
};
|
||||
|
||||
vim('m', {}, deps);
|
||||
vim('j', {}, deps);
|
||||
vim('j', {}, deps);
|
||||
|
||||
t.calledWith(setCurrentFile, [nextSibling], 'should set next file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set next file current: g', (t) => {
|
||||
const nextSibling = 'hello';
|
||||
const element = {
|
||||
nextSibling,
|
||||
};
|
||||
|
||||
const setCurrentFile = stub();
|
||||
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
const deps = {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles: stub(),
|
||||
};
|
||||
|
||||
vim('g', {}, deps);
|
||||
vim('j', {}, deps);
|
||||
|
||||
t.calledWith(setCurrentFile, [nextSibling], 'should ignore g');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set +2 file current', (t) => {
|
||||
const last = {};
|
||||
const setCurrentFile = stub();
|
||||
const element = {};
|
||||
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
const deps = {
|
||||
setCurrentFile,
|
||||
Info,
|
||||
unselectFiles: stub(),
|
||||
};
|
||||
|
||||
const event = {};
|
||||
|
||||
vim('2', event, deps);
|
||||
vim('j', event, deps);
|
||||
|
||||
t.calledWith(setCurrentFile, [last], 'should set next file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: select +2 files from current before delete', (t) => {
|
||||
const last = {};
|
||||
const nextSibling = {
|
||||
nextSibling: last,
|
||||
};
|
||||
|
||||
const element = {
|
||||
nextSibling,
|
||||
};
|
||||
|
||||
const setCurrentFile = stub();
|
||||
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
const Operation = {
|
||||
show: stub(),
|
||||
};
|
||||
|
||||
const selectFile = stub();
|
||||
const getCurrentName = stub().returns('x');
|
||||
|
||||
const event = {};
|
||||
|
||||
const deps = {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
selectFile,
|
||||
getCurrentName,
|
||||
Operation,
|
||||
};
|
||||
|
||||
vim('d', event, deps);
|
||||
vim('2', event, deps);
|
||||
vim('j', event, deps);
|
||||
|
||||
t.calledWith(setCurrentFile, [last], 'should set next file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: delete +2 files from current', (t) => {
|
||||
const last = {};
|
||||
const nextSibling = {
|
||||
nextSibling: last,
|
||||
};
|
||||
|
||||
const element = {
|
||||
nextSibling,
|
||||
};
|
||||
|
||||
const setCurrentFile = stub();
|
||||
const show = stub();
|
||||
|
||||
const deps = {
|
||||
Info: {
|
||||
element,
|
||||
},
|
||||
Operation: {
|
||||
show,
|
||||
},
|
||||
setCurrentFile,
|
||||
selectFile: stub(),
|
||||
getCurrentName: stub().returns('x'),
|
||||
unselectFiles: stub(),
|
||||
};
|
||||
|
||||
const event = {};
|
||||
|
||||
vim('d', event, deps);
|
||||
vim('2', event, deps);
|
||||
vim('j', event, deps);
|
||||
|
||||
t.calledWith(show, ['delete'], 'should call delete');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set previous file current', (t) => {
|
||||
const previousSibling = 'hello';
|
||||
const element = {
|
||||
previousSibling,
|
||||
};
|
||||
|
||||
const setCurrentFile = stub();
|
||||
const unselectFiles = stub();
|
||||
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
const deps = {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles,
|
||||
};
|
||||
|
||||
vim('k', {}, deps);
|
||||
|
||||
t.calledWith(setCurrentFile, [previousSibling], 'should set previous file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: copy: no', (t) => {
|
||||
const copy = stub();
|
||||
|
||||
vim('y', {}, {
|
||||
unselectFiles: stub(),
|
||||
Buffer: {
|
||||
copy,
|
||||
},
|
||||
});
|
||||
|
||||
t.notCalled(copy, 'should not copy files');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: copy', (t) => {
|
||||
const copy = stub();
|
||||
const Info = {
|
||||
element: {},
|
||||
};
|
||||
|
||||
const toggleSelectedFile = stub();
|
||||
const unselectFiles = stub();
|
||||
|
||||
const deps = {
|
||||
Info,
|
||||
unselectFiles,
|
||||
toggleSelectedFile,
|
||||
Buffer: {
|
||||
copy,
|
||||
},
|
||||
};
|
||||
|
||||
vim('v', {}, deps);
|
||||
vim('y', {}, deps);
|
||||
|
||||
t.calledWithNoArgs(copy, 'should copy files');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: copy: unselectFiles', (t) => {
|
||||
const unselectFiles = stub();
|
||||
const Info = {
|
||||
element: {},
|
||||
};
|
||||
|
||||
const toggleSelectedFile = stub();
|
||||
|
||||
const deps = {
|
||||
Info,
|
||||
unselectFiles,
|
||||
toggleSelectedFile,
|
||||
Buffer: {
|
||||
copy: stub(),
|
||||
},
|
||||
};
|
||||
|
||||
vim('v', {}, deps);
|
||||
vim('y', {}, deps);
|
||||
|
||||
t.calledWithNoArgs(unselectFiles, 'should unselect files');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: paste', (t) => {
|
||||
const paste = stub();
|
||||
|
||||
Buffer.paste = paste;
|
||||
|
||||
vim('p', {}, {
|
||||
Buffer,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(paste, 'should paste files');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: selectFile: ..', (t) => {
|
||||
const getCurrentName = stub().returns('..');
|
||||
const selectFile = stub();
|
||||
const current = {};
|
||||
|
||||
vim.selectFile(current, {
|
||||
selectFile,
|
||||
getCurrentName,
|
||||
});
|
||||
|
||||
t.notCalled(selectFile, 'should not call selectFile');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: selectFile', (t) => {
|
||||
const selectFile = stub();
|
||||
const getCurrentName = stub().returns('x');
|
||||
const current = {};
|
||||
|
||||
vim.selectFile(current, {
|
||||
selectFile,
|
||||
getCurrentName,
|
||||
});
|
||||
|
||||
t.calledWith(selectFile, [current], 'should call selectFile');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set last file current: shift + g', async (t) => {
|
||||
const last = 'last';
|
||||
const nextSibling = {
|
||||
nextSibling: last,
|
||||
};
|
||||
|
||||
const element = {
|
||||
nextSibling,
|
||||
};
|
||||
|
||||
const setCurrentFile = stub();
|
||||
|
||||
await vim('G', {}, {
|
||||
Info: {
|
||||
element,
|
||||
},
|
||||
setCurrentFile,
|
||||
unselectFiles: stub(),
|
||||
});
|
||||
|
||||
t.calledWith(setCurrentFile, [last], 'should set last file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set last file current: $', (t) => {
|
||||
const last = 'last';
|
||||
const nextSibling = {
|
||||
nextSibling: last,
|
||||
};
|
||||
|
||||
const element = {
|
||||
nextSibling,
|
||||
};
|
||||
|
||||
const setCurrentFile = stub();
|
||||
|
||||
vim('$', {}, {
|
||||
Info: {
|
||||
element,
|
||||
},
|
||||
setCurrentFile,
|
||||
unselectFiles: stub(),
|
||||
});
|
||||
|
||||
t.calledWith(setCurrentFile, [last], 'should set last file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set first file current: gg', (t) => {
|
||||
const first = 'first';
|
||||
const previousSibling = {
|
||||
previousSibling: first,
|
||||
};
|
||||
|
||||
const element = {
|
||||
previousSibling,
|
||||
};
|
||||
|
||||
const Operation = {
|
||||
show: stub(),
|
||||
};
|
||||
|
||||
const unselectFiles = stub();
|
||||
const setCurrentFile = stub();
|
||||
|
||||
const deps = {
|
||||
Operation,
|
||||
unselectFiles,
|
||||
setCurrentFile,
|
||||
Info: {
|
||||
element,
|
||||
},
|
||||
};
|
||||
|
||||
vim('g', {}, deps);
|
||||
vim('g', {}, deps);
|
||||
|
||||
t.calledWith(setCurrentFile, [first], 'should set first file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: set first file current: ^', async (t) => {
|
||||
const first = 'first';
|
||||
const previousSibling = {
|
||||
previousSibling: first,
|
||||
};
|
||||
|
||||
const element = {
|
||||
previousSibling,
|
||||
};
|
||||
|
||||
const Operation = {
|
||||
show: stub(),
|
||||
};
|
||||
|
||||
const unselectFiles = stub();
|
||||
const setCurrentFile = stub();
|
||||
|
||||
const deps = {
|
||||
setCurrentFile,
|
||||
Info: {
|
||||
element,
|
||||
},
|
||||
unselectFiles,
|
||||
Operation,
|
||||
};
|
||||
|
||||
await vim('^', {}, deps);
|
||||
|
||||
t.calledWith(setCurrentFile, [first], 'should set first file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: visual', (t) => {
|
||||
const element = {};
|
||||
const toggleSelectedFile = stub();
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
vim('v', {}, {
|
||||
Info,
|
||||
toggleSelectedFile,
|
||||
});
|
||||
|
||||
t.calledWith(toggleSelectedFile, [element], 'should toggle selection');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: ESC', (t) => {
|
||||
const element = {};
|
||||
const unselectFiles = stub();
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
vim('Escape', null, {
|
||||
Info,
|
||||
unselectFiles,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(unselectFiles, 'should toggle selection');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: Enter', async (t) => {
|
||||
const nextSibling = 'hello';
|
||||
const element = {
|
||||
nextSibling,
|
||||
};
|
||||
|
||||
const unselectFiles = stub();
|
||||
const setCurrentFile = stub();
|
||||
|
||||
const Info = {
|
||||
element,
|
||||
};
|
||||
|
||||
await vim('Enter', null, {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles,
|
||||
});
|
||||
|
||||
await vim('j', null, {
|
||||
Info,
|
||||
setCurrentFile,
|
||||
unselectFiles,
|
||||
});
|
||||
|
||||
t.calledWith(setCurrentFile, [nextSibling], 'should set next file');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: /', (t) => {
|
||||
const preventDefault = stub();
|
||||
const element = {};
|
||||
const Info = {
|
||||
element,
|
||||
files: [],
|
||||
};
|
||||
|
||||
const getCurrentName = stub().returns('');
|
||||
|
||||
const event = {
|
||||
preventDefault,
|
||||
};
|
||||
|
||||
const prompt = stub().returns([]);
|
||||
|
||||
vim('/', event, {
|
||||
getCurrentName,
|
||||
Info,
|
||||
prompt,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(preventDefault);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: find', (t) => {
|
||||
assign(DOM.Dialog, {
|
||||
prompt: stub().returns([]),
|
||||
});
|
||||
|
||||
const setCurrentByName = stub();
|
||||
|
||||
assign(DOM, {
|
||||
setCurrentByName,
|
||||
});
|
||||
|
||||
const vim = reRequire(pathVim);
|
||||
|
||||
const event = {
|
||||
preventDefault: stub(),
|
||||
};
|
||||
|
||||
vim('/', event);
|
||||
|
||||
stopAll();
|
||||
|
||||
t.notCalled(setCurrentByName);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: n', (t) => {
|
||||
const findNext = stub();
|
||||
const createFindNext = stub().returns(findNext);
|
||||
|
||||
const event = {};
|
||||
|
||||
vim('n', event, {
|
||||
createFindNext,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(findNext, 'should call findNext');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: N', (t) => {
|
||||
const findPrevious = stub();
|
||||
|
||||
mockRequire(pathFind, {
|
||||
findPrevious,
|
||||
});
|
||||
|
||||
const vim = reRequire(`${dir}vim`);
|
||||
const event = {};
|
||||
|
||||
vim('N', event);
|
||||
|
||||
stopAll();
|
||||
|
||||
t.calledWithNoArgs(findPrevious, 'should call findPrevious');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: make directory', async (t) => {
|
||||
const vim = reRequire(pathVim);
|
||||
const {DOM} = globalThis;
|
||||
|
||||
assign(DOM, {
|
||||
promptNewDir: stub(),
|
||||
});
|
||||
|
||||
const event = {
|
||||
stopImmediatePropagation: stub(),
|
||||
preventDefault: stub(),
|
||||
};
|
||||
|
||||
await vim('m', event);
|
||||
await vim('d', event);
|
||||
|
||||
t.calledWithNoArgs(DOM.promptNewDir);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: make file', (t) => {
|
||||
const vim = reRequire(pathVim);
|
||||
const {DOM} = globalThis;
|
||||
|
||||
assign(DOM, {
|
||||
promptNewFile: stub(),
|
||||
});
|
||||
|
||||
const event = {
|
||||
stopImmediatePropagation: stub(),
|
||||
preventDefault: stub(),
|
||||
};
|
||||
|
||||
vim('m', event);
|
||||
vim('f', event);
|
||||
|
||||
t.calledWithNoArgs(DOM.promptNewDir);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: terminal', (t) => {
|
||||
const CloudCmd = {
|
||||
Terminal: {
|
||||
show: stub(),
|
||||
},
|
||||
};
|
||||
|
||||
const event = {};
|
||||
|
||||
vim('t', event, {
|
||||
CloudCmd,
|
||||
});
|
||||
vim('t', event, {
|
||||
CloudCmd,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(CloudCmd.Terminal.show);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: edit', async (t) => {
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const {CloudCmd} = globalThis;
|
||||
|
||||
assign(CloudCmd, {
|
||||
EditFileVim: {
|
||||
show: stub(),
|
||||
},
|
||||
});
|
||||
|
||||
const event = {};
|
||||
|
||||
await vim('e', event);
|
||||
|
||||
t.calledWithNoArgs(CloudCmd.EditFileVim.show);
|
||||
t.end();
|
||||
});
|
||||
36
client/key/vim/set-current.js
Normal file
36
client/key/vim/set-current.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
'use strict';
|
||||
|
||||
/* global DOM */
|
||||
module.exports.selectFileNotParent = selectFileNotParent;
|
||||
function selectFileNotParent(current, {getCurrentName, selectFile} = DOM) {
|
||||
const name = getCurrentName(current);
|
||||
|
||||
if (name === '..')
|
||||
return;
|
||||
|
||||
selectFile(current);
|
||||
}
|
||||
|
||||
module.exports.setCurrent = (sibling, {count, isVisual, isDelete}, {Info, setCurrentFile, unselectFiles, Operation}) => {
|
||||
let current = Info.element;
|
||||
const select = isVisual ? selectFileNotParent : unselectFiles;
|
||||
|
||||
select(current);
|
||||
|
||||
const position = `${sibling}Sibling`;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const next = current[position];
|
||||
|
||||
if (!next)
|
||||
break;
|
||||
|
||||
current = next;
|
||||
select(current);
|
||||
}
|
||||
|
||||
setCurrentFile(current);
|
||||
|
||||
if (isDelete)
|
||||
Operation.show('delete');
|
||||
};
|
||||
203
client/key/vim/vim.js
Normal file
203
client/key/vim/vim.js
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
'use strict';
|
||||
|
||||
const {fullstore} = require('fullstore');
|
||||
const store = fullstore('');
|
||||
const visual = fullstore(false);
|
||||
|
||||
const stopVisual = () => {
|
||||
visual(false);
|
||||
};
|
||||
|
||||
const end = () => {
|
||||
store('');
|
||||
};
|
||||
|
||||
const rmFirst = (a) => {
|
||||
return a
|
||||
.split('')
|
||||
.slice(1)
|
||||
.join('');
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
module.exports = (key, operations = {}) => {
|
||||
const prevStore = store();
|
||||
const isVisual = visual();
|
||||
const value = store(prevStore.concat(key));
|
||||
|
||||
const {
|
||||
escape = noop,
|
||||
moveNext = noop,
|
||||
movePrevious = noop,
|
||||
remove = noop,
|
||||
copy = noop,
|
||||
paste = noop,
|
||||
select = noop,
|
||||
find = noop,
|
||||
findNext = noop,
|
||||
findPrevious = noop,
|
||||
makeFile = noop,
|
||||
makeDirectory = noop,
|
||||
terminal = noop,
|
||||
edit = noop,
|
||||
} = operations;
|
||||
|
||||
if (key === 'Enter')
|
||||
return end();
|
||||
|
||||
if (key === 'Escape') {
|
||||
visual(false);
|
||||
escape();
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === 'j' || key === 'w') {
|
||||
const {
|
||||
count,
|
||||
isDelete,
|
||||
isVisual,
|
||||
} = handleDelete(prevStore);
|
||||
|
||||
!isNaN(count) && moveNext({
|
||||
count,
|
||||
isVisual,
|
||||
isDelete,
|
||||
});
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === 'k' || key === 'b') {
|
||||
const {
|
||||
count,
|
||||
isDelete,
|
||||
isVisual,
|
||||
} = handleDelete(prevStore);
|
||||
|
||||
!isNaN(count) && movePrevious({
|
||||
count,
|
||||
isVisual,
|
||||
isDelete,
|
||||
});
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
if (value === 'gg' || key === '^') {
|
||||
const {isDelete, isVisual} = handleDelete(prevStore);
|
||||
|
||||
movePrevious({
|
||||
count: Infinity,
|
||||
isVisual,
|
||||
isDelete,
|
||||
});
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
if (value === 'md') {
|
||||
makeDirectory();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (value === 'tt') {
|
||||
terminal();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (value === 'e') {
|
||||
edit();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (value === 'mf') {
|
||||
makeFile();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === 'd' && (visual() || prevStore === 'd')) {
|
||||
stopVisual();
|
||||
remove();
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === 'G' || key === '$') {
|
||||
moveNext({
|
||||
count: Infinity,
|
||||
isVisual,
|
||||
});
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === 'y') {
|
||||
if (!visual())
|
||||
return end();
|
||||
|
||||
stopVisual();
|
||||
copy();
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
if (/^p$/i.test(key)) {
|
||||
paste();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (/^v$/i.test(key)) {
|
||||
visual(!visual());
|
||||
select();
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === '/') {
|
||||
find();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === 'n') {
|
||||
findNext();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === 'N') {
|
||||
findPrevious();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (key === ' ')
|
||||
return end();
|
||||
};
|
||||
|
||||
function handleDelete(prevStore) {
|
||||
const isDelete = prevStore[0] === 'd';
|
||||
|
||||
if (isDelete) {
|
||||
visual(true);
|
||||
prevStore = rmFirst(prevStore);
|
||||
}
|
||||
|
||||
const count = getNumber(prevStore);
|
||||
const isVisual = visual();
|
||||
|
||||
return {
|
||||
count,
|
||||
isDelete,
|
||||
isVisual,
|
||||
};
|
||||
}
|
||||
|
||||
function getNumber(value) {
|
||||
if (!value)
|
||||
return 1;
|
||||
|
||||
if (value === 'g')
|
||||
return 1;
|
||||
|
||||
return parseInt(value);
|
||||
}
|
||||
81
client/key/vim/vim.spec.js
Normal file
81
client/key/vim/vim.spec.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
'use strict';
|
||||
|
||||
const {test, stub} = require('supertape');
|
||||
|
||||
const vim = require('./vim');
|
||||
|
||||
test('vim: no operations', (t) => {
|
||||
const result = vim('hello', {});
|
||||
|
||||
t.notOk(result);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('vim: space', (t) => {
|
||||
const moveNext = stub();
|
||||
|
||||
vim(' ');
|
||||
vim('j', {
|
||||
moveNext,
|
||||
});
|
||||
|
||||
const args = [{
|
||||
count: 1,
|
||||
isDelete: false,
|
||||
isVisual: false,
|
||||
}];
|
||||
|
||||
t.calledWith(moveNext, args);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('vim: ^', (t) => {
|
||||
const movePrevious = stub();
|
||||
|
||||
vim('^', {
|
||||
movePrevious,
|
||||
});
|
||||
|
||||
const expected = {
|
||||
count: Infinity,
|
||||
isVisual: false,
|
||||
isDelete: false,
|
||||
};
|
||||
|
||||
t.calledWith(movePrevious, [expected], 'should call movePrevious');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('vim: w', (t) => {
|
||||
const moveNext = stub();
|
||||
|
||||
vim('w', {
|
||||
moveNext,
|
||||
});
|
||||
|
||||
const expected = {
|
||||
count: 1,
|
||||
isVisual: false,
|
||||
isDelete: false,
|
||||
};
|
||||
|
||||
t.calledWith(moveNext, [expected], 'should call moveNext');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('vim: b', (t) => {
|
||||
const movePrevious = stub();
|
||||
|
||||
vim('b', {
|
||||
movePrevious,
|
||||
});
|
||||
|
||||
const expected = {
|
||||
count: 1,
|
||||
isVisual: false,
|
||||
isDelete: false,
|
||||
};
|
||||
|
||||
t.calledWith(movePrevious, [expected], 'should call movePrevious');
|
||||
t.end();
|
||||
});
|
||||
10
client/listeners/get-index.js
Normal file
10
client/listeners/get-index.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = (array, item) => {
|
||||
const index = array.indexOf(item);
|
||||
|
||||
if (!~index)
|
||||
return 0;
|
||||
|
||||
return index;
|
||||
};
|
||||
11
client/listeners/get-range.js
Normal file
11
client/listeners/get-range.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = (indexFrom, indexTo, files) => {
|
||||
if (indexFrom < indexTo)
|
||||
return files.slice(indexFrom, indexTo + 1);
|
||||
|
||||
if (indexFrom > indexTo)
|
||||
return files.slice(indexTo, indexFrom + 1);
|
||||
|
||||
return [files[indexFrom]];
|
||||
};
|
||||
504
client/listeners/index.js
Normal file
504
client/listeners/index.js
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
/* global DOM, CloudCmd */
|
||||
|
||||
'use strict';
|
||||
|
||||
const exec = require('execon');
|
||||
const itype = require('itype');
|
||||
const currify = require('currify');
|
||||
const {tryToCatch} = require('try-to-catch');
|
||||
const clipboard = require('@cloudcmd/clipboard');
|
||||
|
||||
const getRange = require('./get-range');
|
||||
const uploadFiles = require('../dom/upload-files');
|
||||
const {FS} = require('../../common/cloudfunc.mjs');
|
||||
const Events = require('#dom/events');
|
||||
|
||||
const getIndex = currify(require('./get-index'));
|
||||
|
||||
const NBSP_REG = RegExp(String.fromCharCode(160), 'g');
|
||||
const SPACE = ' ';
|
||||
|
||||
module.exports.init = async () => {
|
||||
await Promise.all([
|
||||
contextMenu(),
|
||||
dragndrop(),
|
||||
unload(),
|
||||
pop(),
|
||||
resize(),
|
||||
header(),
|
||||
config(),
|
||||
]);
|
||||
};
|
||||
|
||||
const unselect = (event) => {
|
||||
const isMac = /Mac/.test(globalThis.navigator.platform);
|
||||
const {
|
||||
shiftKey,
|
||||
metaKey,
|
||||
ctrlKey,
|
||||
} = event;
|
||||
|
||||
if (shiftKey || isMac && metaKey || ctrlKey)
|
||||
return;
|
||||
|
||||
DOM.unselectFiles();
|
||||
};
|
||||
|
||||
const execAll = currify((funcs, event) => {
|
||||
for (const fn of funcs)
|
||||
fn(event);
|
||||
});
|
||||
|
||||
const EventsFiles = {
|
||||
mousedown: exec.with(execIfNotUL, setCurrentFileByEvent),
|
||||
click: execAll([onClick, exec.with(execIfNotMobile, unselect)]),
|
||||
dragstart: exec.with(execIfNotUL, onDragStart),
|
||||
dblclick: exec.with(execIfNotUL, onDblClick),
|
||||
touchstart: exec.with(execIfNotUL, onTouch),
|
||||
};
|
||||
|
||||
let EXT;
|
||||
|
||||
function header() {
|
||||
const fm = DOM.getFM();
|
||||
const isDataset = (el) => el.dataset;
|
||||
const isPanel = (el) => {
|
||||
return /^js-(left|right)$/.test(el.dataset.name);
|
||||
};
|
||||
|
||||
Events.addClick(fm, (event) => {
|
||||
const el = event.target;
|
||||
const parent = el.parentElement;
|
||||
|
||||
if (parent.dataset.name !== 'js-fm-header')
|
||||
return;
|
||||
|
||||
const name = (el.dataset.name || '').replace('js-', '');
|
||||
|
||||
if (!/^(name|size|date)$/.test(name))
|
||||
return;
|
||||
|
||||
const panel = getPath(el)
|
||||
.filter(isDataset)
|
||||
.filter(isPanel)
|
||||
.pop();
|
||||
|
||||
CloudCmd.sortPanel(name, panel);
|
||||
});
|
||||
}
|
||||
|
||||
function getPath(el, path = []) {
|
||||
if (!el)
|
||||
return path;
|
||||
|
||||
return getPath(el.parentElement, path.concat(el));
|
||||
}
|
||||
|
||||
async function config() {
|
||||
const [, config] = await tryToCatch(DOM.Files.get, 'config');
|
||||
const type = config?.packer;
|
||||
|
||||
EXT = DOM.getPackerExt(type);
|
||||
}
|
||||
|
||||
module.exports.initKeysPanel = () => {
|
||||
const keysElement = DOM.getById('js-keyspanel');
|
||||
|
||||
if (!keysElement)
|
||||
return;
|
||||
|
||||
Events.addClick(keysElement, (event) => {
|
||||
const {target} = event;
|
||||
const {id} = target;
|
||||
|
||||
const operation = (name) => {
|
||||
const {Operation} = CloudCmd;
|
||||
|
||||
return Operation.show.bind(null, name);
|
||||
};
|
||||
|
||||
const clickFuncs = {
|
||||
'f1': CloudCmd.Help.show,
|
||||
'f2': CloudCmd.UserMenu.show,
|
||||
'f3': CloudCmd.View.show,
|
||||
'f4': CloudCmd.EditFile.show,
|
||||
'f5': operation('copy'),
|
||||
'f6': operation('move'),
|
||||
'f7': DOM.promptNewDir,
|
||||
'f8': operation('delete'),
|
||||
'f9': () => {
|
||||
event.stopPropagation();
|
||||
CloudCmd.Menu.show();
|
||||
},
|
||||
'f10': CloudCmd.Config.show,
|
||||
'~': CloudCmd.Konsole.show,
|
||||
'shift~': CloudCmd.Terminal.show,
|
||||
'contact': CloudCmd.Contact.show,
|
||||
};
|
||||
|
||||
exec(clickFuncs[id]);
|
||||
});
|
||||
};
|
||||
|
||||
const getPanel = (side) => {
|
||||
if (!itype.string(side))
|
||||
return side;
|
||||
|
||||
return DOM.getByDataName(`js-${side}`);
|
||||
};
|
||||
|
||||
module.exports.setOnPanel = (side) => {
|
||||
const panel = getPanel(side);
|
||||
|
||||
const filesElement = DOM.getByDataName('js-files', panel);
|
||||
const pathElement = DOM.getByDataName('js-path', panel);
|
||||
|
||||
/* ставим загрузку гифа на клик*/
|
||||
Events.addClick(pathElement, getPathListener(panel));
|
||||
Events.add(filesElement, EventsFiles);
|
||||
};
|
||||
|
||||
function getPathListener(panel) {
|
||||
return onPathElementClick.bind(null, panel);
|
||||
}
|
||||
|
||||
function isNoCurrent(panel) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const infoPanel = Info.panel;
|
||||
|
||||
if (!infoPanel)
|
||||
return true;
|
||||
|
||||
const namePanel = panel.getAttribute('data-name');
|
||||
const nameInfoPanel = infoPanel.getAttribute('data-name');
|
||||
|
||||
return namePanel !== nameInfoPanel;
|
||||
}
|
||||
|
||||
function decodePath(path) {
|
||||
const url = CloudCmd.HOST;
|
||||
const {prefix} = CloudCmd;
|
||||
const prefixReg = RegExp('^' + prefix + FS);
|
||||
|
||||
return decodeURI(path)
|
||||
.replace(url, '')
|
||||
.replace(prefixReg, '') // browser doesn't replace % -> %25% do it for him
|
||||
.replace('%%', '%25%')
|
||||
.replace(NBSP_REG, SPACE) || '/';
|
||||
}
|
||||
|
||||
async function onPathElementClick(panel, event) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
event.preventDefault();
|
||||
|
||||
const element = event.target;
|
||||
const attr = element.getAttribute('data-name');
|
||||
const noCurrent = isNoCurrent(panel);
|
||||
|
||||
if (attr === 'js-copy-path')
|
||||
return copyPath(element);
|
||||
|
||||
if (attr === 'js-refresh')
|
||||
return CloudCmd.refresh({
|
||||
panel,
|
||||
noCurrent,
|
||||
});
|
||||
|
||||
if (attr !== 'js-path-link')
|
||||
return;
|
||||
|
||||
const {href} = element;
|
||||
const path = decodePath(href);
|
||||
|
||||
await CloudCmd.changeDir(path, {
|
||||
isRefresh: false,
|
||||
panel: noCurrent ? panel : Info.panel,
|
||||
});
|
||||
}
|
||||
|
||||
function copyPath(el) {
|
||||
clipboard
|
||||
.writeText(el.parentElement.title)
|
||||
.then(CloudCmd.log)
|
||||
.catch(CloudCmd.log);
|
||||
}
|
||||
|
||||
function execIfNotMobile(callback, event) {
|
||||
const isMobile = DOM.getCSSVar('is-mobile');
|
||||
|
||||
if (!isMobile)
|
||||
callback(event);
|
||||
}
|
||||
|
||||
function execIfNotUL(callback, event) {
|
||||
const {target} = event;
|
||||
const {tagName} = target;
|
||||
|
||||
if (tagName !== 'UL')
|
||||
callback(event);
|
||||
}
|
||||
|
||||
function onClick(event) {
|
||||
event.preventDefault();
|
||||
changePanel(event.target);
|
||||
}
|
||||
|
||||
function toggleSelect(key, files) {
|
||||
const isMac = /Mac/.test(globalThis.navigator.platform);
|
||||
|
||||
if (!key)
|
||||
throw Error('key should not be undefined!');
|
||||
|
||||
const [file] = files;
|
||||
|
||||
if (isMac && key.meta)
|
||||
return DOM.toggleSelectedFile(file);
|
||||
|
||||
if (key.shift)
|
||||
return files.map(DOM.selectFile);
|
||||
}
|
||||
|
||||
function changePanel(element) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const {panel} = Info;
|
||||
const files = DOM.getByDataName('js-files', panel);
|
||||
const ul = getULElement(element);
|
||||
|
||||
if (ul !== files)
|
||||
DOM.changePanel();
|
||||
}
|
||||
|
||||
async function onDblClick(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const current = getLIElement(event.target);
|
||||
const isDir = DOM.isCurrentIsDir(current);
|
||||
const path = DOM.getCurrentPath(current);
|
||||
|
||||
if (!isDir)
|
||||
return CloudCmd.View.show();
|
||||
|
||||
await CloudCmd.changeDir(path);
|
||||
}
|
||||
|
||||
async function onTouch(event) {
|
||||
const current = getLIElement(event.target);
|
||||
const isDir = DOM.isCurrentIsDir(current);
|
||||
|
||||
if (!isDir)
|
||||
return;
|
||||
|
||||
const isCurrent = DOM.isCurrentFile(current);
|
||||
|
||||
if (!isCurrent)
|
||||
return;
|
||||
|
||||
await CloudCmd.changeDir(DOM.getCurrentPath(current));
|
||||
}
|
||||
|
||||
/*
|
||||
* download file from browser to desktop
|
||||
* in Chrome (HTML5)
|
||||
*/
|
||||
function onDragStart(event) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const {prefixURL} = CloudCmd;
|
||||
const element = getLIElement(event.target);
|
||||
const {isDir} = Info;
|
||||
let link = DOM.getCurrentLink(element);
|
||||
let name = DOM.getCurrentName(element);
|
||||
|
||||
/* if it's directory - adding json extension */
|
||||
if (isDir) {
|
||||
name += EXT;
|
||||
link = document.createElement('a');
|
||||
link.textContent = name;
|
||||
link.href = prefixURL + '/pack' + Info.path + EXT;
|
||||
}
|
||||
|
||||
event.dataTransfer.setData('DownloadURL', 'application/octet-stream' + ':' + name +
|
||||
':' + link);
|
||||
}
|
||||
|
||||
function getLIElement(element) {
|
||||
if (!element)
|
||||
return element;
|
||||
|
||||
while (element.tagName !== 'LI')
|
||||
element = element.parentElement;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function getULElement(element) {
|
||||
while (element.tagName !== 'UL')
|
||||
element = element.parentElement;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function setCurrentFileByEvent(event) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const BUTTON_LEFT = 0;
|
||||
|
||||
const key = {
|
||||
alt: event.altKey,
|
||||
ctrl: event.ctrlKey,
|
||||
meta: event.metaKey,
|
||||
shift: event.shiftKey,
|
||||
};
|
||||
|
||||
const element = getLIElement(event.target);
|
||||
|
||||
const fromName = Info.name;
|
||||
DOM.setCurrentFile(element);
|
||||
const toName = Info.name;
|
||||
|
||||
let files = [];
|
||||
|
||||
if (key.shift)
|
||||
files = getFilesRange(fromName, toName);
|
||||
else
|
||||
files.push(Info.element);
|
||||
|
||||
if (event.button === BUTTON_LEFT)
|
||||
toggleSelect(key, files);
|
||||
}
|
||||
|
||||
function getFilesRange(from, to) {
|
||||
const files = DOM.getAllFiles();
|
||||
const names = DOM.getFilenames(files);
|
||||
const getNameIndex = getIndex(names);
|
||||
|
||||
const indexFrom = getNameIndex(from);
|
||||
const indexTo = getNameIndex(to);
|
||||
|
||||
return getRange(indexFrom, indexTo, files);
|
||||
}
|
||||
|
||||
function contextMenu() {
|
||||
const fm = DOM.getFM();
|
||||
|
||||
Events.addOnce('contextmenu', fm, (event) => {
|
||||
CloudCmd.Menu.show({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
});
|
||||
|
||||
Events.addContextMenu(fm, (event) => {
|
||||
CloudCmd.Menu.ENABLED || event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function dragndrop() {
|
||||
const panels = DOM.getByClassAll('panel');
|
||||
const select = ({target}) => {
|
||||
target.classList.add('selected-panel');
|
||||
};
|
||||
|
||||
const unselect = ({target}) => {
|
||||
target.classList.remove('selected-panel');
|
||||
};
|
||||
|
||||
const onDrop = (event) => {
|
||||
const {files, items} = event.dataTransfer;
|
||||
|
||||
const {length: filesCount} = files;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (filesCount && (!items || !items.length || !items[0].webkitGetAsEntry))
|
||||
return uploadFiles(files);
|
||||
|
||||
const isFile = (item) => item.kind === 'file';
|
||||
|
||||
const dirFiles = Array
|
||||
.from(items)
|
||||
.filter(isFile);
|
||||
|
||||
if (dirFiles.length)
|
||||
return DOM.uploadDirectory(dirFiles);
|
||||
|
||||
const {Operation} = CloudCmd;
|
||||
const operation = event.shiftKey ? 'move' : 'copy';
|
||||
|
||||
return Operation.show(operation);
|
||||
};
|
||||
|
||||
/**
|
||||
* In macOS Chrome dropEffect = 'none'
|
||||
* so drop do not firing up when try
|
||||
* to upload file from download bar
|
||||
*/
|
||||
const onDragOver = (event) => {
|
||||
const {dataTransfer} = event;
|
||||
const {effectAllowed} = dataTransfer;
|
||||
|
||||
if (/move|linkMove/.test(effectAllowed))
|
||||
dataTransfer.dropEffect = 'move';
|
||||
else
|
||||
dataTransfer.dropEffect = 'copy';
|
||||
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
for (const panel of panels)
|
||||
Events
|
||||
.add('dragover', panel, onDragOver)
|
||||
.add('drop', panel, onDrop)
|
||||
.add('dragenter', select)
|
||||
.add(['dragleave', 'drop'], unselect);
|
||||
}
|
||||
|
||||
function unload() {
|
||||
Events.add(['unload', 'beforeunload'], (event) => {
|
||||
const {Key} = CloudCmd;
|
||||
const isBind = Key?.isBind();
|
||||
|
||||
if (isBind)
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
return 'Please make sure that you saved all work.';
|
||||
});
|
||||
}
|
||||
|
||||
function pop() {
|
||||
Events.add('popstate', async ({state}) => {
|
||||
const path = (state || '').replace(FS, '');
|
||||
|
||||
if (!path)
|
||||
return CloudCmd.route(location.hash);
|
||||
|
||||
const history = false;
|
||||
|
||||
await CloudCmd.changeDir(path, {
|
||||
history,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resize() {
|
||||
Events.add('resize', () => {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const is = globalThis.innerWidth < CloudCmd.MIN_ONE_PANEL_WIDTH;
|
||||
|
||||
if (!is)
|
||||
return;
|
||||
|
||||
const {panel} = Info;
|
||||
const isEmptyRoot = !panel;
|
||||
|
||||
if (isEmptyRoot)
|
||||
return;
|
||||
|
||||
const name = panel.getAttribute('data-name');
|
||||
const isLeft = name === 'js-left';
|
||||
|
||||
if (isLeft)
|
||||
return;
|
||||
|
||||
DOM.changePanel();
|
||||
});
|
||||
}
|
||||
56
client/load-module.mjs
Normal file
56
client/load-module.mjs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* global CloudCmd */
|
||||
import exec from 'execon';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import {js as loadJS} from 'load.js';
|
||||
import pascalCase from 'just-pascal-case';
|
||||
|
||||
const noJS = (a) => a.replace(/.js$/, '');
|
||||
|
||||
/**
|
||||
* function load modules
|
||||
* @params = {name, path, func, dobefore, arg}
|
||||
*/
|
||||
export const loadModule = (params) => {
|
||||
if (!params)
|
||||
return;
|
||||
|
||||
const {path} = params;
|
||||
|
||||
const name = path && noJS(pascalCase(path));
|
||||
const doBefore = params.dobefore;
|
||||
|
||||
if (CloudCmd[name])
|
||||
return;
|
||||
|
||||
CloudCmd[name] = async () => {
|
||||
exec(doBefore);
|
||||
|
||||
const {DIR_MODULES} = CloudCmd;
|
||||
const pathFull = `${DIR_MODULES}/${path}.js`;
|
||||
|
||||
await loadJS(pathFull);
|
||||
const newModule = async (f) => f && f();
|
||||
const module = CloudCmd[name];
|
||||
|
||||
Object.assign(newModule, module);
|
||||
|
||||
CloudCmd[name] = newModule;
|
||||
CloudCmd.log('init', name);
|
||||
|
||||
await module.init();
|
||||
|
||||
return newModule;
|
||||
};
|
||||
|
||||
CloudCmd[name].show = async (...args) => {
|
||||
CloudCmd.log('show', name, args);
|
||||
const m = CloudCmd[name];
|
||||
|
||||
const [e, a] = await tryToCatch(m);
|
||||
|
||||
if (e)
|
||||
return;
|
||||
|
||||
return await a.show(...args);
|
||||
};
|
||||
};
|
||||
64
client/modules/cloud.js
Normal file
64
client/modules/cloud.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/* global CloudCmd, filepicker */
|
||||
|
||||
'use strict';
|
||||
|
||||
const exec = require('execon');
|
||||
const currify = require('currify');
|
||||
const load = require('load.js');
|
||||
|
||||
const {ajax} = require('../dom/load');
|
||||
|
||||
const Files = require('../dom/files');
|
||||
const Images = require('../dom/images.mjs');
|
||||
const {log} = CloudCmd;
|
||||
|
||||
const upload = currify(_upload);
|
||||
|
||||
const Name = 'Cloud';
|
||||
|
||||
CloudCmd[Name] = module.exports;
|
||||
|
||||
module.exports.init = async () => {
|
||||
const [modules] = await loadFiles();
|
||||
const {key} = modules.data.FilePicker;
|
||||
|
||||
filepicker.setKey(key);
|
||||
Images.hide();
|
||||
};
|
||||
|
||||
module.exports.uploadFile = (filename, data) => {
|
||||
const mimetype = '';
|
||||
|
||||
filepicker.store(data, {
|
||||
mimetype,
|
||||
filename,
|
||||
}, (fpFile) => {
|
||||
filepicker.exportFile(fpFile, log, log);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.saveFile = (callback) => {
|
||||
filepicker.pick(upload(callback));
|
||||
};
|
||||
|
||||
function _upload(callback, file) {
|
||||
const {url, filename} = file;
|
||||
|
||||
const responseType = 'arraybuffer';
|
||||
const success = exec.with(callback, filename);
|
||||
|
||||
ajax({
|
||||
url,
|
||||
responseType,
|
||||
success,
|
||||
});
|
||||
}
|
||||
|
||||
function loadFiles() {
|
||||
const js = '//api.filepicker.io/v2/filepicker.js';
|
||||
|
||||
return Promise.all([
|
||||
Files.get('modules'),
|
||||
load.js(js),
|
||||
]);
|
||||
}
|
||||
35
client/modules/command-line.js
Normal file
35
client/modules/command-line.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
CloudCmd.CommandLine = exports;
|
||||
|
||||
const Dialog = require('../dom/dialog');
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
module.exports.init = noop;
|
||||
|
||||
module.exports.show = show;
|
||||
module.exports.hide = hide;
|
||||
|
||||
async function show() {
|
||||
const [, cmd] = await Dialog.prompt('Command Line', '');
|
||||
const TERMINAL = '^(t|terminal)';
|
||||
|
||||
if (RegExp(`${TERMINAL}$`).test(cmd))
|
||||
return await CloudCmd.Terminal.show();
|
||||
|
||||
if (RegExp(TERMINAL).test(cmd)) {
|
||||
const command = cmd.replace(RegExp(`${TERMINAL} `), '');
|
||||
const exitCode = await CloudCmd.TerminalRun.show({
|
||||
command: `bash -c '${command}'`,
|
||||
});
|
||||
|
||||
if (exitCode === -1)
|
||||
await Dialog.alert(`☝️ Looks like Terminal is disabled, start Cloud Coammnder with '--terminal' flag.`);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {}
|
||||
248
client/modules/config/index.js
Normal file
248
client/modules/config/index.js
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd, DOM, io */
|
||||
require('../../../css/config.css');
|
||||
|
||||
const rendy = require('rendy');
|
||||
const currify = require('currify');
|
||||
const wraptile = require('wraptile');
|
||||
const squad = require('squad');
|
||||
const {promisify} = require('es6-promisify');
|
||||
const {tryToCatch} = require('try-to-catch');
|
||||
const load = require('load.js');
|
||||
const createElement = require('@cloudcmd/create-element');
|
||||
|
||||
const input = require('./input');
|
||||
const Images = require('../../dom/images.mjs');
|
||||
const Events = require('#dom/events');
|
||||
const Files = require('../../dom/files');
|
||||
|
||||
const {getTitle} = require('../../../common/cloudfunc.mjs');
|
||||
const {Dialog, setTitle} = DOM;
|
||||
|
||||
const Name = 'Config';
|
||||
|
||||
CloudCmd[Name] = module.exports;
|
||||
|
||||
const loadSocket = promisify(DOM.loadSocket);
|
||||
|
||||
const showLoad = () => {
|
||||
Images.show.load('top');
|
||||
};
|
||||
|
||||
const addKey = currify((fn, input) => {
|
||||
Events.addKey(input, fn);
|
||||
return input;
|
||||
});
|
||||
|
||||
const addChange = currify((fn, input) => {
|
||||
Events.add('change', input, fn);
|
||||
return input;
|
||||
});
|
||||
|
||||
const Config = {};
|
||||
|
||||
let Template;
|
||||
|
||||
const loadCSS = load.css;
|
||||
|
||||
module.exports.init = async () => {
|
||||
if (!CloudCmd.config('configDialog'))
|
||||
return;
|
||||
|
||||
showLoad();
|
||||
|
||||
const {DIR_DIST} = CloudCmd;
|
||||
|
||||
[Template] = await Promise.all([
|
||||
Files.get('config-tmpl'),
|
||||
loadSocket(),
|
||||
loadCSS(`${DIR_DIST}/config.css`),
|
||||
CloudCmd.View(),
|
||||
]);
|
||||
|
||||
initSocket();
|
||||
};
|
||||
|
||||
const {config, Key} = CloudCmd;
|
||||
|
||||
let Element;
|
||||
|
||||
function getHost() {
|
||||
const {
|
||||
host,
|
||||
origin,
|
||||
protocol,
|
||||
} = location;
|
||||
|
||||
return origin || `${protocol}//${host}`;
|
||||
}
|
||||
|
||||
function initSocket() {
|
||||
const href = getHost();
|
||||
const {prefixSocket, prefix} = CloudCmd;
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
const socket = io.connect(href + prefixSocket + '/config', {
|
||||
reconnectionAttempts: Infinity,
|
||||
reconnectionDelay: ONE_MINUTE,
|
||||
path: `${prefix}/socket.io`,
|
||||
});
|
||||
|
||||
const save = (data) => {
|
||||
onSave(data);
|
||||
socket.send(data);
|
||||
};
|
||||
|
||||
authCheck(socket);
|
||||
|
||||
socket.on('connect', () => {
|
||||
Config.save = save;
|
||||
});
|
||||
|
||||
socket.on('message', onSave);
|
||||
socket.on('log', CloudCmd.log);
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
Config.save = saveHttp;
|
||||
});
|
||||
|
||||
socket.on('err', Dialog.alert);
|
||||
}
|
||||
|
||||
function authCheck(socket) {
|
||||
socket.emit('auth', config('username'), config('password'));
|
||||
socket.on('reject', wraptile(Dialog.alert, 'Wrong credentials!'));
|
||||
}
|
||||
|
||||
Config.save = saveHttp;
|
||||
|
||||
module.exports.show = show;
|
||||
|
||||
async function show() {
|
||||
if (!CloudCmd.config('configDialog'))
|
||||
return;
|
||||
|
||||
await fillTemplate();
|
||||
}
|
||||
|
||||
async function fillTemplate() {
|
||||
const [error, config] = await tryToCatch(Files.get, 'config');
|
||||
|
||||
if (error)
|
||||
return Dialog.alert('Could not load config!');
|
||||
|
||||
const {
|
||||
editor,
|
||||
menu,
|
||||
packer,
|
||||
columns,
|
||||
theme,
|
||||
configAuth,
|
||||
...obj
|
||||
} = input.convert(config);
|
||||
|
||||
obj[`${menu}-selected`] = 'selected';
|
||||
obj[`${editor}-selected`] = 'selected';
|
||||
obj[`${packer}-selected`] = 'selected';
|
||||
obj[`${columns}-selected`] = 'selected';
|
||||
obj[`${theme}-selected`] = 'selected';
|
||||
obj.configAuth = configAuth ? '' : 'hidden';
|
||||
|
||||
const innerHTML = rendy(Template, obj);
|
||||
|
||||
Element = createElement('form', {
|
||||
className: 'config',
|
||||
innerHTML,
|
||||
});
|
||||
|
||||
const inputs = document.querySelectorAll('input, select', Element);
|
||||
const [inputFirst] = inputs;
|
||||
|
||||
let afterShow;
|
||||
|
||||
if (inputFirst) {
|
||||
onAuthChange(inputFirst.checked);
|
||||
afterShow = inputFirst.focus.bind(inputFirst);
|
||||
}
|
||||
|
||||
const getTarget = ({target}) => target;
|
||||
const handleChange = squad(onChange, getTarget);
|
||||
|
||||
Array
|
||||
.from(inputs)
|
||||
.map(addKey(onKey))
|
||||
.map(addChange(handleChange));
|
||||
|
||||
const autoSize = true;
|
||||
|
||||
CloudCmd.View.show(Element, {
|
||||
autoSize,
|
||||
afterShow,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.hide = hide;
|
||||
|
||||
function hide() {
|
||||
CloudCmd.View.hide();
|
||||
}
|
||||
|
||||
async function onChange(el) {
|
||||
const obj = {};
|
||||
const name = input.getName(el);
|
||||
const data = input.getValue(name, Element);
|
||||
|
||||
if (name === 'name')
|
||||
onNameChange(data);
|
||||
else if (name === 'auth')
|
||||
onAuthChange(data);
|
||||
|
||||
obj[name] = data;
|
||||
|
||||
await Config.save(obj);
|
||||
}
|
||||
|
||||
function onSave(obj) {
|
||||
for (const name of Object.keys(obj)) {
|
||||
const data = obj[name];
|
||||
|
||||
CloudCmd._config(name, data);
|
||||
input.setValue(name, data, Element);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHttp(obj) {
|
||||
const {RESTful} = DOM;
|
||||
const [e] = await RESTful.Config.write(obj);
|
||||
|
||||
if (e)
|
||||
return;
|
||||
|
||||
onSave(obj);
|
||||
}
|
||||
|
||||
function onAuthChange(checked) {
|
||||
const elUsername = input.getElementByName('username', Element);
|
||||
const elPassword = input.getElementByName('password', Element);
|
||||
|
||||
elUsername.disabled = !checked;
|
||||
elPassword.disabled = !checked;
|
||||
}
|
||||
|
||||
function onNameChange(name) {
|
||||
setTitle(getTitle({
|
||||
name,
|
||||
}));
|
||||
}
|
||||
|
||||
async function onKey({keyCode, target}) {
|
||||
switch(keyCode) {
|
||||
case Key.ESC:
|
||||
return hide();
|
||||
|
||||
case Key.ENTER:
|
||||
return await onChange(target);
|
||||
}
|
||||
}
|
||||
75
client/modules/config/input.js
Normal file
75
client/modules/config/input.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
'use strict';
|
||||
|
||||
const currify = require('currify');
|
||||
|
||||
const isType = currify((type, object, name) => type === typeof object[name]);
|
||||
|
||||
const isBool = isType('boolean');
|
||||
|
||||
module.exports.getElementByName = getElementByName;
|
||||
|
||||
function getElementByName(selector, element) {
|
||||
const str = `[data-name="js-${selector}"]`;
|
||||
|
||||
return element.querySelector(str);
|
||||
}
|
||||
|
||||
module.exports.getName = (element) => {
|
||||
const name = element
|
||||
.getAttribute('data-name')
|
||||
.replace(/^js-/, '');
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
module.exports.convert = (config) => {
|
||||
const result = config;
|
||||
const array = Object.keys(config);
|
||||
|
||||
const filtered = array.filter(isBool(config));
|
||||
|
||||
for (const name of filtered) {
|
||||
const item = config[name];
|
||||
result[name] = setState(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
function setState(state) {
|
||||
if (state)
|
||||
return ' checked';
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
module.exports.getValue = (name, element) => {
|
||||
const el = getElementByName(name, element);
|
||||
const {type} = el;
|
||||
|
||||
switch(type) {
|
||||
case 'checkbox':
|
||||
return el.checked;
|
||||
|
||||
case 'number':
|
||||
return Number(el.value);
|
||||
|
||||
default:
|
||||
return el.value;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.setValue = (name, value, element) => {
|
||||
const el = getElementByName(name, element);
|
||||
const {type} = el;
|
||||
|
||||
switch(type) {
|
||||
case 'checkbox':
|
||||
el.checked = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
el.value = value;
|
||||
break;
|
||||
}
|
||||
};
|
||||
41
client/modules/contact.js
Normal file
41
client/modules/contact.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/* global CloudCmd */
|
||||
/* global DOM */
|
||||
|
||||
'use strict';
|
||||
|
||||
CloudCmd.Contact = exports;
|
||||
|
||||
const olark = require('@cloudcmd/olark');
|
||||
const Images = require('../dom/images.mjs');
|
||||
|
||||
const {Events} = DOM;
|
||||
const {Key} = CloudCmd;
|
||||
|
||||
module.exports.show = show;
|
||||
module.exports.hide = hide;
|
||||
|
||||
module.exports.init = () => {
|
||||
Events.addKey(onKey);
|
||||
|
||||
olark.identify('6216-545-10-4223');
|
||||
olark('api.box.onExpand', show);
|
||||
olark('api.box.onShow', show);
|
||||
olark('api.box.onShrink', hide);
|
||||
};
|
||||
|
||||
function show() {
|
||||
Key.unsetBind();
|
||||
Images.hide();
|
||||
|
||||
olark('api.box.expand');
|
||||
}
|
||||
|
||||
function hide() {
|
||||
Key.setBind();
|
||||
olark('api.box.hide');
|
||||
}
|
||||
|
||||
function onKey({keyCode}) {
|
||||
if (keyCode === Key.ESC)
|
||||
hide();
|
||||
}
|
||||
45
client/modules/edit-file-vim.js
Normal file
45
client/modules/edit-file-vim.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
CloudCmd.EditFileVim = exports;
|
||||
|
||||
const Events = require('#dom/events');
|
||||
|
||||
const {Key} = CloudCmd;
|
||||
|
||||
const ConfigView = {
|
||||
bindKeys: false,
|
||||
beforeClose: () => {
|
||||
Events.rmKey(listener);
|
||||
CloudCmd.EditFile.isChanged();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.init = async () => {
|
||||
await CloudCmd.EditFile();
|
||||
};
|
||||
|
||||
module.exports.show = async () => {
|
||||
Events.addKey(listener);
|
||||
|
||||
const editFile = await CloudCmd.EditFile.show(ConfigView);
|
||||
|
||||
editFile
|
||||
.getEditor()
|
||||
.setKeyMap('vim');
|
||||
};
|
||||
|
||||
module.exports.hide = hide;
|
||||
|
||||
function hide() {
|
||||
CloudCmd.Edit.hide();
|
||||
}
|
||||
|
||||
function listener(event) {
|
||||
const {keyCode, shiftKey} = event;
|
||||
|
||||
if (shiftKey && keyCode === Key.ESC) {
|
||||
event.preventDefault();
|
||||
hide();
|
||||
}
|
||||
}
|
||||
194
client/modules/edit-file.js
Normal file
194
client/modules/edit-file.js
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd, DOM*/
|
||||
CloudCmd.EditFile = exports;
|
||||
|
||||
const Format = require('format-io');
|
||||
const {fullstore} = require('fullstore');
|
||||
const exec = require('execon');
|
||||
const supermenu = require('supermenu');
|
||||
|
||||
const Info = DOM.CurrentInfo;
|
||||
|
||||
const {Dialog, Images} = DOM;
|
||||
|
||||
const {config} = CloudCmd;
|
||||
|
||||
let Menu;
|
||||
let MSG_CHANGED;
|
||||
|
||||
const isLoading = fullstore();
|
||||
|
||||
const ConfigView = {
|
||||
beforeClose: async () => {
|
||||
exec.ifExist(Menu, 'hide');
|
||||
await isChanged();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.init = async () => {
|
||||
isLoading(true);
|
||||
|
||||
await CloudCmd.Edit();
|
||||
|
||||
const editor = CloudCmd.Edit.getEditor();
|
||||
authCheck(editor);
|
||||
setListeners(editor);
|
||||
|
||||
isLoading(false);
|
||||
};
|
||||
|
||||
function getName() {
|
||||
const {name, isDir} = Info;
|
||||
|
||||
if (isDir)
|
||||
return `${name}.json`;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
module.exports.show = async (options) => {
|
||||
if (isLoading())
|
||||
return;
|
||||
|
||||
const optionsEdit = {
|
||||
...ConfigView,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (CloudCmd.config('showFileName'))
|
||||
optionsEdit.title = Info.name;
|
||||
|
||||
Images.show.load();
|
||||
|
||||
CloudCmd.Edit
|
||||
.getEditor()
|
||||
.setOption('keyMap', 'default');
|
||||
|
||||
const [error, data] = await Info.getData();
|
||||
|
||||
if (error) {
|
||||
Images.hide();
|
||||
return CloudCmd.Edit;
|
||||
}
|
||||
|
||||
const {path} = Info;
|
||||
const name = getName();
|
||||
|
||||
setMsgChanged(name);
|
||||
|
||||
CloudCmd.Edit
|
||||
.getEditor()
|
||||
.setValueFirst(path, data)
|
||||
.setModeForPath(name)
|
||||
.enableKey();
|
||||
|
||||
CloudCmd.Edit.show(optionsEdit);
|
||||
|
||||
return CloudCmd.Edit;
|
||||
};
|
||||
|
||||
module.exports.hide = hide;
|
||||
|
||||
function hide() {
|
||||
CloudCmd.Edit.hide();
|
||||
}
|
||||
|
||||
function setListeners(editor) {
|
||||
const element = CloudCmd.Edit.getElement();
|
||||
|
||||
DOM.Events.addOnce('contextmenu', element, setMenu);
|
||||
|
||||
editor.on('save', (value) => {
|
||||
DOM.setCurrentSize(Format.size(value));
|
||||
});
|
||||
}
|
||||
|
||||
function authCheck(spawn) {
|
||||
spawn.emit('auth', config('username'), config('password'));
|
||||
spawn.on('reject', () => {
|
||||
Dialog.alert('Wrong credentials!');
|
||||
});
|
||||
}
|
||||
|
||||
function setMenu(event) {
|
||||
const position = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (Menu)
|
||||
return;
|
||||
|
||||
const options = {
|
||||
beforeShow: (params) => {
|
||||
params.x -= 18;
|
||||
params.y -= 27;
|
||||
},
|
||||
|
||||
afterClick: () => {
|
||||
CloudCmd.Edit
|
||||
.getEditor()
|
||||
.focus();
|
||||
},
|
||||
};
|
||||
|
||||
const element = CloudCmd.Edit.getElement();
|
||||
|
||||
Menu = supermenu(element, options, getMenuData());
|
||||
|
||||
Menu.addContextMenuListener();
|
||||
Menu.show(position.x, position.y);
|
||||
}
|
||||
|
||||
function getMenuData() {
|
||||
const editor = CloudCmd.Edit.getEditor();
|
||||
|
||||
return {
|
||||
'Save Ctrl+S': () => {
|
||||
editor.save();
|
||||
},
|
||||
'Go To Line Ctrl+G': () => {
|
||||
editor.goToLine();
|
||||
},
|
||||
'Cut Ctrl+X': () => {
|
||||
editor.cutToClipboard();
|
||||
},
|
||||
'Copy Ctrl+C': () => {
|
||||
editor.copyToClipboard();
|
||||
},
|
||||
'Paste Ctrl+V': () => {
|
||||
editor.pasteFromClipboard();
|
||||
},
|
||||
'Delete Del': () => {
|
||||
editor.remove('right');
|
||||
},
|
||||
'Select All Ctrl+A': () => {
|
||||
editor.selectAll();
|
||||
},
|
||||
'Close Esc': hide,
|
||||
};
|
||||
}
|
||||
|
||||
function setMsgChanged(name) {
|
||||
MSG_CHANGED = `Do you want to save changes to ${name}?`;
|
||||
}
|
||||
|
||||
module.exports.isChanged = isChanged;
|
||||
|
||||
async function isChanged() {
|
||||
const editor = CloudCmd.Edit.getEditor();
|
||||
const is = editor.isChanged();
|
||||
|
||||
if (!is)
|
||||
return;
|
||||
|
||||
const [cancel] = await Dialog.confirm(MSG_CHANGED);
|
||||
|
||||
if (cancel)
|
||||
return;
|
||||
|
||||
editor.save();
|
||||
}
|
||||
43
client/modules/edit-names-vim.js
Normal file
43
client/modules/edit-names-vim.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
CloudCmd.EditNamesVim = exports;
|
||||
|
||||
const Events = require('#dom/events');
|
||||
const {Key} = CloudCmd;
|
||||
|
||||
const ConfigView = {
|
||||
bindKeys: false,
|
||||
beforeClose: () => {
|
||||
Events.rmKey(listener);
|
||||
CloudCmd.EditNames.isChanged();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.init = async () => {
|
||||
await CloudCmd.EditNames();
|
||||
};
|
||||
|
||||
module.exports.show = () => {
|
||||
Events.addKey(listener);
|
||||
|
||||
CloudCmd.EditNames
|
||||
.show(ConfigView)
|
||||
.getEditor()
|
||||
.setKeyMap('vim');
|
||||
};
|
||||
|
||||
module.exports.hide = hide;
|
||||
|
||||
function hide() {
|
||||
CloudCmd.Edit.hide();
|
||||
}
|
||||
|
||||
function listener(event) {
|
||||
const {keyCode, shiftKey} = event;
|
||||
|
||||
if (shiftKey && keyCode === Key.ESC) {
|
||||
event.preventDefault();
|
||||
hide();
|
||||
}
|
||||
}
|
||||
216
client/modules/edit-names.js
Normal file
216
client/modules/edit-names.js
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
'use strict';
|
||||
|
||||
const {tryToCatch} = require('try-to-catch');
|
||||
|
||||
/* global CloudCmd, DOM */
|
||||
CloudCmd.EditNames = exports;
|
||||
|
||||
const exec = require('execon');
|
||||
const supermenu = require('supermenu');
|
||||
const {multiRename} = require('multi-rename');
|
||||
|
||||
const Info = DOM.CurrentInfo;
|
||||
const {Dialog} = DOM;
|
||||
|
||||
let Menu;
|
||||
|
||||
const ConfigView = {
|
||||
beforeClose: async () => {
|
||||
exec.ifExist(Menu, 'hide');
|
||||
DOM.Events.remove('keydown', keyListener);
|
||||
await isChanged();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.init = async () => {
|
||||
await CloudCmd.Edit();
|
||||
|
||||
setListeners();
|
||||
};
|
||||
|
||||
module.exports.show = (options) => {
|
||||
const names = getActiveNames().join('\n');
|
||||
const config = {
|
||||
...ConfigView,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (Info.name === '..' && names.length === 1)
|
||||
return Dialog.alert.noFiles();
|
||||
|
||||
DOM.Events.addKey(keyListener);
|
||||
|
||||
CloudCmd.Edit
|
||||
.getEditor()
|
||||
.setValueFirst('edit-names', names)
|
||||
.setMode()
|
||||
.setOption('keyMap', 'default')
|
||||
.disableKey();
|
||||
|
||||
CloudCmd.Edit.show(config);
|
||||
|
||||
return CloudCmd.Edit;
|
||||
};
|
||||
|
||||
async function keyListener(event) {
|
||||
const ctrl = event.ctrlKey;
|
||||
const meta = event.metaKey;
|
||||
const ctrlMeta = ctrl || meta;
|
||||
const {Key} = CloudCmd;
|
||||
|
||||
if (ctrlMeta && event.keyCode === Key.S) {
|
||||
hide();
|
||||
} else if (ctrlMeta && event.keyCode === Key.P) {
|
||||
const [, pattern] = await Dialog.prompt('Apply pattern:', '[n][e]');
|
||||
pattern && applyPattern(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
function applyPattern(pattern) {
|
||||
const newNames = multiRename(pattern, getActiveNames());
|
||||
const editor = CloudCmd.Edit.getEditor();
|
||||
|
||||
editor.setValue(newNames.join('\n'));
|
||||
}
|
||||
|
||||
function getActiveNames() {
|
||||
return DOM.getFilenames(DOM.getActiveFiles());
|
||||
}
|
||||
|
||||
module.exports.hide = hide;
|
||||
|
||||
function hide() {
|
||||
CloudCmd.Edit.hide();
|
||||
}
|
||||
|
||||
function setListeners() {
|
||||
const element = CloudCmd.Edit.getElement();
|
||||
|
||||
DOM.Events.addOnce('contextmenu', element, setMenu);
|
||||
}
|
||||
|
||||
async function applyNames() {
|
||||
const dir = Info.dirPath;
|
||||
const from = getActiveNames();
|
||||
const nameIndex = from.indexOf(Info.name);
|
||||
|
||||
const editor = CloudCmd.Edit.getEditor();
|
||||
const to = editor
|
||||
.getValue()
|
||||
.split('\n');
|
||||
|
||||
const root = CloudCmd.config('root');
|
||||
|
||||
const response = rename(dir, from, to, root);
|
||||
const [error] = await tryToCatch(refresh, to, nameIndex, response);
|
||||
|
||||
if (error)
|
||||
alert(error);
|
||||
}
|
||||
|
||||
function refresh(to, nameIndex, res) {
|
||||
if (res.status === 404) {
|
||||
const error = res.text();
|
||||
throw error;
|
||||
}
|
||||
|
||||
const currentName = to[nameIndex];
|
||||
|
||||
CloudCmd.refresh({
|
||||
currentName,
|
||||
});
|
||||
}
|
||||
|
||||
function getDir(root, dir) {
|
||||
if (root === '/')
|
||||
return dir;
|
||||
|
||||
return root + dir;
|
||||
}
|
||||
|
||||
function rename(path, from, to, root) {
|
||||
const dir = getDir(root, path);
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
return fetch(`${prefix}/rename`, {
|
||||
method: 'put',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
from,
|
||||
to,
|
||||
dir,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function setMenu(event) {
|
||||
const position = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (Menu)
|
||||
return;
|
||||
|
||||
const editor = CloudCmd.Edit.getEditor();
|
||||
|
||||
const options = {
|
||||
beforeShow: (params) => {
|
||||
params.x -= 18;
|
||||
params.y -= 27;
|
||||
},
|
||||
|
||||
afterClick: () => {
|
||||
editor.focus();
|
||||
},
|
||||
};
|
||||
|
||||
const menuData = {
|
||||
'Save Ctrl+S': async () => {
|
||||
await applyNames();
|
||||
hide();
|
||||
},
|
||||
'Go To Line Ctrl+G': () => {
|
||||
editor.goToLine();
|
||||
},
|
||||
'Cut Ctrl+X': () => {
|
||||
editor.cutToClipboard();
|
||||
},
|
||||
'Copy Ctrl+C': () => {
|
||||
editor.copyToClipboard();
|
||||
},
|
||||
'Paste Ctrl+V': () => {
|
||||
editor.pasteFromClipboard();
|
||||
},
|
||||
'Delete Del': () => {
|
||||
editor.remove('right');
|
||||
},
|
||||
'Select All Ctrl+A': () => {
|
||||
editor.selectAll();
|
||||
},
|
||||
'Close Esc': hide,
|
||||
};
|
||||
|
||||
const element = CloudCmd.Edit.getElement();
|
||||
|
||||
Menu = supermenu(element, options, menuData);
|
||||
|
||||
Menu.addContextMenuListener();
|
||||
Menu.show(position.x, position.y);
|
||||
}
|
||||
|
||||
module.exports.isChanged = isChanged;
|
||||
|
||||
async function isChanged() {
|
||||
const editor = CloudCmd.Edit.getEditor();
|
||||
const msg = 'Apply new names?';
|
||||
|
||||
if (!editor.isChanged())
|
||||
return;
|
||||
|
||||
const [cancel] = await Dialog.confirm(msg);
|
||||
|
||||
!cancel && await applyNames();
|
||||
}
|
||||
122
client/modules/edit.js
Normal file
122
client/modules/edit.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/* global CloudCmd */
|
||||
|
||||
'use strict';
|
||||
|
||||
const montag = require('montag');
|
||||
|
||||
const {promisify} = require('es6-promisify');
|
||||
const {tryToCatch} = require('try-to-catch');
|
||||
const createElement = require('@cloudcmd/create-element');
|
||||
const load = require('load.js');
|
||||
const {MAX_FILE_SIZE: maxSize} = require('../../common/cloudfunc.mjs');
|
||||
|
||||
const {time, timeEnd} = require('../../common/util');
|
||||
const getEditor = () => editor;
|
||||
const isFn = (a) => typeof a === 'function';
|
||||
const loadJS = load.js;
|
||||
|
||||
const Name = 'Edit';
|
||||
|
||||
CloudCmd[Name] = exports;
|
||||
|
||||
const EditorName = CloudCmd.config('editor');
|
||||
|
||||
let Loading = true;
|
||||
let Element;
|
||||
let editor;
|
||||
|
||||
const ConfigView = {
|
||||
afterShow: () => {
|
||||
editor
|
||||
.moveCursorTo(0, 0)
|
||||
.focus();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.init = async () => {
|
||||
const element = create();
|
||||
|
||||
await CloudCmd.View();
|
||||
await loadFiles(element);
|
||||
};
|
||||
|
||||
function create() {
|
||||
const element = createElement('div', {
|
||||
style: montag`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: "Droid Sans Mono";
|
||||
`,
|
||||
notAppend: true,
|
||||
});
|
||||
|
||||
Element = element;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function checkFn(name, fn) {
|
||||
if (!isFn(fn))
|
||||
throw Error(`${name} should be a function!`);
|
||||
}
|
||||
|
||||
function initConfig(options = {}) {
|
||||
const config = {
|
||||
...options,
|
||||
...ConfigView,
|
||||
};
|
||||
|
||||
if (!options.afterShow)
|
||||
return config;
|
||||
|
||||
checkFn('options.afterShow', options.afterShow);
|
||||
|
||||
config.afterShow = () => {
|
||||
ConfigView.afterShow();
|
||||
options.afterShow();
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports.show = (options) => {
|
||||
if (Loading)
|
||||
return;
|
||||
|
||||
CloudCmd.View.show(Element, initConfig(options));
|
||||
|
||||
getEditor().setOptions({
|
||||
fontSize: 16,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getEditor = getEditor;
|
||||
|
||||
module.exports.getElement = () => Element;
|
||||
|
||||
module.exports.hide = () => {
|
||||
CloudCmd.View.hide();
|
||||
};
|
||||
|
||||
const loadFiles = async (element) => {
|
||||
const prefix = `${CloudCmd.prefix}/${EditorName}`;
|
||||
const socketPath = CloudCmd.prefix;
|
||||
const prefixSocket = `${CloudCmd.prefixSocket}/${EditorName}`;
|
||||
const url = `${prefix}/${EditorName}.js`;
|
||||
|
||||
time(`${Name} load`);
|
||||
|
||||
await loadJS(url);
|
||||
|
||||
const word = promisify(window[EditorName]);
|
||||
const [ed] = await tryToCatch(word, element, {
|
||||
maxSize,
|
||||
prefix,
|
||||
prefixSocket,
|
||||
socketPath,
|
||||
});
|
||||
|
||||
timeEnd(`${Name} load`);
|
||||
editor = ed;
|
||||
Loading = false;
|
||||
};
|
||||
27
client/modules/help.js
Normal file
27
client/modules/help.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
CloudCmd.Help = exports;
|
||||
|
||||
const Images = require('../dom/images.mjs');
|
||||
|
||||
module.exports.init = () => {
|
||||
Images.show.load('top');
|
||||
};
|
||||
|
||||
module.exports.show = show;
|
||||
module.exports.hide = hide;
|
||||
|
||||
function show() {
|
||||
const positionLoad = 'top';
|
||||
const relative = true;
|
||||
|
||||
CloudCmd.Markdown.show('/HELP.md', {
|
||||
positionLoad,
|
||||
relative,
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
CloudCmd.View.hide();
|
||||
}
|
||||
141
client/modules/konsole.js
Normal file
141
client/modules/konsole.js
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
/* global Util */
|
||||
/* global DOM */
|
||||
/* global Console */
|
||||
CloudCmd.Konsole = exports;
|
||||
|
||||
const exec = require('execon');
|
||||
const currify = require('currify');
|
||||
const {tryToCatch} = require('try-to-catch');
|
||||
const loadJS = require('load.js').js;
|
||||
const createElement = require('@cloudcmd/create-element');
|
||||
|
||||
const Images = require('../dom/images.mjs');
|
||||
const {Dialog, CurrentInfo: Info} = DOM;
|
||||
|
||||
const rmLastSlash = (a) => a.replace(/\/$/, '') || '/';
|
||||
|
||||
let konsole;
|
||||
const {config} = CloudCmd;
|
||||
|
||||
const cd = currify((fn, dir) => fn(`cd ${rmLastSlash(dir)}`));
|
||||
|
||||
const Name = 'Konsole';
|
||||
|
||||
let Element;
|
||||
let Loaded;
|
||||
|
||||
module.exports.init = async () => {
|
||||
if (!config('console'))
|
||||
return;
|
||||
|
||||
Images.show.load('top');
|
||||
|
||||
await CloudCmd.View();
|
||||
await load();
|
||||
await create();
|
||||
};
|
||||
|
||||
module.exports.hide = () => {
|
||||
CloudCmd.View.hide();
|
||||
};
|
||||
|
||||
module.exports.clear = () => {
|
||||
konsole.clear();
|
||||
};
|
||||
|
||||
const getPrefix = () => CloudCmd.prefix + '/console';
|
||||
|
||||
function getPrefixSocket() {
|
||||
return CloudCmd.prefixSocket + '/console';
|
||||
}
|
||||
|
||||
const getEnv = () => ({
|
||||
ACTIVE_DIR: DOM.getCurrentDirPath.bind(DOM),
|
||||
PASSIVE_DIR: DOM.getNotCurrentDirPath.bind(DOM),
|
||||
CURRENT_NAME: DOM.getCurrentName.bind(DOM),
|
||||
CURRENT_PATH: () => Info.path,
|
||||
});
|
||||
|
||||
async function onPath(path) {
|
||||
if (Info.dirPath === path)
|
||||
return;
|
||||
|
||||
await CloudCmd.changeDir(path);
|
||||
}
|
||||
|
||||
const getDirPath = () => {
|
||||
if (config('syncConsolePath'))
|
||||
return Info.dirPath;
|
||||
};
|
||||
|
||||
const create = async () => {
|
||||
const options = {
|
||||
cwd: getDirPath(),
|
||||
env: getEnv(),
|
||||
prefix: getPrefix(),
|
||||
prefixSocket: getPrefixSocket(),
|
||||
socketPath: CloudCmd.prefix,
|
||||
};
|
||||
|
||||
Element = createElement('div', {
|
||||
className: 'console',
|
||||
});
|
||||
|
||||
konsole = await Console(Element, options);
|
||||
|
||||
konsole.on('connect', exec.with(authCheck, konsole));
|
||||
konsole.on('path', config.if('syncConsolePath', onPath));
|
||||
|
||||
CloudCmd.on('active-dir', config.if('syncConsolePath', cd(konsole.handler)));
|
||||
|
||||
konsole.addShortCuts({
|
||||
P: () => {
|
||||
const command = konsole.getPromptText();
|
||||
const path = DOM.getCurrentDirPath();
|
||||
|
||||
konsole.setPromptText(command + path);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
function authCheck(konsole) {
|
||||
konsole.emit('auth', config('username'), config('password'));
|
||||
|
||||
konsole.on('reject', () => {
|
||||
Dialog.alert('Wrong credentials!');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.show = (callback) => {
|
||||
if (!Loaded)
|
||||
return;
|
||||
|
||||
if (!config('console'))
|
||||
return;
|
||||
|
||||
CloudCmd.View.show(Element, {
|
||||
afterShow: () => {
|
||||
konsole.focus();
|
||||
exec(callback);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const load = async () => {
|
||||
Util.time(`${Name} load`);
|
||||
|
||||
const prefix = getPrefix();
|
||||
const url = `${prefix}/console.js`;
|
||||
const [error] = await tryToCatch(loadJS, url);
|
||||
|
||||
Loaded = true;
|
||||
Util.timeEnd(`${Name} load`);
|
||||
|
||||
if (error)
|
||||
return Dialog.alert(error.message, {
|
||||
cancel: false,
|
||||
});
|
||||
};
|
||||
47
client/modules/markdown.js
Normal file
47
client/modules/markdown.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
CloudCmd.Markdown = exports;
|
||||
|
||||
const createElement = require('@cloudcmd/create-element');
|
||||
|
||||
const Images = require('../dom/images.mjs');
|
||||
const {Markdown} = require('../dom/rest');
|
||||
const {alert} = require('../dom/dialog');
|
||||
|
||||
module.exports.init = async () => {
|
||||
Images.show.load('top');
|
||||
await CloudCmd.View();
|
||||
};
|
||||
|
||||
module.exports.show = show;
|
||||
|
||||
module.exports.hide = () => {
|
||||
CloudCmd.View.hide();
|
||||
};
|
||||
|
||||
async function show(name, options = {}) {
|
||||
const {positionLoad, relative} = options;
|
||||
|
||||
Images.show.load(positionLoad);
|
||||
|
||||
if (relative)
|
||||
name += '?relative';
|
||||
|
||||
const [error, innerHTML] = await Markdown.read(name);
|
||||
Images.hide();
|
||||
|
||||
if (error)
|
||||
return alert(error.message, {
|
||||
cancel: false,
|
||||
});
|
||||
|
||||
const className = 'help';
|
||||
|
||||
const div = createElement('div', {
|
||||
className,
|
||||
innerHTML,
|
||||
});
|
||||
|
||||
CloudCmd.View.show(div);
|
||||
}
|
||||
30
client/modules/menu/cloudmenu.mjs
Normal file
30
client/modules/menu/cloudmenu.mjs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import supermenu from 'supermenu';
|
||||
|
||||
const noop = () => {};
|
||||
const {CloudCmd} = globalThis;
|
||||
|
||||
export const createCloudMenu = async (fm, options, menuData) => {
|
||||
const createMenu = await loadMenu();
|
||||
const menu = await createMenu(fm, options, menuData);
|
||||
|
||||
menu.addContextMenuListener = menu.addContextMenuListener || noop;
|
||||
|
||||
return menu;
|
||||
};
|
||||
|
||||
async function loadMenu() {
|
||||
if (CloudCmd.config('menu') === 'aleman') {
|
||||
const {host, protocol} = globalThis.location;
|
||||
const url = `${protocol}//${host}/node_modules/aleman/menu/menu.js`;
|
||||
const {createMenu} = await import(/* webpackIgnore: true */url);
|
||||
|
||||
return createMenu;
|
||||
}
|
||||
|
||||
return createSupermenu;
|
||||
}
|
||||
|
||||
function createSupermenu(name, options, menuData) {
|
||||
const element = document.querySelector('[data-name="js-fm"]');
|
||||
return supermenu(element, options, menuData);
|
||||
}
|
||||
357
client/modules/menu/index.js
Normal file
357
client/modules/menu/index.js
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/* global CloudCmd, DOM */
|
||||
|
||||
'use strict';
|
||||
|
||||
const exec = require('execon');
|
||||
const wrap = require('wraptile');
|
||||
const createElement = require('@cloudcmd/create-element');
|
||||
|
||||
const {FS} = require('../../../common/cloudfunc.mjs');
|
||||
const {getIdBySrc} = require('../../dom/load');
|
||||
const RESTful = require('../../dom/rest');
|
||||
|
||||
const {config, Key} = CloudCmd;
|
||||
|
||||
const {
|
||||
Buffer,
|
||||
Events,
|
||||
Dialog,
|
||||
Images,
|
||||
} = DOM;
|
||||
|
||||
const Info = DOM.CurrentInfo;
|
||||
const alertNoFiles = Dialog.alert.noFiles;
|
||||
const uploadTo = wrap(_uploadTo);
|
||||
|
||||
let MenuShowedName;
|
||||
let MenuContext;
|
||||
let MenuContextFile;
|
||||
|
||||
module.exports.ENABLED = false;
|
||||
|
||||
CloudCmd.Menu = exports;
|
||||
|
||||
module.exports.init = async () => {
|
||||
const {isAuth, menuDataFile} = getFileMenuData();
|
||||
|
||||
const fm = DOM.getFM();
|
||||
const menuData = getMenuData(isAuth);
|
||||
|
||||
const options = getOptions({
|
||||
type: 'context',
|
||||
});
|
||||
|
||||
const optionsFile = getOptions({
|
||||
type: 'file',
|
||||
});
|
||||
|
||||
const {createCloudMenu} = await import('./cloudmenu.mjs');
|
||||
|
||||
const {name} = fm.dataset;
|
||||
|
||||
MenuContext = await createCloudMenu(name, options, menuData);
|
||||
MenuContextFile = await createCloudMenu(name, optionsFile, menuDataFile);
|
||||
|
||||
MenuContext.addContextMenuListener();
|
||||
MenuContextFile.addContextMenuListener();
|
||||
|
||||
Events.addKey(listener);
|
||||
};
|
||||
|
||||
module.exports.hide = hide;
|
||||
|
||||
function hide() {
|
||||
MenuContext.hide();
|
||||
MenuContextFile.hide();
|
||||
}
|
||||
|
||||
module.exports.show = (position) => {
|
||||
const {x, y} = getPosition(position);
|
||||
|
||||
MenuContext.show(x, y);
|
||||
MenuContextFile.show(x, y);
|
||||
|
||||
Images.hide();
|
||||
};
|
||||
|
||||
function getPosition(position) {
|
||||
if (position)
|
||||
return {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
};
|
||||
|
||||
return getCurrentPosition();
|
||||
}
|
||||
|
||||
function getMenuNameByEl(el) {
|
||||
if (!el)
|
||||
return 'context';
|
||||
|
||||
const name = DOM.getCurrentName(el);
|
||||
|
||||
if (name === '..')
|
||||
return 'context';
|
||||
|
||||
return 'contextFile';
|
||||
}
|
||||
|
||||
function getOptions({type}) {
|
||||
let name;
|
||||
let func;
|
||||
|
||||
if (type === 'context') {
|
||||
name = 'context';
|
||||
func = Key.unsetBind;
|
||||
} else if (type === 'file') {
|
||||
name = 'contextFile';
|
||||
}
|
||||
|
||||
const options = {
|
||||
icon: true,
|
||||
infiniteScroll: false,
|
||||
beforeClose: Key.setBind,
|
||||
beforeHide: Key.setBind,
|
||||
beforeShow: exec.with(beforeShow, func),
|
||||
beforeClick,
|
||||
name,
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function getMenuData(isAuth) {
|
||||
const menu = {
|
||||
'Paste': Buffer.paste,
|
||||
'New': {
|
||||
File: DOM.promptNewFile,
|
||||
Directory: DOM.promptNewDir,
|
||||
},
|
||||
'Upload': () => {
|
||||
CloudCmd.Upload.show();
|
||||
},
|
||||
'Upload From Cloud': uploadFromCloud,
|
||||
'Toggle File Selection': DOM.toggleSelectedFile,
|
||||
'(Un)Select All': DOM.toggleAllSelectedFiles,
|
||||
};
|
||||
|
||||
if (isAuth)
|
||||
menu['Log Out'] = CloudCmd.logOut;
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
function getFileMenuData() {
|
||||
const isAuth = CloudCmd.config('auth');
|
||||
|
||||
const menuBottom = getMenuData(isAuth);
|
||||
const menuTop = {
|
||||
'View': () => {
|
||||
CloudCmd.View.show();
|
||||
},
|
||||
'Edit': () => {
|
||||
const name = config('vim') ? 'EditFileVim' : 'EditFile';
|
||||
CloudCmd[name].show();
|
||||
},
|
||||
'Rename': () => {
|
||||
setTimeout(DOM.renameCurrent, 100);
|
||||
},
|
||||
'Delete': () => {
|
||||
CloudCmd.Operation.show('delete');
|
||||
},
|
||||
'Pack': () => {
|
||||
CloudCmd.Operation.show('pack');
|
||||
},
|
||||
'Extract': () => {
|
||||
CloudCmd.Operation.show('extract');
|
||||
},
|
||||
'Download': preDownload,
|
||||
'Upload To Cloud': uploadTo('Cloud'),
|
||||
'Cut': () => {
|
||||
isCurrent(Buffer.cut, alertNoFiles);
|
||||
},
|
||||
'Copy': () => {
|
||||
isCurrent(Buffer.copy, alertNoFiles);
|
||||
},
|
||||
};
|
||||
|
||||
const menuDataFile = {
|
||||
...menuTop,
|
||||
...menuBottom,
|
||||
};
|
||||
|
||||
return {
|
||||
isAuth,
|
||||
menuDataFile,
|
||||
};
|
||||
}
|
||||
|
||||
function isCurrent(yesFn, noFn) {
|
||||
if (Info.name !== '..')
|
||||
return yesFn();
|
||||
|
||||
noFn();
|
||||
}
|
||||
|
||||
function isPath(x, y) {
|
||||
const {panel} = Info;
|
||||
const isEmptyRoot = !panel;
|
||||
|
||||
if (isEmptyRoot)
|
||||
return false;
|
||||
|
||||
const el = document.elementFromPoint(x, y);
|
||||
const elements = panel.querySelectorAll('[data-name="js-path"] *');
|
||||
|
||||
return !~[].indexOf.call(elements, el);
|
||||
}
|
||||
|
||||
function beforeShow(callback, params) {
|
||||
Key.unsetBind();
|
||||
|
||||
const {
|
||||
name,
|
||||
position = {
|
||||
x: params.x,
|
||||
y: params.y,
|
||||
},
|
||||
} = params;
|
||||
|
||||
const {x, y} = position;
|
||||
|
||||
const el = DOM.getCurrentByPosition({
|
||||
x,
|
||||
y,
|
||||
});
|
||||
|
||||
const menuName = getMenuNameByEl(el);
|
||||
|
||||
let isShow = menuName !== 'contextFile';
|
||||
|
||||
if (params.name === 'contextFile')
|
||||
isShow = !isShow;
|
||||
|
||||
if (isShow)
|
||||
MenuShowedName = name;
|
||||
|
||||
exec(callback);
|
||||
|
||||
if (isShow)
|
||||
isShow = isPath(x, y);
|
||||
|
||||
return isShow;
|
||||
}
|
||||
|
||||
const beforeClick = (name) => MenuShowedName !== name;
|
||||
|
||||
async function _uploadTo(nameModule) {
|
||||
const [error, data] = await Info.getData();
|
||||
|
||||
if (error)
|
||||
return;
|
||||
|
||||
const {name} = Info;
|
||||
|
||||
CloudCmd.execFromModule(nameModule, 'uploadFile', name, data);
|
||||
CloudCmd.log(`Uploading to ${name}...`);
|
||||
}
|
||||
|
||||
function uploadFromCloud() {
|
||||
Images.show.load('top');
|
||||
|
||||
CloudCmd.execFromModule('Cloud', 'saveFile', async (currentName, data) => {
|
||||
const path = DOM.getCurrentDirPath() + currentName;
|
||||
const [e] = await RESTful.write(path, data);
|
||||
|
||||
if (e)
|
||||
return;
|
||||
|
||||
await CloudCmd.refresh({
|
||||
currentName,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function preDownload() {
|
||||
download(config('packer'));
|
||||
}
|
||||
|
||||
function download(type) {
|
||||
const TIME = 30 * 1000;
|
||||
const {prefixURL} = CloudCmd;
|
||||
const PACK = '/pack';
|
||||
const date = Date.now();
|
||||
const files = DOM.getActiveFiles();
|
||||
|
||||
if (!files.length)
|
||||
return alertNoFiles();
|
||||
|
||||
for (const file of files) {
|
||||
const selected = DOM.isSelected(file);
|
||||
const isDir = DOM.isCurrentIsDir(file);
|
||||
const path = DOM.getCurrentPath(file);
|
||||
|
||||
CloudCmd.log(`downloading file ${path}...`);
|
||||
|
||||
/*
|
||||
* if we send ajax request -
|
||||
* no need in hash so we escape #
|
||||
* and all other characters, like "%"
|
||||
*/
|
||||
const encodedPath = encodeURI(path).replace(/#/g, '%23');
|
||||
const id = getIdBySrc(path);
|
||||
|
||||
let src;
|
||||
|
||||
if (isDir)
|
||||
src = prefixURL + PACK + encodedPath + DOM.getPackerExt(type);
|
||||
else
|
||||
src = prefixURL + FS + encodedPath + '?download';
|
||||
|
||||
const element = createElement('iframe', {
|
||||
id: id + '-' + date,
|
||||
async: false,
|
||||
className: 'hidden',
|
||||
src,
|
||||
});
|
||||
|
||||
const {body} = document;
|
||||
const removeChild = body.removeChild.bind(body, element);
|
||||
|
||||
setTimeout(removeChild, TIME);
|
||||
|
||||
if (selected)
|
||||
DOM.toggleSelectedFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentPosition() {
|
||||
const current = Info.element;
|
||||
const rect = current.getBoundingClientRect();
|
||||
|
||||
const position = {
|
||||
x: Math.round(rect.left + rect.width / 3),
|
||||
y: Math.round(rect.top),
|
||||
};
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
function listener(event) {
|
||||
const {F9, ESC} = Key;
|
||||
|
||||
const key = event.keyCode;
|
||||
const isBind = Key.isBind();
|
||||
|
||||
if (key === ESC) {
|
||||
Key.setBind();
|
||||
return hide();
|
||||
}
|
||||
|
||||
if (isBind && key === F9) {
|
||||
const position = getCurrentPosition();
|
||||
MenuContext.show(position.x, position.y);
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
8
client/modules/operation/format.js
Normal file
8
client/modules/operation/format.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = (operation, from, to) => {
|
||||
if (!to)
|
||||
return `${operation} ${from}`;
|
||||
|
||||
return `${operation} ${from} -> ${to}`;
|
||||
};
|
||||
18
client/modules/operation/get-next-current-name.js
Normal file
18
client/modules/operation/get-next-current-name.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
'use strict';
|
||||
|
||||
const currify = require('currify');
|
||||
|
||||
const not = currify((array, value) => !array.includes(value));
|
||||
const notOneOf = currify((a, b) => a.filter(not(b)));
|
||||
|
||||
module.exports = (currentName, names, removedNames) => {
|
||||
const i = names.indexOf(currentName);
|
||||
|
||||
const nextNames = notOneOf(names, removedNames);
|
||||
const {length} = nextNames;
|
||||
|
||||
if (nextNames[i])
|
||||
return nextNames[i];
|
||||
|
||||
return nextNames[length - 1];
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue