mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-07-21 18:49:02 +00:00
Compare commits
2540 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f28803a82 | ||
|
|
943bdecd3e | ||
|
|
e4ff4a78cc | ||
|
|
fec5538f01 | ||
|
|
b7bb363aa8 | ||
|
|
cd4a2cec76 | ||
|
|
0638ca32cb | ||
|
|
fe185cc93e | ||
|
|
7edea50296 | ||
|
|
8258cdb651 | ||
|
|
a9e29eee85 | ||
|
|
a52a6aae62 | ||
|
|
92c79830f5 | ||
|
|
8629f67972 | ||
|
|
c04d363c46 | ||
|
|
046a4ef22e | ||
|
|
579c9e41d7 | ||
|
|
6014898610 | ||
|
|
ad3bf95e5f | ||
|
|
4e22a4edde | ||
|
|
9c50fe53e7 | ||
|
|
a2a2d51ec1 | ||
|
|
c36c32cb08 | ||
|
|
78b206e06b | ||
|
|
ef83a61a0c | ||
|
|
f5c0aa5c4c | ||
|
|
e59167b1fa | ||
|
|
ea584aceea | ||
|
|
4d7116b467 | ||
|
|
b149288545 | ||
|
|
9d06279ba8 | ||
|
|
21e1dcf0e5 | ||
|
|
b3b0c1d159 | ||
|
|
76d7bf4d73 | ||
|
|
f1d80282eb | ||
|
|
220cc5557b | ||
|
|
d2176c93fb | ||
|
|
c558e44a1c | ||
|
|
45ec062bce | ||
|
|
f8edddd442 | ||
|
|
fc3d5ac700 | ||
|
|
4ee38db68c | ||
|
|
1dfa7ca355 | ||
|
|
40ecef5ec4 | ||
|
|
cad5e0f44e | ||
|
|
d139011cc3 | ||
|
|
5d9628ce70 | ||
|
|
161bede8db | ||
|
|
2be395e640 | ||
|
|
447d990f43 | ||
|
|
4f4725ac3c | ||
|
|
eff03299b6 | ||
|
|
a6adf39db2 | ||
|
|
70ff6fb29f | ||
|
|
eb32fff688 | ||
|
|
6071b66118 | ||
|
|
83c0b8d3f1 | ||
|
|
79da2319f8 | ||
|
|
68eacb916a | ||
|
|
ccf608a52e | ||
|
|
4acd294bf6 | ||
|
|
05c800431d | ||
|
|
6ff1db2710 | ||
|
|
800a6545a1 | ||
|
|
a985bb36ca | ||
|
|
e7c55e05b8 | ||
|
|
4315ec6179 | ||
|
|
628d12fa2f | ||
|
|
d82d0335b2 | ||
|
|
acfa27cf74 | ||
|
|
14f009f832 | ||
|
|
d1f6476604 | ||
|
|
c6c6014691 | ||
|
|
9a6b89356e | ||
|
|
882fe83623 | ||
|
|
a9bc22ce29 | ||
|
|
36bacfe915 | ||
|
|
bcbef1a3b0 | ||
|
|
591da25cfe | ||
|
|
1e1b073c8c | ||
|
|
ea7b828cbb | ||
|
|
cd6c11ace2 | ||
|
|
c4beeec63b | ||
|
|
87c13e7cce | ||
|
|
d5cd11e812 | ||
|
|
dc94d2db7a | ||
|
|
1637beee78 | ||
|
|
0541b7e351 | ||
|
|
b1bc4e731f | ||
|
|
7c0dca6038 | ||
|
|
24fd0a8aa7 | ||
|
|
2b3dc1573d | ||
|
|
48693d9e24 | ||
|
|
977a8aaaf4 | ||
|
|
15537ec518 | ||
|
|
7d0098fd5c | ||
|
|
da0c4c4704 | ||
|
|
b0687b54c7 | ||
|
|
a89e901b2b | ||
|
|
ef06a0c310 | ||
|
|
e7242900a4 | ||
|
|
a6b01dce49 | ||
|
|
1cfc1a6f5d | ||
|
|
9915757dce | ||
|
|
673fba2e21 | ||
|
|
d37f8cd36e | ||
|
|
5e509a4d7b | ||
|
|
cb6cabd4c9 | ||
|
|
f61cc46f41 | ||
|
|
32a8f169d4 | ||
|
|
aa74123228 | ||
|
|
27f20d6af8 | ||
|
|
4695480ead | ||
|
|
bec3f5a799 | ||
|
|
13b15b7bea | ||
|
|
969bc770e3 | ||
|
|
5ecc48e348 | ||
|
|
9efefc23e0 | ||
|
|
6a08479a1b | ||
|
|
c2563ed6b3 | ||
|
|
46c655546e | ||
|
|
4fa0786a39 | ||
|
|
ae2ce3882c | ||
|
|
3c9389c4f0 | ||
|
|
bb6be7abbb | ||
|
|
3aac4e68b4 | ||
|
|
ea96d13fab | ||
|
|
f9c5804fb8 | ||
|
|
84f96fa185 | ||
|
|
8238bd4011 | ||
|
|
36cdef3717 | ||
|
|
f8035d6c49 | ||
|
|
845f9bd127 | ||
|
|
b5ec9ce210 | ||
|
|
71a1d78c39 | ||
|
|
6bbfeb9024 | ||
|
|
a038470816 | ||
|
|
45e0ff41f4 | ||
|
|
9970ff7629 | ||
|
|
4395a47178 | ||
|
|
77daf5028e | ||
|
|
c40ae8e405 | ||
|
|
f463c5c5a7 | ||
|
|
d1032f09ab | ||
|
|
b3e682c0a5 | ||
|
|
ffabdc5276 | ||
|
|
7787bfc22e | ||
|
|
406429aec8 | ||
|
|
32f89d3874 | ||
|
|
1c0021dbb6 | ||
|
|
df4fb51716 | ||
|
|
314cceb462 | ||
|
|
ceb7ef4ff7 | ||
|
|
34fad6ea73 | ||
|
|
d12e7bd0c5 | ||
|
|
b8a621ca1b | ||
|
|
1fc1e3734c | ||
|
|
582b7423dc | ||
|
|
e4d7d441fc | ||
|
|
e53aa06cdb | ||
|
|
b4345ed40d | ||
|
|
ddbf4d69a4 | ||
|
|
e2855641ca | ||
|
|
5c6a9a958e | ||
|
|
d2a7f6343a | ||
|
|
2b29448062 | ||
|
|
09a8b2bd1c | ||
|
|
6a52b11e46 | ||
|
|
9a053ec374 | ||
|
|
6aa496c995 | ||
|
|
738059f27f | ||
|
|
3fc8932f4c | ||
|
|
2db458bc7b | ||
|
|
cf424d6c05 | ||
|
|
8b9004b4dd | ||
|
|
efb24e229f | ||
|
|
19347a2b4c | ||
|
|
a80f109d19 | ||
|
|
74e008198f | ||
|
|
d25a08d2bd | ||
|
|
e04c459489 | ||
|
|
3d2944bd13 | ||
|
|
52a9076f14 | ||
|
|
7e2d2ede98 | ||
|
|
6c7709be9c | ||
|
|
340a639f1b | ||
|
|
aa5a772f00 | ||
|
|
439b3710cc | ||
|
|
e41828410c | ||
|
|
799d86bda4 | ||
|
|
a423739cb8 | ||
|
|
47acb8fdf7 | ||
|
|
1f95f18898 | ||
|
|
2aaf0a5385 | ||
|
|
1a44ab0eac | ||
|
|
fe3e201143 | ||
|
|
f0deb32362 | ||
|
|
ba6f66e73e | ||
|
|
cc7f9dc75b | ||
|
|
f671f79814 | ||
|
|
fa8875d53c | ||
|
|
2dd62241f7 | ||
|
|
4d1cd8cdc6 | ||
|
|
54b56fdc38 | ||
|
|
785c32e2e3 | ||
|
|
c3f2b7c32f | ||
|
|
110908e221 | ||
|
|
8be917da4a | ||
|
|
8db81f30dc | ||
|
|
56439d5ec2 | ||
|
|
e761cacbd0 | ||
|
|
0bb706995a | ||
|
|
69498ed6ab | ||
|
|
82f8d846a9 | ||
|
|
1ea4aa67ca | ||
|
|
8763788bad | ||
|
|
c1c67c18af | ||
|
|
09a02074e1 | ||
|
|
c448eaa48b | ||
|
|
05ad2fd635 | ||
|
|
b949a149ec | ||
|
|
6e5318faa7 | ||
|
|
ec66ba78a7 | ||
|
|
a1216cddeb | ||
|
|
2f0affd3a3 | ||
|
|
68b2aa780b | ||
|
|
56e21b4011 | ||
|
|
665ed9c2e9 | ||
|
|
a44a28f496 | ||
|
|
f51229fc6f | ||
|
|
0c9f3922f8 | ||
|
|
4410cd2665 | ||
|
|
618a561514 | ||
|
|
682729ac3e | ||
|
|
67f27d8425 | ||
|
|
97672ef5d5 | ||
|
|
e6684df59e | ||
|
|
9493e4de7e | ||
|
|
025b005e12 | ||
|
|
2d524a4974 | ||
|
|
5e0b681d8b | ||
|
|
b052cf220d | ||
|
|
4fa92f9394 | ||
|
|
02dbe56d79 | ||
|
|
e616cc0bc9 | ||
|
|
ecc76e8b1e | ||
|
|
0fbfa0f0cf | ||
|
|
53c072abe4 | ||
|
|
4b9922bf92 | ||
|
|
1bb77992b0 | ||
|
|
bec991d941 | ||
|
|
56fc8b838a | ||
|
|
df21e81d5a | ||
|
|
8b83277ec0 | ||
|
|
660f57e914 | ||
|
|
151c56f9a3 | ||
|
|
9e529605e1 | ||
|
|
ca97c21e9c | ||
|
|
01677e6a09 | ||
|
|
7e35c60677 | ||
|
|
a20640ed75 | ||
|
|
e5b221f7ac | ||
|
|
2396000a6a | ||
|
|
7661f69f1a | ||
|
|
708a4c6b99 | ||
|
|
84f3a3dc79 | ||
|
|
80613f46fa | ||
|
|
e9a8910c9c | ||
|
|
e78f5d943f | ||
|
|
10934b3ad9 | ||
|
|
ddf5a59f70 | ||
|
|
59037f2c18 | ||
|
|
1444550865 | ||
|
|
b0c1d36c81 | ||
|
|
10b225a337 | ||
|
|
289f67e0d3 | ||
|
|
daf8387516 | ||
|
|
170b4da211 | ||
|
|
c9b27bb514 | ||
|
|
5c19ddba61 | ||
|
|
b28a070a28 | ||
|
|
43c5a01102 | ||
|
|
15dcae5c5e | ||
|
|
5976da81df | ||
|
|
37cb83f2cf | ||
|
|
dd5f392293 | ||
|
|
15fab5142f | ||
|
|
d2801ca896 | ||
|
|
68c7d0be3b | ||
|
|
bfbaae9ec0 | ||
|
|
3987cc82c1 | ||
|
|
559b7424b6 | ||
|
|
964ae989f2 | ||
|
|
ff4e02c210 | ||
|
|
9ead35242a | ||
|
|
061ab38b24 | ||
|
|
07bc9e2782 | ||
|
|
a66eeda37e | ||
|
|
4340533acf | ||
|
|
394939e0d5 | ||
|
|
0857711f61 | ||
|
|
5aba36cca0 | ||
|
|
3d2e5feda2 | ||
|
|
321a54dd59 | ||
|
|
feb6eb2468 | ||
|
|
7c5ac408e5 | ||
|
|
37ecb3d7ac | ||
|
|
52023c82cb | ||
|
|
6d19bf2e14 | ||
|
|
d7214aba70 | ||
|
|
6e1cf4ef52 | ||
|
|
3aed798ff1 | ||
|
|
957fbc6533 | ||
|
|
b20539ef2b | ||
|
|
7ef134f46f | ||
|
|
cdeb40d191 | ||
|
|
1e18d51342 | ||
|
|
8836d816d5 | ||
|
|
ff9fcb1189 | ||
|
|
45cf9bafcd | ||
|
|
3e647290fd | ||
|
|
800ed012ba | ||
|
|
525c17d4bd | ||
|
|
4424749978 | ||
|
|
9fc3c389bf | ||
|
|
9ffe3ef137 | ||
|
|
a9105e6797 | ||
|
|
3438a0c6a1 | ||
|
|
e969b3c2b3 | ||
|
|
d274a2b3db | ||
|
|
5c9ae0a0de | ||
|
|
8fd79a2792 | ||
|
|
9267f7a28d | ||
|
|
3479c3b3f2 | ||
|
|
ac94eccd90 | ||
|
|
144e4a3468 | ||
|
|
938f9e7674 | ||
|
|
c229160605 | ||
|
|
fb40bd9cec | ||
|
|
76125be9f5 | ||
|
|
d0730e12d9 | ||
|
|
66a08c7f71 | ||
|
|
f3a0d3a3fe | ||
|
|
4a5a56f403 | ||
|
|
846dd1bf22 | ||
|
|
99d8435e9d | ||
|
|
30f42e9495 | ||
|
|
f84ce8534e | ||
|
|
a266c1452c | ||
|
|
b61818fe62 | ||
|
|
dad4cd1f57 | ||
|
|
f5cabc7696 | ||
|
|
6e3ba27131 | ||
|
|
e68338a17b | ||
|
|
c7095e2570 | ||
|
|
31570e4fae | ||
|
|
307583e374 | ||
|
|
09601047e5 | ||
|
|
3d19f4727a | ||
|
|
4533a25c6f | ||
|
|
e8a81c49ea | ||
|
|
071141bc5a | ||
|
|
1db64d7324 | ||
|
|
bb32f7c448 | ||
|
|
457c83dba9 | ||
|
|
f8a941bdd9 | ||
|
|
9d6cffafdc | ||
|
|
5b704d0684 | ||
|
|
2c2ca8eb39 | ||
|
|
c9f57c5f20 | ||
|
|
327ac9dee6 | ||
|
|
dc34ee8714 | ||
|
|
3cd3695be6 | ||
|
|
dfcbfd632e | ||
|
|
bf50550911 | ||
|
|
e3ad330ebc | ||
|
|
983fd9af3d | ||
|
|
ebabad948f | ||
|
|
c3b71653bb | ||
|
|
8cad7514e7 | ||
|
|
1f174870ab | ||
|
|
7173f6cb18 | ||
|
|
0d7b037ac2 | ||
|
|
dae57c3d20 | ||
|
|
c1014c9cc0 | ||
|
|
2e486f8bed | ||
|
|
6addb29f7f | ||
|
|
c0988590a7 | ||
|
|
5e742c5b82 | ||
|
|
2a0feac7e0 | ||
|
|
603e232ca2 | ||
|
|
73fa4961c0 | ||
|
|
ebfdf8c087 | ||
|
|
ad6831712b | ||
|
|
9e65c52ff1 | ||
|
|
0054cfa363 | ||
|
|
145bc48722 | ||
|
|
e4d0ece093 | ||
|
|
c704ffe47a | ||
|
|
5182cb8107 | ||
|
|
9543f1adb0 | ||
|
|
21d765fb9f | ||
|
|
f9c2831939 | ||
|
|
9d2c4e4a0d | ||
|
|
5a2aa70f90 | ||
|
|
dee50a8548 | ||
|
|
23c0d77042 | ||
|
|
cf2c641564 | ||
|
|
0eb802e7bd | ||
|
|
8223800ac6 | ||
|
|
041d889970 | ||
|
|
4c242631fa | ||
|
|
75938809ea | ||
|
|
265c0b491a | ||
|
|
9ab2cec877 | ||
|
|
82bb338c1f | ||
|
|
3bc49f02f8 | ||
|
|
4f1a797cd7 | ||
|
|
f8a63b5a13 | ||
|
|
d250bf0223 | ||
|
|
e1d2f01f57 | ||
|
|
2cc97f33f1 | ||
|
|
b5acbafab9 | ||
|
|
bf1ffd0751 | ||
|
|
41b5a96fa6 | ||
|
|
116430e542 | ||
|
|
27a52d33d0 | ||
|
|
54c89e52f8 | ||
|
|
6b049d954b | ||
|
|
5a16b018c5 | ||
|
|
f849b8423d | ||
|
|
091f901789 | ||
|
|
3c0c7c3044 | ||
|
|
3c1a82e702 | ||
|
|
6fe61b775b | ||
|
|
dc5867b006 | ||
|
|
77b70b2142 | ||
|
|
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 |
399 changed files with 23802 additions and 33728 deletions
9
.babelrc
9
.babelrc
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-object-assign"
|
||||
]
|
||||
}
|
||||
|
||||
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"
|
||||
]
|
||||
}
|
||||
3
.bowerrc
3
.bowerrc
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"directory" : "modules"
|
||||
}
|
||||
4
.browserslistrc
Normal file
4
.browserslistrc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
last 2 Chrome versions
|
||||
last 2 Safari versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
31
.cloudcmd.menu.js
Normal file
31
.cloudcmd.menu.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
export default {
|
||||
'F2 - Rename File': renameCurrent,
|
||||
'L - Lint': run('npm run lint'),
|
||||
'F - Fix Lint': run('npm run fix:lint'),
|
||||
'T - Test': run('npm run test'),
|
||||
'C - Coverage': run('npm run coverage'),
|
||||
'D - Build Dev': run('npm run build:client:dev'),
|
||||
'P - Build Prod': run('npm run build:client'),
|
||||
};
|
||||
|
||||
async function renameCurrent(DOM) {
|
||||
await DOM.renameCurrent();
|
||||
}
|
||||
|
||||
function run(command) {
|
||||
return async ({CloudCmd, DOM}) => {
|
||||
const {TerminalRun, config} = CloudCmd;
|
||||
|
||||
const {CurrentInfo} = DOM;
|
||||
const {dirPath} = CurrentInfo;
|
||||
|
||||
const cwd = config('root') + dirPath;
|
||||
|
||||
return await TerminalRun.show({
|
||||
cwd,
|
||||
command,
|
||||
closeMessage: 'Press any key to close Terminal',
|
||||
autoClose: false,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -1,35 +1,23 @@
|
|||
.*
|
||||
*.spec.js
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
coverage
|
||||
test
|
||||
shell
|
||||
manifest.yml
|
||||
app.json
|
||||
bower.json
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
now.json
|
||||
|
||||
Dockerfile
|
||||
Dockerfile.alpine
|
||||
docker
|
||||
|
||||
webpack.config.js
|
||||
cssnano.config.js
|
||||
|
||||
bin/release.js
|
||||
|
||||
modules/jquery/dist
|
||||
modules/jquery/external
|
||||
modules/jquery/src
|
||||
|
||||
!modules/jquery/dist/jquery.min.js
|
||||
|
||||
modules/fancybox/lib/
|
||||
modules/fancybox/demo
|
||||
modules/fancybox/sprite.psd
|
||||
|
||||
modules/execon
|
||||
modules/emitify
|
||||
|
||||
client
|
||||
legacy
|
||||
server_
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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
|
||||
|
||||
|
|
|
|||
19
.eslintrc
19
.eslintrc
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"browser": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", 4],
|
||||
"semi": "error",
|
||||
"no-use-before-define": ["error", "nofunc"]
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", 4],
|
||||
"semi": "error",
|
||||
"no-use-before-define": ["error", "nofunc"],
|
||||
'no-process-exit': 0
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:node/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"node"
|
||||
]
|
||||
}
|
||||
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
|
||||
12
.github/ISSUE_TEMPLATE.md
vendored
12
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -1,12 +0,0 @@
|
|||
<!--
|
||||
Thank you for reporting an issue. Please fill in the template below. If unsure
|
||||
about something, just do as best as you're able.
|
||||
-->
|
||||
|
||||
* **Version** (`cloudcmd -v`):
|
||||
* **Node Version** `node -v`:
|
||||
* **OS** (`uname -a` on Linux):
|
||||
* **Browser name/version**:
|
||||
* **Used Command Line Parameters**:
|
||||
* **Changed Config**:
|
||||
|
||||
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**
|
||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -4,6 +4,5 @@ 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 codestyle` is OK
|
||||
- [ ] `npm run fix:lint` is OK
|
||||
- [ ] `npm test` is OK
|
||||
|
||||
|
|
|
|||
60
.github/workflows/docker-io.yml
vendored
Normal file
60
.github/workflows/docker-io.yml
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
name: Docker IO
|
||||
permissions:
|
||||
contents: write
|
||||
on:
|
||||
- push
|
||||
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 24.x
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.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
|
||||
VERSION=$(grep '"version":' package.json -m1 | cut -d\" -f4)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push io-image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile.io
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
coderaiser/cloudcmd:io
|
||||
coderaiser/cloudcmd:${{ steps.build.outputs.version }}-io
|
||||
ghcr.io/${{ github.repository }}-io
|
||||
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-io
|
||||
88
.github/workflows/docker.yml
vendored
Normal file
88
.github/workflows/docker.yml
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
name: Docker CI
|
||||
permissions:
|
||||
contents: write
|
||||
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
|
||||
VERSION=$(grep '"version":' package.json -m1 | cut -d\" -f4)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push base-image
|
||||
uses: docker/build-push-action@v7
|
||||
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@v7
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile.alpine
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
coderaiser/cloudcmd:alpine
|
||||
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
|
||||
- name: Build and push slim-image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile.slim
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
coderaiser/cloudcmd:slim
|
||||
coderaiser/cloudcmd:latest-slim
|
||||
coderaiser/cloudcmd:${{ steps.build.outputs.version }}-slim
|
||||
ghcr.io/${{ github.repository }}-slim
|
||||
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-slim
|
||||
53
.github/workflows/nodejs.yml
vendored
Normal file
53
.github/workflows/nodejs.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Node CI
|
||||
on:
|
||||
- push
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NAME: cloudcmd
|
||||
strategy:
|
||||
matrix:
|
||||
node-version:
|
||||
- 22.x
|
||||
- 24.x
|
||||
- 26.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 madrun -g --no-save
|
||||
- name: Install
|
||||
run: bun i --no-save
|
||||
- name: Init Madrun
|
||||
run: madrun --init
|
||||
- name: Lint
|
||||
run: redrun fix:lint
|
||||
- name: Typos
|
||||
uses: coderaiser/typos.ai@v1.1.8
|
||||
with:
|
||||
key: ${{ secrets.TYPOS_AI_KEY }}
|
||||
- name: Commit fixes
|
||||
uses: EndBug/add-and-commit@v10
|
||||
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 }}
|
||||
35
.gitignore
vendored
35
.gitignore
vendored
|
|
@ -1,30 +1,17 @@
|
|||
package-lock.json
|
||||
yarn.lock
|
||||
yarn-error.log
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
coverage
|
||||
|
||||
modules/jquery/dist
|
||||
modules/jquery/external
|
||||
modules/jquery/src
|
||||
|
||||
!modules/jquery/dist/jquery.min.js
|
||||
|
||||
modules/fancybox/lib/
|
||||
modules/fancybox/demo
|
||||
modules/fancybox/sprite.psd
|
||||
modules/fancybox/*.json
|
||||
modules/fancybox/gulpfile.js
|
||||
modules/jquery-mousewheel
|
||||
|
||||
modules/execon
|
||||
modules/emitify
|
||||
*.swp
|
||||
*.log
|
||||
*.lock
|
||||
|
||||
.nyc_output
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
*.swp
|
||||
package-lock.json
|
||||
npm-debug.log*
|
||||
|
||||
node_modules
|
||||
coverage
|
||||
modules/execon
|
||||
modules/emitify
|
||||
dist
|
||||
dist-dev
|
||||
|
||||
|
|
|
|||
20
.jscsrc
20
.jscsrc
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"preset": "crockford",
|
||||
"requireSpaceAfterKeywords": false,
|
||||
"requireCapitalizedConstructors": false,
|
||||
"requireMultipleVarDecl": false,
|
||||
"disallowTrailingWhitespace": "ignoreEmptyLines",
|
||||
"disallowTrailingComma": false,
|
||||
"disallowDanglingUnderscores": false,
|
||||
"validateIndentation": false,
|
||||
"requireCurlyBraces": false,
|
||||
"requireVarDeclFirst": false,
|
||||
"requireSpacesInAnonymousFunctionExpression": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"excludeFiles": [
|
||||
"node_modules/**",
|
||||
"modules/**",
|
||||
"bower_components/**"
|
||||
]
|
||||
}
|
||||
24
.jshintrc
24
.jshintrc
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"esversion": 6,
|
||||
"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",
|
||||
"validthis": true,
|
||||
"-W024": true,
|
||||
"-W120": true,
|
||||
"-W079": true,
|
||||
"-W138": true
|
||||
}
|
||||
62
.madrun.js
Normal file
62
.madrun.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import {run, cutEnv} from 'madrun';
|
||||
import {defineEnv} from 'supertape/env';
|
||||
|
||||
const testEnv = defineEnv({
|
||||
timeout: 7000,
|
||||
css: true,
|
||||
});
|
||||
|
||||
const buildEnv = {
|
||||
NODE_ENV: 'production',
|
||||
};
|
||||
|
||||
export default {
|
||||
'start': () => 'node bin/cloudcmd.js',
|
||||
'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': () => 'redlint fix; 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': async () => `putout --fix . && redlint fix`,
|
||||
'lint:stream': () => run('lint', '-f stream'),
|
||||
'test': () => [testEnv, `tape '{test}/**/*.js' '{bin,client,static,common,server}/**/*.spec.js' -f fail`],
|
||||
'test:e2e': () => `tape 'test-e2e/**/*.js'`,
|
||||
'test:client': () => `tape 'test/client/**/*.js'`,
|
||||
'test:server': () => `tape 'test/**/*.js' 'server/**/*.spec.js' 'common/**/*.spec.js'`,
|
||||
'wisdom': async () => await run(['lint:all', 'build', 'test'], null, {
|
||||
CI: 1,
|
||||
}),
|
||||
'wisdom:type': () => 'bin/release.js',
|
||||
'coverage': async () => [testEnv, `c8 ${await cutEnv('test')}`],
|
||||
'coverage:report': () => 'c8 report --reporter=lcov',
|
||||
'report': () => 'c8 report --reporter=lcov',
|
||||
'6to5': () => [buildEnv, 'rspack build --config rspack.config.js'],
|
||||
'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')}`],
|
||||
'watch:fix:lint': async () => `nodemon -w client -w server -w test -w common -x '${await run('fix:lint')}'`,
|
||||
'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'),
|
||||
};
|
||||
49
.npmignore
49
.npmignore
|
|
@ -1,34 +1,29 @@
|
|||
*.spec.*
|
||||
*.config.*
|
||||
*.fixture.js*
|
||||
*.ai
|
||||
*.cdr
|
||||
*.eps
|
||||
*.log
|
||||
*.lock
|
||||
|
||||
.*
|
||||
|
||||
manifest.yml
|
||||
Dockerfile*
|
||||
docker-compose.yml
|
||||
shell
|
||||
test
|
||||
now.json
|
||||
app.json
|
||||
bower.json
|
||||
deno.json
|
||||
bin/release.*
|
||||
img/logo/cloudcmd-hq.png
|
||||
webpack.config.js
|
||||
|
||||
docker
|
||||
test*
|
||||
fixture
|
||||
fixture-*
|
||||
coverage
|
||||
css
|
||||
html
|
||||
yarn-error.log
|
||||
|
||||
modules/jquery/dist
|
||||
modules/jquery/external
|
||||
modules/jquery/src
|
||||
|
||||
!modules/jquery/dist/jquery.min.js
|
||||
|
||||
modules/fancybox/lib/
|
||||
modules/fancybox/demo
|
||||
modules/fancybox/sprite.psd
|
||||
|
||||
modules/execon
|
||||
modules/emitify
|
||||
|
||||
app.json
|
||||
bower.json
|
||||
manifest.yml
|
||||
|
||||
bin/release.js
|
||||
|
||||
client
|
||||
|
||||
webpack.config.js
|
||||
|
||||
|
|
|
|||
15
.nycrc.json
Normal file
15
.nycrc.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"checkCoverage": false,
|
||||
"all": false,
|
||||
"exclude": [
|
||||
"**/*.spec.js",
|
||||
"**/*.*.js",
|
||||
"**/*.config.*",
|
||||
"**/fixture",
|
||||
"**/test/**"
|
||||
],
|
||||
"branches": 100,
|
||||
"lines": 100,
|
||||
"functions": 100,
|
||||
"statements": 100
|
||||
}
|
||||
60
.putout.json
Normal file
60
.putout.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"plugins": ["cloudcmd"],
|
||||
"ignore": [
|
||||
"*.md",
|
||||
"app.json",
|
||||
"fontello.json",
|
||||
"html",
|
||||
"fixture*"
|
||||
],
|
||||
"rules": {
|
||||
"package-json/add-type": "off"
|
||||
},
|
||||
"match": {
|
||||
".filesystem.json": {
|
||||
"nodejs/rename-file-cjs-to-js": "off"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
38
.rspack/css.js
Normal file
38
.rspack/css.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import {env} from 'node:process';
|
||||
import {rspack} from '@rspack/core';
|
||||
|
||||
const {CssExtractRspackPlugin} = rspack;
|
||||
const isDev = env.NODE_ENV === 'development';
|
||||
|
||||
const plugins = [
|
||||
new CssExtractRspackPlugin({
|
||||
filename: '[name].css',
|
||||
}),
|
||||
];
|
||||
|
||||
const rules = [{
|
||||
test: /\.css$/i,
|
||||
use: [CssExtractRspackPlugin.loader, {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
url: true,
|
||||
},
|
||||
}],
|
||||
}, {
|
||||
test: /\.(png|gif|svg|woff|woff2|eot|ttf)$/,
|
||||
type: 'asset/inline',
|
||||
}];
|
||||
|
||||
export default {
|
||||
mode: isDev ? 'development' : 'production',
|
||||
plugins,
|
||||
module: {
|
||||
rules,
|
||||
},
|
||||
optimization: {
|
||||
minimize: !isDev,
|
||||
minimizer: [
|
||||
new rspack.LightningCssMinimizerRspackPlugin(),
|
||||
],
|
||||
},
|
||||
};
|
||||
36
.rspack/html.js
Normal file
36
.rspack/html.js
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,
|
||||
};
|
||||
}
|
||||
173
.rspack/js.js
Normal file
173
.rspack/js.js
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import {resolve, sep} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {env} from 'node:process';
|
||||
import {rspack} from '@rspack/core';
|
||||
|
||||
const resolveModule = (a) => fileURLToPath(import.meta.resolve(a));
|
||||
|
||||
const {
|
||||
EnvironmentPlugin,
|
||||
NormalModuleReplacementPlugin,
|
||||
ProvidePlugin,
|
||||
} = rspack;
|
||||
|
||||
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 noParse = (a) => a.endsWith('.spec.js');
|
||||
|
||||
// codegen.macro is a babel-macro (build-time codegen), not supported by
|
||||
// Rspack's native SWC transform, so client/sw/sw.js (the only file that
|
||||
// uses it) keeps going through babel-loader. Everything else uses
|
||||
// Rspack's built-in SWC loader, which is the main source of the speedup.
|
||||
const rules = [{
|
||||
test: /sw\/sw\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
}, {
|
||||
test: /\.[mc]?js$/,
|
||||
exclude: [/node_modules/, /sw\/sw\.js$/],
|
||||
loader: 'builtin:swc-loader',
|
||||
options: {
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'ecmascript',
|
||||
},
|
||||
},
|
||||
env: {
|
||||
targets: 'defaults',
|
||||
},
|
||||
},
|
||||
}];
|
||||
|
||||
const plugins = [
|
||||
new NormalModuleReplacementPlugin(/^node:/, (resource) => {
|
||||
resource.request = resource.request.replace(/^node:/, '');
|
||||
}),
|
||||
new NormalModuleReplacementPlugin(/^putout$/, '@putout/bundle'),
|
||||
new EnvironmentPlugin({
|
||||
NODE_ENV,
|
||||
}),
|
||||
new ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
];
|
||||
|
||||
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',
|
||||
'columns/name-size-time',
|
||||
'columns/name-size-date-time',
|
||||
];
|
||||
|
||||
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: resolveModule('path-browserify'),
|
||||
process: resolveModule('process/browser'),
|
||||
util: resolveModule('util'),
|
||||
},
|
||||
},
|
||||
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`,
|
||||
'columns/name-size-date-time': `${dirColumns}/name-size-date-time.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.js`,
|
||||
[`${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.js`,
|
||||
[`${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: 800_000,
|
||||
maxAssetSize: 600_000,
|
||||
},
|
||||
};
|
||||
|
||||
function devtoolModuleFilenameTemplate(info) {
|
||||
const resource = info.absoluteResourcePath.replace(rootDir + sep, '');
|
||||
return `file://cloudcmd/${resource}`;
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
extends: stylelint-config-standard
|
||||
rules:
|
||||
indentation: 4
|
||||
declaration-block-trailing-semicolon: always
|
||||
declaration-colon-space-before: null
|
||||
selector-list-comma-newline-after: null
|
||||
comment-empty-line-before: null
|
||||
number-leading-zero: null
|
||||
number-no-trailing-zeros: null
|
||||
string-quotes: single
|
||||
function-url-quotes: never
|
||||
no-eol-whitespace: null
|
||||
font-family-name-quotes: always-unless-keyword
|
||||
rule-empty-line-before: null
|
||||
|
||||
18
.travis.yml
18
.travis.yml
|
|
@ -1,18 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 8
|
||||
- 7
|
||||
- 6
|
||||
|
||||
script:
|
||||
- npm run codestyle
|
||||
- npm run 6to5:client:dev
|
||||
- npm run coverage && npm run report
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: change
|
||||
|
||||
sudo: false
|
||||
|
||||
2
.typos.toml
Normal file
2
.typos.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[files]
|
||||
extend-exclude = ["ChangeLog"]
|
||||
22
.yaspellerrc
22
.yaspellerrc
|
|
@ -10,6 +10,7 @@
|
|||
".md"
|
||||
],
|
||||
"dictionary":[
|
||||
"CloudCmd",
|
||||
"Dev",
|
||||
"Dropbox",
|
||||
"Deepword",
|
||||
|
|
@ -20,17 +21,24 @@
|
|||
"Heroku",
|
||||
"Iptables",
|
||||
"JitSu",
|
||||
"Node",
|
||||
"IO",
|
||||
"Olena",
|
||||
"TarZak",
|
||||
"Termux",
|
||||
"Zalitok",
|
||||
"WebSocket",
|
||||
"auth",
|
||||
"autostart",
|
||||
"binded",
|
||||
"cd",
|
||||
"cloudcmd",
|
||||
"coderaiser",
|
||||
"com",
|
||||
"dev",
|
||||
"deepword",
|
||||
"dev",
|
||||
"destructuring",
|
||||
"dropbox",
|
||||
"dword",
|
||||
"edward",
|
||||
"favicon",
|
||||
|
|
@ -38,17 +46,27 @@
|
|||
"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"
|
||||
"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,27 +1,30 @@
|
|||
Commit
|
||||
---------------
|
||||
## 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)
|
||||
|
|
|
|||
24
Dockerfile
24
Dockerfile
|
|
@ -1,24 +0,0 @@
|
|||
FROM node
|
||||
MAINTAINER Coderaiser
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package.json /usr/src/app/
|
||||
|
||||
RUN npm config set package-lock false && \
|
||||
npm install --production && \
|
||||
npm i gritty && \
|
||||
npm cache clean --force
|
||||
|
||||
COPY . /usr/src/app
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
ENV cloudcmd_terminal true
|
||||
ENV cloudcmd_terminal_path gritty
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/usr/src/app/bin/cloudcmd.js"]
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
FROM mhart/alpine-node
|
||||
MAINTAINER Coderaiser
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package.json /usr/src/app/
|
||||
|
||||
RUN npm config set package-lock false && \
|
||||
npm install --production && \
|
||||
apk update && \
|
||||
apk add --no-cache bash make g++ python && \
|
||||
npm i gritty && \
|
||||
npm cache clean --force && \
|
||||
apk del make g++ python && \
|
||||
rm -rf /usr/include /tmp/* /var/cache/apk/*
|
||||
|
||||
COPY . /usr/src/app
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
ENV cloudcmd_terminal true
|
||||
ENV cloudcmd_terminal_path gritty
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/usr/src/app/bin/cloudcmd.js"]
|
||||
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2012-2017 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
|
||||
|
|
|
|||
197
README.md
197
README.md
|
|
@ -1,41 +1,33 @@
|
|||
# Cloud Commander v7.2.2 [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status][DependencyStatusIMGURL]][DependencyStatusURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Package Quality][PackageQualityIMGURL]][PackageQualityURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [](#backers) [](#sponsors)
|
||||
# Cloud Commander v19.19.1 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL]
|
||||
|
||||
### [Main][MainURL] [Blog][BlogURL] Live([Heroku][HerokuURL])
|
||||
### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL]
|
||||
|
||||
[NPM_INFO_IMG]: https://nodei.co/npm/cloudcmd.png
|
||||
[MainURL]: http://cloudcmd.io "Main"
|
||||
[BlogURL]: http://blog.cloudcmd.io "Blog"
|
||||
[HerokuURL]: http://cloudcmd.herokuapp.com/ "Heroku"
|
||||
[NPMURL]: https://npmjs.org/package/cloudcmd "npm"
|
||||
[NPMIMGURL]: https://img.shields.io/npm/v/cloudcmd.svg?style=flat
|
||||
[LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License"
|
||||
[LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat
|
||||
[DependencyStatusURL]: https://gemnasium.com/coderaiser/cloudcmd "Dependency Status"
|
||||
[DependencyStatusIMGURL]: https://img.shields.io/gemnasium/coderaiser/cloudcmd.svg?style=flat
|
||||
[BuildStatusURL]: https://travis-ci.org/coderaiser/cloudcmd "Build Status"
|
||||
[BuildStatusIMGURL]: https://img.shields.io/travis/coderaiser/cloudcmd.svg?style=flat
|
||||
[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
|
||||
[DeployInstaPodsIMG]: https://img.shields.io/badge/deploy%20on-InstaPods-blue
|
||||
[DeployInstaPodsURL]: https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd
|
||||
|
||||
[PackageQualityURL]: http://packagequality.com/#?package=cloudcmd "Package Quality"
|
||||
[PackageQualityIMGURL]: http://packagequality.com/shield/cloudcmd.svg
|
||||
**Cloud Commander** a file manager for the web with console and editor.
|
||||
|
||||
[CodacyURL]: https://www.codacy.com/app/coderaiser/cloudcmd
|
||||
[CodacyIMG]: https://api.codacy.com/project/badge/Grade/ddda78be780549ce8754f8d47a8c0e36
|
||||
|
||||
[GitterURL]: https://gitter.im/cloudcmd
|
||||
[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** is an orthodox web file manager with console and editor.
|
||||
|
||||

|
||||

|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
npm i cloudcmd -g
|
||||
```
|
||||
|
||||
## Start
|
||||
|
||||
For starting just type in console:
|
||||
|
|
@ -48,7 +40,13 @@ cloudcmd
|
|||
|
||||
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]
|
||||
|
|
@ -72,54 +70,72 @@ npm i cloudcmd express socket.io -S
|
|||
And create `index.js`:
|
||||
|
||||
```js
|
||||
const http = require('http');
|
||||
const cloudcmd = require('cloudcmd');
|
||||
const io = require('socket.io');
|
||||
const app = require('express')();
|
||||
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 = '/cloudcmd';
|
||||
const prefix = '/';
|
||||
|
||||
const server = http.createServer(app);
|
||||
const socket = io.listen(server, {
|
||||
path: `${prefix}/socket.io`
|
||||
const socket = new Server(server, {
|
||||
path: `${prefix}socket.io`,
|
||||
});
|
||||
|
||||
const config = {
|
||||
prefix /* base URL or function which returns base URL (optional) */
|
||||
name: 'cloudcmd :)',
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
__dirname + '/plugin.js'
|
||||
];
|
||||
|
||||
const filePicker = {
|
||||
data: {
|
||||
FilePicker: {
|
||||
key: 'key',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// override option from json/modules.json
|
||||
const modules = {filePicker};
|
||||
const modules = {
|
||||
filePicker,
|
||||
};
|
||||
|
||||
app.use(cloudcmd({
|
||||
socket, /* used by Config, Edit (optional) and Console (required) */
|
||||
config, /* config data (optional) */
|
||||
plugins, /* optional */
|
||||
modules, /* optional */
|
||||
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
|
||||
---------------
|
||||
## 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 -t --rm -v ~:/root -v /:/mnt/fs -p 8000:8000 coderaiser/cloudcmd
|
||||
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`,
|
||||
|
|
@ -145,84 +161,19 @@ When you create this file run:
|
|||
docker-compose up
|
||||
```
|
||||
|
||||
Get involved
|
||||
---------------
|
||||
## Documentation
|
||||
|
||||
More documentation you can find on https://cloudcmd.io/.
|
||||
|
||||
## Get involved
|
||||
|
||||
There is a lot ways to be involved in `Cloud Commander` development:
|
||||
|
||||
- if you find a bug or got idea to share [create issue](https://github.com/coderaiser/cloudcmd/issues/new "Create issue");
|
||||
- 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");
|
||||
|
||||
## Backers
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/cloudcmd#backer)]
|
||||
|
||||
[](https://opencollective.com/cloudcmd/backer/0/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/1/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/2/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/3/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/4/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/5/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/6/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/7/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/8/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/9/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/10/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/11/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/12/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/13/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/14/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/15/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/16/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/17/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/18/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/19/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/20/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/21/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/22/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/23/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/24/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/25/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/26/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/27/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/28/website)
|
||||
[](https://opencollective.com/cloudcmd/backer/29/website)
|
||||
|
||||
## Sponsors
|
||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/cloudcmd#sponsor)]
|
||||
|
||||
[](https://opencollective.com/cloudcmd/sponsor/0/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/1/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/2/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/3/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/4/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/5/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/6/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/7/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/8/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/9/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/10/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/11/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/12/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/13/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/14/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/15/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/16/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/17/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/18/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/19/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/20/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/21/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/22/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/23/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/24/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/25/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/26/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/27/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/28/website)
|
||||
[](https://opencollective.com/cloudcmd/sponsor/29/website)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
|
|
|||
91
app.json
91
app.json
|
|
@ -11,6 +11,7 @@
|
|||
"folder",
|
||||
"orthodox"
|
||||
],
|
||||
"stack": "heroku-18",
|
||||
"env": {
|
||||
"NPM_CONFIG_PRODUCTION": {
|
||||
"description": "Keep false to install devDependencies and build frontend",
|
||||
|
|
@ -32,11 +33,21 @@
|
|||
"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",
|
||||
|
|
@ -47,6 +58,21 @@
|
|||
"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": "",
|
||||
|
|
@ -66,6 +92,71 @@
|
|||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
369
bin/cloudcmd.js
369
bin/cloudcmd.js
|
|
@ -1,23 +1,48 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
import process from 'node:process';
|
||||
import {promisify} from 'node:util';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import parse from 'yargs-parser';
|
||||
import exit from '../server/exit.js';
|
||||
import {createConfig, configPath} from '../server/config.js';
|
||||
import * as env from '../server/env.js';
|
||||
import prefixer from '../server/prefixer.js';
|
||||
import * as validate from '../server/validate.js';
|
||||
import Info from '../package.json' with {
|
||||
type: 'json',
|
||||
};
|
||||
|
||||
const Info = require('../package');
|
||||
const DIR_SERVER = '../server/';
|
||||
process.on('unhandledRejection', exit);
|
||||
|
||||
const exit = require(DIR_SERVER + 'exit');
|
||||
const config = require(DIR_SERVER + 'config');
|
||||
const env = require(DIR_SERVER + 'env');
|
||||
const isUndefined = (a) => typeof a === 'undefined';
|
||||
|
||||
const choose = (a, b) => {
|
||||
if (!a && typeof a !== 'boolean')
|
||||
if (isUndefined(a))
|
||||
return b;
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
const argv = process.argv;
|
||||
const args = require('minimist')(argv.slice(2), {
|
||||
const config = createConfig({
|
||||
configPath,
|
||||
});
|
||||
|
||||
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',
|
||||
|
|
@ -28,7 +53,16 @@ const args = require('minimist')(argv.slice(2), {
|
|||
'packer',
|
||||
'root',
|
||||
'prefix',
|
||||
'prefix-socket',
|
||||
'terminal-path',
|
||||
'terminal-command',
|
||||
'columns',
|
||||
'menu',
|
||||
'theme',
|
||||
'import-url',
|
||||
'import-token',
|
||||
'export-token',
|
||||
'dropbox-token',
|
||||
],
|
||||
boolean: [
|
||||
'auth',
|
||||
|
|
@ -37,52 +71,91 @@ const args = require('minimist')(argv.slice(2), {
|
|||
'server',
|
||||
'online',
|
||||
'open',
|
||||
'progress',
|
||||
'config-dialog',
|
||||
'config-auth',
|
||||
'config-port',
|
||||
'console',
|
||||
'sync-console-path',
|
||||
'contact',
|
||||
'terminal',
|
||||
'one-panel-mode',
|
||||
'html-dialogs',
|
||||
'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 : env('name') || config('name'),
|
||||
auth : choose(env('auth'), config('auth')),
|
||||
port : config('port'),
|
||||
online : config('online'),
|
||||
open : config('open'),
|
||||
editor : env('editor') || config('editor'),
|
||||
packer : config('packer') || 'tar',
|
||||
zip : config('zip'),
|
||||
username : env('username') || config('username'),
|
||||
root : config('root'),
|
||||
prefix : config('prefix'),
|
||||
progress : config('progress'),
|
||||
console : choose(env.bool('console'), config('console')),
|
||||
contact : choose(env.bool('contact'), config('contact')),
|
||||
terminal : choose(env.bool('terminal'), config('terminal')),
|
||||
'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': choose(env.bool('log'), config('log')),
|
||||
|
||||
'terminal-path': env('terminal_path') || config('terminalPath'),
|
||||
'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')),
|
||||
'one-panel-mode': config('onePanelMode'),
|
||||
'html-dialogs': config('htmlDialogs')
|
||||
'config-auth': choose(env.bool('config_auth'), config('configAuth')),
|
||||
'config-port': choose(env.bool('config_port'), config('configPort')),
|
||||
'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: {
|
||||
v: 'version',
|
||||
h: 'help',
|
||||
p: 'password',
|
||||
o: 'online',
|
||||
u: 'username',
|
||||
s: 'save',
|
||||
a: 'auth',
|
||||
c: 'config'
|
||||
version: 'v',
|
||||
help: 'h',
|
||||
password: 'p',
|
||||
online: 'o',
|
||||
username: 'u',
|
||||
save: 's',
|
||||
auth: 'a',
|
||||
config: 'c',
|
||||
},
|
||||
unknown: (cmd) => {
|
||||
exit('\'%s\' is not a cloudcmd option. See \'cloudcmd --help\'.', cmd);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const {argv} = process;
|
||||
const args = parse(argv.slice(2), yargsOptions);
|
||||
|
||||
if (args.version)
|
||||
version();
|
||||
|
|
@ -91,11 +164,22 @@ else if (args.help)
|
|||
else
|
||||
main();
|
||||
|
||||
function main() {
|
||||
if (args.repl)
|
||||
repl();
|
||||
async function main() {
|
||||
const {validateArgs} = await import('@putout/cli-validate-args');
|
||||
|
||||
checkUpdate();
|
||||
const error = await validateArgs(args, [
|
||||
...yargsOptions.boolean,
|
||||
...yargsOptions.string,
|
||||
]);
|
||||
|
||||
if (error)
|
||||
return exit(error);
|
||||
|
||||
if (args.repl)
|
||||
await repl();
|
||||
|
||||
validate.columns(args.columns);
|
||||
validate.theme(args.theme);
|
||||
|
||||
port(args.port);
|
||||
|
||||
|
|
@ -104,53 +188,88 @@ function main() {
|
|||
config('online', args.online);
|
||||
config('open', args.open);
|
||||
config('username', args.username);
|
||||
config('progress', args.progress);
|
||||
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['terminal-path']);
|
||||
config('terminalPath', args.terminalPath);
|
||||
config('terminalCommand', args.terminalCommand);
|
||||
config('terminalAutoRestart', args.terminalAutoRestart);
|
||||
config('editor', args.editor);
|
||||
config('prefix', args.prefix);
|
||||
config('root', args.root);
|
||||
config('htmlDialogs', args['html-dialogs']);
|
||||
config('onePanelMode', args['one-panel-mode']);
|
||||
config('configDialog', args['config-dialog']);
|
||||
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('configPort', args.configPort);
|
||||
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);
|
||||
|
||||
readConfig(args.config);
|
||||
config('dropbox', args.dropbox);
|
||||
config('dropboxToken', args.dropboxToken);
|
||||
|
||||
await readConfig(args.config);
|
||||
|
||||
const options = {
|
||||
root: args.root || '/', /* --no-root */
|
||||
editor: args.editor,
|
||||
packer: args.packer,
|
||||
prefix: args.prefix
|
||||
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('password') || args.password;
|
||||
const password = env.parse('password') || args.password;
|
||||
|
||||
if (password)
|
||||
config('password', getPassword(password));
|
||||
config('password', await getPassword(password));
|
||||
|
||||
validateRoot(options.root);
|
||||
validateRoot(options.root, config);
|
||||
|
||||
if (args['show-config'])
|
||||
showConfig();
|
||||
if (args.showConfig)
|
||||
await showConfig();
|
||||
|
||||
if (!args.save)
|
||||
return start(options);
|
||||
const {distributeImport} = await import('../server/distribute/import.js');
|
||||
const importConfig = promisify(distributeImport);
|
||||
|
||||
config.save(() => {
|
||||
start(options);
|
||||
});
|
||||
await start(options, config);
|
||||
|
||||
if (args.save)
|
||||
config.write();
|
||||
|
||||
await tryToCatch(checkUpdate);
|
||||
await importConfig(config);
|
||||
}
|
||||
|
||||
function validateRoot(root) {
|
||||
const validate = require(DIR_SERVER + 'validate');
|
||||
validate.root(root, console.log);
|
||||
function validateRoot(root, config) {
|
||||
validate.root(root, config);
|
||||
|
||||
if (root === '/')
|
||||
return;
|
||||
|
||||
if (config('log'))
|
||||
console.log(`root: ${root}`);
|
||||
}
|
||||
|
||||
function getPassword(password) {
|
||||
const criton = require('criton');
|
||||
|
||||
async function getPassword(password) {
|
||||
const {default: criton} = await import('criton');
|
||||
return criton(password, config('algo'));
|
||||
}
|
||||
|
||||
|
|
@ -158,11 +277,12 @@ function version() {
|
|||
console.log('v' + Info.version);
|
||||
}
|
||||
|
||||
function start(config) {
|
||||
const SERVER = DIR_SERVER + 'server';
|
||||
async function start(options, config) {
|
||||
if (!args.server)
|
||||
return;
|
||||
|
||||
if (args.server)
|
||||
require(SERVER)(config);
|
||||
const {default: server} = await import('../server/server.js');
|
||||
server(options, config);
|
||||
}
|
||||
|
||||
function port(arg) {
|
||||
|
|
@ -174,84 +294,69 @@ function port(arg) {
|
|||
exit('cloudcmd --port: should be a number');
|
||||
}
|
||||
|
||||
function showConfig() {
|
||||
const show = require('../server/show-config');
|
||||
const data = show(config('*'));
|
||||
async function showConfig() {
|
||||
const {showConfig} = await import('../server/show-config.js');
|
||||
const data = showConfig(config('*'));
|
||||
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
function readConfig(name) {
|
||||
async function readConfig(name) {
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
const fs = require('fs');
|
||||
const tryCatch = require('try-catch');
|
||||
const jju = require('jju');
|
||||
const {default: forEachKey} = await import('for-each-key');
|
||||
|
||||
const readjsonSync = (name) => jju.parse(fs.readFileSync(name, 'utf8'), {
|
||||
mode: 'json'
|
||||
const data = await import(name, {
|
||||
with: {
|
||||
type: 'json',
|
||||
},
|
||||
});
|
||||
|
||||
let data;
|
||||
|
||||
const error = tryCatch(() => {
|
||||
data = readjsonSync(name);
|
||||
});
|
||||
|
||||
if (error)
|
||||
return exit(error.message);
|
||||
|
||||
Object.keys(data).forEach((item) => {
|
||||
config(item, data[item]);
|
||||
});
|
||||
forEachKey(config, data);
|
||||
}
|
||||
|
||||
function help() {
|
||||
const bin = require('../json/help');
|
||||
async function help() {
|
||||
const {default: bin} = await import('../json/help.json', {
|
||||
with: {
|
||||
type: 'json',
|
||||
},
|
||||
});
|
||||
|
||||
const {default: forEachKey} = await import('for-each-key');
|
||||
const {default: currify} = await import('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:');
|
||||
|
||||
Object.keys(bin).forEach((name) => {
|
||||
console.log(' %s %s', name, bin[name]);
|
||||
});
|
||||
|
||||
forEachKey(log(' %s %s'), bin);
|
||||
console.log('\nGeneral help using Cloud Commander: <%s>', url);
|
||||
}
|
||||
|
||||
function repl() {
|
||||
async function repl() {
|
||||
console.log('REPL mode enabled (telnet localhost 1337)');
|
||||
require(DIR_SERVER + 'repl');
|
||||
await import('../server/repl.js');
|
||||
}
|
||||
|
||||
function checkUpdate() {
|
||||
const load = require('package-json');
|
||||
const noop = () => {};
|
||||
async function checkUpdate() {
|
||||
const {default: load} = await import('package-json');
|
||||
const {version} = await load(Info.name, 'latest');
|
||||
|
||||
load(Info.name, 'latest')
|
||||
.then(showUpdateInfo)
|
||||
.catch(noop);
|
||||
await showUpdateInfo(version);
|
||||
}
|
||||
|
||||
function showUpdateInfo(data) {
|
||||
const version = data.version;
|
||||
async function showUpdateInfo(version) {
|
||||
if (version === Info.version)
|
||||
return;
|
||||
|
||||
if (version !== Info.version) {
|
||||
const chalk = require('chalk');
|
||||
const rendy = require('rendy');
|
||||
|
||||
const latest = rendy('update available: {{ latest }}', {
|
||||
latest: chalk.green.bold('v' + version),
|
||||
});
|
||||
|
||||
const current = chalk.dim(rendy('(current: v{{ current }})', {
|
||||
current: Info.version
|
||||
}));
|
||||
|
||||
console.log('%s %s', latest, current);
|
||||
}
|
||||
const {default: chalk} = await import('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);
|
||||
}
|
||||
|
||||
|
|
|
|||
26
bin/cloudcmd.spec.js
Normal file
26
bin/cloudcmd.spec.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import {spawnSync} from 'node:child_process';
|
||||
import {test} from 'supertape';
|
||||
import info from '../package.json' with {
|
||||
type: 'json',
|
||||
};
|
||||
|
||||
const cliPath = new URL('cloudcmd.js', import.meta.url).pathname;
|
||||
|
||||
test('cloudcmd: bin: cli: -h', (t) => {
|
||||
const {stdout} = spawnSync(cliPath, ['-h'], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
t.match(stdout, `Options`);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: bin: cli: -v', (t) => {
|
||||
const {version} = info;
|
||||
const {stdout} = spawnSync(cliPath, ['-v'], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
t.match(stdout, `v${version}`);
|
||||
t.end();
|
||||
});
|
||||
|
|
@ -1,67 +1,72 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
import {promisify} from 'node:util';
|
||||
import process from 'node:process';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import minor from 'minor';
|
||||
import _place from 'place';
|
||||
import {rendy} from 'rendy';
|
||||
import shortdate from 'shortdate';
|
||||
import Info from '../package.json' with {
|
||||
type: 'json',
|
||||
};
|
||||
|
||||
const DIR = '../';
|
||||
const Info = require(DIR + 'package');
|
||||
const place = promisify(_place);
|
||||
|
||||
const minor = require('minor');
|
||||
const place = require('place');
|
||||
const rendy = require('rendy');
|
||||
const shortdate = require('shortdate');
|
||||
await main();
|
||||
|
||||
const ERROR = Error('ERROR: version is missing. release --patch|--minor|--major');
|
||||
|
||||
main((error) => {
|
||||
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)
|
||||
console.error(error.message);
|
||||
});
|
||||
|
||||
function main(callback) {
|
||||
const history = 'Version history\n---------------\n';
|
||||
const link = '//github.com/coderaiser/cloudcmd/releases/tag/';
|
||||
const template = '- *{{ date }}*, ' +
|
||||
'**[v{{ version }}]' +
|
||||
'(' + link + 'v{{ version }})**\n';
|
||||
return console.error(error);
|
||||
|
||||
const version = Info.version;
|
||||
await replaceVersion('README.md', version, versionNew);
|
||||
await replaceVersion('HELP.md', version, versionNew);
|
||||
|
||||
cl((error, versionNew) => {
|
||||
if (error)
|
||||
return callback(error);
|
||||
|
||||
replaceVersion('README.md', version, versionNew, callback);
|
||||
replaceVersion('HELP.md', version, versionNew, function() {
|
||||
const historyNew = history + rendy(template, {
|
||||
date : shortdate(),
|
||||
version : versionNew
|
||||
});
|
||||
|
||||
replaceVersion('HELP.md', history, historyNew, callback);
|
||||
const historyNew = history +
|
||||
rendy(template, {
|
||||
date: shortdate(),
|
||||
version: versionNew,
|
||||
});
|
||||
});
|
||||
|
||||
await replaceVersion('HELP.md', history, historyNew);
|
||||
}
|
||||
|
||||
function replaceVersion(name, version, versionNew, callback) {
|
||||
place(name, version, versionNew, (error) => {
|
||||
if (error)
|
||||
return callback(error);
|
||||
|
||||
callback(null, 'done: ' + name);
|
||||
});
|
||||
async function replaceVersion(name, version, versionNew) {
|
||||
const [error] = await tryToCatch(place, name, version, versionNew);
|
||||
|
||||
if (error)
|
||||
return console.error(error);
|
||||
|
||||
console.log(`done: ${name}`);
|
||||
}
|
||||
|
||||
function cl(callback) {
|
||||
const argv = process.argv;
|
||||
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) || [];
|
||||
|
||||
if (!regExp.test(last))
|
||||
return callback(ERROR);
|
||||
console.log(last);
|
||||
|
||||
callback(null, getVersionNew(last, match));
|
||||
if (!regExp.test(last))
|
||||
throw Error('ERROR: version is missing. release --patch|--minor|--major');
|
||||
|
||||
return getVersionNew(last, match);
|
||||
}
|
||||
|
||||
function getVersionNew(last, match) {
|
||||
|
|
@ -70,4 +75,3 @@ function getVersionNew(last, match) {
|
|||
|
||||
return last.substr(3);
|
||||
}
|
||||
|
||||
|
|
|
|||
40
bower.json
40
bower.json
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"name": "cloudcmd",
|
||||
"homepage": "https://github.com/coderaiser/cloudcmd",
|
||||
"authors": [
|
||||
"coderaiser <mnemonic.enemy@gmail.com>"
|
||||
],
|
||||
"description": "orthodox web file manager with console and editor",
|
||||
"main": "bin/cloudcmd.js",
|
||||
"keywords": [
|
||||
"console",
|
||||
"editor",
|
||||
"file",
|
||||
"file manager",
|
||||
"folder",
|
||||
"orthodox",
|
||||
"view",
|
||||
"copy",
|
||||
"rename",
|
||||
"move",
|
||||
"delete",
|
||||
"file operation"
|
||||
],
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"modules",
|
||||
"test"
|
||||
],
|
||||
"dependencies": {
|
||||
"domtokenlist-shim": "~1.1.0",
|
||||
"findit": "1.1.7",
|
||||
"menu": "~1.0.2",
|
||||
"olark": "^1.0.0",
|
||||
"jquery": "3.2.1",
|
||||
"fancybox": "^2.1.6",
|
||||
"philip": "^1.3.7",
|
||||
"smalltalk": "2.4.1"
|
||||
}
|
||||
}
|
||||
657
client/client.js
657
client/client.js
|
|
@ -1,52 +1,59 @@
|
|||
'use strict';
|
||||
|
||||
const itype = require('itype/legacy');
|
||||
const rendy = require('rendy');
|
||||
const exec = require('execon');
|
||||
const Images = require('./dom/images');
|
||||
const join = require('join-io/www/join');
|
||||
const jonny = require('jonny');
|
||||
|
||||
const {
|
||||
import process from 'node:process';
|
||||
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 {
|
||||
apiURL,
|
||||
formatMsg,
|
||||
buildFromJSON,
|
||||
} = require('../common/cloudfunc');
|
||||
} from '#common/cloudfunc';
|
||||
import * as Images from '#dom/images';
|
||||
import {unregisterSW} from './sw/register.js';
|
||||
import {getJsonFromFileTable} from './get-json-from-file-table.js';
|
||||
import {Key} from './key/index.js';
|
||||
import {loadModule} from './load-module.js';
|
||||
|
||||
/* global Util, DOM */
|
||||
const noJS = (a) => a.replace(/.js$/, '');
|
||||
|
||||
module.exports = new CloudCmdProto(Util, DOM);
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
function CloudCmdProto(Util, DOM) {
|
||||
let Key;
|
||||
let Debug;
|
||||
let Listeners;
|
||||
|
||||
const log = (str) => {
|
||||
if (!Debug)
|
||||
return;
|
||||
|
||||
console.log(str);
|
||||
};
|
||||
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 = DOM.Storage;
|
||||
const Files = DOM.Files;
|
||||
|
||||
this.log = log;
|
||||
this.PREFIX = '';
|
||||
this.PREFIX_URL = '';
|
||||
this.DIRCLIENT = '/dist/';
|
||||
this.DIRCLIENT_MODULES = this.DIRCLIENT + 'modules/';
|
||||
const {Storage, Files} = DOM;
|
||||
|
||||
this.MIN_ONE_PANEL_WIDTH = 1155;
|
||||
this.HOST = location.origin ||
|
||||
location.protocol + '//' + location.host;
|
||||
|
||||
const TITLE = 'Cloud Commander';
|
||||
this.TITLE = TITLE;
|
||||
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',
|
||||
|
|
@ -57,182 +64,75 @@ function CloudCmdProto(Util, DOM) {
|
|||
right: 'asc',
|
||||
};
|
||||
|
||||
log.enable = () => {
|
||||
Debug = true;
|
||||
};
|
||||
|
||||
log.disable = () => {
|
||||
Debug = false;
|
||||
};
|
||||
|
||||
const kebabToCamelCase = Util.kebabToCamelCase;
|
||||
|
||||
/**
|
||||
* Функция привязываеться ко всем ссылкам и
|
||||
* загружает содержимое каталогов
|
||||
*
|
||||
* @param params - {
|
||||
* paramLink - ссылка
|
||||
* needRefresh - необходимость обязательной загрузки данных с сервера
|
||||
* panel
|
||||
* }
|
||||
* @param callback
|
||||
*/
|
||||
this.loadDir = (params, callback) => {
|
||||
const p = params;
|
||||
|
||||
const refresh = p.isRefresh;
|
||||
const panel = p.panel;
|
||||
const history = p.history;
|
||||
const noCurrent = p.noCurrent;
|
||||
const currentName = p.currentName;
|
||||
this.changeDir = async (path, overrides = {}) => {
|
||||
const {
|
||||
isRefresh,
|
||||
panel,
|
||||
history = true,
|
||||
noCurrent,
|
||||
currentName,
|
||||
} = overrides;
|
||||
|
||||
const refresh = isRefresh;
|
||||
let panelChanged;
|
||||
if (!noCurrent)
|
||||
if (panel && panel !== Info.panel) {
|
||||
DOM.changePanel();
|
||||
panelChanged = true;
|
||||
}
|
||||
|
||||
if (!noCurrent && panel && panel !== Info.panel) {
|
||||
DOM.changePanel();
|
||||
panelChanged = true;
|
||||
}
|
||||
|
||||
let imgPosition;
|
||||
|
||||
if (panelChanged || refresh || !history)
|
||||
imgPosition = 'top';
|
||||
|
||||
Images.show.load(imgPosition, panel);
|
||||
|
||||
/* загружаем содержимое каталога */
|
||||
ajaxLoad(p.path, {
|
||||
await ajaxLoad(addSlashToEnd(path), {
|
||||
refresh,
|
||||
history,
|
||||
noCurrent,
|
||||
currentName,
|
||||
}, panel, callback);
|
||||
showDotFiles: CloudCmd.config('showDotFiles'),
|
||||
}, panel);
|
||||
};
|
||||
|
||||
/**
|
||||
* function load modules
|
||||
* @params = {name, path, func, dobefore, arg}
|
||||
*/
|
||||
function loadModule(params) {
|
||||
if (!params)
|
||||
return;
|
||||
|
||||
let path = params.path;
|
||||
const name = params.name || path && kebabToCamelCase(path);
|
||||
const func = params.func;
|
||||
const funcName = params.funcName;
|
||||
const doBefore = params.dobefore;
|
||||
|
||||
const isContain = /\.js/.test(path);
|
||||
|
||||
if (!isContain)
|
||||
path += '.js';
|
||||
|
||||
if (CloudCmd[name])
|
||||
return;
|
||||
|
||||
CloudCmd[name] = (...args) => {
|
||||
const prefix = CloudCmd.PREFIX;
|
||||
const pathFull = prefix + CloudCmd.DIRCLIENT_MODULES + path;
|
||||
|
||||
exec(doBefore);
|
||||
|
||||
const done = (error) => {
|
||||
const Proto = CloudCmd[name];
|
||||
|
||||
if (error || !itype.function(Proto))
|
||||
return;
|
||||
|
||||
CloudCmd[name] = applyConstructor(Proto, args);
|
||||
};
|
||||
|
||||
return DOM.load.js(pathFull, func || done);
|
||||
};
|
||||
|
||||
CloudCmd[name][funcName] = CloudCmd[name];
|
||||
}
|
||||
|
||||
/*
|
||||
* apply arguemnts to constructor
|
||||
*
|
||||
* @param constructor
|
||||
* @param args
|
||||
*/
|
||||
function applyConstructor(constructor, args) {
|
||||
const F = function () {
|
||||
return constructor.apply(this, args);
|
||||
};
|
||||
|
||||
F.prototype = constructor.prototype;
|
||||
return new F();
|
||||
}
|
||||
|
||||
/**
|
||||
* Конструктор CloudClient, который
|
||||
* выполняет весь функционал по
|
||||
* инициализации
|
||||
*/
|
||||
this.init = (prefix, config) => {
|
||||
const func = () => {
|
||||
exec.series([
|
||||
initModules,
|
||||
baseInit,
|
||||
loadPlugins,
|
||||
() => {
|
||||
CloudCmd.route(location.hash);
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
const funcBefore = (callback) => {
|
||||
const src = prefix + '/join:' + [
|
||||
CloudCmd.DIRCLIENT_MODULES + 'polyfill.js',
|
||||
'/modules/domtokenlist-shim/dist/domtokenlist.min.js',
|
||||
].join(':');
|
||||
|
||||
DOM.loadJquery(() => {
|
||||
DOM.load.js(src, callback);
|
||||
});
|
||||
};
|
||||
|
||||
CloudCmd.PREFIX = prefix;
|
||||
CloudCmd.PREFIX_URL = prefix + apiURL;
|
||||
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;
|
||||
};
|
||||
|
||||
DOM.Storage.setAllowed(CloudCmd.config('localStorage'));
|
||||
|
||||
if (config.onePanelMode)
|
||||
if (config.oneFilePanel)
|
||||
CloudCmd.MIN_ONE_PANEL_WIDTH = Infinity;
|
||||
|
||||
exec.if(document.body.scrollIntoViewIfNeeded, func, funcBefore);
|
||||
};
|
||||
|
||||
function loadPlugins(callback) {
|
||||
const prefix = CloudCmd.PREFIX;
|
||||
const plugins = prefix + '/plugins.js';
|
||||
if (!document.body.scrollIntoViewIfNeeded)
|
||||
await load.js(`${CloudCmd.DIR_MODULES}/polyfill.js`);
|
||||
|
||||
DOM.load.js(plugins, callback);
|
||||
}
|
||||
|
||||
this.join = (urls) => {
|
||||
const prefix = CloudCmd.PREFIX;
|
||||
await initModules();
|
||||
await baseInit();
|
||||
|
||||
if (!Array.isArray(urls))
|
||||
throw Error('urls should be array!');
|
||||
|
||||
const noPrefixUrls = urls.map((url) => {
|
||||
return url.replace(prefix, '');
|
||||
});
|
||||
|
||||
return prefix + join(noPrefixUrls);
|
||||
CloudCmd.route(location.hash);
|
||||
};
|
||||
|
||||
this.route = (path) => {
|
||||
|
|
@ -242,14 +142,15 @@ function CloudCmdProto(Util, DOM) {
|
|||
return;
|
||||
|
||||
const [kebabModule] = query;
|
||||
const module = kebabToCamelCase(kebabModule.slice(1));
|
||||
const module = noJS(pascalCase(kebabModule.slice(1)));
|
||||
|
||||
const file = query[1];
|
||||
const [, file] = query;
|
||||
const current = DOM.getCurrentByName(file);
|
||||
|
||||
if (file && !current) {
|
||||
const msg = formatMsg('set current file', file, 'error');
|
||||
CloudCmd.log(msg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -257,73 +158,71 @@ function CloudCmdProto(Util, DOM) {
|
|||
CloudCmd.execFromModule(module, 'show');
|
||||
};
|
||||
|
||||
this.logOut = () => {
|
||||
const url = CloudCmd.PREFIX + '/logout';
|
||||
this.logOut = async () => {
|
||||
const url = CloudCmd.prefix + '/logout';
|
||||
const error = () => document.location.reload();
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
DOM.load.ajax({url, error});
|
||||
await DOM.Storage.clear();
|
||||
unregisterSW(prefix);
|
||||
DOM.load.ajax({
|
||||
url,
|
||||
error,
|
||||
});
|
||||
};
|
||||
|
||||
function initModules(callback) {
|
||||
exec.if(CloudCmd.Key, () => {
|
||||
Key = new CloudCmd.Key();
|
||||
CloudCmd.Key = Key;
|
||||
Key.bind();
|
||||
}, (func) => {
|
||||
/* привязываем клавиши к функциям */
|
||||
const path = 'key.js';
|
||||
|
||||
loadModule({
|
||||
path,
|
||||
func
|
||||
});
|
||||
});
|
||||
const initModules = async () => {
|
||||
CloudCmd.Key = Key;
|
||||
CloudCmd.Key.bind();
|
||||
|
||||
Files.get('modules', (error, modules) => {
|
||||
const showLoad = Images.show.load;
|
||||
|
||||
const doBefore = {
|
||||
'edit': showLoad,
|
||||
'menu': showLoad,
|
||||
};
|
||||
|
||||
const load = (name, path, dobefore) => {
|
||||
const isTmpl = path === 'template';
|
||||
const funcName = isTmpl ? 'get' : 'show';
|
||||
|
||||
loadModule({
|
||||
name,
|
||||
path,
|
||||
dobefore,
|
||||
funcName,
|
||||
});
|
||||
};
|
||||
|
||||
if (!modules)
|
||||
modules = [];
|
||||
|
||||
modules.local.forEach((module) => {
|
||||
load(null, module, doBefore[module]);
|
||||
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,
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
if (!modules)
|
||||
return;
|
||||
|
||||
for (const module of modules.local) {
|
||||
load(null, module, doBefore[module]);
|
||||
}
|
||||
};
|
||||
|
||||
async function saveCurrentName(currentName) {
|
||||
await Storage.set('current-name', currentName);
|
||||
}
|
||||
|
||||
function baseInit(callback) {
|
||||
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(files[0], {
|
||||
DOM.setCurrentFile(currentFile, {
|
||||
// when hash is present
|
||||
// it should be handled with this.route
|
||||
// overwre otherwise
|
||||
history: !location.hash
|
||||
history: !location.hash,
|
||||
});
|
||||
|
||||
const dirPath = DOM.getCurrentDirPath();
|
||||
Listeners = CloudCmd.Listeners;
|
||||
|
||||
Listeners.init();
|
||||
|
||||
const panels = getPanels();
|
||||
|
|
@ -332,21 +231,18 @@ function CloudCmdProto(Util, DOM) {
|
|||
Listeners.initKeysPanel();
|
||||
|
||||
if (!CloudCmd.config('dirStorage'))
|
||||
return callback();
|
||||
return;
|
||||
|
||||
Storage.get(dirPath, (error, data) => {
|
||||
if (!data) {
|
||||
data = getJSONfromFileTable();
|
||||
Storage.set(dirPath, data);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
const data = await Storage.get(dirPath);
|
||||
|
||||
if (!data)
|
||||
await Storage.setJson(dirPath, getJsonFromFileTable());
|
||||
}
|
||||
|
||||
function getPanels() {
|
||||
const panels = ['left'];
|
||||
|
||||
if (CloudCmd.config('onePanelMode'))
|
||||
if (CloudCmd.config('oneFilePanel'))
|
||||
return panels;
|
||||
|
||||
return [
|
||||
|
|
@ -355,40 +251,29 @@ function CloudCmdProto(Util, DOM) {
|
|||
];
|
||||
}
|
||||
|
||||
this.execFromModule = (moduleName, funcName, ...args) => {
|
||||
const obj = CloudCmd[moduleName];
|
||||
const isObj = itype.object(obj);
|
||||
this.execFromModule = async (moduleName, funcName, ...args) => {
|
||||
await CloudCmd[moduleName]();
|
||||
|
||||
exec.if(isObj, () => {
|
||||
const obj = CloudCmd[moduleName];
|
||||
const func = obj[funcName];
|
||||
|
||||
func(...args);
|
||||
}, obj);
|
||||
const func = CloudCmd[moduleName][funcName];
|
||||
func(...args);
|
||||
};
|
||||
|
||||
this.refresh = (options = {}, callback) => {
|
||||
if (!callback && typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
const panel = options.panel || Info.panel;
|
||||
this.refresh = async (options = {}) => {
|
||||
const {panel = Info.panel, currentName} = options;
|
||||
|
||||
const path = DOM.getCurrentDirPath(panel);
|
||||
|
||||
const isRefresh = true;
|
||||
const history = false;
|
||||
const noCurrent = options ? options.noCurrent : false;
|
||||
const currentName = options.currentName;
|
||||
const noCurrent = options?.noCurrent;
|
||||
|
||||
CloudCmd.loadDir({
|
||||
path,
|
||||
await CloudCmd.changeDir(path, {
|
||||
isRefresh,
|
||||
history,
|
||||
panel,
|
||||
noCurrent,
|
||||
currentName,
|
||||
}, callback);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -398,190 +283,146 @@ function CloudCmdProto(Util, DOM) {
|
|||
* @param options
|
||||
* { refresh, history } - необходимость обновить данные о каталоге
|
||||
* @param panel
|
||||
* @param callback
|
||||
*
|
||||
*/
|
||||
function ajaxLoad(path, options, panel, callback) {
|
||||
const create = (error, json) => {
|
||||
const RESTful = DOM.RESTful;
|
||||
const name = options.currentName || Info.name;
|
||||
const obj = jonny.parse(json);
|
||||
const isRefresh = options.refresh;
|
||||
const noCurrent = options.noCurrent;
|
||||
|
||||
if (!isRefresh && json)
|
||||
return createFileTable(obj, panel, options, callback);
|
||||
|
||||
const position = DOM.getPanelPosition(panel);
|
||||
const sort = CloudCmd.sort[position];
|
||||
const order = CloudCmd.order[position];
|
||||
|
||||
const query = rendy('?sort={{ sort }}&order={{ order }}', {
|
||||
sort,
|
||||
order,
|
||||
});
|
||||
|
||||
RESTful.read(path + query, 'json', (error, obj) => {
|
||||
if (error)
|
||||
return;
|
||||
|
||||
options.sort = sort;
|
||||
options.order = order;
|
||||
|
||||
createFileTable(obj, panel, options, () => {
|
||||
if (isRefresh && !noCurrent)
|
||||
DOM.setCurrentByName(name);
|
||||
|
||||
exec(callback);
|
||||
});
|
||||
|
||||
if (!CloudCmd.config('dirStorage'))
|
||||
return;
|
||||
|
||||
Storage.set(path, obj);
|
||||
});
|
||||
};
|
||||
async function ajaxLoad(path, options = {}, panel) {
|
||||
const {RESTful} = DOM;
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
CloudCmd.log(`reading dir: "${path}";`);
|
||||
|
||||
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 create();
|
||||
return;
|
||||
|
||||
Storage.get(path, create);
|
||||
Storage.setJson(path, newObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция строит файловую таблицу
|
||||
* @param json - данные о файлах
|
||||
* @param data - данные о файлах
|
||||
* @param panelParam
|
||||
* @param history
|
||||
* @param callback
|
||||
* @param options - history, noCurrent, showDotFiles
|
||||
*/
|
||||
function createFileTable(json, panelParam, options, callback) {
|
||||
async function createFileTable(data, panelParam, options) {
|
||||
const {
|
||||
history,
|
||||
noCurrent,
|
||||
showDotFiles,
|
||||
} = options;
|
||||
|
||||
const names = ['file', 'path', 'link', 'pathLink'];
|
||||
const names = [
|
||||
'file',
|
||||
'path',
|
||||
'link',
|
||||
'pathLink',
|
||||
];
|
||||
|
||||
Files.get(names, (error, templFile, templPath, templLink, templPathLink) => {
|
||||
const Dialog = DOM.Dialog;
|
||||
const panel = panelParam || DOM.getPanel();
|
||||
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;
|
||||
|
||||
const {
|
||||
dir,
|
||||
name,
|
||||
} = Info;
|
||||
if (name === '..' && dir !== '/')
|
||||
current = DOM.getCurrentByName(dir);
|
||||
|
||||
if (error)
|
||||
return Dialog.alert(TITLE, error.responseText);
|
||||
if (!current)
|
||||
[current] = DOM.getFiles(panel);
|
||||
|
||||
const childNodes = panel.childNodes;
|
||||
let i = childNodes.length;
|
||||
|
||||
while (i--)
|
||||
panel.removeChild(panel.lastChild);
|
||||
|
||||
panel.innerHTML = buildFromJSON({
|
||||
sort : options.sort,
|
||||
order : options.order,
|
||||
data : json,
|
||||
id : panel.id,
|
||||
prefix : CloudCmd.PREFIX,
|
||||
template : {
|
||||
file : templFile,
|
||||
path : templPath,
|
||||
pathLink : templPathLink,
|
||||
link : templLink
|
||||
}
|
||||
DOM.setCurrentFile(current, {
|
||||
history,
|
||||
});
|
||||
|
||||
Listeners.setOnPanel(panel);
|
||||
|
||||
if (!noCurrent) {
|
||||
let current;
|
||||
|
||||
if (name === '..' && dir !== '/')
|
||||
current = DOM.getCurrentByName(dir);
|
||||
|
||||
if (!current)
|
||||
current = DOM.getFiles(panel)[0];
|
||||
|
||||
DOM.setCurrentFile(current, {
|
||||
history: history
|
||||
});
|
||||
}
|
||||
|
||||
exec(callback);
|
||||
});
|
||||
CloudCmd.emit('active-dir', Info.dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция генерирует JSON из html-таблицы файлов и
|
||||
* используеться при первом заходе в корень
|
||||
*/
|
||||
function getJSONfromFileTable() {
|
||||
const path = DOM.getCurrentDirPath();
|
||||
const infoFiles = Info.files || [];
|
||||
|
||||
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);
|
||||
|
||||
return {
|
||||
name,
|
||||
size,
|
||||
mode,
|
||||
owner,
|
||||
date,
|
||||
};
|
||||
};
|
||||
|
||||
const files = infoFiles
|
||||
.filter(notParent)
|
||||
.map(parse);
|
||||
|
||||
const fileTable = {
|
||||
path,
|
||||
files,
|
||||
};
|
||||
|
||||
return fileTable;
|
||||
}
|
||||
|
||||
this.goToParentDir = () => {
|
||||
const dir = Info.dir;
|
||||
this.goToParentDir = async () => {
|
||||
const {
|
||||
dir,
|
||||
dirPath,
|
||||
parentDirPath,
|
||||
panel,
|
||||
} = Info;
|
||||
|
||||
if (dirPath === parentDirPath)
|
||||
return;
|
||||
|
||||
const path = Info.dirPath || parentDirPath;
|
||||
const path = parentDirPath;
|
||||
|
||||
CloudCmd.loadDir({path}, () => {
|
||||
const panel = Info.panel;
|
||||
const current = DOM.getCurrentByName(dir);
|
||||
const first = DOM.getFiles(panel)[0];
|
||||
|
||||
DOM.setCurrentFile(current || first, {
|
||||
history
|
||||
});
|
||||
await CloudCmd.changeDir(path);
|
||||
|
||||
const current = DOM.getCurrentByName(dir);
|
||||
const [first] = DOM.getFiles(panel);
|
||||
|
||||
DOM.setCurrentFile(current || first, {
|
||||
history,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,47 +1,74 @@
|
|||
'use strict';
|
||||
import '../css/main.css';
|
||||
import process from 'node:process';
|
||||
import wraptile from 'wraptile';
|
||||
import load from 'load.js';
|
||||
import * as Util from '#common/util';
|
||||
import * as CloudFunc from '#common/cloudfunc';
|
||||
import DOM from '#dom';
|
||||
import {registerSW, listenSW} from './sw/register.js';
|
||||
import {initSortPanel, sortPanel} from './sort.js';
|
||||
import {createCloudCmd} from './client.js';
|
||||
import * as Listeners from './listeners/index.js';
|
||||
|
||||
require('../css/main.css');
|
||||
require('../css/nojs.css');
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
// prevent additional loading of exec by spero, remedy, ishtar, salam, omnes
|
||||
window.exec = require('execon');
|
||||
export default init;
|
||||
|
||||
// prevent additional loading of emitify
|
||||
window.Emitify = require('emitify/legacy');
|
||||
globalThis.CloudCmd = init;
|
||||
|
||||
window.CloudCmd = (config) => {
|
||||
window.Promise = window.Promise || require('es6-promise');
|
||||
window.Util = require('../common/util');
|
||||
window.CloudFunc = require('../common/cloudfunc');
|
||||
|
||||
const DOM = require('./dom');
|
||||
|
||||
window.DOM = DOM;
|
||||
window.CloudCmd = require('./client');
|
||||
|
||||
const Dialog = require('./dom/dialog');
|
||||
|
||||
const prefix = getPrefix(config.prefix);
|
||||
const {htmlDialogs} = config;
|
||||
|
||||
DOM.Dialog = Dialog(prefix, {
|
||||
htmlDialogs
|
||||
async function init(config) {
|
||||
globalThis.CloudCmd = createCloudCmd({
|
||||
DOM,
|
||||
Listeners,
|
||||
});
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.Util = Util;
|
||||
globalThis.CloudFunc = CloudFunc;
|
||||
|
||||
require('./listeners');
|
||||
require('./key');
|
||||
require('./sort');
|
||||
await register(config);
|
||||
|
||||
window.CloudCmd.init(prefix, 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@5.5');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function getPrefix(prefix) {
|
||||
if (!prefix)
|
||||
return '';
|
||||
|
||||
|
||||
if (!prefix.indexOf('/'))
|
||||
return prefix;
|
||||
|
||||
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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,133 +1,124 @@
|
|||
'use strict';
|
||||
/* global CloudCmd*/
|
||||
import * as Storage from '#dom/storage';
|
||||
import tryToPromiseAll from '../../common/try-to-promise-all.js';
|
||||
|
||||
/* global CloudCmd */
|
||||
const CLASS = 'cut-file';
|
||||
const COPY = 'copy';
|
||||
const CUT = 'cut';
|
||||
|
||||
const jonny = require('jonny');
|
||||
const exec = require('execon');
|
||||
|
||||
const Storage = require('./storage');
|
||||
const DOM = require('./');
|
||||
|
||||
module.exports = new BufferProto();
|
||||
|
||||
function BufferProto() {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const CLASS = 'cut-file';
|
||||
const COPY = 'copy';
|
||||
const CUT = 'cut';
|
||||
const TITLE = 'Buffer';
|
||||
|
||||
const Buffer = {
|
||||
cut : callIfEnabled.bind(null, cut),
|
||||
copy : callIfEnabled.bind(null, copy),
|
||||
clear : callIfEnabled.bind(null, clear),
|
||||
paste : callIfEnabled.bind(null, paste)
|
||||
};
|
||||
|
||||
function showMessage(msg) {
|
||||
DOM.Dialog.alert(TITLE, msg);
|
||||
}
|
||||
|
||||
function getNames() {
|
||||
const files = DOM.getActiveFiles();
|
||||
const names = DOM.getFilenames(files);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
function addCutClass() {
|
||||
const files = DOM.getActiveFiles();
|
||||
|
||||
files.forEach((element) => {
|
||||
element.classList.add(CLASS);
|
||||
});
|
||||
}
|
||||
|
||||
function rmCutClass() {
|
||||
const files = DOM.getByClassAll(CLASS);
|
||||
|
||||
[...files].forEach((element) => {
|
||||
element.classList.remove(CLASS);
|
||||
});
|
||||
}
|
||||
|
||||
function callIfEnabled(callback) {
|
||||
const is = CloudCmd.config('buffer');
|
||||
|
||||
if (is)
|
||||
return callback();
|
||||
|
||||
showMessage('Buffer disabled in config!');
|
||||
}
|
||||
|
||||
function copy() {
|
||||
const names = getNames();
|
||||
const from = Info.dirPath;
|
||||
|
||||
clear();
|
||||
|
||||
if (!names.length)
|
||||
return;
|
||||
|
||||
Storage.remove(CUT)
|
||||
.set(COPY, {
|
||||
from : from,
|
||||
names: names
|
||||
});
|
||||
}
|
||||
|
||||
function cut() {
|
||||
const names = getNames();
|
||||
const from = Info.dirPath;
|
||||
|
||||
clear();
|
||||
|
||||
if (!names.length)
|
||||
return;
|
||||
|
||||
addCutClass();
|
||||
|
||||
Storage
|
||||
.set(CUT, {
|
||||
from : from,
|
||||
names: names
|
||||
});
|
||||
}
|
||||
|
||||
function clear() {
|
||||
Storage.remove(COPY)
|
||||
.remove(CUT);
|
||||
|
||||
rmCutClass();
|
||||
}
|
||||
|
||||
function paste() {
|
||||
const copy = Storage.get.bind(Storage, COPY);
|
||||
const cut = Storage.get.bind(Storage, CUT);
|
||||
|
||||
exec.parallel([copy, cut], function(error, cp, ct) {
|
||||
const opStr = cp ? 'copy' : 'move';
|
||||
const opData = cp || ct;
|
||||
const Operation = CloudCmd.Operation;
|
||||
const msg = 'Path is same!';
|
||||
const path = Info.dirPath;
|
||||
|
||||
if (!error && !cp && !ct)
|
||||
error = 'Buffer is empty!';
|
||||
|
||||
if (error)
|
||||
return showMessage(error);
|
||||
|
||||
const data = jonny.parse(opData);
|
||||
data.to = path;
|
||||
|
||||
if (data.from === path)
|
||||
return showMessage(msg);
|
||||
|
||||
Operation.show(opStr, data);
|
||||
clear();
|
||||
});
|
||||
}
|
||||
|
||||
return Buffer;
|
||||
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();
|
||||
});
|
||||
|
|
|
|||
83
client/dom/cmd.js
Normal file
83
client/dom/cmd.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/* global DOM */
|
||||
const SELECTED_FILE = 'selected-file';
|
||||
const Cmd = {
|
||||
getSelectedFiles,
|
||||
isSelected,
|
||||
unselectFile,
|
||||
selectFile,
|
||||
selectAllFiles,
|
||||
toggleSelectedFile,
|
||||
toggleAllSelectedFiles,
|
||||
};
|
||||
|
||||
/**
|
||||
* selected file check
|
||||
*
|
||||
* @param currentFile
|
||||
*/
|
||||
export function isSelected(currentFile) {
|
||||
if (!currentFile)
|
||||
return false;
|
||||
|
||||
return DOM.isContainClass(currentFile, SELECTED_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* select current file
|
||||
* @param currentFile
|
||||
*/
|
||||
export function selectFile(currentFile) {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
|
||||
current.classList.add(SELECTED_FILE);
|
||||
|
||||
return Cmd;
|
||||
}
|
||||
|
||||
export function unselectFile(currentFile) {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
|
||||
current.classList.remove(SELECTED_FILE);
|
||||
|
||||
return Cmd;
|
||||
}
|
||||
|
||||
export function toggleSelectedFile(currentFile) {
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const name = DOM.getCurrentName(current);
|
||||
|
||||
if (name === '..')
|
||||
return Cmd;
|
||||
|
||||
current.classList.toggle(SELECTED_FILE);
|
||||
|
||||
return Cmd;
|
||||
}
|
||||
|
||||
export function toggleAllSelectedFiles() {
|
||||
DOM
|
||||
.getAllFiles()
|
||||
.map(DOM.toggleSelectedFile);
|
||||
|
||||
return Cmd;
|
||||
}
|
||||
|
||||
export function selectAllFiles() {
|
||||
DOM
|
||||
.getAllFiles()
|
||||
.map(DOM.selectFile);
|
||||
|
||||
return Cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* unified way to get selected files
|
||||
*
|
||||
* @currentFile
|
||||
*/
|
||||
export function getSelectedFiles() {
|
||||
const panel = DOM.getPanel();
|
||||
const selected = DOM.getByClassAll(SELECTED_FILE, panel);
|
||||
|
||||
return Array.from(selected);
|
||||
}
|
||||
311
client/dom/current-file.js
Normal file
311
client/dom/current-file.js
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
/* global DOM */
|
||||
/* global CloudCmd */
|
||||
import createElement from '@cloudcmd/create-element';
|
||||
import {getTitle, FS} from '#common/cloudfunc';
|
||||
import {encode, decode} from '#common/entity';
|
||||
|
||||
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 directory 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 directory name
|
||||
*/
|
||||
export const getCurrentDirName = () => {
|
||||
const href = DOM
|
||||
.getCurrentDirPath()
|
||||
.replace(/\/$/, '');
|
||||
|
||||
const substr = href.substr(href, href.lastIndexOf('/'));
|
||||
|
||||
return href.replace(`${substr}/`, '') || '/';
|
||||
};
|
||||
|
||||
/**
|
||||
* get current directory 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 directory 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 that 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;
|
||||
};
|
||||
308
client/dom/current-file.spec.js
Normal file
308
client/dom/current-file.spec.js
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import {create} from 'auto-globals';
|
||||
import wraptile from 'wraptile';
|
||||
import * as currentFile from './current-file.js';
|
||||
|
||||
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);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(setAttribute, [
|
||||
'data-name',
|
||||
'js-file-aGVsbG8=',
|
||||
], 'should call setAttribute');
|
||||
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);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(setAttribute, [
|
||||
'data-name',
|
||||
'js-file-JUQwJUIwJUQwJUI5',
|
||||
], 'should call setAttribute');
|
||||
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);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(emit, ['current-file', current], 'should call emit');
|
||||
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);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.equal(result, link, 'should return link');
|
||||
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: '/',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,56 +1,18 @@
|
|||
/* global DOM */
|
||||
/* global smalltalk */
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import * as smalltalk from 'smalltalk';
|
||||
|
||||
'use strict';
|
||||
const title = 'Cloud Commander';
|
||||
|
||||
module.exports = Dialog;
|
||||
export const alert = (...a) => smalltalk.alert(title, ...a, {
|
||||
cancel: false,
|
||||
});
|
||||
|
||||
function Dialog(prefix, config) {
|
||||
if (!(this instanceof Dialog))
|
||||
return new Dialog(prefix, config);
|
||||
|
||||
load(config.htmlDialogs);
|
||||
|
||||
function getJsName(htmlDialogs) {
|
||||
const is = window.Promise;
|
||||
const js = '.min.js';
|
||||
const jsName = is ? js : '.poly' + js;
|
||||
|
||||
if (!htmlDialogs)
|
||||
return '.native' + jsName;
|
||||
|
||||
return jsName;
|
||||
}
|
||||
|
||||
function load(htmlDialogs) {
|
||||
const noop = () => {};
|
||||
const name = 'smalltalk';
|
||||
const dir = '/modules/' + name + '/dist/';
|
||||
const jsName = getJsName(htmlDialogs);
|
||||
|
||||
const names = [jsName, '.min.css'].map((ext) => {
|
||||
return prefix + dir + name + ext;
|
||||
});
|
||||
|
||||
DOM.load.parallel(names, noop);
|
||||
}
|
||||
|
||||
const alert = (title, message) => {
|
||||
return smalltalk.alert(title, message);
|
||||
};
|
||||
|
||||
this.alert = alert;
|
||||
|
||||
this.prompt = (title, message, value, options) => {
|
||||
return smalltalk.prompt(title, message, value, options);
|
||||
};
|
||||
|
||||
this.confirm = (title, message, options) => {
|
||||
return smalltalk.confirm(title, message, options);
|
||||
};
|
||||
|
||||
this.alert.noFiles = (title) => {
|
||||
return alert(title, 'No files selected!');
|
||||
};
|
||||
}
|
||||
export const prompt = (...a) => tryToCatch(smalltalk.prompt, title, ...a);
|
||||
export const confirm = (...a) => tryToCatch(smalltalk.confirm, title, ...a);
|
||||
export const progress = (...a) => smalltalk.progress(title, ...a);
|
||||
|
||||
alert.noFiles = () => {
|
||||
return smalltalk.alert(title, 'No files selected!', {
|
||||
cancel: false,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,90 +1,64 @@
|
|||
/* global CloudCmd */
|
||||
/* global DOM */
|
||||
/* global DOM, CloudCmd */
|
||||
import philip from 'philip';
|
||||
import * as Dialog from '#dom/dialog';
|
||||
import {FS} from '#common/cloudfunc';
|
||||
import * as Images from '#dom/images';
|
||||
|
||||
'use strict';
|
||||
|
||||
const Images = require('./images');
|
||||
const {FS} = require('../../common/cloudfunc');
|
||||
|
||||
module.exports = (items) => {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const load = DOM.load;
|
||||
const Dialog = DOM.Dialog;
|
||||
|
||||
export const uploadDirectory = (items) => {
|
||||
if (items.length)
|
||||
Images.show('top');
|
||||
|
||||
const entries = [...items].map((item) => {
|
||||
return item.webkitGetAsEntry();
|
||||
const entries = Array
|
||||
.from(items)
|
||||
.map((item) => item.webkitGetAsEntry());
|
||||
|
||||
const dirPath = DOM.getCurrentDirPath();
|
||||
const path = dirPath.replace(/\/$/, '');
|
||||
|
||||
const progress = Dialog.progress('Uploading...');
|
||||
|
||||
progress.catch(() => {
|
||||
Dialog.alert('Upload aborted');
|
||||
uploader.abort();
|
||||
});
|
||||
|
||||
const addDir = (name) => {
|
||||
return `/modules/${name}/lib/${name}.js`;
|
||||
};
|
||||
|
||||
const array = [
|
||||
'findit',
|
||||
'philip'
|
||||
];
|
||||
|
||||
const url = CloudCmd.join(array.map(addDir));
|
||||
|
||||
load.js(url, () => {
|
||||
const path = Info.dirPath
|
||||
.replace(/\/$/, '');
|
||||
const uploader = philip(entries, (type, name, data, i, n, callback) => {
|
||||
const {prefixURL} = CloudCmd;
|
||||
const full = prefixURL + FS + path + name;
|
||||
|
||||
const uploader = window.philip(entries, (type, name, data, i, n, callback) => {
|
||||
const prefixURL = CloudCmd.PREFIX_URL;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
setProgress(value);
|
||||
});
|
||||
progress.setProgress(value);
|
||||
});
|
||||
|
||||
uploader.on('error', (error) => {
|
||||
Dialog.alert(error);
|
||||
uploader.abort();
|
||||
});
|
||||
|
||||
uploader.on('progress', setProgress);
|
||||
uploader.on('end', CloudCmd.refresh);
|
||||
});
|
||||
|
||||
uploader.on('error', (error) => {
|
||||
Dialog.alert(error);
|
||||
uploader.abort();
|
||||
});
|
||||
|
||||
uploader.on('end', CloudCmd.refresh);
|
||||
};
|
||||
|
||||
function percent(i, n, per = 100) {
|
||||
return Math.round(i * per / n);
|
||||
}
|
||||
const percent = (i, n, per = 100) => Math.round(i * per / n);
|
||||
|
||||
function setProgress(count) {
|
||||
DOM.Images
|
||||
.setProgress(count)
|
||||
.show('top');
|
||||
}
|
||||
|
||||
function uploadFile(url, data) {
|
||||
return DOM.load.put(url, data);
|
||||
}
|
||||
|
||||
function uploadDir(url) {
|
||||
return DOM.load.put(url + '?dir');
|
||||
}
|
||||
const uploadFile = (url, data) => DOM.load.put(url, data);
|
||||
|
||||
const uploadDir = (url) => DOM.load.put(`${url}?dir`);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
'use strict';
|
||||
import currify from 'currify';
|
||||
|
||||
const DOM = module.exports;
|
||||
const DOM = {
|
||||
show,
|
||||
hide,
|
||||
getByClass,
|
||||
getByClassAll,
|
||||
getByDataName,
|
||||
getById,
|
||||
getByTag,
|
||||
isContainClass,
|
||||
};
|
||||
|
||||
/**
|
||||
* check class of element
|
||||
|
|
@ -8,71 +17,76 @@ const DOM = module.exports;
|
|||
* @param element
|
||||
* @param className
|
||||
*/
|
||||
module.exports.isContainClass = (element, className) => {
|
||||
export function isContainClass(element, className) {
|
||||
if (!element)
|
||||
throw Error('element could not be empty!');
|
||||
|
||||
if (!className)
|
||||
throw Error('className could not be empty!');
|
||||
|
||||
const classList = element.classList;
|
||||
const ret = classList.contains(className);
|
||||
if (Array.isArray(className))
|
||||
return className.some(currify(
|
||||
isContainClass,
|
||||
element,
|
||||
));
|
||||
|
||||
return ret;
|
||||
};
|
||||
const {classList} = element;
|
||||
|
||||
return classList.contains(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function search element by tag
|
||||
* @param tag - className
|
||||
* @param element - element
|
||||
*/
|
||||
module.exports.getByTag = (tag, element = document) => {
|
||||
export function getByTag(tag, element = document) {
|
||||
return element.getElementsByTagName(tag);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Function search element by id
|
||||
* @param Id - id
|
||||
* @param id
|
||||
* @param element
|
||||
*/
|
||||
module.exports.getById = (id, element = document) => {
|
||||
return element.querySelector('#' + id);
|
||||
};
|
||||
export function 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) => {
|
||||
export function getByClass(className, element = document) {
|
||||
return DOM.getByClassAll(className, element)[0];
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.getByDataName = (attribute, element = document) => {
|
||||
const selector = '[' + 'data-name="' + attribute + '"]';
|
||||
export function 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
|
||||
* @param className
|
||||
* @param element
|
||||
*/
|
||||
module.exports.getByClassAll = (className, element) => {
|
||||
export function getByClassAll(className, element) {
|
||||
return (element || document).getElementsByClassName(className);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* add class=hidden to element
|
||||
*
|
||||
* @param element
|
||||
*/
|
||||
module.exports.hide = (element) => {
|
||||
export function hide(element) {
|
||||
element.classList.add('hidden');
|
||||
return DOM;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.show = (element) => {
|
||||
export function show(element) {
|
||||
element.classList.remove('hidden');
|
||||
return DOM;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
152
client/dom/dom-tree.spec.js
Normal file
152
client/dom/dom-tree.spec.js
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import {create} from 'auto-globals';
|
||||
import {tryCatch} from 'try-catch';
|
||||
import {
|
||||
isContainClass,
|
||||
getByTag,
|
||||
getById,
|
||||
getByClass,
|
||||
getByDataName,
|
||||
getByClassAll,
|
||||
hide,
|
||||
show,
|
||||
} from './dom-tree.js';
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
test('dom: getByTag', (t) => {
|
||||
const getElementsByTagName = stub();
|
||||
const element = {
|
||||
getElementsByTagName,
|
||||
};
|
||||
|
||||
getByTag('div', element);
|
||||
|
||||
t.calledWith(getElementsByTagName, ['div'], 'should call getElementsByTagName');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: getById', (t) => {
|
||||
const querySelector = stub();
|
||||
const element = {
|
||||
querySelector,
|
||||
};
|
||||
|
||||
getById('my-id', element);
|
||||
|
||||
t.calledWith(querySelector, ['#my-id'], 'should call querySelector with id selector');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: getByClassAll', (t) => {
|
||||
const getElementsByClassName = stub();
|
||||
const element = {
|
||||
getElementsByClassName,
|
||||
};
|
||||
|
||||
getByClassAll('my-class', element);
|
||||
|
||||
t.calledWith(getElementsByClassName, ['my-class'], 'should call getElementsByClassName');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: getByClass: calls getByClassAll', (t) => {
|
||||
const element = {
|
||||
getElementsByClassName: stub().returns(['first']),
|
||||
};
|
||||
|
||||
const result = getByClass('my-class', element);
|
||||
|
||||
t.equal(result, 'first', 'should return first element from class list');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: getByDataName', (t) => {
|
||||
const querySelector = stub();
|
||||
const element = {
|
||||
querySelector,
|
||||
};
|
||||
|
||||
getByDataName('hello', element);
|
||||
|
||||
t.calledWith(querySelector, ['[data-name="hello"]'], 'should call querySelector with data-name selector');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: hide', (t) => {
|
||||
const add = stub();
|
||||
const element = {
|
||||
classList: {
|
||||
add,
|
||||
},
|
||||
};
|
||||
|
||||
hide(element);
|
||||
|
||||
t.calledWith(add, ['hidden'], 'should add hidden class');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: show', (t) => {
|
||||
const remove = stub();
|
||||
const element = {
|
||||
classList: {
|
||||
remove,
|
||||
},
|
||||
};
|
||||
|
||||
show(element);
|
||||
|
||||
t.calledWith(remove, ['hidden'], 'should remove hidden class');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('dom: getByClassAll: without element uses document', (t) => {
|
||||
const getElementsByClassName = stub();
|
||||
const prevDocument = globalThis.document;
|
||||
|
||||
globalThis.document = {
|
||||
getElementsByClassName,
|
||||
};
|
||||
|
||||
getByClassAll('my-class');
|
||||
|
||||
globalThis.document = prevDocument;
|
||||
|
||||
t.calledWith(getElementsByClassName, ['my-class'], 'should fallback to document when no element');
|
||||
t.end();
|
||||
});
|
||||
|
|
@ -1,309 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const itype = require('itype/legacy');
|
||||
|
||||
module.exports = new EventsProto();
|
||||
|
||||
function EventsProto() {
|
||||
const Events = this;
|
||||
|
||||
function parseArgs(eventName, element, listener, callback) {
|
||||
let isFunc;
|
||||
|
||||
const EVENT_NAME = 1;
|
||||
const ELEMENT = 0;
|
||||
const type = itype(eventName);
|
||||
|
||||
switch(type) {
|
||||
default:
|
||||
if (!/element$/.test(type))
|
||||
throw Error('unknown eventName: ' + type);
|
||||
|
||||
parseArgs(
|
||||
arguments[EVENT_NAME],
|
||||
arguments[ELEMENT],
|
||||
listener,
|
||||
callback
|
||||
);
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
isFunc = itype.function(element);
|
||||
|
||||
if (isFunc) {
|
||||
listener = element;
|
||||
element = null;
|
||||
}
|
||||
|
||||
if (!element)
|
||||
element = window;
|
||||
|
||||
callback(element, [
|
||||
eventName,
|
||||
listener,
|
||||
false
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
eventName.forEach((eventName) => {
|
||||
parseArgs(
|
||||
eventName,
|
||||
element,
|
||||
listener,
|
||||
callback
|
||||
);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
Object.keys(eventName).forEach((name) => {
|
||||
const eventListener = eventName[name];
|
||||
|
||||
parseArgs(
|
||||
name,
|
||||
element,
|
||||
eventListener,
|
||||
callback
|
||||
);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* safe add event listener
|
||||
*
|
||||
* @param type
|
||||
* @param element {document by default}
|
||||
* @param listener
|
||||
*/
|
||||
this.add = (type, element, listener) => {
|
||||
checkType(type);
|
||||
|
||||
parseArgs(type, element, listener, function(element, args) {
|
||||
element.addEventListener.apply(element, args);
|
||||
});
|
||||
|
||||
return Events;
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add event listener
|
||||
*
|
||||
* @param type
|
||||
* @param listener
|
||||
* @param element {document by default}
|
||||
*/
|
||||
this.addOnce = (type, element, listener) => {
|
||||
var once = (event) => {
|
||||
Events.remove(type, element, once);
|
||||
listener(event);
|
||||
};
|
||||
|
||||
if (!listener) {
|
||||
listener = element;
|
||||
element = null;
|
||||
}
|
||||
|
||||
this.add(type, element, once);
|
||||
|
||||
return Events;
|
||||
};
|
||||
|
||||
/**
|
||||
* safe remove event listener
|
||||
*
|
||||
* @param type
|
||||
* @param listener
|
||||
* @param element {document by default}
|
||||
*/
|
||||
this.remove = (type, element, listener) => {
|
||||
checkType(type);
|
||||
|
||||
parseArgs(type, element, listener, (element, args) => {
|
||||
element.removeEventListener.apply(element, args);
|
||||
});
|
||||
|
||||
return Events;
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add event keydown listener
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
this.addKey = function(...argsArr) {
|
||||
const name = 'keydown';
|
||||
const args = [name].concat(argsArr);
|
||||
|
||||
return this.add(...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe remove event click listener
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
this.rmKey = function(...argsArr) {
|
||||
const name = 'keydown';
|
||||
const args = [name].concat(argsArr);
|
||||
|
||||
return this.remove(...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add event click listener
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
this.addClick = function(...argsArr) {
|
||||
const name = 'click';
|
||||
const args = [name].concat(argsArr);
|
||||
|
||||
return this.add(...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe remove event click listener
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
this.rmClick = function(...argsArr) {
|
||||
const name = 'click';
|
||||
const args = [name].concat(argsArr);
|
||||
|
||||
return this.remove(...args);
|
||||
};
|
||||
|
||||
this.addContextMenu = function(...argsArr) {
|
||||
const name = 'contextmenu';
|
||||
const args = [name].concat(argsArr);
|
||||
|
||||
return this.add(...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add event click listener
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
this.addError = function(...argsArr) {
|
||||
const name = 'error';
|
||||
const args = [name].concat(argsArr);
|
||||
|
||||
return this.add(...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* safe add load click listener
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
this.addLoad = function(...argsArr) {
|
||||
const name = 'load';
|
||||
const args = [name].concat(argsArr);
|
||||
|
||||
return this.add(...args);
|
||||
};
|
||||
|
||||
/**
|
||||
* crossbrowser create event
|
||||
*
|
||||
* @param eventName
|
||||
* @param keyCode - not necessarily
|
||||
*/
|
||||
this.create = function(eventName, keyCode) {
|
||||
const event = document.createEvent('Event');
|
||||
|
||||
event.initEvent(eventName, true, true);
|
||||
|
||||
if (keyCode)
|
||||
event.keyCode = keyCode;
|
||||
|
||||
event.isDefaultPrevented = function() {
|
||||
return this.defaultPrevented;
|
||||
};
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
/**
|
||||
* create keydown event
|
||||
*
|
||||
* @param keyCode
|
||||
*/
|
||||
this.createKey = function(keyCode) {
|
||||
return this.create('keydown', keyCode);
|
||||
};
|
||||
|
||||
/**
|
||||
* create click event
|
||||
*/
|
||||
this.createClick = function() {
|
||||
return this.create('click');
|
||||
};
|
||||
|
||||
/**
|
||||
* create click event
|
||||
*/
|
||||
this.createDblClick = function() {
|
||||
return this.create('dblclick');
|
||||
};
|
||||
|
||||
/**
|
||||
* dispatch event
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
this.dispatch = (event, element) => {
|
||||
let customEvent;
|
||||
const isStr = itype.string(event);
|
||||
|
||||
if (isStr)
|
||||
customEvent = Events.create(event);
|
||||
else
|
||||
customEvent = event;
|
||||
|
||||
return (element || window).dispatchEvent(customEvent);
|
||||
};
|
||||
|
||||
/**
|
||||
* dispatch keydown event
|
||||
*
|
||||
* @param keyCode
|
||||
* @param element
|
||||
*/
|
||||
this.dispatchKey = function(keyCode, element) {
|
||||
const event = this.createKey(keyCode);
|
||||
return this.dispatch(event, element);
|
||||
};
|
||||
|
||||
/**
|
||||
* dispatch click event
|
||||
*
|
||||
* @param element
|
||||
*/
|
||||
this.dispatchClick = function(element) {
|
||||
const event = this.createClick();
|
||||
return this.dispatch(event, element);
|
||||
};
|
||||
|
||||
/**
|
||||
* dispatch dblclick event
|
||||
*
|
||||
* @param element
|
||||
*/
|
||||
this.dispatchDblClick = function(element) {
|
||||
const event = this.createDblClick();
|
||||
|
||||
return this.dispatch(event, element);
|
||||
};
|
||||
|
||||
function checkType(type) {
|
||||
if (!type)
|
||||
throw Error('type could not be empty!');
|
||||
}
|
||||
}
|
||||
|
||||
15
client/dom/events/event-store.js
Normal file
15
client/dom/events/event-store.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
let list = [];
|
||||
|
||||
export const add = (el, name, fn) => {
|
||||
list.push([
|
||||
el,
|
||||
name,
|
||||
fn,
|
||||
]);
|
||||
};
|
||||
|
||||
export const clear = () => {
|
||||
list = [];
|
||||
};
|
||||
|
||||
export const get = () => list;
|
||||
37
client/dom/events/event-store.spec.js
Normal file
37
client/dom/events/event-store.spec.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import {test} from 'supertape';
|
||||
import * as eventStore from './event-store.js';
|
||||
|
||||
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.js
Normal file
203
client/dom/events/index.js
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
import itype from 'itype';
|
||||
import * as 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,
|
||||
};
|
||||
|
|
@ -1,105 +1,82 @@
|
|||
/* global CloudCmd */
|
||||
|
||||
'use strict';
|
||||
|
||||
const itype = require('itype/legacy');
|
||||
const currify = require('currify/legacy');
|
||||
const exec = require('execon');
|
||||
|
||||
const Storage = require('./storage');
|
||||
const load = require('./load');
|
||||
const RESTful = require('./rest');
|
||||
import itype from 'itype';
|
||||
import {promisify} from 'es6-promisify';
|
||||
import * as load from '#dom/load';
|
||||
import * as RESTful from '#dom/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_HTML_FS = `${DIR_HTML}fs/`;
|
||||
const DIR_JSON = '/json/';
|
||||
const timeout = getTimeoutOnce(2000);
|
||||
|
||||
const get = currify(getFile);
|
||||
const unaryMap = (array, fn) => array.map((a) => fn(a));
|
||||
export const get = getFile;
|
||||
|
||||
module.exports.get = get;
|
||||
|
||||
function getFile(name, callback) {
|
||||
function getFile(name) {
|
||||
const type = itype(name);
|
||||
let array;
|
||||
check(name);
|
||||
|
||||
check(name, callback);
|
||||
if (type === 'string')
|
||||
return getModule(name);
|
||||
|
||||
switch(type) {
|
||||
case 'string':
|
||||
getModule(name, callback);
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
array = unaryMap(name, get);
|
||||
|
||||
exec.parallel(array, callback);
|
||||
break;
|
||||
}
|
||||
if (type === 'array')
|
||||
return Promise.all(name.map(getFile));
|
||||
}
|
||||
|
||||
function check(name, callback) {
|
||||
function check(name) {
|
||||
if (!name)
|
||||
throw Error('name could not be empty!');
|
||||
|
||||
if (typeof callback !== 'function')
|
||||
throw Error('callback should be a function');
|
||||
}
|
||||
|
||||
function getModule(name, callback) {
|
||||
let path;
|
||||
|
||||
const regExpHTML = new RegExp(FILES_HTML + '|' + FILES_HTML_ROOT);
|
||||
const regExpJSON = new RegExp(FILES_JSON);
|
||||
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) {
|
||||
showError(name);
|
||||
} else if (name === 'config') {
|
||||
getConfig(callback);
|
||||
} else {
|
||||
path = getPath(name, isHTML, isJSON);
|
||||
|
||||
getSystemFile(path, callback);
|
||||
}
|
||||
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 = new RegExp(FILES_HTML_ROOT);
|
||||
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 = DIR_HTML_FS + name;
|
||||
|
||||
path += '.hbs';
|
||||
} else if (isJSON) {
|
||||
path = DIR_JSON + name + '.json';
|
||||
path = DIR_JSON + name + '.json';
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function showError(name) {
|
||||
const str = 'Wrong file name: ' + name;
|
||||
const error = new Error(str);
|
||||
const str = `Wrong file name: ${name}`;
|
||||
const error = Error(str);
|
||||
|
||||
throw(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
function getSystemFile(file, callback) {
|
||||
const prefix = CloudCmd.PREFIX;
|
||||
const getSystemFile = promisify((file, callback) => {
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
if (!Promises[file])
|
||||
Promises[file] = new Promise((success, error) => {
|
||||
|
|
@ -108,7 +85,7 @@ function getSystemFile(file, callback) {
|
|||
load.ajax({
|
||||
url,
|
||||
success,
|
||||
error
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -118,37 +95,29 @@ function getSystemFile(file, callback) {
|
|||
Promises[file] = null;
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function getConfig(callback) {
|
||||
const getConfig = async () => {
|
||||
let is;
|
||||
|
||||
if (!Promises.config)
|
||||
Promises.config = new Promise((resolve, reject) => {
|
||||
Promises.config = () => {
|
||||
is = true;
|
||||
RESTful.Config.read((error, data) => {
|
||||
if (error)
|
||||
return reject(error);
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
return RESTful.Config.read();
|
||||
};
|
||||
|
||||
Promises.config.then(function(data) {
|
||||
const [, data] = await Promises.config();
|
||||
|
||||
if (data)
|
||||
is = false;
|
||||
Storage.setAllowed(data.localStorage);
|
||||
|
||||
callback(null, data);
|
||||
|
||||
timeout(() => {
|
||||
if (!is)
|
||||
Promises.config = null;
|
||||
});
|
||||
}, function() {
|
||||
|
||||
timeout(() => {
|
||||
if (!is)
|
||||
Promises.config = null;
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
function getTimeoutOnce(time) {
|
||||
let is;
|
||||
|
|
@ -165,4 +134,3 @@ function getTimeoutOnce(time) {
|
|||
}, time);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,13 @@
|
|||
/* global CloudCmd */
|
||||
|
||||
'use strict';
|
||||
|
||||
const DOM = require('./');
|
||||
|
||||
const Images = module.exports;
|
||||
/* global DOM */
|
||||
import createElement from '@cloudcmd/create-element';
|
||||
|
||||
const LOADING = 'loading';
|
||||
const HIDDEN = 'hidden';
|
||||
const ERROR = 'error';
|
||||
|
||||
const LoadingImage = LOADING + getLoadingType();
|
||||
const getLoadingType = () => isSVG() ? '-svg' : '-gif';
|
||||
|
||||
function getLoadingType() {
|
||||
return isSVG() ? '-svg' : '-gif';
|
||||
}
|
||||
|
||||
module.exports.get = getElement;
|
||||
export const get = getElement;
|
||||
|
||||
/**
|
||||
* check SVG SMIL animation support
|
||||
|
|
@ -35,54 +26,60 @@ function isSVG() {
|
|||
return /SVGAnimate/.test(name);
|
||||
}
|
||||
|
||||
|
||||
function getElement() {
|
||||
return DOM.load({
|
||||
name : 'span',
|
||||
id : 'js-status-image',
|
||||
className : 'icon',
|
||||
attribute : 'data-progress',
|
||||
notAppend : true
|
||||
return createElement('span', {
|
||||
id: 'js-status-image',
|
||||
className: 'icon',
|
||||
dataName: 'progress',
|
||||
notAppend: true,
|
||||
});
|
||||
}
|
||||
|
||||
/* Функция создаёт картинку загрузки */
|
||||
module.exports.loading = () => {
|
||||
export const loading = () => {
|
||||
const element = getElement();
|
||||
const classList = element.classList;
|
||||
const {classList} = element;
|
||||
const loadingImage = LOADING + getLoadingType();
|
||||
|
||||
classList.add(LOADING, LoadingImage);
|
||||
classList.add(LOADING, loadingImage);
|
||||
classList.remove(ERROR, HIDDEN);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
/* Функция создаёт картинку ошибки загрузки */
|
||||
module.exports.error = () => {
|
||||
export const error = () => {
|
||||
const element = getElement();
|
||||
const classList = element.classList;
|
||||
const {classList} = element;
|
||||
const loadingImage = LOADING + getLoadingType();
|
||||
|
||||
classList.add(ERROR);
|
||||
classList.remove(HIDDEN, LOADING, LoadingImage);
|
||||
classList.remove(HIDDEN, LOADING, loadingImage);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
module.exports.show = show;
|
||||
module.exports.show.load = show;
|
||||
module.exports.show.error = error;
|
||||
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};
|
||||
*/
|
||||
function show(position, panel) {
|
||||
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 {
|
||||
|
|
@ -94,7 +91,7 @@ function show(position, panel) {
|
|||
current = refreshButton.parentElement;
|
||||
}
|
||||
|
||||
if (!parent || (parent && parent !== current))
|
||||
if (!parent || parent && parent !== current)
|
||||
current.appendChild(image);
|
||||
|
||||
DOM.show(image);
|
||||
|
|
@ -102,36 +99,25 @@ function show(position, panel) {
|
|||
return image;
|
||||
}
|
||||
|
||||
function error(text) {
|
||||
const image = Images.error();
|
||||
|
||||
DOM.show(image);
|
||||
image.title = text;
|
||||
|
||||
CloudCmd.log(text);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* hide load image
|
||||
*/
|
||||
module.exports.hide = () => {
|
||||
export function hide() {
|
||||
const element = Images.get();
|
||||
|
||||
DOM.hide(element);
|
||||
|
||||
return Images;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.setProgress = (value, title) => {
|
||||
export const setProgress = (value, title) => {
|
||||
const DATA = 'data-progress';
|
||||
const element = Images.get();
|
||||
|
||||
|
||||
if (!element)
|
||||
return Images;
|
||||
|
||||
element.setAttribute(DATA, value + '%');
|
||||
element.setAttribute(DATA, `${value}%`);
|
||||
|
||||
if (title)
|
||||
element.title = title;
|
||||
|
|
@ -139,7 +125,7 @@ module.exports.setProgress = (value, title) => {
|
|||
return Images;
|
||||
};
|
||||
|
||||
module.exports.clearProgress = () => {
|
||||
export const clearProgress = () => {
|
||||
const DATA = 'data-progress';
|
||||
const element = Images.get();
|
||||
|
||||
|
|
@ -152,3 +138,12 @@ module.exports.clearProgress = () => {
|
|||
return Images;
|
||||
};
|
||||
|
||||
const Images = {
|
||||
clearProgress,
|
||||
setProgress,
|
||||
show,
|
||||
hide,
|
||||
get,
|
||||
error,
|
||||
loading,
|
||||
};
|
||||
|
|
|
|||
1986
client/dom/index.js
1986
client/dom/index.js
File diff suppressed because it is too large
Load diff
52
client/dom/index.spec.js
Normal file
52
client/dom/index.spec.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import {getCSSVar, goToDirectory} from '#dom';
|
||||
|
||||
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();
|
||||
});
|
||||
166
client/dom/io/index.js
Normal file
166
client/dom/io/index.js
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import {FS} from '#common/cloudfunc';
|
||||
import {sendRequest as _sendRequest} from './send-request.js';
|
||||
|
||||
const {assign} = Object;
|
||||
|
||||
const imgPosition = {
|
||||
top: true,
|
||||
};
|
||||
|
||||
export const remove = async (url, data, overrides = {}) => {
|
||||
const {
|
||||
sendRequest = _sendRequest,
|
||||
} = overrides;
|
||||
|
||||
const request = {
|
||||
method: 'DELETE',
|
||||
url: FS + url,
|
||||
imgPosition: {
|
||||
top: Boolean(data),
|
||||
},
|
||||
};
|
||||
|
||||
if (data)
|
||||
assign(request, {
|
||||
data,
|
||||
url: `${request.url}?files`,
|
||||
});
|
||||
|
||||
return await sendRequest(request);
|
||||
};
|
||||
|
||||
export const patch = async (url, data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PATCH',
|
||||
url: FS + url,
|
||||
data,
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
export const write = async (url, data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: FS + url,
|
||||
data,
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
export const createDirectory = async (url, overrides = {}) => {
|
||||
const {
|
||||
sendRequest = _sendRequest,
|
||||
} = overrides;
|
||||
|
||||
return await sendRequest({
|
||||
method: 'PUT',
|
||||
url: `${FS}${url}?dir`,
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
export const read = async (url, dataType = 'text') => {
|
||||
const notLog = !url.includes('?');
|
||||
|
||||
return await _sendRequest({
|
||||
method: 'GET',
|
||||
url: FS + url,
|
||||
notLog,
|
||||
dataType,
|
||||
});
|
||||
};
|
||||
|
||||
export const copy = async (from, to, names) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/copy',
|
||||
data: {
|
||||
from,
|
||||
to,
|
||||
names,
|
||||
},
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
export const pack = async (data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/pack',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const extract = async (data) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/extract',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const move = async (from, to, names) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/move',
|
||||
data: {
|
||||
from,
|
||||
to,
|
||||
names,
|
||||
},
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
export const rename = async (from, to) => {
|
||||
return await _sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/rename',
|
||||
data: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
export const 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,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const 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,
|
||||
});
|
||||
},
|
||||
};
|
||||
61
client/dom/io/index.spec.js
Normal file
61
client/dom/io/index.spec.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import * as io from './index.js';
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
test('client: dom: io: remove: no files', async (t) => {
|
||||
const sendRequest = stub();
|
||||
|
||||
await io.remove('/hello', null, {
|
||||
sendRequest,
|
||||
});
|
||||
|
||||
const expected = {
|
||||
imgPosition: {
|
||||
top: false,
|
||||
},
|
||||
method: 'DELETE',
|
||||
url: '/fs/hello',
|
||||
};
|
||||
|
||||
t.calledWith(sendRequest, [expected]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('client: dom: io: remove: files', async (t) => {
|
||||
const sendRequest = stub();
|
||||
const files = ['world'];
|
||||
|
||||
await io.remove('/hello', files, {
|
||||
sendRequest,
|
||||
});
|
||||
|
||||
const expected = {
|
||||
imgPosition: {
|
||||
top: true,
|
||||
},
|
||||
data: ['world'],
|
||||
method: 'DELETE',
|
||||
url: '/fs/hello?files',
|
||||
};
|
||||
|
||||
t.calledWith(sendRequest, [expected]);
|
||||
t.end();
|
||||
});
|
||||
48
client/dom/io/send-request.js
Normal file
48
client/dom/io/send-request.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* global CloudCmd */
|
||||
import {promisify} from 'es6-promisify';
|
||||
import * as Images from '#dom/images';
|
||||
import * as load from '#dom/load';
|
||||
|
||||
export const sendRequest = 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);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export const _replaceHash = replaceHash;
|
||||
|
||||
function replaceHash(url) {
|
||||
/*
|
||||
* if we send ajax request -
|
||||
* no need in hash so we escape #
|
||||
*/
|
||||
return url.replace(/#/g, '%23');
|
||||
}
|
||||
11
client/dom/io/send-request.spec.js
Normal file
11
client/dom/io/send-request.spec.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import {test} from 'supertape';
|
||||
import {_replaceHash} from './send-request.js';
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
@ -1,69 +1,58 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
import {callbackify} from 'node:util';
|
||||
import {rendy} from 'rendy';
|
||||
import itype from 'itype';
|
||||
import * as load from 'load.js';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import {findObjByNameInArr} from '#common/util';
|
||||
import * as Files from '#dom/files';
|
||||
|
||||
const exec = require('execon');
|
||||
const rendy = require('rendy');
|
||||
const itype = require('itype/legacy');
|
||||
const {findObjByNameInArr} = require('../../common/util');
|
||||
|
||||
const load = require('./load');
|
||||
const Files = require('./files');
|
||||
|
||||
module.exports = (name, options, callback = options) => {
|
||||
const {PREFIX, config} = CloudCmd;
|
||||
export const loadRemote = callbackify(async (name, options) => {
|
||||
const {prefix, config} = CloudCmd;
|
||||
const o = options;
|
||||
|
||||
if (o.name && window[o.name])
|
||||
return callback();
|
||||
return;
|
||||
|
||||
Files.get('modules', (error, modules) => {
|
||||
const online = config('online') && navigator.onLine;
|
||||
const module = findObjByNameInArr(modules.remote, name);
|
||||
const modules = await Files.get('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 = [];
|
||||
|
||||
for (const url of local) {
|
||||
localURL.push(prefix + url);
|
||||
}
|
||||
|
||||
const remoteURL = [];
|
||||
|
||||
for (const tmpl of remoteTmpls) {
|
||||
remoteURL.push(rendy(tmpl, {
|
||||
version,
|
||||
}));
|
||||
}
|
||||
|
||||
if (online) {
|
||||
const [e] = await tryToCatch(load.parallel, remoteURL);
|
||||
|
||||
const isArray = itype.array(module.local);
|
||||
const version = module.version;
|
||||
|
||||
let remoteTmpls, local;
|
||||
if (isArray) {
|
||||
remoteTmpls = module.remote;
|
||||
local = module.local;
|
||||
} else {
|
||||
remoteTmpls = [module.remote];
|
||||
local = [module.local];
|
||||
}
|
||||
|
||||
const localURL = local.map((url) => {
|
||||
return PREFIX + url;
|
||||
});
|
||||
|
||||
const remoteURL = remoteTmpls.map((tmpl) => {
|
||||
return rendy(tmpl, {
|
||||
version: version
|
||||
});
|
||||
});
|
||||
|
||||
const on = funcON(localURL, remoteURL, callback);
|
||||
const off = funcOFF(localURL, callback);
|
||||
|
||||
exec.if(online, on, off);
|
||||
});
|
||||
};
|
||||
|
||||
function funcOFF(local, callback) {
|
||||
return () => {
|
||||
load.parallel(local, callback);
|
||||
};
|
||||
}
|
||||
|
||||
function funcON (local, remote,callback) {
|
||||
return () => {
|
||||
load.parallel(remote, (error) => {
|
||||
if (error)
|
||||
return funcOFF();
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (!e)
|
||||
return;
|
||||
}
|
||||
|
||||
await load.parallel(localURL);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,130 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
const itype = require('itype/legacy');
|
||||
const jonny = require('jonny');
|
||||
const Emitify = require('emitify/legacy');
|
||||
const exec = require('execon');
|
||||
const Images = require('./images');
|
||||
const Events = require('./events');
|
||||
|
||||
const {getExt} = require('../../common/util');
|
||||
|
||||
module.exports = load;
|
||||
module.exports.getIdBySrc = getIdBySrc;
|
||||
module.exports.ext = ext;
|
||||
|
||||
/**
|
||||
* Функция создаёт элемент и загружает файл с src.
|
||||
*
|
||||
* @param params = {
|
||||
* name, - название тэга
|
||||
* src', - путь к файлу
|
||||
* func, - обьект, содержаий одну из функций
|
||||
* или сразу две onload и onerror
|
||||
* {onload: function() {}, onerror: function();}
|
||||
* style,
|
||||
* id,
|
||||
* element,
|
||||
* async, - true by default
|
||||
* inner: 'id{color:red, },
|
||||
* class,
|
||||
* notAppend - false by default
|
||||
*
|
||||
*/
|
||||
function load(params) {
|
||||
const {
|
||||
src,
|
||||
id = getIdBySrc(params.src),
|
||||
func,
|
||||
name,
|
||||
async,
|
||||
inner,
|
||||
style,
|
||||
parent = document.body,
|
||||
className,
|
||||
attribute,
|
||||
notAppend,
|
||||
} = params;
|
||||
|
||||
let element = document.getElementById(id);
|
||||
|
||||
if (element) {
|
||||
exec(func);
|
||||
return element;
|
||||
}
|
||||
|
||||
element = document.createElement(name);
|
||||
|
||||
const funcError = () => {
|
||||
const msg = `file ${src} could not be loaded`;
|
||||
const error = new Error(msg);
|
||||
|
||||
parent.removeChild(element);
|
||||
|
||||
Images.show.error(msg);
|
||||
|
||||
const callback = func && func.onerror || func.onload || func;
|
||||
|
||||
exec(callback, error);
|
||||
};
|
||||
|
||||
const funcLoad = () => {
|
||||
const callback = func && func.onload || func;
|
||||
|
||||
Events.remove('error', element, funcError);
|
||||
|
||||
exec(callback);
|
||||
};
|
||||
|
||||
if (/^(script|link)$/.test(name))
|
||||
Events.addOnce('load', element, funcLoad)
|
||||
.addError(element, funcError);
|
||||
|
||||
if (id)
|
||||
element.id = id;
|
||||
|
||||
if (className)
|
||||
element.className = className;
|
||||
|
||||
if (src) {
|
||||
if (name !== 'link') {
|
||||
element.src = src;
|
||||
} else {
|
||||
element.href = src;
|
||||
element.rel = 'stylesheet';
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute) {
|
||||
const type = itype(attribute);
|
||||
|
||||
switch(type) {
|
||||
case 'string':
|
||||
element.setAttribute(attribute, '');
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
Object.keys(attribute).forEach((name) => {
|
||||
element.setAttribute(name, attribute[name]);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (style)
|
||||
element.style.cssText = style;
|
||||
|
||||
if (async && name === 'script' || async === undefined)
|
||||
element.async = true;
|
||||
|
||||
if (!notAppend)
|
||||
parent.appendChild(element);
|
||||
|
||||
if (inner)
|
||||
element.innerHTML = inner;
|
||||
|
||||
return element;
|
||||
}
|
||||
import itype from 'itype';
|
||||
import jonny from 'jonny';
|
||||
import Emitify from 'emitify';
|
||||
import exec from 'execon';
|
||||
import * as Images from '#dom/images';
|
||||
|
||||
/**
|
||||
* Function gets id by src
|
||||
|
|
@ -132,17 +10,18 @@ function load(params) {
|
|||
*
|
||||
* Example: http://domain.com/1.js -> 1_js
|
||||
*/
|
||||
function getIdBySrc(src) {
|
||||
export function getIdBySrc(src) {
|
||||
const isStr = itype.string(src);
|
||||
|
||||
if (!isStr)
|
||||
return;
|
||||
|
||||
if (~src.indexOf(':'))
|
||||
if (src.includes(':'))
|
||||
src += '-join';
|
||||
|
||||
const num = src.lastIndexOf('/') + 1;
|
||||
const sub = src.substr(src, num);
|
||||
|
||||
const id = src
|
||||
.replace(sub, '')
|
||||
.replace(/\./g, '-');
|
||||
|
|
@ -155,34 +34,37 @@ function getIdBySrc(src) {
|
|||
*
|
||||
* @param params
|
||||
*/
|
||||
module.exports.ajax = (params) => {
|
||||
export const 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.headers || {};
|
||||
|
||||
const {headers = {}} = p;
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open(type, p.url, true);
|
||||
|
||||
Object.keys(headers).forEach((name) => {
|
||||
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.data;
|
||||
({data} = p);
|
||||
|
||||
xhr.onreadystatechange = (event) => {
|
||||
const xhr = event.target;
|
||||
const OK = 200;
|
||||
const OK = 200;
|
||||
|
||||
if (xhr.readyState !== xhr.DONE)
|
||||
return;
|
||||
|
|
@ -190,43 +72,41 @@ module.exports.ajax = (params) => {
|
|||
Images.clearProgress();
|
||||
|
||||
const TYPE_JSON = 'application/json';
|
||||
const type = xhr.getResponseHeader('content-type');
|
||||
const type = xhr.getResponseHeader('content-type');
|
||||
|
||||
if (xhr.status !== OK)
|
||||
return exec(p.error, xhr);
|
||||
|
||||
const notText = p.dataType !== 'text';
|
||||
const isContain = ~type.indexOf(TYPE_JSON);
|
||||
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) => {
|
||||
export const put = (url, body) => {
|
||||
const emitter = Emitify();
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
url = encodeURI(url)
|
||||
.replace('#', '%23');
|
||||
url = encodeURI(url).replace(/#/g, '#');
|
||||
|
||||
xhr.open('put', url, true);
|
||||
|
||||
xhr.upload.onprogress = (event) => {
|
||||
var percent, count;
|
||||
if (!event.lengthComputable)
|
||||
return;
|
||||
|
||||
if (event.lengthComputable) {
|
||||
percent = (event.loaded / event.total) * 100;
|
||||
count = Math.round(percent);
|
||||
|
||||
emitter.emit('progress', count);
|
||||
}
|
||||
|
||||
const percent = event.loaded / event.total * 100;
|
||||
const count = Math.round(percent);
|
||||
|
||||
emitter.emit('progress', count);
|
||||
};
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
|
|
@ -236,8 +116,10 @@ module.exports.put = (url, body) => {
|
|||
if (!over)
|
||||
return;
|
||||
|
||||
if (xhr.status === OK)
|
||||
if (xhr.status === OK) {
|
||||
emitter.emit('progress', 100);
|
||||
return emitter.emit('end');
|
||||
}
|
||||
|
||||
const error = Error(xhr.responseText);
|
||||
emitter.emit('error', error);
|
||||
|
|
@ -247,116 +129,3 @@ module.exports.put = (url, body) => {
|
|||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
function ext(src, func) {
|
||||
switch (getExt(src)) {
|
||||
case '.js':
|
||||
return load.js(src, func);
|
||||
|
||||
case '.css':
|
||||
return load.css(src, func);
|
||||
|
||||
default:
|
||||
return load({
|
||||
src,
|
||||
func,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create elements and load them to DOM-tree
|
||||
* one-by-one
|
||||
*
|
||||
* @param params
|
||||
* @param callback
|
||||
*/
|
||||
load.series = (params, callback) => {
|
||||
if (!params)
|
||||
return load;
|
||||
|
||||
const funcs = params
|
||||
.map((url) => ext.bind(null, url))
|
||||
.concat(callback);
|
||||
|
||||
exec.series(funcs);
|
||||
|
||||
return load;
|
||||
};
|
||||
|
||||
/**
|
||||
* improve callback of funcs so
|
||||
* we pop number of function and
|
||||
* if it's last we call pCallBack
|
||||
*
|
||||
* @param params
|
||||
* @param callback - onload function
|
||||
*/
|
||||
load.parallel = (params, callback) => {
|
||||
if (!params)
|
||||
return load;
|
||||
|
||||
const funcs = params.map((url) => {
|
||||
return ext.bind(null, url);
|
||||
});
|
||||
|
||||
exec.parallel(funcs, callback);
|
||||
|
||||
return load;
|
||||
};
|
||||
|
||||
/**
|
||||
* Функция загружает js-файл
|
||||
*
|
||||
* @param src
|
||||
* @param func
|
||||
*/
|
||||
load.js = (src, func) => {
|
||||
const name = 'script';
|
||||
|
||||
return load({
|
||||
name,
|
||||
src,
|
||||
func,
|
||||
});
|
||||
},
|
||||
|
||||
load.css = (src, func) => {
|
||||
const name = 'link';
|
||||
const {head:parent} = document;
|
||||
|
||||
return load({
|
||||
name,
|
||||
src,
|
||||
parent,
|
||||
func
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Функция создаёт елемент style и записывает туда стили
|
||||
* @param params - структура параметров, заполняеться таким
|
||||
* образом: {src: ' ',func: '', id: '', element: '', inner: ''}
|
||||
* все параметры опциональны
|
||||
*/
|
||||
load.style = (params) => {
|
||||
const {
|
||||
id,
|
||||
src,
|
||||
name = 'style',
|
||||
func,
|
||||
inner,
|
||||
parent = document.head,
|
||||
element,
|
||||
} = params;
|
||||
|
||||
return load({
|
||||
id,
|
||||
src,
|
||||
func,
|
||||
name,
|
||||
inner,
|
||||
parent,
|
||||
element,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
66
client/dom/operations/rename-current.js
Normal file
66
client/dom/operations/rename-current.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* global CloudCmd */
|
||||
import capitalize from 'just-capitalize';
|
||||
import * as _Dialog from '#dom/dialog';
|
||||
import * as Storage from '#dom/storage';
|
||||
import * as RESTful from '#dom/rest';
|
||||
import * as _currentFile from '../current-file.js';
|
||||
|
||||
export default 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();
|
||||
};
|
||||
86
client/dom/operations/rename-current.spec.js
Normal file
86
client/dom/operations/rename-current.spec.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import renameCurrent from './rename-current.js';
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,215 +1,40 @@
|
|||
'use strict';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import * as Dialog from '#dom/dialog';
|
||||
import * as Images from '#dom/images';
|
||||
import {encode} from '#common/entity';
|
||||
import * as IO from './io/index.js';
|
||||
|
||||
/* global CloudCmd, DOM */
|
||||
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];
|
||||
};
|
||||
|
||||
const itype = require('itype/legacy');
|
||||
export const remove = handleError(IO.remove);
|
||||
export const patch = handleError(IO.patch);
|
||||
export const write = handleError(IO.write);
|
||||
export const createDirectory = handleError(IO.createDirectory);
|
||||
export const read = handleError(IO.read);
|
||||
export const copy = handleError(IO.copy);
|
||||
export const pack = handleError(IO.pack);
|
||||
export const extract = handleError(IO.extract);
|
||||
export const move = handleError(IO.move);
|
||||
export const rename = handleError(IO.rename);
|
||||
|
||||
const {FS} = require('../../common/cloudfunc');
|
||||
export const Config = {
|
||||
read: handleError(IO.Config.read),
|
||||
write: handleError(IO.Config.write),
|
||||
};
|
||||
|
||||
module.exports = new RESTful();
|
||||
|
||||
const Images = require('./images');
|
||||
const load = require('./load');
|
||||
|
||||
function RESTful() {
|
||||
this.delete = (url, data, callback) => {
|
||||
const isFunc = itype.function(data);
|
||||
|
||||
if (!callback && isFunc) {
|
||||
callback = data;
|
||||
data = null;
|
||||
}
|
||||
|
||||
sendRequest({
|
||||
method : 'DELETE',
|
||||
url : FS + url,
|
||||
data,
|
||||
callback,
|
||||
imgPosition : { top: !!data }
|
||||
});
|
||||
};
|
||||
|
||||
this.patch = (url, data, callback) => {
|
||||
const isFunc = itype.function(data);
|
||||
|
||||
if (!callback && isFunc) {
|
||||
callback = data;
|
||||
data = null;
|
||||
}
|
||||
|
||||
const imgPosition = {
|
||||
top: true
|
||||
};
|
||||
|
||||
sendRequest({
|
||||
method: 'PATCH',
|
||||
url: FS + url,
|
||||
data,
|
||||
callback,
|
||||
imgPosition,
|
||||
});
|
||||
};
|
||||
|
||||
this.write = (url, data, callback) => {
|
||||
const isFunc = itype.function(data);
|
||||
|
||||
if (!callback && isFunc) {
|
||||
callback = data;
|
||||
data = null;
|
||||
}
|
||||
|
||||
sendRequest({
|
||||
method: 'PUT',
|
||||
url: FS + url,
|
||||
data,
|
||||
callback,
|
||||
imgPosition : { top: true }
|
||||
});
|
||||
};
|
||||
|
||||
this.read = (url, dataType, callback) => {
|
||||
const isQuery = /\?/.test(url);
|
||||
const isBeautify = /\?beautify$/.test(url);
|
||||
const isMinify = /\?minify$/.test(url);
|
||||
const notLog = !isQuery || isBeautify || isMinify;
|
||||
const isFunc = itype.function(dataType);
|
||||
|
||||
if (!callback && isFunc) {
|
||||
callback = dataType;
|
||||
dataType = 'text';
|
||||
}
|
||||
|
||||
sendRequest({
|
||||
method: 'GET',
|
||||
url: FS + url,
|
||||
callback,
|
||||
notLog,
|
||||
dataType,
|
||||
});
|
||||
};
|
||||
|
||||
this.cp = (data, callback) => {
|
||||
sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/cp',
|
||||
data,
|
||||
callback,
|
||||
imgPosition : { top: true }
|
||||
});
|
||||
};
|
||||
|
||||
this.pack = (data, callback) => {
|
||||
sendRequest({
|
||||
method : 'PUT',
|
||||
url : '/pack',
|
||||
data,
|
||||
callback,
|
||||
});
|
||||
};
|
||||
|
||||
this.extract = function(data, callback) {
|
||||
sendRequest({
|
||||
method : 'PUT',
|
||||
url : '/extract',
|
||||
data : data,
|
||||
callback : callback
|
||||
});
|
||||
};
|
||||
|
||||
this.mv = function(data, callback) {
|
||||
sendRequest({
|
||||
method : 'PUT',
|
||||
url : '/mv',
|
||||
data : data,
|
||||
callback : callback,
|
||||
imgPosition : { top: true }
|
||||
});
|
||||
};
|
||||
|
||||
this.Config = {
|
||||
read: function(callback) {
|
||||
sendRequest({
|
||||
method : 'GET',
|
||||
url : '/config',
|
||||
callback : callback,
|
||||
imgPosition : { top: true },
|
||||
notLog : true
|
||||
});
|
||||
},
|
||||
|
||||
write: function(data, callback) {
|
||||
sendRequest({
|
||||
method : 'PATCH',
|
||||
url : '/config',
|
||||
data : data,
|
||||
callback : callback,
|
||||
imgPosition : { top: true }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.Markdown = {
|
||||
read : function(url, callback) {
|
||||
sendRequest({
|
||||
method : 'GET',
|
||||
url : '/markdown' + url,
|
||||
callback : callback,
|
||||
imgPosition : { top: true },
|
||||
notLog : true
|
||||
});
|
||||
},
|
||||
|
||||
render : function(data, callback) {
|
||||
sendRequest({
|
||||
method : 'PUT',
|
||||
url : '/markdown',
|
||||
data : data,
|
||||
callback : callback,
|
||||
imgPosition : { top: true },
|
||||
notLog : true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function sendRequest(params) {
|
||||
var p = params,
|
||||
prefixUrl = CloudCmd.PREFIX_URL;
|
||||
|
||||
p.url = prefixUrl + p.url;
|
||||
p.url = encodeURI(p.url);
|
||||
|
||||
/*
|
||||
* if we send ajax request -
|
||||
* no need in hash so we escape #
|
||||
*/
|
||||
p.url = p.url.replace('#', '%23');
|
||||
|
||||
load.ajax({
|
||||
method : p.method,
|
||||
url : p.url,
|
||||
data : p.data,
|
||||
dataType : p.dataType,
|
||||
error : (jqXHR) => {
|
||||
const response = jqXHR.responseText;
|
||||
const statusText = jqXHR.statusText;
|
||||
const status = jqXHR.status;
|
||||
const text = status === 404 ? response : statusText;
|
||||
|
||||
Images.show.error(text);
|
||||
setTimeout(() => {
|
||||
DOM.Dialog.alert(CloudCmd.TITLE, text);
|
||||
}, 100);
|
||||
|
||||
p.callback(Error(text));
|
||||
},
|
||||
success: (data) => {
|
||||
Images.hide();
|
||||
|
||||
if (!p.notLog)
|
||||
CloudCmd.log(data);
|
||||
|
||||
p.callback(null, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export const Markdown = {
|
||||
read: handleError(IO.Markdown.read),
|
||||
render: handleError(IO.Markdown.render),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,52 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
/* global DOM */
|
||||
import {alert, prompt} from '#dom/dialog';
|
||||
import {getRegExp} from '#common/util';
|
||||
import {getCurrentName} from './current-file.js';
|
||||
import {
|
||||
isSelected,
|
||||
toggleSelectedFile,
|
||||
} from './cmd.js';
|
||||
|
||||
let SelectType = '*.*';
|
||||
const TITLE = 'Cloud Commander';
|
||||
|
||||
const {getRegExp} = require('../../common/util');
|
||||
|
||||
module.exports = (msg, files) => {
|
||||
const allMsg = `Specify file type for ${msg} selection`;
|
||||
const cancel = false;
|
||||
const {Dialog} = DOM;
|
||||
export const selectByPattern = async (msg, files) => {
|
||||
if (!files)
|
||||
return;
|
||||
|
||||
Dialog.prompt(TITLE, allMsg, SelectType, {cancel}).then((type) => {
|
||||
SelectType = type;
|
||||
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 = getCurrentName(current);
|
||||
|
||||
const regExp = getRegExp(type);
|
||||
if (name === '..' || !regExp.test(name))
|
||||
continue;
|
||||
|
||||
if (!files)
|
||||
return;
|
||||
++matches;
|
||||
|
||||
let matches = 0;
|
||||
let selected = isSelected(current);
|
||||
const shouldSel = msg === 'expand';
|
||||
|
||||
files.forEach((current) => {
|
||||
const name = DOM.getCurrentName(current);
|
||||
|
||||
if (name === '..')
|
||||
return;
|
||||
|
||||
const isMatch = regExp.test(name);
|
||||
|
||||
if (!isMatch)
|
||||
return;
|
||||
|
||||
++matches;
|
||||
|
||||
let isSelected = DOM.isSelected(current);
|
||||
const shouldSel = msg === 'expand';
|
||||
|
||||
if (shouldSel)
|
||||
isSelected = !isSelected;
|
||||
|
||||
if (isSelected)
|
||||
DOM.toggleSelectedFile(current);
|
||||
});
|
||||
if (shouldSel)
|
||||
selected = !selected;
|
||||
|
||||
if (!matches)
|
||||
Dialog.alert('Select Files', 'No matches found!');
|
||||
});
|
||||
if (selected)
|
||||
toggleSelectedFile(current);
|
||||
}
|
||||
|
||||
if (!matches)
|
||||
alert('No matches found!');
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,84 +1,26 @@
|
|||
'use strict';
|
||||
const {parse, stringify} = JSON;
|
||||
|
||||
const itype = require('itype/legacy');
|
||||
const jonny = require('jonny');
|
||||
const exec = require('execon');
|
||||
|
||||
/* приватный переключатель возможности работы с кэшем */
|
||||
let Allowed;
|
||||
|
||||
/**
|
||||
* allow Storage usage
|
||||
*/
|
||||
module.exports.setAllowed = (isAllowed) => {
|
||||
Allowed = isAllowed;
|
||||
export const set = (name, data) => {
|
||||
localStorage.setItem(name, data);
|
||||
};
|
||||
|
||||
/** remove element */
|
||||
module.exports.remove = (item, callback) => {
|
||||
if (Allowed)
|
||||
localStorage.removeItem(item);
|
||||
|
||||
exec(callback, null, Allowed);
|
||||
|
||||
return module.exports;
|
||||
export const setJson = (name, data) => {
|
||||
localStorage.setItem(name, stringify(data));
|
||||
};
|
||||
|
||||
module.exports.removeMatch = (string, callback) => {
|
||||
const reg = RegExp('^' + string + '.*$');
|
||||
const test = (a) => reg.test(a);
|
||||
const remove = (a) => localStorage.removeItem(a);
|
||||
|
||||
Object.keys(localStorage)
|
||||
.filter(test)
|
||||
.forEach(remove);
|
||||
|
||||
exec(callback);
|
||||
|
||||
return module.exports;
|
||||
export const get = (name) => {
|
||||
return localStorage.getItem(name);
|
||||
};
|
||||
|
||||
/** если доступен localStorage и
|
||||
* в нём есть нужная нам директория -
|
||||
* записываем данные в него
|
||||
*/
|
||||
module.exports.set = (name, data, callback) => {
|
||||
let str, error;
|
||||
|
||||
if (itype.object(data))
|
||||
str = jonny.stringify(data);
|
||||
|
||||
if (Allowed && name)
|
||||
error = exec.try(() => {
|
||||
localStorage.setItem(name, str || data);
|
||||
});
|
||||
|
||||
exec(callback, error);
|
||||
|
||||
return module.exports;
|
||||
},
|
||||
|
||||
/** Если доступен Storage принимаем из него данные*/
|
||||
module.exports.get = (name, callback) => {
|
||||
let ret;
|
||||
|
||||
if (Allowed)
|
||||
ret = localStorage.getItem(name);
|
||||
|
||||
exec(callback, null, ret);
|
||||
|
||||
return module.exports;
|
||||
},
|
||||
|
||||
/** функция чистит весь кэш для всех каталогов*/
|
||||
module.exports.clear = (callback) => {
|
||||
const ret = Allowed;
|
||||
|
||||
if (ret)
|
||||
localStorage.clear();
|
||||
|
||||
exec(callback, null, ret);
|
||||
|
||||
return module.exports;
|
||||
export const getJson = (name) => {
|
||||
const data = localStorage.getItem(name);
|
||||
return parse(data);
|
||||
};
|
||||
|
||||
export const clear = () => {
|
||||
localStorage.clear();
|
||||
};
|
||||
|
||||
export const remove = (item) => {
|
||||
localStorage.removeItem(item);
|
||||
};
|
||||
|
|
|
|||
105
client/dom/storage.spec.js
Normal file
105
client/dom/storage.spec.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import * as storage from '#dom/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();
|
||||
});
|
||||
|
|
@ -1,27 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd */
|
||||
import {eachSeries} from 'execon';
|
||||
import wraptile from 'wraptile';
|
||||
import * as load from '#dom/load';
|
||||
import {alert} from '#dom/dialog';
|
||||
import {FS} from '#common/cloudfunc';
|
||||
import * as Images from '#dom/images';
|
||||
import {getCurrentDirPath} from './current-file.js';
|
||||
|
||||
const {eachSeries} = require('execon');
|
||||
const wraptile = require('wraptile/legacy');
|
||||
|
||||
const DOM = require('.');
|
||||
const load = require('./load');
|
||||
const Images = require('./images');
|
||||
|
||||
const {FS} = require('../../common/cloudfunc');
|
||||
|
||||
const onEnd = wraptile(_onEnd);
|
||||
const loadFile = wraptile(_loadFile);
|
||||
const onEnd = wraptile(_onEnd);
|
||||
|
||||
const {
|
||||
getCurrentDirPath: getPathWhenRootEmpty
|
||||
} = DOM;
|
||||
|
||||
module.exports = (dir, files) => {
|
||||
export const uploadFiles = (dir, files) => {
|
||||
if (!files) {
|
||||
files = dir;
|
||||
dir = getPathWhenRootEmpty();
|
||||
dir = getCurrentDirPath();
|
||||
}
|
||||
|
||||
const n = files.length;
|
||||
|
|
@ -29,7 +21,7 @@ module.exports = (dir, files) => {
|
|||
if (!n)
|
||||
return;
|
||||
|
||||
const array = [...files];
|
||||
const array = Array.from(files);
|
||||
const {name} = files[0];
|
||||
|
||||
eachSeries(array, loadFile(dir, n), onEnd(name));
|
||||
|
|
@ -37,17 +29,17 @@ module.exports = (dir, files) => {
|
|||
|
||||
function _onEnd(currentName) {
|
||||
CloudCmd.refresh({
|
||||
currentName
|
||||
currentName,
|
||||
});
|
||||
}
|
||||
|
||||
function _loadFile(dir, n, file, callback) {
|
||||
let i = 0;
|
||||
|
||||
const name = file.name;
|
||||
const {name} = file;
|
||||
const path = dir + name;
|
||||
const {PREFIX_URL} = CloudCmd;
|
||||
const api = PREFIX_URL + FS;
|
||||
const {prefixURL} = CloudCmd;
|
||||
const api = prefixURL + FS;
|
||||
|
||||
const percent = (i, n, per = 100) => {
|
||||
return Math.round(i * per / n);
|
||||
|
|
@ -57,7 +49,9 @@ function _loadFile(dir, n, file, callback) {
|
|||
|
||||
++i;
|
||||
|
||||
load.put(api + path, file)
|
||||
load
|
||||
.put(api + path, file)
|
||||
.on('error', showError)
|
||||
.on('end', callback)
|
||||
.on('progress', (count) => {
|
||||
const max = step(n);
|
||||
|
|
@ -68,3 +62,6 @@ function _loadFile(dir, n, file, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
function showError({message}) {
|
||||
alert(message);
|
||||
}
|
||||
|
|
|
|||
44
client/get-json-from-file-table.js
Normal file
44
client/get-json-from-file-table.js
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,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const currify = require('currify/legacy');
|
||||
|
||||
const isType = currify((type, object, name) => {
|
||||
return typeof object[name] === type;
|
||||
});
|
||||
|
||||
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 = Object.assign({}, config);
|
||||
const array = Object.keys(result);
|
||||
|
||||
array
|
||||
.filter(isBool(result))
|
||||
.forEach((name) => {
|
||||
const item = result[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.type;
|
||||
|
||||
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.type;
|
||||
|
||||
switch(type) {
|
||||
case 'checkbox':
|
||||
el.checked = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
el.value = value;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
573
client/key.js
573
client/key.js
|
|
@ -1,573 +0,0 @@
|
|||
/* global CloudCmd, DOM */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Info = DOM.CurrentInfo;
|
||||
|
||||
const exec = require('execon');
|
||||
|
||||
const Events = require('./dom/events');
|
||||
const Buffer = require('./dom/buffer');
|
||||
const {escapeRegExp} = require('../common/util');
|
||||
|
||||
let Chars = [];
|
||||
const KEY = {
|
||||
BACKSPACE : 8,
|
||||
TAB : 9,
|
||||
ENTER : 13,
|
||||
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,
|
||||
|
||||
A : 65,
|
||||
|
||||
C : 67,
|
||||
D : 68,
|
||||
|
||||
G : 71,
|
||||
|
||||
M : 77,
|
||||
|
||||
O : 79,
|
||||
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,
|
||||
|
||||
EQUAL : 187,
|
||||
HYPHEN : 189,
|
||||
DOT : 190,
|
||||
SLASH : 191,
|
||||
TRA : 192, /* Typewritten Reverse Apostrophe (`) */
|
||||
BACKSLASH : 220,
|
||||
|
||||
BRACKET_CLOSE: 221
|
||||
};
|
||||
|
||||
KeyProto.prototype = KEY;
|
||||
CloudCmd.Key = KeyProto;
|
||||
|
||||
function KeyProto() {
|
||||
const Key = this;
|
||||
|
||||
let Binded;
|
||||
|
||||
this.isBind = () => {
|
||||
return Binded;
|
||||
};
|
||||
|
||||
this.setBind = () => {
|
||||
Binded = true;
|
||||
};
|
||||
|
||||
this.unsetBind = () => {
|
||||
Binded = false;
|
||||
};
|
||||
|
||||
this.bind = () => {
|
||||
Events.addKey(listener);
|
||||
Binded = true;
|
||||
};
|
||||
|
||||
function getChar(event) {
|
||||
/*
|
||||
* event.keyIdentifier deprecated in chrome v51
|
||||
* but event.key is absent in chrome <= v51
|
||||
*/
|
||||
|
||||
if (event.key)
|
||||
return event.key;
|
||||
|
||||
return fromCharCode(event.keyIdentifier);
|
||||
}
|
||||
|
||||
function listener(event) {
|
||||
const keyCode = event.keyCode;
|
||||
const alt = event.altKey;
|
||||
const ctrl = event.ctrlKey;
|
||||
const shift = event.shiftKey;
|
||||
const meta = event.metaKey;
|
||||
const isBetween = keyCode >= KEY.ZERO && keyCode <= KEY.Z;
|
||||
const isNumpad = /Numpad/.test(event.code);
|
||||
|
||||
let char = getChar(event);
|
||||
let isSymbol = ~['.', '_', '-', '+', '='].indexOf(char);
|
||||
|
||||
if (!isSymbol) {
|
||||
isSymbol = getSymbol(shift, keyCode);
|
||||
|
||||
if (isSymbol)
|
||||
char = isSymbol;
|
||||
}
|
||||
|
||||
/* in case buttons can be processed */
|
||||
if (Key.isBind())
|
||||
if (!isNumpad && !alt && !ctrl && !meta && (isBetween || isSymbol))
|
||||
setCurrentByChar(char);
|
||||
else {
|
||||
Chars = [];
|
||||
switchKey(event);
|
||||
}
|
||||
}
|
||||
|
||||
function getSymbol(shift, keyCode) {
|
||||
switch (keyCode) {
|
||||
case KEY.DOT:
|
||||
return '.';
|
||||
|
||||
case KEY.HYPHEN:
|
||||
return shift ? '_' : '-';
|
||||
|
||||
case KEY.EQUAL:
|
||||
return shift ? '+' : '=';
|
||||
}
|
||||
}
|
||||
|
||||
function fromCharCode(keyIdentifier) {
|
||||
const code = keyIdentifier.substring(2);
|
||||
const hex = parseInt(code, 16);
|
||||
const char = String.fromCharCode(hex);
|
||||
|
||||
return char;
|
||||
}
|
||||
|
||||
function setCurrentByChar(char) {
|
||||
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 n = Chars.length;
|
||||
|
||||
while(i < n && char === Chars[i]) {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!i)
|
||||
Chars = [];
|
||||
|
||||
const skipN = skipCount = i;
|
||||
Chars.push(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;
|
||||
} else {
|
||||
if (skipN === skipCount)
|
||||
firstByName = byName;
|
||||
|
||||
--skipCount;
|
||||
}
|
||||
};
|
||||
|
||||
names
|
||||
.filter(isTest)
|
||||
.filter(not(isRoot))
|
||||
.some(setCurrent);
|
||||
|
||||
if (!setted) {
|
||||
DOM.setCurrentFile(firstByName);
|
||||
Chars = [char];
|
||||
}
|
||||
}
|
||||
|
||||
function switchKey(event) {
|
||||
let i, isSelected, prev, next;
|
||||
let current = Info.element;
|
||||
let name = Info.name;
|
||||
|
||||
const {Operation} = CloudCmd;
|
||||
const panel = Info.panel;
|
||||
const path = Info.path;
|
||||
const isDir = Info.isDir;
|
||||
|
||||
const keyCode = event.keyCode;
|
||||
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:
|
||||
DOM.renameCurrent(current);
|
||||
break;
|
||||
|
||||
case Key.F3:
|
||||
if (shift)
|
||||
CloudCmd.Markdown.show(path);
|
||||
else if (ctrlMeta)
|
||||
CloudCmd.sortPanel('name');
|
||||
else
|
||||
CloudCmd.View.show();
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case Key.F4:
|
||||
if (shift)
|
||||
CloudCmd.EditFileVim.show();
|
||||
else
|
||||
CloudCmd.EditFile.show();
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case Key.F5:
|
||||
if (ctrlMeta)
|
||||
CloudCmd.sortPanel('date');
|
||||
else
|
||||
Operation.show('copy');
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case Key.F6:
|
||||
if (ctrlMeta)
|
||||
CloudCmd.sortPanel('size');
|
||||
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:
|
||||
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:
|
||||
if (!isDir || name === '..')
|
||||
isSelected = true;
|
||||
else
|
||||
isSelected = DOM.isSelected(current);
|
||||
|
||||
exec.if(isSelected, () => {
|
||||
DOM.toggleSelectedFile(current);
|
||||
}, (callback) => {
|
||||
DOM.loadCurrentSize(callback, current);
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
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();
|
||||
|
||||
name = Info.panel.getAttribute('data-name');
|
||||
|
||||
if (name === 'js-right')
|
||||
DOM.duplicatePanel();
|
||||
|
||||
break;
|
||||
|
||||
case Key.RIGHT:
|
||||
if (!alt)
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
name = Info.panel.getAttribute('data-name');
|
||||
|
||||
if (name === '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;
|
||||
|
||||
/* open directory */
|
||||
case Key.ENTER:
|
||||
if (Info.isDir)
|
||||
CloudCmd.loadDir({
|
||||
path: path === '/' ? '/' : path + '/'
|
||||
});
|
||||
break;
|
||||
|
||||
case Key.BACKSPACE:
|
||||
CloudCmd.goToParentDir();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case Key.BACKSLASH:
|
||||
if (ctrlMeta)
|
||||
CloudCmd.loadDir({
|
||||
path: '/'
|
||||
});
|
||||
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) {
|
||||
CloudCmd.EditNames.show();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
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.D:
|
||||
if (ctrlMeta) {
|
||||
CloudCmd.log('clearing storage...');
|
||||
|
||||
DOM.Storage.clear(() => {
|
||||
CloudCmd.log('storage cleared');
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
client/key/binder.js
Normal file
15
client/key/binder.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
export const createBinder = () => {
|
||||
let binded = false;
|
||||
|
||||
return {
|
||||
isBind() {
|
||||
return binded;
|
||||
},
|
||||
setBind() {
|
||||
binded = true;
|
||||
},
|
||||
unsetBind() {
|
||||
binded = false;
|
||||
},
|
||||
};
|
||||
};
|
||||
28
client/key/binder.spec.js
Normal file
28
client/key/binder.spec.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import {test} from 'supertape';
|
||||
import {createBinder} from './binder.js';
|
||||
|
||||
test('client: key: binder: isBind: default', (t) => {
|
||||
const binder = createBinder();
|
||||
|
||||
t.notOk(binder.isBind(), 'should not be bind by default');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('client: key: binder: setBind', (t) => {
|
||||
const binder = createBinder();
|
||||
|
||||
binder.setBind();
|
||||
|
||||
t.ok(binder.isBind(), 'should be bind');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('client: key: binder: unsetBind', (t) => {
|
||||
const binder = createBinder();
|
||||
|
||||
binder.setBind();
|
||||
binder.unsetBind();
|
||||
|
||||
t.notOk(binder.isBind(), 'should not be bind');
|
||||
t.end();
|
||||
});
|
||||
544
client/key/index.js
Normal file
544
client/key/index.js
Normal file
|
|
@ -0,0 +1,544 @@
|
|||
/* global CloudCmd, DOM */
|
||||
import clipboard from '@cloudcmd/clipboard';
|
||||
import {fullstore} from 'fullstore';
|
||||
import * as Events from '#dom/events';
|
||||
import * as Buffer from '../dom/buffer.js';
|
||||
import * as 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 (!config('vim') && keyCode === KEY.ESC)
|
||||
_config('vim', true);
|
||||
};
|
||||
|
||||
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 calls 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.L:
|
||||
if (ctrlMeta && shift) {
|
||||
CloudCmd.logOut();
|
||||
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;
|
||||
}
|
||||
}
|
||||
56
client/key/index.spec.js
Normal file
56
client/key/index.spec.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import autoGlobals from 'auto-globals';
|
||||
import supertape from 'supertape';
|
||||
import {ESC} from './key.js';
|
||||
import {Key, _listener} from './index.js';
|
||||
import {getDOM, getCloudCmd} from './vim/globals.fixture.js';
|
||||
|
||||
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();
|
||||
});
|
||||
58
client/key/key.js
Normal file
58
client/key/key.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
export const BACKSPACE = 8;
|
||||
export const TAB = 9;
|
||||
export const ENTER = 13;
|
||||
export const CAPSLOCK = 20;
|
||||
export const ESC = 27;
|
||||
export const SPACE = 32;
|
||||
export const PAGE_UP = 33;
|
||||
export const PAGE_DOWN = 34;
|
||||
export const END = 35;
|
||||
export const HOME = 36;
|
||||
export const LEFT = 37;
|
||||
export const UP = 38;
|
||||
export const RIGHT = 39;
|
||||
export const DOWN = 40;
|
||||
export const INSERT = 45;
|
||||
export const DELETE = 46;
|
||||
export const ZERO = 48;
|
||||
export const SEMICOLON = 52;
|
||||
export const A = 65;
|
||||
export const C = 67;
|
||||
export const D = 68;
|
||||
export const G = 71;
|
||||
export const J = 74;
|
||||
export const K = 75;
|
||||
export const L = 76;
|
||||
export const M = 77;
|
||||
export const O = 79;
|
||||
export const P = 80;
|
||||
export const Q = 81;
|
||||
export const R = 82;
|
||||
export const S = 83;
|
||||
export const T = 84;
|
||||
export const U = 85;
|
||||
export const V = 86;
|
||||
export const X = 88;
|
||||
export const Z = 90;
|
||||
export const INSERT_MAC = 96;
|
||||
export const ASTERISK = 106;
|
||||
export const PLUS = 107;
|
||||
export const MINUS = 109;
|
||||
export const F1 = 112;
|
||||
export const F2 = 113;
|
||||
export const F3 = 114;
|
||||
export const F4 = 115;
|
||||
export const F5 = 116;
|
||||
export const F6 = 117;
|
||||
export const F7 = 118;
|
||||
export const F8 = 119;
|
||||
export const F9 = 120;
|
||||
export const F10 = 121;
|
||||
export const COLON = 186;
|
||||
export const EQUAL = 187;
|
||||
export const HYPHEN = 189;
|
||||
export const DOT = 190;
|
||||
export const SLASH = 191;
|
||||
export const TRA = 192;
|
||||
export const BACKSLASH = 220;
|
||||
export const BRACKET_CLOSE = 221;
|
||||
57
client/key/set-current-by-char.js
Normal file
57
client/key/set-current-by-char.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/* global DOM */
|
||||
import {escapeRegExp} from '#common/util';
|
||||
|
||||
export default function setCurrentByChar(char, charStore) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
let firstByName;
|
||||
let skipCount = 0;
|
||||
let set = 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) {
|
||||
set = true;
|
||||
DOM.setCurrentFile(byName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (skipN === skipCount)
|
||||
firstByName = byName;
|
||||
|
||||
--skipCount;
|
||||
};
|
||||
|
||||
names
|
||||
.filter(isTest)
|
||||
.filter(not(isRoot))
|
||||
.some(setCurrent);
|
||||
|
||||
if (!set) {
|
||||
DOM.setCurrentFile(firstByName);
|
||||
charStore([char]);
|
||||
}
|
||||
}
|
||||
47
client/key/vim/find.js
Normal file
47
client/key/vim/find.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import {fullstore} from 'fullstore';
|
||||
import limier from 'limier';
|
||||
|
||||
const searchStore = fullstore([]);
|
||||
const searchIndex = fullstore(0);
|
||||
|
||||
export const find = (value, names) => {
|
||||
const result = limier(value, names);
|
||||
|
||||
searchStore(result);
|
||||
searchIndex(0);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const findNext = () => {
|
||||
const names = searchStore();
|
||||
const index = next(searchIndex(), names.length);
|
||||
|
||||
searchIndex(index);
|
||||
return names[searchIndex()];
|
||||
};
|
||||
|
||||
export const findPrevious = () => {
|
||||
const names = searchStore();
|
||||
const index = previous(searchIndex(), names.length);
|
||||
|
||||
searchIndex(index);
|
||||
return names[index];
|
||||
};
|
||||
|
||||
export const _next = next;
|
||||
export const _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;
|
||||
}
|
||||
55
client/key/vim/find.spec.js
Normal file
55
client/key/vim/find.spec.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import test from 'supertape';
|
||||
import {getDOM} from './globals.fixture.js';
|
||||
import {
|
||||
_next,
|
||||
_previous,
|
||||
find,
|
||||
findNext,
|
||||
findPrevious,
|
||||
} from './find.js';
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
|
||||
test('cloudcmd: client: vim: _next', (t) => {
|
||||
const result = _next(1, 2);
|
||||
|
||||
t.notOk(result, 'should return 0');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: _next: increment', (t) => {
|
||||
const result = _next(0, 2);
|
||||
|
||||
t.equal(result, 1, 'should return 1');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: _previous', (t) => {
|
||||
const result = _previous(0, 2);
|
||||
|
||||
t.equal(result, 1, 'should return 1');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: _previous: decrement', (t) => {
|
||||
const result = _previous(1, 2);
|
||||
|
||||
t.equal(result, 0, 'should return 0');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: findNext: after find', (t) => {
|
||||
find('a', ['alpha', 'beta', 'apple']);
|
||||
const result = findNext();
|
||||
|
||||
t.equal(result, 'beta', 'should return next found name');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: findPrevious: after find', (t) => {
|
||||
find('a', ['alpha', 'beta', 'apple']);
|
||||
const result = findPrevious();
|
||||
|
||||
t.equal(result, 'apple', 'should return previous found name');
|
||||
t.end();
|
||||
});
|
||||
46
client/key/vim/globals.fixture.js
Normal file
46
client/key/vim/globals.fixture.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
const noop = () => {};
|
||||
|
||||
export const 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,
|
||||
};
|
||||
};
|
||||
|
||||
export const getCloudCmd = () => {
|
||||
const show = () => {};
|
||||
|
||||
return {
|
||||
Operation: {
|
||||
show,
|
||||
},
|
||||
|
||||
config: noop,
|
||||
_config: noop,
|
||||
};
|
||||
};
|
||||
167
client/key/vim/index.js
Normal file
167
client/key/vim/index.js
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import vim from './vim.js';
|
||||
import * as finder from './find.js';
|
||||
import {
|
||||
setCurrent,
|
||||
selectFileNotParent,
|
||||
} from './set-current.js';
|
||||
|
||||
export default (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),
|
||||
stopImmediatePropagation = event?.preventDefault?.bind(event),
|
||||
promptNewFile = globalThis.DOM.promptNewFile,
|
||||
toggleSelectedFile,
|
||||
Buffer = {},
|
||||
createFindNext = _createFindNext,
|
||||
createFindPrevious = _createFindPrevious,
|
||||
createMakeFile = _createMakeFile,
|
||||
renameCurrent,
|
||||
} = deps;
|
||||
|
||||
return {
|
||||
makeFile: createMakeFile({
|
||||
promptNewFile,
|
||||
preventDefault,
|
||||
stopImmediatePropagation,
|
||||
}),
|
||||
findNext: createFindNext({
|
||||
setCurrentByName,
|
||||
}),
|
||||
findPrevious: createFindPrevious({
|
||||
setCurrentByName,
|
||||
}),
|
||||
escape: unselectFiles,
|
||||
rename: () => {
|
||||
event.preventDefault();
|
||||
renameCurrent();
|
||||
},
|
||||
remove: () => {
|
||||
Operation.show('delete');
|
||||
},
|
||||
operationCopy: () => {
|
||||
event.preventDefault();
|
||||
Operation.show('copy');
|
||||
},
|
||||
operationMove: () => {
|
||||
event.preventDefault();
|
||||
Operation.show('move');
|
||||
},
|
||||
|
||||
makeDirectory: () => {
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
globalThis.DOM.promptNewDir();
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const selectFile = selectFileNotParent;
|
||||
|
||||
const _createFindPrevious = (overrides = {}) => () => {
|
||||
const {setCurrentByName} = overrides;
|
||||
const name = finder.findPrevious();
|
||||
|
||||
setCurrentByName(name);
|
||||
};
|
||||
|
||||
const _createFindNext = (overrides = {}) => () => {
|
||||
const {setCurrentByName} = overrides;
|
||||
const name = finder.findNext();
|
||||
|
||||
setCurrentByName(name);
|
||||
};
|
||||
|
||||
const _createMakeFile = (overrides = {}) => () => {
|
||||
const {
|
||||
promptNewFile,
|
||||
stopImmediatePropagation,
|
||||
preventDefault,
|
||||
} = overrides;
|
||||
|
||||
stopImmediatePropagation();
|
||||
preventDefault();
|
||||
promptNewFile();
|
||||
};
|
||||
756
client/key/vim/index.spec.js
Normal file
756
client/key/vim/index.spec.js
Normal file
|
|
@ -0,0 +1,756 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import {getDOM, getCloudCmd} from './globals.fixture.js';
|
||||
import vim, {selectFile as vimSelectFile} from './index.js';
|
||||
import * as finder from './find.js';
|
||||
|
||||
globalThis.DOM = getDOM();
|
||||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const {assign} = Object;
|
||||
const {DOM} = globalThis;
|
||||
const {Buffer} = DOM;
|
||||
|
||||
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 = {};
|
||||
|
||||
vimSelectFile(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 = {};
|
||||
|
||||
vimSelectFile(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 event = {
|
||||
preventDefault: stub(),
|
||||
};
|
||||
|
||||
vim('/', event);
|
||||
|
||||
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();
|
||||
const createFindPrevious = stub().returns(findPrevious);
|
||||
const event = {};
|
||||
|
||||
vim('N', event, {
|
||||
createFindPrevious,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(findPrevious);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: make directory', async (t) => {
|
||||
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 promptNewFile = stub();
|
||||
|
||||
const event = {
|
||||
stopImmediatePropagation: stub(),
|
||||
preventDefault: stub(),
|
||||
};
|
||||
|
||||
vim('m', event);
|
||||
vim('f', event, {
|
||||
promptNewFile,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(promptNewFile);
|
||||
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();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: vim: rename', async (t) => {
|
||||
const DOM = getDOM();
|
||||
const renameCurrent = stub();
|
||||
|
||||
assign(DOM, {
|
||||
renameCurrent,
|
||||
});
|
||||
|
||||
const event = {
|
||||
preventDefault: stub(),
|
||||
};
|
||||
|
||||
await vim('rr', event, DOM);
|
||||
|
||||
t.calledWithNoArgs(renameCurrent);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: cc: operationCopy', (t) => {
|
||||
const show = stub();
|
||||
const preventDefault = stub();
|
||||
|
||||
const Operation = {
|
||||
show,
|
||||
};
|
||||
|
||||
const event = {
|
||||
preventDefault,
|
||||
};
|
||||
|
||||
vim('c', event, {
|
||||
Operation,
|
||||
});
|
||||
|
||||
vim('c', event, {
|
||||
Operation,
|
||||
});
|
||||
|
||||
t.calledWith(show, ['copy'], 'should show copy operation');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: mm: operationMove', (t) => {
|
||||
const show = stub();
|
||||
const preventDefault = stub();
|
||||
|
||||
const Operation = {
|
||||
show,
|
||||
};
|
||||
|
||||
const event = {
|
||||
preventDefault,
|
||||
};
|
||||
|
||||
vim('m', event, {
|
||||
Operation,
|
||||
});
|
||||
|
||||
vim('m', event, {
|
||||
Operation,
|
||||
});
|
||||
|
||||
t.calledWith(show, ['move'], 'should show move operation');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: n: findNext: real', (t) => {
|
||||
const setCurrentByName = stub();
|
||||
|
||||
finder.find('a', ['alpha', 'beta', 'apple']);
|
||||
|
||||
const event = {};
|
||||
|
||||
vim('n', event, {
|
||||
setCurrentByName,
|
||||
});
|
||||
|
||||
t.calledWith(setCurrentByName, ['beta'], 'should set current by next found name');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: key: N: findPrevious: real', (t) => {
|
||||
const setCurrentByName = stub();
|
||||
|
||||
finder.find('a', ['alpha', 'beta', 'apple']);
|
||||
|
||||
const event = {};
|
||||
|
||||
vim('N', event, {
|
||||
setCurrentByName,
|
||||
});
|
||||
|
||||
t.calledWith(setCurrentByName, ['apple'], 'should set current by previous found name');
|
||||
t.end();
|
||||
});
|
||||
33
client/key/vim/set-current.js
Normal file
33
client/key/vim/set-current.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* global DOM */
|
||||
export function selectFileNotParent(current, {getCurrentName, selectFile} = DOM) {
|
||||
const name = getCurrentName(current);
|
||||
|
||||
if (name === '..')
|
||||
return;
|
||||
|
||||
selectFile(current);
|
||||
}
|
||||
|
||||
export const 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');
|
||||
};
|
||||
220
client/key/vim/vim.js
Normal file
220
client/key/vim/vim.js
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import {fullstore} from '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 = () => {};
|
||||
|
||||
export default (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,
|
||||
operationCopy = noop,
|
||||
operationMove = noop,
|
||||
rename = 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 === 'cc') {
|
||||
operationCopy();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (value === 'mm') {
|
||||
operationMove();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (value === 'mf') {
|
||||
makeFile();
|
||||
return end();
|
||||
}
|
||||
|
||||
if (value === 'rr') {
|
||||
rename();
|
||||
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);
|
||||
}
|
||||
100
client/key/vim/vim.spec.js
Normal file
100
client/key/vim/vim.spec.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import {test, stub} from 'supertape';
|
||||
import vim from './vim.js';
|
||||
|
||||
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: cc', (t) => {
|
||||
const operationCopy = stub();
|
||||
|
||||
vim('cc', {
|
||||
operationCopy,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(operationCopy);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('vim: mm', (t) => {
|
||||
const operationMove = stub();
|
||||
|
||||
vim('mm', {
|
||||
operationMove,
|
||||
});
|
||||
|
||||
t.calledWithNoArgs(operationMove);
|
||||
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();
|
||||
});
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
'use strict';
|
||||
import currify from 'currify';
|
||||
|
||||
module.exports = (array, item) => {
|
||||
export const getIndex = currify((array, item) => {
|
||||
const index = array.indexOf(item);
|
||||
|
||||
if (!~index)
|
||||
return 0;
|
||||
|
||||
return index;
|
||||
};
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('tape');
|
||||
|
||||
const dir = '../../../client/listeners';
|
||||
const getIndex = require(`${dir}/get-index`);
|
||||
import test from 'supertape';
|
||||
import {getIndex} from './get-index.js';
|
||||
|
||||
test('cloudcmd: client: listeners: getIndex: not found', (t) => {
|
||||
const array = ['hello'];
|
||||
|
|
@ -13,9 +9,11 @@ test('cloudcmd: client: listeners: getIndex: not found', (t) => {
|
|||
});
|
||||
|
||||
test('cloudcmd: client: listeners: getIndex: found', (t) => {
|
||||
const array = ['hello', 'world'];
|
||||
const array = [
|
||||
'hello',
|
||||
'world',
|
||||
];
|
||||
|
||||
t.equal(getIndex(array, 'world'), 1, 'should return index');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = (indexFrom, indexTo, files) => {
|
||||
export const getRange = (indexFrom, indexTo, files) => {
|
||||
if (indexFrom < indexTo)
|
||||
return files.slice(indexFrom, indexTo + 1);
|
||||
|
||||
|
|
@ -9,4 +7,3 @@ module.exports = (indexFrom, indexTo, files) => {
|
|||
|
||||
return [files[indexFrom]];
|
||||
};
|
||||
|
||||
|
|
|
|||
52
client/listeners/get-range.spec.js
Normal file
52
client/listeners/get-range.spec.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import test from 'supertape';
|
||||
import {getRange} from './get-range.js';
|
||||
|
||||
test('cloudcmd: client: listeners: getRange: direct', (t) => {
|
||||
const expected = [
|
||||
'hello',
|
||||
'world',
|
||||
];
|
||||
|
||||
const files = [
|
||||
...expected,
|
||||
'how',
|
||||
'come',
|
||||
];
|
||||
|
||||
const result = getRange(0, 1, files);
|
||||
|
||||
t.deepEqual(result, expected, 'should return range');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: listeners: getRange: reverse', (t) => {
|
||||
const expected = [
|
||||
'hello',
|
||||
'world',
|
||||
];
|
||||
|
||||
const files = [
|
||||
...expected,
|
||||
'how',
|
||||
'come',
|
||||
];
|
||||
|
||||
const result = getRange(1, 0, files);
|
||||
|
||||
t.deepEqual(result, expected, 'should return range');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: client: listeners: getRange: one', (t) => {
|
||||
const expected = ['hello'];
|
||||
const files = [
|
||||
...expected,
|
||||
'how',
|
||||
'come',
|
||||
];
|
||||
|
||||
const result = getRange(0, 0, files);
|
||||
|
||||
t.deepEqual(result, expected, 'should return range');
|
||||
t.end();
|
||||
});
|
||||
|
|
@ -1,35 +1,30 @@
|
|||
/* global DOM, CloudCmd */
|
||||
import exec from 'execon';
|
||||
import itype from 'itype';
|
||||
import currify from 'currify';
|
||||
import {tryToCatch} from 'try-to-catch';
|
||||
import clipboard from '@cloudcmd/clipboard';
|
||||
import * as Events from '#dom/events';
|
||||
import {uploadFiles} from '#dom/upload-files';
|
||||
import {FS} from '#common/cloudfunc';
|
||||
import {getRange} from './get-range.js';
|
||||
import {getIndex} from './get-index.js';
|
||||
|
||||
'use strict';
|
||||
const NBSP_REG = RegExp(String.fromCharCode(160), 'g');
|
||||
const SPACE = ' ';
|
||||
|
||||
const exec = require('execon');
|
||||
const itype = require('itype/legacy');
|
||||
const currify = require('currify/legacy');
|
||||
const getRange = require('./get-range');
|
||||
|
||||
const getIndex = currify(require('./get-index'));
|
||||
|
||||
const uploadFiles = require('../dom/upload-files');
|
||||
|
||||
const {
|
||||
FS,
|
||||
apiURL
|
||||
} = require('../../common/cloudfunc');
|
||||
|
||||
module.exports.init = () => {
|
||||
export async function init() {
|
||||
contextMenu();
|
||||
dragndrop();
|
||||
unload();
|
||||
pop();
|
||||
resize();
|
||||
config();
|
||||
header();
|
||||
};
|
||||
|
||||
CloudCmd.Listeners = module.exports;
|
||||
await config();
|
||||
}
|
||||
|
||||
const unselect = (event) => {
|
||||
const isMac = /Mac/.test(window.navigator.platform);
|
||||
const isMac = /Mac/.test(globalThis.navigator.platform);
|
||||
const {
|
||||
shiftKey,
|
||||
metaKey,
|
||||
|
|
@ -43,31 +38,25 @@ const unselect = (event) => {
|
|||
};
|
||||
|
||||
const execAll = currify((funcs, event) => {
|
||||
funcs.forEach((fn) => {
|
||||
for (const fn of funcs)
|
||||
fn(event);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const Info = DOM.CurrentInfo;
|
||||
const Storage = DOM.Storage;
|
||||
const Events = DOM.Events;
|
||||
const EventsFiles = {
|
||||
mousedown: exec.with(execIfNotUL, setCurrentFileByEvent),
|
||||
click: execAll([onClick, unselect]),
|
||||
click: execAll([onClick, exec.with(execIfNotMobile, unselect)]),
|
||||
dragstart: exec.with(execIfNotUL, onDragStart),
|
||||
dblclick: exec.with(execIfNotUL, onDblClick),
|
||||
touchstart: exec.with(execIfNotUL, onTouch)
|
||||
touchstart: exec.with(execIfNotUL, onTouch),
|
||||
};
|
||||
|
||||
let EXT;
|
||||
|
||||
function header() {
|
||||
const fm = DOM.getFM();
|
||||
|
||||
const isDataset = (el) => el.dataset;
|
||||
|
||||
var isPanel = (el) => {
|
||||
const isPanel = (el) => {
|
||||
return /^js-(left|right)$/.test(el.dataset.name);
|
||||
};
|
||||
|
||||
|
|
@ -78,8 +67,7 @@ function header() {
|
|||
if (parent.dataset.name !== 'js-fm-header')
|
||||
return;
|
||||
|
||||
const name = (el.dataset.name || '')
|
||||
.replace('js-', '');
|
||||
const name = (el.dataset.name || '').replace('js-', '');
|
||||
|
||||
if (!/^(name|size|date)$/.test(name))
|
||||
return;
|
||||
|
|
@ -100,44 +88,48 @@ function getPath(el, path = []) {
|
|||
return getPath(el.parentElement, path.concat(el));
|
||||
}
|
||||
|
||||
function config() {
|
||||
DOM.Files.get('config', (e, config) => {
|
||||
const type = config && config.packer;
|
||||
EXT = DOM.getPackerExt(type);
|
||||
});
|
||||
async function config() {
|
||||
const [, config] = await tryToCatch(DOM.Files.get, 'config');
|
||||
const type = config?.packer;
|
||||
|
||||
EXT = DOM.getPackerExt(type);
|
||||
}
|
||||
|
||||
module.exports.initKeysPanel = () => {
|
||||
export const initKeysPanel = () => {
|
||||
const keysElement = DOM.getById('js-keyspanel');
|
||||
|
||||
if (!keysElement)
|
||||
return;
|
||||
|
||||
Events.addClick(keysElement, ({target}) => {
|
||||
const id = target.id;
|
||||
Events.addClick(keysElement, (event) => {
|
||||
const {target} = event;
|
||||
const {id} = target;
|
||||
|
||||
const operation = (name) => {
|
||||
const Operation = CloudCmd.Operation;
|
||||
const fn = Operation.show.bind(null, name);
|
||||
const {Operation} = CloudCmd;
|
||||
|
||||
return fn;
|
||||
return Operation.show.bind(null, name);
|
||||
};
|
||||
|
||||
const clickFuncs = {
|
||||
'f1' : CloudCmd.Help.show,
|
||||
'f2' : DOM.renameCurrent,
|
||||
'f3' : CloudCmd.View.show,
|
||||
'f4' : CloudCmd.EditFile.show,
|
||||
'f5' : operation('copy'),
|
||||
'f6' : operation('move'),
|
||||
'f7' : DOM.promptNewDir,
|
||||
'f8' : operation('delete'),
|
||||
'f9' : CloudCmd.Menu.show,
|
||||
'f10' : CloudCmd.Config.show,
|
||||
'~' : CloudCmd.Konsole.show,
|
||||
'shift~' : CloudCmd.Terminal.show,
|
||||
'contact' : CloudCmd.Contact.show,
|
||||
'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]);
|
||||
});
|
||||
};
|
||||
|
|
@ -146,10 +138,10 @@ const getPanel = (side) => {
|
|||
if (!itype.string(side))
|
||||
return side;
|
||||
|
||||
return DOM.getByDataName('js-' + side);
|
||||
return DOM.getByDataName(`js-${side}`);
|
||||
};
|
||||
|
||||
module.exports.setOnPanel = (side) => {
|
||||
export const setOnPanel = (side) => {
|
||||
const panel = getPanel(side);
|
||||
|
||||
const filesElement = DOM.getByDataName('js-files', panel);
|
||||
|
|
@ -165,61 +157,71 @@ function getPathListener(panel) {
|
|||
}
|
||||
|
||||
function isNoCurrent(panel) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const infoPanel = Info.panel;
|
||||
const namePanel = panel.getAttribute('data-name');
|
||||
const nameInfoPanel = infoPanel.getAttribute('data-name');
|
||||
|
||||
if (namePanel !== nameInfoPanel)
|
||||
|
||||
if (!infoPanel)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
const namePanel = panel.getAttribute('data-name');
|
||||
const nameInfoPanel = infoPanel.getAttribute('data-name');
|
||||
|
||||
return namePanel !== nameInfoPanel;
|
||||
}
|
||||
|
||||
function onPathElementClick(panel, event) {
|
||||
let link, href, url, noCurrent;
|
||||
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 prefix = CloudCmd.PREFIX;
|
||||
const element = event.target;
|
||||
const attr = element.getAttribute('data-name');
|
||||
const noCurrent = isNoCurrent(panel);
|
||||
|
||||
switch (attr) {
|
||||
case 'js-clear-storage':
|
||||
Storage.clear();
|
||||
break;
|
||||
if (attr === 'js-copy-path')
|
||||
return copyPath(element);
|
||||
|
||||
case 'js-refresh':
|
||||
noCurrent = isNoCurrent(panel);
|
||||
|
||||
CloudCmd.refresh({
|
||||
if (attr === 'js-refresh')
|
||||
return CloudCmd.refresh({
|
||||
panel,
|
||||
noCurrent
|
||||
noCurrent,
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case 'js-path-link':
|
||||
url = CloudCmd.HOST;
|
||||
href = element.href;
|
||||
link = href.replace(url, '');
|
||||
/**
|
||||
* browser doesn't replace % -> %25%
|
||||
* do it for him
|
||||
*/
|
||||
link = link.replace('%%', '%25%');
|
||||
link = decodeURI(link);
|
||||
link = link.replace(RegExp('^' + prefix + FS), '') || '/';
|
||||
|
||||
noCurrent = isNoCurrent(panel);
|
||||
|
||||
CloudCmd.loadDir({
|
||||
path : link,
|
||||
isRefresh : false,
|
||||
panel : noCurrent ? panel : Info.panel
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
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) {
|
||||
|
|
@ -231,30 +233,27 @@ function execIfNotUL(callback, event) {
|
|||
}
|
||||
|
||||
function onClick(event) {
|
||||
const ctrl = event.ctrlKey;
|
||||
|
||||
if (!ctrl)
|
||||
event.preventDefault();
|
||||
|
||||
event.preventDefault();
|
||||
changePanel(event.target);
|
||||
}
|
||||
|
||||
function toggleSelect(key, files) {
|
||||
const isMac = /Mac/.test(window.navigator.platform);
|
||||
const isMac = /Mac/.test(globalThis.navigator.platform);
|
||||
|
||||
if (!key)
|
||||
throw Error('key should not be undefined!');
|
||||
|
||||
const [file] = files;
|
||||
|
||||
if (isMac && key.meta || key.ctrl)
|
||||
if (isMac && key.meta)
|
||||
return DOM.toggleSelectedFile(file);
|
||||
|
||||
if (key.shift)
|
||||
return files.forEach(DOM.selectFile);
|
||||
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);
|
||||
|
|
@ -263,21 +262,20 @@ function changePanel(element) {
|
|||
DOM.changePanel();
|
||||
}
|
||||
|
||||
function onDblClick(event) {
|
||||
async function onDblClick(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const current = getLIElement(event.target);
|
||||
const isDir = DOM.isCurrentIsDir(current);
|
||||
const path = DOM.getCurrentPath(current);
|
||||
|
||||
if (isDir) {
|
||||
CloudCmd.loadDir({
|
||||
path: path === '/' ? '/' : path + '/'
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
if (!isDir)
|
||||
return CloudCmd.View.show();
|
||||
|
||||
await CloudCmd.changeDir(path);
|
||||
}
|
||||
|
||||
function onTouch(event) {
|
||||
async function onTouch(event) {
|
||||
const current = getLIElement(event.target);
|
||||
const isDir = DOM.isCurrentIsDir(current);
|
||||
|
||||
|
|
@ -289,11 +287,7 @@ function onTouch(event) {
|
|||
if (!isCurrent)
|
||||
return;
|
||||
|
||||
CloudCmd.loadDir({
|
||||
path: DOM.getCurrentPath(current)
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
await CloudCmd.changeDir(DOM.getCurrentPath(current));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -301,8 +295,10 @@ function onTouch(event) {
|
|||
* in Chrome (HTML5)
|
||||
*/
|
||||
function onDragStart(event) {
|
||||
const Info = DOM.CurrentInfo;
|
||||
const {prefixURL} = CloudCmd;
|
||||
const element = getLIElement(event.target);
|
||||
const isDir = Info.isDir;
|
||||
const {isDir} = Info;
|
||||
let link = DOM.getCurrentLink(element);
|
||||
let name = DOM.getCurrentName(element);
|
||||
|
||||
|
|
@ -311,13 +307,15 @@ function onDragStart(event) {
|
|||
name += EXT;
|
||||
link = document.createElement('a');
|
||||
link.textContent = name;
|
||||
link.href = apiURL + '/pack' + Info.path + EXT;
|
||||
link.href = prefixURL + '/pack' + Info.path + EXT;
|
||||
}
|
||||
|
||||
event.dataTransfer.setData('DownloadURL',
|
||||
'application/octet-stream' + ':' +
|
||||
name + ':' +
|
||||
link);
|
||||
event.dataTransfer.setData(
|
||||
'DownloadURL',
|
||||
'application/octet-stream' + ':' + name +
|
||||
':' +
|
||||
link,
|
||||
);
|
||||
}
|
||||
|
||||
function getLIElement(element) {
|
||||
|
|
@ -338,13 +336,14 @@ function getULElement(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
|
||||
shift: event.shiftKey,
|
||||
};
|
||||
|
||||
const element = getLIElement(event.target);
|
||||
|
|
@ -381,7 +380,7 @@ function contextMenu() {
|
|||
Events.addOnce('contextmenu', fm, (event) => {
|
||||
CloudCmd.Menu.show({
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
y: event.clientY,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -392,21 +391,17 @@ function contextMenu() {
|
|||
|
||||
function dragndrop() {
|
||||
const panels = DOM.getByClassAll('panel');
|
||||
const select = () => {
|
||||
[...panels].forEach((panel) => {
|
||||
panel.classList.add('selected-panel');
|
||||
});
|
||||
const select = ({target}) => {
|
||||
target.classList.add('selected-panel');
|
||||
};
|
||||
|
||||
const unselect = () => {
|
||||
[...panels].forEach((panel) => {
|
||||
panel.classList.remove('selected-panel');
|
||||
});
|
||||
const unselect = ({target}) => {
|
||||
target.classList.remove('selected-panel');
|
||||
};
|
||||
|
||||
const onDrop = (event) => {
|
||||
const files = event.dataTransfer.files;
|
||||
const items = event.dataTransfer.items;
|
||||
const {files, items} = event.dataTransfer;
|
||||
|
||||
const {length: filesCount} = files;
|
||||
|
||||
event.preventDefault();
|
||||
|
|
@ -415,22 +410,28 @@ function dragndrop() {
|
|||
return uploadFiles(files);
|
||||
|
||||
const isFile = (item) => item.kind === 'file';
|
||||
const dirFiles = [...items].filter(isFile);
|
||||
|
||||
const dirFiles = Array
|
||||
.from(items)
|
||||
.filter(isFile);
|
||||
|
||||
if (dirFiles.length)
|
||||
return DOM.uploadDirectory(dirFiles);
|
||||
|
||||
return CloudCmd.Operation.show('copy');
|
||||
const {Operation} = CloudCmd;
|
||||
const operation = event.shiftKey ? 'move' : 'copy';
|
||||
|
||||
return Operation.show(operation);
|
||||
};
|
||||
|
||||
/**
|
||||
* In Mac OS Chrome dropEffect = 'none'
|
||||
* 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.dataTransfer;
|
||||
const effectAllowed = dataTransfer.effectAllowed;
|
||||
const {dataTransfer} = event;
|
||||
const {effectAllowed} = dataTransfer;
|
||||
|
||||
if (/move|linkMove/.test(effectAllowed))
|
||||
dataTransfer.dropEffect = 'move';
|
||||
|
|
@ -440,19 +441,18 @@ function dragndrop() {
|
|||
event.preventDefault();
|
||||
};
|
||||
|
||||
Events.add('dragenter', select);
|
||||
Events.add(['dragleave', 'drop'], unselect);
|
||||
|
||||
[...panels].forEach((panel) => {
|
||||
Events.add('dragover', panel, onDragOver)
|
||||
.add('drop', panel, onDrop);
|
||||
});
|
||||
for (const panel of panels)
|
||||
Events
|
||||
.add('dragover', panel, onDragOver)
|
||||
.add('drop', panel, onDrop)
|
||||
.add('dragenter', select)
|
||||
.add(['dragleave', 'drop'], unselect);
|
||||
}
|
||||
|
||||
function unload() {
|
||||
DOM.Events.add(['unload', 'beforeunload'], (event) => {
|
||||
const Key = CloudCmd.Key;
|
||||
const isBind = Key && Key.isBind();
|
||||
Events.add(['unload', 'beforeunload'], (event) => {
|
||||
const {Key} = CloudCmd;
|
||||
const isBind = Key?.isBind();
|
||||
|
||||
if (isBind)
|
||||
return;
|
||||
|
|
@ -463,15 +463,15 @@ function unload() {
|
|||
}
|
||||
|
||||
function pop() {
|
||||
Events.add('popstate', (event) => {
|
||||
const path = event.state.replace(FS, '');
|
||||
Events.add('popstate', async ({state}) => {
|
||||
const path = (state || '').replace(FS, '');
|
||||
|
||||
if (!path)
|
||||
return CloudCmd.route(location.hash);
|
||||
|
||||
const history = false;
|
||||
CloudCmd.loadDir({
|
||||
path,
|
||||
|
||||
await CloudCmd.changeDir(path, {
|
||||
history,
|
||||
});
|
||||
});
|
||||
|
|
@ -479,7 +479,8 @@ function pop() {
|
|||
|
||||
function resize() {
|
||||
Events.add('resize', () => {
|
||||
const is = window.innerWidth < CloudCmd.MIN_ONE_PANEL_WIDTH;
|
||||
const Info = DOM.CurrentInfo;
|
||||
const is = globalThis.innerWidth < CloudCmd.MIN_ONE_PANEL_WIDTH;
|
||||
|
||||
if (!is)
|
||||
return;
|
||||
|
|
@ -499,4 +500,3 @@ function resize() {
|
|||
DOM.changePanel();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
56
client/load-module.js
Normal file
56
client/load-module.js
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);
|
||||
};
|
||||
};
|
||||
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